@oh-my-pi/pi-coding-agent 17.3.4 → 17.3.7

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 (137) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/{CHANGELOG-trcc215s.md → CHANGELOG-1hmwwt45.md} +88 -0
  3. package/dist/cli.js +3156 -3163
  4. package/dist/docs-index.generated.txt +1 -1
  5. package/dist/types/cli/stats-cli.d.ts +1 -6
  6. package/dist/types/commands/stats.d.ts +4 -0
  7. package/dist/types/config/model-discovery.d.ts +2 -0
  8. package/dist/types/config/model-registry.d.ts +6 -1
  9. package/dist/types/config/settings-schema.d.ts +10 -0
  10. package/dist/types/debug/report-bundle.d.ts +7 -2
  11. package/dist/types/extensibility/extensions/loader.d.ts +2 -0
  12. package/dist/types/extensibility/extensions/runner.d.ts +24 -6
  13. package/dist/types/extensibility/extensions/types.d.ts +60 -0
  14. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +4 -0
  15. package/dist/types/launch/presence.d.ts +11 -0
  16. package/dist/types/lsp/client.d.ts +22 -0
  17. package/dist/types/lsp/workspace-diagnostics.d.ts +2 -0
  18. package/dist/types/mcp/config.d.ts +3 -1
  19. package/dist/types/modes/components/tool-execution.d.ts +3 -1
  20. package/dist/types/registry/agent-lifecycle.d.ts +11 -0
  21. package/dist/types/registry/agent-registry.d.ts +4 -0
  22. package/dist/types/session/agent-session-types.d.ts +0 -2
  23. package/dist/types/session/agent-session.d.ts +20 -0
  24. package/dist/types/session/date-cwd-reminder.d.ts +22 -0
  25. package/dist/types/session/irc-bridge.d.ts +2 -0
  26. package/dist/types/session/messages.d.ts +4 -0
  27. package/dist/types/session/session-loader.d.ts +18 -11
  28. package/dist/types/session/session-maintenance.d.ts +6 -2
  29. package/dist/types/session/session-manager.d.ts +19 -3
  30. package/dist/types/session/session-tools.d.ts +0 -1
  31. package/dist/types/session/turn-recovery.d.ts +7 -7
  32. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +1 -0
  33. package/dist/types/tools/browser/attach.d.ts +16 -0
  34. package/dist/types/tools/browser/launch.d.ts +2 -2
  35. package/dist/types/tools/browser.d.ts +1 -1
  36. package/dist/types/tools/builtin-names.d.ts +1 -1
  37. package/dist/types/tools/file-write-fallback.d.ts +124 -0
  38. package/dist/types/tools/hub/jobs.d.ts +6 -0
  39. package/dist/types/tools/hub/types.d.ts +6 -0
  40. package/dist/types/tools/index.d.ts +1 -0
  41. package/dist/types/tools/path-utils.d.ts +23 -0
  42. package/dist/types/vibe/runtime.d.ts +3 -3
  43. package/package.json +13 -13
  44. package/src/auto-thinking/classifier.ts +37 -23
  45. package/src/cli/models-cli.ts +1 -1
  46. package/src/cli/stats-cli.ts +6 -72
  47. package/src/commands/stats.ts +7 -4
  48. package/src/commit/analysis/conventional.ts +15 -9
  49. package/src/commit/analysis/summary.ts +15 -9
  50. package/src/commit/changelog/generate.ts +15 -9
  51. package/src/commit/map-reduce/map-phase.ts +3 -19
  52. package/src/commit/map-reduce/reduce-phase.ts +15 -9
  53. package/src/config/model-discovery.ts +3 -3
  54. package/src/config/model-registry.ts +29 -16
  55. package/src/config/settings-schema.ts +13 -0
  56. package/src/debug/report-bundle.ts +25 -59
  57. package/src/discovery/claude-plugins.ts +2 -1
  58. package/src/discovery/claude.ts +4 -2
  59. package/src/discovery/helpers.ts +5 -0
  60. package/src/edit/hashline/filesystem.ts +9 -3
  61. package/src/edit/modes/patch.ts +31 -5
  62. package/src/edit/renderer.ts +48 -19
  63. package/src/extensibility/extensions/loader.ts +20 -5
  64. package/src/extensibility/extensions/runner.ts +239 -24
  65. package/src/extensibility/extensions/types.ts +62 -0
  66. package/src/extensibility/extensions/wrapper.ts +26 -11
  67. package/src/extensibility/legacy-pi-ai-shim.ts +4 -0
  68. package/src/extensibility/plugins/marketplace/manager.ts +15 -18
  69. package/src/irc/bus.ts +1 -1
  70. package/src/launch/broker.ts +8 -1
  71. package/src/launch/presence.ts +77 -1
  72. package/src/lsp/client.ts +35 -3
  73. package/src/lsp/clients/biome-client.ts +47 -93
  74. package/src/lsp/servers.ts +17 -11
  75. package/src/lsp/tool.ts +2 -4
  76. package/src/lsp/workspace-diagnostics.ts +24 -2
  77. package/src/lsp/writethrough.ts +20 -10
  78. package/src/mcp/config.ts +50 -6
  79. package/src/memories/index.ts +29 -25
  80. package/src/mnemopi/backend.ts +13 -11
  81. package/src/modes/components/ask-dialog.ts +25 -5
  82. package/src/modes/components/tool-execution.ts +10 -6
  83. package/src/modes/components/welcome.ts +4 -1
  84. package/src/modes/controllers/extension-ui-controller.ts +52 -32
  85. package/src/modes/controllers/selector-controller.ts +6 -0
  86. package/src/modes/controllers/tan-command-controller.ts +1 -0
  87. package/src/modes/interactive-mode.ts +49 -10
  88. package/src/modes/theme/defaults/birch.json +2 -2
  89. package/src/prompts/system/checkpoint-active-notice.md +5 -0
  90. package/src/prompts/system/date-cwd-reminder.md +3 -0
  91. package/src/prompts/system/project-prompt.md +0 -1
  92. package/src/prompts/system/rewind-report.md +1 -3
  93. package/src/prompts/tools/browser.md +1 -0
  94. package/src/prompts/tools/rewind.md +1 -13
  95. package/src/registry/agent-lifecycle.ts +34 -0
  96. package/src/registry/agent-registry.ts +27 -2
  97. package/src/registry/persisted-agents.ts +40 -20
  98. package/src/sdk.ts +32 -2
  99. package/src/session/agent-session-types.ts +0 -2
  100. package/src/session/agent-session.ts +178 -46
  101. package/src/session/date-cwd-reminder.ts +77 -0
  102. package/src/session/irc-bridge.ts +5 -0
  103. package/src/session/messages.ts +5 -1
  104. package/src/session/session-loader.ts +106 -64
  105. package/src/session/session-maintenance.ts +189 -115
  106. package/src/session/session-manager.ts +142 -35
  107. package/src/session/session-persistence.ts +4 -4
  108. package/src/session/session-storage.ts +18 -5
  109. package/src/session/session-tools.ts +5 -10
  110. package/src/session/turn-recovery.ts +102 -20
  111. package/src/session/unexpected-stop-classifier.ts +33 -21
  112. package/src/slash-commands/builtin-session.ts +1 -1
  113. package/src/slash-commands/helpers/stats-dashboard.ts +23 -9
  114. package/src/system-prompt.ts +0 -5
  115. package/src/task/executor.ts +4 -15
  116. package/src/task/persisted-revive.ts +3 -6
  117. package/src/tools/ask.ts +10 -3
  118. package/src/tools/browser/attach.ts +80 -25
  119. package/src/tools/browser/launch.ts +44 -15
  120. package/src/tools/browser/relay/daemon.ts +3 -7
  121. package/src/tools/browser/render.ts +1 -1
  122. package/src/tools/browser/shared-daemon.ts +3 -7
  123. package/src/tools/browser.ts +5 -5
  124. package/src/tools/builtin-names.ts +7 -3
  125. package/src/tools/checkpoint.ts +1 -7
  126. package/src/tools/file-write-fallback.ts +467 -0
  127. package/src/tools/hub/index.ts +1 -1
  128. package/src/tools/hub/jobs.ts +20 -3
  129. package/src/tools/hub/types.ts +6 -0
  130. package/src/tools/index.ts +1 -0
  131. package/src/tools/path-utils.ts +79 -0
  132. package/src/tools/render-utils.ts +53 -21
  133. package/src/tools/think.ts +15 -3
  134. package/src/tts/speech-enhancer.ts +19 -14
  135. package/src/utils/commit-message-generator.ts +14 -12
  136. package/src/utils/title-generator.ts +23 -19
  137. package/src/vibe/runtime.ts +32 -17
@@ -5,13 +5,8 @@
5
5
  */
6
6
  export interface StatsCommandArgs {
7
7
  port: number;
8
+ host: string;
8
9
  json: boolean;
9
10
  summary: boolean;
10
11
  }
11
- /**
12
- * Parse stats subcommand arguments.
13
- * Returns undefined if not a stats command.
14
- */
15
- export declare function parseStatsArgs(args: string[]): StatsCommandArgs | undefined;
16
12
  export declare function runStatsCommand(cmd: StatsCommandArgs): Promise<void>;
17
- export declare function printStatsHelp(): void;
@@ -10,6 +10,10 @@ export default class Stats extends Command {
10
10
  description: string;
11
11
  default: number;
12
12
  };
13
+ host: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
14
+ description: string;
15
+ default: string;
16
+ };
13
17
  json: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
14
18
  char: string;
15
19
  description: string;
@@ -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
  /**
@@ -52,10 +52,12 @@ export type SwitchSessionHandler = (sessionPath: string) => Promise<{
52
52
  }>;
53
53
  export type ShutdownHandler = () => void;
54
54
  /**
55
- * Emit `session_shutdown` and clear timers owned by an extension runner.
55
+ * Emit `session_shutdown`, dispose file-write-fallback registrations, and clear
56
+ * timers owned by an extension runner.
56
57
  *
57
- * Returns whether any shutdown handlers were present. Timer cleanup runs even
58
- * when a handler fails so extension background work cannot outlive its host.
58
+ * Returns whether any shutdown handlers were present. Fallback disposal and timer
59
+ * cleanup run even when a handler fails so extension background work and a
60
+ * fallback bound to this session's context — cannot outlive its host.
59
61
  */
60
62
  export declare function emitSessionShutdownEvent(extensionRunner: ExtensionRunner | undefined): Promise<boolean>;
61
63
  export declare class ExtensionRunner {
@@ -115,6 +117,15 @@ export declare class ExtensionRunner {
115
117
  * leak into another's `ctx.cwd` by reading a shared global.
116
118
  */
117
119
  get cwd(): string;
120
+ /**
121
+ * Stable id of the session this runner serves. Read through `sessionManager`
122
+ * for the same reason as {@link cwd}: it is this session's own, never a
123
+ * process-global, so a subagent runner reports itself and not its parent.
124
+ *
125
+ * Used to attribute a denied file write or delete to the session that issued
126
+ * it, since the fallback registry those handlers live in is process-wide.
127
+ */
128
+ get sessionId(): string;
118
129
  initialize(actions: ExtensionActions, contextActions: ExtensionContextActions, commandContextActions?: ExtensionCommandContextActions, uiContext?: ExtensionUIContext, mode?: ExtensionMode): void;
119
130
  /**
120
131
  * Forward a `credential_disabled` event from `AuthStorage` to extension handlers.
@@ -218,14 +229,21 @@ export declare class ExtensionRunner {
218
229
  * firing against a disposed session).
219
230
  */
220
231
  clearManagedTimers(): void;
232
+ /**
233
+ * Remove every file write and delete fallback this runner installed into the
234
+ * process-wide registries. Called on session shutdown (and before reinstalling
235
+ * on a re-{@link initialize}) so a handler bound to a torn-down session's
236
+ * context can never fire for another session sharing this process.
237
+ */
238
+ disposeFileFallbacks(): void;
221
239
  createCommandContext(): ExtensionCommandContext;
222
240
  emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>>;
223
241
  emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined>;
224
242
  /**
225
243
  * Emit a `tool_call` event to every subscribed extension before the tool executes.
226
244
  *
227
- * Each handler is bounded by `extensionHandlerTimeoutMs` (default 30s). This
228
- * matches the timeout policy already applied to `emitToolResult` and every
245
+ * Each handler is bounded by `extensionHandlers.toolCallTimeoutMs` (default
246
+ * 30s). This matches the timeout policy already applied to `emitToolResult` and every
229
247
  * other handler routed through `#runHandlerWithTimeout`; without it a single
230
248
  * hung extension (unresolved `await`, network call with no timeout) would
231
249
  * park `ExtensionToolWrapper.execute` indefinitely and freeze tool
@@ -236,7 +254,7 @@ export declare class ExtensionRunner {
236
254
  * pre-execution gate — an unresponsive extension MUST NOT be treated as
237
255
  * silent consent to run the tool.
238
256
  */
239
- emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined>;
257
+ emitToolCall(event: ToolCallEvent, signal?: AbortSignal): Promise<ToolCallEventResult | undefined>;
240
258
  emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined>;
241
259
  emitUserPython(event: UserPythonEvent): Promise<UserPythonEventResult | undefined>;
242
260
  private emitUserEvent;
@@ -33,6 +33,7 @@ import type { CustomMessage, CustomMessagePayload } from "../../session/messages
33
33
  import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager.js";
34
34
  import type { BashToolDetails, BashToolInput, GlobToolDetails, GlobToolInput, GrepToolDetails, GrepToolInput, ReadToolDetails, ReadToolInput, WriteToolInput } from "../../tools/index.js";
35
35
  import type { ApprovalMode } from "../../tools/approval.js";
36
+ import type { FileDeleteFallbackHandler, FileWriteFallbackHandler } from "../../tools/file-write-fallback.js";
36
37
  import type { EventBus } from "../../utils/event-bus.js";
37
38
  import type { AgentEndEvent, AgentStartEvent, AutoCompactionEndEvent, AutoCompactionStartEvent, AutoRetryEndEvent, AutoRetryStartEvent, ContextEvent, GoalUpdatedEvent, RetryFallbackAppliedEvent, RetryFallbackSucceededEvent, SessionBeforeBranchEvent, SessionBeforeBranchResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionBranchEvent, SessionCompactEvent, SessionCompactingEvent, SessionCompactingResult, SessionEvent, SessionShutdownEvent, SessionStartEvent, SessionStopEvent, SessionStopEventResult, SessionSwitchEvent, SessionTreeEvent, TodoReminderEvent, ToolCallEventResult, ToolResultEventResult, TtsrTriggeredEvent, TurnEndEvent, TurnStartEvent } from "../shared-events.js";
38
39
  import type { SlashCommandInfo } from "../slash-commands.js";
@@ -138,6 +139,8 @@ export interface ExtensionCustomOptions {
138
139
  overlayOptions?: OverlayOptions | (() => OverlayOptions);
139
140
  /** Invoked with the overlay handle once the overlay is created (overlay mode only). */
140
141
  onHandle?: (handle: OverlayHandle) => void;
142
+ /** Abort the custom UI and reject its promise. */
143
+ signal?: AbortSignal;
141
144
  }
142
145
  /** Wrap the current autocomplete provider with additional behavior (pi-compatible). */
143
146
  export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider;
@@ -832,6 +835,61 @@ export interface ExtensionAPI {
832
835
  on(event: "mcp_notification", handler: ExtensionHandler<McpNotificationEvent>): void;
833
836
  /** Register a tool that the LLM can call. */
834
837
  registerTool<TParams extends TSchema = TSchema, TDetails = unknown>(tool: ToolDefinition<TParams, TDetails>): void;
838
+ /**
839
+ * Register a fallback writer consulted when a native `write`/`edit` byte-write is
840
+ * denied with a permission error (`EPERM`/`EACCES`/`EROFS`). Every other write
841
+ * error is unaffected. Handlers run in registration order; the first one to
842
+ * resolve `true` counts as the bytes being durably on disk, and the native tool
843
+ * continues as if its own write had succeeded — including recording its file
844
+ * snapshot under the real destination path, so a later hashline `edit` on that
845
+ * path keeps working. Intended for a host embedding the agent inside a sandbox
846
+ * that denies direct filesystem writes but exposes a privileged write channel.
847
+ *
848
+ * A denial that `Bun.write` masks as `ENOENT` — a write into a directory the host
849
+ * may not create — also diverts here, with `req.dst`'s parent absent and the
850
+ * handler responsible for creating it.
851
+ *
852
+ * `req.dst` is symlink-RESOLVED: the path the failed write itself acted on, not
853
+ * the one the tool was given. A link anywhere in a lexical path redirects the
854
+ * bytes while still passing a prefix allowlist, so treat `req.dst` as
855
+ * authoritative. A destination that cannot be resolved is never brokered.
856
+ *
857
+ * Call this during extension load, like the other `register*` methods: handlers
858
+ * are installed when the runner initializes, so an extension that has registered
859
+ * none by then is skipped and a first registration made later never takes effect.
860
+ *
861
+ * The underlying registry is process-wide, so a handler may be consulted for a
862
+ * denied write from any session in the process, not only its own.
863
+ * `req.sessionId` names the session that issued the write and
864
+ * `ctx.sessionManager.getSessionId()` names the handler's own; compare them
865
+ * before prompting, because `ctx.ui` belongs to the latter. See
866
+ * `docs/extensions.md`.
867
+ */
868
+ registerFileWriteFallback(handler: FileWriteFallbackHandler): void;
869
+ /**
870
+ * Register a fallback deleter consulted when a native `edit`/`apply_patch` unlink is
871
+ * denied with a permission error (`EPERM`/`EACCES`/`EROFS`). Covers `edit`'s `REM`,
872
+ * the source side of a hashline `MV`, and `apply_patch`'s delete op. Return `true`
873
+ * once `dst` is gone from disk.
874
+ *
875
+ * A handler MUST remove `dst` with a plain unlink and MUST NOT fall back to a
876
+ * recursive removal. `unlink` on a directory reports `EPERM` on Darwin, so the seam
877
+ * checks the target before diverting — but when the target's own metadata is behind
878
+ * the same boundary that denied the unlink, which is the common sandbox case, that
879
+ * check cannot be resolved and `dst` may be a directory. `req.confirmedFile` says
880
+ * which situation the handler is in.
881
+ *
882
+ * `req.dst` resolves every component ABOVE the last, for the same reason the
883
+ * write seam resolves all of them; the last is left alone because `unlink`
884
+ * removes a link rather than its target, so `req.dst` may name a link.
885
+ *
886
+ * Separate from {@link registerFileWriteFallback} on purpose. A write handler
887
+ * brokers `req.content` to `req.dst`, so a delete request reaching it with no
888
+ * content invites brokering an empty write and truncating the file instead of
889
+ * removing it. Registering for deletes is therefore an explicit opt-in, and the
890
+ * same load-time and process-wide notes above apply.
891
+ */
892
+ registerFileDeleteFallback(handler: FileDeleteFallbackHandler): void;
835
893
  /** Register a custom command. */
836
894
  registerCommand(name: string, options: {
837
895
  description?: string;
@@ -1140,6 +1198,8 @@ export interface Extension {
1140
1198
  tools: Map<string, RegisteredTool<any, any>>;
1141
1199
  toolRegistrationListeners?: Set<ToolRegistrationListener>;
1142
1200
  assistantThinkingRenderers: AssistantThinkingRenderer[];
1201
+ fileWriteFallbackHandlers: FileWriteFallbackHandler[];
1202
+ fileDeleteFallbackHandlers: FileDeleteFallbackHandler[];
1143
1203
  messageRenderers: Map<string, MessageRenderer>;
1144
1204
  commands: Map<string, RegisteredCommand>;
1145
1205
  flags: Map<string, ExtensionFlag>;
@@ -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;
@@ -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;