@mercury-fw/core 0.26.0 → 0.27.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @mercury-fw/core
2
2
 
3
+ ## 0.27.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 2c0e020: The REPL no longer prints the answer a second time, under "risposta corretta rispetto a quanto già mostrato sopra", after a turn that reasoned or called a tool.
8
+ - @mercury-fw/plugin-types@0.27.1
9
+ - @mercury-fw/channel-types@0.27.1
10
+ - @mercury-fw/cli-engine@0.27.1
11
+ - @mercury-fw/confirm-engine@0.27.1
12
+
13
+ ## 0.27.0
14
+
15
+ ### Patch Changes
16
+
17
+ - @mercury-fw/plugin-types@0.27.0
18
+ - @mercury-fw/channel-types@0.27.0
19
+ - @mercury-fw/cli-engine@0.27.0
20
+ - @mercury-fw/confirm-engine@0.27.0
21
+
3
22
  ## 0.26.0
4
23
 
5
24
  ### Minor Changes
@@ -21,7 +21,10 @@ export declare const PROMPT = "> ";
21
21
  * resolve. If `onChunk` is never called, the function's returned
22
22
  * string is written instead once `handleInput` resolves, exactly as
23
23
  * before streaming existed — this is what keeps the `/dump` command
24
- * and any other non-streaming reply working unchanged.
24
+ * and any other non-streaming reply working unchanged. A chunk passed
25
+ * with `{ aside: true }` (reasoning, a tool label, a confirmation line)
26
+ * is written the same way but isn't part of the answer, so it's left out
27
+ * when the returned string is compared with what was streamed.
25
28
  * @param io - Test seam. Defaults to real stdin/stdout when omitted.
26
29
  * @param opts.promptSuffix - Optional, called fresh right before every
27
30
  * prompt (including the very first one) and written ahead of it — e.g.
@@ -29,7 +32,9 @@ export declare const PROMPT = "> ";
29
32
  * `formatContextUsage`), recomputed each time since the value changes
30
33
  * turn to turn.
31
34
  */
32
- export declare function startTerminalRepl(handleInput: (input: string, onChunk: (chunk: string) => void) => Promise<string>, io?: {
35
+ export declare function startTerminalRepl(handleInput: (input: string, onChunk: (chunk: string, opts?: {
36
+ aside?: boolean;
37
+ }) => void) => Promise<string>, io?: {
33
38
  input?: AsyncIterable<string>;
34
39
  output?: {
35
40
  write(s: string, opts?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/core",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,10 +31,10 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@mercury-fw/channel-types": "0.26.0",
35
- "@mercury-fw/cli-engine": "0.26.0",
36
- "@mercury-fw/confirm-engine": "0.26.0",
37
- "@mercury-fw/plugin-types": "0.26.0",
34
+ "@mercury-fw/channel-types": "0.27.1",
35
+ "@mercury-fw/cli-engine": "0.27.1",
36
+ "@mercury-fw/confirm-engine": "0.27.1",
37
+ "@mercury-fw/plugin-types": "0.27.1",
38
38
  "@qdrant/js-client-rest": "^1.19.0",
39
39
  "ai": "^7.0.77",
40
40
  "ai-sdk-ollama": "^4.2.0",
@@ -80,7 +80,10 @@ export function createTerminalProvider(deps: TerminalProviderDeps): Provider {
80
80
  // seeing its result), each burst getting its own header/close
81
81
  // rather than being mistaken for a continuation of the first.
82
82
  const reasoningIdsStarted = new Set<string>();
83
- const dim = (label: string) => onChunk(`\x1b[2m\x1b[3m${label}\x1b[0m\n`);
83
+ // Everything but the answer text is an aside: printed as it comes,
84
+ // but not part of the answer the REPL compares with the result.
85
+ const aside = (chunk: string) => onChunk(chunk, { aside: true });
86
+ const dim = (label: string) => aside(`\x1b[2m\x1b[3m${label}\x1b[0m\n`);
84
87
  const sink: TurnSink = {
85
88
  onToolStart: dim,
86
89
  // PENDING_CONFIRMATION_NOTE is dropped here: onStep (below)
@@ -99,10 +102,10 @@ export function createTerminalProvider(deps: TerminalProviderDeps): Provider {
99
102
  reasoningIdsStarted.add(id);
100
103
  dim("Sto pensando…");
101
104
  }
102
- onChunk(`\x1b[2m\x1b[3m${chunk}\x1b[0m`);
105
+ aside(`\x1b[2m\x1b[3m${chunk}\x1b[0m`);
103
106
  },
104
107
  onReasoningEnd: (id) => {
105
- if (reasoningIdsStarted.has(id)) onChunk("\n");
108
+ if (reasoningIdsStarted.has(id)) aside("\n");
106
109
  },
107
110
  onStep: (step) => {
108
111
  lastSteps.push(step);
@@ -111,7 +114,7 @@ export function createTerminalProvider(deps: TerminalProviderDeps): Provider {
111
114
  // say it itself, from the structured step data.
112
115
  const pending = detectPendingConfirmation(step);
113
116
  if (pending) {
114
- onChunk(`Azione in sospeso: \`${pending.summary}\` — scrivi: ${pending.token}\n`);
117
+ aside(`Azione in sospeso: \`${pending.summary}\` — scrivi: ${pending.token}\n`);
115
118
  }
116
119
  },
117
120
  onUsage: (tokens) => {
@@ -140,7 +143,11 @@ export function createTerminalProvider(deps: TerminalProviderDeps): Provider {
140
143
  if (contextLength === null) {
141
144
  contextLength = await getLoadedContextLengthFn(deps.ollamaHost, deps.ollamaModel);
142
145
  }
143
- return finalText;
146
+ // The note's chunk was dropped above, so it isn't returned either,
147
+ // also when a surfaced display follows it.
148
+ return finalText.startsWith(PENDING_CONFIRMATION_NOTE)
149
+ ? finalText.slice(PENDING_CONFIRMATION_NOTE.length).replace(/^\n\n/, "")
150
+ : finalText;
144
151
  },
145
152
  undefined,
146
153
  { promptSuffix: () => formatContextUsage(lastInputTokens, contextLength) },
@@ -53,7 +53,10 @@ export const PROMPT = "> ";
53
53
  * resolve. If `onChunk` is never called, the function's returned
54
54
  * string is written instead once `handleInput` resolves, exactly as
55
55
  * before streaming existed — this is what keeps the `/dump` command
56
- * and any other non-streaming reply working unchanged.
56
+ * and any other non-streaming reply working unchanged. A chunk passed
57
+ * with `{ aside: true }` (reasoning, a tool label, a confirmation line)
58
+ * is written the same way but isn't part of the answer, so it's left out
59
+ * when the returned string is compared with what was streamed.
57
60
  * @param io - Test seam. Defaults to real stdin/stdout when omitted.
58
61
  * @param opts.promptSuffix - Optional, called fresh right before every
59
62
  * prompt (including the very first one) and written ahead of it — e.g.
@@ -62,7 +65,7 @@ export const PROMPT = "> ";
62
65
  * turn to turn.
63
66
  */
64
67
  export async function startTerminalRepl(
65
- handleInput: (input: string, onChunk: (chunk: string) => void) => Promise<string>,
68
+ handleInput: (input: string, onChunk: (chunk: string, opts?: { aside?: boolean }) => void) => Promise<string>,
66
69
  io?: {
67
70
  input?: AsyncIterable<string>;
68
71
  output?: { write(s: string, opts?: { newline?: boolean }): void };
@@ -115,9 +118,9 @@ export async function startTerminalRepl(
115
118
  for await (const line of input) {
116
119
  let streamed = false;
117
120
  let streamedText = "";
118
- const onChunk = (chunk: string) => {
121
+ const onChunk = (chunk: string, chunkOpts?: { aside?: boolean }) => {
119
122
  streamed = true;
120
- streamedText += chunk;
123
+ if (!chunkOpts?.aside) streamedText += chunk;
121
124
  output.write(chunk, { newline: false });
122
125
  };
123
126
  try {
@@ -132,9 +135,9 @@ export async function startTerminalRepl(
132
135
  // needs writing.
133
136
  output.write(result.slice(streamedText.length));
134
137
  } else {
135
- // The final result no longer extends what was already streamed —
136
- // e.g. turn-runner.ts's issue-list correction replaced the text
137
- // with a rewrite or the fixed fallback. What's already printed
138
+ // The final result no longer extends what was already streamed:
139
+ // a plugin's post-turn guard (turn-runner.ts) rewrote the text
140
+ // after it was streamed. What's already printed
138
141
  // can't be un-printed, so show the real final answer in full
139
142
  // instead of a slice computed against text that isn't there
140
143
  // anymore.