pi-crew 0.3.7 → 0.3.9

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