@jwilger/pi-development-system 0.50.0 → 0.51.1
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/package.json +2 -2
- package/skills/strict-lints/SKILL.md +2 -1
- package/src/core/spawn-overrides.ts +2 -2
- package/src/core/test-runner.ts +8 -2
- package/src/review/digest.ts +7 -1
- package/src/subagents/VENDORED.md +1 -1
- package/src/subagents/index.ts +144 -60
- package/src/subagents/orch/inherited-tools.ts +3 -3
- package/src/subagents/orch/mailbox.ts +11 -7
- package/src/subagents/orch/manager.ts +7 -1
- package/src/subagents/orch/runtime.ts +38 -33
- package/src/subagents/orch/tools.ts +12 -10
- package/src/subagents/prefs/agent-import.ts +13 -13
- package/src/subagents/prefs/config.ts +85 -185
- package/src/subagents/prefs/import-discovery.ts +6 -6
- package/src/subagents/prefs/models.ts +6 -10
- package/src/subagents/prefs/settings.ts +38 -38
- package/src/subagents/types.ts +22 -19
- package/src/subagents/ui/agent-navigation-editor.ts +20 -13
- package/src/subagents/ui/dialog.ts +99 -84
- package/src/subagents/ui/import-picker.ts +28 -11
- package/src/subagents/ui/live-agent-view.ts +17 -5
- package/src/subagents/ui/model-picker.ts +26 -17
- package/src/subagents/ui/settings-ui.ts +16 -34
- package/src/subagents/ui/status-ui.ts +140 -51
- package/src/subagents/ui/thread-tree.ts +3 -9
- package/src/subagents/ui/ui.ts +162 -265
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
//
|
|
1
|
+
// biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, realpath } from "node:fs/promises";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
4
6
|
import { clampThinkingLevel } from "@earendil-works/pi-ai/compat";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
|
-
import { mkdir, readFile, realpath } from "node:fs/promises";
|
|
7
7
|
import {
|
|
8
8
|
createAgentSession,
|
|
9
9
|
createCodemodeExtension,
|
|
10
10
|
createToolSearchExtension,
|
|
11
11
|
DefaultResourceLoader,
|
|
12
|
+
type ExtensionContext,
|
|
12
13
|
getAgentDir,
|
|
13
14
|
ModelRuntime,
|
|
14
15
|
SessionManager,
|
|
15
16
|
SettingsManager,
|
|
16
|
-
type ExtensionContext,
|
|
17
17
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import {
|
|
19
|
-
BOOTSTRAP_MESSAGE,
|
|
20
|
-
buildBootstrapMessage,
|
|
21
|
-
buildQueuedUserMessage,
|
|
22
|
-
buildUpdateDetails,
|
|
23
|
-
DurableMailbox,
|
|
24
|
-
} from "./mailbox.ts";
|
|
25
|
-
import { selectTools } from "../prefs/config.ts";
|
|
26
|
-
import { TranscriptChannel } from "./transcript.ts";
|
|
27
|
-
import type { InheritedToolSource } from "./inherited-tools.ts";
|
|
28
|
-
import type { ModelSelectionMode, ToolFilteringMode } from "../prefs/settings.ts";
|
|
29
18
|
import { applySpawnOverrides, modelSource } from "../../core/spawn-overrides.ts";
|
|
19
|
+
import { selectTools } from "../prefs/config.ts";
|
|
30
20
|
import {
|
|
31
|
-
modelIdentity,
|
|
32
21
|
getModelPreferences,
|
|
33
|
-
selectPreferredModel,
|
|
34
22
|
ModelPreferenceError,
|
|
23
|
+
modelIdentity,
|
|
24
|
+
selectPreferredModel,
|
|
35
25
|
} from "../prefs/models.ts";
|
|
26
|
+
import type { ModelSelectionMode, ToolFilteringMode } from "../prefs/settings.ts";
|
|
36
27
|
import {
|
|
37
|
-
THINKING_LEVELS,
|
|
38
28
|
type AgentType,
|
|
39
|
-
type ResolvedAgentSettings,
|
|
40
29
|
type DriverFactory,
|
|
41
30
|
type DriverOptions,
|
|
31
|
+
type ResolvedAgentSettings,
|
|
32
|
+
THINKING_LEVELS,
|
|
42
33
|
type ThinkingLevel,
|
|
43
34
|
} from "../types.ts";
|
|
35
|
+
import type { InheritedToolSource } from "./inherited-tools.ts";
|
|
36
|
+
import {
|
|
37
|
+
BOOTSTRAP_MESSAGE,
|
|
38
|
+
buildBootstrapMessage,
|
|
39
|
+
buildQueuedUserMessage,
|
|
40
|
+
buildUpdateDetails,
|
|
41
|
+
DurableMailbox,
|
|
42
|
+
} from "./mailbox.ts";
|
|
43
|
+
import { TranscriptChannel } from "./transcript.ts";
|
|
44
44
|
|
|
45
45
|
const BUILTINS = ["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"];
|
|
46
46
|
/** Persisted before transcript messages so older-leaf restores still carry ownership. */
|
|
@@ -157,7 +157,7 @@ export function createDriverFactory(
|
|
|
157
157
|
type: AgentType,
|
|
158
158
|
parentPath?: string | null,
|
|
159
159
|
restored?: ReturnType<SessionManager["buildSessionContext"]>,
|
|
160
|
-
): { provider?: string; id?: string; thinkingLevel: ThinkingLevel };
|
|
160
|
+
): { provider?: string | undefined; id?: string | undefined; thinkingLevel: ThinkingLevel };
|
|
161
161
|
} {
|
|
162
162
|
// Keep resolved settings even after disposal: descendants inherit settings, not the caller's history.
|
|
163
163
|
const resolved = new Map<string, { provider: string; id: string; thinking: ThinkingLevel }>();
|
|
@@ -226,9 +226,9 @@ export function createDriverFactory(
|
|
|
226
226
|
const candidates = filtering
|
|
227
227
|
? availableScopedModels(normalizeScopedModels(ctx.scopedModels))
|
|
228
228
|
: availableModelCandidates();
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
));
|
|
229
|
+
const preferred = selectTypeModelPreference(type, candidates, filtering);
|
|
230
|
+
if (preferred === undefined) throw new Error("No preferred model is available");
|
|
231
|
+
({ provider, id } = parseModelIdentity(preferred));
|
|
232
232
|
} else if (source === "restored" && restored?.model) {
|
|
233
233
|
provider = restored.model.provider;
|
|
234
234
|
id = restored.model.modelId;
|
|
@@ -252,7 +252,7 @@ export function createDriverFactory(
|
|
|
252
252
|
const { provider, id, thinkingLevel } = resolveInitialSettings(type, parentPath);
|
|
253
253
|
const model =
|
|
254
254
|
normalizeScopedModels(ctx.scopedModels).find(
|
|
255
|
-
(
|
|
255
|
+
(scoped) => scoped.model.provider === provider && scoped.model.id === id,
|
|
256
256
|
)?.model ?? (provider && id ? ctx.modelRegistry.find(provider, id) : undefined);
|
|
257
257
|
if (!model)
|
|
258
258
|
throw new Error(
|
|
@@ -381,14 +381,15 @@ export function createDriverFactory(
|
|
|
381
381
|
modelId: string | undefined,
|
|
382
382
|
scopedModels: readonly ScopedModel[],
|
|
383
383
|
) => {
|
|
384
|
-
if (!providerId
|
|
384
|
+
if (!(providerId && modelId))
|
|
385
385
|
throw new Error(
|
|
386
386
|
`Subagent model ${providerId ?? "(unset)"}/${modelId ?? "(unset)"} is unavailable; select a physical model in the root or agent type`,
|
|
387
387
|
);
|
|
388
388
|
await mirrorRuntimeAuth(providerId);
|
|
389
389
|
const sourceModel =
|
|
390
|
-
scopedModels.find(
|
|
391
|
-
|
|
390
|
+
scopedModels.find(
|
|
391
|
+
(scoped) => scoped.model.provider === providerId && scoped.model.id === modelId,
|
|
392
|
+
)?.model ?? ctx.modelRegistry.find(providerId, modelId);
|
|
392
393
|
if (sourceModel?.api === "pi-virtual") {
|
|
393
394
|
throw new Error(
|
|
394
395
|
`Virtual model ${providerId}/${modelId} cannot be reproduced through the public registry API. Set this agent type's model to a physical provider/model-id.`,
|
|
@@ -414,13 +415,16 @@ export function createDriverFactory(
|
|
|
414
415
|
const rootActive = new Set(inherited.activeNames);
|
|
415
416
|
// Preserve deferred/codemode exposure rather than flooding model declarations with MCP tools.
|
|
416
417
|
const activeToolNames = toolNames.filter(
|
|
417
|
-
(name) =>
|
|
418
|
+
(name) =>
|
|
419
|
+
localNames.has(name) ||
|
|
420
|
+
rootActive.has(name) ||
|
|
421
|
+
(options.type.tools?.allow?.includes(name) && toolFiltering === "allowed"),
|
|
418
422
|
);
|
|
419
423
|
const customTools = [...options.tools, ...externalTools].filter(
|
|
420
424
|
(tool) => allowed.has(tool.name) && tool.name !== "codemode" && tool.name !== "tool_search",
|
|
421
425
|
);
|
|
422
426
|
const settingsManager = SettingsManager.inMemory({ cacheWarming: "off" });
|
|
423
|
-
let parkQueue = () =>
|
|
427
|
+
let parkQueue: () => void = () => undefined;
|
|
424
428
|
const pauseBoundary = () => {
|
|
425
429
|
if (!options.shouldPause()) return undefined;
|
|
426
430
|
parkQueue();
|
|
@@ -558,7 +562,7 @@ export function createDriverFactory(
|
|
|
558
562
|
throw error;
|
|
559
563
|
}
|
|
560
564
|
const streamFunction = session.agent.streamFunction;
|
|
561
|
-
session.agent.streamFunction =
|
|
565
|
+
session.agent.streamFunction = (requestModel, context, streamOptions) => {
|
|
562
566
|
assertScopedRequestAuthorized(
|
|
563
567
|
modelIdentity(requestModel),
|
|
564
568
|
normalizeScopedModels(getRootContext().scopedModels),
|
|
@@ -579,7 +583,7 @@ export function createDriverFactory(
|
|
|
579
583
|
sessionManager.appendMessage(buildBootstrapMessage());
|
|
580
584
|
emit("activity", BOOTSTRAP_MESSAGE);
|
|
581
585
|
};
|
|
582
|
-
const queueAccepted =
|
|
586
|
+
const queueAccepted = (kind: "steer" | "followUp", content: string): Promise<void> => {
|
|
583
587
|
assertCurrentModelAuthorized();
|
|
584
588
|
ensurePersistedSession();
|
|
585
589
|
const accepted = mailbox.accept(kind, content);
|
|
@@ -587,6 +591,7 @@ export function createDriverFactory(
|
|
|
587
591
|
if (kind === "followUp") session.agent.followUp(buildQueuedUserMessage(accepted));
|
|
588
592
|
else session.agent.steer(buildQueuedUserMessage(accepted));
|
|
589
593
|
mailbox.markEnqueued(accepted.id);
|
|
594
|
+
return Promise.resolve();
|
|
590
595
|
};
|
|
591
596
|
const resumePending = async () => {
|
|
592
597
|
for (const accepted of mailbox.replayablePending()) {
|
|
@@ -641,7 +646,7 @@ export function createDriverFactory(
|
|
|
641
646
|
const usage = message.usage;
|
|
642
647
|
const input = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
643
648
|
const output = usage.output;
|
|
644
|
-
if (!Number.isFinite(input)
|
|
649
|
+
if (!(Number.isFinite(input) && Number.isFinite(output))) return;
|
|
645
650
|
return { input, output, reported: input + output + usage.totalTokens > 0 };
|
|
646
651
|
};
|
|
647
652
|
|
|
@@ -650,7 +655,7 @@ export function createDriverFactory(
|
|
|
650
655
|
.find((entry) => entry.type === "custom" && entry.customType === THREAD_OWNERSHIP_TYPE);
|
|
651
656
|
const inheritedMetadata =
|
|
652
657
|
ownership?.type === "custom"
|
|
653
|
-
? (ownership.data as { inheritedCount?: unknown })
|
|
658
|
+
? (ownership.data as { inheritedCount?: unknown }).inheritedCount
|
|
654
659
|
: undefined;
|
|
655
660
|
const inheritedCount =
|
|
656
661
|
typeof inheritedMetadata === "number" &&
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// @ts-nocheck: vendored upstream pi-subagent-manager 0.14.0 compiled under looser options; see src/subagents/VENDORED.md
|
|
2
|
-
import { Type } from "typebox";
|
|
3
1
|
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import
|
|
2
|
+
import { Type } from "typebox";
|
|
5
3
|
import { ModelPreferenceError } from "../prefs/models.ts";
|
|
6
4
|
import type { AgentType, ResolvedAgentSettings, ThreadView } from "../types.ts";
|
|
5
|
+
import type { ThreadManager } from "./manager.ts";
|
|
7
6
|
|
|
8
7
|
const path = Type.String({ description: "Absolute agent path, or name relative to the caller" });
|
|
9
8
|
const text = Type.String({ minLength: 1 });
|
|
@@ -30,8 +29,10 @@ export function agentTools(
|
|
|
30
29
|
const make = (
|
|
31
30
|
name: string,
|
|
32
31
|
description: string,
|
|
32
|
+
// biome-ignore-start lint/suspicious/noExplicitAny: the host validates params against the TypeBox schema before execute runs, and each handler reads only fields that schema declares
|
|
33
33
|
parameters: any,
|
|
34
34
|
execute: (params: any, signal?: AbortSignal) => Promise<any> | any,
|
|
35
|
+
// biome-ignore-end lint/suspicious/noExplicitAny: end of the schema-validated boundary
|
|
35
36
|
): ToolDefinition => ({
|
|
36
37
|
name,
|
|
37
38
|
label: name,
|
|
@@ -53,12 +54,7 @@ export function agentTools(
|
|
|
53
54
|
return {
|
|
54
55
|
name,
|
|
55
56
|
description,
|
|
56
|
-
error:
|
|
57
|
-
error instanceof ModelPreferenceError
|
|
58
|
-
? error.summary
|
|
59
|
-
: error instanceof Error
|
|
60
|
-
? error.message
|
|
61
|
-
: String(error),
|
|
57
|
+
error: describeError(error),
|
|
62
58
|
};
|
|
63
59
|
}
|
|
64
60
|
});
|
|
@@ -99,7 +95,8 @@ export function agentTools(
|
|
|
99
95
|
),
|
|
100
96
|
thinkingLevel: Type.Optional(
|
|
101
97
|
Type.String({
|
|
102
|
-
description:
|
|
98
|
+
description:
|
|
99
|
+
"Pin this child's thinking level: off, minimal, low, medium, high, xhigh or max.",
|
|
103
100
|
}),
|
|
104
101
|
),
|
|
105
102
|
}),
|
|
@@ -175,3 +172,8 @@ export function agentTools(
|
|
|
175
172
|
),
|
|
176
173
|
];
|
|
177
174
|
}
|
|
175
|
+
|
|
176
|
+
function describeError(error: unknown): string {
|
|
177
|
+
if (error instanceof ModelPreferenceError) return error.summary;
|
|
178
|
+
return error instanceof Error ? error.message : String(error);
|
|
179
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import { constants, lstatSync, mkdirSync, openSync, closeSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { closeSync, constants, lstatSync, mkdirSync, openSync, writeFileSync } from "node:fs";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { dialogText } from "../ui/dialog.ts";
|
|
6
|
+
import { selectImportAgents } from "../ui/import-picker.ts";
|
|
6
7
|
import { AGENT_COLORS, type ConfigStore } from "./config.ts";
|
|
7
8
|
import { discoverImportCandidates, type ImportCandidate } from "./import-discovery.ts";
|
|
8
|
-
import { selectImportAgents } from "../ui/import-picker.ts";
|
|
9
9
|
import { MIGRATION_INSTRUCTIONS } from "./import-instructions.ts";
|
|
10
|
-
import { dialogText } from "../ui/dialog.ts";
|
|
11
10
|
|
|
12
11
|
export const IMPORT_REQUEST_PREFIX =
|
|
13
12
|
"Import ONLY the agents I selected in the pi-subagent-manager migration picker.";
|
|
@@ -70,7 +69,8 @@ function importWasOffered(agentDir: string): boolean {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
export function markImportOffered(agentDir: string): void {
|
|
73
|
-
const directory = stateDirectory(agentDir, true)
|
|
72
|
+
const directory = stateDirectory(agentDir, true);
|
|
73
|
+
if (directory === undefined) throw new Error("Import-state directory could not be created");
|
|
74
74
|
const file = join(directory, OFFER_MARKER);
|
|
75
75
|
const stat = statIfPresent(file);
|
|
76
76
|
if (stat) {
|
|
@@ -94,11 +94,11 @@ function buildImportPrompt(options: {
|
|
|
94
94
|
agentDir: string;
|
|
95
95
|
cwd: string;
|
|
96
96
|
includeProject: boolean;
|
|
97
|
-
existingTypes: { name: string; source?: string; filePath?: string }[];
|
|
97
|
+
existingTypes: { name: string; source?: string | undefined; filePath?: string | undefined }[];
|
|
98
98
|
scopedModels: readonly string[];
|
|
99
|
-
scopedModelDetails?: readonly { identity: string; api: string; virtual: boolean }[];
|
|
100
|
-
parentModel?: string;
|
|
101
|
-
parentModelApi?: string;
|
|
99
|
+
scopedModelDetails?: readonly { identity: string; api: string; virtual: boolean }[] | undefined;
|
|
100
|
+
parentModel?: string | undefined;
|
|
101
|
+
parentModelApi?: string | undefined;
|
|
102
102
|
}): string {
|
|
103
103
|
return [
|
|
104
104
|
IMPORT_REQUEST_PREFIX,
|
|
@@ -162,7 +162,7 @@ export async function offerAgentImport(
|
|
|
162
162
|
homeDir: options.homeDir,
|
|
163
163
|
extraAgentDirs: options.extraAgentDirs,
|
|
164
164
|
});
|
|
165
|
-
if (found.diagnostics.length)
|
|
165
|
+
if (found.diagnostics.length > 0)
|
|
166
166
|
ctx.ui.notify(dialogText(found.diagnostics.join("\n")), "warning");
|
|
167
167
|
const remember = () => {
|
|
168
168
|
try {
|
|
@@ -174,9 +174,9 @@ export async function offerAgentImport(
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
};
|
|
177
|
-
if (
|
|
177
|
+
if (found.candidates.length === 0) {
|
|
178
178
|
// Retry an incomplete scan next startup, rather than permanently hiding unreadable definitions.
|
|
179
|
-
if (
|
|
179
|
+
if (found.diagnostics.length === 0) remember();
|
|
180
180
|
if (!firstRun)
|
|
181
181
|
ctx.ui.notify(
|
|
182
182
|
"No external agent definitions found. Package-provided defaults are not scanned.",
|
|
@@ -208,7 +208,7 @@ export async function offerAgentImport(
|
|
|
208
208
|
}
|
|
209
209
|
const selected = new Set(ids);
|
|
210
210
|
const candidates = found.candidates.filter((candidate) => selected.has(candidate.id));
|
|
211
|
-
if (
|
|
211
|
+
if (candidates.length === 0) return false;
|
|
212
212
|
options.store.reload();
|
|
213
213
|
const prompt = buildImportPrompt({
|
|
214
214
|
candidates,
|