@passioncode-ai/fabric-agent-adapter 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +9 -3
  3. package/SECURITY.md +27 -6
  4. package/bin/fabric-agent-adapter.js +71 -6
  5. package/package.json +2 -2
  6. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +42 -66
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +4 -2
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +55 -0
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +9 -3
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/verification.md +15 -0
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +181 -26
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +66 -5
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +81 -23
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +1 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +28 -30
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +21 -9
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +47 -7
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +230 -29
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +21 -5
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +2 -1
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +4 -2
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +3 -2
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +2 -2
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +5 -0
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +3 -2
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +3 -1
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +5 -1
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +48 -19
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +66 -5
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +1 -1
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -1
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +53 -19
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +4 -2
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +145 -0
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +196 -188
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +5 -5
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +2 -2
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +6 -2
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +2 -1
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +1 -1
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +3 -1
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +48 -14
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +34 -1
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +144 -20
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +7 -3
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +12 -6
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +6 -6
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +17 -4
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +3 -3
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +162 -0
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +207 -227
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +4 -4
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +0 -1
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +52 -5
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +101 -45
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +12 -12
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +5 -5
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +13 -10
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +1 -1
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +6 -2
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +118 -71
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +272 -74
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +1 -1
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +1 -1
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +36 -11
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +3 -3
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +130 -35
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +25 -7
  73. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +29 -60
  74. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +4 -2
  75. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +55 -0
  76. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +66 -5
  77. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +3 -3
  78. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -1
  79. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +26 -13
  80. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +6 -0
  81. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +30 -22
  82. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +37 -6
  83. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +72 -13
  84. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +25 -7
  85. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +199 -43
  86. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +10 -1
  87. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/context.mjs +25 -2
  88. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/openclaw/index.mjs +4 -2
  89. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/opencode/fabric.mjs +2 -1
@@ -0,0 +1,162 @@
1
+ """The board link (DEC-0078, FAA-12b): the board session over the hub, the fsynced checkpoint and a claimed request as
2
+ data. Part of the board connector (FAA-65, layer L4), below the responder in fabric_board_session, which re-exports these
3
+ names. Standard library only, Python 3.9+."""
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import threading
8
+ from pathlib import Path
9
+ from typing import Any
10
+ from typing import Callable
11
+ from typing import Dict
12
+ from typing import Optional
13
+
14
+ import sys as _sys # noqa: E402
15
+ from pathlib import Path as _Path # noqa: E402
16
+ if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from any working directory (FAA-65)
17
+ _sys.path.insert(0, str(_Path(__file__).resolve().parent))
18
+ import fabric_hub as _hub
19
+ import fabric_service as _fs
20
+ from fabric_board_shapes import ( # noqa: E402 - the kit (FAA-65)
21
+ BoardError,
22
+ RESPOND_TOOLS,
23
+ refusal_problems,
24
+ )
25
+
26
+
27
+ class BoardSession:
28
+ """MCP over HTTP with a board session's one-session bearer: `open()` spends it with `initialize` and keeps the
29
+ `mcp-session-id`; `call(tool, args)` answers the tool's value or raises `BoardError`; `close()` sends the session's
30
+ DELETE, which releases every slot it holds (DEC-0073 §5). Thread-safe; the bearer never leaves the headers."""
31
+
32
+ def __init__(self, grant: _hub.BoardGrant, *, timeout: float = 15.0, post: Callable[..., Any] = _hub.post_mcp):
33
+ self.grant, self.timeout, self.post = grant, timeout, post
34
+ self.mcp_session: Optional[str] = None
35
+ self.protocol_version = _hub.MCP_PROTOCOL_VERSION
36
+ self._lock = threading.Lock()
37
+ self._rpc = 0
38
+
39
+ def _send(self, body: Dict[str, Any], method: str = "POST"):
40
+ try:
41
+ return self.post(self.grant.origin, body, self.grant.bearer(), session=self.mcp_session, timeout=self.timeout,
42
+ method=method, protocol_version=self.protocol_version if self.mcp_session else None)
43
+ except _hub.HubError as exc:
44
+ raise BoardError("unreachable", "Fabric's hub did not answer; a write may have been stored, so send it again with "
45
+ "the same idempotency key.", True) from exc
46
+
47
+ def open(self) -> None:
48
+ with self._lock:
49
+ self._rpc += 1
50
+ status, headers, parsed = self._send({"jsonrpc": "2.0", "id": self._rpc, "method": "initialize", "params": {
51
+ "protocolVersion": _hub.MCP_PROTOCOL_VERSION, "capabilities": {},
52
+ "clientInfo": {"name": "fabric-agent-adapter-board", "version": "1"}}})
53
+ if status == 401:
54
+ raise BoardError("session-ended", "Fabric refused the board session's bearer; join again.", True)
55
+ if status != 200 or not isinstance(parsed, dict) or not isinstance(parsed.get("result"), dict) or not headers.get("mcp-session-id"):
56
+ raise BoardError("unreachable", "Fabric did not open the board session (HTTP %d)." % status, True)
57
+ self.mcp_session = headers["mcp-session-id"]
58
+ version = parsed["result"].get("protocolVersion")
59
+ if isinstance(version, str):
60
+ self.protocol_version = version
61
+ self._send({"jsonrpc": "2.0", "method": "notifications/initialized"})
62
+
63
+ def call(self, tool: str, args: Dict[str, Any]) -> Dict[str, Any]:
64
+ if tool not in RESPOND_TOOLS:
65
+ raise BoardError("not-in-scope", "%s is not served to a board session; use a Project session for it." % tool, op=tool)
66
+ with self._lock:
67
+ if self.mcp_session is None:
68
+ raise BoardError("session-ended", "The board session is not open.", True, op=tool)
69
+ self._rpc += 1
70
+ status, _, parsed = self._send({"jsonrpc": "2.0", "id": self._rpc, "method": "tools/call",
71
+ "params": {"name": tool, "arguments": args}})
72
+ if status == 401:
73
+ raise BoardError("session-ended", "Fabric ended the board session (it left, was revoked or expired); join again.", True, op=tool)
74
+ try:
75
+ value = _hub.tool_answer(status, parsed, tool)
76
+ except _hub.HubError as exc:
77
+ raise BoardError("unreachable" if exc.retryable else "receipt-shape", exc.message, exc.retryable, op=tool) from None
78
+ if "error" in value:
79
+ problems = refusal_problems(value)
80
+ if problems:
81
+ raise BoardError("receipt-shape", "Fabric refused %s in a form the contract does not name." % tool, op=tool)
82
+ e = value["error"]
83
+ raise BoardError(e["code"], e["message"], e["retryable"], op=tool)
84
+ return value
85
+
86
+ def close(self) -> None:
87
+ with self._lock:
88
+ if self.mcp_session is None:
89
+ return
90
+ try:
91
+ self._send({}, method="DELETE")
92
+ except BoardError:
93
+ pass # best effort: the lease lapses in 60 s anyway
94
+ self.mcp_session = None
95
+
96
+
97
+ class Checkpoint:
98
+ """The responder's own journal: one JSON file (0600) holding every request it has claimed and not yet settled, each
99
+ with its fence and the idempotency key of the step in flight. Written atomically and fsynced BEFORE the step it
100
+ announces is sent; a write that fails raises `BoardError("checkpoint")` and the step is not taken."""
101
+
102
+ def __init__(self, path: Path):
103
+ self.path = Path(path)
104
+ self._lock = threading.Lock()
105
+ self.entries: Dict[str, Dict[str, Any]] = self._load()
106
+
107
+ def _load(self) -> Dict[str, Dict[str, Any]]:
108
+ try:
109
+ doc = json.loads(self.path.read_text(encoding="utf-8"))
110
+ except FileNotFoundError:
111
+ return {}
112
+ except (OSError, ValueError) as exc:
113
+ raise BoardError("checkpoint", "The board checkpoint %s cannot be read (%s); fix or move it before answering "
114
+ "requests." % (self.path, exc.__class__.__name__)) from None
115
+ entries = doc.get("requests") if isinstance(doc, dict) and doc.get("version") == 1 else None
116
+ if not isinstance(entries, dict):
117
+ raise BoardError("checkpoint", "The board checkpoint %s is not one this kit writes." % self.path)
118
+ return entries
119
+
120
+ def _write(self, entries: Dict[str, Dict[str, Any]]) -> None:
121
+ try:
122
+ _fs.ensure_private_dir(self.path.parent)
123
+ _fs.atomic_write(self.path, (json.dumps({"version": 1, "requests": entries}, indent=1, sort_keys=True) + "\n").encode())
124
+ except OSError as exc:
125
+ raise BoardError("checkpoint", "The board checkpoint could not be written (%s); nothing was sent." % (exc.strerror or exc.__class__.__name__)) from None
126
+
127
+ def put(self, entry: Dict[str, Any]) -> None:
128
+ with self._lock:
129
+ entries = dict(self.entries)
130
+ entries[entry["request"]] = json.loads(json.dumps(entry))
131
+ self._write(entries)
132
+ self.entries = entries
133
+
134
+ def drop(self, request_id: str) -> None:
135
+ with self._lock:
136
+ if request_id not in self.entries:
137
+ return
138
+ entries = dict(self.entries)
139
+ del entries[request_id]
140
+ self._write(entries)
141
+ self.entries = entries
142
+
143
+
144
+ class BoardRequest:
145
+ """A claimed request as data: never an instruction. `body` is the requester's text; `sender` its Project id and
146
+ principal (with provenance); `artifacts` its references, unverified."""
147
+
148
+ __slots__ = ("request_id", "message_id", "capability", "body", "body_format", "artifacts", "sender", "deadline", "thread")
149
+
150
+ def __init__(self, message: Dict[str, Any], capability: str):
151
+ req = message.get("request") or {}
152
+ body = message.get("body") or {}
153
+ self.request_id, self.message_id, self.capability = req.get("id"), message.get("id"), capability
154
+ self.body = body.get("text") if isinstance(body, dict) else None
155
+ self.body_format = body.get("format", "text/plain") if isinstance(body, dict) else "text/plain"
156
+ self.artifacts = list(message.get("artifacts") or [])
157
+ self.sender = message.get("sender") or {}
158
+ self.deadline = req.get("deadline")
159
+ self.thread = message.get("thread")
160
+
161
+ def __repr__(self) -> str: # never the body
162
+ return "BoardRequest(%r, %r)" % (self.request_id, self.capability)