@passioncode-ai/fabric-agent-adapter 0.39.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +203 -0
- package/README.md +18 -6
- package/SECURITY.md +27 -6
- package/bin/fabric-agent-adapter.js +71 -6
- package/package.json +2 -2
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +42 -66
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +4 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +55 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +9 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/verification.md +15 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +181 -26
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +66 -5
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +81 -23
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +42 -33
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +21 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +104 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +15 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/knowledge.example.json +13 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +465 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +21 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +93 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +5 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +129 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +5 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +48 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +66 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +11 -184
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +204 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +53 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +288 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +327 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +47 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +79 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +145 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +848 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +340 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +42 -1237
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +7 -18
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +41 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +15 -46
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +387 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +11 -68
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +52 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +231 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +50 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +235 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +136 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +335 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +146 -32
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +69 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -32
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +122 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +177 -2541
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +404 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +46 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +458 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +408 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +399 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +91 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +64 -1398
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +116 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +162 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +882 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +457 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +36 -39
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +336 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +113 -222
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +586 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +44 -405
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +140 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +402 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +119 -77
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +310 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_kit.py +243 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +308 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +202 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +431 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +285 -109
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +102 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +338 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +32 -39
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +173 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +298 -3450
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +8 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +437 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +120 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +697 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +1621 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +25 -7
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +29 -60
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +4 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +3 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +55 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +66 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +2 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +26 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +122 -1392
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +278 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +539 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +324 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +173 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +491 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +11 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/context.mjs +25 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/openclaw/index.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/opencode/fabric.mjs +2 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// A fixture conversation for the probe (check_service.py, rule board.responder): a scripted board a responder can run against without Fabric.
|
|
2
|
+
// Part of the Node board connector (FAA-65, layer L4); the twin of fabric_board_fixture.py. Node 20+, no dependency.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { Backoff, HubError, MCP_PROTOCOL_VERSION, normalProject, postMcp, toolAnswer } from './fabric-hub.mjs';
|
|
6
|
+
import { COMMS_PROTOCOL, RESPOND_TOOLS } from './fabric-board-shapes.mjs';
|
|
7
|
+
import { BoardSession, Responder } from './fabric-board-session.mjs';
|
|
8
|
+
|
|
9
|
+
const FIXTURE_PROJECT = '3f2a6b1c-0d4e-4f5a-8b6c-7d8e9f0a1b2c';
|
|
10
|
+
|
|
11
|
+
const FIXTURE_DIGEST = `sha256:${'a'.repeat(64)}`;
|
|
12
|
+
|
|
13
|
+
function fixtureAnswers(variant) {
|
|
14
|
+
const lease = '2026-10-13T10:02:00Z';
|
|
15
|
+
const status = { protocol: COMMS_PROTOCOL, board: { state: 'ready' }, project: FIXTURE_PROJECT, responders: [], transport: { telegram: 'off' }, unread: 0, idempotency: { epoch: 3 } };
|
|
16
|
+
if (variant === 'no-epoch') delete status.idempotency;
|
|
17
|
+
const request = { id: 'req_A1b2C3d4E5', state: 'claimed', effect: 'not_started', revision: 2 };
|
|
18
|
+
return {
|
|
19
|
+
'com.status': status,
|
|
20
|
+
'com.enroll': { ok: true, repeated: false, slot: { project: FIXTURE_PROJECT, capability: 'fixture.render', generation: 2, leaseExpiresAt: lease } },
|
|
21
|
+
'com.claim': { ok: true, repeated: false, message: 'msg_K9j8H7g6F5', request, attempt: { id: 'att_Zx9Cv8Bn7M', generation: 2, digest: FIXTURE_DIGEST, leaseExpiresAt: lease } },
|
|
22
|
+
'com.get': { id: 'msg_K9j8H7g6F5', thread: 'thr_8Hc2kQ9xA1', kind: 'request', at: '2026-10-13T10:00:00Z', digest: FIXTURE_DIGEST,
|
|
23
|
+
sender: { project: 'proj-requester', principal: { kind: 'agent', id: 's-1', provenance: 'trusted' } }, body: { text: 'Render the fixture.' },
|
|
24
|
+
request: { id: 'req_A1b2C3d4E5', target: FIXTURE_PROJECT, capability: 'fixture.render', state: 'claimed', effect: 'not_started', revision: 2 } },
|
|
25
|
+
'com.accept': variant === 'fenced' ? { error: { code: 'fenced', message: 'A newer responder holds this slot; this call was not committed.', retryable: false } }
|
|
26
|
+
: { ok: true, repeated: false, request: { ...request, state: 'accepted', revision: 3 } },
|
|
27
|
+
'com.complete': { ok: true, repeated: false, request: { ...request, state: 'completed', revision: 4 } },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One scripted conversation per path through this kit, with no Fabric; answers the problems found (empty: it passes). */
|
|
32
|
+
export async function selfCheck() {
|
|
33
|
+
const os = await import('node:os');
|
|
34
|
+
const problems = [];
|
|
35
|
+
for (const variant of ['whole', 'fenced', 'no-epoch']) {
|
|
36
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'fabric-board-fixture-'));
|
|
37
|
+
try {
|
|
38
|
+
const root = path.join(tmp, 'fabric');
|
|
39
|
+
fs.mkdirSync(root, { mode: 0o700 });
|
|
40
|
+
const door = path.join(root, 'hub-door-token');
|
|
41
|
+
fs.writeFileSync(door, 'd'.repeat(43), { mode: 0o600 });
|
|
42
|
+
fs.writeFileSync(path.join(root, 'hub.json'), JSON.stringify({ protocol: 'fabric-hub/0.1', origin: 'http://127.0.0.1:47070', mcp: '/mcp', doorTokenFile: door, pid: process.pid, startedAt: '2026-10-13T10:00:00Z' }));
|
|
43
|
+
const cred = path.join(tmp, 'credential');
|
|
44
|
+
fs.writeFileSync(cred, 'c'.repeat(43), { mode: 0o600 });
|
|
45
|
+
const answers = fixtureAnswers(variant);
|
|
46
|
+
const calls = [];
|
|
47
|
+
const handled = [];
|
|
48
|
+
const post = async (origin, body, bearer, { method = 'POST' } = {}) => {
|
|
49
|
+
if (method === 'DELETE') return { status: 200, headers: {}, body: null };
|
|
50
|
+
if (body.method === 'initialize') return { status: 200, headers: { 'mcp-session-id': 'fixture-session' }, body: { jsonrpc: '2.0', id: body.id, result: { protocolVersion: MCP_PROTOCOL_VERSION } } };
|
|
51
|
+
if (body.method !== 'tools/call') return { status: 202, headers: {}, body: null };
|
|
52
|
+
const tool = body.params.name;
|
|
53
|
+
calls.push([tool, body.params.arguments]);
|
|
54
|
+
if (tool === 'fabric.board.join') return { status: 200, headers: {}, body: { jsonrpc: '2.0', id: body.id, result: { structuredContent: { session: { bearer: 'b'.repeat(43), sessionId: 's-fixture', projectId: FIXTURE_PROJECT, capabilities: ['fixture.render'] } } } } };
|
|
55
|
+
const value = answers[tool] ?? { error: { code: 'unsupported_capability', message: 'not in the fixture', retryable: false } };
|
|
56
|
+
return { status: 200, headers: {}, body: { jsonrpc: '2.0', id: body.id, result: { content: [{ type: 'text', text: JSON.stringify(value) }] } } };
|
|
57
|
+
};
|
|
58
|
+
const { HubClient } = await import('./fabric-hub.mjs');
|
|
59
|
+
const responder = new Responder({ hub: new HubClient(root, { post }), projectId: FIXTURE_PROJECT, capabilities: ['fixture.render'], credentialFile: cred,
|
|
60
|
+
checkpointFile: path.join(tmp, 'checkpoint.json'), handler: (request) => { handled.push(request.body); return { state: 'completed', summary: 'Rendered.' }; },
|
|
61
|
+
admit: () => null, now: () => 0, rand: () => 0.5, sessionFactory: (grant) => new BoardSession(grant, { post }) });
|
|
62
|
+
await responder.step();
|
|
63
|
+
const tools = calls.map(([t]) => t);
|
|
64
|
+
if (tools.some((t) => !RESPOND_TOOLS.has(t) && t !== 'fabric.board.join')) problems.push(`${variant}: a tool outside the respond scope was sent`);
|
|
65
|
+
if (variant === 'whole') {
|
|
66
|
+
if (tools.join(',') !== 'fabric.board.join,com.status,com.enroll,com.claim,com.get,com.accept,com.complete') problems.push(`whole: the calls were ${tools.join(', ')}`);
|
|
67
|
+
const accept = calls.find(([t]) => t === 'com.accept')?.[1]?.fence ?? {};
|
|
68
|
+
if (accept.digest !== FIXTURE_DIGEST || accept.generation !== 2 || accept.attempt !== 'att_Zx9Cv8Bn7M') problems.push('whole: com.accept did not carry the claim\'s exact fence');
|
|
69
|
+
if (handled.join('|') !== 'Render the fixture.') problems.push('whole: the handler did not see the request once');
|
|
70
|
+
if (Object.keys(responder.checkpoint.entries).length) problems.push('whole: the checkpoint kept a settled request');
|
|
71
|
+
} else if (variant === 'fenced') {
|
|
72
|
+
if (handled.length || tools.includes('com.complete')) problems.push('fenced: the kit kept acting after com.accept answered fenced');
|
|
73
|
+
} else if (tools.includes('com.enroll') || responder.state !== 'down') problems.push('no-epoch: the kit did not fail closed without an idempotency epoch');
|
|
74
|
+
} finally {
|
|
75
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return problems;
|
|
79
|
+
}
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// The board link (DEC-0078, FAA-12b): the board session over the hub, the fsynced checkpoint and a claimed request as data.
|
|
2
|
+
// Part of the Node board connector (FAA-65, layer L4), below the responder in fabric-board-session.mjs, which re-exports
|
|
3
|
+
// these names; the twin of fabric_board_link.py. Node 18+, no dependency.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { HubError, MCP_PROTOCOL_VERSION, postMcp, toolAnswer } from './fabric-hub.mjs';
|
|
7
|
+
import { atomicWrite, ensurePrivateDir } from './fabric-service.mjs';
|
|
8
|
+
import { BoardError, RESPOND_TOOLS, isObj, refusalProblems } from './fabric-board-shapes.mjs';
|
|
9
|
+
|
|
10
|
+
/** MCP over HTTP with a board session's one-session bearer; calls are serialised; close() sends the session's DELETE. */
|
|
11
|
+
export class BoardSession {
|
|
12
|
+
constructor(grant, { timeout = 15000, post = postMcp } = {}) {
|
|
13
|
+
Object.assign(this, { grant, timeout, post });
|
|
14
|
+
this.mcpSession = null;
|
|
15
|
+
this.protocolVersion = MCP_PROTOCOL_VERSION;
|
|
16
|
+
this.rpc = 0;
|
|
17
|
+
this.queue = Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#serial(fn) {
|
|
21
|
+
const run = this.queue.then(fn, fn);
|
|
22
|
+
this.queue = run.catch(() => {});
|
|
23
|
+
return run;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async #send(body, method = 'POST') {
|
|
27
|
+
try {
|
|
28
|
+
return await this.post(this.grant.origin, body, this.grant.bearer(), { session: this.mcpSession, timeout: this.timeout, method, protocolVersion: this.mcpSession ? this.protocolVersion : undefined });
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (e instanceof HubError) throw new BoardError('unreachable', 'Fabric\'s hub did not answer; a write may have been stored, so send it again with the same idempotency key.', true);
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
open() {
|
|
36
|
+
return this.#serial(async () => {
|
|
37
|
+
this.rpc += 1;
|
|
38
|
+
const answer = await this.#send({ jsonrpc: '2.0', id: this.rpc, method: 'initialize', params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'fabric-agent-adapter-board', version: '1' } } });
|
|
39
|
+
if (answer.status === 401) throw new BoardError('session-ended', 'Fabric refused the board session\'s bearer; join again.', true);
|
|
40
|
+
const sid = answer.headers['mcp-session-id'];
|
|
41
|
+
if (answer.status !== 200 || !isObj(answer.body) || !isObj(answer.body.result) || !sid) throw new BoardError('unreachable', `Fabric did not open the board session (HTTP ${answer.status}).`, true);
|
|
42
|
+
this.mcpSession = String(sid);
|
|
43
|
+
if (typeof answer.body.result.protocolVersion === 'string') this.protocolVersion = answer.body.result.protocolVersion;
|
|
44
|
+
await this.#send({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
call(tool, args) {
|
|
49
|
+
if (!RESPOND_TOOLS.has(tool)) return Promise.reject(new BoardError('not-in-scope', `${tool} is not served to a board session; use a Project session for it.`, false, tool));
|
|
50
|
+
return this.#serial(async () => {
|
|
51
|
+
if (!this.mcpSession) throw new BoardError('session-ended', 'The board session is not open.', true, tool);
|
|
52
|
+
this.rpc += 1;
|
|
53
|
+
const answer = await this.#send({ jsonrpc: '2.0', id: this.rpc, method: 'tools/call', params: { name: tool, arguments: args } });
|
|
54
|
+
if (answer.status === 401) throw new BoardError('session-ended', 'Fabric ended the board session (it left, was revoked or expired); join again.', true, tool);
|
|
55
|
+
let value;
|
|
56
|
+
try {
|
|
57
|
+
value = toolAnswer(answer, tool);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
throw new BoardError(e.retryable ? 'unreachable' : 'receipt-shape', e.message, e.retryable, tool);
|
|
60
|
+
}
|
|
61
|
+
if ('error' in value) {
|
|
62
|
+
if (refusalProblems(value).length) throw new BoardError('receipt-shape', `Fabric refused ${tool} in a form the contract does not name.`, false, tool);
|
|
63
|
+
throw new BoardError(value.error.code, value.error.message, value.error.retryable, tool);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
close() {
|
|
70
|
+
return this.#serial(async () => {
|
|
71
|
+
if (!this.mcpSession) return;
|
|
72
|
+
try {
|
|
73
|
+
await this.#send({}, 'DELETE');
|
|
74
|
+
} catch {
|
|
75
|
+
// best effort: the lease lapses in 60 s anyway
|
|
76
|
+
}
|
|
77
|
+
this.mcpSession = null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The responder's journal (0600 JSON, the same file fabric_board.py writes), fsynced before the step it announces. */
|
|
83
|
+
export class Checkpoint {
|
|
84
|
+
constructor(file) {
|
|
85
|
+
this.file = file;
|
|
86
|
+
this.entries = this.#load();
|
|
87
|
+
}
|
|
88
|
+
#load() {
|
|
89
|
+
let text;
|
|
90
|
+
try {
|
|
91
|
+
text = fs.readFileSync(this.file, 'utf8');
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (e.code === 'ENOENT') return {};
|
|
94
|
+
throw new BoardError('checkpoint', `The board checkpoint ${this.file} cannot be read (${e.code}); fix or move it before answering requests.`);
|
|
95
|
+
}
|
|
96
|
+
let doc;
|
|
97
|
+
try {
|
|
98
|
+
doc = JSON.parse(text);
|
|
99
|
+
} catch {
|
|
100
|
+
doc = null;
|
|
101
|
+
}
|
|
102
|
+
if (!isObj(doc) || doc.version !== 1 || !isObj(doc.requests)) throw new BoardError('checkpoint', `The board checkpoint ${this.file} is not one this kit writes.`);
|
|
103
|
+
return doc.requests;
|
|
104
|
+
}
|
|
105
|
+
#write(entries) {
|
|
106
|
+
try {
|
|
107
|
+
ensurePrivateDir(path.dirname(this.file));
|
|
108
|
+
const sorted = Object.fromEntries(Object.keys(entries).sort().map((k) => [k, entries[k]]));
|
|
109
|
+
atomicWrite(this.file, `${JSON.stringify({ requests: sorted, version: 1 }, null, 1)}\n`, 0o600);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
throw new BoardError('checkpoint', `The board checkpoint could not be written (${e.code ?? e.message}); nothing was sent.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
put(entry) {
|
|
115
|
+
const entries = { ...this.entries, [entry.request]: JSON.parse(JSON.stringify(entry)) };
|
|
116
|
+
this.#write(entries);
|
|
117
|
+
this.entries = entries;
|
|
118
|
+
}
|
|
119
|
+
drop(requestId) {
|
|
120
|
+
if (!(requestId in this.entries)) return;
|
|
121
|
+
const { [requestId]: _gone, ...entries } = this.entries;
|
|
122
|
+
this.#write(entries);
|
|
123
|
+
this.entries = entries;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A claimed request as data: never an instruction. */
|
|
128
|
+
export class BoardRequest {
|
|
129
|
+
constructor(message, capability) {
|
|
130
|
+
const req = isObj(message.request) ? message.request : {};
|
|
131
|
+
const body = isObj(message.body) ? message.body : {};
|
|
132
|
+
this.requestId = req.id;
|
|
133
|
+
this.messageId = message.id;
|
|
134
|
+
this.capability = capability;
|
|
135
|
+
this.body = typeof body.text === 'string' ? body.text : null;
|
|
136
|
+
this.bodyFormat = body.format ?? 'text/plain';
|
|
137
|
+
this.artifacts = Array.isArray(message.artifacts) ? [...message.artifacts] : [];
|
|
138
|
+
this.sender = isObj(message.sender) ? message.sender : {};
|
|
139
|
+
this.deadline = req.deadline;
|
|
140
|
+
this.thread = message.thread;
|
|
141
|
+
}
|
|
142
|
+
toJSON() {
|
|
143
|
+
return { requestId: this.requestId, capability: this.capability }; // never the body
|
|
144
|
+
}
|
|
145
|
+
}
|