symbiot 0.63.3 → 0.63.4
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/CHANGELOG.md +5 -0
- package/core.mjs +15 -7
- package/package.json +2 -2
- package/server.mjs +6 -4
- package/ui.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
What each Symbiot release brought, newest first. When Approve bumps the version, it writes that release here from the tasks it approved, worded as release notes by your connected AI (or in the tasks' own words without one). The app shows the releases newer than yours under "What's new" when it offers an update, and once more after you update.
|
|
4
4
|
|
|
5
|
+
## 0.63.4
|
|
6
|
+
|
|
7
|
+
- **Symbiot no longer freezes as your task list grows.** Every 20 seconds it checks the ideas your agents left against all your tasks, and with a few hundred tasks that took up to 10 seconds a time, until the app stopped answering altogether. The update to 0.63.3 then couldn't restart it. The same check now takes a fraction of a second.
|
|
8
|
+
- **An update keeps trying for 10 minutes.** npm can list a new version a few minutes before its download is ready. Symbiot gave up after 2.5 minutes; now it keeps trying every 30 seconds for 10.
|
|
9
|
+
|
|
5
10
|
## 0.63.3
|
|
6
11
|
|
|
7
12
|
- **Allow works on a request that takes access away.** When an agent asked to lock itself out of something (a deny rule, with nothing new allowed), Symbiot dropped the request, so clicking Allow did nothing and the agent waited for good. Now it's turned on, without asking you, and the agent carries on.
|
package/core.mjs
CHANGED
|
@@ -77,7 +77,9 @@ const clipWords = (s, max) => { s = String(s || ""); return s.length > max ? s.s
|
|
|
77
77
|
// in the login form".
|
|
78
78
|
// Or nearly the same (nearTask): an agent that suggests a task again rewords
|
|
79
79
|
// it, and TASKS.md listed both.
|
|
80
|
-
|
|
80
|
+
// kept per text, like wordsOf's words: sameTask runs for every idea against every task
|
|
81
|
+
const keep = (f, max = 4000) => { const m = new Map(); return (s) => { s = String(s || ""); let v = m.get(s); if (v === undefined) { if (m.size >= max) m.clear(); v = f(s); m.set(s, v); } return v; }; };
|
|
82
|
+
const taskWords = keep((s) => s.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim());
|
|
81
83
|
function sameTask(a, b) {
|
|
82
84
|
const x = taskWords(a), y = taskWords(b); if (!x || !y) return false;
|
|
83
85
|
if (x === y) return true;
|
|
@@ -93,14 +95,20 @@ function sameTask(a, b) {
|
|
|
93
95
|
// "Watch WhatsApp…" are two tasks, however alike the rest. A name is a word
|
|
94
96
|
// with a capital that doesn't just start a sentence (Gmail, WhatsApp, WA).
|
|
95
97
|
const NEAR = 0.85, NEAR_WORDS = 12, LEAD_WORDS = 5;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// A word starts a sentence when only spacing and punctuation since the last word
|
|
99
|
+
// end a sentence: only that gap is read, not all the text before it, which made a
|
|
100
|
+
// 4000-character task cost millions of steps. Every 20s each project's ideas are
|
|
101
|
+
// checked against every task (agentQuestions), so the words of a text are kept
|
|
102
|
+
// (keep): 40 ideas against 393 tasks took 10s a tick, and the app stopped
|
|
103
|
+
// answering, its update never restarting it (2026-10-12).
|
|
104
|
+
const wordsOf = keep((s) => {
|
|
105
|
+
let end = 0;
|
|
98
106
|
return [...s.matchAll(/[\p{L}\p{N}]+/gu)].map((m) => {
|
|
99
|
-
const w = m[0],
|
|
100
|
-
const starts = !
|
|
107
|
+
const w = m[0], first = end === 0, gap = s.slice(end, m.index); end = m.index + w.length;
|
|
108
|
+
const starts = (first && !gap.trim()) || /[.!?]["'`)\]]*\s*$/.test(gap);
|
|
101
109
|
return { w: w.toLowerCase(), name: w.length > 1 && /\p{Lu}/u.test(w) && (!starts || /\p{Lu}/u.test(w.slice(1))) };
|
|
102
110
|
});
|
|
103
|
-
}
|
|
111
|
+
});
|
|
104
112
|
// The first sentence's words, when more follows it ("" when it's all one sentence).
|
|
105
113
|
function leadOf(s) { const m = String(s || "").match(/^(.*?[.!?])["'`)\]]*\s+\S/s); return m ? taskWords(m[1]) : ""; }
|
|
106
114
|
function nearTask(a, b) {
|
|
@@ -128,7 +136,7 @@ function nearTask(a, b) {
|
|
|
128
136
|
// "Map the whole page … (not for Gmail)".
|
|
129
137
|
const ASK = 0.6, ASK_WORDS = 8;
|
|
130
138
|
const STOP = new Set(("the and but for from with are was were been its this that these those then than once each his her him she they them their you your yourself can cant not does did have has had will would should could just also there here into onto out about before after when what which who whom how why all any some more most other only own same too very again").split(" "));
|
|
131
|
-
|
|
139
|
+
const askWords = keep((s) => new Set(wordsOf(s).map((x) => x.w).filter((w) => w.length > 2 && !STOP.has(w)).map((w) => w.replace(/(ing|ed|es|s)$/, ""))));
|
|
132
140
|
function sameAsk(a, b) {
|
|
133
141
|
const x = askWords(a), y = askWords(b); if (Math.min(x.size, y.size) < ASK_WORDS) return false;
|
|
134
142
|
const nx = new Set(wordsOf(a).filter((w) => w.name).map((w) => w.w)), ny = new Set(wordsOf(b).filter((w) => w.name).map((w) => w.w));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.63.
|
|
4
|
-
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice
|
|
3
|
+
"version": "0.63.4",
|
|
4
|
+
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice \u2014 Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"symbiot": "index.mjs"
|
package/server.mjs
CHANGED
|
@@ -248,6 +248,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
248
248
|
if (!SANDBOX) { refreshRevoked().catch(() => {}); setInterval(() => refreshRevoked().catch(() => {}), 6 * 3600 * 1000).unref(); } // cancelled Pro keys, about daily
|
|
249
249
|
{ const t0 = Date.now(); refreshRepos().then((l) => console.log(`Found ${l.length} project${l.length === 1 ? "" : "s"} in ${((Date.now() - t0) / 1000).toFixed(1)}s.`)); }
|
|
250
250
|
let UPDATING = null; // an update in flight: { target, attempt, retrying? }
|
|
251
|
+
const UPDATE_TRIES = 20;
|
|
251
252
|
let NEWEST_WIN = ""; // the Symbiot window opened last: older ones close themselves (one window, not a pile)
|
|
252
253
|
process.on("exit", () => closeAway()); // Away's windows go with the app (a quit, an update's restart)
|
|
253
254
|
const server = createServer(async (req, res) => {
|
|
@@ -515,7 +516,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
515
516
|
// a sandbox (symbiot app --fresh) never offers an update: it would replace your real install.
|
|
516
517
|
// A headless browser (an agent's screenshot of Home) never becomes the newest window:
|
|
517
518
|
// that closed the user's real one, again and again (2026-10-08, "symbiot keeps crashing").
|
|
518
|
-
if (u.pathname === "/api/ping") { if (u.searchParams.get("fresh") === "1" && !SANDBOX) await checkLatest(); const w = u.searchParams.get("w") || ""; if (/^[a-z0-9]{6,20}$/.test(w) && u.searchParams.get("new") === "1" && !headlessAgent(req.headers["user-agent"])) NEWEST_WIN = w; return json(res, { ...(NEWEST_WIN ? { window: NEWEST_WIN } : {}), version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION, newer: !SANDBOX && !DESKTOP && semverGt(LATEST_VERSION, VERSION), ...(DESKTOP ? { desktop: true } : {}), ...(UPDATING && UPDATING.retrying ? { retrying: UPDATING } : {}), ...(IN_TERMUX ? { termux: true } : {}), ...(SANDBOX ? { sandbox: true } : {}) }); }
|
|
519
|
+
if (u.pathname === "/api/ping") { if (u.searchParams.get("fresh") === "1" && !SANDBOX) await checkLatest(); const w = u.searchParams.get("w") || ""; if (/^[a-z0-9]{6,20}$/.test(w) && u.searchParams.get("new") === "1" && !headlessAgent(req.headers["user-agent"])) NEWEST_WIN = w; return json(res, { ...(NEWEST_WIN ? { window: NEWEST_WIN } : {}), version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION, newer: !SANDBOX && !DESKTOP && semverGt(LATEST_VERSION, VERSION), ...(DESKTOP ? { desktop: true } : {}), ...(UPDATING && UPDATING.retrying ? { retrying: { ...UPDATING, of: UPDATE_TRIES } } : {}), ...(IN_TERMUX ? { termux: true } : {}), ...(SANDBOX ? { sandbox: true } : {}) }); }
|
|
519
520
|
// What's new: after an update, since the version you last saw (until you click Got it);
|
|
520
521
|
// ?latest=1, what the update on offer brings, from its package on npm
|
|
521
522
|
if (u.pathname === "/api/whatsnew") {
|
|
@@ -552,12 +553,13 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
552
553
|
const { target, cmd } = updateCmd(LATEST_VERSION, VERSION);
|
|
553
554
|
const inst = process.env.SYMBIOT_UPDATE_CMD || cmd;
|
|
554
555
|
// npm lists a new version a little before its download is there (a 404 for the
|
|
555
|
-
// .tgz): then it tries again every 30s
|
|
556
|
-
// giving up and sending you to a terminal.
|
|
556
|
+
// .tgz): then it tries again every 30s for 10 minutes (UPDATE_TRIES), and says so,
|
|
557
|
+
// instead of giving up and sending you to a terminal. 5 tries (2.5 minutes) gave up
|
|
558
|
+
// on 0.63.3, still a 404 four minutes after it was published (2026-10-12).
|
|
557
559
|
const attempt = (n) => track("symbiot update", inst, homedir(), (code) => {
|
|
558
560
|
if (code !== 0) {
|
|
559
561
|
const tail = readRunLog(join(homedir(), ".symbiot", "agent.log")).slice(-3000);
|
|
560
|
-
if (n <
|
|
562
|
+
if (n < UPDATE_TRIES && /E404|ETARGET|notarget|No matching version|is not in this registry/i.test(tail)) { UPDATING = { target, retrying: true, attempt: n + 1, at: Date.now() }; setTimeout(() => attempt(n + 1), 30000).unref(); }
|
|
561
563
|
else UPDATING = null;
|
|
562
564
|
return;
|
|
563
565
|
}
|
package/ui.mjs
CHANGED
|
@@ -2801,7 +2801,7 @@ function heartbeat(fresh){
|
|
|
2801
2801
|
if(tried){
|
|
2802
2802
|
if(p.version!==tried){try{localStorage.removeItem('symbiot_update_tried');}catch(e){} } // advanced → success, clear
|
|
2803
2803
|
else if(p.retrying){ // npm hasn't finished publishing it: the app is trying again by itself
|
|
2804
|
-
b.className='updatebar show';b.innerHTML="Updating to "+esc(p.retrying.target)+": npm is still publishing it, so Symbiot tries again every 30 seconds (try "+p.retrying.attempt+" of
|
|
2804
|
+
b.className='updatebar show';b.innerHTML="Updating to "+esc(p.retrying.target)+": npm is still publishing it, so Symbiot tries again every 30 seconds (try "+p.retrying.attempt+" of "+(p.retrying.of||20)+"). Nothing to do.";return;
|
|
2805
2805
|
}
|
|
2806
2806
|
else if(p.latest&&p.newer){ // still on the same version → the install didn't advance
|
|
2807
2807
|
b.className='updatebar reconnect show';
|