symbiot 0.55.1 → 0.55.3

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 CHANGED
@@ -2,6 +2,25 @@
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.55.3
6
+
7
+ - **Your Claude subscription, no API key.** With Claude Code signed in to your Claude account (Pro or Max), Symbiot now answers through it: chats, write-ups, posts, screenshots. Nothing to connect, no separate bill. It comes first when you haven't picked an AI, ahead of an old saved key. `symbiot login` and Settings → Your AI offer it first; if Claude Code isn't signed in yet, they tell you to run `claude` once and sign in. Before, a fresh install sent you to `symbiot login`, which only asked for an API key.
8
+ - **Home remembers the conversation you're having.** Home's chat didn't pass its own last turns to the AI, only what was said elsewhere in the app, so a "yeah" to its own "want that?" was read as an answer to something else and the task you'd agreed to was never made. Home now keeps its own thread: the last 16 turns, each in full, from the last 12 hours. A short answer (yes, yeah, ok, do it, 2) always answers what it just said there.
9
+ - **Ideas get hashed out first.** Bring Symbiot an idea and it sharpens it with you (what's strong, where it's weak, what's missing) before proposing a task, and files it once you agree.
10
+ - **An ambiguous ask gets readings, not a guess.** When a request could mean two things (which project, how big, what done looks like), Symbiot gives two or three numbered readings, its pick first, and asks which. Clear asks still go straight through.
11
+
12
+ ## 0.55.2 — 2026-10-07
13
+
14
+ - Fixed a feature description being read as something waiting on you: an agent's "lit amber when something needs you: work waiting for your OK" showed as a Marketing lane step only you could do. A "when / if / once / until" before the words now means it describes behaviour.
15
+ - Added a Marketing lane on Home that gathers marketing work for all your products, tags each item by product, and lights up when something needs you.
16
+ - Added drafting of social posts in the Marketing lane, each opening with a hook, explaining the product and its goal, and never posted without your approval.
17
+ - Fixed the Pearl look so the middle orb's words show inside it and stay readable on both its bright and dark sides.
18
+ - Added a Minimize control to the Home chat, also triggered by Esc or clicking outside, which keeps your conversation so you can reopen it.
19
+ - Added a "What could be done next" box on Home when nothing waits on you, suggesting a few useful actions you can start in one tap.
20
+ - Changed finished agent runs so your AI reads their last words, catching new ways of saying something still waits on you.
21
+ - Added a sandbox for project agent runs that limits where they can write, which you can turn off with "agentSandbox": false in settings.
22
+ - Fixed the Seen button and reorganized the Map into tidy lanes, merged Tasks and Agents into a Workdesk, and added Reports and Marketing pages.
23
+
5
24
  ## 0.55.1
6
25
 
7
26
  - **No more stray Symbiot windows.** 0.55.0's smoke test called Away like every other endpoint, so each full test run (yours, or an agent's in the symbiot repo) opened Away windows on your screen. Away now opens only when asked to by name, and never while windows are turned off (as in tests).
package/README.md CHANGED
@@ -40,6 +40,9 @@ agent and brings its work back to you to approve.
40
40
  npm install -g symbiot # or run without installing: npx symbiot week
41
41
  ```
42
42
 
43
+ With Claude Code signed in to your Claude account, that's all: Symbiot uses your
44
+ subscription, no API key. See [Connect it (once)](#connect-it-once) for the other options.
45
+
43
46
  ## Prefer a window? `symbiot app`
44
47
 
45
48
  ```bash
@@ -53,32 +56,34 @@ no install — it's the same CLI. Press Ctrl+C (or click the X in the top corner
53
56
 
54
57
  It opens on [the liquid](#the-liquid-a-home-that-shapes-itself-to-you); each of these parts opens from it:
55
58
 
56
- - **Map** — your repos and project folders as droplets in the liquid, placed by
57
- their **nearest neighbours**: each repo's fingerprint is what it's built with
58
- (languages and tools), the weeks you work on it (your commits per week over the
59
- last 12), and what it's about (the words of its name, README and package
60
- description, TF-IDF weighted), and it sits by the 3 repos most like it
61
- (`mapknn.mjs`). The closest merge into one shape, the next are joined by a thin
62
- thread of liquid, the rest by a faint line; each cluster is named over its region
63
- ("JavaScript · Node"); what you haven't touched in a while is smaller and sinks;
64
- **you are here** marks where your recent work is. **Point at a repo** to light up
65
- its neighbours and see why ("shares JavaScript, Node; you worked on both the same
66
- weeks"). **Click** for its details: branch, commits, uncommitted work, your last
67
- 12 weeks, **Most like it**, a **Suggest next steps** button, and an **AI review**
68
- (what the project does, whether it needs new work at all, and ideas you can tick
69
- into **Tasks**). Scroll to zoom, drag to pan. Under the map: the languages and
70
- tools you build with. The Map needs **no AI key**; reviews and suggestions use
71
- your chosen model. Project folders that **aren't git repos** show up too (see
72
- [Where it looks](#where-it-looks)), as rings. The [experimental](#screens-blueprints-for-screen-automation)
59
+ - **Map** — your repos and project folders as droplets in the liquid, in tidy
60
+ **lanes**: **Needs you** first (a project with a question for you or work ready
61
+ for your OK, and any agent run waiting on you; click one to answer it), then
62
+ **Your repos** (the ones with your commits), **Other repos**, **Folders without
63
+ git** (as rings), and the act-* folders agent runs work in, gathered into one
64
+ **Agent run folders** group until you click it open. Each lane runs newest first
65
+ on a grid as wide as the window, so names never sit on top of each other however
66
+ many folders you add; click a lane's name to fold it away. **Point at a repo** to
67
+ light up the ones most like it and see why: each repo's fingerprint is what it's
68
+ built with, the weeks you work on it and what it's about (`mapknn.mjs`), as in
69
+ "shares JavaScript, Node; you worked on both the same weeks". **Click** for its
70
+ details: branch, commits, uncommitted work, your last 12 weeks, **Most like it**,
71
+ a **Suggest next steps** button, and an **AI review** (what the project does,
72
+ whether it needs new work at all, and ideas you can tick into the **Workdesk**).
73
+ The page scrolls down the lanes; ctrl+scroll (a pinch) or **+** and **−** zoom.
74
+ Under the map: the languages and tools you build with. The Map needs **no AI
75
+ key**; reviews and suggestions use your chosen model. Project folders that
76
+ **aren't git repos** show up too (see [Where it looks](#where-it-looks)). The
77
+ [experimental](#screens-blueprints-for-screen-automation)
73
78
  **Screens** view folds away under the map.
74
79
  - **Dashboard** — everything you [watch](#screens-blueprints-for-screen-automation),
75
80
  as a stream: time runs left to right, one current per feed (your inbox, GitHub,
76
81
  WhatsApp, any other page), "now" on the right where each feed pools. Every message
77
82
  is a bead placed when it arrived; the ones waiting for your reply are bigger and
78
- amber, and what you've already seen stays as small faint beads. Point at or tap a
79
- bead to read it and **Draft a reply** or **Open** it. Under each feed's name:
80
- **Seen** (sets its count back to 0; what it found stays under Watching, and the
81
- other feeds keep theirs), **Talk** (a chat with your AI about what's new there:
83
+ amber. Point at or tap a bead to read it and **Draft a reply** or **Open** it.
84
+ Under each feed's name: **Seen** (sets its count back to 0 and clears its beads,
85
+ and remembers it: only what comes in after counts; what it found stays under
86
+ Watching, and the other feeds keep theirs), **Talk** (a chat with your AI about what's new there:
82
87
  what needs you, and what to say to whom; agree it there, then **Draft a reply**
83
88
  on one, and your agent gets that talk with the message, so it writes what you
84
89
  agreed) and **Check now**. The headline says what's waiting; switch between 24
@@ -93,7 +98,10 @@ It opens on [the liquid](#the-liquid-a-home-that-shapes-itself-to-you); each of
93
98
  a "fetch latest" toggle (and an [experimental](#experimental) "check CI").
94
99
  - **Week / Standup / Todo** — the write-ups (these use your chosen AI), set as a
95
100
  readable page with its dates; **Copy** copies the text as written.
96
- - **Tasks** — a checklist, grouped by kind (Fixes, Tests & CI, Docs, …). You add
101
+ - **Workdesk** — everything handed to an agent, in one place: what needs you, work
102
+ awaiting your review, the tasks queued for later, and below them the agents at
103
+ work, their questions and the handovers between them.
104
+ Tasks are a checklist, grouped by kind (Fixes, Tests & CI, Docs, …). You add
97
105
  a task by telling Symbiot in the bar at the bottom: it files it under the right
98
106
  project. Filter by type or repo, then **Send to repos** (at the top, beside
99
107
  Close) to hand just those to your agent (see
@@ -101,7 +109,7 @@ It opens on [the liquid](#the-liquid-a-home-that-shapes-itself-to-you); each of
101
109
  finishes lands in **Awaiting your review** (see
102
110
  [Review and approve](#review-and-approve-the-agents-work)). A task you tick
103
111
  yourself is done and **auto-archives**; the archived view (the liquid's
104
- **Archive**) can restore it. Opening Tasks or Agents from the liquid shows the
112
+ **Archive**) can restore it. Opening the Workdesk from the liquid shows the
105
113
  work first: a sphere per task an agent is on, named in plain words, with small
106
114
  spheres orbiting it while it works; what's done and waiting for your OK; and what's
107
115
  waiting its turn, gathered round the agent in its repo. One **Go** (or saying
@@ -110,13 +118,20 @@ It opens on [the liquid](#the-liquid-a-home-that-shapes-itself-to-you); each of
110
118
  (once it's awaiting review) what the agent changed. Answers use your chosen AI,
111
119
  grounded in that repo's commits, README, rules and pending diff; the thread is
112
120
  kept with the task.
113
- - **Agents** — every agent run Symbiot has started (and local-model downloads):
114
- live status, elapsed time, exit code, the tail of its output, and **what it
121
+ **Agents and handovers**, under the tasks: every agent run Symbiot has started
122
+ (and local-model downloads): live status, elapsed time, exit code, the tail of its output, and **what it
115
123
  did** — files changed and commits made, read from git, whichever agent it was.
116
124
  When an agent leaves **questions, options or ideas** for you, they show up on its
117
125
  block (see [Questions from your agent](#questions-from-your-agent)).
118
- - **Reports** — what your agents wrote up for you (findings, audits, plans), to
119
- read in the app (see [What a run hands back](#what-a-run-hands-back-facts-to-remember-replies-to-wait-for-reports)).
126
+ - **Reports** — what your agents wrote up for you (findings, audits, plans), newest
127
+ first, the unread ones marked (**Mark all read** clears them), and a reader for
128
+ each; unread reports always have their own droplet on Home (see [What a run hands back](#what-a-run-hands-back-facts-to-remember-replies-to-wait-for-reports)).
129
+ - **Marketing** — marketing for every product you make, in a lane of its own (see
130
+ [The Marketing lane](#the-marketing-lane)): what needs you there first, the
131
+ lane's tasks and what its agent drafted, each tagged with the product it's for,
132
+ the LinkedIn **Drafts to post** (Shipped, Learned / fixed, Longer post), the
133
+ replies to them and the 4-week test (see
134
+ [`symbiot post`](#post-about-your-week-symbiot-post)).
120
135
  - **Settings** — [Link your work](#link-your-work), your AI, the folders to scan, your agent command, the
121
136
  [weekly write-up and start at login](#every-week-and-at-login), plus the
122
137
  [experimental](#experimental) email, model recommendations and one-click local
@@ -272,7 +287,7 @@ in the repo's `SYMBIOT_KEYSTORE_B64` and `SYMBIOT_KEYSTORE_PASS` secrets (see
272
287
  ## Hand tasks to your coding agent: `symbiot push`
273
288
 
274
289
  ```bash
275
- symbiot push # or the "Send to repos" button in the app's Tasks tab
290
+ symbiot push # or the "Send to repos" button in the app's Workdesk
276
291
  symbiot push --open # ...and run your agent command in each repo
277
292
  ```
278
293
 
@@ -281,7 +296,7 @@ Writes a **`.symbiot/TASKS.md`** into each repo your tasks reference — a check
281
296
  open `TODO/FIXME` markers (with `file:line`), and current drift. Then point Claude
282
297
  Code / Cursor / any agent at it: *"Read `.symbiot/TASKS.md` and implement the
283
298
  unchecked items."* Tasks come from ticking a repo review's ideas, or adding your
284
- own in the Tasks tab. Non-git project folders on the Map can receive tasks too.
299
+ own on the Workdesk. Non-git project folders on the Map can receive tasks too.
285
300
 
286
301
  The brief asks the agent to tick each item (`- [x]`) as it finishes it and to leave
287
302
  its changes **uncommitted**, ready for your review.
@@ -289,7 +304,7 @@ its changes **uncommitted**, ready for your review.
289
304
  ### Review and approve the agent's work
290
305
 
291
306
  A tick from the agent doesn't archive anything. It moves the task into **Awaiting
292
- your review** at the top of the Tasks tab, grouped by repo, with the branch, the
307
+ your review** at the top of the Workdesk, grouped by repo, with the branch, the
293
308
  size of the uncommitted change and a **Show diff** button. For each repo:
294
309
 
295
310
  - **Approve → PR** syncs the work. If you're on the default branch it creates
@@ -358,7 +373,7 @@ quieter choice.
358
373
  A project blocked on something only you can give (a token, a sign-in) would
359
374
  otherwise start its agent again with every answer, handover or Go, only to say
360
375
  the same thing. **Park this project** (at the top of its tasks) or **Park** (on
361
- its run in the Agents tab) stops its tasks starting agent runs, even Start it
376
+ its run on the Workdesk) stops its tasks starting agent runs, even Start it
362
377
  anyway, until you unpark it. A run already going finishes; Go skips a parked
363
378
  project and says so.
364
379
 
@@ -425,8 +440,8 @@ Google Drive, Notion) now gets its claude.ai connector's rule on every Claude ru
425
440
  even before Claude records it or while it waits to be authorized. A rule for tools
426
441
  a run doesn't have does nothing.
427
442
 
428
- Each run is logged to `.symbiot/agent.log` in the repo and shown live in the
429
- **Agents** tab. The command is saved as `agentCmd` in
443
+ Each run is logged to `.symbiot/agent.log` in the repo and shown live on the
444
+ **Workdesk**. The command is saved as `agentCmd` in
430
445
  `~/.config/symbiot/config.json` — it's your command, Symbiot only fills in
431
446
  `{dir}` and `{prompt}`. A command saved from an older preset keeps working as-is.
432
447
 
@@ -434,8 +449,8 @@ One agent per repo: while a run is still going (tracked in
434
449
  `.symbiot/agent.pid`, so the app and `symbiot push --open` both see it), sending
435
450
  again doesn't start a second agent there, and doesn't rewrite the `TASKS.md` it's
436
451
  working from either: the new tasks wait in `.symbiot/TASKS.next.md` and replace
437
- `TASKS.md` when it finishes, keeping anything it ticked. The agent's block in the
438
- **Agents** tab shows "tasks held" meanwhile (hover it for their titles), and
452
+ `TASKS.md` when it finishes, keeping anything it ticked. The agent's block on the
453
+ **Workdesk** shows "tasks held" meanwhile (hover it for their titles), and
439
454
  when the agent finishes, the app starts one on the held tasks by itself. If
440
455
  `symbiot push --open` started the running agent, the app can't see it exit, so
441
456
  it starts one the next time it checks that repo after it finishes (opening the
@@ -455,6 +470,19 @@ Talk to Symbiot and its agents just do the work. A Claude run skips permission c
455
470
  folder (or `/`), sudo, piping a script from the internet into a shell, reading your
456
471
  SSH keys or cloud credentials, and changing Symbiot's own settings. A stopped agent
457
472
  is told why, and asks.
473
+ - **The sandbox**, for a repo run, on top of the membrane. Its commands run in Claude
474
+ Code's own sandbox (bubblewrap on Linux, Seatbelt on macOS). They write only in the
475
+ repo, the folders you gave Symbiot or allowed it, and the npm and download caches
476
+ (`~/.npm`, `~/.cache`). They can't read your keys, or your AI's key in Symbiot's
477
+ settings, and there's no way to run a command outside the sandbox. Local sockets
478
+ work, so headless Chrome starts, but Docker's and your desktop session's are hidden,
479
+ since either one is a way out. Its Edit and Write tools are held to the same
480
+ folders by the membrane, and `gh` keeps working (the run gets its token).
481
+ Its settings live in `~/.config/symbiot/sandbox/`, not in the repo, so a run can't
482
+ widen its own sandbox. An ops run, whose job is this computer, and a draft reply's
483
+ run keep the membrane alone. On Linux it needs `bwrap` and `socat` (`sudo apt
484
+ install bubblewrap socat`); without them, runs have the membrane only, and Settings
485
+ says what's missing. `"agentSandbox": false` in `config.json` turns it off.
458
486
  - **One mind.** When an agent asks you something, your answer goes back into the same
459
487
  conversation (`--resume`), so it carries on knowing everything it knew. It starts
460
488
  afresh after 8 runs or 2 days.
@@ -488,7 +516,7 @@ Reading both costs about 40 lines.
488
516
  - [repo: symbiot] Show the drift report in Standup
489
517
  ```
490
518
 
491
- Up to five questions show on that agent's block in the **Agents** tab, each with
519
+ Up to five questions show on that agent's block on the **Workdesk**, each with
492
520
  two options and room for your own answer, alongside its ideas, two at a time.
493
521
  Two, because most people pick the recommended option or add every idea without
494
522
  weighing them, so the agent is told to be the judge. Both options have to be good
@@ -509,7 +537,7 @@ that starts with `[repo: <name>]`, to that repo's Tasks: an agent working on one
509
537
  project can have ideas for another (Symbiot itself, say). The agent is told to
510
538
  keep going with everything that doesn't depend on an answer, and to ask instead of
511
539
  doing anything destructive. Both files live in `.symbiot/`, so they're never
512
- committed. The questions stay on the Agents tab after Symbiot restarts (or when
540
+ committed. The questions stay on the Workdesk after Symbiot restarts (or when
513
541
  `symbiot push --open` started the run): Symbiot notes each folder a run starts in
514
542
  (`~/.config/symbiot/runs.json`) and lists the ones still waiting on you.
515
543
 
@@ -567,7 +595,9 @@ in the app.
567
595
  name: **Ferrofluid** (glossy black liquid metal, lit like a studio; the default),
568
596
  **Glass** (clear droplets that bend the colours behind them) or **Pearl** (silver
569
597
  lit like a product photo). Every panel follows the look, and the typeface (Geist)
570
- ships with Symbiot, so it looks the same offline.
598
+ ships with Symbiot, so it looks the same offline. The core's words sit inside it in
599
+ every look; on Pearl's mirror they sit on a soft pearl sheen, so they read over its
600
+ bright top and its dark underside alike.
571
601
 
572
602
  At the top, in a band of its own, is **what only you can do**: an agent's question
573
603
  to you, an Approve waiting, a first step. Each is a lit lane orb with a liquid line
@@ -580,6 +610,19 @@ and the blob dissolves. More than fit across go on a second page. Clicking the o
580
610
  questions and its Approve under **Needs you here** at the top of its tasks, with its
581
611
  handovers that are waiting; an ops run's question on its block in Agents.
582
612
 
613
+ When nothing waits on you, the band becomes **What could be done next**: three to
614
+ five things that can be done now, from what Symbiot already knows, each with what
615
+ it gains and how long it takes, and one button. **Triage the 8 unread reports**
616
+ starts an agent that reads them and leaves one page, with their next steps as ideas
617
+ you add in a click. **Retry** runs a handover that errored and was left. **Send
618
+ coral's 2 waiting tasks to its agent** is for a project whose tasks have waited
619
+ days with nothing at work there. **Give whatsapp_module "…"** puts a task with no
620
+ project into the project its words name and starts that project's agent. **Draft
621
+ replies** drafts replies to the newest new mail or chats and leaves them unsent.
622
+ **Make a task** turns a decision you made lately, that no task carries yet, into a
623
+ task. One you take stays away for a week, and **×** puts the box away until its
624
+ suggestions change. It never shows while anything still waits on you.
625
+
583
626
  What stopped and can't go on without you is a question there too, on its lane.
584
627
  A handover that couldn't start (an ops run, limited to its own folder, handed
585
628
  "needs a run that can edit `~/Company`" to ops) asks **Allow this run access to
@@ -589,11 +632,20 @@ that asked; **Skip** lets it go, and that agent reads that you skipped it. It as
589
632
  for 3 days, or until the same thing goes through another way. A run that ended in
590
633
  an error asks **Run it again?** for a day, until another run starts there.
591
634
 
635
+ A run that ended saying something waits on you, without asking it in QUESTIONS.md
636
+ ("the draft isn't sent", "needs sudo, and only you can do that"), asks too:
637
+ **Go ahead** or **Done it**, with what to check first. Symbiot catches the usual
638
+ ways of saying it by their words. For a new way of saying it ("the invoice is
639
+ ready as a draft; give me the word and it goes out"), your AI reads each finished
640
+ run's last words once, so it isn't missed.
641
+
592
642
  Under the band, around the core, are the parts of the app, grouped the
593
643
  **P.A.R.A.** way, each group a row with its name at its start, always in the same
594
644
  place and order:
595
645
 
596
- - **Projects** (work with an end): Tasks, Agents and Todo. Agents has spheres
646
+ - **Projects** (work with an end): Tasks, Agents, Todo and Marketing, which is lit
647
+ amber (with how many) when something there needs you and has spheres orbiting
648
+ it while its agent works. Agents has spheres
597
649
  orbiting it while an agent works. Click the **Projects** name for your projects
598
650
  themselves: a sphere per lane with work on it, saying what's going on there,
599
651
  amber when something there needs you, in order (**Needs you** first, **Recent**,
@@ -616,6 +668,10 @@ on the Dashboard. Talk to it in the bar at the bottom: "open tasks" opens Tasks,
616
668
  as every chat. Paste a screenshot into the bar (Ctrl+V) or drop one on it, and
617
669
  Symbiot sees it with what you say (up to 4, with any AI you've connected that reads
618
670
  images); if it hands the work to an agent, the agent gets the screenshot too.
671
+ Once you've said something, the chat has its own bar over the input: **Minimize**
672
+ (or Esc, or a click anywhere outside the chat) shrinks it back to the input and
673
+ brings Home back as it was, and **Chat · 4 messages** opens it again with
674
+ everything said so far. Sending a message opens it too.
619
675
  Leave it alone and it rests as one orb. There are no settings for any
620
676
  of this: it adapts to you.
621
677
 
@@ -712,7 +768,8 @@ and nothing else. **It never posts by itself**, and it doesn't schedule.
712
768
  count the facts don't show is sent back once, then dropped. When every draft is
713
769
  dropped, Symbiot tries once more from the start by itself before giving up.
714
770
  - **You approve each one.** The drafts wait under **Marketing**, a section of its
715
- own (its orb shows on Home once posts can be drafted), with **Approve**,
771
+ own (its orb shows on Home once posts can be drafted, lit while drafts wait on
772
+ you), each tagged with the product it's about, with **Approve**,
716
773
  **Edit** and **Skip**. Approve copies the post to your clipboard and opens
717
774
  LinkedIn's share box: you paste it and post it yourself.
718
775
  - **Pictures and videos.** Each draft says in a line what picture or short video
@@ -764,7 +821,7 @@ can't do.
764
821
  showed. The agent has your tools and connectors (MCP, the command line, a
765
822
  browser signed in to your linked sites). Something for later becomes a task on
766
823
  your list. Anything hard to undo (closing an account, deleting, paying, sending)
767
- the agent asks you about first, in the Agents tab.
824
+ the agent asks you about first, on the Workdesk.
768
825
  - **It looks at links.** Ask about the links on a card and it looks each one up the
769
826
  way a link preview does (not signed in, no scripts run, nothing downloaded):
770
827
  where it really goes, what the page is, and whether it's a file such as an .apk.
@@ -778,8 +835,9 @@ can't do.
778
835
 
779
836
  ### Lanes: agents hand work to each other
780
837
 
781
- Each agent has its own lane: a repo, or **ops** for everything outside one (this
782
- computer, accounts, services, your connectors). When an agent needs something
838
+ Each agent has its own lane: a repo, **ops** for everything outside one (this
839
+ computer, accounts, services, your connectors), or **marketing** for marketing
840
+ across your products ([The Marketing lane](#the-marketing-lane)). When an agent needs something
783
841
  that's another lane's job, it doesn't stop and it doesn't ask you to do it. It
784
842
  writes it to its `.symbiot/HANDOFF.md`, under the lane's name, and Symbiot:
785
843
 
@@ -788,7 +846,7 @@ writes it to its `.symbiot/HANDOFF.md`, under the lane's name, and Symbiot:
788
846
  2. when that agent is done, puts what it did into the asking agent's
789
847
  `.symbiot/ANSWERS.md` and starts that agent again, so it carries on.
790
848
 
791
- The Agents tab lists the **Handovers**: who handed what to whom, and where it
849
+ The Workdesk lists the **Handovers**: who handed what to whom, and where it
792
850
  stands. A handover to a lane that doesn't exist, or back to the agent's own lane,
793
851
  goes back to the agent, not to you. After 4 handovers in a row, the next one isn't
794
852
  started, so two lanes can't pass the same job back and forth.
@@ -799,13 +857,39 @@ hand, a cable, which network you're on), your identity or secrets (signing in, a
799
857
  account, spending money, sending something in your name). Every brief tells the
800
858
  agent so, and such a question says which: `👤 You (only you: your Meta token): …`.
801
859
 
860
+ ### The Marketing lane
861
+
862
+ Marketing is a lane of its own, not one repo's: posts, demo videos and screenshots,
863
+ launches, landing-page copy and campaigns for every product you make (Symbiot,
864
+ Dailify, the agent products…). Give it work on the Marketing page (**Add**, with the
865
+ product it's for), by telling Symbiot ("a marketing task: a launch post for
866
+ Dailify"), or by **Move to Marketing** on another project's task about posting,
867
+ launches or pricing; any agent can hand work to it too, as `marketing`.
868
+
869
+ - **Each item is tagged with its product**: `[Dailify] Write the launch post` as you
870
+ wrote it, else the product its words name (as you write it: "Dailify", from your
871
+ repos' names as you say them, the way Home names them).
872
+ - **Its agent works in its own folder**, `~/.config/symbiot/marketing`, a git repo of
873
+ its own on this computer (no remote). It drafts each piece under
874
+ `drafts/<product>/`, and its brief lists the products in its tasks with where each
875
+ one's code and docs are. Every post opens with a strong hook, says plainly what
876
+ the product is and states its end goal; where the end goal isn't written down, it
877
+ drafts one and asks you to confirm it.
878
+ - **It never posts**, publishes, schedules or sends, and never signs in as you.
879
+ Its work waits for your OK like any project's (**Approve** commits it in its
880
+ folder), and its questions reach you like any agent's.
881
+ - **On Home** it's an orb among your projects, lit amber when something there needs
882
+ you: its questions (which also show as blobs under **Needs you**), its work
883
+ waiting for your OK, and the week's drafts. Click it and its page opens with
884
+ those lit at the top.
885
+
802
886
  ### What a run hands back: facts to remember, replies to wait for, reports
803
887
 
804
888
  Every brief also tells the agent what else it can leave in `.symbiot/`:
805
889
 
806
890
  - **`REMEMBER.json`**: lasting facts it found (who someone is, which account is
807
891
  what, a decision), as `[{ "name", "kind", "fact" }]`. A run can't write
808
- Symbiot's memory itself. Once it stops, its block in the Agents tab shows them,
892
+ Symbiot's memory itself. Once it stops, its block on the Workdesk shows them,
809
893
  each ticked, with **Remember** and **Skip**. Remember puts the ticked ones into
810
894
  memory, marked with the run's name; Skip drops them. Either way it's asked once.
811
895
  - **`AWAITING.json`**: an email it sent (or drafted for you to send) that waits on
@@ -830,18 +914,26 @@ Every brief also tells the agent what else it can leave in `.symbiot/`:
830
914
 
831
915
  ## Connect it (once)
832
916
 
833
- Pick the AI you want it to write with:
917
+ **Have a Claude subscription (Pro or Max)? There's nothing to connect.** If
918
+ [Claude Code](https://claude.com/claude-code) is installed and signed in with your
919
+ Claude account (run `claude` once and sign in), Symbiot answers through it: no API
920
+ key, no separate bill. It runs Claude Code headless with Symbiot's own instructions,
921
+ no tools (Read only, for a screenshot you attach), none of your settings, plugins or
922
+ servers, and no session kept.
923
+
924
+ Otherwise, pick the AI you want it to write with:
834
925
 
835
926
  ```bash
836
- symbiot login # choose Claude, OpenAI, Gemini, or a local model (Ollama)
927
+ symbiot login # your Claude subscription, Claude (API key), OpenAI, Gemini, or a local model (Ollama)
837
928
  ```
838
929
 
839
- You'll get a short menu; paste that provider's API key (or, for Ollama, just point
930
+ You'll get a short menu; for a hosted model paste its API key (for Ollama, just point
840
931
  it at your local server) and it's saved to `~/.config/symbiot/config.json`. The
841
932
  app's Settings tab does the same.
842
933
 
843
934
  | Provider | Get a key | Default model |
844
935
  |----------|-----------|---------------|
936
+ | Your Claude subscription (Claude Code) | no key: sign in to Claude Code | your plan's default |
845
937
  | Claude (Anthropic) | <https://console.anthropic.com/settings/keys> | `claude-opus-5-5` |
846
938
  | OpenAI (GPT) | <https://platform.openai.com/api-keys> | `gpt-4o-mini` |
847
939
  | Gemini (Google) | <https://aistudio.google.com/apikey> | `gemini-1.5-flash` |
@@ -1047,7 +1139,7 @@ med / max, marking which fit your RAM) to run free and private via
1047
1139
  cheap → top). `setup-local` starts Ollama if it isn't running, downloads the model
1048
1140
  with progress, and switches Symbiot over to it. If Ollama isn't installed it prints
1049
1141
  the one-line install for your OS and you re-run it after. Both are buttons in
1050
- Settings, where the download shows in the Agents tab. You can always pick a model
1142
+ Settings, where the download shows on the Workdesk. You can always pick a model
1051
1143
  yourself with `symbiot login`.
1052
1144
 
1053
1145
  ### Screens: blueprints for screen automation
@@ -1198,7 +1290,7 @@ runs by itself (Claude Code, Codex, Gemini or Aider, not an editor or Orca's tab
1198
1290
  the app running (its hidden browser stays open between the agent's steps), and
1199
1291
  your mail's site (`mail.google.com`) under Trusted sites. Each email gets a folder
1200
1292
  of its own in `~/.config/symbiot/drafts` (yours only) with the agent's brief, and
1201
- the run shows in the Agents tab like any other: what it asks (a date only you
1293
+ the run shows on the Workdesk like any other: what it asks (a date only you
1202
1294
  know, say) is answered there. The agent sees the email's text, so it reaches
1203
1295
  whatever AI your agent uses. From a terminal, `symbiot watch new` marks the
1204
1296
  emails `"mail": true`, and `symbiot watch draft <id>` does the same as the button.
package/agents.mjs CHANGED
@@ -169,13 +169,68 @@ function guardSettings() {
169
169
  try { mkdirSync(CONFIG_DIR, { recursive: true }); writeFileSync(GUARD_SETTINGS, JSON.stringify(s, null, 2) + "\n", { mode: 0o600 }); } catch {}
170
170
  return GUARD_SETTINGS;
171
171
  }
172
- function withTrust(tmpl) {
172
+ function withTrust(tmpl, box = null) {
173
173
  if (!isClaudeCmd(tmpl) || !trustFull()) return tmpl;
174
174
  let c = String(tmpl).replace(/\s--permission-mode\s+\S+/g, "");
175
175
  if (!/--dangerously-skip-permissions\b/.test(c)) c += " --dangerously-skip-permissions";
176
- if (!/--settings\b/.test(c)) c += ` --settings ${JSON.stringify(guardSettings())}`;
176
+ if (!/--settings\b/.test(c)) c += ` --settings ${JSON.stringify(box ? box.file : guardSettings())}`;
177
177
  return c;
178
178
  }
179
+ // ---- a repo run, sandboxed ----------------------------------------------------------
180
+ // On top of the membrane, a repo run's commands run in Claude Code's own sandbox
181
+ // (bubblewrap on Linux, Seatbelt on macOS): they write only in the repo, the folders
182
+ // you gave Symbiot or allowed it (--add-dir, the repo's allow list) and the npm and
183
+ // download caches, can't read your keys, and have no way out of it (no unsandboxed
184
+ // commands). Its Edit and Write tools are held to the same folders by the guard
185
+ // (SYMBIOT_WRITES). An ops run, whose job is this computer, and a draft reply's run
186
+ // keep the membrane alone. Its settings live in Symbiot's folder, not the repo, so a
187
+ // run can't widen its own sandbox for the next one. Without what the sandbox needs
188
+ // (Linux: bwrap and socat) runs aren't sandboxed, and Settings says what to install;
189
+ // config.agentSandbox: false turns it off.
190
+ const SANDBOX_DIR = join(CONFIG_DIR, "sandbox");
191
+ const SANDBOX_CACHES = [".npm", ".cache"]; // npm's cache and logs; pip, uv, node-gyp, Chrome for Testing
192
+ const SANDBOX_SECRETS = [".ssh", ".gnupg", ".aws", ".azure", ".kube", ".docker", ".config/gcloud", ".config/gh", ".netrc", ".npmrc", ".pypirc", ".git-credentials", ".config/symbiot/config.json"]; // your AI's key and the app's token too
193
+ // Local sockets are allowed (headless Chrome needs one to start, so the browser tests
194
+ // ran in no sandboxed run), but the ones that would be a way out are hidden: Docker's,
195
+ // and your session's (its bus starts commands outside, through systemd-run; X11 and
196
+ // Wayland type into your windows; the ssh agent and keyring hold your keys).
197
+ const SANDBOX_SOCKETS = (uid = typeof process.getuid === "function" ? process.getuid() : "") => ["/run/docker.sock", "/var/run/docker.sock", "/run/containerd", "/run/podman", "/run/dbus", ...(uid !== "" ? [`/run/user/${uid}`] : []), "/tmp/.X11-unix"];
198
+ function sandboxNeeds({ platform = process.platform, has = hasCmd } = {}) {
199
+ if (platform === "darwin") return { ready: true, missing: [] };
200
+ if (platform !== "linux") return { ready: false, missing: [], unsupported: true };
201
+ const missing = ["bwrap", "socat"].filter((c) => !has(c));
202
+ return { ready: !missing.length, missing };
203
+ }
204
+ const sandboxed = (repoPath, cfg = loadConfig()) => cfg.agentSandbox !== false && !!repoPath && !resolve(repoPath).startsWith(CONFIG_DIR + "/");
205
+ function sandboxWrites(repoPath, tmpl = "") {
206
+ const out = [resolve(repoPath)];
207
+ try { for (const k of knowledgeFolders()) out.push(k.path); } catch {}
208
+ for (const m of String(tmpl).matchAll(/--add-dir\s+(?:"([^"]+)"|'([^']+)'|(\S+))/g)) out.push(m[1] || m[2] || m[3]);
209
+ for (const f of ["settings.json", "settings.local.json"]) { try { const d = JSON.parse(readFileSync(join(repoPath, ".claude", f), "utf8")); for (const x of (d.permissions && d.permissions.additionalDirectories) || []) out.push(String(x)); } catch {} }
210
+ for (const c of SANDBOX_CACHES) out.push(join(homedir(), c));
211
+ return [...new Set(out.filter(Boolean).map((p) => resolve(String(p).replace(/^~(?=\/|$)/, homedir()))))];
212
+ }
213
+ // { file, writes } for a repo run to be sandboxed, else null.
214
+ function sandboxFor(repoPath, tmpl = "", { needs = sandboxNeeds } = {}) {
215
+ if (!sandboxed(repoPath) || !needs().ready) return null;
216
+ const writes = sandboxWrites(repoPath, tmpl);
217
+ const s = { ...JSON.parse(readFileSync(guardSettings(), "utf8")), sandbox: { enabled: true, failIfUnavailable: true, autoAllowBashIfSandboxed: true, allowUnsandboxedCommands: false,
218
+ network: { allowAllUnixSockets: true }, filesystem: { allowWrite: writes, denyRead: [...SANDBOX_SECRETS.map((p) => join(homedir(), p)), ...SANDBOX_SOCKETS()] } } };
219
+ const file = join(SANDBOX_DIR, digest(resolve(repoPath)) + ".json");
220
+ try { mkdirSync(SANDBOX_DIR, { recursive: true, mode: 0o700 }); writeFileSync(file, JSON.stringify(s, null, 2) + "\n", { mode: 0o600 }); } catch { return null; }
221
+ return { file, writes };
222
+ }
223
+ // What a sandboxed run's environment adds: the folders its Edit and Write tools may
224
+ // write (the guard reads them), and gh's token, since the sandbox can't reach the
225
+ // keyring gh keeps it in (gh then says "Requires authentication").
226
+ function sandboxEnv(box, { token = () => (hasCmd("gh") ? sh("gh auth token").trim() : "") } = {}) {
227
+ if (!box) return null;
228
+ const env = { SYMBIOT_WRITES: JSON.stringify(box.writes) };
229
+ if (!process.env.GH_TOKEN) { let t = ""; try { t = token(); } catch {} if (/^[\w-]{20,}$/.test(t)) env.GH_TOKEN = t; }
230
+ return env;
231
+ }
232
+ // For Settings → Handoff: { on, ready, missing } (on: repo runs get it).
233
+ function sandboxState() { const n = sandboxNeeds(); return { on: loadConfig().agentSandbox !== false && trustFull(), ready: n.ready, missing: n.missing, ...(n.unsupported ? { unsupported: true } : {}) }; }
179
234
  const SESSION = "session.json", RESUME_MAX = 8, RESUME_AGE = 2 * 86400000;
180
235
  const RESUME_PROMPT = "The user has answered: read the newest entries in .symbiot/ANSWERS.md and carry on with .symbiot/TASKS.md from where you were. Same rules as before.";
181
236
  function sessionOf(path) { try { return JSON.parse(readSymbiot(path, SESSION)) || null; } catch { return null; } }
@@ -214,14 +269,16 @@ function runHandoff(repoPath, { force = false } = {}) {
214
269
  let opts = {}; try { opts = JSON.parse(readSymbiot(repoPath, "handoff.json")) || {}; } catch {}
215
270
  const busy = runningHandoff(repoPath); if (busy) return { busy: true, id: busy.id || "", pid: busy.pid, auto: !!busy.auto };
216
271
  releaseHeldTasks(repoPath); // held for an agent another process started, which has since exited
217
- tmpl = withStream(withTrust(withScope(withConnectors(tmpl, repoPath), repoPath)));
272
+ const scoped = withScope(withConnectors(tmpl, repoPath), repoPath), box = isClaudeCmd(scoped) && trustFull() ? sandboxFor(repoPath, scoped) : null; // a repo run's sandbox, on top of the membrane
273
+ tmpl = withStream(withTrust(scoped, box));
218
274
  const resume = isClaudeCmd(tmpl) ? resumeFor(repoPath) : null;
219
275
  const runCmd = resume ? tmpl + ` --resume ${resume.id}` : tmpl;
220
276
  if (!force) { const w = waitingFor(repoPath); if (w) { askedFor(repoPath); return { blocked: true, waiting: true, questions: 0, note: waitNote(w) }; } }
221
277
  const blocked = !force && blockedAgain(repoPath, tmpl); if (blocked) return blocked;
222
278
  try { unlinkSync(join(repoPath, ".symbiot", WAITING)); } catch {} // the step's done (or this is Start it anyway): this is the run it waited for
223
279
  const lock = join(repoPath, ".symbiot", LOCK);
224
- const env = opts.env && typeof opts.env === "object" ? Object.fromEntries(Object.entries(opts.env).map(([k, v]) => [k, String(v)])) : null;
280
+ const own = opts.env && typeof opts.env === "object" ? Object.fromEntries(Object.entries(opts.env).map(([k, v]) => [k, String(v)])) : null, boxEnv = sandboxEnv(box);
281
+ const env = own || boxEnv ? { ...(own || {}), ...(boxEnv || {}) } : null;
225
282
  const e = track(typeof opts.name === "string" && opts.name ? opts.name.slice(0, 80) : repoPath.split("/").pop(), fillHandoff(runCmd, repoPath, resume ? RESUME_PROMPT : HANDOFF_PROMPT), repoPath, (code) => {
226
283
  if (noteSession(repoPath, code, !!resume) === "lost") { setTimeout(() => { try { runHandoff(repoPath, { force: true }); } catch {} }, 300); return; } // its conversation is gone: start afresh
227
284
  try { if (JSON.parse(readFileSync(lock, "utf8")).pid === e.pid) unlinkSync(lock); } catch {}
@@ -323,13 +380,86 @@ function takenUp(path, end) {
323
380
  }
324
381
  function needsOf(path, { final = "", waiting = null, end = Date.now(), now = Date.now() } = {}) {
325
382
  if (now - end > NEEDS_FOR) return null;
326
- const n = waiting ? { kind: "step", what: clipWords(String(waiting.step || "").replace(/^\s*👤\s*(You\s*(\([^)]*\))?:)?\s*/, "").trim(), 240), check: "", label: "", waiting: true } : leftToYou(final);
383
+ const n = waiting ? { kind: "step", what: clipWords(String(waiting.step || "").replace(/^\s*👤\s*(You\s*(\([^)]*\))?:)?\s*/, "").trim(), 240), check: "", label: "", waiting: true } : leftToYou(final) || readNeeds(path, final);
327
384
  if (!n || !n.what) return null;
328
385
  const key = digest(n.what);
329
386
  if (loadSettled()[path] === key || takenUp(path, end)) return null;
330
387
  return { ...n, key };
331
388
  }
332
389
 
390
+ // ---- your AI reads the last words too ------------------------------------------
391
+ // leftToYou catches the ways runs have said it so far ("isn't sent", "only you can").
392
+ // A new way of saying it would slip by, so once a run ends your AI reads its last
393
+ // words for anything waiting on you, once per run's words (READ_FILE keeps what it
394
+ // found, by folder and words), and needsOf takes that when the patterns found
395
+ // nothing. Only for a run that asked nothing in QUESTIONS.md and isn't waiting on a
396
+ // step already: those show as they are.
397
+ const READ_FILE = join(CONFIG_DIR, "lastwords.json"), READ_KEEP = 300, READ_RETRY = 15 * 60000, READ_PER_TICK = 3, READ_MAX = 4000;
398
+ const loadRead = (file = READ_FILE) => { try { const o = JSON.parse(readFileSync(file, "utf8")); return o && typeof o === "object" && !Array.isArray(o) ? o : {}; } catch { return {}; } };
399
+ const readKey = (path, final) => digest(path + "\n" + String(final || "").trim());
400
+ function readNeeds(path, final, { file = READ_FILE } = {}) {
401
+ if (!String(final || "").trim()) return null;
402
+ const r = loadRead(file)[readKey(path, final)];
403
+ return r && r.what ? { kind: r.kind === "approve" ? "approve" : "step", what: r.what, check: r.check || "", label: r.label || "", read: true } : null;
404
+ }
405
+ const READ_SYSTEM = `You read the last message a coding agent left when its run ended, and say whether it leaves the user something to do before the work is finished.
406
+ That's either something waiting for their OK before it happens (sending, posting, deleting, paying, closing, merging, publishing) or a step only they can take (signing in, a password or a 2FA code, sudo, a phone or a cable, a token from a provider's console, a choice that's theirs to make).
407
+ Not: work that's done, a summary of what changed, an idea they might like, changes waiting for review or Approve in Symbiot, a connector to sign in to again, or a question already asked in QUESTIONS.md.
408
+ Reply with JSON only, no markdown fence: {"needs": false} or {"needs": true, "kind": "approve" or "step", "what": "the sentence that says so, in its own words, under 240 characters", "check": "what it says to check first, or empty", "label": "what it is in 1 to 3 words, or empty"}.`;
409
+ function parseRead(raw) {
410
+ const t = String(raw || ""), a = t.indexOf("{"), b = t.lastIndexOf("}"); if (a < 0 || b < a) return null;
411
+ let o; try { o = JSON.parse(t.slice(a, b + 1)); } catch { return null; }
412
+ if (!o || typeof o.needs !== "boolean") return null;
413
+ if (!o.needs || !String(o.what || "").trim()) return { needs: false };
414
+ const c = (x, n) => clipWords(String(x || "").replace(/\s+/g, " ").trim(), n);
415
+ return { needs: true, kind: o.kind === "approve" ? "approve" : "step", what: c(o.what, 240), check: c(o.check, 240), label: c(o.label, 60) };
416
+ }
417
+ // One pass (the app runs it every minute): each run that ended in the last
418
+ // NEEDS_FOR, whose words neither the patterns nor your AI has read yet, a few at a
419
+ // time. Without an AI connected it does nothing. { read } (how many it asked about).
420
+ let reading = false; // one pass at a time: a slow model mustn't have the next minute's pass ask again
421
+ async function readLastWords(o = {}) {
422
+ if (reading) return { read: 0, busy: true };
423
+ reading = true; try { return await readPass(o); } finally { reading = false; }
424
+ }
425
+ // Each folder's newest finished run, with its last words as needsOf reads them:
426
+ // those this app started, then those from before it last started (runs.json).
427
+ function finishedRuns() {
428
+ const out = [], seen = new Set();
429
+ for (const e of HANDOFFS) {
430
+ if (seen.has(e.path)) continue; seen.add(e.path); if (e.status === "running") continue;
431
+ let tail = "", work = null; try { ({ tail, work } = workOf(e.path, readRunLog(e.log))); } catch {}
432
+ out.push({ path: e.path, name: e.name, endedAt: e.endedAt, final: work ? tail : "" });
433
+ }
434
+ for (const r of loadRuns()) {
435
+ if (seen.has(r.path)) continue; seen.add(r.path); if (runningHandoff(r.path)) continue;
436
+ let end = 0; try { end = statSync(join(r.path, ".symbiot", "agent.log")).mtimeMs; } catch { continue; }
437
+ out.push({ path: r.path, name: r.name, endedAt: end, final: lastFinal(r.path) });
438
+ }
439
+ return out;
440
+ }
441
+ async function readPass({ list, ask, connected, now = Date.now(), file = READ_FILE } = {}) {
442
+ if (!ask) { const ai = await import("./ai.mjs"); if (!(connected ? connected() : ai.resolveProvider())) return { read: 0 }; ask = ai.write; }
443
+ const store = loadRead(file), due = [];
444
+ for (const a of list || finishedRuns()) {
445
+ const final = String(a.final || "").trim();
446
+ if (!final || now - (a.endedAt || now) > NEEDS_FOR || leftToYou(final)) continue;
447
+ const k = readKey(a.path, a.final), had = store[k];
448
+ if (had && !(had.error && now - had.at > READ_RETRY)) continue;
449
+ if (waitingFor(a.path) || agentQuestions(a.path, a.name).questions.length) continue; // those show as they are
450
+ due.push({ k, final });
451
+ }
452
+ for (const d of due.slice(0, READ_PER_TICK)) {
453
+ let r = null; try { r = parseRead(await ask(READ_SYSTEM, `The run's last message:\n\n${String(d.final).trim().slice(-READ_MAX)}`)); } catch {}
454
+ store[d.k] = r ? { at: now, ...(r.needs ? { kind: r.kind, what: r.what, check: r.check, label: r.label } : {}) } : { at: now, error: true };
455
+ }
456
+ if (due.length) {
457
+ const keys = Object.keys(store).sort((x, y) => store[y].at - store[x].at).slice(0, READ_KEEP), kept = {}; for (const k of keys) kept[k] = store[k];
458
+ try { mkdirSync(CONFIG_DIR, { recursive: true }); writeFileSync(file, JSON.stringify(kept), { mode: 0o600 }); } catch {}
459
+ }
460
+ return { read: Math.min(due.length, READ_PER_TICK) };
461
+ }
462
+
333
463
  // ---- a step that's yours: wait for it -----------------------------------------
334
464
  // An answer that picks a "👤 You:" option leaves a step for the user to do (put a
335
465
  // key in .env, allow a command in Settings). Starting the agent before it's done
@@ -945,4 +1075,4 @@ function agentsList() {
945
1075
  }).concat(earlierRuns());
946
1076
  }
947
1077
 
948
- export { FACTS, factsOf, knownRun, withStream, workOf, HANDOFFS, HANDOFF_PROMPT, QUESTIONS_MAX, OPTIONS_SHOWN, IDEAS_SHOWN, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, grantAgent, grantRule, allowTool, claudeConnectors, withConnectors, linkedConnectors, connectorsLine, connectorsInfo, linkReach, fillHandoff, runHandoff, PARKED_NOTE, parkedPaths, isParked, parkLane, agentMissing, blockedAgain, runningHandoff, loadRuns, earlierRuns, namedFiles, waitingFor, startWaiting, writeTasks, droppedTasks, releaseHeldTasks, startHeldTasks, detectHandoffs, pickAgent, findOrcaCli, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, agentChanges, parseQuestions, suggestionTarget, skipIdea, agentQuestions, answerQuestions, agentsList, needsOf, settleNeeds, NEEDS_FOR , autoAllow, autoAllowSweep, allowlistInWork, installAllowlist, inWork, withScope , withTrust, resumeFor, noteSession, trustFull, GUARD_SETTINGS };
1078
+ export { FACTS, factsOf, knownRun, withStream, workOf, HANDOFFS, HANDOFF_PROMPT, QUESTIONS_MAX, OPTIONS_SHOWN, IDEAS_SHOWN, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, grantAgent, grantRule, allowTool, claudeConnectors, withConnectors, linkedConnectors, connectorsLine, connectorsInfo, linkReach, fillHandoff, runHandoff, PARKED_NOTE, parkedPaths, isParked, parkLane, agentMissing, blockedAgain, runningHandoff, loadRuns, earlierRuns, namedFiles, waitingFor, startWaiting, writeTasks, droppedTasks, releaseHeldTasks, startHeldTasks, detectHandoffs, pickAgent, findOrcaCli, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, agentChanges, parseQuestions, suggestionTarget, skipIdea, agentQuestions, answerQuestions, agentsList, needsOf, settleNeeds, NEEDS_FOR , readLastWords, readNeeds, parseRead, READ_FILE, autoAllow, autoAllowSweep, allowlistInWork, installAllowlist, inWork, withScope , withTrust, sandboxFor, sandboxEnv, sandboxNeeds, sandboxState, sandboxWrites, SANDBOX_DIR, resumeFor, noteSession, trustFull, GUARD_SETTINGS };