@oh-my-pi/pi-coding-agent 17.1.2 → 17.1.4
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 +63 -0
- package/dist/cli.js +3806 -3798
- package/dist/types/cli/__tests__/auth-gateway-catalog.test.d.ts +1 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +8 -0
- package/dist/types/cli/update-cli.d.ts +41 -0
- package/dist/types/cli/usage-cli.d.ts +4 -2
- package/dist/types/commands/update.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +19 -0
- package/dist/types/config/settings-schema.d.ts +30 -7
- package/dist/types/cursor.d.ts +47 -1
- package/dist/types/eval/js/process-entry.d.ts +2 -1
- package/dist/types/eval/js/worker-core.d.ts +4 -1
- package/dist/types/extensibility/extensions/runner.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +4 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +4 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +15 -7
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +9 -0
- package/dist/types/modes/components/custom-editor.d.ts +12 -8
- package/dist/types/plan-mode/approved-plan.d.ts +3 -2
- package/dist/types/secrets/obfuscator.d.ts +7 -31
- package/dist/types/session/agent-session.d.ts +19 -4
- package/dist/types/session/prewalk.d.ts +2 -1
- package/dist/types/session/session-advisors.d.ts +8 -0
- package/dist/types/session/session-metadata.d.ts +29 -0
- package/dist/types/session/turn-recovery.d.ts +5 -5
- package/dist/types/stt/sherpa-runtime.d.ts +38 -0
- package/dist/types/thinking.d.ts +24 -0
- package/dist/types/tools/auto-generated-guard.d.ts +5 -2
- package/dist/types/tools/bash.d.ts +5 -4
- package/dist/types/tools/computer/exposure.d.ts +8 -0
- package/dist/types/tools/computer/supervisor.d.ts +1 -1
- package/dist/types/tools/computer/worker-entry.d.ts +1 -1
- package/dist/types/tools/computer/worker.d.ts +1 -1
- package/dist/types/tools/computer.d.ts +6 -0
- package/dist/types/tools/memory-render.d.ts +1 -4
- package/dist/types/tools/shell-tokenize.d.ts +14 -0
- package/dist/types/tools/todo.d.ts +7 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +80 -10
- package/src/advisor/runtime.ts +27 -1
- package/src/cli/__tests__/auth-gateway-catalog.test.ts +111 -0
- package/src/cli/auth-gateway-cli.ts +63 -16
- package/src/cli/config-cli.ts +25 -7
- package/src/cli/update-cli.ts +229 -29
- package/src/cli/usage-cli.ts +144 -15
- package/src/cli.ts +21 -15
- package/src/commands/update.ts +6 -0
- package/src/config/__tests__/model-registry.test.ts +42 -7
- package/src/config/model-registry.ts +67 -4
- package/src/config/settings-schema.ts +39 -8
- package/src/config/settings.ts +24 -2
- package/src/cursor.ts +153 -0
- package/src/dap/session.ts +70 -11
- package/src/edit/hashline/filesystem.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/eval/__tests__/js-context-manager.test.ts +9 -1
- package/src/eval/__tests__/process-entry-import.test.ts +111 -1
- package/src/eval/js/process-entry.ts +9 -5
- package/src/eval/js/worker-core.ts +6 -2
- package/src/exec/bash-executor.ts +4 -2
- package/src/extensibility/extensions/runner.ts +23 -8
- package/src/extensibility/legacy-pi-ai-shim.ts +9 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +14 -2
- package/src/extensibility/legacy-pi-tui-shim.ts +33 -0
- package/src/extensibility/shared-events.ts +2 -0
- package/src/main.ts +22 -1
- package/src/modes/acp/acp-agent.ts +6 -0
- package/src/modes/components/assistant-message.ts +88 -11
- package/src/modes/components/chat-transcript-builder.ts +2 -0
- package/src/modes/components/custom-editor.test.ts +170 -0
- package/src/modes/components/custom-editor.ts +79 -29
- package/src/modes/components/settings-defs.ts +5 -1
- package/src/modes/controllers/event-controller.ts +96 -4
- package/src/modes/controllers/selector-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +34 -8
- package/src/modes/utils/context-usage.ts +19 -2
- package/src/modes/utils/interactive-context-helpers.ts +1 -0
- package/src/plan-mode/approved-plan.ts +18 -10
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +10 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/sdk.ts +4 -0
- package/src/secrets/obfuscator.ts +36 -126
- package/src/session/agent-session.ts +69 -60
- package/src/session/prewalk.ts +8 -3
- package/src/session/session-advisors.ts +67 -3
- package/src/session/session-metadata.ts +53 -0
- package/src/session/session-provider-boundary.ts +0 -1
- package/src/session/session-tools.ts +35 -1
- package/src/session/stream-guards.ts +1 -1
- package/src/session/turn-recovery.ts +36 -19
- package/src/slash-commands/builtin-registry.ts +49 -7
- package/src/stt/asr-worker.ts +2 -37
- package/src/stt/sherpa-runtime.ts +71 -0
- package/src/task/executor.ts +6 -5
- package/src/thinking.ts +39 -0
- package/src/tools/auto-generated-guard.ts +18 -5
- package/src/tools/bash.ts +43 -15
- package/src/tools/computer/exposure.ts +38 -0
- package/src/tools/computer/supervisor.ts +61 -13
- package/src/tools/computer/worker-entry.ts +28 -19
- package/src/tools/computer/worker.ts +3 -7
- package/src/tools/computer.ts +65 -10
- package/src/tools/gh-cache-invalidation.ts +2 -82
- package/src/tools/memory-render.ts +11 -2
- package/src/tools/render-utils.ts +8 -3
- package/src/tools/shell-tokenize.ts +83 -0
- package/src/tools/todo.ts +44 -17
- package/src/tools/write.ts +1 -1
|
@@ -342,10 +342,7 @@ export declare class AgentSession {
|
|
|
342
342
|
* Transcript for TUI display. Full history is kept for export/resume-style
|
|
343
343
|
* callers; live chat can collapse compacted history to keep the hot render
|
|
344
344
|
* surface bounded. Display-only — NEVER feed the result to
|
|
345
|
-
* `agent.replaceMessages` or a provider.
|
|
346
|
-
* it opts into legacy index-derived alias restoration so pre-keyed sessions
|
|
347
|
-
* still render their secrets; the agent-feeding paths
|
|
348
|
-
* (`buildDisplaySessionContext`) keep the keyed-only default.
|
|
345
|
+
* `agent.replaceMessages` or a provider.
|
|
349
346
|
*/
|
|
350
347
|
buildTranscriptSessionContext(options?: Pick<BuildSessionContextOptions, "collapseCompactedHistory" | "keepDanglingToolCalls">): SessionContext;
|
|
351
348
|
/** Convert session messages using the same pre-LLM pipeline as the active session. */
|
|
@@ -830,7 +827,25 @@ export declare class AgentSession {
|
|
|
830
827
|
toolCallId: string;
|
|
831
828
|
questions: AskToolInput["questions"];
|
|
832
829
|
};
|
|
830
|
+
/**
|
|
831
|
+
* `true` when this call committed a new sibling answer for an `ask`
|
|
832
|
+
* re-answer (`reanswerAskResult` was applied). The interactive caller
|
|
833
|
+
* resumes the agent via {@link resumeAfterAskReanswer} *after* rebuilding
|
|
834
|
+
* its transcript, so the resumed turn never renders against the stale
|
|
835
|
+
* pre-rebuild UI (issue #6483).
|
|
836
|
+
*/
|
|
837
|
+
askReanswerCommitted?: boolean;
|
|
833
838
|
}>;
|
|
839
|
+
/**
|
|
840
|
+
* Resume the agent after the interactive `/tree` caller has committed an
|
|
841
|
+
* `ask` re-answer (`navigateTree` returned `askReanswerCommitted`) and
|
|
842
|
+
* rebuilt its transcript. Mirrors how a live `ask` completion drives a
|
|
843
|
+
* follow-up turn, but is deferred to the caller so the resumed turn renders
|
|
844
|
+
* against the rebuilt UI rather than the stale pre-navigation transcript
|
|
845
|
+
* (issue #6483). The scheduled continue honors the same disposed/compacting
|
|
846
|
+
* guards as every other post-prompt continuation.
|
|
847
|
+
*/
|
|
848
|
+
resumeAfterAskReanswer(): void;
|
|
834
849
|
/**
|
|
835
850
|
* Build a standalone `AgentToolContext` for running `AskTool.execute()`
|
|
836
851
|
* outside a normal agent turn, for `/tree` `ask` re-answer (issue #5642).
|
|
@@ -2,7 +2,7 @@ import type { Agent, AgentMessage, AgentTurnEndContext } from "@oh-my-pi/pi-agen
|
|
|
2
2
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { LocalProtocolOptions } from "../internal-urls/index.js";
|
|
4
4
|
import type { PlanModeState } from "../plan-mode/state.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type ConfiguredThinkingLevel } from "../thinking.js";
|
|
6
6
|
import type { PlanProposalHandler } from "../tools/resolve.js";
|
|
7
7
|
import type { PlanYolo, Prewalk } from "./agent-session-types.js";
|
|
8
8
|
import type { SessionManager } from "./session-manager.js";
|
|
@@ -11,6 +11,7 @@ export interface PrewalkCoordinatorHost {
|
|
|
11
11
|
agent: Agent;
|
|
12
12
|
sessionManager: SessionManager;
|
|
13
13
|
model(): Model | undefined;
|
|
14
|
+
configuredThinkingLevel(): ConfiguredThinkingLevel | undefined;
|
|
14
15
|
emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void;
|
|
15
16
|
setModelTemporary(model: Model, thinkingLevel?: ConfiguredThinkingLevel, options?: {
|
|
16
17
|
ephemeral?: boolean;
|
|
@@ -122,6 +122,14 @@ export declare class SessionAdvisors {
|
|
|
122
122
|
detachAndCloseRecorders(): Promise<void>;
|
|
123
123
|
/** Re-primes advisor transcript views across a conversation boundary. */
|
|
124
124
|
resetSessionState(): void;
|
|
125
|
+
/**
|
|
126
|
+
* Rebind every live advisor to the active primary conversation's provider
|
|
127
|
+
* identity (session id, prompt-cache key, credential + metadata resolvers,
|
|
128
|
+
* telemetry). Invoked on every provider-session change — including branch
|
|
129
|
+
* paths that skip conversation restore — so advisors never keep emitting the
|
|
130
|
+
* previous conversation's session id/metadata (issue #6625).
|
|
131
|
+
*/
|
|
132
|
+
refreshProviderIdentity(): void;
|
|
125
133
|
/** Re-primes advisor transcript views after an in-conversation history rewrite. */
|
|
126
134
|
resetAllRuntimes(): void;
|
|
127
135
|
/** Whether live runtimes still match the resolved advisor configuration. */
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AuthStorage } from "./auth-storage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the per-request `metadata` payload for the Anthropic provider, shaped
|
|
4
|
+
* like real Claude Code's `getAPIMetadata` output (`{ session_id, account_uuid,
|
|
5
|
+
* device_id }`) so the backend buckets requests under one session and attributes
|
|
6
|
+
* them to the authenticated OAuth account when available. Resolved at request
|
|
7
|
+
* time so token refreshes and login/logout transitions don't strand a stale
|
|
8
|
+
* account UUID in memory. `account_uuid` and `device_id` are omitted for
|
|
9
|
+
* non-Anthropic providers to avoid leaking the user's Claude identity to
|
|
10
|
+
* third-party APIs (including Anthropic-format-compatible proxies such as
|
|
11
|
+
* cloudflare-ai-gateway or gitlab-duo).
|
|
12
|
+
*
|
|
13
|
+
* Installed via `Agent#setMetadataResolver` on the main `AgentSession`, each
|
|
14
|
+
* subagent session, and the separately constructed advisor `Agent` — each with
|
|
15
|
+
* its own provider session id — so Main, subagent, and Advisor requests each
|
|
16
|
+
* expose a distinct, stable provider-facing session identity.
|
|
17
|
+
*
|
|
18
|
+
* `provider` is the target provider string (e.g. `"anthropic"`) and gates the
|
|
19
|
+
* `account_uuid` and `device_id` lookups — only `"anthropic"` requests carry them.
|
|
20
|
+
*
|
|
21
|
+
* `sessionId` is forwarded to the auth-storage session-sticky lookup so that
|
|
22
|
+
* multi-credential setups attribute to the same OAuth account used for the
|
|
23
|
+
* actual API request rather than always picking the first credential.
|
|
24
|
+
*
|
|
25
|
+
* `authStorage` is treated as optional so test fixtures that stub `modelRegistry`
|
|
26
|
+
* without a real storage layer still work; the resolver simply skips the lookup
|
|
27
|
+
* and emits `{ session_id }` alone, matching the no-OAuth-credential path.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildSessionMetadata(sessionId: string, provider: string, authStorage: AuthStorage | undefined): Record<string, unknown>;
|
|
@@ -137,12 +137,12 @@ export declare class TurnRecovery {
|
|
|
137
137
|
*/
|
|
138
138
|
isRetryableError(message: AssistantMessage): boolean;
|
|
139
139
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
140
|
+
* Classify a reasonless abort or stream stall whose emitted tool calls all
|
|
141
|
+
* have results. The failed assistant/tool-result pair stays in context so
|
|
142
|
+
* continuation cannot replay completed side effects; synthetic results tell
|
|
143
|
+
* the next turn that an unexecuted call must be reissued.
|
|
144
144
|
*/
|
|
145
|
-
|
|
145
|
+
classifyResolvedInterruptedToolTurn(message: AssistantMessage): "reasonless-abort" | "stream-stall" | undefined;
|
|
146
146
|
/** Checks whether a provider error represents a classifier refusal. */
|
|
147
147
|
isClassifierRefusal(message: AssistantMessage): boolean;
|
|
148
148
|
/** Clears fallback ownership after an explicit model change. */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface SherpaOfflineResult {
|
|
2
|
+
text?: string;
|
|
3
|
+
}
|
|
4
|
+
interface SherpaOfflineStream {
|
|
5
|
+
acceptWaveform(audio: {
|
|
6
|
+
samples: Float32Array;
|
|
7
|
+
sampleRate: number;
|
|
8
|
+
}): void;
|
|
9
|
+
}
|
|
10
|
+
interface SherpaOfflineConfig {
|
|
11
|
+
modelConfig: {
|
|
12
|
+
transducer: {
|
|
13
|
+
encoder: string;
|
|
14
|
+
decoder: string;
|
|
15
|
+
joiner: string;
|
|
16
|
+
};
|
|
17
|
+
tokens: string;
|
|
18
|
+
modelType: string;
|
|
19
|
+
numThreads: number;
|
|
20
|
+
provider: string;
|
|
21
|
+
debug: number;
|
|
22
|
+
};
|
|
23
|
+
decodingMethod: string;
|
|
24
|
+
}
|
|
25
|
+
/** A sherpa-onnx recognizer instance used by the STT worker. */
|
|
26
|
+
export interface SherpaOfflineRecognizer {
|
|
27
|
+
createStream(): SherpaOfflineStream;
|
|
28
|
+
decodeAsync(stream: SherpaOfflineStream): Promise<SherpaOfflineResult>;
|
|
29
|
+
}
|
|
30
|
+
/** The native sherpa-onnx module surface used by the STT worker. */
|
|
31
|
+
export interface SherpaRuntime {
|
|
32
|
+
OfflineRecognizer: {
|
|
33
|
+
createAsync(config: SherpaOfflineConfig): Promise<SherpaOfflineRecognizer>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Loads the nearest working source-workspace sherpa wrapper, including hoisted fallbacks. */
|
|
37
|
+
export declare function loadSourceSherpaRuntime(sourceUrl: string): SherpaRuntime;
|
|
38
|
+
export {};
|
package/dist/types/thinking.d.ts
CHANGED
|
@@ -43,6 +43,30 @@ export declare const AUTO_THINKING: "auto";
|
|
|
43
43
|
export type ConfiguredThinkingLevel = ThinkingLevel | typeof AUTO_THINKING;
|
|
44
44
|
/** Maps the session-level `auto` sentinel to `undefined`; concrete levels pass through. */
|
|
45
45
|
export declare function concreteThinkingLevel(level: ConfiguredThinkingLevel | undefined): ThinkingLevel | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* True when a prewalk hand-off from `current`/`currentLevel` to
|
|
48
|
+
* `target`/`targetLevel` would change nothing observable: same model id, same
|
|
49
|
+
* auto/fixed mode, and the same model-clamped effective effort. Prewalk arms and
|
|
50
|
+
* switches only when this is false.
|
|
51
|
+
*
|
|
52
|
+
* An effort-only delta on the same model id is a legitimate cheapening hand-off
|
|
53
|
+
* — on a reasoning model the effort is the bulk of the cost — so it is NOT a
|
|
54
|
+
* no-op and must still switch. A `targetLevel` of `undefined` means the prewalk
|
|
55
|
+
* pattern carried no explicit `:level` suffix (no effort change requested),
|
|
56
|
+
* which on the same model is a no-op.
|
|
57
|
+
*
|
|
58
|
+
* `auto` mode is compared before efforts: `auto` and a fixed selector that both
|
|
59
|
+
* resolve to `undefined` effort (e.g. `:inherit`) are NOT interchangeable —
|
|
60
|
+
* applying the fixed selector clears per-turn classification, so switching
|
|
61
|
+
* auto↔fixed is always a real change even when the clamped efforts match.
|
|
62
|
+
*
|
|
63
|
+
* Efforts are otherwise compared AFTER model clamping, so a target the model
|
|
64
|
+
* cannot honor (e.g. `:xhigh` on a model capped at `high`) — which
|
|
65
|
+
* `setThinkingLevel` would clamp straight back to the active effort — is
|
|
66
|
+
* recognized as a no-op instead of triggering an ephemeral reset and the
|
|
67
|
+
* plan/checklist nudges for nothing.
|
|
68
|
+
*/
|
|
69
|
+
export declare function prewalkWouldBeNoop(current: Model | undefined, currentLevel: ConfiguredThinkingLevel | undefined, target: Model, targetLevel: ConfiguredThinkingLevel | undefined): boolean;
|
|
46
70
|
/** Metadata used to render the `auto` selector value alongside concrete levels. */
|
|
47
71
|
export interface ConfiguredThinkingLevelMetadata {
|
|
48
72
|
value: ConfiguredThinkingLevel;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { type Settings } from "../config/settings.js";
|
|
1
2
|
/**
|
|
2
3
|
* Check if a file is auto-generated by examining its content.
|
|
3
4
|
* Throws ToolError if the file appears to be auto-generated.
|
|
4
5
|
*
|
|
5
6
|
* @param absolutePath - Absolute path to the file
|
|
6
7
|
* @param displayPath - Path to show in error messages (relative or as provided)
|
|
8
|
+
* @param activeSettings - Session settings; falls back to global settings or the schema default
|
|
7
9
|
*/
|
|
8
|
-
export declare function assertEditableFile(absolutePath: string, displayPath?: string): Promise<void>;
|
|
10
|
+
export declare function assertEditableFile(absolutePath: string, displayPath?: string, activeSettings?: Settings): Promise<void>;
|
|
9
11
|
/**
|
|
10
12
|
* Check if file content is auto-generated.
|
|
11
13
|
* Uses only the first CHECK_BYTE_COUNT characters of the content.
|
|
@@ -13,5 +15,6 @@ export declare function assertEditableFile(absolutePath: string, displayPath?: s
|
|
|
13
15
|
*
|
|
14
16
|
* @param content - File content to check (can be full content or prefix)
|
|
15
17
|
* @param displayPath - Path to show in error messages
|
|
18
|
+
* @param activeSettings - Session settings; falls back to global settings or the schema default
|
|
16
19
|
*/
|
|
17
|
-
export declare function assertEditableFileContent(content: string, displayPath: string): void;
|
|
20
|
+
export declare function assertEditableFileContent(content: string, displayPath: string, activeSettings?: Settings): void;
|
|
@@ -17,12 +17,13 @@ export declare const BASH_DEFAULT_PREVIEW_LINES = 10;
|
|
|
17
17
|
* containing a space, pipe, `&&`, redirect, or `$(...)`.
|
|
18
18
|
*
|
|
19
19
|
* The wrap reuses the same shell binary + args the local `bash-executor` would
|
|
20
|
-
* pick via `settings.getShellConfig()` — Git Bash / `bash.exe` on Windows
|
|
20
|
+
* pick via `settings.getShellConfig()` — Git Bash / `bash.exe` on Windows
|
|
21
|
+
* (`cmd.exe /c` as the last-resort fallback when no bash exists on the host),
|
|
21
22
|
* `$SHELL` (bash/zsh) with the `sh` fallback on POSIX — so the ACP path
|
|
22
23
|
* preserves `bash` tool semantics (`$VAR`, `$(...)`, `source`, POSIX quoting,
|
|
23
|
-
* `-l`)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* `-l`) wherever a POSIX shell is available. The agent host's shell path is
|
|
25
|
+
* used as a proxy for the client's, matching the near-universal ACP
|
|
26
|
+
* deployment shape of an editor spawning omp as a co-hosted subprocess.
|
|
26
27
|
*/
|
|
27
28
|
export declare function wrapShellLineForClientTerminal(line: string, shellConfig: {
|
|
28
29
|
shell: string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
export type ComputerExposureMode = "native" | "function" | "unavailable";
|
|
3
|
+
export interface ComputerExposureOptions {
|
|
4
|
+
azureBaseUrl?: string;
|
|
5
|
+
azureResourceName?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Match the provider transport's effective Computer Use tool representation. */
|
|
8
|
+
export declare function computerExposureMode(model: Model | undefined, options?: ComputerExposureOptions): ComputerExposureMode;
|
|
@@ -29,4 +29,4 @@ export declare class ComputerSupervisor implements ComputerController {
|
|
|
29
29
|
}
|
|
30
30
|
export declare function registerComputerController(ownerId: string | undefined, controller: ComputerController): () => void;
|
|
31
31
|
export declare function releaseComputerSessionsForOwner(ownerId: string | undefined): Promise<void>;
|
|
32
|
-
export declare function smokeTestComputerWorker(timeoutMs?: number): Promise<void>;
|
|
32
|
+
export declare function smokeTestComputerWorker(timeoutMs?: number, createWorker?: ComputerWorkerFactory): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function startComputerWorker(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DesktopAction, DesktopCapture, DesktopSession, DesktopSessionOptions } from "@oh-my-pi/pi-natives";
|
|
2
2
|
import type { ComputerWorkerTransport } from "./protocol.js";
|
|
3
3
|
export interface NativeDesktopSession {
|
|
4
4
|
readonly capabilities: DesktopSession["capabilities"];
|
|
@@ -63,6 +63,12 @@ export declare class ComputerTool implements AgentTool<typeof computerSchema, Co
|
|
|
63
63
|
readonly strict = false;
|
|
64
64
|
readonly approval: typeof computerApproval;
|
|
65
65
|
readonly formatApprovalDetails: (args: unknown) => string[];
|
|
66
|
+
/**
|
|
67
|
+
* Settings snapshot used to create the tool's current controller; refreshed
|
|
68
|
+
* when a model switch crosses the coordinate-safe sizing boundary. Surfaced
|
|
69
|
+
* by `/computer status`.
|
|
70
|
+
*/
|
|
71
|
+
effectiveConfiguration: Readonly<DesktopSessionOptions>;
|
|
66
72
|
constructor(session: ToolSession, createController?: ComputerControllerFactory);
|
|
67
73
|
get description(): string;
|
|
68
74
|
execute(_toolCallId: string, params: ComputerParams, signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<ComputerToolDetails>, context?: AgentToolContext): Promise<AgentToolResult<ComputerToolDetails>>;
|
|
@@ -13,10 +13,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
13
13
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types.js";
|
|
14
14
|
import type { Theme } from "../modes/theme/theme.js";
|
|
15
15
|
interface RetainRenderArgs {
|
|
16
|
-
items?:
|
|
17
|
-
content?: string;
|
|
18
|
-
context?: string;
|
|
19
|
-
}>;
|
|
16
|
+
items?: unknown;
|
|
20
17
|
}
|
|
21
18
|
interface QueryRenderArgs {
|
|
22
19
|
query?: string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative shell command tokenizer shared by the bash approval-pattern
|
|
3
|
+
* matcher and the gh-cache invalidator.
|
|
4
|
+
*
|
|
5
|
+
* Splits a bash command into independent command segments, each a list of word
|
|
6
|
+
* tokens. Handles single/double-quoted strings, backslash escapes, and the
|
|
7
|
+
* standard operators (`;`, `&&`, `||`, `|`, `&`, `(`, `)`, newlines) as segment
|
|
8
|
+
* boundaries so callers treat the pieces as independent command sequences.
|
|
9
|
+
*
|
|
10
|
+
* It is deliberately not a full POSIX parser — heredocs, command substitution,
|
|
11
|
+
* and arithmetic expansion are out of scope; callers fall through when they
|
|
12
|
+
* cannot find the structure they need.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tokenizeShellSegments(command: string): string[][];
|
|
@@ -144,7 +144,13 @@ type TodoRenderArgs = TodoRenderOp & {
|
|
|
144
144
|
};
|
|
145
145
|
/** One-based ASCII roman numeral for display (I, II, III, IV, …). */
|
|
146
146
|
export declare function phaseRomanNumeral(oneBasedIndex: number): string;
|
|
147
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Display-only phase header: `I. Foundation`. State and prompts never see this.
|
|
149
|
+
*
|
|
150
|
+
* Sanitized for the same reason task labels are: this is a render boundary and
|
|
151
|
+
* the name may carry provider or session text holding control sequences. The
|
|
152
|
+
* raw `phase.name` stays the lookup key everywhere else.
|
|
153
|
+
*/
|
|
148
154
|
export declare function formatPhaseDisplayName(name: string, oneBasedIndex: number): string;
|
|
149
155
|
export declare const TODO_STRIKE_HOLD_FRAMES = 2;
|
|
150
156
|
export declare const TODO_STRIKE_REVEAL_FRAMES = 12;
|
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.1.
|
|
4
|
+
"version": "17.1.4",
|
|
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",
|
|
@@ -53,17 +53,17 @@
|
|
|
53
53
|
"@babel/parser": "^7.29.7",
|
|
54
54
|
"@babel/traverse": "^7.29.7",
|
|
55
55
|
"@mozilla/readability": "^0.6.0",
|
|
56
|
-
"@oh-my-pi/hashline": "17.1.
|
|
57
|
-
"@oh-my-pi/omp-stats": "17.1.
|
|
58
|
-
"@oh-my-pi/pi-agent-core": "17.1.
|
|
59
|
-
"@oh-my-pi/pi-ai": "17.1.
|
|
60
|
-
"@oh-my-pi/pi-catalog": "17.1.
|
|
61
|
-
"@oh-my-pi/pi-mnemopi": "17.1.
|
|
62
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
63
|
-
"@oh-my-pi/pi-tui": "17.1.
|
|
64
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
65
|
-
"@oh-my-pi/pi-wire": "17.1.
|
|
66
|
-
"@oh-my-pi/snapcompact": "17.1.
|
|
56
|
+
"@oh-my-pi/hashline": "17.1.4",
|
|
57
|
+
"@oh-my-pi/omp-stats": "17.1.4",
|
|
58
|
+
"@oh-my-pi/pi-agent-core": "17.1.4",
|
|
59
|
+
"@oh-my-pi/pi-ai": "17.1.4",
|
|
60
|
+
"@oh-my-pi/pi-catalog": "17.1.4",
|
|
61
|
+
"@oh-my-pi/pi-mnemopi": "17.1.4",
|
|
62
|
+
"@oh-my-pi/pi-natives": "17.1.4",
|
|
63
|
+
"@oh-my-pi/pi-tui": "17.1.4",
|
|
64
|
+
"@oh-my-pi/pi-utils": "17.1.4",
|
|
65
|
+
"@oh-my-pi/pi-wire": "17.1.4",
|
|
66
|
+
"@oh-my-pi/snapcompact": "17.1.4",
|
|
67
67
|
"@opentelemetry/api": "^1.9.1",
|
|
68
68
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
69
69
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
@@ -1634,7 +1634,7 @@ describe("advisor", () => {
|
|
|
1634
1634
|
await Promise.resolve();
|
|
1635
1635
|
|
|
1636
1636
|
expect(promptInputs).toHaveLength(1);
|
|
1637
|
-
expect(promptInputs[0]).toContain("
|
|
1637
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1638
1638
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1639
1639
|
});
|
|
1640
1640
|
it("does not scan advisor-hidden successful tool-result bodies", async () => {
|
|
@@ -1666,7 +1666,7 @@ describe("advisor", () => {
|
|
|
1666
1666
|
await Promise.resolve();
|
|
1667
1667
|
|
|
1668
1668
|
expect(promptInputs).toHaveLength(1);
|
|
1669
|
-
expect(promptInputs[0]).toContain("
|
|
1669
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1670
1670
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1671
1671
|
});
|
|
1672
1672
|
it("does not scan tool-call arguments hidden by the primary-argument preview", async () => {
|
|
@@ -1693,7 +1693,7 @@ describe("advisor", () => {
|
|
|
1693
1693
|
});
|
|
1694
1694
|
runtime.onTurnEnd();
|
|
1695
1695
|
await runtime.waitForCatchup(1000, 1);
|
|
1696
|
-
expect(promptInputs[0]).toContain("
|
|
1696
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1697
1697
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1698
1698
|
});
|
|
1699
1699
|
|
|
@@ -1722,7 +1722,7 @@ describe("advisor", () => {
|
|
|
1722
1722
|
});
|
|
1723
1723
|
runtime.onTurnEnd();
|
|
1724
1724
|
await runtime.waitForCatchup(1000, 1);
|
|
1725
|
-
expect(promptInputs[0]).toContain("
|
|
1725
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1726
1726
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1727
1727
|
});
|
|
1728
1728
|
|
|
@@ -1761,7 +1761,7 @@ describe("advisor", () => {
|
|
|
1761
1761
|
await Promise.resolve();
|
|
1762
1762
|
|
|
1763
1763
|
expect(promptInputs).toHaveLength(1);
|
|
1764
|
-
expect(promptInputs[0]).toContain("
|
|
1764
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1765
1765
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1766
1766
|
});
|
|
1767
1767
|
it("does not scan execution source after the advisor preview cap", async () => {
|
|
@@ -1798,7 +1798,7 @@ describe("advisor", () => {
|
|
|
1798
1798
|
await Promise.resolve();
|
|
1799
1799
|
|
|
1800
1800
|
expect(promptInputs).toHaveLength(1);
|
|
1801
|
-
expect(promptInputs[0]).toContain("
|
|
1801
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1802
1802
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1803
1803
|
});
|
|
1804
1804
|
|
|
@@ -1829,7 +1829,7 @@ describe("advisor", () => {
|
|
|
1829
1829
|
await Promise.resolve();
|
|
1830
1830
|
|
|
1831
1831
|
expect(promptInputs).toHaveLength(1);
|
|
1832
|
-
expect(promptInputs[0]).toContain("
|
|
1832
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1833
1833
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1834
1834
|
expect(obfuscate).not.toHaveBeenCalledWith("tok_abc123", expect.anything());
|
|
1835
1835
|
});
|
|
@@ -1864,7 +1864,7 @@ describe("advisor", () => {
|
|
|
1864
1864
|
await Promise.resolve();
|
|
1865
1865
|
|
|
1866
1866
|
expect(promptInputs).toHaveLength(1);
|
|
1867
|
-
expect(promptInputs[0]).toContain("
|
|
1867
|
+
expect(promptInputs[0]).toContain("$$TOKABC123_");
|
|
1868
1868
|
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1869
1869
|
expect(obfuscate).not.toHaveBeenCalledWith("tok_abc123", expect.anything());
|
|
1870
1870
|
});
|
|
@@ -1876,7 +1876,7 @@ describe("advisor", () => {
|
|
|
1876
1876
|
// precomputation obfuscateMessages performs for the primary provider path
|
|
1877
1877
|
// (see secrets-obfuscator.test.ts). Redacting message fields independently
|
|
1878
1878
|
// would let the EARLIER user message's plain secret (OTHERSECRET) mint a
|
|
1879
|
-
// friendly-prefixed placeholder ("
|
|
1879
|
+
// friendly-prefixed placeholder ("$$TOKABC123_<hash>$$") before the SIBLING
|
|
1880
1880
|
// toolResult's `details.diff` field, later in the same delta, reveals the
|
|
1881
1881
|
// regex-protected value that friendly name normalizes to
|
|
1882
1882
|
// (tok_abc123 -> TOKABC123) — baking a normalized rendering of that
|
|
@@ -2004,7 +2004,7 @@ describe("advisor", () => {
|
|
|
2004
2004
|
// placeholder from a PRIOR thinking block: if thinking fell through
|
|
2005
2005
|
// unredacted, the advisor prompt would receive both the raw secret AND,
|
|
2006
2006
|
// had it been redacted without sharing the regex collision set, a
|
|
2007
|
-
// normalized "
|
|
2007
|
+
// normalized "$$TOKABC123_<hash>$$" rendering of the regex-protected value
|
|
2008
2008
|
// (tok_abc123) only discovered later in the same delta.
|
|
2009
2009
|
const obfuscator = new SecretObfuscator([
|
|
2010
2010
|
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
@@ -3938,6 +3938,76 @@ describe("advisor", () => {
|
|
|
3938
3938
|
expect(promptInputs[1]).toContain("bbb");
|
|
3939
3939
|
});
|
|
3940
3940
|
|
|
3941
|
+
it("notifies the host after the advisor persistently quarantines its output (issue #6661)", async () => {
|
|
3942
|
+
const state: { messages: AgentMessage[]; error?: string } = { messages: [] };
|
|
3943
|
+
let promptCalls = 0;
|
|
3944
|
+
let shouldQuarantine = true;
|
|
3945
|
+
const agent: AdvisorAgent = {
|
|
3946
|
+
prompt: async input => {
|
|
3947
|
+
promptCalls++;
|
|
3948
|
+
state.messages.push({ role: "user", content: input, timestamp: Date.now() } as AgentMessage);
|
|
3949
|
+
if (shouldQuarantine) {
|
|
3950
|
+
state.messages.push({
|
|
3951
|
+
role: "assistant",
|
|
3952
|
+
content: [
|
|
3953
|
+
{ type: "text", text: "The agent skipped the required plan step." },
|
|
3954
|
+
{ type: "toolCall", id: `tc-${promptCalls}`, name: "bash", arguments: { command: "ls" } },
|
|
3955
|
+
],
|
|
3956
|
+
stopReason: "toolUse",
|
|
3957
|
+
timestamp: Date.now(),
|
|
3958
|
+
} as unknown as AgentMessage);
|
|
3959
|
+
throw new AdvisorOutputQuarantinedError(
|
|
3960
|
+
"Advisor response quarantined: requested unavailable tool bash",
|
|
3961
|
+
);
|
|
3962
|
+
}
|
|
3963
|
+
state.messages.push({
|
|
3964
|
+
role: "assistant",
|
|
3965
|
+
content: [{ type: "text", text: "ok" }],
|
|
3966
|
+
timestamp: Date.now(),
|
|
3967
|
+
} as unknown as AgentMessage);
|
|
3968
|
+
},
|
|
3969
|
+
abort: () => {},
|
|
3970
|
+
reset: () => {
|
|
3971
|
+
state.messages.length = 0;
|
|
3972
|
+
state.error = undefined;
|
|
3973
|
+
},
|
|
3974
|
+
rollbackTo: count => {
|
|
3975
|
+
if (count < state.messages.length) state.messages.length = count;
|
|
3976
|
+
state.error = undefined;
|
|
3977
|
+
},
|
|
3978
|
+
state,
|
|
3979
|
+
};
|
|
3980
|
+
const notifyFailures: string[] = [];
|
|
3981
|
+
const messages: AgentMessage[] = [{ role: "user", content: "aaa", timestamp: 1 } as AgentMessage];
|
|
3982
|
+
const host: AdvisorRuntimeHost = {
|
|
3983
|
+
snapshotMessages: () => messages,
|
|
3984
|
+
enqueueAdvice: () => {},
|
|
3985
|
+
notifyFailure: err => notifyFailures.push(err instanceof Error ? err.message : String(err)),
|
|
3986
|
+
};
|
|
3987
|
+
const runtime = new AdvisorRuntime(agent, host, 0);
|
|
3988
|
+
|
|
3989
|
+
// Every advisor turn calls an ungranted tool and is quarantined, so its
|
|
3990
|
+
// advice never reaches the primary. A persistently-quarantining advisor is
|
|
3991
|
+
// a supervision failure the user must see in the main UI, not an unbounded
|
|
3992
|
+
// silent re-prime loop.
|
|
3993
|
+
for (let i = 2; i <= 5; i++) {
|
|
3994
|
+
messages.push({ role: "user", content: `msg-${i}`, timestamp: i } as AgentMessage);
|
|
3995
|
+
runtime.onTurnEnd(messages);
|
|
3996
|
+
await settleUntil(() => runtime.backlog === 0);
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
expect(promptCalls).toBeGreaterThanOrEqual(2);
|
|
4000
|
+
expect(notifyFailures).toEqual(["Advisor response quarantined: requested unavailable tool bash"]);
|
|
4001
|
+
expect(runtime.failureNotified).toBe(true);
|
|
4002
|
+
|
|
4003
|
+
shouldQuarantine = false;
|
|
4004
|
+
messages.push({ role: "user", content: "recovered", timestamp: 6 } as AgentMessage);
|
|
4005
|
+
runtime.onTurnEnd(messages);
|
|
4006
|
+
await settleUntil(() => runtime.backlog === 0);
|
|
4007
|
+
|
|
4008
|
+
expect(runtime.failureNotified).toBe(false);
|
|
4009
|
+
});
|
|
4010
|
+
|
|
3941
4011
|
it("drops the in-flight batch when a reset aborts the advisor prompt", async () => {
|
|
3942
4012
|
const promptInputs: string[] = [];
|
|
3943
4013
|
const { promise: firstPromptStarted, resolve: startFirstPrompt } = Promise.withResolvers<void>();
|
package/src/advisor/runtime.ts
CHANGED
|
@@ -218,6 +218,15 @@ export function buildAdvisorQuarantineSourceText(currentInput: string, messages:
|
|
|
218
218
|
*/
|
|
219
219
|
const MAX_COALESCE_ROUNDS = 3;
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Consecutive quarantined advisor turns tolerated before the failure is surfaced
|
|
223
|
+
* to the host UI. A quarantine discards the advisor's whole turn before dispatch,
|
|
224
|
+
* so its advice never reaches the primary; one silent re-prime is allowed to
|
|
225
|
+
* recover a one-off hallucination, but a persistent quarantine loop is a real
|
|
226
|
+
* supervision gap the user must see (issue #6661). Reset on any successful turn.
|
|
227
|
+
*/
|
|
228
|
+
const MAX_QUARANTINE_RETRIES = 2;
|
|
229
|
+
|
|
221
230
|
const ADVISOR_RENDER_OPTIONS = {
|
|
222
231
|
includeThinking: true,
|
|
223
232
|
includeToolIntent: true,
|
|
@@ -279,6 +288,8 @@ export class AdvisorRuntime {
|
|
|
279
288
|
#backlog = 0;
|
|
280
289
|
#consecutiveFailures = 0;
|
|
281
290
|
#failureNotified = false;
|
|
291
|
+
/** Consecutive quarantined turns since the last success/reset (issue #6661). */
|
|
292
|
+
#consecutiveQuarantines = 0;
|
|
282
293
|
/** Completed 3-failure backlog-drop cycles since the last success/reset. */
|
|
283
294
|
#droppedBacklogs = 0;
|
|
284
295
|
/**
|
|
@@ -450,7 +461,6 @@ export class AdvisorRuntime {
|
|
|
450
461
|
|
|
451
462
|
#clearAdvisorContextAtCurrentCursor(): void {
|
|
452
463
|
this.#consecutiveFailures = 0;
|
|
453
|
-
this.#failureNotified = false;
|
|
454
464
|
this.#clearSeenContext();
|
|
455
465
|
try {
|
|
456
466
|
this.agent.reset();
|
|
@@ -505,6 +515,8 @@ export class AdvisorRuntime {
|
|
|
505
515
|
this.#halted = false;
|
|
506
516
|
this.#failing = false;
|
|
507
517
|
this.#droppedBacklogs = 0;
|
|
518
|
+
this.#consecutiveQuarantines = 0;
|
|
519
|
+
this.#failureNotified = false;
|
|
508
520
|
this.#resetAdvisorContext(true, true);
|
|
509
521
|
}
|
|
510
522
|
|
|
@@ -865,6 +877,7 @@ export class AdvisorRuntime {
|
|
|
865
877
|
this.#consecutiveFailures = 0;
|
|
866
878
|
this.#failureNotified = false;
|
|
867
879
|
this.#droppedBacklogs = 0;
|
|
880
|
+
this.#consecutiveQuarantines = 0;
|
|
868
881
|
if (this.host.onTurnSuccess) {
|
|
869
882
|
try {
|
|
870
883
|
await this.host.onTurnSuccess();
|
|
@@ -906,6 +919,19 @@ export class AdvisorRuntime {
|
|
|
906
919
|
logger.debug("advisor onTurnError hook failed", { err: String(hookErr) });
|
|
907
920
|
}
|
|
908
921
|
if (err instanceof AdvisorOutputQuarantinedError) {
|
|
922
|
+
// A quarantine discards the advisor's whole turn before dispatch, so
|
|
923
|
+
// its advice never reaches the primary. One re-prime is allowed to
|
|
924
|
+
// recover a one-off hallucination silently; a persistent quarantine
|
|
925
|
+
// loop is a supervision gap the user must see in the main UI — not an
|
|
926
|
+
// unbounded silent retry. Surface it (deduped by #notifyFailureOnce)
|
|
927
|
+
// and drop the batch to break the loop (issue #6661).
|
|
928
|
+
this.#consecutiveQuarantines++;
|
|
929
|
+
if (this.#consecutiveQuarantines >= MAX_QUARANTINE_RETRIES) {
|
|
930
|
+
this.#notifyFailureOnce(err);
|
|
931
|
+
this.#consecutiveQuarantines = 0;
|
|
932
|
+
this.#resetAdvisorContext(true, true);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
909
935
|
const rePrime = this.#pending.length > 0 ? this.#latestMessages : undefined;
|
|
910
936
|
// Wake catchup waiters only when nothing is re-primed; otherwise the
|
|
911
937
|
// re-primed turn restores the backlog and waiters resolve on its completion.
|