ax-agents 0.0.1-alpha.7 → 0.0.1-alpha.9
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/ax.js +218 -48
- package/package.json +1 -1
package/ax.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
readSync,
|
|
28
28
|
closeSync,
|
|
29
29
|
} from "node:fs";
|
|
30
|
-
import { randomUUID } from "node:crypto";
|
|
30
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
import path from "node:path";
|
|
33
33
|
import os from "node:os";
|
|
@@ -305,6 +305,18 @@ const TRUNCATE_THINKING_LEN = 300;
|
|
|
305
305
|
const ARCHANGEL_GIT_CONTEXT_HOURS = 4;
|
|
306
306
|
const ARCHANGEL_GIT_CONTEXT_MAX_LINES = 200;
|
|
307
307
|
const ARCHANGEL_PARENT_CONTEXT_ENTRIES = 10;
|
|
308
|
+
const ARCHANGEL_PREAMBLE = `## Guidelines
|
|
309
|
+
|
|
310
|
+
- Investigate before speaking. If uncertain, read more code and trace the logic until you're confident.
|
|
311
|
+
- Explain WHY something is an issue, not just that it is.
|
|
312
|
+
- Focus on your area of expertise.
|
|
313
|
+
- Calibrate to the task or plan. Don't suggest refactors during a bug fix.
|
|
314
|
+
- Be clear. Brief is fine, but never sacrifice clarity.
|
|
315
|
+
- For critical issues, request for them to be added to the todo list.
|
|
316
|
+
- Don't repeat observations you've already made unless you have more to say or better clarity.
|
|
317
|
+
- Make judgment calls - don't ask questions.
|
|
318
|
+
|
|
319
|
+
"No issues found." is a valid response when there's nothing significant to report.`;
|
|
308
320
|
|
|
309
321
|
/**
|
|
310
322
|
* @param {string} session
|
|
@@ -544,6 +556,16 @@ function generateSessionName(tool) {
|
|
|
544
556
|
return `${tool}-partner-${randomUUID()}`;
|
|
545
557
|
}
|
|
546
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Quick hash for change detection (not cryptographic).
|
|
561
|
+
* @param {string | null | undefined} str
|
|
562
|
+
* @returns {string | null}
|
|
563
|
+
*/
|
|
564
|
+
function quickHash(str) {
|
|
565
|
+
if (!str) return null;
|
|
566
|
+
return createHash("md5").update(str).digest("hex").slice(0, 8);
|
|
567
|
+
}
|
|
568
|
+
|
|
547
569
|
/**
|
|
548
570
|
* @param {string} cwd
|
|
549
571
|
* @returns {string}
|
|
@@ -671,6 +693,93 @@ function findCodexLogPath(sessionName) {
|
|
|
671
693
|
}
|
|
672
694
|
}
|
|
673
695
|
|
|
696
|
+
/**
|
|
697
|
+
* @typedef {Object} SessionMeta
|
|
698
|
+
* @property {string | null} slug - Plan identifier (if plan is active)
|
|
699
|
+
* @property {Array<{content: string, status: string, id?: string}> | null} todos - Current todos
|
|
700
|
+
* @property {string | null} permissionMode - "default", "acceptEdits", "plan"
|
|
701
|
+
* @property {string | null} gitBranch - Current git branch
|
|
702
|
+
* @property {string | null} cwd - Working directory
|
|
703
|
+
*/
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Get metadata from a Claude session's JSONL file.
|
|
707
|
+
* Returns null for Codex sessions (different format, no equivalent metadata).
|
|
708
|
+
* @param {string} sessionName - The tmux session name
|
|
709
|
+
* @returns {SessionMeta | null}
|
|
710
|
+
*/
|
|
711
|
+
function getSessionMeta(sessionName) {
|
|
712
|
+
const parsed = parseSessionName(sessionName);
|
|
713
|
+
if (!parsed) return null;
|
|
714
|
+
|
|
715
|
+
// Only Claude sessions have this metadata
|
|
716
|
+
if (parsed.tool !== "claude") return null;
|
|
717
|
+
if (!parsed.uuid) return null;
|
|
718
|
+
|
|
719
|
+
const logPath = findClaudeLogPath(parsed.uuid, sessionName);
|
|
720
|
+
if (!logPath || !existsSync(logPath)) return null;
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
const content = readFileSync(logPath, "utf-8");
|
|
724
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
725
|
+
|
|
726
|
+
// Read from end to find most recent entry with metadata
|
|
727
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
728
|
+
try {
|
|
729
|
+
const entry = JSON.parse(lines[i]);
|
|
730
|
+
// User entries typically have the metadata fields
|
|
731
|
+
if (entry.type === "user" || entry.slug || entry.gitBranch) {
|
|
732
|
+
return {
|
|
733
|
+
slug: entry.slug || null,
|
|
734
|
+
todos: entry.todos || null,
|
|
735
|
+
permissionMode: entry.permissionMode || null,
|
|
736
|
+
gitBranch: entry.gitBranch || null,
|
|
737
|
+
cwd: entry.cwd || null,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
// Skip malformed lines
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
} catch (err) {
|
|
746
|
+
debugError("getSessionMeta", err);
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Read a plan file by its slug.
|
|
753
|
+
* @param {string} slug - The plan slug (e.g., "curious-roaming-pascal")
|
|
754
|
+
* @returns {string | null} The plan content or null if not found
|
|
755
|
+
*/
|
|
756
|
+
function readPlanFile(slug) {
|
|
757
|
+
const planPath = path.join(CLAUDE_CONFIG_DIR, "plans", `${slug}.md`);
|
|
758
|
+
try {
|
|
759
|
+
if (existsSync(planPath)) {
|
|
760
|
+
return readFileSync(planPath, "utf-8");
|
|
761
|
+
}
|
|
762
|
+
} catch (err) {
|
|
763
|
+
debugError("readPlanFile", err);
|
|
764
|
+
}
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Format todos for display in a prompt.
|
|
770
|
+
* @param {Array<{content: string, status: string, id?: string}>} todos
|
|
771
|
+
* @returns {string}
|
|
772
|
+
*/
|
|
773
|
+
function formatTodos(todos) {
|
|
774
|
+
if (!todos || todos.length === 0) return "";
|
|
775
|
+
return todos
|
|
776
|
+
.map((t) => {
|
|
777
|
+
const status = t.status === "completed" ? "[x]" : t.status === "in_progress" ? "[>]" : "[ ]";
|
|
778
|
+
return `${status} ${t.content || "(no content)"}`;
|
|
779
|
+
})
|
|
780
|
+
.join("\n");
|
|
781
|
+
}
|
|
782
|
+
|
|
674
783
|
/**
|
|
675
784
|
* Extract assistant text responses from a JSONL log file.
|
|
676
785
|
* This provides clean text without screen-scraped artifacts.
|
|
@@ -1725,8 +1834,8 @@ function detectState(screen, config) {
|
|
|
1725
1834
|
// Larger range for confirmation detection (catches dialogs that scrolled slightly)
|
|
1726
1835
|
const recentLines = lines.slice(-15).join("\n");
|
|
1727
1836
|
|
|
1728
|
-
// Rate limited - check full screen
|
|
1729
|
-
if (config.rateLimitPattern && config.rateLimitPattern.test(
|
|
1837
|
+
// Rate limited - check recent lines (not full screen to avoid matching historical output)
|
|
1838
|
+
if (config.rateLimitPattern && config.rateLimitPattern.test(recentLines)) {
|
|
1730
1839
|
return State.RATE_LIMITED;
|
|
1731
1840
|
}
|
|
1732
1841
|
|
|
@@ -1743,7 +1852,22 @@ function detectState(screen, config) {
|
|
|
1743
1852
|
}
|
|
1744
1853
|
}
|
|
1745
1854
|
|
|
1746
|
-
//
|
|
1855
|
+
// Ready - check BEFORE thinking to avoid false positives from timing messages like "✻ Worked for 45s"
|
|
1856
|
+
// If the prompt symbol is visible, the agent is ready regardless of spinner characters in timing messages
|
|
1857
|
+
if (lastLines.includes(config.promptSymbol)) {
|
|
1858
|
+
// Check if any line has the prompt followed by pasted content indicator
|
|
1859
|
+
// "[Pasted text" indicates user has pasted content and Claude is still processing
|
|
1860
|
+
const linesArray = lastLines.split("\n");
|
|
1861
|
+
const promptWithPaste = linesArray.some(
|
|
1862
|
+
(l) => l.includes(config.promptSymbol) && l.includes("[Pasted text"),
|
|
1863
|
+
);
|
|
1864
|
+
if (!promptWithPaste) {
|
|
1865
|
+
return State.READY;
|
|
1866
|
+
}
|
|
1867
|
+
// If prompt has pasted content, Claude is still processing - not ready yet
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// Thinking - spinners (check last lines only)
|
|
1747
1871
|
const spinners = config.spinners || [];
|
|
1748
1872
|
if (spinners.some((s) => lastLines.includes(s))) {
|
|
1749
1873
|
return State.THINKING;
|
|
@@ -1768,20 +1892,6 @@ function detectState(screen, config) {
|
|
|
1768
1892
|
}
|
|
1769
1893
|
}
|
|
1770
1894
|
|
|
1771
|
-
// Ready - only if prompt symbol is visible AND not followed by pasted content
|
|
1772
|
-
// "[Pasted text" indicates user has pasted content and Claude is still processing
|
|
1773
|
-
if (lastLines.includes(config.promptSymbol)) {
|
|
1774
|
-
// Check if any line has the prompt followed by pasted content indicator
|
|
1775
|
-
const linesArray = lastLines.split("\n");
|
|
1776
|
-
const promptWithPaste = linesArray.some(
|
|
1777
|
-
(l) => l.includes(config.promptSymbol) && l.includes("[Pasted text"),
|
|
1778
|
-
);
|
|
1779
|
-
if (!promptWithPaste) {
|
|
1780
|
-
return State.READY;
|
|
1781
|
-
}
|
|
1782
|
-
// If prompt has pasted content, Claude is still processing - not ready yet
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
1895
|
return State.STARTING;
|
|
1786
1896
|
}
|
|
1787
1897
|
|
|
@@ -2508,19 +2618,22 @@ function cmdAgents() {
|
|
|
2508
2618
|
const agent = parsed.tool === "claude" ? ClaudeAgent : CodexAgent;
|
|
2509
2619
|
const screen = tmuxCapture(session);
|
|
2510
2620
|
const state = agent.getState(screen);
|
|
2511
|
-
const logPath = agent.findLogPath(session);
|
|
2512
2621
|
const type = parsed.archangelName ? "archangel" : "-";
|
|
2513
2622
|
const isDefault =
|
|
2514
2623
|
(parsed.tool === "claude" && session === claudeDefault) ||
|
|
2515
2624
|
(parsed.tool === "codex" && session === codexDefault);
|
|
2516
2625
|
|
|
2626
|
+
// Get session metadata (Claude only)
|
|
2627
|
+
const meta = getSessionMeta(session);
|
|
2628
|
+
|
|
2517
2629
|
return {
|
|
2518
2630
|
session,
|
|
2519
2631
|
tool: parsed.tool,
|
|
2520
2632
|
state: state || "unknown",
|
|
2521
2633
|
target: isDefault ? "*" : "",
|
|
2522
2634
|
type,
|
|
2523
|
-
|
|
2635
|
+
plan: meta?.slug || "-",
|
|
2636
|
+
branch: meta?.gitBranch || "-",
|
|
2524
2637
|
};
|
|
2525
2638
|
});
|
|
2526
2639
|
|
|
@@ -2530,13 +2643,14 @@ function cmdAgents() {
|
|
|
2530
2643
|
const maxState = Math.max(5, ...agents.map((a) => a.state.length));
|
|
2531
2644
|
const maxTarget = Math.max(6, ...agents.map((a) => a.target.length));
|
|
2532
2645
|
const maxType = Math.max(4, ...agents.map((a) => a.type.length));
|
|
2646
|
+
const maxPlan = Math.max(4, ...agents.map((a) => a.plan.length));
|
|
2533
2647
|
|
|
2534
2648
|
console.log(
|
|
2535
|
-
`${"SESSION".padEnd(maxSession)} ${"TOOL".padEnd(maxTool)} ${"STATE".padEnd(maxState)} ${"TARGET".padEnd(maxTarget)} ${"TYPE".padEnd(maxType)}
|
|
2649
|
+
`${"SESSION".padEnd(maxSession)} ${"TOOL".padEnd(maxTool)} ${"STATE".padEnd(maxState)} ${"TARGET".padEnd(maxTarget)} ${"TYPE".padEnd(maxType)} ${"PLAN".padEnd(maxPlan)} BRANCH`,
|
|
2536
2650
|
);
|
|
2537
2651
|
for (const a of agents) {
|
|
2538
2652
|
console.log(
|
|
2539
|
-
`${a.session.padEnd(maxSession)} ${a.tool.padEnd(maxTool)} ${a.state.padEnd(maxState)} ${a.target.padEnd(maxTarget)} ${a.type.padEnd(maxType)} ${a.
|
|
2653
|
+
`${a.session.padEnd(maxSession)} ${a.tool.padEnd(maxTool)} ${a.state.padEnd(maxState)} ${a.target.padEnd(maxTarget)} ${a.type.padEnd(maxType)} ${a.plan.padEnd(maxPlan)} ${a.branch}`,
|
|
2540
2654
|
);
|
|
2541
2655
|
}
|
|
2542
2656
|
|
|
@@ -2685,6 +2799,13 @@ async function cmdArchangel(agentName) {
|
|
|
2685
2799
|
let isProcessing = false;
|
|
2686
2800
|
const intervalMs = config.interval * 1000;
|
|
2687
2801
|
|
|
2802
|
+
// Hash tracking for incremental context updates
|
|
2803
|
+
/** @type {string | null} */
|
|
2804
|
+
let lastPlanHash = null;
|
|
2805
|
+
/** @type {string | null} */
|
|
2806
|
+
let lastTodosHash = null;
|
|
2807
|
+
let isFirstTrigger = true;
|
|
2808
|
+
|
|
2688
2809
|
async function processChanges() {
|
|
2689
2810
|
clearTimeout(debounceTimer);
|
|
2690
2811
|
clearTimeout(maxWaitTimer);
|
|
@@ -2702,6 +2823,21 @@ async function cmdArchangel(agentName) {
|
|
|
2702
2823
|
const parent = findParentSession();
|
|
2703
2824
|
const logPath = parent ? findClaudeLogPath(parent.uuid, parent.session) : null;
|
|
2704
2825
|
|
|
2826
|
+
// Get orientation context (plan and todos) from parent session
|
|
2827
|
+
const meta = parent?.session ? getSessionMeta(parent.session) : null;
|
|
2828
|
+
const planContent = meta?.slug ? readPlanFile(meta.slug) : null;
|
|
2829
|
+
const todosContent = meta?.todos?.length ? formatTodos(meta.todos) : null;
|
|
2830
|
+
|
|
2831
|
+
// Check if plan/todos have changed since last trigger
|
|
2832
|
+
const planHash = quickHash(planContent);
|
|
2833
|
+
const todosHash = quickHash(todosContent);
|
|
2834
|
+
const includePlan = planHash !== lastPlanHash;
|
|
2835
|
+
const includeTodos = todosHash !== lastTodosHash;
|
|
2836
|
+
|
|
2837
|
+
// Update tracking for next trigger
|
|
2838
|
+
lastPlanHash = planHash;
|
|
2839
|
+
lastTodosHash = todosHash;
|
|
2840
|
+
|
|
2705
2841
|
// Build file-specific context from JSONL
|
|
2706
2842
|
const fileContexts = [];
|
|
2707
2843
|
for (const file of files.slice(0, 5)) {
|
|
@@ -2713,7 +2849,18 @@ async function cmdArchangel(agentName) {
|
|
|
2713
2849
|
}
|
|
2714
2850
|
|
|
2715
2851
|
// Build the prompt
|
|
2716
|
-
|
|
2852
|
+
// First trigger: include intro, guidelines, and focus (archangel has memory)
|
|
2853
|
+
let prompt = isFirstTrigger
|
|
2854
|
+
? `You are the archangel of ${agentName}.\n\n${ARCHANGEL_PREAMBLE}\n\n## Focus\n\n${basePrompt}\n\n---`
|
|
2855
|
+
: "";
|
|
2856
|
+
|
|
2857
|
+
// Add orientation context (plan and todos) only if changed since last trigger
|
|
2858
|
+
if (includePlan && planContent) {
|
|
2859
|
+
prompt += (prompt ? "\n\n" : "") + "## Current Plan\n\n" + planContent;
|
|
2860
|
+
}
|
|
2861
|
+
if (includeTodos && todosContent) {
|
|
2862
|
+
prompt += (prompt ? "\n\n" : "") + "## Current Todos\n\n" + todosContent;
|
|
2863
|
+
}
|
|
2717
2864
|
|
|
2718
2865
|
if (fileContexts.length > 0) {
|
|
2719
2866
|
prompt += "\n\n## Recent Edits (from parent session)\n";
|
|
@@ -2747,8 +2894,7 @@ async function cmdArchangel(agentName) {
|
|
|
2747
2894
|
prompt += "\n\n## Git Context\n\n" + gitContext;
|
|
2748
2895
|
}
|
|
2749
2896
|
|
|
2750
|
-
prompt +=
|
|
2751
|
-
'\n\nReview these changes in the context of what the user is working on. Report any issues found. Keep your response concise.\nIf there are no significant issues, respond with just "No issues found."';
|
|
2897
|
+
prompt += "\n\nReview these changes.";
|
|
2752
2898
|
} else {
|
|
2753
2899
|
// Fallback: no JSONL context available, use conversation + git context
|
|
2754
2900
|
const parentContext = getParentSessionContext(ARCHANGEL_PARENT_CONTEXT_ENTRIES);
|
|
@@ -2768,8 +2914,7 @@ async function cmdArchangel(agentName) {
|
|
|
2768
2914
|
prompt += "\n\n## Git Context\n\n" + gitContext;
|
|
2769
2915
|
}
|
|
2770
2916
|
|
|
2771
|
-
prompt +=
|
|
2772
|
-
'\n\nReview these changes in the context of what the user is working on. Report any issues found. Keep your response concise.\nIf there are no significant issues, respond with just "No issues found."';
|
|
2917
|
+
prompt += "\n\nReview these changes.";
|
|
2773
2918
|
}
|
|
2774
2919
|
|
|
2775
2920
|
// Check session still exists
|
|
@@ -2798,6 +2943,7 @@ async function cmdArchangel(agentName) {
|
|
|
2798
2943
|
await sleep(200); // Allow time for large prompts to be processed
|
|
2799
2944
|
tmuxSend(sessionName, "Enter");
|
|
2800
2945
|
await sleep(100); // Ensure Enter is processed
|
|
2946
|
+
isFirstTrigger = false;
|
|
2801
2947
|
|
|
2802
2948
|
// Wait for response
|
|
2803
2949
|
const { state: endState, screen: afterScreen } = await waitForResponse(
|
|
@@ -4016,20 +4162,43 @@ async function cmdSelect(agent, session, n, { wait = false, timeoutMs } = {}) {
|
|
|
4016
4162
|
// =============================================================================
|
|
4017
4163
|
|
|
4018
4164
|
/**
|
|
4019
|
-
*
|
|
4165
|
+
* Resolve the agent to use based on (in priority order):
|
|
4166
|
+
* 1. Explicit --tool flag
|
|
4167
|
+
* 2. Session name (e.g., "claude-archangel-..." → ClaudeAgent)
|
|
4168
|
+
* 3. CLI invocation name (axclaude, axcodex)
|
|
4169
|
+
* 4. AX_DEFAULT_TOOL environment variable
|
|
4170
|
+
* 5. Default to CodexAgent
|
|
4171
|
+
*
|
|
4172
|
+
* @param {{toolFlag?: string, sessionName?: string | null}} options
|
|
4173
|
+
* @returns {{agent: Agent, error?: string}}
|
|
4020
4174
|
*/
|
|
4021
|
-
function
|
|
4175
|
+
function resolveAgent({ toolFlag, sessionName } = {}) {
|
|
4176
|
+
// 1. Explicit --tool flag takes highest priority
|
|
4177
|
+
if (toolFlag) {
|
|
4178
|
+
if (toolFlag === "claude") return { agent: ClaudeAgent };
|
|
4179
|
+
if (toolFlag === "codex") return { agent: CodexAgent };
|
|
4180
|
+
return { agent: CodexAgent, error: `unknown tool '${toolFlag}'` };
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
// 2. Infer from session name (e.g., "claude-archangel-..." or "codex-partner-...")
|
|
4184
|
+
if (sessionName) {
|
|
4185
|
+
const parsed = parseSessionName(sessionName);
|
|
4186
|
+
if (parsed?.tool === "claude") return { agent: ClaudeAgent };
|
|
4187
|
+
if (parsed?.tool === "codex") return { agent: CodexAgent };
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
// 3. CLI invocation name
|
|
4022
4191
|
const invoked = path.basename(process.argv[1], ".js");
|
|
4023
|
-
if (invoked === "axclaude" || invoked === "claude") return ClaudeAgent;
|
|
4024
|
-
if (invoked === "axcodex" || invoked === "codex") return CodexAgent;
|
|
4192
|
+
if (invoked === "axclaude" || invoked === "claude") return { agent: ClaudeAgent };
|
|
4193
|
+
if (invoked === "axcodex" || invoked === "codex") return { agent: CodexAgent };
|
|
4025
4194
|
|
|
4026
|
-
//
|
|
4195
|
+
// 4. AX_DEFAULT_TOOL environment variable
|
|
4027
4196
|
const defaultTool = process.env.AX_DEFAULT_TOOL;
|
|
4028
|
-
if (defaultTool === "claude") return ClaudeAgent;
|
|
4029
|
-
if (defaultTool === "codex" || !defaultTool) return CodexAgent;
|
|
4197
|
+
if (defaultTool === "claude") return { agent: ClaudeAgent };
|
|
4198
|
+
if (defaultTool === "codex" || !defaultTool) return { agent: CodexAgent };
|
|
4030
4199
|
|
|
4031
4200
|
console.error(`WARNING: invalid AX_DEFAULT_TOOL="${defaultTool}", using codex`);
|
|
4032
|
-
return CodexAgent;
|
|
4201
|
+
return { agent: CodexAgent };
|
|
4033
4202
|
}
|
|
4034
4203
|
|
|
4035
4204
|
/**
|
|
@@ -4133,19 +4302,8 @@ async function main() {
|
|
|
4133
4302
|
// Extract flags into local variables for convenience
|
|
4134
4303
|
const { wait, noWait, yolo, fresh, reasoning, follow, all, orphans, force } = flags;
|
|
4135
4304
|
|
|
4136
|
-
//
|
|
4137
|
-
let
|
|
4138
|
-
if (flags.tool) {
|
|
4139
|
-
if (flags.tool === "claude") agent = ClaudeAgent;
|
|
4140
|
-
else if (flags.tool === "codex") agent = CodexAgent;
|
|
4141
|
-
else {
|
|
4142
|
-
console.log(`ERROR: unknown tool '${flags.tool}'`);
|
|
4143
|
-
process.exit(1);
|
|
4144
|
-
}
|
|
4145
|
-
}
|
|
4146
|
-
|
|
4147
|
-
// Session resolution
|
|
4148
|
-
let session = agent.getDefaultSession();
|
|
4305
|
+
// Session resolution (must happen before agent resolution so we can infer tool from session name)
|
|
4306
|
+
let session = null;
|
|
4149
4307
|
if (flags.session) {
|
|
4150
4308
|
if (flags.session === "self") {
|
|
4151
4309
|
const current = tmuxCurrentSession();
|
|
@@ -4160,6 +4318,18 @@ async function main() {
|
|
|
4160
4318
|
}
|
|
4161
4319
|
}
|
|
4162
4320
|
|
|
4321
|
+
// Agent resolution (considers --tool flag, session name, invocation, and env vars)
|
|
4322
|
+
const { agent, error: agentError } = resolveAgent({ toolFlag: flags.tool, sessionName: session });
|
|
4323
|
+
if (agentError) {
|
|
4324
|
+
console.log(`ERROR: ${agentError}`);
|
|
4325
|
+
process.exit(1);
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// If no explicit session, use agent's default
|
|
4329
|
+
if (!session) {
|
|
4330
|
+
session = agent.getDefaultSession();
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4163
4333
|
// Timeout (convert seconds to milliseconds)
|
|
4164
4334
|
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
4165
4335
|
if (flags.timeout !== undefined) {
|