@oh-my-pi/pi-coding-agent 16.5.1 → 16.5.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 +65 -0
- package/dist/cli.js +3442 -3408
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +22 -0
- package/dist/types/eval/backend.d.ts +3 -3
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -6
- package/dist/types/extensibility/plugins/bun-git-cache.d.ts +3 -0
- package/dist/types/goals/guided-setup.d.ts +12 -0
- package/dist/types/internal-urls/history-protocol.d.ts +3 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +19 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -0
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +10 -0
- package/dist/types/modes/components/dynamic-border.d.ts +5 -3
- package/dist/types/modes/components/login-dialog.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +0 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/session/messages.d.ts +15 -0
- package/dist/types/tools/grep.d.ts +0 -2
- package/dist/types/tools/read.d.ts +0 -4
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +136 -49
- package/src/advisor/runtime.ts +16 -34
- package/src/autoresearch/dashboard.ts +2 -2
- package/src/cli/config-cli.ts +15 -3
- package/src/config/settings-schema.ts +10 -0
- package/src/cursor.ts +2 -0
- package/src/discovery/claude-plugins.ts +9 -3
- package/src/discovery/omp-plugins.ts +6 -2
- package/src/discovery/substitute-plugin-root.ts +32 -0
- package/src/eval/__tests__/prelude-agent.test.ts +20 -0
- package/src/eval/backend.ts +3 -3
- package/src/eval/py/__tests__/prelude.test.ts +72 -0
- package/src/eval/py/prelude.py +28 -1
- package/src/exec/bash-executor.ts +30 -43
- package/src/extensibility/extensions/wrapper.ts +18 -18
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +32 -16
- package/src/extensibility/plugins/manager.ts +7 -7
- package/src/goals/guided-setup.ts +29 -1
- package/src/internal-urls/history-protocol.ts +95 -15
- package/src/internal-urls/registry-helpers.ts +50 -1
- package/src/launch/broker.ts +38 -25
- package/src/mcp/oauth-discovery.ts +20 -1
- package/src/mcp/oauth-flow.ts +3 -1
- package/src/modes/components/__tests__/dynamic-border.test.ts +55 -0
- package/src/modes/components/agent-dashboard.ts +2 -2
- package/src/modes/components/agent-hub.ts +15 -2
- package/src/modes/components/agent-transcript-viewer.ts +2 -2
- package/src/modes/components/chat-transcript-builder.ts +4 -3
- package/src/modes/components/dynamic-border.ts +9 -6
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/hook-selector.ts +10 -4
- package/src/modes/components/login-dialog.ts +5 -0
- package/src/modes/components/mcp-add-wizard.ts +5 -0
- package/src/modes/components/plan-review-overlay.ts +11 -11
- package/src/modes/components/read-tool-group.ts +1 -8
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/mcp-command-controller.ts +6 -7
- package/src/modes/controllers/selector-controller.ts +9 -2
- package/src/modes/controllers/todo-command-controller.ts +18 -14
- package/src/modes/interactive-mode.ts +7 -3
- package/src/modes/prompt-action-autocomplete.ts +6 -1
- package/src/modes/types.ts +1 -1
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/grep.md +1 -2
- package/src/prompts/tools/read.md +2 -4
- package/src/sdk.ts +28 -31
- package/src/session/agent-session.ts +44 -22
- package/src/session/messages.test.ts +66 -0
- package/src/session/messages.ts +37 -0
- package/src/system-prompt.test.ts +36 -0
- package/src/system-prompt.ts +1 -1
- package/src/tools/browser/registry.ts +17 -3
- package/src/tools/eval.ts +14 -9
- package/src/tools/gh.ts +3 -1
- package/src/tools/grep.ts +5 -45
- package/src/tools/path-utils.ts +7 -1
- package/src/tools/read.ts +23 -74
- package/src/utils/title-generator.ts +10 -6
- package/src/web/search/providers/perplexity-auth.ts +20 -11
- package/src/web/search/providers/perplexity.ts +14 -2
|
@@ -3699,6 +3699,16 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
3699
3699
|
readonly description: "Enable the tts tool for on-device (Kokoro) or xAI Grok Voice speech-file synthesis";
|
|
3700
3700
|
};
|
|
3701
3701
|
};
|
|
3702
|
+
readonly "generate_image.enabled": {
|
|
3703
|
+
readonly type: "boolean";
|
|
3704
|
+
readonly default: true;
|
|
3705
|
+
readonly ui: {
|
|
3706
|
+
readonly tab: "tools";
|
|
3707
|
+
readonly group: "Available Tools";
|
|
3708
|
+
readonly label: "Generate Image";
|
|
3709
|
+
readonly description: "Enable the generate_image tool for text-to-image generation and editing";
|
|
3710
|
+
};
|
|
3711
|
+
};
|
|
3702
3712
|
readonly "inspect_image.enabled": {
|
|
3703
3713
|
readonly type: "boolean";
|
|
3704
3714
|
readonly default: false;
|
|
@@ -1 +1,23 @@
|
|
|
1
1
|
export declare function substitutePluginRoot<T>(value: T, rootPath: string): T;
|
|
2
|
+
/**
|
|
3
|
+
* Rebase relative filesystem values in a discovered plugin stdio config against
|
|
4
|
+
* the directory of the `.mcp.json` that declared them.
|
|
5
|
+
*
|
|
6
|
+
* External plugin configs (bundled ChatGPT/Codex plugins, Claude marketplace
|
|
7
|
+
* plugins) express `command`/`cwd` relative to their own config file, but MCP
|
|
8
|
+
* stdio spawning roots relative values at the session cwd — so a plugin shipping
|
|
9
|
+
* `command: "./bin/server"`, `cwd: "."` launches from the wrong directory and
|
|
10
|
+
* fails with ENOENT. This resolves those against `configDir` instead:
|
|
11
|
+
*
|
|
12
|
+
* - relative `cwd` → resolved against `configDir`;
|
|
13
|
+
* - path-like `command` (`./`, `../`, or the Windows `.\`/`..\` forms) →
|
|
14
|
+
* resolved against `configDir`;
|
|
15
|
+
* - bare executables (`npx`, `uvx`, …) and absolute paths are left untouched.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePluginStdioPaths(config: {
|
|
18
|
+
command?: string;
|
|
19
|
+
cwd?: string;
|
|
20
|
+
}, configDir: string): {
|
|
21
|
+
command?: string;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
};
|
|
@@ -13,10 +13,10 @@ export interface ExecutorBackendExecOptions {
|
|
|
13
13
|
* driven entirely by `signal`, which the eval tool arms as a watchdog that
|
|
14
14
|
* pauses on bridge timeout-control status events and fires a `TimeoutError`
|
|
15
15
|
* reason only while the Python/JS runtime owns control. Backends use this
|
|
16
|
-
* value only for timeout-annotation text and as cold-start headroom;
|
|
17
|
-
* NOT derive a competing wall-clock timer from it.
|
|
16
|
+
* value only for timeout-annotation text and as cold-start headroom; undefined
|
|
17
|
+
* disables the cell timeout. Backends MUST NOT derive a competing wall-clock timer from it.
|
|
18
18
|
*/
|
|
19
|
-
idleTimeoutMs
|
|
19
|
+
idleTimeoutMs?: number;
|
|
20
20
|
reset: boolean;
|
|
21
21
|
onChunk: (chunk: string) => void;
|
|
22
22
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool wrappers for extensions.
|
|
3
3
|
*/
|
|
4
|
-
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import type { Static, TSchema } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import type { ExtensionRunner } from "./runner.js";
|
|
7
7
|
import type { RegisteredTool } from "./types.js";
|
|
@@ -19,7 +19,7 @@ export declare class RegisteredToolAdapter implements AgentTool<any, any, any> {
|
|
|
19
19
|
renderCall?: (args: any, options: any, theme: any) => any;
|
|
20
20
|
renderResult?: (result: any, options: any, theme: any, args?: any) => any;
|
|
21
21
|
constructor(registeredTool: RegisteredTool, runner: ExtensionRunner);
|
|
22
|
-
execute(toolCallId: string, params: any, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<any>, _context?: AgentToolContext): Promise<
|
|
22
|
+
execute(toolCallId: string, params: any, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<any>, _context?: AgentToolContext): Promise<AgentToolResult<unknown, unknown>>;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Backward-compatible factory function wrapper.
|
|
@@ -47,8 +47,5 @@ export declare class ExtensionToolWrapper<TParameters extends TSchema = TSchema,
|
|
|
47
47
|
* Forward browser mode changes when available.
|
|
48
48
|
*/
|
|
49
49
|
restartForModeChange(): Promise<void>;
|
|
50
|
-
execute(toolCallId: string, params: Static<TParameters>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<TDetails, TParameters>, context?: AgentToolContext): Promise<
|
|
51
|
-
content: any;
|
|
52
|
-
details?: TDetails;
|
|
53
|
-
}>;
|
|
50
|
+
execute(toolCallId: string, params: Static<TParameters>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<TDetails, TParameters>, context?: AgentToolContext): Promise<AgentToolResult<TDetails, TParameters>>;
|
|
54
51
|
}
|
|
@@ -14,5 +14,17 @@ export type GuidedGoalTurnResult = {
|
|
|
14
14
|
export interface GuidedGoalTurnOptions {
|
|
15
15
|
messages: readonly GuidedGoalMessage[];
|
|
16
16
|
signal?: AbortSignal;
|
|
17
|
+
/**
|
|
18
|
+
* Stable Codex transport session id reused across every turn of one
|
|
19
|
+
* interview. `handleGuidedGoalCommand` runs up to six turns; minting a fresh
|
|
20
|
+
* id per turn opens a new websocket-only Codex socket each time (kept in
|
|
21
|
+
* `providerSessionState` until session dispose), which can trip
|
|
22
|
+
* `websocket_connection_limit_reached` and drop back to the SSE path this
|
|
23
|
+
* fix avoids. Callers pass one id for the whole interview; omitted for
|
|
24
|
+
* one-shot callers, which mint a unique id per call.
|
|
25
|
+
*/
|
|
26
|
+
sideSessionId?: string;
|
|
17
27
|
}
|
|
28
|
+
/** Mint a guided-goal Codex side-session id keyed off the main session id. */
|
|
29
|
+
export declare function newGuidedGoalSessionId(session: AgentSession): string;
|
|
18
30
|
export declare function runGuidedGoalTurn(session: AgentSession, options: GuidedGoalTurnOptions): Promise<GuidedGoalTurnResult>;
|
|
@@ -2,8 +2,9 @@ import type { InternalResource, InternalUrl, ProtocolHandler, UrlCompletion } fr
|
|
|
2
2
|
/**
|
|
3
3
|
* Handler for history:// URLs.
|
|
4
4
|
*
|
|
5
|
-
* Resolves agent ids against the global AgentRegistry,
|
|
6
|
-
* for
|
|
5
|
+
* Resolves agent ids against the global AgentRegistry, then falls back to
|
|
6
|
+
* on-disk `.jsonl` transcripts, serving read-only history for live, parked,
|
|
7
|
+
* and unregistered agents alike.
|
|
7
8
|
*/
|
|
8
9
|
export declare class HistoryProtocolHandler implements ProtocolHandler {
|
|
9
10
|
#private;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for internal-url protocol handlers that resolve IDs against
|
|
3
|
+
* registered agent sessions.
|
|
4
|
+
*/
|
|
1
5
|
export declare function registerArtifactsDir(dir: string): () => void;
|
|
2
6
|
export declare function resetRegisteredArtifactDirsForTests(): void;
|
|
3
7
|
/**
|
|
@@ -12,3 +16,18 @@ export declare function resetRegisteredArtifactDirsForTests(): void;
|
|
|
12
16
|
* single entry.
|
|
13
17
|
*/
|
|
14
18
|
export declare function artifactsDirsFromRegistry(): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Recursively scan artifacts dirs for agent session transcripts, keyed by
|
|
21
|
+
* agent id (the `.jsonl` basename). Used by `history://` so transcripts of
|
|
22
|
+
* agents no longer in the registry (unregistered one-shot helpers, released
|
|
23
|
+
* agents, or any agent after session resume) remain reachable — mirroring how
|
|
24
|
+
* `agent://` reads `.md` outputs straight off disk.
|
|
25
|
+
*
|
|
26
|
+
* Layout follows `task/index.ts`: a subagent's transcript is
|
|
27
|
+
* `<artifactsDir>/<AgentId>.jsonl`, and its own children nest one level deeper
|
|
28
|
+
* under `<artifactsDir>/<AgentId>/<AgentId>.<ChildId>.jsonl`. Advisor
|
|
29
|
+
* transcripts (`__advisor*.jsonl`) are observability-only and excluded;
|
|
30
|
+
* EPERM-rewrite backups (`.bak`) are skipped. When the same id appears in
|
|
31
|
+
* multiple dirs, the first hit wins (registry dirs are scanned first).
|
|
32
|
+
*/
|
|
33
|
+
export declare function sessionFilesFromDisk(): Promise<Map<string, string>>;
|
|
@@ -52,6 +52,8 @@ export interface MCPOAuthConfig {
|
|
|
52
52
|
authorizationUrl: string;
|
|
53
53
|
/** Token endpoint URL */
|
|
54
54
|
tokenUrl: string;
|
|
55
|
+
/** Dynamic client registration endpoint advertised by the authorization server. */
|
|
56
|
+
registrationUrl?: string;
|
|
55
57
|
/** Client ID (optional when already embedded in authorization URL) */
|
|
56
58
|
clientId?: string;
|
|
57
59
|
/** Client secret (optional for PKCE flows) */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -69,6 +69,16 @@ export declare class AgentHubOverlayComponent extends Container {
|
|
|
69
69
|
dispose(): void;
|
|
70
70
|
render(width: number): readonly string[];
|
|
71
71
|
handleInput(keyData: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Seed the table's left-left close detector with the current time so a single
|
|
74
|
+
* subsequent `←` (within {@link LEFT_TAP_WINDOW_MS}) dismisses the hub.
|
|
75
|
+
*
|
|
76
|
+
* The editor's own double-tap detector consumes the `←←` that opens the hub,
|
|
77
|
+
* leaving this detector at its fresh `0` — without this handoff the user would
|
|
78
|
+
* have to press `←←` a second time to escape. Called by the opener when the hub
|
|
79
|
+
* was raised by that gesture.
|
|
80
|
+
*/
|
|
81
|
+
armCloseTap(): void;
|
|
72
82
|
/**
|
|
73
83
|
* Open the fullscreen transcript viewer for an agent id (public for table Enter
|
|
74
84
|
* and tests). Mounts {@link AgentTranscriptViewer} as a `fullscreen` overlay so it
|
|
@@ -2,9 +2,11 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
2
2
|
/**
|
|
3
3
|
* Dynamic border component that adjusts to viewport width.
|
|
4
4
|
*
|
|
5
|
-
* Note:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Note: the module-level `theme` may be `undefined` — when loaded through jiti
|
|
6
|
+
* (separate module cache) or from a second `src` module graph in npm-package
|
|
7
|
+
* installs, where the host bundle assigns `theme` but this copy never sees it
|
|
8
|
+
* (issue #5366). Both the default color and `render()` degrade to plain,
|
|
9
|
+
* unstyled output instead of crashing the TUI.
|
|
8
10
|
*/
|
|
9
11
|
export declare class DynamicBorder implements Component {
|
|
10
12
|
#private;
|
|
@@ -36,5 +36,7 @@ export declare class LoginDialogComponent extends Container {
|
|
|
36
36
|
* Called by onProgress callback
|
|
37
37
|
*/
|
|
38
38
|
showProgress(message: string): void;
|
|
39
|
+
/** Route non-bracketed paste transports into the active login input. */
|
|
40
|
+
pasteText(text: string): void;
|
|
39
41
|
handleInput(data: string): void;
|
|
40
42
|
}
|
|
@@ -21,6 +21,7 @@ export interface MCPAddWizardOAuthResult {
|
|
|
21
21
|
interface MCPAddWizardOAuthOptions {
|
|
22
22
|
serverUrl?: string;
|
|
23
23
|
resource?: string;
|
|
24
|
+
registrationUrl?: string;
|
|
24
25
|
/**
|
|
25
26
|
* External cancellation source. Aborting it tears down the in-flight OAuth
|
|
26
27
|
* flow and surfaces a neutral cancellation error. The wizard wires its own
|
|
@@ -330,6 +330,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
330
330
|
showDebugSelector(): Promise<void>;
|
|
331
331
|
showAgentHub(options?: {
|
|
332
332
|
requireContent?: boolean;
|
|
333
|
+
armCloseTap?: boolean;
|
|
333
334
|
}): void;
|
|
334
335
|
resetObserverRegistry(): void;
|
|
335
336
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -134,6 +134,21 @@ export declare function wrapSteeringForModel(messages: AgentMessage[]): AgentMes
|
|
|
134
134
|
* pure local mutation and intentionally does neither.
|
|
135
135
|
*/
|
|
136
136
|
export declare function stripImagesFromMessage(message: AgentMessage): number;
|
|
137
|
+
/**
|
|
138
|
+
* Replace every `ImageContent` block in already-converted LLM {@link Message}s
|
|
139
|
+
* with a text placeholder, returning a new array only when something changed.
|
|
140
|
+
*
|
|
141
|
+
* Unlike {@link stripImagesFromMessage} (which mutates persisted `AgentMessage`s
|
|
142
|
+
* in place), this operates on the ephemeral provider-request view produced by
|
|
143
|
+
* {@link convertToLlm}, so history on disk keeps its images while the outbound
|
|
144
|
+
* request is scrubbed. Used to keep image blocks off the wire when the active
|
|
145
|
+
* model has no vision support (or `images.blockImages` is set) — e.g. after
|
|
146
|
+
* switching from a vision model to a text-only one mid-session (#5400).
|
|
147
|
+
*
|
|
148
|
+
* Consecutive placeholder texts collapse into one so a message that was nothing
|
|
149
|
+
* but images does not balloon into a run of identical notes.
|
|
150
|
+
*/
|
|
151
|
+
export declare function replaceLlmImagesWithText(messages: Message[], placeholder: string): Message[];
|
|
137
152
|
/**
|
|
138
153
|
* Message type for bash executions via the ! command.
|
|
139
154
|
*/
|
|
@@ -8,7 +8,6 @@ import type { OutputMeta } from "./output-meta.js";
|
|
|
8
8
|
declare const searchSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
9
9
|
pattern: string;
|
|
10
10
|
path?: string | undefined;
|
|
11
|
-
selector?: string | undefined;
|
|
12
11
|
case?: boolean | undefined;
|
|
13
12
|
gitignore?: boolean | undefined;
|
|
14
13
|
skip?: number | null | undefined;
|
|
@@ -67,7 +66,6 @@ export declare class GrepTool implements AgentTool<typeof searchSchema, GrepTool
|
|
|
67
66
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
68
67
|
pattern: string;
|
|
69
68
|
path?: string | undefined;
|
|
70
|
-
selector?: string | undefined;
|
|
71
69
|
case?: boolean | undefined;
|
|
72
70
|
gitignore?: boolean | undefined;
|
|
73
71
|
skip?: number | null | undefined;
|
|
@@ -7,7 +7,6 @@ import { type TruncationResult } from "../session/streaming-output.js";
|
|
|
7
7
|
import { type OutputMeta } from "./output-meta.js";
|
|
8
8
|
declare const readSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
9
9
|
path: string;
|
|
10
|
-
selector?: string | undefined;
|
|
11
10
|
}, {}>;
|
|
12
11
|
export type ReadToolInput = typeof readSchema.infer;
|
|
13
12
|
export interface ReadToolDetails {
|
|
@@ -60,7 +59,6 @@ export declare class ReadTool implements AgentTool<typeof readSchema, ReadToolDe
|
|
|
60
59
|
readonly description: string;
|
|
61
60
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
62
61
|
path: string;
|
|
63
|
-
selector?: string | undefined;
|
|
64
62
|
}, {}>;
|
|
65
63
|
readonly strict = true;
|
|
66
64
|
constructor(session: ToolSession);
|
|
@@ -69,8 +67,6 @@ export declare class ReadTool implements AgentTool<typeof readSchema, ReadToolDe
|
|
|
69
67
|
interface ReadRenderArgs {
|
|
70
68
|
path?: unknown;
|
|
71
69
|
file_path?: unknown;
|
|
72
|
-
selector?: unknown;
|
|
73
|
-
sel?: string;
|
|
74
70
|
offset?: number;
|
|
75
71
|
limit?: number;
|
|
76
72
|
raw?: boolean;
|
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": "16.5.
|
|
4
|
+
"version": "16.5.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",
|
|
@@ -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": "16.5.
|
|
56
|
-
"@oh-my-pi/omp-stats": "16.5.
|
|
57
|
-
"@oh-my-pi/pi-agent-core": "16.5.
|
|
58
|
-
"@oh-my-pi/pi-ai": "16.5.
|
|
59
|
-
"@oh-my-pi/pi-catalog": "16.5.
|
|
60
|
-
"@oh-my-pi/pi-mnemopi": "16.5.
|
|
61
|
-
"@oh-my-pi/pi-natives": "16.5.
|
|
62
|
-
"@oh-my-pi/pi-tui": "16.5.
|
|
63
|
-
"@oh-my-pi/pi-utils": "16.5.
|
|
64
|
-
"@oh-my-pi/pi-wire": "16.5.
|
|
65
|
-
"@oh-my-pi/snapcompact": "16.5.
|
|
55
|
+
"@oh-my-pi/hashline": "16.5.2",
|
|
56
|
+
"@oh-my-pi/omp-stats": "16.5.2",
|
|
57
|
+
"@oh-my-pi/pi-agent-core": "16.5.2",
|
|
58
|
+
"@oh-my-pi/pi-ai": "16.5.2",
|
|
59
|
+
"@oh-my-pi/pi-catalog": "16.5.2",
|
|
60
|
+
"@oh-my-pi/pi-mnemopi": "16.5.2",
|
|
61
|
+
"@oh-my-pi/pi-natives": "16.5.2",
|
|
62
|
+
"@oh-my-pi/pi-tui": "16.5.2",
|
|
63
|
+
"@oh-my-pi/pi-utils": "16.5.2",
|
|
64
|
+
"@oh-my-pi/pi-wire": "16.5.2",
|
|
65
|
+
"@oh-my-pi/snapcompact": "16.5.2",
|
|
66
66
|
"@opentelemetry/api": "^1.9.1",
|
|
67
67
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
68
68
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.220.0",
|
|
@@ -1856,23 +1856,141 @@ describe("advisor", () => {
|
|
|
1856
1856
|
expect(failures).toHaveLength(2);
|
|
1857
1857
|
});
|
|
1858
1858
|
|
|
1859
|
-
it("
|
|
1860
|
-
const promptInputs: string[] = [];
|
|
1859
|
+
it("accepts a zero-usage empty stop as a successful silent review", async () => {
|
|
1861
1860
|
const turnErrors: unknown[] = [];
|
|
1862
1861
|
const failures: unknown[] = [];
|
|
1863
1862
|
const adviceNotes: string[] = [];
|
|
1864
1863
|
const rollbackCalls: number[] = [];
|
|
1865
|
-
const lengthsBeforePrompt: number[] = [];
|
|
1866
|
-
const events: string[] = [];
|
|
1867
1864
|
const state: { messages: AgentMessage[]; error?: string } = { messages: [] };
|
|
1868
1865
|
let promptCalls = 0;
|
|
1869
1866
|
const agent: AdvisorAgent = {
|
|
1870
1867
|
prompt: async input => {
|
|
1871
1868
|
promptCalls++;
|
|
1872
|
-
promptInputs.push(input);
|
|
1873
|
-
lengthsBeforePrompt.push(state.messages.length);
|
|
1874
|
-
events.push(`prompt:${promptCalls}`);
|
|
1875
1869
|
state.messages.push({ role: "user", content: input, timestamp: promptCalls * 2 - 1 } as AgentMessage);
|
|
1870
|
+
state.messages.push({
|
|
1871
|
+
role: "assistant",
|
|
1872
|
+
content: [],
|
|
1873
|
+
api: "mock",
|
|
1874
|
+
provider: "mock",
|
|
1875
|
+
model: "mock-advisor",
|
|
1876
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },
|
|
1877
|
+
stopReason: "stop",
|
|
1878
|
+
timestamp: promptCalls * 2,
|
|
1879
|
+
} as unknown as AgentMessage);
|
|
1880
|
+
state.error = undefined;
|
|
1881
|
+
},
|
|
1882
|
+
abort: () => {},
|
|
1883
|
+
reset: () => {
|
|
1884
|
+
state.messages.length = 0;
|
|
1885
|
+
state.error = undefined;
|
|
1886
|
+
},
|
|
1887
|
+
rollbackTo: count => {
|
|
1888
|
+
rollbackCalls.push(count);
|
|
1889
|
+
state.messages.length = count;
|
|
1890
|
+
state.error = undefined;
|
|
1891
|
+
},
|
|
1892
|
+
state,
|
|
1893
|
+
};
|
|
1894
|
+
const messages: AgentMessage[] = [{ role: "user", content: "aaa", timestamp: 1 } as AgentMessage];
|
|
1895
|
+
const host: AdvisorRuntimeHost = {
|
|
1896
|
+
snapshotMessages: () => messages,
|
|
1897
|
+
enqueueAdvice: note => adviceNotes.push(note),
|
|
1898
|
+
onTurnError: error => {
|
|
1899
|
+
turnErrors.push(error);
|
|
1900
|
+
},
|
|
1901
|
+
notifyFailure: error => {
|
|
1902
|
+
failures.push(error);
|
|
1903
|
+
},
|
|
1904
|
+
};
|
|
1905
|
+
const runtime = new AdvisorRuntime(agent, host, 0);
|
|
1906
|
+
|
|
1907
|
+
// A model that says nothing and yields completed its review; no retry,
|
|
1908
|
+
// no rollback, no "Advisor unavailable" notification.
|
|
1909
|
+
runtime.onTurnEnd(messages);
|
|
1910
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1911
|
+
|
|
1912
|
+
expect(promptCalls).toBe(1);
|
|
1913
|
+
expect(turnErrors).toEqual([]);
|
|
1914
|
+
expect(failures).toEqual([]);
|
|
1915
|
+
expect(rollbackCalls).toEqual([]);
|
|
1916
|
+
expect(adviceNotes).toEqual([]);
|
|
1917
|
+
expect(state.messages).toHaveLength(2);
|
|
1918
|
+
expect(runtime.backlog).toBe(0);
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
it("never warns for consecutive zero-usage silent stops — a quiet session is a valid session", async () => {
|
|
1922
|
+
const turnErrors: unknown[] = [];
|
|
1923
|
+
const failures: unknown[] = [];
|
|
1924
|
+
const state: { messages: AgentMessage[]; error?: string } = { messages: [] };
|
|
1925
|
+
let promptCalls = 0;
|
|
1926
|
+
const agent: AdvisorAgent = {
|
|
1927
|
+
prompt: async input => {
|
|
1928
|
+
promptCalls++;
|
|
1929
|
+
state.messages.push({ role: "user", content: input, timestamp: promptCalls * 2 - 1 } as AgentMessage);
|
|
1930
|
+
state.messages.push({
|
|
1931
|
+
role: "assistant",
|
|
1932
|
+
content: [],
|
|
1933
|
+
api: "mock",
|
|
1934
|
+
provider: "mock",
|
|
1935
|
+
model: "mock-advisor",
|
|
1936
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },
|
|
1937
|
+
stopReason: "stop",
|
|
1938
|
+
timestamp: promptCalls * 2,
|
|
1939
|
+
} as unknown as AgentMessage);
|
|
1940
|
+
state.error = undefined;
|
|
1941
|
+
},
|
|
1942
|
+
abort: () => {},
|
|
1943
|
+
reset: () => {
|
|
1944
|
+
state.messages.length = 0;
|
|
1945
|
+
state.error = undefined;
|
|
1946
|
+
},
|
|
1947
|
+
rollbackTo: count => {
|
|
1948
|
+
state.messages.length = count;
|
|
1949
|
+
state.error = undefined;
|
|
1950
|
+
},
|
|
1951
|
+
state,
|
|
1952
|
+
};
|
|
1953
|
+
const messages: AgentMessage[] = [{ role: "user", content: "turn-0", timestamp: 1 } as AgentMessage];
|
|
1954
|
+
const host: AdvisorRuntimeHost = {
|
|
1955
|
+
snapshotMessages: () => messages,
|
|
1956
|
+
enqueueAdvice: () => {},
|
|
1957
|
+
onTurnError: error => {
|
|
1958
|
+
turnErrors.push(error);
|
|
1959
|
+
},
|
|
1960
|
+
notifyFailure: error => {
|
|
1961
|
+
failures.push(error);
|
|
1962
|
+
},
|
|
1963
|
+
};
|
|
1964
|
+
const runtime = new AdvisorRuntime(agent, host, 0);
|
|
1965
|
+
|
|
1966
|
+
// Five consecutive turns where the advisor has nothing to add: every one
|
|
1967
|
+
// completes as a single successful prompt — no retries, no rollbacks, no
|
|
1968
|
+
// "Advisor unavailable" notification, ever.
|
|
1969
|
+
for (let i = 0; i < 5; i++) {
|
|
1970
|
+
if (i > 0) messages.push({ role: "user", content: `turn-${i}`, timestamp: i + 1 } as AgentMessage);
|
|
1971
|
+
runtime.onTurnEnd(messages);
|
|
1972
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
expect(promptCalls).toBe(5);
|
|
1976
|
+
expect(turnErrors).toEqual([]);
|
|
1977
|
+
expect(failures).toEqual([]);
|
|
1978
|
+
expect(runtime.backlog).toBe(0);
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1981
|
+
it("treats a content-less stop that generated output tokens as a successful silent review", async () => {
|
|
1982
|
+
const turnErrors: unknown[] = [];
|
|
1983
|
+
const failures: unknown[] = [];
|
|
1984
|
+
const adviceNotes: string[] = [];
|
|
1985
|
+
const state: { messages: AgentMessage[]; error?: string } = { messages: [] };
|
|
1986
|
+
let promptCalls = 0;
|
|
1987
|
+
const agent: AdvisorAgent = {
|
|
1988
|
+
prompt: async input => {
|
|
1989
|
+
promptCalls++;
|
|
1990
|
+
state.messages.push({ role: "user", content: input, timestamp: promptCalls * 2 - 1 } as AgentMessage);
|
|
1991
|
+
// A real model turn that CHOSE silence: it reasoned, spent
|
|
1992
|
+
// output/reasoning tokens, and emitted no `advise` call. This is
|
|
1993
|
+
// the documented verifier behavior, not a provider malfunction.
|
|
1876
1994
|
state.messages.push({
|
|
1877
1995
|
role: "assistant",
|
|
1878
1996
|
content: [],
|
|
@@ -1880,11 +1998,12 @@ describe("advisor", () => {
|
|
|
1880
1998
|
provider: "mock",
|
|
1881
1999
|
model: "mock-advisor",
|
|
1882
2000
|
usage: {
|
|
1883
|
-
input:
|
|
1884
|
-
output:
|
|
2001
|
+
input: 1200,
|
|
2002
|
+
output: 340,
|
|
1885
2003
|
cacheRead: 0,
|
|
1886
2004
|
cacheWrite: 0,
|
|
1887
|
-
totalTokens:
|
|
2005
|
+
totalTokens: 1540,
|
|
2006
|
+
reasoningTokens: 300,
|
|
1888
2007
|
},
|
|
1889
2008
|
stopReason: "stop",
|
|
1890
2009
|
timestamp: promptCalls * 2,
|
|
@@ -1897,23 +2016,22 @@ describe("advisor", () => {
|
|
|
1897
2016
|
state.error = undefined;
|
|
1898
2017
|
},
|
|
1899
2018
|
rollbackTo: count => {
|
|
1900
|
-
rollbackCalls.push(count);
|
|
1901
2019
|
state.messages.length = count;
|
|
1902
2020
|
state.error = undefined;
|
|
1903
2021
|
},
|
|
1904
2022
|
state,
|
|
1905
2023
|
};
|
|
1906
|
-
const messages: AgentMessage[] = [
|
|
2024
|
+
const messages: AgentMessage[] = [
|
|
2025
|
+
{ role: "user", content: "Reply exactly: OK", timestamp: 1 } as AgentMessage,
|
|
2026
|
+
];
|
|
1907
2027
|
const host: AdvisorRuntimeHost = {
|
|
1908
2028
|
snapshotMessages: () => messages,
|
|
1909
2029
|
enqueueAdvice: note => adviceNotes.push(note),
|
|
1910
2030
|
onTurnError: error => {
|
|
1911
2031
|
turnErrors.push(error);
|
|
1912
|
-
events.push(`hook:${error instanceof Error ? error.message : String(error)}`);
|
|
1913
2032
|
},
|
|
1914
2033
|
notifyFailure: error => {
|
|
1915
2034
|
failures.push(error);
|
|
1916
|
-
events.push(`notify:${error instanceof Error ? error.message : String(error)}`);
|
|
1917
2035
|
},
|
|
1918
2036
|
};
|
|
1919
2037
|
const runtime = new AdvisorRuntime(agent, host, 0);
|
|
@@ -1921,42 +2039,11 @@ describe("advisor", () => {
|
|
|
1921
2039
|
runtime.onTurnEnd(messages);
|
|
1922
2040
|
await runtime.waitForCatchup(1000, 1);
|
|
1923
2041
|
|
|
1924
|
-
|
|
1925
|
-
expect(
|
|
1926
|
-
expect(
|
|
1927
|
-
expect(
|
|
1928
|
-
expect(lengthsBeforePrompt).toEqual([0, 0, 0]);
|
|
1929
|
-
expect(rollbackCalls).toEqual([0, 0, 0]);
|
|
1930
|
-
expect(turnErrors).toHaveLength(3);
|
|
1931
|
-
const turnErrorMessages = turnErrors.map(error =>
|
|
1932
|
-
(error instanceof Error ? error.message : String(error)).toLowerCase(),
|
|
1933
|
-
);
|
|
1934
|
-
for (const message of turnErrorMessages) {
|
|
1935
|
-
expect(message).toContain("advisor");
|
|
1936
|
-
expect(message).toContain("empty");
|
|
1937
|
-
expect(message).toContain("response");
|
|
1938
|
-
}
|
|
1939
|
-
expect(failures).toHaveLength(1);
|
|
1940
|
-
const failure = failures[0];
|
|
1941
|
-
if (!(failure instanceof Error)) throw new Error("expected advisor failure error");
|
|
1942
|
-
expect(failure.message.toLowerCase()).toContain("advisor");
|
|
1943
|
-
expect(failure.message.toLowerCase()).toContain("empty");
|
|
1944
|
-
expect(failure.message.toLowerCase()).toContain("response");
|
|
1945
|
-
expect(events).toHaveLength(7);
|
|
1946
|
-
expect(events[0]).toBe("prompt:1");
|
|
1947
|
-
expect(events[1].startsWith("hook:")).toBe(true);
|
|
1948
|
-
expect(events[1].toLowerCase()).toContain("empty");
|
|
1949
|
-
expect(events[2]).toBe("prompt:2");
|
|
1950
|
-
expect(events[3].startsWith("hook:")).toBe(true);
|
|
1951
|
-
expect(events[3].toLowerCase()).toContain("empty");
|
|
1952
|
-
expect(events[4]).toBe("prompt:3");
|
|
1953
|
-
expect(events[5].startsWith("hook:")).toBe(true);
|
|
1954
|
-
expect(events[5].toLowerCase()).toContain("empty");
|
|
1955
|
-
expect(events[6].toLowerCase()).toContain("empty");
|
|
1956
|
-
expect(events[6].startsWith("notify:")).toBe(true);
|
|
2042
|
+
// No retries, no failure hook, no unavailable notification.
|
|
2043
|
+
expect(promptCalls).toBe(1);
|
|
2044
|
+
expect(turnErrors).toEqual([]);
|
|
2045
|
+
expect(failures).toEqual([]);
|
|
1957
2046
|
expect(adviceNotes).toEqual([]);
|
|
1958
|
-
expect(state.messages).toHaveLength(0);
|
|
1959
|
-
expect(state.error).toBeUndefined();
|
|
1960
2047
|
expect(runtime.backlog).toBe(0);
|
|
1961
2048
|
});
|
|
1962
2049
|
|