@passioncode-ai/fabric-agent-adapter 0.78.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 +122 -0
- package/README.md +14 -7
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
- 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/scripts/fabric_provider.py +14 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +1 -1
- 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/lifecycle.md +6 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +3 -0
- 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 +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +17 -5
- 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 +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +64 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +215 -25
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +10 -5
- 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/where-agents-run.md +4 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +10 -9
- 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/scripts/fabric_session_cli.py +26 -10
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +11 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,127 @@
|
|
|
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
|
+
|
|
3
125
|
## 0.78.0 (2026-11-12)
|
|
4
126
|
|
|
5
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
|
package/README.md
CHANGED
|
@@ -24,7 +24,9 @@ The skills are `adapting-projects-to-fabric`, `creating-fabric-agents`,
|
|
|
24
24
|
a terminal that works in a project through Fabric. It finds Fabric (`hub.json`, its `pid`), has the machine's
|
|
25
25
|
lifecycle broker start it when it is down, registers `fabric-mcp` once with no secret, calls `fabric_whoami`
|
|
26
26
|
first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read), answers what waits on its
|
|
27
|
-
Project in the same turn with its state (the result later; a request
|
|
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,
|
|
54
|
+
must name Python 3.9 or later; FAA-183). `fabric_session.py watch-mail` waits for board mail under a background watcher, `gate-id <request>` prints the
|
|
55
|
+
`commandId` of a request's gate question (the one a dispatched run uses), and `install-host-hooks
|
|
53
56
|
--host codex|kimi|hermes|opencode|openclaw|pi` installs each host's native context bridge (dry run unless `--apply`).
|
|
54
57
|
Codex receives structured SessionStart JSON; Hermes, OpenCode, OpenClaw and Pi use native plugin APIs.
|
|
55
58
|
See the [native host matrix](plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md)
|
|
@@ -204,18 +207,20 @@ With Python 3.9+ and the Claude Code CLI, in a throwaway directory and a tempora
|
|
|
204
207
|
```bash
|
|
205
208
|
KIT=plugins/fabric-agent-adapter/skills/building-fabric-services/scripts # or ~/.agents/skills/building-fabric-services/scripts
|
|
206
209
|
DATA=$(mktemp -d); SERVICES=$(mktemp -d); WORK=$(mktemp -d)
|
|
207
|
-
|
|
208
|
-
python3
|
|
210
|
+
# A free loopback port, so the sample never collides with a service already running here.
|
|
211
|
+
PORT=$(python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1])')
|
|
212
|
+
python3 "$KIT/sample_service.py" serve --port "$PORT" --data-dir "$DATA" &
|
|
213
|
+
python3 "$KIT/sample_service.py" register --port "$PORT" --data-dir "$DATA" --services-dir "$SERVICES"
|
|
209
214
|
# The probe exits 0 (no FAIL); only then is the service registered for one client run (listener.owner proves the port
|
|
210
215
|
# is this account's, and the client cannot check it). The token goes in a header, never in argv.
|
|
211
216
|
# On macOS 27.2 run these with Apple's /usr/bin/python3: an ad-hoc-signed Python, such as Homebrew's, sees no other
|
|
212
217
|
# process's sockets (FAA-272).
|
|
213
218
|
python3 "$KIT/check_service.py" sample --services-dir "$SERVICES" && \
|
|
214
|
-
python3 - "$DATA/service.token" "$WORK/mcp.json" <<'EOF'
|
|
219
|
+
python3 - "$DATA/service.token" "$WORK/mcp.json" "$PORT" <<'EOF'
|
|
215
220
|
import json, os, sys
|
|
216
221
|
token = open(sys.argv[1]).read().strip()
|
|
217
222
|
with os.fdopen(os.open(sys.argv[2], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), "w") as f:
|
|
218
|
-
json.dump({"mcpServers": {"sample": {"type": "http", "url": "http://127.0.0.1
|
|
223
|
+
json.dump({"mcpServers": {"sample": {"type": "http", "url": "http://127.0.0.1:%s/mcp" % sys.argv[3],
|
|
219
224
|
"headers": {"Authorization": "Bearer " + token}}}}, f)
|
|
220
225
|
EOF
|
|
221
226
|
(cd "$WORK" && claude -p "Call the tool mcp__sample__sample_echo with text 'ping' and reply with only the text it returns." \
|
|
@@ -230,6 +235,8 @@ the CLI connected to the sample over streamable HTTP, called `sample.echo` and p
|
|
|
230
235
|
([record](docs/evidence/runs/2026-11-08-0.77.0-quick-start.md)). The probe prints one line per rule
|
|
231
236
|
(`PASS`, `FAIL` or `NOT_RUN` with its evidence) and exits 1 on any `FAIL`; run it against your
|
|
232
237
|
own service by its id once its installer has written the descriptor.
|
|
238
|
+
The free-port line (FAA-283) was run again on 2026-11-12 from this repository's kit with Claude Code 2.1.299 and
|
|
239
|
+
Apple's `/usr/bin/python3`: a port the system chose, 44 rules, 0 FAIL, `listener.owner` PASS, and `ping`.
|
|
233
240
|
`FABRIC_REAL_CLIENT=1 python3 -m unittest discover -s test -p test_real_client.py` repeats the connection check
|
|
234
241
|
(every sample tool listed, no model call) in the test suite.
|
|
235
242
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "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'"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
5
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks; and put an agent on a standing watch over a product (observer mode).",
|
|
6
|
-
"version": "0.
|
|
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
|
{
|
|
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
9
9
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
10
10
|
metadata:
|
|
11
11
|
author: PassionCode.ai
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.79.1"
|
|
13
13
|
contract-version: "0.1.0"
|
|
14
14
|
contract-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
15
15
|
---
|
|
@@ -100,6 +100,7 @@ is unavailable, continue only through local structure and mark exact schema vali
|
|
|
100
100
|
|
|
101
101
|
### 3. Scaffold the provider bundle
|
|
102
102
|
|
|
103
|
+
If your organization keeps a private catalogue of agent templates with an architect, ask it first: it names the template, version, variant and known traps; this skill runs after it.
|
|
103
104
|
Before scaffolding, ask the [agents database](references/agents-database.md) for an audit of the project
|
|
104
105
|
(`agents-db.audit`) and carry its `optimize` proposals into the completion report.
|
|
105
106
|
Load [provider bundle](references/provider-bundle.md) before creating or mapping files.
|
|
@@ -290,9 +291,10 @@ host's `open` and `fallback=never`. Never `open <url>`, `open -a Safari`, `xdg-o
|
|
|
290
291
|
`not_installed` host; an installed host that failed to open is reported, never replaced by HTTP.
|
|
291
292
|
Links carry no token or login code.
|
|
292
293
|
|
|
293
|
-
The agent's `AGENTS.md` carries this as its dashboard rule block
|
|
294
|
-
(`scripts/check_dashboard_link.py` from this skill's own directory
|
|
295
|
-
completion and notification action before delivery
|
|
294
|
+
The agent's `AGENTS.md` carries this as its dashboard rule block; the agent runs the adapter's link gate
|
|
295
|
+
(`scripts/check_dashboard_link.py` from this skill's own directory, an agent-side gate never copied into the project or
|
|
296
|
+
run by its CI) on every completion and notification action before delivery, and the project's own test pins the
|
|
297
|
+
negative fixture:
|
|
296
298
|
[dashboard links](references/dashboard-links.md) has the block, the gate's input and the remote case.
|
|
297
299
|
Headless agents need no dashboard.
|
|
298
300
|
|
|
@@ -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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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`)
|
|
61
|
-
|
|
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
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py
CHANGED
|
@@ -61,8 +61,12 @@ _URL = re.compile(URL_PATTERN)
|
|
|
61
61
|
_MANIFEST = re.compile(MANIFEST_PATTERN[:-1] + r"\Z")
|
|
62
62
|
# The manifest's name after a backslash: valid since DEC-0145, but no writer emits it until every reader accepts it.
|
|
63
63
|
_MANIFEST_AFTER_BACKSLASH = re.compile(r"\\fabric-agent\.json\Z")
|
|
64
|
-
# format: uri (RFC 3986 absolute URI)
|
|
65
|
-
|
|
64
|
+
# format: uri (RFC 3986 absolute URI) as ajv-formats reads it: RFC 3986 characters only (unreserved, reserved and %HH),
|
|
65
|
+
# the class the service kit's descriptor check uses (fabric_descriptor `_SOURCE_URI`, 0.49.3); a byte-order mark, a
|
|
66
|
+
# zero-width space, a non-ASCII path or an IDN host is refused (FAA-127).
|
|
67
|
+
_URI = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})+\Z")
|
|
68
|
+
# Brackets belong to an IP-literal host only (RFC 3986 3.2.2); the literal is set aside before the character check.
|
|
69
|
+
_IP_LITERAL = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@\[\]]*@)?)\[[0-9A-Fa-f:.vV]+\]")
|
|
66
70
|
# format: date-time (RFC 3339).
|
|
67
71
|
_TIMESTAMP = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?([Zz]|[+-][0-9]{2}:[0-9]{2})\Z")
|
|
68
72
|
FIELDS = {"protocol", "id", "providerId", "name", "summary", "manifest", "run", "source", "installedAt", "installedBy", "extensions"}
|
|
@@ -145,11 +149,14 @@ def validate_provider_entry(entry: Any) -> List[str]:
|
|
|
145
149
|
source = entry["source"]
|
|
146
150
|
if not isinstance(source, dict) or not set(source) <= {"repository"}:
|
|
147
151
|
problems.append("source is {repository?}")
|
|
152
|
+
elif "repository" in source and isinstance(source["repository"], str) and (
|
|
153
|
+
_URI_PASSWORD.match(source["repository"]) or _secrets.repository_credential(source["repository"])):
|
|
154
|
+
# FAA-120, as the service kit: a repository is named, never reached with a credential in its URI. Said
|
|
155
|
+
# before the character check, so a password beside a character the URI class refuses (a backslash, as
|
|
156
|
+
# git reads one) is still named a credential (FAA-127).
|
|
157
|
+
problems.append("source.repository carries no password or credential: it names the repository")
|
|
148
158
|
elif "repository" in source and not _is_uri(source["repository"]):
|
|
149
159
|
problems.append("source.repository is an absolute URI")
|
|
150
|
-
elif "repository" in source and (_URI_PASSWORD.match(source["repository"]) or _secrets.repository_credential(source["repository"])):
|
|
151
|
-
# FAA-120, as the service kit: a repository is named, never reached with a credential in its URI.
|
|
152
|
-
problems.append("source.repository carries no password or credential: it names the repository")
|
|
153
160
|
if not isinstance(entry["installedAt"], str) or not _TIMESTAMP.fullmatch(entry["installedAt"]):
|
|
154
161
|
problems.append("installedAt is an RFC 3339 date-time")
|
|
155
162
|
if not isinstance(entry["installedBy"], str) or not 1 <= len(entry["installedBy"]) <= 200:
|
|
@@ -161,7 +168,7 @@ def validate_provider_entry(entry: Any) -> List[str]:
|
|
|
161
168
|
|
|
162
169
|
|
|
163
170
|
def _is_uri(value: Any) -> bool:
|
|
164
|
-
return isinstance(value, str) and bool(_URI.fullmatch(value))
|
|
171
|
+
return isinstance(value, str) and bool(_URI.fullmatch(_IP_LITERAL.sub(r"\1h", value, count=1)))
|
|
165
172
|
|
|
166
173
|
|
|
167
174
|
def _run_problems(run: Any) -> List[str]:
|
|
@@ -255,7 +262,7 @@ def write_provider_entry(entry: Dict[str, Any], directory: Optional[Path] = None
|
|
|
255
262
|
# re-pins (CT-192), so the writer does not emit it yet.
|
|
256
263
|
raise ProviderError("The manifest's name follows a backslash, which a reader that has not re-pinned to DEC-0145 refuses (CT-192). "
|
|
257
264
|
"Write the path with / before the name: ~/.../fabric-agent.json, which every Fabric release reads, or "
|
|
258
|
-
"C:/.../fabric-agent.json, which Fabric reads only
|
|
265
|
+
"C:/.../fabric-agent.json, which Fabric reads only from v0.3.16 (its repin, CO-479).")
|
|
259
266
|
problems = manifest_problems(entry)
|
|
260
267
|
if problems:
|
|
261
268
|
raise ProviderError("Provider entry is invalid: %s." % "; ".join(problems))
|
|
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
9
9
|
compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
10
10
|
metadata:
|
|
11
11
|
author: PassionCode.ai
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.79.1"
|
|
13
13
|
contract-version: "0.1.0"
|
|
14
14
|
extension: "fabric-service/0.1"
|
|
15
15
|
extension-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.6 · Fabric's button (Fabric Agent Contract DEC-0040, FB-1…FB-8, FL-1…FL-6)
|
|
2
2
|
Load it same-origin, after fabric-dashboard.css, with the service it opens Fabric on:
|
|
3
3
|
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent"
|
|
4
4
|
data-project="project-a" data-project-name="Project A"></script>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
(function (root) {
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
|
-
var VERSION = '1.1.
|
|
14
|
+
var VERSION = '1.1.6';
|
|
15
15
|
var COOKIE = 'fabric_button';
|
|
16
16
|
var GET_FABRIC = 'https://passioncode.ai/fabric';
|
|
17
17
|
var ABSENT_AFTER_MS = 1500;
|
|
@@ -69,7 +69,10 @@
|
|
|
69
69
|
if (piece === '') return;
|
|
70
70
|
var at = piece.indexOf('=');
|
|
71
71
|
var name = firstKey(at < 0 ? piece : piece.slice(0, at));
|
|
72
|
-
|
|
72
|
+
var value = at < 0 ? '' : decodeKeepingPlus(piece.slice(at + 1));
|
|
73
|
+
var list = values.get(name);
|
|
74
|
+
if (list) list.push(value); // appended in place, as the contract's secret-shape.ts does (FAA-245)
|
|
75
|
+
else values.set(name, [value]);
|
|
73
76
|
});
|
|
74
77
|
return values;
|
|
75
78
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.6 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
A Fabric service dashboard shown inside a host pane: usable from 360 CSS px up, navigation always
|
|
3
3
|
reachable, wide content in its own scroll box, one vertical scroller, appearance from the host.
|
|
4
4
|
Copy this file next to fabric-dashboard.js; do not edit it in place. Put the page's own rules in
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.6 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
Load it same-origin with <script src="fabric-dashboard.js" defer></script>: no inline script, no
|
|
3
3
|
eval, nothing from a CDN. It applies the host's appearance (ED-6, ED-8), folds navigation that
|
|
4
4
|
does not fit into a More menu (ED-3), makes wide boxes keyboard-reachable (ED-4) and keeps
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
var KIT = 'fabric-dashboard-kit';
|
|
11
|
-
var VERSION = '1.1.
|
|
11
|
+
var VERSION = '1.1.6';
|
|
12
12
|
/* fabric-dashboard-kit:theme-ids:begin */
|
|
13
13
|
var THEMES = ['graphite', 'fabric', 'paper', 'forest', 'ocean', 'ember', 'contrast'];
|
|
14
14
|
/* fabric-dashboard-kit:theme-ids:end */
|
|
@@ -148,6 +148,20 @@
|
|
|
148
148
|
root.addEventListener('focus', applyAppearance);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/* ---- resize observers write in the next frame (since 1.1.6) ----
|
|
152
|
+
A ResizeObserver callback that changes the size of what it watches, or of an ancestor, makes WebKit raise
|
|
153
|
+
"ResizeObserver loop completed with undelivered notifications". So a callback only schedules the work for the next
|
|
154
|
+
animation frame, cancelling one already scheduled, and the work itself skips a size it has already laid out. */
|
|
155
|
+
function nextFrame(work) {
|
|
156
|
+
var pending = null;
|
|
157
|
+
var raf = root.requestAnimationFrame;
|
|
158
|
+
return function () {
|
|
159
|
+
if (typeof raf !== 'function') { work(); return; }
|
|
160
|
+
if (pending !== null) root.cancelAnimationFrame(pending);
|
|
161
|
+
pending = raf.call(root, function () { pending = null; work(); });
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
151
165
|
/* ---- navigation: fold what does not fit into More (ED-3) ---- */
|
|
152
166
|
function foldNav(nav) {
|
|
153
167
|
if (typeof nav.fdRefresh === 'function') { nav.fdRefresh(); return; } // folded already: refit, no second observers
|
|
@@ -212,8 +226,16 @@
|
|
|
212
226
|
}
|
|
213
227
|
}
|
|
214
228
|
layout();
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
// The fit depends on the nav's width only: a callback for a height the fold itself changed lays nothing out.
|
|
230
|
+
var laidOutWidth = null;
|
|
231
|
+
var refit = nextFrame(function () {
|
|
232
|
+
var width = nav.clientWidth;
|
|
233
|
+
if (width === laidOutWidth) return;
|
|
234
|
+
laidOutWidth = width;
|
|
235
|
+
layout();
|
|
236
|
+
});
|
|
237
|
+
if (typeof root.ResizeObserver === 'function') new root.ResizeObserver(refit).observe(nav);
|
|
238
|
+
else root.addEventListener('resize', refit);
|
|
217
239
|
// A page that moves aria-current (its route changed) gets a fresh summary and fit, without telling the kit.
|
|
218
240
|
if (typeof root.MutationObserver === 'function') {
|
|
219
241
|
new root.MutationObserver(layout).observe(nav, { subtree: true, attributes: true, attributeFilter: ['aria-current'] });
|
|
@@ -255,12 +277,16 @@
|
|
|
255
277
|
var header = chrome.querySelector('.fd-header');
|
|
256
278
|
var lift = 0;
|
|
257
279
|
if (height > limit && header) lift = Math.min(Math.ceil(header.getBoundingClientRect().height), height);
|
|
258
|
-
|
|
259
|
-
|
|
280
|
+
var top = lift ? -lift + 'px' : '0px';
|
|
281
|
+
var pinned = Math.max(0, height - lift) + 'px';
|
|
282
|
+
// An unchanged value is not written again: a write restyles the page and can wake the observer once more.
|
|
283
|
+
if (html.style.getPropertyValue('--fd-chrome-top') !== top) html.style.setProperty('--fd-chrome-top', top);
|
|
284
|
+
if (html.style.getPropertyValue('--fd-chrome-h') !== pinned) html.style.setProperty('--fd-chrome-h', pinned);
|
|
260
285
|
}
|
|
261
286
|
set();
|
|
262
|
-
|
|
263
|
-
root.
|
|
287
|
+
var reset = nextFrame(set);
|
|
288
|
+
if (typeof root.ResizeObserver === 'function') new root.ResizeObserver(reset).observe(chrome);
|
|
289
|
+
root.addEventListener('resize', reset);
|
|
264
290
|
}
|
|
265
291
|
|
|
266
292
|
function init() {
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-dashboard-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"profile": "fabric-dashboard/0.1",
|
|
5
5
|
"decision": "DEC-0036",
|
|
6
6
|
"declarationKey": "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"contrast"
|
|
16
16
|
],
|
|
17
17
|
"files": {
|
|
18
|
-
"fabric-dashboard.css": "
|
|
19
|
-
"fabric-dashboard.js": "
|
|
20
|
-
"fabric-button.js": "
|
|
18
|
+
"fabric-dashboard.css": "7a90bbe1f1a2170df5dac0f596a3f302167bef9a7243457bb21dd0eb34f4d359",
|
|
19
|
+
"fabric-dashboard.js": "146ec9a0fd50af2df3245c02ec7dfba75b7d4e8cb5476fedb5ef727869fef1cc",
|
|
20
|
+
"fabric-button.js": "60c2b6d44571213e0237c29a94a4cf220f9de3efcfa697ac6a33777d1ad25fd6",
|
|
21
21
|
"themes.json": "511839f1a1a83eb37fadfd132791474497d5c28c5091700b9aa31ad076e359b5"
|
|
22
22
|
},
|
|
23
23
|
"history": {
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
"fabric-dashboard.css": "cc7a96fa2c5eeb03d113e7a543ed88d43ce78c2808c411de199ea0fc6d55b957",
|
|
55
55
|
"fabric-dashboard.js": "5df01193c590a1d8aa82a521954902fecf2d7f655df482165f90d7291b00087f",
|
|
56
56
|
"fabric-button.js": "e945b3252f9a246826d06dbe7bd4be66e82d539a33728c3d3043fd61d4cbd681"
|
|
57
|
+
},
|
|
58
|
+
"1.1.6": {
|
|
59
|
+
"fabric-dashboard.css": "7a90bbe1f1a2170df5dac0f596a3f302167bef9a7243457bb21dd0eb34f4d359",
|
|
60
|
+
"fabric-dashboard.js": "146ec9a0fd50af2df3245c02ec7dfba75b7d4e8cb5476fedb5ef727869fef1cc",
|
|
61
|
+
"fabric-button.js": "60c2b6d44571213e0237c29a94a4cf220f9de3efcfa697ac6a33777d1ad25fd6"
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -26,8 +26,10 @@ skill's business (working-with-fabric, `references/project-board.md`).
|
|
|
26
26
|
|
|
27
27
|
A **request** is addressed to a Project and one of its capabilities (`video.render`), never to a service. One
|
|
28
28
|
**responder slot** exists per Project and capability; a service holds it under a **`board.respond` grant** the
|
|
29
|
-
operator gives in Fabric (
|
|
30
|
-
`board:<project id>/<capability>`. A pid, a descriptor or a localhost origin authorizes nothing.
|
|
29
|
+
operator gives in Fabric (Settings → Agent access): callee `fabric`, capability `board.respond`, resource
|
|
30
|
+
`board:<project id>/<capability>`. A pid, a descriptor or a localhost origin authorizes nothing. The capability is
|
|
31
|
+
never `project.work`: DEC-0140 PR-1 reserves it for a Project's primary responder, a coding session, and `Responder`
|
|
32
|
+
refuses it with `invalid_arguments` (FAA-244).
|
|
31
33
|
|
|
32
34
|
1. Ask once, through the hub's door: `HubClient().request_board_access(agent_id, project_id, [capability], reason)`
|
|
33
35
|
(`requestBoardAccess`). Keep the answer's `pollSecret` in memory or a 0600 file.
|
|
@@ -37,8 +39,9 @@ operator gives in Fabric (Agents & access): callee `fabric`, capability `board.r
|
|
|
37
39
|
3. Each connection joins with `fabric.board.join {projectId}` (`join_board` / `joinBoard`). It re-reads `hub.json`,
|
|
38
40
|
checks the pid it names is alive, tells the owner of the door's listener from the socket table (DEC-0154 HA-2:
|
|
39
41
|
another OS account's process, or one whose owner cannot be told, is `HubError` code `foreign-user`, retryable, with
|
|
40
|
-
nothing sent; a retry never clears a restricted macOS table, `table-restricted` in protocol.md, FAA-272), and only then sends the credential (a session already running
|
|
41
|
-
|
|
42
|
+
nothing sent; a retry never clears a restricted macOS table, `table-restricted` in protocol.md, FAA-272), and only then sends the credential (a session already running carries that refusal as `BoardError` code
|
|
43
|
+
`foreign-user`, one of `SESSION_CODES`, to `board.down` with reason `foreign-user` and a sentence that says nothing
|
|
44
|
+
was sent, never `unreachable`'s "a write may have been stored", FAA-252). A missing grant is `HubError` code
|
|
42
45
|
`not_authorized` whose `data.ask` holds the arguments to ask with.
|
|
43
46
|
|
|
44
47
|
## Wiring it into the service
|
|
@@ -302,7 +305,7 @@ held and not transferred). The responder keeps that move instead of undoing it:
|
|
|
302
305
|
after the slot had moved past the record, reads as a move: the safe side; a slot such a replay took is left to lapse
|
|
303
306
|
unused. A generation written `2.0` is `2`.
|
|
304
307
|
- The operator may move the slot back to this session in Fabric (DEC-0140 PR-2), which this client cannot hear. No
|
|
305
|
-
Fabric release offers the operator's move
|
|
308
|
+
Fabric release through v0.3.19 (2026-11-11) offers the operator's move (the control is CT-174's in Fabric, its agent twin CT-181 in the contract, then CT-125),
|
|
306
309
|
so today every move is another session taking the slot and take-back is the route back; the check below is ready
|
|
307
310
|
for the move. While
|
|
308
311
|
a slot is moved the responder reads `com.status` once a renewal (20 s) and, when the slot's generation is past the
|