@passioncode-ai/fabric-agent-adapter 0.82.0 → 0.83.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -3
- package/README.md +6 -3
- package/SECURITY.md +4 -3
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +7 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +16 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +31 -15
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +37 -12
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +21 -148
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check_cli.py +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +15 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +3 -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 +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/README.md +76 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/config.example.json +28 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/data_agent.py +366 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +32 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support-agent.mjs +115 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +63 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/a2a.md +98 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +20 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +5 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +8 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/ops-digest.md +75 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +25 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +10 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +70 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_a2a.py +158 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +274 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +54 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +45 -31
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +16 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +26 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +18 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +6 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +21 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +3 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +20 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +24 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +455 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +17 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +18 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +55 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +180 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +5 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +468 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +404 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +16 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +97 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +35 -48
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +15 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +27 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +18 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +23 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +31 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +18 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +9 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +55 -96
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +98 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +15 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +43 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +647 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +16 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +20 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +93 -93
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +37 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +390 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +3 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +492 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +365 -53
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample-remote-service.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +16 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/tailnet-groups.schema.json +69 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +5 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +16 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +14 -9
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +4 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +2 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +162 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +14 -9
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +7 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +10 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +2 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +16 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +79 -8
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +147 -45
|
@@ -38,7 +38,7 @@ unset the walk is used). A committed symbolic link is checked by the path it nam
|
|
|
38
38
|
its first 8 KiB) is not read; it is listed. Globs in ROOT's `.fabric-source-ignore` (one per line, `#`
|
|
39
39
|
comments) and `--exclude` leave matching paths out. The owner's list is `--instance-strings`, else the file
|
|
40
40
|
`FABRIC_INSTANCE_STRINGS` names, else `passioncode-fabric/instance-strings.txt` in the user's configuration folder
|
|
41
|
-
(`$XDG_CONFIG_HOME` when it is absolute, else `~/.config`; `%APPDATA%` on Windows) when it exists: one string per
|
|
41
|
+
(`$XDG_CONFIG_HOME` when it is absolute, else `~/.config`; `%APPDATA%` when absolute on Windows) when it exists: one string per
|
|
42
42
|
line, a line starting with `#` a comment, `home: <name>` declaring the owner's home name (never neutral), strings
|
|
43
43
|
shorter than three characters ignored, matched without regard to case. A list inside ROOT
|
|
44
44
|
is refused: it would travel with the source. The summary says whether Git chose the files or every file was read.
|
|
@@ -95,6 +95,9 @@ def _refuse_sibling(step: str, missing: BaseException) -> NoReturn:
|
|
|
95
95
|
|
|
96
96
|
_WROTE_BYTECODE, sys.dont_write_bytecode = sys.dont_write_bytecode, True # a copy inside the checked tree adds no .pyc
|
|
97
97
|
_STEP = "fabric_secrets" # the sibling being loaded, so a refusal names it whatever the exception says
|
|
98
|
+
#: The public names of fabric_secrets the check and its command line read (FAA-190 (2), (12)), checked when it loads.
|
|
99
|
+
SECRETS_NAMES = ("BEARER_INSIDE", "JS_SPACE", "NOT_LINE_END", "SCHEME", "credential_name", "has_jwt", "jwt_spans",
|
|
100
|
+
"long_secret", "raw_values", "secret_value", "url_parts")
|
|
98
101
|
try:
|
|
99
102
|
import fabric_secrets as _secrets # noqa: E402 - the contract's credential detector (DEC-0044), beside this file
|
|
100
103
|
_STEP = "fabric_source_ignore"
|
|
@@ -105,12 +108,18 @@ try:
|
|
|
105
108
|
_STEP = "fabric_source_homes"
|
|
106
109
|
from fabric_source_homes import ( # noqa: E402,F401 - how a declared home is read, beside this file
|
|
107
110
|
HOME_ENTRY, _NAME_JOIN, _NAME_START, _SEP, _dashed_home, _name_parts, _owner_homes, _spellings)
|
|
111
|
+
_STEP = "fabric_source_check_cli"
|
|
112
|
+
from fabric_source_check_cli import ( # noqa: E402,F401 - the command line, beside this file (FAA-190 (104))
|
|
113
|
+
LIST_NAME, _Parser, _absolute, _list_path, _print, default_list, run_check)
|
|
114
|
+
_STEP = "fabric_secrets"
|
|
115
|
+
for _name in SECRETS_NAMES: # a detector from another version fails here, never at the first file read (FAA-190 (89))
|
|
116
|
+
if not hasattr(_secrets, _name):
|
|
117
|
+
raise ImportError(_name)
|
|
108
118
|
except Exception as _missing: # a copy without its siblings, or mixed from two versions: a refusal, never exit 1
|
|
109
119
|
_refuse_sibling(_STEP, _missing)
|
|
110
120
|
finally:
|
|
111
121
|
sys.dont_write_bytecode = _WROTE_BYTECODE
|
|
112
122
|
|
|
113
|
-
LIST_NAME = "instance-strings.txt"
|
|
114
123
|
#: The folders `pack` refuses to carry (fabric_package.JUNK); --every-file leaves them and the files below unread.
|
|
115
124
|
PACK_REFUSES = {".git", "__pycache__"}
|
|
116
125
|
WALK_SKIP = {".git", "node_modules", "__pycache__", ".venv", "venv", ".tox", ".mypy_cache", ".pytest_cache"}
|
|
@@ -145,12 +154,12 @@ SYNTHETIC = ("example", "not-a-key", "notakey", "fake", "dummy", "placeholder",
|
|
|
145
154
|
# This check's own key shapes, the detector's before DEC-0151 (unbounded, ASCII case-insensitive), apart from the shared
|
|
146
155
|
# SECRET_VALUE for parity with the contract's tree check (KEY_SHAPE_SOURCES, `src/source-rules.ts` at the pin; CT-207,
|
|
147
156
|
# FAA-258). Not the detector's `^bearer\s.+`, which reads a whole header value and would take prose that starts with the
|
|
148
|
-
# word: a bearer token is looked for as `
|
|
157
|
+
# word: a bearer token is looked for as `BEARER_INSIDE` does, the word, then a run of 12 or more with a digit.
|
|
149
158
|
KEY_SHAPE_SOURCES = ("sk-[A-Za-z0-9_-]{16,}", "gh[pousr]_[A-Za-z0-9]{20,}", "xox[abposr]-[A-Za-z0-9-]{10,}", "AKIA[0-9A-Z]{16}", "glpat-[A-Za-z0-9_-]{20,}")
|
|
150
159
|
PROVIDER_KEY = re.compile("(" + "|".join(KEY_SHAPE_SOURCES) + ")", re.IGNORECASE | re.ASCII)
|
|
151
160
|
#: A URL parameter's value read as the contract's tree check reads it (`URL_SECRET_VALUE` in `src/source-rules.ts`): the
|
|
152
161
|
#: detector's whole-value `^bearer` branch and this check's own unbounded shapes, never the shared DEC-0151 detector.
|
|
153
|
-
URL_SECRET_VALUE = re.compile("(^bearer[" + _secrets.JS_SPACE + "]" + _secrets.
|
|
162
|
+
URL_SECRET_VALUE = re.compile("(^bearer[" + _secrets.JS_SPACE + "]" + _secrets.NOT_LINE_END + "+|" + "|".join(KEY_SHAPE_SOURCES) + ")",
|
|
154
163
|
re.IGNORECASE | re.ASCII)
|
|
155
164
|
#: Files that are credentials by their name: pack's own rule (fabric_package.SECRET_FILE and SECRET_FILE_OK, held
|
|
156
165
|
#: equal by test_source_check), and the kit's token files (`service.token`, fabric_auth; `board.credential`, fabric_hub).
|
|
@@ -217,7 +226,7 @@ def _url_secret(url: str, depth: int = 0) -> bool:
|
|
|
217
226
|
So: a password of 8 or more mixing letters and digits, or a query or fragment value that, percent-decoded, is shaped
|
|
218
227
|
like a credential (a provider key, a JWT, a long random run under a credential's name)."""
|
|
219
228
|
url = url.rstrip(".,;)]}")
|
|
220
|
-
if not _secrets.
|
|
229
|
+
if not _secrets.SCHEME.match(url):
|
|
221
230
|
return False
|
|
222
231
|
password, query, fragment = _secrets.url_parts(url)
|
|
223
232
|
# what says it is synthetic is the value, never the host or the path (`examples.blob.core.windows.net`)
|
|
@@ -233,7 +242,7 @@ def _url_secret(url: str, depth: int = 0) -> bool:
|
|
|
233
242
|
return True
|
|
234
243
|
if not _real(value):
|
|
235
244
|
continue
|
|
236
|
-
if (_secrets.credential_name(name) and _secrets.
|
|
245
|
+
if (_secrets.credential_name(name) and _secrets.long_secret(value)) or \
|
|
237
246
|
_secrets.has_jwt(value) or URL_SECRET_VALUE.search(value):
|
|
238
247
|
return True
|
|
239
248
|
return False
|
|
@@ -293,13 +302,13 @@ def _secret_in(line: str) -> Optional[str]:
|
|
|
293
302
|
# with a segment ending in `sk` (`task-management-service`) would read as a key.
|
|
294
303
|
if any(True for _ in _provider_keys(line)):
|
|
295
304
|
return "a value shaped like a provider key"
|
|
296
|
-
if any(_real(m.group(0)) for m in _overlapping(_secrets.
|
|
305
|
+
if any(_real(m.group(0)) for m in _overlapping(_secrets.BEARER_INSIDE, line)):
|
|
297
306
|
return "a bearer token"
|
|
298
307
|
if "://" in line and any(_url_secret(m.group(0)) for m in URL.finditer(line)):
|
|
299
308
|
return "a URL that carries a credential"
|
|
300
309
|
for m in (_assigned(line) if ":" in line or "=" in line else ()):
|
|
301
310
|
name, value = m.group(1), m.group(2)
|
|
302
|
-
if _credential(name, value, _quoted(line, m)) and _secrets.
|
|
311
|
+
if _credential(name, value, _quoted(line, m)) and _secrets.long_secret(unquote(value)) and _real(unquote(value)):
|
|
303
312
|
return "a long random value assigned to a credential's name"
|
|
304
313
|
return None
|
|
305
314
|
|
|
@@ -489,10 +498,10 @@ def _raw_secret_spans(text: str) -> List[Tuple[int, int]]:
|
|
|
489
498
|
spans += [(a, b) for a, b in _secrets.jwt_spans(text) if _real(text[a:b])] # one pass (FAA-313)
|
|
490
499
|
spans += [m.span(1) for m in JWK_PRIVATE.finditer(text) if _real(m.group(1))]
|
|
491
500
|
spans += list(_provider_keys(text))
|
|
492
|
-
spans += [m.span() for m in _overlapping(_secrets.
|
|
501
|
+
spans += [m.span() for m in _overlapping(_secrets.BEARER_INSIDE, text) if _real(m.group(0))]
|
|
493
502
|
spans += [m.span() for m in URL.finditer(text) if _url_secret(m.group(0))]
|
|
494
503
|
spans += [m.span(2) for m in _assigned(text)
|
|
495
|
-
if _credential(m.group(1), m.group(2), _quoted(text, m)) and _secrets.
|
|
504
|
+
if _credential(m.group(1), m.group(2), _quoted(text, m)) and _secrets.long_secret(unquote(m.group(2)))
|
|
496
505
|
and _real(unquote(m.group(2)))]
|
|
497
506
|
return spans
|
|
498
507
|
|
|
@@ -814,145 +823,9 @@ def check(root: Path, entries: List[str], globs: List[str], every_file: bool = F
|
|
|
814
823
|
"dependencies": [shown(d) for d in dependencies]}
|
|
815
824
|
|
|
816
825
|
|
|
817
|
-
def _absolute(path: str, platform: str) -> bool:
|
|
818
|
-
return bool(re.match(r"[A-Za-z]:[\\/]|[\\/]{2}", path)) if platform == "win32" else path.startswith("/")
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
def default_list(platform: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> Path:
|
|
822
|
-
"""Where the owner's list is kept when none is named: the user's configuration folder, never a project's."""
|
|
823
|
-
platform = platform or sys.platform
|
|
824
|
-
env = os.environ if env is None else env
|
|
825
|
-
# a relative value is ignored, as the XDG spec says (a quoted `~/.config` is relative too): never "no list"
|
|
826
|
-
if platform == "win32":
|
|
827
|
-
appdata = env.get("APPDATA", "")
|
|
828
|
-
base = Path(appdata) if appdata and _absolute(appdata, platform) else Path.home() / "AppData" / "Roaming"
|
|
829
|
-
else:
|
|
830
|
-
xdg = env.get("XDG_CONFIG_HOME", "")
|
|
831
|
-
base = Path(xdg) if xdg.startswith("/") else Path.home() / ".config"
|
|
832
|
-
return base / "passioncode-fabric" / LIST_NAME
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
def _list_path(given: Optional[str]) -> Tuple[Optional[Path], bool]:
|
|
836
|
-
"""The owner's list and whether it must be read (a named or an existing default list; else no list)."""
|
|
837
|
-
named = given or os.environ.get("FABRIC_INSTANCE_STRINGS")
|
|
838
|
-
if named:
|
|
839
|
-
try:
|
|
840
|
-
return Path(named).expanduser(), True
|
|
841
|
-
except (RuntimeError, KeyError): # `~user` with no such user (KeyError on 3.9): never "no finding"
|
|
842
|
-
raise CheckError("the owner's list cannot be read")
|
|
843
|
-
try:
|
|
844
|
-
default = default_list()
|
|
845
|
-
except (RuntimeError, KeyError): # no HOME and no account entry (a container's arbitrary user)
|
|
846
|
-
raise CheckError("there is no home folder to find the owner's list in: set an absolute XDG_CONFIG_HOME (APPDATA on "
|
|
847
|
-
"Windows) or name the list with --instance-strings")
|
|
848
|
-
try:
|
|
849
|
-
os.stat(str(default))
|
|
850
|
-
except (FileNotFoundError, NotADirectoryError):
|
|
851
|
-
# a link to a list, its folder or the configuration folder on a volume not mounted: there, but not reachable
|
|
852
|
-
if any(os.path.lexists(str(p)) and not os.path.exists(str(p)) for p in (default, *default.parents)):
|
|
853
|
-
raise CheckError("the owner's list in your configuration folder cannot be read")
|
|
854
|
-
return None, False
|
|
855
|
-
except OSError: # a list that is there but cannot be reached is not "no list": that would fail open
|
|
856
|
-
raise CheckError("the owner's list cannot be read")
|
|
857
|
-
return default, True
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
class _Parser(argparse.ArgumentParser):
|
|
861
|
-
def error(self, message: str) -> NoReturn: # argparse would repeat the arguments it could not read
|
|
862
|
-
self.print_usage(sys.stderr)
|
|
863
|
-
self.exit(2, "fabric_source_check.py: the arguments cannot be read; see the usage above\n")
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
def _print(text: str) -> None:
|
|
867
|
-
"""Print a line the console may not encode (a name in another script, a byte Git kept as a surrogate)."""
|
|
868
|
-
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
|
|
869
|
-
print(text.encode(encoding, "backslashreplace").decode(encoding, "replace"))
|
|
870
|
-
|
|
871
|
-
|
|
872
826
|
def run(argv: Optional[List[str]] = None) -> int:
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
cmd = sub.add_parser("check", help="read the source tree and report instance data without repeating it")
|
|
876
|
-
cmd.add_argument("root", nargs="?", default=".")
|
|
877
|
-
cmd.add_argument("--instance-strings", metavar="FILE")
|
|
878
|
-
cmd.add_argument("--exclude", action="append", default=[], metavar="GLOB")
|
|
879
|
-
cmd.add_argument("--json", action="store_true")
|
|
880
|
-
cmd.add_argument("--every-file", action="store_true", help="read every file, not only what Git would commit")
|
|
881
|
-
try:
|
|
882
|
-
args = parser.parse_args(argv)
|
|
883
|
-
except SystemExit as exc: # --help, or arguments argparse refused: the exit code, as the command line gives it
|
|
884
|
-
return exc.code if isinstance(exc.code, int) else 2
|
|
885
|
-
try:
|
|
886
|
-
try:
|
|
887
|
-
root = Path(args.root).expanduser().resolve()
|
|
888
|
-
is_folder = root.is_dir()
|
|
889
|
-
except (OSError, RuntimeError, KeyError): # a parent it cannot search, a symlink loop, `~` with no home
|
|
890
|
-
raise CheckError("the folder cannot be read")
|
|
891
|
-
if not is_folder:
|
|
892
|
-
raise CheckError("ROOT is not a folder")
|
|
893
|
-
list_path, _ = _list_path(args.instance_strings) # may raise CheckError
|
|
894
|
-
entries: List[str] = []
|
|
895
|
-
if list_path is not None:
|
|
896
|
-
try:
|
|
897
|
-
resolved = list_path.resolve()
|
|
898
|
-
except (OSError, RuntimeError): # RuntimeError: Python 3.9's symlink loop
|
|
899
|
-
raise CheckError("the owner's list cannot be read")
|
|
900
|
-
if resolved == root or root in resolved.parents:
|
|
901
|
-
raise CheckError("the owner's list is inside the tree it checks, where it would travel with the source; "
|
|
902
|
-
"keep it in your configuration folder (passioncode-fabric/%s)" % LIST_NAME)
|
|
903
|
-
try:
|
|
904
|
-
present = resolved.is_file()
|
|
905
|
-
except OSError:
|
|
906
|
-
raise CheckError("the owner's list cannot be read")
|
|
907
|
-
if not present:
|
|
908
|
-
raise CheckError("the owner's list named by --instance-strings or FABRIC_INSTANCE_STRINGS does not exist or is "
|
|
909
|
-
"not a regular file"
|
|
910
|
-
if args.instance_strings or os.environ.get("FABRIC_INSTANCE_STRINGS")
|
|
911
|
-
else "the owner's list in your configuration folder is not a file")
|
|
912
|
-
try:
|
|
913
|
-
entries = read_list(resolved)
|
|
914
|
-
except UnicodeDecodeError:
|
|
915
|
-
raise CheckError("the owner's list is not UTF-8")
|
|
916
|
-
except OSError:
|
|
917
|
-
raise CheckError("the owner's list cannot be read")
|
|
918
|
-
result = check(root, entries, ignore_globs(root, args.exclude), args.every_file)
|
|
919
|
-
except CheckError as exc:
|
|
920
|
-
print("fabric_source_check.py: %s" % exc, file=sys.stderr)
|
|
921
|
-
return 2
|
|
922
|
-
if result.get("manifest"): # under fabric.source.json: its own report (fabric_source_tree.print_run)
|
|
923
|
-
return sys.modules["fabric_source_tree"].print_run(result, entries, list_path is not None, args.json, _print)
|
|
924
|
-
findings = result["findings"]
|
|
925
|
-
skipped = result["skipped"]
|
|
926
|
-
listed = sum(1 for e in entries if e and not e.startswith(HOME_ENTRY))
|
|
927
|
-
homes = sum(1 for e in entries if e.startswith(HOME_ENTRY))
|
|
928
|
-
# a binary file is not read either: it is complete only when every one is named as source in the ignore file
|
|
929
|
-
complete = not (skipped["unreadable"] or skipped["binary"] or skipped["nested"]) # type: ignore[index]
|
|
930
|
-
result.update({"instanceStrings": listed, "ownerHomes": homes, "instanceList": list_path is not None, "blocking": bool(findings),
|
|
931
|
-
"complete": complete})
|
|
932
|
-
if args.json:
|
|
933
|
-
_print(json.dumps(result, indent=2))
|
|
934
|
-
else:
|
|
935
|
-
for f in findings: # type: ignore[union-attr]
|
|
936
|
-
_print("%s:%s: %s (%s)" % (f["path"], f["line"], f["kind"], f["rule"]))
|
|
937
|
-
for n in result["notRead"]: # type: ignore[union-attr]
|
|
938
|
-
_print("%s: not read (%s)" % (n["path"], n["why"]))
|
|
939
|
-
for folder in result["dependencies"]: # type: ignore[union-attr]
|
|
940
|
-
_print("%s: not read (a dependency folder; --every-file reads it)" % folder)
|
|
941
|
-
if skipped["dependencies"]: # type: ignore[index]
|
|
942
|
-
_print("%d dependency folder(s) not read (node_modules, virtual environments, caches); --every-file reads "
|
|
943
|
-
"them" % skipped["dependencies"]) # type: ignore[index]
|
|
944
|
-
_print("%d file(s) read (%s), %d finding(s); not read: %d binary, %d unreadable, %d nested repository; "
|
|
945
|
-
"%d left out by name; %d instance string(s) listed%s%s"
|
|
946
|
-
% (result["files"], "what Git would commit" if result["mode"] == "git" else
|
|
947
|
-
"every file, as pack reads them" if args.every_file else "every file but dependency folders, Git not used",
|
|
948
|
-
len(findings), skipped["binary"], skipped["unreadable"], skipped["nested"], # type: ignore[index]
|
|
949
|
-
skipped["excluded"], listed, # type: ignore[index]
|
|
950
|
-
"" if listed or homes else (" (the owner's list holds no string of three or more characters)"
|
|
951
|
-
if list_path else " (no owner's list)"),
|
|
952
|
-
"; %d home name(s) declared" % homes if homes else ""))
|
|
953
|
-
if findings:
|
|
954
|
-
return 1
|
|
955
|
-
return 0 if complete else 2
|
|
827
|
+
"""The command line (fabric_source_check_cli.run_check), run with this module's check."""
|
|
828
|
+
return run_check(argv, check, read_list, ignore_globs, HOME_ENTRY)
|
|
956
829
|
|
|
957
830
|
|
|
958
831
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""The command line of fabric_source_check.py (FAA-190 (104)): where the owner's list is found, the arguments, and the
|
|
2
|
+
printed report. It sits beside fabric_source_check.py, which imports it inside its import guard and keeps the entry
|
|
3
|
+
point (`fabric_source_check.py check ...`, `fabric_source_check.run`); run the check by path, never this file.
|
|
4
|
+
Standard library only."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Callable, Dict, List, NoReturn, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from fabric_source_ignore import CheckError
|
|
16
|
+
|
|
17
|
+
LIST_NAME = "instance-strings.txt"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _absolute(path: str, platform: str) -> bool:
|
|
21
|
+
return bool(re.match(r"[A-Za-z]:[\\/]|[\\/]{2}", path)) if platform == "win32" else path.startswith("/")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def default_list(platform: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> Path:
|
|
25
|
+
"""Where the owner's list is kept when none is named: the user's configuration folder, never a project's."""
|
|
26
|
+
platform = platform or sys.platform
|
|
27
|
+
env = os.environ if env is None else env
|
|
28
|
+
# a relative value is ignored, as the XDG spec says (a quoted `~/.config` is relative too): never "no list"
|
|
29
|
+
if platform == "win32":
|
|
30
|
+
appdata = env.get("APPDATA", "")
|
|
31
|
+
base = Path(appdata) if appdata and _absolute(appdata, platform) else Path.home() / "AppData" / "Roaming"
|
|
32
|
+
else:
|
|
33
|
+
xdg = env.get("XDG_CONFIG_HOME", "")
|
|
34
|
+
base = Path(xdg) if xdg.startswith("/") else Path.home() / ".config"
|
|
35
|
+
return base / "passioncode-fabric" / LIST_NAME
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _list_path(given: Optional[str]) -> Tuple[Optional[Path], bool]:
|
|
39
|
+
"""The owner's list and whether it must be read (a named or an existing default list; else no list)."""
|
|
40
|
+
named = given or os.environ.get("FABRIC_INSTANCE_STRINGS")
|
|
41
|
+
if named:
|
|
42
|
+
try:
|
|
43
|
+
return Path(named).expanduser(), True
|
|
44
|
+
except (RuntimeError, KeyError): # `~user` with no such user (KeyError on 3.9): never "no finding"
|
|
45
|
+
raise CheckError("the owner's list cannot be read")
|
|
46
|
+
try:
|
|
47
|
+
default = default_list()
|
|
48
|
+
except (RuntimeError, KeyError): # no HOME and no account entry (a container's arbitrary user)
|
|
49
|
+
raise CheckError("there is no home folder to find the owner's list in: set an absolute XDG_CONFIG_HOME (APPDATA on "
|
|
50
|
+
"Windows) or name the list with --instance-strings")
|
|
51
|
+
try:
|
|
52
|
+
os.stat(str(default))
|
|
53
|
+
except (FileNotFoundError, NotADirectoryError):
|
|
54
|
+
# a link to a list, its folder or the configuration folder on a volume not mounted: there, but not reachable
|
|
55
|
+
if any(os.path.lexists(str(p)) and not os.path.exists(str(p)) for p in (default, *default.parents)):
|
|
56
|
+
raise CheckError("the owner's list in your configuration folder cannot be read")
|
|
57
|
+
return None, False
|
|
58
|
+
except OSError: # a list that is there but cannot be reached is not "no list": that would fail open
|
|
59
|
+
raise CheckError("the owner's list cannot be read")
|
|
60
|
+
return default, True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _Parser(argparse.ArgumentParser):
|
|
64
|
+
def error(self, message: str) -> NoReturn: # argparse would repeat the arguments it could not read
|
|
65
|
+
self.print_usage(sys.stderr)
|
|
66
|
+
self.exit(2, "fabric_source_check.py: the arguments cannot be read; see the usage above\n")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _print(text: str) -> None:
|
|
70
|
+
"""Print a line the console may not encode (a name in another script, a byte Git kept as a surrogate)."""
|
|
71
|
+
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
|
|
72
|
+
print(text.encode(encoding, "backslashreplace").decode(encoding, "replace"))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def run_check(argv: Optional[List[str]], check: Callable[..., Dict[str, object]], read_list: Callable[[Path], List[str]],
|
|
76
|
+
ignore_globs: Callable[[Path, List[str]], List[str]], home_entry: str) -> int:
|
|
77
|
+
"""The command line, run with the check's own functions (fabric_source_check.run passes them)."""
|
|
78
|
+
parser = _Parser(prog="fabric_source_check.py", description="Find instance data in an agent's source.")
|
|
79
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
80
|
+
cmd = sub.add_parser("check", help="read the source tree and report instance data without repeating it")
|
|
81
|
+
cmd.add_argument("root", nargs="?", default=".", help="the folder to check (default: the current folder)")
|
|
82
|
+
cmd.add_argument("--instance-strings", metavar="FILE", help="the owner's list (default: FABRIC_INSTANCE_STRINGS, else "
|
|
83
|
+
"passioncode-fabric/%s in the user's configuration folder)" % LIST_NAME)
|
|
84
|
+
cmd.add_argument("--exclude", action="append", default=[], metavar="GLOB",
|
|
85
|
+
help="leave matching paths out, as a .fabric-source-ignore line does (repeatable)")
|
|
86
|
+
cmd.add_argument("--json", action="store_true", help="one JSON object: mode, files, findings, notRead, skipped, "
|
|
87
|
+
"dependencies, instanceStrings, ownerHomes, instanceList, blocking, complete; exit 0 clean and "
|
|
88
|
+
"complete, 1 a finding, 2 a file not read or no run")
|
|
89
|
+
cmd.add_argument("--every-file", action="store_true", help="read every file, not only what Git would commit")
|
|
90
|
+
try:
|
|
91
|
+
args = parser.parse_args(argv)
|
|
92
|
+
except SystemExit as exc: # --help, or arguments argparse refused: the exit code, as the command line gives it
|
|
93
|
+
return exc.code if isinstance(exc.code, int) else 2
|
|
94
|
+
try:
|
|
95
|
+
try:
|
|
96
|
+
root = Path(args.root).expanduser().resolve()
|
|
97
|
+
is_folder = root.is_dir()
|
|
98
|
+
except (OSError, RuntimeError, KeyError): # a parent it cannot search, a symlink loop, `~` with no home
|
|
99
|
+
raise CheckError("the folder cannot be read")
|
|
100
|
+
if not is_folder:
|
|
101
|
+
raise CheckError("ROOT is not a folder")
|
|
102
|
+
list_path, _ = _list_path(args.instance_strings) # may raise CheckError
|
|
103
|
+
entries: List[str] = []
|
|
104
|
+
if list_path is not None:
|
|
105
|
+
try:
|
|
106
|
+
resolved = list_path.resolve()
|
|
107
|
+
except (OSError, RuntimeError): # RuntimeError: Python 3.9's symlink loop
|
|
108
|
+
raise CheckError("the owner's list cannot be read")
|
|
109
|
+
if resolved == root or root in resolved.parents:
|
|
110
|
+
raise CheckError("the owner's list is inside the tree it checks, where it would travel with the source; "
|
|
111
|
+
"keep it in your configuration folder (passioncode-fabric/%s)" % LIST_NAME)
|
|
112
|
+
try:
|
|
113
|
+
present = resolved.is_file()
|
|
114
|
+
except OSError:
|
|
115
|
+
raise CheckError("the owner's list cannot be read")
|
|
116
|
+
if not present:
|
|
117
|
+
raise CheckError("the owner's list named by --instance-strings or FABRIC_INSTANCE_STRINGS does not exist or is "
|
|
118
|
+
"not a regular file"
|
|
119
|
+
if args.instance_strings or os.environ.get("FABRIC_INSTANCE_STRINGS")
|
|
120
|
+
else "the owner's list in your configuration folder is not a file")
|
|
121
|
+
try:
|
|
122
|
+
entries = read_list(resolved)
|
|
123
|
+
except UnicodeDecodeError:
|
|
124
|
+
raise CheckError("the owner's list is not UTF-8")
|
|
125
|
+
except OSError:
|
|
126
|
+
raise CheckError("the owner's list cannot be read")
|
|
127
|
+
result = check(root, entries, ignore_globs(root, args.exclude), args.every_file)
|
|
128
|
+
except CheckError as exc:
|
|
129
|
+
print("fabric_source_check.py: %s" % exc, file=sys.stderr)
|
|
130
|
+
return 2
|
|
131
|
+
if result.get("manifest"): # under fabric.source.json: its own report (fabric_source_tree.print_run)
|
|
132
|
+
return sys.modules["fabric_source_tree"].print_run(result, entries, list_path is not None, args.json, _print)
|
|
133
|
+
findings = result["findings"]
|
|
134
|
+
skipped = result["skipped"]
|
|
135
|
+
listed = sum(1 for e in entries if e and not e.startswith(home_entry))
|
|
136
|
+
homes = sum(1 for e in entries if e.startswith(home_entry))
|
|
137
|
+
# a binary file is not read either: it is complete only when every one is named as source in the ignore file
|
|
138
|
+
complete = not (skipped["unreadable"] or skipped["binary"] or skipped["nested"]) # type: ignore[index]
|
|
139
|
+
result.update({"instanceStrings": listed, "ownerHomes": homes, "instanceList": list_path is not None, "blocking": bool(findings),
|
|
140
|
+
"complete": complete})
|
|
141
|
+
if args.json:
|
|
142
|
+
_print(json.dumps(result, indent=2))
|
|
143
|
+
else:
|
|
144
|
+
for f in findings: # type: ignore[union-attr]
|
|
145
|
+
_print("%s:%s: %s (%s)" % (f["path"], f["line"], f["kind"], f["rule"]))
|
|
146
|
+
for n in result["notRead"]: # type: ignore[union-attr]
|
|
147
|
+
_print("%s: not read (%s)" % (n["path"], n["why"]))
|
|
148
|
+
for folder in result["dependencies"]: # type: ignore[union-attr]
|
|
149
|
+
_print("%s: not read (a dependency folder; --every-file reads it)" % folder)
|
|
150
|
+
if skipped["dependencies"]: # type: ignore[index]
|
|
151
|
+
_print("%d dependency folder(s) not read (node_modules, virtual environments, caches); --every-file reads "
|
|
152
|
+
"them" % skipped["dependencies"]) # type: ignore[index]
|
|
153
|
+
_print("%d file(s) read (%s), %d finding(s); not read: %d binary, %d unreadable, %d nested repository; "
|
|
154
|
+
"%d left out by name; %d instance string(s) listed%s%s"
|
|
155
|
+
% (result["files"], "what Git would commit" if result["mode"] == "git" else
|
|
156
|
+
"every file, as pack reads them" if args.every_file else "every file but dependency folders, Git not used",
|
|
157
|
+
len(findings), skipped["binary"], skipped["unreadable"], skipped["nested"], # type: ignore[index]
|
|
158
|
+
skipped["excluded"], listed, # type: ignore[index]
|
|
159
|
+
"" if listed or homes else (" (the owner's list holds no string of three or more characters)"
|
|
160
|
+
if list_path else " (no owner's list)"),
|
|
161
|
+
"; %d home name(s) declared" % homes if homes else ""))
|
|
162
|
+
if findings:
|
|
163
|
+
return 1
|
|
164
|
+
return 0 if complete else 2
|
|
@@ -6,10 +6,10 @@ description: >-
|
|
|
6
6
|
for a provider manifest (adapting-projects-to-fabric), a new agent (creating-fabric-agents) or a session
|
|
7
7
|
(working-with-fabric).
|
|
8
8
|
license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
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.
|
|
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. On macOS 27.2 run kit services and the probe on Apple's /usr/bin/python3.
|
|
10
10
|
metadata:
|
|
11
11
|
author: PassionCode.ai
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.83.1"
|
|
13
13
|
contract-version: "0.1.0"
|
|
14
14
|
extension: "fabric-service/0.1"
|
|
15
15
|
extension-commit: "93e7db164c7829e2783b4b7705c4113bb5201131"
|
|
@@ -52,7 +52,8 @@ to review one. Do not use it for:
|
|
|
52
52
|
|
|
53
53
|
It is a service when at least one is true: other agents call it while the operator is
|
|
54
54
|
away; it runs jobs longer than one request; it owns a store other tools read; the
|
|
55
|
-
operator watches it on a page
|
|
55
|
+
operator watches it on a page; it is an uptime A agent that answers while the owner's computer is off (an online
|
|
56
|
+
service, the remote placement below). Otherwise build a CLI and stop here.
|
|
56
57
|
|
|
57
58
|
Answer before writing code, and record the answers in the project README:
|
|
58
59
|
|
|
@@ -73,7 +74,7 @@ Answer before writing code, and record the answers in the project README:
|
|
|
73
74
|
| Another agent on this machine | MCP `2026-07-28`, `streamable-http`, at `/mcp` on the service origin, token in a header | one process serves every session; `mcp-register` checks the listener (HA-2) first |
|
|
74
75
|
| The operator or a script | a CLI: `<tool> service status\|start\|stop\|restart`, `<tool> dashboard` (opens only in Fabric Dashboards, below), `<tool> doctor --json` | every verb delegates to launchd and the well-known document, never to its own process table |
|
|
75
76
|
| A remote agent | A2A `1.0` over HTTPS, or MCP behind an authenticated gateway | loopback reachability is not authorization |
|
|
76
|
-
| An A2A peer on this computer | `surfaces.a2a` names a public Agent Card, served without the token; its interface URLs are loopback (DEC-0122); the kits read it
|
|
77
|
+
| An A2A peer on this computer | `surfaces.a2a` names a public Agent Card, served without the token; its interface URLs are loopback (DEC-0122); the kits read it and serve one with the JSON-RPC binding ([the A2A reference](references/a2a.md)) | the card carries no credential, process member or required Fabric extension |
|
|
77
78
|
| The dashboard page | same-origin REST under `/api`, session cookie plus a custom request header; every button on it also a tool on the MCP surface (Step 5) | the existing dashboards' CSRF defence |
|
|
78
79
|
|
|
79
80
|
Read [the surfaces and auth reference](references/surfaces-and-auth.md) when wiring
|
|
@@ -105,6 +106,7 @@ When the service answers requests other Projects post to its Project on Fabric's
|
|
|
105
106
|
responder in its own process: [`scripts/fabric_hub.py`](scripts/fabric_hub.py) finds the hub and joins,
|
|
106
107
|
[`scripts/fabric_board.py`](scripts/fabric_board.py) enrolls, claims, accepts the exact digest, checkpoints before
|
|
107
108
|
every step and stops on `fenced`, `lease_expired` or `authority_changed` (Node: `fabric-hub.mjs`, `fabric-board.mjs`).
|
|
109
|
+
It reads an operator's mandate on a request only when it opts in with `agent_id=` and `mandate_from=` (Node: `agentId`, `mandateFrom`).
|
|
108
110
|
The request body is data behind the service's own input gate. A slot moved to another
|
|
109
111
|
session stays moved until `take_back` (`takeBack`) or a move back once Fabric offers one: give it an operator action with its tool and gate
|
|
110
112
|
`relayed` (the operator reaches it through an agent that relays their decision in `_meta`, the service's
|
|
@@ -172,7 +174,8 @@ spend ceiling before every attempt, receipts and a fallback). The draft-and-chec
|
|
|
172
174
|
support agent on the kit: [`recipes/support-agent/`](recipes/support-agent/README.md); the whole agent:
|
|
173
175
|
creating-fabric-agents' `references/agents-that-answer-people.md`. A service that is only a dashboard:
|
|
174
176
|
[`recipes/dashboard-only/`](recipes/dashboard-only/README.md); a door that hands a key on sealed:
|
|
175
|
-
[`recipes/key-delivery-door/`](recipes/key-delivery-door/README.md)
|
|
177
|
+
[`recipes/key-delivery-door/`](recipes/key-delivery-door/README.md); numbers computed by code from a read-only
|
|
178
|
+
source: [`recipes/data-agent/`](recipes/data-agent/README.md).
|
|
176
179
|
|
|
177
180
|
Startup order is the part that goes wrong; keep it exactly:
|
|
178
181
|
|
|
@@ -228,7 +231,7 @@ The installer, not the service, owns the plist and the descriptor. Sequence:
|
|
|
228
231
|
FileVault restart) install a LaunchDaemon instead: the plist in `/Library/LaunchDaemons` with `user_name`, as root
|
|
229
232
|
(the lifecycle reference, *The system domain*).
|
|
230
233
|
3. Uninstall: `launchd_uninstall` (waits for the unload, removes the plist, resets the
|
|
231
|
-
override; `purge=True` removes data only when the operator asks), `remove_descriptor`.
|
|
234
|
+
override; `purge=True` removes data only when the operator asks, and a LaunchDaemon refuses it), `remove_descriptor`.
|
|
232
235
|
4. After an upgrade, `prune_releases(releases_dir, current=…)` keeps the running release
|
|
233
236
|
and the one before it.
|
|
234
237
|
|
|
@@ -268,6 +271,9 @@ agents, models, connectors, stores and channels, each with its state, from
|
|
|
268
271
|
`connections_extension()` / `connectionsExtension()` in `fabric_connections`. Never a credential in it, never a call
|
|
269
272
|
count (that is the usage report); details: [the wire reference](references/protocol.md).
|
|
270
273
|
|
|
274
|
+
A service counts its own log into hourly and daily **operations digests** (DEC-0153) with `fabric_ops_digest`
|
|
275
|
+
(`fabric-ops-digest.mjs`): [the ops digest reference](references/ops-digest.md).
|
|
276
|
+
|
|
271
277
|
The events feed is a **view over the log you already keep** (a jobs table, a JSONL
|
|
272
278
|
journal), not a second store. Each event is one sentence a person reads; set
|
|
273
279
|
`notify: true` only when the operator must decide, or something failed or degrades their
|
|
@@ -355,7 +361,8 @@ Whoever owns the repository, an agent with a page gets the dashboard rule block
|
|
|
355
361
|
(below). org-index `scripts/check_format.py` reports 0 findings for it before it is called done. **An agent
|
|
356
362
|
someone builds for themselves is not a PassionCode.ai repository:** its licence is its owner's choice, the organization
|
|
357
363
|
publishes and lists nothing about it, of these files one built new needs only a `LICENSE` naming its copyright holder (copied kit files keep
|
|
358
|
-
theirs), and the verified MCP quick start teaches anyone to drive it.
|
|
364
|
+
theirs), and the verified MCP quick start teaches anyone to drive it. The kit's files stay AGPL-3.0-only or the
|
|
365
|
+
PassionCode commercial licence there, in an agent built for a client too.
|
|
359
366
|
|
|
360
367
|
## Online services: the remote placement
|
|
361
368
|
|
|
@@ -421,7 +428,7 @@ this estate needs.
|
|
|
421
428
|
explicitly in the plist; never copy the whole shell environment into it.
|
|
422
429
|
- A plist pointing at a worktree or checkout breaks when it moves: point it at a release.
|
|
423
430
|
- `http.server.HTTPServer` resolves its FQDN between `bind()` and `listen()`: with a slow
|
|
424
|
-
resolver (a macOS CI runner, a Mac offline) the port is bound but silent. Use `fs.LoopbackHTTPServer`.
|
|
431
|
+
resolver (a macOS CI runner, a Mac offline) the port is bound but silent: connects time out. Use `fs.LoopbackHTTPServer`.
|
|
425
432
|
- `logging.basicConfig` called twice: the second is a no-op and the log stays empty. Configure logging once, in
|
|
426
433
|
the entry point.
|
|
427
434
|
- Hand-made `.plist.bak` files in `~/Library/LaunchAgents` are never cleaned up; keep
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.8 · 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.8';
|
|
15
15
|
var COOKIE = 'fabric_button';
|
|
16
16
|
var GET_FABRIC = 'https://passioncode.ai/fabric';
|
|
17
17
|
var ABSENT_AFTER_MS = 1500;
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
// The words a person reads (FAA-119): English, or Russian on a page whose lang is ru; the Russian is written as
|
|
181
|
-
// escapes so the file reads the same whatever charset it is served with. Checked with
|
|
181
|
+
// escapes so the file reads the same whatever charset it is served with. Checked with the copy checker (en-US and ru-RU).
|
|
182
182
|
var LABELS = {
|
|
183
183
|
en: { chat: 'Chat with Fabric about {name}', session: 'Open a coding session in {name}', open: 'Open Fabric',
|
|
184
184
|
absent: 'Fabric is not installed. Get it at' },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.8 · 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.8 · 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.8';
|
|
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 */
|
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.8",
|
|
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": "0c58c2758e780f4f520d3ea45d69a7d4221ad97cdc7e6999da7c7511e6beb96c",
|
|
19
|
+
"fabric-dashboard.js": "dfaef6b91102817061f612aea1f4b5a85699a890d7f4fbda2780dc153f2e5da1",
|
|
20
|
+
"fabric-button.js": "11ebad0cc598b09f034666be377caf0edc0042e9e86d7d8e8292dda57789be7f",
|
|
21
21
|
"themes.json": "511839f1a1a83eb37fadfd132791474497d5c28c5091700b9aa31ad076e359b5"
|
|
22
22
|
},
|
|
23
23
|
"history": {
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
"fabric-dashboard.css": "549c1c1546bc9f8d2c1a579e7ad1035d009c94c3324b91d89d892afc437d5800",
|
|
65
65
|
"fabric-dashboard.js": "fa966a8f3b228391c2dae33674887a0d44b102cb58e42dbe6331f08bab57b391",
|
|
66
66
|
"fabric-button.js": "220c5c9719a9f6e35851d8748e2abb1d6606ffdb9fc43f3aff2b01f34637d925"
|
|
67
|
+
},
|
|
68
|
+
"1.1.8": {
|
|
69
|
+
"fabric-dashboard.css": "0c58c2758e780f4f520d3ea45d69a7d4221ad97cdc7e6999da7c7511e6beb96c",
|
|
70
|
+
"fabric-dashboard.js": "dfaef6b91102817061f612aea1f4b5a85699a890d7f4fbda2780dc153f2e5da1",
|
|
71
|
+
"fabric-button.js": "11ebad0cc598b09f034666be377caf0edc0042e9e86d7d8e8292dda57789be7f"
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
}
|