symbiot 0.23.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 +42 -9
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -638,12 +638,16 @@ function detectHandoffs() {
638
638
  // Orca IDE (any OS): register the repo + open a terminal tab. Two variants —
639
639
  // one that just opens the repo (use Orca's own agent, e.g. GPT), and one that
640
640
  // runs Claude in the tab for Claude users. Not Claude-only.
641
+ // `<orca> open` first: it launches Orca AND blocks until the runtime is
642
+ // reachable, so this works even when Orca is closed (its CLI can't talk to a
643
+ // dead app). Use the full orca-ide path, never bare `orca` (that's a different
644
+ // tool on PATH). If Orca is already up, `open` returns fast.
641
645
  const orca = findOrcaCli();
642
646
  if (orca) {
643
647
  const q = JSON.stringify(orca);
644
648
  agents.unshift(
645
- { label: "Orca IDE — open repo (use your Orca agent)", tmpl: `${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --focus`, kind: "agent" },
646
- { label: "Orca IDE — run Claude in a tab", tmpl: `${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" },
647
651
  );
648
652
  }
649
653
  return { agents, editors };
@@ -651,7 +655,33 @@ function detectHandoffs() {
651
655
  const shSingle = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
652
656
  const escDq = (s) => String(s).replace(/[\\"$`]/g, "\\$&");
653
657
  const HANDOFF_PROMPT = "Read .symbiot/TASKS.md and implement the unchecked items in this repo. Confirm before anything destructive.";
654
- function handoffCmd() { const cfg = loadConfig(); return cfg.agentCmd || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
658
+ function handoffCmd() { const cfg = loadConfig(); return migrateOrcaCmd(cfg.agentCmd) || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
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.
673
+ function migrateOrcaCmd(cmd) {
674
+ if (!cmd || typeof cmd !== "string") return cmd;
675
+ if (!/orca-ide/.test(cmd) || !/\brepo add\b/.test(cmd)) return cmd;
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;
684
+ }
655
685
  // Run a shell command as a tracked, logged background job that shows up live in
656
686
  // the Agents tab. Shared by the agent handoff and the local-model setup.
657
687
  function track(name, cmd, cwd, onExit) {
@@ -1274,6 +1304,7 @@ a{color:var(--green);cursor:pointer}.hidden{display:none}
1274
1304
  .updatebar.show{display:flex}
1275
1305
  .updatebar.reconnect{background:var(--amber)}
1276
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}
1277
1308
  footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
1278
1309
  .profile{font-size:13px;margin-bottom:8px;line-height:1.5}
1279
1310
  .profile b{color:var(--bone)}
@@ -1339,7 +1370,7 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
1339
1370
  @media(max-width:760px){.maprow{flex-direction:column}.detail{width:auto;max-height:none}}
1340
1371
  </style></head><body>
1341
1372
  <div id="updatebar" class="updatebar"></div>
1342
- <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>
1343
1374
  <div class="tabs">
1344
1375
  <button class="tab active" data-tab="map">Map</button>
1345
1376
  <button class="tab" data-tab="drift">Drift</button>
@@ -1640,9 +1671,10 @@ document.getElementById('agentcmd').addEventListener('change',saveAgent);
1640
1671
  document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
1641
1672
  var SRV_STARTED=null,srvDown=false,updBusy=false;
1642
1673
  function ubar(){return document.getElementById('updatebar');}
1643
- function heartbeat(){
1644
- api('/api/ping').then(function(p){
1674
+ function heartbeat(fresh){
1675
+ api('/api/ping'+(fresh?'?fresh=1':'')).then(function(p){
1645
1676
  var b=ubar();
1677
+ var ve=document.getElementById('ver');if(ve&&p.version)ve.textContent='v'+p.version;
1646
1678
  if(SRV_STARTED===null){SRV_STARTED=p.started;}
1647
1679
  else if(p.started!==SRV_STARTED){location.reload();return;}
1648
1680
  if(srvDown){location.reload();return;}
@@ -1655,7 +1687,8 @@ function heartbeat(){
1655
1687
  }).catch(function(){srvDown=true;var b=ubar();b.className='updatebar reconnect show';b.textContent='Reconnecting to Symbiot…';});
1656
1688
  }
1657
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',{});}
1658
- 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
1659
1692
  initGraphEvents();syncP();refresh();loadMap();loadAgentCfg();loadScanRoots();
1660
1693
  </script></body></html>`;
1661
1694
 
@@ -1745,7 +1778,7 @@ async function cmdApp() {
1745
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 }; }));
1746
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)); }
1747
1780
  if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
1748
- 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 }); }
1749
1782
  if (u.pathname === "/api/update" && req.method === "POST") {
1750
1783
  // Install the newest symbiot, then relaunch this same app (same port +
1751
1784
  // token => same URL) and exit. The open page's heartbeat reconnects and
@@ -1778,7 +1811,7 @@ async function cmdApp() {
1778
1811
  else { try { server.listen(0, "127.0.0.1"); } catch {} }
1779
1812
  });
1780
1813
  server.listen(PORT, "127.0.0.1");
1781
- checkLatest(); setInterval(checkLatest, 10 * 60 * 1000).unref(); // background update check
1814
+ checkLatest(); setInterval(checkLatest, 2 * 60 * 1000).unref(); // background update check (every 2 min)
1782
1815
  }
1783
1816
 
1784
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.23.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": {