@mercury-fw/core 0.25.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 (117) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +38 -0
  3. package/dist/index.d.ts +23 -0
  4. package/dist/src/admin/cli-routes.d.ts +22 -0
  5. package/dist/src/admin/env-file.d.ts +1 -0
  6. package/dist/src/admin/model-routes.d.ts +26 -0
  7. package/dist/src/admin/qdrant-scroll.d.ts +34 -0
  8. package/dist/src/admin/server.d.ts +40 -0
  9. package/dist/src/admin/wiki-routes.d.ts +31 -0
  10. package/dist/src/compose.d.ts +42 -0
  11. package/dist/src/config/define-config.d.ts +31 -0
  12. package/dist/src/cron/idle-session-cron.d.ts +80 -0
  13. package/dist/src/cron/idle-session-scanner.d.ts +16 -0
  14. package/dist/src/cron/self-review-cron.d.ts +55 -0
  15. package/dist/src/cron/semantic-consolidation.d.ts +71 -0
  16. package/dist/src/memory/embedder.d.ts +9 -0
  17. package/dist/src/memory/episodic-store.d.ts +121 -0
  18. package/dist/src/memory/memory-provider.d.ts +51 -0
  19. package/dist/src/memory/semantic-facts-store.d.ts +37 -0
  20. package/dist/src/memory/tool-corrections-store.d.ts +26 -0
  21. package/dist/src/memory/verbatim-archive-store.d.ts +86 -0
  22. package/dist/src/model/client.d.ts +24 -0
  23. package/dist/src/model/context-size.d.ts +30 -0
  24. package/dist/src/plugins/manifest.d.ts +29 -0
  25. package/dist/src/plugins/plugin-loader.d.ts +85 -0
  26. package/dist/src/router/channel-loader.d.ts +30 -0
  27. package/dist/src/router/provider.d.ts +7 -0
  28. package/dist/src/router/terminal-provider.d.ts +37 -0
  29. package/dist/src/router/terminal.d.ts +41 -0
  30. package/dist/src/router/tool-log.d.ts +65 -0
  31. package/dist/src/router/turn-runner.d.ts +86 -0
  32. package/dist/src/session/agent-turn.d.ts +266 -0
  33. package/dist/src/session/context-primer.d.ts +16 -0
  34. package/dist/src/session/episodic-summarizer.d.ts +25 -0
  35. package/dist/src/session/history.d.ts +95 -0
  36. package/dist/src/session/pending-confirmation.d.ts +8 -0
  37. package/dist/src/session/read-skill-tool.d.ts +4 -0
  38. package/dist/src/session/semantic-fact-extractor.d.ts +45 -0
  39. package/dist/src/session/step-info.d.ts +24 -0
  40. package/dist/src/session/summarizer.d.ts +23 -0
  41. package/dist/src/session/system-prompt.d.ts +38 -0
  42. package/dist/src/session/tool-correction-extractor.d.ts +43 -0
  43. package/dist/src/session/tool-log-buffer.d.ts +24 -0
  44. package/dist/src/session/tool-log-recall-tool.d.ts +18 -0
  45. package/dist/src/session/tool-start-hook.d.ts +57 -0
  46. package/dist/src/tools/display-store.d.ts +36 -0
  47. package/dist/src/tools/present-tool.d.ts +23 -0
  48. package/dist/src/wiki/frontmatter-schema.d.ts +53 -0
  49. package/dist/src/wiki/index-entry.d.ts +15 -0
  50. package/dist/src/wiki/orphan-detector.d.ts +1 -0
  51. package/dist/src/wiki/self-review-runner.d.ts +48 -0
  52. package/dist/src/wiki/self-review-tools.d.ts +22 -0
  53. package/dist/src/wiki/vault-cli.d.ts +2 -0
  54. package/dist/src/wiki/vault-init.d.ts +7 -0
  55. package/dist/src/wiki/wiki-note.d.ts +62 -0
  56. package/dist/src/wiki/wiki-read.d.ts +27 -0
  57. package/dist/src/wiki/wiki-tools.d.ts +7 -0
  58. package/index.ts +23 -0
  59. package/package.json +49 -0
  60. package/src/admin/cli-routes.ts +48 -0
  61. package/src/admin/env-file.ts +29 -0
  62. package/src/admin/model-routes.ts +71 -0
  63. package/src/admin/public/index.html +416 -0
  64. package/src/admin/qdrant-scroll.ts +45 -0
  65. package/src/admin/server.ts +188 -0
  66. package/src/admin/wiki-routes.ts +93 -0
  67. package/src/compose.ts +599 -0
  68. package/src/config/define-config.ts +35 -0
  69. package/src/cron/.gitkeep +0 -0
  70. package/src/cron/idle-session-cron.ts +144 -0
  71. package/src/cron/idle-session-scanner.ts +37 -0
  72. package/src/cron/self-review-cron.ts +103 -0
  73. package/src/cron/semantic-consolidation.ts +228 -0
  74. package/src/memory/.gitkeep +0 -0
  75. package/src/memory/embedder.ts +15 -0
  76. package/src/memory/episodic-store.ts +183 -0
  77. package/src/memory/memory-provider.ts +98 -0
  78. package/src/memory/semantic-facts-store.ts +89 -0
  79. package/src/memory/tool-corrections-store.ts +72 -0
  80. package/src/memory/verbatim-archive-store.ts +202 -0
  81. package/src/model/client.ts +33 -0
  82. package/src/model/context-size.ts +42 -0
  83. package/src/plugins/manifest.ts +47 -0
  84. package/src/plugins/plugin-loader.ts +205 -0
  85. package/src/router/channel-loader.ts +56 -0
  86. package/src/router/provider.ts +7 -0
  87. package/src/router/terminal-provider.ts +155 -0
  88. package/src/router/terminal.ts +151 -0
  89. package/src/router/tool-log.ts +116 -0
  90. package/src/router/turn-runner.ts +205 -0
  91. package/src/session/agent-turn.ts +391 -0
  92. package/src/session/context-primer.ts +134 -0
  93. package/src/session/episodic-summarizer.ts +38 -0
  94. package/src/session/history.ts +168 -0
  95. package/src/session/pending-confirmation.ts +8 -0
  96. package/src/session/read-skill-tool.ts +38 -0
  97. package/src/session/semantic-fact-extractor.ts +69 -0
  98. package/src/session/step-info.ts +27 -0
  99. package/src/session/summarizer.ts +36 -0
  100. package/src/session/system-prompt.ts +142 -0
  101. package/src/session/tool-correction-extractor.ts +133 -0
  102. package/src/session/tool-log-buffer.ts +73 -0
  103. package/src/session/tool-log-recall-tool.ts +38 -0
  104. package/src/session/tool-start-hook.ts +164 -0
  105. package/src/tools/display-store.ts +89 -0
  106. package/src/tools/present-tool.ts +41 -0
  107. package/src/wiki/.gitkeep +0 -0
  108. package/src/wiki/frontmatter-schema.ts +49 -0
  109. package/src/wiki/index-entry.ts +59 -0
  110. package/src/wiki/orphan-detector.ts +61 -0
  111. package/src/wiki/self-review-runner.ts +133 -0
  112. package/src/wiki/self-review-tools.ts +162 -0
  113. package/src/wiki/vault-cli.ts +143 -0
  114. package/src/wiki/vault-init.ts +43 -0
  115. package/src/wiki/wiki-note.ts +326 -0
  116. package/src/wiki/wiki-read.ts +122 -0
  117. package/src/wiki/wiki-tools.ts +112 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Helpers for the terminal channel's tool-call visibility (see
3
+ * `src/index.ts`'s `onStepFinish` wiring): tool results can be
4
+ * arbitrarily large — a single Jira issue search can return tens of KB
5
+ * of raw API JSON — so printing them in full on every turn isn't
6
+ * readable. `truncateForDisplay` bounds what gets printed live;
7
+ * `parseDumpCommand` + `writeDump` back the terminal-only `/dump`
8
+ * command, for when the full untruncated output is actually needed.
9
+ *
10
+ * This module is stateless and doesn't know about turns or sessions —
11
+ * `src/index.ts` owns the per-turn step history and decides when to call
12
+ * these.
13
+ */
14
+ import { tmpdir } from "node:os";
15
+ import type { StepInfo } from "../session/step-info.ts";
16
+
17
+ /**
18
+ * Stringifies `value` as JSON, truncating to `maxChars` and appending a
19
+ * marker with the real total length and a pointer to `/dump` when it
20
+ * doesn't fit — so a human watching the terminal sees something bounded
21
+ * but still knows more is available and how to get it.
22
+ */
23
+ export function truncateForDisplay(value: unknown, maxChars: number): string {
24
+ const json = JSON.stringify(value);
25
+ if (json.length <= maxChars) {
26
+ return json;
27
+ }
28
+ return `${json.slice(0, maxChars)}… (truncated, ${json.length} chars total — run /dump to write the full output to a file)`;
29
+ }
30
+
31
+ /**
32
+ * Builds the file `/dump` writes to when the user doesn't give an
33
+ * explicit path. Lands in the OS temp dir, not the process's cwd — in
34
+ * the Docker image that's `/app`, owned by root, where the `mercury`
35
+ * user can read/execute existing files but not create new ones (every
36
+ * default-path `/dump` failed with EACCES until this). Includes a
37
+ * timestamp (colons/dots replaced since they aren't valid in filenames
38
+ * on every filesystem) so repeated `/dump` calls land in separate files
39
+ * instead of silently overwriting a fixed default each time.
40
+ */
41
+ export function defaultDumpPath(now: Date = new Date()): string {
42
+ return `${tmpdir()}/mercury-last-tools-${now.toISOString().replace(/[:.]/g, "-")}.json`;
43
+ }
44
+
45
+ /**
46
+ * Parses a `/dump [path]` command line. Returns null for anything that
47
+ * isn't exactly this command (including regular conversation input, and
48
+ * a slash-prefixed word that merely starts with "dump") — the caller
49
+ * uses this to tell a real command from a message meant for the model.
50
+ * `path` is undefined when none was given — the caller decides the
51
+ * default (see `defaultDumpPath`), since computing "now" here would make
52
+ * this function's output depend on when it happens to run.
53
+ */
54
+ export function parseDumpCommand(line: string): { path: string | undefined } | null {
55
+ const match = line.trim().match(/^\/dump(?:\s+(\S+))?$/);
56
+ if (!match) {
57
+ return null;
58
+ }
59
+ return { path: match[1] };
60
+ }
61
+
62
+ /**
63
+ * Writes `steps` to `path` as indented JSON — human-readable, since this
64
+ * is the path a person opens by hand to inspect what a tool actually
65
+ * returned, not something machine-parsed downstream.
66
+ */
67
+ export async function writeDump(path: string, steps: StepInfo[]): Promise<void> {
68
+ await Bun.write(path, JSON.stringify(steps, null, 2));
69
+ }
70
+
71
+ /**
72
+ * Describes what happened to the tool call identified by `toolCallId`
73
+ * within `step`: its result if it executed, the `tool-error` content
74
+ * part if it failed before executing (e.g. arguments that don't match
75
+ * the tool's schema — there's no `toolResults` entry for this case, it
76
+ * only shows up in `content`), or an explicit "(none)" if neither is
77
+ * present. Printing "(none)" for an actual failure was the bug this
78
+ * fixes — it read as "nothing happened" when something had, in fact,
79
+ * gone wrong and been silently dropped from view.
80
+ */
81
+ export function describeToolOutcome(step: StepInfo, toolCallId: string, maxChars: number): string {
82
+ const result = step.toolResults.find((r) => r.toolCallId === toolCallId);
83
+ if (result) {
84
+ return `[tool result] ${truncateForDisplay(result.output, maxChars)}`;
85
+ }
86
+ const errorPart = step.content.find((p) => p.type === "tool-error" && p.toolCallId === toolCallId);
87
+ if (errorPart) {
88
+ return `[tool error] ${truncateForDisplay(errorPart.error, maxChars)}`;
89
+ }
90
+ return "[tool result] (none)";
91
+ }
92
+
93
+ /**
94
+ * Formats real token counts for display next to the terminal prompt:
95
+ * `usedTokens` is the real `inputTokens` the last turn's call reported
96
+ * (see `src/session/agent-turn.ts`'s `onUsage`), `maxTokens` is the
97
+ * context length Ollama actually has the model loaded with right now
98
+ * (see `src/model/context-size.ts`) — not an estimate, and not the
99
+ * model's architectural maximum, which can overstate what's really
100
+ * usable. A model degrading over a long conversation is hard to tell
101
+ * apart by eye from "the context is genuinely near full" — this gives a
102
+ * live number instead of having to guess.
103
+ *
104
+ * `maxTokens` is `null` before the model has been loaded at least once
105
+ * this process (nothing to report yet) — the denominator is omitted
106
+ * rather than showing a misleading `/~0k`. `usedTokens` is `undefined`
107
+ * before the first turn has completed, shown as `?` for the same reason.
108
+ */
109
+ export function formatContextUsage(usedTokens: number | undefined, maxTokens: number | null): string {
110
+ const used = usedTokens === undefined ? "?" : Math.floor(usedTokens / 1000);
111
+ if (maxTokens === null) {
112
+ return `[~${used}k tokens] `;
113
+ }
114
+ const max = Math.floor(maxTokens / 1000);
115
+ return `[~${used}k/~${max}k tokens] `;
116
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Deduplicates what `src/index.ts` used to do twice: two near-identical
3
+ * ~50-line closures, one per channel, that (1) tracked the session for
4
+ * Layer-3 capture when the channel has a real per-user identity, (2) ran
5
+ * `runTurn` with a channel-specific tool set/system prompt/output sink,
6
+ * and (3) mirrored new messages to Qdrant and extracted procedural
7
+ * corrections once the turn resolved. `createTurnRunner` is that shared
8
+ * body, parameterized entirely by `Provider`/`InboundTurn`/`TurnSink`
9
+ * (`src/router/provider.ts`) so it doesn't know or care which provider a
10
+ * given turn came from — same channel-agnostic spirit as `runTurn` itself.
11
+ */
12
+ import type { LanguageModel, Tool } from "ai";
13
+ import { runTurn } from "../session/agent-turn.ts";
14
+ import type { StepInfo } from "../session/step-info.ts";
15
+ // `PostTurnGuard` is part of the plugin contract (a plugin's `build()` may
16
+ // return these to inspect/rewrite the model's finished text), so it lives in
17
+ // `@mercury-fw/plugin-types` and is re-exported here for the core callers that
18
+ // import it from this module. No plugin ships one today; the mechanism stays
19
+ // generic for future use. A guard that throws is caught by the core and never
20
+ // blocks delivery — a guard failure is a quality miss, not a reason to
21
+ // withhold an already-generated answer.
22
+ import type { PostTurnGuard } from "@mercury-fw/plugin-types";
23
+ export type { PostTurnGuard };
24
+ import type { SessionHistory } from "../session/history.ts";
25
+ import { recordStep } from "../session/tool-log-buffer.ts";
26
+ import type { HandleTurn, InboundTurn, TurnSink } from "./provider.ts";
27
+
28
+ export type TurnRunnerDeps = {
29
+ model: LanguageModel;
30
+ /** Both variants, precomposed by the composition root; selected per turn by `turn.multiUser`. */
31
+ systemPrompts: { singleUser: string; multiUser: string };
32
+ buildTools: (
33
+ sessionKey: string,
34
+ wikiUserId: string,
35
+ onToolStart?: TurnSink["onToolStart"],
36
+ onToolFinish?: TurnSink["onToolFinish"],
37
+ ) => Record<string, Tool>;
38
+ /**
39
+ * `userId` is forwarded (not interpreted here) so a provider's own
40
+ * closure can decide whether to seed a first-ever session with a
41
+ * context primer (see `src/session/context-primer.ts`) — building one
42
+ * needs a real per-user identity, which only some providers have.
43
+ */
44
+ getOrCreateHistory: (sessionKey: string, trackForCapture: boolean, userId: string | undefined) => Promise<SessionHistory> | SessionHistory;
45
+ /** Layer-3 session tracking (sessionUsers map + idle scanner touch). Only for turns that carry a userId. */
46
+ trackSession: (sessionKey: string, userId: string, at: number) => void;
47
+ /** Refreshes this turn's tool-status callbacks for out-of-band capture messages. */
48
+ registerCaptureCallback: (sessionKey: string, onToolStart: TurnSink["onToolStart"], onToolFinish: TurnSink["onToolFinish"]) => void;
49
+ /** Mid-conversation Layer-3 capture threshold check. Only for turns that carry a userId. */
50
+ maybeCapture: (sessionKey: string, history: SessionHistory) => Promise<void>;
51
+ /**
52
+ * Archives one message of the verbatim user↔model exchange (issue #4).
53
+ * Wired by the composition root to the verbatim-archive provider; absent
54
+ * on an instance with no such provider. Only called for turns that carry a
55
+ * `userId` (the archive is per-user, like Layer-3 capture), keyed on the
56
+ * space-independent `wikiUserId`, and only ever with the model's own answer
57
+ * text — never the appended `present` displays.
58
+ */
59
+ captureVerbatim?: (msg: {
60
+ sessionKey: string;
61
+ userId: string;
62
+ role: "user" | "assistant";
63
+ content: string;
64
+ }) => Promise<void>;
65
+ processToolCorrections: (steps: StepInfo[], onToolStart: TurnSink["onToolStart"], onToolFinish: TurnSink["onToolFinish"]) => Promise<void>;
66
+ logStep: (prefix: string, step: StepInfo) => void;
67
+ /** Test seam; defaults to the real `recordStep`. */
68
+ recordStepFn?: typeof recordStep;
69
+ /** Test seam; defaults to the real `runTurn`. */
70
+ runTurnFn?: typeof runTurn;
71
+ /**
72
+ * Post-turn guards contributed by loaded plugins, run in order over the
73
+ * model's finished text (see `PostTurnGuard`). Empty/absent on an instance
74
+ * with no plugin that registers one. The composition root builds these; the
75
+ * core knows nothing about what any of them does.
76
+ */
77
+ postTurnGuards?: PostTurnGuard[];
78
+ /**
79
+ * Test seam; defaults to `console.log`. Receives a guard's own `log` line,
80
+ * or the core's own note when a guard throws. Exists to measure real-world
81
+ * guard frequency before investing further.
82
+ */
83
+ logPostTurnGuardFn?: (message: string) => void;
84
+ /** Test seam; defaults to `Date.now`. */
85
+ now?: () => number;
86
+ /**
87
+ * Returns the display artifacts the model surfaced via `present` this turn
88
+ * (see `display-store.ts`), in stash order, to append after the model's
89
+ * text. Absent on an instance with no display store — nothing is appended.
90
+ * The old unconditional splicing of every tool-produced display is gone:
91
+ * an artifact is shown only when the model explicitly presented it.
92
+ */
93
+ takeSurfacedDisplays?: (sessionKey: string) => string[];
94
+ };
95
+
96
+ /** Builds the shared `HandleTurn` every provider's driver calls once it has a real message to run through the model. */
97
+ export function createTurnRunner(deps: TurnRunnerDeps): HandleTurn {
98
+ const postTurnGuards = deps.postTurnGuards ?? [];
99
+ const logPostTurnGuard = deps.logPostTurnGuardFn ?? ((message: string) => console.log(message));
100
+
101
+ return async (turn: InboundTurn, sink: TurnSink): Promise<void> => {
102
+ const tracked = turn.userId !== undefined;
103
+ if (tracked) {
104
+ deps.trackSession(turn.sessionKey, turn.userId as string, (deps.now ?? Date.now)());
105
+ deps.registerCaptureCallback(turn.sessionKey, sink.onToolStart, sink.onToolFinish);
106
+ }
107
+
108
+ const steps: StepInfo[] = [];
109
+ let history: SessionHistory;
110
+ // The model's own answer text (post-guards, before any surfaced `present`
111
+ // display is appended) — what the verbatim archive stores as the
112
+ // assistant's message. Assigned once the turn resolves successfully.
113
+ let assistantText = "";
114
+
115
+ try {
116
+ // Inside the try, not before it: a failure building the history
117
+ // (e.g. the context-primer's Qdrant query) must still release the
118
+ // sink (see TurnSink.dispose's doc comment) — a stuck-note timer
119
+ // already scheduled when the sink was constructed keeps running
120
+ // otherwise, firing on its own 60s schedule regardless of whether
121
+ // the turn itself already failed and was reported.
122
+ history = await deps.getOrCreateHistory(turn.sessionKey, tracked, turn.userId);
123
+ const text = await (deps.runTurnFn ?? runTurn)(history, turn.text, {
124
+ model: deps.model,
125
+ tools: deps.buildTools(turn.sessionKey, turn.wikiUserId, sink.onToolStart, sink.onToolFinish),
126
+ system: turn.multiUser ? deps.systemPrompts.multiUser : deps.systemPrompts.singleUser,
127
+ onTextChunk: sink.onTextChunk,
128
+ onReasoningChunk: sink.onReasoningChunk,
129
+ onReasoningEnd: sink.onReasoningEnd,
130
+ onStepFinish: (step) => {
131
+ steps.push(step);
132
+ deps.logStep(turn.logPrefix, step);
133
+ (deps.recordStepFn ?? recordStep)(turn.channel, turn.sessionKey, step);
134
+ sink.onStep?.(step);
135
+ },
136
+ onUsage: sink.onUsage,
137
+ abortSignal: turn.abortSignal,
138
+ });
139
+
140
+ let correctedText = text;
141
+ // Plugin-contributed post-turn guards, run in order. `shouldRun` gates
142
+ // both the rewrite and its status indicator (so a guard that doesn't
143
+ // engage shows nothing), and reuses the same onToolStart/onToolFinish
144
+ // machinery already shared with real tool calls and Layer-3 capture pings
145
+ // — terminal's dim-print and Google Chat's status-card patching both
146
+ // handle any (label, detail?, toolCallId?) triple generically. A guard
147
+ // that throws is caught here and never blocks delivery.
148
+ for (const guard of postTurnGuards) {
149
+ if (!guard.shouldRun(correctedText)) {
150
+ continue;
151
+ }
152
+ sink.onToolStart(guard.statusLabel, undefined, guard.statusId);
153
+ try {
154
+ const guarded = await guard.run(correctedText, steps);
155
+ sink.onToolFinish?.(guard.statusId, guarded.outcome);
156
+ if (guarded.log !== undefined) {
157
+ logPostTurnGuard(guarded.log);
158
+ }
159
+ correctedText = guarded.text;
160
+ } catch (err) {
161
+ sink.onToolFinish?.(guard.statusId, "failed");
162
+ logPostTurnGuard(
163
+ `[post-turn-guard] guard "${guard.statusId}" threw, kept text unchanged: ${String(err instanceof Error ? err.message : err)}`,
164
+ );
165
+ }
166
+ }
167
+
168
+ if (correctedText !== text) {
169
+ history.replaceLastAssistantMessage(correctedText);
170
+ }
171
+ assistantText = correctedText;
172
+
173
+ // Append only what the model chose to `present` this turn — never the
174
+ // whole set of tool-produced displays. Appending (not replacing) keeps
175
+ // the already-streamed prefix intact, so terminal.ts's safe-slice holds.
176
+ const surfaced = deps.takeSurfacedDisplays?.(turn.sessionKey) ?? [];
177
+ const finalText = [correctedText, ...surfaced].filter((part) => part !== "").join("\n\n");
178
+ await sink.finalize(finalText);
179
+ } finally {
180
+ sink.dispose();
181
+ }
182
+
183
+ if (tracked) {
184
+ await deps.maybeCapture(turn.sessionKey, history);
185
+ // Fail-soft: the verbatim archive is pure enrichment (principle #3).
186
+ // The answer is already delivered; a capture failure must never
187
+ // propagate out of this awaited handler and take the process down.
188
+ if (deps.captureVerbatim) {
189
+ // The archive is per-person and space-independent, so it keys on
190
+ // wikiUserId — Mercury's canonical per-user id, the same identity the
191
+ // wiki notes use — not the space-scoped session.
192
+ const userId = turn.wikiUserId;
193
+ try {
194
+ await deps.captureVerbatim({ sessionKey: turn.sessionKey, userId, role: "user", content: turn.text });
195
+ await deps.captureVerbatim({ sessionKey: turn.sessionKey, userId, role: "assistant", content: assistantText });
196
+ } catch (err) {
197
+ console.log(
198
+ `[verbatim-archive] capture failed, turn unaffected: ${String(err instanceof Error ? err.message : err)}`,
199
+ );
200
+ }
201
+ }
202
+ }
203
+ await deps.processToolCorrections(steps, sink.onToolStart, sink.onToolFinish);
204
+ };
205
+ }