@xfey/tutti 0.1.93 → 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.
Files changed (85) hide show
  1. package/dist/collaboration-state/clarification-records.d.ts +2 -1
  2. package/dist/collaboration-state/clarification-records.js +18 -0
  3. package/dist/collaboration-state/index.d.ts +3 -1
  4. package/dist/collaboration-state/index.js +2 -1
  5. package/dist/collaboration-state/messages.js +15 -0
  6. package/dist/collaboration-state/run-recording.js +18 -1
  7. package/dist/collaboration-state/run-results.js +6 -0
  8. package/dist/collaboration-state/task-retry.d.ts +16 -0
  9. package/dist/collaboration-state/task-retry.js +134 -0
  10. package/dist/control-plane/command-recovery.js +9 -1
  11. package/dist/control-plane/index.d.ts +3 -2
  12. package/dist/control-plane/index.js +14 -0
  13. package/dist/control-plane/run-retry.d.ts +19 -0
  14. package/dist/control-plane/run-retry.js +103 -0
  15. package/dist/control-plane/run-start.d.ts +2 -1
  16. package/dist/control-plane/run-start.js +18 -1
  17. package/dist/control-plane/task-compile-clarification-context.d.ts +4 -0
  18. package/dist/control-plane/task-compile-clarification-context.js +9 -22
  19. package/dist/control-plane/task-compile-continuation.js +2 -0
  20. package/dist/control-plane/task-compile-start.js +2 -0
  21. package/dist/control-plane/task-source-context-types.d.ts +11 -0
  22. package/dist/control-plane/task-source-context-types.js +2 -0
  23. package/dist/control-plane/task-source-context.d.ts +7 -0
  24. package/dist/control-plane/task-source-context.js +31 -0
  25. package/dist/control-plane/types.d.ts +15 -0
  26. package/dist/control-plane/workflows/openai.d.ts +1 -0
  27. package/dist/control-plane/workflows/openai.js +87 -15
  28. package/dist/control-plane/workflows/types.d.ts +6 -14
  29. package/dist/control-plane/worklist-terminal-feedback.js +3 -0
  30. package/dist/prompt-templates/index.d.ts +1 -1
  31. package/dist/prompt-templates/index.js +4 -3
  32. package/dist/providers/openai/app-server/durable-session-smoke-cli.d.ts +2 -0
  33. package/dist/providers/openai/app-server/durable-session-smoke-cli.js +229 -0
  34. package/dist/providers/openai/app-server/read-only-procedure.d.ts +10 -0
  35. package/dist/providers/openai/app-server/read-only-procedure.js +42 -13
  36. package/dist/providers/openai/app-server/session-store.d.ts +74 -0
  37. package/dist/providers/openai/app-server/session-store.js +305 -0
  38. package/dist/providers/openai/app-server/workspace-write-run.d.ts +17 -4
  39. package/dist/providers/openai/app-server/workspace-write-run.js +92 -28
  40. package/dist/providers/openai/codex-app-server.d.ts +1 -0
  41. package/dist/providers/openai/codex-app-server.js +2 -0
  42. package/dist/run-pipeline/openai.d.ts +1 -0
  43. package/dist/run-pipeline/openai.js +15 -43
  44. package/dist/run-pipeline/task-run-invocation.js +371 -93
  45. package/dist/server-shell/http/routes/project-api/execution-routes.js +32 -2
  46. package/dist/server-shell/http/routes/project-api/invalidations.d.ts +2 -1
  47. package/dist/server-shell/http/routes/project-api/invalidations.js +13 -0
  48. package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +21 -0
  49. package/dist/server-shell/http/routes/project-api/openapi-execution-routes.js +12 -1
  50. package/dist/server-shell/http/routes/project-api/openapi.d.ts +7 -0
  51. package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +1 -0
  52. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  53. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  54. package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +5 -0
  55. package/node_modules/@tutti/shared/dist/schemas/api/messages.js +1 -0
  56. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +1 -0
  57. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
  58. package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +1 -0
  59. package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +4 -0
  60. package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +3 -0
  61. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +33 -0
  62. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +35 -1
  63. package/package.json +1 -1
  64. package/prompts/README.md +3 -1
  65. package/prompts/artifacts/README.md +1 -1
  66. package/prompts/artifacts/applicability.md +1 -1
  67. package/prompts/procedures/README.md +3 -3
  68. package/prompts/procedures/follow-up-check.md +1 -1
  69. package/prompts/procedures/reference-file-summary.md +1 -1
  70. package/prompts/procedures/task-compile.md +7 -7
  71. package/prompts/prompt-flow-map.md +16 -10
  72. package/prompts/runs/README.md +11 -16
  73. package/prompts/runs/task-execute.md +86 -0
  74. package/prompts/runs/task-finalize.md +11 -0
  75. package/prompts/runs/task-recovery.md +15 -0
  76. package/prompts/runs/task-result-contract.md +64 -0
  77. package/web/assets/{homepage-motion-scene-CLTkixIk.js → homepage-motion-scene-BFo4r4nz.js} +1 -1
  78. package/web/assets/index-CNIl4TF8.js +69 -0
  79. package/web/assets/index-quWLFJgD.css +1 -0
  80. package/web/index.html +2 -2
  81. package/prompts/runs/task-continuation.md +0 -199
  82. package/prompts/runs/task-retry.md +0 -207
  83. package/prompts/runs/task-run.md +0 -186
  84. package/web/assets/index-BTNYElB-.js +0 -69
  85. package/web/assets/index-Deu_eF5K.css +0 -1
@@ -1,4 +1,4 @@
1
- import type { ClarificationRoundRef, ClarificationThreadRef } from "@tutti/shared/ids";
1
+ import type { ClarificationRoundRef, ClarificationThreadRef, WorkflowInvocationRef } from "@tutti/shared/ids";
2
2
  import type { ActiveClarificationProjection, ClarificationRoundProjection, ClarificationThreadProjection, GetClarificationThreadResponse } from "@tutti/shared/schemas/api";
3
3
  import type { SqliteDatabase } from "../store/index.js";
4
4
  import type { SealedClarificationRoundWithoutSuccessor } from "./types.js";
@@ -14,5 +14,6 @@ export declare function readClarificationRoundProjection(db: SqliteDatabase, rou
14
14
  export declare function readClarificationRoundsForThread(db: SqliteDatabase, threadId: ClarificationThreadRef, options?: {
15
15
  throughRoundIndex?: number;
16
16
  }): ClarificationRoundProjection[];
17
+ export declare function readTaskCompileClarificationRoundsByWorkflowRef(db: SqliteDatabase, workflowRef: WorkflowInvocationRef): ClarificationRoundProjection[];
17
18
  export declare function readClarificationThreadResponse(db: SqliteDatabase, threadId: ClarificationThreadRef): GetClarificationThreadResponse | null;
18
19
  //# sourceMappingURL=clarification-records.d.ts.map
@@ -165,6 +165,24 @@ export function readClarificationRoundsForThread(db, threadId, options = {}) {
165
165
  .all(threadId, options.throughRoundIndex);
166
166
  return rows.map((row) => mapClarificationRoundRow(row));
167
167
  }
168
+ export function readTaskCompileClarificationRoundsByWorkflowRef(db, workflowRef) {
169
+ const threads = db
170
+ .prepare(`
171
+ SELECT id
172
+ FROM clarification_threads
173
+ WHERE owner_kind = 'task_compile'
174
+ AND owner_workflow_ref = ?
175
+ ORDER BY created_at ASC, id ASC
176
+ `)
177
+ .all(workflowRef);
178
+ if (threads.length === 0) {
179
+ return [];
180
+ }
181
+ if (threads.length !== 1 || threads[0] === undefined) {
182
+ throw new Error("Task Compile workflow has multiple clarification threads");
183
+ }
184
+ return readClarificationRoundsForThread(db, threads[0].id);
185
+ }
168
186
  export function readClarificationThreadResponse(db, threadId) {
169
187
  const threadRow = readClarificationThreadRow(db, threadId);
170
188
  if (threadRow === null) {
@@ -4,12 +4,14 @@ export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystem
4
4
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
6
  export { captureTaskCompileContext, completeTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, rollbackTaskCompileContext, taskCompileContextSnapshot, } from "./task-compile-context.js";
7
- export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, } from "./clarification-records.js";
7
+ export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readTaskCompileClarificationRoundsByWorkflowRef, readSealedClarificationRoundsWithoutSuccessor, } from "./clarification-records.js";
8
8
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
9
9
  export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, } from "./clarification-rounds.js";
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";
@@ -4,12 +4,13 @@ export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystem
4
4
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
6
  export { captureTaskCompileContext, completeTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, rollbackTaskCompileContext, taskCompileContextSnapshot, } from "./task-compile-context.js";
7
- export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, } from "./clarification-records.js";
7
+ export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readTaskCompileClarificationRoundsByWorkflowRef, readSealedClarificationRoundsWithoutSuccessor, } from "./clarification-records.js";
8
8
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
9
9
  export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, } from "./clarification-rounds.js";
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 resultJson = { ...input.result };
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;
@@ -1,4 +1,7 @@
1
+ import { readScratchpadReceiptSourceRefs } from "../collaboration-state/index.js";
1
2
  import { materializeWorkflowRecentReferences } from "./recent-references.js";
3
+ import { readCompletedTaskCompileClarificationRounds } from "./task-compile-clarification-context.js";
4
+ import { materializePromptSourceMessages } from "./task-source-context.js";
2
5
  import { recordRunPipelineFailure, recordRunPipelineResult, } from "./run-result-recording.js";
3
6
  import { projectBriefShouldRefreshForUpdatedPaths, refreshProjectBriefProjection, } from "./project-brief-refresh.js";
4
7
  function runLineageFields(lineage) {
@@ -80,12 +83,19 @@ export function startControlPlaneRunForTask(input) {
80
83
  const pipelineStartedAt = input.now();
81
84
  input.logger?.info(lifecycleFields, "Run pipeline started");
82
85
  try {
86
+ const workflowRef = input.run.task
87
+ .created_from_workflow_ref;
88
+ const sourceMessages = materializePromptSourceMessages(input.store.db, readScratchpadReceiptSourceRefs(input.store.db, workflowRef));
89
+ const clarificationRounds = readCompletedTaskCompileClarificationRounds({
90
+ store: input.store,
91
+ workflowRef,
92
+ });
83
93
  const recentReferences = input.projectContext === undefined
84
94
  ? []
85
95
  : materializeWorkflowRecentReferences({
86
96
  db: input.store.db,
87
97
  workspaceRoot: input.projectContext.workspaceRoot,
88
- workflowRef: input.run.task.created_from_workflow_ref,
98
+ workflowRef,
89
99
  now: input.now,
90
100
  });
91
101
  const pipelineResult = await input.run.runner.runTask({
@@ -97,6 +107,13 @@ export function startControlPlaneRunForTask(input) {
97
107
  ...(input.run.continuation_context === undefined
98
108
  ? {}
99
109
  : { continuation_context: input.run.continuation_context }),
110
+ ...(input.run.correction_context === undefined
111
+ ? {}
112
+ : { correction_context: input.run.correction_context }),
113
+ ...(sourceMessages.length === 0 ? {} : { source_messages: sourceMessages }),
114
+ ...(clarificationRounds.length === 0
115
+ ? {}
116
+ : { clarification_rounds: clarificationRounds }),
100
117
  ...(recentReferences.length === 0
101
118
  ? {}
102
119
  : { recent_references: recentReferences }),
@@ -6,4 +6,8 @@ export declare function readTaskCompileClarificationRounds(input: {
6
6
  workflowRef: WorkflowInvocationRef;
7
7
  throughRoundId: ClarificationRoundRef;
8
8
  }): NonNullable<TaskCompileWorkflowInput["clarification_rounds"]>;
9
+ export declare function readCompletedTaskCompileClarificationRounds(input: {
10
+ store: HostProjectStore;
11
+ workflowRef: WorkflowInvocationRef;
12
+ }): NonNullable<TaskCompileWorkflowInput["clarification_rounds"]>;
9
13
  //# sourceMappingURL=task-compile-clarification-context.d.ts.map
@@ -1,8 +1,4 @@
1
- import { readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readMessageProjectionById, } from "../collaboration-state/index.js";
2
- import { ScratchpadSourceBudgetExceededError, truncateTextMiddle } from "./scratchpad-source-messages.js";
3
- const MAX_CLARIFICATION_MESSAGE_CHARS = 4_000;
4
- const MAX_CLARIFICATION_MESSAGES_TOTAL_CHARS = 16_000;
5
- const MIN_CLARIFICATION_MESSAGE_CHARS = 160;
1
+ import { readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readMessageProjectionById, readTaskCompileClarificationRoundsByWorkflowRef, } from "../collaboration-state/index.js";
6
2
  function answerMessage(store, messageId, roundId) {
7
3
  const message = readMessageProjectionById(store.db, messageId);
8
4
  if (message === null ||
@@ -36,14 +32,17 @@ export function readTaskCompileClarificationRounds(input) {
36
32
  const rounds = readClarificationRoundsForThread(input.store.db, throughRound.thread_id, {
37
33
  throughRoundIndex: throughRound.round_index,
38
34
  });
39
- const contextRounds = rounds.map((round) => {
35
+ return materializeTaskCompileClarificationRounds(input.store, rounds);
36
+ }
37
+ function materializeTaskCompileClarificationRounds(store, rounds) {
38
+ return rounds.map((round) => {
40
39
  if (round.status !== "sealed") {
41
40
  throw new Error("Task Compile clarification context cannot include a writable round");
42
41
  }
43
42
  const answerMessages = (round.submitted_message_refs ?? [])
44
43
  .filter((messageId) => messageId !== round.opening_message_id && messageId !== round.card_message_id)
45
44
  .flatMap((messageId) => {
46
- const mapped = answerMessage(input.store, messageId, round.id);
45
+ const mapped = answerMessage(store, messageId, round.id);
47
46
  return mapped === null ? [] : [mapped];
48
47
  });
49
48
  return {
@@ -51,20 +50,8 @@ export function readTaskCompileClarificationRounds(input) {
51
50
  answer_messages: answerMessages,
52
51
  };
53
52
  });
54
- const answerCount = contextRounds.reduce((count, round) => count + round.answer_messages.length, 0);
55
- if (answerCount === 0) {
56
- return contextRounds;
57
- }
58
- if (answerCount * MIN_CLARIFICATION_MESSAGE_CHARS > MAX_CLARIFICATION_MESSAGES_TOTAL_CHARS) {
59
- throw new ScratchpadSourceBudgetExceededError();
60
- }
61
- const perMessageChars = Math.min(MAX_CLARIFICATION_MESSAGE_CHARS, Math.floor(MAX_CLARIFICATION_MESSAGES_TOTAL_CHARS / answerCount));
62
- return contextRounds.map((round) => ({
63
- request_payload: round.request_payload,
64
- answer_messages: round.answer_messages.map((message) => ({
65
- ...message,
66
- text: truncateTextMiddle(message.text, perMessageChars),
67
- })),
68
- }));
53
+ }
54
+ export function readCompletedTaskCompileClarificationRounds(input) {
55
+ return materializeTaskCompileClarificationRounds(input.store, readTaskCompileClarificationRoundsByWorkflowRef(input.store.db, input.workflowRef));
69
56
  }
70
57
  //# sourceMappingURL=task-compile-clarification-context.js.map
@@ -3,6 +3,7 @@ import { withHostStoreTransaction } from "../store/index.js";
3
3
  import { clarificationInvalidates } from "./invalidations.js";
4
4
  import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
5
5
  import { readTaskCompileClarificationRounds } from "./task-compile-clarification-context.js";
6
+ import { materializePromptSourceMessages } from "./task-source-context.js";
6
7
  import { logProcedureExecutionError } from "./procedure-logging.js";
7
8
  import { materializeRecentReferences, readRecentReferenceSources, } from "./recent-references.js";
8
9
  export function startControlPlaneTaskCompileContinuation(options) {
@@ -54,6 +55,7 @@ export function startControlPlaneTaskCompileContinuation(options) {
54
55
  const output = await runnerResolution.runner.runTaskCompile({
55
56
  workflow_ref: workflowRef,
56
57
  scratchpad: taskCompileContext.scratchpad_snapshot,
58
+ source_messages: materializePromptSourceMessages(options.store.db, taskCompileContext.sources),
57
59
  worklist: readWorklistProjection(options.store.db),
58
60
  ...(recentReferences.length === 0
59
61
  ? {}