@jskit-ai/assistant-core 0.1.212 → 0.1.214
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/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/ConversationFixture.vue +7 -1
- package/package.json +5 -5
- package/src/client/conversation/AssistantConversationElement.vue +5 -1
- package/src/client/conversation/AssistantTranscript.vue +10 -1
- package/src/server/conversation/agentRun.js +38 -11
- package/src/server/conversation/claudeHistory.js +40 -5
- package/src/server/conversation/claudeProcess.js +28 -4
- package/src/server/conversation/claudeTools.js +11 -2
- package/src/server/conversation/claudeTurn.js +213 -23
- package/src/server/conversation/codexConfiguration.js +1 -1
- package/src/server/conversation/codexHistoryAdapter.js +11 -4
- package/src/server/conversation/codexNotificationQueue.js +11 -2
- package/src/server/conversation/codexObservationControl.js +8 -8
- package/src/server/conversation/codexOutput.js +20 -15
- package/src/server/conversation/codexProvider.js +40 -3
- package/src/server/conversation/codexProviderOwner.js +110 -1
- package/src/server/conversation/codexTurn.js +3 -0
- package/src/server/conversation/continuity.js +24 -6
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/nativeHistoryExport.js +97 -0
- package/src/server/conversation/openCodeClient.js +47 -1
- package/src/server/conversation/openCodeRuntime.js +183 -28
- package/src/server/conversation/openCodeTurn.js +65 -24
- package/src/server/conversation/providers/api.js +7 -0
- package/src/server/conversation/providers/claudeDriver.js +52 -7
- package/src/server/conversation/providers/codex.js +19 -2
- package/src/server/conversation/providers/codexDriver.js +92 -14
- package/src/server/conversation/providers/opencodeDriver.js +31 -9
- package/src/server/conversation/runtime.js +424 -65
- package/src/server/conversation/runtimeStateUpgrade.js +77 -2
- package/src/server/conversation/streams.js +25 -5
- package/src/server/conversation/tools.js +2 -2
- package/src/server/conversation/transcript.js +23 -8
- package/src/server/hosts/claudeHistory.js +1 -0
- package/src/server/hosts/claudeProcess.js +1 -0
- package/src/server/hosts/codexProvider.js +3 -0
- package/src/server/hosts/nativeHistory.js +1 -0
- package/src/server/hosts/openCodeClient.js +1 -0
- package/src/server/hosts/openCodeTurn.js +1 -0
- package/src/server/index.js +1 -1
- package/src/server/lib/assistantToolLoop.js +99 -2
- package/test/aiConnectionClient.test.js +57 -0
- package/test/assistantBoundedToolLoop.test.js +332 -0
- package/test/assistantScroll.browser.test.js +151 -0
- package/test/claudeConversationSession.test.js +1417 -3
- package/test/claudeProcess.test.js +175 -0
- package/test/claudeTurn.test.js +466 -0
- package/test/codexConversationSession.test.js +1257 -12
- package/test/codexHistoryAdapter.test.js +74 -1
- package/test/codexTurn.test.js +282 -1
- package/test/conversation.test.js +83 -0
- package/test/conversationAgentRun.test.js +181 -0
- package/test/conversationRuntime.test.js +95 -0
- package/test/conversationRuntimeStateUpgrade.test.js +89 -0
- package/test/conversationStreaming.test.js +85 -0
- package/test/conversationTools.test.js +165 -1
- package/test/openCodeClient.test.js +186 -0
- package/test/openCodeRuntime.test.js +372 -0
- package/test/opencodeConversationSession.test.js +637 -5
|
@@ -324,3 +324,184 @@ test("native output origin joins only its exact outer authored request and prese
|
|
|
324
324
|
assert.equal(unowned.metadata.runtime.origin, undefined);
|
|
325
325
|
}
|
|
326
326
|
});
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
test("native completed text uses the exact authored row without changing saved or public stream grouping", async () => {
|
|
330
|
+
for (const origin of ["user", "application"]) {
|
|
331
|
+
const f = await fixture();
|
|
332
|
+
let current = true;
|
|
333
|
+
const transcript = createConversationTranscript({ storage: f.storage, applicationTurns: true });
|
|
334
|
+
const streams = createConversationStreams();
|
|
335
|
+
const store = createCodexConversationStore({ storage: f.storage, scope, segmentId: "segment-one",
|
|
336
|
+
isCurrent: () => current, transcript, streams });
|
|
337
|
+
const nativeIdentity = { threadId: "native-thread", turnId: "native-turn" };
|
|
338
|
+
const authoredRequest = { messageId: "outer-request", text: "Exact authored request", origin };
|
|
339
|
+
const row = await store.writeConversationUserMessage(scope, { messageId: authoredRequest.messageId, authoredRequest, nativeIdentity });
|
|
340
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { state: "active", providerThreadId: nativeIdentity.threadId,
|
|
341
|
+
providerTurnId: nativeIdentity.turnId, outerTurnId: authoredRequest.messageId } });
|
|
342
|
+
const before = await transcript.readConversationLog(scope);
|
|
343
|
+
const live = await store.updateConversationStream(scope, { nativeIdentity, turnId: "native-thread:native-turn",
|
|
344
|
+
messageId: "native-item", outputId: "native-item", role: "commentary", delta: "Partial" });
|
|
345
|
+
assert.equal(live.messages[0].origin, origin);
|
|
346
|
+
assert.equal(Object.hasOwn(live.messages[0], "turnId"), false);
|
|
347
|
+
assert.equal(JSON.stringify(live).includes("nativeIdentity"), false);
|
|
348
|
+
const completed = store.completeConversationStreamMessage(scope, "native-item", { text: "Exact completed sentence" });
|
|
349
|
+
assert.equal(completed.completedMessages[0].turnId, row.turnId);
|
|
350
|
+
assert.equal(completed.completedMessages[0].origin, origin);
|
|
351
|
+
assert.equal(completed.completedMessages[0].text, "Exact completed sentence");
|
|
352
|
+
assert.equal(completed.completedMessages[0].status, "complete");
|
|
353
|
+
assert.deepEqual(await transcript.readConversationLog(scope), before, "Completion adds no canonical commentary row");
|
|
354
|
+
assert.equal(streams.read(scope).completedMessages, undefined);
|
|
355
|
+
assert.equal(store.completeConversationStreamMessage(scope, "native-item", { text: "Repeated" }).completedMessages, undefined);
|
|
356
|
+
current = false;
|
|
357
|
+
assert.throws(() => store.completeConversationStreamMessage(scope, "native-item", { text: "Retired" }), /retired provider/);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("pending native custody gains only its same authored row and never a successor's identity", async () => {
|
|
362
|
+
for (const sameRequest of [true, false]) {
|
|
363
|
+
const f = await fixture();
|
|
364
|
+
const transcript = createConversationTranscript({ storage: f.storage, applicationTurns: true });
|
|
365
|
+
const store = createCodexConversationStore({ storage: f.storage, scope, segmentId: "segment-one",
|
|
366
|
+
isCurrent: () => true, transcript, streams: createConversationStreams() });
|
|
367
|
+
const nativeIdentity = { threadId: "native-thread", turnId: "native-turn" };
|
|
368
|
+
await f.storage.write(scope, async transaction => {
|
|
369
|
+
const metadata = await transaction.readMetadata(); metadata.runtime.request.origin = "user";
|
|
370
|
+
await transaction.writeMetadata(metadata);
|
|
371
|
+
});
|
|
372
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { state: "active", providerThreadId: nativeIdentity.threadId,
|
|
373
|
+
providerTurnId: nativeIdentity.turnId, outerTurnId: "authored-request" } });
|
|
374
|
+
const input = { nativeIdentity, turnId: "native-thread:native-turn", messageId: "early-item", role: "commentary" };
|
|
375
|
+
await store.updateConversationStream(scope, { ...input, delta: "Early" });
|
|
376
|
+
const messageId = sameRequest ? "authored-request" : "successor";
|
|
377
|
+
const row = await store.writeConversationUserMessage(scope, { messageId,
|
|
378
|
+
authoredRequest: { messageId, text: "Admitted request", origin: "user" }, nativeIdentity });
|
|
379
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { outerTurnId: messageId } });
|
|
380
|
+
await store.updateConversationStream(scope, { ...input, delta: " complete" });
|
|
381
|
+
const completed = store.completeConversationStreamMessage(scope, input.messageId, { text: "Completed early item" });
|
|
382
|
+
assert.equal(completed.completedMessages?.[0]?.turnId, sameRequest ? row.turnId : undefined);
|
|
383
|
+
assert.equal((await transcript.readConversationLog(scope)).flatMap(turn => turn.commentary || []).length, 0);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("captured native predecessor commentary cannot be reassigned by same-turn steering", async () => {
|
|
388
|
+
const f = await fixture();
|
|
389
|
+
const transcript = createConversationTranscript({ storage: f.storage, applicationTurns: true });
|
|
390
|
+
const store = createCodexConversationStore({ storage: f.storage, scope, segmentId: "segment-one",
|
|
391
|
+
isCurrent: () => true, transcript, streams: createConversationStreams() });
|
|
392
|
+
const nativeIdentity = { threadId: "native-thread", turnId: "native-turn" };
|
|
393
|
+
const authored = async messageId => store.writeConversationUserMessage(scope, { messageId,
|
|
394
|
+
authoredRequest: { messageId, text: messageId, origin: "user" }, nativeIdentity });
|
|
395
|
+
const first = await authored("predecessor");
|
|
396
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { state: "active", providerThreadId: nativeIdentity.threadId,
|
|
397
|
+
providerTurnId: nativeIdentity.turnId, outerTurnId: "predecessor" } });
|
|
398
|
+
const input = { nativeIdentity, turnId: "native-thread:native-turn", messageId: "old-item", role: "commentary" };
|
|
399
|
+
await store.updateConversationStream(scope, { ...input, delta: "Original intent" });
|
|
400
|
+
const successor = await authored("successor");
|
|
401
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { outerTurnId: "successor" } });
|
|
402
|
+
await store.updateConversationStream(scope, { ...input, delta: " late words" });
|
|
403
|
+
assert.equal(store.completeConversationStreamMessage(scope, input.messageId, { text: "Original completed intent" })
|
|
404
|
+
.completedMessages[0].turnId, first.turnId);
|
|
405
|
+
await store.updateConversationStream(scope, { ...input, messageId: "new-item", delta: "Successor intent" });
|
|
406
|
+
assert.equal(store.completeConversationStreamMessage(scope, "new-item", { text: "Successor completed intent" })
|
|
407
|
+
.completedMessages[0].turnId, successor.turnId);
|
|
408
|
+
const unproven = await store.updateConversationStream(scope, { ...input, messageId: "foreign-item",
|
|
409
|
+
nativeIdentity: { ...nativeIdentity, threadId: "foreign-thread" }, delta: "Foreign" });
|
|
410
|
+
assert.equal(unproven.messages[0].origin, undefined);
|
|
411
|
+
assert.equal(store.completeConversationStreamMessage(scope, "foreign-item", { text: "Foreign" }).completedMessages, undefined);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
test("a single pending native delta resolves only its captured same request at exact completion", async () => {
|
|
416
|
+
for (const selected of ["same", "successor", "foreign", "changed-origin"]) {
|
|
417
|
+
const f = await fixture();
|
|
418
|
+
const transcript = createConversationTranscript({ storage: f.storage, applicationTurns: true });
|
|
419
|
+
const store = createCodexConversationStore({ storage: f.storage, scope, segmentId: "segment-one",
|
|
420
|
+
isCurrent: () => true, transcript, streams: createConversationStreams() });
|
|
421
|
+
const nativeIdentity = { threadId: "native-thread", turnId: "native-turn" };
|
|
422
|
+
await f.storage.write(scope, async transaction => {
|
|
423
|
+
const metadata = await transaction.readMetadata(); metadata.runtime.request.origin = "user";
|
|
424
|
+
await transaction.writeMetadata(metadata);
|
|
425
|
+
});
|
|
426
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { state: "active", providerThreadId: nativeIdentity.threadId,
|
|
427
|
+
providerTurnId: nativeIdentity.turnId, outerTurnId: "authored-request" } });
|
|
428
|
+
await store.updateConversationStream(scope, { nativeIdentity, turnId: "native-thread:native-turn",
|
|
429
|
+
messageId: "single-delta", role: "commentary", delta: "Early partial words" });
|
|
430
|
+
const messageId = selected === "successor" ? "successor" : "authored-request";
|
|
431
|
+
const row = await store.writeConversationUserMessage(scope, { messageId, nativeIdentity,
|
|
432
|
+
authoredRequest: { messageId, text: "Admitted exact request", origin: selected === "changed-origin" ? "application" : "user" } });
|
|
433
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { outerTurnId: messageId } });
|
|
434
|
+
const before = await transcript.readConversationLog(scope);
|
|
435
|
+
const completed = await store.completeConversationStreamMessage(scope, "single-delta", {
|
|
436
|
+
nativeIdentity: selected === "foreign" ? { ...nativeIdentity, threadId: "foreign-thread" } : nativeIdentity,
|
|
437
|
+
text: "Exact completed intent", role: "commentary" });
|
|
438
|
+
assert.equal(completed.completedMessages?.[0]?.turnId, selected === "same" ? row.turnId : undefined);
|
|
439
|
+
if (selected === "same") {
|
|
440
|
+
assert.equal(completed.completedMessages[0].text, "Exact completed intent");
|
|
441
|
+
assert.equal(completed.completedMessages[0].origin, "user");
|
|
442
|
+
}
|
|
443
|
+
assert.deepEqual(await transcript.readConversationLog(scope), before);
|
|
444
|
+
assert.equal((await store.completeConversationStreamMessage(scope, "single-delta", {
|
|
445
|
+
nativeIdentity, text: "Repeat", role: "commentary" })).completedMessages, undefined);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
test("native envelope stream markers come only from exact current pending or authored metadata", async () => {
|
|
451
|
+
for (const selected of ["pending", "authored", "foreign", "ordinary"]) {
|
|
452
|
+
const f = await fixture();
|
|
453
|
+
const transcript = createConversationTranscript({ storage: f.storage, applicationTurns: true });
|
|
454
|
+
const store = createCodexConversationStore({ storage: f.storage, scope, segmentId: "segment-one",
|
|
455
|
+
isCurrent: () => true, transcript, streams: createConversationStreams() });
|
|
456
|
+
const nativeIdentity = { threadId: "native-thread", turnId: "native-turn" };
|
|
457
|
+
await f.storage.write(scope, async tx => {
|
|
458
|
+
const metadata = await tx.readMetadata();
|
|
459
|
+
metadata.runtime.request.origin = "application";
|
|
460
|
+
metadata.runtime.request.text = "Original internal prompt";
|
|
461
|
+
if (selected !== "ordinary") metadata.runtime.request.completedEnvelope = true;
|
|
462
|
+
await tx.writeMetadata(metadata);
|
|
463
|
+
});
|
|
464
|
+
if (selected === "authored") await store.writeConversationUserMessage(scope, {
|
|
465
|
+
messageId: "authored-request", nativeIdentity, authoredRequest: { messageId: "authored-request", origin: "application", text: "Original internal prompt" }
|
|
466
|
+
});
|
|
467
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { state: "active", providerThreadId: nativeIdentity.threadId,
|
|
468
|
+
providerTurnId: nativeIdentity.turnId, outerTurnId: "authored-request" } });
|
|
469
|
+
const stream = await store.updateConversationStream(scope, { nativeIdentity: selected === "foreign"
|
|
470
|
+
? { ...nativeIdentity, turnId: "foreign-turn" } : nativeIdentity,
|
|
471
|
+
turnId: "native-thread:native-turn", messageId: "native-partial", text: "Partial internal JSON",
|
|
472
|
+
completedEnvelope: true, data: { completedEnvelope: true } });
|
|
473
|
+
assert.equal(stream.messages[0].completedEnvelope, ["pending", "authored"].includes(selected) ? true : undefined);
|
|
474
|
+
assert.equal(Object.hasOwn(stream.messages[0], "turnId"), false);
|
|
475
|
+
assert.equal(JSON.stringify(stream).includes("authored-request"), false);
|
|
476
|
+
if (selected === "authored") {
|
|
477
|
+
const completed = await store.completeConversationStreamMessage(scope, "native-partial", { nativeIdentity, text: "Exact internal final" });
|
|
478
|
+
assert.equal(completed.completedMessages[0].completedEnvelope, true);
|
|
479
|
+
assert.equal(completed.completedMessages[0].text, "Exact internal final");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("marked native pending stream completion cannot borrow a successor's authored receipt", async () => {
|
|
485
|
+
const f = await fixture();
|
|
486
|
+
const transcript = createConversationTranscript({ storage: f.storage, applicationTurns: true });
|
|
487
|
+
const store = createCodexConversationStore({ storage: f.storage, scope, segmentId: "segment-one",
|
|
488
|
+
isCurrent: () => true, transcript, streams: createConversationStreams() });
|
|
489
|
+
const nativeIdentity = { threadId: "native-thread", turnId: "native-turn" };
|
|
490
|
+
await f.storage.write(scope, async tx => {
|
|
491
|
+
const metadata = await tx.readMetadata();
|
|
492
|
+
metadata.runtime.request.origin = "application";
|
|
493
|
+
metadata.runtime.request.completedEnvelope = true;
|
|
494
|
+
await tx.writeMetadata(metadata);
|
|
495
|
+
});
|
|
496
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { state: "active", providerThreadId: nativeIdentity.threadId,
|
|
497
|
+
providerTurnId: nativeIdentity.turnId, outerTurnId: "authored-request" } });
|
|
498
|
+
const live = await store.updateConversationStream(scope, { nativeIdentity, turnId: "native-thread:native-turn",
|
|
499
|
+
messageId: "captured-item", text: "Partial original" });
|
|
500
|
+
assert.equal(live.messages[0].completedEnvelope, true);
|
|
501
|
+
await store.writeConversationUserMessage(scope, { messageId: "successor", nativeIdentity,
|
|
502
|
+
authoredRequest: { messageId: "successor", text: "New ordinary instruction", origin: "user" } });
|
|
503
|
+
await store.writeAgentRunEvent(scope, runId, { patch: { outerTurnId: "successor" } });
|
|
504
|
+
const completed = await store.completeConversationStreamMessage(scope, "captured-item", { nativeIdentity, text: "Late old final" });
|
|
505
|
+
assert.equal(completed.completedMessages, undefined);
|
|
506
|
+
assert.equal((await transcript.readConversationLog(scope))[0].user.messageId, "successor");
|
|
507
|
+
});
|
|
@@ -408,3 +408,98 @@ test("Codex never interrupts a late active turn for instructions and restores it
|
|
|
408
408
|
assert.equal(goal.status, "active");
|
|
409
409
|
assert.equal(f.requests.some(request => ["host.recordInterruption", "turn/interrupt", "thread/unsubscribe", "thread/resume", "thread/inject_items"].includes(request.method)), false);
|
|
410
410
|
});
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
test("Codex excludes deferred instruction composition without replenishing spent native-control time", async t => {
|
|
414
|
+
for (const mode of ["idle", "became active", "native timed out", "native refused", "binding closed", "budget exhausted before composition"]) {
|
|
415
|
+
await t.test(mode, async t => {
|
|
416
|
+
let now = 0;
|
|
417
|
+
const deadlines = [];
|
|
418
|
+
t.mock.method(performance, "now", () => now);
|
|
419
|
+
t.mock.method(AbortSignal, "timeout", duration => {
|
|
420
|
+
const controller = new AbortController();
|
|
421
|
+
deadlines.push({ duration, due: now + duration, controller });
|
|
422
|
+
return controller.signal;
|
|
423
|
+
});
|
|
424
|
+
const advance = duration => {
|
|
425
|
+
now += duration;
|
|
426
|
+
for (const deadline of deadlines) {
|
|
427
|
+
if (deadline.due <= now && !deadline.controller.signal.aborted) {
|
|
428
|
+
deadline.controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
let refresh = false, instructionReads = 0;
|
|
433
|
+
const f = codexHost({ readInstructions: () => {
|
|
434
|
+
instructionReads += 1;
|
|
435
|
+
if (!refresh) return "Installed project instructions";
|
|
436
|
+
advance(15_000);
|
|
437
|
+
if (mode === "became active") f.state.status = "active";
|
|
438
|
+
if (mode === "binding closed") f.runtime.close();
|
|
439
|
+
return "Fresh composed project instructions";
|
|
440
|
+
} });
|
|
441
|
+
await f.runtime.withThreadContext("native-history", {}, () => {});
|
|
442
|
+
const installed = f.runtime.threadEnvironments.get("native-history");
|
|
443
|
+
f.requests.length = 0;
|
|
444
|
+
const request = f.client.request;
|
|
445
|
+
let statusReads = 0;
|
|
446
|
+
f.client.request = async (method, input, options) => {
|
|
447
|
+
if (method === "thread/read") {
|
|
448
|
+
statusReads += 1;
|
|
449
|
+
if (statusReads === 1) {
|
|
450
|
+
if (mode === "budget exhausted before composition") now += 10_000;
|
|
451
|
+
else advance(2_500);
|
|
452
|
+
}
|
|
453
|
+
if (statusReads === 2 && mode === "native timed out") {
|
|
454
|
+
advance(7_501);
|
|
455
|
+
options.signal.throwIfAborted();
|
|
456
|
+
}
|
|
457
|
+
if (statusReads === 2 && mode === "native refused") throw new Error("Native read refused");
|
|
458
|
+
}
|
|
459
|
+
options.signal.throwIfAborted();
|
|
460
|
+
return request(method, input, options);
|
|
461
|
+
};
|
|
462
|
+
refresh = true;
|
|
463
|
+
let operations = 0;
|
|
464
|
+
const operation = f.runtime.withThreadContext("native-history", {}, params => {
|
|
465
|
+
operations += 1;
|
|
466
|
+
assert.equal(params.developerInstructions, mode === "became active"
|
|
467
|
+
? installed.params.developerInstructions : "Fresh composed project instructions");
|
|
468
|
+
});
|
|
469
|
+
if (mode === "budget exhausted before composition") {
|
|
470
|
+
await assert.rejects(operation, { name: "TimeoutError" });
|
|
471
|
+
assert.equal(instructionReads, 1, "An exhausted native budget cannot begin deferred composition");
|
|
472
|
+
assert.equal(operations, 0);
|
|
473
|
+
assert.equal(statusReads, 1);
|
|
474
|
+
} else if (["native timed out", "native refused"].includes(mode)) {
|
|
475
|
+
await assert.rejects(operation, error => {
|
|
476
|
+
assert.match(error.message, /could not be verified/);
|
|
477
|
+
assert.equal(error.cause.name, mode === "native timed out" ? "TimeoutError" : "Error");
|
|
478
|
+
return true;
|
|
479
|
+
});
|
|
480
|
+
assert.equal(operations, 0);
|
|
481
|
+
assert.equal(f.runtime.bindingCount, 0);
|
|
482
|
+
} else if (mode === "binding closed") {
|
|
483
|
+
await assert.rejects(operation, error => {
|
|
484
|
+
assert.match(error.message, /could not be verified/);
|
|
485
|
+
assert.match(error.cause.message, /closed during recovery/);
|
|
486
|
+
return true;
|
|
487
|
+
});
|
|
488
|
+
assert.equal(operations, 0);
|
|
489
|
+
assert.equal(statusReads, 1, "Retired instructions cannot issue a later native request");
|
|
490
|
+
} else {
|
|
491
|
+
await operation;
|
|
492
|
+
assert.equal(operations, 1);
|
|
493
|
+
if (mode === "became active") {
|
|
494
|
+
assert.equal(f.runtime.threadEnvironments.get("native-history"), installed);
|
|
495
|
+
assert.equal(f.requests.some(r => ["thread/goal/get", "turn/interrupt", "thread/unsubscribe", "thread/resume", "thread/inject_items"].includes(r.method)), false);
|
|
496
|
+
} else {
|
|
497
|
+
assert.equal(f.requests.filter(r => r.method === "thread/inject_items").length, 1);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
assert.deepEqual(deadlines.map(deadline => deadline.duration), mode === "budget exhausted before composition"
|
|
501
|
+
? [10_000, 10_000] : [10_000, 10_000, 7_500],
|
|
502
|
+
"Only host composition is excluded; the 2.5 seconds already spent are not replenished");
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
});
|
|
@@ -142,3 +142,92 @@ test("current journals distinguish an unsent reservation from a frozen attempted
|
|
|
142
142
|
request: { ...frozen.metadata.runtime.request, ...patch } } } }), { code: "conversation_runtime_upgrade_unavailable" });
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
|
+
|
|
146
|
+
function codexRetirementRecord() {
|
|
147
|
+
const value = record("codex", { version: 3, lastEngine: "codex",
|
|
148
|
+
continuity: "Retained discussion", continuityAttachments: [{ attachmentId: "earlier" }],
|
|
149
|
+
binding: { ...bindings.codex, accountIdentity: "retained-account", toolSchemaIdentity: "a".repeat(64),
|
|
150
|
+
run: { turnId: "retained-native-turn", status: "running" }, goal: { status: "active", objective: "Retained native goal" } },
|
|
151
|
+
predecessors: [segment("claude", { segmentId: "earlier-segment" })] });
|
|
152
|
+
value.metadata.application.effects = [{ toolCallId: "already-invoked", status: "unknown" }];
|
|
153
|
+
return { ...value, retirement: { operationId: "retire-old-manifest", successorSegmentId: "inert-successor",
|
|
154
|
+
expectedSegmentId: value.metadata.runtime.segmentId, expectedThreadId: value.metadata.runtime.binding.threadId,
|
|
155
|
+
expectedToolSchemaIdentity: value.metadata.runtime.binding.toolSchemaIdentity,
|
|
156
|
+
workdir: value.metadata.runtime.binding.workdir, configRoot: value.metadata.runtime.binding.configRoot } };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
test("explicit Codex retirement only transforms metadata and preserves the exact predecessor and history", () => {
|
|
160
|
+
const value = codexRetirementRecord();
|
|
161
|
+
const before = structuredClone(value);
|
|
162
|
+
const { retirement, ...withoutRetirement } = value;
|
|
163
|
+
const unchanged = upgradeConversationRuntimeState(withoutRetirement);
|
|
164
|
+
assert.equal(unchanged.changed, false, "Known metadata alone must not activate retirement");
|
|
165
|
+
assert.equal(unchanged.metadata, value.metadata);
|
|
166
|
+
assert.deepEqual(unchanged.warnings, []);
|
|
167
|
+
|
|
168
|
+
const result = upgradeConversationRuntimeState(value);
|
|
169
|
+
const old = before.metadata.runtime;
|
|
170
|
+
const current = result.metadata.runtime;
|
|
171
|
+
assert.equal(result.changed, true);
|
|
172
|
+
assert.deepEqual(result.warnings, []);
|
|
173
|
+
assert.deepEqual(result.metadata.application, before.metadata.application);
|
|
174
|
+
assert.deepEqual(current.predecessors[0], old.predecessors[0]);
|
|
175
|
+
assert.deepEqual(current.predecessors[1], { segmentId: old.segmentId, engine: old.engine,
|
|
176
|
+
configuration: old.configuration, binding: old.binding, seen: old.seen,
|
|
177
|
+
continuity: old.continuity, continuityAttachments: old.continuityAttachments, request: undefined,
|
|
178
|
+
successorId: retirement.successorSegmentId, replacement: { operationId: retirement.operationId,
|
|
179
|
+
expectedSegmentId: old.segmentId, engine: "codex", configuration: old.configuration,
|
|
180
|
+
retireNative: true, operation: "select" } });
|
|
181
|
+
assert.deepEqual(current.binding, { threadId: "", workdir: old.binding.workdir,
|
|
182
|
+
configRoot: old.binding.configRoot, executionId: "" });
|
|
183
|
+
assert.equal(current.segmentId, retirement.successorSegmentId);
|
|
184
|
+
assert.equal(current.engine, "codex");
|
|
185
|
+
assert.equal(current.version, 3);
|
|
186
|
+
assert.deepEqual(current.configuration, old.configuration);
|
|
187
|
+
assert.deepEqual(current.seen, {});
|
|
188
|
+
assert.equal(current.lastEngine, "");
|
|
189
|
+
assert.equal(current.continuity, undefined);
|
|
190
|
+
assert.deepEqual(current.continuityAttachments, []);
|
|
191
|
+
assert.equal(Object.hasOwn(current, "request"), false);
|
|
192
|
+
assert.equal(Object.hasOwn(current, "replacement"), false);
|
|
193
|
+
assert.equal(Object.hasOwn(current.predecessors[1], "preparedAt"), false);
|
|
194
|
+
assert.deepEqual(value, before, "Native activity and effect facts are retained, not certified or mutated");
|
|
195
|
+
assert.deepEqual(upgradeConversationRuntimeState(structuredClone(before)), result,
|
|
196
|
+
"Verified before metadata and stable IDs yield the same pure result");
|
|
197
|
+
const repeated = { ...before, metadata: result.metadata };
|
|
198
|
+
const repeatedBefore = structuredClone(repeated);
|
|
199
|
+
assert.throws(() => upgradeConversationRuntimeState(repeated), { code: "conversation_runtime_upgrade_unavailable" });
|
|
200
|
+
assert.deepEqual(repeated, repeatedBefore, "A converted tuple is not silently retired a second time");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("explicit Codex retirement refuses malformed IDs, changed identity and unresolved metadata without mutation", () => {
|
|
204
|
+
const cases = [
|
|
205
|
+
["numeric operation ID", value => { value.retirement.operationId = 1; }],
|
|
206
|
+
["numeric successor ID", value => { value.retirement.successorSegmentId = 1; }],
|
|
207
|
+
["unknown input field", value => { value.retirement.idle = true; }],
|
|
208
|
+
["changed segment", value => { value.retirement.expectedSegmentId = "other-segment"; }],
|
|
209
|
+
["changed native thread", value => { value.retirement.expectedThreadId = "other-thread"; }],
|
|
210
|
+
["changed tool schema", value => { value.retirement.expectedToolSchemaIdentity = "b".repeat(64); }],
|
|
211
|
+
["changed workdir", value => { value.retirement.workdir = "/other-workspace"; }],
|
|
212
|
+
["changed config root", value => { value.retirement.configRoot = "/other-account"; }],
|
|
213
|
+
["relative path", value => { value.retirement.workdir = "workspace"; }],
|
|
214
|
+
["current successor", value => { value.retirement.successorSegmentId = value.metadata.runtime.segmentId; }],
|
|
215
|
+
["retained successor", value => { value.retirement.successorSegmentId = "earlier-segment"; }],
|
|
216
|
+
["reserved successor", value => { value.metadata.runtime.predecessors[0].successorId = value.retirement.successorSegmentId; }],
|
|
217
|
+
["used operation", value => { value.metadata.runtime.predecessors[0].replacement = { operationId: value.retirement.operationId }; }],
|
|
218
|
+
["missing runtime", value => { delete value.metadata.runtime; }],
|
|
219
|
+
["old runtime version", value => { value.metadata.runtime.version = 2; }],
|
|
220
|
+
["other engine", value => { value.metadata.runtime.engine = "claude"; value.metadata.runtime.binding = bindings.claude; }],
|
|
221
|
+
["pending delivery", value => { value.metadata.runtime.request = { ...request, attempted: false }; }],
|
|
222
|
+
["pending replacement", value => { value.metadata.runtime.replacement = { ...segment("codex"), segmentId: "pending",
|
|
223
|
+
request: { operationId: "pending-selection", expectedSegmentId: value.metadata.runtime.segmentId, operation: "select" } }; }],
|
|
224
|
+
["unfinished Undo", value => { value.metadata.runtime.replacement = { ...segment("codex"), reason: "rewind" }; }]
|
|
225
|
+
];
|
|
226
|
+
for (const [reason, mutate] of cases) {
|
|
227
|
+
const value = codexRetirementRecord();
|
|
228
|
+
mutate(value);
|
|
229
|
+
const before = structuredClone(value);
|
|
230
|
+
assert.throws(() => upgradeConversationRuntimeState(value), { code: "conversation_runtime_upgrade_unavailable" }, reason);
|
|
231
|
+
assert.deepEqual(value, before, reason);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
@@ -153,3 +153,88 @@ test("output identity survives stream revisions and saved projection without cha
|
|
|
153
153
|
assert.equal(final[0].assistant.messageId, "native-saved");
|
|
154
154
|
assert.equal(final[0].assistant.outputId, snapshot.messages[0].outputId);
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
test("native completed carriers retain exact final text once without exposing private custody in live reads", () => {
|
|
159
|
+
const streams = createConversationStreams();
|
|
160
|
+
const input = { nativeIdentity: { threadId: "thread", turnId: "native-turn" }, turnId: "thread:native-turn",
|
|
161
|
+
messageId: "native-item", outputId: "exact-output", role: "commentary", origin: "user",
|
|
162
|
+
authorship: { messageId: "authored-message", turnId: "000001", origin: "user" } };
|
|
163
|
+
streams.update("chat", { ...input, delta: "Partial words" });
|
|
164
|
+
const live = streams.read("chat");
|
|
165
|
+
assert.equal(Object.hasOwn(live.messages[0], "turnId"), false);
|
|
166
|
+
assert.equal(JSON.stringify(live).includes("authored-message"), false);
|
|
167
|
+
assert.equal(JSON.stringify(live).includes("nativeIdentity"), false);
|
|
168
|
+
const final = "Corrected completed sentence.\nExact ending.";
|
|
169
|
+
const completed = streams.complete("chat", input.messageId, { text: final });
|
|
170
|
+
assert.deepEqual(completed.messages, []);
|
|
171
|
+
assert.deepEqual(completed.completedMessages.map(message => ({ turnId: message.turnId, outputId: message.outputId,
|
|
172
|
+
messageId: message.messageId, origin: message.origin, role: message.role, text: message.text, status: message.status })),
|
|
173
|
+
[{ turnId: "000001", outputId: "exact-output", messageId: "native-item", origin: "user", role: "commentary",
|
|
174
|
+
text: final, status: "complete" }]);
|
|
175
|
+
completed.completedMessages[0].text = "Changed return value";
|
|
176
|
+
assert.equal(streams.read("chat").completedMessages, undefined);
|
|
177
|
+
assert.equal(streams.complete("chat", input.messageId, { text: final }).completedMessages, undefined);
|
|
178
|
+
assert.equal(streams.update("chat", { ...input, delta: "late" }), null);
|
|
179
|
+
assert.deepEqual(streams.read("chat").messages, []);
|
|
180
|
+
assert.deepEqual(input.authorship, { messageId: "authored-message", turnId: "000001", origin: "user" });
|
|
181
|
+
streams.update("chat", { ...input, messageId: "phase-less-item", role: "assistant", text: "Initially unclassified" });
|
|
182
|
+
assert.equal(streams.complete("chat", "phase-less-item", { text: "Completed progress", role: "commentary" })
|
|
183
|
+
.completedMessages[0].role, "commentary", "The completed native classification owns the role, not an initial phase-less delta");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("unproven native origin and duplicate cleanup cannot produce completed authored carriers", () => {
|
|
187
|
+
const streams = createConversationStreams();
|
|
188
|
+
const input = { nativeIdentity: { threadId: "thread", turnId: "turn" }, turnId: "thread:turn",
|
|
189
|
+
messageId: "unproven", role: "commentary", origin: "application" };
|
|
190
|
+
streams.update("chat", { ...input, text: "Unproven sentence" });
|
|
191
|
+
assert.equal(streams.complete("chat", input.messageId, { text: "Unproven sentence" }).completedMessages, undefined);
|
|
192
|
+
streams.update("chat", { ...input, messageId: "duplicate", text: "Same sentence",
|
|
193
|
+
authorship: { turnId: "000001", messageId: "request", origin: "application" } });
|
|
194
|
+
assert.equal(streams.complete("chat", "duplicate").completedMessages, undefined,
|
|
195
|
+
"Fingerprint cleanup is not a completed-text publication");
|
|
196
|
+
assert.equal(streams.complete("chat", "duplicate", { text: "Same sentence" }).completedMessages, undefined);
|
|
197
|
+
assert.equal(streams.complete("other", "missing", { text: "No item" }).completedMessages, undefined);
|
|
198
|
+
streams.update("chat", { ...input, messageId: "initially-unproven", text: "Earlier unowned output" });
|
|
199
|
+
streams.update("chat", { ...input, messageId: "initially-unproven", text: "Later words",
|
|
200
|
+
authorship: { turnId: "000002", messageId: "successor-request", origin: "application" } });
|
|
201
|
+
assert.equal(streams.complete("chat", "initially-unproven", { text: "Late completed output" }).completedMessages, undefined,
|
|
202
|
+
"A later current request cannot adopt an item that had no captured authored custody");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
test("completed-envelope stream scalar changes participate in dedup and require captured application authorship", () => {
|
|
207
|
+
const streams = createConversationStreams();
|
|
208
|
+
const input = { turnId: "native:turn", nativeIdentity: { threadId: "native", turnId: "turn" },
|
|
209
|
+
messageId: "item", origin: "application", text: "Exact partial",
|
|
210
|
+
authorship: { messageId: "request", origin: "application" } };
|
|
211
|
+
const first = streams.update("chat", input);
|
|
212
|
+
assert.equal(first.messages[0].completedEnvelope, undefined);
|
|
213
|
+
const marked = streams.update("chat", { ...input, authorship: { ...input.authorship, completedEnvelope: true } });
|
|
214
|
+
assert.equal(marked.messages[0].completedEnvelope, true);
|
|
215
|
+
assert.ok(marked.revision > first.revision, "Equal text does not hide an actual trusted scalar change");
|
|
216
|
+
assert.equal(streams.update("chat", { ...input, authorship: { ...input.authorship, completedEnvelope: true } }), null);
|
|
217
|
+
streams.update("chat", { ...input, messageId: "unowned", authorship: undefined,
|
|
218
|
+
completedEnvelope: true, data: { completedEnvelope: true } });
|
|
219
|
+
assert.equal(streams.read("chat").messages.find(message => message.messageId === "unowned").completedEnvelope, undefined);
|
|
220
|
+
streams.update("chat", { ...input, messageId: "ordinary", origin: "user",
|
|
221
|
+
authorship: { messageId: "user-request", turnId: "000002", origin: "user", completedEnvelope: true } });
|
|
222
|
+
assert.equal(streams.read("chat").messages.find(message => message.messageId === "ordinary").completedEnvelope, undefined);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("completed-envelope completion keeps its captured scalar while resolving only the same authored pending receipt", () => {
|
|
226
|
+
const streams = createConversationStreams();
|
|
227
|
+
const input = { turnId: "native:turn", nativeIdentity: { threadId: "native", turnId: "turn" }, messageId: "item",
|
|
228
|
+
origin: "application", text: "Partial", authorship: { messageId: "request", origin: "application", completedEnvelope: true } };
|
|
229
|
+
streams.update("chat", input);
|
|
230
|
+
const completed = streams.complete("chat", "item", { text: "Exact final", role: "assistant",
|
|
231
|
+
authorship: { messageId: "request", turnId: "000001", origin: "application" } });
|
|
232
|
+
assert.equal(completed.completedMessages[0].completedEnvelope, true);
|
|
233
|
+
assert.equal(completed.completedMessages[0].turnId, "000001");
|
|
234
|
+
assert.equal(completed.completedMessages[0].text, "Exact final");
|
|
235
|
+
assert.equal(streams.complete("chat", "item", { text: "Repeat" }).completedMessages, undefined);
|
|
236
|
+
streams.update("chat", { ...input, messageId: "old-pending" });
|
|
237
|
+
assert.equal(streams.complete("chat", "old-pending", { text: "Wrong association", authorship: {
|
|
238
|
+
messageId: "successor", turnId: "000002", origin: "application", completedEnvelope: true
|
|
239
|
+
} }).completedMessages, undefined);
|
|
240
|
+
});
|