opencode-thrum-pro 0.1.3 → 0.1.5

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.
@@ -21,7 +21,11 @@ every agent in your `roster`:
21
21
  never capture ad-hoc or key on fixed prompt phrases. A failed or empty
22
22
  capture is NOT a dead or idle pane: fall back and re-read; never skip a
23
23
  roster member because its capture failed. If it's blocked on a permission
24
- modal or appears stalled, judge whether to unblock it.
24
+ modal or appears stalled, judge whether to unblock it. A member that is
25
+ *persistently* unwatchable (fails every capture path, isolated,
26
+ unreachable) is escalated to your `parent` every cycle until a coordinator
27
+ or human removes it from your roster — never dropped, never silently
28
+ given up on (see Escalation).
25
29
  2. **Watch its context% and warn.** Read its ctx% from YOUR OWN tmux
26
30
  capture of its status bar — **never from a figure it claims about
27
31
  itself** (a message, a memory-footer line, anywhere). If it's getting
@@ -50,8 +54,10 @@ every agent in your `roster`:
50
54
  - Verify by re-capture after acting — never trust exit status alone.
51
55
  - To unblock a REMOTE roster member's modal, use `thrum tmux send <agent> " "`
52
56
  (proxies by agent name to the owning peer daemon and appends Enter, so a
53
- space+Enter confirms the default-focused option) — never `thrum tmux key`,
54
- which is local-socket-only and errors on a remote agent. This grants only
57
+ space+Enter confirms the default-focused option); for a specific selection
58
+ use `thrum tmux send <agent> --keys <K1,K2,...>`. `thrum tmux send` is
59
+ peer-routed, so it works identically local or remote (fleet-wide) — this is
60
+ the replacement for the retired local-only tmux-key primitive. This grants only
55
61
  the leftmost/default option; the bright line above still applies. From the
56
62
  CLI/peer-router path this send is QUEUED behind the conservative
57
63
  monitor-silence wait, not instant — don't re-send while waiting it out.
@@ -115,6 +121,30 @@ is the entire reason the watch exists — and a blocked coordinator cannot
115
121
  receive its own escalation. The escalation would arrive at the one inbox
116
122
  guaranteed not to be read.
117
123
 
124
+ ### An unwatchable roster member: escalate every cycle until it is removed
125
+
126
+ A roster member you **cannot watch at all** — capture fails by every path
127
+ (local proxy *and* SSH fallback), the agent is fully isolated, or its owning
128
+ daemon is unreachable so you cannot even message it — is NOT a member you may
129
+ drop, mark dead, or fall silent on. It stays in your roster and it stays your
130
+ duty.
131
+
132
+ - **Escalate it to your `parent` every cycle it remains unwatchable —
133
+ repeatedly, not once.** A single "fully isolated" notice that you then stop
134
+ repeating is exactly the silent-give-up this archetype exists to prevent:
135
+ the roster member goes dark with nobody positioned to notice. Re-send the
136
+ escalation on each tick it is still unwatchable. (This is a standing
137
+ per-cycle duty, unlike the one-shot declared-intent nudge.)
138
+ - **You never remove it from your own roster.** Roster membership is
139
+ coordinator-owned config in `watch_params.json`, which you only ever read.
140
+ Only a **coordinator or the human** may remove an agent from your roster —
141
+ the signal that it no longer needs watching. Until that removal lands in
142
+ `watch_params.json`, you keep escalating.
143
+ - **Do not borrow the empty-roster shutdown rule here.** "Shut yourself down"
144
+ applies only when your *whole* roster is empty/missing. One unwatchable
145
+ *member* out of several is not that case: keep watching the rest, keep
146
+ escalating the unwatchable one.
147
+
118
148
  ## Declared-intent reminders (duty #4)
119
149
 
120
150
  Each cycle, for each roster member, judge whether the last assistant turn
@@ -197,18 +227,22 @@ stability check, writes one report file, and emits one matchable summary
197
227
  line. It is mechanical only — no auto-escalate, no auto-key — exactly the
198
228
  scope this section already describes; judgment stays in your own turn.
199
229
 
200
- **Setup (one-time per watcher instance):** `CLAUDE_PLUGIN_ROOT` is a
201
- Claude-Code-hook-only env var — it is NOT present in `thrum monitor`'s
202
- scheduled-process environment, so the script cannot locate itself inside
203
- the plugin tree at run time. Copy both scripts out of the skill's
204
- `resources/` into your own agent directory first:
205
-
206
- ```bash
207
- cp "${CLAUDE_PLUGIN_ROOT}/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh" \
208
- "${CLAUDE_PLUGIN_ROOT}/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh" \
209
- .thrum/agents/<you>/
210
- chmod +x .thrum/agents/<you>/thrum-watch-pane-capture.sh .thrum/agents/<you>/thrum-capture-fallback.sh
211
- ```
230
+ **Setup (one-time per watcher instance):** the script cannot locate itself
231
+ inside the plugin tree at run time — `thrum monitor`'s scheduled-process
232
+ environment has none of the plugin-root env vars a hook invocation gets
233
+ (`CLAUDE_PLUGIN_ROOT`, codex's `PLUGIN_ROOT`), and neither is reliably set
234
+ in an ordinary interactive turn either, so a shell `cp` against either
235
+ variable is not runtime-safe in any host. Copy both scripts out of the
236
+ skill's `resources/` into your own agent directory using your Read/Write
237
+ tools instead — the same skill-relative resolution every other `resources/`
238
+ reference in this plugin already uses, with no env var and no
239
+ runtime-versioned path involved:
240
+
241
+ 1. Read `resources/thrum-watch-pane-capture.sh` (relative to this skill) and
242
+ Write its exact content to `.thrum/agents/<you>/thrum-watch-pane-capture.sh`.
243
+ 2. Read `resources/thrum-capture-fallback.sh` and Write its exact content to
244
+ `.thrum/agents/<you>/thrum-capture-fallback.sh`.
245
+ 3. `chmod +x .thrum/agents/<you>/thrum-watch-pane-capture.sh .thrum/agents/<you>/thrum-capture-fallback.sh`
212
246
 
213
247
  Then register the monitor against that absolute copy path —
214
248
  **`--notify-on-success` is MANDATORY, not optional decoration**: a
@@ -63,12 +63,16 @@ ssh_destination() {
63
63
 
64
64
  run_local() {
65
65
  case "$MODE" in
66
- # "${EXTRA_ARGS[@]:-}" not "${EXTRA_ARGS[@]}": a DECLARED-EMPTY array
67
- # subscripted with [@] is a fatal unbound-variable under `set -u` on
66
+ # "${EXTRA_ARGS[*]:-}" not "${EXTRA_ARGS[*]}": a DECLARED-EMPTY array
67
+ # subscripted with [*] is a fatal unbound-variable under `set -u` on
68
68
  # bash <4.4 (a real bug, not a style choice -- `key` mode with zero
69
69
  # key args crashes on this fleet's /bin/bash 3.2.57 without the ":-").
70
+ # key mode delivers named tmux keys via `thrum tmux send --keys` (the
71
+ # peer-routed replacement for the retired local-only tmux-key primitive);
72
+ # EXTRA_ARGS (one key per element) is comma-joined into a single --keys
73
+ # value inside a subshell so IFS="," never leaks into the caller.
70
74
  capture) thrum tmux capture "$AGENT" 2>&1 ;;
71
- key) thrum tmux key "$AGENT" "${EXTRA_ARGS[@]:-}" 2>&1 ;;
75
+ key) thrum tmux send "$AGENT" --keys "$(IFS=,; printf '%s' "${EXTRA_ARGS[*]:-}")" 2>&1 ;;
72
76
  esac
73
77
  }
74
78
 
@@ -77,7 +81,7 @@ run_ssh() {
77
81
  dest="$(ssh_destination)"
78
82
  case "$MODE" in
79
83
  capture) cmd="thrum tmux capture '$AGENT'" ;;
80
- key) cmd="thrum tmux key '$AGENT' ${EXTRA_ARGS[*]:-}" ;;
84
+ key) cmd="thrum tmux send '$AGENT' --keys $(IFS=,; printf '%s' "${EXTRA_ARGS[*]:-}")" ;;
81
85
  esac
82
86
  # Client-side expansion of REPO_PATH/cmd into the remote command string is
83
87
  # intentional -- that's how the resolved repo path and thrum invocation
@@ -29,7 +29,7 @@
29
29
  #
30
30
  # DELIBERATE SCOPE LIMIT: this script is MECHANICAL ONLY — capture, detect,
31
31
  # emit. It never auto-escalates and never auto-approves/denies a modal
32
- # (never calls `thrum tmux key`/`thrum send`). The persistent-watcher-
32
+ # (never sends keys or text — never calls `thrum tmux send`). The persistent-watcher-
33
33
  # archetype skill is explicit that judgment (approve/cancel/escalate/nudge)
34
34
  # happens in the watcher's OWN turn when it reads the wake message this
35
35
  # script's summary line triggers, not in a script. A bash script cannot
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: state-migration
3
- description: "Use when your agent-local thrum state is empty AND you have an existing State.md file in your agent directory - this is a ONE-TIME conditional onboarding that fires only on that exact combination. Do NOT use if thrum state already has entries (nothing to migrate) or if you have no State.md (brand-new agent, nothing to migrate). Triggers on: first session after the thrum state redesign lands, `thrum state list` returning empty while State.md exists on disk, being asked to migrate your personal state."
3
+ description: "Use when your agent-local thrum state is empty AND you have an existing State.md file in your agent directory - this is a ONE-TIME conditional onboarding that fires only on that exact combination. Do NOT use if thrum state already has entries (nothing to migrate) or if you have no State.md (brand-new agent, nothing to migrate). Triggers on - first session after the thrum state redesign lands, `thrum state list` returning empty while State.md exists on disk, being asked to migrate your personal state."
4
4
  ---
5
5
 
6
6
  # Migrating personal State.md into `thrum state`
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: using-the-dashboard
3
+ description: "Use when you want to present an interactive board to the human user (status, options, NEEDs with A/B/C picks), or when you see a dashboard mention and wonder what is actually usable today. Carries the CLI surface, the write/read discipline, and the live-vs-coming honesty table."
4
+ ---
5
+
6
+ # Using the Agent Dashboard
7
+
8
+ The Dashboard is an agent's way to put an **interactive board** in front of the
9
+ human user: typed widgets (status rows, cards, lists), optional HTML regions,
10
+ and **action-picks the user answers by clicking an option and optionally typing
11
+ nuance**. It replaces the "agent runs a TUI" pattern — it is out-of-band from
12
+ the messaging channel, so it never blocks messages and answers never scroll
13
+ away.
14
+
15
+ This is the COMMONS skill: role-agnostic surface + discipline. Role overlays
16
+ (`coordinator-dashboard-human-loop`, `brainstormer-dashboard-needs`) add the
17
+ role-specific loop on top. When an overlay and this skill conflict, the overlay
18
+ wins for role-specific behavior.
19
+
20
+ ## 🔴 Honesty table — what is usable TODAY
21
+
22
+ Check this BEFORE you promise the human anything. Stale promises are worse
23
+ than no dashboard.
24
+
25
+ | Capability | Status |
26
+ |---|---|
27
+ | `dashboard.set/get/show/clear/delete` RPCs (daemon-side) | **LANDED** on trunk (merge `6c7539236f83`); usable only against daemons BUILT after that merge — verify your daemon's build before relying on the RPCs |
28
+ | `thrum dashboard` CLI (set/show/get/update/delete/clear/results) | **LANDED** on trunk (merge `108511a0`); usable only against a `thrum` binary BUILT after that merge — verify your CLI's build before relying on these commands |
29
+ | Web UI Dashboard tab (where the human SEES a board) | **BUILT, NOT YET DEPLOYED** (E3 landed on trunk, merge `49712f87` — visible after the next deploy) |
30
+ | Answer round-trip (user clicks → message + nudge to you) | **BUILT, NOT YET DEPLOYED** (E4 landed on trunk, merge `ea95318a` — visible after the next deploy) |
31
+
32
+ **Consequence until the next deploy carries E3 and E4:** publishing a board
33
+ is STILL a no-op for the human on any box running a pre-E3/pre-E4 binary —
34
+ it renders NOWHERE there and, even where it renders, an answer cannot
35
+ return to you, even though the UI and answer-round-trip code now both exist
36
+ on trunk. Do NOT substitute a dashboard for a nudge or a direct message
37
+ unless you know the box you're talking to has deployed a build containing
38
+ BOTH E3 and E4. If the human cannot see the board or their answer cannot
39
+ reach you, the question DOES disappear — the exact failure the dashboard
40
+ exists to kill.
41
+
42
+ **Consequence, now that E2 has landed:** the `thrum dashboard ...` command
43
+ shapes below are runnable — but, per the E2 row above, only against a CLI
44
+ binary built after `108511a0`. Against an older binary, or if you're
45
+ calling the daemon directly, the raw RPCs (`dashboard.set` etc. over the
46
+ daemon socket) remain the fallback surface.
47
+
48
+ ## The board model
49
+
50
+ A dashboard is ONE structured document per agent: an envelope
51
+ `{v, agent_id, title, updated_at, rev, widgets[]}`. Six widget types:
52
+ `section` (labeled grouping), `stat-row` (label/value pairs),
53
+ `card` (title + markdown body), `list` (ordered rows),
54
+ `action-pick` (question + options + optional text — the interactive control),
55
+ `html` (sanitized rich region, authored from a FILE).
56
+
57
+ Semantics that matter to you as an author:
58
+
59
+ - **Append-only + rev'd.** Every change appends a new snapshot; `rev` increments.
60
+ Your `update` keeps a widget's position; `delete` removes it; `clear` wipes
61
+ the board. Cleared/deleted content is **GONE** — the board is ephemeral, and
62
+ the DURABLE record of any decision is the answer MESSAGES, not the board.
63
+ - **Widget cardinality (enforced):** action-pick ≤8, html ≤4, section ≤8,
64
+ card ≤24, stat-row ≤12, list ≤4, total ≤64. Lists have NO row cap.
65
+ - **Answer path:** when the user answers an action-pick, you get a **nudge**
66
+ and the answer lands as a thrum MESSAGE (payload type `dashboard.answer`,
67
+ carrying `question_id`, the option `pick`, optional `text`). You read
68
+ answers with `thrum dashboard results`, then flip the widget's status via
69
+ `thrum dashboard update` so the board shows it resolved.
70
+ - **User deletes are SILENT.** If a widget you posted disappears, the user
71
+ dismissed it — you are NOT nudged. Notice it on your next `show`; do not
72
+ repost it reflexively (the user saw it and removed it on purpose).
73
+ - **No dashboard yet is not an error.** `dashboard.get`/`dashboard.show`
74
+ succeed with `found: false` and an empty envelope when nothing has been
75
+ posted for you yet — do not treat that as a failure to recover from.
76
+ - **Concurrent writes are guarded by `expected_rev`, not merged.** If you pass
77
+ `expected_rev` and it no longer matches the board's current `rev`, the write
78
+ is rejected with `dashboard changed (rev %d, you expected %d) — re-read with
79
+ dashboard show and resubmit`. Fix: re-fetch the current document and
80
+ resubmit with the fresh `rev`. Omitting `expected_rev` skips the check
81
+ entirely (last-writer-wins) — fine for your own solo edits, risky if
82
+ something else writes the same board.
83
+
84
+ ## CLI surface (runnable now, against a `thrum` binary built after `108511a0`)
85
+
86
+ ```bash
87
+ # Publish/replace whole widgets (upsert by widget id — targets update in place)
88
+ cat > /tmp/board.md <<'EOF'
89
+ {"title": "My board", "widgets": [ ... ]}
90
+ EOF
91
+ thrum dashboard set --doc-file /tmp/board.md
92
+ thrum dashboard set --stdin < /tmp/board.md
93
+
94
+ # HTML regions come from FILES ONLY — never inline (backticks in shell strings EXECUTE)
95
+ thrum dashboard set --html-file /tmp/panel.html --html-id my-panel
96
+
97
+ # Read
98
+ thrum dashboard show # compact outline: what's on the board, what's PENDING/ANSWERED
99
+ thrum dashboard get <widget-id> # full widget JSON
100
+ thrum dashboard results # user answers (after your nudge), newest N selected, displayed oldest-first
101
+ thrum dashboard results --unanswered # only questions with no answer yet
102
+
103
+ # Mutate
104
+ thrum dashboard update <widget-id> --doc-file /tmp/new-widget.json # in place, keeps position
105
+ thrum dashboard delete <widget-id>
106
+ thrum dashboard clear # wipe the whole board (content is GONE)
107
+ ```
108
+
109
+ **Validation loop:** `set`/`update` either print `posted OK (rev N)` or fail
110
+ with one of three error shapes: a per-widget schema error naming the exact
111
+ widget id + field; a per-type cardinality error naming the widget TYPE + count
112
+ (e.g. "too many action-pick widgets: 9 (max 8)"); or the aggregate/total-cap
113
+ error, which names a CATEGORY TOKEN + count + max rather than a widget type
114
+ (e.g. "too many widgets (total_widgets): 65 (max 64)" — "total_widgets" is the
115
+ violation category, not a widget type: the per-type limits sum to 60 < 64, so
116
+ a doc breaching 64 always breaches some per-type limit too, and there is no
117
+ single offending widget type to name). On any of these: fix the file and
118
+ resubmit — do not recompute the whole board inside one invocation.
119
+
120
+ **Authoring rules with teeth:**
121
+
122
+ - Widget `id`s are load-bearing — stable across updates; `update`/`delete`
123
+ and the answer join key off them (action-picks also carry `question_id`).
124
+ - Text fields render as MARKDOWN (no raw HTML — that is the `html` widget's
125
+ job, sanitized). Keep compact fields single-line.
126
+ - HTML files: no script/style/iframe/event-handlers — the render side strips
127
+ them, but author clean anyway.
128
+
129
+ ## When to reach for the dashboard (vs a nudge vs a message)
130
+
131
+ - **Dashboard:** state the human will consume VISUALLY and possibly answer
132
+ interactively — rosters, queues of options, pipelines, NEEDs with discrete
133
+ choices. Persistent board the human checks on their own rhythm.
134
+ - **Nudge:** something needs attention NOW and is small (one line). The
135
+ dashboard does not pulse or blink; urgent + tiny = nudge.
136
+ - **Message:** conversation, anything needing prose back-and-forth, or anything
137
+ the record must keep forever (decisions live in messages).
138
+
139
+ Anti-pattern: posting a board INSTEAD of answering a direct question. The
140
+ dashboard is a surface you maintain, not a replacement for talking.
141
+
142
+ ## Refs to other skills
143
+
144
+ - `coordinator-dashboard-human-loop` — coordinator overlay: owning a board for
145
+ the human, the human-loop mandate
146
+ - `brainstormer-dashboard-needs` — brainstormer/researcher overlay: authoring
147
+ NEEDs as answerable action-picks
148
+ - `using-the-queue`, `using-thrum-state` — sibling agent-tied local surfaces
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: using-thrum-state
3
- description: "Use when reading or writing thrum state - checking your own agent-local current state at session start, updating your status/next-actions, reading a fleet fact like deploy state or topology before acting on it, citing a SHA or box fact in a decision, deciding whether a fleet row is fresh enough to trust, or responding to a freshness-sweep ping. Also use before writing any fleet row - deploy state, topology, worktree, agent pool, or a standing ruling - to confirm you are writing from a by-effect measurement, not a guess. Triggers on: citing a fleet fact, deploying, restarting a daemon, migrating schema, reconciling state at session start, receiving a stale-row ping."
3
+ description: "Use when reading or writing thrum state - checking your own agent-local current state at session start, updating your status/next-actions, reading a fleet fact like deploy state or topology before acting on it, citing a SHA or box fact in a decision, deciding whether a fleet row is fresh enough to trust, or responding to a freshness-sweep ping. Also use before writing any fleet row - deploy state, topology, worktree, agent pool, or a standing ruling - to confirm you are writing from a by-effect measurement, not a guess. Triggers on - citing a fleet fact, deploying, restarting a daemon, migrating schema, reconciling state at session start, receiving a stale-row ping."
4
4
  ---
5
5
 
6
6
  # Using Thrum State
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-thrum-pro",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Thrum multi-agent coordination plugin for Open Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",