minimal-agent 0.11.0 → 0.12.0
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/package.json +4 -1
- package/plugins/workflow-runner/plugin.js +27 -15
- package/plugins/workflow-runner/plugin.ts +34 -20
- package/plugins/workflow-runner/src/agentCommon.js +13 -12
- package/plugins/workflow-runner/src/agentCommon.ts +22 -13
- package/plugins/workflow-runner/src/index.js +5 -3
- package/plugins/workflow-runner/src/index.ts +31 -13
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +2 -1
- package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
- package/plugins/workflow-runner/src/stepExecutors/tool.js +68 -57
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.ts +26 -7
- package/plugins/workflow-runner/test/context-files.test.ts +47 -16
- package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
- package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/src/cli/print.js +19 -4
- package/src/cli/print.ts +17 -4
- package/src/cli/streamJson.js +19 -4
- package/src/cli/streamJson.ts +17 -4
- package/src/config/configDefaults.js +25 -0
- package/src/config/configDefaults.ts +33 -0
- package/src/config/configFile.js +2 -21
- package/src/config/configFile.ts +11 -24
- package/src/config/configManagement.js +1 -1
- package/src/config/configManagement.ts +2 -2
- package/src/context/compact.js +4 -0
- package/src/context/compact.ts +4 -0
- package/src/context/contextBundle.js +38 -13
- package/src/context/contextBundle.ts +47 -13
- package/src/harness/stallGuard.js +19 -4
- package/src/harness/stallGuard.ts +30 -7
- package/src/loop.js +95 -11
- package/src/loop.ts +125 -16
- package/src/main.js +8 -1
- package/src/main.tsx +7 -1
- package/src/memory/constants.js +10 -0
- package/src/memory/constants.ts +13 -0
- package/src/memory/episodicMemory.js +274 -2
- package/src/memory/episodicMemory.ts +306 -1
- package/src/memory/fileStore.js +24 -4
- package/src/memory/fileStore.ts +22 -2
- package/src/memory/memoryService.js +79 -12
- package/src/memory/memoryService.ts +108 -9
- package/src/memory/repository.js +415 -85
- package/src/memory/repository.ts +437 -86
- package/src/memory/turnJournal.js +95 -0
- package/src/memory/turnJournal.ts +120 -0
- package/src/memory/types.ts +16 -0
- package/src/plugins/pluginApi.ts +30 -6
- package/src/plugins/pluginRunner.js +6 -2
- package/src/plugins/pluginRunner.ts +12 -7
- package/src/prompts/policyRegistry.js +27 -0
- package/src/prompts/policyRegistry.ts +27 -0
- package/src/prompts/promptAssembly.js +39 -7
- package/src/prompts/promptAssembly.ts +145 -59
- package/src/session/runAgentTurn.js +43 -21
- package/src/session/runAgentTurn.ts +68 -26
- package/src/session/runTurn.js +11 -46
- package/src/session/runTurn.ts +12 -46
- package/src/session/runtimeStore.js +149 -0
- package/src/session/runtimeStore.ts +197 -0
- package/src/session/sessionState.js +5 -0
- package/src/session/sessionState.ts +14 -0
- package/src/tools/agent/agent.js +143 -14
- package/src/tools/agent/agent.ts +149 -14
- package/src/tools/agent/agentTaskService.js +60 -0
- package/src/tools/agent/agentTaskService.ts +113 -0
- package/src/tools/agent/agentTypes.js +5 -0
- package/src/tools/agent/agentTypes.ts +5 -0
- package/src/tools/bash/bash.js +31 -11
- package/src/tools/bash/bash.ts +29 -10
- package/src/tools/capabilities.js +5 -0
- package/src/tools/capabilities.ts +11 -1
- package/src/tools/edit/edit.js +7 -4
- package/src/tools/edit/edit.ts +7 -4
- package/src/tools/edit/multi-edit.js +4 -2
- package/src/tools/edit/multi-edit.ts +4 -2
- package/src/tools/glob/glob.js +156 -40
- package/src/tools/glob/glob.ts +185 -46
- package/src/tools/index.js +35 -17
- package/src/tools/index.ts +81 -20
- package/src/tools/lsp/lsp.js +165 -22
- package/src/tools/lsp/lsp.ts +206 -36
- package/src/tools/powershell/powershell.js +116 -26
- package/src/tools/powershell/powershell.ts +155 -30
- package/src/tools/presentresult/presentresult.js +10 -0
- package/src/tools/presentresult/presentresult.ts +10 -0
- package/src/tools/read/read.js +244 -29
- package/src/tools/read/read.ts +391 -59
- package/src/tools/shared/diffSummary.js +44 -0
- package/src/tools/shared/diffSummary.ts +63 -0
- package/src/tools/shared/fileActivity.js +2 -1
- package/src/tools/shared/fileActivity.ts +3 -0
- package/src/tools/shared/permanentDeleteGuard.js +26 -0
- package/src/tools/shared/permanentDeleteGuard.ts +34 -0
- package/src/tools/shared/toolOutputStore.js +97 -0
- package/src/tools/shared/toolOutputStore.ts +110 -0
- package/src/tools/skill/skill.js +86 -0
- package/src/tools/skill/skill.ts +94 -0
- package/src/tools/snip/snip.js +67 -0
- package/src/tools/snip/snip.ts +80 -0
- package/src/tools/tasks/backgroundTaskService.js +79 -28
- package/src/tools/tasks/backgroundTaskService.ts +165 -49
- package/src/tools/tasks/taskManagement.js +163 -0
- package/src/tools/tasks/taskManagement.ts +180 -0
- package/src/tools/tasks/taskOutput.js +64 -14
- package/src/tools/tasks/taskOutput.ts +102 -23
- package/src/tools/tasks/taskStop.js +48 -8
- package/src/tools/tasks/taskStop.ts +69 -9
- package/src/tools/tasks/taskStore.js +46 -0
- package/src/tools/tasks/taskStore.ts +77 -0
- package/src/tools/terminal/terminalCapture.js +167 -0
- package/src/tools/terminal/terminalCapture.ts +179 -0
- package/src/tools/trash/trash.js +233 -0
- package/src/tools/trash/trash.ts +283 -0
- package/src/tools/worktree/sessionWorktree.js +128 -0
- package/src/tools/worktree/sessionWorktree.ts +158 -0
- package/src/tools/write/write.js +6 -2
- package/src/tools/write/write.ts +6 -2
- package/src/types.ts +12 -0
- package/src/ui/hooks/useChat.js +31 -11
- package/src/ui/hooks/useChat.ts +31 -13
- package/src/web/protocol.ts +78 -3
|
@@ -29,6 +29,10 @@ import { getSkillList, formatSkillHint } from './skillList.js';
|
|
|
29
29
|
import { getPluginMetaList, formatPluginHint } from './pluginList.js';
|
|
30
30
|
import { memoryService } from '../memory/memoryService.js';
|
|
31
31
|
import { countTextTokens } from '../context/tokenCounter.js';
|
|
32
|
+
import {
|
|
33
|
+
effectiveTaskStatus,
|
|
34
|
+
listManagedTasks,
|
|
35
|
+
} from '../tools/tasks/taskStore.js';
|
|
32
36
|
import {
|
|
33
37
|
assembleContextBundle,
|
|
34
38
|
contextFragment,
|
|
@@ -77,6 +81,7 @@ export interface PromptRuntimeContext {
|
|
|
77
81
|
surface: PromptSurface;
|
|
78
82
|
cwd: string;
|
|
79
83
|
workspaceId?: string;
|
|
84
|
+
sessionId?: string;
|
|
80
85
|
interactiveAsk: boolean;
|
|
81
86
|
supervisedBrowser: boolean;
|
|
82
87
|
/** Trusted host decision. Defaults to denied; never inferred from profile. */
|
|
@@ -128,18 +133,22 @@ async function buildAdvisorStatusLine(): Promise<string> {
|
|
|
128
133
|
const advisor = await configService.advisor();
|
|
129
134
|
if (!advisor) return 'Advisor 状态:未配置。';
|
|
130
135
|
const mode = advisor.mode === 'auto' ? '自动' : '仅手动';
|
|
131
|
-
const vision =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
const vision =
|
|
137
|
+
advisor.supportsVision && advisor.shareSensitive
|
|
138
|
+
? '允许图片'
|
|
139
|
+
: advisor.supportsVision
|
|
140
|
+
? '支持图片但敏感共享关闭'
|
|
141
|
+
: '不支持图片';
|
|
136
142
|
return `Advisor 状态:${advisor.enabled ? '已启用' : '已关闭'},${advisor.provider.name}/${advisor.provider.model},${mode},${vision},超时 ${Math.round(advisor.timeoutMs / 1000)} 秒。`;
|
|
137
143
|
} catch {
|
|
138
144
|
return 'Advisor 状态:暂时无法读取。';
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
async function buildToolLayer(
|
|
148
|
+
async function buildToolLayer(
|
|
149
|
+
tools: Tool[],
|
|
150
|
+
context: StablePromptContext,
|
|
151
|
+
): Promise<string> {
|
|
143
152
|
const catalog = tools.map((tool) => tool.name).join(', ');
|
|
144
153
|
const rules = renderCapabilityPolicyLayer(context);
|
|
145
154
|
|
|
@@ -162,11 +171,14 @@ export interface AssemblePromptOptions {
|
|
|
162
171
|
async function buildMemoryTransient(
|
|
163
172
|
runtime: PromptRuntimeContext,
|
|
164
173
|
tools: Tool[],
|
|
165
|
-
): Promise<Awaited<
|
|
174
|
+
): Promise<Awaited<
|
|
175
|
+
ReturnType<typeof memoryService.buildContextBundle>
|
|
176
|
+
> | null> {
|
|
166
177
|
const names = new Set(tools.map((tool) => tool.name));
|
|
167
|
-
const memoryAllowed =
|
|
178
|
+
const memoryAllowed =
|
|
179
|
+
(runtime.memoryAccess !== undefined && runtime.memoryAccess !== 'denied') ||
|
|
168
180
|
// Compatibility is explicit-only: omitted ownerLocalSession never grants access.
|
|
169
|
-
|
|
181
|
+
(runtime.memoryAccess === undefined && runtime.ownerLocalSession === true);
|
|
170
182
|
if (!memoryAllowed || !names.has('RecallMemory')) return null;
|
|
171
183
|
try {
|
|
172
184
|
// One repository snapshot feeds persona/preferences/relevance/diagnostics.
|
|
@@ -180,8 +192,12 @@ async function buildMemoryTransient(
|
|
|
180
192
|
}
|
|
181
193
|
}
|
|
182
194
|
|
|
183
|
-
async function buildAdvisorTransient(
|
|
184
|
-
|
|
195
|
+
async function buildAdvisorTransient(
|
|
196
|
+
profile: PromptProfile,
|
|
197
|
+
tools: Tool[],
|
|
198
|
+
): Promise<string> {
|
|
199
|
+
if (profile !== 'main' || !tools.some((tool) => tool.name === 'Advisor'))
|
|
200
|
+
return '';
|
|
185
201
|
return buildAdvisorStatusLine();
|
|
186
202
|
}
|
|
187
203
|
|
|
@@ -280,7 +296,9 @@ export async function assembleStablePrompt(
|
|
|
280
296
|
].join('|');
|
|
281
297
|
const cached = stablePromptCache.get(key);
|
|
282
298
|
if (cached) return cached;
|
|
283
|
-
const base = (
|
|
299
|
+
const base = (
|
|
300
|
+
await buildStableBase(options.cwd, options.tools, profile, runtime)
|
|
301
|
+
).base;
|
|
284
302
|
stablePromptCache.set(key, base);
|
|
285
303
|
if (stablePromptCache.size > 64) {
|
|
286
304
|
stablePromptCache.delete(stablePromptCache.keys().next().value!);
|
|
@@ -310,15 +328,22 @@ export async function buildTransientBundle(
|
|
|
310
328
|
runtime: PromptRuntimeContext,
|
|
311
329
|
profile: PromptProfile,
|
|
312
330
|
tools: Tool[],
|
|
313
|
-
options: Pick<
|
|
331
|
+
options: Pick<
|
|
332
|
+
AssemblePromptOptions,
|
|
333
|
+
'includeProjectInstructions' | 'includePlugins'
|
|
334
|
+
> = {},
|
|
314
335
|
): Promise<ContextBundle> {
|
|
315
336
|
const runtimeLines = [
|
|
316
337
|
`【运行时状态】本地日期:${localDate()}。时效敏感信息必须在线核实并使用明确日期或年份。`,
|
|
317
338
|
runtime.supervisedBrowser
|
|
318
339
|
? '宿主提供可监督浏览器:调用 WebBrowser 时用户可看到过程并在验证或登录环节接管。'
|
|
319
340
|
: '',
|
|
320
|
-
runtime.interactiveAsk
|
|
321
|
-
|
|
341
|
+
runtime.interactiveAsk
|
|
342
|
+
? ''
|
|
343
|
+
: '当前宿主不能交互提问;不要等待用户选择,缺少关键输入时说明阻塞。',
|
|
344
|
+
]
|
|
345
|
+
.filter(Boolean)
|
|
346
|
+
.join('\n');
|
|
322
347
|
const fragments: ContextFragment[] = [
|
|
323
348
|
contextFragment({
|
|
324
349
|
id: 'runtime.host-state',
|
|
@@ -329,7 +354,14 @@ export async function buildTransientBundle(
|
|
|
329
354
|
content: runtimeLines,
|
|
330
355
|
}),
|
|
331
356
|
];
|
|
332
|
-
const [
|
|
357
|
+
const [
|
|
358
|
+
memory,
|
|
359
|
+
advisor,
|
|
360
|
+
skillHint,
|
|
361
|
+
pluginHint,
|
|
362
|
+
projectInstructions,
|
|
363
|
+
activeTasks,
|
|
364
|
+
] = await Promise.all([
|
|
333
365
|
buildMemoryTransient(runtime, tools),
|
|
334
366
|
buildAdvisorTransient(profile, tools),
|
|
335
367
|
getSkillList(runtime.cwd, new Set(tools.map((tool) => tool.name)))
|
|
@@ -337,54 +369,102 @@ export async function buildTransientBundle(
|
|
|
337
369
|
.catch(() => ''),
|
|
338
370
|
options.includePlugins === false
|
|
339
371
|
? Promise.resolve('')
|
|
340
|
-
: getPluginMetaList(runtime.cwd)
|
|
372
|
+
: getPluginMetaList(runtime.cwd)
|
|
373
|
+
.then(formatPluginHint)
|
|
374
|
+
.catch(() => ''),
|
|
341
375
|
options.includeProjectInstructions === false
|
|
342
376
|
? Promise.resolve('')
|
|
343
377
|
: loadProjectPromptInstructions(runtime.cwd).catch(() => ''),
|
|
378
|
+
runtime.sessionId &&
|
|
379
|
+
tools.some((tool) =>
|
|
380
|
+
['TaskCreate', 'TaskGet', 'TaskUpdate', 'TaskList'].includes(tool.name),
|
|
381
|
+
)
|
|
382
|
+
? listManagedTasks({
|
|
383
|
+
workspaceId: runtime.workspaceId,
|
|
384
|
+
sessionId: runtime.sessionId,
|
|
385
|
+
cwd: runtime.cwd,
|
|
386
|
+
}).catch(() => [])
|
|
387
|
+
: Promise.resolve([]),
|
|
344
388
|
]);
|
|
345
389
|
if (memory) {
|
|
346
|
-
const priorities = {
|
|
390
|
+
const priorities = {
|
|
391
|
+
persona: 82,
|
|
392
|
+
preferences: 78,
|
|
393
|
+
relevant: 70,
|
|
394
|
+
health: 10,
|
|
395
|
+
} as const;
|
|
347
396
|
for (const section of memory.sections) {
|
|
348
|
-
fragments.push(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
397
|
+
fragments.push(
|
|
398
|
+
contextFragment({
|
|
399
|
+
id: `memory.${section.id}`,
|
|
400
|
+
kind: section.id === 'persona' ? 'persona' : 'memory',
|
|
401
|
+
authority: section.id === 'health' ? 'host' : 'user-evidence',
|
|
402
|
+
priority: priorities[section.id],
|
|
403
|
+
estimatedTokens: section.estimatedTokens,
|
|
404
|
+
content: section.content,
|
|
405
|
+
}),
|
|
406
|
+
);
|
|
356
407
|
}
|
|
357
408
|
}
|
|
358
|
-
if (advisor)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
409
|
+
if (advisor)
|
|
410
|
+
fragments.push(
|
|
411
|
+
contextFragment({
|
|
412
|
+
id: 'diagnostic.advisor',
|
|
413
|
+
kind: 'diagnostic',
|
|
414
|
+
authority: 'host',
|
|
415
|
+
priority: 20,
|
|
416
|
+
content: advisor,
|
|
417
|
+
}),
|
|
418
|
+
);
|
|
419
|
+
if (skillHint)
|
|
420
|
+
fragments.push(
|
|
421
|
+
contextFragment({
|
|
422
|
+
id: 'skills.catalog',
|
|
423
|
+
kind: 'skill',
|
|
424
|
+
authority: 'constraint',
|
|
425
|
+
priority: 45,
|
|
426
|
+
clippable: true,
|
|
427
|
+
content: skillHint,
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
if (pluginHint)
|
|
431
|
+
fragments.push(
|
|
432
|
+
contextFragment({
|
|
433
|
+
id: 'plugins.catalog',
|
|
434
|
+
kind: 'plugin',
|
|
435
|
+
authority: 'untrusted-data',
|
|
436
|
+
priority: 50,
|
|
437
|
+
content: pluginHint,
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
if (projectInstructions)
|
|
441
|
+
fragments.push(
|
|
442
|
+
contextFragment({
|
|
443
|
+
id: 'project.instructions',
|
|
444
|
+
kind: 'project',
|
|
445
|
+
authority: 'constraint',
|
|
446
|
+
priority: 90,
|
|
447
|
+
required: true,
|
|
448
|
+
content: `# 项目指令\n以下内容来自当前工作目录的 minimal-agent.md。行为约束和仓库约定必须遵守;架构事实若与当前代码冲突,以实际代码为准。不要擅自重写该文件。\n\n${projectInstructions}`,
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
451
|
+
const unfinished = activeTasks.filter((task) => task.status !== 'completed');
|
|
452
|
+
if (unfinished.length)
|
|
453
|
+
fragments.push(
|
|
454
|
+
contextFragment({
|
|
455
|
+
id: 'tasks.active',
|
|
456
|
+
kind: 'task',
|
|
457
|
+
authority: 'constraint',
|
|
458
|
+
priority: 85,
|
|
459
|
+
content: `# Active tasks\n${unfinished
|
|
460
|
+
.slice(0, 20)
|
|
461
|
+
.map(
|
|
462
|
+
(task) =>
|
|
463
|
+
`- ${task.id} [${effectiveTaskStatus(task, activeTasks)}] ${task.title}`,
|
|
464
|
+
)
|
|
465
|
+
.join('\n')}`,
|
|
466
|
+
}),
|
|
467
|
+
);
|
|
388
468
|
return assembleContextBundle(
|
|
389
469
|
fragments,
|
|
390
470
|
runtime.transientBudgetTokens ?? DEFAULT_TRANSIENT_BUDGET_TOKENS,
|
|
@@ -397,12 +477,16 @@ export async function buildTransientBundle(
|
|
|
397
477
|
* 直接调 assemblePromptEnvelope 再取 .transient 也能拿到同样结果,但会白算一遍 base
|
|
398
478
|
* (27 个 SKILL.md + 项目指令共约 40 次文件读)。每回合都白算一遍不划算,故单独开口。
|
|
399
479
|
*/
|
|
400
|
-
export async function buildTransientContext(
|
|
480
|
+
export async function buildTransientContext(
|
|
481
|
+
options: AssemblePromptOptions,
|
|
482
|
+
): Promise<string> {
|
|
401
483
|
return (await buildTransientPromptBundle(options)).text;
|
|
402
484
|
}
|
|
403
485
|
|
|
404
486
|
/** Structured counterpart used by the Harness for request-time budget reduction. */
|
|
405
|
-
export async function buildTransientPromptBundle(
|
|
487
|
+
export async function buildTransientPromptBundle(
|
|
488
|
+
options: AssemblePromptOptions,
|
|
489
|
+
): Promise<ContextBundle> {
|
|
406
490
|
const profile = options.profile ?? 'main';
|
|
407
491
|
const runtime: PromptRuntimeContext = {
|
|
408
492
|
surface: profile === 'main' ? 'cli' : 'subagent',
|
|
@@ -418,7 +502,9 @@ export async function buildTransientPromptBundle(options: AssemblePromptOptions)
|
|
|
418
502
|
});
|
|
419
503
|
}
|
|
420
504
|
|
|
421
|
-
export async function assembleBasePrompt(
|
|
505
|
+
export async function assembleBasePrompt(
|
|
506
|
+
options: AssemblePromptOptions,
|
|
507
|
+
): Promise<string> {
|
|
422
508
|
const envelope = await assemblePromptEnvelope(options);
|
|
423
509
|
return [envelope.base, envelope.transient].filter(Boolean).join('\n\n');
|
|
424
510
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { buildBaseSystemPrompt, buildTransientPromptBundle } from '../prompts/system.js';
|
|
1
|
+
import { buildBaseSystemPrompt, buildTransientPromptBundle, } from '../prompts/system.js';
|
|
2
2
|
import { ALL_TOOLS } from '../tools/index.js';
|
|
3
3
|
import { sessionFileFor } from '../context/sessionPath.js';
|
|
4
|
-
import { getOrCreateSessionState, sessionStateKey } from './sessionState.js';
|
|
4
|
+
import { getOrCreateSessionState, sessionStateKey, } from './sessionState.js';
|
|
5
|
+
import { hydrateSessionWorktree } from '../tools/worktree/sessionWorktree.js';
|
|
5
6
|
import { runTurn } from './runTurn.js';
|
|
6
7
|
import { withWorkingDir } from '../bootstrap/workingDir.js';
|
|
7
|
-
import { listSessionSummaries, updateSessionMetadata } from '../context/sessionRegistry.js';
|
|
8
|
+
import { listSessionSummaries, updateSessionMetadata, } from '../context/sessionRegistry.js';
|
|
8
9
|
import { workspaceIndexService } from '../context/workspaceIndex.js';
|
|
9
10
|
import { configService } from '../config/configService.js';
|
|
10
11
|
import { createPromptCacheContext } from '../llm/promptCache.js';
|
|
@@ -17,9 +18,12 @@ deps = {}) {
|
|
|
17
18
|
const session = (await listSessionSummaries(opts.cwd)).find((item) => item.id === opts.sessionId);
|
|
18
19
|
let sessionModelId = session?.modelId;
|
|
19
20
|
if (!opts.model && !sessionModelId) {
|
|
20
|
-
sessionModelId =
|
|
21
|
+
sessionModelId =
|
|
22
|
+
(await configService.getSnapshot()).config?.baseModelId ?? undefined;
|
|
21
23
|
if (sessionModelId) {
|
|
22
|
-
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
24
|
+
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
25
|
+
modelId: sessionModelId,
|
|
26
|
+
}).catch(() => { });
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
const sessionProvider = !opts.model && sessionModelId
|
|
@@ -27,41 +31,62 @@ deps = {}) {
|
|
|
27
31
|
: null;
|
|
28
32
|
const provider = opts.model
|
|
29
33
|
? { ...opts.provider, model: opts.model }
|
|
30
|
-
: sessionProvider ?? opts.provider;
|
|
31
|
-
const workspace = await workspaceIndexService
|
|
34
|
+
: (sessionProvider ?? opts.provider);
|
|
35
|
+
const workspace = await workspaceIndexService
|
|
36
|
+
.register(opts.cwd)
|
|
37
|
+
.catch(() => undefined);
|
|
32
38
|
const sessionFile = sessionFileFor(opts.cwd, opts.sessionId);
|
|
33
39
|
const events = [];
|
|
34
40
|
let result = '';
|
|
35
41
|
let error;
|
|
36
42
|
let numTurns = 0;
|
|
37
|
-
|
|
43
|
+
const turnSessionState = opts.sessionState ??
|
|
44
|
+
getOrCreateSessionState(sessionStateKey({
|
|
45
|
+
surface: 'headless',
|
|
46
|
+
workspaceId: workspace?.id,
|
|
47
|
+
cwd: workspace?.id ? undefined : opts.cwd,
|
|
48
|
+
sessionId: opts.sessionId,
|
|
49
|
+
}));
|
|
50
|
+
await hydrateSessionWorktree({
|
|
51
|
+
sessionState: turnSessionState,
|
|
52
|
+
workspaceId: workspace?.id,
|
|
53
|
+
sessionId: opts.sessionId,
|
|
54
|
+
cwd: opts.cwd,
|
|
55
|
+
});
|
|
56
|
+
const effectiveCwd = turnSessionState.activeWorktree?.path ?? opts.cwd;
|
|
57
|
+
await withWorkingDir(effectiveCwd, async () => {
|
|
38
58
|
// 稳定层进 history[0](runTurn 每轮重写它,必须逐字节可复现,见 runTurn.ts 的缓存不变量);
|
|
39
59
|
// 易变层每轮单独计算,仅附加到当前用户消息的模型副本。
|
|
40
60
|
const runtimeContext = createAgentRuntimeContext({
|
|
41
61
|
surface: 'headless',
|
|
42
62
|
profile: 'main',
|
|
43
|
-
cwd:
|
|
63
|
+
cwd: effectiveCwd,
|
|
44
64
|
workspaceId: workspace?.id,
|
|
45
65
|
sessionId: opts.sessionId,
|
|
46
|
-
owner: opts.memoryAccess && opts.memoryAccess !== 'denied'
|
|
66
|
+
owner: opts.memoryAccess && opts.memoryAccess !== 'denied'
|
|
67
|
+
? 'local-owner'
|
|
68
|
+
: 'untrusted',
|
|
47
69
|
memoryAccess: opts.memoryAccess ?? 'denied',
|
|
48
70
|
interactiveAsk: false,
|
|
49
71
|
});
|
|
50
72
|
const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
|
|
51
|
-
const systemPrompt = await buildBaseSystemPrompt(
|
|
73
|
+
const systemPrompt = await buildBaseSystemPrompt(effectiveCwd, turnTools);
|
|
52
74
|
const transientBundle = await buildTransientPromptBundle({
|
|
53
|
-
cwd:
|
|
75
|
+
cwd: effectiveCwd,
|
|
54
76
|
tools: turnTools,
|
|
55
77
|
runtime: {
|
|
56
78
|
surface: 'headless',
|
|
57
|
-
cwd:
|
|
79
|
+
cwd: effectiveCwd,
|
|
58
80
|
workspaceId: workspace?.id,
|
|
81
|
+
sessionId: opts.sessionId,
|
|
59
82
|
interactiveAsk: false,
|
|
60
83
|
memoryAccess: runtimeContext.memoryAccess,
|
|
61
84
|
currentUserInput: opts.input,
|
|
62
85
|
},
|
|
63
86
|
}).catch(() => undefined);
|
|
64
|
-
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
87
|
+
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
88
|
+
lastMessagePreview: opts.input,
|
|
89
|
+
}).catch(() => { });
|
|
65
90
|
for await (const ev of run({
|
|
66
91
|
userInput: opts.input,
|
|
67
92
|
provider,
|
|
@@ -69,12 +94,7 @@ deps = {}) {
|
|
|
69
94
|
systemPrompt,
|
|
70
95
|
...(transientBundle ? { transientBundle } : {}),
|
|
71
96
|
promptCache: createPromptCacheContext(`workspace:${workspace?.id ?? opts.cwd}:session:${opts.sessionId}`),
|
|
72
|
-
sessionState:
|
|
73
|
-
surface: runtimeContext.surface,
|
|
74
|
-
workspaceId: runtimeContext.workspaceId,
|
|
75
|
-
cwd: opts.cwd,
|
|
76
|
-
sessionId: opts.sessionId,
|
|
77
|
-
})),
|
|
97
|
+
sessionState: turnSessionState,
|
|
78
98
|
signal: opts.signal,
|
|
79
99
|
maxTurns: opts.maxTurns,
|
|
80
100
|
runtimeContext,
|
|
@@ -103,5 +123,7 @@ deps = {}) {
|
|
|
103
123
|
};
|
|
104
124
|
}
|
|
105
125
|
function isEventLike(ev) {
|
|
106
|
-
return Boolean(ev &&
|
|
126
|
+
return Boolean(ev &&
|
|
127
|
+
typeof ev === 'object' &&
|
|
128
|
+
typeof ev.type === 'string');
|
|
107
129
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildBaseSystemPrompt,
|
|
3
|
+
buildTransientPromptBundle,
|
|
4
|
+
} from '../prompts/system.js';
|
|
2
5
|
import { ALL_TOOLS } from '../tools/index.js';
|
|
3
6
|
import type { LoopEvent, Provider } from '../types.js';
|
|
4
7
|
import { sessionFileFor } from '../context/sessionPath.js';
|
|
5
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getOrCreateSessionState,
|
|
10
|
+
sessionStateKey,
|
|
11
|
+
type SessionState,
|
|
12
|
+
} from './sessionState.js';
|
|
13
|
+
import { hydrateSessionWorktree } from '../tools/worktree/sessionWorktree.js';
|
|
6
14
|
import { runTurn } from './runTurn.js';
|
|
7
15
|
import { withWorkingDir } from '../bootstrap/workingDir.js';
|
|
8
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
listSessionSummaries,
|
|
18
|
+
updateSessionMetadata,
|
|
19
|
+
} from '../context/sessionRegistry.js';
|
|
9
20
|
import { workspaceIndexService } from '../context/workspaceIndex.js';
|
|
10
21
|
import { configService } from '../config/configService.js';
|
|
11
22
|
import { createPromptCacheContext } from '../llm/promptCache.js';
|
|
@@ -44,55 +55,86 @@ export async function runAgentTurn(
|
|
|
44
55
|
deps: { runTurn?: typeof runTurn } = {},
|
|
45
56
|
): Promise<RunAgentTurnResult> {
|
|
46
57
|
const run = deps.runTurn ?? runTurn;
|
|
47
|
-
const session = (await listSessionSummaries(opts.cwd)).find(
|
|
58
|
+
const session = (await listSessionSummaries(opts.cwd)).find(
|
|
59
|
+
(item) => item.id === opts.sessionId,
|
|
60
|
+
);
|
|
48
61
|
let sessionModelId = session?.modelId;
|
|
49
62
|
if (!opts.model && !sessionModelId) {
|
|
50
|
-
sessionModelId =
|
|
63
|
+
sessionModelId =
|
|
64
|
+
(await configService.getSnapshot()).config?.baseModelId ?? undefined;
|
|
51
65
|
if (sessionModelId) {
|
|
52
|
-
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
66
|
+
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
67
|
+
modelId: sessionModelId,
|
|
68
|
+
}).catch(() => {});
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
|
-
const sessionProvider =
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
const sessionProvider =
|
|
72
|
+
!opts.model && sessionModelId
|
|
73
|
+
? await configService.providerForModel(sessionModelId)
|
|
74
|
+
: null;
|
|
58
75
|
const provider = opts.model
|
|
59
76
|
? { ...opts.provider, model: opts.model }
|
|
60
|
-
: sessionProvider ?? opts.provider;
|
|
61
|
-
const workspace = await workspaceIndexService
|
|
77
|
+
: (sessionProvider ?? opts.provider);
|
|
78
|
+
const workspace = await workspaceIndexService
|
|
79
|
+
.register(opts.cwd)
|
|
80
|
+
.catch(() => undefined);
|
|
62
81
|
const sessionFile = sessionFileFor(opts.cwd, opts.sessionId);
|
|
63
82
|
const events: LoopEvent[] = [];
|
|
64
83
|
let result = '';
|
|
65
84
|
let error: string | undefined;
|
|
66
85
|
let numTurns = 0;
|
|
86
|
+
const turnSessionState =
|
|
87
|
+
opts.sessionState ??
|
|
88
|
+
getOrCreateSessionState(
|
|
89
|
+
sessionStateKey({
|
|
90
|
+
surface: 'headless',
|
|
91
|
+
workspaceId: workspace?.id,
|
|
92
|
+
cwd: workspace?.id ? undefined : opts.cwd,
|
|
93
|
+
sessionId: opts.sessionId,
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
await hydrateSessionWorktree({
|
|
97
|
+
sessionState: turnSessionState,
|
|
98
|
+
workspaceId: workspace?.id,
|
|
99
|
+
sessionId: opts.sessionId,
|
|
100
|
+
cwd: opts.cwd,
|
|
101
|
+
});
|
|
102
|
+
const effectiveCwd = turnSessionState.activeWorktree?.path ?? opts.cwd;
|
|
67
103
|
|
|
68
|
-
await withWorkingDir(
|
|
104
|
+
await withWorkingDir(effectiveCwd, async () => {
|
|
69
105
|
// 稳定层进 history[0](runTurn 每轮重写它,必须逐字节可复现,见 runTurn.ts 的缓存不变量);
|
|
70
106
|
// 易变层每轮单独计算,仅附加到当前用户消息的模型副本。
|
|
71
107
|
const runtimeContext = createAgentRuntimeContext({
|
|
72
108
|
surface: 'headless',
|
|
73
109
|
profile: 'main',
|
|
74
|
-
cwd:
|
|
110
|
+
cwd: effectiveCwd,
|
|
75
111
|
workspaceId: workspace?.id,
|
|
76
112
|
sessionId: opts.sessionId,
|
|
77
|
-
owner:
|
|
113
|
+
owner:
|
|
114
|
+
opts.memoryAccess && opts.memoryAccess !== 'denied'
|
|
115
|
+
? 'local-owner'
|
|
116
|
+
: 'untrusted',
|
|
78
117
|
memoryAccess: opts.memoryAccess ?? 'denied',
|
|
79
118
|
interactiveAsk: false,
|
|
80
119
|
});
|
|
81
120
|
const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
|
|
82
|
-
const systemPrompt = await buildBaseSystemPrompt(
|
|
121
|
+
const systemPrompt = await buildBaseSystemPrompt(effectiveCwd, turnTools);
|
|
83
122
|
const transientBundle = await buildTransientPromptBundle({
|
|
84
|
-
cwd:
|
|
123
|
+
cwd: effectiveCwd,
|
|
85
124
|
tools: turnTools,
|
|
86
125
|
runtime: {
|
|
87
126
|
surface: 'headless',
|
|
88
|
-
cwd:
|
|
127
|
+
cwd: effectiveCwd,
|
|
89
128
|
workspaceId: workspace?.id,
|
|
129
|
+
sessionId: opts.sessionId,
|
|
90
130
|
interactiveAsk: false,
|
|
91
131
|
memoryAccess: runtimeContext.memoryAccess,
|
|
92
132
|
currentUserInput: opts.input,
|
|
93
133
|
},
|
|
94
134
|
}).catch(() => undefined);
|
|
95
|
-
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
135
|
+
await updateSessionMetadata(opts.cwd, opts.sessionId, {
|
|
136
|
+
lastMessagePreview: opts.input,
|
|
137
|
+
}).catch(() => {});
|
|
96
138
|
for await (const ev of run({
|
|
97
139
|
userInput: opts.input,
|
|
98
140
|
provider,
|
|
@@ -102,12 +144,7 @@ export async function runAgentTurn(
|
|
|
102
144
|
promptCache: createPromptCacheContext(
|
|
103
145
|
`workspace:${workspace?.id ?? opts.cwd}:session:${opts.sessionId}`,
|
|
104
146
|
),
|
|
105
|
-
sessionState:
|
|
106
|
-
surface: runtimeContext.surface,
|
|
107
|
-
workspaceId: runtimeContext.workspaceId,
|
|
108
|
-
cwd: opts.cwd,
|
|
109
|
-
sessionId: opts.sessionId,
|
|
110
|
-
})),
|
|
147
|
+
sessionState: turnSessionState,
|
|
111
148
|
signal: opts.signal,
|
|
112
149
|
maxTurns: opts.maxTurns,
|
|
113
150
|
runtimeContext,
|
|
@@ -120,7 +157,8 @@ export async function runAgentTurn(
|
|
|
120
157
|
if (ev.type === 'assistant_message') numTurns++;
|
|
121
158
|
// compact_failed 是非致命(loop 压缩失败后 snip 兜底继续跑完,types.ts 有约定;
|
|
122
159
|
// print.ts 同样特判)——不置 error,否则 cron 会把自愈轮误判失败。
|
|
123
|
-
if (ev.type === 'error' && ev.code !== 'compact_failed')
|
|
160
|
+
if (ev.type === 'error' && ev.code !== 'compact_failed')
|
|
161
|
+
error = ev.error;
|
|
124
162
|
}
|
|
125
163
|
}
|
|
126
164
|
});
|
|
@@ -135,5 +173,9 @@ export async function runAgentTurn(
|
|
|
135
173
|
}
|
|
136
174
|
|
|
137
175
|
function isEventLike(ev: unknown): ev is LoopEvent {
|
|
138
|
-
return Boolean(
|
|
176
|
+
return Boolean(
|
|
177
|
+
ev &&
|
|
178
|
+
typeof ev === 'object' &&
|
|
179
|
+
typeof (ev as { type?: unknown }).type === 'string',
|
|
180
|
+
);
|
|
139
181
|
}
|