@oh-my-pi/pi-coding-agent 17.0.4 → 17.0.6
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 +209 -0
- package/dist/cli.js +3616 -3676
- package/dist/types/advisor/advise-tool.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +7 -1
- package/dist/types/async/job-manager.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +7 -0
- package/dist/types/config/model-resolver.d.ts +8 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/dap/client.d.ts +10 -0
- package/dist/types/dap/types.d.ts +6 -5
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/extensions/load-errors.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +16 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
- package/dist/types/launch/spawn-options.d.ts +10 -0
- package/dist/types/launch/spawn-options.test.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +10 -3
- package/dist/types/modes/components/status-line/types.d.ts +3 -1
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +4 -3
- package/dist/types/modes/components/tree-selector.d.ts +6 -2
- package/dist/types/modes/components/usage-row.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +4 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +9 -0
- package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/index.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/model.d.ts +3 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +42 -6
- package/dist/types/session/messages.d.ts +6 -0
- package/dist/types/session/session-paths.d.ts +21 -4
- package/dist/types/tools/browser/aria/aria-snapshot.d.ts +17 -7
- package/dist/types/tools/browser/cmux/socket-client.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/gh.d.ts +5 -3
- package/dist/types/tools/hub/index.d.ts +1 -1
- package/dist/types/tools/hub/jobs.d.ts +1 -0
- package/dist/types/tools/hub/types.d.ts +2 -0
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/tools/xdev.d.ts +5 -3
- package/dist/types/utils/git.d.ts +33 -0
- package/dist/types/vibe/__tests__/token-rate.test.d.ts +1 -0
- package/dist/types/vibe/runtime.d.ts +23 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +150 -0
- package/src/advisor/advise-tool.ts +4 -0
- package/src/advisor/runtime.ts +38 -14
- package/src/async/job-manager.ts +3 -0
- package/src/cli/bench-cli.ts +8 -2
- package/src/cli/dry-balance-cli.ts +1 -0
- package/src/config/model-registry.ts +89 -8
- package/src/config/model-resolver.ts +78 -14
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings.ts +3 -1
- package/src/dap/client.ts +168 -1
- package/src/dap/config.ts +51 -1
- package/src/dap/session.ts +575 -234
- package/src/dap/types.ts +6 -5
- package/src/discovery/agents.ts +2 -2
- package/src/exec/bash-executor.ts +68 -8
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/runner.ts +6 -4
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +26 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +41 -6
- package/src/launch/broker.ts +34 -31
- package/src/launch/client.ts +7 -1
- package/src/launch/spawn-options.test.ts +31 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/lsp/types.ts +5 -1
- package/src/main.ts +25 -4
- package/src/mcp/transports/stdio.test.ts +9 -1
- package/src/modes/components/ask-dialog.ts +137 -73
- package/src/modes/components/chat-transcript-builder.ts +10 -1
- package/src/modes/components/status-line/component.ts +57 -3
- package/src/modes/components/status-line/segments.ts +20 -3
- package/src/modes/components/status-line/types.ts +3 -1
- package/src/modes/components/tool-execution.ts +5 -0
- package/src/modes/components/transcript-container.ts +23 -122
- package/src/modes/components/tree-selector.ts +10 -4
- package/src/modes/components/usage-row.ts +17 -1
- package/src/modes/controllers/command-controller.ts +41 -1
- package/src/modes/controllers/event-controller.ts +7 -3
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +29 -8
- package/src/modes/interactive-mode.ts +102 -60
- package/src/modes/noninteractive-dispose.test.ts +14 -1
- package/src/modes/print-mode.ts +81 -9
- package/src/modes/rpc/rpc-client.ts +76 -35
- package/src/modes/rpc/rpc-frame.ts +156 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-mode.ts +11 -4
- package/src/modes/setup-wizard/index.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +134 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +3 -1
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +9 -3
- package/src/prompts/system/workflow-notice.md +89 -74
- package/src/prompts/tools/browser.md +3 -2
- package/src/prompts/tools/debug.md +2 -7
- package/src/prompts/tools/github.md +6 -1
- package/src/prompts/tools/hub.md +4 -2
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task.md +9 -2
- package/src/sdk.ts +38 -6
- package/src/session/agent-session.ts +484 -188
- package/src/session/messages.test.ts +91 -0
- package/src/session/messages.ts +248 -110
- package/src/session/session-manager.ts +34 -3
- package/src/session/session-paths.ts +38 -9
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/task/executor.ts +104 -33
- package/src/task/index.ts +46 -9
- package/src/task/worktree.ts +10 -0
- package/src/tools/browser/aria/aria-snapshot.ts +36 -8
- package/src/tools/browser/cmux/cmux-tab.ts +2 -1
- package/src/tools/browser/cmux/socket-client.ts +139 -3
- package/src/tools/browser/run-cancellation.ts +4 -0
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +21 -11
- package/src/tools/browser/tab-worker.ts +199 -33
- package/src/tools/debug.ts +3 -0
- package/src/tools/gh.ts +40 -2
- package/src/tools/hub/index.ts +4 -1
- package/src/tools/hub/jobs.ts +42 -1
- package/src/tools/hub/types.ts +2 -0
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/xdev.ts +11 -4
- package/src/tools/yield.ts +4 -1
- package/src/utils/git.ts +237 -0
- package/src/{modes/components/status-line → utils}/token-rate.ts +6 -0
- package/src/vibe/__tests__/token-rate.test.ts +96 -0
- package/src/vibe/runtime.ts +50 -0
- package/src/web/search/index.ts +9 -5
- package/src/web/search/providers/codex.ts +195 -99
- /package/dist/types/{modes/components/status-line → utils}/token-rate.d.ts +0 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Modes use this class and add their own I/O layer on top.
|
|
14
14
|
*/
|
|
15
15
|
import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
16
|
-
import { Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type StreamFn, ThinkingLevel, type ToolChoiceDirective } from "@oh-my-pi/pi-agent-core";
|
|
16
|
+
import { Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type AgentToolResult, type StreamFn, ThinkingLevel, type ToolChoiceDirective } from "@oh-my-pi/pi-agent-core";
|
|
17
17
|
import { type CompactionResult, type ShakeConfig } from "@oh-my-pi/pi-agent-core/compaction";
|
|
18
18
|
import type { AssistantMessage, Context, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ResetCreditAccountStatus, ResetCreditRedeemOutcome, ResetCreditTarget, ServiceTier, ServiceTierByFamily, ServiceTierFamily, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@oh-my-pi/pi-ai";
|
|
19
19
|
import { Effort } from "@oh-my-pi/pi-ai";
|
|
@@ -41,6 +41,7 @@ import type { Goal, GoalModeState } from "../goals/state.js";
|
|
|
41
41
|
import type { HindsightSessionState } from "../hindsight/state.js";
|
|
42
42
|
import { type IrcMessage } from "../irc/bus.js";
|
|
43
43
|
import { type MnemopiSessionState } from "../mnemopi/state.js";
|
|
44
|
+
import { type PlanApprovalDetails } from "../plan-mode/approved-plan.js";
|
|
44
45
|
import type { PlanModeState } from "../plan-mode/state.js";
|
|
45
46
|
import { type SecretObfuscator } from "../secrets/obfuscator.js";
|
|
46
47
|
import { type ConfiguredThinkingLevel } from "../thinking.js";
|
|
@@ -542,6 +543,8 @@ export declare class AgentSession {
|
|
|
542
543
|
* is already armed and waiting.
|
|
543
544
|
*/
|
|
544
545
|
armPrewalk(target: Model, thinkingLevel?: ConfiguredThinkingLevel): void;
|
|
546
|
+
/** Validate the active plan artifact and shape an `xd://propose` result for review-mode hosts. */
|
|
547
|
+
preparePlanForReview(title: string): Promise<AgentToolResult<PlanApprovalDetails>>;
|
|
545
548
|
constructor(config: AgentSessionConfig);
|
|
546
549
|
/** Model registry for API key resolution and model discovery */
|
|
547
550
|
get modelRegistry(): ModelRegistry;
|
|
@@ -657,10 +660,26 @@ export declare class AgentSession {
|
|
|
657
660
|
get isAborting(): boolean;
|
|
658
661
|
/** Wait until streaming and deferred recovery work are fully settled. */
|
|
659
662
|
waitForIdle(): Promise<void>;
|
|
663
|
+
/**
|
|
664
|
+
* Prevent advisor notes from starting hidden primary turns while a headless
|
|
665
|
+
* caller prints and drains the final primary response.
|
|
666
|
+
*/
|
|
667
|
+
prepareForHeadlessAdvisorDrain(): void;
|
|
668
|
+
/**
|
|
669
|
+
* Wait for active advisor reviews and their emitted card events before a
|
|
670
|
+
* headless caller disposes the session. Returns `false` and logs work disposal
|
|
671
|
+
* will abandon when the shared deadline expires or an advisor fails.
|
|
672
|
+
*/
|
|
673
|
+
waitForAdvisorCatchup(timeoutMs: number): Promise<boolean>;
|
|
660
674
|
drainAsyncJobDeliveriesForAcp(options?: {
|
|
661
675
|
timeoutMs?: number;
|
|
662
676
|
}): Promise<boolean>;
|
|
663
|
-
/**
|
|
677
|
+
/**
|
|
678
|
+
* Most recent settled assistant message. A classifier-refusal turn pruned
|
|
679
|
+
* from active context at settle is still reported until the next run
|
|
680
|
+
* starts, so terminal-outcome consumers (print mode, task executor) see
|
|
681
|
+
* the refusal error rather than the previous turn — or nothing.
|
|
682
|
+
*/
|
|
664
683
|
getLastAssistantMessage(): AssistantMessage | undefined;
|
|
665
684
|
/** Current effective system prompt blocks (includes any per-turn extension modifications) */
|
|
666
685
|
get systemPrompt(): string[];
|
|
@@ -714,6 +733,21 @@ export declare class AgentSession {
|
|
|
714
733
|
* Changes take effect before the next model call.
|
|
715
734
|
*/
|
|
716
735
|
setActiveToolsByName(toolNames: string[]): Promise<void>;
|
|
736
|
+
/**
|
|
737
|
+
* Restore an enabled tool set with its exact top-level versus `xd://` partition.
|
|
738
|
+
*
|
|
739
|
+
* Both inputs are required because {@link setActiveToolsByName} only receives the
|
|
740
|
+
* enabled name list and classifies mounts from the *current* `#mountedXdevToolNames`.
|
|
741
|
+
* Rollback/restore callers must pass the snapshotted mounted subset so names that
|
|
742
|
+
* were top-level stay pinned (`#runtimeSelectedToolNames`) and names that were under
|
|
743
|
+
* `xd://` remain mount-eligible, even when the live mount set has drifted.
|
|
744
|
+
*
|
|
745
|
+
* Names outside `mountedToolNames` are pinned top-level for this application;
|
|
746
|
+
* names in the mounted subset remain eligible for xdev mounting. Delegates the
|
|
747
|
+
* actual apply through `#applyActiveToolsByName` and restores the prior runtime
|
|
748
|
+
* selection if that apply throws.
|
|
749
|
+
*/
|
|
750
|
+
setActiveToolPresentation(toolNames: string[], mountedToolNames: string[]): Promise<void>;
|
|
717
751
|
/** Rebuild the base system prompt using the current active tool set. */
|
|
718
752
|
refreshBaseSystemPrompt(): Promise<void>;
|
|
719
753
|
/**
|
|
@@ -738,6 +772,8 @@ export declare class AgentSession {
|
|
|
738
772
|
* to avoid racing against the delivery turn.
|
|
739
773
|
*/
|
|
740
774
|
get hasPostPromptWork(): boolean;
|
|
775
|
+
/** Register post-prompt work in tests without driving a full agent turn. */
|
|
776
|
+
trackPostPromptTaskForTests(task: Promise<unknown>): void;
|
|
741
777
|
/** All messages including custom types like BashExecutionMessage */
|
|
742
778
|
get messages(): AgentMessage[];
|
|
743
779
|
/** Latest image attachments addressable by tools as `Image #N` or `attachment://N`. */
|
|
@@ -1036,10 +1072,10 @@ export declare class AgentSession {
|
|
|
1036
1072
|
*/
|
|
1037
1073
|
getAvailableModels(): Model[];
|
|
1038
1074
|
/**
|
|
1039
|
-
* Set the thinking level. `auto` enables per-turn classification
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1075
|
+
* Set the thinking level. `auto` enables per-turn classification. Entering
|
|
1076
|
+
* auto writes its provisional level plus `configured: "auto"` immediately,
|
|
1077
|
+
* giving external readers an authoritative selection receipt before the next
|
|
1078
|
+
* user turn. Later classifications persist only changed concrete resolutions.
|
|
1043
1079
|
*/
|
|
1044
1080
|
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist?: boolean): void;
|
|
1045
1081
|
/**
|
|
@@ -250,5 +250,11 @@ export declare function sanitizeRehydratedOpenAIResponsesAssistantMessage(messag
|
|
|
250
250
|
* - Agent's transormToLlm option (for prompt calls and queued messages)
|
|
251
251
|
* - Compaction's generateSummary (for summarization)
|
|
252
252
|
* - Custom extensions and tools
|
|
253
|
+
*
|
|
254
|
+
* Settled history converts once and is reused per message identity: an
|
|
255
|
+
* append-only turn on the same array re-pays only the new suffix, and an
|
|
256
|
+
* unchanged re-convert of the same array hands back the same outer `Message[]`.
|
|
257
|
+
* Owner mutations (prune/shake/strip-images) invalidate the affected message
|
|
258
|
+
* through the shared registry before the next pass.
|
|
253
259
|
*/
|
|
254
260
|
export declare function convertToLlm(messages: AgentMessage[]): Message[];
|
|
@@ -10,16 +10,33 @@ export declare function computeDefaultSessionDir(cwd: string, storage: SessionSt
|
|
|
10
10
|
* Write a breadcrumb linking the current terminal to a session file.
|
|
11
11
|
* The breadcrumb contains the cwd and session path so --continue can
|
|
12
12
|
* find "this terminal's last session" even when running concurrent instances.
|
|
13
|
+
*
|
|
14
|
+
* `fresh` marks a `/new` (or freshly-minted) session boundary whose JSONL is
|
|
15
|
+
* not yet materialized (new-session persistence is lazy until assistant output
|
|
16
|
+
* exists). A fresh breadcrumb is honored by {@link readTerminalBreadcrumbEntry}
|
|
17
|
+
* even when its target file is still absent, so relaunch/auto-resume reopens the
|
|
18
|
+
* post-`/new` session instead of falling back to the pre-`/new` transcript. Once
|
|
19
|
+
* the session materializes the caller rewrites the breadcrumb with `fresh:false`
|
|
20
|
+
* so a later external delete is still treated as a genuinely stale crumb.
|
|
13
21
|
*/
|
|
14
|
-
export declare function writeTerminalBreadcrumb(cwd: string, sessionFile: string): void;
|
|
22
|
+
export declare function writeTerminalBreadcrumb(cwd: string, sessionFile: string, fresh?: boolean): void;
|
|
15
23
|
export interface TerminalBreadcrumb {
|
|
16
24
|
cwd: string;
|
|
17
25
|
sessionFile: string;
|
|
26
|
+
/** The recorded session file exists on disk right now. */
|
|
27
|
+
exists: boolean;
|
|
28
|
+
/** Recorded as a `/new` fresh-session boundary whose JSONL may not exist yet. */
|
|
29
|
+
fresh: boolean;
|
|
18
30
|
}
|
|
19
31
|
/**
|
|
20
32
|
* Read the raw terminal breadcrumb for the current terminal.
|
|
21
|
-
* Returns the recorded cwd + session file
|
|
22
|
-
*
|
|
23
|
-
*
|
|
33
|
+
* Returns the recorded cwd + session file regardless of whether the recorded
|
|
34
|
+
* cwd still matches the current one. Callers decide how to interpret a cwd
|
|
35
|
+
* mismatch (e.g. a moved/renamed worktree).
|
|
36
|
+
*
|
|
37
|
+
* A missing target file yields `null` UNLESS the breadcrumb is a `fresh`
|
|
38
|
+
* boundary — a lazy `/new` session whose JSONL was never written — in which case
|
|
39
|
+
* the entry is returned with `exists:false` so the caller can distinguish it
|
|
40
|
+
* from a genuinely stale/deleted breadcrumb.
|
|
24
41
|
*/
|
|
25
42
|
export declare function readTerminalBreadcrumbEntry(): Promise<TerminalBreadcrumb | null>;
|
|
@@ -19,13 +19,23 @@ export declare function captureAriaSnapshot(page: Page, root: ElementHandle | nu
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function resolveAriaRefHandle(page: Page, ref: string): Promise<ElementHandle | null>;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
* Guard the selector funnels: `tab.click`/`type`/`fill`/`waitFor*`/`scrollIntoView`
|
|
23
|
+
* take string selectors only, but user `run` code routinely passes the ElementHandle
|
|
24
|
+
* from `tab.id(n)`/`tab.ref(...)` (or an un-awaited Promise of one) straight in.
|
|
25
|
+
* Without this the value reaches `.trim()`/`.startsWith()` and throws the opaque,
|
|
26
|
+
* minified `A.trim is not a function` instead of a recovery-naming ToolError.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertSelectorString(selector: unknown): asserts selector is string;
|
|
29
|
+
/**
|
|
30
|
+
* Recognize a snapshot-ref selector and return the bare ref id, else null.
|
|
31
|
+
* Accepts `aria-ref=e5` (Playwright-MCP style), `aria-ref/e5`, `ariaref/e5`,
|
|
32
|
+
* and bare `e5`/`@e5`: agents copy ids straight out of the snapshot YAML
|
|
33
|
+
* (`[ref=e5]`), so `tab.click("e5")` must act on the ref instead of falling
|
|
34
|
+
* through to a CSS tag selector that can never match. Bare ids are safe to
|
|
35
|
+
* claim here — an eN tag name is not real HTML, and the tab-worker backend's
|
|
36
|
+
* observe ids are numeric (`tab.id(7)`), so refs are its only eN namespace.
|
|
37
|
+
* (The cmux backend parses selectors itself and routes bare `eN` to its own
|
|
38
|
+
* observe ids; either way `eN` means "the id from the last page dump".)
|
|
29
39
|
*/
|
|
30
40
|
export declare function parseAriaRefSelector(selector: string): string | null;
|
|
31
41
|
/**
|
|
@@ -115,6 +115,8 @@ export interface RunErrorPayload {
|
|
|
115
115
|
stack?: string;
|
|
116
116
|
isToolError: boolean;
|
|
117
117
|
isAbort: boolean;
|
|
118
|
+
/** The worker could not restore tab-scoped browser state and must be recycled. */
|
|
119
|
+
recoverTab?: boolean;
|
|
118
120
|
}
|
|
119
121
|
export type WorkerOutbound = {
|
|
120
122
|
type: "ready";
|
|
@@ -27,6 +27,8 @@ export interface OpTimeouts {
|
|
|
27
27
|
}
|
|
28
28
|
/** Resolve the per-op fail-fast ceilings for a given cell budget. */
|
|
29
29
|
export declare function resolveOpTimeouts(cellTimeoutMs: number): OpTimeouts;
|
|
30
|
+
/** Queue a wheel event without treating a delayed renderer acknowledgement as dispatch failure. */
|
|
31
|
+
export declare function dispatchScroll(dispatch: () => Promise<void>, ackTimeoutMs?: number): Promise<void>;
|
|
30
32
|
/**
|
|
31
33
|
* Effective timeout for a wait helper (`waitFor*`). A positive explicit `{ timeout }` is
|
|
32
34
|
* honored but clamped to the cell budget so it still fails fast + named; raising the tool
|
package/dist/types/tools/gh.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ import type { OutputMeta } from "./output-meta.js";
|
|
|
7
7
|
/** Runs `gh --json` for issue data, retrying without optional stateReason on older gh releases. */
|
|
8
8
|
export declare function githubIssueJsonWithStateReasonFallback<T>(cwd: string, args: readonly string[], signal: AbortSignal | undefined, options?: git.GhCommandOptions): Promise<T>;
|
|
9
9
|
declare const githubSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
10
|
-
op: "pr_checkout" | "pr_create" | "pr_push" | "repo_view" | "run_watch" | "search_code" | "search_commits" | "search_issues" | "search_prs" | "search_repos";
|
|
10
|
+
op: "file_read" | "pr_checkout" | "pr_create" | "pr_push" | "repo_view" | "run_watch" | "search_code" | "search_commits" | "search_issues" | "search_prs" | "search_repos";
|
|
11
11
|
repo?: string | undefined;
|
|
12
12
|
branch?: string | undefined;
|
|
13
|
+
path?: string | undefined;
|
|
13
14
|
pr?: string | string[] | undefined;
|
|
14
15
|
force?: boolean | undefined;
|
|
15
16
|
forceWithLease?: boolean | undefined;
|
|
@@ -219,14 +220,15 @@ export declare class GithubTool implements AgentTool<typeof githubSchema, GhTool
|
|
|
219
220
|
private readonly session;
|
|
220
221
|
readonly name = "github";
|
|
221
222
|
readonly approval: (args: unknown) => ToolApprovalDecision;
|
|
222
|
-
readonly summary = "Interact with GitHub
|
|
223
|
+
readonly summary = "Interact with GitHub repositories, files, pull requests, and Actions";
|
|
223
224
|
readonly loadMode = "discoverable";
|
|
224
225
|
readonly label = "GitHub";
|
|
225
226
|
readonly description: string;
|
|
226
227
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
227
|
-
op: "pr_checkout" | "pr_create" | "pr_push" | "repo_view" | "run_watch" | "search_code" | "search_commits" | "search_issues" | "search_prs" | "search_repos";
|
|
228
|
+
op: "file_read" | "pr_checkout" | "pr_create" | "pr_push" | "repo_view" | "run_watch" | "search_code" | "search_commits" | "search_issues" | "search_prs" | "search_repos";
|
|
228
229
|
repo?: string | undefined;
|
|
229
230
|
branch?: string | undefined;
|
|
231
|
+
path?: string | undefined;
|
|
230
232
|
pr?: string | string[] | undefined;
|
|
231
233
|
force?: boolean | undefined;
|
|
232
234
|
forceWithLease?: boolean | undefined;
|
|
@@ -117,7 +117,7 @@ export declare class HubTool implements AgentTool<typeof hubSchema, HubDetails>
|
|
|
117
117
|
timeout?: number | undefined;
|
|
118
118
|
}, {}>;
|
|
119
119
|
readonly strict = true;
|
|
120
|
-
readonly interruptible
|
|
120
|
+
readonly interruptible: (params: Partial<HubParams>) => boolean;
|
|
121
121
|
readonly loadMode = "essential";
|
|
122
122
|
readonly examples: readonly ToolExample<typeof hubSchema.infer>[];
|
|
123
123
|
constructor(session: ToolSession);
|
|
@@ -30,6 +30,8 @@ export interface JobSnapshot {
|
|
|
30
30
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
31
31
|
label: string;
|
|
32
32
|
durationMs: number;
|
|
33
|
+
/** Effective task model selector, including an explicit reasoning suffix when configured. */
|
|
34
|
+
resolvedModel?: string;
|
|
33
35
|
resultText?: string;
|
|
34
36
|
errorText?: string;
|
|
35
37
|
}
|
|
@@ -32,9 +32,11 @@ import type { ToolRenderer } from "./renderers.js";
|
|
|
32
32
|
/**
|
|
33
33
|
* Discoverable built-ins that must stay top-level even when xdev mounting is
|
|
34
34
|
* active: `todo` feeds the todo prelude/prewalk machinery, `ask` is the
|
|
35
|
-
* model's user-interaction affordance,
|
|
36
|
-
*
|
|
37
|
-
* behind dispatch
|
|
35
|
+
* model's user-interaction affordance, `grep` is the redirect target of the
|
|
36
|
+
* bash interceptor rules, and `web_search` is invoked directly by most models
|
|
37
|
+
* (which have no notion of the `xd://` protocol) so hiding it behind dispatch
|
|
38
|
+
* makes it unreachable in practice (issue #5973) — each loses its harness
|
|
39
|
+
* integration or usability if hidden behind dispatch.
|
|
38
40
|
*/
|
|
39
41
|
export declare const XDEV_KEEP_TOP_LEVEL: Record<string, true>;
|
|
40
42
|
/**
|
|
@@ -219,6 +219,39 @@ export declare function fetch(cwd: string, remote: string, source: string, targe
|
|
|
219
219
|
export declare function readTree(cwd: string, treeish: string, options?: Pick<CommandOptions, "env" | "signal">): Promise<void>;
|
|
220
220
|
/** Write the current index as a tree and return its object id. */
|
|
221
221
|
export declare function writeTree(cwd: string, options?: Pick<CommandOptions, "env" | "signal">): Promise<string>;
|
|
222
|
+
/** Outcome of {@link detachGitDir}. */
|
|
223
|
+
export type DetachGitDirResult =
|
|
224
|
+
/** `worktreeRoot` had no `.git`; nothing to detach. */
|
|
225
|
+
"no-git"
|
|
226
|
+
/** `.git` already resolves to an independent object DB — left untouched. */
|
|
227
|
+
| "independent"
|
|
228
|
+
/** Detached into a standalone repo borrowing `sourceCommonDir`'s objects. */
|
|
229
|
+
| "detached";
|
|
230
|
+
/**
|
|
231
|
+
* Sever a copied/mounted working tree from the git metadata it shares with a
|
|
232
|
+
* source checkout, turning it into a standalone repository that borrows the
|
|
233
|
+
* source object database through `objects/info/alternates`.
|
|
234
|
+
*
|
|
235
|
+
* Isolation backends (reflink/apfs/btrfs/rcopy…) materialise `merged` by
|
|
236
|
+
* copying `worktreeRoot` byte-for-byte. When `worktreeRoot` is a **linked git
|
|
237
|
+
* worktree** its `.git` is a pointer file (`gitdir: …/worktrees/<name>`), so
|
|
238
|
+
* the copy still resolves HEAD/index/refs through the source repo — a task's
|
|
239
|
+
* `git checkout`/`commit` inside the isolation then mutates the *parent*
|
|
240
|
+
* checkout. The rcopy `git worktree add` path leaks the other way: task
|
|
241
|
+
* branches land in the shared ref namespace and stack on each other.
|
|
242
|
+
*
|
|
243
|
+
* After detaching, the working tree keeps its files verbatim while:
|
|
244
|
+
* - HEAD, refs, and the index are frozen to the snapshot at call time;
|
|
245
|
+
* - all commits/branches the task creates stay private to the isolation;
|
|
246
|
+
* - objects resolve against `sourceCommonDir` via alternates, so history reads
|
|
247
|
+
* and later `git fetch <merged>` object transfer keep working;
|
|
248
|
+
* - the source checkout's HEAD, branch, index, and working tree are untouched.
|
|
249
|
+
*
|
|
250
|
+
* A full-copy `.git` (non-worktree source) already owns its object DB and is
|
|
251
|
+
* returned as `"independent"` without modification. `worktreeRoot` without a
|
|
252
|
+
* `.git` yields `"no-git"`.
|
|
253
|
+
*/
|
|
254
|
+
export declare function detachGitDir(worktreeRoot: string, sourceCommonDir: string): Promise<DetachGitDirResult>;
|
|
222
255
|
/** Run `git show` on a revision. */
|
|
223
256
|
export declare const show: ((cwd: string, revision: string, options?: {
|
|
224
257
|
format?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -83,6 +83,17 @@ export declare class VibeSessionRegistry {
|
|
|
83
83
|
static global(): VibeSessionRegistry;
|
|
84
84
|
/** Reset the global registry. Test-only. */
|
|
85
85
|
static resetGlobalForTests(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Insert a bare worker record without the spawn/job machinery. Test-only —
|
|
88
|
+
* lets {@link aggregateVibeWorkerTokensPerSecond} be exercised against a
|
|
89
|
+
* fake roster + AgentRegistry session without driving a real turn.
|
|
90
|
+
*/
|
|
91
|
+
registerRecordForTests(record: {
|
|
92
|
+
id: string;
|
|
93
|
+
cli?: VibeCli;
|
|
94
|
+
ownerId: string;
|
|
95
|
+
state?: VibeSessionState;
|
|
96
|
+
}): void;
|
|
86
97
|
listIds(owner: string): string[];
|
|
87
98
|
/**
|
|
88
99
|
* Live screen snapshots for rich rendering (the "TV wall"): one entry per
|
|
@@ -122,3 +133,15 @@ export declare class VibeSessionRegistry {
|
|
|
122
133
|
/** Kill every session belonging to `owner` (vibe-mode exit / teardown). Returns the number killed. */
|
|
123
134
|
killAll(owner: string, manager?: AsyncJobManager): Promise<number>;
|
|
124
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Aggregate tok/s across every live vibe worker session owned by `ownerId`.
|
|
138
|
+
* Returns null when no workers are streaming (so callers can fall back to
|
|
139
|
+
* their own rate unchanged). The director is often idle while workers stream,
|
|
140
|
+
* so without this aggregation the status-line tok/s badge would show a stale
|
|
141
|
+
* value while parallel work is actively generating tokens.
|
|
142
|
+
*
|
|
143
|
+
* Reads each worker's last assistant message via {@link calculateTokensPerSecond}
|
|
144
|
+
* — the same leaf calculator the main status line uses — so worker rates are
|
|
145
|
+
* computed identically to the main session's rate.
|
|
146
|
+
*/
|
|
147
|
+
export declare function aggregateVibeWorkerTokensPerSecond(ownerId: string): number | null;
|
|
@@ -25,7 +25,7 @@ export interface CodexSearchParams {
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function searchCodex(params: SearchParams): Promise<SearchResponse>;
|
|
27
27
|
/**
|
|
28
|
-
* Checks
|
|
28
|
+
* Checks whether Codex web search has an API key or OAuth credential.
|
|
29
29
|
*/
|
|
30
30
|
export declare function hasCodexSearch(authStorage: AuthStorage): Promise<boolean>;
|
|
31
31
|
/** Search provider for OpenAI Codex web search. */
|
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.0.
|
|
4
|
+
"version": "17.0.6",
|
|
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,17 +52,17 @@
|
|
|
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.0.
|
|
56
|
-
"@oh-my-pi/omp-stats": "17.0.
|
|
57
|
-
"@oh-my-pi/pi-agent-core": "17.0.
|
|
58
|
-
"@oh-my-pi/pi-ai": "17.0.
|
|
59
|
-
"@oh-my-pi/pi-catalog": "17.0.
|
|
60
|
-
"@oh-my-pi/pi-mnemopi": "17.0.
|
|
61
|
-
"@oh-my-pi/pi-natives": "17.0.
|
|
62
|
-
"@oh-my-pi/pi-tui": "17.0.
|
|
63
|
-
"@oh-my-pi/pi-utils": "17.0.
|
|
64
|
-
"@oh-my-pi/pi-wire": "17.0.
|
|
65
|
-
"@oh-my-pi/snapcompact": "17.0.
|
|
55
|
+
"@oh-my-pi/hashline": "17.0.6",
|
|
56
|
+
"@oh-my-pi/omp-stats": "17.0.6",
|
|
57
|
+
"@oh-my-pi/pi-agent-core": "17.0.6",
|
|
58
|
+
"@oh-my-pi/pi-ai": "17.0.6",
|
|
59
|
+
"@oh-my-pi/pi-catalog": "17.0.6",
|
|
60
|
+
"@oh-my-pi/pi-mnemopi": "17.0.6",
|
|
61
|
+
"@oh-my-pi/pi-natives": "17.0.6",
|
|
62
|
+
"@oh-my-pi/pi-tui": "17.0.6",
|
|
63
|
+
"@oh-my-pi/pi-utils": "17.0.6",
|
|
64
|
+
"@oh-my-pi/pi-wire": "17.0.6",
|
|
65
|
+
"@oh-my-pi/snapcompact": "17.0.6",
|
|
66
66
|
"@opentelemetry/api": "^1.9.1",
|
|
67
67
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
68
68
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "bun:test";
|
|
|
2
2
|
import type { AgentMessage, AgentTelemetryConfig } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
5
|
+
import { kCursorExecResolved } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
5
6
|
import type { TUI } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { type } from "arktype";
|
|
7
8
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
@@ -519,6 +520,58 @@ describe("advisor", () => {
|
|
|
519
520
|
expect(message.content).toBe(originalContent);
|
|
520
521
|
});
|
|
521
522
|
|
|
523
|
+
it("keeps advise when Cursor emits exec-resolved native tools outside the grant (issue #5900)", () => {
|
|
524
|
+
const message = {
|
|
525
|
+
role: "assistant",
|
|
526
|
+
content: [
|
|
527
|
+
{ type: "text", text: "Investigating the networking design." },
|
|
528
|
+
{
|
|
529
|
+
type: "toolCall",
|
|
530
|
+
id: "tc-grep",
|
|
531
|
+
name: "grep",
|
|
532
|
+
arguments: { pattern: "backoff" },
|
|
533
|
+
[kCursorExecResolved]: true,
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
type: "toolCall",
|
|
537
|
+
id: "tc-bash",
|
|
538
|
+
name: "bash",
|
|
539
|
+
arguments: { command: "ls" },
|
|
540
|
+
[kCursorExecResolved]: true,
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
type: "toolCall",
|
|
544
|
+
id: "tc-advise",
|
|
545
|
+
name: "advise",
|
|
546
|
+
arguments: { note: "The retry backoff looks unbounded." },
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
stopReason: "toolUse",
|
|
550
|
+
} as unknown as AssistantMessage;
|
|
551
|
+
const originalContent = message.content;
|
|
552
|
+
|
|
553
|
+
// Grant is `advise` only (WATCHDOG.yml `tools: []`). The native grep/bash
|
|
554
|
+
// frames already ran server-side through the advisor-scoped bridge, which
|
|
555
|
+
// rejected them in-band; they must not discard the legitimate advise.
|
|
556
|
+
expect(quarantineAdvisorUnsafeOutput(message, new Set(["advise"]))).toBeUndefined();
|
|
557
|
+
expect(message.stopReason).toBe("toolUse");
|
|
558
|
+
expect(message.content).toBe(originalContent);
|
|
559
|
+
expect(JSON.stringify(message)).toContain("unbounded");
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("still quarantines an ungranted native tool that was not exec-resolved", () => {
|
|
563
|
+
const message = {
|
|
564
|
+
role: "assistant",
|
|
565
|
+
content: [{ type: "toolCall", id: "tc-bash", name: "bash", arguments: { command: "ls" } }],
|
|
566
|
+
stopReason: "toolUse",
|
|
567
|
+
} as unknown as AssistantMessage;
|
|
568
|
+
|
|
569
|
+
expect(quarantineAdvisorUnsafeOutput(message, new Set(["advise"]))).toBe(
|
|
570
|
+
"Advisor response quarantined: requested unavailable tool bash",
|
|
571
|
+
);
|
|
572
|
+
expect(message.stopReason).toBe("error");
|
|
573
|
+
});
|
|
574
|
+
|
|
522
575
|
it("sanitizes destructive advise notes even when advise is an allowed tool", () => {
|
|
523
576
|
const message = {
|
|
524
577
|
role: "assistant",
|
|
@@ -865,6 +918,65 @@ describe("advisor", () => {
|
|
|
865
918
|
expect(promptInputs[1]).toContain("second");
|
|
866
919
|
});
|
|
867
920
|
|
|
921
|
+
it("waits for an in-flight review within the catch-up deadline", async () => {
|
|
922
|
+
const promptStarted = Promise.withResolvers<void>();
|
|
923
|
+
const releasePrompt = Promise.withResolvers<void>();
|
|
924
|
+
const messages: AgentMessage[] = [{ role: "user", content: "first", timestamp: 1 } as AgentMessage];
|
|
925
|
+
const agent: AdvisorAgent = {
|
|
926
|
+
prompt: async () => {
|
|
927
|
+
promptStarted.resolve();
|
|
928
|
+
await releasePrompt.promise;
|
|
929
|
+
},
|
|
930
|
+
abort: () => {},
|
|
931
|
+
reset: () => {},
|
|
932
|
+
state: { messages: [] },
|
|
933
|
+
};
|
|
934
|
+
const runtime = new AdvisorRuntime(agent, {
|
|
935
|
+
snapshotMessages: () => messages,
|
|
936
|
+
enqueueAdvice: () => {},
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
runtime.onTurnEnd();
|
|
940
|
+
await promptStarted.promise;
|
|
941
|
+
let settled = false;
|
|
942
|
+
const catchup = runtime.waitForCatchup(1000, 1).then(caughtUp => {
|
|
943
|
+
settled = true;
|
|
944
|
+
return caughtUp;
|
|
945
|
+
});
|
|
946
|
+
await Promise.resolve();
|
|
947
|
+
expect(settled).toBe(false);
|
|
948
|
+
|
|
949
|
+
releasePrompt.resolve();
|
|
950
|
+
expect(await catchup).toBe(true);
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
it("reports an in-flight review that exceeds the catch-up deadline", async () => {
|
|
954
|
+
const promptStarted = Promise.withResolvers<void>();
|
|
955
|
+
const releasePrompt = Promise.withResolvers<void>();
|
|
956
|
+
const messages: AgentMessage[] = [{ role: "user", content: "first", timestamp: 1 } as AgentMessage];
|
|
957
|
+
const agent: AdvisorAgent = {
|
|
958
|
+
prompt: async () => {
|
|
959
|
+
promptStarted.resolve();
|
|
960
|
+
await releasePrompt.promise;
|
|
961
|
+
},
|
|
962
|
+
abort: () => {},
|
|
963
|
+
reset: () => {},
|
|
964
|
+
state: { messages: [] },
|
|
965
|
+
};
|
|
966
|
+
const runtime = new AdvisorRuntime(agent, {
|
|
967
|
+
snapshotMessages: () => messages,
|
|
968
|
+
enqueueAdvice: () => {},
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
runtime.onTurnEnd();
|
|
972
|
+
await promptStarted.promise;
|
|
973
|
+
expect(await runtime.waitForCatchup(20, 1)).toBe(false);
|
|
974
|
+
expect(runtime.backlog).toBe(1);
|
|
975
|
+
|
|
976
|
+
releasePrompt.resolve();
|
|
977
|
+
await settleUntil(() => runtime.backlog === 0);
|
|
978
|
+
});
|
|
979
|
+
|
|
868
980
|
it("preserves the next user turn when an accepted empty stop is pruned", async () => {
|
|
869
981
|
const promptInputs: string[] = [];
|
|
870
982
|
const agent = makeAgent(promptInputs);
|
|
@@ -3779,6 +3891,44 @@ describe("advisor", () => {
|
|
|
3779
3891
|
// or it strands and #drainStrandedQueuedMessages auto-resumes it. Do not swap
|
|
3780
3892
|
// the call site back to session `isStreaming`.
|
|
3781
3893
|
describe("resolveAdvisorDeliveryChannel", () => {
|
|
3894
|
+
it("preserves every severity when a headless drain forbids primary turns", () => {
|
|
3895
|
+
for (const severity of [undefined, "nit", "concern", "blocker"] as const) {
|
|
3896
|
+
expect(
|
|
3897
|
+
resolveAdvisorDeliveryChannel({
|
|
3898
|
+
severity,
|
|
3899
|
+
autoResumeSuppressed: false,
|
|
3900
|
+
streaming: false,
|
|
3901
|
+
aborting: false,
|
|
3902
|
+
terminalAnswerNoQueuedWork: true,
|
|
3903
|
+
preserveOnly: true,
|
|
3904
|
+
}),
|
|
3905
|
+
).toBe("preserve");
|
|
3906
|
+
}
|
|
3907
|
+
});
|
|
3908
|
+
|
|
3909
|
+
it("keeps live headless advice on normal delivery channels until the primary finishes", () => {
|
|
3910
|
+
expect(
|
|
3911
|
+
resolveAdvisorDeliveryChannel({
|
|
3912
|
+
severity: "nit",
|
|
3913
|
+
autoResumeSuppressed: false,
|
|
3914
|
+
streaming: true,
|
|
3915
|
+
aborting: false,
|
|
3916
|
+
preserveOnly: true,
|
|
3917
|
+
}),
|
|
3918
|
+
).toBe("aside");
|
|
3919
|
+
for (const severity of ["concern", "blocker"] as const) {
|
|
3920
|
+
expect(
|
|
3921
|
+
resolveAdvisorDeliveryChannel({
|
|
3922
|
+
severity,
|
|
3923
|
+
autoResumeSuppressed: false,
|
|
3924
|
+
streaming: true,
|
|
3925
|
+
aborting: false,
|
|
3926
|
+
preserveOnly: true,
|
|
3927
|
+
}),
|
|
3928
|
+
).toBe("steer");
|
|
3929
|
+
}
|
|
3930
|
+
});
|
|
3931
|
+
|
|
3782
3932
|
it("routes a non-interrupting nit to the aside queue regardless of state", () => {
|
|
3783
3933
|
expect(
|
|
3784
3934
|
resolveAdvisorDeliveryChannel({
|
|
@@ -101,6 +101,8 @@ export function isAdvisorInterruptImmuneTurnActive(opts: {
|
|
|
101
101
|
/**
|
|
102
102
|
* Decide how one advisor note reaches the primary agent.
|
|
103
103
|
*
|
|
104
|
+
* - A `preserveOnly` caller records every note that arrives while the primary
|
|
105
|
+
* is idle as a visible card and never starts a new primary turn.
|
|
104
106
|
* - A non-interrupting `nit` always rides the non-interrupting aside queue.
|
|
105
107
|
* - An interrupting `concern`/`blocker` is normally steered into the agent: into
|
|
106
108
|
* the live turn while one is streaming, or (when idle) a triggered turn so the
|
|
@@ -129,7 +131,9 @@ export function resolveAdvisorDeliveryChannel(opts: {
|
|
|
129
131
|
aborting: boolean;
|
|
130
132
|
terminalAnswerNoQueuedWork?: boolean;
|
|
131
133
|
interruptImmuneTurnActive?: boolean;
|
|
134
|
+
preserveOnly?: boolean;
|
|
132
135
|
}): AdvisorDeliveryChannel {
|
|
136
|
+
if (opts.preserveOnly && !opts.streaming) return "preserve";
|
|
133
137
|
if (!isInterruptingSeverity(opts.severity)) return "aside";
|
|
134
138
|
if (opts.autoResumeSuppressed && (opts.aborting || !opts.streaming)) return "preserve";
|
|
135
139
|
if (opts.terminalAnswerNoQueuedWork && opts.severity !== "blocker" && !opts.streaming && !opts.aborting)
|