@oh-my-pi/pi-coding-agent 16.0.9 → 16.0.11

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 (110) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/cli.js +3402 -3443
  3. package/dist/types/advisor/index.d.ts +1 -0
  4. package/dist/types/advisor/transcript-recorder.d.ts +52 -0
  5. package/dist/types/collab/host.d.ts +2 -2
  6. package/dist/types/collab/protocol.d.ts +4 -5
  7. package/dist/types/commit/agentic/agent.d.ts +1 -1
  8. package/dist/types/config/model-resolver.d.ts +11 -2
  9. package/dist/types/config/settings-schema.d.ts +12 -6
  10. package/dist/types/edit/file-snapshot-store.d.ts +1 -1
  11. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  12. package/dist/types/modes/components/agent-hub.d.ts +6 -1
  13. package/dist/types/modes/components/agent-transcript-viewer.d.ts +39 -0
  14. package/dist/types/modes/components/chat-transcript-builder.d.ts +42 -0
  15. package/dist/types/modes/controllers/command-controller.d.ts +3 -2
  16. package/dist/types/modes/interactive-mode.d.ts +2 -1
  17. package/dist/types/modes/types.d.ts +2 -1
  18. package/dist/types/registry/agent-registry.d.ts +10 -3
  19. package/dist/types/session/agent-session.d.ts +13 -0
  20. package/dist/types/session/compact-modes.d.ts +60 -0
  21. package/dist/types/session/streaming-output.d.ts +0 -2
  22. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  23. package/dist/types/slash-commands/helpers/collab-qrcode.d.ts +13 -0
  24. package/dist/types/tools/__tests__/json-tree.test.d.ts +1 -0
  25. package/dist/types/tools/index.d.ts +9 -1
  26. package/dist/types/utils/image-loading.d.ts +12 -0
  27. package/dist/types/utils/qrcode.d.ts +48 -0
  28. package/package.json +12 -12
  29. package/src/advisor/index.ts +1 -0
  30. package/src/advisor/transcript-recorder.ts +136 -0
  31. package/src/cli/args.ts +7 -1
  32. package/src/cli/stats-cli.ts +2 -11
  33. package/src/collab/host.ts +29 -17
  34. package/src/collab/protocol.ts +48 -15
  35. package/src/commit/agentic/agent.ts +2 -1
  36. package/src/commit/agentic/tools/git-file-diff.ts +2 -2
  37. package/src/commit/changelog/index.ts +1 -1
  38. package/src/commit/map-reduce/map-phase.ts +1 -1
  39. package/src/commit/map-reduce/utils.ts +1 -1
  40. package/src/config/config-file.ts +1 -1
  41. package/src/config/keybindings.ts +2 -2
  42. package/src/config/model-registry.ts +16 -4
  43. package/src/config/model-resolver.ts +193 -35
  44. package/src/config/settings-schema.ts +14 -7
  45. package/src/config/settings.ts +3 -9
  46. package/src/edit/file-snapshot-store.ts +1 -1
  47. package/src/edit/renderer.ts +7 -7
  48. package/src/eval/js/tool-bridge.ts +3 -2
  49. package/src/eval/py/prelude.py +3 -2
  50. package/src/export/html/tool-views.generated.js +28 -28
  51. package/src/extensibility/extensions/types.ts +7 -0
  52. package/src/hindsight/mental-models.ts +1 -1
  53. package/src/internal-urls/docs-index.generated.txt +1 -1
  54. package/src/internal-urls/history-protocol.ts +8 -3
  55. package/src/irc/bus.ts +8 -0
  56. package/src/lsp/index.ts +2 -2
  57. package/src/main.ts +6 -3
  58. package/src/modes/acp/acp-agent.ts +63 -0
  59. package/src/modes/components/agent-hub.ts +97 -920
  60. package/src/modes/components/agent-transcript-viewer.ts +461 -0
  61. package/src/modes/components/chat-transcript-builder.ts +462 -0
  62. package/src/modes/components/diff.ts +12 -35
  63. package/src/modes/components/oauth-selector.ts +31 -2
  64. package/src/modes/controllers/command-controller.ts +12 -2
  65. package/src/modes/controllers/event-controller.ts +1 -1
  66. package/src/modes/controllers/input-controller.ts +8 -1
  67. package/src/modes/controllers/selector-controller.ts +4 -1
  68. package/src/modes/interactive-mode.ts +4 -2
  69. package/src/modes/types.ts +2 -1
  70. package/src/prompts/tools/inspect-image.md +1 -1
  71. package/src/prompts/tools/read.md +1 -1
  72. package/src/registry/agent-registry.ts +13 -4
  73. package/src/sdk.ts +27 -8
  74. package/src/session/agent-session.ts +185 -17
  75. package/src/session/compact-modes.ts +105 -0
  76. package/src/session/session-dump-format.ts +1 -1
  77. package/src/session/session-history-format.ts +1 -1
  78. package/src/session/streaming-output.ts +5 -5
  79. package/src/slash-commands/builtin-registry.ts +45 -15
  80. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  81. package/src/task/executor.ts +1 -1
  82. package/src/task/output-manager.ts +5 -0
  83. package/src/thinking.ts +25 -5
  84. package/src/tools/__tests__/json-tree.test.ts +35 -0
  85. package/src/tools/approval.ts +1 -1
  86. package/src/tools/bash.ts +0 -1
  87. package/src/tools/browser.ts +0 -1
  88. package/src/tools/eval.ts +1 -1
  89. package/src/tools/gh.ts +1 -1
  90. package/src/tools/index.ts +10 -1
  91. package/src/tools/inspect-image.ts +72 -9
  92. package/src/tools/irc.ts +1 -1
  93. package/src/tools/json-tree.ts +22 -5
  94. package/src/tools/read.ts +5 -6
  95. package/src/utils/file-mentions.ts +5 -2
  96. package/src/utils/image-loading.ts +58 -0
  97. package/src/utils/qrcode.ts +535 -0
  98. package/src/web/scrapers/firefox-addons.ts +1 -1
  99. package/src/web/scrapers/github.ts +1 -1
  100. package/src/web/scrapers/go-pkg.ts +2 -2
  101. package/src/web/scrapers/metacpan.ts +2 -2
  102. package/src/web/scrapers/nvd.ts +2 -2
  103. package/src/web/scrapers/ollama.ts +1 -1
  104. package/src/web/scrapers/opencorporates.ts +1 -1
  105. package/src/web/scrapers/pub-dev.ts +1 -1
  106. package/src/web/scrapers/repology.ts +1 -1
  107. package/src/web/scrapers/sourcegraph.ts +1 -1
  108. package/src/web/scrapers/terraform.ts +6 -6
  109. package/src/web/scrapers/wikidata.ts +2 -2
  110. package/src/workspace-tree.ts +1 -1
@@ -1,3 +1,4 @@
1
1
  export * from "./advise-tool";
2
2
  export * from "./runtime";
3
+ export * from "./transcript-recorder";
3
4
  export * from "./watchdog";
@@ -0,0 +1,52 @@
1
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
+ /**
3
+ * Reserved transcript stem for advisor session files. Chosen so it cannot
4
+ * collide with a task subagent's `<id>.jsonl` (task ids are reserved against
5
+ * this exact stem in {@link AgentOutputManager}).
6
+ */
7
+ export declare const ADVISOR_TRANSCRIPT_STEM = "__advisor";
8
+ export declare const ADVISOR_TRANSCRIPT_FILENAME = "__advisor.jsonl";
9
+ /**
10
+ * Append-only persister for an advisor agent's transcript.
11
+ *
12
+ * The advisor is a passive reviewer with its own model usage, so — like a task
13
+ * subagent — its turns are written to a JSONL inside the owning session's
14
+ * artifacts dir (`<session>/__advisor.jsonl`, `<session>/<SubId>/__advisor.jsonl`
15
+ * for subagent advisors). That single file gives the advisor model proper usage
16
+ * attribution in `omp stats` (the stats parser scans the session dir
17
+ * recursively) and a read-only transcript in the Agent Hub, without making the
18
+ * advisor a registered, messageable peer.
19
+ *
20
+ * The target is derived from the *session file* (`getSessionFile()`), never
21
+ * `getArtifactsDir()` — subagents adopt the parent's artifact manager, so the
22
+ * artifacts dir points at the parent root and every subagent advisor would
23
+ * collide. The file path is resolved synchronously when a message finalizes and
24
+ * captured for the queued write, so a `/new`, resume, or session switch in
25
+ * flight can never misattribute an old advisor turn into the new session's file.
26
+ * On such a switch the previous writer is closed and the new file opened on the
27
+ * next recorded turn. The recorder never truncates: the advisor's in-memory
28
+ * context resets/compacts independently, but every billed turn is appended here.
29
+ */
30
+ export declare class AdvisorTranscriptRecorder {
31
+ #private;
32
+ private readonly resolveSessionFile;
33
+ private readonly resolveCwd;
34
+ /**
35
+ * @param after Optional barrier the queue starts behind — used on the advisor
36
+ * on→off→on toggle so a fresh recorder's first `open` waits for the prior
37
+ * recorder's `close` and the two never hold the same `__advisor.jsonl` at once.
38
+ */
39
+ constructor(resolveSessionFile: () => string | undefined, resolveCwd: () => string, after?: Promise<unknown>);
40
+ /**
41
+ * Persist one finalized advisor message. Assistant turns carry the usage the
42
+ * stats parser reads; tool results round out the Hub transcript; user deltas
43
+ * (the advisor's "session update" prompts) are persisted but flagged
44
+ * `synthetic`/agent-attributed so they never inflate user-message metrics.
45
+ * Non-conversational message kinds are skipped.
46
+ */
47
+ record(message: AgentMessage): void;
48
+ /** Flush pending writes (best-effort). */
49
+ flush(): Promise<void>;
50
+ /** Flush and close the writer, releasing the session file. */
51
+ close(): Promise<void>;
52
+ }
@@ -16,14 +16,14 @@ export declare class CollabHost {
16
16
  #private;
17
17
  constructor(ctx: InteractiveModeContext);
18
18
  get link(): string;
19
- /** Browser deep link (`https://<relay>/#<link>`) the relay serves the web client at `/`. */
19
+ /** Browser deep link for the configured collab web UI. */
20
20
  get webLink(): string;
21
21
  /** Read-only variant of {@link link}: bare room key, no write token. */
22
22
  get viewLink(): string;
23
23
  /** Read-only variant of {@link webLink}. */
24
24
  get webViewLink(): string;
25
25
  get participants(): CollabParticipant[];
26
- start(relayUrl: string): Promise<void>;
26
+ start(relayUrl: string, webUrl?: string): Promise<void>;
27
27
  /** Broadcast a goodbye, detach all taps, and close the socket. */
28
28
  stop(reason: string): Promise<void>;
29
29
  }
@@ -108,12 +108,11 @@ export declare function generateRoomId(): string;
108
108
  */
109
109
  export declare function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array): string;
110
110
  /**
111
- * Render the browser deep link: `http(s)://<relay-host>/#<collab-link>`. The
112
- * relay serves the web client at `/`, and the whole collab link (including the
113
- * room key) rides in the fragment, so it never appears in any HTTP request.
114
- * Terminals auto-link the https form, making it click-to-join.
111
+ * Render the browser deep link. The browser UI may be hosted separately from
112
+ * the relay; the fragment always carries the relay-specific collab link, so
113
+ * room secrets stay out of HTTP path and query bytes.
115
114
  */
116
- export declare function formatCollabWebLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array): string;
115
+ export declare function formatCollabWebLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array, webUrl?: string): string;
117
116
  export declare function parseCollabLink(link: string): ParsedCollabLink | {
118
117
  error: string;
119
118
  };
@@ -1,4 +1,4 @@
1
- import { type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
1
+ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, Model } from "@oh-my-pi/pi-ai";
3
3
  import type { ModelRegistry } from "../../config/model-registry";
4
4
  import type { Settings } from "../../config/settings";
@@ -32,11 +32,17 @@ export interface ScopedModel {
32
32
  thinkingLevel?: ThinkingLevel;
33
33
  explicitThinkingLevel: boolean;
34
34
  }
35
+ interface ThinkingSuffixOptions {
36
+ allowMaxAlias?: boolean;
37
+ }
38
+ interface ModelStringParseOptions extends ThinkingSuffixOptions {
39
+ isLiteralModelId?: (provider: string, id: string) => boolean;
40
+ }
35
41
  /**
36
42
  * Parse a model string in "provider/modelId" format.
37
43
  * Returns undefined if the format is invalid.
38
44
  */
39
- export declare function parseModelString(modelStr: string): {
45
+ export declare function parseModelString(modelStr: string, options?: ModelStringParseOptions): {
40
46
  provider: string;
41
47
  id: string;
42
48
  thinkingLevel?: ThinkingLevel;
@@ -115,7 +121,10 @@ export declare function resolveModelRoleValue(roleValue: string | undefined, ava
115
121
  matchPreferences?: ModelMatchPreferences;
116
122
  modelRegistry?: CanonicalModelRegistry;
117
123
  }): ResolvedModelRoleValue;
118
- export declare function extractExplicitThinkingSelector(value: string | undefined, settings?: Settings): ThinkingLevel | undefined;
124
+ interface ExplicitThinkingSelectorOptions {
125
+ isLiteralModelId?: (provider: string, id: string) => boolean;
126
+ }
127
+ export declare function extractExplicitThinkingSelector(value: string | undefined, settings?: Settings, options?: ExplicitThinkingSelectorOptions): ThinkingLevel | undefined;
119
128
  /**
120
129
  * Resolve a model identifier or pattern to a Model instance.
121
130
  */
@@ -775,10 +775,6 @@ export declare const SETTINGS_SCHEMA: {
775
775
  readonly description: "Remove the 1-character horizontal padding from the left and right of the terminal output";
776
776
  };
777
777
  };
778
- readonly "display.tabWidth": {
779
- readonly type: "number";
780
- readonly default: 3;
781
- };
782
778
  readonly "display.shimmer": {
783
779
  readonly type: "enum";
784
780
  readonly values: readonly ["classic", "kitt", "disabled"];
@@ -835,7 +831,7 @@ export declare const SETTINGS_SCHEMA: {
835
831
  };
836
832
  readonly defaultThinkingLevel: {
837
833
  readonly type: "enum";
838
- readonly values: readonly [...import("@oh-my-pi/pi-catalog").Effort[], "auto"];
834
+ readonly values: readonly [...import("@oh-my-pi/pi-catalog").Effort[], "auto", "max"];
839
835
  readonly default: "high";
840
836
  readonly ui: {
841
837
  readonly tab: "model";
@@ -1579,6 +1575,16 @@ export declare const SETTINGS_SCHEMA: {
1579
1575
  readonly description: "Relay used by /collab (wss://host[:port])";
1580
1576
  };
1581
1577
  };
1578
+ readonly "collab.webUrl": {
1579
+ readonly type: "string";
1580
+ readonly default: "";
1581
+ readonly ui: {
1582
+ readonly tab: "interaction";
1583
+ readonly group: "Collab";
1584
+ readonly label: "Web UI URL";
1585
+ readonly description: "Browser UI used by /collab links; empty derives from collab.relayUrl; explicit http:// is localhost-only";
1586
+ };
1587
+ };
1582
1588
  readonly "collab.displayName": {
1583
1589
  readonly type: "string";
1584
1590
  readonly default: "";
@@ -2012,7 +2018,7 @@ export declare const SETTINGS_SCHEMA: {
2012
2018
  }, {
2013
2019
  readonly value: "pi";
2014
2020
  readonly label: "Pi";
2015
- readonly description: "Use the Pi owned dialect.";
2021
+ readonly description: "Use the Pi owned dialect (compact sigil-delimited tool calls).";
2016
2022
  }, {
2017
2023
  readonly value: "qwen3";
2018
2024
  readonly label: "Qwen3";
@@ -48,7 +48,7 @@ export declare function recordFileSnapshot(session: FileSnapshotStoreOwner, abso
48
48
  /**
49
49
  * The 1-indexed file lines a hashline-formatted body actually displayed.
50
50
  * Single `NN:` rows contribute that line; a collapsed summary `NN-MM:` row
51
- * (a `{ .. }` brace pair) contributes only its boundary lines `NN` and `MM` —
51
+ * (a `{ }` brace pair) contributes only its boundary lines `NN` and `MM` —
52
52
  * the elided interior was never shown, so editing inside it must be rejected.
53
53
  */
54
54
  export declare function parseSeenLinesFromHashlineBody(body: string): number[];
@@ -25,6 +25,7 @@ import type * as PiCodingAgent from "../../index";
25
25
  import type { MemoryRuntimeContext } from "../../memory-backend";
26
26
  import type { CustomEditor } from "../../modes/components/custom-editor";
27
27
  import type { Theme } from "../../modes/theme/theme";
28
+ import type { CompactMode } from "../../session/compact-modes";
28
29
  import type { CustomMessage } from "../../session/messages";
29
30
  import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager";
30
31
  import type { BashToolDetails, BashToolInput, FindToolDetails, FindToolInput, ReadToolDetails, ReadToolInput, SearchToolDetails, SearchToolInput, WriteToolInput } from "../../tools";
@@ -171,6 +172,12 @@ export interface ContextUsage {
171
172
  export interface CompactOptions {
172
173
  onComplete?: (result: CompactionResult) => void;
173
174
  onError?: (error: Error) => void;
175
+ /**
176
+ * Force a one-off compaction mode for this invocation, overriding the
177
+ * configured `compaction.strategy` / `remoteEnabled` (the `/compact`
178
+ * subcommands: `soft` | `remote` | `snapcompact`). Omitted = configured behavior.
179
+ */
180
+ mode?: CompactMode;
174
181
  }
175
182
  /**
176
183
  * Context passed to extension event handlers.
@@ -62,6 +62,11 @@ export declare class AgentHubOverlayComponent extends Container {
62
62
  dispose(): void;
63
63
  render(width: number): readonly string[];
64
64
  handleInput(keyData: string): void;
65
- /** Open the chat view for an agent id (public for table Enter and tests). */
65
+ /**
66
+ * Open the fullscreen transcript viewer for an agent id (public for table Enter
67
+ * and tests). Mounts {@link AgentTranscriptViewer} as a `fullscreen` overlay so it
68
+ * owns the alternate screen; the hub table stays mounted underneath and is
69
+ * restored when the viewer closes. No-op without a real TUI (render-only test stub).
70
+ */
66
71
  openChat(id: string): void;
67
72
  }
@@ -0,0 +1,39 @@
1
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
+ import { type Component, type TUI } from "@oh-my-pi/pi-tui";
3
+ import type { KeyId } from "../../config/keybindings";
4
+ import type { MessageRenderer } from "../../extensibility/extensions/types";
5
+ import type { AgentLifecycleManager } from "../../registry/agent-lifecycle";
6
+ import type { AgentRegistry } from "../../registry/agent-registry";
7
+ import type { SessionObserverRegistry } from "../session-observer-registry";
8
+ import type { AgentHubRemote } from "./agent-hub";
9
+ export interface AgentTranscriptViewerDeps {
10
+ agentId: string;
11
+ registry: AgentRegistry;
12
+ /** Collab guest: read transcript from the host instead of a local file. */
13
+ remote?: AgentHubRemote;
14
+ /** Progress/cost snapshot source for the stats line. */
15
+ observers?: SessionObserverRegistry;
16
+ /** Revive+prompt path for messageable local agents. Lazy to avoid touching the global. */
17
+ lifecycle?: () => AgentLifecycleManager;
18
+ ui: TUI;
19
+ getTool?: (name: string) => AgentTool | undefined;
20
+ getMessageRenderer?: (customType: string) => MessageRenderer | undefined;
21
+ cwd: string;
22
+ hideThinkingBlock?: () => boolean;
23
+ expandKeys: KeyId[];
24
+ /** Keys that toggle the whole hub closed (app.agents.hub + app.session.observe). */
25
+ hubKeys: KeyId[];
26
+ requestRender: () => void;
27
+ /** Close just this viewer (Esc), returning to the hub table. */
28
+ onClose: () => void;
29
+ /** Close this viewer AND the hub (hub-toggle keys). */
30
+ onHubClose: () => void;
31
+ }
32
+ export declare class AgentTranscriptViewer implements Component {
33
+ #private;
34
+ private readonly deps;
35
+ constructor(deps: AgentTranscriptViewerDeps);
36
+ dispose(): void;
37
+ handleInput(data: string): void;
38
+ render(width: number): readonly string[];
39
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Builds transcript components from persisted session message entries — the
3
+ * file/remote-backed counterpart to {@link UiHelpers.addMessageToChat} (which is
4
+ * bound to the live InteractiveModeContext). Used by the fullscreen transcript
5
+ * viewer ({@link AgentTranscriptViewer}) to render a parked subagent / advisor /
6
+ * collab-guest transcript that has no live session.
7
+ *
8
+ * Unlike the old incremental hub sync, {@link ChatTranscriptBuilder.rebuild}
9
+ * always discards prior components and rebuilds the whole transcript from the
10
+ * supplied entries. Re-rendering a growing transcript is therefore O(n) in the
11
+ * entry count, but it cannot duplicate or misorder rows the way incremental
12
+ * component reuse could.
13
+ */
14
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
15
+ import { type TUI } from "@oh-my-pi/pi-tui";
16
+ import type { MessageRenderer } from "../../extensibility/extensions/types";
17
+ import type { SessionMessageEntry } from "../../session/session-entries";
18
+ import { TranscriptContainer } from "./transcript-container";
19
+ export interface ChatTranscriptBuilderDeps {
20
+ ui: TUI;
21
+ getTool?: (name: string) => AgentTool | undefined;
22
+ getMessageRenderer?: (customType: string) => MessageRenderer | undefined;
23
+ cwd: string;
24
+ hideThinkingBlock?: () => boolean;
25
+ requestRender: () => void;
26
+ }
27
+ export declare class ChatTranscriptBuilder {
28
+ #private;
29
+ private readonly deps;
30
+ readonly container: TranscriptContainer;
31
+ constructor(deps: ChatTranscriptBuilderDeps);
32
+ /** Whether the transcript currently holds any rendered rows. */
33
+ get isEmpty(): boolean;
34
+ /** Discard all components and rebuild the whole transcript from `entries`. */
35
+ rebuild(entries: SessionMessageEntry[]): void;
36
+ /** Toggle tool-output expansion across every expandable component. */
37
+ setExpanded(expanded: boolean): void;
38
+ get expanded(): boolean;
39
+ /** Tear down components (sealing pending spinners) and clear build state. */
40
+ reset(): void;
41
+ dispose(): void;
42
+ }
@@ -3,6 +3,7 @@ import { type ProviderDetails, type UsageReport } from "@oh-my-pi/pi-ai";
3
3
  import type { CompactOptions } from "../../extensibility/extensions/types";
4
4
  import { theme } from "../../modes/theme/theme";
5
5
  import type { InteractiveModeContext } from "../../modes/types";
6
+ import type { CompactMode } from "../../session/compact-modes";
6
7
  import { type ShakeMode } from "../../session/shake-types";
7
8
  export declare class CommandController {
8
9
  #private;
@@ -31,14 +32,14 @@ export declare class CommandController {
31
32
  handleRenameCommand(title: string): Promise<void>;
32
33
  handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
33
34
  handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
34
- handleCompactCommand(customInstructions?: string, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>): Promise<CompactionOutcome>;
35
+ handleCompactCommand(customInstructions?: string, mode?: CompactMode, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>): Promise<CompactionOutcome>;
35
36
  /**
36
37
  * TUI handler for `/shake`. `elide` drops heavy structural content and
37
38
  * `images` strips image blocks. Rebuilds the chat and reports counts.
38
39
  */
39
40
  handleShakeCommand(mode: ShakeMode): Promise<void>;
40
41
  handleSkillCommand(skillPath: string, args: string): Promise<void>;
41
- executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>): Promise<CompactionOutcome>;
42
+ executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>, mode?: CompactMode): Promise<CompactionOutcome>;
42
43
  handleHandoffCommand(customInstructions?: string): Promise<void>;
43
44
  }
44
45
  export declare function renderProviderSection(details: ProviderDetails, uiTheme: Pick<typeof theme, "fg">): string;
@@ -12,6 +12,7 @@ import type { CompactOptions } from "../extensibility/extensions/types";
12
12
  import type { MCPManager } from "../mcp";
13
13
  import { type PlanApprovalDetails } from "../plan-mode/approved-plan";
14
14
  import type { AgentSession } from "../session/agent-session";
15
+ import type { CompactMode } from "../session/compact-modes";
15
16
  import { HistoryStorage } from "../session/history-storage";
16
17
  import type { SessionContext } from "../session/session-context";
17
18
  import type { SessionManager } from "../session/session-manager";
@@ -298,7 +299,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
298
299
  handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
299
300
  handleMCPCommand(text: string): Promise<void>;
300
301
  handleSSHCommand(text: string): Promise<void>;
301
- handleCompactCommand(customInstructions?: string, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>): Promise<CompactionOutcome>;
302
+ handleCompactCommand(customInstructions?: string, mode?: CompactMode, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>): Promise<CompactionOutcome>;
302
303
  handleHandoffCommand(customInstructions?: string): Promise<void>;
303
304
  handleShakeCommand(mode: ShakeMode): Promise<void>;
304
305
  executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean): Promise<CompactionOutcome>;
@@ -11,6 +11,7 @@ import type { CompactOptions } from "../extensibility/extensions/types";
11
11
  import type { MCPManager } from "../mcp";
12
12
  import type { PlanApprovalDetails } from "../plan-mode/approved-plan";
13
13
  import type { AgentSession } from "../session/agent-session";
14
+ import type { CompactMode } from "../session/compact-modes";
14
15
  import type { HistoryStorage } from "../session/history-storage";
15
16
  import type { SessionContext } from "../session/session-context";
16
17
  import type { SessionManager } from "../session/session-manager";
@@ -273,7 +274,7 @@ export interface InteractiveModeContext {
273
274
  handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
274
275
  handleMCPCommand(text: string): Promise<void>;
275
276
  handleSSHCommand(text: string): Promise<void>;
276
- handleCompactCommand(customInstructions?: string): Promise<CompactionOutcome>;
277
+ handleCompactCommand(customInstructions?: string, mode?: CompactMode): Promise<CompactionOutcome>;
277
278
  handleHandoffCommand(customInstructions?: string): Promise<void>;
278
279
  handleShakeCommand(mode: ShakeMode): Promise<void>;
279
280
  handleMoveCommand(targetPath: string): Promise<void>;
@@ -18,7 +18,13 @@ export declare const MAIN_AGENT_ID = "Main";
18
18
  * - `aborted`: hard-killed, terminal.
19
19
  */
20
20
  export type AgentStatus = "running" | "idle" | "parked" | "aborted";
21
- export type AgentKind = "main" | "sub";
21
+ /**
22
+ * - `main`/`sub`: the user-facing agent tree (driving agent + task subagents).
23
+ * - `advisor`: a passive review transcript persisted like a subagent for usage
24
+ * attribution and Agent Hub observability, but never a peer — hidden from
25
+ * agent-facing rosters (`irc`, `history://`) and not messageable/revivable.
26
+ */
27
+ export type AgentKind = "main" | "sub" | "advisor";
22
28
  export interface AgentRef {
23
29
  id: string;
24
30
  displayName: string;
@@ -81,8 +87,9 @@ export declare class AgentRegistry {
81
87
  get(id: string): AgentRef | undefined;
82
88
  list(): AgentRef[];
83
89
  /**
84
- * Returns every alive agent (running | idle) except the caller.
85
- * Flat namespace: every agent can see every other agent.
90
+ * Returns every alive agent (running | idle) except the caller. Advisor refs
91
+ * are observability-only transcripts, never peers, so they are excluded.
92
+ * Flat namespace: every other agent is visible.
86
93
  */
87
94
  listVisibleTo(id: string): AgentRef[];
88
95
  onChange(listener: RegistryListener): () => void;
@@ -237,6 +237,13 @@ export interface AgentSessionConfig {
237
237
  advisorReadOnlyTools?: AgentTool[];
238
238
  /** Preloaded watchdog prompt content for the advisor. */
239
239
  advisorWatchdogPrompt?: string;
240
+ /**
241
+ * Disconnect this session's OWNED MCP manager on dispose. Provided only when
242
+ * the session created the manager (top-level sessions); subagents reuse a
243
+ * parent's manager via `options.mcpManager` and omit this so a child's
244
+ * teardown never tears down the shared servers.
245
+ */
246
+ disconnectOwnedMcpManager?: () => Promise<void>;
240
247
  }
241
248
  /** Options for AgentSession.prompt() */
242
249
  export interface PromptOptions {
@@ -552,6 +559,12 @@ export declare class AgentSession {
552
559
  get hasPostPromptWork(): boolean;
553
560
  /** All messages including custom types like BashExecutionMessage */
554
561
  get messages(): AgentMessage[];
562
+ /** Latest image attachments addressable by tools as `Image #N` or `attachment://N`. */
563
+ getImageAttachments(): {
564
+ label: string;
565
+ uri: string;
566
+ image: ImageContent;
567
+ }[];
555
568
  buildDisplaySessionContext(): SessionContext;
556
569
  /**
557
570
  * Full-history transcript for TUI display: every path entry in
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Manual `/compact` subcommands. Kept in a dependency-free leaf module so the
3
+ * slash-command registry, the interactive controllers, and `AgentSession`
4
+ * can all import the mode metadata + parser without pulling in the heavy
5
+ * `agent-session` module graph (which would form an import cycle through the
6
+ * slash-command registry) — same rationale as `shake-types.ts`.
7
+ *
8
+ * Each mode is a one-off override layered on top of the configured
9
+ * `compaction.*` settings for a single invocation; it never mutates settings.
10
+ * Adding a mode is a single entry here: the command surface (autocomplete +
11
+ * ACP hint), the parser, and the engine override all read this table.
12
+ */
13
+ /** Subcommand selecting a one-off compaction mode for manual `/compact`. */
14
+ export type CompactMode = "soft" | "remote" | "snapcompact";
15
+ /**
16
+ * Per-invocation overrides merged over the configured `compaction.*` settings.
17
+ * Narrowed to the two knobs the modes actually flip; the result stays
18
+ * assignable to the full `CompactionSettings`.
19
+ */
20
+ export interface CompactionOverride {
21
+ strategy?: "context-full" | "snapcompact";
22
+ remoteEnabled?: boolean;
23
+ }
24
+ export interface CompactModeDef {
25
+ readonly name: CompactMode;
26
+ /** One-line description surfaced in autocomplete + help. */
27
+ readonly description: string;
28
+ /** Settings overrides applied on top of `compaction.*` for this run. */
29
+ readonly overrides: CompactionOverride;
30
+ /**
31
+ * When true, the mode produces no LLM summary, so trailing focus text is
32
+ * meaningless and rejected by the parser (snapcompact archives history into
33
+ * images without a directed summary).
34
+ */
35
+ readonly rejectsFocus?: boolean;
36
+ /**
37
+ * When true, the mode explicitly demands a remote path; the engine warns and
38
+ * falls back to a local summary if neither a remote endpoint nor a
39
+ * provider-native compaction path is available.
40
+ */
41
+ readonly requiresRemote?: boolean;
42
+ }
43
+ export declare const COMPACT_MODES: readonly CompactModeDef[];
44
+ /** Resolve a subcommand token (case-insensitive) to its mode definition. */
45
+ export declare function findCompactMode(name: string): CompactModeDef | undefined;
46
+ /** Parsed `/compact` arguments: an optional mode plus optional focus text. */
47
+ export interface ParsedCompactArgs {
48
+ mode?: CompactMode;
49
+ instructions?: string;
50
+ }
51
+ /**
52
+ * Split `/compact` args into a leading mode subcommand + focus instructions.
53
+ *
54
+ * Backward compatible: when the first token is not a known mode, the entire
55
+ * argument string is treated as focus instructions (the historical behavior).
56
+ * A recognized mode with `rejectsFocus` and trailing text is an error.
57
+ */
58
+ export declare function parseCompactArgs(args: string): ParsedCompactArgs | {
59
+ error: string;
60
+ };
@@ -163,8 +163,6 @@ export declare function truncateMiddle(content: string, options?: TruncationOpti
163
163
  export interface InlineByteCapOptions {
164
164
  /** Inline byte budget. Defaults to {@link DEFAULT_MAX_BYTES}. */
165
165
  maxBytes?: number;
166
- /** What the text is, for the elision marker (e.g. "bash output"). */
167
- label: string;
168
166
  /**
169
167
  * Persist the full text as a session artifact. When an artifact id is
170
168
  * returned, a `[raw output: artifact://<id>]` footer is appended so the
@@ -1,4 +1,4 @@
1
- import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
1
+ import { type AutocompleteItem } from "@oh-my-pi/pi-tui";
2
2
  import type { BuiltinSlashCommand, ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime, SlashCommandSpec, TuiSlashCommandRuntime } from "./types";
3
3
  export type { BuiltinSlashCommand, SubcommandDef } from "./types";
4
4
  /** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
@@ -0,0 +1,13 @@
1
+ import { type Component } from "@oh-my-pi/pi-tui";
2
+ /**
3
+ * One-shot transcript block that prints a collab browser-join URL as a
4
+ * scannable QR code. The symbol is encoded once at construction (byte mode,
5
+ * EC level M) and rendered as ANSI half-blocks; on terminals too narrow for
6
+ * the symbol it degrades to a one-line hint pointing at the printed URL.
7
+ */
8
+ export declare class CollabQrCodeComponent implements Component {
9
+ #private;
10
+ readonly url: string;
11
+ constructor(url: string);
12
+ render(width: number): readonly string[];
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
2
2
  import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
3
- import type { FetchImpl, Model, ToolChoice } from "@oh-my-pi/pi-ai";
3
+ import type { FetchImpl, ImageContent, Model, ToolChoice } from "@oh-my-pi/pi-ai";
4
4
  import type { AsyncJobManager } from "../async/job-manager";
5
5
  import type { Rule } from "../capability/rule";
6
6
  import type { PromptTemplate } from "../config/prompt-templates";
@@ -71,6 +71,12 @@ export type ContextFileEntry = {
71
71
  content: string;
72
72
  depth?: number;
73
73
  };
74
+ /** Image attachment handle exposed to tools for user-facing labels such as `Image #1`. */
75
+ export type ImageAttachmentEntry = {
76
+ label: string;
77
+ uri: string;
78
+ image: ImageContent;
79
+ };
74
80
  export type { DiscoverableTool, DiscoverableToolSearchIndex, DiscoverableToolSearchResult, DiscoverableToolSource, } from "../tool-discovery/tool-index";
75
81
  /**
76
82
  * A late LSP diagnostics result that arrived after the edit/write tool already
@@ -309,6 +315,8 @@ export interface ToolSession {
309
315
  /** Get the active OpenTelemetry config so subagent dispatch can forward
310
316
  * the parent's tracer/hooks with the subagent's own identity stamped. */
311
317
  getTelemetry?: () => AgentTelemetryConfig | undefined;
318
+ /** Return image attachments visible to tools for resolving labels such as `Image #1`. */
319
+ getImageAttachments?: () => ImageAttachmentEntry[];
312
320
  }
313
321
  export type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
314
322
  export type BuiltinToolLoadMode = "essential" | "discoverable";
@@ -25,6 +25,16 @@ export interface LoadImageInputOptions {
25
25
  /** Force non-WebP output (e.g. for Ollama). Leave unset to honor `OMP_NO_WEBP`. */
26
26
  excludeWebP?: boolean;
27
27
  }
28
+ /** Options for loading an in-memory chat image attachment as a vision-model input. */
29
+ export interface LoadImageAttachmentInputOptions {
30
+ image: ImageContent;
31
+ label: string;
32
+ uri: string;
33
+ autoResize: boolean;
34
+ maxBytes?: number;
35
+ /** Force non-WebP output (e.g. for Ollama). Leave unset to honor `OMP_NO_WEBP`. */
36
+ excludeWebP?: boolean;
37
+ }
28
38
  export interface LoadedImageInput {
29
39
  resolvedPath: string;
30
40
  mimeType: string;
@@ -53,3 +63,5 @@ export interface NormalizeModelContextImagesOptions {
53
63
  */
54
64
  export declare function normalizeModelContextImages(images: ImageContent[] | undefined, options?: NormalizeModelContextImagesOptions): Promise<ImageContent[] | undefined>;
55
65
  export declare function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null>;
66
+ /** Loads a chat attachment image through the same size and encoder policy as file-backed image inputs. */
67
+ export declare function loadImageAttachmentInput(options: LoadImageAttachmentInputOptions): Promise<LoadedImageInput | null>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Self-contained QR Code generator (byte mode, versions 1-40, EC levels
3
+ * L/M/Q/H) with a half-block ANSI terminal renderer.
4
+ *
5
+ * Pure TypeScript, zero dependencies: the collab `/collab qrcode` command uses
6
+ * it to print scannable browser-join codes without pulling a runtime QR
7
+ * package into the bundle. The algorithm follows ISO/IEC 18004; the two
8
+ * error-correction tables below are direct transcriptions of that spec.
9
+ */
10
+ export type QrEcLevel = "L" | "M" | "Q" | "H";
11
+ export interface QrEncodeOptions {
12
+ /** Lowest version to consider (default 1). */
13
+ minVersion?: number;
14
+ /** Highest version to consider (default 40). */
15
+ maxVersion?: number;
16
+ /** Force a mask 0-7; -1 (default) auto-selects the lowest-penalty mask. */
17
+ mask?: number;
18
+ }
19
+ /**
20
+ * A finished QR symbol: a square grid of dark/light modules plus the chosen
21
+ * version, EC level, and mask. `module(x, y)` is the only access path the
22
+ * renderers need.
23
+ */
24
+ export declare class QrCode {
25
+ #private;
26
+ readonly version: number;
27
+ readonly ecLevel: QrEcLevel;
28
+ readonly size: number;
29
+ /** Selected mask pattern (0-7). */
30
+ readonly mask: number;
31
+ private constructor();
32
+ module(x: number, y: number): boolean;
33
+ /** Encode a string in byte mode (UTF-8). Throws if it exceeds version 40. */
34
+ static encodeText(text: string, ecLevel?: QrEcLevel, options?: QrEncodeOptions): QrCode;
35
+ /** Encode raw bytes in byte mode. Throws if they exceed version 40 at this EC level. */
36
+ static encodeBytes(data: Uint8Array, ecLevel?: QrEcLevel, options?: QrEncodeOptions): QrCode;
37
+ }
38
+ export interface QrRenderOptions {
39
+ /** Quiet-zone width in modules on every side (default 4, per spec). */
40
+ margin?: number;
41
+ }
42
+ /**
43
+ * Render a QR symbol as ANSI half-block rows: each text row packs two module
44
+ * rows via `▀`/`▄`/`█`, drawn black-on-white so a phone camera reads dark
45
+ * modules as data and the quiet zone as the light margin. The leading margin
46
+ * makes the symbol scannable regardless of the terminal's own background.
47
+ */
48
+ export declare function renderQrHalfBlocks(qr: QrCode, options?: QrRenderOptions): string[];