sessionpipe 0.9.0 → 0.11.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 +99 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8737 -4942
- 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 +5 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +107 -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/local.d.ts +24 -0
- package/dist/control/local.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/pool.d.ts +49 -0
- package/dist/control/pool.d.ts.map +1 -0
- package/dist/control/route.d.ts +2 -2
- 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/help.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +43 -1
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1179 -847
- package/dist/index.js.map +4 -4
- package/dist/pool/cli.d.ts +39 -0
- package/dist/pool/cli.d.ts.map +1 -0
- package/dist/pool/credentials.d.ts +44 -0
- package/dist/pool/credentials.d.ts.map +1 -0
- package/dist/pool/import.d.ts +23 -0
- package/dist/pool/import.d.ts.map +1 -0
- package/dist/pool/index.d.ts +138 -0
- package/dist/pool/index.d.ts.map +1 -0
- package/dist/pool/install.d.ts +46 -0
- package/dist/pool/install.d.ts.map +1 -0
- package/dist/pool/login.d.ts +27 -0
- package/dist/pool/login.d.ts.map +1 -0
- package/dist/pool/policy.d.ts +100 -0
- package/dist/pool/policy.d.ts.map +1 -0
- package/dist/pool/server.d.ts +76 -0
- package/dist/pool/server.d.ts.map +1 -0
- package/dist/pool/store.d.ts +89 -0
- package/dist/pool/store.d.ts.map +1 -0
- package/dist/pool/types.d.ts +92 -0
- package/dist/pool/types.d.ts.map +1 -0
- package/dist/run.d.ts +14 -1
- 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 +1182 -850
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/control/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TrustedKey } from "@sessionpipe/core/control";
|
|
1
|
+
import { type RunLevel, type TrustedKey } from "@sessionpipe/core/control";
|
|
2
2
|
import { type Runner, type SecretStore, type StoreName } from "../secrets.js";
|
|
3
3
|
export interface PairedReceiver {
|
|
4
4
|
/** The receiver's origin, as the person typed it. */
|
|
@@ -18,11 +18,16 @@ export interface PairedReceiver {
|
|
|
18
18
|
})[];
|
|
19
19
|
paired_at: string;
|
|
20
20
|
}
|
|
21
|
+
/** How sessions run when a message arrives (CONTROL.md §2): the person's choice at
|
|
22
|
+
* this machine, never a receiver's. */
|
|
23
|
+
export declare const MODES: readonly ["safe", "auto", "inherit"];
|
|
24
|
+
export type ControlMode = (typeof MODES)[number];
|
|
25
|
+
export declare const parseMode: (m: unknown) => ControlMode | null;
|
|
21
26
|
export interface ControlConfig {
|
|
22
27
|
name: string;
|
|
23
28
|
/** Folders sessions may run in; compared on whole path segments, real paths. */
|
|
24
29
|
folders: string[];
|
|
25
|
-
mode:
|
|
30
|
+
mode: ControlMode;
|
|
26
31
|
receivers: PairedReceiver[];
|
|
27
32
|
/** Caps this machine keeps whatever a receiver sends (defaults in daemon.ts,
|
|
28
33
|
* LIMITS): new sessions running at once and per hour, headless Claude Code runs
|
|
@@ -76,7 +81,18 @@ export interface Ack {
|
|
|
76
81
|
code?: string;
|
|
77
82
|
detail?: string;
|
|
78
83
|
reply?: string;
|
|
84
|
+
/** A start's taken and delivered: what it ran with (CONTROL.md §6, Options). */
|
|
85
|
+
applied?: StartOptions;
|
|
79
86
|
}
|
|
87
|
+
/** What a session the daemon started runs with: its level and, when one was asked for,
|
|
88
|
+
* its model. Recorded at the start; every later run of the session reads it. */
|
|
89
|
+
export interface StartOptions {
|
|
90
|
+
/** A level, or `inherit`: started on an inherit machine with none named. */
|
|
91
|
+
permission: RunLevel;
|
|
92
|
+
model?: string;
|
|
93
|
+
}
|
|
94
|
+
/** A recorded entry as read back from disk, or null when it isn't one. */
|
|
95
|
+
export declare function startOptionsOf(v: unknown): StartOptions | null;
|
|
80
96
|
/** Acks not yet accepted: appended before they are sent, rewritten when sent, so a
|
|
81
97
|
* daemon restart re-sends rather than forgets (every message is acked exactly once
|
|
82
98
|
* with a final outcome; a duplicate is tolerated by the receiver). */
|
|
@@ -90,6 +106,9 @@ export declare class AckOutbox {
|
|
|
90
106
|
export interface DaemonState {
|
|
91
107
|
/** Original session → the fork its messages go to. */
|
|
92
108
|
copies: Record<string, string>;
|
|
109
|
+
/** Claude Code sessions the daemon started → what they run with (Codex and
|
|
110
|
+
* Antigravity keep theirs in the alias entry, aliases.ts). */
|
|
111
|
+
started: Record<string, StartOptions>;
|
|
93
112
|
}
|
|
94
113
|
export declare function readState(file: string): DaemonState;
|
|
95
114
|
export declare const writeState: (file: string, s: DaemonState) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/control/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/control/store.ts"],"names":[],"mappings":"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"}
|
package/dist/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoD/E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAO5F"}
|
package/dist/hook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":";AAkBA,iFAAiF;AACjF,MAAM,WAAW,MAAM;IACrB,KAAK,IAAI,MAAM,CAAC;IAChB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;0FAE0F;AAC1F,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":";AAkBA,iFAAiF;AACjF,MAAM,WAAW,MAAM;IACrB,KAAK,IAAI,MAAM,CAAC;IAChB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;0FAE0F;AAC1F,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwQhG"}
|
package/dist/hook.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.11.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/hook.ts
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -87,6 +87,17 @@ async function hookMain(argv, env, io) {
|
|
|
87
87
|
} catch {
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
if (harness === "claude-code" && event === "StatusLine") {
|
|
91
|
+
const limits = statusLimits(stdin);
|
|
92
|
+
if (limits.line) {
|
|
93
|
+
try {
|
|
94
|
+
io.stdout(`${limits.line}
|
|
95
|
+
`);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!limits.sig || !anySinkAbove(0) || !limitsDue(limits.sig)) return finish(false);
|
|
100
|
+
}
|
|
90
101
|
const TOOL = /^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/;
|
|
91
102
|
if (TOOL.test(event) && !anySinkAbove(0)) {
|
|
92
103
|
const id = sessionIdOf(stdin);
|
|
@@ -106,12 +117,18 @@ async function hookMain(argv, env, io) {
|
|
|
106
117
|
// (run.ts sweepJobs runs any stale job file, whatever spawned the worker).
|
|
107
118
|
"APPDATA",
|
|
108
119
|
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
120
|
+
// session.attended: Claude Code's own word, and SESSIONPIPE_CONTROL_JOB below, the
|
|
121
|
+
// control daemon's mark on a turn it runs for a person's message.
|
|
122
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
109
123
|
"CLAUDE_CONFIG_DIR",
|
|
110
124
|
"CLAUDE_PROJECT_DIR",
|
|
111
125
|
"CODEX_HOME",
|
|
112
126
|
// Devin's payload names no folder at all; this is the only thing that does.
|
|
113
127
|
"DEVIN_PROJECT_DIR",
|
|
114
128
|
"GEMINI_CLI_HOME",
|
|
129
|
+
"SESSIONPIPE_CONTROL_JOB",
|
|
130
|
+
// The launcher a script named for a session it starts (PROTOCOL.md §3 launched_by).
|
|
131
|
+
"SESSIONPIPE_LAUNCHED_BY",
|
|
115
132
|
// Devin's config file and session store are both resolved through these.
|
|
116
133
|
"XDG_CONFIG_HOME",
|
|
117
134
|
"XDG_DATA_HOME"
|
|
@@ -215,6 +232,31 @@ async function hookMain(argv, env, io) {
|
|
|
215
232
|
const m = /"(?:session_id|sessionId|conversationId)"\s*:\s*"([^"]{1,128})"/.exec(stdin);
|
|
216
233
|
return m?.[1] ?? "";
|
|
217
234
|
}
|
|
235
|
+
function statusLimits(stdin) {
|
|
236
|
+
let rl;
|
|
237
|
+
try {
|
|
238
|
+
rl = JSON.parse(stdin).rate_limits;
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
if (!rl || typeof rl !== "object") return { line: "", sig: "" };
|
|
242
|
+
const label = { five_hour: "5h", seven_day: "week", spend_limit: "spend" };
|
|
243
|
+
const parts = Object.entries(rl).filter(([, w]) => typeof w?.used_percentage === "number").map(([id, w]) => `${label[id] ?? id} ${Math.round(w.used_percentage)}%`);
|
|
244
|
+
return parts.length ? { line: parts.join(" \xB7 "), sig: JSON.stringify(rl) } : { line: "", sig: "" };
|
|
245
|
+
}
|
|
246
|
+
function limitsDue(sig) {
|
|
247
|
+
const dir = createHash("sha256").update(env.CLAUDE_CONFIG_DIR ?? "").digest("hex").slice(0, 12);
|
|
248
|
+
const stamp = path2.join(state, "beats", `${harness}-limits-${dir}`);
|
|
249
|
+
try {
|
|
250
|
+
if (readFileSync(stamp, "utf8") === sig || Date.now() - statSync(stamp).mtimeMs < 6e4) return false;
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
mkdirSync(path2.dirname(stamp), { recursive: true, mode: 448 });
|
|
255
|
+
writeFileSync(stamp, sig, { mode: 384 });
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
218
260
|
function beatDue(id) {
|
|
219
261
|
const stamp = path2.join(state, "beats", `${harness}-${id.replace(/[^A-Za-z0-9_-]/g, "_")}`);
|
|
220
262
|
try {
|
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": ";;;;AAOA,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,YAAY,WAAW,cAAc,cAAc,UAAU,qBAAqB;AAC3G,OAAOA,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,eAAe,qBAAqB;;;ACN7C,OAAO,SAAS;AAChB,OAAO,QAAQ;AACf,OAAO,UAAU;
|
|
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 // Claude Code's status line (install --status-line) runs on every refresh. It shows\n // the account's limits, and only a change becomes a job (limits.observed): at most\n // one a minute per config dir, and none when no sink takes tier 1.\n if (harness === \"claude-code\" && event === \"StatusLine\") {\n const limits = statusLimits(stdin);\n if (limits.line) {\n try {\n io.stdout(`${limits.line}\\n`);\n } catch {}\n }\n if (!limits.sig || !anySinkAbove(0) || !limitsDue(limits.sig)) return finish(false);\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 /** The status line's `rate_limits` as one short line (`5h 23% \u00B7 week 41%`), and as\n * the text a change is told by. Empty when the account has none. */\n function statusLimits(stdin: string): { line: string; sig: string } {\n let rl: unknown;\n try {\n rl = (JSON.parse(stdin) as { rate_limits?: unknown }).rate_limits;\n } catch {}\n if (!rl || typeof rl !== \"object\") return { line: \"\", sig: \"\" };\n const label: Record<string, string> = { five_hour: \"5h\", seven_day: \"week\", spend_limit: \"spend\" };\n const parts = Object.entries(rl as Record<string, { used_percentage?: unknown }>)\n .filter(([, w]) => typeof w?.used_percentage === \"number\")\n .map(([id, w]) => `${label[id] ?? id} ${Math.round(w.used_percentage as number)}%`);\n return parts.length ? { line: parts.join(\" \u00B7 \"), sig: JSON.stringify(rl) } : { line: \"\", sig: \"\" };\n }\n /** A change of limits, at most one a minute per config dir (one account each): the\n * stamp holds the last limits sent, its mtime when. */\n function limitsDue(sig: string): boolean {\n const dir = createHash(\"sha256\")\n .update(env.CLAUDE_CONFIG_DIR ?? \"\")\n .digest(\"hex\")\n .slice(0, 12);\n const stamp = path.join(state, \"beats\", `${harness}-limits-${dir}`);\n try {\n if (readFileSync(stamp, \"utf8\") === sig || 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, sig, { mode: 0o600 });\n } catch {}\n return true;\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 /** `sessionpipe pool \u2026` while the daemon runs: the daemon holds the pool's engine\n * (pins, credentials, the listener), so a change goes through it. `status` reads;\n * the rest are CONTROL.md \u00A76's actions, plus `import` (reads this machine's own\n * Claude Code logins, which no receiver may ask for). */\n | { op: \"pool\"; action: PoolOp; account?: string; order?: string[]; code?: string };\n\nexport type PoolOp =\n | \"status\"\n | \"on\"\n | \"off\"\n | \"enable\"\n | \"disable\"\n | \"remove\"\n | \"order\"\n | \"refresh\"\n | \"login.begin\"\n | \"login.complete\"\n | \"import\";\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 /** A pool op's answer: the ack it would earn (CONTROL.md \u00A76) and the pool as it now\n * stands (`status`: the fa\u00E7ade's PoolStatus; `detail`: the summary line, or a\n * login.begin's \"<pending id> <URL>\"). */\n | {\n op: \"pool\";\n outcome: \"delivered\" | \"refused\" | \"failed\" | \"unsupported\";\n code?: string;\n detail: string;\n status?: unknown;\n /** `import`: what was added and what was left out (control/pool.ts Imported). */\n imported?: unknown;\n };\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
|
}
|