@oh-my-pi/pi-coding-agent 15.11.6 → 15.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/dist/cli.js +431 -381
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/cli/bench-cli.d.ts +78 -0
  5. package/dist/types/collab/crypto.d.ts +12 -0
  6. package/dist/types/collab/guest.d.ts +21 -0
  7. package/dist/types/collab/host.d.ts +13 -0
  8. package/dist/types/collab/protocol.d.ts +100 -0
  9. package/dist/types/collab/relay-client.d.ts +22 -0
  10. package/dist/types/commands/bench.d.ts +29 -0
  11. package/dist/types/commands/join.d.ts +12 -0
  12. package/dist/types/config/model-resolver.d.ts +3 -2
  13. package/dist/types/config/settings-schema.d.ts +93 -1
  14. package/dist/types/edit/renderer.d.ts +1 -0
  15. package/dist/types/extensibility/slash-commands.d.ts +1 -11
  16. package/dist/types/modes/components/agent-hub.d.ts +13 -0
  17. package/dist/types/modes/components/collab-prompt-message.d.ts +10 -0
  18. package/dist/types/modes/components/hook-selector.d.ts +4 -6
  19. package/dist/types/modes/components/oauth-selector.d.ts +10 -1
  20. package/dist/types/modes/components/segment-track.d.ts +11 -6
  21. package/dist/types/modes/components/settings-selector.d.ts +8 -1
  22. package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
  23. package/dist/types/modes/components/status-line/component.d.ts +4 -1
  24. package/dist/types/modes/components/status-line/types.d.ts +9 -0
  25. package/dist/types/modes/components/tool-execution.d.ts +13 -9
  26. package/dist/types/modes/interactive-mode.d.ts +7 -0
  27. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +3 -0
  28. package/dist/types/modes/setup-wizard/scenes/types.d.ts +10 -1
  29. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +3 -0
  30. package/dist/types/modes/types.d.ts +8 -0
  31. package/dist/types/session/agent-session.d.ts +11 -0
  32. package/dist/types/session/session-manager.d.ts +21 -0
  33. package/dist/types/session/snapcompact-inline.d.ts +8 -3
  34. package/dist/types/slash-commands/builtin-registry.d.ts +9 -0
  35. package/dist/types/tools/bash.d.ts +2 -0
  36. package/dist/types/tools/eval-render.d.ts +1 -0
  37. package/dist/types/tools/renderers.d.ts +13 -0
  38. package/dist/types/tools/ssh.d.ts +1 -0
  39. package/package.json +14 -12
  40. package/scripts/bench-guard.ts +71 -0
  41. package/src/cli/args.ts +2 -0
  42. package/src/cli/bench-cli.ts +437 -0
  43. package/src/cli-commands.ts +2 -0
  44. package/src/collab/crypto.ts +57 -0
  45. package/src/collab/guest.ts +421 -0
  46. package/src/collab/host.ts +494 -0
  47. package/src/collab/protocol.ts +191 -0
  48. package/src/collab/relay-client.ts +216 -0
  49. package/src/commands/bench.ts +42 -0
  50. package/src/commands/join.ts +39 -0
  51. package/src/config/model-registry.ts +74 -19
  52. package/src/config/model-resolver.ts +36 -5
  53. package/src/config/settings-schema.ts +119 -1
  54. package/src/edit/renderer.ts +5 -0
  55. package/src/extensibility/slash-commands.ts +1 -97
  56. package/src/hindsight/client.ts +26 -1
  57. package/src/hindsight/state.ts +6 -2
  58. package/src/internal-urls/docs-index.generated.ts +4 -3
  59. package/src/main.ts +11 -2
  60. package/src/mcp/transports/stdio.ts +81 -7
  61. package/src/modes/components/agent-hub.ts +119 -22
  62. package/src/modes/components/assistant-message.ts +126 -6
  63. package/src/modes/components/collab-prompt-message.ts +30 -0
  64. package/src/modes/components/hook-selector.ts +4 -5
  65. package/src/modes/components/oauth-selector.ts +67 -7
  66. package/src/modes/components/segment-track.ts +44 -7
  67. package/src/modes/components/settings-selector.ts +27 -0
  68. package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
  69. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  70. package/src/modes/components/status-line/component.ts +21 -1
  71. package/src/modes/components/status-line/presets.ts +1 -1
  72. package/src/modes/components/status-line/segments.ts +13 -0
  73. package/src/modes/components/status-line/types.ts +10 -0
  74. package/src/modes/components/tips.txt +2 -1
  75. package/src/modes/components/tool-execution.ts +18 -10
  76. package/src/modes/controllers/input-controller.ts +80 -12
  77. package/src/modes/controllers/selector-controller.ts +6 -2
  78. package/src/modes/controllers/streaming-reveal.ts +7 -0
  79. package/src/modes/interactive-mode.ts +36 -4
  80. package/src/modes/setup-wizard/index.ts +1 -0
  81. package/src/modes/setup-wizard/scenes/glyph.ts +24 -6
  82. package/src/modes/setup-wizard/scenes/providers.ts +36 -2
  83. package/src/modes/setup-wizard/scenes/sign-in.ts +10 -1
  84. package/src/modes/setup-wizard/scenes/theme.ts +28 -1
  85. package/src/modes/setup-wizard/scenes/types.ts +10 -1
  86. package/src/modes/setup-wizard/scenes/web-search.ts +22 -6
  87. package/src/modes/setup-wizard/wizard-overlay.ts +38 -1
  88. package/src/modes/types.ts +8 -0
  89. package/src/modes/utils/context-usage.ts +1 -1
  90. package/src/modes/utils/ui-helpers.ts +7 -0
  91. package/src/prompts/bench.md +7 -0
  92. package/src/sdk.ts +240 -36
  93. package/src/session/agent-session.ts +22 -0
  94. package/src/session/session-manager.ts +44 -0
  95. package/src/session/snapcompact-inline.ts +20 -22
  96. package/src/slash-commands/builtin-registry.ts +210 -0
  97. package/src/tools/bash.ts +3 -0
  98. package/src/tools/eval-render.ts +4 -0
  99. package/src/tools/read.ts +38 -5
  100. package/src/tools/renderers.ts +13 -0
  101. package/src/tools/ssh.ts +3 -0
  102. package/src/tools/write.ts +13 -42
@@ -1,6 +1,6 @@
1
1
  import { type Component } from "@oh-my-pi/pi-tui";
2
2
  import type { AgentSession } from "../../../session/agent-session";
3
- import type { EffectiveStatusLineSettings, StatusLineSettings } from "./types";
3
+ import type { CollabStatus, EffectiveStatusLineSettings, StatusLineSettings } from "./types";
4
4
  export declare class StatusLineComponent implements Component {
5
5
  #private;
6
6
  private readonly session;
@@ -9,6 +9,8 @@ export declare class StatusLineComponent implements Component {
9
9
  getEffectiveSettingsForTest(): EffectiveStatusLineSettings;
10
10
  setAutoCompactEnabled(enabled: boolean): void;
11
11
  setSubagentCount(count: number): void;
12
+ /** Active subagent count as currently displayed (collab state mirroring). */
13
+ get subagentCount(): number;
12
14
  setSessionStartTime(time: number): void;
13
15
  setPlanModeStatus(status: {
14
16
  enabled: boolean;
@@ -21,6 +23,7 @@ export declare class StatusLineComponent implements Component {
21
23
  enabled: boolean;
22
24
  paused: boolean;
23
25
  } | undefined): void;
26
+ setCollabStatus(status: CollabStatus | null): void;
24
27
  setHookStatus(key: string, text: string | undefined): void;
25
28
  watchBranch(onBranchChange: () => void): void;
26
29
  dispose(): void;
@@ -1,6 +1,14 @@
1
+ import type { CollabSessionState } from "../../../collab/protocol";
1
2
  import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../../config/settings-schema";
2
3
  import type { AgentSession } from "../../../session/agent-session";
3
4
  export type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle };
5
+ /** Collab session indicator + (guest-only) host-state override for segments. */
6
+ export interface CollabStatus {
7
+ role: "host" | "guest";
8
+ participantCount: number;
9
+ /** Guest only: host footer snapshot that overrides locally computed values. */
10
+ stateOverride?: CollabSessionState | null;
11
+ }
4
12
  export interface StatusLineSegmentOptions {
5
13
  model?: {
6
14
  showThinkingLevel?: boolean;
@@ -50,6 +58,7 @@ export interface SegmentContext {
50
58
  enabled: boolean;
51
59
  paused: boolean;
52
60
  } | null;
61
+ collab: CollabStatus | null;
53
62
  usageStats: {
54
63
  input: number;
55
64
  output: number;
@@ -74,15 +74,19 @@ export declare class ToolExecutionComponent extends Container {
74
74
  isTranscriptBlockFinalized(): boolean;
75
75
  /**
76
76
  * Whether this still-live block's settled rows may enter native scrollback
77
- * (see `FinalizableBlock.isTranscriptBlockCommitStable`). A pending
78
- * collapsed preview is provisional: the tail-window streaming views
79
- * (edit/bash/eval caps) are re-anchored top-first by the result render, so
80
- * promoting their visually static head e.g. an edit preview idling on
81
- * its last frame while the apply + LSP pass runs would strand a stale
82
- * copy of the call box above the final block the moment the result lands.
83
- * Expanded pending blocks stream top-anchored append-shaped content whose
84
- * rows the result render preserves byte-stable (the over-tall write/eval
85
- * scrollback contract), so they stay commit-eligible. Displaceable waiting
77
+ * (see `FinalizableBlock.isTranscriptBlockCommitStable`). Classification is
78
+ * per renderer (`ToolRenderer.provisionalPendingPreview`): tail-window
79
+ * streaming views (edit's streamed-diff tail, bash/ssh command caps, eval
80
+ * cells) are re-anchored top-first by the result render, so promoting
81
+ * their visually static head e.g. an edit preview idling on its last
82
+ * frame while the apply + LSP pass runs would strand a stale copy of
83
+ * the call box above the final block the moment the result lands. Every
84
+ * other pending preview streams top-anchored append-shaped rows the
85
+ * result render preserves (a task call's context/assignment markdown, a
86
+ * write's content), so it stays commit-eligible — a call taller than the
87
+ * viewport scrolls into native history mid-stream instead of reading as
88
+ * cut off until the result. Expanded blocks always stream top-anchored
89
+ * (the over-tall write/eval scrollback contract). Displaceable waiting
86
90
  * polls are removed wholesale by the next poll and must never commit.
87
91
  */
88
92
  isTranscriptBlockCommitStable(): boolean;
@@ -3,6 +3,8 @@ import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
3
3
  import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
4
4
  import type { Component, EditorTheme } from "@oh-my-pi/pi-tui";
5
5
  import { Container, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
6
+ import type { CollabGuestLink } from "../collab/guest";
7
+ import type { CollabHost } from "../collab/host";
6
8
  import { KeybindingsManager } from "../config/keybindings";
7
9
  import { Settings } from "../config/settings";
8
10
  import type { ExtensionUIContext, ExtensionUIDialogOptions, ExtensionUISelectItem, ExtensionWidgetContent, ExtensionWidgetOptions } from "../extensibility/extensions";
@@ -25,6 +27,7 @@ import type { HookSelectorComponent, HookSelectorSlider } from "./components/hoo
25
27
  import { StatusLineComponent } from "./components/status-line";
26
28
  import type { ToolExecutionHandle } from "./components/tool-execution";
27
29
  import { TranscriptContainer } from "./components/transcript-container";
30
+ import { EventController } from "./controllers/event-controller";
28
31
  import { type LoopLimitRuntime } from "./loop-limit";
29
32
  import { OAuthManualInputManager } from "./oauth-manual-input";
30
33
  import type { ObservableSession } from "./session-observer-registry";
@@ -119,8 +122,12 @@ export declare class InteractiveMode implements InteractiveModeContext {
119
122
  fileSlashCommands: Set<string>;
120
123
  skillCommands: Map<string, string>;
121
124
  oauthManualInput: OAuthManualInputManager;
125
+ collabHost?: CollabHost;
126
+ collabGuest?: CollabGuestLink;
122
127
  readonly lspServers: LspStartupServerInfo[] | undefined;
123
128
  mcpManager?: MCPManager;
129
+ get eventController(): EventController;
130
+ get eventBus(): EventBus | undefined;
124
131
  constructor(session: AgentSession, version: string, changelogMarkdown?: string | undefined, setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void, lspServers?: LspStartupServerInfo[] | undefined, mcpManager?: MCPManager, eventBus?: EventBus, titleSystemPrompt?: string);
125
132
  playWelcomeIntro(): void;
126
133
  init(options?: InteractiveModeInitOptions): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { type SgrMouseEvent } from "@oh-my-pi/pi-tui";
1
2
  import type { SetupSceneHost, SetupTab } from "./types";
2
3
  /**
3
4
  * "Sign in" panel: lets the user authenticate one or more model providers via
@@ -15,5 +16,7 @@ export declare class SignInTab implements SetupTab {
15
16
  dispose(): void;
16
17
  invalidate(): void;
17
18
  handleInput(data: string): void;
19
+ /** Forward mouse to the provider selector; pointer is inert during an active login or code prompt. */
20
+ routeMouse(event: SgrMouseEvent, line: number, col: number): void;
18
21
  render(width: number): readonly string[];
19
22
  }
@@ -1,4 +1,4 @@
1
- import type { Component } from "@oh-my-pi/pi-tui";
1
+ import type { Component, SgrMouseEvent } from "@oh-my-pi/pi-tui";
2
2
  import type { InteractiveModeContext } from "../../types";
3
3
  export type SetupSceneResult = "done" | "skipped";
4
4
  export interface SetupSceneHost {
@@ -14,6 +14,13 @@ export interface SetupSceneController extends Component {
14
14
  onMount?(): void | Promise<void>;
15
15
  onUnmount?(): void;
16
16
  dispose?(): void;
17
+ /**
18
+ * Route an SGR mouse report (tracking is on while the wizard holds the
19
+ * alternate screen). `line`/`col` are 0-based within this controller's
20
+ * last rendered output. When absent, the wizard falls back to synthesizing
21
+ * arrow keys from wheel notches.
22
+ */
23
+ routeMouse?(event: SgrMouseEvent, line: number, col: number): void;
17
24
  }
18
25
  /**
19
26
  * A single panel inside a tabbed setup scene. The host scene owns the tab bar
@@ -32,6 +39,8 @@ export interface SetupTab {
32
39
  invalidate(): void;
33
40
  /** Called when the tab becomes active (including initial mount). */
34
41
  onActivate?(): void;
42
+ /** Mouse routing at tab-local coordinates; see {@link SetupSceneController.routeMouse}. */
43
+ routeMouse?(event: SgrMouseEvent, line: number, col: number): void;
35
44
  dispose(): void;
36
45
  }
37
46
  export interface SetupScene {
@@ -1,3 +1,4 @@
1
+ import { type SgrMouseEvent } from "@oh-my-pi/pi-tui";
1
2
  import type { SetupSceneHost, SetupTab } from "./types";
2
3
  /**
3
4
  * "Web search" panel: picks the provider the web_search tool should prefer and
@@ -14,6 +15,8 @@ export declare class WebSearchTab implements SetupTab {
14
15
  constructor(host: SetupSceneHost);
15
16
  onActivate(): void;
16
17
  handleInput(data: string): void;
18
+ /** Wheel moves the highlight; hover lights the row under the pointer; click confirms it. */
19
+ routeMouse(event: SgrMouseEvent, line: number, _col: number): void;
17
20
  invalidate(): void;
18
21
  dispose(): void;
19
22
  render(width: number): readonly string[];
@@ -2,6 +2,8 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
3
3
  import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
4
4
  import type { Component, Container, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
5
+ import type { CollabGuestLink } from "../collab/guest";
6
+ import type { CollabHost } from "../collab/host";
5
7
  import type { KeybindingsManager } from "../config/keybindings";
6
8
  import type { Settings } from "../config/settings";
7
9
  import type { ExtensionUIContext, ExtensionUIDialogOptions, ExtensionUISelectItem, ExtensionWidgetContent, ExtensionWidgetOptions } from "../extensibility/extensions";
@@ -13,6 +15,7 @@ import type { HistoryStorage } from "../session/history-storage";
13
15
  import type { SessionContext, SessionManager } from "../session/session-manager";
14
16
  import type { ShakeMode } from "../session/shake-types";
15
17
  import type { LspStartupServerInfo } from "../tools";
18
+ import type { EventBus } from "../utils/event-bus";
16
19
  import type { AssistantMessageComponent } from "./components/assistant-message";
17
20
  import type { BashExecutionComponent } from "./components/bash-execution";
18
21
  import type { CustomEditor } from "./components/custom-editor";
@@ -23,6 +26,7 @@ import type { HookSelectorComponent, HookSelectorOptions } from "./components/ho
23
26
  import type { StatusLineComponent } from "./components/status-line";
24
27
  import type { ToolExecutionHandle } from "./components/tool-execution";
25
28
  import type { TranscriptContainer } from "./components/transcript-container";
29
+ import type { EventController } from "./controllers/event-controller";
26
30
  import type { LoopLimitRuntime } from "./loop-limit";
27
31
  import type { OAuthManualInputManager } from "./oauth-manual-input";
28
32
  import type { Theme } from "./theme/theme";
@@ -84,6 +88,10 @@ export interface InteractiveModeContext {
84
88
  mcpManager?: MCPManager;
85
89
  lspServers?: LspStartupServerInfo[];
86
90
  titleSystemPrompt?: string;
91
+ collabHost?: CollabHost;
92
+ collabGuest?: CollabGuestLink;
93
+ eventController: EventController;
94
+ eventBus?: EventBus;
87
95
  isInitialized: boolean;
88
96
  isBashMode: boolean;
89
97
  toolOutputExpanded: boolean;
@@ -385,6 +385,9 @@ export declare class AgentSession {
385
385
  */
386
386
  subscribe(listener: AgentSessionEventListener): () => void;
387
387
  subscribeCommandMetadataChanged(listener: CommandMetadataChangedListener): () => void;
388
+ /** True once dispose() has begun; deferred background work (e.g. the deferred
389
+ * MCP discovery task in sdk.ts) must not touch the session past this point. */
390
+ get isDisposed(): boolean;
388
391
  /**
389
392
  * Synchronously mark the session as disposing so new work is rejected
390
393
  * immediately: Python/eval starts throw, queued asides are dropped, and the
@@ -443,6 +446,14 @@ export declare class AgentSession {
443
446
  */
444
447
  getAllToolNames(): string[];
445
448
  isMCPDiscoveryEnabled(): boolean;
449
+ /**
450
+ * Flip MCP discovery on after deferred discovery learns the real tool count.
451
+ * UI sessions resolve `tools.discoveryMode: "auto"` before MCP servers
452
+ * connect, so a large MCP toolset discovered later must be able to upgrade
453
+ * the session from the force-activate path to the discovery path. One-way:
454
+ * discovery is never downgraded mid-session.
455
+ */
456
+ enableMCPDiscovery(): void;
446
457
  getSelectedMCPToolNames(): string[];
447
458
  activateDiscoveredMCPTools(toolNames: string[]): Promise<string[]>;
448
459
  isToolDiscoveryEnabled(): boolean;
@@ -309,6 +309,12 @@ export declare class SessionManager {
309
309
  private sessionDir;
310
310
  private readonly persist;
311
311
  private readonly storage;
312
+ /**
313
+ * Collab replication tap: invoked for every appended entry with the
314
+ * in-memory (pre-blob-externalization) entry, so inline images survive.
315
+ * Failures are swallowed — a broadcast error must never break persistence.
316
+ */
317
+ onEntryAppended?: (entry: SessionEntry) => void;
312
318
  private constructor();
313
319
  /** Puts a binary blob into the blob store and returns the blob reference */
314
320
  putBlob(data: Buffer, options?: BlobPutOptions): Promise<BlobPutResult>;
@@ -434,6 +440,21 @@ export declare class SessionManager {
434
440
  */
435
441
  setSessionName(name: string, source?: "auto" | "user"): Promise<boolean>;
436
442
  _persist(entry: SessionEntry): void;
443
+ /**
444
+ * Append a foreign (host-authored) entry verbatim, preserving its
445
+ * `id`/`parentId` — no id minting. Used by collab guests to mirror the
446
+ * host session into the local replica file.
447
+ */
448
+ ingestReplicatedEntry(entry: SessionEntry): void;
449
+ /**
450
+ * Snapshot the session for collab replication: the live header plus a deep
451
+ * copy of every entry (the host mutates entries in place on
452
+ * truncation/rewrite paths, so guests must not share references).
453
+ */
454
+ snapshotForReplication(): {
455
+ header: SessionHeader;
456
+ entries: SessionEntry[];
457
+ };
437
458
  /** Append a message as child of current leaf, then advance leaf. Returns entry id.
438
459
  * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
439
460
  * Reason: we want these to be top-level entries in the session, not message session entries,
@@ -19,17 +19,21 @@ export type SnapcompactSystemPromptMode = "none" | "agents-md" | "all";
19
19
  export interface SnapcompactInlineOptions {
20
20
  renderSystemPrompt: SnapcompactSystemPromptMode;
21
21
  renderToolResults: boolean;
22
+ /** Frame variant override; `"auto"`/omitted picks the provider's eval winner. */
23
+ shape?: snapcompact.ShapeVariantName | "auto";
22
24
  }
23
25
  /** Tool-result swap candidate, in context order. */
24
26
  export interface InlineToolResultCandidate {
25
- /** toolCallId stable identity for render caching and application. */
27
+ /** Stable identifier for rendering cache key and applying the swap. */
26
28
  id: string;
27
- /** Token count of the joined text blocks (0 when empty or image-carrying). */
29
+ /** Token count of the joined text blocks (0 for empty or image-carrying). */
28
30
  textTokens: number;
29
- /** Frames needed to render the text (0 = empty or below the token floor). */
31
+ /** Frames needed to render the text (0 = empty, below floor, image-carrying, or error). */
30
32
  frames: number;
31
33
  /** Already carries an image (screenshot etc.) — never re-imaged. */
32
34
  hasImage: boolean;
35
+ /** Error tool results must stay text-only for provider API validation. */
36
+ isError?: boolean;
33
37
  }
34
38
  export interface InlineSystemPromptCandidate {
35
39
  textTokens: number;
@@ -71,6 +75,7 @@ export interface InlineMessageView {
71
75
  role: string;
72
76
  toolCallId?: string;
73
77
  content?: unknown;
78
+ isError?: boolean;
74
79
  }
75
80
  export interface SnapcompactSavingsEstimate {
76
81
  /** Frames only ship on models that accept image input. */
@@ -1,3 +1,4 @@
1
+ import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
1
2
  import type { BuiltinSlashCommand, ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime, SlashCommandSpec, TuiSlashCommandRuntime } from "./types";
2
3
  export type { BuiltinSlashCommand, SubcommandDef } from "./types";
3
4
  /** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
@@ -5,6 +6,14 @@ export type BuiltinSlashCommandRuntime = TuiSlashCommandRuntime;
5
6
  export declare const BUILTIN_SLASH_COMMAND_RESERVED_NAMES: ReadonlySet<string>;
6
7
  /** Builtin command metadata used for slash-command autocomplete and help text. */
7
8
  export declare const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand>;
9
+ /**
10
+ * Materialized builtin slash commands with completion functions derived from
11
+ * declarative subcommand/hint definitions.
12
+ */
13
+ export declare const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand & {
14
+ getArgumentCompletions?: (prefix: string) => AutocompleteItem[] | null;
15
+ getInlineHint?: (argumentText: string) => string | null;
16
+ }>;
8
17
  /**
9
18
  * Unified registry exposed for cross-mode tooling. Each spec carries at least
10
19
  * one of `handle` / `handleTui`. The TUI dispatcher prefers `handleTui`; the
@@ -124,6 +124,7 @@ export declare function createShellRenderer<TArgs>(config: ShellRendererConfig<T
124
124
  }, uiTheme: Theme, args?: TArgs): Component;
125
125
  mergeCallAndResult: boolean;
126
126
  inline: boolean;
127
+ provisionalPendingPreview: boolean;
127
128
  };
128
129
  export declare const bashToolRenderer: {
129
130
  renderCall(args: BashRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component;
@@ -139,5 +140,6 @@ export declare const bashToolRenderer: {
139
140
  }, uiTheme: Theme, args?: BashRenderArgs | undefined): Component;
140
141
  mergeCallAndResult: boolean;
141
142
  inline: boolean;
143
+ provisionalPendingPreview: boolean;
142
144
  };
143
145
  export {};
@@ -48,5 +48,6 @@ export declare const evalToolRenderer: {
48
48
  }, uiTheme: Theme, _args?: EvalRenderArgs): Component;
49
49
  mergeCallAndResult: boolean;
50
50
  inline: boolean;
51
+ provisionalPendingPreview: boolean;
51
52
  };
52
53
  export {};
@@ -21,5 +21,18 @@ export type ToolRenderer = {
21
21
  mergeCallAndResult?: boolean;
22
22
  /** Render without background box, inline in the response flow */
23
23
  inline?: boolean;
24
+ /**
25
+ * Collapsed pending preview is provisional — a tail-window or otherwise
26
+ * re-anchored view the result render replaces wholesale (an edit's
27
+ * streamed-diff tail, bash/ssh command caps, eval cells whose outputs
28
+ * interleave under each cell). Its rows must never commit to native
29
+ * scrollback mid-run; see
30
+ * `ToolExecutionComponent.isTranscriptBlockCommitStable`. Absent = the
31
+ * pending preview streams top-anchored append-shaped rows the result
32
+ * render preserves (task context/assignment, write content), which stay
33
+ * commit-eligible so a call taller than the viewport scrolls into history
34
+ * instead of reading as cut off.
35
+ */
36
+ provisionalPendingPreview?: boolean;
24
37
  };
25
38
  export declare const toolRenderers: Record<string, ToolRenderer>;
@@ -65,5 +65,6 @@ export declare const sshToolRenderer: {
65
65
  renderContext?: SshRenderContext;
66
66
  }, uiTheme: Theme, args?: SshRenderArgs): Component;
67
67
  mergeCallAndResult: boolean;
68
+ provisionalPendingPreview: boolean;
68
69
  };
69
70
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.11.6",
4
+ "version": "15.11.8",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -41,22 +41,24 @@
41
41
  "format-prompts": "bun scripts/format-prompts.ts",
42
42
  "generate-docs-index": "bun scripts/generate-docs-index.ts",
43
43
  "prepack": "bun scripts/generate-docs-index.ts && bun scripts/bundle-dist.ts",
44
- "generate-template": "bun scripts/generate-template.ts"
44
+ "generate-template": "bun scripts/generate-template.ts",
45
+ "bench:guard": "bun scripts/bench-guard.ts"
45
46
  },
46
47
  "dependencies": {
47
48
  "@agentclientprotocol/sdk": "0.22.1",
48
49
  "@babel/parser": "^7.29.7",
49
50
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.11.6",
51
- "@oh-my-pi/omp-stats": "15.11.6",
52
- "@oh-my-pi/pi-agent-core": "15.11.6",
53
- "@oh-my-pi/pi-ai": "15.11.6",
54
- "@oh-my-pi/pi-catalog": "15.11.6",
55
- "@oh-my-pi/pi-mnemopi": "15.11.6",
56
- "@oh-my-pi/pi-natives": "15.11.6",
57
- "@oh-my-pi/pi-tui": "15.11.6",
58
- "@oh-my-pi/pi-utils": "15.11.6",
59
- "@oh-my-pi/snapcompact": "15.11.6",
51
+ "@oh-my-pi/hashline": "15.11.8",
52
+ "@oh-my-pi/omp-stats": "15.11.8",
53
+ "@oh-my-pi/pi-agent-core": "15.11.8",
54
+ "@oh-my-pi/pi-ai": "15.11.8",
55
+ "@oh-my-pi/pi-catalog": "15.11.8",
56
+ "@oh-my-pi/pi-mnemopi": "15.11.8",
57
+ "@oh-my-pi/pi-natives": "15.11.8",
58
+ "@oh-my-pi/pi-tui": "15.11.8",
59
+ "@oh-my-pi/pi-utils": "15.11.8",
60
+ "@oh-my-pi/pi-wire": "15.11.8",
61
+ "@oh-my-pi/snapcompact": "15.11.8",
60
62
  "@opentelemetry/api": "^1.9.1",
61
63
  "@opentelemetry/context-async-hooks": "^2.7.1",
62
64
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Boot-time regression guard (Phase A1 of the boot/TUI perf work).
4
+ *
5
+ * Re-runs the `PI_TIMING=x` cold-boot benchmark under hyperfine and fails when
6
+ * the median regresses past `baseline * THRESHOLD`. `PI_TIMING=x` runs the full
7
+ * pre-paint chain in `runRootCommand` and then `process.exit(0)`, so the
8
+ * never-exiting interactive launch becomes a terminating, benchmarkable boot.
9
+ *
10
+ * Boot wall-clock is MACHINE-RELATIVE: a baseline captured on one machine is
11
+ * meaningless on another (and on CI). This is a LOCAL guard — regenerate the
12
+ * baseline on the machine you measure on, then compare on that same machine.
13
+ * It is intentionally NOT wired into CI for that reason.
14
+ *
15
+ * bun scripts/bench-guard.ts --update # capture/refresh the baseline
16
+ * bun scripts/bench-guard.ts # measure + compare; exit 1 on regression
17
+ *
18
+ * Requires `hyperfine` on PATH.
19
+ */
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+
23
+ const THRESHOLD = 1.05; // 5% regression budget
24
+ const BASELINE_PATH = path.join(import.meta.dir, "..", "bench", "boot-baseline.json");
25
+ const BENCH_COMMAND = "PI_TIMING=x bun src/cli.ts";
26
+ const cwd = path.join(import.meta.dir, "..");
27
+
28
+ function medianOf(hyperfineJson: string): number {
29
+ const parsed = JSON.parse(hyperfineJson) as { results: Array<{ mean: number; median?: number }> };
30
+ const result = parsed.results[0];
31
+ if (!result) throw new Error("hyperfine produced no result");
32
+ return result.median ?? result.mean;
33
+ }
34
+
35
+ async function measure(): Promise<{ seconds: number; raw: string }> {
36
+ const tmp = path.join(import.meta.dir, "..", "bench", `.boot-run-${Date.now()}.json`);
37
+ const proc = Bun.spawn(["hyperfine", "--warmup", "3", "--min-runs", "10", "--export-json", tmp, BENCH_COMMAND], {
38
+ cwd,
39
+ stdout: "inherit",
40
+ stderr: "inherit",
41
+ });
42
+ const code = await proc.exited;
43
+ if (code !== 0) throw new Error(`hyperfine exited ${code}`);
44
+ const raw = await Bun.file(tmp).text();
45
+ fs.rmSync(tmp, { force: true });
46
+ return { seconds: medianOf(raw), raw };
47
+ }
48
+
49
+ const update = process.argv.includes("--update");
50
+ const { seconds, raw } = await measure();
51
+
52
+ if (update) {
53
+ fs.mkdirSync(path.dirname(BASELINE_PATH), { recursive: true });
54
+ await Bun.write(BASELINE_PATH, raw);
55
+ console.log(`Baseline updated: ${(seconds * 1000).toFixed(0)}ms median -> ${BASELINE_PATH}`);
56
+ process.exit(0);
57
+ }
58
+
59
+ if (!fs.existsSync(BASELINE_PATH)) {
60
+ console.error("No baseline found. Run `bun scripts/bench-guard.ts --update` on this machine first.");
61
+ process.exit(2);
62
+ }
63
+
64
+ const baseline = medianOf(await Bun.file(BASELINE_PATH).text());
65
+ const ratio = seconds / baseline;
66
+ const verdict = ratio > THRESHOLD ? "REGRESSION" : "ok";
67
+ console.log(
68
+ `boot median: ${(seconds * 1000).toFixed(0)}ms vs baseline ${(baseline * 1000).toFixed(0)}ms ` +
69
+ `(${((ratio - 1) * 100).toFixed(1)}%, budget ${((THRESHOLD - 1) * 100).toFixed(0)}%) -> ${verdict}`,
70
+ );
71
+ process.exit(ratio > THRESHOLD ? 1 : 0);
package/src/cli/args.ts CHANGED
@@ -32,6 +32,8 @@ export interface Args {
32
32
  sessionDir?: string;
33
33
  providerSessionId?: string;
34
34
  fork?: string;
35
+ /** Collab link to join at startup (set by the `join` subcommand; no CLI flag). */
36
+ join?: string;
35
37
  models?: string[];
36
38
  tools?: string[];
37
39
  noTools?: boolean;