sessionpipe 0.6.0 → 0.7.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe 0.6.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe 0.7.0 · Apache-2.0 · https://sessionpipe.org
2
2
 
3
3
  // src/http-sink.ts
4
4
  import { createHash, createHmac } from "node:crypto";
@@ -92,16 +92,16 @@ var HttpSink = class {
92
92
  import {
93
93
  appendFileSync as appendFileSync3,
94
94
  existsSync as existsSync8,
95
- mkdirSync as mkdirSync7,
95
+ mkdirSync as mkdirSync8,
96
96
  readdirSync as readdirSync6,
97
- readFileSync as readFileSync8,
98
- renameSync as renameSync2,
97
+ readFileSync as readFileSync9,
98
+ renameSync as renameSync3,
99
99
  statSync as statSync5,
100
100
  unlinkSync as unlinkSync4,
101
- writeFileSync as writeFileSync6
101
+ writeFileSync as writeFileSync7
102
102
  } from "node:fs";
103
103
  import os3 from "node:os";
104
- import path12 from "node:path";
104
+ import path13 from "node:path";
105
105
 
106
106
  // ../core/src/adapters/antigravity.ts
107
107
  import {
@@ -289,6 +289,9 @@ function readClaude(lines, from) {
289
289
  }
290
290
  return pairTurns(msgs, endOf(lines));
291
291
  }
292
+ function codexInjected(text) {
293
+ return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
294
+ }
292
295
  function readCodex(lines, from) {
293
296
  const msgs = [];
294
297
  for (let i = from; i < lines.length; i++) {
@@ -300,7 +303,7 @@ function readCodex(lines, from) {
300
303
  const text = textParts(p.content, ["input_text", "output_text"]).trim();
301
304
  if (!text) continue;
302
305
  if (p.role === "user") {
303
- if (/^<[a-z_-]+>/i.test(text)) continue;
306
+ if (codexInjected(text)) continue;
304
307
  msgs.push({ role: "user", text, at, line: i });
305
308
  } else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
306
309
  }
@@ -471,20 +474,19 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
471
474
  // ../core/src/adapters/antigravity.ts
472
475
  var NAME = "antigravity";
473
476
  var HOOK_NAME = "sessionpipe";
474
- var AG_EVENTS = ["PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
477
+ var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
478
+ var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
479
+ var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
475
480
  var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(HOME, ".gemini");
476
481
  var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
477
482
  var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
478
483
  var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
479
484
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
480
485
  var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
481
- function entry(cmd, lean) {
486
+ function entry(cmd) {
482
487
  const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
483
488
  const out = { enabled: true };
484
- for (const e of AG_EVENTS) {
485
- if (lean && e === "PreToolUse") continue;
486
- out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
487
- }
489
+ for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
488
490
  return out;
489
491
  }
490
492
  var antigravity = {
@@ -492,12 +494,12 @@ var antigravity = {
492
494
  events: AG_EVENTS,
493
495
  detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
494
496
  configFiles: (env = process.env) => [hooksFile(env)],
495
- install(cmd, env = process.env, opts = {}) {
497
+ install(cmd, env = process.env) {
496
498
  const file = hooksFile(env);
497
499
  const f = readJsonFile(file);
498
500
  if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
499
501
  const hooks = f.value;
500
- const e = entry(cmd, opts.lean);
502
+ const e = entry(cmd);
501
503
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
502
504
  hooks[HOOK_NAME] = e;
503
505
  mkdirSync2(path4.dirname(file), { recursive: true });
@@ -526,14 +528,14 @@ var antigravity = {
526
528
  writeFileSync2(file, formatJson(hooks, f));
527
529
  return [{ file, changed: true }];
528
530
  },
529
- installed(cmd, env = process.env, opts = {}) {
531
+ installed(cmd, env = process.env) {
530
532
  const file = hooksFile(env);
531
533
  const f = readJsonFile(file);
532
534
  const have = f.ok ? f.value[HOOK_NAME] : void 0;
533
535
  return [
534
536
  {
535
537
  file,
536
- state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
538
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
537
539
  }
538
540
  ];
539
541
  },
@@ -545,7 +547,8 @@ var antigravity = {
545
547
  } catch {
546
548
  }
547
549
  const id = s.conversationId ?? s.session_id ?? s.sessionId;
548
- if (!id) return { session: { id: "" }, events: [], stdout: "{}\n" };
550
+ const stdout = antigravityAnswer(event);
551
+ if (!id) return { session: { id: "" }, events: [], stdout };
549
552
  const workspaces = agWorkspaces(s.workspacePaths);
550
553
  const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
551
554
  if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
@@ -566,7 +569,8 @@ var antigravity = {
566
569
  if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
567
570
  break;
568
571
  case "PostToolUse":
569
- if (tool)
572
+ if (!tool) events.push(ev("session.heartbeat", {}));
573
+ else
570
574
  events.push(
571
575
  ev("tool.ended", {
572
576
  tool,
@@ -580,9 +584,9 @@ var antigravity = {
580
584
  events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
581
585
  break;
582
586
  default:
583
- return { session, events: [], stdout: "{}\n" };
587
+ return { session, events: [], stdout };
584
588
  }
585
- const r = { session, events, stdout: "{}\n", hints: { workspaces } };
589
+ const r = { session, events, stdout, hints: { workspaces } };
586
590
  if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
587
591
  return r;
588
592
  },
@@ -1537,7 +1541,8 @@ function uninstallNotify(env) {
1537
1541
  }
1538
1542
  return { file, changed: false };
1539
1543
  }
1540
- function findRollout(threadId, env) {
1544
+ function findRollout(threadId, env = process.env) {
1545
+ if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
1541
1546
  let best = null;
1542
1547
  const walk = (dir, depth) => {
1543
1548
  let names = [];
@@ -1548,7 +1553,7 @@ function findRollout(threadId, env) {
1548
1553
  }
1549
1554
  for (const n of names.sort().reverse()) {
1550
1555
  const p = path6.join(dir, n);
1551
- if (n.endsWith(".jsonl") && n.includes(threadId)) {
1556
+ if (n.endsWith(`-${threadId}.jsonl`)) {
1552
1557
  best = p;
1553
1558
  return;
1554
1559
  }
@@ -1753,7 +1758,7 @@ function codexFacts(file) {
1753
1758
  const p = j.payload;
1754
1759
  if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
1755
1760
  const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
1756
- if (!text || /^<[a-z_-]+>/i.test(text)) continue;
1761
+ if (!text || codexInjected(text)) continue;
1757
1762
  const line = text.split("\n").find((l) => l.trim()) ?? "";
1758
1763
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1759
1764
  out.title_source = "first-ask";
@@ -2575,14 +2580,39 @@ var StdoutSink = class {
2575
2580
  }
2576
2581
  };
2577
2582
 
2583
+ // src/control/aliases.ts
2584
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
2585
+ import path11 from "node:path";
2586
+ var aliasFile = (controlDir) => path11.join(controlDir, "aliases.json");
2587
+ function readAliases(file) {
2588
+ try {
2589
+ const j = JSON.parse(readFileSync8(file, "utf8"));
2590
+ if (!j || typeof j !== "object" || Array.isArray(j)) return {};
2591
+ const out = {};
2592
+ for (const [k, v] of Object.entries(j)) {
2593
+ const a = v;
2594
+ if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
2595
+ out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
2596
+ }
2597
+ return out;
2598
+ } catch {
2599
+ return {};
2600
+ }
2601
+ }
2602
+ function receiverIdOf(all, harness, id) {
2603
+ const prefix = `${harness}:`;
2604
+ for (const [k, a] of Object.entries(all)) if (a.id === id && k.startsWith(prefix)) return k.slice(prefix.length);
2605
+ return null;
2606
+ }
2607
+
2578
2608
  // src/control/local.ts
2579
2609
  import net from "node:net";
2580
2610
  import os2 from "node:os";
2581
- import path11 from "node:path";
2611
+ import path12 from "node:path";
2582
2612
  function socketPath(state, env = process.env) {
2583
2613
  if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
2584
2614
  if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
2585
- return path11.join(state, "control.sock");
2615
+ return path12.join(state, "control.sock");
2586
2616
  }
2587
2617
  function ask(sock, req, timeoutMs) {
2588
2618
  return new Promise((resolve) => {
@@ -2646,16 +2676,16 @@ function sinkWithSecrets(s, run = defaultRunner) {
2646
2676
  }
2647
2677
 
2648
2678
  // src/run.ts
2649
- async function tellDaemon(state, job, session, transcript) {
2679
+ async function tellDaemon(state, job, session, cwd, transcript) {
2650
2680
  const sock = socketPath(state);
2651
2681
  if (process.platform !== "win32" && !existsSync8(sock)) return;
2652
2682
  await ask(
2653
2683
  sock,
2654
2684
  {
2655
2685
  op: "event",
2656
- session: `claude-code:${session}`,
2686
+ session: `${job.harness}:${session}`,
2657
2687
  event: job.event,
2658
- cwd: job.cwd,
2688
+ ...cwd ? { cwd } : {},
2659
2689
  ...transcript ? { transcript } : {}
2660
2690
  },
2661
2691
  300
@@ -2663,17 +2693,17 @@ async function tellDaemon(state, job, session, transcript) {
2663
2693
  }
2664
2694
  function enqueueJob(state, job) {
2665
2695
  const jobs = jobsDir(state);
2666
- mkdirSync7(jobs, { recursive: true, mode: 448 });
2667
- const f = path12.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
2668
- writeFileSync6(f, JSON.stringify(job), { mode: 384 });
2696
+ mkdirSync8(jobs, { recursive: true, mode: 448 });
2697
+ const f = path13.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
2698
+ writeFileSync7(f, JSON.stringify(job), { mode: 384 });
2669
2699
  return f;
2670
2700
  }
2671
2701
  function log(state, line) {
2672
2702
  try {
2673
- mkdirSync7(state, { recursive: true, mode: 448 });
2674
- const f = path12.join(state, "log");
2703
+ mkdirSync8(state, { recursive: true, mode: 448 });
2704
+ const f = path13.join(state, "log");
2675
2705
  try {
2676
- if (statSync5(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
2706
+ if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
2677
2707
  } catch {
2678
2708
  }
2679
2709
  appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
@@ -2682,11 +2712,11 @@ function log(state, line) {
2682
2712
  }
2683
2713
  }
2684
2714
  function factsState(state) {
2685
- const file = (h, s) => path12.join(state, "facts", safeId(h), `${safeId(s)}.json`);
2715
+ const file = (h, s) => path13.join(state, "facts", safeId(h), `${safeId(s)}.json`);
2686
2716
  return {
2687
2717
  get(h, s) {
2688
2718
  try {
2689
- return JSON.parse(readFileSync8(file(h, s), "utf8"));
2719
+ return JSON.parse(readFileSync9(file(h, s), "utf8"));
2690
2720
  } catch {
2691
2721
  return {};
2692
2722
  }
@@ -2695,8 +2725,8 @@ function factsState(state) {
2695
2725
  const cur = this.get(h, s);
2696
2726
  const next = { ...cur, ...patch };
2697
2727
  if (JSON.stringify(next) === JSON.stringify(cur)) return;
2698
- mkdirSync7(path12.dirname(file(h, s)), { recursive: true, mode: 448 });
2699
- writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
2728
+ mkdirSync8(path13.dirname(file(h, s)), { recursive: true, mode: 448 });
2729
+ writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
2700
2730
  }
2701
2731
  };
2702
2732
  }
@@ -2724,30 +2754,33 @@ async function runJob(job, opts = {}) {
2724
2754
  if (!adapter) return { events: [], delivered: {} };
2725
2755
  const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
2726
2756
  if (!r || !r.session.id) return { events: [], delivered: {} };
2727
- if (job.harness === "claude-code") await tellDaemon(state, job, r.session.id, r.transcript);
2757
+ const id = receiverIdOf(readAliases(aliasFile(path13.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
2758
+ await tellDaemon(state, job, id, r.session.cwd, r.transcript);
2728
2759
  const outbox = new Outbox(state);
2729
- const ref = { harness: job.harness, session: r.session.id };
2760
+ const ref = { harness: job.harness, session: id };
2730
2761
  let release = takeLock(state, ref);
2731
2762
  for (let wait = 50; !release && wait <= 3200; wait *= 2) {
2732
2763
  await new Promise((res) => setTimeout(res, wait));
2733
2764
  release = takeLock(state, ref);
2734
2765
  }
2735
2766
  if (!release) {
2736
- log(state, `${job.harness} ${job.event}: session ${r.session.id} locked for 6 s, re-queued`);
2767
+ log(state, `${job.harness} ${job.event}: session ${id} locked for 6 s, re-queued`);
2737
2768
  enqueueJob(state, job);
2738
2769
  return { events: [], delivered: {} };
2739
2770
  }
2740
2771
  try {
2741
2772
  const facts = factsState(state);
2742
- const mem = facts.get(job.harness, r.session.id);
2773
+ const mem = facts.get(job.harness, id);
2743
2774
  const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
2744
2775
  let learned = {};
2745
2776
  if (heavy) {
2746
2777
  const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
2747
2778
  const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
2748
2779
  learned = rest;
2749
- if (harness_version) facts.save(job.harness, r.session.id, { harness_version });
2750
- facts.save(job.harness, r.session.id, { session: { ...mem.session, ...learned } });
2780
+ if (harness_version) facts.save(job.harness, id, { harness_version });
2781
+ facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
2782
+ if (job.harness === "antigravity" && r.session.cwd && path13.isAbsolute(r.session.cwd))
2783
+ facts.save(job.harness, id, { cwd: r.session.cwd });
2751
2784
  }
2752
2785
  const known = mem.session ?? {};
2753
2786
  const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
@@ -2757,13 +2790,13 @@ async function runJob(job, opts = {}) {
2757
2790
  ...learned,
2758
2791
  ...git,
2759
2792
  ...learned.branch ? { branch: learned.branch } : {},
2760
- id: r.session.id,
2793
+ id,
2761
2794
  seq: 0,
2762
2795
  machine: machineName(cfg, os3.hostname()),
2763
2796
  ...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
2764
2797
  ...r.session.model ? { model: r.session.model } : {}
2765
2798
  };
2766
- const version = facts.get(job.harness, r.session.id).harness_version ?? void 0;
2799
+ const version = facts.get(job.harness, id).harness_version ?? void 0;
2767
2800
  const events = [];
2768
2801
  for (const a of r.events) {
2769
2802
  const e = makeEvent(a, {
@@ -2775,7 +2808,7 @@ async function runJob(job, opts = {}) {
2775
2808
  }
2776
2809
  const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
2777
2810
  if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
2778
- const cur = facts.get(job.harness, r.session.id);
2811
+ const cur = facts.get(job.harness, id);
2779
2812
  const fromLine = Number(cur.transcript_line) || 0;
2780
2813
  let turnNo = Number(cur.transcript_turns) || 0;
2781
2814
  const read = adapter.readTranscript(r.transcript, fromLine);
@@ -2795,17 +2828,13 @@ async function runJob(job, opts = {}) {
2795
2828
  )
2796
2829
  );
2797
2830
  }
2798
- if (read.turns.length)
2799
- facts.save(job.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
2831
+ if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
2800
2832
  }
2801
2833
  outbox.append(
2802
2834
  ref,
2803
2835
  events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
2804
2836
  );
2805
- log(
2806
- state,
2807
- `${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${r.session.id.slice(0, 8)})`
2808
- );
2837
+ log(state, `${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${id.slice(0, 8)})`);
2809
2838
  const delivered = await flush(state, cfg.sinks, outbox, ref);
2810
2839
  if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
2811
2840
  return { events, delivered };
@@ -2896,7 +2925,7 @@ function applySinkVerdict(name, res) {
2896
2925
  } catch {
2897
2926
  }
2898
2927
  }
2899
- var jobsDir = (state) => path12.join(state, "jobs");
2928
+ var jobsDir = (state) => path13.join(state, "jobs");
2900
2929
  export {
2901
2930
  HttpSink,
2902
2931
  buildSinks,