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.
- package/README.md +76 -67
- package/agents/code-reviewer-docs.md +11 -1
- package/agents/code-reviewer-guidelines.md +11 -1
- package/agents/code-reviewer-quality.md +11 -1
- package/agents/code-reviewer-security.md +11 -1
- package/agents/code-reviewer-spec.md +11 -1
- package/agents/git-expert.md +42 -3
- package/agents/github-expert.md +11 -0
- package/extensions/acpx-provider/runtime-models.ts +18 -6
- package/extensions/cli-provider/agents/claude.ts +3 -2
- package/extensions/cli-provider/agents/cursor.ts +3 -5
- package/extensions/cli-provider/agents/gemini.ts +3 -2
- package/extensions/cli-provider/discover.ts +7 -7
- package/extensions/cli-provider/index.ts +5 -0
- package/extensions/cli-provider/runtime-models.ts +18 -6
- package/extensions/cli-provider/session-reaper.ts +5 -7
- package/extensions/cli-provider/sessions.ts +4 -0
- package/extensions/cli-provider/shared/discover-cache.ts +17 -176
- package/extensions/coms/coms-p2p.ts +1719 -423
- package/extensions/coms/coms-shared.ts +261 -39
- package/extensions/coms/coms-wrapper.ts +100 -62
- package/extensions/coms/index.ts +8 -4
- package/extensions/coms/mixed-turn.ts +26 -0
- package/extensions/coms/probe-socket.ts +42 -0
- package/extensions/coms/themeMap.ts +0 -1
- package/extensions/image-gen/image-gen.ts +19 -1
- package/extensions/orchestrator/async-agents.ts +563 -102
- package/extensions/orchestrator/async-capability.ts +8 -8
- package/extensions/orchestrator/cron.ts +15 -9
- package/extensions/orchestrator/dreaming.ts +32 -25
- package/extensions/orchestrator/enforcement-helpers.ts +459 -17
- package/extensions/orchestrator/enforcement-rules.ts +5 -4
- package/extensions/orchestrator/enforcement.ts +80 -27
- package/extensions/orchestrator/extended-autocomplete.ts +7 -0
- package/extensions/orchestrator/git-helpers.ts +2 -2
- package/extensions/orchestrator/index.ts +78 -10
- package/extensions/orchestrator/markdown-transformer.ts +313 -0
- package/extensions/orchestrator/memory-embeddings.ts +36 -34
- package/extensions/orchestrator/memory-provenance.ts +29 -25
- package/extensions/orchestrator/memory-scoring.ts +23 -18
- package/extensions/orchestrator/memory-tools.ts +10 -3
- package/extensions/orchestrator/overlay-dashboard.ts +17 -5
- package/extensions/orchestrator/parse-model-override.ts +29 -0
- package/extensions/orchestrator/pi-config-review-state.ts +57 -33
- package/extensions/orchestrator/project-settings.ts +333 -127
- package/extensions/orchestrator/resolve-agent-model.ts +24 -13
- package/extensions/orchestrator/review-porcelain.ts +4 -0
- package/extensions/orchestrator/review-ui.ts +63 -15
- package/extensions/orchestrator/rule-placeholders.ts +411 -2
- package/extensions/orchestrator/rules.ts +80 -132
- package/extensions/orchestrator/settings-tui-helpers.ts +380 -0
- package/extensions/orchestrator/settings-tui-submenus.ts +647 -0
- package/extensions/orchestrator/settings-tui.ts +636 -0
- package/extensions/orchestrator/state-jsonl.ts +431 -0
- package/extensions/orchestrator/subagent-tool.ts +157 -59
- package/extensions/orchestrator/sync-limit.ts +23 -0
- package/extensions/orchestrator/task-lifecycle.ts +77 -67
- package/extensions/orchestrator/utils.ts +8 -5
- package/extensions/pidash/pidash.ts +193 -156
- package/extensions/pidiff/pidiff.ts +96 -38
- package/extensions/pidiff/spawn-lock.ts +62 -0
- package/extensions/pitasks/index.ts +430 -0
- package/extensions/pitasks/reminders.ts +37 -0
- package/extensions/pitasks/task-store.ts +467 -0
- package/extensions/pitasks/task-tools.ts +375 -0
- package/extensions/pitasks/task-widget.ts +181 -0
- package/extensions/pitasks/types.ts +38 -0
- package/extensions/providers/acpx-driver.ts +12 -2
- package/extensions/providers/built-in-drivers.ts +6 -6
- package/extensions/providers/claude-driver.ts +1 -1
- package/extensions/providers/cursor-cli-driver.ts +1 -1
- package/extensions/providers/gemini-driver.ts +1 -1
- package/extensions/providers/index.ts +144 -26
- package/extensions/providers/initialized-guard.ts +27 -0
- package/extensions/providers/restore-default-model.ts +596 -0
- package/extensions/providers/session-shutdown.ts +39 -0
- package/extensions/shared/coms-active.ts +23 -0
- package/extensions/shared/create-runtime-provider.ts +2 -0
- package/extensions/shared/daemon-manager.ts +0 -8
- package/extensions/shared/file-logger.ts +132 -25
- package/extensions/shared/logger.ts +50 -0
- package/extensions/shared/models-dev.ts +421 -0
- package/extensions/shared/oneshot.ts +116 -0
- package/extensions/shared/resolve-binary.ts +191 -0
- package/extensions/shared/types.ts +3 -0
- package/package.json +9 -3
- package/prompts/coderabbit-rate-limit.md +2 -0
- package/rules/00-orchestrator-core.md +3 -0
- package/rules/10-agent-routing.md +12 -1
- package/rules/20-code-review-loop.md +52 -25
- package/rules/30-prompt-templates.md +4 -0
- package/rules/35-memory.md +3 -3
- package/rules/40-critical-rules.md +3 -0
- package/rules/55-coms-protocol.md +84 -18
- package/rules/60-task-tracking.md +2 -0
- package/settings-keys.json +563 -0
- package/templates/coms-feature-manager-prompt.md +4 -6
- package/extensions/coms/coms-net-server.ts +0 -1642
- package/extensions/coms/coms-net-wrapper.ts +0 -521
- 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
|
-
*
|
|
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
|
-
|
|
53
|
+
internal_operations_provider?: string;
|
|
44
54
|
/** Model id for detached LLM async children when parent is acpx. */
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
|
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.
|
|
87
|
-
result.
|
|
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.
|
|
90
|
-
result.
|
|
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
|
|
119
|
-
if (
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
return
|
|
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 =
|
|
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 =
|
|
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: "
|
|
306
|
-
export function getSetting(cwd: string, key: "
|
|
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.
|
|
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
|
|
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 "
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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()
|
|
528
|
+
const globalAgents = loadGlobalSettings()[agentKey];
|
|
374
529
|
if (globalAgents !== undefined) {
|
|
375
530
|
return parseAgentNameList(globalAgents);
|
|
376
531
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
if (
|
|
410
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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 "
|
|
425
|
-
if (settings.
|
|
426
|
-
|
|
427
|
-
|
|
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 "
|
|
430
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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 "
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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 "
|
|
446
|
-
|
|
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 "
|
|
449
|
-
return
|
|
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 "
|
|
452
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
}
|