0.62.0
本頁內容尚未翻譯。
Raven Keep (鴉閣), a view of its own, and views as lazy themes (spec)
Section titled “Raven Keep (鴉閣), a view of its own, and views as lazy themes (spec)”- Views load on demand. Geek and Zen move to
gui/themes/<view>/and load only when chosen; Standard loads no theme file. A small boot script sets the first frame’s view, and one list of view names (src/views.ts) serves the server and the page. Nothing looks different in Standard, Geek or Zen. - Raven Keep is a gothic skin of Standard: castle palette, fog, bats and ravens, its own icons and sounds. Its backdrop rests dark and lightning reveals the picture (within WCAG 2.3.1: at most two flashes a second, no white or red), and a perched raven turns its head.
- Its status motion has no radar and no rotation: a seal drawn to a diamond, breathing mist and a bat badge while a unit works; a swelling soul-fire, a dotted ring and a floating
?while it waits. Calm pages,prefers-reduced-motionand forced colours keep still, distinct shapes. - Every view owns its
--pbs-*tokens (the document and chat blocks of pi-better-show read them).
The head tower is a band on the map (head band spec)
Section titled “The head tower is a band on the map (head band spec)”- The map’s head pill becomes a band on its own row: crown, name, its newest line, a waits chip and one link to the head page, 44 px tall. Wires to the towers run dashed through the column gutters. Geek and Raven have their own skins.
- The head unit carries
line, the first line of the head’s newest reply (markdown marks dropped, at most 160 characters).
Document tabs (todo #128)
Section titled “Document tabs (todo #128)”- Document tabs are one line, named by file; two files of the same name are told apart by their parent folder. The active tab is bold and underlined.
- GitHub alerts (
> [!NOTE],[!WARNING]and the rest) in a Markdown document show as callouts.
Trains as their own workers: groundwork (todo #125, tasks 1 to 3)
Section titled “Trains as their own workers: groundwork (todo #125, tasks 1 to 3)”Not in use yet: no prompt opens a train before a later release brings the train’s session, its prompt and the integrator’s side (spec).
- The train contract (
src/train/): step kinds and checks, the car slip and its evidence, the refusal of acceptance asks, holds, waits, trainboards and car states. - A train is a role of its own (
sb_role=train,sb_train=<project>/<code>): opened by the tower on an integrator’s manifest while integration is on, never crowned, at mostmaxTrains(default 3) per project over the whole computer, restored as a train. cli.mjs train gateruns one step’s command: it waits for the load, takes a machine-wide named lock (numbered markers, takeover only from a dead holder), honours lanes and targets, runs the command in its own process group, verifies, bounds a hung verify, and stops within MonitorStop’s 5 s.train status,train noticesandtrain clearread and tidy the trainboards.
Wait cards: the server half (todo #126, tasks 1 to 3)
Section titled “Wait cards: the server half (todo #126, tasks 1 to 3)”A tower’s wait for the boss is a card with fields, not a line of free text (docs/specs/2026-11-07-wait-cards.md). The cards’ look in the GUI and the prompts come in later tasks.
tower_memory add kind wait for bosstakestype(action, choice, secret),happened,do,urlorcommand,after,options,secret.name,from,restates, anddue;textis refused. One refusal lists every field that fails, and nothing is kept. sb checks shape, length, URL and command form, and secret shape; it never judges the language.frommust be the session id of an intercom event the run took, orself; the raiser’s name, role, workspace, and tab are read from the net by id at the write. The brief’stower_memoryline names sb’s locale as the language of the card.- Waits stored before are read as they are and ask to be restated; a stored card that fails its check reads as an old wait.
- The memory route has a
pressop (done,choose,secret,decline,restate): the raiser is told through intercom by its session id under await_cardheader, then the tower’s boss event says whether it got the notice./boss memory done|choose|decline|restatedo the same. - The snapshot carries each card and press; whole cards go newest first up to 24,000 characters of text across towers, the rest as short rows with
cut. - A reply in a card’s box does not use the card up: it can still be pressed.
/boss memory doneon a secret card takes the first step for the boss. A declined or resolved boss wait stays in the snapshot (state: resolved,outcome,ended) until the 24 h sweep. A paired device or an operate session cannot take a secret card’s first step. A secret check that no longer refusestoken: expired, a commit hash in a URL path, or a Google Docs address.
Wait cards: the card in the GUI, the prompts, and the demo (todo #126, tasks 4 to 6)
Section titled “Wait cards: the card in the GUI, the prompts, and the demo (todo #126, tasks 4 to 6)”The boss sees a tower’s wait as the card it is (docs/specs/2026-11-07-wait-cards.md, The card in the GUI):
- One component,
gui/waitcard.ts, draws a boss wait wherever one shows: the dock’s dialog (every part whole), the tower card’s rows, the tower’s comms and the head page (folded to one line with the primary button beside it; a tap unfolds the rest), and Zen’s card (in Zen’s skin). Its parts: what happened, what to do, where, what happens after, who raised it and how long ago. Its buttons follow the kind: I’m done, a choice’s options, Start entering and I’ve entered it for a secret (a paired device says “Enter it at the computer” and has no start button), Ask the tower to rewrite it for a wait written before cards. - A URL is text and an Open link button that only a click uses (nothing is opened or fetched at mount); a command is shown with Copy and sb never runs it. Every field is set as text.
- Skip needs a second tap, with an optional reason: it tells the tower and the raiser and keeps the wait, resolved, for a day. Reply to the tower is the answer box of before; Later is unchanged.
Dismissis gone from a card (dropstays on the memory route). - On a phone every button and link is 44 px with 8 px between, the primary and each choice are full-width, a URL wraps and a command wraps whole (nothing of it hides behind a sideways scroll; Copy still takes it as written).
- The dock’s dialog is modal for presses (the page behind it is inert while a wait’s dialog is on top), opens with the focus on the card’s primary button, shows the card’s own kind word as its badge, and Skip’s confirm takes the focus on Skip it with the confirm scrolled into view. Open
goes to the raiser’s own session, also on the head page, is offered in Zen too, and stays on after a press. Disabled buttons look disabled and the state line is green only for a press. - The prompts tell a tower to write a card in plain full sentences in sb’s locale (the fonstar case as the example), what to do when the boss presses it, and a main what a
wait_cardmessage andsecret_dropmean. - The demo’s
?waits=cardsshows one card of each kind and state, the fonstar case first.
What a unit is doing goes round the chat box, not into the log (todo #127)
Section titled “What a unit is doing goes round the chat box, not into the log (todo #127)”The status row under the last message of the comms log (“thinking”, “commanding”, “using
- While the unit works, a ring runs round the box (the composer’s row; a lit line under the box in Zen): the accent for a thought, a brighter and faster one for a tool, a slow quiet one while it only watches a monitor, violet for commanding. A unit that waits for the boss shows a still amber ring, a failed one a still red ring; an idle one shows none.
- The words stay for assistive tech: the composer carries a polite live region with the same text, changed only when the state changes.
- Under
prefers-reduced-motion: reduce, in a calm page (30 s without input) and in Geek the ring stands still (a dim one for work). Zen’s line only breathes in opacity. - Nothing moves in the chat or the composer when the state changes (the ring is a pseudo element out of the layout and the taps); the log keeps the 14 px under its last message the empty row once held.
- The map, the dossier and the unit cards still say the state in their own words.
Operate: an attended computer operates an unattended box (todo #121, spec §6.4, §11.3 and §11.4)
Section titled “Operate: an attended computer operates an unattended box (todo #121, spec §6.4, §11.3 and §11.4)”The server’s start wires the controller’s host and the box’s ledger, so a box’s sessions show in the GUI of an attended computer and the box serves the transcripts of finished runs. Nothing changes on a computer with no team, and an unattended computer has no controller end.
-
One sink per device. The holder’s
operatesink is the controller’s (host.receive) on an attended computer and the box’s bridge on an unattended one, never both; with the sink the holder listsoperate/1in its caps, which a box checks before it answers an open. A new GUI of the same server gets a new host (a stopped host is final). The server’s stop ends the host’s sessions with aclosewhile the holder still runs, and a team that stops closes its sessions at once (the holder’s roster hook). -
Caps reach a peer that starts late or restarts. A device’s
capsgo to the streams open when they are sent, and the relay replays one only past the cursor a restarted holder resumes from, which any queued envelope has moved past it. The firstcapsa holder posts after it started now lists the wordhello(an entry older builds ignore), and a holder that reads one sends its own once at the next tick the 15 s spacing allows, without the word. A box or a Mac that restarts quickly (an update,/boss server restart) is operable again within seconds, not at the peer’s next hourly caps. -
A box that grants nobody is offered to nobody.
operate/1is in an unattended box’s caps only while its policy has anoperategrant, and changes with the policy within a tick. Without a grant the box builds no bridge: no policy watch, no ledger, no timer, no log line, and no controller opens to it. -
Operate’s later fixes. A box’s train units reach the controller; the comms view of a finished run’s transcript shows it; a controller whose holder comes after its page was opened starts operating without a reload; a switch file that is a dangling symlink still reads as off; a
capspost that fails is tried again soon; a box that loses its last grant drops the bridge and its policy watch once no session is left. -
/boss link operatelists the sessions this computer holds (box, team, scope, state, since, pages),/boss link operate offends them all and keeps operate off until/boss link operate on, also after a GUI or the server starts again (the switch islink/operate-off.json, mode 600; the list says “off since -
Transcripts end to end. The box’s server writes its ledger from each net reading an operate session takes (the whole registry, with the owner, the dispatcher’s roster label and the workspace’s label), and gives each session the transcripts of its scope. On the Mac,
GET api/transcripts?base=<a box base's id>is asked of the session that shows the base, and each row comes back with a page key (xand 16 hex) thatapi/history,api/entryandapi/imagetake like any unit’s; the next page’s cursor names the last row by that key, never by the box’s own id, and a row’s role is one of the unit roles. -
The ledger places a run before it fixes its owner. The box reads Herdr’s listing after its net, a unit whose pane token or
openedrow is not written yet gets no row (it is not taken for the box’s), and a live run’s owner is read again until it ends; a team’s run is never moved to the box. While its policy grants operate, the box also reads its own net every 30 s, so a run that starts and ends while nobody is connected has a row. -
A box shows as bases of this GUI’s own net. While any page of this computer’s GUI is connected (the Mac’s own and every paired phone), the sb server holds one operate session per (box, team) to each unattended box whose fresh
capslistoperate/1, and closes them 60 s after the last page leaves. What a box’s sessions send is merged into the net the pages read, after the local bases, each base with aboxfield (device, label, team, scope,can,live | unreachable | ended). Two sessions of one box show one base, and a box-own unit that both show is one unit. -
No box key reaches a page. Every unit key, base id and tab id of a box becomes
xand 16 hex of a hash, kept in a table back to the box’s own. A request, a watch, a terminal or a read for such a key goes to the session that shows it (control.ts and gui.ts ask the bridge before any local route); the guards a paired device’s write meets (a local-only /boss command, a secretvalue) run on this computer first, and the box runs its own again. A page that loads before the session shows its unit gets its watch when it does. -
The terminal and the pad. Terminals are shared by the pages that show a unit and asked of the box again for a page that joins; the pad’s keys go only while the page’s stream got a frame in the last 5 s, at most 4 a second per unit. A request that the box does not answer in 20 s is a 504 (“check before sending again”).
-
The box is treated as hostile. Everything it sends is rebuilt from a schema before a page sees it: unknown fields dropped, keys with
.., a backslash or a control refused with their subtree, labels as plain text, marks and roles from their sets, a unit’s documents with names only and an emptycap, an answer’sthenlimited toterminalandrun, images of four types under 1 MB whose bytes match, a reply of status 200 to 599 under 1 MB, terminal rows as SGR text. What is refused is counted inlink.logasoperate-bad-frame(ten lines, then one in a hundred), never with its content. -
States. No frame for 45 s greys the base (
unreachable) and reopens with a growing wait; a box that closes for its policy or roster replaces the base with one line and is tried again after a minute; a box that stops, is overloaded or swaps its GUI greys the base and is reopened with the same growing wait; a refusal of the grant is remembered ten minutes and shows once per box and team for a minute, while a refusal for “not now” (unavailable, a full slot, an id seen before) is retried with the growing wait and shows nothing. A box no longer offered ends its session and leaves the map. The 12 hour rekey keeps the base live. A dialog a remote unit answers is no longer also shown as a forwarded card.
Link operate: transcripts of finished runs (todo #121, task 8)
Section titled “Link operate: transcripts of finished runs (todo #121, task 8)”An operator’s session on an unattended box can list and read the transcripts of runs that have finished, not only the 10 minutes the net keeps them (docs/specs/2026-11-07-link-operate.md §9).
- The box’s server keeps
link/operate-ledger.jsonl(mode 600, append-only): who owned a unit, who dispatched it, its role, a scrubbed name and workspace label, and where its session file is. It holds no content, keeps 30 days and at most 2,000 rows, and is compacted at start. GET api/transcriptslists the rows of the session’s scope, newest first, whose file still exists.api/history,api/entryandapi/imagetaket<id>keys, resolved in the ledger: a path from the request is never read, the scope is checked again, and a file outside pi’s session folder is refused.- A transcript is read only from a regular, single-link
.jsonlby its real path inside the session folder; a session seen again keeps its recorded owner; at<id>key out of scope is not looked up in the registry; an existing ledger file is set to mode 600.
Link operate: the box’s bridge (todo #121, task 5)
Section titled “Link operate: the box’s bridge (todo #121, task 5)”An unattended box’s sb server answers operate sessions from a granted team admin over Link (docs/specs/2026-11-07-link-operate.md §11.4).
- An unattended computer’s holders take the box’s end of
operate/1(an attended one’s take the controller’s, above); every frame is bound to its session’s team and sender, and each request passes the route table and the guard’s checks against the roster and the policy now. - The mirror and snapshots are paced under one budget of 900 envelopes a minute for the box; refusals are not counted and are throttled per (team, sender) to one in 10 s.
- Sessions end at once on a policy or roster change, the operator’s close, 45 s of silence, a GUI gone, or server stop; a failed Herdr read puts mains, members and subagents out of every scope until a read succeeds.
- link.log records opens, closes, inputs, reads and refusals by word and count, never typed text or file content.
Link operate: input guards, second round (todo #121 task 4)
Section titled “Link operate: input guards, second round (todo #121 task 4)”- An operate session’s keys now go only while the box can confirm what they do. The box’s pi reports which keys its live keybindings bind outside the editor (a user’s
keybindings.jsoncan put the external editor or any app action on a chord, Tab or an arrow); a key on that list, or a pi that cannot say, is refused. Every write (key, text, paste, composer, dequeue) also needs pi in the pane’s foreground by Herdr’s process list: a program that replaces pi, a nested shell, or an unreadable list refuses (the third round below relaxes this to exactly onepi, and adds pi’s raw-mode proof for an editor pi starts). - A confirmed double-Escape is never sent from an operate session, and the second-Escape refusal no longer waits for Herdr’s
idlestatus. - A watch or mirror begun while the session’s scope narrows ends with the narrower scope.
Link operate: input guards, third round (todo #121 task 4)
Section titled “Link operate: input guards, third round (todo #121 task 4)”- Operate no longer refuses a real session. The foreground check wanted pi alone in Herdr’s process list, and pi’s helper children (claude-bridge’s
claude, MCP servers) share its group; now exactly onepimust be listed, and pi’s own report that it holds the terminal in raw mode (which it gives up before an external editor or Ctrl+Z) is the proof the editor is pi’s. Stop works on these sessions. - A pad key is compared with pi’s bindings by bytes, so pi’s other spellings (
return,ctrl+m,esc,alt+left) cannot slip past. Submit, follow-up, clear and interrupt are allowed only on the keys their guards cover (Enter and Alt+Enter, Ctrl+C, Escape). Ids inkeybindings.jsonthat pi does not define (extension shortcuts) count as foreign. - The foreground look moves inside the control job, right before each write. Watches asked twice for one id, or finishing after the session ended, no longer escape the scope.
Link operate: input guards, fourth round (todo #121 task 4)
Section titled “Link operate: input guards, fourth round (todo #121 task 4)”- A rebound key is caught under pi’s other keyboard protocols too: every pad key has its kitty and modifyOtherKeys spellings, so Alt+B/D/F/Y and Alt+Enter bound to the external editor (or an extension shortcut) are refused when pi has the kitty protocol on.
- The composer’s Send checks the binding of the Enter it ends with (Alt+Enter for a follow-up while the session works), as the pad’s Enter does.
- pi’s raw mode is read again right before every write, so an
$EDITORpi opened after the earlier probe stops it (the process list cannot see an editor that sits beside pi). - The spec’s §10 lists the extension-shortcut and custom-editor residual; a stray line in the spec is gone.
The head page gives the room to the conversation, and a starting tower no longer looks interrupted (todo #130)
Section titled “The head page gives the room to the conversation, and a starting tower no longer looks interrupted (todo #130)”- The head’s log of orders (the cards under the conversation) is one folded line by default:
Orders sent (6)with the newest order’s line. Opened, the cards scroll in a box that takes at most 30% of the panel, so the conversation, the queue, and the composer keep the rest. A folded line is cheaper than a capped box that always takes its share, and the boss reads the log on request. - An unfolded order card no longer shows its clamped summary texts above the same texts in full (that was the “same entry twice” of the boss’s screenshot: one open card, not two rows).
- A message waiting for a tower is queued as
startat the source (runs.view) only while a start is under way (state waking, or standing with a wake armed); the page says 「塔台啟動中・準備好就送出」 / “Tower starting · sent when ready” (a head or workspace tower alike). A tower that will not wake says why and that the message is kept (why: service off, a start refused a moment ago, run slots full, tower not live, seat not proven: 「塔台服務已關閉・訊息會留著」, 「執行名額已滿・稍後送出」 and so on), never “sent when ready”. A bridge’s relayed queue no longer replaces the rows of a standing, waking, or held service tower (a closing one still has its pi, so its own queue shows). Once the run is up, what is left says “next run” as before. - The head page’s log fold shows its open/close marker at 18 px in the text colour.
?head=startingin the demo shows a head tower that stands by with the boss’s message waiting.