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

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 (92) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/README.md +18 -5
  3. package/SECURITY.md +25 -8
  4. package/bin/fabric-agent-adapter.js +24 -1
  5. package/package.json +1 -1
  6. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +16 -3
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +44 -7
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +116 -4
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +33 -3
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +257 -10
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +57 -77
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_restart.py +56 -0
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_tree.py +899 -0
  16. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/new_agent.py +308 -0
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +7 -4
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +23 -5
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +1 -1
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +4 -2
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +15 -2
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +16 -3
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +5 -2
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +28 -0
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -3
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +47 -10
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/migrating-a-service.md +1 -0
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +10 -0
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +31 -8
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +21 -0
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +33 -3
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +19 -28
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +9 -6
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +146 -3
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +3 -2
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +2 -0
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +79 -2
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +269 -0
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +64 -5
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -7
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +73 -3
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +98 -1
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +89 -22
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -1
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +39 -6
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -0
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +76 -11
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +8 -153
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +86 -2
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +324 -0
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +34 -3
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +35 -17
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +77 -4
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +103 -1
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +156 -133
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -1
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +257 -10
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +6 -0
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +64 -3
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +246 -24
  64. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +16 -6
  65. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +44 -0
  66. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/communication-agent-template.md +19 -328
  67. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +16 -3
  68. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +1 -1
  69. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/new-agent.md +42 -0
  70. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +44 -7
  71. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/access.schema.json +50 -0
  72. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/connections.schema.json +23 -0
  73. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/data-rooms.schema.json +74 -0
  74. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/manifest.schema.json +170 -0
  75. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/template-header.schema.json +25 -0
  76. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +33 -3
  77. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/template_check.py +491 -0
  78. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/CHANGELOG.md +7 -0
  79. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/TEMPLATE.md +347 -0
  80. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/access.json +24 -0
  81. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/assembly.md +15 -0
  82. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/checks/acceptance.md +17 -0
  83. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/connections.json +29 -0
  84. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/data-rooms.json +9 -0
  85. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/manifest.json +32 -0
  86. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
  87. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +2 -2
  88. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +16 -8
  89. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +46 -31
  90. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +1 -1
  91. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +18 -16
  92. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +23 -13
@@ -209,26 +209,41 @@ adapted, so the reply informs; on a request still open, your reply as its target
209
209
 
210
210
  **A participant reply answers the request** (DEC-0098, DEC-0102). Your Project's `com.reply` (any message of yours
211
211
  whose `replyTo` is the request) moves a queued request to `answered`: no responder claims it any more and its
212
- deadline no longer expires it; only the requester can still cancel it. So reply as a participant only when the reply
213
- is the answer: you did the work, you say why it will not be done, or you are doing it and give its state with the
214
- nearest date (Answering at once, below); the result then follows in the same thread as a second reply. If your Project's
215
- service responder should still do the work (`com.status`'s slot for the capability is `active` or `stale`, or `absent` at a
216
- generation of 1 or more: a holder enrolled on it), do
217
- not reply on the request: tell the person and leave it queued. A session whose own `com.enroll` is refused
218
- `not_authorized` has no route to the grant (before Fabric 0.3.17 every session; from it, where CO-506 grants nothing:
219
- an organization's machine, no live credential, a standing denial, a full ask queue): when its capability's slot is
220
- `absent` at generation 0 (no service of the project ever held it),
221
- it answers the request once that it cannot be taken now and why, does no work on it without the slot, then asks the
222
- 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
223
- `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
224
- opens it; without Python, no `commandId` can be made: tell the person only), tells the person (the question stays
225
- filed even when that is the operator: Fabric keeps its `about` and settles nothing by it yet, CO-519), and stops handling
226
- that request without waiting (Fabric's `board.read-at-start` rule and `com.enroll`'s own tool description say the
227
- same since main `4f3c742a`: the question since `603da12f` (CO-517) in the rule and `12d57bdd` in the description, the slot condition since
228
- `dbfbfce4` and `12d57bdd`, all in Fabric 0.3.17;
229
- on Fabric 0.3.16 this rule wins). Never write "it stays queued" in a reply: the reply itself ends the queue. A
230
- run Fabric started on a board request has no person in its chat to tell: with the slot `absent` at generation 0 it declines,
231
- 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).
212
+ deadline no longer expires it; only the requester can still cancel it. So a request to your Project is answered as the
213
+ pinned contract's DEC-0152 SA-6 says (FAA-311), never by a state reply from a session that holds no slot. A session
214
+ that reads a request to its Project first reads that slot in `com.status` (the request's capability):
215
+
216
+ - `active` (a live holder other than this session): leave the request to that holder, send nothing and ask no one;
217
+ mark it read (`com.read_ack`) when you leave it.
218
+ - Otherwise call `com.enroll` on the slot (from Fabric 0.3.17 a session's own Project is granted `board.respond` there,
219
+ CO-506) and do no work on the request unless it is taken. Taken: you are the holder; `com.claim` it, give its state in
220
+ this turn by a `com.progress` note or a fenced `com.reply` (neither answers the request), then `com.accept`, and
221
+ settle it with `com.complete`, the fenced reply carrying the result.
222
+ - Refused `fenced` (a holder became live meanwhile): as `active`.
223
+ - Refused `not_authorized` (no grant; before Fabric 0.3.17 every session; from it, where CO-506 grants nothing: an
224
+ organization's machine, no live credential, a standing denial, a full ask queue), act by the slot.
225
+ `stale`, `absent` at a generation of 1 or more, or no generation or no row shown: send no
226
+ `com.reply`, leave the request queued for that service, and ask the operator (a holder enrolled on it: a service of
227
+ the Project, or from 0.3.17 a session; the service takes the request when it rejoins, and a reply would take it from
228
+ that service). `absent` at generation 0, with neither the slot nor the Project held: one session of the Project
229
+ replies once that it cannot take the request now, why, and what would lift it (never "it stays queued": the reply
230
+ ends the queue, so what would lift it tells the requester when to submit again), and asks the operator.
231
+ - Refused with any other code: send no reply and decline nothing; with `retryable: true` (`held` among them) try again
232
+ once the cause is gone, otherwise ask the operator, naming the refusal.
233
+
234
+ A session without the slot does no work on the request and gives it no state: work without a slot has no fence and no
235
+ `com.progress`, and a live session or a service may take the request in parallel. Asking the operator is
236
+ `fabric_question_ask` (`kind: access`, `about: board.gate.<capability>`, the `commandId` that
237
+ `python3 <skill-dir>/scripts/fabric_session.py gate-id <request>` prints, the same a run uses, and a `text` naming the
238
+ request, the gate or the refusal, and what opens it; without Python, no `commandId` can be made: tell the person only);
239
+ also tell the person in your chat (the question stays filed even when that is the operator: Fabric keeps its `about`
240
+ and settles nothing by it yet, CO-519), and stop handling that request without waiting. The question answers no request
241
+ and takes nothing from a service. Mark the request read once the operator is asked (or, after a decline, once the
242
+ decline is stored and the operator asked; after a `retryable` refusal, once it is retried). SA-6 asks no one at an
243
+ `active` slot, where FAA-241 asked the operator; Fabric's `board.read-at-start` rule and `com.enroll`'s tool
244
+ description carry the slot condition since main `4f3c742a` (Fabric 0.3.17) and, as DEC-0152's compatibility note reads
245
+ them, still ask at an `active` slot until Fabric's next pin: this rule wins. A run Fabric started on a board request follows the same steps with no person in
246
+ its chat to tell ([on-demand session](on-demand-session.md), step 3).
232
247
 
233
248
  ## Replying
234
249
 
@@ -290,17 +305,17 @@ personal data or private path in a body or an artifact reference: name a vault s
290
305
 
291
306
  ## Answering at once (FAA-205)
292
307
 
293
- A request, an announcement or a question that waits on your Project gets an answer in the same turn you read it: its
294
- state now (what is done, where it lies, a branch or a commit, when it will be ready, what blocks it). Never hold the
295
- answer for a merge, a release or the end of the task; the finished result goes later as a second message in the same
296
- thread. A state with its nearest date is an answer (the operator's rule of 2026-11-04, board thread `11646774`): to a
297
- request it moves the request to `answered` as any participant reply does (above), so where your Project's service
298
- responder should still do the work, tell the person and leave the request queued instead (a session's own refused
299
- `com.enroll` with its slot `absent` at generation 0 answers once that it cannot be taken now, and at any slot state asks
300
- the operator, above). To an announcement the state is its **row** line. A responder holding the request's slot gives its state in the turn
301
- it claims by a `com.progress` note or a fenced reply, neither of which answers the request, and settles it with
302
- `com.complete` (DEC-0152 SA-6, proposed). Fabric's `fabric_whoami` gives a state as a `com.reply` and a slot holder's as a
303
- `com.progress` since Fabric 0.3.16 (`fff4b6d9`); the responder's exception above is this page's.
308
+ An announcement or a question that waits on your Project gets an answer in the same turn you read it: its state now
309
+ (what is done, where it lies, a branch or a commit, when it will be ready, what blocks it). Never hold the answer for a
310
+ merge, a release or the end of the task; the finished result goes later as a second message in the same thread. A
311
+ state with its nearest date is an answer (the operator's rule of 2026-11-04, board thread `11646774`; DEC-0152 SA-2).
312
+ To an announcement the state is its **row** line. A request to your Project is answered only as SA-6 says (above): by
313
+ the holder of its capability's slot, which gives its state in the turn it claims by a `com.progress` note or a fenced
314
+ reply, neither of which answers the request, and settles it with `com.accept`, then `com.complete`; or by one decline
315
+ when the slot is `absent` at generation 0. A session without the slot sends a request no state: at an `active` slot it
316
+ leaves it to that holder and asks no one, and after a refused `com.enroll` it does what the refusal's line above says
317
+ (DEC-0152 SA-6, accepted 2026-11-07, at the pin; FAA-311). Fabric's `fabric_whoami` gives a state as a `com.reply` and a
318
+ slot holder's as a `com.progress` since Fabric 0.3.16 (`fff4b6d9`); for a request this page's rule wins.
304
319
  The session-start preamble and the unread line both say it; a session started on a board request answers only that
305
320
  request and gets no unread line.
306
321
 
@@ -27,7 +27,7 @@ session start and turn; FAA-183 tracks making the plugin find the interpreter it
27
27
 
28
28
  | Event | When it acts | What it does |
29
29
  |---|---|---|
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, read from the `.claude.json` in `CLAUDE_CONFIG_DIR` when it is set, else `~/.claude.json`, where Claude Code reads it; FAA-218, FAA-234; a local entry is the one keyed by the folder's git root, or the folder itself outside git, never an ancestor's, as Claude Code applies it, FAA-235) | 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; a measured win also to `agents-db.contribute`, FAA-277); 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. On a Mac, a registered command that is an absolute path to another user's copy of `fabric-mcp` (outside the home, neither this user's nor root's; Claude Code's entry, Codex's `config.toml`, Kimi Code's `mcp.json`) gets one line first, also when nothing else is said, naming it and `fabric_session.py status`, which says how to register your own; a bare name is left to `status`, which looks it up on `PATH` (FAA-232). |
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, read from the `.claude.json` in `CLAUDE_CONFIG_DIR` when it is set, else `~/.claude.json`, where Claude Code reads it; FAA-218, FAA-234; a local entry is the one keyed by the folder's git root, or the folder itself outside git, never an ancestor's, as Claude Code applies it, FAA-235) | 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 an announcement or question that waits on the Project answered in the same turn with its state, the result later (FAA-205), a request through its capability's slot: an `active` holder keeps it and nobody is asked, and only a holder gives its state (DEC-0152 SA-6, FAA-311); `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; a measured win also to `agents-db.contribute`, FAA-277); 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. On a Mac, a registered command that is an absolute path to another user's copy of `fabric-mcp` (outside the home, neither this user's nor root's; Claude Code's entry, Codex's `config.toml`, Kimi Code's `mcp.json`) gets one line first, also when nothing else is said, naming it and `fabric_session.py status`, which says how to register your own; a bare name is left to `status`, which looks it up on `PATH` (FAA-232). |
31
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) |
@@ -80,10 +80,11 @@ PREAMBLE = (
80
80
  "[fabric-agent-adapter] Fabric is running and this {host} has its MCP server `fabric` registered. "
81
81
  "Registration is not proof of connection or admission. "
82
82
  "Call `fabric_whoami` first. Then read the project board: `com.status` (is it ready, how many unread), "
83
- "`com.list` for the messages, and `com.read_ack` only for what you actually read. A request, announcement or "
83
+ "`com.list` for the messages, and `com.read_ack` only for what you actually read. An announcement or "
84
84
  "question waiting on your Project gets an answer in the same turn: its state now (done, where, when, what "
85
- "blocks), the result later in a second message; a request your Project's service should take stays queued for "
86
- "it. Before Fabric 0.3.17 a run Fabric starts on a request no session can take declines it and the asker resends; "
85
+ "blocks), the result later in a second message. A request to your Project: read its capability's slot in "
86
+ "`com.status` first; `active`, leave it to that holder and ask no one; otherwise `com.enroll`, and only its "
87
+ "holder gives its state (`com.progress`); without the slot, no work and no state on it. Before Fabric 0.3.17 a run Fabric starts on a request no session can take declines it and the asker resends; "
87
88
  "from 0.3.17 (CO-506) the session's own Project is granted `board.respond` at `com.enroll`, so you or that run "
88
89
  "can take it (`com.enroll`, `com.claim`). "
89
90
  "Before you start work, call `fabric_work_list` (who is working on what here) and "
@@ -147,43 +148,44 @@ DISPATCH_ID = re.compile(r"[A-Za-z0-9._-]{1,64}")
147
148
  _WINDOWS = os.name == "nt"
148
149
  DISPATCH_LINE = re.compile(r"fabric-dispatch/1 workflow=(%(workflow)s) handoff=(%(handoff)s) request=(%(request)s)"
149
150
  % _DISPATCH_VALUE)
150
- DISPATCH_HEAD = "[fabric-agent-adapter] Fabric started this session on one board request ({request}; workflow {workflow})."
151
+ DISPATCH_HEAD = "[fabric-agent-adapter] Fabric started this session on one board request ({request}; workflow {workflow}; handoff {handoff})."
151
152
  #: Before the prompt is seen (session start; Codex and Kimi have no prompt hook) the contract is conditional.
152
153
  DISPATCH_IF = (
153
- "[fabric-agent-adapter] This session is the run on a board request only when its own prompt's first line is "
154
+ "[fabric-agent-adapter] This session is a board request's run only when its own prompt's first line is "
154
155
  "exactly `fabric-dispatch/1 workflow={workflow} handoff={handoff} request={request}`. No such line: it is a helper; touch none of the three."
155
156
  " Other values: no run; say so in the request's thread and stop. The run:")
156
157
  #: The conditional contract at its longest (a 64-character handoff and a session id) is held under Hermes' 4000
157
158
  #: characters by test_a_native_bridge_is_always_told_the_contract_and_writes_no_record; measure before adding words
158
- #: (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).
159
+ #: (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, 3996 with FAA-311: a new rule takes its characters back from the text around it).
159
160
  DISPATCH_CONTRACT = (
160
161
  " Do that "
161
162
  "request and end; never wait for a person. 1. `fabric_whoami`, then the packet "
162
163
  "(`observatory_checkpoint_latest` {{workflowId: {workflow}}}): obey its constraints first; its goal describes "
163
164
  "the request; artifacts: capability, deadline, run-ends-at (ids: the line's first). Read the request (`com.get` "
164
165
  "{{message: <request>}}) and its thread (`com.list` {{thread: <the `thread` com.get answered>}}). 2. "
165
- "`observatory_handoff_accept` with {handoff}, the `owner`, `idempotencyKey` and `executor` the launcher's prompt names "
166
+ "`observatory_handoff_accept` with the handoff above, the `owner`, `idempotencyKey` and `executor` the launcher's prompt names "
166
167
  "(when it names none: your `owner` `agent:<name>`, the key `accept-<handoff>`, no executor) and this session's own "
167
168
  "`sessionId` (the same on a retry); keep its `leaseId`. A lapsed or refused handoff holds "
168
- "no lease: end the run without touching the request. 3. `com.status` once ("
169
- "epoch, generation); `com.enroll` (the capability), `com.claim` (the request), "
170
- "`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) "
171
- "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 "
169
+ "no lease: end the run without touching the request. 3. `com.status` (epoch, slot)."
170
+ " Slot `active` or enroll refused `fenced`: leave it to its holder, ask no one, then 7; else "
171
+ "`com.enroll` (the capability), `com.claim` (the request), `com.accept`, `com.effect_begin` before an effect; "
172
+ "a refused enroll: no slot, work or reply but one `com.reply` (replyTo it) declining with the reason (resend then) "
173
+ "if not_authorized, queued and the slot unheld `absent` at generation 0; retryable: retry, else `fabric_question_ask` {{kind: access, about: board.gate.<capability>, commandId: {gate}, text: request, gate or refusal, 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 "
172
174
  "`com.reconcile` and an observed result. After the lease, claim and reply: an in_progress checkpoint (`workflowId`, "
173
175
  "`leaseId`). Every checkpoint repeats `goal`, `constraints`, `artifacts` and `credentials` of "
174
176
  "`checkpoint.body` (not the answer's top-level `credentials` or the accept's "
175
177
  "`pack`) unchanged (add `open`, `done` or `notes`; no value). 4. Do the work; renew (`com.renew`/`com.progress`) every 20 s; "
176
178
  "work that cannot end by run-ends-at posts its state (`com.progress`); "
177
179
  "if it called `com.effect_begin`, or when the request's deadline is not after run-ends-at plus 10 minutes, `com.complete` failed_known (observed: true) saying so and steps 6 and 7; "
178
- "otherwise it is left accepted for the next run, an in_progress checkpoint without `close`, steps 5 and 6 "
179
- "skipped. Money, deletion, publication: `fabric_effect_request`; a sign-in, one-time code, secret "
180
+ "otherwise it is left accepted for the next run: skip 5 and 6, an in_progress checkpoint, no `close`."
181
+ " Money, deletion, publication: `fabric_effect_request`; a sign-in, one-time code, secret "
180
182
  "typed in or handed out, or payment is a person's (none is in this chat), and so is a production step unless the operator's "
181
183
  "words, relayed verbatim, dated and sourced, from his trusted projects with a scope naming it, approve it: name it in "
182
- "the answer. 5. `com.reply` in the thread, then "
184
+ "the answer. 5. `com.reply`, then "
183
185
  "`com.complete` (observed: true): completed, or failed_known with the reason. 6. Once the board took it, "
184
186
  "`observatory_checkpoint_write` with `workflowId` {workflow}, a `stepId`, "
185
- "the `leaseId`, `close: true`, a status, the packet's body plus `done` and `notes`: done for completed; for "
186
- "failed_known blocked with the reason in notes. 7. Write the retro "
187
+ "the `leaseId`, `close: true`, `status` done if completed, blocked if failed_known (reason in notes), the "
188
+ "packet's body plus `done` and `notes`. 7. Write the retro "
187
189
  "(`observatory_lesson` and insight/1 lines in `insights/`, or one line that there is nothing; no "
188
190
  "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 "
189
191
  "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."
@@ -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.80.2"
61
+ VERSION = "0.82.0"
62
62
 
63
63
 
64
64
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",
@@ -280,23 +280,33 @@ def read_small(path: Path, cap: int = READ_CAP) -> Optional[str]:
280
280
  return None
281
281
 
282
282
 
283
+ def _windows_pid_alive(pid: int, kernel: Any = None, last_error: Any = None) -> Optional[bool]:
284
+ """pid_alive on Windows, where os.kill(pid, 0) would send CTRL_C_EVENT: OpenProcess with query access only. Refused
285
+ with ERROR_ACCESS_DENIED (5), another account's or an elevated process: None, as Node's process.kill(pid, 0)
286
+ answers EPERM there (FAA-304); refused otherwise, no process. `kernel` and `last_error` stand in for kernel32 and
287
+ ctypes.get_last_error in a test."""
288
+ import ctypes
289
+ if kernel is None:
290
+ kernel = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore[attr-defined]
291
+ last_error = ctypes.get_last_error # type: ignore[attr-defined]
292
+ handle = kernel.OpenProcess(0x1000, False, pid) # PROCESS_QUERY_LIMITED_INFORMATION
293
+ if not handle:
294
+ return None if last_error() == 5 else False # ERROR_ACCESS_DENIED: not verified, never called running
295
+ try:
296
+ code = ctypes.c_ulong()
297
+ return bool(kernel.GetExitCodeProcess(handle, ctypes.byref(code))) and code.value == 259 # STILL_ACTIVE
298
+ finally:
299
+ kernel.CloseHandle(handle)
300
+
301
+
283
302
  def pid_alive(pid: int) -> Optional[bool]:
284
303
  """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)."""
304
+ account does (EPERM; on Windows, OpenProcess refused with access denied, FAA-304), as when the id hub.json names
305
+ was reused: not verified, never called running (FAA-254)."""
286
306
  if pid <= 0:
287
307
  return False
288
308
  if sys.platform == "win32":
289
- # os.kill(pid, 0) on Windows sends CTRL_C_EVENT: never use it to probe.
290
- import ctypes
291
- kernel = ctypes.windll.kernel32 # type: ignore[attr-defined]
292
- handle = kernel.OpenProcess(0x1000, False, pid) # PROCESS_QUERY_LIMITED_INFORMATION
293
- if not handle:
294
- return False
295
- try:
296
- code = ctypes.c_ulong()
297
- return bool(kernel.GetExitCodeProcess(handle, ctypes.byref(code))) and code.value == 259 # STILL_ACTIVE
298
- finally:
299
- kernel.CloseHandle(handle)
309
+ return _windows_pid_alive(pid)
300
310
  try:
301
311
  os.kill(pid, 0)
302
312
  except PermissionError: