@passioncode-ai/fabric-agent-adapter 0.78.0 → 0.80.2
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 +256 -0
- package/README.md +14 -7
- package/bin/fabric-agent-adapter.js +26 -4
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +23 -8
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/README.md +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/dashboard_service.py +252 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.mjs +168 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.py +179 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/README.md +57 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.mjs +181 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.py +237 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +11 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +26 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +44 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +10 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +15 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +37 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +48 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +9 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +344 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +8 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +55 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +8 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +16 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +97 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +50 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +49 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +9 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +358 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +17 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +24 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +110 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +543 -37
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +12 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +13 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +32 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/working-rules.md +54 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +27 -26
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/cleanup.md +30 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +59 -38
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +52 -26
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/remote-sessions.md +75 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +28 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/task-context.md +58 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/waits.md +36 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +14 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +169 -46
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +146 -19
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +78 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +9 -2
|
@@ -133,12 +133,20 @@ def check(data):
|
|
|
133
133
|
return {'ok': True, 'kind': kind}
|
|
134
134
|
|
|
135
135
|
|
|
136
|
+
def read_input(limit=65536):
|
|
137
|
+
# The handoff's bytes, decoded as UTF-8 with a byte order mark dropped: Windows before Python 3.15 decodes a text
|
|
138
|
+
# stdin with its code page, which misreads a Cyrillic handoff (FAA-210). A stream without bytes is read as text.
|
|
139
|
+
stream = getattr(sys.stdin, 'buffer', None)
|
|
140
|
+
raw = stream.read(limit + 1) if stream is not None else sys.stdin.read(limit + 1)
|
|
141
|
+
require(len(raw) <= limit, 'input_too_large')
|
|
142
|
+
return raw.decode('utf-8-sig') if isinstance(raw, bytes) else raw.lstrip('\ufeff')
|
|
143
|
+
|
|
144
|
+
|
|
136
145
|
def main():
|
|
137
146
|
try:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
except (ValueError, TypeError, KeyError, AttributeError):
|
|
147
|
+
result = check(json.loads(read_input()))
|
|
148
|
+
# RecursionError: Python 3.9 to 3.13 raise it on a deeply nested document under the cap
|
|
149
|
+
except (ValueError, TypeError, KeyError, AttributeError, RecursionError):
|
|
142
150
|
# Fixed reasons only: never echo a submitted URL, token or JSON blob.
|
|
143
151
|
result = {'ok': False, 'reason': 'dashboard_handoff_refused'}
|
|
144
152
|
print(json.dumps(result))
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py
CHANGED
|
@@ -17,6 +17,7 @@ itself takes an option of the same name. --env is for a stdio server only and is
|
|
|
17
17
|
|
|
18
18
|
Normative source: fabric-agent-contract docs/specification/provider.md (DEC-0016, rulings DEC-0017).
|
|
19
19
|
"""
|
|
20
|
+
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
20
21
|
|
|
21
22
|
# #region provider-writer — docs: plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md#the-writer
|
|
22
23
|
|
|
@@ -61,8 +62,12 @@ _URL = re.compile(URL_PATTERN)
|
|
|
61
62
|
_MANIFEST = re.compile(MANIFEST_PATTERN[:-1] + r"\Z")
|
|
62
63
|
# The manifest's name after a backslash: valid since DEC-0145, but no writer emits it until every reader accepts it.
|
|
63
64
|
_MANIFEST_AFTER_BACKSLASH = re.compile(r"\\fabric-agent\.json\Z")
|
|
64
|
-
# format: uri (RFC 3986 absolute URI)
|
|
65
|
-
|
|
65
|
+
# format: uri (RFC 3986 absolute URI) as ajv-formats reads it: RFC 3986 characters only (unreserved, reserved and %HH),
|
|
66
|
+
# the class the service kit's descriptor check uses (fabric_descriptor `_SOURCE_URI`, 0.49.3); a byte-order mark, a
|
|
67
|
+
# zero-width space, a non-ASCII path or an IDN host is refused (FAA-127).
|
|
68
|
+
_URI = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})+\Z")
|
|
69
|
+
# Brackets belong to an IP-literal host only (RFC 3986 3.2.2); the literal is set aside before the character check.
|
|
70
|
+
_IP_LITERAL = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@\[\]]*@)?)\[[0-9A-Fa-f:.vV]+\]")
|
|
66
71
|
# format: date-time (RFC 3339).
|
|
67
72
|
_TIMESTAMP = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?([Zz]|[+-][0-9]{2}:[0-9]{2})\Z")
|
|
68
73
|
FIELDS = {"protocol", "id", "providerId", "name", "summary", "manifest", "run", "source", "installedAt", "installedBy", "extensions"}
|
|
@@ -145,11 +150,14 @@ def validate_provider_entry(entry: Any) -> List[str]:
|
|
|
145
150
|
source = entry["source"]
|
|
146
151
|
if not isinstance(source, dict) or not set(source) <= {"repository"}:
|
|
147
152
|
problems.append("source is {repository?}")
|
|
153
|
+
elif "repository" in source and isinstance(source["repository"], str) and (
|
|
154
|
+
_URI_PASSWORD.match(source["repository"]) or _secrets.repository_credential(source["repository"])):
|
|
155
|
+
# FAA-120, as the service kit: a repository is named, never reached with a credential in its URI. Said
|
|
156
|
+
# before the character check, so a password beside a character the URI class refuses (a backslash, as
|
|
157
|
+
# git reads one) is still named a credential (FAA-127).
|
|
158
|
+
problems.append("source.repository carries no password or credential: it names the repository")
|
|
148
159
|
elif "repository" in source and not _is_uri(source["repository"]):
|
|
149
160
|
problems.append("source.repository is an absolute URI")
|
|
150
|
-
elif "repository" in source and (_URI_PASSWORD.match(source["repository"]) or _secrets.repository_credential(source["repository"])):
|
|
151
|
-
# FAA-120, as the service kit: a repository is named, never reached with a credential in its URI.
|
|
152
|
-
problems.append("source.repository carries no password or credential: it names the repository")
|
|
153
161
|
if not isinstance(entry["installedAt"], str) or not _TIMESTAMP.fullmatch(entry["installedAt"]):
|
|
154
162
|
problems.append("installedAt is an RFC 3339 date-time")
|
|
155
163
|
if not isinstance(entry["installedBy"], str) or not 1 <= len(entry["installedBy"]) <= 200:
|
|
@@ -161,7 +169,7 @@ def validate_provider_entry(entry: Any) -> List[str]:
|
|
|
161
169
|
|
|
162
170
|
|
|
163
171
|
def _is_uri(value: Any) -> bool:
|
|
164
|
-
return isinstance(value, str) and bool(_URI.fullmatch(value))
|
|
172
|
+
return isinstance(value, str) and bool(_URI.fullmatch(_IP_LITERAL.sub(r"\1h", value, count=1)))
|
|
165
173
|
|
|
166
174
|
|
|
167
175
|
def _run_problems(run: Any) -> List[str]:
|
|
@@ -255,7 +263,7 @@ def write_provider_entry(entry: Dict[str, Any], directory: Optional[Path] = None
|
|
|
255
263
|
# re-pins (CT-192), so the writer does not emit it yet.
|
|
256
264
|
raise ProviderError("The manifest's name follows a backslash, which a reader that has not re-pinned to DEC-0145 refuses (CT-192). "
|
|
257
265
|
"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
|
|
266
|
+
"C:/.../fabric-agent.json, which Fabric reads only from v0.3.16 (its repin, CO-479).")
|
|
259
267
|
problems = manifest_problems(entry)
|
|
260
268
|
if problems:
|
|
261
269
|
raise ProviderError("Provider entry is invalid: %s." % "; ".join(problems))
|
|
@@ -285,6 +293,13 @@ def _now() -> str:
|
|
|
285
293
|
return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
286
294
|
|
|
287
295
|
|
|
296
|
+
|
|
297
|
+
def entry_stem(path: str) -> str:
|
|
298
|
+
"""CT-193 (FAC-SEM-014): the stem registry-rules.ts reads, `file.replace(/^.*[\\/]/, "").replace(/\\.json$/, "")`,
|
|
299
|
+
with ECMAScript's `.` (no line break: \\n, \\r, U+2028, U+2029) and `$` (the string's end) (FAA-189)."""
|
|
300
|
+
return re.sub(r"\.json\Z", "", re.sub(r"\A[^\n\r\u2028\u2029]*[\\/]", "", path, count=1))
|
|
301
|
+
|
|
302
|
+
|
|
288
303
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
289
304
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
290
305
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -320,7 +335,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
320
335
|
entry = json.loads(args.path.read_text(encoding="utf-8"))
|
|
321
336
|
problems = validate_provider_entry(entry)
|
|
322
337
|
# CT-193 (FAC-SEM-014): the stem is what follows the last / or \ and loses .json, on every platform.
|
|
323
|
-
stem =
|
|
338
|
+
stem = entry_stem(str(args.path))
|
|
324
339
|
if isinstance(entry, dict) and entry.get("id") != stem:
|
|
325
340
|
problems.append("the file name %s is not <id>.json for %s (FAC-SEM-014)" % (args.path.name, entry.get("id")))
|
|
326
341
|
if not problems:
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py
CHANGED
|
@@ -6,6 +6,7 @@ only. The dashboard kit's fabric-button.js carries its own copy of the name and
|
|
|
6
6
|
|
|
7
7
|
It reads as the contract's ECMAScript does (FAA-136): ECMAScript's whitespace (Python's `\\s` also takes U+0085 and
|
|
8
8
|
U+001C to U+001F, and not U+FEFF), `.` stopping at every line terminator, ASCII case folding, an ASCII word boundary."""
|
|
9
|
+
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
11
12
|
|
|
@@ -142,7 +143,7 @@ def _percent_encode(text: str, also: str) -> str:
|
|
|
142
143
|
return "".join(out)
|
|
143
144
|
|
|
144
145
|
|
|
145
|
-
def
|
|
146
|
+
def url_parts(value: str) -> Tuple[str, str, str]:
|
|
146
147
|
"""The password, query and fragment of an absolute URL as WHATWG URL parsing splits them, read by hand: urlsplit
|
|
147
148
|
refuses a `[` or `]` outside an IPv6 host (`<https>://u:p[w@h/`), where `new URL` reads the password (0.56.0 unit
|
|
148
149
|
r7). The fragment starts at the first `#`, the query at the first `?` before it. A special scheme's authority starts
|
|
@@ -166,6 +167,9 @@ def _url_parts(value: str) -> Tuple[str, str, str]:
|
|
|
166
167
|
_percent_encode(fragment, "`"))
|
|
167
168
|
|
|
168
169
|
|
|
170
|
+
_url_parts = url_parts # the name before FAA-250, kept for code that reached it
|
|
171
|
+
|
|
172
|
+
|
|
169
173
|
def secret_url(value: str) -> bool:
|
|
170
174
|
"""Whether a URL an MCP entry or a launch names carries a credential (LC-21): anything secret_argument finds, a
|
|
171
175
|
password in its user information, or a query or fragment parameter whose name can only carry a credential and has
|
|
@@ -177,7 +181,7 @@ def secret_url(value: str) -> bool:
|
|
|
177
181
|
value = re.sub(r"[\t\n\r]", "", value.strip(_C0_OR_SPACE))
|
|
178
182
|
if not _SCHEME.match(value):
|
|
179
183
|
return False # not an absolute URL (WHATWG URL parsing refuses it)
|
|
180
|
-
password, query, fragment =
|
|
184
|
+
password, query, fragment = url_parts(value)
|
|
181
185
|
if password:
|
|
182
186
|
return True
|
|
183
187
|
for part in (query, fragment):
|
|
@@ -45,6 +45,7 @@ is refused: it would travel with the source. The summary says whether Git chose
|
|
|
45
45
|
Under a locale whose file-system encoding is not UTF-8 the check restarts itself in Python's UTF-8 mode, so a name,
|
|
46
46
|
an argument and a file are read in one encoding. Standard library only.
|
|
47
47
|
"""
|
|
48
|
+
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
48
49
|
|
|
49
50
|
# #region source-check — docs: plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md#the-check
|
|
50
51
|
|
|
@@ -200,7 +201,7 @@ def _url_secret(url: str, depth: int = 0) -> bool:
|
|
|
200
201
|
url = url.rstrip(".,;)]}")
|
|
201
202
|
if not _secrets._SCHEME.match(url):
|
|
202
203
|
return False
|
|
203
|
-
password, query, fragment = _secrets.
|
|
204
|
+
password, query, fragment = _secrets.url_parts(url)
|
|
204
205
|
# what says it is synthetic is the value, never the host or the path (`examples.blob.core.windows.net`)
|
|
205
206
|
if len(password) >= 8 and re.search(r"[0-9]", password) and re.search(r"[A-Za-z]", password) and _real(unquote(password)):
|
|
206
207
|
return True
|
|
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
9
9
|
compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
10
10
|
metadata:
|
|
11
11
|
author: PassionCode.ai
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.80.2"
|
|
13
13
|
contract-version: "0.1.0"
|
|
14
14
|
extension: "fabric-service/0.1"
|
|
15
15
|
extension-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
@@ -168,7 +168,9 @@ Calling out: [the connectors reference](references/connectors.md) (`fabric_http`
|
|
|
168
168
|
deadline, safe retries and a size cap; `fabric_openrouter` with the agent's own key, a
|
|
169
169
|
spend ceiling before every attempt, receipts and a fallback). The draft-and-check step of a
|
|
170
170
|
support agent on the kit: [`recipes/support-agent/`](recipes/support-agent/README.md); the whole agent:
|
|
171
|
-
creating-fabric-agents' `references/agents-that-answer-people.md`.
|
|
171
|
+
creating-fabric-agents' `references/agents-that-answer-people.md`. A service that is only a dashboard:
|
|
172
|
+
[`recipes/dashboard-only/`](recipes/dashboard-only/README.md); a door that hands a key on sealed:
|
|
173
|
+
[`recipes/key-delivery-door/`](recipes/key-delivery-door/README.md).
|
|
172
174
|
|
|
173
175
|
Startup order is the part that goes wrong; keep it exactly:
|
|
174
176
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.6 · Fabric's button (Fabric Agent Contract DEC-0040, FB-1…FB-8, FL-1…FL-6)
|
|
2
2
|
Load it same-origin, after fabric-dashboard.css, with the service it opens Fabric on:
|
|
3
3
|
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent"
|
|
4
4
|
data-project="project-a" data-project-name="Project A"></script>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
(function (root) {
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
|
-
var VERSION = '1.1.
|
|
14
|
+
var VERSION = '1.1.6';
|
|
15
15
|
var COOKIE = 'fabric_button';
|
|
16
16
|
var GET_FABRIC = 'https://passioncode.ai/fabric';
|
|
17
17
|
var ABSENT_AFTER_MS = 1500;
|
|
@@ -69,7 +69,10 @@
|
|
|
69
69
|
if (piece === '') return;
|
|
70
70
|
var at = piece.indexOf('=');
|
|
71
71
|
var name = firstKey(at < 0 ? piece : piece.slice(0, at));
|
|
72
|
-
|
|
72
|
+
var value = at < 0 ? '' : decodeKeepingPlus(piece.slice(at + 1));
|
|
73
|
+
var list = values.get(name);
|
|
74
|
+
if (list) list.push(value); // appended in place, as the contract's secret-shape.ts does (FAA-245)
|
|
75
|
+
else values.set(name, [value]);
|
|
73
76
|
});
|
|
74
77
|
return values;
|
|
75
78
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.6 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
A Fabric service dashboard shown inside a host pane: usable from 360 CSS px up, navigation always
|
|
3
3
|
reachable, wide content in its own scroll box, one vertical scroller, appearance from the host.
|
|
4
4
|
Copy this file next to fabric-dashboard.js; do not edit it in place. Put the page's own rules in
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.6 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
Load it same-origin with <script src="fabric-dashboard.js" defer></script>: no inline script, no
|
|
3
3
|
eval, nothing from a CDN. It applies the host's appearance (ED-6, ED-8), folds navigation that
|
|
4
4
|
does not fit into a More menu (ED-3), makes wide boxes keyboard-reachable (ED-4) and keeps
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
var KIT = 'fabric-dashboard-kit';
|
|
11
|
-
var VERSION = '1.1.
|
|
11
|
+
var VERSION = '1.1.6';
|
|
12
12
|
/* fabric-dashboard-kit:theme-ids:begin */
|
|
13
13
|
var THEMES = ['graphite', 'fabric', 'paper', 'forest', 'ocean', 'ember', 'contrast'];
|
|
14
14
|
/* fabric-dashboard-kit:theme-ids:end */
|
|
@@ -148,6 +148,20 @@
|
|
|
148
148
|
root.addEventListener('focus', applyAppearance);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/* ---- resize observers write in the next frame (since 1.1.6) ----
|
|
152
|
+
A ResizeObserver callback that changes the size of what it watches, or of an ancestor, makes WebKit raise
|
|
153
|
+
"ResizeObserver loop completed with undelivered notifications". So a callback only schedules the work for the next
|
|
154
|
+
animation frame, cancelling one already scheduled, and the work itself skips a size it has already laid out. */
|
|
155
|
+
function nextFrame(work) {
|
|
156
|
+
var pending = null;
|
|
157
|
+
var raf = root.requestAnimationFrame;
|
|
158
|
+
return function () {
|
|
159
|
+
if (typeof raf !== 'function') { work(); return; }
|
|
160
|
+
if (pending !== null) root.cancelAnimationFrame(pending);
|
|
161
|
+
pending = raf.call(root, function () { pending = null; work(); });
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
151
165
|
/* ---- navigation: fold what does not fit into More (ED-3) ---- */
|
|
152
166
|
function foldNav(nav) {
|
|
153
167
|
if (typeof nav.fdRefresh === 'function') { nav.fdRefresh(); return; } // folded already: refit, no second observers
|
|
@@ -212,8 +226,16 @@
|
|
|
212
226
|
}
|
|
213
227
|
}
|
|
214
228
|
layout();
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
// The fit depends on the nav's width only: a callback for a height the fold itself changed lays nothing out.
|
|
230
|
+
var laidOutWidth = null;
|
|
231
|
+
var refit = nextFrame(function () {
|
|
232
|
+
var width = nav.clientWidth;
|
|
233
|
+
if (width === laidOutWidth) return;
|
|
234
|
+
laidOutWidth = width;
|
|
235
|
+
layout();
|
|
236
|
+
});
|
|
237
|
+
if (typeof root.ResizeObserver === 'function') new root.ResizeObserver(refit).observe(nav);
|
|
238
|
+
else root.addEventListener('resize', refit);
|
|
217
239
|
// A page that moves aria-current (its route changed) gets a fresh summary and fit, without telling the kit.
|
|
218
240
|
if (typeof root.MutationObserver === 'function') {
|
|
219
241
|
new root.MutationObserver(layout).observe(nav, { subtree: true, attributes: true, attributeFilter: ['aria-current'] });
|
|
@@ -255,12 +277,16 @@
|
|
|
255
277
|
var header = chrome.querySelector('.fd-header');
|
|
256
278
|
var lift = 0;
|
|
257
279
|
if (height > limit && header) lift = Math.min(Math.ceil(header.getBoundingClientRect().height), height);
|
|
258
|
-
|
|
259
|
-
|
|
280
|
+
var top = lift ? -lift + 'px' : '0px';
|
|
281
|
+
var pinned = Math.max(0, height - lift) + 'px';
|
|
282
|
+
// An unchanged value is not written again: a write restyles the page and can wake the observer once more.
|
|
283
|
+
if (html.style.getPropertyValue('--fd-chrome-top') !== top) html.style.setProperty('--fd-chrome-top', top);
|
|
284
|
+
if (html.style.getPropertyValue('--fd-chrome-h') !== pinned) html.style.setProperty('--fd-chrome-h', pinned);
|
|
260
285
|
}
|
|
261
286
|
set();
|
|
262
|
-
|
|
263
|
-
root.
|
|
287
|
+
var reset = nextFrame(set);
|
|
288
|
+
if (typeof root.ResizeObserver === 'function') new root.ResizeObserver(reset).observe(chrome);
|
|
289
|
+
root.addEventListener('resize', reset);
|
|
264
290
|
}
|
|
265
291
|
|
|
266
292
|
function init() {
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-dashboard-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"profile": "fabric-dashboard/0.1",
|
|
5
5
|
"decision": "DEC-0036",
|
|
6
6
|
"declarationKey": "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"contrast"
|
|
16
16
|
],
|
|
17
17
|
"files": {
|
|
18
|
-
"fabric-dashboard.css": "
|
|
19
|
-
"fabric-dashboard.js": "
|
|
20
|
-
"fabric-button.js": "
|
|
18
|
+
"fabric-dashboard.css": "7a90bbe1f1a2170df5dac0f596a3f302167bef9a7243457bb21dd0eb34f4d359",
|
|
19
|
+
"fabric-dashboard.js": "146ec9a0fd50af2df3245c02ec7dfba75b7d4e8cb5476fedb5ef727869fef1cc",
|
|
20
|
+
"fabric-button.js": "60c2b6d44571213e0237c29a94a4cf220f9de3efcfa697ac6a33777d1ad25fd6",
|
|
21
21
|
"themes.json": "511839f1a1a83eb37fadfd132791474497d5c28c5091700b9aa31ad076e359b5"
|
|
22
22
|
},
|
|
23
23
|
"history": {
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
"fabric-dashboard.css": "cc7a96fa2c5eeb03d113e7a543ed88d43ce78c2808c411de199ea0fc6d55b957",
|
|
55
55
|
"fabric-dashboard.js": "5df01193c590a1d8aa82a521954902fecf2d7f655df482165f90d7291b00087f",
|
|
56
56
|
"fabric-button.js": "e945b3252f9a246826d06dbe7bd4be66e82d539a33728c3d3043fd61d4cbd681"
|
|
57
|
+
},
|
|
58
|
+
"1.1.6": {
|
|
59
|
+
"fabric-dashboard.css": "7a90bbe1f1a2170df5dac0f596a3f302167bef9a7243457bb21dd0eb34f4d359",
|
|
60
|
+
"fabric-dashboard.js": "146ec9a0fd50af2df3245c02ec7dfba75b7d4e8cb5476fedb5ef727869fef1cc",
|
|
61
|
+
"fabric-button.js": "60c2b6d44571213e0237c29a94a4cf220f9de3efcfa697ac6a33777d1ad25fd6"
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Recipe: a service that is only a dashboard
|
|
2
|
+
|
|
3
|
+
A `fabric-service/0.1` service with a dashboard and nothing else: no capabilities, no `mcp` surface, no `usage`
|
|
4
|
+
surface, no provider key and no token of anybody else. Fabric Dashboards asked for it (FD-171, board thread
|
|
5
|
+
`38c35e11`) for a page that shows what the agents of this computer are doing; this recipe is the shape agreed there
|
|
6
|
+
(FAA-268). Built only on the service kit (`fabric_service.py`, `fabric-service.mjs`). Tests:
|
|
7
|
+
`test/test_recipe_dashboard_only.py` and `test/recipe-dashboard-only.test.mjs`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python3 recipes/dashboard-only/install.py --id example-dashboard --folder ~/.local/share/example-dashboard
|
|
13
|
+
node recipes/dashboard-only/install.mjs --id example-dashboard --folder ~/.local/share/example-dashboard
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Both twins take the same flags and write the same descriptor bytes (but `installedAt`). Optional flags: `--instance`
|
|
17
|
+
(default `default`), `--name`, `--repository` (the repository its code is built from; by default the folder's own
|
|
18
|
+
`https://` origin, and a new install without one is refused by the kit, FAA-198), `--port` (this port and no other),
|
|
19
|
+
`--label` with `--plist` (a launchd job, below), `--services-dir` (default `FABRIC_SERVICES_DIR`, else the OS's),
|
|
20
|
+
`--json`.
|
|
21
|
+
|
|
22
|
+
| Output | Rule |
|
|
23
|
+
|---|---|
|
|
24
|
+
| last stdout line | `{"descriptor": "<path>", "key": "<id>.<instance>", "port": <n>}`; `--json` prints only it |
|
|
25
|
+
| a repeat with the same `--id` | changes nothing and answers the same line; a descriptor of that key another installer wrote, or one with another `--folder`, is refused and left as it is |
|
|
26
|
+
| exit codes | 0 installed or already installed, 1 refused (the reason on stderr, never a value), 2 usage |
|
|
27
|
+
|
|
28
|
+
The folder gets the token file (`service.token`, 0600, never printed) and later the event log, the login state and
|
|
29
|
+
`snapshot.json`. A refused install writes nothing.
|
|
30
|
+
|
|
31
|
+
## The port
|
|
32
|
+
|
|
33
|
+
The installer takes the first port of 47300 to 47999 that no other descriptor of this account declares and that
|
|
34
|
+
`127.0.0.1` binds now, and writes the descriptor through the kit's `write_descriptor` / `writeDescriptor`, whose port
|
|
35
|
+
claim check refuses a port another descriptor declares; a port taken between the two is retried on the next one, up to
|
|
36
|
+
eight. That is the kit's port check as it stands, the older sense of a port claim
|
|
37
|
+
([lifecycle.md](../../references/lifecycle.md), *Install, upgrade, uninstall*): a port another OS account listens on
|
|
38
|
+
or claims still passes (FAA-225). The contract's HA-1 claim file per OS account is not written, because its schema
|
|
39
|
+
waits on the contract's CT-228; the recipe writes it the day the kit does, with no change to its flags or its output.
|
|
40
|
+
|
|
41
|
+
## Running it
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python3 recipes/dashboard-only/dashboard_service.py serve --descriptor <path the installer printed>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It serves `/.well-known/fabric-service` (surfaces `dashboard` and `events` only), the events feed and the login code
|
|
48
|
+
for the service token, the one-time login and the signed-in page. Under launchd, pass `--label` and `--plist` on the
|
|
49
|
+
first install and install the job with the kit's `launchd_plist` and `launchd_install`
|
|
50
|
+
([lifecycle.md](../../references/lifecycle.md)), whose `ProgramArguments` run the line above from a release
|
|
51
|
+
directory. `check_service.py <id>` passes every rule it can run on it (the test runs it).
|
|
52
|
+
|
|
53
|
+
## Where its data comes from
|
|
54
|
+
|
|
55
|
+
The page shows `<folder>/snapshot.json`, which the person's agent writes from what it reads through the Fabric
|
|
56
|
+
Dashboards MCP (`list_services`, `spend`, `connections`) and Project Observatory's tools. The service itself calls
|
|
57
|
+
nobody, so it holds no token but its own:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{"takenAt": "2026-11-12T08:00:00Z",
|
|
61
|
+
"sections": [{"title": "Services", "source": "fabric-dashboards list_services",
|
|
62
|
+
"columns": ["Service", "State"], "rows": [["example-agent.default", "ready"]]}]}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A snapshot that is missing, cannot be read, does not have this shape, is over 1 MB or is older than a day is
|
|
66
|
+
reported in `degraded` (source `snapshot`) and never shown as data. The well-known document declares the snapshot as
|
|
67
|
+
its one connection (`kind: store`), so a host draws where the page's numbers come from.
|