pi-crew 0.3.6 → 0.3.8

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 (44) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +1 -1
  3. package/src/agents/discover-agents.ts +2 -1
  4. package/src/config/config.ts +760 -229
  5. package/src/config/types.ts +34 -5
  6. package/src/extension/help.ts +1 -0
  7. package/src/extension/management.ts +2 -1
  8. package/src/extension/register.ts +1176 -255
  9. package/src/extension/registration/commands.ts +15 -2
  10. package/src/extension/registration/team-tool.ts +1 -1
  11. package/src/extension/session-summary.ts +11 -1
  12. package/src/extension/team-tool/api.ts +4 -1
  13. package/src/extension/team-tool/cache-control.ts +23 -0
  14. package/src/extension/team-tool/cancel.ts +27 -16
  15. package/src/extension/team-tool/context.ts +2 -0
  16. package/src/extension/team-tool/handle-settings.ts +2 -0
  17. package/src/extension/team-tool/health-monitor.ts +563 -0
  18. package/src/extension/team-tool/inspect.ts +10 -3
  19. package/src/extension/team-tool/lifecycle-actions.ts +12 -5
  20. package/src/extension/team-tool/respond.ts +6 -3
  21. package/src/extension/team-tool/status.ts +4 -1
  22. package/src/extension/team-tool-types.ts +2 -0
  23. package/src/extension/team-tool.ts +901 -177
  24. package/src/runtime/adaptive-plan.ts +1 -1
  25. package/src/runtime/child-pi.ts +15 -2
  26. package/src/runtime/crash-recovery.ts +30 -0
  27. package/src/runtime/foreground-watchdog.ts +129 -0
  28. package/src/runtime/manifest-cache.ts +4 -2
  29. package/src/runtime/pi-args.ts +3 -2
  30. package/src/runtime/run-tracker.ts +11 -0
  31. package/src/runtime/runtime-policy.ts +15 -2
  32. package/src/runtime/skill-instructions.ts +11 -0
  33. package/src/runtime/stale-reconciler.ts +322 -18
  34. package/src/runtime/task-runner.ts +8 -1
  35. package/src/schema/config-schema.ts +1 -0
  36. package/src/schema/team-tool-schema.ts +204 -76
  37. package/src/state/atomic-write.ts +2 -2
  38. package/src/state/locks.ts +19 -0
  39. package/src/state/mailbox.ts +22 -5
  40. package/src/state/state-store.ts +13 -3
  41. package/src/teams/discover-teams.ts +2 -1
  42. package/src/ui/run-event-bus.ts +2 -1
  43. package/src/ui/settings-overlay.ts +2 -0
  44. package/src/workflows/discover-workflows.ts +5 -1
@@ -1,62 +1,67 @@
1
- import { Type, type Static, type TSchema } from "@sinclair/typebox";
2
- import { Value } from "@sinclair/typebox/value";
3
1
  import * as fs from "node:fs";
4
2
  import * as os from "node:os";
5
3
  import * as path from "node:path";
6
- import { PiTeamsAutonomyProfileSchema, PiTeamsConfigSchema } from "../schema/config-schema.ts";
7
- import { suggestConfigKey } from "./suggestions.ts";
4
+ import { type Static, type TSchema, Type } from "@sinclair/typebox";
5
+ import { Value } from "@sinclair/typebox/value";
6
+ import {
7
+ PiTeamsAutonomyProfileSchema,
8
+ PiTeamsConfigSchema,
9
+ } from "../schema/config-schema.ts";
10
+ import { withFileLockSync } from "../state/locks.ts";
8
11
  import { projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
12
+ import { suggestConfigKey } from "./suggestions.ts";
9
13
 
10
14
  // 2.9: interface types extracted to ./types.ts; re-export for back-compat.
11
15
  export type {
12
- PiTeamsAutonomyProfile,
13
- PiTeamsAutonomousConfig,
14
- CrewLimitsConfig,
15
- CrewRuntimeMode,
16
- CompletionMutationGuardMode,
17
- EffectivenessGuardMode,
18
- CrewRuntimeConfig,
19
- CrewControlConfig,
20
- CrewWorktreeConfig,
21
- CrewUiConfig,
22
16
  AgentOverrideConfig,
17
+ CompletionMutationGuardMode,
18
+ ConfigValidationResult,
23
19
  CrewAgentsConfig,
24
- CrewToolsConfig,
25
- CrewTelemetryConfig,
26
- CrewPolicyConfig,
20
+ CrewControlConfig,
21
+ CrewLimitsConfig,
27
22
  CrewNotificationSeverity,
28
23
  CrewNotificationsConfig,
29
24
  CrewObservabilityConfig,
30
- CrewRetryPolicyConfig,
31
- CrewReliabilityConfig,
32
25
  CrewOtlpConfig,
33
- PiTeamsConfig,
26
+ CrewPolicyConfig,
27
+ CrewReliabilityConfig,
28
+ CrewRetryPolicyConfig,
29
+ CrewRuntimeConfig,
30
+ CrewRuntimeMode,
31
+ CrewTelemetryConfig,
32
+ CrewToolsConfig,
33
+ CrewUiConfig,
34
+ CrewWorktreeConfig,
35
+ EffectivenessGuardMode,
34
36
  LoadedPiTeamsConfig,
35
- ConfigValidationResult,
37
+ PiTeamsAutonomousConfig,
38
+ PiTeamsAutonomyProfile,
39
+ PiTeamsConfig,
36
40
  SavedPiTeamsConfig,
37
41
  UpdateConfigOptions,
38
42
  } from "./types.ts";
43
+
39
44
  import type {
40
- PiTeamsAutonomyProfile,
41
- PiTeamsAutonomousConfig,
42
- CrewLimitsConfig,
43
- CrewRuntimeConfig,
44
- CrewControlConfig,
45
- CrewWorktreeConfig,
46
- CrewUiConfig,
47
45
  AgentOverrideConfig,
46
+ ConfigValidationResult,
48
47
  CrewAgentsConfig,
49
- CrewToolsConfig,
50
- CrewTelemetryConfig,
51
- CrewPolicyConfig,
48
+ CrewControlConfig,
49
+ CrewLimitsConfig,
52
50
  CrewNotificationsConfig,
53
51
  CrewObservabilityConfig,
54
- CrewRetryPolicyConfig,
55
- CrewReliabilityConfig,
56
52
  CrewOtlpConfig,
57
- PiTeamsConfig,
53
+ CrewPolicyConfig,
54
+ CrewReliabilityConfig,
55
+ CrewRetryPolicyConfig,
56
+ CrewRuntimeConfig,
57
+ CrewTelemetryConfig,
58
+ CrewToolsConfig,
59
+ CrewUiConfig,
60
+ CrewWorktreeConfig,
58
61
  LoadedPiTeamsConfig,
59
- ConfigValidationResult,
62
+ PiTeamsAutonomousConfig,
63
+ PiTeamsAutonomyProfile,
64
+ PiTeamsConfig,
60
65
  SavedPiTeamsConfig,
61
66
  UpdateConfigOptions,
62
67
  } from "./types.ts";
@@ -68,7 +73,14 @@ export function configPath(): string {
68
73
 
69
74
  export function legacyConfigPath(): string {
70
75
  const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
71
- return path.join(home, ".pi", "agent", "extensions", "pi-crew", "config.json");
76
+ return path.join(
77
+ home,
78
+ ".pi",
79
+ "agent",
80
+ "extensions",
81
+ "pi-crew",
82
+ "config.json",
83
+ );
72
84
  }
73
85
 
74
86
  export function projectConfigPath(cwd: string): string {
@@ -83,32 +95,55 @@ export function projectPiCrewJsonPath(cwd: string): string {
83
95
  return path.join(projectPiRoot(cwd), "pi-crew.json");
84
96
  }
85
97
 
86
- function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
87
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as Partial<T>;
98
+ function withoutUndefined<T extends Record<string, unknown>>(
99
+ value: T,
100
+ ): Partial<T> {
101
+ return Object.fromEntries(
102
+ Object.entries(value).filter(([, entry]) => entry !== undefined),
103
+ ) as Partial<T>;
88
104
  }
89
105
 
90
106
  function errorPathFromValidation(error: unknown): string {
91
107
  if (error && typeof error === "object") {
92
- if (typeof (error as { path?: unknown }).path === "string") return (error as { path: string }).path;
93
- if (typeof (error as { instancePath?: unknown }).instancePath === "string") return (error as { instancePath: string }).instancePath;
94
- if (typeof (error as { keyword?: unknown }).keyword === "string" && typeof (error as { schemaPath?: unknown }).schemaPath === "string") return (error as { schemaPath: string }).schemaPath;
108
+ if (typeof (error as { path?: unknown }).path === "string")
109
+ return (error as { path: string }).path;
110
+ if (
111
+ typeof (error as { instancePath?: unknown }).instancePath ===
112
+ "string"
113
+ )
114
+ return (error as { instancePath: string }).instancePath;
115
+ if (
116
+ typeof (error as { keyword?: unknown }).keyword === "string" &&
117
+ typeof (error as { schemaPath?: unknown }).schemaPath === "string"
118
+ )
119
+ return (error as { schemaPath: string }).schemaPath;
95
120
  }
96
121
  return "config";
97
122
  }
98
123
 
99
124
  /** Known top-level config keys from the schema — used for fuzzy suggestions. */
100
- const KNOWN_TOP_LEVEL_KEYS = Object.keys(PiTeamsConfigSchema.properties ?? {}) as string[];
125
+ const KNOWN_TOP_LEVEL_KEYS = Object.keys(
126
+ PiTeamsConfigSchema.properties ?? {},
127
+ ) as string[];
101
128
 
102
129
  function validateConfigWithWarnings(raw: unknown): string[] {
103
130
  if (!Value.Check(PiTeamsConfigSchema, raw)) {
104
131
  return [...Value.Errors(PiTeamsConfigSchema, raw)].map((error) => {
105
132
  const path = errorPathFromValidation(error);
106
- const message = (error as { message?: unknown }).message ?? "invalid value";
133
+ const message =
134
+ (error as { message?: unknown }).message ?? "invalid value";
107
135
  // Enhance "additionalProperties" errors with fuzzy suggestions
108
- if ((error as { keyword?: unknown }).keyword === "additionalProperties") {
136
+ if (
137
+ (error as { keyword?: unknown }).keyword ===
138
+ "additionalProperties"
139
+ ) {
109
140
  const offendingKey = path.split("/").pop() ?? path;
110
- const suggestion = suggestConfigKey(offendingKey, KNOWN_TOP_LEVEL_KEYS);
111
- if (suggestion) return `${path}: ${message} (did you mean '${suggestion}'?)`;
141
+ const suggestion = suggestConfigKey(
142
+ offendingKey,
143
+ KNOWN_TOP_LEVEL_KEYS,
144
+ );
145
+ if (suggestion)
146
+ return `${path}: ${message} (did you mean '${suggestion}'?)`;
112
147
  }
113
148
  return `${path}: ${message}`;
114
149
  });
@@ -116,11 +151,18 @@ function validateConfigWithWarnings(raw: unknown): string[] {
116
151
  return [];
117
152
  }
118
153
 
119
- function projectOverrideWarning(projectPath: string, dottedPath: string): string {
154
+ function projectOverrideWarning(
155
+ projectPath: string,
156
+ dottedPath: string,
157
+ ): string {
120
158
  return `${projectPath}: project-level sensitive config '${dottedPath}' is ignored; set it in user config to trust it explicitly`;
121
159
  }
122
160
 
123
- function sanitizeProjectConfig(projectPath: string, userConfig: PiTeamsConfig, config: PiTeamsConfig): ConfigValidationResult {
161
+ function sanitizeProjectConfig(
162
+ projectPath: string,
163
+ userConfig: PiTeamsConfig,
164
+ config: PiTeamsConfig,
165
+ ): ConfigValidationResult {
124
166
  const sanitized: PiTeamsConfig = { ...config };
125
167
  const warnings: string[] = [];
126
168
  const dropTopLevel = (key: keyof PiTeamsConfig): void => {
@@ -133,95 +175,173 @@ function sanitizeProjectConfig(projectPath: string, userConfig: PiTeamsConfig, c
133
175
  dropTopLevel("requireCleanWorktreeLeader");
134
176
  if (config.runtime) {
135
177
  const runtime = { ...config.runtime };
136
- for (const key of ["mode", "preferLiveSession", "allowChildProcessFallback", "inheritContext", "isolationPolicy"] as const) {
178
+ for (const key of [
179
+ "mode",
180
+ "preferLiveSession",
181
+ "allowChildProcessFallback",
182
+ "inheritContext",
183
+ "isolationPolicy",
184
+ ] as const) {
137
185
  if (runtime[key] !== undefined) {
138
186
  delete runtime[key];
139
- warnings.push(projectOverrideWarning(projectPath, `runtime.${key}`));
187
+ warnings.push(
188
+ projectOverrideWarning(projectPath, `runtime.${key}`),
189
+ );
140
190
  }
141
191
  }
142
192
  if (runtime.requirePlanApproval === false) {
143
193
  delete runtime.requirePlanApproval;
144
- warnings.push(projectOverrideWarning(projectPath, "runtime.requirePlanApproval"));
194
+ warnings.push(
195
+ projectOverrideWarning(
196
+ projectPath,
197
+ "runtime.requirePlanApproval",
198
+ ),
199
+ );
145
200
  }
146
- sanitized.runtime = Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
201
+ sanitized.runtime = Object.values(runtime).some(
202
+ (entry) => entry !== undefined,
203
+ )
204
+ ? runtime
205
+ : undefined;
147
206
  }
148
207
  if (config.autonomous) {
149
208
  const autonomous = { ...config.autonomous };
150
- for (const key of ["profile", "enabled", "injectPolicy", "preferAsyncForLongTasks", "allowWorktreeSuggestion"] as const) {
209
+ for (const key of [
210
+ "profile",
211
+ "enabled",
212
+ "injectPolicy",
213
+ "preferAsyncForLongTasks",
214
+ "allowWorktreeSuggestion",
215
+ ] as const) {
151
216
  if (autonomous[key] !== undefined) {
152
217
  delete autonomous[key];
153
- warnings.push(projectOverrideWarning(projectPath, `autonomous.${key}`));
218
+ warnings.push(
219
+ projectOverrideWarning(projectPath, `autonomous.${key}`),
220
+ );
154
221
  }
155
222
  }
156
- sanitized.autonomous = Object.values(autonomous).some((entry) => entry !== undefined) ? autonomous : undefined;
223
+ sanitized.autonomous = Object.values(autonomous).some(
224
+ (entry) => entry !== undefined,
225
+ )
226
+ ? autonomous
227
+ : undefined;
157
228
  }
158
229
  if (config.worktree?.setupHook !== undefined) {
159
230
  sanitized.worktree = { ...config.worktree, setupHook: undefined };
160
- if (!Object.values(sanitized.worktree).some((entry) => entry !== undefined)) sanitized.worktree = undefined;
161
- warnings.push(projectOverrideWarning(projectPath, "worktree.setupHook"));
231
+ if (
232
+ !Object.values(sanitized.worktree).some(
233
+ (entry) => entry !== undefined,
234
+ )
235
+ )
236
+ sanitized.worktree = undefined;
237
+ warnings.push(
238
+ projectOverrideWarning(projectPath, "worktree.setupHook"),
239
+ );
162
240
  }
163
241
  if (config.otlp?.headers !== undefined) {
164
242
  sanitized.otlp = { ...config.otlp, headers: undefined };
165
- if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined)) sanitized.otlp = undefined;
243
+ if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined))
244
+ sanitized.otlp = undefined;
166
245
  warnings.push(projectOverrideWarning(projectPath, "otlp.headers"));
167
246
  }
168
- if (config.agents?.disableBuiltins !== undefined || config.agents?.overrides !== undefined) {
247
+ if (
248
+ config.agents?.disableBuiltins !== undefined ||
249
+ config.agents?.overrides !== undefined
250
+ ) {
169
251
  const agents = { ...config.agents };
170
252
  if (agents.disableBuiltins !== undefined) {
171
253
  delete agents.disableBuiltins;
172
- warnings.push(projectOverrideWarning(projectPath, "agents.disableBuiltins"));
254
+ warnings.push(
255
+ projectOverrideWarning(projectPath, "agents.disableBuiltins"),
256
+ );
173
257
  }
174
258
  if (agents.overrides !== undefined) {
175
259
  delete agents.overrides;
176
- warnings.push(projectOverrideWarning(projectPath, "agents.overrides"));
260
+ warnings.push(
261
+ projectOverrideWarning(projectPath, "agents.overrides"),
262
+ );
177
263
  }
178
- sanitized.agents = Object.values(agents).some((entry) => entry !== undefined) ? agents : undefined;
264
+ sanitized.agents = Object.values(agents).some(
265
+ (entry) => entry !== undefined,
266
+ )
267
+ ? agents
268
+ : undefined;
179
269
  }
180
- if (config.tools?.enableSteer !== undefined || config.tools?.terminateOnForeground !== undefined) {
270
+ if (
271
+ config.tools?.enableSteer !== undefined ||
272
+ config.tools?.terminateOnForeground !== undefined
273
+ ) {
181
274
  const tools = { ...config.tools };
182
275
  if (tools.enableSteer !== undefined) {
183
276
  delete tools.enableSteer;
184
- warnings.push(projectOverrideWarning(projectPath, "tools.enableSteer"));
277
+ warnings.push(
278
+ projectOverrideWarning(projectPath, "tools.enableSteer"),
279
+ );
185
280
  }
186
281
  if (tools.terminateOnForeground !== undefined) {
187
282
  delete tools.terminateOnForeground;
188
- warnings.push(projectOverrideWarning(projectPath, "tools.terminateOnForeground"));
283
+ warnings.push(
284
+ projectOverrideWarning(
285
+ projectPath,
286
+ "tools.terminateOnForeground",
287
+ ),
288
+ );
189
289
  }
190
- sanitized.tools = Object.values(tools).some((entry) => entry !== undefined) ? tools : undefined;
290
+ sanitized.tools = Object.values(tools).some(
291
+ (entry) => entry !== undefined,
292
+ )
293
+ ? tools
294
+ : undefined;
191
295
  }
192
296
  return { config: sanitized, warnings };
193
297
  }
194
298
 
195
- function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfig {
196
- const merged: PiTeamsConfig = { ...base, ...withoutUndefined(override as Record<string, unknown>) };
299
+ function mergeConfig(
300
+ base: PiTeamsConfig,
301
+ override: PiTeamsConfig,
302
+ ): PiTeamsConfig {
303
+ const merged: PiTeamsConfig = {
304
+ ...base,
305
+ ...withoutUndefined(override as Record<string, unknown>),
306
+ };
197
307
  if (base.autonomous || override.autonomous) {
198
308
  merged.autonomous = {
199
309
  ...(base.autonomous ?? {}),
200
- ...withoutUndefined((override.autonomous ?? {}) as Record<string, unknown>),
310
+ ...withoutUndefined(
311
+ (override.autonomous ?? {}) as Record<string, unknown>,
312
+ ),
201
313
  };
202
314
  }
203
315
  if (base.limits || override.limits) {
204
316
  merged.limits = {
205
317
  ...(base.limits ?? {}),
206
- ...withoutUndefined((override.limits ?? {}) as Record<string, unknown>),
318
+ ...withoutUndefined(
319
+ (override.limits ?? {}) as Record<string, unknown>,
320
+ ),
207
321
  };
208
322
  }
209
323
  if (base.runtime || override.runtime) {
210
324
  merged.runtime = {
211
325
  ...(base.runtime ?? {}),
212
- ...withoutUndefined((override.runtime ?? {}) as Record<string, unknown>),
326
+ ...withoutUndefined(
327
+ (override.runtime ?? {}) as Record<string, unknown>,
328
+ ),
213
329
  };
214
330
  }
215
331
  if (base.control || override.control) {
216
332
  merged.control = {
217
333
  ...(base.control ?? {}),
218
- ...withoutUndefined((override.control ?? {}) as Record<string, unknown>),
334
+ ...withoutUndefined(
335
+ (override.control ?? {}) as Record<string, unknown>,
336
+ ),
219
337
  };
220
338
  }
221
339
  if (base.worktree || override.worktree) {
222
340
  merged.worktree = {
223
341
  ...(base.worktree ?? {}),
224
- ...withoutUndefined((override.worktree ?? {}) as Record<string, unknown>),
342
+ ...withoutUndefined(
343
+ (override.worktree ?? {}) as Record<string, unknown>,
344
+ ),
225
345
  };
226
346
  }
227
347
  if (base.ui || override.ui) {
@@ -233,59 +353,98 @@ function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfi
233
353
  if (base.agents || override.agents) {
234
354
  merged.agents = {
235
355
  ...(base.agents ?? {}),
236
- ...withoutUndefined((override.agents ?? {}) as Record<string, unknown>),
356
+ ...withoutUndefined(
357
+ (override.agents ?? {}) as Record<string, unknown>,
358
+ ),
237
359
  overrides: {
238
360
  ...(base.agents?.overrides ?? {}),
239
- ...withoutUndefined((override.agents?.overrides ?? {}) as Record<string, unknown>) as Record<string, AgentOverrideConfig>,
361
+ ...(withoutUndefined(
362
+ (override.agents?.overrides ?? {}) as Record<
363
+ string,
364
+ unknown
365
+ >,
366
+ ) as Record<string, AgentOverrideConfig>),
240
367
  },
241
368
  };
242
369
  }
243
370
  if (base.tools || override.tools) {
244
371
  merged.tools = {
245
372
  ...(base.tools ?? {}),
246
- ...withoutUndefined((override.tools ?? {}) as Record<string, unknown>),
373
+ ...withoutUndefined(
374
+ (override.tools ?? {}) as Record<string, unknown>,
375
+ ),
247
376
  };
248
377
  }
249
378
  if (base.telemetry || override.telemetry) {
250
379
  merged.telemetry = {
251
380
  ...(base.telemetry ?? {}),
252
- ...withoutUndefined((override.telemetry ?? {}) as Record<string, unknown>),
381
+ ...withoutUndefined(
382
+ (override.telemetry ?? {}) as Record<string, unknown>,
383
+ ),
253
384
  };
254
385
  }
255
386
  if (base.policy || override.policy) {
256
387
  merged.policy = {
257
388
  ...(base.policy ?? {}),
258
- ...withoutUndefined((override.policy ?? {}) as Record<string, unknown>),
389
+ ...withoutUndefined(
390
+ (override.policy ?? {}) as Record<string, unknown>,
391
+ ),
259
392
  };
260
393
  }
261
394
  if (base.notifications || override.notifications) {
262
395
  merged.notifications = {
263
396
  ...(base.notifications ?? {}),
264
- ...withoutUndefined((override.notifications ?? {}) as Record<string, unknown>),
397
+ ...withoutUndefined(
398
+ (override.notifications ?? {}) as Record<string, unknown>,
399
+ ),
265
400
  };
266
401
  }
267
402
  if (base.observability || override.observability) {
268
403
  merged.observability = {
269
404
  ...(base.observability ?? {}),
270
- ...withoutUndefined((override.observability ?? {}) as Record<string, unknown>),
405
+ ...withoutUndefined(
406
+ (override.observability ?? {}) as Record<string, unknown>,
407
+ ),
271
408
  };
272
409
  }
273
410
  if (base.reliability || override.reliability) {
274
411
  merged.reliability = {
275
412
  ...(base.reliability ?? {}),
276
- ...withoutUndefined((override.reliability ?? {}) as Record<string, unknown>),
277
- retryPolicy: base.reliability?.retryPolicy || override.reliability?.retryPolicy ? { ...(base.reliability?.retryPolicy ?? {}), ...withoutUndefined((override.reliability?.retryPolicy ?? {}) as Record<string, unknown>) } : undefined,
413
+ ...withoutUndefined(
414
+ (override.reliability ?? {}) as Record<string, unknown>,
415
+ ),
416
+ retryPolicy:
417
+ base.reliability?.retryPolicy ||
418
+ override.reliability?.retryPolicy
419
+ ? {
420
+ ...(base.reliability?.retryPolicy ?? {}),
421
+ ...withoutUndefined(
422
+ (override.reliability?.retryPolicy ??
423
+ {}) as Record<string, unknown>,
424
+ ),
425
+ }
426
+ : undefined,
278
427
  };
279
428
  }
280
429
  if (base.otlp || override.otlp) {
281
430
  merged.otlp = {
282
431
  ...(base.otlp ?? {}),
283
- ...withoutUndefined((override.otlp ?? {}) as Record<string, unknown>),
284
- headers: { ...(base.otlp?.headers ?? {}), ...(override.otlp?.headers ?? {}) },
432
+ ...withoutUndefined(
433
+ (override.otlp ?? {}) as Record<string, unknown>,
434
+ ),
435
+ headers: {
436
+ ...(base.otlp?.headers ?? {}),
437
+ ...(override.otlp?.headers ?? {}),
438
+ },
285
439
  };
286
- if (Object.keys(merged.otlp.headers ?? {}).length === 0) delete merged.otlp.headers;
440
+ if (Object.keys(merged.otlp.headers ?? {}).length === 0)
441
+ delete merged.otlp.headers;
287
442
  }
288
- if (merged.agents?.overrides && Object.keys(merged.agents.overrides).length === 0) delete merged.agents.overrides;
443
+ if (
444
+ merged.agents?.overrides &&
445
+ Object.keys(merged.agents.overrides).length === 0
446
+ )
447
+ delete merged.agents.overrides;
289
448
  return merged;
290
449
  }
291
450
 
@@ -302,20 +461,31 @@ const LIMIT_CEILINGS = {
302
461
  } as const;
303
462
 
304
463
  function asRecord(value: unknown): Record<string, unknown> | undefined {
305
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
464
+ if (!value || typeof value !== "object" || Array.isArray(value))
465
+ return undefined;
306
466
  return value as Record<string, unknown>;
307
467
  }
308
468
 
309
- function parseWithSchema<T extends TSchema>(schema: T, value: unknown): Static<T> | undefined {
469
+ function parseWithSchema<T extends TSchema>(
470
+ schema: T,
471
+ value: unknown,
472
+ ): Static<T> | undefined {
310
473
  if (!Value.Check(schema, value)) return undefined;
311
474
  return Value.Decode(schema, value);
312
475
  }
313
476
 
314
- function parseIntegerInRange(value: unknown, minimum = 1, maximum = Number.MAX_SAFE_INTEGER): number | undefined {
477
+ function parseIntegerInRange(
478
+ value: unknown,
479
+ minimum = 1,
480
+ maximum = Number.MAX_SAFE_INTEGER,
481
+ ): number | undefined {
315
482
  return parseWithSchema(Type.Integer({ minimum, maximum }), value);
316
483
  }
317
484
 
318
- function parsePositiveInteger(value: unknown, max = Number.MAX_SAFE_INTEGER): number | undefined {
485
+ function parsePositiveInteger(
486
+ value: unknown,
487
+ max = Number.MAX_SAFE_INTEGER,
488
+ ): number | undefined {
319
489
  return parseIntegerInRange(value, 1, max);
320
490
  }
321
491
 
@@ -326,37 +496,88 @@ function parseProfile(value: unknown): PiTeamsAutonomyProfile | undefined {
326
496
  function parseStringList(value: unknown): string[] | undefined {
327
497
  const items = parseWithSchema(Type.Array(Type.String()), value);
328
498
  if (!items || items.length === 0) return undefined;
329
- const normalized = items.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
499
+ const normalized = items
500
+ .map((entry) => entry.trim())
501
+ .filter((entry) => entry.length > 0);
330
502
  return normalized.length > 0 ? normalized : undefined;
331
503
  }
332
504
 
333
505
  function parseStringArrayOrFalse(value: unknown): string[] | false | undefined {
334
506
  if (value === false) return false;
335
- if (typeof value === "string") return value.trim() === "" ? [] : parseStringList(value.split(","));
507
+ if (typeof value === "string")
508
+ return value.trim() === "" ? [] : parseStringList(value.split(","));
336
509
  return parseStringList(value);
337
510
  }
338
511
 
339
- export function effectiveAutonomousConfig(config: PiTeamsAutonomousConfig | undefined): Required<Pick<PiTeamsAutonomousConfig, "profile" | "enabled" | "injectPolicy" | "preferAsyncForLongTasks" | "allowWorktreeSuggestion">> & Pick<PiTeamsAutonomousConfig, "magicKeywords"> {
340
- const profile = config?.enabled === false ? "manual" : (config?.profile ?? "suggested");
341
- const profileDefaults: Record<PiTeamsAutonomyProfile, { enabled: boolean; injectPolicy: boolean; preferAsyncForLongTasks: boolean; allowWorktreeSuggestion: boolean }> = {
342
- manual: { enabled: false, injectPolicy: false, preferAsyncForLongTasks: false, allowWorktreeSuggestion: false },
343
- suggested: { enabled: true, injectPolicy: true, preferAsyncForLongTasks: false, allowWorktreeSuggestion: true },
344
- assisted: { enabled: true, injectPolicy: true, preferAsyncForLongTasks: true, allowWorktreeSuggestion: true },
345
- aggressive: { enabled: true, injectPolicy: true, preferAsyncForLongTasks: true, allowWorktreeSuggestion: true },
512
+ export function effectiveAutonomousConfig(
513
+ config: PiTeamsAutonomousConfig | undefined,
514
+ ): Required<
515
+ Pick<
516
+ PiTeamsAutonomousConfig,
517
+ | "profile"
518
+ | "enabled"
519
+ | "injectPolicy"
520
+ | "preferAsyncForLongTasks"
521
+ | "allowWorktreeSuggestion"
522
+ >
523
+ > &
524
+ Pick<PiTeamsAutonomousConfig, "magicKeywords"> {
525
+ const profile =
526
+ config?.enabled === false ? "manual" : (config?.profile ?? "suggested");
527
+ const profileDefaults: Record<
528
+ PiTeamsAutonomyProfile,
529
+ {
530
+ enabled: boolean;
531
+ injectPolicy: boolean;
532
+ preferAsyncForLongTasks: boolean;
533
+ allowWorktreeSuggestion: boolean;
534
+ }
535
+ > = {
536
+ manual: {
537
+ enabled: false,
538
+ injectPolicy: false,
539
+ preferAsyncForLongTasks: false,
540
+ allowWorktreeSuggestion: false,
541
+ },
542
+ suggested: {
543
+ enabled: true,
544
+ injectPolicy: true,
545
+ preferAsyncForLongTasks: false,
546
+ allowWorktreeSuggestion: true,
547
+ },
548
+ assisted: {
549
+ enabled: true,
550
+ injectPolicy: true,
551
+ preferAsyncForLongTasks: true,
552
+ allowWorktreeSuggestion: true,
553
+ },
554
+ aggressive: {
555
+ enabled: true,
556
+ injectPolicy: true,
557
+ preferAsyncForLongTasks: true,
558
+ allowWorktreeSuggestion: true,
559
+ },
346
560
  };
347
561
  const defaults = profileDefaults[profile];
348
562
  return {
349
563
  profile,
350
564
  enabled: config?.enabled ?? defaults.enabled,
351
565
  injectPolicy: config?.injectPolicy ?? defaults.injectPolicy,
352
- preferAsyncForLongTasks: config?.preferAsyncForLongTasks ?? defaults.preferAsyncForLongTasks,
353
- allowWorktreeSuggestion: config?.allowWorktreeSuggestion ?? defaults.allowWorktreeSuggestion,
566
+ preferAsyncForLongTasks:
567
+ config?.preferAsyncForLongTasks ?? defaults.preferAsyncForLongTasks,
568
+ allowWorktreeSuggestion:
569
+ config?.allowWorktreeSuggestion ?? defaults.allowWorktreeSuggestion,
354
570
  magicKeywords: config?.magicKeywords,
355
571
  };
356
572
  }
357
573
 
358
- function parseStringArrayRecord(value: unknown): Record<string, string[]> | undefined {
359
- const record = parseWithSchema(Type.Record(Type.String({ minLength: 1 }), Type.Array(Type.String())), value);
574
+ function parseStringArrayRecord(
575
+ value: unknown,
576
+ ): Record<string, string[]> | undefined {
577
+ const record = parseWithSchema(
578
+ Type.Record(Type.String({ minLength: 1 }), Type.Array(Type.String())),
579
+ value,
580
+ );
360
581
  if (!record) return undefined;
361
582
  const result: Record<string, string[]> = {};
362
583
  for (const [key, rawValues] of Object.entries(record)) {
@@ -366,42 +587,87 @@ function parseStringArrayRecord(value: unknown): Record<string, string[]> | unde
366
587
  return Object.keys(result).length > 0 ? result : undefined;
367
588
  }
368
589
 
369
- function parseAutonomousConfig(value: unknown): PiTeamsAutonomousConfig | undefined {
590
+ function parseAutonomousConfig(
591
+ value: unknown,
592
+ ): PiTeamsAutonomousConfig | undefined {
370
593
  const obj = asRecord(value);
371
594
  if (!obj) return undefined;
372
595
  const config: PiTeamsAutonomousConfig = {
373
596
  profile: parseProfile(obj.profile),
374
597
  enabled: parseWithSchema(Type.Boolean(), obj.enabled),
375
598
  injectPolicy: parseWithSchema(Type.Boolean(), obj.injectPolicy),
376
- preferAsyncForLongTasks: parseWithSchema(Type.Boolean(), obj.preferAsyncForLongTasks),
377
- allowWorktreeSuggestion: parseWithSchema(Type.Boolean(), obj.allowWorktreeSuggestion),
599
+ preferAsyncForLongTasks: parseWithSchema(
600
+ Type.Boolean(),
601
+ obj.preferAsyncForLongTasks,
602
+ ),
603
+ allowWorktreeSuggestion: parseWithSchema(
604
+ Type.Boolean(),
605
+ obj.allowWorktreeSuggestion,
606
+ ),
378
607
  magicKeywords: parseStringArrayRecord(obj.magicKeywords),
379
608
  };
380
- return Object.values(config).some((entry) => entry !== undefined) ? config : undefined;
609
+ return Object.values(config).some((entry) => entry !== undefined)
610
+ ? config
611
+ : undefined;
381
612
  }
382
613
 
383
614
  function parseLimitsConfig(value: unknown): CrewLimitsConfig | undefined {
384
615
  const obj = asRecord(value);
385
616
  if (!obj) return undefined;
386
617
  const limits: CrewLimitsConfig = {
387
- maxConcurrentWorkers: parsePositiveInteger(obj.maxConcurrentWorkers, LIMIT_CEILINGS.maxConcurrentWorkers),
388
- allowUnboundedConcurrency: parseWithSchema(Type.Boolean(), obj.allowUnboundedConcurrency),
389
- maxTaskDepth: parsePositiveInteger(obj.maxTaskDepth, LIMIT_CEILINGS.maxTaskDepth),
390
- maxChildrenPerTask: parsePositiveInteger(obj.maxChildrenPerTask, LIMIT_CEILINGS.maxChildrenPerTask),
391
- maxRunMinutes: parsePositiveInteger(obj.maxRunMinutes, LIMIT_CEILINGS.maxRunMinutes),
392
- maxRetriesPerTask: parsePositiveInteger(obj.maxRetriesPerTask, LIMIT_CEILINGS.maxRetriesPerTask),
393
- maxTasksPerRun: parsePositiveInteger(obj.maxTasksPerRun, LIMIT_CEILINGS.maxTasksPerRun),
394
- heartbeatStaleMs: parsePositiveInteger(obj.heartbeatStaleMs, LIMIT_CEILINGS.heartbeatStaleMs),
618
+ maxConcurrentWorkers: parsePositiveInteger(
619
+ obj.maxConcurrentWorkers,
620
+ LIMIT_CEILINGS.maxConcurrentWorkers,
621
+ ),
622
+ allowUnboundedConcurrency: parseWithSchema(
623
+ Type.Boolean(),
624
+ obj.allowUnboundedConcurrency,
625
+ ),
626
+ maxTaskDepth: parsePositiveInteger(
627
+ obj.maxTaskDepth,
628
+ LIMIT_CEILINGS.maxTaskDepth,
629
+ ),
630
+ maxChildrenPerTask: parsePositiveInteger(
631
+ obj.maxChildrenPerTask,
632
+ LIMIT_CEILINGS.maxChildrenPerTask,
633
+ ),
634
+ maxRunMinutes: parsePositiveInteger(
635
+ obj.maxRunMinutes,
636
+ LIMIT_CEILINGS.maxRunMinutes,
637
+ ),
638
+ maxRetriesPerTask: parsePositiveInteger(
639
+ obj.maxRetriesPerTask,
640
+ LIMIT_CEILINGS.maxRetriesPerTask,
641
+ ),
642
+ maxTasksPerRun: parsePositiveInteger(
643
+ obj.maxTasksPerRun,
644
+ LIMIT_CEILINGS.maxTasksPerRun,
645
+ ),
646
+ heartbeatStaleMs: parsePositiveInteger(
647
+ obj.heartbeatStaleMs,
648
+ LIMIT_CEILINGS.heartbeatStaleMs,
649
+ ),
395
650
  };
396
- return Object.values(limits).some((entry) => entry !== undefined) ? limits : undefined;
651
+ return Object.values(limits).some((entry) => entry !== undefined)
652
+ ? limits
653
+ : undefined;
397
654
  }
398
655
 
399
- function parseIsolationPolicy(value: unknown): CrewRuntimeConfig["isolationPolicy"] | undefined {
656
+ function parseIsolationPolicy(
657
+ value: unknown,
658
+ ): CrewRuntimeConfig["isolationPolicy"] | undefined {
400
659
  const obj = asRecord(value);
401
660
  if (!obj) return undefined;
402
661
  const isolatedRoles = parseStringList(obj.isolatedRoles);
403
- const defaultRuntime = parseWithSchema(Type.Union([Type.Literal("live-session"), Type.Literal("child-process")]), obj.defaultRuntime);
404
- if (isolatedRoles === undefined && defaultRuntime === undefined) return undefined;
662
+ const defaultRuntime = parseWithSchema(
663
+ Type.Union([
664
+ Type.Literal("live-session"),
665
+ Type.Literal("child-process"),
666
+ ]),
667
+ obj.defaultRuntime,
668
+ );
669
+ if (isolatedRoles === undefined && defaultRuntime === undefined)
670
+ return undefined;
405
671
  return {
406
672
  ...(isolatedRoles !== undefined ? { isolatedRoles } : {}),
407
673
  ...(defaultRuntime !== undefined ? { defaultRuntime } : {}),
@@ -412,21 +678,74 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
412
678
  const obj = asRecord(value);
413
679
  if (!obj) return undefined;
414
680
  const runtime: CrewRuntimeConfig = {
415
- mode: parseWithSchema(Type.Union([Type.Literal("auto"), Type.Literal("scaffold"), Type.Literal("child-process"), Type.Literal("live-session")]), obj.mode),
416
- preferLiveSession: parseWithSchema(Type.Boolean(), obj.preferLiveSession),
417
- allowChildProcessFallback: parseWithSchema(Type.Boolean(), obj.allowChildProcessFallback),
418
- maxTurns: parsePositiveInteger(obj.maxTurns, LIMIT_CEILINGS.runtimeMaxTurns),
419
- graceTurns: parsePositiveInteger(obj.graceTurns, LIMIT_CEILINGS.runtimeGraceTurns),
681
+ mode: parseWithSchema(
682
+ Type.Union([
683
+ Type.Literal("auto"),
684
+ Type.Literal("scaffold"),
685
+ Type.Literal("child-process"),
686
+ Type.Literal("live-session"),
687
+ ]),
688
+ obj.mode,
689
+ ),
690
+ preferLiveSession: parseWithSchema(
691
+ Type.Boolean(),
692
+ obj.preferLiveSession,
693
+ ),
694
+ allowChildProcessFallback: parseWithSchema(
695
+ Type.Boolean(),
696
+ obj.allowChildProcessFallback,
697
+ ),
698
+ maxTurns: parsePositiveInteger(
699
+ obj.maxTurns,
700
+ LIMIT_CEILINGS.runtimeMaxTurns,
701
+ ),
702
+ graceTurns: parsePositiveInteger(
703
+ obj.graceTurns,
704
+ LIMIT_CEILINGS.runtimeGraceTurns,
705
+ ),
420
706
  inheritContext: parseWithSchema(Type.Boolean(), obj.inheritContext),
421
- promptMode: parseWithSchema(Type.Union([Type.Literal("replace"), Type.Literal("append")]), obj.promptMode),
422
- groupJoin: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("group"), Type.Literal("smart")]), obj.groupJoin),
423
- groupJoinAckTimeoutMs: parsePositiveInteger(obj.groupJoinAckTimeoutMs, 86_400_000),
424
- requirePlanApproval: parseWithSchema(Type.Boolean(), obj.requirePlanApproval),
425
- completionMutationGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")]), obj.completionMutationGuard),
426
- effectivenessGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]), obj.effectivenessGuard),
707
+ promptMode: parseWithSchema(
708
+ Type.Union([Type.Literal("replace"), Type.Literal("append")]),
709
+ obj.promptMode,
710
+ ),
711
+ groupJoin: parseWithSchema(
712
+ Type.Union([
713
+ Type.Literal("off"),
714
+ Type.Literal("group"),
715
+ Type.Literal("smart"),
716
+ ]),
717
+ obj.groupJoin,
718
+ ),
719
+ groupJoinAckTimeoutMs: parsePositiveInteger(
720
+ obj.groupJoinAckTimeoutMs,
721
+ 86_400_000,
722
+ ),
723
+ requirePlanApproval: parseWithSchema(
724
+ Type.Boolean(),
725
+ obj.requirePlanApproval,
726
+ ),
727
+ completionMutationGuard: parseWithSchema(
728
+ Type.Union([
729
+ Type.Literal("off"),
730
+ Type.Literal("warn"),
731
+ Type.Literal("fail"),
732
+ ]),
733
+ obj.completionMutationGuard,
734
+ ),
735
+ effectivenessGuard: parseWithSchema(
736
+ Type.Union([
737
+ Type.Literal("off"),
738
+ Type.Literal("warn"),
739
+ Type.Literal("block"),
740
+ Type.Literal("fail"),
741
+ ]),
742
+ obj.effectivenessGuard,
743
+ ),
427
744
  isolationPolicy: parseIsolationPolicy(obj.isolationPolicy),
428
745
  };
429
- return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
746
+ return Object.values(runtime).some((entry) => entry !== undefined)
747
+ ? runtime
748
+ : undefined;
430
749
  }
431
750
 
432
751
  function parseControlConfig(value: unknown): CrewControlConfig | undefined {
@@ -436,7 +755,9 @@ function parseControlConfig(value: unknown): CrewControlConfig | undefined {
436
755
  enabled: parseWithSchema(Type.Boolean(), obj.enabled),
437
756
  needsAttentionAfterMs: parsePositiveInteger(obj.needsAttentionAfterMs),
438
757
  };
439
- return Object.values(control).some((entry) => entry !== undefined) ? control : undefined;
758
+ return Object.values(control).some((entry) => entry !== undefined)
759
+ ? control
760
+ : undefined;
440
761
  }
441
762
 
442
763
  function parseWorktreeConfig(value: unknown): CrewWorktreeConfig | undefined {
@@ -446,10 +767,15 @@ function parseWorktreeConfig(value: unknown): CrewWorktreeConfig | undefined {
446
767
  const setupHook = rawSetupHook?.trim();
447
768
  const worktree: CrewWorktreeConfig = {
448
769
  setupHook: setupHook ? setupHook : undefined,
449
- setupHookTimeoutMs: parsePositiveInteger(obj.setupHookTimeoutMs, 300_000),
770
+ setupHookTimeoutMs: parsePositiveInteger(
771
+ obj.setupHookTimeoutMs,
772
+ 300_000,
773
+ ),
450
774
  linkNodeModules: parseWithSchema(Type.Boolean(), obj.linkNodeModules),
451
775
  };
452
- return Object.values(worktree).some((entry) => entry !== undefined) ? worktree : undefined;
776
+ return Object.values(worktree).some((entry) => entry !== undefined)
777
+ ? worktree
778
+ : undefined;
453
779
  }
454
780
 
455
781
  function parseAgentOverride(value: unknown): AgentOverrideConfig | undefined {
@@ -457,45 +783,97 @@ function parseAgentOverride(value: unknown): AgentOverrideConfig | undefined {
457
783
  if (!obj) return undefined;
458
784
  const override: AgentOverrideConfig = {
459
785
  disabled: parseWithSchema(Type.Boolean(), obj.disabled),
460
- model: parseWithSchema(Type.Union([Type.String(), Type.Literal(false)]), obj.model),
786
+ model: parseWithSchema(
787
+ Type.Union([Type.String(), Type.Literal(false)]),
788
+ obj.model,
789
+ ),
461
790
  fallbackModels: parseStringArrayOrFalse(obj.fallbackModels),
462
- thinking: parseWithSchema(Type.Union([Type.String(), Type.Literal(false)]), obj.thinking),
791
+ thinking: parseWithSchema(
792
+ Type.Union([Type.String(), Type.Literal(false)]),
793
+ obj.thinking,
794
+ ),
463
795
  tools: parseStringArrayOrFalse(obj.tools),
464
796
  skills: parseStringArrayOrFalse(obj.skills),
465
797
  };
466
- return Object.values(override).some((entry) => entry !== undefined) ? override : undefined;
798
+ return Object.values(override).some((entry) => entry !== undefined)
799
+ ? override
800
+ : undefined;
467
801
  }
468
802
 
469
803
  function parseUiConfig(value: unknown): CrewUiConfig | undefined {
470
804
  const obj = asRecord(value);
471
805
  if (!obj) return undefined;
472
- const rawWidgetPlacement = parseWithSchema(Type.Union([Type.Literal("aboveEditor"), Type.Literal("belowEditor")]), obj.widgetPlacement);
473
- const rawDashboardPlacement = parseWithSchema(Type.Union([Type.Literal("center"), Type.Literal("right")]), obj.dashboardPlacement);
806
+ const rawWidgetPlacement = parseWithSchema(
807
+ Type.Union([Type.Literal("aboveEditor"), Type.Literal("belowEditor")]),
808
+ obj.widgetPlacement,
809
+ );
810
+ const rawDashboardPlacement = parseWithSchema(
811
+ Type.Union([Type.Literal("center"), Type.Literal("right")]),
812
+ obj.dashboardPlacement,
813
+ );
474
814
  const ui: CrewUiConfig = {
475
815
  widgetPlacement: rawWidgetPlacement,
476
816
  widgetMaxLines: parsePositiveInteger(obj.widgetMaxLines, 50),
477
817
  powerbar: parseWithSchema(Type.Boolean(), obj.powerbar),
478
818
  dashboardPlacement: rawDashboardPlacement,
479
819
  dashboardWidth: parseIntegerInRange(obj.dashboardWidth, 32, 120),
480
- dashboardLiveRefreshMs: parseIntegerInRange(obj.dashboardLiveRefreshMs, 250, 60_000),
481
- autoOpenDashboard: parseWithSchema(Type.Boolean(), obj.autoOpenDashboard),
482
- autoOpenDashboardForForegroundRuns: parseWithSchema(Type.Boolean(), obj.autoOpenDashboardForForegroundRuns),
820
+ dashboardLiveRefreshMs: parseIntegerInRange(
821
+ obj.dashboardLiveRefreshMs,
822
+ 250,
823
+ 60_000,
824
+ ),
825
+ autoOpenDashboard: parseWithSchema(
826
+ Type.Boolean(),
827
+ obj.autoOpenDashboard,
828
+ ),
829
+ autoOpenDashboardForForegroundRuns: parseWithSchema(
830
+ Type.Boolean(),
831
+ obj.autoOpenDashboardForForegroundRuns,
832
+ ),
483
833
  showModel: parseWithSchema(Type.Boolean(), obj.showModel),
484
834
  showTokens: parseWithSchema(Type.Boolean(), obj.showTokens),
485
835
  showTools: parseWithSchema(Type.Boolean(), obj.showTools),
486
- transcriptTailBytes: parseIntegerInRange(obj.transcriptTailBytes, 1024, 50 * 1024 * 1024),
487
- mascotStyle: parseWithSchema(Type.Union([Type.Literal("cat"), Type.Literal("armin")]), obj.mascotStyle),
488
- mascotEffect: parseWithSchema(Type.Union([Type.Literal("random"), Type.Literal("none"), Type.Literal("typewriter"), Type.Literal("scanline"), Type.Literal("rain"), Type.Literal("fade"), Type.Literal("crt"), Type.Literal("glitch"), Type.Literal("dissolve")]), obj.mascotEffect),
836
+ transcriptTailBytes: parseIntegerInRange(
837
+ obj.transcriptTailBytes,
838
+ 1024,
839
+ 50 * 1024 * 1024,
840
+ ),
841
+ mascotStyle: parseWithSchema(
842
+ Type.Union([Type.Literal("cat"), Type.Literal("armin")]),
843
+ obj.mascotStyle,
844
+ ),
845
+ mascotEffect: parseWithSchema(
846
+ Type.Union([
847
+ Type.Literal("random"),
848
+ Type.Literal("none"),
849
+ Type.Literal("typewriter"),
850
+ Type.Literal("scanline"),
851
+ Type.Literal("rain"),
852
+ Type.Literal("fade"),
853
+ Type.Literal("crt"),
854
+ Type.Literal("glitch"),
855
+ Type.Literal("dissolve"),
856
+ ]),
857
+ obj.mascotEffect,
858
+ ),
489
859
  };
490
- return Object.values(ui).some((entry) => entry !== undefined) ? ui : undefined;
860
+ return Object.values(ui).some((entry) => entry !== undefined)
861
+ ? ui
862
+ : undefined;
491
863
  }
492
864
 
493
865
  function parseAgentsConfig(value: unknown): CrewAgentsConfig | undefined {
494
866
  const obj = asRecord(value);
495
867
  if (!obj) return undefined;
496
868
  const overrides: Record<string, AgentOverrideConfig> = {};
497
- if (obj.overrides && typeof obj.overrides === "object" && !Array.isArray(obj.overrides)) {
498
- for (const [name, rawOverride] of Object.entries(obj.overrides as Record<string, unknown>)) {
869
+ if (
870
+ obj.overrides &&
871
+ typeof obj.overrides === "object" &&
872
+ !Array.isArray(obj.overrides)
873
+ ) {
874
+ for (const [name, rawOverride] of Object.entries(
875
+ obj.overrides as Record<string, unknown>,
876
+ )) {
499
877
  const parsed = parseAgentOverride(rawOverride);
500
878
  if (parsed && name.trim()) overrides[name.trim()] = parsed;
501
879
  }
@@ -504,18 +882,28 @@ function parseAgentsConfig(value: unknown): CrewAgentsConfig | undefined {
504
882
  disableBuiltins: parseWithSchema(Type.Boolean(), obj.disableBuiltins),
505
883
  overrides: Object.keys(overrides).length > 0 ? overrides : undefined,
506
884
  };
507
- return Object.values(agents).some((entry) => entry !== undefined) ? agents : undefined;
885
+ return Object.values(agents).some((entry) => entry !== undefined)
886
+ ? agents
887
+ : undefined;
508
888
  }
509
889
 
510
890
  function parseToolsConfig(value: unknown): CrewToolsConfig | undefined {
511
891
  const obj = asRecord(value);
512
892
  if (!obj) return undefined;
513
893
  const tools: CrewToolsConfig = {
514
- enableClaudeStyleAliases: parseWithSchema(Type.Boolean(), obj.enableClaudeStyleAliases),
894
+ enableClaudeStyleAliases: parseWithSchema(
895
+ Type.Boolean(),
896
+ obj.enableClaudeStyleAliases,
897
+ ),
515
898
  enableSteer: parseWithSchema(Type.Boolean(), obj.enableSteer),
516
- terminateOnForeground: parseWithSchema(Type.Boolean(), obj.terminateOnForeground),
899
+ terminateOnForeground: parseWithSchema(
900
+ Type.Boolean(),
901
+ obj.terminateOnForeground,
902
+ ),
517
903
  };
518
- return Object.values(tools).some((entry) => entry !== undefined) ? tools : undefined;
904
+ return Object.values(tools).some((entry) => entry !== undefined)
905
+ ? tools
906
+ : undefined;
519
907
  }
520
908
 
521
909
  function parseTelemetryConfig(value: unknown): CrewTelemetryConfig | undefined {
@@ -524,62 +912,125 @@ function parseTelemetryConfig(value: unknown): CrewTelemetryConfig | undefined {
524
912
  const telemetry: CrewTelemetryConfig = {
525
913
  enabled: parseWithSchema(Type.Boolean(), obj.enabled),
526
914
  };
527
- return Object.values(telemetry).some((entry) => entry !== undefined) ? telemetry : undefined;
915
+ return Object.values(telemetry).some((entry) => entry !== undefined)
916
+ ? telemetry
917
+ : undefined;
528
918
  }
529
919
 
530
920
  function parsePolicyConfig(value: unknown): CrewPolicyConfig | undefined {
531
921
  const obj = asRecord(value);
532
922
  if (!obj) return undefined;
533
923
  const policy: CrewPolicyConfig = {
534
- requireIntentForDestructiveActions: parseWithSchema(Type.Boolean(), obj.requireIntentForDestructiveActions),
535
- disabledCapabilities: parseWithSchema(Type.Array(Type.String()), obj.disabledCapabilities),
924
+ requireIntentForDestructiveActions: parseWithSchema(
925
+ Type.Boolean(),
926
+ obj.requireIntentForDestructiveActions,
927
+ ),
928
+ disabledCapabilities: parseWithSchema(
929
+ Type.Array(Type.String()),
930
+ obj.disabledCapabilities,
931
+ ),
536
932
  };
537
- return Object.values(policy).some((entry) => entry !== undefined) ? policy : undefined;
933
+ return Object.values(policy).some((entry) => entry !== undefined)
934
+ ? policy
935
+ : undefined;
538
936
  }
539
937
 
540
- function parseNotificationsConfig(value: unknown): CrewNotificationsConfig | undefined {
938
+ function parseNotificationsConfig(
939
+ value: unknown,
940
+ ): CrewNotificationsConfig | undefined {
541
941
  const obj = asRecord(value);
542
942
  if (!obj) return undefined;
543
943
  const notifications: CrewNotificationsConfig = {
544
944
  enabled: parseWithSchema(Type.Boolean(), obj.enabled),
545
- severityFilter: parseWithSchema(Type.Array(Type.Union([Type.Literal("info"), Type.Literal("warning"), Type.Literal("error"), Type.Literal("critical")])), obj.severityFilter),
546
- dedupWindowMs: parsePositiveInteger(obj.dedupWindowMs, 24 * 60 * 60 * 1000),
547
- batchWindowMs: parseWithSchema(Type.Integer({ minimum: 0, maximum: 60_000 }), obj.batchWindowMs),
548
- quietHours: parseWithSchema(Type.String({ pattern: "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$" }), obj.quietHours),
945
+ severityFilter: parseWithSchema(
946
+ Type.Array(
947
+ Type.Union([
948
+ Type.Literal("info"),
949
+ Type.Literal("warning"),
950
+ Type.Literal("error"),
951
+ Type.Literal("critical"),
952
+ ]),
953
+ ),
954
+ obj.severityFilter,
955
+ ),
956
+ dedupWindowMs: parsePositiveInteger(
957
+ obj.dedupWindowMs,
958
+ 24 * 60 * 60 * 1000,
959
+ ),
960
+ batchWindowMs: parseWithSchema(
961
+ Type.Integer({ minimum: 0, maximum: 60_000 }),
962
+ obj.batchWindowMs,
963
+ ),
964
+ quietHours: parseWithSchema(
965
+ Type.String({ pattern: "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$" }),
966
+ obj.quietHours,
967
+ ),
549
968
  sinkRetentionDays: parsePositiveInteger(obj.sinkRetentionDays, 90),
550
969
  };
551
- return Object.values(notifications).some((entry) => entry !== undefined) ? notifications : undefined;
970
+ return Object.values(notifications).some((entry) => entry !== undefined)
971
+ ? notifications
972
+ : undefined;
552
973
  }
553
974
 
554
- function parseObservabilityConfig(value: unknown): CrewObservabilityConfig | undefined {
975
+ function parseObservabilityConfig(
976
+ value: unknown,
977
+ ): CrewObservabilityConfig | undefined {
555
978
  const obj = asRecord(value);
556
979
  if (!obj) return undefined;
557
980
  const observability: CrewObservabilityConfig = {
558
981
  enabled: parseWithSchema(Type.Boolean(), obj.enabled),
559
- pollIntervalMs: parseWithSchema(Type.Integer({ minimum: 1000, maximum: 60_000 }), obj.pollIntervalMs),
982
+ pollIntervalMs: parseWithSchema(
983
+ Type.Integer({ minimum: 1000, maximum: 60_000 }),
984
+ obj.pollIntervalMs,
985
+ ),
560
986
  metricRetentionDays: parsePositiveInteger(obj.metricRetentionDays, 365),
561
987
  };
562
- return Object.values(observability).some((entry) => entry !== undefined) ? observability : undefined;
988
+ return Object.values(observability).some((entry) => entry !== undefined)
989
+ ? observability
990
+ : undefined;
563
991
  }
564
992
 
565
- function parseReliabilityConfig(value: unknown): CrewReliabilityConfig | undefined {
993
+ function parseReliabilityConfig(
994
+ value: unknown,
995
+ ): CrewReliabilityConfig | undefined {
566
996
  const obj = asRecord(value);
567
997
  if (!obj) return undefined;
568
998
  const retryObj = asRecord(obj.retryPolicy);
569
- const retryPolicy: CrewRetryPolicyConfig | undefined = retryObj ? {
570
- maxAttempts: parsePositiveInteger(retryObj.maxAttempts, 10),
571
- backoffMs: parseWithSchema(Type.Integer({ minimum: 100, maximum: 60_000 }), retryObj.backoffMs),
572
- jitterRatio: parseWithSchema(Type.Number({ minimum: 0, maximum: 1 }), retryObj.jitterRatio),
573
- exponentialFactor: parseWithSchema(Type.Number({ minimum: 1, maximum: 5 }), retryObj.exponentialFactor),
574
- retryableErrors: parseStringList(retryObj.retryableErrors),
575
- } : undefined;
999
+ const retryPolicy: CrewRetryPolicyConfig | undefined = retryObj
1000
+ ? {
1001
+ maxAttempts: parsePositiveInteger(retryObj.maxAttempts, 10),
1002
+ backoffMs: parseWithSchema(
1003
+ Type.Integer({ minimum: 100, maximum: 60_000 }),
1004
+ retryObj.backoffMs,
1005
+ ),
1006
+ jitterRatio: parseWithSchema(
1007
+ Type.Number({ minimum: 0, maximum: 1 }),
1008
+ retryObj.jitterRatio,
1009
+ ),
1010
+ exponentialFactor: parseWithSchema(
1011
+ Type.Number({ minimum: 1, maximum: 5 }),
1012
+ retryObj.exponentialFactor,
1013
+ ),
1014
+ retryableErrors: parseStringList(retryObj.retryableErrors),
1015
+ }
1016
+ : undefined;
576
1017
  const reliability: CrewReliabilityConfig = {
577
1018
  autoRetry: parseWithSchema(Type.Boolean(), obj.autoRetry),
578
- retryPolicy: retryPolicy && Object.values(retryPolicy).some((entry) => entry !== undefined) ? retryPolicy : undefined,
1019
+ retryPolicy:
1020
+ retryPolicy &&
1021
+ Object.values(retryPolicy).some((entry) => entry !== undefined)
1022
+ ? retryPolicy
1023
+ : undefined,
579
1024
  autoRecover: parseWithSchema(Type.Boolean(), obj.autoRecover),
580
1025
  deadletterThreshold: parsePositiveInteger(obj.deadletterThreshold),
1026
+ cleanupOrphanedTempDirs: parseWithSchema(
1027
+ Type.Boolean(),
1028
+ obj.cleanupOrphanedTempDirs,
1029
+ ),
581
1030
  };
582
- return Object.values(reliability).some((entry) => entry !== undefined) ? reliability : undefined;
1031
+ return Object.values(reliability).some((entry) => entry !== undefined)
1032
+ ? reliability
1033
+ : undefined;
583
1034
  }
584
1035
 
585
1036
  function parseOtlpConfig(value: unknown): CrewOtlpConfig | undefined {
@@ -587,19 +1038,30 @@ function parseOtlpConfig(value: unknown): CrewOtlpConfig | undefined {
587
1038
  if (!obj) return undefined;
588
1039
  const headers: Record<string, string> = Object.create(null);
589
1040
  const rawHeaders = asRecord(obj.headers);
590
- if (rawHeaders) for (const [key, entry] of Object.entries(rawHeaders)) {
591
- if (typeof entry !== "string") continue;
592
- // Prevent prototype pollution via __proto__ / constructor / prototype keys.
593
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
594
- headers[key] = entry;
595
- }
1041
+ if (rawHeaders)
1042
+ for (const [key, entry] of Object.entries(rawHeaders)) {
1043
+ if (typeof entry !== "string") continue;
1044
+ // Prevent prototype pollution via __proto__ / constructor / prototype keys.
1045
+ if (
1046
+ key === "__proto__" ||
1047
+ key === "constructor" ||
1048
+ key === "prototype"
1049
+ )
1050
+ continue;
1051
+ headers[key] = entry;
1052
+ }
596
1053
  const otlp: CrewOtlpConfig = {
597
1054
  enabled: parseWithSchema(Type.Boolean(), obj.enabled),
598
1055
  endpoint: parseWithSchema(Type.String({ minLength: 1 }), obj.endpoint),
599
1056
  headers: Object.keys(headers).length > 0 ? headers : undefined,
600
- intervalMs: parseWithSchema(Type.Integer({ minimum: 5000 }), obj.intervalMs),
1057
+ intervalMs: parseWithSchema(
1058
+ Type.Integer({ minimum: 5000 }),
1059
+ obj.intervalMs,
1060
+ ),
601
1061
  };
602
- return Object.values(otlp).some((entry) => entry !== undefined) ? otlp : undefined;
1062
+ return Object.values(otlp).some((entry) => entry !== undefined)
1063
+ ? otlp
1064
+ : undefined;
603
1065
  }
604
1066
 
605
1067
  export function parseConfig(raw: unknown): PiTeamsConfig {
@@ -608,8 +1070,14 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
608
1070
  return {
609
1071
  asyncByDefault: parseWithSchema(Type.Boolean(), obj.asyncByDefault),
610
1072
  executeWorkers: parseWithSchema(Type.Boolean(), obj.executeWorkers),
611
- notifierIntervalMs: parseWithSchema(Type.Number({ minimum: 1_000 }), obj.notifierIntervalMs),
612
- requireCleanWorktreeLeader: parseWithSchema(Type.Boolean(), obj.requireCleanWorktreeLeader),
1073
+ notifierIntervalMs: parseWithSchema(
1074
+ Type.Number({ minimum: 1_000 }),
1075
+ obj.notifierIntervalMs,
1076
+ ),
1077
+ requireCleanWorktreeLeader: parseWithSchema(
1078
+ Type.Boolean(),
1079
+ obj.requireCleanWorktreeLeader,
1080
+ ),
613
1081
  autonomous: parseAutonomousConfig(obj.autonomous),
614
1082
  limits: parseLimitsConfig(obj.limits),
615
1083
  runtime: parseRuntimeConfig(obj.runtime),
@@ -628,20 +1096,21 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
628
1096
  }
629
1097
 
630
1098
  export function parseConfigWithWarnings(raw: unknown): ConfigValidationResult {
631
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { config: {}, warnings: [] };
1099
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1100
+ return { config: {}, warnings: [] };
632
1101
  const parsed = parseConfig(raw);
633
1102
  const warnings = validateConfigWithWarnings(raw as Record<string, unknown>);
634
1103
  return { config: parsed, warnings };
635
1104
  }
636
1105
 
637
-
638
1106
  function unsetPath(record: Record<string, unknown>, dottedPath: string): void {
639
1107
  const parts = dottedPath.split(".").filter(Boolean);
640
1108
  if (parts.length === 0) return;
641
1109
  let target: Record<string, unknown> = record;
642
1110
  for (const part of parts.slice(0, -1)) {
643
1111
  const current = target[part];
644
- if (!current || typeof current !== "object" || Array.isArray(current)) return;
1112
+ if (!current || typeof current !== "object" || Array.isArray(current))
1113
+ return;
645
1114
  target = current as Record<string, unknown>;
646
1115
  }
647
1116
  delete target[parts[parts.length - 1]!];
@@ -654,15 +1123,30 @@ function readConfigRecord(filePath: string): Record<string, unknown> {
654
1123
  return raw as Record<string, unknown>;
655
1124
  }
656
1125
 
657
- function readOptionalConfig(filePath: string): { exists: boolean; config: PiTeamsConfig; warnings: string[] } {
658
- if (!fs.existsSync(filePath)) return { exists: false, config: {}, warnings: [] };
1126
+ function readOptionalConfig(filePath: string): {
1127
+ exists: boolean;
1128
+ config: PiTeamsConfig;
1129
+ warnings: string[];
1130
+ } {
1131
+ if (!fs.existsSync(filePath))
1132
+ return { exists: false, config: {}, warnings: [] };
659
1133
  try {
660
1134
  const raw = readConfigRecord(filePath);
661
1135
  const parsed = parseConfigWithWarnings(raw);
662
- return { exists: true, config: parsed.config, warnings: parsed.warnings.map((warning) => `${filePath}: ${warning}`) };
1136
+ return {
1137
+ exists: true,
1138
+ config: parsed.config,
1139
+ warnings: parsed.warnings.map(
1140
+ (warning) => `${filePath}: ${warning}`,
1141
+ ),
1142
+ };
663
1143
  } catch (error) {
664
1144
  const message = error instanceof Error ? error.message : String(error);
665
- return { exists: true, config: {}, warnings: [`${filePath}: invalid config ignored: ${message}`] };
1145
+ return {
1146
+ exists: true,
1147
+ config: {},
1148
+ warnings: [`${filePath}: invalid config ignored: ${message}`],
1149
+ };
666
1150
  }
667
1151
  }
668
1152
 
@@ -678,13 +1162,25 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
678
1162
  }
679
1163
  const userConfig = readOptionalConfig(filePath);
680
1164
  warnings.push(...userConfig.warnings);
681
- let config = mergeConfig(legacyConfig.exists && legacyPath !== filePath ? legacyConfig.config : {}, userConfig.config);
1165
+ let config = mergeConfig(
1166
+ legacyConfig.exists && legacyPath !== filePath
1167
+ ? legacyConfig.config
1168
+ : {},
1169
+ userConfig.config,
1170
+ );
682
1171
  if (cwd) {
683
1172
  const projectPath = projectConfigPath(cwd);
684
1173
  const projectConfig = readOptionalConfig(projectPath);
685
1174
  if (projectConfig.exists) {
686
- const projectSafeConfig = sanitizeProjectConfig(projectPath, config, projectConfig.config);
687
- warnings.push(...projectConfig.warnings, ...projectSafeConfig.warnings);
1175
+ const projectSafeConfig = sanitizeProjectConfig(
1176
+ projectPath,
1177
+ config,
1178
+ projectConfig.config,
1179
+ );
1180
+ warnings.push(
1181
+ ...projectConfig.warnings,
1182
+ ...projectSafeConfig.warnings,
1183
+ );
688
1184
  config = mergeConfig(config, projectSafeConfig.config);
689
1185
  }
690
1186
  // `.pi/pi-crew.json` is the project-owned override file. If present and valid,
@@ -698,43 +1194,78 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
698
1194
  paths.push(piCrewJsonPath);
699
1195
  }
700
1196
  }
701
- return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
1197
+ return {
1198
+ path: filePath,
1199
+ paths,
1200
+ config,
1201
+ warnings: warnings.length > 0 ? warnings : undefined,
1202
+ };
702
1203
  }
703
1204
 
704
- export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions = {}): SavedPiTeamsConfig {
705
- const filePath = options.scope === "project" && options.cwd ? projectConfigPath(options.cwd) : configPath();
706
- let current: Record<string, unknown>;
707
- try {
708
- current = readConfigRecord(filePath);
709
- } catch (error) {
710
- const message = error instanceof Error ? error.message : String(error);
711
- throw new Error(`Could not update pi-crew config: ${message}`);
712
- }
713
- let merged = mergeConfig(parseConfig(current), patch);
714
- if (options.unsetPaths?.length) {
715
- const raw = JSON.parse(JSON.stringify(merged)) as Record<string, unknown>;
716
- for (const unset of options.unsetPaths) unsetPath(raw, unset);
717
- merged = parseConfig(raw);
718
- }
719
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
720
- fs.writeFileSync(filePath, `${JSON.stringify(merged, null, 2)}\n`, "utf-8");
721
- return { path: filePath, config: merged };
1205
+ export function updateConfig(
1206
+ patch: PiTeamsConfig,
1207
+ options: UpdateConfigOptions = {},
1208
+ ): SavedPiTeamsConfig {
1209
+ const filePath =
1210
+ options.scope === "project" && options.cwd
1211
+ ? projectConfigPath(options.cwd)
1212
+ : configPath();
1213
+ const lockPath = filePath + ".lock";
1214
+ return withFileLockSync(lockPath, () => {
1215
+ let current: Record<string, unknown>;
1216
+ try {
1217
+ current = readConfigRecord(filePath);
1218
+ } catch (error) {
1219
+ const message =
1220
+ error instanceof Error ? error.message : String(error);
1221
+ throw new Error(`Could not update pi-crew config: ${message}`);
1222
+ }
1223
+ let merged = mergeConfig(parseConfig(current), patch);
1224
+ if (options.unsetPaths?.length) {
1225
+ const raw = JSON.parse(JSON.stringify(merged)) as Record<
1226
+ string,
1227
+ unknown
1228
+ >;
1229
+ for (const unset of options.unsetPaths) unsetPath(raw, unset);
1230
+ merged = parseConfig(raw);
1231
+ }
1232
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1233
+ fs.writeFileSync(
1234
+ filePath,
1235
+ `${JSON.stringify(merged, null, 2)}\n`,
1236
+ "utf-8",
1237
+ );
1238
+ return { path: filePath, config: merged };
1239
+ });
722
1240
  }
723
1241
 
724
- export function updateAutonomousConfig(patch: PiTeamsAutonomousConfig): SavedPiTeamsConfig {
1242
+ export function updateAutonomousConfig(
1243
+ patch: PiTeamsAutonomousConfig,
1244
+ ): SavedPiTeamsConfig {
725
1245
  const filePath = configPath();
726
- let current: Record<string, unknown>;
727
- try {
728
- current = readConfigRecord(filePath);
729
- } catch (error) {
730
- const message = error instanceof Error ? error.message : String(error);
731
- throw new Error(`Could not update pi-crew config: ${message}`);
732
- }
733
- const currentAutonomous = current.autonomous && typeof current.autonomous === "object" && !Array.isArray(current.autonomous)
734
- ? current.autonomous as Record<string, unknown>
735
- : {};
736
- current.autonomous = { ...currentAutonomous, ...patch };
737
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
738
- fs.writeFileSync(filePath, `${JSON.stringify(current, null, 2)}\n`, "utf-8");
739
- return { path: filePath, config: parseConfig(current) };
1246
+ const lockPath = filePath + ".lock";
1247
+ return withFileLockSync(lockPath, () => {
1248
+ let current: Record<string, unknown>;
1249
+ try {
1250
+ current = readConfigRecord(filePath);
1251
+ } catch (error) {
1252
+ const message =
1253
+ error instanceof Error ? error.message : String(error);
1254
+ throw new Error(`Could not update pi-crew config: ${message}`);
1255
+ }
1256
+ const currentAutonomous =
1257
+ current.autonomous &&
1258
+ typeof current.autonomous === "object" &&
1259
+ !Array.isArray(current.autonomous)
1260
+ ? (current.autonomous as Record<string, unknown>)
1261
+ : {};
1262
+ current.autonomous = { ...currentAutonomous, ...patch };
1263
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1264
+ fs.writeFileSync(
1265
+ filePath,
1266
+ `${JSON.stringify(current, null, 2)}\n`,
1267
+ "utf-8",
1268
+ );
1269
+ return { path: filePath, config: parseConfig(current) };
1270
+ });
740
1271
  }