@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.
- package/cli.js +69 -1
- package/dist/__tests__/backend-adapter-types.test.d.ts +2 -0
- package/dist/__tests__/backend-adapter-types.test.d.ts.map +1 -0
- package/dist/__tests__/backend-factory.test.d.ts +2 -0
- package/dist/__tests__/backend-factory.test.d.ts.map +1 -0
- package/dist/__tests__/bootstrap-deps.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-deps.test.d.ts.map +1 -0
- package/dist/__tests__/normalize-backend-output.test.d.ts +2 -0
- package/dist/__tests__/normalize-backend-output.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-env-defaults.test.d.ts +2 -0
- package/dist/__tests__/runtime-env-defaults.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-backend.test.d.ts +2 -0
- package/dist/__tests__/session-manager-backend.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-exit-reason.test.d.ts +2 -0
- package/dist/__tests__/session-manager-exit-reason.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-permission.test.d.ts +2 -0
- package/dist/__tests__/session-manager-permission.test.d.ts.map +1 -0
- package/dist/__tests__/templates-roles-bridge.test.d.ts +2 -0
- package/dist/__tests__/templates-roles-bridge.test.d.ts.map +1 -0
- package/dist/backend-adapter.d.ts +102 -10
- package/dist/backend-adapter.d.ts.map +1 -1
- package/dist/backend-factory-VRPU3534.js +9 -0
- package/dist/backend-factory-VRPU3534.js.map +1 -0
- package/dist/backend-factory.d.ts +19 -0
- package/dist/backend-factory.d.ts.map +1 -0
- package/dist/bootstrap-deps.d.ts +62 -0
- package/dist/bootstrap-deps.d.ts.map +1 -0
- package/dist/bootstrap-deps.js +154 -0
- package/dist/bootstrap-deps.js.map +1 -0
- package/dist/checkpoint.d.ts +1 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/{chunk-QPLG5WHL.js → chunk-B733MQCA.js} +479 -87
- package/dist/chunk-B733MQCA.js.map +1 -0
- package/dist/chunk-FYM7CXUI.js +49 -0
- package/dist/chunk-FYM7CXUI.js.map +1 -0
- package/dist/dist-EGT2NQEW.js +940 -0
- package/dist/dist-EGT2NQEW.js.map +1 -0
- package/dist/fs-handler.d.ts +1 -1
- package/dist/fs-handler.d.ts.map +1 -1
- package/dist/hub-connection.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/normalize-backend-output.d.ts +5 -0
- package/dist/normalize-backend-output.d.ts.map +1 -0
- package/dist/runtime-env-defaults.d.ts +18 -0
- package/dist/runtime-env-defaults.d.ts.map +1 -0
- package/dist/session-manager.d.ts +36 -4
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/start.d.ts +2 -0
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +1 -1
- package/dist/swarm-control-dispatch.d.ts +33 -1
- package/dist/swarm-control-dispatch.d.ts.map +1 -1
- package/dist/swarm-session-bridge.d.ts +3 -3
- package/dist/swarm-session-bridge.d.ts.map +1 -1
- package/dist/templates-roles-bridge.d.ts +14 -0
- package/dist/templates-roles-bridge.d.ts.map +1 -0
- package/package.json +14 -10
- 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
|
|
7
|
-
import { join as
|
|
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,
|
|
171
|
+
async function handleLoadSessionHistory(workDir, backendSessionId, opts) {
|
|
172
172
|
const projectsDir = getClaudeProjectsDir();
|
|
173
173
|
const projectFolder = pathToProjectFolder(workDir);
|
|
174
|
-
const filePath = join(projectsDir, projectFolder, `${
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
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) &&
|
|
1126
|
+
} else if (sm.hasSession(sessionId) && backendSessionId) {
|
|
1045
1127
|
const recommendedMax = sm.getRecommendedMaxOutputTokens(sessionId);
|
|
1046
|
-
log2.warn({ sessionId,
|
|
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(
|
|
1055
|
-
} else if (
|
|
1056
|
-
spawnAndSend(
|
|
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((
|
|
1175
|
+
}).then((backendSessionId) => {
|
|
1090
1176
|
this.send({
|
|
1091
1177
|
type: "session.created",
|
|
1092
1178
|
sessionId: msg.sessionId,
|
|
1093
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
2717
|
-
import { join as
|
|
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 ||
|
|
2725
|
-
this.brainCorePath = options?.brainCorePath ||
|
|
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 (!
|
|
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 =
|
|
2757
|
-
if (!
|
|
3031
|
+
const installJson = join5(home, ".brain", "install.json");
|
|
3032
|
+
if (!existsSync5(installJson)) {
|
|
2758
3033
|
return "needs_config";
|
|
2759
3034
|
}
|
|
2760
|
-
if (!
|
|
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 =
|
|
2769
|
-
if (!
|
|
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(
|
|
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 =
|
|
2979
|
-
if (!
|
|
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
|
|
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 (!
|
|
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/
|
|
3371
|
+
// src/templates-roles-bridge.ts
|
|
3097
3372
|
import { createLogger as createLogger8 } from "@mclawnet/logger";
|
|
3098
|
-
|
|
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
|
-
|
|
3466
|
+
backendSessionId: info.backendSessionId
|
|
3105
3467
|
});
|
|
3106
3468
|
if (deps.swarmCoordinator.isSwarmSession(sessionId)) {
|
|
3107
3469
|
try {
|
|
3108
|
-
const ok = deps.swarmCoordinator.
|
|
3470
|
+
const ok = deps.swarmCoordinator.setRoleBackendSessionIdBySession(
|
|
3109
3471
|
sessionId,
|
|
3110
|
-
info.
|
|
3472
|
+
info.backendSessionId
|
|
3111
3473
|
);
|
|
3112
3474
|
if (!ok) {
|
|
3113
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3509
|
+
log10.error("no token configured \u2014 set CLAWNET_TOKEN or use --token");
|
|
3147
3510
|
process.exit(1);
|
|
3148
3511
|
}
|
|
3149
|
-
|
|
3150
|
-
|
|
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
|
-
|
|
3522
|
+
log10.info({ agentId }, "connected to hub");
|
|
3160
3523
|
const skills = getSkillList();
|
|
3161
3524
|
if (skills.length > 0) {
|
|
3162
3525
|
hub.sendSkillList(skills);
|
|
3163
|
-
|
|
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
|
-
|
|
3534
|
+
backendSessionId: entry.backendSessionId
|
|
3172
3535
|
});
|
|
3173
3536
|
}
|
|
3174
|
-
|
|
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
|
-
|
|
3542
|
+
log10.warn({ err }, "checkpoint recover: failed (degrading to no-op)");
|
|
3180
3543
|
});
|
|
3181
3544
|
},
|
|
3182
3545
|
onDisconnect: (code, reason) => {
|
|
3183
|
-
|
|
3546
|
+
log10.info({ code, reason }, "disconnected from hub");
|
|
3184
3547
|
},
|
|
3185
3548
|
onError: (err) => {
|
|
3186
|
-
|
|
3549
|
+
log10.error({ err }, "hub connection error");
|
|
3187
3550
|
}
|
|
3188
3551
|
});
|
|
3189
3552
|
let swarmCoordinator;
|
|
3190
|
-
const clawnetDir = process.env.CLAWNET_DIR ??
|
|
3191
|
-
const dbPath = process.env.CLAWNET_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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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\/?$/, "") :
|
|
3680
|
+
const home = env ? env.replace(/\/\.clawnet\/?$/, "") : homedir5();
|
|
3302
3681
|
return new TaskStore2({ workDir, home });
|
|
3303
3682
|
} catch (err) {
|
|
3304
|
-
|
|
3683
|
+
log10.warn({ err, workDir }, "TaskStore factory failed");
|
|
3305
3684
|
return void 0;
|
|
3306
3685
|
}
|
|
3307
|
-
}, process.env.CLAWNET_HOME ??
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
3735
|
+
//# sourceMappingURL=chunk-B733MQCA.js.map
|