bit-office 0.1.4 → 0.1.6

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/index.js CHANGED
@@ -4107,7 +4107,8 @@ var RunTaskCommand = external_exports.object({
4107
4107
  repoPath: external_exports.string().optional(),
4108
4108
  name: external_exports.string().optional(),
4109
4109
  role: external_exports.string().optional(),
4110
- personality: external_exports.string().optional()
4110
+ personality: external_exports.string().optional(),
4111
+ backend: external_exports.string().optional()
4111
4112
  });
4112
4113
  var ApprovalDecisionCommand = external_exports.object({
4113
4114
  type: external_exports.literal("APPROVAL_DECISION"),
@@ -4128,7 +4129,8 @@ var CreateAgentCommand = external_exports.object({
4128
4129
  name: external_exports.string(),
4129
4130
  role: external_exports.string(),
4130
4131
  palette: external_exports.number().optional(),
4131
- personality: external_exports.string().optional()
4132
+ personality: external_exports.string().optional(),
4133
+ backend: external_exports.string().optional()
4132
4134
  });
4133
4135
  var FireAgentCommand = external_exports.object({
4134
4136
  type: external_exports.literal("FIRE_AGENT"),
@@ -4196,13 +4198,21 @@ var TaskFailedEvent = external_exports.object({
4196
4198
  taskId: external_exports.string(),
4197
4199
  error: external_exports.string()
4198
4200
  });
4201
+ var TaskDelegatedEvent = external_exports.object({
4202
+ type: external_exports.literal("TASK_DELEGATED"),
4203
+ fromAgentId: external_exports.string(),
4204
+ toAgentId: external_exports.string(),
4205
+ taskId: external_exports.string(),
4206
+ prompt: external_exports.string()
4207
+ });
4199
4208
  var AgentCreatedEvent = external_exports.object({
4200
4209
  type: external_exports.literal("AGENT_CREATED"),
4201
4210
  agentId: external_exports.string(),
4202
4211
  name: external_exports.string(),
4203
4212
  role: external_exports.string(),
4204
4213
  palette: external_exports.number().optional(),
4205
- personality: external_exports.string().optional()
4214
+ personality: external_exports.string().optional(),
4215
+ backend: external_exports.string().optional()
4206
4216
  });
4207
4217
  var AgentFiredEvent = external_exports.object({
4208
4218
  type: external_exports.literal("AGENT_FIRED"),
@@ -4215,6 +4225,7 @@ var GatewayEventSchema = external_exports.discriminatedUnion("type", [
4215
4225
  ApprovalNeededEvent,
4216
4226
  TaskDoneEvent,
4217
4227
  TaskFailedEvent,
4228
+ TaskDelegatedEvent,
4218
4229
  AgentCreatedEvent,
4219
4230
  AgentFiredEvent
4220
4231
  ]);
@@ -4274,7 +4285,9 @@ function buildConfig() {
4274
4285
  wsPort: 9090,
4275
4286
  ablyApiKey: process.env.ABLY_API_KEY || saved.ablyApiKey || void 0,
4276
4287
  webDir: resolveWebDir(),
4277
- telegramBotTokens: process.env.TELEGRAM_BOT_TOKENS ? process.env.TELEGRAM_BOT_TOKENS.split(",").map((t) => t.trim() || void 0) : (saved.telegramBotTokens ?? []).map((t) => t || void 0)
4288
+ telegramBotTokens: process.env.TELEGRAM_BOT_TOKENS ? process.env.TELEGRAM_BOT_TOKENS.split(",").map((t) => t.trim() || void 0) : (saved.telegramBotTokens ?? []).map((t) => t || void 0),
4289
+ detectedBackends: saved.detectedBackends ?? [],
4290
+ defaultBackend: saved.defaultBackend ?? "claude"
4278
4291
  };
4279
4292
  }
4280
4293
  var config = buildConfig();
@@ -4354,7 +4367,8 @@ var wsChannel = {
4354
4367
  res.writeHead(200, { "Content-Type": "application/json" });
4355
4368
  res.end(JSON.stringify({
4356
4369
  machineId: config.machineId,
4357
- wsUrl: `ws://localhost:${config.wsPort}`
4370
+ wsUrl: `ws://localhost:${config.wsPort}`,
4371
+ hasAbly: !!config.ablyApiKey
4358
4372
  }));
4359
4373
  } catch {
4360
4374
  res.writeHead(400, { "Content-Type": "application/json" });
@@ -4553,13 +4567,79 @@ var ablyChannel = {
4553
4567
  import TelegramBot from "node-telegram-bot-api";
4554
4568
 
4555
4569
  // src/agent-session.ts
4556
- import { spawn, execSync } from "child_process";
4570
+ import { spawn, execSync as execSync2 } from "child_process";
4557
4571
  import { nanoid } from "nanoid";
4572
+
4573
+ // src/ai-backends.ts
4574
+ import { execSync } from "child_process";
4575
+ var backends = [
4576
+ {
4577
+ id: "claude",
4578
+ name: "Claude Code",
4579
+ command: "claude",
4580
+ buildArgs(prompt, opts) {
4581
+ const args = ["-p", prompt, "--output-format", "text", "--dangerously-skip-permissions"];
4582
+ if (opts.continue) args.push("--continue");
4583
+ return args;
4584
+ },
4585
+ deleteEnv: ["CLAUDECODE"]
4586
+ },
4587
+ {
4588
+ id: "codex",
4589
+ name: "Codex CLI",
4590
+ command: "codex",
4591
+ buildArgs(prompt) {
4592
+ return ["exec", prompt, "--full-auto"];
4593
+ }
4594
+ },
4595
+ {
4596
+ id: "gemini",
4597
+ name: "Gemini CLI",
4598
+ command: "gemini",
4599
+ buildArgs(prompt) {
4600
+ return ["-p", prompt, "--yolo"];
4601
+ }
4602
+ },
4603
+ {
4604
+ id: "aider",
4605
+ name: "Aider",
4606
+ command: "aider",
4607
+ buildArgs(prompt) {
4608
+ return ["--message", prompt, "--yes", "--no-pretty"];
4609
+ }
4610
+ },
4611
+ {
4612
+ id: "opencode",
4613
+ name: "OpenCode",
4614
+ command: "opencode",
4615
+ buildArgs(prompt) {
4616
+ return ["run", prompt, "--quiet"];
4617
+ }
4618
+ }
4619
+ ];
4620
+ var backendMap = new Map(backends.map((b) => [b.id, b]));
4621
+ function getBackend(id) {
4622
+ return backendMap.get(id);
4623
+ }
4624
+ function detectBackends() {
4625
+ const detected = [];
4626
+ for (const backend of backends) {
4627
+ try {
4628
+ execSync(`which ${backend.command}`, { stdio: "ignore", timeout: 3e3 });
4629
+ detected.push(backend.id);
4630
+ } catch {
4631
+ }
4632
+ }
4633
+ return detected;
4634
+ }
4635
+
4636
+ // src/agent-session.ts
4558
4637
  var AgentSession = class {
4559
4638
  agentId;
4560
4639
  name;
4561
4640
  role;
4562
4641
  personality;
4642
+ backend;
4563
4643
  process = null;
4564
4644
  currentTaskId = null;
4565
4645
  currentCwd = null;
@@ -4571,14 +4651,15 @@ var AgentSession = class {
4571
4651
  workspace;
4572
4652
  stdoutBuffer = "";
4573
4653
  hasHistory = false;
4574
- // track if agent has prior conversation
4575
- constructor(agentId, name, role, personality, workspace, resumeHistory) {
4654
+ onDelegation = null;
4655
+ constructor(agentId, name, role, personality, workspace, resumeHistory, backendId) {
4576
4656
  this.agentId = agentId;
4577
4657
  this.name = name;
4578
4658
  this.role = role;
4579
4659
  this.personality = personality ?? "";
4580
4660
  this.workspace = workspace ?? config.defaultWorkspace;
4581
4661
  this.hasHistory = resumeHistory ?? false;
4662
+ this.backend = getBackend(backendId ?? config.defaultBackend) ?? getBackend("claude");
4582
4663
  }
4583
4664
  async runTask(taskId, prompt, repoPath) {
4584
4665
  if (this.process) {
@@ -4603,26 +4684,32 @@ var AgentSession = class {
4603
4684
  this.setStatus("working");
4604
4685
  try {
4605
4686
  const cleanEnv = { ...process.env };
4606
- delete cleanEnv.CLAUDECODE;
4687
+ for (const key of this.backend.deleteEnv ?? []) {
4688
+ delete cleanEnv[key];
4689
+ }
4607
4690
  const personalityClause = this.personality ? `${this.personality}
4608
4691
 
4609
4692
  ` : "";
4610
- const identityPrompt = `Your name is ${this.name}, your role is ${this.role}. ${personalityClause}
4611
-
4612
- ${prompt}`;
4613
- const args = ["-p", identityPrompt, "--output-format", "text", "--dangerously-skip-permissions"];
4614
- if (this.hasHistory) {
4615
- args.push("--continue");
4616
- }
4617
- this.process = spawn("claude", args, {
4693
+ const delegationHint = "To delegate a task to another agent, output on its own line: @AgentName: <task description>\n\n";
4694
+ const identityPrompt = `Your name is ${this.name}, your role is ${this.role}. ${personalityClause}${delegationHint}${prompt}`;
4695
+ const args = this.backend.buildArgs(identityPrompt, { continue: this.hasHistory });
4696
+ this.process = spawn(this.backend.command, args, {
4618
4697
  cwd,
4619
4698
  env: cleanEnv,
4620
4699
  stdio: ["ignore", "pipe", "pipe"]
4621
4700
  });
4701
+ const DELEGATION_RE = /^@(\w+):\s*(.+)$/;
4622
4702
  this.process.stdout?.on("data", (data) => {
4623
4703
  const chunk = data.toString();
4624
4704
  this.stdoutBuffer += chunk;
4625
4705
  const lines = chunk.split("\n").filter((l) => l.trim());
4706
+ for (const line of lines) {
4707
+ const match = line.match(DELEGATION_RE);
4708
+ if (match && this.onDelegation) {
4709
+ const [, targetName, delegatedPrompt] = match;
4710
+ this.onDelegation(this.agentId, targetName, delegatedPrompt);
4711
+ }
4712
+ }
4626
4713
  if (lines.length > 0) {
4627
4714
  publishEvent({
4628
4715
  type: "LOG_APPEND",
@@ -4754,13 +4841,13 @@ ${prompt}`;
4754
4841
  let changedFiles = [];
4755
4842
  let diffStat = "";
4756
4843
  try {
4757
- const nameOnly = execSync("git diff --name-only HEAD~1 2>/dev/null || git diff --name-only", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
4844
+ const nameOnly = execSync2("git diff --name-only HEAD~1 2>/dev/null || git diff --name-only", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
4758
4845
  changedFiles = nameOnly ? nameOnly.split("\n") : [];
4759
4846
  } catch {
4760
4847
  }
4761
4848
  if (changedFiles.length > 0) {
4762
4849
  try {
4763
- diffStat = execSync("git diff --stat HEAD~1 2>/dev/null || git diff --stat", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
4850
+ diffStat = execSync2("git diff --stat HEAD~1 2>/dev/null || git diff --stat", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
4764
4851
  } catch {
4765
4852
  }
4766
4853
  }
@@ -4784,12 +4871,12 @@ ${prompt}`;
4784
4871
  // src/agent-manager.ts
4785
4872
  var AgentManager = class {
4786
4873
  agents = /* @__PURE__ */ new Map();
4787
- create(agentId, name, role, personality, workspace, resumeHistory) {
4874
+ create(agentId, name, role, personality, workspace, resumeHistory, backendId) {
4788
4875
  const existing = this.agents.get(agentId);
4789
4876
  if (existing) {
4790
4877
  existing.destroy();
4791
4878
  }
4792
- const session = new AgentSession(agentId, name, role, personality, workspace, resumeHistory);
4879
+ const session = new AgentSession(agentId, name, role, personality, workspace, resumeHistory, backendId);
4793
4880
  this.agents.set(agentId, session);
4794
4881
  return session;
4795
4882
  }
@@ -4806,6 +4893,14 @@ var AgentManager = class {
4806
4893
  getAll() {
4807
4894
  return Array.from(this.agents.values());
4808
4895
  }
4896
+ findByName(name) {
4897
+ for (const session of this.agents.values()) {
4898
+ if (session.name.toLowerCase() === name.toLowerCase()) {
4899
+ return session;
4900
+ }
4901
+ }
4902
+ return void 0;
4903
+ }
4809
4904
  };
4810
4905
  var agentManager = new AgentManager();
4811
4906
 
@@ -4956,11 +5051,30 @@ var telegramChannel = {
4956
5051
  import { createInterface } from "readline";
4957
5052
  function ask(rl, question) {
4958
5053
  return new Promise((resolve2) => {
4959
- rl.question(question, (answer) => resolve2(answer.trim()));
5054
+ const onClose = () => resolve2("");
5055
+ rl.once("close", onClose);
5056
+ rl.question(question, (answer) => {
5057
+ rl.removeListener("close", onClose);
5058
+ resolve2(answer.trim());
5059
+ });
4960
5060
  });
4961
5061
  }
4962
5062
  async function runSetup() {
4963
- const rl = createInterface({ input: process.stdin, output: process.stdout });
5063
+ console.log("[Setup] Detecting AI backends...");
5064
+ const detected = detectBackends();
5065
+ const detectedNames = detected.map((id) => getBackend(id)?.name ?? id).join(", ");
5066
+ console.log(`[Setup] Found: ${detectedNames || "none"}`);
5067
+ if (!process.stdin.isTTY) {
5068
+ saveConfig({ detectedBackends: detected, defaultBackend: detected[0] ?? "claude" });
5069
+ console.log("\u2713 Default config saved to ~/.bit-office/config.json");
5070
+ console.log(" Run with --setup in a terminal to configure.\n");
5071
+ return;
5072
+ }
5073
+ const rl = createInterface({
5074
+ input: process.stdin,
5075
+ output: process.stdout,
5076
+ terminal: true
5077
+ });
4964
5078
  console.log("");
4965
5079
  console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
4966
5080
  console.log("\u2551 Bit Office \u2014 First Setup \u2551");
@@ -4970,13 +5084,26 @@ async function runSetup() {
4970
5084
  console.log("\u2500\u2500 Remote Access (Ably) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
4971
5085
  console.log("Enables access from outside your LAN.");
4972
5086
  const ablyApiKey = await ask(rl, "Ably API Key (optional): ");
5087
+ let defaultBackend = detected[0] ?? "claude";
5088
+ if (detected.length > 1) {
5089
+ console.log("\n\u2500\u2500 AI Backends \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
5090
+ console.log(`Detected: ${detectedNames}`);
5091
+ const choices = detected.map((id, i) => `${i + 1}=${getBackend(id)?.name ?? id}`).join(", ");
5092
+ const pick = await ask(rl, `Default backend (${choices}): `);
5093
+ const idx = parseInt(pick, 10) - 1;
5094
+ if (idx >= 0 && idx < detected.length) {
5095
+ defaultBackend = detected[idx];
5096
+ }
5097
+ }
4973
5098
  rl.close();
4974
5099
  saveConfig({
4975
- ablyApiKey: ablyApiKey || void 0
4976
- // telegramBotTokens: tokens,
5100
+ ablyApiKey: ablyApiKey || void 0,
5101
+ detectedBackends: detected,
5102
+ defaultBackend
4977
5103
  });
4978
5104
  console.log("\n\u2713 Config saved to ~/.bit-office/config.json");
4979
5105
  if (ablyApiKey) console.log(" \u2022 Ably: enabled");
5106
+ console.log(` \u2022 Default AI: ${getBackend(defaultBackend)?.name ?? defaultBackend}`);
4980
5107
  console.log(" \u2022 Run with --setup to reconfigure\n");
4981
5108
  }
4982
5109
 
@@ -5001,19 +5128,42 @@ function showPairCode() {
5001
5128
  console.log(`Open your phone \u2192 enter gateway address + code`);
5002
5129
  console.log("");
5003
5130
  }
5131
+ function wireDelegation(session) {
5132
+ if (!session) return;
5133
+ session.onDelegation = (fromAgentId, targetName, prompt) => {
5134
+ const target = agentManager.findByName(targetName);
5135
+ if (!target) {
5136
+ console.log(`[Delegation] Target agent "${targetName}" not found, ignoring`);
5137
+ return;
5138
+ }
5139
+ const taskId = nanoid3();
5140
+ console.log(`[Delegation] ${fromAgentId} -> ${target.agentId} (${targetName}): ${prompt.slice(0, 80)}`);
5141
+ publishEvent({
5142
+ type: "TASK_DELEGATED",
5143
+ fromAgentId,
5144
+ toAgentId: target.agentId,
5145
+ taskId,
5146
+ prompt
5147
+ });
5148
+ target.runTask(taskId, prompt);
5149
+ };
5150
+ }
5004
5151
  function handleCommand(parsed) {
5005
5152
  console.log("[Gateway] Received command:", parsed.type, JSON.stringify(parsed));
5006
5153
  switch (parsed.type) {
5007
5154
  case "CREATE_AGENT": {
5008
- console.log(`[Gateway] Creating agent: ${parsed.agentId} (${parsed.name} - ${parsed.role})`);
5009
- agentManager.create(parsed.agentId, parsed.name, parsed.role, parsed.personality);
5155
+ const backendId = parsed.backend ?? config.defaultBackend;
5156
+ console.log(`[Gateway] Creating agent: ${parsed.agentId} (${parsed.name} - ${parsed.role}) backend=${backendId}`);
5157
+ const session = agentManager.create(parsed.agentId, parsed.name, parsed.role, parsed.personality, void 0, false, backendId);
5158
+ wireDelegation(session);
5010
5159
  publishEvent({
5011
5160
  type: "AGENT_CREATED",
5012
5161
  agentId: parsed.agentId,
5013
5162
  name: parsed.name,
5014
5163
  role: parsed.role,
5015
5164
  palette: parsed.palette,
5016
- personality: parsed.personality
5165
+ personality: parsed.personality,
5166
+ backend: backendId
5017
5167
  });
5018
5168
  publishEvent({
5019
5169
  type: "AGENT_STATUS",
@@ -5034,13 +5184,16 @@ function handleCommand(parsed) {
5034
5184
  case "RUN_TASK": {
5035
5185
  let session = agentManager.get(parsed.agentId);
5036
5186
  if (!session && parsed.name) {
5037
- console.log(`[Gateway] Auto-creating agent for RUN_TASK: ${parsed.agentId}`);
5038
- session = agentManager.create(parsed.agentId, parsed.name, parsed.role ?? "", parsed.personality, void 0, true);
5187
+ const backendId = parsed.backend ?? config.defaultBackend;
5188
+ console.log(`[Gateway] Auto-creating agent for RUN_TASK: ${parsed.agentId} backend=${backendId}`);
5189
+ session = agentManager.create(parsed.agentId, parsed.name, parsed.role ?? "", parsed.personality, void 0, true, backendId);
5190
+ wireDelegation(session);
5039
5191
  publishEvent({
5040
5192
  type: "AGENT_CREATED",
5041
5193
  agentId: parsed.agentId,
5042
5194
  name: parsed.name,
5043
- role: parsed.role ?? ""
5195
+ role: parsed.role ?? "",
5196
+ backend: backendId
5044
5197
  });
5045
5198
  }
5046
5199
  if (session) {
@@ -5082,7 +5235,8 @@ function handleCommand(parsed) {
5082
5235
  agentId: agent.agentId,
5083
5236
  name: agent.name,
5084
5237
  role: agent.role,
5085
- personality: agent.personality
5238
+ personality: agent.personality,
5239
+ backend: agent.backend.id
5086
5240
  });
5087
5241
  publishEvent({
5088
5242
  type: "AGENT_STATUS",
@@ -5099,6 +5253,18 @@ async function main() {
5099
5253
  await runSetup();
5100
5254
  reloadConfig();
5101
5255
  }
5256
+ if (config.detectedBackends.length === 0) {
5257
+ const detected = detectBackends();
5258
+ if (detected.length > 0) {
5259
+ config.detectedBackends = detected;
5260
+ if (!config.defaultBackend || !detected.includes(config.defaultBackend)) {
5261
+ config.defaultBackend = detected[0];
5262
+ }
5263
+ saveConfig({ detectedBackends: detected, defaultBackend: config.defaultBackend });
5264
+ }
5265
+ }
5266
+ const backendNames = config.detectedBackends.map((id) => getBackend(id)?.name ?? id).join(", ");
5267
+ console.log(`[Gateway] AI backends: ${backendNames || "none detected"} (default: ${getBackend(config.defaultBackend)?.name ?? config.defaultBackend})`);
5102
5268
  console.log(`[Gateway] Starting for machine: ${config.machineId}`);
5103
5269
  showPairCode();
5104
5270
  await initTransports(handleCommand);