@oh-my-pi/pi-coding-agent 17.3.3 → 17.3.5

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 (154) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/{CHANGELOG-s5t1xdmy.md → CHANGELOG-tt9k4jpr.md} +81 -0
  3. package/dist/cli.js +3724 -3705
  4. package/dist/docs-index.generated.txt +1 -1
  5. package/dist/types/config/model-discovery.d.ts +2 -0
  6. package/dist/types/config/model-registry.d.ts +6 -1
  7. package/dist/types/config/settings-schema.d.ts +10 -0
  8. package/dist/types/debug/report-bundle.d.ts +7 -2
  9. package/dist/types/extensibility/extensions/loader.d.ts +2 -0
  10. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  11. package/dist/types/extensibility/extensions/types.d.ts +2 -0
  12. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +4 -0
  13. package/dist/types/launch/presence.d.ts +11 -0
  14. package/dist/types/lsp/client.d.ts +22 -0
  15. package/dist/types/lsp/workspace-diagnostics.d.ts +2 -0
  16. package/dist/types/markit/converters/pdf/index.d.ts +2 -1
  17. package/dist/types/mcp/config.d.ts +3 -1
  18. package/dist/types/modes/components/tool-execution.d.ts +3 -1
  19. package/dist/types/registry/agent-lifecycle.d.ts +11 -0
  20. package/dist/types/registry/agent-registry.d.ts +4 -0
  21. package/dist/types/session/agent-session-types.d.ts +0 -2
  22. package/dist/types/session/agent-session.d.ts +20 -0
  23. package/dist/types/session/date-cwd-reminder.d.ts +22 -0
  24. package/dist/types/session/irc-bridge.d.ts +2 -0
  25. package/dist/types/session/messages.d.ts +4 -0
  26. package/dist/types/session/session-loader.d.ts +18 -11
  27. package/dist/types/session/session-maintenance.d.ts +6 -2
  28. package/dist/types/session/session-manager.d.ts +19 -3
  29. package/dist/types/session/session-tools.d.ts +0 -1
  30. package/dist/types/session/turn-recovery.d.ts +7 -7
  31. package/dist/types/tools/browser/attach.d.ts +16 -0
  32. package/dist/types/tools/browser/launch.d.ts +2 -2
  33. package/dist/types/tools/browser.d.ts +1 -1
  34. package/dist/types/tools/builtin-names.d.ts +1 -1
  35. package/dist/types/tools/hub/jobs.d.ts +6 -0
  36. package/dist/types/tools/hub/types.d.ts +6 -0
  37. package/dist/types/tools/read-pdf.d.ts +15 -0
  38. package/dist/types/utils/external-editor.d.ts +7 -0
  39. package/dist/types/utils/markit.d.ts +3 -4
  40. package/dist/types/vibe/runtime.d.ts +3 -3
  41. package/package.json +13 -16
  42. package/scripts/build-binary.ts +0 -2
  43. package/scripts/bundle-dist.ts +0 -1
  44. package/src/auto-thinking/classifier.ts +37 -23
  45. package/src/cli/models-cli.ts +1 -1
  46. package/src/cli/read-cli.ts +2 -0
  47. package/src/commit/analysis/conventional.ts +15 -9
  48. package/src/commit/analysis/summary.ts +15 -9
  49. package/src/commit/changelog/generate.ts +15 -9
  50. package/src/commit/map-reduce/map-phase.ts +3 -19
  51. package/src/commit/map-reduce/reduce-phase.ts +15 -9
  52. package/src/config/model-discovery.ts +3 -3
  53. package/src/config/model-registry.ts +29 -16
  54. package/src/config/settings-schema.ts +13 -0
  55. package/src/debug/report-bundle.ts +25 -59
  56. package/src/discovery/claude-plugins.ts +2 -1
  57. package/src/discovery/claude.ts +4 -2
  58. package/src/discovery/helpers.ts +5 -0
  59. package/src/edit/renderer.ts +48 -19
  60. package/src/extensibility/extensions/loader.ts +9 -5
  61. package/src/extensibility/extensions/runner.ts +124 -21
  62. package/src/extensibility/extensions/types.ts +2 -0
  63. package/src/extensibility/extensions/wrapper.ts +16 -10
  64. package/src/extensibility/legacy-pi-ai-shim.ts +4 -0
  65. package/src/extensibility/plugins/marketplace/manager.ts +15 -18
  66. package/src/irc/bus.ts +1 -1
  67. package/src/launch/broker.ts +8 -1
  68. package/src/launch/presence.ts +77 -1
  69. package/src/lsp/client.ts +35 -3
  70. package/src/lsp/clients/biome-client.ts +47 -93
  71. package/src/lsp/servers.ts +17 -11
  72. package/src/lsp/tool.ts +2 -4
  73. package/src/lsp/workspace-diagnostics.ts +24 -2
  74. package/src/markit/NOTICE +8 -8
  75. package/src/markit/converters/pdf/index.ts +14 -126
  76. package/src/mcp/client.ts +8 -9
  77. package/src/mcp/config.ts +50 -6
  78. package/src/memories/index.ts +29 -25
  79. package/src/mnemopi/backend.ts +13 -11
  80. package/src/modes/components/ask-dialog.ts +25 -5
  81. package/src/modes/components/tool-execution.ts +10 -6
  82. package/src/modes/components/welcome.ts +4 -1
  83. package/src/modes/controllers/extension-ui-controller.ts +52 -32
  84. package/src/modes/controllers/selector-controller.ts +6 -0
  85. package/src/modes/controllers/tan-command-controller.ts +1 -0
  86. package/src/modes/interactive-mode.ts +49 -10
  87. package/src/modes/rpc/rpc-client.ts +7 -0
  88. package/src/modes/theme/defaults/birch.json +2 -2
  89. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  90. package/src/prompts/system/checkpoint-active-notice.md +5 -0
  91. package/src/prompts/system/date-cwd-reminder.md +3 -0
  92. package/src/prompts/system/project-prompt.md +0 -1
  93. package/src/prompts/system/rewind-report.md +1 -3
  94. package/src/prompts/tools/browser.md +1 -0
  95. package/src/prompts/tools/rewind.md +1 -13
  96. package/src/registry/agent-lifecycle.ts +34 -0
  97. package/src/registry/agent-registry.ts +27 -2
  98. package/src/registry/persisted-agents.ts +40 -20
  99. package/src/sdk.ts +32 -2
  100. package/src/session/agent-session-types.ts +0 -2
  101. package/src/session/agent-session.ts +178 -46
  102. package/src/session/date-cwd-reminder.ts +77 -0
  103. package/src/session/irc-bridge.ts +5 -0
  104. package/src/session/messages.ts +5 -1
  105. package/src/session/session-loader.ts +106 -64
  106. package/src/session/session-maintenance.ts +189 -115
  107. package/src/session/session-manager.ts +142 -35
  108. package/src/session/session-persistence.ts +4 -4
  109. package/src/session/session-storage.ts +18 -5
  110. package/src/session/session-tools.ts +5 -10
  111. package/src/session/turn-recovery.ts +102 -20
  112. package/src/session/unexpected-stop-classifier.ts +33 -21
  113. package/src/system-prompt.ts +0 -5
  114. package/src/task/executor.ts +4 -15
  115. package/src/task/persisted-revive.ts +3 -6
  116. package/src/tools/ask.ts +10 -3
  117. package/src/tools/browser/attach.ts +80 -25
  118. package/src/tools/browser/launch.ts +44 -15
  119. package/src/tools/browser/relay/daemon.ts +3 -7
  120. package/src/tools/browser/render.ts +1 -1
  121. package/src/tools/browser/shared-daemon.ts +3 -7
  122. package/src/tools/browser.ts +5 -5
  123. package/src/tools/builtin-names.ts +7 -3
  124. package/src/tools/checkpoint.ts +1 -7
  125. package/src/tools/hub/index.ts +1 -1
  126. package/src/tools/hub/jobs.ts +20 -3
  127. package/src/tools/hub/types.ts +6 -0
  128. package/src/tools/read-pdf.ts +135 -0
  129. package/src/tools/read.ts +63 -37
  130. package/src/tools/render-utils.ts +53 -21
  131. package/src/tools/think.ts +15 -3
  132. package/src/tts/speech-enhancer.ts +19 -14
  133. package/src/utils/commit-message-generator.ts +14 -12
  134. package/src/utils/external-editor.ts +24 -5
  135. package/src/utils/markit.ts +6 -44
  136. package/src/utils/title-generator.ts +23 -19
  137. package/src/vibe/runtime.ts +32 -17
  138. package/dist/types/markit/converters/pdf/columns.d.ts +0 -35
  139. package/dist/types/markit/converters/pdf/extract.d.ts +0 -10
  140. package/dist/types/markit/converters/pdf/grid.d.ts +0 -25
  141. package/dist/types/markit/converters/pdf/headers.d.ts +0 -24
  142. package/dist/types/markit/converters/pdf/render.d.ts +0 -24
  143. package/dist/types/markit/converters/pdf/types.d.ts +0 -75
  144. package/dist/types/tools/read-pdf-images.d.ts +0 -12
  145. package/dist/types/utils/mupdf-wasm-embed.d.ts +0 -1
  146. package/scripts/embed-mupdf-wasm.ts +0 -67
  147. package/src/markit/converters/pdf/columns.ts +0 -103
  148. package/src/markit/converters/pdf/extract.ts +0 -598
  149. package/src/markit/converters/pdf/grid.ts +0 -780
  150. package/src/markit/converters/pdf/headers.ts +0 -106
  151. package/src/markit/converters/pdf/render.ts +0 -501
  152. package/src/markit/converters/pdf/types.ts +0 -84
  153. package/src/tools/read-pdf-images.ts +0 -250
  154. package/src/utils/mupdf-wasm-embed.ts +0 -12
@@ -93,6 +93,8 @@ export declare function discoverLiteLLMModels(providerConfig: DiscoveryProviderC
93
93
  * URL (which ends in `/v1`) round-trips correctly.
94
94
  */
95
95
  export declare function discoverProxyModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
96
+ export declare function normalizeLlamaCppBaseUrl(baseUrl?: string): string;
97
+ export declare function ensureLlamaCppV1BaseUrl(baseUrl: string): string;
96
98
  export declare function normalizeLiteLLMDiscoveryBaseUrl(baseUrl?: string): string;
97
99
  export declare function normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string;
98
100
  export {};
@@ -106,10 +106,15 @@ export declare class ModelRegistry {
106
106
  *
107
107
  * Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
108
108
  * configured by its presence alone — the program is NOT executed — and OAuth
109
- * tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
109
+ * tokens are NOT refreshed (`authStorage.hasResolvableAuth`). This is what keeps the
110
110
  * model-switch pre-flight off the event loop's hot path; the real key
111
111
  * (command execution + OAuth refresh) is resolved lazily per request via
112
112
  * {@link ModelRegistry.resolver}.
113
+ *
114
+ * Cross-provider env aliases count here (`xai-oauth` can borrow `XAI_API_KEY`)
115
+ * so an explicit `xai-oauth/…` selector does not fail with "No API key".
116
+ * Default-model availability still uses {@link AuthStorage.hasAuth}, which
117
+ * ignores that alias so SuperGrok is not auto-selected from a paid key.
113
118
  */
114
119
  hasConfiguredAuth(model: Model<Api>): boolean;
115
120
  /**
@@ -6061,6 +6061,16 @@ export declare const SETTINGS_SCHEMA: {
6061
6061
  readonly type: "number";
6062
6062
  readonly default: 120000;
6063
6063
  };
6064
+ readonly "extensionHandlers.toolCallTimeoutMs": {
6065
+ readonly type: "number";
6066
+ readonly default: 30000;
6067
+ readonly ui: {
6068
+ readonly tab: "tools";
6069
+ readonly group: "Extensions";
6070
+ readonly label: "Tool Call Handler Timeout (ms)";
6071
+ readonly description: "Positive finite active-work timeout for extension tool_call handlers; invalid values use 30000ms, and time awaiting OMP-owned dialogs does not count";
6072
+ };
6073
+ };
6064
6074
  readonly "dev.autoqa": {
6065
6075
  readonly type: "boolean";
6066
6076
  readonly default: true;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Debug report bundle creation.
3
+ *
4
+ * Creates a .tar.gz archive with session data, logs, system info, and optional profiling data.
5
+ */
1
6
  import type { WorkProfile } from "@oh-my-pi/pi-natives";
2
7
  import type { CpuProfile, HeapSnapshot } from "./profiler.js";
3
8
  export interface ReportBundleOptions {
@@ -28,8 +33,8 @@ export interface DebugLogSource {
28
33
  *
29
34
  * Bundle contents:
30
35
  * - session.jsonl: Current session transcript
31
- * - artifacts/: Session artifacts directory
32
- * - subagents/: Subagent sessions + artifacts
36
+ * - artifacts/: Current session's artifacts subtree (recursive), including any
37
+ * subagent session transcripts nested under it
33
38
  * - logs.txt: Recent log entries
34
39
  * - system.json: OS, arch, CPU, memory, versions
35
40
  * - env.json: Sanitized environment variables
@@ -63,6 +63,8 @@ export declare function loadExtensions(paths: string[], cwd: string, eventBus?:
63
63
  export interface DiscoverExtensionPathOptions {
64
64
  /** Include ambient native extensions, hooks, and installed plugins. */
65
65
  ambient?: boolean;
66
+ /** Include ambient hook factories. Disable for read-only catalog commands. */
67
+ includeAmbientHooks?: boolean;
66
68
  }
67
69
  export declare function discoverExtensionPaths(configuredPaths: string[], cwd: string, disabledExtensionIds?: string[], options?: DiscoverExtensionPathOptions): Promise<string[]>;
68
70
  /**
@@ -224,8 +224,8 @@ export declare class ExtensionRunner {
224
224
  /**
225
225
  * Emit a `tool_call` event to every subscribed extension before the tool executes.
226
226
  *
227
- * Each handler is bounded by `extensionHandlerTimeoutMs` (default 30s). This
228
- * matches the timeout policy already applied to `emitToolResult` and every
227
+ * Each handler is bounded by `extensionHandlers.toolCallTimeoutMs` (default
228
+ * 30s). This matches the timeout policy already applied to `emitToolResult` and every
229
229
  * other handler routed through `#runHandlerWithTimeout`; without it a single
230
230
  * hung extension (unresolved `await`, network call with no timeout) would
231
231
  * park `ExtensionToolWrapper.execute` indefinitely and freeze tool
@@ -236,7 +236,7 @@ export declare class ExtensionRunner {
236
236
  * pre-execution gate — an unresponsive extension MUST NOT be treated as
237
237
  * silent consent to run the tool.
238
238
  */
239
- emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined>;
239
+ emitToolCall(event: ToolCallEvent, signal?: AbortSignal): Promise<ToolCallEventResult | undefined>;
240
240
  emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined>;
241
241
  emitUserPython(event: UserPythonEvent): Promise<UserPythonEventResult | undefined>;
242
242
  private emitUserEvent;
@@ -138,6 +138,8 @@ export interface ExtensionCustomOptions {
138
138
  overlayOptions?: OverlayOptions | (() => OverlayOptions);
139
139
  /** Invoked with the overlay handle once the overlay is created (overlay mode only). */
140
140
  onHandle?: (handle: OverlayHandle) => void;
141
+ /** Abort the custom UI and reject its promise. */
142
+ signal?: AbortSignal;
141
143
  }
142
144
  /** Wrap the current autocomplete provider with additional behavior (pi-compatible). */
143
145
  export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider;
@@ -59,6 +59,10 @@ export declare const getModels: typeof getBundledModels;
59
59
  *
60
60
  * Legacy `/compat` callers pass {@link SimpleStreamOptions}; routing through
61
61
  * `streamSimple` preserves option normalization before provider dispatch.
62
+ *
63
+ * Transient-failure retry (overload, rate-limit, 5xx) is the **caller's
64
+ * responsibility**. Oneshot callers that collect the full result before acting
65
+ * should wrap with `retryTransientCompletion` from `@oh-my-pi/pi-ai`.
62
66
  */
63
67
  export declare function streamSimpleOpenAIResponses(model: Model<"openai-responses">, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
64
68
  /**
@@ -6,3 +6,14 @@ export interface DaemonProjectPresence {
6
6
  export declare function registerDaemonProjectPresence(projectDir: string, runtimeOverride?: string): Promise<DaemonProjectPresence>;
7
7
  /** Return whether a registered omp process in this runtime directory is still alive. */
8
8
  export declare function hasLiveDaemonProjectPresence(runtimeDir: string): Promise<boolean>;
9
+ /**
10
+ * Remove sibling project daemon runtime directories whose broker is dead and
11
+ * whose client-presence set is empty, reclaiming the disk that short-lived
12
+ * project directories leave behind (issue #8674).
13
+ *
14
+ * Best-effort and non-throwing: a scope is deleted only when its `broker.pid`
15
+ * is absent/dead, no live client presence remains, and it has been untouched
16
+ * for {@link DAEMON_RUNTIME_STALE_GRACE_MS}. The caller's own `currentRuntimeDir`
17
+ * and the machine-global daemon container are always skipped.
18
+ */
19
+ export declare function pruneDeadDaemonRuntimeDirs(currentRuntimeDir: string): Promise<void>;
@@ -6,6 +6,21 @@ export declare function setSharedLspEnabled(enabled: boolean): void;
6
6
  * @param ms - Timeout in milliseconds, or null/undefined to disable
7
7
  */
8
8
  export declare function setIdleTimeout(ms: number | null | undefined): void;
9
+ /**
10
+ * Whether a client may be reaped by the idle checker.
11
+ *
12
+ * A client with in-flight requests is *busy*, never idle. `lastActivity` is
13
+ * stamped when a request is written, not while it is outstanding, so a single
14
+ * request that runs longer than the idle timeout used to look like silence:
15
+ * the checker tore the client down mid-flight and `shutdownClientInstance`
16
+ * rejected the caller's still-pending promise with "LSP client shutdown"
17
+ * (issue #8390). Requests that settle refresh `lastActivity`, so a client
18
+ * becomes eligible again only after the final one lands and the full idle
19
+ * window then elapses.
20
+ *
21
+ * Exported for tests; the idle checker is the only production caller.
22
+ */
23
+ export declare function isIdleClient(client: LspClient, now: number, timeoutMs: number): boolean;
9
24
  /** LSP `FileChangeType` values for workspace/didChangeWatchedFiles notifications. */
10
25
  export declare enum FileChangeType {
11
26
  Created = 1,
@@ -30,6 +45,13 @@ export declare function applyWorkspaceEditWithLsp(edit: WorkspaceEdit, cwd: stri
30
45
  export declare function supportsDocumentDiagnostics(client: LspClient): boolean;
31
46
  /** Timeout for warmup initialize requests (5 seconds) */
32
47
  export declare const WARMUP_TIMEOUT_MS = 5000;
48
+ /**
49
+ * True when this client speaks the rust-analyzer protocol, detected by the
50
+ * command basename (`rust-analyzer[.exe]`) of the configured or resolved
51
+ * binary. Callers use it to gate rust-analyzer-only requests such as
52
+ * `rust-analyzer/reloadWorkspace` (see {@link reloadServer}).
53
+ */
54
+ export declare function isRustAnalyzerClient(client: LspClient): boolean;
33
55
  /** Allow an explicit user reload to retry a matching initialization failure immediately. */
34
56
  export declare function clearInitializationFailure(config: ServerConfig, cwd: string): void;
35
57
  /**
@@ -4,6 +4,8 @@ interface ProjectType {
4
4
  command?: string[];
5
5
  description: string;
6
6
  }
7
+ /** Interpret an empty checker result without mistaking a crash for a clean workspace. */
8
+ export declare function interpretEmptyDiagnosticsResult(exitCode: number, signalCode: string | null, command: readonly string[]): string;
7
9
  /** Run workspace diagnostics command and parse output */
8
10
  export declare function runWorkspaceDiagnostics(cwd: string, signal?: AbortSignal): Promise<{
9
11
  output: string;
@@ -1,6 +1,7 @@
1
1
  import type { ConversionResult, Converter, StreamInfo } from "../../types.js";
2
+ /** Converts PDF buffers to Markdown through the native `pdf-inspector` bridge. */
2
3
  export declare class PdfConverter implements Converter {
3
4
  name: string;
4
5
  accepts(streamInfo: StreamInfo): boolean;
5
- convert(input: Buffer, streamInfo: StreamInfo): Promise<ConversionResult>;
6
+ convert(input: Buffer, _streamInfo: StreamInfo): Promise<ConversionResult>;
6
7
  }
@@ -50,7 +50,9 @@ export interface ExaFilterResult {
50
50
  }
51
51
  /**
52
52
  * Filter out Exa MCP servers and extract their API keys.
53
- * Since we have native Exa integration, we don't need the MCP server.
53
+ * Since we have native Exa integration, we don't need the MCP server
54
+ * unless the config explicitly requests Exa tools the native integration
55
+ * does not provide (e.g. `web_fetch_exa`, `web_search_advanced_exa`).
54
56
  */
55
57
  export declare function filterExaMCPServers(configs: Record<string, MCPServerConfig>, sources: Record<string, SourceMeta>): ExaFilterResult;
56
58
  /**
@@ -119,8 +119,10 @@ export declare class ToolExecutionComponent extends Container implements NativeS
119
119
  * Reports `false` while it can still visually change so the
120
120
  * {@link TranscriptContainer} keeps it inside the repaintable live region:
121
121
  * a foreground tool awaiting its result, or one streaming partial output.
122
+ * Hidden blocks render no rows, so they cannot gate later streaming content.
123
+ * Visibility toggles reset and replay the transcript before revealing them.
122
124
  * A final (non-partial) result, a background-async tool the agent has moved
123
- * past, or an explicit {@link seal} flips it to `true`.
125
+ * past, or an explicit {@link seal} also flips it to `true`.
124
126
  */
125
127
  isTranscriptBlockFinalized(): boolean;
126
128
  /**
@@ -58,6 +58,17 @@ export declare class AgentLifecycleManager {
58
58
  adopt(id: string, opts: AdoptOptions, expected?: AgentRefExpectation): void;
59
59
  /** True if the id is adopted (parked or live) — and, when `expected` is given, still bound to that ref. */
60
60
  has(id: string, expected?: AgentRefExpectation): boolean;
61
+ /**
62
+ * Reclaim a provably-dead parked corpse so a fresh spawn can reuse its id.
63
+ * Refuses live, adopted, in-flight, or cold-revivable refs. For a parked ref
64
+ * restored from disk, the persisted factory is consulted before removal
65
+ * because cold revivers are created lazily by {@link ensureLive}.
66
+ *
67
+ * Only refs in the registry this manager owns are touched; the transcript
68
+ * stays readable at `history://<id>`. Returns true when the corpse was
69
+ * unregistered.
70
+ */
71
+ reclaimDeadCorpse(id: string, expected: AgentRef): Promise<boolean>;
61
72
  /**
62
73
  * True when this manager owns `registry` — i.e. its adopt/park/revive state
63
74
  * describes that registry's refs. Lets a caller holding a specific registry
@@ -145,6 +145,10 @@ export declare class AgentRegistry {
145
145
  * Flat namespace: every other agent is visible.
146
146
  */
147
147
  listVisibleTo(id: string): AgentRef[];
148
+ /** Whether a ref's claimed running state is corroborated by its attached live session. */
149
+ isRunning(ref: AgentRef): boolean;
150
+ /** Mirror a session's authoritative run-state notifications into its owned registry ref. */
151
+ syncSessionStatus(id: string, session: AgentSession): () => void;
148
152
  onChange(listener: RegistryListener): () => void;
149
153
  }
150
154
  export {};
@@ -185,8 +185,6 @@ export interface AgentSessionConfig {
185
185
  systemPrompt: string[];
186
186
  xdevCatalogNames?: readonly string[];
187
187
  }>;
188
- /** Local calendar date provider used by prompt-cache invalidation. */
189
- getLocalCalendarDate?: () => string;
190
188
  /** Tools mounted under `xd://`, for `/tools` display. */
191
189
  getXdevToolEntries?: () => Array<{
192
190
  name: string;
@@ -182,6 +182,11 @@ export declare class AgentSession {
182
182
  * Multiple listeners can be added. Returns unsubscribe function for this listener.
183
183
  */
184
184
  subscribe(listener: AgentSessionEventListener): () => void;
185
+ /**
186
+ * Observe authoritative run-state transitions before public `agent_end`
187
+ * deferral, for lifecycle owners that must not remain stale while prompts unwind.
188
+ */
189
+ subscribeRunState(listener: (state: "running" | "idle") => void): () => void;
185
190
  /** Register cleanup that runs when this AgentSession adopts a different session ID. */
186
191
  registerSessionChangeCallback(callback: () => void): () => void;
187
192
  subscribeCommandMetadataChanged(listener: CommandMetadataChangedListener): () => void;
@@ -522,6 +527,21 @@ export declare class AgentSession {
522
527
  * flipping advisor auto-resume.
523
528
  */
524
529
  followUp(text: string, images?: ImageContent[], options?: FollowUpOptions): Promise<void>;
530
+ /**
531
+ * Run a mode-exit `teardown` (abort the active turn, swap the toolset, clear
532
+ * mode state) with queued-message auto-resume suppressed, then re-arm the
533
+ * drain so a queued user turn resumes cleanly once the previous toolset is
534
+ * back.
535
+ *
536
+ * `abort()`'s stranded-queue drain runs from its own `finally`; without this
537
+ * guard a queued steer/follow-up behind the aborted turn would start a fresh
538
+ * `agent.continue()` during the teardown's `await`s — while the exiting mode's
539
+ * tools/context are still live — and then have those tools removed underneath
540
+ * it, reintroducing the mode's stale-tool failure on the restarted turn
541
+ * (issue #8326). Suppressing the drain across teardown guarantees the queued
542
+ * turn resumes only after teardown, so it runs as a clean non-mode turn.
543
+ */
544
+ runModeExitTeardown(teardown: () => Promise<void>): Promise<void>;
525
545
  queueDeferredMessage(message: CustomMessage): void;
526
546
  queueLaunchCompletion(notification: DaemonCompletionNotification): Promise<void>;
527
547
  /**
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Date/cwd reminder injection.
3
+ *
4
+ * The system prompt must stay byte-stable so open-weight chat templates that
5
+ * render tool schemas *after* the system content keep their prefix cache
6
+ * (#7404). The per-request date/cwd line used to live at the tail of the
7
+ * system prompt (`project-prompt.md`), which invalidated the whole tool array
8
+ * on every directory change or day rollover. It now rides on the first user
9
+ * turn of each provider request instead: built at request time (never stored
10
+ * in the session), deterministic per `(date, cwd)`, so the bytes are stable
11
+ * for the lifetime of a session/day and refresh automatically at midnight.
12
+ */
13
+ import type { Context, Message } from "@oh-my-pi/pi-ai";
14
+ /** Renders the reminder text for the given local calendar date and cwd. */
15
+ export declare function renderDateCwdReminder(date: string, cwd: string): string;
16
+ export declare function injectDateCwdReminder(messages: Message[], reminder: string): Message[];
17
+ /**
18
+ * Applies the date/cwd reminder to a provider `Context`, keeping the system
19
+ * prompt byte-stable for prompt caching. Skips NULL_PROMPT-style contexts
20
+ * (empty system prompt) so a no-prompt session stays byte-for-byte unchanged.
21
+ */
22
+ export declare function withDateCwdReminder(context: Context, date: string, cwd: string): Context;
@@ -30,6 +30,8 @@ export declare class IrcBridge {
30
30
  hasPending(): boolean;
31
31
  /** Takes every queued IRC record in interrupt-before-aside order. */
32
32
  drainPending(): CustomMessage[];
33
+ /** Queues records whose idle wake must wait for a session transition to finish. */
34
+ deferWake(records: CustomMessage[]): void;
33
35
  /** Surfaces and consumes queued incoming records before automatic injection. */
34
36
  drainInboxMessages(agentId: string, opts?: {
35
37
  from?: string;
@@ -47,6 +47,8 @@ export type CustomMessagePayload<T = unknown> = string | Partial<Pick<CustomMess
47
47
  export type NormalizedCustomMessagePayload<T = unknown> = Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">;
48
48
  /** Custom message type for hidden interrupted-thinking continuity context. */
49
49
  export declare const INTERRUPTED_THINKING_MESSAGE_TYPE = "interrupted-thinking";
50
+ /** Custom message type for the transient checkpoint-active reminder. */
51
+ export declare const CHECKPOINT_ACTIVE_REMINDER_TYPE = "checkpoint-active-reminder";
50
52
  /** Metadata persisted with a hidden interrupted-thinking continuity message. */
51
53
  export interface InterruptedThinkingDetails {
52
54
  interruptedAt: number;
@@ -288,6 +290,8 @@ export declare function bashExecutionToText(msg: BashExecutionMessage): string;
288
290
  */
289
291
  export declare function pythonExecutionToText(msg: PythonExecutionMessage): string;
290
292
  export declare function sanitizeRehydratedOpenAIResponsesAssistantMessage(message: AssistantMessage): AssistantMessage;
293
+ /** True for a `/skill:<name>` prompt the user invoked directly (attribution `user`), as opposed to an agent/autoload injection. */
294
+ export declare function isUserInvokedSkillPrompt(message: CustomMessage): boolean;
291
295
  /**
292
296
  * Transform AgentMessages (including custom types) to LLM-compatible Messages.
293
297
  *
@@ -1,6 +1,6 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import { BlobStore } from "./blob-store.js";
3
- import { type FileEntry, type SessionTitleSlotEntry } from "./session-entries.js";
3
+ import type { FileEntry } from "./session-entries.js";
4
4
  import { type SessionStorage } from "./session-storage.js";
5
5
  import { type SessionTitleUpdate } from "./session-title-slot.js";
6
6
  export interface VisitEntriesFromFileStreamOptions {
@@ -12,25 +12,32 @@ export interface VisitEntriesFromFileStreamOptions {
12
12
  yieldEveryBytes?: number;
13
13
  /** Yield to the macrotask queue after this many entries have been visited. */
14
14
  yieldEveryEntries?: number;
15
+ /** Called once for every malformed JSONL record skipped by the stream. */
16
+ onMalformedRecord?: () => void;
15
17
  }
16
- /** Parse session JSONL while stripping and folding the optional fixed title slot. */
17
- export declare function parseSessionContent(content: string): {
18
+ /** Parsed session entries plus corruption metadata needed by writable loaders. */
19
+ export interface SessionLoadResult {
18
20
  entries: FileEntry[];
19
21
  titleSlot: SessionTitleUpdate | undefined;
20
- };
22
+ malformedRecords: number;
23
+ }
24
+ /** Parse session JSONL while stripping and folding the optional fixed title slot. */
25
+ export declare function parseSessionContent(content: string): SessionLoadResult;
21
26
  /** Parse session JSONL and visit each entry without retaining prior entries. */
22
27
  export declare function visitEntriesFromFileStream(filePath: string, visit: (entry: FileEntry) => void | boolean, options?: VisitEntriesFromFileStreamOptions): Promise<SessionTitleUpdate | undefined>;
23
28
  /** Exported for testing — the ≥8MiB streaming path (works on any file size). */
24
- export declare function loadEntriesFromFileStream(filePath: string): Promise<{
25
- entries: FileEntry[];
26
- titleSlot: SessionTitleUpdate | undefined;
27
- }>;
28
- /** Read only the fixed-size head window to detect a physical title slot. */
29
- export declare function readTitleSlotFromFile(filePath: string, storage?: SessionStorage): Promise<SessionTitleSlotEntry | undefined>;
29
+ export declare function loadEntriesFromFileStream(filePath: string): Promise<SessionLoadResult>;
30
30
  /** Exported for compaction.test.ts */
31
31
  export declare function parseSessionEntries(content: string): FileEntry[];
32
- /** Exported for testing */
32
+ /** Load and validate a session while retaining malformed-record diagnostics. */
33
+ export declare function loadSessionFile(filePath: string, storage?: SessionStorage): Promise<SessionLoadResult>;
34
+ /** Load the valid entries from a session file, skipping malformed records. */
33
35
  export declare function loadEntriesFromFile(filePath: string, storage?: SessionStorage): Promise<FileEntry[]>;
36
+ /**
37
+ * Visit session entries, using bounded streaming for large file-backed journals.
38
+ * Small files and non-file backends keep the existing full-load path.
39
+ */
40
+ export declare function visitEntriesFromFile(filePath: string, visit: (entry: FileEntry) => void | boolean, storage?: SessionStorage): Promise<void>;
34
41
  export declare function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobStore): Promise<void>;
35
42
  /**
36
43
  * Read-only transcript view of a session file: load entries, migrate to the
@@ -52,7 +52,9 @@ export interface SessionMaintenanceHost {
52
52
  planReferencePath(): string;
53
53
  nonMessageTokenSource(): NonMessageTokenSource;
54
54
  memoryBackendSession(): MemoryBackendOperationContext["session"];
55
- emitSessionEvent(event: AgentSessionEvent): Promise<void>;
55
+ emitSessionEvent(event: AgentSessionEvent, options?: {
56
+ detachExtensions?: boolean;
57
+ }): Promise<void>;
56
58
  emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void;
57
59
  schedulePostPromptTask(task: (signal: AbortSignal) => Promise<void>, options?: {
58
60
  delayMs?: number;
@@ -104,7 +106,7 @@ export interface SessionMaintenanceHost {
104
106
  }>;
105
107
  runHandoff(customInstructions?: string, options?: SessionHandoffOptions): Promise<HandoffResult | undefined>;
106
108
  removeAssistantMessageFromActiveContext(message: AssistantMessage): void;
107
- dropPersistedAssistantTurn(message: AssistantMessage): Promise<void>;
109
+ dropPersistedAssistantTurn(message: AssistantMessage): Promise<string | undefined>;
108
110
  runRecoveryCompactionWithRollback(reason: "overflow" | "incomplete", message: AssistantMessage, allowDefer: boolean, options: {
109
111
  autoContinue: boolean;
110
112
  triggerContextTokens?: number;
@@ -261,6 +263,8 @@ export declare class SessionMaintenance {
261
263
  suppressHandoff?: boolean;
262
264
  phase?: CodexCompactionContext["phase"];
263
265
  terminalTextAnswer?: boolean;
266
+ /** Mid-turn: splice history then return; do not await UI/extension fan-out. */
267
+ detachPostCommit?: boolean;
264
268
  }): Promise<CompactionCheckResult>;
265
269
  /**
266
270
  * Toggle auto-compaction setting.
@@ -7,6 +7,8 @@ import { type BuildSessionContextOptions, type SessionContext } from "./session-
7
7
  import { type NewSessionOptions, type SessionEntry, type SessionHeader, type SessionTitleSource, type SessionTreeNode, type UsageStatistics } from "./session-entries.js";
8
8
  import { type SessionInfo } from "./session-listing.js";
9
9
  import { type SessionStorage } from "./session-storage.js";
10
+ /** Copy a session's artifact directory to another session, matching interactive `/fork`. */
11
+ export declare function copySessionArtifacts(sourceSessionFile: string, destinationSessionFile: string): Promise<void>;
10
12
  export type ReadonlySessionManager = Pick<SessionManager, "getCwd" | "getSessionDir" | "getSessionId" | "getSessionFile" | "getSessionName" | "getArtifactsDir" | "getArtifactManager" | "allocateArtifactPath" | "saveArtifact" | "getArtifactPath" | "getLeafId" | "getLeafEntry" | "getEntry" | "getLabel" | "getBranch" | "getHeader" | "getEntries" | "getTree" | "getUsageStatistics" | "putBlob" | "putBlobSync">;
11
13
  interface SessionManagerStateSnapshot {
12
14
  cwd: string;
@@ -213,6 +215,8 @@ export declare class SessionManager {
213
215
  get titleSource(): SessionTitleSource | undefined;
214
216
  getSessionName(): string | undefined;
215
217
  onSessionNameChanged(cb: () => void): () => void;
218
+ /** Subscribe to persistence failures so hosts can surface lost-durability state. */
219
+ onPersistenceError(cb: (error: Error) => void): () => void;
216
220
  /**
217
221
  * Set the session display name.
218
222
  * @param source "user" for explicit renames; "auto" for generated titles.
@@ -355,6 +359,17 @@ export declare class SessionManager {
355
359
  branch(branchFromId: string): void;
356
360
  /** Reset the leaf to null so the next append creates a new root entry. */
357
361
  resetLeaf(): void;
362
+ /**
363
+ * Durably move the active branch past a discarded entry.
364
+ *
365
+ * The loader reconstructs the active branch from the last physical journal
366
+ * entry, so changing the in-memory leaf alone is lost on reload. Known
367
+ * metadata children are chained onto the discarded entry's parent before the
368
+ * entry is removed. If any child may carry content, the subtree is preserved
369
+ * off-branch instead. Both paths append a metadata-only branch marker and
370
+ * rewrite the journal, making the selected path durable.
371
+ */
372
+ discardEntryDurably(entryId: string): Promise<void>;
358
373
  /** Like branch(), but also records a branch_summary of the abandoned path. */
359
374
  branchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromExtension?: boolean): string;
360
375
  /**
@@ -382,11 +397,12 @@ export declare class SessionManager {
382
397
  * session file while creating a fresh session file in this sessionDir.
383
398
  *
384
399
  * `options.sessionFile` pins the new session's file path (default: an
385
- * auto-named `<timestamp>_<id>.jsonl` in `sessionDir`). Callers that register
386
- * the fork as a named agent (e.g. `/tan`) pass `<agentId>.jsonl` so the
387
- * persisted-subagent scan keys the agent by the same id the live ref uses.
400
+ * auto-named `<timestamp>_<id>.jsonl` in `sessionDir`). Artifacts are copied
401
+ * recursively by default; nested agents that deliberately share their parent's
402
+ * artifact root may disable this with `copyArtifacts: false`.
388
403
  */
389
404
  static forkFrom(sourcePath: string, cwd: string, sessionDir?: string, storage?: SessionStorage, options?: {
405
+ copyArtifacts?: boolean;
390
406
  suppressBreadcrumb?: boolean;
391
407
  sessionFile?: string;
392
408
  }): Promise<SessionManager>;
@@ -56,7 +56,6 @@ interface SessionToolsOptions {
56
56
  systemPrompt: string[];
57
57
  xdevCatalogNames?: readonly string[];
58
58
  }>;
59
- getLocalCalendarDate?: () => string;
60
59
  getMcpServerInstructions?: () => Map<string, string> | undefined;
61
60
  xdev?: XdevState;
62
61
  setActiveToolNames?: (names: Iterable<string>) => void;
@@ -129,8 +129,8 @@ export declare class TurnRecovery {
129
129
  handleEmptyAssistantStop(message: AssistantMessage): Promise<"continue" | "terminal" | undefined>;
130
130
  /** Classifies suspicious terminal stops and schedules bounded recovery. */
131
131
  handleUnexpectedAssistantStop(message: AssistantMessage): Promise<boolean>;
132
- /** Removes a persisted failed assistant turn after its persistence slot settles. */
133
- dropPersistedAssistantTurn(message: AssistantMessage): Promise<void>;
132
+ /** Removes a persisted failed assistant turn after its persistence slot settles; returns the dropped branch entry id. */
133
+ dropPersistedAssistantTurn(message: AssistantMessage): Promise<string | undefined>;
134
134
  /** Runs recovery compaction and restores the failed turn when no rewrite occurs. */
135
135
  runRecoveryCompactionWithRollback(reason: "overflow" | "incomplete", message: AssistantMessage, allowDefer: boolean, options: {
136
136
  autoContinue: boolean;
@@ -174,7 +174,7 @@ export declare class TurnRecovery {
174
174
  * the Gemini header-runaway interrupt, which must not replay a partial,
175
175
  * loop-fueling thinking block.
176
176
  */
177
- discardAssistantTurn(assistantMessage: AssistantMessage): void;
177
+ discardAssistantTurn(assistantMessage: AssistantMessage): string | undefined;
178
178
  /**
179
179
  * Retry an empty, reason-less provider abort: a turn with no content that
180
180
  * carries the generic sentinel (bare `abort()`), whether the provider
@@ -198,10 +198,10 @@ export declare class TurnRecovery {
198
198
  */
199
199
  isRetryableError(message: AssistantMessage): boolean;
200
200
  /**
201
- * Classify a reasonless abort or stream stall whose emitted tool calls all
202
- * have results. The failed assistant/tool-result pair stays in context so
203
- * continuation cannot replay completed side effects; synthetic results tell
204
- * the next turn that an unexecuted call must be reissued.
201
+ * Classify a reasonless abort, idle stream stall, or HTTP/2 stream reset whose
202
+ * emitted tool calls all have results. The failed assistant/tool-result pair
203
+ * stays in context so continuation cannot replay completed side effects;
204
+ * synthetic results tell the next turn that an unexecuted call must be reissued.
205
205
  */
206
206
  classifyResolvedInterruptedToolTurn(message: AssistantMessage): "reasonless-abort" | "stream-stall" | undefined;
207
207
  /** Checks whether a provider error represents a classifier refusal. */
@@ -5,6 +5,22 @@ import type { Browser, Page } from "puppeteer-core";
5
5
  * subsequent bind in the launched app, but Chromium's listener will retry.
6
6
  */
7
7
  export declare function findFreeCdpPort(): Promise<number>;
8
+ /**
9
+ * Loopback HTTP/1.1 GET that never routes through a proxy, resolving to the
10
+ * response status code (or null when the endpoint is unreachable, aborted,
11
+ * malformed, or slow past `timeoutMs`).
12
+ *
13
+ * Chrome's DevTools endpoint listens on loopback and speaks plain HTTP/1.1.
14
+ * Both `fetch` and Bun's `node:http` honor `HTTP_PROXY`/`HTTPS_PROXY` and
15
+ * forward even `127.0.0.1` requests to the proxy unless `NO_PROXY` covers them,
16
+ * so a local proxy that 502s internal addresses makes a healthy daemon look
17
+ * dead and the CDP readiness checks tear it down (issue #8567). Talking to the
18
+ * socket over raw TCP sidesteps proxy env entirely.
19
+ */
20
+ export declare function probeCdpStatus(url: string, opts: {
21
+ timeoutMs: number;
22
+ signal?: AbortSignal;
23
+ }): Promise<number | null>;
8
24
  /** Poll `${cdpUrl}/json/version` until it responds with 200, with abort + timeout support. */
9
25
  export declare function waitForCdp(cdpUrl: string, timeoutMs: number, signal?: AbortSignal): Promise<void>;
10
26
  /**
@@ -56,8 +56,8 @@ export interface SharedBrowserLaunchSpec {
56
56
  * Resolve the executable and complete argv for a shared Chromium the daemon
57
57
  * broker spawns directly (no puppeteer inside the broker). Mirrors
58
58
  * `launchHeadlessBrowser` flag assembly — puppeteer's default args minus the
59
- * stealth-suppressed set — plus `--remote-debugging-port=0` so every client
60
- * attaches over CDP. Returns null when no Chromium executable resolves;
59
+ * stealth-suppressed set — suppresses Puppeteer's unowned startup window, and
60
+ * exposes CDP on an ephemeral port. Returns null when no executable resolves;
61
61
  * callers fall back to a process-local launch.
62
62
  */
63
63
  export declare function resolveSharedBrowserLaunchSpec(opts: {
@@ -76,7 +76,7 @@ export interface BrowserToolDetails {
76
76
  /**
77
77
  * Browser tool: stateful, multi-tab. Three actions:
78
78
  * - `open` → acquire/create a named tab on a browser kind (headless | spawned | connected) and optionally goto a url.
79
- * - `close` → release a named tab (or all tabs); dispose browser when refcount hits 0.
79
+ * - `close` → release a named tab handle (or all handles); attached/relay pages remain open, and spawned pages remain unless killed.
80
80
  * - `run` → execute JS code against an existing tab with `page`/`browser`/`tab` helpers in scope.
81
81
  */
82
82
  export declare class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolDetails> {
@@ -2,7 +2,7 @@ export declare const BUILTIN_TOOL_NAMES: readonly ["read", "bash", "edit", "ast_
2
2
  export type BuiltinToolName = (typeof BUILTIN_TOOL_NAMES)[number];
3
3
  export declare const HIDDEN_TOOL_NAMES: readonly ["yield", "goal", "think"];
4
4
  export type HiddenToolName = (typeof HIDDEN_TOOL_NAMES)[number];
5
- /** Return the canonical tool name for current and legacy built-in tool IDs. */
5
+ /** Canonicalize built-in IDs and legacy aliases. Leave plugin names unchanged. */
6
6
  export declare function normalizeToolName(name: string): string;
7
7
  /** Normalize and deduplicate tool names while preserving first-seen order. */
8
8
  export declare function normalizeToolNames(names: Iterable<string>): string[];
@@ -36,6 +36,12 @@ export declare function visibleJobs(manager: AsyncJobManager, ids: string[], own
36
36
  * that activity instead of implying the system is quiet. Existence is
37
37
  * already public via the peer roster, so listing ids here leaks nothing new;
38
38
  * job *control* stays owner-scoped.
39
+ *
40
+ * Reporting deliberately uses the claimed `status`, not the session-corroborated
41
+ * `registry.isRunning` used by the wait-sustaining gates: a ref that claims
42
+ * `running` with no live turn is exactly the stale entry an operator must see
43
+ * here to cancel it (#8634). Hiding it would match the badge count to nothing
44
+ * and remove the only discovery path for the id.
39
45
  */
40
46
  export declare function runningAgentsOutsideJobs(session: ToolSession): AgentActivitySnapshot[];
41
47
  interface TrackedJobLike {
@@ -55,6 +55,12 @@ export interface AgentActivitySnapshot {
55
55
  activity?: string;
56
56
  /** Time since the agent was registered. */
57
57
  ageMs: number;
58
+ /**
59
+ * Whether an attached session corroborates the `running` claim. False marks
60
+ * a ref that says `running` with no turn in flight — either a spawn still
61
+ * wiring up or a stale registration that `hub cancel <id>` clears (#8634).
62
+ */
63
+ live: boolean;
58
64
  }
59
65
  /** Result details for messaging and job ops; fields are disjoint per op. */
60
66
  export interface CoordinationDetails {