@jskit-ai/assistant-core 0.1.209 → 0.1.212
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/package.json +5 -5
- package/src/client/conversation/messageDelivery.js +7 -1
- package/src/server/conversation/codexHistoryAdapter.js +55 -2
- package/src/server/conversation/codexMessageCommands.js +2 -0
- package/src/server/conversation/codexMessageDelivery.js +7 -2
- package/src/server/conversation/codexNotificationObserver.js +15 -2
- package/src/server/conversation/codexProvider.js +64 -7
- package/src/server/conversation/codexTurn.js +16 -2
- package/src/server/conversation/codexTurnRecovery.js +104 -18
- package/src/server/conversation/continuity.js +3 -0
- package/src/server/conversation/openCodeRuntime.js +4 -0
- package/src/server/conversation/providers/codexDriver.js +106 -36
- package/src/server/conversation/providers/opencodeDriver.js +94 -37
- package/src/server/conversation/runtime.js +67 -7
- package/src/server/conversation/tools.js +5 -2
- package/src/server/conversation/transcript.js +2 -2
- package/src/server/hosts/openCodeProcess.js +1 -0
- package/test/claudeConversationSession.test.js +49 -0
- package/test/codexConversationSession.test.js +346 -0
- package/test/codexHistoryAdapter.test.js +103 -0
- package/test/codexTurn.test.js +542 -0
- package/test/conversationSession.test.js +19 -0
- package/test/conversationTools.test.js +298 -0
- package/test/messageDelivery.test.js +18 -0
- package/test/openCodeRuntime.test.js +29 -0
- package/test/opencodeConversationSession.test.js +354 -0
|
@@ -428,3 +428,301 @@ test("strict application calls settle safe receipts before rethrowing the exact
|
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
430
|
});
|
|
431
|
+
|
|
432
|
+
// Bound-host contract coverage: the original Claude driver carries its run
|
|
433
|
+
// command, while the host owns admission/output. Native tool dispatch is not
|
|
434
|
+
// supplied by this fixture and remains a separate integration prerequisite.
|
|
435
|
+
import { createConversationTranscript } from "../src/server/conversation/transcript.js";
|
|
436
|
+
|
|
437
|
+
async function boundToolFixture(t, options = {}) {
|
|
438
|
+
const memory = createMemoryConversationStorage();
|
|
439
|
+
const observed = { executions: [], contexts: [], mappings: [], commands: [], denied: false, failResults: false };
|
|
440
|
+
const storage = { read: memory.read, write: (scope, callback) => memory.write(observed.receiptScope || scope, async transaction => {
|
|
441
|
+
const result = await callback(transaction);
|
|
442
|
+
if (options.revokeAfterReservation) for (const id of await transaction.listTurnIds()) {
|
|
443
|
+
if ((await transaction.readTurn(id)).metadata.applicationTools?.some(call => call.id === "bound-call" && call.status === "running")) observed.denied = true;
|
|
444
|
+
}
|
|
445
|
+
if (observed.failResults) for (const id of await transaction.listTurnIds()) {
|
|
446
|
+
if ((await transaction.readTurn(id)).metadata.applicationTools?.some(call => call.id === "bound-call" && call.result)) throw new Error("Receipt disk unavailable");
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}) };
|
|
450
|
+
const transcript = createConversationTranscript({ storage: memory });
|
|
451
|
+
const catalog = createServiceToolCatalog(actionCatalog(async (input, actual) => {
|
|
452
|
+
observed.executions.push(input);
|
|
453
|
+
observed.contexts.push(actual);
|
|
454
|
+
return options.execute ? options.execute(input, actual) : { total: input.left + input.right };
|
|
455
|
+
}));
|
|
456
|
+
let delivery;
|
|
457
|
+
let active = false;
|
|
458
|
+
let command;
|
|
459
|
+
const finish = Promise.withResolvers();
|
|
460
|
+
async function admit(current) {
|
|
461
|
+
await current.beforeDispatch({ threadId: "native-thread" });
|
|
462
|
+
const conversationTurn = await transcript.writeConversationUserMessage("one", { text: current.input.text,
|
|
463
|
+
messageId: current.input.messageId, turnMetadata: { nativeSentinel: "host-owned" } });
|
|
464
|
+
await current.accept({ conversationTurn, nativeTurnId: "native-turn", nativeResult: { value: { ok: true, delivered: true } } });
|
|
465
|
+
observed.commands.push(current);
|
|
466
|
+
}
|
|
467
|
+
const owner = {
|
|
468
|
+
acquire: async () => ({ id: "native-thread", context: { workdir: "/bound" } }),
|
|
469
|
+
snapshot: () => ({ active }),
|
|
470
|
+
async run(_control, current) {
|
|
471
|
+
command = current;
|
|
472
|
+
active = true;
|
|
473
|
+
await admit(current);
|
|
474
|
+
const abort = () => finish.resolve();
|
|
475
|
+
current.signal.addEventListener("abort", abort, { once: true });
|
|
476
|
+
if (current.signal.aborted) abort();
|
|
477
|
+
try { await finish.promise; }
|
|
478
|
+
finally { current.signal.removeEventListener("abort", abort); active = false; }
|
|
479
|
+
},
|
|
480
|
+
async steer(_control, current) { await admit(current); return { value: { ok: true, delivered: true } }; },
|
|
481
|
+
closeSession: async () => ({ ok: true }),
|
|
482
|
+
cancel: async () => { finish.resolve(); return { ok: true }; }
|
|
483
|
+
};
|
|
484
|
+
const binding = { sessionId: "one", namespace: "bound-one", engine: "claude",
|
|
485
|
+
runtime: { store: {} }, native: { owner, preparation: { cleanup: () => ({ context, application: {} }) } },
|
|
486
|
+
admission() {}, prepareInput: async input => input,
|
|
487
|
+
read: async () => ({ threadId: "native-thread", configuration: { model: "native" }, turn: { active }, delivery }),
|
|
488
|
+
readStream: async () => ({ messages: [] }),
|
|
489
|
+
state: { read: async () => delivery, write: async value => { delivery = structuredClone(value); } },
|
|
490
|
+
transcript: {
|
|
491
|
+
history: async () => [], readConversationLog: () => transcript.readConversationLog("one"),
|
|
492
|
+
hasMessage: id => transcript.conversationMessageIdExists("one", id),
|
|
493
|
+
writeUserMessage: input => transcript.writeConversationUserMessage("one", input)
|
|
494
|
+
},
|
|
495
|
+
...(options.unconfigured ? {} : { applicationTools: { storage, prepareContext(actual, admission) {
|
|
496
|
+
observed.mappings.push({ actual, admission });
|
|
497
|
+
assert.equal(Object.isFrozen(admission), true);
|
|
498
|
+
if (observed.denied) throw new Error("Bound write authority revoked");
|
|
499
|
+
return { ...actual, boundAdmission: admission, fresh: observed.mappings.length };
|
|
500
|
+
} } })
|
|
501
|
+
};
|
|
502
|
+
const runtime = createConversationRuntime({ toolCatalog: catalog, authorize: () => true,
|
|
503
|
+
host: { conversation: () => binding } });
|
|
504
|
+
t.after(async () => { finish.resolve(); await runtime.close(); });
|
|
505
|
+
const conversation = await runtime.open({ id: "one", context });
|
|
506
|
+
async function currentCommand(index = 0) {
|
|
507
|
+
while (!observed.commands[index]) await new Promise(resolve => setImmediate(resolve));
|
|
508
|
+
return observed.commands[index];
|
|
509
|
+
}
|
|
510
|
+
const nativeCall = { id: "bound-call", name: "assistant_action_execute", arguments: JSON.stringify({ actionId: "numbers.add", input: { left: 2, right: 3 } }) };
|
|
511
|
+
return { runtime, conversation, storage, memory, observed, binding, finish, currentCommand, nativeCall,
|
|
512
|
+
tools: () => command.tools,
|
|
513
|
+
async loadContract() {
|
|
514
|
+
const result = await command.tools.execute({ id: "bound-contract", name: "assistant_action_contract",
|
|
515
|
+
arguments: JSON.stringify({ actionId: "numbers.add", version: 1 }) });
|
|
516
|
+
assert.equal(result.ok, true);
|
|
517
|
+
observed.mappings.length = 0;
|
|
518
|
+
} };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
test("bound tools use exact accepted host rows and fresh server context without writing native output or status", async t => {
|
|
522
|
+
const f = await boundToolFixture(t);
|
|
523
|
+
await f.conversation.send({ ...message, data: { turnId: "caller-forgery", nativeTurnId: "caller-forgery" } });
|
|
524
|
+
await f.currentCommand();
|
|
525
|
+
await f.loadContract();
|
|
526
|
+
const before = (await f.conversation.read()).conversationLog[0];
|
|
527
|
+
const result = await f.tools().execute(f.nativeCall);
|
|
528
|
+
assert.deepEqual(result, { ok: true, result: { actionId: "numbers.add", version: 1, result: { total: 5 } } });
|
|
529
|
+
assert.equal(f.observed.mappings.length, 2);
|
|
530
|
+
assert.deepEqual(f.observed.mappings[1].admission, { conversationId: "one", turnId: before.turnId,
|
|
531
|
+
messageId: message.messageId, nativeTurnId: "native-turn", nativeThreadId: "native-thread", origin: "user",
|
|
532
|
+
assertCurrent: f.observed.mappings[1].admission.assertCurrent });
|
|
533
|
+
assert.equal(f.observed.mappings[0].actual, context);
|
|
534
|
+
assert.deepEqual(f.observed.contexts[0].boundAdmission, f.observed.mappings[1].admission);
|
|
535
|
+
await f.tools().execute(f.nativeCall);
|
|
536
|
+
assert.equal(f.observed.mappings.length, 3, "Receipt replay still checks fresh bound authority");
|
|
537
|
+
assert.equal(f.observed.executions.length, 1);
|
|
538
|
+
f.observed.denied = true;
|
|
539
|
+
await assert.rejects(f.tools().execute(f.nativeCall), /write authority revoked/);
|
|
540
|
+
f.finish.resolve();
|
|
541
|
+
const saved = (await f.conversation.wait()).conversationLog[0];
|
|
542
|
+
const { applicationTools, ...metadata } = saved.metadata;
|
|
543
|
+
const { applicationTools: previousTools, ...previousMetadata } = before.metadata;
|
|
544
|
+
assert.deepEqual(metadata, previousMetadata);
|
|
545
|
+
assert.equal(saved.assistant, before.assistant);
|
|
546
|
+
assert.deepEqual(applicationTools[0], previousTools[0]);
|
|
547
|
+
assert.equal(applicationTools.length, 2);
|
|
548
|
+
assert.equal(applicationTools[1].status, "complete");
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("bound tool result-save retry uses its original accepted request after steering without repeating the effect", async t => {
|
|
552
|
+
const f = await boundToolFixture(t);
|
|
553
|
+
await f.conversation.send(message);
|
|
554
|
+
await f.currentCommand();
|
|
555
|
+
await f.loadContract();
|
|
556
|
+
// The existing reservation is durable; simulate only result commits failing.
|
|
557
|
+
f.observed.failResults = true;
|
|
558
|
+
const firstCall = f.tools().execute(f.nativeCall);
|
|
559
|
+
await assert.rejects(firstCall, /Receipt disk unavailable/);
|
|
560
|
+
assert.equal(f.observed.executions.length, 1);
|
|
561
|
+
await f.conversation.send({ messageId: "steered", text: "Keep the original operation.", steer: true });
|
|
562
|
+
await f.currentCommand(1);
|
|
563
|
+
f.finish.resolve();
|
|
564
|
+
const unavailable = await f.conversation.wait();
|
|
565
|
+
assert.equal(unavailable.status, "unavailable");
|
|
566
|
+
assert.equal(unavailable.conversationLog[0].metadata.applicationTools[1].status, "running");
|
|
567
|
+
assert.equal(unavailable.conversationLog[1].metadata.applicationTools, undefined);
|
|
568
|
+
await assert.rejects(f.conversation.send({ messageId: "blocked", text: "Another operation" }), { code: "conversation_storage_unavailable" });
|
|
569
|
+
f.observed.failResults = false;
|
|
570
|
+
assert.deepEqual(await f.conversation.retrySave(), { saved: true });
|
|
571
|
+
const saved = (await f.conversation.read()).conversationLog;
|
|
572
|
+
assert.equal(saved[0].metadata.applicationTools[1].status, "complete");
|
|
573
|
+
assert.equal(saved[1].metadata.applicationTools, undefined);
|
|
574
|
+
assert.equal(f.observed.executions.length, 1);
|
|
575
|
+
assert.equal(f.observed.mappings.every(({ admission }) => admission.messageId === message.messageId), true);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("bound receipt transaction refuses a mismatched authored message before effects", async t => {
|
|
579
|
+
const f = await boundToolFixture(t);
|
|
580
|
+
await f.conversation.send(message);
|
|
581
|
+
await f.currentCommand();
|
|
582
|
+
await f.loadContract();
|
|
583
|
+
const turn = (await f.conversation.read()).conversationLog[0];
|
|
584
|
+
await f.memory.write("other", async transaction => {
|
|
585
|
+
await transaction.appendMessage(turn.turnId, { role: "user", messageId: "other-author", text: "Other", at: new Date().toISOString() });
|
|
586
|
+
});
|
|
587
|
+
f.observed.receiptScope = "other";
|
|
588
|
+
await assert.rejects(f.tools().execute(f.nativeCall), /receipt does not belong/);
|
|
589
|
+
assert.equal(f.observed.executions.length, 0);
|
|
590
|
+
assert.equal((await f.conversation.read()).conversationLog[0].metadata.applicationTools.some(call => call.id === f.nativeCall.id), false);
|
|
591
|
+
f.finish.resolve();
|
|
592
|
+
assert.equal((await f.conversation.wait()).status, "unavailable");
|
|
593
|
+
f.observed.receiptScope = "";
|
|
594
|
+
await f.conversation.retrySave();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("an unconfigured bound host keeps its original tool-free common-runtime branch", async t => {
|
|
598
|
+
const f = await boundToolFixture(t, { unconfigured: true });
|
|
599
|
+
await f.conversation.send(message);
|
|
600
|
+
await f.currentCommand();
|
|
601
|
+
assert.equal(f.tools(), undefined);
|
|
602
|
+
f.finish.resolve();
|
|
603
|
+
const saved = (await f.conversation.wait()).conversationLog[0];
|
|
604
|
+
assert.deepEqual(saved.metadata, { nativeSentinel: "host-owned" });
|
|
605
|
+
assert.equal(f.observed.mappings.length, 0);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("fresh bound authority revoked after reservation saves a not-executed receipt", async t => {
|
|
609
|
+
const f = await boundToolFixture(t, { revokeAfterReservation: true });
|
|
610
|
+
await f.conversation.send(message);
|
|
611
|
+
await f.currentCommand();
|
|
612
|
+
await f.loadContract();
|
|
613
|
+
await assert.rejects(f.tools().execute(f.nativeCall), /write authority revoked/);
|
|
614
|
+
assert.equal(f.observed.mappings.length, 2);
|
|
615
|
+
assert.equal(f.observed.executions.length, 0);
|
|
616
|
+
f.finish.resolve();
|
|
617
|
+
const saved = (await f.conversation.wait()).conversationLog[0];
|
|
618
|
+
assert.equal(saved.metadata.applicationTools[1].status, "not-executed");
|
|
619
|
+
assert.equal(saved.metadata.applicationTools[1].result.error.code, "conversation_tool_not_executed");
|
|
620
|
+
assert.equal(saved.metadata.runtime, undefined);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("an invoked bound tool retains its originating actor and receipt across steering and Stop drains it", async t => {
|
|
624
|
+
const entered = Promise.withResolvers(), effect = Promise.withResolvers();
|
|
625
|
+
const f = await boundToolFixture(t, { execute: async (_input, actual) => {
|
|
626
|
+
entered.resolve(actual);
|
|
627
|
+
return effect.promise;
|
|
628
|
+
} });
|
|
629
|
+
await f.conversation.send(message);
|
|
630
|
+
await f.currentCommand();
|
|
631
|
+
await f.loadContract();
|
|
632
|
+
const executing = f.tools().execute(f.nativeCall);
|
|
633
|
+
const actual = await entered.promise;
|
|
634
|
+
const originalTurnId = actual.boundAdmission.turnId;
|
|
635
|
+
const nextActor = { ...context, actor: { id: "other-authorized-actor" } };
|
|
636
|
+
const otherHandle = await f.runtime.open({ id: "one", context: nextActor });
|
|
637
|
+
await otherHandle.send({ messageId: "another-steer", text: "Keep going.", steer: true });
|
|
638
|
+
await f.currentCommand(1);
|
|
639
|
+
let stopped = false;
|
|
640
|
+
const stopping = otherHandle.cancel().then(() => { stopped = true; });
|
|
641
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
642
|
+
assert.equal(stopped, false, "Original Stop must join an already invoked action");
|
|
643
|
+
effect.resolve({ total: 5 });
|
|
644
|
+
await executing;
|
|
645
|
+
await stopping;
|
|
646
|
+
const saved = (await f.conversation.wait()).conversationLog;
|
|
647
|
+
assert.equal(actual.actor.id, context.actor.id);
|
|
648
|
+
assert.equal(actual.boundAdmission.messageId, message.messageId);
|
|
649
|
+
assert.equal(saved[0].turnId, originalTurnId);
|
|
650
|
+
assert.equal(saved[0].metadata.applicationTools[1].status, "complete");
|
|
651
|
+
assert.equal(saved[1].metadata.applicationTools, undefined);
|
|
652
|
+
assert.equal(f.observed.mappings.every(({ actual }) => actual === context), true);
|
|
653
|
+
assert.equal(f.observed.executions.length, 1);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
test("bound native identity comes from the accepted owner rather than authored data", async t => {
|
|
658
|
+
const f = await boundToolFixture(t);
|
|
659
|
+
await f.conversation.send({ ...message, data: { nativeThreadId: "forged-thread", nativeTurnId: "forged-turn" } });
|
|
660
|
+
await f.currentCommand();
|
|
661
|
+
await f.loadContract();
|
|
662
|
+
await f.tools().execute(f.nativeCall);
|
|
663
|
+
await f.tools().execute(f.nativeCall);
|
|
664
|
+
assert.equal(f.observed.executions.length, 1, "Replaying the receipt must not repeat its effect");
|
|
665
|
+
assert.equal(f.observed.mappings.length, 3, "Receipt replay still refreshes its admitted identity");
|
|
666
|
+
for (const { admission } of f.observed.mappings) {
|
|
667
|
+
assert.equal(Object.isFrozen(admission), true);
|
|
668
|
+
assert.equal(admission.nativeThreadId, "native-thread");
|
|
669
|
+
assert.equal(admission.nativeTurnId, "native-turn");
|
|
670
|
+
assert.equal(admission.messageId, message.messageId);
|
|
671
|
+
}
|
|
672
|
+
const first = f.observed.mappings[0].admission;
|
|
673
|
+
await f.conversation.send({ messageId: "thread-steer", text: "Keep going.", steer: true, data: { nativeThreadId: "another-forgery" } });
|
|
674
|
+
await f.currentCommand(1);
|
|
675
|
+
await f.tools().execute({ id: "steer-contract", name: "assistant_action_contract",
|
|
676
|
+
arguments: JSON.stringify({ actionId: "numbers.add", version: 1 }) });
|
|
677
|
+
const latest = f.observed.mappings.at(-1).admission;
|
|
678
|
+
assert.equal(latest.nativeThreadId, "native-thread");
|
|
679
|
+
assert.equal(latest.messageId, "thread-steer");
|
|
680
|
+
assert.equal(first.messageId, message.messageId, "Later steering must not retarget an earlier tool receipt");
|
|
681
|
+
assert.equal(first.nativeThreadId, "native-thread");
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
test("bound admission current guard fences a held host effect after steering or completion", async t => {
|
|
686
|
+
const f = await boundToolFixture(t);
|
|
687
|
+
await f.conversation.send(message);
|
|
688
|
+
await f.currentCommand();
|
|
689
|
+
await f.loadContract();
|
|
690
|
+
await f.tools().execute(f.nativeCall);
|
|
691
|
+
const original = f.observed.mappings[0].admission;
|
|
692
|
+
assert.equal(typeof original.assertCurrent, "function");
|
|
693
|
+
original.assertCurrent();
|
|
694
|
+
assert.equal(JSON.parse(JSON.stringify(original)).assertCurrent, undefined, "The guard is server-owned, not serialized authority");
|
|
695
|
+
const boundary = Promise.withResolvers();
|
|
696
|
+
const held = Promise.resolve().then(async () => { await boundary.promise; original.assertCurrent(); });
|
|
697
|
+
held.catch(() => {});
|
|
698
|
+
await f.conversation.send({ messageId: "guard-steer", text: "A new instruction.", steer: true });
|
|
699
|
+
await f.currentCommand(1);
|
|
700
|
+
boundary.resolve();
|
|
701
|
+
await assert.rejects(held, { code: "conversation_tool_request_retired" });
|
|
702
|
+
await f.tools().execute({ id: "guard-contract", name: "assistant_action_contract",
|
|
703
|
+
arguments: JSON.stringify({ actionId: "numbers.add", version: 1 }) });
|
|
704
|
+
const current = f.observed.mappings.at(-1).admission;
|
|
705
|
+
current.assertCurrent();
|
|
706
|
+
assert.equal(current.messageId, "guard-steer");
|
|
707
|
+
f.finish.resolve();
|
|
708
|
+
await f.conversation.wait();
|
|
709
|
+
assert.throws(() => current.assertCurrent(), { code: "conversation_tool_request_retired" });
|
|
710
|
+
assert.equal(f.observed.executions.length, 1, "Inspecting current authority must not dispatch or repeat work");
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
test("bound admission guard preserves the original Stop cancellation reason", async t => {
|
|
715
|
+
const f = await boundToolFixture(t);
|
|
716
|
+
await f.conversation.send(message);
|
|
717
|
+
const command = await f.currentCommand();
|
|
718
|
+
await f.loadContract();
|
|
719
|
+
await f.tools().execute({ id: "stop-contract", name: "assistant_action_contract",
|
|
720
|
+
arguments: JSON.stringify({ actionId: "numbers.add", version: 1 }) });
|
|
721
|
+
const admission = f.observed.mappings.at(-1).admission;
|
|
722
|
+
admission.assertCurrent();
|
|
723
|
+
await f.conversation.cancel();
|
|
724
|
+
assert.equal(command.signal.aborted, true);
|
|
725
|
+
assert.throws(() => admission.assertCurrent(), error => error === command.signal.reason);
|
|
726
|
+
assert.equal(command.signal.reason.message, "Work stopped.");
|
|
727
|
+
assert.equal(f.observed.executions.length, 0, "Stop and its authority guard must not execute an application effect");
|
|
728
|
+
});
|
|
@@ -4,6 +4,24 @@ import { nextTick, ref } from "vue";
|
|
|
4
4
|
import { createAssistantMessageDelivery, unmatchedOptimisticMessages } from "../src/client/conversation/messageDelivery.js";
|
|
5
5
|
import { normalizeConversationTurn } from "../src/shared/conversation/patches.js";
|
|
6
6
|
|
|
7
|
+
test("proven no-admission releases only the exact uncertain message without accepting or losing its payload", async () => {
|
|
8
|
+
let accepted = 0;
|
|
9
|
+
const delivery = createAssistantMessageDelivery({ deliver: async () => { throw new Error("Connection lost"); } });
|
|
10
|
+
const payload = { message: "Keep these words", displayAttachments: [{ attachmentId: "keep-file" }] };
|
|
11
|
+
await assert.rejects(delivery.send(payload, { messageId: "not-sent", uncertainOnError: true, onAccepted: () => accepted++ }));
|
|
12
|
+
delivery.restoreUncertain({ messageId: "still-unknown", text: "An attempted message" });
|
|
13
|
+
assert.equal(delivery.rejectUnsent("different-id", "Not sent"), false);
|
|
14
|
+
assert.equal(delivery.rejectUnsent("not-sent", "Not sent"), true);
|
|
15
|
+
assert.equal(delivery.find("not-sent").status, "failed");
|
|
16
|
+
assert.deepEqual(delivery.find("not-sent").payload, payload);
|
|
17
|
+
assert.equal(delivery.find("still-unknown").status, "uncertain");
|
|
18
|
+
assert.equal(accepted, 0);
|
|
19
|
+
delivery.accept("not-sent");
|
|
20
|
+
assert.equal(delivery.rejectUnsent("not-sent", "Not sent"), false);
|
|
21
|
+
assert.equal(delivery.find("not-sent").status, "accepted");
|
|
22
|
+
assert.equal(accepted, 1);
|
|
23
|
+
});
|
|
24
|
+
|
|
7
25
|
test("sending immediately renders the message and reconciles only its authoritative receipt", async () => {
|
|
8
26
|
const request = Promise.withResolvers();
|
|
9
27
|
const delivery = createAssistantMessageDelivery({ deliver: () => request.promise });
|
|
@@ -320,3 +320,32 @@ test("OpenCode bindings share atomic publication without overwriting another con
|
|
|
320
320
|
assert.throws(() => runtime.writeBindings(path.join(root, "another.json"), "foreign", []), /one absolute private binding registry/);
|
|
321
321
|
assert.deepEqual(await readOpenCodeEnvironments(""), []);
|
|
322
322
|
});
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
test("native output identity is opt-in for every existing OpenCode projection writer", async () => {
|
|
326
|
+
const runtime = createOpenCodeSharedRuntime();
|
|
327
|
+
for (const configured of [false, true]) {
|
|
328
|
+
const writes = [];
|
|
329
|
+
const streams = [];
|
|
330
|
+
const native = { id: "native-final", type: "assistant", text: "Exact native final words.", time: { created: 1_000 } };
|
|
331
|
+
const unchanged = structuredClone(native);
|
|
332
|
+
const projection = {
|
|
333
|
+
messageId: (id, role) => `${id}:${role}`,
|
|
334
|
+
...(configured ? { outputId: (id, role) => `${id}:${role}` } : {}),
|
|
335
|
+
readError: () => "", reasoning: async () => {},
|
|
336
|
+
store: {
|
|
337
|
+
async writeConversationAssistantMessage(id, message) { writes.push({ id, message }); return { id, message }; },
|
|
338
|
+
updateConversationStream(id, message) { streams.push({ id, message }); return { id, message }; },
|
|
339
|
+
completeConversationStreamMessage() {}
|
|
340
|
+
},
|
|
341
|
+
publishTurn: async () => {}, publishStream: async () => {}
|
|
342
|
+
};
|
|
343
|
+
await runtime.writeConversationProjection("conversation", [native], { inputMessageId: "", streaming: false }, projection);
|
|
344
|
+
await runtime.writeConversationProjection("conversation", [native], { inputMessageId: "", streaming: true }, projection);
|
|
345
|
+
await runtime.writeConversationProjection("conversation", [native, { id: "next", type: "assistant", text: "", time: { created: 2_000 } }], { streaming: true }, projection);
|
|
346
|
+
const message = { messageId: "native-final:assistant", ...(configured ? { outputId: "native-final:assistant" } : {}), text: native.text };
|
|
347
|
+
assert.deepEqual(writes, [{ id: "conversation", message }, { id: "conversation", message }]);
|
|
348
|
+
assert.deepEqual(streams, [{ id: "conversation", message: { turnId: "", ...message } }]);
|
|
349
|
+
assert.deepEqual(native, unchanged, "The original native history is never rewritten.");
|
|
350
|
+
}
|
|
351
|
+
});
|