@shareai-lab/kode-sdk 2.7.1 → 2.7.2

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 (97) hide show
  1. package/dist/core/agent/breakpoint-manager.js +36 -0
  2. package/dist/core/agent/message-queue.js +57 -0
  3. package/dist/core/agent/permission-manager.js +32 -0
  4. package/dist/core/agent/todo-manager.js +91 -0
  5. package/dist/core/agent/tool-runner.js +45 -0
  6. package/dist/core/agent.js +2035 -0
  7. package/dist/core/config.js +2 -0
  8. package/dist/core/context-manager.js +241 -0
  9. package/dist/core/errors.js +49 -0
  10. package/dist/core/events.js +329 -0
  11. package/dist/core/file-pool.d.ts +2 -0
  12. package/dist/core/file-pool.js +125 -0
  13. package/dist/core/hooks.js +71 -0
  14. package/dist/core/permission-modes.js +61 -0
  15. package/dist/core/pool.js +301 -0
  16. package/dist/core/room.js +57 -0
  17. package/dist/core/scheduler.js +58 -0
  18. package/dist/core/skills/index.js +20 -0
  19. package/dist/core/skills/management-manager.js +557 -0
  20. package/dist/core/skills/manager.js +243 -0
  21. package/dist/core/skills/operation-queue.js +113 -0
  22. package/dist/core/skills/sandbox-file-manager.js +183 -0
  23. package/dist/core/skills/types.js +9 -0
  24. package/dist/core/skills/xml-generator.js +70 -0
  25. package/dist/core/template.js +35 -0
  26. package/dist/core/time-bridge.js +100 -0
  27. package/dist/core/todo.js +89 -0
  28. package/dist/core/types.js +3 -0
  29. package/dist/index.js +148 -60461
  30. package/dist/infra/db/postgres/postgres-store.js +1073 -0
  31. package/dist/infra/db/sqlite/sqlite-store.js +800 -0
  32. package/dist/infra/e2b/e2b-fs.js +128 -0
  33. package/dist/infra/e2b/e2b-sandbox.js +156 -0
  34. package/dist/infra/e2b/e2b-template.js +105 -0
  35. package/dist/infra/e2b/index.js +9 -0
  36. package/dist/infra/e2b/types.js +2 -0
  37. package/dist/infra/provider.js +67 -0
  38. package/dist/infra/providers/anthropic.js +308 -0
  39. package/dist/infra/providers/core/errors.js +353 -0
  40. package/dist/infra/providers/core/fork.js +418 -0
  41. package/dist/infra/providers/core/index.js +76 -0
  42. package/dist/infra/providers/core/logger.js +191 -0
  43. package/dist/infra/providers/core/retry.js +189 -0
  44. package/dist/infra/providers/core/usage.js +376 -0
  45. package/dist/infra/providers/gemini.js +493 -0
  46. package/dist/infra/providers/index.js +83 -0
  47. package/dist/infra/providers/openai.js +662 -0
  48. package/dist/infra/providers/types.js +20 -0
  49. package/dist/infra/providers/utils.js +400 -0
  50. package/dist/infra/sandbox-factory.js +30 -0
  51. package/dist/infra/sandbox.js +243 -0
  52. package/dist/infra/store/factory.js +80 -0
  53. package/dist/infra/store/index.js +26 -0
  54. package/dist/infra/store/json-store.js +606 -0
  55. package/dist/infra/store/types.js +2 -0
  56. package/dist/infra/store.js +29 -0
  57. package/dist/tools/bash_kill/index.js +35 -0
  58. package/dist/tools/bash_kill/prompt.js +14 -0
  59. package/dist/tools/bash_logs/index.js +40 -0
  60. package/dist/tools/bash_logs/prompt.js +14 -0
  61. package/dist/tools/bash_run/index.js +61 -0
  62. package/dist/tools/bash_run/prompt.js +18 -0
  63. package/dist/tools/builtin.js +26 -0
  64. package/dist/tools/define.js +214 -0
  65. package/dist/tools/fs_edit/index.js +62 -0
  66. package/dist/tools/fs_edit/prompt.js +15 -0
  67. package/dist/tools/fs_glob/index.js +40 -0
  68. package/dist/tools/fs_glob/prompt.js +15 -0
  69. package/dist/tools/fs_grep/index.js +66 -0
  70. package/dist/tools/fs_grep/prompt.js +16 -0
  71. package/dist/tools/fs_multi_edit/index.js +106 -0
  72. package/dist/tools/fs_multi_edit/prompt.js +16 -0
  73. package/dist/tools/fs_read/index.js +40 -0
  74. package/dist/tools/fs_read/prompt.js +16 -0
  75. package/dist/tools/fs_write/index.js +40 -0
  76. package/dist/tools/fs_write/prompt.js +15 -0
  77. package/dist/tools/index.js +61 -0
  78. package/dist/tools/mcp.js +185 -0
  79. package/dist/tools/registry.js +26 -0
  80. package/dist/tools/scripts.js +205 -0
  81. package/dist/tools/skills.js +115 -0
  82. package/dist/tools/task_run/index.js +58 -0
  83. package/dist/tools/task_run/prompt.js +25 -0
  84. package/dist/tools/todo_read/index.js +29 -0
  85. package/dist/tools/todo_read/prompt.js +18 -0
  86. package/dist/tools/todo_write/index.js +42 -0
  87. package/dist/tools/todo_write/prompt.js +23 -0
  88. package/dist/tools/tool.js +211 -0
  89. package/dist/tools/toolkit.js +98 -0
  90. package/dist/tools/type-inference.js +207 -0
  91. package/dist/utils/agent-id.js +28 -0
  92. package/dist/utils/logger.js +44 -0
  93. package/dist/utils/session-id.js +64 -0
  94. package/package.json +7 -38
  95. package/dist/index.js.map +0 -7
  96. package/dist/index.mjs +0 -60385
  97. package/dist/index.mjs.map +0 -7
@@ -0,0 +1,2035 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.Agent = void 0;
40
+ const node_crypto_1 = require("node:crypto");
41
+ const events_1 = require("./events");
42
+ const hooks_1 = require("./hooks");
43
+ const scheduler_1 = require("./scheduler");
44
+ const context_manager_1 = require("./context-manager");
45
+ const file_pool_1 = require("./file-pool");
46
+ const ajv_1 = __importDefault(require("ajv"));
47
+ const todo_1 = require("./todo");
48
+ const provider_1 = require("../infra/provider");
49
+ const breakpoint_manager_1 = require("./agent/breakpoint-manager");
50
+ const permission_manager_1 = require("./agent/permission-manager");
51
+ const todo_read_1 = require("../tools/todo_read");
52
+ const todo_write_1 = require("../tools/todo_write");
53
+ const errors_1 = require("./errors");
54
+ const message_queue_1 = require("./agent/message-queue");
55
+ const todo_manager_1 = require("./agent/todo-manager");
56
+ const tool_runner_1 = require("./agent/tool-runner");
57
+ const logger_1 = require("../utils/logger");
58
+ const CONFIG_VERSION = 'v2.7.0';
59
+ class Agent {
60
+ get persistentStore() {
61
+ if (!this.deps.store) {
62
+ throw new Error('Agent persistent store is not configured for this operation.');
63
+ }
64
+ return this.deps.store;
65
+ }
66
+ static requireStore(deps) {
67
+ if (!deps.store) {
68
+ throw new errors_1.ResumeError('CORRUPTED_DATA', 'Agent store is not configured.');
69
+ }
70
+ return deps.store;
71
+ }
72
+ constructor(config, deps, runtime) {
73
+ this.config = config;
74
+ this.deps = deps;
75
+ this.events = new events_1.EventBus();
76
+ this.hooks = new hooks_1.HookManager();
77
+ this.ajv = new ajv_1.default({ allErrors: true, strict: false });
78
+ this.validatorCache = new Map();
79
+ this.toolControllers = new Map();
80
+ this.tools = new Map();
81
+ this.toolDescriptors = [];
82
+ this.toolDescriptorIndex = new Map();
83
+ this.pendingPermissions = new Map();
84
+ this.messages = [];
85
+ this.state = 'READY';
86
+ this.toolRecords = new Map();
87
+ this.interrupted = false;
88
+ this.processingPromise = null;
89
+ this.pendingNextRound = false; // 标志位:表示是否需要下一轮处理
90
+ this.lastProcessingStart = 0;
91
+ this.PROCESSING_TIMEOUT = 5 * 60 * 1000; // 5 分钟
92
+ this.stepCount = 0;
93
+ this.lastSfpIndex = -1;
94
+ this.lineage = [];
95
+ this.mediaCache = new Map();
96
+ Agent.requireStore(this.deps);
97
+ this.template = runtime.template;
98
+ this.model = runtime.model;
99
+ this.sandbox = runtime.sandbox;
100
+ this.sandboxConfig = runtime.sandboxConfig;
101
+ this.permission = runtime.permission;
102
+ this.subagents = runtime.subagents;
103
+ const templateRuntimeMeta = runtime.template.runtime?.metadata || {};
104
+ const metadataRetainThinking = typeof templateRuntimeMeta.retainThinking === 'boolean'
105
+ ? templateRuntimeMeta.retainThinking
106
+ : undefined;
107
+ this.exposeThinking = config.exposeThinking ?? runtime.template.runtime?.exposeThinking ?? false;
108
+ this.retainThinking =
109
+ config.retainThinking ?? metadataRetainThinking ?? runtime.template.runtime?.retainThinking ?? false;
110
+ this.multimodalContinuation =
111
+ config.multimodalContinuation ?? runtime.template.runtime?.multimodalContinuation ?? 'history';
112
+ const keepRecent = config.multimodalRetention?.keepRecent ?? runtime.template.runtime?.multimodalRetention?.keepRecent ?? 3;
113
+ this.multimodalRetentionKeepRecent = Math.max(0, Math.floor(keepRecent));
114
+ this.toolDescriptors = runtime.toolDescriptors;
115
+ for (const descriptor of this.toolDescriptors) {
116
+ this.toolDescriptorIndex.set(descriptor.name, descriptor);
117
+ }
118
+ this.todoConfig = runtime.todoConfig;
119
+ this.permissions = new permission_manager_1.PermissionManager(this.permission, this.toolDescriptorIndex);
120
+ // 保存SkillsManager引用
121
+ this.skillsManager = deps.skillsManager;
122
+ this.scheduler = new scheduler_1.Scheduler({
123
+ onTrigger: (info) => {
124
+ this.events.emitMonitor({
125
+ channel: 'monitor',
126
+ type: 'scheduler_triggered',
127
+ taskId: info.taskId,
128
+ spec: info.spec,
129
+ kind: info.kind,
130
+ triggeredAt: Date.now(),
131
+ });
132
+ },
133
+ });
134
+ const runtimeMeta = { ...(this.template.runtime?.metadata || {}), ...(config.metadata || {}) };
135
+ this.createdAt = new Date().toISOString();
136
+ this.toolTimeoutMs = typeof runtimeMeta.toolTimeoutMs === 'number' ? runtimeMeta.toolTimeoutMs : 60000;
137
+ this.maxToolConcurrency = typeof runtimeMeta.maxToolConcurrency === 'number' ? runtimeMeta.maxToolConcurrency : 3;
138
+ this.toolRunner = new tool_runner_1.ToolRunner(Math.max(1, this.maxToolConcurrency));
139
+ for (const tool of runtime.tools) {
140
+ this.tools.set(tool.name, tool);
141
+ if (tool.hooks) {
142
+ this.hooks.register(tool.hooks, 'toolTune');
143
+ }
144
+ }
145
+ if (this.template.hooks) {
146
+ this.hooks.register(this.template.hooks, 'agent');
147
+ }
148
+ if (config.overrides?.hooks && config.overrides.hooks !== this.template.hooks) {
149
+ this.hooks.register(config.overrides.hooks, 'agent');
150
+ }
151
+ this.breakpoints = new breakpoint_manager_1.BreakpointManager((previous, current, entry) => {
152
+ this.events.emitMonitor({
153
+ channel: 'monitor',
154
+ type: 'breakpoint_changed',
155
+ previous,
156
+ current,
157
+ timestamp: entry.timestamp,
158
+ });
159
+ });
160
+ this.breakpoints.set('READY');
161
+ if (runtime.todoConfig?.enabled) {
162
+ this.todoService = new todo_1.TodoService(this.persistentStore, this.agentId);
163
+ }
164
+ this.filePool = new file_pool_1.FilePool(this.sandbox, {
165
+ watch: this.sandboxConfig?.watchFiles !== false,
166
+ onChange: (event) => this.handleExternalFileChange(event.path, event.mtime),
167
+ });
168
+ const contextOptions = { ...(runtime.context || {}) };
169
+ contextOptions.multimodalRetention = {
170
+ ...(contextOptions.multimodalRetention || {}),
171
+ keepRecent: this.multimodalRetentionKeepRecent,
172
+ };
173
+ this.contextManager = new context_manager_1.ContextManager(this.persistentStore, this.agentId, contextOptions);
174
+ this.messageQueue = new message_queue_1.MessageQueue({
175
+ wrapReminder: this.wrapReminder.bind(this),
176
+ addMessage: (message, kind) => this.enqueueMessage(message, kind),
177
+ persist: () => this.persistMessages(),
178
+ ensureProcessing: () => this.ensureProcessing(),
179
+ });
180
+ this.todoManager = new todo_manager_1.TodoManager({
181
+ service: this.todoService,
182
+ config: this.todoConfig,
183
+ events: this.events,
184
+ remind: (content, options) => this.remind(content, options),
185
+ });
186
+ this.events.setStore(this.persistentStore, this.agentId);
187
+ // 自动注入工具说明书到系统提示
188
+ this.injectManualIntoSystemPrompt();
189
+ }
190
+ get agentId() {
191
+ return this.config.agentId;
192
+ }
193
+ static async create(config, deps) {
194
+ if (!config.agentId) {
195
+ config.agentId = Agent.generateAgentId();
196
+ }
197
+ const template = deps.templateRegistry.get(config.templateId);
198
+ const sandboxConfig = config.sandbox && 'kind' in config.sandbox
199
+ ? config.sandbox
200
+ : template.sandbox;
201
+ const sandbox = typeof config.sandbox === 'object' && 'exec' in config.sandbox
202
+ ? config.sandbox
203
+ : deps.sandboxFactory.create(sandboxConfig || { kind: 'local', workDir: process.cwd() });
204
+ const model = config.model
205
+ ? config.model
206
+ : config.modelConfig
207
+ ? ensureModelFactory(deps.modelFactory)(config.modelConfig)
208
+ : template.model
209
+ ? ensureModelFactory(deps.modelFactory)({ provider: 'anthropic', model: template.model })
210
+ : ensureModelFactory(deps.modelFactory)({ provider: 'anthropic', model: 'claude-sonnet-4-5-20250929' });
211
+ const resolvedTools = resolveTools(config, template, deps.toolRegistry, deps.templateRegistry);
212
+ const permissionConfig = config.overrides?.permission || template.permission || { mode: 'auto' };
213
+ const normalizedPermission = {
214
+ ...permissionConfig,
215
+ mode: permissionConfig.mode || 'auto',
216
+ };
217
+ const agent = new Agent(config, deps, {
218
+ template,
219
+ model,
220
+ sandbox,
221
+ sandboxConfig,
222
+ tools: resolvedTools.instances,
223
+ toolDescriptors: resolvedTools.descriptors,
224
+ permission: normalizedPermission,
225
+ todoConfig: config.overrides?.todo || template.runtime?.todo,
226
+ subagents: config.overrides?.subagents || template.runtime?.subagents,
227
+ context: config.context || template.runtime?.metadata?.context,
228
+ });
229
+ await agent.initialize();
230
+ return agent;
231
+ }
232
+ async initialize() {
233
+ await this.todoService?.load();
234
+ const messages = await this.persistentStore.loadMessages(this.agentId);
235
+ this.messages = messages;
236
+ this.lastSfpIndex = this.findLastSfp();
237
+ this.stepCount = messages.filter((m) => m.role === 'user').length;
238
+ const cachedMedia = await this.persistentStore.loadMediaCache(this.agentId);
239
+ this.mediaCache = new Map(cachedMedia.map((record) => [record.key, record]));
240
+ const records = await this.persistentStore.loadToolCallRecords(this.agentId);
241
+ this.toolRecords = new Map(records.map((record) => [record.id, this.normalizeToolRecord(record)]));
242
+ if (this.todoService) {
243
+ this.registerTodoTools();
244
+ this.todoManager.handleStartup();
245
+ }
246
+ await this.persistInfo();
247
+ // 注入skills元数据(异步,等待完成)
248
+ await this.injectSkillsMetadataIntoSystemPrompt();
249
+ }
250
+ async *chatStream(input, opts) {
251
+ const since = opts?.since ?? this.lastBookmark ?? this.events.getLastBookmark();
252
+ await this.send(input);
253
+ const subscription = this.events.subscribeProgress({ since, kinds: opts?.kinds });
254
+ let seenNonDone = false;
255
+ for await (const event of subscription) {
256
+ if (event.event.type === 'done') {
257
+ if (since && event.bookmark.seq <= since.seq) {
258
+ continue;
259
+ }
260
+ if (!seenNonDone) {
261
+ yield event;
262
+ this.lastBookmark = event.bookmark;
263
+ break;
264
+ }
265
+ yield event;
266
+ this.lastBookmark = event.bookmark;
267
+ break;
268
+ }
269
+ seenNonDone = true;
270
+ yield event;
271
+ }
272
+ }
273
+ async chat(input, opts) {
274
+ let streamedText = '';
275
+ let bookmark;
276
+ for await (const envelope of this.chatStream(input, opts)) {
277
+ if (envelope.event.type === 'text_chunk') {
278
+ streamedText += envelope.event.delta;
279
+ }
280
+ if (envelope.event.type === 'done') {
281
+ bookmark = envelope.bookmark;
282
+ }
283
+ }
284
+ const pending = Array.from(this.pendingPermissions.keys());
285
+ let finalText = streamedText;
286
+ const lastAssistant = [...this.messages].reverse().find((message) => message.role === 'assistant');
287
+ if (lastAssistant) {
288
+ const combined = lastAssistant.content
289
+ .filter((block) => block.type === 'text')
290
+ .map((block) => block.text)
291
+ .join('\n');
292
+ if (combined.trim().length > 0) {
293
+ finalText = combined;
294
+ }
295
+ }
296
+ return {
297
+ status: pending.length ? 'paused' : 'ok',
298
+ text: finalText,
299
+ last: bookmark,
300
+ permissionIds: pending,
301
+ };
302
+ }
303
+ async complete(input, opts) {
304
+ return this.chat(input, opts);
305
+ }
306
+ async *stream(input, opts) {
307
+ yield* this.chatStream(input, opts);
308
+ }
309
+ async send(message, options) {
310
+ if (typeof message === 'string') {
311
+ return this.messageQueue.send(message, options);
312
+ }
313
+ this.validateBlocks(message);
314
+ const resolved = await this.resolveMultimodalBlocks(message);
315
+ return this.messageQueue.send(resolved, options);
316
+ }
317
+ schedule() {
318
+ return this.scheduler;
319
+ }
320
+ on(event, handler) {
321
+ if (event === 'permission_required' || event === 'permission_decided') {
322
+ return this.events.onControl(event, handler);
323
+ }
324
+ return this.events.onMonitor(event, handler);
325
+ }
326
+ subscribe(channels, opts) {
327
+ if (!opts || (!opts.since && !opts.kinds)) {
328
+ return this.events.subscribe(channels);
329
+ }
330
+ return this.events.subscribe(channels, { since: opts.since, kinds: opts.kinds });
331
+ }
332
+ getTodos() {
333
+ return this.todoManager.list();
334
+ }
335
+ async setTodos(todos) {
336
+ await this.todoManager.setTodos(todos);
337
+ }
338
+ async updateTodo(todo) {
339
+ await this.todoManager.update(todo);
340
+ }
341
+ async deleteTodo(id) {
342
+ await this.todoManager.remove(id);
343
+ }
344
+ async decide(permissionId, decision, note) {
345
+ const pending = this.pendingPermissions.get(permissionId);
346
+ if (!pending)
347
+ throw new Error(`Permission not pending: ${permissionId}`);
348
+ pending.resolve(decision, note);
349
+ this.pendingPermissions.delete(permissionId);
350
+ this.events.emitControl({
351
+ channel: 'control',
352
+ type: 'permission_decided',
353
+ callId: permissionId,
354
+ decision,
355
+ decidedBy: 'api',
356
+ note,
357
+ });
358
+ if (decision === 'allow') {
359
+ this.setState('WORKING');
360
+ this.setBreakpoint('PRE_TOOL');
361
+ this.ensureProcessing();
362
+ }
363
+ else {
364
+ this.setBreakpoint('POST_TOOL');
365
+ this.setState('READY');
366
+ }
367
+ }
368
+ async interrupt(opts) {
369
+ this.interrupted = true;
370
+ this.toolRunner.clear();
371
+ for (const controller of this.toolControllers.values()) {
372
+ controller.abort();
373
+ }
374
+ this.toolControllers.clear();
375
+ await this.appendSyntheticToolResults(opts?.note || 'Interrupted by user');
376
+ this.setState('READY');
377
+ this.setBreakpoint('READY');
378
+ }
379
+ async snapshot(label) {
380
+ const id = label || `sfp-${this.lastSfpIndex}`;
381
+ const snapshot = {
382
+ id,
383
+ messages: JSON.parse(JSON.stringify(this.messages)),
384
+ lastSfpIndex: this.lastSfpIndex,
385
+ lastBookmark: this.lastBookmark ?? { seq: -1, timestamp: Date.now() },
386
+ createdAt: new Date().toISOString(),
387
+ metadata: {
388
+ stepCount: this.stepCount,
389
+ },
390
+ };
391
+ await this.persistentStore.saveSnapshot(this.agentId, snapshot);
392
+ return id;
393
+ }
394
+ async fork(sel) {
395
+ const snapshotId = typeof sel === 'string' ? sel : sel?.at ?? (await this.snapshot());
396
+ const snapshot = await this.persistentStore.loadSnapshot(this.agentId, snapshotId);
397
+ if (!snapshot)
398
+ throw new Error(`Snapshot not found: ${snapshotId}`);
399
+ const forkId = `${this.agentId}/fork-${Date.now()}`;
400
+ const forkConfig = {
401
+ ...this.config,
402
+ agentId: forkId,
403
+ };
404
+ const fork = await Agent.create(forkConfig, this.deps);
405
+ fork.messages = JSON.parse(JSON.stringify(snapshot.messages));
406
+ fork.lastSfpIndex = snapshot.lastSfpIndex;
407
+ fork.stepCount = snapshot.metadata?.stepCount ?? fork.messages.filter((m) => m.role === 'user').length;
408
+ fork.lineage = [...this.lineage, this.agentId];
409
+ await fork.persistMessages();
410
+ return fork;
411
+ }
412
+ async status() {
413
+ return {
414
+ agentId: this.agentId,
415
+ state: this.state,
416
+ stepCount: this.stepCount,
417
+ lastSfpIndex: this.lastSfpIndex,
418
+ lastBookmark: this.lastBookmark,
419
+ cursor: this.events.getCursor(),
420
+ breakpoint: this.breakpoints.getCurrent(),
421
+ };
422
+ }
423
+ async info() {
424
+ return {
425
+ agentId: this.agentId,
426
+ templateId: this.template.id,
427
+ createdAt: this.createdAt,
428
+ lineage: this.lineage,
429
+ configVersion: CONFIG_VERSION,
430
+ messageCount: this.messages.length,
431
+ lastSfpIndex: this.lastSfpIndex,
432
+ lastBookmark: this.lastBookmark,
433
+ breakpoint: this.breakpoints.getCurrent(),
434
+ };
435
+ }
436
+ setBreakpoint(state, note) {
437
+ this.breakpoints.set(state, note);
438
+ }
439
+ remind(content, options) {
440
+ this.messageQueue.send(content, { kind: 'reminder', reminder: options });
441
+ this.events.emitMonitor({
442
+ channel: 'monitor',
443
+ type: 'reminder_sent',
444
+ category: options?.category ?? 'general',
445
+ content,
446
+ });
447
+ }
448
+ async spawnSubAgent(templateId, prompt, runtime) {
449
+ if (!this.subagents) {
450
+ throw new Error('Sub-agent configuration not enabled for this agent');
451
+ }
452
+ const remaining = runtime?.depthRemaining ?? this.subagents.depth;
453
+ if (remaining <= 0) {
454
+ throw new Error('Sub-agent recursion limit reached');
455
+ }
456
+ if (this.subagents.templates && !this.subagents.templates.includes(templateId)) {
457
+ throw new Error(`Template ${templateId} not allowed for sub-agent`);
458
+ }
459
+ const subConfig = {
460
+ templateId,
461
+ modelConfig: this.model.toConfig(),
462
+ sandbox: this.sandboxConfig || { kind: 'local', workDir: this.sandbox.workDir },
463
+ exposeThinking: this.exposeThinking,
464
+ multimodalContinuation: this.multimodalContinuation,
465
+ multimodalRetention: { keepRecent: this.multimodalRetentionKeepRecent },
466
+ metadata: this.config.metadata,
467
+ overrides: {
468
+ permission: this.subagents.overrides?.permission || this.permission,
469
+ todo: this.subagents.overrides?.todo || this.template.runtime?.todo,
470
+ subagents: this.subagents.inheritConfig ? { ...this.subagents, depth: remaining - 1 } : undefined,
471
+ },
472
+ };
473
+ const subAgent = await Agent.create(subConfig, this.deps);
474
+ subAgent.lineage = [...this.lineage, this.agentId];
475
+ try {
476
+ const result = await subAgent.complete(prompt);
477
+ return result;
478
+ }
479
+ finally {
480
+ await subAgent.sandbox?.dispose?.();
481
+ }
482
+ }
483
+ /**
484
+ * Create and run a sub-agent with a task, without requiring subagents config.
485
+ * This is useful for tools that want to delegate work to specialized agents.
486
+ */
487
+ async delegateTask(config) {
488
+ const subAgentConfig = {
489
+ templateId: config.templateId,
490
+ modelConfig: config.model
491
+ ? { provider: 'anthropic', model: config.model }
492
+ : this.model.toConfig(),
493
+ sandbox: this.sandboxConfig || { kind: 'local', workDir: this.sandbox.workDir },
494
+ tools: config.tools,
495
+ multimodalContinuation: this.multimodalContinuation,
496
+ multimodalRetention: { keepRecent: this.multimodalRetentionKeepRecent },
497
+ metadata: {
498
+ ...this.config.metadata,
499
+ parentAgentId: this.agentId,
500
+ delegatedBy: 'task_tool',
501
+ },
502
+ };
503
+ const subAgent = await Agent.create(subAgentConfig, this.deps);
504
+ subAgent.lineage = [...this.lineage, this.agentId];
505
+ try {
506
+ const result = await subAgent.complete(config.prompt);
507
+ return result;
508
+ }
509
+ finally {
510
+ await subAgent.sandbox?.dispose?.();
511
+ }
512
+ }
513
+ static async resume(agentId, config, deps, opts) {
514
+ const store = Agent.requireStore(deps);
515
+ const info = await store.loadInfo(agentId);
516
+ if (!info) {
517
+ throw new errors_1.ResumeError('AGENT_NOT_FOUND', `Agent metadata not found: ${agentId}`);
518
+ }
519
+ const metadata = info.metadata;
520
+ if (!metadata) {
521
+ throw new errors_1.ResumeError('CORRUPTED_DATA', `Agent metadata incomplete for: ${agentId}`);
522
+ }
523
+ let resumeBookmark = info.lastBookmark;
524
+ if (!resumeBookmark) {
525
+ for await (const entry of store.readEvents(agentId, { channel: 'progress' })) {
526
+ if (entry.event.type === 'done') {
527
+ resumeBookmark = entry.bookmark;
528
+ }
529
+ }
530
+ }
531
+ const templateId = metadata.templateId;
532
+ let template;
533
+ try {
534
+ template = deps.templateRegistry.get(templateId);
535
+ }
536
+ catch (error) {
537
+ throw new errors_1.ResumeError('TEMPLATE_NOT_FOUND', `Template not registered: ${templateId}`);
538
+ }
539
+ if (config.templateVersion && metadata.templateVersion && config.templateVersion !== metadata.templateVersion) {
540
+ throw new errors_1.ResumeError('TEMPLATE_VERSION_MISMATCH', `Template version mismatch: expected ${config.templateVersion}, got ${metadata.templateVersion}`);
541
+ }
542
+ let sandbox;
543
+ try {
544
+ sandbox = deps.sandboxFactory.create(metadata.sandboxConfig || { kind: 'local', workDir: process.cwd() });
545
+ }
546
+ catch (error) {
547
+ throw new errors_1.ResumeError('SANDBOX_INIT_FAILED', error?.message || 'Failed to create sandbox');
548
+ }
549
+ const model = metadata.modelConfig
550
+ ? ensureModelFactory(deps.modelFactory)(metadata.modelConfig)
551
+ : ensureModelFactory(deps.modelFactory)({ provider: 'anthropic', model: template.model || 'claude-sonnet-4-5-20250929' });
552
+ const toolInstances = metadata.tools.map((descriptor) => {
553
+ try {
554
+ return deps.toolRegistry.create(descriptor.registryId || descriptor.name, descriptor.config);
555
+ }
556
+ catch (error) {
557
+ throw new errors_1.ResumeError('CORRUPTED_DATA', `Failed to restore tool ${descriptor.name}: ${error?.message || error}`);
558
+ }
559
+ });
560
+ const permissionConfig = metadata.permission || template.permission || { mode: 'auto' };
561
+ const normalizedPermission = {
562
+ ...permissionConfig,
563
+ mode: permissionConfig.mode || 'auto',
564
+ };
565
+ const agent = new Agent({
566
+ ...config,
567
+ agentId,
568
+ templateId: templateId,
569
+ exposeThinking: metadata.exposeThinking,
570
+ retainThinking: metadata.retainThinking,
571
+ multimodalContinuation: metadata.multimodalContinuation,
572
+ multimodalRetention: metadata.multimodalRetention,
573
+ }, deps, {
574
+ template,
575
+ model,
576
+ sandbox,
577
+ sandboxConfig: metadata.sandboxConfig,
578
+ tools: toolInstances,
579
+ toolDescriptors: metadata.tools,
580
+ permission: normalizedPermission,
581
+ todoConfig: metadata.todo,
582
+ subagents: metadata.subagents,
583
+ context: metadata.context,
584
+ });
585
+ agent.lineage = metadata.lineage || [];
586
+ agent.createdAt = metadata.createdAt || agent.createdAt;
587
+ await agent.initialize();
588
+ if (metadata.breakpoint) {
589
+ agent.breakpoints.reset(metadata.breakpoint);
590
+ }
591
+ let messages;
592
+ try {
593
+ messages = await store.loadMessages(agentId);
594
+ }
595
+ catch (error) {
596
+ throw new errors_1.ResumeError('CORRUPTED_DATA', error?.message || 'Failed to load messages');
597
+ }
598
+ agent.messages = messages;
599
+ agent.lastSfpIndex = agent.findLastSfp();
600
+ agent.stepCount = messages.filter((m) => m.role === 'user').length;
601
+ const toolRecords = await store.loadToolCallRecords(agentId);
602
+ agent.toolRecords = new Map(toolRecords.map((record) => [record.id, agent.normalizeToolRecord(record)]));
603
+ if (resumeBookmark) {
604
+ agent.lastBookmark = resumeBookmark;
605
+ agent.events.syncCursor(resumeBookmark);
606
+ }
607
+ if (opts?.strategy === 'crash') {
608
+ const sealed = await agent.autoSealIncompleteCalls();
609
+ agent.events.emitMonitor({
610
+ channel: 'monitor',
611
+ type: 'agent_resumed',
612
+ strategy: 'crash',
613
+ sealed,
614
+ });
615
+ }
616
+ else {
617
+ agent.events.emitMonitor({
618
+ channel: 'monitor',
619
+ type: 'agent_resumed',
620
+ strategy: 'manual',
621
+ sealed: [],
622
+ });
623
+ }
624
+ if (opts?.autoRun) {
625
+ agent.ensureProcessing();
626
+ }
627
+ return agent;
628
+ }
629
+ static async resumeFromStore(agentId, deps, opts) {
630
+ const store = Agent.requireStore(deps);
631
+ const info = await store.loadInfo(agentId);
632
+ if (!info || !info.metadata) {
633
+ throw new errors_1.ResumeError('AGENT_NOT_FOUND', `Agent metadata not found: ${agentId}`);
634
+ }
635
+ const metadata = info.metadata;
636
+ const baseConfig = {
637
+ agentId,
638
+ templateId: metadata.templateId,
639
+ templateVersion: metadata.templateVersion,
640
+ modelConfig: metadata.modelConfig,
641
+ sandbox: metadata.sandboxConfig,
642
+ exposeThinking: metadata.exposeThinking,
643
+ retainThinking: metadata.retainThinking,
644
+ context: metadata.context,
645
+ metadata: metadata.metadata,
646
+ overrides: {
647
+ permission: metadata.permission,
648
+ todo: metadata.todo,
649
+ subagents: metadata.subagents,
650
+ },
651
+ tools: metadata.tools.map((descriptor) => descriptor.registryId || descriptor.name),
652
+ };
653
+ const overrides = opts?.overrides ?? {};
654
+ return Agent.resume(agentId, { ...baseConfig, ...overrides }, deps, opts);
655
+ }
656
+ ensureProcessing() {
657
+ // 检查是否超时
658
+ if (this.processingPromise) {
659
+ const now = Date.now();
660
+ if (now - this.lastProcessingStart > this.PROCESSING_TIMEOUT) {
661
+ this.events.emitMonitor({
662
+ channel: 'monitor',
663
+ type: 'error',
664
+ severity: 'error',
665
+ phase: 'lifecycle',
666
+ message: 'Processing timeout detected, forcing restart',
667
+ detail: {
668
+ lastStart: this.lastProcessingStart,
669
+ elapsed: now - this.lastProcessingStart
670
+ }
671
+ });
672
+ this.processingPromise = null; // 强制重启
673
+ }
674
+ else {
675
+ // 正常执行中,设置标志位表示需要下一轮
676
+ this.pendingNextRound = true;
677
+ return;
678
+ }
679
+ }
680
+ // 清除标志位,准备启动新的处理
681
+ this.pendingNextRound = false;
682
+ this.lastProcessingStart = Date.now();
683
+ this.processingPromise = this.runStep()
684
+ .finally(() => {
685
+ this.processingPromise = null;
686
+ // 如果有下一轮待处理,启动它
687
+ if (this.pendingNextRound) {
688
+ this.ensureProcessing();
689
+ }
690
+ })
691
+ .catch((err) => {
692
+ // 确保异常不会导致状态卡住
693
+ this.events.emitMonitor({
694
+ channel: 'monitor',
695
+ type: 'error',
696
+ severity: 'error',
697
+ phase: 'lifecycle',
698
+ message: 'Processing failed',
699
+ detail: { error: err.message, stack: err.stack }
700
+ });
701
+ this.setState('READY');
702
+ this.setBreakpoint('READY');
703
+ });
704
+ }
705
+ async runStep() {
706
+ if (this.state !== 'READY')
707
+ return;
708
+ if (this.interrupted) {
709
+ this.interrupted = false;
710
+ return;
711
+ }
712
+ this.setState('WORKING');
713
+ this.setBreakpoint('PRE_MODEL');
714
+ let doneEmitted = false;
715
+ try {
716
+ await this.messageQueue.flush();
717
+ const usage = this.contextManager.analyze(this.messages);
718
+ if (usage.shouldCompress) {
719
+ this.events.emitMonitor({
720
+ channel: 'monitor',
721
+ type: 'context_compression',
722
+ phase: 'start',
723
+ });
724
+ const compression = await this.contextManager.compress(this.messages, this.events.getTimeline(), this.filePool, this.sandbox);
725
+ if (compression) {
726
+ this.messages = [...compression.retainedMessages];
727
+ this.messages.unshift(compression.summary);
728
+ this.lastSfpIndex = this.messages.length - 1;
729
+ await this.persistMessages();
730
+ this.events.emitMonitor({
731
+ channel: 'monitor',
732
+ type: 'context_compression',
733
+ phase: 'end',
734
+ summary: compression.summary.content.map((block) => (block.type === 'text' ? block.text : JSON.stringify(block))).join('\n'),
735
+ ratio: compression.ratio,
736
+ });
737
+ }
738
+ }
739
+ await this.hooks.runPreModel(this.messages);
740
+ this.setBreakpoint('STREAMING_MODEL');
741
+ let assistantBlocks = [];
742
+ const stream = this.model.stream(this.messages, {
743
+ tools: this.getToolSchemas(),
744
+ maxTokens: this.config.metadata?.maxTokens,
745
+ temperature: this.config.metadata?.temperature,
746
+ system: this.template.systemPrompt,
747
+ });
748
+ let currentBlockIndex = -1;
749
+ let currentToolBuffer = '';
750
+ const textBuffers = new Map();
751
+ const reasoningBuffers = new Map();
752
+ for await (const chunk of stream) {
753
+ if (chunk.type === 'content_block_start') {
754
+ if (chunk.content_block?.type === 'text') {
755
+ currentBlockIndex = chunk.index ?? 0;
756
+ textBuffers.set(currentBlockIndex, '');
757
+ assistantBlocks[currentBlockIndex] = { type: 'text', text: '' };
758
+ this.events.emitProgress({ channel: 'progress', type: 'text_chunk_start', step: this.stepCount });
759
+ }
760
+ else if (chunk.content_block?.type === 'reasoning') {
761
+ currentBlockIndex = chunk.index ?? 0;
762
+ reasoningBuffers.set(currentBlockIndex, '');
763
+ assistantBlocks[currentBlockIndex] = { type: 'reasoning', reasoning: '' };
764
+ if (this.exposeThinking) {
765
+ this.events.emitProgress({ channel: 'progress', type: 'think_chunk_start', step: this.stepCount });
766
+ }
767
+ }
768
+ else if (chunk.content_block?.type === 'image' ||
769
+ chunk.content_block?.type === 'audio' ||
770
+ chunk.content_block?.type === 'file') {
771
+ currentBlockIndex = chunk.index ?? 0;
772
+ assistantBlocks[currentBlockIndex] = chunk.content_block;
773
+ }
774
+ else if (chunk.content_block?.type === 'tool_use') {
775
+ currentBlockIndex = chunk.index ?? 0;
776
+ currentToolBuffer = '';
777
+ const meta = chunk.content_block.meta;
778
+ assistantBlocks[currentBlockIndex] = {
779
+ type: 'tool_use',
780
+ id: chunk.content_block.id,
781
+ name: chunk.content_block.name,
782
+ input: chunk.content_block.input ?? {},
783
+ ...(meta ? { meta } : {}),
784
+ };
785
+ }
786
+ }
787
+ else if (chunk.type === 'content_block_delta') {
788
+ if (chunk.delta?.type === 'text_delta') {
789
+ const text = chunk.delta.text ?? '';
790
+ const existing = textBuffers.get(currentBlockIndex) ?? '';
791
+ textBuffers.set(currentBlockIndex, existing + text);
792
+ if (assistantBlocks[currentBlockIndex]?.type === 'text') {
793
+ assistantBlocks[currentBlockIndex].text = existing + text;
794
+ }
795
+ this.events.emitProgress({ channel: 'progress', type: 'text_chunk', step: this.stepCount, delta: text });
796
+ }
797
+ else if (chunk.delta?.type === 'reasoning_delta') {
798
+ const text = chunk.delta.text ?? '';
799
+ const existing = reasoningBuffers.get(currentBlockIndex) ?? '';
800
+ reasoningBuffers.set(currentBlockIndex, existing + text);
801
+ if (assistantBlocks[currentBlockIndex]?.type === 'reasoning') {
802
+ assistantBlocks[currentBlockIndex].reasoning = existing + text;
803
+ }
804
+ if (this.exposeThinking) {
805
+ this.events.emitProgress({ channel: 'progress', type: 'think_chunk', step: this.stepCount, delta: text });
806
+ }
807
+ }
808
+ else if (chunk.delta?.type === 'input_json_delta') {
809
+ currentToolBuffer += chunk.delta.partial_json ?? '';
810
+ try {
811
+ const parsed = JSON.parse(currentToolBuffer);
812
+ if (assistantBlocks[currentBlockIndex]?.type === 'tool_use') {
813
+ assistantBlocks[currentBlockIndex].input = parsed;
814
+ }
815
+ }
816
+ catch {
817
+ // continue buffering
818
+ }
819
+ }
820
+ }
821
+ else if (chunk.type === 'message_delta') {
822
+ const inputTokens = chunk.usage?.input_tokens ?? 0;
823
+ const outputTokens = chunk.usage?.output_tokens ?? 0;
824
+ if (inputTokens || outputTokens) {
825
+ this.events.emitMonitor({
826
+ channel: 'monitor',
827
+ type: 'token_usage',
828
+ inputTokens,
829
+ outputTokens,
830
+ totalTokens: inputTokens + outputTokens,
831
+ });
832
+ }
833
+ }
834
+ else if (chunk.type === 'content_block_stop') {
835
+ if (assistantBlocks[currentBlockIndex]?.type === 'text') {
836
+ const fullText = textBuffers.get(currentBlockIndex) ?? '';
837
+ this.events.emitProgress({ channel: 'progress', type: 'text_chunk_end', step: this.stepCount, text: fullText });
838
+ }
839
+ else if (assistantBlocks[currentBlockIndex]?.type === 'reasoning') {
840
+ if (this.exposeThinking) {
841
+ this.events.emitProgress({ channel: 'progress', type: 'think_chunk_end', step: this.stepCount });
842
+ }
843
+ }
844
+ currentBlockIndex = -1;
845
+ currentToolBuffer = '';
846
+ }
847
+ }
848
+ assistantBlocks = this.splitThinkBlocksIfNeeded(assistantBlocks);
849
+ await this.hooks.runPostModel({ role: 'assistant', content: assistantBlocks });
850
+ const originalBlocks = assistantBlocks;
851
+ const storedBlocks = this.retainThinking
852
+ ? originalBlocks
853
+ : originalBlocks.filter((block) => block.type !== 'reasoning');
854
+ const metadata = this.buildMessageMetadata(originalBlocks, storedBlocks, this.retainThinking ? 'provider' : 'omit');
855
+ this.messages.push({ role: 'assistant', content: storedBlocks, metadata });
856
+ await this.persistMessages();
857
+ const toolBlocks = assistantBlocks.filter((block) => block.type === 'tool_use');
858
+ if (toolBlocks.length > 0) {
859
+ this.setBreakpoint('TOOL_PENDING');
860
+ const outcomes = await this.executeTools(toolBlocks);
861
+ if (outcomes.length > 0) {
862
+ this.messages.push({ role: 'user', content: outcomes });
863
+ this.lastSfpIndex = this.messages.length - 1;
864
+ this.stepCount++;
865
+ await this.persistMessages();
866
+ this.todoManager.onStep();
867
+ this.ensureProcessing();
868
+ return;
869
+ }
870
+ }
871
+ else {
872
+ this.lastSfpIndex = this.messages.length - 1;
873
+ }
874
+ const previousBookmark = this.lastBookmark;
875
+ const envelope = this.events.emitProgress({
876
+ channel: 'progress',
877
+ type: 'done',
878
+ step: this.stepCount,
879
+ reason: this.pendingPermissions.size > 0 ? 'interrupted' : 'completed',
880
+ });
881
+ doneEmitted = true;
882
+ this.lastBookmark = envelope.bookmark;
883
+ this.stepCount++;
884
+ this.scheduler.notifyStep(this.stepCount);
885
+ this.todoManager.onStep();
886
+ if (!previousBookmark || previousBookmark.seq !== this.lastBookmark.seq) {
887
+ await this.persistInfo();
888
+ }
889
+ this.events.emitMonitor({ channel: 'monitor', type: 'step_complete', step: this.stepCount, bookmark: envelope.bookmark });
890
+ }
891
+ catch (error) {
892
+ this.events.emitMonitor({
893
+ channel: 'monitor',
894
+ type: 'error',
895
+ severity: 'error',
896
+ phase: 'model',
897
+ message: error?.message || 'Model execution failed',
898
+ detail: { stack: error?.stack },
899
+ });
900
+ if (!doneEmitted) {
901
+ const previousBookmark = this.lastBookmark;
902
+ const envelope = this.events.emitProgress({
903
+ channel: 'progress',
904
+ type: 'done',
905
+ step: this.stepCount,
906
+ reason: 'interrupted',
907
+ });
908
+ doneEmitted = true;
909
+ this.lastBookmark = envelope.bookmark;
910
+ this.stepCount++;
911
+ this.scheduler.notifyStep(this.stepCount);
912
+ this.todoManager.onStep();
913
+ if (!previousBookmark || previousBookmark.seq !== this.lastBookmark.seq) {
914
+ await this.persistInfo();
915
+ }
916
+ }
917
+ }
918
+ finally {
919
+ this.setState('READY');
920
+ this.setBreakpoint('READY');
921
+ }
922
+ }
923
+ async executeTools(toolUses) {
924
+ const uses = toolUses.filter((block) => block.type === 'tool_use');
925
+ if (uses.length === 0) {
926
+ return [];
927
+ }
928
+ const results = new Map();
929
+ await Promise.all(uses.map((use) => this.toolRunner.run(async () => {
930
+ const result = await this.processToolCall(use);
931
+ if (result) {
932
+ results.set(use.id, result);
933
+ }
934
+ })));
935
+ await this.persistToolRecords();
936
+ const ordered = [];
937
+ for (const use of uses) {
938
+ const block = results.get(use.id);
939
+ if (block) {
940
+ ordered.push(block);
941
+ }
942
+ }
943
+ return ordered;
944
+ }
945
+ async processToolCall(toolUse) {
946
+ const tool = this.tools.get(toolUse.name);
947
+ const record = this.registerToolRecord(toolUse);
948
+ this.events.emitProgress({ channel: 'progress', type: 'tool:start', call: this.snapshotToolRecord(record.id) });
949
+ if (!tool) {
950
+ const message = `Tool not found: ${toolUse.name}`;
951
+ this.updateToolRecord(record.id, { state: 'FAILED', error: message, isError: true }, 'tool missing');
952
+ this.events.emitMonitor({ channel: 'monitor', type: 'error', severity: 'warn', phase: 'tool', message });
953
+ return this.makeToolResult(toolUse.id, {
954
+ ok: false,
955
+ error: message,
956
+ recommendations: ['确认工具是否已注册', '检查模板或配置中的工具列表'],
957
+ });
958
+ }
959
+ const validation = this.validateToolArgs(tool, toolUse.input);
960
+ if (!validation.ok) {
961
+ const message = validation.error || 'Tool input validation failed';
962
+ this.updateToolRecord(record.id, { state: 'FAILED', error: message, isError: true }, 'input schema invalid');
963
+ return this.makeToolResult(toolUse.id, {
964
+ ok: false,
965
+ error: message,
966
+ recommendations: ['检查工具入参是否符合 schema', '根据提示修正参数后重试'],
967
+ });
968
+ }
969
+ const context = {
970
+ agentId: this.agentId,
971
+ sandbox: this.sandbox,
972
+ agent: this,
973
+ services: {
974
+ todo: this.todoService,
975
+ filePool: this.filePool,
976
+ },
977
+ };
978
+ let approvalMeta;
979
+ let requireApproval = false;
980
+ const policyDecision = this.permissions.evaluate(toolUse.name);
981
+ if (policyDecision === 'deny') {
982
+ const message = 'Tool denied by policy';
983
+ this.updateToolRecord(record.id, {
984
+ state: 'DENIED',
985
+ approval: buildApproval('deny', 'policy', message),
986
+ error: message,
987
+ isError: true,
988
+ }, 'policy deny');
989
+ this.setBreakpoint('POST_TOOL');
990
+ this.events.emitProgress({ channel: 'progress', type: 'tool:end', call: this.snapshotToolRecord(record.id) });
991
+ return this.makeToolResult(toolUse.id, {
992
+ ok: false,
993
+ error: message,
994
+ recommendations: ['检查模板或权限配置的 allow/deny 列表', '如需执行该工具,请调整权限模式或审批策略'],
995
+ });
996
+ }
997
+ if (policyDecision === 'ask') {
998
+ requireApproval = true;
999
+ approvalMeta = { reason: 'Policy requires approval', tool: toolUse.name };
1000
+ }
1001
+ const decision = await this.hooks.runPreToolUse({ id: toolUse.id, name: toolUse.name, args: toolUse.input, agentId: this.agentId }, context);
1002
+ if (decision) {
1003
+ if ('decision' in decision) {
1004
+ if (decision.decision === 'ask') {
1005
+ requireApproval = true;
1006
+ approvalMeta = { ...(approvalMeta || {}), ...(decision.meta || {}) };
1007
+ }
1008
+ else if (decision.decision === 'deny') {
1009
+ const message = decision.reason || 'Denied by hook';
1010
+ this.updateToolRecord(record.id, {
1011
+ state: 'DENIED',
1012
+ approval: buildApproval('deny', 'hook', message),
1013
+ error: message,
1014
+ isError: true,
1015
+ }, 'hook deny');
1016
+ this.setBreakpoint('POST_TOOL');
1017
+ this.events.emitProgress({ channel: 'progress', type: 'tool:end', call: this.snapshotToolRecord(record.id) });
1018
+ return this.makeToolResult(toolUse.id, {
1019
+ ok: false,
1020
+ error: decision.toolResult || message,
1021
+ recommendations: ['根据 Hook 给出的原因调整输入或策略'],
1022
+ });
1023
+ }
1024
+ }
1025
+ else if ('result' in decision) {
1026
+ this.updateToolRecord(record.id, {
1027
+ state: 'COMPLETED',
1028
+ result: decision.result,
1029
+ completedAt: Date.now(),
1030
+ }, 'hook provided result');
1031
+ this.events.emitMonitor({ channel: 'monitor', type: 'tool_executed', call: this.snapshotToolRecord(record.id) });
1032
+ this.setBreakpoint('POST_TOOL');
1033
+ this.events.emitProgress({ channel: 'progress', type: 'tool:end', call: this.snapshotToolRecord(record.id) });
1034
+ return this.makeToolResult(toolUse.id, { ok: true, data: decision.result });
1035
+ }
1036
+ }
1037
+ if (requireApproval) {
1038
+ this.setBreakpoint('AWAITING_APPROVAL');
1039
+ const decisionResult = await this.requestPermission(record.id, toolUse.name, toolUse.input, approvalMeta);
1040
+ if (decisionResult === 'deny') {
1041
+ const message = approvalMeta?.reason || 'Denied by approval';
1042
+ this.updateToolRecord(record.id, { state: 'DENIED', error: message, isError: true }, 'approval denied');
1043
+ this.setBreakpoint('POST_TOOL');
1044
+ this.events.emitProgress({ channel: 'progress', type: 'tool:end', call: this.snapshotToolRecord(record.id) });
1045
+ return this.makeToolResult(toolUse.id, { ok: false, error: message });
1046
+ }
1047
+ this.setBreakpoint('PRE_TOOL');
1048
+ }
1049
+ this.setBreakpoint('PRE_TOOL');
1050
+ this.updateToolRecord(record.id, { state: 'EXECUTING', startedAt: Date.now() }, 'execution start');
1051
+ this.setBreakpoint('TOOL_EXECUTING');
1052
+ const controller = new AbortController();
1053
+ this.toolControllers.set(toolUse.id, controller);
1054
+ context.signal = controller.signal;
1055
+ const timeoutId = setTimeout(() => controller.abort(), this.toolTimeoutMs);
1056
+ try {
1057
+ const output = await tool.exec(toolUse.input, context);
1058
+ // 检查 output 是否包含 ok 字段来判断工具是否成功
1059
+ const outputOk = output && typeof output === 'object' && 'ok' in output ? output.ok : true;
1060
+ let outcome = {
1061
+ id: toolUse.id,
1062
+ name: toolUse.name,
1063
+ ok: outputOk !== false,
1064
+ content: output
1065
+ };
1066
+ outcome = await this.hooks.runPostToolUse(outcome, context);
1067
+ // NOTE: recordRead/recordEdit 已在各工具内部调用,此处不再重复调用
1068
+ // 参考: fs_read/index.ts:25, fs_write/index.ts:26, fs_edit/index.ts:29,53, fs_multi_edit/index.ts:60,92
1069
+ const success = outcome.ok !== false;
1070
+ const duration = Date.now() - (this.toolRecords.get(record.id)?.startedAt ?? Date.now());
1071
+ if (success) {
1072
+ this.updateToolRecord(record.id, {
1073
+ state: 'COMPLETED',
1074
+ result: outcome.content,
1075
+ durationMs: duration,
1076
+ completedAt: Date.now(),
1077
+ }, 'execution complete');
1078
+ this.events.emitMonitor({ channel: 'monitor', type: 'tool_executed', call: this.snapshotToolRecord(record.id) });
1079
+ // 修复双嵌套问题:检查 outcome.content 是否已经是 {ok, data} 结构
1080
+ let resultData = outcome.content;
1081
+ if (outcome.content && typeof outcome.content === 'object' && 'ok' in outcome.content && 'data' in outcome.content) {
1082
+ // 如果工具返回的是 {ok: true, data: {...}} 结构,直接使用 data 部分
1083
+ resultData = outcome.content.data;
1084
+ }
1085
+ return this.makeToolResult(toolUse.id, { ok: true, data: resultData });
1086
+ }
1087
+ else {
1088
+ const errorContent = outcome.content;
1089
+ const errorMessage = errorContent?.error || 'Tool returned failure';
1090
+ const errorType = errorContent?._validationError ? 'validation' :
1091
+ errorContent?._thrownError ? 'runtime' : 'logical';
1092
+ const isRetryable = errorType !== 'validation';
1093
+ this.updateToolRecord(record.id, {
1094
+ state: 'FAILED',
1095
+ result: outcome.content,
1096
+ error: errorMessage,
1097
+ isError: true,
1098
+ durationMs: duration,
1099
+ completedAt: Date.now(),
1100
+ }, 'tool reported failure');
1101
+ this.events.emitProgress({
1102
+ channel: 'progress',
1103
+ type: 'tool:error',
1104
+ call: this.snapshotToolRecord(record.id),
1105
+ error: errorMessage,
1106
+ });
1107
+ this.events.emitMonitor({
1108
+ channel: 'monitor',
1109
+ type: 'error',
1110
+ severity: 'warn',
1111
+ phase: 'tool',
1112
+ message: errorMessage,
1113
+ detail: { ...outcome.content, errorType, retryable: isRetryable },
1114
+ });
1115
+ const recommendations = errorContent?.recommendations || this.getErrorRecommendations(errorType, toolUse.name);
1116
+ return this.makeToolResult(toolUse.id, {
1117
+ ok: false,
1118
+ error: errorMessage,
1119
+ errorType,
1120
+ retryable: isRetryable,
1121
+ data: outcome.content,
1122
+ recommendations,
1123
+ });
1124
+ }
1125
+ }
1126
+ catch (error) {
1127
+ const isAbort = error?.name === 'AbortError';
1128
+ const message = isAbort ? 'Tool execution aborted' : error?.message || String(error);
1129
+ const errorType = isAbort ? 'aborted' : 'exception';
1130
+ this.updateToolRecord(record.id, { state: 'FAILED', error: message, isError: true }, isAbort ? 'tool aborted' : 'execution failed');
1131
+ this.events.emitProgress({
1132
+ channel: 'progress',
1133
+ type: 'tool:error',
1134
+ call: this.snapshotToolRecord(record.id),
1135
+ error: message,
1136
+ });
1137
+ this.events.emitMonitor({
1138
+ channel: 'monitor',
1139
+ type: 'error',
1140
+ severity: isAbort ? 'warn' : 'error',
1141
+ phase: 'tool',
1142
+ message,
1143
+ detail: { errorType, stack: error?.stack },
1144
+ });
1145
+ const recommendations = isAbort
1146
+ ? ['检查是否手动中断', '根据需要重新触发工具', '考虑调整超时时间']
1147
+ : this.getErrorRecommendations('runtime', toolUse.name);
1148
+ return this.makeToolResult(toolUse.id, {
1149
+ ok: false,
1150
+ error: message,
1151
+ errorType,
1152
+ retryable: !isAbort,
1153
+ recommendations,
1154
+ });
1155
+ }
1156
+ finally {
1157
+ clearTimeout(timeoutId);
1158
+ this.toolControllers.delete(toolUse.id);
1159
+ this.setBreakpoint('POST_TOOL');
1160
+ this.events.emitProgress({ channel: 'progress', type: 'tool:end', call: this.snapshotToolRecord(record.id) });
1161
+ }
1162
+ }
1163
+ registerToolRecord(toolUse) {
1164
+ const now = Date.now();
1165
+ const record = {
1166
+ id: toolUse.id,
1167
+ name: toolUse.name,
1168
+ input: toolUse.input,
1169
+ state: 'PENDING',
1170
+ approval: { required: false },
1171
+ createdAt: now,
1172
+ updatedAt: now,
1173
+ auditTrail: [{ state: 'PENDING', timestamp: now }],
1174
+ };
1175
+ this.toolRecords.set(record.id, record);
1176
+ return record;
1177
+ }
1178
+ updateToolRecord(id, update, auditNote) {
1179
+ const record = this.toolRecords.get(id);
1180
+ if (!record)
1181
+ return;
1182
+ const now = Date.now();
1183
+ if (update.state && update.state !== record.state) {
1184
+ record.auditTrail.push({ state: update.state, timestamp: now, note: auditNote });
1185
+ }
1186
+ else if (auditNote) {
1187
+ record.auditTrail.push({ state: record.state, timestamp: now, note: auditNote });
1188
+ }
1189
+ Object.assign(record, update, { updatedAt: now });
1190
+ }
1191
+ snapshotToolRecord(id) {
1192
+ const record = this.toolRecords.get(id);
1193
+ if (!record)
1194
+ throw new Error(`Tool record not found: ${id}`);
1195
+ return {
1196
+ id: record.id,
1197
+ name: record.name,
1198
+ state: record.state,
1199
+ approval: record.approval,
1200
+ result: record.result,
1201
+ error: record.error,
1202
+ isError: record.isError,
1203
+ durationMs: record.durationMs,
1204
+ startedAt: record.startedAt,
1205
+ completedAt: record.completedAt,
1206
+ inputPreview: this.preview(record.input),
1207
+ auditTrail: [...record.auditTrail],
1208
+ };
1209
+ }
1210
+ normalizeToolRecord(record) {
1211
+ const timestamp = record.updatedAt ?? record.createdAt ?? Date.now();
1212
+ const auditTrail = record.auditTrail && record.auditTrail.length > 0
1213
+ ? record.auditTrail.map((entry) => ({ ...entry }))
1214
+ : [{ state: record.state, timestamp }];
1215
+ return { ...record, auditTrail };
1216
+ }
1217
+ preview(value, limit = 200) {
1218
+ const text = typeof value === 'string' ? value : JSON.stringify(value);
1219
+ return text.length > limit ? `${text.slice(0, limit)}…` : text;
1220
+ }
1221
+ validateBlocks(blocks) {
1222
+ const config = this.model.toConfig();
1223
+ const multimodal = config.multimodal || {};
1224
+ const mode = multimodal.mode ?? 'url';
1225
+ const maxBase64Bytes = multimodal.maxBase64Bytes ?? 20000000;
1226
+ const allowMimeTypes = multimodal.allowMimeTypes ?? [
1227
+ 'image/jpeg',
1228
+ 'image/png',
1229
+ 'image/gif',
1230
+ 'image/webp',
1231
+ 'application/pdf',
1232
+ ];
1233
+ for (const block of blocks) {
1234
+ if (block.type === 'image' || block.type === 'audio' || block.type === 'file') {
1235
+ const url = block.url;
1236
+ const fileId = block.file_id;
1237
+ const base64 = block.base64;
1238
+ const mimeType = block.mime_type;
1239
+ if (!url && !fileId && !base64) {
1240
+ throw new errors_1.MultimodalValidationError(`Missing url/file_id/base64 for ${block.type} block.`);
1241
+ }
1242
+ if (base64) {
1243
+ if (mode !== 'url+base64') {
1244
+ throw new errors_1.MultimodalValidationError(`Base64 is not allowed when multimodal.mode=${mode}.`);
1245
+ }
1246
+ if (!mimeType) {
1247
+ throw new errors_1.MultimodalValidationError(`mime_type is required for base64 ${block.type} blocks.`);
1248
+ }
1249
+ const bytes = this.estimateBase64Bytes(base64);
1250
+ if (bytes > maxBase64Bytes) {
1251
+ throw new errors_1.MultimodalValidationError(`base64 payload too large (${bytes} bytes > ${maxBase64Bytes} bytes).`);
1252
+ }
1253
+ }
1254
+ if (mimeType && !allowMimeTypes.includes(mimeType)) {
1255
+ throw new errors_1.MultimodalValidationError(`mime_type not allowed: ${mimeType}`);
1256
+ }
1257
+ if (url) {
1258
+ const allowedSchemes = block.type === 'file' ? ['http', 'https', 'gs'] : ['http', 'https'];
1259
+ const scheme = url.split(':')[0];
1260
+ if (!allowedSchemes.includes(scheme)) {
1261
+ throw new errors_1.MultimodalValidationError(`Unsupported url scheme for ${block.type}: ${scheme}`);
1262
+ }
1263
+ }
1264
+ }
1265
+ else if (block.type !== 'text' &&
1266
+ block.type !== 'tool_use' &&
1267
+ block.type !== 'tool_result' &&
1268
+ block.type !== 'reasoning') {
1269
+ throw new errors_1.UnsupportedContentBlockError(`Unsupported content block type: ${block.type}`);
1270
+ }
1271
+ }
1272
+ }
1273
+ estimateBase64Bytes(payload) {
1274
+ const normalized = payload.replace(/\s+/g, '');
1275
+ const padding = normalized.endsWith('==') ? 2 : normalized.endsWith('=') ? 1 : 0;
1276
+ return Math.floor((normalized.length * 3) / 4) - padding;
1277
+ }
1278
+ async resolveMultimodalBlocks(blocks) {
1279
+ const model = this.model;
1280
+ if (typeof model.uploadFile !== 'function') {
1281
+ return blocks;
1282
+ }
1283
+ const provider = this.model.toConfig().provider;
1284
+ const resolved = [];
1285
+ for (const block of blocks) {
1286
+ if ((block.type === 'image' || block.type === 'file') &&
1287
+ block.base64 &&
1288
+ block.mime_type &&
1289
+ !block.file_id &&
1290
+ !block.url) {
1291
+ try {
1292
+ const buffer = Buffer.from(block.base64, 'base64');
1293
+ const hash = this.computeSha256(buffer);
1294
+ const cached = this.mediaCache.get(hash);
1295
+ if (cached && cached.provider === provider && (cached.fileId || cached.fileUri)) {
1296
+ resolved.push(this.applyMediaCache(block, cached));
1297
+ continue;
1298
+ }
1299
+ const uploadResult = await model.uploadFile({
1300
+ data: buffer,
1301
+ mimeType: block.mime_type,
1302
+ filename: block.filename,
1303
+ kind: block.type,
1304
+ });
1305
+ if (uploadResult?.fileId || uploadResult?.fileUri) {
1306
+ const record = {
1307
+ key: hash,
1308
+ provider,
1309
+ mimeType: block.mime_type,
1310
+ sizeBytes: buffer.length,
1311
+ fileId: uploadResult.fileId,
1312
+ fileUri: uploadResult.fileUri,
1313
+ createdAt: Date.now(),
1314
+ };
1315
+ this.mediaCache.set(hash, record);
1316
+ await this.persistMediaCache();
1317
+ resolved.push(this.applyMediaCache(block, record));
1318
+ continue;
1319
+ }
1320
+ }
1321
+ catch (error) {
1322
+ this.events.emitMonitor({
1323
+ channel: 'monitor',
1324
+ type: 'error',
1325
+ severity: 'warn',
1326
+ phase: 'system',
1327
+ message: 'media upload failed',
1328
+ detail: { error: error?.message || String(error) },
1329
+ });
1330
+ }
1331
+ }
1332
+ resolved.push(block);
1333
+ }
1334
+ return resolved;
1335
+ }
1336
+ applyMediaCache(block, record) {
1337
+ if (block.type !== 'file' && block.type !== 'image') {
1338
+ return block;
1339
+ }
1340
+ const next = { ...block };
1341
+ if (record.fileId) {
1342
+ next.file_id = record.fileId;
1343
+ }
1344
+ else if (record.fileUri) {
1345
+ next.file_id = record.fileUri;
1346
+ }
1347
+ next.base64 = undefined;
1348
+ return next;
1349
+ }
1350
+ computeSha256(buffer) {
1351
+ return (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
1352
+ }
1353
+ async persistMediaCache() {
1354
+ await this.persistentStore.saveMediaCache(this.agentId, Array.from(this.mediaCache.values()));
1355
+ }
1356
+ splitThinkBlocksIfNeeded(blocks) {
1357
+ const config = this.model.toConfig();
1358
+ const transport = config.reasoningTransport ??
1359
+ (config.provider === 'openai' || config.provider === 'gemini' ? 'text' : 'provider');
1360
+ if (transport !== 'text') {
1361
+ return blocks;
1362
+ }
1363
+ const output = [];
1364
+ for (const block of blocks) {
1365
+ if (block.type !== 'text') {
1366
+ output.push(block);
1367
+ continue;
1368
+ }
1369
+ const parts = this.splitThinkText(block.text);
1370
+ if (parts.length === 0) {
1371
+ output.push(block);
1372
+ }
1373
+ else {
1374
+ output.push(...parts);
1375
+ }
1376
+ }
1377
+ return output;
1378
+ }
1379
+ splitThinkText(text) {
1380
+ const blocks = [];
1381
+ const regex = /<think>([\s\S]*?)<\/think>/g;
1382
+ let match;
1383
+ let cursor = 0;
1384
+ let matched = false;
1385
+ while ((match = regex.exec(text)) !== null) {
1386
+ matched = true;
1387
+ const before = text.slice(cursor, match.index);
1388
+ if (before) {
1389
+ blocks.push({ type: 'text', text: before });
1390
+ }
1391
+ const reasoning = match[1] || '';
1392
+ blocks.push({ type: 'reasoning', reasoning });
1393
+ cursor = match.index + match[0].length;
1394
+ }
1395
+ if (!matched) {
1396
+ return [];
1397
+ }
1398
+ const after = text.slice(cursor);
1399
+ if (after) {
1400
+ blocks.push({ type: 'text', text: after });
1401
+ }
1402
+ return blocks;
1403
+ }
1404
+ shouldPreserveBlocks(blocks) {
1405
+ const hasReasoning = blocks.some((block) => block.type === 'reasoning');
1406
+ const hasTools = blocks.some((block) => block.type === 'tool_use' || block.type === 'tool_result');
1407
+ const hasMultimodal = blocks.some((block) => block.type === 'image' || block.type === 'audio' || block.type === 'file');
1408
+ const hasMixed = blocks.length > 1 && (hasReasoning || hasMultimodal);
1409
+ return hasMixed || (hasReasoning && hasTools);
1410
+ }
1411
+ buildMessageMetadata(original, stored, transport) {
1412
+ if (!this.shouldPreserveBlocks(original) && original.length === stored.length) {
1413
+ return undefined;
1414
+ }
1415
+ return {
1416
+ content_blocks: original,
1417
+ transport,
1418
+ };
1419
+ }
1420
+ async requestPermission(id, _toolName, _args, meta) {
1421
+ const approval = {
1422
+ required: true,
1423
+ decision: undefined,
1424
+ decidedAt: undefined,
1425
+ decidedBy: undefined,
1426
+ note: undefined,
1427
+ meta,
1428
+ };
1429
+ this.updateToolRecord(id, { state: 'APPROVAL_REQUIRED', approval }, 'awaiting approval');
1430
+ await this.persistToolRecords();
1431
+ return new Promise((resolve) => {
1432
+ this.pendingPermissions.set(id, {
1433
+ resolve: (decision, note) => {
1434
+ this.updateToolRecord(id, {
1435
+ approval: buildApproval(decision, 'api', note),
1436
+ state: decision === 'allow' ? 'APPROVED' : 'DENIED',
1437
+ error: decision === 'deny' ? note : undefined,
1438
+ isError: decision === 'deny',
1439
+ }, decision === 'allow' ? 'approval granted' : 'approval denied');
1440
+ if (decision === 'allow') {
1441
+ this.setBreakpoint('PRE_TOOL');
1442
+ }
1443
+ else {
1444
+ this.setBreakpoint('POST_TOOL');
1445
+ }
1446
+ resolve(decision);
1447
+ },
1448
+ });
1449
+ this.events.emitControl({
1450
+ channel: 'control',
1451
+ type: 'permission_required',
1452
+ call: this.snapshotToolRecord(id),
1453
+ respond: async (decision, opts) => {
1454
+ await this.decide(id, decision, opts?.note);
1455
+ },
1456
+ });
1457
+ this.setState('PAUSED');
1458
+ this.setBreakpoint('AWAITING_APPROVAL');
1459
+ });
1460
+ }
1461
+ findLastSfp() {
1462
+ for (let i = this.messages.length - 1; i >= 0; i--) {
1463
+ const msg = this.messages[i];
1464
+ if (msg.role === 'user')
1465
+ return i;
1466
+ if (msg.role === 'assistant' && !msg.content.some((block) => block.type === 'tool_use'))
1467
+ return i;
1468
+ }
1469
+ return -1;
1470
+ }
1471
+ async appendSyntheticToolResults(note) {
1472
+ const last = this.messages[this.messages.length - 1];
1473
+ if (!last || last.role !== 'assistant')
1474
+ return;
1475
+ const toolUses = last.content.filter((block) => block.type === 'tool_use');
1476
+ if (!toolUses.length)
1477
+ return;
1478
+ const resultIds = new Set();
1479
+ for (const message of this.messages) {
1480
+ for (const block of message.content) {
1481
+ if (block.type === 'tool_result')
1482
+ resultIds.add(block.tool_use_id);
1483
+ }
1484
+ }
1485
+ const synthetic = [];
1486
+ for (const tu of toolUses) {
1487
+ if (!resultIds.has(tu.id)) {
1488
+ const sealedResult = this.buildSealPayload('TOOL_RESULT_MISSING', tu.id, note);
1489
+ this.updateToolRecord(tu.id, { state: 'SEALED', error: sealedResult.message, isError: true }, 'sealed due to interrupt');
1490
+ synthetic.push(this.makeToolResult(tu.id, sealedResult.payload));
1491
+ }
1492
+ }
1493
+ if (synthetic.length) {
1494
+ this.messages.push({ role: 'user', content: synthetic });
1495
+ await this.persistMessages();
1496
+ await this.persistToolRecords();
1497
+ }
1498
+ }
1499
+ async autoSealIncompleteCalls(note = 'Sealed due to crash while executing; verify potential side effects.') {
1500
+ const sealedSnapshots = [];
1501
+ const resultIds = new Set();
1502
+ for (const message of this.messages) {
1503
+ for (const block of message.content) {
1504
+ if (block.type === 'tool_result') {
1505
+ resultIds.add(block.tool_use_id);
1506
+ }
1507
+ }
1508
+ }
1509
+ const synthetic = [];
1510
+ for (const [id, record] of this.toolRecords) {
1511
+ if (['COMPLETED', 'FAILED', 'DENIED', 'SEALED'].includes(record.state))
1512
+ continue;
1513
+ const sealedResult = this.buildSealPayload(record.state, id, note, record);
1514
+ this.updateToolRecord(id, { state: 'SEALED', error: sealedResult.message, isError: true, completedAt: Date.now() }, 'auto seal');
1515
+ const snapshot = this.snapshotToolRecord(id);
1516
+ sealedSnapshots.push(snapshot);
1517
+ if (!resultIds.has(id)) {
1518
+ synthetic.push(this.makeToolResult(id, sealedResult.payload));
1519
+ }
1520
+ }
1521
+ if (synthetic.length > 0) {
1522
+ this.messages.push({ role: 'user', content: synthetic });
1523
+ await this.persistMessages();
1524
+ }
1525
+ await this.persistToolRecords();
1526
+ return sealedSnapshots;
1527
+ }
1528
+ validateToolArgs(tool, args) {
1529
+ if (!tool.input_schema) {
1530
+ return { ok: true };
1531
+ }
1532
+ const key = JSON.stringify(tool.input_schema);
1533
+ let validator = this.validatorCache.get(key);
1534
+ if (!validator) {
1535
+ validator = this.ajv.compile(tool.input_schema);
1536
+ this.validatorCache.set(key, validator);
1537
+ }
1538
+ const valid = validator(args);
1539
+ if (!valid) {
1540
+ return {
1541
+ ok: false,
1542
+ error: this.ajv.errorsText(validator.errors, { separator: '\n' }),
1543
+ };
1544
+ }
1545
+ return { ok: true };
1546
+ }
1547
+ makeToolResult(toolUseId, payload) {
1548
+ return {
1549
+ type: 'tool_result',
1550
+ tool_use_id: toolUseId,
1551
+ content: {
1552
+ ok: payload.ok,
1553
+ data: payload.data,
1554
+ error: payload.error,
1555
+ errorType: payload.errorType,
1556
+ retryable: payload.retryable,
1557
+ note: payload.note,
1558
+ recommendations: payload.recommendations,
1559
+ },
1560
+ is_error: payload.ok ? false : true,
1561
+ };
1562
+ }
1563
+ buildSealPayload(state, toolUseId, fallbackNote, record) {
1564
+ const baseMessage = (() => {
1565
+ switch (state) {
1566
+ case 'APPROVAL_REQUIRED':
1567
+ return '工具在等待审批时会话中断,系统已自动封口。';
1568
+ case 'APPROVED':
1569
+ return '工具已通过审批但尚未执行,系统已自动封口。';
1570
+ case 'EXECUTING':
1571
+ return '工具执行过程中会话中断,系统已自动封口。';
1572
+ case 'PENDING':
1573
+ return '工具刚准备执行时会话中断,系统已自动封口。';
1574
+ default:
1575
+ return fallbackNote;
1576
+ }
1577
+ })();
1578
+ const recommendations = (() => {
1579
+ switch (state) {
1580
+ case 'APPROVAL_REQUIRED':
1581
+ return ['确认审批是否仍然需要', '如需继续,请重新触发工具并完成审批'];
1582
+ case 'APPROVED':
1583
+ return ['确认工具输入是否仍然有效', '如需执行,请重新触发工具'];
1584
+ case 'EXECUTING':
1585
+ return ['检查工具可能产生的副作用', '确认外部系统状态后再重试'];
1586
+ case 'PENDING':
1587
+ return ['确认工具参数是否正确', '再次触发工具以继续流程'];
1588
+ default:
1589
+ return ['检查封口说明并决定是否重试工具'];
1590
+ }
1591
+ })();
1592
+ const detail = {
1593
+ status: state,
1594
+ startedAt: record?.startedAt,
1595
+ approval: record?.approval,
1596
+ toolId: toolUseId,
1597
+ note: baseMessage,
1598
+ };
1599
+ return {
1600
+ payload: {
1601
+ ok: false,
1602
+ error: baseMessage,
1603
+ data: detail,
1604
+ recommendations,
1605
+ },
1606
+ message: baseMessage,
1607
+ };
1608
+ }
1609
+ wrapReminder(content, options) {
1610
+ if (options?.skipStandardEnding)
1611
+ return content;
1612
+ return [
1613
+ '<system-reminder>',
1614
+ content,
1615
+ '',
1616
+ 'This is a system reminder. DO NOT respond to this message directly.',
1617
+ 'DO NOT mention this reminder to the user.',
1618
+ 'Continue with your current task.',
1619
+ '</system-reminder>',
1620
+ ].join('\n');
1621
+ }
1622
+ getToolSchemas() {
1623
+ return Array.from(this.tools.values()).map((tool) => ({
1624
+ name: tool.name,
1625
+ description: tool.description,
1626
+ input_schema: tool.input_schema,
1627
+ }));
1628
+ }
1629
+ setState(state) {
1630
+ if (this.state === state)
1631
+ return;
1632
+ this.state = state;
1633
+ this.events.emitMonitor({ channel: 'monitor', type: 'state_changed', state });
1634
+ }
1635
+ async persistMessages() {
1636
+ await this.persistentStore.saveMessages(this.agentId, this.messages);
1637
+ await this.persistInfo();
1638
+ const snapshot = {
1639
+ agentId: this.agentId,
1640
+ messages: this.messages.map((message) => ({
1641
+ role: message.role,
1642
+ content: message.content.map((block) => ({ ...block })),
1643
+ metadata: message.metadata ? { ...message.metadata } : undefined,
1644
+ })),
1645
+ lastBookmark: this.lastBookmark,
1646
+ };
1647
+ await this.hooks.runMessagesChanged(snapshot);
1648
+ }
1649
+ async persistToolRecords() {
1650
+ await this.persistentStore.saveToolCallRecords(this.agentId, Array.from(this.toolRecords.values()));
1651
+ }
1652
+ async persistInfo() {
1653
+ const metadata = {
1654
+ agentId: this.agentId,
1655
+ templateId: this.template.id,
1656
+ templateVersion: this.config.templateVersion || this.template.version,
1657
+ sandboxConfig: this.sandboxConfig,
1658
+ modelConfig: this.model.toConfig(),
1659
+ tools: this.toolDescriptors,
1660
+ exposeThinking: this.exposeThinking,
1661
+ retainThinking: this.retainThinking,
1662
+ multimodalContinuation: this.multimodalContinuation,
1663
+ multimodalRetention: { keepRecent: this.multimodalRetentionKeepRecent },
1664
+ permission: this.permission,
1665
+ todo: this.todoConfig,
1666
+ subagents: this.subagents,
1667
+ context: this.config.context,
1668
+ createdAt: this.createdAt,
1669
+ updatedAt: new Date().toISOString(),
1670
+ configVersion: CONFIG_VERSION,
1671
+ metadata: this.config.metadata,
1672
+ lineage: this.lineage,
1673
+ breakpoint: this.breakpoints.getCurrent(),
1674
+ };
1675
+ const info = {
1676
+ agentId: this.agentId,
1677
+ templateId: this.template.id,
1678
+ createdAt: this.createdAt,
1679
+ lineage: metadata.lineage || [],
1680
+ configVersion: CONFIG_VERSION,
1681
+ messageCount: this.messages.length,
1682
+ lastSfpIndex: this.lastSfpIndex,
1683
+ lastBookmark: this.lastBookmark,
1684
+ };
1685
+ info.metadata = metadata;
1686
+ await this.persistentStore.saveInfo(this.agentId, info);
1687
+ }
1688
+ registerTodoTools() {
1689
+ const read = todo_read_1.TodoRead;
1690
+ const write = todo_write_1.TodoWrite;
1691
+ this.tools.set(read.name, read);
1692
+ this.tools.set(write.name, write);
1693
+ const descriptorNames = new Set(this.toolDescriptors.map((d) => d.name));
1694
+ if (!descriptorNames.has(read.name)) {
1695
+ const descriptor = read.toDescriptor();
1696
+ this.toolDescriptors.push(descriptor);
1697
+ this.toolDescriptorIndex.set(descriptor.name, descriptor);
1698
+ }
1699
+ if (!descriptorNames.has(write.name)) {
1700
+ const descriptor = write.toDescriptor();
1701
+ this.toolDescriptors.push(descriptor);
1702
+ this.toolDescriptorIndex.set(descriptor.name, descriptor);
1703
+ }
1704
+ }
1705
+ // ========== 工具说明书自动注入 ==========
1706
+ /**
1707
+ * 收集所有工具的使用说明
1708
+ */
1709
+ collectToolPrompts() {
1710
+ const prompts = [];
1711
+ for (const tool of this.tools.values()) {
1712
+ if (tool.prompt) {
1713
+ const promptText = typeof tool.prompt === 'string' ? tool.prompt : undefined;
1714
+ if (promptText) {
1715
+ prompts.push({
1716
+ name: tool.name,
1717
+ prompt: promptText,
1718
+ });
1719
+ }
1720
+ }
1721
+ }
1722
+ return prompts;
1723
+ }
1724
+ /**
1725
+ * 渲染工具手册
1726
+ */
1727
+ renderManual(prompts) {
1728
+ if (prompts.length === 0)
1729
+ return '';
1730
+ const sections = prompts.map(({ name, prompt }) => {
1731
+ return `**${name}**\n${prompt}`;
1732
+ });
1733
+ return `\n\n### Tools Manual\n\nThe following tools are available for your use. Please read their usage guidance carefully:\n\n${sections.join('\n\n')}`;
1734
+ }
1735
+ /**
1736
+ * 刷新工具手册(运行时工具变更时调用)
1737
+ */
1738
+ refreshToolManual() {
1739
+ // 移除旧的 Tools Manual 部分
1740
+ const manualPattern = /\n\n### Tools Manual\n\n[\s\S]*$/;
1741
+ if (this.template.systemPrompt) {
1742
+ this.template.systemPrompt = this.template.systemPrompt.replace(manualPattern, '');
1743
+ }
1744
+ // 重新注入
1745
+ this.injectManualIntoSystemPrompt();
1746
+ }
1747
+ /**
1748
+ * 根据错误类型生成建议
1749
+ */
1750
+ getErrorRecommendations(errorType, toolName) {
1751
+ switch (errorType) {
1752
+ case 'validation':
1753
+ return [
1754
+ '检查工具参数是否符合schema要求',
1755
+ '确认所有必填参数已提供',
1756
+ '检查参数类型是否正确',
1757
+ '参考工具手册中的参数说明'
1758
+ ];
1759
+ case 'runtime':
1760
+ return [
1761
+ '检查系统资源是否可用',
1762
+ '确认文件/路径是否存在且有权限',
1763
+ '考虑添加错误处理逻辑',
1764
+ '可以重试该操作'
1765
+ ];
1766
+ case 'logical':
1767
+ if (toolName.startsWith('fs_')) {
1768
+ return [
1769
+ '确认文件内容是否符合预期',
1770
+ '检查文件是否被外部修改',
1771
+ '验证路径和模式是否正确',
1772
+ '可以先用 fs_read 确认文件状态'
1773
+ ];
1774
+ }
1775
+ else if (toolName.startsWith('bash_')) {
1776
+ return [
1777
+ '检查命令语法是否正确',
1778
+ '确认命令在沙箱环境中可执行',
1779
+ '查看stderr输出了解详细错误',
1780
+ '考虑调整超时时间或拆分命令'
1781
+ ];
1782
+ }
1783
+ else {
1784
+ return [
1785
+ '检查工具逻辑是否符合预期',
1786
+ '验证输入数据的完整性',
1787
+ '考虑重试或使用替代方案',
1788
+ '查看错误详情调整策略'
1789
+ ];
1790
+ }
1791
+ default:
1792
+ return [
1793
+ '查看错误信息调整输入',
1794
+ '考虑使用替代工具',
1795
+ '必要时寻求人工协助'
1796
+ ];
1797
+ }
1798
+ }
1799
+ /**
1800
+ * 将工具手册注入到系统提示中
1801
+ */
1802
+ injectManualIntoSystemPrompt() {
1803
+ const prompts = this.collectToolPrompts();
1804
+ if (prompts.length === 0)
1805
+ return;
1806
+ const manual = this.renderManual(prompts);
1807
+ // 追加到模板的 systemPrompt
1808
+ if (this.template.systemPrompt) {
1809
+ this.template.systemPrompt += manual;
1810
+ }
1811
+ else {
1812
+ this.template.systemPrompt = manual;
1813
+ }
1814
+ // 发出 Monitor 事件
1815
+ this.events.emitMonitor({
1816
+ channel: 'monitor',
1817
+ type: 'tool_manual_updated',
1818
+ tools: prompts.map((p) => p.name),
1819
+ timestamp: Date.now(),
1820
+ });
1821
+ }
1822
+ /**
1823
+ * 将skills元数据注入到系统提示中
1824
+ * 参考openskills设计,使用<available_skills> XML格式
1825
+ */
1826
+ async injectSkillsMetadataIntoSystemPrompt() {
1827
+ logger_1.logger.log('[Agent] injectSkillsMetadataIntoSystemPrompt: 开始执行');
1828
+ if (!this.skillsManager) {
1829
+ logger_1.logger.log('[Agent] injectSkillsMetadataIntoSystemPrompt: skillsManager未定义,跳过');
1830
+ return;
1831
+ }
1832
+ try {
1833
+ logger_1.logger.log('[Agent] injectSkillsMetadataIntoSystemPrompt: 正在获取skills元数据...');
1834
+ // 获取所有skills的元数据
1835
+ const skills = await this.skillsManager.getSkillsMetadata();
1836
+ logger_1.logger.log(`[Agent] injectSkillsMetadataIntoSystemPrompt: 找到${skills.length}个skills`);
1837
+ if (skills.length === 0) {
1838
+ logger_1.logger.log('[Agent] injectSkillsMetadataIntoSystemPrompt: skills列表为空,跳过');
1839
+ return;
1840
+ }
1841
+ // 导入XML生成器
1842
+ const { generateSkillsMetadataXml } = await Promise.resolve().then(() => __importStar(require('./skills/xml-generator')));
1843
+ // 生成XML格式的skills元数据
1844
+ const skillsXml = generateSkillsMetadataXml(skills);
1845
+ logger_1.logger.log(`[Agent] injectSkillsMetadataIntoSystemPrompt: 生成XML完成,长度=${skillsXml.length}`);
1846
+ // 注入到模板的 systemPrompt
1847
+ if (this.template.systemPrompt) {
1848
+ this.template.systemPrompt += skillsXml;
1849
+ }
1850
+ else {
1851
+ this.template.systemPrompt = skillsXml;
1852
+ }
1853
+ // 发出 Monitor 事件
1854
+ this.events.emitMonitor({
1855
+ channel: 'monitor',
1856
+ type: 'skills_metadata_updated',
1857
+ skills: skills.map(s => s.name),
1858
+ timestamp: Date.now(),
1859
+ });
1860
+ logger_1.logger.log(`[Agent] ✓ Injected ${skills.length} skill(s) metadata into system prompt`);
1861
+ // 输出完整的system prompt以便检查
1862
+ logger_1.logger.log(`[Agent] ========== Complete System Prompt ==========`);
1863
+ logger_1.logger.log(this.template.systemPrompt);
1864
+ logger_1.logger.log(`[Agent] ========== End of System Prompt ==========`);
1865
+ }
1866
+ catch (error) {
1867
+ logger_1.logger.error('[Agent] Failed to inject skills metadata:', error?.message || error);
1868
+ logger_1.logger.error('[Agent] Error stack:', error?.stack);
1869
+ }
1870
+ }
1871
+ /**
1872
+ * 刷新skills元数据(运行时skills变更时调用)
1873
+ * 由于支持热更新,可在执行过程中调用此方法
1874
+ */
1875
+ async refreshSkillsMetadata() {
1876
+ if (!this.skillsManager) {
1877
+ return;
1878
+ }
1879
+ // 移除旧的 <skills_system> 部分
1880
+ const skillsSystemPattern = /<skills_system[\s\S]*?<\/skills_system>\s*/;
1881
+ if (this.template.systemPrompt) {
1882
+ this.template.systemPrompt = this.template.systemPrompt.replace(skillsSystemPattern, '');
1883
+ }
1884
+ // 重新注入
1885
+ await this.injectSkillsMetadataIntoSystemPrompt();
1886
+ }
1887
+ enqueueMessage(message, kind) {
1888
+ if (!message.metadata && this.shouldPreserveBlocks(message.content)) {
1889
+ message.metadata = this.buildMessageMetadata(message.content, message.content, 'provider');
1890
+ }
1891
+ this.messages.push(message);
1892
+ if (kind === 'user') {
1893
+ this.lastSfpIndex = this.messages.length - 1;
1894
+ this.stepCount++;
1895
+ }
1896
+ }
1897
+ handleExternalFileChange(path, mtime) {
1898
+ const relPath = this.relativePath(path);
1899
+ this.events.emitMonitor({ channel: 'monitor', type: 'file_changed', path: relPath, mtime });
1900
+ const reminder = `检测到外部修改:${relPath}。请重新使用 fs_read 确认文件内容,并在必要时向用户同步。`;
1901
+ this.remind(reminder, { category: 'file', priority: 'medium' });
1902
+ }
1903
+ relativePath(absPath) {
1904
+ const path = require('path');
1905
+ return path.relative(this.sandbox.workDir || process.cwd(), this.sandbox.fs.resolve(absPath));
1906
+ }
1907
+ static generateAgentId() {
1908
+ const chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
1909
+ const now = Date.now();
1910
+ const timePart = encodeUlid(now, 10, chars);
1911
+ const random = Array.from({ length: 16 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
1912
+ return `agt-${timePart}${random}`;
1913
+ }
1914
+ }
1915
+ exports.Agent = Agent;
1916
+ function ensureModelFactory(factory) {
1917
+ if (factory)
1918
+ return factory;
1919
+ return (config) => {
1920
+ if (config.provider === 'anthropic') {
1921
+ if (!config.apiKey) {
1922
+ throw new Error('Anthropic provider requires apiKey');
1923
+ }
1924
+ return new provider_1.AnthropicProvider(config.apiKey, config.model, config.baseUrl, config.proxyUrl, {
1925
+ reasoningTransport: config.reasoningTransport,
1926
+ extraHeaders: config.extraHeaders,
1927
+ extraBody: config.extraBody,
1928
+ providerOptions: config.providerOptions,
1929
+ multimodal: config.multimodal,
1930
+ });
1931
+ }
1932
+ if (config.provider === 'openai') {
1933
+ if (!config.apiKey) {
1934
+ throw new Error('OpenAI provider requires apiKey');
1935
+ }
1936
+ return new provider_1.OpenAIProvider(config.apiKey, config.model, config.baseUrl, config.proxyUrl, {
1937
+ reasoningTransport: config.reasoningTransport,
1938
+ extraHeaders: config.extraHeaders,
1939
+ extraBody: config.extraBody,
1940
+ providerOptions: config.providerOptions,
1941
+ multimodal: config.multimodal,
1942
+ });
1943
+ }
1944
+ if (config.provider === 'gemini') {
1945
+ if (!config.apiKey) {
1946
+ throw new Error('Gemini provider requires apiKey');
1947
+ }
1948
+ return new provider_1.GeminiProvider(config.apiKey, config.model, config.baseUrl, config.proxyUrl, {
1949
+ reasoningTransport: config.reasoningTransport,
1950
+ extraHeaders: config.extraHeaders,
1951
+ extraBody: config.extraBody,
1952
+ providerOptions: config.providerOptions,
1953
+ multimodal: config.multimodal,
1954
+ });
1955
+ }
1956
+ if (config.provider === 'glm') {
1957
+ if (!config.apiKey) {
1958
+ throw new Error('GLM provider requires apiKey');
1959
+ }
1960
+ if (!config.baseUrl) {
1961
+ throw new Error('GLM provider requires baseUrl');
1962
+ }
1963
+ return new provider_1.OpenAIProvider(config.apiKey, config.model, config.baseUrl, config.proxyUrl, {
1964
+ reasoningTransport: config.reasoningTransport ?? 'provider',
1965
+ reasoning: {
1966
+ fieldName: 'reasoning_content',
1967
+ requestParams: { thinking: { type: 'enabled', clear_thinking: false } },
1968
+ },
1969
+ extraHeaders: config.extraHeaders,
1970
+ extraBody: config.extraBody,
1971
+ providerOptions: config.providerOptions,
1972
+ multimodal: config.multimodal,
1973
+ });
1974
+ }
1975
+ if (config.provider === 'minimax') {
1976
+ if (!config.apiKey) {
1977
+ throw new Error('Minimax provider requires apiKey');
1978
+ }
1979
+ if (!config.baseUrl) {
1980
+ throw new Error('Minimax provider requires baseUrl');
1981
+ }
1982
+ return new provider_1.OpenAIProvider(config.apiKey, config.model, config.baseUrl, config.proxyUrl, {
1983
+ reasoningTransport: config.reasoningTransport ?? 'provider',
1984
+ reasoning: {
1985
+ fieldName: 'reasoning_details',
1986
+ requestParams: { reasoning_split: true },
1987
+ },
1988
+ extraHeaders: config.extraHeaders,
1989
+ extraBody: config.extraBody,
1990
+ providerOptions: config.providerOptions,
1991
+ multimodal: config.multimodal,
1992
+ });
1993
+ }
1994
+ throw new Error(`Model factory not provided for provider: ${config.provider}`);
1995
+ };
1996
+ }
1997
+ function resolveTools(config, template, registry, templateRegistry) {
1998
+ const requested = config.tools ?? (template.tools === '*' ? registry.list() : template.tools || []);
1999
+ const instances = [];
2000
+ const descriptors = [];
2001
+ for (const id of requested) {
2002
+ const creationConfig = buildToolConfig(id, template, templateRegistry);
2003
+ const tool = registry.create(id, creationConfig);
2004
+ instances.push(tool);
2005
+ descriptors.push(tool.toDescriptor());
2006
+ }
2007
+ return { instances, descriptors };
2008
+ }
2009
+ function buildToolConfig(id, template, templateRegistry) {
2010
+ if (id === 'task_run') {
2011
+ const allowed = template.runtime?.subagents?.templates;
2012
+ const templates = allowed && allowed.length > 0 ? allowed.map((tplId) => templateRegistry.get(tplId)) : templateRegistry.list();
2013
+ return { templates };
2014
+ }
2015
+ return undefined;
2016
+ }
2017
+ function encodeUlid(time, length, chars) {
2018
+ let remaining = time;
2019
+ const encoded = Array(length);
2020
+ for (let i = length - 1; i >= 0; i--) {
2021
+ const mod = remaining % 32;
2022
+ encoded[i] = chars.charAt(mod);
2023
+ remaining = Math.floor(remaining / 32);
2024
+ }
2025
+ return encoded.join('');
2026
+ }
2027
+ function buildApproval(decision, by, note) {
2028
+ return {
2029
+ required: true,
2030
+ decision,
2031
+ decidedBy: by,
2032
+ decidedAt: Date.now(),
2033
+ note,
2034
+ };
2035
+ }