@dungle-scrubs/tallow 0.9.3 → 0.9.6
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/dist/cli.js +7 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +24 -10
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +285 -148
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/interactive-reset.d.ts +49 -0
- package/dist/interactive-reset.d.ts.map +1 -0
- package/dist/interactive-reset.js +40 -0
- package/dist/interactive-reset.js.map +1 -0
- package/dist/pi-tui-editor-patch.d.ts +10 -0
- package/dist/pi-tui-editor-patch.d.ts.map +1 -0
- package/dist/pi-tui-editor-patch.js +159 -0
- package/dist/pi-tui-editor-patch.js.map +1 -0
- package/dist/pi-tui-patch.d.ts +2 -0
- package/dist/pi-tui-patch.d.ts.map +1 -0
- package/dist/pi-tui-patch.js +563 -0
- package/dist/pi-tui-patch.js.map +1 -0
- package/dist/pi-tui-settings-list-patch.d.ts +11 -0
- package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
- package/dist/pi-tui-settings-list-patch.js +38 -0
- package/dist/pi-tui-settings-list-patch.js.map +1 -0
- package/dist/reset-diagnostics.d.ts +69 -0
- package/dist/reset-diagnostics.d.ts.map +1 -0
- package/dist/reset-diagnostics.js +41 -0
- package/dist/reset-diagnostics.js.map +1 -0
- package/dist/sdk.d.ts +5 -21
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +180 -149
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-interactive.d.ts +1 -0
- package/dist/workspace-transition-interactive.d.ts.map +1 -1
- package/dist/workspace-transition-interactive.js +7 -17
- package/dist/workspace-transition-interactive.js.map +1 -1
- package/extensions/__integration__/audit-findings.test.ts +6 -16
- package/extensions/__integration__/teams-runtime.test.ts +4 -1
- package/extensions/_icons/index.ts +2 -4
- package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
- package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
- package/extensions/_shared/image-metadata.ts +99 -0
- package/extensions/_shared/inline-preview.ts +1 -1
- package/extensions/_shared/pid-registry.ts +0 -1
- package/extensions/_shared/terminal-links.ts +22 -0
- package/extensions/ask-user-question-tool/index.ts +0 -3
- package/extensions/clear/__tests__/clear.test.ts +270 -3
- package/extensions/command-expansion/index.ts +1 -1
- package/extensions/context-files/index.ts +5 -1
- package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
- package/extensions/context-fork/extension.json +1 -1
- package/extensions/context-fork/index.ts +32 -0
- package/extensions/edit-tool-enhanced/index.ts +2 -1
- package/extensions/hooks/index.ts +33 -11
- package/extensions/loop/index.ts +14 -1
- package/extensions/lsp/index.ts +64 -13
- package/extensions/lsp/package.json +2 -2
- package/extensions/permissions/__tests__/permissions.test.ts +4 -4
- package/extensions/random-spinner/index.ts +7 -642
- package/extensions/read-tool-enhanced/index.ts +6 -8
- package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +4 -5
- package/extensions/render-stabilizer/index.ts +6 -6
- package/extensions/show-system-prompt/__tests__/show-system-prompt.test.ts +1 -1
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
- package/extensions/slash-command-bridge/index.ts +14 -2
- package/extensions/subagent-tool/index.ts +1 -1
- package/extensions/subagent-tool/model-resolver.ts +274 -7
- package/extensions/tasks/__tests__/state-ui.test.ts +3 -3
- package/extensions/tasks/__tests__/widget-subagents.test.ts +2 -2
- package/extensions/tasks/commands/register-tasks-extension.ts +10 -10
- package/extensions/tasks/state/index.ts +1 -1
- package/extensions/tasks/ui/index.ts +2 -7
- package/extensions/teams-tool/tools/register-extension.ts +1 -3
- package/extensions/web-search-tool/index.ts +2 -1
- package/extensions/write-tool-enhanced/__tests__/write-tool-enhanced.test.ts +21 -6
- package/extensions/write-tool-enhanced/index.ts +2 -1
- package/node_modules/@mariozechner/pi-tui/README.md +56 -34
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +0 -2
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +12 -23
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
- package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
- package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
- package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
- package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/package.json +6 -6
- package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
- package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
- package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
- package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
- package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
- package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
- package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
- package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
- package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
- package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
- package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
- package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +11 -23
- package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
- package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
- package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
- package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
- package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
- package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
- package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
- package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
- package/package.json +13 -13
- package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
- package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
- package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
- package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
- package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
- package/packages/tallow-tui/node_modules/marked/README.md +5 -4
- package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
- package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
- package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
- package/packages/tallow-tui/node_modules/marked/package.json +26 -34
- package/runtime/model-metadata-overrides.ts +10 -1
- package/runtime/pid-schema.ts +26 -6
- package/skills/tallow-expert/SKILL.md +1 -3
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
- package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
- package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -49
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
- package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
- package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
- package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
- package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
|
@@ -43,15 +43,15 @@ function captureTuiRef(ui: {
|
|
|
43
43
|
/**
|
|
44
44
|
* Register render stabilization hooks.
|
|
45
45
|
*
|
|
46
|
+
* The shared reset helper now owns reset-grace handling for the main
|
|
47
|
+
* interactive reset paths. This extension remains as a compatibility shim
|
|
48
|
+
* for any remaining session-switch surfaces that still depend on an
|
|
49
|
+
* extension-side hook.
|
|
50
|
+
*
|
|
46
51
|
* @param pi - Extension API
|
|
47
52
|
*/
|
|
48
53
|
export default function renderStabilizerExtension(pi: ExtensionAPI): void {
|
|
49
|
-
//
|
|
50
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
51
|
-
captureTuiRef(ctx.ui);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Reset the render grace period before a session switch so the
|
|
54
|
+
// Reset the render grace period before a legacy session switch so the
|
|
55
55
|
// chatContainer.clear() → renderInitialMessages() transition uses
|
|
56
56
|
// gentle line-by-line redraws instead of screen-clearing redraws.
|
|
57
57
|
pi.on("session_before_switch", async (_event, ctx) => {
|
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
ExtensionUIContext,
|
|
14
14
|
TurnEndEvent,
|
|
15
15
|
} from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import {
|
|
17
|
+
getResetDiagnosticsForTests,
|
|
18
|
+
resetResetDiagnosticsForTests,
|
|
19
|
+
} from "../../../src/reset-diagnostics.js";
|
|
16
20
|
import { ExtensionHarness } from "../../../test-utils/extension-harness.js";
|
|
17
21
|
import { ManualTimerScheduler } from "../../../test-utils/manual-timer-scheduler.js";
|
|
18
22
|
import slashCommandBridge, {
|
|
@@ -40,6 +44,7 @@ beforeEach(async () => {
|
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
afterEach(() => {
|
|
47
|
+
resetResetDiagnosticsForTests();
|
|
43
48
|
resetSlashCommandBridgeStateForTests();
|
|
44
49
|
});
|
|
45
50
|
|
|
@@ -314,6 +319,11 @@ describe("compact", () => {
|
|
|
314
319
|
const continuation = harness.sentMessages.find(
|
|
315
320
|
(message) => message.customType === "compact-continue"
|
|
316
321
|
);
|
|
322
|
+
expect(
|
|
323
|
+
getResetDiagnosticsForTests().some(
|
|
324
|
+
(event) => event.kind === "deferred_registered" && event.source === "slash-command-bridge"
|
|
325
|
+
)
|
|
326
|
+
).toBe(true);
|
|
317
327
|
expect(continuation?.display).toBe(false);
|
|
318
328
|
expect(continuation?.options?.triggerTurn).toBe(true);
|
|
319
329
|
expect(continuation?.content).toContain("compaction is complete");
|
|
@@ -344,6 +354,14 @@ describe("compact", () => {
|
|
|
344
354
|
await harness.fireEvent("turn_start", { type: "turn_start", turnIndex: 0, timestamp: 0 }, ctx);
|
|
345
355
|
scheduler.advanceBy(200);
|
|
346
356
|
|
|
357
|
+
expect(
|
|
358
|
+
getResetDiagnosticsForTests().some(
|
|
359
|
+
(event) =>
|
|
360
|
+
event.kind === "deferred_cancelled" &&
|
|
361
|
+
event.source === "slash-command-bridge" &&
|
|
362
|
+
event.reason === "turn_start"
|
|
363
|
+
)
|
|
364
|
+
).toBe(true);
|
|
347
365
|
expect(harness.sentMessages).toHaveLength(0);
|
|
348
366
|
expect(widgetUpdates.at(-1)).toEqual({ key: "compact-progress", content: undefined });
|
|
349
367
|
});
|
|
@@ -374,6 +392,14 @@ describe("compact", () => {
|
|
|
374
392
|
compactOptions?.onComplete?.();
|
|
375
393
|
scheduler.advanceBy(200);
|
|
376
394
|
|
|
395
|
+
expect(
|
|
396
|
+
getResetDiagnosticsForTests().some(
|
|
397
|
+
(event) =>
|
|
398
|
+
event.kind === "deferred_dropped" &&
|
|
399
|
+
event.source === "slash-command-bridge" &&
|
|
400
|
+
event.reason === "session_not_idle"
|
|
401
|
+
)
|
|
402
|
+
).toBe(true);
|
|
377
403
|
expect(harness.sentMessages).toHaveLength(0);
|
|
378
404
|
expect(widgetUpdates.at(-1)).toEqual({ key: "compact-progress", content: undefined });
|
|
379
405
|
expect(workingMessages.at(-1)).toBeUndefined();
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
} from "@mariozechner/pi-coding-agent";
|
|
18
18
|
import { Text } from "@mariozechner/pi-tui";
|
|
19
19
|
import { Type } from "@sinclair/typebox";
|
|
20
|
+
import { recordResetDiagnostic } from "../../src/reset-diagnostics.js";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Deferred compact request — set by the tool handler, consumed on the first
|
|
@@ -154,13 +155,18 @@ function stopCompactProgress(ctx?: ExtensionContext): void {
|
|
|
154
155
|
*
|
|
155
156
|
* @returns Nothing
|
|
156
157
|
*/
|
|
157
|
-
function clearContinuationTimer(): void {
|
|
158
|
+
function clearContinuationTimer(reason?: string): void {
|
|
158
159
|
if (!continuationTimer) {
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
timerScheduler.clearTimeout(continuationTimer);
|
|
163
164
|
continuationTimer = null;
|
|
165
|
+
recordResetDiagnostic({
|
|
166
|
+
kind: "deferred_cancelled",
|
|
167
|
+
reason: reason ?? "clear_continuation_timer",
|
|
168
|
+
source: "slash-command-bridge",
|
|
169
|
+
});
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
/**
|
|
@@ -338,6 +344,7 @@ function startDeferredCompact(
|
|
|
338
344
|
// 200ms gives session.prompt()'s async setup (API key resolution,
|
|
339
345
|
// compaction check) time to settle. The turn_start listener cancels
|
|
340
346
|
// this timer if a turn starts before it fires (defense-in-depth).
|
|
347
|
+
recordResetDiagnostic({ kind: "deferred_registered", source: "slash-command-bridge" });
|
|
341
348
|
continuationTimer = timerScheduler.setTimeout(() => {
|
|
342
349
|
continuationTimer = null;
|
|
343
350
|
if (ctx.isIdle()) {
|
|
@@ -354,6 +361,11 @@ function startDeferredCompact(
|
|
|
354
361
|
} else {
|
|
355
362
|
// User sent a message during compaction — their turn is
|
|
356
363
|
// handling things, clean up our indicators.
|
|
364
|
+
recordResetDiagnostic({
|
|
365
|
+
kind: "deferred_dropped",
|
|
366
|
+
reason: "session_not_idle",
|
|
367
|
+
source: "slash-command-bridge",
|
|
368
|
+
});
|
|
357
369
|
resumingAfterCompact = false;
|
|
358
370
|
ctx.ui?.setWidget?.("compact-progress", undefined);
|
|
359
371
|
ctx.ui?.setWorkingMessage?.();
|
|
@@ -601,7 +613,7 @@ WHEN NOT TO USE:
|
|
|
601
613
|
* now active and showing the pending working message ("Resuming task…").
|
|
602
614
|
*/
|
|
603
615
|
pi.on("turn_start", (_event, ctx) => {
|
|
604
|
-
clearContinuationTimer();
|
|
616
|
+
clearContinuationTimer("turn_start");
|
|
605
617
|
if (!resumingAfterCompact) return;
|
|
606
618
|
resumingAfterCompact = false;
|
|
607
619
|
ctx.ui?.setWidget?.("compact-progress", undefined);
|
|
@@ -1412,7 +1412,7 @@ function renderSubagentCall(args: Record<string, unknown>, theme: Theme) {
|
|
|
1412
1412
|
return new Text(lines.join("\n"), 0, 0);
|
|
1413
1413
|
}
|
|
1414
1414
|
|
|
1415
|
-
const
|
|
1415
|
+
const _agentName = (args.agent as string) || "...";
|
|
1416
1416
|
const task = typeof args.task === "string" ? args.task : "...";
|
|
1417
1417
|
// Single mode: skip the redundant "subagent single" header — the result
|
|
1418
1418
|
// renderer already shows "subagent running <duration> <agent>" with a spinner.
|
|
@@ -1,12 +1,279 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Deterministic fuzzy model resolution for subagents.
|
|
3
|
+
*
|
|
4
|
+
* We keep a local implementation instead of delegating straight to synapse
|
|
5
|
+
* because the direct dependency path was flaky under Linux CI in this repo.
|
|
6
|
+
* The behavior mirrors synapse's public resolver cascade closely enough for
|
|
7
|
+
* runtime callers and keeps tests deterministic across platforms.
|
|
3
8
|
*
|
|
4
9
|
* @module
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
import type { CandidateModel, ModelSource, ResolvedModel } from "@dungle-scrubs/synapse";
|
|
13
|
+
import { getModels, getProviders } from "@mariozechner/pi-ai";
|
|
14
|
+
|
|
15
|
+
export type { ModelSource, ResolvedModel };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Collect all models from the registered pi-ai providers.
|
|
19
|
+
*
|
|
20
|
+
* @returns Flat list of candidate models
|
|
21
|
+
*/
|
|
22
|
+
function getAllModels(): CandidateModel[] {
|
|
23
|
+
const result: CandidateModel[] = [];
|
|
24
|
+
for (const provider of getProviders()) {
|
|
25
|
+
for (const model of getModels(provider)) {
|
|
26
|
+
result.push({ provider: model.provider, id: model.id, name: model.name });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Convert a candidate model into the resolved public shape.
|
|
34
|
+
*
|
|
35
|
+
* @param model - Candidate model
|
|
36
|
+
* @returns Resolved model descriptor
|
|
37
|
+
*/
|
|
38
|
+
function toResolved(model: CandidateModel): ResolvedModel {
|
|
39
|
+
return {
|
|
40
|
+
displayName: `${model.provider}/${model.id}`,
|
|
41
|
+
id: model.id,
|
|
42
|
+
provider: model.provider,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Tokenize a query or model identifier for overlap matching.
|
|
48
|
+
*
|
|
49
|
+
* @param value - Raw query or identifier
|
|
50
|
+
* @returns Lowercase tokens split on separators and digit boundaries
|
|
51
|
+
*/
|
|
52
|
+
function tokenize(value: string): string[] {
|
|
53
|
+
return value
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/([a-z])(\d)/g, "$1 $2")
|
|
56
|
+
.replace(/(\d)([a-z])/g, "$1 $2")
|
|
57
|
+
.split(/[\s\-_.]+/)
|
|
58
|
+
.filter((token) => token.length > 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Normalize a query or identifier by removing separators.
|
|
63
|
+
*
|
|
64
|
+
* @param value - Raw query or identifier
|
|
65
|
+
* @returns Lowercase normalized string
|
|
66
|
+
*/
|
|
67
|
+
function normalize(value: string): string {
|
|
68
|
+
return value.toLowerCase().replace(/[\s\-_.]+/g, "");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compare model IDs using numeric-aware sorting.
|
|
73
|
+
*
|
|
74
|
+
* @param a - First model ID
|
|
75
|
+
* @param b - Second model ID
|
|
76
|
+
* @returns Positive when a is newer/higher than b
|
|
77
|
+
*/
|
|
78
|
+
function compareModelIds(a: string, b: string): number {
|
|
79
|
+
return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build a provider-priority lookup map.
|
|
84
|
+
*
|
|
85
|
+
* @param preferredProviders - Ordered provider preference list
|
|
86
|
+
* @returns Provider -> priority map (lower is better)
|
|
87
|
+
*/
|
|
88
|
+
function buildProviderPreferenceMap(
|
|
89
|
+
preferredProviders: readonly string[] | undefined
|
|
90
|
+
): ReadonlyMap<string, number> {
|
|
91
|
+
const priorities = new Map<string, number>();
|
|
92
|
+
for (const [index, provider] of (preferredProviders ?? []).entries()) {
|
|
93
|
+
priorities.set(provider.toLowerCase(), index);
|
|
94
|
+
}
|
|
95
|
+
return priorities;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolve one provider's priority, defaulting unknown providers last.
|
|
100
|
+
*
|
|
101
|
+
* @param preferenceMap - Provider priority map
|
|
102
|
+
* @param provider - Provider to score
|
|
103
|
+
* @returns Numeric priority (lower is better)
|
|
104
|
+
*/
|
|
105
|
+
function getProviderPriority(preferenceMap: ReadonlyMap<string, number>, provider: string): number {
|
|
106
|
+
return preferenceMap.get(provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Pick the best candidate by ID quality only.
|
|
111
|
+
*
|
|
112
|
+
* Tiebreak order:
|
|
113
|
+
* 1. Higher numeric-aware model ID
|
|
114
|
+
* 2. Shorter ID when numeric ordering ties
|
|
115
|
+
* 3. Lexicographically last as deterministic fallback
|
|
116
|
+
*
|
|
117
|
+
* @param models - Candidate models
|
|
118
|
+
* @returns Best candidate
|
|
119
|
+
*/
|
|
120
|
+
function pickBestModel(models: readonly CandidateModel[]): CandidateModel {
|
|
121
|
+
return models.reduce((best, current) => {
|
|
122
|
+
const versionDiff = compareModelIds(best.id, current.id);
|
|
123
|
+
if (versionDiff !== 0) {
|
|
124
|
+
return versionDiff > 0 ? best : current;
|
|
125
|
+
}
|
|
126
|
+
if (best.id.length !== current.id.length) {
|
|
127
|
+
return best.id.length < current.id.length ? best : current;
|
|
128
|
+
}
|
|
129
|
+
return best.id >= current.id ? best : current;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Pick the best final candidate, optionally preferring specific providers.
|
|
135
|
+
*
|
|
136
|
+
* @param models - Candidate models
|
|
137
|
+
* @param preferredProviders - Ordered provider preference list
|
|
138
|
+
* @returns Best candidate
|
|
139
|
+
*/
|
|
140
|
+
function pickBest(
|
|
141
|
+
models: readonly CandidateModel[],
|
|
142
|
+
preferredProviders?: readonly string[]
|
|
143
|
+
): CandidateModel {
|
|
144
|
+
const bestModel = pickBestModel(models);
|
|
145
|
+
if (!preferredProviders || preferredProviders.length === 0) {
|
|
146
|
+
return bestModel;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sameModel = models.filter((model) => model.id === bestModel.id);
|
|
150
|
+
if (sameModel.length <= 1) {
|
|
151
|
+
return bestModel;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const preferenceMap = buildProviderPreferenceMap(preferredProviders);
|
|
155
|
+
return sameModel.reduce((best, current) => {
|
|
156
|
+
return getProviderPriority(preferenceMap, best.provider) <=
|
|
157
|
+
getProviderPriority(preferenceMap, current.provider)
|
|
158
|
+
? best
|
|
159
|
+
: current;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Find all candidates that tie at the best score for the first matching tier.
|
|
165
|
+
*
|
|
166
|
+
* Resolution cascade:
|
|
167
|
+
* 1. Exact ID match
|
|
168
|
+
* 2. Case-insensitive ID match
|
|
169
|
+
* 3. Normalized ID match (strips separators)
|
|
170
|
+
* 4. Provider/ID exact match
|
|
171
|
+
* 5. Token overlap scoring
|
|
172
|
+
* 6. Raw substring match
|
|
173
|
+
* 7. Normalized substring match
|
|
174
|
+
*
|
|
175
|
+
* @param query - Human-friendly query
|
|
176
|
+
* @param modelSource - Optional injected model source
|
|
177
|
+
* @returns Tied candidates from the first matching tier
|
|
178
|
+
*/
|
|
179
|
+
function findCandidates(query: string, modelSource?: ModelSource): CandidateModel[] {
|
|
180
|
+
const models = modelSource ? modelSource() : getAllModels();
|
|
181
|
+
if (models.length === 0) return [];
|
|
182
|
+
|
|
183
|
+
const trimmed = query.trim();
|
|
184
|
+
if (trimmed.length === 0) return [];
|
|
185
|
+
|
|
186
|
+
const lower = trimmed.toLowerCase();
|
|
187
|
+
const normalized = normalize(trimmed);
|
|
188
|
+
|
|
189
|
+
const exact = models.filter((model) => model.id === trimmed);
|
|
190
|
+
if (exact.length > 0) return exact;
|
|
191
|
+
|
|
192
|
+
const caseInsensitive = models.filter((model) => model.id.toLowerCase() === lower);
|
|
193
|
+
if (caseInsensitive.length > 0) return caseInsensitive;
|
|
194
|
+
|
|
195
|
+
const normalizedId = models.filter((model) => normalize(model.id) === normalized);
|
|
196
|
+
if (normalizedId.length > 0) return normalizedId;
|
|
197
|
+
|
|
198
|
+
if (trimmed.includes("/")) {
|
|
199
|
+
const slashIndex = trimmed.indexOf("/");
|
|
200
|
+
const provider = trimmed.slice(0, slashIndex).toLowerCase();
|
|
201
|
+
const id = trimmed.slice(slashIndex + 1).toLowerCase();
|
|
202
|
+
const providerMatch = models.filter(
|
|
203
|
+
(model) => model.provider.toLowerCase() === provider && model.id.toLowerCase() === id
|
|
204
|
+
);
|
|
205
|
+
if (providerMatch.length > 0) return providerMatch;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const queryTokens = tokenize(trimmed);
|
|
209
|
+
if (queryTokens.length > 0) {
|
|
210
|
+
let bestScore = 0;
|
|
211
|
+
let bestMatches: CandidateModel[] = [];
|
|
212
|
+
for (const model of models) {
|
|
213
|
+
const idName = `${model.id} ${model.name}`.toLowerCase();
|
|
214
|
+
const providerText = model.provider.toLowerCase();
|
|
215
|
+
let score = 0;
|
|
216
|
+
for (const token of queryTokens) {
|
|
217
|
+
if (idName.includes(token)) score += 2;
|
|
218
|
+
else if (providerText.includes(token)) score += 1;
|
|
219
|
+
}
|
|
220
|
+
if (score > bestScore) {
|
|
221
|
+
bestScore = score;
|
|
222
|
+
bestMatches = [model];
|
|
223
|
+
} else if (score === bestScore && score > 0) {
|
|
224
|
+
bestMatches.push(model);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (bestMatches.length > 0) return bestMatches;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const substring = models.filter(
|
|
231
|
+
(model) => model.id.toLowerCase().includes(lower) || model.name.toLowerCase().includes(lower)
|
|
232
|
+
);
|
|
233
|
+
if (substring.length > 0) return substring;
|
|
234
|
+
|
|
235
|
+
return models.filter(
|
|
236
|
+
(model) =>
|
|
237
|
+
normalize(model.id).includes(normalized) || normalize(model.name).includes(normalized)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Resolve a fuzzy model query to one best match.
|
|
243
|
+
*
|
|
244
|
+
* @param query - Human-friendly model query
|
|
245
|
+
* @param modelSource - Optional injected model source for deterministic tests
|
|
246
|
+
* @param preferredProviders - Optional provider preference ordering
|
|
247
|
+
* @returns Best matching model or undefined
|
|
248
|
+
*/
|
|
249
|
+
export function resolveModelFuzzy(
|
|
250
|
+
query: string,
|
|
251
|
+
modelSource?: ModelSource,
|
|
252
|
+
preferredProviders?: string[]
|
|
253
|
+
): ResolvedModel | undefined {
|
|
254
|
+
const candidates = findCandidates(query, modelSource);
|
|
255
|
+
if (candidates.length === 0) return undefined;
|
|
256
|
+
return toResolved(pickBest(candidates, preferredProviders));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Resolve a fuzzy model query to all tied candidates.
|
|
261
|
+
*
|
|
262
|
+
* @param query - Human-friendly model query
|
|
263
|
+
* @param modelSource - Optional injected model source for deterministic tests
|
|
264
|
+
* @returns Candidate matches
|
|
265
|
+
*/
|
|
266
|
+
export function resolveModelCandidates(query: string, modelSource?: ModelSource): ResolvedModel[] {
|
|
267
|
+
return findCandidates(query, modelSource).map(toResolved);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* List available provider/model identifiers.
|
|
272
|
+
*
|
|
273
|
+
* @param modelSource - Optional injected model source for deterministic tests
|
|
274
|
+
* @returns Provider/model identifier strings
|
|
275
|
+
*/
|
|
276
|
+
export function listAvailableModels(modelSource?: ModelSource): string[] {
|
|
277
|
+
const models = modelSource ? modelSource() : getAllModels();
|
|
278
|
+
return models.map((model) => `${model.provider}/${model.id}`);
|
|
279
|
+
}
|
|
@@ -114,10 +114,10 @@ describe("tasks ui helpers", () => {
|
|
|
114
114
|
expect(visibleWidth(truncated)).toBe(3);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
it("mergeSideBySide
|
|
117
|
+
it("mergeSideBySide top-aligns right column and keeps width bounds", () => {
|
|
118
118
|
const merged = mergeSideBySide(["left1", "left2", "left3"], ["right"], 6, " | ", 16);
|
|
119
119
|
expect(merged).toHaveLength(3);
|
|
120
|
-
expect(merged[0]).
|
|
121
|
-
expect(merged[2]).
|
|
120
|
+
expect(merged[0]).toContain("right");
|
|
121
|
+
expect(merged[2]).toBe("left3 | ");
|
|
122
122
|
});
|
|
123
123
|
});
|
|
@@ -406,7 +406,7 @@ describe("tasks widget subagent sections", () => {
|
|
|
406
406
|
expectLinesWithinWidth(stackedNarrow, 55);
|
|
407
407
|
expectLinesWithinWidth(stackedWide, 95);
|
|
408
408
|
|
|
409
|
-
const sideBySideNarrow = renderWidget(fixture.captured,
|
|
409
|
+
const sideBySideNarrow = renderWidget(fixture.captured, 144);
|
|
410
410
|
const sideBySideWide = renderWidget(fixture.captured, 180);
|
|
411
411
|
const sideBySideNarrowPreview = extractPreview(sideBySideNarrow, "seg01");
|
|
412
412
|
const sideBySideWidePreview = extractPreview(sideBySideWide, "seg01");
|
|
@@ -417,7 +417,7 @@ describe("tasks widget subagent sections", () => {
|
|
|
417
417
|
expect(visibleWidth(sideBySideWidePreview)).toBeGreaterThan(
|
|
418
418
|
visibleWidth(sideBySideNarrowPreview)
|
|
419
419
|
);
|
|
420
|
-
expectLinesWithinWidth(sideBySideNarrow,
|
|
420
|
+
expectLinesWithinWidth(sideBySideNarrow, 144);
|
|
421
421
|
expectLinesWithinWidth(sideBySideWide, 180);
|
|
422
422
|
} finally {
|
|
423
423
|
await shutdownFixture(fixture);
|
|
@@ -685,7 +685,7 @@ export function registerTasksExtension(
|
|
|
685
685
|
const useSideBySide = width >= MIN_SIDE_BY_SIDE_WIDTH && hasTasks && hasRightColumn;
|
|
686
686
|
|
|
687
687
|
if (useSideBySide) {
|
|
688
|
-
// Side-by-side: tasks on left, subagents + bg tasks on right
|
|
688
|
+
// Side-by-side: tasks on left, subagents + bg tasks on right
|
|
689
689
|
const separator = "\x1b[38;2;60;60;70m │ \x1b[0m"; // Dark gray
|
|
690
690
|
const separatorWidth = 5; // " │ " is 5 visible chars
|
|
691
691
|
const leftColumnWidth = Math.floor((width - separatorWidth) / 2);
|
|
@@ -2168,15 +2168,15 @@ Before calling manage_tasks complete/update, call manage_tasks list first so ind
|
|
|
2168
2168
|
updateAgentBar(ctx);
|
|
2169
2169
|
}
|
|
2170
2170
|
|
|
2171
|
-
pi.on("session_start", async (_event, ctx) =>
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2171
|
+
pi.on("session_start", async (_event, ctx) => restoreSessionState(ctx));
|
|
2172
|
+
(
|
|
2173
|
+
pi as unknown as {
|
|
2174
|
+
on: (
|
|
2175
|
+
event: string,
|
|
2176
|
+
handler: (event: unknown, ctx: ExtensionContext) => Promise<void>
|
|
2177
|
+
) => void;
|
|
2178
|
+
}
|
|
2179
|
+
).on("session_switch", async (_event, ctx) => restoreSessionState(ctx));
|
|
2180
2180
|
pi.on("session_shutdown", async () => {
|
|
2181
2181
|
emitInteropEvent(pi.events, INTEROP_EVENT_NAMES.backgroundTasksPresenterState, {
|
|
2182
2182
|
active: false,
|
|
@@ -38,7 +38,7 @@ export const LEGACY_TEAMS_DIR = getTallowPath("teams");
|
|
|
38
38
|
export const TEAM_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
39
39
|
|
|
40
40
|
/** Minimum width for side-by-side layout (tasks left, subagents right). */
|
|
41
|
-
export const MIN_SIDE_BY_SIDE_WIDTH =
|
|
41
|
+
export const MIN_SIDE_BY_SIDE_WIDTH = 140;
|
|
42
42
|
|
|
43
43
|
/** Prefix used for session-scoped shared task-group directories. */
|
|
44
44
|
const SESSION_TASK_GROUP_PREFIX = "task-group";
|
|
@@ -45,7 +45,7 @@ export function padToWidth(line: string, targetWidth: number): string {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Merge two column arrays into side-by-side lines
|
|
48
|
+
* Merge two column arrays into side-by-side lines with top-aligned rows.
|
|
49
49
|
*
|
|
50
50
|
* Both columns are truncated to their allotted widths to prevent overflow.
|
|
51
51
|
*
|
|
@@ -68,14 +68,9 @@ export function mergeSideBySide(
|
|
|
68
68
|
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
69
69
|
const result: string[] = [];
|
|
70
70
|
|
|
71
|
-
// Bottom-align: pad right column at the top
|
|
72
|
-
const rightPadding = maxRows - rightLines.length;
|
|
73
|
-
|
|
74
71
|
for (let i = 0; i < maxRows; i++) {
|
|
75
72
|
const left = leftLines[i] ?? "";
|
|
76
|
-
const
|
|
77
|
-
const rawRight = rightIndex >= 0 ? (rightLines[rightIndex] ?? "") : "";
|
|
78
|
-
// Truncate right column to prevent overflow
|
|
73
|
+
const rawRight = rightLines[i] ?? "";
|
|
79
74
|
const right =
|
|
80
75
|
rightWidth > 0 && visibleWidth(rawRight) > rightWidth
|
|
81
76
|
? truncateToWidth(rawRight, rightWidth, "")
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
23
|
-
import { Key,
|
|
23
|
+
import { Key, Text, type TUI } from "@mariozechner/pi-tui";
|
|
24
24
|
import { Type } from "@sinclair/typebox";
|
|
25
25
|
import { INTEROP_EVENT_NAMES, onInteropEvent } from "../../_shared/interop-events.js";
|
|
26
26
|
import {
|
|
@@ -248,7 +248,6 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
248
248
|
dashboardEnabled = true;
|
|
249
249
|
setDashboardFlag(true);
|
|
250
250
|
startDashboardTicker();
|
|
251
|
-
ctx.ui.setWorkingMessage(Loader.HIDE);
|
|
252
251
|
ctx.ui.setStatus("team-dashboard", "Team dashboard active");
|
|
253
252
|
ctx.ui.setEditorComponent((tui, editorTheme, keybindings) => {
|
|
254
253
|
enterDashboardViewport(tui);
|
|
@@ -322,7 +321,6 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
322
321
|
|
|
323
322
|
pi.on("turn_start", async (_event, ctx) => {
|
|
324
323
|
if (!dashboardEnabled || !ctx.hasUI) return;
|
|
325
|
-
ctx.ui.setWorkingMessage(Loader.HIDE);
|
|
326
324
|
});
|
|
327
325
|
|
|
328
326
|
// Archive all teams on session shutdown (preserves tasks for future recovery)
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { ExtensionAPI, ThemeColor } from "@mariozechner/pi-coding-agent";
|
|
15
|
-
import {
|
|
15
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
16
16
|
import { Type } from "@sinclair/typebox";
|
|
17
17
|
import { getIcon } from "../_icons/index.js";
|
|
18
|
+
import { hyperlink } from "../_shared/terminal-links.js";
|
|
18
19
|
import { formatToolVerb, renderLines } from "../tool-display/index.js";
|
|
19
20
|
import { BraveSearchProvider } from "./providers/brave.js";
|
|
20
21
|
import { SearchError, type SearchProvider, type SearchResult } from "./providers/interface.js";
|
|
@@ -68,6 +68,21 @@ function stubContext(): ExtensionContext {
|
|
|
68
68
|
} as unknown as ExtensionContext;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the registered write tool from a loaded harness.
|
|
73
|
+
*
|
|
74
|
+
* @param harness - Extension harness with write-tool-enhanced loaded
|
|
75
|
+
* @returns Registered write tool definition
|
|
76
|
+
* @throws {Error} When the write tool is unavailable
|
|
77
|
+
*/
|
|
78
|
+
function getWriteTool(harness: ExtensionHarness) {
|
|
79
|
+
const tool = harness.tools.get("write");
|
|
80
|
+
if (!tool) {
|
|
81
|
+
throw new Error("write tool not registered");
|
|
82
|
+
}
|
|
83
|
+
return tool;
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
72
87
|
|
|
73
88
|
describe("write-tool-enhanced", () => {
|
|
@@ -109,7 +124,7 @@ describe("write-tool-enhanced", () => {
|
|
|
109
124
|
const harness = ExtensionHarness.create();
|
|
110
125
|
await harness.loadExtension(writePreview);
|
|
111
126
|
|
|
112
|
-
const tool = harness
|
|
127
|
+
const tool = getWriteTool(harness);
|
|
113
128
|
const content = "const x = 1;\nconst y = 2;";
|
|
114
129
|
|
|
115
130
|
const result = await tool.execute(
|
|
@@ -128,7 +143,7 @@ describe("write-tool-enhanced", () => {
|
|
|
128
143
|
const harness = ExtensionHarness.create();
|
|
129
144
|
await harness.loadExtension(writePreview);
|
|
130
145
|
|
|
131
|
-
const tool = harness
|
|
146
|
+
const tool = getWriteTool(harness);
|
|
132
147
|
const content = "line1\nline2\nline3"; // 3 lines
|
|
133
148
|
|
|
134
149
|
const result = await tool.execute(
|
|
@@ -148,7 +163,7 @@ describe("write-tool-enhanced", () => {
|
|
|
148
163
|
const harness = ExtensionHarness.create();
|
|
149
164
|
await harness.loadExtension(writePreview);
|
|
150
165
|
|
|
151
|
-
const tool = harness
|
|
166
|
+
const tool = getWriteTool(harness);
|
|
152
167
|
// 1200 chars → 1200/1024 ≈ 1.171875 → toFixed(1) = "1.2"
|
|
153
168
|
const content = "a".repeat(1200);
|
|
154
169
|
const expectedKb = (content.length / 1024).toFixed(1);
|
|
@@ -170,7 +185,7 @@ describe("write-tool-enhanced", () => {
|
|
|
170
185
|
const harness = ExtensionHarness.create();
|
|
171
186
|
await harness.loadExtension(writePreview);
|
|
172
187
|
|
|
173
|
-
const tool = harness
|
|
188
|
+
const tool = getWriteTool(harness);
|
|
174
189
|
const content = "hello\nworld"; // 2 lines, 11 chars
|
|
175
190
|
|
|
176
191
|
const result = await tool.execute(
|
|
@@ -190,7 +205,7 @@ describe("write-tool-enhanced", () => {
|
|
|
190
205
|
const harness = ExtensionHarness.create();
|
|
191
206
|
await harness.loadExtension(writePreview);
|
|
192
207
|
|
|
193
|
-
const tool = harness
|
|
208
|
+
const tool = getWriteTool(harness);
|
|
194
209
|
|
|
195
210
|
const result = await tool.execute(
|
|
196
211
|
"test-id",
|
|
@@ -211,7 +226,7 @@ describe("write-tool-enhanced", () => {
|
|
|
211
226
|
const harness = ExtensionHarness.create();
|
|
212
227
|
await harness.loadExtension(writePreview);
|
|
213
228
|
|
|
214
|
-
const tool = harness
|
|
229
|
+
const tool = getWriteTool(harness);
|
|
215
230
|
|
|
216
231
|
await expect(
|
|
217
232
|
tool.execute(
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Enhanced write tool — shows full written content with summary footer.
|
|
3
3
|
*/
|
|
4
4
|
import { createWriteTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
5
|
-
import {
|
|
5
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
6
6
|
import { getIcon } from "../_icons/index.js";
|
|
7
|
+
import { fileLink } from "../_shared/terminal-links.js";
|
|
7
8
|
import {
|
|
8
9
|
appendSection,
|
|
9
10
|
dimProcessOutputLine,
|