@zq-silk/yui 0.6.8 → 0.6.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/README.md +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +63 -28
- package/dist/commands/taskContextCommand.js +27 -1
- package/dist/commands/taskRoleRuntimeStatus.js +17 -6
- package/dist/controller/agentRuntimeObserver.js +6 -3
- package/dist/controller/controller.js +29 -47
- package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
- package/dist/controller/runtime.js +8 -1
- package/dist/controller/runtimeEventInbox.js +16 -5
- package/dist/controller/runtimeHookRunFence.js +51 -5
- package/dist/controller/runtimeObservationHook.js +8 -2
- package/dist/coordination/workMailbox.js +408 -28
- package/dist/coordination/workMailboxQueue.js +12 -10
- package/dist/executor/agentExecutor.js +101 -94
- package/dist/executor/executorRegistry.js +47 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -2
- package/dist/lifecycle/exactRunTerminalization.js +1 -7
- package/dist/repository/taskWorkspaceCoordinator.js +9 -4
- package/dist/runtime/agentDriver.js +83 -4
- package/dist/runtime/agentDriverObservation.js +25 -10
- package/dist/runtime/builtinAgentDrivers.js +168 -18
- package/dist/runtime/codexAppServerRuntime.js +355 -0
- package/dist/runtime/continuationManager.js +117 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lifecycleReservation.js +4 -3
- package/dist/runtime/promptEnvelope.js +14 -3
- package/dist/runtime/providerContinuation.js +225 -0
- package/dist/runtime/providerContinuationReconciliationService.js +172 -0
- package/dist/runtime/providerRuntimeIdentity.js +232 -0
- package/dist/runtime/providerRuntimeReconciler.js +166 -0
- package/dist/runtime/runtimeContinuationProjection.js +34 -0
- package/dist/runtime/runtimeObservation.js +217 -6
- package/dist/runtime/runtimeProjection.js +172 -11
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +2 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +8 -7
- package/dist/scheduler/roleRunStall.js +4 -2
- package/dist/scheduler/taskExecutionProjection.js +2 -2
- package/dist/storage/migration/productionRegistry.js +474 -1
- package/dist/storage/sqliteSchema.js +102 -21
- package/dist/storage/sqliteStore.js +52 -110
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +40 -53
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
- package/dist/task/nextAction.js +1 -1
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +233 -4
- package/dist/web/assets/client/i18n.js +166 -2
- package/dist/web/assets/client/view.js +30 -13
- package/dist/web/assets/styles/cards.js +62 -0
- package/dist/web/assets/styles/widgets.js +1 -0
- package/dist/web/webSnapshot.js +11 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +33 -24
- package/skills/yui-operator/SKILL.md +7 -5
|
@@ -10,6 +10,8 @@ const HOME_IDENTITY_AGGREGATE_FROM_VERSION = 17;
|
|
|
10
10
|
const HOME_IDENTITY_AGGREGATE_TO_VERSION = 18;
|
|
11
11
|
const RUNTIME_OBSERVATION_AGGREGATE_FROM_VERSION = 18;
|
|
12
12
|
const RUNTIME_OBSERVATION_AGGREGATE_TO_VERSION = 19;
|
|
13
|
+
const MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION = 19;
|
|
14
|
+
const MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION = 20;
|
|
13
15
|
const SQLITE_LAYOUT_FROM_VERSION = 6;
|
|
14
16
|
const SQLITE_LAYOUT_TO_VERSION = 7;
|
|
15
17
|
const PROJECT_FROM_VERSION = 2;
|
|
@@ -59,6 +61,10 @@ const CAPABILITY_GRANT_FROM_VERSION = 0;
|
|
|
59
61
|
const CAPABILITY_GRANT_TO_VERSION = 1;
|
|
60
62
|
const RELEASE_WORKFLOW_FROM_VERSION = 0;
|
|
61
63
|
const RELEASE_WORKFLOW_TO_VERSION = 1;
|
|
64
|
+
const WORK_MAILBOX_FROM_VERSION = 1;
|
|
65
|
+
const WORK_MAILBOX_TO_VERSION = 2;
|
|
66
|
+
const TASK_ROLE_SESSION_SET_FROM_VERSION = 4;
|
|
67
|
+
const TASK_ROLE_SESSION_SET_TO_VERSION = 5;
|
|
62
68
|
/**
|
|
63
69
|
* Build the authoritative production graph. Transition intent and executable
|
|
64
70
|
* transforms are registered together here; compatible loading and offline
|
|
@@ -89,6 +95,14 @@ export function createProductionStorageRegistry() {
|
|
|
89
95
|
preconditions: requireAggregateV17Snapshot,
|
|
90
96
|
transform: migrateAggregateV17ToV18,
|
|
91
97
|
declaredEffects: []
|
|
98
|
+
})
|
|
99
|
+
.registerOfflineMigration({
|
|
100
|
+
axis: "aggregate",
|
|
101
|
+
fromVersion: MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION,
|
|
102
|
+
toVersion: MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION,
|
|
103
|
+
preconditions: requireAggregateV19Snapshot,
|
|
104
|
+
transform: migrateAggregateV19ToV20,
|
|
105
|
+
declaredEffects: []
|
|
92
106
|
})
|
|
93
107
|
.registerOfflineMigration({
|
|
94
108
|
axis: "aggregate",
|
|
@@ -119,7 +133,9 @@ export function createProductionStorageRegistry() {
|
|
|
119
133
|
.registerCompatible(storedTaskJobCallerKeyHashesStep())
|
|
120
134
|
.registerCompatible(durableJobIntroductionStep())
|
|
121
135
|
.registerOfflineMigration(capabilityGrantIntroductionStep())
|
|
122
|
-
.registerOfflineMigration(releaseWorkflowIntroductionStep())
|
|
136
|
+
.registerOfflineMigration(releaseWorkflowIntroductionStep())
|
|
137
|
+
.registerOfflineMigration(workMailboxV2Step())
|
|
138
|
+
.registerOfflineMigration(taskRoleSessionSetV5Step());
|
|
123
139
|
assertRegistryCoversBaselineToCurrent(registry);
|
|
124
140
|
return registry;
|
|
125
141
|
}
|
|
@@ -1375,6 +1391,463 @@ function requireAggregateV18Snapshot(snapshot) {
|
|
|
1375
1391
|
throw new Error("Aggregate 18->19 migration requires state.json schemaVersion 18 to match schema.json.");
|
|
1376
1392
|
}
|
|
1377
1393
|
}
|
|
1394
|
+
function requireAggregateV19Snapshot(snapshot) {
|
|
1395
|
+
if (snapshot.schemaManifest.aggregateSchemaVersion
|
|
1396
|
+
!== MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION) {
|
|
1397
|
+
throw new Error("Aggregate 19->20 migration requires schema.json aggregateSchemaVersion 19.");
|
|
1398
|
+
}
|
|
1399
|
+
if (snapshot.state !== null
|
|
1400
|
+
&& snapshot.state.schemaVersion !== MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION) {
|
|
1401
|
+
throw new Error("Aggregate 19->20 migration requires state.json schemaVersion 19 to match schema.json.");
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function migrateAggregateV19ToV20(snapshot) {
|
|
1405
|
+
requireAggregateV19Snapshot(snapshot);
|
|
1406
|
+
const schemaManifest = {
|
|
1407
|
+
...snapshot.schemaManifest,
|
|
1408
|
+
aggregateSchemaVersion: MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION
|
|
1409
|
+
};
|
|
1410
|
+
if (snapshot.state === null)
|
|
1411
|
+
return { schemaManifest, state: null };
|
|
1412
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1413
|
+
const nextTasks = {};
|
|
1414
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
1415
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
1416
|
+
const events = asObject(aggregate.events, `Task events ${taskId}`);
|
|
1417
|
+
const nextEvents = {};
|
|
1418
|
+
for (const [eventId, rawEvent] of Object.entries(events)) {
|
|
1419
|
+
const event = asObject(rawEvent, `Task event ${taskId}/${eventId}`);
|
|
1420
|
+
nextEvents[eventId] = event.type === "runtime.observation"
|
|
1421
|
+
? migrateRuntimeObservationEventV1ToV2(event, taskId, eventId)
|
|
1422
|
+
: { ...event };
|
|
1423
|
+
}
|
|
1424
|
+
nextTasks[taskId] = { ...aggregate, events: nextEvents };
|
|
1425
|
+
}
|
|
1426
|
+
return {
|
|
1427
|
+
schemaManifest,
|
|
1428
|
+
state: {
|
|
1429
|
+
...snapshot.state,
|
|
1430
|
+
schemaVersion: MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION,
|
|
1431
|
+
tasks: nextTasks
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function migrateRuntimeObservationEventV1ToV2(event, taskId, eventId) {
|
|
1436
|
+
const payload = asObject(event.payload, `Runtime observation event payload ${taskId}/${eventId}`);
|
|
1437
|
+
if (typeof payload.observation !== "string") {
|
|
1438
|
+
throw new Error(`Runtime observation event has no canonical observation: ${taskId}/${eventId}.`);
|
|
1439
|
+
}
|
|
1440
|
+
let raw;
|
|
1441
|
+
try {
|
|
1442
|
+
raw = JSON.parse(payload.observation);
|
|
1443
|
+
}
|
|
1444
|
+
catch {
|
|
1445
|
+
throw new Error(`Runtime observation event JSON is invalid: ${taskId}/${eventId}.`);
|
|
1446
|
+
}
|
|
1447
|
+
const observation = asObject(raw, `Runtime observation ${taskId}/${eventId}`);
|
|
1448
|
+
if (observation.schemaVersion !== 1) {
|
|
1449
|
+
throw new Error(`Runtime observation ${taskId}/${eventId} must use schemaVersion 1.`);
|
|
1450
|
+
}
|
|
1451
|
+
const fence = asObject(observation.fence, `Runtime observation fence ${taskId}/${eventId}`);
|
|
1452
|
+
const kind = String(observation.kind);
|
|
1453
|
+
const semanticKey = migratedRuntimeObservationSemanticKey(observation, fence);
|
|
1454
|
+
const nextObservation = {
|
|
1455
|
+
...observation,
|
|
1456
|
+
schemaVersion: 2,
|
|
1457
|
+
semanticKey,
|
|
1458
|
+
fence: {
|
|
1459
|
+
...fence,
|
|
1460
|
+
...(fence.nativeSessionId === undefined
|
|
1461
|
+
? {}
|
|
1462
|
+
: { conversationId: String(fence.nativeSessionId) }),
|
|
1463
|
+
activationId: String(fence.launchId)
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
return {
|
|
1467
|
+
...event,
|
|
1468
|
+
payload: {
|
|
1469
|
+
...payload,
|
|
1470
|
+
semanticKey,
|
|
1471
|
+
kind,
|
|
1472
|
+
observation: JSON.stringify(nextObservation)
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function migratedRuntimeObservationSemanticKey(observation, fence) {
|
|
1477
|
+
const kind = String(observation.kind);
|
|
1478
|
+
if (Number.isSafeInteger(observation.sequence)) {
|
|
1479
|
+
return `provider-sequence:${String(fence.driverId)}:${String(fence.nativeSessionId ?? fence.launchId)}:${String(observation.sequence)}:${kind}`;
|
|
1480
|
+
}
|
|
1481
|
+
const terminal = [
|
|
1482
|
+
"session.ended",
|
|
1483
|
+
"session.failed",
|
|
1484
|
+
"turn.completed",
|
|
1485
|
+
"turn.failed",
|
|
1486
|
+
"turn.cancelled"
|
|
1487
|
+
].includes(kind);
|
|
1488
|
+
if (terminal) {
|
|
1489
|
+
const payload = asObject(observation.payload, "Runtime observation payload");
|
|
1490
|
+
const failure = payload.failure === undefined
|
|
1491
|
+
? undefined
|
|
1492
|
+
: asObject(payload.failure, "Runtime observation failure");
|
|
1493
|
+
return [
|
|
1494
|
+
"terminal",
|
|
1495
|
+
String(fence.driverId),
|
|
1496
|
+
String(fence.nativeSessionId ?? fence.launchId),
|
|
1497
|
+
String(fence.launchId),
|
|
1498
|
+
String(fence.nativeTurnId ?? "none"),
|
|
1499
|
+
kind,
|
|
1500
|
+
String(payload.outcome ?? failure?.code ?? "terminal")
|
|
1501
|
+
].join(":");
|
|
1502
|
+
}
|
|
1503
|
+
return `provider-event:${String(observation.eventId)}`;
|
|
1504
|
+
}
|
|
1505
|
+
function workMailboxV2Step() {
|
|
1506
|
+
return {
|
|
1507
|
+
axis: "record",
|
|
1508
|
+
recordKind: "workMailbox",
|
|
1509
|
+
fromVersion: WORK_MAILBOX_FROM_VERSION,
|
|
1510
|
+
toVersion: WORK_MAILBOX_TO_VERSION,
|
|
1511
|
+
preconditions: requireWorkMailboxV1Family,
|
|
1512
|
+
transform: migrateWorkMailboxV1ToV2,
|
|
1513
|
+
declaredEffects: []
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
function requireWorkMailboxV1Family(snapshot) {
|
|
1517
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1518
|
+
if (versions.workMailbox !== WORK_MAILBOX_FROM_VERSION) {
|
|
1519
|
+
throw new Error(`Record workMailbox migration requires manifest version ${WORK_MAILBOX_FROM_VERSION}.`);
|
|
1520
|
+
}
|
|
1521
|
+
if (snapshot.state === null)
|
|
1522
|
+
return;
|
|
1523
|
+
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
1524
|
+
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
1525
|
+
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
1526
|
+
if (mailbox.schemaVersion !== WORK_MAILBOX_FROM_VERSION) {
|
|
1527
|
+
throw new Error(`WorkMailbox ${key} must use schemaVersion ${WORK_MAILBOX_FROM_VERSION}.`);
|
|
1528
|
+
}
|
|
1529
|
+
if (mailbox.pending !== null)
|
|
1530
|
+
asObject(mailbox.pending, `WorkMailbox ${key} pending`);
|
|
1531
|
+
if (mailbox.processing !== null)
|
|
1532
|
+
asObject(mailbox.processing, `WorkMailbox ${key} processing`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function migrateWorkMailboxV1ToV2(snapshot) {
|
|
1536
|
+
requireWorkMailboxV1Family(snapshot);
|
|
1537
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1538
|
+
const schemaManifest = {
|
|
1539
|
+
...snapshot.schemaManifest,
|
|
1540
|
+
recordVersions: { ...versions, workMailbox: WORK_MAILBOX_TO_VERSION }
|
|
1541
|
+
};
|
|
1542
|
+
if (snapshot.state === null)
|
|
1543
|
+
return { schemaManifest, state: null };
|
|
1544
|
+
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
1545
|
+
const nextMailboxes = {};
|
|
1546
|
+
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
1547
|
+
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
1548
|
+
const pending = mailbox.pending === null
|
|
1549
|
+
? null
|
|
1550
|
+
: migrateMailboxBatchV1(mailbox.pending, key, "pending");
|
|
1551
|
+
const migratedProcessing = mailbox.processing === null
|
|
1552
|
+
? null
|
|
1553
|
+
: migrateMailboxProcessingV1(mailbox.processing, key);
|
|
1554
|
+
const continuationDelivery = migratedProcessing === null
|
|
1555
|
+
? null
|
|
1556
|
+
: migrateMailboxContinuationDeliveryV1(snapshot, mailbox, migratedProcessing, key);
|
|
1557
|
+
nextMailboxes[key] = {
|
|
1558
|
+
...mailbox,
|
|
1559
|
+
schemaVersion: WORK_MAILBOX_TO_VERSION,
|
|
1560
|
+
processing: continuationDelivery === null ? migratedProcessing : null,
|
|
1561
|
+
pending: {
|
|
1562
|
+
normal: pending,
|
|
1563
|
+
userCorrection: null,
|
|
1564
|
+
cursors: { normal: 0, userCorrection: 0 },
|
|
1565
|
+
recentDedupeKeys: []
|
|
1566
|
+
},
|
|
1567
|
+
inputDelivery: continuationDelivery
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
return {
|
|
1571
|
+
schemaManifest,
|
|
1572
|
+
state: { ...snapshot.state, mailboxes: nextMailboxes }
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* v1 used `processing` for both internal/initial dispatch ownership and later
|
|
1577
|
+
* model input. A crashed later input may already have reached the provider;
|
|
1578
|
+
* migrate it to an explicit delivery-unknown fence instead of making it
|
|
1579
|
+
* eligible for a blind resend. Other processing batches retain their old
|
|
1580
|
+
* controller ownership semantics.
|
|
1581
|
+
*/
|
|
1582
|
+
function migrateMailboxContinuationDeliveryV1(snapshot, mailbox, processing, mailboxKey) {
|
|
1583
|
+
const batchId = String(processing.batchId ?? "");
|
|
1584
|
+
if (!batchId.startsWith("agent-input:"))
|
|
1585
|
+
return null;
|
|
1586
|
+
const target = asObject(mailbox.target, `WorkMailbox ${mailboxKey} target`);
|
|
1587
|
+
if (target.kind !== "role") {
|
|
1588
|
+
throw new Error(`WorkMailbox ${mailboxKey} continuation input requires a Role target.`);
|
|
1589
|
+
}
|
|
1590
|
+
const executionRef = asObject(processing.executionRef, `WorkMailbox ${mailboxKey} continuation executionRef`);
|
|
1591
|
+
if (executionRef.type !== "run") {
|
|
1592
|
+
throw new Error(`WorkMailbox ${mailboxKey} continuation input requires a Run reference.`);
|
|
1593
|
+
}
|
|
1594
|
+
const taskId = String(target.taskId);
|
|
1595
|
+
const roleName = String(target.roleName);
|
|
1596
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1597
|
+
const aggregate = asObject(tasks[taskId], `Task aggregate ${taskId}`);
|
|
1598
|
+
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
1599
|
+
const set = asObject(sets[roleName], `Task Role session set ${taskId}/${roleName}`);
|
|
1600
|
+
const sessions = asObject(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
1601
|
+
const active = asObject(sessions[String(set.activeAgentId)], `Task Role active Session ${taskId}/${roleName}`);
|
|
1602
|
+
const activationId = String(active.launchId ?? asObject(set.inFlight, `Task Role in-flight Run ${taskId}/${roleName}`).receiptId);
|
|
1603
|
+
return {
|
|
1604
|
+
attemptId: batchId,
|
|
1605
|
+
lane: "normal",
|
|
1606
|
+
mode: "followup",
|
|
1607
|
+
batch: processing.batch,
|
|
1608
|
+
owner: processing.owner,
|
|
1609
|
+
status: "delivery-unknown",
|
|
1610
|
+
startedAt: processing.startedAt,
|
|
1611
|
+
pushedAt: processing.startedAt,
|
|
1612
|
+
unknownReason: "migrated-unconfirmed-provider-acceptance",
|
|
1613
|
+
executionRef,
|
|
1614
|
+
providerFence: {
|
|
1615
|
+
conversationId: String(active.nativeSessionId),
|
|
1616
|
+
activationId
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function migrateMailboxProcessingV1(value, mailboxKey) {
|
|
1621
|
+
const processing = asObject(value, `WorkMailbox ${mailboxKey} processing`);
|
|
1622
|
+
return {
|
|
1623
|
+
...processing,
|
|
1624
|
+
lane: "normal",
|
|
1625
|
+
batch: migrateMailboxBatchV1(processing.batch, mailboxKey, "processing")
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
function migrateMailboxBatchV1(value, mailboxKey, location) {
|
|
1629
|
+
const batch = asObject(value, `WorkMailbox ${mailboxKey} ${location} batch`);
|
|
1630
|
+
const from = batch.fromSequence;
|
|
1631
|
+
const to = batch.toSequence;
|
|
1632
|
+
if (!Number.isInteger(from) || !Number.isInteger(to)) {
|
|
1633
|
+
throw new Error(`WorkMailbox ${mailboxKey} ${location} sequence range is invalid.`);
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
...batch,
|
|
1637
|
+
sources: ["migration-v1"],
|
|
1638
|
+
dedupeKeys: [`mailbox-v1:${mailboxKey}:${String(from)}-${String(to)}`],
|
|
1639
|
+
deliveryModes: ["followup"]
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function taskRoleSessionSetV5Step() {
|
|
1643
|
+
return {
|
|
1644
|
+
axis: "record",
|
|
1645
|
+
recordKind: "taskRoleSessionSet",
|
|
1646
|
+
fromVersion: TASK_ROLE_SESSION_SET_FROM_VERSION,
|
|
1647
|
+
toVersion: TASK_ROLE_SESSION_SET_TO_VERSION,
|
|
1648
|
+
preconditions: requireTaskRoleSessionSetV4Family,
|
|
1649
|
+
transform: migrateTaskRoleSessionSetV4ToV5,
|
|
1650
|
+
declaredEffects: []
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
function requireTaskRoleSessionSetV4Family(snapshot) {
|
|
1654
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1655
|
+
if (versions.taskRoleSessionSet !== TASK_ROLE_SESSION_SET_FROM_VERSION) {
|
|
1656
|
+
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${TASK_ROLE_SESSION_SET_FROM_VERSION}.`);
|
|
1657
|
+
}
|
|
1658
|
+
if (snapshot.state === null)
|
|
1659
|
+
return;
|
|
1660
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1661
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
1662
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
1663
|
+
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
1664
|
+
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
1665
|
+
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
1666
|
+
if (set.schemaVersion !== TASK_ROLE_SESSION_SET_FROM_VERSION) {
|
|
1667
|
+
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${TASK_ROLE_SESSION_SET_FROM_VERSION}.`);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
function migrateTaskRoleSessionSetV4ToV5(snapshot) {
|
|
1673
|
+
requireTaskRoleSessionSetV4Family(snapshot);
|
|
1674
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1675
|
+
const schemaManifest = {
|
|
1676
|
+
...snapshot.schemaManifest,
|
|
1677
|
+
recordVersions: {
|
|
1678
|
+
...versions,
|
|
1679
|
+
taskRoleSessionSet: TASK_ROLE_SESSION_SET_TO_VERSION
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
if (snapshot.state === null)
|
|
1683
|
+
return { schemaManifest, state: null };
|
|
1684
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1685
|
+
const nextTasks = {};
|
|
1686
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
1687
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
1688
|
+
const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
1689
|
+
const nextSets = {};
|
|
1690
|
+
let events = { ...asObject(aggregate.events, `Task events ${taskId}`) };
|
|
1691
|
+
const highWater = { ...asObject(aggregate.idHighWaterMarks, `Task id high-water marks ${taskId}`) };
|
|
1692
|
+
let eventHighWater = Number(highWater.event ?? 0);
|
|
1693
|
+
if (!Number.isSafeInteger(eventHighWater) || eventHighWater < 0) {
|
|
1694
|
+
throw new Error(`Task event high-water mark is invalid: ${taskId}.`);
|
|
1695
|
+
}
|
|
1696
|
+
for (const [roleName, rawSet] of Object.entries(rawSets)) {
|
|
1697
|
+
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
1698
|
+
const sessions = asObject(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
1699
|
+
const inFlight = set.inFlight === null
|
|
1700
|
+
? null
|
|
1701
|
+
: asObject(set.inFlight, `Task Role in-flight Run ${taskId}/${roleName}`);
|
|
1702
|
+
const pending = set.pendingTurnCompletion === null
|
|
1703
|
+
? null
|
|
1704
|
+
: asObject(set.pendingTurnCompletion, `Task Role Turn completion ${taskId}/${roleName}`);
|
|
1705
|
+
const activeAgentId = String(set.activeAgentId ?? "");
|
|
1706
|
+
const activeSession = sessions[activeAgentId] === undefined
|
|
1707
|
+
? null
|
|
1708
|
+
: asObject(sessions[activeAgentId], `Task Role active Session ${taskId}/${roleName}`);
|
|
1709
|
+
if (inFlight !== null && activeSession === null) {
|
|
1710
|
+
throw new Error(`Task Role in-flight Run has no active Session: ${taskId}/${roleName}.`);
|
|
1711
|
+
}
|
|
1712
|
+
const nextSessions = { ...sessions };
|
|
1713
|
+
if (pending !== null) {
|
|
1714
|
+
if (activeSession === null
|
|
1715
|
+
|| pending.agentId !== activeAgentId
|
|
1716
|
+
|| pending.runId !== inFlight?.runId
|
|
1717
|
+
|| pending.nativeSessionId !== activeSession.nativeSessionId) {
|
|
1718
|
+
throw new Error(`Task Role Turn completion identity is inconsistent: ${taskId}/${roleName}.`);
|
|
1719
|
+
}
|
|
1720
|
+
const recent = Array.isArray(activeSession.recentCompletedTurnIds)
|
|
1721
|
+
? activeSession.recentCompletedTurnIds.map(String)
|
|
1722
|
+
: [];
|
|
1723
|
+
const turnId = String(pending.turnId);
|
|
1724
|
+
nextSessions[activeAgentId] = {
|
|
1725
|
+
...activeSession,
|
|
1726
|
+
recentCompletedTurnIds: recent.includes(turnId) ? recent : [...recent, turnId],
|
|
1727
|
+
updatedAt: String(pending.observedAt)
|
|
1728
|
+
};
|
|
1729
|
+
eventHighWater += 1;
|
|
1730
|
+
const eventId = `event-${eventHighWater}`;
|
|
1731
|
+
const driverId = migratedDriverId(String(activeSession.adapterId));
|
|
1732
|
+
const launchId = String(activeSession.launchId ?? inFlight?.receiptId ?? `legacy-${eventId}`);
|
|
1733
|
+
const observation = {
|
|
1734
|
+
schemaVersion: 2,
|
|
1735
|
+
eventId: `migrated-turn-completed:${taskId}:${roleName}:${turnId}`,
|
|
1736
|
+
semanticKey: [
|
|
1737
|
+
"terminal",
|
|
1738
|
+
driverId,
|
|
1739
|
+
activeAgentId,
|
|
1740
|
+
String(activeSession.nativeSessionId),
|
|
1741
|
+
launchId,
|
|
1742
|
+
"none",
|
|
1743
|
+
"none",
|
|
1744
|
+
turnId,
|
|
1745
|
+
"turn.completed",
|
|
1746
|
+
"terminal",
|
|
1747
|
+
"none",
|
|
1748
|
+
"turn-terminal"
|
|
1749
|
+
].join(":"),
|
|
1750
|
+
kind: "turn.completed",
|
|
1751
|
+
authority: "controller",
|
|
1752
|
+
receivedAt: String(pending.observedAt),
|
|
1753
|
+
observedAt: String(pending.observedAt),
|
|
1754
|
+
fence: {
|
|
1755
|
+
taskId,
|
|
1756
|
+
roleName,
|
|
1757
|
+
runId: String(pending.runId),
|
|
1758
|
+
agentId: activeAgentId,
|
|
1759
|
+
driverId,
|
|
1760
|
+
launchId,
|
|
1761
|
+
sessionGenerationId: launchId,
|
|
1762
|
+
conversationId: String(activeSession.nativeSessionId),
|
|
1763
|
+
activationId: launchId,
|
|
1764
|
+
nativeSessionId: String(activeSession.nativeSessionId),
|
|
1765
|
+
nativeTurnId: turnId
|
|
1766
|
+
},
|
|
1767
|
+
payload: { summary: String(pending.summary) }
|
|
1768
|
+
};
|
|
1769
|
+
events[eventId] = {
|
|
1770
|
+
schemaVersion: 2,
|
|
1771
|
+
id: eventId,
|
|
1772
|
+
taskId,
|
|
1773
|
+
type: "runtime.observation",
|
|
1774
|
+
payload: {
|
|
1775
|
+
eventId: observation.eventId,
|
|
1776
|
+
roleName,
|
|
1777
|
+
agentId: activeAgentId,
|
|
1778
|
+
driverId,
|
|
1779
|
+
launchId,
|
|
1780
|
+
taskId,
|
|
1781
|
+
runId: String(pending.runId),
|
|
1782
|
+
nativeSessionId: String(activeSession.nativeSessionId),
|
|
1783
|
+
kind: "turn.completed",
|
|
1784
|
+
receivedAt: String(pending.observedAt),
|
|
1785
|
+
observation: JSON.stringify(observation)
|
|
1786
|
+
},
|
|
1787
|
+
createdAt: String(pending.observedAt)
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
const providerBinding = inFlight === null || activeSession === null
|
|
1791
|
+
? null
|
|
1792
|
+
: migratedProviderBinding(activeSession, inFlight);
|
|
1793
|
+
const { pendingTurnCompletion: _removed, ...withoutPending } = set;
|
|
1794
|
+
void _removed;
|
|
1795
|
+
nextSets[roleName] = {
|
|
1796
|
+
...withoutPending,
|
|
1797
|
+
schemaVersion: TASK_ROLE_SESSION_SET_TO_VERSION,
|
|
1798
|
+
sessions: nextSessions,
|
|
1799
|
+
providerBinding
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
highWater.event = eventHighWater;
|
|
1803
|
+
nextTasks[taskId] = {
|
|
1804
|
+
...aggregate,
|
|
1805
|
+
roleSessionSets: nextSets,
|
|
1806
|
+
events,
|
|
1807
|
+
idHighWaterMarks: highWater
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
return {
|
|
1811
|
+
schemaManifest,
|
|
1812
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
function migratedProviderBinding(session, inFlight) {
|
|
1816
|
+
const conversationId = String(session.nativeSessionId);
|
|
1817
|
+
const activationId = String(session.launchId ?? inFlight.receiptId);
|
|
1818
|
+
const startedAt = String(inFlight.preparedAt);
|
|
1819
|
+
const live = session.status !== "stopped" && session.status !== "broken";
|
|
1820
|
+
return {
|
|
1821
|
+
schemaVersion: 1,
|
|
1822
|
+
providerNamespace: migratedDriverId(String(session.adapterId)),
|
|
1823
|
+
accountScope: String(session.agentId),
|
|
1824
|
+
runId: String(inFlight.runId),
|
|
1825
|
+
currentConversationEpoch: 1,
|
|
1826
|
+
conversations: [{
|
|
1827
|
+
conversationId,
|
|
1828
|
+
epoch: 1,
|
|
1829
|
+
status: "current",
|
|
1830
|
+
recoverability: "unknown",
|
|
1831
|
+
createdAt: String(session.createdAt ?? startedAt)
|
|
1832
|
+
}],
|
|
1833
|
+
activations: [{
|
|
1834
|
+
activationId,
|
|
1835
|
+
conversationId,
|
|
1836
|
+
generation: 1,
|
|
1837
|
+
status: live ? "active" : session.status === "broken" ? "failed" : "ended",
|
|
1838
|
+
writerLease: live,
|
|
1839
|
+
startedAt,
|
|
1840
|
+
...(live ? {} : { endedAt: String(session.updatedAt) })
|
|
1841
|
+
}]
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
function migratedDriverId(adapterId) {
|
|
1845
|
+
if (adapterId === "claude")
|
|
1846
|
+
return "anthropic/claude-code";
|
|
1847
|
+
if (adapterId === "codex")
|
|
1848
|
+
return "openai/codex";
|
|
1849
|
+
return adapterId.includes("/") ? adapterId : `legacy/${adapterId}`;
|
|
1850
|
+
}
|
|
1378
1851
|
/**
|
|
1379
1852
|
* Project ownership is a new required field. Every pre-v3 Project is a
|
|
1380
1853
|
* user-registered checkout, so the historical binding is `external`; a managed
|
|
@@ -27,7 +27,7 @@ export const SQLITE_LAYOUT_VERSION = 7;
|
|
|
27
27
|
/** The aggregate version of the normalized SQLite schema. */
|
|
28
28
|
export const SQLITE_AGGREGATE_VERSION = 1;
|
|
29
29
|
/** The current schema migration version. */
|
|
30
|
-
export const SQLITE_SCHEMA_VERSION =
|
|
30
|
+
export const SQLITE_SCHEMA_VERSION = 14;
|
|
31
31
|
/** Telemetry retention bounds (§4.4). Open question 3 in §11; defaults from the design. */
|
|
32
32
|
export const TELEMETRY_KEEP_PER_GENERATION = 200;
|
|
33
33
|
export const TELEMETRY_RUN_CAP = 50_000;
|
|
@@ -183,21 +183,6 @@ CREATE TABLE IF NOT EXISTS mailboxes (
|
|
|
183
183
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_mailboxes_target_key
|
|
184
184
|
ON mailboxes(target_key);
|
|
185
185
|
|
|
186
|
-
-- Signals are the durable append log; (mailbox, sequence) is the exactly-once key.
|
|
187
|
-
CREATE TABLE IF NOT EXISTS mailbox_signals (
|
|
188
|
-
mailbox_id INTEGER NOT NULL,
|
|
189
|
-
sequence INTEGER NOT NULL,
|
|
190
|
-
reason TEXT NOT NULL,
|
|
191
|
-
ref_type TEXT,
|
|
192
|
-
ref_task_id TEXT,
|
|
193
|
-
ref_id TEXT,
|
|
194
|
-
occurred_at TEXT NOT NULL,
|
|
195
|
-
request_id TEXT NOT NULL,
|
|
196
|
-
PRIMARY KEY (mailbox_id, sequence),
|
|
197
|
-
FOREIGN KEY (mailbox_id) REFERENCES mailboxes(mailbox_id)
|
|
198
|
-
);
|
|
199
|
-
CREATE INDEX IF NOT EXISTS idx_mailbox_signals_request ON mailbox_signals(request_id);
|
|
200
|
-
|
|
201
186
|
-- Task-partitioned tables (§4.3). Every task-local read constrains task_id. ----
|
|
202
187
|
|
|
203
188
|
CREATE TABLE IF NOT EXISTS task_records (
|
|
@@ -772,6 +757,104 @@ SELECT
|
|
|
772
757
|
FROM role_session_sets AS source
|
|
773
758
|
WHERE json_type(source.payload, '$.pendingTurnCompletion') = 'object';
|
|
774
759
|
`;
|
|
760
|
+
/** Migration 14: WorkMailbox v2 lanes and the single durable model-input delivery. */
|
|
761
|
+
const MIGRATION_14_SQL = `
|
|
762
|
+
ALTER TABLE mailboxes ADD COLUMN input_delivery TEXT;
|
|
763
|
+
|
|
764
|
+
UPDATE mailboxes
|
|
765
|
+
SET processing = CASE
|
|
766
|
+
WHEN processing IS NULL THEN NULL
|
|
767
|
+
ELSE json_set(
|
|
768
|
+
processing,
|
|
769
|
+
'$.lane', 'normal',
|
|
770
|
+
'$.batch.sources', json_array('migration-v1'),
|
|
771
|
+
'$.batch.dedupeKeys', json_array(
|
|
772
|
+
'mailbox-v1:' || target_key || ':'
|
|
773
|
+
|| json_extract(processing, '$.batch.fromSequence') || '-'
|
|
774
|
+
|| json_extract(processing, '$.batch.toSequence')
|
|
775
|
+
),
|
|
776
|
+
'$.batch.deliveryModes', json_array('followup')
|
|
777
|
+
)
|
|
778
|
+
END;
|
|
779
|
+
|
|
780
|
+
UPDATE mailboxes
|
|
781
|
+
SET input_delivery = json_object(
|
|
782
|
+
'attemptId', json_extract(processing, '$.batchId'),
|
|
783
|
+
'lane', 'normal',
|
|
784
|
+
'mode', 'followup',
|
|
785
|
+
'batch', json(json_extract(processing, '$.batch')),
|
|
786
|
+
'owner', json_extract(processing, '$.owner'),
|
|
787
|
+
'status', 'delivery-unknown',
|
|
788
|
+
'startedAt', json_extract(processing, '$.startedAt'),
|
|
789
|
+
'pushedAt', json_extract(processing, '$.startedAt'),
|
|
790
|
+
'unknownReason', 'migrated-unconfirmed-provider-acceptance',
|
|
791
|
+
'executionRef', json(json_extract(processing, '$.executionRef')),
|
|
792
|
+
'providerFence', json_object(
|
|
793
|
+
'conversationId', (
|
|
794
|
+
SELECT json_extract(
|
|
795
|
+
json_extract(role_session_sets.payload, '$.sessions'),
|
|
796
|
+
'$.' || json_quote(json_extract(role_session_sets.payload, '$.activeAgentId'))
|
|
797
|
+
|| '.nativeSessionId'
|
|
798
|
+
)
|
|
799
|
+
FROM role_session_sets
|
|
800
|
+
WHERE role_session_sets.task_id = mailboxes.task_id
|
|
801
|
+
AND role_session_sets.role_name = mailboxes.role_name
|
|
802
|
+
),
|
|
803
|
+
'activationId', COALESCE((
|
|
804
|
+
SELECT json_extract(
|
|
805
|
+
json_extract(role_session_sets.payload, '$.sessions'),
|
|
806
|
+
'$.' || json_quote(json_extract(role_session_sets.payload, '$.activeAgentId'))
|
|
807
|
+
|| '.launchId'
|
|
808
|
+
)
|
|
809
|
+
FROM role_session_sets
|
|
810
|
+
WHERE role_session_sets.task_id = mailboxes.task_id
|
|
811
|
+
AND role_session_sets.role_name = mailboxes.role_name
|
|
812
|
+
), (
|
|
813
|
+
SELECT json_extract(role_session_sets.payload, '$.inFlight.receiptId')
|
|
814
|
+
FROM role_session_sets
|
|
815
|
+
WHERE role_session_sets.task_id = mailboxes.task_id
|
|
816
|
+
AND role_session_sets.role_name = mailboxes.role_name
|
|
817
|
+
))
|
|
818
|
+
)
|
|
819
|
+
)
|
|
820
|
+
WHERE target_kind = 'role'
|
|
821
|
+
AND json_extract(processing, '$.batchId') LIKE 'agent-input:%';
|
|
822
|
+
|
|
823
|
+
UPDATE mailboxes
|
|
824
|
+
SET processing = NULL
|
|
825
|
+
WHERE input_delivery IS NOT NULL;
|
|
826
|
+
|
|
827
|
+
UPDATE mailboxes
|
|
828
|
+
SET pending = json_object(
|
|
829
|
+
'normal', CASE
|
|
830
|
+
WHEN pending IS NULL THEN json('null')
|
|
831
|
+
ELSE json(json_set(
|
|
832
|
+
pending,
|
|
833
|
+
'$.sources', json_array('migration-v1'),
|
|
834
|
+
'$.dedupeKeys', json_array(
|
|
835
|
+
'mailbox-v1:' || target_key || ':'
|
|
836
|
+
|| json_extract(pending, '$.fromSequence') || '-'
|
|
837
|
+
|| json_extract(pending, '$.toSequence')
|
|
838
|
+
),
|
|
839
|
+
'$.deliveryModes', json_array('followup')
|
|
840
|
+
))
|
|
841
|
+
END,
|
|
842
|
+
'userCorrection', json('null'),
|
|
843
|
+
'cursors', json_object('normal', 0, 'userCorrection', 0),
|
|
844
|
+
'recentDedupeKeys', json_array()
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
DROP INDEX idx_mailboxes_ready;
|
|
848
|
+
CREATE INDEX idx_mailboxes_ready
|
|
849
|
+
ON mailboxes(target_key)
|
|
850
|
+
WHERE processing IS NOT NULL
|
|
851
|
+
OR input_delivery IS NOT NULL
|
|
852
|
+
OR json_type(pending, '$.normal') <> 'null'
|
|
853
|
+
OR json_type(pending, '$.userCorrection') <> 'null';
|
|
854
|
+
|
|
855
|
+
DROP TABLE pending_runtime_turn_completions;
|
|
856
|
+
DROP TABLE IF EXISTS mailbox_signals;
|
|
857
|
+
`;
|
|
775
858
|
const MIGRATIONS = [
|
|
776
859
|
{ version: 1, axis: "layout", sql: MIGRATION_1_SQL },
|
|
777
860
|
{ version: 2, axis: "record", recordKind: "durableJob+capability-grant+release-workflow", sql: MIGRATION_2_SQL },
|
|
@@ -785,14 +868,14 @@ const MIGRATIONS = [
|
|
|
785
868
|
{ version: 10, axis: "layout", sql: MIGRATION_10_SQL },
|
|
786
869
|
{ version: 11, axis: "layout", sql: MIGRATION_11_SQL },
|
|
787
870
|
{ version: 12, axis: "layout", sql: MIGRATION_12_SQL },
|
|
788
|
-
{ version: 13, axis: "layout", sql: MIGRATION_13_SQL }
|
|
871
|
+
{ version: 13, axis: "layout", sql: MIGRATION_13_SQL },
|
|
872
|
+
{ version: 14, axis: "record", recordKind: "workMailbox", sql: MIGRATION_14_SQL }
|
|
789
873
|
];
|
|
790
874
|
/** Current hot-path indexes whose absence would invalidate a current Home. */
|
|
791
875
|
const REQUIRED_SCHEMA_INDEXES = [
|
|
792
876
|
"idx_mailboxes_ready",
|
|
793
877
|
"idx_runtime_session_cleanup_required",
|
|
794
|
-
"idx_input_requests_open_hot"
|
|
795
|
-
"idx_pending_runtime_turn_completions_due"
|
|
878
|
+
"idx_input_requests_open_hot"
|
|
796
879
|
];
|
|
797
880
|
function checksum(sql) {
|
|
798
881
|
return createHash("sha256").update(sql).digest("hex");
|
|
@@ -1023,7 +1106,6 @@ export const SQLITE_SCHEMA_TABLES = [
|
|
|
1023
1106
|
"job_caller_key_hashes",
|
|
1024
1107
|
"outbox",
|
|
1025
1108
|
"mailboxes",
|
|
1026
|
-
"mailbox_signals",
|
|
1027
1109
|
"task_records",
|
|
1028
1110
|
"task_roles",
|
|
1029
1111
|
"role_session_sets",
|
|
@@ -1047,7 +1129,6 @@ export const SQLITE_SCHEMA_TABLES = [
|
|
|
1047
1129
|
"release_workflows",
|
|
1048
1130
|
"session_owners",
|
|
1049
1131
|
"runtime_session_candidates",
|
|
1050
|
-
"pending_runtime_turn_completions",
|
|
1051
1132
|
"resource_registry",
|
|
1052
1133
|
"gate_artifacts",
|
|
1053
1134
|
"gate_artifact_logs"
|