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.
- package/CHANGELOG.md +17 -0
- package/package.json +1 -1
- package/src/agents/discover-agents.ts +2 -1
- package/src/config/config.ts +760 -229
- package/src/config/types.ts +34 -5
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +2 -1
- package/src/extension/register.ts +1176 -255
- package/src/extension/registration/commands.ts +15 -2
- package/src/extension/registration/team-tool.ts +1 -1
- package/src/extension/session-summary.ts +11 -1
- package/src/extension/team-tool/api.ts +4 -1
- package/src/extension/team-tool/cache-control.ts +23 -0
- package/src/extension/team-tool/cancel.ts +27 -16
- package/src/extension/team-tool/context.ts +2 -0
- package/src/extension/team-tool/handle-settings.ts +2 -0
- package/src/extension/team-tool/health-monitor.ts +563 -0
- package/src/extension/team-tool/inspect.ts +10 -3
- package/src/extension/team-tool/lifecycle-actions.ts +12 -5
- package/src/extension/team-tool/respond.ts +6 -3
- package/src/extension/team-tool/status.ts +4 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +901 -177
- package/src/runtime/adaptive-plan.ts +1 -1
- package/src/runtime/child-pi.ts +15 -2
- package/src/runtime/crash-recovery.ts +30 -0
- package/src/runtime/foreground-watchdog.ts +129 -0
- package/src/runtime/manifest-cache.ts +4 -2
- package/src/runtime/pi-args.ts +3 -2
- package/src/runtime/run-tracker.ts +11 -0
- package/src/runtime/runtime-policy.ts +15 -2
- package/src/runtime/skill-instructions.ts +11 -0
- package/src/runtime/stale-reconciler.ts +322 -18
- package/src/runtime/task-runner.ts +8 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +204 -76
- package/src/state/atomic-write.ts +2 -2
- package/src/state/locks.ts +19 -0
- package/src/state/mailbox.ts +22 -5
- package/src/state/state-store.ts +13 -3
- package/src/teams/discover-teams.ts +2 -1
- package/src/ui/run-event-bus.ts +2 -1
- package/src/ui/settings-overlay.ts +2 -0
- package/src/workflows/discover-workflows.ts +5 -1
package/src/config/config.ts
CHANGED
|
@@ -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 {
|
|
7
|
-
import {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
CrewPolicyConfig,
|
|
20
|
+
CrewControlConfig,
|
|
21
|
+
CrewLimitsConfig,
|
|
27
22
|
CrewNotificationSeverity,
|
|
28
23
|
CrewNotificationsConfig,
|
|
29
24
|
CrewObservabilityConfig,
|
|
30
|
-
CrewRetryPolicyConfig,
|
|
31
|
-
CrewReliabilityConfig,
|
|
32
25
|
CrewOtlpConfig,
|
|
33
|
-
|
|
26
|
+
CrewPolicyConfig,
|
|
27
|
+
CrewReliabilityConfig,
|
|
28
|
+
CrewRetryPolicyConfig,
|
|
29
|
+
CrewRuntimeConfig,
|
|
30
|
+
CrewRuntimeMode,
|
|
31
|
+
CrewTelemetryConfig,
|
|
32
|
+
CrewToolsConfig,
|
|
33
|
+
CrewUiConfig,
|
|
34
|
+
CrewWorktreeConfig,
|
|
35
|
+
EffectivenessGuardMode,
|
|
34
36
|
LoadedPiTeamsConfig,
|
|
35
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
CrewPolicyConfig,
|
|
48
|
+
CrewControlConfig,
|
|
49
|
+
CrewLimitsConfig,
|
|
52
50
|
CrewNotificationsConfig,
|
|
53
51
|
CrewObservabilityConfig,
|
|
54
|
-
CrewRetryPolicyConfig,
|
|
55
|
-
CrewReliabilityConfig,
|
|
56
52
|
CrewOtlpConfig,
|
|
57
|
-
|
|
53
|
+
CrewPolicyConfig,
|
|
54
|
+
CrewReliabilityConfig,
|
|
55
|
+
CrewRetryPolicyConfig,
|
|
56
|
+
CrewRuntimeConfig,
|
|
57
|
+
CrewTelemetryConfig,
|
|
58
|
+
CrewToolsConfig,
|
|
59
|
+
CrewUiConfig,
|
|
60
|
+
CrewWorktreeConfig,
|
|
58
61
|
LoadedPiTeamsConfig,
|
|
59
|
-
|
|
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(
|
|
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>>(
|
|
87
|
-
|
|
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")
|
|
93
|
-
|
|
94
|
-
if (
|
|
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(
|
|
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 =
|
|
133
|
+
const message =
|
|
134
|
+
(error as { message?: unknown }).message ?? "invalid value";
|
|
107
135
|
// Enhance "additionalProperties" errors with fuzzy suggestions
|
|
108
|
-
if (
|
|
136
|
+
if (
|
|
137
|
+
(error as { keyword?: unknown }).keyword ===
|
|
138
|
+
"additionalProperties"
|
|
139
|
+
) {
|
|
109
140
|
const offendingKey = path.split("/").pop() ?? path;
|
|
110
|
-
const suggestion = suggestConfigKey(
|
|
111
|
-
|
|
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(
|
|
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(
|
|
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 [
|
|
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(
|
|
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(
|
|
194
|
+
warnings.push(
|
|
195
|
+
projectOverrideWarning(
|
|
196
|
+
projectPath,
|
|
197
|
+
"runtime.requirePlanApproval",
|
|
198
|
+
),
|
|
199
|
+
);
|
|
145
200
|
}
|
|
146
|
-
sanitized.runtime = Object.values(runtime).some(
|
|
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 [
|
|
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(
|
|
218
|
+
warnings.push(
|
|
219
|
+
projectOverrideWarning(projectPath, `autonomous.${key}`),
|
|
220
|
+
);
|
|
154
221
|
}
|
|
155
222
|
}
|
|
156
|
-
sanitized.autonomous = Object.values(autonomous).some(
|
|
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 (
|
|
161
|
-
|
|
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))
|
|
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 (
|
|
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(
|
|
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(
|
|
260
|
+
warnings.push(
|
|
261
|
+
projectOverrideWarning(projectPath, "agents.overrides"),
|
|
262
|
+
);
|
|
177
263
|
}
|
|
178
|
-
sanitized.agents = Object.values(agents).some(
|
|
264
|
+
sanitized.agents = Object.values(agents).some(
|
|
265
|
+
(entry) => entry !== undefined,
|
|
266
|
+
)
|
|
267
|
+
? agents
|
|
268
|
+
: undefined;
|
|
179
269
|
}
|
|
180
|
-
if (
|
|
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(
|
|
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(
|
|
283
|
+
warnings.push(
|
|
284
|
+
projectOverrideWarning(
|
|
285
|
+
projectPath,
|
|
286
|
+
"tools.terminateOnForeground",
|
|
287
|
+
),
|
|
288
|
+
);
|
|
189
289
|
}
|
|
190
|
-
sanitized.tools = Object.values(tools).some(
|
|
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(
|
|
196
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
356
|
+
...withoutUndefined(
|
|
357
|
+
(override.agents ?? {}) as Record<string, unknown>,
|
|
358
|
+
),
|
|
237
359
|
overrides: {
|
|
238
360
|
...(base.agents?.overrides ?? {}),
|
|
239
|
-
...withoutUndefined(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
277
|
-
|
|
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(
|
|
284
|
-
|
|
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)
|
|
440
|
+
if (Object.keys(merged.otlp.headers ?? {}).length === 0)
|
|
441
|
+
delete merged.otlp.headers;
|
|
287
442
|
}
|
|
288
|
-
if (
|
|
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))
|
|
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>(
|
|
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(
|
|
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(
|
|
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
|
|
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")
|
|
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(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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:
|
|
353
|
-
|
|
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(
|
|
359
|
-
|
|
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(
|
|
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(
|
|
377
|
-
|
|
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)
|
|
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(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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)
|
|
651
|
+
return Object.values(limits).some((entry) => entry !== undefined)
|
|
652
|
+
? limits
|
|
653
|
+
: undefined;
|
|
397
654
|
}
|
|
398
655
|
|
|
399
|
-
function parseIsolationPolicy(
|
|
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(
|
|
404
|
-
|
|
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(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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)
|
|
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)
|
|
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(
|
|
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)
|
|
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(
|
|
786
|
+
model: parseWithSchema(
|
|
787
|
+
Type.Union([Type.String(), Type.Literal(false)]),
|
|
788
|
+
obj.model,
|
|
789
|
+
),
|
|
461
790
|
fallbackModels: parseStringArrayOrFalse(obj.fallbackModels),
|
|
462
|
-
thinking: parseWithSchema(
|
|
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)
|
|
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(
|
|
473
|
-
|
|
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(
|
|
481
|
-
|
|
482
|
-
|
|
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(
|
|
487
|
-
|
|
488
|
-
|
|
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)
|
|
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 (
|
|
498
|
-
|
|
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)
|
|
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(
|
|
894
|
+
enableClaudeStyleAliases: parseWithSchema(
|
|
895
|
+
Type.Boolean(),
|
|
896
|
+
obj.enableClaudeStyleAliases,
|
|
897
|
+
),
|
|
515
898
|
enableSteer: parseWithSchema(Type.Boolean(), obj.enableSteer),
|
|
516
|
-
terminateOnForeground: parseWithSchema(
|
|
899
|
+
terminateOnForeground: parseWithSchema(
|
|
900
|
+
Type.Boolean(),
|
|
901
|
+
obj.terminateOnForeground,
|
|
902
|
+
),
|
|
517
903
|
};
|
|
518
|
-
return Object.values(tools).some((entry) => entry !== 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)
|
|
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(
|
|
535
|
-
|
|
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)
|
|
933
|
+
return Object.values(policy).some((entry) => entry !== undefined)
|
|
934
|
+
? policy
|
|
935
|
+
: undefined;
|
|
538
936
|
}
|
|
539
937
|
|
|
540
|
-
function parseNotificationsConfig(
|
|
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(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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)
|
|
970
|
+
return Object.values(notifications).some((entry) => entry !== undefined)
|
|
971
|
+
? notifications
|
|
972
|
+
: undefined;
|
|
552
973
|
}
|
|
553
974
|
|
|
554
|
-
function parseObservabilityConfig(
|
|
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(
|
|
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)
|
|
988
|
+
return Object.values(observability).some((entry) => entry !== undefined)
|
|
989
|
+
? observability
|
|
990
|
+
: undefined;
|
|
563
991
|
}
|
|
564
992
|
|
|
565
|
-
function parseReliabilityConfig(
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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:
|
|
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)
|
|
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)
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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(
|
|
1057
|
+
intervalMs: parseWithSchema(
|
|
1058
|
+
Type.Integer({ minimum: 5000 }),
|
|
1059
|
+
obj.intervalMs,
|
|
1060
|
+
),
|
|
601
1061
|
};
|
|
602
|
-
return Object.values(otlp).some((entry) => entry !== 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(
|
|
612
|
-
|
|
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))
|
|
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))
|
|
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): {
|
|
658
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
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(
|
|
687
|
-
|
|
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 {
|
|
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(
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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(
|
|
1242
|
+
export function updateAutonomousConfig(
|
|
1243
|
+
patch: PiTeamsAutonomousConfig,
|
|
1244
|
+
): SavedPiTeamsConfig {
|
|
725
1245
|
const filePath = configPath();
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
current
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
}
|