@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.
Files changed (105) hide show
  1. package/dist/agent/confirm-port.d.ts +30 -0
  2. package/dist/agent/confirm-port.js +89 -0
  3. package/dist/agent/confirm-port.js.map +1 -0
  4. package/dist/agent/plan-tool.d.ts +26 -0
  5. package/dist/agent/plan-tool.js +188 -0
  6. package/dist/agent/plan-tool.js.map +1 -0
  7. package/dist/agent/runner.d.ts +6 -174
  8. package/dist/agent/runner.js +25 -1606
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/agent/session-policy.d.ts +19 -0
  11. package/dist/agent/session-policy.js +40 -0
  12. package/dist/agent/session-policy.js.map +1 -0
  13. package/dist/agent/stop-summary.d.ts +8 -0
  14. package/dist/agent/stop-summary.js +53 -0
  15. package/dist/agent/stop-summary.js.map +1 -0
  16. package/dist/agent/tool-call-parser.d.ts +155 -0
  17. package/dist/agent/tool-call-parser.js +1107 -0
  18. package/dist/agent/tool-call-parser.js.map +1 -0
  19. package/dist/agent/tool-output-formatting.d.ts +7 -0
  20. package/dist/agent/tool-output-formatting.js +127 -0
  21. package/dist/agent/tool-output-formatting.js.map +1 -0
  22. package/dist/commands/update.js +1 -1
  23. package/dist/prompts/index.d.ts +2 -2
  24. package/dist/prompts/index.js +103 -59
  25. package/dist/prompts/index.js.map +1 -1
  26. package/dist/repl/prompt-line.d.ts +19 -0
  27. package/dist/repl/prompt-line.js +555 -0
  28. package/dist/repl/prompt-line.js.map +1 -0
  29. package/dist/repl/slash-commands.d.ts +24 -0
  30. package/dist/repl/slash-commands.js +317 -0
  31. package/dist/repl/slash-commands.js.map +1 -0
  32. package/dist/repl.d.ts +2 -25
  33. package/dist/repl.js +41 -898
  34. package/dist/repl.js.map +1 -1
  35. package/dist/safety/classifier.js +1 -1
  36. package/dist/safety/classifier.js.map +1 -1
  37. package/dist/store/keys.js +0 -4
  38. package/dist/store/keys.js.map +1 -1
  39. package/dist/store/plan.js +1 -1
  40. package/dist/store/plan.js.map +1 -1
  41. package/dist/tools/nmap-runner.d.ts +19 -0
  42. package/dist/tools/nmap-runner.js +177 -0
  43. package/dist/tools/nmap-runner.js.map +1 -0
  44. package/dist/tools/package-binary.d.ts +1 -0
  45. package/dist/tools/package-binary.js +30 -0
  46. package/dist/tools/package-binary.js.map +1 -0
  47. package/dist/tools/pdf.js +2 -2
  48. package/dist/tools/pdf.js.map +1 -1
  49. package/dist/tools/registry.d.ts +2 -11
  50. package/dist/tools/registry.js +3 -203
  51. package/dist/tools/registry.js.map +1 -1
  52. package/dist/tools/tool-types.d.ts +12 -0
  53. package/dist/tools/tool-types.js +2 -0
  54. package/dist/tools/tool-types.js.map +1 -0
  55. package/dist/tools/web/audit.d.ts +7 -29
  56. package/dist/tools/web/audit.js +7 -29
  57. package/dist/tools/web/audit.js.map +1 -1
  58. package/dist/tools/web/budget.d.ts +5 -28
  59. package/dist/tools/web/budget.js +5 -28
  60. package/dist/tools/web/budget.js.map +1 -1
  61. package/dist/tools/web/capture.d.ts +8 -35
  62. package/dist/tools/web/capture.js +8 -35
  63. package/dist/tools/web/capture.js.map +1 -1
  64. package/dist/tools/web/fetch-core.d.ts +6 -26
  65. package/dist/tools/web/fetch-core.js +6 -26
  66. package/dist/tools/web/fetch-core.js.map +1 -1
  67. package/dist/tools/web/readable.d.ts +7 -13
  68. package/dist/tools/web/readable.js +7 -13
  69. package/dist/tools/web/readable.js.map +1 -1
  70. package/dist/tools/web/search.d.ts +7 -20
  71. package/dist/tools/web/search.js +7 -20
  72. package/dist/tools/web/search.js.map +1 -1
  73. package/dist/tools/web/ssrf-guard.d.ts +8 -27
  74. package/dist/tools/web/ssrf-guard.js +8 -27
  75. package/dist/tools/web/ssrf-guard.js.map +1 -1
  76. package/dist/tui/App.js +19 -88
  77. package/dist/tui/App.js.map +1 -1
  78. package/dist/tui/key-intent.d.ts +36 -0
  79. package/dist/tui/key-intent.js +54 -0
  80. package/dist/tui/key-intent.js.map +1 -0
  81. package/dist/tui/render-lines.js +125 -37
  82. package/dist/tui/render-lines.js.map +1 -1
  83. package/dist/tui/state.js +2 -2
  84. package/dist/tui/state.js.map +1 -1
  85. package/dist/tui/text-format.d.ts +11 -0
  86. package/dist/tui/text-format.js +85 -0
  87. package/dist/tui/text-format.js.map +1 -0
  88. package/dist/ui/ansi-box.d.ts +7 -0
  89. package/dist/ui/ansi-box.js +18 -0
  90. package/dist/ui/ansi-box.js.map +1 -0
  91. package/dist/ui/banner.d.ts +0 -9
  92. package/dist/ui/banner.js +1 -43
  93. package/dist/ui/banner.js.map +1 -1
  94. package/dist/ui/intro-card.d.ts +10 -0
  95. package/dist/ui/intro-card.js +39 -0
  96. package/dist/ui/intro-card.js.map +1 -0
  97. package/dist/ui/markdown.js +1 -1
  98. package/dist/ui/markdown.js.map +1 -1
  99. package/dist/ui/output-pane.js.map +1 -1
  100. package/dist/ui/spinner.js +6 -17
  101. package/dist/ui/spinner.js.map +1 -1
  102. package/dist/ui/wordmark.d.ts +7 -0
  103. package/dist/ui/wordmark.js +42 -0
  104. package/dist/ui/wordmark.js.map +1 -0
  105. 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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"}
@@ -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
- export interface SessionPolicy {
4
- /** Tools the user authorized once during this REPL session. Not persisted. */
5
- allow: Set<string>;
6
- /** Mutable flag so the runner can flip pentest auth for this session only. */
7
- pentestAuthorized: {
8
- value: boolean;
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>;