@pentoshi/clai 2.0.28 → 2.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/confirm-port.d.ts +30 -0
- package/dist/agent/confirm-port.js +89 -0
- package/dist/agent/confirm-port.js.map +1 -0
- package/dist/agent/plan-tool.d.ts +26 -0
- package/dist/agent/plan-tool.js +188 -0
- package/dist/agent/plan-tool.js.map +1 -0
- package/dist/agent/runner.d.ts +6 -174
- package/dist/agent/runner.js +25 -1606
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session-policy.d.ts +19 -0
- package/dist/agent/session-policy.js +40 -0
- package/dist/agent/session-policy.js.map +1 -0
- package/dist/agent/stop-summary.d.ts +8 -0
- package/dist/agent/stop-summary.js +53 -0
- package/dist/agent/stop-summary.js.map +1 -0
- package/dist/agent/tool-call-parser.d.ts +155 -0
- package/dist/agent/tool-call-parser.js +1107 -0
- package/dist/agent/tool-call-parser.js.map +1 -0
- package/dist/agent/tool-output-formatting.d.ts +7 -0
- package/dist/agent/tool-output-formatting.js +127 -0
- package/dist/agent/tool-output-formatting.js.map +1 -0
- package/dist/commands/update.js +1 -1
- package/dist/prompts/index.d.ts +2 -2
- package/dist/prompts/index.js +103 -59
- package/dist/prompts/index.js.map +1 -1
- package/dist/repl/prompt-line.d.ts +19 -0
- package/dist/repl/prompt-line.js +555 -0
- package/dist/repl/prompt-line.js.map +1 -0
- package/dist/repl/slash-commands.d.ts +24 -0
- package/dist/repl/slash-commands.js +317 -0
- package/dist/repl/slash-commands.js.map +1 -0
- package/dist/repl.d.ts +2 -25
- package/dist/repl.js +41 -898
- package/dist/repl.js.map +1 -1
- package/dist/safety/classifier.js +1 -1
- package/dist/safety/classifier.js.map +1 -1
- package/dist/store/keys.js +0 -4
- package/dist/store/keys.js.map +1 -1
- package/dist/store/plan.js +1 -1
- package/dist/store/plan.js.map +1 -1
- package/dist/tools/nmap-runner.d.ts +19 -0
- package/dist/tools/nmap-runner.js +177 -0
- package/dist/tools/nmap-runner.js.map +1 -0
- package/dist/tools/package-binary.d.ts +1 -0
- package/dist/tools/package-binary.js +30 -0
- package/dist/tools/package-binary.js.map +1 -0
- package/dist/tools/pdf.js +2 -2
- package/dist/tools/pdf.js.map +1 -1
- package/dist/tools/registry.d.ts +2 -11
- package/dist/tools/registry.js +3 -203
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/tool-types.d.ts +12 -0
- package/dist/tools/tool-types.js +2 -0
- package/dist/tools/tool-types.js.map +1 -0
- package/dist/tools/web/audit.d.ts +7 -29
- package/dist/tools/web/audit.js +7 -29
- package/dist/tools/web/audit.js.map +1 -1
- package/dist/tools/web/budget.d.ts +5 -28
- package/dist/tools/web/budget.js +5 -28
- package/dist/tools/web/budget.js.map +1 -1
- package/dist/tools/web/capture.d.ts +8 -35
- package/dist/tools/web/capture.js +8 -35
- package/dist/tools/web/capture.js.map +1 -1
- package/dist/tools/web/fetch-core.d.ts +6 -26
- package/dist/tools/web/fetch-core.js +6 -26
- package/dist/tools/web/fetch-core.js.map +1 -1
- package/dist/tools/web/readable.d.ts +7 -13
- package/dist/tools/web/readable.js +7 -13
- package/dist/tools/web/readable.js.map +1 -1
- package/dist/tools/web/search.d.ts +7 -20
- package/dist/tools/web/search.js +7 -20
- package/dist/tools/web/search.js.map +1 -1
- package/dist/tools/web/ssrf-guard.d.ts +8 -27
- package/dist/tools/web/ssrf-guard.js +8 -27
- package/dist/tools/web/ssrf-guard.js.map +1 -1
- package/dist/tui/App.js +19 -88
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/key-intent.d.ts +36 -0
- package/dist/tui/key-intent.js +54 -0
- package/dist/tui/key-intent.js.map +1 -0
- package/dist/tui/render-lines.js +125 -37
- package/dist/tui/render-lines.js.map +1 -1
- package/dist/tui/state.js +2 -2
- package/dist/tui/state.js.map +1 -1
- package/dist/tui/text-format.d.ts +11 -0
- package/dist/tui/text-format.js +85 -0
- package/dist/tui/text-format.js.map +1 -0
- package/dist/ui/ansi-box.d.ts +7 -0
- package/dist/ui/ansi-box.js +18 -0
- package/dist/ui/ansi-box.js.map +1 -0
- package/dist/ui/banner.d.ts +0 -9
- package/dist/ui/banner.js +1 -43
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/intro-card.d.ts +10 -0
- package/dist/ui/intro-card.js +39 -0
- package/dist/ui/intro-card.js.map +1 -0
- package/dist/ui/markdown.js +1 -1
- package/dist/ui/markdown.js.map +1 -1
- package/dist/ui/output-pane.js.map +1 -1
- package/dist/ui/spinner.js +6 -17
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/wordmark.d.ts +7 -0
- package/dist/ui/wordmark.js +42 -0
- package/dist/ui/wordmark.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ToolCall } from "../types.js";
|
|
2
|
+
import type { SessionPolicy } from "./session-policy.js";
|
|
3
|
+
export interface ConfirmPort {
|
|
4
|
+
confirmTool(call: ToolCall): Promise<boolean>;
|
|
5
|
+
confirmPentest(): Promise<boolean>;
|
|
6
|
+
/** Ask the user whether to continue after hitting the step budget. */
|
|
7
|
+
confirmContinue?(steps: number): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* Ask whether to leave ask mode and run an action task in agent mode.
|
|
10
|
+
* Optional so existing ports keep working; ask-mode handoff falls back to a
|
|
11
|
+
* default "no" when a port doesn't implement it.
|
|
12
|
+
*/
|
|
13
|
+
confirmAgentSwitch?(info: {
|
|
14
|
+
reason: string;
|
|
15
|
+
tools: string[];
|
|
16
|
+
}): Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Re-assert raw mode AND resume stdin after an inquirer prompt
|
|
20
|
+
* (confirm/password). inquirer's readline interface pauses stdin and
|
|
21
|
+
* switches it to cooked mode when it closes; if we only flip raw mode back
|
|
22
|
+
* on but leave stdin paused, no `keypress`/`data` events flow to the REPL's
|
|
23
|
+
* ESC/Ctrl+C abort handler — so a long-running tool launched right after a
|
|
24
|
+
* confirmation can no longer be aborted (the user had to kill the terminal).
|
|
25
|
+
* Calling resume() restores the event flow.
|
|
26
|
+
*/
|
|
27
|
+
export declare function restoreInteractiveStdin(): void;
|
|
28
|
+
export declare const inquirerConfirmPort: ConfirmPort;
|
|
29
|
+
export declare function ensurePentestAuthorization(call: ToolCall, autoConfirm: boolean, session: SessionPolicy, confirmPort: ConfirmPort): Promise<boolean>;
|
|
30
|
+
export declare function confirmToolExecution(call: ToolCall, autoConfirm: boolean, session: SessionPolicy, confirmPort: ConfirmPort): Promise<boolean>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getConfig } from "../store/config.js";
|
|
4
|
+
import { isPentestToolCall } from "../safety/classifier.js";
|
|
5
|
+
import { formatToolArgs } from "./tool-call-parser.js";
|
|
6
|
+
/**
|
|
7
|
+
* Re-assert raw mode AND resume stdin after an inquirer prompt
|
|
8
|
+
* (confirm/password). inquirer's readline interface pauses stdin and
|
|
9
|
+
* switches it to cooked mode when it closes; if we only flip raw mode back
|
|
10
|
+
* on but leave stdin paused, no `keypress`/`data` events flow to the REPL's
|
|
11
|
+
* ESC/Ctrl+C abort handler — so a long-running tool launched right after a
|
|
12
|
+
* confirmation can no longer be aborted (the user had to kill the terminal).
|
|
13
|
+
* Calling resume() restores the event flow.
|
|
14
|
+
*/
|
|
15
|
+
export function restoreInteractiveStdin() {
|
|
16
|
+
if (!process.stdin.isTTY)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
if (!process.stdin.isRaw) {
|
|
20
|
+
process.stdin.setRawMode(true);
|
|
21
|
+
}
|
|
22
|
+
process.stdin.resume();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* ignore */
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const inquirerConfirmPort = {
|
|
29
|
+
async confirmTool(call) {
|
|
30
|
+
return confirm({
|
|
31
|
+
message: chalk.yellow(` run ${call.name}: ${formatToolArgs(call)}?`),
|
|
32
|
+
default: true,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
async confirmPentest() {
|
|
36
|
+
return confirm({
|
|
37
|
+
message: chalk.red("clai only assists with security testing on systems you own or have written permission to test. Confirm for this session?"),
|
|
38
|
+
default: false,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
async confirmContinue(steps) {
|
|
42
|
+
return confirm({
|
|
43
|
+
message: chalk.yellow(` ${steps} steps reached — continue?`),
|
|
44
|
+
default: true,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
async confirmAgentSwitch(info) {
|
|
48
|
+
const tools = info.tools.length > 0 ? ` (${info.tools.join(", ")})` : "";
|
|
49
|
+
return confirm({
|
|
50
|
+
message: chalk.yellow(` this needs agent mode${tools} — switch and run it?`),
|
|
51
|
+
default: true,
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export async function ensurePentestAuthorization(call, autoConfirm, session, confirmPort) {
|
|
56
|
+
if (!isPentestToolCall(call))
|
|
57
|
+
return true;
|
|
58
|
+
// Persistent auth (via `clai authorize-pentest AGREE`) wins.
|
|
59
|
+
if (getConfig().pentestAuthorized)
|
|
60
|
+
return true;
|
|
61
|
+
// Session auth flipped earlier in this session — no re-prompt.
|
|
62
|
+
if (session.pentestAuthorized.value)
|
|
63
|
+
return true;
|
|
64
|
+
if (autoConfirm) {
|
|
65
|
+
// -y is session-scoped only. We do NOT touch the persistent config so
|
|
66
|
+
// a one-shot `-y` cannot silently authorize later interactive runs.
|
|
67
|
+
session.pentestAuthorized.value = true;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const ok = await confirmPort.confirmPentest();
|
|
71
|
+
if (!ok)
|
|
72
|
+
return false;
|
|
73
|
+
session.pentestAuthorized.value = true;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
export async function confirmToolExecution(call, autoConfirm, session, confirmPort) {
|
|
77
|
+
const config = getConfig();
|
|
78
|
+
if (autoConfirm)
|
|
79
|
+
return true;
|
|
80
|
+
if (session.allow.has(call.name))
|
|
81
|
+
return true;
|
|
82
|
+
// Persistent allowlist kept for backwards compat with users who set it
|
|
83
|
+
// through `clai config` directly, but `/allow` only mutates the session
|
|
84
|
+
// set so authorizations never leak across processes.
|
|
85
|
+
if (config.allowAlwaysTools.includes(call.name))
|
|
86
|
+
return true;
|
|
87
|
+
return confirmPort.confirmTool(call);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=confirm-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-port.js","sourceRoot":"","sources":["../../src/agent/confirm-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type SessionPlan } from "../store/plan.js";
|
|
2
|
+
import type { LoopGuard } from "./loop-guard.js";
|
|
3
|
+
import type { SessionPolicy } from "./session-policy.js";
|
|
4
|
+
import type { ToolCall } from "../types.js";
|
|
5
|
+
/** Render the plan as a right-side pane on wide terminals, else inline. */
|
|
6
|
+
export declare function renderPlanForTerminal(plan: SessionPlan): string;
|
|
7
|
+
export interface PlanToolResult {
|
|
8
|
+
handled: boolean;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
plan?: SessionPlan | undefined;
|
|
11
|
+
/** What to print to the user's terminal. */
|
|
12
|
+
display: string;
|
|
13
|
+
/** What to feed back to the model as the tool result. */
|
|
14
|
+
modelNote: string;
|
|
15
|
+
}
|
|
16
|
+
/** Build the system-context block describing the session's active plan. */
|
|
17
|
+
export declare function planContextMessage(plan: SessionPlan, approved: boolean): string;
|
|
18
|
+
/**
|
|
19
|
+
* Handle plan.create / task.update inline. These are session-scoped and
|
|
20
|
+
* persisted via the plan store so the user can view the plan (Ctrl+P) and
|
|
21
|
+
* the agent keeps it in context across the whole session.
|
|
22
|
+
*/
|
|
23
|
+
export declare function handlePlanTool(call: ToolCall, session: SessionPolicy, ctx: {
|
|
24
|
+
loopGuard: LoopGuard;
|
|
25
|
+
step: number;
|
|
26
|
+
}): Promise<PlanToolResult>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { createPlan, loadPlan, savePlan, markTask, } from "../store/plan.js";
|
|
3
|
+
import { renderPlanChecklist, renderPlanSidePane } from "../ui/plan-pane.js";
|
|
4
|
+
import { isLumpedSingleTask } from "./tool-call-parser.js";
|
|
5
|
+
/** Render the plan as a right-side pane on wide terminals, else inline. */
|
|
6
|
+
export function renderPlanForTerminal(plan) {
|
|
7
|
+
const cols = process.stdout.columns ?? 0;
|
|
8
|
+
const side = process.stdout.isTTY
|
|
9
|
+
? renderPlanSidePane(plan, cols)
|
|
10
|
+
: undefined;
|
|
11
|
+
return side ?? renderPlanChecklist(plan);
|
|
12
|
+
}
|
|
13
|
+
/** Build the system-context block describing the session's active plan. */
|
|
14
|
+
export function planContextMessage(plan, approved) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
lines.push(`ACTIVE PLAN for this session (goal: ${plan.goal}, status: ${plan.status}):`);
|
|
17
|
+
if (plan.detail.trim())
|
|
18
|
+
lines.push(plan.detail.trim());
|
|
19
|
+
lines.push("Tasks:");
|
|
20
|
+
plan.tasks.forEach((t, i) => {
|
|
21
|
+
lines.push(` ${i + 1}. [${t.id}] (${t.state}) ${t.title}`);
|
|
22
|
+
});
|
|
23
|
+
if (approved) {
|
|
24
|
+
const firstPending = plan.tasks.find((t) => t.state === "pending");
|
|
25
|
+
lines.push("The user APPROVED this plan. Execute it task by task NOW.");
|
|
26
|
+
if (firstPending) {
|
|
27
|
+
lines.push(`START WITH TASK ${firstPending.id} (${firstPending.title}). ` +
|
|
28
|
+
"Do NOT re-do tasks already marked done, and do NOT skip ahead to later tasks.");
|
|
29
|
+
}
|
|
30
|
+
lines.push("STRICT ORDER: call task.update {taskId, state:'in_progress'} → do the real work → " +
|
|
31
|
+
"call task.update {taskId, state:'done'} ONLY after the tool calls actually succeed. " +
|
|
32
|
+
"If a tool fails, mark the task 'failed' with a note, fix the problem, then retry. " +
|
|
33
|
+
"Do NOT mark a task done when its commands error out. " +
|
|
34
|
+
"Never claim something ran without a successful tool call.");
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push("This plan is NOT yet approved, so you MUST NOT execute any of its tasks yet. " +
|
|
38
|
+
"Any new free-text message from the user right now is a PLAN REVISION, not approval — even if it " +
|
|
39
|
+
"sounds like an instruction (e.g. 'do not install new tools', 'use only X', 'also add Y', 'skip task 2'). " +
|
|
40
|
+
"Treat it as feedback: call plan.create AGAIN with the revised goal/detail/tasks to produce an updated " +
|
|
41
|
+
"plan, then STOP and wait. Do NOT call shell.exec, pkg.install, net.scan, tool.check, fs.write, or any " +
|
|
42
|
+
"other execution tool. The user will APPROVE with /implement, or CANCEL with /discard. Only after " +
|
|
43
|
+
"/implement may you begin executing.");
|
|
44
|
+
}
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Handle plan.create / task.update inline. These are session-scoped and
|
|
49
|
+
* persisted via the plan store so the user can view the plan (Ctrl+P) and
|
|
50
|
+
* the agent keeps it in context across the whole session.
|
|
51
|
+
*/
|
|
52
|
+
export async function handlePlanTool(call, session, ctx) {
|
|
53
|
+
void ctx;
|
|
54
|
+
if (call.name === "plan.create") {
|
|
55
|
+
const goal = typeof call.args.goal === "string" ? call.args.goal : "";
|
|
56
|
+
const detail = typeof call.args.detail === "string" ? call.args.detail : "";
|
|
57
|
+
const kind = typeof call.args.kind === "string" ? call.args.kind : "general";
|
|
58
|
+
const rawTasks = Array.isArray(call.args.tasks) ? call.args.tasks : [];
|
|
59
|
+
const taskTitles = rawTasks
|
|
60
|
+
.map((t) => (typeof t === "string" ? t : ""))
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
if (!goal || taskTitles.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
handled: true,
|
|
65
|
+
ok: false,
|
|
66
|
+
display: chalk.red(" ✗ plan.create needs a non-empty goal and at least one task title\n"),
|
|
67
|
+
modelNote: "plan.create failed: provide a string goal and a non-empty tasks array of step titles.",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Reject a low-quality "everything in one step" plan. A single task that
|
|
71
|
+
// itself enumerates many files/actions (commas, "and", slashes) is a sign
|
|
72
|
+
// the model lumped the whole build into one checkbox — split it so the
|
|
73
|
+
// user gets a real, trackable checklist and the executor works step by step.
|
|
74
|
+
if (isLumpedSingleTask(taskTitles)) {
|
|
75
|
+
return {
|
|
76
|
+
handled: true,
|
|
77
|
+
ok: false,
|
|
78
|
+
display: chalk.red(" ✗ plan.create: that single task lumps the whole build into one step\n"),
|
|
79
|
+
modelNote: "plan.create rejected: you put everything into ONE task. Break it into 3-8 SEPARATE, " +
|
|
80
|
+
"ordered tasks — each a distinct action, e.g. 'scaffold package.json + vite config', " +
|
|
81
|
+
"'create index.html + entry (main.jsx)', 'build App + Post components', 'add posts data + styles', " +
|
|
82
|
+
"'install deps and run dev server to verify'. Call plan.create again with that tasks array.",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const plan = createPlan({
|
|
86
|
+
sessionId: session.sessionId,
|
|
87
|
+
goal,
|
|
88
|
+
detail,
|
|
89
|
+
taskTitles,
|
|
90
|
+
kind,
|
|
91
|
+
});
|
|
92
|
+
await savePlan(plan).catch(() => undefined);
|
|
93
|
+
// A freshly (re)created plan resets approval — the user must /implement.
|
|
94
|
+
session.planApproved.value = false;
|
|
95
|
+
const checklist = renderPlanForTerminal(plan);
|
|
96
|
+
const display = chalk.cyan(" ● planning\n") +
|
|
97
|
+
checklist +
|
|
98
|
+
"\n" +
|
|
99
|
+
chalk.dim(" ✦ plan created — press Ctrl+P to view it, /implement to approve and run it,\n" +
|
|
100
|
+
" or /discard to cancel it. Any other message refines this plan.\n");
|
|
101
|
+
return {
|
|
102
|
+
handled: true,
|
|
103
|
+
ok: true,
|
|
104
|
+
plan,
|
|
105
|
+
display,
|
|
106
|
+
modelNote: `Plan saved with ${plan.tasks.length} task(s). STOP here and wait — produce NO other tool calls now. ` +
|
|
107
|
+
"Do NOT start executing tasks until the user approves with /implement. " +
|
|
108
|
+
"If the user's next message gives feedback instead of /implement, that is a REVISION: call plan.create " +
|
|
109
|
+
"again with the updated plan and STOP again. The user may cancel the whole plan with /discard. " +
|
|
110
|
+
"Only after /implement do you begin, working task by task, calling task.update to mark each " +
|
|
111
|
+
"in_progress before and done after you finish it.",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// task.update
|
|
115
|
+
const plan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
116
|
+
if (!plan) {
|
|
117
|
+
return {
|
|
118
|
+
handled: true,
|
|
119
|
+
ok: false,
|
|
120
|
+
display: chalk.red(" ✗ task.update: no active plan — call plan.create first\n"),
|
|
121
|
+
modelNote: "task.update failed: there is no active plan. Call plan.create first.",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const taskId = typeof call.args.taskId === "string" ? call.args.taskId : "";
|
|
125
|
+
const stateRaw = typeof call.args.state === "string" ? call.args.state : "";
|
|
126
|
+
const note = typeof call.args.note === "string" ? call.args.note : undefined;
|
|
127
|
+
const validStates = [
|
|
128
|
+
"pending",
|
|
129
|
+
"in_progress",
|
|
130
|
+
"done",
|
|
131
|
+
"failed",
|
|
132
|
+
"skipped",
|
|
133
|
+
];
|
|
134
|
+
if (!validStates.includes(stateRaw)) {
|
|
135
|
+
return {
|
|
136
|
+
handled: true,
|
|
137
|
+
ok: false,
|
|
138
|
+
display: chalk.red(` ✗ task.update: state must be one of ${validStates.join(", ")}\n`),
|
|
139
|
+
modelNote: `task.update failed: state must be one of ${validStates.join(", ")}.`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Only one task may be in_progress at a time. This forces genuine
|
|
143
|
+
// task-by-task execution: the model must close (done/failed/skipped) the
|
|
144
|
+
// current task before opening the next one, instead of leaving a task
|
|
145
|
+
// "in_progress" as an umbrella while it quietly works through the rest
|
|
146
|
+
// of the plan underneath it.
|
|
147
|
+
if (stateRaw === "in_progress") {
|
|
148
|
+
const otherInProgress = plan.tasks.find((t) => t.id !== taskId && t.state === "in_progress");
|
|
149
|
+
if (otherInProgress) {
|
|
150
|
+
return {
|
|
151
|
+
handled: true,
|
|
152
|
+
ok: false,
|
|
153
|
+
display: chalk.red(` \u2717 task.update: task [${otherInProgress.id}] "${otherInProgress.title}" is still in_progress\n`),
|
|
154
|
+
modelNote: `task.update failed: task [${otherInProgress.id}] "${otherInProgress.title}" is still in_progress. ` +
|
|
155
|
+
"Finish it first \u2014 call task.update with state 'done' (or 'failed'/'skipped' with a note) " +
|
|
156
|
+
`for [${otherInProgress.id}] before starting [${taskId}].`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const ok = markTask(plan, taskId, stateRaw, note);
|
|
161
|
+
if (!ok) {
|
|
162
|
+
const ids = plan.tasks.map((t) => t.id).join(", ");
|
|
163
|
+
return {
|
|
164
|
+
handled: true,
|
|
165
|
+
ok: false,
|
|
166
|
+
display: chalk.red(` ✗ task.update: unknown taskId "${taskId}" (have: ${ids})\n`),
|
|
167
|
+
modelNote: `task.update failed: unknown taskId. Valid ids: ${ids}.`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (plan.status === "draft" || plan.status === "approved") {
|
|
171
|
+
plan.status = "in_progress";
|
|
172
|
+
}
|
|
173
|
+
const allDone = plan.tasks.every((t) => t.state === "done" || t.state === "skipped" || t.state === "failed");
|
|
174
|
+
if (allDone)
|
|
175
|
+
plan.status = "completed";
|
|
176
|
+
await savePlan(plan).catch(() => undefined);
|
|
177
|
+
const checklist = renderPlanForTerminal(plan);
|
|
178
|
+
return {
|
|
179
|
+
handled: true,
|
|
180
|
+
ok: true,
|
|
181
|
+
plan,
|
|
182
|
+
display: checklist + "\n",
|
|
183
|
+
modelNote: allDone
|
|
184
|
+
? "Task updated. ALL tasks are now finished. Verify the result and give your final summary."
|
|
185
|
+
: "Task updated. Continue with the next pending task.",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=plan-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-tool.js","sourceRoot":"","sources":["../../src/agent/plan-tool.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.d.ts
CHANGED
|
@@ -1,35 +1,11 @@
|
|
|
1
1
|
import type { ChatMessage, ChatImage, ProviderId, ToolCall, ToolResult } from "../types.js";
|
|
2
2
|
import type { AgentEvent } from "./events.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
};
|
|
10
|
-
/** Stable id used to scope the session's plan/tasks in the plan store. */
|
|
11
|
-
sessionId: string;
|
|
12
|
-
/** When true, the agent must follow its approved plan (set by /implement). */
|
|
13
|
-
planApproved: {
|
|
14
|
-
value: boolean;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
export declare function createSessionPolicy(sessionId?: string): SessionPolicy;
|
|
18
|
-
export interface ConfirmPort {
|
|
19
|
-
confirmTool(call: ToolCall): Promise<boolean>;
|
|
20
|
-
confirmPentest(): Promise<boolean>;
|
|
21
|
-
/** Ask the user whether to continue after hitting the step budget. */
|
|
22
|
-
confirmContinue?(steps: number): Promise<boolean>;
|
|
23
|
-
/**
|
|
24
|
-
* Ask whether to leave ask mode and run an action task in agent mode.
|
|
25
|
-
* Optional so existing ports keep working; ask-mode handoff falls back to a
|
|
26
|
-
* default "no" when a port doesn't implement it.
|
|
27
|
-
*/
|
|
28
|
-
confirmAgentSwitch?(info: {
|
|
29
|
-
reason: string;
|
|
30
|
-
tools: string[];
|
|
31
|
-
}): Promise<boolean>;
|
|
32
|
-
}
|
|
3
|
+
import { type SessionPolicy } from "./session-policy.js";
|
|
4
|
+
import { type ConfirmPort } from "./confirm-port.js";
|
|
5
|
+
export * from "./tool-call-parser.js";
|
|
6
|
+
export { createSessionPolicy, isPreApprovalAllowedTool, shouldEnableImageOcr, type SessionPolicy, } from "./session-policy.js";
|
|
7
|
+
export { type ConfirmPort } from "./confirm-port.js";
|
|
8
|
+
export declare function styleToolChatter(call: ToolCall, text: string): string;
|
|
33
9
|
export interface AgentRunOptions {
|
|
34
10
|
provider?: ProviderId | undefined;
|
|
35
11
|
model?: string | undefined;
|
|
@@ -56,148 +32,4 @@ export interface AgentRunOptions {
|
|
|
56
32
|
}) => Promise<string | undefined>) | undefined;
|
|
57
33
|
session?: SessionPolicy | undefined;
|
|
58
34
|
}
|
|
59
|
-
export declare function preprocessJson(raw: string): string;
|
|
60
|
-
export interface ParseToolCallOptions {
|
|
61
|
-
/**
|
|
62
|
-
* When true, only formats that are explicitly tool-call delimited are
|
|
63
|
-
* accepted: ```tool fenced JSON, <tool_call> XML, and the Kimi sentinel
|
|
64
|
-
* token format. Loose formats (any fenced block, heading-prefix, trailing
|
|
65
|
-
* JSON) are dropped — useful when models routinely emit JSON examples in
|
|
66
|
-
* prose. Default is `false` so existing free-tier models keep working.
|
|
67
|
-
*/
|
|
68
|
-
strict?: boolean | undefined;
|
|
69
|
-
}
|
|
70
|
-
export declare function parseToolCall(text: string, options?: ParseToolCallOptions): ToolCall | undefined;
|
|
71
|
-
/**
|
|
72
|
-
* When a model emits a bare args object with no {"name", "args"} wrapper and
|
|
73
|
-
* no ```tool fence, infer which tool it MEANT from the argument keys so we
|
|
74
|
-
* can run it directly instead of nudging the model to re-emit (the user
|
|
75
|
-
* should not have to type "run"). Only unambiguous key signatures map to a
|
|
76
|
-
* tool; genuinely ambiguous shapes (a lone `path` could be fs.read / fs.list
|
|
77
|
-
* / pdf.read / image.ocr; a lone `target` could be whois / dns / scan) return
|
|
78
|
-
* undefined so the caller falls back to a re-emit nudge. Inferred calls still
|
|
79
|
-
* pass through the normal safety classifier + confirmation, so inference can
|
|
80
|
-
* never bypass a confirm/block gate.
|
|
81
|
-
*/
|
|
82
|
-
export declare function inferToolFromArgs(obj: Record<string, unknown>): string | undefined;
|
|
83
|
-
/**
|
|
84
|
-
* When a model means to call a tool but emits ONLY a bare JSON object —
|
|
85
|
-
* either a proper {"name","args"} that the strict matchers missed, or a bare
|
|
86
|
-
* args object like {"path":"file.pdf"} with the wrapper/fence dropped — this
|
|
87
|
-
* recognizes it. Returns:
|
|
88
|
-
* - { call } when the object is a complete {name, args} tool call, or
|
|
89
|
-
* - { argsOnly: true } when it looks like a bare args object (so the caller
|
|
90
|
-
* can nudge the model to re-emit a properly named, fenced tool call).
|
|
91
|
-
* Returns undefined for anything that is plainly a normal prose/JSON answer.
|
|
92
|
-
*
|
|
93
|
-
* Also handles the case where a model emits prose followed by a non-`tool`
|
|
94
|
-
* fenced code block (e.g. ```web\n{"url":"..."}\n```) that contains a bare
|
|
95
|
-
* args object — the fence is scanned even when it's not the sole content.
|
|
96
|
-
*/
|
|
97
|
-
export declare function recognizeBareToolJson(text: string): {
|
|
98
|
-
call?: ToolCall;
|
|
99
|
-
argsOnly?: boolean;
|
|
100
|
-
} | undefined;
|
|
101
|
-
/**
|
|
102
|
-
* Detect an opened-but-unparseable tool call. This happens when the model's
|
|
103
|
-
* output is truncated by the token limit mid-JSON: we see the ```tool fence
|
|
104
|
-
* (or a bare {"name":"...","args" prefix) open, but parseToolCall returns
|
|
105
|
-
* undefined because the JSON never closed. Without this, the broken block
|
|
106
|
-
* leaks to the screen as a "final answer" and the requested action (e.g. a
|
|
107
|
-
* multi-file fs.writeMany scaffold) silently never runs.
|
|
108
|
-
*/
|
|
109
|
-
export declare function looksLikeTruncatedToolCall(text: string): boolean;
|
|
110
|
-
/**
|
|
111
|
-
* Count the number of ```tool fenced blocks in a message. Models sometimes
|
|
112
|
-
* emit MULTIPLE tool calls in one response (e.g. fs.writeMany + npm install +
|
|
113
|
-
* npm run dev). Only the FIRST is parsed and executed; the rest are silently
|
|
114
|
-
* dropped and leak to the screen as code fences, while the model believes it
|
|
115
|
-
* ran all of them — a major cause of "everything is done" fabrications. We
|
|
116
|
-
* detect this so the runner can run the first and explicitly tell the model
|
|
117
|
-
* the others did NOT run and must be re-sent one at a time.
|
|
118
|
-
*/
|
|
119
|
-
export declare function countToolFences(text: string): number;
|
|
120
|
-
/**
|
|
121
|
-
* Parse EVERY explicitly-delimited tool call in a message, in document
|
|
122
|
-
* order. Unlike parseToolCall (which returns only the first), this lets the
|
|
123
|
-
* runner execute a batch the model emitted in one turn — e.g. the natural
|
|
124
|
-
* "task.update in_progress → do the work → task.update done" sequence, or
|
|
125
|
-
* several fs.write calls. Only the unambiguous, delimited formats are
|
|
126
|
-
* collected (```tool fences, <tool_call> XML, and Kimi sentinel blocks) so a
|
|
127
|
-
* worked example in prose is far less likely to be mistaken for a call.
|
|
128
|
-
* The runner executes them sequentially and STOPS the batch on the first
|
|
129
|
-
* failure so the model can react, mirroring how Claude Code batches reads
|
|
130
|
-
* and edits but pauses when something breaks.
|
|
131
|
-
*/
|
|
132
|
-
export declare function parseAllToolCalls(text: string): ToolCall[];
|
|
133
|
-
/** Structural equality for two tool calls (name + canonical args JSON). */
|
|
134
|
-
export declare function sameToolCall(a: ToolCall, b: ToolCall): boolean;
|
|
135
|
-
/**
|
|
136
|
-
* Partition a batch of tool calls (in document order) into execution groups.
|
|
137
|
-
* A run of consecutive parallel-safe calls forms one group to be run
|
|
138
|
-
* concurrently (bounded by maxGroupSize); every non-parallel-safe call is its
|
|
139
|
-
* own single-element group, i.e. a sequential barrier. Because plan updates
|
|
140
|
-
* and side-effecting tools are never parallel-safe, they always split the
|
|
141
|
-
* batch — which keeps parallelism scoped within a single task and prevents
|
|
142
|
-
* plan-state races and overlapping writes.
|
|
143
|
-
*/
|
|
144
|
-
export declare function groupToolCallsForExecution(calls: ToolCall[], isParallelSafe: (call: ToolCall) => boolean, maxGroupSize?: number): ToolCall[][];
|
|
145
|
-
/**
|
|
146
|
-
* Build the conversation to hand back to the caller at turn end. Strips system
|
|
147
|
-
* prompts (they're re-added each turn) but keeps the user turn plus every
|
|
148
|
-
* assistant tool-call and tool result, then appends the final answer if it
|
|
149
|
-
* isn't already the last message. Persisting this is what lets a resumed
|
|
150
|
-
* session give the model back what it actually did — commands, outputs, and
|
|
151
|
-
* results — instead of only its prose answers.
|
|
152
|
-
*/
|
|
153
|
-
export declare function buildTurnHistory(messages: ChatMessage[], answer: string): ChatMessage[];
|
|
154
|
-
/**
|
|
155
|
-
* Collapse pathological repetition before a message is stored in history.
|
|
156
|
-
* Some models degenerate into emitting the same short phrase hundreds of
|
|
157
|
-
* times ("We need to wait.We need to wait.…"), which otherwise bloats the
|
|
158
|
-
* context window and wastes tokens on every subsequent turn. We keep a few
|
|
159
|
-
* copies and note the collapse so the meaning is preserved without the bulk.
|
|
160
|
-
*/
|
|
161
|
-
export declare function collapseRepeatedText(text: string): string;
|
|
162
|
-
export declare function formatToolArgs(call: ToolCall): string;
|
|
163
|
-
/**
|
|
164
|
-
* Detect pentest/security tasks that need the full step budget.
|
|
165
|
-
* Mirrors looksLikeBuildTask but for security work.
|
|
166
|
-
*/
|
|
167
|
-
export declare function looksLikePentestTask(prompt: string, history?: ChatMessage[] | undefined): boolean;
|
|
168
|
-
/**
|
|
169
|
-
* Decide whether this turn should get the build workflow (explore → plan →
|
|
170
|
-
* implement) and a generous step budget. Looks at the current prompt first,
|
|
171
|
-
* then falls back to recent USER turns so a terse follow-up inherits an
|
|
172
|
-
* ongoing build — but NOT the agent's own (possibly mistaken) plan narration.
|
|
173
|
-
*/
|
|
174
|
-
export declare function looksLikeBuildTask(prompt: string, history?: ChatMessage[] | undefined): boolean;
|
|
175
|
-
/**
|
|
176
|
-
* Detect a message that narrates an *upcoming* action ("let me explore the
|
|
177
|
-
* directory", "I'll create the components") rather than an actual answer or
|
|
178
|
-
* tool call. Used to catch models that describe intent but emit no tool call,
|
|
179
|
-
* which would otherwise end the turn with nothing done. A real completion
|
|
180
|
-
* summary (past tense, longer, or containing a code block) is NOT flagged.
|
|
181
|
-
*/
|
|
182
|
-
export declare function looksLikeActionNarration(text: string): boolean;
|
|
183
|
-
/**
|
|
184
|
-
* Detect a message that narrates a PLAN as prose ("Goal: … Tasks: 1. … Please
|
|
185
|
-
* approve the plan") instead of calling plan.create. Such a turn leaves no
|
|
186
|
-
* real plan, so the user can't /implement it — we nudge the model to emit the
|
|
187
|
-
* plan.create tool call instead.
|
|
188
|
-
*/
|
|
189
|
-
export declare function looksLikePlanNarration(text: string): boolean;
|
|
190
|
-
export declare function requiresFreshWebSearch(prompt: string): boolean;
|
|
191
|
-
/**
|
|
192
|
-
* Detect a low-quality "everything in one step" plan task. A single task that
|
|
193
|
-
* itself enumerates many files/actions (multiple commas, an "and", several
|
|
194
|
-
* slashes, or an overlong title) means the model lumped the whole build into
|
|
195
|
-
* one checkbox instead of producing a real ordered checklist.
|
|
196
|
-
*/
|
|
197
|
-
export declare function isLumpedSingleTask(taskTitles: string[]): boolean;
|
|
198
|
-
export declare function shouldDimToolChatter(call: ToolCall): boolean;
|
|
199
|
-
export declare function isPreApprovalAllowedTool(name: string): boolean;
|
|
200
|
-
export declare function styleToolChatter(call: ToolCall, text: string): string;
|
|
201
|
-
/** OCR is opt-in when real image pixels are already attached to the model. */
|
|
202
|
-
export declare function shouldEnableImageOcr(prompt: string, hasAttachedImages: boolean): boolean;
|
|
203
35
|
export declare function runAgentLoop(prompt: string, options?: AgentRunOptions): Promise<string>;
|