@synap-core/cli 1.12.2 → 1.13.1-next.20261102.246dc73
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/LICENSE +21 -0
- package/README.md +38 -11
- package/dist/build-info.json +2 -3
- package/dist/commands/agents.d.ts +0 -1
- package/dist/commands/agents.js +13 -3
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/app.js +2 -1
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/automation.js +46 -41
- package/dist/commands/automation.js.map +1 -1
- package/dist/commands/box.d.ts +42 -0
- package/dist/commands/box.js +89 -0
- package/dist/commands/box.js.map +1 -0
- package/dist/commands/bridge-setup.js +3 -1
- package/dist/commands/bridge-setup.js.map +1 -1
- package/dist/commands/capability.d.ts +87 -1
- package/dist/commands/capability.js +174 -64
- package/dist/commands/capability.js.map +1 -1
- package/dist/commands/close-questions.d.ts +27 -0
- package/dist/commands/close-questions.js +49 -0
- package/dist/commands/close-questions.js.map +1 -0
- package/dist/commands/close-questions.test.d.ts +1 -0
- package/dist/commands/close-questions.test.js +52 -0
- package/dist/commands/close-questions.test.js.map +1 -0
- package/dist/commands/connect.d.ts +1 -3
- package/dist/commands/connect.js +2 -2
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/context.js +74 -44
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/data-extra.d.ts +13 -0
- package/dist/commands/data-extra.js +35 -6
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/data.js +2 -1
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/doctor.d.ts +39 -0
- package/dist/commands/doctor.js +136 -14
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +70 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/import.d.ts +25 -0
- package/dist/commands/import.js +98 -12
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.js +63 -21
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys-request.d.ts +48 -0
- package/dist/commands/keys-request.js +236 -0
- package/dist/commands/keys-request.js.map +1 -0
- package/dist/commands/keys-request.test.d.ts +1 -0
- package/dist/commands/keys-request.test.js +176 -0
- package/dist/commands/keys-request.test.js.map +1 -0
- package/dist/commands/knowledge.d.ts +42 -5
- package/dist/commands/knowledge.js +94 -29
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/lens.js +2 -1
- package/dist/commands/lens.js.map +1 -1
- package/dist/commands/market-authoring.js +1 -1
- package/dist/commands/market-authoring.js.map +1 -1
- package/dist/commands/market.d.ts +89 -31
- package/dist/commands/market.js +215 -222
- package/dist/commands/market.js.map +1 -1
- package/dist/commands/mcp.d.ts +1 -1
- package/dist/commands/mcp.js +14 -9
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/observe.category.test.d.ts +1 -0
- package/dist/commands/observe.category.test.js +59 -0
- package/dist/commands/observe.category.test.js.map +1 -0
- package/dist/commands/observe.d.ts +3 -0
- package/dist/commands/observe.js +7 -5
- package/dist/commands/observe.js.map +1 -1
- package/dist/commands/observe.title.test.d.ts +1 -0
- package/dist/commands/observe.title.test.js +37 -0
- package/dist/commands/observe.title.test.js.map +1 -0
- package/dist/commands/playbook.js +9 -0
- package/dist/commands/playbook.js.map +1 -1
- package/dist/commands/pods.d.ts +6 -1
- package/dist/commands/pods.js +38 -17
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.js +7 -1
- package/dist/commands/proposals-actions.js.map +1 -1
- package/dist/commands/proton-setup.js +3 -1
- package/dist/commands/proton-setup.js.map +1 -1
- package/dist/commands/providers.d.ts +22 -6
- package/dist/commands/providers.js +39 -32
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/raycast.d.ts +15 -0
- package/dist/commands/raycast.js +36 -10
- package/dist/commands/raycast.js.map +1 -1
- package/dist/commands/session-feed-wait.d.ts +48 -0
- package/dist/commands/session-feed-wait.js +261 -0
- package/dist/commands/session-feed-wait.js.map +1 -0
- package/dist/commands/session-post.d.ts +40 -0
- package/dist/commands/session-post.js +96 -0
- package/dist/commands/session-post.js.map +1 -0
- package/dist/commands/session-wait.d.ts +9 -4
- package/dist/commands/session-wait.js +20 -5
- package/dist/commands/session-wait.js.map +1 -1
- package/dist/commands/sessions.d.ts +2 -0
- package/dist/commands/sessions.js +23 -0
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/skills-commands.d.ts +2 -0
- package/dist/commands/skills-commands.js +9 -1
- package/dist/commands/skills-commands.js.map +1 -1
- package/dist/commands/skills-commands.test.d.ts +1 -0
- package/dist/commands/skills-commands.test.js +57 -0
- package/dist/commands/skills-commands.test.js.map +1 -0
- package/dist/commands/status.js +13 -7
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/statusline.backoff.test.js +13 -3
- package/dist/commands/statusline.backoff.test.js.map +1 -1
- package/dist/commands/statusline.d.ts +104 -9
- package/dist/commands/statusline.js +392 -148
- package/dist/commands/statusline.js.map +1 -1
- package/dist/commands/statusline.line2.test.d.ts +15 -0
- package/dist/commands/statusline.line2.test.js +226 -0
- package/dist/commands/statusline.line2.test.js.map +1 -0
- package/dist/commands/statusline.needs-you.test.d.ts +1 -0
- package/dist/commands/statusline.needs-you.test.js +17 -0
- package/dist/commands/statusline.needs-you.test.js.map +1 -0
- package/dist/commands/statusline.now.test.d.ts +10 -0
- package/dist/commands/statusline.now.test.js +250 -0
- package/dist/commands/statusline.now.test.js.map +1 -0
- package/dist/commands/tracks.d.ts +68 -6
- package/dist/commands/tracks.js +272 -16
- package/dist/commands/tracks.js.map +1 -1
- package/dist/commands/upload.js +12 -0
- package/dist/commands/upload.js.map +1 -1
- package/dist/commands/wait.d.ts +124 -0
- package/dist/commands/wait.js +384 -0
- package/dist/commands/wait.js.map +1 -0
- package/dist/commands/wait.test.d.ts +8 -0
- package/dist/commands/wait.test.js +308 -0
- package/dist/commands/wait.test.js.map +1 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/worker-accounts.d.ts +149 -0
- package/dist/commands/worker-accounts.js +339 -0
- package/dist/commands/worker-accounts.js.map +1 -0
- package/dist/commands/worker-accounts.test.d.ts +1 -0
- package/dist/commands/worker-accounts.test.js +224 -0
- package/dist/commands/worker-accounts.test.js.map +1 -0
- package/dist/commands/worker-github-app.d.ts +63 -0
- package/dist/commands/worker-github-app.js +161 -0
- package/dist/commands/worker-github-app.js.map +1 -0
- package/dist/commands/worker-github-app.test.d.ts +1 -0
- package/dist/commands/worker-github-app.test.js +102 -0
- package/dist/commands/worker-github-app.test.js.map +1 -0
- package/dist/commands/worker-github-broker.d.ts +39 -0
- package/dist/commands/worker-github-broker.js +106 -0
- package/dist/commands/worker-github-broker.js.map +1 -0
- package/dist/commands/worker-github-broker.test.d.ts +1 -0
- package/dist/commands/worker-github-broker.test.js +90 -0
- package/dist/commands/worker-github-broker.test.js.map +1 -0
- package/dist/commands/worker-runs.test.d.ts +1 -0
- package/dist/commands/worker-runs.test.js +150 -0
- package/dist/commands/worker-runs.test.js.map +1 -0
- package/dist/commands/worker-worktrees.d.ts +34 -0
- package/dist/commands/worker-worktrees.js +87 -0
- package/dist/commands/worker-worktrees.js.map +1 -0
- package/dist/commands/worker.d.ts +406 -10
- package/dist/commands/worker.js +1180 -126
- package/dist/commands/worker.js.map +1 -1
- package/dist/commands/worker.test.js +588 -11
- package/dist/commands/worker.test.js.map +1 -1
- package/dist/index.js +220 -28
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-door.d.ts +1 -3
- package/dist/lib/agent-door.js +10 -7
- package/dist/lib/agent-door.js.map +1 -1
- package/dist/lib/agents-config.d.ts +9 -0
- package/dist/lib/agents-config.js +20 -0
- package/dist/lib/agents-config.js.map +1 -1
- package/dist/lib/atomic-file.d.ts +20 -1
- package/dist/lib/atomic-file.js +100 -2
- package/dist/lib/atomic-file.js.map +1 -1
- package/dist/lib/auth.d.ts +28 -2
- package/dist/lib/auth.js +52 -12
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/box/egress.d.ts +82 -0
- package/dist/lib/box/egress.js +264 -0
- package/dist/lib/box/egress.js.map +1 -0
- package/dist/lib/box/evidence.d.ts +86 -0
- package/dist/lib/box/evidence.js +121 -0
- package/dist/lib/box/evidence.js.map +1 -0
- package/dist/lib/box/job-server.d.ts +72 -0
- package/dist/lib/box/job-server.js +250 -0
- package/dist/lib/box/job-server.js.map +1 -0
- package/dist/lib/box/provisioner.d.ts +83 -0
- package/dist/lib/box/provisioner.js +312 -0
- package/dist/lib/box/provisioner.js.map +1 -0
- package/dist/lib/box/proxmox.d.ts +112 -0
- package/dist/lib/box/proxmox.js +211 -0
- package/dist/lib/box/proxmox.js.map +1 -0
- package/dist/lib/box/wire.d.ts +98 -0
- package/dist/lib/box/wire.js +70 -0
- package/dist/lib/box/wire.js.map +1 -0
- package/dist/lib/bridge-invocation.test.d.ts +1 -0
- package/dist/lib/bridge-invocation.test.js +75 -0
- package/dist/lib/bridge-invocation.test.js.map +1 -0
- package/dist/lib/bridge-status.d.ts +144 -0
- package/dist/lib/bridge-status.js +227 -0
- package/dist/lib/bridge-status.js.map +1 -0
- package/dist/lib/bridge-status.test.d.ts +6 -0
- package/dist/lib/bridge-status.test.js +197 -0
- package/dist/lib/bridge-status.test.js.map +1 -0
- package/dist/lib/client-header.test.d.ts +1 -0
- package/dist/lib/client-header.test.js +103 -0
- package/dist/lib/client-header.test.js.map +1 -0
- package/dist/lib/conversation-headers.d.ts +89 -9
- package/dist/lib/conversation-headers.js +156 -3
- package/dist/lib/conversation-headers.js.map +1 -1
- package/dist/lib/conversation-headers.test.d.ts +1 -0
- package/dist/lib/conversation-headers.test.js +78 -0
- package/dist/lib/conversation-headers.test.js.map +1 -0
- package/dist/lib/credential-class.d.ts +40 -2
- package/dist/lib/credential-class.js +48 -6
- package/dist/lib/credential-class.js.map +1 -1
- package/dist/lib/device-connect.d.ts +45 -0
- package/dist/lib/device-connect.js +60 -0
- package/dist/lib/device-connect.js.map +1 -1
- package/dist/lib/exporter-coverage.d.ts +1 -1
- package/dist/lib/exporter-coverage.js +1 -1
- package/dist/lib/goal-words.d.ts +38 -0
- package/dist/lib/goal-words.js +115 -0
- package/dist/lib/goal-words.js.map +1 -0
- package/dist/lib/hub-client.d.ts +87 -8
- package/dist/lib/hub-client.js +205 -70
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/init-handoff.d.ts +8 -0
- package/dist/lib/init-handoff.js +9 -2
- package/dist/lib/init-handoff.js.map +1 -1
- package/dist/lib/key-locations.d.ts +3 -3
- package/dist/lib/key-locations.js +3 -39
- package/dist/lib/key-locations.js.map +1 -1
- package/dist/lib/key-source.d.ts +3 -1
- package/dist/lib/key-source.js +7 -4
- package/dist/lib/key-source.js.map +1 -1
- package/dist/lib/knowledge-title.d.ts +3 -2
- package/dist/lib/knowledge-title.js +3 -2
- package/dist/lib/knowledge-title.js.map +1 -1
- package/dist/lib/machine-instance.d.ts +7 -5
- package/dist/lib/machine-instance.js +7 -5
- package/dist/lib/machine-instance.js.map +1 -1
- package/dist/lib/mcp-bridge.d.ts +50 -0
- package/dist/lib/mcp-bridge.js +232 -11
- package/dist/lib/mcp-bridge.js.map +1 -1
- package/dist/lib/mcp-bridge.test.js +224 -4
- package/dist/lib/mcp-bridge.test.js.map +1 -1
- package/dist/lib/mcp-call.d.ts +5 -1
- package/dist/lib/mcp-call.js +7 -6
- package/dist/lib/mcp-call.js.map +1 -1
- package/dist/lib/mcp-connector-url.test.d.ts +1 -0
- package/dist/lib/mcp-connector-url.test.js +13 -0
- package/dist/lib/mcp-connector-url.test.js.map +1 -0
- package/dist/lib/mcp-snippets.d.ts +16 -0
- package/dist/lib/mcp-snippets.js +20 -2
- package/dist/lib/mcp-snippets.js.map +1 -1
- package/dist/lib/now-line.d.ts +66 -0
- package/dist/lib/now-line.js +193 -0
- package/dist/lib/now-line.js.map +1 -0
- package/dist/lib/now-line.test.d.ts +5 -0
- package/dist/lib/now-line.test.js +177 -0
- package/dist/lib/now-line.test.js.map +1 -0
- package/dist/lib/package-updates.d.ts +66 -0
- package/dist/lib/package-updates.js +76 -0
- package/dist/lib/package-updates.js.map +1 -0
- package/dist/lib/pod-moved.d.ts +70 -0
- package/dist/lib/pod-moved.js +146 -0
- package/dist/lib/pod-moved.js.map +1 -0
- package/dist/lib/pod-reach.d.ts +39 -0
- package/dist/lib/pod-reach.js +117 -0
- package/dist/lib/pod-reach.js.map +1 -0
- package/dist/lib/pod-reach.test.d.ts +1 -0
- package/dist/lib/pod-reach.test.js +67 -0
- package/dist/lib/pod-reach.test.js.map +1 -0
- package/dist/lib/pod.d.ts +8 -4
- package/dist/lib/pod.js +12 -10
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/register-claude-code-mcp.test.d.ts +1 -0
- package/dist/lib/register-claude-code-mcp.test.js +97 -0
- package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
- package/dist/lib/review-credential.js +3 -1
- package/dist/lib/review-credential.js.map +1 -1
- package/dist/lib/safe-open.d.ts +5 -3
- package/dist/lib/safe-open.js +9 -8
- package/dist/lib/safe-open.js.map +1 -1
- package/dist/lib/self-host.d.ts +17 -0
- package/dist/lib/self-host.js +24 -0
- package/dist/lib/self-host.js.map +1 -0
- package/dist/lib/session-feed.d.ts +185 -0
- package/dist/lib/session-feed.js +257 -0
- package/dist/lib/session-feed.js.map +1 -0
- package/dist/lib/targets.d.ts +52 -3
- package/dist/lib/targets.js +150 -209
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/template-lifecycle.d.ts +0 -31
- package/dist/lib/template-lifecycle.js +0 -74
- package/dist/lib/template-lifecycle.js.map +1 -1
- package/dist/lib/term.d.ts +24 -0
- package/dist/lib/term.js +52 -0
- package/dist/lib/term.js.map +1 -0
- package/dist/lib/term.test.d.ts +1 -0
- package/dist/lib/term.test.js +29 -0
- package/dist/lib/term.test.js.map +1 -0
- package/dist/lib/transcript-index.d.ts +94 -0
- package/dist/lib/transcript-index.js +321 -0
- package/dist/lib/transcript-index.js.map +1 -0
- package/dist/lib/transcript-index.test.d.ts +10 -0
- package/dist/lib/transcript-index.test.js +200 -0
- package/dist/lib/transcript-index.test.js.map +1 -0
- package/dist/lib/wait-help.d.ts +11 -0
- package/dist/lib/wait-help.js +25 -0
- package/dist/lib/wait-help.js.map +1 -0
- package/dist/lib/wait-state.d.ts +57 -0
- package/dist/lib/wait-state.js +114 -0
- package/dist/lib/wait-state.js.map +1 -0
- package/dist/lib/wake-hook-settings.d.ts +57 -0
- package/dist/lib/wake-hook-settings.js +153 -0
- package/dist/lib/wake-hook-settings.js.map +1 -0
- package/dist/lib/wake-state.d.ts +65 -0
- package/dist/lib/wake-state.js +122 -0
- package/dist/lib/wake-state.js.map +1 -0
- package/package.json +5 -2
- package/skills/README.md +1 -1
- package/skills/agent-os/SKILL.md +8 -8
- package/skills/synap/SKILL.md +188 -94
- package/skills/synap/code-skills.md +84 -0
- package/skills/synap/concepts.md +38 -20
- package/skills/synap/creative-loop.md +1 -1
- package/skills/synap/document-embeds.md +17 -1
- package/skills/synap/focus-sessions.md +17 -8
- package/skills/synap/from-intent.md +33 -17
- package/skills/synap/governance-rules.md +3 -1
- package/skills/synap/hand-off.md +10 -6
- package/skills/synap/lenses.md +4 -4
- package/skills/synap/linking.md +2 -2
- package/skills/synap/mental-model.md +1 -1
- package/skills/synap/operating-mode-rules.md +1 -1
- package/skills/synap/quick-reference.md +2 -1
- package/skills/synap/reflexes.md +8 -8
- package/skills/synap/viewframe-cells.md +1 -1
- package/skills/synap/work-model.md +24 -10
- package/skills/synap/workspace-design.md +5 -5
- package/skills/synap/writes.md +16 -9
- package/skills/synap/writing-titles.md +7 -0
- package/skills/synap-app/SKILL.md +148 -66
- package/skills/synap-app/creating-the-base-app.md +12 -7
- package/skills/synap-app/governance.md +27 -16
- package/skills/synap-app/install-commands.md +18 -9
- package/skills/synap-app/overview.md +16 -9
- package/skills/synap-app/using-the-sdk.md +75 -25
- package/skills/synap-market/SKILL.md +1 -1
- package/skills/synap-market/governance-and-catalog.md +1 -1
- package/skills/synap-schema/SKILL.md +24 -21
- package/skills/synap-schema/intent-vocabulary-ssot.md +24 -21
- package/templates/box/runner.py +206 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every call names its client program (`X-Synap-Client: <name>/<version>`), so
|
|
3
|
+
* the pod's usage telemetry can tell the CLI, its stdio MCP bridge and the
|
|
4
|
+
* synap-bridge mod apart on one key and one conversation.
|
|
5
|
+
*
|
|
6
|
+
* Driven through the real doors: `hubGet` (every Hub call goes through
|
|
7
|
+
* `sessionHeaders`), the real bridge pump, and the `synap mcp bridge` command.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { PassThrough } from "node:stream";
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
const captured = vi.hoisted(() => ({ opts: undefined }));
|
|
13
|
+
vi.mock("./mcp-bridge.js", async (orig) => {
|
|
14
|
+
const real = await orig();
|
|
15
|
+
return {
|
|
16
|
+
...real,
|
|
17
|
+
runMcpBridge: vi.fn(async (opts) => {
|
|
18
|
+
captured.opts = opts;
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
import { hubGet } from "./hub-client.js";
|
|
23
|
+
import { mcpBridgeHeaders, CLIENT_HEADER, RUNNER_HEADER, claudeCodeProcess, clientHeader } from "./conversation-headers.js";
|
|
24
|
+
import { mcpBridge } from "../commands/mcp.js";
|
|
25
|
+
const VERSION = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.unstubAllGlobals();
|
|
28
|
+
vi.unstubAllEnvs();
|
|
29
|
+
});
|
|
30
|
+
describe("X-Synap-Client", () => {
|
|
31
|
+
it("names the package version; an unusable version says unknown", () => {
|
|
32
|
+
expect(VERSION).toMatch(/^\d+\.\d+\.\d+/);
|
|
33
|
+
expect(clientHeader("synap-cli")).toEqual({ [CLIENT_HEADER]: `synap-cli/${VERSION}` });
|
|
34
|
+
expect(clientHeader("synap-cli", "1 2")).toEqual({ [CLIENT_HEADER]: "synap-cli/unknown" });
|
|
35
|
+
});
|
|
36
|
+
it("every Hub call sends synap-cli/<version>", async () => {
|
|
37
|
+
const seen = [];
|
|
38
|
+
vi.stubGlobal("fetch", vi.fn(async (_url, init) => {
|
|
39
|
+
seen.push(init.headers);
|
|
40
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
41
|
+
status: 200,
|
|
42
|
+
headers: { "content-type": "application/json" },
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
await hubGet("/workspaces", {}, { podUrl: "https://pod.example", apiKey: "k" });
|
|
46
|
+
expect(seen).toHaveLength(1);
|
|
47
|
+
expect(seen[0][CLIENT_HEADER]).toBe(`synap-cli/${VERSION}`);
|
|
48
|
+
// The worker's served stamp is the worker poll's alone: an ordinary command never claims it.
|
|
49
|
+
expect(seen[0][RUNNER_HEADER]).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
it("the stdio MCP bridge sends synap-cli-mcp/<version> on every request", async () => {
|
|
52
|
+
const { runMcpBridge } = await vi.importActual("./mcp-bridge.js");
|
|
53
|
+
const input = new PassThrough();
|
|
54
|
+
const output = new PassThrough();
|
|
55
|
+
const sent = [];
|
|
56
|
+
const fetchImpl = (async (_u, init) => {
|
|
57
|
+
sent.push(init.headers);
|
|
58
|
+
return new Response(JSON.stringify({ jsonrpc: "2.0", id: sent.length, result: {} }), {
|
|
59
|
+
status: 200,
|
|
60
|
+
headers: { "content-type": "application/json" },
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
const done = runMcpBridge({
|
|
64
|
+
url: "https://pod/mcp",
|
|
65
|
+
apiKey: "k",
|
|
66
|
+
headers: mcpBridgeHeaders(undefined, "/x"),
|
|
67
|
+
input,
|
|
68
|
+
output,
|
|
69
|
+
fetchImpl,
|
|
70
|
+
log: () => { },
|
|
71
|
+
});
|
|
72
|
+
input.write(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }) + "\n");
|
|
73
|
+
input.write(JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" }) + "\n");
|
|
74
|
+
input.end();
|
|
75
|
+
await done;
|
|
76
|
+
expect(sent.length).toBe(2);
|
|
77
|
+
for (const h of sent)
|
|
78
|
+
expect(h[CLIENT_HEADER]).toBe(`synap-cli-mcp/${VERSION}`);
|
|
79
|
+
for (const h of sent)
|
|
80
|
+
expect(h[RUNNER_HEADER]).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
it("`synap mcp bridge` hands the bridge those headers", async () => {
|
|
83
|
+
vi.stubEnv("SYNAP_HUB_API_KEY", "k");
|
|
84
|
+
await mcpBridge({ url: "https://pod/mcp" });
|
|
85
|
+
expect(captured.opts?.headers?.[CLIENT_HEADER]).toBe(`synap-cli-mcp/${VERSION}`);
|
|
86
|
+
expect(captured.opts?.headers?.["X-Synap-Conversation"]).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
it("`synap mcp bridge` inside Claude Code names its parent process (plan C2); outside it names none", async () => {
|
|
89
|
+
vi.stubEnv("SYNAP_HUB_API_KEY", "k");
|
|
90
|
+
vi.stubEnv("CLAUDE_CODE_SESSION_ID", "8c34a72b-b1c1-4c37-9c1f-02920b69ccb2");
|
|
91
|
+
await mcpBridge({ url: "https://pod/mcp" });
|
|
92
|
+
const sent = captured.opts?.headers?.["X-Synap-Process"];
|
|
93
|
+
// This runner's own parent stands for Claude Code; a host without ps sends none.
|
|
94
|
+
if (sent !== undefined)
|
|
95
|
+
expect(sent).toMatch(new RegExp(`^[A-Za-z0-9._-]+:${process.ppid}:\\d{13}$`));
|
|
96
|
+
else
|
|
97
|
+
expect(claudeCodeProcess()).toBeUndefined();
|
|
98
|
+
vi.stubEnv("CLAUDE_CODE_SESSION_ID", "");
|
|
99
|
+
await mcpBridge({ url: "https://pod/mcp" });
|
|
100
|
+
expect(captured.opts?.headers?.["X-Synap-Process"]).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=client-header.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-header.test.js","sourceRoot":"","sources":["../../src/lib/client-header.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,98 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The headers that name the calling CONVERSATION to the pod — the agent's own
|
|
3
|
-
* id, the one a person can reopen (`claude --resume <id>`). The pod reads
|
|
4
|
-
* `X-Synap-Conversation` as the conversation half of the client key, so every
|
|
5
|
-
* session the conversation touches records it (`resolveAgentConversation`,
|
|
6
|
-
* @synap-core/types/agents). Sent by every Hub call (`hub-client.ts`) and by
|
|
7
|
-
* the stdio MCP bridge (`mcp-bridge.ts`). Empty outside Claude Code.
|
|
8
|
-
*/
|
|
9
1
|
export declare const CONVERSATION_HEADER = "X-Synap-Conversation";
|
|
10
2
|
export declare const CONVERSATION_CWD_HEADER = "X-Synap-Conversation-Cwd";
|
|
3
|
+
/**
|
|
4
|
+
* Which CLIENT program makes the call, `<name>/<version>`: `synap-cli/<v>` for
|
|
5
|
+
* the CLI's Hub calls, `synap-cli-mcp/<v>` for the stdio MCP bridge (and
|
|
6
|
+
* `synap-bridge/<v>` from the Claude Code mod). The pod counts calls per client
|
|
7
|
+
* in its usage telemetry, so two programs sharing one key and one conversation
|
|
8
|
+
* can be told apart. Never an identity: it only labels the count.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CLIENT_HEADER = "X-Synap-Client";
|
|
11
|
+
export type SynapClientName = "synap-cli" | "synap-cli-mcp";
|
|
12
|
+
/** `{ "X-Synap-Client": "<name>/<version>" }`; the version is the package's. */
|
|
13
|
+
export declare function clientHeader(name: SynapClientName, version?: string): Record<string, string>;
|
|
14
|
+
/**
|
|
15
|
+
* The worker's stamp, `synap-worker/<version>`: sent ONLY by `synap worker` on
|
|
16
|
+
* its own orient poll. The pod records, per key, the last time a request
|
|
17
|
+
* carried it (`servedAt`): proof that a process serves that agent, which a key
|
|
18
|
+
* merely being used is not. Interactive commands and the MCP bridge never send
|
|
19
|
+
* it, or every use of a key would read as "a worker runs".
|
|
20
|
+
*/
|
|
21
|
+
export declare const RUNNER_HEADER = "X-Synap-Runner";
|
|
22
|
+
/** `{ "X-Synap-Runner": "synap-worker/<version>" }`; the version is the package's. */
|
|
23
|
+
export declare function workerRunnerHeader(version?: string): Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Which OS process of a conversation calls (`<host>:<pid>:<startedAtMs>`), and
|
|
26
|
+
* what that runner is doing (`busy` | `idle` | `needs_permission` |
|
|
27
|
+
* `needs_answer` (a question or a plan dialog) | `ended`).
|
|
28
|
+
* The pod records both on the conversation's `session_runners` rows; a pod
|
|
29
|
+
* that reads neither ignores them. synap-bridge sends the state the same way.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PROCESS_HEADER = "X-Synap-Process";
|
|
32
|
+
export declare const RUNNER_STATE_HEADER = "X-Synap-Runner-State";
|
|
33
|
+
/**
|
|
34
|
+
* Which MODEL a run uses and which PROVIDER serves it: facts the client
|
|
35
|
+
* reports, never identity (pod decision 5e7c77dc). The pod keeps a provider
|
|
36
|
+
* slug it knows (anthropic, bedrock, vertex, foundry, openai, openrouter, zai,
|
|
37
|
+
* deepseek) and reads any other word as `other`; it drops a model id that is
|
|
38
|
+
* not an id. Send nothing rather than a guess: absent reads unknown, never
|
|
39
|
+
* "anthropic". Never send a URL. A pod that reads neither ignores them.
|
|
40
|
+
*/
|
|
41
|
+
export declare const MODEL_HEADER = "X-Synap-Model";
|
|
42
|
+
export declare const MODEL_PROVIDER_HEADER = "X-Synap-Model-Provider";
|
|
43
|
+
/** A model id the pod will keep, else undefined (`<synthetic>`, a URL, junk). */
|
|
44
|
+
export declare function reportableModel(model: string | undefined): string | undefined;
|
|
45
|
+
/** `{ X-Synap-Model?, X-Synap-Model-Provider? }`: each only when known. */
|
|
46
|
+
export declare function modelHeaders(model: string | undefined, provider: string | undefined): Record<string, string>;
|
|
47
|
+
/**
|
|
48
|
+
* THE process identity format, `<host>:<pid>:<startedAtMs>` (the pod's
|
|
49
|
+
* `parseProcessIdentity`): the host with every character outside
|
|
50
|
+
* `[A-Za-z0-9._-]` made `-`, at most 255, `computer` when empty. `synap worker`
|
|
51
|
+
* (`runProcessIdentity`), the stdio MCP bridge (`claudeCodeProcess`) and the
|
|
52
|
+
* synap-bridge mod (`processIdentity`, `hooks/lib.ts`, byte for byte, tested on
|
|
53
|
+
* the same fixture) all format through it, so one Claude Code process is one
|
|
54
|
+
* runner row on the pod.
|
|
55
|
+
*/
|
|
56
|
+
export declare function processIdentity(hostname: string, pid: number, startedAtMs: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* `ps -o lstart=` read in UTC with the C locale (`Mon Nov 2 13:09:07 2026`,
|
|
59
|
+
* BSD and procps alike) -> epoch ms; undefined for anything else. Parsed field
|
|
60
|
+
* by field, never by `Date.parse`, as the synap-bridge mod parses it.
|
|
61
|
+
*/
|
|
62
|
+
export declare function lstartMs(text: string): number | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* The Claude Code process this stdio MCP bridge serves: Claude Code spawns it
|
|
65
|
+
* directly, so it is the parent (`process.ppid`), started when `ps` says. Read
|
|
66
|
+
* once, at the bridge's start. Undefined when it cannot be read (no `ps`, an
|
|
67
|
+
* orphan): the header is then not sent, never guessed.
|
|
68
|
+
*/
|
|
69
|
+
export declare function claudeCodeProcess(read?: {
|
|
70
|
+
ppid?: number;
|
|
71
|
+
hostname?: string;
|
|
72
|
+
startMs?: (pid: number) => number | undefined;
|
|
73
|
+
}): string | undefined;
|
|
74
|
+
/** The same two values in a request's `params._meta` (reverse-DNS vendor keys; `io.modelcontextprotocol/` is reserved). */
|
|
75
|
+
export declare const CONVERSATION_META_KEY = "live.synap/conversation";
|
|
76
|
+
export declare const CONVERSATION_CWD_META_KEY = "live.synap/conversationCwd";
|
|
11
77
|
/** `claude-code:<uuid>` for the Claude Code conversation we run in, if any. */
|
|
12
78
|
export declare function claudeCodeConversation(sessionId: string | undefined): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* `synap-cli:<id>` for a run outside Claude Code: `SYNAP_RUN_ID` when the caller
|
|
81
|
+
* names one (a multi-command script), else a UUID minted once per process. An
|
|
82
|
+
* unusable `SYNAP_RUN_ID` falls back to the process id rather than failing.
|
|
83
|
+
*/
|
|
84
|
+
export declare function cliConversation(env?: NodeJS.ProcessEnv): string;
|
|
13
85
|
/**
|
|
14
86
|
* The headers for `sessionId` (pass it explicitly: the CLI's live id is
|
|
15
87
|
* `getClaudeSessionId()`, a stdio server's is its spawn env). Empty without a
|
|
16
|
-
* real Claude Code id
|
|
88
|
+
* real Claude Code id the run names itself (`cliConversation`); the cwd rides
|
|
89
|
+
* only with a Claude Code conversation (the one a person can reopen).
|
|
17
90
|
*/
|
|
18
91
|
export declare function conversationHeaders(sessionId: string | undefined, cwd?: string): Record<string, string>;
|
|
92
|
+
/**
|
|
93
|
+
* Every header the stdio MCP bridge sends: its client name, the conversation,
|
|
94
|
+
* and, with a Claude Code conversation, the Claude Code process it runs in
|
|
95
|
+
* (`claudeCodeProcess`, the same value the synap-bridge mod sends), so the
|
|
96
|
+
* pod's runner rows of that process are one, whichever door called.
|
|
97
|
+
*/
|
|
98
|
+
export declare function mcpBridgeHeaders(sessionId: string | undefined, cwd?: string, processId?: string): Record<string, string>;
|
|
@@ -4,29 +4,182 @@
|
|
|
4
4
|
* `X-Synap-Conversation` as the conversation half of the client key, so every
|
|
5
5
|
* session the conversation touches records it (`resolveAgentConversation`,
|
|
6
6
|
* @synap-core/types/agents). Sent by every Hub call (`hub-client.ts`) and by
|
|
7
|
-
* the stdio MCP bridge (`mcp-bridge.ts`).
|
|
7
|
+
* the stdio MCP bridge (`mcp-bridge.ts`). Outside Claude Code the conversation is
|
|
8
|
+
* this process's own run (`synap-cli:<uuid>`, or `SYNAP_RUN_ID` to span a script).
|
|
8
9
|
*/
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { cliBuild } from "./cli-build.js";
|
|
9
14
|
export const CONVERSATION_HEADER = "X-Synap-Conversation";
|
|
10
15
|
export const CONVERSATION_CWD_HEADER = "X-Synap-Conversation-Cwd";
|
|
16
|
+
/**
|
|
17
|
+
* Which CLIENT program makes the call, `<name>/<version>`: `synap-cli/<v>` for
|
|
18
|
+
* the CLI's Hub calls, `synap-cli-mcp/<v>` for the stdio MCP bridge (and
|
|
19
|
+
* `synap-bridge/<v>` from the Claude Code mod). The pod counts calls per client
|
|
20
|
+
* in its usage telemetry, so two programs sharing one key and one conversation
|
|
21
|
+
* can be told apart. Never an identity: it only labels the count.
|
|
22
|
+
*/
|
|
23
|
+
export const CLIENT_HEADER = "X-Synap-Client";
|
|
24
|
+
/** A header value must be visible ASCII: an odd version reads `unknown` and never fails the call. */
|
|
25
|
+
function headerVersion(version) {
|
|
26
|
+
return /^[\x21-\x7E]{1,64}$/.test(version) ? version : "unknown";
|
|
27
|
+
}
|
|
28
|
+
/** `{ "X-Synap-Client": "<name>/<version>" }`; the version is the package's. */
|
|
29
|
+
export function clientHeader(name, version = cliBuild().version) {
|
|
30
|
+
return { [CLIENT_HEADER]: `${name}/${headerVersion(version)}` };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The worker's stamp, `synap-worker/<version>`: sent ONLY by `synap worker` on
|
|
34
|
+
* its own orient poll. The pod records, per key, the last time a request
|
|
35
|
+
* carried it (`servedAt`): proof that a process serves that agent, which a key
|
|
36
|
+
* merely being used is not. Interactive commands and the MCP bridge never send
|
|
37
|
+
* it, or every use of a key would read as "a worker runs".
|
|
38
|
+
*/
|
|
39
|
+
export const RUNNER_HEADER = "X-Synap-Runner";
|
|
40
|
+
/** `{ "X-Synap-Runner": "synap-worker/<version>" }`; the version is the package's. */
|
|
41
|
+
export function workerRunnerHeader(version = cliBuild().version) {
|
|
42
|
+
return { [RUNNER_HEADER]: `synap-worker/${headerVersion(version)}` };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Which OS process of a conversation calls (`<host>:<pid>:<startedAtMs>`), and
|
|
46
|
+
* what that runner is doing (`busy` | `idle` | `needs_permission` |
|
|
47
|
+
* `needs_answer` (a question or a plan dialog) | `ended`).
|
|
48
|
+
* The pod records both on the conversation's `session_runners` rows; a pod
|
|
49
|
+
* that reads neither ignores them. synap-bridge sends the state the same way.
|
|
50
|
+
*/
|
|
51
|
+
export const PROCESS_HEADER = "X-Synap-Process";
|
|
52
|
+
export const RUNNER_STATE_HEADER = "X-Synap-Runner-State";
|
|
53
|
+
/**
|
|
54
|
+
* Which MODEL a run uses and which PROVIDER serves it: facts the client
|
|
55
|
+
* reports, never identity (pod decision 5e7c77dc). The pod keeps a provider
|
|
56
|
+
* slug it knows (anthropic, bedrock, vertex, foundry, openai, openrouter, zai,
|
|
57
|
+
* deepseek) and reads any other word as `other`; it drops a model id that is
|
|
58
|
+
* not an id. Send nothing rather than a guess: absent reads unknown, never
|
|
59
|
+
* "anthropic". Never send a URL. A pod that reads neither ignores them.
|
|
60
|
+
*/
|
|
61
|
+
export const MODEL_HEADER = "X-Synap-Model";
|
|
62
|
+
export const MODEL_PROVIDER_HEADER = "X-Synap-Model-Provider";
|
|
63
|
+
/** The pod's model-id rule (`parseReportedModel`, @synap-core/types/agents): an id, at most 128 chars, never a URL. */
|
|
64
|
+
const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]{0,127}$/;
|
|
65
|
+
/** A model id the pod will keep, else undefined (`<synthetic>`, a URL, junk). */
|
|
66
|
+
export function reportableModel(model) {
|
|
67
|
+
const id = model?.trim();
|
|
68
|
+
return id && MODEL_ID.test(id) && !id.includes("://") ? id : undefined;
|
|
69
|
+
}
|
|
70
|
+
/** `{ X-Synap-Model?, X-Synap-Model-Provider? }`: each only when known. */
|
|
71
|
+
export function modelHeaders(model, provider) {
|
|
72
|
+
const id = reportableModel(model);
|
|
73
|
+
const slug = provider?.trim().toLowerCase();
|
|
74
|
+
return {
|
|
75
|
+
...(id ? { [MODEL_HEADER]: id } : {}),
|
|
76
|
+
...(slug && /^[a-z0-9-]{1,32}$/.test(slug) ? { [MODEL_PROVIDER_HEADER]: slug } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* THE process identity format, `<host>:<pid>:<startedAtMs>` (the pod's
|
|
81
|
+
* `parseProcessIdentity`): the host with every character outside
|
|
82
|
+
* `[A-Za-z0-9._-]` made `-`, at most 255, `computer` when empty. `synap worker`
|
|
83
|
+
* (`runProcessIdentity`), the stdio MCP bridge (`claudeCodeProcess`) and the
|
|
84
|
+
* synap-bridge mod (`processIdentity`, `hooks/lib.ts`, byte for byte, tested on
|
|
85
|
+
* the same fixture) all format through it, so one Claude Code process is one
|
|
86
|
+
* runner row on the pod.
|
|
87
|
+
*/
|
|
88
|
+
export function processIdentity(hostname, pid, startedAtMs) {
|
|
89
|
+
const host = hostname.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 255) || "computer";
|
|
90
|
+
return `${host}:${pid}:${Math.trunc(startedAtMs)}`;
|
|
91
|
+
}
|
|
92
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
93
|
+
/**
|
|
94
|
+
* `ps -o lstart=` read in UTC with the C locale (`Mon Nov 2 13:09:07 2026`,
|
|
95
|
+
* BSD and procps alike) -> epoch ms; undefined for anything else. Parsed field
|
|
96
|
+
* by field, never by `Date.parse`, as the synap-bridge mod parses it.
|
|
97
|
+
*/
|
|
98
|
+
export function lstartMs(text) {
|
|
99
|
+
const m = /^[A-Z][a-z]{2} ([A-Z][a-z]{2}) +(\d{1,2}) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/.exec(text.trim());
|
|
100
|
+
if (!m)
|
|
101
|
+
return undefined;
|
|
102
|
+
const month = MONTHS.indexOf(m[1]);
|
|
103
|
+
if (month < 0)
|
|
104
|
+
return undefined;
|
|
105
|
+
const ms = Date.UTC(Number(m[6]), month, Number(m[2]), Number(m[3]), Number(m[4]), Number(m[5]));
|
|
106
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
107
|
+
}
|
|
108
|
+
/** When `pid` started, from `ps` in UTC with the C locale; undefined when it cannot be read. */
|
|
109
|
+
function psStartMs(pid) {
|
|
110
|
+
try {
|
|
111
|
+
const out = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
112
|
+
encoding: "utf8",
|
|
113
|
+
env: { ...process.env, LC_ALL: "C", TZ: "UTC" },
|
|
114
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
115
|
+
timeout: 2_000,
|
|
116
|
+
});
|
|
117
|
+
return lstartMs(out);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The Claude Code process this stdio MCP bridge serves: Claude Code spawns it
|
|
125
|
+
* directly, so it is the parent (`process.ppid`), started when `ps` says. Read
|
|
126
|
+
* once, at the bridge's start. Undefined when it cannot be read (no `ps`, an
|
|
127
|
+
* orphan): the header is then not sent, never guessed.
|
|
128
|
+
*/
|
|
129
|
+
export function claudeCodeProcess(read = {}) {
|
|
130
|
+
const pid = read.ppid ?? process.ppid;
|
|
131
|
+
if (!Number.isInteger(pid) || pid <= 1)
|
|
132
|
+
return undefined;
|
|
133
|
+
const startedAt = (read.startMs ?? psStartMs)(pid);
|
|
134
|
+
if (startedAt === undefined)
|
|
135
|
+
return undefined;
|
|
136
|
+
return processIdentity(read.hostname ?? os.hostname(), pid, startedAt);
|
|
137
|
+
}
|
|
138
|
+
/** The same two values in a request's `params._meta` (reverse-DNS vendor keys; `io.modelcontextprotocol/` is reserved). */
|
|
139
|
+
export const CONVERSATION_META_KEY = "live.synap/conversation";
|
|
140
|
+
export const CONVERSATION_CWD_META_KEY = "live.synap/conversationCwd";
|
|
11
141
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12
142
|
/** `claude-code:<uuid>` for the Claude Code conversation we run in, if any. */
|
|
13
143
|
export function claudeCodeConversation(sessionId) {
|
|
14
144
|
const id = sessionId?.trim();
|
|
15
145
|
return id && UUID.test(id) ? `claude-code:${id.toLowerCase()}` : undefined;
|
|
16
146
|
}
|
|
147
|
+
const RUN_ID = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
148
|
+
/** One id per process, so every Hub call of one CLI run lands in one conversation. */
|
|
149
|
+
const PROCESS_RUN_ID = randomUUID();
|
|
150
|
+
/**
|
|
151
|
+
* `synap-cli:<id>` for a run outside Claude Code: `SYNAP_RUN_ID` when the caller
|
|
152
|
+
* names one (a multi-command script), else a UUID minted once per process. An
|
|
153
|
+
* unusable `SYNAP_RUN_ID` falls back to the process id rather than failing.
|
|
154
|
+
*/
|
|
155
|
+
export function cliConversation(env = process.env) {
|
|
156
|
+
const named = env.SYNAP_RUN_ID?.trim();
|
|
157
|
+
return `synap-cli:${named && RUN_ID.test(named) ? named : PROCESS_RUN_ID}`;
|
|
158
|
+
}
|
|
17
159
|
/**
|
|
18
160
|
* The headers for `sessionId` (pass it explicitly: the CLI's live id is
|
|
19
161
|
* `getClaudeSessionId()`, a stdio server's is its spawn env). Empty without a
|
|
20
|
-
* real Claude Code id
|
|
162
|
+
* real Claude Code id the run names itself (`cliConversation`); the cwd rides
|
|
163
|
+
* only with a Claude Code conversation (the one a person can reopen).
|
|
21
164
|
*/
|
|
22
165
|
export function conversationHeaders(sessionId, cwd = process.cwd()) {
|
|
23
166
|
const conversation = claudeCodeConversation(sessionId);
|
|
24
167
|
if (!conversation)
|
|
25
|
-
return {};
|
|
168
|
+
return { [CONVERSATION_HEADER]: cliConversation() };
|
|
26
169
|
// HTTP header values must be ByteStrings; a non-ASCII cwd is dropped rather
|
|
27
170
|
// than letting fetch throw and take the whole request down with it.
|
|
28
171
|
return /^[\x20-\x7E]{1,1024}$/.test(cwd)
|
|
29
172
|
? { [CONVERSATION_HEADER]: conversation, [CONVERSATION_CWD_HEADER]: cwd }
|
|
30
173
|
: { [CONVERSATION_HEADER]: conversation };
|
|
31
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Every header the stdio MCP bridge sends: its client name, the conversation,
|
|
177
|
+
* and, with a Claude Code conversation, the Claude Code process it runs in
|
|
178
|
+
* (`claudeCodeProcess`, the same value the synap-bridge mod sends), so the
|
|
179
|
+
* pod's runner rows of that process are one, whichever door called.
|
|
180
|
+
*/
|
|
181
|
+
export function mcpBridgeHeaders(sessionId, cwd = process.cwd(), processId) {
|
|
182
|
+
const headers = { ...clientHeader("synap-cli-mcp"), ...conversationHeaders(sessionId, cwd) };
|
|
183
|
+
return processId && claudeCodeConversation(sessionId) ? { ...headers, [PROCESS_HEADER]: processId } : headers;
|
|
184
|
+
}
|
|
32
185
|
//# sourceMappingURL=conversation-headers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-headers.js","sourceRoot":"","sources":["../../src/lib/conversation-headers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"conversation-headers.js","sourceRoot":"","sources":["../../src/lib/conversation-headers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The process key (`X-Synap-Process`) the stdio MCP bridge sends for the
|
|
3
|
+
* Claude Code process it serves (sessions cockpit, plan 3884fadf C2). The
|
|
4
|
+
* synap-bridge mod sends the same key from inside Claude Code; the pod keys a
|
|
5
|
+
* runner row by it, so the two doors must format it byte for byte alike.
|
|
6
|
+
*
|
|
7
|
+
* ONE fixture, the same in synap-bridge (`tests/adapter.test.ts`,
|
|
8
|
+
* PROCESS_FIXTURE): host `devbox`, Claude Code's pid 4242, started
|
|
9
|
+
* Wed Oct 14 17:46:40 2026 UTC -> `devbox:4242:1792000000000`.
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
import { claudeCodeProcess, lstartMs, mcpBridgeHeaders, PROCESS_HEADER, processIdentity, } from "./conversation-headers.js";
|
|
14
|
+
import { runProcessIdentity } from "../commands/worker.js";
|
|
15
|
+
const PROCESS_FIXTURE = { host: "devbox", pid: 4242, lstart: "Wed Oct 14 17:46:40 2026", key: "devbox:4242:1792000000000" };
|
|
16
|
+
/** The pod's own check (`parseProcessIdentity`, backend request-write-context.ts). */
|
|
17
|
+
const POD_PROCESS_RE = /^([A-Za-z0-9._-]{1,255}):(\d{1,10}):(\d{10,14})$/;
|
|
18
|
+
const SID = "8c34a72b-b1c1-4c37-9c1f-02920b69ccb2";
|
|
19
|
+
describe("the process key, one format for every door", () => {
|
|
20
|
+
it("formats the shared fixture as synap-bridge does, from ps's UTC text", () => {
|
|
21
|
+
const startedAt = lstartMs(`${PROCESS_FIXTURE.lstart} \n`);
|
|
22
|
+
expect(startedAt).toBe(1_792_000_000_000);
|
|
23
|
+
expect(processIdentity(PROCESS_FIXTURE.host, PROCESS_FIXTURE.pid, startedAt)).toBe(PROCESS_FIXTURE.key);
|
|
24
|
+
expect(PROCESS_FIXTURE.key).toMatch(POD_PROCESS_RE);
|
|
25
|
+
});
|
|
26
|
+
it("is the worker's own format: runProcessIdentity goes through it", () => {
|
|
27
|
+
expect(runProcessIdentity(1_792_000_000_000, "devbox", 1)).toBe(processIdentity("devbox", 1, 1_792_000_000_000));
|
|
28
|
+
expect(processIdentity("MacBook Pro (2).local", 7, 1_792_000_000_000.9)).toBe("MacBook-Pro--2-.local:7:1792000000000");
|
|
29
|
+
expect(processIdentity("", 7, 1_792_000_000_000)).toBe("computer:7:1792000000000");
|
|
30
|
+
});
|
|
31
|
+
it("reads ps's padded day; a local, translated or partial text is no time, never a guess", () => {
|
|
32
|
+
expect(lstartMs("Mon Nov 2 13:09:07 2026")).toBe(Date.UTC(2026, 10, 2, 13, 9, 7));
|
|
33
|
+
for (const bad of ["lun. 2 nov 13:09:07 2026", "Nov 2 13:09:07 2026", "Mon Foo 2 13:09:07 2026", "2026-11-02T13:09:07Z", ""]) {
|
|
34
|
+
expect(lstartMs(bad)).toBeUndefined();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("claudeCodeProcess: the parent the stdio bridge serves", () => {
|
|
39
|
+
it("is the parent's pid and start on this host", () => {
|
|
40
|
+
const asked = [];
|
|
41
|
+
const key = claudeCodeProcess({
|
|
42
|
+
ppid: PROCESS_FIXTURE.pid,
|
|
43
|
+
hostname: PROCESS_FIXTURE.host,
|
|
44
|
+
startMs: (pid) => (asked.push(pid), lstartMs(PROCESS_FIXTURE.lstart)),
|
|
45
|
+
});
|
|
46
|
+
expect(key).toBe(PROCESS_FIXTURE.key);
|
|
47
|
+
expect(asked).toEqual([PROCESS_FIXTURE.pid]);
|
|
48
|
+
});
|
|
49
|
+
it("is undefined for an orphan or a start ps could not give", () => {
|
|
50
|
+
expect(claudeCodeProcess({ ppid: 1, hostname: "devbox", startMs: () => 1_792_000_000_000 })).toBeUndefined();
|
|
51
|
+
expect(claudeCodeProcess({ ppid: 4242, hostname: "devbox", startMs: () => undefined })).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
it("reads the real parent through ps (this test runner's own parent), its start in UTC whatever the zone", () => {
|
|
54
|
+
const key = claudeCodeProcess();
|
|
55
|
+
// A CI sandbox without ps reads undefined, never a made-up key.
|
|
56
|
+
if (key === undefined)
|
|
57
|
+
return;
|
|
58
|
+
expect(key).toMatch(new RegExp(`^[A-Za-z0-9._-]+:${process.ppid}:\\d{13}$`));
|
|
59
|
+
// An independent reading: now minus ps's elapsed time ([[dd-]hh:]mm:ss), which no time zone moves.
|
|
60
|
+
const etime = execFileSync("ps", ["-o", "etime=", "-p", String(process.ppid)], { encoding: "utf8" }).trim();
|
|
61
|
+
const [days, clock] = etime.includes("-") ? etime.split("-") : ["0", etime];
|
|
62
|
+
const elapsedS = clock.split(":").map(Number).reduce((acc, n) => acc * 60 + n, 0) + Number(days) * 86_400;
|
|
63
|
+
const startedAt = Number(key.split(":").at(-1));
|
|
64
|
+
expect(Math.abs(startedAt - (Date.now() - elapsedS * 1000))).toBeLessThan(3_000);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("mcpBridgeHeaders sends the process with a Claude Code conversation only", () => {
|
|
68
|
+
it("adds X-Synap-Process beside the conversation", () => {
|
|
69
|
+
const h = mcpBridgeHeaders(SID, "/x", PROCESS_FIXTURE.key);
|
|
70
|
+
expect(h[PROCESS_HEADER]).toBe(PROCESS_FIXTURE.key);
|
|
71
|
+
expect(h["X-Synap-Conversation"]).toBe(`claude-code:${SID}`);
|
|
72
|
+
});
|
|
73
|
+
it("sends none without a process, or outside Claude Code", () => {
|
|
74
|
+
expect(PROCESS_HEADER in mcpBridgeHeaders(SID, "/x")).toBe(false);
|
|
75
|
+
expect(PROCESS_HEADER in mcpBridgeHeaders(undefined, "/x", PROCESS_FIXTURE.key)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=conversation-headers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-headers.test.js","sourceRoot":"","sources":["../../src/lib/conversation-headers.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* blocked), classification is deliberately three-valued and the guard is
|
|
29
29
|
* belt-and-braces:
|
|
30
30
|
* • `agent` — positive agent signal → refuse locally, before the network call.
|
|
31
|
-
* • `human` — positive human signal (`user_pat`) → never blocked.
|
|
31
|
+
* • `human` — positive human signal (`user_pat` NOT owned by an agent user) → never blocked.
|
|
32
32
|
* • `unknown` — no signal → PROCEED, and translate the server's 403 after the
|
|
33
33
|
* fact (`isAgentReviewRejection`). The server stays authoritative.
|
|
34
34
|
*
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* self-approval / undo risk"). So `synap proposals reject` genuinely WORKS for
|
|
40
40
|
* an agent credential and must NOT be guarded here.
|
|
41
41
|
*/
|
|
42
|
-
import { type HubConfig } from "./hub-client.js";
|
|
42
|
+
import { HubError, type HubConfig } from "./hub-client.js";
|
|
43
43
|
/**
|
|
44
44
|
* Shape of `GET /api/hub/auth/status`. Mirrors the fields the route returns
|
|
45
45
|
* (`rest/auth.ts:140-157`). `keyType` and `workspaceId` are optional because
|
|
@@ -58,9 +58,36 @@ export interface AuthStatus {
|
|
|
58
58
|
isActive?: boolean;
|
|
59
59
|
keyType?: string | null;
|
|
60
60
|
workspaceId?: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* What the key may touch — its grant (pod ≥ owner-app-connect-key; absent
|
|
63
|
+
* on older pods, null when the pod read no grant for the request).
|
|
64
|
+
*/
|
|
65
|
+
access?: KeyAccess | null;
|
|
66
|
+
}
|
|
67
|
+
/** `/auth/status` `access`: the calling key's grant, ids and patterns only. */
|
|
68
|
+
export interface KeyAccess {
|
|
69
|
+
isFullAccess: boolean;
|
|
70
|
+
/** "full access", or what limits it ("limited to 1 space (<id>)"). */
|
|
71
|
+
summary: string;
|
|
72
|
+
scopes: Array<{
|
|
73
|
+
permissions: string[];
|
|
74
|
+
workspaceIds: string[] | null;
|
|
75
|
+
projectIds: string[] | null;
|
|
76
|
+
entityIds: string[] | null;
|
|
77
|
+
}>;
|
|
78
|
+
clientId: string | null;
|
|
61
79
|
}
|
|
62
80
|
/** Introspect a key. `apiKey` overrides `cfg.apiKey` (used by `whoami` to probe both sides of a divergence). */
|
|
63
81
|
export declare function fetchAuthStatus(cfg: HubConfig, apiKey?: string): Promise<AuthStatus>;
|
|
82
|
+
/**
|
|
83
|
+
* WHO this key is — the agent principal, for recognising the agent's OWN
|
|
84
|
+
* posts (a room message carries it as `routedTeammateId`). Never the acting
|
|
85
|
+
* user sent to the pod: that is `resolveUserId` (/users/me), which for an
|
|
86
|
+
* agent key answers the linked HUMAN. The id recorded with the key
|
|
87
|
+
* (`cfg.agentUserId`) wins; else the pod's key-owner answer (/auth/status),
|
|
88
|
+
* cached per key. For a person's own key both questions answer the person.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveAgentUserId(cfg: HubConfig): Promise<string>;
|
|
64
91
|
/**
|
|
65
92
|
* Shape of `GET /api/hub/users/me` (`hub-protocol/rest/users.ts:36-46`).
|
|
66
93
|
*
|
|
@@ -105,6 +132,17 @@ export declare function classifyActiveCredential(cfg: HubConfig): Promise<Review
|
|
|
105
132
|
* as its own error.
|
|
106
133
|
*/
|
|
107
134
|
export declare function isAgentReviewRejection(err: unknown): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Did the pod refuse this approve because it must be reviewed on its page?
|
|
137
|
+
*
|
|
138
|
+
* `proposals.approve` answers a page-only proposal (one whose approval turns a
|
|
139
|
+
* skill, automation or rule on) with CONFLICT, which the Hub door sends as a
|
|
140
|
+
* bare 409 `{ error: <sentence> }` and no machine code. So: 409, and the
|
|
141
|
+
* sentence is the pod's own ("Open it to approve: …"), shown as it came.
|
|
142
|
+
*/
|
|
143
|
+
export declare function isReviewOnItsPageRefusal(err: unknown): boolean;
|
|
144
|
+
/** Say the pod's sentence and print the page that can approve it, never the raw 409. */
|
|
145
|
+
export declare function renderReviewOnItsPage(id: string, cfg: HubConfig, err: HubError): void;
|
|
108
146
|
/** The `synap://` deep link the desktop app registers — `synap open proposal <id>` sends exactly this (open.ts:36-38). */
|
|
109
147
|
export declare function proposalDeepLink(id: string): string;
|
|
110
148
|
/** The pod's web review page for a bare id — the same route `bridge-setup.ts:894` and `statusline.ts:425` print. */
|