@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.
- package/CHANGELOG.md +207 -0
- package/README.md +31 -11
- package/SECURITY.md +17 -3
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +2 -2
- package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +15 -5
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +37 -5
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +2 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/when-to-use.md +5 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +14 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +27 -39
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +8 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +37 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +74 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +8 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +43 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/when-to-use.md +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +12 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +7 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +339 -26
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet.mjs +198 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +13 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +14 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +367 -27
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet.py +208 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +323 -25
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +19 -6
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +37 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +6 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/when-to-use.md +5 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +4 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +179 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/references/channels.md +129 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/references/cycle.md +129 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/references/when-to-use.md +5 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/scripts/observer_inventory.py +259 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +30 -39
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +57 -37
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +50 -25
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/when-to-use.md +5 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +26 -10
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +11 -5
|
@@ -120,7 +120,11 @@ A request to a Project whose owner is not working now would wait for nobody. Aft
|
|
|
120
120
|
change nothing.
|
|
121
121
|
2. **Live:** wait under a watcher (below) and work on independent tasks meanwhile; never touch files a live session
|
|
122
122
|
holds.
|
|
123
|
-
3. **Nobody live, and the change is small.**
|
|
123
|
+
3. **Nobody live, the machine is the operator's, and the change is small.** The machine's trust decides first
|
|
124
|
+
(ADR-0176): `fabric_whoami` names it as `protocol.machine` (`trust: mine | organization | unknown`, from Fabric
|
|
125
|
+
0.3.12, set by the person in Settings). Only `mine` (the operator's own computer, or a remote one of theirs) allows
|
|
126
|
+
this step; on `organization` or `unknown` never edit another Project: step 4. Before Fabric reports trust, the
|
|
127
|
+
operator's own computer reads as `mine`, any other as `unknown`. Small means all three:
|
|
124
128
|
- it carries out what the owner's own records already say (its contract, its specification, a backlog row, a
|
|
125
129
|
finding with its evidence) or fixes a defect with a test; it decides nothing new: no ADR or DEC, no change to
|
|
126
130
|
what a public interface means, nothing recorded as the owner's or the operator's decision;
|
|
@@ -130,32 +134,50 @@ A request to a Project whose owner is not working now would wait for nobody. Aft
|
|
|
130
134
|
Make it in your own worktree of the owner's repository by its AGENTS.md (its branch naming, its agent-sync leases,
|
|
131
135
|
its gate green, its handoff and a backlog row closed with the receipt), and integrate the pull request as its rules
|
|
132
136
|
allow. Then reply on the request's thread, one line of body text (not a `reaction`, which only an announcement's
|
|
133
|
-
reply carries): **adapted by** your Project, the merge commit, and what the owner should look at.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
reply carries): **adapted by** your Project, the merge commit, and what the owner should look at. Then cancel your own
|
|
138
|
+
request (`com.cancel`, the reason "done by the asker"): your reply as the asker does not answer it (only the target's
|
|
139
|
+
does), so without the cancel it stays queued and expires. The owner's next session reads the thread and may revert
|
|
140
|
+
the change. One such change per target repository at a time (the knowledge base's agents-first.md, "When nobody
|
|
141
|
+
answers", step 3).
|
|
142
|
+
4. **Not small** (a decision, a contract, a release, a large change), **or the machine is not the operator's**: the request stays queued for its owner until its
|
|
138
143
|
deadline (seven days by default); never take a decision that is the owner's, record the wait in one line (what it
|
|
139
144
|
waits on, which watcher acts) and go on with your own work. An expiry posts no message, so the mail watcher does
|
|
140
145
|
not see it: at each idle look, read your open request's state (`com.get`) before its deadline. If it expires and the
|
|
141
146
|
ask still stands, submit it again.
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
The rule is the operator's (2026-10-22); the knowledge base's agents-first.md, "When nobody answers", records it. The
|
|
149
|
+
decline below is the adapter's own.
|
|
150
|
+
|
|
151
|
+
**A decline from a run Fabric started** (adapter 0.68.0; the question to the operator since 0.69.0): where Fabric's
|
|
152
|
+
dispatcher runs, a request to a Project with no live session goes, after a two-minute grace, to a run Fabric starts.
|
|
153
|
+
From Fabric 0.3.17 (CO-506) that run's own Project is granted `board.respond` at `com.enroll`, so the run takes the
|
|
154
|
+
request. Before 0.3.17, and from it only where CO-506 grants nothing (a machine marked as an organization's, no live
|
|
155
|
+
credential, a standing denial, a full ask queue), its `com.enroll` is refused; when the capability's slot is `absent`
|
|
156
|
+
at generation 0 (no service of the project ever held it), the run declines the request once with the reason and what
|
|
157
|
+
opens it (before 0.3.17: a Fabric with CO-506), asks for a resend and asks the operator about the gate
|
|
158
|
+
(`fabric_question_ask`, `about: board.gate.<capability>`) (where the run carries this adapter's exit contract or Fabric
|
|
159
|
+
0.3.17's packet text; a run under Switchboard's or Fabric 0.3.16's own text alone has no such branch, and the request
|
|
160
|
+
may stay queued) ([on-demand session](on-demand-session.md), step 3).
|
|
150
161
|
The request is then `answered`: read the decline and `com.read_ack` it (unread, it shows in «На проверке»); it no
|
|
151
|
-
longer waits for its owner, so steps 1, 2 and 4 no longer apply to it, while a
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`cancelled`, submit it again with a new key, once: a second
|
|
155
|
-
|
|
156
|
-
below) is read the same way: `com.read_ack`, record the wait on the reason it names, one resend once that
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
longer waits for its owner, so steps 1, 2 and 4 no longer apply to it, while a small change may still be adapted under
|
|
163
|
+
step 3. Record the wait on the reason the decline names (what it waits on, which watcher acts); once that clears (an
|
|
164
|
+
upgrade to Fabric 0.3.17 or later, or the grant the reason names) and the ask still stands, cancel the answered request
|
|
165
|
+
(`com.cancel`) and, once the cancel's receipt reads it `cancelled`, submit it again with a new key, once: a second
|
|
166
|
+
decline goes to the person. A live session's answer that the request cannot be taken now (its own refused
|
|
167
|
+
`com.enroll`, below) is read the same way: `com.read_ack`, record the wait on the reason it names, one resend once that
|
|
168
|
+
clears.
|
|
169
|
+
|
|
170
|
+
**A gate question is moot once the request is answered or taken.** The usual end of a refusal at a slot a service held is that
|
|
171
|
+
service rejoining and taking the request; then the gate question a refused run or session filed stays open though
|
|
172
|
+
nothing waits on it (Fabric settles no question by its `about`, CO-519, and no Fabric through v0.3.19 lets an agent close
|
|
173
|
+
one). A run or session that later finds the request answered, or taken and completed, says so once, naming the question (`about:
|
|
174
|
+
board.gate.<capability>` and the `commandId` that `gate-id` prints), in its closing `notes` or to the person (FAA-248).
|
|
175
|
+
|
|
176
|
+
**A dispatched Claude Code run that sees only `fabric_whoami`** among Fabric's tools (a headless `claude -p` session
|
|
177
|
+
reads its list of tools once, and `fabric-mcp` offered the rest only after the call admitted it) is Fabric's CO-563,
|
|
178
|
+
fixed on Fabric's main at `780022af` and expected in the release after v0.3.19. Until then such a run cannot read or
|
|
179
|
+
answer the board: it says so in its exit report (an `in_progress` checkpoint naming CO-563 when it holds the lease, then the retro) and never
|
|
180
|
+
claims the board was read; the request waits for the next run or a live session (FAA-296).
|
|
159
181
|
|
|
160
182
|
## The project directory
|
|
161
183
|
|
|
@@ -177,11 +199,12 @@ The rule is the operator's (2026-10-22); the knowledge base's agents-first.md, "
|
|
|
177
199
|
|
|
178
200
|
Answering it as a responder is `com.enroll` then `com.claim`, under a `board.respond` grant: a service asks it with
|
|
179
201
|
`fabric.access.request` and the operator decides it in Fabric (Agent access); a coding session cannot ask it, and
|
|
180
|
-
gets its own Project's at `com.enroll`
|
|
202
|
+
gets its own Project's at `com.enroll` from Fabric 0.3.17 (CO-506). The request then moves with `com.accept` and `com.complete`. A product agent that answers
|
|
181
203
|
requests as a service uses the service kit's responder (building-fabric-services, `references/board.md`).
|
|
182
204
|
|
|
183
205
|
**A request whose thread holds an *adapted by* reply** was carried out by another Project under "When nobody answers".
|
|
184
|
-
Review the merged change; then reply on
|
|
206
|
+
Review the merged change; then reply on its thread: kept, or reverted and why (the asker cancels its request once it
|
|
207
|
+
adapted, so the reply informs; on a request still open, your reply as its target answers it).
|
|
185
208
|
|
|
186
209
|
**A participant reply answers the request** (DEC-0098, DEC-0102). Your Project's `com.reply` (any message of yours
|
|
187
210
|
whose `replyTo` is the request) moves a queued request to `answered`: no responder claims it any more and its
|
|
@@ -191,7 +214,9 @@ nearest date (Answering at once, below); the result then follows in the same thr
|
|
|
191
214
|
service responder should still do the work (`com.status`'s slot for the capability is `active` or `stale`, or `absent` at a
|
|
192
215
|
generation of 1 or more: a holder enrolled on it), do
|
|
193
216
|
not reply on the request: tell the person and leave it queued. A session whose own `com.enroll` is refused
|
|
194
|
-
`not_authorized` has no route to the grant before Fabric
|
|
217
|
+
`not_authorized` has no route to the grant (before Fabric 0.3.17 every session; from it, where CO-506 grants nothing:
|
|
218
|
+
an organization's machine, no live credential, a standing denial, a full ask queue): when its capability's slot is
|
|
219
|
+
`absent` at generation 0 (no service of the project ever held it),
|
|
195
220
|
it answers the request once that it cannot be taken now and why, does no work on it without the slot, then asks the
|
|
196
221
|
operator once (at any other slot state it sends no reply and still asks, FAA-241) with `fabric_question_ask` (`kind: access`, `about: board.gate.<capability>`, the `commandId` that
|
|
197
222
|
`python3 <skill-dir>/scripts/fabric_session.py gate-id <request>` prints, the same a run uses, and a `text` naming the request, the gate and what
|
|
@@ -199,7 +224,7 @@ opens it; without Python, no `commandId` can be made: tell the person only), tel
|
|
|
199
224
|
filed even when that is the operator: Fabric keeps its `about` and settles nothing by it yet, CO-519), and stops handling
|
|
200
225
|
that request without waiting (Fabric's `board.read-at-start` rule and `com.enroll`'s own tool description say the
|
|
201
226
|
same since main `4f3c742a`: the question since `603da12f` (CO-517) in the rule and `12d57bdd` in the description, the slot condition since
|
|
202
|
-
`dbfbfce4` and `12d57bdd
|
|
227
|
+
`dbfbfce4` and `12d57bdd`, all in Fabric 0.3.17;
|
|
203
228
|
on Fabric 0.3.16 this rule wins). Never write "it stays queued" in a reply: the reply itself ends the queue. A
|
|
204
229
|
run Fabric started on a board request has no person in its chat to tell: with the slot `absent` at generation 0 it declines,
|
|
205
230
|
asks for a resend and asks the operator with `fabric_question_ask`; otherwise it leaves the request queued and still asks ([on-demand session](on-demand-session.md), step 3).
|
|
@@ -28,7 +28,7 @@ session start and turn; FAA-183 tracks making the plugin find the interpreter it
|
|
|
28
28
|
| Event | When it acts | What it does |
|
|
29
29
|
|---|---|---|
|
|
30
30
|
| SessionStart (`startup`, `clear`; the plugin's matcher, and the script ignores `compact` and `resume` for every host) | Claude Code has an MCP server named `fabric` (local, project or user scope) | Fabric running, in a project that uses Fabric (below): adds the preamble: `fabric_whoami` first; `com.status` and `com.list`, `com.read_ack` only what was read, and what waits on the Project answered in the same turn with its state, the result later (FAA-205); `fabric_work_list`, then `fabric_work_claim`; `fabric_work_release` at the end, and, when a piece of work or the session ends, its retro (`observatory_lesson`, insight/1 lines in `insights/` (below), or one line that there is nothing; FAA-206); ask another Project only through `com.submit`. Fabric running in a folder with no such sign (a user-scope registration reaches every folder): one line pointing at `fabric_whoami`, which names the project when Fabric serves the folder anyway (FAA-60). Fabric down, in a project that uses Fabric: one line, skip `fabric_whoami` and the board, start Fabric through the lifecycle broker only if the task needs it. Fabric down anywhere else, or not registered: nothing. |
|
|
31
|
-
| Stop (each turn) | as for SessionStart, and the running Fabric's `hub.json` lists `"session-hooks/1"` in `features` | measures the turn's facts (HEAD, files changed, lines added and removed: counts and ids only, git sharing 1.5 s) and names the other Git repositories the turn touched (FAA-140, below: only once Fabric takes them), spools them in one entry and starts a detached worker that sends `fabric_session_observe {event: "turn", head, filesChanged, linesAdded, linesRemoved, …}` as stored (with `repositories` only where that tool's input schema lists the field as an array of items with host, owner and name requiring nothing else, at most its `maxItems`, each item meeting what the schema says of its fields or left out; a turn spooled with the mark and delivered to a Fabric whose schema no longer takes them goes without them, and the log says so once per session; with a Fabric that never took them nothing is read and nothing is logged), keeps the `nudges` its answer carries (Fabric 0.3.14: each `{kind, says, task_id?, request_id?}`, shown on a turn once, at most five; it stays for `fabric_work_list` and `fabric_heartbeat`, so it may come again there) in `nudges.json`, then reads `com.status` and keeps the unread count in the session's spool (`board.json`) |
|
|
31
|
+
| Stop (each turn) | as for SessionStart, and the running Fabric's `hub.json` lists `"session-hooks/1"` in `features` | measures the turn's facts (HEAD, files changed, lines added and removed: counts and ids only, git sharing 1.5 s) and names the other Git repositories the turn touched (FAA-140, below: only once Fabric takes them), spools them in one entry and starts a detached worker that sends `fabric_session_observe {event: "turn", head, filesChanged, linesAdded, linesRemoved, …}` as stored (with `repositories` only where that tool's input schema lists the field as an array of items with host, owner and name requiring nothing else, at most its `maxItems`, each item meeting what the schema says of its fields or left out; a turn spooled with the mark and delivered to a Fabric whose schema no longer takes them goes without them, and the log says so once per session; with a Fabric that never took them nothing is read and nothing is logged), keeps the `nudges` its answer carries (Fabric 0.3.14: each `{kind, says, task_id?, request_id?}`, shown on a turn once, at most five; it stays for `fabric_work_list` and `fabric_heartbeat`, so it may come again there: act on it once) in `nudges.json`, then reads `com.status` and keeps the unread count in the session's spool (`board.json`) |
|
|
32
32
|
| UserPromptSubmit | the session's spool holds a count or nudges, and the session was not started on a board request (nor runs inside such a run) | adds ONE line, `N unread on the project board: com.list; answer what waits on you in this turn: ...` (FAA-205: its state now, the result later), only when the count rose since the last line (a fall or the same count says nothing), then each nudge's `says` once, as Fabric writes it (Fabric 0.3.14 starts each with `Fabric:`; any other line gets the tag `Fabric reminder: `), one line each, at most five lines of at most 400 characters; the nudges file is taken by renaming it away before they are printed (FAA-142). It reads, renames and writes small files in the session's folder and never calls Fabric. |
|
|
33
33
|
| SessionEnd | as for Stop | spools the end; the worker sends `fabric_work_release {claims: "session", outcome: "let-go", …}` (with `observedAt`, the time the hook fired, where the tool's input schema names it: Fabric 0.3.9 then refuses an end from before the session enrolled), then `fabric_session_observe {event: "end", reason}`, and removes the session's spool. Reason `clear` ends nothing: `/clear` keeps the agent process and its fabric-mcp, so no claim is released and no end observed; turns already spooled still go out (since 0.42.0). Their nudges stay in the old id's folder and are not printed here; Fabric 0.3.14 only shows a nudge on a turn's answer and keeps it for `fabric_work_list` and `fabric_heartbeat`, so the new session gets it there (board `b605a636`; decision of 0.51.2, FAA-142) |
|
|
34
34
|
| PostToolUse (the plugin's matcher: Fabric's `fabric_work_release` and `fabric_task_release` only) | the call released a claim as done (`how`, or `outcome` when `how` is absent, done; the older tool's `outcome` succeeded) and Fabric's answer did not refuse it or release nothing (`released: false`, Fabric's refusal; `already-released`; a `claims: "session"` release with no live claim, `claims: []`; a repeat, which Fabric answers with the first release's `at`; a claim the operator released first, `released-by-operator`; a repeat after a first answer the agent never saw (lost in the client, or `unconfirmed`), and a session release answered `partial` or `unconfirmed`; in each the session-end retro still stands; a payload over 4 MiB is read as nothing and gets no line, as on every hook (below); or `isError`; an answer the hook cannot read keeps the line) | adds ONE line naming the retro in this turn (`observatory_lesson`, or the turn's answer without it; insight/1 lines in `insights/` (below); or one line that there is nothing; FAA-206, Fabric's `retro-after-work`, since Fabric 0.3.16, `e1eecec0`); reads no file, calls nothing, also in a session Fabric launched |
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# When to use working-with-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 a coding agent (Claude Code, Codex, Kimi Code, Kilo, Hermes) works in a project through Fabric: finding Fabric, having the lifecycle broker start Fabric, registering fabric-mcp once with no secret, fabric_whoami first, the project board (com.status, com.list, com.read_ack, com.submit to another Project, watch-mail), claiming and releasing work (fabric_work_claim, fabric_work_release), relaying the operator's decision or mandate (operator-decision-required), reports, handoffs, refusals. Triggers: "connect to Fabric", "register fabric-mcp", "claim the task in Fabric", "check the project board", "ask the other project through Fabric", "Fabric is not running", «подключись к Fabric», «возьми задачу в Fabric», «проверь доску проекта», «Fabric не запущен», "hit your limit, resets at". Explains the plugin's session hooks. NOT for building or adapting an agent or a board responder (creating-fabric-agents, adapting-projects-to-fabric, building-fabric-services), changing Fabric's own code, or messaging people.
|
package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py
CHANGED
|
@@ -74,7 +74,10 @@ PREAMBLE = (
|
|
|
74
74
|
"Call `fabric_whoami` first. Then read the project board: `com.status` (is it ready, how many unread), "
|
|
75
75
|
"`com.list` for the messages, and `com.read_ack` only for what you actually read. A request, announcement or "
|
|
76
76
|
"question waiting on your Project gets an answer in the same turn: its state now (done, where, when, what "
|
|
77
|
-
"blocks), the result later in a second message; a request
|
|
77
|
+
"blocks), the result later in a second message; a request your Project's service should take stays queued for "
|
|
78
|
+
"it. Before Fabric 0.3.17 a run Fabric starts on a request no session can take declines it and the asker resends; "
|
|
79
|
+
"from 0.3.17 (CO-506) the session's own Project is granted `board.respond` at `com.enroll`, so you or that run "
|
|
80
|
+
"can take it (`com.enroll`, `com.claim`). "
|
|
78
81
|
"Before you start work, call `fabric_work_list` (who is working on what here) and "
|
|
79
82
|
"claim your task with `fabric_work_claim`; release it with `fabric_work_release` when you stop. When a piece of "
|
|
80
83
|
"work ends (released as done, or the session about to end), write its retro in the same turn: `observatory_lesson` "
|
|
@@ -137,12 +140,12 @@ DISPATCH_IF = (
|
|
|
137
140
|
" Other values: no run; say so in the request's thread and stop. The run:")
|
|
138
141
|
#: The conditional contract at its longest (a 64-character handoff and a session id) is held under Hermes' 4000
|
|
139
142
|
#: characters by test_a_native_bridge_is_always_told_the_contract_and_writes_no_record; measure before adding words
|
|
140
|
-
#: (3996 in 0.68.0 and in 0.69.0, 3999 in 0.71.0, 3996 in 0.72.0: a new rule takes its characters back from the text around it).
|
|
143
|
+
#: (3996 in 0.68.0 and in 0.69.0, 3999 in 0.71.0, 3996 in 0.72.0, 3994 in 0.79.0: a new rule takes its characters back from the text around it).
|
|
141
144
|
DISPATCH_CONTRACT = (
|
|
142
145
|
" Do that "
|
|
143
146
|
"request and end; never wait for a person. 1. `fabric_whoami`, then the packet "
|
|
144
147
|
"(`observatory_checkpoint_latest` {{workflowId: {workflow}}}): obey its constraints first; its goal describes "
|
|
145
|
-
"the request; artifacts: capability, deadline, run-ends-at. Read the request (`com.get` "
|
|
148
|
+
"the request; artifacts: capability, deadline, run-ends-at (ids: the line's first). Read the request (`com.get` "
|
|
146
149
|
"{{message: <request>}}) and its thread (`com.list` {{thread: <the `thread` com.get answered>}}). 2. "
|
|
147
150
|
"`observatory_handoff_accept` with {handoff}, the `owner`, `idempotencyKey` and `executor` the launcher's prompt names "
|
|
148
151
|
"(when it names none: your `owner` `agent:<name>`, the key `accept-<handoff>`, no executor) and this session's own "
|
|
@@ -150,7 +153,7 @@ DISPATCH_CONTRACT = (
|
|
|
150
153
|
"no lease: end the run without touching the request. 3. `com.status` once ("
|
|
151
154
|
"epoch, generation); `com.enroll` (the capability), `com.claim` (the request), "
|
|
152
155
|
"`com.accept` and `com.effect_begin` before an effect; a not_authorized enroll: no slot, no work and, if queued and its `com.status` slot `absent` at generation 0, one `com.reply` (replyTo it) "
|
|
153
|
-
"declining
|
|
156
|
+
"declining with the reason (resend then), either way `fabric_question_ask` {{kind: access, about: board.gate.<capability>, commandId: {gate}, text: the request, its gate, what opens it}}, then step 6 (done) once `com.get` reads answered, and 7; left accepted or in_progress by an earlier run: `com.responder_replace` (`expectedGeneration`) and its transfer; resolve `outcome_unknown` only with "
|
|
154
157
|
"`com.reconcile` and an observed result. After the lease, claim and reply: an in_progress checkpoint (`workflowId`, "
|
|
155
158
|
"`leaseId`). Every checkpoint repeats `goal`, `constraints`, `artifacts` and `credentials` of "
|
|
156
159
|
"`checkpoint.body` (not the answer's top-level `credentials` or the accept's "
|
|
@@ -165,7 +168,7 @@ DISPATCH_CONTRACT = (
|
|
|
165
168
|
"`com.complete` (observed: true): completed, or failed_known with the reason. 6. Once the board took it, "
|
|
166
169
|
"`observatory_checkpoint_write` with `workflowId` {workflow}, a `stepId`, "
|
|
167
170
|
"the `leaseId`, `close: true`, a status, the packet's body plus `done` and `notes`: done for completed; for "
|
|
168
|
-
"failed_known blocked with the reason in notes
|
|
171
|
+
"failed_known blocked with the reason in notes. 7. Write the retro "
|
|
169
172
|
"(`observatory_lesson` and insight/1 lines in `insights/`, or one line that there is nothing; no "
|
|
170
173
|
"values), then end: no board sweep, work claim or watch-mail. On an error you cannot get past: complete the request failed_known if you hold its fence; close the workflow "
|
|
171
174
|
"only once the request is settled (`com.get`: answered, terminal or outcome_unknown), else leave it open; then the retro. Details: working-with-fabric references/on-demand-session.md."
|
|
@@ -331,7 +334,7 @@ def dispatch_cleared_from(dispatch: Dict[str, str], env: Optional[Dict[str, str]
|
|
|
331
334
|
folder, key = _dispatch_record(dispatch, env)
|
|
332
335
|
try:
|
|
333
336
|
held = json.loads(read_small(folder / (key + ".json")) or "null")
|
|
334
|
-
except ValueError:
|
|
337
|
+
except (ValueError, RecursionError): # 3.9 raises RecursionError on a deep record (FAA-208)
|
|
335
338
|
return None
|
|
336
339
|
if not isinstance(held, dict) or "cleared_from" not in held:
|
|
337
340
|
return None
|
|
@@ -356,7 +359,7 @@ def dispatch_holder(dispatch: Dict[str, str], session_id: Any, env: Optional[Dic
|
|
|
356
359
|
if take_over:
|
|
357
360
|
try:
|
|
358
361
|
held = json.loads(read_small(folder / (key + ".json")) or "null")
|
|
359
|
-
except ValueError:
|
|
362
|
+
except (ValueError, RecursionError):
|
|
360
363
|
held = None
|
|
361
364
|
# no record: no prompt of this process confirmed the run (Fabric's spec 3.5), so the clear holds nothing. On
|
|
362
365
|
# Windows the hook's parent is the Git Bash a hook runs in, a new one each time, so a /clear cannot tell the
|
|
@@ -381,18 +384,28 @@ def dispatch_holder(dispatch: Dict[str, str], session_id: Any, env: Optional[Dic
|
|
|
381
384
|
return True
|
|
382
385
|
# another agent process holds it: this is a session inside the run that cleared, still nested
|
|
383
386
|
try:
|
|
387
|
+
# a `dispatch` that is not a folder raises FileExistsError here: a folder that cannot be written, never an
|
|
388
|
+
# existing record (FAA-208)
|
|
384
389
|
folder.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
390
|
+
except OSError:
|
|
391
|
+
return True
|
|
392
|
+
try:
|
|
385
393
|
fd = os.open(str(folder / (key + ".json")), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
386
394
|
except FileExistsError:
|
|
387
395
|
held = read_small(folder / (key + ".json")) or ""
|
|
388
396
|
try:
|
|
389
397
|
return json.loads(held).get("session") == session_id
|
|
390
|
-
except (ValueError, AttributeError):
|
|
398
|
+
except (ValueError, AttributeError, RecursionError):
|
|
391
399
|
return False
|
|
392
400
|
except OSError:
|
|
393
401
|
return True
|
|
394
402
|
try:
|
|
395
|
-
|
|
403
|
+
try:
|
|
404
|
+
handle = os.fdopen(fd, "w", encoding="utf-8")
|
|
405
|
+
except BaseException: # the descriptor is still open: Windows refuses to unlink an open file (0.79.0 seam)
|
|
406
|
+
os.close(fd)
|
|
407
|
+
raise
|
|
408
|
+
with handle:
|
|
396
409
|
handle.write(json.dumps(record))
|
|
397
410
|
except OSError: # a full disk: no half record that would make this session a stranger later
|
|
398
411
|
try:
|
|
@@ -415,7 +428,7 @@ def dispatch_held_elsewhere(dispatch: Dict[str, str], session_id: Any, env: Opti
|
|
|
415
428
|
return False
|
|
416
429
|
try:
|
|
417
430
|
return json.loads(read_small(folder / (key + ".json")) or "null").get("session") != session_id
|
|
418
|
-
except (ValueError, AttributeError):
|
|
431
|
+
except (ValueError, AttributeError, RecursionError):
|
|
419
432
|
return True
|
|
420
433
|
|
|
421
434
|
|
|
@@ -839,6 +852,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
839
852
|
p_status = sub.add_parser("status", help="where Fabric is and what to do next")
|
|
840
853
|
p_status.add_argument("--json", action="store_true")
|
|
841
854
|
p_status.add_argument("--cwd", default=".")
|
|
855
|
+
# FAA-236: named in --help; the command itself is answered above, before argparse
|
|
856
|
+
p_gate = sub.add_parser("gate-id", help="the commandId of a request's gate question, the one a dispatched run uses")
|
|
857
|
+
p_gate.add_argument("request", help="the board request id, a UUID")
|
|
842
858
|
args = parser.parse_args(argv)
|
|
843
859
|
if args.command != "status":
|
|
844
860
|
parser.print_help()
|
package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py
CHANGED
|
@@ -58,7 +58,7 @@ SESSION_ID = re.compile(r"[A-Za-z0-9._:-]{1,128}")
|
|
|
58
58
|
LOOPBACK_ORIGIN = re.compile(r"http://127\.0\.0\.1:(\d{1,5})")
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
VERSION = "0.
|
|
61
|
+
VERSION = "0.79.1"
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",
|
|
@@ -280,7 +280,9 @@ def read_small(path: Path, cap: int = READ_CAP) -> Optional[str]:
|
|
|
280
280
|
return None
|
|
281
281
|
|
|
282
282
|
|
|
283
|
-
def pid_alive(pid: int) -> bool:
|
|
283
|
+
def pid_alive(pid: int) -> Optional[bool]:
|
|
284
|
+
"""True: a process of this OS account runs under `pid`; False: none does. None (falsy): a process of another
|
|
285
|
+
account does (EPERM), as when the id hub.json names was reused: not verified, never called running (FAA-254)."""
|
|
284
286
|
if pid <= 0:
|
|
285
287
|
return False
|
|
286
288
|
if sys.platform == "win32":
|
|
@@ -298,7 +300,7 @@ def pid_alive(pid: int) -> bool:
|
|
|
298
300
|
try:
|
|
299
301
|
os.kill(pid, 0)
|
|
300
302
|
except PermissionError:
|
|
301
|
-
return
|
|
303
|
+
return None
|
|
302
304
|
except OSError:
|
|
303
305
|
return False
|
|
304
306
|
return True
|
|
@@ -324,7 +326,11 @@ def locate_hub(root: Path, alive=pid_alive) -> Dict[str, Any]:
|
|
|
324
326
|
pid = doc.get("pid")
|
|
325
327
|
if not isinstance(pid, int) or isinstance(pid, bool) or pid <= 0:
|
|
326
328
|
return {"state": "not-a-hub", "reason": "hub.json names no process"}
|
|
327
|
-
|
|
329
|
+
live = alive(pid)
|
|
330
|
+
if live is None: # FAA-254: another account's process under that id, so not this account's Fabric
|
|
331
|
+
return {"state": "not-a-hub", "reason": "hub.json names process %d, which runs under another OS account: "
|
|
332
|
+
"not verified" % pid}
|
|
333
|
+
if not live:
|
|
328
334
|
return {"state": "not-running", "reason": "hub.json names process %d, which is not running" % pid}
|
|
329
335
|
features = doc.get("features")
|
|
330
336
|
features = [f for f in features if isinstance(f, str)] if isinstance(features, list) else []
|
|
@@ -699,7 +705,7 @@ def prune_dispatch_records(env: Optional[Dict[str, str]] = None, now: Optional[f
|
|
|
699
705
|
records = []
|
|
700
706
|
for record in records:
|
|
701
707
|
try:
|
|
702
|
-
if now - record.
|
|
708
|
+
if now - record.lstat().st_mtime > SESSION_STALE_S: # the link's own age: a dangling one goes too (FAA-208)
|
|
703
709
|
record.unlink()
|
|
704
710
|
removed += 1
|
|
705
711
|
except OSError:
|