feat(board): session attention, Discord rows, task attribution and relaunch activity (rows 18, 22, #1511, #1512)

One cumulative control-board, webui and seat state. The four rows edit the
same files (scan.mjs, page.html, README.md, app.js), so they land together,
each on its own receipt:

- Row 18, Discord connector rows on the board (#1509): R3 approved by
  Darkwing and Dewey, Gitea comment 26257, manifest 254403b8. Jason
  accepted the visual test.
- Row 22, board attention status (#1503): Filbert approved R1, comment
  26248, manifest e40b58ec; restart receipt 26249.
- #1511, task attribution (row 6 code phase): R2 approved by Filbert and
  Dewey, manifest d4c96395. docs/TOOLS.md carries the approved --by usage
  line (tools-usage.patch 86bcba3c).
- #1512, relaunch activity (row 6 pilot): R1 approved by Darkwing and
  Dewey, candidate manifest 47769fad. All seven source files match it.

Row 16, internal development bootstrap (#1510): the seven files outside
shared records match Filbert's R1 pins, receipt 26204 (agents/researcher/*,
scripts/test-darkwing-launch.mjs, the bootstrap plan).

packages/webui/src/public/app.js is committed at its #1512 R1 pin ce7d79a4.
The working copy holds Dewey's unreviewed return-flow candidate on top of
that, and it stays uncommitted.

Also: the four row briefs and Darkwing's evidence records under
agents/darkwing/work, including the 2026-09-26 tree manifest and the #1512
re-run against 21e3e908. Serial acceptance command: 397/397, three runs.
The failures that only show when tests run concurrently are in #1509 engine
tests, and they reproduce on clean HEAD.

Suites on the exact staged tree: config 24, task 90, foundation 43,
conductor 17, release 14, auth 15, discord 63; package union 397/397
(serial); test-darkwing-launch 5/5.

Shared records (BUILD-LOG, QUEUE, CURRENT, DEFERRED, SESSIONS, AGENTS.md,
agents/README.md) follow in Sage's records commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-26 14:54:18 -05:00
co-authored by Claude Opus 5.5
parent 21e3e908b6
commit af4203ca92
67 changed files with 2417 additions and 78 deletions
+88 -4
View File
@@ -12,10 +12,10 @@ rewritable; they are not run records and are not evidence.
| State | Plain-words meaning |
|---------|----------------------|
| working | The agent is in the middle of a turn: thinking or running a tool. A seat whose newest log entry is a tool call or a tool result is working even if its last words looked like a question. |
| waiting | The agent finished its turn with a text-only message. It is your move now. |
| waiting | The agent explicitly requested your input in its completed reply. |
| error | The agent's last turn ended in an error, was aborted, or was cut off. Go look at it. |
| offline | There is no live tmux session for this agent right now, or its session exists but no longer runs `pi`. |
| idle | The agent is live but has not had a conversation yet. |
| idle | The agent is live and available, including after a normal completed reply. |
| unknown | The scanner could not ask tmux (missing or not answering). It does not assume the agent is alive. |
Liveness means a pane in the agent's tmux session is actually running `pi`
@@ -23,6 +23,79 @@ Liveness means a pane in the agent's tmux session is actually running `pi`
exists but only runs bash or some other program counts as offline, not
waiting.
### Explicit human attention
A completed assistant reply requests attention only when its first nonblank text
line begins at column zero with `Input needed: ` and a nonempty request. Example:
```text
Input needed: Choose staging or production for the approved test.
```
Ordinary replies such as `BOARD_REPLY_OK`, completion reports and questions without
this explicit signal are idle. Code/quote examples and thinking blocks do not
count. Tool activity/errors retain precedence. A later ordinary completed reply
clears the previous request; a user/tool message is working, not waiting.
Agents reserve the signal for Jason's decision or input, not another agent's
review or routine completion. It is display state, never action authorization.
Existing unmarked replies cannot establish a human blocker. Seen acknowledges
the current event and removes it from the attention list; it does not resolve a
genuine request or change waiting to idle. A new request reappears.
## Relaunch activity notice
A live native row with a positively live, matching registration gets a
`relaunchedAt` timestamp when that registration's valid `startedAt` is strictly
newer than valid recorded `lastActivity`. Both board presentations then show
`relaunched at X, no messages since` in the current activity/preview positions.
The inspector explicitly labels retained last activity, assistant text and errors
as historical. CLI `scan --print` also replaces its old preview/age with the notice. New recorded session activity at or after the launch timestamp
clears the notice. Equality does not assert a relaunch.
Missing/invalid timestamps, missing or mismatched registrations, unknown/dead PID
or row liveness, and connector rows yield `relaunchedAt: null`. Unknown is not
proof of relaunch. The comparison uses the existing session activity timestamp,
not a new transcript index or authenticated process-incarnation protocol.
This field changes presentation only. Historical transcript files and serialized
lastActivity/lastAssistantText/lastError remain intact. State, attention, Seen,
task selection/attribution and reply eligibility are unchanged. An old unresolved
waiting/error state therefore remains visible, with its text labelled historical,
rather than being silently cleared by the new notice. No launcher or live
registration mutation is required to test this behavior.
## Discord connector rows
The CLI discovers private `<dataRoot>/discord/<binding>.json` files on every
scan, including server rescans. Only matching safe binding name and seat identity
are used. Files must be regular, non-symlink, mode 0600 and at most 1 MiB.
Discovery never resolves token paths or projects binding policy, Discord IDs or
user/channel lists. Invalid bindings produce fixed, content-free discoveryErrors.
Library callers enable this with `discordDataRoot` on scan/startServer.
Rows use project `fleet` and agent `<seat> (discord: <binding>)`, distinct from
native seats. Task is fixed `Discord connector`, with source `connector`, never
inferred from the first user message: Discord routing envelopes contain private
IDs. Ordinary-agent task derivation and assistant transcript display are unchanged.
Session history comes from `sessions/discord-<binding>` without
following linked directories/files. Liveness uses the connector's readPid and
ownerState checks, not tmux or registration: only a live PID with matching boot
ID and start tick is live. Missing, invalid, dead or unverifiable owners are
non-live. The `connector` projection contains binding, alive, ownerState and
braked only; it does not expose the journal directory or owner record.
STOP presence is shown separately as braked, even when the owner is offline.
An unsafe journal path gives brake unknown, not an unbraked claim. STOP contents
are never read. Activity retains the usual idle/waiting/error rules. Both board
pages omit Reply for connector rows; the backend refuses connector replies before
transport even if a stale/forged native registration supplies tmux details.
This is read-only observation, not connector control. No brake, unlock, recovery,
Discord request, counter collection or engine action is performed. Tests use
isolated bindings, sessions and process identities; they do not prove live
service transitions or authorize replacing a running board.
## Commands
```
@@ -156,8 +229,18 @@ name alone. A registered task, project or workspace replaces the derived
value and the row's source field (`taskSource`, `activeProjectSource`,
`workspaceSource`) reads `registration`; the page shows a small source tag
next to the value and a "Registered" line in the detail with the start time,
harness, pid and tmux session. An empty task or a null project or workspace
in the record leaves the derived value in place. Rows with no registration
harness, pid and tmux session. When the task shown is the registered one,
the row also carries `taskSetBy` (#1511): the record's `taskSetBy` from
`mosaic seat task --by NAME` (else `$MOSAIC_AGENT_NAME`, else `unknown`),
or `unknown` for a record written before the field existed. Both pages show
it as a "set by NAME" tag after the task's source tag and a "Task set by"
detail row. It is what the caller claimed, not a verified identity: the
board escapes and displays it and nothing else reads it; in particular the
reply gate looks only at the registration. For every other task source
(`first-user-message`, `connector`, a stale registration, no task) it is
null, so a record never lends its setter to a task it did not set. An empty
task or a null project or workspace in the record leaves the derived value
in place. Rows with no registration
are exactly as before. The board only reads `seats/`; `mosaic launch` and
`mosaic seat task` are the only writers. A malformed record is listed in
`registrationErrors` on the index (and on stderr for `scan`) and skipped.
@@ -194,6 +277,7 @@ values), the scan refuses rather than silently dropping every mark.
"cwd": "/mnt/storage/src/mosaic-stack",
"task": "Read agents/darkwing/work/RESTART.md",
"taskSource": "first-user-message",
"taskSetBy": null,
"workspace": "/mnt/storage/src/mosaic-stack",
"workspaceSource": "tmux-pane",
"activeProject": "mosaic-stack",