@mclawnet/agent 0.6.33 → 0.6.35

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 (61) hide show
  1. package/cli.js +69 -1
  2. package/dist/__tests__/backend-adapter-types.test.d.ts +2 -0
  3. package/dist/__tests__/backend-adapter-types.test.d.ts.map +1 -0
  4. package/dist/__tests__/backend-factory.test.d.ts +2 -0
  5. package/dist/__tests__/backend-factory.test.d.ts.map +1 -0
  6. package/dist/__tests__/bootstrap-deps.test.d.ts +2 -0
  7. package/dist/__tests__/bootstrap-deps.test.d.ts.map +1 -0
  8. package/dist/__tests__/normalize-backend-output.test.d.ts +2 -0
  9. package/dist/__tests__/normalize-backend-output.test.d.ts.map +1 -0
  10. package/dist/__tests__/runtime-env-defaults.test.d.ts +2 -0
  11. package/dist/__tests__/runtime-env-defaults.test.d.ts.map +1 -0
  12. package/dist/__tests__/session-manager-backend.test.d.ts +2 -0
  13. package/dist/__tests__/session-manager-backend.test.d.ts.map +1 -0
  14. package/dist/__tests__/session-manager-exit-reason.test.d.ts +2 -0
  15. package/dist/__tests__/session-manager-exit-reason.test.d.ts.map +1 -0
  16. package/dist/__tests__/session-manager-permission.test.d.ts +2 -0
  17. package/dist/__tests__/session-manager-permission.test.d.ts.map +1 -0
  18. package/dist/__tests__/templates-roles-bridge.test.d.ts +2 -0
  19. package/dist/__tests__/templates-roles-bridge.test.d.ts.map +1 -0
  20. package/dist/backend-adapter.d.ts +102 -10
  21. package/dist/backend-adapter.d.ts.map +1 -1
  22. package/dist/backend-factory-VRPU3534.js +9 -0
  23. package/dist/backend-factory-VRPU3534.js.map +1 -0
  24. package/dist/backend-factory.d.ts +19 -0
  25. package/dist/backend-factory.d.ts.map +1 -0
  26. package/dist/bootstrap-deps.d.ts +62 -0
  27. package/dist/bootstrap-deps.d.ts.map +1 -0
  28. package/dist/bootstrap-deps.js +154 -0
  29. package/dist/bootstrap-deps.js.map +1 -0
  30. package/dist/checkpoint.d.ts +1 -1
  31. package/dist/checkpoint.d.ts.map +1 -1
  32. package/dist/{chunk-QPLG5WHL.js → chunk-B733MQCA.js} +479 -87
  33. package/dist/chunk-B733MQCA.js.map +1 -0
  34. package/dist/chunk-FYM7CXUI.js +49 -0
  35. package/dist/chunk-FYM7CXUI.js.map +1 -0
  36. package/dist/dist-EGT2NQEW.js +940 -0
  37. package/dist/dist-EGT2NQEW.js.map +1 -0
  38. package/dist/fs-handler.d.ts +1 -1
  39. package/dist/fs-handler.d.ts.map +1 -1
  40. package/dist/hub-connection.d.ts.map +1 -1
  41. package/dist/index.d.ts +2 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +7 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/normalize-backend-output.d.ts +5 -0
  46. package/dist/normalize-backend-output.d.ts.map +1 -0
  47. package/dist/runtime-env-defaults.d.ts +18 -0
  48. package/dist/runtime-env-defaults.d.ts.map +1 -0
  49. package/dist/session-manager.d.ts +36 -4
  50. package/dist/session-manager.d.ts.map +1 -1
  51. package/dist/start.d.ts +2 -0
  52. package/dist/start.d.ts.map +1 -1
  53. package/dist/start.js +1 -1
  54. package/dist/swarm-control-dispatch.d.ts +33 -1
  55. package/dist/swarm-control-dispatch.d.ts.map +1 -1
  56. package/dist/swarm-session-bridge.d.ts +3 -3
  57. package/dist/swarm-session-bridge.d.ts.map +1 -1
  58. package/dist/templates-roles-bridge.d.ts +14 -0
  59. package/dist/templates-roles-bridge.d.ts.map +1 -0
  60. package/package.json +14 -10
  61. 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() {
@@ -2122,6 +2281,12 @@ var SessionManager = class {
2122
2281
  // handler. A leftover entry would only matter if a session id were reused,
2123
2282
  // which createSession already forbids (line 193 throws on duplicate).
2124
2283
  expectedExits = /* @__PURE__ */ new Set();
2284
+ // Most recent adapter-emitted error per session, used to enrich the
2285
+ // otherwise opaque "exit code=N" reason that swarm coordinator's crash
2286
+ // inbox surfaces. Entries are auto-pruned on session exit and ignored if
2287
+ // older than 5s (so a stale entry from a long-lived session doesn't
2288
+ // misattribute a later unrelated exit).
2289
+ lastSessionError = /* @__PURE__ */ new Map();
2125
2290
  idleSweepTimer = null;
2126
2291
  // PR-A: effective sweeper config. Initialized from env at construct time
2127
2292
  // and overridable per-instance via startIdleSweeper(overrides) — the
@@ -2152,6 +2317,23 @@ var SessionManager = class {
2152
2317
  */
2153
2318
  recoveredLadderIndex = /* @__PURE__ */ new Map();
2154
2319
  adapter;
2320
+ /**
2321
+ * Per-process adapter override. Populated by `createSession` when
2322
+ * `options.backend` is set and `resolveAdapter` returns a non-default
2323
+ * adapter. All adapter calls (send/stop/onOutput/...) consult
2324
+ * `getAdapterFor(process)` instead of `this.adapter` directly.
2325
+ */
2326
+ processAdapters = /* @__PURE__ */ new WeakMap();
2327
+ /**
2328
+ * Optional resolver for per-spawn alternate backends. Wired by the agent
2329
+ * host to `createBackendAdapter` so role.backend can pick codex/claude.
2330
+ * When omitted, options.backend is ignored and `this.adapter` is used.
2331
+ */
2332
+ resolveAdapter;
2333
+ /** Resolve the adapter that owns a particular spawned process. */
2334
+ getAdapterFor(proc) {
2335
+ return this.processAdapters.get(proc) ?? this.adapter;
2336
+ }
2155
2337
  onOutput;
2156
2338
  onTurnComplete;
2157
2339
  onSessionError;
@@ -2165,6 +2347,7 @@ var SessionManager = class {
2165
2347
  * swarm-role exits to SwarmCoordinator.handleRoleCrashed.
2166
2348
  */
2167
2349
  onSessionExit;
2350
+ onPermissionRequest;
2168
2351
  // PR-A: classifies a sessionId as 'chat' or 'swarm-role'. Injected by
2169
2352
  // start.ts via SwarmCoordinator.isSwarmSession to keep SessionManager
2170
2353
  // independent of the swarm package. Defaults to 'chat' if absent — safe
@@ -2173,12 +2356,14 @@ var SessionManager = class {
2173
2356
  classify;
2174
2357
  constructor(options) {
2175
2358
  this.adapter = options.adapter;
2359
+ this.resolveAdapter = options.resolveAdapter;
2176
2360
  this.onOutput = options.onOutput;
2177
2361
  this.onTurnComplete = options.onTurnComplete;
2178
2362
  this.onSessionError = options.onSessionError;
2179
2363
  this.onSessionStarted = options.onSessionStarted;
2180
2364
  this.onBeforeClose = options.onBeforeClose;
2181
2365
  this.onSessionExit = options.onSessionExit;
2366
+ this.onPermissionRequest = options.onPermissionRequest;
2182
2367
  this.classify = options.classify ?? (() => "chat");
2183
2368
  this.checkpointPath = options.checkpointPath ?? null;
2184
2369
  this.checkpointDebounceMs = options.checkpointDebounceMs ?? 5e3;
@@ -2234,7 +2419,37 @@ ${notice.text}`;
2234
2419
  options.systemPrompt = options.systemPrompt ? `${options.systemPrompt}${ideaHint}` : ideaHint.trimStart();
2235
2420
  }
2236
2421
  try {
2237
- const process2 = await this.adapter.spawn(options);
2422
+ let spawnAdapter = this.adapter;
2423
+ if (options.backend && this.resolveAdapter) {
2424
+ const resolved = await this.resolveAdapter(options.backend);
2425
+ log5.info(
2426
+ {
2427
+ sessionId: options.sessionId,
2428
+ requested: options.backend,
2429
+ resolvedType: resolved.type,
2430
+ defaultType: this.adapter.type,
2431
+ willSwitch: resolved.type !== this.adapter.type
2432
+ },
2433
+ "createSession: adapter resolved"
2434
+ );
2435
+ if (resolved.type !== this.adapter.type) {
2436
+ spawnAdapter = resolved;
2437
+ }
2438
+ } else {
2439
+ log5.info(
2440
+ {
2441
+ sessionId: options.sessionId,
2442
+ requestedBackend: options.backend,
2443
+ hasResolver: !!this.resolveAdapter,
2444
+ defaultType: this.adapter.type
2445
+ },
2446
+ "createSession: using default adapter"
2447
+ );
2448
+ }
2449
+ const process2 = await spawnAdapter.spawn(options);
2450
+ if (spawnAdapter !== this.adapter) {
2451
+ this.processAdapters.set(process2, spawnAdapter);
2452
+ }
2238
2453
  this.sessions.set(options.sessionId, process2);
2239
2454
  const now = Date.now();
2240
2455
  const kind = this.classify(options.sessionId);
@@ -2253,7 +2468,9 @@ ${notice.text}`;
2253
2468
  });
2254
2469
  this.recoveredLadderIndex.delete(options.sessionId);
2255
2470
  this.scheduleCheckpoint();
2256
- this.adapter.onOutput(process2, (data) => {
2471
+ spawnAdapter.onOutput(process2, (rawData) => {
2472
+ const data = normalizeBackendOutput(rawData, log5);
2473
+ if (data === null) return;
2257
2474
  const msg = data;
2258
2475
  if (msg?.type === "assistant" && msg?.message?.content) {
2259
2476
  const buf = this.conversationBuffer.get(options.sessionId) ?? [];
@@ -2265,7 +2482,7 @@ ${notice.text}`;
2265
2482
  }
2266
2483
  this.onOutput(options.sessionId, data);
2267
2484
  });
2268
- this.adapter.onTurnComplete?.(process2, (info) => {
2485
+ spawnAdapter.onTurnComplete?.(process2, (info) => {
2269
2486
  const meta = this.sessionMeta.get(options.sessionId);
2270
2487
  if (meta) {
2271
2488
  meta.lastActivityAt = Date.now();
@@ -2274,11 +2491,12 @@ ${notice.text}`;
2274
2491
  this.activelyExecuting.delete(options.sessionId);
2275
2492
  this.onTurnComplete(options.sessionId, info);
2276
2493
  });
2277
- this.adapter.onError?.(process2, (error) => {
2494
+ spawnAdapter.onError?.(process2, (error) => {
2278
2495
  this.activelyExecuting.delete(options.sessionId);
2496
+ this.lastSessionError.set(options.sessionId, { message: error.message, ts: Date.now() });
2279
2497
  this.onSessionError(options.sessionId, error.message);
2280
2498
  });
2281
- this.adapter.onTokenBudgetWarning?.(process2, (info) => {
2499
+ spawnAdapter.onTokenBudgetWarning?.(process2, (info) => {
2282
2500
  const meta = this.sessionMeta.get(options.sessionId);
2283
2501
  if (!meta) return;
2284
2502
  const currentIndex = clampLadderIndex(meta.currentLadderIndex);
@@ -2311,7 +2529,7 @@ ${notice.text}`;
2311
2529
  this.scheduleCheckpoint();
2312
2530
  });
2313
2531
  if (this.onSessionStarted) {
2314
- this.adapter.onSessionStarted?.(process2, (info) => {
2532
+ spawnAdapter.onSessionStarted?.(process2, (info) => {
2315
2533
  if (this.aborting.has(options.sessionId)) {
2316
2534
  log5.debug(
2317
2535
  { sessionId: options.sessionId },
@@ -2323,12 +2541,49 @@ ${notice.text}`;
2323
2541
  return;
2324
2542
  }
2325
2543
  const meta = this.sessionMeta.get(options.sessionId);
2326
- if (meta) meta.claudeSessionId = info.claudeSessionId;
2327
- this.scheduleCheckpoint();
2328
- this.onSessionStarted(options.sessionId, info);
2544
+ const sid = info.backendSessionId;
2545
+ let sidIsTrustworthy = true;
2546
+ const workDir = meta?.workDir;
2547
+ const isClaudeAdapter = spawnAdapter.type === "claude-code";
2548
+ if (isClaudeAdapter && workDir && sid) {
2549
+ const encoded = workDir.replace(/\//g, "-");
2550
+ const jsonlPath = join4(
2551
+ homedir4(),
2552
+ ".claude",
2553
+ "projects",
2554
+ encoded,
2555
+ `${sid}.jsonl`
2556
+ );
2557
+ if (!existsSync4(jsonlPath)) {
2558
+ log5.warn(
2559
+ {
2560
+ sessionId: options.sessionId,
2561
+ backendSessionId: sid,
2562
+ jsonlPath
2563
+ },
2564
+ "session_started reports backendSessionId whose jsonl is missing on disk \u2014 refusing to persist to avoid ghost-sid propagation"
2565
+ );
2566
+ sidIsTrustworthy = false;
2567
+ }
2568
+ }
2569
+ if (meta && sidIsTrustworthy) meta.backendSessionId = sid;
2570
+ if (sidIsTrustworthy) this.scheduleCheckpoint();
2571
+ this.onSessionStarted(options.sessionId, {
2572
+ backendSessionId: sid
2573
+ });
2329
2574
  });
2330
2575
  }
2331
- this.adapter.onExit?.(process2, (code) => {
2576
+ if (this.onPermissionRequest) {
2577
+ spawnAdapter.onPermissionRequest?.(process2, (req) => {
2578
+ if (this.sessions.get(options.sessionId) !== process2) return;
2579
+ this.onPermissionRequest(options.sessionId, req);
2580
+ });
2581
+ }
2582
+ spawnAdapter.onExit?.(process2, (code) => {
2583
+ const lastErr = this.lastSessionError.get(options.sessionId);
2584
+ this.lastSessionError.delete(options.sessionId);
2585
+ const enriched = lastErr && Date.now() - lastErr.ts < 5e3 ? lastErr.message : void 0;
2586
+ const reasonText = enriched ? `exit code=${code ?? "null"} \u2014 ${enriched}` : `exit code=${code ?? "null"}`;
2332
2587
  if (this.sessions.get(options.sessionId) === process2) {
2333
2588
  const expected = this.expectedExits.delete(options.sessionId);
2334
2589
  this.sessions.delete(options.sessionId);
@@ -2337,7 +2592,7 @@ ${notice.text}`;
2337
2592
  this.conversationBuffer.delete(options.sessionId);
2338
2593
  this.scheduleCheckpoint();
2339
2594
  if (!expected) {
2340
- log5.warn({ sessionId: options.sessionId, exitCode: code }, "backend process exited unexpectedly, evicted from session map");
2595
+ log5.warn({ sessionId: options.sessionId, exitCode: code, reason: enriched }, "backend process exited unexpectedly, evicted from session map");
2341
2596
  this.onSessionError(options.sessionId, `backend process exited (code=${code ?? "null"})`);
2342
2597
  } else {
2343
2598
  log5.debug({ sessionId: options.sessionId, exitCode: code }, "backend process exited as expected");
@@ -2346,7 +2601,7 @@ ${notice.text}`;
2346
2601
  this.onSessionExit?.(options.sessionId, {
2347
2602
  code: code ?? null,
2348
2603
  expected,
2349
- ...expected ? {} : { reason: `exit code=${code ?? "null"}` }
2604
+ ...expected ? {} : { reason: reasonText }
2350
2605
  });
2351
2606
  } catch (err) {
2352
2607
  log5.warn({ err, sessionId: options.sessionId }, "onSessionExit listener threw");
@@ -2383,7 +2638,7 @@ ${notice.text}`;
2383
2638
  { sessionId, ...previewFields2(input) },
2384
2639
  "sendInput \u2192 backend stdin"
2385
2640
  );
2386
- this.adapter.send(process2, input);
2641
+ this.getAdapterFor(process2).send(process2, input);
2387
2642
  }
2388
2643
  /**
2389
2644
  * User-input variant of sendInput: best-effort prepend a `<memory-context>`
@@ -2429,11 +2684,24 @@ ${content}`;
2429
2684
  this.activelyExecuting.delete(sessionId);
2430
2685
  this.scheduleCheckpoint();
2431
2686
  try {
2432
- await this.adapter.stop(process2);
2687
+ await this.getAdapterFor(process2).stop(process2);
2433
2688
  } finally {
2434
2689
  this.aborting.delete(sessionId);
2435
2690
  }
2436
2691
  }
2692
+ /**
2693
+ * M3.S2b — Deliver a permission decision back to the adapter for the named
2694
+ * session. No-ops silently when the adapter doesn't implement the optional
2695
+ * `respondToPermission` (e.g. claude-adapter today). Throws when the session
2696
+ * is unknown so callers don't silently lose decisions to dead sessions.
2697
+ */
2698
+ async respondToPermission(sessionId, decision) {
2699
+ const process2 = this.sessions.get(sessionId);
2700
+ if (!process2) {
2701
+ throw new Error(`Session not found: ${sessionId}`);
2702
+ }
2703
+ await this.getAdapterFor(process2).respondToPermission?.(process2, decision);
2704
+ }
2437
2705
  async closeSession(sessionId) {
2438
2706
  const process2 = this.sessions.get(sessionId);
2439
2707
  if (!process2) return;
@@ -2448,7 +2716,7 @@ ${content}`;
2448
2716
  this.sessionMeta.delete(sessionId);
2449
2717
  this.activelyExecuting.delete(sessionId);
2450
2718
  this.scheduleCheckpoint();
2451
- await this.adapter.stop(process2);
2719
+ await this.getAdapterFor(process2).stop(process2);
2452
2720
  }
2453
2721
  async closeAll() {
2454
2722
  this.stopIdleSweeper();
@@ -2464,7 +2732,7 @@ ${content}`;
2464
2732
  this.sessions.delete(sessionId);
2465
2733
  this.sessionMeta.delete(sessionId);
2466
2734
  this.activelyExecuting.delete(sessionId);
2467
- await this.adapter.stop(process2).catch(() => {
2735
+ await this.getAdapterFor(process2).stop(process2).catch(() => {
2468
2736
  });
2469
2737
  }
2470
2738
  );
@@ -2486,7 +2754,7 @@ ${content}`;
2486
2754
  * Stricter than hasSession(): also requires the underlying process to still
2487
2755
  * be alive (not exited, stdin still writable). Callers in the reuse path
2488
2756
  * 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.
2757
+ * false they should fall back to `--resume` against db's backendSessionId.
2490
2758
  */
2491
2759
  isHealthy(sessionId) {
2492
2760
  if (this.aborting.has(sessionId)) return false;
@@ -2656,7 +2924,7 @@ ${content}`;
2656
2924
  sessionId: sid,
2657
2925
  kind: meta.kind,
2658
2926
  workDir: meta.workDir,
2659
- claudeSessionId: meta.claudeSessionId,
2927
+ backendSessionId: meta.backendSessionId,
2660
2928
  agentInstanceId: meta.agentInstanceId,
2661
2929
  startedAt: meta.startedAt,
2662
2930
  lastActivityAt: meta.lastActivityAt,
@@ -2708,21 +2976,28 @@ ${content}`;
2708
2976
  }
2709
2977
  };
2710
2978
 
2979
+ // src/runtime-env-defaults.ts
2980
+ function ensureRuntimeEnvDefaults() {
2981
+ if (!process.env.COPILOT_API_KEY) {
2982
+ process.env.COPILOT_API_KEY = "dummy";
2983
+ }
2984
+ }
2985
+
2711
2986
  // src/start.ts
2712
- import { SwarmCoordinator, initRoles } from "@mclawnet/swarm";
2987
+ import { SwarmCoordinator, initRoles, WakeupScheduler, listRecoverableSwarmIds as listRecoverableSwarmIds2 } from "@mclawnet/swarm";
2713
2988
  import { TaskStore as TaskStore2 } from "@mclawnet/task";
2714
2989
 
2715
2990
  // src/brain-bridge.ts
2716
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync4 } from "fs";
2717
- import { join as join4 } from "path";
2991
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync4 } from "fs";
2992
+ import { join as join5 } from "path";
2718
2993
  import { createLogger as createLogger6 } from "@mclawnet/logger";
2719
2994
  var log6 = createLogger6({ module: "brain-bridge" });
2720
2995
  var BrainBridge = class {
2721
2996
  constructor(hub, options) {
2722
2997
  this.hub = hub;
2723
2998
  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");
2999
+ this.brainHome = options?.brainHomePath || process.env.BRAIN_HOME || join5(home, "BrainData");
3000
+ this.brainCorePath = options?.brainCorePath || join5(home, ".brain", "BrainCore");
2726
3001
  this.hub.registerNamespace("brain", (msg) => this.handleRequest(msg));
2727
3002
  log6.info(
2728
3003
  { brainHome: this.brainHome, brainCorePath: this.brainCorePath },
@@ -2749,15 +3024,15 @@ var BrainBridge = class {
2749
3024
  }
2750
3025
  }
2751
3026
  checkSetup() {
2752
- if (!existsSync4(this.brainCorePath)) {
3027
+ if (!existsSync5(this.brainCorePath)) {
2753
3028
  return "not_installed";
2754
3029
  }
2755
3030
  const home = process.env.HOME || process.env.USERPROFILE || "";
2756
- const installJson = join4(home, ".brain", "install.json");
2757
- if (!existsSync4(installJson)) {
3031
+ const installJson = join5(home, ".brain", "install.json");
3032
+ if (!existsSync5(installJson)) {
2758
3033
  return "needs_config";
2759
3034
  }
2760
- if (!existsSync4(this.brainHome)) {
3035
+ if (!existsSync5(this.brainHome)) {
2761
3036
  return "needs_config";
2762
3037
  }
2763
3038
  return "ready";
@@ -2765,8 +3040,8 @@ var BrainBridge = class {
2765
3040
  /** Read the most recent daily briefing report */
2766
3041
  async getBriefing(date) {
2767
3042
  const targetDate = date || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2768
- const reportsDir = join4(this.brainHome, "reports", "daily");
2769
- if (!existsSync4(reportsDir)) {
3043
+ const reportsDir = join5(this.brainHome, "reports", "daily");
3044
+ if (!existsSync5(reportsDir)) {
2770
3045
  log6.info({ reportsDir }, "get_briefing: reports dir not found");
2771
3046
  return { briefing: null, actions: [], projects: [], meetings: [], feed: [] };
2772
3047
  }
@@ -2776,7 +3051,7 @@ var BrainBridge = class {
2776
3051
  return { briefing: null, actions: [], projects: [], meetings: [], feed: [] };
2777
3052
  }
2778
3053
  log6.info({ targetDate, file: files[0] }, "get_briefing: reading report");
2779
- const content = readFileSync3(join4(reportsDir, files[0]), "utf-8");
3054
+ const content = readFileSync3(join5(reportsDir, files[0]), "utf-8");
2780
3055
  const tldrMatch = content.match(/## TL;DR\n([\s\S]*?)(?=\n---|\n## )/);
2781
3056
  const tldr = tldrMatch ? tldrMatch[1].trim() : content.slice(0, 200);
2782
3057
  const actions = this.parseActions(content);
@@ -2975,8 +3250,8 @@ var BrainBridge = class {
2975
3250
  if (!recapPath) {
2976
3251
  return { error: "No recap path provided" };
2977
3252
  }
2978
- const fullPath = join4(this.brainHome, recapPath);
2979
- if (!existsSync4(fullPath)) {
3253
+ const fullPath = join5(this.brainHome, recapPath);
3254
+ if (!existsSync5(fullPath)) {
2980
3255
  log6.warn({ fullPath }, "meeting recap file not found");
2981
3256
  return { error: "Recap file not found" };
2982
3257
  }
@@ -3013,7 +3288,7 @@ var BrainBridge = class {
3013
3288
  };
3014
3289
 
3015
3290
  // src/fs-bridge.ts
3016
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
3291
+ import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
3017
3292
  import { extname, isAbsolute } from "path";
3018
3293
  import { createLogger as createLogger7 } from "@mclawnet/logger";
3019
3294
  var log7 = createLogger7({ module: "fs-bridge" });
@@ -3071,7 +3346,7 @@ var FsBridge = class {
3071
3346
  if (!isAbsolute(filePath)) {
3072
3347
  throw new Error(`Access denied: only absolute paths allowed`);
3073
3348
  }
3074
- if (!existsSync5(filePath)) {
3349
+ if (!existsSync6(filePath)) {
3075
3350
  throw new Error(`File not found: ${filePath}`);
3076
3351
  }
3077
3352
  const stat2 = statSync2(filePath);
@@ -3093,37 +3368,124 @@ var FsBridge = class {
3093
3368
  }
3094
3369
  };
3095
3370
 
3096
- // src/swarm-session-bridge.ts
3371
+ // src/templates-roles-bridge.ts
3097
3372
  import { createLogger as createLogger8 } from "@mclawnet/logger";
3098
- var log8 = createLogger8({ module: "agent:swarm-bridge" });
3373
+ import {
3374
+ listTemplatesWithSource,
3375
+ getTemplateRaw,
3376
+ getTemplateSource,
3377
+ saveTemplate,
3378
+ deleteTemplate,
3379
+ loadTemplate as loadTemplate2,
3380
+ listRolesWithSource,
3381
+ getRoleRaw,
3382
+ getRoleSource,
3383
+ saveRole,
3384
+ deleteRole,
3385
+ loadRole as loadRole2
3386
+ } from "@mclawnet/swarm";
3387
+ var log8 = createLogger8({ module: "templates-roles-bridge" });
3388
+ function requireStringParam(params, key) {
3389
+ const v = params[key];
3390
+ if (typeof v !== "string" || !v) throw new Error(`Missing '${key}' parameter`);
3391
+ return v;
3392
+ }
3393
+ var TemplatesRolesBridge = class {
3394
+ constructor(hub) {
3395
+ this.hub = hub;
3396
+ this.hub.registerNamespace("templates", (msg) => this.handleTemplates(msg));
3397
+ this.hub.registerNamespace("roles", (msg) => this.handleRoles(msg));
3398
+ log8.info("TemplatesRolesBridge initialized");
3399
+ }
3400
+ async handleTemplates(msg) {
3401
+ log8.info({ action: msg.action, requestId: msg.requestId }, "templates request");
3402
+ switch (msg.action) {
3403
+ case "list":
3404
+ return { items: listTemplatesWithSource() };
3405
+ case "get": {
3406
+ const name = requireStringParam(msg.params, "name");
3407
+ const raw = getTemplateRaw(name);
3408
+ if (raw == null) throw new Error(`Template not found: ${name}`);
3409
+ const parsed = loadTemplate2(name);
3410
+ const source = getTemplateSource(name);
3411
+ return { raw, parsed, source };
3412
+ }
3413
+ case "save": {
3414
+ const name = requireStringParam(msg.params, "name");
3415
+ const raw = requireStringParam(msg.params, "raw");
3416
+ const source = saveTemplate(name, raw);
3417
+ return { ok: true, source };
3418
+ }
3419
+ case "delete": {
3420
+ const name = requireStringParam(msg.params, "name");
3421
+ const r = deleteTemplate(name);
3422
+ return { ok: true, ...r };
3423
+ }
3424
+ default:
3425
+ throw new Error(`Unknown templates action: ${msg.action}`);
3426
+ }
3427
+ }
3428
+ async handleRoles(msg) {
3429
+ log8.info({ action: msg.action, requestId: msg.requestId }, "roles request");
3430
+ switch (msg.action) {
3431
+ case "list":
3432
+ return { items: listRolesWithSource() };
3433
+ case "get": {
3434
+ const name = requireStringParam(msg.params, "name");
3435
+ const raw = getRoleRaw(name);
3436
+ if (raw == null) throw new Error(`Role not found: ${name}`);
3437
+ const parsed = loadRole2(name);
3438
+ const source = getRoleSource(name);
3439
+ return { raw, parsed, source };
3440
+ }
3441
+ case "save": {
3442
+ const name = requireStringParam(msg.params, "name");
3443
+ const raw = requireStringParam(msg.params, "raw");
3444
+ const source = saveRole(name, raw);
3445
+ return { ok: true, source };
3446
+ }
3447
+ case "delete": {
3448
+ const name = requireStringParam(msg.params, "name");
3449
+ const r = deleteRole(name);
3450
+ return { ok: true, ...r };
3451
+ }
3452
+ default:
3453
+ throw new Error(`Unknown roles action: ${msg.action}`);
3454
+ }
3455
+ }
3456
+ };
3457
+
3458
+ // src/swarm-session-bridge.ts
3459
+ import { createLogger as createLogger9 } from "@mclawnet/logger";
3460
+ var log9 = createLogger9({ module: "agent:swarm-bridge" });
3099
3461
  function createSwarmAwareSessionStartedHandler(deps) {
3100
3462
  return (sessionId, info) => {
3101
3463
  deps.hub.send({
3102
3464
  type: "claude.session_started",
3103
3465
  sessionId,
3104
- claudeSessionId: info.claudeSessionId
3466
+ backendSessionId: info.backendSessionId
3105
3467
  });
3106
3468
  if (deps.swarmCoordinator.isSwarmSession(sessionId)) {
3107
3469
  try {
3108
- const ok = deps.swarmCoordinator.setRoleClaudeSessionIdBySession(
3470
+ const ok = deps.swarmCoordinator.setRoleBackendSessionIdBySession(
3109
3471
  sessionId,
3110
- info.claudeSessionId
3472
+ info.backendSessionId
3111
3473
  );
3112
3474
  if (!ok) {
3113
- log8.debug(
3475
+ log9.debug(
3114
3476
  { sessionId },
3115
3477
  "session_started for swarm-shaped sessionId, but no matching role (already destroyed?)"
3116
3478
  );
3117
3479
  }
3118
3480
  } catch (err) {
3119
- log8.warn({ err, sessionId }, "failed to bridge session_started into swarm");
3481
+ log9.warn({ err, sessionId }, "failed to bridge session_started into swarm");
3120
3482
  }
3121
3483
  }
3122
3484
  };
3123
3485
  }
3124
3486
 
3125
3487
  // src/start.ts
3126
- import { createLogger as createLogger9 } from "@mclawnet/logger";
3488
+ import { createLogger as createLogger10 } from "@mclawnet/logger";
3127
3489
  import {
3128
3490
  initDatabase as initDatabase2,
3129
3491
  MemoryStore,
@@ -3139,15 +3501,16 @@ import {
3139
3501
  triggerFromAccumulation
3140
3502
  } from "@mclawnet/skill-manager";
3141
3503
  import { PROJECTS_RPC_CAPABILITY } from "@mclawnet/shared";
3142
- var log9 = createLogger9({ module: "agent" });
3504
+ var log10 = createLogger10({ module: "agent" });
3143
3505
  async function startAgent(options) {
3506
+ ensureRuntimeEnvDefaults();
3144
3507
  const config = loadConfig(options.config);
3145
3508
  if (!config.token) {
3146
- log9.error("no token configured \u2014 set CLAWNET_TOKEN or use --token");
3509
+ log10.error("no token configured \u2014 set CLAWNET_TOKEN or use --token");
3147
3510
  process.exit(1);
3148
3511
  }
3149
- log9.info({ backend: options.adapter.type }, "starting agent");
3150
- log9.info({ hubUrl: config.hubUrl }, "connecting to hub");
3512
+ log10.info({ backend: options.adapter.type }, "starting agent");
3513
+ log10.info({ hubUrl: config.hubUrl }, "connecting to hub");
3151
3514
  await initRoles();
3152
3515
  await initSkills();
3153
3516
  const hub = new HubConnection({
@@ -3156,11 +3519,11 @@ async function startAgent(options) {
3156
3519
  hostname: config.name,
3157
3520
  capabilities: [PROJECTS_RPC_CAPABILITY],
3158
3521
  onConnect: (agentId) => {
3159
- log9.info({ agentId }, "connected to hub");
3522
+ log10.info({ agentId }, "connected to hub");
3160
3523
  const skills = getSkillList();
3161
3524
  if (skills.length > 0) {
3162
3525
  hub.sendSkillList(skills);
3163
- log9.info({ count: skills.length }, "pushed skill list to hub");
3526
+ log10.info({ count: skills.length }, "pushed skill list to hub");
3164
3527
  }
3165
3528
  void sessionManager.recoverFromCheckpoint().then((report) => {
3166
3529
  for (const entry of report.dead) {
@@ -3168,27 +3531,27 @@ async function startAgent(options) {
3168
3531
  type: "session.died",
3169
3532
  sessionId: entry.sessionId,
3170
3533
  reason: "agent_restart",
3171
- claudeSessionId: entry.claudeSessionId
3534
+ backendSessionId: entry.backendSessionId
3172
3535
  });
3173
3536
  }
3174
- log9.info(
3537
+ log10.info(
3175
3538
  { deadCount: report.dead.length, orphanCount: report.orphan.length },
3176
3539
  "checkpoint recover: cleanup complete"
3177
3540
  );
3178
3541
  }).catch((err) => {
3179
- log9.warn({ err }, "checkpoint recover: failed (degrading to no-op)");
3542
+ log10.warn({ err }, "checkpoint recover: failed (degrading to no-op)");
3180
3543
  });
3181
3544
  },
3182
3545
  onDisconnect: (code, reason) => {
3183
- log9.info({ code, reason }, "disconnected from hub");
3546
+ log10.info({ code, reason }, "disconnected from hub");
3184
3547
  },
3185
3548
  onError: (err) => {
3186
- log9.error({ err }, "hub connection error");
3549
+ log10.error({ err }, "hub connection error");
3187
3550
  }
3188
3551
  });
3189
3552
  let swarmCoordinator;
3190
- const clawnetDir = process.env.CLAWNET_DIR ?? join5(homedir4(), ".clawnet");
3191
- const dbPath = process.env.CLAWNET_MEMORY_DB ?? join5(clawnetDir, "memory.db");
3553
+ const clawnetDir = process.env.CLAWNET_DIR ?? join6(homedir5(), ".clawnet");
3554
+ const dbPath = process.env.CLAWNET_MEMORY_DB ?? join6(clawnetDir, "memory.db");
3192
3555
  let memoryStore = null;
3193
3556
  let embeddingService = null;
3194
3557
  let evolutionPipeline = null;
@@ -3201,7 +3564,7 @@ async function startAgent(options) {
3201
3564
  skillStore = new SkillStore(clawnetDir);
3202
3565
  evolutionPipeline = new EvolutionPipeline(clawnetDir);
3203
3566
  } catch (err) {
3204
- log9.warn({ err }, "failed to init memory/skill infra; distillation disabled");
3567
+ log10.warn({ err }, "failed to init memory/skill infra; distillation disabled");
3205
3568
  }
3206
3569
  const onBeforeClose = async (sessionId, messages) => {
3207
3570
  if (messages.length === 0) return;
@@ -3214,7 +3577,7 @@ async function startAgent(options) {
3214
3577
  embeddingService
3215
3578
  );
3216
3579
  } catch (err) {
3217
- log9.warn({ err, sessionId }, "distillation failed (non-fatal)");
3580
+ log10.warn({ err, sessionId }, "distillation failed (non-fatal)");
3218
3581
  }
3219
3582
  try {
3220
3583
  if (!skillStore || !evolutionPipeline) return;
@@ -3236,11 +3599,15 @@ async function startAgent(options) {
3236
3599
  await triggerFromAccumulation(signals, evolutionPipeline);
3237
3600
  }
3238
3601
  } catch (err) {
3239
- log9.warn({ err, sessionId }, "accumulation scan failed (non-fatal)");
3602
+ log10.warn({ err, sessionId }, "accumulation scan failed (non-fatal)");
3240
3603
  }
3241
3604
  };
3242
3605
  const sessionManager = new SessionManager({
3243
3606
  adapter: options.adapter,
3607
+ resolveAdapter: async (kind) => {
3608
+ const { createBackendAdapter } = await import("./backend-factory-VRPU3534.js");
3609
+ return createBackendAdapter(kind);
3610
+ },
3244
3611
  onOutput: (sessionId, data) => {
3245
3612
  if (swarmCoordinator.handleRoleOutput(sessionId, data)) return;
3246
3613
  hub.send({
@@ -3254,7 +3621,7 @@ async function startAgent(options) {
3254
3621
  hub.send({
3255
3622
  type: "claude.turn_complete",
3256
3623
  sessionId,
3257
- claudeSessionId: info.claudeSessionId,
3624
+ backendSessionId: info.backendSessionId,
3258
3625
  cost: info.cost,
3259
3626
  duration: info.duration,
3260
3627
  contextUsage: info.contextUsage
@@ -3280,10 +3647,22 @@ async function startAgent(options) {
3280
3647
  try {
3281
3648
  swarmCoordinator.handleRoleCrashed(sessionId, info.reason ?? `exit code=${info.code ?? "null"}`);
3282
3649
  } catch (err) {
3283
- log9.warn({ err, sessionId }, "swarmCoordinator.handleRoleCrashed threw");
3650
+ log10.warn({ err, sessionId }, "swarmCoordinator.handleRoleCrashed threw");
3284
3651
  }
3285
3652
  },
3286
3653
  onBeforeClose,
3654
+ onPermissionRequest: (sessionId, req) => {
3655
+ hub.send({
3656
+ type: "agent.permission_request",
3657
+ sessionId,
3658
+ requestId: req.callId,
3659
+ toolName: req.toolName,
3660
+ args: req.input ?? {},
3661
+ reason: req.meta?.reason,
3662
+ backendType: req.meta?.backend,
3663
+ meta: req.meta
3664
+ });
3665
+ },
3287
3666
  // PR-A: classify session kind for the idle sweeper. SessionManager stays
3288
3667
  // independent of the swarm package; we hand it a closure that defers to
3289
3668
  // SwarmCoordinator (created below) at call time. Lazy-read is safe because
@@ -3293,28 +3672,41 @@ async function startAgent(options) {
3293
3672
  // PR-C: enable logical-state checkpoint. Lives next to memory.db under
3294
3673
  // CLAWNET_DIR so backup/wipe affects both consistently. Per-call writes
3295
3674
  // are debounced to 5s by SessionManager itself.
3296
- checkpointPath: join5(clawnetDir, "agent-sessions.json")
3675
+ checkpointPath: join6(clawnetDir, "agent-sessions.json")
3297
3676
  });
3298
3677
  swarmCoordinator = new SwarmCoordinator(sessionManager, hub, (workDir) => {
3299
3678
  try {
3300
3679
  const env = process.env.CLAWNET_DIR;
3301
- const home = env ? env.replace(/\/\.clawnet\/?$/, "") : homedir4();
3680
+ const home = env ? env.replace(/\/\.clawnet\/?$/, "") : homedir5();
3302
3681
  return new TaskStore2({ workDir, home });
3303
3682
  } catch (err) {
3304
- log9.warn({ err, workDir }, "TaskStore factory failed");
3683
+ log10.warn({ err, workDir }, "TaskStore factory failed");
3305
3684
  return void 0;
3306
3685
  }
3307
- }, process.env.CLAWNET_HOME ?? homedir4());
3686
+ }, process.env.CLAWNET_HOME ?? homedir5());
3308
3687
  hub.setSessionManager(sessionManager);
3309
3688
  hub.setSwarmCoordinator(swarmCoordinator);
3689
+ const wakeupHome = process.env.CLAWNET_HOME ?? homedir5();
3690
+ const wakeupScheduler = new WakeupScheduler(swarmCoordinator.inboxRelay);
3691
+ try {
3692
+ const knownSwarms = listRecoverableSwarmIds2().map(({ swarmId, workDir }) => ({
3693
+ swarmId,
3694
+ workDir
3695
+ }));
3696
+ await wakeupScheduler.restoreFromInbox(wakeupHome, knownSwarms);
3697
+ } catch (err) {
3698
+ log10.warn({ err }, "WakeupScheduler.restoreFromInbox failed (non-fatal)");
3699
+ }
3310
3700
  const scheduleRuntime = new ScheduleRuntime({ hub, sessionManager, swarmCoordinator });
3311
3701
  hub.setScheduleRuntime(scheduleRuntime);
3312
3702
  await scheduleRuntime.start();
3313
3703
  const brainBridge = new BrainBridge(hub);
3314
3704
  const fsBridge = new FsBridge(hub);
3705
+ const templatesRolesBridge = new TemplatesRolesBridge(hub);
3315
3706
  sessionManager.startIdleSweeper();
3316
3707
  const shutdown = async () => {
3317
- log9.info("shutting down");
3708
+ log10.info("shutting down");
3709
+ wakeupScheduler.dispose();
3318
3710
  await sessionManager.closeAll();
3319
3711
  await scheduleRuntime.stop();
3320
3712
  hub.destroy();
@@ -3322,7 +3714,7 @@ async function startAgent(options) {
3322
3714
  try {
3323
3715
  memoryDb.close();
3324
3716
  } catch (err) {
3325
- log9.warn({ err }, "failed to close memory db");
3717
+ log10.warn({ err }, "failed to close memory db");
3326
3718
  }
3327
3719
  }
3328
3720
  process.exit(0);
@@ -3330,7 +3722,7 @@ async function startAgent(options) {
3330
3722
  process.on("SIGINT", shutdown);
3331
3723
  process.on("SIGTERM", shutdown);
3332
3724
  hub.connect();
3333
- return { hub, sessionManager, swarmCoordinator, scheduleRuntime, brainBridge, fsBridge };
3725
+ return { hub, sessionManager, swarmCoordinator, scheduleRuntime, brainBridge, fsBridge, templatesRolesBridge };
3334
3726
  }
3335
3727
 
3336
3728
  export {
@@ -3340,4 +3732,4 @@ export {
3340
3732
  FsBridge,
3341
3733
  startAgent
3342
3734
  };
3343
- //# sourceMappingURL=chunk-QPLG5WHL.js.map
3735
+ //# sourceMappingURL=chunk-B733MQCA.js.map