@beevibe/daemon 0.1.1 → 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.
Files changed (3) hide show
  1. package/README.md +91 -301
  2. package/dist/main.js +1076 -70
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2,8 +2,206 @@
2
2
 
3
3
  // src/setup.ts
4
4
  import { hostname, userInfo } from "node:os";
5
- import { spawnSync } from "node:child_process";
6
5
 
6
+ // ../../node_modules/.pnpm/nanoid@5.1.9/node_modules/nanoid/index.js
7
+ import { webcrypto as crypto } from "node:crypto";
8
+ var POOL_SIZE_MULTIPLIER = 128;
9
+ var pool;
10
+ var poolOffset;
11
+ function fillPool(bytes) {
12
+ if (!pool || pool.length < bytes) {
13
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
14
+ crypto.getRandomValues(pool);
15
+ poolOffset = 0;
16
+ } else if (poolOffset + bytes > pool.length) {
17
+ crypto.getRandomValues(pool);
18
+ poolOffset = 0;
19
+ }
20
+ poolOffset += bytes;
21
+ }
22
+ function random(bytes) {
23
+ fillPool(bytes |= 0);
24
+ return pool.subarray(poolOffset - bytes, poolOffset);
25
+ }
26
+ function customRandom(alphabet, defaultSize, getRandom) {
27
+ let safeByteCutoff = 256 - 256 % alphabet.length;
28
+ if (safeByteCutoff === 256) {
29
+ let mask = alphabet.length - 1;
30
+ return (size = defaultSize) => {
31
+ if (!size)
32
+ return "";
33
+ let id = "";
34
+ while (true) {
35
+ let bytes = getRandom(size);
36
+ let i = size;
37
+ while (i--) {
38
+ id += alphabet[bytes[i] & mask];
39
+ if (id.length >= size)
40
+ return id;
41
+ }
42
+ }
43
+ };
44
+ }
45
+ let step = Math.ceil(1.6 * 256 * defaultSize / safeByteCutoff);
46
+ return (size = defaultSize) => {
47
+ if (!size)
48
+ return "";
49
+ let id = "";
50
+ while (true) {
51
+ let bytes = getRandom(step);
52
+ let i = step;
53
+ while (i--) {
54
+ if (bytes[i] < safeByteCutoff) {
55
+ id += alphabet[bytes[i] % alphabet.length];
56
+ if (id.length >= size)
57
+ return id;
58
+ }
59
+ }
60
+ }
61
+ };
62
+ }
63
+ function customAlphabet(alphabet, size = 21) {
64
+ return customRandom(alphabet, size, random);
65
+ }
66
+
67
+ // ../core/dist/domain/ids.js
68
+ var alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
69
+ var nanoid12 = customAlphabet(alphabet, 12);
70
+ // ../core/dist/domain/core-memory.js
71
+ var DEFAULT_BLOCK_TEMPLATES = {
72
+ ic: [
73
+ {
74
+ block_name: "tag_line",
75
+ char_limit: 100,
76
+ is_system: true,
77
+ initial_content: "",
78
+ description: "One-line headline of my enduring specialization — shown on agent " + "cards in the UI. Describes what I'm an expert in, not what " + "project I'm currently on. Examples: 'Go backend specialist " + "(Chi/sqlc, websockets)', 'Next.js + React UI lead'. Max 100 " + "chars. Update only when my specialization itself shifts."
79
+ },
80
+ {
81
+ block_name: "persona",
82
+ char_limit: 2000,
83
+ is_system: true,
84
+ initial_content: "",
85
+ description: "Who I am and how I work — my role and working style, persistent " + "across every project I touch. 1-3 sentences in first person. " + "Update when my self-conception genuinely shifts (acquired a " + "major capability, refined my approach). NOT my current project " + "(that's `active_context`), NOT my domain scope (that's `domain`)."
86
+ },
87
+ {
88
+ block_name: "domain",
89
+ char_limit: 2000,
90
+ is_system: true,
91
+ initial_content: "",
92
+ description: "The areas I specialize in ACROSS all projects — my enduring " + "expertise. As I work on more projects in my domain, this can " + "deepen (becoming truly expert in narrower sub-areas). Bullet " + "format. Example: 'Go backend services: HTTP via Chi/echo, DB " + "via sqlc, websockets via gorilla, distribution via goreleaser.' " + "NOT project-specific paths (those go in `active_context`). NOT " + "the rules I follow (those go in `constraints`)."
93
+ },
94
+ {
95
+ block_name: "active_context",
96
+ char_limit: 2000,
97
+ is_system: true,
98
+ initial_content: "",
99
+ description: "What I'm currently working on — the specific project and its " + "in-flight details. Bullet format. Example: 'Project: " + "github.com/multica-ai/multica. Local clone: /tmp/multica-repo. " + "Owned paths in this project: server/cmd/**, server/internal/**. " + "Current task: task_xfQpuEHWjbvk.' Transient — rewrite when the " + "project changes. This is where ALL project/codebase-specific " + "details live, NOT in `domain`."
100
+ },
101
+ {
102
+ block_name: "constraints",
103
+ char_limit: 2000,
104
+ is_system: true,
105
+ initial_content: "",
106
+ description: "Hard rules I follow — non-negotiable conventions and " + "coordination boundaries. Mix of persistent rules ('queries " + "always via sqlc — never hand-write the DB layer') and " + "project-specific rules ('read /CLAUDE.md before changes in " + "this codebase'). Bullet format. Reference docs by path, not " + "content."
107
+ }
108
+ ],
109
+ team: [
110
+ {
111
+ block_name: "tag_line",
112
+ char_limit: 100,
113
+ is_system: true,
114
+ initial_content: "",
115
+ description: "One-line headline of my enduring role — shown on agent cards. " + "Describes the team I lead, not the project we're on. Examples: " + "'Daniel's team — orchestrates 3 backend/frontend/platform " + "specialists', 'Solo lead — driving a small team for hire'. " + "Max 100 chars."
116
+ },
117
+ {
118
+ block_name: "persona",
119
+ char_limit: 2000,
120
+ is_system: true,
121
+ initial_content: "",
122
+ description: "Who I am as a team lead — my orchestration style and how I " + "delegate. 1-3 sentences in first person. Persistent across " + "projects. NOT my team roster (that's `team_members`), NOT the " + "current work (that's `active_work`)."
123
+ },
124
+ {
125
+ block_name: "team_members",
126
+ char_limit: 3000,
127
+ is_system: true,
128
+ initial_content: "",
129
+ description: "Roster of my direct reports — for each: name, agent_id, " + "specialization (NOT project assignment — same agents stay over " + "time as we work different projects). Bullet format. Update " + "when subordinates are spawned/archived/reassigned."
130
+ },
131
+ {
132
+ block_name: "active_work",
133
+ char_limit: 2000,
134
+ is_system: true,
135
+ initial_content: "",
136
+ description: "What my team is currently working on — the active project + " + "high-level work in flight across specialists. Bullet format. " + "Example: 'Project: github.com/multica-ai/multica. Backend " + "specialist running CLI audit (task_xfQpuEHWjbvk); Frontend on " + "standby.' Transient — rewrite on project shifts."
137
+ },
138
+ {
139
+ block_name: "patterns",
140
+ char_limit: 2000,
141
+ is_system: true,
142
+ initial_content: "",
143
+ description: "Cross-project patterns I've observed in how my team operates — " + "what works, what trips them up. Persistent. Example: 'When I " + "assign an audit task, the IC tends to produce thorough work " + "products but forgets to save_memory mid-pass.' NOT specific " + "findings about a codebase (those go in archival memory via " + "save_memory)."
144
+ }
145
+ ],
146
+ org: [
147
+ {
148
+ block_name: "tag_line",
149
+ char_limit: 100,
150
+ is_system: true,
151
+ initial_content: "",
152
+ description: "One-line headline of my org-level role — shown on agent cards. " + "Describes the scope I oversee, not the current project. " + "Examples: 'Eng org lead — 3 teams (product/platform/infra)'. " + "Max 100 chars."
153
+ },
154
+ {
155
+ block_name: "persona",
156
+ char_limit: 2000,
157
+ is_system: true,
158
+ initial_content: "",
159
+ description: "Who I am as an org leader — my decision style and how I balance " + "across teams. 1-3 sentences. Persistent."
160
+ },
161
+ {
162
+ block_name: "teams",
163
+ char_limit: 3000,
164
+ is_system: true,
165
+ initial_content: "",
166
+ description: "Teams under my oversight — for each: name, team-lead agent_id, " + "scope. Persistent identity. Bullet format."
167
+ },
168
+ {
169
+ block_name: "strategy",
170
+ char_limit: 2000,
171
+ is_system: true,
172
+ initial_content: "",
173
+ description: "Cross-project / cross-team direction I'm driving. Higher-level " + "than active_work. Examples: 'Q2 focus: ship Multica self-host " + "v1', 'Hiring: prioritize backend over frontend this quarter'."
174
+ },
175
+ {
176
+ block_name: "decisions",
177
+ char_limit: 2000,
178
+ is_system: true,
179
+ initial_content: "",
180
+ description: "Cross-team decisions I've resolved — bullet log. Each entry: " + "what was decided, when, why. Persistent record so the same " + "question doesn't come back up."
181
+ }
182
+ ]
183
+ };
184
+ // ../core/dist/domain/memory.js
185
+ var FACT_TYPE_DESCRIPTIONS = {
186
+ belief: "A position you hold based on multiple sessions of evidence. A lasting view, " + "not a fleeting reaction to one session.",
187
+ pattern: "A recurring observation about the codebase or the domain you work in — " + "knowledge another agent could reuse. NOT a pattern about your own behavior " + "(your search habits, your memory-keeping, how you should have responded next " + "time). Save the thing you learned about the world, not a note-to-self about " + "how to behave.",
188
+ gotcha: "A non-obvious thing-that-bites — a footgun that's easy to step on, where the " + "surprise itself is the value. Concrete and reusable across future tasks in " + "the same area.",
189
+ preference: `A user's stated durable rule. Trigger words: "always", "from now on", ` + '"every time", "as a default", "going forward". Do NOT save preferences ' + "for one-off requests scoped to a specific task, session, or work-product " + '("after this task", "for this audit", "now"). When in doubt, just do the ' + "thing once without saving — the user can restate it if they want it to stick.",
190
+ decision: 'A chosen path with rationale. The "why" that future-you (or another agent) ' + "needs to understand why the codebase / approach looks the way it does — not " + 'the mechanical "what" (read the code for that).'
191
+ };
192
+ // ../core/dist/domain/runtime.js
193
+ var KNOWN_CLIS = ["claude", "codex", "opencode"];
194
+ var RUNTIME_HEARTBEAT_INTERVAL_MS = 15000;
195
+ // ../core/dist/auth/api-key.js
196
+ var KEY_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
197
+ var nanoid24 = customAlphabet(KEY_ALPHABET, 24);
198
+ // ../core/dist/auth/password.js
199
+ import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
200
+ import { promisify } from "node:util";
201
+ var scryptAsync = promisify(scrypt);
202
+ var SCRYPT_N = 16384;
203
+ var SCRYPT_R = 8;
204
+ var SCRYPT_MAXMEM = 128 * SCRYPT_N * SCRYPT_R * 2;
7
205
  // src/config.ts
8
206
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
207
  import { homedir } from "node:os";
@@ -25,8 +223,30 @@ function saveConfig(cfg) {
25
223
  `, { mode: 384 });
26
224
  }
27
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
+
28
249
  // src/setup.ts
29
- var KNOWN_CLIS = ["claude", "codex", "opencode"];
30
250
  async function runSetup(options) {
31
251
  if (!/^https?:\/\//.test(options.apiUrl)) {
32
252
  throw new Error("--api must be an http(s) URL");
@@ -36,7 +256,7 @@ async function runSetup(options) {
36
256
  }
37
257
  const externalId = options.externalId ?? hostname();
38
258
  const deviceName = options.deviceName ?? `${userInfo().username}@${hostname()}`;
39
- const runtimes = options.detectedClis ?? detectClis();
259
+ const runtimes = options.detectedClis ?? await detectClis();
40
260
  if (runtimes.length === 0) {
41
261
  throw new Error(`No supported CLIs detected on PATH. beevibe currently looks for: ${KNOWN_CLIS.join(", ")}`);
42
262
  }
@@ -66,26 +286,11 @@ async function runSetup(options) {
66
286
  saveConfig(config);
67
287
  return config;
68
288
  }
69
- function detectClis() {
70
- const out = [];
71
- for (const cli of KNOWN_CLIS) {
72
- const which = spawnSync("which", [cli], { encoding: "utf8" });
73
- if (which.status !== 0 || !which.stdout.trim())
74
- continue;
75
- const version = spawnSync(cli, ["--version"], { encoding: "utf8" });
76
- out.push({
77
- cli,
78
- cli_version: version.status === 0 ? version.stdout.trim().split(`
79
- `)[0] : undefined
80
- });
81
- }
82
- return out;
83
- }
84
289
 
85
290
  // ../core/dist/adapters/local-workspace/manager.js
86
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
291
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
87
292
  import { homedir as homedir2 } from "node:os";
88
- import { join as join2 } from "node:path";
293
+ import { isAbsolute, join as join2 } from "node:path";
89
294
 
90
295
  // ../core/dist/services/skills/sync.js
91
296
  import { promises as fs } from "node:fs";
@@ -216,35 +421,48 @@ class LocalWorkspaceManager {
216
421
  root;
217
422
  constructor(config) {
218
423
  this.config = config;
219
- this.root = config.workspaceRoot ?? join2(homedir2(), ".beevibe", "workspaces");
424
+ this.root = config.workspaceRoot || join2(homedir2(), ".beevibe", "workspaces");
425
+ if (!isAbsolute(this.root)) {
426
+ throw new Error(`LocalWorkspaceManager: workspaceRoot must be absolute, got "${this.root}"`);
427
+ }
220
428
  }
221
- async ensureWorkspace({ agent }) {
222
- const path2 = join2(this.root, agent.id);
429
+ async ensureWorkspace({ agent: agent2 }) {
430
+ const path2 = join2(this.root, agent2.id);
223
431
  mkdirSync2(path2, { recursive: true, mode: 448 });
432
+ if (!agent2.api_key) {
433
+ throw new Error(`Cannot write mcp-config.json for agent ${agent2.id}: agent.api_key is missing`);
434
+ }
224
435
  const configPath = join2(path2, "mcp-config.json");
225
- if (!existsSync2(configPath)) {
226
- if (!agent.api_key) {
227
- throw new Error(`Cannot write mcp-config.json for agent ${agent.id}: agent.api_key is missing`);
436
+ const expected = buildMcpConfig(agent2.api_key, this.config.mcpServerUrl);
437
+ const needsWrite = !existsSync2(configPath) || readFileSync2(configPath, "utf-8") !== expected;
438
+ if (needsWrite) {
439
+ writeFileSync2(configPath, expected, { mode: 384 });
440
+ }
441
+ const workspace2 = { path: path2 };
442
+ const runtime3 = this.config.runtimeRegistry[agent2.runtime_config.type];
443
+ if (!runtime3) {
444
+ throw new Error(`No runtime registered for agent ${agent2.id} (runtime_config.type='${agent2.runtime_config.type}')`);
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`);
228
449
  }
229
- writeFileSync2(configPath, buildMcpConfig(agent.api_key, this.config.mcpServerUrl), {
230
- mode: 384
450
+ await runtime3.prepareWorkspace({
451
+ workspace: workspace2,
452
+ agentApiKey: agent2.api_key,
453
+ mcpServerUrl: this.config.mcpServerUrl
231
454
  });
232
455
  }
233
- const workspace = { path: path2 };
234
- const runtime = this.config.runtimeRegistry[agent.runtime_config.type];
235
- if (!runtime) {
236
- throw new Error(`No runtime registered for agent ${agent.id} (runtime_config.type='${agent.runtime_config.type}')`);
237
- }
238
456
  await syncSkills({
239
457
  sourceDir: this.config.skillsSourceDir,
240
- targetDir: runtime.skillsDir(workspace),
241
- filter: tierFilterFor(agent.hierarchy_level),
458
+ targetDir: runtime3.skillsDir(workspace2),
459
+ filter: tierFilterFor(agent2.hierarchy_level),
242
460
  namespacePrefix: "beevibe"
243
461
  });
244
- return workspace;
462
+ return workspace2;
245
463
  }
246
- async removeWorkspace(workspace) {
247
- rmSync(workspace.path, { recursive: true, force: true });
464
+ async removeWorkspace(workspace2) {
465
+ rmSync(workspace2.path, { recursive: true, force: true });
248
466
  }
249
467
  }
250
468
  function buildMcpConfig(apiKey, mcpServerUrl) {
@@ -447,6 +665,15 @@ function extractStepEvents(msg) {
447
665
  }
448
666
  ];
449
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
+ }
450
677
  if (msg.type === STREAM_TYPE.ContentBlockStart && msg.content_block?.type === BLOCK_TYPE.ToolUse) {
451
678
  const block = msg.content_block;
452
679
  return [
@@ -480,6 +707,11 @@ function extractStepEvents(msg) {
480
707
  }
481
708
  return [];
482
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
+ }
483
715
  var PREFERRED_INPUT_FIELDS = [
484
716
  "file_path",
485
717
  "command",
@@ -594,12 +826,15 @@ function parseClaudeMessages(messages, exitCode) {
594
826
  } : undefined;
595
827
  return {
596
828
  status: succeeded ? "completed" : "failed",
597
- output: output || (succeeded ? "Session completed." : `CLI exited with code ${exitCode}`),
829
+ output: output || (succeeded ? "Session completed." : bareCliExitMessage(exitCode)),
598
830
  transcript: transcript || undefined,
599
831
  usage,
600
832
  cli_session_id: sessionId
601
833
  };
602
834
  }
835
+ function bareCliExitMessage(exitCode) {
836
+ return `CLI exited with code ${exitCode}`;
837
+ }
603
838
 
604
839
  // ../core/dist/adapters/claude-code/runtime.js
605
840
  var NESTING_GUARD_VARS = [
@@ -641,16 +876,19 @@ class ClaudeCodeRuntime {
641
876
  args.push("--max-turns", String(maxTurns));
642
877
  if (context.resume_session_id)
643
878
  args.push("--resume", context.resume_session_id);
879
+ if (context.disallowed_tools?.length) {
880
+ args.push("--disallowedTools", context.disallowed_tools.join(","));
881
+ }
644
882
  if (context.system_prompt_append.length > 0) {
645
883
  args.push("--append-system-prompt", context.system_prompt_append);
646
884
  }
647
- const env = { ...process.env };
885
+ const env2 = { ...process.env };
648
886
  for (const key of NESTING_GUARD_VARS)
649
- delete env[key];
887
+ delete env2[key];
650
888
  for (const key of ANTHROPIC_AUTH_VARS)
651
- delete env[key];
889
+ delete env2[key];
652
890
  if (context.env)
653
- Object.assign(env, context.env);
891
+ Object.assign(env2, context.env);
654
892
  const messages = [];
655
893
  let pending = "";
656
894
  const handleLine = (line) => {
@@ -668,7 +906,7 @@ class ClaudeCodeRuntime {
668
906
  command: this.config.command ?? "claude",
669
907
  args,
670
908
  cwd,
671
- env,
909
+ env: env2,
672
910
  stdin: context.intent,
673
911
  abortSignal: context.abort_signal,
674
912
  onSpawn: ({ pid, process_group_id }) => {
@@ -700,10 +938,14 @@ class ClaudeCodeRuntime {
700
938
  };
701
939
  }
702
940
  const parsed = parseClaudeMessages(messages, result.exitCode);
941
+ const STDERR_TAIL_BYTES = 4096;
942
+ const stderrTail = parsed.status === "failed" && result.stderr ? result.stderr.slice(-STDERR_TAIL_BYTES) : undefined;
703
943
  return {
704
944
  ...parsed,
705
945
  process_pid: result.pid ?? undefined,
706
- process_group_id: result.process_group_id ?? undefined
946
+ process_group_id: result.process_group_id ?? undefined,
947
+ exit_code: result.exitCode,
948
+ ...stderrTail ? { stderr: stderrTail } : {}
707
949
  };
708
950
  }
709
951
  async healthCheck() {
@@ -724,15 +966,710 @@ class ClaudeCodeRuntime {
724
966
  }
725
967
  }
726
968
  async shutdown() {}
727
- skillsDir(workspace) {
728
- return join3(workspace.path, ".claude", "skills");
969
+ skillsDir(workspace2) {
970
+ return join3(workspace2.path, ".claude", "skills");
971
+ }
972
+ }
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
+ });
729
1634
  }
730
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
+ }
731
1666
 
732
1667
  // ../core/dist/adapters/runtime-registry.js
733
1668
  function createDefaultRuntimeRegistry() {
734
1669
  return {
735
- claude: new ClaudeCodeRuntime({})
1670
+ claude: new ClaudeCodeRuntime({}),
1671
+ codex: new CodexRuntime({}),
1672
+ opencode: new OpenCodeRuntime({})
736
1673
  };
737
1674
  }
738
1675
 
@@ -794,16 +1731,22 @@ class ApiClient {
794
1731
  return `${this.cfg.apiUrl}${path2}`;
795
1732
  }
796
1733
  }
1734
+
797
1735
  // src/spawner.ts
798
1736
  async function runDispatch(deps, payload, abortSignal) {
799
1737
  const syntheticAgent = {
800
1738
  id: payload.agent_id,
801
1739
  api_key: payload.agent_api_key,
802
1740
  hierarchy_level: payload.agent_hierarchy_level,
803
- runtime_config: { type: "claude" }
1741
+ runtime_config: { type: payload.runtime_type }
804
1742
  };
805
1743
  const ws = await deps.workspaceManager.ensureWorkspace({ agent: syntheticAgent });
806
- const runtime = deps.runtime ?? new ClaudeCodeRuntime;
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
+ }
807
1750
  const buffer = [];
808
1751
  let flushTimer;
809
1752
  const flush = async () => {
@@ -839,12 +1782,13 @@ async function runDispatch(deps, payload, abortSignal) {
839
1782
  let result;
840
1783
  let runError;
841
1784
  try {
842
- result = await runtime.execute({
1785
+ result = await runtime3.execute({
843
1786
  intent: payload.intent,
844
1787
  workspace: ws,
845
1788
  system_prompt_append: payload.system_prompt_append,
846
1789
  model: payload.model,
847
1790
  max_turns: payload.max_turns,
1791
+ disallowed_tools: payload.disallowed_tools,
848
1792
  env: payload.env,
849
1793
  resume_session_id: payload.resume_session_id,
850
1794
  abort_signal: abortSignal,
@@ -859,15 +1803,25 @@ async function runDispatch(deps, payload, abortSignal) {
859
1803
  }
860
1804
  await flush();
861
1805
  const status = runError ? "failed" : result?.status === "completed" ? "succeeded" : result?.status === "cancelled" ? "cancelled" : "failed";
1806
+ const errorDetail = runError?.message ?? result?.stderr;
862
1807
  const done = {
863
1808
  session_id: payload.session_id,
864
1809
  status,
865
1810
  cli_session_id: result?.cli_session_id,
866
1811
  result_summary: result?.output ?? "",
867
- exit_code: status === "succeeded" ? 0 : 1,
868
- error: runError?.message,
1812
+ exit_code: result?.exit_code ?? null,
1813
+ error: errorDetail,
869
1814
  usage: result?.usage
870
1815
  };
1816
+ if (status === "succeeded") {
1817
+ console.log(`[daemon/spawn] sess=${payload.session_id} exit=0`);
1818
+ } else {
1819
+ console.error(`[daemon/spawn] sess=${payload.session_id} status=${status} exit=${done.exit_code}` + (errorDetail ? `
1820
+ error:
1821
+ ${errorDetail.split(`
1822
+ `).join(`
1823
+ `)}` : ""));
1824
+ }
871
1825
  try {
872
1826
  await deps.api.post("/runtime/done", done);
873
1827
  } catch (err) {
@@ -877,7 +1831,6 @@ async function runDispatch(deps, payload, abortSignal) {
877
1831
 
878
1832
  // src/claimer.ts
879
1833
  var DEFAULT_POLL_MS = 30000;
880
- var DEFAULT_HEARTBEAT_MS = 15000;
881
1834
  var DEFAULT_WS_RECONNECT_MAX_MS = 30000;
882
1835
 
883
1836
  class Claimer {
@@ -894,7 +1847,7 @@ class Claimer {
894
1847
  constructor(cfg) {
895
1848
  this.cfg = cfg;
896
1849
  this.pollIntervalMs = cfg.pollIntervalMs ?? DEFAULT_POLL_MS;
897
- this.heartbeatIntervalMs = cfg.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
1850
+ this.heartbeatIntervalMs = cfg.heartbeatIntervalMs ?? RUNTIME_HEARTBEAT_INTERVAL_MS;
898
1851
  this.wsReconnectMaxDelayMs = cfg.wsReconnectMaxDelayMs ?? DEFAULT_WS_RECONNECT_MAX_MS;
899
1852
  }
900
1853
  start() {
@@ -988,11 +1941,22 @@ class Claimer {
988
1941
  }
989
1942
  async pollRuntime(runtimeId) {
990
1943
  while (this.running && this.cfg.supervisor.hasCapacity()) {
991
- const payload = await this.cfg.api.claim(runtimeId);
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
+ }
992
1951
  if (!payload)
993
1952
  return;
1953
+ console.log(`[daemon/claim] sess=${payload.session_id} agent=${payload.agent_id} runtime=${runtimeId}`);
994
1954
  const ctrl = this.cfg.supervisor.start(payload.session_id);
995
- runDispatch({ api: this.cfg.api, workspaceManager: this.cfg.workspaceManager }, 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));
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));
996
1960
  }
997
1961
  }
998
1962
  }
@@ -1000,14 +1964,14 @@ class Claimer {
1000
1964
  // src/skills-cache.ts
1001
1965
  import { promises as fs2 } from "node:fs";
1002
1966
  import { homedir as homedir3 } from "node:os";
1003
- import { join as join4 } from "node:path";
1967
+ import { join as join6 } from "node:path";
1004
1968
  function skillsCacheDir() {
1005
- return join4(homedir3(), ".beevibe", "skills");
1969
+ return join6(homedir3(), ".beevibe", "skills");
1006
1970
  }
1007
1971
  var VERSION_FILE = ".version";
1008
1972
  async function readCachedVersion() {
1009
1973
  try {
1010
- return (await fs2.readFile(join4(skillsCacheDir(), VERSION_FILE), "utf8")).trim();
1974
+ return (await fs2.readFile(join6(skillsCacheDir(), VERSION_FILE), "utf8")).trim();
1011
1975
  } catch {
1012
1976
  return;
1013
1977
  }
@@ -1028,19 +1992,19 @@ async function syncSkillsCache(api) {
1028
1992
  if (!dirent.isDirectory())
1029
1993
  continue;
1030
1994
  if (dirent.name === "beevibe" || dirent.name.startsWith("beevibe-")) {
1031
- await fs2.rm(join4(cache, dirent.name), { recursive: true, force: true });
1995
+ await fs2.rm(join6(cache, dirent.name), { recursive: true, force: true });
1032
1996
  }
1033
1997
  }
1034
1998
  for (const skill of res.skills) {
1035
- const skillDir = join4(cache, skill.name);
1999
+ const skillDir = join6(cache, skill.name);
1036
2000
  await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
1037
2001
  for (const file of skill.files) {
1038
- const filePath = join4(skillDir, file.path);
1039
- await fs2.mkdir(join4(filePath, ".."), { recursive: true });
2002
+ const filePath = join6(skillDir, file.path);
2003
+ await fs2.mkdir(join6(filePath, ".."), { recursive: true });
1040
2004
  await fs2.writeFile(filePath, file.content, { mode: 384 });
1041
2005
  }
1042
2006
  }
1043
- await fs2.writeFile(join4(cache, VERSION_FILE), res.version, { mode: 384 });
2007
+ await fs2.writeFile(join6(cache, VERSION_FILE), res.version, { mode: 384 });
1044
2008
  return cache;
1045
2009
  }
1046
2010
 
@@ -1119,6 +2083,7 @@ async function runStart() {
1119
2083
  api,
1120
2084
  supervisor,
1121
2085
  workspaceManager,
2086
+ runtimeRegistry,
1122
2087
  runtimeIds: cfg.runtimes.map((r) => r.id)
1123
2088
  });
1124
2089
  claimer.start();
@@ -1134,16 +2099,46 @@ async function runStart() {
1134
2099
  };
1135
2100
  process.on("SIGINT", () => void stop("SIGINT"));
1136
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
+ });
1137
2105
  await new Promise(() => {
1138
2106
  return;
1139
2107
  });
1140
2108
  }
1141
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
+
1142
2137
  // src/update.ts
1143
2138
  import { createHash } from "node:crypto";
1144
2139
  import { createWriteStream, mkdtempSync, rmSync as rmSync2, chmodSync, renameSync } from "node:fs";
1145
- import { tmpdir as tmpdir2 } from "node:os";
1146
- import { join as join5 } from "node:path";
2140
+ import { tmpdir as tmpdir4 } from "node:os";
2141
+ import { join as join7 } from "node:path";
1147
2142
  import { Readable } from "node:stream";
1148
2143
  import { pipeline } from "node:stream/promises";
1149
2144
  import { createInterface } from "node:readline/promises";
@@ -1157,7 +2152,7 @@ var PLATFORM_ASSETS = {
1157
2152
  "linux-arm64": "beevibe-daemon-linux-arm64"
1158
2153
  };
1159
2154
  function currentVersion() {
1160
- return "0.1.1";
2155
+ return "0.1.3";
1161
2156
  }
1162
2157
  function isCompiledBinary() {
1163
2158
  if (!process.versions.bun)
@@ -1270,8 +2265,8 @@ async function runUpdate(opts = {}) {
1270
2265
  return;
1271
2266
  }
1272
2267
  }
1273
- const stagingDir = mkdtempSync(join5(tmpdir2(), "beevibe-daemon-update-"));
1274
- const stagingPath = join5(stagingDir, asset);
2268
+ const stagingDir = mkdtempSync(join7(tmpdir4(), "beevibe-daemon-update-"));
2269
+ const stagingPath = join7(stagingDir, asset);
1275
2270
  try {
1276
2271
  console.log(`Downloading ${asset}…`);
1277
2272
  const downloadUrl = `${DOWNLOAD_BASE}/${latest}/${asset}`;
@@ -1329,6 +2324,7 @@ function printHelp() {
1329
2324
  "Commands:",
1330
2325
  " setup Register this machine with a beevibe api server.",
1331
2326
  " start Run the daemon: claim pending sessions and spawn the CLI.",
2327
+ " sync Re-detect CLIs on PATH and register newly-installed ones.",
1332
2328
  " update Check for and install a newer daemon binary (brew/curl installs).",
1333
2329
  "",
1334
2330
  "setup flags:",
@@ -1370,6 +2366,16 @@ async function main() {
1370
2366
  await runStart();
1371
2367
  return;
1372
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
+ }
1373
2379
  if (command === "update") {
1374
2380
  const skipPrompt = rest.includes("--yes") || rest.includes("-y");
1375
2381
  await runUpdate({ skipPrompt });