sessionpipe-conformance 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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // sessionpipe-conformance 0.9.0 · Apache-2.0 · https://sessionpipe.org
2
+ // sessionpipe-conformance 0.10.0 · Apache-2.0 · https://sessionpipe.org
3
3
 
4
4
  // src/cli.ts
5
5
  import { readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
@@ -804,14 +804,28 @@ var claudeCode = {
804
804
  case "SessionStart":
805
805
  events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
806
806
  break;
807
- case "UserPromptSubmit":
807
+ case "UserPromptSubmit": {
808
+ const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
808
809
  events.push(
809
810
  ev("turn.started", {
810
811
  turn_id,
811
- prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
812
+ prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
813
+ // Claude Code's hook schema documents `source`
814
+ // (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
815
+ // payload may omit it while the field rolls out; 2.1.304's payload has no such
816
+ // key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
817
+ // to extend.
818
+ submitted_by: harnessWord(s.source),
819
+ // One of Claude Code's own prompt wrappers: the prompt is a block the harness
820
+ // wrote, so it is not the person's words. Absent is unknown, never "the person" —
821
+ // and `true` is never "no person caused this turn", because a Talk or
822
+ // `sessionpipe wait` delivery hands the person's own message over inside a
823
+ // <task-notification> (PROTOCOL.md §4).
824
+ injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
812
825
  })
813
826
  );
814
827
  break;
828
+ }
815
829
  case "PreToolUse":
816
830
  if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
817
831
  break;
@@ -866,7 +880,13 @@ var claudeCode = {
866
880
  );
867
881
  break;
868
882
  case "Stop":
869
- events.push(ev("turn.ended", { turn_id, reason: "stop" }));
883
+ events.push(
884
+ ev("turn.ended", {
885
+ turn_id,
886
+ reason: "stop",
887
+ background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
888
+ })
889
+ );
870
890
  break;
871
891
  case "StopFailure":
872
892
  events.push(
@@ -909,6 +929,8 @@ var claudeCode = {
909
929
  if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
910
930
  const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
911
931
  if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
932
+ const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
933
+ if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
912
934
  if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
913
935
  const r = { session, events, hints };
914
936
  if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
@@ -929,6 +951,7 @@ var claudeCode = {
929
951
  const cwd = out.cwd ?? session.cwd;
930
952
  Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
931
953
  if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
954
+ if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
932
955
  return out;
933
956
  },
934
957
  backfill(sinceMs, env = process.env) {
@@ -964,6 +987,9 @@ function notClaudeCode(id, s) {
964
987
  function pickSource(v) {
965
988
  return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
966
989
  }
990
+ var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
991
+ var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
992
+ var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
967
993
  function errText(v) {
968
994
  if (typeof v === "string") return v.slice(0, 2e3);
969
995
  if (v && typeof v === "object") {
@@ -1080,7 +1106,27 @@ function claudeLogin(dir, home = homeDir()) {
1080
1106
  } catch {
1081
1107
  }
1082
1108
  }
1083
- return null;
1109
+ return apiAccount(dir);
1110
+ }
1111
+ var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
1112
+ function apiAccount(dir) {
1113
+ let base;
1114
+ try {
1115
+ base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
1116
+ } catch {
1117
+ return null;
1118
+ }
1119
+ if (typeof base !== "string") return null;
1120
+ let u;
1121
+ try {
1122
+ u = new URL(base.trim());
1123
+ } catch {
1124
+ return null;
1125
+ }
1126
+ if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
1127
+ return null;
1128
+ const origin = `${u.protocol}//${u.host}`;
1129
+ return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
1084
1130
  }
1085
1131
  var accountCache = /* @__PURE__ */ new Map();
1086
1132
  function claudeAccount(dir) {
@@ -1561,7 +1607,7 @@ var codex = {
1561
1607
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
1562
1608
  } catch {
1563
1609
  }
1564
- const id = s.session_id;
1610
+ const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
1565
1611
  if (!id) return null;
1566
1612
  if (s.transcript_path === null) return null;
1567
1613
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
@@ -2323,6 +2369,22 @@ function prune5(o) {
2323
2369
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
2324
2370
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
2325
2371
 
2372
+ // ../core/src/launched-by.ts
2373
+ var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
2374
+ var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
2375
+ function launchedBy(env) {
2376
+ const v = env[LAUNCHED_BY_ENV]?.trim();
2377
+ return v && LAUNCHED_BY.test(v) ? v : void 0;
2378
+ }
2379
+ function readHook(adapter, input) {
2380
+ const r = adapter.fromHook(input);
2381
+ if (r && !r.session.parent_id) {
2382
+ const launcher = launchedBy(input.env);
2383
+ if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
2384
+ }
2385
+ return r;
2386
+ }
2387
+
2326
2388
  // src/index.ts
2327
2389
  function matches(expect, got, pathStr = "") {
2328
2390
  if (expect === "*") return null;
@@ -2353,7 +2415,7 @@ async function runAdapterFixtures(files) {
2353
2415
  results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
2354
2416
  continue;
2355
2417
  }
2356
- const r = adapter.fromHook({
2418
+ const r = readHook(adapter, {
2357
2419
  argv: fx.input.argv,
2358
2420
  stdin: fx.input.stdin,
2359
2421
  env: fx.input.env ?? {},