symbiot 0.24.0 → 0.26.0

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 (2) hide show
  1. package/index.mjs +33 -17
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -646,8 +646,8 @@ function detectHandoffs() {
646
646
  if (orca) {
647
647
  const q = JSON.stringify(orca);
648
648
  agents.unshift(
649
- { label: "Orca IDE — open repo (use your Orca agent)", tmpl: `${q} open; ${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --focus`, kind: "agent" },
650
- { label: "Orca IDE — run Claude in a tab", tmpl: `${q} open; ${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --command "claude {prompt}" --focus`, kind: "agent" },
649
+ { label: "Orca IDE — open repo (use your Orca agent)", tmpl: orcaHandoffCmd(q, ""), kind: "agent" },
650
+ { label: "Orca IDE — run Claude in a tab", tmpl: orcaHandoffCmd(q, ` --command "claude {prompt}"`), kind: "agent" },
651
651
  );
652
652
  }
653
653
  return { agents, editors };
@@ -656,18 +656,31 @@ const shSingle = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
656
656
  const escDq = (s) => String(s).replace(/[\\"$`]/g, "\\$&");
657
657
  const HANDOFF_PROMPT = "Read .symbiot/TASKS.md and implement the unchecked items in this repo. Confirm before anything destructive.";
658
658
  function handoffCmd() { const cfg = loadConfig(); return migrateOrcaCmd(cfg.agentCmd) || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
659
- // An Orca command saved before we learned its CLI needs a running app lacks the
660
- // `open` step and silently fails when Orca is closed. Patch it in place: insert
661
- // `<orca> open; ` before the first `repo add`. Idempotent (skips if already there).
659
+ // Build the full Orca handoff, cold-start safe. `bin` is the quoted orca-ide
660
+ // path; `commandPart` is e.g. ` --command "claude {prompt}"` or "" (open only).
661
+ // open launches Orca & waits for the runtime to be REACHABLE, but on a cold
662
+ // start the workspace graph isn't ready yet (runtime.state=graph_not_ready) and
663
+ // `terminal create` times out — so we poll `status` until state=ready, then add
664
+ // the repo and create the terminal (retry: the worktree can lag a beat behind).
665
+ function orcaHandoffCmd(bin, commandPart) {
666
+ const waitReady = `for i in $(seq 1 40); do ${bin} status --json 2>/dev/null | grep -q '"state": *"ready"' && break; sleep 1; done`;
667
+ const mkTerm = `for j in 1 2 3; do ${bin} terminal create --worktree path:{dir}${commandPart} --focus && break; sleep 2; done`;
668
+ return `${bin} open; ${waitReady}; ${bin} repo add --path {dir}; ${mkTerm}`;
669
+ }
670
+ // Normalise an Orca command saved by an older version (no launch / no wait-for-
671
+ // ready) to the current cold-start-safe form, preserving its binary path and any
672
+ // custom `--command`. Idempotent: already-current commands are left untouched.
662
673
  function migrateOrcaCmd(cmd) {
663
674
  if (!cmd || typeof cmd !== "string") return cmd;
664
675
  if (!/orca-ide/.test(cmd) || !/\brepo add\b/.test(cmd)) return cmd;
665
- if (/orca-ide("|')?\s+open\b|\bopen;\s/.test(cmd)) return cmd; // already migrated
666
- const m = cmd.match(/^(\S+|"[^"]*"|'[^']*')\s+repo add/); // the orca binary token
667
- const bin = m ? m[1] : "";
668
- const patched = bin ? cmd.replace(/(\S+|"[^"]*"|'[^']*')\s+repo add/, `${bin} open; $1 repo add`) : cmd;
669
- if (patched !== cmd) { try { const cfg = loadConfig(); if (cfg.agentCmd === cmd) { cfg.agentCmd = patched; saveConfig(cfg); } } catch {} }
670
- return patched;
676
+ if (/status --json/.test(cmd) && /grep -q/.test(cmd)) return cmd; // already the wait-for-ready form
677
+ const bm = cmd.match(/^\s*("[^"]*"|'[^']*'|\S+)/); // leading orca-ide binary token
678
+ const bin = bm ? bm[1] : "";
679
+ const cm = cmd.match(/--command\s+"([^"]*)"/); // preserve a custom agent command
680
+ const commandPart = cm ? ` --command "${cm[1]}"` : "";
681
+ const rebuilt = bin ? orcaHandoffCmd(bin, commandPart) : cmd;
682
+ if (rebuilt !== cmd) { try { const cfg = loadConfig(); if (cfg.agentCmd === cmd) { cfg.agentCmd = rebuilt; saveConfig(cfg); } } catch {} }
683
+ return rebuilt;
671
684
  }
672
685
  // Run a shell command as a tracked, logged background job that shows up live in
673
686
  // the Agents tab. Shared by the agent handoff and the local-model setup.
@@ -1291,6 +1304,7 @@ a{color:var(--green);cursor:pointer}.hidden{display:none}
1291
1304
  .updatebar.show{display:flex}
1292
1305
  .updatebar.reconnect{background:var(--amber)}
1293
1306
  .updatebar button{font:inherit;font-weight:700;border:0;border-radius:8px;padding:5px 12px;background:var(--ink);color:var(--bone);cursor:pointer}
1307
+ .ver{font-size:11px;color:var(--faint);background:var(--ink3);border:1px solid var(--line);border-radius:999px;padding:2px 8px;margin-left:2px;font-family:ui-monospace,Menlo,Consolas,monospace}
1294
1308
  footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
1295
1309
  .profile{font-size:13px;margin-bottom:8px;line-height:1.5}
1296
1310
  .profile b{color:var(--bone)}
@@ -1356,7 +1370,7 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
1356
1370
  @media(max-width:760px){.maprow{flex-direction:column}.detail{width:auto;max-height:none}}
1357
1371
  </style></head><body>
1358
1372
  <div id="updatebar" class="updatebar"></div>
1359
- <header><span class="dot"></span><span class="brand">Symbiot</span><span class="status" id="status">...</span></header>
1373
+ <header><span class="dot"></span><span class="brand">Symbiot</span><span class="ver" id="ver"></span><span class="status" id="status">...</span></header>
1360
1374
  <div class="tabs">
1361
1375
  <button class="tab active" data-tab="map">Map</button>
1362
1376
  <button class="tab" data-tab="drift">Drift</button>
@@ -1657,9 +1671,10 @@ document.getElementById('agentcmd').addEventListener('change',saveAgent);
1657
1671
  document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
1658
1672
  var SRV_STARTED=null,srvDown=false,updBusy=false;
1659
1673
  function ubar(){return document.getElementById('updatebar');}
1660
- function heartbeat(){
1661
- api('/api/ping').then(function(p){
1674
+ function heartbeat(fresh){
1675
+ api('/api/ping'+(fresh?'?fresh=1':'')).then(function(p){
1662
1676
  var b=ubar();
1677
+ var ve=document.getElementById('ver');if(ve&&p.version)ve.textContent='v'+p.version;
1663
1678
  if(SRV_STARTED===null){SRV_STARTED=p.started;}
1664
1679
  else if(p.started!==SRV_STARTED){location.reload();return;}
1665
1680
  if(srvDown){location.reload();return;}
@@ -1672,7 +1687,8 @@ function heartbeat(){
1672
1687
  }).catch(function(){srvDown=true;var b=ubar();b.className='updatebar reconnect show';b.textContent='Reconnecting to Symbiot…';});
1673
1688
  }
1674
1689
  function doUpdate(){updBusy=true;var b=ubar();b.className='updatebar show';b.textContent='Updating & restarting… this page will reload itself when it is back.';api('/api/update',{});}
1675
- setInterval(heartbeat,4000);heartbeat();
1690
+ setInterval(heartbeat,4000);heartbeat(true);
1691
+ window.addEventListener('focus',function(){heartbeat(true);}); // re-check for updates when you come back to the window
1676
1692
  initGraphEvents();syncP();refresh();loadMap();loadAgentCfg();loadScanRoots();
1677
1693
  </script></body></html>`;
1678
1694
 
@@ -1762,7 +1778,7 @@ async function cmdApp() {
1762
1778
  if (u.pathname === "/api/agents") return json(res, HANDOFFS.map((e) => { let tail = ""; try { tail = readFileSync(e.log, "utf8").slice(-1200); } catch {} return { id: e.id, name: e.name, path: e.path, status: e.status, elapsed: (e.endedAt || Date.now()) - e.startedAt, exitCode: e.exitCode, tail }; }));
1763
1779
  if (u.pathname === "/api/run" && req.method === "POST") { const b = await readBody(req); const cmd = ["week", "standup", "todo"].includes(b.cmd) ? b.cmd : "week"; return json(res, await produce(cmd)); }
1764
1780
  if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
1765
- if (u.pathname === "/api/ping") return json(res, { version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION });
1781
+ if (u.pathname === "/api/ping") { if (u.searchParams.get("fresh") === "1") await checkLatest(); return json(res, { version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION }); }
1766
1782
  if (u.pathname === "/api/update" && req.method === "POST") {
1767
1783
  // Install the newest symbiot, then relaunch this same app (same port +
1768
1784
  // token => same URL) and exit. The open page's heartbeat reconnects and
@@ -1795,7 +1811,7 @@ async function cmdApp() {
1795
1811
  else { try { server.listen(0, "127.0.0.1"); } catch {} }
1796
1812
  });
1797
1813
  server.listen(PORT, "127.0.0.1");
1798
- checkLatest(); setInterval(checkLatest, 10 * 60 * 1000).unref(); // background update check
1814
+ checkLatest(); setInterval(checkLatest, 2 * 60 * 1000).unref(); // background update check (every 2 min)
1799
1815
  }
1800
1816
 
1801
1817
  const HELP = `${c.b("symbiot")} — your week, written from your real work.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "symbiot",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice — Claude, OpenAI, Gemini, or a local model.",
5
5
  "type": "module",
6
6
  "bin": {