@locusai/cli 0.17.13 → 0.17.15
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/bin/locus.js +310 -57
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -105,20 +105,11 @@ function padEnd(str, width) {
|
|
|
105
105
|
return str;
|
|
106
106
|
return str + " ".repeat(width - currentWidth);
|
|
107
107
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
function hideCursor() {
|
|
114
|
-
if (getCapabilities().isTTY) {
|
|
115
|
-
process.stdout.write("\x1B[?25l");
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function showCursor() {
|
|
119
|
-
if (getCapabilities().isTTY) {
|
|
120
|
-
process.stdout.write("\x1B[?25h");
|
|
121
|
-
}
|
|
108
|
+
function truncate(str, maxWidth) {
|
|
109
|
+
const stripped = stripAnsi(str);
|
|
110
|
+
if (stripped.length <= maxWidth)
|
|
111
|
+
return str;
|
|
112
|
+
return `${stripped.slice(0, maxWidth - 1)}…`;
|
|
122
113
|
}
|
|
123
114
|
function beginSync() {
|
|
124
115
|
if (getCapabilities().isTTY) {
|
|
@@ -821,9 +812,10 @@ class Spinner {
|
|
|
821
812
|
this.message = message;
|
|
822
813
|
this.frame = 0;
|
|
823
814
|
this.timer = setInterval(() => {
|
|
815
|
+
if (!process.stderr.isTTY)
|
|
816
|
+
return;
|
|
824
817
|
const char = cyan(SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length]);
|
|
825
|
-
|
|
826
|
-
process.stderr.write(`${char} ${this.message}`);
|
|
818
|
+
process.stderr.write(`\x1B[2K\r${char} ${this.message}`);
|
|
827
819
|
this.frame++;
|
|
828
820
|
}, 80);
|
|
829
821
|
if (this.timer.unref) {
|
|
@@ -838,7 +830,9 @@ class Spinner {
|
|
|
838
830
|
clearInterval(this.timer);
|
|
839
831
|
this.timer = null;
|
|
840
832
|
}
|
|
841
|
-
|
|
833
|
+
if (process.stderr.isTTY) {
|
|
834
|
+
process.stderr.write("\x1B[2K\r");
|
|
835
|
+
}
|
|
842
836
|
if (finalMessage) {
|
|
843
837
|
process.stderr.write(`${finalMessage}
|
|
844
838
|
`);
|
|
@@ -1640,9 +1634,103 @@ ${bold(green("Locus initialized!"))}
|
|
|
1640
1634
|
reInit: isReInit
|
|
1641
1635
|
});
|
|
1642
1636
|
}
|
|
1643
|
-
var LOCUS_MD_TEMPLATE =
|
|
1637
|
+
var LOCUS_MD_TEMPLATE = `## Planning First
|
|
1638
|
+
|
|
1639
|
+
Complex tasks must be planned before writing code. Create ".locus/plans/<task-name>.md" with:
|
|
1640
|
+
- **Goal**: What we're trying to achieve and why
|
|
1641
|
+
- **Approach**: Step-by-step strategy with technical decisions
|
|
1642
|
+
- **Affected files**: List of files to create/modify/delete
|
|
1643
|
+
- **Acceptance criteria**: Specific, testable conditions for completion
|
|
1644
|
+
- **Dependencies**: Required packages, APIs, or external services
|
|
1645
|
+
|
|
1646
|
+
Delete the planning .md files after successful execution.
|
|
1647
|
+
|
|
1648
|
+
## Code Quality
|
|
1649
|
+
|
|
1650
|
+
- **Follow existing patterns**: Run formatters and linters before finishing (check "package.json" scripts or project config)
|
|
1651
|
+
- **Minimize changes**: Keep modifications atomic. Separate refactors from behavioral changes into different tasks
|
|
1652
|
+
- **Never commit secrets**: No API keys, passwords, or credentials in code. Use environment variables or secret management
|
|
1653
|
+
- **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
|
|
1654
|
+
- **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
|
|
1655
|
+
|
|
1656
|
+
## Artifacts
|
|
1657
|
+
|
|
1658
|
+
When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
|
|
1659
|
+
|
|
1660
|
+
**Always create artifacts for:**
|
|
1661
|
+
- Code quality audits, security reviews, vulnerability assessments
|
|
1662
|
+
- Architecture analyses, system design proposals, or recommendations
|
|
1663
|
+
- Dependency reports, performance profiling, benchmarking results
|
|
1664
|
+
- Research summaries, technology comparisons, or feasibility studies
|
|
1665
|
+
- Migration plans, deployment strategies, or rollback procedures
|
|
1666
|
+
- Post-mortems, incident analysis, or debugging investigations
|
|
1667
|
+
|
|
1668
|
+
**Artifact structure:**
|
|
1669
|
+
- Clear title and date
|
|
1670
|
+
- Executive summary (2-3 sentences)
|
|
1671
|
+
- Detailed findings/analysis
|
|
1672
|
+
- Actionable recommendations (if applicable)
|
|
1673
|
+
|
|
1674
|
+
## Git Operations
|
|
1675
|
+
|
|
1676
|
+
- **Do NOT run**: git add, git commit, git push, git checkout, git branch, or any git commands
|
|
1677
|
+
- **Why**: The Locus orchestrator handles all version control automatically after execution
|
|
1678
|
+
- **Your role**: Focus solely on making file changes. The system commits, pushes, and creates PRs
|
|
1679
|
+
|
|
1680
|
+
## Continuous Learning
|
|
1681
|
+
|
|
1682
|
+
Read ".locus/LEARNINGS.md" **before starting any task** to avoid repeating mistakes.
|
|
1683
|
+
|
|
1684
|
+
**The quality bar:** Ask yourself — "Would a new agent working on a completely different task benefit from knowing this?" If yes, record it. If it only matters for the current task or file, skip it.
|
|
1685
|
+
|
|
1686
|
+
**When to update:**
|
|
1687
|
+
- User corrects your approach, rejects a choice, or states a preference explicitly
|
|
1688
|
+
- You discover where something lives architecturally (e.g., which package owns shared types)
|
|
1689
|
+
- A structural or design decision would not be obvious from reading the code
|
|
1690
|
+
- You encounter a non-obvious constraint that applies project-wide
|
|
1691
|
+
|
|
1692
|
+
**What to record (high-value):**
|
|
1693
|
+
- Where things live: package ownership, shared utilities, config locations
|
|
1694
|
+
- Architectural decisions and their rationale ("we use X not Y because Z")
|
|
1695
|
+
- Explicit user preference overrides — when the user corrects or rejects an approach
|
|
1696
|
+
- Project-wide conventions that aren't visible from a single file
|
|
1644
1697
|
|
|
1645
|
-
|
|
1698
|
+
**What NOT to record (low-value):**
|
|
1699
|
+
- One-time fixes or workarounds specific to a single file or function
|
|
1700
|
+
- Implementation details that are obvious from reading the code
|
|
1701
|
+
- Startup sequences, signal handlers, or local patterns — unless they represent a project-wide rule
|
|
1702
|
+
- Anything the next agent could discover in 30 seconds by reading the relevant file
|
|
1703
|
+
|
|
1704
|
+
**Good examples:**
|
|
1705
|
+
- \`[Architecture]\`: Shared types for all packages live in \`@locusai/shared\` — never redefine them locally in CLI or API packages.
|
|
1706
|
+
- \`[User Preferences]\`: User prefers not to track low-level interrupt/signal handling patterns in learnings — focus on architectural and decision-level entries.
|
|
1707
|
+
- \`[Packages]\`: Validation uses Zod throughout — do not introduce a second validation library.
|
|
1708
|
+
|
|
1709
|
+
**Bad examples (do not write these):**
|
|
1710
|
+
- \`[Patterns]\`: \`run.ts\` must call \`registerShutdownHandlers()\` at startup. ← too local, obvious from the file.
|
|
1711
|
+
- \`[Debugging]\`: Fixed a regex bug in \`image-detect.ts\`. ← one-time fix, irrelevant to future tasks.
|
|
1712
|
+
|
|
1713
|
+
**Format (append-only, never delete):**
|
|
1714
|
+
|
|
1715
|
+
\`\`\`
|
|
1716
|
+
- **[Category]**: Concise description (1-2 lines max). *Rationale if non-obvious.*
|
|
1717
|
+
\`\`\`
|
|
1718
|
+
|
|
1719
|
+
**Categories:** Architecture, Packages, User Preferences, Conventions, Debugging
|
|
1720
|
+
|
|
1721
|
+
## Error Handling
|
|
1722
|
+
|
|
1723
|
+
- **Read error messages carefully**: Don't guess. Parse the actual error before proposing fixes
|
|
1724
|
+
- **Check dependencies first**: Missing packages, wrong versions, and environment issues are common
|
|
1725
|
+
- **Verify assumptions**: If something "should work," confirm it actually does in this environment
|
|
1726
|
+
- **Ask for context**: If you need environment details, configuration, or logs, request them explicitly
|
|
1727
|
+
|
|
1728
|
+
## Communication
|
|
1729
|
+
|
|
1730
|
+
- **Be precise**: When uncertain, state what you know and what you're assuming
|
|
1731
|
+
- **Show your work**: For complex changes, briefly explain the approach before executing
|
|
1732
|
+
- **Highlight trade-offs**: If multiple approaches exist, note why you chose one over others
|
|
1733
|
+
- **Request feedback**: For ambiguous requirements, propose an approach and ask for confirmation
|
|
1646
1734
|
|
|
1647
1735
|
## Project Overview
|
|
1648
1736
|
|
|
@@ -1655,16 +1743,13 @@ This file provides context to AI agents when executing tasks.
|
|
|
1655
1743
|
## Development Workflow
|
|
1656
1744
|
|
|
1657
1745
|
<!-- How to run, test, build, and deploy the project -->
|
|
1658
|
-
|
|
1659
|
-
## Important Notes
|
|
1660
|
-
|
|
1661
|
-
<!-- Anything the AI agent should know: gotchas, design decisions, constraints -->
|
|
1662
1746
|
`, LEARNINGS_MD_TEMPLATE = `# Learnings
|
|
1663
1747
|
|
|
1664
1748
|
This file captures important lessons, decisions, and corrections made during development.
|
|
1665
1749
|
It is read by AI agents before every task to avoid repeating mistakes and to follow established patterns.
|
|
1666
1750
|
|
|
1667
1751
|
<!-- Add learnings below this line. Format: - **[Category]**: Description -->
|
|
1752
|
+
- **[User Preferences]**: Do not record low-level implementation details or one-time fixes in learnings. Focus on architectural decisions, package ownership, and explicit user preference overrides — entries that help any future agent on any task, not just the current one.
|
|
1668
1753
|
`, GITIGNORE_ENTRIES;
|
|
1669
1754
|
var init_init = __esm(() => {
|
|
1670
1755
|
init_config();
|
|
@@ -2533,7 +2618,9 @@ var init_status_indicator = __esm(() => {
|
|
|
2533
2618
|
this.startTime = Date.now();
|
|
2534
2619
|
this.activity = options?.activity ?? "";
|
|
2535
2620
|
this.frame = 0;
|
|
2536
|
-
|
|
2621
|
+
if (process.stderr.isTTY) {
|
|
2622
|
+
process.stderr.write("\x1B[?25l");
|
|
2623
|
+
}
|
|
2537
2624
|
this.timer = setInterval(() => {
|
|
2538
2625
|
this.render(message);
|
|
2539
2626
|
this.frame++;
|
|
@@ -2547,8 +2634,9 @@ var init_status_indicator = __esm(() => {
|
|
|
2547
2634
|
clearInterval(this.timer);
|
|
2548
2635
|
this.timer = null;
|
|
2549
2636
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2637
|
+
if (process.stderr.isTTY) {
|
|
2638
|
+
process.stderr.write("\x1B[2K\r\x1B[?25h");
|
|
2639
|
+
}
|
|
2552
2640
|
}
|
|
2553
2641
|
isActive() {
|
|
2554
2642
|
return this.timer !== null;
|
|
@@ -2569,12 +2657,13 @@ var init_status_indicator = __esm(() => {
|
|
|
2569
2657
|
line += ` ${dim("—")} ${dim(this.activity)}`;
|
|
2570
2658
|
}
|
|
2571
2659
|
line += ` ${dim("— esc to interrupt")}`;
|
|
2572
|
-
const
|
|
2573
|
-
if (line
|
|
2574
|
-
line = line
|
|
2660
|
+
const cols = process.stderr.columns ?? process.stdout.columns ?? 80;
|
|
2661
|
+
if (visualWidth(line) > cols - 1) {
|
|
2662
|
+
line = truncate(line, cols - 1);
|
|
2575
2663
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2664
|
+
if (!process.stderr.isTTY)
|
|
2665
|
+
return;
|
|
2666
|
+
process.stderr.write("\x1B[2K\r" + line);
|
|
2578
2667
|
}
|
|
2579
2668
|
renderShimmer() {
|
|
2580
2669
|
const t = Date.now() / 1000;
|
|
@@ -2959,8 +3048,6 @@ class InputHandler {
|
|
|
2959
3048
|
isLocked() {
|
|
2960
3049
|
return this.locked;
|
|
2961
3050
|
}
|
|
2962
|
-
enableProtocols() {}
|
|
2963
|
-
disableProtocols() {}
|
|
2964
3051
|
async readline() {
|
|
2965
3052
|
await this.waitUntilUnlocked();
|
|
2966
3053
|
return new Promise((resolve2) => {
|
|
@@ -3739,6 +3826,9 @@ class ClaudeRunner {
|
|
|
3739
3826
|
if (options.model) {
|
|
3740
3827
|
args.push("--model", options.model);
|
|
3741
3828
|
}
|
|
3829
|
+
if (options.verbose) {
|
|
3830
|
+
args.push("--verbose", "--output-format", "stream-json");
|
|
3831
|
+
}
|
|
3742
3832
|
log.debug("Spawning claude", { args: args.join(" "), cwd: options.cwd });
|
|
3743
3833
|
return new Promise((resolve2) => {
|
|
3744
3834
|
let output = "";
|
|
@@ -3751,11 +3841,46 @@ class ClaudeRunner {
|
|
|
3751
3841
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3752
3842
|
env
|
|
3753
3843
|
});
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3844
|
+
if (options.verbose) {
|
|
3845
|
+
let lineBuffer = "";
|
|
3846
|
+
const seenToolIds = new Set;
|
|
3847
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
3848
|
+
lineBuffer += chunk.toString();
|
|
3849
|
+
const lines = lineBuffer.split(`
|
|
3850
|
+
`);
|
|
3851
|
+
lineBuffer = lines.pop() ?? "";
|
|
3852
|
+
for (const line of lines) {
|
|
3853
|
+
if (!line.trim())
|
|
3854
|
+
continue;
|
|
3855
|
+
try {
|
|
3856
|
+
const event = JSON.parse(line);
|
|
3857
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
3858
|
+
for (const item of event.message.content) {
|
|
3859
|
+
if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
|
|
3860
|
+
seenToolIds.add(item.id);
|
|
3861
|
+
options.onToolActivity?.(formatToolCall(item.name ?? "", item.input ?? {}));
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
} else if (event.type === "result") {
|
|
3865
|
+
const text = event.result ?? "";
|
|
3866
|
+
output = text;
|
|
3867
|
+
options.onOutput?.(text);
|
|
3868
|
+
}
|
|
3869
|
+
} catch {
|
|
3870
|
+
const newLine = `${line}
|
|
3871
|
+
`;
|
|
3872
|
+
output += newLine;
|
|
3873
|
+
options.onOutput?.(newLine);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
});
|
|
3877
|
+
} else {
|
|
3878
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
3879
|
+
const text = chunk.toString();
|
|
3880
|
+
output += text;
|
|
3881
|
+
options.onOutput?.(text);
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3759
3884
|
this.process.stderr?.on("data", (chunk) => {
|
|
3760
3885
|
const text = chunk.toString();
|
|
3761
3886
|
errorOutput += text;
|
|
@@ -3823,6 +3948,33 @@ class ClaudeRunner {
|
|
|
3823
3948
|
}
|
|
3824
3949
|
}
|
|
3825
3950
|
}
|
|
3951
|
+
function formatToolCall(name, input) {
|
|
3952
|
+
switch (name) {
|
|
3953
|
+
case "Read":
|
|
3954
|
+
return `reading ${input.file_path ?? ""}`;
|
|
3955
|
+
case "Write":
|
|
3956
|
+
return `writing ${input.file_path ?? ""}`;
|
|
3957
|
+
case "Edit":
|
|
3958
|
+
case "MultiEdit":
|
|
3959
|
+
return `editing ${input.file_path ?? ""}`;
|
|
3960
|
+
case "Bash":
|
|
3961
|
+
return `running: ${String(input.command ?? "").slice(0, 60)}`;
|
|
3962
|
+
case "Glob":
|
|
3963
|
+
return `glob ${input.pattern ?? ""}`;
|
|
3964
|
+
case "Grep":
|
|
3965
|
+
return `grep ${input.pattern ?? ""}`;
|
|
3966
|
+
case "LS":
|
|
3967
|
+
return `ls ${input.path ?? ""}`;
|
|
3968
|
+
case "WebFetch":
|
|
3969
|
+
return `fetching ${String(input.url ?? "").slice(0, 50)}`;
|
|
3970
|
+
case "WebSearch":
|
|
3971
|
+
return `searching: ${input.query ?? ""}`;
|
|
3972
|
+
case "Task":
|
|
3973
|
+
return `spawning agent`;
|
|
3974
|
+
default:
|
|
3975
|
+
return name;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3826
3978
|
var init_claude = __esm(() => {
|
|
3827
3979
|
init_logger();
|
|
3828
3980
|
});
|
|
@@ -3830,7 +3982,7 @@ var init_claude = __esm(() => {
|
|
|
3830
3982
|
// src/ai/codex.ts
|
|
3831
3983
|
import { execSync as execSync5, spawn as spawn3 } from "node:child_process";
|
|
3832
3984
|
function buildCodexArgs(model) {
|
|
3833
|
-
const args = ["exec", "--full-auto", "--skip-git-repo-check"];
|
|
3985
|
+
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
|
|
3834
3986
|
if (model) {
|
|
3835
3987
|
args.push("--model", model);
|
|
3836
3988
|
}
|
|
@@ -3870,17 +4022,65 @@ class CodexRunner {
|
|
|
3870
4022
|
const args = buildCodexArgs(options.model);
|
|
3871
4023
|
log.debug("Spawning codex", { args: args.join(" "), cwd: options.cwd });
|
|
3872
4024
|
return new Promise((resolve2) => {
|
|
3873
|
-
let
|
|
4025
|
+
let rawOutput = "";
|
|
3874
4026
|
let errorOutput = "";
|
|
3875
4027
|
this.process = spawn3("codex", args, {
|
|
3876
4028
|
cwd: options.cwd,
|
|
3877
4029
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3878
4030
|
env: { ...process.env }
|
|
3879
4031
|
});
|
|
4032
|
+
let agentMessages = [];
|
|
4033
|
+
const flushAgentMessages = () => {
|
|
4034
|
+
if (agentMessages.length > 0) {
|
|
4035
|
+
options.onOutput?.(agentMessages.join(`
|
|
4036
|
+
|
|
4037
|
+
`));
|
|
4038
|
+
agentMessages = [];
|
|
4039
|
+
}
|
|
4040
|
+
};
|
|
4041
|
+
let lineBuffer = "";
|
|
3880
4042
|
this.process.stdout?.on("data", (chunk) => {
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4043
|
+
lineBuffer += chunk.toString();
|
|
4044
|
+
const lines = lineBuffer.split(`
|
|
4045
|
+
`);
|
|
4046
|
+
lineBuffer = lines.pop() ?? "";
|
|
4047
|
+
for (const line of lines) {
|
|
4048
|
+
if (!line.trim())
|
|
4049
|
+
continue;
|
|
4050
|
+
rawOutput += `${line}
|
|
4051
|
+
`;
|
|
4052
|
+
log.debug("codex stdout line", { line });
|
|
4053
|
+
try {
|
|
4054
|
+
const event = JSON.parse(line);
|
|
4055
|
+
const { type, item } = event;
|
|
4056
|
+
if (type === "item.started" && item?.type === "command_execution") {
|
|
4057
|
+
const cmd = (item.command ?? "").split(`
|
|
4058
|
+
`)[0].slice(0, 80);
|
|
4059
|
+
options.onToolActivity?.(`running: ${cmd}`);
|
|
4060
|
+
} else if (type === "item.completed" && item?.type === "command_execution") {
|
|
4061
|
+
const code = item.exit_code;
|
|
4062
|
+
options.onToolActivity?.(code === 0 ? "done" : `exit ${code}`);
|
|
4063
|
+
} else if (type === "item.completed" && item?.type === "reasoning") {
|
|
4064
|
+
const text = (item.text ?? "").trim().replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
4065
|
+
if (text)
|
|
4066
|
+
options.onToolActivity?.(text);
|
|
4067
|
+
} else if (type === "item.completed" && item?.type === "agent_message") {
|
|
4068
|
+
const text = item.text ?? "";
|
|
4069
|
+
if (text) {
|
|
4070
|
+
agentMessages.push(text);
|
|
4071
|
+
options.onToolActivity?.(text.split(`
|
|
4072
|
+
`)[0].slice(0, 80));
|
|
4073
|
+
}
|
|
4074
|
+
} else if (type === "turn.completed") {
|
|
4075
|
+
flushAgentMessages();
|
|
4076
|
+
}
|
|
4077
|
+
} catch {
|
|
4078
|
+
const newLine = `${line}
|
|
4079
|
+
`;
|
|
4080
|
+
rawOutput += newLine;
|
|
4081
|
+
options.onOutput?.(newLine);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
3884
4084
|
});
|
|
3885
4085
|
this.process.stderr?.on("data", (chunk) => {
|
|
3886
4086
|
const text = chunk.toString();
|
|
@@ -3889,10 +4089,11 @@ class CodexRunner {
|
|
|
3889
4089
|
});
|
|
3890
4090
|
this.process.on("close", (code) => {
|
|
3891
4091
|
this.process = null;
|
|
4092
|
+
flushAgentMessages();
|
|
3892
4093
|
if (this.aborted) {
|
|
3893
4094
|
resolve2({
|
|
3894
4095
|
success: false,
|
|
3895
|
-
output,
|
|
4096
|
+
output: rawOutput,
|
|
3896
4097
|
error: "Aborted by user",
|
|
3897
4098
|
exitCode: code ?? 143
|
|
3898
4099
|
});
|
|
@@ -3901,13 +4102,13 @@ class CodexRunner {
|
|
|
3901
4102
|
if (code === 0) {
|
|
3902
4103
|
resolve2({
|
|
3903
4104
|
success: true,
|
|
3904
|
-
output,
|
|
4105
|
+
output: rawOutput,
|
|
3905
4106
|
exitCode: 0
|
|
3906
4107
|
});
|
|
3907
4108
|
} else {
|
|
3908
4109
|
resolve2({
|
|
3909
4110
|
success: false,
|
|
3910
|
-
output,
|
|
4111
|
+
output: rawOutput,
|
|
3911
4112
|
error: errorOutput || `codex exited with code ${code}`,
|
|
3912
4113
|
exitCode: code ?? 1
|
|
3913
4114
|
});
|
|
@@ -3917,7 +4118,7 @@ class CodexRunner {
|
|
|
3917
4118
|
this.process = null;
|
|
3918
4119
|
resolve2({
|
|
3919
4120
|
success: false,
|
|
3920
|
-
output,
|
|
4121
|
+
output: rawOutput,
|
|
3921
4122
|
error: `Failed to spawn codex: ${err.message}`,
|
|
3922
4123
|
exitCode: 1
|
|
3923
4124
|
});
|
|
@@ -4025,6 +4226,7 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
4025
4226
|
model: options.model,
|
|
4026
4227
|
cwd: options.cwd,
|
|
4027
4228
|
signal: abortController.signal,
|
|
4229
|
+
verbose: options.verbose,
|
|
4028
4230
|
onOutput: (chunk) => {
|
|
4029
4231
|
if (wasAborted)
|
|
4030
4232
|
return;
|
|
@@ -4034,7 +4236,19 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
4034
4236
|
}
|
|
4035
4237
|
renderer?.push(chunk);
|
|
4036
4238
|
output += chunk;
|
|
4037
|
-
}
|
|
4239
|
+
},
|
|
4240
|
+
onToolActivity: (() => {
|
|
4241
|
+
let lastActivityTime = 0;
|
|
4242
|
+
return (summary) => {
|
|
4243
|
+
if (wasAborted)
|
|
4244
|
+
return;
|
|
4245
|
+
const now = Date.now();
|
|
4246
|
+
if (now - lastActivityTime >= 2000) {
|
|
4247
|
+
lastActivityTime = now;
|
|
4248
|
+
indicator.setActivity(summary);
|
|
4249
|
+
}
|
|
4250
|
+
};
|
|
4251
|
+
})()
|
|
4038
4252
|
});
|
|
4039
4253
|
renderer?.stop();
|
|
4040
4254
|
indicator.stop();
|
|
@@ -5939,6 +6153,12 @@ function getSlashCommands() {
|
|
|
5939
6153
|
description: "Force-save current session",
|
|
5940
6154
|
handler: cmdSave
|
|
5941
6155
|
},
|
|
6156
|
+
{
|
|
6157
|
+
name: "/verbose",
|
|
6158
|
+
aliases: ["/v"],
|
|
6159
|
+
description: "Toggle verbose mode (show agent stderr streams)",
|
|
6160
|
+
handler: cmdVerbose
|
|
6161
|
+
},
|
|
5942
6162
|
{
|
|
5943
6163
|
name: "/exit",
|
|
5944
6164
|
aliases: ["/quit", "/q"],
|
|
@@ -5999,8 +6219,30 @@ function cmdReset(_args, ctx) {
|
|
|
5999
6219
|
process.stderr.write(`${green("✓")} Conversation context reset.
|
|
6000
6220
|
`);
|
|
6001
6221
|
}
|
|
6002
|
-
function cmdHistory(_args,
|
|
6003
|
-
|
|
6222
|
+
function cmdHistory(_args, ctx) {
|
|
6223
|
+
const entries = ctx.getHistory();
|
|
6224
|
+
if (entries.length === 0) {
|
|
6225
|
+
process.stderr.write(`${dim("No input history.")}
|
|
6226
|
+
`);
|
|
6227
|
+
return;
|
|
6228
|
+
}
|
|
6229
|
+
process.stderr.write(`
|
|
6230
|
+
${bold("Input History:")} ${dim(`(${entries.length} entries)`)}
|
|
6231
|
+
|
|
6232
|
+
`);
|
|
6233
|
+
const display = entries.slice(0, 50);
|
|
6234
|
+
for (let i = 0;i < display.length; i++) {
|
|
6235
|
+
const num = dim(`${String(i + 1).padStart(3)}.`);
|
|
6236
|
+
const entry = display[i].replace(/\n/g, dim("↵"));
|
|
6237
|
+
process.stderr.write(` ${num} ${entry}
|
|
6238
|
+
`);
|
|
6239
|
+
}
|
|
6240
|
+
if (entries.length > 50) {
|
|
6241
|
+
process.stderr.write(`
|
|
6242
|
+
${dim(`… and ${entries.length - 50} more`)}
|
|
6243
|
+
`);
|
|
6244
|
+
}
|
|
6245
|
+
process.stderr.write(`
|
|
6004
6246
|
`);
|
|
6005
6247
|
}
|
|
6006
6248
|
function cmdSession(_args, ctx) {
|
|
@@ -6109,6 +6351,12 @@ function cmdSave(_args, ctx) {
|
|
|
6109
6351
|
process.stderr.write(`${green("✓")} Session saved.
|
|
6110
6352
|
`);
|
|
6111
6353
|
}
|
|
6354
|
+
function cmdVerbose(_args, ctx) {
|
|
6355
|
+
ctx.onVerboseToggle();
|
|
6356
|
+
const isOn = ctx.getVerbose();
|
|
6357
|
+
process.stderr.write(`${isOn ? green("✓") : dim("○")} Verbose mode ${isOn ? bold("on") : "off"} — agent streams ${isOn ? "visible" : "hidden"}.
|
|
6358
|
+
`);
|
|
6359
|
+
}
|
|
6112
6360
|
function cmdExit(_args, ctx) {
|
|
6113
6361
|
ctx.onExit();
|
|
6114
6362
|
}
|
|
@@ -6526,11 +6774,11 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
6526
6774
|
getHistory: () => history.getEntries(),
|
|
6527
6775
|
onTab: (text) => completion.complete(text)
|
|
6528
6776
|
});
|
|
6529
|
-
input.enableProtocols();
|
|
6530
6777
|
printWelcome(session);
|
|
6531
6778
|
let shouldExit = false;
|
|
6532
6779
|
let currentProvider = inferProviderFromModel(config.ai.model) || config.ai.provider;
|
|
6533
6780
|
let currentModel = config.ai.model;
|
|
6781
|
+
let verbose = true;
|
|
6534
6782
|
const slashCtx = {
|
|
6535
6783
|
projectRoot,
|
|
6536
6784
|
session,
|
|
@@ -6556,7 +6804,12 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
6556
6804
|
},
|
|
6557
6805
|
onExit: () => {
|
|
6558
6806
|
shouldExit = true;
|
|
6559
|
-
}
|
|
6807
|
+
},
|
|
6808
|
+
getHistory: () => history.getEntries(),
|
|
6809
|
+
onVerboseToggle: () => {
|
|
6810
|
+
verbose = !verbose;
|
|
6811
|
+
},
|
|
6812
|
+
getVerbose: () => verbose
|
|
6560
6813
|
};
|
|
6561
6814
|
while (!shouldExit) {
|
|
6562
6815
|
const result = await input.readline();
|
|
@@ -6586,7 +6839,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
6586
6839
|
...config,
|
|
6587
6840
|
ai: { provider: currentProvider, model: currentModel }
|
|
6588
6841
|
}
|
|
6589
|
-
});
|
|
6842
|
+
}, verbose);
|
|
6590
6843
|
sessionManager.addMessage(session, {
|
|
6591
6844
|
role: "assistant",
|
|
6592
6845
|
content: response,
|
|
@@ -6611,7 +6864,6 @@ ${red("✗")} ${msg}
|
|
|
6611
6864
|
break;
|
|
6612
6865
|
}
|
|
6613
6866
|
}
|
|
6614
|
-
input.disableProtocols();
|
|
6615
6867
|
const shouldPersistOnExit = session.messages.length > 0 || sessionManager.isPersisted(session);
|
|
6616
6868
|
if (shouldPersistOnExit) {
|
|
6617
6869
|
sessionManager.save(session);
|
|
@@ -6627,13 +6879,14 @@ ${red("✗")} ${msg}
|
|
|
6627
6879
|
process.stdin.pause();
|
|
6628
6880
|
process.exit(0);
|
|
6629
6881
|
}
|
|
6630
|
-
async function executeAITurn(prompt, session, options) {
|
|
6882
|
+
async function executeAITurn(prompt, session, options, verbose = false) {
|
|
6631
6883
|
const { config, projectRoot } = options;
|
|
6632
6884
|
const aiResult = await runAI({
|
|
6633
6885
|
prompt,
|
|
6634
6886
|
provider: config.ai.provider,
|
|
6635
6887
|
model: config.ai.model,
|
|
6636
|
-
cwd: projectRoot
|
|
6888
|
+
cwd: projectRoot,
|
|
6889
|
+
verbose
|
|
6637
6890
|
});
|
|
6638
6891
|
if (aiResult.interrupted) {
|
|
6639
6892
|
if (aiResult.output) {
|
|
@@ -8534,7 +8787,7 @@ ${bold("Plan saved:")} ${cyan(id)}
|
|
|
8534
8787
|
`);
|
|
8535
8788
|
return;
|
|
8536
8789
|
}
|
|
8537
|
-
process.stderr.write(` To create these issues: ${bold(`locus plan approve ${id.slice(0, 8)}
|
|
8790
|
+
process.stderr.write(` To create these issues: ${bold(`locus plan approve ${id.slice(0, 8)} --sprint <sprint name>`)}
|
|
8538
8791
|
|
|
8539
8792
|
`);
|
|
8540
8793
|
}
|