@oh-my-pi/pi-coding-agent 17.2.3 → 17.2.4

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 (92) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/{CHANGELOG-c5hpqt9r.md → CHANGELOG-bpmhv26t.md} +41 -0
  3. package/dist/cli.js +3270 -3270
  4. package/dist/types/advisor/advise-tool.d.ts +6 -0
  5. package/dist/types/advisor/runtime.d.ts +4 -4
  6. package/dist/types/capability/mcp.d.ts +9 -0
  7. package/dist/types/cli/models-cli.d.ts +1 -1
  8. package/dist/types/config/keybindings.d.ts +1 -1
  9. package/dist/types/config/settings-schema.d.ts +1 -1
  10. package/dist/types/discovery/helpers.d.ts +6 -0
  11. package/dist/types/discovery/omp-extension-roots.d.ts +21 -2
  12. package/dist/types/extensibility/extensions/loader.d.ts +6 -2
  13. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +2 -2
  14. package/dist/types/launch/paths.d.ts +2 -1
  15. package/dist/types/mcp/request-id.d.ts +19 -0
  16. package/dist/types/mcp/types.d.ts +14 -0
  17. package/dist/types/memory-backend/index.d.ts +1 -0
  18. package/dist/types/memory-backend/messages.d.ts +15 -0
  19. package/dist/types/modes/components/model-browser.d.ts +7 -5
  20. package/dist/types/modes/components/model-picker.d.ts +9 -3
  21. package/dist/types/modes/components/read-tool-group.d.ts +9 -0
  22. package/dist/types/modes/types.d.ts +1 -1
  23. package/dist/types/registry/agent-lifecycle.d.ts +14 -5
  24. package/dist/types/registry/agent-registry.d.ts +4 -3
  25. package/dist/types/sdk.d.ts +1 -1
  26. package/dist/types/secrets/index.d.ts +6 -8
  27. package/dist/types/session/agent-session.d.ts +6 -1
  28. package/dist/types/session/model-controls.d.ts +0 -1
  29. package/dist/types/session/session-advisors.d.ts +7 -0
  30. package/dist/types/session/session-maintenance.d.ts +1 -0
  31. package/dist/types/session/session-manager.d.ts +11 -5
  32. package/dist/types/system-prompt.d.ts +2 -0
  33. package/dist/types/tools/default-renderer.d.ts +3 -0
  34. package/package.json +12 -12
  35. package/scripts/legacy-pi-virtual-module.ts +12 -2
  36. package/src/advisor/advise-tool.ts +18 -0
  37. package/src/advisor/runtime.ts +6 -6
  38. package/src/capability/mcp.ts +7 -0
  39. package/src/cli/models-cli.ts +13 -16
  40. package/src/collab/host.ts +1 -1
  41. package/src/config/keybindings.ts +14 -9
  42. package/src/config/mcp-schema.json +5 -0
  43. package/src/config/model-registry.ts +7 -1
  44. package/src/config/settings-schema.ts +1 -1
  45. package/src/discovery/builtin.ts +10 -0
  46. package/src/discovery/helpers.ts +10 -0
  47. package/src/discovery/mcp-json.ts +11 -1
  48. package/src/discovery/omp-extension-roots.ts +79 -20
  49. package/src/discovery/omp-plugins.ts +10 -1
  50. package/src/extensibility/extensions/loader.ts +66 -28
  51. package/src/extensibility/plugins/marketplace/registry.ts +4 -8
  52. package/src/launch/paths.ts +2 -5
  53. package/src/main.ts +24 -22
  54. package/src/mcp/config.ts +1 -0
  55. package/src/mcp/request-id.ts +24 -0
  56. package/src/mcp/transports/http.ts +4 -2
  57. package/src/mcp/transports/sse.ts +4 -2
  58. package/src/mcp/transports/stdio.ts +4 -2
  59. package/src/mcp/types.ts +15 -0
  60. package/src/memory-backend/index.ts +1 -0
  61. package/src/memory-backend/messages.ts +19 -0
  62. package/src/modes/components/agent-hub.ts +1 -1
  63. package/src/modes/components/custom-editor.ts +1 -1
  64. package/src/modes/components/model-browser.ts +25 -17
  65. package/src/modes/components/model-picker.ts +10 -6
  66. package/src/modes/components/read-tool-group.ts +26 -0
  67. package/src/modes/components/tool-execution.ts +77 -20
  68. package/src/modes/controllers/command-controller.ts +2 -2
  69. package/src/modes/controllers/event-controller.ts +182 -10
  70. package/src/modes/controllers/selector-controller.ts +27 -6
  71. package/src/modes/types.ts +5 -1
  72. package/src/prompts/tools/bash.md +1 -1
  73. package/src/registry/agent-lifecycle.ts +36 -11
  74. package/src/registry/agent-registry.ts +12 -5
  75. package/src/sdk.ts +68 -29
  76. package/src/secrets/index.ts +15 -17
  77. package/src/session/agent-session.ts +10 -1
  78. package/src/session/agent-storage.ts +6 -4
  79. package/src/session/history-storage.ts +4 -2
  80. package/src/session/messages.ts +31 -7
  81. package/src/session/model-controls.ts +0 -1
  82. package/src/session/session-advisors.ts +19 -1
  83. package/src/session/session-maintenance.ts +3 -0
  84. package/src/session/session-manager.ts +128 -76
  85. package/src/session/session-tools.ts +25 -23
  86. package/src/slash-commands/builtin-registry.ts +2 -2
  87. package/src/system-prompt.ts +6 -1
  88. package/src/task/executor.ts +8 -1
  89. package/src/tiny/text.ts +12 -0
  90. package/src/tools/default-renderer.ts +19 -4
  91. package/src/tools/index.ts +10 -6
  92. package/src/web/search/providers/anthropic.ts +3 -1
@@ -109,6 +109,12 @@ export declare class AdviseTool implements AgentTool<typeof adviseSchema, Advise
109
109
  }, {}>;
110
110
  readonly intent: "omit";
111
111
  constructor(onAdvice: (note: string, severity?: AdviseDetails["severity"]) => void);
112
+ /**
113
+ * Mark whether the next advisor prompt reviews an in-progress primary turn.
114
+ * Non-blockers are withheld until a completed update so partial work does
115
+ * not interrupt the primary before it can finish its planned steps.
116
+ */
117
+ beginUpdate(inProgress: boolean): void;
112
118
  /** Clear delivered-note memory when the advisor starts a fresh conversation. */
113
119
  resetDeliveredNotes(): void;
114
120
  execute(_toolCallId: string, args: AdviseParams, _signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<AdviseDetails>, _context?: AgentToolContext): Promise<AgentToolResult<AdviseDetails>>;
@@ -41,11 +41,11 @@ export interface AdvisorRuntimeHost {
41
41
  maintainContext?(incomingTokens: number, signal: AbortSignal): Promise<boolean>;
42
42
  /**
43
43
  * Called immediately before each `agent.prompt(batch)` cycle. Lets the host
44
- * clear per-update advisor state currently the one-advise-per-update gate
45
- * in {@link AdvisorEmissionGuard}, which the host owns because it is the
46
- * one that routes `advise()` results back to the primary.
44
+ * clear per-update advisor state and apply the in-progress delivery policy.
45
+ * The host owns these gates because it routes `advise()` results back to the
46
+ * primary.
47
47
  */
48
- beginAdvisorUpdate?(): void;
48
+ beginAdvisorUpdate?(inProgress: boolean): void;
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
@@ -1,3 +1,10 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Servers Capability
3
+ *
4
+ * Canonical shape for MCP server configurations, regardless of source format.
5
+ * All providers translate their native format to this shape.
6
+ */
7
+ import type { MCPRequestIdFormat } from "../mcp/types.js";
1
8
  import type { SourceMeta } from "./types.js";
2
9
  /**
3
10
  * Canonical MCP server configuration.
@@ -9,6 +16,8 @@ export interface MCPServer {
9
16
  enabled?: boolean;
10
17
  /** Connection timeout in milliseconds */
11
18
  timeout?: number;
19
+ /** Encoding for outgoing JSON-RPC request ids (default: `"number"`) */
20
+ requestIdFormat?: MCPRequestIdFormat;
12
21
  /** Command to run (for stdio transport) */
13
22
  command?: string;
14
23
  /** Command arguments */
@@ -37,7 +37,7 @@ export interface RunModelsListingOptions {
37
37
  settingsExtensions?: string[];
38
38
  /** Disabled extension ids from settings (`disabledExtensions`). */
39
39
  disabledExtensionIds?: string[];
40
- /** When true, skip discovery and only load `additionalExtensionPaths`. */
40
+ /** When true, exclude ambient factories and resolve only `additionalExtensionPaths`. */
41
41
  disableExtensionDiscovery?: boolean;
42
42
  }
43
43
  export declare function runModelsListing(options: RunModelsListingOptions): Promise<void>;
@@ -240,7 +240,7 @@ export declare const KEYBINDINGS: {
240
240
  readonly description: "Retry last failed assistant turn";
241
241
  };
242
242
  readonly "app.message.dequeue": {
243
- readonly defaultKeys: "alt+up";
243
+ readonly defaultKeys: ["alt+up", "shift+up"];
244
244
  readonly description: "Dequeue message";
245
245
  };
246
246
  readonly "app.clipboard.pasteImage": {
@@ -4352,7 +4352,7 @@ export declare const SETTINGS_SCHEMA: {
4352
4352
  readonly tab: "tools";
4353
4353
  readonly group: "Discovery & MCP";
4354
4354
  readonly label: "xd:// Tools";
4355
- readonly description: "Mount rarely-used (discoverable) tools under xd:// device URLs driven via read/write instead of shipping their schemas on every request. Disable to expose every enabled tool top-level.";
4355
+ readonly description: "Mount rarely-used (discoverable) tools under xd:// device URLs driven via read/write instead of shipping their schemas on every request. Sessions without a granted write tool skip mounting and expose every tool top-level. Disable to expose every enabled tool top-level.";
4356
4356
  };
4357
4357
  };
4358
4358
  readonly "tools.xdevDocs": {
@@ -2,6 +2,7 @@ import type { ExtensionModule } from "../capability/extension-module.js";
2
2
  import { type Rule } from "../capability/rule.js";
3
3
  import type { Skill } from "../capability/skill.js";
4
4
  import type { LoadContext, LoadResult, SourceMeta } from "../capability/types.js";
5
+ import type { MCPRequestIdFormat } from "../mcp/types.js";
5
6
  import { type ConfiguredThinkingLevel } from "../thinking.js";
6
7
  /**
7
8
  * Standard paths for each config source.
@@ -79,6 +80,11 @@ export declare function resolveCopilotHome(home: string): string;
79
80
  */
80
81
  export declare function createSourceMeta(provider: string, filePath: string, level: "user" | "project"): SourceMeta;
81
82
  export declare function parseBoolean(value: unknown): boolean | undefined;
83
+ /**
84
+ * Parse an MCP `requestIdFormat` value. Unrecognized values are dropped so a typo
85
+ * degrades to the default integer ids rather than reaching a transport.
86
+ */
87
+ export declare function parseRequestIdFormat(value: unknown): MCPRequestIdFormat | undefined;
82
88
  /**
83
89
  * Parse a comma-separated string into an array of trimmed, non-empty strings.
84
90
  */
@@ -8,6 +8,24 @@ export interface OmpExtensionRoot {
8
8
  /** Scope from which the path was sourced. */
9
9
  level: "user" | "project";
10
10
  }
11
+ export type OmpExtensionRootMode = "merge" | "explicit-only";
12
+ export interface InjectOmpExtensionCliRootOptions {
13
+ /**
14
+ * `explicit-only` exposes only roots named by this CLI invocation. Use it
15
+ * with `--no-extensions` so configured and installed packages cannot
16
+ * contribute sibling capabilities through the `omp-plugins` provider.
17
+ */
18
+ mode?: OmpExtensionRootMode;
19
+ /** Replace roots from an earlier invocation instead of extending them. */
20
+ replace?: boolean;
21
+ }
22
+ /**
23
+ * Run one SDK invocation with its own extension-package roots. Async resources
24
+ * started inside `callback` retain this scope, including discovery deliberately
25
+ * deferred until the end of session startup. Raw relative paths are resolved by
26
+ * {@link listOmpExtensionRoots} against that invocation's active cwd.
27
+ */
28
+ export declare function withOmpExtensionRootScope<T>(paths: readonly string[], mode: OmpExtensionRootMode, callback: () => T): T;
11
29
  /**
12
30
  * Register CLI-provided extension package paths (e.g. from `--extension`/`-e`)
13
31
  * so the sub-discovery providers can find their sibling `skills/`, `hooks/`,
@@ -17,7 +35,7 @@ export interface OmpExtensionRoot {
17
35
  * Call once during startup before any capability load. Repeated calls extend
18
36
  * the registered set; {@link clearOmpExtensionCliRoots} resets for tests.
19
37
  */
20
- export declare function injectOmpExtensionCliRoots(paths: readonly string[], home: string, cwd: string): void;
38
+ export declare function injectOmpExtensionCliRoots(paths: readonly string[], home: string, cwd: string, options?: InjectOmpExtensionCliRootOptions): void;
21
39
  /** Drop every CLI-injected root. Tests use this between cases. */
22
40
  export declare function clearOmpExtensionCliRoots(): void;
23
41
  /** Inspect currently-injected CLI roots (read-only). Exposed for diagnostics + tests. */
@@ -28,7 +46,8 @@ export declare function getInjectedOmpExtensionCliRoots(): readonly OmpExtension
28
46
  * Sources, in order of precedence (later entries with the same absolute path
29
47
  * are dropped):
30
48
  *
31
- * 1. CLI roots injected via {@link injectOmpExtensionCliRoots}
49
+ * 1. Invocation-scoped SDK roots, when present; otherwise CLI roots injected
50
+ * via {@link injectOmpExtensionCliRoots}
32
51
  * 2. Project `<cwd>/.omp/settings.json#extensions`
33
52
  * 3. User `~/.omp/agent/settings.json#extensions`
34
53
  * 4. Enabled npm/link plugins installed under `<plugins>/node_modules/` (for
@@ -53,10 +53,14 @@ export declare function loadExtensions(paths: string[], cwd: string, eventBus?:
53
53
  * `LoadExtensionsResult` directly would reuse handlers/tools/commands that
54
54
  * closed over the parent's `cwd` and event bus.
55
55
  */
56
- export declare function discoverExtensionPaths(configuredPaths: string[], cwd: string, disabledExtensionIds?: string[]): Promise<string[]>;
56
+ export interface DiscoverExtensionPathOptions {
57
+ /** Include ambient native extensions, hooks, and installed plugins. */
58
+ ambient?: boolean;
59
+ }
60
+ export declare function discoverExtensionPaths(configuredPaths: string[], cwd: string, disabledExtensionIds?: string[], options?: DiscoverExtensionPathOptions): Promise<string[]>;
57
61
  /**
58
62
  * Discover and load extensions from standard locations. Composed of
59
63
  * {@link discoverExtensionPaths} (FS scan) + {@link loadExtensions}
60
64
  * (per-session binding).
61
65
  */
62
- export declare function discoverAndLoadExtensions(configuredPaths: string[], cwd: string, eventBus?: EventBus, disabledExtensionIds?: string[]): Promise<LoadExtensionsResult>;
66
+ export declare function discoverAndLoadExtensions(configuredPaths: string[], cwd: string, eventBus?: EventBus, disabledExtensionIds?: string[], options?: DiscoverExtensionPathOptions): Promise<LoadExtensionsResult>;
@@ -2,7 +2,7 @@
2
2
  * Registry read/write operations for the marketplace plugin system.
3
3
  *
4
4
  * Two registries:
5
- * - marketplaces.json under getConfigRootDir() — which catalogs the user has added
5
+ * - marketplaces.json at getMarketplacesRegistryPath() — which catalogs the user has added
6
6
  * - installed_plugins.json under getPluginsDir() — which plugins are installed
7
7
  *
8
8
  * Read/write functions accept explicit file paths so callers control the
@@ -11,8 +11,8 @@
11
11
  * Both use atomic write (tmp + rename). On Windows, rename over existing file
12
12
  * can fail with EPERM — fallback: unlink target then rename.
13
13
  */
14
+ export { getMarketplacesRegistryPath } from "@oh-my-pi/pi-utils";
14
15
  import type { InstalledPluginEntry, InstalledPluginsRegistry, MarketplaceRegistryEntry, MarketplacesRegistry } from "./types.js";
15
- export declare function getMarketplacesRegistryPath(): string;
16
16
  export declare function getInstalledPluginsRegistryPath(): string;
17
17
  export declare function getMarketplacesCacheDir(): string;
18
18
  export declare function getPluginsCacheDir(): string;
@@ -1,4 +1,5 @@
1
+ import { getDaemonRuntimeDir } from "@oh-my-pi/pi-utils";
1
2
  /** Resolve the private runtime directory shared by omp processes in one project directory. */
2
- export declare function daemonRuntimeDir(projectDir: string, configRoot?: string): string;
3
+ export { getDaemonRuntimeDir as daemonRuntimeDir };
3
4
  /** Resolve the Unix socket or Windows named pipe used by one project broker. */
4
5
  export declare function daemonBrokerEndpoint(projectDir: string, runtimeDir: string): string;
@@ -0,0 +1,19 @@
1
+ import type { MCPRequestIdFormat } from "./types.js";
2
+ /**
3
+ * Per-transport allocator for outgoing JSON-RPC request ids.
4
+ *
5
+ * JSON-RPC 2.0 permits String and Number ids equally, but the wider MCP
6
+ * ecosystem (reference SDKs included) issues incrementing integers, so servers
7
+ * are best tested against numbers — Apple's `xcrun mcpbridge` even logs
8
+ * `mcpbridge.DecodeError Code=1` and never answers a string id. Integers are
9
+ * therefore the default. `"string"` opts a server back into collision-resistant
10
+ * snowflake ids.
11
+ *
12
+ * The counter is per instance and never reset, so ids stay unique for the life
13
+ * of the transport even though a reconnect rejects and clears every pending
14
+ * request.
15
+ */
16
+ export declare class RequestIdAllocator {
17
+ #private;
18
+ next(format: MCPRequestIdFormat | undefined): string | number;
19
+ }
@@ -43,12 +43,26 @@ export interface MCPAuthConfig {
43
43
  /** MCP resource URI — persisted for OAuth resource indicators during refresh */
44
44
  resource?: string;
45
45
  }
46
+ /** Encoding used for outgoing JSON-RPC request ids. */
47
+ export type MCPRequestIdFormat = "string" | "number";
46
48
  /** Base server config with shared options */
47
49
  interface MCPServerConfigBase {
48
50
  /** Whether this server is enabled (default: true) */
49
51
  enabled?: boolean;
50
52
  /** MCP request timeout in milliseconds (default: 30000, 0 to disable) */
51
53
  timeout?: number;
54
+ /**
55
+ * Encoding for outgoing JSON-RPC request ids (default: `"number"`).
56
+ *
57
+ * Set `"string"` for servers that need collision-resistant snowflake string
58
+ * ids instead of per-transport integers. See `RequestIdAllocator` in
59
+ * `./request-id`.
60
+ *
61
+ * OMP-specific, so only the OMP-owned discovery providers parse it (native,
62
+ * standalone `mcp.json`, OMP plugins). Providers that translate another
63
+ * tool's config do not, since the key is not part of those formats.
64
+ */
65
+ requestIdFormat?: MCPRequestIdFormat;
52
66
  /** Authentication configuration (optional) */
53
67
  auth?: MCPAuthConfig;
54
68
  /** OAuth configuration for servers requiring explicit client credentials */
@@ -1,6 +1,7 @@
1
1
  export type { MnemopiBackendConfig, MnemopiLlmMode, MnemopiProviderOptions, MnemopiScoping, } from "../mnemopi/config.js";
2
2
  export type { MnemopiMemoryEditOperation, MnemopiMemoryEditOptions, MnemopiMemoryEditResult, MnemopiSessionState, MnemopiSessionStateOptions, } from "../mnemopi/state.js";
3
3
  export * from "./local-backend.js";
4
+ export * from "./messages.js";
4
5
  export * from "./off-backend.js";
5
6
  export * from "./resolve.js";
6
7
  export * from "./runtime.js";
@@ -0,0 +1,15 @@
1
+ import type { MemoryBackendId } from "./types.js";
2
+ /**
3
+ * Fallback text for `/memory stats` and `/memory diagnose` when the active
4
+ * backend's hook is missing or returns nothing.
5
+ *
6
+ * The `off` backend isn't a real backend a user picked among several
7
+ * stats-capable options — it's the no-op state memory falls back to by
8
+ * default — so it gets its own message instead of the generic
9
+ * "not available for the X backend" template, which would otherwise read as
10
+ * the self-contradictory "not available for the off backend".
11
+ *
12
+ * Shared by both the TUI (`CommandController.handleMemoryCommand`) and the
13
+ * ACP/RPC slash-command handler so the two surfaces stay consistent.
14
+ */
15
+ export declare function memoryStatsUnavailableMessage(backendId: MemoryBackendId, action: "stats" | "diagnose"): string;
@@ -58,10 +58,10 @@ export declare function formatRoleChip(role: string, assignment: RoleAssignment,
58
58
  export interface ModelBrowserOptions {
59
59
  /** Render the dim `provider/` prefix before model ids. Default true. */
60
60
  showProvider?: boolean;
61
- /** Session token count used to disable models whose context window is exceeded. */
61
+ /** Session token count used to flag models whose context window is exceeded. */
62
62
  currentContextTokens?: number;
63
- /** When true, rows over the current context are unselectable (session-switch mode). */
64
- disableOverContext?: boolean;
63
+ /** When true, over-context rows render grayed; picking one compacts first (session-switch mode). */
64
+ markOverContext?: boolean;
65
65
  /** Host-provided empty-state text (e.g. provider discovery status). */
66
66
  emptyText?: () => string | undefined;
67
67
  }
@@ -91,8 +91,8 @@ export declare class ModelBrowser implements Component {
91
91
  setShowProvider(show: boolean): void;
92
92
  /** Keep the source order after fuzzy filtering instead of applying model-specific ranking. */
93
93
  setPreserveQueryOrder(preserve: boolean): void;
94
- /** Allow hosts to toggle context-window eligibility between browser modes. */
95
- setDisableOverContext(disable: boolean): void;
94
+ /** Allow hosts to toggle context-window flagging between browser modes. */
95
+ setMarkOverContext(mark: boolean): void;
96
96
  /** Focused: accent cursor + selected-row background band. Unfocused: dim cursor, no band. */
97
97
  setFocused(focused: boolean): void;
98
98
  /** Total rendered height for the current `maxVisible` (host layout budgeting). */
@@ -103,6 +103,8 @@ export declare class ModelBrowser implements Component {
103
103
  get visibleCount(): number;
104
104
  /** Move selection to `selector`; false when it is not in the current view. */
105
105
  selectSelector(selector: string): boolean;
106
+ /** True when `item`'s context window is smaller than the live session token count (grayed row; hosts compact before switching). */
107
+ isOverContext(item: ModelBrowserItem): boolean;
106
108
  /**
107
109
  * Move the selection by `delta` rows, skipping disabled rows. Single steps
108
110
  * wrap at the ends; `wrap: false` (page/home/end jumps) clamps instead.
@@ -11,15 +11,21 @@ import type { Settings } from "../../config/settings.js";
11
11
  import type { ResolvedRoleModel } from "../../session/agent-session.js";
12
12
  import type { ScopedModelItem } from "./model-hub.js";
13
13
  export interface ModelPickerCallbacks {
14
- /** A model was chosen for a session-only switch. `selector` is `provider/id`. */
15
- onPick: (model: Model, selector: string) => void;
14
+ /**
15
+ * A model was chosen for a session-only switch. `selector` is `provider/id`.
16
+ * `overContext` is true when the session transcript exceeds the model's
17
+ * context window — the host must compact before switching.
18
+ */
19
+ onPick: (model: Model, selector: string, meta: {
20
+ overContext: boolean;
21
+ }) => void;
16
22
  /** A configured ctrl+p quick role was chosen. */
17
23
  onPickRole?: (entry: ResolvedRoleModel) => void;
18
24
  /** The picker was dismissed. */
19
25
  onCancel: () => void;
20
26
  }
21
27
  export interface ModelPickerOptions {
22
- /** Session token count; models with smaller context windows are disabled. */
28
+ /** Session token count; models with smaller context windows are grayed and compact-first on pick. */
23
29
  currentContextTokens?: number;
24
30
  /** `provider/id` of the session's active model; highlighted and preselected. */
25
31
  currentSelector?: string;
@@ -36,6 +36,15 @@ export declare class ReadToolGroupComponent extends Container implements ToolExe
36
36
  */
37
37
  seal(): void;
38
38
  updateArgs(args: ReadRenderArgs, toolCallId?: string): void;
39
+ /**
40
+ * Re-key an entry whose streamed tool-call id changed mid-stream (a provider
41
+ * rewriting the id across deltas; see EventController's
42
+ * `#streamedToolCallIdByIndex`). Preserves row order so a sibling read run is
43
+ * not visibly reshuffled, and no-ops when the rename would collide.
44
+ */
45
+ renameEntry(oldId: string, newId: string): void;
46
+ /** Remove one call without discarding successful siblings in the shared group. */
47
+ removeEntry(toolCallId: string): boolean;
39
48
  updateResult(result: {
40
49
  content: Array<{
41
50
  type: string;
@@ -331,7 +331,7 @@ export interface InteractiveModeContext {
331
331
  handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
332
332
  handleMCPCommand(text: string): Promise<void>;
333
333
  handleSSHCommand(text: string): Promise<void>;
334
- handleCompactCommand(customInstructions?: string, mode?: CompactMode): Promise<CompactionOutcome>;
334
+ handleCompactCommand(customInstructions?: string, mode?: CompactMode, beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>): Promise<CompactionOutcome>;
335
335
  handleHandoffCommand(customInstructions?: string): Promise<void>;
336
336
  handleShakeCommand(mode: ShakeMode): Promise<void>;
337
337
  handleMoveCommand(targetPath?: string): Promise<void>;
@@ -92,12 +92,21 @@ export declare class AgentLifecycleManager {
92
92
  */
93
93
  ensureLive(id: string): Promise<AgentSession>;
94
94
  /**
95
- * Hard removal: dispose if live, unregister from registry, drop timers.
96
- * When `expected` is given, only a ref matching it is released; a stale
97
- * release can never take down a newer same-id ref. Returns true when a
98
- * matching ref was released.
95
+ * Dispose if live and drop timers. When `expected` is given, only a ref
96
+ * matching it is released; a stale release can never take down a newer
97
+ * same-id ref. Returns true when a matching ref was released.
98
+ *
99
+ * By default the ref is unregistered (teardown / one-shot removal). Pass
100
+ * `tombstone: true` for an explicit kill: the ref is kept registered as a
101
+ * terminal `aborted` row (session detached) instead of being removed, so a
102
+ * later persisted-subagent scan (e.g. Agent Hub reopen) skips it via its
103
+ * `if (!registry.get(id))` guard rather than re-adopting the surviving
104
+ * on-disk transcript as a fresh `parked` row. Mirrors
105
+ * `finalizeSubagentLifecycle`'s genuine-kill path.
99
106
  */
100
- release(id: string, expected?: AgentRefExpectation): Promise<boolean>;
107
+ release(id: string, expected?: AgentRefExpectation, options?: {
108
+ tombstone?: boolean;
109
+ }): Promise<boolean>;
101
110
  /** Teardown everything (process exit / main session dispose). */
102
111
  dispose(): Promise<void>;
103
112
  }
@@ -67,9 +67,10 @@ export declare class AgentRegistry {
67
67
  static resetGlobalForTests(): void;
68
68
  register(input: RegisterInput): AgentRef;
69
69
  /**
70
- * Register a new id only when it is absent, or reuse the exact ref a parked
71
- * revival was authorized to revive. A missing expected ref is a failed CAS:
72
- * callers must never claim an id after its prior generation disappeared.
70
+ * Register a new id only when it is absent, or reuse the exact detached
71
+ * `parked` ref a revival was authorized to revive. A missing, replaced, or
72
+ * terminal expected ref is a failed CAS: delayed revivers must never claim an
73
+ * id after its prior generation disappeared or was hard-killed.
73
74
  */
74
75
  registerIfAvailable(input: RegisterInput, expected: AgentRef | null): AgentRef | undefined;
75
76
  setStatus(id: string, status: AgentStatus, expected?: AgentRefExpectation): boolean;
@@ -349,7 +349,7 @@ export declare function discoverSkills(cwd?: string, _agentDir?: string, setting
349
349
  * Discover context files (AGENTS.md) walking up from cwd.
350
350
  * Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
351
351
  */
352
- export declare function discoverContextFiles(cwd?: string, _agentDir?: string): Promise<Array<{
352
+ export declare function discoverContextFiles(cwd?: string, _agentDir?: string, disabledExtensions?: string[]): Promise<Array<{
353
353
  path: string;
354
354
  content: string;
355
355
  depth?: number;
@@ -1,13 +1,11 @@
1
1
  import { type SecretEntry } from "./obfuscator.js";
2
2
  /**
3
- * Per-install secret key for the placeholder digest. Persisted under the agent
4
- * config directory and never sent to a provider, so model-visible placeholders
5
- * cannot be reversed by dictionary-hashing candidate secrets. Stable across
6
- * sessions so persisted transcripts deobfuscate consistently. Defaults to
7
- * `getAgentDir()` the same directory `createAgentSession()` passes as
8
- * `agentDir` so a caller relying on the default reads/writes the identical
9
- * key file live sessions use, per `~/.omp/agent/secret-placeholder.key` in
10
- * docs/secrets.md.
3
+ * Per-install secret key for the placeholder digest. Persisted under XDG state
4
+ * and never sent to a provider, so model-visible placeholders cannot be reversed
5
+ * by dictionary-hashing candidate secrets. Stable across sessions so persisted
6
+ * transcripts deobfuscate consistently. Defaults to `getSecretPlaceholderKeyPath()`
7
+ * — `$XDG_STATE_HOME/omp/secret-placeholder.key` (or `~/.omp/agent/secret-placeholder.key`
8
+ * without XDG), per docs/secrets.md.
11
9
  */
12
10
  export declare function getSecretPlaceholderKey(keyDir?: string): Promise<string>;
13
11
  /** Return an existing placeholder key for redaction without creating a new key file. */
@@ -610,7 +610,6 @@ export declare class AgentSession {
610
610
  selector?: string;
611
611
  thinkingLevel?: ThinkingLevel;
612
612
  persist?: boolean;
613
- currentContextTokens?: number;
614
613
  }): Promise<{
615
614
  switched: boolean;
616
615
  }>;
@@ -1009,6 +1008,12 @@ export declare class AgentSession {
1009
1008
  * @returns the number of advisors active after the rebuild.
1010
1009
  */
1011
1010
  applyAdvisorConfigs(advisors: AdvisorConfig[], sharedInstructions: string | undefined): number;
1011
+ /**
1012
+ * Refresh the project context prompt advisor sessions run against after
1013
+ * context files change on `/reload-plugins`. Rebuilds live advisor runtimes so
1014
+ * they stop evaluating turns against stale `AGENTS.md` instructions.
1015
+ */
1016
+ setAdvisorContextPrompt(contextPrompt: string | undefined): void;
1012
1017
  /**
1013
1018
  * Whether the advisor setting is enabled for this session.
1014
1019
  */
@@ -71,7 +71,6 @@ export declare class ModelControls {
71
71
  selector?: string;
72
72
  thinkingLevel?: ThinkingLevel;
73
73
  persist?: boolean;
74
- currentContextTokens?: number;
75
74
  }): Promise<{
76
75
  switched: boolean;
77
76
  }>;
@@ -227,6 +227,13 @@ export declare class SessionAdvisors {
227
227
  * @returns the number of advisors active after the rebuild.
228
228
  */
229
229
  applyAdvisorConfigs(advisors: AdvisorConfig[], sharedInstructions: string | undefined): number;
230
+ /**
231
+ * Swap the project context prompt handed to advisor sessions after context
232
+ * files change (`/reload-plugins` edit/disable). Rebuilds live runtimes in
233
+ * place so the next advisor turn evaluates against the current instructions;
234
+ * a no-op when the rendered prompt is unchanged.
235
+ */
236
+ setContextPrompt(contextPrompt: string | undefined): void;
230
237
  /**
231
238
  * Whether the advisor setting is enabled for this session.
232
239
  */
@@ -38,6 +38,7 @@ export interface SessionMaintenanceHost {
38
38
  extensionRunner: ExtensionRunner | undefined;
39
39
  sideStreamFn: StreamFn;
40
40
  providerSessionState: Map<string, ProviderSessionState>;
41
+ preferWebsockets: boolean | undefined;
41
42
  model(): Model | undefined;
42
43
  thinkingLevel(): ThinkingLevel | undefined;
43
44
  isDisposed(): boolean;
@@ -39,11 +39,17 @@ export declare class SessionPersistenceIndeterminateError extends AggregateError
39
39
  * tree by `(id, parentId)`, and the mutable leaf pointer selects which path is
40
40
  * active for future appends and for LLM context construction.
41
41
  *
42
- * Durability is software-crash safe but not power-loss safe: appends are handed
43
- * to the OS synchronously in-body (so an entry survives an OOM/SIGKILL the
44
- * instant `appendMessage` returns) but never `fsync`'d. Full-file rewrites go
45
- * through the storage layer's atomic temp-write+rename so a crash mid-rewrite
46
- * cannot truncate the prior good file.
42
+ * Durability is software-crash safe but not power-loss safe: appends are normally
43
+ * handed to the OS synchronously in-body but never `fsync`'d. While an in-place
44
+ * atomic rewrite publishes, a synchronous `flushSync` still persists fenced
45
+ * appends by superseding the pending publish against the same (stable) path.
46
+ * A {@link moveTo} relocation is the one exception: the session file is being
47
+ * renamed to a new path, so appends landing in the rename window are held in
48
+ * memory and persisted only by the move's trailing rewrite. A `flushSync` in
49
+ * that narrow window cannot durably place them — writing the vacated source
50
+ * would recreate the orphan the move eliminates, and a queued backend publish
51
+ * (fire-and-forget on `IndexedSessionStorage`) could land after the rename all
52
+ * the same. This gap is inherent to closing the writer for a Windows-safe rename.
47
53
  */
48
54
  export declare class SessionManager {
49
55
  #private;
@@ -19,6 +19,8 @@ export declare function resolvePromptInput(input: string | undefined, descriptio
19
19
  export interface LoadContextFilesOptions {
20
20
  /** Working directory to start walking up from. Default: getProjectDir() */
21
21
  cwd?: string;
22
+ /** Disabled extension IDs to honor instead of the process-global settings. */
23
+ disabledExtensions?: string[];
22
24
  }
23
25
  /**
24
26
  * Load all project context files using the capability API.
@@ -11,6 +11,9 @@ export interface DefaultToolRenderInput {
11
11
  result?: {
12
12
  output: string;
13
13
  isError?: boolean;
14
+ /** Synthetic placeholder for a call skipped mid-batch to service steering/peer
15
+ * input — the tool never ran, so it renders neutral (info) rather than as an error. */
16
+ skipped?: boolean;
14
17
  };
15
18
  /** Current expansion and lifecycle state. */
16
19
  options: RenderResultOptions;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "17.2.3",
4
+ "version": "17.2.4",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -52,17 +52,17 @@
52
52
  "@agentclientprotocol/sdk": "1.2.1",
53
53
  "@babel/parser": "^7.29.7",
54
54
  "@mozilla/readability": "^0.6.0",
55
- "@oh-my-pi/hashline": "17.2.3",
56
- "@oh-my-pi/omp-stats": "17.2.3",
57
- "@oh-my-pi/pi-agent-core": "17.2.3",
58
- "@oh-my-pi/pi-ai": "17.2.3",
59
- "@oh-my-pi/pi-catalog": "17.2.3",
60
- "@oh-my-pi/pi-mnemopi": "17.2.3",
61
- "@oh-my-pi/pi-natives": "17.2.3",
62
- "@oh-my-pi/pi-tui": "17.2.3",
63
- "@oh-my-pi/pi-utils": "17.2.3",
64
- "@oh-my-pi/pi-wire": "17.2.3",
65
- "@oh-my-pi/snapcompact": "17.2.3",
55
+ "@oh-my-pi/hashline": "17.2.4",
56
+ "@oh-my-pi/omp-stats": "17.2.4",
57
+ "@oh-my-pi/pi-agent-core": "17.2.4",
58
+ "@oh-my-pi/pi-ai": "17.2.4",
59
+ "@oh-my-pi/pi-catalog": "17.2.4",
60
+ "@oh-my-pi/pi-mnemopi": "17.2.4",
61
+ "@oh-my-pi/pi-natives": "17.2.4",
62
+ "@oh-my-pi/pi-tui": "17.2.4",
63
+ "@oh-my-pi/pi-utils": "17.2.4",
64
+ "@oh-my-pi/pi-wire": "17.2.4",
65
+ "@oh-my-pi/snapcompact": "17.2.4",
66
66
  "@opentelemetry/api": "^1.9.1",
67
67
  "@opentelemetry/api-logs": "^0.220.0",
68
68
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -142,7 +142,13 @@ export async function collectBundledPiEntries(): Promise<BundledPiEntry[]> {
142
142
 
143
143
  const sourceDir = path.join(packageRoot, pattern.sourcePrefix);
144
144
  try {
145
- const glob = new Bun.Glob(`*${pattern.sourceSuffix}`);
145
+ // Recursive on purpose: Node matches `*` in an `exports` pattern across
146
+ // `/`, so `./slash-commands/*` genuinely serves
147
+ // `slash-commands/helpers/active-oauth-account`. Enumerating only the
148
+ // top level left every nested key out of the compiled registry, where
149
+ // it fell through to `Bun.resolveSync` and died under bunfs — so such
150
+ // an import worked from source and failed inside a binary.
151
+ const glob = new Bun.Glob(`**/*${pattern.sourceSuffix}`);
146
152
  const matches: string[] = [];
147
153
  for await (const match of glob.scan({ cwd: sourceDir, onlyFiles: true })) {
148
154
  matches.push(match);
@@ -151,7 +157,11 @@ export async function collectBundledPiEntries(): Promise<BundledPiEntry[]> {
151
157
  for (const match of matches) {
152
158
  if (!match.endsWith(pattern.sourceSuffix)) continue;
153
159
  const basename = match.slice(0, match.length - pattern.sourceSuffix.length);
154
- if (!isSafeWildcardBasename(basename) || basename.includes("/")) continue;
160
+ const segments = basename.split("/");
161
+ // Every directory on the way has to be importable too: a private or
162
+ // hidden folder is no more exported than a private file.
163
+ if (segments.some(segment => segment.startsWith(".") || segment.startsWith("_"))) continue;
164
+ if (!isSafeWildcardBasename(segments.at(-1) ?? "")) continue;
155
165
  const subpath = `${pattern.exportPrefix}${basename}${pattern.exportSuffix}`;
156
166
  const key = `${manifest.name}/${subpath}`;
157
167
  addEntry(key, bindingForSubpath(pkg.identifier, subpath), key);