@oh-my-pi/pi-coding-agent 16.2.3 → 16.2.5

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 (49) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli.js +3449 -3438
  3. package/dist/types/cli/update-cli.d.ts +15 -0
  4. package/dist/types/collab/replication-shrink.d.ts +39 -0
  5. package/dist/types/config/provider-globals.d.ts +7 -0
  6. package/dist/types/memories/index.d.ts +20 -1
  7. package/dist/types/modes/components/status-line/component.d.ts +5 -0
  8. package/dist/types/modes/components/status-line/types.d.ts +10 -0
  9. package/dist/types/modes/controllers/command-controller.d.ts +3 -3
  10. package/dist/types/modes/interactive-mode.d.ts +1 -0
  11. package/dist/types/modes/theme/theme.d.ts +2 -1
  12. package/dist/types/modes/types.d.ts +2 -1
  13. package/dist/types/session/agent-session.d.ts +7 -0
  14. package/dist/types/session/session-manager.d.ts +2 -3
  15. package/dist/types/task/provider-concurrency.d.ts +40 -0
  16. package/dist/types/utils/git.d.ts +11 -0
  17. package/package.json +12 -12
  18. package/src/autolearn/controller.ts +13 -22
  19. package/src/cli/update-cli.ts +254 -0
  20. package/src/cli/worktree-cli.ts +8 -5
  21. package/src/collab/host.ts +13 -8
  22. package/src/collab/replication-shrink.ts +111 -0
  23. package/src/config/model-discovery.ts +33 -13
  24. package/src/config/provider-globals.ts +25 -0
  25. package/src/config/settings-schema.ts +5 -2
  26. package/src/eval/__tests__/julia-prelude.test.ts +2 -2
  27. package/src/memories/index.ts +115 -9
  28. package/src/memory-backend/local-backend.ts +5 -3
  29. package/src/modes/components/status-line/component.ts +35 -2
  30. package/src/modes/components/status-line/segments.ts +22 -8
  31. package/src/modes/components/status-line/types.ts +7 -0
  32. package/src/modes/controllers/command-controller.ts +5 -35
  33. package/src/modes/controllers/event-controller.ts +7 -1
  34. package/src/modes/controllers/input-controller.ts +1 -1
  35. package/src/modes/interactive-mode.ts +9 -20
  36. package/src/modes/theme/theme.ts +6 -0
  37. package/src/modes/types.ts +2 -1
  38. package/src/prompts/goals/goal-todo-context.md +12 -0
  39. package/src/sdk.ts +13 -5
  40. package/src/session/agent-session.ts +129 -14
  41. package/src/session/session-manager.ts +2 -3
  42. package/src/slash-commands/builtin-registry.ts +7 -21
  43. package/src/task/executor.ts +4 -62
  44. package/src/task/provider-concurrency.ts +100 -0
  45. package/src/task/worktree.ts +13 -4
  46. package/src/task/yield-assembly.ts +27 -39
  47. package/src/tools/read.ts +11 -1
  48. package/src/utils/git.ts +13 -0
  49. package/src/prompts/system/autolearn-nudge.md +0 -5
@@ -27,6 +27,21 @@ interface UpdateMethodResolutionOptions {
27
27
  miseDataDir?: string;
28
28
  }
29
29
  export declare function resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined, options?: UpdateMethodResolutionOptions): UpdateMethod;
30
+ interface BunInstallCachePruneResult {
31
+ scannedPackages: number;
32
+ removedEntries: number;
33
+ }
34
+ /**
35
+ * Prune Bun's package cache so each package keeps only its newest cached version.
36
+ *
37
+ * Bun stores package cache entries as both a package marker directory
38
+ * (`react/19.2.6@@@1`) and a materialized package directory
39
+ * (`react@19.2.6@@@1`). Global `omp` updates can leave one full copy per
40
+ * release. The marker and materialized entries are removed together so the
41
+ * cache stays internally consistent.
42
+ */
43
+ export declare function pruneBunInstallCache(cacheDir: string, packageNames?: Set<string>): Promise<BunInstallCachePruneResult>;
44
+ export declare function resolveBunGlobalNodeModulesDirFromLocations(globalBinDir: string | undefined, cacheDir: string | undefined): string | undefined;
30
45
  /**
31
46
  * Best-effort removal of binary-update backups left by earlier runs.
32
47
  *
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Hard-cap helper for host→guest collab frames.
3
+ *
4
+ * The host wraps every {@link CollabFrame} in an AES-GCM envelope and ships it
5
+ * through the relay's WebSocket. WebSocket servers enforce a per-frame
6
+ * `maxPayloadLength` (Bun's default is 16 MB; many proxies cap lower). A
7
+ * single oversized payload — typically a `read`/`bash`/`search` tool result
8
+ * captured as one multi-megabyte string, or a tool result whose `content`
9
+ * array holds thousands of small blocks — would otherwise ship as its own
10
+ * oversized frame and trip that limit, killing the host's WebSocket with
11
+ * `1006 Received too big message`. `CollabSocket` treats 1006 as transient
12
+ * and reconnects, the next guest hello triggers the same oversized send, and
13
+ * the loop never breaks (issue #3739).
14
+ *
15
+ * This helper bounds any JSON-serializable payload below
16
+ * {@link MAX_REPLICATED_PAYLOAD_BYTES}. Already-small payloads pass through
17
+ * untouched; oversized ones are returned as a deep-cloned shadow where long
18
+ * strings are head-truncated AND long arrays are head-clipped, with
19
+ * `[…N chars elided for collab session]` / `[…N items elided for collab
20
+ * session]` markers. Both axes are needed: string truncation alone leaves
21
+ * the cap unenforced for a payload built of many short strings, where no
22
+ * field exceeds the per-string floor.
23
+ */
24
+ /**
25
+ * Per-payload ceiling for host→guest frames. Bun's default WebSocket
26
+ * `maxPayloadLength` is 16 MB; we leave a generous margin so the AES-GCM
27
+ * envelope (+ IV + tag), the 4-byte peer header, and the outer wire wrapper
28
+ * fit comfortably under that on every reasonable relay.
29
+ */
30
+ export declare const MAX_REPLICATED_PAYLOAD_BYTES: number;
31
+ /**
32
+ * Return `value` unchanged when its JSON serialization already fits
33
+ * {@link MAX_REPLICATED_PAYLOAD_BYTES}; otherwise return a deep-cloned
34
+ * shadow shrunk along both string and array axes until the payload fits.
35
+ * The function is generic over `T` because the wire shape is preserved:
36
+ * only string leaves and array tails change; discriminator fields, ids, and
37
+ * other small metadata pass through untouched.
38
+ */
39
+ export declare function shrinkForReplication<T>(value: T): T;
@@ -0,0 +1,7 @@
1
+ interface ProviderGlobalSettings {
2
+ get(path: "providers.webSearchExclude"): unknown;
3
+ get(path: "providers.webSearch"): unknown;
4
+ get(path: "providers.image"): unknown;
5
+ }
6
+ export declare function applyProviderGlobalsFromSettings(settings: ProviderGlobalSettings): void;
7
+ export {};
@@ -14,10 +14,28 @@ export declare function startMemoryStartupTask(options: {
14
14
  agentDir: string;
15
15
  taskDepth: number;
16
16
  }): void;
17
+ interface MemoryInstructionSession {
18
+ sessionManager: Pick<AgentSession["sessionManager"], "getSessionFile">;
19
+ }
20
+ /**
21
+ * Drop the per-session memory instruction snapshot after explicit memory state
22
+ * changes that must affect the active conversation immediately, such as
23
+ * `/memory clear`.
24
+ */
25
+ export declare function clearMemoryToolDeveloperInstructionsCache(session: MemoryInstructionSession | undefined): void;
26
+ /**
27
+ * Refresh the active session's consolidated-memory snapshot after startup maintenance.
28
+ *
29
+ * Startup may finish after the first prompt build and write `memory_summary.md`;
30
+ * the active session should see that summary. It must not reread `learned.md`,
31
+ * because a `learn` call racing with startup belongs to the next session's
32
+ * memory prompt, not the active prompt-cache prefix.
33
+ */
34
+ export declare function refreshMemoryToolDeveloperInstructionsCacheAfterStartup(session: MemoryInstructionSession, agentDir: string, settings: Settings): Promise<void>;
17
35
  /**
18
36
  * Build memory usage instructions for prompt injection.
19
37
  */
20
- export declare function buildMemoryToolDeveloperInstructions(agentDir: string, settings: Settings): Promise<string | undefined>;
38
+ export declare function buildMemoryToolDeveloperInstructions(agentDir: string, settings: Settings, session?: MemoryInstructionSession): Promise<string | undefined>;
21
39
  /**
22
40
  * Clear all persisted memory state and generated artifacts.
23
41
  */
@@ -33,3 +51,4 @@ export declare function getMemoryRoot(agentDir: string, cwd: string): string;
33
51
  * tool when `memory.backend` is `local`.
34
52
  */
35
53
  export declare function saveLearnedLesson(agentDir: string, cwd: string, input: MemoryBackendSaveInput): Promise<MemoryBackendSaveResult>;
54
+ export {};
@@ -14,6 +14,11 @@ export declare class StatusLineComponent implements Component {
14
14
  getEffectiveSettingsForTest(): EffectiveStatusLineSettings;
15
15
  setAutoCompactEnabled(enabled: boolean): void;
16
16
  setSubagentCount(count: number): void;
17
+ /**
18
+ * Compatibility shim for callers predating the simplified subagent badge.
19
+ * The status line now intentionally shows only the active count.
20
+ */
21
+ setSubagentHubHint(_hint: string | undefined): void;
17
22
  /** Active subagent count as currently displayed (collab state mirroring). */
18
23
  get subagentCount(): number;
19
24
  /**
@@ -103,6 +103,16 @@ export interface SegmentContext {
103
103
  url: string;
104
104
  } | null;
105
105
  };
106
+ /**
107
+ * Set when the path cwd is a *linked* git worktree, naming the shared
108
+ * primary checkout (the project). Lets the path segment collapse the
109
+ * base-prefixed `<base>/<project>/<worktree>` path to the project name —
110
+ * the worktree/branch is already shown by the git segment.
111
+ */
112
+ worktree: {
113
+ projectName: string;
114
+ worktreeName: string;
115
+ } | null;
106
116
  usage: {
107
117
  tier?: string;
108
118
  fiveHour?: {
@@ -30,13 +30,13 @@ export declare class CommandController {
30
30
  handleDropCommand(): Promise<void>;
31
31
  handleForkCommand(): Promise<void>;
32
32
  /**
33
- * `/move` — switch to a fresh empty session in a different directory.
33
+ * `/move` — relocate the current session to a different directory.
34
34
  *
35
35
  * With no `targetPath` (TUI only), opens an autocomplete overlay so the user
36
36
  * can pick or type a directory. With a `targetPath`, resolves it directly.
37
37
  * If the target directory does not exist, the user is asked whether to create
38
- * it. A brand-new empty session is then started in the target directory and
39
- * the current session is left behind (resumable via `/resume`).
38
+ * it. The active session file and artifacts are moved into the target
39
+ * directory's session bucket so `/resume` from that directory can find it.
40
40
  */
41
41
  handleMoveCommand(targetPath?: string): Promise<void>;
42
42
  handleRenameCommand(title: string): Promise<void>;
@@ -83,6 +83,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
83
83
  chatContainer: TranscriptContainer;
84
84
  pendingMessagesContainer: Container;
85
85
  statusContainer: Container;
86
+ todoReminderContainer: Container;
86
87
  todoContainer: Container;
87
88
  subagentContainer: Container;
88
89
  btwContainer: Container;
@@ -6,7 +6,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -164,6 +164,7 @@ export declare class Theme {
164
164
  pause: string;
165
165
  loop: string;
166
166
  folder: string;
167
+ worktree: string;
167
168
  scratchFolder: string;
168
169
  file: string;
169
170
  git: string;
@@ -81,6 +81,7 @@ export interface InteractiveModeContext {
81
81
  chatContainer: TranscriptContainer;
82
82
  pendingMessagesContainer: Container;
83
83
  statusContainer: Container;
84
+ todoReminderContainer: Container;
84
85
  todoContainer: Container;
85
86
  subagentContainer: Container;
86
87
  btwContainer: Container;
@@ -104,7 +105,7 @@ export interface InteractiveModeContext {
104
105
  focusParentSession(): Promise<void>;
105
106
  /** Return the view to the main session (delegates to SessionFocusController.unfocus). */
106
107
  unfocusSession(): Promise<void>;
107
- /** Clear loader, status/pending containers, streaming state, and pending tools. */
108
+ /** Clear loader, transient HUD/pending containers, streaming state, and pending tools. */
108
109
  clearTransientSessionUi(): void;
109
110
  settings: Settings;
110
111
  keybindings: KeybindingsManager;
@@ -169,6 +169,13 @@ export interface AgentSessionConfig {
169
169
  * inherits this so its requests undergo the same shaping as the main turn.
170
170
  */
171
171
  transformProviderContext?: (context: Context, model: Model) => Context | Promise<Context>;
172
+ /**
173
+ * Stream wrapper passed to side-channel requests (`/btw`, `/omfg`, IRC
174
+ * auto-replies, and handoff generation) so they apply the same provider
175
+ * shaping and host-level request wrappers as normal agent turns. Defaults
176
+ * to plain `streamSimple` when omitted.
177
+ */
178
+ sideStreamFn?: StreamFn;
172
179
  /**
173
180
  * Stream wrapper passed to the advisor agent so its requests apply the
174
181
  * session's `providers.openrouterVariant`, `providers.antigravityEndpoint`,
@@ -242,9 +242,8 @@ export declare class SessionManager {
242
242
  /**
243
243
  * Create a fresh empty session file in the default session directory for
244
244
  * `cwd`, writing only the session header. The returned path can be passed to
245
- * `setSessionFile` / `AgentSession.switchSession` to start a new empty
246
- * session in that directory. Used by `/move` to switch projects without
247
- * dragging the current conversation along.
245
+ * `setSessionFile` / `AgentSession.switchSession` when a caller explicitly
246
+ * needs a brand-new persisted session at a cwd-derived path.
248
247
  */
249
248
  static createEmptySessionFile(cwd: string, storage?: SessionStorage): string;
250
249
  /**
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Per-provider LLM concurrency cap, applied around each provider HTTP request.
3
+ *
4
+ * The semaphore brackets only the streaming request itself, not the whole
5
+ * agent lifetime: a parent subagent releases its slot the moment its LLM
6
+ * stream finishes producing, so children spawned during tool execution can
7
+ * acquire slots for their own turns. Holding the slot across the parent's
8
+ * full conversation deadlocks any spawn tree whose width exceeds
9
+ * `maxConcurrency` because the parents wait for children that wait for
10
+ * slots the parents are holding (issue
11
+ * [#3749](https://github.com/can1357/oh-my-pi/issues/3749)).
12
+ */
13
+ import type { StreamFn } from "@oh-my-pi/pi-agent-core";
14
+ import type { Settings } from "../config/settings";
15
+ import { Semaphore } from "./parallel";
16
+ /**
17
+ * Resolve the configured concurrency ceiling for a provider, or `undefined`
18
+ * when the provider has no cap concept at all. A configured value `<= 0` means
19
+ * "unlimited" and maps to `Infinity` — still a tracked ceiling, so every run
20
+ * holds a slot and a later finite resize counts work started while unlimited.
21
+ */
22
+ export declare function getProviderConcurrencyLimit(settings: Settings, provider: string): number | undefined;
23
+ /**
24
+ * Hand out the single shared limiter for `provider` (creating one lazily) and
25
+ * resize it in place when the configured limit changes. Replacing the
26
+ * semaphore would orphan in-flight slots on the old instance and let a
27
+ * runtime or mixed limit value exceed the cap (issue #3464 review feedback).
28
+ */
29
+ export declare function getProviderSemaphore(settings: Settings, provider: string): Semaphore | undefined;
30
+ /**
31
+ * Wrap a {@link StreamFn} so every LLM HTTP request acquires the provider's
32
+ * concurrency slot before the request goes out and releases it when the
33
+ * stream finishes producing (success, error, or abort). Providers without a
34
+ * configured cap pass straight through.
35
+ *
36
+ * The acquire bracket is intentionally narrow (one slot per LLM call), so
37
+ * spawn trees deeper than `maxConcurrency` no longer deadlock on themselves —
38
+ * see the module-level comment for the failure mode this fixes.
39
+ */
40
+ export declare function wrapStreamFnWithProviderConcurrency(settings: Settings, base: StreamFn): StreamFn;
@@ -349,6 +349,17 @@ export declare const repo: {
349
349
  * the shared common dir (`foo.git`) because they have no primary checkout.
350
350
  */
351
351
  primaryRootSync(cwd: string): string | null;
352
+ /**
353
+ * Linked-worktree metadata for `cwd`, or `null` when `cwd` is the primary
354
+ * checkout (or outside a repository). `root` is the worktree's own checkout
355
+ * root; `primaryRoot` is the shared main checkout that names the project.
356
+ * Resolves purely via on-disk `.git`/`commondir` walking — no subprocess —
357
+ * so the status line may call it on every render.
358
+ */
359
+ linkedWorktreeSync(cwd: string): {
360
+ root: string;
361
+ primaryRoot: string;
362
+ } | null;
352
363
  /** Full GitRepository metadata (sync). */
353
364
  resolveSync(cwd: string): GitRepository | null;
354
365
  /** Full GitRepository metadata. */
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": "16.2.3",
4
+ "version": "16.2.5",
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",
@@ -55,17 +55,17 @@
55
55
  "@agentclientprotocol/sdk": "0.25.0",
56
56
  "@babel/parser": "^7.29.7",
57
57
  "@mozilla/readability": "^0.6.0",
58
- "@oh-my-pi/hashline": "16.2.3",
59
- "@oh-my-pi/omp-stats": "16.2.3",
60
- "@oh-my-pi/pi-agent-core": "16.2.3",
61
- "@oh-my-pi/pi-ai": "16.2.3",
62
- "@oh-my-pi/pi-catalog": "16.2.3",
63
- "@oh-my-pi/pi-mnemopi": "16.2.3",
64
- "@oh-my-pi/pi-natives": "16.2.3",
65
- "@oh-my-pi/pi-tui": "16.2.3",
66
- "@oh-my-pi/pi-utils": "16.2.3",
67
- "@oh-my-pi/pi-wire": "16.2.3",
68
- "@oh-my-pi/snapcompact": "16.2.3",
58
+ "@oh-my-pi/hashline": "16.2.5",
59
+ "@oh-my-pi/omp-stats": "16.2.5",
60
+ "@oh-my-pi/pi-agent-core": "16.2.5",
61
+ "@oh-my-pi/pi-ai": "16.2.5",
62
+ "@oh-my-pi/pi-catalog": "16.2.5",
63
+ "@oh-my-pi/pi-mnemopi": "16.2.5",
64
+ "@oh-my-pi/pi-natives": "16.2.5",
65
+ "@oh-my-pi/pi-tui": "16.2.5",
66
+ "@oh-my-pi/pi-utils": "16.2.5",
67
+ "@oh-my-pi/pi-wire": "16.2.5",
68
+ "@oh-my-pi/snapcompact": "16.2.5",
69
69
  "@opentelemetry/api": "^1.9.1",
70
70
  "@opentelemetry/context-async-hooks": "^2.7.1",
71
71
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -2,9 +2,9 @@
2
2
  * Auto-learn session controller (experimental).
3
3
  *
4
4
  * Subscribes to the session event stream and, after a substantive turn,
5
- * nudges the agent to capture reusable lessons. Default posture is passive
6
- * (a hidden reminder rides the next real turn); with `autolearn.autoContinue`
7
- * it auto-runs exactly one synthetic capture turn at stop.
5
+ * optionally auto-runs a synthetic capture turn. Passive mode is intentionally
6
+ * prompt-cache neutral: the standing system guidance remains available, but no
7
+ * hidden mid-session reminder is inserted into the conversation.
8
8
  *
9
9
  * Installed once per top-level session (taskDepth 0). The subscription lives
10
10
  * for the session's lifetime — `newSession` resets the session in place
@@ -14,11 +14,9 @@ import { logger } from "@oh-my-pi/pi-utils";
14
14
  import type { Settings } from "../config/settings";
15
15
  import autolearnGuidance from "../prompts/system/autolearn-guidance.md" with { type: "text" };
16
16
  import autolearnGuidanceLearn from "../prompts/system/autolearn-guidance-learn.md" with { type: "text" };
17
- import autolearnNudge from "../prompts/system/autolearn-nudge.md" with { type: "text" };
18
17
  import autolearnNudgeAutoContinue from "../prompts/system/autolearn-nudge-autocontinue.md" with { type: "text" };
19
18
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
20
19
 
21
- const AUTOLEARN_NUDGE_PASSIVE = autolearnNudge.trim();
22
20
  const AUTOLEARN_NUDGE_AUTOCONTINUE = autolearnNudgeAutoContinue.trim();
23
21
  const DEFAULT_MIN_TOOL_CALLS = 5;
24
22
 
@@ -110,28 +108,21 @@ export class AutoLearnController {
110
108
  // auto-continue would compete with it.
111
109
  if (startedInGoalMode || this.#session.getGoalModeState()?.enabled) return;
112
110
 
113
- // Auto-run a capture turn only when explicitly enabled; otherwise the
114
- // hidden reminder rides the next real turn passively.
115
- //
116
- // The two paths get DIFFERENT nudge text:
117
- // - Auto-continue spawns a synthetic turn with the nudge as its only
118
- // user-role payload, so the prompt must be terminal — it tells the
119
- // agent to capture and STOP, and must not be read as the user's
120
- // reply to any pending question. Without that contract, the agent
121
- // conflates the synthetic prompt with user approval and resumes
122
- // prior work (e.g. commits/pushes an unanswered "want me to push?"
123
- // question — #3504).
124
- // - Passive mode appends the nudge to the user's real next message,
125
- // so the agent must answer the user normally and treat the capture
126
- // as additive — not as the whole turn's job.
111
+ // Auto-run a capture turn only when explicitly enabled. Passive mode used to
112
+ // queue a hidden custom message for the next real turn, but that mutates the
113
+ // persisted conversation prefix after providers have cached it. The standing
114
+ // auto-learn system guidance is stable; keep passive mode to that guidance
115
+ // so Anthropic prompt-cache prefixes survive long sessions.
127
116
  const autoContinue = this.#settings.get("autolearn.autoContinue") === true;
128
- const content = autoContinue ? AUTOLEARN_NUDGE_AUTOCONTINUE : AUTOLEARN_NUDGE_PASSIVE;
117
+ if (!autoContinue) return;
118
+
119
+ const content = AUTOLEARN_NUDGE_AUTOCONTINUE;
129
120
  // Arm suppression synchronously: the synthetic capture turn's agent_end
130
121
  // fires inside sendCustomMessage (before it resolves), so the flag must be
131
122
  // set before then. Disarm when no turn actually started — a deferred/queued
132
123
  // dispatch or a failed send produces no agent_end, and a latched flag would
133
124
  // otherwise swallow the next real stop.
134
- if (autoContinue) this.#suppressNext = true;
125
+ this.#suppressNext = true;
135
126
 
136
127
  this.#session
137
128
  .sendCustomMessage(
@@ -141,7 +132,7 @@ export class AutoLearnController {
141
132
  display: false,
142
133
  attribution: "user",
143
134
  },
144
- { deliverAs: "nextTurn", triggerTurn: autoContinue },
135
+ { deliverAs: "nextTurn", triggerTurn: true },
145
136
  )
146
137
  .then(started => {
147
138
  if (!started) this.#suppressNext = false;