sessionpipe-conformance 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/dist/cli.js +1010 -755
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1007 -752
- package/dist/index.js.map +4 -4
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe-conformance 0.
|
|
2
|
+
// sessionpipe-conformance 0.11.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as
|
|
6
|
-
import
|
|
5
|
+
import { readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
|
6
|
+
import path9 from "node:path";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
10
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
11
11
|
|
|
12
|
-
// ../core/src/adapters/antigravity.ts
|
|
13
|
-
import {
|
|
14
|
-
existsSync as existsSync2,
|
|
15
|
-
mkdirSync as mkdirSync2,
|
|
16
|
-
readdirSync as readdirSync2,
|
|
17
|
-
readFileSync as readFileSync2,
|
|
18
|
-
realpathSync,
|
|
19
|
-
statSync as statSync2,
|
|
20
|
-
unlinkSync as unlinkSync2,
|
|
21
|
-
writeFileSync as writeFileSync2
|
|
22
|
-
} from "node:fs";
|
|
23
|
-
import path3 from "node:path";
|
|
24
|
-
|
|
25
12
|
// ../core/src/paths.ts
|
|
26
13
|
import os from "node:os";
|
|
27
14
|
var homeDir = () => os.homedir();
|
|
28
15
|
|
|
16
|
+
// ../core/src/adapters/claude-code.ts
|
|
17
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20
|
+
import { userInfo } from "node:os";
|
|
21
|
+
import path3 from "node:path";
|
|
22
|
+
|
|
29
23
|
// ../core/src/readers/files.ts
|
|
30
24
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
31
25
|
import path from "node:path";
|
|
@@ -116,6 +110,38 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
116
110
|
return out;
|
|
117
111
|
}
|
|
118
112
|
|
|
113
|
+
// ../core/src/readers/git.ts
|
|
114
|
+
import { execFileSync } from "node:child_process";
|
|
115
|
+
var cache = /* @__PURE__ */ new Map();
|
|
116
|
+
function gitFacts(cwd) {
|
|
117
|
+
if (!cwd) return {};
|
|
118
|
+
const hit = cache.get(cwd);
|
|
119
|
+
if (hit) return hit;
|
|
120
|
+
const run = (args) => {
|
|
121
|
+
try {
|
|
122
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
123
|
+
encoding: "utf8",
|
|
124
|
+
timeout: 1500,
|
|
125
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
126
|
+
}).trim();
|
|
127
|
+
} catch {
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const out = {};
|
|
132
|
+
const repo = run(["remote", "get-url", "origin"]);
|
|
133
|
+
if (repo) out.repo = stripCredentials(repo);
|
|
134
|
+
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
135
|
+
if (branch && branch !== "HEAD") out.branch = branch;
|
|
136
|
+
cache.set(cwd, out);
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function stripCredentials(url) {
|
|
140
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
141
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
142
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
// ../core/src/adapters/codex-rollout.ts
|
|
120
146
|
function codexSubagent(meta) {
|
|
121
147
|
if (meta?.type !== "session_meta") return false;
|
|
@@ -275,8 +301,10 @@ function readJsonFile(file) {
|
|
|
275
301
|
try {
|
|
276
302
|
raw = readFileSync(file, "utf8");
|
|
277
303
|
} catch {
|
|
278
|
-
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
304
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true, bom: false };
|
|
279
305
|
}
|
|
306
|
+
const bom = raw.charCodeAt(0) === 65279;
|
|
307
|
+
if (bom) raw = raw.slice(1);
|
|
280
308
|
let value;
|
|
281
309
|
try {
|
|
282
310
|
value = JSON.parse(raw);
|
|
@@ -296,12 +324,13 @@ function readJsonFile(file) {
|
|
|
296
324
|
raw,
|
|
297
325
|
indent,
|
|
298
326
|
eol,
|
|
299
|
-
finalNewline: /\r?\n$/.test(raw)
|
|
327
|
+
finalNewline: /\r?\n$/.test(raw),
|
|
328
|
+
bom
|
|
300
329
|
};
|
|
301
330
|
}
|
|
302
331
|
function formatJson(value, f) {
|
|
303
332
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
304
|
-
return f.finalNewline ? text + f.eol : text;
|
|
333
|
+
return (f.bom ? "\uFEFF" : "") + (f.finalNewline ? text + f.eol : text);
|
|
305
334
|
}
|
|
306
335
|
function readJson(file) {
|
|
307
336
|
const f = readJsonFile(file);
|
|
@@ -397,74 +426,79 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
397
426
|
return current === events.length ? "current" : "stale";
|
|
398
427
|
}
|
|
399
428
|
|
|
400
|
-
// ../core/src/adapters/
|
|
401
|
-
var NAME = "
|
|
402
|
-
var
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
429
|
+
// ../core/src/adapters/claude-code.ts
|
|
430
|
+
var NAME = "claude-code";
|
|
431
|
+
var CLAUDE_EVENTS = [
|
|
432
|
+
"SessionStart",
|
|
433
|
+
"UserPromptSubmit",
|
|
434
|
+
"PreToolUse",
|
|
435
|
+
"PostToolUse",
|
|
436
|
+
"PostToolUseFailure",
|
|
437
|
+
"PermissionRequest",
|
|
438
|
+
"Notification",
|
|
439
|
+
"Stop",
|
|
440
|
+
"SubagentStart",
|
|
441
|
+
"SubagentStop",
|
|
442
|
+
"PreCompact",
|
|
443
|
+
"PostCompact",
|
|
444
|
+
"SessionEnd",
|
|
445
|
+
"StopFailure"
|
|
446
|
+
];
|
|
447
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
448
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
449
|
+
var OPTS = {
|
|
450
|
+
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
451
|
+
timeout: 5,
|
|
452
|
+
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
453
|
+
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
454
|
+
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
455
|
+
};
|
|
456
|
+
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
457
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
458
|
+
const out = [];
|
|
459
|
+
const add = (d) => {
|
|
460
|
+
if (!d) return;
|
|
461
|
+
const r = path3.resolve(d);
|
|
462
|
+
if (!out.includes(r)) out.push(r);
|
|
463
|
+
};
|
|
464
|
+
add(path3.join(home, ".claude"));
|
|
465
|
+
add(env.CLAUDE_CONFIG_DIR);
|
|
466
|
+
try {
|
|
467
|
+
for (const e of readdirSync2(home, { withFileTypes: true })) {
|
|
468
|
+
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
469
|
+
const d = path3.join(home, e.name);
|
|
470
|
+
if (existsSync2(path3.join(d, "projects")) || existsSync2(path3.join(d, "settings.json"))) add(d);
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
return out.filter((d, i) => i === 0 || existsSync2(d));
|
|
417
475
|
}
|
|
418
|
-
var
|
|
476
|
+
var settingsIn = (dir) => path3.join(dir, "settings.json");
|
|
477
|
+
var configDirOf = (transcript) => path3.dirname(path3.dirname(path3.dirname(transcript)));
|
|
478
|
+
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
479
|
+
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
480
|
+
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
481
|
+
var claudeCode = {
|
|
419
482
|
name: NAME,
|
|
420
|
-
events:
|
|
421
|
-
detect: (env = process.env) =>
|
|
422
|
-
configFiles: (env = process.env) =>
|
|
423
|
-
install(cmd, env = process.env) {
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
hooks
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
];
|
|
441
|
-
},
|
|
442
|
-
uninstall(env = process.env, opts = {}) {
|
|
443
|
-
const file = hooksFile(env);
|
|
444
|
-
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
445
|
-
const f = readJsonFile(file);
|
|
446
|
-
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
447
|
-
const hooks = f.value;
|
|
448
|
-
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
449
|
-
delete hooks[HOOK_NAME];
|
|
450
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
451
|
-
unlinkSync2(file);
|
|
452
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
453
|
-
}
|
|
454
|
-
writeFileSync2(file, formatJson(hooks, f));
|
|
455
|
-
return [{ file, changed: true }];
|
|
456
|
-
},
|
|
457
|
-
installed(cmd, env = process.env) {
|
|
458
|
-
const file = hooksFile(env);
|
|
459
|
-
const f = readJsonFile(file);
|
|
460
|
-
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
461
|
-
return [
|
|
462
|
-
{
|
|
463
|
-
file,
|
|
464
|
-
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
465
|
-
}
|
|
466
|
-
];
|
|
467
|
-
},
|
|
483
|
+
events: CLAUDE_EVENTS,
|
|
484
|
+
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync2(d)),
|
|
485
|
+
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
486
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
487
|
+
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
488
|
+
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
489
|
+
return line ? [hooks, line] : [hooks];
|
|
490
|
+
}),
|
|
491
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
492
|
+
const line = statusLine(settingsIn(d), false);
|
|
493
|
+
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
494
|
+
created: opts.created?.includes(settingsIn(d))
|
|
495
|
+
});
|
|
496
|
+
return line && !hooks.changed ? line : hooks;
|
|
497
|
+
}),
|
|
498
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
499
|
+
file: settingsIn(d),
|
|
500
|
+
state: installedClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS)
|
|
501
|
+
})),
|
|
468
502
|
fromHook(input) {
|
|
469
503
|
const event = input.argv[1] ?? "";
|
|
470
504
|
let s = {};
|
|
@@ -473,310 +507,735 @@ var antigravity = {
|
|
|
473
507
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
474
508
|
} catch {
|
|
475
509
|
}
|
|
476
|
-
const id = s.
|
|
477
|
-
|
|
478
|
-
if (
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
if (typeof s.
|
|
482
|
-
|
|
483
|
-
const
|
|
510
|
+
const id = s.session_id ?? s.sessionId;
|
|
511
|
+
if (!id) return null;
|
|
512
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
513
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
514
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
515
|
+
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
516
|
+
session.parent_id = null;
|
|
517
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
518
|
+
const tool = validTool(s.tool_name) ? s.tool_name : void 0;
|
|
519
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
520
|
+
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
521
|
+
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
522
|
+
const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
484
523
|
const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
|
|
485
524
|
const events = [];
|
|
486
525
|
switch (event) {
|
|
487
|
-
case "
|
|
488
|
-
|
|
489
|
-
events.push(ev("session.started", { source: "unknown" }));
|
|
490
|
-
events.push(ev("turn.started", {}));
|
|
491
|
-
} else events.push(ev("session.heartbeat", {}));
|
|
526
|
+
case "SessionStart":
|
|
527
|
+
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
492
528
|
break;
|
|
493
|
-
case "
|
|
529
|
+
case "UserPromptSubmit": {
|
|
530
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
531
|
+
events.push(
|
|
532
|
+
ev("turn.started", {
|
|
533
|
+
turn_id,
|
|
534
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
535
|
+
// Claude Code's hook schema documents `source`
|
|
536
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
537
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
538
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
539
|
+
// to extend.
|
|
540
|
+
submitted_by: harnessWord(s.source),
|
|
541
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
542
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
543
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
544
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
545
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
546
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
547
|
+
})
|
|
548
|
+
);
|
|
494
549
|
break;
|
|
550
|
+
}
|
|
495
551
|
case "PreToolUse":
|
|
496
|
-
if (tool) events.push(ev("tool.started", { tool, input:
|
|
552
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
497
553
|
break;
|
|
498
554
|
case "PostToolUse":
|
|
499
|
-
if (
|
|
500
|
-
else
|
|
555
|
+
if (tool)
|
|
501
556
|
events.push(
|
|
502
557
|
ev("tool.ended", {
|
|
503
558
|
tool,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
559
|
+
call_id,
|
|
560
|
+
turn_id,
|
|
561
|
+
agent_id,
|
|
562
|
+
ms,
|
|
563
|
+
ok: true,
|
|
564
|
+
input: s.tool_input,
|
|
565
|
+
output: s.tool_response
|
|
566
|
+
})
|
|
567
|
+
);
|
|
568
|
+
break;
|
|
569
|
+
case "PostToolUseFailure":
|
|
570
|
+
if (tool)
|
|
571
|
+
events.push(
|
|
572
|
+
ev("tool.ended", {
|
|
573
|
+
tool,
|
|
574
|
+
call_id,
|
|
575
|
+
turn_id,
|
|
576
|
+
agent_id,
|
|
577
|
+
ms,
|
|
578
|
+
ok: false,
|
|
579
|
+
error: errText(s.error ?? s.tool_response),
|
|
580
|
+
input: s.tool_input,
|
|
581
|
+
output: s.tool_response
|
|
507
582
|
})
|
|
508
583
|
);
|
|
509
584
|
break;
|
|
585
|
+
case "PermissionRequest":
|
|
586
|
+
events.push(
|
|
587
|
+
ev("attention.needed", {
|
|
588
|
+
attention_id: call_id ?? permissionId(),
|
|
589
|
+
kind: "permission",
|
|
590
|
+
tool,
|
|
591
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
592
|
+
})
|
|
593
|
+
);
|
|
594
|
+
break;
|
|
595
|
+
case "Notification":
|
|
596
|
+
events.push(
|
|
597
|
+
ev("attention.needed", {
|
|
598
|
+
attention_id: `notif-${Date.now()}`,
|
|
599
|
+
kind: notifKind(s.notification_type),
|
|
600
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
601
|
+
})
|
|
602
|
+
);
|
|
603
|
+
break;
|
|
510
604
|
case "Stop":
|
|
511
|
-
events.push(
|
|
605
|
+
events.push(
|
|
606
|
+
ev("turn.ended", {
|
|
607
|
+
turn_id,
|
|
608
|
+
reason: "stop",
|
|
609
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
610
|
+
})
|
|
611
|
+
);
|
|
512
612
|
break;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
613
|
+
case "StopFailure":
|
|
614
|
+
events.push(
|
|
615
|
+
ev("turn.ended", {
|
|
616
|
+
turn_id,
|
|
617
|
+
reason: "error",
|
|
618
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
619
|
+
})
|
|
620
|
+
);
|
|
621
|
+
break;
|
|
622
|
+
case "SubagentStart":
|
|
623
|
+
events.push(
|
|
624
|
+
ev("subagent.started", {
|
|
625
|
+
agent_id: String(s.agent_id ?? ""),
|
|
626
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
627
|
+
})
|
|
628
|
+
);
|
|
629
|
+
break;
|
|
630
|
+
case "SubagentStop":
|
|
631
|
+
events.push(
|
|
632
|
+
ev("subagent.ended", {
|
|
633
|
+
agent_id: String(s.agent_id ?? ""),
|
|
634
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
635
|
+
})
|
|
636
|
+
);
|
|
637
|
+
break;
|
|
638
|
+
case "PreCompact":
|
|
639
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
640
|
+
break;
|
|
641
|
+
case "PostCompact":
|
|
642
|
+
break;
|
|
643
|
+
// PreCompact already said it; a second event would double-count
|
|
644
|
+
case "SessionEnd":
|
|
645
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
646
|
+
break;
|
|
647
|
+
case "StatusLine": {
|
|
648
|
+
const limits = claudeLimits(s.rate_limits);
|
|
649
|
+
if (!limits) return null;
|
|
650
|
+
events.push(ev("limits.observed", limits));
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
default:
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
const hints = {};
|
|
657
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
658
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
659
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
660
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
661
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
662
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
663
|
+
const r = { session, events, hints };
|
|
664
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
665
|
+
return r;
|
|
666
|
+
},
|
|
667
|
+
readTranscript(file, fromLine) {
|
|
668
|
+
let text = "";
|
|
669
|
+
try {
|
|
670
|
+
text = readFileSync2(file, "utf8");
|
|
671
|
+
} catch {
|
|
672
|
+
return { turns: [], line: fromLine };
|
|
673
|
+
}
|
|
674
|
+
return readClaude(text.split("\n"), fromLine);
|
|
675
|
+
},
|
|
676
|
+
facts(session, transcript, hints, state) {
|
|
677
|
+
const out = {};
|
|
678
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
679
|
+
const cwd = out.cwd ?? session.cwd;
|
|
680
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
681
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
682
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
683
|
+
return out;
|
|
536
684
|
},
|
|
685
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
537
686
|
backfill(sinceMs, env = process.env) {
|
|
538
687
|
const rows2 = [];
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
688
|
+
const files = [];
|
|
689
|
+
for (const d of claudeDirs(env))
|
|
690
|
+
files.push(...recentFiles(path3.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
691
|
+
const noState = { get: () => ({}), save: () => {
|
|
692
|
+
} };
|
|
693
|
+
for (const f of files) {
|
|
694
|
+
const id = path3.basename(f, ".jsonl");
|
|
695
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
696
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
697
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
698
|
+
const cwd = rest.cwd;
|
|
699
|
+
rows2.push({
|
|
700
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
701
|
+
started_at,
|
|
702
|
+
last_at,
|
|
703
|
+
transcript: f
|
|
704
|
+
});
|
|
557
705
|
}
|
|
558
706
|
return rows2;
|
|
559
707
|
}
|
|
560
708
|
};
|
|
709
|
+
var LINE_REFRESH_S = 60;
|
|
710
|
+
function statusLine(file, want, cmd) {
|
|
711
|
+
const f = readJsonFile(file);
|
|
712
|
+
if (!f.ok) return null;
|
|
713
|
+
const cur = f.value.statusLine;
|
|
714
|
+
const ours = typeof cur?.command === "string" && OURS.test(cur.command);
|
|
715
|
+
const command = cmd?.([NAME, "StatusLine"]).command;
|
|
716
|
+
if (want && cur && !ours)
|
|
717
|
+
return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
|
|
718
|
+
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
719
|
+
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
720
|
+
else delete f.value.statusLine;
|
|
721
|
+
writeFileSync2(file, formatJson(f.value, f));
|
|
722
|
+
return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
|
|
723
|
+
}
|
|
724
|
+
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
725
|
+
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
726
|
+
function claudeLimits(rl) {
|
|
727
|
+
if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
|
|
728
|
+
const windows = [];
|
|
729
|
+
for (const [id, w] of Object.entries(rl)) {
|
|
730
|
+
if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
|
|
731
|
+
windows.push(
|
|
732
|
+
prune({
|
|
733
|
+
id,
|
|
734
|
+
used_percent: w.used_percentage,
|
|
735
|
+
window_minutes: WINDOW_MINUTES[id],
|
|
736
|
+
resets_at: epochIso(w.resets_at)
|
|
737
|
+
})
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
return windows.length ? { windows: windows.slice(0, 8) } : null;
|
|
741
|
+
}
|
|
561
742
|
function prune(o) {
|
|
562
743
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
563
744
|
return o;
|
|
564
745
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (/^file:/i.test(s)) {
|
|
569
|
-
try {
|
|
570
|
-
s = decodeURIComponent(new URL(s).pathname);
|
|
571
|
-
} catch {
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
575
|
-
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
576
|
-
if (!path3.isAbsolute(s)) return null;
|
|
577
|
-
return path3.resolve(s);
|
|
746
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
747
|
+
function notClaudeCode(id, s) {
|
|
748
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
578
749
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
750
|
+
function pickSource(v) {
|
|
751
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
752
|
+
}
|
|
753
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
754
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
755
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
756
|
+
function errText(v) {
|
|
757
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
758
|
+
if (v && typeof v === "object") {
|
|
759
|
+
const e = v;
|
|
760
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
761
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
586
762
|
}
|
|
587
|
-
return
|
|
763
|
+
return void 0;
|
|
588
764
|
}
|
|
589
|
-
function
|
|
590
|
-
|
|
765
|
+
function describe(input) {
|
|
766
|
+
if (!input || typeof input !== "object") return "";
|
|
767
|
+
const i = input;
|
|
768
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
769
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
770
|
+
}
|
|
771
|
+
var NAME_RANK = {
|
|
772
|
+
user: "custom",
|
|
773
|
+
auto: "harness",
|
|
774
|
+
hook: "harness",
|
|
775
|
+
peer: "harness"
|
|
776
|
+
};
|
|
777
|
+
function registryEntry(j) {
|
|
778
|
+
if (!j || typeof j !== "object") return null;
|
|
779
|
+
const r = j;
|
|
780
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
781
|
+
return {
|
|
782
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
783
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
784
|
+
name_source: source ?? null,
|
|
785
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
789
|
+
function claudeLive(dir) {
|
|
790
|
+
const hit = liveCache.get(dir);
|
|
791
|
+
if (hit) return hit;
|
|
792
|
+
const map = /* @__PURE__ */ new Map();
|
|
591
793
|
let names = [];
|
|
592
794
|
try {
|
|
593
|
-
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
795
|
+
names = readdirSync2(path3.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
594
796
|
} catch {
|
|
595
|
-
return out;
|
|
596
797
|
}
|
|
597
798
|
for (const n of names) {
|
|
598
|
-
const id = n.slice(0, -5);
|
|
599
|
-
if (!ID_RE.test(id)) continue;
|
|
600
|
-
let j;
|
|
601
799
|
try {
|
|
602
|
-
|
|
800
|
+
const e = registryEntry(JSON.parse(readFileSync2(path3.join(dir, "sessions", n), "utf8")));
|
|
801
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
603
802
|
} catch {
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
const folders = [];
|
|
607
|
-
for (const r of j?.projectResources?.resources ?? []) {
|
|
608
|
-
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
609
|
-
if (f) folders.push(...agVariants(f));
|
|
610
803
|
}
|
|
611
|
-
if (folders.length) out.push({ id, folders });
|
|
612
804
|
}
|
|
613
|
-
|
|
805
|
+
liveCache.set(dir, map);
|
|
806
|
+
return map;
|
|
614
807
|
}
|
|
615
|
-
function
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
808
|
+
function claudeLiveByPid(dir, pid) {
|
|
809
|
+
if (!pid) return null;
|
|
810
|
+
try {
|
|
811
|
+
return registryEntry(JSON.parse(readFileSync2(path3.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
812
|
+
} catch {
|
|
813
|
+
return null;
|
|
620
814
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
815
|
+
}
|
|
816
|
+
function claudePid(env = process.env) {
|
|
817
|
+
try {
|
|
818
|
+
const dirs = claudeDirs(env);
|
|
819
|
+
const has = (pid2) => dirs.some((d) => existsSync2(path3.join(d, "sessions", `${pid2}.json`)));
|
|
820
|
+
let pid = process.ppid;
|
|
821
|
+
if (has(pid)) return pid;
|
|
822
|
+
const parent = parentOf();
|
|
823
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
824
|
+
pid = parent(pid);
|
|
825
|
+
if (pid > 1 && has(pid)) return pid;
|
|
826
|
+
}
|
|
827
|
+
} catch {
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
function parentOf() {
|
|
832
|
+
if (process.platform === "linux") {
|
|
833
|
+
return (pid) => {
|
|
834
|
+
try {
|
|
835
|
+
const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
836
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
837
|
+
} catch {
|
|
838
|
+
return 0;
|
|
629
839
|
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const table = /* @__PURE__ */ new Map();
|
|
843
|
+
try {
|
|
844
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
845
|
+
encoding: "utf8",
|
|
846
|
+
timeout: 2e3,
|
|
847
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
848
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
849
|
+
});
|
|
850
|
+
for (const l of out.split("\n")) {
|
|
851
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
852
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
630
853
|
}
|
|
631
|
-
|
|
854
|
+
} catch {
|
|
855
|
+
}
|
|
856
|
+
return (pid) => table.get(pid) || 0;
|
|
632
857
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
858
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
859
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
860
|
+
const files = [path3.join(dir, ".claude.json")];
|
|
861
|
+
if (path3.resolve(dir) === path3.join(home, ".claude")) files.unshift(path3.join(home, ".claude.json"));
|
|
862
|
+
for (const f of files) {
|
|
637
863
|
try {
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const conv = opts.conversation ?? "";
|
|
644
|
-
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
645
|
-
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
646
|
-
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
864
|
+
const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
|
|
865
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
866
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
867
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
868
|
+
}
|
|
647
869
|
} catch {
|
|
648
870
|
}
|
|
649
871
|
}
|
|
650
|
-
return
|
|
872
|
+
return apiAccount(dir);
|
|
651
873
|
}
|
|
652
|
-
|
|
653
|
-
|
|
874
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
875
|
+
function apiAccount(dir) {
|
|
876
|
+
const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
|
|
877
|
+
if (typeof base !== "string") return null;
|
|
878
|
+
let u;
|
|
879
|
+
try {
|
|
880
|
+
u = new URL(base.trim());
|
|
881
|
+
} catch {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
885
|
+
return null;
|
|
886
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
887
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
888
|
+
}
|
|
889
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
890
|
+
function claudeAccount(dir) {
|
|
891
|
+
const hit = accountCache.get(dir);
|
|
892
|
+
if (hit !== void 0) return hit;
|
|
893
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
894
|
+
accountCache.set(dir, acct);
|
|
895
|
+
return acct;
|
|
896
|
+
}
|
|
897
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
898
|
+
function transcriptLink(records) {
|
|
899
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
900
|
+
const j = records[i];
|
|
901
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
902
|
+
return j.url;
|
|
903
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
904
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
905
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
function appTitle(text) {
|
|
911
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
912
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
913
|
+
}
|
|
914
|
+
function askText(j) {
|
|
915
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
916
|
+
const origin = j.origin;
|
|
917
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
918
|
+
const c = j.message?.content;
|
|
919
|
+
const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
|
|
920
|
+
if (!text || isInjected(text)) return null;
|
|
921
|
+
return text;
|
|
922
|
+
}
|
|
923
|
+
function firstAsk(records) {
|
|
924
|
+
for (const j of records) {
|
|
925
|
+
const t = askText(j);
|
|
926
|
+
if (t) return appTitle(t);
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
function scanTitles(file, from, found) {
|
|
931
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
932
|
+
let size = 0;
|
|
933
|
+
try {
|
|
934
|
+
size = statSync2(file).size;
|
|
935
|
+
} catch {
|
|
936
|
+
return out;
|
|
937
|
+
}
|
|
938
|
+
if (size < from) {
|
|
939
|
+
out.upto = 0;
|
|
940
|
+
out.custom = out.ai = null;
|
|
941
|
+
from = 0;
|
|
942
|
+
}
|
|
943
|
+
let pos = 0;
|
|
944
|
+
scanLines(
|
|
945
|
+
file,
|
|
946
|
+
(l) => {
|
|
947
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
948
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
949
|
+
try {
|
|
950
|
+
const j = JSON.parse(l);
|
|
951
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
952
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
return null;
|
|
956
|
+
},
|
|
957
|
+
512 * 1024 * 1024
|
|
958
|
+
);
|
|
959
|
+
out.upto = size;
|
|
960
|
+
return out;
|
|
961
|
+
}
|
|
962
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
963
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
654
964
|
const H = parseLines(head);
|
|
655
965
|
const T = parseLines(tail);
|
|
656
966
|
const out = {};
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
967
|
+
const id = sessionId || path3.basename(file, ".jsonl");
|
|
968
|
+
const mem = state.get(NAME, id);
|
|
969
|
+
let custom = null;
|
|
970
|
+
let ai = null;
|
|
971
|
+
for (const j of H.concat(T)) {
|
|
972
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
973
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
974
|
+
}
|
|
975
|
+
let size = 0;
|
|
976
|
+
try {
|
|
977
|
+
size = statSync2(file).size;
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
981
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
982
|
+
custom: mem.scan_custom,
|
|
983
|
+
ai: mem.scan_ai
|
|
984
|
+
});
|
|
985
|
+
custom = s.custom;
|
|
986
|
+
ai = s.ai;
|
|
987
|
+
state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
988
|
+
}
|
|
989
|
+
const dir = configDirOf(file);
|
|
990
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
991
|
+
if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
|
|
992
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
993
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
994
|
+
if (url) {
|
|
995
|
+
out.url = url;
|
|
996
|
+
state.save(NAME, id, { url });
|
|
997
|
+
}
|
|
998
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
999
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1000
|
+
let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1001
|
+
if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1002
|
+
ask = scanLines(file, (l) => {
|
|
1003
|
+
try {
|
|
1004
|
+
const t = askText(JSON.parse(l));
|
|
1005
|
+
return t ? appTitle(t) : null;
|
|
1006
|
+
} catch {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
if (!ask && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
|
|
1011
|
+
}
|
|
1012
|
+
if (ask) {
|
|
1013
|
+
out.first_ask = ask;
|
|
1014
|
+
if (!mem.first_ask) state.save(NAME, id, { first_ask: ask });
|
|
1015
|
+
}
|
|
1016
|
+
if (pick) {
|
|
1017
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1018
|
+
out.title_source = pick.source;
|
|
1019
|
+
} else if (ask) {
|
|
1020
|
+
out.title = ask;
|
|
666
1021
|
out.title_source = "first-ask";
|
|
667
|
-
break;
|
|
668
1022
|
}
|
|
669
|
-
const
|
|
1023
|
+
const acct = claudeAccount(dir);
|
|
1024
|
+
if (acct) out.account_id = acct;
|
|
1025
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1026
|
+
const m = T[i].message?.model;
|
|
1027
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1028
|
+
out.model = m;
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1033
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1034
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
670
1035
|
if (times.length) {
|
|
671
1036
|
out.started_at = Math.min(...times);
|
|
672
1037
|
out.last_at = Math.max(...times);
|
|
673
1038
|
}
|
|
1039
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1040
|
+
if (withCwd) {
|
|
1041
|
+
out.cwd = withCwd.cwd;
|
|
1042
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1043
|
+
}
|
|
674
1044
|
return out;
|
|
675
1045
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1046
|
+
var REMOTE_RETRY_MS = 6e4;
|
|
1047
|
+
var REMOTE_REFRESH_MS = 30 * 6e4;
|
|
1048
|
+
var CLAUDE_API = "https://api.anthropic.com";
|
|
1049
|
+
var KEYCHAIN = "Claude Code-credentials";
|
|
1050
|
+
var REMOTE_TIMEOUT_MS = 3e3;
|
|
1051
|
+
var KEYCHAIN_TIMEOUT_MS = 2e3;
|
|
1052
|
+
var REMOTE_MAX_BYTES = 1 << 20;
|
|
1053
|
+
var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1054
|
+
async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
|
|
1055
|
+
if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
|
|
1056
|
+
const mem = state.get(NAME, id);
|
|
1057
|
+
let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
|
|
1058
|
+
if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
|
|
1059
|
+
state.save(NAME, id, { remote_at: now });
|
|
1060
|
+
const token = claudeAiToken(configDirOf(transcript), env, now);
|
|
1061
|
+
const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
|
|
1062
|
+
if (got !== void 0) {
|
|
1063
|
+
title = got?.slice(0, 200) ?? null;
|
|
1064
|
+
state.save(NAME, id, { remote_title: title });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
|
|
1068
|
+
}
|
|
1069
|
+
function keychainServices(dir, env) {
|
|
1070
|
+
const named = (p) => `${KEYCHAIN}-${createHash("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
1071
|
+
const out = /* @__PURE__ */ new Set();
|
|
1072
|
+
if (env.CLAUDE_CONFIG_DIR && path3.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
|
|
1073
|
+
if (dir === path3.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
|
|
1074
|
+
out.add(named(dir));
|
|
1075
|
+
return [...out];
|
|
1076
|
+
}
|
|
1077
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1078
|
+
function claudeAiToken(dir, env, now) {
|
|
1079
|
+
const hit = tokenCache.get(dir);
|
|
1080
|
+
if (hit && hit.expires > now + 6e4) return hit.token;
|
|
1081
|
+
const raw = [];
|
|
1082
|
+
if (process.platform === "darwin")
|
|
1083
|
+
for (const service of keychainServices(dir, env)) {
|
|
1084
|
+
try {
|
|
1085
|
+
const user = env.USER || userInfo().username;
|
|
1086
|
+
const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
|
|
1087
|
+
raw.push(
|
|
1088
|
+
execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
|
|
1089
|
+
encoding: "utf8",
|
|
1090
|
+
timeout: KEYCHAIN_TIMEOUT_MS,
|
|
1091
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1092
|
+
})
|
|
1093
|
+
);
|
|
1094
|
+
break;
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
try {
|
|
1099
|
+
raw.push(readFileSync2(path3.join(dir, ".credentials.json"), "utf8"));
|
|
1100
|
+
} catch {
|
|
1101
|
+
}
|
|
1102
|
+
for (const r of raw) {
|
|
691
1103
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1104
|
+
const t = r.trim();
|
|
1105
|
+
const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
|
|
1106
|
+
if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
|
|
1107
|
+
const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
|
|
1108
|
+
if (expires <= now + 6e4) continue;
|
|
1109
|
+
tokenCache.set(dir, { token: o.accessToken, expires });
|
|
1110
|
+
return o.accessToken;
|
|
697
1111
|
} catch {
|
|
698
|
-
return "";
|
|
699
1112
|
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const repo = run(["remote", "get-url", "origin"]);
|
|
703
|
-
if (repo) out.repo = stripCredentials(repo);
|
|
704
|
-
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
705
|
-
if (branch && branch !== "HEAD") out.branch = branch;
|
|
706
|
-
cache.set(cwd, out);
|
|
707
|
-
return out;
|
|
1113
|
+
}
|
|
1114
|
+
return null;
|
|
708
1115
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1116
|
+
async function claudeAiTitle(bridge, token) {
|
|
1117
|
+
try {
|
|
1118
|
+
const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
|
|
1119
|
+
headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
|
|
1120
|
+
redirect: "error",
|
|
1121
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1122
|
+
});
|
|
1123
|
+
if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
|
|
1124
|
+
const text = await r.text();
|
|
1125
|
+
if (text.length > REMOTE_MAX_BYTES) return void 0;
|
|
1126
|
+
const j = JSON.parse(text);
|
|
1127
|
+
const s = j?.response_shape ?? j?.session ?? j;
|
|
1128
|
+
const bare = (x) => x.replace(/^(?:session|cse)_/, "");
|
|
1129
|
+
if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
|
|
1130
|
+
return typeof s.title === "string" && cleanName(s.title) || null;
|
|
1131
|
+
} catch {
|
|
1132
|
+
return void 0;
|
|
1133
|
+
}
|
|
713
1134
|
}
|
|
714
1135
|
|
|
715
|
-
// ../core/src/adapters/
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
"PostToolUse",
|
|
722
|
-
"PostToolUseFailure",
|
|
723
|
-
"PermissionRequest",
|
|
724
|
-
"Notification",
|
|
725
|
-
"Stop",
|
|
726
|
-
"SubagentStart",
|
|
727
|
-
"SubagentStop",
|
|
728
|
-
"PreCompact",
|
|
729
|
-
"PostCompact",
|
|
730
|
-
"SessionEnd",
|
|
731
|
-
"StopFailure"
|
|
732
|
-
];
|
|
733
|
-
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
734
|
-
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
735
|
-
var OPTS = {
|
|
736
|
-
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
737
|
-
timeout: 5,
|
|
738
|
-
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
739
|
-
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
740
|
-
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
741
|
-
};
|
|
742
|
-
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
743
|
-
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
744
|
-
const out = [];
|
|
745
|
-
const add = (d) => {
|
|
746
|
-
if (!d) return;
|
|
747
|
-
const r = path4.resolve(d);
|
|
748
|
-
if (!out.includes(r)) out.push(r);
|
|
749
|
-
};
|
|
750
|
-
add(path4.join(home, ".claude"));
|
|
751
|
-
add(env.CLAUDE_CONFIG_DIR);
|
|
1136
|
+
// ../core/src/adapters/headless.ts
|
|
1137
|
+
import { accessSync, constants, statSync as statSync3 } from "node:fs";
|
|
1138
|
+
import path4 from "node:path";
|
|
1139
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1140
|
+
var OUT_CAP = 1024 * 1024;
|
|
1141
|
+
function isExe(p) {
|
|
752
1142
|
try {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (existsSync3(path4.join(d, "projects")) || existsSync3(path4.join(d, "settings.json"))) add(d);
|
|
757
|
-
}
|
|
1143
|
+
if (!statSync3(p).isFile()) return false;
|
|
1144
|
+
accessSync(p, constants.X_OK);
|
|
1145
|
+
return true;
|
|
758
1146
|
} catch {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function findBinary(o) {
|
|
1151
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
1152
|
+
for (const d of String(o.env?.PATH || "").split(path4.delimiter)) {
|
|
1153
|
+
if (!d || !path4.isAbsolute(d)) continue;
|
|
1154
|
+
for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
|
|
759
1155
|
}
|
|
760
|
-
|
|
1156
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
|
|
1157
|
+
return null;
|
|
761
1158
|
}
|
|
762
|
-
|
|
763
|
-
|
|
1159
|
+
|
|
1160
|
+
// ../core/src/adapters/antigravity.ts
|
|
1161
|
+
import {
|
|
1162
|
+
existsSync as existsSync3,
|
|
1163
|
+
mkdirSync as mkdirSync2,
|
|
1164
|
+
readdirSync as readdirSync3,
|
|
1165
|
+
readFileSync as readFileSync3,
|
|
1166
|
+
realpathSync,
|
|
1167
|
+
statSync as statSync4,
|
|
1168
|
+
unlinkSync as unlinkSync2,
|
|
1169
|
+
writeFileSync as writeFileSync3
|
|
1170
|
+
} from "node:fs";
|
|
1171
|
+
import path5 from "node:path";
|
|
1172
|
+
var NAME2 = "antigravity";
|
|
1173
|
+
var HOOK_NAME = "sessionpipe";
|
|
1174
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
1175
|
+
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';
|
|
1176
|
+
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
1177
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path5.join(homeDir(), ".gemini");
|
|
1178
|
+
var hooksFile = (env) => path5.join(gemini(env), "config", "hooks.json");
|
|
1179
|
+
var projectsDir = (env) => path5.join(gemini(env), "config", "projects");
|
|
1180
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path5.join(gemini(env), d));
|
|
1181
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
764
1182
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1183
|
+
function entry(cmd) {
|
|
1184
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
|
|
1185
|
+
const out = { enabled: true };
|
|
1186
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
1187
|
+
return out;
|
|
1188
|
+
}
|
|
1189
|
+
var antigravity = {
|
|
768
1190
|
name: NAME2,
|
|
769
|
-
events:
|
|
770
|
-
detect: (env = process.env) =>
|
|
771
|
-
configFiles: (env = process.env) =>
|
|
772
|
-
install
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1191
|
+
events: AG_EVENTS,
|
|
1192
|
+
detect: (env = process.env) => existsSync3(path5.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
|
|
1193
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
1194
|
+
install(cmd, env = process.env) {
|
|
1195
|
+
const file = hooksFile(env);
|
|
1196
|
+
const f = readJsonFile(file);
|
|
1197
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1198
|
+
const hooks = f.value;
|
|
1199
|
+
const e = entry(cmd);
|
|
1200
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1201
|
+
hooks[HOOK_NAME] = e;
|
|
1202
|
+
mkdirSync2(path5.dirname(file), { recursive: true });
|
|
1203
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1204
|
+
return [
|
|
1205
|
+
{
|
|
1206
|
+
file,
|
|
1207
|
+
changed: true,
|
|
1208
|
+
created: !f.existed,
|
|
1209
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
1210
|
+
}
|
|
1211
|
+
];
|
|
1212
|
+
},
|
|
1213
|
+
uninstall(env = process.env, opts = {}) {
|
|
1214
|
+
const file = hooksFile(env);
|
|
1215
|
+
if (!existsSync3(file)) return [{ file, changed: false }];
|
|
1216
|
+
const f = readJsonFile(file);
|
|
1217
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1218
|
+
const hooks = f.value;
|
|
1219
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1220
|
+
delete hooks[HOOK_NAME];
|
|
1221
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
1222
|
+
unlinkSync2(file);
|
|
1223
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1224
|
+
}
|
|
1225
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1226
|
+
return [{ file, changed: true }];
|
|
1227
|
+
},
|
|
1228
|
+
installed(cmd, env = process.env) {
|
|
1229
|
+
const file = hooksFile(env);
|
|
1230
|
+
const f = readJsonFile(file);
|
|
1231
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
1232
|
+
return [
|
|
1233
|
+
{
|
|
1234
|
+
file,
|
|
1235
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
1236
|
+
}
|
|
1237
|
+
];
|
|
1238
|
+
},
|
|
780
1239
|
fromHook(input) {
|
|
781
1240
|
const event = input.argv[1] ?? "";
|
|
782
1241
|
let s = {};
|
|
@@ -785,133 +1244,48 @@ var claudeCode = {
|
|
|
785
1244
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
786
1245
|
} catch {
|
|
787
1246
|
}
|
|
788
|
-
const id = s.session_id ?? s.sessionId;
|
|
789
|
-
|
|
790
|
-
if (
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
if (typeof s.
|
|
794
|
-
|
|
795
|
-
const
|
|
796
|
-
const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
|
|
797
|
-
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
798
|
-
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
799
|
-
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
800
|
-
const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
1247
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
1248
|
+
const stdout = antigravityAnswer(event);
|
|
1249
|
+
if (!id) return { session: { id: "" }, events: [], stdout };
|
|
1250
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
1251
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
1252
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
1253
|
+
const toolCall = s.toolCall;
|
|
1254
|
+
const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
|
|
801
1255
|
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
802
1256
|
const events = [];
|
|
803
1257
|
switch (event) {
|
|
804
|
-
case "
|
|
805
|
-
|
|
1258
|
+
case "PreInvocation":
|
|
1259
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
1260
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
1261
|
+
events.push(ev("turn.started", {}));
|
|
1262
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
806
1263
|
break;
|
|
807
|
-
case "
|
|
808
|
-
events.push(
|
|
809
|
-
ev("turn.started", {
|
|
810
|
-
turn_id,
|
|
811
|
-
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
812
|
-
})
|
|
813
|
-
);
|
|
1264
|
+
case "PostInvocation":
|
|
814
1265
|
break;
|
|
815
1266
|
case "PreToolUse":
|
|
816
|
-
if (tool) events.push(ev("tool.started", { tool,
|
|
1267
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
817
1268
|
break;
|
|
818
1269
|
case "PostToolUse":
|
|
819
|
-
if (tool)
|
|
820
|
-
|
|
821
|
-
ev("tool.ended", {
|
|
822
|
-
tool,
|
|
823
|
-
call_id,
|
|
824
|
-
turn_id,
|
|
825
|
-
agent_id,
|
|
826
|
-
ms,
|
|
827
|
-
ok: true,
|
|
828
|
-
input: s.tool_input,
|
|
829
|
-
output: s.tool_response
|
|
830
|
-
})
|
|
831
|
-
);
|
|
832
|
-
break;
|
|
833
|
-
case "PostToolUseFailure":
|
|
834
|
-
if (tool)
|
|
1270
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1271
|
+
else
|
|
835
1272
|
events.push(
|
|
836
1273
|
ev("tool.ended", {
|
|
837
1274
|
tool,
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
ms,
|
|
842
|
-
ok: false,
|
|
843
|
-
error: errText(s.error ?? s.tool_response),
|
|
844
|
-
input: s.tool_input,
|
|
845
|
-
output: s.tool_response
|
|
1275
|
+
ok: !s.error,
|
|
1276
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1277
|
+
input: toolCall?.args
|
|
846
1278
|
})
|
|
847
1279
|
);
|
|
848
1280
|
break;
|
|
849
|
-
case "PermissionRequest":
|
|
850
|
-
events.push(
|
|
851
|
-
ev("attention.needed", {
|
|
852
|
-
attention_id: call_id ?? permissionId(),
|
|
853
|
-
kind: "permission",
|
|
854
|
-
tool,
|
|
855
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
856
|
-
})
|
|
857
|
-
);
|
|
858
|
-
break;
|
|
859
|
-
case "Notification":
|
|
860
|
-
events.push(
|
|
861
|
-
ev("attention.needed", {
|
|
862
|
-
attention_id: `notif-${Date.now()}`,
|
|
863
|
-
kind: notifKind(s.notification_type),
|
|
864
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
865
|
-
})
|
|
866
|
-
);
|
|
867
|
-
break;
|
|
868
1281
|
case "Stop":
|
|
869
|
-
events.push(ev("turn.ended", {
|
|
870
|
-
break;
|
|
871
|
-
case "StopFailure":
|
|
872
|
-
events.push(
|
|
873
|
-
ev("turn.ended", {
|
|
874
|
-
turn_id,
|
|
875
|
-
reason: "error",
|
|
876
|
-
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
877
|
-
})
|
|
878
|
-
);
|
|
879
|
-
break;
|
|
880
|
-
case "SubagentStart":
|
|
881
|
-
events.push(
|
|
882
|
-
ev("subagent.started", {
|
|
883
|
-
agent_id: String(s.agent_id ?? ""),
|
|
884
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
885
|
-
})
|
|
886
|
-
);
|
|
887
|
-
break;
|
|
888
|
-
case "SubagentStop":
|
|
889
|
-
events.push(
|
|
890
|
-
ev("subagent.ended", {
|
|
891
|
-
agent_id: String(s.agent_id ?? ""),
|
|
892
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
893
|
-
})
|
|
894
|
-
);
|
|
895
|
-
break;
|
|
896
|
-
case "PreCompact":
|
|
897
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
898
|
-
break;
|
|
899
|
-
case "PostCompact":
|
|
900
|
-
break;
|
|
901
|
-
// PreCompact already said it; a second event would double-count
|
|
902
|
-
case "SessionEnd":
|
|
903
|
-
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
1282
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
904
1283
|
break;
|
|
905
1284
|
default:
|
|
906
|
-
return
|
|
1285
|
+
return { session, events: [], stdout };
|
|
907
1286
|
}
|
|
908
|
-
const
|
|
909
|
-
if (
|
|
910
|
-
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
911
|
-
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
912
|
-
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
913
|
-
const r = { session, events, hints };
|
|
914
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1287
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
1288
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
915
1289
|
return r;
|
|
916
1290
|
},
|
|
917
1291
|
readTranscript(file, fromLine) {
|
|
@@ -921,331 +1295,162 @@ var claudeCode = {
|
|
|
921
1295
|
} catch {
|
|
922
1296
|
return { turns: [], line: fromLine };
|
|
923
1297
|
}
|
|
924
|
-
return
|
|
1298
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
925
1299
|
},
|
|
926
|
-
facts(session, transcript, hints
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1300
|
+
facts(session, transcript, hints) {
|
|
1301
|
+
if (!transcript) return {};
|
|
1302
|
+
return antigravityFacts(transcript, {
|
|
1303
|
+
conversation: session.id,
|
|
1304
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
1305
|
+
env: process.env
|
|
1306
|
+
});
|
|
933
1307
|
},
|
|
934
1308
|
backfill(sinceMs, env = process.env) {
|
|
935
1309
|
const rows2 = [];
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
939
|
-
const noState = { get: () => ({}), save: () => {
|
|
940
|
-
} };
|
|
941
|
-
for (const f of files) {
|
|
942
|
-
const id = path4.basename(f, ".jsonl");
|
|
943
|
-
const facts = claudeFacts(f, id, {}, noState);
|
|
944
|
-
if (!facts.started_at || !facts.last_at) continue;
|
|
945
|
-
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
946
|
-
const cwd = rest.cwd;
|
|
947
|
-
rows2.push({
|
|
948
|
-
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
949
|
-
started_at,
|
|
950
|
-
last_at
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
return rows2;
|
|
954
|
-
}
|
|
955
|
-
};
|
|
956
|
-
function prune2(o) {
|
|
957
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
958
|
-
return o;
|
|
959
|
-
}
|
|
960
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
961
|
-
function notClaudeCode(id, s) {
|
|
962
|
-
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
963
|
-
}
|
|
964
|
-
function pickSource(v) {
|
|
965
|
-
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
966
|
-
}
|
|
967
|
-
function errText(v) {
|
|
968
|
-
if (typeof v === "string") return v.slice(0, 2e3);
|
|
969
|
-
if (v && typeof v === "object") {
|
|
970
|
-
const e = v;
|
|
971
|
-
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
972
|
-
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
973
|
-
}
|
|
974
|
-
return void 0;
|
|
975
|
-
}
|
|
976
|
-
function describe(input) {
|
|
977
|
-
if (!input || typeof input !== "object") return "";
|
|
978
|
-
const i = input;
|
|
979
|
-
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
980
|
-
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
981
|
-
}
|
|
982
|
-
var NAME_RANK = {
|
|
983
|
-
user: "custom",
|
|
984
|
-
auto: "harness",
|
|
985
|
-
hook: "harness",
|
|
986
|
-
peer: "harness"
|
|
987
|
-
};
|
|
988
|
-
function registryEntry(j) {
|
|
989
|
-
if (!j || typeof j !== "object") return null;
|
|
990
|
-
const r = j;
|
|
991
|
-
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
992
|
-
return {
|
|
993
|
-
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
994
|
-
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
995
|
-
name_source: source ?? null,
|
|
996
|
-
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
var liveCache = /* @__PURE__ */ new Map();
|
|
1000
|
-
function claudeLive(dir) {
|
|
1001
|
-
const hit = liveCache.get(dir);
|
|
1002
|
-
if (hit) return hit;
|
|
1003
|
-
const map = /* @__PURE__ */ new Map();
|
|
1004
|
-
let names = [];
|
|
1005
|
-
try {
|
|
1006
|
-
names = readdirSync3(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
1007
|
-
} catch {
|
|
1008
|
-
}
|
|
1009
|
-
for (const n of names) {
|
|
1010
|
-
try {
|
|
1011
|
-
const e = registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", n), "utf8")));
|
|
1012
|
-
if (e?.sessionId) map.set(e.sessionId, e);
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
liveCache.set(dir, map);
|
|
1017
|
-
return map;
|
|
1018
|
-
}
|
|
1019
|
-
function claudeLiveByPid(dir, pid) {
|
|
1020
|
-
if (!pid) return null;
|
|
1021
|
-
try {
|
|
1022
|
-
return registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1023
|
-
} catch {
|
|
1024
|
-
return null;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
function claudePid(env = process.env) {
|
|
1028
|
-
try {
|
|
1029
|
-
const dirs = claudeDirs(env);
|
|
1030
|
-
const has = (pid2) => dirs.some((d) => existsSync3(path4.join(d, "sessions", `${pid2}.json`)));
|
|
1031
|
-
let pid = process.ppid;
|
|
1032
|
-
if (has(pid)) return pid;
|
|
1033
|
-
const parent = parentOf();
|
|
1034
|
-
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
1035
|
-
pid = parent(pid);
|
|
1036
|
-
if (pid > 1 && has(pid)) return pid;
|
|
1037
|
-
}
|
|
1038
|
-
} catch {
|
|
1039
|
-
}
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
function parentOf() {
|
|
1043
|
-
if (process.platform === "linux") {
|
|
1044
|
-
return (pid) => {
|
|
1310
|
+
for (const data of dataDirs(env)) {
|
|
1311
|
+
let ids = [];
|
|
1045
1312
|
try {
|
|
1046
|
-
|
|
1047
|
-
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
1313
|
+
ids = readdirSync3(path5.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
1048
1314
|
} catch {
|
|
1049
|
-
return 0;
|
|
1050
1315
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
1316
|
+
for (const id of ids) {
|
|
1317
|
+
const f = path5.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
1318
|
+
try {
|
|
1319
|
+
if (statSync4(f).mtimeMs < sinceMs) continue;
|
|
1320
|
+
} catch {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
1324
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1325
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1326
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
1327
|
+
}
|
|
1064
1328
|
}
|
|
1065
|
-
|
|
1329
|
+
return rows2;
|
|
1066
1330
|
}
|
|
1067
|
-
|
|
1331
|
+
};
|
|
1332
|
+
function prune2(o) {
|
|
1333
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1334
|
+
return o;
|
|
1068
1335
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (
|
|
1073
|
-
for (const f of files) {
|
|
1336
|
+
function agPath(p) {
|
|
1337
|
+
if (typeof p !== "string" || !p) return null;
|
|
1338
|
+
let s = p;
|
|
1339
|
+
if (/^file:/i.test(s)) {
|
|
1074
1340
|
try {
|
|
1075
|
-
|
|
1076
|
-
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1077
|
-
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1078
|
-
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1079
|
-
}
|
|
1341
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1080
1342
|
} catch {
|
|
1343
|
+
return null;
|
|
1081
1344
|
}
|
|
1082
|
-
|
|
1083
|
-
return null;
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
function claudeAccount(dir) {
|
|
1087
|
-
const hit = accountCache.get(dir);
|
|
1088
|
-
if (hit !== void 0) return hit;
|
|
1089
|
-
const acct = claudeLogin(dir)?.id ?? null;
|
|
1090
|
-
accountCache.set(dir, acct);
|
|
1091
|
-
return acct;
|
|
1092
|
-
}
|
|
1093
|
-
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1094
|
-
function transcriptLink(records) {
|
|
1095
|
-
for (let i = records.length - 1; i >= 0; i--) {
|
|
1096
|
-
const j = records[i];
|
|
1097
|
-
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
1098
|
-
return j.url;
|
|
1099
|
-
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
1100
|
-
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
1101
|
-
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
return null;
|
|
1105
|
-
}
|
|
1106
|
-
function appTitle(text) {
|
|
1107
|
-
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1108
|
-
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1109
|
-
}
|
|
1110
|
-
function askText(j) {
|
|
1111
|
-
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1112
|
-
const origin = j.origin;
|
|
1113
|
-
if (origin?.kind && origin.kind !== "human") return null;
|
|
1114
|
-
const c = j.message?.content;
|
|
1115
|
-
const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
|
|
1116
|
-
if (!text || isInjected(text)) return null;
|
|
1117
|
-
return text;
|
|
1118
|
-
}
|
|
1119
|
-
function firstAsk(records) {
|
|
1120
|
-
for (const j of records) {
|
|
1121
|
-
const t = askText(j);
|
|
1122
|
-
if (t) return appTitle(t);
|
|
1123
|
-
}
|
|
1124
|
-
return null;
|
|
1345
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
1346
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
1347
|
+
if (!path5.isAbsolute(s)) return null;
|
|
1348
|
+
return path5.resolve(s);
|
|
1125
1349
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1350
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
1351
|
+
function agVariants(p) {
|
|
1352
|
+
const out = [p];
|
|
1129
1353
|
try {
|
|
1130
|
-
|
|
1354
|
+
const r = realpathSync.native(p);
|
|
1355
|
+
if (r !== p) out.push(r);
|
|
1131
1356
|
} catch {
|
|
1132
|
-
return out;
|
|
1133
|
-
}
|
|
1134
|
-
if (size < from) {
|
|
1135
|
-
out.upto = 0;
|
|
1136
|
-
out.custom = out.ai = null;
|
|
1137
|
-
from = 0;
|
|
1138
1357
|
}
|
|
1139
|
-
let pos = 0;
|
|
1140
|
-
scanLines(
|
|
1141
|
-
file,
|
|
1142
|
-
(l) => {
|
|
1143
|
-
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1144
|
-
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1145
|
-
try {
|
|
1146
|
-
const j = JSON.parse(l);
|
|
1147
|
-
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1148
|
-
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1149
|
-
} catch {
|
|
1150
|
-
}
|
|
1151
|
-
return null;
|
|
1152
|
-
},
|
|
1153
|
-
512 * 1024 * 1024
|
|
1154
|
-
);
|
|
1155
|
-
out.upto = size;
|
|
1156
1358
|
return out;
|
|
1157
1359
|
}
|
|
1158
|
-
function
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
const T = parseLines(tail);
|
|
1162
|
-
const out = {};
|
|
1163
|
-
const id = sessionId || path4.basename(file, ".jsonl");
|
|
1164
|
-
const mem = state.get(NAME2, id);
|
|
1165
|
-
let custom = null;
|
|
1166
|
-
let ai = null;
|
|
1167
|
-
for (const j of H.concat(T)) {
|
|
1168
|
-
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1169
|
-
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1170
|
-
}
|
|
1171
|
-
let size = 0;
|
|
1360
|
+
function agProjects(dir) {
|
|
1361
|
+
const out = [];
|
|
1362
|
+
let names = [];
|
|
1172
1363
|
try {
|
|
1173
|
-
|
|
1364
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1174
1365
|
} catch {
|
|
1366
|
+
return out;
|
|
1175
1367
|
}
|
|
1176
|
-
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1368
|
+
for (const n of names) {
|
|
1369
|
+
const id = n.slice(0, -5);
|
|
1370
|
+
if (!ID_RE.test(id)) continue;
|
|
1371
|
+
let j;
|
|
1372
|
+
try {
|
|
1373
|
+
j = JSON.parse(readFileSync3(path5.join(dir, n), "utf8"));
|
|
1374
|
+
} catch {
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
const folders = [];
|
|
1378
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
1379
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
1380
|
+
if (f) folders.push(...agVariants(f));
|
|
1381
|
+
}
|
|
1382
|
+
if (folders.length) out.push({ id, folders });
|
|
1184
1383
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
state.save(NAME2, id, { url });
|
|
1384
|
+
return out;
|
|
1385
|
+
}
|
|
1386
|
+
function agMatchProject(projects, workspaces) {
|
|
1387
|
+
const ws = [];
|
|
1388
|
+
for (const w of workspaces) {
|
|
1389
|
+
const p = agPath(w);
|
|
1390
|
+
if (p) ws.push(...agVariants(p));
|
|
1193
1391
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
} catch {
|
|
1203
|
-
return null;
|
|
1392
|
+
let best = null;
|
|
1393
|
+
let bestLen = -1;
|
|
1394
|
+
for (const pr of projects)
|
|
1395
|
+
for (const f of pr.folders) {
|
|
1396
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path5.sep) ? f : f + path5.sep));
|
|
1397
|
+
if (within && f.length > bestLen) {
|
|
1398
|
+
best = pr.id;
|
|
1399
|
+
bestLen = f.length;
|
|
1204
1400
|
}
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1401
|
+
}
|
|
1402
|
+
return best;
|
|
1403
|
+
}
|
|
1404
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
1405
|
+
const i = transcript.indexOf(`${path5.sep}brain${path5.sep}`);
|
|
1406
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
1407
|
+
for (const d of dirs) {
|
|
1408
|
+
try {
|
|
1409
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1410
|
+
readFileSync3(path5.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1411
|
+
);
|
|
1412
|
+
if (!m) continue;
|
|
1413
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
1414
|
+
const conv = opts.conversation ?? "";
|
|
1415
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
1416
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
1417
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
1418
|
+
} catch {
|
|
1419
|
+
}
|
|
1211
1420
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1421
|
+
return null;
|
|
1422
|
+
}
|
|
1423
|
+
function antigravityFacts(file, opts) {
|
|
1424
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1425
|
+
const H = parseLines(head);
|
|
1426
|
+
const T = parseLines(tail);
|
|
1427
|
+
const out = {};
|
|
1428
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
1429
|
+
if (url) out.url = url;
|
|
1430
|
+
for (const j of H) {
|
|
1431
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
1432
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
1433
|
+
const text = (m ? m[1] : j.content).trim();
|
|
1434
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
1435
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
1436
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1217
1437
|
out.title_source = "first-ask";
|
|
1438
|
+
break;
|
|
1218
1439
|
}
|
|
1219
|
-
const
|
|
1220
|
-
if (acct) out.account_id = acct;
|
|
1221
|
-
for (let i = T.length - 1; i >= 0; i--) {
|
|
1222
|
-
const m = T[i].message?.model;
|
|
1223
|
-
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1224
|
-
out.model = m;
|
|
1225
|
-
break;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1229
|
-
if (meta?.version) out.harness_version = String(meta.version);
|
|
1230
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1440
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1231
1441
|
if (times.length) {
|
|
1232
1442
|
out.started_at = Math.min(...times);
|
|
1233
1443
|
out.last_at = Math.max(...times);
|
|
1234
1444
|
}
|
|
1235
|
-
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1236
|
-
if (withCwd) {
|
|
1237
|
-
out.cwd = withCwd.cwd;
|
|
1238
|
-
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1239
|
-
}
|
|
1240
1445
|
return out;
|
|
1241
1446
|
}
|
|
1242
1447
|
|
|
1243
1448
|
// ../core/src/adapters/codex.ts
|
|
1244
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as
|
|
1245
|
-
import
|
|
1449
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1450
|
+
import path6 from "node:path";
|
|
1246
1451
|
|
|
1247
1452
|
// ../core/src/adapters/codex-config.ts
|
|
1248
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1453
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1249
1454
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1250
1455
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1251
1456
|
function scan(line, st) {
|
|
@@ -1335,10 +1540,11 @@ function dropLine(text, index, length) {
|
|
|
1335
1540
|
}
|
|
1336
1541
|
function insertTopLevel(text, line) {
|
|
1337
1542
|
const { head, tail } = split(text);
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1543
|
+
const body = head.replace(/\s*$/, "");
|
|
1544
|
+
const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
|
|
1545
|
+
const top = head.trim() ? head.replace(/\s*$/, eol) : "";
|
|
1546
|
+
if (!tail) return top + line + eol;
|
|
1547
|
+
return `${top + line}${eol}${eol}${tail}`;
|
|
1342
1548
|
}
|
|
1343
1549
|
function withNotify(text, line) {
|
|
1344
1550
|
const n = readNotify(text);
|
|
@@ -1387,10 +1593,10 @@ var CODEX_EVENTS = [
|
|
|
1387
1593
|
];
|
|
1388
1594
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1389
1595
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1390
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1391
|
-
var hooksFile2 = (env) =>
|
|
1392
|
-
var configFile = (env) =>
|
|
1393
|
-
var sessionsDir = (env) =>
|
|
1596
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1597
|
+
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1598
|
+
var configFile = (env) => path6.join(codexHome(env), "config.toml");
|
|
1599
|
+
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
1394
1600
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1395
1601
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1396
1602
|
function installNotify(cmd, env) {
|
|
@@ -1404,8 +1610,8 @@ function installNotify(cmd, env) {
|
|
|
1404
1610
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1405
1611
|
const n = readNotify(text);
|
|
1406
1612
|
const write = (next) => {
|
|
1407
|
-
mkdirSync3(
|
|
1408
|
-
|
|
1613
|
+
mkdirSync3(path6.dirname(file), { recursive: true });
|
|
1614
|
+
writeFileSync4(file, next);
|
|
1409
1615
|
};
|
|
1410
1616
|
if (n && !n.other) {
|
|
1411
1617
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1450,12 +1656,12 @@ function uninstallNotify(env) {
|
|
|
1450
1656
|
const n = readNotify(text);
|
|
1451
1657
|
if (!n) return { file, changed: false };
|
|
1452
1658
|
if (!n.other) {
|
|
1453
|
-
|
|
1659
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1454
1660
|
return { file, changed: true };
|
|
1455
1661
|
}
|
|
1456
1662
|
const prev = previousNotify(n.argv);
|
|
1457
1663
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1458
|
-
|
|
1664
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1459
1665
|
return { file, changed: true };
|
|
1460
1666
|
}
|
|
1461
1667
|
return { file, changed: false };
|
|
@@ -1471,7 +1677,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1471
1677
|
return;
|
|
1472
1678
|
}
|
|
1473
1679
|
for (const n of names.sort().reverse()) {
|
|
1474
|
-
const p =
|
|
1680
|
+
const p = path6.join(dir, n);
|
|
1475
1681
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1476
1682
|
best = p;
|
|
1477
1683
|
return;
|
|
@@ -1561,7 +1767,7 @@ var codex = {
|
|
|
1561
1767
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1562
1768
|
} catch {
|
|
1563
1769
|
}
|
|
1564
|
-
const id = s.session_id;
|
|
1770
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1565
1771
|
if (!id) return null;
|
|
1566
1772
|
if (s.transcript_path === null) return null;
|
|
1567
1773
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
@@ -1656,7 +1862,7 @@ var codex = {
|
|
|
1656
1862
|
backfill(sinceMs, env = process.env) {
|
|
1657
1863
|
const rows2 = [];
|
|
1658
1864
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1659
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
1865
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1660
1866
|
if (!m?.[1]) continue;
|
|
1661
1867
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1662
1868
|
const facts = codexFacts(f);
|
|
@@ -1665,12 +1871,40 @@ var codex = {
|
|
|
1665
1871
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1666
1872
|
}
|
|
1667
1873
|
return rows2;
|
|
1668
|
-
}
|
|
1874
|
+
},
|
|
1875
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1669
1876
|
};
|
|
1877
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1670
1878
|
function prune3(o) {
|
|
1671
1879
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1672
1880
|
return o;
|
|
1673
1881
|
}
|
|
1882
|
+
function codexLimits(file) {
|
|
1883
|
+
const { tail } = ends(file, 0, 256 * 1024);
|
|
1884
|
+
for (const r of parseLines(tail).reverse()) {
|
|
1885
|
+
const p = r.payload;
|
|
1886
|
+
if (r.type !== "event_msg" || p?.type !== "token_count") continue;
|
|
1887
|
+
if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
|
|
1888
|
+
const rl = p.rate_limits;
|
|
1889
|
+
if (rl.limit_id != null && rl.limit_id !== "codex") continue;
|
|
1890
|
+
const windows = [];
|
|
1891
|
+
for (const id of ["primary", "secondary"]) {
|
|
1892
|
+
const w = rl[id];
|
|
1893
|
+
if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
|
|
1894
|
+
windows.push(
|
|
1895
|
+
prune3({
|
|
1896
|
+
id,
|
|
1897
|
+
used_percent: w.used_percent,
|
|
1898
|
+
window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
|
|
1899
|
+
resets_at: epochIso2(w.resets_at)
|
|
1900
|
+
})
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
if (!windows.length) return null;
|
|
1904
|
+
return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
|
|
1905
|
+
}
|
|
1906
|
+
return null;
|
|
1907
|
+
}
|
|
1674
1908
|
function codexFacts(file) {
|
|
1675
1909
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1676
1910
|
let H = parseLines(head);
|
|
@@ -1715,7 +1949,7 @@ function codexFacts(file) {
|
|
|
1715
1949
|
// ../core/src/adapters/devin.ts
|
|
1716
1950
|
import { createHash as createHash2 } from "node:crypto";
|
|
1717
1951
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1718
|
-
import
|
|
1952
|
+
import path7 from "node:path";
|
|
1719
1953
|
|
|
1720
1954
|
// ../core/src/readers/sqlite.ts
|
|
1721
1955
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -1899,11 +2133,11 @@ function foreignEvent(file) {
|
|
|
1899
2133
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1900
2134
|
}
|
|
1901
2135
|
var homeOf = (env) => env.HOME || homeDir();
|
|
1902
|
-
var appData = (env) => env.APPDATA ||
|
|
1903
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
1904
|
-
var configFile2 = (env) =>
|
|
1905
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
1906
|
-
var sessionsDb = (env) =>
|
|
2136
|
+
var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
|
|
2137
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path7.join(appData(env), "devin") : path7.join(env.XDG_CONFIG_HOME || path7.join(homeOf(env), ".config"), "devin");
|
|
2138
|
+
var configFile2 = (env) => path7.join(devinConfigDir(env), "config.json");
|
|
2139
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path7.join(appData(env), "devin", "cli") : path7.join(env.XDG_DATA_HOME || path7.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
2140
|
+
var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
|
|
1907
2141
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1908
2142
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1909
2143
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2075,7 +2309,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2075
2309
|
function sessionFacts(row) {
|
|
2076
2310
|
const out = {};
|
|
2077
2311
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2078
|
-
if (typeof row.working_directory === "string" &&
|
|
2312
|
+
if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
|
|
2079
2313
|
out.cwd = row.working_directory;
|
|
2080
2314
|
const started = Number(row.created_at);
|
|
2081
2315
|
const last = Number(row.last_activity_at);
|
|
@@ -2120,8 +2354,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2120
2354
|
}
|
|
2121
2355
|
function devinVersion(env = process.env) {
|
|
2122
2356
|
try {
|
|
2123
|
-
const target = readlinkSync(
|
|
2124
|
-
const name =
|
|
2357
|
+
const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
|
|
2358
|
+
const name = path7.basename(target);
|
|
2125
2359
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2126
2360
|
} catch {
|
|
2127
2361
|
return void 0;
|
|
@@ -2136,7 +2370,7 @@ function devinAccount(env = process.env) {
|
|
|
2136
2370
|
// ../core/src/adapters/gemini-cli.ts
|
|
2137
2371
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2138
2372
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2139
|
-
import
|
|
2373
|
+
import path8 from "node:path";
|
|
2140
2374
|
var NAME5 = "gemini-cli";
|
|
2141
2375
|
var GEMINI_EVENTS = [
|
|
2142
2376
|
"SessionStart",
|
|
@@ -2149,26 +2383,31 @@ var GEMINI_EVENTS = [
|
|
|
2149
2383
|
"SessionEnd"
|
|
2150
2384
|
];
|
|
2151
2385
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2152
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2153
|
-
var settings = (env) =>
|
|
2386
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
|
|
2387
|
+
var settings = (env) => path8.join(geminiDir(env), "settings.json");
|
|
2154
2388
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2155
2389
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2390
|
+
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2391
|
+
const win = platform === "win32";
|
|
2392
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path8.dirname(process.execPath)], env }) : "gemini";
|
|
2393
|
+
if (!bin) throw new Error("no gemini on PATH");
|
|
2394
|
+
const o = {
|
|
2395
|
+
encoding: "utf8",
|
|
2396
|
+
timeout: 4e3,
|
|
2397
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2398
|
+
env
|
|
2399
|
+
};
|
|
2400
|
+
const out = /\.cmd$/i.test(bin) ? execFileSync4(`"${bin}" --version`, { ...o, shell: true }) : execFileSync4(bin, ["--version"], o);
|
|
2401
|
+
return out.trim();
|
|
2402
|
+
}
|
|
2156
2403
|
function geminiHooksActive(settingsFile) {
|
|
2157
|
-
|
|
2158
|
-
try {
|
|
2159
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2160
|
-
} catch {
|
|
2161
|
-
}
|
|
2404
|
+
const tools = readJson(settingsFile).tools;
|
|
2162
2405
|
if (tools?.enableHooks === true) return { active: true };
|
|
2163
2406
|
if (tools?.enableHooks === false)
|
|
2164
2407
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2165
2408
|
let version = "";
|
|
2166
2409
|
try {
|
|
2167
|
-
version =
|
|
2168
|
-
encoding: "utf8",
|
|
2169
|
-
timeout: 4e3,
|
|
2170
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2171
|
-
}).trim();
|
|
2410
|
+
version = geminiVersion();
|
|
2172
2411
|
} catch {
|
|
2173
2412
|
return {
|
|
2174
2413
|
active: true,
|
|
@@ -2186,7 +2425,7 @@ var geminiCli = {
|
|
|
2186
2425
|
name: NAME5,
|
|
2187
2426
|
events: GEMINI_EVENTS,
|
|
2188
2427
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2189
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
2428
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
2190
2429
|
configFiles: (env = process.env) => [settings(env)],
|
|
2191
2430
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2192
2431
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2323,6 +2562,22 @@ function prune5(o) {
|
|
|
2323
2562
|
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
2324
2563
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2325
2564
|
|
|
2565
|
+
// ../core/src/launched-by.ts
|
|
2566
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2567
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2568
|
+
function launchedBy(env) {
|
|
2569
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2570
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2571
|
+
}
|
|
2572
|
+
function readHook(adapter, input) {
|
|
2573
|
+
const r = adapter.fromHook(input);
|
|
2574
|
+
if (r && !r.session.parent_id) {
|
|
2575
|
+
const launcher = launchedBy(input.env);
|
|
2576
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2577
|
+
}
|
|
2578
|
+
return r;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2326
2581
|
// src/index.ts
|
|
2327
2582
|
function matches(expect, got, pathStr = "") {
|
|
2328
2583
|
if (expect === "*") return null;
|
|
@@ -2353,7 +2608,7 @@ async function runAdapterFixtures(files) {
|
|
|
2353
2608
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
2354
2609
|
continue;
|
|
2355
2610
|
}
|
|
2356
|
-
const r = adapter
|
|
2611
|
+
const r = readHook(adapter, {
|
|
2357
2612
|
argv: fx.input.argv,
|
|
2358
2613
|
stdin: fx.input.stdin,
|
|
2359
2614
|
env: fx.input.env ?? {},
|
|
@@ -2383,8 +2638,8 @@ async function runAdapterFixtures(files) {
|
|
|
2383
2638
|
|
|
2384
2639
|
// src/cli.ts
|
|
2385
2640
|
var walk = (d) => readdirSync5(d).flatMap((n) => {
|
|
2386
|
-
const p =
|
|
2387
|
-
return
|
|
2641
|
+
const p = path9.join(d, n);
|
|
2642
|
+
return statSync5(p).isDirectory() ? walk(p) : p.endsWith(".json") ? [p] : [];
|
|
2388
2643
|
});
|
|
2389
2644
|
async function main(argv, io = process) {
|
|
2390
2645
|
const target = argv[0];
|