@oh-my-pi/pi-coding-agent 17.3.0 → 17.3.2
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/{CHANGELOG-66nakf5b.md → CHANGELOG-fr2awajz.md} +20 -0
- package/dist/cli.js +2823 -2823
- package/dist/docs-index.generated.txt +1 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/extension-flags.d.ts +3 -3
- package/dist/types/cli/flag-tables.d.ts +0 -1
- package/dist/types/cli/setup-cli.d.ts +10 -0
- package/dist/types/cli/update-cli.d.ts +48 -9
- package/dist/types/commands/completions.d.ts +3 -0
- package/dist/types/config/claude-paths.d.ts +7 -0
- package/dist/types/discovery/agents.d.ts +6 -6
- package/dist/types/discovery/helpers.d.ts +3 -4
- package/dist/types/extensibility/extensions/runner.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +4 -0
- package/dist/types/launch/broker.d.ts +5 -1
- package/dist/types/main.d.ts +1 -1
- package/dist/types/mcp/transports/stdio.d.ts +6 -3
- package/dist/types/modes/components/footer.d.ts +3 -2
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
- package/dist/types/modes/runtime-init.d.ts +3 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/utils/git.d.ts +19 -0
- package/dist/types/utils/shell-snapshot.d.ts +4 -1
- package/package.json +13 -13
- package/src/async/job-manager.ts +33 -4
- package/src/cli/args.ts +14 -3
- package/src/cli/extension-flags.ts +6 -10
- package/src/cli/flag-tables.ts +2 -10
- package/src/cli/gc-cli.ts +13 -3
- package/src/cli/setup-cli.ts +2 -2
- package/src/cli/update-cli.ts +246 -107
- package/src/commands/completions.ts +16 -14
- package/src/config/claude-paths.ts +18 -0
- package/src/config/model-registry.ts +2 -2
- package/src/config.ts +4 -3
- package/src/discovery/agents.ts +7 -7
- package/src/discovery/claude.ts +5 -6
- package/src/discovery/helpers.ts +12 -11
- package/src/extensibility/extensions/runner.ts +5 -0
- package/src/extensibility/extensions/types.ts +5 -0
- package/src/extensibility/legacy-typebox.ts +45 -4
- package/src/launch/broker.ts +26 -4
- package/src/lsp/mux/server.ts +7 -1
- package/src/main.ts +30 -3
- package/src/mcp/transports/stdio.ts +7 -3
- package/src/modes/acp/acp-agent.ts +1 -0
- package/src/modes/components/footer.ts +17 -35
- package/src/modes/components/status-line/component.ts +14 -27
- package/src/modes/controllers/extension-ui-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +16 -9
- package/src/modes/print-mode.ts +1 -0
- package/src/modes/rpc/rpc-client.ts +4 -2
- package/src/modes/rpc/rpc-input.ts +27 -0
- package/src/modes/rpc/rpc-mode.ts +11 -19
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/utils/ui-helpers.ts +74 -53
- package/src/session/agent-session.ts +24 -9
- package/src/session/claude-session-store.ts +4 -3
- package/src/task/executor.ts +8 -4
- package/src/tools/browser/launch.ts +9 -0
- package/src/tools/read-format.ts +11 -10
- package/src/tools/run-scope.ts +4 -2
- package/src/utils/external-editor.ts +10 -11
- package/src/utils/git.ts +27 -0
- package/src/utils/shell-snapshot.ts +5 -1
- package/src/web/search/providers/gemini.ts +4 -9
package/dist/types/cli/args.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ export interface Args {
|
|
|
82
82
|
export declare function parseArgs(inputArgs: string[], extensionFlags?: Map<string, {
|
|
83
83
|
type: "boolean" | "string";
|
|
84
84
|
}>): Args;
|
|
85
|
+
/** Reject requested tool names absent from the fully discovered session registry. */
|
|
86
|
+
export declare function validateToolNames(requested: readonly string[] | undefined, known: readonly string[]): void;
|
|
85
87
|
/**
|
|
86
88
|
* Emit a stderr error listing the unrecognized flags and return `true` when
|
|
87
89
|
* there were any. Caller is expected to exit with a non-zero status. Splitting
|
|
@@ -29,8 +29,8 @@ export interface ExtensionFlagSink {
|
|
|
29
29
|
* semantics and surfaces in `unknownFlags` — without consuming the following
|
|
30
30
|
* message or overwriting the built-in field. No built-in name list to maintain.
|
|
31
31
|
*
|
|
32
|
-
* Returns `null` when there is no sink
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Returns `null` only when there is no sink. Once extensions have loaded, the
|
|
33
|
+
* reparse always runs so `--tools` can be validated against their registered
|
|
34
|
+
* tools even when no extension registered CLI flags.
|
|
35
35
|
*/
|
|
36
36
|
export declare function applyExtensionFlags(runner: ExtensionFlagSink | undefined, rawArgs: string[]): Args | null;
|
|
@@ -45,7 +45,6 @@ export interface ParseDeps {
|
|
|
45
45
|
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
46
46
|
};
|
|
47
47
|
parseThinking: (value: string | null | undefined) => ConfiguredThinkingLevel | undefined;
|
|
48
|
-
builtinToolNames: readonly string[];
|
|
49
48
|
normalizeToolNames: (values: Iterable<string>) => string[];
|
|
50
49
|
thinkingEfforts: readonly string[];
|
|
51
50
|
}
|
|
@@ -11,6 +11,16 @@ export interface SetupCommandArgs {
|
|
|
11
11
|
* Returns undefined if not a setup command.
|
|
12
12
|
*/
|
|
13
13
|
export declare function parseSetupArgs(args: string[]): SetupCommandArgs | undefined;
|
|
14
|
+
export interface PythonCheckResult {
|
|
15
|
+
available: boolean;
|
|
16
|
+
pythonPath?: string;
|
|
17
|
+
usingManagedEnv?: boolean;
|
|
18
|
+
managedEnvPath?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check Python environment and kernel dependencies.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkPythonSetup(cwd: string, interpreter?: string): Promise<PythonCheckResult>;
|
|
14
24
|
/**
|
|
15
25
|
* Install Python packages using uv (preferred) or pip.
|
|
16
26
|
*/
|
|
@@ -112,6 +112,8 @@ interface UpdateMethodResolutionOptions {
|
|
|
112
112
|
miseBinDirs?: readonly string[];
|
|
113
113
|
miseDataDir?: string;
|
|
114
114
|
npmBinDir?: string;
|
|
115
|
+
/** Bun's configured global package directory, independent of its bin directory. */
|
|
116
|
+
bunGlobalDir?: string;
|
|
115
117
|
/**
|
|
116
118
|
* Whether the resolved omp path is a plain file (the standalone binary)
|
|
117
119
|
* rather than a package-manager symlink. Stops a binary install from being
|
|
@@ -119,8 +121,34 @@ interface UpdateMethodResolutionOptions {
|
|
|
119
121
|
* target directory.
|
|
120
122
|
*/
|
|
121
123
|
ompIsRegularFile?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Absolute path named by the bin entry's first symlink hop. This deliberately
|
|
126
|
+
* preserves a global package symlink instead of resolving into its checkout.
|
|
127
|
+
*/
|
|
128
|
+
ompLinkTarget?: string;
|
|
122
129
|
}
|
|
130
|
+
type UpdateTarget = {
|
|
131
|
+
method: "brew";
|
|
132
|
+
} | {
|
|
133
|
+
method: "mise";
|
|
134
|
+
} | {
|
|
135
|
+
method: "nix";
|
|
136
|
+
} | {
|
|
137
|
+
method: "bun";
|
|
138
|
+
path?: string;
|
|
139
|
+
} | {
|
|
140
|
+
method: "npm";
|
|
141
|
+
path?: string;
|
|
142
|
+
} | {
|
|
143
|
+
method: "binary";
|
|
144
|
+
path: string;
|
|
145
|
+
replacesSymlink: boolean;
|
|
146
|
+
};
|
|
123
147
|
export declare function resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined, options?: UpdateMethodResolutionOptions): UpdateMethod;
|
|
148
|
+
/** Resolve an update target from the concrete PATH entry selected by the shell. */
|
|
149
|
+
export declare function resolveUpdateTargetFromPath(ompPath: string, bunBinDir: string | undefined, options: UpdateMethodResolutionOptions & {
|
|
150
|
+
allowPackageManagers: boolean;
|
|
151
|
+
}): UpdateTarget;
|
|
124
152
|
/**
|
|
125
153
|
* Get the latest release info from the npm registry, following `omp.rename`
|
|
126
154
|
* pointers ({@link resolveReleaseRename}) when the package has moved to a new
|
|
@@ -145,7 +173,13 @@ interface BunInstallCachePruneResult {
|
|
|
145
173
|
* cache stays internally consistent.
|
|
146
174
|
*/
|
|
147
175
|
export declare function pruneBunInstallCache(cacheDir: string, packageNames?: Set<string>): Promise<BunInstallCachePruneResult>;
|
|
148
|
-
|
|
176
|
+
interface BunGlobalInstallLocations {
|
|
177
|
+
globalDir?: string;
|
|
178
|
+
globalBinDir?: string;
|
|
179
|
+
cacheDir?: string;
|
|
180
|
+
}
|
|
181
|
+
/** Resolve Bun's global node_modules root from explicit, default, or cache locations. */
|
|
182
|
+
export declare function resolveBunGlobalNodeModulesDirFromLocations({ globalDir, globalBinDir, cacheDir, }: BunGlobalInstallLocations): string | undefined;
|
|
149
183
|
/**
|
|
150
184
|
* Detect a musl-libc Linux host (Alpine, Void-musl) so self-update replaces a
|
|
151
185
|
* musl binary with the musl release asset instead of the glibc build, which
|
|
@@ -168,16 +202,21 @@ declare function verifyBinaryAtPath(binaryPath: string, expectedVersion: string)
|
|
|
168
202
|
*/
|
|
169
203
|
declare function verifyInstalledVersion(expectedVersion: string): Promise<InstalledVersionVerification>;
|
|
170
204
|
/**
|
|
171
|
-
* Best-effort removal of binary-update
|
|
205
|
+
* Best-effort removal of binary-update leftovers from earlier runs.
|
|
172
206
|
*
|
|
173
|
-
* Each self-update
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
207
|
+
* Each self-update writes to `<binary>.<timestamp>.<pid>.new` and moves the
|
|
208
|
+
* previous executable to `<binary>.<timestamp>.<pid>.bak` before swapping the
|
|
209
|
+
* new one in. On Windows a backup cannot be deleted while the updating process
|
|
210
|
+
* is alive (it is the running process image), so it is left for a later run to
|
|
211
|
+
* reclaim once its owning process has exited. A `.new` temp file only survives
|
|
212
|
+
* a hard kill mid-download; it is reaped once older than the download window,
|
|
213
|
+
* which a live download cannot exceed without timing out and cleaning up after
|
|
214
|
+
* itself — so a concurrent run's in-progress temp is never deleted. Legacy
|
|
215
|
+
* fixed `<binary>.bak` / `<binary>.new` names (from before suffixes were made
|
|
216
|
+
* unique) are matched too, so users upgrading from a buggy release get the
|
|
217
|
+
* orphaned files cleaned up.
|
|
179
218
|
*/
|
|
180
|
-
export declare function
|
|
219
|
+
export declare function sweepStaleUpdateArtifacts(targetPath: string): Promise<void>;
|
|
181
220
|
/**
|
|
182
221
|
* Atomically replace the installed binary and roll back if version verification fails.
|
|
183
222
|
*/
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* (see `cli/completion-gen.ts`), so it never drifts from the actual CLI surface.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from "@oh-my-pi/pi-utils/cli";
|
|
8
|
+
import { type Shell } from "../cli/completion-gen.js";
|
|
9
|
+
/** Generate a completion script from the live command registry. */
|
|
10
|
+
export declare function generateLiveCompletion(shell: Shell): Promise<string>;
|
|
8
11
|
export default class Completions extends Command {
|
|
9
12
|
static description: string;
|
|
10
13
|
static args: {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Paths to Claude Code's user data and configuration file. */
|
|
2
|
+
export interface ClaudePaths {
|
|
3
|
+
configDir: string;
|
|
4
|
+
configFile: string;
|
|
5
|
+
}
|
|
6
|
+
/** Resolves Claude Code's user paths, honoring `CLAUDE_CONFIG_DIR`. */
|
|
7
|
+
export declare function resolveClaudePaths(home?: string): ClaudePaths;
|
|
@@ -7,13 +7,13 @@ interface UserPathCandidateOptions {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Run a best-effort discovery probe and return its trimmed stdout, or
|
|
10
|
-
* `undefined` when the command fails, produces no output, or exceeds
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* `undefined` when the command fails, produces no output, or exceeds the
|
|
11
|
+
* timeout. On timeout the child is killed with SIGKILL so a wedged interop pipe
|
|
12
|
+
* cannot hang startup; the killed/non-zero exit is then reported as
|
|
13
|
+
* "unavailable" and discovery falls back to the Linux `$HOME`/`~/.omp`
|
|
14
|
+
* candidates.
|
|
15
15
|
*/
|
|
16
|
-
export declare function runHostProbe(cmd: string[]): string | undefined;
|
|
16
|
+
export declare function runHostProbe(cmd: string[], timeoutMs?: number): string | undefined;
|
|
17
17
|
/** Resolve the Windows host profile home exposed to WSL, if available. */
|
|
18
18
|
export declare function getWslWindowsHomeCandidate(options?: UserPathCandidateOptions): string | undefined;
|
|
19
19
|
/** User-level paths: ~/.agent[s]/<segments>, plus the Windows host profile under WSL. */
|
|
@@ -261,11 +261,10 @@ export declare function resolveOrDefaultProjectRegistryPath(cwd: string): Promis
|
|
|
261
261
|
/** Register a process-global plugin cache invalidator called whenever plugin roots are cleared. */
|
|
262
262
|
export declare function registerPluginCacheInvalidator(invalidator: () => void): void;
|
|
263
263
|
/**
|
|
264
|
-
* List all installed Claude Code plugin roots from
|
|
265
|
-
*
|
|
266
|
-
* and optionally the nearest project-scoped registry resolved from `cwd`.
|
|
264
|
+
* List all installed Claude Code plugin roots from its active plugin cache and
|
|
265
|
+
* ~/.omp/plugins/installed_plugins.json, plus the nearest project registry when present.
|
|
267
266
|
*
|
|
268
|
-
* Results are cached per
|
|
267
|
+
* Results are cached per Claude and OMP config directories, project registry, and canonical active project.
|
|
269
268
|
*/
|
|
270
269
|
export declare function listClaudePluginRoots(home: string, cwd?: string): Promise<{
|
|
271
270
|
roots: ClaudePluginRoot[];
|
|
@@ -8,7 +8,7 @@ import type { MemoryRuntimeContext } from "../../memory-backend/index.js";
|
|
|
8
8
|
import type { AsyncJobSnapshot } from "../../session/agent-session.js";
|
|
9
9
|
import type { SessionManager } from "../../session/session-manager.js";
|
|
10
10
|
import type { BranchHandler, NavigateTreeHandler, NewSessionHandler } from "../session-handler-types.js";
|
|
11
|
-
import type { AfterProviderResponseEvent, AssistantThinkingRenderer, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, ContextEvent, Extension, ExtensionActions, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFlag, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, InputEvent, InputEventResult, McpNotificationEvent, MessageRenderer, RegisteredCommand, RegisteredTool, ResourcesDiscoverEvent, SessionBeforeBranchResult, SessionBeforeCompactResult, SessionBeforeSwitchResult, SessionBeforeTreeResult, SessionCompactingResult, SessionStopEvent, SessionStopEventResult, ToolCallEvent, ToolCallEventResult, ToolResultEvent, ToolResultEventResult, UserBashEvent, UserBashEventResult, UserPythonEvent, UserPythonEventResult } from "./types.js";
|
|
11
|
+
import type { AfterProviderResponseEvent, AssistantThinkingRenderer, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, ContextEvent, Extension, ExtensionActions, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFlag, ExtensionMode, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, InputEvent, InputEventResult, McpNotificationEvent, MessageRenderer, RegisteredCommand, RegisteredTool, ResourcesDiscoverEvent, SessionBeforeBranchResult, SessionBeforeCompactResult, SessionBeforeSwitchResult, SessionBeforeTreeResult, SessionCompactingResult, SessionStopEvent, SessionStopEventResult, ToolCallEvent, ToolCallEventResult, ToolResultEvent, ToolResultEventResult, UserBashEvent, UserBashEventResult, UserPythonEvent, UserPythonEventResult } from "./types.js";
|
|
12
12
|
/** Combined result from all before_agent_start handlers */
|
|
13
13
|
interface BeforeAgentStartCombinedResult {
|
|
14
14
|
messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
|
|
@@ -115,7 +115,7 @@ export declare class ExtensionRunner {
|
|
|
115
115
|
* leak into another's `ctx.cwd` by reading a shared global.
|
|
116
116
|
*/
|
|
117
117
|
get cwd(): string;
|
|
118
|
-
initialize(actions: ExtensionActions, contextActions: ExtensionContextActions, commandContextActions?: ExtensionCommandContextActions, uiContext?: ExtensionUIContext): void;
|
|
118
|
+
initialize(actions: ExtensionActions, contextActions: ExtensionContextActions, commandContextActions?: ExtensionCommandContextActions, uiContext?: ExtensionUIContext, mode?: ExtensionMode): void;
|
|
119
119
|
/**
|
|
120
120
|
* Forward a `credential_disabled` event from `AuthStorage` to extension handlers.
|
|
121
121
|
*
|
|
@@ -280,9 +280,13 @@ export interface ExtensionModelQuery {
|
|
|
280
280
|
*/
|
|
281
281
|
family(model: Model): string;
|
|
282
282
|
}
|
|
283
|
+
/** Runtime host mode exposed to Pi-compatible extensions. */
|
|
284
|
+
export type ExtensionMode = "tui" | "rpc" | "json" | "print";
|
|
283
285
|
export interface ExtensionContext {
|
|
284
286
|
/** UI methods for user interaction */
|
|
285
287
|
ui: ExtensionUIContext;
|
|
288
|
+
/** Current run mode. Use `"tui"` to guard terminal-only UI such as custom components. */
|
|
289
|
+
mode: ExtensionMode;
|
|
286
290
|
/** Get current context usage for the active model. */
|
|
287
291
|
getContextUsage(): ContextUsage | undefined;
|
|
288
292
|
/** Get a read-only snapshot of async jobs owned by this session. */
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
export interface DaemonBrokerStartOptions {
|
|
2
|
+
/** Base of the exponential child-restart backoff. */
|
|
3
|
+
restartBackoffBaseMs?: number;
|
|
4
|
+
}
|
|
1
5
|
/** Start the detached project or global daemon broker selected by the CLI worker host. */
|
|
2
|
-
export declare function startDaemonBrokerFromEnvironment(): Promise<void>;
|
|
6
|
+
export declare function startDaemonBrokerFromEnvironment(options?: DaemonBrokerStartOptions): Promise<void>;
|
package/dist/types/main.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface AcpSessionFactoryOptions {
|
|
|
28
28
|
sessionDir?: string;
|
|
29
29
|
authStorage: AuthStorage;
|
|
30
30
|
modelRegistry: ModelRegistry;
|
|
31
|
-
parsedArgs: Pick<Args, "apiKey" | "trustedExtensions">;
|
|
31
|
+
parsedArgs: Pick<Args, "apiKey" | "trustedExtensions" | "tools">;
|
|
32
32
|
rawArgs: string[];
|
|
33
33
|
createSession: (options: CreateAgentSessionOptions) => Promise<CreateAgentSessionResult>;
|
|
34
34
|
}
|
|
@@ -110,7 +110,7 @@ interface KillableSubprocess {
|
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Terminate an MCP stdio subprocess: SIGTERM (process-group when `detached`
|
|
113
|
-
* on POSIX, direct child otherwise), wait up to `
|
|
113
|
+
* on POSIX, direct child otherwise), wait up to `termGraceMs` for a
|
|
114
114
|
* cooperative exit, then escalate to SIGKILL — waiting up to `KILL_GRACE_MS`
|
|
115
115
|
* more only when the leader itself hadn't already exited. A detached
|
|
116
116
|
* leader's cooperative exit does not prove the whole process group is gone
|
|
@@ -123,9 +123,12 @@ interface KillableSubprocess {
|
|
|
123
123
|
* `detached`/`platform` pair: `StdioTransport.connect()` derives `detached`
|
|
124
124
|
* from `resolveStdioSpawnCommand()`, which is tied to the host's real
|
|
125
125
|
* `process.platform`, so a POSIX detached session cannot be reproduced
|
|
126
|
-
* end-to-end through `connect()` on a non-Linux dev/CI host.
|
|
126
|
+
* end-to-end through `connect()` on a non-Linux dev/CI host. `termGraceMs`
|
|
127
|
+
* preserves the production grace by default while allowing those real
|
|
128
|
+
* subprocess tests to cover the same transition without sleeping for a
|
|
129
|
+
* production-length shutdown window.
|
|
127
130
|
*/
|
|
128
|
-
export declare function terminateStdioProcess(proc: KillableSubprocess, detached: boolean, platform?: NodeJS.Platform): Promise<void>;
|
|
131
|
+
export declare function terminateStdioProcess(proc: KillableSubprocess, detached: boolean, platform?: NodeJS.Platform, termGraceMs?: number): Promise<void>;
|
|
129
132
|
/**
|
|
130
133
|
* Stdio transport for MCP servers.
|
|
131
134
|
* Spawns a subprocess and communicates via stdin/stdout.
|
|
@@ -17,8 +17,9 @@ export declare class FooterComponent implements Component {
|
|
|
17
17
|
*/
|
|
18
18
|
setExtensionStatus(key: string, text: string | undefined): void;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Watch the repository HEAD for branch changes; invokes the callback so the
|
|
21
|
+
* footer repaints with the new branch. Uses `git.head.watch` (stat-poll) —
|
|
22
|
+
* see that helper for why `fs.watch` cannot track git's atomic HEAD swaps.
|
|
22
23
|
*/
|
|
23
24
|
watchBranch(onBranchChange: () => void): void;
|
|
24
25
|
/**
|
|
@@ -51,6 +51,7 @@ import type { CompactionQueuedMessage, InteractiveModeContext, InteractiveModeIn
|
|
|
51
51
|
* any further, it would only misreport the rows it actually occupies.
|
|
52
52
|
*/
|
|
53
53
|
export declare function computeEditorMaxHeight(terminalRows: number): number;
|
|
54
|
+
export declare function shouldEnterPlanModeOnStartup(sessionManager: Pick<SessionManager, "buildSessionContext" | "getEntries">, sessionSettings: Pick<Settings, "get">): boolean;
|
|
54
55
|
/** Options for creating an InteractiveMode instance (for future API use) */
|
|
55
56
|
export interface InteractiveModeOptions {
|
|
56
57
|
/** Providers that were migrated during startup */
|
|
@@ -334,7 +335,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
334
335
|
reuseSettledComponent?: boolean;
|
|
335
336
|
}): Component[];
|
|
336
337
|
renderSessionContext(sessionContext: SessionContext, options?: RenderSessionContextOptions): void;
|
|
337
|
-
/**
|
|
338
|
+
/** Build a session context in bounded chunks so terminal input runs between event-loop turns. */
|
|
338
339
|
renderSessionContextIncrementally(sessionContext: SessionContext, options: RenderSessionContextOptions, renderChunk?: () => void): Promise<void>;
|
|
339
340
|
renderInitialMessages(options?: {
|
|
340
341
|
preserveExistingChat?: boolean;
|
|
@@ -25,6 +25,8 @@ export interface RpcClientOptions {
|
|
|
25
25
|
sessionDir?: string;
|
|
26
26
|
/** Additional CLI arguments */
|
|
27
27
|
args?: string[];
|
|
28
|
+
/** Grace period before escalating process termination (default: process utility default, 1000ms) */
|
|
29
|
+
terminationGraceMs?: number;
|
|
28
30
|
/** Custom tools owned by the embedding host and exposed over the RPC transport */
|
|
29
31
|
customTools?: RpcClientCustomTool[];
|
|
30
32
|
}
|
|
@@ -3,3 +3,8 @@
|
|
|
3
3
|
* RPC startup uses this before extension discovery so in-process modules cannot steal protocol input.
|
|
4
4
|
*/
|
|
5
5
|
export declare function claimRpcInput(): ReadableStream<Uint8Array>;
|
|
6
|
+
/**
|
|
7
|
+
* Parses newline-delimited RPC input without letting one malformed line stop
|
|
8
|
+
* subsequent protocol frames.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readRpcInputFrames(input: ReadableStream<Uint8Array>, onFrame: (frame: unknown) => void, onParseError: (message: string) => void): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionError, ExtensionUIContext } from "../extensibility/extensions/types.js";
|
|
1
|
+
import type { ExtensionError, ExtensionMode, ExtensionUIContext } from "../extensibility/extensions/types.js";
|
|
2
2
|
import type { AgentSession } from "../session/agent-session.js";
|
|
3
3
|
/** Action name for an extension-originated send failure. */
|
|
4
4
|
export type ExtensionSendAction = "extension_send" | "extension_send_user";
|
|
@@ -9,6 +9,8 @@ export interface InitializeExtensionsOptions {
|
|
|
9
9
|
reportRuntimeError: (error: ExtensionError) => void;
|
|
10
10
|
/** Optional shutdown hook (rpc mode signals its loop; print mode is a no-op). */
|
|
11
11
|
onShutdown?: () => void;
|
|
12
|
+
/** Pi-compatible mode exposed to extension contexts. Defaults to `"print"`. */
|
|
13
|
+
mode?: ExtensionMode;
|
|
12
14
|
/** Optional UI context (rpc supplies one; print runs headless). */
|
|
13
15
|
uiContext?: ExtensionUIContext;
|
|
14
16
|
/** Optional lifecycle hook for extension-originated messages that can start an agent turn. */
|
|
@@ -35,7 +35,7 @@ export declare class UiHelpers {
|
|
|
35
35
|
* @param options.populateHistory Add user messages to editor history
|
|
36
36
|
*/
|
|
37
37
|
renderSessionContext(sessionContext: SessionContext, options?: RenderSessionContextOptions): void;
|
|
38
|
-
/**
|
|
38
|
+
/** Build a session context in bounded chunks so terminal input runs between event-loop turns. */
|
|
39
39
|
renderSessionContextIncrementally(sessionContext: SessionContext, options: RenderSessionContextOptions, renderChunk?: () => void): Promise<void>;
|
|
40
40
|
renderInitialMessages(options?: RenderInitialMessagesOptions): Promise<void>;
|
|
41
41
|
clearEditor(): void;
|
|
@@ -213,6 +213,8 @@ export interface ExecutorOptions {
|
|
|
213
213
|
keepAlive?: boolean;
|
|
214
214
|
/** Internal ownership handoff for cleanup that outlives the visible Task result. */
|
|
215
215
|
onCleanupDeferred?: (completion: Promise<void>) => void;
|
|
216
|
+
/** Internal cleanup grace override for deterministic lifecycle tests. */
|
|
217
|
+
cleanupGraceMs?: number;
|
|
216
218
|
}
|
|
217
219
|
interface FinalizeSubprocessOutputArgs {
|
|
218
220
|
rawOutput: string;
|
|
@@ -155,6 +155,11 @@ export declare const GIT_COMMAND_OUTPUT_LIMIT_BYTES: number;
|
|
|
155
155
|
* degrades instead of freezing the UI indefinitely.
|
|
156
156
|
*/
|
|
157
157
|
export declare const GIT_SPAWN_SYNC_TIMEOUT_MS = 5000;
|
|
158
|
+
/**
|
|
159
|
+
* Stat-poll interval for {@link head.watch}. One `stat` per interval keeps an
|
|
160
|
+
* always-on status line cheap while surfacing a branch switch within a second.
|
|
161
|
+
*/
|
|
162
|
+
export declare const HEAD_WATCH_INTERVAL_MS = 1000;
|
|
158
163
|
interface CommandOptions {
|
|
159
164
|
readonly env?: Record<string, string | undefined>;
|
|
160
165
|
readonly maxOutputBytes?: number;
|
|
@@ -458,6 +463,20 @@ export declare const head: {
|
|
|
458
463
|
sha(cwd: string, signal?: AbortSignal): Promise<string | null>;
|
|
459
464
|
/** Abbreviated HEAD commit SHA. */
|
|
460
465
|
short(cwd: string, length?: number, signal?: AbortSignal): Promise<string | null>;
|
|
466
|
+
/**
|
|
467
|
+
* Watch the repository's HEAD for branch moves. Returns a disposer.
|
|
468
|
+
*
|
|
469
|
+
* Deliberately stat-polls via `fs.watchFile` instead of `fs.watch`: git
|
|
470
|
+
* swaps HEAD with `HEAD.lock` + atomic rename, which unlinks the HEAD inode
|
|
471
|
+
* — and Bun's inotify-backed `fs.watch` permanently stops delivering events
|
|
472
|
+
* after observing a rename in the watched directory (oven-sh/bun#24875), so
|
|
473
|
+
* an event watcher fires once and then freezes on Linux (issue #8412 was
|
|
474
|
+
* the same freeze for file-inode watches on every platform). A path-based
|
|
475
|
+
* stat poll re-resolves the path each interval and survives inode swaps
|
|
476
|
+
* everywhere. Reftable repos keep ref state in `<gitDir>/reftable` (their
|
|
477
|
+
* HEAD file is a static stub), so the poll targets that directory instead.
|
|
478
|
+
*/
|
|
479
|
+
watch(repository: GitRepository, onChange: () => void): () => void;
|
|
461
480
|
};
|
|
462
481
|
export declare const repo: {
|
|
463
482
|
/** Resolve the repository root (may be a worktree root). */
|
|
@@ -11,5 +11,8 @@ export declare function sanitizeSnapshotForBrush(content: string): {
|
|
|
11
11
|
/**
|
|
12
12
|
* Create a shell snapshot, caching the result.
|
|
13
13
|
* Returns the path to the snapshot file, or null if creation failed.
|
|
14
|
+
*
|
|
15
|
+
* `timeoutMs` is configurable so callers exercising failure handling do not
|
|
16
|
+
* have to wait out the production startup budget.
|
|
14
17
|
*/
|
|
15
|
-
export declare function getOrCreateSnapshot(shell: string, env: Record<string, string | undefined
|
|
18
|
+
export declare function getOrCreateSnapshot(shell: string, env: Record<string, string | undefined>, timeoutMs?: number): Promise<string | null>;
|
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.3.
|
|
4
|
+
"version": "17.3.2",
|
|
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",
|
|
@@ -50,18 +50,18 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@babel/parser": "^7.29.7",
|
|
53
|
-
"@oh-my-pi/hashline": "17.3.
|
|
54
|
-
"@oh-my-pi/omp-stats": "17.3.
|
|
55
|
-
"@oh-my-pi/omptype": "17.3.
|
|
56
|
-
"@oh-my-pi/pi-agent-core": "17.3.
|
|
57
|
-
"@oh-my-pi/pi-ai": "17.3.
|
|
58
|
-
"@oh-my-pi/pi-catalog": "17.3.
|
|
59
|
-
"@oh-my-pi/pi-mnemopi": "17.3.
|
|
60
|
-
"@oh-my-pi/pi-natives": "17.3.
|
|
61
|
-
"@oh-my-pi/pi-tui": "17.3.
|
|
62
|
-
"@oh-my-pi/pi-utils": "17.3.
|
|
63
|
-
"@oh-my-pi/pi-wire": "17.3.
|
|
64
|
-
"@oh-my-pi/snapcompact": "17.3.
|
|
53
|
+
"@oh-my-pi/hashline": "17.3.2",
|
|
54
|
+
"@oh-my-pi/omp-stats": "17.3.2",
|
|
55
|
+
"@oh-my-pi/omptype": "17.3.2",
|
|
56
|
+
"@oh-my-pi/pi-agent-core": "17.3.2",
|
|
57
|
+
"@oh-my-pi/pi-ai": "17.3.2",
|
|
58
|
+
"@oh-my-pi/pi-catalog": "17.3.2",
|
|
59
|
+
"@oh-my-pi/pi-mnemopi": "17.3.2",
|
|
60
|
+
"@oh-my-pi/pi-natives": "17.3.2",
|
|
61
|
+
"@oh-my-pi/pi-tui": "17.3.2",
|
|
62
|
+
"@oh-my-pi/pi-utils": "17.3.2",
|
|
63
|
+
"@oh-my-pi/pi-wire": "17.3.2",
|
|
64
|
+
"@oh-my-pi/snapcompact": "17.3.2",
|
|
65
65
|
"@opentelemetry/api": "^1.9.1",
|
|
66
66
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
67
67
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
package/src/async/job-manager.ts
CHANGED
|
@@ -152,6 +152,7 @@ export class AsyncJobManager {
|
|
|
152
152
|
readonly #maxRunningJobs: number;
|
|
153
153
|
readonly #retentionMs: number;
|
|
154
154
|
#deliveryLoop: Promise<void> | undefined;
|
|
155
|
+
#deliveryQueueChanged = Promise.withResolvers<void>();
|
|
155
156
|
#disposed = false;
|
|
156
157
|
|
|
157
158
|
#filterJobs(jobs: Iterable<AsyncJob>, filter?: AsyncJobFilter): AsyncJob[] {
|
|
@@ -335,6 +336,7 @@ export class AsyncJobManager {
|
|
|
335
336
|
for (const jobId of uniqueJobIds) {
|
|
336
337
|
this.#watchedJobs.add(jobId);
|
|
337
338
|
}
|
|
339
|
+
this.#notifyDeliveryQueueChanged();
|
|
338
340
|
return uniqueJobIds.length;
|
|
339
341
|
}
|
|
340
342
|
|
|
@@ -389,6 +391,7 @@ export class AsyncJobManager {
|
|
|
389
391
|
this.#deliveries.length,
|
|
390
392
|
...this.#deliveries.filter(delivery => !this.isDeliverySuppressed(delivery.jobId)),
|
|
391
393
|
);
|
|
394
|
+
this.#notifyDeliveryQueueChanged();
|
|
392
395
|
return before - this.#deliveries.length;
|
|
393
396
|
}
|
|
394
397
|
|
|
@@ -603,6 +606,7 @@ export class AsyncJobManager {
|
|
|
603
606
|
this.#clearEvictionTimers();
|
|
604
607
|
this.#jobs.clear();
|
|
605
608
|
this.#deliveries.length = 0;
|
|
609
|
+
this.#notifyDeliveryQueueChanged();
|
|
606
610
|
this.#inFlightDeliveries.length = 0;
|
|
607
611
|
this.#suppressedDeliveries.clear();
|
|
608
612
|
this.#watchedJobs.clear();
|
|
@@ -704,13 +708,14 @@ export class AsyncJobManager {
|
|
|
704
708
|
const now = Date.now();
|
|
705
709
|
if (selected.nextAttemptAt > now) {
|
|
706
710
|
if (selected.nextAttemptAt > deadline) return false;
|
|
707
|
-
await
|
|
711
|
+
await this.#waitForDeliveryQueueChange(selected.nextAttemptAt - now);
|
|
708
712
|
continue;
|
|
709
713
|
}
|
|
710
714
|
|
|
711
715
|
const index = this.#deliveries.indexOf(selected);
|
|
712
716
|
if (index === -1) continue;
|
|
713
717
|
this.#deliveries.splice(index, 1);
|
|
718
|
+
this.#notifyDeliveryQueueChanged();
|
|
714
719
|
if (this.isDeliverySuppressed(selected.jobId)) continue;
|
|
715
720
|
|
|
716
721
|
return this.#waitForDeliveryPromise(this.#deliverDelivery(selected), deadline);
|
|
@@ -726,7 +731,7 @@ export class AsyncJobManager {
|
|
|
726
731
|
if (this.isDeliverySuppressed(jobId)) {
|
|
727
732
|
return;
|
|
728
733
|
}
|
|
729
|
-
this.#
|
|
734
|
+
this.#queueDelivery({
|
|
730
735
|
jobId,
|
|
731
736
|
text,
|
|
732
737
|
attempt: 0,
|
|
@@ -762,7 +767,8 @@ export class AsyncJobManager {
|
|
|
762
767
|
}
|
|
763
768
|
const waitMs = delivery.nextAttemptAt - Date.now();
|
|
764
769
|
if (waitMs > 0) {
|
|
765
|
-
await
|
|
770
|
+
await this.#waitForDeliveryQueueChange(waitMs);
|
|
771
|
+
continue;
|
|
766
772
|
}
|
|
767
773
|
if (this.#deliveries[0] !== delivery) {
|
|
768
774
|
continue;
|
|
@@ -813,7 +819,7 @@ export class AsyncJobManager {
|
|
|
813
819
|
delivery.lastError = error instanceof Error ? error.message : String(error);
|
|
814
820
|
delivery.nextAttemptAt = Date.now() + this.#getRetryDelay(delivery.attempt);
|
|
815
821
|
if (!this.isDeliverySuppressed(delivery.jobId)) {
|
|
816
|
-
this.#
|
|
822
|
+
this.#queueDelivery(delivery);
|
|
817
823
|
}
|
|
818
824
|
logger.warn("Async job completion delivery failed", {
|
|
819
825
|
jobId: delivery.jobId,
|
|
@@ -831,6 +837,29 @@ export class AsyncJobManager {
|
|
|
831
837
|
return promise;
|
|
832
838
|
}
|
|
833
839
|
|
|
840
|
+
#queueDelivery(delivery: AsyncJobDelivery): void {
|
|
841
|
+
const index = this.#deliveries.findIndex(candidate => candidate.nextAttemptAt > delivery.nextAttemptAt);
|
|
842
|
+
if (index === -1) this.#deliveries.push(delivery);
|
|
843
|
+
else this.#deliveries.splice(index, 0, delivery);
|
|
844
|
+
this.#notifyDeliveryQueueChanged();
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async #waitForDeliveryQueueChange(delayMs: number): Promise<void> {
|
|
848
|
+
const timerElapsed = Promise.withResolvers<void>();
|
|
849
|
+
const timer = setTimeout(timerElapsed.resolve, delayMs);
|
|
850
|
+
timer.unref();
|
|
851
|
+
try {
|
|
852
|
+
await Promise.race([timerElapsed.promise, this.#deliveryQueueChanged.promise]);
|
|
853
|
+
} finally {
|
|
854
|
+
clearTimeout(timer);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
#notifyDeliveryQueueChanged(): void {
|
|
859
|
+
this.#deliveryQueueChanged.resolve();
|
|
860
|
+
this.#deliveryQueueChanged = Promise.withResolvers<void>();
|
|
861
|
+
}
|
|
862
|
+
|
|
834
863
|
async #waitForDeliveryPromise(promise: Promise<void> | undefined, deadline: number): Promise<boolean> {
|
|
835
864
|
if (!promise) return true;
|
|
836
865
|
if (deadline === Number.POSITIVE_INFINITY) {
|
package/src/cli/args.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { $env, APP_NAME, logger } from "@oh-my-pi/pi-utils";
|
|
|
6
6
|
import chalk from "@oh-my-pi/pi-utils/chalk";
|
|
7
7
|
import type { ServiceTierOpenAISettingValue } from "../config/service-tier";
|
|
8
8
|
import { CLI_THINKING_LEVELS, type ConfiguredThinkingLevel, parseCliThinkingLevel } from "../thinking";
|
|
9
|
-
import {
|
|
9
|
+
import { normalizeToolNames } from "../tools/builtin-names";
|
|
10
10
|
import {
|
|
11
11
|
OPTIONAL_FLAGS,
|
|
12
12
|
OPTIONAL_VALUE_FLAGS,
|
|
@@ -108,7 +108,6 @@ export interface Args {
|
|
|
108
108
|
const PARSE_DEPS: ParseDeps = {
|
|
109
109
|
logger,
|
|
110
110
|
parseThinking: parseCliThinkingLevel,
|
|
111
|
-
builtinToolNames: [...BUILTIN_TOOL_NAMES, ...HIDDEN_TOOL_NAMES],
|
|
112
111
|
normalizeToolNames,
|
|
113
112
|
thinkingEfforts: CLI_THINKING_LEVELS,
|
|
114
113
|
};
|
|
@@ -148,6 +147,7 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
148
147
|
// reparse in `runRootCommand` parses it a second time). Mutating the input
|
|
149
148
|
// would corrupt that later parse, so never touch the caller's array.
|
|
150
149
|
const args = [...inputArgs];
|
|
150
|
+
const parseDeps = PARSE_DEPS;
|
|
151
151
|
const result: Args = {
|
|
152
152
|
messages: [],
|
|
153
153
|
fileArgs: [],
|
|
@@ -214,7 +214,7 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
214
214
|
if (i + 1 < args.length && args[i + 1] !== PROFILE_BOOTSTRAP_BOUNDARY_ARG) {
|
|
215
215
|
const consumed = consumeBuiltInStringValue(arg, args, i + 1);
|
|
216
216
|
i = consumed.index;
|
|
217
|
-
STRING_SETTERS[arg](result, consumed.value,
|
|
217
|
+
STRING_SETTERS[arg](result, consumed.value, parseDeps);
|
|
218
218
|
}
|
|
219
219
|
} else if (OPTIONAL_VALUE_FLAGS.has(arg)) {
|
|
220
220
|
const config = OPTIONAL_FLAGS[arg];
|
|
@@ -332,6 +332,17 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
332
332
|
return result;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
/** Reject requested tool names absent from the fully discovered session registry. */
|
|
336
|
+
export function validateToolNames(requested: readonly string[] | undefined, known: readonly string[]): void {
|
|
337
|
+
if (!requested) return;
|
|
338
|
+
const knownNames = new Set(known);
|
|
339
|
+
const unknown = requested.filter(name => !knownNames.has(name));
|
|
340
|
+
if (unknown.length === 0) return;
|
|
341
|
+
throw new CliUsageError(
|
|
342
|
+
`Unknown tool${unknown.length === 1 ? "" : "s"} in --tools: ${unknown.join(", ")}. Valid tools: ${known.join(", ")}.`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
335
346
|
/**
|
|
336
347
|
* Emit a stderr error listing the unrecognized flags and return `true` when
|
|
337
348
|
* there were any. Caller is expected to exit with a non-zero status. Splitting
|
|
@@ -29,18 +29,14 @@ export interface ExtensionFlagSink {
|
|
|
29
29
|
* semantics and surfaces in `unknownFlags` — without consuming the following
|
|
30
30
|
* message or overwriting the built-in field. No built-in name list to maintain.
|
|
31
31
|
*
|
|
32
|
-
* Returns `null` when there is no sink
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Returns `null` only when there is no sink. Once extensions have loaded, the
|
|
33
|
+
* reparse always runs so `--tools` can be validated against their registered
|
|
34
|
+
* tools even when no extension registered CLI flags.
|
|
35
35
|
*/
|
|
36
36
|
export function applyExtensionFlags(runner: ExtensionFlagSink | undefined, rawArgs: string[]): Args | null {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
const parsed = parseArgs(rawArgs, extensionFlags);
|
|
42
|
-
// `parseArgs` only records registered extension flags in `unknownFlags`, so
|
|
43
|
-
// every entry here is a flag this runner owns that was actually passed.
|
|
37
|
+
if (!runner) return null;
|
|
38
|
+
const parsed = parseArgs(rawArgs, runner.getFlags());
|
|
39
|
+
// `parseArgs` records extension flag values in `unknownFlags`.
|
|
44
40
|
for (const [name, value] of parsed.unknownFlags) {
|
|
45
41
|
runner.setFlagValue(name, value);
|
|
46
42
|
}
|