@staix/agent-hub 0.12.25 → 0.12.27

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,19 @@
2
2
 
3
3
  Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the previous repository, archived on 2026-09-30 when this repository's history was rewritten; the one exception is the open smoke-check issue, formerly #12, which moved here as #1. Numbers in newer entries refer to this repository.
4
4
 
5
+ ## 0.12.27
6
+
7
+ - Refine the dashboard Office view with an intrinsic 12 px/unit scale, 1 px non-scaling hairlines and padded room cards in flat and isometric themes, and un-nest the desk More control into a separately reconciled sibling button so screen readers can reach it (#394).
8
+ - Shrink the floating pet window to a compact 150x150 panel with the original pixel art in deeper state colors and a configurable base palette (`pet.palette`, or 1/2/3/4 while the window is key: blue, teal, violet, ochre) with a matching rim line so it reads on light backgrounds, reveal the status pill and message composer only while the pointer is over the character, and drop the pill shadow (#391 follow-up).
9
+ - Split the local dashboard into cookie-backed views, compact task cards with shared dialog/column details, and exact-id attention badges; move character presentation to the standalone pet window (#391 Task A).
10
+ - Add an opt-in macOS floating pet window with built-in pixels, counts-only local state, a console message pencil, saved position and signed process ownership; keep terminal pet reads available to agent shells (#391 Task B).
11
+
12
+ ## 0.12.26
13
+
14
+ - Import claude-mem observations under a checkout-independent identity, so the same observation is one entry on every machine after sync however the checkouts differ; scan the source database project-aware with a persisted machine-local cursor (`--restart` re-scans), so unrelated projects cannot starve the requested one; reconcile legacy duplicate imports with `ahub memory dedupe` through append-only supersede operations (#380).
15
+ - Protect the targets of direct-child symlinks of a protected directory under every data-policy form (exact entry, `data/*`, `data/**`), with case-compatible on-disk root discovery and a streaming scan inside the shared 4096-entry bound: a dangling link covers its lexical target, an unresolvable link loop aliases nothing, a root whose discovery budget is exhausted contributes no aliases, and an unreadable protected root still fails closed (#385).
16
+ - State the document converter's project-scoped read guarantee and the bounded, cleared-when-full failure record precisely in code comments and docs (#386).
17
+
5
18
  ## 0.12.25
6
19
 
7
20
  - Preserve person-owned reservations through planner grooming, including same-value changes (#378).
package/README.md CHANGED
@@ -4,7 +4,7 @@ Native multi-agent hub for one developer's machine: Claude Code, Codex, Kimi Cod
4
4
  hub-owned local-LLM worker, with optional Qwen Code and Jcode support, collaborate as peers in independent project directories, with
5
5
  task-aware model routing (Switchyard) in front of a self-hosted gateway (OmniRoute).
6
6
 
7
- Status: 0.12.25, control protocol 16. Durable delivery records distinguish queued
7
+ Status: 0.12.27, control protocol 16. Durable delivery records distinguish queued
8
8
  work from uncertain execution. The [smoke checklist](docs/smoke.md) records
9
9
  verified paths and remaining prerequisites.
10
10
 
@@ -33,7 +33,7 @@ cd <your project> && ahub init && ahub up
33
33
  Or install the same version from GitHub:
34
34
 
35
35
  ```bash
36
- bun add -g github:STAIxBWLB/agent-hub#v0.12.25 && ahub setup
36
+ bun add -g github:STAIxBWLB/agent-hub#v0.12.27 && ahub setup
37
37
  ```
38
38
 
39
39
  The installed commands remain `ahub` and `agent-hub`.
@@ -28,7 +28,8 @@
28
28
 
29
29
  - `*` matches any characters within one path segment, `?` matches one character, and a whole-segment `**` matches zero or more segments. Matching includes dot names and folds case consistently with the existing exact policy. A matched directory protects its subtree. Directory task scopes are private if they can contain a future matching descendant, even before a file exists.
30
30
  - Examples: `pii.paths = ["data/**/*.csv"]` and `data.readonly = ["data/raw-*/**"]`. Up to 16 wildcard characters per pattern. Absolute paths, parent traversal, bracket classes, braces, negation and mixed `**suffix` forms are refused. Backslashes remain literal in exact entries but are refused in glob entries; no escape syntax is interpreted.
31
- - Match both declared spelling and real target. Wildcard directory/file aliases are discovered from compatible prefixes, including symlink aliases to future targets. Discovery is refreshed per decision, with no stale cached expansion. It reads at most 4096 directory entries and traverses at most 32 levels. An unreadable/dangling/cyclic/out-of-project alias or an exhausted discovery bound conservatively protects rather than declaring a path public/writable. No discovered name or policy body is logged.
31
+ - An exact directory entry (`data`), a single-segment glob (`data/*`) and a subtree glob (`data/**`) all protect targets of symlinks directly inside `data`, including file targets. Only this one level is aliased: `data/sub/link` does not protect its outside target under any of these forms, while paths through the protected tree remain protected lexically. Exact entries and trailing `**` matches scan immediate children once, without descending; `*` retains its existing matched-link resolution. Retargeted links are evaluated afresh on the next decision. A self-link such as `data/loop -> .` adds only the directory itself and does not trigger a recursive walk.
32
+ - Match both declared spelling and real target. Wildcard directory/file aliases are discovered from compatible prefixes, including symlink aliases to future targets. Discovery is refreshed per decision, with no stale cached expansion. Wildcard discovery and immediate alias scans share one per-call budget of 4096 directory entries; wildcard traversal is limited to depth 32. Existing wildcard discovery runs before the new direct-child scans, so these scans cannot consume its budget first. Immediate scans stream directory entry types and resolve only symlink children, with no descent. For exact entries and trailing-`**` roots, a dangling direct child protects its lexical target (including a future target), an ELOOP child is skipped, and a root whose scan cannot complete within the remaining shared budget contributes no child aliases, even if an earlier child matched. Unreadable matched roots still fail closed, including when earlier roots used up the budget. Lexical protection of the tree stays in force; tree depth and cycles below that one level are never traversed. `data/*` retains its existing matched-link failure behavior, and descending glob discovery (`data/**/*.csv`, `**/ß`) retains its existing fail-closed handling of unreadable/dangling/cyclic/out-of-project aliases and exhausted bounds. No discovered name or policy body is logged.
32
33
  - Managed local/Pi write/edit and private Tasks/facts use this same matcher. Existing hardlink refusal, post-approval recheck and filesystem-read-only command profile remain unchanged.
33
34
  - Claude's managed `ask-when-needed` hook receives `ask` for a write matching data.readonly. Reads and ordinary project writes retain their existing scoped decisions. The daemon evaluates policy locally only for the current managed PreToolUse ask-when-needed write grant and sends no pattern list to the hook. PostToolUse, unavailable/ask/never-ask modes and non-write tools perform no grant-policy discovery. Explicit native approval and `never-ask` remain the person's native authority; this is scoped auto-grant filtering, not a new Claude sandbox or containment claim.
34
35
  - The previous malformed-config fixture that refused every wildcard now refuses an unsupported bracket class; its rejection assertion is retained. New tests establish the deliberately added glob grammar, future paths, wildcard aliases/retargeting, private scopes, discovery failures and actual daemon hook replies. Native account/model consumption is unverified.
package/docs/memory.md CHANGED
@@ -196,21 +196,58 @@ ahub memory demote 1a2b3c4d # a long-term entry becomes short-term
196
196
  ahub memory forget 1a2b3c4d # gone from the index here, and on other machines once synced
197
197
  ahub memory supersede <old> <new> # the old one is dropped from briefs and recall
198
198
  ahub memory rebuild
199
+ ahub memory dedupe # inspect legacy claude-mem duplicate groups
200
+ ahub memory dedupe --apply # append supersede relations, never rewrite log bytes
199
201
  ```
200
202
 
201
203
  A forgotten long-term entry stays in the log file and in the sync remote's git history; removing it from there is a
202
- person's rewrite of that history.
204
+ person's rewrite of that history. Forgetting a claude-mem import forgets every known non-forgotten member of its tuple group (source, project, source timestamp and text), including hidden duplicates, so they cannot reappear when their replacement is forgotten.
203
205
 
204
206
  ## Importing from claude-mem
205
207
 
206
- `ahub memory import claude-mem [--db <path>] [--limit N]` (a person's) copies the hub's own notes from claude-mem
208
+ `ahub memory import claude-mem [--db <path>] [--limit N] [--restart]` (a person's) copies the hub's own notes from claude-mem
207
209
  (observations whose metadata names a `peer`: `hub_remember` and the hub's task notes) into long-term hub memory as
208
- `source: claude-mem`, for this project only (claude-mem files them under the repository's own name; a superproject's
209
- notes stay out, and two unrelated repositories with one name share it). Model answers from `ahub ask --remember`, text
210
- matching a PII pattern, text about a path the project's `pii.paths` makes private and secret-looking text are skipped. An
211
- imported note is placed in the graph by the paths and symbols in its text (claude-mem keeps no task proposal time).
212
- claude-mem's database is opened read-only and never changed; an observation always maps to the same entry, on any
213
- machine, so importing again adds nothing.
210
+ `source: claude-mem`, for this project only. The SQL filters by the repository's basename before applying the limit;
211
+ a superproject's notes stay out, while two unrelated repositories with one basename still share claude-mem's namespace.
212
+ Model answers from `ahub ask --remember`, PII-pattern text, text about a path made private by the project's `pii.paths`
213
+ and secret-looking text are skipped. The source database is opened read-only and never changed.
214
+
215
+ - Import graph nodes are derived from text only: no checkout existence check and no machine-local `local.deny`.
216
+ Built-in denylisted names, relative-path normalization, absolute/parent exclusion, sorting and the 64-node cap still apply.
217
+ The source's timestamp and the existing content hash stay unchanged. Matching observations on different checkouts now
218
+ produce the same id; old ids, references and content validation remain valid. The local tuple fallback keeps re-import
219
+ idempotent even for legacy ids (its known ceiling is that the tuple excludes title).
220
+ - A machine-local `memory/import-state.json` records the last fully handled row, including all skips. The cursor key
221
+ combines the source DB's real path, claude-mem project name and hub project key. The file uses mode 0600 and atomic
222
+ replacement; it never syncs. Unreadable or malformed state replays from zero, write failure logs a warning and preserves
223
+ the import result, and a source whose maximum candidate id for this project is below the cursor resets it (unrelated projects cannot mask a recreated source). Completed progress is saved even if a later
224
+ row throws. `--restart` starts from zero; use it after changing a PII policy to revisit rows skipped under the old policy.
225
+ - The default limit is 10000. JSON reports `read` (project-matched, peer-tagged SQL rows this run), `added`,
226
+ `skipped.model`, `skipped.pii`, `skipped.secret`, `cursor` (last handled id), and `more` (`read === limit`). Malformed
227
+ metadata, non-string peers and empty text count only in `read`; already imported rows add nothing. `more` asks for
228
+ another run, which can return zero rows when the last batch exactly filled the limit. There is no project-skip counter.
229
+ - Repository identity still depends on the local real path without an origin remote, and non-GitHub remotes spelled
230
+ differently can yield different project keys. These affect all hub memory and remain outside this import fix.
231
+
232
+ ### Legacy duplicate imports
233
+
234
+ `ahub memory dedupe [--apply] [--json]` is console-only, including the listing, because it shows unscreened note text.
235
+ It groups this project's non-forgotten claude-mem entries by `(source, project, at, text)`. Listing reports only actionable
236
+ groups, their members, heads and chosen target; `--apply` appends ordinary supersede operations in this machine's log.
237
+ Historical log bytes are never rewritten. Forget operates on the whole known tuple group.
238
+
239
+ - Heads have no current replacement. Existing replacement relations, including a person's distinct outside notes, stay
240
+ intact. If every head can reach a valid synced outside terminal (shared when any head is shared), the newest qualifying
241
+ terminal by `(at, id)` replaces all heads, even a single head. Otherwise multiple heads converge onto the smallest id
242
+ among the heads; zero heads or one head without a qualifying outside terminal require no writes. A refused supersede
243
+ is reported as an error. Re-running after convergence writes nothing.
244
+ - When `A -> B` already exists inside a group, dedupe connects another head `C -> B`, without trying the cyclic `B -> A`.
245
+ When `B -> X` and `C -> Y` name outside user replacements, `X` and `Y` both remain visible; a remaining head moves to
246
+ the newer qualifying terminal. A later outside replacement can make a previously settled group actionable again.
247
+ - Old or mixed extractor versions can still introduce duplicate ids. Run dedupe after syncing them. Two machines acting
248
+ on divergent prior relations can choose different targets; sync folds relations deterministically using the existing
249
+ latest-relation and cycle guards. If several group members remain visible, one more dedupe run converges them. New
250
+ same-version imports with stable project keys converge automatically in either sync order.
214
251
 
215
252
  ## Measuring it
216
253
 
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.12.25 and control protocol 16. Live verification
3
+ This guide describes ahub 0.12.27 and control protocol 16. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Command output
@@ -156,8 +156,8 @@ status read it says loading; later failures preserve the prior count with a noti
156
156
  Panels retain their existing full-board refresh and loading/error handling.
157
157
 
158
158
  The dashboard Task board shows the same shared public-board progress model,
159
- including state counts, waiting, stacked segments and a labeled four-stage track
160
- per row. Filtering rows does not filter overall progress. The existing signature guard
159
+ including overall state counts, waiting and stacked segments; each task detail
160
+ retains its labeled four-stage track. Filtering rows does not filter overall progress. The existing signature guard
161
161
  updates the live region only when progress changes. The stacked SVG fills the
162
162
  section width; its text legend carries every state count. Theme offers System,
163
163
  Light and Dark; it is applied before first paint and stored in a host-scoped loopback cookie
@@ -170,8 +170,8 @@ is tracked in `docs/smoke.md` and remains unverified until performed.
170
170
  The dashboard Office section (#264, phase 1 of 4) draws the same snapshot as a
171
171
  static flat office: one desk per peer in a stable slot (claude, codex, kimi, pi,
172
172
  local, then others by id; a peer going offline dims its desk and never moves
173
- another), rooms for the board columns in a fixed order (To Do, Work floor,
174
- Review, Done shelf as a count only), at most 8 cards per room with "+N more"
173
+ another), rooms for the board columns in a fixed order (To do, Work floor,
174
+ Review, Done as a count only), at most 8 cards per room with "+N more"
175
175
  filtering the task list to that state, and at most 12 desks with "+N peers".
176
176
  Work-floor cards sit at the owner's desk, marked for rework; a busy peer's task
177
177
  bubble names its task only when it has exactly one in progress. Every peer
@@ -1619,21 +1619,21 @@ section is what they run.
1619
1619
 
1620
1620
  Upgrade running projects with the target release's own coordinator. It accepts
1621
1621
  a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
1622
- 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.25), and only
1622
+ 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.27), and only
1623
1623
  a target on its own protocol, so the target's coordinator fits every supported
1624
1624
  source and carries every recovery fix released up to it. Protocol 8 and older
1625
1625
  (0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
1626
1626
  project directory, without replacing the global CLI first:
1627
1627
 
1628
1628
  ```bash
1629
- bunx --package @staix/agent-hub@0.12.25 ahub upgrade --to 0.12.25 --dry-run
1630
- bunx --package @staix/agent-hub@0.12.25 ahub upgrade --to 0.12.25 --yes
1629
+ bunx --package @staix/agent-hub@0.12.27 ahub upgrade --to 0.12.27 --dry-run
1630
+ bunx --package @staix/agent-hub@0.12.27 ahub upgrade --to 0.12.27 --yes
1631
1631
  ```
1632
1632
 
1633
1633
  | Running now | Coordinator to use |
1634
1634
  | --- | --- |
1635
1635
  | 0.6.x (protocol 9) | the target's, through `bunx` as above |
1636
- | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.25 (protocol 16) | the target's, through `bunx` as above |
1636
+ | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.27 (protocol 16) | the target's, through `bunx` as above |
1637
1637
  | any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
1638
1638
  | 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
1639
1639
 
@@ -1665,14 +1665,14 @@ projects first:
1665
1665
 
1666
1666
  ```bash
1667
1667
  ahub restart --dry-run
1668
- ahub upgrade --to 0.12.25 --dry-run
1668
+ ahub upgrade --to 0.12.27 --dry-run
1669
1669
  ```
1670
1670
 
1671
1671
  Apply only after reviewing the plan:
1672
1672
 
1673
1673
  ```bash
1674
1674
  ahub restart --yes
1675
- ahub upgrade --to 0.12.25 --yes
1675
+ ahub upgrade --to 0.12.27 --yes
1676
1676
  ahub recovery status [<operation-id>]
1677
1677
  ahub recovery resume [<operation-id>]
1678
1678
  ahub recovery abort [<operation-id>]
@@ -2622,7 +2622,7 @@ Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or
2622
2622
 
2623
2623
  # Rich-document text projections (#259)
2624
2624
 
2625
- - Configure text_projection only in an untracked machine-local file, with a separate {input} argv argument. Facts uses bounded read-only/no-network conversion from a scratch directory, where the converter reads only its input snapshot and its own program and script files (no other project file, even under `local.read_allow`), and a content/config cache; failure keeps the prior file view and logs a bounded notice, and a conversion that failed with its full deadline is not retried for the same bytes and command for the life of the hub process (a bounded in-memory record) (#382).
2625
+ - Configure text_projection only in an untracked machine-local file, with a separate {input} argv argument. Facts uses bounded read-only/no-network conversion from a scratch directory, where the converter reads only its input snapshot and its own program and script files (no other project file, even under `local.read_allow`), and a content/config cache; failure keeps the prior file view and logs a bounded notice, and a conversion that failed with its full deadline is not retried for the same bytes and command while its entry lives in the failure record, which is in memory, bounded at 1024 entries, and cleared when full or at restart (#382).
2626
2626
  - Raw change identity and projected presentation stay separate; private paths never enter conversion. Schema, limits and current surface scope: [text projections](text-projection.md).
2627
2627
 
2628
2628
  # Task catalog guidance (#295)
@@ -2879,20 +2879,36 @@ nor drawing a character changes authority, config files, task state or polling.
2879
2879
  Explicit `ahub pet` and text status use the configured line face; prompt tokens
2880
2880
  remain character-independent. Native glyph width and tmux are unverified.
2881
2881
 
2882
- ## Dashboard attention character (#280)
2882
+ ## Dashboard views and attention (#391, replaces the #280 in-page character)
2883
+
2884
+ Top buttons select Board, Office, Stream, Agents, Message, Memory or Settings
2885
+ within the same page. The selected view and board column focus use the shared
2886
+ host-scoped cookie helper; the URL fragment remains reserved for the session
2887
+ ticket. Sections are hidden rather than fetched separately. Projects and
2888
+ Benchmarks live under Agents alongside peers, approvals and usage budgets.
2889
+ Office mode stays an explicit Off/Flat/Isometric preference inside Office.
2890
+
2891
+ Board cards show id/title, priority, owner dot and overdue/check markers.
2892
+ Click or Enter opens a native detail dialog with the full stage/dependency,
2893
+ release/date, SDLC, detail and GitHub content plus existing Pull/Return/Assign
2894
+ controls. Snapshot changes refresh the dialog; project reset or a task leaving
2895
+ the snapshot closes it. Expand on a column shows one wide list of full details;
2896
+ Back returns to compact cards. State/agent/release filters still apply.
2897
+
2898
+ The shared pure needs model now drives the nav attention badge and existing
2899
+ action-count tab title. Agents has a live pending-approval count even when
2900
+ attention metadata is unavailable. Activation switches to the matching view
2901
+ and focuses only the exact visible approval/task id; missing or filtered rows
2902
+ receive fixed structural guidance, with no peer or first-row fallback. No
2903
+ workflow action is issued and no filter is changed by attention navigation.
2904
+ Invalid/legacy attention hides its badge and restores the base title.
2905
+
2906
+ The in-page pet, its SVG and character/pixel payload are removed. The standalone
2907
+ macOS window owns character presentation. Existing snapshot poll, session,
2908
+ request types and CSP hashes remain; no route, request or timer is added.
2909
+ Browser screenshots/CSP-console verification are separate manual evidence on
2910
+ the implementation PR; VM/HTTP fixtures do not establish human visual results.
2883
2911
 
2884
- The dashboard header draws the configured built-in character independently of
2885
- Office. It uses the same structural needs model as status and the console. The
2886
- accessible button names the character, state and action/watch counts; the tab
2887
- title shows the action count. Activating the button scrolls and focuses the exact
2888
- visible approval/task/peer row, or its related section with fixed id-only guidance
2889
- when the row is filtered or unavailable. It performs no action and changes no
2890
- filters. Legacy snapshots without attention keep the widget hidden.
2891
-
2892
- Only a live approval alternates the two waiting poses once per second through
2893
- CSS. `pet.blink:false` and reduced motion retain a static pose. No new timer,
2894
- request, image protocol or character-file load is added. Project reset clears
2895
- the widget and title. Native/browser visual validation remains unverified.
2896
2912
  With mini/full characters, active console notices remain visible away from the
2897
2913
  character: panel body rows show the bounded wrapped notice, and stream mode
2898
2914
  prints each fresh notice once through the existing local stream. Footer prose
@@ -2943,3 +2959,43 @@ A later issue lookup or profile-preparation failure also returns the IDs confirm
2943
2959
  Release cut audits return exact counts with at most 32 example IDs per category; the dashboard states how many IDs are shown when truncated. Scope grouping and indexed approval dates avoid scanning each task history for every release. Full audit pagination remains a future extension.
2944
2960
 
2945
2961
  Release cards include task states in their refresh key, so completion of an outside prerequisite updates blocked counts even when the release and audit preview are unchanged.
2962
+
2963
+ ## Standalone macOS pet window (#391)
2964
+
2965
+ From a person's terminal, `ahub pet --window` starts one floating, transparent
2966
+ JXA panel per project; `ahub pet --window --close` closes it. Other platforms
2967
+ print unsupported and retain `ahub pet` / `ahub pet --short`. An agent-marked
2968
+ shell may read the terminal pet but cannot start or close the window.
2969
+
2970
+ Set `pet.window: true` in `.agenthub/config.json` or `config.local.json` to start
2971
+ it with `ahub up`. A window-only local opt-in preserves inherited `pet:false`
2972
+ for terminal/status/dashboard presentation; explicit local character options
2973
+ retain their existing presentation override behavior. The
2974
+ window uses the configured built-in character, full pixel frame and blink
2975
+ choice. Its body opens `ahub ui`; the pencil opens an inline console message
2976
+ box (Enter sends through `ahub say`, Esc cancels). Drag the body to move it;
2977
+ `pet-window-pos.json` remembers the position, with an on-screen fallback if a
2978
+ saved display has disappeared. The panel joins all Spaces without a Dock icon.
2979
+ The 150x150 panel keeps the status pill and message composer hidden until the
2980
+ pointer is over the character. Deep state colors and a dark rim shadow keep the
2981
+ pixel silhouette readable on light backgrounds. `pet.palette` selects the base
2982
+ color: teal (default, b), blue (a), violet (c) or ochre (d), each with its own
2983
+ rim line; the waiting/blocked/review/offline colors stay fixed. While the
2984
+ window is key, 1/2/3/4 switch blue/teal/violet/ochre and save the choice.
2985
+ From a terminal, `ahub pet --palette <blue|teal|violet|ochre>` writes the
2986
+ choice to `pet.palette` in `.agenthub/config.local.json`.
2987
+ While working, the pixel art bobs in a 0.7-second stepped CSS loop; reduced-motion preferences keep it still.
2988
+
2989
+ A detached Bun host refreshes `pet-view.json` once per second using `readPet`
2990
+ and `loadPetConfig`. The file contains only state/counts/character/blink and
2991
+ built-in pixels. It uses no dashboard session or HTTP listener. An unavailable
2992
+ hub displays Offline for two seconds, then the panel and host exit. Closing
2993
+ JXA exits the host too. `pet-window.pid` holds the host's process signature;
2994
+ an uncertain owner is retained rather than signalled. A stale owner requires
2995
+ an explicit `--close` before restarting; a malformed pidfile requires operator
2996
+ inspection. A send failure is unconfirmed: check the stream before retrying.
2997
+
2998
+ Native transparency/floating and the bridge were verified in the macOS spike.
2999
+ Real keystrokes, dragging and persistence, Spaces, message delivery and body
3000
+ click need the manual PR checklist before merge; automated fixtures do not
3001
+ establish those native results.
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.12.25
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.12.27
21
21
  ahub setup
22
22
  ```
23
23
 
package/docs/security.md CHANGED
@@ -109,7 +109,7 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
109
109
 
110
110
  - `ahub ui` uses the authenticated console control connection to start an ephemeral HTTP listener on `127.0.0.1`. No UI listener exists before that command. The control and Codex proxy ports continue to refuse every `Origin` header.
111
111
  - The daemon issues a cryptographically random, single-use bootstrap ticket valid for 60 seconds. The CLI opens the dashboard with that ticket in the URL fragment, never a query string or the control token. The static page removes the fragment immediately and exchanges it with a same-origin POST. Tickets are consumed once; expired tickets cannot create sessions.
112
- - The exchange creates a separate random session, valid for one hour without renewal, in an `HttpOnly; SameSite=Strict; Path=/` cookie named for this listener's port. Sessions and tickets live only in memory and die with the daemon. The page itself keeps one more cookie, `agent-hub-theme` (issue #246): script-set, `SameSite=Strict`, kept for a year for the whole `127.0.0.1` host, and read back only as one of `system`, `light` or `dark`. A second script-set cookie, `agent-hub-office` (issue #264), keeps the Office view choice under the same attributes and is read back only as `off`, `flat` or `isometric`. HTTP is loopback-only; the cookie is not a substitute for the origin checks.
112
+ - The exchange creates a separate random session, valid for one hour without renewal, in an `HttpOnly; SameSite=Strict; Path=/` cookie named for this listener's port. Sessions and tickets live only in memory and die with the daemon. The page itself keeps one more cookie, `agent-hub-theme` (issue #246): script-set, `SameSite=Strict`, kept for a year for the whole `127.0.0.1` host, and read back only as one of `system`, `light` or `dark`. A second script-set cookie, `agent-hub-office` (issue #264), keeps the Office view choice under the same attributes and is read back only as `off`, `flat` or `isometric`. The #391 view split adds two more script-set cookies under the same attributes: `agent-hub-view`, read back only as one of the seven view names, and `agent-hub-column-focus`, read back only as one of the five board columns or empty. HTTP is loopback-only; the cookie is not a substitute for the origin checks.
113
113
  - Every request must have the exact listener Host. Every data or action request, including the ticket exchange and snapshot polling, must be POST with the exact listener Origin and JSON content type. Foreign and missing origins, missing or expired sessions, oversized bodies and unknown actions are rejected. There is no CORS support. The only unauthenticated GET is the fixed, data-free HTML shell. No files or paths are served dynamically.
114
114
  - Responses are non-cacheable, cannot be framed, suppress referrers, and use a content security policy restricting scripts/styles to the shipped inline content and connections to this origin. Browser text is rendered with textContent, never interpreted as HTML.
115
115
  - The browser receives a bounded redacted event stream, peer states and queue counts, public task views, budget windows, pending approvals, the settings rows (closed values and ids, the file each comes from) and each startable peer's start plan (issue #269), benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text, and the office view's optional fields (issue #264): each peer's open turn (start time, attributed task id, attribution kind), a `checking` flag on tasks whose check is running, and warning `links` of overlapping, conflicting or cohort tasks (task and peer ids only, never paths, symbols or reasons; a PII task never joins a link, and a private or PII-attributed turn exposes no task id). Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted.
@@ -226,11 +226,14 @@ owner/reviewer ids, approval ids and numeric times, peer delivery states and
226
226
  budget pause times. It is assembled from the same allowlisted input as status;
227
227
  task text, plans, approval prose, error text and paths are excluded. This input
228
228
  is supplied only inside console-authorized dashboard snapshots, never ordinary
229
- status replies or status.json. The pet creates trusted inline SVG pixel nodes
230
- from built-in literal frames with existing theme colours, without HTML insertion,
231
- inline handlers, style attributes or external assets. Existing CSP hashing,
232
- headers, origin and session authorization remain in force. Clicking scrolls and
233
- focuses only; it cannot grant permissions or invoke a mutation.
229
+ status replies or status.json. The #391 dashboard projects this input into nav
230
+ and tab-title counts; the in-page pet and its character/pixel injection are gone.
231
+ Attention navigation switches a local view and focuses an exact visible row id,
232
+ with structural guidance when missing. It cannot grant permissions or invoke a
233
+ mutation. Compact-card detail/dialog controls retain the existing authenticated
234
+ workflow actions and project/instance fencing. Existing CSP hashing, headers,
235
+ origin and session authorization remain in force; data uses text/DOM nodes,
236
+ without HTML insertion, inline handlers or external assets.
234
237
 
235
238
 
236
239
  ## Hub-owned structured memory (#258)
@@ -251,3 +254,28 @@ refused; unknown visibility proceeds with a warning for the configured remote.
251
254
  Short-term segments, machine identity and the derived index are excluded from
252
255
  sync. Suggested graph edges are off by default and are not evidence until a
253
256
  person confirms them. Operational details and limits: [hub memory](memory.md).
257
+
258
+ ## Standalone pet window (#391)
259
+
260
+ The macOS window reads local files only. Its detached host reuses `readPet`,
261
+ including signed-daemon liveness and recovery-lock handling, and publishes an
262
+ atomic counts-only `pet-view.json` (state, act/watch, known character, blink,
263
+ palette from the closed list, built-in pixel frame). It excludes item ids,
264
+ task titles/text, refs, paths,
265
+ notices and approval prose. Missing or unconfirmed status produces only
266
+ `{offline:true}`. Process and position files are separate from this view.
267
+
268
+ The local WKWebView has a deny-by-default CSP, with only its shipped inline
269
+ script/style allowed and network connections forbidden. Text never becomes
270
+ HTML or executable source. The retained JXA bridge accepts fixed drag/send/
271
+ palette/open/close operations; `NSTask` invokes the host-supplied Bun/CLI/project argv
272
+ and appends `say`, `--` and a single message argument, or `pet`, `--palette`
273
+ and one closed palette name. It does not evaluate a
274
+ shell string. Message text is never logged by the window/host; command stdout
275
+ and stderr go to the null device. `ahub say` keeps its existing console-message
276
+ authority and recording behavior, and `ahub ui` opens its existing session.
277
+ The CLI restricts `pet --window` (including close) to console identity, while
278
+ plain terminal pet reads and `pet --palette` remain available to agent shells;
279
+ the palette write merges only the closed name into `.agenthub/config.local.json`,
280
+ validated and replaced by temp file and rename. Signed pidfiles
281
+ fence instance ownership and close; unknown owners are never signalled.
@@ -710,7 +710,11 @@ fragment ticket for an HttpOnly, SameSite=Strict browser session. The control li
710
710
  retains its blanket Origin refusal. The browser receives no control token.
711
711
 
712
712
  One static HTML file, without a build step or runtime dependency, polls public
713
- snapshots once per second. The latest 200 redacted bus events are retained only
713
+ snapshots once per second. #391 Task A keeps this `/` surface and poll while
714
+ splitting its sections into Board/Office/Stream/Agents/Message/Memory/Settings
715
+ views with a cookie-backed selection. Compact task cards open a native dialog;
716
+ one `taskDetail` function renders both the dialog and expanded-column cards.
717
+ Filters, exact-id attention navigation and existing workflow controls remain. The latest 200 redacted bus events are retained only
714
718
  once the listener starts. The API exposes only permission answers, peer
715
719
  pause/resume, console messages, task proposal and assignment. All task changes
716
720
  use `Tasks`; budget pauses keep the terminal's override requirement.
@@ -731,7 +735,9 @@ project registry and adds explicit project selection, authenticated instance
731
735
  ownership and a separate optional dashboard manager. Runtime data and routing
732
736
  remain per project. Native memory aliases remain compatible and shared.
733
737
  Control protocol 7 adds project/instance identity and console-only dashboard
734
- snapshot/action forwarding. Existing project-local UI security rules still apply.
738
+ snapshot/action forwarding. Existing project-local UI security rules still apply. #391 Task A changes only client view selection;
739
+ project/instance fencing still invalidates open task details and stale controls.
740
+ No additional project route, request or timer is introduced.
735
741
 
736
742
 
737
743
  ## Amendment: externally managed Ollama MLX (issue #51)
@@ -2424,7 +2430,7 @@ the queue.
2424
2430
 
2425
2431
  ## Amendment: bounded data policy globs (#259)
2426
2432
 
2427
- pii.paths and data.readonly accept exact paths/subtrees and *, ? and whole-segment ** patterns. Validation keeps 64 entries, 200 characters each and at most 16 wildcard characters. Unsupported bracket/brace/negation/escape syntax, absolute paths and parent traversal refuse. Matching folds case, protects matched subtrees and treats parent task scopes as private when a future matching descendant is possible. Resolve both declared and real paths; wildcard alias discovery is uncached and bounded to 4096 directory entries and 32 levels. Resolution/discovery failure conservatively matches an active policy. Managed local/Pi tools retain hardlink and read-only command protections. Only an authenticated pre-tool ask-when-needed write grant evaluates the daemon-side read-only matcher; other phases/modes perform no grant-policy discovery. Protected grants are withheld without exposing policy paths; explicit approval/never-ask remain native authority, with no new sandbox claim. No account/research pilot is implied.
2433
+ pii.paths and data.readonly accept exact paths/subtrees and *, ? and whole-segment ** patterns. Validation keeps 64 entries, 200 characters each and at most 16 wildcard characters. Unsupported bracket/brace/negation/escape syntax, absolute paths and parent traversal refuse. Matching folds case, protects matched subtrees and treats parent task scopes as private when a future matching descendant is possible. Resolve both declared and real paths; wildcard alias discovery is uncached and bounded to 4096 directory entries per decision and 32 levels. Wildcard resolution/discovery failure conservatively matches an active policy. Exact entries and trailing-** roots also protect the targets of symlinks directly inside the matched directory, one level only (#385): a dangling child protects its lexical target, an ELOOP child is skipped, a root whose scan does not complete within the shared budget contributes no child aliases, and an unreadable root matches; * keeps its matched-link resolution. Managed local/Pi tools retain hardlink and read-only command protections. Only an authenticated pre-tool ask-when-needed write grant evaluates the daemon-side read-only matcher; other phases/modes perform no grant-policy discovery. Protected grants are withheld without exposing policy paths; explicit approval/never-ask remain native authority, with no new sandbox claim. No account/research pilot is implied.
2428
2434
 
2429
2435
  ## Amendment: custom lifecycle data (#257)
2430
2436
 
@@ -2678,21 +2684,21 @@ C/dumb/linux/unknown locale surfaces use ASCII even when blocks were requested.
2678
2684
  Body foreground is uncoloured; only the Gyul leaf uses an existing accent tone.
2679
2685
  Native terminal/font/tmux proof remains a separate verification leg.
2680
2686
 
2681
- ## Shared dashboard attention (#280, phase 4)
2687
+ ## Shared dashboard attention (#280, replaced by #391 Task A)
2682
2688
 
2683
- The existing authenticated UI snapshot receives an optional allowlisted attention
2684
- input and enabled/character/blink presentation fields. Status wire/file callers
2685
- retain their phase3 summary/items projection and never receive this input. The
2686
- page invokes the same injected needs function on this body-free input, renders
2687
- the selected built-in full block frame as SVG pixels and exposes state/counts
2688
- through an accessible button. Its click only reaches an exact visible list row
2689
- or the relevant heading with fixed structural guidance; no action request occurs.
2689
+ The authenticated snapshot retains optional body-free needs input. The same
2690
+ injected pure needs function drives global nav action/watch counts and the
2691
+ existing tab-title action count; status/file readers retain their summary/items
2692
+ projection. The Agents button separately shows live pending approvals. The
2693
+ in-page character, SVG, character frames and pixel injection are removed; the
2694
+ standalone #391 Task B window owns that presentation.
2690
2695
 
2691
- DOM writes and title counts follow the derived signature, not every poll. A
2692
- waiting pulse uses CSS transform/opacity only, with a static reduced-motion and
2693
- blink-off escape. There is no extra timer, request, route or authority; legacy
2694
- or invalid snapshots make attention unavailable. Project changes clear stale
2695
- attention and restore the title. The existing CSP/header policy is unchanged.
2696
+ Attention activation switches views and selects only the exact visible
2697
+ approval/task id. Default/delivery/budget items without a visible exact target
2698
+ show their related heading and fixed id-only guidance, never a peer or first-row
2699
+ fallback. Filters and workflow state are unchanged. Signature guards suppress
2700
+ unchanged DOM writes, invalid legacy input stays unavailable, and project
2701
+ changes reset attention and close stale dialogs. CSP/header policy remains.
2696
2702
 
2697
2703
  ## Amendment: hub memory, tiers and graph (#258, phase 1)
2698
2704
 
@@ -2710,20 +2716,12 @@ and rebuilds; the dashboard's Memory section is read-only inside the existing CS
2710
2716
  long-term tasks with leases (decision 9's amendment), claude-mem import and model-suggested edges follow in later phases.
2711
2717
 
2712
2718
 
2713
- ## Dashboard attention projection (#280, phase 4 in progress)
2719
+ ## Dashboard attention projection (#280, superseded by #391)
2714
2720
 
2715
- The existing dashboard snapshot supplies optional allowlisted needs input and
2716
- known presentation choices; normal status/file payloads do not gain that input.
2717
- The header independently projects the shared pure needs function into literal
2718
- built-in SVG pixels and an accessible state/count label. Unchanged state and
2719
- choices do not rewrite pet DOM. Clicks navigate only to exact visible rows or
2720
- related headings, never issue actions or alter filters. Missing/invalid legacy
2721
- input hides the widget. Project resets clear retained navigation and title.
2721
+ #391 Task A replaces the in-page pixel widget with nav/title counts as defined
2722
+ in Shared dashboard attention above. Native character presentation is the
2723
+ standalone Task B window; browser attention uses no character data or frames.
2722
2724
 
2723
- The two approved waiting frames alternate in CSS once per second only while
2724
- a live approval exists. Reduced motion and blink-off remain static. Every other
2725
- state stays still. No timer/request or new asset dependency is introduced;
2726
- existing header policy and computed CSP hashes apply.
2727
2725
  ## Amendment: hub memory between machines and long-term tasks (#258, phase 2)
2728
2726
 
2729
2727
  Decision 9 is amended for memory only: the memory directory is a git repository that tracks `log/*.jsonl` and syncs
@@ -2830,3 +2828,54 @@ session instead of assuming that an older extension provides the fence.
2830
2828
  Prompt lineage lasts until logical `agent_settled`: native retry, compaction or
2831
2829
  continuation starts inside that run retain the same command id. A later personal
2832
2830
  run has no hub command lineage.
2831
+
2832
+ ## Amendment: standalone attention window (#391 Task B, supersedes #280 dashboard pet)
2833
+
2834
+ The #280 phase-4 dashboard pet is replaced by an optional standalone macOS JXA
2835
+ window. Task B adds the window first; #391 Task A removes the existing in-page
2836
+ character and retains shared attention counts/navigation. Terminal characters
2837
+ and read-only `ahub pet` behavior remain.
2838
+
2839
+ `ahub pet --window` / `--window --close` are console-identity commands;
2840
+ `pet.window:true` opts into startup from `ahub up`. Non-macOS reports unsupported.
2841
+ One detached Bun host per project holds a signed pidfile in stateDir, reads
2842
+ `readPet` and `loadPetConfig` every second and atomically writes state, act/watch,
2843
+ character, blink and the full built-in `petPixels(CHARACTERS[...])` frame. No
2844
+ item ids/text/titles or external image assets are included. Unavailable status
2845
+ writes exactly `{offline:true}`; JXA displays Offline for two seconds, exits,
2846
+ and the host follows. A closed/crashed JXA also ends the host. A stale owner
2847
+ requires explicit close; an unconfirmed owner is retained.
2848
+
2849
+ `osascript -l JavaScript` creates an accessory, borderless nonactivating
2850
+ NSPanel subclass that can become key, uses NSFloatingWindowLevel and
2851
+ canJoinAllSpaces | stationary, and embeds a transparent local WKWebView. Its
2852
+ one-second NSTimer loads the atomic view and calls `update(json)` with an
2853
+ explicit no-op completion function (a JS null completion block crashes on the
2854
+ verified macOS). The fixed bridge moves/saves position, makes the pencil's
2855
+ inline text box key, sends a console message through NSTask argv to `ahub say`,
2856
+ opens the dashboard through `ahub ui`, or closes. Enter sends; Esc cancels.
2857
+ No shell evaluation, new dependency, compile step, HTTP listener or network
2858
+ request is added. Message contents are not logged by this surface.
2859
+ Working animates only the pixel SVG through a 0.7-second stepped CSS bob, disabled by prefers-reduced-motion; no frame, timer or view-file change is added.
2860
+
2861
+ The repaired Mac spike verified a transparent floating key panel and both
2862
+ bridge directions. Real input/drag/Spaces and end-to-end stream delivery remain
2863
+ manual acceptance evidence on the implementation PR, not inferred from the
2864
+ spike's process metrics or automated fixtures.
2865
+
2866
+ ## Amendment: dashboard views and task detail (#391 Task A)
2867
+
2868
+ The existing page has one nav button per view, with `aria-current`; sections
2869
+ carry `data-view` and selection toggles only `hidden`. A shared cookie helper
2870
+ stores theme, Office mode, selected view and optional board column focus.
2871
+ The one-time ticket keeps exclusive use of the URL fragment. Projects and
2872
+ Benchmarks join peers, approvals and budgets under Agents.
2873
+
2874
+ Compact task cards retain drag/drop, id/title, priority, owner dot and
2875
+ check/overdue markers. Click/Enter opens a native `dialog` via `showModal()`.
2876
+ `taskDetail` supplies full current public content and existing controls to that
2877
+ dialog and to a focused column's one-wide-list cards. Each snapshot updates the
2878
+ open detail; disappearance/project reset closes it. Back restores compact mode,
2879
+ and state/peer/release filters remain active in focus. Attention targets exact
2880
+ visible row ids with heading guidance when absent. No new route, API request,
2881
+ timer, source text/ref/history projection or external asset is introduced.