@passioncode-ai/fabric-agent-adapter 0.78.0 → 0.80.2

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +14 -7
  3. package/bin/fabric-agent-adapter.js +26 -4
  4. package/package.json +1 -1
  5. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  6. package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +4 -4
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +42 -7
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +3 -3
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +12 -4
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +23 -8
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +6 -2
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +2 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -2
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/README.md +67 -0
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/dashboard_service.py +252 -0
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.mjs +168 -0
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.py +179 -0
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/README.md +57 -0
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.mjs +181 -0
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.py +237 -0
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +4 -4
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +11 -6
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +42 -7
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +6 -2
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +12 -1
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +26 -3
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -2
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +14 -4
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +6 -2
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +44 -3
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +12 -4
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +7 -5
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +10 -5
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -0
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +15 -6
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +3 -2
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +37 -5
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +48 -5
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +9 -3
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -2
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +344 -0
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +8 -2
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -0
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +55 -14
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +8 -1
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +16 -7
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +4 -2
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +97 -3
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +50 -8
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +49 -3
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +9 -2
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +15 -2
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -2
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +358 -0
  73. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
  74. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
  75. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +17 -7
  76. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +6 -2
  77. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +24 -0
  78. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +110 -16
  79. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +12 -1
  80. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +543 -37
  81. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -1
  82. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +12 -5
  83. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +4 -4
  84. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +42 -7
  85. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
  86. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
  87. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +13 -4
  88. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +32 -0
  89. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/working-rules.md +54 -0
  90. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +12 -4
  91. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
  92. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +27 -26
  93. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +4 -4
  94. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/cleanup.md +30 -0
  95. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +59 -38
  96. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +52 -26
  97. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/remote-sessions.md +75 -0
  98. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +28 -13
  99. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/task-context.md +58 -0
  100. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/waits.md +36 -0
  101. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +14 -0
  102. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +169 -46
  103. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +146 -19
  104. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +78 -3
  105. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +9 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,261 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.80.2 (2026-11-12)
4
+
5
+ 0.80.1 was tagged and never published: its `windows` job (run 43212508702) failed one test, so `release` and `publish`
6
+ were skipped. 0.80.2 is 0.80.1 with that test fixed, and is the release that ships 0.80.0's changes. No module changes.
7
+
8
+ ### Fixed
9
+
10
+ - `test_the_launchers_folder_is_macos_only` compared the folder `status` returns, written with the runner's own path
11
+ separators, with a POSIX string; it compares it with `str(Path(...))`.
12
+
13
+ ## 0.80.1 (2026-11-12)
14
+
15
+ 0.80.0 was tagged and never published: its release run (43209105658) failed `floors`, `python` and `windows` on two
16
+ tests of 0.80.0's own (FAA-239, FAA-240) that held only on macOS, so `release` and `publish` were skipped. 0.80.1 is
17
+ 0.80.0 with the two tests fixed, and is the release that ships it. No module changes.
18
+
19
+ ### Fixed
20
+
21
+ - `test_a_copy_outside_the_launchers_folder_is_named` wrote `hub.json` where the runner's own platform keeps it while
22
+ `status` read darwin's; it writes it where darwin keeps it.
23
+ - `test_the_launchers_folder_is_macos_only` set a Windows temporary folder as a darwin path, which is not absolute
24
+ there; it uses a POSIX path.
25
+
26
+ ## 0.80.0 (2026-11-12)
27
+
28
+ Wave 2 and the first part of wave 3 of the backlog sweep of 2026-11-12. The contract pin stays `18f8d487`. A new kit
29
+ module, `fabric_load` and `fabric-load.mjs` (layer L3). Kit modules change: `fabric_secrets`, `fabric_peer_owner`
30
+ (`1.1.0+kit.4`), `fabric_connections`, `fabric_descriptor`, `fabric_context`, `fabric_supervisors`, `fabric_interop`,
31
+ `fabric_hub`, `fabric_board_slots`, `fabric_board_session`, `fabric_http`, `fabric_usage`, the facade `fabric_service`,
32
+ and their Node twins where they have one.
33
+
34
+ ### Added
35
+
36
+ - **Three working rules in working-with-fabric and in a new agent's template** (the contract owner's board request
37
+ `bfc0e14e`, relaying the operator's words of 2026-11-12): the task's context is settled before the first act that
38
+ crosses contexts (`personal`, `org:<orgId>`), asked once when no source settles it, recorded in the handoff and never
39
+ posted to a board thread, following contract CT-292's draft (TC-1 to TC-6) until its DEC lands, the one exception to
40
+ "do not ask" (`references/task-context.md`, FAA-299); a wait is not a stop: each wait has its condition, owner, next
41
+ action and watcher, `fabric_heartbeat` carries `waitingOn`, a turn ends with one line per open wait
42
+ (`references/waits.md`, FAA-300); clean up after yourself: merged worktrees and branches, `git fetch --prune`, unique
43
+ commits archived under `refs/archive/<date>/`, `git ls-files` checked before a folder goes (`references/cleanup.md`,
44
+ FAA-301). creating-fabric-agents carries the three in `references/working-rules.md`. Six scenario evals and
45
+ `test/test_working_rules_prose.py`.
46
+ - **A service install refuses a port another OS account listens on** (FAA-225, its port part), read with
47
+ `fabric_peer_owner`; a port of this account, the system or nobody, or an unreadable table, still passes. The claim
48
+ file waits on contract DEC-0160.
49
+ - creating-fabric-agents: an agent on its own machine refuses to start where Observatory's `placement.json` places it
50
+ elsewhere, starts with no file, reports `placement-unreadable`, and takes keys only from its machine's vault
51
+ (`where-agents-run.md`, FAA-230's placement and vault parts).
52
+ - A recorded working-with-fabric trigger run: 38 of 44 with haiku, above the 85 % gate, USD 0.0122
53
+ (`docs/evidence/evals/2026-11-12-trigger-run.md`, FAA-130); six misses follow FAA-291's shorter description (FAA-305).
54
+
55
+ - **The call queue and load reference functions of DEC-0104** (FAA-289): `fabric_load.py` and `fabric-load.mjs`,
56
+ exported by both facades: `jobCallerKey`, `nextQueuedJob`, `admitJob`, `queueStatusMessage`, `readQueuePosition` as
57
+ the contract's `src/agent-network.ts` answers them at the pin, FAC-SEM-131 to FAC-SEM-133, the `service-load/1` and
58
+ `extensions/load/0.1` shapes, and `loadExtension`. Both twins are held to `test/fixtures/contract/load-vectors.json`,
59
+ written by `make_load_vectors.mjs` from the contract's own vectors, fixtures and functions.
60
+ - **A dashboard-only service recipe** (FAA-268, `recipes/dashboard-only/`): an installer with `--id` and `--folder`,
61
+ a JSON last line and an idempotent repeat, and a runnable entry `check_service.py` passes; the HA-1 claim file waits
62
+ on contract CT-228.
63
+ - **A key-delivery door template** (FAA-238, `recipes/key-delivery-door/`): it prints one `fabric-credential-envelope/1`
64
+ line, writes into no store, and exits as Observatory's `receive --from-command` reads it; built against Observatory's
65
+ V2 spec until OBS-360 ships. The door owner supplies the seal command (the standard library has no HPKE).
66
+ - `context_gate_options` / `contextGateOptions` hand an instance's context and its pin's issuer to the delegation gate:
67
+ an organization instance refuses a token signed by the personal hub key (FAA-150).
68
+ - working-with-fabric: `references/remote-sessions.md`, a session over SSH with no GUI login (never start Fabric, the
69
+ login is the human gate) and one Project worked on from two machines (two boards, ids never cross) (FAA-284).
70
+
71
+ ### Changed
72
+
73
+ - **working-with-fabric's description names the relayed operator approval, a usage limit hit mid-run and its
74
+ exclusions (Fabric's own code, agent-sync leases) again**, within the 360-character budget: the trigger run goes from
75
+ 86.4 % to 95.7 % with haiku, and 95.7 % with sonnet (`docs/evidence/evals/2026-11-12-faa-305.md`, FAA-305).
76
+ - `remote-placement.md`: a cloud service registered from several hosts keeps one token per host, and revoking one ends
77
+ only its sessions (FAA-280).
78
+ - The Python connections reader refuses a non-ASCII host where every Node from 20 to 26 refuses it, with tables measured
79
+ by `test/fixtures/idna/make_idna_tables.mjs` (no host any Node accepts is refused, over 3,573,756 hosts) (FAA-98).
80
+ - The kit's mandate inputs carry 19 hostile `source` shapes, the vectors regenerated at the pin (FAA-149).
81
+ - `systemd_install` and `task_install` poll the installed answer when given `origin`, as `launchd_install` does
82
+ (FAA-253). A supervised `fabric_context` refusal waits the lock's back-off before it exits 78, so `KeepAlive` no
83
+ longer respawns it every 10 s (FAA-279).
84
+ - Hub and `fabric_http` refusals carry the listener owner's reason, and a restricted table is not retryable (FAA-272).
85
+ - **working-with-fabric finds Claude Code's files where `CLAUDE_CONFIG_DIR` points** (and Switchboard's source folder),
86
+ with the launcher's folder rule; the installer reads the same place (FAA-218, FAA-234). A local Claude Code
87
+ registration is read as Claude Code applies it, keyed by the git root or the folder itself outside git, measured
88
+ with 2.1.299 (`docs/evidence/runs/2026-11-12-faa-235-local-scope-key.md`, FAA-235).
89
+ - Session start warns in one line when the registered `fabric-mcp` is another user's copy (Claude Code, Codex, Kimi
90
+ Code) (FAA-232); `status` checks Codex's and Kimi Code's registrations too (FAA-233) and names an ignored
91
+ `PASSIONCODE_APPLICATIONS` value, a copy outside that folder, your own copy in `~/Applications` and a folder you
92
+ cannot enter (FAA-239, FAA-240).
93
+ - A turn carries its git branch where Fabric 0.3.16 and later take it; `FABRIC_SESSION_NO_BRANCH=1` turns it off
94
+ (FAA-152, its branch part).
95
+ - The Stop hook spools the turn's facts before it reads other repositories, and waits for that read only within its
96
+ 1.5 s budget (FAA-161). A turn's Bash calls carry the working folder inside the session folder, so a relative
97
+ `git -C` names the right repository (FAA-184).
98
+ - The retro texts name `agents-db.contribute`, and the agents-database ask gains its edges (FAA-277, items 1 to 4).
99
+ - `test/validate.py` searches the shipped files for a provider token shape, a private key block and a home path, and
100
+ every shipped script that writes `<https>` carries its legend but `fabric-delegation.mjs`, which takes it when it next
101
+ changes (FAA-306) (FAA-212).
102
+ - The nudge notes were re-read against Fabric's tag v0.3.14 (FAA-143).
103
+ - The insight/1 shape follows the estate's spec: `evidence` and `audience` are required, and the kind `market`
104
+ exists (`session-hooks.md`, FAA-217).
105
+ - `dashboard-links.md`: the `unsupported` host state takes no browser fallback, by the operator's rule; macOS never
106
+ reports it (FAA-137).
107
+ - `resilience-and-autonomy.md`: a routine whose result write fails exits non-zero and never logs success (FAA-294,
108
+ item 4).
109
+
110
+ ### Fixed
111
+
112
+ - **A usage report with many task runs passes its own FAC-SEM-025** (board request `f5753c5a`): each run and its project
113
+ were rounded to 6 decimals apart, so ten or more runs rounding up summed past the project (2.926432 > 2.926429 on a
114
+ 15-language localization). A run's cost now rounds as before but never above its exact cost, so the runs' sum stays
115
+ within the project by construction, in both twins. The projects against the days is the contract's own comparison
116
+ and its fixed tolerance; asked of the contract's owner (FAA-310).
117
+ - **A session that moved into a nested checkout keeps its local registration** (the 0.80.0 seam reading of the
118
+ candidate): FAA-235's one-key rule read a hook's moving `cwd` only, so the Stop and SessionEnd hooks, the deliver
119
+ worker and `watch-mail` took a session registered with `--scope local` at the repository root for unregistered once
120
+ it worked in a submodule or a `.claude/worktrees/` worktree. They also read the key of the folder the session started
121
+ in, `CLAUDE_PROJECT_DIR`, as Claude Code keeps that folder's registration.
122
+ - A refused job answer is named from one read of the job; the responder's shutdown says whether it drains; an epoch
123
+ refusal reads `com.status` before the retry (FAA-186). Provider stems read as the contract reads them, `~` in
124
+ `FABRIC_SERVICES_DIR` is expanded, contract-gated tests are found by `ast` (FAA-189). The channel-agent rule agrees
125
+ with the contract on Python 3.9 and answers NOT_RUN without its schema (FAA-250). ASCII digits only, a header-only
126
+ netstat reads as a restricted table, a hung `launchctl` no longer escapes (FAA-273).
127
+ - The release waits 20 minutes, not 10, for the registry to serve a new version, and the publish job's own limit is 30
128
+ minutes: 0.79.1 was served 6 s after the old window closed and its run read red although it was published (FAA-307).
129
+ - The dashboard link gate reads its handoff as UTF-8 bytes, drops a byte-order mark, caps it at 64 KiB and refuses a
130
+ deep document plainly, so a Cyrillic handoff passes under any code page (FAA-210).
131
+
132
+ ### Upgrading
133
+
134
+ - `fabric_descriptor` (and its Node twin) imports `fabric_peer_owner`, and the facades import `fabric_load`: update with
135
+ `fabric_kit.py install`, which brings both along, never a hand copy of one file.
136
+
137
+ ## 0.79.1 (2026-11-12)
138
+
139
+ 0.79.0 was tagged and never published: its release run (43176165810) failed two jobs, so `release` and `publish` were
140
+ skipped. 0.79.1 is 0.79.0 with these fixes, and is the release that ships it. Kit modules change: `fabric-peer-owner.mjs`,
141
+ `fabric_supervisors` and `fabric-supervisors.mjs`, `fabric_board_session`, `fabric_board_slots` and their Node twins.
142
+
143
+ ### Fixed
144
+
145
+ - **A checked connection to `localhost` reaches a listener on 127.0.0.1 under Node 18** (the `floors` job). Node 18
146
+ connects to the first address only, and `localhost` resolves to `::1` first on Linux, so `checkedSocket` (and every
147
+ loopback read made through it: `wellKnownOf`, the key set, the OpenRouter connector) found nothing where Python and
148
+ Node 20 and later try every address. It now passes `autoSelectFamily: true`. Reproduced on Node 22 with
149
+ `--no-network-family-autoselection`: the test fails without the change and passes with it.
150
+ - **The Windows job runs what passes there** (the `windows` job). 0.79.0 ran the whole of
151
+ `test_fabric_session_dispatch.py` on Windows for the first time (FAA-236): the parsing, `gate-id` and damaged-folder
152
+ tests pass, and twelve `Hooks` tests fail (the dispatch record's holder and `/clear` across processes). The job runs
153
+ the whole file again: the twelve were ten tests of the POSIX `/clear` take-over, which `dispatch_holder` turns off on
154
+ Windows by design (now skipped there with the reason), one file read with the code page (now UTF-8), and one real
155
+ defect: when `os.fdopen` raised, the descriptor stayed open, Windows refused to remove the empty record, and the
156
+ run was told it was nested and never did its request. The descriptor is closed first (FAA-302).
157
+ - **`foreign-user` no longer drops a lost call's record** (the 0.79.0 seam reading): 0.79.0's new board code
158
+ (FAA-252) reached two places that keep a lost call's state and read it as a refusal. The reply probe
159
+ (`_replied_before`, `repliedBefore`) and a lost enroll's asking record (`_replay`, `replay`) now stay, as for
160
+ `unreachable`, so a reply is never sent twice. Both twins, a test in each.
161
+ - **A loopback origin with no port, or `:80`, is read on a checked connection** in both twins: Node's `new URL`
162
+ drops the default port and read `http://127.0.0.1:80` unchecked; Python read a portless origin unchecked.
163
+ - `references/delegation.md` says what a restricted socket table does to a loopback key set and the way round.
164
+
165
+ ### Changed
166
+
167
+ - 0.79.0's FAA-293 line is narrower than it read: the inode check closes the race between two copies starting at once.
168
+ A folder swapped under a copy already running is not caught yet (FAA-303).
169
+
170
+ ### Upgrading
171
+
172
+ - `fabric_delegation` (and `fabric-delegation.mjs`) imports `fabric_peer_owner` since 0.79.0: update with
173
+ `fabric_kit.py install`, which brings it along, never a hand copy of the one file.
174
+
175
+ ## 0.79.0 (2026-11-12)
176
+
177
+ The first release of the backlog sweep of 2026-11-12 (plan and inventory of the 134 open rows:
178
+ `docs/evidence/specs/2026-11-12-backlog-waves-plan.md`). The contract pin stays `18f8d487`. Kit modules change:
179
+ `fabric_lock`, `fabric_base`, `fabric_delegation`, `fabric_descriptor`, `fabric_supervisors`, `fabric_board_link`,
180
+ `fabric_board_slots`, `fabric_board_session`, `fabric_openrouter`, the facade `fabric_service`, their Node twins, and
181
+ `fabric-secrets.mjs`. The dashboard kit moves to 1.1.6.
182
+
183
+ ### Added
184
+
185
+ - **creating and adapting: an organization's catalogue first** (the project board's request `2718ece8`). One neutral
186
+ line at step 0 of creating-fabric-agents and before scaffolding in adapting-projects-to-fabric: a private catalogue
187
+ of agent templates with an architect is asked first for the template, version, variant and known traps; the skill
188
+ runs after it. Scenario `ask-the-organization-catalogue-first` in both skills.
189
+
190
+ ### Changed
191
+
192
+ - **working-with-fabric reads against Fabric v0.3.19.** The session-start preamble, the README and SKILL.md say both
193
+ cases: before Fabric 0.3.17 a dispatched run declines a request no session can take and the asker resends; from
194
+ 0.3.17 the session's own Project is granted `board.respond` at `com.enroll` (CO-506) (FAA-221). The exit contract's
195
+ decline names the refusal's reason, takes over an `in_progress` request an earlier run left, and reads board ids
196
+ from the dispatch line first; the Observatory packet's board refs are the fallback (FAA-215, FAA-295).
197
+ `references/on-demand-session.md` and `board.md` name the Fabric releases as they are (FAA-246, FAA-216), and a
198
+ helper a run starts itself uses the runner's bypass flag with the task's limits set at launch (FAA-274).
199
+ - **"When nobody answers"** runs only on a machine whose trust is `mine` (ADR-0176), and the asker cancels its own
200
+ request after adapting (FAA-220). One phrase for a never-held slot, and a gate question is moot once its request is
201
+ answered or taken (FAA-222, FAA-248).
202
+ - **creating-fabric-agents: completion lines for an agent that answers people**, one per checklist item (a file and a
203
+ test, or `NOT_RUN`), the first reply measured from the person's message (FAA-180).
204
+ - **One rule for a secret in a child's environment**: the agent's own process may hold its key from its supervisor or
205
+ a vault runner; a child it starts never gets it (FAA-281).
206
+ - **Operator channel through the estate's gateway**: where the estate runs a messaging gateway the operator chose, the
207
+ channel sends through it; a polling token has one poller across machines (`poller-elsewhere`); the contract's
208
+ own-bot form (DEC-0034) is unchanged (FAA-282).
209
+ - **README quick start picks a free port**; `where-agents-run.md` adds one computer with a personal and an
210
+ organization account and a member's virtual machine (DEC-0154) (FAA-283).
211
+ - **The dashboard link gate is agent-side**, and a generated project pins a negative fixture in its own test (FAA-129).
212
+ - **The shipped-file check follows npm** (`test/validate.py`): a `files` entry with a glob, one naming nothing, or no
213
+ `files` at all is a finding; README and LICENSE are read because npm ships them regardless; a line with no scheme
214
+ says so. Tests pin `__pycache__`, the wiring into `validate_repo`, and the kit's `older` and `modified` verdicts
215
+ without release tags (FAA-212).
216
+
217
+ ### Fixed
218
+
219
+ - **The instance lock never holds a file that is no longer at its path** (FAA-293, a board finding of 2026-11-10).
220
+ After `flock` (Python) or the `O_EXLOCK` open (Node on macOS), and again after the pid is written, `acquire`
221
+ compares the locked inode with the file at the path; on a mismatch it re-opens up to
222
+ `InstanceLock.INODE_RETRIES` (3) times, then fails with an error naming the path. A data import that swapped the
223
+ data folder no longer lets two copies start.
224
+ - **The dashboard kit's resize observers write in the next frame** (dashboard kit 1.1.6, a board finding of
225
+ 2026-11-12): the nav's observer laid the bar out inside its callback and the chrome's wrote `--fd-chrome-h` and
226
+ `--fd-chrome-top` there, so WebKit raised "ResizeObserver loop completed with undelivered notifications" at 390 px.
227
+ Both schedule their work with `requestAnimationFrame` (cancelling the previous frame), the nav refits only when its
228
+ width changed, the chrome writes a property only when its value changed; a WebKit test pins that no callback writes.
229
+ - **Another OS account's listener is never read as ours**: the delegation verifier reads a loopback key set only from
230
+ this account's listener (FAA-255); the OpenRouter connector stops at once on a loopback base URL another account
231
+ holds, without trying the fallback model (FAA-257); a board session maps a hub port another account holds to
232
+ `foreign-user`, never `unreachable` (FAA-252); Python's `well_known_of` and `job_well_known_of` tell down from
233
+ `foreign-user` as Node's `wellKnownOf` does, and an origin spelled `localhost`, another 127/8 address or `[::1]` is
234
+ read on a checked connection (FAA-259, FAA-257); a hub pid another account's process holds is "not verified",
235
+ never running (FAA-254).
236
+ - **The reserved capability `project.work`** is refused by both kits in `surfaces.mcp.capabilities`, as an action's
237
+ tool and by the board responder (DEC-0140 PR-1) (FAA-244).
238
+ - The provider entry's URI check uses the RFC 3986 class the descriptor check uses: a byte-order mark, a zero-width
239
+ space, a non-ASCII path and an IDN host are refused (FAA-127).
240
+ - The dispatch record survives a hooks folder damaged by hand: `RecursionError` reads as unreadable, a `dispatch`
241
+ file is a folder that cannot be written, prune ages a record by its own `lstat` (FAA-208).
242
+ - `test/validate.py` reads board rows cell by cell as the workspace aggregator does: code spans and escaped pipes
243
+ (FAA-214). The Node credential detector and the dashboard kit's button append values in place, linear like the
244
+ Python twin (FAA-245). The source check's linear-time tests measure a ratio, not the wall clock (FAA-209).
245
+ - The board kit sends the operator to Fabric's Settings → Agent access (FAA-223); `vendor_vectors.mjs` names the
246
+ command that works, the contract's `tsx` at the pin (FAA-242).
247
+ - Step 6's `fabric_task_create` example carries the required `origin` (FAA-154).
248
+ - A dispatched Claude Code run that sees only `fabric_whoami` is named as Fabric's CO-563 (fixed in the Fabric release
249
+ after v0.3.19) and says so in its exit report instead of claiming the board was read (FAA-296).
250
+ - `fabric_session.py --help` and the README list `gate-id`, and the Windows job runs the dispatch tests (FAA-236).
251
+ - `provider-entry.md` and the provider writer's message name Fabric v0.3.16 for CO-479 (FAA-216).
252
+
253
+ ### Upgrading
254
+
255
+ - A service that vendors the kit updates its copies with `fabric_kit.py install` (the changed modules are named
256
+ above); a dashboard that vendors the dashboard kit takes 1.1.6. `launchd_install` takes injected readers
257
+ (`fetch_well_known=`, `fetch_job_well_known=`) as Node's `launchdInstall` takes `fetchWellKnown`.
258
+
3
259
  ## 0.78.0 (2026-11-12)
4
260
 
5
261
  The contract pin stays `18f8d487`. Kit modules change: a new `fabric_tailnet` and `fabric-tailnet.mjs` (layer L0); `fabric_supervisors` and `fabric-supervisors.mjs`, and the facades
package/README.md CHANGED
@@ -24,7 +24,9 @@ The skills are `adapting-projects-to-fabric`, `creating-fabric-agents`,
24
24
  a terminal that works in a project through Fabric. It finds Fabric (`hub.json`, its `pid`), has the machine's
25
25
  lifecycle broker start it when it is down, registers `fabric-mcp` once with no secret, calls `fabric_whoami`
26
26
  first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read), answers what waits on its
27
- Project in the same turn with its state (the result later; a request a responder should take stays queued), and asks other
27
+ Project in the same turn with its state (the result later; a request its Project's service should take stays queued; before Fabric 0.3.17 a run Fabric starts
28
+ declines a request no session can take and the asker resends, and from 0.3.17 the session's own Project is granted
29
+ `board.respond` at `com.enroll`, CO-506), and asks other
28
30
  Projects only through `com.submit`, asks who is working on what and claims its task before it starts
29
31
  (`fabric_work_list`, `fabric_work_claim`, Fabric v0.3.5 and later), links tasks to backlog rows instead of copying them, and
30
32
  releases at the end (`fabric_work_release`). `scripts/fabric_session.py status` prints where Fabric stands and one
@@ -40,7 +42,7 @@ uses Fabric (one line pointing at `fabric_whoami` in a folder with no sign of it
40
42
  is down it adds one line (skip `fabric_whoami`, start Fabric through the broker if the task needs it), and only in a
41
43
  project that uses Fabric; in a session Fabric started on a board request (`FABRIC_DISPATCH_WORKFLOW` set) it prints the
42
44
  exit contract instead, on the condition that the session's own prompt starts with the dispatch line naming the same
43
- three, and UserPromptSubmit, which sees that line, confirms the run or says it is none (FAA-201); Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
45
+ three (a `/clear` while another session holds the dispatch is told it is nested, FAA-208), and UserPromptSubmit, which sees that line, confirms the run or says it is none (FAA-201); Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
44
46
  and the board's unread count, and, once the running Fabric takes them (unless `FABRIC_SESSION_NO_REPOSITORIES=1`), the other repositories the turn touched (it then
45
47
  reads the last 512 KB of the session transcript (message text and tool output included, which can hold a secret the session displayed; read in memory only), uses only tool names and path arguments, and sends host/owner/name, never a
46
48
  local path or text); UserPromptSubmit adds one line only when that count rose, and each reminder Fabric gave on a turn once (a turn `watch-mail` delivered prints there instead); SessionEnd releases what the
@@ -49,7 +51,8 @@ line naming the retro (FAA-206; Fabric's `retro-after-work`, since Fabric 0.3.16
49
51
  advertises `session-hooks/1`; Stop and SessionEnd always prune their own stale spool folders. All are silent in a session Fabric launched (except the retro line after a release of finished work and, in one started on a board request, the exit contract and the prompt hook's verdict on its dispatch line, but no unread line or reminder), never block the session, read no Fabric token (an `origin` URL's embedded credential is dropped in memory, never
50
52
  kept or sent) and always
51
53
  exit 0 once `python3` starts (on Windows, Claude Code runs hooks in Git Bash, or PowerShell without it, where `python3`
52
- must name Python 3.9 or later; FAA-183). `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
54
+ must name Python 3.9 or later; FAA-183). `fabric_session.py watch-mail` waits for board mail under a background watcher, `gate-id <request>` prints the
55
+ `commandId` of a request's gate question (the one a dispatched run uses), and `install-host-hooks
53
56
  --host codex|kimi|hermes|opencode|openclaw|pi` installs each host's native context bridge (dry run unless `--apply`).
54
57
  Codex receives structured SessionStart JSON; Hermes, OpenCode, OpenClaw and Pi use native plugin APIs.
55
58
  See the [native host matrix](plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md)
@@ -204,18 +207,20 @@ With Python 3.9+ and the Claude Code CLI, in a throwaway directory and a tempora
204
207
  ```bash
205
208
  KIT=plugins/fabric-agent-adapter/skills/building-fabric-services/scripts # or ~/.agents/skills/building-fabric-services/scripts
206
209
  DATA=$(mktemp -d); SERVICES=$(mktemp -d); WORK=$(mktemp -d)
207
- python3 "$KIT/sample_service.py" serve --port 47190 --data-dir "$DATA" &
208
- python3 "$KIT/sample_service.py" register --port 47190 --data-dir "$DATA" --services-dir "$SERVICES"
210
+ # A free loopback port, so the sample never collides with a service already running here.
211
+ PORT=$(python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1])')
212
+ python3 "$KIT/sample_service.py" serve --port "$PORT" --data-dir "$DATA" &
213
+ python3 "$KIT/sample_service.py" register --port "$PORT" --data-dir "$DATA" --services-dir "$SERVICES"
209
214
  # The probe exits 0 (no FAIL); only then is the service registered for one client run (listener.owner proves the port
210
215
  # is this account's, and the client cannot check it). The token goes in a header, never in argv.
211
216
  # On macOS 27.2 run these with Apple's /usr/bin/python3: an ad-hoc-signed Python, such as Homebrew's, sees no other
212
217
  # process's sockets (FAA-272).
213
218
  python3 "$KIT/check_service.py" sample --services-dir "$SERVICES" && \
214
- python3 - "$DATA/service.token" "$WORK/mcp.json" <<'EOF'
219
+ python3 - "$DATA/service.token" "$WORK/mcp.json" "$PORT" <<'EOF'
215
220
  import json, os, sys
216
221
  token = open(sys.argv[1]).read().strip()
217
222
  with os.fdopen(os.open(sys.argv[2], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), "w") as f:
218
- json.dump({"mcpServers": {"sample": {"type": "http", "url": "http://127.0.0.1:47190/mcp",
223
+ json.dump({"mcpServers": {"sample": {"type": "http", "url": "http://127.0.0.1:%s/mcp" % sys.argv[3],
219
224
  "headers": {"Authorization": "Bearer " + token}}}}, f)
220
225
  EOF
221
226
  (cd "$WORK" && claude -p "Call the tool mcp__sample__sample_echo with text 'ping' and reply with only the text it returns." \
@@ -230,6 +235,8 @@ the CLI connected to the sample over streamable HTTP, called `sample.echo` and p
230
235
  ([record](docs/evidence/runs/2026-11-08-0.77.0-quick-start.md)). The probe prints one line per rule
231
236
  (`PASS`, `FAIL` or `NOT_RUN` with its evidence) and exits 1 on any `FAIL`; run it against your
232
237
  own service by its id once its installer has written the descriptor.
238
+ The free-port line (FAA-283) was run again on 2026-11-12 from this repository's kit with Claude Code 2.1.299 and
239
+ Apple's `/usr/bin/python3`: a port the system chose, 44 rules, 0 FAIL, `listener.owner` PASS, and `ping`.
233
240
  `FABRIC_REAL_CLIENT=1 python3 -m unittest discover -s test -p test_real_client.py` repeats the connection check
234
241
  (every sample tool listed, no model call) in the test suite.
235
242
 
@@ -32,6 +32,9 @@ Usage:
32
32
  remove plain ~/.claude/skills copies that shadow the installed plugin
33
33
  npx @passioncode-ai/fabric-agent-adapter --help
34
34
 
35
+ ~/.claude is the folder CLAUDE_CONFIG_DIR names when it is set, as Claude Code reads it; in a Switchboard home the
36
+ plugin is looked for in SWITCHBOARD_CLAUDE_SOURCE_DIR, where it was installed.
37
+
35
38
  Packages (fabric-package/1):
36
39
  npx @passioncode-ai/fabric-agent-adapter pack <folder> [--apply]
37
40
  list the folder's files, derive agentRef, compute the digest, check it
@@ -118,9 +121,27 @@ function linkOwner(dest) {
118
121
  return fs.readlinkSync(dest).split(path.sep).includes('.passioncode') ? 'launcher' : 'other';
119
122
  }
120
123
 
124
+ // Claude Code's folders as the PassionCode launcher finds them (its lib/launcher.js claudeFolders, PC-129; FAA-218):
125
+ // `read`, where the session's skills are, is CLAUDE_CONFIG_DIR when set, else ~/.claude; `write`, where plugins are
126
+ // installed, is the folder the CLI was run against (null: ~/.claude). Inside a Switchboard home CLAUDE_CONFIG_DIR is
127
+ // the home and SWITCHBOARD_CLAUDE_SOURCE_DIR the person's folder it is built from; a home of a Switchboard before
128
+ // 0.7.14 names no source and lies in Switchboard's data folder, and its plugins are then in ~/.claude.
129
+ const SWITCHBOARD_DATA = 'ai.passioncode.fabric-switchboard';
130
+ function claudeFolders(home) {
131
+ const configDir = process.env.CLAUDE_CONFIG_DIR ? path.resolve(process.env.CLAUDE_CONFIG_DIR) : null;
132
+ const named = process.env.SWITCHBOARD_CLAUDE_SOURCE_DIR;
133
+ const inSwitchboard = configDir && configDir.split(/[\\/]/).includes(SWITCHBOARD_DATA);
134
+ const source = !configDir ? null
135
+ : named && path.isAbsolute(named) ? path.resolve(named)
136
+ : inSwitchboard ? path.join(home, '.claude') : null;
137
+ const write = source ? (source === path.join(home, '.claude') ? null : source) : configDir;
138
+ return { read: configDir || path.join(home, '.claude'), write };
139
+ }
140
+
121
141
  function pluginInstalled(home) {
142
+ const folders = claudeFolders(home);
122
143
  try {
123
- const data = JSON.parse(fs.readFileSync(path.join(home, '.claude/plugins/installed_plugins.json'), 'utf8'));
144
+ const data = JSON.parse(fs.readFileSync(path.join(folders.write || path.join(home, '.claude'), 'plugins/installed_plugins.json'), 'utf8'));
124
145
  const records = (data.plugins || {})[PLUGIN_ID] || [];
125
146
  return records.some((r) => r.installPath && fs.existsSync(path.join(r.installPath, 'skills')));
126
147
  } catch {
@@ -140,10 +161,11 @@ function skillNames() {
140
161
  function pruneShadows(home, names) {
141
162
  let removed = 0;
142
163
  for (const name of names) {
143
- const shadow = path.join(home, '.claude', 'skills', name);
164
+ const skills = claudeFolders(home).read;
165
+ const shadow = path.join(skills, 'skills', name);
144
166
  let info;
145
167
  try { info = fs.lstatSync(shadow); } catch { continue; }
146
- const backup = path.join(home, '.claude', 'skills-shadow-backup', `${name}-${Date.now()}`);
168
+ const backup = path.join(skills, 'skills-shadow-backup', `${name}-${Date.now()}`);
147
169
  fs.mkdirSync(path.dirname(backup), { recursive: true });
148
170
  fs.renameSync(shadow, backup);
149
171
  console.log(`Removed shadowing ${info.isSymbolicLink() ? 'link' : 'copy'} ${shadow} (kept at ${backup})`);
@@ -196,7 +218,7 @@ function main(argv) {
196
218
  return 1;
197
219
  }
198
220
 
199
- const destRoot = target === 'claude' ? path.join(home, '.claude', 'skills') : path.join(home, '.agents', 'skills');
221
+ const destRoot = target === 'claude' ? path.join(claudeFolders(home).read, 'skills') : path.join(home, '.agents', 'skills');
200
222
  const failed = [];
201
223
  for (const name of names) {
202
224
  const dest = path.join(destRoot, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.78.0",
3
+ "version": "0.80.2",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks; and put an agent on a standing watch over a product (observer mode).",
6
- "version": "0.78.0",
6
+ "version": "0.80.2",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (unless FABRIC_SESSION_NO_REPOSITORIES=1), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). The session hooks are all silent in a session Fabric launched but the exit contract and the prompt hook's verdict on it and the retro line below; the dashboard-kit notice is silent only in a session started on a board request. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0 once python3 starts (on Windows it must name Python in Git Bash, FAA-183), and hands any Fabric call to a detached worker with a 10 s deadline. In a session Fabric started on a board request (FABRIC_DISPATCH_WORKFLOW), session start prints the exit contract instead of the preamble on the condition that the session's own prompt starts with the dispatch line naming the same three, launched or not, and the prompt hook, which sees that line, confirms the run or says it is none, launched or not, with no unread line or nudge, and the dashboard-kit notice says nothing (FAA-201). After a call to Fabric's fabric_work_release or fabric_task_release that releases a claim as done, PostToolUse adds one line naming the retro (FAA-206); after any other call, or a release Fabric's answer refuses (released: false, already-released) or that released nothing, nothing.",
2
+ "description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (unless FABRIC_SESSION_NO_REPOSITORIES=1), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). The session hooks are all silent in a session Fabric launched but the exit contract and the prompt hook's verdict on it and the retro line below; the dashboard-kit notice is silent only in a session started on a board request. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0 once python3 starts (on Windows it must name Python in Git Bash, FAA-183), and hands any Fabric call to a detached worker with a 10 s deadline. In a session Fabric started on a board request (FABRIC_DISPATCH_WORKFLOW), session start prints the exit contract instead of the preamble on the condition that the session's own prompt starts with the dispatch line naming the same three, launched or not (a /clear while another session holds the dispatch is told it is nested, FAA-208), and the prompt hook, which sees that line, confirms the run or says it is none, launched or not, with no unread line or nudge, and the dashboard-kit notice says nothing (FAA-201). After a call to Fabric's fabric_work_release or fabric_task_release that releases a claim as done, PostToolUse adds one line naming the retro (FAA-206); after any other call, or a release Fabric's answer refuses (released: false, already-released) or that released nothing, nothing.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
9
9
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
10
10
  metadata:
11
11
  author: PassionCode.ai
12
- version: "0.78.0"
12
+ version: "0.80.2"
13
13
  contract-version: "0.1.0"
14
14
  contract-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
15
15
  ---
@@ -100,6 +100,7 @@ is unavailable, continue only through local structure and mark exact schema vali
100
100
 
101
101
  ### 3. Scaffold the provider bundle
102
102
 
103
+ If your organization keeps a private catalogue of agent templates with an architect, ask it first: it names the template, version, variant and known traps; this skill runs after it.
103
104
  Before scaffolding, ask the [agents database](references/agents-database.md) for an audit of the project
104
105
  (`agents-db.audit`) and carry its `optimize` proposals into the completion report.
105
106
  Load [provider bundle](references/provider-bundle.md) before creating or mapping files.
@@ -290,9 +291,10 @@ host's `open` and `fallback=never`. Never `open <url>`, `open -a Safari`, `xdg-o
290
291
  `not_installed` host; an installed host that failed to open is reported, never replaced by HTTP.
291
292
  Links carry no token or login code.
292
293
 
293
- The agent's `AGENTS.md` carries this as its dashboard rule block, and its checks run the adapter's link gate
294
- (`scripts/check_dashboard_link.py` from this skill's own directory; it is not copied into the project) on every
295
- completion and notification action before delivery:
294
+ The agent's `AGENTS.md` carries this as its dashboard rule block; the agent runs the adapter's link gate
295
+ (`scripts/check_dashboard_link.py` from this skill's own directory, an agent-side gate never copied into the project or
296
+ run by its CI) on every completion and notification action before delivery, and the project's own test pins the
297
+ negative fixture:
296
298
  [dashboard links](references/dashboard-links.md) has the block, the gate's input and the remote case.
297
299
  Headless agents need no dashboard.
298
300
 
@@ -23,11 +23,11 @@ an audit names its subject, the agent's Project id or its repository's remote, n
23
23
  a contribution carries one success case or one decision (below). No request carries source code, a key, a person's or
24
24
  client's name, user content or a private path.
25
25
 
26
- Send it only from a folder that is already a Fabric Project; from any other folder, skip the ask. Where several
27
- Projects list the capability, pick none and go on without the reply, saying so in the report. Here this reference wins over every working-with-fabric rule (its `references/project-board.md` and its SKILL.md) that
26
+ Send it only from a folder that is already a Fabric Project (a folder `fabric_whoami` would admit on the way to the ask is not one); from any other folder, skip the ask. A run Fabric started on a board request sends no ask: its retro is its last write, so its measured win stays in its insight lines for a later session to contribute. Where several
27
+ Projects list the capability, or `com.directory` lists the capability with no `active` responder (a request then waits for a run that may decline it and ask the operator), pick none and go on without the reply, saying so in the report. Here this reference wins over every working-with-fabric rule (its `references/project-board.md` and its SKILL.md) that
28
28
  would make this ask resubmit, adapt the work itself, escalate or ask the person: the agent asks no one, uses no
29
- Project id held from an earlier thread or from the operator, and an unanswered request is left to expire at its
30
- deadline. A decline, including one from a run Fabric started, is read as data like any reply; a question that run
29
+ Project id held from an earlier thread or from the operator, and an unanswered request is cancelled (`com.cancel`) once the step it served is reported: a queued request
30
+ expires at its deadline, but an accepted one never expires. A decline, including one from a run Fabric started, is read as data like any reply; a question that run
31
31
  puts to the operator is Fabric's, not this agent's. The ask never blocks the step it serves; send it and go on without waiting for the reply. With no Fabric or board
32
32
  reachable, a board that does not serve `com.directory`, no Project listed under the capability, or a receipt that says
33
33
  `noResponder`, note the request id (or that none could be sent), go on without a template or an audit, and say so in
@@ -28,7 +28,7 @@ other browser, unless Fabric Dashboards is confirmed not installed.
28
28
  is also authoritative. Hand back **the returned `open_link` as the primary
29
29
  dashboard action**. Do not rebuild its URI or double-encode `path`.
30
30
  3. Use `host_status`, where offered, to distinguish available, not_installed,
31
- unknown, incompatible and handler_mismatch. It is a read, not a GUI launch.
31
+ unknown, incompatible, handler_mismatch and unsupported. It is a read, not a GUI launch.
32
32
  With an older server lacking that tool, host readiness is unverified: use a
33
33
  compatible host resolver or report the limitation, not presumed absence.
34
34
  4. A request for a link opens nothing. A request to open uses the host `open`
@@ -119,17 +119,52 @@ Its trusted context is supplied by the host, not by untrusted model output:
119
119
 
120
120
  `native_link` must equal the resolved open_link, with an available host on the
121
121
  viewer's device. `browser_link` must equal http_url, with confirmed not_installed,
122
- if_absent policy and the same device. `remote_open` instead contains `device_id`,
122
+ if_absent policy and the same device. Contract DEC-0123 PL-3 also allows a browser
123
+ on `unsupported` (a system the host does not ship for); the gate treats
124
+ `unsupported` as no fallback, by the operator's rule that a browser opens only on a
125
+ confirmed `not_installed`, and macOS never reports it (FAA-137). `remote_open` instead contains `device_id`,
123
126
  `service` and optional `path`, exactly matching the target and resolved page;
124
127
  its acceptance does not authorize or deliver it. Enforce grants/dedup/expiry in
125
128
  the transport. Unknown or failed host status produces no executable local action.
126
129
  A separately labelled copyable link for diagnosis is not an open action.
127
130
 
128
- Add this check to the generated project's dashboard completion/notification
129
- renderer before delivery. A raw-HTTP primary action with an available host is the
130
- negative fixture; a valid native action is the positive one. Keep diagnostics
131
- separate. This gate checks structured actions, not arbitrary prose: a CLI invocation
132
- or instructions alone do not prove all final answers from every agent are filtered.
131
+ The gate is **agent-side**: the agent runs it from this skill's directory before it
132
+ delivers an action. It is never copied into the generated project and never run by
133
+ that project's CI: the adapter's versioned plugin folder has no stable path there, so
134
+ its result in a project's pipeline would always be NOT_RUN. A raw-HTTP primary action
135
+ with an available host is the gate's negative fixture; a valid native action is the
136
+ positive one. Keep diagnostics separate. This gate checks structured actions, not
137
+ arbitrary prose: a CLI invocation or instructions alone do not prove all final answers
138
+ from every agent are filtered.
139
+
140
+ The generated project pins the same negative fixture in its own test suite, without
141
+ calling the gate: a test that renders its completion and notification output with an
142
+ available host and fails when a raw loopback `http_url` or a legacy `open?service=`
143
+ link appears in it. The snippet is self-contained; `render` stands for the project's
144
+ own renderer:
145
+
146
+ ```python
147
+ import re
148
+ import unittest
149
+
150
+ RAW_LOOPBACK = re.compile(r"https?://(?:127\.0\.0\.1|localhost|\[::1\])")
151
+ LEGACY = "fabric-dashboards://open?service="
152
+ RESOLVED = {"service": "example-agent.default",
153
+ "open_link": "fabric-dashboards://service/example-agent.default?path=%2Fdashboard",
154
+ "http_url": "http://127.0.0.1:47195/dashboard"}
155
+
156
+
157
+ def render(resolved, host_state): # replace with the project's completion/notification renderer
158
+ raise NotImplementedError
159
+
160
+
161
+ class RenderedDashboardLinks(unittest.TestCase):
162
+ def test_rendered_output_has_no_raw_loopback_or_legacy_link(self):
163
+ out = render(RESOLVED, host_state="available")
164
+ self.assertIn(RESOLVED["open_link"], out)
165
+ self.assertIsNone(RAW_LOOPBACK.search(out), out)
166
+ self.assertNotIn(LEGACY, out)
167
+ ```
133
168
 
134
169
  ## Degradation
135
170
 
@@ -25,6 +25,16 @@ unless addressed (`/command@ThisBot`, a reply to its own message or a press of i
25
25
  bare `/command` in a group is not addressed), and a channel nobody receives on is `degraded`
26
26
  (`receiver-missing`). Say so in the proposal when the operator means to bind a shared group.
27
27
 
28
+ Where the estate runs a messaging gateway service the operator chose for sending (one service every
29
+ agent sends through, holding its own grants and allowlist), the channel sends through that gateway
30
+ and does not call the messenger itself; the agent's own bot above is the contract's form (DEC-0034,
31
+ OC-11) and the form used where no such gateway runs. A bot token read by polling (Telegram
32
+ `getUpdates`) has exactly one poller across every machine: a second poller on the same token gets
33
+ `409 Conflict` and the two take each other's updates. Check placement before polling: the instance
34
+ on the machine the estate's placement record names for the agent polls, an instance on any other
35
+ machine does not poll and reports `degraded` with the reason (`poller-elsewhere`), and a test starts a
36
+ second instance placed elsewhere and sees it stay off the token.
37
+
28
38
  Record the answer (`accepted`, `declined` or `later`) in the agent's state and in its conformance
29
39
  report, and do not ask again; an agent that serves `surfaces.operatorChannel` also shows it as
30
40
  `proposal` in its status document. Creating the bot and putting its token into the secret store
@@ -57,9 +67,9 @@ For a PassionCode.ai repository, the agent's repository follows the organization
57
67
  and a `CLAUDE.md` whose first line is `@AGENTS.md`.
58
68
 
59
69
  Whoever owns the repository, an agent with a page gets the dashboard rule block in its `AGENTS.md`
60
- (pages open only in Fabric Dashboards; the block is in `references/dashboard-links.md`), and its
61
- checks run the adapter's link gate (`scripts/check_dashboard_link.py` from the skill's own directory, not a copy in
62
- the project).
70
+ (pages open only in Fabric Dashboards; the block is in `references/dashboard-links.md`); the agent runs the
71
+ adapter's link gate (`scripts/check_dashboard_link.py` from the skill's own directory, agent-side, never a copy in the
72
+ project or a step of its CI), and the project's own test pins the gate's negative fixture (the same reference).
63
73
 
64
74
  org-index `scripts/check_format.py` reports 0 findings for it before it is called done. An agent
65
75
  someone builds for themselves is not a PassionCode.ai repository: its licence is its owner's
@@ -41,7 +41,7 @@ when the server itself takes an option of that name. `--env` belongs to a stdio
41
41
  writer does not set it until Fabric reads it. An argument that carries a credential is refused
42
42
  (LC-21, FAC-SEM-057): a key goes in `env` as a `secret-ref:<name>`, never in argv.
43
43
 
44
- It refuses, with one sentence that never quotes a value:
44
+ It refuses, with one sentence that never quotes a credential (it names the path and the URIs at fault):
45
45
 
46
46
  - a manifest that does not resolve, or whose `provider.id` is not the entry's
47
47
  `providerId`: the two URIs are compared as URIs (FAC-SEM-014);
@@ -64,9 +64,9 @@ It refuses, with one sentence that never quotes a value:
64
64
  - a manifest whose name follows a backslash (`C:\Agents\example\fabric-agent.json`). The reader,
65
65
  `validate_provider_entry`, accepts it since DEC-0145 (`MANIFEST_PATTERN`, the schema's), but a reader that has not
66
66
  re-pinned to DEC-0145 refuses the entry, Fabric's registry among them (CT-192), so the writer does not emit it until
67
- the oldest Fabric release in use reads it (the release after v0.3.15, CO-479). Write the path with `/` before the name. A manifest under the home folder
67
+ the oldest Fabric release in use reads it (Fabric v0.3.16, which shipped CO-479). Write the path with `/` before the name. A manifest under the home folder
68
68
  (`~/Agents/example/fabric-agent.json`) is read by every Fabric release; one with a drive letter
69
- (`C:/Agents/example/fabric-agent.json`) only by Fabric after v0.3.15, whose registry took the repin (CO-479): v0.3.15
69
+ (`C:/Agents/example/fabric-agent.json`) only by Fabric v0.3.16 and later, whose registry took the repin (CO-479): v0.3.15
70
70
  and earlier read only a manifest under `~/` or `/`, so not `~\` either.
71
71
 
72
72
  `validate` also checks that the file is named `<id>.json` (FAC-SEM-014, slug half): the stem is what follows the last