@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.
@@ -1,46 +1,46 @@
1
- // @ts-nocheck: vendored upstream pi-subagent-manager 0.14.0 compiled under looser options; see src/subagents/VENDORED.md
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
- ({ provider, id } = parseModelIdentity(
230
- selectTypeModelPreference(type, candidates, filtering)!,
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
- ({ model }) => model.provider === provider && model.id === id,
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 || !modelId)
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(({ model }) => model.provider === providerId && model.id === modelId)
391
- ?.model ?? ctx.modelRegistry.find(providerId, modelId);
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) => localNames.has(name) || rootActive.has(name) || options.type.tools?.allow?.includes(name) && toolFiltering === "allowed",
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 = async (requestModel, context, streamOptions) => {
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 = async (kind: "steer" | "followUp", content: string) => {
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) || !Number.isFinite(output)) return;
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 })?.inheritedCount
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 type { ThreadManager } from "./manager.ts";
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: "Pin this child's thinking level: off, minimal, low, medium, high, xhigh or max.",
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
- // @ts-nocheck: vendored upstream pi-subagent-manager 0.14.0 compiled under looser options; see src/subagents/VENDORED.md
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 (!found.candidates.length) {
177
+ if (found.candidates.length === 0) {
178
178
  // Retry an incomplete scan next startup, rather than permanently hiding unreadable definitions.
179
- if (!found.diagnostics.length) remember();
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 (!candidates.length) return false;
211
+ if (candidates.length === 0) return false;
212
212
  options.store.reload();
213
213
  const prompt = buildImportPrompt({
214
214
  candidates,