pi-crew 0.3.7 → 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 +732 -208
- package/src/config/types.ts +34 -5
- package/src/extension/help.ts +1 -0
- package/src/extension/register.ts +1173 -257
- 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 +15 -5
- 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/respond.ts +5 -2
- 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/foreground-watchdog.ts +129 -0
- package/src/runtime/manifest-cache.ts +4 -2
- package/src/runtime/run-tracker.ts +11 -0
- package/src/runtime/runtime-policy.ts +15 -2
- package/src/runtime/stale-reconciler.ts +322 -18
- package/src/runtime/task-runner.ts +6 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +204 -76
- package/src/state/state-store.ts +9 -1
- 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,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 {
|
|
7
|
-
import {
|
|
8
|
-
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";
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
CrewPolicyConfig,
|
|
20
|
+
CrewControlConfig,
|
|
21
|
+
CrewLimitsConfig,
|
|
28
22
|
CrewNotificationSeverity,
|
|
29
23
|
CrewNotificationsConfig,
|
|
30
24
|
CrewObservabilityConfig,
|
|
31
|
-
CrewRetryPolicyConfig,
|
|
32
|
-
CrewReliabilityConfig,
|
|
33
25
|
CrewOtlpConfig,
|
|
34
|
-
|
|
26
|
+
CrewPolicyConfig,
|
|
27
|
+
CrewReliabilityConfig,
|
|
28
|
+
CrewRetryPolicyConfig,
|
|
29
|
+
CrewRuntimeConfig,
|
|
30
|
+
CrewRuntimeMode,
|
|
31
|
+
CrewTelemetryConfig,
|
|
32
|
+
CrewToolsConfig,
|
|
33
|
+
CrewUiConfig,
|
|
34
|
+
CrewWorktreeConfig,
|
|
35
|
+
EffectivenessGuardMode,
|
|
35
36
|
LoadedPiTeamsConfig,
|
|
36
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
CrewPolicyConfig,
|
|
48
|
+
CrewControlConfig,
|
|
49
|
+
CrewLimitsConfig,
|
|
53
50
|
CrewNotificationsConfig,
|
|
54
51
|
CrewObservabilityConfig,
|
|
55
|
-
CrewRetryPolicyConfig,
|
|
56
|
-
CrewReliabilityConfig,
|
|
57
52
|
CrewOtlpConfig,
|
|
58
|
-
|
|
53
|
+
CrewPolicyConfig,
|
|
54
|
+
CrewReliabilityConfig,
|
|
55
|
+
CrewRetryPolicyConfig,
|
|
56
|
+
CrewRuntimeConfig,
|
|
57
|
+
CrewTelemetryConfig,
|
|
58
|
+
CrewToolsConfig,
|
|
59
|
+
CrewUiConfig,
|
|
60
|
+
CrewWorktreeConfig,
|
|
59
61
|
LoadedPiTeamsConfig,
|
|
60
|
-
|
|
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(
|
|
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>>(
|
|
88
|
-
|
|
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")
|
|
94
|
-
|
|
95
|
-
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;
|
|
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(
|
|
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 =
|
|
133
|
+
const message =
|
|
134
|
+
(error as { message?: unknown }).message ?? "invalid value";
|
|
108
135
|
// Enhance "additionalProperties" errors with fuzzy suggestions
|
|
109
|
-
if (
|
|
136
|
+
if (
|
|
137
|
+
(error as { keyword?: unknown }).keyword ===
|
|
138
|
+
"additionalProperties"
|
|
139
|
+
) {
|
|
110
140
|
const offendingKey = path.split("/").pop() ?? path;
|
|
111
|
-
const suggestion = suggestConfigKey(
|
|
112
|
-
|
|
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(
|
|
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(
|
|
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 [
|
|
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(
|
|
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(
|
|
194
|
+
warnings.push(
|
|
195
|
+
projectOverrideWarning(
|
|
196
|
+
projectPath,
|
|
197
|
+
"runtime.requirePlanApproval",
|
|
198
|
+
),
|
|
199
|
+
);
|
|
146
200
|
}
|
|
147
|
-
sanitized.runtime = Object.values(runtime).some(
|
|
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 [
|
|
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(
|
|
218
|
+
warnings.push(
|
|
219
|
+
projectOverrideWarning(projectPath, `autonomous.${key}`),
|
|
220
|
+
);
|
|
155
221
|
}
|
|
156
222
|
}
|
|
157
|
-
sanitized.autonomous = Object.values(autonomous).some(
|
|
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 (
|
|
162
|
-
|
|
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))
|
|
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 (
|
|
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(
|
|
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(
|
|
260
|
+
warnings.push(
|
|
261
|
+
projectOverrideWarning(projectPath, "agents.overrides"),
|
|
262
|
+
);
|
|
178
263
|
}
|
|
179
|
-
sanitized.agents = Object.values(agents).some(
|
|
264
|
+
sanitized.agents = Object.values(agents).some(
|
|
265
|
+
(entry) => entry !== undefined,
|
|
266
|
+
)
|
|
267
|
+
? agents
|
|
268
|
+
: undefined;
|
|
180
269
|
}
|
|
181
|
-
if (
|
|
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(
|
|
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(
|
|
283
|
+
warnings.push(
|
|
284
|
+
projectOverrideWarning(
|
|
285
|
+
projectPath,
|
|
286
|
+
"tools.terminateOnForeground",
|
|
287
|
+
),
|
|
288
|
+
);
|
|
190
289
|
}
|
|
191
|
-
sanitized.tools = Object.values(tools).some(
|
|
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(
|
|
197
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
356
|
+
...withoutUndefined(
|
|
357
|
+
(override.agents ?? {}) as Record<string, unknown>,
|
|
358
|
+
),
|
|
238
359
|
overrides: {
|
|
239
360
|
...(base.agents?.overrides ?? {}),
|
|
240
|
-
...withoutUndefined(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
278
|
-
|
|
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(
|
|
285
|
-
|
|
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)
|
|
440
|
+
if (Object.keys(merged.otlp.headers ?? {}).length === 0)
|
|
441
|
+
delete merged.otlp.headers;
|
|
288
442
|
}
|
|
289
|
-
if (
|
|
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))
|
|
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>(
|
|
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(
|
|
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(
|
|
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
|
|
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")
|
|
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(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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:
|
|
354
|
-
|
|
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(
|
|
360
|
-
|
|
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(
|
|
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(
|
|
378
|
-
|
|
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)
|
|
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(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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)
|
|
651
|
+
return Object.values(limits).some((entry) => entry !== undefined)
|
|
652
|
+
? limits
|
|
653
|
+
: undefined;
|
|
398
654
|
}
|
|
399
655
|
|
|
400
|
-
function parseIsolationPolicy(
|
|
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(
|
|
405
|
-
|
|
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(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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)
|
|
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)
|
|
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(
|
|
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)
|
|
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(
|
|
786
|
+
model: parseWithSchema(
|
|
787
|
+
Type.Union([Type.String(), Type.Literal(false)]),
|
|
788
|
+
obj.model,
|
|
789
|
+
),
|
|
462
790
|
fallbackModels: parseStringArrayOrFalse(obj.fallbackModels),
|
|
463
|
-
thinking: parseWithSchema(
|
|
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)
|
|
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(
|
|
474
|
-
|
|
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(
|
|
482
|
-
|
|
483
|
-
|
|
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(
|
|
488
|
-
|
|
489
|
-
|
|
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)
|
|
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 (
|
|
499
|
-
|
|
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)
|
|
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(
|
|
894
|
+
enableClaudeStyleAliases: parseWithSchema(
|
|
895
|
+
Type.Boolean(),
|
|
896
|
+
obj.enableClaudeStyleAliases,
|
|
897
|
+
),
|
|
516
898
|
enableSteer: parseWithSchema(Type.Boolean(), obj.enableSteer),
|
|
517
|
-
terminateOnForeground: parseWithSchema(
|
|
899
|
+
terminateOnForeground: parseWithSchema(
|
|
900
|
+
Type.Boolean(),
|
|
901
|
+
obj.terminateOnForeground,
|
|
902
|
+
),
|
|
518
903
|
};
|
|
519
|
-
return Object.values(tools).some((entry) => entry !== 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)
|
|
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(
|
|
536
|
-
|
|
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)
|
|
933
|
+
return Object.values(policy).some((entry) => entry !== undefined)
|
|
934
|
+
? policy
|
|
935
|
+
: undefined;
|
|
539
936
|
}
|
|
540
937
|
|
|
541
|
-
function parseNotificationsConfig(
|
|
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(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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)
|
|
970
|
+
return Object.values(notifications).some((entry) => entry !== undefined)
|
|
971
|
+
? notifications
|
|
972
|
+
: undefined;
|
|
553
973
|
}
|
|
554
974
|
|
|
555
|
-
function parseObservabilityConfig(
|
|
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(
|
|
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)
|
|
988
|
+
return Object.values(observability).some((entry) => entry !== undefined)
|
|
989
|
+
? observability
|
|
990
|
+
: undefined;
|
|
564
991
|
}
|
|
565
992
|
|
|
566
|
-
function parseReliabilityConfig(
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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:
|
|
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)
|
|
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)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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(
|
|
1057
|
+
intervalMs: parseWithSchema(
|
|
1058
|
+
Type.Integer({ minimum: 5000 }),
|
|
1059
|
+
obj.intervalMs,
|
|
1060
|
+
),
|
|
602
1061
|
};
|
|
603
|
-
return Object.values(otlp).some((entry) => entry !== 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(
|
|
613
|
-
|
|
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))
|
|
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))
|
|
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): {
|
|
659
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
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(
|
|
688
|
-
|
|
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 {
|
|
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(
|
|
706
|
-
|
|
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 =
|
|
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<
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
740
|
-
|
|
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(
|
|
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
|
}
|