@jskit-ai/assistant-runtime 0.1.232 → 0.1.233
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.233",
|
|
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.239",
|
|
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.243",
|
|
20
|
+
"@jskit-ai/http-web": "0.1.91",
|
|
21
|
+
"@jskit-ai/kernel": "0.1.245",
|
|
22
|
+
"@jskit-ai/realtime": "0.1.243",
|
|
23
|
+
"@jskit-ai/shell-web": "0.1.250",
|
|
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.245"
|
|
28
28
|
},
|
|
29
29
|
"description": "Capability-based assistant runtime with per-surface chat, transcripts, settings, and action tools.",
|
|
30
30
|
"jskit": {
|
|
@@ -419,20 +419,23 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
419
419
|
}
|
|
420
420
|
return result;
|
|
421
421
|
}
|
|
422
|
-
|
|
422
|
+
// A deferred message waits only while the working turn cannot take it. It
|
|
423
|
+
// steers as soon as that turn accepts steering, or sends once it is ready.
|
|
424
|
+
function waitForAdmission({ controller }) {
|
|
423
425
|
return new Promise(resolve => {
|
|
424
426
|
let stop;
|
|
425
|
-
const finish =
|
|
427
|
+
const finish = admission => {
|
|
426
428
|
stop?.();
|
|
427
429
|
controller.signal.removeEventListener("abort", check);
|
|
428
|
-
resolve(
|
|
430
|
+
resolve(admission);
|
|
429
431
|
};
|
|
430
432
|
const check = () => {
|
|
431
433
|
if (controller.signal.aborted || !available.value) finish(false);
|
|
432
|
-
else if (snapshot.value.status === "ready") finish(
|
|
434
|
+
else if (snapshot.value.status === "ready") finish("send");
|
|
435
|
+
else if (steerable.value) finish("steer");
|
|
433
436
|
else if (snapshot.value.status !== "working") finish(false);
|
|
434
437
|
};
|
|
435
|
-
stop = watch([available, snapshot], check, { flush: "sync" });
|
|
438
|
+
stop = watch([available, snapshot, steerable], check, { flush: "sync" });
|
|
436
439
|
controller.signal.addEventListener("abort", check, { once: true });
|
|
437
440
|
check();
|
|
438
441
|
});
|
|
@@ -451,19 +454,31 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
451
454
|
receiptTurns: turns, uncertainOnError: true,
|
|
452
455
|
async deliver(submission) {
|
|
453
456
|
pending.started = true;
|
|
457
|
+
let admission = null;
|
|
454
458
|
try {
|
|
455
459
|
if (controller.signal.aborted || appAdmission && (!available.value || stopping.value || !workingPolicy.value.admitWhileWorking)) return false;
|
|
456
460
|
if (deferred) {
|
|
457
461
|
// Refresh through the same subscription after a queued predecessor;
|
|
458
462
|
// its receipt may have arrived before the working snapshot.
|
|
459
463
|
await subscription?.reload();
|
|
460
|
-
if (error.value
|
|
461
|
-
if (
|
|
464
|
+
if (error.value) return false;
|
|
465
|
+
if (snapshot.value.status === "working" && !steerable.value) {
|
|
466
|
+
delivery.wait(messageId, "Waiting for the assistant to finish this step");
|
|
467
|
+
}
|
|
468
|
+
admission = await waitForAdmission(pending);
|
|
469
|
+
delivery.wait(messageId, "");
|
|
470
|
+
if (!admission || controller.signal.aborted || !available.value ||
|
|
471
|
+
snapshot.value.status !== (admission === "steer" ? "working" : "ready")) return false;
|
|
462
472
|
if (submission.displayAttachments?.length && snapshot.value.capabilities?.attachments !== true) {
|
|
463
473
|
return { ok: false, error: "This conversation no longer accepts attachments." };
|
|
464
474
|
}
|
|
465
475
|
}
|
|
466
476
|
let request = prepare ? await prepare(submission, { signal: controller.signal }) : submission.request;
|
|
477
|
+
if (admission === "steer") {
|
|
478
|
+
request = { ...request, steer: true };
|
|
479
|
+
const retained = delivery.find(messageId);
|
|
480
|
+
if (retained) retained.payload.request = request;
|
|
481
|
+
}
|
|
467
482
|
if (prepare) {
|
|
468
483
|
if (!current.value || accessDenied.value || controller.signal.aborted || request === false) return false;
|
|
469
484
|
if (!request || typeof request !== "object" || Array.isArray(request)) {
|
|
@@ -952,6 +952,32 @@ test("opted-in nonsteerable follow-ups use the original serial queue and dispatc
|
|
|
952
952
|
assert.equal((await f.sent()).at(-1).input.steer, true);
|
|
953
953
|
});
|
|
954
954
|
|
|
955
|
+
test("a deferred follow-up explains its wait and steers once the working turn accepts steering", options, async t => {
|
|
956
|
+
const f = await fixture(t);
|
|
957
|
+
await f.page.evaluate(() => {
|
|
958
|
+
window.deferredBinding = window.conversationFixture.acquireConversation({
|
|
959
|
+
conversationId: "chat:2", deferWhileWorking: true, queueWhileSending: true
|
|
960
|
+
});
|
|
961
|
+
window.conversationFixture.target("chat:2");
|
|
962
|
+
});
|
|
963
|
+
await expect.poll(() => f.page.evaluate(() => window.conversationFixture.current()?.identity.conversationId)).toBe("chat:2");
|
|
964
|
+
await expect(f.input).toBeEnabled();
|
|
965
|
+
await f.input.fill("Original nonsteerable request");
|
|
966
|
+
await f.input.press("Enter");
|
|
967
|
+
await expect.poll(() => f.page.evaluate(() => window.conversationFixture.current().snapshot.value.status)).toBe("working");
|
|
968
|
+
await f.input.fill("Follow-up while the turn starts");
|
|
969
|
+
await f.input.press("Enter");
|
|
970
|
+
await expect(f.primary.getByText("Pending: Waiting for the assistant to finish this step")).toHaveCount(1);
|
|
971
|
+
assert.equal((await f.sent()).length, 1, "the follow-up waits while the turn cannot take steering");
|
|
972
|
+
await f.command("steering", { id: "chat:2", enabled: true });
|
|
973
|
+
await f.notify("chat:2");
|
|
974
|
+
await expect.poll(async () => (await f.sent()).length).toBe(2);
|
|
975
|
+
const [, followUp] = await f.sent();
|
|
976
|
+
assert.equal(followUp.input.text, "Follow-up while the turn starts");
|
|
977
|
+
assert.equal(followUp.input.steer, true, "the waiting follow-up reaches the running turn without waiting for it to finish");
|
|
978
|
+
await expect(f.primary.getByText(/^Pending/)).toHaveCount(0);
|
|
979
|
+
});
|
|
980
|
+
|
|
955
981
|
for (const steer of [false, true]) {
|
|
956
982
|
test(`Stop cancels locally queued native ${steer ? "steering" : "startup"} follow-ups`, options, async t => {
|
|
957
983
|
const f = await fixture(t);
|