@xfey/tutti 0.1.50 → 0.1.52
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.
- package/dist/approvals/projections.d.ts +0 -1
- package/dist/approvals/projections.js +0 -1
- package/dist/artifacts/candidate-validator.js +3 -3
- package/dist/artifacts/manifest.d.ts +2 -0
- package/dist/artifacts/manifest.js +9 -2
- package/dist/artifacts/preview-runtime.js +1 -0
- package/dist/collaboration-state/index.d.ts +1 -1
- package/dist/collaboration-state/index.js +1 -1
- package/dist/collaboration-state/recovery.d.ts +2 -1
- package/dist/collaboration-state/recovery.js +16 -0
- package/dist/collaboration-state/types.d.ts +5 -0
- package/dist/control-plane/follow-up-run.js +0 -3
- package/dist/control-plane/formatters.d.ts +2 -4
- package/dist/control-plane/formatters.js +5 -11
- package/dist/control-plane/index.d.ts +1 -1
- package/dist/control-plane/run-result-recording.d.ts +2 -9
- package/dist/control-plane/run-result-recording.js +6 -24
- package/dist/control-plane/run-scheduler.js +0 -3
- package/dist/control-plane/run-start.js +1 -8
- package/dist/control-plane/startup-recovery.d.ts +1 -1
- package/dist/control-plane/startup-recovery.js +90 -23
- package/dist/control-plane/types.d.ts +4 -17
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +0 -2
- package/dist/providers/openai/app-server/workspace-write-run.js +0 -1
- package/dist/run-pipeline/candidate-diff.js +1 -7
- package/dist/run-pipeline/openai.d.ts +0 -4
- package/dist/run-pipeline/openai.js +42 -148
- package/dist/run-pipeline/task-run-invocation.d.ts +1 -2
- package/dist/run-pipeline/task-run-invocation.js +2 -4
- package/dist/server-shell/cli/host-server-runtime.js +3 -10
- package/dist/store/lifecycle.d.ts +1 -0
- package/dist/store/lifecycle.js +8 -6
- package/dist/workspace-ops/index.d.ts +3 -2
- package/dist/workspace-ops/index.js +2 -1
- package/dist/workspace-ops/mainline.d.ts +1 -1
- package/dist/workspace-ops/mainline.js +1 -5
- package/dist/workspace-ops/project-docs.js +26 -15
- package/dist/workspace-ops/project-workspace.d.ts +12 -0
- package/dist/workspace-ops/project-workspace.js +257 -0
- package/dist/workspace-ops/reference-files.js +9 -9
- package/dist/workspace-ops/reference-summaries.js +9 -7
- package/dist/workspace-ops/system-commits.d.ts +7 -0
- package/dist/workspace-ops/system-commits.js +43 -0
- package/dist/workspace-ops/types.d.ts +22 -41
- package/migrations/README.md +2 -2
- package/package.json +1 -1
- package/prompts/README.md +2 -2
- package/prompts/prompt-flow-experiment-plan.md +19 -19
- package/prompts/prompt-flow-map.md +20 -19
- package/prompts/runs/README.md +5 -3
- package/prompts/runs/task-continuation.md +6 -4
- package/prompts/runs/task-retry.md +11 -10
- package/prompts/runs/task-run.md +6 -4
- package/web/assets/{homepage-motion-scene-DYV3iw6y.js → homepage-motion-scene-ENUzAyyp.js} +1 -1
- package/web/assets/{index-DNnf10zz.css → index-CWWkCr48.css} +1 -1
- package/web/assets/{index-D_bRz29f.js → index-DK8LBgHp.js} +3 -3
- package/web/index.html +2 -2
- package/dist/run-pipeline/promotion-reconcile.d.ts +0 -21
- package/dist/run-pipeline/promotion-reconcile.js +0 -195
- package/dist/workspace-ops/run-workspaces.d.ts +0 -19
- package/dist/workspace-ops/run-workspaces.js +0 -302
|
@@ -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 {
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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
|
? {}
|
|
@@ -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
|
-
|
|
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.
|
|
23
|
-
parts.push(`${countNoun(input.
|
|
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,
|
|
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
|
|
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
|
|
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" &&
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
125
|
+
for (const recoveredRun of recoveredRuns) {
|
|
57
126
|
const taskId = recoveredRun.task.id;
|
|
58
|
-
const invalidates =
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|