@agtd/agent 0.1.1 → 0.1.3
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/agent.js +1 -1
- package/dist/{chunk-24ORBJSI.js → chunk-RJLNNX7L.js} +489 -32
- package/dist/cli.js +1 -1
- package/package.json +7 -2
package/dist/agent.js
CHANGED
|
@@ -354,7 +354,26 @@ async function sendHeartbeat(config, payload) {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
// src/spawn.ts
|
|
357
|
+
import { existsSync as existsSync3 } from "fs";
|
|
358
|
+
import { execSync as execSync3 } from "child_process";
|
|
359
|
+
import { join as join2 } from "path";
|
|
360
|
+
import { homedir as homedir2 } from "os";
|
|
357
361
|
var TMUX_SESSION_PREFIX = "aidash";
|
|
362
|
+
function findCodexThreadId(cwd, afterTimeSec) {
|
|
363
|
+
const dbPath = join2(homedir2(), ".codex", "state_5.sqlite");
|
|
364
|
+
if (!existsSync3(dbPath)) return null;
|
|
365
|
+
try {
|
|
366
|
+
const safeDb = dbPath.replace(/'/g, "'\\''");
|
|
367
|
+
const safeCwd = cwd.replace(/'/g, "'\\''");
|
|
368
|
+
const output = execSync3(
|
|
369
|
+
`sqlite3 '${safeDb}' "SELECT id FROM threads WHERE cwd='${safeCwd}' AND archived=0 AND created_at >= ${afterTimeSec} ORDER BY created_at ASC LIMIT 1;"`,
|
|
370
|
+
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
371
|
+
).trim();
|
|
372
|
+
return output || null;
|
|
373
|
+
} catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
358
377
|
function delay(ms) {
|
|
359
378
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
360
379
|
}
|
|
@@ -363,6 +382,7 @@ async function spawnAgentSession(config, params) {
|
|
|
363
382
|
const tmuxSessionName = `${TMUX_SESSION_PREFIX}-${sessionId}`;
|
|
364
383
|
const adapter = getAdapter(params.agentType);
|
|
365
384
|
const spawnCmd = adapter.buildSpawnCommand(params.task, params.projectPath);
|
|
385
|
+
const spawnTimeSec = Math.floor(Date.now() / 1e3);
|
|
366
386
|
createSession(tmuxSessionName, params.projectPath);
|
|
367
387
|
tmuxExec(
|
|
368
388
|
`tmux send-keys -t '${sanitizeShellArg(tmuxSessionName)}' '${sanitizeShellArg(spawnCmd)}' Enter`
|
|
@@ -373,6 +393,15 @@ async function spawnAgentSession(config, params) {
|
|
|
373
393
|
`tmux send-keys -t '${sanitizeShellArg(tmuxSessionName)}' '${sanitizeShellArg(params.task)}' Enter`
|
|
374
394
|
);
|
|
375
395
|
}
|
|
396
|
+
let codexThreadId = null;
|
|
397
|
+
if (params.agentType === "codex") {
|
|
398
|
+
for (let i = 0; i < 4; i++) {
|
|
399
|
+
codexThreadId = findCodexThreadId(params.projectPath, spawnTimeSec);
|
|
400
|
+
if (codexThreadId) break;
|
|
401
|
+
await delay(2e3);
|
|
402
|
+
}
|
|
403
|
+
console.log(`[spawn] codexThreadId=${codexThreadId ?? "null"}`);
|
|
404
|
+
}
|
|
376
405
|
await sendHeartbeat(config, {
|
|
377
406
|
deviceId: config.deviceId,
|
|
378
407
|
sessionId,
|
|
@@ -383,7 +412,8 @@ async function spawnAgentSession(config, params) {
|
|
|
383
412
|
status: "working",
|
|
384
413
|
task: params.task,
|
|
385
414
|
tmuxSession: tmuxSessionName,
|
|
386
|
-
tmuxWindow: params.projectName
|
|
415
|
+
tmuxWindow: params.projectName,
|
|
416
|
+
codexThreadId
|
|
387
417
|
});
|
|
388
418
|
console.log(`Spawned session ${sessionId} in tmux:${tmuxSessionName}`);
|
|
389
419
|
return sessionId;
|
|
@@ -436,6 +466,352 @@ function stopTerminalBridge(sessionId) {
|
|
|
436
466
|
}
|
|
437
467
|
}
|
|
438
468
|
|
|
469
|
+
// src/transcriptAdapters/index.ts
|
|
470
|
+
var adapters2 = /* @__PURE__ */ new Map();
|
|
471
|
+
function registerAdapter(agentType, adapter) {
|
|
472
|
+
adapters2.set(agentType, adapter);
|
|
473
|
+
}
|
|
474
|
+
function getAdapter2(agentType) {
|
|
475
|
+
return adapters2.get(agentType);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/transcriptAdapters/claude-code.ts
|
|
479
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
480
|
+
import { join as join3 } from "path";
|
|
481
|
+
import { homedir as homedir3 } from "os";
|
|
482
|
+
function truncate(s, max) {
|
|
483
|
+
if (!s) return "";
|
|
484
|
+
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
485
|
+
}
|
|
486
|
+
function summarizeToolInput(toolName, input) {
|
|
487
|
+
if (!input) return {};
|
|
488
|
+
switch (toolName) {
|
|
489
|
+
case "Read":
|
|
490
|
+
return { file_path: input.file_path };
|
|
491
|
+
case "Write":
|
|
492
|
+
return { file_path: input.file_path, content: truncate(input.content, 100) };
|
|
493
|
+
case "Edit":
|
|
494
|
+
return { file_path: input.file_path, old_string: truncate(input.old_string, 80), new_string: truncate(input.new_string, 80) };
|
|
495
|
+
case "Bash":
|
|
496
|
+
return { command: truncate(input.command, 200) };
|
|
497
|
+
case "Grep":
|
|
498
|
+
return { pattern: input.pattern, path: input.path };
|
|
499
|
+
case "Glob":
|
|
500
|
+
return { pattern: input.pattern, path: input.path };
|
|
501
|
+
case "Agent":
|
|
502
|
+
return { description: input.description, subagent_type: input.subagent_type };
|
|
503
|
+
default:
|
|
504
|
+
return Object.fromEntries(
|
|
505
|
+
Object.entries(input).slice(0, 3).map(([k, v]) => [k, typeof v === "string" ? truncate(v, 100) : v])
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function parseContent(content) {
|
|
510
|
+
if (!content) return [];
|
|
511
|
+
if (typeof content === "string") return content.trim() ? [{ type: "text", text: content }] : [];
|
|
512
|
+
if (!Array.isArray(content)) return [];
|
|
513
|
+
const result = [];
|
|
514
|
+
for (const block of content) {
|
|
515
|
+
if (block.type === "text" && block.text?.trim()) {
|
|
516
|
+
result.push({ type: "text", text: block.text });
|
|
517
|
+
} else if (block.type === "thinking" && block.thinking?.trim()) {
|
|
518
|
+
result.push({ type: "thinking", text: block.thinking });
|
|
519
|
+
} else if (block.type === "tool_use") {
|
|
520
|
+
result.push({ type: "tool_use", toolName: block.name ?? "", toolInput: summarizeToolInput(block.name, block.input) });
|
|
521
|
+
} else if (block.type === "tool_result") {
|
|
522
|
+
const text = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.filter((c) => c.type === "text").map((c) => c.text).join("\n") : "";
|
|
523
|
+
if (text.trim()) result.push({ type: "tool_result", text: truncate(text, 500) });
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
function parseRecord(record) {
|
|
529
|
+
if (!record.type || !record.uuid) return null;
|
|
530
|
+
if (record.type === "user" && record.message) {
|
|
531
|
+
const content = parseContent(record.message.content);
|
|
532
|
+
if (!content.some((c) => c.type === "text" || c.type === "thinking")) return null;
|
|
533
|
+
const textOnly = content.filter((c) => c.type === "text");
|
|
534
|
+
const firstText = textOnly[0]?.text || "";
|
|
535
|
+
if (firstText.startsWith("This session is being continued") || firstText.startsWith("<system-reminder>") || firstText.startsWith("Conversation compacted") || firstText.startsWith("Base directory for this skill:") || firstText.startsWith("# ")) return null;
|
|
536
|
+
return { id: record.uuid, role: "user", timestamp: record.timestamp || "", content: textOnly };
|
|
537
|
+
}
|
|
538
|
+
if (record.type === "assistant" && record.message) {
|
|
539
|
+
const content = parseContent(record.message.content);
|
|
540
|
+
if (content.length === 0) return null;
|
|
541
|
+
return {
|
|
542
|
+
id: record.uuid,
|
|
543
|
+
role: "assistant",
|
|
544
|
+
timestamp: record.timestamp || "",
|
|
545
|
+
content,
|
|
546
|
+
model: record.message.model,
|
|
547
|
+
usage: record.message.usage ? { input_tokens: record.message.usage.input_tokens || 0, output_tokens: record.message.usage.output_tokens || 0 } : void 0
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
if (record.type === "system" && record.content && record.subtype !== "local_command") {
|
|
551
|
+
const text = typeof record.content === "string" ? record.content.replace(/<[^>]+>/g, "").trim() : "";
|
|
552
|
+
if (!text || text.length < 5) return null;
|
|
553
|
+
return { id: record.uuid, role: "system", timestamp: record.timestamp || "", content: [{ type: "text", text }] };
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
var claudeCodeAdapter2 = {
|
|
558
|
+
findFile(session, homeDir = homedir3()) {
|
|
559
|
+
const projectDirName = session.cwd.replace(/\/+$/, "").replace(/\//g, "-");
|
|
560
|
+
const projPath = join3(homeDir, ".claude", "projects", projectDirName);
|
|
561
|
+
if (!existsSync4(projPath)) return null;
|
|
562
|
+
try {
|
|
563
|
+
const files = readdirSync2(projPath).filter((f) => f.endsWith(".jsonl"));
|
|
564
|
+
let latest = null;
|
|
565
|
+
for (const file of files) {
|
|
566
|
+
const filePath = join3(projPath, file);
|
|
567
|
+
try {
|
|
568
|
+
const { mtimeMs } = statSync2(filePath);
|
|
569
|
+
if (!latest || mtimeMs > latest.mtime) latest = { path: filePath, mtime: mtimeMs };
|
|
570
|
+
} catch {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return latest?.path ?? null;
|
|
575
|
+
} catch {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
parse(filePath, cursor, limit) {
|
|
580
|
+
try {
|
|
581
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
582
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
583
|
+
const messages = [];
|
|
584
|
+
for (const line of lines) {
|
|
585
|
+
try {
|
|
586
|
+
const record = JSON.parse(line);
|
|
587
|
+
const msg = parseRecord(record);
|
|
588
|
+
if (msg) messages.push(msg);
|
|
589
|
+
} catch {
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const total = messages.length;
|
|
593
|
+
const page = messages.slice(cursor, cursor + limit);
|
|
594
|
+
const nextCursor = cursor + page.length;
|
|
595
|
+
return { messages: page, total, hasMore: nextCursor < total, nextCursor };
|
|
596
|
+
} catch {
|
|
597
|
+
return { messages: [], total: 0, hasMore: false, nextCursor: 0 };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
registerAdapter("claude-code", claudeCodeAdapter2);
|
|
602
|
+
|
|
603
|
+
// src/transcriptAdapters/codex.ts
|
|
604
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
605
|
+
import { join as join4 } from "path";
|
|
606
|
+
import { homedir as homedir4 } from "os";
|
|
607
|
+
import { execSync as execSync4 } from "child_process";
|
|
608
|
+
function truncate2(s, max) {
|
|
609
|
+
if (!s) return "";
|
|
610
|
+
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
611
|
+
}
|
|
612
|
+
function extractCodexText(content) {
|
|
613
|
+
if (!content) return "";
|
|
614
|
+
if (typeof content === "string") return content;
|
|
615
|
+
if (!Array.isArray(content)) return "";
|
|
616
|
+
return content.map((c) => c.text || "").filter((t) => t.trim()).join("\n");
|
|
617
|
+
}
|
|
618
|
+
function summarizeCodexToolInput(name, args) {
|
|
619
|
+
if (name === "exec_command" || name === "shell")
|
|
620
|
+
return { cmd: truncate2(args.cmd, 200) };
|
|
621
|
+
if (name === "read_file" || name === "write_file")
|
|
622
|
+
return {
|
|
623
|
+
path: args.path || args.file_path,
|
|
624
|
+
content: args.content ? truncate2(args.content, 100) : void 0
|
|
625
|
+
};
|
|
626
|
+
return Object.fromEntries(
|
|
627
|
+
Object.entries(args).slice(0, 3).map(([k, v]) => [k, typeof v === "string" ? truncate2(v, 150) : v])
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
function parseRecord2(record, counter) {
|
|
631
|
+
if (!record.type || !record.payload) return null;
|
|
632
|
+
const p = record.payload;
|
|
633
|
+
const ts = record.timestamp || "";
|
|
634
|
+
if (record.type !== "response_item") return null;
|
|
635
|
+
if (p.type === "message" && p.role === "user") {
|
|
636
|
+
const texts = Array.isArray(p.content) ? p.content.map((c) => typeof c === "string" ? c : c.text || "").filter(
|
|
637
|
+
(t) => t.trim() && !t.startsWith("#") && !t.startsWith("<permissions")
|
|
638
|
+
) : (() => {
|
|
639
|
+
const t = extractCodexText(p.content);
|
|
640
|
+
return t && !t.startsWith("#") && !t.startsWith("<permissions") ? [t] : [];
|
|
641
|
+
})();
|
|
642
|
+
if (texts.length === 0) return null;
|
|
643
|
+
if (texts.length === 1)
|
|
644
|
+
return {
|
|
645
|
+
id: `codex-${++counter.value}`,
|
|
646
|
+
role: "user",
|
|
647
|
+
timestamp: ts,
|
|
648
|
+
content: [{ type: "text", text: texts[0] }]
|
|
649
|
+
};
|
|
650
|
+
return texts.map((text) => ({
|
|
651
|
+
id: `codex-${++counter.value}`,
|
|
652
|
+
role: "user",
|
|
653
|
+
timestamp: ts,
|
|
654
|
+
content: [{ type: "text", text }]
|
|
655
|
+
}));
|
|
656
|
+
}
|
|
657
|
+
if (p.type === "message" && p.role === "assistant") {
|
|
658
|
+
const text = extractCodexText(p.content);
|
|
659
|
+
if (!text) return null;
|
|
660
|
+
return {
|
|
661
|
+
id: `codex-${++counter.value}`,
|
|
662
|
+
role: "assistant",
|
|
663
|
+
timestamp: ts,
|
|
664
|
+
content: [{ type: "text", text }],
|
|
665
|
+
model: "codex"
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (p.type === "reasoning") {
|
|
669
|
+
const summaryText = Array.isArray(p.summary) && p.summary.length > 0 ? p.summary.map((s) => s.text || "").join("\n") : null;
|
|
670
|
+
if (!summaryText) return null;
|
|
671
|
+
return {
|
|
672
|
+
id: `codex-${++counter.value}`,
|
|
673
|
+
role: "assistant",
|
|
674
|
+
timestamp: ts,
|
|
675
|
+
content: [{ type: "thinking", text: summaryText }]
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (p.type === "function_call") {
|
|
679
|
+
let args = {};
|
|
680
|
+
try {
|
|
681
|
+
args = typeof p.arguments === "string" ? JSON.parse(p.arguments) : p.arguments || {};
|
|
682
|
+
} catch {
|
|
683
|
+
args = { raw: truncate2(p.arguments, 200) };
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
id: `codex-${++counter.value}`,
|
|
687
|
+
role: "assistant",
|
|
688
|
+
timestamp: ts,
|
|
689
|
+
content: [
|
|
690
|
+
{
|
|
691
|
+
type: "tool_use",
|
|
692
|
+
toolName: p.name || "unknown",
|
|
693
|
+
toolInput: summarizeCodexToolInput(p.name, args)
|
|
694
|
+
}
|
|
695
|
+
]
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
if (p.type === "function_call_output") {
|
|
699
|
+
const output = typeof p.output === "string" ? p.output : "";
|
|
700
|
+
if (!output.trim()) return null;
|
|
701
|
+
return {
|
|
702
|
+
id: `codex-${++counter.value}`,
|
|
703
|
+
role: "assistant",
|
|
704
|
+
timestamp: ts,
|
|
705
|
+
content: [{ type: "tool_result", text: truncate2(output, 500) }]
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
function safeReaddir(dir) {
|
|
711
|
+
try {
|
|
712
|
+
return readdirSync3(dir);
|
|
713
|
+
} catch {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function findLatestJsonl(dir) {
|
|
718
|
+
let latest = null;
|
|
719
|
+
try {
|
|
720
|
+
for (const year of readdirSync3(dir)) {
|
|
721
|
+
for (const month of safeReaddir(join4(dir, year))) {
|
|
722
|
+
for (const day of safeReaddir(join4(dir, year, month))) {
|
|
723
|
+
for (const file of safeReaddir(join4(dir, year, month, day))) {
|
|
724
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
725
|
+
const fp = join4(dir, year, month, day, file);
|
|
726
|
+
try {
|
|
727
|
+
const { mtimeMs } = statSync3(fp);
|
|
728
|
+
if (!latest || mtimeMs > latest.mtime)
|
|
729
|
+
latest = { path: fp, mtime: mtimeMs };
|
|
730
|
+
} catch {
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
return latest?.path ?? null;
|
|
740
|
+
}
|
|
741
|
+
var codexAdapter2 = {
|
|
742
|
+
findFile(session, homeDir = homedir4()) {
|
|
743
|
+
const s = session;
|
|
744
|
+
const dbPath = join4(homeDir, ".codex", "state_5.sqlite");
|
|
745
|
+
if (existsSync5(dbPath)) {
|
|
746
|
+
try {
|
|
747
|
+
const safeDb = dbPath.replace(/'/g, "'\\''");
|
|
748
|
+
if (s.codexThreadId) {
|
|
749
|
+
const safeId = s.codexThreadId.replace(/'/g, "'\\''");
|
|
750
|
+
const idOutput = execSync4(
|
|
751
|
+
`sqlite3 '${safeDb}' "SELECT rollout_path FROM threads WHERE id='${safeId}' LIMIT 1;"`,
|
|
752
|
+
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
753
|
+
).trim();
|
|
754
|
+
if (idOutput && existsSync5(idOutput)) return idOutput;
|
|
755
|
+
}
|
|
756
|
+
if (session.cwd && s.tmuxSession) {
|
|
757
|
+
try {
|
|
758
|
+
const safeTmux = s.tmuxSession.replace(/'/g, "'\\''");
|
|
759
|
+
const tmuxCreated = execSync4(
|
|
760
|
+
`tmux display-message -t '${safeTmux}' -p '#{session_created}'`,
|
|
761
|
+
{ encoding: "utf-8", timeout: 2e3, stdio: "pipe" }
|
|
762
|
+
).trim();
|
|
763
|
+
if (tmuxCreated && /^\d+$/.test(tmuxCreated)) {
|
|
764
|
+
const safeCwd = session.cwd.replace(/"/g, '\\"');
|
|
765
|
+
const tsOutput = execSync4(
|
|
766
|
+
`sqlite3 '${safeDb}' "SELECT rollout_path FROM threads WHERE cwd='${safeCwd}' AND archived=0 AND created_at >= ${tmuxCreated} ORDER BY created_at ASC LIMIT 1;"`,
|
|
767
|
+
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
768
|
+
).trim();
|
|
769
|
+
if (tsOutput && existsSync5(tsOutput)) return tsOutput;
|
|
770
|
+
}
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (session.cwd) {
|
|
775
|
+
const safeCwd = session.cwd.replace(/"/g, '\\"');
|
|
776
|
+
const cwdOutput = execSync4(
|
|
777
|
+
`sqlite3 '${safeDb}' "SELECT rollout_path FROM threads WHERE cwd='${safeCwd}' AND archived=0 ORDER BY updated_at DESC LIMIT 1;"`,
|
|
778
|
+
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
779
|
+
).trim();
|
|
780
|
+
if (cwdOutput && existsSync5(cwdOutput)) return cwdOutput;
|
|
781
|
+
}
|
|
782
|
+
} catch {
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const sessionsDir = join4(homeDir, ".codex", "sessions");
|
|
786
|
+
if (!existsSync5(sessionsDir)) return null;
|
|
787
|
+
return findLatestJsonl(sessionsDir);
|
|
788
|
+
},
|
|
789
|
+
parse(filePath, cursor, limit) {
|
|
790
|
+
try {
|
|
791
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
792
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
793
|
+
const counter = { value: 0 };
|
|
794
|
+
const messages = [];
|
|
795
|
+
for (const line of lines) {
|
|
796
|
+
try {
|
|
797
|
+
const record = JSON.parse(line);
|
|
798
|
+
const msg = parseRecord2(record, counter);
|
|
799
|
+
if (Array.isArray(msg)) messages.push(...msg);
|
|
800
|
+
else if (msg) messages.push(msg);
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const total = messages.length;
|
|
805
|
+
const page = messages.slice(cursor, cursor + limit);
|
|
806
|
+
const nextCursor = cursor + page.length;
|
|
807
|
+
return { messages: page, total, hasMore: nextCursor < total, nextCursor };
|
|
808
|
+
} catch {
|
|
809
|
+
return { messages: [], total: 0, hasMore: false, nextCursor: 0 };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
registerAdapter("codex", codexAdapter2);
|
|
814
|
+
|
|
439
815
|
// src/wsClient.ts
|
|
440
816
|
var reconnectAttempts = 0;
|
|
441
817
|
function connectToBackend(config) {
|
|
@@ -498,6 +874,87 @@ function connectToBackend(config) {
|
|
|
498
874
|
}
|
|
499
875
|
break;
|
|
500
876
|
}
|
|
877
|
+
case "user-message": {
|
|
878
|
+
const tmuxAvail = isTmuxAvailable();
|
|
879
|
+
const sessionExists = tmuxSessionExists(msg.payload.tmuxSession);
|
|
880
|
+
let paneInfo = "n/a";
|
|
881
|
+
try {
|
|
882
|
+
paneInfo = tmuxExec(
|
|
883
|
+
`tmux list-panes -t '${sanitizeShellArg(msg.payload.tmuxSession)}' -F '#I:#P #{pane_current_command} #{pane_active}'`
|
|
884
|
+
).trim();
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
console.log(
|
|
888
|
+
`[user-message] session=${msg.payload.tmuxSession} exists=${sessionExists} available=${tmuxAvail} panes="${paneInfo}" msg="${msg.payload.message?.slice(0, 80)}"`
|
|
889
|
+
);
|
|
890
|
+
if (tmuxAvail && sessionExists) {
|
|
891
|
+
try {
|
|
892
|
+
let paneBefore = "";
|
|
893
|
+
try {
|
|
894
|
+
paneBefore = tmuxExec(
|
|
895
|
+
`tmux capture-pane -t '${sanitizeShellArg(msg.payload.tmuxSession)}' -p -S -10`
|
|
896
|
+
).split("\n").slice(-5).join(" | ");
|
|
897
|
+
} catch {
|
|
898
|
+
}
|
|
899
|
+
console.log(`[user-message] pane-before: ${paneBefore}`);
|
|
900
|
+
sendKeys(msg.payload.tmuxSession, msg.payload.message);
|
|
901
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
902
|
+
sendKeys(msg.payload.tmuxSession, "\r");
|
|
903
|
+
console.log(`[user-message] sent OK`);
|
|
904
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
905
|
+
let paneAfter = "";
|
|
906
|
+
try {
|
|
907
|
+
paneAfter = tmuxExec(
|
|
908
|
+
`tmux capture-pane -t '${sanitizeShellArg(msg.payload.tmuxSession)}' -p -S -10`
|
|
909
|
+
).split("\n").slice(-5).join(" | ");
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
console.log(`[user-message] pane-after: ${paneAfter}`);
|
|
913
|
+
} catch (e) {
|
|
914
|
+
console.error(`[user-message] sendKeys failed:`, e);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
case "get-transcript": {
|
|
920
|
+
const {
|
|
921
|
+
requestId,
|
|
922
|
+
sessionId,
|
|
923
|
+
agentType,
|
|
924
|
+
cwd,
|
|
925
|
+
cursor = 0,
|
|
926
|
+
limit = 50,
|
|
927
|
+
codexThreadId = null,
|
|
928
|
+
tmuxSession = null
|
|
929
|
+
} = msg.payload;
|
|
930
|
+
const adapter = getAdapter2(agentType);
|
|
931
|
+
let result = {
|
|
932
|
+
messages: [],
|
|
933
|
+
total: 0,
|
|
934
|
+
hasMore: false,
|
|
935
|
+
nextCursor: 0
|
|
936
|
+
};
|
|
937
|
+
if (adapter) {
|
|
938
|
+
const fakeSession = {
|
|
939
|
+
id: sessionId,
|
|
940
|
+
cwd,
|
|
941
|
+
agentType,
|
|
942
|
+
codexThreadId,
|
|
943
|
+
tmuxSession
|
|
944
|
+
};
|
|
945
|
+
const filePath = adapter.findFile(fakeSession);
|
|
946
|
+
if (filePath) {
|
|
947
|
+
result = adapter.parse(filePath, cursor, limit);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
ws.send(
|
|
951
|
+
JSON.stringify({
|
|
952
|
+
type: "transcript-response",
|
|
953
|
+
payload: { requestId, ...result }
|
|
954
|
+
})
|
|
955
|
+
);
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
501
958
|
}
|
|
502
959
|
} catch (e) {
|
|
503
960
|
console.error("Error handling WS message:", e);
|
|
@@ -514,29 +971,29 @@ function connectToBackend(config) {
|
|
|
514
971
|
}
|
|
515
972
|
|
|
516
973
|
// src/sessionScanner.ts
|
|
517
|
-
import { execSync as
|
|
974
|
+
import { execSync as execSync6 } from "child_process";
|
|
518
975
|
import {
|
|
519
|
-
statSync as
|
|
520
|
-
readdirSync as
|
|
521
|
-
existsSync as
|
|
976
|
+
statSync as statSync4,
|
|
977
|
+
readdirSync as readdirSync4,
|
|
978
|
+
existsSync as existsSync7,
|
|
522
979
|
openSync,
|
|
523
980
|
readSync,
|
|
524
981
|
closeSync
|
|
525
982
|
} from "fs";
|
|
526
|
-
import { join as
|
|
527
|
-
import { homedir as
|
|
983
|
+
import { join as join5, basename as basename2 } from "path";
|
|
984
|
+
import { homedir as homedir5 } from "os";
|
|
528
985
|
|
|
529
986
|
// src/enrichers/git.ts
|
|
530
|
-
import { execSync as
|
|
531
|
-
import { existsSync as
|
|
987
|
+
import { execSync as execSync5 } from "child_process";
|
|
988
|
+
import { existsSync as existsSync6 } from "fs";
|
|
532
989
|
function getGitInfo(cwd) {
|
|
533
|
-
if (!
|
|
990
|
+
if (!existsSync6(`${cwd}/.git`)) return null;
|
|
534
991
|
try {
|
|
535
|
-
const branch =
|
|
536
|
-
const repoName =
|
|
992
|
+
const branch = execSync5("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
|
|
993
|
+
const repoName = execSync5("git rev-parse --show-toplevel", { cwd, encoding: "utf-8", timeout: 3e3 }).trim().split("/").pop() || "";
|
|
537
994
|
let remoteUrl = "";
|
|
538
995
|
try {
|
|
539
|
-
remoteUrl =
|
|
996
|
+
remoteUrl = execSync5("git remote get-url origin", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
|
|
540
997
|
} catch {
|
|
541
998
|
}
|
|
542
999
|
return { repoName, branch, remoteUrl };
|
|
@@ -549,7 +1006,7 @@ function getGitInfo(cwd) {
|
|
|
549
1006
|
function buildTmuxPidMap() {
|
|
550
1007
|
const map = /* @__PURE__ */ new Map();
|
|
551
1008
|
try {
|
|
552
|
-
const output =
|
|
1009
|
+
const output = execSync6(
|
|
553
1010
|
"tmux list-panes -a -F '#{pane_pid} #{session_name}' 2>/dev/null",
|
|
554
1011
|
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
555
1012
|
).trim();
|
|
@@ -576,7 +1033,7 @@ function findTmuxSessionForPid(pid, tmuxPidMap) {
|
|
|
576
1033
|
if (session) return session;
|
|
577
1034
|
try {
|
|
578
1035
|
const ppid = parseInt(
|
|
579
|
-
|
|
1036
|
+
execSync6(`ps -p ${currentPid} -o ppid=`, {
|
|
580
1037
|
encoding: "utf-8",
|
|
581
1038
|
timeout: 2e3,
|
|
582
1039
|
stdio: "pipe"
|
|
@@ -596,7 +1053,7 @@ function pathToProjectDir(p) {
|
|
|
596
1053
|
}
|
|
597
1054
|
function getProcessCwd(pid) {
|
|
598
1055
|
try {
|
|
599
|
-
const output =
|
|
1056
|
+
const output = execSync6(
|
|
600
1057
|
`lsof -a -p ${pid} -d cwd -Fn 2>/dev/null | grep '^n'`,
|
|
601
1058
|
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
602
1059
|
).trim();
|
|
@@ -609,7 +1066,7 @@ function getProcessCwd(pid) {
|
|
|
609
1066
|
function findAgentProcesses() {
|
|
610
1067
|
const processes = [];
|
|
611
1068
|
try {
|
|
612
|
-
const psOutput =
|
|
1069
|
+
const psOutput = execSync6("ps aux", {
|
|
613
1070
|
encoding: "utf-8",
|
|
614
1071
|
timeout: 5e3,
|
|
615
1072
|
stdio: "pipe"
|
|
@@ -655,12 +1112,12 @@ function findAgentProcesses() {
|
|
|
655
1112
|
}
|
|
656
1113
|
function findLatestSessionFile(projPath) {
|
|
657
1114
|
try {
|
|
658
|
-
const files =
|
|
1115
|
+
const files = readdirSync4(projPath).filter((f) => f.endsWith(".jsonl"));
|
|
659
1116
|
let latest = null;
|
|
660
1117
|
for (const file of files) {
|
|
661
|
-
const filePath =
|
|
1118
|
+
const filePath = join5(projPath, file);
|
|
662
1119
|
try {
|
|
663
|
-
const mtime =
|
|
1120
|
+
const mtime = statSync4(filePath).mtimeMs;
|
|
664
1121
|
if (!latest || mtime > latest.mtimeMs) {
|
|
665
1122
|
latest = { sessionId: basename2(file, ".jsonl"), mtimeMs: mtime };
|
|
666
1123
|
}
|
|
@@ -675,8 +1132,8 @@ function findLatestSessionFile(projPath) {
|
|
|
675
1132
|
}
|
|
676
1133
|
function isAwaitingPermissionFromJsonl(projPath, sessionId) {
|
|
677
1134
|
try {
|
|
678
|
-
const filePath =
|
|
679
|
-
const size =
|
|
1135
|
+
const filePath = join5(projPath, `${sessionId}.jsonl`);
|
|
1136
|
+
const size = statSync4(filePath).size;
|
|
680
1137
|
if (size === 0) return false;
|
|
681
1138
|
const TAIL_BYTES = 4096;
|
|
682
1139
|
const start = Math.max(0, size - TAIL_BYTES);
|
|
@@ -715,7 +1172,7 @@ var AWAITING_INPUT_PATTERNS = [
|
|
|
715
1172
|
function isAwaitingInputFromTmux(tmuxSession) {
|
|
716
1173
|
if (!tmuxSession) return false;
|
|
717
1174
|
try {
|
|
718
|
-
const output =
|
|
1175
|
+
const output = execSync6(
|
|
719
1176
|
`tmux capture-pane -t '${tmuxSession.replace(/'/g, "'\\''")}' -p -S -20`,
|
|
720
1177
|
{
|
|
721
1178
|
encoding: "utf-8",
|
|
@@ -730,8 +1187,8 @@ function isAwaitingInputFromTmux(tmuxSession) {
|
|
|
730
1187
|
}
|
|
731
1188
|
function discoverClaudeSessions(processes, tmuxPidMap) {
|
|
732
1189
|
const sessions = [];
|
|
733
|
-
const claudeProjectsDir =
|
|
734
|
-
if (!
|
|
1190
|
+
const claudeProjectsDir = join5(homedir5(), ".claude", "projects");
|
|
1191
|
+
if (!existsSync7(claudeProjectsDir)) return sessions;
|
|
735
1192
|
const now = Date.now();
|
|
736
1193
|
const WORKING_THRESHOLD_MS = 5 * 1e3;
|
|
737
1194
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -741,14 +1198,14 @@ function discoverClaudeSessions(processes, tmuxPidMap) {
|
|
|
741
1198
|
const cwd = proc.cwd;
|
|
742
1199
|
if (!cwd) continue;
|
|
743
1200
|
const projDirName = pathToProjectDir(cwd);
|
|
744
|
-
const projPath =
|
|
745
|
-
if (!
|
|
1201
|
+
const projPath = join5(claudeProjectsDir, projDirName);
|
|
1202
|
+
if (!existsSync7(projPath)) continue;
|
|
746
1203
|
let sessionId = proc.sessionId;
|
|
747
1204
|
let mtimeMs = null;
|
|
748
1205
|
if (sessionId) {
|
|
749
|
-
const filePath =
|
|
1206
|
+
const filePath = join5(projPath, `${sessionId}.jsonl`);
|
|
750
1207
|
try {
|
|
751
|
-
mtimeMs =
|
|
1208
|
+
mtimeMs = statSync4(filePath).mtimeMs;
|
|
752
1209
|
} catch {
|
|
753
1210
|
sessionId = null;
|
|
754
1211
|
}
|
|
@@ -807,10 +1264,10 @@ function discoverClaudeSessions(processes, tmuxPidMap) {
|
|
|
807
1264
|
}
|
|
808
1265
|
function getCodexSessionsFromDb() {
|
|
809
1266
|
const map = /* @__PURE__ */ new Map();
|
|
810
|
-
const dbPath =
|
|
811
|
-
if (!
|
|
1267
|
+
const dbPath = join5(homedir5(), ".codex", "state_5.sqlite");
|
|
1268
|
+
if (!existsSync7(dbPath)) return map;
|
|
812
1269
|
try {
|
|
813
|
-
const output =
|
|
1270
|
+
const output = execSync6(
|
|
814
1271
|
`sqlite3 "${dbPath}" "SELECT id, cwd, title, model_provider, updated_at FROM threads ORDER BY updated_at DESC LIMIT 50;"`,
|
|
815
1272
|
{ encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
|
|
816
1273
|
).trim();
|
package/dist/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agtd/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agtd-agent": "./bin/agtd-agent.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "dist/agent.js",
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
10
14
|
"scripts": {
|
|
11
15
|
"dev": "tsx watch src/agent.ts",
|
|
12
16
|
"build": "tsc",
|
|
13
17
|
"build:publish": "tsup",
|
|
18
|
+
"prepublishOnly": "tsup",
|
|
14
19
|
"start": "node dist/agent.js",
|
|
15
20
|
"test": "vitest run",
|
|
16
21
|
"test:watch": "vitest"
|