pi-orchestrator-config 4.2.0 → 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 (100) hide show
  1. package/README.md +76 -67
  2. package/agents/code-reviewer-docs.md +11 -1
  3. package/agents/code-reviewer-guidelines.md +11 -1
  4. package/agents/code-reviewer-quality.md +11 -1
  5. package/agents/code-reviewer-security.md +11 -1
  6. package/agents/code-reviewer-spec.md +11 -1
  7. package/agents/git-expert.md +42 -3
  8. package/agents/github-expert.md +11 -0
  9. package/extensions/acpx-provider/runtime-models.ts +18 -6
  10. package/extensions/cli-provider/agents/claude.ts +3 -2
  11. package/extensions/cli-provider/agents/cursor.ts +3 -5
  12. package/extensions/cli-provider/agents/gemini.ts +3 -2
  13. package/extensions/cli-provider/discover.ts +7 -7
  14. package/extensions/cli-provider/index.ts +5 -0
  15. package/extensions/cli-provider/runtime-models.ts +18 -6
  16. package/extensions/cli-provider/session-reaper.ts +5 -7
  17. package/extensions/cli-provider/sessions.ts +4 -0
  18. package/extensions/cli-provider/shared/discover-cache.ts +17 -176
  19. package/extensions/coms/coms-p2p.ts +1719 -423
  20. package/extensions/coms/coms-shared.ts +261 -39
  21. package/extensions/coms/coms-wrapper.ts +100 -62
  22. package/extensions/coms/index.ts +8 -4
  23. package/extensions/coms/mixed-turn.ts +26 -0
  24. package/extensions/coms/probe-socket.ts +42 -0
  25. package/extensions/coms/themeMap.ts +0 -1
  26. package/extensions/image-gen/image-gen.ts +19 -1
  27. package/extensions/orchestrator/async-agents.ts +563 -102
  28. package/extensions/orchestrator/async-capability.ts +8 -8
  29. package/extensions/orchestrator/cron.ts +15 -9
  30. package/extensions/orchestrator/dreaming.ts +32 -25
  31. package/extensions/orchestrator/enforcement-helpers.ts +459 -17
  32. package/extensions/orchestrator/enforcement-rules.ts +5 -4
  33. package/extensions/orchestrator/enforcement.ts +80 -27
  34. package/extensions/orchestrator/extended-autocomplete.ts +7 -0
  35. package/extensions/orchestrator/git-helpers.ts +2 -2
  36. package/extensions/orchestrator/index.ts +78 -10
  37. package/extensions/orchestrator/markdown-transformer.ts +313 -0
  38. package/extensions/orchestrator/memory-embeddings.ts +36 -34
  39. package/extensions/orchestrator/memory-provenance.ts +29 -25
  40. package/extensions/orchestrator/memory-scoring.ts +23 -18
  41. package/extensions/orchestrator/memory-tools.ts +10 -3
  42. package/extensions/orchestrator/overlay-dashboard.ts +17 -5
  43. package/extensions/orchestrator/parse-model-override.ts +29 -0
  44. package/extensions/orchestrator/pi-config-review-state.ts +57 -33
  45. package/extensions/orchestrator/project-settings.ts +333 -127
  46. package/extensions/orchestrator/resolve-agent-model.ts +24 -13
  47. package/extensions/orchestrator/review-porcelain.ts +4 -0
  48. package/extensions/orchestrator/review-ui.ts +63 -15
  49. package/extensions/orchestrator/rule-placeholders.ts +411 -2
  50. package/extensions/orchestrator/rules.ts +80 -132
  51. package/extensions/orchestrator/settings-tui-helpers.ts +380 -0
  52. package/extensions/orchestrator/settings-tui-submenus.ts +647 -0
  53. package/extensions/orchestrator/settings-tui.ts +636 -0
  54. package/extensions/orchestrator/state-jsonl.ts +431 -0
  55. package/extensions/orchestrator/subagent-tool.ts +157 -59
  56. package/extensions/orchestrator/sync-limit.ts +23 -0
  57. package/extensions/orchestrator/task-lifecycle.ts +77 -67
  58. package/extensions/orchestrator/utils.ts +8 -5
  59. package/extensions/pidash/pidash.ts +193 -156
  60. package/extensions/pidiff/pidiff.ts +96 -38
  61. package/extensions/pidiff/spawn-lock.ts +62 -0
  62. package/extensions/pitasks/index.ts +430 -0
  63. package/extensions/pitasks/reminders.ts +37 -0
  64. package/extensions/pitasks/task-store.ts +467 -0
  65. package/extensions/pitasks/task-tools.ts +375 -0
  66. package/extensions/pitasks/task-widget.ts +181 -0
  67. package/extensions/pitasks/types.ts +38 -0
  68. package/extensions/providers/acpx-driver.ts +12 -2
  69. package/extensions/providers/built-in-drivers.ts +6 -6
  70. package/extensions/providers/claude-driver.ts +1 -1
  71. package/extensions/providers/cursor-cli-driver.ts +1 -1
  72. package/extensions/providers/gemini-driver.ts +1 -1
  73. package/extensions/providers/index.ts +144 -26
  74. package/extensions/providers/initialized-guard.ts +27 -0
  75. package/extensions/providers/restore-default-model.ts +596 -0
  76. package/extensions/providers/session-shutdown.ts +39 -0
  77. package/extensions/shared/coms-active.ts +23 -0
  78. package/extensions/shared/create-runtime-provider.ts +2 -0
  79. package/extensions/shared/daemon-manager.ts +0 -8
  80. package/extensions/shared/file-logger.ts +132 -25
  81. package/extensions/shared/logger.ts +50 -0
  82. package/extensions/shared/models-dev.ts +421 -0
  83. package/extensions/shared/oneshot.ts +116 -0
  84. package/extensions/shared/resolve-binary.ts +191 -0
  85. package/extensions/shared/types.ts +3 -0
  86. package/package.json +9 -3
  87. package/prompts/coderabbit-rate-limit.md +2 -0
  88. package/rules/00-orchestrator-core.md +3 -0
  89. package/rules/10-agent-routing.md +12 -1
  90. package/rules/20-code-review-loop.md +52 -25
  91. package/rules/30-prompt-templates.md +4 -0
  92. package/rules/35-memory.md +3 -3
  93. package/rules/40-critical-rules.md +3 -0
  94. package/rules/55-coms-protocol.md +84 -18
  95. package/rules/60-task-tracking.md +2 -0
  96. package/settings-keys.json +563 -0
  97. package/templates/coms-feature-manager-prompt.md +4 -6
  98. package/extensions/coms/coms-net-server.ts +0 -1642
  99. package/extensions/coms/coms-net-wrapper.ts +0 -521
  100. package/extensions/coms/coms-net.ts +0 -1589
@@ -1,12 +1,14 @@
1
1
  /**
2
- * Project-level settings — loads .pi/pi-config-settings.json with env var fallback.
2
+ * Project-level settings — loads .pi/pi-config-settings.jsonc (or .json) with env var fallback.
3
3
  *
4
4
  * Resolution order:
5
- * 1. Project .pi/pi-config-settings.json (wins if set)
6
- * 2. Global ~/.pi/pi-config-settings.json (fallback for all projects)
5
+ * 1. Project .pi/pi-config-settings.jsonc or .json (wins if set, .jsonc preferred)
6
+ * 2. Global ~/.pi/pi-config-settings.jsonc or .json (fallback for all projects)
7
7
  * 3. Env var (PI_COMMIT_TRAILER, PI_USE_WORKTREES, PI_DREAM_INTERVAL_HOURS, PI_DCO,
8
8
  * ACPX_AGENTS, CLI_AGENTS, PI_PIDASH_ENABLE, PI_PIDIFF_ENABLE, PI_PIDASH_PORT, PI_IMAGE_MODEL,
9
- * PI_ASYNC_LLM_PROVIDER, PI_ASYNC_LLM_MODEL, PI_REVIEW_LOOP_MAX_CYCLES)
9
+ * PI_INTERNAL_OPERATIONS_PROVIDER, PI_INTERNAL_OPERATIONS_MODEL, PI_REVIEW_LOOP_MAX_CYCLES,
10
+ * PI_ENFORCEMENT_ALLOWED_COMMANDS, VERTEX_CLAUDE_1M, PI_SIDECAR_LOG_LEVEL,
11
+ * PI_COMS_MAX_HOPS, PI_COMS_TIMEOUT_MS, PI_COMS_DIR)
10
12
  * 4. Default (dream_interval_hours defaults to 3; acpx_agents/cli_agents to []; pidash_enable/pidiff_enable to true; pidash_port to 19190;
11
13
  * review_loop_max_cycles to 3)
12
14
  *
@@ -17,8 +19,10 @@
17
19
  */
18
20
 
19
21
  import { existsSync, statSync, readFileSync } from "node:fs";
20
- import { join } from "node:path";
22
+ import { dirname, join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
21
24
  import { homedir } from "node:os";
25
+ import stripJsonComments from "strip-json-comments";
22
26
  import { resolveRepoRoot } from "./utils.js";
23
27
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
24
28
 
@@ -38,11 +42,17 @@ interface ProjectSettings {
38
42
  pidash_enable?: boolean;
39
43
  pidiff_enable?: boolean;
40
44
  pidash_port?: number;
45
+ task_auto_clear_enabled?: boolean;
46
+ task_auto_clear_minutes?: number;
47
+ task_reminder_enabled?: boolean;
48
+ task_reminder_interval_minutes?: number;
49
+ task_stale_in_progress_enabled?: boolean;
50
+ task_stale_in_progress_minutes?: number;
41
51
  image_model?: string;
42
52
  /** Provider for detached LLM async children when parent is acpx (must-async / dream). */
43
- async_llm_provider?: string;
53
+ internal_operations_provider?: string;
44
54
  /** Model id for detached LLM async children when parent is acpx. */
45
- async_llm_model?: string;
55
+ internal_operations_model?: string;
46
56
  /** Max review-loop cycles injected into the rules prompt. Integer 1-10 only. */
47
57
  review_loop_max_cycles?: number;
48
58
  /** Default provider for all subagents. */
@@ -51,18 +61,127 @@ interface ProjectSettings {
51
61
  agent_model?: string;
52
62
  /** Per-agent provider/model overrides. null = use parent model (skip global agent_provider/agent_model). */
53
63
  agent_overrides?: Record<string, { provider?: string | null; model?: string | null }>;
64
+ /** Enable 1M context window variants for Vertex Claude models. */
65
+ vertex_claude_1m?: boolean;
66
+ /** Log level for pi-sidecar (debug, info, warn, error). */
67
+ sidecar_log_level?: string;
68
+ /** Colon-separated command allowlist for enforcement. Empty = allow all. */
69
+ enforcement_allowed_commands?: string;
70
+ /** Enforcement: max sleep seconds allowed in loop context. */
71
+ enforcement_loop_sleep_threshold_s?: number;
72
+ /** Enforcement: max sleep seconds allowed standalone. */
73
+ enforcement_sleep_threshold_s?: number;
74
+ /** Subagent: max sync agent duration in seconds. */
75
+ sync_agent_max_seconds?: number;
76
+ /** Subagent: async poller interval in ms. */
77
+ async_poll_interval_ms?: number;
78
+ /** Subagent: async phantom timeout in ms. */
79
+ async_phantom_timeout_ms?: number;
80
+ /** Max hops for P2P coms message relay. */
81
+ coms_max_hops?: number;
82
+ /** P2P coms message response timeout in ms. */
83
+ coms_timeout_ms?: number;
84
+ /** P2P coms data directory. Empty = ~/.pi/coms. */
85
+ coms_dir?: string;
86
+ /** Coms task heartbeat interval in ms. */
87
+ coms_task_heartbeat_ms?: number;
88
+ /** Coms: entry grace period in ms. */
89
+ coms_entry_grace_period_ms?: number;
90
+ /** Coms: probe timeout in ms. */
91
+ coms_probe_timeout_ms?: number;
92
+ /** Pidiff: stale lock timeout in ms. */
93
+ pidiff_stale_lock_timeout_ms?: number;
94
+ /** Pidiff: daemon startup timeout in seconds. */
95
+ pidiff_daemon_startup_timeout_s?: number;
54
96
  }
55
97
 
56
- const SETTINGS_FILENAME = "pi-config-settings.json";
98
+ /** Key definition from settings-keys.json — single source of truth for env names + defaults. */
99
+ export interface SettingsKeyDef {
100
+ description: string;
101
+ type: string;
102
+ env?: string;
103
+ default: unknown;
104
+ min?: number;
105
+ max?: number;
106
+ strict_digits?: boolean;
107
+ per_key_resolution?: boolean;
108
+ }
109
+
110
+ const SETTINGS_FILENAMES = ["pi-config-settings.jsonc", "pi-config-settings.json"];
111
+
112
+ /** Find the first existing settings file in a directory (.jsonc preferred over .json). */
113
+ export function findSettingsFile(dir: string): string | null {
114
+ for (const name of SETTINGS_FILENAMES) {
115
+ const p = join(dir, name);
116
+ if (existsSync(p)) return p;
117
+ }
118
+ return null;
119
+ }
120
+
121
+ export const SETTINGS_KEYS: Record<string, SettingsKeyDef> = JSON.parse(
122
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "settings-keys.json"), "utf-8"),
123
+ );
57
124
 
58
- function getSettingsPath(cwd: string): string {
59
- return join(resolveRepoRoot(cwd), ".pi", SETTINGS_FILENAME);
125
+ /** Dev-time check: every ProjectSettings field must have a JSON definition. */
126
+ const PROJECT_SETTINGS_KEYS: (keyof ProjectSettings)[] = [
127
+ "commit_trailer",
128
+ "allow_push_to_protected_branches",
129
+ "use_worktrees",
130
+ "dream_interval_hours",
131
+ "dco",
132
+ "comment_signature",
133
+ "review_loop_enforcement",
134
+ "orchestrator_edit_write_block",
135
+ "acpx_agents",
136
+ "cli_agents",
137
+ "pidash_enable",
138
+ "pidiff_enable",
139
+ "pidash_port",
140
+ "task_auto_clear_enabled",
141
+ "task_auto_clear_minutes",
142
+ "task_reminder_enabled",
143
+ "task_reminder_interval_minutes",
144
+ "task_stale_in_progress_enabled",
145
+ "task_stale_in_progress_minutes",
146
+ "image_model",
147
+ "internal_operations_provider",
148
+ "internal_operations_model",
149
+ "review_loop_max_cycles",
150
+ "agent_provider",
151
+ "agent_model",
152
+ "agent_overrides",
153
+ "vertex_claude_1m",
154
+ "sidecar_log_level",
155
+ "enforcement_allowed_commands",
156
+ "enforcement_loop_sleep_threshold_s",
157
+ "enforcement_sleep_threshold_s",
158
+ "sync_agent_max_seconds",
159
+ "async_poll_interval_ms",
160
+ "async_phantom_timeout_ms",
161
+ "coms_max_hops",
162
+ "coms_timeout_ms",
163
+ "coms_dir",
164
+ "coms_task_heartbeat_ms",
165
+ "coms_entry_grace_period_ms",
166
+ "coms_probe_timeout_ms",
167
+ "pidiff_stale_lock_timeout_ms",
168
+ "pidiff_daemon_startup_timeout_s",
169
+ ];
170
+ for (const key of PROJECT_SETTINGS_KEYS) {
171
+ if (!(key in SETTINGS_KEYS)) {
172
+ throw new Error(`[project-settings] settings-keys.json missing key: ${key}`);
173
+ }
60
174
  }
61
175
 
62
- function parseSettingsFile(filePath: string): ProjectSettings {
176
+ export function getSettingsPath(cwd: string): string {
177
+ const piDir = join(resolveRepoRoot(cwd), ".pi");
178
+ return findSettingsFile(piDir) ?? join(piDir, SETTINGS_FILENAMES[0]);
179
+ }
180
+
181
+ export function parseSettingsFile(filePath: string): ProjectSettings {
63
182
  if (!existsSync(filePath)) return {};
64
183
  try {
65
- const raw = JSON.parse(readFileSync(filePath, "utf-8"));
184
+ const raw = JSON.parse(stripJsonComments(readFileSync(filePath, "utf-8")));
66
185
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
67
186
  const result: ProjectSettings = {};
68
187
  if (typeof raw.commit_trailer === "boolean" || typeof raw.commit_trailer === "string") result.commit_trailer = raw.commit_trailer;
@@ -83,11 +202,11 @@ function parseSettingsFile(filePath: string): ProjectSettings {
83
202
  if (typeof raw.image_model === "string" && raw.image_model.trim()) {
84
203
  result.image_model = raw.image_model.trim();
85
204
  }
86
- if (typeof raw.async_llm_provider === "string" && raw.async_llm_provider.trim()) {
87
- result.async_llm_provider = raw.async_llm_provider.trim();
205
+ if (typeof raw.internal_operations_provider === "string" && raw.internal_operations_provider.trim()) {
206
+ result.internal_operations_provider = raw.internal_operations_provider.trim();
88
207
  }
89
- if (typeof raw.async_llm_model === "string" && raw.async_llm_model.trim()) {
90
- result.async_llm_model = raw.async_llm_model.trim();
208
+ if (typeof raw.internal_operations_model === "string" && raw.internal_operations_model.trim()) {
209
+ result.internal_operations_model = raw.internal_operations_model.trim();
91
210
  }
92
211
  const parsedMaxCycles = parseReviewLoopMaxCycles(raw.review_loop_max_cycles);
93
212
  if (parsedMaxCycles !== undefined) {
@@ -115,13 +234,56 @@ function parseSettingsFile(filePath: string): ProjectSettings {
115
234
  if (typeof val === "object" && val !== null && !Array.isArray(val)) {
116
235
  const v = val as { provider?: unknown; model?: unknown };
117
236
  const entry: { provider?: string | null; model?: string | null } = {};
118
- if (v.provider === null || (typeof v.provider === "string")) entry.provider = v.provider === null ? null : v.provider.trim() || undefined;
119
- if (v.model === null || (typeof v.model === "string")) entry.model = v.model === null ? null : v.model.trim() || undefined;
237
+ if (v.provider === null) entry.provider = null;
238
+ else if (typeof v.provider === "string") entry.provider = v.provider.trim() || undefined;
239
+ if (v.model === null) entry.model = null;
240
+ else if (typeof v.model === "string") entry.model = v.model.trim() || undefined;
120
241
  if (entry.provider !== undefined || entry.model !== undefined) overrides[name] = entry;
121
242
  }
122
243
  }
123
244
  if (Object.keys(overrides).length > 0) result.agent_overrides = overrides;
124
245
  }
246
+
247
+ // Data-driven parsing for remaining settings — derive from settings-keys.json types
248
+ const SPECIAL_CASE_KEYS = new Set<string>([
249
+ "commit_trailer", "allow_push_to_protected_branches", "use_worktrees",
250
+ "dream_interval_hours", "dco", "comment_signature", "review_loop_enforcement",
251
+ "orchestrator_edit_write_block", "pidash_enable", "pidiff_enable", "pidash_port",
252
+ "image_model", "internal_operations_provider", "internal_operations_model",
253
+ "review_loop_max_cycles", "acpx_agents", "cli_agents", "agent_provider",
254
+ "agent_model", "agent_overrides",
255
+ ]);
256
+ for (const [k, def] of Object.entries(SETTINGS_KEYS)) {
257
+ if (SPECIAL_CASE_KEYS.has(k) || k in result) continue;
258
+ if (!(k in raw)) continue;
259
+ const val = raw[k];
260
+ switch (def.type) {
261
+ case "bool":
262
+ if (typeof val === "boolean") (result as any)[k] = val;
263
+ break;
264
+ case "string":
265
+ if (typeof val === "string") (result as any)[k] = val.trim();
266
+ break;
267
+ case "int": {
268
+ if (typeof val === "number" && Number.isInteger(val)) {
269
+ const min = def.min ?? 0;
270
+ const max = def.max ?? Number.MAX_SAFE_INTEGER;
271
+ if (val >= min && val <= max) (result as any)[k] = val;
272
+ }
273
+ break;
274
+ }
275
+ case "port": {
276
+ const min = def.min ?? 0;
277
+ if (typeof val === "number" && Number.isInteger(val) && val >= min && val <= 65535) {
278
+ (result as any)[k] = val;
279
+ }
280
+ break;
281
+ }
282
+ case "number":
283
+ if (typeof val === "number" && Number.isFinite(val)) (result as any)[k] = val;
284
+ break;
285
+ }
286
+ }
125
287
  return result;
126
288
  } catch (e: any) {
127
289
  console.debug(`[project-settings] failed to parse ${filePath}:`, e?.message?.slice(0, 100));
@@ -129,7 +291,7 @@ function parseSettingsFile(filePath: string): ProjectSettings {
129
291
  }
130
292
  }
131
293
 
132
- function loadProjectSettings(cwd: string): ProjectSettings {
294
+ export function loadProjectSettings(cwd: string): ProjectSettings {
133
295
  return parseSettingsFile(getSettingsPath(cwd));
134
296
  }
135
297
 
@@ -142,16 +304,20 @@ export function setGlobalSettingsPath(path: string | null): void {
142
304
  clearSettingsCache();
143
305
  }
144
306
 
145
- function loadGlobalSettings(): ProjectSettings {
146
- const globalPath = globalSettingsPathOverride ?? join(homedir(), ".pi", SETTINGS_FILENAME);
147
- return parseSettingsFile(globalPath);
307
+ /** Resolved global settings file path (honors test override). */
308
+ export function getGlobalSettingsPath(): string {
309
+ return globalSettingsPathOverride ?? findSettingsFile(join(homedir(), ".pi")) ?? join(homedir(), ".pi", SETTINGS_FILENAMES[0]);
310
+ }
311
+
312
+ export function loadGlobalSettings(): ProjectSettings {
313
+ return parseSettingsFile(getGlobalSettingsPath());
148
314
  }
149
315
 
150
316
  function projectSettingsFileHasKey(cwd: string, key: string): boolean {
151
317
  const filePath = getSettingsPath(cwd);
152
318
  if (!existsSync(filePath)) return false;
153
319
  try {
154
- const raw = JSON.parse(readFileSync(filePath, "utf-8"));
320
+ const raw = JSON.parse(stripJsonComments(readFileSync(filePath, "utf-8")));
155
321
  return typeof raw === "object" && raw !== null && !Array.isArray(raw) && key in raw;
156
322
  } catch (e: any) {
157
323
  console.debug(`[project-settings] failed to check key in ${filePath}:`, e?.message?.slice(0, 100));
@@ -257,7 +423,7 @@ function getSettings(cwd: string): ProjectSettings {
257
423
  cachedMtime = existsSync(settingsPath) ? statSync(settingsPath).mtimeMs : 0;
258
424
  } catch { cachedMtime = 0; }
259
425
  try {
260
- const globalPath = globalSettingsPathOverride ?? join(homedir(), ".pi", SETTINGS_FILENAME);
426
+ const globalPath = getGlobalSettingsPath();
261
427
  cachedGlobalMtime = existsSync(globalPath) ? statSync(globalPath).mtimeMs : 0;
262
428
  } catch { cachedGlobalMtime = 0; }
263
429
  lastMtimeCheck = now;
@@ -267,7 +433,7 @@ function getSettings(cwd: string): ProjectSettings {
267
433
  if (now - lastMtimeCheck < MTIME_CHECK_INTERVAL_MS) return cachedSettings;
268
434
  lastMtimeCheck = now;
269
435
  const settingsPath = getSettingsPath(cwd);
270
- const globalPath = globalSettingsPathOverride ?? join(homedir(), ".pi", SETTINGS_FILENAME);
436
+ const globalPath = getGlobalSettingsPath();
271
437
  let mtime = 0;
272
438
  let globalMtime = 0;
273
439
  try { if (existsSync(settingsPath)) mtime = statSync(settingsPath).mtimeMs; } catch {}
@@ -301,68 +467,57 @@ export function getSetting(cwd: string, key: "cli_agents"): string[];
301
467
  export function getSetting(cwd: string, key: "pidash_enable"): boolean;
302
468
  export function getSetting(cwd: string, key: "pidiff_enable"): boolean;
303
469
  export function getSetting(cwd: string, key: "pidash_port"): number;
470
+ export function getSetting(cwd: string, key: "task_auto_clear_enabled"): boolean;
471
+ export function getSetting(cwd: string, key: "task_auto_clear_minutes"): number;
472
+ export function getSetting(cwd: string, key: "task_reminder_enabled"): boolean;
473
+ export function getSetting(cwd: string, key: "task_reminder_interval_minutes"): number;
474
+ export function getSetting(cwd: string, key: "task_stale_in_progress_enabled"): boolean;
475
+ export function getSetting(cwd: string, key: "task_stale_in_progress_minutes"): number;
304
476
  export function getSetting(cwd: string, key: "image_model"): string;
305
- export function getSetting(cwd: string, key: "async_llm_provider"): string;
306
- export function getSetting(cwd: string, key: "async_llm_model"): string;
477
+ export function getSetting(cwd: string, key: "internal_operations_provider"): string;
478
+ export function getSetting(cwd: string, key: "internal_operations_model"): string;
307
479
  export function getSetting(cwd: string, key: "review_loop_max_cycles"): number;
308
480
  export function getSetting(cwd: string, key: "agent_provider"): string;
309
481
  export function getSetting(cwd: string, key: "agent_model"): string;
310
482
  export function getSetting(cwd: string, key: "agent_overrides"): Record<string, { provider?: string | null; model?: string | null }>;
483
+ export function getSetting(cwd: string, key: "vertex_claude_1m"): boolean;
484
+ export function getSetting(cwd: string, key: "sidecar_log_level"): string;
485
+ export function getSetting(cwd: string, key: "enforcement_allowed_commands"): string;
486
+ export function getSetting(cwd: string, key: "enforcement_loop_sleep_threshold_s"): number;
487
+ export function getSetting(cwd: string, key: "enforcement_sleep_threshold_s"): number;
488
+ export function getSetting(cwd: string, key: "sync_agent_max_seconds"): number;
489
+ export function getSetting(cwd: string, key: "async_poll_interval_ms"): number;
490
+ export function getSetting(cwd: string, key: "async_phantom_timeout_ms"): number;
491
+ export function getSetting(cwd: string, key: "coms_max_hops"): number;
492
+ export function getSetting(cwd: string, key: "coms_timeout_ms"): number;
493
+ export function getSetting(cwd: string, key: "coms_dir"): string;
494
+ export function getSetting(cwd: string, key: "coms_task_heartbeat_ms"): number;
495
+ export function getSetting(cwd: string, key: "coms_entry_grace_period_ms"): number;
496
+ export function getSetting(cwd: string, key: "coms_probe_timeout_ms"): number;
497
+ export function getSetting(cwd: string, key: "pidiff_stale_lock_timeout_ms"): number;
498
+ export function getSetting(cwd: string, key: "pidiff_daemon_startup_timeout_s"): number;
311
499
  export function getSetting(cwd: string, key: string): boolean | string | number | string[] | Record<string, { provider?: string | null; model?: string | null }> {
312
500
  const settings = getSettings(cwd);
501
+ const def = SETTINGS_KEYS[key];
502
+ if (!def) return false;
313
503
 
504
+ // Special cases — unique parsing beyond simple settings → env → default
314
505
  switch (key) {
315
506
  case "commit_trailer": {
316
507
  if (settings.commit_trailer !== undefined) return settings.commit_trailer;
317
- const envStr = process.env.PI_COMMIT_TRAILER;
508
+ const envStr = def.env ? process.env[def.env] : undefined;
318
509
  if (envStr !== undefined && envStr !== "") {
319
510
  if (["true", "1", "yes", "on"].includes(envStr.toLowerCase())) return true;
320
511
  if (["false", "0", "no", "off"].includes(envStr.toLowerCase())) return false;
321
512
  return envStr; // treat as custom trailer string
322
513
  }
323
- return false; // default: disabled
324
- }
325
- case "allow_push_to_protected_branches": {
326
- if (settings.allow_push_to_protected_branches !== undefined) return settings.allow_push_to_protected_branches;
327
- const env = parseBoolEnv("PI_ALLOW_PUSH_TO_PROTECTED_BRANCHES");
328
- if (env !== undefined) return env;
329
- return false; // default: block pushes to protected branches
330
- }
331
- case "use_worktrees": {
332
- if (settings.use_worktrees !== undefined) return settings.use_worktrees;
333
- const env = parseBoolEnv("PI_USE_WORKTREES");
334
- if (env !== undefined) return env;
335
- return false; // default: disabled
336
- }
337
- case "dream_interval_hours": {
338
- if (settings.dream_interval_hours !== undefined) return settings.dream_interval_hours;
339
- const env = parseNumEnv("PI_DREAM_INTERVAL_HOURS");
340
- if (env !== undefined) return env;
341
- return 3; // default: 3 hours
342
- }
343
- case "dco": {
344
- if (settings.dco !== undefined) return settings.dco;
345
- const env = parseBoolEnv("PI_DCO");
346
- if (env !== undefined) return env;
347
- return false; // default: disabled
348
- }
349
- case "comment_signature": {
350
- if (settings.comment_signature !== undefined) return settings.comment_signature;
351
- return false; // default: disabled
352
- }
353
- case "review_loop_enforcement": {
354
- if (settings.review_loop_enforcement !== undefined) return settings.review_loop_enforcement;
355
- const env = parseBoolEnv("PI_REVIEW_LOOP_ENFORCEMENT");
356
- if (env !== undefined) return env;
357
- return false; // default: disabled (opt-in)
514
+ return def.default as boolean;
358
515
  }
359
- case "orchestrator_edit_write_block": {
360
- if (settings.orchestrator_edit_write_block !== undefined) return settings.orchestrator_edit_write_block;
361
- return false; // default: disabled
362
- }
363
- case "acpx_agents": {
364
- if (projectSettingsFileHasKey(cwd, "acpx_agents")) {
365
- const projectValue = loadProjectSettings(cwd).acpx_agents;
516
+ case "acpx_agents":
517
+ case "cli_agents": {
518
+ const agentKey = key as "acpx_agents" | "cli_agents";
519
+ if (projectSettingsFileHasKey(cwd, agentKey)) {
520
+ const projectValue = loadProjectSettings(cwd)[agentKey];
366
521
  // Only override when the raw value parsed as a valid type (string or array).
367
522
  // Invalid types (number, object, etc.) are skipped by parseSettingsFile,
368
523
  // leaving projectValue undefined — fall through to global/env.
@@ -370,44 +525,27 @@ export function getSetting(cwd: string, key: string): boolean | string | number
370
525
  return parseAgentNameList(projectValue);
371
526
  }
372
527
  }
373
- const globalAgents = loadGlobalSettings().acpx_agents;
528
+ const globalAgents = loadGlobalSettings()[agentKey];
374
529
  if (globalAgents !== undefined) {
375
530
  return parseAgentNameList(globalAgents);
376
531
  }
377
- const env = process.env.ACPX_AGENTS;
378
- if (env !== undefined && env !== "") {
379
- return parseAgentNameList(env);
380
- }
381
- return [];
382
- }
383
- case "cli_agents": {
384
- if (projectSettingsFileHasKey(cwd, "cli_agents")) {
385
- const projectValue = loadProjectSettings(cwd).cli_agents;
386
- if (projectValue !== undefined) {
387
- return parseAgentNameList(projectValue);
532
+ if (def.env) {
533
+ const env = process.env[def.env];
534
+ if (env !== undefined && env !== "") {
535
+ return parseAgentNameList(env);
388
536
  }
389
537
  }
390
- const globalAgents = loadGlobalSettings().cli_agents;
391
- if (globalAgents !== undefined) {
392
- return parseAgentNameList(globalAgents);
393
- }
394
- const env = process.env.CLI_AGENTS;
395
- if (env !== undefined && env !== "") {
396
- return parseAgentNameList(env);
397
- }
398
- return [];
399
- }
400
- case "pidash_enable": {
401
- if (settings.pidash_enable !== undefined) return settings.pidash_enable;
402
- const disabled = parseDisabledEnv("PI_PIDASH_ENABLE");
403
- if (disabled !== undefined) return !disabled;
404
- return true;
538
+ return parseAgentNameList(def.default as string | string[] | undefined);
405
539
  }
540
+ case "pidash_enable":
406
541
  case "pidiff_enable": {
407
- if (settings.pidiff_enable !== undefined) return settings.pidiff_enable;
408
- const disabled = parseDisabledEnv("PI_PIDIFF_ENABLE");
409
- if (disabled !== undefined) return !disabled;
410
- return true;
542
+ const enableKey = key as "pidash_enable" | "pidiff_enable";
543
+ if (settings[enableKey] !== undefined) return settings[enableKey]!;
544
+ if (def.env) {
545
+ const disabled = parseDisabledEnv(def.env);
546
+ if (disabled !== undefined) return !disabled;
547
+ }
548
+ return def.default as boolean;
411
549
  }
412
550
  case "pidash_port": {
413
551
  if (settings.pidash_port !== undefined) {
@@ -417,39 +555,85 @@ export function getSetting(cwd: string, key: string): boolean | string | number
417
555
  }
418
556
  // Invalid value in merged settings — fall through to env/default
419
557
  }
420
- const envPort = parsePortEnv("PI_PIDASH_PORT");
421
- if (envPort !== undefined) return envPort;
422
- return 19190;
558
+ if (def.env) {
559
+ const envPort = parsePortEnv(def.env);
560
+ if (envPort !== undefined) return envPort;
561
+ }
562
+ return def.default as number;
423
563
  }
424
- case "image_model": {
425
- if (settings.image_model !== undefined) return settings.image_model;
426
- const env = process.env.PI_IMAGE_MODEL;
427
- return env !== undefined && env !== "" ? env : "";
564
+ case "review_loop_max_cycles": {
565
+ if (settings.review_loop_max_cycles !== undefined) return settings.review_loop_max_cycles;
566
+ if (def.env) {
567
+ const env = parseReviewLoopMaxCyclesEnv(def.env);
568
+ if (env !== undefined) return env;
569
+ }
570
+ return def.default as number;
428
571
  }
429
- case "async_llm_provider": {
430
- if (settings.async_llm_provider !== undefined) return settings.async_llm_provider;
431
- const env = process.env.PI_ASYNC_LLM_PROVIDER;
432
- return env !== undefined && env !== "" ? env.trim() : "";
572
+ case "agent_overrides": {
573
+ return settings.agent_overrides || (def.default as Record<string, { provider?: string | null; model?: string | null }>);
433
574
  }
434
- case "async_llm_model": {
435
- if (settings.async_llm_model !== undefined) return settings.async_llm_model;
436
- const env = process.env.PI_ASYNC_LLM_MODEL;
437
- return env !== undefined && env !== "" ? env.trim() : "";
575
+ }
576
+
577
+ // Generic handler — simple bool / number / string keys driven by settings-keys.json
578
+ const merged = settings[key as keyof ProjectSettings];
579
+ switch (def.type) {
580
+ case "bool": {
581
+ if (merged !== undefined) return merged as boolean;
582
+ if (def.env) {
583
+ const env = parseBoolEnv(def.env);
584
+ if (env !== undefined) return env;
585
+ }
586
+ return def.default as boolean;
438
587
  }
439
- case "review_loop_max_cycles": {
440
- if (settings.review_loop_max_cycles !== undefined) return settings.review_loop_max_cycles;
441
- const env = parseReviewLoopMaxCyclesEnv("PI_REVIEW_LOOP_MAX_CYCLES");
442
- if (env !== undefined) return env;
443
- return 3; // default: 3 cycles
588
+ case "int": {
589
+ if (merged !== undefined) return merged as number;
590
+ if (def.env) {
591
+ const val = process.env[def.env];
592
+ if (val !== undefined && val !== "") {
593
+ const trimmed = val.trim();
594
+ if (/^-?\d+$/.test(trimmed)) {
595
+ const n = Number(trimmed);
596
+ const min = def.min ?? 0;
597
+ const max = def.max ?? Number.MAX_SAFE_INTEGER;
598
+ if (n >= min && n <= max) return n;
599
+ }
600
+ }
601
+ }
602
+ return def.default as number;
444
603
  }
445
- case "agent_provider": {
446
- return settings.agent_provider || "";
604
+ case "port": {
605
+ if (merged !== undefined) {
606
+ const n = merged as number;
607
+ if (Number.isInteger(n) && n >= 0 && n <= 65535) return n;
608
+ }
609
+ if (def.env) {
610
+ const val = process.env[def.env];
611
+ if (val !== undefined && val !== "") {
612
+ const trimmed = val.trim();
613
+ if (/^\d+$/.test(trimmed)) {
614
+ const n = Number(trimmed);
615
+ if (n >= 0 && n <= 65535) return n;
616
+ }
617
+ }
618
+ }
619
+ return def.default as number;
447
620
  }
448
- case "agent_model": {
449
- return settings.agent_model || "";
621
+ case "number": {
622
+ if (merged !== undefined) return merged as number;
623
+ if (def.env) {
624
+ const env = parseNumEnv(def.env);
625
+ if (env !== undefined) return env;
626
+ }
627
+ return def.default as number;
450
628
  }
451
- case "agent_overrides": {
452
- return settings.agent_overrides || {};
629
+ case "string": {
630
+ // Preserve empty strings — some settings use empty as meaningful ("allow all", "use default dir")
631
+ if (merged !== undefined) return merged as string;
632
+ if (def.env) {
633
+ const env = process.env[def.env];
634
+ if (env !== undefined && env !== "") return env.trim();
635
+ }
636
+ return def.default as string;
453
637
  }
454
638
  default:
455
639
  return false;
@@ -464,5 +648,27 @@ export function registerProjectSettings(pi: ExtensionAPI): void {
464
648
 
465
649
  pi.on("session_start", (_event, ctx) => {
466
650
  clearSettingsCache();
651
+
652
+ // Inject settings into process.env for standalone packages that can't call getSetting
653
+ const cwd = ctx.cwd;
654
+ const vertexClaude1m = getSetting(cwd, "vertex_claude_1m");
655
+ if (vertexClaude1m) process.env.VERTEX_CLAUDE_1M = "true";
656
+ else delete process.env.VERTEX_CLAUDE_1M;
657
+
658
+ const sidecarLogLevel = getSetting(cwd, "sidecar_log_level");
659
+ if (sidecarLogLevel) process.env.PI_SIDECAR_LOG_LEVEL = sidecarLogLevel;
660
+ else delete process.env.PI_SIDECAR_LOG_LEVEL;
661
+
662
+ const acpxAgents = getSetting(cwd, "acpx_agents");
663
+ if (acpxAgents.length > 0) process.env.ACPX_AGENTS = acpxAgents.join(",");
664
+ else delete process.env.ACPX_AGENTS;
665
+
666
+ const cliAgents = getSetting(cwd, "cli_agents");
667
+ if (cliAgents.length > 0) process.env.CLI_AGENTS = cliAgents.join(",");
668
+ else delete process.env.CLI_AGENTS;
669
+
670
+ const imageModel = getSetting(cwd, "image_model");
671
+ if (imageModel) process.env.PI_IMAGE_MODEL = imageModel;
672
+ else delete process.env.PI_IMAGE_MODEL;
467
673
  });
468
674
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Resolve effective model/provider for an agent.
3
- * Priority: agent_overrides[name] > agent frontmatter > agent_provider/agent_model setting > parent
3
+ * Each field resolved independently through priority chain:
4
+ * explicit > agent_overrides[name] > agent frontmatter > agent_provider/agent_model setting > parent
4
5
  * null in overrides = skip settings, use parent directly.
5
6
  */
6
7
 
@@ -11,22 +12,32 @@ export function resolveAgentModelProvider(
11
12
  agent: { model?: string; provider?: string },
12
13
  parentModelId: string | undefined,
13
14
  parentProvider: string | undefined,
14
- cwd: string
15
+ cwd: string,
16
+ explicit?: { model?: string; provider?: string },
15
17
  ): { model: string | undefined; provider: string | undefined } {
16
18
  const overrides = getSetting(cwd, "agent_overrides");
17
19
  const override = overrides[agentName];
18
20
 
19
- if (override) {
20
- // null = explicitly use parent (skip agent frontmatter AND global settings)
21
- // Model and provider resolve independently on purpose: a partial override
22
- // (e.g. only model) still lets provider fall through frontmatter → settings → parent.
23
- const model = override.model === null ? parentModelId : (override.model || agent.model || getSetting(cwd, "agent_model") || parentModelId);
24
- const provider = override.provider === null ? parentProvider : (override.provider || agent.provider || getSetting(cwd, "agent_provider") || parentProvider);
25
- return { model: model || undefined, provider: provider || undefined };
21
+ // Resolve each field independently through the priority chain:
22
+ // explicit > agent_overrides[name] > agent frontmatter > agent_provider/agent_model setting > parent
23
+ // null in overrides = skip settings, use parent directly.
24
+ function resolveModel(): string | undefined {
25
+ if (explicit?.model) return explicit.model;
26
+ if (override) {
27
+ if (override.model === null) return parentModelId;
28
+ if (override.model) return override.model;
29
+ }
30
+ return agent.model || getSetting(cwd, "agent_model") || parentModelId || undefined;
26
31
  }
27
32
 
28
- // No override: agent frontmatter > global setting > parent
29
- const model = agent.model || getSetting(cwd, "agent_model") || parentModelId;
30
- const provider = agent.provider || getSetting(cwd, "agent_provider") || parentProvider;
31
- return { model: model || undefined, provider: provider || undefined };
33
+ function resolveProvider(): string | undefined {
34
+ if (explicit?.provider) return explicit.provider;
35
+ if (override) {
36
+ if (override.provider === null) return parentProvider;
37
+ if (override.provider) return override.provider;
38
+ }
39
+ return agent.provider || getSetting(cwd, "agent_provider") || parentProvider || undefined;
40
+ }
41
+
42
+ return { model: resolveModel(), provider: resolveProvider() };
32
43
  }