@oh-my-pi/pi-coding-agent 16.1.19 → 16.1.21
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 +25 -0
- package/dist/cli.js +3795 -3760
- package/dist/types/advisor/advise-tool.d.ts +3 -0
- package/dist/types/cli/gallery-cli.d.ts +6 -0
- package/dist/types/commands/gallery.d.ts +1 -1
- package/dist/types/config/service-tier.d.ts +34 -0
- package/dist/types/config/settings-schema.d.ts +36 -33
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +42 -5
- package/dist/types/modes/components/custom-editor.d.ts +32 -0
- package/dist/types/modes/components/tool-execution.d.ts +5 -5
- package/dist/types/modes/controllers/event-controller.d.ts +10 -0
- package/dist/types/modes/controllers/input-controller.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/task/parallel.d.ts +17 -1
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/utils/clipboard.d.ts +10 -0
- package/package.json +13 -13
- package/scripts/generate-legacy-pi-bundled-registry.ts +10 -0
- package/src/advisor/__tests__/advisor.test.ts +44 -0
- package/src/advisor/advise-tool.ts +33 -0
- package/src/autolearn/controller.ts +17 -2
- package/src/cli/gallery-cli.ts +31 -2
- package/src/cli/gallery-fixtures/agentic.ts +13 -4
- package/src/commands/gallery.ts +11 -3
- package/src/config/service-tier.ts +87 -0
- package/src/config/settings-schema.ts +48 -23
- package/src/eval/agent-bridge.ts +2 -0
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +1 -1
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +4 -4
- package/src/extensibility/plugins/legacy-pi-compat.ts +193 -5
- package/src/main.ts +1 -0
- package/src/mcp/manager.ts +12 -3
- package/src/mcp/oauth-flow.ts +121 -7
- package/src/mcp/transports/stdio.ts +5 -0
- package/src/modes/components/chat-transcript-builder.ts +31 -0
- package/src/modes/components/custom-editor.test.ts +80 -0
- package/src/modes/components/custom-editor.ts +86 -6
- package/src/modes/components/tool-execution.ts +50 -25
- package/src/modes/controllers/event-controller.ts +57 -8
- package/src/modes/controllers/input-controller.ts +213 -93
- package/src/modes/controllers/mcp-command-controller.ts +18 -2
- package/src/modes/utils/ui-helpers.ts +40 -0
- package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
- package/src/prompts/system/autolearn-nudge.md +4 -2
- package/src/prompts/tools/todo.md +1 -1
- package/src/sdk.ts +1 -0
- package/src/session/agent-session.ts +76 -15
- package/src/session/session-history-format.ts +21 -4
- package/src/task/executor.ts +79 -2
- package/src/task/index.ts +11 -6
- package/src/task/parallel.ts +59 -7
- package/src/tools/index.ts +3 -1
- package/src/tools/irc.ts +16 -2
- package/src/tools/todo.ts +20 -10
- package/src/utils/clipboard.ts +57 -0
- package/src/utils/shell-snapshot-fn-env.sh +60 -0
- package/src/utils/shell-snapshot.ts +77 -20
|
@@ -15,9 +15,11 @@ import type { Settings } from "../config/settings";
|
|
|
15
15
|
import autolearnGuidance from "../prompts/system/autolearn-guidance.md" with { type: "text" };
|
|
16
16
|
import autolearnGuidanceLearn from "../prompts/system/autolearn-guidance-learn.md" with { type: "text" };
|
|
17
17
|
import autolearnNudge from "../prompts/system/autolearn-nudge.md" with { type: "text" };
|
|
18
|
+
import autolearnNudgeAutoContinue from "../prompts/system/autolearn-nudge-autocontinue.md" with { type: "text" };
|
|
18
19
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
+
const AUTOLEARN_NUDGE_PASSIVE = autolearnNudge.trim();
|
|
22
|
+
const AUTOLEARN_NUDGE_AUTOCONTINUE = autolearnNudgeAutoContinue.trim();
|
|
21
23
|
const DEFAULT_MIN_TOOL_CALLS = 5;
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -110,7 +112,20 @@ export class AutoLearnController {
|
|
|
110
112
|
|
|
111
113
|
// Auto-run a capture turn only when explicitly enabled; otherwise the
|
|
112
114
|
// hidden reminder rides the next real turn passively.
|
|
115
|
+
//
|
|
116
|
+
// The two paths get DIFFERENT nudge text:
|
|
117
|
+
// - Auto-continue spawns a synthetic turn with the nudge as its only
|
|
118
|
+
// user-role payload, so the prompt must be terminal — it tells the
|
|
119
|
+
// agent to capture and STOP, and must not be read as the user's
|
|
120
|
+
// reply to any pending question. Without that contract, the agent
|
|
121
|
+
// conflates the synthetic prompt with user approval and resumes
|
|
122
|
+
// prior work (e.g. commits/pushes an unanswered "want me to push?"
|
|
123
|
+
// question — #3504).
|
|
124
|
+
// - Passive mode appends the nudge to the user's real next message,
|
|
125
|
+
// so the agent must answer the user normally and treat the capture
|
|
126
|
+
// as additive — not as the whole turn's job.
|
|
113
127
|
const autoContinue = this.#settings.get("autolearn.autoContinue") === true;
|
|
128
|
+
const content = autoContinue ? AUTOLEARN_NUDGE_AUTOCONTINUE : AUTOLEARN_NUDGE_PASSIVE;
|
|
114
129
|
// Arm suppression synchronously: the synthetic capture turn's agent_end
|
|
115
130
|
// fires inside sendCustomMessage (before it resolves), so the flag must be
|
|
116
131
|
// set before then. Disarm when no turn actually started — a deferred/queued
|
|
@@ -122,7 +137,7 @@ export class AutoLearnController {
|
|
|
122
137
|
.sendCustomMessage(
|
|
123
138
|
{
|
|
124
139
|
customType: "autolearn-nudge",
|
|
125
|
-
content
|
|
140
|
+
content,
|
|
126
141
|
display: false,
|
|
127
142
|
attribution: "user",
|
|
128
143
|
},
|
package/src/cli/gallery-cli.ts
CHANGED
|
@@ -21,13 +21,42 @@ import { captureGalleryScreenshots } from "./gallery-screenshot";
|
|
|
21
21
|
export const GALLERY_STATES = ["streaming", "progress", "success", "error"] as const;
|
|
22
22
|
export type GalleryState = (typeof GALLERY_STATES)[number];
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/** User-facing labels printed above each rendered lifecycle state. */
|
|
25
|
+
export const GALLERY_STATE_LABELS: Record<GalleryState, string> = {
|
|
25
26
|
streaming: "streaming args",
|
|
26
27
|
progress: "in progress",
|
|
27
28
|
success: "done",
|
|
28
29
|
error: "failed",
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const GALLERY_STATE_ALIASES: Record<string, GalleryState> = {
|
|
33
|
+
streaming: "streaming",
|
|
34
|
+
"streaming args": "streaming",
|
|
35
|
+
progress: "progress",
|
|
36
|
+
"in progress": "progress",
|
|
37
|
+
success: "success",
|
|
38
|
+
done: "success",
|
|
39
|
+
error: "error",
|
|
40
|
+
failed: "error",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Accepted `--state` tokens, including legacy lifecycle names and displayed labels. */
|
|
44
|
+
export const GALLERY_STATE_TOKENS = Object.keys(GALLERY_STATE_ALIASES);
|
|
45
|
+
|
|
46
|
+
/** Normalize user-provided `--state` tokens to the internal gallery lifecycle states. */
|
|
47
|
+
export function parseGalleryStates(states: readonly string[] | undefined): GalleryState[] | undefined {
|
|
48
|
+
if (!states || states.length === 0) return undefined;
|
|
49
|
+
const parsed: GalleryState[] = [];
|
|
50
|
+
for (const raw of states) {
|
|
51
|
+
const state = GALLERY_STATE_ALIASES[raw.trim().toLowerCase()];
|
|
52
|
+
if (!state) {
|
|
53
|
+
throw new Error(`Invalid --state '${raw}'. Valid values: ${GALLERY_STATE_TOKENS.join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
if (!parsed.includes(state)) parsed.push(state);
|
|
56
|
+
}
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
export interface GalleryCommandArgs {
|
|
32
61
|
/** Render width in columns (defaults to terminal width, clamped). */
|
|
33
62
|
width?: number;
|
|
@@ -166,7 +195,7 @@ async function renderGallerySections(
|
|
|
166
195
|
const heading = fixture.label && fixture.label !== name ? `${name} — ${fixture.label}` : name;
|
|
167
196
|
const lines: string[] = ["", sectionRule(heading, width)];
|
|
168
197
|
for (const state of states) {
|
|
169
|
-
lines.push("", theme.fg("dim", ` · ${
|
|
198
|
+
lines.push("", theme.fg("dim", ` · ${GALLERY_STATE_LABELS[state]}`));
|
|
170
199
|
try {
|
|
171
200
|
for (const line of await renderGalleryState(name, fixture, state, width, expanded)) lines.push(line);
|
|
172
201
|
} catch (err) {
|
|
@@ -263,6 +263,11 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
|
|
|
263
263
|
],
|
|
264
264
|
} satisfies IrcDetails,
|
|
265
265
|
},
|
|
266
|
+
errorResult: {
|
|
267
|
+
isError: true,
|
|
268
|
+
content: [{ type: "text", text: "IRC inbox failed: message store unavailable." }],
|
|
269
|
+
details: { op: "inbox" } satisfies IrcDetails,
|
|
270
|
+
},
|
|
266
271
|
},
|
|
267
272
|
|
|
268
273
|
irc_list: {
|
|
@@ -309,6 +314,11 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
|
|
|
309
314
|
],
|
|
310
315
|
} satisfies IrcDetails,
|
|
311
316
|
},
|
|
317
|
+
errorResult: {
|
|
318
|
+
isError: true,
|
|
319
|
+
content: [{ type: "text", text: "IRC list failed: agent hub is unavailable." }],
|
|
320
|
+
details: { op: "list" } satisfies IrcDetails,
|
|
321
|
+
},
|
|
312
322
|
},
|
|
313
323
|
|
|
314
324
|
goal: {
|
|
@@ -388,19 +398,18 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
|
|
|
388
398
|
},
|
|
389
399
|
errorResult: {
|
|
390
400
|
isError: true,
|
|
391
|
-
content: [{ type: "text", text: "
|
|
401
|
+
content: [{ type: "text", text: "1 job failed." }],
|
|
392
402
|
details: {
|
|
393
403
|
jobs: [
|
|
394
404
|
{
|
|
395
405
|
id: "job_d4",
|
|
396
406
|
type: "task",
|
|
397
|
-
status: "
|
|
407
|
+
status: "failed",
|
|
398
408
|
label: "Refactor the session store to Redis",
|
|
399
409
|
durationMs: 52_300,
|
|
400
|
-
errorText: "
|
|
410
|
+
errorText: "Subagent exited 1: Redis connection string is missing.",
|
|
401
411
|
},
|
|
402
412
|
],
|
|
403
|
-
cancelled: [{ id: "job_d4", status: "cancelled" }],
|
|
404
413
|
},
|
|
405
414
|
},
|
|
406
415
|
},
|
package/src/commands/gallery.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Render every built-in tool's renderer across its lifecycle states.
|
|
3
3
|
*/
|
|
4
4
|
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
|
-
import {
|
|
5
|
+
import { GALLERY_STATE_TOKENS, type GalleryState, parseGalleryStates, runGalleryCommand } from "../cli/gallery-cli";
|
|
6
6
|
|
|
7
7
|
export default class Gallery extends Command {
|
|
8
8
|
static description = "Preview tool renderers across streaming, in-progress, success, and failure states";
|
|
@@ -12,7 +12,7 @@ export default class Gallery extends Command {
|
|
|
12
12
|
state: Flags.string({
|
|
13
13
|
char: "s",
|
|
14
14
|
description: "Render only the given lifecycle state(s)",
|
|
15
|
-
options:
|
|
15
|
+
options: GALLERY_STATE_TOKENS,
|
|
16
16
|
multiple: true,
|
|
17
17
|
}),
|
|
18
18
|
width: Flags.integer({ char: "w", description: "Render width in columns" }),
|
|
@@ -37,9 +37,17 @@ export default class Gallery extends Command {
|
|
|
37
37
|
|
|
38
38
|
async run(): Promise<void> {
|
|
39
39
|
const { flags } = await this.parse(Gallery);
|
|
40
|
+
let states: GalleryState[] | undefined;
|
|
41
|
+
try {
|
|
42
|
+
states = parseGalleryStates(flags.state);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
40
48
|
await runGalleryCommand({
|
|
41
49
|
tool: flags.tool,
|
|
42
|
-
states
|
|
50
|
+
states,
|
|
43
51
|
width: flags.width,
|
|
44
52
|
expanded: flags.expanded,
|
|
45
53
|
plain: flags.plain,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ServiceTier } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { SubmenuOption } from "./settings-schema";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Service-tier setting values shared by every "Service Tier" setting. `"none"`
|
|
6
|
+
* is the omit-the-parameter sentinel; the remaining values mirror
|
|
7
|
+
* {@link ServiceTier}.
|
|
8
|
+
*/
|
|
9
|
+
export const SERVICE_TIER_SETTING_VALUES = [
|
|
10
|
+
"none",
|
|
11
|
+
"auto",
|
|
12
|
+
"default",
|
|
13
|
+
"flex",
|
|
14
|
+
"scale",
|
|
15
|
+
"priority",
|
|
16
|
+
"openai-only",
|
|
17
|
+
"claude-only",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
export type ServiceTierSettingValue = (typeof SERVICE_TIER_SETTING_VALUES)[number];
|
|
21
|
+
|
|
22
|
+
/** Variant value set for scoped service-tier settings (subagent/advisor) that can defer to the main agent. */
|
|
23
|
+
export const SERVICE_TIER_INHERIT_SETTING_VALUES = ["inherit", ...SERVICE_TIER_SETTING_VALUES] as const;
|
|
24
|
+
|
|
25
|
+
export type ServiceTierInheritSettingValue = (typeof SERVICE_TIER_INHERIT_SETTING_VALUES)[number];
|
|
26
|
+
|
|
27
|
+
/** Submenu descriptions shared by the base `serviceTier` setting. */
|
|
28
|
+
export const SERVICE_TIER_OPTIONS: ReadonlyArray<SubmenuOption<ServiceTierSettingValue>> = [
|
|
29
|
+
{ value: "none", label: "None", description: "Omit service_tier parameter" },
|
|
30
|
+
{ value: "auto", label: "Auto", description: "Use provider default tier selection (OpenAI)" },
|
|
31
|
+
{ value: "default", label: "Default", description: "Standard priority processing (OpenAI)" },
|
|
32
|
+
{ value: "flex", label: "Flex", description: "Flexible capacity tier when available (OpenAI)" },
|
|
33
|
+
{ value: "scale", label: "Scale", description: "Scale Tier credits when available (OpenAI)" },
|
|
34
|
+
{
|
|
35
|
+
value: "priority",
|
|
36
|
+
label: "Priority",
|
|
37
|
+
description: "Priority on every supported provider (OpenAI `service_tier`, Anthropic fast mode)",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
value: "openai-only",
|
|
41
|
+
label: "Priority (OpenAI only)",
|
|
42
|
+
description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: "claude-only",
|
|
46
|
+
label: "Priority (Claude only)",
|
|
47
|
+
description: "Anthropic fast mode on direct Claude requests; ignored elsewhere (incl. Bedrock/Vertex)",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/** Submenu descriptions for inherit-capable service-tier settings. */
|
|
52
|
+
export const SERVICE_TIER_INHERIT_OPTIONS: ReadonlyArray<SubmenuOption<ServiceTierInheritSettingValue>> = [
|
|
53
|
+
{ value: "inherit", label: "Inherit", description: "Use the main agent's Service Tier" },
|
|
54
|
+
...SERVICE_TIER_OPTIONS,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a service-tier setting value to the wire {@link ServiceTier} (or
|
|
59
|
+
* `undefined` to omit). `"inherit"` defers to `inherited`; `"none"` omits.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveServiceTierSetting(value: string, inherited: ServiceTier | undefined): ServiceTier | undefined {
|
|
62
|
+
if (value === "inherit") return inherited;
|
|
63
|
+
if (value === "none" || value === "") return undefined;
|
|
64
|
+
return value as ServiceTier;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the `serviceTier` *setting value* to stamp onto a subagent's settings
|
|
69
|
+
* snapshot.
|
|
70
|
+
*
|
|
71
|
+
* - A concrete `subagentSetting` (`"none"` or a tier) wins outright.
|
|
72
|
+
* - `"inherit"` defers to the parent's live effective tier when the caller has a
|
|
73
|
+
* live session (`inherited` passed as `ServiceTier | null`, where `null` means
|
|
74
|
+
* the parent explicitly has no tier — e.g. `/fast off`). When no live session
|
|
75
|
+
* is available (`inherited === undefined`, e.g. cold subagent revive) it falls
|
|
76
|
+
* back to the parent's configured `serviceTier` setting so behavior matches a
|
|
77
|
+
* plain settings snapshot.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveSubagentServiceTier(
|
|
80
|
+
subagentSetting: string,
|
|
81
|
+
configuredTier: ServiceTierSettingValue,
|
|
82
|
+
inherited: ServiceTier | null | undefined,
|
|
83
|
+
): ServiceTierSettingValue {
|
|
84
|
+
if (subagentSetting !== "inherit") return subagentSetting as ServiceTierSettingValue;
|
|
85
|
+
if (inherited === undefined) return configuredTier;
|
|
86
|
+
return inherited ?? "none";
|
|
87
|
+
}
|
|
@@ -35,6 +35,12 @@ import {
|
|
|
35
35
|
} from "../tts/models";
|
|
36
36
|
import { EDIT_MODES } from "../utils/edit-mode";
|
|
37
37
|
import { SEARCH_PROVIDER_OPTIONS, SEARCH_PROVIDER_PREFERENCES, type SearchProviderId } from "../web/search/types";
|
|
38
|
+
import {
|
|
39
|
+
SERVICE_TIER_INHERIT_OPTIONS,
|
|
40
|
+
SERVICE_TIER_INHERIT_SETTING_VALUES,
|
|
41
|
+
SERVICE_TIER_OPTIONS,
|
|
42
|
+
SERVICE_TIER_SETTING_VALUES,
|
|
43
|
+
} from "./service-tier";
|
|
38
44
|
|
|
39
45
|
/** Unified settings schema - single source of truth for all settings.
|
|
40
46
|
*
|
|
@@ -1122,7 +1128,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1122
1128
|
|
|
1123
1129
|
serviceTier: {
|
|
1124
1130
|
type: "enum",
|
|
1125
|
-
values:
|
|
1131
|
+
values: SERVICE_TIER_SETTING_VALUES,
|
|
1126
1132
|
default: "none",
|
|
1127
1133
|
ui: {
|
|
1128
1134
|
tab: "model",
|
|
@@ -1130,28 +1136,36 @@ export const SETTINGS_SCHEMA = {
|
|
|
1130
1136
|
label: "Service Tier",
|
|
1131
1137
|
description:
|
|
1132
1138
|
'Processing priority hint (none = omit). OpenAI accepts the tier values directly; Anthropic realizes `priority` as `speed: "fast"` on supported Opus models. Scoped values target one family.',
|
|
1133
|
-
options:
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1139
|
+
options: SERVICE_TIER_OPTIONS,
|
|
1140
|
+
},
|
|
1141
|
+
},
|
|
1142
|
+
|
|
1143
|
+
serviceTierSubagent: {
|
|
1144
|
+
type: "enum",
|
|
1145
|
+
values: SERVICE_TIER_INHERIT_SETTING_VALUES,
|
|
1146
|
+
default: "inherit",
|
|
1147
|
+
ui: {
|
|
1148
|
+
tab: "model",
|
|
1149
|
+
group: "Sampling",
|
|
1150
|
+
label: "Service Tier - Subagent",
|
|
1151
|
+
description:
|
|
1152
|
+
"Service Tier for spawned task/eval subagents. Inherit = match the main agent's live tier (tracks /fast); pick a value to scope subagents independently.",
|
|
1153
|
+
options: SERVICE_TIER_INHERIT_OPTIONS,
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
serviceTierAdvisor: {
|
|
1158
|
+
type: "enum",
|
|
1159
|
+
values: SERVICE_TIER_INHERIT_SETTING_VALUES,
|
|
1160
|
+
default: "none",
|
|
1161
|
+
ui: {
|
|
1162
|
+
tab: "model",
|
|
1163
|
+
group: "Sampling",
|
|
1164
|
+
label: "Service Tier - Advisor",
|
|
1165
|
+
description:
|
|
1166
|
+
"Service Tier for the advisor model. None = standard processing; Inherit = match the main agent's live tier; pick a value (e.g. Priority) to run the advisor on a faster serving path.",
|
|
1167
|
+
options: SERVICE_TIER_INHERIT_OPTIONS,
|
|
1168
|
+
condition: "advisorEnabled",
|
|
1155
1169
|
},
|
|
1156
1170
|
},
|
|
1157
1171
|
|
|
@@ -4041,6 +4055,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
4041
4055
|
},
|
|
4042
4056
|
|
|
4043
4057
|
// Provider selection
|
|
4058
|
+
"providers.ollama-cloud.maxConcurrency": {
|
|
4059
|
+
type: "number",
|
|
4060
|
+
default: 3,
|
|
4061
|
+
ui: {
|
|
4062
|
+
tab: "providers",
|
|
4063
|
+
group: "Services",
|
|
4064
|
+
label: "Ollama Cloud Max Concurrency",
|
|
4065
|
+
description:
|
|
4066
|
+
"Maximum concurrent Ollama Cloud subagent runs per process; 0 disables the provider-specific limit",
|
|
4067
|
+
},
|
|
4068
|
+
},
|
|
4044
4069
|
"providers.webSearch": {
|
|
4045
4070
|
type: "enum",
|
|
4046
4071
|
values: SEARCH_PROVIDER_PREFERENCES,
|
package/src/eval/agent-bridge.ts
CHANGED
|
@@ -397,6 +397,8 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
397
397
|
parentMnemopiSessionState: options.session.getMnemopiSessionState?.(),
|
|
398
398
|
parentTelemetry: options.session.getTelemetry?.(),
|
|
399
399
|
parentAgentId: options.session.getAgentId?.() ?? MAIN_AGENT_ID,
|
|
400
|
+
// Live source of truth for `serviceTierSubagent: inherit` (null = explicit none).
|
|
401
|
+
parentServiceTier: options.session.getServiceTier ? (options.session.getServiceTier() ?? null) : undefined,
|
|
400
402
|
// Deliberately omit parentEvalSessionId: the parent's Python kernel is
|
|
401
403
|
// blocked on this bridge call, so sharing the eval session would deadlock
|
|
402
404
|
// (subagent queues behind the parent's in-flight execution, parent waits
|
|
@@ -360,6 +360,7 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet<string> = new Set([
|
|
|
360
360
|
"@oh-my-pi/pi-coding-agent/config/models-config",
|
|
361
361
|
"@oh-my-pi/pi-coding-agent/config/prompt-templates",
|
|
362
362
|
"@oh-my-pi/pi-coding-agent/config/resolve-config-value",
|
|
363
|
+
"@oh-my-pi/pi-coding-agent/config/service-tier",
|
|
363
364
|
"@oh-my-pi/pi-coding-agent/config/settings-schema",
|
|
364
365
|
"@oh-my-pi/pi-coding-agent/config/settings",
|
|
365
366
|
"@oh-my-pi/pi-coding-agent/dap/client",
|
|
@@ -483,7 +484,6 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet<string> = new Set([
|
|
|
483
484
|
"@oh-my-pi/pi-coding-agent/eval/js/executor",
|
|
484
485
|
"@oh-my-pi/pi-coding-agent/eval/js/tool-bridge",
|
|
485
486
|
"@oh-my-pi/pi-coding-agent/eval/js/worker-core",
|
|
486
|
-
"@oh-my-pi/pi-coding-agent/eval/js/worker-entry",
|
|
487
487
|
"@oh-my-pi/pi-coding-agent/eval/js/worker-protocol",
|
|
488
488
|
"@oh-my-pi/pi-coding-agent/eval/py/display",
|
|
489
489
|
"@oh-my-pi/pi-coding-agent/eval/py/executor",
|
|
@@ -328,6 +328,7 @@ import * as bundledPiCodingAgentConfigModelsConfig from "@oh-my-pi/pi-coding-age
|
|
|
328
328
|
import * as bundledPiCodingAgentConfigModelsConfigSchema from "@oh-my-pi/pi-coding-agent/config/models-config-schema";
|
|
329
329
|
import * as bundledPiCodingAgentConfigPromptTemplates from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
|
|
330
330
|
import * as bundledPiCodingAgentConfigResolveConfigValue from "@oh-my-pi/pi-coding-agent/config/resolve-config-value";
|
|
331
|
+
import * as bundledPiCodingAgentConfigServiceTier from "@oh-my-pi/pi-coding-agent/config/service-tier";
|
|
331
332
|
import * as bundledPiCodingAgentConfigSettings from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
332
333
|
import * as bundledPiCodingAgentConfigSettingsSchema from "@oh-my-pi/pi-coding-agent/config/settings-schema";
|
|
333
334
|
import * as bundledPiCodingAgentDap from "@oh-my-pi/pi-coding-agent/dap";
|
|
@@ -385,7 +386,6 @@ import * as bundledPiCodingAgentEvalJsContextManager from "@oh-my-pi/pi-coding-a
|
|
|
385
386
|
import * as bundledPiCodingAgentEvalJsExecutor from "@oh-my-pi/pi-coding-agent/eval/js/executor";
|
|
386
387
|
import * as bundledPiCodingAgentEvalJsToolBridge from "@oh-my-pi/pi-coding-agent/eval/js/tool-bridge";
|
|
387
388
|
import * as bundledPiCodingAgentEvalJsWorkerCore from "@oh-my-pi/pi-coding-agent/eval/js/worker-core";
|
|
388
|
-
import * as bundledPiCodingAgentEvalJsWorkerEntry from "@oh-my-pi/pi-coding-agent/eval/js/worker-entry";
|
|
389
389
|
import * as bundledPiCodingAgentEvalJsWorkerProtocol from "@oh-my-pi/pi-coding-agent/eval/js/worker-protocol";
|
|
390
390
|
import * as bundledPiCodingAgentEvalPyDisplay from "@oh-my-pi/pi-coding-agent/eval/py/display";
|
|
391
391
|
import * as bundledPiCodingAgentEvalPyExecutor from "@oh-my-pi/pi-coding-agent/eval/py/executor";
|
|
@@ -1802,6 +1802,9 @@ export const BUNDLED_PI_REGISTRY: Readonly<Record<string, Readonly<Record<string
|
|
|
1802
1802
|
bundledPiCodingAgentConfigPromptTemplates as unknown as Readonly<Record<string, unknown>>,
|
|
1803
1803
|
"@oh-my-pi/pi-coding-agent/config/resolve-config-value":
|
|
1804
1804
|
bundledPiCodingAgentConfigResolveConfigValue as unknown as Readonly<Record<string, unknown>>,
|
|
1805
|
+
"@oh-my-pi/pi-coding-agent/config/service-tier": bundledPiCodingAgentConfigServiceTier as unknown as Readonly<
|
|
1806
|
+
Record<string, unknown>
|
|
1807
|
+
>,
|
|
1805
1808
|
"@oh-my-pi/pi-coding-agent/config/settings-schema": bundledPiCodingAgentConfigSettingsSchema as unknown as Readonly<
|
|
1806
1809
|
Record<string, unknown>
|
|
1807
1810
|
>,
|
|
@@ -2100,9 +2103,6 @@ export const BUNDLED_PI_REGISTRY: Readonly<Record<string, Readonly<Record<string
|
|
|
2100
2103
|
"@oh-my-pi/pi-coding-agent/eval/js/worker-core": bundledPiCodingAgentEvalJsWorkerCore as unknown as Readonly<
|
|
2101
2104
|
Record<string, unknown>
|
|
2102
2105
|
>,
|
|
2103
|
-
"@oh-my-pi/pi-coding-agent/eval/js/worker-entry": bundledPiCodingAgentEvalJsWorkerEntry as unknown as Readonly<
|
|
2104
|
-
Record<string, unknown>
|
|
2105
|
-
>,
|
|
2106
2106
|
"@oh-my-pi/pi-coding-agent/eval/js/worker-protocol": bundledPiCodingAgentEvalJsWorkerProtocol as unknown as Readonly<
|
|
2107
2107
|
Record<string, unknown>
|
|
2108
2108
|
>,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { isBuiltin } from "node:module";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import * as url from "node:url";
|
|
4
5
|
import { isCompiledBinary } from "@oh-my-pi/pi-utils";
|
|
@@ -456,10 +457,11 @@ const TYPEBOX_IMPORT_SPECIFIER_REGEX = /((?:from\s+|import\s+|import\s*\(\s*)["'
|
|
|
456
457
|
|
|
457
458
|
/**
|
|
458
459
|
* Rewrite the extension-owned specifiers OMP must host-resolve — legacy
|
|
459
|
-
* `@(scope)/pi-*`, bare TypeBox packages,
|
|
460
|
-
* `#src
|
|
461
|
-
*
|
|
462
|
-
*
|
|
460
|
+
* `@(scope)/pi-*`, bare TypeBox packages, package `imports` aliases like
|
|
461
|
+
* `#src/*`, and extension-local bare dependencies — to absolute `file://` URLs
|
|
462
|
+
* or compiled-mode virtual specifiers. Relative siblings and built-in modules
|
|
463
|
+
* are left untouched so Bun resolves them from the extension's real on-disk
|
|
464
|
+
* location.
|
|
463
465
|
*/
|
|
464
466
|
async function rewriteLegacyExtensionSource(source: string, importerPath: string): Promise<string> {
|
|
465
467
|
const withPi = rewriteLegacyPiImports(source);
|
|
@@ -474,7 +476,12 @@ async function rewriteLegacyExtensionSource(source: string, importerPath: string
|
|
|
474
476
|
`${prefix}${toImportSpecifier(TYPEBOX_SHIM_PATH)}${suffix}`,
|
|
475
477
|
)
|
|
476
478
|
: withPi;
|
|
477
|
-
return rewriteExtensionPackageImports(withTypeBox, importerPath);
|
|
479
|
+
return rewriteExtensionBareImports(await rewriteExtensionPackageImports(withTypeBox, importerPath), importerPath);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Test seam for compiled-binary legacy extension source rewriting. */
|
|
483
|
+
export async function __rewriteLegacyExtensionSourceForTests(source: string, importerPath: string): Promise<string> {
|
|
484
|
+
return rewriteLegacyExtensionSource(source, importerPath);
|
|
478
485
|
}
|
|
479
486
|
|
|
480
487
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -688,6 +695,187 @@ async function rewriteExtensionPackageImports(source: string, importerPath: stri
|
|
|
688
695
|
return `${rewritten}${source.slice(lastIndex)}`;
|
|
689
696
|
}
|
|
690
697
|
|
|
698
|
+
const BARE_EXTENSION_IMPORT_SPECIFIER_REGEX = /((?:from\s+|import\s+|import\s*\(\s*)["'])([^"'()\s]+)(["'])/g;
|
|
699
|
+
|
|
700
|
+
function isBareExtensionDependencySpecifier(specifier: string): boolean {
|
|
701
|
+
if (
|
|
702
|
+
specifier.startsWith(".") ||
|
|
703
|
+
specifier.startsWith("/") ||
|
|
704
|
+
specifier.startsWith("#") ||
|
|
705
|
+
specifier.startsWith("node:") ||
|
|
706
|
+
specifier.startsWith("bun:") ||
|
|
707
|
+
/^[a-z][a-z0-9+.-]*:/i.test(specifier)
|
|
708
|
+
) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
const packageName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
|
712
|
+
return Boolean(packageName && !isBuiltin(packageName));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
interface BarePackageSpecifier {
|
|
716
|
+
readonly name: string;
|
|
717
|
+
readonly subpath: string | null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function splitBarePackageSpecifier(specifier: string): BarePackageSpecifier | null {
|
|
721
|
+
const parts = specifier.split("/");
|
|
722
|
+
if (specifier.startsWith("@")) {
|
|
723
|
+
const [scope, name, ...rest] = parts;
|
|
724
|
+
if (!scope || !name) return null;
|
|
725
|
+
return { name: `${scope}/${name}`, subpath: rest.length > 0 ? rest.join("/") : null };
|
|
726
|
+
}
|
|
727
|
+
const [name, ...rest] = parts;
|
|
728
|
+
if (!name) return null;
|
|
729
|
+
return { name, subpath: rest.length > 0 ? rest.join("/") : null };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async function findNodePackageRoot(packageName: string, importerPath: string): Promise<string | null> {
|
|
733
|
+
let dir = path.dirname(importerPath);
|
|
734
|
+
while (true) {
|
|
735
|
+
const candidate = path.join(dir, "node_modules", packageName);
|
|
736
|
+
if (await pathExists(path.join(candidate, "package.json"))) {
|
|
737
|
+
return candidate;
|
|
738
|
+
}
|
|
739
|
+
const parent = path.dirname(dir);
|
|
740
|
+
if (parent === dir) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
dir = parent;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async function readPackageManifest(packageRoot: string): Promise<Record<string, unknown> | null> {
|
|
748
|
+
try {
|
|
749
|
+
const manifest = await Bun.file(path.join(packageRoot, "package.json")).json();
|
|
750
|
+
return isRecord(manifest) ? manifest : null;
|
|
751
|
+
} catch {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async function resolvePackageExportTarget(
|
|
757
|
+
packageRoot: string,
|
|
758
|
+
target: string,
|
|
759
|
+
wildcard: string | null,
|
|
760
|
+
): Promise<string | null> {
|
|
761
|
+
if (!target.startsWith("./")) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
const substituted = wildcard === null ? target : target.replaceAll("*", wildcard);
|
|
765
|
+
return resolveSourceModuleFile(path.resolve(packageRoot, substituted));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function resolveNodePackageExport(
|
|
769
|
+
packageRoot: string,
|
|
770
|
+
subpath: string | null,
|
|
771
|
+
manifest: Record<string, unknown>,
|
|
772
|
+
): Promise<string | null> {
|
|
773
|
+
const exportsField = manifest.exports;
|
|
774
|
+
const rootTarget = subpath === null ? selectPackageImportTarget(exportsField) : null;
|
|
775
|
+
if (rootTarget !== null && rootTarget !== PACKAGE_IMPORT_EXCLUDED) {
|
|
776
|
+
return resolvePackageExportTarget(packageRoot, rootTarget, null);
|
|
777
|
+
}
|
|
778
|
+
if (!isRecord(exportsField)) {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const exactKey = subpath === null ? "." : `./${subpath}`;
|
|
783
|
+
const exactTarget = selectPackageImportTarget(exportsField[exactKey]);
|
|
784
|
+
if (exactTarget !== null && exactTarget !== PACKAGE_IMPORT_EXCLUDED) {
|
|
785
|
+
return resolvePackageExportTarget(packageRoot, exactTarget, null);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
for (const [key, entry] of Object.entries(exportsField)) {
|
|
789
|
+
const starIndex = key.indexOf("*");
|
|
790
|
+
if (starIndex === -1 || subpath === null) continue;
|
|
791
|
+
const prefix = key.slice(2, starIndex);
|
|
792
|
+
const suffix = key.slice(starIndex + 1);
|
|
793
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
const target = selectPackageImportTarget(entry);
|
|
797
|
+
if (target === null || target === PACKAGE_IMPORT_EXCLUDED) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
return resolvePackageExportTarget(
|
|
801
|
+
packageRoot,
|
|
802
|
+
target,
|
|
803
|
+
subpath.slice(prefix.length, subpath.length - suffix.length),
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async function resolveNodePackageFallback(
|
|
810
|
+
packageRoot: string,
|
|
811
|
+
subpath: string | null,
|
|
812
|
+
manifest: Record<string, unknown>,
|
|
813
|
+
): Promise<string | null> {
|
|
814
|
+
if (subpath !== null) {
|
|
815
|
+
return resolveSourceModuleFile(path.join(packageRoot, subpath));
|
|
816
|
+
}
|
|
817
|
+
for (const field of ["module", "main"]) {
|
|
818
|
+
const target = manifest[field];
|
|
819
|
+
if (typeof target === "string") {
|
|
820
|
+
const resolved = await resolveSourceModuleFile(path.resolve(packageRoot, target));
|
|
821
|
+
if (resolved) return resolved;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return resolveSourceModuleFile(path.join(packageRoot, "index"));
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async function resolveNodePackageDependency(specifier: string, importerPath: string): Promise<string | null> {
|
|
828
|
+
const parsed = splitBarePackageSpecifier(specifier);
|
|
829
|
+
if (!parsed) return null;
|
|
830
|
+
const packageRoot = await findNodePackageRoot(parsed.name, importerPath);
|
|
831
|
+
if (!packageRoot) return null;
|
|
832
|
+
const manifest = await readPackageManifest(packageRoot);
|
|
833
|
+
if (!manifest) return null;
|
|
834
|
+
return (
|
|
835
|
+
(await resolveNodePackageExport(packageRoot, parsed.subpath, manifest)) ??
|
|
836
|
+
(await resolveNodePackageFallback(packageRoot, parsed.subpath, manifest))
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function resolveExtensionBareDependency(specifier: string, importerPath: string): Promise<string | null> {
|
|
841
|
+
if (!isBareExtensionDependencySpecifier(specifier)) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
try {
|
|
845
|
+
const resolved = Bun.resolveSync(specifier, path.dirname(importerPath));
|
|
846
|
+
if (resolved && resolved !== specifier && !resolved.startsWith("node:") && !resolved.startsWith("bun:")) {
|
|
847
|
+
return resolved;
|
|
848
|
+
}
|
|
849
|
+
} catch {
|
|
850
|
+
// Compiled binaries do not reliably resolve runtime extension node_modules.
|
|
851
|
+
}
|
|
852
|
+
return resolveNodePackageDependency(specifier, importerPath);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function rewriteExtensionBareImports(source: string, importerPath: string): Promise<string> {
|
|
856
|
+
let rewritten = "";
|
|
857
|
+
let lastIndex = 0;
|
|
858
|
+
for (const match of source.matchAll(BARE_EXTENSION_IMPORT_SPECIFIER_REGEX)) {
|
|
859
|
+
const matchIndex = match.index;
|
|
860
|
+
if (matchIndex === undefined) continue;
|
|
861
|
+
|
|
862
|
+
const [fullMatch, prefix, specifier, suffix] = match;
|
|
863
|
+
if (!prefix || !specifier || !suffix) continue;
|
|
864
|
+
|
|
865
|
+
const resolved = await resolveExtensionBareDependency(specifier, importerPath);
|
|
866
|
+
if (!resolved) continue;
|
|
867
|
+
|
|
868
|
+
rewritten += source.slice(lastIndex, matchIndex);
|
|
869
|
+
rewritten += `${prefix}${toImportSpecifier(resolved)}${suffix}`;
|
|
870
|
+
lastIndex = matchIndex + fullMatch.length;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (lastIndex === 0) {
|
|
874
|
+
return source;
|
|
875
|
+
}
|
|
876
|
+
return `${rewritten}${source.slice(lastIndex)}`;
|
|
877
|
+
}
|
|
878
|
+
|
|
691
879
|
function escapeRegExp(value: string): string {
|
|
692
880
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
693
881
|
}
|
package/src/main.ts
CHANGED