@teamclaws/teamclaw 2026.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/api.ts +10 -0
- package/index.ts +246 -0
- package/openclaw.plugin.json +41 -0
- package/package.json +63 -0
- package/src/config.ts +297 -0
- package/src/controller/controller-service.ts +197 -0
- package/src/controller/controller-tools.ts +224 -0
- package/src/controller/http-server.ts +1946 -0
- package/src/controller/local-worker-manager.ts +531 -0
- package/src/controller/message-router.ts +62 -0
- package/src/controller/prompt-injector.ts +116 -0
- package/src/controller/task-router.ts +97 -0
- package/src/controller/websocket.ts +63 -0
- package/src/controller/worker-provisioning.ts +1286 -0
- package/src/discovery.ts +101 -0
- package/src/git-collaboration.ts +690 -0
- package/src/identity.ts +149 -0
- package/src/openclaw-workspace.ts +101 -0
- package/src/protocol.ts +88 -0
- package/src/roles.ts +275 -0
- package/src/state.ts +118 -0
- package/src/task-executor.ts +478 -0
- package/src/types.ts +469 -0
- package/src/ui/app.js +1400 -0
- package/src/ui/index.html +207 -0
- package/src/ui/style.css +1281 -0
- package/src/worker/http-handler.ts +136 -0
- package/src/worker/message-queue.ts +31 -0
- package/src/worker/prompt-injector.ts +72 -0
- package/src/worker/tools.ts +318 -0
- package/src/worker/worker-service.ts +194 -0
- package/src/workspace-browser.ts +312 -0
- package/tsconfig.json +22 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
export type TeamClawMode = "controller" | "worker";
|
|
2
|
+
|
|
3
|
+
export type WorkerStatus = "idle" | "busy" | "offline";
|
|
4
|
+
|
|
5
|
+
export type TaskStatus =
|
|
6
|
+
| "pending"
|
|
7
|
+
| "assigned"
|
|
8
|
+
| "in_progress"
|
|
9
|
+
| "blocked"
|
|
10
|
+
| "review"
|
|
11
|
+
| "completed"
|
|
12
|
+
| "failed";
|
|
13
|
+
|
|
14
|
+
export type TaskPriority = "low" | "medium" | "high" | "critical";
|
|
15
|
+
|
|
16
|
+
export type TaskExecutionEventType = "lifecycle" | "progress" | "output" | "error";
|
|
17
|
+
|
|
18
|
+
export type TaskExecutionStatus = "pending" | "running" | "completed" | "failed";
|
|
19
|
+
|
|
20
|
+
export type GitSyncMode = "shared" | "bundle" | "remote";
|
|
21
|
+
|
|
22
|
+
export type WorkerProvisioningType = "none" | "process" | "docker" | "kubernetes";
|
|
23
|
+
|
|
24
|
+
export type ProvisionedWorkerStatus =
|
|
25
|
+
| "launching"
|
|
26
|
+
| "registered"
|
|
27
|
+
| "terminating"
|
|
28
|
+
| "terminated"
|
|
29
|
+
| "failed";
|
|
30
|
+
|
|
31
|
+
export type GitRepoState = {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
mode: GitSyncMode;
|
|
34
|
+
defaultBranch: string;
|
|
35
|
+
remoteUrl?: string;
|
|
36
|
+
remoteReady: boolean;
|
|
37
|
+
headCommit?: string;
|
|
38
|
+
headSummary?: string;
|
|
39
|
+
dirty: boolean;
|
|
40
|
+
lastPreparedAt: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type RepoSyncInfo = {
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
mode: GitSyncMode;
|
|
47
|
+
defaultBranch: string;
|
|
48
|
+
remoteUrl?: string;
|
|
49
|
+
bundleUrl?: string;
|
|
50
|
+
importUrl?: string;
|
|
51
|
+
headCommit?: string;
|
|
52
|
+
headSummary?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type RoleId =
|
|
56
|
+
| "pm"
|
|
57
|
+
| "architect"
|
|
58
|
+
| "developer"
|
|
59
|
+
| "qa"
|
|
60
|
+
| "release-engineer"
|
|
61
|
+
| "infra-engineer"
|
|
62
|
+
| "devops"
|
|
63
|
+
| "security-engineer"
|
|
64
|
+
| "designer"
|
|
65
|
+
| "marketing";
|
|
66
|
+
|
|
67
|
+
export type RoleDefinition = {
|
|
68
|
+
id: RoleId;
|
|
69
|
+
label: string;
|
|
70
|
+
icon: string;
|
|
71
|
+
description: string;
|
|
72
|
+
capabilities: string[];
|
|
73
|
+
systemPrompt: string;
|
|
74
|
+
suggestedNextRoles: RoleId[];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type WorkerInfo = {
|
|
78
|
+
id: string;
|
|
79
|
+
role: RoleId;
|
|
80
|
+
label: string;
|
|
81
|
+
status: WorkerStatus;
|
|
82
|
+
transport?: "http" | "local";
|
|
83
|
+
url: string;
|
|
84
|
+
lastHeartbeat: number;
|
|
85
|
+
capabilities: string[];
|
|
86
|
+
currentTaskId?: string;
|
|
87
|
+
registeredAt: number;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type TaskInfo = {
|
|
91
|
+
id: string;
|
|
92
|
+
title: string;
|
|
93
|
+
description: string;
|
|
94
|
+
status: TaskStatus;
|
|
95
|
+
priority: TaskPriority;
|
|
96
|
+
assignedRole?: RoleId;
|
|
97
|
+
assignedWorkerId?: string;
|
|
98
|
+
createdBy: string;
|
|
99
|
+
controllerSessionKey?: string;
|
|
100
|
+
createdAt: number;
|
|
101
|
+
updatedAt: number;
|
|
102
|
+
startedAt?: number;
|
|
103
|
+
completedAt?: number;
|
|
104
|
+
progress?: string;
|
|
105
|
+
result?: string;
|
|
106
|
+
error?: string;
|
|
107
|
+
clarificationRequestId?: string;
|
|
108
|
+
execution?: TaskExecution;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export type TaskExecutionEvent = {
|
|
112
|
+
id: string;
|
|
113
|
+
type: TaskExecutionEventType;
|
|
114
|
+
createdAt: number;
|
|
115
|
+
message: string;
|
|
116
|
+
phase?: string;
|
|
117
|
+
source?: "controller" | "worker" | "subagent";
|
|
118
|
+
stream?: string;
|
|
119
|
+
role?: RoleId;
|
|
120
|
+
workerId?: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type TaskAssignmentPayload = {
|
|
124
|
+
taskId: string;
|
|
125
|
+
title: string;
|
|
126
|
+
description: string;
|
|
127
|
+
priority?: TaskPriority;
|
|
128
|
+
repo?: RepoSyncInfo;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type TaskExecution = {
|
|
132
|
+
status: TaskExecutionStatus;
|
|
133
|
+
runId?: string;
|
|
134
|
+
sessionKey?: string;
|
|
135
|
+
startedAt?: number;
|
|
136
|
+
endedAt?: number;
|
|
137
|
+
lastUpdatedAt?: number;
|
|
138
|
+
events: TaskExecutionEvent[];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export type TaskExecutionSummary = {
|
|
142
|
+
status: TaskExecutionStatus;
|
|
143
|
+
runId?: string;
|
|
144
|
+
startedAt?: number;
|
|
145
|
+
endedAt?: number;
|
|
146
|
+
lastUpdatedAt?: number;
|
|
147
|
+
eventCount: number;
|
|
148
|
+
lastEvent?: TaskExecutionEvent;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type TaskExecutionEventInput = {
|
|
152
|
+
type: TaskExecutionEventType;
|
|
153
|
+
message: string;
|
|
154
|
+
createdAt?: number;
|
|
155
|
+
phase?: string;
|
|
156
|
+
source?: "controller" | "worker" | "subagent";
|
|
157
|
+
stream?: string;
|
|
158
|
+
role?: RoleId;
|
|
159
|
+
workerId?: string;
|
|
160
|
+
runId?: string;
|
|
161
|
+
sessionKey?: string;
|
|
162
|
+
status?: TaskExecutionStatus;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type ClarificationStatus = "pending" | "answered";
|
|
166
|
+
|
|
167
|
+
export type ClarificationRequest = {
|
|
168
|
+
id: string;
|
|
169
|
+
taskId: string;
|
|
170
|
+
requestedBy: string;
|
|
171
|
+
requestedByWorkerId?: string;
|
|
172
|
+
requestedByRole?: RoleId;
|
|
173
|
+
question: string;
|
|
174
|
+
blockingReason: string;
|
|
175
|
+
context?: string;
|
|
176
|
+
status: ClarificationStatus;
|
|
177
|
+
answer?: string;
|
|
178
|
+
answeredBy?: string;
|
|
179
|
+
createdAt: number;
|
|
180
|
+
updatedAt: number;
|
|
181
|
+
answeredAt?: number;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export type TeamMessage = {
|
|
185
|
+
id: string;
|
|
186
|
+
from: string;
|
|
187
|
+
fromRole?: RoleId;
|
|
188
|
+
to?: string;
|
|
189
|
+
toRole?: RoleId;
|
|
190
|
+
type: "direct" | "broadcast" | "review-request";
|
|
191
|
+
content: string;
|
|
192
|
+
taskId?: string;
|
|
193
|
+
createdAt: number;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export type ProvisionedWorkerRecord = {
|
|
197
|
+
workerId: string;
|
|
198
|
+
role: RoleId;
|
|
199
|
+
provider: WorkerProvisioningType;
|
|
200
|
+
status: ProvisionedWorkerStatus;
|
|
201
|
+
launchToken: string;
|
|
202
|
+
requestedAt: number;
|
|
203
|
+
updatedAt: number;
|
|
204
|
+
registeredAt?: number;
|
|
205
|
+
idleSince?: number;
|
|
206
|
+
instanceId?: string;
|
|
207
|
+
instanceName?: string;
|
|
208
|
+
runtimeHomeDir?: string;
|
|
209
|
+
lastError?: string;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export type TeamProvisioningState = {
|
|
213
|
+
workers: Record<string, ProvisionedWorkerRecord>;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type PluginConfig = {
|
|
217
|
+
mode: TeamClawMode;
|
|
218
|
+
port: number;
|
|
219
|
+
role: RoleId;
|
|
220
|
+
controllerUrl: string;
|
|
221
|
+
teamName: string;
|
|
222
|
+
heartbeatIntervalMs: number;
|
|
223
|
+
localRoles: RoleId[];
|
|
224
|
+
taskTimeoutMs: number;
|
|
225
|
+
gitEnabled: boolean;
|
|
226
|
+
gitRemoteUrl: string;
|
|
227
|
+
gitDefaultBranch: string;
|
|
228
|
+
gitAuthorName: string;
|
|
229
|
+
gitAuthorEmail: string;
|
|
230
|
+
workerProvisioningType: WorkerProvisioningType;
|
|
231
|
+
workerProvisioningControllerUrl: string;
|
|
232
|
+
workerProvisioningRoles: RoleId[];
|
|
233
|
+
workerProvisioningMinPerRole: number;
|
|
234
|
+
workerProvisioningMaxPerRole: number;
|
|
235
|
+
workerProvisioningIdleTtlMs: number;
|
|
236
|
+
workerProvisioningStartupTimeoutMs: number;
|
|
237
|
+
workerProvisioningImage: string;
|
|
238
|
+
workerProvisioningPassEnv: string[];
|
|
239
|
+
workerProvisioningExtraEnv: Record<string, string>;
|
|
240
|
+
workerProvisioningDockerNetwork: string;
|
|
241
|
+
workerProvisioningDockerMounts: string[];
|
|
242
|
+
workerProvisioningKubernetesNamespace: string;
|
|
243
|
+
workerProvisioningKubernetesContext: string;
|
|
244
|
+
workerProvisioningKubernetesServiceAccount: string;
|
|
245
|
+
workerProvisioningKubernetesLabels: Record<string, string>;
|
|
246
|
+
workerProvisioningKubernetesAnnotations: Record<string, string>;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export type WorkerIdentity = {
|
|
250
|
+
workerId: string;
|
|
251
|
+
role: RoleId;
|
|
252
|
+
controllerUrl: string;
|
|
253
|
+
registeredAt: number;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export type TeamState = {
|
|
257
|
+
teamName: string;
|
|
258
|
+
workers: Record<string, WorkerInfo>;
|
|
259
|
+
tasks: Record<string, TaskInfo>;
|
|
260
|
+
messages: TeamMessage[];
|
|
261
|
+
clarifications: Record<string, ClarificationRequest>;
|
|
262
|
+
repo?: GitRepoState;
|
|
263
|
+
provisioning?: TeamProvisioningState;
|
|
264
|
+
createdAt: number;
|
|
265
|
+
updatedAt: number;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export type DiscoveryResult = {
|
|
269
|
+
name: string;
|
|
270
|
+
host: string;
|
|
271
|
+
port: number;
|
|
272
|
+
teamName: string;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export type RegistrationRequest = {
|
|
276
|
+
workerId: string;
|
|
277
|
+
role: RoleId;
|
|
278
|
+
label: string;
|
|
279
|
+
url: string;
|
|
280
|
+
capabilities: string[];
|
|
281
|
+
launchToken?: string;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export type HeartbeatPayload = {
|
|
285
|
+
workerId: string;
|
|
286
|
+
status: WorkerStatus;
|
|
287
|
+
currentTaskId?: string;
|
|
288
|
+
timestamp: number;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export function parsePluginConfig(raw: Record<string, unknown> = {}): PluginConfig {
|
|
292
|
+
const mode = (typeof raw.mode === "string" && (raw.mode === "controller" || raw.mode === "worker"))
|
|
293
|
+
? raw.mode
|
|
294
|
+
: "worker" as TeamClawMode;
|
|
295
|
+
|
|
296
|
+
const port = typeof raw.port === "number" && raw.port > 0 && raw.port < 65536
|
|
297
|
+
? raw.port
|
|
298
|
+
: 9527;
|
|
299
|
+
|
|
300
|
+
const role = typeof raw.role === "string" && VALID_ROLES.includes(raw.role as RoleId)
|
|
301
|
+
? raw.role as RoleId
|
|
302
|
+
: "developer" as RoleId;
|
|
303
|
+
|
|
304
|
+
const controllerUrl = typeof raw.controllerUrl === "string"
|
|
305
|
+
? raw.controllerUrl.trim()
|
|
306
|
+
: "";
|
|
307
|
+
|
|
308
|
+
const teamName = typeof raw.teamName === "string" && raw.teamName.trim()
|
|
309
|
+
? raw.teamName.trim()
|
|
310
|
+
: "default";
|
|
311
|
+
|
|
312
|
+
const heartbeatIntervalMs = typeof raw.heartbeatIntervalMs === "number" && raw.heartbeatIntervalMs >= 1000
|
|
313
|
+
? raw.heartbeatIntervalMs
|
|
314
|
+
: 10000;
|
|
315
|
+
|
|
316
|
+
const localRoles = parseRoleList(raw.localRoles);
|
|
317
|
+
|
|
318
|
+
const taskTimeoutMs = typeof raw.taskTimeoutMs === "number" && raw.taskTimeoutMs >= 1000
|
|
319
|
+
? raw.taskTimeoutMs
|
|
320
|
+
: 1_800_000;
|
|
321
|
+
|
|
322
|
+
const gitEnabled = typeof raw.gitEnabled === "boolean" ? raw.gitEnabled : true;
|
|
323
|
+
|
|
324
|
+
const gitRemoteUrl = typeof raw.gitRemoteUrl === "string"
|
|
325
|
+
? raw.gitRemoteUrl.trim()
|
|
326
|
+
: "";
|
|
327
|
+
|
|
328
|
+
const gitDefaultBranch = typeof raw.gitDefaultBranch === "string" && raw.gitDefaultBranch.trim()
|
|
329
|
+
? raw.gitDefaultBranch.trim()
|
|
330
|
+
: "main";
|
|
331
|
+
|
|
332
|
+
const gitAuthorName = typeof raw.gitAuthorName === "string" && raw.gitAuthorName.trim()
|
|
333
|
+
? raw.gitAuthorName.trim()
|
|
334
|
+
: "TeamClaw";
|
|
335
|
+
|
|
336
|
+
const gitAuthorEmail = typeof raw.gitAuthorEmail === "string" && raw.gitAuthorEmail.trim()
|
|
337
|
+
? raw.gitAuthorEmail.trim()
|
|
338
|
+
: "teamclaw@local";
|
|
339
|
+
|
|
340
|
+
const workerProvisioningType = parseProvisioningType(raw.workerProvisioningType);
|
|
341
|
+
const workerProvisioningControllerUrl = typeof raw.workerProvisioningControllerUrl === "string"
|
|
342
|
+
? raw.workerProvisioningControllerUrl.trim()
|
|
343
|
+
: "";
|
|
344
|
+
const workerProvisioningRoles = parseRoleList(raw.workerProvisioningRoles);
|
|
345
|
+
const workerProvisioningMinPerRole = typeof raw.workerProvisioningMinPerRole === "number" && raw.workerProvisioningMinPerRole >= 0
|
|
346
|
+
? Math.floor(raw.workerProvisioningMinPerRole)
|
|
347
|
+
: 0;
|
|
348
|
+
const rawProvisioningMaxPerRole = typeof raw.workerProvisioningMaxPerRole === "number" && raw.workerProvisioningMaxPerRole >= 1
|
|
349
|
+
? Math.floor(raw.workerProvisioningMaxPerRole)
|
|
350
|
+
: 1;
|
|
351
|
+
const workerProvisioningMaxPerRole = Math.max(rawProvisioningMaxPerRole, workerProvisioningMinPerRole);
|
|
352
|
+
const workerProvisioningIdleTtlMs = typeof raw.workerProvisioningIdleTtlMs === "number" && raw.workerProvisioningIdleTtlMs >= 1000
|
|
353
|
+
? raw.workerProvisioningIdleTtlMs
|
|
354
|
+
: 120_000;
|
|
355
|
+
const workerProvisioningStartupTimeoutMs =
|
|
356
|
+
typeof raw.workerProvisioningStartupTimeoutMs === "number" && raw.workerProvisioningStartupTimeoutMs >= 1000
|
|
357
|
+
? raw.workerProvisioningStartupTimeoutMs
|
|
358
|
+
: 120_000;
|
|
359
|
+
const workerProvisioningImage = typeof raw.workerProvisioningImage === "string"
|
|
360
|
+
? raw.workerProvisioningImage.trim()
|
|
361
|
+
: "";
|
|
362
|
+
const workerProvisioningPassEnv = parseStringArray(raw.workerProvisioningPassEnv);
|
|
363
|
+
const workerProvisioningExtraEnv = parseStringRecord(raw.workerProvisioningExtraEnv);
|
|
364
|
+
const workerProvisioningDockerNetwork = typeof raw.workerProvisioningDockerNetwork === "string"
|
|
365
|
+
? raw.workerProvisioningDockerNetwork.trim()
|
|
366
|
+
: "";
|
|
367
|
+
const workerProvisioningDockerMounts = parseStringArray(raw.workerProvisioningDockerMounts);
|
|
368
|
+
const workerProvisioningKubernetesNamespace = typeof raw.workerProvisioningKubernetesNamespace === "string" &&
|
|
369
|
+
raw.workerProvisioningKubernetesNamespace.trim()
|
|
370
|
+
? raw.workerProvisioningKubernetesNamespace.trim()
|
|
371
|
+
: "default";
|
|
372
|
+
const workerProvisioningKubernetesContext = typeof raw.workerProvisioningKubernetesContext === "string"
|
|
373
|
+
? raw.workerProvisioningKubernetesContext.trim()
|
|
374
|
+
: "";
|
|
375
|
+
const workerProvisioningKubernetesServiceAccount = typeof raw.workerProvisioningKubernetesServiceAccount === "string"
|
|
376
|
+
? raw.workerProvisioningKubernetesServiceAccount.trim()
|
|
377
|
+
: "";
|
|
378
|
+
const workerProvisioningKubernetesLabels = parseStringRecord(raw.workerProvisioningKubernetesLabels);
|
|
379
|
+
const workerProvisioningKubernetesAnnotations = parseStringRecord(raw.workerProvisioningKubernetesAnnotations);
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
mode,
|
|
383
|
+
port,
|
|
384
|
+
role,
|
|
385
|
+
controllerUrl,
|
|
386
|
+
teamName,
|
|
387
|
+
heartbeatIntervalMs,
|
|
388
|
+
localRoles,
|
|
389
|
+
taskTimeoutMs,
|
|
390
|
+
gitEnabled,
|
|
391
|
+
gitRemoteUrl,
|
|
392
|
+
gitDefaultBranch,
|
|
393
|
+
gitAuthorName,
|
|
394
|
+
gitAuthorEmail,
|
|
395
|
+
workerProvisioningType,
|
|
396
|
+
workerProvisioningControllerUrl,
|
|
397
|
+
workerProvisioningRoles,
|
|
398
|
+
workerProvisioningMinPerRole,
|
|
399
|
+
workerProvisioningMaxPerRole,
|
|
400
|
+
workerProvisioningIdleTtlMs,
|
|
401
|
+
workerProvisioningStartupTimeoutMs,
|
|
402
|
+
workerProvisioningImage,
|
|
403
|
+
workerProvisioningPassEnv,
|
|
404
|
+
workerProvisioningExtraEnv,
|
|
405
|
+
workerProvisioningDockerNetwork,
|
|
406
|
+
workerProvisioningDockerMounts,
|
|
407
|
+
workerProvisioningKubernetesNamespace,
|
|
408
|
+
workerProvisioningKubernetesContext,
|
|
409
|
+
workerProvisioningKubernetesServiceAccount,
|
|
410
|
+
workerProvisioningKubernetesLabels,
|
|
411
|
+
workerProvisioningKubernetesAnnotations,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function parseRoleList(raw: unknown): RoleId[] {
|
|
416
|
+
return Array.isArray(raw)
|
|
417
|
+
? [...new Set(raw
|
|
418
|
+
.filter((entry): entry is RoleId => typeof entry === "string" && VALID_ROLES.includes(entry as RoleId))
|
|
419
|
+
.map((entry) => entry as RoleId))]
|
|
420
|
+
: [];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function parseProvisioningType(raw: unknown): WorkerProvisioningType {
|
|
424
|
+
return typeof raw === "string" && VALID_PROVISIONING_TYPES.includes(raw as WorkerProvisioningType)
|
|
425
|
+
? raw as WorkerProvisioningType
|
|
426
|
+
: "none";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function parseStringArray(raw: unknown): string[] {
|
|
430
|
+
return Array.isArray(raw)
|
|
431
|
+
? [...new Set(raw
|
|
432
|
+
.filter((entry): entry is string => typeof entry === "string")
|
|
433
|
+
.map((entry) => entry.trim())
|
|
434
|
+
.filter(Boolean))]
|
|
435
|
+
: [];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function parseStringRecord(raw: unknown): Record<string, string> {
|
|
439
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
440
|
+
return {};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const result: Record<string, string> = {};
|
|
444
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
445
|
+
if (typeof value !== "string") {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const normalizedKey = key.trim();
|
|
449
|
+
const normalizedValue = value.trim();
|
|
450
|
+
if (!normalizedKey || !normalizedValue) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
result[normalizedKey] = normalizedValue;
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const VALID_PROVISIONING_TYPES: WorkerProvisioningType[] = [
|
|
459
|
+
"none",
|
|
460
|
+
"process",
|
|
461
|
+
"docker",
|
|
462
|
+
"kubernetes",
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
const VALID_ROLES: RoleId[] = [
|
|
466
|
+
"pm", "architect", "developer", "qa",
|
|
467
|
+
"release-engineer", "infra-engineer", "devops", "security-engineer",
|
|
468
|
+
"designer", "marketing",
|
|
469
|
+
];
|