@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.2

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 (126) hide show
  1. package/CHANGELOG.md +92 -20
  2. package/dist/cli.js +3485 -3448
  3. package/dist/types/advisor/config.d.ts +14 -6
  4. package/dist/types/advisor/runtime.d.ts +20 -11
  5. package/dist/types/autolearn/controller.d.ts +1 -0
  6. package/dist/types/config/model-discovery.d.ts +17 -0
  7. package/dist/types/config/model-resolver.d.ts +6 -2
  8. package/dist/types/config/settings-schema.d.ts +32 -7
  9. package/dist/types/config/settings.d.ts +50 -0
  10. package/dist/types/cursor.d.ts +11 -0
  11. package/dist/types/discovery/helpers.d.ts +5 -2
  12. package/dist/types/exec/bash-executor.d.ts +2 -0
  13. package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
  14. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  15. package/dist/types/extensibility/extensions/types.d.ts +18 -0
  16. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
  17. package/dist/types/extensibility/shared-events.d.ts +6 -0
  18. package/dist/types/extensibility/utils.d.ts +2 -2
  19. package/dist/types/mcp/transports/stdio.d.ts +13 -1
  20. package/dist/types/modes/components/advisor-config.d.ts +8 -1
  21. package/dist/types/modes/components/hook-editor.d.ts +7 -0
  22. package/dist/types/modes/components/model-hub.d.ts +5 -2
  23. package/dist/types/modes/components/session-selector.d.ts +2 -0
  24. package/dist/types/modes/controllers/command-controller.d.ts +8 -0
  25. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  26. package/dist/types/modes/print-mode.d.ts +1 -1
  27. package/dist/types/modes/warp-events.d.ts +24 -0
  28. package/dist/types/modes/warp-events.test.d.ts +1 -0
  29. package/dist/types/plan-mode/model-transition.d.ts +47 -0
  30. package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
  31. package/dist/types/registry/agent-lifecycle.d.ts +26 -1
  32. package/dist/types/sdk.d.ts +13 -2
  33. package/dist/types/session/agent-session.d.ts +34 -10
  34. package/dist/types/session/session-history-format.d.ts +10 -0
  35. package/dist/types/session/session-manager.d.ts +14 -0
  36. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
  37. package/dist/types/task/label.d.ts +1 -1
  38. package/dist/types/telemetry-export.d.ts +34 -9
  39. package/dist/types/tools/approval.d.ts +8 -0
  40. package/dist/types/tools/bash.d.ts +2 -0
  41. package/dist/types/tools/essential-tools.d.ts +29 -0
  42. package/dist/types/tools/image-gen.d.ts +2 -1
  43. package/dist/types/tools/index.d.ts +1 -0
  44. package/dist/types/tools/xdev.d.ts +11 -2
  45. package/dist/types/utils/title-generator.d.ts +2 -1
  46. package/dist/types/web/search/providers/kimi.d.ts +4 -1
  47. package/dist/types/web/search/types.d.ts +1 -1
  48. package/package.json +21 -16
  49. package/src/advisor/__tests__/advisor.test.ts +1304 -42
  50. package/src/advisor/__tests__/config.test.ts +58 -2
  51. package/src/advisor/config.ts +76 -24
  52. package/src/advisor/runtime.ts +445 -92
  53. package/src/autolearn/controller.ts +23 -28
  54. package/src/cli.ts +5 -1
  55. package/src/config/model-discovery.ts +81 -21
  56. package/src/config/model-registry.ts +25 -6
  57. package/src/config/model-resolver.ts +14 -7
  58. package/src/config/settings-schema.ts +42 -6
  59. package/src/config/settings.ts +405 -25
  60. package/src/cursor.ts +20 -3
  61. package/src/debug/report-bundle.ts +40 -4
  62. package/src/discovery/helpers.ts +28 -5
  63. package/src/exec/bash-executor.ts +14 -5
  64. package/src/extensibility/custom-tools/loader.ts +3 -3
  65. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  66. package/src/extensibility/extensions/loader.ts +3 -3
  67. package/src/extensibility/extensions/managed-timers.ts +83 -0
  68. package/src/extensibility/extensions/runner.ts +26 -0
  69. package/src/extensibility/extensions/types.ts +18 -0
  70. package/src/extensibility/extensions/wrapper.ts +2 -1
  71. package/src/extensibility/hooks/loader.ts +3 -3
  72. package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
  73. package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
  74. package/src/extensibility/plugins/manager.ts +2 -2
  75. package/src/extensibility/shared-events.ts +6 -0
  76. package/src/extensibility/utils.ts +91 -25
  77. package/src/irc/bus.ts +22 -3
  78. package/src/launch/broker.ts +3 -2
  79. package/src/launch/client.ts +2 -2
  80. package/src/launch/presence.ts +2 -2
  81. package/src/lsp/client.ts +1 -1
  82. package/src/main.ts +11 -8
  83. package/src/mcp/manager.ts +9 -3
  84. package/src/mcp/transports/stdio.ts +103 -23
  85. package/src/modes/components/advisor-config.ts +65 -3
  86. package/src/modes/components/ask-dialog.ts +1 -1
  87. package/src/modes/components/hook-editor.ts +18 -3
  88. package/src/modes/components/model-hub.ts +138 -42
  89. package/src/modes/components/session-selector.ts +4 -0
  90. package/src/modes/components/status-line/component.test.ts +1 -0
  91. package/src/modes/components/status-line/segments.ts +21 -6
  92. package/src/modes/controllers/command-controller.ts +167 -47
  93. package/src/modes/controllers/event-controller.ts +5 -0
  94. package/src/modes/controllers/extension-ui-controller.ts +4 -22
  95. package/src/modes/controllers/input-controller.ts +12 -12
  96. package/src/modes/controllers/selector-controller.ts +191 -31
  97. package/src/modes/interactive-mode.ts +139 -54
  98. package/src/modes/print-mode.ts +3 -3
  99. package/src/modes/rpc/host-tools.ts +2 -1
  100. package/src/modes/rpc/rpc-mode.ts +19 -4
  101. package/src/modes/warp-events.test.ts +794 -0
  102. package/src/modes/warp-events.ts +232 -0
  103. package/src/plan-mode/model-transition.test.ts +60 -0
  104. package/src/plan-mode/model-transition.ts +51 -0
  105. package/src/registry/agent-lifecycle.ts +133 -18
  106. package/src/sdk.ts +221 -42
  107. package/src/session/agent-session.ts +1285 -348
  108. package/src/session/session-history-format.ts +20 -5
  109. package/src/session/session-manager.ts +48 -0
  110. package/src/slash-commands/builtin-registry.ts +7 -0
  111. package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
  112. package/src/task/executor.ts +1 -1
  113. package/src/task/label.ts +2 -0
  114. package/src/telemetry-export.ts +453 -97
  115. package/src/tools/approval.ts +11 -0
  116. package/src/tools/bash.ts +71 -38
  117. package/src/tools/essential-tools.ts +45 -0
  118. package/src/tools/gh.ts +169 -2
  119. package/src/tools/image-gen.ts +69 -7
  120. package/src/tools/index.ts +7 -5
  121. package/src/tools/read.ts +48 -3
  122. package/src/tools/write.ts +22 -4
  123. package/src/tools/xdev.ts +14 -3
  124. package/src/utils/title-generator.ts +15 -4
  125. package/src/web/search/providers/kimi.ts +18 -12
  126. package/src/web/search/types.ts +6 -1
@@ -13,7 +13,21 @@ export interface AdvisorConfig {
13
13
  model?: string;
14
14
  tools?: string[];
15
15
  instructions?: string;
16
+ /** Per-advisor on/off toggle (default `true`). When `false`, the advisor
17
+ * stays in the roster but its runtime is never built — it shows `○` in
18
+ * the status line and `/advisor status` rather than disappearing. */
19
+ enabled?: boolean;
16
20
  }
21
+ /**
22
+ * Runtime health of a single advisor, surfaced in stats and the status line.
23
+ * - `running` — actively processing primary turns
24
+ * - `paused` — user-toggled off via per-advisor switch (runtime disposed)
25
+ * - `quota_exhausted` — provider returned a quota/rate-limit error; the
26
+ * runtime auto-retries after a cooldown so it can resume without user action
27
+ * - `error` — repeated transient failures; backlog dropped to prevent stall
28
+ * - `no_model` — no model resolved for this advisor's role/explicit model
29
+ */
30
+ export type AdvisorRuntimeStatus = "running" | "paused" | "quota_exhausted" | "error" | "no_model";
17
31
  /**
18
32
  * The result of walking the `WATCHDOG.yml`/`WATCHDOG.yaml` search path: the
19
33
  * deduped advisor roster plus the concatenated top-level `instructions` baseline
@@ -83,12 +97,6 @@ export declare function resolveAdvisorConfigEditPath(scope: AdvisorConfigScope,
83
97
  * editor opens cleanly on a fresh or broken file.
84
98
  */
85
99
  export declare function loadWatchdogConfigFile(filePath: string): Promise<WatchdogConfigDoc>;
86
- /**
87
- * Serialize an editable doc back to block-style `WATCHDOG.yml` text via Bun's
88
- * `YAML.stringify` (the same API the repo uses for other hand-editable config),
89
- * omitting empty fields. Round-trips through {@link loadWatchdogConfigFile}.
90
- * Returns `""` for an empty doc.
91
- */
92
100
  export declare function serializeWatchdogConfig(doc: WatchdogConfigDoc): string;
93
101
  /**
94
102
  * Write an editable doc to `WATCHDOG.yml`. An empty doc removes the file so
@@ -33,10 +33,10 @@ export interface AdvisorRuntimeHost {
33
33
  * Pre-prompt context maintenance for the advisor's own append-only context.
34
34
  * Promotes the advisor model to a larger sibling when its context nears the
35
35
  * window (mirroring the primary's promote-first policy) and resolves `true`
36
- * when the advisor should re-prime reset and replay the current
37
- * primary-bounded transcript because promotion did not free enough room.
38
- * Optional: hosts that omit it get no maintenance (context only shrinks when
39
- * the primary's next compaction triggers {@link AdvisorRuntime.reset}).
36
+ * when the advisor must clear its own context before sending the current
37
+ * incremental update. The cursor stays at the current primary position: this
38
+ * recovery path must never replay the full primary transcript.
39
+ * Optional: hosts that omit it get no proactive maintenance.
40
40
  */
41
41
  maintainContext?(incomingTokens: number): Promise<boolean>;
42
42
  /**
@@ -49,16 +49,21 @@ export interface AdvisorRuntimeHost {
49
49
  /**
50
50
  * Called with the error of every failed advisor turn, before the retry sleep
51
51
  * or the dropped-after-3 path. Lets the host apply credential-level remedies
52
- * the advisor loop lacks: the in-stream a/b/c auth retry rotates through
53
- * sibling credentials within one request but never blocks the LAST failing
54
- * one the primary agent's retry pipeline does that via
55
- * `markUsageLimitReached`, so without this hook the advisor re-picks the
56
- * same usage-limited account on every retry. Errors thrown here are logged
57
- * and swallowed.
52
+ * and configured model fallback that the advisor loop cannot perform itself.
53
+ * Return `true` after switching models so the same clean batch is retried
54
+ * immediately with a fresh failure budget. `failedMessages` contains the
55
+ * failed prompt's appended turns before rollback. Errors thrown here are
56
+ * logged and swallowed.
58
57
  */
59
- onTurnError?(error: unknown): Promise<void> | void;
58
+ onTurnError?(error: unknown, failedMessages: readonly AgentMessage[]): Promise<boolean | undefined> | boolean | undefined;
59
+ /** Called after a successful advisor turn so the host can finish fallback lifecycle reporting. */
60
+ onTurnSuccess?(): Promise<void> | void;
60
61
  /** Surface a non-recovering advisor failure to the host UI without adding model-visible context. */
61
62
  notifyFailure?(error: unknown): void;
63
+ /** Signal that the advisor paused on a quota/rate-limit after host-level
64
+ * recovery (credential switch, fallback chain) declined. Cleared only by
65
+ * an explicit reset (`/new`, config rebuild, session restart). */
66
+ notifyQuotaExhausted?(): void;
62
67
  }
63
68
  /** Signals that an advisor response was discarded before it could become model-visible context. */
64
69
  export declare class AdvisorOutputQuarantinedError extends Error {
@@ -86,6 +91,10 @@ export declare class AdvisorRuntime {
86
91
  disposed: boolean;
87
92
  constructor(agent: AdvisorAgent, host: AdvisorRuntimeHost, retryDelayMs?: number);
88
93
  get backlog(): number;
94
+ get quotaExhausted(): boolean;
95
+ get failureNotified(): boolean;
96
+ /** True after the runtime hard-stopped on repeated or permanent failures. */
97
+ get halted(): boolean;
89
98
  /**
90
99
  * True when `#pending` is non-empty while the drain loop is busy — i.e., newer
91
100
  * primary turns arrived after the current batch's transcript window was fixed
@@ -18,6 +18,7 @@ export declare function buildAutoLearnInstructions(available: {
18
18
  export interface AutoLearnControllerOptions {
19
19
  session: AgentSession;
20
20
  settings: Settings;
21
+ capture: (content: string) => Promise<void>;
21
22
  }
22
23
  export declare class AutoLearnController {
23
24
  #private;
@@ -44,6 +44,23 @@ type LlamaCppDiscoveredModelRuntimeMetadata = {
44
44
  };
45
45
  export declare function discoverModelsByProviderType(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
46
46
  export declare function discoverOllamaModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
47
+ /**
48
+ * applyLlamaCppQwenThinking rewrites a discovered or cached llama.cpp model so a
49
+ * Qwen-family chat template (which defaults `enable_thinking: true`) can be
50
+ * turned off. Qwen ids and the Qwen3.6-based PrismLM Ternary Bonsai GGUFs are
51
+ * routed through chat-completions (the implicit llama.cpp provider defaults to
52
+ * `openai-responses`, whose disable path has no Qwen encoding) with the
53
+ * `qwen-template-false` dialect; omp emits `preserve_thinking` inside
54
+ * `chat_template_kwargs` for Qwen, so the toggle rides there too and history
55
+ * `<think>` blocks survive (`qwenPreserveThinking`). The runtime base URL gets a
56
+ * `/v1` suffix because the chat-completions request would otherwise POST to the
57
+ * native root, which does not serve it. A model with a custom transport (e.g.
58
+ * `pi-native`, whose client appends `/v1/pi/stream`) keeps its base URL so the
59
+ * suffix is not doubled. Non-Qwen models pass through unchanged. Applied on both
60
+ * fresh discovery and cache load, so an upgraded cache is corrected without
61
+ * waiting for re-discovery.
62
+ */
63
+ export declare function applyLlamaCppQwenThinking(model: Model<Api>): Model<Api>;
47
64
  export declare function discoverLlamaCppModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
48
65
  export declare function discoverLlamaCppModelRuntimeMetadata(model: Pick<Model<Api>, "provider" | "id" | "baseUrl" | "headers">, ctx: DiscoveryContext): Promise<LlamaCppDiscoveredModelRuntimeMetadata | undefined>;
49
66
  export declare function discoverOpenAIModelsList(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
@@ -94,11 +94,14 @@ export interface ParsedModelResult {
94
94
  export declare function parseModelPattern(pattern: string, availableModels: Model<Api>[], preferences?: ModelMatchPreferences, options?: {
95
95
  allowInvalidThinkingSelectorFallback?: boolean;
96
96
  }): ParsedModelResult;
97
+ export interface ModelRoleLookup {
98
+ getModelRole(role: ModelRole | string): string | undefined;
99
+ }
97
100
  /**
98
101
  * Expand a role alias like "@smol" to the configured model string.
99
102
  */
100
- export declare function expandRoleAlias(value: string, settings?: Settings): string;
101
- export declare function resolveConfiguredModelPatterns(value: string | string[] | undefined, settings?: Settings): string[];
103
+ export declare function expandRoleAlias(value: string, settings?: ModelRoleLookup): string;
104
+ export declare function resolveConfiguredModelPatterns(value: string | string[] | undefined, settings?: ModelRoleLookup): string[];
102
105
  export interface AgentModelPatternResolutionOptions {
103
106
  settingsOverride?: string | string[];
104
107
  agentModel?: string | string[];
@@ -135,6 +138,7 @@ export interface ResolvedModelRoleValue {
135
138
  }
136
139
  export declare function resolveModelRoleValue(roleValue: string | undefined, availableModels: Model<Api>[], options?: {
137
140
  settings?: Settings;
141
+ roleLookup?: ModelRoleLookup;
138
142
  matchPreferences?: ModelMatchPreferences;
139
143
  }): ResolvedModelRoleValue;
140
144
  interface ExplicitThinkingSelectorOptions {
@@ -14,6 +14,7 @@ import { type SearchProviderId } from "../web/search/types.js";
14
14
  * settings.get("compaction.enabled") // => boolean
15
15
  * settings.set("theme.dark", "titanium") // sync, saves in background
16
16
  */
17
+ export type ModelRoleStorage = "global" | "project";
17
18
  export type SettingTab = "appearance" | "model" | "interaction" | "context" | "memory" | "files" | "shell" | "tools" | "tasks" | "providers";
18
19
  /** Tab display metadata - icon is resolved via theme.symbol() */
19
20
  export type TabMetadata = {
@@ -281,6 +282,26 @@ export declare const SETTINGS_SCHEMA: {
281
282
  readonly type: "array";
282
283
  readonly default: string[];
283
284
  };
285
+ readonly modelRoleStorage: {
286
+ readonly type: "enum";
287
+ readonly values: readonly ["global", "project"];
288
+ readonly default: "global";
289
+ readonly ui: {
290
+ readonly tab: "model";
291
+ readonly group: "Prompt";
292
+ readonly label: "Model Role Storage";
293
+ readonly description: "Where model selector role assignments are saved";
294
+ readonly options: readonly [{
295
+ readonly value: "global";
296
+ readonly label: "Global";
297
+ readonly description: "Save role models in the active profile config (current behavior)";
298
+ }, {
299
+ readonly value: "project";
300
+ readonly label: "Per-project";
301
+ readonly description: "Save project role models in .omp/config.yml; missing project roles use global defaults";
302
+ }];
303
+ };
304
+ };
284
305
  readonly modelRoles: {
285
306
  readonly type: "record";
286
307
  readonly default: Record<string, string>;
@@ -1424,7 +1445,7 @@ export declare const SETTINGS_SCHEMA: {
1424
1445
  readonly tab: "model";
1425
1446
  readonly group: "Retry & Fallback";
1426
1447
  readonly label: "Retry Fallback Chains";
1427
- readonly description: 'JSON object mapping model roles, model selectors ("provider/model-id"), or provider wildcards ("provider/*") to ordered fallback selectors, e.g. {"default":["openai/gpt-4o-mini"],"google-antigravity/*":["google/*","google-vertex/*"]}. Model-oriented keys apply whenever that model/provider is active, regardless of role; a "provider/*" entry keeps the failing model\'s id and swaps the provider.';
1448
+ readonly description: 'JSON object mapping model roles, model selectors ("provider/model-id"), or provider wildcards ("provider/*") to ordered fallback selectors, e.g. {"default":["openai/gpt-4o-mini"],"google-antigravity/*":["google/*","google-vertex/*"]}. Model-oriented keys apply whenever that model/provider is active, regardless of role; a "provider/*" entry keeps the failing model\'s id and swaps the provider. An id-prefixed wildcard ("openrouter/google/*") re-prefixes the failing model\'s bare id (google-antigravity/gemini-x -> openrouter/google/gemini-x) and, used as a key, matches only that provider\'s ids under the prefix.';
1428
1449
  };
1429
1450
  };
1430
1451
  readonly "retry.fallbackRevertPolicy": {
@@ -2547,7 +2568,7 @@ export declare const SETTINGS_SCHEMA: {
2547
2568
  readonly tab: "memory";
2548
2569
  readonly group: "Auto-Learn";
2549
2570
  readonly label: "Auto-run capture at stop";
2550
- readonly description: "When on, auto-run one capture turn at stop (uses extra tokens). Off = passive reminder on your next turn.";
2571
+ readonly description: "When on, auto-run one private capture turn at stop (uses extra tokens). When off, only standing auto-learn guidance remains.";
2551
2572
  readonly condition: "autolearnActive";
2552
2573
  };
2553
2574
  };
@@ -3721,7 +3742,7 @@ export declare const SETTINGS_SCHEMA: {
3721
3742
  };
3722
3743
  readonly "generate_image.enabled": {
3723
3744
  readonly type: "boolean";
3724
- readonly default: true;
3745
+ readonly default: false;
3725
3746
  readonly ui: {
3726
3747
  readonly tab: "tools";
3727
3748
  readonly group: "Available Tools";
@@ -4619,7 +4640,7 @@ export declare const SETTINGS_SCHEMA: {
4619
4640
  }, {
4620
4641
  readonly value: "kimi";
4621
4642
  readonly label: "Kimi";
4622
- readonly description: "Requires MOONSHOT_SEARCH_API_KEY or MOONSHOT_API_KEY";
4643
+ readonly description: "Kimi Code search (requires a Kimi Code Console key via KIMI_SEARCH_API_KEY/MOONSHOT_SEARCH_API_KEY or /login kimi-code; not MOONSHOT_API_KEY)";
4623
4644
  }, {
4624
4645
  readonly value: "parallel";
4625
4646
  readonly label: "Parallel";
@@ -4705,7 +4726,7 @@ export declare const SETTINGS_SCHEMA: {
4705
4726
  };
4706
4727
  readonly "providers.image": {
4707
4728
  readonly type: "enum";
4708
- readonly values: readonly ["auto", "openai", "antigravity", "xai", "gemini", "openrouter"];
4729
+ readonly values: readonly ["auto", "openai", "openai-codex", "antigravity", "xai", "gemini", "openrouter"];
4709
4730
  readonly default: "auto";
4710
4731
  readonly ui: {
4711
4732
  readonly tab: "providers";
@@ -4715,11 +4736,15 @@ export declare const SETTINGS_SCHEMA: {
4715
4736
  readonly options: readonly [{
4716
4737
  readonly value: "auto";
4717
4738
  readonly label: "Auto";
4718
- readonly description: "Priority: active session provider > GPT model image tool > Antigravity > xAI > OpenRouter > Gemini";
4739
+ readonly description: "Priority: per-request provider > configured provider > active session provider > GPT model image tool > Codex subscription > Antigravity > xAI > OpenRouter > Gemini";
4719
4740
  }, {
4720
4741
  readonly value: "openai";
4721
4742
  readonly label: "OpenAI";
4722
- readonly description: "Uses the active GPT Responses/Codex model";
4743
+ readonly description: "OPENAI_API_KEY (gpt-image-2) or active GPT model; falls back to a connected Codex subscription";
4744
+ }, {
4745
+ readonly value: "openai-codex";
4746
+ readonly label: "OpenAI Codex (ChatGPT)";
4747
+ readonly description: "Uses a connected Codex / ChatGPT subscription — no OPENAI_API_KEY needed";
4723
4748
  }, {
4724
4749
  readonly value: "antigravity";
4725
4750
  readonly label: "Antigravity";
@@ -131,12 +131,62 @@ export declare class Settings {
131
131
  /**
132
132
  * Set a model role (helper for modelRoles record). Passing `undefined`
133
133
  * clears the role from the persisted record and any runtime override.
134
+ *
135
+ * In project storage mode, when a project edit has temporarily replaced
136
+ * the process-wide runtime override for `role` and that override is still
137
+ * active (the runtime slot currently matches the project value), the
138
+ * global-layer write must not rewrite that runtime slot — otherwise the
139
+ * global fallback would immediately shadow the still-configured project
140
+ * role. The global layer is still persisted; only the runtime override is
141
+ * left untouched. The guard is precise so that a later clear, a late
142
+ * `overrideModelRoles`, or a storage-mode transition does not leave a
143
+ * stale skip in place.
134
144
  */
135
145
  setModelRole(role: ModelRole | string, modelId: string | undefined): void;
146
+ /**
147
+ * Whether `role`'s runtime override slot currently holds the temporary
148
+ * project-scoped value installed by a prior `setProjectModelRole`. Returns
149
+ * `false` when storage is not project-mode, no capture exists, or the
150
+ * project role was cleared. With explicit provenance invalidation, a
151
+ * surviving capture implies no external supersession occurred.
152
+ */
153
+ isProjectModelRoleRuntimeOverrideActive(role: ModelRole | string): boolean;
154
+ /**
155
+ * Set a model role in the current project's settings layer.
156
+ */
157
+ setProjectModelRole(role: ModelRole | string, modelId: string): void;
158
+ /**
159
+ * Clear a model role from the current project's settings layer.
160
+ */
161
+ clearProjectModelRole(role: ModelRole | string): void;
136
162
  /**
137
163
  * Get a model role (helper for modelRoles record).
138
164
  */
139
165
  getModelRole(role: ModelRole | string): string | undefined;
166
+ /**
167
+ * Get a model role from only the global settings layer.
168
+ */
169
+ getGlobalModelRole(role: ModelRole | string): string | undefined;
170
+ /**
171
+ * Get a model role from only the current project settings layer.
172
+ */
173
+ getProjectModelRole(role: ModelRole | string): string | undefined;
174
+ /**
175
+ * Report which layer actually supplies the effective model role across
176
+ * full merge precedence (runtime override → config overlay → project →
177
+ * global → default). Unlike {@link getModelRoleSource}, this accounts
178
+ * for runtime and config-overlay layers and detects ownership by key
179
+ * presence rather than normalized value, so a `null` tombstone in the
180
+ * overlay or runtime layer correctly blocks lower layers. The project
181
+ * layer is checked through {@link #projectSettingsForMerge} because a
182
+ * project null is a cleared value (falls back to global), not a
183
+ * tombstone.
184
+ */
185
+ getModelRoleProvenance(role: ModelRole | string): "runtime" | "overlay" | "project" | "global" | "default";
186
+ /**
187
+ * Get the persisted layer supplying a model role (project/global/default only).
188
+ */
189
+ getModelRoleSource(role: ModelRole | string): "project" | "global" | "default";
140
190
  /**
141
191
  * Get all model roles (helper for modelRoles record).
142
192
  */
@@ -2,9 +2,20 @@ import type { AgentEvent, AgentTool, AgentToolContext } from "@oh-my-pi/pi-agent
2
2
  import type { CursorMcpCall, CursorShellStreamCallbacks, CursorExecHandlers as ICursorExecHandlers, ToolResultMessage } from "@oh-my-pi/pi-ai";
3
3
  interface CursorExecBridgeOptions {
4
4
  cwd: string;
5
+ getCwd?: () => string;
5
6
  tools: Map<string, AgentTool>;
7
+ getTool?: (name: string) => AgentTool | undefined;
6
8
  getToolContext?: () => AgentToolContext | undefined;
7
9
  emitEvent?: (event: AgentEvent) => void;
10
+ /**
11
+ * Whether the Cursor native `delete` frame may remove files. Unlike every
12
+ * other exec handler, `executeDelete` mutates the filesystem directly instead
13
+ * of consulting {@link tools}, so a background read-only advisor could delete
14
+ * workspace files it was never granted a mutating tool for (issue #5680
15
+ * review). Defaults to allowed to preserve the primary agent's behavior;
16
+ * callers with a restricted tool set (advisors) opt out.
17
+ */
18
+ allowNativeDelete?: boolean;
8
19
  }
9
20
  export declare class CursorExecHandlers implements ICursorExecHandlers {
10
21
  private options;
@@ -185,13 +185,16 @@ export declare function buildExtensionModuleItems(providerId: string, userPaths:
185
185
  * Entry for an installed Claude Code plugin.
186
186
  */
187
187
  export interface ClaudePluginEntry {
188
- scope: "user" | "project";
188
+ /** Claude registry scope; local entries are restricted to their project path. */
189
+ scope?: "user" | "project" | "local";
189
190
  installPath: string;
190
191
  version: string;
191
192
  installedAt: string;
192
193
  lastUpdated: string;
193
194
  gitCommitSha?: string;
194
195
  enabled?: boolean;
196
+ /** Project root recorded by Claude for a local installation. */
197
+ projectPath?: string;
195
198
  }
196
199
  /**
197
200
  * Claude Code installed_plugins.json registry format.
@@ -254,7 +257,7 @@ export declare function registerPluginCacheInvalidator(invalidator: () => void):
254
257
  * Reads ~/.claude/plugins/installed_plugins.json and ~/.omp/plugins/installed_plugins.json,
255
258
  * and optionally the nearest project-scoped registry resolved from `cwd`.
256
259
  *
257
- * Results are cached per `home:resolvedProjectPath` key to avoid repeated parsing.
260
+ * Results are cached per home, project registry, and canonical active project.
258
261
  */
259
262
  export declare function listClaudePluginRoots(home: string, cwd?: string): Promise<{
260
263
  roots: ClaudePluginRoot[];
@@ -33,6 +33,8 @@ export interface BashResult {
33
33
  output: string;
34
34
  exitCode: number | undefined;
35
35
  cancelled: boolean;
36
+ /** True when the command was killed by its timeout deadline (not a user abort). */
37
+ timedOut?: boolean;
36
38
  truncated: boolean;
37
39
  totalLines: number;
38
40
  totalBytes: number;
@@ -0,0 +1,15 @@
1
+ /** Callback invoked when a managed timer's callback throws or rejects. */
2
+ export type ManagedTimerErrorHandler = (event: string, error: string, stack?: string) => void;
3
+ export declare class ManagedTimers {
4
+ #private;
5
+ private readonly onError;
6
+ constructor(onError: ManagedTimerErrorHandler);
7
+ /** Schedule a repeating callback whose throws are contained. */
8
+ setInterval(callback: (...args: unknown[]) => void, ms?: number, ...args: unknown[]): Timer;
9
+ /** Schedule a one-shot callback whose throws are contained. Deregisters after it fires. */
10
+ setTimeout(callback: (...args: unknown[]) => void, ms?: number, ...args: unknown[]): Timer;
11
+ /** Clear one managed timer. Accepts an interval or timeout handle. */
12
+ clear(timer: Timer): void;
13
+ /** Clear every outstanding managed timer. Called on session teardown. */
14
+ clearAll(): void;
15
+ }
@@ -118,6 +118,13 @@ export declare class ExtensionRunner {
118
118
  * Request a graceful shutdown. Called by extension tools and event handlers.
119
119
  */
120
120
  shutdown(): void;
121
+ /**
122
+ * Clear every timer scheduled through `ctx.setInterval` / `ctx.setTimeout`.
123
+ * Called during session teardown so extension background work does not
124
+ * outlive the session (a self-scheduling interval would otherwise keep
125
+ * firing against a disposed session).
126
+ */
127
+ clearManagedTimers(): void;
121
128
  createCommandContext(): ExtensionCommandContext;
122
129
  emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>>;
123
130
  emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined>;
@@ -304,6 +304,24 @@ export interface ExtensionContext {
304
304
  getSystemPrompt(): string[];
305
305
  /** Structured memory runtime for status/search/save across the configured backend. */
306
306
  memory?: MemoryRuntimeContext;
307
+ /**
308
+ * Schedule a repeating callback whose throws are contained. Unlike raw
309
+ * `setInterval`, a synchronous throw or rejected promise from `callback` is
310
+ * logged and surfaced through the extension error channel instead of
311
+ * escaping as a process-fatal `uncaughtException` — one misbehaving timer
312
+ * can no longer take down the whole session. The handle is `unref`'d and
313
+ * cleared automatically on `session_shutdown`. Prefer this over raw
314
+ * `setInterval` for any extension background work.
315
+ */
316
+ setInterval(callback: (...args: unknown[]) => void, ms?: number, ...args: unknown[]): Timer;
317
+ /**
318
+ * Schedule a one-shot callback whose throws are contained, mirroring
319
+ * {@link setInterval}. Cleared automatically on `session_shutdown` if it has
320
+ * not yet fired.
321
+ */
322
+ setTimeout(callback: (...args: unknown[]) => void, ms?: number, ...args: unknown[]): Timer;
323
+ /** Clear a timer scheduled via {@link setInterval} or {@link setTimeout}. */
324
+ clearTimer(timer: Timer): void;
307
325
  }
308
326
  /**
309
327
  * Extended context for command handlers.
@@ -114,6 +114,46 @@ export declare const SettingsManager: {
114
114
  readonly create: (cwd: string, agentDir?: string) => Promise<Settings>;
115
115
  readonly inMemory: () => Settings;
116
116
  };
117
+ /** Scope used by the legacy package manager for discovered resources. */
118
+ export type SourceScope = "user" | "project" | "temporary";
119
+ /** Discovery metadata exposed alongside a legacy package resource path. */
120
+ export interface PathMetadata {
121
+ source: string;
122
+ scope: SourceScope;
123
+ origin: "package" | "top-level";
124
+ baseDir?: string;
125
+ }
126
+ /** One extension, skill, prompt, or theme resolved by the legacy package manager. */
127
+ export interface ResolvedResource {
128
+ path: string;
129
+ enabled: boolean;
130
+ metadata: PathMetadata;
131
+ }
132
+ /** Resource groups returned by {@link DefaultPackageManager.resolve}. */
133
+ export interface ResolvedPaths {
134
+ extensions: ResolvedResource[];
135
+ skills: ResolvedResource[];
136
+ prompts: ResolvedResource[];
137
+ themes: ResolvedResource[];
138
+ }
139
+ /** Action a legacy caller requests when a configured package is unavailable. */
140
+ export type MissingSourceAction = "install" | "skip" | "error";
141
+ /** Construction inputs accepted by the legacy package manager. */
142
+ export interface DefaultPackageManagerOptions {
143
+ cwd: string;
144
+ agentDir: string;
145
+ settingsManager: Settings | Promise<Settings>;
146
+ }
147
+ /**
148
+ * Enumerates the extensions OMP would load through the historical package
149
+ * manager surface used by legacy extensions.
150
+ */
151
+ export declare class DefaultPackageManager {
152
+ #private;
153
+ constructor(options: DefaultPackageManagerOptions);
154
+ /** Resolve enabled extension paths with their OMP plugin provenance. */
155
+ resolve(_onMissing?: (source: string) => Promise<MissingSourceAction>): Promise<ResolvedPaths>;
156
+ }
117
157
  /**
118
158
  * Resource-loader compatibility layer for legacy pi extensions.
119
159
  *
@@ -152,6 +152,12 @@ export interface AgentStartEvent {
152
152
  export interface AgentEndEvent {
153
153
  type: "agent_end";
154
154
  messages: AgentMessage[];
155
+ /**
156
+ * When true, the session has already scheduled an automatic continuation
157
+ * (auto-retry, empty/unexpected-stop retry, etc.). Subscribers must not
158
+ * treat this as a user-visible terminal settle.
159
+ */
160
+ willContinue?: boolean;
155
161
  }
156
162
  /** Fired at the start of each turn */
157
163
  export interface TurnStartEvent {
@@ -11,7 +11,7 @@ export declare function resolvePath(filePath: string, cwd: string): string;
11
11
  */
12
12
  export declare function createNoOpUIContext(): HookUIContext;
13
13
  /**
14
- * Raised by {@link withExitGuard} when a guarded callback synchronously
14
+ * Raised by {@link withHostGuard} when a guarded callback synchronously
15
15
  * attempts to terminate the host process. Callers catch this like any other
16
16
  * load-time failure so the extension/hook is skipped with a logged error
17
17
  * instead of taking the CLI down with it.
@@ -21,4 +21,4 @@ export declare class ExtensionExitError extends Error {
21
21
  readonly code: number | string | undefined;
22
22
  constructor(code: number | string | undefined, alias?: string);
23
23
  }
24
- export declare function withExitGuard<T>(fn: () => Promise<T>): Promise<T>;
24
+ export declare function withHostGuard<T>(fn: () => Promise<T>): Promise<T>;
@@ -37,6 +37,18 @@ export interface StdioSpawnCommand {
37
37
  * grandchildren keep stdout routed through our pipe (#3544).
38
38
  */
39
39
  detached: boolean;
40
+ /**
41
+ * Pass argv to `Bun.spawn` verbatim (Windows only), suppressing the
42
+ * default libuv backslash-quoting.
43
+ *
44
+ * Set when `cmd` already holds a `cmd.exe /d /e:ON /v:OFF /c "<line>"`
45
+ * command line escaped for `cmd.exe`'s parser (see `buildCmdExeArgv`).
46
+ * libuv's quoting targets `CommandLineToArgvW`, not `cmd.exe`, so letting
47
+ * it re-quote a batch launch would corrupt arguments and re-open the
48
+ * `%VAR%` / quote-injection holes the escaping closes (BatBadBut,
49
+ * CVE-2024-24576).
50
+ */
51
+ windowsVerbatimArguments?: boolean;
40
52
  }
41
53
  /** Inputs used to resolve platform-specific stdio spawn behavior. */
42
54
  export interface ResolveStdioSpawnOptions {
@@ -55,7 +67,7 @@ export interface ResolveStdioSpawnOptions {
55
67
  * only appends `.exe` for extensionless names — `.cmd`/`.bat` are never
56
68
  * tried, so `npx` (which exists only as `npx.cmd` on Windows) crashes the
57
69
  * subprocess immediately. When the resolver can't pin the command down,
58
- * route through `cmd.exe /d /s /c` so Windows's own PATHEXT lookup runs.
70
+ * route through `cmd.exe` so Windows's own PATHEXT lookup runs.
59
71
  */
60
72
  export declare function resolveStdioSpawnCommand(config: MCPStdioServerConfig, options: ResolveStdioSpawnOptions): Promise<StdioSpawnCommand>;
61
73
  /** Minimal write surface of `Subprocess.stdin` we need for framed sends. */
@@ -16,11 +16,13 @@
16
16
  * `save` callback.
17
17
  */
18
18
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
19
- import type { Model } from "@oh-my-pi/pi-ai";
19
+ import type { Model, UsageReport } from "@oh-my-pi/pi-ai";
20
20
  import { type Component, type TUI } from "@oh-my-pi/pi-tui";
21
21
  import { type AdvisorConfigScope, type WatchdogConfigDoc } from "../../advisor/index.js";
22
22
  import type { ModelRegistry } from "../../config/model-registry.js";
23
23
  import type { Settings } from "../../config/settings.js";
24
+ import type { PerAdvisorStat } from "../../session/agent-session.js";
25
+ import type { OAuthAccountIdentity } from "../../session/auth-storage.js";
24
26
  /** Host callbacks: all disk + live-runtime effects flow through these. */
25
27
  export interface AdvisorConfigCallbacks {
26
28
  /** Load a scope's `WATCHDOG.yml` into an editable doc (empty when absent). */
@@ -32,6 +34,11 @@ export interface AdvisorConfigCallbacks {
32
34
  requestRender: () => void;
33
35
  /** Surface a transient status/warning line to the user. */
34
36
  notify: (message: string) => void;
37
+ /** Live advisor usage stats; lets the preview show tokens/cost per advisor. */
38
+ getAdvisorStats?: () => PerAdvisorStat[];
39
+ getUsageReports?: () => Promise<UsageReport[] | null>;
40
+ /** Resolve the active OAuth identity for quota filtering (per-advisor account stickiness). */
41
+ resolveActiveAccount?: (provider: string, sessionId?: string) => OAuthAccountIdentity | undefined;
35
42
  }
36
43
  export interface AdvisorConfigDeps {
37
44
  modelRegistry: ModelRegistry;
@@ -11,6 +11,13 @@ import { Container, type Focusable, type TUI } from "@oh-my-pi/pi-tui";
11
11
  export interface HookEditorOptions {
12
12
  /** When true, use prompt-style keybindings with the legacy ask prompt chrome. */
13
13
  promptStyle?: boolean;
14
+ /**
15
+ * Max rows the inner Editor may occupy. When omitted, the editor is
16
+ * bounded to the current terminal height minus the component's chrome
17
+ * (≈10 rows) so long content scrolls instead of pushing the submit
18
+ * hint out of view.
19
+ */
20
+ maxHeight?: number;
14
21
  }
15
22
  /** Interactive multiline dialog used by hooks and the ask tool's Other response. */
16
23
  export declare class HookEditorComponent extends Container implements Focusable {
@@ -8,11 +8,12 @@ export interface ScopedModelItem {
8
8
  model: Model;
9
9
  thinkingLevel?: string;
10
10
  }
11
+ export type ModelRoleSelectionScope = "global" | "project";
11
12
  export interface ModelHubCallbacks {
12
13
  /** Persist a role assignment. */
13
- onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string) => void;
14
+ onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string, scope?: ModelRoleSelectionScope) => void;
14
15
  /** Clear a configured role back to auto-selection. */
15
- onUnassign: (role: string) => void;
16
+ onUnassign: (role: string, scope?: ModelRoleSelectionScope) => void;
16
17
  /** Persist a `retry.fallbackChains` entry — keyed by a role, `provider/model-id`, or `provider/*`; an empty chain clears the key. */
17
18
  onFallbackChainChange?: (role: string, chain: string[]) => void;
18
19
  /** Locked provider activation: forward to the /login flow. */
@@ -37,6 +38,8 @@ export declare class ModelHubComponent implements Component {
37
38
  /** Cancel pending provider refresh timers and the spinner. Host calls this on overlay close. */
38
39
  dispose(): void;
39
40
  invalidate(): void;
41
+ /** Re-sync after an asynchronous callback finishes mutating settings. */
42
+ refreshAfterExternalMutation(): void;
40
43
  handleInput(data: string): void;
41
44
  render(width: number): string[];
42
45
  }
@@ -85,6 +85,8 @@ export declare class SessionSelectorComponent extends Container {
85
85
  setOnRequestRender(callback: () => void): void;
86
86
  /** Ignore input after selection while the host resumes the session. */
87
87
  lockInput(): void;
88
+ /** Re-enable input after a failed resume so the user can pick again. */
89
+ unlockInput(): void;
88
90
  /**
89
91
  * Dispose the session list explicitly: while the delete-confirmation dialog
90
92
  * is mounted the list is detached from the child tree, so Container's
@@ -52,4 +52,12 @@ export declare class CommandController {
52
52
  handleHandoffCommand(customInstructions?: string): Promise<void>;
53
53
  }
54
54
  export declare function renderProviderSection(details: ProviderDetails, uiTheme: Pick<typeof theme, "fg">): string;
55
+ /**
56
+ * Compact one-line quota summary for a single advisor's provider.
57
+ * Returns `null` when the provider has no usage data.
58
+ * When `activeAccount` is provided, only limits matching that credential
59
+ * are shown (mirrors `renderUsageReports`'s account-stickiness filtering).
60
+ * Example output: `Quota: 7d window · 67% used · resets in 3.2d`
61
+ */
62
+ export declare function formatCompactQuota(provider: string, reports: UsageReport[], nowMs: number, activeAccount?: OAuthAccountIdentity): string | null;
55
63
  export declare function renderUsageReports(reports: UsageReport[], uiTheme: typeof theme, nowMs: number, availableWidth: number, resolveActiveAccount?: (provider: string) => OAuthAccountIdentity | undefined): string;