mocode-ai 1.4.2 → 1.4.3

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 (47) hide show
  1. package/README.md +13 -1
  2. package/dist/agent/core.js +14 -936
  3. package/dist/agent/index.js +37 -13
  4. package/dist/agent/model-turn.js +218 -0
  5. package/dist/agent/pipeline.js +18 -0
  6. package/dist/agent/run-contracts.js +1 -0
  7. package/dist/agent/run-coordinator.js +758 -0
  8. package/dist/agent/runtime-context.js +118 -24
  9. package/dist/agent/spawn.js +11 -7
  10. package/dist/agent/stages/context-trimmer.js +63 -0
  11. package/dist/agent/stages/contracts.js +12 -0
  12. package/dist/agent/stages/history-manager.js +178 -0
  13. package/dist/agent/stages/legacy-adapters.js +19 -0
  14. package/dist/agent/stages/model-runner.js +29 -0
  15. package/dist/agent/stages/run-policy.js +73 -0
  16. package/dist/agent/stages/tool-dispatcher.js +341 -0
  17. package/dist/agent/tool-helpers.js +12 -12
  18. package/dist/agent/tool-turn.js +87 -0
  19. package/dist/agent/trace-state.js +97 -101
  20. package/dist/agent/turn-lifecycle.js +110 -0
  21. package/dist/config/index.js +14 -0
  22. package/dist/host/stdio.js +101 -40
  23. package/dist/llm/index.js +51 -35
  24. package/dist/llm/providers/anthropic.js +16 -10
  25. package/dist/llm/runtime.js +1 -0
  26. package/dist/permissions/index.js +21 -5
  27. package/dist/repl/commands/compact.js +2 -2
  28. package/dist/repl/commands/session.js +3 -12
  29. package/dist/repl/message-format.js +5 -0
  30. package/dist/repl/runtime.js +95 -55
  31. package/dist/rollback/index.js +29 -624
  32. package/dist/rollback/store.js +593 -0
  33. package/dist/runtime/index.js +1 -0
  34. package/dist/runtime/runtime.js +307 -0
  35. package/dist/session/compact.js +22 -14
  36. package/dist/session/index.js +1 -0
  37. package/dist/session/persist.js +10 -146
  38. package/dist/session/scheduler.js +28 -16
  39. package/dist/session/state.js +16 -12
  40. package/dist/session/store.js +218 -0
  41. package/dist/session/trace.js +5 -15
  42. package/dist/tools/policy.js +19 -15
  43. package/dist/tools/registry.js +21 -229
  44. package/dist/tools/router.js +5 -3
  45. package/dist/tools/tool-runtime.js +267 -0
  46. package/dist/ui/layout-internal/content-write.js +4 -0
  47. package/package.json +7 -3
@@ -8,10 +8,11 @@ import { summarizeToolCall, summarizeToolResult, truncateDisplay, fmtElapsed } f
8
8
  import { renderFileChange } from '../ui/diff.js';
9
9
  import * as layout from '../ui/layout.js';
10
10
  import * as batch from '../ui/batch.js';
11
- import { beginTurn } from '../rollback/index.js';
12
11
  import { config } from '../config/index.js';
13
- import { runAgentCore, isMutationTool } from './core.js';
12
+ import { isMutationTool } from './core.js';
13
+ import { createRuntime, defaultRuntime, Runtime } from '../runtime/index.js';
14
14
  import { createPetHooks } from '../pet/state.js';
15
+ import { defaultAgentRuntimeContext } from './runtime-context.js';
15
16
  import { t } from '../i18n/index.js';
16
17
  import { isToolErrorOutput } from '../tools/result.js';
17
18
  import { appendCurrentSessionTraceEvent } from '../session/index.js';
@@ -81,7 +82,7 @@ function firstLineOf(ui) {
81
82
  /** 工具调用 ● 头:工具名 + 参数摘要(按 tool_calls 原顺序打印,让用户看到本轮跑哪些工具)。
82
83
  * 重构后改为累积到 BatchRenderer,onToolBatchEnd 时统一打摘要行;
83
84
  * 展开/折叠由 BatchRenderer + 鼠标 release 决定,本函数不再直接写屏。 */
84
- function writeToolHeader(tc) {
85
+ function writeToolHeader(tc, toolRuntime) {
85
86
  if (tc.name === SUB_AGENT_TOOL) {
86
87
  // sub-agent:同一轮并行派发的多个 sub-agent 合并进同一个「组容器批」——
87
88
  // 顶层只有一个 ● 探索 N ... sub-agent N 摘要行,下面按 └─ sub-agent {...} 逐条
@@ -92,6 +93,8 @@ function writeToolHeader(tc) {
92
93
  // 避免普通批摘要行与 sub-agent 组摘要行粘在一起。
93
94
  if (currentBatchId)
94
95
  flushToolBatch();
96
+ // 气泡/正文 → 组摘要行边界:尾部视觉空行幂等收成 1 条(见 mutation 分支同注释)。
97
+ layout.normalizeMutationBoundary();
95
98
  // 首个 sub-agent 调用:建组容器批(顶层)。label 缺省=「探索」,entries 累计各 sub-agent 调用。
96
99
  subAgentGroupId = batch.beginBatch(undefined, { groupParent: true });
97
100
  batch.recordCall(subAgentGroupId, tc.name, summarizeToolCall(tc.name, tc.arguments), tc.id);
@@ -115,10 +118,15 @@ function writeToolHeader(tc) {
115
118
  if (subAgentGroupId || subAgentGroupPendingSeparator) {
116
119
  flushToolBatch();
117
120
  }
118
- if (isMutationTool(tc.name)) {
121
+ if (isMutationTool(tc.name, toolRuntime)) {
119
122
  // mutation 永远独占一批(diff 要紧跟调用行)。先收口当前普通批。
120
123
  if (currentBatchId)
121
124
  flushToolBatch();
125
+ // 气泡/正文 → 首摘要行边界:尾部视觉空行幂等收成恰好 1 条。
126
+ // 气泡 trailingBlank、轮次分隔 \n、flush 补行等各路写入都可能让尾部留 2+ 条空行,
127
+ // 首摘要落屏前统一归一,与正文 → mutation 的 onToolHeader normalize 同语义(此处覆盖
128
+ // 无前置正文的 turn 首工具;有前置正文时 toolBatchFollowsText 分支也会归一,幂等无副作用)。
129
+ layout.normalizeMutationBoundary();
122
130
  const id = batch.beginBatch();
123
131
  currentBatchId = id;
124
132
  batch.bindCall(tc.id, id);
@@ -127,7 +135,11 @@ function writeToolHeader(tc) {
127
135
  }
128
136
  else {
129
137
  // 普通工具合并到 currentBatchId;同轮并行或连续无正文的工具轮次共享一个摘要行。
138
+ const isNewBatch = currentBatchId == null;
130
139
  const id = (currentBatchId ??= batch.beginBatch());
140
+ // 新建批 = 新摘要行落屏:同 mutation 分支,边界空行归一(复用已有批时不碰,避免打断批内续行)。
141
+ if (isNewBatch)
142
+ layout.normalizeMutationBoundary();
131
143
  // 结果按 tool_call id 归位:并行执行时 currentBatchId 会漂移,只按“当前批”回填会漏填,
132
144
  // 摘要行就永远停在 ◇(用户实测:子 agent 跑完主侧菱形没变成实心圆)。
133
145
  batch.bindCall(tc.id, id);
@@ -138,7 +150,7 @@ function writeToolHeader(tc) {
138
150
  }
139
151
  /** 渲染工具结果:mutation 成功走 diff 块(行号 + 语法高亮);其余走一行 preview。
140
152
  * 同 writeToolHeader,改为累积到 BatchRenderer(只缓存字符串,不写屏)。 */
141
- function writeToolResult(tc, output, parsed, preWriteOld, editStartLine) {
153
+ function writeToolResult(tc, output, parsed, preWriteOld, editStartLine, toolRuntime) {
142
154
  // 优先按 tool_call id 反查所属批(并行 / sub-agent 组时 currentBatchId 已不是它)。
143
155
  const batchId = batch.batchIdForCall(tc.id) ?? currentBatchId;
144
156
  if (!batchId)
@@ -180,7 +192,7 @@ function writeToolResult(tc, output, parsed, preWriteOld, editStartLine) {
180
192
  batch.recordResult(batchId, tc.name, preview, diff, output, isToolErrorOutput(output));
181
193
  batch.showLiveBatch(batchId, layout);
182
194
  // mutation 结果(成功 diff 或错误输出)立即可见,并阻止后续普通工具并入这一批。
183
- if (isMutationTool(tc.name))
195
+ if (isMutationTool(tc.name, toolRuntime))
184
196
  finishStandaloneBatch(batchId, true);
185
197
  }
186
198
  /** 收尾一个独占批(mutation / sub-agent)。按 id 收尾而不是按 currentBatchId:
@@ -250,9 +262,14 @@ onContextUpdate,
250
262
  /** 本用户 turn 的自动路由工具策略。 */
251
263
  toolPolicy,
252
264
  /** 仅真实用户 turn 传入;合成 plan 执行轮继承 policy 但不重复记录初始路由。 */
253
- initialToolRoute) {
254
- // 开新轮次(回滚用):首行截断 40,供 /rollback 轮次菜单展示。
255
- beginTurn(truncateDisplay(firstLineOf(userInput), 40));
265
+ initialToolRoute,
266
+ /** TUI composition root 绑定的 Runtime;旧调用传 AgentRuntimeContext 仍兼容。 */
267
+ runtimeOrContext = defaultRuntime) {
268
+ const runtime = runtimeOrContext instanceof Runtime
269
+ ? runtimeOrContext
270
+ : runtimeOrContext === defaultAgentRuntimeContext
271
+ ? defaultRuntime
272
+ : createRuntime({ context: runtimeOrContext });
256
273
  layout.contentMode(); // 防御性:运行态光标归输入框光标位供 IME 锚定(enterRunningMode 已置,这里兜底)
257
274
  currentBatchId = null; // 新 turn 清旧 batch id(防上 turn 残留)
258
275
  subAgentGroupId = null;
@@ -295,6 +312,11 @@ initialToolRoute) {
295
312
  // 一旦模型开始解释阶段结果,立即收尾当前摘要;后续工具重新建立批次。
296
313
  if (visible)
297
314
  flushToolBatch();
315
+ // 工具块 → 正文边界幂等归一:无论 flush/expand 路径漏出几条空行(历史 bug 实测最多 6 条),
316
+ // 落正文前把尾部视觉空行收成恰好 1 条。与正文 → mutation 的 normalizeMutationBoundary 对称。
317
+ // 仅首 chunk(followsToolBatch)做:后续 chunk 属同一段流式正文,归一会截断 md 段。
318
+ if (followsToolBatch && visible)
319
+ layout.normalizeMutationBoundary();
298
320
  spinner.stop(); // 任何正文 token 都停 spinner(首 token 停「思考中」;onToolCall 重启后若又来文本则停「生成中」)。未旋转时 stop 为 no-op。
299
321
  layout.contentWriteMd(visible); // 正文走 markdown 渲染(代码块高亮 / 标题 / 列表 / 行内 …),见 ui/markdown.ts
300
322
  if (visible) {
@@ -373,15 +395,15 @@ initialToolRoute) {
373
395
  onToolHeader: (tc) => {
374
396
  // mutation 的自动展开会把 current/committed 空行状态互相转换;在首摘要真正
375
397
  // 落屏前按视觉行归一,避免同样的文本→edit 边界偶发 1 行或 2 行。
376
- if (toolBatchFollowsText && isMutationTool(tc.name)) {
398
+ if (toolBatchFollowsText && isMutationTool(tc.name, runtime.context.toolRuntime)) {
377
399
  layout.normalizeMutationBoundary();
378
400
  }
379
401
  toolBatchFollowsText = false;
380
- writeToolHeader(tc);
402
+ writeToolHeader(tc, runtime.context.toolRuntime);
381
403
  },
382
404
  onToolStart: (name) => spinner.start(t('agent.executing', { tool: name })),
383
405
  onToolDone: () => spinner.stop(),
384
- onToolResult: (tc, output, parsed, preWriteOld, editStartLine) => writeToolResult(tc, output, parsed, preWriteOld, editStartLine),
406
+ onToolResult: (tc, output, parsed, preWriteOld, editStartLine) => writeToolResult(tc, output, parsed, preWriteOld, editStartLine, runtime.context.toolRuntime),
385
407
  onToolBatchEnd: () => {
386
408
  // 一次工具轮次结束不再切 UI batch;下一轮若仍无正文,继续复用 currentBatchId。
387
409
  },
@@ -419,7 +441,9 @@ initialToolRoute) {
419
441
  const combinedHooks = mergeHooks(hooks, petHooks);
420
442
  let result;
421
443
  try {
422
- result = await runAgentCore({
444
+ result = await runtime.run({
445
+ turn: 'new',
446
+ turnLabel: truncateDisplay(firstLineOf(userInput), 40),
423
447
  history,
424
448
  userInput,
425
449
  signal,
@@ -0,0 +1,218 @@
1
+ import { estimatePromptTokens, estimateTokens, isContextLengthError, } from '../llm/index.js';
2
+ /** Executes context preparation plus exactly one model step, including the single overflow retry path. */
3
+ export async function runModelTurn(input) {
4
+ const { opts, ctx, history, historyManager, runtimeContextState, scheduler, contextTrimmer, modelRunner, activeTools, runPolicy, step, cacheState, turnLifecycle, cancellationLifecycle, rebuildHistoryIndexes, } = input;
5
+ const { signal, onContextUpdate, hooks } = opts;
6
+ const { usageMeter, emitTrace } = turnLifecycle;
7
+ const requestBaseURL = ctx.config.baseURL;
8
+ const requestModel = ctx.getActiveModel();
9
+ const storedCalibration = ctx.getTokenCalibration(requestBaseURL, requestModel, activeTools);
10
+ runtimeContextState.correction = storedCalibration.correction;
11
+ runtimeContextState.calibrationSamples = storedCalibration.samples;
12
+ let sessionStateText = opts.suppressSessionState ? '' : ctx.buildSessionStateReminder();
13
+ runtimeContextState.ephemeralText = sessionStateText || undefined;
14
+ onContextUpdate?.();
15
+ let historyRebuilt = false;
16
+ let overflowRetried = false;
17
+ const compactStartedAt = Date.now();
18
+ const trimResult = await contextTrimmer.trim({
19
+ mode: scheduler ? 'scheduled' : 'fallback',
20
+ history: historyManager.snapshot(),
21
+ step,
22
+ tools: activeTools,
23
+ ephemeralTokens: sessionStateText ? estimateTokens(sessionStateText) : 0,
24
+ signal,
25
+ });
26
+ historyRebuilt = trimResult.kind === 'rebuild';
27
+ const trimStats = trimResult.kind === 'aborted' ? {} : trimResult.stats;
28
+ if (scheduler && trimStats.compactHistoryCalled) {
29
+ emitTrace('compact', {
30
+ source: 'automatic',
31
+ reason: 'scheduled',
32
+ historyRebuilt,
33
+ durationMs: Date.now() - compactStartedAt,
34
+ });
35
+ }
36
+ else if (!scheduler && trimResult.kind !== 'aborted' && trimStats.reason) {
37
+ emitTrace('compact', {
38
+ source: 'automatic_fallback',
39
+ reason: trimStats.reason,
40
+ compacted: trimStats.compacted,
41
+ historyRebuilt,
42
+ estimateBefore: trimStats.estimateBefore,
43
+ estimateAfter: trimStats.estimateAfter,
44
+ durationMs: Date.now() - compactStartedAt,
45
+ });
46
+ }
47
+ if (historyRebuilt) {
48
+ rebuildHistoryIndexes();
49
+ if (!opts.suppressSessionState)
50
+ sessionStateText = ctx.buildSessionStateReminder();
51
+ }
52
+ hooks.onStepStart?.();
53
+ const stream = { mode: 'idle', gotText: false, lastChar: '' };
54
+ const onText = (text) => {
55
+ hooks.onText?.(text);
56
+ stream.mode = 'text';
57
+ stream.gotText = true;
58
+ if (text)
59
+ stream.lastChar = text[text.length - 1];
60
+ };
61
+ const onToolCall = (name) => {
62
+ if (stream.lastChar && stream.lastChar !== '\n') {
63
+ hooks.onTextEnd?.();
64
+ stream.lastChar = '\n';
65
+ }
66
+ hooks.onToolCall?.(name);
67
+ };
68
+ let result;
69
+ const modelStartedAt = Date.now();
70
+ const provider = ctx.safeProviderId(requestBaseURL);
71
+ emitTrace('model_start', { model: requestModel, provider });
72
+ const buildRequestHistory = () => {
73
+ const ephemeralReminder = [
74
+ runPolicy.reminder,
75
+ !opts.suppressOpeningAnalysis && step === 0
76
+ ? '## Opening analysis\nBegin your FIRST response of this turn with a brief analysis of the request and your planned approach (1-3 sentences, no filler), THEN start tool calls. This opening is the only place where pre-tool prose is expected; after it, work quietly with no narration between tool calls.'
77
+ : '',
78
+ historyRebuilt
79
+ ? '## Post-compaction recovery\n' +
80
+ 'Context was compacted before this request. Recover before doing anything else, in this order:\n' +
81
+ '1. Read the session summary at the top of the history: `## Completed` is already done — do not redo or re-verify it. `## In Progress` / `## Next Steps` tell you exactly where work stopped and what is next.\n' +
82
+ '2. Read `## Session state` below (from notes.md, refreshed every step): the active plan is authoritative — `[x]` steps are finished, resume from the first `[ ]`. A `## Compaction Snapshot` section there is the progress checkpoint written at this compaction.\n' +
83
+ (sessionStateText
84
+ ? ''
85
+ : '(No active plan or snapshot was found in notes.md — reconstruct what is done purely from the summary and treat its `## Completed` as ground truth.)\n') +
86
+ '3. Before any file edit, read_file the target fresh to get the current content hash — never edit from memory of pre-compaction content.\n' +
87
+ '4. Before re-running a search/read you think you already did, check the summary and notes first: only repeat it if the result is genuinely missing or the target has changed.'
88
+ : '',
89
+ sessionStateText,
90
+ ]
91
+ .filter(Boolean)
92
+ .join('\n\n');
93
+ return ephemeralReminder ? [...history, { role: 'system', content: ephemeralReminder }] : history;
94
+ };
95
+ let requestHistory = buildRequestHistory();
96
+ onContextUpdate?.();
97
+ let stepPromptEst = estimatePromptTokens(requestHistory, activeTools, runtimeContextState.correction);
98
+ const reportLive = (progress) => {
99
+ const completedUsage = usageMeter.snapshot();
100
+ const curPrompt = progress.promptTokens ?? stepPromptEst;
101
+ const curCached = progress.cachedTokens ??
102
+ (cacheState.providerCacheSeen ? Math.min(cacheState.lastStepPromptTokens, curPrompt) : 0);
103
+ hooks.onLiveUsage?.({
104
+ promptTokens: (completedUsage?.promptTokens ?? 0) + curPrompt,
105
+ completionTokens: (completedUsage?.completionTokens ?? 0) + progress.completionTokens,
106
+ totalTokens: (completedUsage?.totalTokens ?? 0) + curPrompt + progress.completionTokens,
107
+ cachedTokens: (completedUsage?.cachedTokens ?? 0) + curCached,
108
+ });
109
+ };
110
+ reportLive({ completionTokens: 0 });
111
+ const chatHandlers = {
112
+ onText,
113
+ onToolCall,
114
+ onProgress: reportLive,
115
+ onRetry: (retry) => emitTrace('model_retry', {
116
+ model: requestModel,
117
+ provider,
118
+ attempt: retry.attempt,
119
+ nextAttempt: retry.nextAttempt,
120
+ waitMs: retry.waitMs,
121
+ code: retry.code,
122
+ }),
123
+ };
124
+ const runChatOnce = async () => {
125
+ try {
126
+ return await modelRunner.run({ history: requestHistory, handlers: chatHandlers, tools: activeTools }, signal);
127
+ }
128
+ catch (error) {
129
+ const errorValue = error && typeof error === 'object' ? error : undefined;
130
+ emitTrace('model_end', {
131
+ model: requestModel,
132
+ provider,
133
+ status: signal?.aborted ? 'aborted' : 'error',
134
+ code: typeof errorValue?.status === 'number'
135
+ ? `HTTP_${errorValue.status}`
136
+ : (errorValue?.code ?? errorValue?.name ?? 'MODEL_ERROR'),
137
+ durationMs: Date.now() - modelStartedAt,
138
+ });
139
+ throw error;
140
+ }
141
+ };
142
+ try {
143
+ result = await runChatOnce();
144
+ }
145
+ catch (error) {
146
+ if (signal?.aborted ||
147
+ (error instanceof Error && (error.name === 'AbortError' || error.name === 'APIUserAbortError'))) {
148
+ cancellationLifecycle.restore();
149
+ turnLifecycle.markAborted();
150
+ return { kind: 'aborted', result: turnLifecycle.buildAbortedResult() };
151
+ }
152
+ if (!overflowRetried && isContextLengthError(error)) {
153
+ overflowRetried = true;
154
+ const rawEstimate = estimatePromptTokens(requestHistory, activeTools);
155
+ if (rawEstimate > 1_000) {
156
+ const calibration = ctx.updateTokenCalibration(requestBaseURL, requestModel, activeTools, rawEstimate, ctx.config.contextWindowTokens);
157
+ runtimeContextState.correction = calibration.correction;
158
+ runtimeContextState.calibrationSamples = calibration.samples;
159
+ }
160
+ const overflowResult = await contextTrimmer.trim({
161
+ mode: 'overflow',
162
+ history: historyManager.snapshot(),
163
+ step,
164
+ tools: activeTools,
165
+ ephemeralTokens: sessionStateText ? estimateTokens(sessionStateText) : 0,
166
+ signal,
167
+ });
168
+ const overflowStats = overflowResult.kind === 'aborted' ? {} : overflowResult.stats;
169
+ emitTrace('compact', {
170
+ source: 'overflow_retry',
171
+ reason: overflowStats.reason ?? 'noop',
172
+ compacted: overflowStats.compacted === true,
173
+ estimateBefore: overflowStats.estimateBefore,
174
+ estimateAfter: overflowStats.estimateAfter,
175
+ durationMs: Date.now() - modelStartedAt,
176
+ });
177
+ if (overflowResult.kind === 'none' || overflowResult.kind === 'aborted')
178
+ throw error;
179
+ if (overflowResult.kind === 'rebuild') {
180
+ historyRebuilt = true;
181
+ rebuildHistoryIndexes();
182
+ }
183
+ requestHistory = buildRequestHistory();
184
+ stepPromptEst = estimatePromptTokens(requestHistory, activeTools, runtimeContextState.correction);
185
+ result = await runChatOnce();
186
+ }
187
+ else {
188
+ throw error;
189
+ }
190
+ }
191
+ emitTrace('model_end', {
192
+ model: requestModel,
193
+ provider,
194
+ status: 'success',
195
+ durationMs: Date.now() - modelStartedAt,
196
+ promptTokens: result.usage?.promptTokens,
197
+ completionTokens: result.usage?.completionTokens,
198
+ totalTokens: result.usage?.totalTokens,
199
+ cachedTokens: result.usage?.cachedTokens,
200
+ reasoningTokens: result.usage?.reasoningTokens,
201
+ });
202
+ runtimeContextState.lastUsage = result.usage;
203
+ usageMeter.add(result.usage);
204
+ if (result.usage) {
205
+ cacheState.lastStepPromptTokens = result.usage.promptTokens;
206
+ if (result.usage.cachedTokens > 0)
207
+ cacheState.providerCacheSeen = true;
208
+ }
209
+ if (result.usage?.promptTokens && result.usage.promptTokens > 100) {
210
+ const estimated = estimatePromptTokens(requestHistory, activeTools);
211
+ const updated = ctx.updateTokenCalibration(requestBaseURL, requestModel, activeTools, estimated, result.usage.promptTokens);
212
+ runtimeContextState.correction = updated.correction;
213
+ runtimeContextState.calibrationSamples = updated.samples;
214
+ }
215
+ hooks.onChatDone?.();
216
+ onContextUpdate?.();
217
+ return { kind: 'result', result, stream };
218
+ }
@@ -0,0 +1,18 @@
1
+ import { AGENT_STAGE_NAMES, } from './stages/contracts.js';
2
+ import { createLegacyHistoryManager, createStagedHistoryManager } from './stages/history-manager.js';
3
+ import { createLegacyCoordinatorAdapter, createLegacyStageAdapters, } from './stages/legacy-adapters.js';
4
+ /** Build a fresh, independently overridable stage assembly for one agent run. */
5
+ export function createAgentPipelineAssembly(init) {
6
+ const pipeline = init.pipeline ?? 'legacy';
7
+ const coordinator = createLegacyCoordinatorAdapter(init.runLegacy);
8
+ const defaults = Object.fromEntries(AGENT_STAGE_NAMES.map((name) => [name, pipeline === 'staged' ? 'staged' : 'legacy']));
9
+ const stages = createLegacyStageAdapters({ ...defaults, ...init.stageOverrides });
10
+ const createHistoryManager = stages.history.implementation === 'staged' ? createStagedHistoryManager : createLegacyHistoryManager;
11
+ return Object.freeze({
12
+ pipeline,
13
+ coordinator,
14
+ stages,
15
+ createHistoryManager,
16
+ run: (options) => coordinator.run(options, createHistoryManager({ messages: options.history }), stages),
17
+ });
18
+ }
@@ -0,0 +1 @@
1
+ export {};