symbiot 0.53.0 → 0.53.2
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 +12 -1
- package/README.md +23 -20
- package/agents.mjs +3 -3
- package/guard.mjs +2 -2
- package/handover.mjs +1 -1
- package/package.json +1 -1
- package/ui.mjs +80 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,20 @@
|
|
|
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.53.2
|
|
6
|
+
|
|
7
|
+
- **Replies you're waiting on sit on the stream.** An email your agent is waiting on a reply to is a ring on your inbox's current, where it was sent, with a thread to now; it fills when the reply is in. Point at it for who, what was asked, and Stop waiting. The header says how many you're waiting on.
|
|
8
|
+
- **Drafts to post, as a shelf.** Your drafted posts sit side by side as cards: the start of each, where it goes, and Approve, Edit or Skip; Read it all opens the rest.
|
|
9
|
+
- **A project's tasks, calmer.** How tasks work folds away; each task shows two lines (click for all of it), without its repo's name inside its own page; the review card is a quiet card with a live dot while its agent works.
|
|
10
|
+
- **Fixed:** the top of the work view drew the heading, the old summary and the status line over each other; Go sat off the bottom of the window. Go now sits beside Home.
|
|
11
|
+
|
|
12
|
+
## 0.53.1
|
|
13
|
+
|
|
14
|
+
- **Named for Symbiot.** Agents working on their own is **Symbiosis** (Settings → Your agent); the guard that stops what only you do is **the membrane**; your answer going back into the same conversation is **one mind**; agents deciding what can be undone themselves is **instinct**. Same behaviour, each with what it does spelled out next to it.
|
|
15
|
+
|
|
5
16
|
## 0.53.0
|
|
6
17
|
|
|
7
|
-
- **Agents work
|
|
18
|
+
- **Agents work on their own.** A Claude agent now just does the work: no permission prompts, no "You" for a folder or a command. Symbiot's guard, which Claude Code runs before every action, still stops the few things only you do: pushing to main, force-pushing, publishing, deleting outside its folder, sudo, piping a script from the internet into a shell, reading your keys, changing Symbiot's settings. A stopped agent is told why and asks. Settings → Your agent → **Agents work on their own** (on; off keeps allow lists).
|
|
8
19
|
- **One conversation that goes on.** When you answer an agent, the same conversation resumes, so it carries on knowing everything it knew, instead of a new agent rereading a brief.
|
|
9
20
|
- **Agents decide like a colleague.** They choose what can be undone themselves and say what they chose; they ask only about what can't be undone, costs money, goes out in your name or needs who you are, with their pick first.
|
|
10
21
|
|
package/README.md
CHANGED
|
@@ -439,26 +439,29 @@ editor presets only open a tab and exit, so Symbiot can't see the agent you run
|
|
|
439
439
|
in them. Send to repos stays disabled until its
|
|
440
440
|
handoffs have started, so a double click can't send twice.
|
|
441
441
|
|
|
442
|
-
###
|
|
443
|
-
|
|
444
|
-
Talk to Symbiot and its agents just do the work
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
442
|
+
### Symbiosis: agents work on their own
|
|
443
|
+
|
|
444
|
+
Talk to Symbiot and its agents just do the work. A Claude run skips permission checks
|
|
445
|
+
(`--dangerously-skip-permissions`); three things make that safe and useful:
|
|
446
|
+
|
|
447
|
+
- **The membrane** (`guard.mjs`) lets the work through and stops the few things only
|
|
448
|
+
you do. Claude Code runs it before every action, whatever the agent decided:
|
|
449
|
+
pushing to main or master, force-pushing, publishing a package, deleting outside its
|
|
450
|
+
folder (or `/`), sudo, piping a script from the internet into a shell, reading your
|
|
451
|
+
SSH keys or cloud credentials, and changing Symbiot's own settings. A stopped agent
|
|
452
|
+
is told why, and asks.
|
|
453
|
+
- **One mind.** When an agent asks you something, your answer goes back into the same
|
|
454
|
+
conversation (`--resume`), so it carries on knowing everything it knew. It starts
|
|
455
|
+
afresh after 8 runs or 2 days.
|
|
456
|
+
- **Instinct.** Its brief tells it to decide what can be undone itself (the approach,
|
|
457
|
+
names, which of two fixes) and say what it chose, and to ask only about what can't be
|
|
458
|
+
undone, costs money, goes out in your name or needs who you are, with options and its
|
|
459
|
+
pick first.
|
|
460
|
+
|
|
461
|
+
Settings → Your agent → **Symbiosis** is on by default. Off, a run keeps its allow
|
|
462
|
+
list, and an allow list it proposes is turned on by itself only when it stays inside
|
|
463
|
+
your work: the folders you gave Symbiot, not `~/.ssh` or `~/.config`, nothing that
|
|
464
|
+
could run anything, nothing that publishes or reaches another machine.
|
|
462
465
|
|
|
463
466
|
### Questions from your agent
|
|
464
467
|
|
package/agents.mjs
CHANGED
|
@@ -142,9 +142,9 @@ function connectorsLine(tmpl = handoffCmd(), file, linked) {
|
|
|
142
142
|
// the sites linked in Symbiot that aren't Claude connectors (so runs can't use them).
|
|
143
143
|
function connectorsInfo(file) { const conns = claudeConnectors("", file); return { claude: isClaudeCmd(handoffCmd()), list: conns.map(({ name, ready }) => ({ name, ready })), links: linkedConnectors(conns) }; }
|
|
144
144
|
const fillHandoff = (tmpl, repoPath, prompt = HANDOFF_PROMPT) => tmpl.replace(/\{dir\}/g, shSingle(repoPath)).replace(/\{prompt\}/g, escDq(prompt));
|
|
145
|
-
// ----
|
|
146
|
-
//
|
|
147
|
-
// conversation that goes on. Symbiot's Claude runs work
|
|
145
|
+
// ---- agents work on their own --------------------------------------------------------
|
|
146
|
+
// Talk to an agent and it just does the work: permission checks skipped, one
|
|
147
|
+
// conversation that goes on. Symbiot's Claude runs work that way (config.agentTrust:
|
|
148
148
|
// "full", the default; "ask" keeps the allow lists): --dangerously-skip-permissions, with
|
|
149
149
|
// guard.mjs as a PreToolUse hook that stops the few things only the owner does (push to
|
|
150
150
|
// main, publish, delete outside the folder, sudo, keys, Symbiot's settings), whatever the
|
package/guard.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The guard: what an agent working
|
|
2
|
+
// The guard: what an agent working on its own (permission checks skipped, so it just
|
|
3
3
|
// does the work) still can't do. Claude Code runs this before every tool call (a
|
|
4
4
|
// PreToolUse hook, from the settings Symbiot passes the run), and a call it blocks
|
|
5
5
|
// never happens, whatever the agent was told or decided. The list is short on purpose:
|
|
@@ -79,7 +79,7 @@ if (main) {
|
|
|
79
79
|
let raw = ""; try { raw = readFileSync(0, "utf8"); } catch {}
|
|
80
80
|
let ev = {}; try { ev = JSON.parse(raw); } catch {}
|
|
81
81
|
const r = judge(ev.tool_name, ev.tool_input || {}, { cwd: ev.cwd || process.cwd() });
|
|
82
|
-
if (r) { process.stderr.write("Blocked by Symbiot's
|
|
82
|
+
if (r) { process.stderr.write("Blocked by Symbiot's membrane: " + r.why + ". If it's needed, ask the user in .symbiot/QUESTIONS.md with options.\n"); process.exit(2); }
|
|
83
83
|
process.exit(0);
|
|
84
84
|
}
|
|
85
85
|
|
package/handover.mjs
CHANGED
|
@@ -24,7 +24,7 @@ function handoverRules(lanes = [], here = "") {
|
|
|
24
24
|
}
|
|
25
25
|
// The rule for options in QUESTIONS.md: the user gets only what no agent can do,
|
|
26
26
|
// and the option says which kind it is.
|
|
27
|
-
const ONLY_YOU = "- Only what no agent can do is the user's: their body (a phone in their hand, a cable, which network they're on), their identity or secrets (signing in, a 2FA code, a token from a provider's console) or a decision that's theirs (closing an account, spending money, sending something in their name). Start such an option with `👤 You (only you: <which>):`, e.g. `👤 You (only you: your Meta token): paste it into .env`. Anything an agent could do, yours or another lane's, is never a 👤 step: do it, or hand it over. An option where picking it is enough starts with `🤖 Agent:`. Tag actions only: an option that just reports what the user saw or decides (`it notified me`, `not tried yet`) gets no tag. A permission your run doesn't have (a command, a folder) is never a chore for the user. Write the rules, as narrow as the task needs, to `.symbiot/allowlist.proposed.json` (`{\"permissions\": {\"allow\": [...], \"deny\": [...], \"additionalDirectories\": [...]}}`), ask for it in QUESTIONS.md with the option `👤 You (only you: a permission): allow the list in .symbiot/allowlist.proposed.json`, and stop. If it stays inside the user's work (the folders they gave Symbiot, not ~/.ssh or ~/.config), asks for nothing wide, and doesn't publish or reach another machine, Symbiot turns it on by itself and runs you again: no question reaches the user. Their knowledge folders are already yours to read. Never ask them to copy files or run claude in a terminal for it. Decide what can be undone yourself, as a trusted colleague would: the approach, names, which of two fixes, how to lay files out; pick, do it, and say what you chose and why in your last message. Ask only about what can't be undone, costs money, goes out in the user's name, or needs who they are, and then give 2–3 options with your pick first, marked (recommended). Their answer comes back into this same conversation, so you'll remember everything when it does. Symbiot's guard stops the few things only the user does (pushing to main, publishing, deleting outside your folder, sudo, their keys); if it stops you, ask, don't work around it.";
|
|
27
|
+
const ONLY_YOU = "- Only what no agent can do is the user's: their body (a phone in their hand, a cable, which network they're on), their identity or secrets (signing in, a 2FA code, a token from a provider's console) or a decision that's theirs (closing an account, spending money, sending something in their name). Start such an option with `👤 You (only you: <which>):`, e.g. `👤 You (only you: your Meta token): paste it into .env`. Anything an agent could do, yours or another lane's, is never a 👤 step: do it, or hand it over. An option where picking it is enough starts with `🤖 Agent:`. Tag actions only: an option that just reports what the user saw or decides (`it notified me`, `not tried yet`) gets no tag. A permission your run doesn't have (a command, a folder) is never a chore for the user. Write the rules, as narrow as the task needs, to `.symbiot/allowlist.proposed.json` (`{\"permissions\": {\"allow\": [...], \"deny\": [...], \"additionalDirectories\": [...]}}`), ask for it in QUESTIONS.md with the option `👤 You (only you: a permission): allow the list in .symbiot/allowlist.proposed.json`, and stop. If it stays inside the user's work (the folders they gave Symbiot, not ~/.ssh or ~/.config), asks for nothing wide, and doesn't publish or reach another machine, Symbiot turns it on by itself and runs you again: no question reaches the user. Their knowledge folders are already yours to read. Never ask them to copy files or run claude in a terminal for it. Decide what can be undone yourself, as a trusted colleague would: the approach, names, which of two fixes, how to lay files out; pick, do it, and say what you chose and why in your last message. Ask only about what can't be undone, costs money, goes out in the user's name, or needs who they are, and then give 2–3 options with your pick first, marked (recommended). Their answer comes back into this same conversation, so you'll remember everything when it does. Symbiot's membrane (its guard) stops the few things only the user does (pushing to main, publishing, deleting outside your folder, sudo, their keys); if it stops you, ask, don't work around it.";
|
|
28
28
|
|
|
29
29
|
// What a run hands back to Symbiot itself (handback.mjs reads it): facts for
|
|
30
30
|
// memory, and emails that wait on a reply, which Symbiot then watches for; and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.2",
|
|
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": {
|
package/ui.mjs
CHANGED
|
@@ -122,6 +122,9 @@ body.lq-light #quit{border-color:rgba(20,30,45,.16);background:rgba(255,255,255,
|
|
|
122
122
|
.rcard .partly{margin:8px 0 4px;padding:8px 10px;border-left:3px solid var(--amber);border-radius:0 10px 10px 0;background:rgba(242,165,65,.08);color:var(--bone);font-size:13px;line-height:1.45}
|
|
123
123
|
.rcard .partly b{color:var(--amber)}
|
|
124
124
|
body.lq-pooled #lqtalk,body.lq-pooled #lqmore,body.lq-pooled #lqcore{display:none}
|
|
125
|
+
/* the work view says where things stand in its line under the heading, not in the core */
|
|
126
|
+
body.lq-work #lqcore{display:none}
|
|
127
|
+
body.lq-pooled #lqgo,body.lq-pooled #lqgo.on{display:none}
|
|
125
128
|
body.lq-talking .lqd{opacity:.55}
|
|
126
129
|
@media (prefers-reduced-motion: reduce){.lqd,#lqcore{transition:none}}
|
|
127
130
|
/* ---- the Symbiot look on every part: glass over the liquid, chrome, a silver edge ---- */
|
|
@@ -166,14 +169,14 @@ body.lq-pooled #lqgroups{display:none}
|
|
|
166
169
|
@media (prefers-reduced-motion: reduce){.lqorbit span{animation:none}.lqorbit span:nth-child(2){transform:rotate(120deg)}.lqorbit span:nth-child(3){transform:rotate(240deg)}}
|
|
167
170
|
#lqgo,#lqback,#lqsort{display:none}
|
|
168
171
|
/* Go is docked in the corner, small: it never sits between you and the spheres */
|
|
169
|
-
body.lq-work #lqgo.on{display:inline-flex;position:absolute;left:
|
|
172
|
+
body.lq-work #lqgo.on{display:inline-flex;position:absolute;left:112px;top:48px;z-index:4;align-items:center;gap:6px;min-height:36px;padding:0 14px;border-radius:999px;border:1px solid rgba(220,228,240,.3);background:rgba(4,5,7,.6);backdrop-filter:blur(8px);color:#EEF2F8;font:600 13px var(--sans);cursor:pointer}
|
|
170
173
|
body.lq-work #lqgo.on::before{content:'';width:7px;height:7px;border-radius:50%;background:#3DDC97}
|
|
171
174
|
body.lq-work #lqgo.on:hover{border-color:rgba(220,228,240,.6)}
|
|
172
175
|
body.lq-work #lqgo.on:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
173
176
|
body.lq-light.lq-work #lqgo.on{background:rgba(255,255,255,.8);color:#10151C;border-color:rgba(20,30,45,.2)}
|
|
174
177
|
body.lq-light.lq-work #lqgo.on::before{background:#0B7A55}
|
|
175
178
|
body.lq-touch.lq-work #lqgo.on{min-height:44px}
|
|
176
|
-
|
|
179
|
+
|
|
177
180
|
/* the lanes' order, in the Projects scene: what needs you first, the most recent, or by name */
|
|
178
181
|
body.lq-work.lq-byproj #lqsort{display:inline-flex;position:absolute;left:112px;top:52px;z-index:4;gap:2px;padding:3px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1)}
|
|
179
182
|
#lqsort button{min-height:36px;padding:0 12px;border:0;border-radius:999px;background:transparent;color:#8A919B;font:500 12.5px var(--sans);cursor:pointer}
|
|
@@ -725,6 +728,53 @@ body.lq-light .rlia button:hover,body.lq-light .rlf button:hover{color:#151A21;b
|
|
|
725
728
|
.rlia button:focus-visible,.rlf button:focus-visible{outline:2px solid currentColor;outline-offset:1px}
|
|
726
729
|
.rldone{font-size:12px;color:#8A919B}
|
|
727
730
|
.rlf{display:flex;justify-content:space-between;margin:2px -7px -4px}
|
|
731
|
+
|
|
732
|
+
/* waiting on replies: a ring on the inbox's current, a thread to now */
|
|
733
|
+
.bwait{position:absolute;left:0;top:0;width:16px;height:16px;margin:-8px 0 0 -8px;padding:0;border-radius:50%;border:2px solid #F2A541;background:rgba(8,9,11,.6);cursor:pointer;z-index:2;box-shadow:0 0 12px rgba(242,165,65,.35)}
|
|
734
|
+
.bwait.in{background:#F2A541}
|
|
735
|
+
.bwait.early{border-style:dashed}
|
|
736
|
+
.bwait:focus-visible{outline:2px solid var(--bone);outline-offset:3px}
|
|
737
|
+
body.lq-light .bwait{background:rgba(255,255,255,.8);border-color:#B86A0C}body.lq-light .bwait.in{background:#B86A0C}
|
|
738
|
+
.bwthread{position:absolute;height:0;border-top:1.5px dashed rgba(242,165,65,.45);pointer-events:none}
|
|
739
|
+
body.lq-light .bwthread{border-top-color:rgba(184,106,12,.45)}
|
|
740
|
+
.btip .bwask{margin-top:6px;font-size:12.5px;color:var(--faint)}
|
|
741
|
+
.btip .bwnote{font-size:11.5px;color:var(--faint);margin-left:4px}
|
|
742
|
+
/* drafts to post: a shelf of cards */
|
|
743
|
+
.pshead{display:flex;flex-direction:column;gap:2px;margin:24px 0 10px}
|
|
744
|
+
.pshead span{font:600 15px var(--sans);color:var(--bone)}
|
|
745
|
+
.pshead small{font-size:12.5px;color:var(--faint)}
|
|
746
|
+
.pshelf{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px;align-items:start}
|
|
747
|
+
body.lq-liquid .pshelf .pcard{position:relative;display:flex;flex-direction:column;gap:10px;margin:0;padding:16px 18px;border-radius:20px;border:1px solid rgba(255,255,255,.08);border-left:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.035)}
|
|
748
|
+
body.lq-liquid.lq-light .pshelf .pcard{background:rgba(255,255,255,.75);border-color:rgba(21,26,33,.08)}
|
|
749
|
+
body.lq-liquid .pshelf .pcard.editing{grid-column:1/-1}
|
|
750
|
+
.pshelf .pcard .rhead{display:flex;flex-direction:column;gap:1px}
|
|
751
|
+
.pshelf .pcard .rhead b{font:600 15px var(--sans);color:var(--bone)}
|
|
752
|
+
.pshelf .pcard .rhead .muted{font-size:12px}
|
|
753
|
+
.pshelf .ptext{white-space:pre-wrap;font-size:14px;line-height:1.55;color:var(--text);max-height:9.5em;overflow:hidden;-webkit-mask-image:linear-gradient(to bottom,#000 60%,transparent);mask-image:linear-gradient(to bottom,#000 60%,transparent)}
|
|
754
|
+
.pshelf .pcard.open .ptext{max-height:none;-webkit-mask-image:none;mask-image:none}
|
|
755
|
+
.pshelf .pmore{all:unset;cursor:pointer;align-self:flex-start;font:500 12.5px var(--sans);color:var(--faint);text-decoration:underline;text-underline-offset:3px;text-decoration-color:rgba(127,127,127,.4)}
|
|
756
|
+
.pshelf .pmore:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
757
|
+
.pshelf .pedit{min-height:260px;font:14px/1.55 var(--sans)}
|
|
758
|
+
.pshelf .pcard .row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:2px}
|
|
759
|
+
.pshelf .psrc summary{font-size:12px;color:var(--faint);cursor:pointer}
|
|
760
|
+
|
|
761
|
+
/* a project's tasks: calm cards, two lines a task (click for all), no repo name inside its own page */
|
|
762
|
+
.tnhow{margin:2px 0 8px}
|
|
763
|
+
.tnhow > summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:7px;font:500 12.5px var(--sans);color:var(--faint)}
|
|
764
|
+
.tnhow > summary::-webkit-details-marker{display:none}
|
|
765
|
+
.tnhow > summary::before{content:'?';display:inline-grid;place-items:center;width:16px;height:16px;border-radius:50%;border:1px solid currentColor;font-size:10.5px;font-weight:600}
|
|
766
|
+
.tnhow .note{margin-top:6px;max-width:80ch}
|
|
767
|
+
body.lq-liquid #panel-tasks .task .t{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;cursor:pointer}
|
|
768
|
+
body.lq-liquid #panel-tasks .task.open .t{display:block;-webkit-line-clamp:unset;overflow:visible}
|
|
769
|
+
#tasklist.onerepo .task .rp,.rcard .task .rp{display:none}
|
|
770
|
+
body.lq-liquid .rcard{border:1px solid rgba(242,165,65,.3);border-radius:20px;padding:16px 18px 14px;background:rgba(255,255,255,.035);box-shadow:none;margin-top:10px}
|
|
771
|
+
body.lq-liquid.lq-light .rcard{background:rgba(255,255,255,.75);border-color:rgba(154,82,0,.25)}
|
|
772
|
+
body.lq-liquid .rcard .rhead b{font:600 16px var(--sans);letter-spacing:-.01em}
|
|
773
|
+
body.lq-liquid .rcard .task{margin-top:6px;padding:9px 12px;border-radius:12px}
|
|
774
|
+
.rwork{display:flex;align-items:center;gap:9px;margin:10px 0 2px;font-size:13px;color:var(--text)}
|
|
775
|
+
.rwork i{flex:none;width:9px;height:9px;border-radius:50%;background:#F2A541;box-shadow:0 0 0 0 rgba(242,165,65,.5);animation:rwork 1.8s ease-out infinite}
|
|
776
|
+
@keyframes rwork{0%{box-shadow:0 0 0 0 rgba(242,165,65,.5)}100%{box-shadow:0 0 0 9px rgba(242,165,65,0)}}
|
|
777
|
+
@media (prefers-reduced-motion: reduce){.rwork i{animation:none}}
|
|
728
778
|
/* the Dashboard as a stream: time runs left to right, a current per feed, now on the right */
|
|
729
779
|
.bstream{position:relative;margin-top:10px;border-radius:18px;overflow:hidden;isolation:isolate;background:#08090B;box-shadow:0 0 0 1px rgba(255,255,255,.08)}
|
|
730
780
|
body.lq-light .bstream{background:#EBEEF0;box-shadow:0 0 0 1px rgba(21,26,33,.08)}
|
|
@@ -862,7 +912,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
862
912
|
</section>
|
|
863
913
|
<section id="panel-tasks" class="hidden">
|
|
864
914
|
<div class="row"><input id="newtask" placeholder="Add a task..." style="flex:1"><select id="newtaskrepo" title="Which repo this task is for (needed to send it to an agent)" style="flex:0 0 auto;max-width:180px"><option value="">repo…</option></select><button class="act" id="addtask">Add</button><button class="ghost" id="pushtasks" title="Write .symbiot/TASKS.md into each repo for your coding agent">Send to repos</button></div>
|
|
865
|
-
<div class="note muted"
|
|
915
|
+
<details class="tnhow"><summary>How tasks work</summary><div class="note muted">To give tasks to your agent, use <b>Send to repos</b> — filter by tag below to choose which. As the agent finishes each one it lands in <b>Awaiting your review</b>: <b>Approve</b> commits it on a branch and opens a PR, <b>↶</b> sends it back. Ticking a task yourself marks it done (it auto-archives). Click <b><i class=ic-chat></i></b> on a task to ask questions about it.</div></details>
|
|
866
916
|
<div id="needsbox"></div>
|
|
867
917
|
<div id="pushout"></div>
|
|
868
918
|
<div id="reviewout"></div>
|
|
@@ -929,7 +979,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
929
979
|
<h3 class="ssh">Hand off to your agent when you "Send to repos"</h3>
|
|
930
980
|
<input id="agentcmd" type="text" placeholder="e.g. aider --message "{prompt}" --yes · code {dir} · leave blank to just write the file">
|
|
931
981
|
<div class="note muted">Runs in each repo after tasks are written. Use <b>{dir}</b> = repo path, <b>{prompt}</b> = the task instruction. Works with any agent or editor — it's your command.</div>
|
|
932
|
-
<label class="check swl" style="margin-top:12px"><input type="checkbox" class="sw" id="agenttrust" checked> <span>
|
|
982
|
+
<label class="check swl" style="margin-top:12px"><input type="checkbox" class="sw" id="agenttrust" checked> <span><b>Symbiosis</b> · agents work on their own. They do the work without stopping to ask permission. <b>The membrane</b> still stops pushing to main, publishing, deleting outside their folder, sudo, reading your keys and changing Symbiot's settings. Off: they ask before anything outside their allow list.</span></label>
|
|
933
983
|
<div id="agentpresets" style="margin-top:8px"></div>
|
|
934
984
|
<div class="note muted" id="agentconnectors"></div>
|
|
935
985
|
<div id="grantbox" style="margin-top:10px">
|
|
@@ -1261,7 +1311,7 @@ fb+="<button class='fchip' id='archtoggle' style='margin-left:auto'><i class=ic-
|
|
|
1261
1311
|
box.innerHTML=fb;
|
|
1262
1312
|
box.querySelectorAll('.fchip[data-dim]').forEach(function(c){c.addEventListener('click',function(){TFILTER[c.getAttribute('data-dim')]=c.getAttribute('data-val');renderTasks();});});
|
|
1263
1313
|
document.getElementById('archtoggle').addEventListener('click',loadArchived);}
|
|
1264
|
-
function renderTasks(){var el=document.getElementById('tasklist');TARCH=false;renderFilter();renderNeeds();lqTitle();
|
|
1314
|
+
function renderTasks(){var el=document.getElementById('tasklist');TARCH=false;if(el&&el.classList)el.classList.toggle('onerepo',!!TFILTER.repo);renderFilter();renderNeeds();lqTitle();
|
|
1265
1315
|
var list=ALLTASKS.filter(function(t){return !t.review&&(!TFILTER.type||(t.type||'Features & other')===TFILTER.type)&&(!TFILTER.repo||t.repo===TFILTER.repo);});
|
|
1266
1316
|
if(!list.length){el.innerHTML="<div class='muted' style='margin-top:12px'>"+(ALLTASKS.length?"Nothing matches this filter.":"No tasks yet. Tick ideas in a repo review, or add one above.")+"</div>";return;}
|
|
1267
1317
|
var groups={};list.forEach(function(t){var ty=t.type||'Features & other';(groups[ty]=groups[ty]||[]).push(t);});
|
|
@@ -1291,7 +1341,7 @@ else if(r.untasked)h+="<div class='muted' style='margin:6px 0'>Uncommitted chang
|
|
|
1291
1341
|
// The repo's open tasks: tick the ones these changes finished, and they're approved with them instead of going out again on the next send.
|
|
1292
1342
|
if(r.untasked&&r.open&&r.open.length){var said=r.open.some(function(o){return o.finished;});h+="<div class='muted' style='margin:6px 0'>Did these changes finish any of "+esc(r.repo)+"'s open tasks? Tick them and they're approved with the changes, so they don't come back as new tasks"+(said?". Ticked already: the run's summary says it finished them.":".")+"</div>";
|
|
1293
1343
|
r.open.forEach(function(o){h+="<label class='idea'><input type='checkbox' class='tickopen' data-id='"+esc(o.id)+"'"+(o.finished?" checked":"")+"><span>"+esc(o.text)+(o.finished?" <span class='tag'>run says done</span>":"")+"</span></label>";});}
|
|
1294
|
-
if(r.running)h+="<div class='
|
|
1344
|
+
if(r.running)h+="<div class='rwork'><i aria-hidden='true'></i>Agent still working: its changes may be half done. Approve unlocks when it finishes.</div>";
|
|
1295
1345
|
// A repo that publishes on merge (ur.npm) is measured from the version npm has,
|
|
1296
1346
|
// and a bump publishes it once merged; otherwise from the last v* tag, tagged by hand.
|
|
1297
1347
|
var ur=r.unreleased;if(ur&&ur.npm)h+="<div class='muted' style='margin:6px 0'><span class='err'>Unreleased:</span> "+(ur.pending?"<code>"+esc(ur.base)+"</code> is at <b>"+esc(ur.pending)+"</b>, which isn't on npm yet. This repo publishes when a version bump merges, so it should appear shortly; if it doesn't, check the publish workflow's run on GitHub.":"<code>"+esc(ur.base)+"</code> has "+ur.ahead+" commit"+(ur.ahead>1?"s":"")+" merged since <b>"+esc(ur.since)+"</b> went to npm. This repo publishes on merge, but only when the version changes, so they wait for the next bump. "+(ur.bump?"These changes bump it to <b>"+esc(ur.bump)+"</b>: it publishes when this PR merges.":r.bumpOffer&&r.files.length?"Approve can bump it in this PR (below), so it publishes when the PR merges.":"Bump the version in a PR to publish them."))+"</div>";
|
|
@@ -1793,18 +1843,19 @@ function renderPosts(){var p=POSTS,el=$('boardposts');if(!el||!p)return;var L=p.
|
|
|
1793
1843
|
// it can't draft yet (no AI, or neither LinkedIn linked nor examples in voice.md): one line, not a block a new user meets first
|
|
1794
1844
|
if(!p.posts.length&&p.canDraft===false){var need=[];if(!p.connected)need.push('an AI is connected (Settings)');if(!p.linkedin&&!p.voice.count)need.push('LinkedIn is linked (below), or <code>'+esc(p.voice.file)+'</code> has a few of your posts');
|
|
1795
1845
|
el.innerHTML="<div class='muted' style='margin-top:12px;font-size:13px'>Posts: Symbiot drafts your week's LinkedIn posts here once "+need.join(', and ')+".</div>";return;}
|
|
1796
|
-
if(p.posts.length){h+="<div class='
|
|
1846
|
+
if(p.posts.length){h+="<div class='pshead'><span>Drafts to post</span><small>"+p.posts.length+" waiting on you · Approve copies one and opens LinkedIn, where you post it yourself</small></div><div class='pshelf'>";
|
|
1797
1847
|
h+=p.posts.map(function(x){var ed=Object.prototype.hasOwnProperty.call(PEDIT,x.id);
|
|
1798
|
-
return "<div class='rcard pcard' data-id='"+escQ(x.id)+"'><div class='rhead'><b>"+esc(L[x.kind]||x.kind)+"</b><span class='muted'>LinkedIn · drafted "+agoTxt(x.drafted)+(x.edited?" · edited":"")+"</span></div>"
|
|
1799
|
-
+(ed?"<textarea class='pedit' maxlength='3000'>"+esc(PEDIT[x.id])+"</textarea>":"<div class='ptext'>"+esc(x.text)+"</div>")
|
|
1848
|
+
return "<div class='rcard pcard"+(ed?" editing":"")+"' data-id='"+escQ(x.id)+"'><div class='rhead'><b>"+esc(L[x.kind]||x.kind)+"</b><span class='muted'>LinkedIn · drafted "+agoTxt(x.drafted)+(x.edited?" · edited":"")+"</span></div>"
|
|
1849
|
+
+(ed?"<textarea class='pedit' maxlength='3000'>"+esc(PEDIT[x.id])+"</textarea>":"<div class='ptext'>"+esc(x.text)+"</div><button type='button' class='pmore' aria-expanded='false'>Read it all</button>")
|
|
1800
1850
|
+(x.sources&&x.sources.length?"<details class='psrc'><summary>From git: "+x.sources.length+" thing"+(x.sources.length===1?"":"s")+" it says</summary>"+x.sources.map(function(s){return "<div>"+esc(s)+"</div>";}).join('')+"</details>":"")
|
|
1801
1851
|
+"<div class='row'>"+(ed?"<button class='act psave'>Save</button><button class='ghost pcancel'>Cancel</button><span class='pchars'>"+String(PEDIT[x.id]).length+" / 3000</span>"
|
|
1802
|
-
:"<button class='act papprove' title='copies it to your clipboard and opens LinkedIn's share box, where you paste it and post it yourself. Symbiot never posts it, and doesn't schedule it'>Approve</button><button class='ghost pedit-btn' title='change its words here'>Edit</button><button class='ghost pskip' title='drop this draft (it stays in the log)'>Skip</button>")+"</div></div>";}).join('');}
|
|
1852
|
+
:"<button class='act papprove' title='copies it to your clipboard and opens LinkedIn's share box, where you paste it and post it yourself. Symbiot never posts it, and doesn't schedule it'>Approve</button><button class='ghost pedit-btn' title='change its words here'>Edit</button><button class='ghost pskip' title='drop this draft (it stays in the log)'>Skip</button>")+"</div></div>";}).join('')+"</div>";}
|
|
1803
1853
|
else h+="<div class='row' style='margin-top:10px'><span class='muted' style='flex:1'>"+(p.voice.count?"Posts: draft this week's 3 LinkedIn posts from your git, in your voice ("+p.voice.count+" example"+(p.voice.count===1?"":"s")+"). Nothing is posted until you approve one, and then you post it yourself."
|
|
1804
1854
|
:"Posts: Symbiot drafts LinkedIn posts from your week's git in your own voice, once it has examples of your posts. Link LinkedIn (below) and click <b>Fill from LinkedIn</b>, or paste 5–10 of your posts into <code>"+esc(p.voice.file)+"</code>, a line of --- between each.")+"</span>"
|
|
1805
1855
|
+"<button class='ghost pvoice' title='reads your recent posts on LinkedIn in Symbiot's signed-in browser into voice.md, which you can read over'>Fill from LinkedIn</button>"
|
|
1806
1856
|
+"<button class='ghost pdraft'"+(p.voice.count?" title='your AI drafts 3 posts from your last 7 days of commits, release tags and changelog; each must cite what git shows'":" disabled title='add example posts first'")+">Draft this week's posts</button></div>";
|
|
1807
1857
|
el.innerHTML=h+"<div id='postsmsg'>"+PMSG+"</div>";wireShare();
|
|
1858
|
+
el.querySelectorAll('.pmore').forEach(function(b){b.addEventListener('click',function(){var c=b.closest('.pcard'),on=c.classList.toggle('open');b.setAttribute('aria-expanded',on?'true':'false');b.textContent=on?'Show less':'Read it all';});});
|
|
1808
1859
|
function post(btn){var id=btn.closest('.pcard').getAttribute('data-id');return p.posts.filter(function(x){return x.id===id;})[0];}
|
|
1809
1860
|
el.querySelectorAll('.papprove').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn);if(!x)return;btn.disabled=true;postsMsg('','');
|
|
1810
1861
|
api('/api/posts/approve',{id:x.id}).then(function(r){if(!r||r.error){btn.disabled=false;postsMsg('err',esc((r&&r.error)||'failed'));return;}
|
|
@@ -1838,8 +1889,14 @@ function boardMsg(cls,html){$('boardmsg').innerHTML="<div class='note "+cls+"'>"
|
|
|
1838
1889
|
|
|
1839
1890
|
// Replies an agent's email waits on (handback.mjs): Symbiot watches your inbox for them, and hands each on when it's in
|
|
1840
1891
|
var AWAITWORD={waiting:'waiting',replied:'replied',error:"replied, but the next step didn't start"};
|
|
1841
|
-
|
|
1842
|
-
|
|
1892
|
+
var AWAITS=[];
|
|
1893
|
+
// An email an agent waits on a reply to sits on your inbox's current as a ring, where it was
|
|
1894
|
+
// sent, with a thread to now: hollow while it waits, filled once the reply is in. Only with
|
|
1895
|
+
// no inbox current to put it on does it fall back to a list here.
|
|
1896
|
+
function bsWaitWord(){var n=(AWAITS||[]).filter(function(w){return w.status==='waiting';}).length;return n?' · waiting on '+n+(n===1?' reply':' replies'):'';}
|
|
1897
|
+
function bsMailLane(){var cs=(BS.b&&BS.b.cards)||[];for(var i=0;i<cs.length;i++)if(cs[i].source==='mail')return i;return -1;}
|
|
1898
|
+
function loadAwaiting(){api('/api/awaiting').then(function(d){var el=$('boardawait');if(!el||!d)return;var ws=d.waits||[];AWAITS=ws;if(BS.b)bsBuild(BS.b);
|
|
1899
|
+
if(!ws.length||bsMailLane()>=0){el.innerHTML='';return;}var open=ws.filter(function(w){return w.status==='waiting';}).length;
|
|
1843
1900
|
el.innerHTML="<div class='tgroup' style='margin-top:14px'>Waiting on replies <span class='tcount'>"+open+"</span></div><div class='muted' style='font-size:12px;margin-bottom:6px'>Emails your agents sent that need an answer. Symbiot looks for each reply in your inbox itself, and when one is in, the agent that does the next step starts on it. You don't have to say they replied.</div>"+
|
|
1844
1901
|
ws.map(function(w){var who=(w.to||[]).join(', ')||'?',st=w.status==='waiting'?'waiting since '+agoTxt(w.sent):(AWAITWORD[w.status]||w.status)+(w.reply?' '+agoTxt(w.reply.ts):'')+(w.handed?' → '+(w.handed==='ops'?'an agent':esc(w.handed)+''s agent')+' has the next step':'');
|
|
1845
1902
|
return "<div class='task'><span class='t'><b>"+esc(who)+"</b>: "+esc(w.subject)+(w.asked?"<div class='muted' style='font-size:12px'>asked for: "+esc(w.asked)+"</div>":"")+(w.error?"<div class='note err'>"+esc(w.error)+"</div>":"")+"</span><span class='rp'"+(w.status==='waiting'?" style='color:var(--amber)'":"")+">"+st+"</span>"+(w.status==='waiting'?"<button class='ghost astop' data-id='"+esc(w.id)+"' title='stop looking for this reply' style='padding:3px 9px;font-size:12px'>Stop waiting</button>":"")+"</div>";}).join('');
|
|
@@ -1887,11 +1944,17 @@ cs.forEach(function(c,i){(c.items||[]).forEach(function(n,j){var age=Math.max(0,
|
|
|
1887
1944
|
BS.beads.push({key:key,i:i,j:j,n:n,need:!!n.need&&!!(n.mail||n.chat),tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:n.need&&(n.mail||n.chat)?13:n.read?6:9,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});
|
|
1888
1945
|
(c.past||[]).forEach(function(n,j){var age=Math.max(0,now-(n.ts||now));if(age>H)return;var key=c.id+':p:'+(n.id||j),o=old[key];BS.beads.push({key:key,i:i,j:-1-j,n:n,past:true,need:false,tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:4.5,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});});
|
|
1889
1946
|
html+=BS.beads.map(function(x,k){return "<button type='button' class='bbead' data-k='"+k+"' aria-label='"+esc(x.n.text||'')+"'></button>";}).join('');
|
|
1947
|
+
var ml=bsMailLane();BS.waits=[];if(ml>=0)(AWAITS||[]).forEach(function(w,k){if(w.status!=='waiting'&&!(w.reply&&now-w.reply.ts<H))return;var sent=w.sent||now,age=Math.max(0,now-sent),x=g.x0+(1-Math.min(age,H)/H)*(g.x1-g.x0),y=laneY(ml)+(k%2?14:-14);BS.waits.push({w:w,x:x,y:y});
|
|
1948
|
+
html+=(w.status==='waiting'?"<div class='bwthread' style='left:"+Math.round(x)+"px;width:"+Math.max(0,Math.round(g.x1-x))+"px;top:"+Math.round(y)+"px'></div>":"")+"<button type='button' class='bwait"+(w.status==='waiting'?'':' in')+(age>H?' early':'')+"' data-w='"+(BS.waits.length-1)+"' style='left:"+Math.round(x)+"px;top:"+Math.round(y)+"px' aria-label='"+escQ((w.status==='waiting'?'waiting on a reply from ':'reply in from ')+(w.to||[]).join(', '))+"'></button>";});
|
|
1890
1949
|
html+="<div class='btip' role='dialog' aria-live='polite'></div>";ov.innerHTML=html;
|
|
1950
|
+
ov.querySelectorAll('.bwait').forEach(function(b){var i=+b.getAttribute('data-w');b.addEventListener('pointerenter',function(){bsShowWait(i);});b.addEventListener('focus',function(){bsShowWait(i);});b.addEventListener('click',function(){bsShowWait(i);});});
|
|
1891
1951
|
BS.btns=[];ov.querySelectorAll('.bbead').forEach(function(bt){BS.btns.push(bt);var k=+bt.getAttribute('data-k');bt.addEventListener('pointerenter',function(){bsShow(k);});bt.addEventListener('focus',function(){bsShow(k);});bt.addEventListener('click',function(){bsShow(k);});});
|
|
1892
1952
|
if(el.addEventListener&&!BS.wired){BS.wired=true;el.addEventListener('pointerleave',function(){bsShow(null);});el.addEventListener('keydown',function(e){if(e&&e.key==='Escape')bsShow(null);});}
|
|
1893
1953
|
BS.hot=null;bsStart();}
|
|
1894
|
-
function
|
|
1954
|
+
function bsShowWait(i){var tip=document.querySelector('#bsov .btip'),x=BS.waits&&BS.waits[i];if(!tip||!x)return;BS.hot=null;BS.hotWait=i;var w=x.w,who=(w.to||[]).join(', ')||'?';
|
|
1955
|
+
tip.innerHTML="<div class='bcard'><div class='bi'><div class='btf'>"+(w.status==='waiting'?'Waiting on a reply':'Reply in')+" <span>· "+esc(who)+" · "+(w.status==='waiting'?'sent '+agoTxt(w.sent):agoTxt(w.reply&&w.reply.ts))+"</span></div><div class='btx'>"+esc(w.subject||'')+(w.asked?"<div class='bwask'>Asked for: "+esc(w.asked)+"</div>":"")+(w.handed&&w.status!=='waiting'?"<div class='bwask'>"+(w.handed==='ops'?'An agent':esc(w.handed)+''s agent')+" has the next step.</div>":"")+(w.error?"<div class='bwask err'>"+esc(w.error)+"</div>":"")+"</div><div class='bta'>"+(w.status==='waiting'?"<button class='quiet bwstop'>Stop waiting</button><span class='bwnote'>Symbiot watches your inbox for it, and hands it on when it's in.</span>":"")+"</div></div></div>";tip.className='btip on';
|
|
1956
|
+
var st=tip.querySelector('.bwstop');if(st)st.addEventListener('click',function(){st.disabled=true;api('/api/awaiting/stop',{id:w.id}).then(function(r){if(!r||r.error){st.disabled=false;boardMsg('err',esc((r&&r.error)||'failed'));return;}bsShow(null);loadAwaiting();});});}
|
|
1957
|
+
function bsShow(k){var tip=document.querySelector('#bsov .btip');if(!tip)return;BS.hot=k;BS.hotWait=null;if(k==null||!BS.beads[k]){tip.className='btip';return;}
|
|
1895
1958
|
var x=BS.beads[k],c=BS.b.cards[x.i],n=x.n;if(x.past){tip.innerHTML="<div class='bcard'><div class='bi'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>· "+esc(bsName(c).n)+" · "+agoTxt(n.ts)+" · seen</span></div><div class='btx' style='margin-bottom:0'>"+esc(n.text||'')+"</div></div></div>";tip.className='btip on';return;}
|
|
1896
1959
|
tip.innerHTML="<div class='bcard' data-i='"+x.i+"'><div class='bi' data-j='"+x.j+"'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>· "+esc(bsName(c).n)+" · "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" · "+CHATFROM[n.from]:"")+(n.read?" · read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted · again":"Draft a reply")+"</button>":"")+(n.href?"<button class='bopen"+(n.mail||n.chat?" quiet":"")+"'>Open</button>":"")+(n.chat&&n.drafted?"<button class='bopenwa quiet'>Open in WhatsApp</button>":"")+"</div></div></div>";
|
|
1897
1960
|
tip.className='btip on';if(BWIRE)BWIRE(tip);}
|
|
@@ -1900,7 +1963,8 @@ var loop=function(){var p=$('panel-board'),el=$('bstream');if(!p||!el||!p.offset
|
|
|
1900
1963
|
function bsFrame(){var el=$('bstream'),cv=$('bsc');if(!el||!cv)return;if(!BS.G)BS.G=lqGL(cv,40);var g=bsGeom();if(BS.b&&BS.gw!==g.w){bsBuild(BS.b);g=bsGeom();}var t=LQ.t||0,still=LQ.theme&&LQ.theme.still;
|
|
1901
1964
|
BS.beads.forEach(function(x,k){var hot=BS.hot===k;x.x+=(x.tx-x.x)*0.12;x.y+=((x.ty+(still?0:Math.sin(t*0.8+k*1.3)*2))-x.y)*0.12;x.r+=((x.tr*(hot?1.35:1))-x.r)*0.15;
|
|
1902
1965
|
var bt=BS.btns&&BS.btns[k];if(bt&&bt.style){var R=Math.max(14,x.r+6);bt.style.width=bt.style.height=(2*R)+'px';bt.style.transform='translate('+Math.round(x.x-R)+'px,'+Math.round(x.y-R)+'px)';}});
|
|
1903
|
-
var tip=document.querySelector('#bsov .btip');if(tip&&BS.
|
|
1966
|
+
var tip=document.querySelector('#bsov .btip');if(tip&&BS.hotWait!=null&&BS.waits&&BS.waits[BS.hotWait]){var wx=BS.waits[BS.hotWait],tw2=tip.offsetWidth||300,th2=tip.offsetHeight||140,tx2=Math.max(12,Math.min(g.w-tw2-12,wx.x-tw2/2)),ty2=wx.y-th2-16;if(ty2<8)ty2=wx.y+16;tip.style.transform='translate('+Math.round(tx2)+'px,'+Math.round(ty2)+'px)';}
|
|
1967
|
+
else if(tip&&BS.hot!=null&&BS.beads[BS.hot]){var x=BS.beads[BS.hot],tw=tip.offsetWidth||300,th=tip.offsetHeight||120,tx=Math.max(12,Math.min(g.w-tw-12,x.x-tw/2)),ty=x.y-x.r-th-14;if(ty<8)ty=x.y+x.r+14;tip.style.transform='translate('+Math.round(tx)+'px,'+Math.round(ty)+'px)';}
|
|
1904
1968
|
var G=BS.G;if(!G)return;var gl=G.gl,d=Math.min(window.devicePixelRatio||1,3),W=Math.max(1,Math.floor(g.w*d)),H=Math.max(1,Math.floor(g.h*d));if(cv.width!==W||cv.height!==H){cv.width=W;cv.height=H;gl.viewport(0,0,W,H);}
|
|
1905
1969
|
var o=G.out;for(var i=0;i<o.length;i++)o[i]=0;var k=0,put=function(px,py,r,a){if(k>=40)return;o[k*4]=px/g.w;o[k*4+1]=py/g.h;o[k*4+2]=Math.max(0,r);o[k*4+3]=a;k++;};
|
|
1906
1970
|
(BS.b.cards||[]).forEach(function(c,i){put(g.x1,60+(g.h-110)*(i+0.5)/Math.max(1,g.n),c.count?9+2*Math.min(c.count,6):5,0);});
|
|
@@ -1913,7 +1977,7 @@ if(!cs.length){$('boardsum').textContent='Everything you watch, side by side.';
|
|
|
1913
1977
|
el.innerHTML="<div style='grid-column:1/-1'><div class='note muted' style='margin-top:0'>Nothing watched yet. Map your inbox, a chat (web.whatsapp.com) or any page under <b>Screens</b> on the Map tab and click <b>Watch</b> on it, or click <b>Watch GitHub</b> there. Each one gets a card here with what's new on it.</div><div class='row' style='margin-top:8px'><button class='ghost' id='boardgo'>Go to Screens</button></div></div>";
|
|
1914
1978
|
$('boardgo').addEventListener('click',function(){setTab('map');var sbx=$('screensbox');if(sbx)sbx.open=true;var w=$('watchbox');if(w&&w.scrollIntoView)w.scrollIntoView({block:'center'});});return;}
|
|
1915
1979
|
var when=b.hours===24?'since yesterday':b.hours===72?'in the last 3 days':'in the last 7 days';
|
|
1916
|
-
var lastRead=0;cs.forEach(function(c){if(c.checked>lastRead)lastRead=c.checked;});var sub=$('boardsub');if(sub)sub.textContent=(lastRead?'Read '+agoTxt(lastRead)+' \u00b7 ':'')+cs.length+(cs.length===1?' feed':' feeds');
|
|
1980
|
+
var lastRead=0;cs.forEach(function(c){if(c.checked>lastRead)lastRead=c.checked;});var sub=$('boardsub');if(sub)sub.textContent=(lastRead?'Read '+agoTxt(lastRead)+' \u00b7 ':'')+cs.length+(cs.length===1?' feed':' feeds')+bsWaitWord();
|
|
1917
1981
|
var waiting=cs.filter(function(c){return c.count;}).map(function(c){return c.label+(c.source==='page'?' on '+c.name:'');});
|
|
1918
1982
|
$('boardsum').innerHTML=waiting.length?"<b style='color:var(--bone)'>Waiting on you "+when+":</b> "+esc(waiting.join(', ')):"Nothing new "+when+" on the "+cs.length+" thing"+(cs.length===1?"":"s")+" you watch.";
|
|
1919
1983
|
el.innerHTML=cs.map(function(c,i){var nt=Math.ceil(((c.chat||[]).length)/2);
|
|
@@ -1952,6 +2016,7 @@ el.querySelectorAll('.bdraft').forEach(function(btn){btn.addEventListener('click
|
|
|
1952
2016
|
api('/api/watch/draft',{id:n.id}).then(function(x){btn.disabled=false;if(!x||x.error){boardMsg('err',esc((x&&x.error)||'failed'));return;}boardMsg('ok',x.chat?DRAFTING_CHAT:x.social?DRAFTING_SOCIAL:DRAFTING);loadWatchUI();}).catch(function(e){btn.disabled=false;boardMsg('err',esc(String((e&&e.message)||e)));});});});}
|
|
1953
2017
|
wire(el);wire($('boardtalk'));wire($('bsov'));BWIRE=wire;}
|
|
1954
2018
|
$('boardrefresh').addEventListener('click',loadBoard);
|
|
2019
|
+
if(document.addEventListener)document.addEventListener('click',function(ev){var t=ev&&ev.target;if(!t||!t.closest)return;var tx=t.closest('#panel-tasks .task .t');if(!tx||t.closest('a,button,input,textarea,summary'))return;var sel=window.getSelection?String(window.getSelection()):'';if(sel)return;var row=tx.closest('.task');if(row&&row.classList)row.classList.toggle('open');});
|
|
1955
2020
|
var atr=$('agenttrust');if(atr&&atr.addEventListener){api('/api/agent/trust').then(function(r){if(r)atr.checked=!!r.full;}).catch(function(){});atr.addEventListener('change',function(){api('/api/agent/trust/set',{full:atr.checked}).then(function(r){if(r)atr.checked=!!r.full;});});}
|
|
1956
2021
|
var bw=$('boardwin');if(bw&&bw.querySelectorAll)bw.querySelectorAll('button').forEach(function(b){b.addEventListener('click',function(){BOARDH=+b.getAttribute('data-h')||24;bw.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-checked',x===b?'true':'false');});loadBoard();});});
|
|
1957
2022
|
// A web page, mapped by itself in the hidden browser (headless.mjs): no capture,
|