@oh-my-pi/pi-coding-agent 15.11.7 → 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 (61) hide show
  1. package/CHANGELOG.md +30 -2
  2. package/dist/cli.js +363 -356
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/collab/crypto.d.ts +12 -0
  5. package/dist/types/collab/guest.d.ts +21 -0
  6. package/dist/types/collab/host.d.ts +13 -0
  7. package/dist/types/collab/protocol.d.ts +100 -0
  8. package/dist/types/collab/relay-client.d.ts +22 -0
  9. package/dist/types/commands/join.d.ts +12 -0
  10. package/dist/types/config/settings-schema.d.ts +21 -1
  11. package/dist/types/extensibility/slash-commands.d.ts +1 -11
  12. package/dist/types/modes/components/agent-hub.d.ts +13 -0
  13. package/dist/types/modes/components/collab-prompt-message.d.ts +10 -0
  14. package/dist/types/modes/components/hook-selector.d.ts +4 -6
  15. package/dist/types/modes/components/segment-track.d.ts +11 -6
  16. package/dist/types/modes/components/status-line/component.d.ts +4 -1
  17. package/dist/types/modes/components/status-line/types.d.ts +9 -0
  18. package/dist/types/modes/interactive-mode.d.ts +7 -0
  19. package/dist/types/modes/types.d.ts +8 -0
  20. package/dist/types/session/agent-session.d.ts +11 -0
  21. package/dist/types/session/session-manager.d.ts +21 -0
  22. package/dist/types/session/snapcompact-inline.d.ts +6 -3
  23. package/dist/types/slash-commands/builtin-registry.d.ts +9 -0
  24. package/package.json +14 -12
  25. package/scripts/bench-guard.ts +71 -0
  26. package/src/cli/args.ts +2 -0
  27. package/src/cli-commands.ts +1 -0
  28. package/src/collab/crypto.ts +57 -0
  29. package/src/collab/guest.ts +421 -0
  30. package/src/collab/host.ts +494 -0
  31. package/src/collab/protocol.ts +191 -0
  32. package/src/collab/relay-client.ts +216 -0
  33. package/src/commands/join.ts +39 -0
  34. package/src/config/model-registry.ts +22 -14
  35. package/src/config/settings-schema.ts +27 -1
  36. package/src/extensibility/slash-commands.ts +1 -97
  37. package/src/internal-urls/docs-index.generated.ts +3 -2
  38. package/src/main.ts +11 -2
  39. package/src/modes/components/agent-hub.ts +119 -22
  40. package/src/modes/components/assistant-message.ts +126 -6
  41. package/src/modes/components/collab-prompt-message.ts +30 -0
  42. package/src/modes/components/hook-selector.ts +4 -5
  43. package/src/modes/components/segment-track.ts +44 -7
  44. package/src/modes/components/status-line/component.ts +21 -1
  45. package/src/modes/components/status-line/presets.ts +1 -1
  46. package/src/modes/components/status-line/segments.ts +13 -0
  47. package/src/modes/components/status-line/types.ts +10 -0
  48. package/src/modes/components/tips.txt +2 -1
  49. package/src/modes/controllers/input-controller.ts +72 -6
  50. package/src/modes/controllers/selector-controller.ts +2 -0
  51. package/src/modes/controllers/streaming-reveal.ts +7 -0
  52. package/src/modes/interactive-mode.ts +12 -4
  53. package/src/modes/types.ts +8 -0
  54. package/src/modes/utils/ui-helpers.ts +7 -0
  55. package/src/sdk.ts +239 -36
  56. package/src/session/agent-session.ts +17 -0
  57. package/src/session/session-manager.ts +44 -0
  58. package/src/session/snapcompact-inline.ts +9 -3
  59. package/src/slash-commands/builtin-registry.ts +210 -0
  60. package/src/tools/read.ts +38 -5
  61. package/src/tools/write.ts +13 -42
@@ -26,6 +26,8 @@ export interface Args {
26
26
  sessionDir?: string;
27
27
  providerSessionId?: string;
28
28
  fork?: string;
29
+ /** Collab link to join at startup (set by the `join` subcommand; no CLI flag). */
30
+ join?: string;
29
31
  models?: string[];
30
32
  tools?: string[];
31
33
  noTools?: boolean;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * AES-256-GCM sealing for collab frames.
3
+ *
4
+ * The room key lives only in the link fragment; the relay sees opaque bytes.
5
+ * Sealed layout: `[12B IV][ciphertext+tag]`.
6
+ */
7
+ import type { CollabFrame } from "./protocol";
8
+ export declare function generateRoomKey(): Uint8Array;
9
+ export declare function importRoomKey(raw: Uint8Array): Promise<CryptoKey>;
10
+ export declare function seal(key: CryptoKey, frame: CollabFrame): Promise<Uint8Array>;
11
+ /** Inverse of {@link seal}. Throws on auth failure or malformed input. */
12
+ export declare function open(key: CryptoKey, data: Uint8Array): Promise<CollabFrame>;
@@ -0,0 +1,21 @@
1
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+ import type { AgentHubRemote } from "../modes/components/agent-hub";
3
+ import type { InteractiveModeContext } from "../modes/types";
4
+ import { AgentRegistry } from "../registry/agent-registry";
5
+ import { type CollabSessionState } from "./protocol";
6
+ /** Commands a guest may run locally; everything else is host-only. */
7
+ export declare const COLLAB_GUEST_ALLOWED_COMMANDS: Record<string, true>;
8
+ export declare class CollabGuestLink {
9
+ #private;
10
+ state: CollabSessionState | null;
11
+ /** Local mirror of the host's agent ecosystem (refs carry `session: null`). */
12
+ readonly agentRegistry: AgentRegistry;
13
+ /** Agent Hub actions routed to the host over the wire. */
14
+ get hubRemote(): AgentHubRemote;
15
+ constructor(ctx: InteractiveModeContext);
16
+ join(link: string): Promise<void>;
17
+ /** User-initiated leave (or post-disconnect cleanup): restore the previous session. */
18
+ leave(_reason: string): Promise<void>;
19
+ sendPrompt(text: string, images?: ImageContent[]): void;
20
+ sendAbort(): void;
21
+ }
@@ -0,0 +1,13 @@
1
+ import type { InteractiveModeContext } from "../modes/types";
2
+ import { type CollabParticipant } from "./protocol";
3
+ /** Display name for this process's user in collab sessions. */
4
+ export declare function collabDisplayName(ctx: InteractiveModeContext): string;
5
+ export declare class CollabHost {
6
+ #private;
7
+ constructor(ctx: InteractiveModeContext);
8
+ get link(): string;
9
+ get participants(): CollabParticipant[];
10
+ start(relayUrl: string): Promise<void>;
11
+ /** Broadcast a goodbye, detach all taps, and close the socket. */
12
+ stop(reason: string): Promise<void>;
13
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Collab live-session wire protocol.
3
+ *
4
+ * Hub topology: the host is authoritative, guests never peer. All session
5
+ * payloads (`CollabFrame`) travel AES-256-GCM sealed; the relay only sees the
6
+ * plaintext envelope (`[4B uint32 BE peerId][sealed payload]`) plus TEXT JSON
7
+ * control messages that carry no session data.
8
+ */
9
+ import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
10
+ import type { BusChannel, GuestFrame, ParsedCollabLink, Participant, SessionState, AgentSnapshot as WireAgentSnapshot } from "@oh-my-pi/pi-wire";
11
+ import { DEFAULT_RELAY_URL, ENVELOPE_HEADER_LENGTH, ROOM_ID_BYTES } from "@oh-my-pi/pi-wire";
12
+ import type { ContextUsage } from "../extensibility/extensions/types";
13
+ import type { AgentSessionEvent } from "../session/agent-session";
14
+ import type { SessionEntry, SessionHeader } from "../session/session-manager";
15
+ export type { CollabPromptDetails, ParsedCollabLink, RelayControlMessage, RelayControlToGuest, RelayControlToHost, } from "@oh-my-pi/pi-wire";
16
+ export { COLLAB_PROMPT_MESSAGE_TYPE, COLLAB_PROTO } from "@oh-my-pi/pi-wire";
17
+ export { DEFAULT_RELAY_URL, ENVELOPE_HEADER_LENGTH, ROOM_ID_BYTES };
18
+ export type CollabParticipant = Participant;
19
+ export type AgentSnapshot = WireAgentSnapshot;
20
+ /** Debounced footer snapshot broadcast by the host. */
21
+ export type CollabSessionState = SessionState & {
22
+ /**
23
+ * Host model (full catalog object). Guests apply it to their replica
24
+ * agent state so model display and context-window math are native.
25
+ */
26
+ model?: Model;
27
+ /** Host status-line context numbers (guest system prompt/tools differ, so local estimates drift). */
28
+ contextUsage?: ContextUsage;
29
+ };
30
+ /**
31
+ * Encrypted payload frames (inside AES-GCM, JSON). The wire package pins the
32
+ * JSON skeleton (`WireFrame`); host-side frames carry the rich session types
33
+ * that serialize into those shapes.
34
+ */
35
+ export type CollabFrame = Exclude<GuestFrame, {
36
+ t: "prompt";
37
+ }> | {
38
+ t: "prompt";
39
+ text: string;
40
+ images?: ImageContent[];
41
+ } | {
42
+ t: "welcome";
43
+ proto: number;
44
+ header: SessionHeader;
45
+ entries: SessionEntry[];
46
+ state: CollabSessionState;
47
+ agents: AgentSnapshot[];
48
+ } | {
49
+ t: "entry";
50
+ entry: SessionEntry;
51
+ } | {
52
+ t: "event";
53
+ event: AgentSessionEvent;
54
+ } | {
55
+ t: "state";
56
+ state: CollabSessionState;
57
+ }
58
+ /** Mirrored EventBus traffic (task subagent lifecycle/progress channels only). */
59
+ | {
60
+ t: "bus";
61
+ channel: BusChannel;
62
+ data: unknown;
63
+ }
64
+ /** Full agent-registry snapshot (debounced on registry change). */
65
+ | {
66
+ t: "agents";
67
+ agents: AgentSnapshot[];
68
+ }
69
+ /** Targeted reply to fetch-transcript; `text` is decoded JSONL from `fromByte`, `newSize` the next offset base. */
70
+ | {
71
+ t: "transcript";
72
+ reqId: number;
73
+ text: string;
74
+ newSize: number;
75
+ error?: string;
76
+ } | {
77
+ t: "bye";
78
+ reason: string;
79
+ } | {
80
+ t: "error";
81
+ message: string;
82
+ };
83
+ export declare function packEnvelope(peerId: number, sealed: Uint8Array): Uint8Array;
84
+ export declare function unpackEnvelope(data: Uint8Array): {
85
+ peerId: number;
86
+ payload: Uint8Array;
87
+ } | null;
88
+ /** Rewrite the peerId in place without copying the payload. */
89
+ export declare function rewriteEnvelopePeer(data: Uint8Array, peerId: number): void;
90
+ export declare function generateRoomId(): string;
91
+ /**
92
+ * Render the shareable link. Compact forms: the default relay collapses to
93
+ * `<roomId>#<key>`, other wss relays drop the scheme (`host[:port]/r/…`);
94
+ * only localhost ws:// links keep their full URL so parsing cannot
95
+ * mis-infer wss.
96
+ */
97
+ export declare function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Array): string;
98
+ export declare function parseCollabLink(link: string): ParsedCollabLink | {
99
+ error: string;
100
+ };
@@ -0,0 +1,22 @@
1
+ import type { CollabFrame, RelayControlMessage } from "./protocol";
2
+ export interface CollabSocketOptions {
3
+ /** wss://host[:port]/r/<roomId> — no query string. */
4
+ wsUrl: string;
5
+ role: "host" | "guest";
6
+ key: CryptoKey;
7
+ }
8
+ export declare class CollabSocket {
9
+ #private;
10
+ /** Fires after every successful (re)connect. */
11
+ onOpen?: () => void;
12
+ onFrame?: (frame: CollabFrame, fromPeer: number) => void;
13
+ onControl?: (msg: RelayControlMessage) => void;
14
+ /** Fires once per terminal close (intentional, fatal code, or bad key). willReconnect=true for transient drops that will retry. */
15
+ onClose?: (reason: string, willReconnect: boolean) => void;
16
+ constructor(opts: CollabSocketOptions);
17
+ get isOpen(): boolean;
18
+ connect(): void;
19
+ send(frame: CollabFrame, targetPeer?: number): void;
20
+ /** Intentional close: clears any retry timer, suppresses reconnect. A later connect() starts fresh. */
21
+ close(): void;
22
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from "@oh-my-pi/pi-utils/cli";
2
+ export default class Join extends Command {
3
+ static description: string;
4
+ static args: {
5
+ link: import("@oh-my-pi/pi-utils/cli").ArgDescriptor & {
6
+ description: string;
7
+ required: true;
8
+ };
9
+ };
10
+ static examples: string[];
11
+ run(): Promise<void>;
12
+ }
@@ -33,7 +33,7 @@ export declare const TAB_METADATA: Record<SettingTab, {
33
33
  */
34
34
  export declare const TAB_GROUPS: Record<SettingTab, readonly string[]>;
35
35
  /** Status line segment identifiers */
36
- export type StatusLineSegmentId = "pi" | "model" | "mode" | "path" | "git" | "pr" | "subagents" | "token_in" | "token_out" | "token_total" | "token_rate" | "cost" | "context_pct" | "context_total" | "time_spent" | "time" | "session" | "hostname" | "cache_read" | "cache_write" | "cache_hit" | "session_name" | "usage";
36
+ export type StatusLineSegmentId = "pi" | "model" | "mode" | "path" | "git" | "pr" | "subagents" | "token_in" | "token_out" | "token_total" | "token_rate" | "cost" | "context_pct" | "context_total" | "time_spent" | "time" | "session" | "hostname" | "cache_read" | "cache_write" | "cache_hit" | "session_name" | "usage" | "collab";
37
37
  /** Submenu choice metadata. */
38
38
  export type SubmenuOption<V extends string = string> = {
39
39
  value: V;
@@ -1394,6 +1394,26 @@ export declare const SETTINGS_SCHEMA: {
1394
1394
  readonly description: "Notify when the ask tool is waiting for input";
1395
1395
  };
1396
1396
  };
1397
+ readonly "collab.relayUrl": {
1398
+ readonly type: "string";
1399
+ readonly default: "wss://relay.omp.sh";
1400
+ readonly ui: {
1401
+ readonly tab: "interaction";
1402
+ readonly group: "Collab";
1403
+ readonly label: "Relay URL";
1404
+ readonly description: "Relay used by /collab (wss://host[:port]; self-host with the omp-collab-relay service)";
1405
+ };
1406
+ };
1407
+ readonly "collab.displayName": {
1408
+ readonly type: "string";
1409
+ readonly default: "";
1410
+ readonly ui: {
1411
+ readonly tab: "interaction";
1412
+ readonly group: "Collab";
1413
+ readonly label: "Display Name";
1414
+ readonly description: "Name shown to other collab participants (default: OS username)";
1415
+ };
1416
+ };
1397
1417
  readonly "stt.enabled": {
1398
1418
  readonly type: "boolean";
1399
1419
  readonly default: false;
@@ -1,5 +1,3 @@
1
- import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
2
- import { type BuiltinSlashCommand } from "../slash-commands/builtin-registry";
3
1
  export type SlashCommandSource = "extension" | "prompt" | "skill";
4
2
  export type SlashCommandLocation = "user" | "project" | "path";
5
3
  export interface SlashCommandInfo {
@@ -9,15 +7,7 @@ export interface SlashCommandInfo {
9
7
  location?: SlashCommandLocation;
10
8
  path?: string;
11
9
  }
12
- export type { BuiltinSlashCommand, SubcommandDef } from "../slash-commands/builtin-registry";
13
- /**
14
- * Materialized builtin slash commands with completion functions derived from
15
- * declarative subcommand/hint definitions.
16
- */
17
- export declare const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand & {
18
- getArgumentCompletions?: (prefix: string) => AutocompleteItem[] | null;
19
- getInlineHint?: (argumentText: string) => string | null;
20
- }>;
10
+ export type { BuiltinSlashCommand, SubcommandDef } from "../slash-commands/types";
21
11
  /**
22
12
  * Represents a custom slash command loaded from a file
23
13
  */
@@ -4,6 +4,17 @@ import { IrcBus } from "../../irc/bus";
4
4
  import { AgentLifecycleManager } from "../../registry/agent-lifecycle";
5
5
  import { AgentRegistry } from "../../registry/agent-registry";
6
6
  import type { SessionObserverRegistry } from "../session-observer-registry";
7
+ /** Guest-side proxy for hub actions executed on the collab host. */
8
+ export interface AgentHubRemote {
9
+ chat(id: string, text: string): void;
10
+ kill(id: string): void;
11
+ revive(id: string): void;
12
+ /** Mirrors readFileIncremental: text from fromByte (complete JSONL lines), newSize = next fromByte base; null = unavailable. */
13
+ readTranscript(id: string, fromByte: number): Promise<{
14
+ text: string;
15
+ newSize: number;
16
+ } | null>;
17
+ }
7
18
  export interface AgentHubDeps {
8
19
  /** Progress/status snapshot source (task lifecycle + progress channels). */
9
20
  observers: SessionObserverRegistry;
@@ -17,6 +28,8 @@ export interface AgentHubDeps {
17
28
  lifecycle?: AgentLifecycleManager;
18
29
  /** Injectable for tests; defaults to the process-global bus. */
19
30
  irc?: IrcBus;
31
+ /** Collab guest: route actions/transcripts to the host instead of local sessions. */
32
+ remote?: AgentHubRemote;
20
33
  }
21
34
  export declare class AgentHubOverlayComponent extends Container {
22
35
  #private;
@@ -0,0 +1,10 @@
1
+ import { Container } from "@oh-my-pi/pi-tui";
2
+ import type { CollabPromptDetails } from "../../collab/protocol";
3
+ import type { CustomMessage } from "../../session/messages";
4
+ /**
5
+ * Renders a collab guest prompt on every participant's transcript: a
6
+ * user-message-styled bubble prefixed with the author's name.
7
+ */
8
+ export declare class CollabPromptMessageComponent extends Container {
9
+ constructor(message: CustomMessage<CollabPromptDetails>);
10
+ }
@@ -3,14 +3,12 @@
3
3
  * Displays a list of string options with keyboard navigation.
4
4
  */
5
5
  import { Container, type TUI } from "@oh-my-pi/pi-tui";
6
- import { type ThemeColor } from "../../modes/theme/theme";
7
- /** One segment of a {@link HookSelectorSlider} a label, its accent color, and
8
- * an optional detail line (e.g. the resolved model name) shown beneath the
9
- * track while the segment is active. */
6
+ /** One segment of a {@link HookSelectorSlider} a label and an optional
7
+ * detail line (e.g. the resolved model name) shown beneath the track while
8
+ * the segment is active. Segment colors come from the track's theme palette,
9
+ * assigned by position. */
10
10
  export interface HookSelectorSliderSegment {
11
11
  label: string;
12
- /** Theme color for the segment label; defaults to `accent`. */
13
- color?: ThemeColor;
14
12
  /** Secondary line rendered under the track when this segment is selected. */
15
13
  detail?: string;
16
14
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Shared renderer for a horizontal row of colored "segments" styled after the
3
- * status line: each segment shows in its own accent, the active one is filled
4
- * as a powerline chip (its accent as the background, a luminance-matched label,
5
- * flanked by triangle caps) and the rest are plain colored labels joined by a
6
- * thin separator.
3
+ * status line: each segment is colored by its track position from the theme's
4
+ * own palette, the active one is filled as a powerline chip (its color as the
5
+ * background, a luminance-matched label, flanked by triangle caps) and the
6
+ * rest are plain colored labels joined by a thin separator.
7
7
  *
8
8
  * Used by the plan-mode model-tier slider ({@link HookSelectorComponent}) and
9
9
  * the ctrl+p role-cycle status so both surfaces read identically.
@@ -11,9 +11,14 @@
11
11
  import { type ThemeColor } from "../theme/theme";
12
12
  export interface TrackSegment {
13
13
  label: string;
14
- /** Theme color for the segment; defaults to `accent`. */
15
- color?: ThemeColor;
16
14
  }
15
+ /**
16
+ * Resolve up to `count` theme colors that render distinctly under the active
17
+ * theme, in candidate preference order. May return fewer than `count` when the
18
+ * theme has fewer distinct hues (e.g. monochrome themes) — callers wrap with
19
+ * modulo. Never returns an empty array: `accent` always resolves.
20
+ */
21
+ export declare function resolveSegmentPalette(count: number): ThemeColor[];
17
22
  /**
18
23
  * Render `segments` as a colored chip track with `activeIndex` filled. Returns
19
24
  * a single line of styled text with no surrounding caption or arrows — callers
@@ -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;
@@ -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>;
@@ -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,
@@ -24,14 +24,16 @@ export interface SnapcompactInlineOptions {
24
24
  }
25
25
  /** Tool-result swap candidate, in context order. */
26
26
  export interface InlineToolResultCandidate {
27
- /** toolCallId stable identity for render caching and application. */
27
+ /** Stable identifier for rendering cache key and applying the swap. */
28
28
  id: string;
29
- /** 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). */
30
30
  textTokens: number;
31
- /** 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). */
32
32
  frames: number;
33
33
  /** Already carries an image (screenshot etc.) — never re-imaged. */
34
34
  hasImage: boolean;
35
+ /** Error tool results must stay text-only for provider API validation. */
36
+ isError?: boolean;
35
37
  }
36
38
  export interface InlineSystemPromptCandidate {
37
39
  textTokens: number;
@@ -73,6 +75,7 @@ export interface InlineMessageView {
73
75
  role: string;
74
76
  toolCallId?: string;
75
77
  content?: unknown;
78
+ isError?: boolean;
76
79
  }
77
80
  export interface SnapcompactSavingsEstimate {
78
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
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.7",
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.7",
51
- "@oh-my-pi/omp-stats": "15.11.7",
52
- "@oh-my-pi/pi-agent-core": "15.11.7",
53
- "@oh-my-pi/pi-ai": "15.11.7",
54
- "@oh-my-pi/pi-catalog": "15.11.7",
55
- "@oh-my-pi/pi-mnemopi": "15.11.7",
56
- "@oh-my-pi/pi-natives": "15.11.7",
57
- "@oh-my-pi/pi-tui": "15.11.7",
58
- "@oh-my-pi/pi-utils": "15.11.7",
59
- "@oh-my-pi/snapcompact": "15.11.7",
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",