@jskit-ai/assistant-core 0.1.219 → 0.1.221

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.
@@ -8,14 +8,14 @@
8
8
  "build": "vite build"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/assistant-core": "0.1.219",
12
- "@jskit-ai/http-runtime": "0.1.239",
13
- "@jskit-ai/kernel": "0.1.241",
11
+ "@jskit-ai/assistant-core": "0.1.221",
12
+ "@jskit-ai/http-runtime": "0.1.241",
13
+ "@jskit-ai/kernel": "0.1.243",
14
14
  "@mdi/js": "^7.4.47",
15
15
  "vue": "^3.5.13",
16
16
  "vuetify": "^4.0.0",
17
- "@jskit-ai/connectors-core": "0.1.58",
18
- "@jskit-ai/connectors-catalog": "0.1.58"
17
+ "@jskit-ai/connectors-core": "0.1.60",
18
+ "@jskit-ai/connectors-catalog": "0.1.60"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@vitejs/plugin-vue": "^6.0.7",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-core",
3
- "version": "0.1.219",
3
+ "version": "0.1.221",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test --test-concurrency=1"
@@ -47,8 +47,8 @@
47
47
  "@ai-sdk/perplexity": "^4.0.43",
48
48
  "@ai-sdk/togetherai": "^3.0.49",
49
49
  "@ai-sdk/xai": "^4.0.58",
50
- "@jskit-ai/resource-core": "0.1.183",
51
- "@jskit-ai/resource-crud-core": "0.1.183",
50
+ "@jskit-ai/resource-core": "0.1.185",
51
+ "@jskit-ai/resource-crud-core": "0.1.185",
52
52
  "@mdi/js": "^7.4.47",
53
53
  "@openrouter/ai-sdk-provider": "^3.0.0",
54
54
  "ai": "^7.0.101",
@@ -57,8 +57,8 @@
57
57
  "ws": "^8.20.1"
58
58
  },
59
59
  "peerDependencies": {
60
- "@jskit-ai/http-runtime": "0.1.239",
61
- "@jskit-ai/kernel": "0.1.241",
60
+ "@jskit-ai/http-runtime": "0.1.241",
61
+ "@jskit-ai/kernel": "0.1.243",
62
62
  "vue": "^3.5.13",
63
63
  "vuetify": "^4.0.0"
64
64
  },
@@ -1634,7 +1634,9 @@ export function createOpenCodeSharedRuntime({ onStop = () => {}, scope } = {}) {
1634
1634
 
1635
1635
  async function interruptTurn(key, target, { threadId = "", writeRun } = {}) {
1636
1636
  const turn = turns.get(key);
1637
- if (!target) {
1637
+ // Helpers can acquire the shared process before a main conversation exists.
1638
+ // Their cleanup is owned by closeSession, not a native main-turn interrupt.
1639
+ if (!target || (!text(target.upstreamSessionId) && !turn?.active)) {
1638
1640
  return {
1639
1641
  interrupted: false,
1640
1642
  ok: true,
@@ -241,7 +241,8 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
241
241
  await emit(entry, { type: "message", turnId: turn.turnId, messageId: message.messageId,
242
242
  ...(message.outputId ? { outputId: message.outputId } : {}),
243
243
  ...(["user", "application"].includes(turn.metadata?.runtime?.origin) ? { origin: turn.metadata.runtime.origin } : {}),
244
- role: message.role, text: message.text, status: "complete", streaming: payload.conversationStream || streams.read(entry.id) });
244
+ role: message.role, text: message.text, at: message.at, status: "complete", persisted: true,
245
+ streaming: payload.conversationStream || streams.read(entry.id) });
245
246
  }
246
247
  }
247
248
  if (payload.conversationStream) {
@@ -3108,6 +3108,8 @@ test("default Codex commentary publishes its saved output identity rather than a
3108
3108
  assert.equal(completed.every(event => event.messageId === progress[0].messageId), true,
3109
3109
  "The existing canonical publication owns this exact output; its native stream must not complete a second identity");
3110
3110
  assert.equal(completed.every(event => event.outputId === progress[0].outputId && event.text === progress[0].text), true);
3111
+ assert.equal(completed.every(event => event.persisted === true && event.at === progress[0].at), true,
3112
+ "Saved output notifications retain their timestamp and cannot masquerade as transient output outside a loaded history page");
3111
3113
  assert.equal((await f.conversation.read()).streaming.completedMessages, undefined);
3112
3114
  assert.equal((await f.trace()).filter(row => row.method === "turn/start").length, 1);
3113
3115
  });
@@ -4,6 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
6
  import { createOpenCodeSharedRuntime, ensureOpenCodeSession, openCodeServerForDirectory, readOpenCodeEnvironments } from "../src/server/conversation/openCodeRuntime.js";
7
+ import { createOpenCodeServerClient } from "../src/server/conversation/openCodeClient.js";
7
8
 
8
9
  function fixture(options = {}) {
9
10
  const runtime = createOpenCodeSharedRuntime();
@@ -186,6 +187,54 @@ test("OpenCode shares one lazy server across open sessions and stops it after th
186
187
  assert.equal(f.stops.length, 2);
187
188
  });
188
189
 
190
+ test("an acquired OpenCode process without a main conversation can close without interrupting a peer", async () => {
191
+ const interrupted = [];
192
+ const f = fixture({ interrupt: id => { interrupted.push(id); return true; } });
193
+ const target = await f.acquire("empty");
194
+ target.sessionId = "empty";
195
+ const peer = await f.acquire("peer");
196
+ peer.upstreamSessionId = "peer-native";
197
+ const nativeClient = createOpenCodeServerClient({ baseUrl: "http://127.0.0.1:1",
198
+ fetchImpl: async () => assert.fail("No native main conversation exists to interrupt.") });
199
+ target.server = { ...target.server, client: nativeClient };
200
+ const stopped = await f.runtime.interruptPreparedTurn({
201
+ key: "empty", threadId: "", failure: error => { throw error; }
202
+ });
203
+ assert.equal(stopped.ok, true);
204
+ assert.equal(stopped.interrupted, false);
205
+ const calls = [];
206
+ const closed = await f.runtime.closeSession("empty", {}, {
207
+ closeTerminals: async () => { calls.push("terminals"); return { ok: true, closed: 0 }; },
208
+ beforeRelease: async () => { calls.push("helpers"); },
209
+ onRemoved: async () => { calls.push("removed"); },
210
+ afterRelease: async () => { calls.push("history"); },
211
+ failure: error => { throw error; }
212
+ });
213
+ assert.equal(closed.ok, true);
214
+ assert.equal(closed.closed, 1);
215
+ assert.deepEqual(calls, ["terminals", "helpers", "removed", "history"]);
216
+ assert.equal(target.abortController.signal.aborted, true);
217
+ assert.equal(peer.abortController.signal.aborted, false);
218
+ assert.equal(f.stops.length, 0);
219
+ assert.deepEqual(interrupted, []);
220
+ await f.runtime.interruptTurn("peer", peer);
221
+ assert.deepEqual(interrupted, ["peer-native"]);
222
+ await f.runtime.release(peer);
223
+ assert.equal(f.stops.length, 1);
224
+ });
225
+
226
+ test("an active OpenCode turn with missing native identity still fails instead of claiming Stop", async () => {
227
+ const runtime = createOpenCodeSharedRuntime();
228
+ const client = createOpenCodeServerClient({ baseUrl: "http://127.0.0.1:1",
229
+ fetchImpl: async () => assert.fail("Invalid identity must fail before transport.") });
230
+ const turn = { active: true, state: "inProgress" };
231
+ runtime.turns.set("invalid", turn);
232
+ await assert.rejects(runtime.interruptTurn("invalid", { upstreamSessionId: "", server: { client } }),
233
+ /OpenCode session requests require a session id/u);
234
+ assert.equal(turn.active, true);
235
+ assert.equal(turn.interruptRequested, false);
236
+ });
237
+
189
238
  test("a pending OpenCode session start retains the shared server while another session closes", async () => {
190
239
  const f = fixture();
191
240
  const first = await f.acquire("session-1");