@xfey/tutti 0.1.94 → 0.1.95
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 +134 -0
- package/dist/control-plane/command-recovery.js +9 -1
- package/dist/control-plane/index.d.ts +3 -2
- package/dist/control-plane/index.js +14 -0
- 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/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 +305 -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,134 @@
|
|
|
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.prepare("SELECT * FROM tasks WHERE id = ?").get(input.task_id);
|
|
52
|
+
if (taskRow === undefined) {
|
|
53
|
+
return { disposition: { kind: "task_not_failed" } };
|
|
54
|
+
}
|
|
55
|
+
if (taskRow.latest_run_result_id !== input.expected_latest_run_result_id) {
|
|
56
|
+
return {
|
|
57
|
+
disposition: {
|
|
58
|
+
kind: "latest_run_result_changed",
|
|
59
|
+
...(taskRow.latest_run_result_id === null
|
|
60
|
+
? {}
|
|
61
|
+
: { latest_run_result_id: taskRow.latest_run_result_id }),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (taskRow.status === "running" && taskRow.current_activity_ref !== null) {
|
|
66
|
+
return {
|
|
67
|
+
disposition: {
|
|
68
|
+
kind: "already_running",
|
|
69
|
+
activity_ref: taskRow.current_activity_ref,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (taskRow.status !== "failed") {
|
|
74
|
+
return { disposition: { kind: "task_not_failed" } };
|
|
75
|
+
}
|
|
76
|
+
const runResultRow = tx
|
|
77
|
+
.prepare("SELECT * FROM run_results WHERE id = ?")
|
|
78
|
+
.get(input.expected_latest_run_result_id);
|
|
79
|
+
if (runResultRow === undefined || runResultRow.task_id !== input.task_id) {
|
|
80
|
+
return { disposition: { kind: "latest_run_result_changed" } };
|
|
81
|
+
}
|
|
82
|
+
const result = parseJson(runResultRow.result_json, {});
|
|
83
|
+
if (result.retry_activity_ref !== undefined) {
|
|
84
|
+
return {
|
|
85
|
+
disposition: {
|
|
86
|
+
kind: "retry_already_started",
|
|
87
|
+
activity_ref: result.retry_activity_ref,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (!failedResultCanRetry(runResultRow, result)) {
|
|
92
|
+
return { disposition: { kind: "retry_requires_resolution" } };
|
|
93
|
+
}
|
|
94
|
+
const updatedAt = nowIso(input.now);
|
|
95
|
+
tx.prepare("UPDATE run_results SET result_json = ? WHERE id = ?").run(stringifyJson({
|
|
96
|
+
...result,
|
|
97
|
+
retry_action: "retried",
|
|
98
|
+
retry_activity_ref: input.activity_ref,
|
|
99
|
+
retried_at: updatedAt,
|
|
100
|
+
}), input.expected_latest_run_result_id);
|
|
101
|
+
tx.prepare(`
|
|
102
|
+
UPDATE tasks
|
|
103
|
+
SET status = 'running',
|
|
104
|
+
current_activity_ref = ?,
|
|
105
|
+
updated_at = ?
|
|
106
|
+
WHERE id = ?
|
|
107
|
+
AND status = 'failed'
|
|
108
|
+
`).run(input.activity_ref, updatedAt, input.task_id);
|
|
109
|
+
const detail = readTaskDetailProjection(tx, input.task_id);
|
|
110
|
+
if (detail === null) {
|
|
111
|
+
throw new Error("Task retry could not read task detail.");
|
|
112
|
+
}
|
|
113
|
+
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);
|
|
114
|
+
recordReadNotificationEvent(tx, {
|
|
115
|
+
scope: "worklist",
|
|
116
|
+
source_kind: "task_run_started",
|
|
117
|
+
source_ref: input.task_id,
|
|
118
|
+
occurred_at: updatedAt,
|
|
119
|
+
});
|
|
120
|
+
const task = readTaskProjection(tx, input.task_id);
|
|
121
|
+
if (task === null) {
|
|
122
|
+
throw new Error("Task retry did not produce a readable task.");
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
disposition: {
|
|
126
|
+
kind: "accepted",
|
|
127
|
+
task_id: input.task_id,
|
|
128
|
+
activity_ref: input.activity_ref,
|
|
129
|
+
retried_run_result_id: input.expected_latest_run_result_id,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//# 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";
|
|
@@ -28,6 +28,7 @@ export declare class Phase5ControlPlane {
|
|
|
28
28
|
constructor(options: Phase5ControlPlaneOptions);
|
|
29
29
|
getExecutionStatus(): ExecutionStatusProjection;
|
|
30
30
|
runSchedulerNow(payload?: RunSchedulerNowPayload): ControlPlaneCommandResult<RunSchedulerNowDisposition, never>;
|
|
31
|
+
retryTask(taskId: TaskId, payload: RetryTaskPayload): ControlPlaneCommandResult<RetryTaskDisposition, never>;
|
|
31
32
|
runStartupRecovery(options?: StartupRecoveryOptions): StartupRecoveryResult;
|
|
32
33
|
startScratchpadRefresh(options?: {
|
|
33
34
|
lane?: ProcedureLane;
|
|
@@ -16,6 +16,7 @@ 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";
|
|
21
22
|
export class Phase5ControlPlane {
|
|
@@ -82,6 +83,19 @@ export class Phase5ControlPlane {
|
|
|
82
83
|
now: this.now,
|
|
83
84
|
});
|
|
84
85
|
}
|
|
86
|
+
retryTask(taskId, payload) {
|
|
87
|
+
return retryControlPlaneTask({
|
|
88
|
+
store: this.store,
|
|
89
|
+
taskId,
|
|
90
|
+
payload,
|
|
91
|
+
resolveRunPipeline: this.resolveRunPipeline,
|
|
92
|
+
readActiveActivity: () => this.readSchedulerBlocker(),
|
|
93
|
+
startRunForTask: (runInput) => {
|
|
94
|
+
void this.startRunForTask(runInput);
|
|
95
|
+
},
|
|
96
|
+
now: this.now,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
85
99
|
runStartupRecovery(options = {}) {
|
|
86
100
|
return runControlPlaneStartupRecovery({
|
|
87
101
|
store: this.store,
|
|
@@ -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
|
? {}
|
|
@@ -85,6 +85,18 @@ export type RunContinuationContext = {
|
|
|
85
85
|
};
|
|
86
86
|
};
|
|
87
87
|
export type RunCorrectionContext = {
|
|
88
|
+
previous_provider_result: {
|
|
89
|
+
decision: "completed" | "completed_no_repo_changes";
|
|
90
|
+
summary: string;
|
|
91
|
+
user_note_candidate?: string;
|
|
92
|
+
} | {
|
|
93
|
+
decision: "needs_human";
|
|
94
|
+
summary: string;
|
|
95
|
+
clarification_request_payload: ClarificationRequestPayload;
|
|
96
|
+
} | {
|
|
97
|
+
decision: "failed";
|
|
98
|
+
summary: string;
|
|
99
|
+
};
|
|
88
100
|
summary: string;
|
|
89
101
|
reason_code: string;
|
|
90
102
|
guidance: string;
|
|
@@ -19,6 +19,7 @@ export type OpenAiProcedureWorkflowRunnerOptions = {
|
|
|
19
19
|
turnTimeoutMs?: number;
|
|
20
20
|
baseEnv?: NodeJS.ProcessEnv;
|
|
21
21
|
tuttiHome?: string;
|
|
22
|
+
projectId?: ProjectId;
|
|
22
23
|
userSkillsRoot?: string;
|
|
23
24
|
builtInSkillsRoot?: string;
|
|
24
25
|
agentContext?: CodexAppServerAgentContextRuntime;
|
|
@@ -7,7 +7,9 @@ import { createOpenAiStructuredOutputClient, runOpenAiStructuredOutput, } from "
|
|
|
7
7
|
import { readOpenAiApiKeyCredential } from "../../providers/openai/credential-store.js";
|
|
8
8
|
import { resolveOpenAiProviderConfig, } from "../../providers/openai/provider-config.js";
|
|
9
9
|
import { SCRATCHPAD_REFRESH_OUTPUT_SCHEMA, TASK_COMPILE_OUTPUT_SCHEMA, PROJECT_CONTEXT_BOOTSTRAP_OUTPUT_SCHEMA, CONTEXT_SYNC_OUTPUT_SCHEMA, PROJECT_BRIEF_REFRESH_OUTPUT_SCHEMA, FOLLOW_UP_CHECK_OUTPUT_SCHEMA, REFERENCE_FILE_SUMMARY_OUTPUT_SCHEMA, isContextSyncWorkflowOutput, isFollowUpCheckStructuredOutput, isProjectBriefRefreshWorkflowOutput, isProjectContextBootstrapWorkflowOutput, isReferenceFileSummaryStructuredOutput, isScratchpadRefreshStructuredOutput, isTaskCompileStructuredOutput, } from "./structured-output.js";
|
|
10
|
-
import { runCodexAppServerReadOnlyProcedure } from "../../providers/openai/app-server/read-only-procedure.js";
|
|
10
|
+
import { runCodexAppServerReadOnlyProcedure, } from "../../providers/openai/app-server/read-only-procedure.js";
|
|
11
|
+
import { CODEX_APP_SERVER_VERSION } from "../../providers/openai/codex-app-server.js";
|
|
12
|
+
import { acquireCodexTaskSessionLock, createProviderFingerprint, markCodexTaskSessionArchived, openCodexDurableSession, recordCodexSessionArchiveFailure, recordCodexTaskSessionThread, } from "../../providers/openai/app-server/session-store.js";
|
|
11
13
|
import { getProjectUserSkillsRoot } from "../../skills/index.js";
|
|
12
14
|
function readOnlyAppServerOptions(options, prompt, outputSchema, skillSelectionName) {
|
|
13
15
|
if (options.readOnlyAppServer === undefined) {
|
|
@@ -70,6 +72,56 @@ function readOnlyRuntimeTelemetry(context, stageId) {
|
|
|
70
72
|
},
|
|
71
73
|
};
|
|
72
74
|
}
|
|
75
|
+
async function runDurableReadOnlyProcedure(input) {
|
|
76
|
+
const runtime = input.options.readOnlyAppServer;
|
|
77
|
+
if (runtime?.tuttiHome === undefined ||
|
|
78
|
+
runtime.projectId === undefined ||
|
|
79
|
+
input.context === undefined) {
|
|
80
|
+
return runCodexAppServerReadOnlyProcedure(input.invocation);
|
|
81
|
+
}
|
|
82
|
+
let session = openCodexDurableSession({
|
|
83
|
+
tuttiHome: runtime.tuttiHome,
|
|
84
|
+
projectId: runtime.projectId,
|
|
85
|
+
kind: input.kind,
|
|
86
|
+
ownerRef: input.context.workflow_ref,
|
|
87
|
+
compatibility: {
|
|
88
|
+
codexVersion: CODEX_APP_SERVER_VERSION,
|
|
89
|
+
model: input.options.config.default_model,
|
|
90
|
+
providerFingerprint: createProviderFingerprint({
|
|
91
|
+
providerKind: "openai",
|
|
92
|
+
...(input.options.config.api_base_url === undefined
|
|
93
|
+
? {}
|
|
94
|
+
: { apiBaseUrl: input.options.config.api_base_url }),
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const release = acquireCodexTaskSessionLock(session);
|
|
99
|
+
try {
|
|
100
|
+
return await runCodexAppServerReadOnlyProcedure({
|
|
101
|
+
...input.invocation,
|
|
102
|
+
session: {
|
|
103
|
+
codexHome: session.codexHome,
|
|
104
|
+
...(session.metadata.thread_id === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { threadId: session.metadata.thread_id }),
|
|
107
|
+
...(session.metadata.lifecycle === "archived" ? { unarchive: true } : {}),
|
|
108
|
+
onThreadReady: (threadId) => {
|
|
109
|
+
session = recordCodexTaskSessionThread(session, threadId);
|
|
110
|
+
},
|
|
111
|
+
archiveAfterTurn: input.archiveAfterTurn,
|
|
112
|
+
onThreadArchived: () => {
|
|
113
|
+
session = markCodexTaskSessionArchived(session);
|
|
114
|
+
},
|
|
115
|
+
onThreadArchiveFailure: () => {
|
|
116
|
+
recordCodexSessionArchiveFailure(session);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
release();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
73
125
|
function referenceSummarySourcePath(options, input) {
|
|
74
126
|
if (options.readOnlyAppServer === undefined) {
|
|
75
127
|
throw new Error("Codex app-server read-only Procedure adapter is not configured");
|
|
@@ -284,12 +336,18 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
284
336
|
workflow_input_json: buildTaskCompilePromptInput(input),
|
|
285
337
|
},
|
|
286
338
|
});
|
|
287
|
-
const result = await
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
339
|
+
const result = await runDurableReadOnlyProcedure({
|
|
340
|
+
options,
|
|
341
|
+
context,
|
|
342
|
+
kind: "task_compile",
|
|
343
|
+
archiveAfterTurn: (output) => "proposal" in output.result,
|
|
344
|
+
invocation: {
|
|
345
|
+
...readOnlyAppServerOptions(options, instructions, TASK_COMPILE_OUTPUT_SCHEMA, "task_compile"),
|
|
346
|
+
...readOnlyRuntimeTelemetry(context, "task_compile"),
|
|
347
|
+
validateOutput: isTaskCompileStructuredOutput,
|
|
348
|
+
networkAccess: false,
|
|
349
|
+
webSearchMode: "disabled",
|
|
350
|
+
},
|
|
293
351
|
});
|
|
294
352
|
recordUsage(options, "task_compile", result, {
|
|
295
353
|
workflow_ref: context?.workflow_ref ?? input.workflow_ref,
|
|
@@ -333,10 +391,16 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
333
391
|
workflow_input_json: input,
|
|
334
392
|
},
|
|
335
393
|
});
|
|
336
|
-
const result = await
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
394
|
+
const result = await runDurableReadOnlyProcedure({
|
|
395
|
+
options,
|
|
396
|
+
context,
|
|
397
|
+
kind: "project_context_bootstrap",
|
|
398
|
+
archiveAfterTurn: () => true,
|
|
399
|
+
invocation: {
|
|
400
|
+
...readOnlyAppServerOptions(options, instructions, PROJECT_CONTEXT_BOOTSTRAP_OUTPUT_SCHEMA, "project_context_bootstrap"),
|
|
401
|
+
...readOnlyRuntimeTelemetry(context, "project_context_bootstrap"),
|
|
402
|
+
validateOutput: isProjectContextBootstrapWorkflowOutput,
|
|
403
|
+
},
|
|
340
404
|
});
|
|
341
405
|
recordUsage(options, "project_context_bootstrap", result, {
|
|
342
406
|
...(context?.workflow_ref === undefined ? {} : { workflow_ref: context.workflow_ref }),
|
|
@@ -349,10 +413,16 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
349
413
|
templateKey: "procedures.context_sync",
|
|
350
414
|
...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
|
|
351
415
|
});
|
|
352
|
-
const result = await
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
416
|
+
const result = await runDurableReadOnlyProcedure({
|
|
417
|
+
options,
|
|
418
|
+
context,
|
|
419
|
+
kind: "context_sync",
|
|
420
|
+
archiveAfterTurn: () => true,
|
|
421
|
+
invocation: {
|
|
422
|
+
...readOnlyAppServerOptions(options, instructions, CONTEXT_SYNC_OUTPUT_SCHEMA, "context_sync"),
|
|
423
|
+
...readOnlyRuntimeTelemetry(context, "context_sync"),
|
|
424
|
+
validateOutput: isContextSyncWorkflowOutput,
|
|
425
|
+
},
|
|
356
426
|
});
|
|
357
427
|
recordUsage(options, "context_sync", result, {
|
|
358
428
|
workflow_ref: context?.workflow_ref ?? input.workflow_ref,
|
|
@@ -426,6 +496,7 @@ export function resolveProjectOpenAiProcedureWorkflowRunner(options) {
|
|
|
426
496
|
apiKey: credential.secret,
|
|
427
497
|
workspaceRoot: options.workspaceRoot,
|
|
428
498
|
tuttiHome: options.tuttiHome,
|
|
499
|
+
projectId: options.projectId,
|
|
429
500
|
userSkillsRoot: getProjectUserSkillsRoot(options.tuttiHome, options.projectId),
|
|
430
501
|
...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
|
|
431
502
|
...(options.invocationCoordinator === undefined
|