Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

586 lines
19 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useOptionalTuttiModePlanReviewRuntime } from "../workspaceWorkflowRuntime";
import type {
TuttiModePlanAssignmentAgentDetail,
TuttiModePlanAssignmentAgentOption,
TuttiModePlanTaskAssignmentInput
} from "../workspaceWorkflowRuntime";
import type {
TuttiPlanIssueMaterializationFailure,
TuttiPlanIssueQueryResult,
TuttiPlanIssueSnapshot
} from "../workspaceWorkflowRuntime";
import {
projectTuttiModePlanPanel,
type TuttiModePlanPanelViewModel,
type TuttiModePlanReviewSnapshot
} from "./tuttiModePlanPanelProjection";
interface PanelState {
error: unknown;
loading: boolean;
scopeKey: string;
snapshots: readonly TuttiModePlanReviewSnapshot[];
submittingCheckpointId: string | null;
}
interface PlanIssueState {
result: TuttiPlanIssueQueryResult;
scopeKey: string;
}
interface AssignmentCatalogState {
agents: readonly TuttiModePlanAssignmentAgentOption[] | null;
optionsByAgentId: Readonly<
Record<string, TuttiModePlanAssignmentAgentDetail>
>;
scopeKey: string;
}
export interface TuttiModePlanAssignmentCatalog {
/** Null until loaded; empty array is a real "no agents" answer. */
agents: readonly TuttiModePlanAssignmentAgentOption[] | null;
optionsByAgentId: Readonly<
Record<string, TuttiModePlanAssignmentAgentDetail>
>;
loadAgentOptions(agentTargetId: string): void;
}
function emptyState(scopeKey: string): PanelState {
return {
error: null,
loading: false,
scopeKey,
snapshots: [],
submittingCheckpointId: null
};
}
function emptyAssignmentState(scopeKey: string): AssignmentCatalogState {
return { agents: null, optionsByAgentId: {}, scopeKey };
}
const EMPTY_AGENT_DETAIL: TuttiModePlanAssignmentAgentDetail = {
models: [],
modelPlans: [],
permissionModes: [],
reasoningEfforts: []
};
const TERMINAL_PLAN_ISSUE_TASK_STATUSES = new Set([
"completed",
"failed",
"canceled"
]);
export function tuttiPlanIssueExecutionIsActive(
issue: TuttiPlanIssueSnapshot | null
): boolean {
return Boolean(
issue &&
!issue.dispatchPaused &&
issue.tasks.some(
(task) =>
!TERMINAL_PLAN_ISSUE_TASK_STATUSES.has(task.status.trim().toLowerCase())
)
);
}
export function useTuttiModePlanPanels(input: {
decidedBy: string;
/** Passive previews keep the hook mounted without starting transport work. */
enabled?: boolean;
sourceSessionId: string | null;
workspaceId: string;
}): {
assignmentCatalog: TuttiModePlanAssignmentCatalog;
decide(input: {
checkpointId: string;
decision: "accepted" | "rejected" | "canceled";
reason?: string | null;
taskAssignments?: readonly TuttiModePlanTaskAssignmentInput[];
workflowId: string;
}): Promise<void>;
error: unknown;
loading: boolean;
panels: readonly TuttiModePlanPanelViewModel[];
/** Live snapshot of the Issue this session's accepted plan materialized. */
planIssue: TuttiPlanIssueSnapshot | null;
/** The accepted plan's durable create_issue failure, when no Issue exists. */
planIssueMaterializationFailure: TuttiPlanIssueMaterializationFailure | null;
/** Stop the plan issue's execution (pause + cancel runs); null until loaded. */
cancelPlanIssueExecution: (() => Promise<void>) | null;
/** Resolve a task's delegate session for jump-to-conversation; null until loaded. */
resolvePlanIssueTaskSession:
| ((taskId: string) => Promise<{ agentSessionId: string } | null>)
| null;
retry(): void;
submittingCheckpointId: string | null;
} {
const runtime = useOptionalTuttiModePlanReviewRuntime();
const [state, setState] = useState<PanelState>(() => emptyState(""));
const [assignmentState, setAssignmentState] =
useState<AssignmentCatalogState>(() => emptyAssignmentState(""));
const [planIssueState, setPlanIssueState] = useState<PlanIssueState>({
result: null,
scopeKey: ""
});
const requestSequenceRef = useRef(0);
const [retrySequence, setRetrySequence] = useState(0);
const enabled = input.enabled ?? true;
const workspaceId = input.workspaceId.trim();
const sourceSessionId = input.sourceSessionId?.trim() ?? "";
const scopeKey =
enabled && runtime && workspaceId && sourceSessionId
? JSON.stringify([workspaceId, sourceSessionId])
: "";
const activeScopeRef = useRef("");
const assignmentRequestsRef = useRef(new Set<string>());
const planIssueRefreshRef = useRef<(() => void) | null>(null);
const assignmentSource = runtime?.assignmentOptions ?? null;
const loadAgentOptions = useCallback(
(agentTargetId: string): void => {
const capturedScope = scopeKey;
const trimmed = agentTargetId.trim();
if (
!capturedScope ||
!assignmentSource ||
!trimmed ||
assignmentRequestsRef.current.has(trimmed)
) {
return;
}
assignmentRequestsRef.current.add(trimmed);
const cached = assignmentSource.readAgentOptions?.({
workspaceId,
agentTargetId: trimmed
});
if (cached) {
setAssignmentState((current) =>
current.scopeKey === capturedScope
? {
...current,
optionsByAgentId: {
...current.optionsByAgentId,
[trimmed]: cached
}
}
: current
);
}
void assignmentSource
.loadAgentOptions({ workspaceId, agentTargetId: trimmed })
.then((options) => {
if (activeScopeRef.current !== capturedScope) return;
setAssignmentState((current) =>
current.scopeKey === capturedScope
? {
...current,
optionsByAgentId: {
...current.optionsByAgentId,
[trimmed]: options
}
}
: current
);
})
.catch(() => {
// Allow a later explicit agent re-selection to retry the load, but
// settle the entry with an empty catalog so selectors degrade to
// the current values instead of wedging on the loading placeholder.
assignmentRequestsRef.current.delete(trimmed);
if (activeScopeRef.current !== capturedScope) return;
setAssignmentState((current) =>
current.scopeKey === capturedScope &&
current.optionsByAgentId[trimmed] === undefined
? {
...current,
optionsByAgentId: {
...current.optionsByAgentId,
[trimmed]: EMPTY_AGENT_DETAIL
}
}
: current
);
});
},
[assignmentSource, scopeKey, workspaceId]
);
// Catalog loading piggybacks on snapshot refreshes instead of adding a
// component effect: every successful listPending with pending work triggers
// the (deduplicated) agent-directory load plus a preload of the option
// catalogs for agents already referenced by the plan document.
const ensureAssignmentCatalog = useCallback(
(
capturedScope: string,
snapshots: readonly TuttiModePlanReviewSnapshot[]
): void => {
if (!capturedScope || !assignmentSource || snapshots.length === 0) {
return;
}
if (!assignmentRequestsRef.current.has("__agents__")) {
assignmentRequestsRef.current.add("__agents__");
const cached = assignmentSource.readAgents?.({ workspaceId });
if (cached) {
setAssignmentState((current) =>
current.scopeKey === capturedScope
? { ...current, agents: [...cached] }
: current
);
}
void assignmentSource
.listAgents({ workspaceId })
.then((agents) => {
if (activeScopeRef.current !== capturedScope) return;
setAssignmentState((current) =>
current.scopeKey === capturedScope
? { ...current, agents: [...agents] }
: current
);
})
.catch(() => {
assignmentRequestsRef.current.delete("__agents__");
});
}
for (const snapshot of snapshots) {
const revision = snapshot.revisions.find(
(candidate) => candidate.id === snapshot.workflow.currentRevisionId
);
for (const task of revision?.document.tasks ?? []) {
if (task.agentTargetId?.trim()) {
loadAgentOptions(task.agentTargetId);
}
}
}
},
[assignmentSource, loadAgentOptions, workspaceId]
);
const refresh = useCallback(async (): Promise<void> => {
const capturedScope = scopeKey;
if (activeScopeRef.current !== capturedScope) return;
if (!enabled || !runtime || !workspaceId || !sourceSessionId) {
requestSequenceRef.current += 1;
setState(emptyState(capturedScope));
return;
}
const sequence = ++requestSequenceRef.current;
setState((current) =>
current.scopeKey === capturedScope
? { ...current, error: null, loading: true }
: { ...emptyState(capturedScope), loading: true }
);
try {
const snapshots = await runtime.listPending({
workspaceId,
sourceSessionId
});
if (
requestSequenceRef.current !== sequence ||
activeScopeRef.current !== capturedScope
) {
return;
}
setState((current) => ({
...current,
error: null,
loading: false,
scopeKey: capturedScope,
snapshots: [...snapshots]
}));
ensureAssignmentCatalog(capturedScope, snapshots);
} catch (error) {
if (
requestSequenceRef.current !== sequence ||
activeScopeRef.current !== capturedScope
) {
return;
}
setState((current) => ({
...current,
error,
loading: false,
scopeKey: capturedScope,
snapshots: []
}));
}
}, [
enabled,
ensureAssignmentCatalog,
runtime,
scopeKey,
sourceSessionId,
workspaceId
]);
const planIssueSource = runtime?.planIssues ?? null;
useEffect(() => {
requestSequenceRef.current += 1;
activeScopeRef.current = scopeKey;
assignmentRequestsRef.current = new Set<string>();
setAssignmentState(emptyAssignmentState(scopeKey));
setPlanIssueState({ result: null, scopeKey });
void refresh();
const capturedScope = scopeKey;
// The materialized plan Issue shares this scope's lifecycle: load with the
// review snapshots and refresh on workspace issue events, with a trailing
// re-read so the view never settles on a snapshot older than the last one.
let planIssueInFlight = false;
let planIssueTrailing = false;
const refreshPlanIssue = (): void => {
if (!capturedScope || !planIssueSource) return;
if (planIssueInFlight) {
planIssueTrailing = true;
return;
}
planIssueInFlight = true;
void planIssueSource
.getSessionPlanIssue({ workspaceId, sourceSessionId })
.then((result) => {
if (activeScopeRef.current !== capturedScope) return;
setPlanIssueState((current) =>
current.scopeKey === capturedScope
? { result, scopeKey: capturedScope }
: current
);
})
.catch(() => {
// Best-effort read model; a later update event retries.
})
.finally(() => {
planIssueInFlight = false;
if (planIssueTrailing && activeScopeRef.current === capturedScope) {
planIssueTrailing = false;
refreshPlanIssue();
}
});
};
refreshPlanIssue();
planIssueRefreshRef.current = refreshPlanIssue;
const unsubscribe =
enabled && runtime && workspaceId && sourceSessionId
? runtime.subscribe(workspaceId, (update) => {
if (update.kind === "assignment_options_invalidated") {
for (const agentTargetId of update.agentTargetIds) {
const normalizedAgentTargetId = agentTargetId.trim();
if (
normalizedAgentTargetId &&
assignmentRequestsRef.current.delete(normalizedAgentTargetId)
) {
loadAgentOptions(normalizedAgentTargetId);
}
}
return;
}
if (
update.kind === "connection_restored" ||
update.sourceSessionId === sourceSessionId
) {
void refresh();
// The workflow's operation_updated event is the authoritative
// "Issue materialized" signal: workspace.issue.updated fires
// during materialization, before the create_issue operation
// records its outcome, so a re-read triggered only by the issue
// event reliably finds no succeeded operation and leaves the
// panel invisible until a remount.
refreshPlanIssue();
}
})
: undefined;
const unsubscribePlanIssue =
capturedScope && planIssueSource
? planIssueSource.subscribeIssueUpdates(workspaceId, () => {
refreshPlanIssue();
})
: undefined;
return () => {
planIssueRefreshRef.current = null;
unsubscribe?.();
unsubscribePlanIssue?.();
if (activeScopeRef.current === scopeKey) {
activeScopeRef.current = "";
}
requestSequenceRef.current += 1;
};
}, [
enabled,
planIssueSource,
refresh,
retrySequence,
runtime,
scopeKey,
sourceSessionId,
workspaceId
]);
const decide = useCallback(
async (decision: {
checkpointId: string;
decision: "accepted" | "rejected" | "canceled";
reason?: string | null;
taskAssignments?: readonly TuttiModePlanTaskAssignmentInput[];
workflowId: string;
}): Promise<void> => {
const capturedScope = scopeKey;
if (
!runtime ||
!capturedScope ||
activeScopeRef.current !== capturedScope
)
return;
setState((current) => ({
...(current.scopeKey === capturedScope
? current
: emptyState(capturedScope)),
error: null,
submittingCheckpointId: decision.checkpointId,
scopeKey: capturedScope
}));
try {
await runtime.decide({
workspaceId,
workflowId: decision.workflowId,
checkpointId: decision.checkpointId,
decision: decision.decision,
decidedBy: input.decidedBy,
reason: decision.reason,
taskAssignments: decision.taskAssignments
});
if (activeScopeRef.current !== capturedScope) return;
await refresh();
// The decide response returns after the daemon completed the
// create_issue operation, so this read observes the materialized
// Issue immediately — the accepted review panel hands over to the
// issue panel in place without waiting for an event round-trip.
planIssueRefreshRef.current?.();
} catch (error) {
if (activeScopeRef.current === capturedScope) {
setState((current) => ({ ...current, error }));
}
} finally {
if (activeScopeRef.current === capturedScope) {
setState((current) => ({
...current,
submittingCheckpointId: null
}));
}
}
},
[input.decidedBy, refresh, runtime, scopeKey, workspaceId]
);
const visibleState =
state.scopeKey === scopeKey ? state : emptyState(scopeKey);
const panels = useMemo(
() =>
visibleState.snapshots
.map(projectTuttiModePlanPanel)
.filter((panel): panel is TuttiModePlanPanelViewModel => panel !== null)
.sort(
(left, right) =>
left.checkpoint.createdAtUnixMs - right.checkpoint.createdAtUnixMs
),
[visibleState.snapshots]
);
const visiblePlanIssueResult =
planIssueState.scopeKey === scopeKey ? planIssueState.result : null;
const visiblePlanIssue =
visiblePlanIssueResult?.kind === "issue"
? visiblePlanIssueResult.issue
: null;
const visiblePlanIssueFailure = useMemo(
() =>
visiblePlanIssueResult?.kind === "materialization_failed"
? {
workflowId: visiblePlanIssueResult.workflowId,
sourceTurnId: visiblePlanIssueResult.sourceTurnId,
errorMessage: visiblePlanIssueResult.errorMessage
}
: null,
[visiblePlanIssueResult]
);
const planIssueId = visiblePlanIssue?.issueId ?? "";
const cancelPlanIssueExecutionAction =
useCallback(async (): Promise<void> => {
if (!planIssueSource || !planIssueId) return;
// Stop is one daemon-owned cascade: pause dispatch, cancel every running
// run. The issue-updated event refreshes the embed.
await planIssueSource.cancelExecution({
workspaceId,
issueId: planIssueId
});
}, [planIssueId, planIssueSource, workspaceId]);
const resolvePlanIssueTaskSessionAction = useCallback(
(taskId: string): Promise<{ agentSessionId: string } | null> => {
if (!planIssueSource || !planIssueId) return Promise.resolve(null);
return planIssueSource.resolveTaskSession({
workspaceId,
issueId: planIssueId,
taskId
});
},
[planIssueId, planIssueSource, workspaceId]
);
const cancelPlanIssueExecution =
planIssueSource && planIssueId ? cancelPlanIssueExecutionAction : null;
const resolvePlanIssueTaskSession =
planIssueSource && planIssueId ? resolvePlanIssueTaskSessionAction : null;
const visibleAssignmentState =
assignmentState.scopeKey === scopeKey
? assignmentState
: emptyAssignmentState(scopeKey);
const assignmentCatalog = useMemo(
() => ({
agents: assignmentSource ? visibleAssignmentState.agents : null,
optionsByAgentId: visibleAssignmentState.optionsByAgentId,
loadAgentOptions
}),
[
assignmentSource,
loadAgentOptions,
visibleAssignmentState.agents,
visibleAssignmentState.optionsByAgentId
]
);
const retry = useCallback(
() => setRetrySequence((current) => current + 1),
[]
);
return useMemo(
() => ({
assignmentCatalog,
decide,
error: visibleState.error,
loading: visibleState.loading,
panels,
planIssue: visiblePlanIssue,
planIssueMaterializationFailure: visiblePlanIssueFailure,
cancelPlanIssueExecution,
resolvePlanIssueTaskSession,
retry,
submittingCheckpointId: visibleState.submittingCheckpointId
}),
[
assignmentCatalog,
cancelPlanIssueExecution,
decide,
panels,
resolvePlanIssueTaskSession,
retry,
visiblePlanIssue,
visiblePlanIssueFailure,
visibleState.error,
visibleState.loading,
visibleState.submittingCheckpointId
]
);
}