@passioncode-ai/fabric-agent-adapter 0.55.0 → 0.57.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 +242 -0
- package/README.md +5 -4
- package/SECURITY.md +6 -5
- 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 +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +18 -7
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +30 -6
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +73 -18
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +7 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +70 -20
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +7 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +19 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +16 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +87 -156
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +180 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +15 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +17 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +32 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +115 -147
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +173 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +9 -42
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +53 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +7 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +87 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +19 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +30 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +9 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +19 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +73 -18
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +226 -26
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +155 -21
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +11 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +18 -18
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +2 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +39 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +6 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +17 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +23 -6
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +10 -39
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +190 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,247 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.57.0 (2026-10-28)
|
|
4
|
+
|
|
5
|
+
The contract pin moves to `3830a33` (`v0.1.0+2026-10-27.6`) from `00e050ba` (FAA-176).
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`comparison` as a human step** (DEC-0142, contract CT-186). `humanStep` in an action declaration may be
|
|
10
|
+
`comparison`: the person compares two codes shown on two screens, or chooses to share without a comparison. Both
|
|
11
|
+
kits' action-declaration checks take it (`fabric_actions.py`, `fabric-actions.mjs`, `actions-vectors.json`
|
|
12
|
+
regenerated: 68 cases, 12 declarations), the probe passes a declaration that uses it, and interop.md, delegation.md
|
|
13
|
+
and working-with-fabric's human gates (OA-5), which its refusal table cites, name it. A reader pinned before (a 0.56.0
|
|
14
|
+
kit, Fabric Dashboards before 0.8.9) refuses such a declaration, which Project Observatory declares on its share
|
|
15
|
+
buttons from OBS-264.
|
|
16
|
+
- **The protocol register** gains the six peer-share revisions (DEC-0142, `fabric-peer-share/1` and its parts) and
|
|
17
|
+
`org-adaptation/1` and `org-adaptation-receipt/1` (DEC-0138) in both twins (`feed-protocols-vectors.json`), so a
|
|
18
|
+
service's well-known that names one is read as a known revision.
|
|
19
|
+
- **The provider entry's manifest path** (DEC-0145, CT-192): `fabric_provider.py` names the schema's
|
|
20
|
+
`MANIFEST_PATTERN`, read as a search that ends at the string's end; the reader accepts the manifest's name after a
|
|
21
|
+
backslash, the writer still writes it with `/` and refuses the other with a sentence naming CT-192, and the CLI's
|
|
22
|
+
stem is read after either separator (CT-193). The contract's ten `provider.schema.json` fixtures are vendored
|
|
23
|
+
(`provider-fixtures.json`) and tested in every run.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- On Windows, `fabric_provider.py`'s `services_dir()` (its FAC-SEM-013 check that an id is not a service's) and
|
|
28
|
+
`providers_dir()` read `%LOCALAPPDATA%\passioncode-fabric` (provider.md), as the service kit's readers already did.
|
|
29
|
+
- agents-that-answer-people.md (A5) names the contract's `support-quality-daily.schema.json`, says when a producer may
|
|
30
|
+
carry `calibration` (CT-188) and that a reader validates against its own pinned copy (DEC-0144).
|
|
31
|
+
- The hosted contract job runs `test_provider`, and `test/validate.py` fails a workflow whose contract job leaves out a
|
|
32
|
+
module that holds a test gated on the contract.
|
|
33
|
+
|
|
34
|
+
### Upgrading
|
|
35
|
+
|
|
36
|
+
- A bundle pinned to `00e050ba` or an earlier supported commit keeps working; `adapt_project.py`
|
|
37
|
+
accepts it (`SUPPORTED_CONTRACT_COMMITS`). A new scaffold writes `3830a33`.
|
|
38
|
+
- DEC-0139 (the machines' report) and DEC-0138's organization server route reach no kit code: the adapter registers
|
|
39
|
+
their protocol revisions and implements neither side. DEC-0143 (`secret.prompt`, the local channel) and DEC-0142's
|
|
40
|
+
peer-share profile beyond its register entries reach no kit code either; Project Observatory implements them
|
|
41
|
+
(CT-186).
|
|
42
|
+
- On Windows, a provider entry written by `fabric_provider.py` now goes to the folder Fabric reads
|
|
43
|
+
(`%LOCALAPPDATA%\passioncode-fabric\providers`). Fabric v0.3.15 and earlier read a manifest path only under `~/` or
|
|
44
|
+
`/`; a drive letter (`C:/.../fabric-agent.json`) is read by the Fabric release after v0.3.15 (CO-479). Until then
|
|
45
|
+
write the manifest path under `~/`. An entry an older kit wrote on Windows sits in
|
|
46
|
+
`%USERPROFILE%\.local\share\passioncode-fabric\providers`, which Fabric never read and `remove` no longer finds:
|
|
47
|
+
write it again and delete the old file.
|
|
48
|
+
|
|
49
|
+
## 0.56.0 (2026-10-28)
|
|
50
|
+
|
|
51
|
+
Stability and Windows: the debts of 0.53.0 to 0.55.0 that touch how the hooks, the board responder and the interop
|
|
52
|
+
gate behave.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- The board responder's notices reach a service's event feed: they were sent at level `warn`, which
|
|
57
|
+
`JsonlEventLog.append` refuses (its levels are `info`, `notice`, `warning`, `error`), so `board.down`,
|
|
58
|
+
`board.access.needed`, `board.slot.moved`, `board.slot.contested`, `board.request.held`, `board.request.skipped`,
|
|
59
|
+
`board.reconcile.needed`, `board.attempt.lost` and `board.outcome.invalid` were dropped wherever `emit=log.append`
|
|
60
|
+
(the reference service included, since 0.28.0; `board.slot.moved` since 0.53.0). They are sent at `warning` now, in
|
|
61
|
+
both twins, as is the new `board.slot.refused` (FAA-163). Each notice names its `subject` (`{type, id}`: its request,
|
|
62
|
+
else its capability, else its episode, such as a block's cause, else the Project), so a host tells one request's,
|
|
63
|
+
capability's or cause's episode from another's. In Python the subject goes to an `emit` whose signature names a
|
|
64
|
+
`subject` parameter with no value bound (`JsonlEventLog.append` now names one); any other `emit` (only `**kwargs`, a
|
|
65
|
+
subject bound with `functools.partial`, a signature that cannot be read) is called as 0.55.0 called it, once, without
|
|
66
|
+
one. The Node responder always passes it in its options object, which an `emit` that does not read it ignores.
|
|
67
|
+
- **working-with-fabric on Windows** (FAA-175, FAA-177). A turn names the repositories it touched on Windows too:
|
|
68
|
+
Git Bash `/c/...` and `/cygdrive/c/...`, `C:/...` and quoted `C:\...` paths, and `git.exe -C` (any case); Bash
|
|
69
|
+
text is split with Git Bash's own quoting, so an unquoted backslash is an escape there too. A `cd` inside `( )`, a
|
|
70
|
+
pipeline's stage or a list sent to the background no longer moves the folder a later relative `-C` is read
|
|
71
|
+
against; a newline ends a command, a here-document's body is text (a `#` comment no longer hides its lines, and
|
|
72
|
+
`<<<` opens none), `&>` only redirects, `git -C /a -C b` runs in `/a/b`, and `pushd` and a `cd` after `{` or `then`
|
|
73
|
+
are read. Cygwin and MSYS Python read paths as POSIX. Before, the Stop hook did not read the
|
|
74
|
+
transcript on Windows at all, and a turn from Windows came without `repositories`. The hooks run `python3`, which
|
|
75
|
+
must name Python 3.9 or later in Git Bash there (session-hooks.md; FAA-183 makes the plugin find it itself).
|
|
76
|
+
- **A relative `git -C` after `cd`** (FAA-166) is read against the folder the command last changed into with an
|
|
77
|
+
absolute `cd`; after a relative or unknown `cd` it is dropped, never read against the session's folder.
|
|
78
|
+
- **The board responder's recovery** (FAA-163, FAA-158). A refused enroll of one capability no longer stops the
|
|
79
|
+
others: it is retried on its own, the pause doubling, and said once as `board.slot.refused`. A slot's move is written
|
|
80
|
+
under the responder's lock and only while its record is unchanged, so an operator's take-back at the same moment
|
|
81
|
+
wins. The slot code moved to its own twin modules, `fabric_board_slots.py` and `fabric-board-slots.mjs`.
|
|
82
|
+
- **An idle stop leaves the board within 3 s** (FAA-173): a responder with no work in hand closes its session with a
|
|
83
|
+
confirmed `DELETE` inside the hard exit (`close(timeout)`: a timeout not above 0 is the session's own, and a session built
|
|
84
|
+
with one takes 15 s, in both twins),
|
|
85
|
+
instead of letting the slot lapse (a board call already in flight when the
|
|
86
|
+
stop comes runs first, and when the join ends before the DELETE the slot lapses as before). The slot records are forgotten only after
|
|
87
|
+
a 2xx: a 404 (the session already gone) keeps them, since a move made before it ended is read at the next
|
|
88
|
+
`com.status`. A refusal of the whole board session (`not_available` included) is the board down, never a refused
|
|
89
|
+
capability; a capability refused, or contested, again after it was held is said again, once per capability. A clean
|
|
90
|
+
leave forgets only the records this session confirmed (a slot it held, paused under the operator's hold included, or
|
|
91
|
+
one `com.status` showed unmoved) and no fenced call has put in doubt since, so a slot moved while the service was
|
|
92
|
+
down stays `moved` when the session is dropped at connect or recovery stops early, and is never enrolled again on
|
|
93
|
+
its own. A paused or retrying slot reads `com.status` before each new ask, so a move during the operator's hold is
|
|
94
|
+
said. `Checkpoint.leave(project, held)` takes `held` as a required argument in both twins.
|
|
95
|
+
- **`action_gate` fails closed** (FAA-139): a spend that is not a finite number is read as no estimate, which a
|
|
96
|
+
mandate's `perCallMaxUsd` ceiling refuses (a mandate with no ceiling covers it; the question is with the contract,
|
|
97
|
+
FAA-185), and a project that cannot be read is never covered by a mandate limited to `projects`. A `null` mandate in
|
|
98
|
+
`_meta` reads as an unreadable one, not as none (FAA-178; Python's `approval_step`, while a Node service passes `{}`
|
|
99
|
+
to `jobStep` itself, interop.md). A job's start (`job_step`) reads spend and project as its answer does, and keeps
|
|
100
|
+
the spend as it read it (a NaN is no estimate, never a bare `NaN` in the job file).
|
|
101
|
+
- **`fabric_secrets` matches the contract's ECMAScript reading** (FAA-136): `\s`, `.` and case folding as JavaScript
|
|
102
|
+
reads them, so both ports flag the same values. `secret_url` also reads a special scheme as `new URL` does: every
|
|
103
|
+
slash or backslash after `https:` (or `http`, `ftp`, `ws`, `wss`) is skipped and a backslash before the query reads
|
|
104
|
+
as a slash, so `https:u:p@host` and `https:\\u:p@host` carry a password in Python as in Node. Python splits the URL
|
|
105
|
+
by hand rather than with `urlsplit`, which refuses a `[` or `]` outside an IPv6 host (`https://u:p[w@h/`), and drops
|
|
106
|
+
one leading `?` from a parameter's name as `URLSearchParams` does. Where `new URL` refuses a host (none, a port over
|
|
107
|
+
65535), Python still reads the password and the parameters, so it can flag more than Node, never less. The
|
|
108
|
+
`source.repository` check of a descriptor and of a provider entry reads a special scheme's password the same way
|
|
109
|
+
(`https:bot:pw@host`), up to the user information's last `@` (`https://me@corp.example:pw@host`), and says so
|
|
110
|
+
before it judges the host. The query and the fragment are read percent-encoded, as `new URL` hands them over.
|
|
111
|
+
`fabric_http.redact_url` reads a special scheme the same way (below).
|
|
112
|
+
- **What the kit sends, refuses and redacts as a URL** (FAA-136; the remaining differences are FAA-187):
|
|
113
|
+
- `redact_url`: the URL an `HttpError` carries for `https:u:pw@host` has no password, as in Node; a URL of any
|
|
114
|
+
other scheme (a misspelled `htps:` included) keeps only its scheme in both twins (`htps:<redacted>`), since its
|
|
115
|
+
user information may sit anywhere in the rest. It first trims C0 controls and space from the ends and removes
|
|
116
|
+
tabs and line breaks, as `new URL` does, so ` https://u:pw@host` keeps no password on Python 3.9 either. A
|
|
117
|
+
string with no scheme (`ht tps:`, `1ttps:`, a leading byte order mark or no-break space) and a URL whose host or
|
|
118
|
+
port `new URL` refuses (a password holding `/`, a port that is not ASCII digits such as `1_2` or `+1`, which
|
|
119
|
+
Python 3.9's `int()` takes, a bracketed host that is no IPv6 address, a `ws`, `wss` or `ftp` URL with no host or
|
|
120
|
+
a port above 65535) read as `<unparseable url>`; the scheme is read by the WHATWG rule, not by `urlsplit`, whose
|
|
121
|
+
rule differs between 3.9 patch releases. On Python a host holding `%` reads as `<unparseable url>` too, where
|
|
122
|
+
Node writes it decoded. The path is not redacted in either twin (Node writes it as `new URL` normalises it,
|
|
123
|
+
Python as given), so a key never belongs in it.
|
|
124
|
+
- `check_url` and `request`: the same ports and bracketed hosts (an IPv6 zone included) are refused. On Python a
|
|
125
|
+
backslash, `%`, space or control character in the host (a Unicode space and a C1 control included) is refused
|
|
126
|
+
too, since urllib would decode it or dial another host (`h.example%3A8443` reached port 8443), and so is a space
|
|
127
|
+
or ASCII control character before the URL's first `#`, or a character outside ASCII in its path or query, which
|
|
128
|
+
urllib would send and http.client refuse: such a URL is refused before it is sent (kind `refused`), where 0.55.0
|
|
129
|
+
retried it and answered kind `connection`. Python's `check_url` checks the host's shape, not its name (FAA-187
|
|
130
|
+
(14), (16)). The Node twin reads several of these as `new URL` does, among them a tab or line break removed, a
|
|
131
|
+
space or a character outside ASCII percent-encoded, a backslash as `/`, an extra slash before the host skipped
|
|
132
|
+
and a `%` host decoded (refused when the decoded host is not allowed, as `h.example%3A8443`).
|
|
133
|
+
- What is sent: the default transport sends a URL up to its first `#` in both twins (urllib alone kept everything
|
|
134
|
+
before the last one, so `/x#a#b` asked for `/x#a`); a transport you pass receives the URL as given. ASCII
|
|
135
|
+
whitespace at the ends, which urllib and `new URL` both trim, is trimmed, so a URL ending in a line break is still
|
|
136
|
+
sent, as in 0.55.0 (on Python 3.9 a URL with a leading space too, which 0.55.0 refused there). On Python a URL
|
|
137
|
+
whose part before its first `#` ends in a Unicode space (a no-break or ideographic space) is now refused; 0.55.0
|
|
138
|
+
sent it trimmed when the space ended the URL and failed every call when a fragment followed it. Node keeps it
|
|
139
|
+
percent-encoded.
|
|
140
|
+
- The OpenRouter clients trim a base URL's ends first: ASCII whitespace and trailing slashes in both, and on Node
|
|
141
|
+
every C0 control there and a tab or line break anywhere, as `new URL` reads them. So `.../v1\n`, `.../v1/ ` and,
|
|
142
|
+
on Node, `.../v1\n/` are sent to `.../v1/chat/completions`. Both then refuse, when they are built, a base URL
|
|
143
|
+
holding a query or a fragment (kind `config`, read before `check_url`; `/chat/completions` would land in it, so a path-routed endpoint
|
|
144
|
+
such as OpenRouter failed every call in 0.55.0). Python also refuses, through `check_url` (kind `refused`), a
|
|
145
|
+
base with a space, a control or a non-ASCII character in its path, which 0.55.0 accepted and then failed every
|
|
146
|
+
call with; Node sends such a path percent-encoded, as 0.55.0 did (FAA-187 (18)). Attribution answers `not-openrouter` for a port of other
|
|
147
|
+
digits (`:٤٤٣`), as `new URL` reads it.
|
|
148
|
+
- A JWKS source: the Python kit reads its host and port as urllib dials them, https included. It refuses a space
|
|
149
|
+
or ASCII control character before the URL's first `#` (it reads the URL up to there, as Node does), a character
|
|
150
|
+
outside ASCII in its path or query, a host holding `%`, a backslash, a Unicode space or a control character, a
|
|
151
|
+
bracketed host that is no IPv6 address, a port that is not ASCII digits or is above 65535, no host, and any `@`
|
|
152
|
+
(an empty userinfo dialled `@127.0.0.1`). A trailing ASCII space or line break is trimmed, as urllib reads it; a trailing Unicode space
|
|
153
|
+
before the first `#` is refused for the same reason as in `check_url`, though urllib would trim it and read the
|
|
154
|
+
keys when it ends the URL; a leading space is refused as not a URL, as in 0.55.0 and in Node. Neither `[::1%...]` nor
|
|
155
|
+
`http://[::1]%2Eevil.example/` (which Python 3.9's `urlsplit` reads as `::1`) nor a mapped `[::ffff:127.0.0.1]`
|
|
156
|
+
(loopback to Python 3.13+) counts as loopback. The Node kit accepts several of these as `new URL` reads them,
|
|
157
|
+
among them a `%` host decoded, a backslash as `/`, a tab or line break removed, an empty userinfo dropped and an
|
|
158
|
+
extra slash before the host skipped (`https:///j` reads host `j`).
|
|
159
|
+
- **A too-deep protocol value** is refused by name (FAA-122), in both twins.
|
|
160
|
+
- **A job's approval step and its answer read the mandate under the same name** (FAA-179): the job's stored
|
|
161
|
+
capability, also when a handler created the job under another name; `on_fault` hears both under it, not the answer
|
|
162
|
+
under `fabric.job.get`.
|
|
163
|
+
- **Nudges** (FAA-143): `lost=` counts every reminder that did not make it back to the file (this turn's capped lines,
|
|
164
|
+
and the pending ones when the write-back fails too); an answer's nudges are read up to 1,000 items, in linear time.
|
|
165
|
+
|
|
166
|
+
### Added
|
|
167
|
+
|
|
168
|
+
- `FABRIC_SESSION_NO_REPOSITORIES=1`, or a `no-repositories` file in the hooks' folder (which a cache cleaner may
|
|
169
|
+
remove), turns the transcript read off (FAA-160); `fabric_session.py status` says
|
|
170
|
+
whether it is on (only while a running Fabric advertises `session-hooks/1`, `fabric` is registered for the folder and
|
|
171
|
+
Fabric takes the repositories), off, waiting for Fabric, or not done because `fabric` is not registered. The repositories of a turn can miss one (FAA-174): a turn longer
|
|
172
|
+
than the transcript's last 512 KB names only what that tail shows; until FAA-184 they can also name a wrong one,
|
|
173
|
+
since a relative `git -C` in a later Bash call is read against the session's folder.
|
|
174
|
+
- The reference service's dashboard shows a moved slot with a Take back button (`POST /api/board/take-back`, operator
|
|
175
|
+
only; FAA-157), and the button has its tool: `board.take-back`, a `relayed` action an agent whose client sets `_meta`
|
|
176
|
+
calls with the operator's decision (Claude Code and Codex reach the page until Fabric forwards decisions, CT-125).
|
|
177
|
+
The tool is a manifest capability too (effect `change-policy`, idempotency `required`), as every tool the well-known
|
|
178
|
+
lists must be: `register --board-project <uuid>` writes it into the manifest, so `check_service.py` passes the sample
|
|
179
|
+
with its board on. The manifest stays register's to write: a serve whose board mode differs from it at its start
|
|
180
|
+
logs `service.manifest.mismatch` (warning), and the probe fails until it is registered again. Its `/mcp` reads a loopback call the service token authenticated as T0. The
|
|
181
|
+
page, the route and the tool read a move from the checkpoint's record too, so a take-back made after a restart and
|
|
182
|
+
before the session reconnects (or while Fabric is down) is kept; a checkpoint it cannot write is a tool error. A
|
|
183
|
+
repeat after a lost answer is answered `{capability, moved: false}` by the route as by the tool, and each take-back is
|
|
184
|
+
a `board.slot.returned` event (the capability as its subject) whose sentence says the route in words and whose `data`
|
|
185
|
+
holds `by`, `via` and the `caller` (none when the feed refuses the caller), never the decision's words. The service installs its stop handler before its board thread starts, so a SIGTERM at any moment drains and leaves the board session. The
|
|
186
|
+
page's CSP allows the button's own `fetch` (`connect-src 'self'`), and a failed request says so on the button.
|
|
187
|
+
- working-with-fabric Step 4: a `fabric_task_create` that ended in an error may have filed the piece, so look for it
|
|
188
|
+
by title in `fabric_work_list` before filing it again (FAA-168), with the eval `a-failed-create-may-have-filed`.
|
|
189
|
+
|
|
190
|
+
### Changed
|
|
191
|
+
|
|
192
|
+
- working-with-fabric names Fabric v0.3.5 as the release that brought `fabric-mcp` and the `fabric_work_*` tools, not
|
|
193
|
+
0.4.0 (the plan milestone), and goes by whether the tools are listed (FAA-170).
|
|
194
|
+
- The `board.slot.moved` notice says the accepted work stays `accepted` (not "held", the operator's word) and that
|
|
195
|
+
after its take-back action a restart or reconnect while accepted work waits takes the slot back at once; a restart
|
|
196
|
+
alone does not (FAA-164, FAA-171);
|
|
197
|
+
project-board.md says the same to a requester. board.md says a take-back without the operator's relayed decision is
|
|
198
|
+
refused. session-hooks.md gives the Bash cap as 64 K characters and the downgrade window as until a worker next
|
|
199
|
+
reaches the running Fabric.
|
|
200
|
+
|
|
201
|
+
### Upgrading
|
|
202
|
+
|
|
203
|
+
- On Windows the Stop hook now reads the transcript's tail and the touched repositories' `origin` (FAA-175), once
|
|
204
|
+
Fabric takes `repositories`, as it does on macOS and Linux; `FABRIC_SESSION_NO_REPOSITORIES=1` (in Claude Code's
|
|
205
|
+
settings `env`, which reached hooks declared in settings and in a plugin's `hooks.json` in runs on macOS; Windows is
|
|
206
|
+
FAA-183's run) turns it off.
|
|
207
|
+
- A Python `JwksCache` source is now refused when the cache is built, so a service that builds it at start stops
|
|
208
|
+
there, if it is any source the JWKS source item under Fixed lists. Among them, 0.55.0 read: a host holding `%` (an
|
|
209
|
+
IPv6 zone included, as in `http://[::1%25lo0]/`), which urllib decoded and dialled; a port that is not ASCII digits
|
|
210
|
+
(`:1_8080`); a bracketed IPv4 host (`[127.0.0.1]`, which Python 3.9 dialled as `127.0.0.1`); a mapped
|
|
211
|
+
`[::ffff:127.0.0.1]` loopback (on Python 3.13+); a Unicode space ending the URL. Write the host and port plainly
|
|
212
|
+
(`127.0.0.1:18080`) and trim the space. The other sources it lists 0.55.0 accepted and then answered every
|
|
213
|
+
delegated call `issuer-unavailable`.
|
|
214
|
+
- On Python, `check_url` and `request` now refuse before sending (kind `refused`) a URL that 0.55.0 sent: a host
|
|
215
|
+
holding `%` (`h.example%3A8443` reached port 8443), a port that is not ASCII digits (`:1_2` reached port 12 on
|
|
216
|
+
3.9), a bracketed IPv4 host (`[127.0.0.1]`, dialled on 3.9), and a URL whose part before its first `#` ends in a Unicode space (sent trimmed). A URL with a space, a
|
|
217
|
+
control or a non-ASCII character in its path or query, which 0.55.0 retried and answered kind `connection`, is
|
|
218
|
+
refused the same way. Both OpenRouter clients refuse, when they are built, a base URL holding a query or a
|
|
219
|
+
fragment, and the Python client one with a space, a control or a non-ASCII character in its path, so a service
|
|
220
|
+
that builds its client at start stops there. Write the host and port plainly, trim the space, drop the query or fragment where the URL is
|
|
221
|
+
configured, and percent-encode a space, a control or a non-ASCII character in each path segment or query
|
|
222
|
+
value (`urllib.parse.quote(value, safe="")`, `urllib.parse.urlencode` for a query), never the whole URL.
|
|
223
|
+
- A service on the kit joins its responder thread at stop, bounded inside its hard exit, as `sample_service.py` does
|
|
224
|
+
(board.md, "Wiring it into the service"); on Node it aborts the run's signal and awaits the run. The idle leave above
|
|
225
|
+
happens only then. A service that stops without the join still works; its
|
|
226
|
+
slot lapses on the board's clock as before.
|
|
227
|
+
- A service that calls `Checkpoint.leave` itself now passes the generations it confirmed (`held`, required in both
|
|
228
|
+
twins); the responder's own leave does this.
|
|
229
|
+
- A service's own `emit` that matched the board responder's level `warn` now sees `warning`, with a `subject` when its
|
|
230
|
+
signature names one. A service with `emit=log.append` now logs the board's notices, and those with `notify` (access
|
|
231
|
+
needed, a contested or moved slot, a held request, a reconcile needed) reach the host's banners, without a link to
|
|
232
|
+
the page that resolves them (FAA-187 (21)). A Fabric outage's `board.down` (no notify) is now a row in the feed too,
|
|
233
|
+
said again after each recovery (FAA-187 (22)).
|
|
234
|
+
- A reference service served with `--board-project` registers again with the same `--board-project`
|
|
235
|
+
(`sample_service.py register ... --board-project <uuid>`): a registration from 0.55.0 lacks `board.take-back`, so
|
|
236
|
+
`check_service.py` fails `interop.well-known-capabilities` and the log says `service.manifest.mismatch`. Register
|
|
237
|
+
before you serve: the service checks its manifest only when it starts, so a warning logged before the registration
|
|
238
|
+
stays in the log until the next start.
|
|
239
|
+
- A service that calls `action_gate` (`actionGate`) itself passes a finite `spend_usd` and a string `project`, or
|
|
240
|
+
expects the operator's decision under a mandate with a `perCallMaxUsd` ceiling or a `projects` limit; an `on_fault`
|
|
241
|
+
that matched a job's answer under `fabric.job.get` now matches the job's capability; a descriptor's or provider
|
|
242
|
+
entry's `source.repository` names the repository without a password in its user information (`https:bot:pw@host`
|
|
243
|
+
is refused).
|
|
244
|
+
|
|
3
245
|
## 0.55.0 (2026-10-27)
|
|
4
246
|
|
|
5
247
|
### Added
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ a terminal that works in a project through Fabric. It finds Fabric (`hub.json`,
|
|
|
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) and asks other
|
|
27
27
|
Projects only through `com.submit`, asks who is working on what and claims its task before it starts
|
|
28
|
-
(`fabric_work_list`, `fabric_work_claim`, Fabric
|
|
28
|
+
(`fabric_work_list`, `fabric_work_claim`, Fabric v0.3.5 and later), links tasks to backlog rows instead of copying them, and
|
|
29
29
|
releases at the end (`fabric_work_release`). `scripts/fabric_session.py status` prints where Fabric stands and one
|
|
30
30
|
next action:
|
|
31
31
|
|
|
@@ -38,13 +38,14 @@ resume) puts that preamble into the session when the MCP server `fabric` is regi
|
|
|
38
38
|
uses Fabric (one line pointing at `fabric_whoami` in a folder with no sign of it); when Fabric
|
|
39
39
|
is down it adds one line (skip `fabric_whoami`, start Fabric through the broker if the task needs it), and only in a
|
|
40
40
|
project that uses Fabric; Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
|
|
41
|
-
and the board's unread count, and, once the running Fabric takes them (
|
|
41
|
+
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
|
|
42
42
|
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
|
|
43
43
|
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
|
|
44
44
|
session left claimed. Stop, UserPromptSubmit and SessionEnd spool and call Fabric only when the running Fabric
|
|
45
45
|
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 Fabric token (an `origin` URL's embedded credential is dropped in memory, never
|
|
46
46
|
kept or sent) and always
|
|
47
|
-
exit 0
|
|
47
|
+
exit 0 once `python3` starts (on Windows, Claude Code runs hooks in Git Bash, or PowerShell without it, where `python3`
|
|
48
|
+
must name Python 3.9 or later; FAA-183). `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
|
|
48
49
|
--host codex|kimi|hermes|opencode|openclaw|pi` installs each host's native context bridge (dry run unless `--apply`).
|
|
49
50
|
Codex receives structured SessionStart JSON; Hermes, OpenCode, OpenClaw and Pi use native plugin APIs.
|
|
50
51
|
See the [native host matrix](plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md)
|
|
@@ -284,7 +285,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
|
|
|
284
285
|
The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
|
|
285
286
|
|
|
286
287
|
- version: `0.1.0`
|
|
287
|
-
- commit: `
|
|
288
|
+
- commit: `3830a3353923e20f826d466610e1c479f120bec0`
|
|
288
289
|
|
|
289
290
|
Every other mention of the default contract revision (this section, the skills' metadata, the
|
|
290
291
|
skill card, `adapt_project.py`) must equal it; `python3 test/validate.py` fails a tree
|
package/SECURITY.md
CHANGED
|
@@ -33,9 +33,10 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
|
|
|
33
33
|
reads by name at run time; the kit itself calls no outside host on its own. A board
|
|
34
34
|
responder talks only to Fabric's hub on loopback.
|
|
35
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; with the mark below
|
|
37
|
-
session Fabric launched. Stop also,
|
|
38
|
-
`repositories` (FAA-140; a worker sees it in the tool's schema and leaves a mark)
|
|
36
|
+
in the session's folder (counts only, at most 1.5 s; with the mark below, also that folder's own `origin`, only to leave its repository out), read the process table to tell a
|
|
37
|
+
session Fabric launched. Stop also, only once the running Fabric's `fabric_session_observe` takes
|
|
38
|
+
`repositories` (FAA-140; a worker sees it in the tool's schema and leaves a mark) and unless the person turned it off
|
|
39
|
+
(`FABRIC_SESSION_NO_REPOSITORIES=1` or a `no-repositories` file in the hooks' folder, FAA-160), reads the
|
|
39
40
|
last 512 KB of the session's transcript (earlier turns included; only the entries after the
|
|
40
41
|
last prompt the person typed are used; the tail holds message text and tool output too, file contents and
|
|
41
42
|
command output that can carry a secret the session displayed, read in memory only and never spooled, logged or sent)
|
|
@@ -45,8 +46,8 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
|
|
|
45
46
|
host, owner and name, never a local path, a command or message text (on a self-hosted ssh
|
|
46
47
|
remote, `owner` is the folder path on that server before the repository's name). The mark is
|
|
47
48
|
removed only by a worker that sees a schema without the field, so after Fabric is downgraded
|
|
48
|
-
the turns that end before a worker next
|
|
49
|
-
names is sent. They write their spool and log under the user's cache folder (0600),
|
|
49
|
+
the turns that end before a worker next reaches the running Fabric (about a minute; longer while
|
|
50
|
+
rejoins fail) are still read; none of their names is sent. They write their spool and log under the user's cache folder (0600),
|
|
50
51
|
and the delivery worker starts `fabric-mcp` in its own process group and ends that group
|
|
51
52
|
(`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no Fabric token. An
|
|
52
53
|
`origin` URL that embeds a credential (`https://user:token@host/...`) is read by `git config`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.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'"
|
|
@@ -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.57.0",
|
|
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 (
|
|
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). All silent in a session Fabric launched. 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.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -5,9 +5,9 @@ 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.57.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "3830a3353923e20f826d466610e1c479f120bec0"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Adapting projects to Fabric
|
|
@@ -76,7 +76,7 @@ Use exactly:
|
|
|
76
76
|
|
|
77
77
|
- contract version `0.1.0`;
|
|
78
78
|
- repository `https://github.com/passioncode-ai/fabric-agent-contract`;
|
|
79
|
-
- commit `
|
|
79
|
+
- commit `3830a3353923e20f826d466610e1c479f120bec0`.
|
|
80
80
|
|
|
81
81
|
This is the default for new bundles. When checking an issued legacy bundle, preserve its
|
|
82
82
|
explicit supported lock and use that exact clean checkout. Never silently upgrade or
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md
CHANGED
|
@@ -11,6 +11,7 @@ directory:
|
|
|
11
11
|
|---|---|
|
|
12
12
|
| macOS | `~/Library/Application Support/ai.passioncode.fabric/providers/` |
|
|
13
13
|
| Linux | `${XDG_DATA_HOME:-~/.local/share}/passioncode-fabric/providers/` |
|
|
14
|
+
| Windows | `%LOCALAPPDATA%\passioncode-fabric\providers\` (the same root as the services folder, DEC-0032; `~\AppData\Local` when `LOCALAPPDATA` is unset) |
|
|
14
15
|
| any | `FABRIC_PROVIDERS_DIR` when set |
|
|
15
16
|
|
|
16
17
|
A service uses a descriptor instead (the `building-fabric-services` skill). One id is a
|
|
@@ -49,15 +50,25 @@ It refuses, with one sentence that never quotes a value:
|
|
|
49
50
|
credential itself: a token prefix, a private key, a JWT (FAC-SEM-015: a value is
|
|
50
51
|
checked by its form; name patterns apply to names);
|
|
51
52
|
- what the pinned `provider.schema.json` refuses: a URL other than `http://127.0.0.1:<port>/mcp` with a port from 100
|
|
52
|
-
to 65535, a manifest path that is not an absolute or
|
|
53
|
-
|
|
54
|
-
names, a `source.repository` that is not an absolute URI, an
|
|
55
|
-
unknown fields. The patterns are the schema's, copied
|
|
56
|
-
contract checkout at the pin when `FABRIC_CONTRACT_NEW` names
|
|
53
|
+
to 65535, a manifest path that is not an absolute, `~/` or `~\` path to a file named `fabric-agent.json` after
|
|
54
|
+
either separator (DEC-0145; or is a network share, or over 1024 characters), a shell-string command, more than 32
|
|
55
|
+
arguments or one over 1024 characters, more than 64 env names, a `source.repository` that is not an absolute URI, an
|
|
56
|
+
`installedAt` that is not an RFC 3339 date-time, unknown fields. The patterns are the schema's, copied
|
|
57
|
+
(`test_provider.SchemaParityTests` compares them with a contract checkout at the pin when `FABRIC_CONTRACT_NEW` names
|
|
58
|
+
one, as the contract job of the nightly CI does), and the reader is held to every `provider.schema.json` fixture of
|
|
59
|
+
the pin (`test/fixtures/contract/provider-fixtures.json`, `test_provider.ContractFixtureTests`);
|
|
57
60
|
- a `source.repository` with a `user:password@` or a value shaped like a credential: it names the repository, never
|
|
58
|
-
reaches it (since 0.49.3, as the service kit's descriptor check)
|
|
61
|
+
reaches it (since 0.49.3, as the service kit's descriptor check);
|
|
62
|
+
- a manifest whose name follows a backslash (`C:\Agents\example\fabric-agent.json`). The reader,
|
|
63
|
+
`validate_provider_entry`, accepts it since DEC-0145 (`MANIFEST_PATTERN`, the schema's), but a reader that has not
|
|
64
|
+
re-pinned to DEC-0145 refuses the entry, Fabric's registry among them (CT-192), so the writer does not emit it until
|
|
65
|
+
the oldest Fabric release in use reads it (the release after v0.3.15, CO-479). Write the path with `/` before the name. A manifest under the home folder
|
|
66
|
+
(`~/Agents/example/fabric-agent.json`) is read by every Fabric release; one with a drive letter
|
|
67
|
+
(`C:/Agents/example/fabric-agent.json`) only by Fabric after v0.3.15, whose registry took the repin (CO-479): v0.3.15
|
|
68
|
+
and earlier read only a manifest under `~/` or `/`, so not `~\` either.
|
|
59
69
|
|
|
60
|
-
`validate` also checks that the file is named `<id>.json` (FAC-SEM-014, slug half)
|
|
70
|
+
`validate` also checks that the file is named `<id>.json` (FAC-SEM-014, slug half): the stem is what follows the last
|
|
71
|
+
`/` or `\`, on every platform (CT-193).
|
|
61
72
|
From Python: `write_provider_entry(entry)`, `remove_provider_entry(id)`,
|
|
62
73
|
`validate_provider_entry(entry)`, `manifest_problems(entry)`, `providers_dir()`.
|
|
63
74
|
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py
CHANGED
|
@@ -22,10 +22,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
|
|
|
22
22
|
|
|
23
23
|
CONTRACT_VERSION = "0.1.0"
|
|
24
24
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
25
|
-
CONTRACT_COMMIT = "
|
|
25
|
+
CONTRACT_COMMIT = "3830a3353923e20f826d466610e1c479f120bec0"
|
|
26
26
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
27
27
|
# Only this declaration may name a legacy revision in live files.
|
|
28
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "1197a90dc4b57152cabcc71ad17db7559f35a27f", "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
28
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "00e050ba7429c0c60261cec871779e0fd6aeeb28", "1197a90dc4b57152cabcc71ad17db7559f35a27f", "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
29
29
|
# #endregion supported-contract-revisions
|
|
30
30
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
31
31
|
MCP_REVISION = "2026-07-28"
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py
CHANGED
|
@@ -41,16 +41,26 @@ PROTOCOL = "fabric-provider/0.1"
|
|
|
41
41
|
URL_PATTERN = r"^http://127\.0\.0\.1:([1-9][0-9]{2,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])/mcp$"
|
|
42
42
|
LOCAL_PATH_PATTERN = "^(~[\\\\/]|/(?![\\\\/])|[A-Za-z]:[\\\\/])[^\\u0000]*$"
|
|
43
43
|
LOCAL_PATH_MAX = 1024
|
|
44
|
+
# DEC-0145 (CT-192): the manifest is named fabric-agent.json after either separator, so a Windows path names it too. The
|
|
45
|
+
# schema's pattern is a search, not a whole-value match; it is applied with its `$` read as \Z (R1, below).
|
|
46
|
+
MANIFEST_PATTERN = r"(^|[\\/])fabric-agent\.json$"
|
|
44
47
|
ARGV_MAX_ITEMS = 32
|
|
45
48
|
ARGV_MAX_ITEM = 1024
|
|
46
49
|
ENV_MAX = 64
|
|
47
50
|
# Whole-value patterns end in \Z, never `$` (R1: `$` also matches before a trailing newline).
|
|
48
51
|
_ID = re.compile(r"^[a-z][a-z0-9-]{1,62}\Z")
|
|
49
52
|
_ENV_NAME = re.compile(r"^[A-Z_][A-Z0-9_]*\Z")
|
|
50
|
-
|
|
53
|
+
# A special scheme (http, https, ws, wss, ftp) reads a user:password@ after any run of slashes, none included, as
|
|
54
|
+
# `new URL` does: https:bot:pw@host carries the password too (0.56.0 seam r7).
|
|
55
|
+
_URI_PASSWORD = re.compile(r"^(?:(?:[Hh][Tt][Tt][Pp][Ss]?|[Ww][Ss][Ss]?|[Ff][Tt][Pp]):[/\\]*[^/\\?#]*:[^/\\?#]*@"
|
|
56
|
+
# any other scheme: a backslash belongs to the user information (seam r8)
|
|
57
|
+
r"|(?![Hh][Tt][Tt][Pp][Ss]?:|[Ww][Ss][Ss]?:|[Ff][Tt][Pp]:)[A-Za-z][A-Za-z0-9+.-]*://[^/?#]*:[^/?#]*@)")
|
|
51
58
|
_SECRET_REF = re.compile(r"^secret-ref:([A-Za-z0-9][A-Za-z0-9._/:@-]{0,255})\Z")
|
|
52
59
|
_LOCAL_PATH = re.compile(LOCAL_PATH_PATTERN)
|
|
53
60
|
_URL = re.compile(URL_PATTERN)
|
|
61
|
+
_MANIFEST = re.compile(MANIFEST_PATTERN[:-1] + r"\Z")
|
|
62
|
+
# The manifest's name after a backslash: valid since DEC-0145, but no writer emits it until every reader accepts it.
|
|
63
|
+
_MANIFEST_AFTER_BACKSLASH = re.compile(r"\\fabric-agent\.json\Z")
|
|
54
64
|
# format: uri (RFC 3986 absolute URI): a scheme, a colon, then no whitespace or control character.
|
|
55
65
|
_URI = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:[^\s\x00-\x1f\x7f]+\Z")
|
|
56
66
|
# format: date-time (RFC 3339).
|
|
@@ -85,6 +95,10 @@ def services_dir() -> Path:
|
|
|
85
95
|
home = Path.home()
|
|
86
96
|
if sys.platform == "darwin":
|
|
87
97
|
return home / "Library/Application Support/ai.passioncode.fabric/services"
|
|
98
|
+
if sys.platform == "win32":
|
|
99
|
+
# provider.md "Where the directory is" (DEC-0032): LOCALAPPDATA, never the roaming APPDATA.
|
|
100
|
+
local = os.environ.get("LOCALAPPDATA")
|
|
101
|
+
return (Path(local) if local else home / "AppData" / "Local") / "passioncode-fabric" / "services"
|
|
88
102
|
return Path(os.environ.get("XDG_DATA_HOME") or str(home / ".local/share")) / "passioncode-fabric/services"
|
|
89
103
|
|
|
90
104
|
|
|
@@ -124,8 +138,9 @@ def validate_provider_entry(entry: Any) -> List[str]:
|
|
|
124
138
|
problems.append("summary is at most 200 characters")
|
|
125
139
|
manifest = entry["manifest"]
|
|
126
140
|
if (not isinstance(manifest, str) or len(manifest) > LOCAL_PATH_MAX or not _LOCAL_PATH.fullmatch(manifest)
|
|
127
|
-
or not
|
|
128
|
-
problems.append("manifest is an absolute or
|
|
141
|
+
or not _MANIFEST.search(manifest)):
|
|
142
|
+
problems.append("manifest is an absolute, ~/ or ~\\ path to fabric-agent.json (the name after either separator), at most %d characters, "
|
|
143
|
+
"never a network share" % LOCAL_PATH_MAX)
|
|
129
144
|
if "source" in entry:
|
|
130
145
|
source = entry["source"]
|
|
131
146
|
if not isinstance(source, dict) or not set(source) <= {"repository"}:
|
|
@@ -233,8 +248,15 @@ def _service_ids(directory: Path) -> Dict[str, str]:
|
|
|
233
248
|
def write_provider_entry(entry: Dict[str, Any], directory: Optional[Path] = None, services: Optional[Path] = None) -> Path:
|
|
234
249
|
"""Installer-only. Refuses an invalid entry and an id that is already a service (FAC-SEM-013)."""
|
|
235
250
|
problems = validate_provider_entry(entry)
|
|
236
|
-
if
|
|
237
|
-
|
|
251
|
+
if problems:
|
|
252
|
+
raise ProviderError("Provider entry is invalid: %s." % "; ".join(problems))
|
|
253
|
+
if _MANIFEST_AFTER_BACKSLASH.search(entry["manifest"]):
|
|
254
|
+
# provider.md (DEC-0145): a reader before DEC-0145, Fabric's registry among them, refuses this entry until it
|
|
255
|
+
# re-pins (CT-192), so the writer does not emit it yet.
|
|
256
|
+
raise ProviderError("The manifest's name follows a backslash, which a reader that has not re-pinned to DEC-0145 refuses (CT-192). "
|
|
257
|
+
"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 after v0.3.15 (its repin, CO-479).")
|
|
259
|
+
problems = manifest_problems(entry)
|
|
238
260
|
if problems:
|
|
239
261
|
raise ProviderError("Provider entry is invalid: %s." % "; ".join(problems))
|
|
240
262
|
clash = _service_ids(services or services_dir()).get(entry["id"])
|
|
@@ -297,7 +319,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
297
319
|
if args.command == "validate":
|
|
298
320
|
entry = json.loads(args.path.read_text(encoding="utf-8"))
|
|
299
321
|
problems = validate_provider_entry(entry)
|
|
300
|
-
|
|
322
|
+
# CT-193 (FAC-SEM-014): the stem is what follows the last / or \ and loses .json, on every platform.
|
|
323
|
+
stem = re.sub(r"\.json\Z", "", re.sub(r"^.*[\\/]", "", str(args.path), flags=re.S))
|
|
324
|
+
if isinstance(entry, dict) and entry.get("id") != stem:
|
|
301
325
|
problems.append("the file name %s is not <id>.json for %s (FAC-SEM-014)" % (args.path.name, entry.get("id")))
|
|
302
326
|
if not problems:
|
|
303
327
|
problems = manifest_problems(entry)
|