@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.
- package/dist/collaboration-state/clarification-records.d.ts +2 -1
- package/dist/collaboration-state/clarification-records.js +18 -0
- package/dist/collaboration-state/index.d.ts +3 -1
- package/dist/collaboration-state/index.js +2 -1
- package/dist/collaboration-state/messages.js +15 -0
- package/dist/collaboration-state/run-recording.js +18 -1
- package/dist/collaboration-state/run-results.js +6 -0
- package/dist/collaboration-state/task-retry.d.ts +16 -0
- package/dist/collaboration-state/task-retry.js +134 -0
- package/dist/control-plane/command-recovery.js +9 -1
- package/dist/control-plane/index.d.ts +3 -2
- package/dist/control-plane/index.js +14 -0
- package/dist/control-plane/run-retry.d.ts +19 -0
- package/dist/control-plane/run-retry.js +103 -0
- package/dist/control-plane/run-start.d.ts +2 -1
- package/dist/control-plane/run-start.js +18 -1
- package/dist/control-plane/task-compile-clarification-context.d.ts +4 -0
- package/dist/control-plane/task-compile-clarification-context.js +9 -22
- package/dist/control-plane/task-compile-continuation.js +2 -0
- package/dist/control-plane/task-compile-start.js +2 -0
- package/dist/control-plane/task-source-context-types.d.ts +11 -0
- package/dist/control-plane/task-source-context-types.js +2 -0
- package/dist/control-plane/task-source-context.d.ts +7 -0
- package/dist/control-plane/task-source-context.js +31 -0
- package/dist/control-plane/types.d.ts +15 -0
- package/dist/control-plane/workflows/openai.d.ts +1 -0
- package/dist/control-plane/workflows/openai.js +87 -15
- package/dist/control-plane/workflows/types.d.ts +6 -14
- package/dist/control-plane/worklist-terminal-feedback.js +3 -0
- package/dist/prompt-templates/index.d.ts +1 -1
- package/dist/prompt-templates/index.js +4 -3
- package/dist/providers/openai/app-server/durable-session-smoke-cli.d.ts +2 -0
- package/dist/providers/openai/app-server/durable-session-smoke-cli.js +229 -0
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +10 -0
- package/dist/providers/openai/app-server/read-only-procedure.js +42 -13
- package/dist/providers/openai/app-server/session-store.d.ts +74 -0
- package/dist/providers/openai/app-server/session-store.js +305 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +17 -4
- package/dist/providers/openai/app-server/workspace-write-run.js +92 -28
- package/dist/providers/openai/codex-app-server.d.ts +1 -0
- package/dist/providers/openai/codex-app-server.js +2 -0
- package/dist/run-pipeline/openai.d.ts +1 -0
- package/dist/run-pipeline/openai.js +15 -43
- package/dist/run-pipeline/task-run-invocation.js +371 -93
- package/dist/server-shell/http/routes/project-api/execution-routes.js +32 -2
- package/dist/server-shell/http/routes/project-api/invalidations.d.ts +2 -1
- package/dist/server-shell/http/routes/project-api/invalidations.js +13 -0
- package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +21 -0
- package/dist/server-shell/http/routes/project-api/openapi-execution-routes.js +12 -1
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +7 -0
- package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +5 -0
- package/node_modules/@tutti/shared/dist/schemas/api/messages.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +4 -0
- package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +3 -0
- package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +33 -0
- package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +35 -1
- package/package.json +1 -1
- package/prompts/README.md +3 -1
- package/prompts/artifacts/README.md +1 -1
- package/prompts/artifacts/applicability.md +1 -1
- package/prompts/procedures/README.md +3 -3
- package/prompts/procedures/follow-up-check.md +1 -1
- package/prompts/procedures/reference-file-summary.md +1 -1
- package/prompts/procedures/task-compile.md +7 -7
- package/prompts/prompt-flow-map.md +16 -10
- package/prompts/runs/README.md +11 -16
- package/prompts/runs/task-execute.md +86 -0
- package/prompts/runs/task-finalize.md +11 -0
- package/prompts/runs/task-recovery.md +15 -0
- package/prompts/runs/task-result-contract.md +64 -0
- package/web/assets/{homepage-motion-scene-CLTkixIk.js → homepage-motion-scene-BFo4r4nz.js} +1 -1
- package/web/assets/index-CNIl4TF8.js +69 -0
- package/web/assets/index-quWLFJgD.css +1 -0
- package/web/index.html +2 -2
- package/prompts/runs/task-continuation.md +0 -199
- package/prompts/runs/task-retry.md +0 -207
- package/prompts/runs/task-run.md +0 -186
- package/web/assets/index-BTNYElB-.js +0 -69
- 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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
37
|
-
validateOutput
|
|
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
|