@jskit-ai/assistant-runtime 0.1.212 → 0.1.213
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.213",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
"./server": "./src/server/index.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
15
|
+
"@jskit-ai/assistant-core": "0.1.220",
|
|
16
16
|
"json-rest-schema": "^1.0.17"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
20
|
-
"@jskit-ai/http-web": "0.1.
|
|
21
|
-
"@jskit-ai/kernel": "0.1.
|
|
22
|
-
"@jskit-ai/realtime": "0.1.
|
|
23
|
-
"@jskit-ai/shell-web": "0.1.
|
|
19
|
+
"@jskit-ai/http-runtime": "0.1.240",
|
|
20
|
+
"@jskit-ai/http-web": "0.1.87",
|
|
21
|
+
"@jskit-ai/kernel": "0.1.242",
|
|
22
|
+
"@jskit-ai/realtime": "0.1.239",
|
|
23
|
+
"@jskit-ai/shell-web": "0.1.246",
|
|
24
24
|
"@tanstack/vue-query": "^5.90.5",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
26
|
"vuetify": "^4.0.0",
|
|
27
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
27
|
+
"@jskit-ai/database-runtime": "0.1.242"
|
|
28
28
|
},
|
|
29
29
|
"description": "Capability-based assistant runtime with per-surface chat, transcripts, settings, and action tools.",
|
|
30
30
|
"jskit": {
|
|
@@ -148,6 +148,10 @@ function subscribeAssistantConversation({
|
|
|
148
148
|
changedPresentation = true;
|
|
149
149
|
}
|
|
150
150
|
} else if (event.type === "message" || event.type === "message-complete") {
|
|
151
|
+
// A saved turn can be outside the loaded page. Its transcript patch owns
|
|
152
|
+
// presentation; the following completion must not recreate it at the tail.
|
|
153
|
+
const persisted = event.type === "message" && event.status === "complete" && event.persisted === true;
|
|
154
|
+
if (persisted) changedPresentation = completed.delete(event.messageId) || changedPresentation;
|
|
151
155
|
// A later initial read can already contain the result of buffered events.
|
|
152
156
|
const older = streaming && event.streaming?.revision < streaming.revision;
|
|
153
157
|
const completedWhileRunning = event.type === "message" && event.status === "complete" &&
|
|
@@ -155,13 +159,13 @@ function subscribeAssistantConversation({
|
|
|
155
159
|
if (!older || completedWhileRunning) {
|
|
156
160
|
const previous = streaming?.messages?.find(message => message.messageId === event.messageId);
|
|
157
161
|
const changed = receiveStreaming(event.streaming);
|
|
158
|
-
if (event.type === "message" && event.status === "complete" && event.text) {
|
|
162
|
+
if (event.type === "message" && event.status === "complete" && event.text && !persisted) {
|
|
159
163
|
completed.set(event.messageId, { turnId: event.turnId, message: {
|
|
160
164
|
messageId: event.messageId, role: event.role, text: event.text, status: "complete",
|
|
161
165
|
...(event.outputId ? { outputId: event.outputId } : {}),
|
|
162
166
|
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
163
167
|
...(event.origin ? { origin: event.origin } : {}),
|
|
164
|
-
...(previous?.at ? { at: previous.at } : {})
|
|
168
|
+
...((event.at || previous?.at) ? { at: event.at || previous.at } : {})
|
|
165
169
|
} });
|
|
166
170
|
}
|
|
167
171
|
changedPresentation ||= changed || event.status === "complete";
|
|
@@ -2435,6 +2435,39 @@ test("canonical rewind replaces the loaded saved window without retaining remove
|
|
|
2435
2435
|
});
|
|
2436
2436
|
|
|
2437
2437
|
|
|
2438
|
+
test("saved history replay stays off the live tail and loads once with its timestamp", options, async t => {
|
|
2439
|
+
const f = await fixture(t, 390);
|
|
2440
|
+
const progress = { role: "commentary", messageId: "replayed-progress", text: "Earlier saved progress",
|
|
2441
|
+
at: "2026-10-13T15:50:59.328Z" };
|
|
2442
|
+
const answer = { role: "assistant", messageId: "replayed-answer", text: "Earlier saved answer",
|
|
2443
|
+
at: "2026-10-13T15:52:58.929Z" };
|
|
2444
|
+
const turns = Array.from({ length: 37 }, (_, index) => {
|
|
2445
|
+
const user = { role: "user", messageId: `replay-question-${index}`, text: `Replay question ${index + 1}` };
|
|
2446
|
+
return { turnId: String(2485 + index).padStart(6, "0"), user,
|
|
2447
|
+
...(index === 0 ? { commentary: [progress], assistant: answer, messages: [user, progress, answer] } : {}) };
|
|
2448
|
+
});
|
|
2449
|
+
await f.command("history", { id: "chat:1", turns, limit: 20 });
|
|
2450
|
+
await f.notify("chat:1");
|
|
2451
|
+
await expect.poll(() => f.page.evaluate(() => window.conversationFixture.current().turns.value.length)).toBe(20);
|
|
2452
|
+
await f.page.evaluate(turn => {
|
|
2453
|
+
const socket = window.conversationFixture.socket;
|
|
2454
|
+
socket.notify("chat:1", { type: "transcript", patch: { type: "upsert-turn", turn } });
|
|
2455
|
+
for (const message of turn.messages.filter(message => message.role !== "user")) {
|
|
2456
|
+
socket.notify("chat:1", { type: "message", ...message, turnId: turn.turnId, status: "complete",
|
|
2457
|
+
persisted: true, streaming: { revision: 1, messages: [] } });
|
|
2458
|
+
}
|
|
2459
|
+
}, turns[0]);
|
|
2460
|
+
await expect(f.primary.getByText(answer.text, { exact: true })).toHaveCount(0);
|
|
2461
|
+
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().turns.value.length), 20);
|
|
2462
|
+
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().loadMore()), true);
|
|
2463
|
+
await expect(f.primary.getByText(answer.text, { exact: true })).toHaveCount(1);
|
|
2464
|
+
const saved = await f.page.evaluate(() => window.conversationFixture.current().turns.value.find(turn => turn.turnId === "002485"));
|
|
2465
|
+
assert.equal(saved.user.text, "Replay question 1");
|
|
2466
|
+
assert.equal(saved.assistant.at, answer.at);
|
|
2467
|
+
assert.equal(saved.commentary[0].at, progress.at);
|
|
2468
|
+
assert.deepEqual(await f.sent(), [], "Reading replayed history cannot send another request");
|
|
2469
|
+
});
|
|
2470
|
+
|
|
2438
2471
|
test("canonical loaded-history replay retains older completed output and its authored row", options, async t => {
|
|
2439
2472
|
const f = await fixture(t, 390);
|
|
2440
2473
|
const turns = Array.from({ length: 25 }, (_, index) => ({
|
|
@@ -570,6 +570,52 @@ test("stream snapshots preserve completed commentary and coalesce lifecycle read
|
|
|
570
570
|
assert.deepEqual(errors, []);
|
|
571
571
|
});
|
|
572
572
|
|
|
573
|
+
test("replayed saved completions outside the history page never become live tail rows", async t => {
|
|
574
|
+
const socket = new EventEmitter();
|
|
575
|
+
socket.connected = true;
|
|
576
|
+
const states = [], events = [];
|
|
577
|
+
const latest = Array.from({ length: 20 }, (_, index) => {
|
|
578
|
+
const turnId = String(2502 + index).padStart(6, "0");
|
|
579
|
+
const user = { messageId: `question-${turnId}`, role: "user", text: `Question ${turnId}` };
|
|
580
|
+
return { turnId, user, messages: [user] };
|
|
581
|
+
});
|
|
582
|
+
const initial = { id: "chat", status: "working", conversationLog: latest,
|
|
583
|
+
pagination: { limit: 20, hasMoreBefore: true }, streaming: { revision: 0, messages: [] } };
|
|
584
|
+
let subscriptionId, revision = 0;
|
|
585
|
+
socket.timeout = () => ({ emit(_name, input, acknowledge) {
|
|
586
|
+
subscriptionId = input.subscriptionId;
|
|
587
|
+
acknowledge(null, { ok: true, streamEpoch: "epoch", state: initial });
|
|
588
|
+
} });
|
|
589
|
+
const dispose = subscribeAssistantConversation({ socket, conversationId: "chat", targetSurfaceId: "home", hostSurfaceId: "home",
|
|
590
|
+
read: async () => initial, onState: state => states.push(state), onEvent: event => events.push(event) });
|
|
591
|
+
t.after(dispose);
|
|
592
|
+
function deliver(event) {
|
|
593
|
+
socket.emit(ASSISTANT_CONVERSATION_EVENT, { subscriptionId, conversationId: "chat", streamEpoch: "epoch",
|
|
594
|
+
streamRevision: ++revision, event });
|
|
595
|
+
}
|
|
596
|
+
const messages = [
|
|
597
|
+
{ messageId: "old-progress", role: "commentary", text: "Earlier progress", at: "2026-10-13T15:50:59.328Z" },
|
|
598
|
+
{ messageId: "old-answer", role: "assistant", text: "Earlier answer", at: "2026-10-13T15:52:58.929Z" }
|
|
599
|
+
];
|
|
600
|
+
// Native history reconciliation first publishes the saved turn, then its
|
|
601
|
+
// completion notifications. Paging deliberately excludes this older turn.
|
|
602
|
+
const turn = { turnId: "002485", commentary: [messages[0]], assistant: messages[1], messages };
|
|
603
|
+
deliver({ type: "transcript", patch: { type: "upsert-turn", turn } });
|
|
604
|
+
for (const message of messages) deliver({ type: "message", ...message, turnId: turn.turnId,
|
|
605
|
+
status: "complete", persisted: true, streaming: initial.streaming });
|
|
606
|
+
assert.deepEqual(states.at(-1).turns, latest, "Saved replay cannot append a synthetic old turn after the current request");
|
|
607
|
+
await dispose.reload();
|
|
608
|
+
assert.deepEqual(states.at(-1).turns, latest, "Ordinary canonical refresh does not revive completed replay entries");
|
|
609
|
+
assert.equal(events.filter(event => event.type === "message").length, 2, "Notifications still reach non-transcript consumers");
|
|
610
|
+
|
|
611
|
+
const live = { messageId: "live-progress", turnId: "002521", role: "commentary", text: "New progress",
|
|
612
|
+
at: "2026-10-13T16:13:22.233Z", status: "complete" };
|
|
613
|
+
deliver({ type: "message", ...live, streaming: { revision: 1, messages: [] } });
|
|
614
|
+
assert.equal(states.at(-1).turns.at(-1).commentary[0].text, live.text);
|
|
615
|
+
assert.equal(states.at(-1).turns.at(-1).commentary[0].at, live.at,
|
|
616
|
+
"A completed message without an earlier partial retains its supplied timestamp");
|
|
617
|
+
});
|
|
618
|
+
|
|
573
619
|
test("original transcript patches survive a read already in flight and the next canonical read remains authoritative", async t => {
|
|
574
620
|
const changes = new EventEmitter();
|
|
575
621
|
const socket = new EventEmitter();
|