@xfey/tutti 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/approvals/projections.d.ts +0 -1
  2. package/dist/approvals/projections.js +0 -1
  3. package/dist/artifacts/candidate-validator.js +3 -3
  4. package/dist/artifacts/manifest.d.ts +2 -0
  5. package/dist/artifacts/manifest.js +9 -2
  6. package/dist/artifacts/preview-runtime.js +1 -0
  7. package/dist/collaboration-state/index.d.ts +1 -1
  8. package/dist/collaboration-state/index.js +1 -1
  9. package/dist/collaboration-state/recovery.d.ts +2 -1
  10. package/dist/collaboration-state/recovery.js +16 -0
  11. package/dist/collaboration-state/types.d.ts +5 -0
  12. package/dist/control-plane/follow-up-run.js +0 -3
  13. package/dist/control-plane/formatters.d.ts +2 -4
  14. package/dist/control-plane/formatters.js +5 -11
  15. package/dist/control-plane/index.d.ts +1 -1
  16. package/dist/control-plane/run-result-recording.d.ts +2 -9
  17. package/dist/control-plane/run-result-recording.js +6 -24
  18. package/dist/control-plane/run-scheduler.js +0 -3
  19. package/dist/control-plane/run-start.js +1 -8
  20. package/dist/control-plane/startup-recovery.d.ts +1 -1
  21. package/dist/control-plane/startup-recovery.js +90 -23
  22. package/dist/control-plane/types.d.ts +4 -17
  23. package/dist/providers/openai/app-server/workspace-write-run.d.ts +0 -2
  24. package/dist/providers/openai/app-server/workspace-write-run.js +0 -1
  25. package/dist/run-pipeline/candidate-diff.js +1 -7
  26. package/dist/run-pipeline/openai.d.ts +0 -4
  27. package/dist/run-pipeline/openai.js +42 -148
  28. package/dist/run-pipeline/task-run-invocation.d.ts +1 -2
  29. package/dist/run-pipeline/task-run-invocation.js +2 -4
  30. package/dist/server-shell/cli/host-server-runtime.js +3 -10
  31. package/dist/store/lifecycle.d.ts +1 -0
  32. package/dist/store/lifecycle.js +8 -6
  33. package/dist/workspace-ops/index.d.ts +3 -2
  34. package/dist/workspace-ops/index.js +2 -1
  35. package/dist/workspace-ops/mainline.d.ts +1 -1
  36. package/dist/workspace-ops/mainline.js +1 -5
  37. package/dist/workspace-ops/project-docs.js +26 -15
  38. package/dist/workspace-ops/project-workspace.d.ts +12 -0
  39. package/dist/workspace-ops/project-workspace.js +257 -0
  40. package/dist/workspace-ops/reference-files.js +9 -9
  41. package/dist/workspace-ops/reference-summaries.js +9 -7
  42. package/dist/workspace-ops/system-commits.d.ts +7 -0
  43. package/dist/workspace-ops/system-commits.js +43 -0
  44. package/dist/workspace-ops/types.d.ts +22 -41
  45. package/package.json +1 -1
  46. package/prompts/README.md +2 -2
  47. package/prompts/prompt-flow-experiment-plan.md +5 -6
  48. package/prompts/runs/README.md +5 -3
  49. package/prompts/runs/task-continuation.md +6 -4
  50. package/prompts/runs/task-retry.md +11 -10
  51. package/prompts/runs/task-run.md +6 -4
  52. package/web/assets/{homepage-motion-scene-CKLsNPVv.js → homepage-motion-scene-CV9ceFzS.js} +1 -1
  53. package/web/assets/index-CxVBmEoi.js +70 -0
  54. package/web/assets/{index-DQogRzu_.css → index-DNnf10zz.css} +1 -1
  55. package/web/index.html +2 -2
  56. package/dist/run-pipeline/promotion-reconcile.d.ts +0 -21
  57. package/dist/run-pipeline/promotion-reconcile.js +0 -195
  58. package/dist/workspace-ops/run-workspaces.d.ts +0 -19
  59. package/dist/workspace-ops/run-workspaces.js +0 -302
  60. package/web/assets/index-CkXOC6nC.js +0 -69
@@ -24,7 +24,6 @@ export type ApprovalProjectionContext = {
24
24
  requestedAt: string;
25
25
  expiresAt?: string;
26
26
  workspaceRoot?: string;
27
- runWorkspaceRoot?: string;
28
27
  codexHome?: string;
29
28
  tuttiHome?: string;
30
29
  builtInSkillsRoot?: string;
@@ -22,7 +22,6 @@ function underRoot(path, root) {
22
22
  }
23
23
  function runtimeRoots(context) {
24
24
  return [
25
- { value: context.runWorkspaceRoot ?? "", label: "run workspace" },
26
25
  { value: context.workspaceRoot ?? "", label: "workspace" },
27
26
  { value: context.codexHome ?? "", label: "codex home" },
28
27
  { value: context.tuttiHome ?? "", label: "tutti home" },
@@ -1,6 +1,6 @@
1
1
  import { lstatSync, readFileSync, realpathSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
- import { fingerprintWorkspaceCandidate } from "../workspace-ops/index.js";
3
+ import { fingerprintProjectSourceState } from "../workspace-ops/index.js";
4
4
  import { ARTIFACT_MANIFEST_PATH, MAX_ARTIFACT_MANIFEST_BYTES, parseArtifactManifestContract, } from "./manifest-contract.js";
5
5
  import { allocateArtifactPreviewPort, ArtifactDiagnosticBuffer, cleanupArtifactProcessRuntime, createArtifactPreviewEnv, createArtifactProcessRuntime, spawnArtifactPreviewProcess, terminateArtifactProcessTree, } from "./process-boundary.js";
6
6
  const MAX_PACKAGE_JSON_BYTES = 1024 * 1024;
@@ -290,7 +290,7 @@ async function validateServerCandidate(repoRealPath, contract, options) {
290
290
  sensitive: { repoRoot: repoRealPath, extra: [cwd.path] },
291
291
  });
292
292
  }
293
- const fingerprint = options.fingerprintCandidate ?? fingerprintWorkspaceCandidate;
293
+ const fingerprint = options.fingerprintCandidate ?? fingerprintProjectSourceState;
294
294
  let beforeFingerprint;
295
295
  try {
296
296
  beforeFingerprint = fingerprint(repoRealPath);
@@ -299,7 +299,7 @@ async function validateServerCandidate(repoRealPath, contract, options) {
299
299
  return failure({
300
300
  reasonCode: "artifact_preview_modified_candidate",
301
301
  summary: "Tutti could not capture the candidate state before preview validation.",
302
- guidance: "Keep the candidate repository in a valid Git worktree and retry.",
302
+ guidance: "Keep the candidate project directory in a valid Git repository and retry.",
303
303
  source: "candidate_diff",
304
304
  detail: error instanceof Error ? error.message : "Candidate state capture failed",
305
305
  sensitive: { repoRoot: repoRealPath },
@@ -21,6 +21,7 @@ export type ArtifactDeclaration = {
21
21
  status: "declared";
22
22
  manifest: ArtifactManifest;
23
23
  manifest_blob_oid: string;
24
+ snapshot_oid: string;
24
25
  runtime: DeclaredArtifactRuntime;
25
26
  projection: ArtifactDeclaredProjection;
26
27
  };
@@ -33,6 +34,7 @@ export declare function buildArtifactPreviewUrl(options: {
33
34
  basePath?: string | undefined;
34
35
  entryPath: string;
35
36
  manifestBlobOid: string;
37
+ snapshotOid?: string;
36
38
  }): string;
37
39
  export declare function readArtifactDeclaration(options: ReadArtifactDeclarationOptions): ArtifactDeclaration;
38
40
  export declare function readArtifactProjection(options: ReadArtifactDeclarationOptions): GetArtifactsResponse;
@@ -56,7 +56,11 @@ export function buildArtifactPreviewUrl(options) {
56
56
  const basePath = (options.basePath ?? ARTIFACT_PREVIEW_BASE_PATH).replace(/\/+$/u, "");
57
57
  const encodedEntryPath = encodePreviewPath(options.entryPath);
58
58
  const path = encodedEntryPath.length === 0 ? basePath : `${basePath}/${encodedEntryPath}`;
59
- return `${path}?v=${encodeURIComponent(options.manifestBlobOid)}`;
59
+ const query = new URLSearchParams({ v: options.manifestBlobOid });
60
+ if (options.snapshotOid !== undefined) {
61
+ query.set("s", options.snapshotOid);
62
+ }
63
+ return `${path}?${query.toString()}`;
60
64
  }
61
65
  function createDeclaredProjection(options) {
62
66
  const artifact = options.manifest.artifact;
@@ -66,6 +70,7 @@ function createDeclaredProjection(options) {
66
70
  url: buildArtifactPreviewUrl({
67
71
  entryPath: options.runtime.entry_path,
68
72
  manifestBlobOid: options.manifestBlobOid,
73
+ snapshotOid: options.snapshotOid,
69
74
  ...(options.previewBasePath === undefined ? {} : { basePath: options.previewBasePath }),
70
75
  }),
71
76
  }
@@ -113,7 +118,7 @@ function validateCommittedArtifact(options) {
113
118
  return runtime;
114
119
  }
115
120
  export function readArtifactDeclaration(options) {
116
- readViewerSnapshot(options);
121
+ const snapshot = readViewerSnapshot(options);
117
122
  const manifestEntry = lsPath(options.workspaceRoot, ARTIFACT_MANIFEST_PATH);
118
123
  if (manifestEntry === null) {
119
124
  return {
@@ -143,10 +148,12 @@ export function readArtifactDeclaration(options) {
143
148
  status: "declared",
144
149
  manifest: parsed.declaration.manifest,
145
150
  manifest_blob_oid: fileEntry.oid,
151
+ snapshot_oid: snapshot.head.oid,
146
152
  runtime,
147
153
  projection: createDeclaredProjection({
148
154
  manifest: parsed.declaration.manifest,
149
155
  manifestBlobOid: fileEntry.oid,
156
+ snapshotOid: snapshot.head.oid,
150
157
  runtime,
151
158
  ...(options.previewBasePath === undefined
152
159
  ? {}
@@ -49,6 +49,7 @@ function isServerDeclaration(declaration) {
49
49
  }
50
50
  function serverDeclarationKey(declaration) {
51
51
  return [
52
+ declaration.snapshot_oid,
52
53
  declaration.manifest_blob_oid,
53
54
  declaration.runtime.cwd_path,
54
55
  declaration.runtime.entry_path,
@@ -9,7 +9,7 @@ export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRo
9
9
  export { closeTaskBoundClarificationAsRetaskingRequired, materializeClarificationSuccessor, materializeTaskBoundRunSuccessor, submitClarificationRound, } from "./clarification-successors.js";
10
10
  export { readRecoverySummariesPage, readRecoverySummaryProjection, readRunResultProjectionByActivityRef, readTaskRunResultsPage, recordRecoverySummary, } from "./run-results.js";
11
11
  export { recordRunNeedsHumanResult, recordRunResult } from "./run-recording.js";
12
- export { recoverStaleRunningTasksAsFailed } from "./recovery.js";
12
+ export { readStaleRunningTaskIdentities, recoverStaleRunningTasksAsFailed, } from "./recovery.js";
13
13
  export { markReadStateScope, readReadStateSummary, recordReadNotificationEvent, } from "./read-state.js";
14
14
  export { readTaskDetailProjection } from "./task-details.js";
15
15
  export { applyTaskCompileProposal, markTaskRunStarted, readNextRunnableTask, readRecoverableScheduledRun, readCompiledTaskSummariesByWorkflowRef, readTaskProjection, readWorklistProjection, } from "./worklist.js";
@@ -9,7 +9,7 @@ export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRo
9
9
  export { closeTaskBoundClarificationAsRetaskingRequired, materializeClarificationSuccessor, materializeTaskBoundRunSuccessor, submitClarificationRound, } from "./clarification-successors.js";
10
10
  export { readRecoverySummariesPage, readRecoverySummaryProjection, readRunResultProjectionByActivityRef, readTaskRunResultsPage, recordRecoverySummary, } from "./run-results.js";
11
11
  export { recordRunNeedsHumanResult, recordRunResult } from "./run-recording.js";
12
- export { recoverStaleRunningTasksAsFailed } from "./recovery.js";
12
+ export { readStaleRunningTaskIdentities, recoverStaleRunningTasksAsFailed, } from "./recovery.js";
13
13
  export { markReadStateScope, readReadStateSummary, recordReadNotificationEvent, } from "./read-state.js";
14
14
  export { readTaskDetailProjection } from "./task-details.js";
15
15
  export { applyTaskCompileProposal, markTaskRunStarted, readNextRunnableTask, readRecoverableScheduledRun, readCompiledTaskSummariesByWorkflowRef, readTaskProjection, readWorklistProjection, } from "./worklist.js";
@@ -1,5 +1,6 @@
1
1
  import { type SqliteDatabase } from "../store/index.js";
2
- import type { RecoverStaleRunningTasksResult } from "./types.js";
2
+ import type { RecoverStaleRunningTasksResult, StaleRunningTaskIdentity } from "./types.js";
3
+ export declare function readStaleRunningTaskIdentities(db: SqliteDatabase): StaleRunningTaskIdentity[];
3
4
  export declare function recoverStaleRunningTasksAsFailed(db: SqliteDatabase, input?: {
4
5
  now?: () => Date;
5
6
  }): RecoverStaleRunningTasksResult;
@@ -27,6 +27,22 @@ function inferRecoveryRunLineage(tx, taskRow) {
27
27
  root_activity_ref: parentLineage.root_activity_ref ?? latestRunResult.activity_ref,
28
28
  };
29
29
  }
30
+ export function readStaleRunningTaskIdentities(db) {
31
+ const staleTasks = db
32
+ .prepare(`
33
+ SELECT *
34
+ FROM tasks
35
+ WHERE status = 'running'
36
+ AND current_activity_ref IS NOT NULL
37
+ ORDER BY updated_at ASC, id ASC
38
+ `)
39
+ .all();
40
+ return staleTasks.map((taskRow) => ({
41
+ task_id: taskRow.id,
42
+ activity_ref: taskRow.current_activity_ref,
43
+ run_lineage: inferRecoveryRunLineage(db, taskRow),
44
+ }));
45
+ }
30
46
  export function recoverStaleRunningTasksAsFailed(db, input = {}) {
31
47
  return withHostStoreTransaction(db, (tx) => {
32
48
  const recoveredAt = nowIso(input.now);
@@ -176,6 +176,11 @@ export type RecoverStaleRunningTasksResult = {
176
176
  task_detail: TaskDetailProjection;
177
177
  }>;
178
178
  };
179
+ export type StaleRunningTaskIdentity = {
180
+ task_id: TaskId;
181
+ activity_ref: ActivityRef;
182
+ run_lineage: RunLineage;
183
+ };
179
184
  export type RecordRecoverySummaryInput = {
180
185
  summary: string;
181
186
  affected_activities: ActivityRef[];
@@ -8,9 +8,6 @@ export function startRunFromFollowUp(input) {
8
8
  if (pipelineResolution === undefined || pipelineResolution.kind !== "ready") {
9
9
  return;
10
10
  }
11
- if (input.readMainlineStatus !== undefined && !input.readMainlineStatus().mainline_clean) {
12
- return;
13
- }
14
11
  const latestRunResult = readTaskRunResultsPage(input.store.db, input.taskId, {
15
12
  limit: 1,
16
13
  })?.items[0];
@@ -1,12 +1,10 @@
1
1
  import type { ActivityProjection } from "@tutti/shared/schemas/api";
2
2
  export declare function defaultProcedureFailureSystemMessage(activity: ActivityProjection): string;
3
3
  export declare function formatStartupRecoverySummary(input: {
4
- recoveredRunCount: number;
4
+ interruptedRunCount: number;
5
+ acceptedCheckpointRunCount: number;
5
6
  replayedRoundCount: number;
6
7
  interruptedCommandCount: number;
7
- removedRunWorkspaceCount: number;
8
- removedTempBranchCount: number;
9
- cleanupWarningCount: number;
10
8
  staleReplayHintCount: number;
11
9
  }): string;
12
10
  //# sourceMappingURL=formatters.d.ts.map
@@ -19,8 +19,11 @@ function countNoun(count, singular, plural = `${singular}s`) {
19
19
  }
20
20
  export function formatStartupRecoverySummary(input) {
21
21
  const parts = [];
22
- if (input.recoveredRunCount > 0) {
23
- parts.push(`${countNoun(input.recoveredRunCount, "stale Run")} marked failed`);
22
+ if (input.acceptedCheckpointRunCount > 0) {
23
+ parts.push(`${countNoun(input.acceptedCheckpointRunCount, "accepted Run checkpoint")} restored`);
24
+ }
25
+ if (input.interruptedRunCount > 0) {
26
+ parts.push(`${countNoun(input.interruptedRunCount, "stale Run")} marked failed`);
24
27
  }
25
28
  if (input.replayedRoundCount > 0) {
26
29
  parts.push(`${countNoun(input.replayedRoundCount, "sealed clarification round")} replayed`);
@@ -28,18 +31,9 @@ export function formatStartupRecoverySummary(input) {
28
31
  if (input.interruptedCommandCount > 0) {
29
32
  parts.push(`${countNoun(input.interruptedCommandCount, "in-progress command")} interrupted`);
30
33
  }
31
- if (input.removedRunWorkspaceCount > 0) {
32
- parts.push(`${countNoun(input.removedRunWorkspaceCount, "stale run workspace")} removed`);
33
- }
34
- if (input.removedTempBranchCount > 0) {
35
- parts.push(`${countNoun(input.removedTempBranchCount, "stale run branch", "stale run branches")} removed`);
36
- }
37
34
  if (input.staleReplayHintCount > 0) {
38
35
  parts.push(`${countNoun(input.staleReplayHintCount, "stale replay hint")} cleared`);
39
36
  }
40
- if (input.cleanupWarningCount > 0) {
41
- parts.push(`${countNoun(input.cleanupWarningCount, "cleanup warning")} recorded`);
42
- }
43
37
  return parts.length === 0
44
38
  ? "Startup recovery completed."
45
39
  : `Startup recovery completed: ${parts.join("; ")}.`;
@@ -3,7 +3,7 @@ import type { ExecutionStatusProjection, ContextSyncDisposition, ContextSyncResu
3
3
  import type { ContextSyncWorkflowTrigger } from "./workflows/index.js";
4
4
  import type { ControlPlaneCommandResult, Phase5ControlPlaneOptions, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor } from "./types.js";
5
5
  import { type ProjectBriefRefreshReason, type RefreshProjectBriefProjectionResult } from "./project-brief-refresh.js";
6
- export type { ControlPlaneCommandResult, ControlPlaneLogger, Phase5ControlPlaneOptions, RunCorrectionContext, RunContinuationContext, RunDispatchContext, RunDispatchTrigger, RunPipelineInput, RunPipelineResolution, RunPipelineResolver, RunPipelineResult, RunPipelineRunner, RunPipelineTimelineEvent, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor, } from "./types.js";
6
+ export type { ControlPlaneCommandResult, ControlPlaneLogger, Phase5ControlPlaneOptions, RunCorrectionContext, RunContinuationContext, RunDispatchContext, RunDispatchTrigger, RunPipelineInput, RunPipelineResolution, RunPipelineResolver, RunPipelineResult, RunPipelineRunner, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor, } from "./types.js";
7
7
  export declare class Phase5ControlPlane {
8
8
  private readonly engine;
9
9
  private readonly runEngine;
@@ -1,9 +1,9 @@
1
1
  import type { RunLineage } from "@tutti/shared/domain";
2
- import type { ActivityRef, TaskId } from "@tutti/shared/ids";
2
+ import type { ActivityRef } from "@tutti/shared/ids";
3
3
  import type { TaskProjection } from "@tutti/shared/schemas/api";
4
4
  import { type RecordRunNeedsHumanResultResult, type RecordRunResultResult } from "../collaboration-state/index.js";
5
5
  import type { HostProjectStore } from "../store/index.js";
6
- import type { RunPipelineResult, RunPipelineTimelineEvent } from "./types.js";
6
+ import type { RunPipelineResult } from "./types.js";
7
7
  type RecordedRunResult = RecordRunResultResult | RecordRunNeedsHumanResultResult;
8
8
  type RunEngineTerminalResult = {
9
9
  kind: "needs_human";
@@ -19,13 +19,6 @@ export type ControlPlaneRunResultRecord = {
19
19
  recorded: RecordedRunResult;
20
20
  terminal: RunEngineTerminalResult;
21
21
  };
22
- export declare function recordRunPipelineTimelineEvents(input: {
23
- store: HostProjectStore;
24
- activityRef: ActivityRef;
25
- taskId: TaskId;
26
- timelineEvents: readonly RunPipelineTimelineEvent[] | undefined;
27
- now: () => Date;
28
- }): void;
29
22
  export declare function recordRunPipelineResult(input: {
30
23
  store: HostProjectStore;
31
24
  activityRef: ActivityRef;
@@ -1,54 +1,36 @@
1
1
  import { recordRunNeedsHumanResult, recordRunResult, } from "../collaboration-state/index.js";
2
- import { recordProjectTimelineEvent } from "../project-timeline/index.js";
3
- export function recordRunPipelineTimelineEvents(input) {
4
- for (const event of input.timelineEvents ?? []) {
5
- recordProjectTimelineEvent(input.store.db, {
6
- event_kind: event.event_kind,
7
- status: event.status,
8
- summary: event.summary,
9
- activity_ref: input.activityRef,
10
- task_id: input.taskId,
11
- ...(event.commit_oid === undefined ? {} : { commit_oid: event.commit_oid }),
12
- ...(event.meta === undefined ? {} : { meta: event.meta }),
13
- now: input.now,
14
- });
15
- }
16
- }
17
2
  export function recordRunPipelineResult(input) {
18
3
  if (input.pipelineResult.result_kind === "needs_human") {
19
- const { timeline_events: timelineEvents, ...pipelineResult } = input.pipelineResult;
20
- void timelineEvents;
21
4
  const recorded = recordRunNeedsHumanResult(input.store.db, {
22
5
  activity_ref: input.activityRef,
23
6
  task_id: input.task.id,
24
7
  run_lineage: input.runLineage,
25
- result: pipelineResult,
8
+ result: input.pipelineResult,
26
9
  now: input.now,
27
10
  });
28
11
  return {
29
12
  recorded,
30
13
  terminal: {
31
14
  kind: "needs_human",
32
- summary: pipelineResult.summary,
15
+ summary: input.pipelineResult.summary,
33
16
  },
34
17
  };
35
18
  }
36
- const { timeline_events: timelineEvents, ...pipelineResult } = input.pipelineResult;
37
- void timelineEvents;
38
19
  const recorded = recordRunResult(input.store.db, {
39
20
  activity_ref: input.activityRef,
40
21
  task_id: input.task.id,
41
22
  run_lineage: input.runLineage,
42
- result: pipelineResult,
23
+ result: input.pipelineResult,
43
24
  now: input.now,
44
25
  });
45
26
  return {
46
27
  recorded,
47
28
  terminal: {
48
- kind: pipelineResult.result_kind === "finished" && pipelineResult.outcome === "completed"
29
+ kind: input.pipelineResult.result_kind === "finished" &&
30
+ input.pipelineResult.outcome === "completed"
49
31
  ? "completed"
50
32
  : "failed",
51
- summary: pipelineResult.summary,
33
+ summary: input.pipelineResult.summary,
52
34
  },
53
35
  };
54
36
  }
@@ -39,9 +39,6 @@ function runControlPlaneScheduler(input) {
39
39
  },
40
40
  };
41
41
  }
42
- if (input.readMainlineStatus !== undefined && !input.readMainlineStatus().mainline_clean) {
43
- return { disposition: { kind: "mainline_not_clean" } };
44
- }
45
42
  const next = readNextRunnableTask(input.store.db);
46
43
  if (next.kind === "none") {
47
44
  return { disposition: { kind: "nothing_to_run" } };
@@ -1,4 +1,4 @@
1
- import { recordRunPipelineFailure, recordRunPipelineResult, recordRunPipelineTimelineEvents, } from "./run-result-recording.js";
1
+ import { recordRunPipelineFailure, recordRunPipelineResult, } from "./run-result-recording.js";
2
2
  import { projectBriefShouldRefreshForUpdatedPaths, refreshProjectBriefProjection, } from "./project-brief-refresh.js";
3
3
  function runLineageFields(lineage) {
4
4
  return {
@@ -102,13 +102,6 @@ export function startControlPlaneRunForTask(input) {
102
102
  pipelineResult,
103
103
  now: input.now,
104
104
  });
105
- recordRunPipelineTimelineEvents({
106
- store: input.store,
107
- activityRef: context.activity_ref,
108
- taskId: input.run.task.id,
109
- timelineEvents: pipelineResult.timeline_events,
110
- now: input.now,
111
- });
112
105
  input.publishRunResultRecorded(resultRecord.recorded);
113
106
  if (shouldRefreshProjectBriefAfterRun(pipelineResult)) {
114
107
  void refreshProjectBriefProjection({
@@ -2,7 +2,7 @@ import type { ClarificationRoundRef } from "@tutti/shared/ids";
2
2
  import type { ExecutionStatusProjection } from "@tutti/shared/schemas/api";
3
3
  import { type SealedClarificationRoundWithoutSuccessor } from "../collaboration-state/index.js";
4
4
  import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
5
- import type { HostProjectStore } from "../store/index.js";
5
+ import { type HostProjectStore } from "../store/index.js";
6
6
  import type { StartupRecoveryOptions, StartupRecoveryResult } from "./types.js";
7
7
  type LastRecoveryConclusion = NonNullable<ExecutionStatusProjection["last_conclusion"]>;
8
8
  export type StartupRecoveryReplaySealedRound = (sealed: SealedClarificationRoundWithoutSuccessor, roundId: ClarificationRoundRef) => boolean;
@@ -1,15 +1,89 @@
1
- import { deleteStaleClarificationSubmitReplayHints, readSealedClarificationRoundsWithoutSuccessor, recoverStaleRunningTasksAsFailed, recordRecoverySummary, } from "../collaboration-state/index.js";
1
+ import { deleteStaleClarificationSubmitReplayHints, readStaleRunningTaskIdentities, readSealedClarificationRoundsWithoutSuccessor, recoverStaleRunningTasksAsFailed, recordRunResult, recordRecoverySummary, } from "../collaboration-state/index.js";
2
+ import { docsStatusForChangedPaths } from "../run-pipeline/result-projections.js";
3
+ import { recoverInProgressCommandIdempotencyRecords, } from "../store/index.js";
4
+ import { recoverAcceptedCheckpoint } from "../workspace-ops/index.js";
5
+ import { resolveControlPlaneCommandRecovery } from "./command-recovery.js";
2
6
  import { formatStartupRecoverySummary } from "./formatters.js";
3
- import { EXECUTION_STATUS_INVALIDATES, STARTUP_RECOVERY_INVALIDATES, taskRunInvalidates, } from "./invalidations.js";
7
+ import { EXECUTION_STATUS_INVALIDATES, STARTUP_RECOVERY_INVALIDATES, taskRunResultInvalidates, } from "./invalidations.js";
4
8
  export function runControlPlaneStartupRecovery(input) {
5
9
  const options = input.options ?? {};
6
- const cleanup = options.cleanupStaleRunWorkspaces?.() ?? {
7
- removed_run_workspace_count: 0,
8
- removed_temp_branch_count: 0,
9
- warnings: [],
10
- };
11
10
  const staleReplayHintCount = deleteStaleClarificationSubmitReplayHints(input.store.db);
12
- const recovered = recoverStaleRunningTasksAsFailed(input.store.db, { now: input.now });
11
+ const checkpointRecoveredRuns = [];
12
+ const checkpointConflictRuns = [];
13
+ if (options.workspaceRoot !== undefined) {
14
+ for (const staleRun of readStaleRunningTaskIdentities(input.store.db)) {
15
+ let checkpoint;
16
+ try {
17
+ checkpoint = recoverAcceptedCheckpoint({
18
+ workspaceRoot: options.workspaceRoot,
19
+ taskId: staleRun.task_id,
20
+ activityRef: staleRun.activity_ref,
21
+ });
22
+ }
23
+ catch {
24
+ const recorded = recordRunResult(input.store.db, {
25
+ activity_ref: staleRun.activity_ref,
26
+ task_id: staleRun.task_id,
27
+ run_lineage: staleRun.run_lineage,
28
+ result: {
29
+ result_kind: "failed",
30
+ summary: "Run checkpoint evidence conflicted with Git truth during startup recovery.",
31
+ error_code: "accepted_checkpoint_recovery_conflict",
32
+ retryable: false,
33
+ promotion: {
34
+ status: "not_promoted",
35
+ reason_code: "accepted_checkpoint_recovery_conflict",
36
+ summary: "Accepted checkpoint could not be proven without moving Git truth.",
37
+ },
38
+ },
39
+ now: input.now,
40
+ });
41
+ checkpointConflictRuns.push({
42
+ activity_ref: staleRun.activity_ref,
43
+ ...recorded,
44
+ });
45
+ continue;
46
+ }
47
+ if (checkpoint.kind === "not_found") {
48
+ continue;
49
+ }
50
+ const recorded = recordRunResult(input.store.db, {
51
+ activity_ref: staleRun.activity_ref,
52
+ task_id: staleRun.task_id,
53
+ run_lineage: staleRun.run_lineage,
54
+ result: {
55
+ result_kind: "finished",
56
+ outcome: "completed",
57
+ summary: "Run accepted checkpoint was restored after host restart.",
58
+ checks: "passed",
59
+ docs: docsStatusForChangedPaths(checkpoint.changed_paths),
60
+ changed_paths: checkpoint.changed_paths,
61
+ promotion: {
62
+ status: "promoted",
63
+ commit_oid: checkpoint.commit_oid,
64
+ summary: "Accepted checkpoint recovered from durable Git evidence.",
65
+ },
66
+ },
67
+ now: input.now,
68
+ });
69
+ checkpointRecoveredRuns.push({
70
+ activity_ref: staleRun.activity_ref,
71
+ ...recorded,
72
+ });
73
+ }
74
+ }
75
+ const interrupted = recoverStaleRunningTasksAsFailed(input.store.db, { now: input.now });
76
+ const recoveredRuns = [
77
+ ...checkpointRecoveredRuns,
78
+ ...checkpointConflictRuns,
79
+ ...interrupted.recovered_runs,
80
+ ];
81
+ const commandRecovery = options.recoverInProgressCommands
82
+ ? recoverInProgressCommandIdempotencyRecords(input.store.db, {
83
+ resolve: resolveControlPlaneCommandRecovery,
84
+ now: input.now,
85
+ })
86
+ : null;
13
87
  const sealedRounds = readSealedClarificationRoundsWithoutSuccessor(input.store.db);
14
88
  const replayedRoundIds = [];
15
89
  for (const sealed of sealedRounds) {
@@ -21,25 +95,20 @@ export function runControlPlaneStartupRecovery(input) {
21
95
  replayedRoundIds.push(roundId);
22
96
  }
23
97
  }
24
- const interruptedCommandCount = options.interruptedCommandCount ?? 0;
25
- const affectedActivities = recovered.recovered_runs.map((recoveredRun) => recoveredRun.activity_ref);
98
+ const interruptedCommandCount = (options.interruptedCommandCount ?? 0) + (commandRecovery?.interrupted_count ?? 0);
99
+ const affectedActivities = recoveredRuns.map((recoveredRun) => recoveredRun.activity_ref);
26
100
  const shouldRecord = affectedActivities.length > 0 ||
27
101
  replayedRoundIds.length > 0 ||
28
102
  interruptedCommandCount > 0 ||
29
- cleanup.removed_run_workspace_count > 0 ||
30
- cleanup.removed_temp_branch_count > 0 ||
31
- cleanup.warnings.length > 0 ||
32
103
  staleReplayHintCount > 0;
33
104
  if (!shouldRecord) {
34
105
  return { kind: "noop" };
35
106
  }
36
107
  const summary = formatStartupRecoverySummary({
37
- recoveredRunCount: recovered.recovered_runs.length,
108
+ interruptedRunCount: checkpointConflictRuns.length + interrupted.recovered_runs.length,
109
+ acceptedCheckpointRunCount: checkpointRecoveredRuns.length,
38
110
  replayedRoundCount: replayedRoundIds.length,
39
111
  interruptedCommandCount,
40
- removedRunWorkspaceCount: cleanup.removed_run_workspace_count,
41
- removedTempBranchCount: cleanup.removed_temp_branch_count,
42
- cleanupWarningCount: cleanup.warnings.length,
43
112
  staleReplayHintCount,
44
113
  });
45
114
  const recorded = recordRecoverySummary(input.store.db, {
@@ -53,9 +122,9 @@ export function runControlPlaneStartupRecovery(input) {
53
122
  ...(affectedActivities[0] === undefined ? {} : { activity_ref: affectedActivities[0] }),
54
123
  occurred_at: recorded.recovery_summary.created_at,
55
124
  });
56
- for (const recoveredRun of recovered.recovered_runs) {
125
+ for (const recoveredRun of recoveredRuns) {
57
126
  const taskId = recoveredRun.task.id;
58
- const invalidates = taskRunInvalidates(taskId);
127
+ const invalidates = taskRunResultInvalidates(taskId, recoveredRun.task_detail.detail.result);
59
128
  input.events.publish({
60
129
  event_type: "worklist.changed",
61
130
  payload: {
@@ -101,12 +170,10 @@ export function runControlPlaneStartupRecovery(input) {
101
170
  return {
102
171
  kind: "recovered",
103
172
  recovery_summary: recorded.recovery_summary,
104
- recovered_run_count: recovered.recovered_runs.length,
173
+ recovered_run_count: recoveredRuns.length,
174
+ accepted_checkpoint_run_count: checkpointRecoveredRuns.length,
105
175
  replayed_round_ids: replayedRoundIds,
106
176
  interrupted_command_count: interruptedCommandCount,
107
- removed_run_workspace_count: cleanup.removed_run_workspace_count,
108
- removed_temp_branch_count: cleanup.removed_temp_branch_count,
109
- cleanup_warning_count: cleanup.warnings.length,
110
177
  stale_replay_hint_count: staleReplayHintCount,
111
178
  };
112
179
  }
@@ -4,7 +4,6 @@ import type { ActivityRef, ClarificationRoundRef, TaskId, TaskModuleId } from "@
4
4
  import type { ActiveApprovalProjection, ClarificationRequestPayload, ExecutionRuntimeSnapshot, MessageProjection, RecoverySummaryProjection, TaskDetailProjection, TaskProjection } from "@tutti/shared/schemas/api";
5
5
  import type { HostProjectStore } from "../store/index.js";
6
6
  import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
7
- import type { CleanupStaleRunWorkspacesResult } from "../workspace-ops/index.js";
8
7
  import type { ProcedureWorkflowRunnerResolver } from "./workflows/index.js";
9
8
  export type Phase5ControlPlaneOptions = {
10
9
  store: HostProjectStore;
@@ -33,7 +32,8 @@ export type ControlPlaneCommandResult<TDisposition, TResult> = {
33
32
  result?: TResult;
34
33
  };
35
34
  export type StartupRecoveryOptions = {
36
- cleanupStaleRunWorkspaces?: () => CleanupStaleRunWorkspacesResult;
35
+ workspaceRoot?: string;
36
+ recoverInProgressCommands?: boolean;
37
37
  interruptedCommandCount?: number;
38
38
  };
39
39
  export type StartupRecoveryResult = {
@@ -42,11 +42,9 @@ export type StartupRecoveryResult = {
42
42
  kind: "recovered";
43
43
  recovery_summary: RecoverySummaryProjection;
44
44
  recovered_run_count: number;
45
+ accepted_checkpoint_run_count: number;
45
46
  replayed_round_ids: ClarificationRoundRef[];
46
47
  interrupted_command_count: number;
47
- removed_run_workspace_count: number;
48
- removed_temp_branch_count: number;
49
- cleanup_warning_count: number;
50
48
  stale_replay_hint_count: number;
51
49
  };
52
50
  export type RunDispatchTrigger = "auto_idle" | "task_arrival" | "manual_run_now" | "continuation" | "recovery";
@@ -109,17 +107,7 @@ export type RunPipelineStageProgress = {
109
107
  summary: string;
110
108
  runtime?: ExecutionRuntimeSnapshot;
111
109
  };
112
- export type RunPipelineTimelineEvent = {
113
- event_kind: "promotion_reconcile";
114
- status: "completed" | "failed";
115
- summary: string;
116
- commit_oid?: string;
117
- meta?: string[];
118
- };
119
- type RunPipelineResultMetadata = {
120
- timeline_events?: RunPipelineTimelineEvent[];
121
- };
122
- export type RunPipelineResult = RunPipelineResultMetadata & ({
110
+ export type RunPipelineResult = ({
123
111
  result_kind: "needs_human";
124
112
  summary: string;
125
113
  clarification_request_payload: ClarificationRequestPayload;
@@ -158,5 +146,4 @@ export type RunPipelineResolution = {
158
146
  retryable: boolean;
159
147
  };
160
148
  export type RunPipelineResolver = () => RunPipelineResolution;
161
- export {};
162
149
  //# sourceMappingURL=types.d.ts.map
@@ -16,7 +16,6 @@ export type CodexAppServerApprovalHandler = (input: {
16
16
  request: CodexAppServerJsonRpcServerRequest;
17
17
  context: {
18
18
  workspaceRoot: string;
19
- runWorkspaceRoot: string;
20
19
  codexHome: string;
21
20
  tuttiHome?: string;
22
21
  builtInSkillsRoot?: string;
@@ -46,7 +45,6 @@ export type CodexAppServerWorkspaceWriteRunInput<TOutput> = {
46
45
  approvalHandler?: CodexAppServerApprovalHandler;
47
46
  approvalContext?: {
48
47
  workspaceRoot?: string;
49
- runWorkspaceRoot?: string;
50
48
  taskId?: TaskId;
51
49
  activityRef?: ActivityRef;
52
50
  };
@@ -299,7 +299,6 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
299
299
  : { approvalHandler: options.approvalHandler }),
300
300
  approvalContext: {
301
301
  workspaceRoot: options.approvalContext?.workspaceRoot ?? options.workspaceRoot,
302
- runWorkspaceRoot: options.approvalContext?.runWorkspaceRoot ?? options.workspaceRoot,
303
302
  codexHome,
304
303
  ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
305
304
  ...(options.builtInSkillsRoot === undefined
@@ -1,10 +1,4 @@
1
- function sameChangedPaths(left, right) {
2
- if (left.length !== right.length) {
3
- return false;
4
- }
5
- return left.every((path, index) => path === right[index]);
6
- }
7
1
  export function candidateChangedAfterChecks(before, after) {
8
- return (!sameChangedPaths(before.changed_paths, after.changed_paths) || before.summary !== after.summary);
2
+ return before.fingerprint !== after.fingerprint;
9
3
  }
10
4
  //# sourceMappingURL=candidate-diff.js.map
@@ -12,8 +12,6 @@ export type OpenAiRunPipelineRunnerOptions = {
12
12
  config: OpenAiProviderConfig;
13
13
  apiKey: string;
14
14
  workspaceRoot: string;
15
- runWorkspaceRoot: string;
16
- mergeWorkspaceRoot?: string;
17
15
  promptsRoot?: string;
18
16
  tuttiHome?: string;
19
17
  userSkillsRoot?: string;
@@ -35,8 +33,6 @@ export type ProjectOpenAiRunPipelineRunnerOptions = {
35
33
  tuttiHome: string;
36
34
  projectId: ProjectId;
37
35
  workspaceRoot: string;
38
- runWorkspaceRoot: string;
39
- mergeWorkspaceRoot?: string;
40
36
  promptsRoot?: string;
41
37
  agentContext?: CodexAppServerAgentContextRuntime;
42
38
  approvalManager?: ApprovalRequestManager;