@dxos/agent-runtime 0.14.0 → 0.16.0

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": "@dxos/agent-runtime",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Local agent process runtime (agent-service) and assistant-coupled test helpers.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -38,30 +38,30 @@
38
38
  "src"
39
39
  ],
40
40
  "dependencies": {
41
- "effect": "4.0.0",
42
- "@dxos/ai": "0.14.0",
43
- "@dxos/compute": "0.14.0",
44
- "@dxos/compute-runtime": "0.14.0",
45
- "@dxos/echo": "0.14.0",
46
- "@dxos/echo-client": "0.14.0",
47
- "@dxos/effect": "0.14.0",
48
- "@dxos/keys": "0.14.0",
49
- "@dxos/link": "0.14.0",
50
- "@dxos/log": "0.14.0",
51
- "@dxos/mcp-client": "0.14.0",
52
- "@dxos/schema": "0.14.0",
53
- "@dxos/types": "0.14.0",
54
- "@dxos/util": "0.14.0"
41
+ "effect": "4.0.4",
42
+ "@dxos/ai": "0.16.0",
43
+ "@dxos/compute": "0.16.0",
44
+ "@dxos/compute-runtime": "0.16.0",
45
+ "@dxos/echo": "0.16.0",
46
+ "@dxos/echo-client": "0.16.0",
47
+ "@dxos/effect": "0.16.0",
48
+ "@dxos/keys": "0.16.0",
49
+ "@dxos/link": "0.16.0",
50
+ "@dxos/log": "0.16.0",
51
+ "@dxos/mcp-client": "0.16.0",
52
+ "@dxos/schema": "0.16.0",
53
+ "@dxos/types": "0.16.0",
54
+ "@dxos/util": "0.16.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@effect/vitest": "4.0.0",
57
+ "@effect/vitest": "4.0.4",
58
58
  "vitest": "4.1.10",
59
- "@dxos/assistant": "0.14.0",
60
- "@dxos/errors": "0.14.0"
59
+ "@dxos/assistant": "0.16.0",
60
+ "@dxos/errors": "0.16.0"
61
61
  },
62
62
  "peerDependencies": {
63
- "effect": "4.0.0",
64
- "@dxos/assistant": "^0.14.0"
63
+ "effect": "4.0.4",
64
+ "@dxos/assistant": "^0.16.0"
65
65
  },
66
66
  "publishConfig": {
67
67
  "access": "public"
@@ -891,6 +891,47 @@ describe('Agent Service (control plane)', () => {
891
891
  TestHelpers.provideTestContext,
892
892
  ),
893
893
  );
894
+ it.effect(
895
+ 'session reuse tracks the harness selected on the chat',
896
+ Effect.fnUntraced(
897
+ function* (_) {
898
+ const processManager = yield* ProcessManager.ProcessManagerService;
899
+
900
+ const feed = yield* Database.add(Feed.make());
901
+ const chat = yield* Database.add(Chat.make({ feed: Ref.make(feed) }));
902
+ yield* Database.flush();
903
+ const target = Obj.getURI(chat);
904
+
905
+ const isActive = (state: Process.State) =>
906
+ state !== Process.State.SUCCEEDED && state !== Process.State.FAILED && state !== Process.State.TERMINATED;
907
+ const activePids = Effect.gen(function* () {
908
+ const processes = yield* processManager.list({ target, key: AGENT_PROCESS_KEY });
909
+ return processes.filter((process) => isActive(process.status.state)).map((process) => String(process.pid));
910
+ });
911
+
912
+ // Opening a chat spawns its process on the default harness before the user picks one.
913
+ const sessionA = yield* ComputeAgentService.getSession(chat);
914
+ const [pidA] = yield* activePids;
915
+
916
+ // The turn producer is chosen when the process starts, so picking a harness must respawn it.
917
+ Obj.update(chat, (chat) => {
918
+ chat.session = { harness: 'claude-code-edge' };
919
+ });
920
+ yield* Database.flush();
921
+ const sessionB = yield* ComputeAgentService.getSession(chat);
922
+ const [pidB] = yield* activePids;
923
+ expect(sessionB).not.toBe(sessionA);
924
+ expect(pidB).not.toBe(pidA);
925
+ expect(yield* activePids).toEqual([pidB]);
926
+
927
+ expect(yield* ComputeAgentService.getSession(chat)).toBe(sessionB);
928
+
929
+ yield* sessionB.terminate();
930
+ },
931
+ Effect.provide(TestLayer()),
932
+ TestHelpers.provideTestContext,
933
+ ),
934
+ );
894
935
  it.effect(
895
936
  'traces a turn run through the agent process',
896
937
  Effect.fnUntraced(
@@ -10,6 +10,7 @@ import * as Layer from 'effect/Layer';
10
10
  import * as Atom from 'effect/reactivity/Atom';
11
11
  import * as Semaphore from 'effect/Semaphore';
12
12
 
13
+ import { SessionConfig } from '@dxos/ai';
13
14
  import { AiContext, type HarnessControlRpcs } from '@dxos/assistant';
14
15
  import * as Chat from '@dxos/assistant/Chat';
15
16
  import * as AgentService from '@dxos/compute/AgentService';
@@ -145,13 +146,14 @@ export const layer = (opts?: Options): Layer.Layer<AgentService.AgentService, ne
145
146
  return { kind: 'edge', space: spaceId };
146
147
  };
147
148
 
148
- // The agent's model and steering instructions are bound to its process at spawn time, so the
149
- // cache tracks what each session was created with. A chat repointed at a different model or
150
- // instructions ref tears down the old process and spawns a fresh one (see below).
149
+ // The agent's model, harness and steering instructions are bound to its process at spawn time, so
150
+ // the cache tracks what each session was created with. A chat repointed at a different model,
151
+ // harness or instructions ref tears down the old process and spawns a fresh one (see below).
151
152
  const sessionCache = new Map<
152
153
  string,
153
154
  {
154
155
  model: string | undefined;
156
+ harness: string;
155
157
  provider: DXN.DXN | undefined;
156
158
  instructions: string | undefined;
157
159
  location: AgentService.AgentLocation;
@@ -249,6 +251,7 @@ export const layer = (opts?: Options): Layer.Layer<AgentService.AgentService, ne
249
251
  // Read off the chat rather than passed in: the process is bound to the chat, so its
250
252
  // model and steering are whatever the chat points at when the process is spawned.
251
253
  const model = chat.session?.model;
254
+ const harness = SessionConfig.harnessOf(chat.session);
252
255
  const instructions = chat.instructions?.uri;
253
256
  const location: AgentService.AgentLocation = options?.location ?? 'local';
254
257
  const executable = executableFor(chat, provider);
@@ -262,6 +265,7 @@ export const layer = (opts?: Options): Layer.Layer<AgentService.AgentService, ne
262
265
  if (cached) {
263
266
  if (
264
267
  cached.model === model &&
268
+ cached.harness === harness &&
265
269
  cached.provider === provider &&
266
270
  cached.instructions === instructions &&
267
271
  cached.location === location &&
@@ -272,7 +276,7 @@ export const layer = (opts?: Options): Layer.Layer<AgentService.AgentService, ne
272
276
  }
273
277
 
274
278
  if (!Process.isTerminal(cached.handle.status.state)) {
275
- // Model, provider, steering instructions, location or process changed (e.g. the user
279
+ // Model, harness, provider, steering instructions, location or process changed (e.g. the user
276
280
  // toggled online/offline, or moved the chat to the cloud): terminate the
277
281
  // existing process so the conversation continues on a fresh process bound to the new
278
282
  // configuration. Conversation history is preserved via the feed, which the new
@@ -363,6 +367,7 @@ export const layer = (opts?: Options): Layer.Layer<AgentService.AgentService, ne
363
367
  const session = makeSession(handle, chat, feed, releaseSession, isFinished, resubmit);
364
368
  sessionCache.set(chat.id, {
365
369
  model,
370
+ harness,
366
371
  provider,
367
372
  instructions,
368
373
  location,