@oh-my-pi/pi-coding-agent 15.9.1 → 15.9.3

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 (57) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/dist/types/cli/dry-balance-cli.d.ts +104 -0
  3. package/dist/types/commands/dry-balance.d.ts +31 -0
  4. package/dist/types/config/model-registry.d.ts +2 -0
  5. package/dist/types/config/models-config-schema.d.ts +3 -0
  6. package/dist/types/config/settings.d.ts +11 -0
  7. package/dist/types/discovery/helpers.d.ts +1 -0
  8. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
  9. package/dist/types/hindsight/bank.d.ts +17 -9
  10. package/dist/types/hindsight/mental-models.d.ts +1 -1
  11. package/dist/types/hindsight/state.d.ts +9 -3
  12. package/dist/types/mcp/manager.d.ts +1 -1
  13. package/dist/types/modes/components/transcript-container.d.ts +3 -2
  14. package/dist/types/session/agent-session.d.ts +9 -0
  15. package/dist/types/session/auth-storage.d.ts +2 -2
  16. package/dist/types/task/types.d.ts +2 -0
  17. package/dist/types/tools/index.d.ts +16 -0
  18. package/dist/types/tools/path-utils.d.ts +11 -0
  19. package/package.json +9 -9
  20. package/src/cli/dry-balance-cli.ts +823 -0
  21. package/src/cli-commands.ts +1 -0
  22. package/src/commands/dry-balance.ts +43 -0
  23. package/src/config/model-registry.ts +6 -0
  24. package/src/config/models-config-schema.ts +2 -0
  25. package/src/config/settings.ts +38 -0
  26. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
  27. package/src/discovery/github.ts +37 -1
  28. package/src/discovery/helpers.ts +3 -1
  29. package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
  30. package/src/hindsight/backend.ts +184 -35
  31. package/src/hindsight/bank.ts +32 -22
  32. package/src/hindsight/mental-models.ts +1 -1
  33. package/src/hindsight/state.ts +21 -7
  34. package/src/internal-urls/docs-index.generated.ts +4 -4
  35. package/src/internal-urls/omp-protocol.ts +8 -2
  36. package/src/mcp/manager.ts +40 -21
  37. package/src/modes/components/transcript-container.ts +14 -3
  38. package/src/modes/components/tree-selector.ts +29 -2
  39. package/src/modes/controllers/input-controller.ts +8 -2
  40. package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
  41. package/src/prompts/agents/explore.md +1 -0
  42. package/src/prompts/agents/librarian.md +1 -0
  43. package/src/prompts/dry-balance-bench.md +8 -0
  44. package/src/sdk.ts +82 -9
  45. package/src/session/agent-session.ts +66 -7
  46. package/src/session/auth-storage.ts +4 -0
  47. package/src/task/executor.ts +6 -2
  48. package/src/task/index.ts +8 -7
  49. package/src/task/types.ts +2 -0
  50. package/src/tools/bash.ts +3 -4
  51. package/src/tools/index.ts +16 -0
  52. package/src/tools/job.ts +3 -3
  53. package/src/tools/memory-reflect.ts +2 -2
  54. package/src/tools/path-utils.ts +21 -0
  55. package/src/tools/search.ts +18 -1
  56. package/src/utils/file-mentions.ts +7 -107
  57. package/src/utils/title-generator.ts +58 -37
package/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.9.3] - 2026-06-05
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `@`-mention auto-read injecting an unrelated, same-named file when a mention did not point at a real path — e.g. an npm scope like `@scope/`, a partial path, or a bare token. `generateFileMentionMessages` resolution previously fell back to prefix and repo-wide fuzzy matching (globbing the whole project on every such mention) and auto-read the single "best" guess. Resolution is now exact-only: a mention is auto-read only when it resolves to an existing file or directory; otherwise it is left as prose. The TUI `@`-selector already inserts the real, complete path before send, so post-send guessing was both unnecessary and the source of the wrong-file reads. Directories still resolve and are listed. Removes the per-mention `**/*` project scan.
10
+
11
+ ## [15.9.2] - 2026-06-05
12
+
13
+ ### Added
14
+
15
+ - Added an encrypted local auth-broker snapshot cache for `discoverAuthStorage`, with `OMP_AUTH_BROKER_SNAPSHOT_TTL_MS` and `OMP_AUTH_BROKER_SNAPSHOT_CACHE`, so fresh cached broker credentials can boot without a blocking `/v1/snapshot` fetch and survive broker-down startup windows.
16
+ - Added `dry-balance` CLI command to perform a dry-run OAuth account balancing check across configurable random session IDs, with sample and concurrency options, JSON output, and success/failure summary reporting
17
+ - Added `--json` output mode and machine-readable result format to `omp dry-balance` for automated use
18
+ - Added `omitMaxOutputTokens` to `models.yml` model definitions and `modelOverrides`, so users can opt a model out of the on-the-wire `max_output_tokens` / `max_tokens` cap while keeping the catalog `maxTokens` for local budgeting. Intended for Ollama-style proxies whose upstream output limit OMP cannot discover. ([#1881](https://github.com/can1357/oh-my-pi/issues/1881))
19
+
20
+ ### Fixed
21
+
22
+ - Fixed TTSR rule-violation injections leaking the absolute home directory to the model: the `ttsr-interrupt` / `ttsr-tool-reminder` blocks rendered the matched rule's `path` as its absolute on-disk path (e.g. `/Users/me/Projects/app/.omp/rules/no-any.md`). The path is now relativized to the session cwd when the rule lives in the project (`.omp/rules/no-any.md`), or `~`-relative when it lives under home, so no absolute path is fed into the agent's context outside the system prompt.
23
+ - Fixed `AsyncJobManager.instance()` being cleared while the owning top-level session was still live, which broke the `task` async path with "Async execution is enabled but no async job manager is available" until process restart. Any in-process secondary top-level `createAgentSession()` call (e.g. the Agent Control Center's create flow in `agent-dashboard.ts`) constructed a fresh `AsyncJobManager`, overwrote the singleton, and then cleared it on its own dispose. Secondary sessions now leave the live singleton untouched, and their dispose-time cleanup is scoped so it can no longer cancel the primary session's running bash/task jobs. `bash` / `task` / `job` tools and session job snapshots now resolve the manager through session-scoped async manager wiring rather than `AsyncJobManager.instance()`, so a secondary in-process top-level session cannot accidentally register background work on the owning session's manager or report the owning session's jobs; subagents still inherit the parent's manager via their scoped async manager. Startup failures after a top-level session installs its manager now clear and dispose that manager before the next session decides whether it can create its own ([#1923](https://github.com/can1357/oh-my-pi/issues/1923)).
24
+ - Fixed the `task` tool returning a hard `Async execution is enabled but no async job manager is available.` error when `async.enabled` was true but `AsyncJobManager.instance()` returned `undefined`, leaving `task` non-functional for the rest of the session. The tool now falls back to the existing synchronous execution path (which still runs subagents concurrently via `mapWithConcurrencyLimit`), and logs a warning so the missing-manager state stays diagnosable ([#1922](https://github.com/can1357/oh-my-pi/issues/1922)).
25
+ - Fixed Hindsight retain/recall/reflect calls staying pinned to the bank that was selected when the session started after the operator edited `hindsight.bankId`, `hindsight.bankIdPrefix`, or `hindsight.scoping` mid-session. The backend now subscribes to those settings via `onHindsightScopeChanged` and rebuilds the active `HindsightSessionState` against the recomputed scope, disposing the old state after flushing its queue so in-flight tool-initiated retains still land in the bank they were enqueued for. Also renamed `ensureBankMission` to `ensureBankExists` so a blank `bankMission` no longer skips bank creation entirely, and called it before mental-model bootstrap so `createMentalModel` is never the first POST against a missing bank. `AgentSession.dispose` now flushes the retain queue before clearing `#hindsightSessionState`, since the queue's identity guard would otherwise drop the spliced batch ([#1902](https://github.com/can1357/oh-my-pi/issues/1902)).
26
+ - Fixed `/tree` rendering a bare "No entries found" line on a fresh session where the only persisted entries are the `model_change` + `thinking_level_change` written by `sdk.ts` at startup — both are hidden by the tree-selector's default filter, so `#filteredNodes.length === 0` while `tree.length === 2` and the controller's `tree.length === 0` short-circuit never fired. The selector now splits the empty-state into three distinct shapes — truly empty tree, search query with no matches, and filter mode rejecting every entry — surfacing the cause and the recovery key (`Alt+A` to show all, `Backspace` to clear a stale search) so users on a fresh session can see immediately that the panel isn't broken ([#1909](https://github.com/can1357/oh-my-pi/issues/1909)).
27
+ - Fixed remote MCP OAuth refresh failures leaving stale credentials in `agent.db`: when the token endpoint returns a definitive failure (`invalid_grant`, `invalid_token`, `revoked`, plain 401/403 not classified as transient), `MCPManager#resolveAuthConfig` now drops the credential via `AuthStorage.remove(credentialId)` and skips re-attaching the dead `Authorization: Bearer …` header. Previously a revoked refresh token kept producing `401 invalid_token` on every MCP request and survived restarts, so users had to hand-clear the credential row to recover; the next connect now surfaces a clean auth error and `/mcp reauth <server>` (or `/mcp unauth`) recovers without restarting. Transient refresh failures (network/`fetch failed`/`ECONNREFUSED`) still fall back to the existing access token ([#1908](https://github.com/can1357/oh-my-pi/issues/1908)).
28
+ - Fixed `omp://docs` and `omp://docs/...` internal documentation URLs in the distributed package to resolve through the embedded documentation index instead of failing with `Documentation file not found` ([#1898](https://github.com/can1357/oh-my-pi/issues/1898)).
29
+ - Fixed the `github` discovery provider silently ignoring `.github/skills/<name>/SKILL.md`, GitHub's documented Agent Skills layout. The provider now registers a `skills` capability (priority 30, project-only) that scans `.github/skills/` non-recursively via `scanSkillsFromDir` with `requireDescription: true`, matching the Agent Skills spec and the sibling `native`/`omp-plugins` providers ([#1906](https://github.com/can1357/oh-my-pi/issues/1906)).
30
+ - Fixed inline images rendering as a wall of empty PUA box glyphs with laggy scrolling on Kitty-protocol terminals that do not honor Unicode placeholders (most notably WezTerm and tmux/screen passthrough to a non-Kitty outer terminal). The 15.9 placeholder rollout enabled the `U=1`/U+10EEEE grid for every Kitty-protocol path; it now defaults on only for `kitty` and `ghostty`, with `PI_NO_KITTY_PLACEHOLDERS=1` as a hard opt-out and `PI_KITTY_PLACEHOLDERS=1` as opt-in for terminals (e.g. wezterm nightlies) that have since added support ([#1877](https://github.com/can1357/oh-my-pi/issues/1877)).
31
+ - Fixed auto session-title generation failures being swallowed without an actionable diagnostic. Title generation now logs structured start, missing-model/API-key, provider-error, empty-result, and exception outcomes with the session id and resolved title model; the interactive auto-title caller also logs uncaught persistence/generation errors instead of dropping them. ([#1892](https://github.com/can1357/oh-my-pi/issues/1892))
32
+ - Fixed `TranscriptContainer` reporting the live block boundary to the TUI again, so ED3-risk foreground streaming can append newly sealed transcript blocks to native scrollback once while deferring only the active live block.
33
+
5
34
  ## [15.9.1] - 2026-06-04
6
35
 
7
36
  ### Added
@@ -20,7 +49,6 @@
20
49
  ### Fixed
21
50
 
22
51
  - Fixed a streamed assistant message freezing at a partial prefix (e.g. only "Nat" of "Natives built, now…") on ED3-risk terminals (Ghostty/kitty/iTerm2/Alacritty), with the final text appearing only after a resize. `TranscriptContainer` freezes each non-live block by replaying its last live render, but render coalescing can finalize a block's content and append the next block within the same throttled frame — so the block was sealed at its stale mid-stream snapshot and never repainted until the next `thaw`. The block that was live on the previous render is now recomputed once on the live→frozen transition, sealing it at its final content.
23
-
24
52
  - Fixed ACP/RPC stdio startup so protocol frames are no longer consumed as one-shot piped prompt input before the JSON-RPC transport starts.
25
53
  - Fixed `omp completions` to await the completion script write before exiting.
26
54
  - Fixed `AssistantMessageComponent` exposing its stable-prefix completion API again so streamed assistant messages remain unstable until explicitly completed.
@@ -0,0 +1,104 @@
1
+ import type { Api, AssistantMessageEventStream, Context, Model, OAuthAccess, OAuthAccessResolution, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
2
+ import type { CanonicalModelVariant } from "../config/model-equivalence";
3
+ import { type CanonicalModelQueryOptions } from "../config/model-registry";
4
+ import { Settings } from "../config/settings";
5
+ export interface DryBalanceCommandArgs {
6
+ model?: string;
7
+ flags: {
8
+ model?: string;
9
+ count?: number;
10
+ concurrency?: number;
11
+ json?: boolean;
12
+ bench?: boolean;
13
+ };
14
+ }
15
+ export interface DryBalanceAuthOptions {
16
+ baseUrl?: string;
17
+ modelId?: string;
18
+ signal?: AbortSignal;
19
+ }
20
+ export interface DryBalanceAuthStorage {
21
+ getOAuthAccess(provider: string, sessionId?: string, options?: DryBalanceAuthOptions): Promise<OAuthAccess | undefined>;
22
+ getOAuthAccesses?(provider: string, options?: DryBalanceAuthOptions): Promise<OAuthAccessResolution[]>;
23
+ }
24
+ export interface DryBalanceModelRegistry {
25
+ authStorage: DryBalanceAuthStorage;
26
+ getAll(): Model<Api>[];
27
+ getAvailable(): Model<Api>[];
28
+ getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
29
+ getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
30
+ resolveCanonicalModel?(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
31
+ getCanonicalId?(model: Model<Api>): string | undefined;
32
+ }
33
+ export interface DryBalanceRuntime {
34
+ modelRegistry: DryBalanceModelRegistry;
35
+ settings?: Settings;
36
+ close?: () => void;
37
+ }
38
+ export interface DryBalanceAccountStat {
39
+ account: string;
40
+ count: number;
41
+ percent: number;
42
+ }
43
+ export interface DryBalanceFailureStat {
44
+ reason: string;
45
+ count: number;
46
+ percent: number;
47
+ }
48
+ export interface DryBalanceBenchSuccessResult {
49
+ ok: true;
50
+ account: string;
51
+ ttftMs: number;
52
+ durationMs: number;
53
+ outputTokens: number;
54
+ tokensPerSecond: number;
55
+ }
56
+ export interface DryBalanceBenchFailureResult {
57
+ ok: false;
58
+ account?: string;
59
+ error: string;
60
+ }
61
+ export type DryBalanceBenchResult = DryBalanceBenchSuccessResult | DryBalanceBenchFailureResult;
62
+ export interface DryBalanceBenchSummary {
63
+ total: number;
64
+ success: {
65
+ total: number;
66
+ averageTtftMs: number | null;
67
+ averageTokensPerSecond: number | null;
68
+ };
69
+ failure: {
70
+ total: number;
71
+ reasons: DryBalanceFailureStat[];
72
+ };
73
+ results: DryBalanceBenchResult[];
74
+ }
75
+ export interface DryBalanceSummary {
76
+ model: string;
77
+ provider: string;
78
+ samples: number;
79
+ concurrency: number;
80
+ success: {
81
+ total: number;
82
+ accounts: DryBalanceAccountStat[];
83
+ };
84
+ failure: {
85
+ total: number;
86
+ reasons: DryBalanceFailureStat[];
87
+ };
88
+ bench?: DryBalanceBenchSummary;
89
+ }
90
+ type DryBalanceStreamSimple = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
91
+ export interface DryBalanceDependencies {
92
+ createRuntime?: () => Promise<DryBalanceRuntime>;
93
+ randomSessionId?: () => string;
94
+ writeStdout?: (text: string) => void;
95
+ writeStderr?: (text: string) => void;
96
+ setExitCode?: (code: number) => void;
97
+ streamSimple?: DryBalanceStreamSimple;
98
+ now?: () => number;
99
+ stdoutIsTTY?: boolean;
100
+ stderrIsTTY?: boolean;
101
+ }
102
+ export declare function formatDryBalanceText(summary: DryBalanceSummary): string;
103
+ export declare function runDryBalanceCommand(command: DryBalanceCommandArgs, deps?: DryBalanceDependencies): Promise<DryBalanceSummary>;
104
+ export {};
@@ -0,0 +1,31 @@
1
+ import { Command } from "@oh-my-pi/pi-utils/cli";
2
+ export default class DryBalance extends Command {
3
+ static description: string;
4
+ static args: {
5
+ model: import("@oh-my-pi/pi-utils/cli").ArgDescriptor & {
6
+ description: string;
7
+ required: false;
8
+ };
9
+ };
10
+ static flags: {
11
+ model: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
12
+ description: string;
13
+ };
14
+ count: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
15
+ description: string;
16
+ default: number;
17
+ };
18
+ concurrency: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
19
+ description: string;
20
+ default: number;
21
+ };
22
+ json: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
23
+ description: string;
24
+ };
25
+ bench: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
26
+ description: string;
27
+ };
28
+ };
29
+ static examples: string[];
30
+ run(): Promise<void>;
31
+ }
@@ -104,6 +104,7 @@ export declare const ModelsConfigFile: ConfigFile<{
104
104
  premiumMultiplier?: number | undefined;
105
105
  contextWindow?: number | undefined;
106
106
  maxTokens?: number | undefined;
107
+ omitMaxOutputTokens?: boolean | undefined;
107
108
  headers?: Record<string, string> | undefined;
108
109
  compat?: {
109
110
  supportsStore?: boolean | undefined;
@@ -166,6 +167,7 @@ export declare const ModelsConfigFile: ConfigFile<{
166
167
  premiumMultiplier?: number | undefined;
167
168
  contextWindow?: number | undefined;
168
169
  maxTokens?: number | undefined;
170
+ omitMaxOutputTokens?: boolean | undefined;
169
171
  headers?: Record<string, string> | undefined;
170
172
  compat?: {
171
173
  supportsStore?: boolean | undefined;
@@ -109,6 +109,7 @@ export declare const ModelOverrideSchema: z.ZodObject<{
109
109
  premiumMultiplier: z.ZodOptional<z.ZodNumber>;
110
110
  contextWindow: z.ZodOptional<z.ZodNumber>;
111
111
  maxTokens: z.ZodOptional<z.ZodNumber>;
112
+ omitMaxOutputTokens: z.ZodOptional<z.ZodBoolean>;
112
113
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
113
114
  compat: z.ZodOptional<z.ZodObject<{
114
115
  supportsStore: z.ZodOptional<z.ZodBoolean>;
@@ -336,6 +337,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
336
337
  premiumMultiplier: z.ZodOptional<z.ZodNumber>;
337
338
  contextWindow: z.ZodOptional<z.ZodNumber>;
338
339
  maxTokens: z.ZodOptional<z.ZodNumber>;
340
+ omitMaxOutputTokens: z.ZodOptional<z.ZodBoolean>;
339
341
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
340
342
  compat: z.ZodOptional<z.ZodObject<{
341
343
  supportsStore: z.ZodOptional<z.ZodBoolean>;
@@ -449,6 +451,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
449
451
  premiumMultiplier: z.ZodOptional<z.ZodNumber>;
450
452
  contextWindow: z.ZodOptional<z.ZodNumber>;
451
453
  maxTokens: z.ZodOptional<z.ZodNumber>;
454
+ omitMaxOutputTokens: z.ZodOptional<z.ZodBoolean>;
452
455
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
453
456
  compat: z.ZodOptional<z.ZodObject<{
454
457
  supportsStore: z.ZodOptional<z.ZodBoolean>;
@@ -132,6 +132,17 @@ export declare class Settings {
132
132
  * can register independently without overwriting each other.
133
133
  */
134
134
  export declare function onAppendOnlyModeChanged(cb: (value: string) => void): () => void;
135
+ /**
136
+ * Subscribe to changes in the Hindsight bank-scoping settings. Lets the
137
+ * Hindsight backend rebuild the active `HindsightSessionState` when the
138
+ * operator switches `hindsight.bankId`, `hindsight.bankIdPrefix`, or
139
+ * `hindsight.scoping` mid-session so subsequent retain/recall calls land in
140
+ * the new bank instead of the one selected at session start.
141
+ *
142
+ * Returns an unsubscribe function. The callback receives no arguments — the
143
+ * caller is expected to re-read the relevant settings via `Settings.get`.
144
+ */
145
+ export declare function onHindsightScopeChanged(cb: () => void): () => void;
135
146
  export declare function isSettingsInitialized(): boolean;
136
147
  /**
137
148
  * Reset the global singleton for testing.
@@ -102,6 +102,7 @@ export interface ParsedAgentFields {
102
102
  output?: unknown;
103
103
  thinkingLevel?: ThinkingLevel;
104
104
  autoloadSkills?: string[];
105
+ readSummarize?: boolean;
105
106
  blocking?: boolean;
106
107
  }
107
108
  /**
@@ -20,9 +20,8 @@ export declare function __computeBunfsPackageRoot(metaDir: string, pathImpl?: ty
20
20
  * and `__dirname`-relative `readFileSync` asset loads (HTML/CSS bundled next to
21
21
  * the entry) resolve exactly as they do under the original Pi runtime — no
22
22
  * temp-directory mirroring and no asset copying. An `onLoad` hook scoped to the
23
- * entry's relative-import graph rewrites only the legacy `@(scope)/pi-*` and
24
- * `@sinclair/typebox` imports in the extension's own source; everything else
25
- * resolves natively.
23
+ * entry's source graph rewrites only host-resolved compatibility imports in the
24
+ * extension's own source; everything else resolves natively.
26
25
  */
27
26
  export declare function loadLegacyPiModule(resolvedPath: string): Promise<unknown>;
28
27
  export declare function installLegacyPiSpecifierShim(): void;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bank ID derivation, project-tag scoping, and first-use mission setup.
2
+ * Bank ID derivation, project-tag scoping, and first-use bank setup.
3
3
  *
4
4
  * Three scoping modes (`HindsightConfig.scoping`):
5
5
  * - `global` — single shared bank, no per-project filter.
@@ -11,10 +11,13 @@
11
11
  * The base bank id is `bankIdPrefix-bankId` (default `omp`). Per-project mode
12
12
  * appends `-<project>`; tagged mode leaves the bank untouched and uses tags.
13
13
  *
14
- * Mission setup is idempotent at module level — a missionsSet keeps track of
15
- * banks we've already POSTed to so each session boundary doesn't fire a fresh
16
- * `createBank` call. Failures are swallowed: missions are an optimisation, not
17
- * a precondition for retain/recall.
14
+ * Bank existence is idempotent at module level — a banksSet keeps track of
15
+ * banks we've already PUT so each session boundary doesn't fire a fresh
16
+ * `createBank` call. The PUT is idempotent server-side, so re-firing on a hot
17
+ * path would only burn round-trips. Failures are swallowed: missing the
18
+ * mission patch is an optimisation, but the bank ITSELF must exist before
19
+ * mental-model bootstrap or the first retain, otherwise the very first POST
20
+ * lands against a missing bank.
18
21
  */
19
22
  import type { HindsightApi } from "./client";
20
23
  import type { HindsightConfig } from "./config";
@@ -46,9 +49,14 @@ export declare function computeBankScope(config: HindsightConfig, directory: str
46
49
  */
47
50
  export declare function deriveBankId(config: HindsightConfig, directory: string): string;
48
51
  /**
49
- * Ensure a bank's reflect/retain mission is set, exactly once per process.
52
+ * Ensure a bank exists, and patch its reflect/retain mission on first use.
50
53
  *
51
- * Tracked via the supplied set; on overflow we drop the oldest half so the set
52
- * cannot grow unboundedly across long-lived processes.
54
+ * Idempotent: skips the PUT when the bank id is already in the supplied set.
55
+ * The mission body is optional — when `bankMission` is blank we still PUT to
56
+ * make sure the bank itself is created, so mental-model bootstrap and the
57
+ * first retain don't land against a non-existent bank.
58
+ *
59
+ * The set is capped; on overflow we drop the oldest half so it cannot grow
60
+ * unboundedly across long-lived processes.
53
61
  */
54
- export declare function ensureBankMission(client: HindsightApi, bankId: string, config: HindsightConfig, missionsSet: Set<string>): Promise<void>;
62
+ export declare function ensureBankExists(client: HindsightApi, bankId: string, config: HindsightConfig, banksSet: Set<string>): Promise<void>;
@@ -70,7 +70,7 @@ export declare function resolveSeedsForScope(scope: BankScope, scoping: Hindsigh
70
70
  * Idempotently create any seed mental models that don't already exist on the
71
71
  * bank. Best-effort: a list/create failure does not throw — mental models are
72
72
  * an optimization, not a precondition for retain/recall, and we mirror the
73
- * swallow-on-failure pattern used by `ensureBankMission`.
73
+ * swallow-on-failure pattern used by `ensureBankExists`.
74
74
  *
75
75
  * Existing models are NEVER modified. See module docstring.
76
76
  */
@@ -19,12 +19,12 @@ export interface HindsightSessionStateOptions {
19
19
  recallTagsMatch?: "any" | "all" | "any_strict" | "all_strict";
20
20
  config: HindsightConfig;
21
21
  session: AgentSession;
22
- missionsSet: Set<string>;
22
+ banksSet: Set<string>;
23
23
  lastRetainedTurn?: number;
24
24
  hasRecalledForFirstTurn?: boolean;
25
25
  /**
26
26
  * When set, this entry is a subagent alias that reuses the parent's bank,
27
- * scope, config, client, and missionsSet. Aliases skip auto-recall and
27
+ * scope, config, client, and banksSet. Aliases skip auto-recall and
28
28
  * auto-retain — those run on the parent only — but the recall/retain/reflect
29
29
  * tools resolve via the alias so they persist to the same bank as the parent.
30
30
  */
@@ -60,7 +60,7 @@ export declare class HindsightSessionState {
60
60
  recallTagsMatch?: "any" | "all" | "any_strict" | "all_strict";
61
61
  config: HindsightConfig;
62
62
  session: AgentSession;
63
- missionsSet: Set<string>;
63
+ banksSet: Set<string>;
64
64
  lastRetainedTurn: number;
65
65
  hasRecalledForFirstTurn: boolean;
66
66
  lastRecallSnippet?: string;
@@ -75,6 +75,12 @@ export declare class HindsightSessionState {
75
75
  */
76
76
  mentalModelsLoadPromise?: Promise<void>;
77
77
  unsubscribe?: () => void;
78
+ /**
79
+ * Releases the `onHindsightScopeChanged` subscription that drives live
80
+ * rebuilds when `hindsight.bankId` / `bankIdPrefix` / `scoping` change.
81
+ * Only set on primary states; aliases inherit the parent's subscription.
82
+ */
83
+ unsubscribeScope?: () => void;
78
84
  /** Alias states delegate persistence config to a primary parent state. */
79
85
  aliasOf?: HindsightSessionState;
80
86
  readonly retainQueue: HindsightRetainQueue;
@@ -1,4 +1,4 @@
1
- import type { TSchema } from "@oh-my-pi/pi-ai";
1
+ import { type TSchema } from "@oh-my-pi/pi-ai";
2
2
  import type { SourceMeta } from "../capability/types";
3
3
  import type { CustomTool } from "../extensibility/custom-tools/types";
4
4
  import type { AuthStorage } from "../session/auth-storage";
@@ -1,4 +1,4 @@
1
- import { Container } from "@oh-my-pi/pi-tui";
1
+ import { Container, type NativeScrollbackLiveRegion } from "@oh-my-pi/pi-tui";
2
2
  /**
3
3
  * Transcript container that freezes the rendered output of every block except
4
4
  * the bottom-most (live) one on terminals where committed native scrollback is
@@ -21,10 +21,11 @@ import { Container } from "@oh-my-pi/pi-tui";
21
21
  * and any drift reconciles safely. On terminals that can rebuild history this
22
22
  * freezing is unnecessary, so it renders every block live for full fidelity.
23
23
  */
24
- export declare class TranscriptContainer extends Container {
24
+ export declare class TranscriptContainer extends Container implements NativeScrollbackLiveRegion {
25
25
  #private;
26
26
  invalidate(): void;
27
27
  clear(): void;
28
+ getNativeScrollbackLiveRegionStart(): number | undefined;
28
29
  /**
29
30
  * Retire all frozen snapshots so the next render reflects each block's current
30
31
  * state. Call at reconciliation checkpoints (prompt submit) where the whole
@@ -201,6 +201,15 @@ export interface AgentSessionConfig {
201
201
  * **MUST NOT** dispose it on their own teardown.
202
202
  */
203
203
  ownedAsyncJobManager?: AsyncJobManager;
204
+ /**
205
+ * AsyncJobManager reachable by this session for scoped job actions.
206
+ *
207
+ * Top-level owners receive their own manager, subagents receive the inherited
208
+ * parent manager, and secondary in-process top-level sessions receive
209
+ * `undefined` so job snapshots and ACP drains cannot observe the primary's
210
+ * state.
211
+ */
212
+ asyncJobManager?: AsyncJobManager;
204
213
  /** Agent identity (registry id like "Main" or "Alice") used for IRC routing. */
205
214
  agentId?: string;
206
215
  /** Shared agent registry (for forwarding IRC observations to the main session UI). */
@@ -2,5 +2,5 @@
2
2
  * Re-exports from @oh-my-pi/pi-ai.
3
3
  * All credential storage types and the AuthStorage class now live in the ai package.
4
4
  */
5
- export type { ApiKeyCredential, AuthCredential, AuthCredentialEntry, AuthCredentialStore, AuthStorageData, AuthStorageOptions, OAuthCredential, SerializedAuthStorage, StoredAuthCredential, } from "@oh-my-pi/pi-ai";
6
- export { AuthBrokerClient, AuthStorage, REMOTE_REFRESH_SENTINEL, RemoteAuthCredentialStore, SqliteAuthCredentialStore, } from "@oh-my-pi/pi-ai";
5
+ export type { ApiKeyCredential, AuthCredential, AuthCredentialEntry, AuthCredentialStore, AuthStorageData, AuthStorageOptions, OAuthCredential, SerializedAuthStorage, SnapshotResponse, StoredAuthCredential, } from "@oh-my-pi/pi-ai";
6
+ export { AuthBrokerClient, AuthStorage, DEFAULT_SNAPSHOT_CACHE_TTL_MS, REMOTE_REFRESH_SENTINEL, RemoteAuthCredentialStore, readAuthBrokerSnapshotCache, SqliteAuthCredentialStore, writeAuthBrokerSnapshotCache, } from "@oh-my-pi/pi-ai";
@@ -149,6 +149,8 @@ export interface AgentDefinition {
149
149
  output?: unknown;
150
150
  blocking?: boolean;
151
151
  autoloadSkills?: string[];
152
+ /** When `false`, the agent's `read` tool returns verbatim file content instead of structural summaries. */
153
+ readSummarize?: boolean;
152
154
  source: AgentSource;
153
155
  filePath?: string;
154
156
  }
@@ -1,6 +1,7 @@
1
1
  import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
2
2
  import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
3
3
  import type { ToolChoice } from "@oh-my-pi/pi-ai";
4
+ import type { AsyncJobManager } from "../async/job-manager";
4
5
  import type { PromptTemplate } from "../config/prompt-templates";
5
6
  import type { Settings } from "../config/settings";
6
7
  import type { Skill } from "../extensibility/skills";
@@ -139,6 +140,21 @@ export interface ToolSession {
139
140
  modelRegistry?: import("../config/model-registry").ModelRegistry;
140
141
  /** Agent output manager for unique agent:// IDs across task invocations */
141
142
  agentOutputManager?: AgentOutputManager;
143
+ /**
144
+ * Async job manager scoped to this session.
145
+ *
146
+ * - Top-level session that constructed one: its own manager.
147
+ * - Subagent (`parentTaskPrefix` set): the parent's manager, so background
148
+ * bash/task work and `onJobComplete` deliveries flow into the conversation
149
+ * that spawned it.
150
+ * - Secondary in-process top-level session that found a singleton already
151
+ * installed (issue #1923): `undefined`. Tools refuse async work rather
152
+ * than silently route completions into the owning session's `yieldQueue`.
153
+ *
154
+ * Tools MUST use this instead of `AsyncJobManager.instance()` so a secondary
155
+ * session never borrows the owning session's manager by accident.
156
+ */
157
+ asyncJobManager?: AsyncJobManager;
142
158
  /** MCP manager visible to subagents without relying on the process-global singleton. */
143
159
  mcpManager?: MCPManager;
144
160
  /** Local protocol root to propagate to nested subagents and eval-created agents. */
@@ -17,6 +17,17 @@ export declare function parseLineRangeChunk(sel: string): LineRange | null;
17
17
  * downstream consumers can stream the file in a single forward pass per range.
18
18
  */
19
19
  export declare function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null;
20
+ /**
21
+ * Extract the line-range component from a read-tool selector that may also
22
+ * carry a verbatim/index display mode (`raw`, `conflicts`) — alone or compounded
23
+ * with a range (`raw:50-100`, `50-100:raw`). Returns the parsed ranges when the
24
+ * selector names any, otherwise `undefined` (pure `raw`/`conflicts`/none).
25
+ *
26
+ * Used by content search, which honors line ranges as a match filter but has no
27
+ * use for verbatim/conflict display modes — so those selectors are accepted and
28
+ * treated as an unfiltered, whole-resource search rather than rejected.
29
+ */
30
+ export declare function selectorLineRanges(sel: string | undefined): [LineRange, ...LineRange[]] | undefined;
20
31
  /** Return `true` when `lineNumber` (1-indexed) falls in any of the supplied ranges. */
21
32
  export declare function isLineInRanges(lineNumber: number, ranges: readonly LineRange[]): boolean;
22
33
  export declare function splitPathAndSel(rawPath: string): {
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": "15.9.1",
4
+ "version": "15.9.3",
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",
@@ -47,14 +47,14 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.9.1",
51
- "@oh-my-pi/omp-stats": "15.9.1",
52
- "@oh-my-pi/pi-agent-core": "15.9.1",
53
- "@oh-my-pi/pi-ai": "15.9.1",
54
- "@oh-my-pi/pi-mnemopi": "15.9.1",
55
- "@oh-my-pi/pi-natives": "15.9.1",
56
- "@oh-my-pi/pi-tui": "15.9.1",
57
- "@oh-my-pi/pi-utils": "15.9.1",
50
+ "@oh-my-pi/hashline": "15.9.3",
51
+ "@oh-my-pi/omp-stats": "15.9.3",
52
+ "@oh-my-pi/pi-agent-core": "15.9.3",
53
+ "@oh-my-pi/pi-ai": "15.9.3",
54
+ "@oh-my-pi/pi-mnemopi": "15.9.3",
55
+ "@oh-my-pi/pi-natives": "15.9.3",
56
+ "@oh-my-pi/pi-tui": "15.9.3",
57
+ "@oh-my-pi/pi-utils": "15.9.3",
58
58
  "@opentelemetry/api": "^1.9.1",
59
59
  "@opentelemetry/context-async-hooks": "^2.7.1",
60
60
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",