@teamclaws/teamclaw 2026.3.25 → 2026.3.26-1
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 +6 -0
- package/cli.mjs +519 -28
- package/index.ts +31 -16
- package/openclaw.plugin.json +3 -3
- package/package.json +16 -4
- package/src/config.ts +2 -2
- package/src/controller/controller-capacity.ts +23 -0
- package/src/controller/controller-service.ts +27 -1
- package/src/controller/controller-tools.ts +251 -7
- package/src/controller/http-server.ts +976 -38
- package/src/controller/orchestration-manifest.ts +105 -0
- package/src/controller/prompt-injector.ts +42 -7
- package/src/controller/worker-provisioning.ts +171 -13
- package/src/git-collaboration.ts +2 -0
- package/src/interaction-contracts.ts +459 -0
- package/src/task-executor.ts +482 -33
- package/src/types.ts +96 -0
- package/src/ui/app.js +313 -8
- package/src/ui/style.css +152 -0
- package/src/worker/http-handler.ts +15 -7
- package/src/worker/prompt-injector.ts +2 -0
- package/src/worker/skill-installer.ts +13 -0
- package/src/worker/tools.ts +172 -3
- package/src/worker/worker-service.ts +9 -6
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { ROLES } from "./roles.js";
|
|
2
|
+
import type {
|
|
3
|
+
RoleId,
|
|
4
|
+
TaskHandoffContract,
|
|
5
|
+
TaskInfo,
|
|
6
|
+
TeamMessage,
|
|
7
|
+
TeamMessageContract,
|
|
8
|
+
TeamMessageIntent,
|
|
9
|
+
WorkerProgressContract,
|
|
10
|
+
WorkerTaskResultContract,
|
|
11
|
+
WorkerTaskResultDeliverable,
|
|
12
|
+
WorkerTaskResultFollowUp,
|
|
13
|
+
WorkerTaskResultOutcome,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
const CONTRACT_VERSION = "1.0";
|
|
17
|
+
const ROLE_IDS = new Set<RoleId>(ROLES.map((role) => role.id));
|
|
18
|
+
const RESULT_DELIVERABLE_KINDS = new Set<WorkerTaskResultDeliverable["kind"]>([
|
|
19
|
+
"file",
|
|
20
|
+
"directory",
|
|
21
|
+
"command",
|
|
22
|
+
"artifact",
|
|
23
|
+
"note",
|
|
24
|
+
]);
|
|
25
|
+
const RESULT_FOLLOW_UP_TYPES = new Set<WorkerTaskResultFollowUp["type"]>([
|
|
26
|
+
"review",
|
|
27
|
+
"handoff",
|
|
28
|
+
"clarification",
|
|
29
|
+
"downstream-task",
|
|
30
|
+
]);
|
|
31
|
+
const MESSAGE_INTENTS = new Set<TeamMessageIntent>([
|
|
32
|
+
"question",
|
|
33
|
+
"announcement",
|
|
34
|
+
"handoff",
|
|
35
|
+
"review-request",
|
|
36
|
+
"review-response",
|
|
37
|
+
"update",
|
|
38
|
+
"coordination",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export function normalizeContractStringList(raw: unknown): string[] {
|
|
42
|
+
if (!Array.isArray(raw)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return raw
|
|
46
|
+
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function normalizeOptionalContractText(raw: unknown): string | undefined {
|
|
51
|
+
if (typeof raw !== "string") {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const normalized = raw.trim();
|
|
55
|
+
return normalized || undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function normalizeContractRole(raw: unknown): RoleId | undefined {
|
|
59
|
+
if (typeof raw !== "string") {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const normalized = raw.trim() as RoleId;
|
|
63
|
+
return normalized && ROLE_IDS.has(normalized) ? normalized : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function summarizeContractText(text: string, maxChars = 180): string {
|
|
67
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
68
|
+
if (!normalized) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
if (normalized.length <= maxChars) {
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
return `${normalized.slice(0, maxChars).trimEnd()}…`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function ensureTeamMessageContract(
|
|
78
|
+
raw: unknown,
|
|
79
|
+
fallback: {
|
|
80
|
+
type: TeamMessage["type"];
|
|
81
|
+
content: string;
|
|
82
|
+
toRole?: RoleId;
|
|
83
|
+
taskId?: string;
|
|
84
|
+
summary?: string;
|
|
85
|
+
details?: string;
|
|
86
|
+
requestedAction?: string;
|
|
87
|
+
needsResponse?: boolean;
|
|
88
|
+
references?: string[];
|
|
89
|
+
intent?: TeamMessageIntent;
|
|
90
|
+
},
|
|
91
|
+
): TeamMessageContract {
|
|
92
|
+
return normalizeTeamMessageContract(raw) ?? buildBackfilledTeamMessageContract(fallback);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function normalizeTeamMessageContract(raw: unknown): TeamMessageContract | null {
|
|
96
|
+
if (!raw || typeof raw !== "object") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const input = raw as Record<string, unknown>;
|
|
100
|
+
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
101
|
+
if (!summary) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const intent = normalizeMessageIntent(input.intent) ?? "update";
|
|
105
|
+
return {
|
|
106
|
+
version: typeof input.version === "string" && input.version.trim() ? input.version.trim() : CONTRACT_VERSION,
|
|
107
|
+
intent,
|
|
108
|
+
summary,
|
|
109
|
+
details: normalizeOptionalContractText(input.details),
|
|
110
|
+
requestedAction: normalizeOptionalContractText(input.requestedAction),
|
|
111
|
+
requestedRole: normalizeContractRole(input.requestedRole),
|
|
112
|
+
needsResponse: Boolean(input.needsResponse),
|
|
113
|
+
references: normalizeContractStringList(input.references),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function normalizeWorkerProgressContract(raw: unknown): WorkerProgressContract | null {
|
|
118
|
+
if (!raw || typeof raw !== "object") {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const input = raw as Record<string, unknown>;
|
|
122
|
+
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
123
|
+
if (!summary) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const status = normalizeProgressStatus(input.status) ?? "in_progress";
|
|
127
|
+
return {
|
|
128
|
+
version: typeof input.version === "string" && input.version.trim() ? input.version.trim() : CONTRACT_VERSION,
|
|
129
|
+
summary,
|
|
130
|
+
status,
|
|
131
|
+
currentStep: normalizeOptionalContractText(input.currentStep),
|
|
132
|
+
nextStep: normalizeOptionalContractText(input.nextStep),
|
|
133
|
+
blockers: normalizeContractStringList(input.blockers),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function backfillWorkerProgressContract(progress: string, status?: string): WorkerProgressContract | undefined {
|
|
138
|
+
const normalized = progress.trim();
|
|
139
|
+
if (!normalized) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
version: CONTRACT_VERSION,
|
|
144
|
+
summary: summarizeContractText(normalized),
|
|
145
|
+
status: normalizeProgressStatus(status) ?? "in_progress",
|
|
146
|
+
currentStep: normalized,
|
|
147
|
+
blockers: extractQuestionOrBulletLines(normalized, 3, /blocked|waiting|stuck|need/i),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function renderWorkerProgressText(
|
|
152
|
+
contract: WorkerProgressContract | undefined,
|
|
153
|
+
fallbackProgress?: string,
|
|
154
|
+
): string {
|
|
155
|
+
if (!contract) {
|
|
156
|
+
return fallbackProgress?.trim() ?? "";
|
|
157
|
+
}
|
|
158
|
+
const lines = [contract.summary];
|
|
159
|
+
if (contract.currentStep) {
|
|
160
|
+
lines.push(`Current step: ${contract.currentStep}`);
|
|
161
|
+
}
|
|
162
|
+
if (contract.nextStep) {
|
|
163
|
+
lines.push(`Next step: ${contract.nextStep}`);
|
|
164
|
+
}
|
|
165
|
+
if (contract.blockers.length > 0) {
|
|
166
|
+
lines.push(`Blockers: ${contract.blockers.join("; ")}`);
|
|
167
|
+
}
|
|
168
|
+
return lines.filter(Boolean).join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function normalizeTaskHandoffContract(
|
|
172
|
+
raw: unknown,
|
|
173
|
+
fallback: {
|
|
174
|
+
targetRole?: RoleId;
|
|
175
|
+
reason: string;
|
|
176
|
+
summary?: string;
|
|
177
|
+
expectedNextStep?: string;
|
|
178
|
+
artifacts?: string[];
|
|
179
|
+
},
|
|
180
|
+
): TaskHandoffContract {
|
|
181
|
+
if (raw && typeof raw === "object") {
|
|
182
|
+
const input = raw as Record<string, unknown>;
|
|
183
|
+
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
184
|
+
const reason = typeof input.reason === "string" ? input.reason.trim() : "";
|
|
185
|
+
if (summary && reason) {
|
|
186
|
+
return {
|
|
187
|
+
version: typeof input.version === "string" && input.version.trim() ? input.version.trim() : CONTRACT_VERSION,
|
|
188
|
+
summary,
|
|
189
|
+
reason,
|
|
190
|
+
targetRole: normalizeContractRole(input.targetRole) ?? fallback.targetRole,
|
|
191
|
+
expectedNextStep: normalizeOptionalContractText(input.expectedNextStep),
|
|
192
|
+
artifacts: normalizeContractStringList(input.artifacts),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
version: CONTRACT_VERSION,
|
|
199
|
+
summary: fallback.summary?.trim() || buildDefaultHandoffSummary(fallback.targetRole, fallback.reason),
|
|
200
|
+
reason: fallback.reason.trim(),
|
|
201
|
+
targetRole: fallback.targetRole,
|
|
202
|
+
expectedNextStep: fallback.expectedNextStep?.trim() || undefined,
|
|
203
|
+
artifacts: (fallback.artifacts ?? []).map((entry) => entry.trim()).filter(Boolean),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function normalizeWorkerTaskResultContract(raw: unknown): WorkerTaskResultContract | null {
|
|
208
|
+
if (!raw || typeof raw !== "object") {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const input = raw as Record<string, unknown>;
|
|
212
|
+
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
213
|
+
if (!summary) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
version: typeof input.version === "string" && input.version.trim() ? input.version.trim() : CONTRACT_VERSION,
|
|
218
|
+
outcome: normalizeResultOutcome(input.outcome),
|
|
219
|
+
summary,
|
|
220
|
+
deliverables: normalizeResultDeliverables(input.deliverables),
|
|
221
|
+
keyPoints: normalizeContractStringList(input.keyPoints),
|
|
222
|
+
blockers: normalizeContractStringList(input.blockers),
|
|
223
|
+
followUps: normalizeResultFollowUps(input.followUps),
|
|
224
|
+
questions: normalizeContractStringList(input.questions),
|
|
225
|
+
notes: normalizeOptionalContractText(input.notes),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function backfillWorkerTaskResultContract(
|
|
230
|
+
task: Pick<TaskInfo, "title" | "description" | "assignedRole" | "lastHandoff"> | undefined,
|
|
231
|
+
result: string,
|
|
232
|
+
error?: string,
|
|
233
|
+
): WorkerTaskResultContract {
|
|
234
|
+
const normalizedResult = result.trim();
|
|
235
|
+
const normalizedError = error?.trim();
|
|
236
|
+
const summarySource = normalizedError
|
|
237
|
+
|| firstMeaningfulLine(normalizedResult)
|
|
238
|
+
|| task?.lastHandoff?.summary
|
|
239
|
+
|| task?.description
|
|
240
|
+
|| task?.title
|
|
241
|
+
|| "Worker result summary unavailable.";
|
|
242
|
+
const outcome: WorkerTaskResultOutcome = normalizedError
|
|
243
|
+
? "failed"
|
|
244
|
+
: task?.lastHandoff
|
|
245
|
+
? "blocked"
|
|
246
|
+
: "completed";
|
|
247
|
+
const keyPoints = extractQuestionOrBulletLines(normalizedResult, 5);
|
|
248
|
+
const questions = extractQuestionOrBulletLines(normalizedResult, 5, /\?$/);
|
|
249
|
+
const blockers = normalizedError
|
|
250
|
+
? [normalizedError]
|
|
251
|
+
: task?.lastHandoff
|
|
252
|
+
? [task.lastHandoff.reason]
|
|
253
|
+
: [];
|
|
254
|
+
const followUps: WorkerTaskResultFollowUp[] = task?.lastHandoff
|
|
255
|
+
? [{
|
|
256
|
+
type: "handoff",
|
|
257
|
+
targetRole: task.lastHandoff.targetRole,
|
|
258
|
+
reason: task.lastHandoff.reason,
|
|
259
|
+
}]
|
|
260
|
+
: [];
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
version: CONTRACT_VERSION,
|
|
264
|
+
outcome,
|
|
265
|
+
summary: summarizeContractText(summarySource),
|
|
266
|
+
deliverables: inferResultDeliverables(normalizedResult, normalizedError),
|
|
267
|
+
keyPoints,
|
|
268
|
+
blockers,
|
|
269
|
+
followUps,
|
|
270
|
+
questions,
|
|
271
|
+
notes: "Backfilled by TeamClaw because the worker did not submit a structured result contract.",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildBackfilledTeamMessageContract(fallback: {
|
|
276
|
+
type: TeamMessage["type"];
|
|
277
|
+
content: string;
|
|
278
|
+
toRole?: RoleId;
|
|
279
|
+
taskId?: string;
|
|
280
|
+
summary?: string;
|
|
281
|
+
details?: string;
|
|
282
|
+
requestedAction?: string;
|
|
283
|
+
needsResponse?: boolean;
|
|
284
|
+
references?: string[];
|
|
285
|
+
intent?: TeamMessageIntent;
|
|
286
|
+
}): TeamMessageContract {
|
|
287
|
+
const summary = fallback.summary?.trim() || summarizeContractText(fallback.content);
|
|
288
|
+
const intent = fallback.intent ?? inferMessageIntent(fallback.type, fallback.content);
|
|
289
|
+
const requestedAction = fallback.requestedAction?.trim() || buildDefaultRequestedAction(intent, fallback.toRole);
|
|
290
|
+
const references = [...(fallback.references ?? []), ...(fallback.taskId ? [fallback.taskId] : [])]
|
|
291
|
+
.map((entry) => entry.trim())
|
|
292
|
+
.filter(Boolean);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
version: CONTRACT_VERSION,
|
|
296
|
+
intent,
|
|
297
|
+
summary: summary || "TeamClaw message",
|
|
298
|
+
details: fallback.details?.trim() || deriveMessageDetails(summary, fallback.content),
|
|
299
|
+
requestedAction: requestedAction || undefined,
|
|
300
|
+
requestedRole: fallback.toRole,
|
|
301
|
+
needsResponse: typeof fallback.needsResponse === "boolean" ? fallback.needsResponse : intent === "question" || intent === "review-request" || intent === "handoff",
|
|
302
|
+
references: Array.from(new Set(references)),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function normalizeResultDeliverables(raw: unknown): WorkerTaskResultDeliverable[] {
|
|
307
|
+
if (!Array.isArray(raw)) {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
return raw
|
|
311
|
+
.filter((entry): entry is Record<string, unknown> => !!entry && typeof entry === "object")
|
|
312
|
+
.map((entry) => {
|
|
313
|
+
const kind = typeof entry.kind === "string" && RESULT_DELIVERABLE_KINDS.has(entry.kind as WorkerTaskResultDeliverable["kind"])
|
|
314
|
+
? entry.kind as WorkerTaskResultDeliverable["kind"]
|
|
315
|
+
: "note";
|
|
316
|
+
const value = typeof entry.value === "string" ? entry.value.trim() : "";
|
|
317
|
+
const summary = normalizeOptionalContractText(entry.summary);
|
|
318
|
+
return value ? { kind, value, summary } : null;
|
|
319
|
+
})
|
|
320
|
+
.filter((entry): entry is WorkerTaskResultDeliverable => !!entry);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeResultFollowUps(raw: unknown): WorkerTaskResultFollowUp[] {
|
|
324
|
+
if (!Array.isArray(raw)) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
return raw
|
|
328
|
+
.filter((entry): entry is Record<string, unknown> => !!entry && typeof entry === "object")
|
|
329
|
+
.map((entry) => {
|
|
330
|
+
const type = typeof entry.type === "string" && RESULT_FOLLOW_UP_TYPES.has(entry.type as WorkerTaskResultFollowUp["type"])
|
|
331
|
+
? entry.type as WorkerTaskResultFollowUp["type"]
|
|
332
|
+
: null;
|
|
333
|
+
const reason = typeof entry.reason === "string" ? entry.reason.trim() : "";
|
|
334
|
+
if (!type || !reason) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
type,
|
|
339
|
+
targetRole: normalizeContractRole(entry.targetRole),
|
|
340
|
+
reason,
|
|
341
|
+
};
|
|
342
|
+
})
|
|
343
|
+
.filter((entry): entry is WorkerTaskResultFollowUp => !!entry);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function normalizeResultOutcome(raw: unknown): WorkerTaskResultOutcome {
|
|
347
|
+
if (raw === "blocked" || raw === "failed") {
|
|
348
|
+
return raw;
|
|
349
|
+
}
|
|
350
|
+
return "completed";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function normalizeProgressStatus(raw: unknown): WorkerProgressContract["status"] | null {
|
|
354
|
+
if (raw === "in_progress" || raw === "review") {
|
|
355
|
+
return raw;
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeMessageIntent(raw: unknown): TeamMessageIntent | null {
|
|
361
|
+
if (typeof raw !== "string") {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const normalized = raw.trim() as TeamMessageIntent;
|
|
365
|
+
return MESSAGE_INTENTS.has(normalized) ? normalized : null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function inferMessageIntent(type: TeamMessage["type"], content: string): TeamMessageIntent {
|
|
369
|
+
if (type === "review-request") {
|
|
370
|
+
return "review-request";
|
|
371
|
+
}
|
|
372
|
+
if (content.includes("?")) {
|
|
373
|
+
return "question";
|
|
374
|
+
}
|
|
375
|
+
return type === "broadcast" ? "announcement" : "update";
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function buildDefaultRequestedAction(intent: TeamMessageIntent, toRole?: RoleId): string {
|
|
379
|
+
switch (intent) {
|
|
380
|
+
case "question":
|
|
381
|
+
return "Answer the question so the sender can continue safely.";
|
|
382
|
+
case "review-request":
|
|
383
|
+
return toRole
|
|
384
|
+
? `Review the referenced work as ${toRole} and reply with findings.`
|
|
385
|
+
: "Review the referenced work and reply with findings.";
|
|
386
|
+
case "handoff":
|
|
387
|
+
return "Take over the next step described in the handoff summary.";
|
|
388
|
+
case "announcement":
|
|
389
|
+
return "Read the update and align your work if needed.";
|
|
390
|
+
default:
|
|
391
|
+
return "";
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function buildDefaultHandoffSummary(targetRole: RoleId | undefined, reason: string): string {
|
|
396
|
+
if (targetRole) {
|
|
397
|
+
return `Hand off the current task to ${targetRole}: ${summarizeContractText(reason)}`;
|
|
398
|
+
}
|
|
399
|
+
return `Hand off the current task: ${summarizeContractText(reason)}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function deriveMessageDetails(summary: string, content: string): string | undefined {
|
|
403
|
+
const normalized = content.trim();
|
|
404
|
+
if (!normalized || normalized === summary) {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
return normalized;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function firstMeaningfulLine(text: string): string {
|
|
411
|
+
for (const line of text.split(/\n+/)) {
|
|
412
|
+
const normalized = line.replace(/^[-*]\s*/, "").trim();
|
|
413
|
+
if (normalized) {
|
|
414
|
+
return normalized;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return "";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function extractQuestionOrBulletLines(text: string, maxItems: number, matcher?: RegExp): string[] {
|
|
421
|
+
const lines = text
|
|
422
|
+
.split(/\n+/)
|
|
423
|
+
.map((line) => line.replace(/^[-*]\s*/, "").trim())
|
|
424
|
+
.filter(Boolean)
|
|
425
|
+
.filter((line) => !matcher || matcher.test(line));
|
|
426
|
+
return Array.from(new Set(lines.map((line) => summarizeContractText(line, 220)))).slice(0, maxItems);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function inferResultDeliverables(result: string, error?: string): WorkerTaskResultDeliverable[] {
|
|
430
|
+
if (error) {
|
|
431
|
+
return [{
|
|
432
|
+
kind: "note",
|
|
433
|
+
value: error,
|
|
434
|
+
summary: "Execution error surfaced by the worker.",
|
|
435
|
+
}];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const deliverables: WorkerTaskResultDeliverable[] = [];
|
|
439
|
+
const pathMatches = Array.from(result.matchAll(/(?:^|[\s:(])([A-Za-z0-9_./-]+\.[A-Za-z0-9_-]+)/g))
|
|
440
|
+
.map((match) => match[1])
|
|
441
|
+
.filter(Boolean);
|
|
442
|
+
for (const filePath of Array.from(new Set(pathMatches)).slice(0, 5)) {
|
|
443
|
+
deliverables.push({
|
|
444
|
+
kind: "file",
|
|
445
|
+
value: filePath,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (deliverables.length > 0) {
|
|
449
|
+
return deliverables;
|
|
450
|
+
}
|
|
451
|
+
if (!result.trim()) {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
return [{
|
|
455
|
+
kind: "note",
|
|
456
|
+
value: summarizeContractText(result, 300),
|
|
457
|
+
summary: "Backfilled from the worker's final reply.",
|
|
458
|
+
}];
|
|
459
|
+
}
|