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

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 (80) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/dist/cli.js +8083 -7692
  3. package/dist/types/collab/crypto.d.ts +1 -6
  4. package/dist/types/collab/guest.d.ts +2 -0
  5. package/dist/types/collab/host.d.ts +16 -0
  6. package/dist/types/collab/protocol.d.ts +14 -1
  7. package/dist/types/config/settings-schema.d.ts +40 -5
  8. package/dist/types/export/custom-share.d.ts +1 -2
  9. package/dist/types/export/html/index.d.ts +39 -1
  10. package/dist/types/export/share.d.ts +43 -0
  11. package/dist/types/main.d.ts +2 -0
  12. package/dist/types/modes/components/agent-hub.d.ts +19 -1
  13. package/dist/types/modes/components/status-line/component.d.ts +6 -1
  14. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  15. package/dist/types/modes/controllers/event-controller.d.ts +7 -0
  16. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  17. package/dist/types/modes/controllers/session-focus-controller.d.ts +31 -0
  18. package/dist/types/modes/interactive-mode.d.ts +9 -0
  19. package/dist/types/modes/session-observer-registry.d.ts +7 -0
  20. package/dist/types/modes/theme/theme.d.ts +2 -1
  21. package/dist/types/modes/types.d.ts +12 -0
  22. package/dist/types/session/agent-session.d.ts +2 -0
  23. package/dist/types/session/codex-auto-reset.d.ts +8 -4
  24. package/dist/types/task/executor.d.ts +7 -0
  25. package/dist/types/task/types.d.ts +9 -0
  26. package/package.json +13 -14
  27. package/scripts/build-binary.ts +4 -0
  28. package/scripts/bundle-dist.ts +4 -0
  29. package/scripts/generate-share-viewer.ts +34 -0
  30. package/src/collab/crypto.ts +10 -4
  31. package/src/collab/guest.ts +31 -2
  32. package/src/collab/host.ts +73 -11
  33. package/src/collab/protocol.ts +48 -7
  34. package/src/commands/join.ts +1 -1
  35. package/src/config/settings-schema.ts +40 -4
  36. package/src/config/settings.ts +12 -0
  37. package/src/export/custom-share.ts +1 -1
  38. package/src/export/html/index.ts +122 -17
  39. package/src/export/html/share-loader.js +102 -0
  40. package/src/export/html/template.css +745 -459
  41. package/src/export/html/template.html +6 -3
  42. package/src/export/html/template.js +240 -915
  43. package/src/export/html/tool-views.generated.js +38 -0
  44. package/src/export/share.ts +268 -0
  45. package/src/internal-urls/docs-index.generated.ts +73 -73
  46. package/src/main.ts +22 -9
  47. package/src/modes/components/agent-hub.ts +541 -410
  48. package/src/modes/components/status-line/component.ts +38 -5
  49. package/src/modes/components/status-line/segments.ts +5 -1
  50. package/src/modes/components/status-line/types.ts +2 -0
  51. package/src/modes/components/tips.txt +3 -1
  52. package/src/modes/controllers/command-controller.ts +55 -96
  53. package/src/modes/controllers/event-controller.ts +45 -16
  54. package/src/modes/controllers/input-controller.ts +104 -4
  55. package/src/modes/controllers/selector-controller.ts +11 -15
  56. package/src/modes/controllers/session-focus-controller.ts +112 -0
  57. package/src/modes/interactive-mode.ts +44 -2
  58. package/src/modes/session-observer-registry.ts +11 -0
  59. package/src/modes/theme/theme.ts +6 -0
  60. package/src/modes/types.ts +12 -0
  61. package/src/modes/utils/ui-helpers.ts +16 -13
  62. package/src/prompts/tools/job.md +1 -1
  63. package/src/session/agent-session.ts +65 -7
  64. package/src/session/codex-auto-reset.ts +23 -11
  65. package/src/slash-commands/builtin-registry.ts +62 -35
  66. package/src/task/executor.ts +14 -0
  67. package/src/task/index.ts +5 -1
  68. package/src/task/render.ts +76 -5
  69. package/src/task/types.ts +9 -0
  70. package/src/tiny/worker.ts +17 -95
  71. package/src/tools/job.ts +6 -9
  72. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  73. package/dist/types/export/html/template.generated.d.ts +0 -1
  74. package/dist/types/export/html/template.macro.d.ts +0 -5
  75. package/dist/types/tiny/compiled-runtime.d.ts +0 -35
  76. package/scripts/generate-template.ts +0 -33
  77. package/src/bun-imports.d.ts +0 -28
  78. package/src/export/html/template.generated.ts +0 -2
  79. package/src/export/html/template.macro.ts +0 -25
  80. package/src/tiny/compiled-runtime.ts +0 -179
@@ -1,11 +1,6 @@
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
1
  import type { CollabFrame } from "./protocol";
8
2
  export declare function generateRoomKey(): Uint8Array;
3
+ export declare function generateWriteToken(): Uint8Array;
9
4
  export declare function importRoomKey(raw: Uint8Array): Promise<CryptoKey>;
10
5
  export declare function seal(key: CryptoKey, frame: CollabFrame): Promise<Uint8Array>;
11
6
  /** Inverse of {@link seal}. Throws on auth failure or malformed input. */
@@ -12,6 +12,8 @@ export declare class CollabGuestLink {
12
12
  readonly agentRegistry: AgentRegistry;
13
13
  /** Agent Hub actions routed to the host over the wire. */
14
14
  get hubRemote(): AgentHubRemote;
15
+ /** True when this guest joined through a read-only (view) link. */
16
+ get readOnly(): boolean;
15
17
  constructor(ctx: InteractiveModeContext);
16
18
  join(link: string): Promise<void>;
17
19
  /** User-initiated leave (or post-disconnect cleanup): restore the previous session. */
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Host side of a collab live session.
3
+ *
4
+ * Taps the host session's event stream and SessionManager append chokepoint,
5
+ * broadcasting entries/events/state to guests through the relay. Guests prompt
6
+ * and abort through us; the host machine runs the agent and tools. The host's
7
+ * subagent ecosystem is mirrored too: task EventBus traffic (observer HUD),
8
+ * agent-registry snapshots (Agent Hub table), hub chat/kill/revive commands,
9
+ * and incremental subagent-transcript reads.
10
+ */
1
11
  import type { InteractiveModeContext } from "../modes/types";
2
12
  import { type CollabParticipant } from "./protocol";
3
13
  /** Display name for this process's user in collab sessions. */
@@ -6,6 +16,12 @@ export declare class CollabHost {
6
16
  #private;
7
17
  constructor(ctx: InteractiveModeContext);
8
18
  get link(): string;
19
+ /** Browser deep link (`https://<relay>/#<link>`) — the relay serves the web client at `/`. */
20
+ get webLink(): string;
21
+ /** Read-only variant of {@link link}: bare room key, no write token. */
22
+ get viewLink(): string;
23
+ /** Read-only variant of {@link webLink}. */
24
+ get webViewLink(): string;
9
25
  get participants(): CollabParticipant[];
10
26
  start(relayUrl: string): Promise<void>;
11
27
  /** Broadcast a goodbye, detach all taps, and close the socket. */
@@ -45,6 +45,8 @@ export type CollabFrame = Exclude<GuestFrame, {
45
45
  entries: SessionEntry[];
46
46
  state: CollabSessionState;
47
47
  agents: AgentSnapshot[];
48
+ /** True when this peer joined through a read-only (view) link. */
49
+ readOnly?: boolean;
48
50
  } | {
49
51
  t: "entry";
50
52
  entry: SessionEntry;
@@ -93,8 +95,19 @@ export declare function generateRoomId(): string;
93
95
  * `<roomId>#<key>`, other wss relays drop the scheme (`host[:port]/r/…`);
94
96
  * only localhost ws:// links keep their full URL so parsing cannot
95
97
  * mis-infer wss.
98
+ *
99
+ * Full links append the write token to the key in the fragment
100
+ * (`base64url(key ∥ writeToken)`); read-only (view) links carry the bare
101
+ * 32-byte key, which is also the pre-token link format.
102
+ */
103
+ export declare function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array): string;
104
+ /**
105
+ * Render the browser deep link: `http(s)://<relay-host>/#<collab-link>`. The
106
+ * relay serves the web client at `/`, and the whole collab link (including the
107
+ * room key) rides in the fragment, so it never appears in any HTTP request.
108
+ * Terminals auto-link the https form, making it click-to-join.
96
109
  */
97
- export declare function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Array): string;
110
+ export declare function formatCollabWebLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array): string;
98
111
  export declare function parseCollabLink(link: string): ParsedCollabLink | {
99
112
  error: string;
100
113
  };
@@ -1396,7 +1396,7 @@ export declare const SETTINGS_SCHEMA: {
1396
1396
  };
1397
1397
  readonly "collab.relayUrl": {
1398
1398
  readonly type: "string";
1399
- readonly default: "wss://relay.omp.sh";
1399
+ readonly default: "wss://my.omp.sh";
1400
1400
  readonly ui: {
1401
1401
  readonly tab: "interaction";
1402
1402
  readonly group: "Collab";
@@ -1414,6 +1414,26 @@ export declare const SETTINGS_SCHEMA: {
1414
1414
  readonly description: "Name shown to other collab participants (default: OS username)";
1415
1415
  };
1416
1416
  };
1417
+ readonly "share.serverUrl": {
1418
+ readonly type: "string";
1419
+ readonly default: "https://my.omp.sh/s";
1420
+ readonly ui: {
1421
+ readonly tab: "interaction";
1422
+ readonly group: "Collab";
1423
+ readonly label: "Share Server";
1424
+ readonly description: "Share viewer/upload base used by /share (encrypted blob upload + viewer; links are <base>/<id>#<key>)";
1425
+ };
1426
+ };
1427
+ readonly "share.redactSecrets": {
1428
+ readonly type: "boolean";
1429
+ readonly default: true;
1430
+ readonly ui: {
1431
+ readonly tab: "interaction";
1432
+ readonly group: "Collab";
1433
+ readonly label: "Share Secret Redaction";
1434
+ readonly description: "Run the secret obfuscator over /share snapshots before upload (uses the secrets.* config)";
1435
+ };
1436
+ };
1417
1437
  readonly "stt.enabled": {
1418
1438
  readonly type: "boolean";
1419
1439
  readonly default: false;
@@ -4209,13 +4229,27 @@ export declare const SETTINGS_SCHEMA: {
4209
4229
  };
4210
4230
  };
4211
4231
  readonly "codexResets.autoRedeem": {
4212
- readonly type: "boolean";
4213
- readonly default: false;
4232
+ readonly type: "enum";
4233
+ readonly values: readonly ["unset", "yes", "no"];
4234
+ readonly default: "unset";
4214
4235
  readonly ui: {
4215
4236
  readonly tab: "providers";
4216
4237
  readonly group: "Services";
4217
4238
  readonly label: "Codex Auto-Redeem Saved Resets";
4218
- readonly description: "When a turn is blocked by the Codex weekly limit on the active account and no other account is available, automatically spend one saved rate-limit reset (ChatGPT 'save rate limit resets'). Conservative: never fires for 5-hour-only or Spark limits, near a natural reset, or twice for the same block. Requires retries enabled.";
4239
+ readonly description: "When a turn is blocked by the Codex weekly limit on the active account and no other account is available, run the conservative saved-reset check. unset asks before spending the first eligible reset, yes spends eligible resets without prompting, and no disables the check entirely. Requires retries enabled.";
4240
+ readonly options: readonly [{
4241
+ readonly value: "unset";
4242
+ readonly label: "Unset";
4243
+ readonly description: "Check eligibility, then ask before spending the first saved reset.";
4244
+ }, {
4245
+ readonly value: "yes";
4246
+ readonly label: "Yes";
4247
+ readonly description: "Spend eligible saved resets without prompting.";
4248
+ }, {
4249
+ readonly value: "no";
4250
+ readonly label: "No";
4251
+ readonly description: "Do not run the saved-reset auto-redeem check.";
4252
+ }];
4219
4253
  };
4220
4254
  };
4221
4255
  readonly "codexResets.minBlockedMinutes": {
@@ -4587,8 +4621,9 @@ export interface ShellMinimizerSettings {
4587
4621
  sourceOutlineLevel: "default" | "aggressive";
4588
4622
  legacyFilters: boolean | undefined;
4589
4623
  }
4624
+ export type CodexAutoRedeemMode = "unset" | "yes" | "no";
4590
4625
  export interface CodexResetsSettings {
4591
- autoRedeem: boolean;
4626
+ autoRedeem: CodexAutoRedeemMode;
4592
4627
  minBlockedMinutes: number;
4593
4628
  keepCredits: number;
4594
4629
  }
@@ -5,7 +5,7 @@ export interface CustomShareResult {
5
5
  message?: string;
6
6
  }
7
7
  export type CustomShareFn = (htmlPath: string) => Promise<CustomShareResult | string | undefined>;
8
- interface LoadedCustomShare {
8
+ export interface LoadedCustomShare {
9
9
  path: string;
10
10
  fn: CustomShareFn;
11
11
  }
@@ -17,4 +17,3 @@ export declare function getCustomSharePath(): string | null;
17
17
  * Load the custom share script if it exists.
18
18
  */
19
19
  export declare function loadCustomShare(): Promise<LoadedCustomShare | null>;
20
- export {};
@@ -1,9 +1,47 @@
1
1
  import type { AgentState } from "@oh-my-pi/pi-agent-core";
2
- import { SessionManager } from "../../session/session-manager";
2
+ import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager";
3
+ /** Compose the standalone export template: minified CSS, tool renderers, and viewer JS inlined. */
4
+ export declare function getTemplate(): string;
3
5
  export interface ExportOptions {
4
6
  outputPath?: string;
5
7
  themeName?: string;
8
+ /** Embed subagent session transcripts found next to the session file (default true). */
9
+ includeSubSessions?: boolean;
6
10
  }
11
+ /** Generate CSS custom properties for theme. Exported for the share-viewer build script. */
12
+ export declare function generateThemeVars(themeName?: string): Promise<string>;
13
+ /** Embedded subagent session transcript, keyed by slash-joined agent path in `SessionData.subSessions`. */
14
+ export interface SubSession {
15
+ /** Bare agent id (session file stem), e.g. "ToolAsk". */
16
+ agentId: string;
17
+ /** Key of the parent sub-session, or null when spawned by the main session. */
18
+ parent: string | null;
19
+ header: SessionHeader | null;
20
+ entries: SessionEntry[];
21
+ leafId: string | null;
22
+ }
23
+ export interface SessionData {
24
+ header: SessionHeader | null;
25
+ entries: SessionEntry[];
26
+ leafId: string | null;
27
+ systemPrompt?: string;
28
+ tools?: {
29
+ name: string;
30
+ description: string;
31
+ }[];
32
+ subSessions?: Record<string, SubSession>;
33
+ }
34
+ /** Snapshot the session (plus optional agent state) into the JSON shape the viewer renders. */
35
+ export declare function buildSessionData(sm: SessionManager, state?: AgentState): SessionData;
36
+ /**
37
+ * Collect subagent session transcripts stored next to a session file.
38
+ *
39
+ * A session at `<dir>/<name>.jsonl` keeps its subagent sessions at `<dir>/<name>/<AgentId>.jsonl`;
40
+ * each subagent's own children nest the same way under `<dir>/<name>/<AgentId>/`. Keys in the
41
+ * returned record are slash-joined ids relative to the main session ("ToolAsk", "ToolAsk/Helper").
42
+ * Corrupt or empty files are skipped silently.
43
+ */
44
+ export declare function collectSubSessions(sessionFile: string): Promise<Record<string, SubSession>>;
7
45
  /** Export session to HTML using SessionManager and AgentState. */
8
46
  export declare function exportSessionToHtml(sm: SessionManager, state?: AgentState, options?: ExportOptions | string): Promise<string>;
9
47
  /** Export session file to HTML (standalone). */
@@ -0,0 +1,43 @@
1
+ import type { AgentState } from "@oh-my-pi/pi-agent-core";
2
+ import { DEFAULT_SHARE_URL } from "@oh-my-pi/pi-wire";
3
+ import type { SecretObfuscator } from "../secrets/obfuscator";
4
+ import type { SessionManager } from "../session/session-manager";
5
+ import { type SessionData } from "./html";
6
+ export { DEFAULT_SHARE_URL };
7
+ /** Hard cap for blobs accepted by the share server (mirrors relay shareMaxBytes). */
8
+ export declare const SERVER_MAX_SEALED_BYTES = 1000000;
9
+ export interface ShareSessionOptions {
10
+ /** Share server/viewer base URL; defaults to {@link DEFAULT_SHARE_URL}. */
11
+ serverUrl?: string;
12
+ /** Agent state for system prompt + tool descriptions in the snapshot. */
13
+ state?: AgentState;
14
+ /**
15
+ * Redacts the snapshot before sealing: deep-walks every string (entries,
16
+ * header, system prompt, tool descriptions) through the obfuscator, so
17
+ * secrets that landed in persisted entries (tool outputs reading .env,
18
+ * etc.) never leave the machine. Pass undefined to skip.
19
+ */
20
+ obfuscator?: SecretObfuscator;
21
+ }
22
+ export interface ShareSessionResult {
23
+ /** Viewer link: `<serverUrl>/<id>#<key>`. */
24
+ url: string;
25
+ method: "gist" | "server";
26
+ /** Underlying gist URL (gist method only). */
27
+ gistUrl?: string;
28
+ /** True when content was trimmed to fit the upload budget. */
29
+ truncated: boolean;
30
+ sealedBytes: number;
31
+ }
32
+ /** Build the snapshot that gets sealed and uploaded, redacted when an obfuscator is provided. */
33
+ export declare function buildShareSnapshot(sm: SessionManager, options?: ShareSessionOptions): SessionData;
34
+ /** Share the session; tries a secret gist first, then the share server. */
35
+ export declare function shareSession(sm: SessionManager, options?: ShareSessionOptions): Promise<ShareSessionResult>;
36
+ /** Strip trailing slashes so `<base>/<id>` composes cleanly. */
37
+ export declare function normalizeShareServerUrl(serverUrl?: string): string;
38
+ interface SealedSession {
39
+ sealed: Uint8Array<ArrayBuffer>;
40
+ truncated: boolean;
41
+ }
42
+ /** Seal `data`, trimming content until the sealed blob fits `maxBytes`. Exported for tests. */
43
+ export declare function sealToFit(key: CryptoKey, data: SessionData, maxBytes: number): Promise<SealedSession>;
@@ -1,5 +1,6 @@
1
1
  import type { Args } from "./cli/args";
2
2
  import { ModelRegistry } from "./config/model-registry";
3
+ import { type ScopedModel } from "./config/model-resolver";
3
4
  import { Settings } from "./config/settings";
4
5
  import { InteractiveMode } from "./modes/interactive-mode";
5
6
  import type { SubmittedUserInput } from "./modes/types";
@@ -12,6 +13,7 @@ export interface InteractiveModeNotify {
12
13
  kind: "warn" | "error" | "info";
13
14
  message: string;
14
15
  }
16
+ export declare function buildModelScopeNotification(scopedModelsForDisplay: readonly Pick<ScopedModel, "model" | "thinkingLevel" | "explicitThinkingLevel">[], startupQuiet: boolean): InteractiveModeNotify | null;
15
17
  export declare function submitInteractiveInput(mode: Pick<InteractiveMode, "markPendingSubmissionStarted" | "finishPendingSubmission" | "showError" | "checkShutdownRequested">, session: Pick<AgentSession, "prompt" | "promptCustomMessage">, input: SubmittedUserInput): Promise<void>;
16
18
  type AcpSessionFactory = (cwd: string) => Promise<AgentSession>;
17
19
  export interface AcpSessionFactoryOptions {
@@ -1,5 +1,7 @@
1
- import { Container } from "@oh-my-pi/pi-tui";
1
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
+ import { Container, type TUI } from "@oh-my-pi/pi-tui";
2
3
  import type { KeyId } from "../../config/keybindings";
4
+ import type { MessageRenderer } from "../../extensibility/extensions/types";
3
5
  import { IrcBus } from "../../irc/bus";
4
6
  import { AgentLifecycleManager } from "../../registry/agent-lifecycle";
5
7
  import { AgentRegistry } from "../../registry/agent-registry";
@@ -28,6 +30,22 @@ export interface AgentHubDeps {
28
30
  lifecycle?: AgentLifecycleManager;
29
31
  /** Injectable for tests; defaults to the process-global bus. */
30
32
  irc?: IrcBus;
33
+ /** TUI handle for transcript components; tests omit it and get a render-only stub. */
34
+ ui?: TUI;
35
+ /** Tool lookup for transcript renderers (labels, custom render functions). */
36
+ getTool?: (name: string) => AgentTool | undefined;
37
+ /** Extension message renderers for custom messages in the transcript. */
38
+ getMessageRenderer?: (customType: string) => MessageRenderer | undefined;
39
+ /** Cwd used by tool renderers for path shortening; defaults to the project dir. */
40
+ cwd?: string;
41
+ /** Mirrors the main transcript's thinking-block visibility. */
42
+ hideThinkingBlock?: () => boolean;
43
+ /** Keys toggling tool output expansion (app.tools.expand). */
44
+ expandKeys?: KeyId[];
45
+ /** Focus the main view on this agent's live session (ctx.focusAgentSession). When absent (collab guest, tests), Enter opens the in-hub chat view instead. */
46
+ focusAgent?: (id: string) => Promise<void>;
47
+ /** Current main session file; used to seed parked historical subagents after restart. */
48
+ sessionFile?: string | null;
31
49
  /** Collab guest: route actions/transcripts to the host instead of local sessions. */
32
50
  remote?: AgentHubRemote;
33
51
  }
@@ -3,8 +3,13 @@ import type { AgentSession } from "../../../session/agent-session";
3
3
  import type { CollabStatus, EffectiveStatusLineSettings, StatusLineSettings } from "./types";
4
4
  export declare class StatusLineComponent implements Component {
5
5
  #private;
6
- private readonly session;
6
+ private session;
7
7
  constructor(session: AgentSession);
8
+ /**
9
+ * Re-point the status line at another session (focus proxy). Invalidate: model/context/usage all derive
10
+ * from it. `focusedAgentId` is the focused subagent id while the view is proxied, undefined for main.
11
+ */
12
+ setSession(session: AgentSession, focusedAgentId?: string): void;
8
13
  updateSettings(settings: StatusLineSettings): void;
9
14
  getEffectiveSettingsForTest(): EffectiveStatusLineSettings;
10
15
  setAutoCompactEnabled(enabled: boolean): void;
@@ -45,6 +45,8 @@ export type EffectiveStatusLineSettings = Required<Pick<StatusLineSettings, "lef
45
45
  export type RGB = readonly [number, number, number];
46
46
  export interface SegmentContext {
47
47
  session: AgentSession;
48
+ /** Focused subagent id while the view is proxied at its session, undefined otherwise. */
49
+ focusedAgentId?: string | undefined;
48
50
  width: number;
49
51
  options: StatusLineSegmentOptions;
50
52
  planMode: {
@@ -23,6 +23,13 @@ export declare class EventController {
23
23
  */
24
24
  notifyInterrupting(): void;
25
25
  subscribeToAgent(): void;
26
+ /**
27
+ * Clear every transcript-anchored/turn-scoped piece of state. Used by the
28
+ * session focus proxy when re-pointing the transcript at another session:
29
+ * components, timers, and stream-reveal state all reference the previous
30
+ * session's transcript and must not bleed into the new one.
31
+ */
32
+ resetTranscriptAnchors(): void;
26
33
  handleEvent(event: AgentSessionEvent): Promise<void>;
27
34
  sendCompletionNotification(): void;
28
35
  }
@@ -1,4 +1,4 @@
1
- import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
1
+ import { type AutocompleteProvider, type SlashCommand } from "@oh-my-pi/pi-tui";
2
2
  import type { InteractiveModeContext } from "../../modes/types";
3
3
  import { readImageFromClipboard, readTextFromClipboard } from "../../utils/clipboard";
4
4
  export declare class InputController {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * SessionFocusController - Weak retargeting primitive between the rendering/
3
+ * input layer and the AgentSession it displays.
4
+ *
5
+ * Focusing re-points the transcript, streaming event subscription, status
6
+ * line, and editor prompt/interrupt at a subagent's live AgentSession (from
7
+ * AgentRegistry) without touching the main session underneath; unfocusing
8
+ * re-attaches the main session and rebuilds the transcript from its
9
+ * authoritative state.
10
+ */
11
+ import { AgentLifecycleManager } from "../../registry/agent-lifecycle";
12
+ import { AgentRegistry } from "../../registry/agent-registry";
13
+ import type { AgentSession } from "../../session/agent-session";
14
+ import type { InteractiveModeContext } from "../types";
15
+ export declare class SessionFocusController {
16
+ #private;
17
+ private ctx;
18
+ private registry;
19
+ private lifecycle;
20
+ constructor(ctx: InteractiveModeContext, registry?: AgentRegistry, lifecycle?: () => AgentLifecycleManager);
21
+ get focusedAgentId(): string | undefined;
22
+ /** Focused live session, undefined when unfocused. */
23
+ get target(): AgentSession | undefined;
24
+ /** Focus the main view on an agent's live session. Throws an Error with a user-displayable message. */
25
+ focusAgent(id: string): Promise<void>;
26
+ /** Focus the focused agent's parent agent, falling back to the main session. No-op when unfocused. */
27
+ focusParent(): Promise<void>;
28
+ /** Return to the main session. No-op when unfocused. */
29
+ unfocus(): Promise<void>;
30
+ dispose(): void;
31
+ }
@@ -50,6 +50,9 @@ export interface InteractiveModeOptions {
50
50
  * Build the anchored subagent HUD block: a bold accent "Subagents" header plus
51
51
  * one hooked row per running agent in the same `Id: description` shape the
52
52
  * inline task rows use (muted task preview when no description was given).
53
+ * Only detached background spawns are listed: a sync task call blocks the
54
+ * parent turn and its inline tool block already renders progress live, and
55
+ * eval `agent()` spawns are rendered by their own eval cell tree.
53
56
  * Returns an empty array when nothing is running so the container can clear.
54
57
  */
55
58
  export declare function renderSubagentHudLines(sessions: ObservableSession[], columns: number): string[];
@@ -128,6 +131,12 @@ export declare class InteractiveMode implements InteractiveModeContext {
128
131
  mcpManager?: MCPManager;
129
132
  get eventController(): EventController;
130
133
  get eventBus(): EventBus | undefined;
134
+ get viewSession(): AgentSession;
135
+ get focusedAgentId(): string | undefined;
136
+ focusAgentSession(id: string): Promise<void>;
137
+ focusParentSession(): Promise<void>;
138
+ unfocusSession(): Promise<void>;
139
+ clearTransientSessionUi(): void;
131
140
  constructor(session: AgentSession, version: string, changelogMarkdown?: string | undefined, setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void, lspServers?: LspStartupServerInfo[] | undefined, mcpManager?: MCPManager, eventBus?: EventBus, titleSystemPrompt?: string);
132
141
  playWelcomeIntro(): void;
133
142
  init(options?: InteractiveModeInitOptions): Promise<void>;
@@ -9,6 +9,13 @@ export interface ObservableSession {
9
9
  status: "active" | "completed" | "failed" | "aborted";
10
10
  sessionFile?: string;
11
11
  parentToolCallId?: string;
12
+ /**
13
+ * Spawn runs as a detached background job (parent turn not blocked on it).
14
+ * The anchored subagent HUD only lists detached spawns: sync task spawns
15
+ * and eval `agent()` spawns are already rendered live by their own inline
16
+ * tool block / eval cell.
17
+ */
18
+ detached?: boolean;
12
19
  index?: number;
13
20
  lastUpdate: number;
14
21
  /** Latest progress snapshot from the subagent executor */
@@ -6,7 +6,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.job" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -169,6 +169,7 @@ export declare class Theme {
169
169
  cost: string;
170
170
  time: string;
171
171
  pi: string;
172
+ ghost: string;
172
173
  agents: string;
173
174
  job: string;
174
175
  cache: string;
@@ -81,6 +81,18 @@ export interface InteractiveModeContext {
81
81
  statusLine: StatusLineComponent;
82
82
  session: AgentSession;
83
83
  sessionManager: SessionManager;
84
+ /** Session the transcript/editor/status are attached to: the focused agent's, else `session`. */
85
+ readonly viewSession: AgentSession;
86
+ /** Id of the focused agent, undefined when the main session is attached. */
87
+ readonly focusedAgentId: string | undefined;
88
+ /** Focus the main view on an agent's live session (delegates to SessionFocusController.focusAgent). */
89
+ focusAgentSession(id: string): Promise<void>;
90
+ /** Focus the focused agent's parent session, falling back to main (delegates to focusParent). */
91
+ focusParentSession(): Promise<void>;
92
+ /** Return the view to the main session (delegates to SessionFocusController.unfocus). */
93
+ unfocusSession(): Promise<void>;
94
+ /** Clear loader, status/pending containers, streaming state, and pending tools. */
95
+ clearTransientSessionUi(): void;
84
96
  settings: Settings;
85
97
  keybindings: KeybindingsManager;
86
98
  agent: AgentSession["agent"];
@@ -341,6 +341,8 @@ export declare class AgentSession {
341
341
  getMnemopiSessionState(): MnemopiSessionState | undefined;
342
342
  /** TTSR manager for time-traveling stream rules */
343
343
  get ttsrManager(): TtsrManager | undefined;
344
+ /** Secret obfuscator, when secrets are configured; /share redaction reuses it. */
345
+ get obfuscator(): SecretObfuscator | undefined;
344
346
  /** Whether a TTSR abort is pending (stream was aborted to inject rules) */
345
347
  get isTtsrAbortPending(): boolean;
346
348
  /** Whether the plan-mode → compaction transition's expected internal abort is
@@ -24,8 +24,8 @@
24
24
  * eligibility off exact limit ids (`openai-codex:primary` /
25
25
  * `openai-codex:secondary`) and `usedFraction`, never off `status`.
26
26
  *
27
- * ANTI-WASTE GATES (in evaluation order): the policy must be OFF unless opted
28
- * in; the active model must be Codex (not Spark — a Spark block lives on a
27
+ * ANTI-WASTE GATES (in evaluation order): the policy must not be set to "no";
28
+ * the active model must be Codex (not Spark — a Spark block lives on a
29
29
  * separate meter and it is unknown whether a credit even resets it); a fresh
30
30
  * usage report for the active account must confirm `limitReached`; the WEEKLY
31
31
  * (secondary) window must be genuinely exhausted — a 5h-only block self-heals
@@ -38,6 +38,7 @@
38
38
  * read-only views are passed in so the predicate itself stays deterministic.
39
39
  */
40
40
  import type { OAuthAccountIdentity, ResetCreditTarget, UsageReport } from "@oh-my-pi/pi-ai";
41
+ import type { CodexAutoRedeemMode } from "../config/settings-schema";
41
42
  /** Weekly window counts as exhausted at `usedFraction >= 0.999` (used_percent >= 99.9). */
42
43
  export declare const WEEKLY_EXHAUSTED_MIN_FRACTION = 0.999;
43
44
  /** A weekly reset can never be more than one window length (7d) away; +1h slack for skew. */
@@ -48,6 +49,8 @@ export declare const REPORT_FRESHNESS_MS: number;
48
49
  export declare const ATTEMPT_COOLDOWN_MS = 60000;
49
50
  /** Minute bucket for blockKey, absorbing `reset_after_seconds`-derived jitter. */
50
51
  export declare const DEBOUNCE_BUCKET_MS = 60000;
52
+ export declare function shouldEvaluateCodexAutoRedeem(mode: CodexAutoRedeemMode): boolean;
53
+ export declare function shouldPromptCodexAutoRedeem(mode: CodexAutoRedeemMode): boolean;
51
54
  export type CodexAutoRedeemSkipReason = "disabled" | "wrong-provider" | "spark-model" | "no-identity" | "no-report" | "stale-report" | "not-limit-reached" | "weekly-not-exhausted" | "no-reset-time" | "reset-too-soon" | "reset-implausible" | "credits-unknown" | "reserve" | "already-attempted" | "cooldown";
52
55
  export interface CodexAutoRedeemInput {
53
56
  nowMs: number;
@@ -67,7 +70,7 @@ export interface CodexAutoRedeemInput {
67
70
  attemptedBlockKeys: ReadonlySet<string>;
68
71
  lastAttemptAtByAccount: ReadonlyMap<string, number>;
69
72
  }
70
- export type CodexAutoRedeemDecision = {
73
+ export interface CodexAutoRedeemRedeemDecision {
71
74
  redeem: true;
72
75
  target: ResetCreditTarget;
73
76
  accountKey: string;
@@ -75,7 +78,8 @@ export type CodexAutoRedeemDecision = {
75
78
  weeklyResetAtMs: number;
76
79
  remainingMs: number;
77
80
  availableCount: number;
78
- } | {
81
+ }
82
+ export type CodexAutoRedeemDecision = CodexAutoRedeemRedeemDecision | {
79
83
  redeem: false;
80
84
  reason: CodexAutoRedeemSkipReason;
81
85
  };
@@ -54,6 +54,13 @@ export interface ExecutorOptions {
54
54
  index: number;
55
55
  id: string;
56
56
  parentToolCallId?: string;
57
+ /**
58
+ * Spawn runs as a detached background job (parent turn not blocked on it).
59
+ * Rides the subagent lifecycle/progress payloads so HUD-style surfaces can
60
+ * skip spawns the transcript already renders inline. See
61
+ * {@link SubagentLifecyclePayload.detached}.
62
+ */
63
+ detached?: boolean;
57
64
  modelOverride?: string | string[];
58
65
  /**
59
66
  * Active model selector of the parent session, used as an auth-aware fallback
@@ -25,6 +25,8 @@ export interface SubagentProgressPayload {
25
25
  assignment?: string;
26
26
  progress: AgentProgress;
27
27
  sessionFile?: string;
28
+ /** See {@link SubagentLifecyclePayload.detached}. */
29
+ detached?: boolean;
28
30
  }
29
31
  /** Payload emitted on TASK_SUBAGENT_EVENT_CHANNEL */
30
32
  export interface SubagentEventPayload {
@@ -41,6 +43,13 @@ export interface SubagentLifecyclePayload {
41
43
  sessionFile?: string;
42
44
  parentToolCallId?: string;
43
45
  index: number;
46
+ /**
47
+ * Spawn runs as a detached background job: the parent turn keeps working
48
+ * while this agent runs. Sync task spawns (parent blocked on the call) and
49
+ * eval `agent()` bridge spawns (rendered inside their eval cell) leave this
50
+ * unset — surfaces like the subagent HUD only list detached spawns.
51
+ */
52
+ detached?: boolean;
44
53
  }
45
54
  export declare const taskItemSchema: z.ZodObject<{
46
55
  id: z.ZodOptional<z.ZodString>;