@pentoshi/clai 2.0.21 → 2.0.23
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 +4 -2
- package/dist/agent/loop-guard.d.ts +14 -0
- package/dist/agent/loop-guard.js +50 -0
- package/dist/agent/loop-guard.js.map +1 -1
- package/dist/agent/runner.d.ts +1 -0
- package/dist/agent/runner.js +192 -31
- package/dist/agent/runner.js.map +1 -1
- package/dist/commands/providers.js +2 -0
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/llm/bynara.d.ts +2 -0
- package/dist/llm/bynara.js +80 -0
- package/dist/llm/bynara.js.map +1 -0
- package/dist/llm/capabilities.js +3 -0
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/http.d.ts +1 -1
- package/dist/llm/http.js +32 -24
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/kimchi.js +28 -1
- package/dist/llm/kimchi.js.map +1 -1
- package/dist/llm/provider.d.ts +2 -0
- package/dist/llm/provider.js +27 -0
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/router.js +40 -18
- package/dist/llm/router.js.map +1 -1
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.js +6 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/repl.js +75 -3
- package/dist/repl.js.map +1 -1
- package/dist/store/config.js +1 -0
- package/dist/store/config.js.map +1 -1
- package/dist/store/keys.js +10 -5
- package/dist/store/keys.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -0
- package/dist/tools/registry.js +11 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tui/App.js +30 -5
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/PickerPanel.js +7 -1
- package/dist/tui/components/PickerPanel.js.map +1 -1
- package/dist/tui/render-lines.js +78 -32
- package/dist/tui/render-lines.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/markdown.d.ts +1 -1
- package/dist/ui/markdown.js +16 -9
- package/dist/ui/markdown.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ classic REPL automatically.
|
|
|
106
106
|
|
|
107
107
|
- **`/ask` mode** — Read-only. AI explains, gives commands & step-by-step guidance, but does NOT execute anything.
|
|
108
108
|
- **`/agent` mode** — Agentic. AI plans, waits for approval, then executes shell commands, edits files, installs missing tools, parses output, and continues until the goal is met. Tasks run on an approve/refine/discard plan workflow (`/implement`, free-text to refine, `/discard` to cancel).
|
|
109
|
-
- **
|
|
109
|
+
- **11 LLM providers** — Groq, Google Gemini, OpenRouter, OpenAI, Anthropic, NVIDIA NIM, AgentRouter, Kimchi, AWS Mantle, Ollama (local), and Bynara. All with streaming.
|
|
110
110
|
- **10 built-in tools** — `shell.exec`, `fs.read`, `fs.write`, `fs.list`, `fs.search`, `pkg.install`, `net.scan`, `http.fetch`, `sysinfo`, `pentest.recon`.
|
|
111
111
|
- **Smart safety gate** — Read-only commands auto-execute; mutating commands require confirmation; destructive patterns are blocked.
|
|
112
112
|
- **OS-aware & tool-frugal** — Picks the best approach for your OS, prefers tools already installed (installs only when nothing suitable exists), broadens its approach and escalates privileges as needed to finish the task.
|
|
@@ -122,7 +122,7 @@ classic REPL automatically.
|
|
|
122
122
|
|
|
123
123
|
## Provider Setup
|
|
124
124
|
|
|
125
|
-
clai supports
|
|
125
|
+
clai supports 11 LLM providers (9 with free tiers):
|
|
126
126
|
|
|
127
127
|
| Provider | Default Model | Free? | API Key Prefix |
|
|
128
128
|
|-------------|----------------------------------------------|-------|----------------|
|
|
@@ -136,6 +136,7 @@ clai supports 10 LLM providers (8 with free tiers):
|
|
|
136
136
|
| Kimchi | `kimi-k2.6` | ✓ | (any) |
|
|
137
137
|
| AWS Mantle | `anthropic.claude-haiku-4-5` | — | `sk-ant-` |
|
|
138
138
|
| Ollama | `llama3.1:8b` | ✓ | (local URL) |
|
|
139
|
+
| Bynara | `mimo-v2.5-free` | ✓ | `sk_nry_` |
|
|
139
140
|
|
|
140
141
|
```sh
|
|
141
142
|
# Store an API key
|
|
@@ -192,6 +193,7 @@ export OLLAMA_HOST=http://localhost:11434
|
|
|
192
193
|
| `/set <provider> [key]` | Store API key (masked input if key omitted) |
|
|
193
194
|
| `/unset <provider>` | Remove stored key |
|
|
194
195
|
| `/keys` | List configured providers, masked |
|
|
196
|
+
| `/info [provider]` | Show details & usage plan for a provider |
|
|
195
197
|
| `/variants [on|off|low|medium|high]` | Toggle model thinking/reasoning variants |
|
|
196
198
|
| `/think` | Show hidden thinking from last response |
|
|
197
199
|
| `/output [last|id|list]`| Toggle full saved tool output |
|
|
@@ -46,6 +46,20 @@ export declare class LoopGuard {
|
|
|
46
46
|
* (e.g., command not found → retry → not found → ...).
|
|
47
47
|
*/
|
|
48
48
|
hasRepeatedFailures(threshold?: number): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Count consecutive failures trailing the most recent attempts.
|
|
51
|
+
* Stops at the first success (or the start of the history).
|
|
52
|
+
*/
|
|
53
|
+
consecutiveFailureCount(): number;
|
|
54
|
+
/**
|
|
55
|
+
* Returns a reflection prompt when recent failures suggest the agent may
|
|
56
|
+
* be stuck, or null if everything looks fine.
|
|
57
|
+
*
|
|
58
|
+
* Unlike hardcoded thresholds, this provides context for the MODEL to
|
|
59
|
+
* decide whether to continue (lengthy but progressing approach) or
|
|
60
|
+
* switch/stop (genuinely stuck approach).
|
|
61
|
+
*/
|
|
62
|
+
getFailureReflection(): string | null;
|
|
49
63
|
/**
|
|
50
64
|
* Reset counters for read-only tools. Called after context compaction
|
|
51
65
|
* so the model can re-fetch data whose results were compacted away.
|
package/dist/agent/loop-guard.js
CHANGED
|
@@ -118,6 +118,56 @@ export class LoopGuard {
|
|
|
118
118
|
const recent = this.attempts.slice(-threshold);
|
|
119
119
|
return recent.every((a) => !a.ok);
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Count consecutive failures trailing the most recent attempts.
|
|
123
|
+
* Stops at the first success (or the start of the history).
|
|
124
|
+
*/
|
|
125
|
+
consecutiveFailureCount() {
|
|
126
|
+
let count = 0;
|
|
127
|
+
for (let i = this.attempts.length - 1; i >= 0; i--) {
|
|
128
|
+
if (!this.attempts[i].ok)
|
|
129
|
+
count++;
|
|
130
|
+
else
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
return count;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Returns a reflection prompt when recent failures suggest the agent may
|
|
137
|
+
* be stuck, or null if everything looks fine.
|
|
138
|
+
*
|
|
139
|
+
* Unlike hardcoded thresholds, this provides context for the MODEL to
|
|
140
|
+
* decide whether to continue (lengthy but progressing approach) or
|
|
141
|
+
* switch/stop (genuinely stuck approach).
|
|
142
|
+
*/
|
|
143
|
+
getFailureReflection() {
|
|
144
|
+
const consecutiveFailures = this.consecutiveFailureCount();
|
|
145
|
+
if (consecutiveFailures < 3)
|
|
146
|
+
return null;
|
|
147
|
+
// Build context: what tools failed and what they were trying
|
|
148
|
+
const recentFails = this.attempts.slice(-consecutiveFailures);
|
|
149
|
+
const toolSummary = recentFails
|
|
150
|
+
.map((a) => ` - ${a.callName}: ${a.canonicalSignature.slice(0, 120)}`)
|
|
151
|
+
.slice(-5) // Show last 5 max
|
|
152
|
+
.join("\n");
|
|
153
|
+
const severity = consecutiveFailures >= 6 ? "CRITICAL" : "WARNING";
|
|
154
|
+
return `⚠ APPROACH EVALUATION REQUIRED (${severity}) — ${consecutiveFailures} consecutive tool calls have FAILED.
|
|
155
|
+
|
|
156
|
+
Recent failures:
|
|
157
|
+
${toolSummary}
|
|
158
|
+
|
|
159
|
+
You MUST now pause and evaluate:
|
|
160
|
+
1. Are these failures all related to the SAME approach/method? If yes, this approach may not be viable.
|
|
161
|
+
2. Is there a DIFFERENT approach that could work? (different tool, different protocol, different technique)
|
|
162
|
+
3. Or is this a series of unrelated small issues that are being fixed incrementally?
|
|
163
|
+
|
|
164
|
+
DECIDE one of:
|
|
165
|
+
- CONTINUE: if you're making real progress and each failure teaches you something new
|
|
166
|
+
- SWITCH: describe the new approach before trying it
|
|
167
|
+
- STOP: if you've exhausted viable approaches — tell the user honestly what you tried and why it didn't work
|
|
168
|
+
|
|
169
|
+
Do NOT keep trying variations of the same failing approach without explicitly deciding to SWITCH or STOP first.`;
|
|
170
|
+
}
|
|
121
171
|
/**
|
|
122
172
|
* Reset counters for read-only tools. Called after context compaction
|
|
123
173
|
* so the model can re-fetch data whose results were compacted away.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-guard.js","sourceRoot":"","sources":["../../src/agent/loop-guard.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,SAAS;IACT,SAAS;IACT,WAAW;IACX,SAAS;IACT,aAAa;IACb,YAAY;IACZ,WAAW;IACX,UAAU;CACX,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,OAAO,SAAS;IACZ,QAAQ,GAAkB,EAAE,CAAC;IAC7B,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,gBAAgB,GAAG,IAAI,GAAG,EAAmB,CAAC;IAEtD;;;;OAIG;IACH,YAAY,CAAC,IAAY,EAAE,IAA6B;QACtD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC3C,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACtB,8CAA8C;YAC9C,IAAI,IAAI,KAAK,YAAY,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5E,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC5C,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;QACD,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9C,CAAC;IAED,aAAa,CACX,IAAY,EACZ,IAAY,EACZ,IAA6B,EAC7B,EAAW,EACX,QAA6B;QAE7B,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtE,wEAAwE;QACxE,yEAAyE;QACzE,qEAAqE;QACrE,kBAAkB;QAClB,IAAI,EAAE;YAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;aACxC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAClF,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CACT,IAAY,EACZ,IAA6B;QAE7B,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEhD,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAEzC,2DAA2D;QAC3D,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAEtE,wEAAwE;QACxE,kEAAkE;QAClE,sEAAsE;QACtE,MAAM,OAAO,GACX,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,SAAS,CAAC;QAEvE,oEAAoE;QACpE,0DAA0D;QAC1D,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;YACtB,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,OAAO;oBACb,CAAC,CAAC,GAAG,IAAI,8HAA8H;oBACvI,CAAC,CAAC,GAAG,IAAI,gHAAgH;aAC5H,CAAC;QACJ,CAAC;QAED,qDAAqD;QACrD,OAAO;YACL,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,OAAO;gBACb,CAAC,CAAC,GAAG,IAAI,uBAAuB,KAAK,gJAAgJ;gBACrL,CAAC,CAAC,GAAG,IAAI,uBAAuB,KAAK,0HAA0H;SAClK,CAAC;IACJ,CAAC;IAED,eAAe,CAAC,IAAY,EAAE,IAA6B;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACH,mBAAmB,CAAC,SAAS,GAAG,CAAC;QAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,SAAS;YAAE,OAAO,KAAK,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC;QAC/C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,aAAa;QACX,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACtC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CACxC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"loop-guard.js","sourceRoot":"","sources":["../../src/agent/loop-guard.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface AgentRunOptions {
|
|
|
39
39
|
}) => Promise<string | undefined>) | undefined;
|
|
40
40
|
session?: SessionPolicy | undefined;
|
|
41
41
|
}
|
|
42
|
+
export declare function preprocessJson(raw: string): string;
|
|
42
43
|
export interface ParseToolCallOptions {
|
|
43
44
|
/**
|
|
44
45
|
* When true, only formats that are explicitly tool-call delimited are
|
package/dist/agent/runner.js
CHANGED
|
@@ -44,9 +44,58 @@ export function createSessionPolicy() {
|
|
|
44
44
|
planApproved: { value: false },
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
export function preprocessJson(raw) {
|
|
48
|
+
let inString = false;
|
|
49
|
+
let escaped = false;
|
|
50
|
+
let result = "";
|
|
51
|
+
for (let i = 0; i < raw.length; i++) {
|
|
52
|
+
const char = raw[i];
|
|
53
|
+
if (char === '"' && !escaped) {
|
|
54
|
+
inString = !inString;
|
|
55
|
+
result += char;
|
|
56
|
+
}
|
|
57
|
+
else if (inString) {
|
|
58
|
+
if (char === '\n') {
|
|
59
|
+
result += '\\n';
|
|
60
|
+
}
|
|
61
|
+
else if (char === '\r') {
|
|
62
|
+
result += '\\r';
|
|
63
|
+
}
|
|
64
|
+
else if (char === '\t') {
|
|
65
|
+
result += '\\t';
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
result += char;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (char === ',' && i + 1 < raw.length) {
|
|
73
|
+
let nextNonWs = "";
|
|
74
|
+
for (let j = i + 1; j < raw.length; j++) {
|
|
75
|
+
if (!/\s/.test(raw[j])) {
|
|
76
|
+
nextNonWs = raw[j];
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (nextNonWs === '}' || nextNonWs === ']') {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
result += char;
|
|
85
|
+
}
|
|
86
|
+
if (char === '\\' && inString) {
|
|
87
|
+
escaped = !escaped;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
escaped = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
47
95
|
function tryParseCall(raw) {
|
|
48
96
|
try {
|
|
49
|
-
const
|
|
97
|
+
const preprocessed = preprocessJson(raw);
|
|
98
|
+
const parsed = JSON.parse(preprocessed.trim());
|
|
50
99
|
if (typeof parsed.name === "string" &&
|
|
51
100
|
parsed.args &&
|
|
52
101
|
typeof parsed.args === "object") {
|
|
@@ -78,9 +127,84 @@ function parseKimiToolCall(text) {
|
|
|
78
127
|
const name = match[1];
|
|
79
128
|
return tryParseCall(JSON.stringify({ name, args: tryJson(match[2]) ?? {} }));
|
|
80
129
|
}
|
|
130
|
+
function parseXmlToolCall(text) {
|
|
131
|
+
// Pattern 1:
|
|
132
|
+
// <tool_call>
|
|
133
|
+
// <name>tool.name</name>
|
|
134
|
+
// <args>{...}</args>
|
|
135
|
+
// </tool_call>
|
|
136
|
+
const xmlNameArgs = text.match(/<tool_call>[\s\S]*?<name>\s*([\w.]+?)\s*<\/name>\s*<args>\s*(\{[\s\S]*?\})\s*<\/args>[\s\S]*?<\/tool_call>/i);
|
|
137
|
+
if (xmlNameArgs?.[1] && xmlNameArgs?.[2]) {
|
|
138
|
+
try {
|
|
139
|
+
const args = JSON.parse(preprocessJson(xmlNameArgs[2]));
|
|
140
|
+
return {
|
|
141
|
+
name: xmlNameArgs[1],
|
|
142
|
+
args: args,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch { }
|
|
146
|
+
}
|
|
147
|
+
// Pattern 1b (MiMo alternative):
|
|
148
|
+
// <tool_call>
|
|
149
|
+
// <tool_name>tool.name</tool_name>
|
|
150
|
+
// <parameters>{...}</parameters>
|
|
151
|
+
// </tool_call>
|
|
152
|
+
const xmlToolNameParams = text.match(/<tool_call>[\s\S]*?<tool_name>\s*([\w.]+?)\s*<\/tool_name>\s*<parameters>\s*(\{[\s\S]*?\})\s*<\/parameters>[\s\S]*?<\/tool_call>/i);
|
|
153
|
+
if (xmlToolNameParams?.[1] && xmlToolNameParams?.[2]) {
|
|
154
|
+
try {
|
|
155
|
+
const args = JSON.parse(preprocessJson(xmlToolNameParams[2]));
|
|
156
|
+
return {
|
|
157
|
+
name: xmlToolNameParams[1],
|
|
158
|
+
args: args,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch { }
|
|
162
|
+
}
|
|
163
|
+
// Pattern 1c (MiMo function/parameter format):
|
|
164
|
+
// <tool_call>
|
|
165
|
+
// <function=tool.name>
|
|
166
|
+
// <parameter=name>value</parameter>
|
|
167
|
+
// </function>
|
|
168
|
+
// </tool_call>
|
|
169
|
+
const xmlFunctionBlock = text.match(/<tool_call>[\s\S]*?<function=([\w.]+?)>([\s\S]*?)<\/function>[\s\S]*?<\/tool_call>/i);
|
|
170
|
+
if (xmlFunctionBlock?.[1] && xmlFunctionBlock?.[2]) {
|
|
171
|
+
const name = xmlFunctionBlock[1];
|
|
172
|
+
const inner = xmlFunctionBlock[2];
|
|
173
|
+
const args = {};
|
|
174
|
+
const paramRegex = /<parameter=([\w.]+?)>([\s\S]*?)<\/parameter>/gi;
|
|
175
|
+
let paramMatch;
|
|
176
|
+
while ((paramMatch = paramRegex.exec(inner)) !== null) {
|
|
177
|
+
const paramName = paramMatch[1];
|
|
178
|
+
const paramValueStr = paramMatch[2].trim();
|
|
179
|
+
let paramValue = paramValueStr;
|
|
180
|
+
try {
|
|
181
|
+
if (/^(?:\[|\{|true|false|null|\d+(\.\d+)?$)/i.test(paramValueStr)) {
|
|
182
|
+
paramValue = JSON.parse(preprocessJson(paramValueStr));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch { }
|
|
186
|
+
args[paramName] = paramValue;
|
|
187
|
+
}
|
|
188
|
+
return { name, args };
|
|
189
|
+
}
|
|
190
|
+
// Pattern 2:
|
|
191
|
+
// <tool_call>
|
|
192
|
+
// <tool>
|
|
193
|
+
// {"name": "...", "args": {...}}
|
|
194
|
+
// </tool_call>
|
|
195
|
+
// or simply <tool_call> {"name": "...", "args": {...}} </tool_call>
|
|
196
|
+
const xmlJson = text.match(/<tool_call>[\s\S]*?(?:<tool>)?\s*(\{[\s\S]*?\})\s*<\/tool_call>/i);
|
|
197
|
+
if (xmlJson?.[1]) {
|
|
198
|
+
const call = tryParseCall(xmlJson[1]);
|
|
199
|
+
if (call)
|
|
200
|
+
return call;
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
81
204
|
function tryJson(raw) {
|
|
82
205
|
try {
|
|
83
|
-
const
|
|
206
|
+
const preprocessed = preprocessJson(raw);
|
|
207
|
+
const parsed = JSON.parse(preprocessed);
|
|
84
208
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
85
209
|
return parsed;
|
|
86
210
|
}
|
|
@@ -110,13 +234,10 @@ export function parseToolCall(text, options = {}) {
|
|
|
110
234
|
if (call)
|
|
111
235
|
return call;
|
|
112
236
|
}
|
|
113
|
-
// 2. <tool_call>...</tool_call>
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
if (call)
|
|
118
|
-
return call;
|
|
119
|
-
}
|
|
237
|
+
// 2. <tool_call>...</tool_call> (XML formats)
|
|
238
|
+
const xmlCall = parseXmlToolCall(text);
|
|
239
|
+
if (xmlCall)
|
|
240
|
+
return xmlCall;
|
|
120
241
|
// 3. Kimi/Moonshot sentinel format (used by kimi-k2 family on NIM).
|
|
121
242
|
const kimi = parseKimiToolCall(text);
|
|
122
243
|
if (kimi)
|
|
@@ -437,7 +558,7 @@ export function parseAllToolCalls(text) {
|
|
|
437
558
|
}
|
|
438
559
|
const xmlRe = /<tool_call>([\s\S]*?)<\/tool_call>/gi;
|
|
439
560
|
while ((m = xmlRe.exec(text)) !== null) {
|
|
440
|
-
const call =
|
|
561
|
+
const call = parseXmlToolCall(m[0]);
|
|
441
562
|
if (call)
|
|
442
563
|
found.push({ index: m.index, call });
|
|
443
564
|
}
|
|
@@ -481,16 +602,16 @@ export function collapseRepeatedText(text) {
|
|
|
481
602
|
/** Extract the text before the tool call block for display purposes */
|
|
482
603
|
function textBeforeToolCall(text) {
|
|
483
604
|
const patterns = [
|
|
484
|
-
/```tool\s*\n?[\s\S]
|
|
485
|
-
/<tool_call>[\s\S]
|
|
605
|
+
/```tool\s*\n?[\s\S]*$/i,
|
|
606
|
+
/<tool_call>[\s\S]*$/i,
|
|
486
607
|
// Kimi/Moonshot sentinel block — strip from the section opener
|
|
487
608
|
// (or the first call opener if the section header is missing).
|
|
488
609
|
/<\|tool_calls_section_begin\|>[\s\S]*$/i,
|
|
489
610
|
/<\|tool_call_begin\|>[\s\S]*$/i,
|
|
490
|
-
/#{1,3}\s*tool\s*\n\s*\{[\s\S]
|
|
491
|
-
/\*\*tool\*\*\s*\n\s*\{[\s\S]
|
|
492
|
-
/```\w*\s*\n?\{[\s\S]*?"name"[\s\S]
|
|
493
|
-
/\{"name"\s*:\s*"[^"]+"\s*,\s*"args"\s*:\s*\{[\s\S]
|
|
611
|
+
/#{1,3}\s*tool\s*\n\s*\{[\s\S]*$/i,
|
|
612
|
+
/\*\*tool\*\*\s*\n\s*\{[\s\S]*$/i,
|
|
613
|
+
/```\w*\s*\n?\{[\s\S]*?"name"[\s\S]*$/i,
|
|
614
|
+
/\{"name"\s*:\s*"[^"]+"\s*,\s*"args"\s*:\s*\{[\s\S]*$/i,
|
|
494
615
|
];
|
|
495
616
|
for (const pattern of patterns) {
|
|
496
617
|
const idx = text.search(pattern);
|
|
@@ -638,7 +759,7 @@ export function looksLikeBuildTask(prompt, history) {
|
|
|
638
759
|
}
|
|
639
760
|
// Matrix of action-verb narration: the model says it is *about to* do
|
|
640
761
|
// something but hasn't. Used to detect "narrate, don't act" stalls.
|
|
641
|
-
const ACTION_NARRATION_RE = /\b(?:let me|let's|i'?ll|i will|i'?m going to|i am going to|i need to|i should|i'?m about to|going to|now i'?ll|first[,]?\s*i'?ll)\s+(?:now\s+|first\s+|quickly\s+|just\s+|go\s+ahead\s+and\s+)?(?:explore|list|read|fetch|browse|check|inspect|examine|look|create|run|start|write|build|add|scaffold|set\s*up|setup|install|initialize|init|generate|make|review|open|find|search|verify|update|edit|modify|fix|implement)\b/i;
|
|
762
|
+
const ACTION_NARRATION_RE = /\b(?:let me|let's|i'?ll|i will|i'?m going to|i am going to|i need to|i should|i'?m about to|going to|now i'?ll|first[,]?\s*i'?ll)\s+(?:now\s+|first\s+|quickly\s+|just\s+|go\s+ahead\s+and\s+)?(?:explore|list|read|fetch|browse|check|inspect|examine|look|create|run|start|write|build|add|scaffold|set\s*up|setup|install|initialize|init|generate|make|review|open|find|search|verify|update|edit|modify|fix|implement|gather|assess|scan|audit)\b/i;
|
|
642
763
|
/**
|
|
643
764
|
* Detect a message that narrates an *upcoming* action ("let me explore the
|
|
644
765
|
* directory", "I'll create the components") rather than an actual answer or
|
|
@@ -1567,6 +1688,12 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1567
1688
|
artifactPath: "",
|
|
1568
1689
|
};
|
|
1569
1690
|
jobManager.registerJob(jobId, backgroundJob, toolAc);
|
|
1691
|
+
const TOOL_STALL_WARNING_MS = 60_000; // 1 minute
|
|
1692
|
+
const stallTimer = setTimeout(() => {
|
|
1693
|
+
if (!toolAc.signal.aborted) {
|
|
1694
|
+
writeNotice("info", `${call.name} has been running for >60s — still waiting (ESC to abort)`, chalk.yellow(` ⏳ ${call.name} still running — ESC to abort\n`));
|
|
1695
|
+
}
|
|
1696
|
+
}, TOOL_STALL_WARNING_MS);
|
|
1570
1697
|
try {
|
|
1571
1698
|
result = await runToolCall(call, {
|
|
1572
1699
|
signal: toolAc.signal,
|
|
@@ -1577,6 +1704,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1577
1704
|
printLive(chunk);
|
|
1578
1705
|
},
|
|
1579
1706
|
confirmed: true,
|
|
1707
|
+
userPrompt: prompt,
|
|
1580
1708
|
});
|
|
1581
1709
|
if (liveBytes > 0 || liveTruncatedNotified) {
|
|
1582
1710
|
writeToolOutput(toolEventId, "\n", "\n");
|
|
@@ -1593,6 +1721,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1593
1721
|
result = { ok: false, output: `Tool error: ${errMsg}`, exitCode: 1 };
|
|
1594
1722
|
}
|
|
1595
1723
|
finally {
|
|
1724
|
+
clearTimeout(stallTimer);
|
|
1596
1725
|
parentSignal.removeEventListener("abort", onParentAbort);
|
|
1597
1726
|
}
|
|
1598
1727
|
const output = result.output.trim();
|
|
@@ -1622,6 +1751,18 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1622
1751
|
output: result.output.slice(0, 4_000),
|
|
1623
1752
|
});
|
|
1624
1753
|
loopGuard.recordAttempt(step, call.name, call.args, result.ok, result.exitCode);
|
|
1754
|
+
// Inject approach evaluation when consecutive failures are detected.
|
|
1755
|
+
// Lets the MODEL decide (with full context) whether to continue a
|
|
1756
|
+
// legitimately long approach, switch, or stop — instead of a
|
|
1757
|
+
// hardcoded kill threshold.
|
|
1758
|
+
if (!result.ok) {
|
|
1759
|
+
const reflection = loopGuard.getFailureReflection();
|
|
1760
|
+
if (reflection) {
|
|
1761
|
+
messages.push({ role: "system", content: reflection });
|
|
1762
|
+
const failCount = loopGuard.consecutiveFailureCount();
|
|
1763
|
+
writeNotice("warn", `${failCount} consecutive failures — model evaluating approach`, chalk.yellow(` ⚠ ${failCount} consecutive failures — evaluating approach\n`));
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1625
1766
|
const statusIcon = result.ok ? chalk.green(" ✓") : chalk.red(" ✗");
|
|
1626
1767
|
writeToolOutput(toolEventId, result.ok ? "ok\n" : "failed\n", statusIcon + "\n");
|
|
1627
1768
|
if (output) {
|
|
@@ -1824,16 +1965,24 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1824
1965
|
writeNotice("info", "recovered tool call from thinking content", chalk.dim(" ℹ recovered tool call from thinking content\n"));
|
|
1825
1966
|
}
|
|
1826
1967
|
}
|
|
1827
|
-
// ──
|
|
1828
|
-
// Some models
|
|
1829
|
-
//
|
|
1830
|
-
//
|
|
1831
|
-
//
|
|
1832
|
-
|
|
1968
|
+
// ── Empty-response recovery ───────────────────────────────────────
|
|
1969
|
+
// Some models occasionally return an empty completion: a reasoning
|
|
1970
|
+
// model that spent its whole budget on hidden <think> reasoning and emitted
|
|
1971
|
+
// no visible text, OR (more perniciously) a gateway hiccup that
|
|
1972
|
+
// streamed [DONE] with no content deltas at all. Without this guard
|
|
1973
|
+
// the agent silently ends the turn with no answer, no warning, and no
|
|
1974
|
+
// error — the user just sees the spinner stop. Catch BOTH cases
|
|
1975
|
+
// (thinking-only AND truly empty) and nudge the model to retry.
|
|
1976
|
+
if (!assistantText.visible.trim() && !call) {
|
|
1833
1977
|
emptyVisibleRetries += 1;
|
|
1834
1978
|
if (emptyVisibleRetries <= 3) {
|
|
1835
|
-
|
|
1836
|
-
|
|
1979
|
+
if (assistantText.hasThinking) {
|
|
1980
|
+
writeThinkingBlock(assistantText.thinkContent);
|
|
1981
|
+
writeNotice("warn", "model produced only thinking — nudging it to take action", chalk.yellow(" ⚠ model produced only thinking — nudging it to take action\n"));
|
|
1982
|
+
}
|
|
1983
|
+
else {
|
|
1984
|
+
writeNotice("warn", "model returned an empty response — nudging it to answer", chalk.yellow(" ⚠ model returned an empty response — nudging it to answer\n"));
|
|
1985
|
+
}
|
|
1837
1986
|
messages.push({
|
|
1838
1987
|
role: "assistant",
|
|
1839
1988
|
content: collapseRepeatedText(completion.text),
|
|
@@ -1847,8 +1996,10 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1847
1996
|
messages.push(recoveryUserMessage(buildNudge));
|
|
1848
1997
|
continue;
|
|
1849
1998
|
}
|
|
1850
|
-
// Exhausted retries —
|
|
1851
|
-
// which
|
|
1999
|
+
// Exhausted retries — surface a clear notice instead of ending the
|
|
2000
|
+
// turn silently with no answer (which left the user staring at a
|
|
2001
|
+
// stopped spinner with no clue what happened).
|
|
2002
|
+
writeNotice("warn", "model returned an empty response after retries — no answer produced", chalk.yellow(" ⚠ model returned an empty response after retries — no answer produced\n"));
|
|
1852
2003
|
}
|
|
1853
2004
|
else {
|
|
1854
2005
|
// Reset the counter on any successful visible output or recovered call.
|
|
@@ -1979,6 +2130,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1979
2130
|
// Nudge it to emit a real tool call, with a concrete example.
|
|
1980
2131
|
const narratedAction = looksLikeActionNarration(cleaned);
|
|
1981
2132
|
const wantsAction = buildLikeTurn ||
|
|
2133
|
+
pentestLikeTurn ||
|
|
1982
2134
|
(activePlan && session.planApproved.value) ||
|
|
1983
2135
|
freshWebSearchRequired ||
|
|
1984
2136
|
narratedAction;
|
|
@@ -1996,6 +2148,13 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1996
2148
|
"You wrote a message but emitted NO ```tool block, so NOTHING ran. Do NOT narrate what you will do — DO it. Emit the next tool call now (task.update / fs.writeMany / shell.exec) in a single ```tool block.";
|
|
1997
2149
|
writeNotice("warn", "described an action but emitted no tool call — nudging it to run one", chalk.yellow(" ⚠ described an action but emitted no tool call — nudging it to run one\n"));
|
|
1998
2150
|
}
|
|
2151
|
+
else if (pentestLikeTurn) {
|
|
2152
|
+
nudge =
|
|
2153
|
+
"You described what you will do but emitted NO ```tool block, so NOTHING actually happened — narration is not action. Emit a real tool call NOW (e.g. net.scan / sysinfo / shell.exec). For example, to scan local network or read system settings:\n" +
|
|
2154
|
+
'```tool\n{"name":"sysinfo","args":{}}\n```\n' +
|
|
2155
|
+
"Every turn MUST contain a ```tool block until the task is done.";
|
|
2156
|
+
writeNotice("warn", "described a security/pentest action but emitted no tool call — nudging it to run one", chalk.yellow(" ⚠ described a security/pentest action but emitted no tool call — nudging it to run one\n"));
|
|
2157
|
+
}
|
|
1999
2158
|
else if (!buildLikeTurn) {
|
|
2000
2159
|
nudge =
|
|
2001
2160
|
"You wrote that you would fetch/search/read something but emitted NO ```tool block, so NOTHING ran. Do NOT narrate the next browsing step — DO it. Emit exactly one valid ```tool block now. If you know the exact page, use:\n" +
|
|
@@ -2107,14 +2266,16 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
2107
2266
|
if (assistantText.hasThinking) {
|
|
2108
2267
|
writeThinkingBlock(assistantText.thinkContent);
|
|
2109
2268
|
}
|
|
2110
|
-
messages.push({
|
|
2111
|
-
role: "assistant",
|
|
2112
|
-
content: collapseRepeatedText(assistantText.visible),
|
|
2113
|
-
});
|
|
2114
2269
|
let allCalls = parseAllToolCalls(assistantText.visible || assistantText.thinkContent);
|
|
2115
2270
|
if (allCalls.length === 0 && call) {
|
|
2116
2271
|
allCalls = [call];
|
|
2117
2272
|
}
|
|
2273
|
+
const standardizedContent = (beforeTool ? beforeTool.trim() + "\n\n" : "") +
|
|
2274
|
+
allCalls.map(c => `\`\`\`tool\n${JSON.stringify(c)}\n\`\`\``).join("\n\n");
|
|
2275
|
+
messages.push({
|
|
2276
|
+
role: "assistant",
|
|
2277
|
+
content: standardizedContent,
|
|
2278
|
+
});
|
|
2118
2279
|
if (allCalls.length > 1) {
|
|
2119
2280
|
writeNotice("info", `${allCalls.length} tool calls in this message — running them in parallel`, chalk.dim(` ℹ ${allCalls.length} tool calls in this message — running them in parallel\n`));
|
|
2120
2281
|
}
|