@mclawnet/agent 0.6.33 → 0.6.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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__/normalize-backend-output.test.d.ts +2 -0
- package/dist/__tests__/normalize-backend-output.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-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-RUYUBJVF.js +9 -0
- package/dist/backend-factory-RUYUBJVF.js.map +1 -0
- package/dist/backend-factory.d.ts +19 -0
- package/dist/backend-factory.d.ts.map +1 -0
- package/dist/checkpoint.d.ts +1 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/{chunk-QPLG5WHL.js → chunk-2JDX6XFD.js} +445 -84
- package/dist/chunk-2JDX6XFD.js.map +1 -0
- package/dist/chunk-MFXF77LG.js +49 -0
- package/dist/chunk-MFXF77LG.js.map +1 -0
- package/dist/dist-VLBO5CT3.js +775 -0
- package/dist/dist-VLBO5CT3.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/session-manager.d.ts +35 -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 +13 -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() {
|
|
@@ -2152,6 +2311,23 @@ var SessionManager = class {
|
|
|
2152
2311
|
*/
|
|
2153
2312
|
recoveredLadderIndex = /* @__PURE__ */ new Map();
|
|
2154
2313
|
adapter;
|
|
2314
|
+
/**
|
|
2315
|
+
* Per-process adapter override. Populated by `createSession` when
|
|
2316
|
+
* `options.backend` is set and `resolveAdapter` returns a non-default
|
|
2317
|
+
* adapter. All adapter calls (send/stop/onOutput/...) consult
|
|
2318
|
+
* `getAdapterFor(process)` instead of `this.adapter` directly.
|
|
2319
|
+
*/
|
|
2320
|
+
processAdapters = /* @__PURE__ */ new WeakMap();
|
|
2321
|
+
/**
|
|
2322
|
+
* Optional resolver for per-spawn alternate backends. Wired by the agent
|
|
2323
|
+
* host to `createBackendAdapter` so role.backend can pick codex/claude.
|
|
2324
|
+
* When omitted, options.backend is ignored and `this.adapter` is used.
|
|
2325
|
+
*/
|
|
2326
|
+
resolveAdapter;
|
|
2327
|
+
/** Resolve the adapter that owns a particular spawned process. */
|
|
2328
|
+
getAdapterFor(proc) {
|
|
2329
|
+
return this.processAdapters.get(proc) ?? this.adapter;
|
|
2330
|
+
}
|
|
2155
2331
|
onOutput;
|
|
2156
2332
|
onTurnComplete;
|
|
2157
2333
|
onSessionError;
|
|
@@ -2165,6 +2341,7 @@ var SessionManager = class {
|
|
|
2165
2341
|
* swarm-role exits to SwarmCoordinator.handleRoleCrashed.
|
|
2166
2342
|
*/
|
|
2167
2343
|
onSessionExit;
|
|
2344
|
+
onPermissionRequest;
|
|
2168
2345
|
// PR-A: classifies a sessionId as 'chat' or 'swarm-role'. Injected by
|
|
2169
2346
|
// start.ts via SwarmCoordinator.isSwarmSession to keep SessionManager
|
|
2170
2347
|
// independent of the swarm package. Defaults to 'chat' if absent — safe
|
|
@@ -2173,12 +2350,14 @@ var SessionManager = class {
|
|
|
2173
2350
|
classify;
|
|
2174
2351
|
constructor(options) {
|
|
2175
2352
|
this.adapter = options.adapter;
|
|
2353
|
+
this.resolveAdapter = options.resolveAdapter;
|
|
2176
2354
|
this.onOutput = options.onOutput;
|
|
2177
2355
|
this.onTurnComplete = options.onTurnComplete;
|
|
2178
2356
|
this.onSessionError = options.onSessionError;
|
|
2179
2357
|
this.onSessionStarted = options.onSessionStarted;
|
|
2180
2358
|
this.onBeforeClose = options.onBeforeClose;
|
|
2181
2359
|
this.onSessionExit = options.onSessionExit;
|
|
2360
|
+
this.onPermissionRequest = options.onPermissionRequest;
|
|
2182
2361
|
this.classify = options.classify ?? (() => "chat");
|
|
2183
2362
|
this.checkpointPath = options.checkpointPath ?? null;
|
|
2184
2363
|
this.checkpointDebounceMs = options.checkpointDebounceMs ?? 5e3;
|
|
@@ -2234,7 +2413,37 @@ ${notice.text}`;
|
|
|
2234
2413
|
options.systemPrompt = options.systemPrompt ? `${options.systemPrompt}${ideaHint}` : ideaHint.trimStart();
|
|
2235
2414
|
}
|
|
2236
2415
|
try {
|
|
2237
|
-
|
|
2416
|
+
let spawnAdapter = this.adapter;
|
|
2417
|
+
if (options.backend && this.resolveAdapter) {
|
|
2418
|
+
const resolved = await this.resolveAdapter(options.backend);
|
|
2419
|
+
log5.info(
|
|
2420
|
+
{
|
|
2421
|
+
sessionId: options.sessionId,
|
|
2422
|
+
requested: options.backend,
|
|
2423
|
+
resolvedType: resolved.type,
|
|
2424
|
+
defaultType: this.adapter.type,
|
|
2425
|
+
willSwitch: resolved.type !== this.adapter.type
|
|
2426
|
+
},
|
|
2427
|
+
"createSession: adapter resolved"
|
|
2428
|
+
);
|
|
2429
|
+
if (resolved.type !== this.adapter.type) {
|
|
2430
|
+
spawnAdapter = resolved;
|
|
2431
|
+
}
|
|
2432
|
+
} else {
|
|
2433
|
+
log5.info(
|
|
2434
|
+
{
|
|
2435
|
+
sessionId: options.sessionId,
|
|
2436
|
+
requestedBackend: options.backend,
|
|
2437
|
+
hasResolver: !!this.resolveAdapter,
|
|
2438
|
+
defaultType: this.adapter.type
|
|
2439
|
+
},
|
|
2440
|
+
"createSession: using default adapter"
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
const process2 = await spawnAdapter.spawn(options);
|
|
2444
|
+
if (spawnAdapter !== this.adapter) {
|
|
2445
|
+
this.processAdapters.set(process2, spawnAdapter);
|
|
2446
|
+
}
|
|
2238
2447
|
this.sessions.set(options.sessionId, process2);
|
|
2239
2448
|
const now = Date.now();
|
|
2240
2449
|
const kind = this.classify(options.sessionId);
|
|
@@ -2253,7 +2462,9 @@ ${notice.text}`;
|
|
|
2253
2462
|
});
|
|
2254
2463
|
this.recoveredLadderIndex.delete(options.sessionId);
|
|
2255
2464
|
this.scheduleCheckpoint();
|
|
2256
|
-
|
|
2465
|
+
spawnAdapter.onOutput(process2, (rawData) => {
|
|
2466
|
+
const data = normalizeBackendOutput(rawData, log5);
|
|
2467
|
+
if (data === null) return;
|
|
2257
2468
|
const msg = data;
|
|
2258
2469
|
if (msg?.type === "assistant" && msg?.message?.content) {
|
|
2259
2470
|
const buf = this.conversationBuffer.get(options.sessionId) ?? [];
|
|
@@ -2265,7 +2476,7 @@ ${notice.text}`;
|
|
|
2265
2476
|
}
|
|
2266
2477
|
this.onOutput(options.sessionId, data);
|
|
2267
2478
|
});
|
|
2268
|
-
|
|
2479
|
+
spawnAdapter.onTurnComplete?.(process2, (info) => {
|
|
2269
2480
|
const meta = this.sessionMeta.get(options.sessionId);
|
|
2270
2481
|
if (meta) {
|
|
2271
2482
|
meta.lastActivityAt = Date.now();
|
|
@@ -2274,11 +2485,11 @@ ${notice.text}`;
|
|
|
2274
2485
|
this.activelyExecuting.delete(options.sessionId);
|
|
2275
2486
|
this.onTurnComplete(options.sessionId, info);
|
|
2276
2487
|
});
|
|
2277
|
-
|
|
2488
|
+
spawnAdapter.onError?.(process2, (error) => {
|
|
2278
2489
|
this.activelyExecuting.delete(options.sessionId);
|
|
2279
2490
|
this.onSessionError(options.sessionId, error.message);
|
|
2280
2491
|
});
|
|
2281
|
-
|
|
2492
|
+
spawnAdapter.onTokenBudgetWarning?.(process2, (info) => {
|
|
2282
2493
|
const meta = this.sessionMeta.get(options.sessionId);
|
|
2283
2494
|
if (!meta) return;
|
|
2284
2495
|
const currentIndex = clampLadderIndex(meta.currentLadderIndex);
|
|
@@ -2311,7 +2522,7 @@ ${notice.text}`;
|
|
|
2311
2522
|
this.scheduleCheckpoint();
|
|
2312
2523
|
});
|
|
2313
2524
|
if (this.onSessionStarted) {
|
|
2314
|
-
|
|
2525
|
+
spawnAdapter.onSessionStarted?.(process2, (info) => {
|
|
2315
2526
|
if (this.aborting.has(options.sessionId)) {
|
|
2316
2527
|
log5.debug(
|
|
2317
2528
|
{ sessionId: options.sessionId },
|
|
@@ -2323,12 +2534,45 @@ ${notice.text}`;
|
|
|
2323
2534
|
return;
|
|
2324
2535
|
}
|
|
2325
2536
|
const meta = this.sessionMeta.get(options.sessionId);
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2537
|
+
const sid = info.backendSessionId;
|
|
2538
|
+
let sidIsTrustworthy = true;
|
|
2539
|
+
const workDir = meta?.workDir;
|
|
2540
|
+
const isClaudeAdapter = spawnAdapter.type === "claude-code";
|
|
2541
|
+
if (isClaudeAdapter && workDir && sid) {
|
|
2542
|
+
const encoded = workDir.replace(/\//g, "-");
|
|
2543
|
+
const jsonlPath = join4(
|
|
2544
|
+
homedir4(),
|
|
2545
|
+
".claude",
|
|
2546
|
+
"projects",
|
|
2547
|
+
encoded,
|
|
2548
|
+
`${sid}.jsonl`
|
|
2549
|
+
);
|
|
2550
|
+
if (!existsSync4(jsonlPath)) {
|
|
2551
|
+
log5.warn(
|
|
2552
|
+
{
|
|
2553
|
+
sessionId: options.sessionId,
|
|
2554
|
+
backendSessionId: sid,
|
|
2555
|
+
jsonlPath
|
|
2556
|
+
},
|
|
2557
|
+
"session_started reports backendSessionId whose jsonl is missing on disk \u2014 refusing to persist to avoid ghost-sid propagation"
|
|
2558
|
+
);
|
|
2559
|
+
sidIsTrustworthy = false;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
if (meta && sidIsTrustworthy) meta.backendSessionId = sid;
|
|
2563
|
+
if (sidIsTrustworthy) this.scheduleCheckpoint();
|
|
2564
|
+
this.onSessionStarted(options.sessionId, {
|
|
2565
|
+
backendSessionId: sid
|
|
2566
|
+
});
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
if (this.onPermissionRequest) {
|
|
2570
|
+
spawnAdapter.onPermissionRequest?.(process2, (req) => {
|
|
2571
|
+
if (this.sessions.get(options.sessionId) !== process2) return;
|
|
2572
|
+
this.onPermissionRequest(options.sessionId, req);
|
|
2329
2573
|
});
|
|
2330
2574
|
}
|
|
2331
|
-
|
|
2575
|
+
spawnAdapter.onExit?.(process2, (code) => {
|
|
2332
2576
|
if (this.sessions.get(options.sessionId) === process2) {
|
|
2333
2577
|
const expected = this.expectedExits.delete(options.sessionId);
|
|
2334
2578
|
this.sessions.delete(options.sessionId);
|
|
@@ -2383,7 +2627,7 @@ ${notice.text}`;
|
|
|
2383
2627
|
{ sessionId, ...previewFields2(input) },
|
|
2384
2628
|
"sendInput \u2192 backend stdin"
|
|
2385
2629
|
);
|
|
2386
|
-
this.
|
|
2630
|
+
this.getAdapterFor(process2).send(process2, input);
|
|
2387
2631
|
}
|
|
2388
2632
|
/**
|
|
2389
2633
|
* User-input variant of sendInput: best-effort prepend a `<memory-context>`
|
|
@@ -2429,11 +2673,24 @@ ${content}`;
|
|
|
2429
2673
|
this.activelyExecuting.delete(sessionId);
|
|
2430
2674
|
this.scheduleCheckpoint();
|
|
2431
2675
|
try {
|
|
2432
|
-
await this.
|
|
2676
|
+
await this.getAdapterFor(process2).stop(process2);
|
|
2433
2677
|
} finally {
|
|
2434
2678
|
this.aborting.delete(sessionId);
|
|
2435
2679
|
}
|
|
2436
2680
|
}
|
|
2681
|
+
/**
|
|
2682
|
+
* M3.S2b — Deliver a permission decision back to the adapter for the named
|
|
2683
|
+
* session. No-ops silently when the adapter doesn't implement the optional
|
|
2684
|
+
* `respondToPermission` (e.g. claude-adapter today). Throws when the session
|
|
2685
|
+
* is unknown so callers don't silently lose decisions to dead sessions.
|
|
2686
|
+
*/
|
|
2687
|
+
async respondToPermission(sessionId, decision) {
|
|
2688
|
+
const process2 = this.sessions.get(sessionId);
|
|
2689
|
+
if (!process2) {
|
|
2690
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
2691
|
+
}
|
|
2692
|
+
await this.getAdapterFor(process2).respondToPermission?.(process2, decision);
|
|
2693
|
+
}
|
|
2437
2694
|
async closeSession(sessionId) {
|
|
2438
2695
|
const process2 = this.sessions.get(sessionId);
|
|
2439
2696
|
if (!process2) return;
|
|
@@ -2448,7 +2705,7 @@ ${content}`;
|
|
|
2448
2705
|
this.sessionMeta.delete(sessionId);
|
|
2449
2706
|
this.activelyExecuting.delete(sessionId);
|
|
2450
2707
|
this.scheduleCheckpoint();
|
|
2451
|
-
await this.
|
|
2708
|
+
await this.getAdapterFor(process2).stop(process2);
|
|
2452
2709
|
}
|
|
2453
2710
|
async closeAll() {
|
|
2454
2711
|
this.stopIdleSweeper();
|
|
@@ -2464,7 +2721,7 @@ ${content}`;
|
|
|
2464
2721
|
this.sessions.delete(sessionId);
|
|
2465
2722
|
this.sessionMeta.delete(sessionId);
|
|
2466
2723
|
this.activelyExecuting.delete(sessionId);
|
|
2467
|
-
await this.
|
|
2724
|
+
await this.getAdapterFor(process2).stop(process2).catch(() => {
|
|
2468
2725
|
});
|
|
2469
2726
|
}
|
|
2470
2727
|
);
|
|
@@ -2486,7 +2743,7 @@ ${content}`;
|
|
|
2486
2743
|
* Stricter than hasSession(): also requires the underlying process to still
|
|
2487
2744
|
* be alive (not exited, stdin still writable). Callers in the reuse path
|
|
2488
2745
|
* must use this to avoid sending input into a dead pipe — if it returns
|
|
2489
|
-
* false they should fall back to `--resume` against db's
|
|
2746
|
+
* false they should fall back to `--resume` against db's backendSessionId.
|
|
2490
2747
|
*/
|
|
2491
2748
|
isHealthy(sessionId) {
|
|
2492
2749
|
if (this.aborting.has(sessionId)) return false;
|
|
@@ -2656,7 +2913,7 @@ ${content}`;
|
|
|
2656
2913
|
sessionId: sid,
|
|
2657
2914
|
kind: meta.kind,
|
|
2658
2915
|
workDir: meta.workDir,
|
|
2659
|
-
|
|
2916
|
+
backendSessionId: meta.backendSessionId,
|
|
2660
2917
|
agentInstanceId: meta.agentInstanceId,
|
|
2661
2918
|
startedAt: meta.startedAt,
|
|
2662
2919
|
lastActivityAt: meta.lastActivityAt,
|
|
@@ -2713,16 +2970,16 @@ import { SwarmCoordinator, initRoles } from "@mclawnet/swarm";
|
|
|
2713
2970
|
import { TaskStore as TaskStore2 } from "@mclawnet/task";
|
|
2714
2971
|
|
|
2715
2972
|
// src/brain-bridge.ts
|
|
2716
|
-
import { existsSync as
|
|
2717
|
-
import { join as
|
|
2973
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync4 } from "fs";
|
|
2974
|
+
import { join as join5 } from "path";
|
|
2718
2975
|
import { createLogger as createLogger6 } from "@mclawnet/logger";
|
|
2719
2976
|
var log6 = createLogger6({ module: "brain-bridge" });
|
|
2720
2977
|
var BrainBridge = class {
|
|
2721
2978
|
constructor(hub, options) {
|
|
2722
2979
|
this.hub = hub;
|
|
2723
2980
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
2724
|
-
this.brainHome = options?.brainHomePath || process.env.BRAIN_HOME ||
|
|
2725
|
-
this.brainCorePath = options?.brainCorePath ||
|
|
2981
|
+
this.brainHome = options?.brainHomePath || process.env.BRAIN_HOME || join5(home, "BrainData");
|
|
2982
|
+
this.brainCorePath = options?.brainCorePath || join5(home, ".brain", "BrainCore");
|
|
2726
2983
|
this.hub.registerNamespace("brain", (msg) => this.handleRequest(msg));
|
|
2727
2984
|
log6.info(
|
|
2728
2985
|
{ brainHome: this.brainHome, brainCorePath: this.brainCorePath },
|
|
@@ -2749,15 +3006,15 @@ var BrainBridge = class {
|
|
|
2749
3006
|
}
|
|
2750
3007
|
}
|
|
2751
3008
|
checkSetup() {
|
|
2752
|
-
if (!
|
|
3009
|
+
if (!existsSync5(this.brainCorePath)) {
|
|
2753
3010
|
return "not_installed";
|
|
2754
3011
|
}
|
|
2755
3012
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
2756
|
-
const installJson =
|
|
2757
|
-
if (!
|
|
3013
|
+
const installJson = join5(home, ".brain", "install.json");
|
|
3014
|
+
if (!existsSync5(installJson)) {
|
|
2758
3015
|
return "needs_config";
|
|
2759
3016
|
}
|
|
2760
|
-
if (!
|
|
3017
|
+
if (!existsSync5(this.brainHome)) {
|
|
2761
3018
|
return "needs_config";
|
|
2762
3019
|
}
|
|
2763
3020
|
return "ready";
|
|
@@ -2765,8 +3022,8 @@ var BrainBridge = class {
|
|
|
2765
3022
|
/** Read the most recent daily briefing report */
|
|
2766
3023
|
async getBriefing(date) {
|
|
2767
3024
|
const targetDate = date || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2768
|
-
const reportsDir =
|
|
2769
|
-
if (!
|
|
3025
|
+
const reportsDir = join5(this.brainHome, "reports", "daily");
|
|
3026
|
+
if (!existsSync5(reportsDir)) {
|
|
2770
3027
|
log6.info({ reportsDir }, "get_briefing: reports dir not found");
|
|
2771
3028
|
return { briefing: null, actions: [], projects: [], meetings: [], feed: [] };
|
|
2772
3029
|
}
|
|
@@ -2776,7 +3033,7 @@ var BrainBridge = class {
|
|
|
2776
3033
|
return { briefing: null, actions: [], projects: [], meetings: [], feed: [] };
|
|
2777
3034
|
}
|
|
2778
3035
|
log6.info({ targetDate, file: files[0] }, "get_briefing: reading report");
|
|
2779
|
-
const content = readFileSync3(
|
|
3036
|
+
const content = readFileSync3(join5(reportsDir, files[0]), "utf-8");
|
|
2780
3037
|
const tldrMatch = content.match(/## TL;DR\n([\s\S]*?)(?=\n---|\n## )/);
|
|
2781
3038
|
const tldr = tldrMatch ? tldrMatch[1].trim() : content.slice(0, 200);
|
|
2782
3039
|
const actions = this.parseActions(content);
|
|
@@ -2975,8 +3232,8 @@ var BrainBridge = class {
|
|
|
2975
3232
|
if (!recapPath) {
|
|
2976
3233
|
return { error: "No recap path provided" };
|
|
2977
3234
|
}
|
|
2978
|
-
const fullPath =
|
|
2979
|
-
if (!
|
|
3235
|
+
const fullPath = join5(this.brainHome, recapPath);
|
|
3236
|
+
if (!existsSync5(fullPath)) {
|
|
2980
3237
|
log6.warn({ fullPath }, "meeting recap file not found");
|
|
2981
3238
|
return { error: "Recap file not found" };
|
|
2982
3239
|
}
|
|
@@ -3013,7 +3270,7 @@ var BrainBridge = class {
|
|
|
3013
3270
|
};
|
|
3014
3271
|
|
|
3015
3272
|
// src/fs-bridge.ts
|
|
3016
|
-
import { existsSync as
|
|
3273
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
3017
3274
|
import { extname, isAbsolute } from "path";
|
|
3018
3275
|
import { createLogger as createLogger7 } from "@mclawnet/logger";
|
|
3019
3276
|
var log7 = createLogger7({ module: "fs-bridge" });
|
|
@@ -3071,7 +3328,7 @@ var FsBridge = class {
|
|
|
3071
3328
|
if (!isAbsolute(filePath)) {
|
|
3072
3329
|
throw new Error(`Access denied: only absolute paths allowed`);
|
|
3073
3330
|
}
|
|
3074
|
-
if (!
|
|
3331
|
+
if (!existsSync6(filePath)) {
|
|
3075
3332
|
throw new Error(`File not found: ${filePath}`);
|
|
3076
3333
|
}
|
|
3077
3334
|
const stat2 = statSync2(filePath);
|
|
@@ -3093,37 +3350,124 @@ var FsBridge = class {
|
|
|
3093
3350
|
}
|
|
3094
3351
|
};
|
|
3095
3352
|
|
|
3096
|
-
// src/
|
|
3353
|
+
// src/templates-roles-bridge.ts
|
|
3097
3354
|
import { createLogger as createLogger8 } from "@mclawnet/logger";
|
|
3098
|
-
|
|
3355
|
+
import {
|
|
3356
|
+
listTemplatesWithSource,
|
|
3357
|
+
getTemplateRaw,
|
|
3358
|
+
getTemplateSource,
|
|
3359
|
+
saveTemplate,
|
|
3360
|
+
deleteTemplate,
|
|
3361
|
+
loadTemplate as loadTemplate2,
|
|
3362
|
+
listRolesWithSource,
|
|
3363
|
+
getRoleRaw,
|
|
3364
|
+
getRoleSource,
|
|
3365
|
+
saveRole,
|
|
3366
|
+
deleteRole,
|
|
3367
|
+
loadRole as loadRole2
|
|
3368
|
+
} from "@mclawnet/swarm";
|
|
3369
|
+
var log8 = createLogger8({ module: "templates-roles-bridge" });
|
|
3370
|
+
function requireStringParam(params, key) {
|
|
3371
|
+
const v = params[key];
|
|
3372
|
+
if (typeof v !== "string" || !v) throw new Error(`Missing '${key}' parameter`);
|
|
3373
|
+
return v;
|
|
3374
|
+
}
|
|
3375
|
+
var TemplatesRolesBridge = class {
|
|
3376
|
+
constructor(hub) {
|
|
3377
|
+
this.hub = hub;
|
|
3378
|
+
this.hub.registerNamespace("templates", (msg) => this.handleTemplates(msg));
|
|
3379
|
+
this.hub.registerNamespace("roles", (msg) => this.handleRoles(msg));
|
|
3380
|
+
log8.info("TemplatesRolesBridge initialized");
|
|
3381
|
+
}
|
|
3382
|
+
async handleTemplates(msg) {
|
|
3383
|
+
log8.info({ action: msg.action, requestId: msg.requestId }, "templates request");
|
|
3384
|
+
switch (msg.action) {
|
|
3385
|
+
case "list":
|
|
3386
|
+
return { items: listTemplatesWithSource() };
|
|
3387
|
+
case "get": {
|
|
3388
|
+
const name = requireStringParam(msg.params, "name");
|
|
3389
|
+
const raw = getTemplateRaw(name);
|
|
3390
|
+
if (raw == null) throw new Error(`Template not found: ${name}`);
|
|
3391
|
+
const parsed = loadTemplate2(name);
|
|
3392
|
+
const source = getTemplateSource(name);
|
|
3393
|
+
return { raw, parsed, source };
|
|
3394
|
+
}
|
|
3395
|
+
case "save": {
|
|
3396
|
+
const name = requireStringParam(msg.params, "name");
|
|
3397
|
+
const raw = requireStringParam(msg.params, "raw");
|
|
3398
|
+
const source = saveTemplate(name, raw);
|
|
3399
|
+
return { ok: true, source };
|
|
3400
|
+
}
|
|
3401
|
+
case "delete": {
|
|
3402
|
+
const name = requireStringParam(msg.params, "name");
|
|
3403
|
+
const r = deleteTemplate(name);
|
|
3404
|
+
return { ok: true, ...r };
|
|
3405
|
+
}
|
|
3406
|
+
default:
|
|
3407
|
+
throw new Error(`Unknown templates action: ${msg.action}`);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
async handleRoles(msg) {
|
|
3411
|
+
log8.info({ action: msg.action, requestId: msg.requestId }, "roles request");
|
|
3412
|
+
switch (msg.action) {
|
|
3413
|
+
case "list":
|
|
3414
|
+
return { items: listRolesWithSource() };
|
|
3415
|
+
case "get": {
|
|
3416
|
+
const name = requireStringParam(msg.params, "name");
|
|
3417
|
+
const raw = getRoleRaw(name);
|
|
3418
|
+
if (raw == null) throw new Error(`Role not found: ${name}`);
|
|
3419
|
+
const parsed = loadRole2(name);
|
|
3420
|
+
const source = getRoleSource(name);
|
|
3421
|
+
return { raw, parsed, source };
|
|
3422
|
+
}
|
|
3423
|
+
case "save": {
|
|
3424
|
+
const name = requireStringParam(msg.params, "name");
|
|
3425
|
+
const raw = requireStringParam(msg.params, "raw");
|
|
3426
|
+
const source = saveRole(name, raw);
|
|
3427
|
+
return { ok: true, source };
|
|
3428
|
+
}
|
|
3429
|
+
case "delete": {
|
|
3430
|
+
const name = requireStringParam(msg.params, "name");
|
|
3431
|
+
const r = deleteRole(name);
|
|
3432
|
+
return { ok: true, ...r };
|
|
3433
|
+
}
|
|
3434
|
+
default:
|
|
3435
|
+
throw new Error(`Unknown roles action: ${msg.action}`);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
};
|
|
3439
|
+
|
|
3440
|
+
// src/swarm-session-bridge.ts
|
|
3441
|
+
import { createLogger as createLogger9 } from "@mclawnet/logger";
|
|
3442
|
+
var log9 = createLogger9({ module: "agent:swarm-bridge" });
|
|
3099
3443
|
function createSwarmAwareSessionStartedHandler(deps) {
|
|
3100
3444
|
return (sessionId, info) => {
|
|
3101
3445
|
deps.hub.send({
|
|
3102
3446
|
type: "claude.session_started",
|
|
3103
3447
|
sessionId,
|
|
3104
|
-
|
|
3448
|
+
backendSessionId: info.backendSessionId
|
|
3105
3449
|
});
|
|
3106
3450
|
if (deps.swarmCoordinator.isSwarmSession(sessionId)) {
|
|
3107
3451
|
try {
|
|
3108
|
-
const ok = deps.swarmCoordinator.
|
|
3452
|
+
const ok = deps.swarmCoordinator.setRoleBackendSessionIdBySession(
|
|
3109
3453
|
sessionId,
|
|
3110
|
-
info.
|
|
3454
|
+
info.backendSessionId
|
|
3111
3455
|
);
|
|
3112
3456
|
if (!ok) {
|
|
3113
|
-
|
|
3457
|
+
log9.debug(
|
|
3114
3458
|
{ sessionId },
|
|
3115
3459
|
"session_started for swarm-shaped sessionId, but no matching role (already destroyed?)"
|
|
3116
3460
|
);
|
|
3117
3461
|
}
|
|
3118
3462
|
} catch (err) {
|
|
3119
|
-
|
|
3463
|
+
log9.warn({ err, sessionId }, "failed to bridge session_started into swarm");
|
|
3120
3464
|
}
|
|
3121
3465
|
}
|
|
3122
3466
|
};
|
|
3123
3467
|
}
|
|
3124
3468
|
|
|
3125
3469
|
// src/start.ts
|
|
3126
|
-
import { createLogger as
|
|
3470
|
+
import { createLogger as createLogger10 } from "@mclawnet/logger";
|
|
3127
3471
|
import {
|
|
3128
3472
|
initDatabase as initDatabase2,
|
|
3129
3473
|
MemoryStore,
|
|
@@ -3139,15 +3483,15 @@ import {
|
|
|
3139
3483
|
triggerFromAccumulation
|
|
3140
3484
|
} from "@mclawnet/skill-manager";
|
|
3141
3485
|
import { PROJECTS_RPC_CAPABILITY } from "@mclawnet/shared";
|
|
3142
|
-
var
|
|
3486
|
+
var log10 = createLogger10({ module: "agent" });
|
|
3143
3487
|
async function startAgent(options) {
|
|
3144
3488
|
const config = loadConfig(options.config);
|
|
3145
3489
|
if (!config.token) {
|
|
3146
|
-
|
|
3490
|
+
log10.error("no token configured \u2014 set CLAWNET_TOKEN or use --token");
|
|
3147
3491
|
process.exit(1);
|
|
3148
3492
|
}
|
|
3149
|
-
|
|
3150
|
-
|
|
3493
|
+
log10.info({ backend: options.adapter.type }, "starting agent");
|
|
3494
|
+
log10.info({ hubUrl: config.hubUrl }, "connecting to hub");
|
|
3151
3495
|
await initRoles();
|
|
3152
3496
|
await initSkills();
|
|
3153
3497
|
const hub = new HubConnection({
|
|
@@ -3156,11 +3500,11 @@ async function startAgent(options) {
|
|
|
3156
3500
|
hostname: config.name,
|
|
3157
3501
|
capabilities: [PROJECTS_RPC_CAPABILITY],
|
|
3158
3502
|
onConnect: (agentId) => {
|
|
3159
|
-
|
|
3503
|
+
log10.info({ agentId }, "connected to hub");
|
|
3160
3504
|
const skills = getSkillList();
|
|
3161
3505
|
if (skills.length > 0) {
|
|
3162
3506
|
hub.sendSkillList(skills);
|
|
3163
|
-
|
|
3507
|
+
log10.info({ count: skills.length }, "pushed skill list to hub");
|
|
3164
3508
|
}
|
|
3165
3509
|
void sessionManager.recoverFromCheckpoint().then((report) => {
|
|
3166
3510
|
for (const entry of report.dead) {
|
|
@@ -3168,27 +3512,27 @@ async function startAgent(options) {
|
|
|
3168
3512
|
type: "session.died",
|
|
3169
3513
|
sessionId: entry.sessionId,
|
|
3170
3514
|
reason: "agent_restart",
|
|
3171
|
-
|
|
3515
|
+
backendSessionId: entry.backendSessionId
|
|
3172
3516
|
});
|
|
3173
3517
|
}
|
|
3174
|
-
|
|
3518
|
+
log10.info(
|
|
3175
3519
|
{ deadCount: report.dead.length, orphanCount: report.orphan.length },
|
|
3176
3520
|
"checkpoint recover: cleanup complete"
|
|
3177
3521
|
);
|
|
3178
3522
|
}).catch((err) => {
|
|
3179
|
-
|
|
3523
|
+
log10.warn({ err }, "checkpoint recover: failed (degrading to no-op)");
|
|
3180
3524
|
});
|
|
3181
3525
|
},
|
|
3182
3526
|
onDisconnect: (code, reason) => {
|
|
3183
|
-
|
|
3527
|
+
log10.info({ code, reason }, "disconnected from hub");
|
|
3184
3528
|
},
|
|
3185
3529
|
onError: (err) => {
|
|
3186
|
-
|
|
3530
|
+
log10.error({ err }, "hub connection error");
|
|
3187
3531
|
}
|
|
3188
3532
|
});
|
|
3189
3533
|
let swarmCoordinator;
|
|
3190
|
-
const clawnetDir = process.env.CLAWNET_DIR ??
|
|
3191
|
-
const dbPath = process.env.CLAWNET_MEMORY_DB ??
|
|
3534
|
+
const clawnetDir = process.env.CLAWNET_DIR ?? join6(homedir5(), ".clawnet");
|
|
3535
|
+
const dbPath = process.env.CLAWNET_MEMORY_DB ?? join6(clawnetDir, "memory.db");
|
|
3192
3536
|
let memoryStore = null;
|
|
3193
3537
|
let embeddingService = null;
|
|
3194
3538
|
let evolutionPipeline = null;
|
|
@@ -3201,7 +3545,7 @@ async function startAgent(options) {
|
|
|
3201
3545
|
skillStore = new SkillStore(clawnetDir);
|
|
3202
3546
|
evolutionPipeline = new EvolutionPipeline(clawnetDir);
|
|
3203
3547
|
} catch (err) {
|
|
3204
|
-
|
|
3548
|
+
log10.warn({ err }, "failed to init memory/skill infra; distillation disabled");
|
|
3205
3549
|
}
|
|
3206
3550
|
const onBeforeClose = async (sessionId, messages) => {
|
|
3207
3551
|
if (messages.length === 0) return;
|
|
@@ -3214,7 +3558,7 @@ async function startAgent(options) {
|
|
|
3214
3558
|
embeddingService
|
|
3215
3559
|
);
|
|
3216
3560
|
} catch (err) {
|
|
3217
|
-
|
|
3561
|
+
log10.warn({ err, sessionId }, "distillation failed (non-fatal)");
|
|
3218
3562
|
}
|
|
3219
3563
|
try {
|
|
3220
3564
|
if (!skillStore || !evolutionPipeline) return;
|
|
@@ -3236,11 +3580,15 @@ async function startAgent(options) {
|
|
|
3236
3580
|
await triggerFromAccumulation(signals, evolutionPipeline);
|
|
3237
3581
|
}
|
|
3238
3582
|
} catch (err) {
|
|
3239
|
-
|
|
3583
|
+
log10.warn({ err, sessionId }, "accumulation scan failed (non-fatal)");
|
|
3240
3584
|
}
|
|
3241
3585
|
};
|
|
3242
3586
|
const sessionManager = new SessionManager({
|
|
3243
3587
|
adapter: options.adapter,
|
|
3588
|
+
resolveAdapter: async (kind) => {
|
|
3589
|
+
const { createBackendAdapter } = await import("./backend-factory-RUYUBJVF.js");
|
|
3590
|
+
return createBackendAdapter(kind);
|
|
3591
|
+
},
|
|
3244
3592
|
onOutput: (sessionId, data) => {
|
|
3245
3593
|
if (swarmCoordinator.handleRoleOutput(sessionId, data)) return;
|
|
3246
3594
|
hub.send({
|
|
@@ -3254,7 +3602,7 @@ async function startAgent(options) {
|
|
|
3254
3602
|
hub.send({
|
|
3255
3603
|
type: "claude.turn_complete",
|
|
3256
3604
|
sessionId,
|
|
3257
|
-
|
|
3605
|
+
backendSessionId: info.backendSessionId,
|
|
3258
3606
|
cost: info.cost,
|
|
3259
3607
|
duration: info.duration,
|
|
3260
3608
|
contextUsage: info.contextUsage
|
|
@@ -3280,10 +3628,22 @@ async function startAgent(options) {
|
|
|
3280
3628
|
try {
|
|
3281
3629
|
swarmCoordinator.handleRoleCrashed(sessionId, info.reason ?? `exit code=${info.code ?? "null"}`);
|
|
3282
3630
|
} catch (err) {
|
|
3283
|
-
|
|
3631
|
+
log10.warn({ err, sessionId }, "swarmCoordinator.handleRoleCrashed threw");
|
|
3284
3632
|
}
|
|
3285
3633
|
},
|
|
3286
3634
|
onBeforeClose,
|
|
3635
|
+
onPermissionRequest: (sessionId, req) => {
|
|
3636
|
+
hub.send({
|
|
3637
|
+
type: "agent.permission_request",
|
|
3638
|
+
sessionId,
|
|
3639
|
+
requestId: req.callId,
|
|
3640
|
+
toolName: req.toolName,
|
|
3641
|
+
args: req.input ?? {},
|
|
3642
|
+
reason: req.meta?.reason,
|
|
3643
|
+
backendType: req.meta?.backend,
|
|
3644
|
+
meta: req.meta
|
|
3645
|
+
});
|
|
3646
|
+
},
|
|
3287
3647
|
// PR-A: classify session kind for the idle sweeper. SessionManager stays
|
|
3288
3648
|
// independent of the swarm package; we hand it a closure that defers to
|
|
3289
3649
|
// SwarmCoordinator (created below) at call time. Lazy-read is safe because
|
|
@@ -3293,18 +3653,18 @@ async function startAgent(options) {
|
|
|
3293
3653
|
// PR-C: enable logical-state checkpoint. Lives next to memory.db under
|
|
3294
3654
|
// CLAWNET_DIR so backup/wipe affects both consistently. Per-call writes
|
|
3295
3655
|
// are debounced to 5s by SessionManager itself.
|
|
3296
|
-
checkpointPath:
|
|
3656
|
+
checkpointPath: join6(clawnetDir, "agent-sessions.json")
|
|
3297
3657
|
});
|
|
3298
3658
|
swarmCoordinator = new SwarmCoordinator(sessionManager, hub, (workDir) => {
|
|
3299
3659
|
try {
|
|
3300
3660
|
const env = process.env.CLAWNET_DIR;
|
|
3301
|
-
const home = env ? env.replace(/\/\.clawnet\/?$/, "") :
|
|
3661
|
+
const home = env ? env.replace(/\/\.clawnet\/?$/, "") : homedir5();
|
|
3302
3662
|
return new TaskStore2({ workDir, home });
|
|
3303
3663
|
} catch (err) {
|
|
3304
|
-
|
|
3664
|
+
log10.warn({ err, workDir }, "TaskStore factory failed");
|
|
3305
3665
|
return void 0;
|
|
3306
3666
|
}
|
|
3307
|
-
}, process.env.CLAWNET_HOME ??
|
|
3667
|
+
}, process.env.CLAWNET_HOME ?? homedir5());
|
|
3308
3668
|
hub.setSessionManager(sessionManager);
|
|
3309
3669
|
hub.setSwarmCoordinator(swarmCoordinator);
|
|
3310
3670
|
const scheduleRuntime = new ScheduleRuntime({ hub, sessionManager, swarmCoordinator });
|
|
@@ -3312,9 +3672,10 @@ async function startAgent(options) {
|
|
|
3312
3672
|
await scheduleRuntime.start();
|
|
3313
3673
|
const brainBridge = new BrainBridge(hub);
|
|
3314
3674
|
const fsBridge = new FsBridge(hub);
|
|
3675
|
+
const templatesRolesBridge = new TemplatesRolesBridge(hub);
|
|
3315
3676
|
sessionManager.startIdleSweeper();
|
|
3316
3677
|
const shutdown = async () => {
|
|
3317
|
-
|
|
3678
|
+
log10.info("shutting down");
|
|
3318
3679
|
await sessionManager.closeAll();
|
|
3319
3680
|
await scheduleRuntime.stop();
|
|
3320
3681
|
hub.destroy();
|
|
@@ -3322,7 +3683,7 @@ async function startAgent(options) {
|
|
|
3322
3683
|
try {
|
|
3323
3684
|
memoryDb.close();
|
|
3324
3685
|
} catch (err) {
|
|
3325
|
-
|
|
3686
|
+
log10.warn({ err }, "failed to close memory db");
|
|
3326
3687
|
}
|
|
3327
3688
|
}
|
|
3328
3689
|
process.exit(0);
|
|
@@ -3330,7 +3691,7 @@ async function startAgent(options) {
|
|
|
3330
3691
|
process.on("SIGINT", shutdown);
|
|
3331
3692
|
process.on("SIGTERM", shutdown);
|
|
3332
3693
|
hub.connect();
|
|
3333
|
-
return { hub, sessionManager, swarmCoordinator, scheduleRuntime, brainBridge, fsBridge };
|
|
3694
|
+
return { hub, sessionManager, swarmCoordinator, scheduleRuntime, brainBridge, fsBridge, templatesRolesBridge };
|
|
3334
3695
|
}
|
|
3335
3696
|
|
|
3336
3697
|
export {
|
|
@@ -3340,4 +3701,4 @@ export {
|
|
|
3340
3701
|
FsBridge,
|
|
3341
3702
|
startAgent
|
|
3342
3703
|
};
|
|
3343
|
-
//# sourceMappingURL=chunk-
|
|
3704
|
+
//# sourceMappingURL=chunk-2JDX6XFD.js.map
|