@oh-my-pi/pi-coding-agent 17.1.8 → 17.2.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 +110 -0
- package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-dj46zzrm.md} +110 -0
- package/dist/cli.js +10009 -9654
- package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
- package/dist/types/advisor/runtime.d.ts +8 -2
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-discovery.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +73 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +25 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/internal-urls/security-protocol.d.ts +15 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +18 -0
- package/dist/types/security/auth.d.ts +30 -0
- package/dist/types/security/cloud.d.ts +79 -0
- package/dist/types/security/comparison.d.ts +49 -0
- package/dist/types/security/contracts/ids.d.ts +15 -0
- package/dist/types/security/contracts/index.d.ts +4 -0
- package/dist/types/security/contracts/schemas.d.ts +660 -0
- package/dist/types/security/contracts/types.d.ts +234 -0
- package/dist/types/security/contracts/validation.d.ts +5 -0
- package/dist/types/security/coordinator.d.ts +100 -0
- package/dist/types/security/importers/codex-security.d.ts +7 -0
- package/dist/types/security/importers/index.d.ts +2 -0
- package/dist/types/security/importers/sarif.d.ts +9 -0
- package/dist/types/security/index.d.ts +13 -0
- package/dist/types/security/preflight.d.ts +61 -0
- package/dist/types/security/provenance.d.ts +24 -0
- package/dist/types/security/publication.d.ts +78 -0
- package/dist/types/security/remediation.d.ts +27 -0
- package/dist/types/security/resource-output.d.ts +8 -0
- package/dist/types/security/sarif.d.ts +2 -0
- package/dist/types/security/store.d.ts +40 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/slash-commands/helpers/security.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +4 -1
- package/dist/types/task/executor.d.ts +14 -2
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +10 -2
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/security-scan.d.ts +96 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/runtime.ts +138 -12
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli.ts +9 -0
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-discovery.ts +32 -5
- package/src/config/model-registry.ts +107 -25
- package/src/config/settings-schema.ts +77 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/eval/py/prelude.py +7 -3
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +65 -13
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/internal-urls/router.ts +4 -1
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/types.ts +1 -1
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +7 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +15 -9
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +31 -17
- package/src/modes/rpc/host-uris.ts +6 -0
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/grep.md +7 -6
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/sdk.ts +212 -21
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +201 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +65 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +302 -175
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +11 -3
- package/src/session/session-advisors.ts +140 -16
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +152 -3
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/agents.ts +2 -0
- package/src/task/executor.ts +20 -4
- package/src/task/render.ts +20 -4
- package/src/task/structured-subagent.ts +6 -4
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/browser.ts +5 -0
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +37 -5
- package/src/tools/path-utils.ts +91 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/write.ts +9 -3
- package/src/tools/xdev.ts +39 -14
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/types.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SnapshotStore } from "@oh-my-pi/hashline";
|
|
1
|
+
import type { Clipboard, SnapshotStore } from "@oh-my-pi/hashline";
|
|
2
2
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import {
|
|
4
4
|
Box,
|
|
@@ -203,6 +203,13 @@ class SafeToolRendererComponent implements Component {
|
|
|
203
203
|
*/
|
|
204
204
|
export interface TranscriptLiveRegionProbe {
|
|
205
205
|
isBlockInLiveRegion(component: Component): boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Whether none of the block's rows have entered native scrollback (see
|
|
208
|
+
* `TranscriptContainer.isBlockUncommitted`). Optional: standalone hosts
|
|
209
|
+
* without commit tracking omit it, and blocks treat their rows as
|
|
210
|
+
* uncommitted.
|
|
211
|
+
*/
|
|
212
|
+
isBlockUncommitted?(component: Component): boolean;
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
/** Minimal TUI surface ToolExecutionComponent uses to schedule repaints and share image budget. */
|
|
@@ -215,6 +222,8 @@ export interface ToolExecutionUi {
|
|
|
215
222
|
|
|
216
223
|
export interface ToolExecutionOptions {
|
|
217
224
|
snapshots?: SnapshotStore;
|
|
225
|
+
/** Session-persistent edit clipboard register, forked per preview frame. */
|
|
226
|
+
clipboard?: Clipboard;
|
|
218
227
|
showImages?: boolean; // default: true (only used if terminal supports images)
|
|
219
228
|
editFuzzyThreshold?: number;
|
|
220
229
|
editAllowFuzzy?: boolean;
|
|
@@ -278,6 +287,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
278
287
|
#editFuzzyThreshold: number | undefined;
|
|
279
288
|
#editAllowFuzzy: boolean | undefined;
|
|
280
289
|
#snapshots?: SnapshotStore;
|
|
290
|
+
#clipboard?: Clipboard;
|
|
281
291
|
#isPartial = true;
|
|
282
292
|
#resultVersion = 0;
|
|
283
293
|
#lastDisplayKey: string | undefined;
|
|
@@ -337,10 +347,21 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
337
347
|
// transcript, e.g. in tests): whether this block is still repaintable.
|
|
338
348
|
#liveRegion?: TranscriptLiveRegionProbe;
|
|
339
349
|
// One-way latch for a detached (`async.state === "running"`) task block
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
// dropped
|
|
350
|
+
// whose rows became native-scrollback history — it left the transcript
|
|
351
|
+
// live region, or its head rows were committed while it was still the
|
|
352
|
+
// live tail. Further partial snapshots are dropped so committed rows are
|
|
353
|
+
// never mutated (see #maybeFreezeBackgroundTask).
|
|
343
354
|
#backgroundTaskFrozen = false;
|
|
355
|
+
// Whether the freeze may restyle the progress rows static gray. Set only
|
|
356
|
+
// when the latch fired while no row was committed: a recolor of rows
|
|
357
|
+
// already on the tape would itself diverge immutable history and force an
|
|
358
|
+
// erase-replay (or, with scrollback rebuild off, a duplicate slab).
|
|
359
|
+
#backgroundTaskFrozenStyled = false;
|
|
360
|
+
// Wall clock captured at each repaintable rebuild of a task card and
|
|
361
|
+
// reused verbatim once the card freezes or any of its rows commit, so
|
|
362
|
+
// time-derived rows (current-tool elapsed, retry countdown) cannot drift
|
|
363
|
+
// a committed byte on later rebuilds (theme epoch, image toggles).
|
|
364
|
+
#taskRenderNowMs = Date.now();
|
|
344
365
|
// Set on each `render()` when the last painted pending shape must be
|
|
345
366
|
// replayed wholesale when the first result arrives. Reset gates key off
|
|
346
367
|
// these so a topology-changing update that lands before the shape reaches
|
|
@@ -375,6 +396,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
375
396
|
this.#editFuzzyThreshold = options.editFuzzyThreshold;
|
|
376
397
|
this.#editAllowFuzzy = options.editAllowFuzzy;
|
|
377
398
|
this.#snapshots = options.snapshots;
|
|
399
|
+
this.#clipboard = options.clipboard;
|
|
378
400
|
this.#liveRegion = options.liveRegion;
|
|
379
401
|
this.#tool = tool;
|
|
380
402
|
this.#ui = ui;
|
|
@@ -532,6 +554,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
532
554
|
cwd: this.#cwd,
|
|
533
555
|
signal: controller.signal,
|
|
534
556
|
snapshots: this.#snapshots!,
|
|
557
|
+
clipboard: this.#clipboard,
|
|
535
558
|
fuzzyThreshold: this.#editFuzzyThreshold,
|
|
536
559
|
allowFuzzy: this.#editAllowFuzzy,
|
|
537
560
|
isStreaming,
|
|
@@ -559,13 +582,17 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
559
582
|
_toolCallId?: string,
|
|
560
583
|
): void {
|
|
561
584
|
// A detached task spawn keeps streaming progress snapshots after the
|
|
562
|
-
// block froze (left the transcript live region
|
|
563
|
-
//
|
|
564
|
-
// may already have committed
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
585
|
+
// block froze (left the transcript live region, or its rows entered
|
|
586
|
+
// native scrollback). Drop them: repainting would rewrite rows the
|
|
587
|
+
// engine may already have committed. The terminal snapshot (async
|
|
588
|
+
// completed/failed → isPartial=false) still settles a card that is
|
|
589
|
+
// wholly uncommitted (still on screen); once any row is on the tape
|
|
590
|
+
// the card is immutable history — replacing it would re-commit the
|
|
591
|
+
// whole slab below the stale copy — so the settlement is dropped and
|
|
592
|
+
// the job's result surfaces through its own delivery message.
|
|
593
|
+
if (this.#toolName === "task" && this.#maybeFreezeBackgroundTask()) {
|
|
594
|
+
if (isPartial) return;
|
|
595
|
+
if (!(this.#liveRegion?.isBlockUncommitted?.(this) ?? true)) return;
|
|
569
596
|
}
|
|
570
597
|
const hadNoResult = this.#result === undefined;
|
|
571
598
|
const wasPartialResult = this.#result !== undefined && this.#isPartial;
|
|
@@ -713,22 +740,30 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
713
740
|
}
|
|
714
741
|
|
|
715
742
|
/**
|
|
716
|
-
* Freeze a detached (`async.state === "running"`) task block once
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
743
|
+
* Freeze a detached (`async.state === "running"`) task block once its rows
|
|
744
|
+
* become native-scrollback history: the block left the transcript's live
|
|
745
|
+
* region (a later block streams below it), or — while it is still the
|
|
746
|
+
* live tail — its head rows were committed because the frame outgrew the
|
|
747
|
+
* viewport. Committed rows are immutable, so from that point every further
|
|
748
|
+
* partial snapshot is dropped. Rows restyle static gray only when nothing
|
|
749
|
+
* is committed yet; otherwise the bytes stay exactly as painted. One-way —
|
|
750
|
+
* blocks never re-enter the live region. Returns whether the block is
|
|
751
|
+
* frozen.
|
|
721
752
|
*/
|
|
722
753
|
#maybeFreezeBackgroundTask(): boolean {
|
|
723
754
|
if (this.#backgroundTaskFrozen) return true;
|
|
724
755
|
if (this.#toolName !== "task" || this.#liveRegion === undefined) return false;
|
|
725
756
|
const asyncState = (this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state;
|
|
726
757
|
if (asyncState !== "running") return false;
|
|
727
|
-
|
|
758
|
+
const uncommitted = this.#liveRegion.isBlockUncommitted?.(this) ?? true;
|
|
759
|
+
if (uncommitted && this.#liveRegion.isBlockInLiveRegion(this)) return false;
|
|
728
760
|
this.#backgroundTaskFrozen = true;
|
|
729
761
|
this.#updateSpinnerAnimation();
|
|
730
|
-
|
|
731
|
-
|
|
762
|
+
if (uncommitted) {
|
|
763
|
+
this.#backgroundTaskFrozenStyled = true;
|
|
764
|
+
this.#updateDisplay();
|
|
765
|
+
this.#ui.requestRender();
|
|
766
|
+
}
|
|
732
767
|
return true;
|
|
733
768
|
}
|
|
734
769
|
|
|
@@ -784,16 +819,16 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
784
819
|
|
|
785
820
|
/**
|
|
786
821
|
* Keeps in-flight TV-wall frames out of immutable native scrollback: the
|
|
787
|
-
* `vibe_wait` wall
|
|
788
|
-
* lists). Their frames replace each other rather
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
822
|
+
* `vibe_wait` wall, displaceable snapshots (`hub` waiting polls, `todo`
|
|
823
|
+
* lists), and live `task` calls. Their frames replace each other rather
|
|
824
|
+
* than append — task progress rows rewrite in place on every snapshot —
|
|
825
|
+
* so an unpinned commit records a per-tick frozen snapshot (and for
|
|
826
|
+
* displaceable blocks force-seals them, stacking the next poll below).
|
|
827
|
+
* The finalized frame commits exactly once when the pin lifts.
|
|
793
828
|
*/
|
|
794
829
|
isNativeScrollbackLiveRegionPinned(): boolean {
|
|
795
830
|
if (this.isTranscriptBlockFinalized()) return false;
|
|
796
|
-
return this.#toolName === "vibe_wait" || this.#displaceableByToolName !== undefined;
|
|
831
|
+
return this.#toolName === "vibe_wait" || this.#toolName === "task" || this.#displaceableByToolName !== undefined;
|
|
797
832
|
}
|
|
798
833
|
|
|
799
834
|
/**
|
|
@@ -827,8 +862,12 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
827
862
|
this.#sealed = true;
|
|
828
863
|
this.#displaceableByToolName = undefined;
|
|
829
864
|
// A sealed detached task is abandoned history: settle its progress rows
|
|
830
|
-
// on static gray
|
|
865
|
+
// on static gray — but only while none of them are committed; a recolor
|
|
866
|
+
// on the tape would diverge immutable history.
|
|
831
867
|
this.#backgroundTaskFrozen = true;
|
|
868
|
+
if (this.#liveRegion?.isBlockUncommitted?.(this) ?? true) {
|
|
869
|
+
this.#backgroundTaskFrozenStyled = true;
|
|
870
|
+
}
|
|
832
871
|
this.stopAnimation();
|
|
833
872
|
this.#updateDisplay();
|
|
834
873
|
this.#ui.requestRender();
|
|
@@ -888,7 +927,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
888
927
|
// TUI startup, so a result rendered before it lands must re-shape once it
|
|
889
928
|
// does (it gates Image children vs text fallback in #rebuildDisplay); keyed
|
|
890
929
|
// here for the same reason markdown.ts keys its render cache on it.
|
|
891
|
-
const key = `${this.#resultVersion}|${this.#expanded}|${this.#isPartial}|${this.#spinnerFrame ?? "-"}|${this.#showImages}|${getThemeEpoch()}|${this.#displayInputVersion}|${this.#
|
|
930
|
+
const key = `${this.#resultVersion}|${this.#expanded}|${this.#isPartial}|${this.#spinnerFrame ?? "-"}|${this.#showImages}|${getThemeEpoch()}|${this.#displayInputVersion}|${this.#backgroundTaskFrozenStyled}|${TERMINAL.imageProtocol ?? "-"}|${this.#imageSizeKey()}`;
|
|
892
931
|
if (key === this.#lastDisplayKey && this.#displayBuilt) return;
|
|
893
932
|
this.#lastDisplayKey = key;
|
|
894
933
|
|
|
@@ -1292,9 +1331,17 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
1292
1331
|
// draws every dispatched agent as a progress/result line, so tell
|
|
1293
1332
|
// `renderCall` to drop its duplicate streaming preview list.
|
|
1294
1333
|
context.hasResult = Boolean(this.#result);
|
|
1295
|
-
//
|
|
1296
|
-
// (see task/render.ts).
|
|
1297
|
-
context.frozen = this.#
|
|
1334
|
+
// Settled as history (out of the live region, before any row entered
|
|
1335
|
+
// the tape): progress rows render static gray (see task/render.ts).
|
|
1336
|
+
context.frozen = this.#backgroundTaskFrozenStyled;
|
|
1337
|
+
// Freeze the render clock alongside the latch — and independently the
|
|
1338
|
+
// moment any row commits, closing the window between a commit paint
|
|
1339
|
+
// and the next snapshot where a settings-triggered rebuild could
|
|
1340
|
+
// re-derive elapsed/countdown bytes under committed rows.
|
|
1341
|
+
if (!this.#backgroundTaskFrozen && (this.#liveRegion?.isBlockUncommitted?.(this) ?? true)) {
|
|
1342
|
+
this.#taskRenderNowMs = Date.now();
|
|
1343
|
+
}
|
|
1344
|
+
context.nowMs = this.#taskRenderNowMs;
|
|
1298
1345
|
} else if (isEditLikeToolName(this.#toolName)) {
|
|
1299
1346
|
context.editMode = this.#editMode;
|
|
1300
1347
|
const previews = this.#editDiffPreview;
|
|
@@ -15,10 +15,8 @@ function formatUsageTimestamp(ms: number): string {
|
|
|
15
15
|
return `${date} ${time}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// public export surface (./modes/components/*).
|
|
21
|
-
export function createUsageRowBlock(usage: Usage, durationMs?: number, ttftMs?: number, timestamp?: number): Container {
|
|
18
|
+
/** Format the metrics shared by standalone usage blocks and compact tool groups. */
|
|
19
|
+
export function formatUsageRow(usage: Usage, durationMs?: number, ttftMs?: number, timestamp?: number): string {
|
|
22
20
|
const totalInput = usage.input + usage.cacheWrite;
|
|
23
21
|
const parts: string[] = [];
|
|
24
22
|
// Lead with the turn's local wall-clock time (down to the second), log-line style.
|
|
@@ -40,8 +38,15 @@ export function createUsageRowBlock(usage: Usage, durationMs?: number, ttftMs?:
|
|
|
40
38
|
const tokPerSec = (usage.output / durationMs) * 1000;
|
|
41
39
|
parts.push(`${theme.icon.throughput} ${tokPerSec.toFixed(1)}/s`);
|
|
42
40
|
}
|
|
41
|
+
return parts.join(" ");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// `timestamp` is optional and trails the throughput args to preserve the existing
|
|
45
|
+
// (usage, durationMs, ttftMs) call contract — this function is part of the package's
|
|
46
|
+
// public export surface (./modes/components/*).
|
|
47
|
+
export function createUsageRowBlock(usage: Usage, durationMs?: number, ttftMs?: number, timestamp?: number): Container {
|
|
43
48
|
const block = new Container();
|
|
44
49
|
block.addChild(new Spacer(1));
|
|
45
|
-
block.addChild(new Text(theme.fg("dim",
|
|
50
|
+
block.addChild(new Text(theme.fg("dim", formatUsageRow(usage, durationMs, ttftMs, timestamp)), 1, 0));
|
|
46
51
|
return block;
|
|
47
52
|
}
|
|
@@ -6,10 +6,12 @@ import { logger, prompt, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
|
6
6
|
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
7
7
|
import { extractTextContent } from "../../commit/utils";
|
|
8
8
|
import { settings } from "../../config/settings";
|
|
9
|
+
import { getEditClipboard } from "../../edit/edit-clipboard";
|
|
9
10
|
import { getFileSnapshotStore } from "../../edit/file-snapshot-store";
|
|
10
11
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
11
12
|
import { detectCacheInvalidation } from "../../modes/components/cache-invalidation-marker";
|
|
12
13
|
import {
|
|
14
|
+
groupedReadUsageCallIds,
|
|
13
15
|
ReadToolGroupComponent,
|
|
14
16
|
readArgsCollapseIntoGroup,
|
|
15
17
|
readArgsHaveTarget,
|
|
@@ -193,6 +195,10 @@ export class EventController {
|
|
|
193
195
|
todo_auto_clear: e => this.#handleTodoAutoClear(e),
|
|
194
196
|
irc_message: e => this.#handleIrcMessage(e),
|
|
195
197
|
notice: e => this.#handleNotice(e),
|
|
198
|
+
model_changed: async () => {
|
|
199
|
+
this.ctx.statusLine.invalidate();
|
|
200
|
+
this.ctx.ui.requestRender();
|
|
201
|
+
},
|
|
196
202
|
thinking_level_changed: async () => {
|
|
197
203
|
this.ctx.statusLine.invalidate();
|
|
198
204
|
this.ctx.updateEditorBorderColor();
|
|
@@ -805,6 +811,7 @@ export class EventController {
|
|
|
805
811
|
renderArgs,
|
|
806
812
|
{
|
|
807
813
|
snapshots: getFileSnapshotStore(this.ctx.viewSession),
|
|
814
|
+
clipboard: getEditClipboard(this.ctx.viewSession),
|
|
808
815
|
showImages: settings.get("terminal.showImages"),
|
|
809
816
|
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
810
817
|
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
@@ -954,14 +961,28 @@ export class EventController {
|
|
|
954
961
|
}
|
|
955
962
|
this.#lastAssistantComponent = lastPostToolAssistantComponent ?? this.ctx.streamingComponent;
|
|
956
963
|
if (settings.get("display.showTokenUsage") && assistantUsageIsBilled(event.message.usage)) {
|
|
957
|
-
|
|
958
|
-
|
|
964
|
+
const readCallIds = groupedReadUsageCallIds(event.message);
|
|
965
|
+
const usageAttached =
|
|
966
|
+
readCallIds !== undefined &&
|
|
967
|
+
(this.#lastReadGroup?.attachUsage(
|
|
968
|
+
readCallIds,
|
|
959
969
|
event.message.usage,
|
|
960
970
|
event.message.duration,
|
|
961
971
|
event.message.ttft,
|
|
962
972
|
event.message.timestamp,
|
|
963
|
-
)
|
|
964
|
-
|
|
973
|
+
) ??
|
|
974
|
+
false);
|
|
975
|
+
if (!usageAttached) {
|
|
976
|
+
this.#resetReadGroup();
|
|
977
|
+
this.ctx.chatContainer.addChild(
|
|
978
|
+
createUsageRowBlock(
|
|
979
|
+
event.message.usage,
|
|
980
|
+
event.message.duration,
|
|
981
|
+
event.message.ttft,
|
|
982
|
+
event.message.timestamp,
|
|
983
|
+
),
|
|
984
|
+
);
|
|
985
|
+
}
|
|
965
986
|
}
|
|
966
987
|
if (displayMessage === event.message) {
|
|
967
988
|
this.ctx.transcriptMessageComponents.set(event.message, this.ctx.streamingComponent);
|
|
@@ -1014,6 +1035,7 @@ export class EventController {
|
|
|
1014
1035
|
event.args,
|
|
1015
1036
|
{
|
|
1016
1037
|
snapshots: getFileSnapshotStore(this.ctx.viewSession),
|
|
1038
|
+
clipboard: getEditClipboard(this.ctx.viewSession),
|
|
1017
1039
|
showImages: settings.get("terminal.showImages"),
|
|
1018
1040
|
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
1019
1041
|
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
@@ -48,7 +48,13 @@ import {
|
|
|
48
48
|
searchSmitheryRegistry,
|
|
49
49
|
toConfigName,
|
|
50
50
|
} from "../../mcp/smithery-registry";
|
|
51
|
-
import type {
|
|
51
|
+
import type {
|
|
52
|
+
MCPAuthChallenge,
|
|
53
|
+
MCPAuthConfig,
|
|
54
|
+
MCPConfigFile,
|
|
55
|
+
MCPServerConfig,
|
|
56
|
+
MCPServerConnection,
|
|
57
|
+
} from "../../mcp/types";
|
|
52
58
|
import { shortenPath } from "../../tools/render-utils";
|
|
53
59
|
import { urlHyperlinkAlways } from "../../tui";
|
|
54
60
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
@@ -246,6 +252,64 @@ type MCPSearchParsed = {
|
|
|
246
252
|
error?: string;
|
|
247
253
|
};
|
|
248
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Collect the de-duplicated union of every MCP server name we know about:
|
|
257
|
+
* user config, project config, and any runtime-discovered servers not
|
|
258
|
+
* already present in either config (`ctx.mcpManager.getAllServerNames()`
|
|
259
|
+
* covers connections, pending connections, and discovered-but-not-yet-
|
|
260
|
+
* connected sources).
|
|
261
|
+
*
|
|
262
|
+
* `includeDisabledOnly` controls names found only in
|
|
263
|
+
* `userConfig.disabledServers`, while `includeDisabledConfigured` controls
|
|
264
|
+
* config entries whose `enabled` flag is false. Both default to true because
|
|
265
|
+
* callers such as `/mcp list` need the complete union. Autocomplete callers
|
|
266
|
+
* must disable the categories their target operation cannot accept.
|
|
267
|
+
*
|
|
268
|
+
* This is the single source of truth for "every known server name": both
|
|
269
|
+
* `MCPCommandController#handleList()` and the `/mcp` slash-command argument
|
|
270
|
+
* completer (server-name autocomplete for `enable`/`disable`/`test`/etc.)
|
|
271
|
+
* call this instead of re-deriving the union themselves.
|
|
272
|
+
*
|
|
273
|
+
* `preloaded` lets a caller that already read both config files (e.g.
|
|
274
|
+
* `#handleList()`) pass them in and skip the redundant re-read.
|
|
275
|
+
*/
|
|
276
|
+
export async function collectMcpServerNames(
|
|
277
|
+
ctx: InteractiveModeContext,
|
|
278
|
+
preloaded?: { userConfig: MCPConfigFile; projectConfig: MCPConfigFile },
|
|
279
|
+
includeDisabledOnly = true,
|
|
280
|
+
includeDisabledConfigured = true,
|
|
281
|
+
): Promise<string[]> {
|
|
282
|
+
let userConfig: MCPConfigFile;
|
|
283
|
+
let projectConfig: MCPConfigFile;
|
|
284
|
+
if (preloaded) {
|
|
285
|
+
({ userConfig, projectConfig } = preloaded);
|
|
286
|
+
} else {
|
|
287
|
+
const cwd = getProjectDir();
|
|
288
|
+
[userConfig, projectConfig] = await Promise.all([
|
|
289
|
+
readMCPConfigFile(getMCPConfigPath("user", cwd)),
|
|
290
|
+
readMCPConfigFile(getMCPConfigPath("project", cwd)),
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const names = new Set<string>(includeDisabledOnly ? (userConfig.disabledServers ?? []) : []);
|
|
295
|
+
const addConfiguredNames = (config: MCPConfigFile): void => {
|
|
296
|
+
const servers = config.mcpServers;
|
|
297
|
+
if (!servers) return;
|
|
298
|
+
for (const name in servers) {
|
|
299
|
+
const server = servers[name];
|
|
300
|
+
if (server && (includeDisabledConfigured || server.enabled !== false)) names.add(name);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
addConfiguredNames(userConfig);
|
|
304
|
+
addConfiguredNames(projectConfig);
|
|
305
|
+
if (ctx.mcpManager) {
|
|
306
|
+
for (const name of ctx.mcpManager.getAllServerNames()) {
|
|
307
|
+
names.add(name);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return [...names].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
|
|
311
|
+
}
|
|
312
|
+
|
|
249
313
|
export class MCPCommandController {
|
|
250
314
|
constructor(private ctx: InteractiveModeContext) {}
|
|
251
315
|
|
|
@@ -1271,10 +1335,11 @@ export class MCPCommandController {
|
|
|
1271
1335
|
|
|
1272
1336
|
// Collect runtime-discovered servers not in config files
|
|
1273
1337
|
const configServerNames = new Set([...userServers, ...projectServers]);
|
|
1274
|
-
const disabledServerNames = new Set(
|
|
1338
|
+
const disabledServerNames = new Set(userConfig.disabledServers ?? []);
|
|
1275
1339
|
const discoveredServers: { name: string; source: SourceMeta }[] = [];
|
|
1276
1340
|
if (this.ctx.mcpManager) {
|
|
1277
|
-
|
|
1341
|
+
const allServerNames = await collectMcpServerNames(this.ctx, { userConfig, projectConfig });
|
|
1342
|
+
for (const name of allServerNames) {
|
|
1278
1343
|
if (configServerNames.has(name)) continue;
|
|
1279
1344
|
if (disabledServerNames.has(name)) continue;
|
|
1280
1345
|
const source = this.ctx.mcpManager.getSource(name);
|
|
@@ -42,6 +42,13 @@ import {
|
|
|
42
42
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
43
43
|
import type { SessionOAuthAccountList } from "../../session/agent-session-types";
|
|
44
44
|
import type { ResetCreditAccountStatus, ResetCreditRedeemOutcome } from "../../session/auth-storage";
|
|
45
|
+
import {
|
|
46
|
+
createForeignSessionStore,
|
|
47
|
+
foreignSessionInfoToSessionInfo,
|
|
48
|
+
foreignSessionSourceName,
|
|
49
|
+
persistForeignSession,
|
|
50
|
+
} from "../../session/foreign-session-import";
|
|
51
|
+
import type { ForeignSessionInfo, ForeignSessionSource } from "../../session/foreign-session-store";
|
|
45
52
|
import type { SessionInfo } from "../../session/session-listing";
|
|
46
53
|
import { SessionManager } from "../../session/session-manager";
|
|
47
54
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
@@ -87,7 +94,7 @@ import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
|
87
94
|
import { ResetUsageSelectorComponent } from "../components/reset-usage-selector";
|
|
88
95
|
import { renderSegmentTrack } from "../components/segment-track";
|
|
89
96
|
import { SessionAccountSelectorComponent } from "../components/session-account-selector";
|
|
90
|
-
import { SessionSelectorComponent } from "../components/session-selector";
|
|
97
|
+
import { SessionSelectorComponent, type SessionSelectorOptions } from "../components/session-selector";
|
|
91
98
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
92
99
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
93
100
|
import { TranscriptBlock } from "../components/transcript-container";
|
|
@@ -1358,20 +1365,87 @@ export class SelectorController {
|
|
|
1358
1365
|
return result;
|
|
1359
1366
|
}
|
|
1360
1367
|
|
|
1361
|
-
async showSessionSelector(): Promise<void> {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1368
|
+
async showSessionSelector(source?: ForeignSessionSource): Promise<void> {
|
|
1369
|
+
let sessions: SessionInfo[];
|
|
1370
|
+
let onSelectSession: (session: SessionInfo) => Promise<boolean>;
|
|
1371
|
+
let selectorOptions: SessionSelectorOptions;
|
|
1372
|
+
|
|
1373
|
+
if (source) {
|
|
1374
|
+
const sourceName = foreignSessionSourceName(source);
|
|
1375
|
+
const store = createForeignSessionStore(source);
|
|
1376
|
+
let foreignSessions: ForeignSessionInfo[];
|
|
1377
|
+
try {
|
|
1378
|
+
foreignSessions = await store.list();
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
this.ctx.showError(
|
|
1381
|
+
`Failed to list ${sourceName} sessions: ${error instanceof Error ? error.message : String(error)}`,
|
|
1382
|
+
);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
if (foreignSessions.length === 0) {
|
|
1386
|
+
this.ctx.showWarning(`No ${sourceName} sessions found`);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const foreignByPath = new Map(foreignSessions.map(session => [session.path, session]));
|
|
1390
|
+
sessions = foreignSessions.map(foreignSessionInfoToSessionInfo);
|
|
1391
|
+
onSelectSession = async session => {
|
|
1392
|
+
try {
|
|
1393
|
+
await this.ctx.settings.flush();
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
this.ctx.showError(
|
|
1396
|
+
`Failed to save pending settings: ${error instanceof Error ? error.message : String(error)}`,
|
|
1397
|
+
);
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
const foreignSession = foreignByPath.get(session.path);
|
|
1401
|
+
if (!foreignSession) throw new Error(`Selected ${sourceName} session is no longer available`);
|
|
1402
|
+
const imported = await persistForeignSession(store, foreignSession, {
|
|
1403
|
+
fallbackCwd: this.ctx.sessionManager.getCwd(),
|
|
1404
|
+
suppressBreadcrumb: true,
|
|
1405
|
+
});
|
|
1406
|
+
const sessionFile = imported.getSessionFile();
|
|
1407
|
+
if (!sessionFile) throw new Error(`Failed to persist ${sourceName} session`);
|
|
1408
|
+
await imported.close();
|
|
1409
|
+
return await this.handleResumeSession(sessionFile, { settingsFlushed: true });
|
|
1410
|
+
};
|
|
1411
|
+
selectorOptions = {
|
|
1412
|
+
title: `Import ${sourceName} Session`,
|
|
1413
|
+
scopeLabel: false,
|
|
1414
|
+
showCwd: true,
|
|
1415
|
+
};
|
|
1416
|
+
} else {
|
|
1417
|
+
sessions = await SessionManager.list(
|
|
1418
|
+
this.ctx.sessionManager.getCwd(),
|
|
1419
|
+
this.ctx.sessionManager.getSessionDir(),
|
|
1420
|
+
);
|
|
1421
|
+
const historyStorage = this.ctx.historyStorage;
|
|
1422
|
+
const historyMatcher = historyStorage
|
|
1423
|
+
? (query: string) => historyStorage.matchingSessionIds(query)
|
|
1424
|
+
: undefined;
|
|
1425
|
+
onSelectSession = session => this.handleResumeSession(session.path);
|
|
1426
|
+
selectorOptions = {
|
|
1427
|
+
onDelete: async (session: SessionInfo) => {
|
|
1428
|
+
if (!(await this.#detachActiveSessionBeforeDeletion(session.path))) {
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
const storage = new FileSessionStorage();
|
|
1432
|
+
try {
|
|
1433
|
+
await storage.deleteSessionWithArtifacts(session.path);
|
|
1434
|
+
return true;
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
throw new Error(
|
|
1437
|
+
`Failed to delete session: ${error instanceof Error ? error.message : String(error)}`,
|
|
1438
|
+
{ cause: error },
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1442
|
+
historyMatcher,
|
|
1443
|
+
loadAllSessions: () => SessionManager.listAll(),
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1371
1447
|
// Keep the fullscreen picker on the alternate buffer while a selected
|
|
1372
|
-
// session is loaded and its transcript is rebuilt.
|
|
1373
|
-
// the stale normal buffer for the entire async switch on terminals without
|
|
1374
|
-
// effective synchronized output.
|
|
1448
|
+
// session is loaded and its transcript is rebuilt.
|
|
1375
1449
|
let overlayHandle: OverlayHandle | undefined;
|
|
1376
1450
|
const done = () => {
|
|
1377
1451
|
overlayHandle?.hide();
|
|
@@ -1384,43 +1458,27 @@ export class SelectorController {
|
|
|
1384
1458
|
selector.lockInput();
|
|
1385
1459
|
let keepOpen = false;
|
|
1386
1460
|
try {
|
|
1387
|
-
const success = await
|
|
1461
|
+
const success = await onSelectSession(session);
|
|
1388
1462
|
if (!success) {
|
|
1389
1463
|
keepOpen = true;
|
|
1390
1464
|
selector.unlockInput();
|
|
1391
1465
|
this.ctx.ui.requestRender();
|
|
1392
1466
|
}
|
|
1467
|
+
} catch (error) {
|
|
1468
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
1393
1469
|
} finally {
|
|
1394
1470
|
if (!keepOpen) done();
|
|
1395
1471
|
}
|
|
1396
1472
|
},
|
|
1397
|
-
|
|
1398
|
-
done();
|
|
1399
|
-
},
|
|
1473
|
+
done,
|
|
1400
1474
|
() => {
|
|
1401
1475
|
// Release the alt buffer before teardown: shutdown() awaits flush/save/
|
|
1402
|
-
// dispose/drain before stop() leaves the alt screen
|
|
1403
|
-
// fullscreen picker would freeze on screen for that window on Ctrl+C.
|
|
1476
|
+
// dispose/drain before stop() leaves the alt screen.
|
|
1404
1477
|
done();
|
|
1405
1478
|
void this.ctx.shutdown();
|
|
1406
1479
|
},
|
|
1407
1480
|
{
|
|
1408
|
-
|
|
1409
|
-
if (!(await this.#detachActiveSessionBeforeDeletion(session.path))) {
|
|
1410
|
-
return false;
|
|
1411
|
-
}
|
|
1412
|
-
const storage = new FileSessionStorage();
|
|
1413
|
-
try {
|
|
1414
|
-
await storage.deleteSessionWithArtifacts(session.path);
|
|
1415
|
-
return true;
|
|
1416
|
-
} catch (err) {
|
|
1417
|
-
throw new Error(`Failed to delete session: ${err instanceof Error ? err.message : String(err)}`, {
|
|
1418
|
-
cause: err,
|
|
1419
|
-
});
|
|
1420
|
-
}
|
|
1421
|
-
},
|
|
1422
|
-
historyMatcher,
|
|
1423
|
-
loadAllSessions: () => SessionManager.listAll(),
|
|
1481
|
+
...selectorOptions,
|
|
1424
1482
|
getTerminalRows: () => this.ctx.ui.terminal.rows,
|
|
1425
1483
|
fillHeight: true,
|
|
1426
1484
|
},
|
|
@@ -80,6 +80,19 @@ export class TanCommandController {
|
|
|
80
80
|
const ownerId = session.getAgentId() ?? MAIN_AGENT_ID;
|
|
81
81
|
const mcpManager = this.ctx.mcpManager;
|
|
82
82
|
const cwd = this.ctx.sessionManager.getCwd();
|
|
83
|
+
const parentArtifactsDir = this.ctx.sessionManager.getArtifactsDir();
|
|
84
|
+
// Snapshot the parent session's local:// mapping when dispatching. The
|
|
85
|
+
// interactive SessionManager is mutable and may switch transcripts while
|
|
86
|
+
// this background tan is still running. Use the session-manager id (not
|
|
87
|
+
// `session.sessionId`, which can diverge after `/fresh` or a provider
|
|
88
|
+
// session override) so the tan resolves the same local root the parent's
|
|
89
|
+
// large-paste writes and `local://` reads use — notably the Windows
|
|
90
|
+
// short-root fallback keys `%TEMP%/omp-local/<id>` off this id.
|
|
91
|
+
const parentLocalSessionId = this.ctx.sessionManager.getSessionId();
|
|
92
|
+
const localProtocolOptions = {
|
|
93
|
+
getArtifactsDir: () => parentArtifactsDir,
|
|
94
|
+
getSessionId: () => parentLocalSessionId,
|
|
95
|
+
};
|
|
83
96
|
// Nest the clone inside the parent's artifact directory (like a subagent
|
|
84
97
|
// session) rather than as a top-level sibling, so it shares the parent's
|
|
85
98
|
// artifacts in place — no copy needed.
|
|
@@ -132,6 +145,7 @@ export class TanCommandController {
|
|
|
132
145
|
parentAgentId: ownerId,
|
|
133
146
|
agentRegistry,
|
|
134
147
|
disableExtensionDiscovery: true,
|
|
148
|
+
localProtocolOptions,
|
|
135
149
|
});
|
|
136
150
|
clone = created.session;
|
|
137
151
|
clone.sessionManager?.appendSessionInit?.({
|