@oh-my-pi/pi-coding-agent 15.2.4 → 15.3.0
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.
- package/CHANGELOG.md +20 -0
- package/dist/types/config/settings-schema.d.ts +34 -1
- package/dist/types/config/settings.d.ts +6 -0
- package/dist/types/discovery/helpers.d.ts +1 -0
- package/dist/types/goals/runtime.d.ts +4 -0
- package/dist/types/modes/components/status-line/types.d.ts +10 -0
- package/dist/types/modes/components/status-line.d.ts +10 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +17 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/task/types.d.ts +35 -0
- package/dist/types/tools/bash-command-fixup.d.ts +0 -5
- package/dist/types/utils/clipboard.d.ts +3 -1
- package/dist/types/utils/image-resize.d.ts +4 -1
- package/package.json +7 -7
- package/src/config/settings-schema.ts +29 -1
- package/src/config/settings.ts +19 -0
- package/src/discovery/helpers.ts +5 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +27 -5
- package/src/goals/runtime.ts +35 -13
- package/src/main.ts +1 -1
- package/src/modes/components/model-selector.ts +53 -22
- package/src/modes/components/status-line/segments.ts +53 -0
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +147 -12
- package/src/modes/controllers/command-controller.ts +9 -0
- package/src/modes/controllers/event-controller.ts +8 -0
- package/src/modes/interactive-mode.ts +23 -8
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/context-usage.ts +25 -2
- package/src/modes/utils/ui-helpers.ts +11 -1
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/sdk.ts +24 -0
- package/src/session/agent-session.ts +58 -0
- package/src/session/session-manager.ts +54 -1
- package/src/slash-commands/builtin-registry.ts +10 -0
- package/src/task/executor.ts +50 -1
- package/src/task/index.ts +11 -0
- package/src/task/render.ts +26 -2
- package/src/task/types.ts +35 -0
- package/src/tools/bash-command-fixup.ts +0 -10
- package/src/tools/bash.ts +1 -9
- package/src/utils/clipboard.ts +68 -3
- package/src/utils/image-resize.ts +51 -26
- package/dist/types/modes/components/status-line-segment-editor.d.ts +0 -24
- package/src/modes/components/status-line-segment-editor.ts +0 -359
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.3.0] - 2026-05-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `OMP_NO_WEBP` environment variable to disable WebP encoding in image resize, fixing HTTP 400 errors when attaching browser snapshots to vision models running on local llama.cpp (which uses STB library that lacks WebP support)
|
|
10
|
+
- Fixed loop mode submitting the next prompt while a background async-job delivery turn (idle flush) was still pending, which could cause the job result to be silently dropped and make the session appear to keep firing while work was ongoing ([#1294](https://github.com/can1357/oh-my-pi/issues/1294))
|
|
11
|
+
- Fixed clipboard image paste (Ctrl+V) silently failing on WSL2 by routing image reads through a `powershell.exe` bridge when WSL interop is detected, since `arboard` returns `ContentNotAvailable` under WSLg ([#1280](https://github.com/can1357/oh-my-pi/issues/1280))
|
|
12
|
+
- Fixed extension `ctx.ui.notify()` messages emitted during `session_start` being cleared before the first interactive render ([#1316](https://github.com/can1357/oh-my-pi/issues/1316)).
|
|
13
|
+
- Fixed append-only context mode not being recomputed after model switches — the mode was frozen at session construction time using the initial model's provider, so `provider.appendOnlyContext=auto` left append-only enabled after switching away from DeepSeek (or disabled after switching to DeepSeek) for the rest of the session
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Fixed clipboard image paste (Ctrl+V) silently failing on WSL2 by routing image reads through a `powershell.exe` bridge when WSL interop is detected, since `arboard` returns `ContentNotAvailable` under WSLg ([#1280](https://github.com/can1357/oh-my-pi/issues/1280))
|
|
5
17
|
## [15.2.4] - 2026-05-22
|
|
6
18
|
### Breaking Changes
|
|
7
19
|
|
|
@@ -114,6 +126,10 @@
|
|
|
114
126
|
- Fixed streaming edit previews for `apply_patch` and `hashline` jittering as the model typed `+added` lines. Two root causes addressed: (1) the trailing partial line of the streaming text input is now trimmed at each tick so a half-typed `+added` line no longer flickers; (2) the preview is rendered in the model's input order during streaming instead of re-deriving a unified diff via `Diff.structuredPatch`, whose coalescing previously reshuffled existing `+added` lines downward each time a new `-removed` line arrived. Existing additions now stay put and the preview only grows at the bottom while streaming. A residual trailing `-removed`/hunk-header block whose matching `+added` companion has not yet arrived is also suppressed until the additions land.
|
|
115
127
|
- Fixed Perplexity web search appearing "logged out" roughly an hour after `omp auth login perplexity`. The search provider's `findOAuthToken` was honoring the bogus `expires = login_time + 1h` written by older logins (Perplexity JWTs typically omit `exp` because sessions are server-side) and silently dropping the credential. The loader now decodes the JWT's `exp` claim directly and only skips when the JWT itself is expired; tokens without an `exp` claim are treated as non-expiring.
|
|
116
128
|
|
|
129
|
+
### Fixed
|
|
130
|
+
|
|
131
|
+
- Fixed `legacy-pi-compat` failing to load plugin extensions (e.g. `pi-schedule-prompt@0.3.0`) that import `@mariozechner/pi-ai` when running from a compiled binary. `getResolvedSpecifier` called `Bun.resolveSync` against `import.meta.dir` inside `/$bunfs/root`, where the virtual FS exposes no resolvable `node_modules` tree at runtime; the throw silently dropped the plugin. The fix lets `rewriteLegacyPiImports` fall back gracefully on resolution failure so that `rewriteBareImportsForLegacyExtension` — which already runs immediately after — can resolve the original specifier against the plugin's own installed peer deps instead. The same fallback is applied to `resolveLegacyPiSpecifier` (the Bun plugin shim's `onResolve` handler) for tool/hook files loaded directly via Bun's import system. ([#1215](https://github.com/can1357/oh-my-pi/issues/1215))
|
|
132
|
+
|
|
117
133
|
## [15.1.7] - 2026-05-19
|
|
118
134
|
|
|
119
135
|
### Fixed
|
|
@@ -122,6 +138,9 @@
|
|
|
122
138
|
- Fixed hashline edit payloads that use a readability space after `~` by warning on separator-padding-shaped payload blocks and tightening the model prompt. ([#1166](https://github.com/can1357/oh-my-pi/issues/1166))
|
|
123
139
|
- Fixed ACP bash permission requests to include execute tool metadata and command content so clients can render command approval prompts consistently. ([#1189](https://github.com/can1357/oh-my-pi/issues/1189))
|
|
124
140
|
- Fixed the status-line fast-mode indicator (`⚡`) rendering for scoped service tiers (`openai-only`, `claude-only`) even when the active model's provider didn't realize them — e.g. `serviceTier: "openai-only"` would still show the indicator next to a Claude model the wire request couldn't apply fast mode to. The indicator now consults a new `AgentSession.isFastModeActive()` predicate that runs the configured tier through `resolveServiceTier(tier, model.provider)` and only lights up when the result is `"priority"` for the current model. `isFastModeEnabled()` keeps its scope-aware semantics so `/fast on|off|toggle` and `/fast status` continue to reflect the user's configured intent.
|
|
141
|
+
### Fixed
|
|
142
|
+
|
|
143
|
+
- Fixed status-line context% computation freezing the UI for ~1.1 s every 2 s on long sessions (2,000+ messages). The earlier alignment fix (which uses `computeContextBreakdown` to match the `/context` slash command) was running on every agent event via `updateEditorTopBorder()` (event-controller.ts:163), and `computeContextBreakdown` walks every message through the native `countTokens` tokenizer (~0.5 ms each) — for the user's 2,312-message session this was ~1,120 ms synchronous blocking per cache miss, producing the user-visible "jittery rendering" and "status bar disappearing during streaming". `StatusLineComponent.getCachedContextBreakdown()` now uses an incremental per-message token cache: messages are walked ONCE during warm-up, and subsequent refreshes only compute tokens for the NEW messages appended since last call (typically 0–1 per refresh during streaming). The LAST message is always recomputed because its content may still be growing mid-stream; all prior messages are immutable once a newer message exists. Compaction (messages array shrinks) resets the cache. Non-message tokens (system prompt + tools + skills) are cached separately and invalidated via a cheap identity fingerprint. Result: 2,300-message warm refresh drops from ~1,120 ms to ~0.04 ms — 28,000× faster. Functional parity with the prior `computeContextBreakdown` path is preserved.
|
|
125
144
|
|
|
126
145
|
### Added
|
|
127
146
|
|
|
@@ -156,6 +175,7 @@
|
|
|
156
175
|
- Fixed `deferAgentInitiatedTurns` handling during ACP async-job draining so background completion follow-up turns are delivered even when agent-initiated turns are deferred
|
|
157
176
|
- Fixed ACP ordinary file-editing calls (`edit`, `write`, `ast_edit`) incorrectly requesting `session/request_permission` before every call, while keeping permission prompts for edit operations that delete or move files; permission requests now report the gated tool call as `pending` so clients can render the approval UI instead of returning `Permission request cancelled` without a visible prompt. ([#1134](https://github.com/can1357/oh-my-pi/pull/1134) by [@jiwangyihao](https://github.com/jiwangyihao))
|
|
158
177
|
- Fixed the session tree selector to preserve a readable message column when deeply nested branch gutters would otherwise consume the viewport. ([#1144](https://github.com/can1357/oh-my-pi/issues/1144))
|
|
178
|
+
- Fixed the TUI model selector to keep provider tab labels separate from provider ids, so the human-readable Ollama Cloud tab refreshes and filters `ollama-cloud` models correctly. ([#1153](https://github.com/can1357/oh-my-pi/issues/1153))
|
|
159
179
|
|
|
160
180
|
## [15.1.3] - 2026-05-17
|
|
161
181
|
### Breaking Changes
|
|
@@ -23,7 +23,7 @@ export declare const TAB_METADATA: Record<SettingTab, {
|
|
|
23
23
|
icon: `tab.${string}`;
|
|
24
24
|
}>;
|
|
25
25
|
/** Status line segment identifiers */
|
|
26
|
-
export type StatusLineSegmentId = "pi" | "model" | "mode" | "path" | "git" | "pr" | "subagents" | "token_in" | "token_out" | "token_total" | "token_rate" | "cost" | "context_pct" | "context_total" | "time_spent" | "time" | "session" | "hostname" | "cache_read" | "cache_write" | "session_name";
|
|
26
|
+
export type StatusLineSegmentId = "pi" | "model" | "mode" | "path" | "git" | "pr" | "subagents" | "token_in" | "token_out" | "token_total" | "token_rate" | "cost" | "context_pct" | "context_total" | "time_spent" | "time" | "session" | "hostname" | "cache_read" | "cache_write" | "session_name" | "usage";
|
|
27
27
|
/** Submenu choice metadata. */
|
|
28
28
|
export type SubmenuOption<V extends string = string> = {
|
|
29
29
|
value: V;
|
|
@@ -991,6 +991,15 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
991
991
|
readonly type: "number";
|
|
992
992
|
readonly default: 2000;
|
|
993
993
|
};
|
|
994
|
+
readonly "retry.maxDelayMs": {
|
|
995
|
+
readonly type: "number";
|
|
996
|
+
readonly default: number;
|
|
997
|
+
readonly ui: {
|
|
998
|
+
readonly tab: "model";
|
|
999
|
+
readonly label: "Max Retry Delay";
|
|
1000
|
+
readonly description: "Maximum wait between retries, in ms. When the provider asks us to wait longer than this and no credential or model fallback succeeds, the request fails fast instead of sleeping (e.g. 3-hour Anthropic rate-limit windows).";
|
|
1001
|
+
};
|
|
1002
|
+
};
|
|
994
1003
|
readonly "retry.fallbackChains": {
|
|
995
1004
|
readonly type: "record";
|
|
996
1005
|
readonly default: Record<string, string[]>;
|
|
@@ -3075,6 +3084,29 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
3075
3084
|
readonly description: "Use Parallel extract API for URL fetching when credentials are available";
|
|
3076
3085
|
};
|
|
3077
3086
|
};
|
|
3087
|
+
readonly "provider.appendOnlyContext": {
|
|
3088
|
+
readonly type: "enum";
|
|
3089
|
+
readonly values: readonly ["auto", "on", "off"];
|
|
3090
|
+
readonly default: "auto";
|
|
3091
|
+
readonly ui: {
|
|
3092
|
+
readonly tab: "providers";
|
|
3093
|
+
readonly label: "Append-Only Context";
|
|
3094
|
+
readonly description: "Cache system prompt + tool specs and keep an append-only message log so provider prefix caches (DeepSeek, Anthropic) hit at maximum rate. Auto enables for DeepSeek.";
|
|
3095
|
+
readonly options: readonly [{
|
|
3096
|
+
readonly value: "auto";
|
|
3097
|
+
readonly label: "Auto";
|
|
3098
|
+
readonly description: "Enable for DeepSeek (recommended)";
|
|
3099
|
+
}, {
|
|
3100
|
+
readonly value: "on";
|
|
3101
|
+
readonly label: "On";
|
|
3102
|
+
readonly description: "Always enable append-only context";
|
|
3103
|
+
}, {
|
|
3104
|
+
readonly value: "off";
|
|
3105
|
+
readonly label: "Off";
|
|
3106
|
+
readonly description: "Disable append-only context";
|
|
3107
|
+
}];
|
|
3108
|
+
};
|
|
3109
|
+
};
|
|
3078
3110
|
readonly "exa.enabled": {
|
|
3079
3111
|
readonly type: "boolean";
|
|
3080
3112
|
readonly default: true;
|
|
@@ -3283,6 +3315,7 @@ export interface RetrySettings {
|
|
|
3283
3315
|
enabled: boolean;
|
|
3284
3316
|
maxRetries: number;
|
|
3285
3317
|
baseDelayMs: number;
|
|
3318
|
+
maxDelayMs: number;
|
|
3286
3319
|
}
|
|
3287
3320
|
export interface MemoriesSettings {
|
|
3288
3321
|
enabled: boolean;
|
|
@@ -113,6 +113,12 @@ export declare class Settings {
|
|
|
113
113
|
*/
|
|
114
114
|
setDisabledProviders(ids: string[]): void;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Subscribe to append-only mode setting changes.
|
|
118
|
+
* Returns an unsubscribe function. Multiple sessions (main + subagents)
|
|
119
|
+
* can register independently without overwriting each other.
|
|
120
|
+
*/
|
|
121
|
+
export declare function onAppendOnlyModeChanged(cb: (value: string) => void): () => void;
|
|
116
122
|
export declare function isSettingsInitialized(): boolean;
|
|
117
123
|
/**
|
|
118
124
|
* Reset the global singleton for testing.
|
|
@@ -54,6 +54,10 @@ export declare class GoalRuntime {
|
|
|
54
54
|
objective: string;
|
|
55
55
|
tokenBudget?: number;
|
|
56
56
|
}): Promise<GoalModeState>;
|
|
57
|
+
replaceGoal(input: {
|
|
58
|
+
objective: string;
|
|
59
|
+
tokenBudget?: number;
|
|
60
|
+
}): Promise<GoalModeState>;
|
|
57
61
|
resumeGoal(): Promise<GoalModeState>;
|
|
58
62
|
pauseGoal(): Promise<GoalModeState | undefined>;
|
|
59
63
|
dropGoal(): Promise<Goal | undefined>;
|
|
@@ -44,6 +44,16 @@ export interface SegmentContext {
|
|
|
44
44
|
url: string;
|
|
45
45
|
} | null;
|
|
46
46
|
};
|
|
47
|
+
usage: {
|
|
48
|
+
fiveHour?: {
|
|
49
|
+
percent: number;
|
|
50
|
+
resetMinutes?: number;
|
|
51
|
+
};
|
|
52
|
+
sevenDay?: {
|
|
53
|
+
percent: number;
|
|
54
|
+
resetHours?: number;
|
|
55
|
+
};
|
|
56
|
+
} | null;
|
|
47
57
|
}
|
|
48
58
|
export interface RenderedSegment {
|
|
49
59
|
content: string;
|
|
@@ -53,6 +53,16 @@ export declare class StatusLineComponent implements Component {
|
|
|
53
53
|
watchBranch(onBranchChange: () => void): void;
|
|
54
54
|
dispose(): void;
|
|
55
55
|
invalidate(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Compute the (cached) used-tokens / context-window totals for the
|
|
58
|
+
* status-line context% segment. Exposed (non-private) so unit tests can
|
|
59
|
+
* verify the incremental-cache invariants; not part of any external
|
|
60
|
+
* API.
|
|
61
|
+
*/
|
|
62
|
+
getCachedContextBreakdown(): {
|
|
63
|
+
usedTokens: number;
|
|
64
|
+
contextWindow: number;
|
|
65
|
+
};
|
|
56
66
|
getTopBorder(width: number): {
|
|
57
67
|
content: string;
|
|
58
68
|
width: number;
|
|
@@ -171,7 +171,9 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
171
171
|
updateFooter?: boolean;
|
|
172
172
|
populateHistory?: boolean;
|
|
173
173
|
}): void;
|
|
174
|
-
renderInitialMessages(prebuiltContext?: SessionContext
|
|
174
|
+
renderInitialMessages(prebuiltContext?: SessionContext, options?: {
|
|
175
|
+
preserveExistingChat?: boolean;
|
|
176
|
+
}): void;
|
|
175
177
|
getUserMessageText(message: Message): string;
|
|
176
178
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
177
179
|
extractAssistantText(message: AssistantMessage): string;
|
|
@@ -168,7 +168,9 @@ export interface InteractiveModeContext {
|
|
|
168
168
|
updateFooter?: boolean;
|
|
169
169
|
populateHistory?: boolean;
|
|
170
170
|
}): void;
|
|
171
|
-
renderInitialMessages(prebuiltContext?: SessionContext
|
|
171
|
+
renderInitialMessages(prebuiltContext?: SessionContext, options?: {
|
|
172
|
+
preserveExistingChat?: boolean;
|
|
173
|
+
}): void;
|
|
172
174
|
getUserMessageText(message: Message): string;
|
|
173
175
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
174
176
|
extractAssistantText(message: AssistantMessage): string;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Skill } from "../../extensibility/skills";
|
|
2
3
|
import type { AgentSession } from "../../session/agent-session";
|
|
4
|
+
import type { Tool } from "../../tools";
|
|
3
5
|
import type { theme as Theme } from "../theme/theme";
|
|
4
6
|
type CategoryId = "systemPrompt" | "systemContext" | "systemTools" | "skills" | "messages";
|
|
5
7
|
interface CategoryInfo {
|
|
@@ -17,6 +19,21 @@ export interface ContextBreakdown {
|
|
|
17
19
|
autoCompactBufferTokens: number;
|
|
18
20
|
freeTokens: number;
|
|
19
21
|
}
|
|
22
|
+
export declare function estimateSkillsTokens(skills: readonly Skill[]): number;
|
|
23
|
+
export declare function estimateToolSchemaTokens(tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>): number;
|
|
24
|
+
/**
|
|
25
|
+
* Compute just the NON-MESSAGE token total: system prompt (with its skills
|
|
26
|
+
* section subtracted, since skills are tokenized separately) + system context
|
|
27
|
+
* (the rest of the system-prompt array) + tools + skills.
|
|
28
|
+
*
|
|
29
|
+
* Exposed so callers like `StatusLineComponent` can cache the non-message
|
|
30
|
+
* total separately from the message total. Non-message inputs (skills,
|
|
31
|
+
* tools, system prompt) change rarely; the message list grows on every
|
|
32
|
+
* streaming turn. Splitting the two lets the caller refresh each on its own
|
|
33
|
+
* cadence — non-message recomputed only when the inputs identity changes,
|
|
34
|
+
* messages walked incrementally as new entries append.
|
|
35
|
+
*/
|
|
36
|
+
export declare function computeNonMessageTokens(session: AgentSession): number;
|
|
20
37
|
/**
|
|
21
38
|
* Compute a breakdown of estimated context usage by category for the active
|
|
22
39
|
* session and model.
|
|
@@ -3,6 +3,9 @@ import type { AssistantMessage, Message } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import { type Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
5
5
|
import type { SessionContext } from "../../session/session-manager";
|
|
6
|
+
interface RenderInitialMessagesOptions {
|
|
7
|
+
preserveExistingChat?: boolean;
|
|
8
|
+
}
|
|
6
9
|
export declare class UiHelpers {
|
|
7
10
|
#private;
|
|
8
11
|
private ctx;
|
|
@@ -31,7 +34,7 @@ export declare class UiHelpers {
|
|
|
31
34
|
updateFooter?: boolean;
|
|
32
35
|
populateHistory?: boolean;
|
|
33
36
|
}): void;
|
|
34
|
-
renderInitialMessages(prebuiltContext?: SessionContext): void;
|
|
37
|
+
renderInitialMessages(prebuiltContext?: SessionContext, options?: RenderInitialMessagesOptions): void;
|
|
35
38
|
clearEditor(): void;
|
|
36
39
|
showError(errorMessage: string): void;
|
|
37
40
|
showWarning(warningMessage: string): void;
|
|
@@ -47,3 +50,4 @@ export declare class UiHelpers {
|
|
|
47
50
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
48
51
|
extractAssistantText(message: AssistantMessage): string;
|
|
49
52
|
}
|
|
53
|
+
export {};
|
|
@@ -417,6 +417,15 @@ export declare class AgentSession {
|
|
|
417
417
|
refreshRpcHostTools(rpcTools: AgentTool[]): Promise<void>;
|
|
418
418
|
/** Whether auto-compaction is currently running */
|
|
419
419
|
get isCompacting(): boolean;
|
|
420
|
+
/**
|
|
421
|
+
* Whether idle-flush tasks, auto-continuations, or other short-lived
|
|
422
|
+
* post-prompt work are pending. True in the brief window after
|
|
423
|
+
* `session.prompt()` returns but before a scheduled background delivery
|
|
424
|
+
* (e.g. an async-job result) has finished its own streaming turn.
|
|
425
|
+
* Loop-mode and similar auto-submit paths should treat this as a block
|
|
426
|
+
* to avoid racing against the delivery turn.
|
|
427
|
+
*/
|
|
428
|
+
get hasPostPromptWork(): boolean;
|
|
420
429
|
/** All messages including custom types like BashExecutionMessage */
|
|
421
430
|
get messages(): AgentMessage[];
|
|
422
431
|
buildDisplaySessionContext(): SessionContext;
|
|
@@ -7,7 +7,7 @@ import type { AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-cor
|
|
|
7
7
|
import { ModelRegistry } from "../config/model-registry";
|
|
8
8
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
9
9
|
import { Settings } from "../config/settings";
|
|
10
|
-
import type
|
|
10
|
+
import { type Skill } from "../extensibility/skills";
|
|
11
11
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
12
12
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
13
13
|
import type { MCPManager } from "../mcp/manager";
|
|
@@ -73,6 +73,8 @@ export interface ExecutorOptions {
|
|
|
73
73
|
* transition explicitly.
|
|
74
74
|
*/
|
|
75
75
|
parentTelemetry?: AgentTelemetryConfig;
|
|
76
|
+
/** Skills to autoload via sendCustomMessage before the first prompt */
|
|
77
|
+
autoloadSkills?: Skill[];
|
|
76
78
|
}
|
|
77
79
|
export interface YieldItem {
|
|
78
80
|
data?: unknown;
|
|
@@ -148,6 +148,7 @@ export interface AgentDefinition {
|
|
|
148
148
|
thinkingLevel?: ThinkingLevel;
|
|
149
149
|
output?: unknown;
|
|
150
150
|
blocking?: boolean;
|
|
151
|
+
autoloadSkills?: string[];
|
|
151
152
|
source: AgentSource;
|
|
152
153
|
filePath?: string;
|
|
153
154
|
}
|
|
@@ -189,6 +190,30 @@ export interface AgentProgress {
|
|
|
189
190
|
modelOverride?: string | string[];
|
|
190
191
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
191
192
|
extractedToolData?: Record<string, unknown[]>;
|
|
193
|
+
/**
|
|
194
|
+
* Auto-retry state when the subagent is sleeping between provider retries
|
|
195
|
+
* (e.g. 429 rate-limit with retry-after). Cleared when the retry resolves
|
|
196
|
+
* or fails. Surfacing this to the parent prevents the task tool from
|
|
197
|
+
* looking indefinitely "in progress" when a child is actually blocked on
|
|
198
|
+
* provider quota.
|
|
199
|
+
*/
|
|
200
|
+
retryState?: {
|
|
201
|
+
attempt: number;
|
|
202
|
+
maxAttempts: number;
|
|
203
|
+
delayMs: number;
|
|
204
|
+
errorMessage: string;
|
|
205
|
+
startedAtMs: number;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Terminal retry failure surfaced once the subagent gave up retrying
|
|
209
|
+
* (e.g. retry-after exceeded the cap, or all attempts exhausted). Carries
|
|
210
|
+
* the final error so the parent UI can render "blocked: rate-limited"
|
|
211
|
+
* instead of waiting for a status that never arrives.
|
|
212
|
+
*/
|
|
213
|
+
retryFailure?: {
|
|
214
|
+
attempt: number;
|
|
215
|
+
errorMessage: string;
|
|
216
|
+
};
|
|
192
217
|
}
|
|
193
218
|
/** Result from a single agent execution */
|
|
194
219
|
export interface SingleResult {
|
|
@@ -227,6 +252,16 @@ export interface SingleResult {
|
|
|
227
252
|
nestedPatches?: NestedRepoPatch[];
|
|
228
253
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
229
254
|
extractedToolData?: Record<string, unknown[]>;
|
|
255
|
+
/**
|
|
256
|
+
* Terminal retry failure, when the subagent exited because the auto-retry
|
|
257
|
+
* loop gave up (retry-after exceeded the cap, or all attempts exhausted).
|
|
258
|
+
* Lets the parent task tool surface a "blocked: rate-limited" outcome
|
|
259
|
+
* instead of a generic failure.
|
|
260
|
+
*/
|
|
261
|
+
retryFailure?: {
|
|
262
|
+
attempt: number;
|
|
263
|
+
errorMessage: string;
|
|
264
|
+
};
|
|
230
265
|
/** Output metadata for agent:// URL integration */
|
|
231
266
|
outputMeta?: {
|
|
232
267
|
lineCount: number;
|
|
@@ -9,8 +9,3 @@ export interface BashFixupResult {
|
|
|
9
9
|
* or no-op transform, returns the input verbatim with `stripped: []`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function applyBashFixups(command: string): BashFixupResult;
|
|
12
|
-
/**
|
|
13
|
-
* Human-readable notice for the fixups that fired. Mirrors the shape of
|
|
14
|
-
* `formatTimeoutClampNotice` so it can ride alongside the other bash notices.
|
|
15
|
-
*/
|
|
16
|
-
export declare function formatBashFixupNotice(stripped: readonly string[]): string | undefined;
|
|
@@ -13,7 +13,9 @@ export declare function copyToClipboard(text: string): Promise<void>;
|
|
|
13
13
|
* Read an image from the system clipboard.
|
|
14
14
|
*
|
|
15
15
|
* Returns null on Termux (no image clipboard support) or when no display
|
|
16
|
-
* server is available (headless/SSH without forwarding).
|
|
16
|
+
* server is available (headless/SSH without forwarding). Under WSL the
|
|
17
|
+
* Windows clipboard is reached through `powershell.exe`, since WSLg's
|
|
18
|
+
* Wayland clipboard does not carry image payloads through to `arboard`.
|
|
17
19
|
*
|
|
18
20
|
* @returns PNG payload or null when no image is available.
|
|
19
21
|
*/
|
|
@@ -4,6 +4,7 @@ export interface ImageResizeOptions {
|
|
|
4
4
|
maxHeight?: number;
|
|
5
5
|
maxBytes?: number;
|
|
6
6
|
jpegQuality?: number;
|
|
7
|
+
excludeWebP?: boolean;
|
|
7
8
|
}
|
|
8
9
|
export interface ResizedImage {
|
|
9
10
|
buffer: Uint8Array;
|
|
@@ -20,11 +21,13 @@ export interface ResizedImage {
|
|
|
20
21
|
*
|
|
21
22
|
* Strategy:
|
|
22
23
|
* 1. Probe metadata. If already within all limits, return original.
|
|
23
|
-
* 2. Resize to fit max dimensions and encode at high quality across PNG/JPEG
|
|
24
|
+
* 2. Resize to fit max dimensions and encode at high quality across PNG/JPEG (+ WebP) — return smallest.
|
|
24
25
|
* 3. If still too large, walk a lossy JPEG/WebP quality ladder.
|
|
25
26
|
* 4. If still too large, walk a dimension-scale ladder × quality ladder.
|
|
26
27
|
* 5. If still too large, return the smallest variant produced.
|
|
27
28
|
*
|
|
29
|
+
* Set OMP_NO_WEBP to exclude WebP from encoding (llama.cpp STB doesn't decode it).
|
|
30
|
+
*
|
|
28
31
|
* Backed by `Bun.Image`: a chainable native pipeline that runs decode/transform/encode
|
|
29
32
|
* off the JS thread when the terminal (`.bytes()`) is awaited.
|
|
30
33
|
*/
|
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.
|
|
4
|
+
"version": "15.3.0",
|
|
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,12 +47,12 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@babel/parser": "^7.29.3",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/omp-stats": "15.
|
|
51
|
-
"@oh-my-pi/pi-agent-core": "15.
|
|
52
|
-
"@oh-my-pi/pi-ai": "15.
|
|
53
|
-
"@oh-my-pi/pi-natives": "15.
|
|
54
|
-
"@oh-my-pi/pi-tui": "15.
|
|
55
|
-
"@oh-my-pi/pi-utils": "15.
|
|
50
|
+
"@oh-my-pi/omp-stats": "15.3.0",
|
|
51
|
+
"@oh-my-pi/pi-agent-core": "15.3.0",
|
|
52
|
+
"@oh-my-pi/pi-ai": "15.3.0",
|
|
53
|
+
"@oh-my-pi/pi-natives": "15.3.0",
|
|
54
|
+
"@oh-my-pi/pi-tui": "15.3.0",
|
|
55
|
+
"@oh-my-pi/pi-utils": "15.3.0",
|
|
56
56
|
"@puppeteer/browsers": "^2.13.0",
|
|
57
57
|
"@types/turndown": "5.0.6",
|
|
58
58
|
"@xterm/headless": "^6.0.0",
|
|
@@ -81,7 +81,8 @@ export type StatusLineSegmentId =
|
|
|
81
81
|
| "hostname"
|
|
82
82
|
| "cache_read"
|
|
83
83
|
| "cache_write"
|
|
84
|
-
| "session_name"
|
|
84
|
+
| "session_name"
|
|
85
|
+
| "usage";
|
|
85
86
|
|
|
86
87
|
/** Submenu choice metadata. */
|
|
87
88
|
export type SubmenuOption<V extends string = string> = {
|
|
@@ -836,6 +837,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
836
837
|
},
|
|
837
838
|
|
|
838
839
|
"retry.baseDelayMs": { type: "number", default: 2000 },
|
|
840
|
+
"retry.maxDelayMs": {
|
|
841
|
+
type: "number",
|
|
842
|
+
default: 5 * 60 * 1000,
|
|
843
|
+
ui: {
|
|
844
|
+
tab: "model",
|
|
845
|
+
label: "Max Retry Delay",
|
|
846
|
+
description:
|
|
847
|
+
"Maximum wait between retries, in ms. When the provider asks us to wait longer than this and no credential or model fallback succeeds, the request fails fast instead of sleeping (e.g. 3-hour Anthropic rate-limit windows).",
|
|
848
|
+
},
|
|
849
|
+
},
|
|
839
850
|
"retry.fallbackChains": { type: "record", default: {} as Record<string, string[]> },
|
|
840
851
|
"retry.fallbackRevertPolicy": {
|
|
841
852
|
type: "enum",
|
|
@@ -2612,6 +2623,22 @@ export const SETTINGS_SCHEMA = {
|
|
|
2612
2623
|
description: "Use Parallel extract API for URL fetching when credentials are available",
|
|
2613
2624
|
},
|
|
2614
2625
|
},
|
|
2626
|
+
"provider.appendOnlyContext": {
|
|
2627
|
+
type: "enum",
|
|
2628
|
+
values: ["auto", "on", "off"] as const,
|
|
2629
|
+
default: "auto",
|
|
2630
|
+
ui: {
|
|
2631
|
+
tab: "providers",
|
|
2632
|
+
label: "Append-Only Context",
|
|
2633
|
+
description:
|
|
2634
|
+
"Cache system prompt + tool specs and keep an append-only message log so provider prefix caches (DeepSeek, Anthropic) hit at maximum rate. Auto enables for DeepSeek.",
|
|
2635
|
+
options: [
|
|
2636
|
+
{ value: "auto", label: "Auto", description: "Enable for DeepSeek (recommended)" },
|
|
2637
|
+
{ value: "on", label: "On", description: "Always enable append-only context" },
|
|
2638
|
+
{ value: "off", label: "Off", description: "Disable append-only context" },
|
|
2639
|
+
],
|
|
2640
|
+
},
|
|
2641
|
+
},
|
|
2615
2642
|
|
|
2616
2643
|
// Exa
|
|
2617
2644
|
"exa.enabled": {
|
|
@@ -2843,6 +2870,7 @@ export interface RetrySettings {
|
|
|
2843
2870
|
enabled: boolean;
|
|
2844
2871
|
maxRetries: number;
|
|
2845
2872
|
baseDelayMs: number;
|
|
2873
|
+
maxDelayMs: number;
|
|
2846
2874
|
}
|
|
2847
2875
|
|
|
2848
2876
|
export interface MemoriesSettings {
|
package/src/config/settings.ts
CHANGED
|
@@ -856,7 +856,26 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
|
856
856
|
setDefaultTabWidth(value);
|
|
857
857
|
}
|
|
858
858
|
},
|
|
859
|
+
"provider.appendOnlyContext": value => {
|
|
860
|
+
if (typeof value === "string") {
|
|
861
|
+
for (const cb of appendOnlyModeCallbacks) cb(value);
|
|
862
|
+
}
|
|
863
|
+
},
|
|
859
864
|
};
|
|
865
|
+
/** Callbacks invoked when `provider.appendOnlyContext` changes at runtime. */
|
|
866
|
+
const appendOnlyModeCallbacks = new Set<(value: string) => void>();
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Subscribe to append-only mode setting changes.
|
|
870
|
+
* Returns an unsubscribe function. Multiple sessions (main + subagents)
|
|
871
|
+
* can register independently without overwriting each other.
|
|
872
|
+
*/
|
|
873
|
+
export function onAppendOnlyModeChanged(cb: (value: string) => void): () => void {
|
|
874
|
+
appendOnlyModeCallbacks.add(cb);
|
|
875
|
+
return () => {
|
|
876
|
+
appendOnlyModeCallbacks.delete(cb);
|
|
877
|
+
};
|
|
878
|
+
}
|
|
860
879
|
|
|
861
880
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
862
881
|
// Global Singleton
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -211,6 +211,7 @@ export interface ParsedAgentFields {
|
|
|
211
211
|
model?: string[];
|
|
212
212
|
output?: unknown;
|
|
213
213
|
thinkingLevel?: ThinkingLevel;
|
|
214
|
+
autoloadSkills?: string[];
|
|
214
215
|
blocking?: boolean;
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -264,7 +265,10 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
|
|
|
264
265
|
const thinkingLevel = parseThinkingLevel(rawThinkingLevel);
|
|
265
266
|
const model = parseModelList(frontmatter.model);
|
|
266
267
|
const blocking = parseBoolean(frontmatter.blocking);
|
|
267
|
-
|
|
268
|
+
const autoloadSkills = parseArrayOrCSV(frontmatter.autoloadSkills)
|
|
269
|
+
?.map(s => s.trim())
|
|
270
|
+
.filter(Boolean);
|
|
271
|
+
return { name, description, tools, spawns, model, output, thinkingLevel, blocking, autoloadSkills };
|
|
268
272
|
}
|
|
269
273
|
|
|
270
274
|
async function globIf(
|
|
@@ -98,7 +98,16 @@ function rewriteLegacyPiImports(source: string): string {
|
|
|
98
98
|
return match;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
try {
|
|
102
|
+
return `${prefix}${toImportSpecifier(getResolvedSpecifier(remappedSpecifier))}${suffix}`;
|
|
103
|
+
} catch {
|
|
104
|
+
// Resolution failed — typically in compiled binary mode where
|
|
105
|
+
// Bun.resolveSync cannot walk up from /$bunfs/root to find the
|
|
106
|
+
// bundled node_modules. Return the original specifier unchanged so
|
|
107
|
+
// rewriteBareImportsForLegacyExtension can resolve it against the
|
|
108
|
+
// plugin's own installed peer deps instead.
|
|
109
|
+
return match;
|
|
110
|
+
}
|
|
102
111
|
},
|
|
103
112
|
);
|
|
104
113
|
}
|
|
@@ -232,15 +241,28 @@ function getLoader(path: string): "js" | "jsx" | "ts" | "tsx" {
|
|
|
232
241
|
return "js";
|
|
233
242
|
}
|
|
234
243
|
|
|
235
|
-
function resolveLegacyPiSpecifier(args: { path: string }): { path: string } | undefined {
|
|
244
|
+
function resolveLegacyPiSpecifier(args: { path: string; importer: string }): { path: string } | undefined {
|
|
236
245
|
const remappedSpecifier = remapLegacyPiSpecifier(args.path);
|
|
237
246
|
if (!remappedSpecifier) {
|
|
238
247
|
return undefined;
|
|
239
248
|
}
|
|
240
249
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
250
|
+
// Primary: resolve the canonical @oh-my-pi/* specifier from the host binary
|
|
251
|
+
// location. Works in dev mode and in source-link installs.
|
|
252
|
+
try {
|
|
253
|
+
return { path: getResolvedSpecifier(remappedSpecifier) };
|
|
254
|
+
} catch {
|
|
255
|
+
// Fallback for compiled binary mode: the bundled packages live inside
|
|
256
|
+
// /$bunfs/root and aren't reachable by filesystem resolution. Try the
|
|
257
|
+
// original (pre-remap) specifier against the importing file's directory,
|
|
258
|
+
// which resolves to the plugin's installed peer dep.
|
|
259
|
+
const importerDir = path.dirname(args.importer);
|
|
260
|
+
try {
|
|
261
|
+
return { path: Bun.resolveSync(args.path, importerDir) };
|
|
262
|
+
} catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
244
266
|
}
|
|
245
267
|
|
|
246
268
|
function resolveTypeBoxSpecifier(): { path: string } {
|
package/src/goals/runtime.ts
CHANGED
|
@@ -373,6 +373,21 @@ export class GoalRuntime {
|
|
|
373
373
|
await this.#withAccounting(() => this.#flushUsageLocked(steering, currentUsage));
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
#createGoalState(objective: string, tokenBudget: number | undefined): GoalModeState {
|
|
377
|
+
const now = this.#now();
|
|
378
|
+
const goal: Goal = {
|
|
379
|
+
id: String(Snowflake.next()),
|
|
380
|
+
objective,
|
|
381
|
+
status: "active",
|
|
382
|
+
tokenBudget,
|
|
383
|
+
tokensUsed: 0,
|
|
384
|
+
timeUsedSeconds: 0,
|
|
385
|
+
createdAt: now,
|
|
386
|
+
updatedAt: now,
|
|
387
|
+
};
|
|
388
|
+
return { enabled: true, mode: "active", goal };
|
|
389
|
+
}
|
|
390
|
+
|
|
376
391
|
async createGoal(input: { objective: string; tokenBudget?: number }): Promise<GoalModeState> {
|
|
377
392
|
const objective = input.objective.trim();
|
|
378
393
|
if (!objective) throw new Error("objective is required when op=create");
|
|
@@ -382,20 +397,27 @@ export class GoalRuntime {
|
|
|
382
397
|
if (existing?.goal && existing.goal.status !== "dropped" && existing.goal.status !== "complete") {
|
|
383
398
|
throw new Error("cannot create a new goal because this session already has a goal");
|
|
384
399
|
}
|
|
385
|
-
const
|
|
386
|
-
const goal: Goal = {
|
|
387
|
-
id: String(Snowflake.next()),
|
|
388
|
-
objective,
|
|
389
|
-
status: "active",
|
|
390
|
-
tokenBudget: input.tokenBudget,
|
|
391
|
-
tokensUsed: 0,
|
|
392
|
-
timeUsedSeconds: 0,
|
|
393
|
-
createdAt: now,
|
|
394
|
-
updatedAt: now,
|
|
395
|
-
};
|
|
396
|
-
const state: GoalModeState = { enabled: true, mode: "active", goal };
|
|
400
|
+
const state = this.#createGoalState(objective, input.tokenBudget);
|
|
397
401
|
this.#budgetReportedFor = undefined;
|
|
398
|
-
this.#markActiveAccounting(goal);
|
|
402
|
+
this.#markActiveAccounting(state.goal);
|
|
403
|
+
await this.#commitState(state, { persist: "goal" });
|
|
404
|
+
return state;
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async replaceGoal(input: { objective: string; tokenBudget?: number }): Promise<GoalModeState> {
|
|
409
|
+
const objective = input.objective.trim();
|
|
410
|
+
if (!objective) throw new Error("objective is required when op=replace");
|
|
411
|
+
validateTokenBudget(input.tokenBudget);
|
|
412
|
+
return await this.#withAccounting(async () => {
|
|
413
|
+
const existing = this.#host.getState();
|
|
414
|
+
if (!existing?.enabled || !isAccountingStatus(existing.goal)) {
|
|
415
|
+
throw new Error("cannot replace goal because no goal is active");
|
|
416
|
+
}
|
|
417
|
+
await this.#flushUsageLocked("suppressed");
|
|
418
|
+
const state = this.#createGoalState(objective, input.tokenBudget);
|
|
419
|
+
this.#budgetReportedFor = undefined;
|
|
420
|
+
this.#markActiveAccounting(state.goal);
|
|
399
421
|
await this.#commitState(state, { persist: "goal" });
|
|
400
422
|
return state;
|
|
401
423
|
});
|