@evo-dev/core 0.0.1-alpha → 0.0.1-alpha.2
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/assets/agents/review/code-reviewer/examples.md +1 -1
- package/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/assets/agents/review/code-reviewer/verification.md +1 -1
- package/assets/skills/coding/knowledge-distillation/SKILL.md +249 -0
- package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/config/index.js +968 -39
- package/dist/index.js +10914 -1476
- package/dist/plugins/index.js +32 -32
- package/package.json +5 -1
- package/src/agents/index.ts +84 -49
- package/src/code-agent-traces/index.ts +521 -0
- package/src/config/index.ts +5 -0
- package/src/config/paths.ts +30 -0
- package/src/config/settings.ts +130 -0
- package/src/config/store.ts +152 -0
- package/src/daemon/index.ts +465 -3
- package/src/evolution/index.ts +2827 -0
- package/src/hooks/index.ts +543 -247
- package/src/index.ts +6 -0
- package/src/knowledge/index.ts +4784 -0
- package/src/pack/index.ts +13 -13
- package/src/plugins/capabilities.ts +40 -42
- package/src/plugins/index.ts +0 -1
- package/src/plugins/types.ts +4 -0
- package/src/protected-zones/index.ts +29 -11
- package/src/runtime-logs/index.ts +798 -0
- package/src/sync/orchestrator.ts +6 -0
- package/src/task/index.ts +3 -3
- package/src/team/index.ts +3069 -0
- package/src/team/mcp.ts +405 -0
- package/src/team/prompts.ts +141 -0
- package/src/workflow/index.ts +6 -6
package/src/daemon/index.ts
CHANGED
|
@@ -2,7 +2,24 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { type IncomingMessage, createServer } from "node:http";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
+
import { listEvolutionTriggers, processEvolutionTriggers } from "../evolution/index.ts";
|
|
5
6
|
import { listObservabilityEvents } from "../observability/index.ts";
|
|
7
|
+
import {
|
|
8
|
+
type TeamRuntimeAdapter,
|
|
9
|
+
isActiveTeamAgentStatus,
|
|
10
|
+
isIdleTeamAgentStatus,
|
|
11
|
+
isMidTurnTeamAgentStatus,
|
|
12
|
+
listTeamRuns,
|
|
13
|
+
markTeamMessagesDelivered,
|
|
14
|
+
readPendingTeamMessagesForRole,
|
|
15
|
+
reconcileTeamRun,
|
|
16
|
+
resumeTeamRun,
|
|
17
|
+
schedulePendingTeamMessageDelivery,
|
|
18
|
+
sendTeamMessage,
|
|
19
|
+
spawnTeamRole,
|
|
20
|
+
stopTeamRole,
|
|
21
|
+
updateTeamAgentHookState,
|
|
22
|
+
} from "../team/index.ts";
|
|
6
23
|
|
|
7
24
|
export interface DaemonPaths {
|
|
8
25
|
rootDir: string;
|
|
@@ -43,6 +60,15 @@ export interface DaemonRequestInput {
|
|
|
43
60
|
token?: string | null;
|
|
44
61
|
homeDir: string;
|
|
45
62
|
origin?: string | null;
|
|
63
|
+
body?: unknown;
|
|
64
|
+
runtimeAdapter?: TeamRuntimeAdapter;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface DaemonTeamStatusSummary {
|
|
68
|
+
run: unknown;
|
|
69
|
+
agents: unknown[];
|
|
70
|
+
stoppedAgents: string[];
|
|
71
|
+
notifications: unknown[];
|
|
46
72
|
}
|
|
47
73
|
|
|
48
74
|
const DEFAULT_PORT = 37645;
|
|
@@ -163,6 +189,66 @@ export async function handleDaemonRequest(
|
|
|
163
189
|
const removed = await cleanupDaemonState(input.homeDir, input.token ?? "");
|
|
164
190
|
return ok({ stopped: true, removed }, warnings);
|
|
165
191
|
}
|
|
192
|
+
if (
|
|
193
|
+
(input.path === "/team/message/enqueue" || input.path === "/teams/send") &&
|
|
194
|
+
input.method === "POST"
|
|
195
|
+
) {
|
|
196
|
+
return dashboardMutation(
|
|
197
|
+
await sendDashboardTeamMessage(input.homeDir, input.body, input.runtimeAdapter),
|
|
198
|
+
warnings,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (input.path === "/team/agent/state" && input.method === "POST") {
|
|
202
|
+
return dashboardMutation(
|
|
203
|
+
await updateDashboardTeamAgentState(input.homeDir, input.body),
|
|
204
|
+
warnings,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (input.path === "/team/message/claim" && input.method === "POST") {
|
|
208
|
+
return ok(await claimDashboardTeamMessages(input.homeDir, input.body), warnings);
|
|
209
|
+
}
|
|
210
|
+
if (input.path === "/team/message/delivered" && input.method === "POST") {
|
|
211
|
+
return dashboardMutation(
|
|
212
|
+
await markDashboardTeamMessagesDelivered(input.homeDir, input.body),
|
|
213
|
+
warnings,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (
|
|
217
|
+
(input.path === "/team/reconcile" || input.path === "/teams/reconcile") &&
|
|
218
|
+
input.method === "POST"
|
|
219
|
+
) {
|
|
220
|
+
await schedulePendingTeamMessageDelivery({
|
|
221
|
+
homeDir: input.homeDir,
|
|
222
|
+
runtimeAdapter: input.runtimeAdapter,
|
|
223
|
+
}).catch((error) =>
|
|
224
|
+
warnings.push(`Team delivery scheduling unavailable: ${describeError(error)}`),
|
|
225
|
+
);
|
|
226
|
+
return ok(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter), warnings);
|
|
227
|
+
}
|
|
228
|
+
if (input.path === "/teams/spawn" && input.method === "POST") {
|
|
229
|
+
return dashboardMutation(
|
|
230
|
+
await spawnDashboardTeamRole(input.homeDir, input.body, input.runtimeAdapter),
|
|
231
|
+
warnings,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (input.path === "/teams/stop-role" && input.method === "POST") {
|
|
235
|
+
return dashboardMutation(
|
|
236
|
+
await stopDashboardTeamRole(input.homeDir, input.body, input.runtimeAdapter),
|
|
237
|
+
warnings,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (input.path === "/teams/resume" && input.method === "POST") {
|
|
241
|
+
return dashboardMutation(
|
|
242
|
+
await resumeDashboardTeamRun(input.homeDir, input.body, input.runtimeAdapter),
|
|
243
|
+
warnings,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (input.path === "/evolution/process" && input.method === "POST") {
|
|
247
|
+
return dashboardMutation(
|
|
248
|
+
await processDashboardEvolutionTriggers(input.homeDir, input.body),
|
|
249
|
+
warnings,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
166
252
|
if (input.method !== "GET") return notFound(warnings);
|
|
167
253
|
|
|
168
254
|
if (input.path === "/tasks")
|
|
@@ -181,8 +267,16 @@ export async function handleDaemonRequest(
|
|
|
181
267
|
await collectDirectorySummaries(join(input.homeDir, ".evodev", "PACKS"), warnings),
|
|
182
268
|
warnings,
|
|
183
269
|
);
|
|
184
|
-
if (input.path === "/runs") return ok(
|
|
185
|
-
if (input.path === "/
|
|
270
|
+
if (input.path === "/runs") return ok(await collectTeamRuns(input.homeDir, warnings), warnings);
|
|
271
|
+
if (input.path === "/evolution/triggers")
|
|
272
|
+
return ok(await collectEvolutionTriggers(input.homeDir, warnings), warnings);
|
|
273
|
+
if (input.path === "/agents")
|
|
274
|
+
return ok(
|
|
275
|
+
(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter)).agents,
|
|
276
|
+
warnings,
|
|
277
|
+
);
|
|
278
|
+
if (input.path === "/teams/status")
|
|
279
|
+
return ok(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter), warnings);
|
|
186
280
|
|
|
187
281
|
return notFound(warnings);
|
|
188
282
|
}
|
|
@@ -211,6 +305,7 @@ export async function runDaemonForeground(input: {
|
|
|
211
305
|
url.searchParams.get("token"),
|
|
212
306
|
homeDir: input.homeDir,
|
|
213
307
|
origin: readHeader(request, "origin"),
|
|
308
|
+
body: await readJsonBody(request),
|
|
214
309
|
});
|
|
215
310
|
|
|
216
311
|
response.writeHead(result.status, { "content-type": "application/json" });
|
|
@@ -248,8 +343,22 @@ export async function runDaemonForeground(input: {
|
|
|
248
343
|
input.write?.(
|
|
249
344
|
`Daemon listening on ${state.lock.host}:${state.lock.port}; token path: ${state.paths.tokenPath}`,
|
|
250
345
|
);
|
|
346
|
+
const reconcileInterval = setInterval(() => {
|
|
347
|
+
void reconcileTeamRun({ homeDir: input.homeDir }).catch(() => undefined);
|
|
348
|
+
}, 2_000);
|
|
349
|
+
reconcileInterval.unref();
|
|
350
|
+
const evolutionInterval = setInterval(() => {
|
|
351
|
+
void processEvolutionTriggers({ homeDir: input.homeDir, limit: 20 })
|
|
352
|
+
.then(() => clearDaemonEvolutionProcessError(input.homeDir))
|
|
353
|
+
.catch((error) => recordDaemonEvolutionProcessError(input.homeDir, error));
|
|
354
|
+
}, 5_000);
|
|
355
|
+
evolutionInterval.unref();
|
|
251
356
|
await new Promise<void>((resolve, reject) => {
|
|
252
|
-
server.once("close",
|
|
357
|
+
server.once("close", () => {
|
|
358
|
+
clearInterval(reconcileInterval);
|
|
359
|
+
clearInterval(evolutionInterval);
|
|
360
|
+
resolve();
|
|
361
|
+
});
|
|
253
362
|
server.once("error", reject);
|
|
254
363
|
});
|
|
255
364
|
}
|
|
@@ -264,6 +373,22 @@ function createDaemonToken(): string {
|
|
|
264
373
|
return randomBytes(32).toString("hex");
|
|
265
374
|
}
|
|
266
375
|
|
|
376
|
+
async function readJsonBody(request: IncomingMessage): Promise<unknown> {
|
|
377
|
+
if (request.method === "GET" || request.method === "HEAD") return undefined;
|
|
378
|
+
const chunks: Buffer[] = [];
|
|
379
|
+
let totalBytes = 0;
|
|
380
|
+
for await (const chunk of request) {
|
|
381
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
382
|
+
totalBytes += buffer.length;
|
|
383
|
+
if (totalBytes > 64 * 1024) throw new Error("Request body too large.");
|
|
384
|
+
chunks.push(buffer);
|
|
385
|
+
}
|
|
386
|
+
if (chunks.length === 0) return undefined;
|
|
387
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
388
|
+
if (raw === "") return undefined;
|
|
389
|
+
return JSON.parse(raw);
|
|
390
|
+
}
|
|
391
|
+
|
|
267
392
|
async function authorize(homeDir: string, token: string | null): Promise<{ ok: boolean }> {
|
|
268
393
|
const expected = await readDaemonToken(homeDir);
|
|
269
394
|
return { ok: expected !== null && token !== null && token === expected };
|
|
@@ -352,6 +477,258 @@ async function collectLearningCandidateSummaries(
|
|
|
352
477
|
});
|
|
353
478
|
}
|
|
354
479
|
|
|
480
|
+
async function collectEvolutionTriggers(homeDir: string, warnings: string[]): Promise<unknown[]> {
|
|
481
|
+
const lastError = await readDaemonEvolutionProcessError(homeDir).catch(() => null);
|
|
482
|
+
if (lastError !== null) warnings.push(`Evolution processor last error: ${lastError}`);
|
|
483
|
+
try {
|
|
484
|
+
return (await listEvolutionTriggers({ homeDir })).map((trigger) =>
|
|
485
|
+
sanitizeMetadata({
|
|
486
|
+
id: trigger.id,
|
|
487
|
+
projectKey: trigger.projectKey,
|
|
488
|
+
runId: trigger.runId,
|
|
489
|
+
roleId: trigger.roleId,
|
|
490
|
+
taskId: trigger.taskId,
|
|
491
|
+
eventType: trigger.eventType,
|
|
492
|
+
triggerStrength: trigger.triggerStrength,
|
|
493
|
+
triggerReason: trigger.triggerReason,
|
|
494
|
+
status: trigger.status,
|
|
495
|
+
attempts: trigger.attempts,
|
|
496
|
+
createdAt: trigger.createdAt,
|
|
497
|
+
updatedAt: trigger.updatedAt,
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
warnings.push(`Evolution triggers unavailable: ${describeError(error)}`);
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function processDashboardEvolutionTriggers(homeDir: string, body: unknown): Promise<unknown> {
|
|
507
|
+
if (body === undefined) {
|
|
508
|
+
return {
|
|
509
|
+
ok: false,
|
|
510
|
+
error: "evolution process requires JSON body with dryRun=true or confirm=true.",
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const input = expectRequestBody(body);
|
|
514
|
+
const dryRun = optionalBodyBoolean(input.dryRun) === true;
|
|
515
|
+
const confirm = optionalBodyBoolean(input.confirm) === true;
|
|
516
|
+
if (!dryRun && !confirm) {
|
|
517
|
+
return {
|
|
518
|
+
ok: false,
|
|
519
|
+
error: "evolution process requires dryRun=true or confirm=true.",
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return processEvolutionTriggers({
|
|
523
|
+
homeDir,
|
|
524
|
+
projectKey: optionalBodyString(input.projectKey),
|
|
525
|
+
runId: optionalBodyString(input.runId),
|
|
526
|
+
limit: optionalBodyNumber(input.limit),
|
|
527
|
+
dryRun,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function collectTeamRuns(homeDir: string, warnings: string[]): Promise<unknown[]> {
|
|
532
|
+
try {
|
|
533
|
+
return (await listTeamRuns({ homeDir })).map((run) =>
|
|
534
|
+
sanitizeMetadata({
|
|
535
|
+
runId: run.runId,
|
|
536
|
+
status: run.status,
|
|
537
|
+
repoRoot: run.repoRoot,
|
|
538
|
+
mainAgentId: run.mainAgentId,
|
|
539
|
+
session: run.tmux.session,
|
|
540
|
+
roleCount: Object.keys(run.roles).length,
|
|
541
|
+
createdAt: run.createdAt,
|
|
542
|
+
updatedAt: run.updatedAt,
|
|
543
|
+
}),
|
|
544
|
+
);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
warnings.push(`Team runs unavailable: ${describeError(error)}`);
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function collectTeamStatus(
|
|
552
|
+
homeDir: string,
|
|
553
|
+
warnings: string[],
|
|
554
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
555
|
+
): Promise<DaemonTeamStatusSummary> {
|
|
556
|
+
try {
|
|
557
|
+
const status = await reconcileTeamRun({ homeDir, runtimeAdapter });
|
|
558
|
+
return sanitizeMetadata({
|
|
559
|
+
run:
|
|
560
|
+
status.run === null
|
|
561
|
+
? null
|
|
562
|
+
: {
|
|
563
|
+
runId: status.run.runId,
|
|
564
|
+
status: status.run.status,
|
|
565
|
+
repoRoot: status.run.repoRoot,
|
|
566
|
+
session: status.run.tmux.session,
|
|
567
|
+
updatedAt: status.run.updatedAt,
|
|
568
|
+
},
|
|
569
|
+
agents: status.agents.map((agent) => ({
|
|
570
|
+
roleId: agent.roleId,
|
|
571
|
+
roleName: agent.roleName,
|
|
572
|
+
runId: agent.runId,
|
|
573
|
+
runtime: agent.runtime,
|
|
574
|
+
model: agent.model,
|
|
575
|
+
thinkingLevel: agent.thinkingLevel,
|
|
576
|
+
status: agent.status,
|
|
577
|
+
paneId: agent.tmux.paneId,
|
|
578
|
+
canReceiveMessages: isActiveTeamAgentStatus(agent.status),
|
|
579
|
+
isIdle: isIdleTeamAgentStatus(agent.status),
|
|
580
|
+
isMidTurn: isMidTurnTeamAgentStatus(agent.status),
|
|
581
|
+
nativeSessionRecorded: agent.nativeSession.sessionId !== null,
|
|
582
|
+
updatedAt: agent.updatedAt,
|
|
583
|
+
})),
|
|
584
|
+
stoppedAgents: status.stoppedAgents.map((agent) => agent.roleId),
|
|
585
|
+
notifications: status.notifications.map((notification) => ({
|
|
586
|
+
ok: notification.ok,
|
|
587
|
+
error: notification.error ?? null,
|
|
588
|
+
delivery: notification.delivery ?? null,
|
|
589
|
+
queuedFor: notification.queuedFor ?? null,
|
|
590
|
+
})),
|
|
591
|
+
}) as DaemonTeamStatusSummary;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
warnings.push(`Team status unavailable: ${describeError(error)}`);
|
|
594
|
+
return { run: null, agents: [], stoppedAgents: [], notifications: [] };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function sendDashboardTeamMessage(
|
|
599
|
+
homeDir: string,
|
|
600
|
+
body: unknown,
|
|
601
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
602
|
+
): Promise<unknown> {
|
|
603
|
+
const input = expectRequestBody(body);
|
|
604
|
+
return sendTeamMessage({
|
|
605
|
+
homeDir,
|
|
606
|
+
runId: optionalBodyString(input.runId),
|
|
607
|
+
fromRoleId: "user",
|
|
608
|
+
toRoleId: expectBodyString(input, "toRoleId"),
|
|
609
|
+
message: expectBodyString(input, "message"),
|
|
610
|
+
runtimeAdapter,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function updateDashboardTeamAgentState(homeDir: string, body: unknown): Promise<unknown> {
|
|
615
|
+
const input = expectRequestBody(body);
|
|
616
|
+
const result = await updateTeamAgentHookState({
|
|
617
|
+
homeDir,
|
|
618
|
+
runId: expectBodyString(input, "runId"),
|
|
619
|
+
roleId: expectBodyString(input, "roleId"),
|
|
620
|
+
hookEvent: expectBodyString(input, "hookEvent"),
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
ok: true,
|
|
624
|
+
roleId: result.agent.roleId,
|
|
625
|
+
status: result.agent.status,
|
|
626
|
+
statusPath: result.statusPath,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function claimDashboardTeamMessages(homeDir: string, body: unknown): Promise<unknown> {
|
|
631
|
+
const input = expectRequestBody(body);
|
|
632
|
+
const runId = expectBodyString(input, "runId");
|
|
633
|
+
const roleId = expectBodyString(input, "roleId");
|
|
634
|
+
const messages = await readPendingTeamMessagesForRole({
|
|
635
|
+
homeDir,
|
|
636
|
+
runId,
|
|
637
|
+
roleId,
|
|
638
|
+
limit: optionalBodyNumber(input.limit),
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
runId,
|
|
642
|
+
roleId,
|
|
643
|
+
messages,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function markDashboardTeamMessagesDelivered(
|
|
648
|
+
homeDir: string,
|
|
649
|
+
body: unknown,
|
|
650
|
+
): Promise<unknown> {
|
|
651
|
+
const input = expectRequestBody(body);
|
|
652
|
+
const messageIdsValue = input.messageIds;
|
|
653
|
+
if (!Array.isArray(messageIdsValue)) throw new Error("Expected body.messageIds array.");
|
|
654
|
+
const messageIds = messageIdsValue.map((value) => {
|
|
655
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
656
|
+
throw new Error("Expected body.messageIds to contain non-empty strings.");
|
|
657
|
+
}
|
|
658
|
+
return value;
|
|
659
|
+
});
|
|
660
|
+
await markTeamMessagesDelivered({
|
|
661
|
+
homeDir,
|
|
662
|
+
runId: expectBodyString(input, "runId"),
|
|
663
|
+
roleId: expectBodyString(input, "roleId"),
|
|
664
|
+
messageIds,
|
|
665
|
+
});
|
|
666
|
+
return { ok: true, delivered: messageIds };
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function spawnDashboardTeamRole(
|
|
670
|
+
homeDir: string,
|
|
671
|
+
body: unknown,
|
|
672
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
673
|
+
): Promise<unknown> {
|
|
674
|
+
const input = expectRequestBody(body);
|
|
675
|
+
const roleId = expectBodyString(input, "roleId");
|
|
676
|
+
const status = await reconcileTeamRun({
|
|
677
|
+
homeDir,
|
|
678
|
+
runId: optionalBodyString(input.runId),
|
|
679
|
+
runtimeAdapter,
|
|
680
|
+
notifyMain: false,
|
|
681
|
+
});
|
|
682
|
+
if (status.run === null) return { ok: false, error: "team-run-not-found" };
|
|
683
|
+
if (status.run.status !== "running") return { ok: false, error: "team-run-not-running" };
|
|
684
|
+
const result = await spawnTeamRole({
|
|
685
|
+
homeDir,
|
|
686
|
+
repoRoot: status.run.repoRoot,
|
|
687
|
+
runId: status.run.runId,
|
|
688
|
+
roleId,
|
|
689
|
+
runtimeAdapter,
|
|
690
|
+
});
|
|
691
|
+
return { ok: true, created: result.created, agent: result.agent };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async function stopDashboardTeamRole(
|
|
695
|
+
homeDir: string,
|
|
696
|
+
body: unknown,
|
|
697
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
698
|
+
): Promise<unknown> {
|
|
699
|
+
const input = expectRequestBody(body);
|
|
700
|
+
return stopTeamRole({
|
|
701
|
+
homeDir,
|
|
702
|
+
runId: optionalBodyString(input.runId),
|
|
703
|
+
roleId: expectBodyString(input, "roleId"),
|
|
704
|
+
runtimeAdapter,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async function resumeDashboardTeamRun(
|
|
709
|
+
homeDir: string,
|
|
710
|
+
body: unknown,
|
|
711
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
712
|
+
): Promise<unknown> {
|
|
713
|
+
const input = expectRequestBody(body);
|
|
714
|
+
const decision = optionalBodyString(input.onSessionMissing);
|
|
715
|
+
if (decision !== undefined && !["ask", "fail", "recreate", "resume-latest"].includes(decision)) {
|
|
716
|
+
return { ok: false, error: "invalid-on-session-missing" };
|
|
717
|
+
}
|
|
718
|
+
const result = await resumeTeamRun({
|
|
719
|
+
homeDir,
|
|
720
|
+
runId: optionalBodyString(input.runId),
|
|
721
|
+
runtimeAdapter,
|
|
722
|
+
missingSessionDecision: decision as "ask" | "fail" | "recreate" | "resume-latest" | undefined,
|
|
723
|
+
});
|
|
724
|
+
return {
|
|
725
|
+
ok: result.decisionRequired.length === 0,
|
|
726
|
+
run: result.run,
|
|
727
|
+
outcomes: result.outcomes,
|
|
728
|
+
decisionRequired: result.decisionRequired,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
355
732
|
async function collectDirectorySummaries(root: string, warnings: string[]): Promise<unknown[]> {
|
|
356
733
|
if (!(await pathExists(root))) {
|
|
357
734
|
warnings.push(`Store not found: ${root}`);
|
|
@@ -379,6 +756,91 @@ function sanitizeMetadata(value: unknown): unknown {
|
|
|
379
756
|
return output;
|
|
380
757
|
}
|
|
381
758
|
|
|
759
|
+
function dashboardMutation(
|
|
760
|
+
data: unknown,
|
|
761
|
+
warnings: string[],
|
|
762
|
+
): { status: number; body: DaemonResponseBody } {
|
|
763
|
+
const okResult = !(
|
|
764
|
+
typeof data === "object" &&
|
|
765
|
+
data !== null &&
|
|
766
|
+
"ok" in data &&
|
|
767
|
+
(data as { ok?: unknown }).ok === false
|
|
768
|
+
);
|
|
769
|
+
return {
|
|
770
|
+
status: okResult ? 200 : 400,
|
|
771
|
+
body: { ok: okResult, data: sanitizeMetadata(data), warnings },
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function expectRequestBody(value: unknown): Record<string, unknown> {
|
|
776
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
777
|
+
throw new Error("Expected JSON object request body.");
|
|
778
|
+
}
|
|
779
|
+
return value as Record<string, unknown>;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function expectBodyString(input: Record<string, unknown>, key: string): string {
|
|
783
|
+
const value = input[key];
|
|
784
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
785
|
+
throw new Error(`Expected non-empty string body.${key}.`);
|
|
786
|
+
}
|
|
787
|
+
return value;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function optionalBodyString(value: unknown): string | undefined {
|
|
791
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function optionalBodyNumber(value: unknown): number | undefined {
|
|
795
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function optionalBodyBoolean(value: unknown): boolean | undefined {
|
|
799
|
+
return typeof value === "boolean" ? value : undefined;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function resolveDaemonEvolutionProcessErrorPath(homeDir: string): string {
|
|
803
|
+
return join(resolveDaemonPaths(homeDir).rootDir, "evolution-process-error.json");
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function recordDaemonEvolutionProcessError(homeDir: string, error: unknown): Promise<void> {
|
|
807
|
+
try {
|
|
808
|
+
const path = resolveDaemonEvolutionProcessErrorPath(homeDir);
|
|
809
|
+
await mkdir(dirname(path), { recursive: true });
|
|
810
|
+
await writeFile(
|
|
811
|
+
path,
|
|
812
|
+
`${JSON.stringify(
|
|
813
|
+
{
|
|
814
|
+
schemaVersion: 1,
|
|
815
|
+
kind: "daemon-evolution-process-error",
|
|
816
|
+
updatedAt: new Date().toISOString(),
|
|
817
|
+
summary: sanitizeMetadata(describeError(error)),
|
|
818
|
+
},
|
|
819
|
+
null,
|
|
820
|
+
2,
|
|
821
|
+
)}\n`,
|
|
822
|
+
{ encoding: "utf8", flag: "w", mode: 0o600 },
|
|
823
|
+
);
|
|
824
|
+
} catch {
|
|
825
|
+
// Daemon background diagnostics must not affect foreground request handling.
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async function clearDaemonEvolutionProcessError(homeDir: string): Promise<void> {
|
|
830
|
+
await rm(resolveDaemonEvolutionProcessErrorPath(homeDir), { force: true }).catch(() => undefined);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async function readDaemonEvolutionProcessError(homeDir: string): Promise<string | null> {
|
|
834
|
+
const path = resolveDaemonEvolutionProcessErrorPath(homeDir);
|
|
835
|
+
if (!(await pathExists(path))) return null;
|
|
836
|
+
const value = JSON.parse(await readFile(path, "utf8")) as { summary?: unknown };
|
|
837
|
+
return typeof value.summary === "string" && value.summary.length > 0 ? value.summary : null;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function describeError(error: unknown): string {
|
|
841
|
+
return error instanceof Error ? error.message : String(error);
|
|
842
|
+
}
|
|
843
|
+
|
|
382
844
|
async function collectNamedFiles(root: string, name: string): Promise<string[]> {
|
|
383
845
|
const entries = await readdir(root, { withFileTypes: true });
|
|
384
846
|
const files: string[] = [];
|