@passioncode-ai/fabric-agent-adapter 0.77.0 → 0.79.1

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 (74) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +31 -11
  3. package/SECURITY.md +17 -3
  4. package/package.json +1 -1
  5. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +2 -2
  6. package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +15 -5
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +37 -5
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +2 -2
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/when-to-use.md +5 -0
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +14 -7
  13. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +27 -39
  14. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
  15. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +8 -5
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +37 -5
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +4 -1
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +2 -1
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +74 -3
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +8 -3
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +5 -0
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +43 -1
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/when-to-use.md +5 -0
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +3 -2
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +12 -6
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +2 -2
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +7 -2
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +3 -1
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +6 -0
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +339 -26
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet.mjs +198 -0
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +13 -5
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +2 -2
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +14 -1
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +6 -0
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +367 -27
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet.py +208 -0
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +323 -25
  55. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +19 -6
  56. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +37 -5
  57. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
  58. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
  59. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +6 -3
  60. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/when-to-use.md +5 -0
  61. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +4 -0
  62. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +179 -0
  63. package/plugins/fabric-agent-adapter/skills/observing-products/references/channels.md +129 -0
  64. package/plugins/fabric-agent-adapter/skills/observing-products/references/cycle.md +129 -0
  65. package/plugins/fabric-agent-adapter/skills/observing-products/references/when-to-use.md +5 -0
  66. package/plugins/fabric-agent-adapter/skills/observing-products/scripts/observer_inventory.py +259 -0
  67. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +30 -39
  68. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -0
  69. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +57 -37
  70. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +50 -25
  71. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +1 -1
  72. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/when-to-use.md +5 -0
  73. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +26 -10
  74. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +11 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,212 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.79.1 (2026-11-12)
4
+
5
+ 0.79.0 was tagged and never published: its release run (43176165810) failed two jobs, so `release` and `publish` were
6
+ 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`,
7
+ `fabric_supervisors` and `fabric-supervisors.mjs`, `fabric_board_session`, `fabric_board_slots` and their Node twins.
8
+
9
+ ### Fixed
10
+
11
+ - **A checked connection to `localhost` reaches a listener on 127.0.0.1 under Node 18** (the `floors` job). Node 18
12
+ connects to the first address only, and `localhost` resolves to `::1` first on Linux, so `checkedSocket` (and every
13
+ loopback read made through it: `wellKnownOf`, the key set, the OpenRouter connector) found nothing where Python and
14
+ Node 20 and later try every address. It now passes `autoSelectFamily: true`. Reproduced on Node 22 with
15
+ `--no-network-family-autoselection`: the test fails without the change and passes with it.
16
+ - **The Windows job runs what passes there** (the `windows` job). 0.79.0 ran the whole of
17
+ `test_fabric_session_dispatch.py` on Windows for the first time (FAA-236): the parsing, `gate-id` and damaged-folder
18
+ tests pass, and twelve `Hooks` tests fail (the dispatch record's holder and `/clear` across processes). The job runs
19
+ the whole file again: the twelve were ten tests of the POSIX `/clear` take-over, which `dispatch_holder` turns off on
20
+ Windows by design (now skipped there with the reason), one file read with the code page (now UTF-8), and one real
21
+ defect: when `os.fdopen` raised, the descriptor stayed open, Windows refused to remove the empty record, and the
22
+ run was told it was nested and never did its request. The descriptor is closed first (FAA-302).
23
+ - **`foreign-user` no longer drops a lost call's record** (the 0.79.0 seam reading): 0.79.0's new board code
24
+ (FAA-252) reached two places that keep a lost call's state and read it as a refusal. The reply probe
25
+ (`_replied_before`, `repliedBefore`) and a lost enroll's asking record (`_replay`, `replay`) now stay, as for
26
+ `unreachable`, so a reply is never sent twice. Both twins, a test in each.
27
+ - **A loopback origin with no port, or `:80`, is read on a checked connection** in both twins: Node's `new URL`
28
+ drops the default port and read `http://127.0.0.1:80` unchecked; Python read a portless origin unchecked.
29
+ - `references/delegation.md` says what a restricted socket table does to a loopback key set and the way round.
30
+
31
+ ### Changed
32
+
33
+ - 0.79.0's FAA-293 line is narrower than it read: the inode check closes the race between two copies starting at once.
34
+ A folder swapped under a copy already running is not caught yet (FAA-303).
35
+
36
+ ### Upgrading
37
+
38
+ - `fabric_delegation` (and `fabric-delegation.mjs`) imports `fabric_peer_owner` since 0.79.0: update with
39
+ `fabric_kit.py install`, which brings it along, never a hand copy of the one file.
40
+
41
+ ## 0.79.0 (2026-11-12)
42
+
43
+ The first release of the backlog sweep of 2026-11-12 (plan and inventory of the 134 open rows:
44
+ `docs/evidence/specs/2026-11-12-backlog-waves-plan.md`). The contract pin stays `18f8d487`. Kit modules change:
45
+ `fabric_lock`, `fabric_base`, `fabric_delegation`, `fabric_descriptor`, `fabric_supervisors`, `fabric_board_link`,
46
+ `fabric_board_slots`, `fabric_board_session`, `fabric_openrouter`, the facade `fabric_service`, their Node twins, and
47
+ `fabric-secrets.mjs`. The dashboard kit moves to 1.1.6.
48
+
49
+ ### Added
50
+
51
+ - **creating and adapting: an organization's catalogue first** (the project board's request `2718ece8`). One neutral
52
+ line at step 0 of creating-fabric-agents and before scaffolding in adapting-projects-to-fabric: a private catalogue
53
+ of agent templates with an architect is asked first for the template, version, variant and known traps; the skill
54
+ runs after it. Scenario `ask-the-organization-catalogue-first` in both skills.
55
+
56
+ ### Changed
57
+
58
+ - **working-with-fabric reads against Fabric v0.3.19.** The session-start preamble, the README and SKILL.md say both
59
+ cases: before Fabric 0.3.17 a dispatched run declines a request no session can take and the asker resends; from
60
+ 0.3.17 the session's own Project is granted `board.respond` at `com.enroll` (CO-506) (FAA-221). The exit contract's
61
+ decline names the refusal's reason, takes over an `in_progress` request an earlier run left, and reads board ids
62
+ from the dispatch line first; the Observatory packet's board refs are the fallback (FAA-215, FAA-295).
63
+ `references/on-demand-session.md` and `board.md` name the Fabric releases as they are (FAA-246, FAA-216), and a
64
+ helper a run starts itself uses the runner's bypass flag with the task's limits set at launch (FAA-274).
65
+ - **"When nobody answers"** runs only on a machine whose trust is `mine` (ADR-0176), and the asker cancels its own
66
+ request after adapting (FAA-220). One phrase for a never-held slot, and a gate question is moot once its request is
67
+ answered or taken (FAA-222, FAA-248).
68
+ - **creating-fabric-agents: completion lines for an agent that answers people**, one per checklist item (a file and a
69
+ test, or `NOT_RUN`), the first reply measured from the person's message (FAA-180).
70
+ - **One rule for a secret in a child's environment**: the agent's own process may hold its key from its supervisor or
71
+ a vault runner; a child it starts never gets it (FAA-281).
72
+ - **Operator channel through the estate's gateway**: where the estate runs a messaging gateway the operator chose, the
73
+ channel sends through it; a polling token has one poller across machines (`poller-elsewhere`); the contract's
74
+ own-bot form (DEC-0034) is unchanged (FAA-282).
75
+ - **README quick start picks a free port**; `where-agents-run.md` adds one computer with a personal and an
76
+ organization account and a member's virtual machine (DEC-0154) (FAA-283).
77
+ - **The dashboard link gate is agent-side**, and a generated project pins a negative fixture in its own test (FAA-129).
78
+ - **The shipped-file check follows npm** (`test/validate.py`): a `files` entry with a glob, one naming nothing, or no
79
+ `files` at all is a finding; README and LICENSE are read because npm ships them regardless; a line with no scheme
80
+ says so. Tests pin `__pycache__`, the wiring into `validate_repo`, and the kit's `older` and `modified` verdicts
81
+ without release tags (FAA-212).
82
+
83
+ ### Fixed
84
+
85
+ - **The instance lock never holds a file that is no longer at its path** (FAA-293, a board finding of 2026-11-10).
86
+ After `flock` (Python) or the `O_EXLOCK` open (Node on macOS), and again after the pid is written, `acquire`
87
+ compares the locked inode with the file at the path; on a mismatch it re-opens up to
88
+ `InstanceLock.INODE_RETRIES` (3) times, then fails with an error naming the path. A data import that swapped the
89
+ data folder no longer lets two copies start.
90
+ - **The dashboard kit's resize observers write in the next frame** (dashboard kit 1.1.6, a board finding of
91
+ 2026-11-12): the nav's observer laid the bar out inside its callback and the chrome's wrote `--fd-chrome-h` and
92
+ `--fd-chrome-top` there, so WebKit raised "ResizeObserver loop completed with undelivered notifications" at 390 px.
93
+ Both schedule their work with `requestAnimationFrame` (cancelling the previous frame), the nav refits only when its
94
+ width changed, the chrome writes a property only when its value changed; a WebKit test pins that no callback writes.
95
+ - **Another OS account's listener is never read as ours**: the delegation verifier reads a loopback key set only from
96
+ this account's listener (FAA-255); the OpenRouter connector stops at once on a loopback base URL another account
97
+ holds, without trying the fallback model (FAA-257); a board session maps a hub port another account holds to
98
+ `foreign-user`, never `unreachable` (FAA-252); Python's `well_known_of` and `job_well_known_of` tell down from
99
+ `foreign-user` as Node's `wellKnownOf` does, and an origin spelled `localhost`, another 127/8 address or `[::1]` is
100
+ read on a checked connection (FAA-259, FAA-257); a hub pid another account's process holds is "not verified",
101
+ never running (FAA-254).
102
+ - **The reserved capability `project.work`** is refused by both kits in `surfaces.mcp.capabilities`, as an action's
103
+ tool and by the board responder (DEC-0140 PR-1) (FAA-244).
104
+ - The provider entry's URI check uses the RFC 3986 class the descriptor check uses: a byte-order mark, a zero-width
105
+ space, a non-ASCII path and an IDN host are refused (FAA-127).
106
+ - The dispatch record survives a hooks folder damaged by hand: `RecursionError` reads as unreadable, a `dispatch`
107
+ file is a folder that cannot be written, prune ages a record by its own `lstat` (FAA-208).
108
+ - `test/validate.py` reads board rows cell by cell as the workspace aggregator does: code spans and escaped pipes
109
+ (FAA-214). The Node credential detector and the dashboard kit's button append values in place, linear like the
110
+ Python twin (FAA-245). The source check's linear-time tests measure a ratio, not the wall clock (FAA-209).
111
+ - The board kit sends the operator to Fabric's Settings → Agent access (FAA-223); `vendor_vectors.mjs` names the
112
+ command that works, the contract's `tsx` at the pin (FAA-242).
113
+ - Step 6's `fabric_task_create` example carries the required `origin` (FAA-154).
114
+ - A dispatched Claude Code run that sees only `fabric_whoami` is named as Fabric's CO-563 (fixed in the Fabric release
115
+ after v0.3.19) and says so in its exit report instead of claiming the board was read (FAA-296).
116
+ - `fabric_session.py --help` and the README list `gate-id`, and the Windows job runs the dispatch tests (FAA-236).
117
+ - `provider-entry.md` and the provider writer's message name Fabric v0.3.16 for CO-479 (FAA-216).
118
+
119
+ ### Upgrading
120
+
121
+ - A service that vendors the kit updates its copies with `fabric_kit.py install` (the changed modules are named
122
+ above); a dashboard that vendors the dashboard kit takes 1.1.6. `launchd_install` takes injected readers
123
+ (`fetch_well_known=`, `fetch_job_well_known=`) as Node's `launchdInstall` takes `fetchWellKnown`.
124
+
125
+ ## 0.78.0 (2026-11-12)
126
+
127
+ 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
128
+ `fabric_service` and `fabric-service.mjs` (new exports `launchd_domain` / `launchdDomain`, `SYSTEM_DAEMONS`,
129
+ `jobWellKnownOf`, `ForeignUserAnswer`); the probe `check_service.py` (`lifecycle.one-copy`); guidance in building-fabric-services and
130
+ working-with-fabric.
131
+
132
+ ### Added
133
+
134
+ - **A LaunchDaemon for a headless Mac** (FAA-278, with FAA-228): the launchd helpers in both kits work in launchd's system
135
+ domain, for a Mac that a FileVault restart (`fdesetup authrestart`) leaves with no GUI login, where a `gui/<uid>` job
136
+ never starts. A plist in `/Library/LaunchDaemons` (the folder's case ignored) or `domain="system"` /
137
+ `domain: 'system'` is a LaunchDaemon, macOS only; `launchd_plist(..., user_name=)` / `launchdPlist({userName})` writes
138
+ its `UserName`, which a system install requires; `launchd_domain` / `launchdDomain` names the domain.
139
+ `launchd_loaded`, `launchd_pid` and `launchd_override` read the system domain without root. `launchd_install` and
140
+ `launchd_uninstall` there need root, refuse before writing anything otherwise and name the command to run as root,
141
+ refuse `purge` (the data is the `UserName`'s), and accept the installed service's token-free answer only on a
142
+ connection launchd's own job accepted (its pid from `launchctl print system/<label>`, running as the `UserName`'s uid)
143
+ or, under a restricted socket
144
+ table, on a port that pid listens on (`lsof -p`, a weaker vouch, CT-261). A `gui/<uid>` bootstrap that fails for lack
145
+ of a GUI session (error 125, an SSH install) now says to remove that LaunchAgent and use the system domain. The probe's
146
+ `lifecycle.one-copy` asks launchd in the domain the descriptor's plist names. A LaunchDaemon's label is the machine's:
147
+ an install refuses, before writing anything, a label another plist holds, a plist at its path of another label or
148
+ another `UserName`, a `UserName` that is not a short name, is root or is no account of the Mac, and an origin other
149
+ than `http://127.0.0.1:<port>`; one refused after its bootstrap leaves the job loaded and names the `bootout`. An
150
+ uninstall refuses a label another plist holds before its `bootout`. The root installer's read of its own job's token-free answer, proved by the job's pid, its uid and a bind on
151
+ exactly `127.0.0.1`, departs from the pinned HA-2 (and the contract's later RT-3); the contract owner accepted it in
152
+ principle as DEC-0161, proposed (FAA-288). `references/lifecycle.md`
153
+ *The system domain* says which steps run as the `UserName` and which as root, and what a host must not do (FAA-287);
154
+ one scenario eval.
155
+
156
+ - **A kit module for an agent behind `tailscale serve`: `fabric_tailnet`** (FAA-297, a board request of 2026-11-11).
157
+ `fabric_tailnet.py` and `fabric-tailnet.mjs` (layer L0, no imports) decide who is calling and what that person may
158
+ open. The person comes from `Tailscale-User-Login`, believed only on the agent's own serve listener (a loopback port
159
+ or a Unix socket that only serve reaches) and from a loopback peer; a request carrying serve's headers on any other
160
+ listener is refused (`serve-header-off-serve`), never half-trusted. Groups, dashboards and capabilities come from a
161
+ `tailnet-perimeter/1` file generated from the organization's access registry: a file that does not name its source
162
+ (`generatedFrom`: registry, revision, time) is refused (`perimeter-not-generated`). MCP also takes a personal key,
163
+ stored only as its SHA-256, owned by one login, and refused when its capabilities are wider than that login's groups
164
+ (`key-wider-than-group`). Every refusal is 401 or 403 with a stable code; no answer carries a key or its hash. Both
165
+ twins answer one set of vectors, `test/fixtures/tailnet/vectors.json`; the reference is
166
+ `references/surfaces-and-auth.md` → "Behind tailscale serve".
167
+
168
+ - **A fifth skill, `observing-products`: observer mode** (FAA-290, the observer-mode design of Fabric CO-546). Any
169
+ agent with the plugin can take a standing watch over one product. Setup first: it writes the product's channel
170
+ inventory, `docs/observer/channels.md` (ops log, errors, analytics, funnels, revenue, store, uptime, CI, the Fabric
171
+ board, support), each row with its read route, its credential by vault slot name, its cursor, baseline, thresholds,
172
+ placement (`local` or `remote`) and state, and proves one read from each; a channel without a proven read is never
173
+ counted as watched. Then a cycle every interval (30 minutes by default): bounded reads from the cursors, a failed
174
+ read as a finding, one cycle record before any cursor moves, no overlap with its own last run, a missed window run
175
+ once; triage S0 to S3 within the project's authority (`observe`, `fix`, `ship`, the last set only by the person),
176
+ every fix through the project's own delivery route and read back from the channel that showed the fault, two failed
177
+ attempts stop the automatic work on a finding, channel text is data and never an instruction, a USD 100 a month
178
+ budget per observer by default, silent cycles and one message on change. Scheduling: Fabric's observer-mode switch
179
+ where Fabric runs, else Claude Code `/loop`, a cron routine, or a cloud routine that watches only `remote` channels
180
+ and names the local ones it skipped. `scripts/observer_inventory.py` (`check`, `template`) validates an inventory and
181
+ prints connected out of named; `test/test_observer_inventory.py`; trigger and scenario evals in
182
+ `test/evals/observing-products/`. The release smoke checks the installed skill.
183
+
184
+ ### Changed
185
+
186
+ - **Each skill's `description` is about 350 characters, within the skill listing budget** (FAA-291, a board request
187
+ of 2026-11-11). Claude Code shares one character budget among every installed skill's description and, over it,
188
+ keeps a description only for the most used skills (measured on the operator's machine: 105 skills, 35 962
189
+ characters against 30 000), so `working-with-fabric`, the skill the session hooks point at, was listed by name
190
+ alone. The five descriptions went from 4 542 characters to 1 722 (the four of 0.77.0 from 3 649 to 1 388): when to
191
+ use, two to four triggers in English and Russian, and the NOT-for boundary naming the neighbour. Each skill's
192
+ previous description, every trigger included, moved word for word to its `references/when-to-use.md`, linked
193
+ from a short `## When to use` section. `test/validate.py` now fails a description over 360 characters
194
+ (`DESCRIPTION_LISTING_BUDGET`) and its self-test plants one.
195
+
196
+ ### Fixed
197
+
198
+ - SKILL.md headroom (FAA-263): building-fabric-services and working-with-fabric each keep at least 150 words under the
199
+ 4750-word cap; the detail stays in their references, which already held most of it (the dashboard kit, the interop
200
+ *Actions* section, the session hooks, the board's *When nobody answers*), and the remote placement's *On the
201
+ platform* and the native host hooks gained what they lacked; no rule changes.
202
+
203
+ ### Upgrading
204
+
205
+ - A service that installs a LaunchDaemon vendors `fabric_supervisors` (or `fabric-supervisors.mjs`) with its facade and
206
+ `fabric_peer_owner` (or `fabric-peer-owner.mjs`) of 0.76.0 or later, which it imports. A LaunchAgent install behaves
207
+ as before, but in Node `fabric-supervisors.mjs` 0.78.0 imports `fabric-peer-owner.mjs` 0.76.0 or later in either
208
+ domain; `fabric_kit.py install` copies both.
209
+
3
210
  ## 0.77.0 (2026-11-08)
4
211
 
5
212
  The contract pin stays `18f8d487`. Kit modules change: `fabric_events` and `fabric-events.mjs`, `fabric_peer_owner`
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **Fabric Agent Adapter** makes any agent Fabric-compatible. Fabric is PassionCode.ai's
10
10
  product, the CEO AI agent: you talk to Fabric, and it chooses, binds and runs the agents that
11
- do the work. This adapter is how an agent becomes one Fabric can bind: four Agent Skills,
11
+ do the work. This adapter is how an agent becomes one Fabric can bind: five Agent Skills,
12
12
  Python and Node reference kits and a live conformance probe for the
13
13
  [Fabric Agent Contract](https://github.com/passioncode-ai/fabric-agent-contract). It also works
14
14
  on its own: the skills, kits and probe need no Fabric install.
@@ -18,13 +18,15 @@ on its own: the skills, kits and probe need no Fabric install.
18
18
  [![license](https://img.shields.io/badge/license-AGPL--3.0%20or%20commercial-blue.svg)](LICENSE)
19
19
 
20
20
  The skills are `adapting-projects-to-fabric`, `creating-fabric-agents`,
21
- `building-fabric-services` and `working-with-fabric`.
21
+ `building-fabric-services`, `working-with-fabric` and `observing-products`.
22
22
 
23
23
  `working-with-fabric` is for the other side: a coding agent (Claude Code, Codex, Kimi Code, Kilo, Hermes) opened in
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)
@@ -58,6 +61,18 @@ for activation, reload and verification limits. The
58
61
  they need from Fabric; the [board reference](plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md)
59
62
  is the board-first workflow.
60
63
 
64
+ `observing-products` puts an agent on a standing watch over one product (observer mode). It first writes the
65
+ product's channel inventory, `docs/observer/channels.md`: every channel the product reports through (operations log,
66
+ errors, analytics, funnels, revenue, store, uptime, CI, the Fabric board, support), each with its read route, its
67
+ credential by vault slot name, its baseline and a proven read. Then every interval (30 minutes by default) it reads
68
+ each channel from its cursor, triages what changed (S0 to S3) and fixes it through the project's own delivery route,
69
+ within the authority the project sets (`observe`, `fix`, `ship`). `scripts/observer_inventory.py check` validates the
70
+ inventory and prints connected out of named:
71
+
72
+ ```bash
73
+ python3 plugins/fabric-agent-adapter/skills/observing-products/scripts/observer_inventory.py template --project example-app
74
+ ```
75
+
61
76
  `building-fabric-services` makes an agent a long-lived local service with a dashboard
62
77
  that is always alive, runs as one copy, keeps its state through a reinstall and appears
63
78
  in Fabric Dashboards by itself: the `fabric-service/0.1` extension. It also makes an **online** agent
@@ -174,6 +189,7 @@ npx skills add passioncode-ai/fabric-agent-adapter --skill adapting-projects-to-
174
189
  npx skills add passioncode-ai/fabric-agent-adapter --skill creating-fabric-agents
175
190
  npx skills add passioncode-ai/fabric-agent-adapter --skill building-fabric-services
176
191
  npx skills add passioncode-ai/fabric-agent-adapter --skill working-with-fabric
192
+ npx skills add passioncode-ai/fabric-agent-adapter --skill observing-products
177
193
  ```
178
194
 
179
195
  ### Configure
@@ -191,18 +207,20 @@ With Python 3.9+ and the Claude Code CLI, in a throwaway directory and a tempora
191
207
  ```bash
192
208
  KIT=plugins/fabric-agent-adapter/skills/building-fabric-services/scripts # or ~/.agents/skills/building-fabric-services/scripts
193
209
  DATA=$(mktemp -d); SERVICES=$(mktemp -d); WORK=$(mktemp -d)
194
- python3 "$KIT/sample_service.py" serve --port 47190 --data-dir "$DATA" &
195
- 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"
196
214
  # The probe exits 0 (no FAIL); only then is the service registered for one client run (listener.owner proves the port
197
215
  # is this account's, and the client cannot check it). The token goes in a header, never in argv.
198
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
199
217
  # process's sockets (FAA-272).
200
218
  python3 "$KIT/check_service.py" sample --services-dir "$SERVICES" && \
201
- python3 - "$DATA/service.token" "$WORK/mcp.json" <<'EOF'
219
+ python3 - "$DATA/service.token" "$WORK/mcp.json" "$PORT" <<'EOF'
202
220
  import json, os, sys
203
221
  token = open(sys.argv[1]).read().strip()
204
222
  with os.fdopen(os.open(sys.argv[2], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), "w") as f:
205
- 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],
206
224
  "headers": {"Authorization": "Bearer " + token}}}}, f)
207
225
  EOF
208
226
  (cd "$WORK" && claude -p "Call the tool mcp__sample__sample_echo with text 'ping' and reply with only the text it returns." \
@@ -211,12 +229,14 @@ EOF
211
229
  kill %1
212
230
  ```
213
231
 
214
- Verified 2026-11-07 from the published npm package 0.76.0 (`npm pack` into a throwaway folder, `KIT`
232
+ Verified 2026-11-08 from the published npm package 0.77.0 (`npm pack` into a throwaway folder, `KIT`
215
233
  pointing at its skill) with Claude Code 2.1.299: the probe reported 44 rules, 0 FAIL, `listener.owner` PASS;
216
234
  the CLI connected to the sample over streamable HTTP, called `sample.echo` and printed `ping`
217
- ([record](docs/evidence/runs/2026-11-07-0.76.0-quick-start.md)). The probe prints one line per rule
235
+ ([record](docs/evidence/runs/2026-11-08-0.77.0-quick-start.md)). The probe prints one line per rule
218
236
  (`PASS`, `FAIL` or `NOT_RUN` with its evidence) and exits 1 on any `FAIL`; run it against your
219
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`.
220
240
  `FABRIC_REAL_CLIENT=1 python3 -m unittest discover -s test -p test_real_client.py` repeats the connection check
221
241
  (every sample tool listed, no model call) in the test suite.
222
242
 
package/SECURITY.md CHANGED
@@ -25,7 +25,13 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
25
25
  - The service kits create the service token (0600) and the instance lock in the
26
26
  service's own data directory, and write supervisor files only when an installer calls
27
27
  them: a launchd plist in `~/Library/LaunchAgents` (macOS), a systemd user unit (Linux) or
28
- a per-user Scheduled Task (Windows), plus one descriptor in the services directory. The
28
+ a per-user Scheduled Task (Windows), plus one descriptor in the services directory. A LaunchDaemon in
29
+ `/Library/LaunchDaemons` (FAA-278) is written only by an installer running as root, which the kits never become:
30
+ without root they refuse before writing and name the command, and refuse a plist without `UserName` (except one written
31
+ there with an explicit `domain="gui"`, not refused yet, FAA-286 item 21); the job runs as the account its `UserName`
32
+ names. The installer accepts the job's answer only on a connection launchd's own job accepted
33
+ (its pid from `launchctl print system/<label>`) or, where the socket table is restricted, on a port that pid listens
34
+ on (`lsof -p`, the weaker vouch below, CT-261). The
29
35
  token is readable by any process of the same user; the protocol defends against web pages
30
36
  and mistakes, not against hostile local code.
31
37
  - The kits' connectors (`fabric_http`, `fabric_openrouter` and their Node twins) reach the
@@ -36,7 +42,12 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
36
42
  `check_dashboard_layout.mjs` (a local login code) and the launchd installers' poll send
37
43
  anything to `127.0.0.1:<port>`, they read the computer's socket table to tell which OS
38
44
  account owns the listener (DEC-0154 HA-2, `fabric_peer_owner`), and send nothing to
39
- another account's, root's, SYSTEM's or an untellable listener. They read, never change:
45
+ another account's, root's, SYSTEM's or an untellable listener; the one exception is a LaunchDaemon's installer
46
+ (root, FAA-278), which sends a token-free well-known GET only on a connection the pid launchd names for its job
47
+ accepted while it runs as the plist's `UserName` and listens on exactly `127.0.0.1` (`lsof -p`), or, under a
48
+ restricted table, to a port that pid listens on there (the weaker vouch below): a departure from the pinned HA-2 and
49
+ the contract's later RT-3, accepted in principle by the contract owner as DEC-0161 (proposed; the restricted-table vouch
50
+ included) and kept as a departure until the next repin (FAA-288). They read, never change:
40
51
  macOS `sysctl net.inet.tcp.pcblist_n` in the process, or the programs `/usr/sbin/netstat
41
52
  -anv -p tcp` and `/bin/ps -o uid= -p <pid>` (the Node twin always); Linux
42
53
  `/proc/net/tcp` and `/proc/net/tcp6`; Windows `GetExtendedTcpTable` and the process
@@ -45,7 +56,10 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
45
56
  short-lived child of its own interpreter listening on a random `127.0.0.1` port; when the
46
57
  table is restricted (macOS 27.2, FAA-267) the launchd installers also run `/usr/sbin/lsof
47
58
  -nP -a -p <pid> -i4TCP -sTCP:LISTEN -Fn` on their own job's pid. That vouch shows the job listens on the port, not that it accepted
48
- the connection (a more specific bind by root over a wildcard job would pass; contract CT-261). A token-free loopback door is not yet protected against
59
+ the connection (a more specific bind by root over a wildcard job would pass; contract CT-261). A LaunchDaemon's root
60
+ installer runs that `lsof` and `ps -o uid=` on its job's pid before it accepts an answer, and reads, never changes, every other
61
+ plist in `/Library/LaunchDaemons` (to find another holder of its label, `plutil -convert xml1` for a binary one in
62
+ Node), `launchctl print system/<label>` and the `UserName`'s uid (`pwd`, or `id -u` in Node). A token-free loopback door is not yet protected against
49
63
  another account's process (HA-3, FAA-227). An MCP client (Claude Code, Codex) sends the
50
64
  token it was registered with on every later connection without this check (the contract's
51
65
  residue "a coding agent's bearer"): when the port may have changed hands, rotate the token
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.77.0",
3
+ "version": "0.79.1",
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'"
@@ -2,8 +2,8 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
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.",
6
- "version": "0.77.0",
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.79.1",
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
  {
@@ -1,11 +1,15 @@
1
1
  ---
2
2
  name: adapting-projects-to-fabric
3
- description: Use when adapting an existing agent, MCP server, A2A peer, HTTP service, or terminal CLI to the Fabric Agent Contract (choosing MCP/A2A/local-runner, scaffolding a provider bundle, or checking Fabric compatibility); also for «подключить проект к Fabric» or «сделать агента совместимым с Fabric». NOT for building the Fabric host/orchestrator, configuring an MCP client, a brand-new agent (creating-fabric-agents), running an agent as a local or online Fabric service with a dashboard (building-fabric-services), or a coding session working through Fabric, its board or claims, «подключись к Fabric» (working-with-fabric).
3
+ description: >-
4
+ Use when adapting an existing agent, MCP server, A2A peer, HTTP service or CLI to the Fabric Agent Contract:
5
+ profile, provider bundle, compatibility check. Triggers: "adapt this to Fabric", «подключить проект к Fabric».
6
+ NOT for a new agent (creating-fabric-agents), a service (building-fabric-services) or a session
7
+ (working-with-fabric).
4
8
  license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
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.
6
10
  metadata:
7
11
  author: PassionCode.ai
8
- version: "0.77.0"
12
+ version: "0.79.1"
9
13
  contract-version: "0.1.0"
10
14
  contract-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
11
15
  ---
@@ -16,6 +20,10 @@ Turn an existing stable interface into a proposed Fabric provider bundle, then p
16
20
  only the compatibility gates for which receipts exist. The Fabric Agent Contract is
17
21
  normative; this skill is an authoring workflow pinned to one immutable revision.
18
22
 
23
+ ## When to use
24
+
25
+ The description above stays short for the skill listing budget; the whole reach, every trigger included, is in [references/when-to-use.md](references/when-to-use.md).
26
+
19
27
  ## Boundary
20
28
 
21
29
  Use this skill to adapt or assess a provider project. Do not use it to:
@@ -92,6 +100,7 @@ is unavailable, continue only through local structure and mark exact schema vali
92
100
 
93
101
  ### 3. Scaffold the provider bundle
94
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.
95
104
  Before scaffolding, ask the [agents database](references/agents-database.md) for an audit of the project
96
105
  (`agents-db.audit`) and carry its `optimize` proposals into the completion report.
97
106
  Load [provider bundle](references/provider-bundle.md) before creating or mapping files.
@@ -282,9 +291,10 @@ host's `open` and `fallback=never`. Never `open <url>`, `open -a Safari`, `xdg-o
282
291
  `not_installed` host; an installed host that failed to open is reported, never replaced by HTTP.
283
292
  Links carry no token or login code.
284
293
 
285
- The agent's `AGENTS.md` carries this as its dashboard rule block, and its checks run the adapter's link gate
286
- (`scripts/check_dashboard_link.py` from this skill's own directory; it is not copied into the project) on every
287
- 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:
288
298
  [dashboard links](references/dashboard-links.md) has the block, the gate's input and the remote case.
289
299
  Headless agents need no dashboard.
290
300
 
@@ -125,11 +125,43 @@ its acceptance does not authorize or deliver it. Enforce grants/dedup/expiry in
125
125
  the transport. Unknown or failed host status produces no executable local action.
126
126
  A separately labelled copyable link for diagnosis is not an open action.
127
127
 
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.
128
+ The gate is **agent-side**: the agent runs it from this skill's directory before it
129
+ delivers an action. It is never copied into the generated project and never run by
130
+ that project's CI: the adapter's versioned plugin folder has no stable path there, so
131
+ its result in a project's pipeline would always be NOT_RUN. A raw-HTTP primary action
132
+ with an available host is the gate's negative fixture; a valid native action is the
133
+ positive one. Keep diagnostics separate. This gate checks structured actions, not
134
+ arbitrary prose: a CLI invocation or instructions alone do not prove all final answers
135
+ from every agent are filtered.
136
+
137
+ The generated project pins the same negative fixture in its own test suite, without
138
+ calling the gate: a test that renders its completion and notification output with an
139
+ available host and fails when a raw loopback `http_url` or a legacy `open?service=`
140
+ link appears in it. The snippet is self-contained; `render` stands for the project's
141
+ own renderer:
142
+
143
+ ```python
144
+ import re
145
+ import unittest
146
+
147
+ RAW_LOOPBACK = re.compile(r"https?://(?:127\.0\.0\.1|localhost|\[::1\])")
148
+ LEGACY = "fabric-dashboards://open?service="
149
+ RESOLVED = {"service": "example-agent.default",
150
+ "open_link": "fabric-dashboards://service/example-agent.default?path=%2Fdashboard",
151
+ "http_url": "http://127.0.0.1:47195/dashboard"}
152
+
153
+
154
+ def render(resolved, host_state): # replace with the project's completion/notification renderer
155
+ raise NotImplementedError
156
+
157
+
158
+ class RenderedDashboardLinks(unittest.TestCase):
159
+ def test_rendered_output_has_no_raw_loopback_or_legacy_link(self):
160
+ out = render(RESOLVED, host_state="available")
161
+ self.assertIn(RESOLVED["open_link"], out)
162
+ self.assertIsNone(RAW_LOOPBACK.search(out), out)
163
+ self.assertNotIn(LEGACY, out)
164
+ ```
133
165
 
134
166
  ## Degradation
135
167
 
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # When to use adapting-projects-to-fabric
2
+
3
+ The skill's full trigger description, kept here so the front matter's `description` stays within the client's skill listing budget (about 350 characters a skill). Read it when choosing between this skill and a neighbour.
4
+
5
+ Use when adapting an existing agent, MCP server, A2A peer, HTTP service, or terminal CLI to the Fabric Agent Contract (choosing MCP/A2A/local-runner, scaffolding a provider bundle, or checking Fabric compatibility); also for «подключить проект к Fabric» or «сделать агента совместимым с Fabric». NOT for building the Fabric host/orchestrator, configuring an MCP client, a brand-new agent (creating-fabric-agents), running an agent as a local or online Fabric service with a dashboard (building-fabric-services), or a coding session working through Fabric, its board or claims, «подключись к Fabric» (working-with-fabric).