@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
@@ -128,7 +128,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
128
128
  }
129
129
  const model = options.defaultModel ?? DEFAULT_OPENAI_MODEL;
130
130
  const tempRoot = mkdtempSync(join(tmpdir(), "tutti-codex-readonly-procedure-"));
131
- const codexHome = join(tempRoot, "codex-home");
131
+ const codexHome = options.session?.codexHome ?? join(tempRoot, "codex-home");
132
132
  mkdirSync(codexHome, { recursive: true, mode: 0o700 });
133
133
  const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
134
134
  const turnTimeoutMs = options.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;
@@ -210,21 +210,40 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
210
210
  if (!isApiKeyAccount(loginResponse)) {
211
211
  throw new CodexAppServerReadOnlyProcedureError("auth_failed", "Codex app-server did not accept API key auth.");
212
212
  }
213
- const threadStartResponse = await client.request("thread/start", {
214
- cwd: appServerCwd,
215
- model,
216
- modelProvider,
217
- approvalPolicy: "never",
218
- sandbox: "read-only",
219
- config: buildReadOnlyThreadConfig(options),
220
- ephemeral: true,
221
- experimentalRawEvents: false,
222
- persistExtendedHistory: false,
223
- }, requestTimeoutMs);
224
- const threadId = readThreadId(threadStartResponse);
213
+ let threadId;
214
+ if (options.session?.threadId === undefined) {
215
+ const threadStartResponse = await client.request("thread/start", {
216
+ cwd: appServerCwd,
217
+ model,
218
+ modelProvider,
219
+ approvalPolicy: "never",
220
+ sandbox: "read-only",
221
+ config: buildReadOnlyThreadConfig(options),
222
+ ephemeral: options.session === undefined,
223
+ experimentalRawEvents: false,
224
+ persistExtendedHistory: false,
225
+ }, requestTimeoutMs);
226
+ threadId = readThreadId(threadStartResponse);
227
+ }
228
+ else {
229
+ if (options.session.unarchive === true) {
230
+ await client.request("thread/unarchive", { threadId: options.session.threadId }, requestTimeoutMs);
231
+ }
232
+ const threadResumeResponse = await client.request("thread/resume", {
233
+ threadId: options.session.threadId,
234
+ cwd: appServerCwd,
235
+ model,
236
+ modelProvider,
237
+ approvalPolicy: "never",
238
+ sandbox: "read-only",
239
+ config: buildReadOnlyThreadConfig(options),
240
+ }, requestTimeoutMs);
241
+ threadId = readThreadId(threadResumeResponse) ?? options.session.threadId;
242
+ }
225
243
  if (threadId === null) {
226
244
  throw new CodexAppServerReadOnlyProcedureError("thread_start_failed", "Codex app-server did not return a thread id.");
227
245
  }
246
+ await options.session?.onThreadReady?.(threadId);
228
247
  await client.request("turn/start", {
229
248
  threadId,
230
249
  input: buildCodexAppServerTurnInput({
@@ -308,8 +327,18 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
308
327
  throw new CodexAppServerReadOnlyProcedureError("secret_leak_detected", "Codex app-server output contained a sensitive runtime value.");
309
328
  }
310
329
  const usage = extractCodexAppServerTokenUsage(client.notifications);
330
+ if (options.session?.archiveAfterTurn?.(parsed) === true) {
331
+ try {
332
+ await client.request("thread/archive", { threadId }, requestTimeoutMs);
333
+ await options.session.onThreadArchived?.();
334
+ }
335
+ catch {
336
+ await options.session.onThreadArchiveFailure?.();
337
+ }
338
+ }
311
339
  procedureResult = {
312
340
  output: parsed,
341
+ thread_id: threadId,
313
342
  model,
314
343
  turn_status: "completed",
315
344
  notification_count: client.notifications.length,
@@ -0,0 +1,74 @@
1
+ import type { ActivityRef, ProjectId, TaskId } from "@tutti/shared/ids";
2
+ export type CodexSessionLifecycle = "active" | "archived" | "incompatible";
3
+ export type CodexSessionKind = "formal_task_run" | "task_compile" | "project_context_bootstrap" | "context_sync";
4
+ export type CodexSessionMetadata = {
5
+ version: 1;
6
+ session_ref: string;
7
+ kind: CodexSessionKind;
8
+ owner_ref: string;
9
+ thread_id?: string;
10
+ codex_version: string;
11
+ model: string;
12
+ provider_fingerprint: string;
13
+ lifecycle: CodexSessionLifecycle;
14
+ created_at: string;
15
+ last_used_at: string;
16
+ };
17
+ export type CodexDurableSession = {
18
+ root: string;
19
+ codexHome: string;
20
+ metadataPath: string;
21
+ metadata: CodexSessionMetadata;
22
+ disposition: "created" | "resumed" | "replaced_incompatible";
23
+ };
24
+ export type CodexTaskSession = CodexDurableSession;
25
+ export type CodexTaskSessionCompatibility = {
26
+ codexVersion: string;
27
+ model: string;
28
+ providerFingerprint: string;
29
+ };
30
+ export declare function createProviderFingerprint(input: {
31
+ providerKind: "openai";
32
+ apiBaseUrl?: string;
33
+ }): string;
34
+ export declare function openCodexDurableSession(input: {
35
+ tuttiHome: string;
36
+ projectId: ProjectId;
37
+ kind: CodexSessionKind;
38
+ ownerRef: string;
39
+ compatibility: CodexTaskSessionCompatibility;
40
+ now?: Date;
41
+ }): CodexDurableSession;
42
+ export declare function openCodexTaskSession(input: {
43
+ tuttiHome: string;
44
+ projectId: ProjectId;
45
+ taskId: TaskId;
46
+ compatibility: CodexTaskSessionCompatibility;
47
+ now?: Date;
48
+ }): CodexTaskSession;
49
+ export type CodexSessionRecoveryEvidence = {
50
+ version: 1;
51
+ owner_activity_ref: ActivityRef;
52
+ updated_at: string;
53
+ truncated: boolean;
54
+ evidence: Record<string, unknown>;
55
+ };
56
+ export declare function writeCodexSessionRecoveryEvidence(input: {
57
+ session: CodexDurableSession;
58
+ ownerActivityRef: ActivityRef;
59
+ evidence: Record<string, unknown>;
60
+ now?: Date;
61
+ }): CodexSessionRecoveryEvidence;
62
+ export declare function readCodexSessionRecoveryEvidence(session: CodexDurableSession): CodexSessionRecoveryEvidence | null;
63
+ export declare function updateCodexTaskSession(session: CodexDurableSession, update: (metadata: CodexSessionMetadata) => CodexSessionMetadata): CodexDurableSession;
64
+ export declare function recordCodexTaskSessionThread(session: CodexDurableSession, threadId: string, now?: Date): CodexDurableSession;
65
+ export declare function markCodexTaskSessionArchived(session: CodexDurableSession, now?: Date): CodexDurableSession;
66
+ export type CodexSessionArchiveIncident = {
67
+ version: 1;
68
+ kind: "thread_archive_failed";
69
+ occurred_at: string;
70
+ };
71
+ export declare function recordCodexSessionArchiveFailure(session: CodexDurableSession, now?: Date): CodexSessionArchiveIncident;
72
+ export declare function readCodexSessionArchiveIncident(session: CodexDurableSession): CodexSessionArchiveIncident | null;
73
+ export declare function acquireCodexTaskSessionLock(session: CodexDurableSession): () => void;
74
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1,305 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { chmodSync, existsSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ const SESSION_METADATA_FILE = "metadata.json";
5
+ const SESSION_RECOVERY_EVIDENCE_FILE = "recovery-evidence.json";
6
+ const SESSION_ARCHIVE_INCIDENT_FILE = "archive-incident.json";
7
+ const SESSION_CODEX_HOME_DIRECTORY = "codex-home";
8
+ const SESSION_LOCK_FILE = ".turn.lock";
9
+ function isRecord(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
12
+ function ensurePrivateDirectory(path) {
13
+ mkdirSync(path, { recursive: true, mode: 0o700 });
14
+ chmodSync(path, 0o700);
15
+ }
16
+ function writePrivateJson(path, value) {
17
+ ensurePrivateDirectory(dirname(path));
18
+ const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
19
+ writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, {
20
+ encoding: "utf8",
21
+ mode: 0o600,
22
+ });
23
+ chmodSync(temporaryPath, 0o600);
24
+ renameSync(temporaryPath, path);
25
+ chmodSync(path, 0o600);
26
+ }
27
+ function readJson(path) {
28
+ if (!existsSync(path)) {
29
+ return null;
30
+ }
31
+ return JSON.parse(readFileSync(path, "utf8"));
32
+ }
33
+ function parseMetadata(value) {
34
+ if (!isRecord(value) ||
35
+ value.version !== 1 ||
36
+ typeof value.session_ref !== "string" ||
37
+ (value.kind !== "formal_task_run" &&
38
+ value.kind !== "task_compile" &&
39
+ value.kind !== "project_context_bootstrap" &&
40
+ value.kind !== "context_sync") ||
41
+ typeof value.owner_ref !== "string" ||
42
+ (value.thread_id !== undefined && typeof value.thread_id !== "string") ||
43
+ typeof value.codex_version !== "string" ||
44
+ typeof value.model !== "string" ||
45
+ typeof value.provider_fingerprint !== "string" ||
46
+ (value.lifecycle !== "active" &&
47
+ value.lifecycle !== "archived" &&
48
+ value.lifecycle !== "incompatible") ||
49
+ typeof value.created_at !== "string" ||
50
+ typeof value.last_used_at !== "string") {
51
+ return null;
52
+ }
53
+ return value;
54
+ }
55
+ function codexSessionsRoot(tuttiHome, projectId) {
56
+ return join(tuttiHome, "projects", projectId, "codex-sessions");
57
+ }
58
+ function ownerIndexPath(tuttiHome, projectId, kind, ownerRef) {
59
+ return kind === "formal_task_run"
60
+ ? join(codexSessionsRoot(tuttiHome, projectId), "task-index", `${ownerRef}.json`)
61
+ : join(codexSessionsRoot(tuttiHome, projectId), "procedure-index", kind, `${ownerRef}.json`);
62
+ }
63
+ function sessionRoot(tuttiHome, projectId, sessionRef) {
64
+ return join(codexSessionsRoot(tuttiHome, projectId), sessionRef);
65
+ }
66
+ function compatible(metadata, expected) {
67
+ return (metadata.lifecycle !== "incompatible" &&
68
+ metadata.codex_version === expected.codexVersion &&
69
+ metadata.model === expected.model &&
70
+ metadata.provider_fingerprint === expected.providerFingerprint);
71
+ }
72
+ function createSession(input) {
73
+ const sessionRef = `codex-session-${randomUUID()}`;
74
+ const root = sessionRoot(input.tuttiHome, input.projectId, sessionRef);
75
+ const codexHome = join(root, SESSION_CODEX_HOME_DIRECTORY);
76
+ ensurePrivateDirectory(codexSessionsRoot(input.tuttiHome, input.projectId));
77
+ ensurePrivateDirectory(root);
78
+ ensurePrivateDirectory(codexHome);
79
+ const timestamp = input.now.toISOString();
80
+ const metadata = {
81
+ version: 1,
82
+ session_ref: sessionRef,
83
+ kind: input.kind,
84
+ owner_ref: input.ownerRef,
85
+ codex_version: input.compatibility.codexVersion,
86
+ model: input.compatibility.model,
87
+ provider_fingerprint: input.compatibility.providerFingerprint,
88
+ lifecycle: "active",
89
+ created_at: timestamp,
90
+ last_used_at: timestamp,
91
+ };
92
+ const metadataPath = join(root, SESSION_METADATA_FILE);
93
+ writePrivateJson(metadataPath, metadata);
94
+ writePrivateJson(ownerIndexPath(input.tuttiHome, input.projectId, input.kind, input.ownerRef), {
95
+ version: 1,
96
+ kind: input.kind,
97
+ owner_ref: input.ownerRef,
98
+ session_ref: sessionRef,
99
+ });
100
+ return {
101
+ root,
102
+ codexHome,
103
+ metadataPath,
104
+ metadata,
105
+ disposition: input.disposition,
106
+ };
107
+ }
108
+ export function createProviderFingerprint(input) {
109
+ const canonical = JSON.stringify({
110
+ provider_kind: input.providerKind,
111
+ api_base_url: input.apiBaseUrl?.trim() || "https://api.openai.com/v1",
112
+ });
113
+ return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
114
+ }
115
+ export function openCodexDurableSession(input) {
116
+ const now = input.now ?? new Date();
117
+ const indexValue = readJson(ownerIndexPath(input.tuttiHome, input.projectId, input.kind, input.ownerRef));
118
+ if (isRecord(indexValue) &&
119
+ indexValue.version === 1 &&
120
+ indexValue.kind === input.kind &&
121
+ indexValue.owner_ref === input.ownerRef &&
122
+ typeof indexValue.session_ref === "string") {
123
+ const root = sessionRoot(input.tuttiHome, input.projectId, indexValue.session_ref);
124
+ const metadataPath = join(root, SESSION_METADATA_FILE);
125
+ const metadata = parseMetadata(readJson(metadataPath));
126
+ if (metadata !== null &&
127
+ metadata.kind === input.kind &&
128
+ metadata.owner_ref === input.ownerRef) {
129
+ if (compatible(metadata, input.compatibility)) {
130
+ ensurePrivateDirectory(root);
131
+ const codexHome = join(root, SESSION_CODEX_HOME_DIRECTORY);
132
+ ensurePrivateDirectory(codexHome);
133
+ const updatedMetadata = {
134
+ ...metadata,
135
+ last_used_at: now.toISOString(),
136
+ };
137
+ writePrivateJson(metadataPath, updatedMetadata);
138
+ return {
139
+ root,
140
+ codexHome,
141
+ metadataPath,
142
+ metadata: updatedMetadata,
143
+ disposition: "resumed",
144
+ };
145
+ }
146
+ writePrivateJson(metadataPath, {
147
+ ...metadata,
148
+ lifecycle: "incompatible",
149
+ last_used_at: now.toISOString(),
150
+ });
151
+ return createSession({
152
+ ...input,
153
+ now,
154
+ disposition: "replaced_incompatible",
155
+ });
156
+ }
157
+ }
158
+ return createSession({ ...input, now, disposition: "created" });
159
+ }
160
+ export function openCodexTaskSession(input) {
161
+ return openCodexDurableSession({
162
+ tuttiHome: input.tuttiHome,
163
+ projectId: input.projectId,
164
+ kind: "formal_task_run",
165
+ ownerRef: input.taskId,
166
+ compatibility: input.compatibility,
167
+ ...(input.now === undefined ? {} : { now: input.now }),
168
+ });
169
+ }
170
+ const MAX_RECOVERY_EVIDENCE_BYTES = 64 * 1024;
171
+ function recoveryEvidencePath(session) {
172
+ return join(session.root, SESSION_RECOVERY_EVIDENCE_FILE);
173
+ }
174
+ export function writeCodexSessionRecoveryEvidence(input) {
175
+ const serialized = JSON.stringify(input.evidence);
176
+ const exceedsLimit = Buffer.byteLength(serialized, "utf8") > MAX_RECOVERY_EVIDENCE_BYTES;
177
+ const record = {
178
+ version: 1,
179
+ owner_activity_ref: input.ownerActivityRef,
180
+ updated_at: (input.now ?? new Date()).toISOString(),
181
+ truncated: exceedsLimit,
182
+ evidence: exceedsLimit
183
+ ? {
184
+ bounded_original_json: Buffer.from(serialized, "utf8")
185
+ .subarray(0, MAX_RECOVERY_EVIDENCE_BYTES)
186
+ .toString("utf8"),
187
+ truncation_notice: "Recovery evidence exceeded the machine-local byte limit.",
188
+ }
189
+ : input.evidence,
190
+ };
191
+ writePrivateJson(recoveryEvidencePath(input.session), record);
192
+ return record;
193
+ }
194
+ export function readCodexSessionRecoveryEvidence(session) {
195
+ const value = readJson(recoveryEvidencePath(session));
196
+ if (!isRecord(value) ||
197
+ value.version !== 1 ||
198
+ typeof value.owner_activity_ref !== "string" ||
199
+ typeof value.updated_at !== "string" ||
200
+ typeof value.truncated !== "boolean" ||
201
+ !isRecord(value.evidence)) {
202
+ return null;
203
+ }
204
+ return value;
205
+ }
206
+ export function updateCodexTaskSession(session, update) {
207
+ const current = parseMetadata(readJson(session.metadataPath));
208
+ if (current === null || current.session_ref !== session.metadata.session_ref) {
209
+ throw new Error("Codex task session metadata is unavailable or invalid.");
210
+ }
211
+ const next = update(current);
212
+ if (next.session_ref !== current.session_ref ||
213
+ next.kind !== current.kind ||
214
+ next.owner_ref !== current.owner_ref) {
215
+ throw new Error("Codex task session identity cannot change.");
216
+ }
217
+ writePrivateJson(session.metadataPath, next);
218
+ return { ...session, metadata: next };
219
+ }
220
+ export function recordCodexTaskSessionThread(session, threadId, now = new Date()) {
221
+ return updateCodexTaskSession(session, (metadata) => {
222
+ if (metadata.thread_id !== undefined && metadata.thread_id !== threadId) {
223
+ throw new Error("Codex task session thread identity cannot change.");
224
+ }
225
+ return {
226
+ ...metadata,
227
+ thread_id: threadId,
228
+ lifecycle: "active",
229
+ last_used_at: now.toISOString(),
230
+ };
231
+ });
232
+ }
233
+ export function markCodexTaskSessionArchived(session, now = new Date()) {
234
+ return updateCodexTaskSession(session, (metadata) => ({
235
+ ...metadata,
236
+ lifecycle: "archived",
237
+ last_used_at: now.toISOString(),
238
+ }));
239
+ }
240
+ export function recordCodexSessionArchiveFailure(session, now = new Date()) {
241
+ const incident = {
242
+ version: 1,
243
+ kind: "thread_archive_failed",
244
+ occurred_at: now.toISOString(),
245
+ };
246
+ writePrivateJson(join(session.root, SESSION_ARCHIVE_INCIDENT_FILE), incident);
247
+ return incident;
248
+ }
249
+ export function readCodexSessionArchiveIncident(session) {
250
+ const value = readJson(join(session.root, SESSION_ARCHIVE_INCIDENT_FILE));
251
+ if (!isRecord(value) ||
252
+ value.version !== 1 ||
253
+ value.kind !== "thread_archive_failed" ||
254
+ typeof value.occurred_at !== "string") {
255
+ return null;
256
+ }
257
+ return value;
258
+ }
259
+ function isProcessAlive(pid) {
260
+ try {
261
+ process.kill(pid, 0);
262
+ return true;
263
+ }
264
+ catch {
265
+ return false;
266
+ }
267
+ }
268
+ export function acquireCodexTaskSessionLock(session) {
269
+ const lockPath = join(session.root, SESSION_LOCK_FILE);
270
+ for (let attempt = 0; attempt < 2; attempt += 1) {
271
+ try {
272
+ const descriptor = openSync(lockPath, "wx", 0o600);
273
+ writeFileSync(descriptor, `${JSON.stringify({ version: 1, owner_pid: process.pid, created_at: new Date().toISOString() })}\n`, "utf8");
274
+ closeSync(descriptor);
275
+ let released = false;
276
+ return () => {
277
+ if (released) {
278
+ return;
279
+ }
280
+ released = true;
281
+ try {
282
+ unlinkSync(lockPath);
283
+ }
284
+ catch {
285
+ // Releasing a process-local safety lock is best effort during shutdown.
286
+ }
287
+ };
288
+ }
289
+ catch (error) {
290
+ if (!existsSync(lockPath)) {
291
+ throw error;
292
+ }
293
+ const lockValue = readJson(lockPath);
294
+ const ownerPid = isRecord(lockValue) && typeof lockValue.owner_pid === "number"
295
+ ? lockValue.owner_pid
296
+ : null;
297
+ if (ownerPid !== null && isProcessAlive(ownerPid)) {
298
+ throw new Error("Codex task session already has an active turn owner.");
299
+ }
300
+ unlinkSync(lockPath);
301
+ }
302
+ }
303
+ throw new Error("Codex task session lock could not be acquired.");
304
+ }
305
+ //# sourceMappingURL=session-store.js.map
@@ -33,8 +33,18 @@ export type CodexAppServerWorkspaceWriteRunInput<TOutput> = {
33
33
  defaultModel?: string;
34
34
  workspaceRoot: string;
35
35
  prompt: string;
36
- outputSchema: unknown;
37
- validateOutput: (value: unknown) => value is TOutput;
36
+ outputSchema?: unknown;
37
+ validateOutput?: (value: unknown) => value is TOutput;
38
+ sandboxMode?: "workspace-write" | "read-only";
39
+ session?: {
40
+ codexHome: string;
41
+ threadId?: string;
42
+ unarchive?: boolean;
43
+ onThreadReady?: (threadId: string) => void | Promise<void>;
44
+ archiveAfterTurn?: (output: TOutput) => boolean;
45
+ onThreadArchived?: () => void | Promise<void>;
46
+ onThreadArchiveFailure?: () => void | Promise<void>;
47
+ };
38
48
  networkAccess?: boolean;
39
49
  webSearchMode?: "disabled" | "cached" | "live";
40
50
  approvalPolicy?: CodexAppServerApprovalPolicy;
@@ -63,6 +73,7 @@ export type CodexAppServerWorkspaceWriteRunInput<TOutput> = {
63
73
  };
64
74
  export type CodexAppServerWorkspaceWriteRunResult<TOutput> = {
65
75
  output: TOutput;
76
+ thread_id: string;
66
77
  model: string;
67
78
  turn_status: "completed";
68
79
  notification_count: number;
@@ -73,6 +84,7 @@ export type CodexAppServerWorkspaceWriteRunResult<TOutput> = {
73
84
  usage?: OpenAiTokenUsage;
74
85
  };
75
86
  export type CodexAppServerWorkspaceWriteRunErrorDetails = {
87
+ turn_start_state?: "not_started" | "start_requested" | "terminal";
76
88
  turn_status?: string;
77
89
  turn_error_message?: string;
78
90
  turn_error_code?: string;
@@ -83,12 +95,13 @@ export type CodexAppServerWorkspaceWriteRunErrorDetails = {
83
95
  server_request_methods?: string[];
84
96
  notification_methods?: string[];
85
97
  agent_message_excerpt?: string;
98
+ agent_output?: string;
86
99
  stderr_excerpt?: string;
87
100
  };
88
101
  export declare class CodexAppServerWorkspaceWriteRunError extends Error {
89
- readonly code: "app_server_protocol_error" | "auth_failed" | "thread_start_failed" | "turn_failed" | "turn_timeout" | "unexpected_server_request" | "skill_selection_failed" | "output_parse_failed" | "output_validation_failed" | "secret_leak_detected" | "process_lifecycle_failed" | "admission_unavailable";
102
+ readonly code: "app_server_protocol_error" | "auth_failed" | "thread_start_failed" | "turn_failed" | "turn_timeout" | "unexpected_server_request" | "skill_selection_failed" | "output_parse_failed" | "output_validation_failed" | "secret_leak_detected" | "process_lifecycle_failed" | "admission_unavailable" | "session_unavailable";
90
103
  readonly details: CodexAppServerWorkspaceWriteRunErrorDetails;
91
- constructor(code: "app_server_protocol_error" | "auth_failed" | "thread_start_failed" | "turn_failed" | "turn_timeout" | "unexpected_server_request" | "skill_selection_failed" | "output_parse_failed" | "output_validation_failed" | "secret_leak_detected" | "process_lifecycle_failed" | "admission_unavailable", message: string, details?: CodexAppServerWorkspaceWriteRunErrorDetails);
104
+ constructor(code: "app_server_protocol_error" | "auth_failed" | "thread_start_failed" | "turn_failed" | "turn_timeout" | "unexpected_server_request" | "skill_selection_failed" | "output_parse_failed" | "output_validation_failed" | "secret_leak_detected" | "process_lifecycle_failed" | "admission_unavailable" | "session_unavailable", message: string, details?: CodexAppServerWorkspaceWriteRunErrorDetails);
92
105
  }
93
106
  export declare function runCodexAppServerWorkspaceWriteRun<TOutput>(options: CodexAppServerWorkspaceWriteRunInput<TOutput>): Promise<CodexAppServerWorkspaceWriteRunResult<TOutput>>;
94
107
  //# sourceMappingURL=workspace-write-run.d.ts.map