sessionpipe 0.1.1 → 0.2.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/dist/run.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,UAAU,EAMf,MAAM,EAIN,KAAK,IAAI,EACT,KAAK,UAAU,EAOhB,MAAM,mBAAmB,CAAC;AAI3B,QAAA,MAAM,OAAO,QAAkF,CAAC;AAEhG,MAAM,WAAW,GAAG;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAM1D;AAED;;;2BAG2B;AAC3B,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,SAAO,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAuB9F;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CASrD;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAkBpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,CAWtD;AAED,mFAAmF;AACnF,wBAAsB,MAAM,CAC1B,GAAG,EAAE,GAAG,EACR,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,OAAO,CAAC;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAyGjE;AAKD;;;;;4DAK4D;AAC5D,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,UAAU,EAAE,EACzB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAmEjC;AAaD,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,eAAO,MAAM,WAAW,QAAO,MAAkC,CAAC;AAClE,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAAkC,CAAC;AAC3E,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,OAAqC,CAAC"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,UAAU,EAMf,MAAM,EAIN,KAAK,IAAI,EACT,KAAK,UAAU,EAOhB,MAAM,mBAAmB,CAAC;AAqB3B,QAAA,MAAM,OAAO,QAAkF,CAAC;AAEhG,MAAM,WAAW,GAAG;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAM1D;AAED;;;2BAG2B;AAC3B,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,SAAO,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAuB9F;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CASrD;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAkBpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,CAWtD;AAED,mFAAmF;AACnF,wBAAsB,MAAM,CAC1B,GAAG,EAAE,GAAG,EACR,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,OAAO,CAAC;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CA6GjE;AAKD;;;;;4DAK4D;AAC5D,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,UAAU,EAAE,EACzB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAmEjC;AAaD,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,eAAO,MAAM,WAAW,QAAO,MAAkC,CAAC;AAClE,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAAkC,CAAC;AAC3E,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,OAAqC,CAAC"}
package/dist/worker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // sessionpipe 0.1.1 · Apache-2.0 · https://sessionpipe.org
2
+ // sessionpipe 0.2.0 · Apache-2.0 · https://sessionpipe.org
3
3
 
4
4
  // src/worker.ts
5
5
  import { readFileSync as readFileSync9, unlinkSync as unlinkSync5 } from "node:fs";
@@ -282,7 +282,7 @@ function entryFor(event, harness, cmd, o) {
282
282
  const matcher = o.matcherFor(event);
283
283
  return {
284
284
  ...matcher !== void 0 ? { matcher } : {},
285
- hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeout }]
285
+ hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeoutFor?.(event) ?? o.timeout }]
286
286
  };
287
287
  }
288
288
  function installClaudeShaped(file, harness, events, cmd, o) {
@@ -706,7 +706,13 @@ var CLAUDE_EVENTS = [
706
706
  ];
707
707
  var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
708
708
  var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
709
- var OPTS = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
709
+ var OPTS = {
710
+ matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
711
+ timeout: 5,
712
+ // The hook waits up to 125 s for a verified control answer, and only when control
713
+ // is paired on this machine; everywhere else it exits in milliseconds as before.
714
+ timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
715
+ };
710
716
  var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
711
717
  function claudeDirs(env = process.env, home = HOME) {
712
718
  const out = [];
@@ -1150,9 +1156,9 @@ function claudeFacts(file, sessionId, hints, state) {
1150
1156
  }
1151
1157
  const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
1152
1158
  const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
1153
- let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1154
- if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
1155
- ask = scanLines(file, (l) => {
1159
+ let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1160
+ if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
1161
+ ask2 = scanLines(file, (l) => {
1156
1162
  try {
1157
1163
  const t = askText(JSON.parse(l));
1158
1164
  return t ? appTitle(t) : null;
@@ -1160,17 +1166,17 @@ function claudeFacts(file, sessionId, hints, state) {
1160
1166
  return null;
1161
1167
  }
1162
1168
  });
1163
- if (!ask && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
1169
+ if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
1164
1170
  }
1165
- if (ask) {
1166
- out.first_ask = ask;
1167
- if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask });
1171
+ if (ask2) {
1172
+ out.first_ask = ask2;
1173
+ if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask2 });
1168
1174
  }
1169
1175
  if (pick) {
1170
1176
  out.title = String(pick.title).slice(0, 120);
1171
1177
  out.title_source = pick.source;
1172
- } else if (ask) {
1173
- out.title = ask;
1178
+ } else if (ask2) {
1179
+ out.title = ask2;
1174
1180
  out.title_source = "first-ask";
1175
1181
  }
1176
1182
  const acct = claudeAccount(dir);
@@ -2482,12 +2488,54 @@ import {
2482
2488
  unlinkSync as unlinkSync4,
2483
2489
  writeFileSync as writeFileSync6
2484
2490
  } from "node:fs";
2491
+ import os3 from "node:os";
2492
+ import path12 from "node:path";
2493
+
2494
+ // src/control/local.ts
2495
+ import net from "node:net";
2485
2496
  import os2 from "node:os";
2486
2497
  import path11 from "node:path";
2498
+ function socketPath(state, env = process.env) {
2499
+ if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
2500
+ if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
2501
+ return path11.join(state, "control.sock");
2502
+ }
2503
+ function ask(sock, req, timeoutMs) {
2504
+ return new Promise((resolve) => {
2505
+ let buf = "";
2506
+ let done = false;
2507
+ const s = net.connect(sock);
2508
+ const finish = (r) => {
2509
+ if (done) return;
2510
+ done = true;
2511
+ clearTimeout(t);
2512
+ s.destroy();
2513
+ resolve(r);
2514
+ };
2515
+ const t = setTimeout(() => finish(null), timeoutMs);
2516
+ s.on("connect", () => s.write(`${JSON.stringify(req)}
2517
+ `));
2518
+ s.on("data", (d) => {
2519
+ buf += d.toString("utf8");
2520
+ const i = buf.indexOf("\n");
2521
+ if (i < 0) return;
2522
+ try {
2523
+ finish(JSON.parse(buf.slice(0, i)));
2524
+ } catch {
2525
+ finish(null);
2526
+ }
2527
+ });
2528
+ s.on("error", () => finish(null));
2529
+ s.on("close", () => finish(null));
2530
+ });
2531
+ }
2487
2532
 
2488
2533
  // src/http-sink.ts
2489
- import { createHmac, randomUUID } from "node:crypto";
2534
+ import { createHash as createHash3, createHmac } from "node:crypto";
2490
2535
  var TIMEOUT_MS = 15e3;
2536
+ function webhookId(events) {
2537
+ return `msg_${createHash3("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
2538
+ }
2491
2539
  var HttpSink = class {
2492
2540
  constructor(cfg, tier) {
2493
2541
  this.cfg = cfg;
@@ -2510,7 +2558,7 @@ var HttpSink = class {
2510
2558
  const headers = { "content-type": "application/json", "sessionpipe-protocol": "1" };
2511
2559
  if (this.cfg.token) headers.authorization = `Bearer ${this.cfg.token}`;
2512
2560
  if (this.cfg.secret?.startsWith("whsec_")) {
2513
- const id = `msg_${randomUUID()}`;
2561
+ const id = webhookId(events);
2514
2562
  const ts = Math.floor(Date.now() / 1e3);
2515
2563
  const key = Buffer.from(this.cfg.secret.slice(6), "base64");
2516
2564
  const sig = createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64");
@@ -2571,10 +2619,25 @@ var HttpSink = class {
2571
2619
  };
2572
2620
 
2573
2621
  // src/run.ts
2622
+ async function tellDaemon(state, job2, session, transcript) {
2623
+ const sock = socketPath(state);
2624
+ if (process.platform !== "win32" && !existsSync8(sock)) return;
2625
+ await ask(
2626
+ sock,
2627
+ {
2628
+ op: "event",
2629
+ session: `claude-code:${session}`,
2630
+ event: job2.event,
2631
+ cwd: job2.cwd,
2632
+ ...transcript ? { transcript } : {}
2633
+ },
2634
+ 300
2635
+ );
2636
+ }
2574
2637
  function enqueueJob(state, job2) {
2575
2638
  const jobs = jobsDir(state);
2576
2639
  mkdirSync7(jobs, { recursive: true, mode: 448 });
2577
- const f = path11.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
2640
+ const f = path12.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
2578
2641
  writeFileSync6(f, JSON.stringify(job2), { mode: 384 });
2579
2642
  return f;
2580
2643
  }
@@ -2588,7 +2651,7 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20) {
2588
2651
  }
2589
2652
  let ran = 0;
2590
2653
  for (const n of names.sort().slice(0, limit)) {
2591
- const f = path11.join(jobs, n);
2654
+ const f = path12.join(jobs, n);
2592
2655
  let job2 = null;
2593
2656
  try {
2594
2657
  if (Date.now() - statSync5(f).mtimeMs < olderThanMs) continue;
@@ -2606,7 +2669,7 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20) {
2606
2669
  function log(state, line) {
2607
2670
  try {
2608
2671
  mkdirSync7(state, { recursive: true, mode: 448 });
2609
- const f = path11.join(state, "log");
2672
+ const f = path12.join(state, "log");
2610
2673
  try {
2611
2674
  if (statSync5(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
2612
2675
  } catch {
@@ -2617,7 +2680,7 @@ function log(state, line) {
2617
2680
  }
2618
2681
  }
2619
2682
  function factsState(state) {
2620
- const file = (h, s) => path11.join(state, "facts", safeId(h), `${safeId(s)}.json`);
2683
+ const file = (h, s) => path12.join(state, "facts", safeId(h), `${safeId(s)}.json`);
2621
2684
  return {
2622
2685
  get(h, s) {
2623
2686
  try {
@@ -2630,7 +2693,7 @@ function factsState(state) {
2630
2693
  const cur = this.get(h, s);
2631
2694
  const next = { ...cur, ...patch };
2632
2695
  if (JSON.stringify(next) === JSON.stringify(cur)) return;
2633
- mkdirSync7(path11.dirname(file(h, s)), { recursive: true, mode: 448 });
2696
+ mkdirSync7(path12.dirname(file(h, s)), { recursive: true, mode: 448 });
2634
2697
  writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
2635
2698
  }
2636
2699
  };
@@ -2642,7 +2705,7 @@ function buildSinks(sinks) {
2642
2705
  const tier = Math.min(s.tier, s.max_tier ?? 3);
2643
2706
  if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
2644
2707
  else if (s.url.startsWith("file:"))
2645
- out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os2.homedir()), tier, !!s.pii));
2708
+ out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os3.homedir()), tier, !!s.pii));
2646
2709
  else if (/^https?:\/\//.test(s.url)) out.push(new HttpSink(s, tier));
2647
2710
  }
2648
2711
  return out;
@@ -2654,6 +2717,7 @@ async function runJob(job2, opts = {}) {
2654
2717
  if (!adapter) return { events: [], delivered: {} };
2655
2718
  const r = adapter.fromHook({ argv: job2.argv, stdin: job2.stdin, env: { ...process.env, ...job2.env }, cwd: job2.cwd });
2656
2719
  if (!r || !r.session.id) return { events: [], delivered: {} };
2720
+ if (job2.harness === "claude-code") await tellDaemon(state, job2, r.session.id, r.transcript);
2657
2721
  const outbox = new Outbox(state);
2658
2722
  const ref = { harness: job2.harness, session: r.session.id };
2659
2723
  let release = takeLock(state, ref);
@@ -2688,7 +2752,7 @@ async function runJob(job2, opts = {}) {
2688
2752
  ...learned.branch ? { branch: learned.branch } : {},
2689
2753
  id: r.session.id,
2690
2754
  seq: 0,
2691
- machine: machineName(cfg, os2.hostname()),
2755
+ machine: machineName(cfg, os3.hostname()),
2692
2756
  ...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
2693
2757
  ...r.session.model ? { model: r.session.model } : {}
2694
2758
  };
@@ -2791,7 +2855,7 @@ async function flush(state, sinkConfigs, outbox, first) {
2791
2855
  const { events, offset: next } = outbox.read(ref, offset, room);
2792
2856
  if (!events.length) break;
2793
2857
  for (const e of events) {
2794
- const f = filterEvent(e, sink.tier, sink.pii, { home: os2.homedir() });
2858
+ const f = filterEvent(e, sink.tier, sink.pii, { home: os3.homedir() });
2795
2859
  if (f) batch.push(f);
2796
2860
  }
2797
2861
  offset = next;
@@ -2823,7 +2887,7 @@ function applySinkVerdict(name, res) {
2823
2887
  } catch {
2824
2888
  }
2825
2889
  }
2826
- var jobsDir = (state) => path11.join(state, "jobs");
2890
+ var jobsDir = (state) => path12.join(state, "jobs");
2827
2891
 
2828
2892
  // src/worker.ts
2829
2893
  var jobFile = process.argv[2];