@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.
Files changed (103) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +5 -5
  3. package/src/client/conversation/AssistantTranscript.vue +26 -29
  4. package/src/client/conversation/LongTextPreviewBlocks.vue +11 -0
  5. package/src/client/conversation/messageDelivery.js +8 -2
  6. package/src/client/conversation/useAssistantAttachments.js +1 -1
  7. package/src/server/conversation/agentRun.js +12 -198
  8. package/src/server/conversation/attachments.js +6 -5
  9. package/src/server/conversation/boundedToolLoop.js +160 -0
  10. package/src/server/conversation/claudeHistory.js +20 -16
  11. package/src/server/conversation/claudeProcess.js +2 -2
  12. package/src/server/conversation/claudeTurn.js +51 -40
  13. package/src/server/conversation/codexAuthPreflight.js +104 -0
  14. package/src/server/conversation/codexClient.js +11 -16
  15. package/src/server/conversation/codexConfiguration.js +112 -98
  16. package/src/server/conversation/codexConversationStore.js +189 -0
  17. package/src/server/conversation/codexConversations.js +47 -44
  18. package/src/server/conversation/codexDetachedTurn.js +12 -11
  19. package/src/server/conversation/codexEvents.js +95 -99
  20. package/src/server/conversation/codexGoalCommands.js +1 -1
  21. package/src/server/conversation/codexHelperExecution.js +3 -13
  22. package/src/server/conversation/codexHelperThreadLedger.js +7 -13
  23. package/src/server/conversation/codexHelperThreadRecovery.js +2 -16
  24. package/src/server/conversation/codexHelperThreads.js +5 -6
  25. package/src/server/conversation/codexHistoryAdapter.js +32 -26
  26. package/src/server/conversation/codexMessageCommands.js +22 -22
  27. package/src/server/conversation/codexMessageDelivery.js +12 -7
  28. package/src/server/conversation/codexModelCatalog.js +97 -0
  29. package/src/server/conversation/codexNotificationObserver.js +24 -40
  30. package/src/server/conversation/codexNotificationQueue.js +2 -3
  31. package/src/server/conversation/codexOutput.js +20 -27
  32. package/src/server/conversation/codexProcess.js +123 -127
  33. package/src/server/conversation/codexProcessLeader.js +3 -3
  34. package/src/server/conversation/codexProvider.js +31 -614
  35. package/src/server/conversation/codexProviderOwner.js +2 -2
  36. package/src/server/conversation/codexProviderSelection.js +38 -40
  37. package/src/server/conversation/codexRenewalCommands.js +13 -10
  38. package/src/server/conversation/codexRenewalThreads.js +412 -0
  39. package/src/server/conversation/codexRunJournal.js +24 -42
  40. package/src/server/conversation/codexRuntimeLifecycle.js +19 -15
  41. package/src/server/conversation/codexSessionCleanup.js +11 -10
  42. package/src/server/conversation/codexSessionReconciliation.js +6 -6
  43. package/src/server/conversation/codexThreadReadiness.js +19 -33
  44. package/src/server/conversation/codexThreadStatus.js +3 -3
  45. package/src/server/conversation/codexTurn.js +124 -297
  46. package/src/server/conversation/codexTurnRecovery.js +49 -28
  47. package/src/server/conversation/codexTurnState.js +34 -13
  48. package/src/server/conversation/commandWrapper.js +3 -0
  49. package/src/server/conversation/deliveryTiming.js +14 -5
  50. package/src/server/conversation/index.js +1 -0
  51. package/src/server/conversation/localExecution.js +13 -17
  52. package/src/server/conversation/normalize.js +8 -3
  53. package/src/server/conversation/openCodeClient.js +9 -10
  54. package/src/server/conversation/openCodeProcess.js +9 -5
  55. package/src/server/conversation/openCodeRuntime.js +10 -11
  56. package/src/server/conversation/openCodeTurn.js +43 -29
  57. package/src/server/conversation/providers/claudeDriver.js +10 -16
  58. package/src/server/conversation/providers/codexDriver.js +27 -43
  59. package/src/server/conversation/providers/opencodeDriver.js +17 -42
  60. package/src/server/conversation/providers/scoped.js +48 -4
  61. package/src/server/conversation/runtime.js +50 -49
  62. package/src/server/conversation/runtimeStateUpgrade.js +34 -22
  63. package/src/server/conversation/transcript.js +10 -10
  64. package/src/server/hosts/codexConfiguration.js +1 -0
  65. package/src/server/hosts/codexProvider.js +8 -6
  66. package/src/server/index.js +2 -1
  67. package/src/server/lib/assistantToolLoop.js +1 -153
  68. package/src/server/lib/serviceToolCatalog.js +17 -42
  69. package/src/shared/assistantResource.js +4 -2
  70. package/src/shared/conversation/attachments.js +4 -0
  71. package/src/shared/conversation/longTextBlocks.js +54 -19
  72. package/src/shared/conversation/numberedQuestionSugar.js +17 -10
  73. package/src/shared/conversation/pagination.js +4 -6
  74. package/src/shared/conversation/patches.js +1 -4
  75. package/src/shared/conversation/streaming.js +3 -11
  76. package/src/shared/conversation/turns.js +11 -0
  77. package/src/shared/index.js +1 -0
  78. package/test/assistantBoundedToolLoop.test.js +2 -2
  79. package/test/assistantResource.test.js +4 -4
  80. package/test/assistantScroll.browser.test.js +29 -6
  81. package/test/assistantSettingsResource.test.js +2 -2
  82. package/test/claudeConversationSession.test.js +20 -3
  83. package/test/claudeTurn.test.js +27 -7
  84. package/test/codexClient.test.js +20 -0
  85. package/test/codexConversationSession.test.js +5 -3
  86. package/test/codexConversationsCheckpoint.test.js +51 -0
  87. package/test/codexModelCatalog.test.js +1 -1
  88. package/test/codexProviderHome.test.js +51 -0
  89. package/test/codexProviderSelection.test.js +71 -0
  90. package/test/codexTurn.test.js +222 -6
  91. package/test/conversationAgentRun.test.js +2 -1
  92. package/test/conversationAttachments.test.js +27 -2
  93. package/test/conversationDeliveryTiming.test.js +21 -0
  94. package/test/conversationNormalize.test.js +11 -0
  95. package/test/fixtures/nestedListQuestionMessage.md +37 -0
  96. package/test/frameworkActions.test.js +2 -2
  97. package/test/longTextBlocks.test.js +102 -0
  98. package/test/messageDelivery.test.js +7 -1
  99. package/test/openCodeClient.test.js +20 -0
  100. package/test/openCodeRuntime.test.js +1 -1
  101. package/test/openCodeTurn.test.js +90 -0
  102. package/test/opencodeConversationSession.test.js +1 -1
  103. 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/lib/assistantToolLoop.js");
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"]],