@passioncode-ai/fabric-agent-adapter 0.39.1 → 0.41.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.
- package/CHANGELOG.md +203 -0
- package/README.md +18 -6
- package/SECURITY.md +27 -6
- package/bin/fabric-agent-adapter.js +71 -6
- package/package.json +2 -2
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +42 -66
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +4 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +55 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +9 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/verification.md +15 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +181 -26
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +66 -5
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +81 -23
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +42 -33
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +21 -9
- 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 +2 -2
- 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/recipes/support-agent/README.md +104 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +15 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/knowledge.example.json +13 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +465 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +21 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +93 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +5 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +129 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +5 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +48 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +66 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +11 -184
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +204 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +53 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +288 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +327 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +47 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +79 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +145 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +848 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +340 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +42 -1237
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +7 -18
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +41 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +15 -46
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +387 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +11 -68
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +52 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +231 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +50 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +235 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +136 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +335 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +146 -32
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +69 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -32
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +122 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +177 -2541
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +404 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +46 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +458 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +408 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +399 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +91 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +64 -1398
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +116 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +162 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +882 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +457 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +36 -39
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +336 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +113 -222
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +586 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +44 -405
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +140 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +402 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +119 -77
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +310 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_kit.py +243 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +308 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +202 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +431 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +285 -109
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +102 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +338 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +32 -39
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +173 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +298 -3450
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +8 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +437 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +120 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +697 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +1621 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +25 -7
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +29 -60
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +4 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +3 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +55 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +66 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +26 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +122 -1392
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +278 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +539 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +324 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +173 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +491 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +11 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/context.mjs +25 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/openclaw/index.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/opencode/fabric.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,208 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.41.0 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
The 2026-10-22 audit, closed (FAA-68 to FAA-81; [report](docs/reports/2026-10-22-adapter-audit/README.md),
|
|
6
|
+
[brief](docs/evidence/specs/2026-10-22-audit-fixes-brief.md)). Its 53 findings fell into six classes; each class got
|
|
7
|
+
its fixes and a guard that fails the next instance. Each fix has a test; the builders' reports and the commit
|
|
8
|
+
messages name the ones seen failing before the fix. Three blind readings (unit, seam, product) certified the result
|
|
9
|
+
and their findings are fixed here too.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Session hooks and the delivery worker** (FAA-68). One JSON-RPC error or unreadable spool entry no longer blocks
|
|
14
|
+
a session's spool for good: the entry is logged by its code and dropped, and the end, the claim release and the end
|
|
15
|
+
observation still go out. `fabric-mcp` runs in its own process group and the whole group is ended at the deadline,
|
|
16
|
+
so a wrapper (`npx`, a shell script, `fabric-mcp.cmd`) cannot hold the worker for a minute. A process walk that
|
|
17
|
+
cannot read the processes spools nothing and releases nothing; a launched session is remembered. Turn facts are
|
|
18
|
+
measured when the turn ends (counts only). watch-mail keeps its delay between 10 s and 15 min after the jitter and
|
|
19
|
+
honours the once-a-minute rejoin mark. Stale spool folders are pruned even while the hooks are inert; a stale
|
|
20
|
+
worker lock is taken by an atomic rename, and its identity includes the write time and size (Linux reuses a freed
|
|
21
|
+
inode number at once). An end whose release got no answer stays for the next worker. On Windows a bare `fabric-mcp`
|
|
22
|
+
resolves to `fabric-mcp.cmd`.
|
|
23
|
+
- **Host installers and the dashboard notice** (FAA-69). The SessionStart notice asks whether the repository owns a
|
|
24
|
+
service before it reads the tree, all within one 3.5 s budget. The Kimi installer repairs or removes a stale script
|
|
25
|
+
path; Kimi and Codex require the script to exist; Hermes `--remove` works without the source folder; the OpenCode
|
|
26
|
+
and OpenClaw bridges run the helper once per workspace, not per request, and never keep a failed run.
|
|
27
|
+
- **The service kit's Python twin** (FAA-70). `launchd_plist` refuses what the Node twin refuses: a secret-shaped
|
|
28
|
+
argument or environment value, a bad label, an empty argv, an `ExitTimeOut` under the drain. `JobStore` and the
|
|
29
|
+
event log hold a lock per store path (one idempotency key made two jobs; a cancelled job became completed; 41 of
|
|
30
|
+
160 events were kept). `pause` checks for a terminal job first (both twins). The JWKS fetch has one 5 s deadline,
|
|
31
|
+
runs outside the cache lock and turns a garbage answer into `issuer-unavailable`. Whole-value patterns end in
|
|
32
|
+
`\Z`, so a trailing newline no longer validates. An origin written with its default port is accepted as Node
|
|
33
|
+
accepts it. A socket timeout is a timeout on Python 3.9. Non-object MCP params answer -32602. systemd
|
|
34
|
+
`Environment=` no longer doubles `$`. The sample service answers 400 to a bad `Content-Length` or body.
|
|
35
|
+
- **The board responder** (FAA-77). A lost reply answer no longer leaves a request `accepted` for good: a transfer
|
|
36
|
+
re-derives the per-attempt keys after re-sending the old reply under its old key, and `idempotency_conflict` is its
|
|
37
|
+
own `board.blocked` event. One fenced call per entry at a time, and a `revision_conflict` on renew re-reads the
|
|
38
|
+
revision. An effect lost while the session holds the slot is observed and completed, or its slot is replaced so the
|
|
39
|
+
board records `outcome_unknown`. One lost answer keeps the session while the lease holds. Every transferred request
|
|
40
|
+
resumes, and a transfer survives a second lost answer (the request completes with one reply). The session's link
|
|
41
|
+
(MCP session, checkpoint, request) is its own module, `fabric_board_link` / `fabric-board-link.mjs`.
|
|
42
|
+
- **Interop job keys** (FAA-70, FAA-77). A key admits only the intent it was first used for: another capability or
|
|
43
|
+
input is `idempotency-conflict` in `create` and `conflict` in `repeat`, which now takes the intent. A key file
|
|
44
|
+
written before 0.41.0 is answered with its job, and runs again only for the intent its job stored.
|
|
45
|
+
- **Package, delegation and estate twins** (FAA-78). Both kits refuse a package path holding a control character,
|
|
46
|
+
U+2028 or U+2029, whatever the schema's pattern lets through (the contract owner was told). An unreadable folder
|
|
47
|
+
entry is refused instead of skipped (Python) or crashing (Node). Files are opened without following links. Node
|
|
48
|
+
reads JSON as strict UTF-8 and keeps a top-level `__proto__`; both cap nesting at 256 levels; dates take ASCII
|
|
49
|
+
digits. An explicit `null` estate, applied mark or revocation list fails closed in both kits. JWKS `x` must be
|
|
50
|
+
canonical base64url in both. Python Ed25519 signing is a Montgomery ladder that does not branch on the key. The
|
|
51
|
+
hub's loopback origin takes ASCII digits only. On Windows, `fabric.package.json` is written with `\n` like the Node
|
|
52
|
+
kit's, and the command line answers in UTF-8 whatever the console's code page.
|
|
53
|
+
- **The adapting skill's scripts** (FAA-71). `fabric_provider.py write` refuses what the pinned provider schema
|
|
54
|
+
refuses (ports under 100, a non-URI repository, an argument over 1024 characters), refuses `--env` with `--url`
|
|
55
|
+
and a writer option after `--stdio`. `check` names every template value still present and keeps
|
|
56
|
+
`readyForAdmission` false; `check --contract` calls the checkout's own `tsx`, never `pnpm exec`; a client
|
|
57
|
+
`.mcp.json` is not an MCP surface; a non-UTF-8 manifest is a typed receipt. `scaffold --dry-run` prints the writes
|
|
58
|
+
and a diff of each collision.
|
|
59
|
+
- **Dashboard probes and the link gate** (FAA-79). The layout probe never prints a login code (it starts the browser
|
|
60
|
+
before asking for one); ED-3 checks vertical clipping; ED-4 says NOT_RUN past its element cap. The handoff-link
|
|
61
|
+
gate normalises the path and refuses `code`, `api_key` and the other credential names. The "Get Fabric" menu link is
|
|
62
|
+
reachable by keyboard; the dashboard kit is 1.1.2.
|
|
63
|
+
- **Installers** (FAA-80). `install.sh` leaves an existing copy alone without `--force` and never copies
|
|
64
|
+
`__pycache__`; both installers build a new copy beside the old and rename it, report each failure and exit 1.
|
|
65
|
+
- **The support-agent recipe** (FAA-81). A reply carrying a link (bare host names included), address, phone number,
|
|
66
|
+
amount or number its cited entries do not contain goes to a person; links and addresses are compared by equality,
|
|
67
|
+
never as substrings; the customer's message reaches the model as fenced data. A promise written in words is not
|
|
68
|
+
caught, and the README says so. Decisions are an
|
|
69
|
+
append-only log; `prune --keep-days N` deletes old rows; the README says the outbox keeps customer text.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **CI follows the organization's rule** (FAA-72). `validate.yml` runs nightly on `main` and on dispatch, with job
|
|
74
|
+
timeouts and a concurrency group; a `contract` job installs the contract at the pin and at the legacy revision and
|
|
75
|
+
runs the tests that used to skip (one of them had rotted); a `floors` job runs Python 3.9 and Node 18; a `macos` job
|
|
76
|
+
runs both suites; Windows runs the session tests and the whole Node suite (16 tests that assumed POSIX now check
|
|
77
|
+
the Windows ACL or say why they skip). `engines.node` is `>=18` (the npm CLI); the Node service kit needs 20, and on
|
|
78
|
+
Linux its instance lock needs 22: Node 18 and early 20 collapse every abstract socket name into one, and later 20
|
|
79
|
+
pads it so no Python copy or probe sees it, so the kit refuses to start there with a sentence naming Node 22.
|
|
80
|
+
- **The release path** (FAA-73). The tarball smoke test runs before the GitHub release is created; every action is
|
|
81
|
+
pinned to a commit SHA; npm and the Claude Code CLI are pinned; release titles carry no em dash; the dead
|
|
82
|
+
private-repository branch is gone. Moving npm publishing to trusted publishing (and removing `NPM_TOKEN`) waits on
|
|
83
|
+
the operator's npmjs.com sign-in.
|
|
84
|
+
- **Skills and documents** (FAA-74, FAA-75). SECURITY.md and SKILL-CARD say what the probe and the kit really reach
|
|
85
|
+
and write; the `vault.py moved` command is correct; the secret tools have a path and a fallback; each skill's
|
|
86
|
+
NOT-for list names its siblings; shared blocks live in one reference; the building skill's description is 799
|
|
87
|
+
characters. `test/evals/run_evals.py` runs the trigger evals against a real client when asked; its first run scored
|
|
88
|
+
25/25 on `adapting-projects-to-fabric`.
|
|
89
|
+
|
|
90
|
+
### Guards (each fails on a planted defect)
|
|
91
|
+
|
|
92
|
+
- `validate.py` refuses a `$`-anchored whole-value pattern in every skill's scripts (R1; it found a real one in
|
|
93
|
+
`adapt_project`'s cost preset id), an em dash under
|
|
94
|
+
`.github/` (R6), and a workflow that floats an action, uses `@latest`, lacks a timeout, drops the nightly or gated
|
|
95
|
+
jobs, gains a push or PR trigger, or creates the release before the smoke test (R5).
|
|
96
|
+
- Shared hostile-input fixtures run by both twins: `kit-hostile-inputs-core.json`, `kit-hostile-inputs-package.json`,
|
|
97
|
+
`kit-hostile-inputs-estate.json` (R1).
|
|
98
|
+
- Fault-injection tests for the spool, the worker, the responder and the package walk (R2); concurrency tests for every
|
|
99
|
+
store a threaded server reaches (R3).
|
|
100
|
+
- `test_a_released_version_keeps_its_digests`: a release's `kit.json` history entry stays what the release shipped (a
|
|
101
|
+
build before the version moved on had rewritten 0.40.0's, which would have read genuine 0.40.0 copies as modified).
|
|
102
|
+
It runs in a clone that has the release tags (a full local clone); the shallow hosted checkout skips it (FAA-82).
|
|
103
|
+
- Two timing tests no longer fail on a loaded host: the runner probe's positive cases take the most a probe may take,
|
|
104
|
+
and the interop probe test lets the machine-timing verdict be the one that fails.
|
|
105
|
+
|
|
106
|
+
### Upgrading
|
|
107
|
+
|
|
108
|
+
- **A service built on the copied kit:** `fabric_kit.py status <dir>` names the modules to update; `fabric_kit.py install
|
|
109
|
+
<dir>` brings the closure, `fabric_board_link` included. Passing `estate=None` (or `estate_applied=None`,
|
|
110
|
+
`estate_revocations=None`) on purpose now refuses every token as `estate-unreadable`: leave the option out instead.
|
|
111
|
+
- **A Linux service on the Node kit:** run it on Node 22 or later; on 18 or 20 it stops at start with a sentence saying so.
|
|
112
|
+
- **A scaffolded bundle:** new scaffolds write a `urn:fabric:adapter:template` block into `fabric-agent.json` and take
|
|
113
|
+
`--data-class`, `--created-by`, `--identity-method` and `--probe-assertion`. Rerun `check`: an older bundle that still
|
|
114
|
+
holds the template's author, schemas or assertions now reads `readyForAdmission: false` until they are replaced.
|
|
115
|
+
- **A dashboard on kit 1.1.1:** the SessionStart notice now points at 1.1.2; `dashboard_kit.py install` updates it.
|
|
116
|
+
- **The support recipe:** its state moves to `decisions.jsonl` (an old `decisions.json` is still read); handoff rows gain
|
|
117
|
+
`question` and `at`, and new reasons `unsupported:url|email|phone|amount|number`. Run `prune --keep-days N` once, then
|
|
118
|
+
delete `decisions.json`.
|
|
119
|
+
- **Links you hand out:** the gate now refuses a query or fragment name such as `code`, `key`, `auth` or `api_key`, and
|
|
120
|
+
a path with a dot segment or an encoded separator.
|
|
121
|
+
- **Installing:** a copy already in the hub is kept unless `--force` (both installers); the npm installer reports each
|
|
122
|
+
skill `up to date`, `differs` or installed, and never copies Python bytecode.
|
|
123
|
+
- **Open human step:** moving npm publishing to trusted publishing and deleting `NPM_TOKEN` needs the operator's
|
|
124
|
+
npmjs.com sign-in.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
## 0.40.0 - 2026-10-21
|
|
128
|
+
|
|
129
|
+
The kits as production modules (FAA-65). Every name a kit module made public at 0.39.1 is still there; a service that
|
|
130
|
+
copies the kit now installs it with `fabric_kit.py install` (one file is no longer enough, see below).
|
|
131
|
+
|
|
132
|
+
### Added
|
|
133
|
+
|
|
134
|
+
- **A shared core, once per language** (layer L0 and L1): `fabric_jsvalue` / `fabric-jsvalue.mjs` (JavaScript's view
|
|
135
|
+
of JSON values: `show`, `js_to_number`, `same`, `json_equal`, `jcs` and the rest), `fabric_time` / `fabric-time.mjs`
|
|
136
|
+
(strict RFC 3339, UTC days, formatting, day windows, an injectable clock: the public dates API, on shared cases) and
|
|
137
|
+
`fabric_crypto` / `fabric-crypto.mjs` (Ed25519 and P-256 checks, small-order keys, base64url, sha256). They used to
|
|
138
|
+
live, twice, inside the estate and delegation readers; every reader now imports them.
|
|
139
|
+
- **The kit is a manifest-listed set, installable as one.** `kit.json` lists every module in dependency order, with the
|
|
140
|
+
kit modules it imports, its sha256 and the sha256 of every release since 0.8.0. `fabric_kit.py install <dir>`
|
|
141
|
+
copies a module's closure (data files included); `status <dir>` says `current`, `older` (with its release) or
|
|
142
|
+
`modified`, and names a module a copy imports but lacks. SKILL.md said the kit was "one self-contained file"; it was
|
|
143
|
+
not, and a one-file copy of `fabric_service.py` failed in `usage_findings`.
|
|
144
|
+
- **Connectors** (layer L4, both languages, shared cases): `fabric_http` (one deadline across attempts, bounded
|
|
145
|
+
retries with jittered backoff only when repeating is safe, `Retry-After` up to a cap, a response size cap, redirects
|
|
146
|
+
answered and never followed, plain http refused off loopback, redacted URLs in errors) and `fabric_openrouter` (the
|
|
147
|
+
agent's own key from a named environment variable, a spend ceiling checked before every attempt at the worst case
|
|
148
|
+
with calls in flight reserved, one usage receipt per answered call, empty, `length`, `error` and filtered answers
|
|
149
|
+
never returned as success, a fallback of another vendor required, 401/402/403 stop at once, attribution off by
|
|
150
|
+
default). References: `kit-modules.md`, `connectors.md`.
|
|
151
|
+
- **A support-agent recipe** (`building-fabric-services/recipes/support-agent/`): answers a question from a knowledge
|
|
152
|
+
file through `fabric_openrouter`, accepts a reply only when code proves it (citations exist and were given, no em
|
|
153
|
+
dash or substitute, the question's script, a length limit), otherwise hands it to a person with the failed checks
|
|
154
|
+
named; idempotent per message id. `test/validate.py` lets a recipe import the kit modules `kit.json` lists.
|
|
155
|
+
- **Layering held by tests**: a module imports only modules listed before it (a planted cycle is caught), none is over
|
|
156
|
+
1000 lines across every skill's scripts, the core is defined once, every module is in the module map, and the
|
|
157
|
+
0.39.1 public API of both facades is a snapshot test.
|
|
158
|
+
|
|
159
|
+
### Changed
|
|
160
|
+
|
|
161
|
+
- `fabric_service` / `fabric-service.mjs` and `fabric_board` / `fabric-board.mjs` are facades over modules split by
|
|
162
|
+
responsibility (base, platform, paths, protocols, lock, usage, backups, descriptor, auth, events, logs, supervisors;
|
|
163
|
+
board shapes, session, fixture). The probe's rules moved to `check_service_rules.py`. In working-with-fabric,
|
|
164
|
+
`fabric_session.py` is a facade over `fabric_session_{env,mcp,mail,hosts,cli}.py`; the hooks, the worker and
|
|
165
|
+
`install-host-hooks` still run the facade.
|
|
166
|
+
- No import cycle is left: `fabric_estate` no longer imports `fabric_delegation`, in both languages.
|
|
167
|
+
- A Node service that imported a module-private helper of `fabric-service.mjs` or `fabric-board.mjs` (not exported at
|
|
168
|
+
0.39.1, so outside the snapshot) imports it from the module that holds it now.
|
|
169
|
+
- The probe runs the board responder's self-check in a clean Python (`-E -s`, its own working directory), so it reads
|
|
170
|
+
the kit it names and not a copy already imported.
|
|
171
|
+
|
|
172
|
+
### Fixed
|
|
173
|
+
|
|
174
|
+
- **Found by the release review (three blind readings) and fixed before release:** the OpenRouter ceiling counted
|
|
175
|
+
only the messages, so a large `tools` list could pass it (the worst case now bounds the messages and every param;
|
|
176
|
+
`models`, `route`, `plugins` and `web_search_options` are refused); an attempt that may have been billed without an
|
|
177
|
+
answer to read (a timeout, a 200 over the size cap or not JSON) left no receipt (it now records one with no tokens
|
|
178
|
+
and no cost, counted at the worst case); the Python transport could overrun its deadline on a slowly trickled body
|
|
179
|
+
(it reads in chunks against one deadline); the twins differed on a lone surrogate, NaN in a body, a transport bug and
|
|
180
|
+
`http://127.1` (both now behave the same, on shared cases); `fabric_service.py` could not be loaded by its path from
|
|
181
|
+
another folder (every kit module now puts its own folder on `sys.path`, tested from `/`); `fabric_kit.py status`
|
|
182
|
+
judged an older copy by the current release's imports and named only direct ones (it now reads each copy's own
|
|
183
|
+
imports and follows the closure); an unknown `--module` is a usage error (exit 2); the facade keeps `_number`,
|
|
184
|
+
`_DAY_MS` and `_utc_date_ms`; `install-host-hooks` requires the session helper's new modules; the recipe hands off a
|
|
185
|
+
message holding bytes a terminal could not decode instead of exiting, and its dash check also catches U+2012 and
|
|
186
|
+
U+2015.
|
|
187
|
+
- **Found by the second review round and fixed:** a connection cut after a call may have been billed left no receipt
|
|
188
|
+
(every connection error now records an unmeasured one); `max_completion_tokens` and `n` could raise the output past
|
|
189
|
+
the bound (refused); an unmeasured call was counted at the current call's worst case (now at its own, while the
|
|
190
|
+
client runs); Node read a blank or hex price as a number (both twins now read a plain decimal or a number only);
|
|
191
|
+
Python's deadline started after the headers (it now covers them); `fabric_kit.py status` raised on a copy it could
|
|
192
|
+
not parse (it reports it `modified`) and now names a missing module's data files; `fabric_http` writes a JSON body
|
|
193
|
+
byte for byte as `JSON.stringify` (numbers and key order too, through the core's `json_stringify`); loopback edge
|
|
194
|
+
forms agree in both twins; the recipe's dash check covers U+2E3A, U+2E3B, U+FE58 and no-break spaces, and no longer
|
|
195
|
+
flags `1 - 2`.
|
|
196
|
+
- **Found by the third review round and fixed:** an answer with no usage was counted at the next call's worst case
|
|
197
|
+
(now at its own); image, audio and file parts could be sent past the bound (only text content is sent); Python
|
|
198
|
+
raised outside `OpenRouterError` and kept no receipt on a 400-digit count or cost or JSON nested too deep (both read
|
|
199
|
+
as unmeasured now); Python's deadline could be held by trickled headers (a watchdog shuts the socket at the
|
|
200
|
+
deadline); `Response.json()` reads a byte order mark and refuses `NaN` as `JSON.parse` does; a `Retry-After` year
|
|
201
|
+
before 1000 is no date in either twin.
|
|
202
|
+
- **A usage receipt's project id is checked the same way in both kits.** `fabric_service.py` defined its URI pattern
|
|
203
|
+
twice and the second won: Python accepted `project_id="urn:"` where Node refused it. Now one pattern per module, and
|
|
204
|
+
both kits refuse it (a test in each).
|
|
205
|
+
|
|
3
206
|
## 0.39.1 - 2026-10-21
|
|
4
207
|
|
|
5
208
|
Fixes from two rounds of blind review of 0.39.0 (FAA-64).
|
package/README.md
CHANGED
|
@@ -38,8 +38,8 @@ resume) puts that preamble into the session when the MCP server `fabric` is regi
|
|
|
38
38
|
is down it adds one line (skip `fabric_whoami`, start Fabric through the broker if the task needs it), and only in a
|
|
39
39
|
project that uses Fabric; Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
|
|
40
40
|
and the board's unread count; UserPromptSubmit adds one line only when that count rose; SessionEnd releases what the
|
|
41
|
-
session left claimed. Stop, UserPromptSubmit and SessionEnd
|
|
42
|
-
`session-hooks/1
|
|
41
|
+
session left claimed. Stop, UserPromptSubmit and SessionEnd spool and call Fabric only when the running Fabric
|
|
42
|
+
advertises `session-hooks/1`; Stop and SessionEnd always prune their own stale spool folders. All are silent in a session Fabric launched, never block the session, read no token and always
|
|
43
43
|
exit 0. `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
|
|
44
44
|
--host codex|kimi|hermes|opencode|openclaw|pi` installs each host's native context bridge (dry run unless `--apply`).
|
|
45
45
|
Codex receives structured SessionStart JSON; Hermes, OpenCode, OpenClaw and Pi use native plugin APIs.
|
|
@@ -54,9 +54,15 @@ that is always alive, runs as one copy, keeps its state through a reinstall and
|
|
|
54
54
|
in Fabric Dashboards by itself: the `fabric-service/0.1` extension. It also makes an **online** agent
|
|
55
55
|
or dashboard (an `https` origin on a platform) a Fabric service (the remote placement, DEC-0019): the same
|
|
56
56
|
four routes behind the token, a `__Host-` session cookie, and one descriptor on the operator's computer;
|
|
57
|
-
`scripts/sample-remote-service.mjs` is the complete example. It ships Python and
|
|
58
|
-
Node
|
|
59
|
-
|
|
57
|
+
`scripts/sample-remote-service.mjs` is the complete example. It ships the service kit in Python and
|
|
58
|
+
Node: layered modules listed in `scripts/kit.json` behind the `fabric_service` / `fabric-service.mjs` facades,
|
|
59
|
+
installed into a service with `scripts/fabric_kit.py install` and checked with `fabric_kit.py status`
|
|
60
|
+
([kit modules](plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md), including
|
|
61
|
+
the dates API); connectors for outbound HTTP and OpenRouter (`fabric_http`, `fabric_openrouter`: one deadline, safe
|
|
62
|
+
retries, the agent's own key, a spend ceiling before every attempt, usage receipts, a fallback model;
|
|
63
|
+
[connectors](plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md)); a ready
|
|
64
|
+
[support-agent recipe](plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md);
|
|
65
|
+
a complete `scripts/sample_service.py`; and `scripts/check_service.py`, a live probe. A service that answers
|
|
60
66
|
requests other Projects post to its Project on Fabric's project board uses the board responder
|
|
61
67
|
(`scripts/fabric_hub.py` and `scripts/fabric_board.py`, Node `fabric-hub.mjs` and `fabric-board.mjs`): the
|
|
62
68
|
`board.respond` grant, enroll, claim, accept the exact digest, complete, a checkpoint before every step, and restarts
|
|
@@ -123,8 +129,13 @@ then restart your agents. Alone, without any account:
|
|
|
123
129
|
```bash
|
|
124
130
|
npx @passioncode-ai/fabric-agent-adapter # installs every skill into the agents hub ~/.agents/skills
|
|
125
131
|
npx @passioncode-ai/fabric-agent-adapter --prune-shadow # remove ~/.claude/skills copies that shadow the plugin
|
|
132
|
+
npx @passioncode-ai/fabric-agent-adapter --force # update a copy already in the hub (a launcher link is never replaced)
|
|
126
133
|
```
|
|
127
134
|
|
|
135
|
+
A copy already in the hub is left alone unless `--force` is given; with it, the new copy is built beside the old one
|
|
136
|
+
and renamed into place, so a failed run leaves the old copy or nothing. `install.sh --force` from a checkout does the
|
|
137
|
+
same.
|
|
138
|
+
|
|
128
139
|
Claude Code plugin marketplace:
|
|
129
140
|
|
|
130
141
|
```text
|
|
@@ -183,7 +194,8 @@ own service by its id once its installer has written the descriptor.
|
|
|
183
194
|
|
|
184
195
|
### Develop
|
|
185
196
|
|
|
186
|
-
There is no build step and no dependency beyond Python 3.9+ and Node.js
|
|
197
|
+
There is no build step and no dependency beyond Python 3.9+ and Node.js. The npm CLI (installer, `pack`, `sign`,
|
|
198
|
+
`verify`) needs Node.js 18+, the Node service kit 20+, and a Linux service's instance lock 22+:
|
|
187
199
|
|
|
188
200
|
```bash
|
|
189
201
|
npm test # validate.py, the Python unit tests, the Node kit tests
|
package/SECURITY.md
CHANGED
|
@@ -11,13 +11,34 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
|
|
|
11
11
|
## What the installers and scripts touch
|
|
12
12
|
|
|
13
13
|
- `npx @passioncode-ai/fabric-agent-adapter` and `install.sh` write only
|
|
14
|
-
`~/.agents/skills/<skill
|
|
14
|
+
`~/.agents/skills/<skill>` (and, while replacing a copy with `--force`, the sibling folders
|
|
15
|
+
`.<skill>.installing-*` and `.<skill>.replaced-*`, removed when the run ends; a killed run can
|
|
16
|
+
leave one behind); `--target claude` writes `~/.claude/skills/<skill>` and is
|
|
15
17
|
refused while the plugin is installed; `--prune-shadow` moves shadowing copies into
|
|
16
18
|
`~/.claude/skills-shadow-backup/`.
|
|
17
19
|
- `building-fabric-services/scripts/check_service.py` reads a descriptor and the token
|
|
18
|
-
file it names,
|
|
19
|
-
|
|
20
|
+
file it names, and redeems one login code it asked for itself (creating one dashboard
|
|
21
|
+
session). It never prints the token. For a local service it talks only to
|
|
22
|
+
`127.0.0.1:<port>`; for a remote placement it talks to the service's own `https` origin
|
|
23
|
+
named in the descriptor (`--ca-file` and `--connect` only change which CA it trusts and
|
|
24
|
+
which address it dials, for tests).
|
|
20
25
|
- The service kits create the service token (0600) and the instance lock in the
|
|
21
|
-
service's own data directory, and write
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
service's own data directory, and write supervisor files only when an installer calls
|
|
27
|
+
them: a launchd plist in `~/Library/LaunchAgents` (macOS), a systemd user unit (Linux) or
|
|
28
|
+
a per-user Scheduled Task (Windows), plus one descriptor in the services directory. The
|
|
29
|
+
token is readable by any process of the same user; the protocol defends against web pages
|
|
30
|
+
and mistakes, not against hostile local code.
|
|
31
|
+
- The kits' connectors (`fabric_http`, `fabric_openrouter` and their Node twins) reach the
|
|
32
|
+
external APIs the service's author configures, such as OpenRouter, with keys the service
|
|
33
|
+
reads by name at run time; the kit itself calls no outside host on its own. A board
|
|
34
|
+
responder talks only to Fabric's hub on loopback.
|
|
35
|
+
- The working-with-fabric session hooks run as the person: Stop and SessionEnd run `git`
|
|
36
|
+
in the session's folder (counts only, at most 1.5 s), read the process table to tell a
|
|
37
|
+
session Fabric launched, write their spool and log under the user's cache folder (0600),
|
|
38
|
+
and the delivery worker starts `fabric-mcp` in its own process group and ends that group
|
|
39
|
+
(`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no token.
|
|
40
|
+
- `adapting-projects-to-fabric/scripts/adapt_project.py` reads the target project without
|
|
41
|
+
executing it and writes only the seven documented bundle paths (`scaffold --dry-run`
|
|
42
|
+
writes nothing); `check --contract` runs the contract checkout's own
|
|
43
|
+
`node_modules/.bin/tsx` and installs nothing. `fabric_provider.py write` writes one
|
|
44
|
+
entry (0600) in the providers directory.
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* ~/.agents/skills/<name> (read by Codex, Cursor, Gemini, OpenCode and others).
|
|
7
7
|
* Claude Code gets the skills through the plugin, never through a plain copy:
|
|
8
8
|
* a copy in ~/.claude/skills shadows the plugin and serves its frozen version
|
|
9
|
-
* forever. Idempotent; an existing install is skipped unless --force.
|
|
10
|
-
*
|
|
9
|
+
* forever. Idempotent; an existing install is skipped unless --force. Each skill is
|
|
10
|
+
* copied into a sibling temporary folder and renamed into place, so a failure
|
|
11
|
+
* leaves the old copy or nothing, never half of each. Zero dependencies.
|
|
11
12
|
*/
|
|
12
13
|
'use strict';
|
|
13
14
|
|
|
@@ -46,9 +47,13 @@ Every PassionCode.ai skill for every agent at once:
|
|
|
46
47
|
npx @passioncode-ai/passioncode@latest update`);
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// Bytecode a checkout's own runs left behind is not part of a skill.
|
|
51
|
+
function bytecode(name) { return name === '__pycache__' || name.endsWith('.pyc'); }
|
|
52
|
+
|
|
49
53
|
function copyDir(src, dest) {
|
|
50
54
|
fs.mkdirSync(dest, { recursive: true });
|
|
51
55
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
56
|
+
if (bytecode(entry.name)) continue;
|
|
52
57
|
const s = path.join(src, entry.name);
|
|
53
58
|
const d = path.join(dest, entry.name);
|
|
54
59
|
if (entry.isDirectory()) copyDir(s, d);
|
|
@@ -56,6 +61,54 @@ function copyDir(src, dest) {
|
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
// The same files with the same bytes: what "already installed" means.
|
|
65
|
+
function sameTree(src, dest) {
|
|
66
|
+
let a;
|
|
67
|
+
let b;
|
|
68
|
+
try {
|
|
69
|
+
a = fs.readdirSync(src, { withFileTypes: true }).filter((e) => !bytecode(e.name));
|
|
70
|
+
// The same filter on both sides: an installed skill whose Python ran has its own __pycache__.
|
|
71
|
+
b = fs.readdirSync(dest, { withFileTypes: true }).filter((e) => !bytecode(e.name));
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (a.length !== b.length) return false;
|
|
76
|
+
const there = new Map(b.map((e) => [e.name, e]));
|
|
77
|
+
return a.every((e) => {
|
|
78
|
+
const d = there.get(e.name);
|
|
79
|
+
if (!d || e.isDirectory() !== d.isDirectory() || d.isSymbolicLink()) return false;
|
|
80
|
+
const s = path.join(src, e.name);
|
|
81
|
+
const t = path.join(dest, e.name);
|
|
82
|
+
if (e.isDirectory()) return sameTree(s, t);
|
|
83
|
+
try { return fs.readFileSync(s).equals(fs.readFileSync(t)); } catch { return false; }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Copy beside, then rename over: the old copy stays until the new one is whole.
|
|
88
|
+
function installSkill(src, dest) {
|
|
89
|
+
const stamp = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
|
90
|
+
const temp = path.join(path.dirname(dest), `.${path.basename(dest)}.installing-${stamp}`);
|
|
91
|
+
const old = path.join(path.dirname(dest), `.${path.basename(dest)}.replaced-${stamp}`);
|
|
92
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
93
|
+
try {
|
|
94
|
+
copyDir(src, temp);
|
|
95
|
+
if (fs.existsSync(dest)) {
|
|
96
|
+
fs.renameSync(dest, old);
|
|
97
|
+
try {
|
|
98
|
+
fs.renameSync(temp, dest);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
fs.renameSync(old, dest);
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
fs.rmSync(old, { recursive: true, force: true });
|
|
104
|
+
} else {
|
|
105
|
+
fs.renameSync(temp, dest);
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
59
112
|
// A link in the hub belongs to whoever made it: 'launcher' for the PassionCode launcher's
|
|
60
113
|
// ~/.passioncode/... links, 'other' for any other link, null for a directory or nothing.
|
|
61
114
|
function linkOwner(dest) {
|
|
@@ -144,6 +197,7 @@ function main(argv) {
|
|
|
144
197
|
}
|
|
145
198
|
|
|
146
199
|
const destRoot = target === 'claude' ? path.join(home, '.claude', 'skills') : path.join(home, '.agents', 'skills');
|
|
200
|
+
const failed = [];
|
|
147
201
|
for (const name of names) {
|
|
148
202
|
const dest = path.join(destRoot, name);
|
|
149
203
|
const owner = linkOwner(dest);
|
|
@@ -155,13 +209,24 @@ function main(argv) {
|
|
|
155
209
|
: `skip: ${name} at ${dest} is a link managed elsewhere; remove the link first to install a copy here`);
|
|
156
210
|
continue;
|
|
157
211
|
}
|
|
212
|
+
const src = path.join(skillRoot, name);
|
|
158
213
|
if (fs.existsSync(dest) && !force) {
|
|
159
|
-
console.log(
|
|
214
|
+
console.log(sameTree(src, dest)
|
|
215
|
+
? `up to date: ${name} at ${dest}`
|
|
216
|
+
: `skip: ${name} at ${dest} differs from this version (rerun with --force to replace it)`);
|
|
160
217
|
continue;
|
|
161
218
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
219
|
+
try {
|
|
220
|
+
installSkill(src, dest);
|
|
221
|
+
console.log(`Installed ${name} -> ${dest}`);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
failed.push(name);
|
|
224
|
+
console.error(`error: ${name} was not installed (${err.code || 'error'}: ${err.message}); ${fs.existsSync(dest) ? 'the copy that was there is unchanged' : 'nothing was left at ' + dest}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (failed.length) {
|
|
228
|
+
console.error(`error: ${failed.length} of ${names.length} skill(s) failed: ${failed.join(', ')}`);
|
|
229
|
+
return 1;
|
|
165
230
|
}
|
|
166
231
|
if (target === 'agents' && !hasPlugin) {
|
|
167
232
|
console.log(`Claude Code reads plugins, not the hub:\n claude plugin marketplace add ${REPO} && claude plugin install ${PLUGIN_ID}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
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'"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"url": "https://passioncode.ai/"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=18"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"fabric",
|
|
@@ -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.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.41.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: adapting-projects-to-fabric
|
|
3
|
-
description: Use when adapting an existing agent, MCP server, A2A peer, HTTP service, or terminal CLI to the Fabric Agent Contract (choosing MCP/A2A/local-runner, scaffolding a provider bundle, or checking Fabric compatibility); also for «подключить проект к Fabric» or «сделать агента совместимым с Fabric». NOT for building the Fabric host/orchestrator, configuring an MCP client,
|
|
3
|
+
description: Use when adapting an existing agent, MCP server, A2A peer, HTTP service, or terminal CLI to the Fabric Agent Contract (choosing MCP/A2A/local-runner, scaffolding a provider bundle, or checking Fabric compatibility); also for «подключить проект к Fabric» or «сделать агента совместимым с Fabric». NOT for building the Fabric host/orchestrator, configuring an MCP client, a brand-new agent (creating-fabric-agents), running an agent as a local or online Fabric service with a dashboard (building-fabric-services), or a coding session working through Fabric, its board or claims, «подключись к Fabric» (working-with-fabric).
|
|
4
4
|
license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.41.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "c55f2d5a11c9a0197e958e87212fca7ae1154769"
|
|
11
11
|
---
|
|
@@ -25,7 +25,11 @@ Use this skill to adapt or assess a provider project. Do not use it to:
|
|
|
25
25
|
- answer wire-level MCP or A2A questions (transports, sessions, auth handshakes, task
|
|
26
26
|
states on the wire): that is `agent-interop` (agent-stack); the pinned contract stays
|
|
27
27
|
normative for what Fabric requires;
|
|
28
|
-
- design a new agent from scratch: that is the sibling skill creating-fabric-agents
|
|
28
|
+
- design a new agent from scratch: that is the sibling skill `creating-fabric-agents`;
|
|
29
|
+
- run the agent as a resident service with a dashboard, a descriptor and launchd: that is
|
|
30
|
+
`building-fabric-services`;
|
|
31
|
+
- work in a project through Fabric from a coding session (registering `fabric-mcp`, the board,
|
|
32
|
+
claims): that is `working-with-fabric`;
|
|
29
33
|
- claim admission merely because generated files or a valid manifest exist.
|
|
30
34
|
|
|
31
35
|
If the project has only a browser interface and no stable API, MCP/A2A surface, or CLI,
|
|
@@ -57,7 +61,9 @@ python3 <skill-dir>/scripts/adapt_project.py inspect <project-root> --json
|
|
|
57
61
|
```
|
|
58
62
|
|
|
59
63
|
The inspector samples repository files but does not execute the project. Verify its
|
|
60
|
-
evidence manually. Existing runtime documentation and code outrank filename heuristics.
|
|
64
|
+
evidence manually. Existing runtime documentation and code outrank filename heuristics. A
|
|
65
|
+
client configuration (`.mcp.json`, an `mcpServers` block) lists servers the project calls, not one
|
|
66
|
+
it serves: inspect reports it as `mcp-client-config` and asks instead of choosing `mcp`.
|
|
61
67
|
|
|
62
68
|
If the inspector returns `undetermined`, answer its lifecycle question before creating
|
|
63
69
|
files. A repository can expose multiple surfaces; choose a profile per capability, not
|
|
@@ -110,12 +116,18 @@ target paths. It refuses any collision.
|
|
|
110
116
|
`charge`, `delete`, `merge`, `deploy`, `change-policy`), read from what the code does or asked of the author. Choose
|
|
111
117
|
`--idempotency required|supported|none` for an external effect on purpose. When a run's cost varies, give the
|
|
112
118
|
launcher fixed options (DEC-0037): `--cost-preset ID MAX_USD LABEL` per option and `--default-cost-preset ID`; the
|
|
113
|
-
amounts come from the author or measured runs, never invented.
|
|
114
|
-
|
|
119
|
+
amounts come from the author or measured runs, never invented. Pass what the author knows so it is not written as a
|
|
120
|
+
scaffold default: `--data-class` (repeat), `--created-by URI`, `--identity-method`, `--probe-assertion` (repeat).
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
On a collision run `scaffold --dry-run` first: it writes nothing and prints the planned writes and a unified diff of
|
|
123
|
+
each existing file. Use `--force` only when the user authorizes replacing exactly those files.
|
|
124
|
+
|
|
125
|
+
Every value the scaffold invents is a sentinel that `check` names: the zero hash, `.invalid` endpoints,
|
|
126
|
+
`replace-me` values, `urn:fabric:adapter:author`, the generic schemas (marked `fabric-adapter-template`), the
|
|
127
|
+
generic fixture and assertions, and each value listed under the manifest's `urn:fabric:adapter:template`
|
|
128
|
+
extension (the defaulted `dataClasses`, a defaulted `tls` identity method). Replace each with an
|
|
129
|
+
implementation-backed fact, then remove it from that list and the block when it is empty. While one remains,
|
|
130
|
+
`readyForAdmission` is false.
|
|
119
131
|
|
|
120
132
|
### 4. Implement the adapter seam
|
|
121
133
|
|
|
@@ -138,11 +150,14 @@ Treat all provider output as untrusted until schemas and semantic assertions pas
|
|
|
138
150
|
|
|
139
151
|
### 4b. Announce an agent that is not a service
|
|
140
152
|
|
|
141
|
-
An agent Fabric reaches as a
|
|
142
|
-
its installer with
|
|
143
|
-
by its uninstaller: argv
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
An agent Fabric reaches as a stdio MCP server, or as an MCP server on a loopback URL that is not
|
|
154
|
+
a service, gets a provider entry, written by its installer with
|
|
155
|
+
[`scripts/fabric_provider.py`](scripts/fabric_provider.py) and removed by its uninstaller: argv
|
|
156
|
+
arrays only, env values as `secret-ref:` references, never the id of an existing service, and a
|
|
157
|
+
`providerId` equal to the manifest's `provider.id`. Load [provider entries](references/provider-entry.md).
|
|
158
|
+
A provider entry only describes an MCP server: a CLI that does not speak MCP gets no entry; it is
|
|
159
|
+
announced by its `local-runner` manifest (step 3). An agent that runs as a service uses a
|
|
160
|
+
descriptor instead (`building-fabric-services`).
|
|
146
161
|
|
|
147
162
|
### 5. Make probes safe and meaningful
|
|
148
163
|
|
|
@@ -195,58 +210,25 @@ Update `fabric/FABRIC-CONFORMANCE.md` with dated receipts for:
|
|
|
195
210
|
|
|
196
211
|
Include commands and exit codes, immutable endpoint or artifact identities, and explicit
|
|
197
212
|
unverified surfaces. If live Fabric admission/runtime does not yet exist, the last gates
|
|
198
|
-
remain `
|
|
213
|
+
remain `NOT_VERIFIED`; the completed deliverable is an adaptation-ready provider bundle,
|
|
199
214
|
not a connected provider.
|
|
200
215
|
|
|
201
216
|
### 8. Propose the operator channel
|
|
202
217
|
|
|
203
218
|
If the agent has a human stop (a choice, an approval, a confirmation) or emits `notify: true`
|
|
204
|
-
events, propose its
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
1. create a bot with @BotFather;
|
|
211
|
-
2. put its token into the operator's secret store under the name the agent reads (for example
|
|
212
|
-
`EXAMPLE_AGENT_TELEGRAM_BOT_TOKEN`), never into a chat, a file or the bundle;
|
|
213
|
-
3. run the agent's link command;
|
|
214
|
-
4. type the code it shows in the chat with the bot.
|
|
215
|
-
|
|
216
|
-
In a group shared with other bots the channel follows OC-14…OC-17 (DEC-0035): it stays silent
|
|
217
|
-
unless addressed (`/command@ThisBot`, a reply to its own message or a press of its own button; a
|
|
218
|
-
bare `/command` in a group is not addressed), and a channel nobody receives on is `degraded`
|
|
219
|
-
(`receiver-missing`). Say so in the proposal when the operator means to bind a shared group.
|
|
220
|
-
|
|
221
|
-
Record the answer (`accepted`, `declined` or `later`) in the agent's state and the
|
|
222
|
-
conformance report, and do not ask again; an agent that serves `surfaces.operatorChannel` also
|
|
223
|
-
shows it as `proposal` in its status document. Creating the bot, storing the token and enabling the
|
|
224
|
-
channel are the operator's action: never create the bot, ask for or store the token's value, or
|
|
225
|
-
enable the channel yourself. The default pin carries DEC-0034 and DEC-0035: a served status document is checked
|
|
226
|
-
against the pinned `schemas/operator-channel-status.schema.json`; the channel's runtime rules have
|
|
227
|
-
no probe rule here yet, so report them `NOT_RUN`. An agent with neither stops nor notify
|
|
228
|
-
events skips this step.
|
|
219
|
+
events, propose its operator channel (`fabric-operator-channel/0.1`, OC-11) to the operator once,
|
|
220
|
+
now, as [the operator channel and the repository standard](references/operator-channel-and-repository.md)
|
|
221
|
+
describes: the operator's four steps, the answer recorded and never asked again, and the bot, the
|
|
222
|
+
token and enabling the channel left to the operator. An agent with neither stops nor notify events
|
|
223
|
+
skips this step.
|
|
229
224
|
|
|
230
225
|
## The repository around the provider
|
|
231
226
|
|
|
232
227
|
**For a PassionCode.ai repository**, the adapted project's repository follows the organization's
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
- `LICENSE`, `COMMERCIAL-LICENSE.md` and `CLA.md` copied byte for byte from the knowledge base's
|
|
238
|
-
`templates/`, and `AGPL-3.0-only OR LicenseRef-PassionCode-Commercial` in every manifest and
|
|
239
|
-
every SKILL.md `license:`; `SECURITY.md` when the repository is public;
|
|
240
|
-
- a README whose first heading is the full name, with `## Quick start for a new teammate`
|
|
241
|
-
(Install; Configure, key names only; MCP, the registration command and one proving tool call,
|
|
242
|
-
verified with a real client in a temporary config; Develop) and `## License`;
|
|
243
|
-
- an `AGENTS.md` that opens with the template's *Read first* block and closes with *After work*,
|
|
244
|
-
and a `CLAUDE.md` whose first line is `@AGENTS.md`.
|
|
245
|
-
|
|
246
|
-
org-index `scripts/check_format.py` reports 0 findings for it before it is called done. **An agent
|
|
247
|
-
someone builds for themselves is not a PassionCode.ai repository:** its licence is its owner's
|
|
248
|
-
choice, nothing about it is published or listed by the organization, and none of these files is
|
|
249
|
-
required of it, though the verified MCP quick start is still how anyone learns to drive it.
|
|
228
|
+
repository standard (`knowledge/repository-standard.md`) from its first commit: the licence files,
|
|
229
|
+
the README quick start verified with a real client, `AGENTS.md` and `CLAUDE.md`, listed in the
|
|
230
|
+
shared reference above. An agent someone builds for themselves is not a PassionCode.ai repository:
|
|
231
|
+
its licence is its owner's choice and none of those files is required of it.
|
|
250
232
|
|
|
251
233
|
## Completion format
|
|
252
234
|
|
|
@@ -265,12 +247,6 @@ passed against a real provider and the project binding is valid.
|
|
|
265
247
|
|
|
266
248
|
## Dashboard handoff: also for consumers
|
|
267
249
|
|
|
268
|
-
When handing a registered service dashboard to a person, or writing the generated
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
check the target device and host, and run the bundled output gate before delivery.
|
|
272
|
-
An installed host failure never means browser fallback. This applies in Claude
|
|
273
|
-
Code, Codex and other providers; it needs no host-specific hook. MCP unavailable:
|
|
274
|
-
report the unresolved capability or use an approved project resolver; no automatic
|
|
275
|
-
client configuration. Python unavailable: perform the reference's manual checks
|
|
276
|
-
and report the executable gate NOT_RUN. Headless agents need no dashboard.
|
|
250
|
+
When handing a registered service dashboard to a person, or writing the generated agent's
|
|
251
|
+
completion or notification instructions, read and apply [dashboard links](references/dashboard-links.md)
|
|
252
|
+
and run its output gate before delivery, in any host. Headless agents need no dashboard.
|