@mclawnet/agent 0.6.32 → 0.6.34

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 (48) hide show
  1. package/dist/__tests__/backend-adapter-types.test.d.ts +2 -0
  2. package/dist/__tests__/backend-adapter-types.test.d.ts.map +1 -0
  3. package/dist/__tests__/backend-factory.test.d.ts +2 -0
  4. package/dist/__tests__/backend-factory.test.d.ts.map +1 -0
  5. package/dist/__tests__/normalize-backend-output.test.d.ts +2 -0
  6. package/dist/__tests__/normalize-backend-output.test.d.ts.map +1 -0
  7. package/dist/__tests__/session-manager-backend.test.d.ts +2 -0
  8. package/dist/__tests__/session-manager-backend.test.d.ts.map +1 -0
  9. package/dist/__tests__/session-manager-permission.test.d.ts +2 -0
  10. package/dist/__tests__/session-manager-permission.test.d.ts.map +1 -0
  11. package/dist/__tests__/templates-roles-bridge.test.d.ts +2 -0
  12. package/dist/__tests__/templates-roles-bridge.test.d.ts.map +1 -0
  13. package/dist/backend-adapter.d.ts +102 -10
  14. package/dist/backend-adapter.d.ts.map +1 -1
  15. package/dist/backend-factory-RUYUBJVF.js +9 -0
  16. package/dist/backend-factory-RUYUBJVF.js.map +1 -0
  17. package/dist/backend-factory.d.ts +19 -0
  18. package/dist/backend-factory.d.ts.map +1 -0
  19. package/dist/checkpoint.d.ts +1 -1
  20. package/dist/checkpoint.d.ts.map +1 -1
  21. package/dist/{chunk-QPLG5WHL.js → chunk-2JDX6XFD.js} +445 -84
  22. package/dist/chunk-2JDX6XFD.js.map +1 -0
  23. package/dist/chunk-MFXF77LG.js +49 -0
  24. package/dist/chunk-MFXF77LG.js.map +1 -0
  25. package/dist/dist-VLBO5CT3.js +775 -0
  26. package/dist/dist-VLBO5CT3.js.map +1 -0
  27. package/dist/fs-handler.d.ts +1 -1
  28. package/dist/fs-handler.d.ts.map +1 -1
  29. package/dist/hub-connection.d.ts.map +1 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +7 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/normalize-backend-output.d.ts +5 -0
  35. package/dist/normalize-backend-output.d.ts.map +1 -0
  36. package/dist/session-manager.d.ts +35 -4
  37. package/dist/session-manager.d.ts.map +1 -1
  38. package/dist/start.d.ts +2 -0
  39. package/dist/start.d.ts.map +1 -1
  40. package/dist/start.js +1 -1
  41. package/dist/swarm-control-dispatch.d.ts +33 -1
  42. package/dist/swarm-control-dispatch.d.ts.map +1 -1
  43. package/dist/swarm-session-bridge.d.ts +3 -3
  44. package/dist/swarm-session-bridge.d.ts.map +1 -1
  45. package/dist/templates-roles-bridge.d.ts +14 -0
  46. package/dist/templates-roles-bridge.d.ts.map +1 -0
  47. package/package.json +13 -10
  48. package/dist/chunk-QPLG5WHL.js.map +0 -1
@@ -3,8 +3,8 @@ import {
3
3
  } from "./chunk-PJ5M6Q36.js";
4
4
 
5
5
  // src/start.ts
6
- import { homedir as homedir4 } from "os";
7
- import { join as join5 } from "path";
6
+ import { homedir as homedir5 } from "os";
7
+ import { join as join6 } from "path";
8
8
 
9
9
  // src/hub-connection.ts
10
10
  import { hostname as osHostname } from "os";
@@ -168,10 +168,10 @@ async function handleListHistorySessions(workDir) {
168
168
  sessions.sort((a, b) => (b.lastModified ?? 0) - (a.lastModified ?? 0));
169
169
  return { workDir, sessions };
170
170
  }
171
- async function handleLoadSessionHistory(workDir, claudeSessionId, opts) {
171
+ async function handleLoadSessionHistory(workDir, backendSessionId, opts) {
172
172
  const projectsDir = getClaudeProjectsDir();
173
173
  const projectFolder = pathToProjectFolder(workDir);
174
- const filePath = join(projectsDir, projectFolder, `${claudeSessionId}.jsonl`);
174
+ const filePath = join(projectsDir, projectFolder, `${backendSessionId}.jsonl`);
175
175
  if (!existsSync(filePath)) {
176
176
  return { messages: [], oldestSeq: 0, hasMore: false };
177
177
  }
@@ -418,8 +418,9 @@ import { randomUUID } from "crypto";
418
418
  import { createLogger } from "@mclawnet/logger";
419
419
  import { InboxStore as InboxStore2 } from "@mclawnet/swarm";
420
420
  var log = createLogger({ module: "agent/swarm-control" });
421
+ var MAX_ROLES_PER_SWARM = 20;
421
422
  async function handleSwarmControl(coord, msg, opts) {
422
- if (!msg || msg.type !== "swarm_spawn" && msg.type !== "swarm_resume") {
423
+ if (!msg || msg.type !== "swarm_spawn" && msg.type !== "swarm_resume" && msg.type !== "swarm_add_role") {
423
424
  return { ok: false, error: "unknown control type" };
424
425
  }
425
426
  if (msg.type === "swarm_resume") {
@@ -433,10 +434,54 @@ async function handleSwarmControl(coord, msg, opts) {
433
434
  return { ok: false, error };
434
435
  }
435
436
  }
437
+ if (msg.type === "swarm_add_role") {
438
+ const swarm = coord.getSwarm?.(msg.swarmId);
439
+ if (!swarm) {
440
+ return { ok: false, error: `swarm ${msg.swarmId} not found` };
441
+ }
442
+ const effectiveStatus = swarm.status ?? "unknown";
443
+ if (effectiveStatus !== "running") {
444
+ return {
445
+ ok: false,
446
+ error: `swarm ${msg.swarmId} is ${effectiveStatus}, not running \u2014 add not allowed`
447
+ };
448
+ }
449
+ if (msg.roleName === "queen") {
450
+ return { ok: false, error: "cannot add a second queen to a running swarm" };
451
+ }
452
+ const roleCount = swarm.roles?.size ?? 0;
453
+ if (roleCount >= MAX_ROLES_PER_SWARM) {
454
+ return {
455
+ ok: false,
456
+ error: `member cap reached (${MAX_ROLES_PER_SWARM}) \u2014 destroy or pause some members first`
457
+ };
458
+ }
459
+ if (!coord.spawnRole) {
460
+ return { ok: false, error: "coordinator does not expose spawnRole" };
461
+ }
462
+ try {
463
+ const role = await coord.spawnRole(msg.swarmId, msg.roleName, {
464
+ backendOverride: msg.backend,
465
+ sandboxOverride: msg.sandbox,
466
+ customPrompt: msg.customPrompt,
467
+ taskPrompt: msg.taskPrompt,
468
+ isDynamicAdd: true
469
+ });
470
+ log.info(
471
+ { swarmId: msg.swarmId, roleName: msg.roleName, instanceId: role.instanceId },
472
+ "swarm_add_role: spawned"
473
+ );
474
+ return { ok: true, swarmId: msg.swarmId, instanceId: role.instanceId };
475
+ } catch (err) {
476
+ const error = err instanceof Error ? err.message : String(err);
477
+ log.error({ err, swarmId: msg.swarmId, roleName: msg.roleName }, "swarm_add_role failed");
478
+ return { ok: false, error };
479
+ }
480
+ }
436
481
  const workDir = msg.workDir ?? opts.defaultWorkDir;
437
482
  const swarmId = msg.swarmId ?? randomUUID();
438
483
  try {
439
- await coord.create(swarmId, { workDir, templateName: msg.teamName });
484
+ await coord.create(swarmId, { workDir, templateName: msg.teamName, displayName: msg.displayName });
440
485
  } catch (err) {
441
486
  const error = err instanceof Error ? err.message : String(err);
442
487
  log.error({ err, teamName: msg.teamName }, "swarm_spawn: create failed");
@@ -698,10 +743,10 @@ var HubConnection = class {
698
743
  }
699
744
  if (msg.type === "load_session_history") {
700
745
  log2.info(
701
- { workDir: msg.workDir, claudeSessionId: msg.claudeSessionId, before: msg.before, limit: msg.limit },
746
+ { workDir: msg.workDir, backendSessionId: msg.backendSessionId, before: msg.before, limit: msg.limit },
702
747
  "load_session_history"
703
748
  );
704
- handleLoadSessionHistory(msg.workDir, msg.claudeSessionId, { before: msg.before, limit: msg.limit }).then((result) => {
749
+ handleLoadSessionHistory(msg.workDir, msg.backendSessionId, { before: msg.before, limit: msg.limit }).then((result) => {
705
750
  this.send({ type: "session_history_result", requestId: msg.requestId, ...result });
706
751
  }).catch((err) => {
707
752
  log2.error({ err, workDir: msg.workDir }, "load_session_history failed");
@@ -939,6 +984,41 @@ var HubConnection = class {
939
984
  });
940
985
  return true;
941
986
  }
987
+ if (msg.type === "projects.swarm_add_role" && this.swarmCoordinator) {
988
+ const coord = this.swarmCoordinator;
989
+ const requestId = msg.requestId;
990
+ handleSwarmControl(
991
+ coord,
992
+ {
993
+ type: "swarm_add_role",
994
+ swarmId: msg.swarmId,
995
+ roleName: msg.roleName,
996
+ ...msg.backend ? { backend: msg.backend } : {},
997
+ ...msg.sandbox ? { sandbox: msg.sandbox } : {},
998
+ ...msg.customPrompt ? { customPrompt: msg.customPrompt } : {},
999
+ ...msg.taskPrompt ? { taskPrompt: msg.taskPrompt } : {}
1000
+ },
1001
+ { defaultWorkDir: process.cwd(), home: process.env.CLAWNET_HOME }
1002
+ ).then((result) => {
1003
+ this.send({
1004
+ type: "projects.swarm_add_role_result",
1005
+ requestId,
1006
+ ok: result.ok,
1007
+ ...result.swarmId ? { swarmId: result.swarmId } : {},
1008
+ ...result.instanceId ? { instanceId: result.instanceId } : {},
1009
+ ...result.error ? { error: result.error } : {}
1010
+ });
1011
+ }).catch((err) => {
1012
+ log2.error({ err, swarmId: msg.swarmId }, "projects.swarm_add_role crashed");
1013
+ this.send({
1014
+ type: "projects.swarm_add_role_result",
1015
+ requestId,
1016
+ ok: false,
1017
+ error: err instanceof Error ? err.message : String(err)
1018
+ });
1019
+ });
1020
+ return true;
1021
+ }
942
1022
  if (msg.type === "swarm.execute" && this.swarmCoordinator) {
943
1023
  const { sessionId, content, workDir, targetInstance, crewConfig } = msg;
944
1024
  if (this.swarmCoordinator.hasSwarm(sessionId)) {
@@ -955,8 +1035,9 @@ var HubConnection = class {
955
1035
  this.finishedSwarms.delete(sessionId);
956
1036
  const templateName = crewConfig.templateName;
957
1037
  const roles = crewConfig.roles;
1038
+ const displayName = crewConfig.displayName;
958
1039
  log2.info({ sessionId, templateName, rolesCount: roles?.length }, "swarm.execute: continuing finished swarm");
959
- this.swarmCoordinator.create(sessionId, { workDir, templateName, roles, task: content, isContinuation: true }).catch((err) => {
1040
+ this.swarmCoordinator.create(sessionId, { workDir, templateName, displayName, roles, task: content, isContinuation: true }).catch((err) => {
960
1041
  this.send({
961
1042
  type: "session.error",
962
1043
  sessionId,
@@ -974,8 +1055,9 @@ var HubConnection = class {
974
1055
  } else if (crewConfig?.templateName || crewConfig?.roles) {
975
1056
  const templateName = crewConfig.templateName;
976
1057
  const roles = crewConfig.roles;
1058
+ const displayName = crewConfig.displayName;
977
1059
  log2.info({ sessionId, templateName, rolesCount: roles?.length }, "swarm.execute: creating new swarm");
978
- this.swarmCoordinator.create(sessionId, { workDir, templateName, roles, task: content }).catch((err) => {
1060
+ this.swarmCoordinator.create(sessionId, { workDir, templateName, displayName, roles, task: content }).catch((err) => {
979
1061
  this.send({
980
1062
  type: "session.error",
981
1063
  sessionId,
@@ -1008,11 +1090,11 @@ var HubConnection = class {
1008
1090
  return true;
1009
1091
  }
1010
1092
  if (msg.type === "claude.execute") {
1011
- const { sessionId, content, workDir, claudeSessionId, useBrainCore } = msg;
1093
+ const { sessionId, content, workDir, backendSessionId, useBrainCore } = msg;
1012
1094
  const sm = this.sessionManager;
1013
1095
  const spawnAndSend = (resumeId, label, maxOutputTokens) => {
1014
1096
  log2.info(
1015
- { sessionId, claudeSessionId: resumeId, workDir, label, maxOutputTokens },
1097
+ { sessionId, backendSessionId: resumeId, workDir, label, maxOutputTokens },
1016
1098
  "claude.execute: spawning"
1017
1099
  );
1018
1100
  log2.debug({ sessionId, ...previewFields(content) }, "claude.execute: input");
@@ -1041,9 +1123,9 @@ var HubConnection = class {
1041
1123
  sm.sendUserInput(sessionId, content).catch((err) => {
1042
1124
  log2.warn({ err, sessionId }, "sendUserInput failed");
1043
1125
  });
1044
- } else if (sm.hasSession(sessionId) && claudeSessionId) {
1126
+ } else if (sm.hasSession(sessionId) && backendSessionId) {
1045
1127
  const recommendedMax = sm.getRecommendedMaxOutputTokens(sessionId);
1046
- log2.warn({ sessionId, claudeSessionId }, "claude.execute: session unhealthy, recreating with --resume");
1128
+ log2.warn({ sessionId, backendSessionId }, "claude.execute: session unhealthy, recreating with --resume");
1047
1129
  this.send({
1048
1130
  type: "session.died",
1049
1131
  sessionId,
@@ -1051,10 +1133,14 @@ var HubConnection = class {
1051
1133
  });
1052
1134
  sm.abortSession(sessionId).catch((err) => {
1053
1135
  log2.warn({ sessionId, err: err instanceof Error ? err.message : String(err) }, "abortSession failed during unhealthy fallback, proceeding to respawn");
1054
- }).then(() => spawnAndSend(claudeSessionId, "unhealthy_fallback_resume", recommendedMax));
1055
- } else if (claudeSessionId) {
1056
- spawnAndSend(claudeSessionId, "fresh_resume", sm.getRecommendedMaxOutputTokens(sessionId));
1136
+ }).then(() => spawnAndSend(backendSessionId, "unhealthy_fallback_resume", recommendedMax));
1137
+ } else if (backendSessionId) {
1138
+ spawnAndSend(backendSessionId, "fresh_resume", sm.getRecommendedMaxOutputTokens(sessionId));
1057
1139
  } else {
1140
+ log2.warn(
1141
+ { sessionId, workDir },
1142
+ "claude.execute: entering brand_new spawn path \u2014 no backendSessionId from hub; if this sessionId already has prior turns in db, upstream hub\u2192agent hydration is broken"
1143
+ );
1058
1144
  spawnAndSend(void 0, "brand_new", void 0);
1059
1145
  }
1060
1146
  return true;
@@ -1086,11 +1172,11 @@ var HubConnection = class {
1086
1172
  workDir: msg.workDir,
1087
1173
  resumeId: msg.resumeId,
1088
1174
  roleId: DEFAULT_ASSISTANT_ROLE_ID
1089
- }).then((claudeSessionId) => {
1175
+ }).then((backendSessionId) => {
1090
1176
  this.send({
1091
1177
  type: "session.created",
1092
1178
  sessionId: msg.sessionId,
1093
- claudeSessionId
1179
+ backendSessionId
1094
1180
  });
1095
1181
  }).catch((err) => {
1096
1182
  this.send({
@@ -1117,6 +1203,14 @@ var HubConnection = class {
1117
1203
  });
1118
1204
  return true;
1119
1205
  }
1206
+ if (msg.type === "client.permission_decision") {
1207
+ const { sessionId, requestId, decision, reason } = msg;
1208
+ log2.info({ sessionId, requestId, decision }, "client.permission_decision");
1209
+ this.sessionManager.respondToPermission(sessionId, { callId: requestId, decision, reason }).catch((err) => {
1210
+ log2.warn({ err, sessionId, requestId }, "respondToPermission failed");
1211
+ });
1212
+ return true;
1213
+ }
1120
1214
  return false;
1121
1215
  }
1122
1216
  // ── Internal helpers ─────────────────────────────────────────────
@@ -1710,6 +1804,9 @@ function makeRealSwarmStarter(_deps) {
1710
1804
 
1711
1805
  // src/session-manager.ts
1712
1806
  import { createLogger as createLogger5, previewFields as previewFields2 } from "@mclawnet/logger";
1807
+ import { existsSync as existsSync4 } from "fs";
1808
+ import { homedir as homedir4 } from "os";
1809
+ import { join as join4 } from "path";
1713
1810
  import {
1714
1811
  buildMemorySection,
1715
1812
  EmbeddingService,
@@ -2076,6 +2173,68 @@ function isPidAlive(pid) {
2076
2173
  }
2077
2174
  }
2078
2175
 
2176
+ // src/normalize-backend-output.ts
2177
+ function normalizeBackendOutput(data, logger) {
2178
+ if (data === null || typeof data !== "object") return data;
2179
+ const d = data;
2180
+ if (typeof d.type === "string" && d.message) return data;
2181
+ if (typeof d.kind !== "string") return data;
2182
+ const envelope = data;
2183
+ switch (envelope.kind) {
2184
+ case "assistant_text":
2185
+ return {
2186
+ type: "assistant",
2187
+ message: {
2188
+ content: [{ type: "text", text: envelope.text ?? "" }]
2189
+ }
2190
+ };
2191
+ case "tool_use":
2192
+ return {
2193
+ type: "assistant",
2194
+ message: {
2195
+ content: [
2196
+ {
2197
+ type: "tool_use",
2198
+ id: envelope.callId ?? "",
2199
+ name: envelope.toolName ?? "",
2200
+ input: envelope.input
2201
+ }
2202
+ ]
2203
+ }
2204
+ };
2205
+ case "tool_result":
2206
+ return {
2207
+ type: "assistant",
2208
+ message: {
2209
+ content: [
2210
+ {
2211
+ type: "tool_result",
2212
+ tool_use_id: envelope.callId ?? "",
2213
+ content: typeof envelope.output === "string" ? envelope.output : JSON.stringify(envelope.output),
2214
+ is_error: Boolean(envelope.isError)
2215
+ }
2216
+ ]
2217
+ }
2218
+ };
2219
+ case "system":
2220
+ return null;
2221
+ case "raw":
2222
+ logger?.warn(
2223
+ { backend: envelope.backend, payload: envelope.payload },
2224
+ "normalize-backend-output: unmapped frame dropped"
2225
+ );
2226
+ return null;
2227
+ default: {
2228
+ const exhaustive = envelope;
2229
+ logger?.warn(
2230
+ { envelope: exhaustive },
2231
+ "normalize-backend-output: unknown kind"
2232
+ );
2233
+ return null;
2234
+ }
2235
+ }
2236
+ }
2237
+
2079
2238
  // src/session-manager.ts
2080
2239
  var sharedEmbeddingService = null;
2081
2240
  function getSharedEmbeddingService() {
@@ -2152,6 +2311,23 @@ var SessionManager = class {
2152
2311
  */
2153
2312
  recoveredLadderIndex = /* @__PURE__ */ new Map();
2154
2313
  adapter;
2314
+ /**
2315
+ * Per-process adapter override. Populated by `createSession` when
2316
+ * `options.backend` is set and `resolveAdapter` returns a non-default
2317
+ * adapter. All adapter calls (send/stop/onOutput/...) consult
2318
+ * `getAdapterFor(process)` instead of `this.adapter` directly.
2319
+ */
2320
+ processAdapters = /* @__PURE__ */ new WeakMap();
2321
+ /**
2322
+ * Optional resolver for per-spawn alternate backends. Wired by the agent
2323
+ * host to `createBackendAdapter` so role.backend can pick codex/claude.
2324
+ * When omitted, options.backend is ignored and `this.adapter` is used.
2325
+ */
2326
+ resolveAdapter;
2327
+ /** Resolve the adapter that owns a particular spawned process. */
2328
+ getAdapterFor(proc) {
2329
+ return this.processAdapters.get(proc) ?? this.adapter;
2330
+ }
2155
2331
  onOutput;
2156
2332
  onTurnComplete;
2157
2333
  onSessionError;
@@ -2165,6 +2341,7 @@ var SessionManager = class {
2165
2341
  * swarm-role exits to SwarmCoordinator.handleRoleCrashed.
2166
2342
  */
2167
2343
  onSessionExit;
2344
+ onPermissionRequest;
2168
2345
  // PR-A: classifies a sessionId as 'chat' or 'swarm-role'. Injected by
2169
2346
  // start.ts via SwarmCoordinator.isSwarmSession to keep SessionManager
2170
2347
  // independent of the swarm package. Defaults to 'chat' if absent — safe
@@ -2173,12 +2350,14 @@ var SessionManager = class {
2173
2350
  classify;
2174
2351
  constructor(options) {
2175
2352
  this.adapter = options.adapter;
2353
+ this.resolveAdapter = options.resolveAdapter;
2176
2354
  this.onOutput = options.onOutput;
2177
2355
  this.onTurnComplete = options.onTurnComplete;
2178
2356
  this.onSessionError = options.onSessionError;
2179
2357
  this.onSessionStarted = options.onSessionStarted;
2180
2358
  this.onBeforeClose = options.onBeforeClose;
2181
2359
  this.onSessionExit = options.onSessionExit;
2360
+ this.onPermissionRequest = options.onPermissionRequest;
2182
2361
  this.classify = options.classify ?? (() => "chat");
2183
2362
  this.checkpointPath = options.checkpointPath ?? null;
2184
2363
  this.checkpointDebounceMs = options.checkpointDebounceMs ?? 5e3;
@@ -2234,7 +2413,37 @@ ${notice.text}`;
2234
2413
  options.systemPrompt = options.systemPrompt ? `${options.systemPrompt}${ideaHint}` : ideaHint.trimStart();
2235
2414
  }
2236
2415
  try {
2237
- const process2 = await this.adapter.spawn(options);
2416
+ let spawnAdapter = this.adapter;
2417
+ if (options.backend && this.resolveAdapter) {
2418
+ const resolved = await this.resolveAdapter(options.backend);
2419
+ log5.info(
2420
+ {
2421
+ sessionId: options.sessionId,
2422
+ requested: options.backend,
2423
+ resolvedType: resolved.type,
2424
+ defaultType: this.adapter.type,
2425
+ willSwitch: resolved.type !== this.adapter.type
2426
+ },
2427
+ "createSession: adapter resolved"
2428
+ );
2429
+ if (resolved.type !== this.adapter.type) {
2430
+ spawnAdapter = resolved;
2431
+ }
2432
+ } else {
2433
+ log5.info(
2434
+ {
2435
+ sessionId: options.sessionId,
2436
+ requestedBackend: options.backend,
2437
+ hasResolver: !!this.resolveAdapter,
2438
+ defaultType: this.adapter.type
2439
+ },
2440
+ "createSession: using default adapter"
2441
+ );
2442
+ }
2443
+ const process2 = await spawnAdapter.spawn(options);
2444
+ if (spawnAdapter !== this.adapter) {
2445
+ this.processAdapters.set(process2, spawnAdapter);
2446
+ }
2238
2447
  this.sessions.set(options.sessionId, process2);
2239
2448
  const now = Date.now();
2240
2449
  const kind = this.classify(options.sessionId);
@@ -2253,7 +2462,9 @@ ${notice.text}`;
2253
2462
  });
2254
2463
  this.recoveredLadderIndex.delete(options.sessionId);
2255
2464
  this.scheduleCheckpoint();
2256
- this.adapter.onOutput(process2, (data) => {
2465
+ spawnAdapter.onOutput(process2, (rawData) => {
2466
+ const data = normalizeBackendOutput(rawData, log5);
2467
+ if (data === null) return;
2257
2468
  const msg = data;
2258
2469
  if (msg?.type === "assistant" && msg?.message?.content) {
2259
2470
  const buf = this.conversationBuffer.get(options.sessionId) ?? [];
@@ -2265,7 +2476,7 @@ ${notice.text}`;
2265
2476
  }
2266
2477
  this.onOutput(options.sessionId, data);
2267
2478
  });
2268
- this.adapter.onTurnComplete?.(process2, (info) => {
2479
+ spawnAdapter.onTurnComplete?.(process2, (info) => {
2269
2480
  const meta = this.sessionMeta.get(options.sessionId);
2270
2481
  if (meta) {
2271
2482
  meta.lastActivityAt = Date.now();
@@ -2274,11 +2485,11 @@ ${notice.text}`;
2274
2485
  this.activelyExecuting.delete(options.sessionId);
2275
2486
  this.onTurnComplete(options.sessionId, info);
2276
2487
  });
2277
- this.adapter.onError?.(process2, (error) => {
2488
+ spawnAdapter.onError?.(process2, (error) => {
2278
2489
  this.activelyExecuting.delete(options.sessionId);
2279
2490
  this.onSessionError(options.sessionId, error.message);
2280
2491
  });
2281
- this.adapter.onTokenBudgetWarning?.(process2, (info) => {
2492
+ spawnAdapter.onTokenBudgetWarning?.(process2, (info) => {
2282
2493
  const meta = this.sessionMeta.get(options.sessionId);
2283
2494
  if (!meta) return;
2284
2495
  const currentIndex = clampLadderIndex(meta.currentLadderIndex);
@@ -2311,7 +2522,7 @@ ${notice.text}`;
2311
2522
  this.scheduleCheckpoint();
2312
2523
  });
2313
2524
  if (this.onSessionStarted) {
2314
- this.adapter.onSessionStarted?.(process2, (info) => {
2525
+ spawnAdapter.onSessionStarted?.(process2, (info) => {
2315
2526
  if (this.aborting.has(options.sessionId)) {
2316
2527
  log5.debug(
2317
2528
  { sessionId: options.sessionId },
@@ -2323,12 +2534,45 @@ ${notice.text}`;
2323
2534
  return;
2324
2535
  }
2325
2536
  const meta = this.sessionMeta.get(options.sessionId);
2326
- if (meta) meta.claudeSessionId = info.claudeSessionId;
2327
- this.scheduleCheckpoint();
2328
- this.onSessionStarted(options.sessionId, info);
2537
+ const sid = info.backendSessionId;
2538
+ let sidIsTrustworthy = true;
2539
+ const workDir = meta?.workDir;
2540
+ const isClaudeAdapter = spawnAdapter.type === "claude-code";
2541
+ if (isClaudeAdapter && workDir && sid) {
2542
+ const encoded = workDir.replace(/\//g, "-");
2543
+ const jsonlPath = join4(
2544
+ homedir4(),
2545
+ ".claude",
2546
+ "projects",
2547
+ encoded,
2548
+ `${sid}.jsonl`
2549
+ );
2550
+ if (!existsSync4(jsonlPath)) {
2551
+ log5.warn(
2552
+ {
2553
+ sessionId: options.sessionId,
2554
+ backendSessionId: sid,
2555
+ jsonlPath
2556
+ },
2557
+ "session_started reports backendSessionId whose jsonl is missing on disk \u2014 refusing to persist to avoid ghost-sid propagation"
2558
+ );
2559
+ sidIsTrustworthy = false;
2560
+ }
2561
+ }
2562
+ if (meta && sidIsTrustworthy) meta.backendSessionId = sid;
2563
+ if (sidIsTrustworthy) this.scheduleCheckpoint();
2564
+ this.onSessionStarted(options.sessionId, {
2565
+ backendSessionId: sid
2566
+ });
2567
+ });
2568
+ }
2569
+ if (this.onPermissionRequest) {
2570
+ spawnAdapter.onPermissionRequest?.(process2, (req) => {
2571
+ if (this.sessions.get(options.sessionId) !== process2) return;
2572
+ this.onPermissionRequest(options.sessionId, req);
2329
2573
  });
2330
2574
  }
2331
- this.adapter.onExit?.(process2, (code) => {
2575
+ spawnAdapter.onExit?.(process2, (code) => {
2332
2576
  if (this.sessions.get(options.sessionId) === process2) {
2333
2577
  const expected = this.expectedExits.delete(options.sessionId);
2334
2578
  this.sessions.delete(options.sessionId);
@@ -2383,7 +2627,7 @@ ${notice.text}`;
2383
2627
  { sessionId, ...previewFields2(input) },
2384
2628
  "sendInput \u2192 backend stdin"
2385
2629
  );
2386
- this.adapter.send(process2, input);
2630
+ this.getAdapterFor(process2).send(process2, input);
2387
2631
  }
2388
2632
  /**
2389
2633
  * User-input variant of sendInput: best-effort prepend a `<memory-context>`
@@ -2429,11 +2673,24 @@ ${content}`;
2429
2673
  this.activelyExecuting.delete(sessionId);
2430
2674
  this.scheduleCheckpoint();
2431
2675
  try {
2432
- await this.adapter.stop(process2);
2676
+ await this.getAdapterFor(process2).stop(process2);
2433
2677
  } finally {
2434
2678
  this.aborting.delete(sessionId);
2435
2679
  }
2436
2680
  }
2681
+ /**
2682
+ * M3.S2b — Deliver a permission decision back to the adapter for the named
2683
+ * session. No-ops silently when the adapter doesn't implement the optional
2684
+ * `respondToPermission` (e.g. claude-adapter today). Throws when the session
2685
+ * is unknown so callers don't silently lose decisions to dead sessions.
2686
+ */
2687
+ async respondToPermission(sessionId, decision) {
2688
+ const process2 = this.sessions.get(sessionId);
2689
+ if (!process2) {
2690
+ throw new Error(`Session not found: ${sessionId}`);
2691
+ }
2692
+ await this.getAdapterFor(process2).respondToPermission?.(process2, decision);
2693
+ }
2437
2694
  async closeSession(sessionId) {
2438
2695
  const process2 = this.sessions.get(sessionId);
2439
2696
  if (!process2) return;
@@ -2448,7 +2705,7 @@ ${content}`;
2448
2705
  this.sessionMeta.delete(sessionId);
2449
2706
  this.activelyExecuting.delete(sessionId);
2450
2707
  this.scheduleCheckpoint();
2451
- await this.adapter.stop(process2);
2708
+ await this.getAdapterFor(process2).stop(process2);
2452
2709
  }
2453
2710
  async closeAll() {
2454
2711
  this.stopIdleSweeper();
@@ -2464,7 +2721,7 @@ ${content}`;
2464
2721
  this.sessions.delete(sessionId);
2465
2722
  this.sessionMeta.delete(sessionId);
2466
2723
  this.activelyExecuting.delete(sessionId);
2467
- await this.adapter.stop(process2).catch(() => {
2724
+ await this.getAdapterFor(process2).stop(process2).catch(() => {
2468
2725
  });
2469
2726
  }
2470
2727
  );
@@ -2486,7 +2743,7 @@ ${content}`;
2486
2743
  * Stricter than hasSession(): also requires the underlying process to still
2487
2744
  * be alive (not exited, stdin still writable). Callers in the reuse path
2488
2745
  * must use this to avoid sending input into a dead pipe — if it returns
2489
- * false they should fall back to `--resume` against db's claudeSessionId.
2746
+ * false they should fall back to `--resume` against db's backendSessionId.
2490
2747
  */
2491
2748
  isHealthy(sessionId) {
2492
2749
  if (this.aborting.has(sessionId)) return false;
@@ -2656,7 +2913,7 @@ ${content}`;
2656
2913
  sessionId: sid,
2657
2914
  kind: meta.kind,
2658
2915
  workDir: meta.workDir,
2659
- claudeSessionId: meta.claudeSessionId,
2916
+ backendSessionId: meta.backendSessionId,
2660
2917
  agentInstanceId: meta.agentInstanceId,
2661
2918
  startedAt: meta.startedAt,
2662
2919
  lastActivityAt: meta.lastActivityAt,
@@ -2713,16 +2970,16 @@ import { SwarmCoordinator, initRoles } from "@mclawnet/swarm";
2713
2970
  import { TaskStore as TaskStore2 } from "@mclawnet/task";
2714
2971
 
2715
2972
  // src/brain-bridge.ts
2716
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync4 } from "fs";
2717
- import { join as join4 } from "path";
2973
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync4 } from "fs";
2974
+ import { join as join5 } from "path";
2718
2975
  import { createLogger as createLogger6 } from "@mclawnet/logger";
2719
2976
  var log6 = createLogger6({ module: "brain-bridge" });
2720
2977
  var BrainBridge = class {
2721
2978
  constructor(hub, options) {
2722
2979
  this.hub = hub;
2723
2980
  const home = process.env.HOME || process.env.USERPROFILE || "";
2724
- this.brainHome = options?.brainHomePath || process.env.BRAIN_HOME || join4(home, "BrainData");
2725
- this.brainCorePath = options?.brainCorePath || join4(home, ".brain", "BrainCore");
2981
+ this.brainHome = options?.brainHomePath || process.env.BRAIN_HOME || join5(home, "BrainData");
2982
+ this.brainCorePath = options?.brainCorePath || join5(home, ".brain", "BrainCore");
2726
2983
  this.hub.registerNamespace("brain", (msg) => this.handleRequest(msg));
2727
2984
  log6.info(
2728
2985
  { brainHome: this.brainHome, brainCorePath: this.brainCorePath },
@@ -2749,15 +3006,15 @@ var BrainBridge = class {
2749
3006
  }
2750
3007
  }
2751
3008
  checkSetup() {
2752
- if (!existsSync4(this.brainCorePath)) {
3009
+ if (!existsSync5(this.brainCorePath)) {
2753
3010
  return "not_installed";
2754
3011
  }
2755
3012
  const home = process.env.HOME || process.env.USERPROFILE || "";
2756
- const installJson = join4(home, ".brain", "install.json");
2757
- if (!existsSync4(installJson)) {
3013
+ const installJson = join5(home, ".brain", "install.json");
3014
+ if (!existsSync5(installJson)) {
2758
3015
  return "needs_config";
2759
3016
  }
2760
- if (!existsSync4(this.brainHome)) {
3017
+ if (!existsSync5(this.brainHome)) {
2761
3018
  return "needs_config";
2762
3019
  }
2763
3020
  return "ready";
@@ -2765,8 +3022,8 @@ var BrainBridge = class {
2765
3022
  /** Read the most recent daily briefing report */
2766
3023
  async getBriefing(date) {
2767
3024
  const targetDate = date || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2768
- const reportsDir = join4(this.brainHome, "reports", "daily");
2769
- if (!existsSync4(reportsDir)) {
3025
+ const reportsDir = join5(this.brainHome, "reports", "daily");
3026
+ if (!existsSync5(reportsDir)) {
2770
3027
  log6.info({ reportsDir }, "get_briefing: reports dir not found");
2771
3028
  return { briefing: null, actions: [], projects: [], meetings: [], feed: [] };
2772
3029
  }
@@ -2776,7 +3033,7 @@ var BrainBridge = class {
2776
3033
  return { briefing: null, actions: [], projects: [], meetings: [], feed: [] };
2777
3034
  }
2778
3035
  log6.info({ targetDate, file: files[0] }, "get_briefing: reading report");
2779
- const content = readFileSync3(join4(reportsDir, files[0]), "utf-8");
3036
+ const content = readFileSync3(join5(reportsDir, files[0]), "utf-8");
2780
3037
  const tldrMatch = content.match(/## TL;DR\n([\s\S]*?)(?=\n---|\n## )/);
2781
3038
  const tldr = tldrMatch ? tldrMatch[1].trim() : content.slice(0, 200);
2782
3039
  const actions = this.parseActions(content);
@@ -2975,8 +3232,8 @@ var BrainBridge = class {
2975
3232
  if (!recapPath) {
2976
3233
  return { error: "No recap path provided" };
2977
3234
  }
2978
- const fullPath = join4(this.brainHome, recapPath);
2979
- if (!existsSync4(fullPath)) {
3235
+ const fullPath = join5(this.brainHome, recapPath);
3236
+ if (!existsSync5(fullPath)) {
2980
3237
  log6.warn({ fullPath }, "meeting recap file not found");
2981
3238
  return { error: "Recap file not found" };
2982
3239
  }
@@ -3013,7 +3270,7 @@ var BrainBridge = class {
3013
3270
  };
3014
3271
 
3015
3272
  // src/fs-bridge.ts
3016
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
3273
+ import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
3017
3274
  import { extname, isAbsolute } from "path";
3018
3275
  import { createLogger as createLogger7 } from "@mclawnet/logger";
3019
3276
  var log7 = createLogger7({ module: "fs-bridge" });
@@ -3071,7 +3328,7 @@ var FsBridge = class {
3071
3328
  if (!isAbsolute(filePath)) {
3072
3329
  throw new Error(`Access denied: only absolute paths allowed`);
3073
3330
  }
3074
- if (!existsSync5(filePath)) {
3331
+ if (!existsSync6(filePath)) {
3075
3332
  throw new Error(`File not found: ${filePath}`);
3076
3333
  }
3077
3334
  const stat2 = statSync2(filePath);
@@ -3093,37 +3350,124 @@ var FsBridge = class {
3093
3350
  }
3094
3351
  };
3095
3352
 
3096
- // src/swarm-session-bridge.ts
3353
+ // src/templates-roles-bridge.ts
3097
3354
  import { createLogger as createLogger8 } from "@mclawnet/logger";
3098
- var log8 = createLogger8({ module: "agent:swarm-bridge" });
3355
+ import {
3356
+ listTemplatesWithSource,
3357
+ getTemplateRaw,
3358
+ getTemplateSource,
3359
+ saveTemplate,
3360
+ deleteTemplate,
3361
+ loadTemplate as loadTemplate2,
3362
+ listRolesWithSource,
3363
+ getRoleRaw,
3364
+ getRoleSource,
3365
+ saveRole,
3366
+ deleteRole,
3367
+ loadRole as loadRole2
3368
+ } from "@mclawnet/swarm";
3369
+ var log8 = createLogger8({ module: "templates-roles-bridge" });
3370
+ function requireStringParam(params, key) {
3371
+ const v = params[key];
3372
+ if (typeof v !== "string" || !v) throw new Error(`Missing '${key}' parameter`);
3373
+ return v;
3374
+ }
3375
+ var TemplatesRolesBridge = class {
3376
+ constructor(hub) {
3377
+ this.hub = hub;
3378
+ this.hub.registerNamespace("templates", (msg) => this.handleTemplates(msg));
3379
+ this.hub.registerNamespace("roles", (msg) => this.handleRoles(msg));
3380
+ log8.info("TemplatesRolesBridge initialized");
3381
+ }
3382
+ async handleTemplates(msg) {
3383
+ log8.info({ action: msg.action, requestId: msg.requestId }, "templates request");
3384
+ switch (msg.action) {
3385
+ case "list":
3386
+ return { items: listTemplatesWithSource() };
3387
+ case "get": {
3388
+ const name = requireStringParam(msg.params, "name");
3389
+ const raw = getTemplateRaw(name);
3390
+ if (raw == null) throw new Error(`Template not found: ${name}`);
3391
+ const parsed = loadTemplate2(name);
3392
+ const source = getTemplateSource(name);
3393
+ return { raw, parsed, source };
3394
+ }
3395
+ case "save": {
3396
+ const name = requireStringParam(msg.params, "name");
3397
+ const raw = requireStringParam(msg.params, "raw");
3398
+ const source = saveTemplate(name, raw);
3399
+ return { ok: true, source };
3400
+ }
3401
+ case "delete": {
3402
+ const name = requireStringParam(msg.params, "name");
3403
+ const r = deleteTemplate(name);
3404
+ return { ok: true, ...r };
3405
+ }
3406
+ default:
3407
+ throw new Error(`Unknown templates action: ${msg.action}`);
3408
+ }
3409
+ }
3410
+ async handleRoles(msg) {
3411
+ log8.info({ action: msg.action, requestId: msg.requestId }, "roles request");
3412
+ switch (msg.action) {
3413
+ case "list":
3414
+ return { items: listRolesWithSource() };
3415
+ case "get": {
3416
+ const name = requireStringParam(msg.params, "name");
3417
+ const raw = getRoleRaw(name);
3418
+ if (raw == null) throw new Error(`Role not found: ${name}`);
3419
+ const parsed = loadRole2(name);
3420
+ const source = getRoleSource(name);
3421
+ return { raw, parsed, source };
3422
+ }
3423
+ case "save": {
3424
+ const name = requireStringParam(msg.params, "name");
3425
+ const raw = requireStringParam(msg.params, "raw");
3426
+ const source = saveRole(name, raw);
3427
+ return { ok: true, source };
3428
+ }
3429
+ case "delete": {
3430
+ const name = requireStringParam(msg.params, "name");
3431
+ const r = deleteRole(name);
3432
+ return { ok: true, ...r };
3433
+ }
3434
+ default:
3435
+ throw new Error(`Unknown roles action: ${msg.action}`);
3436
+ }
3437
+ }
3438
+ };
3439
+
3440
+ // src/swarm-session-bridge.ts
3441
+ import { createLogger as createLogger9 } from "@mclawnet/logger";
3442
+ var log9 = createLogger9({ module: "agent:swarm-bridge" });
3099
3443
  function createSwarmAwareSessionStartedHandler(deps) {
3100
3444
  return (sessionId, info) => {
3101
3445
  deps.hub.send({
3102
3446
  type: "claude.session_started",
3103
3447
  sessionId,
3104
- claudeSessionId: info.claudeSessionId
3448
+ backendSessionId: info.backendSessionId
3105
3449
  });
3106
3450
  if (deps.swarmCoordinator.isSwarmSession(sessionId)) {
3107
3451
  try {
3108
- const ok = deps.swarmCoordinator.setRoleClaudeSessionIdBySession(
3452
+ const ok = deps.swarmCoordinator.setRoleBackendSessionIdBySession(
3109
3453
  sessionId,
3110
- info.claudeSessionId
3454
+ info.backendSessionId
3111
3455
  );
3112
3456
  if (!ok) {
3113
- log8.debug(
3457
+ log9.debug(
3114
3458
  { sessionId },
3115
3459
  "session_started for swarm-shaped sessionId, but no matching role (already destroyed?)"
3116
3460
  );
3117
3461
  }
3118
3462
  } catch (err) {
3119
- log8.warn({ err, sessionId }, "failed to bridge session_started into swarm");
3463
+ log9.warn({ err, sessionId }, "failed to bridge session_started into swarm");
3120
3464
  }
3121
3465
  }
3122
3466
  };
3123
3467
  }
3124
3468
 
3125
3469
  // src/start.ts
3126
- import { createLogger as createLogger9 } from "@mclawnet/logger";
3470
+ import { createLogger as createLogger10 } from "@mclawnet/logger";
3127
3471
  import {
3128
3472
  initDatabase as initDatabase2,
3129
3473
  MemoryStore,
@@ -3139,15 +3483,15 @@ import {
3139
3483
  triggerFromAccumulation
3140
3484
  } from "@mclawnet/skill-manager";
3141
3485
  import { PROJECTS_RPC_CAPABILITY } from "@mclawnet/shared";
3142
- var log9 = createLogger9({ module: "agent" });
3486
+ var log10 = createLogger10({ module: "agent" });
3143
3487
  async function startAgent(options) {
3144
3488
  const config = loadConfig(options.config);
3145
3489
  if (!config.token) {
3146
- log9.error("no token configured \u2014 set CLAWNET_TOKEN or use --token");
3490
+ log10.error("no token configured \u2014 set CLAWNET_TOKEN or use --token");
3147
3491
  process.exit(1);
3148
3492
  }
3149
- log9.info({ backend: options.adapter.type }, "starting agent");
3150
- log9.info({ hubUrl: config.hubUrl }, "connecting to hub");
3493
+ log10.info({ backend: options.adapter.type }, "starting agent");
3494
+ log10.info({ hubUrl: config.hubUrl }, "connecting to hub");
3151
3495
  await initRoles();
3152
3496
  await initSkills();
3153
3497
  const hub = new HubConnection({
@@ -3156,11 +3500,11 @@ async function startAgent(options) {
3156
3500
  hostname: config.name,
3157
3501
  capabilities: [PROJECTS_RPC_CAPABILITY],
3158
3502
  onConnect: (agentId) => {
3159
- log9.info({ agentId }, "connected to hub");
3503
+ log10.info({ agentId }, "connected to hub");
3160
3504
  const skills = getSkillList();
3161
3505
  if (skills.length > 0) {
3162
3506
  hub.sendSkillList(skills);
3163
- log9.info({ count: skills.length }, "pushed skill list to hub");
3507
+ log10.info({ count: skills.length }, "pushed skill list to hub");
3164
3508
  }
3165
3509
  void sessionManager.recoverFromCheckpoint().then((report) => {
3166
3510
  for (const entry of report.dead) {
@@ -3168,27 +3512,27 @@ async function startAgent(options) {
3168
3512
  type: "session.died",
3169
3513
  sessionId: entry.sessionId,
3170
3514
  reason: "agent_restart",
3171
- claudeSessionId: entry.claudeSessionId
3515
+ backendSessionId: entry.backendSessionId
3172
3516
  });
3173
3517
  }
3174
- log9.info(
3518
+ log10.info(
3175
3519
  { deadCount: report.dead.length, orphanCount: report.orphan.length },
3176
3520
  "checkpoint recover: cleanup complete"
3177
3521
  );
3178
3522
  }).catch((err) => {
3179
- log9.warn({ err }, "checkpoint recover: failed (degrading to no-op)");
3523
+ log10.warn({ err }, "checkpoint recover: failed (degrading to no-op)");
3180
3524
  });
3181
3525
  },
3182
3526
  onDisconnect: (code, reason) => {
3183
- log9.info({ code, reason }, "disconnected from hub");
3527
+ log10.info({ code, reason }, "disconnected from hub");
3184
3528
  },
3185
3529
  onError: (err) => {
3186
- log9.error({ err }, "hub connection error");
3530
+ log10.error({ err }, "hub connection error");
3187
3531
  }
3188
3532
  });
3189
3533
  let swarmCoordinator;
3190
- const clawnetDir = process.env.CLAWNET_DIR ?? join5(homedir4(), ".clawnet");
3191
- const dbPath = process.env.CLAWNET_MEMORY_DB ?? join5(clawnetDir, "memory.db");
3534
+ const clawnetDir = process.env.CLAWNET_DIR ?? join6(homedir5(), ".clawnet");
3535
+ const dbPath = process.env.CLAWNET_MEMORY_DB ?? join6(clawnetDir, "memory.db");
3192
3536
  let memoryStore = null;
3193
3537
  let embeddingService = null;
3194
3538
  let evolutionPipeline = null;
@@ -3201,7 +3545,7 @@ async function startAgent(options) {
3201
3545
  skillStore = new SkillStore(clawnetDir);
3202
3546
  evolutionPipeline = new EvolutionPipeline(clawnetDir);
3203
3547
  } catch (err) {
3204
- log9.warn({ err }, "failed to init memory/skill infra; distillation disabled");
3548
+ log10.warn({ err }, "failed to init memory/skill infra; distillation disabled");
3205
3549
  }
3206
3550
  const onBeforeClose = async (sessionId, messages) => {
3207
3551
  if (messages.length === 0) return;
@@ -3214,7 +3558,7 @@ async function startAgent(options) {
3214
3558
  embeddingService
3215
3559
  );
3216
3560
  } catch (err) {
3217
- log9.warn({ err, sessionId }, "distillation failed (non-fatal)");
3561
+ log10.warn({ err, sessionId }, "distillation failed (non-fatal)");
3218
3562
  }
3219
3563
  try {
3220
3564
  if (!skillStore || !evolutionPipeline) return;
@@ -3236,11 +3580,15 @@ async function startAgent(options) {
3236
3580
  await triggerFromAccumulation(signals, evolutionPipeline);
3237
3581
  }
3238
3582
  } catch (err) {
3239
- log9.warn({ err, sessionId }, "accumulation scan failed (non-fatal)");
3583
+ log10.warn({ err, sessionId }, "accumulation scan failed (non-fatal)");
3240
3584
  }
3241
3585
  };
3242
3586
  const sessionManager = new SessionManager({
3243
3587
  adapter: options.adapter,
3588
+ resolveAdapter: async (kind) => {
3589
+ const { createBackendAdapter } = await import("./backend-factory-RUYUBJVF.js");
3590
+ return createBackendAdapter(kind);
3591
+ },
3244
3592
  onOutput: (sessionId, data) => {
3245
3593
  if (swarmCoordinator.handleRoleOutput(sessionId, data)) return;
3246
3594
  hub.send({
@@ -3254,7 +3602,7 @@ async function startAgent(options) {
3254
3602
  hub.send({
3255
3603
  type: "claude.turn_complete",
3256
3604
  sessionId,
3257
- claudeSessionId: info.claudeSessionId,
3605
+ backendSessionId: info.backendSessionId,
3258
3606
  cost: info.cost,
3259
3607
  duration: info.duration,
3260
3608
  contextUsage: info.contextUsage
@@ -3280,10 +3628,22 @@ async function startAgent(options) {
3280
3628
  try {
3281
3629
  swarmCoordinator.handleRoleCrashed(sessionId, info.reason ?? `exit code=${info.code ?? "null"}`);
3282
3630
  } catch (err) {
3283
- log9.warn({ err, sessionId }, "swarmCoordinator.handleRoleCrashed threw");
3631
+ log10.warn({ err, sessionId }, "swarmCoordinator.handleRoleCrashed threw");
3284
3632
  }
3285
3633
  },
3286
3634
  onBeforeClose,
3635
+ onPermissionRequest: (sessionId, req) => {
3636
+ hub.send({
3637
+ type: "agent.permission_request",
3638
+ sessionId,
3639
+ requestId: req.callId,
3640
+ toolName: req.toolName,
3641
+ args: req.input ?? {},
3642
+ reason: req.meta?.reason,
3643
+ backendType: req.meta?.backend,
3644
+ meta: req.meta
3645
+ });
3646
+ },
3287
3647
  // PR-A: classify session kind for the idle sweeper. SessionManager stays
3288
3648
  // independent of the swarm package; we hand it a closure that defers to
3289
3649
  // SwarmCoordinator (created below) at call time. Lazy-read is safe because
@@ -3293,18 +3653,18 @@ async function startAgent(options) {
3293
3653
  // PR-C: enable logical-state checkpoint. Lives next to memory.db under
3294
3654
  // CLAWNET_DIR so backup/wipe affects both consistently. Per-call writes
3295
3655
  // are debounced to 5s by SessionManager itself.
3296
- checkpointPath: join5(clawnetDir, "agent-sessions.json")
3656
+ checkpointPath: join6(clawnetDir, "agent-sessions.json")
3297
3657
  });
3298
3658
  swarmCoordinator = new SwarmCoordinator(sessionManager, hub, (workDir) => {
3299
3659
  try {
3300
3660
  const env = process.env.CLAWNET_DIR;
3301
- const home = env ? env.replace(/\/\.clawnet\/?$/, "") : homedir4();
3661
+ const home = env ? env.replace(/\/\.clawnet\/?$/, "") : homedir5();
3302
3662
  return new TaskStore2({ workDir, home });
3303
3663
  } catch (err) {
3304
- log9.warn({ err, workDir }, "TaskStore factory failed");
3664
+ log10.warn({ err, workDir }, "TaskStore factory failed");
3305
3665
  return void 0;
3306
3666
  }
3307
- }, process.env.CLAWNET_HOME ?? homedir4());
3667
+ }, process.env.CLAWNET_HOME ?? homedir5());
3308
3668
  hub.setSessionManager(sessionManager);
3309
3669
  hub.setSwarmCoordinator(swarmCoordinator);
3310
3670
  const scheduleRuntime = new ScheduleRuntime({ hub, sessionManager, swarmCoordinator });
@@ -3312,9 +3672,10 @@ async function startAgent(options) {
3312
3672
  await scheduleRuntime.start();
3313
3673
  const brainBridge = new BrainBridge(hub);
3314
3674
  const fsBridge = new FsBridge(hub);
3675
+ const templatesRolesBridge = new TemplatesRolesBridge(hub);
3315
3676
  sessionManager.startIdleSweeper();
3316
3677
  const shutdown = async () => {
3317
- log9.info("shutting down");
3678
+ log10.info("shutting down");
3318
3679
  await sessionManager.closeAll();
3319
3680
  await scheduleRuntime.stop();
3320
3681
  hub.destroy();
@@ -3322,7 +3683,7 @@ async function startAgent(options) {
3322
3683
  try {
3323
3684
  memoryDb.close();
3324
3685
  } catch (err) {
3325
- log9.warn({ err }, "failed to close memory db");
3686
+ log10.warn({ err }, "failed to close memory db");
3326
3687
  }
3327
3688
  }
3328
3689
  process.exit(0);
@@ -3330,7 +3691,7 @@ async function startAgent(options) {
3330
3691
  process.on("SIGINT", shutdown);
3331
3692
  process.on("SIGTERM", shutdown);
3332
3693
  hub.connect();
3333
- return { hub, sessionManager, swarmCoordinator, scheduleRuntime, brainBridge, fsBridge };
3694
+ return { hub, sessionManager, swarmCoordinator, scheduleRuntime, brainBridge, fsBridge, templatesRolesBridge };
3334
3695
  }
3335
3696
 
3336
3697
  export {
@@ -3340,4 +3701,4 @@ export {
3340
3701
  FsBridge,
3341
3702
  startAgent
3342
3703
  };
3343
- //# sourceMappingURL=chunk-QPLG5WHL.js.map
3704
+ //# sourceMappingURL=chunk-2JDX6XFD.js.map