cmdr-agent 2.5.4 → 3.0.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/README.md +122 -205
- package/dist/bin/cmdr.js +64 -3
- package/dist/bin/cmdr.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/agents/executor.d.ts +1 -1
- package/dist/src/agents/executor.d.ts.map +1 -1
- package/dist/src/agents/executor.js +2 -2
- package/dist/src/agents/executor.js.map +1 -1
- package/dist/src/agents/subagent-tool.d.ts.map +1 -1
- package/dist/src/agents/subagent-tool.js +1 -1
- package/dist/src/agents/subagent-tool.js.map +1 -1
- package/dist/src/cli/args.d.ts +11 -2
- package/dist/src/cli/args.d.ts.map +1 -1
- package/dist/src/cli/args.js +49 -8
- package/dist/src/cli/args.js.map +1 -1
- package/dist/src/cli/buddy.d.ts +49 -0
- package/dist/src/cli/buddy.d.ts.map +1 -0
- package/dist/src/cli/buddy.js +187 -0
- package/dist/src/cli/buddy.js.map +1 -0
- package/dist/src/cli/commands.js +105 -11
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/daemon.d.ts +45 -0
- package/dist/src/cli/daemon.d.ts.map +1 -0
- package/dist/src/cli/daemon.js +157 -0
- package/dist/src/cli/daemon.js.map +1 -0
- package/dist/src/cli/ink/App.d.ts.map +1 -1
- package/dist/src/cli/ink/App.js +257 -1
- package/dist/src/cli/ink/App.js.map +1 -1
- package/dist/src/cli/repl.d.ts +5 -3
- package/dist/src/cli/repl.d.ts.map +1 -1
- package/dist/src/cli/repl.js +87 -26
- package/dist/src/cli/repl.js.map +1 -1
- package/dist/src/config/schema.d.ts +113 -12
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +26 -2
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/agent-runner.d.ts.map +1 -1
- package/dist/src/core/agent-runner.js +42 -3
- package/dist/src/core/agent-runner.js.map +1 -1
- package/dist/src/core/agent.d.ts +2 -0
- package/dist/src/core/agent.d.ts.map +1 -1
- package/dist/src/core/agent.js +13 -0
- package/dist/src/core/agent.js.map +1 -1
- package/dist/src/core/types.d.ts +16 -1
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/types.js +6 -0
- package/dist/src/core/types.js.map +1 -1
- package/dist/src/index.d.ts +13 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/llm/anthropic.d.ts.map +1 -1
- package/dist/src/llm/anthropic.js +11 -0
- package/dist/src/llm/anthropic.js.map +1 -1
- package/dist/src/llm/ollama.d.ts.map +1 -1
- package/dist/src/llm/ollama.js +8 -1
- package/dist/src/llm/ollama.js.map +1 -1
- package/dist/src/llm/openai.d.ts.map +1 -1
- package/dist/src/llm/openai.js +21 -6
- package/dist/src/llm/openai.js.map +1 -1
- package/dist/src/memory/index-manager.d.ts +65 -0
- package/dist/src/memory/index-manager.d.ts.map +1 -0
- package/dist/src/memory/index-manager.js +241 -0
- package/dist/src/memory/index-manager.js.map +1 -0
- package/dist/src/plugins/plugin-manager.d.ts +2 -1
- package/dist/src/plugins/plugin-manager.d.ts.map +1 -1
- package/dist/src/plugins/plugin-manager.js +13 -2
- package/dist/src/plugins/plugin-manager.js.map +1 -1
- package/dist/src/server/index.d.ts +20 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +281 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/session/branch-manager.d.ts +39 -0
- package/dist/src/session/branch-manager.d.ts.map +1 -0
- package/dist/src/session/branch-manager.js +142 -0
- package/dist/src/session/branch-manager.js.map +1 -0
- package/dist/src/session/checkpoint-manager.d.ts +28 -0
- package/dist/src/session/checkpoint-manager.d.ts.map +1 -0
- package/dist/src/session/checkpoint-manager.js +96 -0
- package/dist/src/session/checkpoint-manager.js.map +1 -0
- package/dist/src/tools/built-in/browser.d.ts +32 -0
- package/dist/src/tools/built-in/browser.d.ts.map +1 -0
- package/dist/src/tools/built-in/browser.js +168 -0
- package/dist/src/tools/built-in/browser.js.map +1 -0
- package/dist/src/tools/built-in/git-diff.d.ts +1 -0
- package/dist/src/tools/built-in/git-diff.d.ts.map +1 -1
- package/dist/src/tools/built-in/git-diff.js +7 -2
- package/dist/src/tools/built-in/git-diff.js.map +1 -1
- package/dist/src/tools/built-in/index.d.ts +7 -1
- package/dist/src/tools/built-in/index.d.ts.map +1 -1
- package/dist/src/tools/built-in/index.js +19 -1
- package/dist/src/tools/built-in/index.js.map +1 -1
- package/dist/src/tools/built-in/rag-search.d.ts +12 -0
- package/dist/src/tools/built-in/rag-search.d.ts.map +1 -0
- package/dist/src/tools/built-in/rag-search.js +37 -0
- package/dist/src/tools/built-in/rag-search.js.map +1 -0
- package/dist/src/tools/executor.d.ts.map +1 -1
- package/dist/src/tools/executor.js +55 -2
- package/dist/src/tools/executor.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon mode — background file watcher + on-change command execution.
|
|
3
|
+
*
|
|
4
|
+
* cmdr daemon start --watch src/ --on-change "npm run lint --fix"
|
|
5
|
+
* cmdr daemon status
|
|
6
|
+
* cmdr daemon stop
|
|
7
|
+
*
|
|
8
|
+
* Uses Node.js fs.watch (recursive) and spawns on-change commands.
|
|
9
|
+
*/
|
|
10
|
+
import { watch } from 'node:fs';
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { join, resolve } from 'node:path';
|
|
13
|
+
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
const PID_DIR = join(homedir(), '.cmdr', 'daemon');
|
|
16
|
+
function pidFilePath(cwd) {
|
|
17
|
+
// Use a hash of cwd for unique pid file
|
|
18
|
+
const { createHash } = require('node:crypto');
|
|
19
|
+
const hash = createHash('sha256').update(cwd).digest('hex').slice(0, 12);
|
|
20
|
+
return join(PID_DIR, `${hash}.json`);
|
|
21
|
+
}
|
|
22
|
+
export class CmdrDaemon {
|
|
23
|
+
config;
|
|
24
|
+
watchers = [];
|
|
25
|
+
debounceTimer = null;
|
|
26
|
+
running = false;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
}
|
|
30
|
+
/** Start watching and executing on change. */
|
|
31
|
+
async start() {
|
|
32
|
+
if (this.running)
|
|
33
|
+
return;
|
|
34
|
+
const debounceMs = this.config.debounceMs ?? 500;
|
|
35
|
+
for (const watchPath of this.config.watchPaths) {
|
|
36
|
+
const fullPath = resolve(this.config.cwd, watchPath);
|
|
37
|
+
try {
|
|
38
|
+
const watcher = watch(fullPath, { recursive: true }, (_eventType, filename) => {
|
|
39
|
+
if (!filename)
|
|
40
|
+
return;
|
|
41
|
+
// Skip hidden files and node_modules
|
|
42
|
+
if (filename.startsWith('.') || filename.includes('node_modules'))
|
|
43
|
+
return;
|
|
44
|
+
// Debounce rapid changes
|
|
45
|
+
if (this.debounceTimer)
|
|
46
|
+
clearTimeout(this.debounceTimer);
|
|
47
|
+
this.debounceTimer = setTimeout(() => {
|
|
48
|
+
this.executeOnChange(filename);
|
|
49
|
+
}, debounceMs);
|
|
50
|
+
});
|
|
51
|
+
this.watchers.push(watcher);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(`Cannot watch ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.running = true;
|
|
58
|
+
await this.writePidFile();
|
|
59
|
+
console.log(`Daemon started (PID ${process.pid})`);
|
|
60
|
+
console.log(` Watching: ${this.config.watchPaths.join(', ')}`);
|
|
61
|
+
console.log(` On change: ${this.config.onChange}`);
|
|
62
|
+
// Keep process alive
|
|
63
|
+
const keepAlive = setInterval(() => { }, 60_000);
|
|
64
|
+
// Graceful shutdown
|
|
65
|
+
const shutdown = async () => {
|
|
66
|
+
clearInterval(keepAlive);
|
|
67
|
+
await this.stop();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
};
|
|
70
|
+
process.on('SIGINT', shutdown);
|
|
71
|
+
process.on('SIGTERM', shutdown);
|
|
72
|
+
}
|
|
73
|
+
/** Execute the on-change command. */
|
|
74
|
+
executeOnChange(filename) {
|
|
75
|
+
console.log(`[daemon] Change detected: ${filename}`);
|
|
76
|
+
console.log(`[daemon] Running: ${this.config.onChange}`);
|
|
77
|
+
const child = spawn('sh', ['-c', this.config.onChange], {
|
|
78
|
+
cwd: this.config.cwd,
|
|
79
|
+
stdio: 'inherit',
|
|
80
|
+
env: { ...process.env, CMDR_CHANGED_FILE: filename },
|
|
81
|
+
});
|
|
82
|
+
child.on('exit', (code) => {
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
console.log(`[daemon] Command completed successfully`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(`[daemon] Command exited with code ${code}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Stop watching. */
|
|
92
|
+
async stop() {
|
|
93
|
+
if (this.debounceTimer)
|
|
94
|
+
clearTimeout(this.debounceTimer);
|
|
95
|
+
for (const watcher of this.watchers) {
|
|
96
|
+
watcher.close();
|
|
97
|
+
}
|
|
98
|
+
this.watchers = [];
|
|
99
|
+
this.running = false;
|
|
100
|
+
await this.removePidFile();
|
|
101
|
+
console.log('Daemon stopped.');
|
|
102
|
+
}
|
|
103
|
+
/** Write PID file for status/stop from another process. */
|
|
104
|
+
async writePidFile() {
|
|
105
|
+
await mkdir(PID_DIR, { recursive: true });
|
|
106
|
+
const data = {
|
|
107
|
+
pid: process.pid,
|
|
108
|
+
cwd: this.config.cwd,
|
|
109
|
+
watchPaths: this.config.watchPaths,
|
|
110
|
+
onChange: this.config.onChange,
|
|
111
|
+
startedAt: new Date().toISOString(),
|
|
112
|
+
};
|
|
113
|
+
await writeFile(pidFilePath(this.config.cwd), JSON.stringify(data, null, 2));
|
|
114
|
+
}
|
|
115
|
+
/** Remove PID file on shutdown. */
|
|
116
|
+
async removePidFile() {
|
|
117
|
+
try {
|
|
118
|
+
await unlink(pidFilePath(this.config.cwd));
|
|
119
|
+
}
|
|
120
|
+
catch { /* already gone */ }
|
|
121
|
+
}
|
|
122
|
+
/** Read daemon status for a given cwd. */
|
|
123
|
+
static async status(cwd) {
|
|
124
|
+
try {
|
|
125
|
+
const raw = await readFile(pidFilePath(cwd), 'utf-8');
|
|
126
|
+
const data = JSON.parse(raw);
|
|
127
|
+
// Check if process is actually running
|
|
128
|
+
try {
|
|
129
|
+
process.kill(data.pid, 0);
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Process no longer running, clean up stale PID file
|
|
134
|
+
await unlink(pidFilePath(cwd)).catch(() => { });
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Stop a running daemon for a given cwd. */
|
|
143
|
+
static async stopByPid(cwd) {
|
|
144
|
+
const info = await CmdrDaemon.status(cwd);
|
|
145
|
+
if (!info)
|
|
146
|
+
return false;
|
|
147
|
+
try {
|
|
148
|
+
process.kill(info.pid, 'SIGTERM');
|
|
149
|
+
await unlink(pidFilePath(cwd)).catch(() => { });
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../src/cli/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAA;AAGhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAkE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACjI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAI9D,OAAO,EACsD,cAAc,EAC1E,MAAM,sCAAsC,CAAA;AAE7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAqEvE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,OAAO,EAAE,UAAU,CAAA;IACnB,YAAY,EAAE,YAAY,CAAA;IAC1B,gBAAgB,CAAC,EAAE,UAAU,CAAA;IAC7B,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE,WAAW,CAAA;IACxB,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAoED,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAA;AAGhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAkE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACjI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAI9D,OAAO,EACsD,cAAc,EAC1E,MAAM,sCAAsC,CAAA;AAE7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAqEvE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,OAAO,EAAE,UAAU,CAAA;IACnB,YAAY,EAAE,YAAY,CAAA;IAC1B,gBAAgB,CAAC,EAAE,UAAU,CAAA;IAC7B,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE,WAAW,CAAA;IACxB,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAoED,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,CAozClE"}
|
package/dist/src/cli/ink/App.js
CHANGED
|
@@ -124,6 +124,7 @@ export default function App(props) {
|
|
|
124
124
|
const [tokensOut, setTokensOut] = useState(0);
|
|
125
125
|
const [turnCount, setTurnCount] = useState(0);
|
|
126
126
|
const planModeRef = useRef(false);
|
|
127
|
+
const pendingImageRef = useRef(null);
|
|
127
128
|
const currentModelRef = useRef(props.model);
|
|
128
129
|
const activeTeamRef = useRef(props.activeTeamConfig);
|
|
129
130
|
const stateRef = useRef(state);
|
|
@@ -291,6 +292,10 @@ export default function App(props) {
|
|
|
291
292
|
const contextLimit = getDefaultContextLength(currentModelRef.current);
|
|
292
293
|
if (session.tokenCount > contextLimit * 0.70) {
|
|
293
294
|
try {
|
|
295
|
+
// Auto-checkpoint before compaction
|
|
296
|
+
const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
|
|
297
|
+
const cpMgr = new CheckpointManager(session.id ?? 'default');
|
|
298
|
+
await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
|
|
294
299
|
const stats = await session.compact(adapter, currentModelRef.current);
|
|
295
300
|
agent.replaceMessages(session.messages);
|
|
296
301
|
appendOutput(` ${DIM(`◇ pre-compacted: ${stats.before} → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
|
|
@@ -499,6 +504,10 @@ export default function App(props) {
|
|
|
499
504
|
// Auto-compact if needed
|
|
500
505
|
if (session.shouldCompact()) {
|
|
501
506
|
try {
|
|
507
|
+
// Auto-checkpoint before compaction
|
|
508
|
+
const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
|
|
509
|
+
const cpMgr = new CheckpointManager(session.id ?? 'default');
|
|
510
|
+
await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
|
|
502
511
|
const stats = await session.compact(adapter, currentModelRef.current);
|
|
503
512
|
agent.replaceMessages(session.messages);
|
|
504
513
|
appendOutput(` ${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
|
|
@@ -612,6 +621,10 @@ export default function App(props) {
|
|
|
612
621
|
}
|
|
613
622
|
if (result === '__COMPACT__') {
|
|
614
623
|
session.syncFromAgent(agent.getHistory());
|
|
624
|
+
// Auto-checkpoint before compaction
|
|
625
|
+
const { CheckpointManager: CpMgr } = await import('../../session/checkpoint-manager.js');
|
|
626
|
+
const cpMgr = new CpMgr(session.id ?? 'default');
|
|
627
|
+
await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
|
|
615
628
|
const stats = await session.compact(adapter, currentModelRef.current);
|
|
616
629
|
agent.replaceMessages(session.messages);
|
|
617
630
|
appendOutput(` ${DIM('ℹ')} ${WHITE(`${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`)}`);
|
|
@@ -628,6 +641,233 @@ export default function App(props) {
|
|
|
628
641
|
}
|
|
629
642
|
return false;
|
|
630
643
|
}
|
|
644
|
+
// /review command — get diff and send as structured review prompt
|
|
645
|
+
if (typeof result === 'string' && result.startsWith('__REVIEW__:')) {
|
|
646
|
+
const reviewArgs = result.slice('__REVIEW__:'.length).trim();
|
|
647
|
+
const gitTool = toolRegistry.get('git_diff');
|
|
648
|
+
if (!gitTool) {
|
|
649
|
+
appendOutput(` ${ERROR_SYM} ${RED('git_diff tool not available.')}`);
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
// Parse review args: --staged, range (e.g. HEAD~3..HEAD), or path
|
|
653
|
+
let diffInput = {};
|
|
654
|
+
if (!reviewArgs) {
|
|
655
|
+
// Default: HEAD~1..HEAD
|
|
656
|
+
diffInput = { range: 'HEAD~1..HEAD' };
|
|
657
|
+
}
|
|
658
|
+
else if (reviewArgs === '--staged') {
|
|
659
|
+
diffInput = { staged: true };
|
|
660
|
+
}
|
|
661
|
+
else if (reviewArgs.includes('..')) {
|
|
662
|
+
// Commit range like HEAD~3..HEAD
|
|
663
|
+
const parts = reviewArgs.split(/\s+/);
|
|
664
|
+
diffInput = { range: parts[0] };
|
|
665
|
+
if (parts[1])
|
|
666
|
+
diffInput.path = parts[1];
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
// Path scope
|
|
670
|
+
diffInput = { range: 'HEAD~1..HEAD', path: reviewArgs };
|
|
671
|
+
}
|
|
672
|
+
const diffResult = await gitTool.execute(diffInput, {
|
|
673
|
+
agent: { name: 'cmdr', role: 'assistant', model: currentModelRef.current },
|
|
674
|
+
cwd: process.cwd(),
|
|
675
|
+
});
|
|
676
|
+
if (diffResult.isError || diffResult.data === '(no changes)') {
|
|
677
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(diffResult.data)}`);
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
const reviewPrompt = `Please review the following git diff. Analyze for:\n1. **Logic errors** — incorrect behavior, off-by-one, wrong conditions\n2. **Security** — injection, exposure, auth gaps\n3. **Performance** — unnecessary work, missing caching, O(n²) patterns\n4. **Style** — naming, consistency, readability\n5. **Error handling** — missing catches, swallowed errors, unclear messages\n\nBe specific. Reference line numbers and file names. Suggest fixes where applicable.\n\n\`\`\`diff\n${diffResult.data}\n\`\`\``;
|
|
681
|
+
await handleUserMessage(reviewPrompt);
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
// /effort command — set effort level
|
|
685
|
+
if (typeof result === 'string' && result.startsWith('__EFFORT__:')) {
|
|
686
|
+
const level = result.slice('__EFFORT__:'.length);
|
|
687
|
+
const { EFFORT_CONFIGS } = await import('../../core/types.js');
|
|
688
|
+
const config = EFFORT_CONFIGS[level];
|
|
689
|
+
const cfg = agent.config;
|
|
690
|
+
cfg.thinkingEnabled = config.thinkingEnabled;
|
|
691
|
+
cfg.temperature = config.temperature;
|
|
692
|
+
const levelColors = { low: GREEN, medium: YELLOW, high: PURPLE, max: RED };
|
|
693
|
+
const colorFn = levelColors[level] || WHITE;
|
|
694
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Effort:')} ${colorFn(level)} ${DIM(`(think=${config.thinkingEnabled ?? 'auto'}, temp=${config.temperature})`)}`);
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
// /image command — set pending image for next prompt
|
|
698
|
+
if (typeof result === 'string' && result.startsWith('__IMAGE__:')) {
|
|
699
|
+
const imgPath = result.slice('__IMAGE__:'.length);
|
|
700
|
+
const { existsSync } = await import('fs');
|
|
701
|
+
const { resolve } = await import('path');
|
|
702
|
+
const resolvedPath = resolve(imgPath);
|
|
703
|
+
if (!existsSync(resolvedPath)) {
|
|
704
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Image not found: ${imgPath}`)}`);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
pendingImageRef.current = resolvedPath;
|
|
708
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Image attached: ${GREEN(imgPath)} — will be included in your next message`)}`);
|
|
709
|
+
}
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
// /checkpoint command
|
|
713
|
+
if (typeof result === 'string' && result.startsWith('__CHECKPOINT__:')) {
|
|
714
|
+
const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
|
|
715
|
+
const cpManager = new CheckpointManager(session.id ?? 'default');
|
|
716
|
+
const payload = result.slice('__CHECKPOINT__:'.length);
|
|
717
|
+
if (payload === 'list') {
|
|
718
|
+
const checkpoints = await cpManager.list();
|
|
719
|
+
if (checkpoints.length === 0) {
|
|
720
|
+
appendOutput(` ${DIM('No checkpoints saved. Use /checkpoint save [label]')}`);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
const lines = ['', ` ${PURPLE.bold('Checkpoints')}`, ''];
|
|
724
|
+
for (const cp of checkpoints) {
|
|
725
|
+
const date = new Date(cp.createdAt);
|
|
726
|
+
const timeStr = date.toLocaleTimeString();
|
|
727
|
+
lines.push(` ${GREEN('•')} ${DIM(cp.id.slice(0, 20))} ${WHITE(cp.label)} ${DIM(cp.model)} ${DIM(timeStr)} ${DIM(`${cp.messageCount} msgs`)}`);
|
|
728
|
+
}
|
|
729
|
+
lines.push('');
|
|
730
|
+
appendLines(lines);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
else if (payload.startsWith('save:')) {
|
|
734
|
+
const label = payload.slice('save:'.length);
|
|
735
|
+
session.syncFromAgent(agent.getHistory());
|
|
736
|
+
const cp = await cpManager.save(label, session.messages, currentModelRef.current);
|
|
737
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Checkpoint saved: ${GREEN(cp.label)} (${cp.messageCount} messages)`)}`);
|
|
738
|
+
}
|
|
739
|
+
else if (payload.startsWith('restore:')) {
|
|
740
|
+
const id = payload.slice('restore:'.length);
|
|
741
|
+
const cp = await cpManager.restore(id);
|
|
742
|
+
if (cp) {
|
|
743
|
+
agent.replaceMessages(cp.messages);
|
|
744
|
+
session.syncFromAgent(cp.messages);
|
|
745
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Restored checkpoint: ${GREEN(cp.label)} (${cp.messageCount} messages)`)}`);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Checkpoint not found: ${id}`)}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else if (payload.startsWith('delete:')) {
|
|
752
|
+
const id = payload.slice('delete:'.length);
|
|
753
|
+
const deleted = await cpManager.delete(id);
|
|
754
|
+
if (deleted) {
|
|
755
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Checkpoint deleted.')}`);
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Checkpoint not found: ${id}`)}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
// /fork, /branches, /switch, /merge commands
|
|
764
|
+
if (typeof result === 'string' && result.startsWith('__BRANCH__:')) {
|
|
765
|
+
const { BranchManager } = await import('../../session/branch-manager.js');
|
|
766
|
+
const brManager = new BranchManager(session.id ?? 'default');
|
|
767
|
+
const payload = result.slice('__BRANCH__:'.length);
|
|
768
|
+
if (payload === 'list') {
|
|
769
|
+
const branches = await brManager.list();
|
|
770
|
+
if (branches.length === 0) {
|
|
771
|
+
appendOutput(` ${DIM('No branches. Use /fork [name] to create one.')}`);
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
const lines = ['', ` ${PURPLE.bold('Branches')}`, ''];
|
|
775
|
+
for (const br of branches) {
|
|
776
|
+
const date = new Date(br.createdAt);
|
|
777
|
+
const timeStr = date.toLocaleTimeString();
|
|
778
|
+
const active = br.id === brManager.activeBranch ? ` ${GREEN('← active')}` : '';
|
|
779
|
+
lines.push(` ${GREEN('•')} ${DIM(br.id.slice(0, 20))} ${WHITE(br.name)} ${DIM(timeStr)} ${DIM(`${br.messageCount} msgs`)}${active}`);
|
|
780
|
+
}
|
|
781
|
+
lines.push('');
|
|
782
|
+
appendLines(lines);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
else if (payload.startsWith('fork:')) {
|
|
786
|
+
const name = payload.slice('fork:'.length);
|
|
787
|
+
session.syncFromAgent(agent.getHistory());
|
|
788
|
+
const br = await brManager.fork(name, session.messages, currentModelRef.current);
|
|
789
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Forked branch: ${GREEN(br.name)} (${br.messageCount} messages)`)}`);
|
|
790
|
+
}
|
|
791
|
+
else if (payload.startsWith('switch:')) {
|
|
792
|
+
const id = payload.slice('switch:'.length);
|
|
793
|
+
const br = await brManager.switch(id);
|
|
794
|
+
if (br) {
|
|
795
|
+
agent.replaceMessages(br.messages);
|
|
796
|
+
session.syncFromAgent(br.messages);
|
|
797
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Switched to branch: ${GREEN(br.name)} (${br.messageCount} messages)`)}`);
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Branch not found: ${id}`)}`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
else if (payload.startsWith('merge:')) {
|
|
804
|
+
const id = payload.slice('merge:'.length);
|
|
805
|
+
const merged = await brManager.merge(id, agent.getHistory());
|
|
806
|
+
if (merged) {
|
|
807
|
+
agent.replaceMessages(merged);
|
|
808
|
+
session.syncFromAgent(merged);
|
|
809
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Merged branch into current conversation (${merged.length} messages)`)}`);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Branch not found: ${id}`)}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
// /index and /search commands — RAG indexing
|
|
818
|
+
if (typeof result === 'string' && result.startsWith('__INDEX__:')) {
|
|
819
|
+
const { IndexManager } = await import('../../memory/index-manager.js');
|
|
820
|
+
const { setIndexManager } = await import('../../tools/built-in/rag-search.js');
|
|
821
|
+
const idxManager = new IndexManager(process.cwd(), props.ollamaUrl);
|
|
822
|
+
setIndexManager(idxManager);
|
|
823
|
+
const payload = result.slice('__INDEX__:'.length);
|
|
824
|
+
if (payload === 'status') {
|
|
825
|
+
const status = await idxManager.status();
|
|
826
|
+
appendOutput(` ${DIM('Index:')} ${WHITE(`${status.fileCount} files, ${status.chunkCount} chunks`)} ${DIM(`(model: ${status.model}, updated: ${status.updatedAt})`)}`);
|
|
827
|
+
}
|
|
828
|
+
else if (payload === 'clear') {
|
|
829
|
+
await idxManager.clear();
|
|
830
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Index cleared.')}`);
|
|
831
|
+
}
|
|
832
|
+
else if (payload.startsWith('search:')) {
|
|
833
|
+
const query = payload.slice('search:'.length);
|
|
834
|
+
try {
|
|
835
|
+
const results = await idxManager.search(query);
|
|
836
|
+
if (results.length === 0) {
|
|
837
|
+
appendOutput(` ${DIM('No results. Make sure files are indexed with /index <path>.')}`);
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
const lines = ['', ` ${PURPLE.bold('Search Results')}`, ''];
|
|
841
|
+
for (const r of results) {
|
|
842
|
+
lines.push(` ${GREEN('•')} ${CYAN(`${r.file}:${r.startLine}-${r.endLine}`)} ${DIM(`(score: ${r.score.toFixed(3)})`)}`);
|
|
843
|
+
const preview = r.text.split('\n').slice(0, 3).map(l => ` ${DIM(l)}`).join('\n');
|
|
844
|
+
lines.push(preview);
|
|
845
|
+
}
|
|
846
|
+
lines.push('');
|
|
847
|
+
appendLines(lines);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch (err) {
|
|
851
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
852
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Search failed: ${msg}`)}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
else if (payload.startsWith('index:')) {
|
|
856
|
+
const path = payload.slice('index:'.length);
|
|
857
|
+
appendOutput(` ${DIM('Indexing')} ${WHITE(path)}${DIM('...')}`);
|
|
858
|
+
try {
|
|
859
|
+
const count = await idxManager.index([path], (msg) => {
|
|
860
|
+
appendOutput(` ${DIM(msg)}`);
|
|
861
|
+
});
|
|
862
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Indexed ${GREEN(String(count))} new chunks`)}`);
|
|
863
|
+
}
|
|
864
|
+
catch (err) {
|
|
865
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
866
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Indexing failed: ${msg}`)}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
631
871
|
if (result === '__SESSION_SAVE__') {
|
|
632
872
|
session.syncFromAgent(agent.getHistory());
|
|
633
873
|
if (session.messages.length > 0) {
|
|
@@ -972,7 +1212,23 @@ export default function App(props) {
|
|
|
972
1212
|
await handleUserMessage(planMsg);
|
|
973
1213
|
}
|
|
974
1214
|
else {
|
|
975
|
-
|
|
1215
|
+
// If an image is pending, attach it to this message
|
|
1216
|
+
if (pendingImageRef.current) {
|
|
1217
|
+
const { readFile: readFs } = await import('fs/promises');
|
|
1218
|
+
const { extname } = await import('path');
|
|
1219
|
+
const ext = extname(pendingImageRef.current).toLowerCase();
|
|
1220
|
+
const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp' };
|
|
1221
|
+
const mediaType = mimeMap[ext] || 'image/png';
|
|
1222
|
+
const imageData = await readFs(pendingImageRef.current);
|
|
1223
|
+
const base64 = imageData.toString('base64');
|
|
1224
|
+
agent.addImageMessage(input, base64, mediaType);
|
|
1225
|
+
pendingImageRef.current = null;
|
|
1226
|
+
// Stream with empty message since we already added the user message with image
|
|
1227
|
+
await handleUserMessage('');
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
await handleUserMessage(input);
|
|
1231
|
+
}
|
|
976
1232
|
}
|
|
977
1233
|
}
|
|
978
1234
|
catch (err) {
|