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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/{CHANGELOG-tt9k4jpr.md → CHANGELOG-vr9cckb4.md} +80 -0
  3. package/dist/cli.js +2993 -3001
  4. package/dist/docs-index.generated.txt +1 -1
  5. package/dist/{tool-views.generated-jdfmzwmn.js → tool-views.generated-dd2km5r2.js} +19 -19
  6. package/dist/types/advisor/advise-tool.d.ts +4 -2
  7. package/dist/types/cli/auth-broker-cli.d.ts +15 -0
  8. package/dist/types/cli/stats-cli.d.ts +1 -6
  9. package/dist/types/cli/update-cli.d.ts +8 -0
  10. package/dist/types/cli-commands.d.ts +10 -2
  11. package/dist/types/commands/stats.d.ts +4 -0
  12. package/dist/types/config/settings-schema.d.ts +28 -0
  13. package/dist/types/config/settings.d.ts +9 -0
  14. package/dist/types/extensibility/extensions/runner.d.ts +23 -4
  15. package/dist/types/extensibility/extensions/types.d.ts +58 -0
  16. package/dist/types/launch/presence.d.ts +4 -1
  17. package/dist/types/mcp/oauth-credentials.d.ts +23 -0
  18. package/dist/types/mcp/oauth-flow.d.ts +11 -0
  19. package/dist/types/mnemopi/backend.d.ts +12 -0
  20. package/dist/types/modes/components/tool-execution.d.ts +12 -0
  21. package/dist/types/modes/controllers/input-controller.d.ts +2 -0
  22. package/dist/types/modes/interactive-mode.d.ts +25 -3
  23. package/dist/types/modes/types.d.ts +10 -0
  24. package/dist/types/session/agent-session.d.ts +3 -0
  25. package/dist/types/session/prewalk.d.ts +4 -0
  26. package/dist/types/session/session-entries.d.ts +0 -1
  27. package/dist/types/session/session-manager.d.ts +12 -0
  28. package/dist/types/session/session-stats.d.ts +13 -1
  29. package/dist/types/session/skill-title-input.d.ts +13 -0
  30. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +1 -0
  31. package/dist/types/subprocess/worker-client.d.ts +7 -4
  32. package/dist/types/task/label.d.ts +2 -0
  33. package/dist/types/task/render.d.ts +2 -0
  34. package/dist/types/tiny/completion-prompt.d.ts +2 -0
  35. package/dist/types/tiny/title-client.d.ts +6 -4
  36. package/dist/types/tiny/title-protocol.d.ts +1 -0
  37. package/dist/types/tiny/worker.d.ts +27 -0
  38. package/dist/types/tools/bash.d.ts +1 -1
  39. package/dist/types/tools/file-write-fallback.d.ts +124 -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/tools/read-format.d.ts +6 -0
  43. package/dist/types/tools/read-summary.d.ts +7 -1
  44. package/dist/types/utils/block-context.d.ts +14 -0
  45. package/dist/types/utils/fetch-timeout.d.ts +15 -0
  46. package/dist/types/utils/git.d.ts +25 -1
  47. package/dist/types/web/search/providers/tinyfish.d.ts +4 -0
  48. package/package.json +13 -13
  49. package/src/advisor/advise-tool.ts +5 -3
  50. package/src/cli/auth-broker-cli.ts +36 -1
  51. package/src/cli/profile-bootstrap.ts +2 -6
  52. package/src/cli/stats-cli.ts +6 -72
  53. package/src/cli/update-cli.ts +63 -11
  54. package/src/cli-commands.ts +61 -7
  55. package/src/commands/completions.ts +2 -1
  56. package/src/commands/stats.ts +7 -4
  57. package/src/commit/agentic/index.ts +15 -2
  58. package/src/commit/git/diff.ts +6 -2
  59. package/src/config/model-resolver.ts +52 -6
  60. package/src/config/models-config.ts +2 -2
  61. package/src/config/settings-schema.ts +33 -0
  62. package/src/config/settings.ts +159 -30
  63. package/src/discovery/helpers.ts +45 -2
  64. package/src/discovery/omp-plugins.ts +2 -1
  65. package/src/discovery/opencode.ts +56 -3
  66. package/src/edit/hashline/filesystem.ts +9 -3
  67. package/src/edit/modes/patch.ts +31 -5
  68. package/src/eval/js/process-entry.ts +4 -4
  69. package/src/export/html/tool-views.generated.js +19 -19
  70. package/src/extensibility/extensions/loader.ts +11 -0
  71. package/src/extensibility/extensions/runner.ts +118 -5
  72. package/src/extensibility/extensions/types.ts +60 -0
  73. package/src/extensibility/extensions/wrapper.ts +10 -1
  74. package/src/extensibility/plugins/legacy-pi-compat.ts +47 -0
  75. package/src/launch/client.ts +9 -4
  76. package/src/launch/presence.ts +19 -4
  77. package/src/lsp/defaults.json +1 -1
  78. package/src/lsp/writethrough.ts +20 -10
  79. package/src/mcp/manager.ts +41 -20
  80. package/src/mcp/oauth-credentials.ts +38 -0
  81. package/src/mcp/oauth-flow.ts +21 -0
  82. package/src/mcp/tool-bridge.ts +32 -16
  83. package/src/mnemopi/backend.ts +35 -3
  84. package/src/modes/components/model-hub.ts +37 -4
  85. package/src/modes/components/settings-selector.ts +17 -11
  86. package/src/modes/components/tool-execution.ts +97 -29
  87. package/src/modes/components/tree-selector.ts +7 -2
  88. package/src/modes/controllers/event-controller.ts +12 -2
  89. package/src/modes/controllers/input-controller.ts +64 -27
  90. package/src/modes/controllers/mcp-command-controller.ts +13 -4
  91. package/src/modes/interactive-mode.ts +79 -11
  92. package/src/modes/types.ts +11 -0
  93. package/src/prompts/system/memory-extraction-system.md +5 -22
  94. package/src/prompts/system/system-prompt.md +1 -1
  95. package/src/session/agent-session.ts +52 -6
  96. package/src/session/messages.ts +6 -0
  97. package/src/session/prewalk.ts +25 -7
  98. package/src/session/session-entries.ts +0 -1
  99. package/src/session/session-maintenance.ts +10 -1
  100. package/src/session/session-manager.ts +15 -0
  101. package/src/session/session-stats.ts +24 -3
  102. package/src/session/settings-stream-fn.ts +7 -0
  103. package/src/session/skill-title-input.ts +32 -0
  104. package/src/session/turn-recovery.ts +23 -18
  105. package/src/slash-commands/builtin-session.ts +1 -1
  106. package/src/slash-commands/helpers/stats-dashboard.ts +23 -9
  107. package/src/subprocess/worker-client.ts +8 -5
  108. package/src/task/executor.ts +11 -0
  109. package/src/task/index.ts +2 -0
  110. package/src/task/label.ts +14 -1
  111. package/src/task/persisted-revive.ts +13 -0
  112. package/src/task/render.ts +1 -1
  113. package/src/task/structured-subagent.ts +5 -2
  114. package/src/tiny/completion-prompt.ts +16 -0
  115. package/src/tiny/title-client.ts +15 -6
  116. package/src/tiny/title-protocol.ts +8 -1
  117. package/src/tiny/worker.ts +21 -19
  118. package/src/tools/bash.ts +7 -1
  119. package/src/tools/file-write-fallback.ts +467 -0
  120. package/src/tools/index.ts +1 -0
  121. package/src/tools/path-utils.ts +79 -0
  122. package/src/tools/read-format.ts +16 -2
  123. package/src/tools/read-summary.ts +9 -4
  124. package/src/tools/read.ts +306 -72
  125. package/src/utils/block-context.ts +15 -1
  126. package/src/utils/fetch-timeout.ts +33 -0
  127. package/src/utils/git.ts +54 -11
  128. package/src/web/search/providers/browser-page.ts +21 -3
  129. package/src/web/search/providers/tinyfish.ts +26 -0
@@ -70,8 +70,10 @@ export declare function isAdvisorInterruptImmuneTurnActive(opts: {
70
70
  * auto-resume anything, so it is delivered live. Parking it during an active
71
71
  * run instead strands it (it never reaches the running agent) and the withheld
72
72
  * notes dump as one burst at the next user prompt — the bug this guards.
73
- * - During the post-interrupt immune-turn window, further `concern`/`blocker`
74
- * notes are downgraded to asides; preservation still wins.
73
+ * - During the post-interrupt immune-turn window, further `concern` notes are
74
+ * downgraded to asides; preservation still wins. A `blocker` is exempt: it
75
+ * means the agent handed off broken or unexercised work, so it still steers a
76
+ * triggered turn even right after a prior interrupt (#5628).
75
77
  */
76
78
  export declare function resolveAdvisorDeliveryChannel(opts: {
77
79
  severity: AdvisorSeverity | undefined;
@@ -1,3 +1,5 @@
1
+ import { type OAuthCredential } from "@oh-my-pi/pi-ai";
2
+ import type { OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types";
1
3
  export type AuthBrokerAction = "serve" | "token" | "login" | "logout" | "status" | "import" | "migrate" | "list";
2
4
  export interface AuthBrokerCommandArgs {
3
5
  action: AuthBrokerAction;
@@ -21,5 +23,18 @@ export interface AuthBrokerCommandArgs {
21
23
  };
22
24
  }
23
25
  declare const ACTIONS: readonly AuthBrokerAction[];
26
+ /**
27
+ * OAuth refresh handler for `omp auth-broker serve`'s {@link AuthStorage}.
28
+ *
29
+ * The vault holds provider OAuth rows AND OMP-managed `mcp_oauth:*` rows.
30
+ * Provider rows refresh through the per-provider registry. MCP rows are
31
+ * self-describing — the embedded token endpoint and client credentials are the
32
+ * only refresh material — so they refresh with a generic `refresh_token` grant.
33
+ * The serve process never loads the MCP manager, so this is the only place that
34
+ * teaches the broker to refresh MCP tokens; without it
35
+ * `POST /v1/credential/:id/refresh` fails with "Unknown OAuth provider" and the
36
+ * background refresher lets MCP access tokens expire (issue #8933).
37
+ */
38
+ export declare function refreshBrokerOAuthCredential(provider: string, credential: OAuthCredential, signal?: AbortSignal): Promise<OAuthCredentials>;
24
39
  export declare function runAuthBrokerCommand(cmd: AuthBrokerCommandArgs): Promise<void>;
25
40
  export { ACTIONS as AUTH_BROKER_ACTIONS };
@@ -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;
@@ -126,6 +126,14 @@ interface UpdateMethodResolutionOptions {
126
126
  * preserves a global package symlink instead of resolving into its checkout.
127
127
  */
128
128
  ompLinkTarget?: string;
129
+ /**
130
+ * Whether package-manager routing (bun/npm) is permitted. Binary-only
131
+ * releases pass `false`: a manager launcher then resolves to `"binary"` and
132
+ * is taken over in place rather than reinstalled through its manager. Defaults
133
+ * to `true` in {@link resolveUpdateMethod} so callers that only classify need
134
+ * not set it.
135
+ */
136
+ allowPackageManagers?: boolean;
129
137
  }
130
138
  type UpdateTarget = {
131
139
  method: "brew";
@@ -36,11 +36,19 @@ export type ResolvedCliArgv = {
36
36
  } | {
37
37
  error: string;
38
38
  };
39
+ /**
40
+ * Subcommands that share the launch flag surface, so leading global flags
41
+ * (`--cwd`, `--model`, `--approval-mode`, …) placed before them are meaningful
42
+ * and must be forwarded ({@link resolveCliArgv}, #2970). Every other subcommand
43
+ * parses only its own flags.
44
+ */
45
+ export declare const LAUNCH_FLAG_COMMANDS: Record<string, true>;
39
46
  /**
40
47
  * Decide what the CLI runner should do with raw argv: reject bare reserved
41
48
  * management words, pass help/version through untouched, route a recognized
42
49
  * subcommand (even behind leading global flags like `--approval-mode=yolo`) to
43
- * that command with the flags preserved, and forward everything else to
44
- * `launch` (#2970).
50
+ * that command, and forward everything else to `launch` (#2970). Leading
51
+ * launch-global flags are forwarded to launch-shaped commands but stripped for
52
+ * other subcommands that cannot parse them (#8891).
45
53
  */
46
54
  export declare function resolveCliArgv(argv: string[]): ResolvedCliArgv;
@@ -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;
@@ -5775,6 +5775,34 @@ export declare const SETTINGS_SCHEMA: {
5775
5775
  }];
5776
5776
  };
5777
5777
  };
5778
+ readonly "providers.cacheRetention": {
5779
+ readonly type: "enum";
5780
+ readonly values: readonly ["auto", "short", "long", "none"];
5781
+ readonly default: "auto";
5782
+ readonly ui: {
5783
+ readonly tab: "providers";
5784
+ readonly group: "Protocol";
5785
+ readonly label: "Prompt Cache Retention";
5786
+ readonly description: "Prompt-cache retention forwarded to providers that support it (Anthropic, Bedrock, OpenRouter, OpenAI)";
5787
+ readonly options: readonly [{
5788
+ readonly value: "auto";
5789
+ readonly label: "Auto";
5790
+ readonly description: "Provider default — Anthropic uses 5m entries kept warm by idle keep-alive refreshes; PI_CACHE_RETENTION still applies";
5791
+ }, {
5792
+ readonly value: "short";
5793
+ readonly label: "Short (5m)";
5794
+ readonly description: "Cheapest cache writes; Anthropic keeps the entry warm with bounded keep-alive refreshes while idle";
5795
+ }, {
5796
+ readonly value: "long";
5797
+ readonly label: "Long (1h)";
5798
+ readonly description: "1h TTL where the provider supports it; pricier writes, no keep-alive refresh requests";
5799
+ }, {
5800
+ readonly value: "none";
5801
+ readonly label: "Off";
5802
+ readonly description: "Disable prompt caching and cache-affinity routing";
5803
+ }];
5804
+ };
5805
+ };
5778
5806
  readonly "providers.streamFirstEventTimeoutSeconds": {
5779
5807
  readonly type: "number";
5780
5808
  readonly default: -1;
@@ -103,6 +103,15 @@ export declare class Settings {
103
103
  */
104
104
  flush(): Promise<void>;
105
105
  cloneForCwd(cwd: string): Promise<Settings>;
106
+ /**
107
+ * Re-read the current global, project, and explicit overlay layers from disk
108
+ * without replacing this instance or discarding runtime overrides.
109
+ *
110
+ * All sources are loaded before any live layer is replaced, so readers never
111
+ * observe a partially refreshed configuration. Concurrent callers share the
112
+ * same reload.
113
+ */
114
+ reloadFromDisk(): Promise<void>;
106
115
  /**
107
116
  * Re-scope this instance to a new working directory *in place*: reload the
108
117
  * project layer (`.claude/settings.yml` etc.) from `cwd`, re-resolve
@@ -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,6 +229,13 @@ 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>;
@@ -259,6 +277,7 @@ export declare class ExtensionRunner {
259
277
  emitContext(messages: AgentMessage[]): Promise<AgentMessage[]>;
260
278
  /** Runs request payload hooks with the model used for that provider request. */
261
279
  emitBeforeProviderRequest(payload: unknown, model?: Model): Promise<BeforeProviderRequestEventResult>;
262
- emitAfterProviderResponse(response: ProviderResponseMetadata, _model?: Model): Promise<void>;
280
+ /** Runs response hooks with the model that produced that provider response. */
281
+ emitAfterProviderResponse(response: ProviderResponseMetadata, model?: Model): Promise<void>;
263
282
  emitBeforeAgentStart(prompt: string, images: ImageContent[] | undefined, systemPrompt: string[]): Promise<BeforeAgentStartCombinedResult | undefined>;
264
283
  }
@@ -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";
@@ -834,6 +835,61 @@ export interface ExtensionAPI {
834
835
  on(event: "mcp_notification", handler: ExtensionHandler<McpNotificationEvent>): void;
835
836
  /** Register a tool that the LLM can call. */
836
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;
837
893
  /** Register a custom command. */
838
894
  registerCommand(name: string, options: {
839
895
  description?: string;
@@ -1142,6 +1198,8 @@ export interface Extension {
1142
1198
  tools: Map<string, RegisteredTool<any, any>>;
1143
1199
  toolRegistrationListeners?: Set<ToolRegistrationListener>;
1144
1200
  assistantThinkingRenderers: AssistantThinkingRenderer[];
1201
+ fileWriteFallbackHandlers: FileWriteFallbackHandler[];
1202
+ fileDeleteFallbackHandlers: FileDeleteFallbackHandler[];
1145
1203
  messageRenderers: Map<string, MessageRenderer>;
1146
1204
  commands: Map<string, RegisteredCommand>;
1147
1205
  flags: Map<string, ExtensionFlag>;
@@ -14,6 +14,9 @@ export declare function hasLiveDaemonProjectPresence(runtimeDir: string): Promis
14
14
  * Best-effort and non-throwing: a scope is deleted only when its `broker.pid`
15
15
  * is absent/dead, no live client presence remains, and it has been untouched
16
16
  * for {@link DAEMON_RUNTIME_STALE_GRACE_MS}. The caller's own `currentRuntimeDir`
17
- * and the machine-global daemon container are always skipped.
17
+ * is always skipped, and the sweep runs only inside the {@link DAEMONS_DIR}
18
+ * container over entries named like a {@link DAEMON_SCOPE_KEY} — so a runtime
19
+ * dir relocated elsewhere (e.g. the smoke test under `os.tmpdir()`) never
20
+ * reclaims unrelated neighbours (issue #8721).
18
21
  */
19
22
  export declare function pruneDeadDaemonRuntimeDirs(currentRuntimeDir: string): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import type { OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types";
1
2
  import type { AuthStorage } from "../session/auth-storage.js";
2
3
  import { type MCPStoredOAuthCredential } from "./oauth-flow.js";
3
4
  import type { MCPAuthConfig, MCPServerConfig } from "./types.js";
@@ -13,5 +14,27 @@ export declare function lookupMcpOAuthCredentialForServer(authStorage: AuthStora
13
14
  }): MCPOAuthCredentialLookup | undefined;
14
15
  export declare function lookupMcpOAuthCredential(authStorage: AuthStorage | null | undefined, config: MCPServerConfig): MCPOAuthCredentialLookup | undefined;
15
16
  export declare function selectMcpOAuthRefreshMaterial(credential: MCPStoredOAuthCredential, auth: MCPAuthConfig | undefined): MCPOAuthRefreshMaterial;
17
+ /**
18
+ * Refresh a stored MCP OAuth credential via the standard `refresh_token` grant.
19
+ *
20
+ * Refresh material is taken from the credential itself (self-contained modern
21
+ * credentials embed `tokenUrl`/`clientId`/`clientSecret`/`resource`) or, for
22
+ * legacy credentials that carry none, the server's `auth` block. Shared by the
23
+ * local MCP manager and the `omp auth-broker serve` refresh path so a broker
24
+ * with no access to the MCP config can still refresh `mcp_oauth:*` credentials
25
+ * from the vault.
26
+ *
27
+ * `serverUrl` supplies the RFC 8707 fallback resource indicator when neither
28
+ * the credential nor the auth block advertised one; the manager passes the
29
+ * configured server URL, the broker recovers it from the credential id via
30
+ * {@link mcpOAuthServerUrlFromCredentialId}.
31
+ *
32
+ * @throws when no usable refresh token or token endpoint is available.
33
+ */
34
+ export declare function refreshManagedMcpOAuthCredential(credential: MCPStoredOAuthCredential, opts?: {
35
+ serverUrl?: string;
36
+ auth?: MCPAuthConfig;
37
+ signal?: AbortSignal;
38
+ }): Promise<OAuthCredentials>;
16
39
  export declare function removeManagedMcpOAuthCredential(authStorage: AuthStorage, credentialId: string | undefined): Promise<boolean>;
17
40
  export declare function removeManagedMcpOAuthCredentials(authStorage: AuthStorage, credentialIds: readonly (string | undefined)[]): Promise<boolean>;
@@ -29,6 +29,17 @@ export declare function isManagedMCPOAuthCredentialId(credentialId: string | und
29
29
  * is the profile; everything after it is the URL.
30
30
  */
31
31
  export declare function mcpOAuthCredentialProfile(credentialId: string): string | undefined;
32
+ /**
33
+ * Server URL embedded in a managed MCP OAuth credential id, or `undefined`
34
+ * for legacy random ids (`mcp_oauth_<rand>`) minted before URL-keyed ids.
35
+ *
36
+ * Inverse of {@link mcpOAuthCredentialId}. Mirrors {@link mcpOAuthCredentialProfile}:
37
+ * the URL contains `:` and `/`, so for profile-scoped ids the URL is everything
38
+ * after the profile segment; for legacy url-keyed ids (`mcp_oauth:<url>`) it is
39
+ * everything after the prefix. Lets the auth-broker — which never sees the MCP
40
+ * config — recover the server URL for the RFC 8707 fallback resource on refresh.
41
+ */
42
+ export declare function mcpOAuthServerUrlFromCredentialId(credentialId: string): string | undefined;
32
43
  /**
33
44
  * Stored MCP OAuth credential. Refresh material is embedded so token refresh
34
45
  * works without any `auth` block persisted in (possibly shared) config files.
@@ -1,4 +1,16 @@
1
+ import type { MnemopiLlmCompleteOptions } from "@oh-my-pi/pi-mnemopi/core/runtime-options";
1
2
  import type { MemoryBackend } from "../memory-backend/types.js";
2
3
  import type { AgentSession } from "../session/agent-session.js";
4
+ /** Prompt turns for one Mnemopi completion. */
5
+ export interface MemoryCompletionInput {
6
+ prompt: string;
7
+ systemPrompt?: string;
8
+ }
9
+ /** Maps a Mnemopi completion into instruction and input turns.
10
+ *
11
+ * Extraction is the only task with its own instructions, and it always supplies
12
+ * the raw text, so the instructions become the system turn and the text becomes
13
+ * the user turn. Every other task keeps the prompt Mnemopi rendered. */
14
+ export declare function resolveMemoryCompletionInput(prompt: string, options?: MnemopiLlmCompleteOptions): MemoryCompletionInput;
3
15
  export declare const mnemopiBackend: MemoryBackend;
4
16
  export declare function getMnemopiDbDirForTests(session: AgentSession): string | undefined;
@@ -66,6 +66,12 @@ export declare const SPINNER_GLYPH_ADVANCE_MS = 80;
66
66
  /** Phase-locked spinner glyph index shared by every live tool block so parallel
67
67
  * spinners advance in lockstep instead of each tracking its own start time. */
68
68
  export declare function sharedSpinnerFrame(frameCount: number, now?: number): number;
69
+ /** Stop the shared spinner ticker and drop every registered live block.
70
+ * Called on interactive-mode teardown so a stray live block cannot keep the
71
+ * process-wide 80ms interval alive past shutdown (lingering event-loop
72
+ * handles pin the process; cf. `postmortem.quit`). Test files that assert on
73
+ * ticker arming also use this to start from a clean slate. */
74
+ export declare function stopSharedSpinnerTicker(): void;
69
75
  /**
70
76
  * Component that renders a tool call with its result (updateable)
71
77
  */
@@ -96,6 +102,12 @@ export declare class ToolExecutionComponent extends Container implements NativeS
96
102
  details?: any;
97
103
  isError?: boolean;
98
104
  }, isPartial?: boolean, _toolCallId?: string): void;
105
+ /**
106
+ * Advance to the shared spinner glyph and repaint just this block. Driven by
107
+ * the single shared spinner ticker (see `registerSpinnerBlock`); the tick is
108
+ * component-scoped so the TUI reuses every other root subtree (issue #4377).
109
+ */
110
+ tickSpinner(frame: number): void;
99
111
  /**
100
112
  * Standalone harnesses may mount a tool component directly under `TUI`
101
113
  * instead of inside `TranscriptContainer`. In that shape the component must
@@ -26,6 +26,8 @@ export declare class InputController {
26
26
  readText: typeof readTextFromClipboard;
27
27
  readMacFileUrls?: typeof readMacFileUrlsFromClipboard;
28
28
  });
29
+ /** Session-level title starts (user `/skill:` via promptCustomMessage) reuse this UI. */
30
+ notifyTitleGenerationStart(): void;
29
31
  setupKeyHandlers(): void;
30
32
  setupEditorSubmitHandler(): void;
31
33
  handleCtrlC(): void;
@@ -31,7 +31,7 @@ import type { HookInputComponent } from "./components/hook-input.js";
31
31
  import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector.js";
32
32
  import { type PlanReviewAnnotationState } from "./components/plan-review-overlay.js";
33
33
  import { StatusLineComponent } from "./components/status-line/index.js";
34
- import type { ToolExecutionHandle } from "./components/tool-execution.js";
34
+ import { type ToolExecutionHandle } from "./components/tool-execution.js";
35
35
  import { TranscriptContainer } from "./components/transcript-container.js";
36
36
  import { EventController } from "./controllers/event-controller.js";
37
37
  import { type LoopLimitRuntime } from "./loop-limit.js";
@@ -67,8 +67,8 @@ export interface InteractiveModeOptions {
67
67
  }
68
68
  /**
69
69
  * Build the anchored subagent HUD block: a bold accent "Subagents" header plus
70
- * a bounded set of running-agent rows in the same `Id: description` shape the
71
- * inline task rows use (muted task preview when no description was given).
70
+ * a bounded set of running-agent rows in the same `Id ⟨role⟩: description` shape
71
+ * the inline task rows use (muted task preview when no description was given).
72
72
  * Layout mirrors the Todos HUD exactly: unindented header, then
73
73
  * `renderTreeList` rows (dim connectors) shifted right by one space.
74
74
  * Only detached background spawns are listed: a sync task call blocks the
@@ -150,6 +150,9 @@ export declare class InteractiveMode implements InteractiveModeContext {
150
150
  onInputCallback?: (input: SubmittedUserInput) => void;
151
151
  optimisticUserMessageSignature: string | undefined;
152
152
  locallySubmittedUserSignatures: Set<string>;
153
+ /** True while an optimistically-rendered `/skill:` row awaits its canonical
154
+ * `message_start`. Read by the event controller to reconcile the row. */
155
+ optimisticSkillMessagePending: boolean;
153
156
  lastSigintTime: number;
154
157
  lastEscapeTime: number;
155
158
  lastLeftTapTime: number;
@@ -219,6 +222,25 @@ export declare class InteractiveMode implements InteractiveModeContext {
219
222
  replaceOptimisticUserMessage(message: AgentMessage, options?: {
220
223
  imageLinks?: readonly (string | undefined)[];
221
224
  }): void;
225
+ /**
226
+ * Optimistically render a user-invoked `/skill:` row before its awaited
227
+ * dispatch so a slow preflight (memory recall, `before_agent_start` hooks,
228
+ * auto-thinking classification, pre-prompt compaction) does not leave the
229
+ * submission invisible — normal prompts paint their row via
230
+ * {@link startPendingSubmission} the same way (issue #8895). The canonical
231
+ * skill `message_start` swaps this row in place via
232
+ * {@link reconcileOptimisticSkillMessage}; a failed or bailed dispatch drops
233
+ * it via {@link clearOptimisticSkillMessage}.
234
+ */
235
+ renderOptimisticSkillMessage(message: AgentMessage, options?: {
236
+ imageLinks?: readonly (string | undefined)[];
237
+ }): void;
238
+ /** Replace the optimistic `/skill:` row with the canonical message emitted by
239
+ * the session, mirroring {@link replaceOptimisticUserMessage} for skills. */
240
+ reconcileOptimisticSkillMessage(message: AgentMessage): void;
241
+ /** Drop the optimistic `/skill:` row when dispatch fails or bails before the
242
+ * message reaches the agent (aborted preflight, streaming-race requeue). */
243
+ clearOptimisticSkillMessage(): void;
222
244
  startPendingSubmission(input: {
223
245
  text: string;
224
246
  images?: ImageContent[];
@@ -290,6 +290,16 @@ export interface InteractiveModeContext {
290
290
  replaceOptimisticUserMessage(message: AgentMessage, options?: {
291
291
  imageLinks?: readonly (string | undefined)[];
292
292
  }): void;
293
+ /** True while an optimistically-rendered `/skill:` row awaits its canonical `message_start`. */
294
+ optimisticSkillMessagePending: boolean;
295
+ /** Optimistically renders a user-invoked `/skill:` row before its awaited dispatch (issue #8895). */
296
+ renderOptimisticSkillMessage(message: AgentMessage, options?: {
297
+ imageLinks?: readonly (string | undefined)[];
298
+ }): void;
299
+ /** Swaps the optimistic `/skill:` row for the canonical message emitted by the session. */
300
+ reconcileOptimisticSkillMessage(message: AgentMessage): void;
301
+ /** Drops the optimistic `/skill:` row when dispatch fails or bails before reaching the agent. */
302
+ clearOptimisticSkillMessage(): void;
293
303
  isKnownSlashCommand(text: string): boolean;
294
304
  addMessageToChat(message: AgentMessage, options?: {
295
305
  populateHistory?: boolean;
@@ -630,6 +630,9 @@ export declare class AgentSession {
630
630
  * changes (e.g. `/move` relocation) so the next replan refresh resolves
631
631
  * against the destination project's override. */
632
632
  setTitleSystemPrompt(prompt: string | undefined): void;
633
+ /** Install the interactive title-download UI hook. Used when `/skill:` starts
634
+ * titling from {@link promptCustomMessage} without the input-controller callback. */
635
+ setTitleGenerationStart(handler: (() => void) | undefined): void;
633
636
  /**
634
637
  * Abort current operation and wait for agent to become idle.
635
638
  *
@@ -19,8 +19,12 @@ export interface PrewalkCoordinatorHost {
19
19
  ephemeral?: boolean;
20
20
  }): Promise<void>;
21
21
  setActiveToolsByName(names: string[]): Promise<void>;
22
+ setActiveToolPresentation(toolNames: string[], mountedToolNames: string[]): Promise<void>;
23
+ runToolRegistryMutation<T>(mutation: () => Promise<T>): Promise<T>;
22
24
  getActiveToolNames(): string[];
23
25
  getEnabledToolNames(): string[];
26
+ getSelectedMCPToolNames(): string[];
27
+ getMountedXdevToolNames(): string[];
24
28
  hasBuiltInTool(name: string): boolean;
25
29
  getPlanModeState(): PlanModeState | undefined;
26
30
  setPlanModeState(state: PlanModeState | undefined): void;
@@ -150,7 +150,6 @@ declare module "@oh-my-pi/pi-agent-core/compaction/entries" {
150
150
  interface CustomCompactionSessionEntries {
151
151
  titleChange: TitleChangeEntry;
152
152
  credentialPin: CredentialPinEntry;
153
- resetBoundary: ResetBoundaryEntry;
154
153
  }
155
154
  }
156
155
  /** TTSR injection entry - tracks which time-traveling rules have been injected this session. */
@@ -200,6 +200,18 @@ export declare class SessionManager {
200
200
  getSessionDir(): string;
201
201
  getSessionId(): string;
202
202
  getSessionFile(): string | undefined;
203
+ /**
204
+ * Whether the current session has actually been materialized to durable
205
+ * storage (the JSONL exists on disk / in the active storage backend).
206
+ *
207
+ * Session persistence is lazy: the file is only written once the history
208
+ * contains an assistant message (or an explicit {@link ensureOnDisk}
209
+ * caller forces it). Until then {@link getSessionFile} returns an allocated
210
+ * path that leads nowhere, so a `--resume <id>` hint built from it would
211
+ * always fail. Consumers that advertise a resume command must gate on this
212
+ * (issue #8860).
213
+ */
214
+ isSessionOnDisk(): boolean;
203
215
  getArtifactsDir(): string | null;
204
216
  adoptArtifactManager(manager: ArtifactManager): void;
205
217
  getArtifactManager(): ArtifactManager | null;
@@ -9,6 +9,12 @@ interface PendingContextSnapshot {
9
9
  promptTokens: number;
10
10
  nonMessageTokens: number;
11
11
  cutoffCount: number;
12
+ /**
13
+ * Compaction epoch at rebase time. Distinguishes a genuinely fresh in-turn
14
+ * anchor (same epoch) from a post-cutoff anchor that predates a mid-run
15
+ * compaction (older epoch) so the latter never out-ranks this snapshot.
16
+ */
17
+ epoch: number;
12
18
  }
13
19
  /** Capabilities the stats tracker borrows from its owning session. */
14
20
  export interface SessionStatsTrackerHost {
@@ -36,6 +42,12 @@ export declare class SessionStatsTracker {
36
42
  }): ContextUsage | undefined;
37
43
  /** Monotonic revision for in-flight context snapshot changes. */
38
44
  get revision(): number;
45
+ /**
46
+ * Monotonic compaction epoch, bumped whenever history is compacted. Stamped
47
+ * onto each assistant snapshot at record time so {@link getContextBreakdown}
48
+ * can reject a post-cutoff anchor whose usage predates the last compaction.
49
+ */
50
+ get compactionEpoch(): number;
39
51
  /** Non-message token count captured for the active provider request. */
40
52
  get pendingNonMessageTokens(): number | undefined;
41
53
  /**
@@ -49,7 +61,7 @@ export declare class SessionStatsTracker {
49
61
  */
50
62
  recordAnchoredHistoryRewrite(tokensRemoved: number): void;
51
63
  /** Sets or clears the in-flight context snapshot. */
52
- setPendingSnapshot(snapshot: PendingContextSnapshot | undefined): void;
64
+ setPendingSnapshot(snapshot: Omit<PendingContextSnapshot, "epoch"> | undefined): void;
53
65
  /** Recomputes an in-flight snapshot after history is compacted or rewritten. */
54
66
  rebaseAfterCompaction(): void;
55
67
  /** Records provider usage headers against the active session account. */
@@ -0,0 +1,13 @@
1
+ /** Compact title-model input for a user-invoked `/skill:<name>` prompt. */
2
+ export declare function skillPromptTitleInput(input: {
3
+ name?: string;
4
+ args?: string;
5
+ queueChipText?: string;
6
+ }): string;
7
+ /** Title text for a persisted skill-prompt custom message. Never the expanded SKILL.md body. */
8
+ export declare function titleTextFromSkillPrompt(message: {
9
+ role: string;
10
+ customType?: string;
11
+ attribution?: string;
12
+ details?: unknown;
13
+ }): string | undefined;
@@ -1,6 +1,7 @@
1
1
  export declare const DEFAULT_STATS_DASHBOARD_PORT = 3847;
2
2
  export interface StatsDashboardArgs {
3
3
  port: number;
4
+ host: string;
4
5
  }
5
6
  export interface StatsDashboardLaunchResult {
6
7
  url: string;
@@ -89,10 +89,13 @@ export declare const SMOKE_TEST_TIMEOUT_MS = 30000;
89
89
  /**
90
90
  * Resolve the command used to relaunch the agent CLI into worker mode. In a
91
91
  * compiled binary the entry point is the binary itself; otherwise re-enter the
92
- * declared worker-host entry with a cwd-relative script path (Bun's subprocess
93
- * IPC is more reliable that way under `bun test`), falling back to this
94
- * package's own `src/cli.ts` when no host entry is declared (bun test, SDK
95
- * embedding).
92
+ * declared worker-host entry by absolute path. Workers deliberately spawn
93
+ * without a pinned cwd there: they share the parent's foreground process
94
+ * group, and terminal cwd heuristics (kitty's new_tab_with_cwd) read the
95
+ * newest process in that group, so anchoring them to the install dir leaks
96
+ * into newly opened terminal tabs. With no declared host entry (bun test, SDK
97
+ * embedding) fall back to a cwd-relative `src/cli.ts`, which Bun subprocess
98
+ * IPC handles more reliably under `bun test`.
96
99
  */
97
100
  export declare function resolveWorkerSpawnCmd(workerArg: string): WorkerSpawnCommand;
98
101
  /**
@@ -1,4 +1,6 @@
1
1
  import type { ModelRegistry } from "../config/model-registry.js";
2
2
  import type { Settings } from "../config/settings.js";
3
+ /** True when a generated label is just the spawn handle, including `Name-2`. */
4
+ export declare function labelEchoesHandle(handle: string | undefined, label: string): boolean;
3
5
  /** Compresses a delegated assignment into a one-sentence UI label via the tiny title model — fired by the executor spawn path because the task wire schema no longer carries a `description`; null on empty input or failure. */
4
6
  export declare function generateTaskLabel(assignment: string, registry: ModelRegistry, settings: Settings, sessionId?: string, signal?: AbortSignal): Promise<string | null>;