@xfey/tutti 0.1.94 → 0.1.96
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/collaboration-state/index.d.ts +2 -0
- package/dist/collaboration-state/index.js +1 -0
- package/dist/collaboration-state/messages.js +15 -0
- package/dist/collaboration-state/run-recording.js +18 -1
- package/dist/collaboration-state/run-results.js +6 -0
- package/dist/collaboration-state/task-retry.d.ts +16 -0
- package/dist/collaboration-state/task-retry.js +136 -0
- package/dist/control-plane/command-recovery.js +9 -1
- package/dist/control-plane/index.d.ts +7 -3
- package/dist/control-plane/index.js +68 -6
- package/dist/control-plane/run-retry.d.ts +19 -0
- package/dist/control-plane/run-retry.js +103 -0
- package/dist/control-plane/run-start.d.ts +2 -1
- package/dist/control-plane/run-start.js +3 -0
- package/dist/control-plane/scratchpad-refresh.js +3 -4
- package/dist/control-plane/types.d.ts +12 -0
- package/dist/control-plane/workflows/openai.d.ts +1 -0
- package/dist/control-plane/workflows/openai.js +86 -15
- package/dist/control-plane/worklist-terminal-feedback.js +3 -0
- package/dist/prompt-templates/index.d.ts +1 -1
- package/dist/prompt-templates/index.js +4 -3
- package/dist/providers/openai/app-server/durable-session-smoke-cli.d.ts +2 -0
- package/dist/providers/openai/app-server/durable-session-smoke-cli.js +229 -0
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +10 -0
- package/dist/providers/openai/app-server/read-only-procedure.js +42 -13
- package/dist/providers/openai/app-server/session-store.d.ts +74 -0
- package/dist/providers/openai/app-server/session-store.js +303 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +17 -4
- package/dist/providers/openai/app-server/workspace-write-run.js +92 -28
- package/dist/providers/openai/codex-app-server.d.ts +1 -0
- package/dist/providers/openai/codex-app-server.js +2 -0
- package/dist/run-pipeline/openai.d.ts +1 -0
- package/dist/run-pipeline/openai.js +15 -43
- package/dist/run-pipeline/task-run-invocation.js +354 -93
- package/dist/server-shell/http/routes/project-api/execution-routes.js +32 -2
- package/dist/server-shell/http/routes/project-api/invalidations.d.ts +2 -1
- package/dist/server-shell/http/routes/project-api/invalidations.js +13 -0
- package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +21 -0
- package/dist/server-shell/http/routes/project-api/openapi-execution-routes.js +12 -1
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +7 -0
- package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +5 -0
- package/node_modules/@tutti/shared/dist/schemas/api/messages.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +4 -0
- package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +3 -0
- package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +33 -0
- package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +35 -1
- package/package.json +1 -1
- package/prompts/runs/README.md +11 -16
- package/prompts/runs/task-execute.md +86 -0
- package/prompts/runs/task-finalize.md +11 -0
- package/prompts/runs/task-recovery.md +15 -0
- package/prompts/runs/task-result-contract.md +64 -0
- package/web/assets/{homepage-motion-scene-CLTkixIk.js → homepage-motion-scene-BFo4r4nz.js} +1 -1
- package/web/assets/index-CNIl4TF8.js +69 -0
- package/web/assets/index-quWLFJgD.css +1 -0
- package/web/index.html +2 -2
- package/prompts/runs/task-continuation.md +0 -201
- package/prompts/runs/task-retry.md +0 -209
- package/prompts/runs/task-run.md +0 -188
- package/web/assets/index-BTNYElB-.js +0 -69
- package/web/assets/index-Deu_eF5K.css +0 -1
|
@@ -10,6 +10,8 @@ export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRo
|
|
|
10
10
|
export { closeTaskBoundClarificationAsRetaskingRequired, materializeClarificationSuccessor, materializeTaskBoundRunSuccessor, submitClarificationRound, } from "./clarification-successors.js";
|
|
11
11
|
export { readRecoverySummariesPage, readRecoverySummaryProjection, readRunResultProjectionByActivityRef, readTaskRunResultsPage, recordRecoverySummary, } from "./run-results.js";
|
|
12
12
|
export { recordRunNeedsHumanResult, recordRunResult } from "./run-recording.js";
|
|
13
|
+
export { materializeTaskRetryRun, readMaterializedTaskRetry } from "./task-retry.js";
|
|
14
|
+
export type { MaterializeTaskRetryResult } from "./task-retry.js";
|
|
13
15
|
export { readStaleRunningTaskIdentities, recoverStaleRunningTasksAsFailed, } from "./recovery.js";
|
|
14
16
|
export { markReadStateScope, readReadStateSummary, recordReadNotificationEvent, } from "./read-state.js";
|
|
15
17
|
export { readTaskDetailProjection } from "./task-details.js";
|
|
@@ -10,6 +10,7 @@ export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRo
|
|
|
10
10
|
export { closeTaskBoundClarificationAsRetaskingRequired, materializeClarificationSuccessor, materializeTaskBoundRunSuccessor, submitClarificationRound, } from "./clarification-successors.js";
|
|
11
11
|
export { readRecoverySummariesPage, readRecoverySummaryProjection, readRunResultProjectionByActivityRef, readTaskRunResultsPage, recordRecoverySummary, } from "./run-results.js";
|
|
12
12
|
export { recordRunNeedsHumanResult, recordRunResult } from "./run-recording.js";
|
|
13
|
+
export { materializeTaskRetryRun, readMaterializedTaskRetry } from "./task-retry.js";
|
|
13
14
|
export { readStaleRunningTaskIdentities, recoverStaleRunningTasksAsFailed, } from "./recovery.js";
|
|
14
15
|
export { markReadStateScope, readReadStateSummary, recordReadNotificationEvent, } from "./read-state.js";
|
|
15
16
|
export { readTaskDetailProjection } from "./task-details.js";
|
|
@@ -76,6 +76,21 @@ export function mapMessageRow(row, db) {
|
|
|
76
76
|
if (row.refs_json !== null) {
|
|
77
77
|
message.refs = parseJson(row.refs_json, {});
|
|
78
78
|
}
|
|
79
|
+
const taskResult = message.refs?.task_result;
|
|
80
|
+
if (db !== undefined && taskResult?.latest_run_result_id !== undefined) {
|
|
81
|
+
const runResult = db
|
|
82
|
+
.prepare("SELECT result_json FROM run_results WHERE id = ?")
|
|
83
|
+
.get(taskResult.latest_run_result_id);
|
|
84
|
+
if (runResult !== undefined) {
|
|
85
|
+
const result = parseJson(runResult.result_json, {});
|
|
86
|
+
if (result.retry_action === "retryable" || result.retry_action === "retried") {
|
|
87
|
+
taskResult.retry_action = result.retry_action;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
delete taskResult.retry_action;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
79
94
|
if (db !== undefined && row.message_kind === "clarification_card") {
|
|
80
95
|
const clarificationCard = readClarificationCardForMessage(db, row.id);
|
|
81
96
|
if (clarificationCard !== undefined) {
|
|
@@ -19,13 +19,30 @@ export function recordRunResult(db, input) {
|
|
|
19
19
|
const taskStatus = input.result.result_kind === "finished" && input.result.outcome === "completed"
|
|
20
20
|
? "done"
|
|
21
21
|
: "failed";
|
|
22
|
-
const
|
|
22
|
+
const blocksManualRetry = input.result.result_kind === "failed" &&
|
|
23
|
+
(input.result.error_code === "codex_app_server_output_schema_invalid" ||
|
|
24
|
+
input.result.error_code === "codex_app_server_auth_failed" ||
|
|
25
|
+
input.result.error_code === "codex_app_server_session_unavailable" ||
|
|
26
|
+
input.result.error_code === "codex_app_server_turn_state_unknown" ||
|
|
27
|
+
input.result.error_code === "codex_app_server_process_lifecycle_failed" ||
|
|
28
|
+
input.result.error_code === "run_pipeline_failed" ||
|
|
29
|
+
input.result.error_code.startsWith("workspace_ops_"));
|
|
30
|
+
const externalSideEffectBlocksRetry = input.result.result_kind === "finished" &&
|
|
31
|
+
input.result.external_side_effects?.some((effect) => effect.status === "succeeded" || effect.status === "unknown") === true;
|
|
32
|
+
const retryAction = taskStatus === "failed" && !blocksManualRetry && !externalSideEffectBlocksRetry
|
|
33
|
+
? "retryable"
|
|
34
|
+
: undefined;
|
|
35
|
+
const resultJson = {
|
|
36
|
+
...input.result,
|
|
37
|
+
...(retryAction === undefined ? {} : { retry_action: retryAction }),
|
|
38
|
+
};
|
|
23
39
|
const detailResult = {
|
|
24
40
|
latest_run_result_id: runResultId,
|
|
25
41
|
result_kind: input.result.result_kind,
|
|
26
42
|
run_lineage: input.run_lineage,
|
|
27
43
|
summary: input.result.summary,
|
|
28
44
|
updated_at: createdAt,
|
|
45
|
+
...(retryAction === undefined ? {} : { retry_action: retryAction }),
|
|
29
46
|
};
|
|
30
47
|
if (input.result.result_kind === "finished") {
|
|
31
48
|
detailResult.outcome = input.result.outcome;
|
|
@@ -31,6 +31,9 @@ export function mapRunResultRow(row) {
|
|
|
31
31
|
summary: typeof result.summary === "string" ? result.summary : "",
|
|
32
32
|
error_code: typeof result.error_code === "string" ? result.error_code : "unknown",
|
|
33
33
|
retryable: typeof result.retryable === "boolean" ? result.retryable : false,
|
|
34
|
+
...(result.retry_action === "retryable" || result.retry_action === "retried"
|
|
35
|
+
? { retry_action: result.retry_action }
|
|
36
|
+
: {}),
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
const projection = {
|
|
@@ -68,6 +71,9 @@ export function mapRunResultRow(row) {
|
|
|
68
71
|
projection.external_side_effects =
|
|
69
72
|
result.external_side_effects;
|
|
70
73
|
}
|
|
74
|
+
if (result.retry_action === "retryable" || result.retry_action === "retried") {
|
|
75
|
+
projection.retry_action = result.retry_action;
|
|
76
|
+
}
|
|
71
77
|
return projection;
|
|
72
78
|
}
|
|
73
79
|
export function readRunResultProjectionByActivityRef(db, activityRef) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ActivityRef, RunResultId, TaskId } from "@tutti/shared/ids";
|
|
2
|
+
import type { RetryTaskDisposition } from "@tutti/shared/schemas/api";
|
|
3
|
+
import { type SqliteDatabase } from "../store/index.js";
|
|
4
|
+
export type MaterializeTaskRetryResult = {
|
|
5
|
+
disposition: RetryTaskDisposition;
|
|
6
|
+
};
|
|
7
|
+
export declare function readMaterializedTaskRetry(db: SqliteDatabase, runResultId: RunResultId): Extract<RetryTaskDisposition, {
|
|
8
|
+
kind: "accepted";
|
|
9
|
+
}> | null;
|
|
10
|
+
export declare function materializeTaskRetryRun(db: SqliteDatabase, input: {
|
|
11
|
+
task_id: TaskId;
|
|
12
|
+
expected_latest_run_result_id: RunResultId;
|
|
13
|
+
activity_ref: ActivityRef;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
}): MaterializeTaskRetryResult;
|
|
16
|
+
//# sourceMappingURL=task-retry.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { withHostStoreTransaction } from "../store/index.js";
|
|
2
|
+
import { nowIso, parseJson, stringifyJson } from "./serialization.js";
|
|
3
|
+
import { readTaskDetailProjection } from "./task-details.js";
|
|
4
|
+
import { readTaskProjection } from "./worklist.js";
|
|
5
|
+
import { recordReadNotificationEvent } from "./read-state.js";
|
|
6
|
+
export function readMaterializedTaskRetry(db, runResultId) {
|
|
7
|
+
const row = db
|
|
8
|
+
.prepare("SELECT * FROM run_results WHERE id = ?")
|
|
9
|
+
.get(runResultId);
|
|
10
|
+
if (row === undefined) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const result = parseJson(row.result_json, {});
|
|
14
|
+
if (typeof result.retry_activity_ref !== "string") {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
kind: "accepted",
|
|
19
|
+
task_id: row.task_id,
|
|
20
|
+
activity_ref: result.retry_activity_ref,
|
|
21
|
+
retried_run_result_id: row.id,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function failedResultCanRetry(row, result) {
|
|
25
|
+
if (result.retry_action === "retryable") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (row.result_kind === "failed") {
|
|
29
|
+
const errorCode = typeof result.error_code === "string" ? result.error_code : "unknown";
|
|
30
|
+
return (errorCode !== "codex_app_server_output_schema_invalid" &&
|
|
31
|
+
errorCode !== "codex_app_server_auth_failed" &&
|
|
32
|
+
errorCode !== "codex_app_server_session_unavailable" &&
|
|
33
|
+
errorCode !== "codex_app_server_turn_state_unknown" &&
|
|
34
|
+
errorCode !== "codex_app_server_process_lifecycle_failed" &&
|
|
35
|
+
errorCode !== "run_pipeline_failed" &&
|
|
36
|
+
!errorCode.startsWith("workspace_ops_"));
|
|
37
|
+
}
|
|
38
|
+
if (row.result_kind !== "finished" || result.outcome !== "failed") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(result.external_side_effects)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return !result.external_side_effects.some((effect) => typeof effect === "object" &&
|
|
45
|
+
effect !== null &&
|
|
46
|
+
"status" in effect &&
|
|
47
|
+
(effect.status === "succeeded" || effect.status === "unknown"));
|
|
48
|
+
}
|
|
49
|
+
export function materializeTaskRetryRun(db, input) {
|
|
50
|
+
return withHostStoreTransaction(db, (tx) => {
|
|
51
|
+
const taskRow = tx
|
|
52
|
+
.prepare("SELECT * FROM tasks WHERE id = ?")
|
|
53
|
+
.get(input.task_id);
|
|
54
|
+
if (taskRow === undefined) {
|
|
55
|
+
return { disposition: { kind: "task_not_failed" } };
|
|
56
|
+
}
|
|
57
|
+
if (taskRow.latest_run_result_id !== input.expected_latest_run_result_id) {
|
|
58
|
+
return {
|
|
59
|
+
disposition: {
|
|
60
|
+
kind: "latest_run_result_changed",
|
|
61
|
+
...(taskRow.latest_run_result_id === null
|
|
62
|
+
? {}
|
|
63
|
+
: { latest_run_result_id: taskRow.latest_run_result_id }),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (taskRow.status === "running" && taskRow.current_activity_ref !== null) {
|
|
68
|
+
return {
|
|
69
|
+
disposition: {
|
|
70
|
+
kind: "already_running",
|
|
71
|
+
activity_ref: taskRow.current_activity_ref,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (taskRow.status !== "failed") {
|
|
76
|
+
return { disposition: { kind: "task_not_failed" } };
|
|
77
|
+
}
|
|
78
|
+
const runResultRow = tx
|
|
79
|
+
.prepare("SELECT * FROM run_results WHERE id = ?")
|
|
80
|
+
.get(input.expected_latest_run_result_id);
|
|
81
|
+
if (runResultRow === undefined || runResultRow.task_id !== input.task_id) {
|
|
82
|
+
return { disposition: { kind: "latest_run_result_changed" } };
|
|
83
|
+
}
|
|
84
|
+
const result = parseJson(runResultRow.result_json, {});
|
|
85
|
+
if (result.retry_activity_ref !== undefined) {
|
|
86
|
+
return {
|
|
87
|
+
disposition: {
|
|
88
|
+
kind: "retry_already_started",
|
|
89
|
+
activity_ref: result.retry_activity_ref,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (!failedResultCanRetry(runResultRow, result)) {
|
|
94
|
+
return { disposition: { kind: "retry_requires_resolution" } };
|
|
95
|
+
}
|
|
96
|
+
const updatedAt = nowIso(input.now);
|
|
97
|
+
tx.prepare("UPDATE run_results SET result_json = ? WHERE id = ?").run(stringifyJson({
|
|
98
|
+
...result,
|
|
99
|
+
retry_action: "retried",
|
|
100
|
+
retry_activity_ref: input.activity_ref,
|
|
101
|
+
retried_at: updatedAt,
|
|
102
|
+
}), input.expected_latest_run_result_id);
|
|
103
|
+
tx.prepare(`
|
|
104
|
+
UPDATE tasks
|
|
105
|
+
SET status = 'running',
|
|
106
|
+
current_activity_ref = ?,
|
|
107
|
+
updated_at = ?
|
|
108
|
+
WHERE id = ?
|
|
109
|
+
AND status = 'failed'
|
|
110
|
+
`).run(input.activity_ref, updatedAt, input.task_id);
|
|
111
|
+
const detail = readTaskDetailProjection(tx, input.task_id);
|
|
112
|
+
if (detail === null) {
|
|
113
|
+
throw new Error("Task retry could not read task detail.");
|
|
114
|
+
}
|
|
115
|
+
tx.prepare("UPDATE task_details SET result_json = ?, updated_at = ? WHERE task_id = ?").run(stringifyJson({ ...detail.detail.result, retry_action: "retried", updated_at: updatedAt }), updatedAt, input.task_id);
|
|
116
|
+
recordReadNotificationEvent(tx, {
|
|
117
|
+
scope: "worklist",
|
|
118
|
+
source_kind: "task_run_started",
|
|
119
|
+
source_ref: input.task_id,
|
|
120
|
+
occurred_at: updatedAt,
|
|
121
|
+
});
|
|
122
|
+
const task = readTaskProjection(tx, input.task_id);
|
|
123
|
+
if (task === null) {
|
|
124
|
+
throw new Error("Task retry did not produce a readable task.");
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
disposition: {
|
|
128
|
+
kind: "accepted",
|
|
129
|
+
task_id: input.task_id,
|
|
130
|
+
activity_ref: input.activity_ref,
|
|
131
|
+
retried_run_result_id: input.expected_latest_run_result_id,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=task-retry.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readClarificationRoundProjection, readRecoverableScheduledRun, readRunResultProjectionByActivityRef, } from "../collaboration-state/index.js";
|
|
1
|
+
import { readClarificationRoundProjection, readRecoverableScheduledRun, readRunResultProjectionByActivityRef, readMaterializedTaskRetry, } from "../collaboration-state/index.js";
|
|
2
2
|
const SUBMIT_CLARIFICATION_ROUND_COMMANDS = new Set([
|
|
3
3
|
"clarification.round.submit",
|
|
4
4
|
"submit_clarification_round",
|
|
@@ -10,6 +10,7 @@ const MATERIALIZE_CLARIFICATION_SUCCESSOR_COMMANDS = new Set([
|
|
|
10
10
|
]);
|
|
11
11
|
const RECORD_RUN_RESULT_COMMANDS = new Set(["run.result.record", "record_run_result"]);
|
|
12
12
|
const RUN_SCHEDULER_NOW_COMMANDS = new Set(["scheduler.run_now", "runSchedulerNow"]);
|
|
13
|
+
const RETRY_TASK_COMMANDS = new Set(["task.retry"]);
|
|
13
14
|
function completed(disposition) {
|
|
14
15
|
return {
|
|
15
16
|
status: "completed",
|
|
@@ -74,6 +75,13 @@ export const resolveControlPlaneCommandRecovery = (db, record) => {
|
|
|
74
75
|
queued_task_ids: scheduled.queued_task_ids,
|
|
75
76
|
});
|
|
76
77
|
}
|
|
78
|
+
if (RETRY_TASK_COMMANDS.has(record.command_name)) {
|
|
79
|
+
if (record.target_ref === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const retry = readMaterializedTaskRetry(db, record.target_ref);
|
|
83
|
+
return retry === null ? null : completed(retry);
|
|
84
|
+
}
|
|
77
85
|
return null;
|
|
78
86
|
};
|
|
79
87
|
//# sourceMappingURL=command-recovery.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ActivityRef, type ClarificationRoundRef, type IdempotencyKey, type WorkflowInvocationRef } from "@tutti/shared/ids";
|
|
2
|
-
import type { ExecutionStatusProjection, MessageProjection, ContextSyncDisposition, ContextSyncResult, RefreshScratchpadDisposition, RefreshScratchpadResult, RunSchedulerNowDisposition, RunSchedulerNowPayload, RefreshReferenceSummariesDisposition, RefreshReferenceSummariesPayload, RefreshReferenceSummariesResult, SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult, SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult, ProcedureLane } from "@tutti/shared/schemas/api";
|
|
1
|
+
import { type ActivityRef, type ClarificationRoundRef, type IdempotencyKey, type TaskId, type WorkflowInvocationRef } from "@tutti/shared/ids";
|
|
2
|
+
import type { ExecutionStatusProjection, MessageProjection, ContextSyncDisposition, ContextSyncResult, RefreshScratchpadDisposition, RefreshScratchpadResult, RunSchedulerNowDisposition, RunSchedulerNowPayload, RetryTaskDisposition, RetryTaskPayload, RefreshReferenceSummariesDisposition, RefreshReferenceSummariesPayload, RefreshReferenceSummariesResult, SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult, SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult, ProcedureLane } from "@tutti/shared/schemas/api";
|
|
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";
|
|
@@ -18,6 +18,7 @@ export declare class Phase5ControlPlane {
|
|
|
18
18
|
private readonly runLifecycle;
|
|
19
19
|
private readonly now;
|
|
20
20
|
private readonly scratchpadAutoRefreshScheduler;
|
|
21
|
+
private pendingWorklistCompletionContextSyncTimer;
|
|
21
22
|
private readonly pendingReferenceSummaryPaths;
|
|
22
23
|
private readonly referenceSummaryWaiters;
|
|
23
24
|
private pendingAllReferenceSummaries;
|
|
@@ -28,6 +29,7 @@ export declare class Phase5ControlPlane {
|
|
|
28
29
|
constructor(options: Phase5ControlPlaneOptions);
|
|
29
30
|
getExecutionStatus(): ExecutionStatusProjection;
|
|
30
31
|
runSchedulerNow(payload?: RunSchedulerNowPayload): ControlPlaneCommandResult<RunSchedulerNowDisposition, never>;
|
|
32
|
+
retryTask(taskId: TaskId, payload: RetryTaskPayload): ControlPlaneCommandResult<RetryTaskDisposition, never>;
|
|
31
33
|
runStartupRecovery(options?: StartupRecoveryOptions): StartupRecoveryResult;
|
|
32
34
|
startScratchpadRefresh(options?: {
|
|
33
35
|
lane?: ProcedureLane;
|
|
@@ -66,7 +68,9 @@ export declare class Phase5ControlPlane {
|
|
|
66
68
|
private tryRunTaskArrivalScheduler;
|
|
67
69
|
private maybeScheduleAfterRunCompletion;
|
|
68
70
|
private worklistCompletionContextSyncTrigger;
|
|
69
|
-
private
|
|
71
|
+
private scheduleContextSyncAfterWorklistCompletion;
|
|
72
|
+
private attemptContextSyncAfterWorklistCompletion;
|
|
73
|
+
private cancelPendingWorklistCompletionContextSync;
|
|
70
74
|
private startTaskCompileContinuation;
|
|
71
75
|
private startTaskBoundFollowUpCheck;
|
|
72
76
|
private publishProcedureTransition;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readActiveTaskCompileContext, readWorklistProjection, } from "../collaboration-state/index.js";
|
|
1
|
+
import { readActiveTaskCompileContext, readScratchpadSourceState, readWorklistProjection, } from "../collaboration-state/index.js";
|
|
2
2
|
import { ProcedureEngine } from "../procedure-engine/index.js";
|
|
3
3
|
import { RunEngine } from "../run-engine/index.js";
|
|
4
|
-
import { WorkspaceOpsError, readReferenceSummaryTargets
|
|
4
|
+
import { WorkspaceOpsError, readReferenceSummaryTargets } from "../workspace-ops/index.js";
|
|
5
5
|
import { publishProcedureTransition as publishProcedureTransitionEvent, publishRunResultRecorded as publishRunResultRecordedEvent, publishRunTransition as publishRunTransitionEvent, publishTaskRunStarted as publishTaskRunStartedEvent, } from "./event-publishers.js";
|
|
6
6
|
import { startContextSyncAnalysisProcedure } from "./context-sync.js";
|
|
7
7
|
import { refreshProjectBriefProjection, } from "./project-brief-refresh.js";
|
|
@@ -16,8 +16,10 @@ import { startControlPlaneTaskCompileContinuation } from "./task-compile-continu
|
|
|
16
16
|
import { startControlPlaneTaskBoundFollowUpCheck } from "./follow-up-start.js";
|
|
17
17
|
import { runControlPlaneSchedulerForTaskArrival, runControlPlaneSchedulerNow, } from "./run-scheduler.js";
|
|
18
18
|
import { startControlPlaneRunForTask } from "./run-start.js";
|
|
19
|
+
import { retryControlPlaneTask } from "./run-retry.js";
|
|
19
20
|
import { runSendClarificationRoundMessage, runSubmitClarificationRound, } from "./clarification-commands.js";
|
|
20
21
|
import { emitTaskCompletedResultMessage, emitTaskFailedResultMessage, } from "./worklist-terminal-feedback.js";
|
|
22
|
+
const WORKLIST_COMPLETION_CONTEXT_SYNC_GRACE_MS = 30_000;
|
|
21
23
|
export class Phase5ControlPlane {
|
|
22
24
|
engine;
|
|
23
25
|
runEngine;
|
|
@@ -32,6 +34,7 @@ export class Phase5ControlPlane {
|
|
|
32
34
|
runLifecycle;
|
|
33
35
|
now;
|
|
34
36
|
scratchpadAutoRefreshScheduler;
|
|
37
|
+
pendingWorklistCompletionContextSyncTimer = null;
|
|
35
38
|
pendingReferenceSummaryPaths = new Set();
|
|
36
39
|
referenceSummaryWaiters = new Map();
|
|
37
40
|
pendingAllReferenceSummaries = false;
|
|
@@ -82,6 +85,19 @@ export class Phase5ControlPlane {
|
|
|
82
85
|
now: this.now,
|
|
83
86
|
});
|
|
84
87
|
}
|
|
88
|
+
retryTask(taskId, payload) {
|
|
89
|
+
return retryControlPlaneTask({
|
|
90
|
+
store: this.store,
|
|
91
|
+
taskId,
|
|
92
|
+
payload,
|
|
93
|
+
resolveRunPipeline: this.resolveRunPipeline,
|
|
94
|
+
readActiveActivity: () => this.readSchedulerBlocker(),
|
|
95
|
+
startRunForTask: (runInput) => {
|
|
96
|
+
void this.startRunForTask(runInput);
|
|
97
|
+
},
|
|
98
|
+
now: this.now,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
85
101
|
runStartupRecovery(options = {}) {
|
|
86
102
|
return runControlPlaneStartupRecovery({
|
|
87
103
|
store: this.store,
|
|
@@ -98,6 +114,7 @@ export class Phase5ControlPlane {
|
|
|
98
114
|
});
|
|
99
115
|
}
|
|
100
116
|
startScratchpadRefresh(options = {}) {
|
|
117
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
101
118
|
return startControlPlaneScratchpadRefresh({
|
|
102
119
|
engine: this.engine,
|
|
103
120
|
store: this.store,
|
|
@@ -114,6 +131,7 @@ export class Phase5ControlPlane {
|
|
|
114
131
|
if (!shouldUseMessageAsScratchpadSource(message)) {
|
|
115
132
|
return;
|
|
116
133
|
}
|
|
134
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
117
135
|
this.scratchpadAutoRefreshScheduler.notifySourceChanged();
|
|
118
136
|
}
|
|
119
137
|
refreshProjectBrief(options) {
|
|
@@ -134,6 +152,7 @@ export class Phase5ControlPlane {
|
|
|
134
152
|
}
|
|
135
153
|
this.closing = true;
|
|
136
154
|
this.scratchpadAutoRefreshScheduler.dispose();
|
|
155
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
137
156
|
this.pendingAllReferenceSummaries = false;
|
|
138
157
|
this.pendingReferenceSummaryPaths.clear();
|
|
139
158
|
}
|
|
@@ -157,6 +176,7 @@ export class Phase5ControlPlane {
|
|
|
157
176
|
}
|
|
158
177
|
}
|
|
159
178
|
startTaskCompile(payload) {
|
|
179
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
160
180
|
const activeRun = this.readActiveRun();
|
|
161
181
|
if (activeRun !== null) {
|
|
162
182
|
return {
|
|
@@ -197,6 +217,9 @@ export class Phase5ControlPlane {
|
|
|
197
217
|
});
|
|
198
218
|
}
|
|
199
219
|
startContextSyncAnalysis(trigger = { kind: "manual" }) {
|
|
220
|
+
if (trigger.kind === "manual") {
|
|
221
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
222
|
+
}
|
|
200
223
|
return startContextSyncAnalysisProcedure({
|
|
201
224
|
engine: this.engine,
|
|
202
225
|
store: this.store,
|
|
@@ -352,6 +375,7 @@ export class Phase5ControlPlane {
|
|
|
352
375
|
publishRunResultRecorded: (recorded) => this.publishRunResultRecorded(recorded),
|
|
353
376
|
});
|
|
354
377
|
if (result.kind === "accepted") {
|
|
378
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
355
379
|
this.runLifecycle?.started();
|
|
356
380
|
}
|
|
357
381
|
return result;
|
|
@@ -483,7 +507,7 @@ export class Phase5ControlPlane {
|
|
|
483
507
|
if (this.tryRunTaskArrivalScheduler()) {
|
|
484
508
|
return;
|
|
485
509
|
}
|
|
486
|
-
this.
|
|
510
|
+
this.scheduleContextSyncAfterWorklistCompletion();
|
|
487
511
|
}
|
|
488
512
|
worklistCompletionContextSyncTrigger() {
|
|
489
513
|
const worklist = readWorklistProjection(this.store.db);
|
|
@@ -497,16 +521,46 @@ export class Phase5ControlPlane {
|
|
|
497
521
|
module_count: worklist.modules.length,
|
|
498
522
|
};
|
|
499
523
|
}
|
|
500
|
-
|
|
501
|
-
if (this.closing
|
|
524
|
+
scheduleContextSyncAfterWorklistCompletion() {
|
|
525
|
+
if (this.closing ||
|
|
526
|
+
!this.store.db.open ||
|
|
527
|
+
this.pendingWorklistCompletionContextSyncTimer !== null) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const sourceState = readScratchpadSourceState(this.store.db);
|
|
531
|
+
if (this.worklistCompletionContextSyncTrigger() === undefined ||
|
|
532
|
+
sourceState.dirty_since !== undefined ||
|
|
533
|
+
sourceState.in_flight !== undefined ||
|
|
534
|
+
this.readSchedulerBlocker() !== null) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
this.pendingWorklistCompletionContextSyncTimer = setTimeout(() => {
|
|
538
|
+
this.attemptContextSyncAfterWorklistCompletion();
|
|
539
|
+
}, WORKLIST_COMPLETION_CONTEXT_SYNC_GRACE_MS);
|
|
540
|
+
this.pendingWorklistCompletionContextSyncTimer.unref?.();
|
|
541
|
+
}
|
|
542
|
+
attemptContextSyncAfterWorklistCompletion() {
|
|
543
|
+
this.pendingWorklistCompletionContextSyncTimer = null;
|
|
544
|
+
if (this.closing || !this.store.db.open) {
|
|
502
545
|
return;
|
|
503
546
|
}
|
|
504
547
|
const trigger = this.worklistCompletionContextSyncTrigger();
|
|
505
|
-
|
|
548
|
+
const sourceState = readScratchpadSourceState(this.store.db);
|
|
549
|
+
if (trigger === undefined ||
|
|
550
|
+
sourceState.dirty_since !== undefined ||
|
|
551
|
+
sourceState.in_flight !== undefined ||
|
|
552
|
+
this.readSchedulerBlocker() !== null) {
|
|
506
553
|
return;
|
|
507
554
|
}
|
|
508
555
|
void this.startContextSyncAnalysis(trigger);
|
|
509
556
|
}
|
|
557
|
+
cancelPendingWorklistCompletionContextSync() {
|
|
558
|
+
if (this.pendingWorklistCompletionContextSyncTimer === null) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
clearTimeout(this.pendingWorklistCompletionContextSyncTimer);
|
|
562
|
+
this.pendingWorklistCompletionContextSyncTimer = null;
|
|
563
|
+
}
|
|
510
564
|
startTaskCompileContinuation(owner, roundId) {
|
|
511
565
|
let tasksAdded = false;
|
|
512
566
|
return startControlPlaneTaskCompileContinuation({
|
|
@@ -556,6 +610,9 @@ export class Phase5ControlPlane {
|
|
|
556
610
|
});
|
|
557
611
|
}
|
|
558
612
|
publishProcedureTransition(state) {
|
|
613
|
+
if (state.transition_kind === "started" && state.activity.lane === "foreground") {
|
|
614
|
+
this.cancelPendingWorklistCompletionContextSync();
|
|
615
|
+
}
|
|
559
616
|
this.logProcedureTransition(state);
|
|
560
617
|
publishProcedureTransitionEvent({
|
|
561
618
|
events: this.events,
|
|
@@ -582,6 +639,11 @@ export class Phase5ControlPlane {
|
|
|
582
639
|
}
|
|
583
640
|
if (!this.closing) {
|
|
584
641
|
this.schedulePendingReferenceSummaryRefresh();
|
|
642
|
+
if (state.activity.status === "finished" &&
|
|
643
|
+
(state.activity.workflow_kind === "scratchpad_refresh" ||
|
|
644
|
+
state.activity.workflow_kind === "reference_summary_refresh")) {
|
|
645
|
+
this.scheduleContextSyncAfterWorklistCompletion();
|
|
646
|
+
}
|
|
585
647
|
}
|
|
586
648
|
}
|
|
587
649
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ActivityRef, type TaskId } from "@tutti/shared/ids";
|
|
2
|
+
import type { RetryTaskDisposition, RetryTaskPayload } from "@tutti/shared/schemas/api";
|
|
3
|
+
import type { HostProjectStore } from "../store/index.js";
|
|
4
|
+
import type { StartRunForTaskInput } from "./run-start.js";
|
|
5
|
+
import type { ControlPlaneCommandResult, RunPipelineResolver } from "./types.js";
|
|
6
|
+
type ActiveActivity = {
|
|
7
|
+
activity_ref: ActivityRef;
|
|
8
|
+
};
|
|
9
|
+
export declare function retryControlPlaneTask(input: {
|
|
10
|
+
store: HostProjectStore;
|
|
11
|
+
taskId: TaskId;
|
|
12
|
+
payload: RetryTaskPayload;
|
|
13
|
+
resolveRunPipeline: RunPipelineResolver | undefined;
|
|
14
|
+
readActiveActivity: () => ActiveActivity | null;
|
|
15
|
+
startRunForTask: (run: StartRunForTaskInput) => void;
|
|
16
|
+
now: () => Date;
|
|
17
|
+
}): ControlPlaneCommandResult<RetryTaskDisposition, never>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=run-retry.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createActivityRef, } from "@tutti/shared/ids";
|
|
2
|
+
import { materializeTaskRetryRun, readNextRunnableTask, readTaskDetailProjection, readTaskRunResultsPage, } from "../collaboration-state/index.js";
|
|
3
|
+
import { toDomainRunLineage } from "./run-lineage.js";
|
|
4
|
+
function retryCorrectionContext(result, detail) {
|
|
5
|
+
const resultProjection = detail.detail.result;
|
|
6
|
+
const reasonCode = result.result_kind === "failed"
|
|
7
|
+
? result.error_code
|
|
8
|
+
: resultProjection.checks === "failed"
|
|
9
|
+
? "checks_failed"
|
|
10
|
+
: resultProjection.promotion?.reason_code ?? "previous_run_failed";
|
|
11
|
+
return {
|
|
12
|
+
previous_provider_result: result.result_kind === "finished"
|
|
13
|
+
? {
|
|
14
|
+
decision: "completed",
|
|
15
|
+
summary: result.summary,
|
|
16
|
+
...(result.user_note_candidate === undefined
|
|
17
|
+
? {}
|
|
18
|
+
: { user_note_candidate: result.user_note_candidate }),
|
|
19
|
+
}
|
|
20
|
+
: { decision: "failed", summary: result.summary },
|
|
21
|
+
summary: result.summary,
|
|
22
|
+
reason_code: reasonCode,
|
|
23
|
+
guidance: "Continue the same task from the retained project state and correct the problem that caused the previous Run to fail.",
|
|
24
|
+
diagnostic: {
|
|
25
|
+
source: "previous_run_result",
|
|
26
|
+
text: result.summary,
|
|
27
|
+
truncated: false,
|
|
28
|
+
},
|
|
29
|
+
promotion_failure: resultProjection.promotion?.status === "not_promoted"
|
|
30
|
+
? {
|
|
31
|
+
reason_code: resultProjection.promotion.reason_code ?? reasonCode,
|
|
32
|
+
summary: resultProjection.promotion.summary ?? result.summary,
|
|
33
|
+
}
|
|
34
|
+
: null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function retryControlPlaneTask(input) {
|
|
38
|
+
const active = input.readActiveActivity();
|
|
39
|
+
if (active !== null) {
|
|
40
|
+
return { disposition: { kind: "already_running", activity_ref: active.activity_ref } };
|
|
41
|
+
}
|
|
42
|
+
const pipelineResolution = input.resolveRunPipeline?.();
|
|
43
|
+
if (pipelineResolution === undefined || pipelineResolution.kind === "not_configured") {
|
|
44
|
+
return { disposition: { kind: "provider_not_configured" } };
|
|
45
|
+
}
|
|
46
|
+
if (pipelineResolution.kind === "unavailable") {
|
|
47
|
+
return {
|
|
48
|
+
disposition: {
|
|
49
|
+
kind: "provider_unavailable",
|
|
50
|
+
reason: pipelineResolution.reason,
|
|
51
|
+
retryable: pipelineResolution.retryable,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const latestResult = readTaskRunResultsPage(input.store.db, input.taskId, { limit: 1 })?.items[0];
|
|
56
|
+
const detail = readTaskDetailProjection(input.store.db, input.taskId);
|
|
57
|
+
if (latestResult === undefined || detail === null) {
|
|
58
|
+
return { disposition: { kind: "task_not_failed" } };
|
|
59
|
+
}
|
|
60
|
+
const activityRef = createActivityRef();
|
|
61
|
+
const materialized = materializeTaskRetryRun(input.store.db, {
|
|
62
|
+
task_id: input.taskId,
|
|
63
|
+
expected_latest_run_result_id: input.payload.expected_latest_run_result_id,
|
|
64
|
+
activity_ref: activityRef,
|
|
65
|
+
now: input.now,
|
|
66
|
+
});
|
|
67
|
+
if (materialized.disposition.kind !== "accepted") {
|
|
68
|
+
return materialized;
|
|
69
|
+
}
|
|
70
|
+
const taskDetail = readTaskDetailProjection(input.store.db, input.taskId);
|
|
71
|
+
if (taskDetail === null) {
|
|
72
|
+
return { disposition: { kind: "retry_requires_resolution" } };
|
|
73
|
+
}
|
|
74
|
+
const parentLineage = toDomainRunLineage(latestResult.run_lineage);
|
|
75
|
+
const runLineage = {
|
|
76
|
+
kind: "retry",
|
|
77
|
+
attempt: parentLineage.attempt + 1,
|
|
78
|
+
parent_activity_ref: latestResult.activity_ref,
|
|
79
|
+
root_activity_ref: parentLineage.root_activity_ref ?? latestResult.activity_ref,
|
|
80
|
+
};
|
|
81
|
+
const next = readNextRunnableTask(input.store.db);
|
|
82
|
+
const orderedTaskRefs = next.kind === "found"
|
|
83
|
+
? [input.taskId, ...next.queued_task_ids.filter((taskId) => taskId !== input.taskId)]
|
|
84
|
+
: [input.taskId];
|
|
85
|
+
const dispatchContext = {
|
|
86
|
+
trigger: "recovery",
|
|
87
|
+
queue_scope: { kind: "worklist" },
|
|
88
|
+
current_task_id: input.taskId,
|
|
89
|
+
ordered_task_refs: orderedTaskRefs,
|
|
90
|
+
current_module_id: taskDetail.task.module_id,
|
|
91
|
+
};
|
|
92
|
+
input.startRunForTask({
|
|
93
|
+
activity_ref: activityRef,
|
|
94
|
+
task: taskDetail.task,
|
|
95
|
+
task_detail: taskDetail,
|
|
96
|
+
run_lineage: runLineage,
|
|
97
|
+
dispatch_context: dispatchContext,
|
|
98
|
+
correction_context: retryCorrectionContext(latestResult, detail),
|
|
99
|
+
runner: pipelineResolution.runner,
|
|
100
|
+
});
|
|
101
|
+
return materialized;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=run-retry.js.map
|
|
@@ -4,7 +4,7 @@ import type { TaskDetailProjection, TaskProjection } from "@tutti/shared/schemas
|
|
|
4
4
|
import type { RunEngine, RunEngineState, StartRunResult } from "../run-engine/index.js";
|
|
5
5
|
import type { HostProjectStore } from "../store/index.js";
|
|
6
6
|
import { recordRunPipelineResult } from "./run-result-recording.js";
|
|
7
|
-
import type { ControlPlaneLogger, Phase5ControlPlaneOptions, RunContinuationContext, RunDispatchContext, RunPipelineRunner } from "./types.js";
|
|
7
|
+
import type { ControlPlaneLogger, Phase5ControlPlaneOptions, RunCorrectionContext, RunContinuationContext, RunDispatchContext, RunPipelineRunner } from "./types.js";
|
|
8
8
|
export type StartRunForTaskInput = {
|
|
9
9
|
activity_ref: ActivityRef;
|
|
10
10
|
task: TaskProjection;
|
|
@@ -12,6 +12,7 @@ export type StartRunForTaskInput = {
|
|
|
12
12
|
run_lineage: RunLineage;
|
|
13
13
|
dispatch_context: RunDispatchContext;
|
|
14
14
|
continuation_context?: RunContinuationContext;
|
|
15
|
+
correction_context?: RunCorrectionContext;
|
|
15
16
|
runner: RunPipelineRunner;
|
|
16
17
|
};
|
|
17
18
|
export type RunResultRecordedPublisher = (recorded: ReturnType<typeof recordRunPipelineResult>["recorded"]) => void;
|
|
@@ -107,6 +107,9 @@ export function startControlPlaneRunForTask(input) {
|
|
|
107
107
|
...(input.run.continuation_context === undefined
|
|
108
108
|
? {}
|
|
109
109
|
: { continuation_context: input.run.continuation_context }),
|
|
110
|
+
...(input.run.correction_context === undefined
|
|
111
|
+
? {}
|
|
112
|
+
: { correction_context: input.run.correction_context }),
|
|
110
113
|
...(sourceMessages.length === 0 ? {} : { source_messages: sourceMessages }),
|
|
111
114
|
...(clarificationRounds.length === 0
|
|
112
115
|
? {}
|