@jskit-ai/assistant-core 0.1.239 → 0.1.241
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/AssistantTranscript.vue +26 -29
- package/src/client/conversation/LongTextPreviewBlocks.vue +11 -0
- package/src/client/conversation/messageDelivery.js +8 -2
- package/src/client/conversation/useAssistantAttachments.js +1 -1
- package/src/server/conversation/agentRun.js +12 -198
- package/src/server/conversation/attachments.js +6 -5
- package/src/server/conversation/boundedToolLoop.js +160 -0
- package/src/server/conversation/claudeHistory.js +20 -16
- package/src/server/conversation/claudeProcess.js +2 -2
- package/src/server/conversation/claudeTurn.js +51 -40
- package/src/server/conversation/codexAuthPreflight.js +104 -0
- package/src/server/conversation/codexClient.js +11 -16
- package/src/server/conversation/codexConfiguration.js +112 -98
- package/src/server/conversation/codexConversationStore.js +189 -0
- package/src/server/conversation/codexConversations.js +47 -44
- package/src/server/conversation/codexDetachedTurn.js +12 -11
- package/src/server/conversation/codexEvents.js +95 -99
- package/src/server/conversation/codexGoalCommands.js +1 -1
- package/src/server/conversation/codexHelperExecution.js +3 -13
- package/src/server/conversation/codexHelperThreadLedger.js +7 -13
- package/src/server/conversation/codexHelperThreadRecovery.js +2 -16
- package/src/server/conversation/codexHelperThreads.js +5 -6
- package/src/server/conversation/codexHistoryAdapter.js +32 -26
- package/src/server/conversation/codexMessageCommands.js +22 -22
- package/src/server/conversation/codexMessageDelivery.js +12 -7
- package/src/server/conversation/codexModelCatalog.js +97 -0
- package/src/server/conversation/codexNotificationObserver.js +24 -40
- package/src/server/conversation/codexNotificationQueue.js +2 -3
- package/src/server/conversation/codexOutput.js +20 -27
- package/src/server/conversation/codexProcess.js +123 -127
- package/src/server/conversation/codexProcessLeader.js +3 -3
- package/src/server/conversation/codexProvider.js +31 -614
- package/src/server/conversation/codexProviderOwner.js +2 -2
- package/src/server/conversation/codexProviderSelection.js +38 -40
- package/src/server/conversation/codexRenewalCommands.js +13 -10
- package/src/server/conversation/codexRenewalThreads.js +412 -0
- package/src/server/conversation/codexRunJournal.js +24 -42
- package/src/server/conversation/codexRuntimeLifecycle.js +19 -15
- package/src/server/conversation/codexSessionCleanup.js +11 -10
- package/src/server/conversation/codexSessionReconciliation.js +6 -6
- package/src/server/conversation/codexThreadReadiness.js +19 -33
- package/src/server/conversation/codexThreadStatus.js +3 -3
- package/src/server/conversation/codexTurn.js +124 -297
- package/src/server/conversation/codexTurnRecovery.js +49 -28
- package/src/server/conversation/codexTurnState.js +34 -13
- package/src/server/conversation/commandWrapper.js +3 -0
- package/src/server/conversation/deliveryTiming.js +14 -5
- package/src/server/conversation/index.js +1 -0
- package/src/server/conversation/localExecution.js +13 -17
- package/src/server/conversation/normalize.js +8 -3
- package/src/server/conversation/openCodeClient.js +9 -10
- package/src/server/conversation/openCodeProcess.js +9 -5
- package/src/server/conversation/openCodeRuntime.js +10 -11
- package/src/server/conversation/openCodeTurn.js +43 -29
- package/src/server/conversation/providers/claudeDriver.js +10 -16
- package/src/server/conversation/providers/codexDriver.js +27 -43
- package/src/server/conversation/providers/opencodeDriver.js +17 -42
- package/src/server/conversation/providers/scoped.js +48 -4
- package/src/server/conversation/runtime.js +50 -49
- package/src/server/conversation/runtimeStateUpgrade.js +34 -22
- package/src/server/conversation/transcript.js +10 -10
- package/src/server/hosts/codexConfiguration.js +1 -0
- package/src/server/hosts/codexProvider.js +8 -6
- package/src/server/index.js +2 -1
- package/src/server/lib/assistantToolLoop.js +1 -153
- package/src/server/lib/serviceToolCatalog.js +17 -42
- package/src/shared/assistantResource.js +4 -2
- package/src/shared/conversation/attachments.js +4 -0
- package/src/shared/conversation/longTextBlocks.js +54 -19
- package/src/shared/conversation/numberedQuestionSugar.js +17 -10
- package/src/shared/conversation/pagination.js +4 -6
- package/src/shared/conversation/patches.js +1 -4
- package/src/shared/conversation/streaming.js +3 -11
- package/src/shared/conversation/turns.js +11 -0
- package/src/shared/index.js +1 -0
- package/test/assistantBoundedToolLoop.test.js +2 -2
- package/test/assistantResource.test.js +4 -4
- package/test/assistantScroll.browser.test.js +29 -6
- package/test/assistantSettingsResource.test.js +2 -2
- package/test/claudeConversationSession.test.js +20 -3
- package/test/claudeTurn.test.js +27 -7
- package/test/codexClient.test.js +20 -0
- package/test/codexConversationSession.test.js +5 -3
- package/test/codexConversationsCheckpoint.test.js +51 -0
- package/test/codexModelCatalog.test.js +1 -1
- package/test/codexProviderHome.test.js +51 -0
- package/test/codexProviderSelection.test.js +71 -0
- package/test/codexTurn.test.js +222 -6
- package/test/conversationAgentRun.test.js +2 -1
- package/test/conversationAttachments.test.js +27 -2
- package/test/conversationDeliveryTiming.test.js +21 -0
- package/test/conversationNormalize.test.js +11 -0
- package/test/fixtures/nestedListQuestionMessage.md +37 -0
- package/test/frameworkActions.test.js +2 -2
- package/test/longTextBlocks.test.js +102 -0
- package/test/messageDelivery.test.js +7 -1
- package/test/openCodeClient.test.js +20 -0
- package/test/openCodeRuntime.test.js +1 -1
- package/test/openCodeTurn.test.js +90 -0
- package/test/opencodeConversationSession.test.js +1 -1
- package/test/serviceToolCatalog.test.js +27 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
inspectOpenCodeMessageAdmission,
|
|
6
|
+
readOpenCodeMessagesUntil,
|
|
7
|
+
runOpenCodeConversationTurn,
|
|
8
|
+
waitForOpenCodeMessages
|
|
9
|
+
} from "../src/server/conversation/openCodeTurn.js";
|
|
10
|
+
|
|
11
|
+
// Mirrors the native message route: a limited read returns the newest rows in
|
|
12
|
+
// chronological order and an opaque cursor that continues with older rows.
|
|
13
|
+
function pagedClient(history) {
|
|
14
|
+
const reads = [];
|
|
15
|
+
const prompts = [];
|
|
16
|
+
return {
|
|
17
|
+
reads,
|
|
18
|
+
prompts,
|
|
19
|
+
async messages(_sessionId, { limit, before } = {}) {
|
|
20
|
+
reads.push({ limit, before });
|
|
21
|
+
const end = before ? Number(before.slice("cursor-".length)) : history.length;
|
|
22
|
+
const start = Math.max(0, end - limit);
|
|
23
|
+
return { data: history.slice(start, end), ...(start > 0 ? { nextCursor: `cursor-${start}` } : {}) };
|
|
24
|
+
},
|
|
25
|
+
async sessionStatus() { return { type: "idle" }; },
|
|
26
|
+
async prompt(_sessionId, input) { prompts.push(input); return { id: input.id }; }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// One user input followed by many native model steps, the last one final.
|
|
31
|
+
function longTurnHistory(steps = 150) {
|
|
32
|
+
const history = [
|
|
33
|
+
{ id: "msg_older", type: "user", time: { created: 1 }, content: [{ type: "text", text: "earlier" }] },
|
|
34
|
+
{ id: "msg_input", type: "user", time: { created: 2 }, content: [{ type: "text", text: "build it" }] }
|
|
35
|
+
];
|
|
36
|
+
for (let step = 1; step <= steps; step += 1) {
|
|
37
|
+
const final = step === steps;
|
|
38
|
+
history.push({
|
|
39
|
+
id: `msg_step_${step}`,
|
|
40
|
+
type: "assistant",
|
|
41
|
+
time: { created: 2 + step, ...(final ? { completed: 3 + step } : {}) },
|
|
42
|
+
finish: final ? "stop" : "tool-calls",
|
|
43
|
+
content: final ? [{ type: "text", text: "Done." }] : [{ type: "tool", tool: "bash", callID: `call_${step}` }]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return history;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test("a turn with more than one native page of steps after its input still completes", async () => {
|
|
50
|
+
const client = pagedClient(longTurnHistory());
|
|
51
|
+
const completion = await waitForOpenCodeMessages(client, "ses_long", "msg_input", {
|
|
52
|
+
signal: AbortSignal.timeout(5_000)
|
|
53
|
+
});
|
|
54
|
+
assert.equal(completion.result.complete, true);
|
|
55
|
+
assert.equal(completion.result.text, "Done.");
|
|
56
|
+
assert.equal(completion.result.turnId, "msg_step_150");
|
|
57
|
+
// The input sits on the second page; reading stops there instead of exhausting history.
|
|
58
|
+
assert.deepEqual(client.reads.slice(0, 2), [{ limit: 100, before: undefined }, { limit: 100, before: "cursor-52" }]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("admission of an input followed by more than one native page of steps is still proven", async () => {
|
|
62
|
+
const client = pagedClient(longTurnHistory());
|
|
63
|
+
const observation = await inspectOpenCodeMessageAdmission(client, "ses_long", "msg_input");
|
|
64
|
+
assert.equal(observation.accepted, true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a bounded turn whose input left the newest native page is not dispatched again", async () => {
|
|
68
|
+
const client = pagedClient(longTurnHistory());
|
|
69
|
+
const result = await runOpenCodeConversationTurn(client, "ses_long", {
|
|
70
|
+
inputMessageId: "msg_input", prompt: "build it", timeoutMs: 5_000
|
|
71
|
+
}, { completeResult: value => value });
|
|
72
|
+
assert.deepEqual(client.prompts, []);
|
|
73
|
+
assert.equal(result.text, "Done.");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("paged native history reading stops when its rows are found or history ends", async () => {
|
|
77
|
+
const history = longTurnHistory(250);
|
|
78
|
+
const client = pagedClient(history);
|
|
79
|
+
const newest = await readOpenCodeMessagesUntil(client, "ses_long", () => true);
|
|
80
|
+
assert.equal(newest.data.length, 100);
|
|
81
|
+
assert.equal(client.reads.length, 1);
|
|
82
|
+
|
|
83
|
+
client.reads.length = 0;
|
|
84
|
+
const whole = await readOpenCodeMessagesUntil(client, "ses_long", () => false);
|
|
85
|
+
assert.deepEqual(whole.data.map(row => row.id), history.map(row => row.id));
|
|
86
|
+
assert.equal(client.reads.length, 3);
|
|
87
|
+
|
|
88
|
+
const repeating = { async messages() { return { data: [], nextCursor: "same" }; } };
|
|
89
|
+
await assert.rejects(readOpenCodeMessagesUntil(repeating, "ses_long", () => false), /repeated a native history cursor/u);
|
|
90
|
+
});
|
|
@@ -1813,7 +1813,7 @@ for (const kind of ["tool", "reply"]) test(`completed OpenCode ${kind} wire reta
|
|
|
1813
1813
|
assert.equal(saved.assistant.messageId, `${receipt.turnId}:${answer.info.id}:assistant`);
|
|
1814
1814
|
const prepared = await f.prepare(receipt);
|
|
1815
1815
|
assert.equal(prepared.text, raw);
|
|
1816
|
-
const { readAssistantResponseEnvelope } = await import("../src/server/
|
|
1816
|
+
const { readAssistantResponseEnvelope } = await import("../src/server/conversation/boundedToolLoop.js");
|
|
1817
1817
|
const decoded = readAssistantResponseEnvelope(prepared.text);
|
|
1818
1818
|
assert.deepEqual(decoded, value);
|
|
1819
1819
|
if (kind === "tool") assert.equal(JSON.parse(decoded.arguments).value, handover);
|
|
@@ -618,6 +618,33 @@ test("assistant tools reject unknown tools and return safe action failures", asy
|
|
|
618
618
|
});
|
|
619
619
|
});
|
|
620
620
|
|
|
621
|
+
test("malformed tool arguments fail without running the action", async () => {
|
|
622
|
+
let executions = 0;
|
|
623
|
+
const actions = createActions([action({
|
|
624
|
+
kind: "command",
|
|
625
|
+
input: schema({ title: { type: "string" } }),
|
|
626
|
+
execute: async () => {
|
|
627
|
+
executions += 1;
|
|
628
|
+
return { ok: true };
|
|
629
|
+
}
|
|
630
|
+
})]);
|
|
631
|
+
const catalog = createServiceToolCatalog(actions);
|
|
632
|
+
const context = { actor: { id: "7" }, surface: "admin" };
|
|
633
|
+
const toolSet = catalog.resolveToolSet(context);
|
|
634
|
+
const toolName = toolSet.tools[0].name;
|
|
635
|
+
|
|
636
|
+
for (const argumentsText of ['{"title":"x"', "[]", '"x"', "null", "7"]) {
|
|
637
|
+
assert.deepEqual(await catalog.executeToolCall({ toolName, context, toolSet, argumentsText }), {
|
|
638
|
+
ok: false,
|
|
639
|
+
error: { code: "assistant_tool_arguments_invalid", message: "Tool arguments must be a JSON object.", status: 400 }
|
|
640
|
+
}, argumentsText);
|
|
641
|
+
}
|
|
642
|
+
assert.equal(executions, 0);
|
|
643
|
+
|
|
644
|
+
assert.equal((await catalog.executeToolCall({ toolName, context, toolSet, argumentsText: " " })).ok, true);
|
|
645
|
+
assert.equal(executions, 1);
|
|
646
|
+
});
|
|
647
|
+
|
|
621
648
|
test("wildcard and empty surfaces match named surfaces without bypassing permissions", async () => {
|
|
622
649
|
for (const [surfaces, allowed] of [
|
|
623
650
|
[["*"], ["admin", "app", "console", "custom"]],
|