@evo-dev/core 0.0.1-alpha → 0.0.1-alpha.10
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 +251 -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/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -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 +1115 -81
- package/dist/index.js +13796 -2196
- package/dist/plugins/index.js +32 -32
- package/package.json +5 -1
- package/src/agents/index.ts +63 -292
- package/src/code-agent-traces/index.ts +520 -0
- package/src/config/index.ts +7 -0
- package/src/config/paths.ts +30 -0
- package/src/config/settings.ts +201 -0
- package/src/config/store.ts +152 -0
- package/src/daemon/index.ts +462 -40
- package/src/evolution/candidates/index.ts +564 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +281 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +7 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/segment.ts +202 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +379 -0
- package/src/evolution/evidence/session-memory/types.ts +221 -0
- package/src/evolution/evidence/session-memory/updater.ts +191 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -0
- package/src/evolution/knowledge/index.ts +5427 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +528 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +568 -0
- package/src/evolution/shared.ts +758 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +652 -376
- package/src/index.ts +16 -3
- 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/projects/index.ts +453 -0
- package/src/protected-zones/index.ts +29 -11
- package/src/runtime-logs/index.ts +790 -0
- package/src/sync/orchestrator.ts +6 -0
- package/src/team/index.ts +3642 -0
- package/src/team/mcp.ts +405 -0
- package/src/team/prompts.ts +141 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- package/src/workflow/index.ts +6 -24
- package/src/project/index.ts +0 -507
- package/src/task/index.ts +0 -840
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/control/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,10 +189,68 @@ 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
|
-
if (input.path === "/tasks")
|
|
169
|
-
return ok(await collectTaskSummaries(input.homeDir, warnings), warnings);
|
|
170
254
|
if (input.path === "/observability/events")
|
|
171
255
|
return ok(await collectObservabilitySummaries(input.homeDir, warnings), warnings);
|
|
172
256
|
if (input.path === "/memory/candidates")
|
|
@@ -181,8 +265,16 @@ export async function handleDaemonRequest(
|
|
|
181
265
|
await collectDirectorySummaries(join(input.homeDir, ".evodev", "PACKS"), warnings),
|
|
182
266
|
warnings,
|
|
183
267
|
);
|
|
184
|
-
if (input.path === "/runs") return ok(
|
|
185
|
-
if (input.path === "/
|
|
268
|
+
if (input.path === "/runs") return ok(await collectTeamRuns(input.homeDir, warnings), warnings);
|
|
269
|
+
if (input.path === "/evolution/triggers")
|
|
270
|
+
return ok(await collectEvolutionTriggers(input.homeDir, warnings), warnings);
|
|
271
|
+
if (input.path === "/agents")
|
|
272
|
+
return ok(
|
|
273
|
+
(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter)).agents,
|
|
274
|
+
warnings,
|
|
275
|
+
);
|
|
276
|
+
if (input.path === "/teams/status")
|
|
277
|
+
return ok(await collectTeamStatus(input.homeDir, warnings, input.runtimeAdapter), warnings);
|
|
186
278
|
|
|
187
279
|
return notFound(warnings);
|
|
188
280
|
}
|
|
@@ -211,6 +303,7 @@ export async function runDaemonForeground(input: {
|
|
|
211
303
|
url.searchParams.get("token"),
|
|
212
304
|
homeDir: input.homeDir,
|
|
213
305
|
origin: readHeader(request, "origin"),
|
|
306
|
+
body: await readJsonBody(request),
|
|
214
307
|
});
|
|
215
308
|
|
|
216
309
|
response.writeHead(result.status, { "content-type": "application/json" });
|
|
@@ -248,8 +341,22 @@ export async function runDaemonForeground(input: {
|
|
|
248
341
|
input.write?.(
|
|
249
342
|
`Daemon listening on ${state.lock.host}:${state.lock.port}; token path: ${state.paths.tokenPath}`,
|
|
250
343
|
);
|
|
344
|
+
const reconcileInterval = setInterval(() => {
|
|
345
|
+
void reconcileTeamRun({ homeDir: input.homeDir }).catch(() => undefined);
|
|
346
|
+
}, 2_000);
|
|
347
|
+
reconcileInterval.unref();
|
|
348
|
+
const evolutionInterval = setInterval(() => {
|
|
349
|
+
void processEvolutionTriggers({ homeDir: input.homeDir, limit: 20 })
|
|
350
|
+
.then(() => clearDaemonEvolutionProcessError(input.homeDir))
|
|
351
|
+
.catch((error) => recordDaemonEvolutionProcessError(input.homeDir, error));
|
|
352
|
+
}, 5_000);
|
|
353
|
+
evolutionInterval.unref();
|
|
251
354
|
await new Promise<void>((resolve, reject) => {
|
|
252
|
-
server.once("close",
|
|
355
|
+
server.once("close", () => {
|
|
356
|
+
clearInterval(reconcileInterval);
|
|
357
|
+
clearInterval(evolutionInterval);
|
|
358
|
+
resolve();
|
|
359
|
+
});
|
|
253
360
|
server.once("error", reject);
|
|
254
361
|
});
|
|
255
362
|
}
|
|
@@ -264,6 +371,22 @@ function createDaemonToken(): string {
|
|
|
264
371
|
return randomBytes(32).toString("hex");
|
|
265
372
|
}
|
|
266
373
|
|
|
374
|
+
async function readJsonBody(request: IncomingMessage): Promise<unknown> {
|
|
375
|
+
if (request.method === "GET" || request.method === "HEAD") return undefined;
|
|
376
|
+
const chunks: Buffer[] = [];
|
|
377
|
+
let totalBytes = 0;
|
|
378
|
+
for await (const chunk of request) {
|
|
379
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
380
|
+
totalBytes += buffer.length;
|
|
381
|
+
if (totalBytes > 64 * 1024) throw new Error("Request body too large.");
|
|
382
|
+
chunks.push(buffer);
|
|
383
|
+
}
|
|
384
|
+
if (chunks.length === 0) return undefined;
|
|
385
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
386
|
+
if (raw === "") return undefined;
|
|
387
|
+
return JSON.parse(raw);
|
|
388
|
+
}
|
|
389
|
+
|
|
267
390
|
async function authorize(homeDir: string, token: string | null): Promise<{ ok: boolean }> {
|
|
268
391
|
const expected = await readDaemonToken(homeDir);
|
|
269
392
|
return { ok: expected !== null && token !== null && token === expected };
|
|
@@ -282,33 +405,6 @@ function isAllowedLocalOrigin(origin: string | null): boolean {
|
|
|
282
405
|
}
|
|
283
406
|
}
|
|
284
407
|
|
|
285
|
-
async function collectTaskSummaries(homeDir: string, warnings: string[]): Promise<unknown[]> {
|
|
286
|
-
const root = join(homeDir, ".evodev", "STATE", "tasks");
|
|
287
|
-
if (!(await pathExists(root))) {
|
|
288
|
-
warnings.push("Task store not found; returning empty tasks.");
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
const contracts = await collectNamedFiles(root, "contract.json");
|
|
292
|
-
const summaries: unknown[] = [];
|
|
293
|
-
for (const file of contracts) {
|
|
294
|
-
try {
|
|
295
|
-
const contract = JSON.parse(await readFile(file, "utf8"));
|
|
296
|
-
summaries.push(
|
|
297
|
-
sanitizeMetadata({
|
|
298
|
-
taskId: contract.taskId,
|
|
299
|
-
status: contract.status,
|
|
300
|
-
mode: contract.route?.mode ?? null,
|
|
301
|
-
workflowId: contract.route?.workflowId ?? null,
|
|
302
|
-
verificationStatus: contract.verification?.status ?? null,
|
|
303
|
-
}),
|
|
304
|
-
);
|
|
305
|
-
} catch {
|
|
306
|
-
warnings.push(`Skipped unreadable task contract: ${file}`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return summaries;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
408
|
async function collectObservabilitySummaries(
|
|
313
409
|
homeDir: string,
|
|
314
410
|
warnings: string[],
|
|
@@ -352,6 +448,258 @@ async function collectLearningCandidateSummaries(
|
|
|
352
448
|
});
|
|
353
449
|
}
|
|
354
450
|
|
|
451
|
+
async function collectEvolutionTriggers(homeDir: string, warnings: string[]): Promise<unknown[]> {
|
|
452
|
+
const lastError = await readDaemonEvolutionProcessError(homeDir).catch(() => null);
|
|
453
|
+
if (lastError !== null) warnings.push(`Evolution processor last error: ${lastError}`);
|
|
454
|
+
try {
|
|
455
|
+
return (await listEvolutionTriggers({ homeDir })).map((trigger) =>
|
|
456
|
+
sanitizeMetadata({
|
|
457
|
+
id: trigger.id,
|
|
458
|
+
projectKey: trigger.projectKey,
|
|
459
|
+
runId: trigger.runId,
|
|
460
|
+
roleId: trigger.roleId,
|
|
461
|
+
taskId: trigger.taskId,
|
|
462
|
+
eventType: trigger.eventType,
|
|
463
|
+
triggerStrength: trigger.triggerStrength,
|
|
464
|
+
triggerReason: trigger.triggerReason,
|
|
465
|
+
status: trigger.status,
|
|
466
|
+
attempts: trigger.attempts,
|
|
467
|
+
createdAt: trigger.createdAt,
|
|
468
|
+
updatedAt: trigger.updatedAt,
|
|
469
|
+
}),
|
|
470
|
+
);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
warnings.push(`Evolution triggers unavailable: ${describeError(error)}`);
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function processDashboardEvolutionTriggers(homeDir: string, body: unknown): Promise<unknown> {
|
|
478
|
+
if (body === undefined) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
error: "evolution process requires JSON body with dryRun=true or confirm=true.",
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const input = expectRequestBody(body);
|
|
485
|
+
const dryRun = optionalBodyBoolean(input.dryRun) === true;
|
|
486
|
+
const confirm = optionalBodyBoolean(input.confirm) === true;
|
|
487
|
+
if (!dryRun && !confirm) {
|
|
488
|
+
return {
|
|
489
|
+
ok: false,
|
|
490
|
+
error: "evolution process requires dryRun=true or confirm=true.",
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return processEvolutionTriggers({
|
|
494
|
+
homeDir,
|
|
495
|
+
projectKey: optionalBodyString(input.projectKey),
|
|
496
|
+
runId: optionalBodyString(input.runId),
|
|
497
|
+
limit: optionalBodyNumber(input.limit),
|
|
498
|
+
dryRun,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function collectTeamRuns(homeDir: string, warnings: string[]): Promise<unknown[]> {
|
|
503
|
+
try {
|
|
504
|
+
return (await listTeamRuns({ homeDir })).map((run) =>
|
|
505
|
+
sanitizeMetadata({
|
|
506
|
+
runId: run.runId,
|
|
507
|
+
status: run.status,
|
|
508
|
+
repoRoot: run.repoRoot,
|
|
509
|
+
mainAgentId: run.mainAgentId,
|
|
510
|
+
session: run.tmux.session,
|
|
511
|
+
roleCount: Object.keys(run.roles).length,
|
|
512
|
+
createdAt: run.createdAt,
|
|
513
|
+
updatedAt: run.updatedAt,
|
|
514
|
+
}),
|
|
515
|
+
);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
warnings.push(`Team runs unavailable: ${describeError(error)}`);
|
|
518
|
+
return [];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function collectTeamStatus(
|
|
523
|
+
homeDir: string,
|
|
524
|
+
warnings: string[],
|
|
525
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
526
|
+
): Promise<DaemonTeamStatusSummary> {
|
|
527
|
+
try {
|
|
528
|
+
const status = await reconcileTeamRun({ homeDir, runtimeAdapter });
|
|
529
|
+
return sanitizeMetadata({
|
|
530
|
+
run:
|
|
531
|
+
status.run === null
|
|
532
|
+
? null
|
|
533
|
+
: {
|
|
534
|
+
runId: status.run.runId,
|
|
535
|
+
status: status.run.status,
|
|
536
|
+
repoRoot: status.run.repoRoot,
|
|
537
|
+
session: status.run.tmux.session,
|
|
538
|
+
updatedAt: status.run.updatedAt,
|
|
539
|
+
},
|
|
540
|
+
agents: status.agents.map((agent) => ({
|
|
541
|
+
roleId: agent.roleId,
|
|
542
|
+
roleName: agent.roleName,
|
|
543
|
+
runId: agent.runId,
|
|
544
|
+
runtime: agent.runtime,
|
|
545
|
+
model: agent.model,
|
|
546
|
+
thinkingLevel: agent.thinkingLevel,
|
|
547
|
+
status: agent.status,
|
|
548
|
+
paneId: agent.tmux.paneId,
|
|
549
|
+
canReceiveMessages: isActiveTeamAgentStatus(agent.status),
|
|
550
|
+
isIdle: isIdleTeamAgentStatus(agent.status),
|
|
551
|
+
isMidTurn: isMidTurnTeamAgentStatus(agent.status),
|
|
552
|
+
nativeSessionRecorded: agent.nativeSession.sessionId !== null,
|
|
553
|
+
updatedAt: agent.updatedAt,
|
|
554
|
+
})),
|
|
555
|
+
stoppedAgents: status.stoppedAgents.map((agent) => agent.roleId),
|
|
556
|
+
notifications: status.notifications.map((notification) => ({
|
|
557
|
+
ok: notification.ok,
|
|
558
|
+
error: notification.error ?? null,
|
|
559
|
+
delivery: notification.delivery ?? null,
|
|
560
|
+
queuedFor: notification.queuedFor ?? null,
|
|
561
|
+
})),
|
|
562
|
+
}) as DaemonTeamStatusSummary;
|
|
563
|
+
} catch (error) {
|
|
564
|
+
warnings.push(`Team status unavailable: ${describeError(error)}`);
|
|
565
|
+
return { run: null, agents: [], stoppedAgents: [], notifications: [] };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function sendDashboardTeamMessage(
|
|
570
|
+
homeDir: string,
|
|
571
|
+
body: unknown,
|
|
572
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
573
|
+
): Promise<unknown> {
|
|
574
|
+
const input = expectRequestBody(body);
|
|
575
|
+
return sendTeamMessage({
|
|
576
|
+
homeDir,
|
|
577
|
+
runId: optionalBodyString(input.runId),
|
|
578
|
+
fromRoleId: "user",
|
|
579
|
+
toRoleId: expectBodyString(input, "toRoleId"),
|
|
580
|
+
message: expectBodyString(input, "message"),
|
|
581
|
+
runtimeAdapter,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function updateDashboardTeamAgentState(homeDir: string, body: unknown): Promise<unknown> {
|
|
586
|
+
const input = expectRequestBody(body);
|
|
587
|
+
const result = await updateTeamAgentHookState({
|
|
588
|
+
homeDir,
|
|
589
|
+
runId: expectBodyString(input, "runId"),
|
|
590
|
+
roleId: expectBodyString(input, "roleId"),
|
|
591
|
+
hookEvent: expectBodyString(input, "hookEvent"),
|
|
592
|
+
});
|
|
593
|
+
return {
|
|
594
|
+
ok: true,
|
|
595
|
+
roleId: result.agent.roleId,
|
|
596
|
+
status: result.agent.status,
|
|
597
|
+
statusPath: result.statusPath,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function claimDashboardTeamMessages(homeDir: string, body: unknown): Promise<unknown> {
|
|
602
|
+
const input = expectRequestBody(body);
|
|
603
|
+
const runId = expectBodyString(input, "runId");
|
|
604
|
+
const roleId = expectBodyString(input, "roleId");
|
|
605
|
+
const messages = await readPendingTeamMessagesForRole({
|
|
606
|
+
homeDir,
|
|
607
|
+
runId,
|
|
608
|
+
roleId,
|
|
609
|
+
limit: optionalBodyNumber(input.limit),
|
|
610
|
+
});
|
|
611
|
+
return {
|
|
612
|
+
runId,
|
|
613
|
+
roleId,
|
|
614
|
+
messages,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function markDashboardTeamMessagesDelivered(
|
|
619
|
+
homeDir: string,
|
|
620
|
+
body: unknown,
|
|
621
|
+
): Promise<unknown> {
|
|
622
|
+
const input = expectRequestBody(body);
|
|
623
|
+
const messageIdsValue = input.messageIds;
|
|
624
|
+
if (!Array.isArray(messageIdsValue)) throw new Error("Expected body.messageIds array.");
|
|
625
|
+
const messageIds = messageIdsValue.map((value) => {
|
|
626
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
627
|
+
throw new Error("Expected body.messageIds to contain non-empty strings.");
|
|
628
|
+
}
|
|
629
|
+
return value;
|
|
630
|
+
});
|
|
631
|
+
await markTeamMessagesDelivered({
|
|
632
|
+
homeDir,
|
|
633
|
+
runId: expectBodyString(input, "runId"),
|
|
634
|
+
roleId: expectBodyString(input, "roleId"),
|
|
635
|
+
messageIds,
|
|
636
|
+
});
|
|
637
|
+
return { ok: true, delivered: messageIds };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async function spawnDashboardTeamRole(
|
|
641
|
+
homeDir: string,
|
|
642
|
+
body: unknown,
|
|
643
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
644
|
+
): Promise<unknown> {
|
|
645
|
+
const input = expectRequestBody(body);
|
|
646
|
+
const roleId = expectBodyString(input, "roleId");
|
|
647
|
+
const status = await reconcileTeamRun({
|
|
648
|
+
homeDir,
|
|
649
|
+
runId: optionalBodyString(input.runId),
|
|
650
|
+
runtimeAdapter,
|
|
651
|
+
notifyMain: false,
|
|
652
|
+
});
|
|
653
|
+
if (status.run === null) return { ok: false, error: "team-run-not-found" };
|
|
654
|
+
if (status.run.status !== "running") return { ok: false, error: "team-run-not-running" };
|
|
655
|
+
const result = await spawnTeamRole({
|
|
656
|
+
homeDir,
|
|
657
|
+
repoRoot: status.run.repoRoot,
|
|
658
|
+
runId: status.run.runId,
|
|
659
|
+
roleId,
|
|
660
|
+
runtimeAdapter,
|
|
661
|
+
});
|
|
662
|
+
return { ok: true, created: result.created, agent: result.agent };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function stopDashboardTeamRole(
|
|
666
|
+
homeDir: string,
|
|
667
|
+
body: unknown,
|
|
668
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
669
|
+
): Promise<unknown> {
|
|
670
|
+
const input = expectRequestBody(body);
|
|
671
|
+
return stopTeamRole({
|
|
672
|
+
homeDir,
|
|
673
|
+
runId: optionalBodyString(input.runId),
|
|
674
|
+
roleId: expectBodyString(input, "roleId"),
|
|
675
|
+
runtimeAdapter,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async function resumeDashboardTeamRun(
|
|
680
|
+
homeDir: string,
|
|
681
|
+
body: unknown,
|
|
682
|
+
runtimeAdapter?: TeamRuntimeAdapter,
|
|
683
|
+
): Promise<unknown> {
|
|
684
|
+
const input = expectRequestBody(body);
|
|
685
|
+
const decision = optionalBodyString(input.onSessionMissing);
|
|
686
|
+
if (decision !== undefined && !["ask", "fail", "recreate", "resume-latest"].includes(decision)) {
|
|
687
|
+
return { ok: false, error: "invalid-on-session-missing" };
|
|
688
|
+
}
|
|
689
|
+
const result = await resumeTeamRun({
|
|
690
|
+
homeDir,
|
|
691
|
+
runId: optionalBodyString(input.runId),
|
|
692
|
+
runtimeAdapter,
|
|
693
|
+
missingSessionDecision: decision as "ask" | "fail" | "recreate" | "resume-latest" | undefined,
|
|
694
|
+
});
|
|
695
|
+
return {
|
|
696
|
+
ok: result.decisionRequired.length === 0,
|
|
697
|
+
run: result.run,
|
|
698
|
+
outcomes: result.outcomes,
|
|
699
|
+
decisionRequired: result.decisionRequired,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
355
703
|
async function collectDirectorySummaries(root: string, warnings: string[]): Promise<unknown[]> {
|
|
356
704
|
if (!(await pathExists(root))) {
|
|
357
705
|
warnings.push(`Store not found: ${root}`);
|
|
@@ -379,15 +727,89 @@ function sanitizeMetadata(value: unknown): unknown {
|
|
|
379
727
|
return output;
|
|
380
728
|
}
|
|
381
729
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
730
|
+
function dashboardMutation(
|
|
731
|
+
data: unknown,
|
|
732
|
+
warnings: string[],
|
|
733
|
+
): { status: number; body: DaemonResponseBody } {
|
|
734
|
+
const okResult = !(
|
|
735
|
+
typeof data === "object" &&
|
|
736
|
+
data !== null &&
|
|
737
|
+
"ok" in data &&
|
|
738
|
+
(data as { ok?: unknown }).ok === false
|
|
739
|
+
);
|
|
740
|
+
return {
|
|
741
|
+
status: okResult ? 200 : 400,
|
|
742
|
+
body: { ok: okResult, data: sanitizeMetadata(data), warnings },
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function expectRequestBody(value: unknown): Record<string, unknown> {
|
|
747
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
748
|
+
throw new Error("Expected JSON object request body.");
|
|
389
749
|
}
|
|
390
|
-
return
|
|
750
|
+
return value as Record<string, unknown>;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function expectBodyString(input: Record<string, unknown>, key: string): string {
|
|
754
|
+
const value = input[key];
|
|
755
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
756
|
+
throw new Error(`Expected non-empty string body.${key}.`);
|
|
757
|
+
}
|
|
758
|
+
return value;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function optionalBodyString(value: unknown): string | undefined {
|
|
762
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function optionalBodyNumber(value: unknown): number | undefined {
|
|
766
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function optionalBodyBoolean(value: unknown): boolean | undefined {
|
|
770
|
+
return typeof value === "boolean" ? value : undefined;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function resolveDaemonEvolutionProcessErrorPath(homeDir: string): string {
|
|
774
|
+
return join(resolveDaemonPaths(homeDir).rootDir, "evolution-process-error.json");
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async function recordDaemonEvolutionProcessError(homeDir: string, error: unknown): Promise<void> {
|
|
778
|
+
try {
|
|
779
|
+
const path = resolveDaemonEvolutionProcessErrorPath(homeDir);
|
|
780
|
+
await mkdir(dirname(path), { recursive: true });
|
|
781
|
+
await writeFile(
|
|
782
|
+
path,
|
|
783
|
+
`${JSON.stringify(
|
|
784
|
+
{
|
|
785
|
+
schemaVersion: 1,
|
|
786
|
+
kind: "daemon-evolution-process-error",
|
|
787
|
+
updatedAt: new Date().toISOString(),
|
|
788
|
+
summary: sanitizeMetadata(describeError(error)),
|
|
789
|
+
},
|
|
790
|
+
null,
|
|
791
|
+
2,
|
|
792
|
+
)}\n`,
|
|
793
|
+
{ encoding: "utf8", flag: "w", mode: 0o600 },
|
|
794
|
+
);
|
|
795
|
+
} catch {
|
|
796
|
+
// Daemon background diagnostics must not affect foreground request handling.
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async function clearDaemonEvolutionProcessError(homeDir: string): Promise<void> {
|
|
801
|
+
await rm(resolveDaemonEvolutionProcessErrorPath(homeDir), { force: true }).catch(() => undefined);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function readDaemonEvolutionProcessError(homeDir: string): Promise<string | null> {
|
|
805
|
+
const path = resolveDaemonEvolutionProcessErrorPath(homeDir);
|
|
806
|
+
if (!(await pathExists(path))) return null;
|
|
807
|
+
const value = JSON.parse(await readFile(path, "utf8")) as { summary?: unknown };
|
|
808
|
+
return typeof value.summary === "string" && value.summary.length > 0 ? value.summary : null;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function describeError(error: unknown): string {
|
|
812
|
+
return error instanceof Error ? error.message : String(error);
|
|
391
813
|
}
|
|
392
814
|
|
|
393
815
|
function ok(data: unknown, warnings: string[]): { status: number; body: DaemonResponseBody } {
|