@oh-my-pi/pi-coding-agent 17.2.7 → 17.2.9
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 +57 -1
- package/dist/{CHANGELOG-1p8yvde7.md → CHANGELOG-8yy8h3bc.md} +57 -1
- package/dist/cli.js +4974 -4968
- package/dist/types/async/job-manager.d.ts +12 -0
- package/dist/types/discovery/agents.d.ts +11 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +10 -0
- package/dist/types/hindsight/state.d.ts +0 -1
- package/dist/types/lsp/utils.d.ts +8 -0
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/registry/agent-lifecycle.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +5 -3
- package/dist/types/session/turn-recovery.d.ts +6 -2
- package/dist/types/task/executor.d.ts +4 -0
- package/dist/types/tools/browser/launch.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/shell-tokenize.d.ts +21 -3
- package/dist/types/utils/changelog.d.ts +3 -2
- package/dist/types/utils/late-cleanup.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +2 -0
- package/package.json +13 -13
- package/scripts/omp +11 -2
- package/src/async/job-manager.ts +26 -0
- package/src/cli/stats-cli.ts +3 -3
- package/src/cli/update-cli.ts +2 -56
- package/src/config/config-file.ts +2 -2
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-discovery.ts +36 -2
- package/src/config/model-registry.ts +4 -1
- package/src/discovery/agents.ts +71 -3
- package/src/discovery/claude.ts +8 -5
- package/src/discovery/cursor.ts +8 -5
- package/src/discovery/gemini.ts +11 -8
- package/src/discovery/vscode.ts +1 -0
- package/src/discovery/windsurf.ts +4 -2
- package/src/edit/hashline/filesystem.ts +7 -7
- package/src/eval/agent-bridge.ts +1 -3
- package/src/eval/jl/prelude.jl +4 -4
- package/src/eval/js/shared/prelude.txt +2 -2
- package/src/eval/py/prelude.py +0 -3
- package/src/eval/rb/prelude.rb +1 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +23 -0
- package/src/hindsight/state.ts +2 -22
- package/src/internal-urls/memory-protocol.ts +29 -0
- package/src/lsp/client.ts +2 -2
- package/src/lsp/utils.ts +29 -0
- package/src/modes/acp/acp-agent.ts +2 -1
- package/src/modes/components/agent-hub.ts +36 -20
- package/src/modes/components/agent-transcript-viewer.ts +1 -3
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/custom-editor.ts +2 -2
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/session-selector.ts +2 -2
- package/src/modes/controllers/event-controller.ts +159 -2
- package/src/modes/controllers/extension-ui-controller.ts +2 -1
- package/src/modes/print-mode.ts +29 -12
- package/src/modes/session-observer-registry.ts +5 -0
- package/src/registry/agent-lifecycle.ts +21 -3
- package/src/sdk.ts +11 -5
- package/src/session/agent-session-types.ts +8 -0
- package/src/session/agent-session.ts +156 -186
- package/src/session/session-paths.ts +68 -121
- package/src/session/settings-stream-fn.ts +7 -3
- package/src/session/turn-recovery.ts +234 -16
- package/src/session/unexpected-stop-classifier.ts +12 -3
- package/src/slash-commands/helpers/stats-dashboard.ts +2 -1
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +105 -29
- package/src/task/isolation-runner.ts +19 -1
- package/src/task/structured-subagent.ts +17 -2
- package/src/tiny/worker.ts +1 -1
- package/src/tools/bash-interceptor.ts +8 -4
- package/src/tools/browser/launch.ts +37 -9
- package/src/tools/glob.ts +4 -2
- package/src/tools/path-utils.ts +13 -3
- package/src/tools/read.ts +6 -0
- package/src/tools/shell-tokenize.ts +38 -9
- package/src/utils/changelog.ts +5 -4
- package/src/utils/late-cleanup.ts +17 -0
- package/src/vibe/runtime.ts +197 -53
- package/src/web/scrapers/hackage.ts +1 -12
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/ecosia.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/firecrawl.ts +1 -1
- package/src/web/search/providers/mojeek.ts +1 -1
- package/src/web/search/providers/perplexity.ts +1 -1
- package/src/web/search/providers/public.ts +1 -1
- package/src/web/search/providers/startpage.ts +1 -1
|
@@ -51,6 +51,11 @@ export interface AsyncJobDeliveryState {
|
|
|
51
51
|
nextRetryAt?: number;
|
|
52
52
|
pendingJobIds: string[];
|
|
53
53
|
}
|
|
54
|
+
export interface AsyncJobReapResult {
|
|
55
|
+
settled: boolean;
|
|
56
|
+
pendingJobIds: string[];
|
|
57
|
+
completion: Promise<void>;
|
|
58
|
+
}
|
|
54
59
|
export interface AsyncJobRegisterOptions {
|
|
55
60
|
id?: string;
|
|
56
61
|
/** Registry id of the agent that owns this job; used to scope cancelAll. */
|
|
@@ -167,6 +172,13 @@ export declare class AsyncJobManager {
|
|
|
167
172
|
timeoutMs?: number;
|
|
168
173
|
excludeSuppressed?: boolean;
|
|
169
174
|
}): Promise<boolean>;
|
|
175
|
+
/**
|
|
176
|
+
* Cancel every job owned by `ownerId`, then wait only until `deadlineAt`.
|
|
177
|
+
* The returned completion keeps waiting for actual process settlement when
|
|
178
|
+
* the deadline expires, so callers can move that cleanup out of the
|
|
179
|
+
* user-visible Task wait without losing ownership of the live work.
|
|
180
|
+
*/
|
|
181
|
+
cancelAndReapOwnerJobs(ownerId: string, deadlineAt: number): Promise<AsyncJobReapResult>;
|
|
170
182
|
drainDeliveries(options?: {
|
|
171
183
|
timeoutMs?: number;
|
|
172
184
|
filter?: AsyncJobFilter;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { LoadContext } from "../capability/types.js";
|
|
2
|
+
interface UserPathCandidateOptions {
|
|
3
|
+
platform?: NodeJS.Platform;
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
windowsUserProfile?: () => string | undefined;
|
|
6
|
+
wslPath?: (windowsPath: string) => string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/** Resolve the Windows host profile home exposed to WSL, if available. */
|
|
9
|
+
export declare function getWslWindowsHomeCandidate(options?: UserPathCandidateOptions): string | undefined;
|
|
10
|
+
/** User-level paths: ~/.agent[s]/<segments>, plus the Windows host profile under WSL. */
|
|
11
|
+
export declare function getUserPathCandidates(ctx: LoadContext, ...segments: string[]): string[];
|
|
2
12
|
/**
|
|
3
13
|
* Project-level paths: walk up from cwd to repoRoot, returning `.agent/<segments>`
|
|
4
14
|
* and `.agents/<segments>` at each ancestor.
|
|
@@ -10,3 +20,4 @@ import type { LoadContext } from "../capability/types.js";
|
|
|
10
20
|
* directory — see https://github.com/can1357/oh-my-pi/issues/1116.
|
|
11
21
|
*/
|
|
12
22
|
export declare function getProjectPathCandidates(ctx: LoadContext, ...segments: string[]): string[];
|
|
23
|
+
export {};
|
|
@@ -92,6 +92,16 @@ export interface WriteOperations {
|
|
|
92
92
|
export interface WriteToolOptions {
|
|
93
93
|
operations?: WriteOperations;
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Convert an image attachment to PNG using the legacy package-root contract.
|
|
97
|
+
*
|
|
98
|
+
* Invalid or unsupported image data returns `null`, matching Pi's historical
|
|
99
|
+
* helper instead of surfacing Bun's decoder error to extensions.
|
|
100
|
+
*/
|
|
101
|
+
export declare function convertToPng(base64Data: string, mimeType: string): Promise<{
|
|
102
|
+
data: string;
|
|
103
|
+
mimeType: string;
|
|
104
|
+
} | null>;
|
|
95
105
|
/** Format the active shortcut for legacy extensions that render keybinding hints. */
|
|
96
106
|
export declare function keyText(action: Keybinding): string;
|
|
97
107
|
/** Parse frontmatter using the historical Pi package-root helper. */
|
|
@@ -93,7 +93,6 @@ export declare class HindsightSessionState {
|
|
|
93
93
|
retainSession(messages: HindsightMessage[]): Promise<void>;
|
|
94
94
|
maybeRetainOnAgentEnd(): Promise<void>;
|
|
95
95
|
forceRetainCurrentSession(): Promise<void>;
|
|
96
|
-
maybeRecallOnAgentStart(): Promise<void>;
|
|
97
96
|
beforeAgentStartPrompt(promptText: string): Promise<string | undefined>;
|
|
98
97
|
recallForCompaction(messages: HindsightMessage[]): Promise<string | undefined>;
|
|
99
98
|
runMentalModelLoad(scope: BankScope): Promise<void>;
|
|
@@ -14,6 +14,14 @@ export declare function fileToUri(filePath: string): string;
|
|
|
14
14
|
* Handles Windows drive letters correctly.
|
|
15
15
|
*/
|
|
16
16
|
export declare function uriToFile(uri: string): string;
|
|
17
|
+
/** Map that treats equivalent file URI spellings as the same key. */
|
|
18
|
+
export declare class EquivalentUriMap<Value> extends Map<string, Value> {
|
|
19
|
+
#private;
|
|
20
|
+
delete(uri: string): boolean;
|
|
21
|
+
get(uri: string): Value | undefined;
|
|
22
|
+
has(uri: string): boolean;
|
|
23
|
+
set(uri: string, value: Value): this;
|
|
24
|
+
}
|
|
17
25
|
/**
|
|
18
26
|
* Convert diagnostic severity number to string name.
|
|
19
27
|
*/
|
|
@@ -28,6 +28,8 @@ export declare class SessionObserverRegistry {
|
|
|
28
28
|
/** Add a change listener. Returns unsubscribe function. */
|
|
29
29
|
onChange(cb: (kind: SessionObserverChangeKind) => void): () => void;
|
|
30
30
|
setMainSession(sessionFile?: string): void;
|
|
31
|
+
/** Return one tracked session without copying or sorting the registry. */
|
|
32
|
+
getSession(id: string): ObservableSession | undefined;
|
|
31
33
|
getSessions(): ObservableSession[];
|
|
32
34
|
getActiveSubagentCount(): number;
|
|
33
35
|
/** Clear all tracked sessions (e.g. on session switch). Keeps EventBus subscriptions and listeners. */
|
|
@@ -65,6 +65,13 @@ export interface UsageFallbackConfirmation {
|
|
|
65
65
|
to: string;
|
|
66
66
|
remainingPercent: number | undefined;
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Confirms whether a reserve-triggered model fallback may proceed.
|
|
70
|
+
*
|
|
71
|
+
* Interactive callers use the confirmation details to present the pending
|
|
72
|
+
* route change; aborting `signal` cancels that pending confirmation.
|
|
73
|
+
*/
|
|
74
|
+
export type UsageFallbackConfirmer = (confirmation: UsageFallbackConfirmation, signal: AbortSignal) => Promise<boolean>;
|
|
68
75
|
/** Identifies a retry fallback chain already entered during startup model resolution. */
|
|
69
76
|
export interface InitialRetryFallbackState {
|
|
70
77
|
/** Role whose configured primary was unavailable. */
|
|
@@ -20,7 +20,7 @@ import { type Effort } from "@oh-my-pi/pi-ai";
|
|
|
20
20
|
import { type AdvisorConfig, type AdvisorRuntimeStatus } from "../advisor/index.js";
|
|
21
21
|
import { AsyncJobManager } from "../async/index.js";
|
|
22
22
|
import type { ModelRegistry } from "../config/model-registry.js";
|
|
23
|
-
import {
|
|
23
|
+
import type { ResolvedModelRoleValue } from "../config/model-resolver.js";
|
|
24
24
|
import { type PromptTemplate } from "../config/prompt-templates.js";
|
|
25
25
|
import type { Settings, SkillsSettings } from "../config/settings.js";
|
|
26
26
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer.js";
|
|
@@ -50,7 +50,7 @@ import type { TodoPhase } from "../tools/todo.js";
|
|
|
50
50
|
import type { InspectImageMode } from "../utils/inspect-image-mode.js";
|
|
51
51
|
import type { VibeModeState } from "../vibe/state.js";
|
|
52
52
|
import type { AgentSessionEventListener } from "./agent-session-events.js";
|
|
53
|
-
import type { AgentSessionConfig, AgentSessionDisposeOptions, AsyncJobSnapshot, CommandMetadataChangedListener, ContextUsageBreakdown, FollowUpOptions, FreshSessionResult, HandoffResult, ModelCycleResult, Prewalk, PromptOptions, ResetSessionContextResult, ResolvedRoleModel, RestoredQueuedMessage, RoleModelCycle, RoleModelCycleResult, SessionHandoffOptions, SessionOAuthAccountList, SessionStats,
|
|
53
|
+
import type { AgentSessionConfig, AgentSessionDisposeOptions, AsyncJobSnapshot, CommandMetadataChangedListener, ContextUsageBreakdown, FollowUpOptions, FreshSessionResult, HandoffResult, ModelCycleResult, Prewalk, PromptOptions, ResetSessionContextResult, ResolvedRoleModel, RestoredQueuedMessage, RoleModelCycle, RoleModelCycleResult, SessionHandoffOptions, SessionOAuthAccountList, SessionStats, UsageFallbackConfirmer } from "./agent-session-types.js";
|
|
54
54
|
import type { ClientBridge } from "./client-bridge.js";
|
|
55
55
|
import { type CustomMessage, type CustomMessagePayload } from "./messages.js";
|
|
56
56
|
import { type AdvisorStats } from "./session-advisors.js";
|
|
@@ -228,7 +228,7 @@ export declare class AgentSession {
|
|
|
228
228
|
/** Resolved selector while retry routing is using a fallback model. */
|
|
229
229
|
get retryFallbackModel(): string | undefined;
|
|
230
230
|
/** Install the interactive decision surface for reserve-triggered model changes. */
|
|
231
|
-
setUsageFallbackConfirmer(confirmer:
|
|
231
|
+
setUsageFallbackConfirmer(confirmer: UsageFallbackConfirmer | undefined): void;
|
|
232
232
|
/** Effective thinking level applied to the agent (the resolved level when `auto`). */
|
|
233
233
|
get thinkingLevel(): ThinkingLevel | undefined;
|
|
234
234
|
/** The selector the user configured: `auto` when auto mode is active, else the effective level. */
|
|
@@ -267,6 +267,8 @@ export declare class AgentSession {
|
|
|
267
267
|
getLastAssistantMessage(): AssistantMessage | undefined;
|
|
268
268
|
/** Current effective system prompt blocks (includes any per-turn extension modifications) */
|
|
269
269
|
get systemPrompt(): string[];
|
|
270
|
+
/** Marks streamed text as committed or buffered for turn-recovery replay decisions. */
|
|
271
|
+
setTextOutputCommitted(committed: boolean): void;
|
|
270
272
|
/** Current retry attempt (0 if not retrying) */
|
|
271
273
|
get retryAttempt(): number;
|
|
272
274
|
/** Names of tools currently exposed at the top level. */
|
|
@@ -4,7 +4,7 @@ import type { ModelRegistry } from "../config/model-registry.js";
|
|
|
4
4
|
import type { Settings } from "../config/settings.js";
|
|
5
5
|
import { type ConfiguredThinkingLevel } from "../thinking.js";
|
|
6
6
|
import type { AgentSessionEvent } from "./agent-session-events.js";
|
|
7
|
-
import type { InitialRetryFallbackState } from "./agent-session-types.js";
|
|
7
|
+
import type { InitialRetryFallbackState, UsageFallbackConfirmer } from "./agent-session-types.js";
|
|
8
8
|
import { type RetryFallbackSelector } from "./retry-fallback-chains.js";
|
|
9
9
|
import type { SessionManager } from "./session-manager.js";
|
|
10
10
|
/** Result shape shared with automatic maintenance recovery. */
|
|
@@ -22,6 +22,8 @@ export interface TurnRecoveryHost {
|
|
|
22
22
|
modelRegistry: ModelRegistry;
|
|
23
23
|
configWarnings: string[];
|
|
24
24
|
model(): Model | undefined;
|
|
25
|
+
/** Whether streamed text has already been committed to the active output sink. */
|
|
26
|
+
textOutputCommitted(): boolean;
|
|
25
27
|
thinkingLevel(): ThinkingLevel | undefined;
|
|
26
28
|
configuredThinkingLevel(): ConfiguredThinkingLevel | undefined;
|
|
27
29
|
setThinkingLevel(level: ConfiguredThinkingLevel | undefined): void;
|
|
@@ -100,6 +102,8 @@ export declare class TurnRecovery {
|
|
|
100
102
|
}): Promise<RecoveryCompactionResult>;
|
|
101
103
|
/** Restores the configured primary after fallback cooldown expiry. */
|
|
102
104
|
maybeRestoreRetryFallbackPrimary(): Promise<void>;
|
|
105
|
+
/** Applies model fallback policy from live usage health before a turn starts. */
|
|
106
|
+
maybeApplyUsageAwareFallback(signal: AbortSignal, confirmer?: UsageFallbackConfirmer): Promise<boolean>;
|
|
103
107
|
/** Applies automatic retry, credential rotation, and model fallback policy. */
|
|
104
108
|
handleRetryableError(message: AssistantMessage, options?: {
|
|
105
109
|
allowModelFallback?: boolean;
|
|
@@ -181,7 +185,7 @@ export declare class TurnRecovery {
|
|
|
181
185
|
pinFallback?: boolean;
|
|
182
186
|
apiKey?: string;
|
|
183
187
|
signal?: AbortSignal;
|
|
184
|
-
}): Promise<
|
|
188
|
+
}): Promise<boolean>;
|
|
185
189
|
/**
|
|
186
190
|
* True when the current turn failed on a Fireworks Fast (`-fast`) model in a
|
|
187
191
|
* way that should degrade to the reliable base (Standard) model. Fast is a
|
|
@@ -209,6 +209,8 @@ export interface ExecutorOptions {
|
|
|
209
209
|
* set this false so disposal unregisters them instead of leaving idle peers.
|
|
210
210
|
*/
|
|
211
211
|
keepAlive?: boolean;
|
|
212
|
+
/** Internal ownership handoff for cleanup that outlives the visible Task result. */
|
|
213
|
+
onCleanupDeferred?: (completion: Promise<void>) => void;
|
|
212
214
|
}
|
|
213
215
|
interface FinalizeSubprocessOutputArgs {
|
|
214
216
|
rawOutput: string;
|
|
@@ -294,6 +296,8 @@ export declare function finalizeSubagentLifecycle(args: {
|
|
|
294
296
|
isolated: boolean;
|
|
295
297
|
agentIdleTtlMs: number;
|
|
296
298
|
reviveSession: AgentReviver | null;
|
|
299
|
+
cleanupDeadlineAt?: number;
|
|
300
|
+
onCleanupDeferred?: (completion: Promise<void>) => void;
|
|
297
301
|
}): Promise<void>;
|
|
298
302
|
/** Options for {@link runSubagentFollowUpTurn}. */
|
|
299
303
|
export interface FollowUpTurnOptions {
|
|
@@ -115,6 +115,8 @@ export declare function applyStealthPatches(browser: Browser, page: Page, state:
|
|
|
115
115
|
browserSession: CDPSession | null;
|
|
116
116
|
override: UserAgentOverride | null;
|
|
117
117
|
}): Promise<void>;
|
|
118
|
+
/** Exposes executable candidates for detection tests. */
|
|
119
|
+
export declare function systemChromiumCandidatesForTest(platform?: NodeJS.Platform, home?: string, which?: (name: string) => string | undefined): string[];
|
|
118
120
|
export declare function stealthIgnoreDefaultArgsForTest(executablePath: string | undefined): string[];
|
|
119
121
|
export declare function targetSupportsUserAgentOverrideForTest(target: Target): boolean;
|
|
120
122
|
export declare function configureUserAgentTargetsForTest(browser: Browser, state: {
|
|
@@ -44,6 +44,13 @@ export declare function splitPathAndSel(rawPath: string): {
|
|
|
44
44
|
* plus selector `1-2` (issue #4618). `lstat` inspects the entry itself, so a
|
|
45
45
|
* dangling symlink is still detected as present; ambiguous errors resolve to
|
|
46
46
|
* `"unknown"` so callers keep the raw path instead of guessing.
|
|
47
|
+
*
|
|
48
|
+
* `ENAMETOOLONG` resolves to `"missing"` rather than `"unknown"`: a path whose
|
|
49
|
+
* component or whole length exceeds the OS limit can never name a real single
|
|
50
|
+
* entry, so it is strictly stronger evidence of non-existence than `ENOENT`.
|
|
51
|
+
* Without this, a semicolon-joined `path` list long enough to trip the limit
|
|
52
|
+
* (bare filenames past `NAME_MAX`, or a total past `PATH_MAX`) was read as one
|
|
53
|
+
* literal path and the delimited split was suppressed (issue #7597).
|
|
47
54
|
*/
|
|
48
55
|
export declare function probeLiteralPathExists(filePath: string, cwd: string): Promise<"exists" | "missing" | "unknown">;
|
|
49
56
|
/**
|
|
@@ -28,6 +28,8 @@ export interface ReadToolDetails {
|
|
|
28
28
|
meta?: OutputMeta;
|
|
29
29
|
/** Full on-disk byte size recorded before applying a file range. */
|
|
30
30
|
fileSize?: number;
|
|
31
|
+
/** Full source line count when the read reached EOF and the count is exact. */
|
|
32
|
+
totalLines?: number;
|
|
31
33
|
/** Raw text + start line for user-visible TUI rendering, set when content is text-like.
|
|
32
34
|
* Mirrors the same lines the model receives but without hashline/line-number prefixes,
|
|
33
35
|
* so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
|
|
@@ -13,13 +13,31 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function tokenizeShellSegments(command: string): string[][];
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* A flat shell command segment with the context needed to decide interception.
|
|
17
|
+
*
|
|
18
|
+
* @see extractFlatShellCommandSegments
|
|
19
|
+
*/
|
|
20
|
+
export interface FlatShellCommandSegment {
|
|
21
|
+
/** Original segment text with quoting and escaping preserved. */
|
|
22
|
+
text: string;
|
|
23
|
+
/**
|
|
24
|
+
* True when this segment consumes the previous stage's stdout via an
|
|
25
|
+
* unquoted `|` or `|&`. Blank and comment-only continuation lines preserve
|
|
26
|
+
* the pending pipe state. Such a stage reads piped stdin, so path-based
|
|
27
|
+
* dedicated tools (read/grep/glob) cannot replace it. `||`, `;`, `&`, and
|
|
28
|
+
* `&&` start an independent command and leave this false.
|
|
29
|
+
*/
|
|
30
|
+
pipedStdin: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the flat shell command segments with the original text of each. Unlike
|
|
17
34
|
* `tokenizeShellSegments`, this preserves quoting and escaping so the results
|
|
18
|
-
* are safe to match against user-configured regular expressions
|
|
35
|
+
* are safe to match against user-configured regular expressions, and flags
|
|
36
|
+
* segments that receive piped stdin.
|
|
19
37
|
*
|
|
20
38
|
* The extractor deliberately declines to split syntax whose execution context
|
|
21
39
|
* cannot be determined with this small scanner (heredocs, command substitution,
|
|
22
40
|
* backticks, grouping, and malformed quoting). Callers must still check the
|
|
23
41
|
* complete input in that case.
|
|
24
42
|
*/
|
|
25
|
-
export declare function extractFlatShellCommandSegments(command: string):
|
|
43
|
+
export declare function extractFlatShellCommandSegments(command: string): FlatShellCommandSegment[];
|
|
@@ -39,9 +39,10 @@ export declare function formatStartupChangelogSummary(selection: StartupChangelo
|
|
|
39
39
|
export declare function resolveBundledChangelogPath(assetPath: string, moduleUrl: string | URL): string | URL;
|
|
40
40
|
export declare function parseChangelog(changelogPath: string | undefined): Promise<ChangelogEntry[]>;
|
|
41
41
|
/**
|
|
42
|
-
* Compare
|
|
42
|
+
* Compare changelog entries by their parsed version parts.
|
|
43
|
+
* Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
43
44
|
*/
|
|
44
|
-
export declare function
|
|
45
|
+
export declare function compareChangelogEntries(v1: ChangelogEntry, v2: ChangelogEntry): number;
|
|
45
46
|
/**
|
|
46
47
|
* Parse an omp changelog marker version into comparable parts.
|
|
47
48
|
*/
|
|
@@ -113,6 +113,8 @@ export declare class VibeSessionRegistry {
|
|
|
113
113
|
ownerId: string;
|
|
114
114
|
state?: VibeSessionState;
|
|
115
115
|
}): void;
|
|
116
|
+
/** Override the teardown grace period for deterministic lifecycle tests. */
|
|
117
|
+
setTeardownGraceForTesting(timeoutMs: number): void;
|
|
116
118
|
ownerScope(session: VibeParentSession): VibeOwnerScope;
|
|
117
119
|
/** Re-open spawn admission after an explicit Vibe-mode entry. */
|
|
118
120
|
activateScope(scope: VibeOwnerScope): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "17.2.
|
|
4
|
+
"version": "17.2.9",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -52,18 +52,18 @@
|
|
|
52
52
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
53
53
|
"@babel/parser": "^7.29.7",
|
|
54
54
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@oh-my-pi/hashline": "17.2.
|
|
56
|
-
"@oh-my-pi/omp-stats": "17.2.
|
|
57
|
-
"@oh-my-pi/omptype": "17.2.
|
|
58
|
-
"@oh-my-pi/pi-agent-core": "17.2.
|
|
59
|
-
"@oh-my-pi/pi-ai": "17.2.
|
|
60
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
61
|
-
"@oh-my-pi/pi-mnemopi": "17.2.
|
|
62
|
-
"@oh-my-pi/pi-natives": "17.2.
|
|
63
|
-
"@oh-my-pi/pi-tui": "17.2.
|
|
64
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
65
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
66
|
-
"@oh-my-pi/snapcompact": "17.2.
|
|
55
|
+
"@oh-my-pi/hashline": "17.2.9",
|
|
56
|
+
"@oh-my-pi/omp-stats": "17.2.9",
|
|
57
|
+
"@oh-my-pi/omptype": "17.2.9",
|
|
58
|
+
"@oh-my-pi/pi-agent-core": "17.2.9",
|
|
59
|
+
"@oh-my-pi/pi-ai": "17.2.9",
|
|
60
|
+
"@oh-my-pi/pi-catalog": "17.2.9",
|
|
61
|
+
"@oh-my-pi/pi-mnemopi": "17.2.9",
|
|
62
|
+
"@oh-my-pi/pi-natives": "17.2.9",
|
|
63
|
+
"@oh-my-pi/pi-tui": "17.2.9",
|
|
64
|
+
"@oh-my-pi/pi-utils": "17.2.9",
|
|
65
|
+
"@oh-my-pi/pi-wire": "17.2.9",
|
|
66
|
+
"@oh-my-pi/snapcompact": "17.2.9",
|
|
67
67
|
"@opentelemetry/api": "^1.9.1",
|
|
68
68
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
69
69
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
package/scripts/omp
CHANGED
|
@@ -36,7 +36,16 @@ mkdir -p "$launch_dir"
|
|
|
36
36
|
OMP_LAUNCH_CWD=$PWD
|
|
37
37
|
export OMP_LAUNCH_CWD
|
|
38
38
|
cd "$launch_dir"
|
|
39
|
+
# Herdr 0.7.5 validates the foreground process name. The macOS shell can
|
|
40
|
+
# preserve OMP's identity while still running the development CLI through Bun.
|
|
41
|
+
run_bun() {
|
|
42
|
+
if [ "${HERDR_ENV:-}" = 1 ] && [ "$(uname -s)" = Darwin ]; then
|
|
43
|
+
exec -a omp bun "$@"
|
|
44
|
+
fi
|
|
45
|
+
exec bun "$@"
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
if [ -n "${PI_TIMING:-}" ]; then
|
|
40
|
-
|
|
49
|
+
run_bun --preload "$preload" --preload "$timing_preload" "$cli" "$@"
|
|
41
50
|
fi
|
|
42
|
-
|
|
51
|
+
run_bun --preload "$preload" "$cli" "$@"
|
package/src/async/job-manager.ts
CHANGED
|
@@ -94,6 +94,12 @@ export interface AsyncJobDeliveryState {
|
|
|
94
94
|
pendingJobIds: string[];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
export interface AsyncJobReapResult {
|
|
98
|
+
settled: boolean;
|
|
99
|
+
pendingJobIds: string[];
|
|
100
|
+
completion: Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
export interface AsyncJobRegisterOptions {
|
|
98
104
|
id?: string;
|
|
99
105
|
/** Registry id of the agent that owns this job; used to scope cancelAll. */
|
|
@@ -490,6 +496,26 @@ export class AsyncJobManager {
|
|
|
490
496
|
}
|
|
491
497
|
}
|
|
492
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Cancel every job owned by `ownerId`, then wait only until `deadlineAt`.
|
|
501
|
+
* The returned completion keeps waiting for actual process settlement when
|
|
502
|
+
* the deadline expires, so callers can move that cleanup out of the
|
|
503
|
+
* user-visible Task wait without losing ownership of the live work.
|
|
504
|
+
*/
|
|
505
|
+
async cancelAndReapOwnerJobs(ownerId: string, deadlineAt: number): Promise<AsyncJobReapResult> {
|
|
506
|
+
this.cancelAll({ ownerId });
|
|
507
|
+
const timeoutMs = Math.max(0, deadlineAt - Date.now());
|
|
508
|
+
const settled = await this.waitForOwnerJobs(ownerId, { timeoutMs });
|
|
509
|
+
if (settled) {
|
|
510
|
+
return { settled: true, pendingJobIds: [], completion: Promise.resolve() };
|
|
511
|
+
}
|
|
512
|
+
const pendingJobIds = this.getAllJobs({ ownerId })
|
|
513
|
+
.filter(job => job.status === "running" || job.status === "cancelled")
|
|
514
|
+
.map(job => job.id);
|
|
515
|
+
const completion = this.waitForOwnerJobs(ownerId).then(() => {});
|
|
516
|
+
return { settled: false, pendingJobIds, completion };
|
|
517
|
+
}
|
|
518
|
+
|
|
493
519
|
async #waitForAllUntil(deadline: number): Promise<boolean> {
|
|
494
520
|
const promises = Array.from(this.#jobs.values()).map(job => job.promise);
|
|
495
521
|
if (promises.length === 0) return true;
|
package/src/cli/stats-cli.ts
CHANGED
|
@@ -136,11 +136,11 @@ export async function runStatsCommand(cmd: StatsCommandArgs): Promise<void> {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// Start the dashboard server
|
|
139
|
-
const { port } = await startServer(cmd.port);
|
|
140
|
-
|
|
139
|
+
const { hostname, port } = await startServer(cmd.port);
|
|
140
|
+
const url = `http://${hostname}:${port}`;
|
|
141
|
+
console.log(chalk.green(`Dashboard available at: ${url}`));
|
|
141
142
|
|
|
142
143
|
// Open browser
|
|
143
|
-
const url = `http://localhost:${port}`;
|
|
144
144
|
openPath(url);
|
|
145
145
|
|
|
146
146
|
console.log("Press Ctrl+C to stop\n");
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -10,7 +10,7 @@ import * as os from "node:os";
|
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { Transform } from "node:stream";
|
|
12
12
|
import { pipeline } from "node:stream/promises";
|
|
13
|
-
import { $env, $which, APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import { $env, $which, APP_NAME, compareVersions, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
|
|
14
14
|
import { $ } from "bun";
|
|
15
15
|
import chalk from "chalk";
|
|
16
16
|
import { theme } from "../modes/theme/theme";
|
|
@@ -498,24 +498,6 @@ async function getLatestRelease(): Promise<ReleaseInfo> {
|
|
|
498
498
|
};
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
/**
|
|
502
|
-
* Compare semver versions. Returns:
|
|
503
|
-
* - negative if a < b
|
|
504
|
-
* - 0 if a == b
|
|
505
|
-
* - positive if a > b
|
|
506
|
-
*/
|
|
507
|
-
function compareVersions(a: string, b: string): number {
|
|
508
|
-
const pa = a.split(".").map(Number);
|
|
509
|
-
const pb = b.split(".").map(Number);
|
|
510
|
-
|
|
511
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
512
|
-
const na = pa[i] || 0;
|
|
513
|
-
const nb = pb[i] || 0;
|
|
514
|
-
if (na !== nb) return na - nb;
|
|
515
|
-
}
|
|
516
|
-
return 0;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
501
|
interface BunInstallCachePruneResult {
|
|
520
502
|
scannedPackages: number;
|
|
521
503
|
removedEntries: number;
|
|
@@ -532,42 +514,6 @@ function stripBunCacheVersionSuffix(name: string): string {
|
|
|
532
514
|
return metadataIndex === -1 ? name : name.slice(0, metadataIndex);
|
|
533
515
|
}
|
|
534
516
|
|
|
535
|
-
function compareSemverIdentifier(a: string, b: string): number {
|
|
536
|
-
const aNumber = /^\d+$/.test(a);
|
|
537
|
-
const bNumber = /^\d+$/.test(b);
|
|
538
|
-
if (aNumber && bNumber) return Number(a) - Number(b);
|
|
539
|
-
if (aNumber) return -1;
|
|
540
|
-
if (bNumber) return 1;
|
|
541
|
-
return a.localeCompare(b);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function compareSemverLikeVersions(a: string, b: string): number {
|
|
545
|
-
const [aCoreWithPrerelease] = a.split("+", 1);
|
|
546
|
-
const [bCoreWithPrerelease] = b.split("+", 1);
|
|
547
|
-
const [aCore, aPrerelease] = aCoreWithPrerelease.split("-", 2);
|
|
548
|
-
const [bCore, bPrerelease] = bCoreWithPrerelease.split("-", 2);
|
|
549
|
-
const aParts = aCore.split(".");
|
|
550
|
-
const bParts = bCore.split(".");
|
|
551
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
552
|
-
const diff = Number(aParts[i] ?? 0) - Number(bParts[i] ?? 0);
|
|
553
|
-
if (diff !== 0 && Number.isFinite(diff)) return diff;
|
|
554
|
-
}
|
|
555
|
-
if (!aPrerelease && !bPrerelease) return 0;
|
|
556
|
-
if (!aPrerelease) return 1;
|
|
557
|
-
if (!bPrerelease) return -1;
|
|
558
|
-
const aPrereleaseParts = aPrerelease.split(".");
|
|
559
|
-
const bPrereleaseParts = bPrerelease.split(".");
|
|
560
|
-
for (let i = 0; i < Math.max(aPrereleaseParts.length, bPrereleaseParts.length); i++) {
|
|
561
|
-
const aPart = aPrereleaseParts[i];
|
|
562
|
-
const bPart = bPrereleaseParts[i];
|
|
563
|
-
if (aPart === undefined) return -1;
|
|
564
|
-
if (bPart === undefined) return 1;
|
|
565
|
-
const diff = compareSemverIdentifier(aPart, bPart);
|
|
566
|
-
if (diff !== 0) return diff;
|
|
567
|
-
}
|
|
568
|
-
return 0;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
517
|
async function readdirIfExists(dir: string): Promise<fs.Dirent[]> {
|
|
572
518
|
try {
|
|
573
519
|
return await fs.promises.readdir(dir, { withFileTypes: true });
|
|
@@ -689,7 +635,7 @@ export async function pruneBunInstallCache(
|
|
|
689
635
|
scannedPackages++;
|
|
690
636
|
let latestVersion: string | undefined;
|
|
691
637
|
for (const version of group.actualDirs.keys()) {
|
|
692
|
-
if (!latestVersion ||
|
|
638
|
+
if (!latestVersion || compareVersions(version, latestVersion) > 0) latestVersion = version;
|
|
693
639
|
}
|
|
694
640
|
if (!latestVersion) continue;
|
|
695
641
|
for (const [version, paths] of group.actualDirs) {
|
|
@@ -109,11 +109,11 @@ export class ConfigError extends Error {
|
|
|
109
109
|
this.#message = message;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
get message(): string {
|
|
112
|
+
override get message(): string {
|
|
113
113
|
return this.#message;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
toString(): string {
|
|
116
|
+
override toString(): string {
|
|
117
117
|
return this.message;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -607,12 +607,12 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
607
607
|
this.setUserBindings(mergeKeybindingsConfig(inheritedConfig, profileConfig));
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
-
setUserBindings(userBindings: KeybindingsConfig): void {
|
|
610
|
+
override setUserBindings(userBindings: KeybindingsConfig): void {
|
|
611
611
|
this.#userBindings = userBindings;
|
|
612
612
|
super.setUserBindings(userBindings);
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
-
getKeys(keybinding: Keybinding): KeyId[] {
|
|
615
|
+
override getKeys(keybinding: Keybinding): KeyId[] {
|
|
616
616
|
const keys = super.getKeys(keybinding);
|
|
617
617
|
const fallbackKey = getFallbackKey(keybinding);
|
|
618
618
|
if (fallbackKey === undefined || this.#userBindings[keybinding] !== undefined) return keys;
|
|
@@ -620,7 +620,7 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
620
620
|
return removeKey(keys, fallbackKey);
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
-
getResolvedBindings(): KeybindingsConfig {
|
|
623
|
+
override getResolvedBindings(): KeybindingsConfig {
|
|
624
624
|
const resolved = super.getResolvedBindings();
|
|
625
625
|
resolved[FOLLOW_UP_KEYBINDING] = keyConfigValue(this.getKeys(FOLLOW_UP_KEYBINDING));
|
|
626
626
|
return resolved;
|
|
@@ -724,6 +724,31 @@ export async function discoverLlamaCppModelRuntimeMetadata(
|
|
|
724
724
|
}
|
|
725
725
|
}
|
|
726
726
|
|
|
727
|
+
/**
|
|
728
|
+
* Read image-input support from an OpenAI-compatible `/v1/models` row. Handles
|
|
729
|
+
* direct `input` arrays, Synthetic-style top-level `input_modalities`, and
|
|
730
|
+
* OpenRouter-style `architecture.input_modalities`; returns undefined when none
|
|
731
|
+
* is present so the bundled reference (or the `["text"]` default) can take over.
|
|
732
|
+
*/
|
|
733
|
+
function extractOpenAIModelsListInputCapabilities(item: {
|
|
734
|
+
input?: unknown;
|
|
735
|
+
input_modalities?: unknown;
|
|
736
|
+
architecture?: unknown;
|
|
737
|
+
}): ("text" | "image")[] | undefined {
|
|
738
|
+
const modalities = new Set<string>();
|
|
739
|
+
const collect = (value: unknown): void => {
|
|
740
|
+
if (!Array.isArray(value)) return;
|
|
741
|
+
for (const entry of value) {
|
|
742
|
+
if (typeof entry === "string") modalities.add(entry.toLowerCase());
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
collect(item.input);
|
|
746
|
+
collect(item.input_modalities);
|
|
747
|
+
if (isRecord(item.architecture)) collect(item.architecture.input_modalities);
|
|
748
|
+
if (modalities.size === 0) return undefined;
|
|
749
|
+
return modalities.has("image") ? ["text", "image"] : ["text"];
|
|
750
|
+
}
|
|
751
|
+
|
|
727
752
|
export async function discoverOpenAIModelsList(
|
|
728
753
|
providerConfig: DiscoveryProviderConfig,
|
|
729
754
|
ctx: DiscoveryContext,
|
|
@@ -752,7 +777,14 @@ export async function discoverOpenAIModelsList(
|
|
|
752
777
|
}
|
|
753
778
|
headers = h;
|
|
754
779
|
return (await res.json()) as {
|
|
755
|
-
data?: Array<{
|
|
780
|
+
data?: Array<{
|
|
781
|
+
id?: string;
|
|
782
|
+
max_model_len?: unknown;
|
|
783
|
+
context_length?: unknown;
|
|
784
|
+
input?: unknown;
|
|
785
|
+
input_modalities?: unknown;
|
|
786
|
+
architecture?: unknown;
|
|
787
|
+
}>;
|
|
756
788
|
};
|
|
757
789
|
}),
|
|
758
790
|
nativeMetadataPromise,
|
|
@@ -796,7 +828,9 @@ export async function discoverOpenAIModelsList(
|
|
|
796
828
|
baseUrl,
|
|
797
829
|
reasoning: reference?.reasoning ?? false,
|
|
798
830
|
thinking: inheritReferenceThinking(undefined, reference, providerConfig.provider),
|
|
799
|
-
input: nativeMetadataForModel?.input ??
|
|
831
|
+
input: nativeMetadataForModel?.input ??
|
|
832
|
+
extractOpenAIModelsListInputCapabilities(item) ??
|
|
833
|
+
reference?.input ?? ["text"],
|
|
800
834
|
...(providerConfig.discovery.type === "lm-studio" ? { imageInputDecoder: "stb" as const } : {}),
|
|
801
835
|
// Proxy/gateway pricing is provider-specific and rarely matches
|
|
802
836
|
// upstream bundled catalogs, so keep costs local-unknown even
|
|
@@ -1715,7 +1715,10 @@ export class ModelRegistry {
|
|
|
1715
1715
|
return resolveOllamaModelCacheProviderId(providerConfig.provider, providerConfig.baseUrl);
|
|
1716
1716
|
}
|
|
1717
1717
|
if (providerConfig.discovery.type === "openai-models-list") {
|
|
1718
|
-
|
|
1718
|
+
// context-v3 invalidates rows cached before server-advertised input
|
|
1719
|
+
// modalities were parsed from `/v1/models`; warm v2 rows pinned
|
|
1720
|
+
// vision-capable ids at `input: ["text"]` until a forced refresh.
|
|
1721
|
+
return `${providerConfig.provider}:openai-models-list-context-v3`;
|
|
1719
1722
|
}
|
|
1720
1723
|
if (providerConfig.discovery.type === "litellm") {
|
|
1721
1724
|
// rich-v2 invalidates rows cached before reseller usage-suffix stripping
|