pi-orchestrator-config 4.2.1 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +52 -61
  2. package/agents/git-expert.md +5 -1
  3. package/agents/github-expert.md +4 -1
  4. package/extensions/acpx-provider/runtime-models.ts +18 -6
  5. package/extensions/cli-provider/agents/claude.ts +3 -2
  6. package/extensions/cli-provider/agents/cursor.ts +3 -5
  7. package/extensions/cli-provider/agents/gemini.ts +3 -2
  8. package/extensions/cli-provider/discover.ts +7 -7
  9. package/extensions/cli-provider/index.ts +5 -0
  10. package/extensions/cli-provider/runtime-models.ts +18 -6
  11. package/extensions/cli-provider/session-reaper.ts +5 -7
  12. package/extensions/cli-provider/sessions.ts +4 -0
  13. package/extensions/cli-provider/shared/discover-cache.ts +17 -176
  14. package/extensions/coms/coms-p2p.ts +1719 -423
  15. package/extensions/coms/coms-shared.ts +261 -39
  16. package/extensions/coms/coms-wrapper.ts +100 -62
  17. package/extensions/coms/index.ts +8 -4
  18. package/extensions/coms/mixed-turn.ts +26 -0
  19. package/extensions/coms/probe-socket.ts +42 -0
  20. package/extensions/coms/themeMap.ts +0 -1
  21. package/extensions/image-gen/image-gen.ts +19 -1
  22. package/extensions/orchestrator/async-agents.ts +509 -85
  23. package/extensions/orchestrator/cron.ts +14 -8
  24. package/extensions/orchestrator/dreaming.ts +31 -24
  25. package/extensions/orchestrator/enforcement-helpers.ts +459 -17
  26. package/extensions/orchestrator/enforcement-rules.ts +5 -4
  27. package/extensions/orchestrator/enforcement.ts +79 -25
  28. package/extensions/orchestrator/extended-autocomplete.ts +7 -0
  29. package/extensions/orchestrator/git-helpers.ts +2 -2
  30. package/extensions/orchestrator/index.ts +23 -10
  31. package/extensions/orchestrator/markdown-transformer.ts +313 -0
  32. package/extensions/orchestrator/memory-embeddings.ts +36 -34
  33. package/extensions/orchestrator/memory-provenance.ts +29 -25
  34. package/extensions/orchestrator/memory-scoring.ts +23 -18
  35. package/extensions/orchestrator/memory-tools.ts +10 -3
  36. package/extensions/orchestrator/overlay-dashboard.ts +17 -5
  37. package/extensions/orchestrator/pi-config-review-state.ts +44 -33
  38. package/extensions/orchestrator/project-settings.ts +220 -21
  39. package/extensions/orchestrator/review-porcelain.ts +4 -0
  40. package/extensions/orchestrator/review-ui.ts +11 -5
  41. package/extensions/orchestrator/rule-placeholders.ts +384 -1
  42. package/extensions/orchestrator/rules.ts +80 -132
  43. package/extensions/orchestrator/settings-tui-helpers.ts +380 -0
  44. package/extensions/orchestrator/settings-tui-submenus.ts +647 -0
  45. package/extensions/orchestrator/settings-tui.ts +636 -0
  46. package/extensions/orchestrator/state-jsonl.ts +431 -0
  47. package/extensions/orchestrator/subagent-tool.ts +105 -50
  48. package/extensions/orchestrator/sync-limit.ts +23 -0
  49. package/extensions/orchestrator/task-lifecycle.ts +77 -67
  50. package/extensions/orchestrator/utils.ts +8 -5
  51. package/extensions/pidash/pidash.ts +193 -156
  52. package/extensions/pidiff/pidiff.ts +64 -38
  53. package/extensions/pidiff/spawn-lock.ts +62 -0
  54. package/extensions/pitasks/index.ts +430 -0
  55. package/extensions/pitasks/reminders.ts +37 -0
  56. package/extensions/pitasks/task-store.ts +467 -0
  57. package/extensions/pitasks/task-tools.ts +375 -0
  58. package/extensions/pitasks/task-widget.ts +181 -0
  59. package/extensions/pitasks/types.ts +38 -0
  60. package/extensions/providers/acpx-driver.ts +12 -2
  61. package/extensions/providers/built-in-drivers.ts +6 -6
  62. package/extensions/providers/claude-driver.ts +1 -1
  63. package/extensions/providers/cursor-cli-driver.ts +1 -1
  64. package/extensions/providers/gemini-driver.ts +1 -1
  65. package/extensions/providers/index.ts +144 -26
  66. package/extensions/providers/initialized-guard.ts +27 -0
  67. package/extensions/providers/restore-default-model.ts +596 -0
  68. package/extensions/providers/session-shutdown.ts +39 -0
  69. package/extensions/shared/coms-active.ts +23 -0
  70. package/extensions/shared/create-runtime-provider.ts +2 -0
  71. package/extensions/shared/daemon-manager.ts +0 -8
  72. package/extensions/shared/file-logger.ts +132 -25
  73. package/extensions/shared/logger.ts +50 -0
  74. package/extensions/shared/models-dev.ts +421 -0
  75. package/extensions/shared/oneshot.ts +116 -0
  76. package/extensions/shared/resolve-binary.ts +191 -0
  77. package/extensions/shared/types.ts +3 -0
  78. package/package.json +7 -2
  79. package/prompts/coderabbit-rate-limit.md +2 -0
  80. package/rules/00-orchestrator-core.md +3 -0
  81. package/rules/10-agent-routing.md +1 -1
  82. package/rules/20-code-review-loop.md +38 -16
  83. package/rules/30-prompt-templates.md +4 -0
  84. package/rules/35-memory.md +3 -3
  85. package/rules/40-critical-rules.md +3 -0
  86. package/rules/55-coms-protocol.md +84 -18
  87. package/rules/60-task-tracking.md +2 -0
  88. package/settings-keys.json +481 -20
  89. package/templates/coms-feature-manager-prompt.md +4 -6
  90. package/extensions/coms/coms-net-server.ts +0 -1642
  91. package/extensions/coms/coms-net-wrapper.ts +0 -521
  92. package/extensions/coms/coms-net.ts +0 -1589
@@ -26,8 +26,10 @@ import type {
26
26
  SimpleStreamOptions,
27
27
  StreamOptions,
28
28
  } from "@earendil-works/pi-ai";
29
+ import { createLogger } from "../shared/logger.js";
29
30
  import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
30
31
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
32
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
31
33
  import { asStringArray, getSetting } from "../orchestrator/project-settings.js";
32
34
  import { buildExternalSystemPrompt } from "../shared/build-system-prompt.js";
33
35
  import {
@@ -36,10 +38,14 @@ import {
36
38
  } from "../orchestrator/utils.js";
37
39
  import {
38
40
  buildAmbientLoginAuth,
39
- buildRuntimeModel,
40
41
  createRuntimeProvider,
41
42
  filterModelsWhenConfigured,
42
43
  } from "../shared/create-runtime-provider.js";
44
+ import {
45
+ applyThinkingLevelFromModel,
46
+ loadModelsDevCatalog,
47
+ type ModelsDevCatalog,
48
+ } from "../shared/models-dev.js";
43
49
  import { StreamAssembler, createAssistantMessageOutput } from "../shared/stream-builder.js";
44
50
  import { ProviderDriverRegistry } from "../shared/provider-registry.js";
45
51
  import type { ProviderInstance, DiscoveredModel } from "../shared/provider-driver.js";
@@ -48,13 +54,18 @@ import { mapCliDiscoveredModels } from "../cli-provider/runtime-models.js";
48
54
  import { mapAcpxDiscoveredModels } from "../acpx-provider/runtime-models.js";
49
55
  import {
50
56
  startCliSessionReaper,
51
- stopCliSessionReaper,
52
57
  } from "../cli-provider/session-reaper.js";
53
58
  import {
54
59
  readPiSessionIdFromManager,
55
60
  } from "../cli-provider/sessions.js";
56
61
  import { fileLog } from "../shared/file-logger.js";
57
62
  import { isCliAgentName } from "../cli-provider/providers.js";
63
+ import {
64
+ isProvidersInitialized,
65
+ markProvidersInitialized,
66
+ } from "./initialized-guard.js";
67
+ import { restoreDefaultModelOnSessionStart } from "./restore-default-model.js";
68
+ import { teardownProvidersOnSessionShutdown } from "./session-shutdown.js";
58
69
 
59
70
  const LOG_DOMAIN = "providers";
60
71
  const DISCOVERY_TIMEOUT_MS = 30_000;
@@ -70,7 +81,6 @@ const cliInstances = new Map<string, ProviderInstance>();
70
81
  const acpxInstances = new Map<string, ProviderInstance>();
71
82
 
72
83
  let projectCwd = "";
73
- let initialized = false;
74
84
 
75
85
  // ---------------------------------------------------------------------------
76
86
  // Configured Gates
@@ -185,12 +195,13 @@ async function registerCliAgent(
185
195
  pi: ExtensionAPI,
186
196
  agent: string,
187
197
  instance: ProviderInstance,
198
+ catalog: ModelsDevCatalog | null,
188
199
  ): Promise<void> {
189
200
  const providerId = `cli-${agent}`;
190
201
  const snapshot = instance.snapshot.getSnapshot();
191
202
  const discovered = snapshot.models;
192
203
 
193
- const models = mapCliDiscoveredModels(agent, discovered);
204
+ const models = mapCliDiscoveredModels(agent, discovered, catalog);
194
205
  const streamFn = makeStreamFunction("cli", agent, () => cliInstances.get(agent));
195
206
 
196
207
  const provider = await createRuntimeProvider({
@@ -209,7 +220,8 @@ async function registerCliAgent(
209
220
  const inst = cliInstances.get(agent);
210
221
  if (!inst) return [];
211
222
  const snap = await inst.snapshot.refresh();
212
- return mapCliDiscoveredModels(agent, snap.models);
223
+ const fresh = await loadModelsDevCatalog();
224
+ return mapCliDiscoveredModels(agent, snap.models, fresh);
213
225
  },
214
226
  filterModels: (catalog, credential) =>
215
227
  filterModelsWhenConfigured(catalog, credential, () =>
@@ -226,12 +238,13 @@ async function registerAcpxAgent(
226
238
  pi: ExtensionAPI,
227
239
  agent: string,
228
240
  instance: ProviderInstance,
241
+ catalog: ModelsDevCatalog | null,
229
242
  ): Promise<void> {
230
243
  const providerId = `acpx-${agent}`;
231
244
  const snapshot = instance.snapshot.getSnapshot();
232
245
  const modelIds = snapshot.models.map((m) => m.id);
233
246
 
234
- const models = mapAcpxDiscoveredModels(agent, modelIds);
247
+ const models = mapAcpxDiscoveredModels(agent, modelIds, catalog);
235
248
  const streamFn = makeStreamFunction("acpx", agent, () => acpxInstances.get(agent));
236
249
 
237
250
  const provider = await createRuntimeProvider({
@@ -249,7 +262,8 @@ async function registerAcpxAgent(
249
262
  const inst = acpxInstances.get(agent);
250
263
  if (!inst) return [];
251
264
  const snap = await inst.snapshot.refresh();
252
- return mapAcpxDiscoveredModels(agent, snap.models.map((m) => m.id));
265
+ const fresh = await loadModelsDevCatalog();
266
+ return mapAcpxDiscoveredModels(agent, snap.models.map((m) => m.id), fresh);
253
267
  },
254
268
  filterModels: (catalog, credential) =>
255
269
  filterModelsWhenConfigured(catalog, credential, () =>
@@ -266,15 +280,27 @@ async function registerAcpxAgent(
266
280
  // Extension Entry Point
267
281
  // ---------------------------------------------------------------------------
268
282
 
283
+ const log = createLogger("providers");
284
+ log.debug("providers module loaded");
285
+
269
286
  export default async function (pi: ExtensionAPI) {
270
287
  // pi --help / --version — skip discovery
271
288
  if (isPiMetaInvocation()) return;
272
289
 
273
290
  // Idempotency guard — shims re-export this default, so it may be called
274
291
  // multiple times (cli-provider shim, acpx-provider shim, providers dir).
275
- // Only run once per process.
276
- if (initialized) return;
277
- initialized = true;
292
+ // Only run once per session; session_shutdown resets so /new|/resume|/fork
293
+ // re-registers providers on the next factory invocation.
294
+ if (isProvidersInitialized()) {
295
+ log.debug(
296
+ "providers factory early-return: already initialized (same-session double-load)",
297
+ );
298
+ return;
299
+ }
300
+ markProvidersInitialized();
301
+ log.debug(
302
+ "providers factory: proceeding after reset / first load — re-entering discovery",
303
+ );
278
304
 
279
305
  const versionCheck = checkMinPiVersion();
280
306
  if (!versionCheck.ok && versionCheck.installed !== null) {
@@ -295,6 +321,14 @@ export default async function (pi: ExtensionAPI) {
295
321
  // CLI agents (load in subagent children too)
296
322
  // ---------------------------------------------------------------------------
297
323
  const cliAgentList = asStringArray(getSetting(projectCwd, "cli_agents")).filter(isCliAgentName);
324
+ const acpxAgentList = asStringArray(getSetting(projectCwd, "acpx_agents"));
325
+ const modelsDevCatalog =
326
+ cliAgentList.length > 0 || acpxAgentList.length > 0
327
+ ? await loadModelsDevCatalog()
328
+ : null;
329
+ if (modelsDevCatalog) {
330
+ log.info("models.dev catalog loaded for cli/acpx model fill");
331
+ }
298
332
 
299
333
  if (cliAgentList.length > 0) {
300
334
  startCliSessionReaper({
@@ -339,8 +373,8 @@ export default async function (pi: ExtensionAPI) {
339
373
 
340
374
  const cliResults = await Promise.allSettled(
341
375
  cliAgentList.map(async (agent) => {
342
- const driverKind = CLI_AGENT_TO_DRIVER[agent];
343
- if (!driverKind) {
376
+ const driverKind = Object.hasOwn(CLI_AGENT_TO_DRIVER, agent) ? CLI_AGENT_TO_DRIVER[agent] : undefined;
377
+ if (typeof driverKind !== "string" || driverKind.length === 0) {
344
378
  fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
345
379
  `cli-${agent}: no driver registered`);
346
380
  return null;
@@ -397,7 +431,7 @@ export default async function (pi: ExtensionAPI) {
397
431
  if (result.status !== "fulfilled" || !result.value) continue;
398
432
  const { agent, instance } = result.value;
399
433
  try {
400
- await registerCliAgent(pi, agent, instance);
434
+ await registerCliAgent(pi, agent, instance, modelsDevCatalog);
401
435
  } catch (err) {
402
436
  fileLog(LOG_DOMAIN, "error", LOG_DOMAIN,
403
437
  `cli-${agent}: registration failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -412,7 +446,12 @@ export default async function (pi: ExtensionAPI) {
412
446
 
413
447
  const acpxResults = await Promise.allSettled(
414
448
  acpxAgentList.map(async (agent) => {
415
- const driverKind = ACPX_AGENT_TO_DRIVER[agent] || "acpx";
449
+ const driverKind = Object.hasOwn(ACPX_AGENT_TO_DRIVER, agent) ? ACPX_AGENT_TO_DRIVER[agent] : undefined;
450
+ if (typeof driverKind !== "string" || driverKind.length === 0) {
451
+ fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
452
+ `acpx-${agent}: no driver mapping in ACPX_AGENT_TO_DRIVER`);
453
+ return null;
454
+ }
416
455
  // Only proceed if the driver is registered
417
456
  if (!registry.getDriver(driverKind)) {
418
457
  fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
@@ -437,7 +476,11 @@ export default async function (pi: ExtensionAPI) {
437
476
  { driver: driverKind, config: { agent }, enabled: true },
438
477
  projectCwd,
439
478
  );
440
- if (timedOut) return { agent, instance };
479
+ if (timedOut) {
480
+ fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
481
+ `acpx-${agent}: completed after timeout — cleaning up`);
482
+ return { agent, instance };
483
+ }
441
484
  acpxInstances.set(agent, instance);
442
485
  return { agent, instance };
443
486
  } catch (err) {
@@ -471,7 +514,7 @@ export default async function (pi: ExtensionAPI) {
471
514
  if (result.status !== "fulfilled" || !result.value) continue;
472
515
  const { agent, instance } = result.value;
473
516
  try {
474
- await registerAcpxAgent(pi, agent, instance);
517
+ await registerAcpxAgent(pi, agent, instance, modelsDevCatalog);
475
518
  } catch (err) {
476
519
  fileLog(LOG_DOMAIN, "error", LOG_DOMAIN,
477
520
  `acpx-${agent}: registration failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -482,7 +525,8 @@ export default async function (pi: ExtensionAPI) {
482
525
  // Run CLI and ACPX discovery concurrently
483
526
  await Promise.all([discoverAndRegisterCli(), discoverAndRegisterAcpx()]);
484
527
 
485
- // Show discovery summary on session start
528
+ // Show discovery summary on session start; restore saved default model when
529
+ // findInitialModel raced hasConfiguredAuth false → wrong initial model (#753).
486
530
  const providerSummaryParts: string[] = [];
487
531
  for (const [agent, inst] of cliInstances) {
488
532
  const count = inst.snapshot.getSnapshot().models.length;
@@ -492,22 +536,96 @@ export default async function (pi: ExtensionAPI) {
492
536
  const count = inst.snapshot.getSnapshot().models.length;
493
537
  providerSummaryParts.push(`acpx-${agent} (${count})`);
494
538
  }
495
- if (providerSummaryParts.length > 0) {
496
- pi.on("session_start", (_event, ctx) => {
497
- if (!ctx.hasUI) return;
539
+
540
+ // Fire-and-forget restore so session_start is not blocked by retries (#753).
541
+ // Omit registeredProviders: cli/acpx-only lists falsely fail-fast native defaults.
542
+ const applyCliAcpxThinking = (model: { id: string; provider: string } | undefined) => {
543
+ log.debug("applyCliAcpxThinking", model?.provider, model?.id);
544
+ try {
545
+ applyThinkingLevelFromModel(
546
+ model,
547
+ (level) => pi.setThinkingLevel(level as ThinkingLevel),
548
+ () => pi.getThinkingLevel(),
549
+ );
550
+ } catch (err) {
551
+ log.warn(
552
+ "thinking from id failed",
553
+ err instanceof Error ? err.message : String(err),
554
+ );
555
+ }
556
+ };
557
+
558
+ pi.on("session_start", (event, ctx) => {
559
+ const reason = typeof event?.reason === "string" ? event.reason : "";
560
+ if (providerSummaryParts.length > 0 && ctx.hasUI) {
498
561
  try {
499
562
  ctx.ui.notify(`Providers: ${providerSummaryParts.join(", ")}`, "info");
500
- } catch { /* stale ctx */ }
563
+ } catch (err) {
564
+ log.debug(
565
+ "providers notify skipped",
566
+ err instanceof Error ? err.message : String(err),
567
+ );
568
+ }
569
+ }
570
+ applyCliAcpxThinking(
571
+ ctx.model
572
+ ? { id: ctx.model.id, provider: String(ctx.model.provider) }
573
+ : undefined,
574
+ );
575
+ // agentDir via PI_CODING_AGENT_DIR / ~/.pi/agent only (ctx has cwd, not agentDir).
576
+ // Merge global + cwd/.pi/settings.json (project wins) only when trusted —
577
+ // same gate as pi SettingsManager (ctx.isProjectTrusted). Fail closed.
578
+ const cwd = typeof ctx.cwd === "string" ? ctx.cwd : undefined;
579
+ let projectTrusted = false;
580
+ try {
581
+ projectTrusted = typeof ctx.isProjectTrusted === "function"
582
+ ? ctx.isProjectTrusted() === true
583
+ : false;
584
+ } catch (err) {
585
+ log.debug(
586
+ "restore-default-model isProjectTrusted failed",
587
+ err instanceof Error ? err.message : String(err),
588
+ );
589
+ projectTrusted = false;
590
+ }
591
+ void restoreDefaultModelOnSessionStart({
592
+ ctx: {
593
+ model: ctx.model
594
+ ? { id: ctx.model.id, provider: String(ctx.model.provider) }
595
+ : undefined,
596
+ modelRegistry: ctx.modelRegistry,
597
+ },
598
+ reason,
599
+ cwd,
600
+ projectTrusted,
601
+ getCurrentModel: () =>
602
+ ctx.model
603
+ ? { id: ctx.model.id, provider: String(ctx.model.provider) }
604
+ : undefined,
605
+ setModel: (model) => pi.setModel(model as Model<any>),
606
+ }).catch((err) => {
607
+ log.warn(
608
+ "restore-default-model session_start error",
609
+ err instanceof Error ? err.message : String(err),
610
+ );
501
611
  });
502
- }
612
+ });
613
+
614
+ pi.on("model_select", (event) => {
615
+ applyCliAcpxThinking({
616
+ id: event.model.id,
617
+ provider: String(event.model.provider),
618
+ });
619
+ });
503
620
 
504
621
  // ---------------------------------------------------------------------------
505
622
  // Shutdown
506
623
  // ---------------------------------------------------------------------------
507
624
  pi.on("session_shutdown", async () => {
508
- stopCliSessionReaper();
509
- await registry.teardownAll();
510
- cliInstances.clear();
511
- acpxInstances.clear();
625
+ await teardownProvidersOnSessionShutdown(
626
+ registry,
627
+ cliInstances,
628
+ acpxInstances,
629
+ );
512
630
  });
513
631
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Session-scoped init guard for the unified providers extension.
3
+ *
4
+ * Shims (cli-provider / acpx-provider) and providers/ may call the same
5
+ * default factory multiple times in one process — early-return while set.
6
+ * session_shutdown must clear this so /new|/resume|/fork re-registers (#752).
7
+ */
8
+
9
+ import { createLogger } from "../shared/logger.js";
10
+
11
+ const log = createLogger("providers");
12
+
13
+ let initialized = false;
14
+
15
+ export function isProvidersInitialized(): boolean {
16
+ return initialized;
17
+ }
18
+
19
+ export function markProvidersInitialized(): void {
20
+ initialized = true;
21
+ log.debug("initialized-guard", "markProvidersInitialized → true");
22
+ }
23
+
24
+ export function resetProvidersInitialized(): void {
25
+ initialized = false;
26
+ log.debug("initialized-guard", "resetProvidersInitialized → false");
27
+ }