sessionpipe 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -2
- package/dist/cli.js +4358 -3288
- package/dist/cli.js.map +4 -4
- package/dist/control/aliases.d.ts +5 -0
- package/dist/control/aliases.d.ts.map +1 -1
- package/dist/control/autoupdate.d.ts +1 -18
- package/dist/control/autoupdate.d.ts.map +1 -1
- package/dist/control/cli.d.ts +1 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +83 -12
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/files.d.ts +6 -4
- package/dist/control/files.d.ts.map +1 -1
- package/dist/control/pair.d.ts +15 -23
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/route.d.ts +1 -1
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/sdk.d.ts.map +1 -1
- package/dist/control/store.d.ts +21 -2
- package/dist/control/store.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +7 -1
- package/dist/hook.js.map +2 -2
- package/dist/index.js +150 -44
- package/dist/index.js.map +4 -4
- package/dist/run.d.ts.map +1 -1
- package/dist/update.d.ts +1 -7
- package/dist/update.d.ts.map +1 -1
- package/dist/worker.js +152 -46
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/hook.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/hook.ts", "../src/control/local.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n// SPDX-License-Identifier: Apache-2.0\n// The hook path. A harness runs `node hook.js <harness> <event>`; this reads stdin,\n// writes one job file, spawns the worker detached, prints what the harness needs\n// (Antigravity's answer) and exits 0 \u2014 in milliseconds, with nothing but node:\n// builtins imported (the build checks that). No network, no schema library, no\n// config parsing beyond one small file read for the throttle.\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { ask, socketPath } from \"./control/local.js\";\n\n/** A Claude Code session id (the same test as core's notClaudeCode). */\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The two streams the hook touches: stdin once, whole; stdout for an answer. */\nexport interface HookIO {\n stdin(): Buffer;\n stdout(text: string): void;\n}\n\n/** One hook run: `argv` is [harness, event, ...] as the harness invoked it, `env` the\n * process environment. Resolves once the job is on its way and any answer printed;\n * the process then exits 0, whatever happened (the hook is invisible to the harness). */\nexport async function hookMain(argv: string[], env: NodeJS.ProcessEnv, io: HookIO): Promise<void> {\n const t0 = process.hrtime.bigint();\n /** The hook's own work, when a control wait follows it (the wait is not the hook's cost). */\n let workMs: number | null = null;\n const [harness = \"\", event = \"\", ...rest] = argv;\n\n function stateDir(): string {\n if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.LOCALAPPDATA || path.join(home, \"AppData\", \"Local\"), \"sessionpipe\");\n return path.join(env.XDG_STATE_HOME || path.join(home, \".local\", \"state\"), \"sessionpipe\");\n }\n function configFile(): string {\n if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.APPDATA || path.join(home, \"AppData\", \"Roaming\"), \"sessionpipe\", \"config.json\");\n return path.join(env.XDG_CONFIG_HOME || path.join(home, \".config\"), \"sessionpipe\", \"config.json\");\n }\n\n // Antigravity parses stdout as the hook's answer; `{}` is \"carry on\" for every event we\n // register. PreToolUse is not one of them: `{}` there is a deny, so an entry an older\n // install left behind asks the person instead (core's AG_STALE_PRETOOL, same bytes).\n if (harness === \"antigravity\") {\n try {\n io.stdout(\n event === \"PreToolUse\"\n ? '{\"decision\":\"ask\",\"reason\":\"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it.\"}\\n'\n : \"{}\\n\",\n );\n } catch {}\n }\n\n const state = stateDir();\n try {\n if (!harness || !event) throw new Error(\"usage: hook <harness> <event>\");\n let stdin = \"\";\n if (harness !== \"codex\" || event !== \"notify\") {\n try {\n const buf = io.stdin();\n stdin = buf.subarray(0, 1024 * 1024).toString(\"utf8\");\n } catch {}\n }\n // Tool events are the bulk of what a harness fires. When no sink takes tier \u2265 1\n // they are only a \"still working\" signal: one heartbeat a minute per session.\n const TOOL = /^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/;\n if (TOOL.test(event) && !anySinkAbove(0)) {\n const id = sessionIdOf(stdin);\n if (!id || !beatDue(id)) return finish(false);\n }\n // A Stop asks the daemon BEFORE its event goes to the worker: the worker reports the\n // Stop to the daemon too, and a message queued for this Stop must be taken here,\n // not rerouted because the event got there first.\n const stopAnswer = event === \"Stop\" ? await (controlAsk(stdin) ?? Promise.resolve(null)) : null;\n mkdirSync(path.join(state, \"jobs\"), { recursive: true, mode: 0o700 });\n const job = {\n harness,\n event,\n argv: [harness, event, ...rest],\n stdin,\n cwd: process.cwd(),\n env: pick([\n // Where a harness keeps its config and its sessions, so a job swept by a worker\n // some other harness's hook spawned still resolves this harness's own homes\n // (run.ts sweepJobs runs any stale job file, whatever spawned the worker).\n \"APPDATA\",\n \"CLAUDE_CODE_HOST_SESSION_ID\",\n \"CLAUDE_CONFIG_DIR\",\n \"CLAUDE_PROJECT_DIR\",\n \"CODEX_HOME\",\n // Devin's payload names no folder at all; this is the only thing that does.\n \"DEVIN_PROJECT_DIR\",\n \"GEMINI_CLI_HOME\",\n // Devin's config file and session store are both resolved through these.\n \"XDG_CONFIG_HOME\",\n \"XDG_DATA_HOME\",\n ]),\n ppid: process.ppid,\n at: Date.now(),\n };\n const jobFile = path.join(\n state,\n \"jobs\",\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`,\n );\n writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });\n if (!env.SESSIONPIPE_NO_WORKER) {\n const worker = path.join(path.dirname(fileURLToPath(import.meta.url)), \"worker.js\");\n const p = spawn(process.execPath, [worker, jobFile], { detached: true, stdio: \"ignore\", windowsHide: true, env });\n p.unref();\n }\n if (stopAnswer) {\n try {\n io.stdout(`${stopAnswer}\\n`);\n } catch {}\n }\n // A PermissionRequest waits AFTER its event is on its way, so the receiver already\n // shows the Allow / Deny this answer is for.\n const ctl = event === \"PermissionRequest\" ? controlAsk(stdin) : null;\n if (ctl) {\n workMs = Number(process.hrtime.bigint() - t0) / 1e6;\n const out = await ctl;\n if (out) {\n try {\n io.stdout(`${out}\\n`);\n } catch {}\n }\n }\n return finish(true);\n } catch {\n return finish(false); // the hook must be invisible to the harness\n }\n\n /** Control (spec/CONTROL.md \u00A76, \u00A79): only on a machine with control paired, and only\n * for a message the daemon itself verified. A Stop hook asks for a queued message and\n * answers a block with it; a PermissionRequest hook waits for the person's signed\n * answer while the terminal shows its own prompt, first answer wins. Anything that\n * goes wrong (no daemon, a timeout) prints nothing: the harness carries on as usual. */\n function controlAsk(stdin: string): Promise<string | null> | null {\n if (harness !== \"claude-code\" || (event !== \"Stop\" && event !== \"PermissionRequest\")) return null;\n if (env.SESSIONPIPE_CONTROL_JOB) return null; // a headless turn the daemon itself runs\n const sock = socketPath(state, env);\n if (\n process.platform === \"win32\"\n ? !existsSync(path.join(path.dirname(configFile()), \"control.json\"))\n : !existsSync(sock)\n )\n return null;\n let s: Record<string, unknown>;\n try {\n s = JSON.parse(stdin) as Record<string, unknown>;\n } catch {\n return null;\n }\n const id = typeof s.session_id === \"string\" ? s.session_id : \"\";\n if (!id) return null;\n // Our entry in ~/.claude/settings.json is run by every harness that imports that file\n // \u2014 Devin's `claude` hooks provider is live, not a stub \u2014 and it arrives under the\n // claude-code name. Every Claude Code payload names a transcript and a UUID session;\n // a payload with neither is somebody else's, and must not be parked for up to 125 s\n // waiting on an answer that is not coming. Same test as core's notClaudeCode\n // (claude-code.ts), which the worker applies; repeated here because this file imports\n // nothing but node builtins.\n if (typeof s.transcript_path !== \"string\" && !UUID.test(id)) return null;\n const session = `claude-code:${id}`;\n if (event === \"Stop\")\n return ask(sock, { op: \"stop\", session, active: s.stop_hook_active === true }, 300).then((r) =>\n r?.op === \"block\" ? JSON.stringify({ decision: \"block\", reason: r.reason }) : null,\n );\n // The attention id the adapter puts on attention.needed (claude-code.ts), so the\n // receiver's Allow / Deny names this very prompt.\n const tool =\n typeof s.tool_name === \"string\" && /^[A-Za-z0-9_.:-]{1,120}$/.test(s.tool_name) ? s.tool_name : undefined;\n const turn = typeof s.prompt_id === \"string\" ? s.prompt_id : undefined;\n const attention = `perm-${createHash(\"sha256\")\n .update(`${turn ?? \"\"}|${tool ?? \"\"}|${JSON.stringify(s.tool_input ?? null)}`)\n .digest(\"hex\")\n .slice(0, 16)}`;\n return ask(sock, { op: \"permission\", session, attention, ...(tool ? { tool } : {}) }, 125_000).then((r) =>\n r?.op === \"decision\"\n ? JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PermissionRequest\",\n decision:\n r.behavior === \"allow\"\n ? { behavior: \"allow\" }\n : { behavior: \"deny\", message: r.message || \"Denied by the owner (sessionpipe control).\" },\n },\n })\n : null,\n );\n }\n\n /** The timing line; the process exits 0 right after. */\n function finish(spawned: boolean): void {\n const total = Number(process.hrtime.bigint() - t0) / 1e6;\n const ms = workMs ?? total;\n try {\n mkdirSync(state, { recursive: true });\n appendFileSync(\n path.join(state, \"timing.jsonl\"),\n `${JSON.stringify({ at: Date.now(), harness, event, ms: Math.round(ms * 10) / 10, spawned, ...(workMs !== null ? { control_wait_ms: Math.round(total - workMs) } : {}) })}\\n`,\n );\n } catch {}\n }\n function pick(names: string[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const n of names) if (env[n] !== undefined) out[n] = env[n] as string;\n return out;\n }\n function anySinkAbove(tier: number): boolean {\n try {\n const c = JSON.parse(readFileSync(configFile(), \"utf8\")) as { sinks?: { tier?: number; paused?: string }[] };\n return (c.sinks ?? []).some((s) => Number(s.tier) > tier && !s.paused);\n } catch {\n return false;\n }\n }\n function sessionIdOf(stdin: string): string {\n const m = /\"(?:session_id|sessionId|conversationId)\"\\s*:\\s*\"([^\"]{1,128})\"/.exec(stdin);\n return m?.[1] ?? \"\";\n }\n /** One heartbeat per minute per session: the stamp file's mtime is the clock. */\n function beatDue(id: string): boolean {\n const stamp = path.join(state, \"beats\", `${harness}-${id.replace(/[^A-Za-z0-9_-]/g, \"_\")}`);\n try {\n if (Date.now() - statSync(stamp).mtimeMs < 60_000) return false;\n } catch {}\n try {\n mkdirSync(path.dirname(stamp), { recursive: true, mode: 0o700 });\n writeFileSync(stamp, \"\", { mode: 0o600 });\n } catch {}\n return true;\n }\n}\n\n// Run as `node hook.js <harness> <event>`; importing this module runs nothing. The main\n// module is known by its real path: node resolves symlinks for import.meta.url, argv[1]\n// keeps the spelling it was given (/var \u2192 /private/var on macOS).\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href)\n hookMain(process.argv.slice(2), process.env, {\n // fd 0 and 1 are the test runner's own (a read of 0 would block it); launcher.test.ts\n // and the bench run these two through the real binary.\n /* v8 ignore next -- see above */\n stdin: () => readFileSync(0),\n /* v8 ignore next -- see above */\n stdout: (text) => writeFileSync(1, text),\n }).then(() => process.exit(0));\n", "// SPDX-License-Identifier: Apache-2.0\n// The daemon's local socket (spec/CONTROL.md \u00A78): how the waiter, the Stop and\n// PermissionRequest hooks and the worker talk to the one daemon on this machine.\n// Line-delimited JSON, one request per connection. A Unix socket in the state\n// directory (0600, in a 0700 dir); a per-user named pipe on Windows. Only node:\n// builtins \u2014 the hook bundle imports this.\nimport net from \"node:net\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type LocalRequest =\n /** A waiter parks until a message for its session arrives. */\n | { op: \"wait\"; session: string; pid?: number }\n /** A Stop hook asks for a queued message (turn mode). */\n | { op: \"stop\"; session: string; active: boolean }\n /** A PermissionRequest hook parks until a verified answer, or until it's released. */\n | { op: \"permission\"; session: string; attention: string; tool?: string }\n /** The worker reports a hook event: turn state and attention closure. */\n | { op: \"event\"; session: string; event: string; transcript?: string; cwd?: string }\n /** `sessionpipe control status`. */\n | { op: \"status\" }\n /** `sessionpipe update` installed a new copy: restart onto it once nothing is in hand. */\n | { op: \"restart\" };\n\nexport type LocalReply =\n | { op: \"message\"; text: string; id: string }\n | { op: \"block\"; reason: string; id: string }\n | { op: \"decision\"; behavior: \"allow\" | \"deny\"; message?: string; id: string }\n | { op: \"none\" }\n /** A newer waiter for the same session took over; this one stops. */\n | { op: \"replaced\" }\n | { op: \"ok\" }\n | { op: \"status\"; status: unknown };\n\n/** Where the daemon listens. `SESSIONPIPE_SOCKET` overrides (tests). */\nexport function socketPath(state: string, env: NodeJS.ProcessEnv = process.env): string {\n if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;\n if (process.platform === \"win32\") return `\\\\\\\\.\\\\pipe\\\\sessionpipe-control-${os.userInfo().username}`;\n return path.join(state, \"control.sock\");\n}\n\n/** One request, one reply. `timeoutMs` bounds the whole exchange; null on any\n * failure (no daemon, a timeout, a garbled reply) \u2014 callers treat that as \"none\". */\nexport function ask(sock: string, req: LocalRequest, timeoutMs: number): Promise<LocalReply | null> {\n return new Promise((resolve) => {\n let buf = \"\";\n let done = false;\n const s = net.connect(sock);\n const finish = (r: LocalReply | null) => {\n if (done) return;\n done = true;\n clearTimeout(t);\n s.destroy();\n resolve(r);\n };\n const t = setTimeout(() => finish(null), timeoutMs);\n s.on(\"connect\", () => s.write(`${JSON.stringify(req)}\\n`));\n s.on(\"data\", (d) => {\n buf += d.toString(\"utf8\");\n const i = buf.indexOf(\"\\n\");\n if (i < 0) return;\n try {\n finish(JSON.parse(buf.slice(0, i)) as LocalReply);\n } catch {\n finish(null);\n }\n });\n s.on(\"error\", () => finish(null));\n s.on(\"close\", () => finish(null));\n });\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n// SPDX-License-Identifier: Apache-2.0\n// The hook path. A harness runs `node hook.js <harness> <event>`; this reads stdin,\n// writes one job file, spawns the worker detached, prints what the harness needs\n// (Antigravity's answer) and exits 0 \u2014 in milliseconds, with nothing but node:\n// builtins imported (the build checks that). No network, no schema library, no\n// config parsing beyond one small file read for the throttle.\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { ask, socketPath } from \"./control/local.js\";\n\n/** A Claude Code session id (the same test as core's notClaudeCode). */\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The two streams the hook touches: stdin once, whole; stdout for an answer. */\nexport interface HookIO {\n stdin(): Buffer;\n stdout(text: string): void;\n}\n\n/** One hook run: `argv` is [harness, event, ...] as the harness invoked it, `env` the\n * process environment. Resolves once the job is on its way and any answer printed;\n * the process then exits 0, whatever happened (the hook is invisible to the harness). */\nexport async function hookMain(argv: string[], env: NodeJS.ProcessEnv, io: HookIO): Promise<void> {\n const t0 = process.hrtime.bigint();\n /** The hook's own work, when a control wait follows it (the wait is not the hook's cost). */\n let workMs: number | null = null;\n const [harness = \"\", event = \"\", ...rest] = argv;\n\n function stateDir(): string {\n if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.LOCALAPPDATA || path.join(home, \"AppData\", \"Local\"), \"sessionpipe\");\n return path.join(env.XDG_STATE_HOME || path.join(home, \".local\", \"state\"), \"sessionpipe\");\n }\n function configFile(): string {\n if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.APPDATA || path.join(home, \"AppData\", \"Roaming\"), \"sessionpipe\", \"config.json\");\n return path.join(env.XDG_CONFIG_HOME || path.join(home, \".config\"), \"sessionpipe\", \"config.json\");\n }\n\n // Antigravity parses stdout as the hook's answer; `{}` is \"carry on\" for every event we\n // register. PreToolUse is not one of them: `{}` there is a deny, so an entry an older\n // install left behind asks the person instead (core's AG_STALE_PRETOOL, same bytes).\n if (harness === \"antigravity\") {\n try {\n io.stdout(\n event === \"PreToolUse\"\n ? '{\"decision\":\"ask\",\"reason\":\"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it.\"}\\n'\n : \"{}\\n\",\n );\n } catch {}\n }\n\n const state = stateDir();\n try {\n if (!harness || !event) throw new Error(\"usage: hook <harness> <event>\");\n let stdin = \"\";\n if (harness !== \"codex\" || event !== \"notify\") {\n try {\n const buf = io.stdin();\n stdin = buf.subarray(0, 1024 * 1024).toString(\"utf8\");\n } catch {}\n }\n // Tool events are the bulk of what a harness fires. When no sink takes tier \u2265 1\n // they are only a \"still working\" signal: one heartbeat a minute per session.\n const TOOL = /^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/;\n if (TOOL.test(event) && !anySinkAbove(0)) {\n const id = sessionIdOf(stdin);\n if (!id || !beatDue(id)) return finish(false);\n }\n // A Stop asks the daemon BEFORE its event goes to the worker: the worker reports the\n // Stop to the daemon too, and a message queued for this Stop must be taken here,\n // not rerouted because the event got there first.\n const stopAnswer = event === \"Stop\" ? await (controlAsk(stdin) ?? Promise.resolve(null)) : null;\n mkdirSync(path.join(state, \"jobs\"), { recursive: true, mode: 0o700 });\n const job = {\n harness,\n event,\n argv: [harness, event, ...rest],\n stdin,\n cwd: process.cwd(),\n env: pick([\n // Where a harness keeps its config and its sessions, so a job swept by a worker\n // some other harness's hook spawned still resolves this harness's own homes\n // (run.ts sweepJobs runs any stale job file, whatever spawned the worker).\n \"APPDATA\",\n \"CLAUDE_CODE_HOST_SESSION_ID\",\n // session.attended: Claude Code's own word, and SESSIONPIPE_CONTROL_JOB below, the\n // control daemon's mark on a turn it runs for a person's message.\n \"CLAUDE_CODE_SESSION_ATTENDED\",\n \"CLAUDE_CONFIG_DIR\",\n \"CLAUDE_PROJECT_DIR\",\n \"CODEX_HOME\",\n // Devin's payload names no folder at all; this is the only thing that does.\n \"DEVIN_PROJECT_DIR\",\n \"GEMINI_CLI_HOME\",\n \"SESSIONPIPE_CONTROL_JOB\",\n // The launcher a script named for a session it starts (PROTOCOL.md \u00A73 launched_by).\n \"SESSIONPIPE_LAUNCHED_BY\",\n // Devin's config file and session store are both resolved through these.\n \"XDG_CONFIG_HOME\",\n \"XDG_DATA_HOME\",\n ]),\n ppid: process.ppid,\n at: Date.now(),\n };\n const jobFile = path.join(\n state,\n \"jobs\",\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`,\n );\n writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });\n if (!env.SESSIONPIPE_NO_WORKER) {\n const worker = path.join(path.dirname(fileURLToPath(import.meta.url)), \"worker.js\");\n const p = spawn(process.execPath, [worker, jobFile], { detached: true, stdio: \"ignore\", windowsHide: true, env });\n p.unref();\n }\n if (stopAnswer) {\n try {\n io.stdout(`${stopAnswer}\\n`);\n } catch {}\n }\n // A PermissionRequest waits AFTER its event is on its way, so the receiver already\n // shows the Allow / Deny this answer is for.\n const ctl = event === \"PermissionRequest\" ? controlAsk(stdin) : null;\n if (ctl) {\n workMs = Number(process.hrtime.bigint() - t0) / 1e6;\n const out = await ctl;\n if (out) {\n try {\n io.stdout(`${out}\\n`);\n } catch {}\n }\n }\n return finish(true);\n } catch {\n return finish(false); // the hook must be invisible to the harness\n }\n\n /** Control (spec/CONTROL.md \u00A76, \u00A79): only on a machine with control paired, and only\n * for a message the daemon itself verified. A Stop hook asks for a queued message and\n * answers a block with it; a PermissionRequest hook waits for the person's signed\n * answer while the terminal shows its own prompt, first answer wins. Anything that\n * goes wrong (no daemon, a timeout) prints nothing: the harness carries on as usual. */\n function controlAsk(stdin: string): Promise<string | null> | null {\n if (harness !== \"claude-code\" || (event !== \"Stop\" && event !== \"PermissionRequest\")) return null;\n if (env.SESSIONPIPE_CONTROL_JOB) return null; // a headless turn the daemon itself runs\n const sock = socketPath(state, env);\n if (\n process.platform === \"win32\"\n ? !existsSync(path.join(path.dirname(configFile()), \"control.json\"))\n : !existsSync(sock)\n )\n return null;\n let s: Record<string, unknown>;\n try {\n s = JSON.parse(stdin) as Record<string, unknown>;\n } catch {\n return null;\n }\n const id = typeof s.session_id === \"string\" ? s.session_id : \"\";\n if (!id) return null;\n // Our entry in ~/.claude/settings.json is run by every harness that imports that file\n // \u2014 Devin's `claude` hooks provider is live, not a stub \u2014 and it arrives under the\n // claude-code name. Every Claude Code payload names a transcript and a UUID session;\n // a payload with neither is somebody else's, and must not be parked for up to 125 s\n // waiting on an answer that is not coming. Same test as core's notClaudeCode\n // (claude-code.ts), which the worker applies; repeated here because this file imports\n // nothing but node builtins.\n if (typeof s.transcript_path !== \"string\" && !UUID.test(id)) return null;\n const session = `claude-code:${id}`;\n if (event === \"Stop\")\n return ask(sock, { op: \"stop\", session, active: s.stop_hook_active === true }, 300).then((r) =>\n r?.op === \"block\" ? JSON.stringify({ decision: \"block\", reason: r.reason }) : null,\n );\n // The attention id the adapter puts on attention.needed (claude-code.ts), so the\n // receiver's Allow / Deny names this very prompt.\n const tool =\n typeof s.tool_name === \"string\" && /^[A-Za-z0-9_.:-]{1,120}$/.test(s.tool_name) ? s.tool_name : undefined;\n const turn = typeof s.prompt_id === \"string\" ? s.prompt_id : undefined;\n const attention = `perm-${createHash(\"sha256\")\n .update(`${turn ?? \"\"}|${tool ?? \"\"}|${JSON.stringify(s.tool_input ?? null)}`)\n .digest(\"hex\")\n .slice(0, 16)}`;\n return ask(sock, { op: \"permission\", session, attention, ...(tool ? { tool } : {}) }, 125_000).then((r) =>\n r?.op === \"decision\"\n ? JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PermissionRequest\",\n decision:\n r.behavior === \"allow\"\n ? { behavior: \"allow\" }\n : { behavior: \"deny\", message: r.message || \"Denied by the owner (sessionpipe control).\" },\n },\n })\n : null,\n );\n }\n\n /** The timing line; the process exits 0 right after. */\n function finish(spawned: boolean): void {\n const total = Number(process.hrtime.bigint() - t0) / 1e6;\n const ms = workMs ?? total;\n try {\n mkdirSync(state, { recursive: true });\n appendFileSync(\n path.join(state, \"timing.jsonl\"),\n `${JSON.stringify({ at: Date.now(), harness, event, ms: Math.round(ms * 10) / 10, spawned, ...(workMs !== null ? { control_wait_ms: Math.round(total - workMs) } : {}) })}\\n`,\n );\n } catch {}\n }\n function pick(names: string[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const n of names) if (env[n] !== undefined) out[n] = env[n] as string;\n return out;\n }\n function anySinkAbove(tier: number): boolean {\n try {\n const c = JSON.parse(readFileSync(configFile(), \"utf8\")) as { sinks?: { tier?: number; paused?: string }[] };\n return (c.sinks ?? []).some((s) => Number(s.tier) > tier && !s.paused);\n } catch {\n return false;\n }\n }\n function sessionIdOf(stdin: string): string {\n const m = /\"(?:session_id|sessionId|conversationId)\"\\s*:\\s*\"([^\"]{1,128})\"/.exec(stdin);\n return m?.[1] ?? \"\";\n }\n /** One heartbeat per minute per session: the stamp file's mtime is the clock. */\n function beatDue(id: string): boolean {\n const stamp = path.join(state, \"beats\", `${harness}-${id.replace(/[^A-Za-z0-9_-]/g, \"_\")}`);\n try {\n if (Date.now() - statSync(stamp).mtimeMs < 60_000) return false;\n } catch {}\n try {\n mkdirSync(path.dirname(stamp), { recursive: true, mode: 0o700 });\n writeFileSync(stamp, \"\", { mode: 0o600 });\n } catch {}\n return true;\n }\n}\n\n// Run as `node hook.js <harness> <event>`; importing this module runs nothing. The main\n// module is known by its real path: node resolves symlinks for import.meta.url, argv[1]\n// keeps the spelling it was given (/var \u2192 /private/var on macOS).\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href)\n hookMain(process.argv.slice(2), process.env, {\n // fd 0 and 1 are the test runner's own (a read of 0 would block it); launcher.test.ts\n // and the bench run these two through the real binary.\n /* v8 ignore next -- see above */\n stdin: () => readFileSync(0),\n /* v8 ignore next -- see above */\n stdout: (text) => writeFileSync(1, text),\n }).then(() => process.exit(0));\n", "// SPDX-License-Identifier: Apache-2.0\n// The daemon's local socket (spec/CONTROL.md \u00A78): how the waiter, the Stop and\n// PermissionRequest hooks and the worker talk to the one daemon on this machine.\n// Line-delimited JSON, one request per connection. A Unix socket in the state\n// directory (0600, in a 0700 dir); a per-user named pipe on Windows. Only node:\n// builtins \u2014 the hook bundle imports this.\nimport net from \"node:net\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type LocalRequest =\n /** A waiter parks until a message for its session arrives. */\n | { op: \"wait\"; session: string; pid?: number }\n /** A Stop hook asks for a queued message (turn mode). */\n | { op: \"stop\"; session: string; active: boolean }\n /** A PermissionRequest hook parks until a verified answer, or until it's released. */\n | { op: \"permission\"; session: string; attention: string; tool?: string }\n /** The worker reports a hook event: turn state and attention closure. */\n | { op: \"event\"; session: string; event: string; transcript?: string; cwd?: string }\n /** `sessionpipe control status`. */\n | { op: \"status\" }\n /** `sessionpipe update` installed a new copy: restart onto it once nothing is in hand. */\n | { op: \"restart\" };\n\nexport type LocalReply =\n | { op: \"message\"; text: string; id: string }\n | { op: \"block\"; reason: string; id: string }\n | { op: \"decision\"; behavior: \"allow\" | \"deny\"; message?: string; id: string }\n | { op: \"none\" }\n /** A newer waiter for the same session took over; this one stops. */\n | { op: \"replaced\" }\n | { op: \"ok\" }\n | { op: \"status\"; status: unknown };\n\n/** Where the daemon listens. `SESSIONPIPE_SOCKET` overrides (tests). */\nexport function socketPath(state: string, env: NodeJS.ProcessEnv = process.env): string {\n if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;\n if (process.platform === \"win32\") return `\\\\\\\\.\\\\pipe\\\\sessionpipe-control-${os.userInfo().username}`;\n return path.join(state, \"control.sock\");\n}\n\n/** One request, one reply. `timeoutMs` bounds the whole exchange; null on any\n * failure (no daemon, a timeout, a garbled reply) \u2014 callers treat that as \"none\". */\nexport function ask(sock: string, req: LocalRequest, timeoutMs: number): Promise<LocalReply | null> {\n return new Promise((resolve) => {\n let buf = \"\";\n let done = false;\n const s = net.connect(sock);\n const finish = (r: LocalReply | null) => {\n if (done) return;\n done = true;\n clearTimeout(t);\n s.destroy();\n resolve(r);\n };\n const t = setTimeout(() => finish(null), timeoutMs);\n s.on(\"connect\", () => s.write(`${JSON.stringify(req)}\\n`));\n s.on(\"data\", (d) => {\n buf += d.toString(\"utf8\");\n const i = buf.indexOf(\"\\n\");\n if (i < 0) return;\n try {\n finish(JSON.parse(buf.slice(0, i)) as LocalReply);\n } catch {\n finish(null);\n }\n });\n s.on(\"error\", () => finish(null));\n s.on(\"close\", () => finish(null));\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["os", "path", "path", "os"]
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe 0.
|
|
1
|
+
// sessionpipe 0.10.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
4
|
import { createHash, createHmac } from "node:crypto";
|
|
@@ -924,14 +924,28 @@ var claudeCode = {
|
|
|
924
924
|
case "SessionStart":
|
|
925
925
|
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
926
926
|
break;
|
|
927
|
-
case "UserPromptSubmit":
|
|
927
|
+
case "UserPromptSubmit": {
|
|
928
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
928
929
|
events.push(
|
|
929
930
|
ev("turn.started", {
|
|
930
931
|
turn_id,
|
|
931
|
-
prompt_chars:
|
|
932
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
933
|
+
// Claude Code's hook schema documents `source`
|
|
934
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
935
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
936
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
937
|
+
// to extend.
|
|
938
|
+
submitted_by: harnessWord(s.source),
|
|
939
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
940
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
941
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
942
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
943
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
944
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
932
945
|
})
|
|
933
946
|
);
|
|
934
947
|
break;
|
|
948
|
+
}
|
|
935
949
|
case "PreToolUse":
|
|
936
950
|
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
937
951
|
break;
|
|
@@ -986,7 +1000,13 @@ var claudeCode = {
|
|
|
986
1000
|
);
|
|
987
1001
|
break;
|
|
988
1002
|
case "Stop":
|
|
989
|
-
events.push(
|
|
1003
|
+
events.push(
|
|
1004
|
+
ev("turn.ended", {
|
|
1005
|
+
turn_id,
|
|
1006
|
+
reason: "stop",
|
|
1007
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
1008
|
+
})
|
|
1009
|
+
);
|
|
990
1010
|
break;
|
|
991
1011
|
case "StopFailure":
|
|
992
1012
|
events.push(
|
|
@@ -1029,6 +1049,8 @@ var claudeCode = {
|
|
|
1029
1049
|
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
1030
1050
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
1031
1051
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
1052
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
1053
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
1032
1054
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
1033
1055
|
const r = { session, events, hints };
|
|
1034
1056
|
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
@@ -1049,6 +1071,7 @@ var claudeCode = {
|
|
|
1049
1071
|
const cwd = out.cwd ?? session.cwd;
|
|
1050
1072
|
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
1051
1073
|
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
1074
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
1052
1075
|
return out;
|
|
1053
1076
|
},
|
|
1054
1077
|
backfill(sinceMs, env = process.env) {
|
|
@@ -1084,6 +1107,9 @@ function notClaudeCode(id, s) {
|
|
|
1084
1107
|
function pickSource(v) {
|
|
1085
1108
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1086
1109
|
}
|
|
1110
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
1111
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
1112
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
1087
1113
|
function errText(v) {
|
|
1088
1114
|
if (typeof v === "string") return v.slice(0, 2e3);
|
|
1089
1115
|
if (v && typeof v === "object") {
|
|
@@ -1200,7 +1226,27 @@ function claudeLogin(dir, home = homeDir()) {
|
|
|
1200
1226
|
} catch {
|
|
1201
1227
|
}
|
|
1202
1228
|
}
|
|
1203
|
-
return
|
|
1229
|
+
return apiAccount(dir);
|
|
1230
|
+
}
|
|
1231
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
1232
|
+
function apiAccount(dir) {
|
|
1233
|
+
let base;
|
|
1234
|
+
try {
|
|
1235
|
+
base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
|
|
1236
|
+
} catch {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof base !== "string") return null;
|
|
1240
|
+
let u;
|
|
1241
|
+
try {
|
|
1242
|
+
u = new URL(base.trim());
|
|
1243
|
+
} catch {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1247
|
+
return null;
|
|
1248
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1249
|
+
return { id: `api-${createHash2("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1204
1250
|
}
|
|
1205
1251
|
var accountCache = /* @__PURE__ */ new Map();
|
|
1206
1252
|
function claudeAccount(dir) {
|
|
@@ -1360,6 +1406,18 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1360
1406
|
return out;
|
|
1361
1407
|
}
|
|
1362
1408
|
|
|
1409
|
+
// ../core/src/control/verify.ts
|
|
1410
|
+
var GRANT_MAX_MS = 24 * 36e5;
|
|
1411
|
+
var SKEW_MS = 5 * 6e4;
|
|
1412
|
+
var MACHINE_MAX_AGE_MS = 24 * 36e5;
|
|
1413
|
+
var RECEIVER_MAX_AGE_MS = 10 * 6e4;
|
|
1414
|
+
var NONCE_TTL_MS = 25 * 36e5;
|
|
1415
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1416
|
+
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1417
|
+
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1418
|
+
var enc = new TextEncoder();
|
|
1419
|
+
var dec = new TextDecoder();
|
|
1420
|
+
|
|
1363
1421
|
// ../core/src/adapters/codex.ts
|
|
1364
1422
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1365
1423
|
import path6 from "node:path";
|
|
@@ -1681,7 +1739,7 @@ var codex = {
|
|
|
1681
1739
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1682
1740
|
} catch {
|
|
1683
1741
|
}
|
|
1684
|
-
const id = s.session_id;
|
|
1742
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1685
1743
|
if (!id) return null;
|
|
1686
1744
|
if (s.transcript_path === null) return null;
|
|
1687
1745
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
@@ -2525,6 +2583,22 @@ function makeEvent(a, ctx) {
|
|
|
2525
2583
|
};
|
|
2526
2584
|
}
|
|
2527
2585
|
|
|
2586
|
+
// ../core/src/launched-by.ts
|
|
2587
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2588
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2589
|
+
function launchedBy(env) {
|
|
2590
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2591
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2592
|
+
}
|
|
2593
|
+
function readHook(adapter, input) {
|
|
2594
|
+
const r = adapter.fromHook(input);
|
|
2595
|
+
if (r && !r.session.parent_id) {
|
|
2596
|
+
const launcher = launchedBy(input.env);
|
|
2597
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2598
|
+
}
|
|
2599
|
+
return r;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2528
2602
|
// ../core/src/outbox.ts
|
|
2529
2603
|
import {
|
|
2530
2604
|
appendFileSync,
|
|
@@ -2967,8 +3041,8 @@ var DATA_ALLOW = {
|
|
|
2967
3041
|
"session.ended": { 0: ["reason"] },
|
|
2968
3042
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2969
3043
|
"session.forgotten": { 0: [] },
|
|
2970
|
-
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2971
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
3044
|
+
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
3045
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
2972
3046
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2973
3047
|
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2974
3048
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
@@ -2992,7 +3066,9 @@ var SESSION_ALLOW = [
|
|
|
2992
3066
|
"title_source",
|
|
2993
3067
|
"url",
|
|
2994
3068
|
"account_id",
|
|
2995
|
-
"
|
|
3069
|
+
"attended",
|
|
3070
|
+
"parent_id",
|
|
3071
|
+
"launched_by"
|
|
2996
3072
|
];
|
|
2997
3073
|
var MAX_SIDE = 2e4;
|
|
2998
3074
|
var MAX_IO = 64 * 1024;
|
|
@@ -3106,6 +3182,49 @@ var StdoutSink = class {
|
|
|
3106
3182
|
// src/control/aliases.ts
|
|
3107
3183
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3108
3184
|
import path12 from "node:path";
|
|
3185
|
+
|
|
3186
|
+
// src/secrets.ts
|
|
3187
|
+
import { spawnSync } from "node:child_process";
|
|
3188
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
3189
|
+
var SERVICE = "sessionpipe";
|
|
3190
|
+
var defaultRunner = (cmd, args, input) => {
|
|
3191
|
+
const r = spawnSync(cmd, args, {
|
|
3192
|
+
input: input ?? "",
|
|
3193
|
+
encoding: "utf8",
|
|
3194
|
+
timeout: 8e3,
|
|
3195
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
3196
|
+
});
|
|
3197
|
+
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3198
|
+
};
|
|
3199
|
+
var sinkAccount = (name, what = "token") => {
|
|
3200
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3201
|
+
return `sink:${name}:${what}${scope}`;
|
|
3202
|
+
};
|
|
3203
|
+
function secretGet(store, account, run = defaultRunner) {
|
|
3204
|
+
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3205
|
+
if (r.status !== 0) return null;
|
|
3206
|
+
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3207
|
+
return v || null;
|
|
3208
|
+
}
|
|
3209
|
+
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3210
|
+
if (!s.token_in) return s;
|
|
3211
|
+
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3212
|
+
if (!token) return null;
|
|
3213
|
+
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3214
|
+
return { ...s, token, ...secret ? { secret } : {} };
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
// src/control/store.ts
|
|
3218
|
+
function startOptionsOf(v) {
|
|
3219
|
+
const o = v;
|
|
3220
|
+
if (!o || typeof o !== "object" || levelRank(o.permission) < 0 && o.permission !== "inherit") return null;
|
|
3221
|
+
return {
|
|
3222
|
+
permission: o.permission,
|
|
3223
|
+
...typeof o.model === "string" && MODEL_RE.test(o.model) ? { model: o.model } : {}
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/control/aliases.ts
|
|
3109
3228
|
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
3110
3229
|
function readAliases(file) {
|
|
3111
3230
|
try {
|
|
@@ -3115,7 +3234,7 @@ function readAliases(file) {
|
|
|
3115
3234
|
for (const [k, v] of Object.entries(j)) {
|
|
3116
3235
|
const a = v;
|
|
3117
3236
|
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
3118
|
-
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3237
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "", ...startOptionsOf(a) };
|
|
3119
3238
|
}
|
|
3120
3239
|
return out;
|
|
3121
3240
|
} catch {
|
|
@@ -3167,37 +3286,6 @@ function ask(sock, req, timeoutMs) {
|
|
|
3167
3286
|
});
|
|
3168
3287
|
}
|
|
3169
3288
|
|
|
3170
|
-
// src/secrets.ts
|
|
3171
|
-
import { spawnSync } from "node:child_process";
|
|
3172
|
-
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
3173
|
-
var SERVICE = "sessionpipe";
|
|
3174
|
-
var defaultRunner = (cmd, args, input) => {
|
|
3175
|
-
const r = spawnSync(cmd, args, {
|
|
3176
|
-
input: input ?? "",
|
|
3177
|
-
encoding: "utf8",
|
|
3178
|
-
timeout: 8e3,
|
|
3179
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
3180
|
-
});
|
|
3181
|
-
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3182
|
-
};
|
|
3183
|
-
var sinkAccount = (name, what = "token") => {
|
|
3184
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3185
|
-
return `sink:${name}:${what}${scope}`;
|
|
3186
|
-
};
|
|
3187
|
-
function secretGet(store, account, run = defaultRunner) {
|
|
3188
|
-
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3189
|
-
if (r.status !== 0) return null;
|
|
3190
|
-
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3191
|
-
return v || null;
|
|
3192
|
-
}
|
|
3193
|
-
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3194
|
-
if (!s.token_in) return s;
|
|
3195
|
-
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3196
|
-
if (!token) return null;
|
|
3197
|
-
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3198
|
-
return { ...s, token, ...secret ? { secret } : {} };
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
3289
|
// src/run.ts
|
|
3202
3290
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
3203
3291
|
const sock = socketPath(state);
|
|
@@ -3298,6 +3386,12 @@ function openTurn(read, o) {
|
|
|
3298
3386
|
};
|
|
3299
3387
|
}
|
|
3300
3388
|
var MAX_TURN_SIDE = 2e4;
|
|
3389
|
+
var JOB_OWN_ENV = [
|
|
3390
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3391
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
3392
|
+
"SESSIONPIPE_CONTROL_JOB",
|
|
3393
|
+
"SESSIONPIPE_LAUNCHED_BY"
|
|
3394
|
+
];
|
|
3301
3395
|
async function runJob(job, opts = {}) {
|
|
3302
3396
|
const state = opts.state ?? stateDir();
|
|
3303
3397
|
const cfg = readConfig();
|
|
@@ -3306,9 +3400,20 @@ async function runJob(job, opts = {}) {
|
|
|
3306
3400
|
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3307
3401
|
return { events: [], delivered: {} };
|
|
3308
3402
|
}
|
|
3309
|
-
const
|
|
3403
|
+
const env = { ...process.env };
|
|
3404
|
+
for (const k of JOB_OWN_ENV) delete env[k];
|
|
3405
|
+
const r = readHook(adapter, { argv: job.argv, stdin: job.stdin, env: { ...env, ...job.env }, cwd: job.cwd });
|
|
3310
3406
|
if (!r || !r.session.id) {
|
|
3311
|
-
|
|
3407
|
+
let keys = [];
|
|
3408
|
+
try {
|
|
3409
|
+
const payload = JSON.parse(job.argv.find((a) => a.startsWith("{")) ?? job.stdin);
|
|
3410
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) keys = Object.keys(payload);
|
|
3411
|
+
} catch {
|
|
3412
|
+
}
|
|
3413
|
+
log(
|
|
3414
|
+
state,
|
|
3415
|
+
`${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped; keys: ${keys.join(", ") || "(none)"}`
|
|
3416
|
+
);
|
|
3312
3417
|
return { events: [], delivered: {} };
|
|
3313
3418
|
}
|
|
3314
3419
|
const id = receiverIdOf(readAliases(aliasFile(path14.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
@@ -3360,7 +3465,8 @@ async function runJob(job, opts = {}) {
|
|
|
3360
3465
|
seq: 0,
|
|
3361
3466
|
machine: machineName(cfg, os3.hostname()),
|
|
3362
3467
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
3363
|
-
...r.session.model ? { model: r.session.model } : {}
|
|
3468
|
+
...r.session.model ? { model: r.session.model } : {},
|
|
3469
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
3364
3470
|
};
|
|
3365
3471
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
3366
3472
|
for (const a of r.events) {
|