@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.
Files changed (66) hide show
  1. package/dist/collaboration-state/index.d.ts +2 -0
  2. package/dist/collaboration-state/index.js +1 -0
  3. package/dist/collaboration-state/messages.js +15 -0
  4. package/dist/collaboration-state/run-recording.js +18 -1
  5. package/dist/collaboration-state/run-results.js +6 -0
  6. package/dist/collaboration-state/task-retry.d.ts +16 -0
  7. package/dist/collaboration-state/task-retry.js +134 -0
  8. package/dist/control-plane/command-recovery.js +9 -1
  9. package/dist/control-plane/index.d.ts +3 -2
  10. package/dist/control-plane/index.js +14 -0
  11. package/dist/control-plane/run-retry.d.ts +19 -0
  12. package/dist/control-plane/run-retry.js +103 -0
  13. package/dist/control-plane/run-start.d.ts +2 -1
  14. package/dist/control-plane/run-start.js +3 -0
  15. package/dist/control-plane/types.d.ts +12 -0
  16. package/dist/control-plane/workflows/openai.d.ts +1 -0
  17. package/dist/control-plane/workflows/openai.js +86 -15
  18. package/dist/control-plane/worklist-terminal-feedback.js +3 -0
  19. package/dist/prompt-templates/index.d.ts +1 -1
  20. package/dist/prompt-templates/index.js +4 -3
  21. package/dist/providers/openai/app-server/durable-session-smoke-cli.d.ts +2 -0
  22. package/dist/providers/openai/app-server/durable-session-smoke-cli.js +229 -0
  23. package/dist/providers/openai/app-server/read-only-procedure.d.ts +10 -0
  24. package/dist/providers/openai/app-server/read-only-procedure.js +42 -13
  25. package/dist/providers/openai/app-server/session-store.d.ts +74 -0
  26. package/dist/providers/openai/app-server/session-store.js +305 -0
  27. package/dist/providers/openai/app-server/workspace-write-run.d.ts +17 -4
  28. package/dist/providers/openai/app-server/workspace-write-run.js +92 -28
  29. package/dist/providers/openai/codex-app-server.d.ts +1 -0
  30. package/dist/providers/openai/codex-app-server.js +2 -0
  31. package/dist/run-pipeline/openai.d.ts +1 -0
  32. package/dist/run-pipeline/openai.js +15 -43
  33. package/dist/run-pipeline/task-run-invocation.js +354 -93
  34. package/dist/server-shell/http/routes/project-api/execution-routes.js +32 -2
  35. package/dist/server-shell/http/routes/project-api/invalidations.d.ts +2 -1
  36. package/dist/server-shell/http/routes/project-api/invalidations.js +13 -0
  37. package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +21 -0
  38. package/dist/server-shell/http/routes/project-api/openapi-execution-routes.js +12 -1
  39. package/dist/server-shell/http/routes/project-api/openapi.d.ts +7 -0
  40. package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +1 -0
  41. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  42. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  43. package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +5 -0
  44. package/node_modules/@tutti/shared/dist/schemas/api/messages.js +1 -0
  45. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +1 -0
  46. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
  47. package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +1 -0
  48. package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +4 -0
  49. package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +3 -0
  50. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +33 -0
  51. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +35 -1
  52. package/package.json +1 -1
  53. package/prompts/runs/README.md +11 -16
  54. package/prompts/runs/task-execute.md +86 -0
  55. package/prompts/runs/task-finalize.md +11 -0
  56. package/prompts/runs/task-recovery.md +15 -0
  57. package/prompts/runs/task-result-contract.md +64 -0
  58. package/web/assets/{homepage-motion-scene-CLTkixIk.js → homepage-motion-scene-BFo4r4nz.js} +1 -1
  59. package/web/assets/index-CNIl4TF8.js +69 -0
  60. package/web/assets/index-quWLFJgD.css +1 -0
  61. package/web/index.html +2 -2
  62. package/prompts/runs/task-continuation.md +0 -201
  63. package/prompts/runs/task-retry.md +0 -209
  64. package/prompts/runs/task-run.md +0 -188
  65. package/web/assets/index-BTNYElB-.js +0 -69
  66. package/web/assets/index-Deu_eF5K.css +0 -1
@@ -17,17 +17,18 @@ function summarizeProviderError(error) {
17
17
  error.message.includes("invalid_json_schema") ||
18
18
  error.message.includes("Invalid schema") ||
19
19
  error.message.includes("response_format");
20
+ const turnStateUnknown = error.details.turn_start_state === "start_requested";
20
21
  const errorCode = looksLikeSchemaError
21
22
  ? "codex_app_server_output_schema_invalid"
22
- : `codex_app_server_${error.code}`;
23
+ : turnStateUnknown
24
+ ? "codex_app_server_turn_state_unknown"
25
+ : `codex_app_server_${error.code}`;
23
26
  return failedBeforeChecks({
24
27
  summary: `Provider execution failed before checks. (${errorCode})`,
25
28
  errorCode,
26
29
  retryable: !looksLikeSchemaError &&
27
- (error.code === "app_server_protocol_error" ||
28
- error.code === "turn_timeout" ||
29
- error.code === "turn_failed" ||
30
- error.code === "thread_start_failed"),
30
+ error.details.turn_start_state === "not_started" &&
31
+ (error.code === "app_server_protocol_error" || error.code === "thread_start_failed"),
31
32
  });
32
33
  }
33
34
  function summarizeWorkspaceOpsError(error, changedPaths) {
@@ -153,12 +154,12 @@ function loggedSyncStage(options, input, stage, run, completedFields = () => ({}
153
154
  }
154
155
  }
155
156
  const MAX_SELF_CORRECTION_RETRIES = 1;
156
- const MAX_PROVIDER_TRANSIENT_RETRIES = 1;
157
157
  function runPipelineResultPromotion(result) {
158
158
  return "promotion" in result ? result.promotion : undefined;
159
159
  }
160
160
  function buildCorrectionContext(input) {
161
161
  const correction = {
162
+ previous_provider_result: input.previousProviderResult,
162
163
  summary: input.result.summary,
163
164
  reason_code: input.reasonCode,
164
165
  guidance: input.guidance,
@@ -189,23 +190,12 @@ function selfCorrectionInput(input, nextAttempt, correctionContext) {
189
190
  return {
190
191
  ...input,
191
192
  run_lineage: {
192
- kind: "retry",
193
+ ...input.run_lineage,
193
194
  attempt: nextAttempt,
194
- parent_activity_ref: input.activity_ref,
195
- root_activity_ref: input.run_lineage.root_activity_ref ?? input.activity_ref,
196
195
  },
197
196
  correction_context: correctionContext,
198
197
  };
199
198
  }
200
- function providerTransientRetryInput(input) {
201
- return {
202
- ...input,
203
- run_lineage: {
204
- ...input.run_lineage,
205
- attempt: input.run_lineage.attempt + 1,
206
- },
207
- };
208
- }
209
199
  function appendSelfCorrectionExhaustedSummary(input) {
210
200
  if (input.attemptedRetries <= 0) {
211
201
  return input.result;
@@ -218,12 +208,6 @@ function appendSelfCorrectionExhaustedSummary(input) {
218
208
  function isSelfCorrectionRequest(result) {
219
209
  return "request_kind" in result && result.request_kind === "self_correction";
220
210
  }
221
- function isProviderTransientRunFailure(result) {
222
- return (!isSelfCorrectionRequest(result) &&
223
- result.result_kind === "failed" &&
224
- result.retryable &&
225
- result.error_code.startsWith("codex_app_server_"));
226
- }
227
211
  async function runTaskAttempt(options, input) {
228
212
  let changedPaths;
229
213
  try {
@@ -289,8 +273,9 @@ async function runTaskAttempt(options, input) {
289
273
  });
290
274
  return requestSelfCorrection({
291
275
  result,
276
+ previousProviderResult: taskOutput,
292
277
  reasonCode: "no_candidate_changes",
293
- guidance: "Produce a concrete candidate diff for the frozen task contract.",
278
+ guidance: "Produce a concrete candidate diff for the current task goal and scope.",
294
279
  });
295
280
  }
296
281
  const artifactGate = await loggedAsyncStage(options, input, "artifact_candidate_validate", () => runArtifactCandidateGate({
@@ -336,6 +321,7 @@ async function runTaskAttempt(options, input) {
336
321
  }
337
322
  return requestSelfCorrection({
338
323
  result: artifactResult,
324
+ previousProviderResult: taskOutput,
339
325
  reasonCode: artifactGate.failure.reason_code,
340
326
  guidance: artifactGate.failure.guidance,
341
327
  diagnostic: artifactGate.failure.diagnostic,
@@ -361,6 +347,7 @@ async function runTaskAttempt(options, input) {
361
347
  });
362
348
  return requestSelfCorrection({
363
349
  result,
350
+ previousProviderResult: taskOutput,
364
351
  reasonCode: "checks_failed",
365
352
  guidance: "Use the failed checks summary to fix the candidate so the task can pass Tutti checks.",
366
353
  });
@@ -378,8 +365,9 @@ async function runTaskAttempt(options, input) {
378
365
  });
379
366
  return requestSelfCorrection({
380
367
  result,
368
+ previousProviderResult: taskOutput,
381
369
  reasonCode: "no_candidate_changes",
382
- guidance: "Produce a concrete final candidate diff for the frozen task contract.",
370
+ guidance: "Produce a concrete final candidate diff for the current task goal and scope.",
383
371
  });
384
372
  }
385
373
  const checkpoint = loggedSyncStage(options, input, "accepted_checkpoint", () => createAcceptedCheckpoint({
@@ -444,25 +432,8 @@ export function createOpenAiRunPipelineRunner(options) {
444
432
  async runTask(input) {
445
433
  let currentInput = input;
446
434
  let selfCorrectionRetries = 0;
447
- let providerTransientRetries = 0;
448
435
  for (;;) {
449
436
  const result = await runTaskAttempt(options, currentInput);
450
- if (isProviderTransientRunFailure(result) &&
451
- providerTransientRetries < MAX_PROVIDER_TRANSIENT_RETRIES) {
452
- providerTransientRetries += 1;
453
- input.on_stage?.({
454
- stage: "provider_retry",
455
- summary: "retrying Codex task after a transient provider failure.",
456
- });
457
- logRunPipelineStage(options, currentInput, {
458
- stage: "provider_retry",
459
- next_attempt: currentInput.run_lineage.attempt + 1,
460
- error_code: result.error_code,
461
- }, "Run pipeline scheduling provider transient retry");
462
- currentInput = providerTransientRetryInput(currentInput);
463
- continue;
464
- }
465
- providerTransientRetries = 0;
466
437
  if (!isSelfCorrectionRequest(result)) {
467
438
  return result;
468
439
  }
@@ -512,6 +483,7 @@ export function resolveProjectOpenAiRunPipelineRunner(options) {
512
483
  apiKey: credential.secret,
513
484
  workspaceRoot: options.workspaceRoot,
514
485
  tuttiHome: options.tuttiHome,
486
+ projectId: options.projectId,
515
487
  userSkillsRoot: getProjectUserSkillsRoot(options.tuttiHome, options.projectId),
516
488
  ...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
517
489
  ...(options.approvalManager === undefined
@@ -1,5 +1,7 @@
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 {
@@ -28,57 +30,77 @@ function taskContractRunInput(input) {
28
30
  : { recent_references: input.recent_references }),
29
31
  };
30
32
  }
31
- function initialTaskRunInput(input) {
32
- return taskContractRunInput(input);
33
- }
34
- function continuationTaskRunInput(input) {
35
- return {
36
- ...taskContractRunInput(input),
37
- ...(input.continuation_context === undefined
38
- ? {}
39
- : { continuation: input.continuation_context }),
40
- };
33
+ function renderResultContract(options) {
34
+ return renderPromptTemplate({
35
+ templateKey: "runs.task_result_contract",
36
+ ...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
37
+ });
41
38
  }
42
- function retryTaskRunInput(input) {
43
- if (input.correction_context === undefined) {
44
- 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
+ };
45
53
  }
46
- return {
47
- ...taskContractRunInput(input),
48
- correction: input.correction_context,
49
- };
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
+ });
50
62
  }
51
- function createRunPrompt(options, input) {
52
- const templateKey = input.run_lineage.kind === "continuation"
53
- ? "runs.task_continuation"
54
- : input.run_lineage.kind === "retry"
55
- ? "runs.task_retry"
56
- : "runs.task_run";
63
+ function createFinalizePrompt(options) {
57
64
  return renderPromptTemplate({
58
- templateKey,
65
+ templateKey: "runs.task_finalize",
59
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 }),
60
77
  variables: {
61
- run_input_json: input.run_lineage.kind === "initial"
62
- ? initialTaskRunInput(input)
63
- : input.run_lineage.kind === "continuation"
64
- ? continuationTaskRunInput(input)
65
- : 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),
66
85
  },
67
86
  });
68
87
  }
69
- function usageSource(input) {
70
- switch (input.run_lineage.kind) {
71
- case "continuation":
72
- return "follow_up_run";
73
- case "retry":
74
- return "pipeline_self_correction";
75
- case "initial":
76
- return "workspace_write_run";
88
+ function usageSource(input, phase) {
89
+ if (phase === "finalize") {
90
+ return "task_result_finalize";
77
91
  }
92
+ if (phase === "recovery") {
93
+ return "task_result_recovery";
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";
78
100
  }
79
- export async function runWorkspaceWriteTask(options, input) {
101
+ function createApprovalHandler(options, input) {
80
102
  const manager = options.approvalManager;
81
- const approvalHandler = manager === undefined
103
+ return manager === undefined
82
104
  ? undefined
83
105
  : (approvalInput) => manager.awaitApproval({
84
106
  request: approvalInput.request,
@@ -97,69 +119,308 @@ export async function runWorkspaceWriteTask(options, input) {
97
119
  },
98
120
  timeoutMs: approvalInput.context.turnTimeoutMs,
99
121
  });
100
- let result;
122
+ }
123
+ function openTaskSession(options, input) {
124
+ if (options.tuttiHome === undefined || options.projectId === undefined) {
125
+ return undefined;
126
+ }
101
127
  try {
102
- result = await runCodexAppServerWorkspaceWriteRun({
103
- apiKey: options.apiKey,
104
- workspaceRoot: options.workspaceRoot,
105
- prompt: createRunPrompt(options, input),
106
- outputSchema: TASK_RUN_OUTPUT_SCHEMA,
107
- validateOutput: (value) => isInitialTaskRunOutput(value),
108
- skillSelectionName: input.run_lineage.kind === "continuation" ? "follow_up_run" : "workspace_write_run",
109
- defaultModel: options.config.default_model,
110
- approvalContext: {
111
- workspaceRoot: options.workspaceRoot,
112
- taskId: input.task.id,
113
- activityRef: input.activity_ref,
114
- },
115
- ...(approvalHandler === undefined ? {} : { approvalHandler }),
116
- ...(options.config.api_base_url === undefined
117
- ? {}
118
- : { apiBaseUrl: options.config.api_base_url }),
119
- ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
120
- ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
121
- ...(options.builtInSkillsRoot === undefined
122
- ? {}
123
- : { builtInSkillsRoot: options.builtInSkillsRoot }),
124
- ...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
125
- ...(options.codexCommand === undefined ? {} : { codexCommand: options.codexCommand }),
126
- ...(options.requestTimeoutMs === undefined
127
- ? {}
128
- : { requestTimeoutMs: options.requestTimeoutMs }),
129
- ...(options.turnTimeoutMs === undefined ? {} : { turnTimeoutMs: options.turnTimeoutMs }),
130
- ...(options.baseEnv === undefined ? {} : { baseEnv: options.baseEnv }),
131
- ...(options.invocationCoordinator === undefined
132
- ? {}
133
- : { invocationCoordinator: options.invocationCoordinator }),
134
- ...(options.onLifecycleIncident === undefined
135
- ? {}
136
- : { onLifecycleIncident: options.onLifecycleIncident }),
137
- runtimeTelemetry: {
138
- activityRef: input.activity_ref,
139
- stageId: "provider_task_run",
140
- workspaceRoot: options.workspaceRoot,
141
- onSnapshot: (snapshot) => input.on_stage?.({
142
- stage: "provider_task_run",
143
- summary: "running Codex task implementation.",
144
- 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 }),
145
140
  }),
146
141
  },
147
142
  });
148
143
  }
149
- finally {
150
- 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();
151
246
  }
152
- if (options.recordUsage !== undefined && result.usage !== undefined) {
153
- 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({
154
262
  provider: "openai",
155
- source: usageSource(input),
263
+ source: usageSource(input.pipelineInput, input.phase),
156
264
  model: result.model,
157
265
  usage: result.usage,
158
- activity_ref: input.activity_ref,
159
- task_id: input.task.id,
160
- 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,
161
269
  });
162
270
  }
163
- 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
+ }
164
425
  }
165
426
  //# sourceMappingURL=task-run-invocation.js.map