@oh-my-pi/pi-coding-agent 17.0.8 → 17.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -1
- package/dist/cli.js +4850 -4774
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +27 -0
- package/dist/types/config/settings-schema.d.ts +34 -3
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +1 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +8 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +13 -12
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +1 -1
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +40 -0
- package/src/config/model-resolver.ts +0 -1
- package/src/config/settings-schema.ts +41 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +851 -310
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/lsp/client.ts +23 -0
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +25 -7
- package/src/main.ts +1 -0
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/render.ts +94 -35
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/modes/components/agent-hub.ts +26 -9
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/settings-defs.ts +5 -2
- package/src/modes/components/settings-selector.ts +9 -5
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/controllers/command-controller.ts +14 -7
- package/src/modes/controllers/mcp-command-controller.ts +70 -40
- package/src/modes/interactive-mode.ts +3 -0
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +6 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/tools/task.md +12 -2
- package/src/sdk.ts +45 -3
- package/src/session/agent-session.ts +63 -32
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +41 -2
- package/src/session/session-loader.ts +10 -5
- package/src/slash-commands/helpers/usage-report.ts +3 -1
- package/src/task/executor.ts +3 -0
- package/src/task/index.ts +52 -8
- package/src/task/structured-subagent.ts +3 -1
- package/src/task/types.ts +16 -0
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/index.ts +3 -0
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/types.ts +5 -1
|
@@ -83,6 +83,7 @@ export declare const taskItemSchema: import("arktype/internal/variants/object.ts
|
|
|
83
83
|
name?: string | undefined;
|
|
84
84
|
agent: import("arktype/internal/attributes.ts").Default<string, "task">;
|
|
85
85
|
task: string;
|
|
86
|
+
model?: string | string[] | undefined;
|
|
86
87
|
outputSchema?: string | boolean | object | null | undefined;
|
|
87
88
|
schemaMode?: "permissive" | "strict" | undefined;
|
|
88
89
|
}, {}>;
|
|
@@ -94,6 +95,8 @@ export interface TaskItem {
|
|
|
94
95
|
agent?: string;
|
|
95
96
|
/** The work; required by the schema. */
|
|
96
97
|
task?: string;
|
|
98
|
+
/** Explicit model selector or fallback chain for this spawn, including optional reasoning suffixes. */
|
|
99
|
+
model?: string | string[];
|
|
97
100
|
/** Caller-provided output schema; its presence overrides the selected agent's schema. */
|
|
98
101
|
outputSchema?: unknown;
|
|
99
102
|
/** Validation behavior for a caller-provided or inherited output schema. */
|
|
@@ -105,6 +108,7 @@ export declare const taskSchema: import("arktype/internal/variants/object.ts").O
|
|
|
105
108
|
name?: string | undefined;
|
|
106
109
|
agent: import("arktype/internal/attributes.ts").Default<string, "task">;
|
|
107
110
|
task: string;
|
|
111
|
+
model?: string | string[] | undefined;
|
|
108
112
|
outputSchema?: string | boolean | object | null | undefined;
|
|
109
113
|
schemaMode?: "permissive" | "strict" | undefined;
|
|
110
114
|
isolated?: boolean | undefined;
|
|
@@ -113,6 +117,7 @@ declare const ALL_TASK_SCHEMAS: readonly [import("arktype/internal/variants/obje
|
|
|
113
117
|
name?: string | undefined;
|
|
114
118
|
agent: import("arktype/internal/attributes.ts").Default<string, "task">;
|
|
115
119
|
task: string;
|
|
120
|
+
model?: string | string[] | undefined;
|
|
116
121
|
outputSchema?: string | boolean | object | null | undefined;
|
|
117
122
|
schemaMode?: "permissive" | "strict" | undefined;
|
|
118
123
|
isolated?: boolean | undefined;
|
|
@@ -120,6 +125,7 @@ declare const ALL_TASK_SCHEMAS: readonly [import("arktype/internal/variants/obje
|
|
|
120
125
|
name?: string | undefined;
|
|
121
126
|
agent: import("arktype/internal/attributes.ts").Default<string, "task">;
|
|
122
127
|
task: string;
|
|
128
|
+
model?: string | string[] | undefined;
|
|
123
129
|
outputSchema?: string | boolean | object | null | undefined;
|
|
124
130
|
schemaMode?: "permissive" | "strict" | undefined;
|
|
125
131
|
}, {}>, import("arktype/internal/variants/object.ts").ObjectType<{
|
|
@@ -128,6 +134,7 @@ declare const ALL_TASK_SCHEMAS: readonly [import("arktype/internal/variants/obje
|
|
|
128
134
|
name?: string | undefined;
|
|
129
135
|
agent: import("arktype/internal/attributes.ts").Default<string, "task">;
|
|
130
136
|
task: string;
|
|
137
|
+
model?: string | string[] | undefined;
|
|
131
138
|
outputSchema?: string | boolean | object | null | undefined;
|
|
132
139
|
schemaMode?: "permissive" | "strict" | undefined;
|
|
133
140
|
isolated?: boolean | undefined;
|
|
@@ -138,6 +145,7 @@ declare const ALL_TASK_SCHEMAS: readonly [import("arktype/internal/variants/obje
|
|
|
138
145
|
name?: string | undefined;
|
|
139
146
|
agent: import("arktype/internal/attributes.ts").Default<string, "task">;
|
|
140
147
|
task: string;
|
|
148
|
+
model?: string | string[] | undefined;
|
|
141
149
|
outputSchema?: string | boolean | object | null | undefined;
|
|
142
150
|
schemaMode?: "permissive" | "strict" | undefined;
|
|
143
151
|
}[];
|
|
@@ -168,6 +176,8 @@ export interface TaskParams {
|
|
|
168
176
|
agent?: string;
|
|
169
177
|
/** The work (flat form). */
|
|
170
178
|
task?: string;
|
|
179
|
+
/** Explicit model selector or fallback chain for the spawn, including optional reasoning suffixes. */
|
|
180
|
+
model?: string | string[];
|
|
171
181
|
/** Caller-provided output schema; its presence overrides the selected agent's schema. */
|
|
172
182
|
outputSchema?: unknown;
|
|
173
183
|
/** Validation behavior for a caller-provided or inherited output schema. */
|
|
@@ -291,6 +301,8 @@ export interface AgentProgress {
|
|
|
291
301
|
modelOverride?: string | string[];
|
|
292
302
|
/** Resolved model display string in the form `<provider>/<id>`, optionally suffixed with `:<thinkingLevel>` when the level was set explicitly. Undefined when the model could not be resolved. */
|
|
293
303
|
resolvedModel?: string;
|
|
304
|
+
/** True when {@link resolvedModel} is the target of an active retry fallback (not the originally configured model). Lets observer-only UIs (collab guests, Agent Hub rows with no live session) flag the fallback and keep the provider. */
|
|
305
|
+
resolvedModelIsFallback?: boolean;
|
|
294
306
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
295
307
|
extractedToolData?: Record<string, unknown[]>;
|
|
296
308
|
/**
|
|
@@ -357,6 +369,8 @@ export interface SingleResult {
|
|
|
357
369
|
modelOverride?: string | string[];
|
|
358
370
|
/** Resolved model display string in the form `<provider>/<id>`, optionally suffixed with `:<thinkingLevel>` when the level was set explicitly. Omitted from tool-result JSON when undefined to keep wire payloads small. */
|
|
359
371
|
resolvedModel?: string;
|
|
372
|
+
/** True when {@link resolvedModel} is the target of an active retry fallback. Mirrors {@link AgentProgress.resolvedModelIsFallback} onto the settled result. */
|
|
373
|
+
resolvedModelIsFallback?: boolean;
|
|
360
374
|
error?: string;
|
|
361
375
|
aborted?: boolean;
|
|
362
376
|
abortReason?: string;
|
|
@@ -55,7 +55,7 @@ export declare function noMatchingJobsResult(session: ToolSession, ids: string[]
|
|
|
55
55
|
/** Bare `wait` with no running jobs and nobody who could message: nothing to block on. */
|
|
56
56
|
export declare function nothingToWaitForResult(session: ToolSession): AgentToolResult<CoordinationDetails>;
|
|
57
57
|
/** `cancel`: kill the named jobs; returns immediately with outcomes + snapshots. */
|
|
58
|
-
export declare function executeCancel(session: ToolSession, manager: AsyncJobManager, ownerId: string | undefined, ids: string[]): AgentToolResult<CoordinationDetails
|
|
58
|
+
export declare function executeCancel(session: ToolSession, manager: AsyncJobManager, ownerId: string | undefined, ids: string[]): Promise<AgentToolResult<CoordinationDetails>>;
|
|
59
59
|
/** `jobs`: read-only snapshot of every job plus the jobless running-agent roster. */
|
|
60
60
|
export declare function executeJobsSnapshot(session: ToolSession, manager: AsyncJobManager, ownerId: string | undefined): AgentToolResult<CoordinationDetails>;
|
|
61
61
|
/** Pending-call frame for job ops (wait/cancel/jobs). */
|
|
@@ -13,6 +13,7 @@ import type { LocalProtocolOptions } from "../internal-urls/index.js";
|
|
|
13
13
|
import type { MCPManager } from "../mcp/index.js";
|
|
14
14
|
import type { MnemopiSessionState } from "../mnemopi/state.js";
|
|
15
15
|
import type { PlanModeState } from "../plan-mode/state.js";
|
|
16
|
+
import type { AgentLifecycleManager } from "../registry/agent-lifecycle.js";
|
|
16
17
|
import type { AgentRegistry } from "../registry/agent-registry.js";
|
|
17
18
|
import type { ArtifactManager } from "../session/artifacts.js";
|
|
18
19
|
import type { ClientBridge } from "../session/client-bridge.js";
|
|
@@ -200,6 +201,8 @@ export interface ToolSession {
|
|
|
200
201
|
xdevRegistry?: XdevRegistry;
|
|
201
202
|
/** Agent registry for IRC routing across live sessions. */
|
|
202
203
|
agentRegistry?: AgentRegistry;
|
|
204
|
+
/** Idle→parked→revive lifecycle owner; lets the hub kill a non-job-backed agent registration. Default: AgentLifecycleManager.global(). */
|
|
205
|
+
agentLifecycle?: () => AgentLifecycleManager;
|
|
203
206
|
/** Get artifacts directory for artifact:// URLs */
|
|
204
207
|
getArtifactsDir?: () => string | null;
|
|
205
208
|
/** Get the ArtifactManager backing this session (shared across parent + subagents). */
|
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
* `xd://report_issue`, and the system prompt tells the model to write
|
|
6
6
|
* `<tool>: <concise description>` there when auto-QA is enabled.
|
|
7
7
|
*
|
|
8
|
-
* Enabled by default
|
|
9
|
-
*
|
|
8
|
+
* Enabled by default (`dev.autoqa` defaults to true); `PI_AUTO_QA=0` or an
|
|
9
|
+
* explicit `dev.autoqa: false` short-circuits injection entirely. When the
|
|
10
|
+
* user is only enabled by default (never configured `dev.autoqa` themselves),
|
|
11
|
+
* a persisted `dev.autoqaConsent: "denied"` also disables injection so a "No"
|
|
12
|
+
* in the consent dialog fully turns the feature off.
|
|
10
13
|
* Records grievances to a local SQLite database; never throws from the device
|
|
11
14
|
* dispatch path.
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Nothing is written until consent resolves. If the user has never been asked
|
|
17
|
+
* (`dev.autoqaConsent === "unset"`) the process-global consent handler —
|
|
18
|
+
* wired by `InteractiveMode` to a Yes/No popup — is invoked exactly once and
|
|
19
|
+
* the decision is persisted; a denial (or dismissal) drops the pending report
|
|
20
|
+
* without touching the database. Subsequent calls (including from subagents)
|
|
21
|
+
* read the cached decision without prompting. `PI_AUTO_QA_PUSH=1` bypasses
|
|
22
|
+
* the dialog for headless environments.
|
|
18
23
|
*
|
|
19
24
|
* When the user grants consent, push is automatically active against the
|
|
20
25
|
* bundled endpoint (`dev.autoqaPush.endpoint`, default `qa.omp.sh`). Each
|
|
@@ -42,6 +47,14 @@ export declare function isReportIssueToolCall(toolCall: {
|
|
|
42
47
|
}): boolean;
|
|
43
48
|
/** Call preview for an `xd://report_issue` write. */
|
|
44
49
|
export declare function renderReportIssueDeviceCall(content: unknown, uiTheme: Theme): Component;
|
|
50
|
+
/**
|
|
51
|
+
* Whether Auto-QA is active for this session.
|
|
52
|
+
*
|
|
53
|
+
* Precedence: `PI_AUTO_QA` env flag > explicit `dev.autoqa` setting >
|
|
54
|
+
* default-on unless the user previously denied consent. The denial veto only
|
|
55
|
+
* applies to the default: explicitly configuring `dev.autoqa: true` re-enables
|
|
56
|
+
* injection (recording still no-ops until consent is granted).
|
|
57
|
+
*/
|
|
45
58
|
export declare function isAutoQaEnabled(settings?: Settings): boolean;
|
|
46
59
|
/**
|
|
47
60
|
* Resolver for the user's "share grievances?" consent.
|
|
@@ -82,8 +95,8 @@ export declare function __resetAutoQaConsentForTests(): void;
|
|
|
82
95
|
export declare function resolveAutoQaConsent(settings: Settings | undefined): Promise<boolean>;
|
|
83
96
|
/**
|
|
84
97
|
* Open (or return the cached handle for) the auto-QA SQLite database at
|
|
85
|
-
* `~/.omp/
|
|
86
|
-
*
|
|
98
|
+
* `~/.omp/autoqa.db` (XDG: `$XDG_DATA_HOME/omp/autoqa.db`), creating the
|
|
99
|
+
* schema lazily. Returns `null` when the path cannot be resolved or opened.
|
|
87
100
|
*/
|
|
88
101
|
export declare function openAutoQaDb(): Database | null;
|
|
89
102
|
export interface FlushResult {
|
|
@@ -127,6 +140,8 @@ export declare function __resetAutoQaFlushStateForTests(): void;
|
|
|
127
140
|
* Flush queued grievances to the configured backend.
|
|
128
141
|
*/
|
|
129
142
|
export declare function flushGrievances(db?: Database, settings?: Settings, options?: FlushOptions): Promise<FlushResult>;
|
|
143
|
+
/** Test-only: await the last consent → insert → flush pipeline. */
|
|
144
|
+
export declare function __awaitAutoQaRecordPipelineForTests(): Promise<void>;
|
|
130
145
|
/**
|
|
131
146
|
* Execute `write xd://report_issue`. `text` must be either:
|
|
132
147
|
* - `<tool>: <concise description>` on one line, or
|
|
@@ -23,6 +23,15 @@ export declare function searchFirecrawl(params: SearchParams): Promise<SearchRes
|
|
|
23
23
|
export declare class FirecrawlProvider extends SearchProvider {
|
|
24
24
|
readonly id = "firecrawl";
|
|
25
25
|
readonly label = "Firecrawl";
|
|
26
|
+
/**
|
|
27
|
+
* Auto-chain admission: requires a credential so an unconfigured Firecrawl
|
|
28
|
+
* doesn't displace other providers that the user has set up with API keys.
|
|
29
|
+
*/
|
|
26
30
|
isAvailable(authStorage: AuthStorage): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Firecrawl supports keyless mode, so an explicit user selection
|
|
33
|
+
* (`webSearch: firecrawl`) works without any credential configured.
|
|
34
|
+
*/
|
|
35
|
+
isExplicitlyAvailable(_authStorage: AuthStorage): boolean;
|
|
27
36
|
search(params: SearchParams): Promise<SearchResponse>;
|
|
28
37
|
}
|
|
@@ -54,7 +54,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
54
54
|
}, {
|
|
55
55
|
readonly value: "firecrawl";
|
|
56
56
|
readonly label: "Firecrawl";
|
|
57
|
-
readonly description: "
|
|
57
|
+
readonly description: "Uses Firecrawl API when FIRECRAWL_API_KEY is set; falls back to keyless mode";
|
|
58
58
|
}, {
|
|
59
59
|
readonly value: "brave";
|
|
60
60
|
readonly label: "Brave";
|
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.9",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -51,18 +51,19 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
53
53
|
"@babel/parser": "^7.29.7",
|
|
54
|
+
"@babel/traverse": "^7.29.7",
|
|
54
55
|
"@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.
|
|
56
|
+
"@oh-my-pi/hashline": "17.0.9",
|
|
57
|
+
"@oh-my-pi/omp-stats": "17.0.9",
|
|
58
|
+
"@oh-my-pi/pi-agent-core": "17.0.9",
|
|
59
|
+
"@oh-my-pi/pi-ai": "17.0.9",
|
|
60
|
+
"@oh-my-pi/pi-catalog": "17.0.9",
|
|
61
|
+
"@oh-my-pi/pi-mnemopi": "17.0.9",
|
|
62
|
+
"@oh-my-pi/pi-natives": "17.0.9",
|
|
63
|
+
"@oh-my-pi/pi-tui": "17.0.9",
|
|
64
|
+
"@oh-my-pi/pi-utils": "17.0.9",
|
|
65
|
+
"@oh-my-pi/pi-wire": "17.0.9",
|
|
66
|
+
"@oh-my-pi/snapcompact": "17.0.9",
|
|
66
67
|
"@opentelemetry/api": "^1.9.1",
|
|
67
68
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
68
69
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
@@ -19,7 +19,7 @@ const BUNDLED_PACKAGES: readonly BundledPackage[] = [
|
|
|
19
19
|
{ dir: "ai", identifier: "PiAi", rootShim: "legacy-pi-ai-shim.ts" },
|
|
20
20
|
{ dir: "coding-agent", identifier: "PiCodingAgent", rootShim: "legacy-pi-coding-agent-shim.ts" },
|
|
21
21
|
{ dir: "natives", identifier: "PiNatives", rootShim: null },
|
|
22
|
-
{ dir: "tui", identifier: "PiTui", rootShim:
|
|
22
|
+
{ dir: "tui", identifier: "PiTui", rootShim: "legacy-pi-tui-shim.ts" },
|
|
23
23
|
{ dir: "utils", identifier: "PiUtils", rootShim: null },
|
|
24
24
|
];
|
|
25
25
|
|
|
@@ -41,7 +41,7 @@ export async function listGrievances(options: ListGrievancesOptions): Promise<vo
|
|
|
41
41
|
console.log("[]");
|
|
42
42
|
} else {
|
|
43
43
|
console.log(
|
|
44
|
-
chalk.dim("No grievances database found.
|
|
44
|
+
chalk.dim("No grievances database found. Auto-QA has not recorded any reports yet (or was disabled)."),
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
return;
|
|
@@ -110,7 +110,7 @@ export async function cleanGrievances(options: CleanGrievancesOptions): Promise<
|
|
|
110
110
|
console.log(JSON.stringify({ deleted: 0 }));
|
|
111
111
|
} else {
|
|
112
112
|
console.log(
|
|
113
|
-
chalk.dim("No grievances database found.
|
|
113
|
+
chalk.dim("No grievances database found. Auto-QA has not recorded any reports yet (or was disabled)."),
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
116
|
return;
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -409,7 +409,7 @@ function formatLimitLine(limit: UsageLimit, labelWidth: number, nowMs: number):
|
|
|
409
409
|
const details: string[] = [describeAmount(limit)];
|
|
410
410
|
const resetsAt = limit.window?.resetsAt;
|
|
411
411
|
if (resetsAt !== undefined && resetsAt > nowMs) {
|
|
412
|
-
details.push(
|
|
412
|
+
details.push(`${limit.window?.resetLabel ?? "resets"} in ${formatDuration(resetsAt - nowMs)}`);
|
|
413
413
|
}
|
|
414
414
|
const lines = [
|
|
415
415
|
` ${STATUS_COLOR[status]("●")} ${padded} ${renderBar(limit)} ${chalk.dim(details.join(" · "))}`,
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import { ModelRegistry } from "../model-registry";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stub AuthStorage that satisfies the surface used by ModelRegistry's
|
|
10
|
+
* constructor (#loadModels → clearConfigApiKeys, constructor →
|
|
11
|
+
* setFallbackResolver). The awaiter under test never reaches auth-gated code
|
|
12
|
+
* paths, so no real credential store is required.
|
|
13
|
+
*/
|
|
14
|
+
function createStubAuthStorage(): AuthStorage {
|
|
15
|
+
const stub = {
|
|
16
|
+
setFallbackResolver: () => {},
|
|
17
|
+
clearConfigApiKeys: () => {},
|
|
18
|
+
hasAuth: () => false,
|
|
19
|
+
};
|
|
20
|
+
return stub as unknown as AuthStorage;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("ModelRegistry.awaitBackgroundRefresh", () => {
|
|
24
|
+
let tmpDir: string;
|
|
25
|
+
let registry: ModelRegistry;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tmpDir = mkdtempSync(path.join(os.tmpdir(), "omp-reg-"));
|
|
29
|
+
// Construct with an explicit modelsPath inside the temp dir so the
|
|
30
|
+
// constructor's #loadModels read returns "not-found" rather than
|
|
31
|
+
// touching the host's ~/.omp/agent/models.yaml. isBunTestRuntime()
|
|
32
|
+
// auto-stubs #fetch in the constructor.
|
|
33
|
+
registry = new ModelRegistry(createStubAuthStorage(), path.join(tmpDir, "models.yaml"));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("resolves immediately when no background refresh is in flight", async () => {
|
|
42
|
+
// No refreshInBackground() called → #backgroundRefresh is undefined.
|
|
43
|
+
// The awaiter must settle within a single microtask, never hanging.
|
|
44
|
+
let settled = false;
|
|
45
|
+
const p = registry.awaitBackgroundRefresh().then(() => {
|
|
46
|
+
settled = true;
|
|
47
|
+
});
|
|
48
|
+
await Promise.resolve();
|
|
49
|
+
await p;
|
|
50
|
+
expect(settled).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("blocks until the in-flight background refresh resolves, then resolves", async () => {
|
|
54
|
+
// Drive refreshInBackground with a controlled refresh() return value so
|
|
55
|
+
// #backgroundRefresh is captured but not yet settled.
|
|
56
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
57
|
+
const refreshSpy = vi.spyOn(registry, "refresh").mockReturnValue(promise);
|
|
58
|
+
|
|
59
|
+
registry.refreshInBackground();
|
|
60
|
+
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
61
|
+
|
|
62
|
+
let settled = false;
|
|
63
|
+
const awaitPromise = registry.awaitBackgroundRefresh().then(() => {
|
|
64
|
+
settled = true;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Yield to the microtask queue: the awaiter must still be pending.
|
|
68
|
+
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
69
|
+
expect(settled).toBe(false);
|
|
70
|
+
|
|
71
|
+
resolve();
|
|
72
|
+
|
|
73
|
+
await awaitPromise;
|
|
74
|
+
expect(settled).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("resolves even when the underlying refresh rejects (refreshInBackground swallows)", async () => {
|
|
78
|
+
// refreshInBackground wraps refresh() in .catch(...) so discovery errors
|
|
79
|
+
// never reach awaitBackgroundRefresh callers. The awaiter must resolve,
|
|
80
|
+
// not propagate the rejection.
|
|
81
|
+
const { promise, reject } = Promise.withResolvers<void>();
|
|
82
|
+
vi.spyOn(registry, "refresh").mockReturnValue(promise);
|
|
83
|
+
|
|
84
|
+
registry.refreshInBackground();
|
|
85
|
+
|
|
86
|
+
let settled = false;
|
|
87
|
+
const awaitPromise = registry.awaitBackgroundRefresh().then(() => {
|
|
88
|
+
settled = true;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
reject(new Error("synthetic discovery failure"));
|
|
92
|
+
|
|
93
|
+
await awaitPromise;
|
|
94
|
+
expect(settled).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("awaiter is a no-op after the in-flight refresh settles and clears #backgroundRefresh", async () => {
|
|
98
|
+
// Once refreshInBackground's promise resolves, #backgroundRefresh is
|
|
99
|
+
// cleared in the .finally. A subsequent awaitBackgroundRefresh must be
|
|
100
|
+
// an immediate no-op (microtask), not hang waiting for a stale promise
|
|
101
|
+
// or a second refresh that was never started.
|
|
102
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
103
|
+
vi.spyOn(registry, "refresh").mockReturnValue(promise);
|
|
104
|
+
|
|
105
|
+
registry.refreshInBackground();
|
|
106
|
+
resolve();
|
|
107
|
+
await registry.awaitBackgroundRefresh();
|
|
108
|
+
|
|
109
|
+
// Now #backgroundRefresh is cleared. A fresh await must resolve in a
|
|
110
|
+
// single microtask — measure by asserting it settles before a second
|
|
111
|
+
// microtask tick.
|
|
112
|
+
let settled = false;
|
|
113
|
+
const p = registry.awaitBackgroundRefresh().then(() => {
|
|
114
|
+
settled = true;
|
|
115
|
+
});
|
|
116
|
+
await Promise.resolve();
|
|
117
|
+
expect(settled).toBe(true);
|
|
118
|
+
await p;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("refreshInBackground deduplicates: a second call while in-flight starts no new refresh", async () => {
|
|
122
|
+
// The guard `if (this.#backgroundRefresh) return` at the top of
|
|
123
|
+
// refreshInBackground prevents concurrent refreshes. A second call
|
|
124
|
+
// while the first is still pending must not invoke refresh() again.
|
|
125
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
126
|
+
const refreshSpy = vi.spyOn(registry, "refresh").mockReturnValue(promise);
|
|
127
|
+
|
|
128
|
+
registry.refreshInBackground();
|
|
129
|
+
registry.refreshInBackground();
|
|
130
|
+
registry.refreshInBackground();
|
|
131
|
+
|
|
132
|
+
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
133
|
+
|
|
134
|
+
resolve();
|
|
135
|
+
await registry.awaitBackgroundRefresh();
|
|
136
|
+
|
|
137
|
+
// After settle, #backgroundRefresh is cleared — a new call DOES start
|
|
138
|
+
// a fresh refresh.
|
|
139
|
+
const { promise: secondPromise, resolve: secondResolve } = Promise.withResolvers<void>();
|
|
140
|
+
refreshSpy.mockReturnValue(secondPromise);
|
|
141
|
+
registry.refreshInBackground();
|
|
142
|
+
expect(refreshSpy).toHaveBeenCalledTimes(2);
|
|
143
|
+
|
|
144
|
+
secondResolve();
|
|
145
|
+
await registry.awaitBackgroundRefresh();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -869,6 +869,28 @@ export class ModelRegistry {
|
|
|
869
869
|
this.#backgroundRefresh = refreshPromise;
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Wait for any in-flight background model discovery to settle.
|
|
874
|
+
*
|
|
875
|
+
* Background discovery started by {@link refreshInBackground} is
|
|
876
|
+
* fire-and-forget; RPC consumers (e.g. `get_available_models`,
|
|
877
|
+
* `set_model`) and deferred `--model` resolution that read the registry
|
|
878
|
+
* immediately after session creation can otherwise observe a partial
|
|
879
|
+
* catalog before discovery-backed providers have populated `#models`.
|
|
880
|
+
* Awaiting the tracked promise ensures the response reflects every
|
|
881
|
+
* configured provider once the initial background refresh resolves.
|
|
882
|
+
*
|
|
883
|
+
* No-op when no refresh is in flight (`#backgroundRefresh` cleared in the
|
|
884
|
+
* `finally` of `refreshInBackground` on completion). Resolves immediately
|
|
885
|
+
* in that case so already-warm sessions are unaffected. Discovery errors
|
|
886
|
+
* remain swallowed by `refreshInBackground`'s existing `.catch`.
|
|
887
|
+
*/
|
|
888
|
+
async awaitBackgroundRefresh(): Promise<void> {
|
|
889
|
+
if (this.#backgroundRefresh) {
|
|
890
|
+
await this.#backgroundRefresh;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
872
894
|
async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
|
|
873
895
|
this.#reloadStaticModels();
|
|
874
896
|
for (const selector of this.#suppressedSelectors.keys()) {
|
|
@@ -2101,6 +2123,24 @@ export class ModelRegistry {
|
|
|
2101
2123
|
.map(provider => provider.provider);
|
|
2102
2124
|
}
|
|
2103
2125
|
|
|
2126
|
+
/**
|
|
2127
|
+
* Whether `providerId` is known to the registry: it has at least one live
|
|
2128
|
+
* model, or it is configured for dynamic discovery (models.yml `discovery:`
|
|
2129
|
+
* or a runtime extension provider) and is not disabled. Discovery-only
|
|
2130
|
+
* providers can hold zero models at startup — cached rows never persist
|
|
2131
|
+
* live auth headers (#5780), so a provider whose discovered models all
|
|
2132
|
+
* carry config headers (`authHeader: true`) only materializes models after
|
|
2133
|
+
* the online refresh completes.
|
|
2134
|
+
*/
|
|
2135
|
+
hasProvider(providerId: string): boolean {
|
|
2136
|
+
if (this.#models.some(model => model.provider === providerId)) return true;
|
|
2137
|
+
if (getDisabledProviderIdsFromSettings().has(providerId)) return false;
|
|
2138
|
+
return (
|
|
2139
|
+
this.#discoverableProviders.some(provider => provider.provider === providerId) ||
|
|
2140
|
+
this.#runtimeModelManagers.has(providerId)
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2104
2144
|
getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
|
|
2105
2145
|
return this.#providerDiscoveryStates.get(provider);
|
|
2106
2146
|
}
|
|
@@ -782,7 +782,6 @@ function parseModelPatternWithContext(
|
|
|
782
782
|
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
|
783
783
|
): ParsedModelResult {
|
|
784
784
|
// Exact match on the full pattern first (no fuzzy): a literal id that
|
|
785
|
-
// contains a colon (`coding-router:max`) wins over any suffix split.
|
|
786
785
|
const exactMatch = matchModel(pattern, availableModels, context, { exactOnly: true });
|
|
787
786
|
if (exactMatch) {
|
|
788
787
|
return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
@@ -207,6 +207,8 @@ interface UiNumber extends UiBase {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
interface UiString extends UiBase {
|
|
210
|
+
/** Mask the value in both the settings row and text editor. */
|
|
211
|
+
secret?: boolean;
|
|
210
212
|
/**
|
|
211
213
|
* Submenu options.
|
|
212
214
|
* - Array → submenu with these choices.
|
|
@@ -219,6 +221,7 @@ interface UiString extends UiBase {
|
|
|
219
221
|
/** Wide ui shape exposed to consumers that walk the schema generically. */
|
|
220
222
|
export type AnyUiMetadata = UiBase & {
|
|
221
223
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
224
|
+
secret?: boolean;
|
|
222
225
|
};
|
|
223
226
|
|
|
224
227
|
interface BooleanDef {
|
|
@@ -2753,7 +2756,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
2753
2756
|
},
|
|
2754
2757
|
},
|
|
2755
2758
|
|
|
2756
|
-
"hindsight.apiToken": {
|
|
2759
|
+
"hindsight.apiToken": {
|
|
2760
|
+
type: "string",
|
|
2761
|
+
default: undefined,
|
|
2762
|
+
ui: {
|
|
2763
|
+
tab: "memory",
|
|
2764
|
+
group: "Hindsight",
|
|
2765
|
+
label: "Hindsight API Token",
|
|
2766
|
+
description: "Bearer token for authenticated Hindsight servers",
|
|
2767
|
+
condition: "hindsightActive",
|
|
2768
|
+
secret: true,
|
|
2769
|
+
},
|
|
2770
|
+
},
|
|
2757
2771
|
|
|
2758
2772
|
"hindsight.bankId": {
|
|
2759
2773
|
type: "string",
|
|
@@ -3945,6 +3959,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
3945
3959
|
},
|
|
3946
3960
|
},
|
|
3947
3961
|
|
|
3962
|
+
"mcp.renderMarkdownResults": {
|
|
3963
|
+
type: "boolean",
|
|
3964
|
+
default: true,
|
|
3965
|
+
ui: {
|
|
3966
|
+
tab: "tools",
|
|
3967
|
+
group: "Discovery & MCP",
|
|
3968
|
+
label: "MCP Markdown Results",
|
|
3969
|
+
description: "Render non-JSON MCP text results as Markdown in the transcript",
|
|
3970
|
+
},
|
|
3971
|
+
},
|
|
3972
|
+
|
|
3948
3973
|
"mcp.notifications": {
|
|
3949
3974
|
type: "boolean",
|
|
3950
3975
|
default: false,
|
|
@@ -4089,6 +4114,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
4089
4114
|
},
|
|
4090
4115
|
},
|
|
4091
4116
|
|
|
4117
|
+
"task.isolation.apply": {
|
|
4118
|
+
type: "boolean",
|
|
4119
|
+
default: true,
|
|
4120
|
+
ui: {
|
|
4121
|
+
tab: "tasks",
|
|
4122
|
+
group: "Isolation",
|
|
4123
|
+
label: "Apply Isolated Changes",
|
|
4124
|
+
description:
|
|
4125
|
+
"Automatically apply successful isolated task changes to the parent checkout; disable to retain patch or branch artifacts",
|
|
4126
|
+
},
|
|
4127
|
+
},
|
|
4128
|
+
|
|
4092
4129
|
"task.isolation.merge": {
|
|
4093
4130
|
type: "enum",
|
|
4094
4131
|
values: ["patch", "branch"] as const,
|
|
@@ -5032,12 +5069,13 @@ export const SETTINGS_SCHEMA = {
|
|
|
5032
5069
|
|
|
5033
5070
|
"dev.autoqa": {
|
|
5034
5071
|
type: "boolean",
|
|
5035
|
-
default:
|
|
5072
|
+
default: true,
|
|
5036
5073
|
ui: {
|
|
5037
5074
|
tab: "tools",
|
|
5038
5075
|
group: "Developer",
|
|
5039
5076
|
label: "Auto QA",
|
|
5040
|
-
description:
|
|
5077
|
+
description:
|
|
5078
|
+
"Automated tool issue reporting (xd://report_issue). On by default; the first report asks for consent, and denying it disables reporting until re-enabled explicitly",
|
|
5041
5079
|
},
|
|
5042
5080
|
},
|
|
5043
5081
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility shim for legacy extensions importing the package root of
|
|
3
|
+
* `@earendil-works/pi-tui` or `@mariozechner/pi-tui`.
|
|
4
|
+
*
|
|
5
|
+
* The historical root exported `decodeKittyPrintable`; the canonical TUI now
|
|
6
|
+
* exposes the equivalent, broader `decodePrintableKey` helper. Keep the legacy
|
|
7
|
+
* name available without reintroducing it into the canonical package surface.
|
|
8
|
+
*/
|
|
9
|
+
export * from "@oh-my-pi/pi-tui";
|
|
10
|
+
export { decodePrintableKey as decodeKittyPrintable } from "@oh-my-pi/pi-tui";
|