@beevibe/daemon 0.1.2 → 0.1.3
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/main.js +823 -36
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// src/setup.ts
|
|
4
4
|
import { hostname, userInfo } from "node:os";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
5
|
|
|
7
6
|
// ../../node_modules/.pnpm/nanoid@5.1.9/node_modules/nanoid/index.js
|
|
8
7
|
import { webcrypto as crypto } from "node:crypto";
|
|
@@ -224,6 +223,29 @@ function saveConfig(cfg) {
|
|
|
224
223
|
`, { mode: 384 });
|
|
225
224
|
}
|
|
226
225
|
|
|
226
|
+
// src/detect-clis.ts
|
|
227
|
+
import { execFile } from "node:child_process";
|
|
228
|
+
import { promisify as promisify2 } from "node:util";
|
|
229
|
+
var execFileAsync = promisify2(execFile);
|
|
230
|
+
async function probeOne(cli) {
|
|
231
|
+
try {
|
|
232
|
+
await execFileAsync("which", [cli]);
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
let cli_version;
|
|
237
|
+
try {
|
|
238
|
+
const { stdout } = await execFileAsync(cli, ["--version"]);
|
|
239
|
+
cli_version = stdout.trim().split(`
|
|
240
|
+
`)[0];
|
|
241
|
+
} catch {}
|
|
242
|
+
return { cli, cli_version };
|
|
243
|
+
}
|
|
244
|
+
async function detectClis() {
|
|
245
|
+
const results = await Promise.all(KNOWN_CLIS.map((cli) => probeOne(cli)));
|
|
246
|
+
return results.filter((r) => r !== null);
|
|
247
|
+
}
|
|
248
|
+
|
|
227
249
|
// src/setup.ts
|
|
228
250
|
async function runSetup(options) {
|
|
229
251
|
if (!/^https?:\/\//.test(options.apiUrl)) {
|
|
@@ -234,7 +256,7 @@ async function runSetup(options) {
|
|
|
234
256
|
}
|
|
235
257
|
const externalId = options.externalId ?? hostname();
|
|
236
258
|
const deviceName = options.deviceName ?? `${userInfo().username}@${hostname()}`;
|
|
237
|
-
const runtimes = options.detectedClis ?? detectClis();
|
|
259
|
+
const runtimes = options.detectedClis ?? await detectClis();
|
|
238
260
|
if (runtimes.length === 0) {
|
|
239
261
|
throw new Error(`No supported CLIs detected on PATH. beevibe currently looks for: ${KNOWN_CLIS.join(", ")}`);
|
|
240
262
|
}
|
|
@@ -264,21 +286,6 @@ async function runSetup(options) {
|
|
|
264
286
|
saveConfig(config);
|
|
265
287
|
return config;
|
|
266
288
|
}
|
|
267
|
-
function detectClis() {
|
|
268
|
-
const out = [];
|
|
269
|
-
for (const cli of KNOWN_CLIS) {
|
|
270
|
-
const which = spawnSync("which", [cli], { encoding: "utf8" });
|
|
271
|
-
if (which.status !== 0 || !which.stdout.trim())
|
|
272
|
-
continue;
|
|
273
|
-
const version = spawnSync(cli, ["--version"], { encoding: "utf8" });
|
|
274
|
-
out.push({
|
|
275
|
-
cli,
|
|
276
|
-
cli_version: version.status === 0 ? version.stdout.trim().split(`
|
|
277
|
-
`)[0] : undefined
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
return out;
|
|
281
|
-
}
|
|
282
289
|
|
|
283
290
|
// ../core/dist/adapters/local-workspace/manager.js
|
|
284
291
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -436,6 +443,16 @@ class LocalWorkspaceManager {
|
|
|
436
443
|
if (!runtime3) {
|
|
437
444
|
throw new Error(`No runtime registered for agent ${agent2.id} (runtime_config.type='${agent2.runtime_config.type}')`);
|
|
438
445
|
}
|
|
446
|
+
if (runtime3.prepareWorkspace) {
|
|
447
|
+
if (!agent2.api_key) {
|
|
448
|
+
throw new Error(`Cannot prepare workspace for agent ${agent2.id}: agent.api_key is missing`);
|
|
449
|
+
}
|
|
450
|
+
await runtime3.prepareWorkspace({
|
|
451
|
+
workspace: workspace2,
|
|
452
|
+
agentApiKey: agent2.api_key,
|
|
453
|
+
mcpServerUrl: this.config.mcpServerUrl
|
|
454
|
+
});
|
|
455
|
+
}
|
|
439
456
|
await syncSkills({
|
|
440
457
|
sourceDir: this.config.skillsSourceDir,
|
|
441
458
|
targetDir: runtime3.skillsDir(workspace2),
|
|
@@ -648,6 +665,15 @@ function extractStepEvents(msg) {
|
|
|
648
665
|
}
|
|
649
666
|
];
|
|
650
667
|
}
|
|
668
|
+
if (msg.type === STREAM_TYPE.ToolResult) {
|
|
669
|
+
return [
|
|
670
|
+
{
|
|
671
|
+
kind: "tool_result",
|
|
672
|
+
description: describeToolResult(msg.content, msg.is_error === true),
|
|
673
|
+
timestamp: now
|
|
674
|
+
}
|
|
675
|
+
];
|
|
676
|
+
}
|
|
651
677
|
if (msg.type === STREAM_TYPE.ContentBlockStart && msg.content_block?.type === BLOCK_TYPE.ToolUse) {
|
|
652
678
|
const block = msg.content_block;
|
|
653
679
|
return [
|
|
@@ -681,6 +707,11 @@ function extractStepEvents(msg) {
|
|
|
681
707
|
}
|
|
682
708
|
return [];
|
|
683
709
|
}
|
|
710
|
+
function describeToolResult(content, isError) {
|
|
711
|
+
const text = typeof content === "string" ? content : JSON.stringify(content ?? "");
|
|
712
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
713
|
+
return isError ? `[error] ${collapsed}` : collapsed;
|
|
714
|
+
}
|
|
684
715
|
var PREFERRED_INPUT_FIELDS = [
|
|
685
716
|
"file_path",
|
|
686
717
|
"command",
|
|
@@ -845,6 +876,9 @@ class ClaudeCodeRuntime {
|
|
|
845
876
|
args.push("--max-turns", String(maxTurns));
|
|
846
877
|
if (context.resume_session_id)
|
|
847
878
|
args.push("--resume", context.resume_session_id);
|
|
879
|
+
if (context.disallowed_tools?.length) {
|
|
880
|
+
args.push("--disallowedTools", context.disallowed_tools.join(","));
|
|
881
|
+
}
|
|
848
882
|
if (context.system_prompt_append.length > 0) {
|
|
849
883
|
args.push("--append-system-prompt", context.system_prompt_append);
|
|
850
884
|
}
|
|
@@ -937,10 +971,705 @@ class ClaudeCodeRuntime {
|
|
|
937
971
|
}
|
|
938
972
|
}
|
|
939
973
|
|
|
974
|
+
// ../core/dist/adapters/codex/runtime.js
|
|
975
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, unlinkSync } from "node:fs";
|
|
976
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
977
|
+
import { join as join4 } from "node:path";
|
|
978
|
+
|
|
979
|
+
// ../core/dist/adapters/codex/stream-json.js
|
|
980
|
+
var CODEX_EVENT_TYPE = {
|
|
981
|
+
ThreadStarted: "thread.started",
|
|
982
|
+
TurnStarted: "turn.started",
|
|
983
|
+
TurnCompleted: "turn.completed",
|
|
984
|
+
TurnFailed: "turn.failed",
|
|
985
|
+
ItemStarted: "item.started",
|
|
986
|
+
ItemUpdated: "item.updated",
|
|
987
|
+
ItemCompleted: "item.completed",
|
|
988
|
+
Error: "error"
|
|
989
|
+
};
|
|
990
|
+
var CODEX_ITEM_TYPE = {
|
|
991
|
+
AgentMessage: "agent_message",
|
|
992
|
+
Reasoning: "reasoning",
|
|
993
|
+
CommandExecution: "command_execution",
|
|
994
|
+
FileChange: "file_change",
|
|
995
|
+
McpToolCall: "mcp_tool_call",
|
|
996
|
+
CollabToolCall: "collab_tool_call",
|
|
997
|
+
WebSearch: "web_search",
|
|
998
|
+
TodoList: "todo_list",
|
|
999
|
+
Error: "error"
|
|
1000
|
+
};
|
|
1001
|
+
function parseCodexEventLine(line) {
|
|
1002
|
+
const trimmed = line.trim();
|
|
1003
|
+
if (!trimmed || !trimmed.startsWith("{"))
|
|
1004
|
+
return null;
|
|
1005
|
+
try {
|
|
1006
|
+
return JSON.parse(trimmed);
|
|
1007
|
+
} catch {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function extractCodexStepEvents(evt) {
|
|
1012
|
+
const now = new Date().toISOString();
|
|
1013
|
+
if (evt.type !== CODEX_EVENT_TYPE.ItemStarted && evt.type !== CODEX_EVENT_TYPE.ItemCompleted) {
|
|
1014
|
+
return [];
|
|
1015
|
+
}
|
|
1016
|
+
const item = evt.item;
|
|
1017
|
+
if (!item || !item.type)
|
|
1018
|
+
return [];
|
|
1019
|
+
const isCompletion = evt.type === CODEX_EVENT_TYPE.ItemCompleted;
|
|
1020
|
+
if (item.type === CODEX_ITEM_TYPE.AgentMessage) {
|
|
1021
|
+
if (!isCompletion)
|
|
1022
|
+
return [];
|
|
1023
|
+
const text = item.text?.trim();
|
|
1024
|
+
if (!text)
|
|
1025
|
+
return [];
|
|
1026
|
+
return [{ kind: "agent", description: text, timestamp: now }];
|
|
1027
|
+
}
|
|
1028
|
+
if (item.type === CODEX_ITEM_TYPE.McpToolCall) {
|
|
1029
|
+
return [
|
|
1030
|
+
{
|
|
1031
|
+
kind: isCompletion ? "tool_result" : "tool_call",
|
|
1032
|
+
tool: item.tool ?? "unknown",
|
|
1033
|
+
description: describeCodexInput(item.arguments),
|
|
1034
|
+
timestamp: now
|
|
1035
|
+
}
|
|
1036
|
+
];
|
|
1037
|
+
}
|
|
1038
|
+
if (item.type === CODEX_ITEM_TYPE.CommandExecution) {
|
|
1039
|
+
return [
|
|
1040
|
+
{
|
|
1041
|
+
kind: isCompletion ? "tool_result" : "tool_call",
|
|
1042
|
+
tool: "shell",
|
|
1043
|
+
description: (item.command ?? "").slice(0, 200),
|
|
1044
|
+
timestamp: now
|
|
1045
|
+
}
|
|
1046
|
+
];
|
|
1047
|
+
}
|
|
1048
|
+
if (item.type === CODEX_ITEM_TYPE.FileChange) {
|
|
1049
|
+
const summary = (item.changes ?? []).map((c) => `${c.kind ?? "?"} ${c.path ?? ""}`).join(", ").slice(0, 200);
|
|
1050
|
+
return [
|
|
1051
|
+
{
|
|
1052
|
+
kind: isCompletion ? "tool_result" : "tool_call",
|
|
1053
|
+
tool: "file_change",
|
|
1054
|
+
description: summary,
|
|
1055
|
+
timestamp: now
|
|
1056
|
+
}
|
|
1057
|
+
];
|
|
1058
|
+
}
|
|
1059
|
+
if (item.type === CODEX_ITEM_TYPE.WebSearch) {
|
|
1060
|
+
return [
|
|
1061
|
+
{
|
|
1062
|
+
kind: isCompletion ? "tool_result" : "tool_call",
|
|
1063
|
+
tool: "web_search",
|
|
1064
|
+
description: (item.query ?? "").slice(0, 200),
|
|
1065
|
+
timestamp: now
|
|
1066
|
+
}
|
|
1067
|
+
];
|
|
1068
|
+
}
|
|
1069
|
+
return [];
|
|
1070
|
+
}
|
|
1071
|
+
var PREFERRED_CODEX_FIELDS = [
|
|
1072
|
+
"file_path",
|
|
1073
|
+
"path",
|
|
1074
|
+
"command",
|
|
1075
|
+
"cmd",
|
|
1076
|
+
"query",
|
|
1077
|
+
"pattern",
|
|
1078
|
+
"url",
|
|
1079
|
+
"intent"
|
|
1080
|
+
];
|
|
1081
|
+
function describeCodexInput(input) {
|
|
1082
|
+
if (typeof input === "string")
|
|
1083
|
+
return input.slice(0, 200);
|
|
1084
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
1085
|
+
return "";
|
|
1086
|
+
const obj = input;
|
|
1087
|
+
for (const key of PREFERRED_CODEX_FIELDS) {
|
|
1088
|
+
const v = obj[key];
|
|
1089
|
+
if (typeof v === "string" && v.length > 0)
|
|
1090
|
+
return v.slice(0, 200);
|
|
1091
|
+
}
|
|
1092
|
+
return JSON.stringify(input).slice(0, 200);
|
|
1093
|
+
}
|
|
1094
|
+
function parseCodexEvents(events, exitCode, lastMessage) {
|
|
1095
|
+
let threadId;
|
|
1096
|
+
let usage;
|
|
1097
|
+
let assistantText = "";
|
|
1098
|
+
let turnFailed;
|
|
1099
|
+
let topLevelError;
|
|
1100
|
+
const transcriptParts = [];
|
|
1101
|
+
for (const evt of events) {
|
|
1102
|
+
switch (evt.type) {
|
|
1103
|
+
case CODEX_EVENT_TYPE.ThreadStarted:
|
|
1104
|
+
if (evt.thread_id)
|
|
1105
|
+
threadId = evt.thread_id;
|
|
1106
|
+
break;
|
|
1107
|
+
case CODEX_EVENT_TYPE.TurnCompleted:
|
|
1108
|
+
if (evt.usage)
|
|
1109
|
+
usage = evt.usage;
|
|
1110
|
+
break;
|
|
1111
|
+
case CODEX_EVENT_TYPE.TurnFailed:
|
|
1112
|
+
turnFailed = evt.error?.message ?? turnFailed;
|
|
1113
|
+
break;
|
|
1114
|
+
case CODEX_EVENT_TYPE.Error:
|
|
1115
|
+
topLevelError = evt.message ?? topLevelError;
|
|
1116
|
+
if (evt.message)
|
|
1117
|
+
transcriptParts.push(`[error] ${evt.message}
|
|
1118
|
+
`);
|
|
1119
|
+
break;
|
|
1120
|
+
case CODEX_EVENT_TYPE.ItemCompleted: {
|
|
1121
|
+
const item = evt.item;
|
|
1122
|
+
if (!item || !item.type)
|
|
1123
|
+
break;
|
|
1124
|
+
if (item.type === CODEX_ITEM_TYPE.AgentMessage && item.text) {
|
|
1125
|
+
assistantText = item.text;
|
|
1126
|
+
transcriptParts.push(`[assistant] ${item.text}
|
|
1127
|
+
`);
|
|
1128
|
+
} else if (item.type === CODEX_ITEM_TYPE.McpToolCall) {
|
|
1129
|
+
const tool = item.tool ?? "unknown";
|
|
1130
|
+
transcriptParts.push(`[tool_call] ${tool}
|
|
1131
|
+
`);
|
|
1132
|
+
const resultSummary = summarizeMcpResult(item.result);
|
|
1133
|
+
if (resultSummary || item.error?.message) {
|
|
1134
|
+
transcriptParts.push(`[tool_result from ${tool}] ${item.error?.message ?? resultSummary}
|
|
1135
|
+
`);
|
|
1136
|
+
}
|
|
1137
|
+
} else if (item.type === CODEX_ITEM_TYPE.CommandExecution) {
|
|
1138
|
+
transcriptParts.push(`[tool_call] shell ${(item.command ?? "").slice(0, 200)}
|
|
1139
|
+
`);
|
|
1140
|
+
if (item.aggregated_output) {
|
|
1141
|
+
transcriptParts.push(`[tool_result from shell] ${item.aggregated_output.slice(0, 200).replace(/\n/g, " ")}
|
|
1142
|
+
`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
default:
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const failed = exitCode !== 0 || !!turnFailed || !!topLevelError;
|
|
1152
|
+
const trimmedLast = lastMessage.trim();
|
|
1153
|
+
const failureMessage = turnFailed ?? topLevelError;
|
|
1154
|
+
const output = failed ? failureMessage || assistantText || bareCliExitMessage(exitCode) : trimmedLast || assistantText || "Session completed.";
|
|
1155
|
+
return {
|
|
1156
|
+
status: failed ? "failed" : "completed",
|
|
1157
|
+
output,
|
|
1158
|
+
transcript: transcriptParts.join("") || undefined,
|
|
1159
|
+
cli_session_id: threadId,
|
|
1160
|
+
usage: usage ? {
|
|
1161
|
+
input_tokens: usage.input_tokens ?? 0,
|
|
1162
|
+
output_tokens: (usage.output_tokens ?? 0) + (usage.reasoning_output_tokens ?? 0),
|
|
1163
|
+
cache_creation_input_tokens: 0,
|
|
1164
|
+
cache_read_input_tokens: usage.cached_input_tokens ?? 0
|
|
1165
|
+
} : undefined
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function summarizeMcpResult(result) {
|
|
1169
|
+
if (!result || !Array.isArray(result.content))
|
|
1170
|
+
return "";
|
|
1171
|
+
for (const block of result.content) {
|
|
1172
|
+
if (block && typeof block === "object" && block.type === "text") {
|
|
1173
|
+
const text = block.text;
|
|
1174
|
+
if (typeof text === "string")
|
|
1175
|
+
return text.slice(0, 200).replace(/\n/g, " ");
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return JSON.stringify(result.content).slice(0, 200);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// ../core/dist/adapters/codex/runtime.js
|
|
1182
|
+
var OPENAI_AUTH_VARS = ["OPENAI_API_KEY", "OPENAI_AUTH_TOKEN"];
|
|
1183
|
+
|
|
1184
|
+
class CodexRuntime {
|
|
1185
|
+
config;
|
|
1186
|
+
type = "codex";
|
|
1187
|
+
prepared = new Map;
|
|
1188
|
+
constructor(config = {}) {
|
|
1189
|
+
this.config = config;
|
|
1190
|
+
}
|
|
1191
|
+
async execute(context) {
|
|
1192
|
+
const prepared = this.prepared.get(context.workspace.path);
|
|
1193
|
+
const sid = context.env?.BEEVIBE_SESSION_ID;
|
|
1194
|
+
const lastMessagePath = join4(context.workspace.path, `.beevibe-codex-last-message-${Date.now()}.txt`);
|
|
1195
|
+
const globalArgs = buildGlobalArgs(context, this.config);
|
|
1196
|
+
const execArgs = [
|
|
1197
|
+
"--json",
|
|
1198
|
+
"--skip-git-repo-check",
|
|
1199
|
+
"--output-last-message",
|
|
1200
|
+
lastMessagePath
|
|
1201
|
+
];
|
|
1202
|
+
if (prepared && sid) {
|
|
1203
|
+
globalArgs.push("-c", `mcp_servers.beevibe.url=${tomlString(withBeevibeSession(prepared.mcpServerUrl, sid))}`, "-c", `mcp_servers.beevibe.bearer_token_env_var=${tomlString("BEEVIBE_AGENT_API_KEY")}`, "-c", `mcp_servers.beevibe.default_tools_approval_mode=${tomlString("approve")}`);
|
|
1204
|
+
}
|
|
1205
|
+
const args = context.resume_session_id ? [
|
|
1206
|
+
...globalArgs,
|
|
1207
|
+
"exec",
|
|
1208
|
+
"resume",
|
|
1209
|
+
...execArgs,
|
|
1210
|
+
context.resume_session_id,
|
|
1211
|
+
composePrompt(context)
|
|
1212
|
+
] : [...globalArgs, "exec", ...execArgs, composePrompt(context)];
|
|
1213
|
+
const env2 = { ...process.env };
|
|
1214
|
+
for (const key of OPENAI_AUTH_VARS)
|
|
1215
|
+
delete env2[key];
|
|
1216
|
+
if (context.env)
|
|
1217
|
+
Object.assign(env2, context.env);
|
|
1218
|
+
if (prepared)
|
|
1219
|
+
env2.BEEVIBE_AGENT_API_KEY = prepared.agentApiKey;
|
|
1220
|
+
const events = [];
|
|
1221
|
+
let pending = "";
|
|
1222
|
+
const handleLine = (line) => {
|
|
1223
|
+
const evt = parseCodexEventLine(line);
|
|
1224
|
+
if (!evt)
|
|
1225
|
+
return;
|
|
1226
|
+
events.push(evt);
|
|
1227
|
+
if (!context.onStep)
|
|
1228
|
+
return;
|
|
1229
|
+
for (const step of extractCodexStepEvents(evt)) {
|
|
1230
|
+
context.onStep(step);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
const result = await runCliProcess({
|
|
1234
|
+
command: this.config.command ?? "codex",
|
|
1235
|
+
args,
|
|
1236
|
+
cwd: context.workspace.path,
|
|
1237
|
+
env: env2,
|
|
1238
|
+
abortSignal: context.abort_signal,
|
|
1239
|
+
onSpawn: ({ pid, process_group_id }) => {
|
|
1240
|
+
context.onSpawn?.({ process_pid: pid, process_group_id });
|
|
1241
|
+
},
|
|
1242
|
+
onLog: (stream, chunk) => {
|
|
1243
|
+
if (stream !== "stdout")
|
|
1244
|
+
return;
|
|
1245
|
+
pending += chunk;
|
|
1246
|
+
let nl;
|
|
1247
|
+
while ((nl = pending.indexOf(`
|
|
1248
|
+
`)) !== -1) {
|
|
1249
|
+
handleLine(pending.slice(0, nl));
|
|
1250
|
+
pending = pending.slice(nl + 1);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
if (pending)
|
|
1255
|
+
handleLine(pending);
|
|
1256
|
+
if (result.truncated) {
|
|
1257
|
+
console.warn("[CodexRuntime] stdout truncated at 4MB — result parsing may be incomplete");
|
|
1258
|
+
}
|
|
1259
|
+
if (result.aborted) {
|
|
1260
|
+
removeIfExists(lastMessagePath);
|
|
1261
|
+
return {
|
|
1262
|
+
status: "cancelled",
|
|
1263
|
+
output: "Session cancelled.",
|
|
1264
|
+
process_pid: result.pid ?? undefined,
|
|
1265
|
+
process_group_id: result.process_group_id ?? undefined
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
const lastMessage = readIfExists(lastMessagePath);
|
|
1269
|
+
removeIfExists(lastMessagePath);
|
|
1270
|
+
const parsed = parseCodexEvents(events, result.exitCode, lastMessage);
|
|
1271
|
+
const STDERR_TAIL_BYTES = 4096;
|
|
1272
|
+
const stderrTail = parsed.status === "failed" && result.stderr ? result.stderr.slice(-STDERR_TAIL_BYTES) : undefined;
|
|
1273
|
+
return {
|
|
1274
|
+
...parsed,
|
|
1275
|
+
process_pid: result.pid ?? undefined,
|
|
1276
|
+
process_group_id: result.process_group_id ?? undefined,
|
|
1277
|
+
exit_code: result.exitCode,
|
|
1278
|
+
...stderrTail ? { stderr: stderrTail } : {}
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
async healthCheck() {
|
|
1282
|
+
try {
|
|
1283
|
+
const result = await runCliProcess({
|
|
1284
|
+
command: this.config.command ?? "codex",
|
|
1285
|
+
args: ["--version"],
|
|
1286
|
+
cwd: tmpdir2(),
|
|
1287
|
+
timeoutMs: 5000,
|
|
1288
|
+
graceMs: 0
|
|
1289
|
+
});
|
|
1290
|
+
return {
|
|
1291
|
+
healthy: result.exitCode === 0,
|
|
1292
|
+
error: result.exitCode === 0 ? undefined : result.stderr.slice(-500)
|
|
1293
|
+
};
|
|
1294
|
+
} catch {
|
|
1295
|
+
return {
|
|
1296
|
+
healthy: false,
|
|
1297
|
+
error: `Command not found: ${this.config.command ?? "codex"}`
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
async shutdown() {}
|
|
1302
|
+
skillsDir(workspace2) {
|
|
1303
|
+
return join4(workspace2.path, ".codex", "skills");
|
|
1304
|
+
}
|
|
1305
|
+
prepareWorkspace(context) {
|
|
1306
|
+
this.prepared.set(context.workspace.path, {
|
|
1307
|
+
agentApiKey: context.agentApiKey,
|
|
1308
|
+
mcpServerUrl: context.mcpServerUrl
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
function buildGlobalArgs(context, config) {
|
|
1313
|
+
const args = [
|
|
1314
|
+
"--sandbox",
|
|
1315
|
+
"workspace-write",
|
|
1316
|
+
"--ask-for-approval",
|
|
1317
|
+
"never",
|
|
1318
|
+
"--cd",
|
|
1319
|
+
context.workspace.path
|
|
1320
|
+
];
|
|
1321
|
+
const model = context.model ?? config.model;
|
|
1322
|
+
if (model)
|
|
1323
|
+
args.push("--model", model);
|
|
1324
|
+
return args;
|
|
1325
|
+
}
|
|
1326
|
+
function composePrompt(context) {
|
|
1327
|
+
if (context.system_prompt_append.length === 0)
|
|
1328
|
+
return context.intent;
|
|
1329
|
+
return [
|
|
1330
|
+
"<beevibe_system_context>",
|
|
1331
|
+
context.system_prompt_append,
|
|
1332
|
+
"</beevibe_system_context>",
|
|
1333
|
+
"",
|
|
1334
|
+
context.intent
|
|
1335
|
+
].join(`
|
|
1336
|
+
`);
|
|
1337
|
+
}
|
|
1338
|
+
function withBeevibeSession(mcpServerUrl, sid) {
|
|
1339
|
+
const url = new URL(mcpServerUrl);
|
|
1340
|
+
url.searchParams.set("beevibe_session", sid);
|
|
1341
|
+
return url.toString();
|
|
1342
|
+
}
|
|
1343
|
+
function tomlString(value) {
|
|
1344
|
+
return JSON.stringify(value);
|
|
1345
|
+
}
|
|
1346
|
+
function readIfExists(path2) {
|
|
1347
|
+
try {
|
|
1348
|
+
return existsSync3(path2) ? readFileSync3(path2, "utf8") : "";
|
|
1349
|
+
} catch {
|
|
1350
|
+
return "";
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function removeIfExists(path2) {
|
|
1354
|
+
try {
|
|
1355
|
+
if (existsSync3(path2))
|
|
1356
|
+
unlinkSync(path2);
|
|
1357
|
+
} catch {}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// ../core/dist/adapters/opencode/runtime.js
|
|
1361
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1362
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
1363
|
+
import { join as join5 } from "node:path";
|
|
1364
|
+
|
|
1365
|
+
// ../core/dist/adapters/opencode/stream-json.js
|
|
1366
|
+
var OPENCODE_EVENT_TYPE = {
|
|
1367
|
+
Text: "text",
|
|
1368
|
+
Reasoning: "reasoning",
|
|
1369
|
+
ToolUse: "tool_use",
|
|
1370
|
+
StepStart: "step_start",
|
|
1371
|
+
StepFinish: "step_finish",
|
|
1372
|
+
Error: "error"
|
|
1373
|
+
};
|
|
1374
|
+
var OPENCODE_TOOL_STATUS = {
|
|
1375
|
+
Pending: "pending",
|
|
1376
|
+
Running: "running",
|
|
1377
|
+
Completed: "completed",
|
|
1378
|
+
Error: "error"
|
|
1379
|
+
};
|
|
1380
|
+
function parseOpenCodeEventLine(line) {
|
|
1381
|
+
const trimmed = line.trim();
|
|
1382
|
+
if (!trimmed || !trimmed.startsWith("{"))
|
|
1383
|
+
return null;
|
|
1384
|
+
try {
|
|
1385
|
+
return JSON.parse(trimmed);
|
|
1386
|
+
} catch {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
function extractOpenCodeStepEvents(evt) {
|
|
1391
|
+
const now = new Date().toISOString();
|
|
1392
|
+
if (evt.type === OPENCODE_EVENT_TYPE.Text) {
|
|
1393
|
+
const text = evt.part?.text?.trim();
|
|
1394
|
+
if (!text)
|
|
1395
|
+
return [];
|
|
1396
|
+
return [{ kind: "agent", description: text, timestamp: now }];
|
|
1397
|
+
}
|
|
1398
|
+
if (evt.type === OPENCODE_EVENT_TYPE.ToolUse) {
|
|
1399
|
+
const part = evt.part;
|
|
1400
|
+
if (!part)
|
|
1401
|
+
return [];
|
|
1402
|
+
const status = part.state?.status;
|
|
1403
|
+
const isTerminal = status === OPENCODE_TOOL_STATUS.Completed || status === OPENCODE_TOOL_STATUS.Error;
|
|
1404
|
+
return [
|
|
1405
|
+
{
|
|
1406
|
+
kind: isTerminal ? "tool_result" : "tool_call",
|
|
1407
|
+
tool: part.tool ?? "unknown",
|
|
1408
|
+
description: describeOpenCodeInput(part.state?.input),
|
|
1409
|
+
timestamp: now
|
|
1410
|
+
}
|
|
1411
|
+
];
|
|
1412
|
+
}
|
|
1413
|
+
return [];
|
|
1414
|
+
}
|
|
1415
|
+
var PREFERRED_OPENCODE_FIELDS = [
|
|
1416
|
+
"file_path",
|
|
1417
|
+
"path",
|
|
1418
|
+
"command",
|
|
1419
|
+
"cmd",
|
|
1420
|
+
"query",
|
|
1421
|
+
"pattern",
|
|
1422
|
+
"url",
|
|
1423
|
+
"intent"
|
|
1424
|
+
];
|
|
1425
|
+
function describeOpenCodeInput(input) {
|
|
1426
|
+
if (typeof input === "string")
|
|
1427
|
+
return input.slice(0, 200);
|
|
1428
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
1429
|
+
return "";
|
|
1430
|
+
const obj = input;
|
|
1431
|
+
for (const key of PREFERRED_OPENCODE_FIELDS) {
|
|
1432
|
+
const v = obj[key];
|
|
1433
|
+
if (typeof v === "string" && v.length > 0)
|
|
1434
|
+
return v.slice(0, 200);
|
|
1435
|
+
}
|
|
1436
|
+
return JSON.stringify(input).slice(0, 200);
|
|
1437
|
+
}
|
|
1438
|
+
function parseOpenCodeEvents(events, exitCode) {
|
|
1439
|
+
let sessionId;
|
|
1440
|
+
let sawUsage = false;
|
|
1441
|
+
let totalInput = 0;
|
|
1442
|
+
let totalOutput = 0;
|
|
1443
|
+
let totalReasoning = 0;
|
|
1444
|
+
let totalCacheRead = 0;
|
|
1445
|
+
let totalCacheWrite = 0;
|
|
1446
|
+
let totalCost = 0;
|
|
1447
|
+
const assistantTexts = [];
|
|
1448
|
+
const transcriptParts = [];
|
|
1449
|
+
let errorMessage;
|
|
1450
|
+
for (const evt of events) {
|
|
1451
|
+
if (evt.sessionID)
|
|
1452
|
+
sessionId = evt.sessionID;
|
|
1453
|
+
switch (evt.type) {
|
|
1454
|
+
case OPENCODE_EVENT_TYPE.StepFinish: {
|
|
1455
|
+
const part = evt.part;
|
|
1456
|
+
if (!part)
|
|
1457
|
+
break;
|
|
1458
|
+
sawUsage = true;
|
|
1459
|
+
totalCost += part.cost ?? 0;
|
|
1460
|
+
totalInput += part.tokens?.input ?? 0;
|
|
1461
|
+
totalOutput += part.tokens?.output ?? 0;
|
|
1462
|
+
totalReasoning += part.tokens?.reasoning ?? 0;
|
|
1463
|
+
totalCacheRead += part.tokens?.cache?.read ?? 0;
|
|
1464
|
+
totalCacheWrite += part.tokens?.cache?.write ?? 0;
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
case OPENCODE_EVENT_TYPE.Text: {
|
|
1468
|
+
const text = evt.part?.text;
|
|
1469
|
+
if (text) {
|
|
1470
|
+
assistantTexts.push(text);
|
|
1471
|
+
transcriptParts.push(`[assistant] ${text}
|
|
1472
|
+
`);
|
|
1473
|
+
}
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
case OPENCODE_EVENT_TYPE.ToolUse: {
|
|
1477
|
+
const part = evt.part;
|
|
1478
|
+
if (!part)
|
|
1479
|
+
break;
|
|
1480
|
+
const tool = part.tool ?? "unknown";
|
|
1481
|
+
const status = part.state?.status;
|
|
1482
|
+
if (status === OPENCODE_TOOL_STATUS.Completed || status === OPENCODE_TOOL_STATUS.Error) {
|
|
1483
|
+
const detail = (part.state?.error ?? part.state?.output ?? "").slice(0, 200).replace(/\n/g, " ");
|
|
1484
|
+
transcriptParts.push(detail ? `[tool_result from ${tool}] ${detail}
|
|
1485
|
+
` : `[tool_result from ${tool}]
|
|
1486
|
+
`);
|
|
1487
|
+
} else {
|
|
1488
|
+
transcriptParts.push(`[tool_call] ${tool}
|
|
1489
|
+
`);
|
|
1490
|
+
}
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
case OPENCODE_EVENT_TYPE.Error:
|
|
1494
|
+
errorMessage = evt.error?.message ?? evt.result?.error?.message ?? errorMessage;
|
|
1495
|
+
if (errorMessage)
|
|
1496
|
+
transcriptParts.push(`[error] ${errorMessage}
|
|
1497
|
+
`);
|
|
1498
|
+
break;
|
|
1499
|
+
default:
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
const assistantText = assistantTexts.join(`
|
|
1504
|
+
`).trim();
|
|
1505
|
+
const failed = exitCode !== 0 || !!errorMessage;
|
|
1506
|
+
const output = failed ? errorMessage || assistantText || bareCliExitMessage(exitCode) : assistantText || "Session completed.";
|
|
1507
|
+
const usage = sawUsage ? {
|
|
1508
|
+
input_tokens: totalInput,
|
|
1509
|
+
output_tokens: totalOutput + totalReasoning,
|
|
1510
|
+
cache_creation_input_tokens: totalCacheWrite,
|
|
1511
|
+
cache_read_input_tokens: totalCacheRead,
|
|
1512
|
+
cost_usd: totalCost
|
|
1513
|
+
} : undefined;
|
|
1514
|
+
return {
|
|
1515
|
+
status: failed ? "failed" : "completed",
|
|
1516
|
+
output,
|
|
1517
|
+
transcript: transcriptParts.join("") || undefined,
|
|
1518
|
+
cli_session_id: sessionId,
|
|
1519
|
+
usage
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// ../core/dist/adapters/opencode/runtime.js
|
|
1524
|
+
class OpenCodeRuntime {
|
|
1525
|
+
config;
|
|
1526
|
+
type = "opencode";
|
|
1527
|
+
constructor(config = {}) {
|
|
1528
|
+
this.config = config;
|
|
1529
|
+
}
|
|
1530
|
+
async execute(context) {
|
|
1531
|
+
const args = [
|
|
1532
|
+
"run",
|
|
1533
|
+
"--format",
|
|
1534
|
+
"json",
|
|
1535
|
+
"--dangerously-skip-permissions",
|
|
1536
|
+
"--dir",
|
|
1537
|
+
context.workspace.path
|
|
1538
|
+
];
|
|
1539
|
+
const model = context.model ?? this.config.model;
|
|
1540
|
+
if (model)
|
|
1541
|
+
args.push("--model", model);
|
|
1542
|
+
if (context.resume_session_id)
|
|
1543
|
+
args.push("--session", context.resume_session_id);
|
|
1544
|
+
args.push(composePrompt2(context));
|
|
1545
|
+
const env2 = { ...process.env };
|
|
1546
|
+
if (context.env)
|
|
1547
|
+
Object.assign(env2, context.env);
|
|
1548
|
+
const events = [];
|
|
1549
|
+
let pending = "";
|
|
1550
|
+
const handleLine = (line) => {
|
|
1551
|
+
const evt = parseOpenCodeEventLine(line);
|
|
1552
|
+
if (!evt)
|
|
1553
|
+
return;
|
|
1554
|
+
events.push(evt);
|
|
1555
|
+
if (!context.onStep)
|
|
1556
|
+
return;
|
|
1557
|
+
for (const step of extractOpenCodeStepEvents(evt)) {
|
|
1558
|
+
context.onStep(step);
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
const result = await runCliProcess({
|
|
1562
|
+
command: this.config.command ?? "opencode",
|
|
1563
|
+
args,
|
|
1564
|
+
cwd: context.workspace.path,
|
|
1565
|
+
env: env2,
|
|
1566
|
+
abortSignal: context.abort_signal,
|
|
1567
|
+
onSpawn: ({ pid, process_group_id }) => {
|
|
1568
|
+
context.onSpawn?.({ process_pid: pid, process_group_id });
|
|
1569
|
+
},
|
|
1570
|
+
onLog: (stream, chunk) => {
|
|
1571
|
+
if (stream !== "stdout")
|
|
1572
|
+
return;
|
|
1573
|
+
pending += chunk;
|
|
1574
|
+
let nl;
|
|
1575
|
+
while ((nl = pending.indexOf(`
|
|
1576
|
+
`)) !== -1) {
|
|
1577
|
+
handleLine(pending.slice(0, nl));
|
|
1578
|
+
pending = pending.slice(nl + 1);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
if (pending)
|
|
1583
|
+
handleLine(pending);
|
|
1584
|
+
if (result.truncated) {
|
|
1585
|
+
console.warn("[OpenCodeRuntime] stdout truncated at 4MB — result parsing may be incomplete");
|
|
1586
|
+
}
|
|
1587
|
+
if (result.aborted) {
|
|
1588
|
+
return {
|
|
1589
|
+
status: "cancelled",
|
|
1590
|
+
output: "Session cancelled.",
|
|
1591
|
+
process_pid: result.pid ?? undefined,
|
|
1592
|
+
process_group_id: result.process_group_id ?? undefined
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
const parsed = parseOpenCodeEvents(events, result.exitCode);
|
|
1596
|
+
const STDERR_TAIL_BYTES = 4096;
|
|
1597
|
+
const stderrTail = parsed.status === "failed" && result.stderr ? result.stderr.slice(-STDERR_TAIL_BYTES) : undefined;
|
|
1598
|
+
return {
|
|
1599
|
+
...parsed,
|
|
1600
|
+
process_pid: result.pid ?? undefined,
|
|
1601
|
+
process_group_id: result.process_group_id ?? undefined,
|
|
1602
|
+
exit_code: result.exitCode,
|
|
1603
|
+
...stderrTail ? { stderr: stderrTail } : {}
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
async healthCheck() {
|
|
1607
|
+
try {
|
|
1608
|
+
const result = await runCliProcess({
|
|
1609
|
+
command: this.config.command ?? "opencode",
|
|
1610
|
+
args: ["--version"],
|
|
1611
|
+
cwd: tmpdir3(),
|
|
1612
|
+
timeoutMs: 5000,
|
|
1613
|
+
graceMs: 0
|
|
1614
|
+
});
|
|
1615
|
+
return { healthy: result.exitCode === 0 };
|
|
1616
|
+
} catch {
|
|
1617
|
+
return {
|
|
1618
|
+
healthy: false,
|
|
1619
|
+
error: `Command not found: ${this.config.command ?? "opencode"}`
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
async shutdown() {}
|
|
1624
|
+
skillsDir(workspace2) {
|
|
1625
|
+
return join5(workspace2.path, ".opencode", "skills");
|
|
1626
|
+
}
|
|
1627
|
+
prepareWorkspace(context) {
|
|
1628
|
+
const configPath = join5(context.workspace.path, "opencode.json");
|
|
1629
|
+
if (existsSync4(configPath))
|
|
1630
|
+
return;
|
|
1631
|
+
writeFileSync3(configPath, buildOpenCodeConfig(context.agentApiKey, context.mcpServerUrl), {
|
|
1632
|
+
mode: 384
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function composePrompt2(context) {
|
|
1637
|
+
if (context.system_prompt_append.length === 0)
|
|
1638
|
+
return context.intent;
|
|
1639
|
+
return [
|
|
1640
|
+
"<beevibe_system_context>",
|
|
1641
|
+
context.system_prompt_append,
|
|
1642
|
+
"</beevibe_system_context>",
|
|
1643
|
+
"",
|
|
1644
|
+
context.intent
|
|
1645
|
+
].join(`
|
|
1646
|
+
`);
|
|
1647
|
+
}
|
|
1648
|
+
function buildOpenCodeConfig(apiKey, mcpServerUrl) {
|
|
1649
|
+
return JSON.stringify({
|
|
1650
|
+
$schema: "https://opencode.ai/config.json",
|
|
1651
|
+
mcp: {
|
|
1652
|
+
beevibe: {
|
|
1653
|
+
type: "remote",
|
|
1654
|
+
url: mcpServerUrl,
|
|
1655
|
+
enabled: true,
|
|
1656
|
+
oauth: false,
|
|
1657
|
+
headers: {
|
|
1658
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1659
|
+
"X-Beevibe-Session": "{env:BEEVIBE_SESSION_ID}"
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}, null, 2) + `
|
|
1664
|
+
`;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
940
1667
|
// ../core/dist/adapters/runtime-registry.js
|
|
941
1668
|
function createDefaultRuntimeRegistry() {
|
|
942
1669
|
return {
|
|
943
|
-
claude: new ClaudeCodeRuntime({})
|
|
1670
|
+
claude: new ClaudeCodeRuntime({}),
|
|
1671
|
+
codex: new CodexRuntime({}),
|
|
1672
|
+
opencode: new OpenCodeRuntime({})
|
|
944
1673
|
};
|
|
945
1674
|
}
|
|
946
1675
|
|
|
@@ -1002,17 +1731,22 @@ class ApiClient {
|
|
|
1002
1731
|
return `${this.cfg.apiUrl}${path2}`;
|
|
1003
1732
|
}
|
|
1004
1733
|
}
|
|
1734
|
+
|
|
1005
1735
|
// src/spawner.ts
|
|
1006
1736
|
async function runDispatch(deps, payload, abortSignal) {
|
|
1007
1737
|
const syntheticAgent = {
|
|
1008
1738
|
id: payload.agent_id,
|
|
1009
1739
|
api_key: payload.agent_api_key,
|
|
1010
1740
|
hierarchy_level: payload.agent_hierarchy_level,
|
|
1011
|
-
runtime_config: { type:
|
|
1741
|
+
runtime_config: { type: payload.runtime_type }
|
|
1012
1742
|
};
|
|
1013
1743
|
const ws = await deps.workspaceManager.ensureWorkspace({ agent: syntheticAgent });
|
|
1014
|
-
console.log(`[daemon/spawn] sess=${payload.session_id} agent=${payload.agent_id} type=${payload.type} cwd=${ws.path}`);
|
|
1015
|
-
const
|
|
1744
|
+
console.log(`[daemon/spawn] sess=${payload.session_id} agent=${payload.agent_id} runtime=${payload.runtime_type} type=${payload.type} cwd=${ws.path}`);
|
|
1745
|
+
const registry = deps.runtimeRegistry ?? createDefaultRuntimeRegistry();
|
|
1746
|
+
const runtime3 = registry[payload.runtime_type];
|
|
1747
|
+
if (!runtime3) {
|
|
1748
|
+
throw new Error(`No runtime registered for dispatch payload type '${payload.runtime_type}'`);
|
|
1749
|
+
}
|
|
1016
1750
|
const buffer = [];
|
|
1017
1751
|
let flushTimer;
|
|
1018
1752
|
const flush = async () => {
|
|
@@ -1054,6 +1788,7 @@ async function runDispatch(deps, payload, abortSignal) {
|
|
|
1054
1788
|
system_prompt_append: payload.system_prompt_append,
|
|
1055
1789
|
model: payload.model,
|
|
1056
1790
|
max_turns: payload.max_turns,
|
|
1791
|
+
disallowed_tools: payload.disallowed_tools,
|
|
1057
1792
|
env: payload.env,
|
|
1058
1793
|
resume_session_id: payload.resume_session_id,
|
|
1059
1794
|
abort_signal: abortSignal,
|
|
@@ -1206,12 +1941,22 @@ class Claimer {
|
|
|
1206
1941
|
}
|
|
1207
1942
|
async pollRuntime(runtimeId) {
|
|
1208
1943
|
while (this.running && this.cfg.supervisor.hasCapacity()) {
|
|
1209
|
-
|
|
1944
|
+
let payload;
|
|
1945
|
+
try {
|
|
1946
|
+
payload = await this.cfg.api.claim(runtimeId);
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
console.warn(`[daemon] claim failed for runtime=${runtimeId}:`, err instanceof Error ? err.message : String(err));
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1210
1951
|
if (!payload)
|
|
1211
1952
|
return;
|
|
1212
1953
|
console.log(`[daemon/claim] sess=${payload.session_id} agent=${payload.agent_id} runtime=${runtimeId}`);
|
|
1213
1954
|
const ctrl = this.cfg.supervisor.start(payload.session_id);
|
|
1214
|
-
runDispatch({
|
|
1955
|
+
runDispatch({
|
|
1956
|
+
api: this.cfg.api,
|
|
1957
|
+
workspaceManager: this.cfg.workspaceManager,
|
|
1958
|
+
runtimeRegistry: this.cfg.runtimeRegistry
|
|
1959
|
+
}, payload, ctrl.signal).catch((err) => console.error(`[daemon] dispatch ${payload.session_id} failed:`, err instanceof Error ? err.message : String(err))).finally(() => this.cfg.supervisor.finish(payload.session_id));
|
|
1215
1960
|
}
|
|
1216
1961
|
}
|
|
1217
1962
|
}
|
|
@@ -1219,14 +1964,14 @@ class Claimer {
|
|
|
1219
1964
|
// src/skills-cache.ts
|
|
1220
1965
|
import { promises as fs2 } from "node:fs";
|
|
1221
1966
|
import { homedir as homedir3 } from "node:os";
|
|
1222
|
-
import { join as
|
|
1967
|
+
import { join as join6 } from "node:path";
|
|
1223
1968
|
function skillsCacheDir() {
|
|
1224
|
-
return
|
|
1969
|
+
return join6(homedir3(), ".beevibe", "skills");
|
|
1225
1970
|
}
|
|
1226
1971
|
var VERSION_FILE = ".version";
|
|
1227
1972
|
async function readCachedVersion() {
|
|
1228
1973
|
try {
|
|
1229
|
-
return (await fs2.readFile(
|
|
1974
|
+
return (await fs2.readFile(join6(skillsCacheDir(), VERSION_FILE), "utf8")).trim();
|
|
1230
1975
|
} catch {
|
|
1231
1976
|
return;
|
|
1232
1977
|
}
|
|
@@ -1247,19 +1992,19 @@ async function syncSkillsCache(api) {
|
|
|
1247
1992
|
if (!dirent.isDirectory())
|
|
1248
1993
|
continue;
|
|
1249
1994
|
if (dirent.name === "beevibe" || dirent.name.startsWith("beevibe-")) {
|
|
1250
|
-
await fs2.rm(
|
|
1995
|
+
await fs2.rm(join6(cache, dirent.name), { recursive: true, force: true });
|
|
1251
1996
|
}
|
|
1252
1997
|
}
|
|
1253
1998
|
for (const skill of res.skills) {
|
|
1254
|
-
const skillDir =
|
|
1999
|
+
const skillDir = join6(cache, skill.name);
|
|
1255
2000
|
await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
1256
2001
|
for (const file of skill.files) {
|
|
1257
|
-
const filePath =
|
|
1258
|
-
await fs2.mkdir(
|
|
2002
|
+
const filePath = join6(skillDir, file.path);
|
|
2003
|
+
await fs2.mkdir(join6(filePath, ".."), { recursive: true });
|
|
1259
2004
|
await fs2.writeFile(filePath, file.content, { mode: 384 });
|
|
1260
2005
|
}
|
|
1261
2006
|
}
|
|
1262
|
-
await fs2.writeFile(
|
|
2007
|
+
await fs2.writeFile(join6(cache, VERSION_FILE), res.version, { mode: 384 });
|
|
1263
2008
|
return cache;
|
|
1264
2009
|
}
|
|
1265
2010
|
|
|
@@ -1338,6 +2083,7 @@ async function runStart() {
|
|
|
1338
2083
|
api,
|
|
1339
2084
|
supervisor,
|
|
1340
2085
|
workspaceManager,
|
|
2086
|
+
runtimeRegistry,
|
|
1341
2087
|
runtimeIds: cfg.runtimes.map((r) => r.id)
|
|
1342
2088
|
});
|
|
1343
2089
|
claimer.start();
|
|
@@ -1353,16 +2099,46 @@ async function runStart() {
|
|
|
1353
2099
|
};
|
|
1354
2100
|
process.on("SIGINT", () => void stop("SIGINT"));
|
|
1355
2101
|
process.on("SIGTERM", () => void stop("SIGTERM"));
|
|
2102
|
+
process.on("unhandledRejection", (reason) => {
|
|
2103
|
+
console.warn("[daemon] unhandledRejection (continuing):", reason instanceof Error ? reason.message : String(reason));
|
|
2104
|
+
});
|
|
1356
2105
|
await new Promise(() => {
|
|
1357
2106
|
return;
|
|
1358
2107
|
});
|
|
1359
2108
|
}
|
|
1360
2109
|
|
|
2110
|
+
// src/sync.ts
|
|
2111
|
+
async function runSync() {
|
|
2112
|
+
const config = loadConfig();
|
|
2113
|
+
if (!config) {
|
|
2114
|
+
throw new Error(`No daemon config at ${CONFIG_PATH}. Run 'beevibe-daemon setup' first.`);
|
|
2115
|
+
}
|
|
2116
|
+
const detected = await detectClis();
|
|
2117
|
+
if (detected.length === 0) {
|
|
2118
|
+
throw new Error(`No supported CLIs detected on PATH. beevibe currently looks for: ${KNOWN_CLIS.join(", ")}`);
|
|
2119
|
+
}
|
|
2120
|
+
const api = new ApiClient({
|
|
2121
|
+
apiUrl: config.api_url,
|
|
2122
|
+
daemonToken: config.daemon_token
|
|
2123
|
+
});
|
|
2124
|
+
const { status, body } = await api.post("/runtime/sync", {
|
|
2125
|
+
runtimes: detected
|
|
2126
|
+
});
|
|
2127
|
+
if (status !== 200 || !body) {
|
|
2128
|
+
throw new Error(`/runtime/sync failed: ${status}`);
|
|
2129
|
+
}
|
|
2130
|
+
const before = new Set(config.runtimes.map((r) => r.cli));
|
|
2131
|
+
const added = body.runtimes.filter((r) => !before.has(r.cli));
|
|
2132
|
+
const next = { ...config, runtimes: body.runtimes };
|
|
2133
|
+
saveConfig(next);
|
|
2134
|
+
return { added, runtimes: body.runtimes };
|
|
2135
|
+
}
|
|
2136
|
+
|
|
1361
2137
|
// src/update.ts
|
|
1362
2138
|
import { createHash } from "node:crypto";
|
|
1363
2139
|
import { createWriteStream, mkdtempSync, rmSync as rmSync2, chmodSync, renameSync } from "node:fs";
|
|
1364
|
-
import { tmpdir as
|
|
1365
|
-
import { join as
|
|
2140
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
2141
|
+
import { join as join7 } from "node:path";
|
|
1366
2142
|
import { Readable } from "node:stream";
|
|
1367
2143
|
import { pipeline } from "node:stream/promises";
|
|
1368
2144
|
import { createInterface } from "node:readline/promises";
|
|
@@ -1376,7 +2152,7 @@ var PLATFORM_ASSETS = {
|
|
|
1376
2152
|
"linux-arm64": "beevibe-daemon-linux-arm64"
|
|
1377
2153
|
};
|
|
1378
2154
|
function currentVersion() {
|
|
1379
|
-
return "0.1.
|
|
2155
|
+
return "0.1.3";
|
|
1380
2156
|
}
|
|
1381
2157
|
function isCompiledBinary() {
|
|
1382
2158
|
if (!process.versions.bun)
|
|
@@ -1489,8 +2265,8 @@ async function runUpdate(opts = {}) {
|
|
|
1489
2265
|
return;
|
|
1490
2266
|
}
|
|
1491
2267
|
}
|
|
1492
|
-
const stagingDir = mkdtempSync(
|
|
1493
|
-
const stagingPath =
|
|
2268
|
+
const stagingDir = mkdtempSync(join7(tmpdir4(), "beevibe-daemon-update-"));
|
|
2269
|
+
const stagingPath = join7(stagingDir, asset);
|
|
1494
2270
|
try {
|
|
1495
2271
|
console.log(`Downloading ${asset}…`);
|
|
1496
2272
|
const downloadUrl = `${DOWNLOAD_BASE}/${latest}/${asset}`;
|
|
@@ -1548,6 +2324,7 @@ function printHelp() {
|
|
|
1548
2324
|
"Commands:",
|
|
1549
2325
|
" setup Register this machine with a beevibe api server.",
|
|
1550
2326
|
" start Run the daemon: claim pending sessions and spawn the CLI.",
|
|
2327
|
+
" sync Re-detect CLIs on PATH and register newly-installed ones.",
|
|
1551
2328
|
" update Check for and install a newer daemon binary (brew/curl installs).",
|
|
1552
2329
|
"",
|
|
1553
2330
|
"setup flags:",
|
|
@@ -1589,6 +2366,16 @@ async function main() {
|
|
|
1589
2366
|
await runStart();
|
|
1590
2367
|
return;
|
|
1591
2368
|
}
|
|
2369
|
+
if (command === "sync") {
|
|
2370
|
+
const result = await runSync();
|
|
2371
|
+
if (result.added.length === 0) {
|
|
2372
|
+
console.log("No new CLIs detected.");
|
|
2373
|
+
} else {
|
|
2374
|
+
console.log(`Added ${result.added.length} runtime(s): ${result.added.map((r) => `${r.cli} (${r.id})`).join(", ")}.`);
|
|
2375
|
+
console.log("Restart the daemon to pick up the new runtime(s).");
|
|
2376
|
+
}
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
1592
2379
|
if (command === "update") {
|
|
1593
2380
|
const skipPrompt = rest.includes("--yes") || rest.includes("-y");
|
|
1594
2381
|
await runUpdate({ skipPrompt });
|
package/package.json
CHANGED