@prismer/runtime 1.9.0-hotfix.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.
Files changed (39) hide show
  1. package/README.md +288 -0
  2. package/dist/adapter-registry-EMZFKFYK.mjs +8 -0
  3. package/dist/adapter-registry-EMZFKFYK.mjs.map +1 -0
  4. package/dist/artifacts-uploader-N2HNBSTW.mjs +159 -0
  5. package/dist/artifacts-uploader-N2HNBSTW.mjs.map +1 -0
  6. package/dist/auto-register-M4YSPJS6.mjs +204 -0
  7. package/dist/auto-register-M4YSPJS6.mjs.map +1 -0
  8. package/dist/bin/prismer.js +14988 -0
  9. package/dist/chunk-BJTO5JO5.mjs +11 -0
  10. package/dist/chunk-BJTO5JO5.mjs.map +1 -0
  11. package/dist/chunk-JIEDFDVI.mjs +148 -0
  12. package/dist/chunk-JIEDFDVI.mjs.map +1 -0
  13. package/dist/chunk-NDNX2G6O.mjs +68 -0
  14. package/dist/chunk-NDNX2G6O.mjs.map +1 -0
  15. package/dist/chunk-POWV475F.mjs +45 -0
  16. package/dist/chunk-POWV475F.mjs.map +1 -0
  17. package/dist/chunk-VTFKZAUY.mjs +68 -0
  18. package/dist/chunk-VTFKZAUY.mjs.map +1 -0
  19. package/dist/dispatch-mux-MW4HHICS.mjs +8 -0
  20. package/dist/dispatch-mux-MW4HHICS.mjs.map +1 -0
  21. package/dist/dispatch-rpc-J7H6KC2V.mjs +78 -0
  22. package/dist/dispatch-rpc-J7H6KC2V.mjs.map +1 -0
  23. package/dist/fs-rpc-OSHJZYK6.mjs +58 -0
  24. package/dist/fs-rpc-OSHJZYK6.mjs.map +1 -0
  25. package/dist/heartbeat-loop-H2LAI3V5.mjs +95 -0
  26. package/dist/heartbeat-loop-H2LAI3V5.mjs.map +1 -0
  27. package/dist/icon +21 -0
  28. package/dist/index.d.mts +2063 -0
  29. package/dist/index.d.ts +2063 -0
  30. package/dist/index.js +10013 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/index.mjs +8807 -0
  33. package/dist/index.mjs.map +1 -0
  34. package/dist/mode-b-DXJ7FJAL.mjs +152 -0
  35. package/dist/mode-b-DXJ7FJAL.mjs.map +1 -0
  36. package/dist/registry-ZYU2HDFL.mjs +12 -0
  37. package/dist/registry-ZYU2HDFL.mjs.map +1 -0
  38. package/dist/smallicon +6 -0
  39. package/package.json +72 -0
@@ -0,0 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
11
+ //# sourceMappingURL=chunk-BJTO5JO5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,148 @@
1
+ // src/agents/registry.ts
2
+ import { execFile } from "child_process";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { promisify } from "util";
6
+ var execFileAsync = promisify(execFile);
7
+ var AGENT_SOURCE_ROOT = process.env["PRISMER_AGENT_SOURCE_ROOT"] ?? path.join(os.homedir(), "workspace", "agent");
8
+ function sourcePath(name) {
9
+ return path.join(AGENT_SOURCE_ROOT, name);
10
+ }
11
+ function parseSemver(v) {
12
+ const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
13
+ if (!m) return null;
14
+ return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
15
+ }
16
+ function satisfiesRange(version, range) {
17
+ if (!range) return true;
18
+ const m = range.match(/^>=\s*(.+)$/);
19
+ if (!m) return true;
20
+ const minParsed = parseSemver(m[1]);
21
+ const vParsed = parseSemver(version);
22
+ if (!minParsed || !vParsed) return true;
23
+ const [maj1, min1, pat1] = minParsed;
24
+ const [maj2, min2, pat2] = vParsed;
25
+ if (maj2 !== maj1) return maj2 > maj1;
26
+ if (min2 !== min1) return min2 > min1;
27
+ return pat2 >= pat1;
28
+ }
29
+ function makeDetect(binary, versionFlag = "--version", parseVersion) {
30
+ return async () => {
31
+ let binaryPath;
32
+ try {
33
+ const { stdout } = await execFileAsync("which", [binary]);
34
+ binaryPath = stdout.trim();
35
+ } catch {
36
+ return { found: false };
37
+ }
38
+ if (!binaryPath) return { found: false };
39
+ try {
40
+ const { stdout, stderr } = await execFileAsync(binaryPath, [versionFlag]);
41
+ const raw = (stdout + stderr).trim();
42
+ const version = parseVersion ? parseVersion(raw) : extractVersion(raw);
43
+ return { found: true, binaryPath, version };
44
+ } catch {
45
+ return { found: true, binaryPath, version: void 0 };
46
+ }
47
+ };
48
+ }
49
+ function extractVersion(output) {
50
+ const m = output.match(/(\d+\.\d+\.\d+)/);
51
+ return m ? m[1] : void 0;
52
+ }
53
+ var AGENT_CATALOG = [
54
+ {
55
+ name: "claude-code",
56
+ displayName: "Claude Code",
57
+ packPackage: "@prismer/claude-code-plugin",
58
+ packVersionRange: "^1.9.0",
59
+ hookConfigPath: "~/.claude/hooks.json",
60
+ mcpConfigPath: "~/.claude/mcp_servers.json",
61
+ upstreamBinary: "claude",
62
+ upstreamVersionRange: ">=2.0.0",
63
+ localSourcePath: sourcePath("claude-code"),
64
+ installCommand: "curl -fsSL https://claude.ai/install.sh | bash",
65
+ tiersSupported: [1, 2, 3, 4, 5, 6, 7],
66
+ capabilityTags: ["code", "shell", "mcp", "approval", "skill", "fs"],
67
+ detect: makeDetect("claude", "--version")
68
+ },
69
+ {
70
+ name: "codex",
71
+ displayName: "Codex",
72
+ packPackage: "@prismer/codex-plugin",
73
+ packVersionRange: "^1.9.0",
74
+ hookConfigPath: "~/.codex/hooks.json",
75
+ mcpConfigPath: void 0,
76
+ upstreamBinary: "codex",
77
+ upstreamVersionRange: void 0,
78
+ localSourcePath: sourcePath("codex"),
79
+ installCommand: "npm install -g @openai/codex",
80
+ tiersSupported: [1, 2, 3, 4, 5, 6],
81
+ capabilityTags: ["code", "shell", "mcp"],
82
+ detect: makeDetect("codex", "--version")
83
+ },
84
+ {
85
+ name: "hermes",
86
+ displayName: "Hermes",
87
+ packPackage: "@prismer/hermes-plugin",
88
+ packVersionRange: "^1.9.0",
89
+ hookConfigPath: "~/.hermes/hooks.json",
90
+ mcpConfigPath: void 0,
91
+ upstreamBinary: "hermes",
92
+ // NousResearch/hermes-agent upstream is currently 0.10.x (as of 2026-04).
93
+ // Previously pinned `>=1.0.0` — rejected every current release so every
94
+ // fresh install hit "Incompatible version: hermes 0.10.0 does not satisfy
95
+ // >=1.0.0" and fell back to skipping Hermes entirely. G-14 closed 2026-04-22.
96
+ // The hook-plugin API we target (ctx.register_hook + **kwargs) has been
97
+ // stable since 0.10.0 per real integration verification in v0.1.1-0.2.0
98
+ // of prismer-adapter-hermes.
99
+ upstreamVersionRange: ">=0.10.0",
100
+ localSourcePath: sourcePath("hermes-agent"),
101
+ // v1.9.0 B.3: public installer curl path instead of the dev-machine
102
+ // `cd ~/workspace/agent/hermes-agent && uv pip install -e ".[cli]"` form —
103
+ // Docker / CI hosts do not have that worktree. --skip-setup keeps the
104
+ // install non-interactive so the outer agent-install wizard can follow up
105
+ // with config.yaml / .env writes under its own sequence.
106
+ installCommand: "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup",
107
+ tiersSupported: [1, 2, 3, 4],
108
+ capabilityTags: ["code", "shell", "mcp"],
109
+ detect: makeDetect("hermes", "--version")
110
+ },
111
+ {
112
+ name: "openclaw",
113
+ displayName: "OpenClaw",
114
+ packPackage: "@prismer/openclaw-channel",
115
+ packVersionRange: "^1.9.0",
116
+ // NOTE: OpenClaw does NOT read `~/.openclaw/hooks.json` — its hook
117
+ // discovery scans `~/.openclaw/hooks/<name>/HOOK.md` directories and the
118
+ // plugin registry (see openclaw docs/automation/hooks.md). PARA events
119
+ // during `openclaw agent --local` are actually fired by the
120
+ // `@prismer/openclaw-channel` package itself via the native
121
+ // `api.registerHook(...)` + `api.on(...)` plugin APIs (see
122
+ // `sdk/prismer-cloud/openclaw-channel/src/para/register.ts`). We keep
123
+ // this path here because install-agent's smoke-test step reads it to
124
+ // verify the adapter is in place; removing it would break that check.
125
+ // v1.9.0 report break #5 fix.
126
+ hookConfigPath: "~/.openclaw/hooks.json",
127
+ mcpConfigPath: void 0,
128
+ upstreamBinary: "openclaw",
129
+ upstreamVersionRange: void 0,
130
+ localSourcePath: sourcePath("openclaw"),
131
+ // v1.9.0 B.3: replaced the monorepo-relative pnpm build with the upstream
132
+ // npm release, matching how claude-code and codex pull from public sources.
133
+ installCommand: "npm install -g openclaw@latest",
134
+ tiersSupported: [1, 2, 3, 4, 5, 6],
135
+ capabilityTags: ["code", "shell", "mcp", "fs"],
136
+ detect: makeDetect("openclaw", "--version")
137
+ }
138
+ ];
139
+ function getAgent(name) {
140
+ return AGENT_CATALOG.find((e) => e.name === name);
141
+ }
142
+
143
+ export {
144
+ satisfiesRange,
145
+ AGENT_CATALOG,
146
+ getAgent
147
+ };
148
+ //# sourceMappingURL=chunk-JIEDFDVI.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agents/registry.ts"],"sourcesContent":["// T13 — Agent catalog: static registry of known agents Prismer can install adapters for.\n\nimport { execFile } from 'node:child_process';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\nconst AGENT_SOURCE_ROOT = process.env['PRISMER_AGENT_SOURCE_ROOT'] ?? path.join(os.homedir(), 'workspace', 'agent');\n\nfunction sourcePath(name: string): string {\n return path.join(AGENT_SOURCE_ROOT, name);\n}\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface AgentCatalogEntry {\n name: string;\n displayName: string;\n packPackage: string;\n packVersionRange: string;\n hookConfigPath: string; // raw path with ~ (expanded by callers via os.homedir())\n mcpConfigPath?: string;\n upstreamBinary: string;\n upstreamVersionRange?: string;\n localSourcePath?: string;\n installCommand?: string;\n tiersSupported: number[]; // PARA tiers this agent supports (L1-L10)\n capabilityTags: string[]; // PARA capability tags (code, shell, mcp, approval, etc.)\n detect: () => Promise<{ found: boolean; binaryPath?: string; version?: string }>;\n}\n\n// ============================================================\n// Tiny internal semver comparator\n// ============================================================\n// Supports only >=X.Y.Z (no upper bound) and undefined (skip).\n\nfunction parseSemver(v: string): [number, number, number] | null {\n const m = v.match(/^(\\d+)\\.(\\d+)\\.(\\d+)/);\n if (!m) return null;\n return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];\n}\n\n/**\n * Returns true if `version` satisfies `range`.\n * Only >=X.Y.Z is supported. If range is undefined, always returns true.\n */\nfunction satisfiesRange(version: string, range: string | undefined): boolean {\n if (!range) return true;\n const m = range.match(/^>=\\s*(.+)$/);\n if (!m) return true; // unsupported range form — lenient pass\n const minParsed = parseSemver(m[1]);\n const vParsed = parseSemver(version);\n if (!minParsed || !vParsed) return true; // cannot parse — lenient pass\n const [maj1, min1, pat1] = minParsed;\n const [maj2, min2, pat2] = vParsed;\n if (maj2 !== maj1) return maj2 > maj1;\n if (min2 !== min1) return min2 > min1;\n return pat2 >= pat1;\n}\n\n// ============================================================\n// detect() factory\n// ============================================================\n\nfunction makeDetect(\n binary: string,\n versionFlag = '--version',\n parseVersion?: (output: string) => string,\n): AgentCatalogEntry['detect'] {\n return async () => {\n // Step 1: which <binary>\n let binaryPath: string | undefined;\n try {\n const { stdout } = await execFileAsync('which', [binary]);\n binaryPath = stdout.trim();\n } catch {\n return { found: false };\n }\n\n if (!binaryPath) return { found: false };\n\n // Step 2: <binary> --version\n try {\n const { stdout, stderr } = await execFileAsync(binaryPath, [versionFlag]);\n const raw = (stdout + stderr).trim();\n const version = parseVersion ? parseVersion(raw) : extractVersion(raw);\n return { found: true, binaryPath, version };\n } catch {\n // Binary exists but --version failed — report found with no version\n return { found: true, binaryPath, version: undefined };\n }\n };\n}\n\n/** Extract the first semver-like string from a version output line. */\nfunction extractVersion(output: string): string | undefined {\n const m = output.match(/(\\d+\\.\\d+\\.\\d+)/);\n return m ? m[1] : undefined;\n}\n\n// ============================================================\n// Catalog\n// ============================================================\n\nexport const AGENT_CATALOG: AgentCatalogEntry[] = [\n {\n name: 'claude-code',\n displayName: 'Claude Code',\n packPackage: '@prismer/claude-code-plugin',\n packVersionRange: '^1.9.0',\n hookConfigPath: '~/.claude/hooks.json',\n mcpConfigPath: '~/.claude/mcp_servers.json',\n upstreamBinary: 'claude',\n upstreamVersionRange: '>=2.0.0',\n localSourcePath: sourcePath('claude-code'),\n installCommand: 'curl -fsSL https://claude.ai/install.sh | bash',\n tiersSupported: [1, 2, 3, 4, 5, 6, 7],\n capabilityTags: ['code', 'shell', 'mcp', 'approval', 'skill', 'fs'],\n detect: makeDetect('claude', '--version'),\n },\n {\n name: 'codex',\n displayName: 'Codex',\n packPackage: '@prismer/codex-plugin',\n packVersionRange: '^1.9.0',\n hookConfigPath: '~/.codex/hooks.json',\n mcpConfigPath: undefined,\n upstreamBinary: 'codex',\n upstreamVersionRange: undefined,\n localSourcePath: sourcePath('codex'),\n installCommand: 'npm install -g @openai/codex',\n tiersSupported: [1, 2, 3, 4, 5, 6],\n capabilityTags: ['code', 'shell', 'mcp'],\n detect: makeDetect('codex', '--version'),\n },\n {\n name: 'hermes',\n displayName: 'Hermes',\n packPackage: '@prismer/hermes-plugin',\n packVersionRange: '^1.9.0',\n hookConfigPath: '~/.hermes/hooks.json',\n mcpConfigPath: undefined,\n upstreamBinary: 'hermes',\n // NousResearch/hermes-agent upstream is currently 0.10.x (as of 2026-04).\n // Previously pinned `>=1.0.0` — rejected every current release so every\n // fresh install hit \"Incompatible version: hermes 0.10.0 does not satisfy\n // >=1.0.0\" and fell back to skipping Hermes entirely. G-14 closed 2026-04-22.\n // The hook-plugin API we target (ctx.register_hook + **kwargs) has been\n // stable since 0.10.0 per real integration verification in v0.1.1-0.2.0\n // of prismer-adapter-hermes.\n upstreamVersionRange: '>=0.10.0',\n localSourcePath: sourcePath('hermes-agent'),\n // v1.9.0 B.3: public installer curl path instead of the dev-machine\n // `cd ~/workspace/agent/hermes-agent && uv pip install -e \".[cli]\"` form —\n // Docker / CI hosts do not have that worktree. --skip-setup keeps the\n // install non-interactive so the outer agent-install wizard can follow up\n // with config.yaml / .env writes under its own sequence.\n installCommand: 'curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup',\n tiersSupported: [1, 2, 3, 4],\n capabilityTags: ['code', 'shell', 'mcp'],\n detect: makeDetect('hermes', '--version'),\n },\n {\n name: 'openclaw',\n displayName: 'OpenClaw',\n packPackage: '@prismer/openclaw-channel',\n packVersionRange: '^1.9.0',\n // NOTE: OpenClaw does NOT read `~/.openclaw/hooks.json` — its hook\n // discovery scans `~/.openclaw/hooks/<name>/HOOK.md` directories and the\n // plugin registry (see openclaw docs/automation/hooks.md). PARA events\n // during `openclaw agent --local` are actually fired by the\n // `@prismer/openclaw-channel` package itself via the native\n // `api.registerHook(...)` + `api.on(...)` plugin APIs (see\n // `sdk/prismer-cloud/openclaw-channel/src/para/register.ts`). We keep\n // this path here because install-agent's smoke-test step reads it to\n // verify the adapter is in place; removing it would break that check.\n // v1.9.0 report break #5 fix.\n hookConfigPath: '~/.openclaw/hooks.json',\n mcpConfigPath: undefined,\n upstreamBinary: 'openclaw',\n upstreamVersionRange: undefined,\n localSourcePath: sourcePath('openclaw'),\n // v1.9.0 B.3: replaced the monorepo-relative pnpm build with the upstream\n // npm release, matching how claude-code and codex pull from public sources.\n installCommand: 'npm install -g openclaw@latest',\n tiersSupported: [1, 2, 3, 4, 5, 6],\n capabilityTags: ['code', 'shell', 'mcp', 'fs'],\n detect: makeDetect('openclaw', '--version'),\n },\n];\n\nexport function getAgent(name: string): AgentCatalogEntry | undefined {\n return AGENT_CATALOG.find((e) => e.name === name);\n}\n\n// Re-export for convenience (used in install-agent)\nexport { satisfiesRange };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,68 @@
1
+ // src/dispatch-mux.ts
2
+ var DispatchMux = class {
3
+ constructor(registry) {
4
+ this.registry = registry;
5
+ }
6
+ /**
7
+ * Resolve the adapter that would handle a given request without
8
+ * actually dispatching. Returned undefined means "no adapter matches".
9
+ * Useful for cloud-side capability probing.
10
+ */
11
+ resolve(req) {
12
+ if (req.preferAdapter) {
13
+ const preferred = this.registry.get(req.preferAdapter);
14
+ if (preferred && preferred.capabilityTags.some((tag) => matches(tag, req.capability))) {
15
+ return { adapter: preferred.name };
16
+ }
17
+ }
18
+ const candidates = this.registry.findByCapability(req.capability);
19
+ if (candidates.length === 0) return void 0;
20
+ return { adapter: candidates[0].name };
21
+ }
22
+ async dispatch(req) {
23
+ if (!req.capability || typeof req.capability !== "string") {
24
+ return { ok: false, error: "capability required" };
25
+ }
26
+ let chosen = req.preferAdapter ? this.registry.get(req.preferAdapter) : void 0;
27
+ if (chosen && !chosen.capabilityTags.some((tag) => matches(tag, req.capability))) {
28
+ chosen = void 0;
29
+ }
30
+ if (!chosen) {
31
+ const matchesList = this.registry.findByCapability(req.capability);
32
+ chosen = matchesList[0];
33
+ }
34
+ if (!chosen) {
35
+ return {
36
+ ok: false,
37
+ error: `no_adapter_for_capability:${req.capability}`
38
+ };
39
+ }
40
+ try {
41
+ const result = await chosen.dispatch({
42
+ taskId: req.taskId,
43
+ stepIdx: req.stepIdx,
44
+ capability: req.capability,
45
+ prompt: req.prompt,
46
+ metadata: req.metadata,
47
+ deadlineAt: req.deadlineAt
48
+ });
49
+ return { ...result, adapter: chosen.name };
50
+ } catch (err) {
51
+ return {
52
+ ok: false,
53
+ error: `adapter_threw:${err.message}`,
54
+ adapter: chosen.name
55
+ };
56
+ }
57
+ }
58
+ };
59
+ function matches(tag, capability) {
60
+ if (tag === capability) return true;
61
+ if (tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))) return true;
62
+ return false;
63
+ }
64
+
65
+ export {
66
+ DispatchMux
67
+ };
68
+ //# sourceMappingURL=chunk-NDNX2G6O.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dispatch-mux.ts"],"sourcesContent":["/**\n * Prismer Runtime — Dispatch Mux (Sprint A3, D4).\n *\n * Picks an adapter for an incoming task and forwards the dispatch call.\n * Selection policy (in priority order):\n *\n * 1. `preferAdapter` (caller-supplied) wins if registered AND the\n * adapter can satisfy the requested capability.\n * 2. If exactly one adapter matches the capability, use it.\n * 3. If multiple match, pick deterministically — adapters sorted by\n * `name` (so the same task always lands on the same adapter when\n * multiple are eligible). Future: load/latency-aware ranking.\n * 4. None matches → return a structured \"no_adapter\" failure rather\n * than throw — the caller (cloud) decides whether to reroute.\n *\n * The mux never throws on adapter errors; it always returns a result\n * with `ok=false` so the cloud-side TaskRouter can report `step_failed`\n * instead of seeing a daemon-side stack trace.\n */\n\nimport type { AdapterDispatchInput, AdapterDispatchResult, AdapterRegistry } from './adapter-registry.js';\n\nexport interface DispatchMuxRequest extends AdapterDispatchInput {\n /** If set and registered, win selection. */\n preferAdapter?: string;\n}\n\nexport interface DispatchMuxResult extends AdapterDispatchResult {\n /** Which adapter handled this request. Useful for telemetry +\n * debugging when the answer \"feels wrong\". */\n adapter?: string;\n}\n\nexport class DispatchMux {\n constructor(private readonly registry: AdapterRegistry) {}\n\n /**\n * Resolve the adapter that would handle a given request without\n * actually dispatching. Returned undefined means \"no adapter matches\".\n * Useful for cloud-side capability probing.\n */\n resolve(req: { capability: string; preferAdapter?: string }): { adapter: string } | undefined {\n if (req.preferAdapter) {\n const preferred = this.registry.get(req.preferAdapter);\n if (preferred && preferred.capabilityTags.some((tag) => matches(tag, req.capability))) {\n return { adapter: preferred.name };\n }\n }\n const candidates = this.registry.findByCapability(req.capability);\n if (candidates.length === 0) return undefined;\n // findByCapability already returns name-sorted output, so [0] is deterministic.\n return { adapter: candidates[0].name };\n }\n\n async dispatch(req: DispatchMuxRequest): Promise<DispatchMuxResult> {\n if (!req.capability || typeof req.capability !== 'string') {\n return { ok: false, error: 'capability required' };\n }\n\n let chosen = req.preferAdapter ? this.registry.get(req.preferAdapter) : undefined;\n if (chosen && !chosen.capabilityTags.some((tag) => matches(tag, req.capability))) {\n // Preferred adapter exists but cannot satisfy capability — fall through.\n chosen = undefined;\n }\n if (!chosen) {\n const matchesList = this.registry.findByCapability(req.capability);\n chosen = matchesList[0];\n }\n if (!chosen) {\n return {\n ok: false,\n error: `no_adapter_for_capability:${req.capability}`,\n };\n }\n\n try {\n const result = await chosen.dispatch({\n taskId: req.taskId,\n stepIdx: req.stepIdx,\n capability: req.capability,\n prompt: req.prompt,\n metadata: req.metadata,\n deadlineAt: req.deadlineAt,\n });\n return { ...result, adapter: chosen.name };\n } catch (err) {\n // Adapter implementations should not throw, but defend against\n // misbehaving plug-ins so the daemon doesn't crash.\n return {\n ok: false,\n error: `adapter_threw:${(err as Error).message}`,\n adapter: chosen.name,\n };\n }\n }\n}\n\n/** Tag matches capability (literal or wildcard prefix `xxx.*`). */\nfunction matches(tag: string, capability: string): boolean {\n if (tag === capability) return true;\n if (tag.endsWith('.*') && capability.startsWith(tag.slice(0, -1))) return true;\n return false;\n}\n"],"mappings":";AAiCO,IAAM,cAAN,MAAkB;AAAA,EACvB,YAA6B,UAA2B;AAA3B;AAAA,EAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzD,QAAQ,KAAsF;AAC5F,QAAI,IAAI,eAAe;AACrB,YAAM,YAAY,KAAK,SAAS,IAAI,IAAI,aAAa;AACrD,UAAI,aAAa,UAAU,eAAe,KAAK,CAAC,QAAQ,QAAQ,KAAK,IAAI,UAAU,CAAC,GAAG;AACrF,eAAO,EAAE,SAAS,UAAU,KAAK;AAAA,MACnC;AAAA,IACF;AACA,UAAM,aAAa,KAAK,SAAS,iBAAiB,IAAI,UAAU;AAChE,QAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,WAAO,EAAE,SAAS,WAAW,CAAC,EAAE,KAAK;AAAA,EACvC;AAAA,EAEA,MAAM,SAAS,KAAqD;AAClE,QAAI,CAAC,IAAI,cAAc,OAAO,IAAI,eAAe,UAAU;AACzD,aAAO,EAAE,IAAI,OAAO,OAAO,sBAAsB;AAAA,IACnD;AAEA,QAAI,SAAS,IAAI,gBAAgB,KAAK,SAAS,IAAI,IAAI,aAAa,IAAI;AACxE,QAAI,UAAU,CAAC,OAAO,eAAe,KAAK,CAAC,QAAQ,QAAQ,KAAK,IAAI,UAAU,CAAC,GAAG;AAEhF,eAAS;AAAA,IACX;AACA,QAAI,CAAC,QAAQ;AACX,YAAM,cAAc,KAAK,SAAS,iBAAiB,IAAI,UAAU;AACjE,eAAS,YAAY,CAAC;AAAA,IACxB;AACA,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,6BAA6B,IAAI,UAAU;AAAA,MACpD;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,SAAS;AAAA,QACnC,QAAQ,IAAI;AAAA,QACZ,SAAS,IAAI;AAAA,QACb,YAAY,IAAI;AAAA,QAChB,QAAQ,IAAI;AAAA,QACZ,UAAU,IAAI;AAAA,QACd,YAAY,IAAI;AAAA,MAClB,CAAC;AACD,aAAO,EAAE,GAAG,QAAQ,SAAS,OAAO,KAAK;AAAA,IAC3C,SAAS,KAAK;AAGZ,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,iBAAkB,IAAc,OAAO;AAAA,QAC9C,SAAS,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACF;AAGA,SAAS,QAAQ,KAAa,YAA6B;AACzD,MAAI,QAAQ,WAAY,QAAO;AAC/B,MAAI,IAAI,SAAS,IAAI,KAAK,WAAW,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC,EAAG,QAAO;AAC1E,SAAO;AACT;","names":[]}
@@ -0,0 +1,45 @@
1
+ // src/agents/published-registry.ts
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import TOML from "@iarna/toml";
6
+ function defaultPath() {
7
+ return path.join(os.homedir(), ".prismer", "published-agents.toml");
8
+ }
9
+ function loadPublishedRegistry(filePath) {
10
+ const file = filePath ?? defaultPath();
11
+ let raw;
12
+ try {
13
+ raw = fs.readFileSync(file, "utf-8");
14
+ } catch (err) {
15
+ if (err.code === "ENOENT") return [];
16
+ throw err;
17
+ }
18
+ let parsed;
19
+ try {
20
+ parsed = TOML.parse(raw);
21
+ } catch {
22
+ return [];
23
+ }
24
+ const list = parsed.agent;
25
+ if (!Array.isArray(list)) return [];
26
+ const out = [];
27
+ for (const row of list) {
28
+ if (!row || typeof row !== "object") continue;
29
+ const r = row;
30
+ if (typeof r.name !== "string" || typeof r.cloud_agent_id !== "string") continue;
31
+ out.push({
32
+ name: r.name,
33
+ cloudAgentId: r.cloud_agent_id,
34
+ localAgentId: typeof r.local_agent_id === "string" ? r.local_agent_id : void 0,
35
+ adapter: typeof r.adapter === "string" ? r.adapter : void 0,
36
+ publishedAt: typeof r.published_at === "string" ? r.published_at : (/* @__PURE__ */ new Date(0)).toISOString()
37
+ });
38
+ }
39
+ return out;
40
+ }
41
+
42
+ export {
43
+ loadPublishedRegistry
44
+ };
45
+ //# sourceMappingURL=chunk-POWV475F.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agents/published-registry.ts"],"sourcesContent":["/**\n * Prismer Runtime — Published agents registry (Sprint A2.2, D3 publish flow)\n *\n * Tracks which locally-installed agents have been published to cloud, so\n * the daemon's heartbeat loop knows which `cloudAgentId`s to refresh and\n * `prismer agent unpublish` knows what to delete.\n *\n * On-disk format: TOML at `~/.prismer/published-agents.toml`. Schema is\n * forward-compatible — unknown keys are preserved on round-trip.\n *\n * schemaVersion = 1\n * [[agent]]\n * name = \"claude-code\"\n * cloud_agent_id = \"cmo0qzzxw011lvm01qplbgs1i\"\n * local_agent_id = \"claude-code@MacBook-Pro\"\n * adapter = \"claude-code\"\n * published_at = \"2026-04-20T10:30:00.000Z\"\n *\n * Concurrency: callers serialize via the daemon process. We never assume\n * multi-writer safety — a second `prismer agent publish` racing with the\n * heartbeat loop is acceptable because both eventually converge through\n * read-modify-write of this single file.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport TOML from '@iarna/toml';\n\nexport interface PublishedAgent {\n /** Local short name (matches `prismer agent install <name>`). */\n name: string;\n /** Cloud-side IMAgentCard.id returned from POST /api/im/agents/register. */\n cloudAgentId: string;\n /** Daemon-internal id, e.g. \"claude-code@hostname\". For trace only. */\n localAgentId?: string;\n /** PARA adapter name. */\n adapter?: string;\n /** ISO timestamp when publish succeeded. */\n publishedAt: string;\n}\n\nconst SCHEMA_VERSION = 1;\n\nfunction defaultPath(): string {\n return path.join(os.homedir(), '.prismer', 'published-agents.toml');\n}\n\n/**\n * Load the registry. Returns an empty list if the file doesn't exist or\n * is malformed (we tolerate corruption — the daemon still functions, the\n * agent simply needs to be republished).\n */\nexport function loadPublishedRegistry(filePath?: string): PublishedAgent[] {\n const file = filePath ?? defaultPath();\n let raw: string;\n try {\n raw = fs.readFileSync(file, 'utf-8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n\n let parsed: TOML.JsonMap;\n try {\n parsed = TOML.parse(raw);\n } catch {\n // Don't throw — let publish recover gracefully.\n return [];\n }\n\n const list = parsed.agent;\n if (!Array.isArray(list)) return [];\n\n const out: PublishedAgent[] = [];\n for (const row of list) {\n if (!row || typeof row !== 'object') continue;\n const r = row as Record<string, unknown>;\n if (typeof r.name !== 'string' || typeof r.cloud_agent_id !== 'string') continue;\n out.push({\n name: r.name,\n cloudAgentId: r.cloud_agent_id,\n localAgentId: typeof r.local_agent_id === 'string' ? r.local_agent_id : undefined,\n adapter: typeof r.adapter === 'string' ? r.adapter : undefined,\n publishedAt: typeof r.published_at === 'string' ? r.published_at : new Date(0).toISOString(),\n });\n }\n return out;\n}\n\n/**\n * Atomically write the registry. Uses a temp file + rename so a crashed\n * write never leaves the file half-truncated.\n */\nexport function savePublishedRegistry(entries: PublishedAgent[], filePath?: string): void {\n const file = filePath ?? defaultPath();\n fs.mkdirSync(path.dirname(file), { recursive: true });\n\n const doc: TOML.JsonMap = {\n schemaVersion: SCHEMA_VERSION,\n agent: entries.map((e) => ({\n name: e.name,\n cloud_agent_id: e.cloudAgentId,\n ...(e.localAgentId ? { local_agent_id: e.localAgentId } : {}),\n ...(e.adapter ? { adapter: e.adapter } : {}),\n published_at: e.publishedAt,\n })),\n };\n\n const tmp = file + '.tmp';\n fs.writeFileSync(tmp, TOML.stringify(doc), { mode: 0o600 });\n fs.renameSync(tmp, file);\n}\n\n/** Add or replace by `name`. Returns the resulting list. */\nexport function upsertPublished(entry: PublishedAgent, filePath?: string): PublishedAgent[] {\n const all = loadPublishedRegistry(filePath);\n const idx = all.findIndex((a) => a.name === entry.name);\n if (idx >= 0) all[idx] = entry;\n else all.push(entry);\n savePublishedRegistry(all, filePath);\n return all;\n}\n\n/** Remove by `name`. Returns the resulting list (no-op if missing). */\nexport function removePublished(name: string, filePath?: string): PublishedAgent[] {\n const all = loadPublishedRegistry(filePath);\n const filtered = all.filter((a) => a.name !== name);\n if (filtered.length !== all.length) {\n savePublishedRegistry(filtered, filePath);\n }\n return filtered;\n}\n\n/** Look up by `name` (returns undefined if not published). */\nexport function findPublished(name: string, filePath?: string): PublishedAgent | undefined {\n return loadPublishedRegistry(filePath).find((a) => a.name === name);\n}\n"],"mappings":";AAwBA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,OAAO,UAAU;AAiBjB,SAAS,cAAsB;AAC7B,SAAY,UAAQ,WAAQ,GAAG,YAAY,uBAAuB;AACpE;AAOO,SAAS,sBAAsB,UAAqC;AACzE,QAAM,OAAO,YAAY,YAAY;AACrC,MAAI;AACJ,MAAI;AACF,UAAS,gBAAa,MAAM,OAAO;AAAA,EACrC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;AAAA,EACR;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AAEN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,OAAO,OAAO;AACpB,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC;AAElC,QAAM,MAAwB,CAAC;AAC/B,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;AACrC,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,mBAAmB,SAAU;AACxE,QAAI,KAAK;AAAA,MACP,MAAM,EAAE;AAAA,MACR,cAAc,EAAE;AAAA,MAChB,cAAc,OAAO,EAAE,mBAAmB,WAAW,EAAE,iBAAiB;AAAA,MACxE,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;AAAA,MACrD,aAAa,OAAO,EAAE,iBAAiB,WAAW,EAAE,gBAAe,oBAAI,KAAK,CAAC,GAAE,YAAY;AAAA,IAC7F,CAAC;AAAA,EACH;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,68 @@
1
+ // src/adapter-registry.ts
2
+ var AdapterRegistry = class {
3
+ adapters = /* @__PURE__ */ new Map();
4
+ register(adapter) {
5
+ if (!adapter.name || typeof adapter.name !== "string") {
6
+ throw new Error("AdapterRegistry.register: adapter.name required");
7
+ }
8
+ if (typeof adapter.dispatch !== "function") {
9
+ throw new Error(`AdapterRegistry.register: adapter "${adapter.name}" missing dispatch()`);
10
+ }
11
+ this.adapters.set(adapter.name, adapter);
12
+ }
13
+ unregister(name) {
14
+ return this.adapters.delete(name);
15
+ }
16
+ has(name) {
17
+ return this.adapters.has(name);
18
+ }
19
+ get(name) {
20
+ return this.adapters.get(name);
21
+ }
22
+ list() {
23
+ return Array.from(this.adapters.values()).map(({ dispatch: _dispatch, health: _health, ...descriptor }) => descriptor);
24
+ }
25
+ size() {
26
+ return this.adapters.size;
27
+ }
28
+ /**
29
+ * Find adapters that can satisfy the given capability tag.
30
+ *
31
+ * An adapter matches if `capabilityTags` includes the tag verbatim or
32
+ * if the adapter declared a wildcard prefix match (e.g. `code.*` matches
33
+ * `code.write`). Returns deterministic ordering — adapters are sorted by
34
+ * name so callers see the same result for the same registry state.
35
+ */
36
+ findByCapability(capability) {
37
+ const matches = [];
38
+ for (const adapter of this.adapters.values()) {
39
+ if (adapter.capabilityTags.includes(capability)) {
40
+ matches.push(adapter);
41
+ continue;
42
+ }
43
+ if (adapter.capabilityTags.some(
44
+ (tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
45
+ )) {
46
+ matches.push(adapter);
47
+ }
48
+ }
49
+ matches.sort((a, b) => a.name.localeCompare(b.name));
50
+ return matches;
51
+ }
52
+ /**
53
+ * Find adapters that can host a given PARA tier.
54
+ */
55
+ findByTier(tier) {
56
+ const matches = [];
57
+ for (const adapter of this.adapters.values()) {
58
+ if (adapter.tiersSupported.includes(tier)) matches.push(adapter);
59
+ }
60
+ matches.sort((a, b) => a.name.localeCompare(b.name));
61
+ return matches;
62
+ }
63
+ };
64
+
65
+ export {
66
+ AdapterRegistry
67
+ };
68
+ //# sourceMappingURL=chunk-VTFKZAUY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapter-registry.ts"],"sourcesContent":["/**\n * Prismer Runtime — Adapter Registry (Sprint A3, D4 dispatch mux).\n *\n * One process-wide registry maps adapter `name` → `AdapterImpl`. The\n * registry is the data store; selection policy lives in DispatchMux\n * (dispatch-mux.ts) so the same registry can drive multiple selectors\n * (capability-based, name-based, weighted load, etc.) without coupling.\n *\n * An adapter advertises:\n * - `name` : stable identifier (matches catalog name).\n * - `tiersSupported` : PARA Tier numbers it can host (e.g. [1..7]).\n * - `capabilityTags` : tags from the task's `requiresCapability`\n * vocabulary that this adapter can satisfy.\n *\n * On register conflict, replace — adapter modules are expected to be\n * the only source of truth for their own descriptor.\n *\n * Adapters typically run as out-of-process workers (Claude Code CLI,\n * OpenClaw daemon plug-in, Hermes Python). The `dispatch` boundary\n * here is the *runtime side* of that bridge — it returns a Promise\n * whose resolution corresponds to the adapter's reported completion.\n */\n\nexport interface AdapterDescriptor {\n /** Stable identifier — must match the catalog entry. */\n name: string;\n /** PARA Tier numbers (L1–L10). */\n tiersSupported: number[];\n /** Capability tag vocabulary (e.g. [\"code.write\", \"code.review\"]). */\n capabilityTags: string[];\n /** Free-form for telemetry — version, build, etc. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface AdapterDispatchInput {\n /** Cloud task ID (im_tasks.id). */\n taskId: string;\n /** Step index when the task is multi-step (route step). */\n stepIdx?: number;\n /** Capability the cloud asked for — drives adapter selection. */\n capability: string;\n /** User-facing prompt / instruction. */\n prompt: string;\n /** Free-form metadata passed through to the adapter. */\n metadata?: Record<string, unknown>;\n /** Optional deadline (ms epoch). Adapters should respect it best-effort. */\n deadlineAt?: number;\n}\n\nexport interface AdapterDispatchResult {\n ok: boolean;\n /** Output text (stdout, summary, etc.). */\n output?: string;\n /** Files produced by the adapter (paths are relative to the agent\n * workspace; cloud uploads them via the artifact stream). */\n artifacts?: Array<{ path: string; bytes: number; mime?: string }>;\n /** Failure reason — must be set when ok=false. */\n error?: string;\n /** Free-form telemetry — token counts, latency, model, etc. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface AdapterImpl extends AdapterDescriptor {\n /** Dispatch a task step to this adapter. Should not throw — errors\n * belong in the result. */\n dispatch(input: AdapterDispatchInput): Promise<AdapterDispatchResult>;\n /** Optional: per-adapter health probe. Defaults to \"healthy\". */\n health?(): Promise<{ healthy: boolean; reason?: string }>;\n /**\n * Reset adapter state for a specific agent (or all agents if undefined).\n *\n * v1.9.x remote-command `agent_restart` semantic: abort/clear whatever\n * per-agent context the adapter holds. For stateless adapters (CLI shim),\n * this is a no-op; for Mode B adapters, it's typically a POST /reset to\n * the loopback so the adapter host clears its own session state.\n *\n * NOT a process restart — the adapter is not expected to own a PID.\n *\n * @param agentName Optional agent name to scope the reset. Undefined = reset all.\n * @returns Arbitrary result; ok:true for success, ok:false+reason otherwise.\n */\n reset?(agentName?: string): Promise<{ ok: boolean; state?: string; reason?: string; [k: string]: unknown }>;\n}\n\nexport class AdapterRegistry {\n private readonly adapters = new Map<string, AdapterImpl>();\n\n register(adapter: AdapterImpl): void {\n if (!adapter.name || typeof adapter.name !== 'string') {\n throw new Error('AdapterRegistry.register: adapter.name required');\n }\n if (typeof adapter.dispatch !== 'function') {\n throw new Error(`AdapterRegistry.register: adapter \"${adapter.name}\" missing dispatch()`);\n }\n this.adapters.set(adapter.name, adapter);\n }\n\n unregister(name: string): boolean {\n return this.adapters.delete(name);\n }\n\n has(name: string): boolean {\n return this.adapters.has(name);\n }\n\n get(name: string): AdapterImpl | undefined {\n return this.adapters.get(name);\n }\n\n list(): AdapterDescriptor[] {\n // Return only descriptor fields — callers shouldn't be poking at dispatch\n // implementations through the registry.\n return Array.from(this.adapters.values()).map(({ dispatch: _dispatch, health: _health, ...descriptor }) => descriptor);\n }\n\n size(): number {\n return this.adapters.size;\n }\n\n /**\n * Find adapters that can satisfy the given capability tag.\n *\n * An adapter matches if `capabilityTags` includes the tag verbatim or\n * if the adapter declared a wildcard prefix match (e.g. `code.*` matches\n * `code.write`). Returns deterministic ordering — adapters are sorted by\n * name so callers see the same result for the same registry state.\n */\n findByCapability(capability: string): AdapterImpl[] {\n const matches: AdapterImpl[] = [];\n for (const adapter of this.adapters.values()) {\n if (adapter.capabilityTags.includes(capability)) {\n matches.push(adapter);\n continue;\n }\n // Wildcard match: tag ends with \".*\" — e.g. \"code.*\" matches \"code.review\".\n if (\n adapter.capabilityTags.some(\n (tag) => tag.endsWith('.*') && capability.startsWith(tag.slice(0, -1)),\n )\n ) {\n matches.push(adapter);\n }\n }\n matches.sort((a, b) => a.name.localeCompare(b.name));\n return matches;\n }\n\n /**\n * Find adapters that can host a given PARA tier.\n */\n findByTier(tier: number): AdapterImpl[] {\n const matches: AdapterImpl[] = [];\n for (const adapter of this.adapters.values()) {\n if (adapter.tiersSupported.includes(tier)) matches.push(adapter);\n }\n matches.sort((a, b) => a.name.localeCompare(b.name));\n return matches;\n }\n}\n"],"mappings":";AAoFO,IAAM,kBAAN,MAAsB;AAAA,EACV,WAAW,oBAAI,IAAyB;AAAA,EAEzD,SAAS,SAA4B;AACnC,QAAI,CAAC,QAAQ,QAAQ,OAAO,QAAQ,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AACA,QAAI,OAAO,QAAQ,aAAa,YAAY;AAC1C,YAAM,IAAI,MAAM,sCAAsC,QAAQ,IAAI,sBAAsB;AAAA,IAC1F;AACA,SAAK,SAAS,IAAI,QAAQ,MAAM,OAAO;AAAA,EACzC;AAAA,EAEA,WAAW,MAAuB;AAChC,WAAO,KAAK,SAAS,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,IAAI,MAAuB;AACzB,WAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EAC/B;AAAA,EAEA,IAAI,MAAuC;AACzC,WAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EAC/B;AAAA,EAEA,OAA4B;AAG1B,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,WAAW,QAAQ,SAAS,GAAG,WAAW,MAAM,UAAU;AAAA,EACvH;AAAA,EAEA,OAAe;AACb,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBAAiB,YAAmC;AAClD,UAAM,UAAyB,CAAC;AAChC,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,UAAI,QAAQ,eAAe,SAAS,UAAU,GAAG;AAC/C,gBAAQ,KAAK,OAAO;AACpB;AAAA,MACF;AAEA,UACE,QAAQ,eAAe;AAAA,QACrB,CAAC,QAAQ,IAAI,SAAS,IAAI,KAAK,WAAW,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC;AAAA,MACvE,GACA;AACA,gBAAQ,KAAK,OAAO;AAAA,MACtB;AAAA,IACF;AACA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACnD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAA6B;AACtC,UAAM,UAAyB,CAAC;AAChC,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,UAAI,QAAQ,eAAe,SAAS,IAAI,EAAG,SAAQ,KAAK,OAAO;AAAA,IACjE;AACA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACnD,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,8 @@
1
+ import {
2
+ DispatchMux
3
+ } from "./chunk-NDNX2G6O.mjs";
4
+ import "./chunk-BJTO5JO5.mjs";
5
+ export {
6
+ DispatchMux
7
+ };
8
+ //# sourceMappingURL=dispatch-mux-MW4HHICS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,78 @@
1
+ import "./chunk-BJTO5JO5.mjs";
2
+
3
+ // src/dispatch-rpc.ts
4
+ function registerDispatchRpcHandlers(sink, deps) {
5
+ sink.registerRpcHandler("task.dispatch", async (params) => {
6
+ const req = parseDispatchRequest(params);
7
+ if ("error" in req) {
8
+ const fail = { ok: false, error: req.error };
9
+ return fail;
10
+ }
11
+ const result = await deps.mux.dispatch(req);
12
+ if (deps.artifactUploader && result.ok && result.artifacts && result.artifacts.length > 0) {
13
+ const uploader = deps.artifactUploader;
14
+ const enriched = await Promise.all(
15
+ result.artifacts.map(async (a) => {
16
+ try {
17
+ const r = await uploader.upload(req.taskId, a.path, {
18
+ ...a.mime ? { mimeType: a.mime } : {}
19
+ });
20
+ if (r.ok) {
21
+ return { ...a, cloudUploadId: r.uploadId, cloudCdnUrl: r.cdnUrl };
22
+ }
23
+ return { ...a, cloudUploadError: r.error };
24
+ } catch (err) {
25
+ return { ...a, cloudUploadError: `unexpected:${err.message}` };
26
+ }
27
+ })
28
+ );
29
+ return { ...result, artifacts: enriched };
30
+ }
31
+ return result;
32
+ });
33
+ sink.registerRpcHandler("task.list-adapters", async () => {
34
+ return { adapters: deps.registry.list() };
35
+ });
36
+ sink.registerRpcHandler("task.resolve", async (params) => {
37
+ const body = params ?? {};
38
+ if (!body.capability || typeof body.capability !== "string") {
39
+ return { ok: false, error: "capability required" };
40
+ }
41
+ const resolved = deps.mux.resolve({
42
+ capability: body.capability,
43
+ preferAdapter: body.preferAdapter
44
+ });
45
+ if (!resolved) {
46
+ return { ok: false, error: `no_adapter_for_capability:${body.capability}` };
47
+ }
48
+ return { ok: true, adapter: resolved.adapter };
49
+ });
50
+ }
51
+ function parseDispatchRequest(params) {
52
+ if (!params || typeof params !== "object") {
53
+ return { error: "task.dispatch params must be an object" };
54
+ }
55
+ const body = params;
56
+ if (typeof body.taskId !== "string" || body.taskId.length === 0) {
57
+ return { error: "taskId (string) required" };
58
+ }
59
+ if (typeof body.capability !== "string" || body.capability.length === 0) {
60
+ return { error: "capability (string) required" };
61
+ }
62
+ if (typeof body.prompt !== "string") {
63
+ return { error: "prompt (string) required" };
64
+ }
65
+ return {
66
+ taskId: body.taskId,
67
+ capability: body.capability,
68
+ prompt: body.prompt,
69
+ ...typeof body.stepIdx === "number" ? { stepIdx: body.stepIdx } : {},
70
+ ...typeof body.deadlineAt === "number" ? { deadlineAt: body.deadlineAt } : {},
71
+ ...typeof body.preferAdapter === "string" ? { preferAdapter: body.preferAdapter } : {},
72
+ ...body.metadata && typeof body.metadata === "object" ? { metadata: body.metadata } : {}
73
+ };
74
+ }
75
+ export {
76
+ registerDispatchRpcHandlers
77
+ };
78
+ //# sourceMappingURL=dispatch-rpc-J7H6KC2V.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dispatch-rpc.ts"],"sourcesContent":["/**\n * Prismer Runtime — Dispatch RPC handlers (Sprint C0)\n *\n * Bridges cloud → daemon dispatch. Cloud's TaskRouter sends an\n * `rpc.request` over the relay control channel with `method:\n * \"task.dispatch\"`; this handler routes the request through the local\n * DispatchMux, which selects the adapter and runs it. The result is\n * returned via `rpc.response` (RelayClient handles the envelope).\n *\n * Mirrors http/fs-rpc.ts so the relay-side dispatch surface and the\n * daemon-side HTTP surface (POST /api/v1/adapters/dispatch) share the\n * same DispatchMux semantics — same selection rules, same failure\n * shapes, only the transport differs.\n */\n\nimport type { DispatchMux, DispatchMuxRequest, DispatchMuxResult } from './dispatch-mux.js';\nimport type { AdapterRegistry } from './adapter-registry.js';\nimport type { ArtifactUploader } from './artifacts-uploader.js';\n\ninterface RpcHandlerSink {\n registerRpcHandler(method: string, handler: (params: unknown) => Promise<unknown>): void;\n}\n\nexport interface DispatchRpcDeps {\n mux: DispatchMux;\n registry: AdapterRegistry;\n /**\n * v1.9.x Task 2: optional artifact uploader. When set, after a successful\n * dispatch we upload each `result.artifacts[*].path` to cloud and attach\n * `cloudUploadId` / `cloudCdnUrl` back onto the artifact. Failures are\n * absorbed (the artifact stays in the result without cloudUploadId).\n */\n artifactUploader?: ArtifactUploader;\n}\n\n/**\n * Register dispatch-related RPC handlers on a RelayClient or TransportManager.\n * Safe to call multiple times — later registrations overwrite earlier ones.\n *\n * Methods:\n * task.dispatch — run one task step on a selected adapter\n * task.list-adapters — return the registry's descriptors (for cloud probing)\n * task.resolve — return which adapter would handle the capability\n * without actually dispatching (cheap probe)\n */\nexport function registerDispatchRpcHandlers(sink: RpcHandlerSink, deps: DispatchRpcDeps): void {\n sink.registerRpcHandler('task.dispatch', async (params) => {\n const req = parseDispatchRequest(params);\n if ('error' in req) {\n // Surface a structured failure rather than throwing — the cloud\n // promise resolves with ok:false so TaskRouter can mark the step\n // failed without timing out on the rpc.response timer.\n const fail: DispatchMuxResult = { ok: false, error: req.error };\n return fail;\n }\n const result = await deps.mux.dispatch(req);\n\n // v1.9.x Task 2: if the adapter returned artifacts and we have an\n // uploader, push each one to cloud and stamp cloudUploadId on the\n // artifact entry. All failures are swallowed — the rpc.response still\n // carries the raw `path` so the cloud can report something useful.\n if (deps.artifactUploader && result.ok && result.artifacts && result.artifacts.length > 0) {\n const uploader = deps.artifactUploader;\n const enriched = await Promise.all(\n result.artifacts.map(async (a) => {\n try {\n const r = await uploader.upload(req.taskId, a.path, {\n ...(a.mime ? { mimeType: a.mime } : {}),\n });\n if (r.ok) {\n return { ...a, cloudUploadId: r.uploadId, cloudCdnUrl: r.cdnUrl };\n }\n return { ...a, cloudUploadError: r.error };\n } catch (err) {\n // Defensive: should never happen — uploader.upload() is\n // contractually no-throw — but absorb just in case.\n return { ...a, cloudUploadError: `unexpected:${(err as Error).message}` };\n }\n }),\n );\n return { ...result, artifacts: enriched };\n }\n\n return result;\n });\n\n sink.registerRpcHandler('task.list-adapters', async () => {\n return { adapters: deps.registry.list() };\n });\n\n sink.registerRpcHandler('task.resolve', async (params) => {\n const body = (params ?? {}) as { capability?: string; preferAdapter?: string };\n if (!body.capability || typeof body.capability !== 'string') {\n return { ok: false, error: 'capability required' };\n }\n const resolved = deps.mux.resolve({\n capability: body.capability,\n preferAdapter: body.preferAdapter,\n });\n if (!resolved) {\n return { ok: false, error: `no_adapter_for_capability:${body.capability}` };\n }\n return { ok: true, adapter: resolved.adapter };\n });\n}\n\nfunction parseDispatchRequest(params: unknown): DispatchMuxRequest | { error: string } {\n if (!params || typeof params !== 'object') {\n return { error: 'task.dispatch params must be an object' };\n }\n const body = params as Record<string, unknown>;\n\n if (typeof body.taskId !== 'string' || body.taskId.length === 0) {\n return { error: 'taskId (string) required' };\n }\n if (typeof body.capability !== 'string' || body.capability.length === 0) {\n return { error: 'capability (string) required' };\n }\n if (typeof body.prompt !== 'string') {\n return { error: 'prompt (string) required' };\n }\n\n return {\n taskId: body.taskId,\n capability: body.capability,\n prompt: body.prompt,\n ...(typeof body.stepIdx === 'number' ? { stepIdx: body.stepIdx } : {}),\n ...(typeof body.deadlineAt === 'number' ? { deadlineAt: body.deadlineAt } : {}),\n ...(typeof body.preferAdapter === 'string' ? { preferAdapter: body.preferAdapter } : {}),\n ...(body.metadata && typeof body.metadata === 'object'\n ? { metadata: body.metadata as Record<string, unknown> }\n : {}),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,58 @@
1
+ import "./chunk-BJTO5JO5.mjs";
2
+
3
+ // src/http/fs-rpc.ts
4
+ import {
5
+ fsRead,
6
+ fsWrite,
7
+ fsDelete,
8
+ fsEdit,
9
+ fsList,
10
+ fsSearch
11
+ } from "@prismer/sandbox-runtime";
12
+ function requireContext(params, deps) {
13
+ const body = params ?? {};
14
+ if (!body.agentId) {
15
+ throw new Error("agentId required");
16
+ }
17
+ const ctx = deps.fsContextProvider({ agentId: body.agentId, workspace: body.workspace });
18
+ if (!ctx) {
19
+ throw new Error(`FS not configured for agent: ${body.agentId}`);
20
+ }
21
+ return { ...ctx, callPath: "relay" };
22
+ }
23
+ function registerFsRpcHandlers(relayClient, deps) {
24
+ relayClient.registerRpcHandler("fs.read", async (params) => {
25
+ const ctx = requireContext(params, deps);
26
+ const body = params;
27
+ return fsRead(ctx, { path: body.path, offset: body.offset, limit: body.limit });
28
+ });
29
+ relayClient.registerRpcHandler("fs.write", async (params) => {
30
+ const ctx = requireContext(params, deps);
31
+ const body = params;
32
+ return fsWrite(ctx, { path: body.path, content: body.content, encoding: body.encoding });
33
+ });
34
+ relayClient.registerRpcHandler("fs.delete", async (params) => {
35
+ const ctx = requireContext(params, deps);
36
+ const body = params;
37
+ return fsDelete(ctx, { path: body.path });
38
+ });
39
+ relayClient.registerRpcHandler("fs.edit", async (params) => {
40
+ const ctx = requireContext(params, deps);
41
+ const body = params;
42
+ return fsEdit(ctx, { path: body.path, oldString: body.oldString, newString: body.newString });
43
+ });
44
+ relayClient.registerRpcHandler("fs.list", async (params) => {
45
+ const ctx = requireContext(params, deps);
46
+ const body = params;
47
+ return fsList(ctx, { path: body.path, maxDepth: body.maxDepth });
48
+ });
49
+ relayClient.registerRpcHandler("fs.search", async (params) => {
50
+ const ctx = requireContext(params, deps);
51
+ const body = params;
52
+ return fsSearch(ctx, { query: body.query, path: body.path, glob: body.glob });
53
+ });
54
+ }
55
+ export {
56
+ registerFsRpcHandlers
57
+ };
58
+ //# sourceMappingURL=fs-rpc-OSHJZYK6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/http/fs-rpc.ts"],"sourcesContent":["// FS RPC handlers — daemon side of the Cloud Relay Path (v1.9.0 Path 3).\n//\n// The runtime's RelayClient dispatches cloud-initiated `rpc.request` messages\n// to handlers registered here. Each handler wraps the corresponding\n// @prismer/sandbox-runtime FS primitive, using callPath: 'relay' so audit\n// logs show the mobile → cloud → daemon origin (vs 'http' or 'native').\n//\n// Cloud endpoints (src/im/api/remote.ts) send:\n// { type: 'rpc.request', rpcId, method: 'fs.read', params: { agentId, path, ... } }\n//\n// Daemon replies:\n// { type: 'rpc.response', rpcId, result: {...} } // or { ..., error: \"...\" }\n\nimport type { FsContext } from '@prismer/sandbox-runtime';\nimport {\n fsRead,\n fsWrite,\n fsDelete,\n fsEdit,\n fsList,\n fsSearch,\n} from '@prismer/sandbox-runtime';\n/** Structural type — anything with a registerRpcHandler method works here.\n * RelayClient and TransportManager both satisfy this. */\ninterface RpcHandlerSink {\n registerRpcHandler(method: string, handler: (params: unknown) => Promise<unknown>): void;\n}\n\nexport interface FsRpcDeps {\n /** Same provider as HTTP routes — given {agentId, workspace?}, returns an\n * FsContext or undefined if FS is not available for that agent. */\n fsContextProvider: (req: { agentId: string; workspace?: string }) => FsContext | undefined;\n}\n\ninterface FsRequestBase {\n agentId?: string;\n workspace?: string;\n}\n\nfunction requireContext(params: unknown, deps: FsRpcDeps): FsContext {\n const body = (params ?? {}) as FsRequestBase;\n if (!body.agentId) {\n throw new Error('agentId required');\n }\n const ctx = deps.fsContextProvider({ agentId: body.agentId, workspace: body.workspace });\n if (!ctx) {\n throw new Error(`FS not configured for agent: ${body.agentId}`);\n }\n // Mark call path so audit logs reflect relay origin.\n return { ...ctx, callPath: 'relay' as const };\n}\n\n/** Register all fs.* RPC handlers on a RelayClient or TransportManager.\n * Safe to call multiple times — later registrations overwrite earlier ones. */\nexport function registerFsRpcHandlers(relayClient: RpcHandlerSink, deps: FsRpcDeps): void {\n relayClient.registerRpcHandler('fs.read', async (params) => {\n const ctx = requireContext(params, deps);\n const body = params as FsRequestBase & { path: string; offset?: number; limit?: number };\n return fsRead(ctx, { path: body.path, offset: body.offset, limit: body.limit });\n });\n\n relayClient.registerRpcHandler('fs.write', async (params) => {\n const ctx = requireContext(params, deps);\n const body = params as FsRequestBase & { path: string; content: string; encoding?: 'utf8' | 'base64' };\n return fsWrite(ctx, { path: body.path, content: body.content, encoding: body.encoding });\n });\n\n relayClient.registerRpcHandler('fs.delete', async (params) => {\n const ctx = requireContext(params, deps);\n const body = params as FsRequestBase & { path: string };\n return fsDelete(ctx, { path: body.path });\n });\n\n relayClient.registerRpcHandler('fs.edit', async (params) => {\n const ctx = requireContext(params, deps);\n const body = params as FsRequestBase & { path: string; oldString: string; newString: string };\n return fsEdit(ctx, { path: body.path, oldString: body.oldString, newString: body.newString });\n });\n\n relayClient.registerRpcHandler('fs.list', async (params) => {\n const ctx = requireContext(params, deps);\n const body = params as FsRequestBase & { path: string; maxDepth?: number };\n return fsList(ctx, { path: body.path, maxDepth: body.maxDepth });\n });\n\n relayClient.registerRpcHandler('fs.search', async (params) => {\n const ctx = requireContext(params, deps);\n const body = params as FsRequestBase & { query: string; path?: string; glob?: string };\n return fsSearch(ctx, { query: body.query, path: body.path, glob: body.glob });\n });\n}\n"],"mappings":";;;AAcA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAkBP,SAAS,eAAe,QAAiB,MAA4B;AACnE,QAAM,OAAQ,UAAU,CAAC;AACzB,MAAI,CAAC,KAAK,SAAS;AACjB,UAAM,IAAI,MAAM,kBAAkB;AAAA,EACpC;AACA,QAAM,MAAM,KAAK,kBAAkB,EAAE,SAAS,KAAK,SAAS,WAAW,KAAK,UAAU,CAAC;AACvF,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,gCAAgC,KAAK,OAAO,EAAE;AAAA,EAChE;AAEA,SAAO,EAAE,GAAG,KAAK,UAAU,QAAiB;AAC9C;AAIO,SAAS,sBAAsB,aAA6B,MAAuB;AACxF,cAAY,mBAAmB,WAAW,OAAO,WAAW;AAC1D,UAAM,MAAM,eAAe,QAAQ,IAAI;AACvC,UAAM,OAAO;AACb,WAAO,OAAO,KAAK,EAAE,MAAM,KAAK,MAAM,QAAQ,KAAK,QAAQ,OAAO,KAAK,MAAM,CAAC;AAAA,EAChF,CAAC;AAED,cAAY,mBAAmB,YAAY,OAAO,WAAW;AAC3D,UAAM,MAAM,eAAe,QAAQ,IAAI;AACvC,UAAM,OAAO;AACb,WAAO,QAAQ,KAAK,EAAE,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,UAAU,KAAK,SAAS,CAAC;AAAA,EACzF,CAAC;AAED,cAAY,mBAAmB,aAAa,OAAO,WAAW;AAC5D,UAAM,MAAM,eAAe,QAAQ,IAAI;AACvC,UAAM,OAAO;AACb,WAAO,SAAS,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,EAC1C,CAAC;AAED,cAAY,mBAAmB,WAAW,OAAO,WAAW;AAC1D,UAAM,MAAM,eAAe,QAAQ,IAAI;AACvC,UAAM,OAAO;AACb,WAAO,OAAO,KAAK,EAAE,MAAM,KAAK,MAAM,WAAW,KAAK,WAAW,WAAW,KAAK,UAAU,CAAC;AAAA,EAC9F,CAAC;AAED,cAAY,mBAAmB,WAAW,OAAO,WAAW;AAC1D,UAAM,MAAM,eAAe,QAAQ,IAAI;AACvC,UAAM,OAAO;AACb,WAAO,OAAO,KAAK,EAAE,MAAM,KAAK,MAAM,UAAU,KAAK,SAAS,CAAC;AAAA,EACjE,CAAC;AAED,cAAY,mBAAmB,aAAa,OAAO,WAAW;AAC5D,UAAM,MAAM,eAAe,QAAQ,IAAI;AACvC,UAAM,OAAO;AACb,WAAO,SAAS,KAAK,EAAE,OAAO,KAAK,OAAO,MAAM,KAAK,MAAM,MAAM,KAAK,KAAK,CAAC;AAAA,EAC9E,CAAC;AACH;","names":[]}