@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,67 +1,106 @@
1
1
  import { renderPromptTemplate } from "../prompt-templates/index.js";
2
- import { runCodexAppServerWorkspaceWriteRun, } from "../providers/openai/app-server/workspace-write-run.js";
2
+ import { CODEX_APP_SERVER_VERSION } from "../providers/openai/codex-app-server.js";
3
+ import { CodexAppServerWorkspaceWriteRunError, runCodexAppServerWorkspaceWriteRun, } from "../providers/openai/app-server/workspace-write-run.js";
4
+ import { acquireCodexTaskSessionLock, createProviderFingerprint, markCodexTaskSessionArchived, openCodexTaskSession, readCodexSessionRecoveryEvidence, recordCodexSessionArchiveFailure, recordCodexTaskSessionThread, writeCodexSessionRecoveryEvidence, } from "../providers/openai/app-server/session-store.js";
3
5
  import { TASK_RUN_OUTPUT_SCHEMA, isInitialTaskRunOutput, normalizeTaskRunOutput, } from "./task-run-output.js";
4
6
  function taskContractRunInput(input) {
5
7
  return {
6
8
  title: input.task.title,
7
9
  goal: input.task_detail.detail.contract.goal,
8
10
  scope: input.task_detail.detail.contract.scope,
11
+ ...(input.source_messages === undefined || input.source_messages.length === 0
12
+ ? {}
13
+ : { source_messages: input.source_messages.map((message) => ({ ...message })) }),
14
+ ...(input.clarification_rounds === undefined || input.clarification_rounds.length === 0
15
+ ? {}
16
+ : {
17
+ clarification_rounds: input.clarification_rounds.map((round) => ({
18
+ request: {
19
+ ...(round.request_payload.title === undefined
20
+ ? {}
21
+ : { title: round.request_payload.title }),
22
+ summary: round.request_payload.summary,
23
+ request: round.request_payload.request,
24
+ },
25
+ answer_messages: round.answer_messages.map((message) => ({ ...message })),
26
+ })),
27
+ }),
9
28
  ...(input.recent_references === undefined || input.recent_references.length === 0
10
29
  ? {}
11
30
  : { recent_references: input.recent_references }),
12
31
  };
13
32
  }
14
- function initialTaskRunInput(input) {
15
- return taskContractRunInput(input);
16
- }
17
- function continuationTaskRunInput(input) {
18
- return {
19
- ...taskContractRunInput(input),
20
- ...(input.continuation_context === undefined
21
- ? {}
22
- : { continuation: input.continuation_context }),
23
- };
33
+ function renderResultContract(options) {
34
+ return renderPromptTemplate({
35
+ templateKey: "runs.task_result_contract",
36
+ ...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
37
+ });
24
38
  }
25
- function retryTaskRunInput(input) {
26
- if (input.correction_context === undefined) {
27
- throw new Error("Retry task run requires correction context.");
39
+ function createExecutePrompt(options, input, session) {
40
+ let executionInput;
41
+ if (input.run_lineage.kind === "continuation") {
42
+ if (input.continuation_context === undefined) {
43
+ throw new CodexAppServerWorkspaceWriteRunError("session_unavailable", "Task continuation context is unavailable.");
44
+ }
45
+ executionInput = {
46
+ continuation: input.continuation_context,
47
+ ...(session === undefined ||
48
+ session.disposition !== "resumed" ||
49
+ session.metadata.thread_id === undefined
50
+ ? { task_context: taskContractRunInput(input) }
51
+ : {}),
52
+ };
28
53
  }
29
- return {
30
- ...taskContractRunInput(input),
31
- correction: input.correction_context,
32
- };
54
+ else {
55
+ executionInput = taskContractRunInput(input);
56
+ }
57
+ return renderPromptTemplate({
58
+ templateKey: "runs.task_execute",
59
+ ...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
60
+ variables: { execution_input_json: executionInput },
61
+ });
33
62
  }
34
- function createRunPrompt(options, input) {
35
- const templateKey = input.run_lineage.kind === "continuation"
36
- ? "runs.task_continuation"
37
- : input.run_lineage.kind === "retry"
38
- ? "runs.task_retry"
39
- : "runs.task_run";
63
+ function createFinalizePrompt(options) {
40
64
  return renderPromptTemplate({
41
- templateKey,
65
+ templateKey: "runs.task_finalize",
42
66
  ...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
67
+ variables: { result_contract: renderResultContract(options) },
68
+ });
69
+ }
70
+ function createRecoveryPrompt(input) {
71
+ const needsCanonicalContext = input.session === undefined || input.session.metadata.thread_id === undefined;
72
+ return renderPromptTemplate({
73
+ templateKey: "runs.task_recovery",
74
+ ...(input.options.promptsRoot === undefined
75
+ ? {}
76
+ : { promptsRoot: input.options.promptsRoot }),
43
77
  variables: {
44
- run_input_json: input.run_lineage.kind === "initial"
45
- ? initialTaskRunInput(input)
46
- : input.run_lineage.kind === "continuation"
47
- ? continuationTaskRunInput(input)
48
- : retryTaskRunInput(input),
78
+ recovery_input_json: {
79
+ ...(needsCanonicalContext
80
+ ? { task_context: taskContractRunInput(input.pipelineInput) }
81
+ : {}),
82
+ recovery: input.recovery,
83
+ },
84
+ result_contract: renderResultContract(input.options),
49
85
  },
50
86
  });
51
87
  }
52
- function usageSource(input) {
53
- switch (input.run_lineage.kind) {
54
- case "continuation":
55
- return "follow_up_run";
56
- case "retry":
57
- return "pipeline_self_correction";
58
- case "initial":
59
- return "workspace_write_run";
88
+ function usageSource(input, phase) {
89
+ if (phase === "finalize") {
90
+ return "task_result_finalize";
91
+ }
92
+ if (phase === "recovery") {
93
+ return "task_result_recovery";
60
94
  }
95
+ return input.run_lineage.kind === "continuation"
96
+ ? "follow_up_execute"
97
+ : input.run_lineage.kind === "retry"
98
+ ? "manual_retry_execute"
99
+ : "workspace_write_execute";
61
100
  }
62
- export async function runWorkspaceWriteTask(options, input) {
101
+ function createApprovalHandler(options, input) {
63
102
  const manager = options.approvalManager;
64
- const approvalHandler = manager === undefined
103
+ return manager === undefined
65
104
  ? undefined
66
105
  : (approvalInput) => manager.awaitApproval({
67
106
  request: approvalInput.request,
@@ -80,69 +119,308 @@ export async function runWorkspaceWriteTask(options, input) {
80
119
  },
81
120
  timeoutMs: approvalInput.context.turnTimeoutMs,
82
121
  });
83
- let result;
122
+ }
123
+ function openTaskSession(options, input) {
124
+ if (options.tuttiHome === undefined || options.projectId === undefined) {
125
+ return undefined;
126
+ }
84
127
  try {
85
- result = await runCodexAppServerWorkspaceWriteRun({
86
- apiKey: options.apiKey,
87
- workspaceRoot: options.workspaceRoot,
88
- prompt: createRunPrompt(options, input),
89
- outputSchema: TASK_RUN_OUTPUT_SCHEMA,
90
- validateOutput: (value) => isInitialTaskRunOutput(value),
91
- skillSelectionName: input.run_lineage.kind === "continuation" ? "follow_up_run" : "workspace_write_run",
92
- defaultModel: options.config.default_model,
93
- approvalContext: {
94
- workspaceRoot: options.workspaceRoot,
95
- taskId: input.task.id,
96
- activityRef: input.activity_ref,
97
- },
98
- ...(approvalHandler === undefined ? {} : { approvalHandler }),
99
- ...(options.config.api_base_url === undefined
100
- ? {}
101
- : { apiBaseUrl: options.config.api_base_url }),
102
- ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
103
- ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
104
- ...(options.builtInSkillsRoot === undefined
105
- ? {}
106
- : { builtInSkillsRoot: options.builtInSkillsRoot }),
107
- ...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
108
- ...(options.codexCommand === undefined ? {} : { codexCommand: options.codexCommand }),
109
- ...(options.requestTimeoutMs === undefined
110
- ? {}
111
- : { requestTimeoutMs: options.requestTimeoutMs }),
112
- ...(options.turnTimeoutMs === undefined ? {} : { turnTimeoutMs: options.turnTimeoutMs }),
113
- ...(options.baseEnv === undefined ? {} : { baseEnv: options.baseEnv }),
114
- ...(options.invocationCoordinator === undefined
115
- ? {}
116
- : { invocationCoordinator: options.invocationCoordinator }),
117
- ...(options.onLifecycleIncident === undefined
118
- ? {}
119
- : { onLifecycleIncident: options.onLifecycleIncident }),
120
- runtimeTelemetry: {
121
- activityRef: input.activity_ref,
122
- stageId: "provider_task_run",
123
- workspaceRoot: options.workspaceRoot,
124
- onSnapshot: (snapshot) => input.on_stage?.({
125
- stage: "provider_task_run",
126
- summary: "running Codex task implementation.",
127
- runtime: snapshot,
128
+ return openCodexTaskSession({
129
+ tuttiHome: options.tuttiHome,
130
+ projectId: options.projectId,
131
+ taskId: input.task.id,
132
+ compatibility: {
133
+ codexVersion: CODEX_APP_SERVER_VERSION,
134
+ model: options.config.default_model,
135
+ providerFingerprint: createProviderFingerprint({
136
+ providerKind: "openai",
137
+ ...(options.config.api_base_url === undefined
138
+ ? {}
139
+ : { apiBaseUrl: options.config.api_base_url }),
128
140
  }),
129
141
  },
130
142
  });
131
143
  }
132
- finally {
133
- options.approvalManager?.clearPending();
144
+ catch {
145
+ throw new CodexAppServerWorkspaceWriteRunError("session_unavailable", "The durable Codex task session is unavailable.");
146
+ }
147
+ }
148
+ async function runTaskTurn(input) {
149
+ let activeSession = input.session;
150
+ const invoke = () => runCodexAppServerWorkspaceWriteRun({
151
+ apiKey: input.options.apiKey,
152
+ workspaceRoot: input.options.workspaceRoot,
153
+ prompt: input.prompt,
154
+ sandboxMode: input.sandboxMode,
155
+ networkAccess: input.networkAccess,
156
+ defaultModel: input.options.config.default_model,
157
+ approvalContext: {
158
+ workspaceRoot: input.options.workspaceRoot,
159
+ taskId: input.pipelineInput.task.id,
160
+ activityRef: input.pipelineInput.activity_ref,
161
+ },
162
+ ...(input.outputSchema === undefined ? {} : { outputSchema: input.outputSchema }),
163
+ ...(input.validateOutput === undefined ? {} : { validateOutput: input.validateOutput }),
164
+ ...(input.skillSelectionName === undefined
165
+ ? {}
166
+ : { skillSelectionName: input.skillSelectionName }),
167
+ ...(input.approvalHandler === undefined
168
+ ? {}
169
+ : { approvalHandler: input.approvalHandler }),
170
+ ...(input.options.config.api_base_url === undefined
171
+ ? {}
172
+ : { apiBaseUrl: input.options.config.api_base_url }),
173
+ ...(input.options.tuttiHome === undefined ? {} : { tuttiHome: input.options.tuttiHome }),
174
+ ...(input.options.userSkillsRoot === undefined
175
+ ? {}
176
+ : { userSkillsRoot: input.options.userSkillsRoot }),
177
+ ...(input.options.builtInSkillsRoot === undefined
178
+ ? {}
179
+ : { builtInSkillsRoot: input.options.builtInSkillsRoot }),
180
+ ...(input.options.agentContext === undefined
181
+ ? {}
182
+ : { agentContext: input.options.agentContext }),
183
+ ...(input.options.codexCommand === undefined
184
+ ? {}
185
+ : { codexCommand: input.options.codexCommand }),
186
+ ...(input.options.requestTimeoutMs === undefined
187
+ ? {}
188
+ : { requestTimeoutMs: input.options.requestTimeoutMs }),
189
+ ...(input.options.turnTimeoutMs === undefined
190
+ ? {}
191
+ : { turnTimeoutMs: input.options.turnTimeoutMs }),
192
+ ...(input.options.baseEnv === undefined ? {} : { baseEnv: input.options.baseEnv }),
193
+ ...(input.options.invocationCoordinator === undefined
194
+ ? {}
195
+ : { invocationCoordinator: input.options.invocationCoordinator }),
196
+ ...(input.options.onLifecycleIncident === undefined
197
+ ? {}
198
+ : { onLifecycleIncident: input.options.onLifecycleIncident }),
199
+ ...(activeSession === undefined
200
+ ? {}
201
+ : {
202
+ session: {
203
+ codexHome: activeSession.codexHome,
204
+ ...(activeSession.metadata.thread_id === undefined
205
+ ? {}
206
+ : { threadId: activeSession.metadata.thread_id }),
207
+ ...(activeSession.metadata.lifecycle === "archived" ? { unarchive: true } : {}),
208
+ onThreadReady: (threadId) => {
209
+ const updated = recordCodexTaskSessionThread(activeSession, threadId);
210
+ activeSession = updated;
211
+ input.setSession(updated);
212
+ },
213
+ ...(input.archiveAfterTurn === undefined
214
+ ? {}
215
+ : {
216
+ archiveAfterTurn: input.archiveAfterTurn,
217
+ onThreadArchived: () => {
218
+ const updated = markCodexTaskSessionArchived(activeSession);
219
+ activeSession = updated;
220
+ input.setSession(updated);
221
+ },
222
+ onThreadArchiveFailure: () => {
223
+ recordCodexSessionArchiveFailure(activeSession);
224
+ },
225
+ }),
226
+ },
227
+ }),
228
+ runtimeTelemetry: {
229
+ activityRef: input.pipelineInput.activity_ref,
230
+ stageId: `provider_task_${input.phase}`,
231
+ workspaceRoot: input.options.workspaceRoot,
232
+ onSnapshot: (snapshot) => input.pipelineInput.on_stage?.({
233
+ stage: `provider_task_${input.phase}`,
234
+ summary: input.phase === "execute"
235
+ ? "running Codex task implementation."
236
+ : input.phase === "finalize"
237
+ ? "preparing the task result."
238
+ : "recovering the task result.",
239
+ runtime: snapshot,
240
+ }),
241
+ },
242
+ });
243
+ let result;
244
+ try {
245
+ result = await invoke();
134
246
  }
135
- if (options.recordUsage !== undefined && result.usage !== undefined) {
136
- options.recordUsage({
247
+ catch (error) {
248
+ const canRetryBeforeTurn = error instanceof CodexAppServerWorkspaceWriteRunError &&
249
+ error.details.turn_start_state === "not_started" &&
250
+ (error.code === "app_server_protocol_error" || error.code === "thread_start_failed");
251
+ if (!canRetryBeforeTurn) {
252
+ throw error;
253
+ }
254
+ input.pipelineInput.on_stage?.({
255
+ stage: "provider_retry",
256
+ summary: "retrying the Codex connection before the task turn started.",
257
+ });
258
+ result = await invoke();
259
+ }
260
+ if (input.options.recordUsage !== undefined && result.usage !== undefined) {
261
+ input.options.recordUsage({
137
262
  provider: "openai",
138
- source: usageSource(input),
263
+ source: usageSource(input.pipelineInput, input.phase),
139
264
  model: result.model,
140
265
  usage: result.usage,
141
- activity_ref: input.activity_ref,
142
- task_id: input.task.id,
143
- run_lineage_kind: input.run_lineage.kind,
266
+ activity_ref: input.pipelineInput.activity_ref,
267
+ task_id: input.pipelineInput.task.id,
268
+ run_lineage_kind: input.pipelineInput.run_lineage.kind,
144
269
  });
145
270
  }
146
- return normalizeTaskRunOutput(result.output);
271
+ return result;
272
+ }
273
+ function outputRecoveryEvidence(error) {
274
+ return {
275
+ previous_agent_output: error.details.agent_output ?? "",
276
+ provider_output_failure: {
277
+ kind: error.code === "output_parse_failed"
278
+ ? "malformed_or_missing_json"
279
+ : "schema_mismatch",
280
+ diagnostic: error.message,
281
+ },
282
+ };
283
+ }
284
+ function taskResultEndsCurrentLineage(output) {
285
+ return !("needs_human" in output.result);
286
+ }
287
+ function gateRecoveryEvidence(input) {
288
+ const context = input.correction_context;
289
+ return {
290
+ previous_provider_result: context.previous_provider_result,
291
+ gate_failure: {
292
+ summary: context.summary,
293
+ reason_code: context.reason_code,
294
+ guidance: context.guidance,
295
+ diagnostic: context.diagnostic,
296
+ promotion_failure: context.promotion_failure,
297
+ },
298
+ };
299
+ }
300
+ function recoveryEvidenceForRun(input) {
301
+ if (input.pipelineInput.run_lineage.kind !== "retry" ||
302
+ input.session === undefined) {
303
+ return input.current;
304
+ }
305
+ const persisted = readCodexSessionRecoveryEvidence(input.session);
306
+ if (persisted === null ||
307
+ persisted.owner_activity_ref !== input.pipelineInput.run_lineage.parent_activity_ref) {
308
+ return input.current;
309
+ }
310
+ return {
311
+ ...input.current,
312
+ previous_run_recovery_evidence: persisted.evidence,
313
+ previous_run_recovery_evidence_truncated: persisted.truncated,
314
+ };
315
+ }
316
+ function persistRecoveryEvidence(input) {
317
+ if (input.session === undefined) {
318
+ return;
319
+ }
320
+ writeCodexSessionRecoveryEvidence({
321
+ session: input.session,
322
+ ownerActivityRef: input.pipelineInput.activity_ref,
323
+ evidence: input.evidence,
324
+ });
325
+ }
326
+ export async function runWorkspaceWriteTask(options, input) {
327
+ let session = openTaskSession(options, input);
328
+ const releaseSessionLock = session === undefined ? undefined : acquireCodexTaskSessionLock(session);
329
+ const setSession = (updated) => {
330
+ session = updated;
331
+ };
332
+ const approvalHandler = createApprovalHandler(options, input);
333
+ const skillSelectionName = input.run_lineage.kind === "continuation" ? "follow_up_run" : "workspace_write_run";
334
+ try {
335
+ let structuredResult;
336
+ if (input.correction_context !== undefined) {
337
+ const recovery = recoveryEvidenceForRun({
338
+ pipelineInput: input,
339
+ session,
340
+ current: gateRecoveryEvidence(input),
341
+ });
342
+ persistRecoveryEvidence({ pipelineInput: input, session, evidence: recovery });
343
+ structuredResult = await runTaskTurn({
344
+ options,
345
+ pipelineInput: input,
346
+ prompt: createRecoveryPrompt({
347
+ options,
348
+ pipelineInput: input,
349
+ session,
350
+ recovery,
351
+ }),
352
+ phase: "recovery",
353
+ sandboxMode: "workspace-write",
354
+ networkAccess: true,
355
+ session,
356
+ setSession,
357
+ skillSelectionName,
358
+ ...(approvalHandler === undefined ? {} : { approvalHandler }),
359
+ outputSchema: TASK_RUN_OUTPUT_SCHEMA,
360
+ validateOutput: (value) => isInitialTaskRunOutput(value),
361
+ archiveAfterTurn: taskResultEndsCurrentLineage,
362
+ });
363
+ }
364
+ else {
365
+ await runTaskTurn({
366
+ options,
367
+ pipelineInput: input,
368
+ prompt: createExecutePrompt(options, input, session),
369
+ phase: "execute",
370
+ sandboxMode: "workspace-write",
371
+ networkAccess: true,
372
+ session,
373
+ setSession,
374
+ skillSelectionName,
375
+ ...(approvalHandler === undefined ? {} : { approvalHandler }),
376
+ });
377
+ try {
378
+ structuredResult = await runTaskTurn({
379
+ options,
380
+ pipelineInput: input,
381
+ prompt: createFinalizePrompt(options),
382
+ phase: "finalize",
383
+ sandboxMode: "read-only",
384
+ networkAccess: false,
385
+ session,
386
+ setSession,
387
+ outputSchema: TASK_RUN_OUTPUT_SCHEMA,
388
+ validateOutput: (value) => isInitialTaskRunOutput(value),
389
+ archiveAfterTurn: taskResultEndsCurrentLineage,
390
+ });
391
+ }
392
+ catch (error) {
393
+ if (!(error instanceof CodexAppServerWorkspaceWriteRunError) ||
394
+ (error.code !== "output_parse_failed" && error.code !== "output_validation_failed")) {
395
+ throw error;
396
+ }
397
+ const recovery = outputRecoveryEvidence(error);
398
+ persistRecoveryEvidence({ pipelineInput: input, session, evidence: recovery });
399
+ structuredResult = await runTaskTurn({
400
+ options,
401
+ pipelineInput: input,
402
+ prompt: createRecoveryPrompt({
403
+ options,
404
+ pipelineInput: input,
405
+ session,
406
+ recovery,
407
+ }),
408
+ phase: "recovery",
409
+ sandboxMode: "read-only",
410
+ networkAccess: false,
411
+ session,
412
+ setSession,
413
+ outputSchema: TASK_RUN_OUTPUT_SCHEMA,
414
+ validateOutput: (value) => isInitialTaskRunOutput(value),
415
+ archiveAfterTurn: taskResultEndsCurrentLineage,
416
+ });
417
+ }
418
+ }
419
+ return normalizeTaskRunOutput(structuredResult.output);
420
+ }
421
+ finally {
422
+ options.approvalManager?.clearPending();
423
+ releaseSessionLock?.();
424
+ }
147
425
  }
148
426
  //# sourceMappingURL=task-run-invocation.js.map
@@ -1,9 +1,11 @@
1
- import { CursorPageRequestSchema, ExecutionStatusProjectionSchema, GetRecoverySummariesRequestSchema, GetRecoverySummariesResponseSchema, GetRecoverySummaryResponseSchema, TaskDetailProjectionSchema, TaskProjectionSchema, TaskRunResultsPageSchema, } from "@tutti/shared/schemas/api";
1
+ import { CursorPageRequestSchema, ExecutionStatusProjectionSchema, GetRecoverySummariesRequestSchema, GetRecoverySummariesResponseSchema, GetRecoverySummaryResponseSchema, TaskDetailProjectionSchema, TaskProjectionSchema, TaskRunResultsPageSchema, RetryTaskBodySchema, RetryTaskResponseSchema, } from "@tutti/shared/schemas/api";
2
2
  import { createIdleExecutionStatusProjection, readRecoverySummariesPage, readRecoverySummaryProjection, readTaskDetailProjection, readTaskProjection, readTaskRunResultsPage, } from "../../../../collaboration-state/index.js";
3
+ import { executeIdempotentCommand } from "../../../command-idempotency/index.js";
3
4
  import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
4
5
  import { HostProjectNotFoundError } from "./errors.js";
5
6
  import { RecoverySummaryParamsSchema, TaskParamsSchema, } from "./schemas.js";
6
- import { requireHostProjectSession, requireProjectStore, } from "./session-requirements.js";
7
+ import { requireControlPlane, requireHostProjectSession, requireProjectStore, } from "./session-requirements.js";
8
+ import { taskRetryInvalidates } from "./invalidations.js";
7
9
  export function registerExecutionRoutes(app, options) {
8
10
  app.get(`${HOST_PROJECT_API_BASE_PATH}/tasks/:taskId/detail`, {
9
11
  schema: {
@@ -36,6 +38,34 @@ export function registerExecutionRoutes(app, options) {
36
38
  }
37
39
  return page;
38
40
  });
41
+ app.post(`${HOST_PROJECT_API_BASE_PATH}/tasks/:taskId/retry`, {
42
+ schema: {
43
+ params: TaskParamsSchema,
44
+ body: RetryTaskBodySchema,
45
+ response: { 200: RetryTaskResponseSchema },
46
+ },
47
+ }, (request) => {
48
+ requireHostProjectSession(request, options);
49
+ const store = requireProjectStore(options);
50
+ const controlPlane = requireControlPlane(options);
51
+ const execution = executeIdempotentCommand({
52
+ db: store.db,
53
+ command_id: request.body.command_id,
54
+ command_name: "task.retry",
55
+ target_ref: request.body.payload.expected_latest_run_result_id,
56
+ payload: {
57
+ task_id: request.params.taskId,
58
+ ...request.body.payload,
59
+ },
60
+ ...(options.now === undefined ? {} : { now: options.now }),
61
+ execute: () => controlPlane.retryTask(request.params.taskId, request.body.payload),
62
+ });
63
+ return {
64
+ command_id: execution.command_id,
65
+ disposition: execution.disposition,
66
+ invalidates: taskRetryInvalidates(request.params.taskId),
67
+ };
68
+ });
39
69
  app.get(`${HOST_PROJECT_API_BASE_PATH}/recovery-summaries`, {
40
70
  schema: {
41
71
  querystring: GetRecoverySummariesRequestSchema,
@@ -1,8 +1,9 @@
1
- import type { ClarificationRoundRef } from "@tutti/shared/ids";
1
+ import type { ClarificationRoundRef, TaskId } from "@tutti/shared/ids";
2
2
  import type { QueryInvalidationHint, RunSchedulerNowDisposition } from "@tutti/shared/schemas/api";
3
3
  export declare function mainChatMessageInvalidates(): QueryInvalidationHint[];
4
4
  export declare function worklistSubmitInvalidates(): QueryInvalidationHint[];
5
5
  export declare function schedulerRunNowInvalidates(disposition: RunSchedulerNowDisposition): QueryInvalidationHint[];
6
+ export declare function taskRetryInvalidates(taskId: TaskId): QueryInvalidationHint[];
6
7
  export declare function clarificationRoundInvalidates(roundId: ClarificationRoundRef): QueryInvalidationHint[];
7
8
  export declare function approvalInvalidates(): QueryInvalidationHint[];
8
9
  export declare function readStateInvalidates(): QueryInvalidationHint[];
@@ -31,6 +31,19 @@ export function schedulerRunNowInvalidates(disposition) {
31
31
  { kind: "task_run_results", task_id: disposition.task_id },
32
32
  ];
33
33
  }
34
+ export function taskRetryInvalidates(taskId) {
35
+ return [
36
+ { kind: "worklist" },
37
+ { kind: "task", task_id: taskId },
38
+ { kind: "task_detail", task_id: taskId },
39
+ { kind: "task_run_results", task_id: taskId },
40
+ { kind: "execution_status" },
41
+ { kind: "messages", scope: "main_chat" },
42
+ { kind: "project_timeline" },
43
+ { kind: "read_state" },
44
+ { kind: "bootstrap" },
45
+ ];
46
+ }
34
47
  export function clarificationRoundInvalidates(roundId) {
35
48
  return [
36
49
  { kind: "clarification", round_id: roundId },
@@ -24,6 +24,7 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
24
24
  };
25
25
  };
26
26
  querystring?: never;
27
+ requestBody?: never;
27
28
  } | {
28
29
  method: "get";
29
30
  path: string;
@@ -87,6 +88,7 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
87
88
  }>>;
88
89
  error_code: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
89
90
  retryable: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
91
+ retry_action: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"retryable">, import("@sinclair/typebox").TLiteral<"retried">]>>;
90
92
  updated_at: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
91
93
  }>;
92
94
  frozen_contract_since_activity_ref: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
@@ -96,6 +98,7 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
96
98
  };
97
99
  };
98
100
  querystring?: never;
101
+ requestBody?: never;
99
102
  } | {
100
103
  method: "get";
101
104
  path: string;
@@ -112,6 +115,21 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
112
115
  schema: import("@sinclair/typebox").TSchema;
113
116
  };
114
117
  };
118
+ requestBody?: never;
119
+ } | {
120
+ method: "post";
121
+ path: string;
122
+ summary: string;
123
+ tags: string[];
124
+ parameters: import("@tutti/shared/schemas/api").BrowserFacingOpenApiParameter[];
125
+ requestBody: import("@sinclair/typebox").TSchema;
126
+ responses: {
127
+ 200: {
128
+ description: string;
129
+ schema: import("@sinclair/typebox").TSchema;
130
+ };
131
+ };
132
+ querystring?: never;
115
133
  } | {
116
134
  method: "get";
117
135
  path: string;
@@ -128,6 +146,7 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
128
146
  };
129
147
  };
130
148
  parameters?: never;
149
+ requestBody?: never;
131
150
  } | {
132
151
  method: "get";
133
152
  path: string;
@@ -149,6 +168,7 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
149
168
  };
150
169
  };
151
170
  querystring?: never;
171
+ requestBody?: never;
152
172
  } | {
153
173
  method: "get";
154
174
  path: string;
@@ -228,5 +248,6 @@ export declare const HOST_PROJECT_EXECUTION_OPENAPI_ROUTES: ({
228
248
  };
229
249
  parameters?: never;
230
250
  querystring?: never;
251
+ requestBody?: never;
231
252
  })[];
232
253
  //# sourceMappingURL=openapi-execution-routes.d.ts.map