lcagent-cli 0.1.6 → 0.1.7

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 CHANGED
@@ -10,6 +10,8 @@
10
10
  - `lcagent provider [name]`:查看或修改模型 provider
11
11
  - `lcagent model [name]`:查看或修改默认模型
12
12
  - `lcagent doctor`:检查当前模型配置和接口连通性
13
+ - `lcagent config set approvalMode auto|manual`:切换工具权限模式
14
+ - `lcagent config set autoContinueOnMaxTurns true|false`:切换超限自动续跑
13
15
  - `lcagent config show`
14
16
  - `lcagent config set <key> <value>`
15
17
 
@@ -20,6 +22,26 @@
20
22
  - `grep`
21
23
  - `run_shell`
22
24
 
25
+ 其中 `edit_file` 现已支持:
26
+
27
+ - 单文件 `unified diff / patch` 应用
28
+ - 精确文本替换
29
+ - 直引号 / 弯引号不一致时的归一化匹配
30
+ - 命中弯引号内容时自动保留原文件引号风格
31
+ - 多处命中时提示使用 `occurrence` 或 `replaceAll`
32
+ - 替换失败时返回更具体的上下文诊断
33
+
34
+ `edit_file` 现在有两种模式:
35
+
36
+ - 文本替换模式:传 `path + oldText + newText`
37
+ - Patch 模式:传 `patch`,也可以额外传 `path`
38
+
39
+ Patch 模式当前限制:
40
+
41
+ - 只支持单文件 unified diff
42
+ - patch 需要能对当前文件内容干净应用
43
+ - 如果 `path` 与 patch 头里的目标文件不一致,会直接报错
44
+
23
45
  ## 安装
24
46
 
25
47
  ```bash
@@ -28,6 +50,21 @@ npm install
28
50
  npm run build
29
51
  ```
30
52
 
53
+ 如果你想先确认本地最小闭环是否正常,可以直接运行:
54
+
55
+ ```bash
56
+ npm run smoke
57
+ ```
58
+
59
+ 这条命令不会访问模型服务,只会验证:
60
+
61
+ - `lcagent tools`
62
+ - `lcagent config show`
63
+ - `read_file`
64
+ - `grep`
65
+ - `edit_file`
66
+ - `run_shell`
67
+
31
68
  ## 发布到公网 npm
32
69
 
33
70
  先登录 npm 官方仓库:
@@ -137,16 +174,111 @@ npm run start -- tools
137
174
  npm run start -- provider
138
175
  npm run start -- model
139
176
  npm run start -- doctor
177
+ npm run start -- config set approvalMode manual
140
178
  ```
141
179
 
180
+ 注意:`chat` 和 `-p` 都依赖一个可用的模型配置。
181
+
182
+ - 如果你走 `anthropic`,需要先配置 `ANTHROPIC_API_KEY` 或 `lcagent config set apiKey ...`
183
+ - 如果你走本地模型,建议先执行 `init-local`,再执行 `doctor`,最后再进入 `chat`
184
+
185
+ ## 最小闭环验收顺序
186
+
187
+ 推荐按下面顺序验证当前版本:
188
+
189
+ 1. 本地静态能力:
190
+
191
+ ```bash
192
+ npm run check
193
+ npm run build
194
+ npm run smoke
195
+ ```
196
+
197
+ 2. 本地模型接入(OpenAI-compatible):
198
+
199
+ ```bash
200
+ npm run start -- init-local --base-url http://127.0.0.1:8000/v1 --model your-model-name
201
+ npm run start -- doctor
202
+ ```
203
+
204
+ 如果你使用 Anthropic 官方接口,则把上面两条替换成:
205
+
206
+ ```bash
207
+ export ANTHROPIC_API_KEY="your-key"
208
+ npm run start -- doctor
209
+ ```
210
+
211
+ 3. 交互式 chat:
212
+
213
+ ```bash
214
+ npm run start -- chat
215
+ ```
216
+
217
+ 进到 chat 后,建议先做一个最小读文件任务,例如:
218
+
219
+ - `读取 README.md 的前 40 行并总结项目结构`
220
+ - `搜索项目里有哪些 edit_file 相关实现`
221
+
222
+ 如果 `doctor` 可通过、`chat` 能读文件、`npm run smoke` 通过,就说明当前最小闭环已经稳定可用。
223
+
224
+ 如果 `doctor` 能跑但 `chat` 提示缺少 API key,通常不是程序损坏,而是当前配置仍停留在默认的 `anthropic` 且没有完成认证。先执行 `config show` 检查当前 provider,再按上面的配置步骤修正即可。
225
+
226
+ ## 权限模式
227
+
228
+ `lcagent` 当前支持两种最小权限模式:
229
+
230
+ - `auto`:工具直接执行
231
+ - `manual`:`read_file` / `grep` 直接放行,`edit_file` / `run_shell` 在执行前询问确认
232
+
233
+ 其中 `manual` 模式已经裁剪复用了 Claude Code 权限层里的两类思路:
234
+
235
+ - 权限模式分层思路
236
+ - 危险文件 / 危险目录名单(如 `.git`、`.vscode`、`.claude`、shell 配置文件)
237
+
238
+ 如果 `edit_file` 命中的目标位于这些高风险路径下,确认提示会给出更高风险说明。
239
+
240
+ 如果当前不是交互式终端,`manual` 模式下的写入/执行工具会被拒绝,并返回明确原因。
241
+
142
242
  当模型触发工具时,CLI 会打印:
143
243
 
144
244
  - `[tool-call] 工具名`:后面跟模型传入的原始 JSON 参数
145
245
  - `[tool-result] 工具名`:显示工具执行结果
146
246
  - `[tool-result] 工具名 (error)`:显示工具失败原因
247
+ - 同时附带 `cwd`、执行耗时、审批状态、失败阶段等元数据,便于排查是校验失败、审批拒绝还是工具执行异常
147
248
 
148
249
  这样可以直接看出是模型参数字段名不对、路径不对,还是工具执行本身报错。
149
250
 
251
+ ## 超限续跑
252
+
253
+ 当前默认配置下,`lcagent` 在单段运行达到 `maxTurns` 后,不会立刻报错退出,而是会自动续跑下一段:
254
+
255
+ - `maxTurns`:每一段最多运行多少轮,默认 `8`
256
+ - `autoContinueOnMaxTurns`:达到上限后是否自动续跑,默认 `true`
257
+ - `maxContinuations`:最多自动续跑多少段,默认 `3`
258
+
259
+ 也就是说,默认情况下最多会跑 `1 + 3 = 4` 段。
260
+
261
+ 如果自动续跑次数也耗尽了,CLI 不会只打印一条生硬错误,而是会额外输出:
262
+
263
+ - 最近的 assistant 文本进展
264
+ - 最近几次工具调用
265
+ - 最近几次工具结果
266
+ - 一个建议的“继续”提示词
267
+
268
+ 常用配置示例:
269
+
270
+ ```bash
271
+ npm run start -- config set maxTurns 12
272
+ npm run start -- config set autoContinueOnMaxTurns true
273
+ npm run start -- config set maxContinuations 4
274
+ ```
275
+
276
+ 如果你想恢复原来那种“达到上限就停止”的行为:
277
+
278
+ ```bash
279
+ npm run start -- config set autoContinueOnMaxTurns false
280
+ ```
281
+
150
282
  ## 设计目标
151
283
 
152
284
  - 保留 Claude Code 式的核心代理循环
@@ -157,7 +289,6 @@ npm run start -- doctor
157
289
 
158
290
  - 当前是最小版,没有流式输出
159
291
  - 工具权限策略还是简化版
160
- - `edit_file` 只做简单字符串替换
161
292
  - `grep` 是纯 Node 实现,不如 ripgrep 快
162
293
  - 当前按 Anthropic Messages API 直接调用,需要有效 API Key
163
294
 
@@ -1,7 +1,10 @@
1
1
  import { loadConfig } from '../config/store.js';
2
2
  import { AgentEngine } from '../core/engine.js';
3
3
  import { getDefaultTools } from '../tools/registry.js';
4
- export declare function createApp(cwd: string): Promise<{
4
+ import type { ToolContext } from '../tools/types.js';
5
+ export declare function createApp(cwd: string, options?: {
6
+ requestApproval?: ToolContext['requestApproval'];
7
+ }): Promise<{
5
8
  config: Awaited<ReturnType<typeof loadConfig>>;
6
9
  engine: AgentEngine;
7
10
  tools: ReturnType<typeof getDefaultTools>;
@@ -4,7 +4,7 @@ import { buildSystemPrompt } from '../core/systemPrompt.js';
4
4
  import { ModelClient } from '../core/model.js';
5
5
  import { AgentEngine } from '../core/engine.js';
6
6
  import { getDefaultTools } from '../tools/registry.js';
7
- export async function createApp(cwd) {
7
+ export async function createApp(cwd, options) {
8
8
  const config = await loadConfig();
9
9
  const apiKey = config.apiKey ??
10
10
  process.env.MODEL_API_KEY ??
@@ -21,7 +21,7 @@ export async function createApp(cwd) {
21
21
  baseUrl: config.baseUrl,
22
22
  model: config.model,
23
23
  maxTokens: config.maxTokens,
24
- }), tools, buildSystemPrompt(cwd), cwd);
24
+ }), tools, buildSystemPrompt(cwd), cwd, options?.requestApproval);
25
25
  return {
26
26
  config,
27
27
  engine,
package/dist/bin/cli.js CHANGED
@@ -54,6 +54,33 @@ function formatToolInput(input) {
54
54
  return String(input);
55
55
  }
56
56
  }
57
+ function formatToolResultMeta(meta) {
58
+ if (!meta) {
59
+ return null;
60
+ }
61
+ const parts = [`cwd=${meta.cwd}`, `duration=${meta.durationMs}ms`];
62
+ if (meta.approval.required) {
63
+ const approvalState = meta.approval.approved === false
64
+ ? 'denied'
65
+ : meta.approval.approved === true
66
+ ? 'approved'
67
+ : 'pending';
68
+ parts.push(`approval=${approvalState}`);
69
+ if (meta.approval.risk) {
70
+ parts.push(`risk=${meta.approval.risk}`);
71
+ }
72
+ if (meta.approval.targets && meta.approval.targets.length > 0) {
73
+ parts.push(`targets=${meta.approval.targets.join(',')}`);
74
+ }
75
+ }
76
+ else {
77
+ parts.push(`approval=${meta.approval.mode === 'manual' ? 'not-required' : 'auto'}`);
78
+ }
79
+ if (meta.failureStage) {
80
+ parts.push(`stage=${meta.failureStage}`);
81
+ }
82
+ return parts.join(' | ');
83
+ }
57
84
  function printEvent(event) {
58
85
  switch (event.type) {
59
86
  case 'status':
@@ -64,14 +91,54 @@ function printEvent(event) {
64
91
  break;
65
92
  case 'tool_call':
66
93
  console.log(`\n[tool-call] ${event.toolName}`);
94
+ console.log(`cwd=${event.cwd} | approvalMode=${event.approvalMode}`);
67
95
  console.log(formatToolInput(event.input));
68
96
  break;
69
97
  case 'tool_result':
70
98
  console.log(`\n[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
99
+ const metaLine = formatToolResultMeta(event.meta);
100
+ if (metaLine) {
101
+ console.log(metaLine);
102
+ }
71
103
  console.log(event.result);
72
104
  break;
73
105
  }
74
106
  }
107
+ async function promptForToolApproval(request, rl) {
108
+ if (!input.isTTY || !output.isTTY) {
109
+ return {
110
+ approved: false,
111
+ reason: `Denied ${request.toolName}: manual approval requires an interactive terminal.`,
112
+ };
113
+ }
114
+ const lines = [
115
+ '',
116
+ `[approval] ${request.toolName}`,
117
+ `- risk: ${request.risk}`,
118
+ `- summary: ${request.summary}`,
119
+ `- reason: ${request.reason}`,
120
+ ];
121
+ if (request.targets.length > 0) {
122
+ lines.push(`- targets: ${request.targets.join(', ')}`);
123
+ }
124
+ console.log(lines.join('\n'));
125
+ const temporaryRl = rl ?? createInterface({ input, output });
126
+ try {
127
+ const answer = (await questionAsync(temporaryRl, 'Allow this tool call? [y/N] ')).trim().toLowerCase();
128
+ if (answer === 'y' || answer === 'yes') {
129
+ return { approved: true };
130
+ }
131
+ return {
132
+ approved: false,
133
+ reason: `User denied ${request.toolName}.`,
134
+ };
135
+ }
136
+ finally {
137
+ if (!rl) {
138
+ temporaryRl.close();
139
+ }
140
+ }
141
+ }
75
142
  async function probeUrl(url) {
76
143
  try {
77
144
  const response = await fetchWithTimeout(url, {
@@ -229,17 +296,21 @@ async function runDoctor() {
229
296
  });
230
297
  }
231
298
  async function printAgentRun(prompt) {
232
- const { engine } = await createApp(process.cwd());
299
+ const { engine } = await createApp(process.cwd(), {
300
+ requestApproval: request => promptForToolApproval(request),
301
+ });
233
302
  for await (const event of engine.submit(prompt)) {
234
303
  printEvent(event);
235
304
  }
236
305
  }
237
306
  async function runChat() {
238
- const { engine, config } = await createApp(process.cwd());
239
- console.log(`lcagent chat started with provider: ${config.provider}, model: ${config.model}`);
240
- console.log('Type `exit` or `quit` to leave.');
241
307
  const rl = createInterface({ input, output });
242
308
  try {
309
+ const { engine, config } = await createApp(process.cwd(), {
310
+ requestApproval: request => promptForToolApproval(request, rl),
311
+ });
312
+ console.log(`lcagent chat started with provider: ${config.provider}, model: ${config.model}, approvalMode: ${config.approvalMode}`);
313
+ console.log('Type `exit` or `quit` to leave.');
243
314
  while (true) {
244
315
  const line = (await questionAsync(rl, '> ')).trim();
245
316
  if (!line) {
@@ -358,11 +429,13 @@ configCommand
358
429
  .argument('<value>', 'Config value')
359
430
  .action(async (key, value) => {
360
431
  const current = await loadConfig();
361
- const nextValue = key === 'maxTurns' || key === 'maxTokens'
432
+ const nextValue = key === 'maxTurns' || key === 'maxTokens' || key === 'maxContinuations'
362
433
  ? Number(value)
363
- : key === 'approvalMode' || key === 'provider'
364
- ? value
365
- : value;
434
+ : key === 'autoContinueOnMaxTurns'
435
+ ? value === 'true'
436
+ : key === 'approvalMode' || key === 'provider'
437
+ ? value
438
+ : value;
366
439
  const next = agentConfigSchema.parse({
367
440
  ...current,
368
441
  [key]: nextValue,
@@ -8,6 +8,8 @@ export declare const agentConfigSchema: z.ZodObject<{
8
8
  baseUrl: z.ZodDefault<z.ZodString>;
9
9
  model: z.ZodDefault<z.ZodString>;
10
10
  maxTurns: z.ZodDefault<z.ZodNumber>;
11
+ autoContinueOnMaxTurns: z.ZodDefault<z.ZodBoolean>;
12
+ maxContinuations: z.ZodDefault<z.ZodNumber>;
11
13
  maxTokens: z.ZodDefault<z.ZodNumber>;
12
14
  approvalMode: z.ZodDefault<z.ZodEnum<{
13
15
  auto: "auto";
@@ -5,6 +5,8 @@ export const agentConfigSchema = z.object({
5
5
  baseUrl: z.string().url().default('https://api.anthropic.com'),
6
6
  model: z.string().trim().min(1).default('claude-3-7-sonnet-latest'),
7
7
  maxTurns: z.number().int().positive().default(8),
8
+ autoContinueOnMaxTurns: z.boolean().default(true),
9
+ maxContinuations: z.number().int().min(0).default(3),
8
10
  maxTokens: z.number().int().positive().default(2048),
9
11
  approvalMode: z.enum(['auto', 'manual']).default('auto'),
10
12
  });
@@ -1,8 +1,8 @@
1
+ import type { ToolContext, ToolDefinition } from '../tools/types.js';
1
2
  import type { AgentConfig } from '../config/schema.js';
2
3
  import type { AgentSession } from '../app/session.js';
3
4
  import { type AgentEvent } from './message.js';
4
5
  import type { ModelClient } from './model.js';
5
- import type { ToolDefinition } from '../tools/types.js';
6
6
  export declare class AgentEngine {
7
7
  private readonly session;
8
8
  private readonly config;
@@ -10,6 +10,7 @@ export declare class AgentEngine {
10
10
  private readonly tools;
11
11
  private readonly systemPrompt;
12
12
  private readonly cwd;
13
- constructor(session: AgentSession, config: AgentConfig, modelClient: ModelClient, tools: ToolDefinition[], systemPrompt: string, cwd: string);
13
+ private readonly requestApproval?;
14
+ constructor(session: AgentSession, config: AgentConfig, modelClient: ModelClient, tools: ToolDefinition[], systemPrompt: string, cwd: string, requestApproval?: ToolContext['requestApproval']);
14
15
  submit(prompt: string): AsyncGenerator<AgentEvent, void>;
15
16
  }
@@ -1,5 +1,63 @@
1
- import { createUserTextMessage } from './message.js';
1
+ import { createAutoContinuationMessage, createUserTextMessage } from './message.js';
2
2
  import { runAgentLoop } from './loop.js';
3
+ function truncate(value, maxLength = 140) {
4
+ const normalized = value.replace(/\s+/g, ' ').trim();
5
+ if (normalized.length <= maxLength) {
6
+ return normalized;
7
+ }
8
+ return `${normalized.slice(0, maxLength - 1)}…`;
9
+ }
10
+ function buildRecentProgressSummary(messages) {
11
+ const recentToolNames = [];
12
+ const recentToolResults = [];
13
+ let latestAssistantText = null;
14
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
15
+ const message = messages[index];
16
+ if (!latestAssistantText && message.role === 'assistant') {
17
+ const textBlock = message.content.find(block => block.type === 'text' && block.text.trim().length > 0);
18
+ if (textBlock && textBlock.type === 'text') {
19
+ latestAssistantText = truncate(textBlock.text);
20
+ }
21
+ }
22
+ if (message.role === 'assistant') {
23
+ const toolUses = message.content.filter(block => block.type === 'tool_use');
24
+ for (let toolIndex = toolUses.length - 1; toolIndex >= 0; toolIndex -= 1) {
25
+ const toolUse = toolUses[toolIndex];
26
+ recentToolNames.push(toolUse.name);
27
+ if (recentToolNames.length >= 3) {
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ if (message.role === 'user') {
33
+ const toolResults = message.content.filter(block => block.type === 'tool_result');
34
+ for (let resultIndex = toolResults.length - 1; resultIndex >= 0; resultIndex -= 1) {
35
+ const toolResult = toolResults[resultIndex];
36
+ recentToolResults.push(truncate(toolResult.content));
37
+ if (recentToolResults.length >= 3) {
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ if (latestAssistantText &&
43
+ recentToolNames.length >= 3 &&
44
+ recentToolResults.length >= 3) {
45
+ break;
46
+ }
47
+ }
48
+ const lines = ['Recent progress summary:'];
49
+ if (latestAssistantText) {
50
+ lines.push(`- Last assistant note: ${latestAssistantText}`);
51
+ }
52
+ if (recentToolNames.length > 0) {
53
+ lines.push(`- Recent tools: ${recentToolNames.reverse().join(' -> ')}`);
54
+ }
55
+ if (recentToolResults.length > 0) {
56
+ lines.push(`- Recent tool results: ${recentToolResults.reverse().join(' | ')}`);
57
+ }
58
+ lines.push('- Suggested next prompt: 继续,基于当前上下文完成剩余任务,并优先处理尚未完成的步骤。');
59
+ return lines.join('\n');
60
+ }
3
61
  export class AgentEngine {
4
62
  session;
5
63
  config;
@@ -7,23 +65,55 @@ export class AgentEngine {
7
65
  tools;
8
66
  systemPrompt;
9
67
  cwd;
10
- constructor(session, config, modelClient, tools, systemPrompt, cwd) {
68
+ requestApproval;
69
+ constructor(session, config, modelClient, tools, systemPrompt, cwd, requestApproval) {
11
70
  this.session = session;
12
71
  this.config = config;
13
72
  this.modelClient = modelClient;
14
73
  this.tools = tools;
15
74
  this.systemPrompt = systemPrompt;
16
75
  this.cwd = cwd;
76
+ this.requestApproval = requestApproval;
17
77
  }
18
78
  async *submit(prompt) {
19
79
  this.session.messages.push(createUserTextMessage(prompt));
20
- yield* runAgentLoop({
21
- config: this.config,
22
- modelClient: this.modelClient,
23
- messages: this.session.messages,
24
- tools: this.tools,
25
- systemPrompt: this.systemPrompt,
26
- cwd: this.cwd,
27
- });
80
+ let continuationCount = 0;
81
+ while (true) {
82
+ const result = yield* runAgentLoop({
83
+ config: this.config,
84
+ modelClient: this.modelClient,
85
+ messages: this.session.messages,
86
+ tools: this.tools,
87
+ systemPrompt: this.systemPrompt,
88
+ cwd: this.cwd,
89
+ requestApproval: this.requestApproval,
90
+ });
91
+ if (result.reason !== 'max_turns') {
92
+ return;
93
+ }
94
+ if (!this.config.autoContinueOnMaxTurns ||
95
+ continuationCount >= this.config.maxContinuations) {
96
+ yield {
97
+ type: 'status',
98
+ message: `Reached maxTurns=${this.config.maxTurns} and stopped after ${continuationCount} auto-continuation(s). ` +
99
+ 'Increase maxTurns or maxContinuations if you want longer autonomous runs.',
100
+ };
101
+ yield {
102
+ type: 'status',
103
+ message: buildRecentProgressSummary(this.session.messages),
104
+ };
105
+ return;
106
+ }
107
+ continuationCount += 1;
108
+ yield {
109
+ type: 'status',
110
+ message: `Reached maxTurns=${this.config.maxTurns}; auto-continuing segment ${continuationCount}/${this.config.maxContinuations}...`,
111
+ };
112
+ this.session.messages.push(createAutoContinuationMessage({
113
+ maxTurns: this.config.maxTurns,
114
+ segment: continuationCount,
115
+ maxContinuations: this.config.maxContinuations,
116
+ }));
117
+ }
28
118
  }
29
119
  }
@@ -1,7 +1,11 @@
1
1
  import type { AgentConfig } from '../config/schema.js';
2
2
  import type { AgentEvent, AgentMessage } from './message.js';
3
- import type { ToolDefinition } from '../tools/types.js';
3
+ import type { ToolContext, ToolDefinition } from '../tools/types.js';
4
4
  import type { ModelClient } from './model.js';
5
+ export type AgentLoopResult = {
6
+ reason: 'completed' | 'max_turns';
7
+ turnCount: number;
8
+ };
5
9
  export declare function runAgentLoop(params: {
6
10
  config: AgentConfig;
7
11
  modelClient: ModelClient;
@@ -9,4 +13,5 @@ export declare function runAgentLoop(params: {
9
13
  tools: ToolDefinition[];
10
14
  systemPrompt: string;
11
15
  cwd: string;
12
- }): AsyncGenerator<AgentEvent, void>;
16
+ requestApproval?: ToolContext['requestApproval'];
17
+ }): AsyncGenerator<AgentEvent, AgentLoopResult>;
package/dist/core/loop.js CHANGED
@@ -3,6 +3,7 @@ export async function* runAgentLoop(params) {
3
3
  const toolContext = {
4
4
  cwd: params.cwd,
5
5
  approvalMode: params.config.approvalMode,
6
+ requestApproval: params.requestApproval,
6
7
  };
7
8
  for (let turn = 0; turn < params.config.maxTurns; turn += 1) {
8
9
  yield { type: 'status', message: `Thinking (turn ${turn + 1}/${params.config.maxTurns})...` };
@@ -23,13 +24,15 @@ export async function* runAgentLoop(params) {
23
24
  }
24
25
  }
25
26
  if (toolUses.length === 0) {
26
- return;
27
+ return { reason: 'completed', turnCount: turn + 1 };
27
28
  }
28
29
  for (const toolUse of toolUses) {
29
30
  yield {
30
31
  type: 'tool_call',
31
32
  toolName: toolUse.name,
32
33
  input: toolUse.input,
34
+ cwd: params.cwd,
35
+ approvalMode: params.config.approvalMode,
33
36
  };
34
37
  const result = await executeToolCall(toolUse, params.tools, toolContext);
35
38
  yield {
@@ -37,6 +40,7 @@ export async function* runAgentLoop(params) {
37
40
  toolName: toolUse.name,
38
41
  result: result.content,
39
42
  isError: result.isError,
43
+ meta: result.meta,
40
44
  };
41
45
  const toolResultBlock = {
42
46
  type: 'tool_result',
@@ -50,5 +54,8 @@ export async function* runAgentLoop(params) {
50
54
  });
51
55
  }
52
56
  }
53
- throw new Error(`Agent stopped after reaching maxTurns=${params.config.maxTurns}`);
57
+ return {
58
+ reason: 'max_turns',
59
+ turnCount: params.config.maxTurns,
60
+ };
54
61
  }
@@ -1,3 +1,4 @@
1
+ import type { ToolExecutionMeta } from '../tools/types.js';
1
2
  export type TextBlock = {
2
3
  type: 'text';
3
4
  text: string;
@@ -29,10 +30,18 @@ export type AgentEvent = {
29
30
  type: 'tool_call';
30
31
  toolName: string;
31
32
  input: unknown;
33
+ cwd: string;
34
+ approvalMode: 'auto' | 'manual';
32
35
  } | {
33
36
  type: 'tool_result';
34
37
  toolName: string;
35
38
  result: string;
36
39
  isError?: boolean;
40
+ meta?: ToolExecutionMeta;
37
41
  };
38
42
  export declare function createUserTextMessage(text: string): AgentMessage;
43
+ export declare function createAutoContinuationMessage(params: {
44
+ maxTurns: number;
45
+ segment: number;
46
+ maxContinuations: number;
47
+ }): AgentMessage;
@@ -4,3 +4,12 @@ export function createUserTextMessage(text) {
4
4
  content: [{ type: 'text', text }],
5
5
  };
6
6
  }
7
+ export function createAutoContinuationMessage(params) {
8
+ const text = [
9
+ `Automatic continuation ${params.segment}/${params.maxContinuations} after reaching maxTurns=${params.maxTurns}.`,
10
+ 'Continue from the current state without repeating completed work.',
11
+ 'Prioritize the remaining unfinished steps and finish the task if possible.',
12
+ 'If the task is already complete, answer directly without additional tool calls.',
13
+ ].join(' ');
14
+ return createUserTextMessage(text);
15
+ }
@@ -4,10 +4,18 @@ declare const inputSchema: z.ZodObject<{
4
4
  path: z.ZodOptional<z.ZodString>;
5
5
  filePath: z.ZodOptional<z.ZodString>;
6
6
  file_path: z.ZodOptional<z.ZodString>;
7
+ patch: z.ZodOptional<z.ZodString>;
8
+ diff: z.ZodOptional<z.ZodString>;
9
+ unifiedDiff: z.ZodOptional<z.ZodString>;
10
+ unified_diff: z.ZodOptional<z.ZodString>;
7
11
  oldText: z.ZodOptional<z.ZodString>;
8
12
  old_text: z.ZodOptional<z.ZodString>;
9
13
  newText: z.ZodOptional<z.ZodString>;
10
14
  new_text: z.ZodOptional<z.ZodString>;
15
+ replaceAll: z.ZodOptional<z.ZodBoolean>;
16
+ replace_all: z.ZodOptional<z.ZodBoolean>;
17
+ occurrence: z.ZodOptional<z.ZodNumber>;
18
+ occurrence_index: z.ZodOptional<z.ZodNumber>;
11
19
  }, z.core.$strip>;
12
20
  export declare const editFileTool: ToolDefinition<z.infer<typeof inputSchema>>;
13
21
  export {};