@jskit-ai/assistant-core 0.1.239 → 0.1.240
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 +2 -2
- package/package.json +1 -1
- package/src/server/conversation/claudeTurn.js +10 -2
- package/src/server/conversation/codexGoalCommands.js +1 -1
- package/src/server/conversation/providers/claudeDriver.js +2 -0
- package/src/server/conversation/providers/codexDriver.js +2 -2
- package/test/assistantScroll.browser.test.js +29 -6
- package/test/claudeConversationSession.test.js +9 -2
- package/test/codexConversationSession.test.js +3 -1
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"build": "vite build"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.1.240",
|
|
12
12
|
"@jskit-ai/http-runtime": "0.1.243",
|
|
13
13
|
"@jskit-ai/kernel": "0.1.245",
|
|
14
14
|
"@mdi/js": "^7.4.47",
|
|
15
15
|
"vue": "^3.5.13",
|
|
16
16
|
"vuetify": "^4.0.0",
|
|
17
17
|
"@jskit-ai/connectors-core": "0.1.62",
|
|
18
|
-
"@jskit-ai/connectors-catalog": "0.1.
|
|
18
|
+
"@jskit-ai/connectors-catalog": "0.1.64"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@vitejs/plugin-vue": "^6.0.7",
|
package/package.json
CHANGED
|
@@ -893,6 +893,14 @@ export function createClaudeConversationOwner({
|
|
|
893
893
|
return { status: "available", threadId: entry.id, goal: await entry.goals.read(history), goalCommandId: history.goalCommandId || null };
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
+
// A native Set, Resume or Cancel supersedes a retained Pause or blocker.
|
|
897
|
+
async function releaseRetainedGoal(entry) {
|
|
898
|
+
if (!entry.pausedGoal && !entry.blockedGoal) return;
|
|
899
|
+
entry.pausedGoal = null;
|
|
900
|
+
entry.blockedGoal = null;
|
|
901
|
+
await store.save(entry);
|
|
902
|
+
}
|
|
903
|
+
|
|
896
904
|
async function updateGoal(entry, input, operations) {
|
|
897
905
|
await bindAccount(entry);
|
|
898
906
|
const call = applicationCall.getStore();
|
|
@@ -928,7 +936,7 @@ export function createClaudeConversationOwner({
|
|
|
928
936
|
for (;;) {
|
|
929
937
|
const history = await readHistory(entry);
|
|
930
938
|
if (history.goalCommandId === claudeNativeMessageId(messageId)) {
|
|
931
|
-
|
|
939
|
+
await releaseRetainedGoal(entry);
|
|
932
940
|
result = { status: "available", threadId: entry.id, goal: await entry.goals.read(history), goalCommandId: history.goalCommandId };
|
|
933
941
|
break;
|
|
934
942
|
}
|
|
@@ -1124,7 +1132,7 @@ export function createClaudeConversationOwner({
|
|
|
1124
1132
|
if (!binding) return { value: delivered.value };
|
|
1125
1133
|
}
|
|
1126
1134
|
|
|
1127
|
-
return Object.freeze({ applicationToolsSupported: true, entries, closingSessions, get closing() { return closing; }, createAccountQueries, open, acquire, createConversation, snapshot, readRetainedTurn, readFinalAssistantResult, readHistory, ensureReady, ensureProcess, readContextUsage, compactContext, stopForTerminal, prepareTerminal, send, startTurn,
|
|
1135
|
+
return Object.freeze({ applicationToolsSupported: true, entries, closingSessions, get closing() { return closing; }, createAccountQueries, open, acquire, createConversation, snapshot, readRetainedTurn, readFinalAssistantResult, readHistory, ensureReady, ensureProcess, readContextUsage, compactContext, releaseRetainedGoal, stopForTerminal, prepareTerminal, send, startTurn,
|
|
1128
1136
|
interrupt, read, inspectAdmission, hasActiveTemporaryConversation, hasActiveConversation, retireConversationHistory, reconcileSessions, restoreSessionEntries, closeSession, closeProject, invalidateRuntimes, drain, forget, close, deleteConversation, recover, stopSelected, wait, stopConversation,
|
|
1129
1137
|
readGoal, updateGoal, renewal, run, steer, cancel });
|
|
1130
1138
|
}
|
|
@@ -117,7 +117,7 @@ export function createCodexGoalCommands({
|
|
|
117
117
|
params: { goal: result.goal }
|
|
118
118
|
});
|
|
119
119
|
await publish(sessionId, { reason: "codex-goal", nativeGoal: { threadId, goal: result.goal || null } });
|
|
120
|
-
return { ok: true, status: "available", goal: result.goal || null };
|
|
120
|
+
return { ok: true, status: "available", threadId, goal: result.goal || null };
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
|
|
@@ -170,6 +170,8 @@ export function createClaudeConversationDriver({ connections, host = {}, limits
|
|
|
170
170
|
if (event.type === "message") await current.onMessage({ ...event.message, transient: !event.message.complete });
|
|
171
171
|
else if (event.type === "message-complete") await current.onEvent(event);
|
|
172
172
|
else if ((event.type === "admitted" && current.goal) || event.type === "settled") {
|
|
173
|
+
// Claude has the authored /goal command; it now owns the goal state.
|
|
174
|
+
if (event.type === "admitted" && current.goal.action !== "pause") await standalone.releaseRetainedGoal(entry);
|
|
173
175
|
await current.onEvent({ type: "goal", goal: claudeGoalValue(await entry.goals.read(), entry.id) });
|
|
174
176
|
} else if (event.type === "phase") await current.onEvent({ type: "phase", phase: ["compacting", "background"].includes(event.phase) ? event.phase : "working" });
|
|
175
177
|
}
|
|
@@ -933,7 +933,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
933
933
|
createdAt: goal?.createdAt, objective: input.action === "set" ? input.objective : goal?.objective };
|
|
934
934
|
}
|
|
935
935
|
result = await owner.updateGoal(sessionId, command, {
|
|
936
|
-
context: nativeContext, signal: context
|
|
936
|
+
context: nativeContext, signal: context?.signal, prepareConversation: () => goalContext(configuration, context, undefined, { prepareThread: true })
|
|
937
937
|
});
|
|
938
938
|
if (result.ok !== false) binding.goal = result.goal || null;
|
|
939
939
|
} finally { admission.release(); }
|
|
@@ -957,7 +957,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
957
957
|
if ((goalValue(goal)?.id || null) !== (input.expectedGoalId || null)) throw new Error("The native goal changed. Read it again before changing it.");
|
|
958
958
|
const result = await owner.updateGoal(sessionId, { ...input, threadId: binding.threadId,
|
|
959
959
|
createdAt: goal?.createdAt, objective: input.action === "set" ? input.objective : goal?.objective }, {
|
|
960
|
-
context: nativeContext, signal: context
|
|
960
|
+
context: nativeContext, signal: context?.signal,
|
|
961
961
|
prepareConversation: () => goalContext(configuration, context, signal, { prepareThread: true, tools })
|
|
962
962
|
});
|
|
963
963
|
if (result.ok === false) throw new Error(result.error);
|
|
@@ -13,6 +13,19 @@ import {
|
|
|
13
13
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
14
14
|
const FIXTURE_ROOT = path.resolve(TEST_DIRECTORY, "../fixtures/responsive-assistant");
|
|
15
15
|
const RUN_BROWSER_TEST = process.env.JSKIT_ASSISTANT_CORE_BROWSER_INTEGRATION === "1";
|
|
16
|
+
// Turns use content-visibility estimates. Moving the reader materializes rows
|
|
17
|
+
// near the new position, and Chromium's scroll anchoring then shifts scrollTop
|
|
18
|
+
// to keep the same content in view. Hold a reading position only once that
|
|
19
|
+
// layout has settled, so later checks measure the conversation, not the estimates.
|
|
20
|
+
async function settleReaderAt(body, scrollTop) {
|
|
21
|
+
await expect.poll(() => body.evaluate(async (element, top) => {
|
|
22
|
+
element.scrollTop = top;
|
|
23
|
+
const height = element.scrollHeight;
|
|
24
|
+
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
25
|
+
return element.scrollTop === top && element.scrollHeight === height;
|
|
26
|
+
}, scrollTop)).toBe(true);
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
const VIEWPORTS = Object.freeze([
|
|
17
30
|
Object.freeze({ name: "phone", width: 390, height: 844 }),
|
|
18
31
|
Object.freeze({ name: "compact", width: 800, height: 900 }),
|
|
@@ -405,6 +418,14 @@ test("upward scrolling loads older history once and preserves retry and selectio
|
|
|
405
418
|
await expect(page.getByText("Conversation line 70:", { exact: false })).toBeVisible();
|
|
406
419
|
await expect(requests).toHaveText("History requests: 3");
|
|
407
420
|
await external("Exhaust history");
|
|
421
|
+
// Removing the history control re-anchors the scroller, which would
|
|
422
|
+
// cancel a keyboard scroll already in flight; press Home once it settles.
|
|
423
|
+
await expect(page.getByRole("button", { name: "Load older messages", exact: true })).toHaveCount(0);
|
|
424
|
+
await expect.poll(() => body.evaluate(async element => {
|
|
425
|
+
const height = element.scrollHeight;
|
|
426
|
+
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
427
|
+
return element.scrollHeight === height;
|
|
428
|
+
})).toBe(true);
|
|
408
429
|
await expect.poll(async () => {
|
|
409
430
|
await body.focus();
|
|
410
431
|
return body.evaluate(element => document.activeElement === element);
|
|
@@ -412,7 +433,6 @@ test("upward scrolling loads older history once and preserves retry and selectio
|
|
|
412
433
|
await body.press("Home");
|
|
413
434
|
await expect.poll(() => body.evaluate(element => element.scrollTop)).toBe(0);
|
|
414
435
|
await expect(requests).toHaveText("History requests: 3");
|
|
415
|
-
await expect(page.getByRole("button", { name: "Load older messages", exact: true })).toHaveCount(0);
|
|
416
436
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth));
|
|
417
437
|
assert.deepEqual(errors, []);
|
|
418
438
|
await page.close();
|
|
@@ -1235,15 +1255,18 @@ test("connection warnings keep avatar controls and Reload reachable without repl
|
|
|
1235
1255
|
await body.hover();
|
|
1236
1256
|
await page.mouse.wheel(0, -1000);
|
|
1237
1257
|
await expect.poll(() => body.evaluate(element => element.scrollHeight - element.clientHeight - element.scrollTop)).toBeGreaterThan(50);
|
|
1238
|
-
await body
|
|
1239
|
-
await
|
|
1258
|
+
await settleReaderAt(body, 420);
|
|
1259
|
+
await body.evaluate(element => { window.errorAvatarBody = element; });
|
|
1240
1260
|
const history = await body.locator(".assistant-transcript__turn").allTextContents();
|
|
1241
1261
|
let reloads = 0;
|
|
1242
1262
|
for (const size of ["standard", "large", "hidden"]) {
|
|
1243
1263
|
await page.getByRole("combobox", { name: "Avatar preference", exact: true }).selectOption(size);
|
|
1244
|
-
// Establish the reading position after changing the fixture-only avatar
|
|
1245
|
-
|
|
1246
|
-
|
|
1264
|
+
// Establish the reading position after changing the fixture-only avatar
|
|
1265
|
+
// preference. A reader's own wheel detaches the view from the latest
|
|
1266
|
+
// message even if a slow initial bottom scroll resumed following.
|
|
1267
|
+
await body.hover();
|
|
1268
|
+
await page.mouse.wheel(0, -200);
|
|
1269
|
+
await settleReaderAt(body, 420);
|
|
1247
1270
|
await external("Show update error");
|
|
1248
1271
|
await expect(conversation).toHaveClass(/assistant-conversation--error/u);
|
|
1249
1272
|
await expect(avatar).toHaveCSS("position", "absolute");
|
|
@@ -320,7 +320,13 @@ async function fixture(t, options = {}) {
|
|
|
320
320
|
attachment: { type: "goal_status", condition, sentinel: true, met: condition === "clear" } });
|
|
321
321
|
if (process.env.TEST_GOAL_LOST_ACK) return;
|
|
322
322
|
emit(frame);
|
|
323
|
-
if (condition === "clear")
|
|
323
|
+
if (condition === "clear") {
|
|
324
|
+
// Claude 2.1.289 saves the clear's local-command receipt and ends it
|
|
325
|
+
// with a result naming the command and the authored message.
|
|
326
|
+
record({ type: "system", subtype: "local_command", uuid: frame.uuid + "-clear", parentUuid: frame.uuid,
|
|
327
|
+
commandRun: { command: "goal", args: "clear" }, content: "<local-command-stdout>Goal cleared: prior goal</local-command-stdout>" });
|
|
328
|
+
emit({ type: "result", subtype: "success", result: "", local_command: "goal", user_message_uuid: frame.uuid });
|
|
329
|
+
}
|
|
324
330
|
return;
|
|
325
331
|
}
|
|
326
332
|
emit(frame);
|
|
@@ -1167,8 +1173,9 @@ test("Claude native goal commands use ordinary admission, pause retains the goal
|
|
|
1167
1173
|
assert.equal(events.filter(event => event.type === "accepted").length, 3);
|
|
1168
1174
|
assert.equal(events.some(event => event.type === "goal" && event.goal === null), true);
|
|
1169
1175
|
const trace = await f.trace();
|
|
1176
|
+
// Resume starts a new process, which first suspends the retained paused checker.
|
|
1170
1177
|
assert.deepEqual(trace.filter(row => row.frame?.type === "user").map(row => row.frame.message.content),
|
|
1171
|
-
["/goal Tests pass", "/goal Tests pass", "/goal clear"]);
|
|
1178
|
+
["/goal Tests pass", "/goal clear", "/goal Tests pass", "/goal clear"]);
|
|
1172
1179
|
const starts = trace.filter(row => row.args?.includes("--print"));
|
|
1173
1180
|
assert.equal(starts.length, 3);
|
|
1174
1181
|
assert.equal(starts[1].args.includes("--resume"), true);
|
|
@@ -1229,7 +1229,9 @@ async function fixture(t, options = {}) {
|
|
|
1229
1229
|
t.after(async () => {
|
|
1230
1230
|
try { for (const runtime of runtimes) await runtime.close(); }
|
|
1231
1231
|
finally {
|
|
1232
|
-
|
|
1232
|
+
// A supplied execution host is closed here too (its test may close it
|
|
1233
|
+
// again): its native processes must stop before their folder is removed.
|
|
1234
|
+
await execution.close();
|
|
1233
1235
|
await rm(directory, { recursive: true, force: true });
|
|
1234
1236
|
}
|
|
1235
1237
|
});
|