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.
Files changed (128) hide show
  1. package/package.json +4 -1
  2. package/plugins/workflow-runner/plugin.js +27 -15
  3. package/plugins/workflow-runner/plugin.ts +34 -20
  4. package/plugins/workflow-runner/src/agentCommon.js +13 -12
  5. package/plugins/workflow-runner/src/agentCommon.ts +22 -13
  6. package/plugins/workflow-runner/src/index.js +5 -3
  7. package/plugins/workflow-runner/src/index.ts +31 -13
  8. package/plugins/workflow-runner/src/multimodal.js +2 -1
  9. package/plugins/workflow-runner/src/multimodal.ts +2 -1
  10. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  11. package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
  12. package/plugins/workflow-runner/src/stepExecutors/tool.js +68 -57
  13. package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
  14. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  15. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  16. package/plugins/workflow-runner/src/types.ts +26 -7
  17. package/plugins/workflow-runner/test/context-files.test.ts +47 -16
  18. package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
  19. package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
  20. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  21. package/src/cli/print.js +19 -4
  22. package/src/cli/print.ts +17 -4
  23. package/src/cli/streamJson.js +19 -4
  24. package/src/cli/streamJson.ts +17 -4
  25. package/src/config/configDefaults.js +25 -0
  26. package/src/config/configDefaults.ts +33 -0
  27. package/src/config/configFile.js +2 -21
  28. package/src/config/configFile.ts +11 -24
  29. package/src/config/configManagement.js +1 -1
  30. package/src/config/configManagement.ts +2 -2
  31. package/src/context/compact.js +4 -0
  32. package/src/context/compact.ts +4 -0
  33. package/src/context/contextBundle.js +38 -13
  34. package/src/context/contextBundle.ts +47 -13
  35. package/src/harness/stallGuard.js +19 -4
  36. package/src/harness/stallGuard.ts +30 -7
  37. package/src/loop.js +95 -11
  38. package/src/loop.ts +125 -16
  39. package/src/main.js +8 -1
  40. package/src/main.tsx +7 -1
  41. package/src/memory/constants.js +10 -0
  42. package/src/memory/constants.ts +13 -0
  43. package/src/memory/episodicMemory.js +274 -2
  44. package/src/memory/episodicMemory.ts +306 -1
  45. package/src/memory/fileStore.js +24 -4
  46. package/src/memory/fileStore.ts +22 -2
  47. package/src/memory/memoryService.js +79 -12
  48. package/src/memory/memoryService.ts +108 -9
  49. package/src/memory/repository.js +415 -85
  50. package/src/memory/repository.ts +437 -86
  51. package/src/memory/turnJournal.js +95 -0
  52. package/src/memory/turnJournal.ts +120 -0
  53. package/src/memory/types.ts +16 -0
  54. package/src/plugins/pluginApi.ts +30 -6
  55. package/src/plugins/pluginRunner.js +6 -2
  56. package/src/plugins/pluginRunner.ts +12 -7
  57. package/src/prompts/policyRegistry.js +27 -0
  58. package/src/prompts/policyRegistry.ts +27 -0
  59. package/src/prompts/promptAssembly.js +39 -7
  60. package/src/prompts/promptAssembly.ts +145 -59
  61. package/src/session/runAgentTurn.js +43 -21
  62. package/src/session/runAgentTurn.ts +68 -26
  63. package/src/session/runTurn.js +11 -46
  64. package/src/session/runTurn.ts +12 -46
  65. package/src/session/runtimeStore.js +149 -0
  66. package/src/session/runtimeStore.ts +197 -0
  67. package/src/session/sessionState.js +5 -0
  68. package/src/session/sessionState.ts +14 -0
  69. package/src/tools/agent/agent.js +143 -14
  70. package/src/tools/agent/agent.ts +149 -14
  71. package/src/tools/agent/agentTaskService.js +60 -0
  72. package/src/tools/agent/agentTaskService.ts +113 -0
  73. package/src/tools/agent/agentTypes.js +5 -0
  74. package/src/tools/agent/agentTypes.ts +5 -0
  75. package/src/tools/bash/bash.js +31 -11
  76. package/src/tools/bash/bash.ts +29 -10
  77. package/src/tools/capabilities.js +5 -0
  78. package/src/tools/capabilities.ts +11 -1
  79. package/src/tools/edit/edit.js +7 -4
  80. package/src/tools/edit/edit.ts +7 -4
  81. package/src/tools/edit/multi-edit.js +4 -2
  82. package/src/tools/edit/multi-edit.ts +4 -2
  83. package/src/tools/glob/glob.js +156 -40
  84. package/src/tools/glob/glob.ts +185 -46
  85. package/src/tools/index.js +35 -17
  86. package/src/tools/index.ts +81 -20
  87. package/src/tools/lsp/lsp.js +165 -22
  88. package/src/tools/lsp/lsp.ts +206 -36
  89. package/src/tools/powershell/powershell.js +116 -26
  90. package/src/tools/powershell/powershell.ts +155 -30
  91. package/src/tools/presentresult/presentresult.js +10 -0
  92. package/src/tools/presentresult/presentresult.ts +10 -0
  93. package/src/tools/read/read.js +244 -29
  94. package/src/tools/read/read.ts +391 -59
  95. package/src/tools/shared/diffSummary.js +44 -0
  96. package/src/tools/shared/diffSummary.ts +63 -0
  97. package/src/tools/shared/fileActivity.js +2 -1
  98. package/src/tools/shared/fileActivity.ts +3 -0
  99. package/src/tools/shared/permanentDeleteGuard.js +26 -0
  100. package/src/tools/shared/permanentDeleteGuard.ts +34 -0
  101. package/src/tools/shared/toolOutputStore.js +97 -0
  102. package/src/tools/shared/toolOutputStore.ts +110 -0
  103. package/src/tools/skill/skill.js +86 -0
  104. package/src/tools/skill/skill.ts +94 -0
  105. package/src/tools/snip/snip.js +67 -0
  106. package/src/tools/snip/snip.ts +80 -0
  107. package/src/tools/tasks/backgroundTaskService.js +79 -28
  108. package/src/tools/tasks/backgroundTaskService.ts +165 -49
  109. package/src/tools/tasks/taskManagement.js +163 -0
  110. package/src/tools/tasks/taskManagement.ts +180 -0
  111. package/src/tools/tasks/taskOutput.js +64 -14
  112. package/src/tools/tasks/taskOutput.ts +102 -23
  113. package/src/tools/tasks/taskStop.js +48 -8
  114. package/src/tools/tasks/taskStop.ts +69 -9
  115. package/src/tools/tasks/taskStore.js +46 -0
  116. package/src/tools/tasks/taskStore.ts +77 -0
  117. package/src/tools/terminal/terminalCapture.js +167 -0
  118. package/src/tools/terminal/terminalCapture.ts +179 -0
  119. package/src/tools/trash/trash.js +233 -0
  120. package/src/tools/trash/trash.ts +283 -0
  121. package/src/tools/worktree/sessionWorktree.js +128 -0
  122. package/src/tools/worktree/sessionWorktree.ts +158 -0
  123. package/src/tools/write/write.js +6 -2
  124. package/src/tools/write/write.ts +6 -2
  125. package/src/types.ts +12 -0
  126. package/src/ui/hooks/useChat.js +31 -11
  127. package/src/ui/hooks/useChat.ts +31 -13
  128. package/src/web/protocol.ts +78 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minimal-agent",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Bun-first 的本地 Agent 内核,支持工具、插件、工作流、上下文压缩、长期记忆与 OpenAI 兼容接口;Tauri 桌面宿主和 Node.js npm 发布可复用同一内核",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Bill Wang <leiwang0359@gmail.com>",
@@ -131,9 +131,11 @@
131
131
  "prepublishOnly": "bun run clean && bun run build"
132
132
  },
133
133
  "dependencies": {
134
+ "diff": "^8.0.2",
134
135
  "fast-glob": "^3.3.2",
135
136
  "ink": "^5.0.1",
136
137
  "marked": "^18.0.6",
138
+ "pdfjs-dist": "4.10.38",
137
139
  "react": "^18.3.1",
138
140
  "turndown": "^7.2.4",
139
141
  "vscode-jsonrpc": "^8.2.1",
@@ -148,6 +150,7 @@
148
150
  "typescript": "^5.5.0"
149
151
  },
150
152
  "optionalDependencies": {
153
+ "@napi-rs/canvas": "^0.1.100",
151
154
  "playwright-core": "^1.40.0",
152
155
  "pngjs": "^7.0.0",
153
156
  "pptxgenjs": "^4.0.1"
@@ -12,7 +12,7 @@
12
12
  * 缓存这次 import,所以 cold start 后 dynamic import 只跑一次。
13
13
  * ============================================================
14
14
  */
15
- import { ALL_TOOLS, derivePromptCacheContext, getWorkingDir, createAgentRuntimeContext, filterToolsForRuntime, buildTransientPromptBundle, } from '../../src/plugin-sdk.js';
15
+ import { ALL_TOOLS, buildTransientPromptBundle, createAgentRuntimeContext, derivePromptCacheContext, filterToolsForRuntime, getWorkingDir, } from '../../src/plugin-sdk.js';
16
16
  import { runWorkflowFromCommand, runWorkflowsList } from './src/index.js';
17
17
  const api = {
18
18
  async *runCommand(commandName, args, ctx) {
@@ -43,21 +43,22 @@ const api = {
43
43
  ? ALL_TOOLS.filter((tool) => hostToolNames.has(tool.name))
44
44
  : ALL_TOOLS;
45
45
  const workflowTools = filterToolsForRuntime(hostTools, workflowRuntime);
46
- const transientBundle = ctx.transientBundle ?? await buildTransientPromptBundle({
47
- cwd,
48
- tools: workflowTools,
49
- profile: 'main',
50
- includeProjectInstructions: true,
51
- includePlugins: true,
52
- runtime: {
53
- surface: 'workflow',
46
+ const transientBundle = ctx.transientBundle ??
47
+ (await buildTransientPromptBundle({
54
48
  cwd,
55
- workspaceId: workflowRuntime.workspaceId,
56
- interactiveAsk: workflowRuntime.interactiveAsk,
57
- memoryAccess: workflowRuntime.memoryAccess,
58
- currentUserInput: args,
59
- },
60
- }).catch(() => undefined);
49
+ tools: workflowTools,
50
+ profile: 'main',
51
+ includeProjectInstructions: true,
52
+ includePlugins: true,
53
+ runtime: {
54
+ surface: 'workflow',
55
+ cwd,
56
+ workspaceId: workflowRuntime.workspaceId,
57
+ interactiveAsk: workflowRuntime.interactiveAsk,
58
+ memoryAccess: workflowRuntime.memoryAccess,
59
+ currentUserInput: args,
60
+ },
61
+ }).catch(() => undefined));
61
62
  yield* runWorkflowFromCommand(args, {
62
63
  provider: ctx.provider,
63
64
  history: ctx.history,
@@ -66,6 +67,17 @@ const api = {
66
67
  cwd,
67
68
  tools: workflowTools,
68
69
  runtimeContext: workflowRuntime,
70
+ executeTool: ctx.executeTool,
71
+ toolContext: {
72
+ history: ctx.history,
73
+ sessionState: ctx.sessionState,
74
+ provider: ctx.provider,
75
+ toolGate: ctx.host?.toolGate,
76
+ promptCache: ctx.promptCache,
77
+ workspaceId: workflowRuntime.workspaceId,
78
+ sessionId: workflowRuntime.sessionId,
79
+ memoryAccess: workflowRuntime.memoryAccess,
80
+ },
69
81
  // 宿主授权上下文原样带上;引擎按各步 `uses:` 切片后才转给工具,不整份下发。
70
82
  host: ctx.host,
71
83
  transientContext: ctx.transientContext,
@@ -16,11 +16,11 @@
16
16
  import type { PluginApi } from '../../src/plugin-sdk.js';
17
17
  import {
18
18
  ALL_TOOLS,
19
- derivePromptCacheContext,
20
- getWorkingDir,
19
+ buildTransientPromptBundle,
21
20
  createAgentRuntimeContext,
21
+ derivePromptCacheContext,
22
22
  filterToolsForRuntime,
23
- buildTransientPromptBundle,
23
+ getWorkingDir,
24
24
  } from '../../src/plugin-sdk.js';
25
25
  import { runWorkflowFromCommand, runWorkflowsList } from './src/index.js';
26
26
 
@@ -43,9 +43,10 @@ const api: PluginApi = {
43
43
  cwd,
44
44
  surface: 'workflow',
45
45
  owner: ctx.runtimeContext?.owner ?? 'untrusted',
46
- memoryAccess: ctx.runtimeContext?.memoryAccess === 'denied'
47
- ? 'denied'
48
- : 'read-only',
46
+ memoryAccess:
47
+ ctx.runtimeContext?.memoryAccess === 'denied'
48
+ ? 'denied'
49
+ : 'read-only',
49
50
  });
50
51
  const hostToolNames = ctx.allowedTools
51
52
  ? new Set(ctx.allowedTools)
@@ -54,21 +55,23 @@ const api: PluginApi = {
54
55
  ? ALL_TOOLS.filter((tool) => hostToolNames.has(tool.name))
55
56
  : ALL_TOOLS;
56
57
  const workflowTools = filterToolsForRuntime(hostTools, workflowRuntime);
57
- const transientBundle = ctx.transientBundle ?? await buildTransientPromptBundle({
58
- cwd,
59
- tools: workflowTools,
60
- profile: 'main',
61
- includeProjectInstructions: true,
62
- includePlugins: true,
63
- runtime: {
64
- surface: 'workflow',
58
+ const transientBundle =
59
+ ctx.transientBundle ??
60
+ (await buildTransientPromptBundle({
65
61
  cwd,
66
- workspaceId: workflowRuntime.workspaceId,
67
- interactiveAsk: workflowRuntime.interactiveAsk,
68
- memoryAccess: workflowRuntime.memoryAccess,
69
- currentUserInput: args,
70
- },
71
- }).catch(() => undefined);
62
+ tools: workflowTools,
63
+ profile: 'main',
64
+ includeProjectInstructions: true,
65
+ includePlugins: true,
66
+ runtime: {
67
+ surface: 'workflow',
68
+ cwd,
69
+ workspaceId: workflowRuntime.workspaceId,
70
+ interactiveAsk: workflowRuntime.interactiveAsk,
71
+ memoryAccess: workflowRuntime.memoryAccess,
72
+ currentUserInput: args,
73
+ },
74
+ }).catch(() => undefined));
72
75
  yield* runWorkflowFromCommand(args, {
73
76
  provider: ctx.provider,
74
77
  history: ctx.history,
@@ -77,6 +80,17 @@ const api: PluginApi = {
77
80
  cwd,
78
81
  tools: workflowTools,
79
82
  runtimeContext: workflowRuntime,
83
+ executeTool: ctx.executeTool,
84
+ toolContext: {
85
+ history: ctx.history,
86
+ sessionState: ctx.sessionState,
87
+ provider: ctx.provider,
88
+ toolGate: ctx.host?.toolGate,
89
+ promptCache: ctx.promptCache,
90
+ workspaceId: workflowRuntime.workspaceId,
91
+ sessionId: workflowRuntime.sessionId,
92
+ memoryAccess: workflowRuntime.memoryAccess,
93
+ },
80
94
  // 宿主授权上下文原样带上;引擎按各步 `uses:` 切片后才转给工具,不整份下发。
81
95
  host: ctx.host,
82
96
  transientContext: ctx.transientContext,
@@ -19,7 +19,8 @@
19
19
  * ③ 检测逻辑增加内容长度启发式(reason > 80 字符视为误报)
20
20
  * ============================================================
21
21
  */
22
- import { runQuery, createAgentRuntimeContext, stripThinkBlocks, } from '../../../src/plugin-sdk.js';
22
+ import { createAgentRuntimeContext, runQuery, stripThinkBlocks, } from '../../../src/plugin-sdk.js';
23
+ import { effectiveWorkflowCwd } from './toolRuntime.js';
23
24
  import { normalizeCapture, } from './types.js';
24
25
  /**
25
26
  * agent 显式放弃的唯一标记;recover 与 agent 节点共用。
@@ -59,7 +60,9 @@ export function effectiveGoalAnchor(ctx) {
59
60
  if (supps.length === 0)
60
61
  return base;
61
62
  const lines = supps.map((s) => `> 📌 用户补充要求:${s}`).join('\n');
62
- return base ? `${base}\n${lines}` : `${lines}\n> ⚠️ 以上补充要求与原始目标同等约束所有操作。`;
63
+ return base
64
+ ? `${base}\n${lines}`
65
+ : `${lines}\n> ⚠️ 以上补充要求与原始目标同等约束所有操作。`;
63
66
  }
64
67
  /** 把 llm_error 细节拼进可重试失败的抛错文案(无细节时退化为原样文案)。 */
65
68
  export function describeLlmFailure(stepId, detail) {
@@ -96,14 +99,10 @@ function createStepHistory(step, ctx) {
96
99
  export async function* runStepQuery(prompt, step, ctx, opts) {
97
100
  const activeHistory = createStepHistory(step, ctx);
98
101
  const availableTools = ctx.tools ?? [];
99
- const hostToolNames = availableTools.length > 0
100
- ? new Set(availableTools.map((tool) => tool.name))
101
- : null;
102
- const effectiveStepTools = hostToolNames === null
103
- ? step.allowed_tools
104
- : step.allowed_tools === undefined
105
- ? [...hostToolNames]
106
- : step.allowed_tools.filter((name) => hostToolNames.has(name));
102
+ const hostToolNames = new Set(availableTools.map((tool) => tool.name));
103
+ const effectiveStepTools = step.allowed_tools === undefined
104
+ ? [...hostToolNames]
105
+ : step.allowed_tools.filter((name) => hostToolNames.has(name));
107
106
  // 记录 runQuery 开始前的 history 长度,用于之后提取本步的 assistant 输出
108
107
  const historyLenBefore = activeHistory.length;
109
108
  const runtimeContext = createAgentRuntimeContext({
@@ -112,7 +111,7 @@ export async function* runStepQuery(prompt, step, ctx, opts) {
112
111
  profile: 'main',
113
112
  interactiveAsk: false,
114
113
  }),
115
- cwd: ctx.cwd,
114
+ cwd: effectiveWorkflowCwd(ctx),
116
115
  owner: ctx.runtimeContext?.owner ?? 'untrusted',
117
116
  memoryAccess: ctx.runtimeContext?.memoryAccess ?? 'denied',
118
117
  });
@@ -283,7 +282,9 @@ function isLikelyFalsePositive(reason) {
283
282
  if (reason.length > 80)
284
283
  return true;
285
284
  // 如果 reason 看起来像 markdown 正文(标题/章节),也是误报
286
- if (/^#{1,6}\s/m.test(reason) || /^[-*+]\s/m.test(reason) || /^\d+\.\s/m.test(reason)) {
285
+ if (/^#{1,6}\s/m.test(reason) ||
286
+ /^[-*+]\s/m.test(reason) ||
287
+ /^\d+\.\s/m.test(reason)) {
287
288
  return true;
288
289
  }
289
290
  return false;
@@ -25,10 +25,11 @@ import {
25
25
  type ImageInputSpec,
26
26
  type LoopEvent,
27
27
  type Message,
28
- runQuery,
29
28
  createAgentRuntimeContext,
29
+ runQuery,
30
30
  stripThinkBlocks,
31
31
  } from '../../../src/plugin-sdk.js';
32
+ import { effectiveWorkflowCwd } from './toolRuntime.js';
32
33
  import {
33
34
  type RunContext,
34
35
  type StepDef,
@@ -81,7 +82,9 @@ export function effectiveGoalAnchor(ctx: RunContext): string {
81
82
  const supps = (ctx.goalSupplements ?? []).filter((s) => s.trim());
82
83
  if (supps.length === 0) return base;
83
84
  const lines = supps.map((s) => `> 📌 用户补充要求:${s}`).join('\n');
84
- return base ? `${base}\n${lines}` : `${lines}\n> ⚠️ 以上补充要求与原始目标同等约束所有操作。`;
85
+ return base
86
+ ? `${base}\n${lines}`
87
+ : `${lines}\n> ⚠️ 以上补充要求与原始目标同等约束所有操作。`;
85
88
  }
86
89
 
87
90
  /** runStepQuery 的归纳结果:本步产出文本 + 终结性错误旗标(语义判断留给各执行器)。 */
@@ -164,12 +167,9 @@ export async function* runStepQuery(
164
167
  ): AsyncGenerator<LoopEvent, StepQueryOutcome, void> {
165
168
  const activeHistory = createStepHistory(step, ctx);
166
169
  const availableTools = ctx.tools ?? [];
167
- const hostToolNames = availableTools.length > 0
168
- ? new Set(availableTools.map((tool) => tool.name))
169
- : null;
170
- const effectiveStepTools = hostToolNames === null
171
- ? step.allowed_tools
172
- : step.allowed_tools === undefined
170
+ const hostToolNames = new Set(availableTools.map((tool) => tool.name));
171
+ const effectiveStepTools =
172
+ step.allowed_tools === undefined
173
173
  ? [...hostToolNames]
174
174
  : step.allowed_tools.filter((name) => hostToolNames.has(name));
175
175
  // 记录 runQuery 开始前的 history 长度,用于之后提取本步的 assistant 输出
@@ -180,7 +180,7 @@ export async function* runStepQuery(
180
180
  profile: 'main' as const,
181
181
  interactiveAsk: false,
182
182
  }),
183
- cwd: ctx.cwd,
183
+ cwd: effectiveWorkflowCwd(ctx),
184
184
  owner: ctx.runtimeContext?.owner ?? 'untrusted',
185
185
  memoryAccess: ctx.runtimeContext?.memoryAccess ?? 'denied',
186
186
  });
@@ -200,7 +200,8 @@ export async function* runStepQuery(
200
200
  signal: ctx.signal,
201
201
  maxTurns: opts.maxTurns,
202
202
  allowedTools: effectiveStepTools,
203
- requiredTools: step.allowed_tools === undefined ? undefined : effectiveStepTools,
203
+ requiredTools:
204
+ step.allowed_tools === undefined ? undefined : effectiveStepTools,
204
205
  chat: ctx.chat,
205
206
  executeTool: ctx.executeTool,
206
207
  sessionState: ctx.sessionState,
@@ -296,7 +297,10 @@ export async function* runStepQuery(
296
297
  export const MAX_TURNS_CUTOFF_MARKER = '达到最大轮数';
297
298
 
298
299
  /** maxTurns 截断的可重试失败文案(重试 = 在共享 history 上续作,再给一份轮数预算)。 */
299
- export function describeMaxTurnsCutoff(stepId: string, maxTurns: number): string {
300
+ export function describeMaxTurnsCutoff(
301
+ stepId: string,
302
+ maxTurns: number,
303
+ ): string {
300
304
  return `step ${stepId}: ${MAX_TURNS_CUTOFF_MARKER} ${maxTurns} 被截断(可续跑)`;
301
305
  }
302
306
 
@@ -351,7 +355,11 @@ export function parseGiveup(text: string): string | null {
351
355
  function isLikelyFalsePositive(reason: string): boolean {
352
356
  if (reason.length > 80) return true;
353
357
  // 如果 reason 看起来像 markdown 正文(标题/章节),也是误报
354
- if (/^#{1,6}\s/m.test(reason) || /^[-*+]\s/m.test(reason) || /^\d+\.\s/m.test(reason)) {
358
+ if (
359
+ /^#{1,6}\s/m.test(reason) ||
360
+ /^[-*+]\s/m.test(reason) ||
361
+ /^\d+\.\s/m.test(reason)
362
+ ) {
355
363
  return true;
356
364
  }
357
365
  return false;
@@ -405,7 +413,8 @@ export function interpretMiniReActResult(
405
413
  ok: true,
406
414
  value: {
407
415
  raw,
408
- preview: cleanText.length > 200 ? `${cleanText.slice(0, 200)}…` : cleanText,
416
+ preview:
417
+ cleanText.length > 200 ? `${cleanText.slice(0, 200)}…` : cleanText,
409
418
  },
410
419
  };
411
420
  }
@@ -10,9 +10,9 @@
10
10
  * 不污染默认 T-A-O-R 路径。
11
11
  * ============================================================
12
12
  */
13
+ import { buildBaseSystemPrompt, } from '../../../src/plugin-sdk.js';
13
14
  import { findWorkflowByName, listWorkflows } from './loader.js';
14
15
  import { runWorkflow } from './runner.js';
15
- import { buildBaseSystemPrompt, } from '../../../src/plugin-sdk.js';
16
16
  /**
17
17
  * 把 "/workflow <name> [pos1 pos2 ...] [--input k=v ...]" 拆成 name + 显式 inputs + 位置参数。
18
18
  * 简单解析器(不依赖第三方):
@@ -103,7 +103,7 @@ function coerceInputs(raw, defs) {
103
103
  continue;
104
104
  }
105
105
  if (def.type === 'number') {
106
- const n = parseFloat(rawVal);
106
+ const n = Number.parseFloat(rawVal);
107
107
  if (Number.isNaN(n))
108
108
  throw new Error(`input ${def.name} 必须是数字,收到 "${rawVal}"`);
109
109
  out[def.name] = n;
@@ -229,6 +229,8 @@ export async function* runWorkflowFromCommand(rawArgs, opts) {
229
229
  transientContext: opts.transientContext,
230
230
  transientBundle: opts.transientBundle,
231
231
  promptCache: opts.promptCache,
232
+ executeTool: opts.executeTool,
233
+ toolContext: opts.toolContext,
232
234
  };
233
235
  // 透传 runWorkflow 的事件,并观察是否出现终结事件(error / interrupted = 失败或中断,
234
236
  // 与 gateway broker 的 TERMINAL 判定同一套逻辑)。成功(没见到终结事件)则在末尾补一条
@@ -265,6 +267,6 @@ export async function* runWorkflowsList() {
265
267
  lines.push(' /workflow <name> <val> 位置参数(按 inputs 声明顺序,第一个 input 直接写值)');
266
268
  lines.push(' /workflow <name>.yaml <val> .yaml 后缀可省');
267
269
  lines.push(' /workflow <name> --input k=v 显式 KV(多 input 或想跳过中间 input 时用)');
268
- yield { type: 'text', delta: lines.join('\n') + '\n' };
270
+ yield { type: 'text', delta: `${lines.join('\n')}\n` };
269
271
  yield { type: 'turn_done' };
270
272
  }
@@ -11,16 +11,17 @@
11
11
  * ============================================================
12
12
  */
13
13
 
14
- import { findWorkflowByName, listWorkflows } from './loader.js';
15
- import { runWorkflow } from './runner.js';
16
- import type { InputDef, RunContext, WorkflowEvent } from './types.js';
17
14
  import {
18
- buildBaseSystemPrompt,
19
15
  type LoopEvent,
20
16
  type Message,
17
+ type PluginContext,
21
18
  type Provider,
22
19
  type Tool,
20
+ buildBaseSystemPrompt,
23
21
  } from '../../../src/plugin-sdk.js';
22
+ import { findWorkflowByName, listWorkflows } from './loader.js';
23
+ import { runWorkflow } from './runner.js';
24
+ import type { InputDef, RunContext, WorkflowEvent } from './types.js';
24
25
 
25
26
  export interface RunWorkflowFromCommandOptions {
26
27
  provider: Provider;
@@ -31,11 +32,13 @@ export interface RunWorkflowFromCommandOptions {
31
32
  sessionState?: import('../../../src/plugin-sdk.js').SessionState;
32
33
  /** 当前工作目录(默认 process.cwd()) */
33
34
  cwd?: string;
34
- /** 可用工具列表(默认空数组,由 runner 注入 ALL_TOOLS) */
35
+ /** 宿主过滤后的可用工具列表;缺省为空,绝不在 runner 内恢复全量工具。 */
35
36
  tools?: Tool[];
36
37
  transientContext?: string;
37
38
  transientBundle?: import('../../../src/plugin-sdk.js').ContextBundle;
38
39
  promptCache?: import('../../../src/plugin-sdk.js').PromptCacheContext;
40
+ executeTool?: PluginContext['executeTool'];
41
+ toolContext?: RunContext['toolContext'];
39
42
  /**
40
43
  * 宿主授权上下文(浏览器 / 电脑操控)。
41
44
  *
@@ -138,8 +141,9 @@ function coerceInputs(
138
141
  continue;
139
142
  }
140
143
  if (def.type === 'number') {
141
- const n = parseFloat(rawVal);
142
- if (Number.isNaN(n)) throw new Error(`input ${def.name} 必须是数字,收到 "${rawVal}"`);
144
+ const n = Number.parseFloat(rawVal);
145
+ if (Number.isNaN(n))
146
+ throw new Error(`input ${def.name} 必须是数字,收到 "${rawVal}"`);
143
147
  out[def.name] = n;
144
148
  } else if (def.type === 'enum') {
145
149
  if (!def.values?.includes(rawVal)) {
@@ -167,7 +171,9 @@ async function refreshWorkflowSystemPrompt(
167
171
  // 只刷稳定层:workflow 一跑就是几十步,system 消息若含日期/记忆等易变内容,
168
172
  // 每步都会重写 history[0]、把 provider 前缀缓存打散(见 prompts/system.ts)。
169
173
  const content = await buildBaseSystemPrompt(cwd, tools);
170
- const systemIndex = history.findIndex((m) => m.role === 'system' && !m._ephemeral);
174
+ const systemIndex = history.findIndex(
175
+ (m) => m.role === 'system' && !m._ephemeral,
176
+ );
171
177
  const fresh: Message = { role: 'system', content };
172
178
 
173
179
  if (systemIndex === 0) {
@@ -278,6 +284,8 @@ export async function* runWorkflowFromCommand(
278
284
  transientContext: opts.transientContext,
279
285
  transientBundle: opts.transientBundle,
280
286
  promptCache: opts.promptCache,
287
+ executeTool: opts.executeTool,
288
+ toolContext: opts.toolContext,
281
289
  };
282
290
 
283
291
  // 透传 runWorkflow 的事件,并观察是否出现终结事件(error / interrupted = 失败或中断,
@@ -295,7 +303,11 @@ export async function* runWorkflowFromCommand(
295
303
  }
296
304
 
297
305
  /** /workflows —— 列出可用 workflow。yield 一条 text 事件让 UI 显示。 */
298
- export async function* runWorkflowsList(): AsyncGenerator<LoopEvent, void, void> {
306
+ export async function* runWorkflowsList(): AsyncGenerator<
307
+ LoopEvent,
308
+ void,
309
+ void
310
+ > {
299
311
  const list = await listWorkflows();
300
312
  if (list.length === 0) {
301
313
  yield {
@@ -308,13 +320,19 @@ export async function* runWorkflowsList(): AsyncGenerator<LoopEvent, void, void>
308
320
  }
309
321
  const lines = ['可用工作流:'];
310
322
  for (const w of list) {
311
- lines.push(` /workflow ${w.name} — ${w.description.split('\n')[0].trim()}`);
323
+ lines.push(
324
+ ` /workflow ${w.name} — ${w.description.split('\n')[0].trim()}`,
325
+ );
312
326
  }
313
327
  lines.push('');
314
328
  lines.push('运行示例:');
315
- lines.push(' /workflow <name> <val> 位置参数(按 inputs 声明顺序,第一个 input 直接写值)');
329
+ lines.push(
330
+ ' /workflow <name> <val> 位置参数(按 inputs 声明顺序,第一个 input 直接写值)',
331
+ );
316
332
  lines.push(' /workflow <name>.yaml <val> .yaml 后缀可省');
317
- lines.push(' /workflow <name> --input k=v 显式 KV(多 input 或想跳过中间 input 时用)');
318
- yield { type: 'text', delta: lines.join('\n') + '\n' };
333
+ lines.push(
334
+ ' /workflow <name> --input k=v 显式 KV(多 input 或想跳过中间 input 时用)',
335
+ );
336
+ yield { type: 'text', delta: `${lines.join('\n')}\n` };
319
337
  yield { type: 'turn_done' };
320
338
  }
@@ -1,5 +1,6 @@
1
1
  import { isAbsolute, resolve } from 'node:path';
2
2
  import { interpolate } from './expressions.js';
3
+ import { effectiveWorkflowCwd } from './toolRuntime.js';
3
4
  export function resolveWorkflowMultimodalOptions(step, vars, ctx, onMissing) {
4
5
  const imageDetail = step.image_detail;
5
6
  const imageMaxLongSidePixel = step.image_max_long_side_pixel;
@@ -33,5 +34,5 @@ function resolveWorkflowImageSource(source, ctx) {
33
34
  isAbsolute(clean)) {
34
35
  return clean;
35
36
  }
36
- return resolve(ctx.cwd, clean);
37
+ return resolve(effectiveWorkflowCwd(ctx), clean);
37
38
  }
@@ -2,6 +2,7 @@ import { isAbsolute, resolve } from 'node:path';
2
2
 
3
3
  import type { ImageDetail, ImageInputSpec } from '../../../src/plugin-sdk.js';
4
4
  import { interpolate } from './expressions.js';
5
+ import { effectiveWorkflowCwd } from './toolRuntime.js';
5
6
  import type { RunContext, StepDef, VarStack } from './types.js';
6
7
 
7
8
  export interface WorkflowMultimodalOptions {
@@ -59,5 +60,5 @@ function resolveWorkflowImageSource(source: string, ctx: RunContext): string {
59
60
  ) {
60
61
  return clean;
61
62
  }
62
- return resolve(ctx.cwd, clean);
63
+ return resolve(effectiveWorkflowCwd(ctx), clean);
63
64
  }
@@ -15,11 +15,12 @@
15
15
  * capture:{ text: var_name } / { result: var_name }
16
16
  * ============================================================
17
17
  */
18
- import { readdir, readFile } from 'node:fs/promises';
18
+ import { readFile, readdir } from 'node:fs/promises';
19
19
  import { join } from 'node:path';
20
- import { getResourceSearchPaths } from '../../../../src/plugin-sdk.js';
20
+ import { getResourceSearchPaths, } from '../../../../src/plugin-sdk.js';
21
21
  import { describeLlmFailure, describeMaxTurnsCutoff, effectiveGoalAnchor, runStepQuery, } from '../agentCommon.js';
22
22
  import { interpolate } from '../expressions.js';
23
+ import { effectiveWorkflowCwd } from '../toolRuntime.js';
23
24
  function stripFrontmatter(content) {
24
25
  return content.replace(/^---\n[\s\S]*?\n---\n?/, '');
25
26
  }
@@ -53,9 +54,7 @@ async function findSkillFile(skillName, cwd) {
53
54
  if (parseFrontmatterName(md) === skillName)
54
55
  return skillFile;
55
56
  }
56
- catch {
57
- continue;
58
- }
57
+ catch { }
59
58
  }
60
59
  }
61
60
  return null;
@@ -64,7 +63,7 @@ export async function* execSkillStep(step, vars, ctx) {
64
63
  if (!step.skill)
65
64
  throw new Error(`step ${step.id}: 缺少 skill 字段`);
66
65
  const skillName = interpolate(step.skill, vars);
67
- const skillFile = await findSkillFile(skillName, ctx.cwd);
66
+ const skillFile = await findSkillFile(skillName, effectiveWorkflowCwd(ctx));
68
67
  if (!skillFile) {
69
68
  throw new Error(`step ${step.id}: 未找到 skill "${skillName}"(cwd/skills/ 与 packageRoot/skills/ 双源均未命中)`);
70
69
  }
@@ -16,10 +16,13 @@
16
16
  * ============================================================
17
17
  */
18
18
 
19
- import { readdir, readFile } from 'node:fs/promises';
19
+ import { readFile, readdir } from 'node:fs/promises';
20
20
  import { join } from 'node:path';
21
21
 
22
- import { getResourceSearchPaths, type LoopEvent } from '../../../../src/plugin-sdk.js';
22
+ import {
23
+ type LoopEvent,
24
+ getResourceSearchPaths,
25
+ } from '../../../../src/plugin-sdk.js';
23
26
  import {
24
27
  describeLlmFailure,
25
28
  describeMaxTurnsCutoff,
@@ -27,6 +30,7 @@ import {
27
30
  runStepQuery,
28
31
  } from '../agentCommon.js';
29
32
  import { interpolate } from '../expressions.js';
33
+ import { effectiveWorkflowCwd } from '../toolRuntime.js';
30
34
  import type { RunContext, StepDef, StepResult, VarStack } from '../types.js';
31
35
 
32
36
  function stripFrontmatter(content: string): string {
@@ -41,7 +45,10 @@ function parseFrontmatterName(content: string): string | null {
41
45
  return nm[1].trim().replace(/^["']|["']$/g, '');
42
46
  }
43
47
 
44
- async function findSkillFile(skillName: string, cwd: string): Promise<string | null> {
48
+ async function findSkillFile(
49
+ skillName: string,
50
+ cwd: string,
51
+ ): Promise<string | null> {
45
52
  const roots = getResourceSearchPaths('skills', import.meta.url, cwd);
46
53
 
47
54
  for (const root of roots) {
@@ -58,9 +65,7 @@ async function findSkillFile(skillName: string, cwd: string): Promise<string | n
58
65
  try {
59
66
  const md = await readFile(skillFile, 'utf8');
60
67
  if (parseFrontmatterName(md) === skillName) return skillFile;
61
- } catch {
62
- continue;
63
- }
68
+ } catch {}
64
69
  }
65
70
  }
66
71
  return null;
@@ -74,7 +79,7 @@ export async function* execSkillStep(
74
79
  if (!step.skill) throw new Error(`step ${step.id}: 缺少 skill 字段`);
75
80
  const skillName = interpolate(step.skill, vars);
76
81
 
77
- const skillFile = await findSkillFile(skillName, ctx.cwd);
82
+ const skillFile = await findSkillFile(skillName, effectiveWorkflowCwd(ctx));
78
83
  if (!skillFile) {
79
84
  throw new Error(
80
85
  `step ${step.id}: 未找到 skill "${skillName}"(cwd/skills/ 与 packageRoot/skills/ 双源均未命中)`,