sessionpipe-conformance 0.10.0 → 0.12.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 +1018 -796
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1015 -793
- 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.12.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
|
-
import { readFileSync as
|
|
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";
|
|
10
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
24
11
|
|
|
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,50 +507,158 @@ 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(ev("turn.ended", { reason:
|
|
605
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop", background: backgroundCount(s.background_tasks) }));
|
|
512
606
|
break;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
607
|
+
case "StopFailure":
|
|
608
|
+
events.push(
|
|
609
|
+
ev("turn.ended", {
|
|
610
|
+
turn_id,
|
|
611
|
+
reason: "error",
|
|
612
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
613
|
+
})
|
|
614
|
+
);
|
|
615
|
+
break;
|
|
616
|
+
case "SubagentStart":
|
|
617
|
+
events.push(
|
|
618
|
+
ev("subagent.started", {
|
|
619
|
+
agent_id: String(s.agent_id ?? ""),
|
|
620
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
621
|
+
})
|
|
622
|
+
);
|
|
623
|
+
break;
|
|
624
|
+
case "SubagentStop":
|
|
625
|
+
events.push(
|
|
626
|
+
ev("subagent.ended", {
|
|
627
|
+
agent_id: String(s.agent_id ?? ""),
|
|
628
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0,
|
|
629
|
+
background: backgroundCount(s.background_tasks, String(s.agent_id ?? ""))
|
|
630
|
+
})
|
|
631
|
+
);
|
|
632
|
+
break;
|
|
633
|
+
case "PreCompact":
|
|
634
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
635
|
+
break;
|
|
636
|
+
case "PostCompact":
|
|
637
|
+
break;
|
|
638
|
+
// PreCompact already said it; a second event would double-count
|
|
639
|
+
case "SessionEnd":
|
|
640
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
641
|
+
break;
|
|
642
|
+
case "StatusLine": {
|
|
643
|
+
const limits = claudeLimits(s.rate_limits);
|
|
644
|
+
if (!limits) return null;
|
|
645
|
+
events.push(ev("limits.observed", limits));
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
default:
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const hints = {};
|
|
652
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
653
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
654
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
655
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
656
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
657
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
658
|
+
const r = { session, events, hints };
|
|
659
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
660
|
+
return r;
|
|
661
|
+
},
|
|
520
662
|
readTranscript(file, fromLine) {
|
|
521
663
|
let text = "";
|
|
522
664
|
try {
|
|
@@ -524,259 +666,605 @@ var antigravity = {
|
|
|
524
666
|
} catch {
|
|
525
667
|
return { turns: [], line: fromLine };
|
|
526
668
|
}
|
|
527
|
-
return
|
|
669
|
+
return readClaude(text.split("\n"), fromLine);
|
|
528
670
|
},
|
|
529
|
-
facts(session, transcript, hints) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
671
|
+
facts(session, transcript, hints, state) {
|
|
672
|
+
const out = {};
|
|
673
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
674
|
+
const cwd = out.cwd ?? session.cwd;
|
|
675
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
676
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
677
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
678
|
+
return out;
|
|
536
679
|
},
|
|
680
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
537
681
|
backfill(sinceMs, env = process.env) {
|
|
538
682
|
const rows2 = [];
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
683
|
+
const files = [];
|
|
684
|
+
for (const d of claudeDirs(env))
|
|
685
|
+
files.push(...recentFiles(path3.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
686
|
+
const noState = { get: () => ({}), save: () => {
|
|
687
|
+
} };
|
|
688
|
+
for (const f of files) {
|
|
689
|
+
const id = path3.basename(f, ".jsonl");
|
|
690
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
691
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
692
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
693
|
+
const cwd = rest.cwd;
|
|
694
|
+
rows2.push({
|
|
695
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
696
|
+
started_at,
|
|
697
|
+
last_at,
|
|
698
|
+
transcript: f
|
|
699
|
+
});
|
|
557
700
|
}
|
|
558
701
|
return rows2;
|
|
559
702
|
}
|
|
560
703
|
};
|
|
704
|
+
var WAITER_RE = /(?:^|[\s"'`/\\])sessionpipe(?:\.\w+)?["']?\s+wait(?:\s|$)/;
|
|
705
|
+
function backgroundCount(tasks, ending) {
|
|
706
|
+
if (!Array.isArray(tasks)) return void 0;
|
|
707
|
+
return tasks.filter((t) => {
|
|
708
|
+
const task = t;
|
|
709
|
+
if (ending !== void 0 && task?.id === ending) return false;
|
|
710
|
+
return !(typeof task?.command === "string" && WAITER_RE.test(task.command));
|
|
711
|
+
}).length;
|
|
712
|
+
}
|
|
713
|
+
var LINE_REFRESH_S = 60;
|
|
714
|
+
function statusLine(file, want, cmd) {
|
|
715
|
+
const f = readJsonFile(file);
|
|
716
|
+
if (!f.ok) return null;
|
|
717
|
+
const cur = f.value.statusLine;
|
|
718
|
+
const ours = typeof cur?.command === "string" && OURS.test(cur.command);
|
|
719
|
+
const command = cmd?.([NAME, "StatusLine"]).command;
|
|
720
|
+
if (want && cur && !ours)
|
|
721
|
+
return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
|
|
722
|
+
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
723
|
+
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
724
|
+
else delete f.value.statusLine;
|
|
725
|
+
writeFileSync2(file, formatJson(f.value, f));
|
|
726
|
+
return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
|
|
727
|
+
}
|
|
728
|
+
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
729
|
+
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
730
|
+
function claudeLimits(rl) {
|
|
731
|
+
if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
|
|
732
|
+
const windows = [];
|
|
733
|
+
for (const [id, w] of Object.entries(rl)) {
|
|
734
|
+
if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
|
|
735
|
+
windows.push(
|
|
736
|
+
prune({
|
|
737
|
+
id,
|
|
738
|
+
used_percent: w.used_percentage,
|
|
739
|
+
window_minutes: WINDOW_MINUTES[id],
|
|
740
|
+
resets_at: epochIso(w.resets_at)
|
|
741
|
+
})
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return windows.length ? { windows: windows.slice(0, 8) } : null;
|
|
745
|
+
}
|
|
561
746
|
function prune(o) {
|
|
562
747
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
563
748
|
return o;
|
|
564
749
|
}
|
|
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);
|
|
750
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
751
|
+
function notClaudeCode(id, s) {
|
|
752
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
578
753
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
754
|
+
function pickSource(v) {
|
|
755
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
756
|
+
}
|
|
757
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
758
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
759
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
760
|
+
function errText(v) {
|
|
761
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
762
|
+
if (v && typeof v === "object") {
|
|
763
|
+
const e = v;
|
|
764
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
765
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
586
766
|
}
|
|
587
|
-
return
|
|
767
|
+
return void 0;
|
|
588
768
|
}
|
|
589
|
-
function
|
|
590
|
-
|
|
769
|
+
function describe(input) {
|
|
770
|
+
if (!input || typeof input !== "object") return "";
|
|
771
|
+
const i = input;
|
|
772
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
773
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
774
|
+
}
|
|
775
|
+
var NAME_RANK = {
|
|
776
|
+
user: "custom",
|
|
777
|
+
auto: "harness",
|
|
778
|
+
hook: "harness",
|
|
779
|
+
peer: "harness"
|
|
780
|
+
};
|
|
781
|
+
function registryEntry(j) {
|
|
782
|
+
if (!j || typeof j !== "object") return null;
|
|
783
|
+
const r = j;
|
|
784
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
785
|
+
return {
|
|
786
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
787
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
788
|
+
name_source: source ?? null,
|
|
789
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
793
|
+
function claudeLive(dir) {
|
|
794
|
+
const hit = liveCache.get(dir);
|
|
795
|
+
if (hit) return hit;
|
|
796
|
+
const map = /* @__PURE__ */ new Map();
|
|
591
797
|
let names = [];
|
|
592
798
|
try {
|
|
593
|
-
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
799
|
+
names = readdirSync2(path3.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
594
800
|
} catch {
|
|
595
|
-
return out;
|
|
596
801
|
}
|
|
597
802
|
for (const n of names) {
|
|
598
|
-
const id = n.slice(0, -5);
|
|
599
|
-
if (!ID_RE.test(id)) continue;
|
|
600
|
-
let j;
|
|
601
803
|
try {
|
|
602
|
-
|
|
804
|
+
const e = registryEntry(JSON.parse(readFileSync2(path3.join(dir, "sessions", n), "utf8")));
|
|
805
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
603
806
|
} 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
807
|
}
|
|
611
|
-
if (folders.length) out.push({ id, folders });
|
|
612
808
|
}
|
|
613
|
-
|
|
809
|
+
liveCache.set(dir, map);
|
|
810
|
+
return map;
|
|
614
811
|
}
|
|
615
|
-
function
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
812
|
+
function claudeLiveByPid(dir, pid) {
|
|
813
|
+
if (!pid) return null;
|
|
814
|
+
try {
|
|
815
|
+
return registryEntry(JSON.parse(readFileSync2(path3.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
816
|
+
} catch {
|
|
817
|
+
return null;
|
|
620
818
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
819
|
+
}
|
|
820
|
+
function claudePid(env = process.env) {
|
|
821
|
+
try {
|
|
822
|
+
const dirs = claudeDirs(env);
|
|
823
|
+
const has = (pid2) => dirs.some((d) => existsSync2(path3.join(d, "sessions", `${pid2}.json`)));
|
|
824
|
+
let pid = process.ppid;
|
|
825
|
+
if (has(pid)) return pid;
|
|
826
|
+
const parent = parentOf();
|
|
827
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
828
|
+
pid = parent(pid);
|
|
829
|
+
if (pid > 1 && has(pid)) return pid;
|
|
830
|
+
}
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
function parentOf() {
|
|
836
|
+
if (process.platform === "linux") {
|
|
837
|
+
return (pid) => {
|
|
838
|
+
try {
|
|
839
|
+
const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
840
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
841
|
+
} catch {
|
|
842
|
+
return 0;
|
|
629
843
|
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const table = /* @__PURE__ */ new Map();
|
|
847
|
+
try {
|
|
848
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
849
|
+
encoding: "utf8",
|
|
850
|
+
timeout: 2e3,
|
|
851
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
852
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
853
|
+
});
|
|
854
|
+
for (const l of out.split("\n")) {
|
|
855
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
856
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
630
857
|
}
|
|
631
|
-
|
|
858
|
+
} catch {
|
|
859
|
+
}
|
|
860
|
+
return (pid) => table.get(pid) || 0;
|
|
632
861
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
862
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
863
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
864
|
+
const files = [path3.join(dir, ".claude.json")];
|
|
865
|
+
if (path3.resolve(dir) === path3.join(home, ".claude")) files.unshift(path3.join(home, ".claude.json"));
|
|
866
|
+
for (const f of files) {
|
|
637
867
|
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;
|
|
868
|
+
const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
|
|
869
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
870
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
871
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
872
|
+
}
|
|
647
873
|
} catch {
|
|
648
874
|
}
|
|
649
875
|
}
|
|
650
|
-
return
|
|
876
|
+
return apiAccount(dir);
|
|
651
877
|
}
|
|
652
|
-
|
|
653
|
-
|
|
878
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
879
|
+
function apiAccount(dir) {
|
|
880
|
+
const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
|
|
881
|
+
if (typeof base !== "string") return null;
|
|
882
|
+
let u;
|
|
883
|
+
try {
|
|
884
|
+
u = new URL(base.trim());
|
|
885
|
+
} catch {
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
889
|
+
return null;
|
|
890
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
891
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
892
|
+
}
|
|
893
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
894
|
+
function claudeAccount(dir) {
|
|
895
|
+
const hit = accountCache.get(dir);
|
|
896
|
+
if (hit !== void 0) return hit;
|
|
897
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
898
|
+
accountCache.set(dir, acct);
|
|
899
|
+
return acct;
|
|
900
|
+
}
|
|
901
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
902
|
+
function transcriptLink(records) {
|
|
903
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
904
|
+
const j = records[i];
|
|
905
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
906
|
+
return j.url;
|
|
907
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
908
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
909
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
function appTitle(text) {
|
|
915
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
916
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
917
|
+
}
|
|
918
|
+
function askText(j) {
|
|
919
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
920
|
+
const origin = j.origin;
|
|
921
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
922
|
+
const c = j.message?.content;
|
|
923
|
+
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();
|
|
924
|
+
if (!text || isInjected(text)) return null;
|
|
925
|
+
return text;
|
|
926
|
+
}
|
|
927
|
+
function firstAsk(records) {
|
|
928
|
+
for (const j of records) {
|
|
929
|
+
const t = askText(j);
|
|
930
|
+
if (t) return appTitle(t);
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
function scanTitles(file, from, found) {
|
|
935
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
936
|
+
let size = 0;
|
|
937
|
+
try {
|
|
938
|
+
size = statSync2(file).size;
|
|
939
|
+
} catch {
|
|
940
|
+
return out;
|
|
941
|
+
}
|
|
942
|
+
if (size < from) {
|
|
943
|
+
out.upto = 0;
|
|
944
|
+
out.custom = out.ai = null;
|
|
945
|
+
from = 0;
|
|
946
|
+
}
|
|
947
|
+
let pos = 0;
|
|
948
|
+
scanLines(
|
|
949
|
+
file,
|
|
950
|
+
(l) => {
|
|
951
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
952
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
953
|
+
try {
|
|
954
|
+
const j = JSON.parse(l);
|
|
955
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
956
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
957
|
+
} catch {
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
},
|
|
961
|
+
512 * 1024 * 1024
|
|
962
|
+
);
|
|
963
|
+
out.upto = size;
|
|
964
|
+
return out;
|
|
965
|
+
}
|
|
966
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
967
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
654
968
|
const H = parseLines(head);
|
|
655
969
|
const T = parseLines(tail);
|
|
656
970
|
const out = {};
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
971
|
+
const id = sessionId || path3.basename(file, ".jsonl");
|
|
972
|
+
const mem = state.get(NAME, id);
|
|
973
|
+
let custom = null;
|
|
974
|
+
let ai = null;
|
|
975
|
+
for (const j of H.concat(T)) {
|
|
976
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
977
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
978
|
+
}
|
|
979
|
+
let size = 0;
|
|
980
|
+
try {
|
|
981
|
+
size = statSync2(file).size;
|
|
982
|
+
} catch {
|
|
983
|
+
}
|
|
984
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
985
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
986
|
+
custom: mem.scan_custom,
|
|
987
|
+
ai: mem.scan_ai
|
|
988
|
+
});
|
|
989
|
+
custom = s.custom;
|
|
990
|
+
ai = s.ai;
|
|
991
|
+
state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
992
|
+
}
|
|
993
|
+
const dir = configDirOf(file);
|
|
994
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
995
|
+
if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
|
|
996
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
997
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
998
|
+
if (url) {
|
|
999
|
+
out.url = url;
|
|
1000
|
+
state.save(NAME, id, { url });
|
|
1001
|
+
}
|
|
1002
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1003
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1004
|
+
let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1005
|
+
if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1006
|
+
ask = scanLines(file, (l) => {
|
|
1007
|
+
try {
|
|
1008
|
+
const t = askText(JSON.parse(l));
|
|
1009
|
+
return t ? appTitle(t) : null;
|
|
1010
|
+
} catch {
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
if (!ask && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
|
|
1015
|
+
}
|
|
1016
|
+
if (ask) {
|
|
1017
|
+
out.first_ask = ask;
|
|
1018
|
+
if (!mem.first_ask) state.save(NAME, id, { first_ask: ask });
|
|
1019
|
+
}
|
|
1020
|
+
if (pick) {
|
|
1021
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1022
|
+
out.title_source = pick.source;
|
|
1023
|
+
} else if (ask) {
|
|
1024
|
+
out.title = ask;
|
|
666
1025
|
out.title_source = "first-ask";
|
|
667
|
-
break;
|
|
668
1026
|
}
|
|
669
|
-
const
|
|
1027
|
+
const acct = claudeAccount(dir);
|
|
1028
|
+
if (acct) out.account_id = acct;
|
|
1029
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1030
|
+
const m = T[i].message?.model;
|
|
1031
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1032
|
+
out.model = m;
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1037
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1038
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
670
1039
|
if (times.length) {
|
|
671
1040
|
out.started_at = Math.min(...times);
|
|
672
1041
|
out.last_at = Math.max(...times);
|
|
673
1042
|
}
|
|
1043
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1044
|
+
if (withCwd) {
|
|
1045
|
+
out.cwd = withCwd.cwd;
|
|
1046
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1047
|
+
}
|
|
674
1048
|
return out;
|
|
675
1049
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1050
|
+
var REMOTE_RETRY_MS = 6e4;
|
|
1051
|
+
var REMOTE_REFRESH_MS = 30 * 6e4;
|
|
1052
|
+
var CLAUDE_API = "https://api.anthropic.com";
|
|
1053
|
+
var KEYCHAIN = "Claude Code-credentials";
|
|
1054
|
+
var REMOTE_TIMEOUT_MS = 3e3;
|
|
1055
|
+
var KEYCHAIN_TIMEOUT_MS = 2e3;
|
|
1056
|
+
var REMOTE_MAX_BYTES = 1 << 20;
|
|
1057
|
+
var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1058
|
+
async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
|
|
1059
|
+
if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
|
|
1060
|
+
const mem = state.get(NAME, id);
|
|
1061
|
+
let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
|
|
1062
|
+
if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
|
|
1063
|
+
state.save(NAME, id, { remote_at: now });
|
|
1064
|
+
const token = claudeAiToken(configDirOf(transcript), env, now);
|
|
1065
|
+
const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
|
|
1066
|
+
if (got !== void 0) {
|
|
1067
|
+
title = got?.slice(0, 200) ?? null;
|
|
1068
|
+
state.save(NAME, id, { remote_title: title });
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
|
|
1072
|
+
}
|
|
1073
|
+
function keychainServices(dir, env) {
|
|
1074
|
+
const named = (p) => `${KEYCHAIN}-${createHash("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
1075
|
+
const out = /* @__PURE__ */ new Set();
|
|
1076
|
+
if (env.CLAUDE_CONFIG_DIR && path3.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
|
|
1077
|
+
if (dir === path3.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
|
|
1078
|
+
out.add(named(dir));
|
|
1079
|
+
return [...out];
|
|
1080
|
+
}
|
|
1081
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1082
|
+
function claudeAiToken(dir, env, now) {
|
|
1083
|
+
const hit = tokenCache.get(dir);
|
|
1084
|
+
if (hit && hit.expires > now + 6e4) return hit.token;
|
|
1085
|
+
const raw = [];
|
|
1086
|
+
if (process.platform === "darwin")
|
|
1087
|
+
for (const service of keychainServices(dir, env)) {
|
|
1088
|
+
try {
|
|
1089
|
+
const user = env.USER || userInfo().username;
|
|
1090
|
+
const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
|
|
1091
|
+
raw.push(
|
|
1092
|
+
execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
|
|
1093
|
+
encoding: "utf8",
|
|
1094
|
+
timeout: KEYCHAIN_TIMEOUT_MS,
|
|
1095
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1096
|
+
})
|
|
1097
|
+
);
|
|
1098
|
+
break;
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
raw.push(readFileSync2(path3.join(dir, ".credentials.json"), "utf8"));
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
for (const r of raw) {
|
|
691
1107
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1108
|
+
const t = r.trim();
|
|
1109
|
+
const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
|
|
1110
|
+
if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
|
|
1111
|
+
const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
|
|
1112
|
+
if (expires <= now + 6e4) continue;
|
|
1113
|
+
tokenCache.set(dir, { token: o.accessToken, expires });
|
|
1114
|
+
return o.accessToken;
|
|
697
1115
|
} catch {
|
|
698
|
-
return "";
|
|
699
1116
|
}
|
|
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;
|
|
1117
|
+
}
|
|
1118
|
+
return null;
|
|
708
1119
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1120
|
+
async function claudeAiTitle(bridge, token) {
|
|
1121
|
+
try {
|
|
1122
|
+
const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
|
|
1123
|
+
headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
|
|
1124
|
+
redirect: "error",
|
|
1125
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1126
|
+
});
|
|
1127
|
+
if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
|
|
1128
|
+
const text = await r.text();
|
|
1129
|
+
if (text.length > REMOTE_MAX_BYTES) return void 0;
|
|
1130
|
+
const j = JSON.parse(text);
|
|
1131
|
+
const s = j?.response_shape ?? j?.session ?? j;
|
|
1132
|
+
const bare = (x) => x.replace(/^(?:session|cse)_/, "");
|
|
1133
|
+
if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
|
|
1134
|
+
return typeof s.title === "string" && cleanName(s.title) || null;
|
|
1135
|
+
} catch {
|
|
1136
|
+
return void 0;
|
|
1137
|
+
}
|
|
713
1138
|
}
|
|
714
1139
|
|
|
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);
|
|
1140
|
+
// ../core/src/adapters/headless.ts
|
|
1141
|
+
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1142
|
+
import path4 from "node:path";
|
|
1143
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1144
|
+
var OUT_CAP = 1024 * 1024;
|
|
1145
|
+
function isExe(p) {
|
|
752
1146
|
try {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1147
|
+
if (!statSync3(p).isFile()) return false;
|
|
1148
|
+
accessSync(p, constants.X_OK);
|
|
1149
|
+
return true;
|
|
1150
|
+
} catch {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
function findBinary(o) {
|
|
1155
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
1156
|
+
for (const d of String(o.env?.PATH || "").split(path4.delimiter)) {
|
|
1157
|
+
if (!d || !path4.isAbsolute(d)) continue;
|
|
1158
|
+
for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
|
|
1159
|
+
}
|
|
1160
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
function shimTarget(shim) {
|
|
1164
|
+
let text = "";
|
|
1165
|
+
try {
|
|
1166
|
+
text = readFileSync3(shim, "utf8");
|
|
1167
|
+
} catch {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
const m = /"%(?:~dp0|dp0%)\\([^"]+)"\s+%\*/.exec(text);
|
|
1171
|
+
if (!m) return null;
|
|
1172
|
+
const target = path4.resolve(path4.dirname(shim), m[1].split("\\").join(path4.sep));
|
|
1173
|
+
try {
|
|
1174
|
+
return statSync3(target).isFile() ? target : null;
|
|
758
1175
|
} catch {
|
|
1176
|
+
return null;
|
|
759
1177
|
}
|
|
760
|
-
return out.filter((d, i) => i === 0 || existsSync3(d));
|
|
761
1178
|
}
|
|
762
|
-
|
|
763
|
-
|
|
1179
|
+
function spawnable(bin, args) {
|
|
1180
|
+
if (!/\.(cmd|bat)$/i.test(bin)) return { file: bin, args };
|
|
1181
|
+
const target = shimTarget(bin);
|
|
1182
|
+
return target ? { file: process.execPath, args: [target, ...args] } : null;
|
|
1183
|
+
}
|
|
1184
|
+
function probe(bin, args) {
|
|
1185
|
+
const s = spawnable(bin, args);
|
|
1186
|
+
return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// ../core/src/adapters/antigravity.ts
|
|
1190
|
+
import {
|
|
1191
|
+
existsSync as existsSync3,
|
|
1192
|
+
mkdirSync as mkdirSync2,
|
|
1193
|
+
readdirSync as readdirSync3,
|
|
1194
|
+
readFileSync as readFileSync4,
|
|
1195
|
+
realpathSync,
|
|
1196
|
+
statSync as statSync4,
|
|
1197
|
+
unlinkSync as unlinkSync2,
|
|
1198
|
+
writeFileSync as writeFileSync3
|
|
1199
|
+
} from "node:fs";
|
|
1200
|
+
import path5 from "node:path";
|
|
1201
|
+
var NAME2 = "antigravity";
|
|
1202
|
+
var HOOK_NAME = "sessionpipe";
|
|
1203
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
1204
|
+
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';
|
|
1205
|
+
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
1206
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path5.join(homeDir(), ".gemini");
|
|
1207
|
+
var hooksFile = (env) => path5.join(gemini(env), "config", "hooks.json");
|
|
1208
|
+
var projectsDir = (env) => path5.join(gemini(env), "config", "projects");
|
|
1209
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path5.join(gemini(env), d));
|
|
1210
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
764
1211
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1212
|
+
function entry(cmd) {
|
|
1213
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
|
|
1214
|
+
const out = { enabled: true };
|
|
1215
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
1216
|
+
return out;
|
|
1217
|
+
}
|
|
1218
|
+
var antigravity = {
|
|
768
1219
|
name: NAME2,
|
|
769
|
-
events:
|
|
770
|
-
detect: (env = process.env) =>
|
|
771
|
-
configFiles: (env = process.env) =>
|
|
772
|
-
install
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1220
|
+
events: AG_EVENTS,
|
|
1221
|
+
detect: (env = process.env) => existsSync3(path5.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
|
|
1222
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
1223
|
+
install(cmd, env = process.env) {
|
|
1224
|
+
const file = hooksFile(env);
|
|
1225
|
+
const f = readJsonFile(file);
|
|
1226
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1227
|
+
const hooks = f.value;
|
|
1228
|
+
const e = entry(cmd);
|
|
1229
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1230
|
+
hooks[HOOK_NAME] = e;
|
|
1231
|
+
mkdirSync2(path5.dirname(file), { recursive: true });
|
|
1232
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1233
|
+
return [
|
|
1234
|
+
{
|
|
1235
|
+
file,
|
|
1236
|
+
changed: true,
|
|
1237
|
+
created: !f.existed,
|
|
1238
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
1239
|
+
}
|
|
1240
|
+
];
|
|
1241
|
+
},
|
|
1242
|
+
uninstall(env = process.env, opts = {}) {
|
|
1243
|
+
const file = hooksFile(env);
|
|
1244
|
+
if (!existsSync3(file)) return [{ file, changed: false }];
|
|
1245
|
+
const f = readJsonFile(file);
|
|
1246
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1247
|
+
const hooks = f.value;
|
|
1248
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1249
|
+
delete hooks[HOOK_NAME];
|
|
1250
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
1251
|
+
unlinkSync2(file);
|
|
1252
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1253
|
+
}
|
|
1254
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1255
|
+
return [{ file, changed: true }];
|
|
1256
|
+
},
|
|
1257
|
+
installed(cmd, env = process.env) {
|
|
1258
|
+
const file = hooksFile(env);
|
|
1259
|
+
const f = readJsonFile(file);
|
|
1260
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
1261
|
+
return [
|
|
1262
|
+
{
|
|
1263
|
+
file,
|
|
1264
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
1265
|
+
}
|
|
1266
|
+
];
|
|
1267
|
+
},
|
|
780
1268
|
fromHook(input) {
|
|
781
1269
|
const event = input.argv[1] ?? "";
|
|
782
1270
|
let s = {};
|
|
@@ -785,513 +1273,213 @@ var claudeCode = {
|
|
|
785
1273
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
786
1274
|
} catch {
|
|
787
1275
|
}
|
|
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)}`;
|
|
1276
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
1277
|
+
const stdout = antigravityAnswer(event);
|
|
1278
|
+
if (!id) return { session: { id: "" }, events: [], stdout };
|
|
1279
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
1280
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
1281
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
1282
|
+
const toolCall = s.toolCall;
|
|
1283
|
+
const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
|
|
801
1284
|
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
802
1285
|
const events = [];
|
|
803
1286
|
switch (event) {
|
|
804
|
-
case "
|
|
805
|
-
|
|
1287
|
+
case "PreInvocation":
|
|
1288
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
1289
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
1290
|
+
events.push(ev("turn.started", {}));
|
|
1291
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
806
1292
|
break;
|
|
807
|
-
case "
|
|
808
|
-
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
809
|
-
events.push(
|
|
810
|
-
ev("turn.started", {
|
|
811
|
-
turn_id,
|
|
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
|
|
825
|
-
})
|
|
826
|
-
);
|
|
1293
|
+
case "PostInvocation":
|
|
827
1294
|
break;
|
|
828
|
-
}
|
|
829
1295
|
case "PreToolUse":
|
|
830
|
-
if (tool) events.push(ev("tool.started", { tool,
|
|
1296
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
831
1297
|
break;
|
|
832
1298
|
case "PostToolUse":
|
|
833
|
-
if (tool)
|
|
834
|
-
|
|
835
|
-
ev("tool.ended", {
|
|
836
|
-
tool,
|
|
837
|
-
call_id,
|
|
838
|
-
turn_id,
|
|
839
|
-
agent_id,
|
|
840
|
-
ms,
|
|
841
|
-
ok: true,
|
|
842
|
-
input: s.tool_input,
|
|
843
|
-
output: s.tool_response
|
|
844
|
-
})
|
|
845
|
-
);
|
|
846
|
-
break;
|
|
847
|
-
case "PostToolUseFailure":
|
|
848
|
-
if (tool)
|
|
1299
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1300
|
+
else
|
|
849
1301
|
events.push(
|
|
850
1302
|
ev("tool.ended", {
|
|
851
1303
|
tool,
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
ms,
|
|
856
|
-
ok: false,
|
|
857
|
-
error: errText(s.error ?? s.tool_response),
|
|
858
|
-
input: s.tool_input,
|
|
859
|
-
output: s.tool_response
|
|
1304
|
+
ok: !s.error,
|
|
1305
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1306
|
+
input: toolCall?.args
|
|
860
1307
|
})
|
|
861
1308
|
);
|
|
862
1309
|
break;
|
|
863
|
-
case "PermissionRequest":
|
|
864
|
-
events.push(
|
|
865
|
-
ev("attention.needed", {
|
|
866
|
-
attention_id: call_id ?? permissionId(),
|
|
867
|
-
kind: "permission",
|
|
868
|
-
tool,
|
|
869
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
870
|
-
})
|
|
871
|
-
);
|
|
872
|
-
break;
|
|
873
|
-
case "Notification":
|
|
874
|
-
events.push(
|
|
875
|
-
ev("attention.needed", {
|
|
876
|
-
attention_id: `notif-${Date.now()}`,
|
|
877
|
-
kind: notifKind(s.notification_type),
|
|
878
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
879
|
-
})
|
|
880
|
-
);
|
|
881
|
-
break;
|
|
882
1310
|
case "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
|
-
);
|
|
890
|
-
break;
|
|
891
|
-
case "StopFailure":
|
|
892
|
-
events.push(
|
|
893
|
-
ev("turn.ended", {
|
|
894
|
-
turn_id,
|
|
895
|
-
reason: "error",
|
|
896
|
-
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
897
|
-
})
|
|
898
|
-
);
|
|
899
|
-
break;
|
|
900
|
-
case "SubagentStart":
|
|
901
|
-
events.push(
|
|
902
|
-
ev("subagent.started", {
|
|
903
|
-
agent_id: String(s.agent_id ?? ""),
|
|
904
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
905
|
-
})
|
|
906
|
-
);
|
|
907
|
-
break;
|
|
908
|
-
case "SubagentStop":
|
|
909
|
-
events.push(
|
|
910
|
-
ev("subagent.ended", {
|
|
911
|
-
agent_id: String(s.agent_id ?? ""),
|
|
912
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
913
|
-
})
|
|
914
|
-
);
|
|
915
|
-
break;
|
|
916
|
-
case "PreCompact":
|
|
917
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
918
|
-
break;
|
|
919
|
-
case "PostCompact":
|
|
920
|
-
break;
|
|
921
|
-
// PreCompact already said it; a second event would double-count
|
|
922
|
-
case "SessionEnd":
|
|
923
|
-
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
1311
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
924
1312
|
break;
|
|
925
1313
|
default:
|
|
926
|
-
return
|
|
1314
|
+
return { session, events: [], stdout };
|
|
927
1315
|
}
|
|
928
|
-
const
|
|
929
|
-
if (
|
|
930
|
-
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
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;
|
|
934
|
-
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
935
|
-
const r = { session, events, hints };
|
|
936
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1316
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
1317
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
937
1318
|
return r;
|
|
938
1319
|
},
|
|
939
1320
|
readTranscript(file, fromLine) {
|
|
940
1321
|
let text = "";
|
|
941
1322
|
try {
|
|
942
|
-
text =
|
|
1323
|
+
text = readFileSync4(file, "utf8");
|
|
943
1324
|
} catch {
|
|
944
1325
|
return { turns: [], line: fromLine };
|
|
945
1326
|
}
|
|
946
|
-
return
|
|
1327
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
947
1328
|
},
|
|
948
|
-
facts(session, transcript, hints
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
return out;
|
|
1329
|
+
facts(session, transcript, hints) {
|
|
1330
|
+
if (!transcript) return {};
|
|
1331
|
+
return antigravityFacts(transcript, {
|
|
1332
|
+
conversation: session.id,
|
|
1333
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
1334
|
+
env: process.env
|
|
1335
|
+
});
|
|
956
1336
|
},
|
|
957
1337
|
backfill(sinceMs, env = process.env) {
|
|
958
1338
|
const rows2 = [];
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
962
|
-
const noState = { get: () => ({}), save: () => {
|
|
963
|
-
} };
|
|
964
|
-
for (const f of files) {
|
|
965
|
-
const id = path4.basename(f, ".jsonl");
|
|
966
|
-
const facts = claudeFacts(f, id, {}, noState);
|
|
967
|
-
if (!facts.started_at || !facts.last_at) continue;
|
|
968
|
-
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
969
|
-
const cwd = rest.cwd;
|
|
970
|
-
rows2.push({
|
|
971
|
-
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
972
|
-
started_at,
|
|
973
|
-
last_at
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
return rows2;
|
|
977
|
-
}
|
|
978
|
-
};
|
|
979
|
-
function prune2(o) {
|
|
980
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
981
|
-
return o;
|
|
982
|
-
}
|
|
983
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
984
|
-
function notClaudeCode(id, s) {
|
|
985
|
-
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
986
|
-
}
|
|
987
|
-
function pickSource(v) {
|
|
988
|
-
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
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());
|
|
993
|
-
function errText(v) {
|
|
994
|
-
if (typeof v === "string") return v.slice(0, 2e3);
|
|
995
|
-
if (v && typeof v === "object") {
|
|
996
|
-
const e = v;
|
|
997
|
-
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
998
|
-
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
999
|
-
}
|
|
1000
|
-
return void 0;
|
|
1001
|
-
}
|
|
1002
|
-
function describe(input) {
|
|
1003
|
-
if (!input || typeof input !== "object") return "";
|
|
1004
|
-
const i = input;
|
|
1005
|
-
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
1006
|
-
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
1007
|
-
}
|
|
1008
|
-
var NAME_RANK = {
|
|
1009
|
-
user: "custom",
|
|
1010
|
-
auto: "harness",
|
|
1011
|
-
hook: "harness",
|
|
1012
|
-
peer: "harness"
|
|
1013
|
-
};
|
|
1014
|
-
function registryEntry(j) {
|
|
1015
|
-
if (!j || typeof j !== "object") return null;
|
|
1016
|
-
const r = j;
|
|
1017
|
-
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
1018
|
-
return {
|
|
1019
|
-
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
1020
|
-
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
1021
|
-
name_source: source ?? null,
|
|
1022
|
-
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
var liveCache = /* @__PURE__ */ new Map();
|
|
1026
|
-
function claudeLive(dir) {
|
|
1027
|
-
const hit = liveCache.get(dir);
|
|
1028
|
-
if (hit) return hit;
|
|
1029
|
-
const map = /* @__PURE__ */ new Map();
|
|
1030
|
-
let names = [];
|
|
1031
|
-
try {
|
|
1032
|
-
names = readdirSync3(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
1033
|
-
} catch {
|
|
1034
|
-
}
|
|
1035
|
-
for (const n of names) {
|
|
1036
|
-
try {
|
|
1037
|
-
const e = registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", n), "utf8")));
|
|
1038
|
-
if (e?.sessionId) map.set(e.sessionId, e);
|
|
1039
|
-
} catch {
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
liveCache.set(dir, map);
|
|
1043
|
-
return map;
|
|
1044
|
-
}
|
|
1045
|
-
function claudeLiveByPid(dir, pid) {
|
|
1046
|
-
if (!pid) return null;
|
|
1047
|
-
try {
|
|
1048
|
-
return registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1049
|
-
} catch {
|
|
1050
|
-
return null;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
function claudePid(env = process.env) {
|
|
1054
|
-
try {
|
|
1055
|
-
const dirs = claudeDirs(env);
|
|
1056
|
-
const has = (pid2) => dirs.some((d) => existsSync3(path4.join(d, "sessions", `${pid2}.json`)));
|
|
1057
|
-
let pid = process.ppid;
|
|
1058
|
-
if (has(pid)) return pid;
|
|
1059
|
-
const parent = parentOf();
|
|
1060
|
-
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
1061
|
-
pid = parent(pid);
|
|
1062
|
-
if (pid > 1 && has(pid)) return pid;
|
|
1063
|
-
}
|
|
1064
|
-
} catch {
|
|
1065
|
-
}
|
|
1066
|
-
return null;
|
|
1067
|
-
}
|
|
1068
|
-
function parentOf() {
|
|
1069
|
-
if (process.platform === "linux") {
|
|
1070
|
-
return (pid) => {
|
|
1339
|
+
for (const data of dataDirs(env)) {
|
|
1340
|
+
let ids = [];
|
|
1071
1341
|
try {
|
|
1072
|
-
|
|
1073
|
-
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
1342
|
+
ids = readdirSync3(path5.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
1074
1343
|
} catch {
|
|
1075
|
-
return 0;
|
|
1076
1344
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
1345
|
+
for (const id of ids) {
|
|
1346
|
+
const f = path5.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
1347
|
+
try {
|
|
1348
|
+
if (statSync4(f).mtimeMs < sinceMs) continue;
|
|
1349
|
+
} catch {
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
1353
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1354
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1355
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
1356
|
+
}
|
|
1090
1357
|
}
|
|
1091
|
-
|
|
1358
|
+
return rows2;
|
|
1092
1359
|
}
|
|
1093
|
-
|
|
1360
|
+
};
|
|
1361
|
+
function prune2(o) {
|
|
1362
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1363
|
+
return o;
|
|
1094
1364
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
1099
|
-
for (const f of files) {
|
|
1365
|
+
function agPath(p) {
|
|
1366
|
+
if (typeof p !== "string" || !p) return null;
|
|
1367
|
+
let s = p;
|
|
1368
|
+
if (/^file:/i.test(s)) {
|
|
1100
1369
|
try {
|
|
1101
|
-
|
|
1102
|
-
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1103
|
-
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1104
|
-
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1105
|
-
}
|
|
1370
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1106
1371
|
} catch {
|
|
1372
|
+
return null;
|
|
1107
1373
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1374
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
1375
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
1376
|
+
if (!path5.isAbsolute(s)) return null;
|
|
1377
|
+
return path5.resolve(s);
|
|
1110
1378
|
}
|
|
1111
|
-
var
|
|
1112
|
-
function
|
|
1113
|
-
|
|
1379
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
1380
|
+
function agVariants(p) {
|
|
1381
|
+
const out = [p];
|
|
1114
1382
|
try {
|
|
1115
|
-
|
|
1383
|
+
const r = realpathSync.native(p);
|
|
1384
|
+
if (r !== p) out.push(r);
|
|
1116
1385
|
} catch {
|
|
1117
|
-
return null;
|
|
1118
1386
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1387
|
+
return out;
|
|
1388
|
+
}
|
|
1389
|
+
function agProjects(dir) {
|
|
1390
|
+
const out = [];
|
|
1391
|
+
let names = [];
|
|
1121
1392
|
try {
|
|
1122
|
-
|
|
1393
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1123
1394
|
} catch {
|
|
1124
|
-
return
|
|
1395
|
+
return out;
|
|
1125
1396
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1140
|
-
function transcriptLink(records) {
|
|
1141
|
-
for (let i = records.length - 1; i >= 0; i--) {
|
|
1142
|
-
const j = records[i];
|
|
1143
|
-
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
1144
|
-
return j.url;
|
|
1145
|
-
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
1146
|
-
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
1147
|
-
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
1397
|
+
for (const n of names) {
|
|
1398
|
+
const id = n.slice(0, -5);
|
|
1399
|
+
if (!ID_RE.test(id)) continue;
|
|
1400
|
+
let j;
|
|
1401
|
+
try {
|
|
1402
|
+
j = JSON.parse(readFileSync4(path5.join(dir, n), "utf8"));
|
|
1403
|
+
} catch {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const folders = [];
|
|
1407
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
1408
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
1409
|
+
if (f) folders.push(...agVariants(f));
|
|
1148
1410
|
}
|
|
1411
|
+
if (folders.length) out.push({ id, folders });
|
|
1149
1412
|
}
|
|
1150
|
-
return
|
|
1151
|
-
}
|
|
1152
|
-
function appTitle(text) {
|
|
1153
|
-
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1154
|
-
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1155
|
-
}
|
|
1156
|
-
function askText(j) {
|
|
1157
|
-
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1158
|
-
const origin = j.origin;
|
|
1159
|
-
if (origin?.kind && origin.kind !== "human") return null;
|
|
1160
|
-
const c = j.message?.content;
|
|
1161
|
-
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();
|
|
1162
|
-
if (!text || isInjected(text)) return null;
|
|
1163
|
-
return text;
|
|
1413
|
+
return out;
|
|
1164
1414
|
}
|
|
1165
|
-
function
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1415
|
+
function agMatchProject(projects, workspaces) {
|
|
1416
|
+
const ws = [];
|
|
1417
|
+
for (const w of workspaces) {
|
|
1418
|
+
const p = agPath(w);
|
|
1419
|
+
if (p) ws.push(...agVariants(p));
|
|
1169
1420
|
}
|
|
1170
|
-
|
|
1421
|
+
let best = null;
|
|
1422
|
+
let bestLen = -1;
|
|
1423
|
+
for (const pr of projects)
|
|
1424
|
+
for (const f of pr.folders) {
|
|
1425
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path5.sep) ? f : f + path5.sep));
|
|
1426
|
+
if (within && f.length > bestLen) {
|
|
1427
|
+
best = pr.id;
|
|
1428
|
+
bestLen = f.length;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return best;
|
|
1171
1432
|
}
|
|
1172
|
-
function
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1433
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
1434
|
+
const i = transcript.indexOf(`${path5.sep}brain${path5.sep}`);
|
|
1435
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
1436
|
+
for (const d of dirs) {
|
|
1437
|
+
try {
|
|
1438
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1439
|
+
readFileSync4(path5.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1440
|
+
);
|
|
1441
|
+
if (!m) continue;
|
|
1442
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
1443
|
+
const conv = opts.conversation ?? "";
|
|
1444
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
1445
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
1446
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1184
1449
|
}
|
|
1185
|
-
|
|
1186
|
-
scanLines(
|
|
1187
|
-
file,
|
|
1188
|
-
(l) => {
|
|
1189
|
-
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1190
|
-
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1191
|
-
try {
|
|
1192
|
-
const j = JSON.parse(l);
|
|
1193
|
-
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1194
|
-
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1195
|
-
} catch {
|
|
1196
|
-
}
|
|
1197
|
-
return null;
|
|
1198
|
-
},
|
|
1199
|
-
512 * 1024 * 1024
|
|
1200
|
-
);
|
|
1201
|
-
out.upto = size;
|
|
1202
|
-
return out;
|
|
1450
|
+
return null;
|
|
1203
1451
|
}
|
|
1204
|
-
function
|
|
1205
|
-
const { head, tail } = ends(file,
|
|
1452
|
+
function antigravityFacts(file, opts) {
|
|
1453
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1206
1454
|
const H = parseLines(head);
|
|
1207
1455
|
const T = parseLines(tail);
|
|
1208
1456
|
const out = {};
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
try {
|
|
1219
|
-
size = statSync3(file).size;
|
|
1220
|
-
} catch {
|
|
1221
|
-
}
|
|
1222
|
-
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
1223
|
-
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
1224
|
-
custom: mem.scan_custom,
|
|
1225
|
-
ai: mem.scan_ai
|
|
1226
|
-
});
|
|
1227
|
-
custom = s.custom;
|
|
1228
|
-
ai = s.ai;
|
|
1229
|
-
state.save(NAME2, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
1230
|
-
}
|
|
1231
|
-
const dir = configDirOf(file);
|
|
1232
|
-
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
1233
|
-
if (live?.name) state.save(NAME2, id, { name: live.name, name_source: live.name_source });
|
|
1234
|
-
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
1235
|
-
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
1236
|
-
if (url) {
|
|
1237
|
-
out.url = url;
|
|
1238
|
-
state.save(NAME2, id, { url });
|
|
1239
|
-
}
|
|
1240
|
-
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1241
|
-
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1242
|
-
let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1243
|
-
if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1244
|
-
ask = scanLines(file, (l) => {
|
|
1245
|
-
try {
|
|
1246
|
-
const t = askText(JSON.parse(l));
|
|
1247
|
-
return t ? appTitle(t) : null;
|
|
1248
|
-
} catch {
|
|
1249
|
-
return null;
|
|
1250
|
-
}
|
|
1251
|
-
});
|
|
1252
|
-
if (!ask && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
|
|
1253
|
-
}
|
|
1254
|
-
if (ask) {
|
|
1255
|
-
out.first_ask = ask;
|
|
1256
|
-
if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask });
|
|
1257
|
-
}
|
|
1258
|
-
if (pick) {
|
|
1259
|
-
out.title = String(pick.title).slice(0, 120);
|
|
1260
|
-
out.title_source = pick.source;
|
|
1261
|
-
} else if (ask) {
|
|
1262
|
-
out.title = ask;
|
|
1457
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
1458
|
+
if (url) out.url = url;
|
|
1459
|
+
for (const j of H) {
|
|
1460
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
1461
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
1462
|
+
const text = (m ? m[1] : j.content).trim();
|
|
1463
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
1464
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
1465
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1263
1466
|
out.title_source = "first-ask";
|
|
1467
|
+
break;
|
|
1264
1468
|
}
|
|
1265
|
-
const
|
|
1266
|
-
if (acct) out.account_id = acct;
|
|
1267
|
-
for (let i = T.length - 1; i >= 0; i--) {
|
|
1268
|
-
const m = T[i].message?.model;
|
|
1269
|
-
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1270
|
-
out.model = m;
|
|
1271
|
-
break;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1275
|
-
if (meta?.version) out.harness_version = String(meta.version);
|
|
1276
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1469
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1277
1470
|
if (times.length) {
|
|
1278
1471
|
out.started_at = Math.min(...times);
|
|
1279
1472
|
out.last_at = Math.max(...times);
|
|
1280
1473
|
}
|
|
1281
|
-
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1282
|
-
if (withCwd) {
|
|
1283
|
-
out.cwd = withCwd.cwd;
|
|
1284
|
-
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1285
|
-
}
|
|
1286
1474
|
return out;
|
|
1287
1475
|
}
|
|
1288
1476
|
|
|
1289
1477
|
// ../core/src/adapters/codex.ts
|
|
1290
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as
|
|
1291
|
-
import
|
|
1478
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1479
|
+
import path6 from "node:path";
|
|
1292
1480
|
|
|
1293
1481
|
// ../core/src/adapters/codex-config.ts
|
|
1294
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1482
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1295
1483
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1296
1484
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1297
1485
|
function scan(line, st) {
|
|
@@ -1381,10 +1569,11 @@ function dropLine(text, index, length) {
|
|
|
1381
1569
|
}
|
|
1382
1570
|
function insertTopLevel(text, line) {
|
|
1383
1571
|
const { head, tail } = split(text);
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1572
|
+
const body = head.replace(/\s*$/, "");
|
|
1573
|
+
const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
|
|
1574
|
+
const top = head.trim() ? head.replace(/\s*$/, eol) : "";
|
|
1575
|
+
if (!tail) return top + line + eol;
|
|
1576
|
+
return `${top + line}${eol}${eol}${tail}`;
|
|
1388
1577
|
}
|
|
1389
1578
|
function withNotify(text, line) {
|
|
1390
1579
|
const n = readNotify(text);
|
|
@@ -1433,25 +1622,25 @@ var CODEX_EVENTS = [
|
|
|
1433
1622
|
];
|
|
1434
1623
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1435
1624
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1436
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1437
|
-
var hooksFile2 = (env) =>
|
|
1438
|
-
var configFile = (env) =>
|
|
1439
|
-
var sessionsDir = (env) =>
|
|
1625
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1626
|
+
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1627
|
+
var configFile = (env) => path6.join(codexHome(env), "config.toml");
|
|
1628
|
+
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
1440
1629
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1441
1630
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1442
1631
|
function installNotify(cmd, env) {
|
|
1443
1632
|
const file = configFile(env);
|
|
1444
1633
|
let text = "";
|
|
1445
1634
|
try {
|
|
1446
|
-
text =
|
|
1635
|
+
text = readFileSync5(file, "utf8");
|
|
1447
1636
|
} catch {
|
|
1448
1637
|
}
|
|
1449
1638
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1450
1639
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1451
1640
|
const n = readNotify(text);
|
|
1452
1641
|
const write = (next) => {
|
|
1453
|
-
mkdirSync3(
|
|
1454
|
-
|
|
1642
|
+
mkdirSync3(path6.dirname(file), { recursive: true });
|
|
1643
|
+
writeFileSync4(file, next);
|
|
1455
1644
|
};
|
|
1456
1645
|
if (n && !n.other) {
|
|
1457
1646
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1489,19 +1678,19 @@ function uninstallNotify(env) {
|
|
|
1489
1678
|
const file = configFile(env);
|
|
1490
1679
|
let text;
|
|
1491
1680
|
try {
|
|
1492
|
-
text =
|
|
1681
|
+
text = readFileSync5(file, "utf8");
|
|
1493
1682
|
} catch {
|
|
1494
1683
|
return { file, changed: false };
|
|
1495
1684
|
}
|
|
1496
1685
|
const n = readNotify(text);
|
|
1497
1686
|
if (!n) return { file, changed: false };
|
|
1498
1687
|
if (!n.other) {
|
|
1499
|
-
|
|
1688
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1500
1689
|
return { file, changed: true };
|
|
1501
1690
|
}
|
|
1502
1691
|
const prev = previousNotify(n.argv);
|
|
1503
1692
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1504
|
-
|
|
1693
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1505
1694
|
return { file, changed: true };
|
|
1506
1695
|
}
|
|
1507
1696
|
return { file, changed: false };
|
|
@@ -1517,7 +1706,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1517
1706
|
return;
|
|
1518
1707
|
}
|
|
1519
1708
|
for (const n of names.sort().reverse()) {
|
|
1520
|
-
const p =
|
|
1709
|
+
const p = path6.join(dir, n);
|
|
1521
1710
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1522
1711
|
best = p;
|
|
1523
1712
|
return;
|
|
@@ -1561,7 +1750,7 @@ var codex = {
|
|
|
1561
1750
|
];
|
|
1562
1751
|
let text = "";
|
|
1563
1752
|
try {
|
|
1564
|
-
text =
|
|
1753
|
+
text = readFileSync5(configFile(env), "utf8");
|
|
1565
1754
|
} catch {
|
|
1566
1755
|
}
|
|
1567
1756
|
const n = readNotify(text);
|
|
@@ -1688,7 +1877,7 @@ var codex = {
|
|
|
1688
1877
|
readTranscript(file, fromLine) {
|
|
1689
1878
|
let text = "";
|
|
1690
1879
|
try {
|
|
1691
|
-
text =
|
|
1880
|
+
text = readFileSync5(file, "utf8");
|
|
1692
1881
|
} catch {
|
|
1693
1882
|
return { turns: [], line: fromLine };
|
|
1694
1883
|
}
|
|
@@ -1702,7 +1891,7 @@ var codex = {
|
|
|
1702
1891
|
backfill(sinceMs, env = process.env) {
|
|
1703
1892
|
const rows2 = [];
|
|
1704
1893
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1705
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
1894
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1706
1895
|
if (!m?.[1]) continue;
|
|
1707
1896
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1708
1897
|
const facts = codexFacts(f);
|
|
@@ -1711,12 +1900,40 @@ var codex = {
|
|
|
1711
1900
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1712
1901
|
}
|
|
1713
1902
|
return rows2;
|
|
1714
|
-
}
|
|
1903
|
+
},
|
|
1904
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1715
1905
|
};
|
|
1906
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1716
1907
|
function prune3(o) {
|
|
1717
1908
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1718
1909
|
return o;
|
|
1719
1910
|
}
|
|
1911
|
+
function codexLimits(file) {
|
|
1912
|
+
const { tail } = ends(file, 0, 256 * 1024);
|
|
1913
|
+
for (const r of parseLines(tail).reverse()) {
|
|
1914
|
+
const p = r.payload;
|
|
1915
|
+
if (r.type !== "event_msg" || p?.type !== "token_count") continue;
|
|
1916
|
+
if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
|
|
1917
|
+
const rl = p.rate_limits;
|
|
1918
|
+
if (rl.limit_id != null && rl.limit_id !== "codex") continue;
|
|
1919
|
+
const windows = [];
|
|
1920
|
+
for (const id of ["primary", "secondary"]) {
|
|
1921
|
+
const w = rl[id];
|
|
1922
|
+
if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
|
|
1923
|
+
windows.push(
|
|
1924
|
+
prune3({
|
|
1925
|
+
id,
|
|
1926
|
+
used_percent: w.used_percent,
|
|
1927
|
+
window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
|
|
1928
|
+
resets_at: epochIso2(w.resets_at)
|
|
1929
|
+
})
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
if (!windows.length) return null;
|
|
1933
|
+
return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
|
|
1934
|
+
}
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1720
1937
|
function codexFacts(file) {
|
|
1721
1938
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1722
1939
|
let H = parseLines(head);
|
|
@@ -1761,7 +1978,7 @@ function codexFacts(file) {
|
|
|
1761
1978
|
// ../core/src/adapters/devin.ts
|
|
1762
1979
|
import { createHash as createHash2 } from "node:crypto";
|
|
1763
1980
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1764
|
-
import
|
|
1981
|
+
import path7 from "node:path";
|
|
1765
1982
|
|
|
1766
1983
|
// ../core/src/readers/sqlite.ts
|
|
1767
1984
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -1945,11 +2162,11 @@ function foreignEvent(file) {
|
|
|
1945
2162
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1946
2163
|
}
|
|
1947
2164
|
var homeOf = (env) => env.HOME || homeDir();
|
|
1948
|
-
var appData = (env) => env.APPDATA ||
|
|
1949
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
1950
|
-
var configFile2 = (env) =>
|
|
1951
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
1952
|
-
var sessionsDb = (env) =>
|
|
2165
|
+
var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
|
|
2166
|
+
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");
|
|
2167
|
+
var configFile2 = (env) => path7.join(devinConfigDir(env), "config.json");
|
|
2168
|
+
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");
|
|
2169
|
+
var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
|
|
1953
2170
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1954
2171
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1955
2172
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2121,7 +2338,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2121
2338
|
function sessionFacts(row) {
|
|
2122
2339
|
const out = {};
|
|
2123
2340
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2124
|
-
if (typeof row.working_directory === "string" &&
|
|
2341
|
+
if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
|
|
2125
2342
|
out.cwd = row.working_directory;
|
|
2126
2343
|
const started = Number(row.created_at);
|
|
2127
2344
|
const last = Number(row.last_activity_at);
|
|
@@ -2166,8 +2383,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2166
2383
|
}
|
|
2167
2384
|
function devinVersion(env = process.env) {
|
|
2168
2385
|
try {
|
|
2169
|
-
const target = readlinkSync(
|
|
2170
|
-
const name =
|
|
2386
|
+
const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
|
|
2387
|
+
const name = path7.basename(target);
|
|
2171
2388
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2172
2389
|
} catch {
|
|
2173
2390
|
return void 0;
|
|
@@ -2181,8 +2398,8 @@ function devinAccount(env = process.env) {
|
|
|
2181
2398
|
|
|
2182
2399
|
// ../core/src/adapters/gemini-cli.ts
|
|
2183
2400
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2184
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2185
|
-
import
|
|
2401
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
2402
|
+
import path8 from "node:path";
|
|
2186
2403
|
var NAME5 = "gemini-cli";
|
|
2187
2404
|
var GEMINI_EVENTS = [
|
|
2188
2405
|
"SessionStart",
|
|
@@ -2195,26 +2412,31 @@ var GEMINI_EVENTS = [
|
|
|
2195
2412
|
"SessionEnd"
|
|
2196
2413
|
];
|
|
2197
2414
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2198
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2199
|
-
var settings = (env) =>
|
|
2415
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
|
|
2416
|
+
var settings = (env) => path8.join(geminiDir(env), "settings.json");
|
|
2200
2417
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2201
2418
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2419
|
+
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2420
|
+
const win = platform === "win32";
|
|
2421
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path8.dirname(process.execPath)], env }) : "gemini";
|
|
2422
|
+
if (!bin) throw new Error("no gemini on PATH");
|
|
2423
|
+
const c = probe(bin, ["--version"]);
|
|
2424
|
+
return execFileSync4(c.file, c.args, {
|
|
2425
|
+
encoding: "utf8",
|
|
2426
|
+
timeout: 4e3,
|
|
2427
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2428
|
+
env,
|
|
2429
|
+
shell: c.shell
|
|
2430
|
+
}).trim();
|
|
2431
|
+
}
|
|
2202
2432
|
function geminiHooksActive(settingsFile) {
|
|
2203
|
-
|
|
2204
|
-
try {
|
|
2205
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2206
|
-
} catch {
|
|
2207
|
-
}
|
|
2433
|
+
const tools = readJson(settingsFile).tools;
|
|
2208
2434
|
if (tools?.enableHooks === true) return { active: true };
|
|
2209
2435
|
if (tools?.enableHooks === false)
|
|
2210
2436
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2211
2437
|
let version = "";
|
|
2212
2438
|
try {
|
|
2213
|
-
version =
|
|
2214
|
-
encoding: "utf8",
|
|
2215
|
-
timeout: 4e3,
|
|
2216
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2217
|
-
}).trim();
|
|
2439
|
+
version = geminiVersion();
|
|
2218
2440
|
} catch {
|
|
2219
2441
|
return {
|
|
2220
2442
|
active: true,
|
|
@@ -2232,7 +2454,7 @@ var geminiCli = {
|
|
|
2232
2454
|
name: NAME5,
|
|
2233
2455
|
events: GEMINI_EVENTS,
|
|
2234
2456
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2235
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
2457
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
2236
2458
|
configFiles: (env = process.env) => [settings(env)],
|
|
2237
2459
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2238
2460
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2330,7 +2552,7 @@ var geminiCli = {
|
|
|
2330
2552
|
readTranscript(file, fromLine) {
|
|
2331
2553
|
let text = "";
|
|
2332
2554
|
try {
|
|
2333
|
-
text =
|
|
2555
|
+
text = readFileSync6(file, "utf8");
|
|
2334
2556
|
} catch {
|
|
2335
2557
|
return { turns: [], line: fromLine };
|
|
2336
2558
|
}
|
|
@@ -2409,7 +2631,7 @@ function matches(expect, got, pathStr = "") {
|
|
|
2409
2631
|
async function runAdapterFixtures(files) {
|
|
2410
2632
|
const results = [];
|
|
2411
2633
|
for (const file of files) {
|
|
2412
|
-
const fx = JSON.parse(
|
|
2634
|
+
const fx = JSON.parse(readFileSync7(file, "utf8"));
|
|
2413
2635
|
const adapter = adapterByName(fx.harness);
|
|
2414
2636
|
if (!adapter) {
|
|
2415
2637
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
@@ -2445,8 +2667,8 @@ async function runAdapterFixtures(files) {
|
|
|
2445
2667
|
|
|
2446
2668
|
// src/cli.ts
|
|
2447
2669
|
var walk = (d) => readdirSync5(d).flatMap((n) => {
|
|
2448
|
-
const p =
|
|
2449
|
-
return
|
|
2670
|
+
const p = path9.join(d, n);
|
|
2671
|
+
return statSync5(p).isDirectory() ? walk(p) : p.endsWith(".json") ? [p] : [];
|
|
2450
2672
|
});
|
|
2451
2673
|
async function main(argv, io = process) {
|
|
2452
2674
|
const target = argv[0];
|