import { createContext, useContext, type JSX, type ReactNode } from "react"; import type { AgentActivityComposerSettingOption } from "@tutti-os/agent-activity-core"; import type { TuttiModePlanReviewSnapshot } from "./tuttiModePlan/tuttiModePlanPanelProjection"; export interface TuttiModePlanReviewUpdate { kind: "workflow_updated"; workspaceId: string; workflowId: string; sourceSessionId: string; checkpointId: string; changeKind: | "proposal_created" | "revision_created" | "checkpoint_decided" | "operation_updated"; } export interface TuttiModePlanReviewConnectionRestored { kind: "connection_restored"; workspaceId: string; } /** * A root turn of the source session settled. The host derives this from the * daemon's canonical turn fan-out and relays it as a read-repair trigger: a * plan proposed mid-turn announces itself through one workflow event, and if * that single event is lost no later workflow signal re-reads review state. */ export interface TuttiModePlanReviewSessionSettled { kind: "session_settled"; workspaceId: string; sourceSessionId: string; } /** Cached assignment catalogs changed for exact Agent Targets. */ export interface TuttiModePlanAssignmentOptionsInvalidated { kind: "assignment_options_invalidated"; workspaceId: string; agentTargetIds: readonly string[]; } export type TuttiModePlanReviewInvalidation = | TuttiModePlanReviewUpdate | TuttiModePlanReviewConnectionRestored | TuttiModePlanReviewSessionSettled | TuttiModePlanAssignmentOptionsInvalidated; export interface TuttiModePlanTaskAssignmentInput { taskId: string; agentTargetId?: string | null; modelPlanId?: string | null; model?: string | null; permissionModeId?: string | null; reasoningEffort?: string | null; parallelizable?: boolean | null; autoAccept?: boolean | null; } export interface TuttiModePlanReviewDecisionInput { workspaceId: string; workflowId: string; checkpointId: string; decision: "accepted" | "rejected" | "canceled"; decidedBy: string; reason?: string | null; /** Per-task overrides; only meaningful with an accepted task review. */ taskAssignments?: readonly TuttiModePlanTaskAssignmentInput[]; } export interface TuttiModePlanAssignmentAgentOption { agentTargetId: string; label: string; } export interface TuttiModePlanAssignmentAgentDetail { /** Provider-native models usable without a model plan. */ models: readonly AgentActivityComposerSettingOption[]; modelPlans: readonly { modelPlanId: string; label: string; models: readonly AgentActivityComposerSettingOption[]; }[]; permissionModes: readonly { id: string; label: string }[]; reasoningEfforts: readonly AgentActivityComposerSettingOption[]; } export interface TuttiPlanIssueTaskSnapshot { taskId: string; title: string; content: string; status: string; sortIndex: number; parallelizable: boolean; autoAccept: boolean; dependencyTaskIds: string[]; } /** Read-only snapshot of the Issue a session's accepted plan materialized. */ export interface TuttiPlanIssueSnapshot { workflowId: string; sourceTurnId: string | null; issueId: string; topicId: string; title: string; /** Durable Issue execution gate; paused graphs are not active work. */ dispatchPaused: boolean; tasks: TuttiPlanIssueTaskSnapshot[]; } /** * An accepted plan whose create_issue operation durably failed. The * conversation must surface this instead of rendering nothing: there is no * pending checkpoint (the review panel is gone) and no Issue (the issue panel * never appears), so silence would hide the failure entirely. */ export interface TuttiPlanIssueMaterializationFailure { workflowId: string; sourceTurnId: string | null; errorMessage: string | null; } export type TuttiPlanIssueQueryResult = | { kind: "issue"; issue: TuttiPlanIssueSnapshot } | ({ kind: "materialization_failed" } & TuttiPlanIssueMaterializationFailure) | null; /** * Source for the embedded plan-issue panel in the conversation. * The host resolves "the Issue this session's accepted plan created", relays * live issue updates, and exposes only daemon-owned product commands. Task * accept/rework actions remain source-Agent prompts rather than generic Issue * mutations. */ export interface TuttiPlanIssueSource { getSessionPlanIssue(input: { workspaceId: string; sourceSessionId: string; }): Promise; subscribeIssueUpdates( workspaceId: string, listener: (update: { issueId: string }) => void ): () => void; /** * Stops the Issue's execution: pauses future dispatch and cancels every * running task run. Idempotent; the daemon owns the cascade. */ cancelExecution(input: { workspaceId: string; issueId: string; }): Promise; /** * Resolves the delegate agent session that ran (or is running) a task's * latest run, so a task card can jump straight into that conversation. * Null when the task has not launched yet. */ resolveTaskSession(input: { workspaceId: string; issueId: string; taskId: string; }): Promise<{ agentSessionId: string } | null>; } /** * Option catalogs for per-task assignment editing. The desktop host reuses * its agent directory and composer capability catalogs; the panel never * hardcodes providers or modes. */ export interface TuttiModePlanAssignmentOptionsSource { /** * Returns the last successful workspace directory, including stale data. * Hosts may omit this when they do not retain a query cache. */ readAgents?(input: { workspaceId: string; }): readonly TuttiModePlanAssignmentAgentOption[] | null; listAgents(input: { workspaceId: string; }): Promise; /** * Returns the last successful target catalog, including stale data, so a * refresh never replaces usable selectors with a loading placeholder. */ readAgentOptions?(input: { workspaceId: string; agentTargetId: string; }): TuttiModePlanAssignmentAgentDetail | null; loadAgentOptions(input: { workspaceId: string; agentTargetId: string; }): Promise; } export interface TuttiModePlanReviewRuntime { listPending(input: { workspaceId: string; sourceSessionId: string; }): Promise; decide(input: TuttiModePlanReviewDecisionInput): Promise; subscribe( workspaceId: string, listener: (update: TuttiModePlanReviewInvalidation) => void ): () => void; /** Optional; the panel degrades to read-only assignment display without it. */ assignmentOptions?: TuttiModePlanAssignmentOptionsSource; /** Optional; without it the embedded plan-issue panel never renders. */ planIssues?: TuttiPlanIssueSource; } const TuttiModePlanReviewRuntimeContext = createContext(null); export function TuttiModePlanReviewRuntimeProvider({ children, runtime }: { children: ReactNode; runtime?: TuttiModePlanReviewRuntime | null; }): JSX.Element { return ( {children} ); } export function useOptionalTuttiModePlanReviewRuntime(): TuttiModePlanReviewRuntime | null { return useContext(TuttiModePlanReviewRuntimeContext); }