@vercel/factory 0.0.12 → 0.0.13
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/dist/approval-CwhTP1XB.mjs +269 -0
- package/dist/approval-CwhTP1XB.mjs.map +1 -0
- package/dist/blob/index.d.mts +1 -1
- package/dist/build-factory.d.mts +1 -1
- package/dist/build-factory.mjs +1 -1
- package/dist/change-CzEAoFVE.mjs +620 -0
- package/dist/change-CzEAoFVE.mjs.map +1 -0
- package/dist/connectors/slack.d.mts +4 -4
- package/dist/connectors/slack.mjs +81 -30
- package/dist/connectors/slack.mjs.map +1 -1
- package/dist/{approval-CIXB8IF8.d.mts → dispatch-B77cuPxh.d.mts} +290 -99
- package/dist/{driver-CrZsy7sm.d.mts → driver-BJutB0PE.d.mts} +2 -2
- package/dist/driver-Bbd25rNM.mjs.map +1 -1
- package/dist/eve/index.d.mts +546 -120
- package/dist/eve/index.mjs +358 -82
- package/dist/eve/index.mjs.map +1 -1
- package/dist/{factory-config-ifd3JxiU.mjs → factory-config-BJcjsPKl.mjs} +42 -5
- package/dist/factory-config-BJcjsPKl.mjs.map +1 -0
- package/dist/{factory-config-Bu4mklm9.d.mts → factory-config-D2yU3G0c.d.mts} +21 -2
- package/dist/{github-wThBfbm-.d.mts → github-DXsxboUL.d.mts} +25 -2
- package/dist/github.d.mts +2 -2
- package/dist/github.mjs +47 -1
- package/dist/github.mjs.map +1 -1
- package/dist/index.d.mts +119 -59
- package/dist/index.mjs +289 -330
- package/dist/index.mjs.map +1 -1
- package/dist/postgres/index.d.mts +1 -1
- package/package.json +1 -1
- package/dist/approval-DdvEKLiE.mjs +0 -132
- package/dist/approval-DdvEKLiE.mjs.map +0 -1
- package/dist/change-CFNGXVqD.mjs +0 -230
- package/dist/change-CFNGXVqD.mjs.map +0 -1
- package/dist/factory-config-ifd3JxiU.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
import { a as repositoryIdsSchema, c as SCHEMA_VERSION, d as schemaVersionSchema, f as usdSchema, i as repositoryIdSchema, l as isoDateTimeSchema, n as factoryIdSchema, o as signalIdSchema, p as vocabularySchema, r as receiptIdSchema, s as taskIdSchema, t as changeIdSchema, u as replyToSchema } from "./id-DwCihQr4.mjs";
|
|
2
|
-
import { a as findingCategorySchema, c as findingSchema, d as findingVerificationSchema, i as requiresApproval, l as findingVerificationLevelSchema, n as approvalRuleSchema, o as findingFingerprint, r as defaultApprovalPolicy, s as findingPrioritySchema, t as approvalPolicySchema, u as findingVerificationMethodSchema } from "./approval-
|
|
2
|
+
import { C as taskUsageSchema, S as taskStateSchema, _ as taskExecutionRepositoryIdsSchema, a as findingCategorySchema, b as taskQuestionSchema, c as findingSchema, d as findingVerificationSchema, f as coordinationRepositoryIdsSchema, g as taskApprovalSchema, h as standardTaskKinds, i as requiresApproval, l as findingVerificationLevelSchema, m as harnessSchema, n as approvalRuleSchema, o as findingFingerprint, p as executionReferenceSchema, r as defaultApprovalPolicy, s as findingPrioritySchema, t as approvalPolicySchema, u as findingVerificationMethodSchema, v as taskKindSchema, x as taskSchema, y as taskOriginSchema } from "./approval-CwhTP1XB.mjs";
|
|
3
|
+
import { C as budgetStatus, D as sweepBudget, E as deriveFactoryId, O as sweepSettlements, S as budgetMonthOf, T as computeMonthSpendUsd, _ as taskInitialState, a as BudgetExhaustedError, b as createCoordination, c as dispatchTask, d as changeInitialState, f as changeTransitions, g as signalOutcomeTransitions, h as signalInitialOutcome, i as levelSchema, l as InvalidTransitionError, m as legalNextStates, n as changeSchema, o as DEFAULT_MAX_LAUNCH_ATTEMPTS, p as isTransitionAllowed, r as changeStateSchema, s as DispatchRefusedError, t as changeEvidenceSchema, u as assertTransition, v as taskTransitions, w as canReserve, x as reconcileCoordination, y as terminalStates } from "./change-CzEAoFVE.mjs";
|
|
3
4
|
import { n as RecordNotFoundError, r as VersionConflictError, t as RecordExistsError } from "./driver-Bbd25rNM.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { a as repositorySlugSchema, i as repositorySchema, n as factoryConfigRepositorySchema, r as factoryConfigSchema, t as defineFactory } from "./factory-config-ifd3JxiU.mjs";
|
|
5
|
+
import { a as factoryConfigSchema, c as budgetSchema, i as factoryConfigRepositorySchema, l as repositorySchema, n as defineFactory, o as budgetLimitsSchema, r as executionPolicySchema, s as budgetMonthSchema, t as defaultExecutionPolicy, u as repositorySlugSchema } from "./factory-config-BJcjsPKl.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
//#region src/agent-routes.ts
|
|
9
|
+
const agentRouteSchema = z.strictObject({
|
|
10
|
+
id: z.string().regex(/^[a-z][a-z0-9-]{0,63}$/u).describe("Stable route selected by a conversational agent or workflow."),
|
|
11
|
+
agent: z.string().regex(/^[a-z][a-z0-9-]{0,31}$/u).describe("Internally addressable Eve agent that executes this route."),
|
|
12
|
+
taskKind: taskKindSchema.describe("Factory task kind created for this route."),
|
|
13
|
+
description: z.string().min(1).max(500).describe("Model-facing guidance for when this route is appropriate."),
|
|
14
|
+
permissions: z.array(z.string().min(1)).default([]).describe("Inspectable capabilities granted to the target agent.")
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Define the application-owned routes an agent or workflow may select. Route
|
|
18
|
+
* descriptions inform judgment; this validated registry remains the authority.
|
|
19
|
+
*/
|
|
20
|
+
function defineAgentRoutes(inputs) {
|
|
21
|
+
const routes = inputs.map((input) => agentRouteSchema.parse(input));
|
|
22
|
+
const byId = /* @__PURE__ */ new Map();
|
|
23
|
+
const byTaskKind = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const route of routes) {
|
|
25
|
+
if (byId.has(route.id)) throw new Error(`Agent route ${route.id} is registered more than once.`);
|
|
26
|
+
if (byTaskKind.has(route.taskKind)) throw new Error(`Task kind ${route.taskKind} is handled by more than one agent route.`);
|
|
27
|
+
byId.set(route.id, route);
|
|
28
|
+
byTaskKind.set(route.taskKind, route);
|
|
29
|
+
}
|
|
30
|
+
const frozen = Object.freeze(routes.map((route) => Object.freeze(route)));
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
routes: frozen,
|
|
33
|
+
get(id) {
|
|
34
|
+
return byId.get(id);
|
|
35
|
+
},
|
|
36
|
+
forTaskKind(kind) {
|
|
37
|
+
return byTaskKind.get(kind);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
8
42
|
//#region src/briefs.ts
|
|
9
43
|
/** The finding behind a task, when its signal carries one. */
|
|
10
44
|
async function findingForTask(stores, task) {
|
|
@@ -64,76 +98,6 @@ async function adoptPullRequest(stores, input) {
|
|
|
64
98
|
});
|
|
65
99
|
}
|
|
66
100
|
//#endregion
|
|
67
|
-
//#region src/coordination.ts
|
|
68
|
-
/** Create one non-executable parent and one independently executable child per repository. */
|
|
69
|
-
async function createCoordination(stores, input) {
|
|
70
|
-
const repositoryIds = [...new Set(input.repositoryIds)];
|
|
71
|
-
if (repositoryIds.length < 2) throw new Error("coordination requires at least two distinct repositories");
|
|
72
|
-
const parent = await stores.tasks.create({
|
|
73
|
-
repositoryIds,
|
|
74
|
-
kind: "coordination",
|
|
75
|
-
origin: { signalId: input.signalId },
|
|
76
|
-
replyTo: input.replyTo
|
|
77
|
-
});
|
|
78
|
-
return {
|
|
79
|
-
parent: await stores.tasks.transition(parent.id, "running", { reason: "repository-scoped child tasks created" }),
|
|
80
|
-
children: await Promise.all(repositoryIds.map((repositoryId) => stores.tasks.create({
|
|
81
|
-
repositoryIds: [repositoryId],
|
|
82
|
-
kind: "code_change",
|
|
83
|
-
origin: { parentTaskId: parent.id },
|
|
84
|
-
replyTo: input.replyTo
|
|
85
|
-
})))
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
/** Reconcile a coordination parent from its direct children without ever dispatching the parent. */
|
|
89
|
-
async function reconcileCoordination(stores, parentTaskId, options) {
|
|
90
|
-
const parent = await stores.tasks.get(parentTaskId);
|
|
91
|
-
if (parent === null) throw new RecordNotFoundError("tasks", parentTaskId);
|
|
92
|
-
if (parent.kind !== "coordination") throw new Error(`task ${parentTaskId} is ${parent.kind}, not coordination`);
|
|
93
|
-
const children = (await stores.tasks.list()).filter((task) => task.origin.parentTaskId === parentTaskId);
|
|
94
|
-
if (children.length !== parent.repositoryIds.length) return {
|
|
95
|
-
status: "pending",
|
|
96
|
-
parent,
|
|
97
|
-
children
|
|
98
|
-
};
|
|
99
|
-
if (parent.state === "succeeded" || parent.state === "failed") return {
|
|
100
|
-
status: parent.state,
|
|
101
|
-
transitioned: false,
|
|
102
|
-
parent,
|
|
103
|
-
children
|
|
104
|
-
};
|
|
105
|
-
if (children.some((child) => child.state === "failed" || child.state === "cancelled")) {
|
|
106
|
-
await options.present({
|
|
107
|
-
status: "failed",
|
|
108
|
-
parent,
|
|
109
|
-
children
|
|
110
|
-
});
|
|
111
|
-
return {
|
|
112
|
-
status: "failed",
|
|
113
|
-
transitioned: true,
|
|
114
|
-
parent: await stores.tasks.transition(parent.id, "failed", { reason: "one or more repository tasks failed" }),
|
|
115
|
-
children
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if (!children.every((child) => child.state === "succeeded")) return {
|
|
119
|
-
status: "pending",
|
|
120
|
-
parent,
|
|
121
|
-
children
|
|
122
|
-
};
|
|
123
|
-
await options.present({
|
|
124
|
-
status: "succeeded",
|
|
125
|
-
parent,
|
|
126
|
-
children
|
|
127
|
-
});
|
|
128
|
-
await stores.tasks.transition(parent.id, "verifying", { reason: "all repository tasks succeeded" });
|
|
129
|
-
return {
|
|
130
|
-
status: "succeeded",
|
|
131
|
-
transitioned: true,
|
|
132
|
-
parent: await stores.tasks.transition(parent.id, "succeeded", { reason: "aggregate multi-repository handoff is ready" }),
|
|
133
|
-
children
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
101
|
//#region src/metrics.ts
|
|
138
102
|
const bucketTable = {
|
|
139
103
|
low: {
|
|
@@ -258,9 +222,10 @@ function computeLedgerMetrics(rows, options = {}) {
|
|
|
258
222
|
}
|
|
259
223
|
const openChangeStates = /* @__PURE__ */ new Set(["verifying", "ready"]);
|
|
260
224
|
/**
|
|
261
|
-
* Green, yellow, or red from rows, never from a model. Budget and
|
|
262
|
-
*
|
|
263
|
-
*
|
|
225
|
+
* Green, yellow, or red from rows, never from a model. Budget threshold and
|
|
226
|
+
* onboarding rules are not evaluated here yet: budget notices live on the
|
|
227
|
+
* budgets collection (not in these rows), and onboarding investigations are
|
|
228
|
+
* not recorded.
|
|
264
229
|
*/
|
|
265
230
|
function computeRepositoryHealth(rows, options = {}) {
|
|
266
231
|
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
@@ -296,31 +261,13 @@ function computeRepositoryHealth(rows, options = {}) {
|
|
|
296
261
|
};
|
|
297
262
|
}
|
|
298
263
|
//#endregion
|
|
299
|
-
//#region src/schema/budget.ts
|
|
300
|
-
const budgetMonthSchema = z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/);
|
|
301
|
-
const budgetSchema = z.strictObject({
|
|
302
|
-
schemaVersion: schemaVersionSchema,
|
|
303
|
-
factoryId: factoryIdSchema,
|
|
304
|
-
month: budgetMonthSchema,
|
|
305
|
-
limits: z.strictObject({
|
|
306
|
-
totalUsd: usdSchema,
|
|
307
|
-
investigationUsd: usdSchema,
|
|
308
|
-
perTaskReservationUsd: usdSchema,
|
|
309
|
-
warningPercent: z.number().int().min(1).max(100),
|
|
310
|
-
perRepositoryUsd: z.record(repositorySlugSchema, usdSchema).optional()
|
|
311
|
-
}),
|
|
312
|
-
notices: z.strictObject({
|
|
313
|
-
warningAt: isoDateTimeSchema.optional(),
|
|
314
|
-
exhaustedAt: isoDateTimeSchema.optional()
|
|
315
|
-
})
|
|
316
|
-
});
|
|
317
|
-
//#endregion
|
|
318
264
|
//#region src/schema/receipt.ts
|
|
319
265
|
/** States the engine writes on transitions. Any vocabulary value is valid. */
|
|
320
266
|
const standardReceiptStates = [
|
|
321
267
|
"approved",
|
|
322
268
|
"rejected",
|
|
323
269
|
"dispatched",
|
|
270
|
+
"stalled",
|
|
324
271
|
"succeeded",
|
|
325
272
|
"failed",
|
|
326
273
|
"needs_human",
|
|
@@ -429,232 +376,6 @@ const signalSchema = z.strictObject({
|
|
|
429
376
|
});
|
|
430
377
|
});
|
|
431
378
|
//#endregion
|
|
432
|
-
//#region src/schema/transitions.ts
|
|
433
|
-
const taskInitialState = "queued";
|
|
434
|
-
const taskTransitions = [
|
|
435
|
-
{
|
|
436
|
-
from: "queued",
|
|
437
|
-
to: "running",
|
|
438
|
-
trigger: "dependencies have a change, reservation succeeded, devbox created",
|
|
439
|
-
actor: "reconciler"
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
from: "queued",
|
|
443
|
-
to: "failed",
|
|
444
|
-
trigger: "reservation refused",
|
|
445
|
-
actor: "reconciler"
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
from: "queued",
|
|
449
|
-
to: "needs_human",
|
|
450
|
-
trigger: "kind is question",
|
|
451
|
-
actor: "system"
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
from: "queued",
|
|
455
|
-
to: "cancelled",
|
|
456
|
-
trigger: "cancel or kill",
|
|
457
|
-
actor: "operator"
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
from: "running",
|
|
461
|
-
to: "verifying",
|
|
462
|
-
trigger: "devbox task reported complete",
|
|
463
|
-
actor: "reconciler"
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
from: "running",
|
|
467
|
-
to: "queued",
|
|
468
|
-
trigger: "launch failed below attempt limit",
|
|
469
|
-
actor: "reconciler"
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
from: "running",
|
|
473
|
-
to: "failed",
|
|
474
|
-
trigger: "devbox task errored, deadline exceeded, or launch failed at attempt limit",
|
|
475
|
-
actor: "reconciler"
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
from: "running",
|
|
479
|
-
to: "needs_human",
|
|
480
|
-
trigger: "devbox task requires attention",
|
|
481
|
-
actor: "reconciler"
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
from: "running",
|
|
485
|
-
to: "cancelled",
|
|
486
|
-
trigger: "cancel or kill",
|
|
487
|
-
actor: "operator"
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
from: "verifying",
|
|
491
|
-
to: "succeeded",
|
|
492
|
-
trigger: "kind criterion true",
|
|
493
|
-
actor: "reconciler"
|
|
494
|
-
},
|
|
495
|
-
{
|
|
496
|
-
from: "verifying",
|
|
497
|
-
to: "running",
|
|
498
|
-
trigger: "verification failed below attempt limit",
|
|
499
|
-
actor: "reconciler"
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
from: "verifying",
|
|
503
|
-
to: "failed",
|
|
504
|
-
trigger: "verification failed at attempt limit",
|
|
505
|
-
actor: "reconciler"
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
from: "verifying",
|
|
509
|
-
to: "cancelled",
|
|
510
|
-
trigger: "cancel or kill",
|
|
511
|
-
actor: "operator"
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
from: "needs_human",
|
|
515
|
-
to: "succeeded",
|
|
516
|
-
trigger: "question answered",
|
|
517
|
-
actor: "operator"
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
from: "needs_human",
|
|
521
|
-
to: "queued",
|
|
522
|
-
trigger: "operator retry",
|
|
523
|
-
actor: "operator"
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
from: "needs_human",
|
|
527
|
-
to: "cancelled",
|
|
528
|
-
trigger: "cancel or kill",
|
|
529
|
-
actor: "operator"
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
from: "failed",
|
|
533
|
-
to: "queued",
|
|
534
|
-
trigger: "operator retry",
|
|
535
|
-
actor: "operator"
|
|
536
|
-
}
|
|
537
|
-
];
|
|
538
|
-
const changeInitialState = "verifying";
|
|
539
|
-
const changeTransitions = [
|
|
540
|
-
{
|
|
541
|
-
from: "verifying",
|
|
542
|
-
to: "ready",
|
|
543
|
-
trigger: "verification task succeeded, risk and confidence written",
|
|
544
|
-
actor: "reconciler"
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
from: "verifying",
|
|
548
|
-
to: "closed",
|
|
549
|
-
trigger: "verification failed at attempt limit or pr closed upstream",
|
|
550
|
-
actor: "reconciler"
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
from: "ready",
|
|
554
|
-
to: "merged",
|
|
555
|
-
trigger: "pr merged on github",
|
|
556
|
-
actor: "webhook"
|
|
557
|
-
},
|
|
558
|
-
{
|
|
559
|
-
from: "ready",
|
|
560
|
-
to: "closed",
|
|
561
|
-
trigger: "pr closed without merge",
|
|
562
|
-
actor: "webhook"
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
from: "merged",
|
|
566
|
-
to: "deployed",
|
|
567
|
-
trigger: "production deployment ready containing the merge sha",
|
|
568
|
-
actor: "webhook"
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
from: "deployed",
|
|
572
|
-
to: "rolled_out",
|
|
573
|
-
trigger: "no flag, or flag ramped to full and held",
|
|
574
|
-
actor: "foreman"
|
|
575
|
-
},
|
|
576
|
-
{
|
|
577
|
-
from: "deployed",
|
|
578
|
-
to: "rolled_back",
|
|
579
|
-
trigger: "rollback rule crossed",
|
|
580
|
-
actor: "foreman"
|
|
581
|
-
},
|
|
582
|
-
{
|
|
583
|
-
from: "rolled_out",
|
|
584
|
-
to: "rolled_back",
|
|
585
|
-
trigger: "rollback rule crossed or flag flipped off",
|
|
586
|
-
actor: "foreman"
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
from: "rolled_back",
|
|
590
|
-
to: "deployed",
|
|
591
|
-
trigger: "human retried the ramp",
|
|
592
|
-
actor: "human"
|
|
593
|
-
}
|
|
594
|
-
];
|
|
595
|
-
const signalInitialOutcome = "pending";
|
|
596
|
-
const signalOutcomeTransitions = [
|
|
597
|
-
{
|
|
598
|
-
from: "pending",
|
|
599
|
-
to: "accepted",
|
|
600
|
-
trigger: "classified as actionable",
|
|
601
|
-
actor: "reconciler"
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
from: "pending",
|
|
605
|
-
to: "duplicate",
|
|
606
|
-
trigger: "classified as duplicate",
|
|
607
|
-
actor: "reconciler"
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
from: "pending",
|
|
611
|
-
to: "unactionable",
|
|
612
|
-
trigger: "classified as unactionable",
|
|
613
|
-
actor: "reconciler"
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
from: "pending",
|
|
617
|
-
to: "low_confidence",
|
|
618
|
-
trigger: "classified below confidence threshold",
|
|
619
|
-
actor: "reconciler"
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
from: "pending",
|
|
623
|
-
to: "ignored",
|
|
624
|
-
trigger: "classified as ignorable",
|
|
625
|
-
actor: "reconciler"
|
|
626
|
-
}
|
|
627
|
-
];
|
|
628
|
-
function isTransitionAllowed(transitions, from, to) {
|
|
629
|
-
return transitions.some((transition) => transition.from === from && transition.to === to);
|
|
630
|
-
}
|
|
631
|
-
function legalNextStates(transitions, from) {
|
|
632
|
-
return [...new Set(transitions.filter((transition) => transition.from === from).map((t) => t.to))];
|
|
633
|
-
}
|
|
634
|
-
var InvalidTransitionError = class extends Error {
|
|
635
|
-
from;
|
|
636
|
-
to;
|
|
637
|
-
legalNextStates;
|
|
638
|
-
constructor(from, to, legal) {
|
|
639
|
-
super(legal.length > 0 ? `invalid transition from "${from}" to "${to}"; legal next states: ${legal.map((state) => `"${state}"`).join(", ")}` : `invalid transition from "${from}" to "${to}"; "${from}" is terminal`);
|
|
640
|
-
this.name = "InvalidTransitionError";
|
|
641
|
-
this.from = from;
|
|
642
|
-
this.to = to;
|
|
643
|
-
this.legalNextStates = legal;
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
function assertTransition(transitions, from, to) {
|
|
647
|
-
if (!isTransitionAllowed(transitions, from, to)) throw new InvalidTransitionError(from, to, legalNextStates(transitions, from));
|
|
648
|
-
}
|
|
649
|
-
function terminalStates(transitions) {
|
|
650
|
-
const states = /* @__PURE__ */ new Set();
|
|
651
|
-
for (const transition of transitions) {
|
|
652
|
-
states.add(transition.from);
|
|
653
|
-
states.add(transition.to);
|
|
654
|
-
}
|
|
655
|
-
return [...states].filter((state) => !transitions.some((transition) => transition.from === state));
|
|
656
|
-
}
|
|
657
|
-
//#endregion
|
|
658
379
|
//#region src/signals.ts
|
|
659
380
|
/**
|
|
660
381
|
* Bind normalized intake to signal creation alone. Connectors and API routes
|
|
@@ -667,6 +388,64 @@ function createSignalCreator(stores) {
|
|
|
667
388
|
} };
|
|
668
389
|
}
|
|
669
390
|
//#endregion
|
|
391
|
+
//#region src/stall.ts
|
|
392
|
+
function stallWindowMinutes(policy, kind) {
|
|
393
|
+
return policy.stallMinutesByKind[kind] ?? policy.stallMinutes;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The reconciler's stall pass. A running task whose execution has not
|
|
397
|
+
* progressed within its stall window is marked stalled; a stalled task is
|
|
398
|
+
* re-queued for a replacement attempt while attempts remain, and failed once
|
|
399
|
+
* they are exhausted. Only `running` tasks can stall: a task whose result has
|
|
400
|
+
* already been recorded (verifying, or terminal) is never retried by this
|
|
401
|
+
* pass. Coordination parents run only through their children and are skipped.
|
|
402
|
+
*/
|
|
403
|
+
async function sweepStalled(stores, options) {
|
|
404
|
+
const nowMs = (options.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
405
|
+
const result = {
|
|
406
|
+
stalled: [],
|
|
407
|
+
requeued: [],
|
|
408
|
+
failed: []
|
|
409
|
+
};
|
|
410
|
+
const tasks = await stores.tasks.list();
|
|
411
|
+
for (const task of tasks) {
|
|
412
|
+
if (task.state !== "running" || task.kind === "coordination") continue;
|
|
413
|
+
const minutes = stallWindowMinutes(options.policy, task.kind);
|
|
414
|
+
const sinceProgress = nowMs - Date.parse(task.lastProgressAt ?? task.updatedAt);
|
|
415
|
+
if (sinceProgress < minutes * 6e4) continue;
|
|
416
|
+
try {
|
|
417
|
+
await stores.tasks.transition(task.id, "stalled", {
|
|
418
|
+
reason: `no progress for ${Math.floor(sinceProgress / 6e4)} minutes (window ${minutes})`,
|
|
419
|
+
expectFrom: "running"
|
|
420
|
+
});
|
|
421
|
+
result.stalled.push(task.id);
|
|
422
|
+
} catch {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
for (const task of await stores.tasks.list()) {
|
|
427
|
+
if (task.state !== "stalled") continue;
|
|
428
|
+
try {
|
|
429
|
+
if (task.attempt < options.policy.maxAttempts) {
|
|
430
|
+
await stores.tasks.transition(task.id, "queued", {
|
|
431
|
+
reason: `stalled on attempt ${task.attempt} of ${options.policy.maxAttempts}; replacing`,
|
|
432
|
+
expectFrom: "stalled"
|
|
433
|
+
});
|
|
434
|
+
result.requeued.push(task.id);
|
|
435
|
+
} else {
|
|
436
|
+
await stores.tasks.transition(task.id, "failed", {
|
|
437
|
+
reason: `stalled on attempt ${task.attempt} of ${options.policy.maxAttempts}; attempts exhausted`,
|
|
438
|
+
expectFrom: "stalled"
|
|
439
|
+
});
|
|
440
|
+
result.failed.push(task.id);
|
|
441
|
+
}
|
|
442
|
+
} catch {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
670
449
|
//#region src/store/engine.ts
|
|
671
450
|
/** An approval decision that does not apply to the task's current approval or state. */
|
|
672
451
|
var ApprovalError = class extends Error {
|
|
@@ -679,6 +458,17 @@ var ApprovalError = class extends Error {
|
|
|
679
458
|
this.reason = reason;
|
|
680
459
|
}
|
|
681
460
|
};
|
|
461
|
+
/** A settlement that does not apply to the task's current state. */
|
|
462
|
+
var SettlementError = class extends Error {
|
|
463
|
+
taskId;
|
|
464
|
+
reason;
|
|
465
|
+
constructor(taskId, reason) {
|
|
466
|
+
super(`settlement on "${taskId}" refused: ${reason}`);
|
|
467
|
+
this.name = "SettlementError";
|
|
468
|
+
this.taskId = taskId;
|
|
469
|
+
this.reason = reason;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
682
472
|
var ChangeAssociationError = class extends Error {
|
|
683
473
|
taskId;
|
|
684
474
|
repositoryId;
|
|
@@ -693,6 +483,7 @@ var ChangeAssociationError = class extends Error {
|
|
|
693
483
|
};
|
|
694
484
|
const taskReceiptStates = {
|
|
695
485
|
running: "dispatched",
|
|
486
|
+
stalled: "stalled",
|
|
696
487
|
succeeded: "succeeded",
|
|
697
488
|
failed: "failed",
|
|
698
489
|
needs_human: "needs_human",
|
|
@@ -714,6 +505,17 @@ function derivedId(prefix, key) {
|
|
|
714
505
|
function bumpsAttempt(from, to) {
|
|
715
506
|
return to === "queued" || from === "verifying" && to === "running";
|
|
716
507
|
}
|
|
508
|
+
const emptyUsage = {
|
|
509
|
+
costUsd: 0,
|
|
510
|
+
inputTokens: 0,
|
|
511
|
+
outputTokens: 0,
|
|
512
|
+
cacheReadTokens: 0,
|
|
513
|
+
cacheWriteTokens: 0,
|
|
514
|
+
steps: {}
|
|
515
|
+
};
|
|
516
|
+
function addUsd(a, b) {
|
|
517
|
+
return (Math.round(a * 1e6) + Math.round(b * 1e6)) / 1e6;
|
|
518
|
+
}
|
|
717
519
|
function createStores(options) {
|
|
718
520
|
const { driver } = options;
|
|
719
521
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -749,7 +551,7 @@ function createStores(options) {
|
|
|
749
551
|
}
|
|
750
552
|
}
|
|
751
553
|
async function appendReceipt(key, receipt) {
|
|
752
|
-
|
|
554
|
+
return insertReceipt(derivedId("rcpt", key), receipt);
|
|
753
555
|
}
|
|
754
556
|
return {
|
|
755
557
|
driverName: driver.name,
|
|
@@ -898,30 +700,124 @@ function createStores(options) {
|
|
|
898
700
|
await driver.update("tasks", id, next, existing.version);
|
|
899
701
|
return next;
|
|
900
702
|
},
|
|
703
|
+
async recordExecution(id, execution) {
|
|
704
|
+
const existing = await mustRead("tasks", id, taskSchema);
|
|
705
|
+
const task = existing.value;
|
|
706
|
+
if (task.state !== "running") throw new InvalidTransitionError(task.state, "running", legalNextStates(taskTransitions, task.state));
|
|
707
|
+
const timestamp = now();
|
|
708
|
+
const next = taskSchema.parse({
|
|
709
|
+
...task,
|
|
710
|
+
execution,
|
|
711
|
+
lastProgressAt: timestamp,
|
|
712
|
+
updatedAt: timestamp
|
|
713
|
+
});
|
|
714
|
+
await driver.update("tasks", id, next, existing.version);
|
|
715
|
+
return next;
|
|
716
|
+
},
|
|
717
|
+
async recordUsage(id, step) {
|
|
718
|
+
for (;;) {
|
|
719
|
+
const existing = await mustRead("tasks", id, taskSchema);
|
|
720
|
+
const task = existing.value;
|
|
721
|
+
const usage = task.usage ?? emptyUsage;
|
|
722
|
+
const recordedIndex = usage.steps[step.turnId];
|
|
723
|
+
if (recordedIndex !== void 0 && step.stepIndex <= recordedIndex) return task;
|
|
724
|
+
const timestamp = now();
|
|
725
|
+
const next = taskSchema.parse({
|
|
726
|
+
...task,
|
|
727
|
+
lastProgressAt: timestamp,
|
|
728
|
+
usage: {
|
|
729
|
+
costUsd: addUsd(usage.costUsd, step.costUsd ?? 0),
|
|
730
|
+
inputTokens: usage.inputTokens + (step.inputTokens ?? 0),
|
|
731
|
+
outputTokens: usage.outputTokens + (step.outputTokens ?? 0),
|
|
732
|
+
cacheReadTokens: usage.cacheReadTokens + (step.cacheReadTokens ?? 0),
|
|
733
|
+
cacheWriteTokens: usage.cacheWriteTokens + (step.cacheWriteTokens ?? 0),
|
|
734
|
+
steps: {
|
|
735
|
+
...usage.steps,
|
|
736
|
+
[step.turnId]: step.stepIndex
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
updatedAt: timestamp
|
|
740
|
+
});
|
|
741
|
+
try {
|
|
742
|
+
await driver.update("tasks", id, next, existing.version);
|
|
743
|
+
return next;
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (!(error instanceof VersionConflictError)) throw error;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
async settle(id, cost, settleOptions = {}) {
|
|
750
|
+
const existing = await mustRead("tasks", id, taskSchema);
|
|
751
|
+
const task = existing.value;
|
|
752
|
+
if (task.settledUsd !== void 0) return task;
|
|
753
|
+
if (task.state !== "succeeded" && task.state !== "failed" && task.state !== "cancelled") throw new SettlementError(id, `task is ${task.state}, not terminal`);
|
|
754
|
+
const validated = costSchema.parse(cost);
|
|
755
|
+
const normalized = { totalUsd: Math.round(validated.totalUsd * 1e6) / 1e6 };
|
|
756
|
+
let recorded = normalized;
|
|
757
|
+
if (task.repositoryIds !== void 0) recorded = (await appendReceipt(`task:${id}:settled:${task.attempt}`, {
|
|
758
|
+
repositoryIds: task.repositoryIds,
|
|
759
|
+
taskId: id,
|
|
760
|
+
changeId: task.changeId,
|
|
761
|
+
kind: task.kind,
|
|
762
|
+
state: "settled",
|
|
763
|
+
phase: "budget",
|
|
764
|
+
reason: settleOptions.reason,
|
|
765
|
+
attempt: task.attempt,
|
|
766
|
+
cost: normalized
|
|
767
|
+
})).cost ?? normalized;
|
|
768
|
+
const next = taskSchema.parse({
|
|
769
|
+
...task,
|
|
770
|
+
settledUsd: recorded.totalUsd,
|
|
771
|
+
updatedAt: now()
|
|
772
|
+
});
|
|
773
|
+
await driver.update("tasks", id, next, existing.version);
|
|
774
|
+
return next;
|
|
775
|
+
},
|
|
901
776
|
async get(id) {
|
|
902
777
|
const record = await read("tasks", id, taskSchema);
|
|
903
778
|
return record === null ? null : record.value;
|
|
904
779
|
},
|
|
905
780
|
async transition(id, to, transitionOptions = {}) {
|
|
781
|
+
if (transitionOptions.patch !== void 0 && "settledUsd" in transitionOptions.patch) throw new SettlementError(id, "settledUsd is set only by settle, not a transition");
|
|
782
|
+
if (transitionOptions.patch !== void 0 && "usage" in transitionOptions.patch) throw new SettlementError(id, "usage is set only by recordUsage, not a transition");
|
|
906
783
|
const existing = await mustRead("tasks", id, taskSchema);
|
|
784
|
+
if (transitionOptions.expectFrom !== void 0 && existing.value.state !== transitionOptions.expectFrom) throw new InvalidTransitionError(existing.value.state, to, legalNextStates(taskTransitions, existing.value.state));
|
|
907
785
|
assertTransition(taskTransitions, existing.value.state, to);
|
|
908
|
-
|
|
786
|
+
if (to === "needs_human" && existing.value.kind !== "question") {
|
|
787
|
+
if (!(await listAll("tasks", taskSchema)).some((task) => task.kind === "question" && task.state === "needs_human" && task.origin.parentTaskId === id)) throw new Error(`task ${id} cannot enter needs_human without an open QuestionTask`);
|
|
788
|
+
}
|
|
789
|
+
const bumps = bumpsAttempt(existing.value.state, to);
|
|
790
|
+
const timestamp = now();
|
|
791
|
+
const { stalledAt: _stalledAt, stalledReason: _stalledReason, ...cleared } = existing.value;
|
|
909
792
|
const next = taskSchema.parse({
|
|
910
|
-
...
|
|
793
|
+
...cleared,
|
|
794
|
+
...bumps ? { execution: void 0 } : {},
|
|
911
795
|
...transitionOptions.patch,
|
|
796
|
+
...to === "stalled" ? {
|
|
797
|
+
stalledAt: timestamp,
|
|
798
|
+
stalledReason: transitionOptions.reason
|
|
799
|
+
} : {},
|
|
800
|
+
...to === "running" ? { lastProgressAt: timestamp } : {},
|
|
912
801
|
state: to,
|
|
913
|
-
attempt,
|
|
914
|
-
updatedAt:
|
|
802
|
+
attempt: bumps ? existing.value.attempt + 1 : existing.value.attempt,
|
|
803
|
+
updatedAt: timestamp
|
|
915
804
|
});
|
|
916
805
|
const receiptState = taskReceiptStates[to];
|
|
917
|
-
if (receiptState !== void 0 && next.repositoryIds !== void 0) await appendReceipt(
|
|
806
|
+
if (receiptState !== void 0 && next.repositoryIds !== void 0) await appendReceipt(JSON.stringify([
|
|
807
|
+
"task",
|
|
808
|
+
id,
|
|
809
|
+
receiptState,
|
|
810
|
+
next.attempt,
|
|
811
|
+
next.changeId ?? null,
|
|
812
|
+
transitionOptions.reason ?? null
|
|
813
|
+
]), {
|
|
918
814
|
repositoryIds: next.repositoryIds,
|
|
919
815
|
taskId: id,
|
|
920
816
|
changeId: next.changeId,
|
|
921
817
|
kind: next.kind,
|
|
922
818
|
state: receiptState,
|
|
923
819
|
reason: transitionOptions.reason,
|
|
924
|
-
attempt
|
|
820
|
+
attempt: next.attempt
|
|
925
821
|
});
|
|
926
822
|
await driver.update("tasks", id, next, existing.version);
|
|
927
823
|
return next;
|
|
@@ -1005,6 +901,66 @@ function createStores(options) {
|
|
|
1005
901
|
list() {
|
|
1006
902
|
return listAll("receipts", receiptSchema);
|
|
1007
903
|
}
|
|
904
|
+
},
|
|
905
|
+
budgets: {
|
|
906
|
+
async ensure(input) {
|
|
907
|
+
const budget = budgetSchema.parse({
|
|
908
|
+
schemaVersion: 1,
|
|
909
|
+
factoryId: input.factoryId,
|
|
910
|
+
month: input.month,
|
|
911
|
+
limits: input.limits,
|
|
912
|
+
notices: {}
|
|
913
|
+
});
|
|
914
|
+
try {
|
|
915
|
+
await driver.insert("budgets", input.month, budget);
|
|
916
|
+
return budget;
|
|
917
|
+
} catch (error) {
|
|
918
|
+
if (!(error instanceof RecordExistsError)) throw error;
|
|
919
|
+
}
|
|
920
|
+
const existing = await mustRead("budgets", input.month, budgetSchema);
|
|
921
|
+
const refreshed = budgetSchema.parse({
|
|
922
|
+
...existing.value,
|
|
923
|
+
limits: input.limits
|
|
924
|
+
});
|
|
925
|
+
if (JSON.stringify(refreshed) === JSON.stringify(existing.value)) return existing.value;
|
|
926
|
+
try {
|
|
927
|
+
await driver.update("budgets", input.month, refreshed, existing.version);
|
|
928
|
+
return refreshed;
|
|
929
|
+
} catch (error) {
|
|
930
|
+
if (error instanceof VersionConflictError) return (await mustRead("budgets", input.month, budgetSchema)).value;
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
async get(month) {
|
|
935
|
+
const record = await read("budgets", month, budgetSchema);
|
|
936
|
+
return record === null ? null : record.value;
|
|
937
|
+
},
|
|
938
|
+
async stampNotice(month, notice) {
|
|
939
|
+
const field = notice === "warning" ? "warningAt" : "exhaustedAt";
|
|
940
|
+
for (;;) {
|
|
941
|
+
const existing = await mustRead("budgets", month, budgetSchema);
|
|
942
|
+
if (existing.value.notices[field] !== void 0) return {
|
|
943
|
+
budget: existing.value,
|
|
944
|
+
stamped: false
|
|
945
|
+
};
|
|
946
|
+
const next = budgetSchema.parse({
|
|
947
|
+
...existing.value,
|
|
948
|
+
notices: {
|
|
949
|
+
...existing.value.notices,
|
|
950
|
+
[field]: now()
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
try {
|
|
954
|
+
await driver.update("budgets", month, next, existing.version);
|
|
955
|
+
return {
|
|
956
|
+
budget: next,
|
|
957
|
+
stamped: true
|
|
958
|
+
};
|
|
959
|
+
} catch (error) {
|
|
960
|
+
if (!(error instanceof VersionConflictError)) throw error;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
1008
964
|
}
|
|
1009
965
|
};
|
|
1010
966
|
}
|
|
@@ -1112,7 +1068,10 @@ async function sweepQueued(stores, options) {
|
|
|
1112
1068
|
continue;
|
|
1113
1069
|
}
|
|
1114
1070
|
try {
|
|
1115
|
-
await dispatchTask(stores, task.id, {
|
|
1071
|
+
await dispatchTask(stores, task.id, {
|
|
1072
|
+
launch: (dispatched) => launcher(dispatched, repository),
|
|
1073
|
+
budget: options.budget
|
|
1074
|
+
});
|
|
1116
1075
|
result.dispatched.push(task.id);
|
|
1117
1076
|
} catch (error) {
|
|
1118
1077
|
if (error instanceof DispatchRefusedError) {
|
|
@@ -1160,6 +1119,6 @@ async function ensureVerificationTasks(stores) {
|
|
|
1160
1119
|
return result;
|
|
1161
1120
|
}
|
|
1162
1121
|
//#endregion
|
|
1163
|
-
export { ApprovalError, ChangeAssociationError, DEFAULT_MAX_LAUNCH_ATTEMPTS, DispatchRefusedError, InvalidTransitionError, RecordExistsError, RecordNotFoundError, SCHEMA_VERSION, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, computeLedgerMetrics, computeRepositoryHealth, coordinationRepositoryIdsSchema, costSchema, createCoordination, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, ensureVerificationTasks, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, reconcileCoordination, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, reviewBucket, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued,
|
|
1122
|
+
export { ApprovalError, BudgetExhaustedError, ChangeAssociationError, DEFAULT_MAX_LAUNCH_ATTEMPTS, DispatchRefusedError, InvalidTransitionError, RecordExistsError, RecordNotFoundError, SCHEMA_VERSION, SettlementError, VersionConflictError, adoptPullRequest, agentRouteSchema, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetLimitsSchema, budgetMonthOf, budgetMonthSchema, budgetSchema, budgetStatus, canReserve, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, computeLedgerMetrics, computeMonthSpendUsd, computeRepositoryHealth, coordinationRepositoryIdsSchema, costSchema, createCoordination, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defaultExecutionPolicy, defineAgentRoutes, defineFactory, deriveFactoryId, dispatchTask, ensureVerificationTasks, executionPolicySchema, executionReferenceSchema, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, reconcileCoordination, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, reviewBucket, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, stallWindowMinutes, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepBudget, sweepQueued, sweepSettlements, sweepStalled, taskApprovalSchema, taskExecutionRepositoryIdsSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, taskUsageSchema, terminalStates, usdSchema, verificationBrief, vocabularySchema };
|
|
1164
1123
|
|
|
1165
1124
|
//# sourceMappingURL=index.mjs.map
|