@khorsheed/dsh-room 0.1.1 → 0.2.1

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 (40) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.en.md +9 -8
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +9 -8
  5. package/lib/client.js +3422 -1481
  6. package/lib/index.js +88 -12
  7. package/lib/{journal-CuRnWk66.js → journal-BWL3-i0s.js} +27 -0
  8. package/lib/tool.js +63 -11
  9. package/lib/typert.host.js +161 -26
  10. package/lib/typert.remote-client.d.ts +2 -0
  11. package/lib/typert.remote-client.js +142 -22
  12. package/lib/types/adapter.d.ts +2 -2
  13. package/lib/types/client/RoomActivityDock.d.ts +19 -0
  14. package/lib/types/client/RoomActivityDock.js +105 -0
  15. package/lib/types/client/RoomComposer.d.ts +1 -1
  16. package/lib/types/client/RoomComposer.js +3 -3
  17. package/lib/types/client/RoomPlanTab.d.ts +15 -0
  18. package/lib/types/client/RoomPlanTab.js +13 -0
  19. package/lib/types/client/RoomPlanView.d.ts +3 -2
  20. package/lib/types/client/RoomPlanView.js +5 -6
  21. package/lib/types/client/executions.d.ts +50 -0
  22. package/lib/types/client/executions.js +70 -0
  23. package/lib/types/client/index.d.ts +1 -1
  24. package/lib/types/client/index.js +33 -1
  25. package/lib/types/client/locales.d.ts +50 -0
  26. package/lib/types/client/locales.js +50 -0
  27. package/lib/types/client/preset-visibility.d.ts +3 -3
  28. package/lib/types/client/preset-visibility.js +2 -2
  29. package/lib/types/client/slots.d.ts +6 -1
  30. package/lib/types/dispatch.js +30 -6
  31. package/lib/types/index.d.ts +5 -0
  32. package/lib/types/index.js +42 -7
  33. package/lib/types/journal.d.ts +1 -1
  34. package/lib/types/journal.js +27 -1
  35. package/lib/types/tool-definition.d.ts +56 -0
  36. package/lib/types/tool-definition.js +393 -0
  37. package/lib/types/tool.d.ts +37 -49
  38. package/lib/types/tool.js +47 -381
  39. package/lib/types/types.d.ts +28 -0
  40. package/package.json +27 -26
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as memberId, c as pendingInstructions, d as rosterStaleSince, f as replayDeliveries, i as isRoomLog, l as previousCursor, n as ROOM_EVENT_TYPES, o as parseMentions, r as coordinatorMember, s as parseRelayDirective, t as MAIN_AGENT_MEMBER, u as replay } from "./journal-CuRnWk66.js";
1
+ import { a as memberId, c as pendingInstructions, d as rosterStaleSince, f as replayDeliveries, i as isRoomLog, l as previousCursor, n as ROOM_EVENT_TYPES, o as parseMentions, r as coordinatorMember, s as parseRelayDirective, t as MAIN_AGENT_MEMBER, u as replay } from "./journal-BWL3-i0s.js";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import { SessionId } from "@deepseek-ai/dsh-session";
4
4
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
@@ -1218,6 +1218,14 @@ var DispatchEngine = class {
1218
1218
  const error = "Execution outcome is unknown after restart; reconcile before retrying";
1219
1219
  return {
1220
1220
  ...state,
1221
+ ...state.executions === void 0 ? {} : { executions: state.executions.map((execution) => {
1222
+ const active = execution.runId === void 0 ? [...this.inFlight.values()].includes(`${sessionId} ${execution.memberId}`) : this.scheduled.has(`${sessionId}:${execution.runId}`);
1223
+ return execution.state === "running" && !active ? {
1224
+ ...execution,
1225
+ state: "failed",
1226
+ error
1227
+ } : execution;
1228
+ }) },
1221
1229
  ...state.deliveries === void 0 ? {} : { deliveries: state.deliveries.map((row) => row.status === "queued" && closedGoal(state, row.plan?.goalId) ? {
1222
1230
  ...row,
1223
1231
  status: "cancelled"
@@ -1318,7 +1326,8 @@ var DispatchEngine = class {
1318
1326
  member: memberName,
1319
1327
  state: "running",
1320
1328
  startedAt,
1321
- runId
1329
+ runId,
1330
+ ...member.kind === "main-agent" ? { childSessionId: room.id } : member.childSessionId === void 0 ? {} : { childSessionId: member.childSessionId }
1322
1331
  });
1323
1332
  await this.hooks.admitted?.(room, options.dispatchSeq);
1324
1333
  await this.ctx.sessions.flush(room);
@@ -1326,7 +1335,7 @@ var DispatchEngine = class {
1326
1335
  };
1327
1336
  let outcome;
1328
1337
  try {
1329
- outcome = member.kind === "main-agent" ? await this.runMainAgent(room, member, cursor, text, await admitted(), options.relayIds ?? []) : await this.runCliMember(room, member, cursor, text, admitted, options.relayIds ?? [], submitted);
1338
+ outcome = member.kind === "main-agent" ? await this.runMainAgent(room, member, cursor, text, await admitted(), options.relayIds ?? []) : await this.runCliMember(room, member, cursor, text, admitted, options.relayIds ?? [], submitted, runId);
1330
1339
  } catch (error) {
1331
1340
  this.ctx.logger.warn(`room: dispatch to "${memberName}" failed: ${String(error)}`);
1332
1341
  outcome = {
@@ -1409,7 +1418,7 @@ var DispatchEngine = class {
1409
1418
  return { state: "done" };
1410
1419
  }
1411
1420
  /** CLI member turn: facade start (first round) or resume, then the speech mirror. */
1412
- async runCliMember(room, member, cursor, text, admitted, relayIds, submitted) {
1421
+ async runCliMember(room, member, cursor, text, admitted, relayIds, submitted, runId) {
1413
1422
  const facade = probeLocalAgent(this.ctx);
1414
1423
  if (facade === void 0) {
1415
1424
  this.ctx.logger.warn(`room: cannot dispatch to "${member.name}": local-agent facade unavailable`);
@@ -1418,9 +1427,23 @@ var DispatchEngine = class {
1418
1427
  const provider = member.provider ?? "";
1419
1428
  let startedAt;
1420
1429
  const controlled = facade.supportsMemberConfiguration?.(provider) === true;
1421
- const admission = controlled ? { onAdmitted: async () => {
1422
- startedAt = await admitted();
1423
- } } : {};
1430
+ const admission = {
1431
+ ...controlled ? { onAdmitted: async () => {
1432
+ startedAt = await admitted();
1433
+ } } : {},
1434
+ onProgress: (event) => {
1435
+ if (event.kind !== "settled") return;
1436
+ if (room.snapshotEvents().filter((row) => row.type === "room/run-state" && row.data.runId === runId).at(-1)?.type !== "room/run-state") return;
1437
+ const total = event.usage?.totalTokens ?? (event.usage === void 0 ? void 0 : event.usage.inputTokens + event.usage.outputTokens);
1438
+ room.append("room/execution-metadata", {
1439
+ runId,
1440
+ ...event.observedModel === void 0 ? {} : { model: event.observedModel },
1441
+ ...event.observedEffort === void 0 ? {} : { effort: event.observedEffort },
1442
+ ...total === void 0 || !Number.isFinite(total) || total < 0 ? {} : { tokens: total }
1443
+ });
1444
+ this.ctx.sessions.flush(room).catch((error) => this.ctx.logger.warn(`room: execution metadata flush failed: ${String(error)}`));
1445
+ }
1446
+ };
1424
1447
  if (!controlled) startedAt = await admitted();
1425
1448
  const { prompt, carried } = assemblePrompt(room, member, cursor, text, relayIds);
1426
1449
  const prepared = member.childSessionId !== void 0 && facade.isPreparedMember?.(member.childSessionId) === true;
@@ -1433,14 +1456,14 @@ var DispatchEngine = class {
1433
1456
  }) : member.childSessionId === void 0 ? facade.start(room.id, provider, [{
1434
1457
  type: "text",
1435
1458
  text: prompt
1436
- }], member.model === void 0 && member.cwd === void 0 && !controlled ? void 0 : {
1459
+ }], {
1437
1460
  ...admission,
1438
1461
  ...member.model === void 0 ? {} : { model: member.model },
1439
1462
  ...member.cwd === void 0 ? {} : { cwd: member.cwd }
1440
1463
  }) : facade.resume(room.id, provider, member.childSessionId, [{
1441
1464
  type: "text",
1442
1465
  text: prompt
1443
- }], member.cwd === void 0 && !controlled ? void 0 : {
1466
+ }], {
1444
1467
  ...admission,
1445
1468
  ...member.cwd === void 0 ? {} : { cwd: member.cwd }
1446
1469
  });
@@ -1533,8 +1556,8 @@ var DispatchEngine = class {
1533
1556
  * room host half: the `room` Typert Remote service. (The `room_invite` /
1534
1557
  * `room_task` / `room_message` model tools left this package's profile-root
1535
1558
  * registration in the M4' tool-row split — their definition factories live
1536
- * in ./tool.ts and the companion `@khorsheed/dsh-room-tool` mounts them
1537
- * inside agent-preset compositions.) The room's entire state is the session's `room/*` custom-event
1559
+ * in ./tool-definition.ts and the package's own `./tool` composition entry
1560
+ * mounts them inside agent-preset compositions.) The room's entire state is the session's `room/*` custom-event
1538
1561
  * journal (log-only events — persistence and reload-replay come free, the
1539
1562
  * model never sees them, and harnesses without this plugin replay the session
1540
1563
  * safely); every read folds the journal through the pure replay, and every
@@ -1637,6 +1660,7 @@ let RoomService = (() => {
1637
1660
  let _closeTask_decorators;
1638
1661
  let _setGoal_decorators;
1639
1662
  let _cancel_decorators;
1663
+ let _cancelChild_decorators;
1640
1664
  return class RoomService extends _classSuper {
1641
1665
  static {
1642
1666
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
@@ -1657,6 +1681,7 @@ let RoomService = (() => {
1657
1681
  _closeTask_decorators = [Remote("closeTask")];
1658
1682
  _setGoal_decorators = [Remote("setGoal")];
1659
1683
  _cancel_decorators = [Remote("cancel")];
1684
+ _cancelChild_decorators = [Remote("cancelChild")];
1660
1685
  __esDecorate(this, null, _isRoom_decorators, {
1661
1686
  kind: "method",
1662
1687
  name: "isRoom",
@@ -1844,6 +1869,17 @@ let RoomService = (() => {
1844
1869
  },
1845
1870
  metadata: _metadata
1846
1871
  }, null, _instanceExtraInitializers);
1872
+ __esDecorate(this, null, _cancelChild_decorators, {
1873
+ kind: "method",
1874
+ name: "cancelChild",
1875
+ static: false,
1876
+ private: false,
1877
+ access: {
1878
+ has: (obj) => "cancelChild" in obj,
1879
+ get: (obj) => obj.cancelChild
1880
+ },
1881
+ metadata: _metadata
1882
+ }, null, _instanceExtraInitializers);
1847
1883
  if (_metadata) Object.defineProperty(this, Symbol.metadata, {
1848
1884
  enumerable: true,
1849
1885
  configurable: true,
@@ -3197,8 +3233,15 @@ let RoomService = (() => {
3197
3233
  ok: false,
3198
3234
  error: { code: "member-not-found" }
3199
3235
  };
3236
+ const expected = loaded.state.runs.find((entry) => entry.member === request.name && entry.state === "running");
3237
+ if (request.expectedRunId !== void 0 && expected?.runId !== request.expectedRunId || request.expectedStartedAt !== void 0 && expected?.startedAt !== request.expectedStartedAt) return {
3238
+ ok: true,
3239
+ value: { cancelled: false }
3240
+ };
3200
3241
  const facade = probeLocalAgent(this.ctx);
3201
- if (facade !== void 0 && member.childSessionId !== void 0 ? facade.cancel(member.childSessionId) : false) {
3242
+ const native = member.kind === "main-agent" && expected !== void 0 ? this.ctx.agents.get(loaded.session.id) : void 0;
3243
+ if (native !== void 0) native.cancel({ kind: "user" });
3244
+ if (native !== void 0 || (facade !== void 0 && member.childSessionId !== void 0 ? facade.cancel(member.childSessionId) : false)) {
3202
3245
  const running = loaded.state.runs.find((entry) => entry.member === request.name && entry.state === "running");
3203
3246
  if (running === void 0) return {
3204
3247
  ok: true,
@@ -3208,6 +3251,7 @@ let RoomService = (() => {
3208
3251
  member: request.name,
3209
3252
  state: "cancelled",
3210
3253
  startedAt: running.startedAt,
3254
+ elapsedMs: Math.max(0, Date.now() - running.startedAt),
3211
3255
  ...running.runId === void 0 ? {} : { runId: running.runId }
3212
3256
  });
3213
3257
  for (const task of loaded.state.tasks) if (task.member === request.name && task.status === "in_progress" && (running.runId === void 0 || task.deliveryId === running.runId)) loaded.session.append("room/task-updated", {
@@ -3225,6 +3269,38 @@ let RoomService = (() => {
3225
3269
  value: { cancelled: false }
3226
3270
  };
3227
3271
  }
3272
+ /** Native descendant controls share the host's continuation and cancellation channels. */
3273
+ async cancelChild(request) {
3274
+ const loaded = await this.ensureLive(request.sessionId);
3275
+ if (!loaded.ok) return {
3276
+ ok: false,
3277
+ error: loaded.error
3278
+ };
3279
+ const target = this.ctx.sessions.get(request.childSessionId);
3280
+ const seen = /* @__PURE__ */ new Set([request.childSessionId]);
3281
+ let parent = target?.header.parentSession;
3282
+ while (parent !== void 0 && parent !== request.sessionId && !seen.has(parent)) {
3283
+ seen.add(parent);
3284
+ parent = this.ctx.sessions.get(parent)?.header.parentSession;
3285
+ }
3286
+ if (!target || target.id === request.sessionId || parent !== request.sessionId) return {
3287
+ ok: true,
3288
+ value: { cancelled: false }
3289
+ };
3290
+ const facadeHit = probeLocalAgent(this.ctx)?.cancel(target.id) ?? false;
3291
+ const agent = this.ctx.agents.get(target.id);
3292
+ if (agent) {
3293
+ this.ctx.get("subagents")?.interrupt(target.id, {
3294
+ kind: "user",
3295
+ parentSessionId: target.header.parentSession
3296
+ });
3297
+ agent.cancel({ kind: "user" });
3298
+ }
3299
+ return {
3300
+ ok: true,
3301
+ value: { cancelled: facadeHit || agent !== void 0 }
3302
+ };
3303
+ }
3228
3304
  };
3229
3305
  })();
3230
3306
  //#endregion
@@ -68,6 +68,7 @@ const ROOM_EVENT_TYPES = [
68
68
  "room/dispatch",
69
69
  "room/speech",
70
70
  "room/run-state",
71
+ "room/execution-metadata",
71
72
  "room/relay",
72
73
  "room/relay-resolved",
73
74
  "room/task-added",
@@ -144,6 +145,7 @@ function replay(events) {
144
145
  const tasks = [];
145
146
  const taskById = /* @__PURE__ */ new Map();
146
147
  const runs = /* @__PURE__ */ new Map();
148
+ const executions = /* @__PURE__ */ new Map();
147
149
  let goal;
148
150
  let coordinator;
149
151
  for (const event of events) switch (event.type) {
@@ -185,6 +187,10 @@ function replay(events) {
185
187
  },
186
188
  ...event.data.childSessionId === void 0 ? {} : { childSessionId: event.data.childSessionId }
187
189
  };
190
+ for (const [id, execution] of executions) if (execution.memberId === member.id && execution.childSessionId === void 0 && patched.childSessionId !== void 0) executions.set(id, {
191
+ ...execution,
192
+ childSessionId: patched.childSessionId
193
+ });
188
194
  const rename = event.data.rename;
189
195
  if (rename === void 0 || rename === member.name || byName.has(rename)) {
190
196
  byName.set(member.name, patched);
@@ -310,7 +316,27 @@ function replay(events) {
310
316
  goal = text === "" ? void 0 : text;
311
317
  break;
312
318
  }
319
+ case "room/execution-metadata": {
320
+ const previous = executions.get(event.data.runId);
321
+ if (previous) executions.set(previous.id, {
322
+ ...previous,
323
+ ...event.data
324
+ });
325
+ break;
326
+ }
313
327
  case "room/run-state": {
328
+ const member = byName.get(event.data.member);
329
+ const id = event.data.runId ?? `legacy:${member?.id ?? event.data.member}:${event.data.startedAt}`;
330
+ const previous = executions.get(id);
331
+ executions.set(id, {
332
+ id,
333
+ ...member?.id === void 0 ? {} : { memberId: member.id },
334
+ ...member?.childSessionId === void 0 ? {} : { childSessionId: member.childSessionId },
335
+ ...member?.provider === void 0 ? {} : { provider: member.provider },
336
+ ...previous,
337
+ ...event.data,
338
+ ...event.data.state === "running" || event.data.elapsedMs !== void 0 ? {} : { elapsedMs: Math.max(0, event.time - event.data.startedAt) }
339
+ });
314
340
  const current = runs.get(event.data.member);
315
341
  if (event.data.state !== "running" && event.data.runId !== void 0 && current?.runId !== void 0 && current.runId !== event.data.runId) break;
316
342
  runs.set(event.data.member, {
@@ -331,6 +357,7 @@ function replay(events) {
331
357
  members,
332
358
  relays,
333
359
  tasks,
360
+ ...executions.size === 0 ? {} : { executions: [...executions.values()] },
334
361
  ...plan === void 0 ? {} : { plan },
335
362
  ...deliveries.length === 0 ? {} : { deliveries },
336
363
  runs: [...runs.values()],
package/lib/tool.js CHANGED
@@ -1,6 +1,6 @@
1
- import { i as isRoomLog, r as coordinatorMember } from "./journal-CuRnWk66.js";
1
+ import { i as isRoomLog, r as coordinatorMember } from "./journal-BWL3-i0s.js";
2
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
- //#region lib/types/tool.js
3
+ //#region lib/types/tool-definition.js
4
4
  /**
5
5
  * The room's model-facing tool DEFINITIONS, `room_invite`, `room_task`, and
6
6
  * `room_message`: the main
@@ -18,14 +18,18 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
18
18
  * a message to a member through the same engine path as the human's
19
19
  * `@member text`, minus the user bubble (the caller is the room's own agent).
20
20
  *
21
- * Since the tool-row split (M4') the core no longer REGISTERS these tools:
22
- * the factories are consumed by the companion `@khorsheed/dsh-room-tool`,
23
- * which mounts the tool row inside agent-preset compositions (session-granted,
24
- * never at the profile root). The definitions are therefore returned
25
- * UNTAGGED — the registering package applies its own tool-origin tag
26
- * (AGENTS.md § Tool origin tagging — the owner is whichever package mounts
27
- * the row).
28
- * @module @khorsheed/dsh-room/tool
21
+ * Since the tool-row split (M4') the core never REGISTERS these tools: the
22
+ * factories are consumed by the package's `./tool` subpath row
23
+ * ({@link file://./tool.ts}), which mounts them inside agent-preset
24
+ * compositions (session-granted, never at the profile root). The definitions
25
+ * are therefore returned UNTAGGED — the registering row applies its own
26
+ * tool-origin tag (AGENTS.md § Tool origin tagging — the owner is whichever
27
+ * package mounts the row).
28
+ *
29
+ * Internal module: not on the package's exports map (consumers reach it only
30
+ * through `./src/*`).
31
+ *
32
+ * @module @khorsheed/dsh-room
29
33
  */
30
34
  /**
31
35
  * Build the room_invite tool definition bound to the room service.
@@ -347,4 +351,52 @@ function roomPlanTool(backend) {
347
351
  });
348
352
  }
349
353
  //#endregion
350
- export { roomInviteTool, roomMessageTool, roomPlanTool, roomReadTool, roomTaskTool };
354
+ //#region lib/types/tool.js
355
+ const PACKAGE_NAME = "@khorsheed/dsh-room";
356
+ /**
357
+ * Tag the model-visible tools with their origin (AGENTS.md § Tool origin
358
+ * tagging; seam S12): the capability catalog reads this `Symbol.for`-keyed
359
+ * tag back through `ctx.tools.get()`, so the room tools attribute to THIS
360
+ * package (the row the preset mounts), not to the service core. The tag is
361
+ * host-side only and never travels on the model wire. Generic form (not
362
+ * `typeof defineTool`, which trips typert TS2321).
363
+ */
364
+ const definePluginTool = (def) => Object.assign(def, { [Symbol.for("dsh.tool.origin")]: {
365
+ channel: "plugin",
366
+ owner: PACKAGE_NAME
367
+ } });
368
+ /** Cordis plugin name used by loader diagnostics. */
369
+ const name = "room-tool";
370
+ /**
371
+ * The `room` core is a declared inject (the owning-family companion
372
+ * exception to the community-service probe rule): a preset's standing scope
373
+ * mounts at registry-activation time, BEFORE the profile's later bundle rows
374
+ * provide the core, so a one-shot ctx.get probe at apply saw ABSENT there and
375
+ * nothing re-ran the row (rc.1 boot order; 3080 production 2026-09-27). The
376
+ * declared inject pends the row until the core provides, then the body
377
+ * applies. The tools registry still joins through deferred injection so its
378
+ * mount order cannot strand the registrations.
379
+ */
380
+ const inject = ["room"];
381
+ /**
382
+ * Plugin body: register the room tools. The declared `room` inject pends the
383
+ * row until the core provides, so mount order can no longer strand it; the
384
+ * in-body guard stays as the defensive direct-call path.
385
+ * @param ctx - Cordis context (the preset's agent-plane mount).
386
+ */
387
+ function apply(ctx) {
388
+ const service = ctx.get("room");
389
+ if (service === void 0) {
390
+ ctx.logger.info(`${name}: the global room service is absent — the room tools are not registered`);
391
+ return;
392
+ }
393
+ ctx.inject(["tools"], (toolsCtx) => {
394
+ if (typeof service.commandPlan === "function") toolsCtx.effect(() => toolsCtx.tools.register(definePluginTool(roomPlanTool(service))), "room-tool: room_plan tool");
395
+ if (typeof service.readRoomContext === "function") toolsCtx.effect(() => toolsCtx.tools.register(definePluginTool(roomReadTool(service))), "room-tool: room_read tool");
396
+ toolsCtx.effect(() => toolsCtx.tools.register(definePluginTool(roomInviteTool(service))), "room-tool: room_invite tool");
397
+ toolsCtx.effect(() => toolsCtx.tools.register(definePluginTool(roomTaskTool(service))), "room-tool: room_task tool");
398
+ toolsCtx.effect(() => toolsCtx.tools.register(definePluginTool(roomMessageTool(service))), "room-tool: room_message tool");
399
+ });
400
+ }
401
+ //#endregion
402
+ export { apply, inject, name };