sortie-dogs 0.13.21 → 0.13.22
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/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/cli/codex-mission.js +6 -1
- package/dist/codex/app-server.d.ts +8 -0
- package/dist/codex/app-server.js +37 -14
- package/dist/codex/mission-session.d.ts +7 -0
- package/dist/codex/mission-session.js +29 -4
- package/package.json +1 -1
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export declare const V010_RUNTIME_ASSET_VERSION = "0.13.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.13.22-owned-task-lifecycle-v1";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export const V010_RUNTIME_ASSET_VERSION = "0.13.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.13.22-owned-task-lifecycle-v1";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
@@ -47,7 +47,7 @@ export async function runCodexMissionCommand(argv) {
|
|
|
47
47
|
const result = await adapter.run(prompt);
|
|
48
48
|
process.stdout.write(`${JSON.stringify({ status: result.accepted ? "succeeded" : "incomplete", root_session_id: result.rootSessionID,
|
|
49
49
|
thread_id: result.turn.threadID, turn_id: result.turn.turnID, native_status: result.turn.status,
|
|
50
|
-
response: result.turn.finalResponse, sessions: result.sessions, cost: null })}\n`);
|
|
50
|
+
response: result.turn.finalResponse, lifecycle: result.lifecycle, sessions: result.sessions, cost: null })}\n`);
|
|
51
51
|
return signalExit ?? (result.accepted ? 0 : 1);
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
@@ -66,6 +66,11 @@ export async function runCodexMissionCommand(argv) {
|
|
|
66
66
|
}
|
|
67
67
|
/** Bounded user-facing progress; do not dump control packets or hidden reasoning. */
|
|
68
68
|
export function codexMissionProgress(event) {
|
|
69
|
+
if (event.method === "turn/completed")
|
|
70
|
+
return { thread_id: event.threadId, phase: "native-turn-terminal", turn_id: event.params.turn?.id,
|
|
71
|
+
native_status: event.params.turn?.status };
|
|
72
|
+
if (event.method === "sortie/continuation")
|
|
73
|
+
return { thread_id: event.threadId, turn_id: event.params.turnId, phase: event.params.phase, calls: event.params.calls };
|
|
69
74
|
if (event.method === "sortie/modelRoute")
|
|
70
75
|
return { thread_id: event.threadId, phase: "model-route", agent: event.params.agent, model: event.params.model,
|
|
71
76
|
effort: event.params.effort, service_tier: event.params.serviceTier };
|
|
@@ -46,6 +46,14 @@ export interface CodexTurnResult {
|
|
|
46
46
|
readonly finalResponse?: string;
|
|
47
47
|
readonly items: readonly JsonObject[];
|
|
48
48
|
readonly usage?: JsonObject;
|
|
49
|
+
/** Host tools still running at native turn completion, drained without resending them. */
|
|
50
|
+
readonly deferredToolResults?: readonly CodexDeferredToolResult[];
|
|
51
|
+
}
|
|
52
|
+
export interface CodexDeferredToolResult {
|
|
53
|
+
readonly callID: string;
|
|
54
|
+
readonly tool: string;
|
|
55
|
+
readonly success: boolean;
|
|
56
|
+
readonly output: string;
|
|
49
57
|
}
|
|
50
58
|
export interface CodexAuthenticationState {
|
|
51
59
|
readonly type: string | null;
|
package/dist/codex/app-server.js
CHANGED
|
@@ -188,7 +188,7 @@ export class CodexAppServerHost {
|
|
|
188
188
|
if (!turnID)
|
|
189
189
|
throw new CodexHostError("protocol-invalid-response", "turn/start returned no turn id.");
|
|
190
190
|
return new Promise((resolve, reject) => {
|
|
191
|
-
this.active = { threadID, turnID, items: [], observer: Promise.resolve(), onEvent: options.onEvent, resolve, reject };
|
|
191
|
+
this.active = { threadID, turnID, items: [], observer: Promise.resolve(), tools: new Set(), onEvent: options.onEvent, resolve, reject };
|
|
192
192
|
const backlog = this.notificationBacklog.splice(0);
|
|
193
193
|
void backlog.reduce((prior, event) => prior.then(() => this.notification(event.method, event.params)), Promise.resolve())
|
|
194
194
|
.catch(error => { this.active?.reject(error); this.active = undefined; });
|
|
@@ -289,20 +289,34 @@ export class CodexAppServerHost {
|
|
|
289
289
|
const params = object(message.params) ? message.params : {};
|
|
290
290
|
if (method === "item/tool/call") {
|
|
291
291
|
const active = this.active;
|
|
292
|
-
|
|
292
|
+
if (!active || active.completing || params.threadId !== active.threadID || params.turnId !== active.turnID ||
|
|
293
|
+
(params.namespace != null) || typeof params.callId !== "string" || typeof params.tool !== "string" || !this.options.dynamicTool) {
|
|
294
|
+
this.transport.send({ id: message.id, result: { contentItems: [{ type: "inputText",
|
|
295
|
+
text: "Dynamic tool call does not belong to an active Sortie turn." }], success: false } });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const call = { threadId: active.threadID, turnId: active.turnID, callId: params.callId, tool: params.tool, arguments: params.arguments };
|
|
299
|
+
const pending = Promise.resolve().then(async () => {
|
|
300
|
+
let output, success;
|
|
301
|
+
try {
|
|
302
|
+
output = await this.options.dynamicTool(call);
|
|
303
|
+
success = true;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
output = error instanceof Error ? error.message : "Dynamic tool failed.";
|
|
307
|
+
success = false;
|
|
308
|
+
}
|
|
309
|
+
if (!this.closed && !this.fault)
|
|
310
|
+
this.transport.send({ id: message.id, result: { contentItems: [{ type: "inputText", text: output }], success } });
|
|
311
|
+
return { callID: call.callId, tool: call.tool, success, output };
|
|
312
|
+
});
|
|
313
|
+
active.tools.add(pending);
|
|
293
314
|
try {
|
|
294
|
-
|
|
295
|
-
(params.namespace != null) || typeof params.callId !== "string" || typeof params.tool !== "string" || !this.options.dynamicTool)
|
|
296
|
-
throw new Error("Dynamic tool call does not belong to an active Sortie turn.");
|
|
297
|
-
const output = await this.options.dynamicTool({ threadId: active.threadID, turnId: active.turnID,
|
|
298
|
-
callId: params.callId, tool: params.tool, arguments: params.arguments });
|
|
299
|
-
result = { contentItems: [{ type: "inputText", text: output }], success: true };
|
|
315
|
+
await pending;
|
|
300
316
|
}
|
|
301
|
-
|
|
302
|
-
|
|
317
|
+
finally {
|
|
318
|
+
active.tools.delete(pending);
|
|
303
319
|
}
|
|
304
|
-
if (!this.closed)
|
|
305
|
-
this.transport.send({ id: message.id, result });
|
|
306
320
|
return;
|
|
307
321
|
}
|
|
308
322
|
if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval") {
|
|
@@ -374,14 +388,23 @@ export class CodexAppServerHost {
|
|
|
374
388
|
// requests such as interrupt(), whose response must be read by this pump.
|
|
375
389
|
// Keep the completing turn active until observers settle so close() can
|
|
376
390
|
// reject it and a later turn cannot receive an interrupt meant for it.
|
|
377
|
-
|
|
391
|
+
const deferred = status === "completed" ? [...active.tools] : [];
|
|
392
|
+
// Native response completion is not host execution completion. Keep pumping
|
|
393
|
+
// command responses and child Tasks; explicit close/transport loss can still
|
|
394
|
+
// reject this turn. Failed/interrupted native turns never auto-continue.
|
|
395
|
+
void Promise.all([active.observer, Promise.all(deferred)]).then(([, tools]) => {
|
|
378
396
|
if (this.active !== active)
|
|
379
397
|
return;
|
|
380
398
|
this.active = undefined;
|
|
381
399
|
if (active.observerError)
|
|
382
400
|
active.reject(new CodexHostError("request-failed", "Codex turn event observer failed.", active.observerError));
|
|
383
401
|
else
|
|
384
|
-
active.resolve(result);
|
|
402
|
+
active.resolve({ ...result, ...(tools.length ? { deferredToolResults: tools } : {}) });
|
|
403
|
+
}).catch(error => {
|
|
404
|
+
if (this.active !== active)
|
|
405
|
+
return;
|
|
406
|
+
this.active = undefined;
|
|
407
|
+
active.reject(error);
|
|
385
408
|
});
|
|
386
409
|
}
|
|
387
410
|
}
|
|
@@ -67,6 +67,12 @@ export declare class CodexMissionSession {
|
|
|
67
67
|
rootSessionID: string;
|
|
68
68
|
turn: CodexTurnResult;
|
|
69
69
|
accepted: boolean;
|
|
70
|
+
lifecycle: {
|
|
71
|
+
root_turn_terminal: boolean;
|
|
72
|
+
owned_tasks_settled: boolean;
|
|
73
|
+
mission_phase: string | null;
|
|
74
|
+
mission_accepted: boolean;
|
|
75
|
+
};
|
|
70
76
|
sessions: readonly JsonObject[];
|
|
71
77
|
}>;
|
|
72
78
|
private stopHosts;
|
|
@@ -85,6 +91,7 @@ export declare class CodexMissionSession {
|
|
|
85
91
|
private restoreSession;
|
|
86
92
|
private tools;
|
|
87
93
|
private prompt;
|
|
94
|
+
private promptTurn;
|
|
88
95
|
private execute;
|
|
89
96
|
}
|
|
90
97
|
export {};
|
|
@@ -72,8 +72,11 @@ export class CodexMissionSession {
|
|
|
72
72
|
const previous = await new OperatorMissionRuntime(this.directory, profile).read(session.id);
|
|
73
73
|
const turn = await this.prompt(session, prompt);
|
|
74
74
|
const mission = await new OperatorMissionRuntime(this.directory, profile).read(session.id);
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const ownedTasksSettled = [...this.sessions.values()].every(item => !["running", "unknown"].includes(item.outcome));
|
|
76
|
+
const accepted = turn.status === "completed" && ownedTasksSettled && mission?.phase === "completed" &&
|
|
77
|
+
(previous?.phase !== "completed" || previous.id !== mission.id);
|
|
78
|
+
return { rootSessionID: session.id, turn, accepted, lifecycle: { root_turn_terminal: ["completed", "failed", "interrupted", "cancelled"].includes(turn.status),
|
|
79
|
+
owned_tasks_settled: ownedTasksSettled, mission_phase: mission?.phase ?? null, mission_accepted: accepted }, sessions: [...this.sessions.values()].map(item => ({ thread_id: item.id, agent: item.agent, model: item.model, usage: item.usage ?? null, usage_scope: "native-thread-cumulative", cost: null })) };
|
|
77
80
|
}
|
|
78
81
|
async stopHosts() {
|
|
79
82
|
this.closed = true;
|
|
@@ -459,11 +462,33 @@ export class CodexMissionSession {
|
|
|
459
462
|
return tools;
|
|
460
463
|
}
|
|
461
464
|
async prompt(session, prompt, onDispatch) {
|
|
465
|
+
let result = await this.promptTurn(session, prompt, onDispatch);
|
|
466
|
+
while (result.status === "completed" && result.deferredToolResults?.length) {
|
|
467
|
+
if (this.closed)
|
|
468
|
+
throw new Error("Codex Mission adapter is closed.");
|
|
469
|
+
const mission = await new OperatorMissionRuntime(this.directory, profile).read(this.rootOf(session));
|
|
470
|
+
if (!mission || ["completed", "cancelled"].includes(mission.phase))
|
|
471
|
+
break;
|
|
472
|
+
await this.claimOwner(this.rootOf(session));
|
|
473
|
+
await this.options.onEvent?.({ method: "sortie/continuation", threadId: session.id,
|
|
474
|
+
params: { turnId: result.turnID, phase: "owned-tools-settled", calls: result.deferredToolResults.map(item => ({ callId: item.callID, tool: item.tool, success: item.success })) } });
|
|
475
|
+
result = await this.promptTurn(session, "The previous native response ended while existing host tools were still running. Those exact calls have now returned:\n" +
|
|
476
|
+
JSON.stringify(result.deferredToolResults) +
|
|
477
|
+
"\nContinue this same Mission from these results through the remaining validation, delivery, review and root acceptance. " +
|
|
478
|
+
"Do not resend the original request or redispatch the completed Tasks. Preserve failures, ownership and cumulative budget; use existing Mission status for the next action.", undefined, true);
|
|
479
|
+
}
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
async promptTurn(session, prompt, onDispatch, continuation = false) {
|
|
462
483
|
const startedAt = Date.now();
|
|
463
484
|
const messageID = randomUUID();
|
|
464
485
|
const message = { id: messageID, agent: session.agent, model: { ...session.model } };
|
|
465
|
-
const parts = [{ type: "text", text: prompt }];
|
|
466
|
-
|
|
486
|
+
const parts = [{ type: "text", text: prompt, ...(continuation ? { synthetic: true } : {}) }];
|
|
487
|
+
// Only this host's drained results can continue an already-admitted execution.
|
|
488
|
+
// Reclaiming that Task with the result prompt rejects Workers' exact handoff;
|
|
489
|
+
// treating it as a real root request also grants spurious user-turn authority.
|
|
490
|
+
if (!continuation)
|
|
491
|
+
await this.hooks["chat.message"]?.({ sessionID: session.id, messageID, agent: session.agent }, { message, parts });
|
|
467
492
|
// Shared Task routing may select a model; explicit adapter overrides still govern every native turn.
|
|
468
493
|
if (message.model.providerID !== "openai")
|
|
469
494
|
throw new Error("Codex Mission cannot use a non-OpenAI model route.");
|