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