@zq-silk/yui 0.13.8 → 0.13.9
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 +9 -8
- package/dist/cli/commandCatalog.js +21 -2
- package/dist/cli.js +42 -13
- package/dist/commands/agentCommands.js +1 -1
- package/dist/commands/configCommands.js +1 -86
- package/dist/commands/executionAuditCommands.js +17 -16
- package/dist/commands/globalRoleCommands.js +4 -4
- package/dist/commands/sessionCommands.js +2 -6
- package/dist/commands/taskActor.js +1 -2
- package/dist/commands/taskCommands.js +92 -9
- package/dist/commands/taskRoleRuntimeStatus.js +3 -3
- package/dist/config/configCatalog.js +1 -6
- package/dist/config/yuiConfig.js +0 -79
- package/dist/controller/clientRuntime.js +40 -3
- package/dist/controller/controller.js +29 -52
- package/dist/controller/fileSchedulerStoreAdapter.js +305 -1056
- package/dist/controller/runtime.js +12 -5
- package/dist/controller/runtimeHookRunFence.js +3 -9
- package/dist/controller/runtimeLaunchCoordinator.js +44 -67
- package/dist/controller/structuredProviderObservation.js +18 -5
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/execution/executionHealth.js +1 -1
- package/dist/executor/agentExecutor.js +92 -68
- package/dist/executor/executorRegistry.js +8 -20
- package/dist/executor/fileRoleLaunchPlanner.js +24 -90
- package/dist/executor/turnCompletion.js +5 -5
- package/dist/lifecycle/exactRunTerminalization.js +1 -1
- package/dist/observability/executionAudit.js +40 -94
- package/dist/operator/operatorSessionHistory.js +7 -5
- package/dist/role/role.js +1 -1
- package/dist/run/agentRun.js +4 -54
- package/dist/runtime/agentDriver.js +2 -0
- package/dist/runtime/agentError.js +114 -0
- package/dist/runtime/agentHost.js +55 -82
- package/dist/runtime/builtinAgentDrivers.js +21 -9
- package/dist/runtime/builtinAgentErrorMappers.js +150 -0
- package/dist/runtime/exactControlPlane.js +6 -12
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/launchBroker.js +5 -19
- package/dist/runtime/lifecycleReservation.js +20 -4
- package/dist/runtime/providerRuntimeIdentity.js +3 -2
- package/dist/runtime/runtimeBinding.js +0 -27
- package/dist/runtime/runtimeObservation.js +7 -16
- package/dist/runtime/runtimeSessionCandidate.js +3 -10
- package/dist/runtime/sessionLaunchRequest.js +1 -2
- package/dist/runtime/sessionReconciliation.js +2 -2
- package/dist/runtime/structuredProviderHost.js +44 -79
- package/dist/runtime/taskRuntimeIsolation.js +0 -7
- package/dist/runtime/tmuxAdapters.js +6 -49
- package/dist/scheduler/activeRoleRunDelivery.js +218 -168
- package/dist/scheduler/leaderWakeupProcessor.js +126 -86
- package/dist/scheduler/roleRunLiveness.js +4 -1
- package/dist/scheduler/roleRunStall.js +7 -11
- package/dist/scheduler/wakeReason.js +4 -0
- package/dist/storage/migration/productionRegistry.js +332 -0
- package/dist/storage/sqliteSchema.js +54 -2
- package/dist/storage/sqliteStore.js +11 -44
- package/dist/storage/taskStore.js +7 -35
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +30 -8
- package/skills/yui-operator/SKILL.md +14 -6
- package/skills/yui-runtime/SKILL.md +6 -4
- package/dist/lifecycle/providerErrorClass.js +0 -152
- package/dist/run/providerRetry.js +0 -226
- package/dist/run/providerRetryConfig.js +0 -27
- package/dist/runtime/providerErrorCodes.js +0 -278
- package/dist/runtime/providerRecoveryDecision.js +0 -55
|
@@ -51,6 +51,8 @@ const AGENT_RUN_CONTEXT_PROTOCOL_FROM_VERSION = 7;
|
|
|
51
51
|
const AGENT_RUN_CONTEXT_PROTOCOL_TO_VERSION = 8;
|
|
52
52
|
const AGENT_RUN_RETRY_EPISODE_FROM_VERSION = 8;
|
|
53
53
|
const AGENT_RUN_RETRY_EPISODE_TO_VERSION = 9;
|
|
54
|
+
const AGENT_RUN_ERROR_FACTS_FROM_VERSION = 9;
|
|
55
|
+
const AGENT_RUN_ERROR_FACTS_TO_VERSION = 10;
|
|
54
56
|
const MESSAGE_WAKE_POLICY_FROM_VERSION = 2;
|
|
55
57
|
const MESSAGE_WAKE_POLICY_TO_VERSION = 3;
|
|
56
58
|
const REVIEW_ROUND_FROM_VERSION = 2;
|
|
@@ -99,6 +101,8 @@ const RELEASE_WORKFLOW_FROM_VERSION = 0;
|
|
|
99
101
|
const RELEASE_WORKFLOW_TO_VERSION = 1;
|
|
100
102
|
const WORK_MAILBOX_FROM_VERSION = 1;
|
|
101
103
|
const WORK_MAILBOX_TO_VERSION = 2;
|
|
104
|
+
const HOST_OWNED_WORK_MAILBOX_FROM_VERSION = 2;
|
|
105
|
+
const HOST_OWNED_WORK_MAILBOX_TO_VERSION = 3;
|
|
102
106
|
const TASK_ROLE_FROM_VERSION = 3;
|
|
103
107
|
const TASK_ROLE_TO_VERSION = 4;
|
|
104
108
|
const TASK_ROLE_SESSION_SET_FROM_VERSION = 4;
|
|
@@ -107,12 +111,18 @@ const STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION = 5;
|
|
|
107
111
|
const STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION = 6;
|
|
108
112
|
const AGENT_FIRST_SESSION_SET_FROM_VERSION = 6;
|
|
109
113
|
const AGENT_FIRST_SESSION_SET_TO_VERSION = 7;
|
|
114
|
+
const UNIFIED_TASK_SESSION_SET_FROM_VERSION = 7;
|
|
115
|
+
const UNIFIED_TASK_SESSION_SET_TO_VERSION = 8;
|
|
116
|
+
const UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION = 3;
|
|
117
|
+
const UNIFIED_GLOBAL_SESSION_SET_TO_VERSION = 4;
|
|
110
118
|
const PUBLICATION_REFERENCE_FROM_VERSION = 0;
|
|
111
119
|
const PUBLICATION_REFERENCE_TO_VERSION = 1;
|
|
112
120
|
const CONFIG_FROM_VERSION = 1;
|
|
113
121
|
const CONFIG_TO_VERSION = 2;
|
|
114
122
|
const CONFIG_REVIEW_CONTROLS_FROM_VERSION = 2;
|
|
115
123
|
const CONFIG_REVIEW_CONTROLS_TO_VERSION = 3;
|
|
124
|
+
const CONFIG_AGENT_RECOVERY_FROM_VERSION = 3;
|
|
125
|
+
const CONFIG_AGENT_RECOVERY_TO_VERSION = 4;
|
|
116
126
|
/**
|
|
117
127
|
* Build the authoritative production graph. Transition intent and executable
|
|
118
128
|
* transforms are registered together here; compatible loading and offline
|
|
@@ -163,6 +173,7 @@ export function createProductionStorageRegistry() {
|
|
|
163
173
|
.registerOfflineMigration(projectOwnershipStep())
|
|
164
174
|
.registerOfflineMigration(configV2Step())
|
|
165
175
|
.registerOfflineMigration(configReviewControlsRemovalStep())
|
|
176
|
+
.registerOfflineMigration(configAgentRecoveryStep())
|
|
166
177
|
.registerCompatible(projectKnowledgeProposalsStep())
|
|
167
178
|
.registerCompatible(projectLifecycleStep())
|
|
168
179
|
.registerOfflineMigration(taskWorkspaceIdentityStep())
|
|
@@ -183,6 +194,7 @@ export function createProductionStorageRegistry() {
|
|
|
183
194
|
.registerOfflineMigration(recordFamilyStep("agentRun", AGENT_RUN_OPTIONAL_FIELDS_FROM_VERSION, AGENT_RUN_OPTIONAL_FIELDS_TO_VERSION, "agentRuns"))
|
|
184
195
|
.registerOfflineMigration(agentRunContextProtocolStep())
|
|
185
196
|
.registerOfflineMigration(agentRunRetryEpisodeStep())
|
|
197
|
+
.registerOfflineMigration(agentRunErrorFactsStep())
|
|
186
198
|
.registerOfflineMigration(messageWakePolicyStep())
|
|
187
199
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_FROM_VERSION, REVIEW_ROUND_TO_VERSION, "reviewRounds"))
|
|
188
200
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION, REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION, "reviewRounds"))
|
|
@@ -207,10 +219,13 @@ export function createProductionStorageRegistry() {
|
|
|
207
219
|
.registerOfflineMigration(capabilityGrantIntroductionStep())
|
|
208
220
|
.registerOfflineMigration(releaseWorkflowIntroductionStep())
|
|
209
221
|
.registerOfflineMigration(workMailboxV2Step())
|
|
222
|
+
.registerOfflineMigration(hostOwnedWorkMailboxStep())
|
|
210
223
|
.registerOfflineMigration(agentRunOwnedTaskRoleStep())
|
|
211
224
|
.registerOfflineMigration(taskRoleSessionSetV5Step())
|
|
212
225
|
.registerOfflineMigration(structuredProviderSessionSetV6Step())
|
|
213
226
|
.registerOfflineMigration(agentFirstSessionSetV7Step())
|
|
227
|
+
.registerOfflineMigration(unifiedTaskSessionSetStep())
|
|
228
|
+
.registerOfflineMigration(unifiedGlobalSessionSetStep())
|
|
214
229
|
.registerOfflineMigration(publicationReferenceIntroductionStep());
|
|
215
230
|
assertRegistryCoversBaselineToCurrent(registry);
|
|
216
231
|
return registry;
|
|
@@ -379,6 +394,53 @@ function migrateConfigV2ToV3(snapshot) {
|
|
|
379
394
|
}
|
|
380
395
|
};
|
|
381
396
|
}
|
|
397
|
+
/** Removes Core-owned Provider retry policy; recovery is chosen from AgentError facts. */
|
|
398
|
+
function configAgentRecoveryStep() {
|
|
399
|
+
return {
|
|
400
|
+
axis: "record",
|
|
401
|
+
recordKind: "config",
|
|
402
|
+
fromVersion: CONFIG_AGENT_RECOVERY_FROM_VERSION,
|
|
403
|
+
toVersion: CONFIG_AGENT_RECOVERY_TO_VERSION,
|
|
404
|
+
preconditions: requireConfigV3,
|
|
405
|
+
transform: migrateConfigV3ToV4,
|
|
406
|
+
declaredEffects: []
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function requireConfigV3(snapshot) {
|
|
410
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
411
|
+
if (versions.config !== CONFIG_AGENT_RECOVERY_FROM_VERSION) {
|
|
412
|
+
throw new Error(`Record config migration requires manifest version ${CONFIG_AGENT_RECOVERY_FROM_VERSION}.`);
|
|
413
|
+
}
|
|
414
|
+
if (snapshot.state === null)
|
|
415
|
+
return;
|
|
416
|
+
const config = asObject(snapshot.state.config, "Yui config");
|
|
417
|
+
if (config.schemaVersion !== CONFIG_AGENT_RECOVERY_FROM_VERSION) {
|
|
418
|
+
throw new Error(`Yui config must use schemaVersion ${CONFIG_AGENT_RECOVERY_FROM_VERSION} before migration.`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function migrateConfigV3ToV4(snapshot) {
|
|
422
|
+
requireConfigV3(snapshot);
|
|
423
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
424
|
+
const schemaManifest = {
|
|
425
|
+
...snapshot.schemaManifest,
|
|
426
|
+
recordVersions: { ...versions, config: CONFIG_AGENT_RECOVERY_TO_VERSION }
|
|
427
|
+
};
|
|
428
|
+
if (snapshot.state === null)
|
|
429
|
+
return { schemaManifest, state: null };
|
|
430
|
+
const config = asObject(snapshot.state.config, "Yui config");
|
|
431
|
+
const { providerRetryMode: _mode, providerRetryAdapters: _adapters, providerRetryDelaysSeconds: _delays, providerRetryMaxWindowSeconds: _window, ...retained } = config;
|
|
432
|
+
void _mode;
|
|
433
|
+
void _adapters;
|
|
434
|
+
void _delays;
|
|
435
|
+
void _window;
|
|
436
|
+
return {
|
|
437
|
+
schemaManifest,
|
|
438
|
+
state: {
|
|
439
|
+
...snapshot.state,
|
|
440
|
+
config: { ...retained, schemaVersion: CONFIG_AGENT_RECOVERY_TO_VERSION }
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
382
444
|
function workItemExecutionGroupHistoryStep() {
|
|
383
445
|
return {
|
|
384
446
|
axis: "record",
|
|
@@ -2245,6 +2307,46 @@ function migrateAgentRunsToRetryEpisodes(snapshot) {
|
|
|
2245
2307
|
}
|
|
2246
2308
|
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
2247
2309
|
}
|
|
2310
|
+
/** Deletes the retired retry episode; an AgentRun now records scheduling only. */
|
|
2311
|
+
function agentRunErrorFactsStep() {
|
|
2312
|
+
return {
|
|
2313
|
+
axis: "record",
|
|
2314
|
+
recordKind: "agentRun",
|
|
2315
|
+
fromVersion: AGENT_RUN_ERROR_FACTS_FROM_VERSION,
|
|
2316
|
+
toVersion: AGENT_RUN_ERROR_FACTS_TO_VERSION,
|
|
2317
|
+
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_ERROR_FACTS_FROM_VERSION, "agentRuns"),
|
|
2318
|
+
transform: migrateAgentRunsToErrorFacts,
|
|
2319
|
+
declaredEffects: []
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
function migrateAgentRunsToErrorFacts(snapshot) {
|
|
2323
|
+
requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_ERROR_FACTS_FROM_VERSION, "agentRuns");
|
|
2324
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2325
|
+
const schemaManifest = {
|
|
2326
|
+
...snapshot.schemaManifest,
|
|
2327
|
+
recordVersions: { ...versions, agentRun: AGENT_RUN_ERROR_FACTS_TO_VERSION }
|
|
2328
|
+
};
|
|
2329
|
+
if (snapshot.state === null)
|
|
2330
|
+
return { schemaManifest, state: null };
|
|
2331
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2332
|
+
const nextTasks = {};
|
|
2333
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2334
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2335
|
+
const records = asObject(task.agentRuns ?? {}, `agentRun map ${taskId}`);
|
|
2336
|
+
const nextRecords = {};
|
|
2337
|
+
for (const [runId, rawRecord] of Object.entries(records)) {
|
|
2338
|
+
const record = asObject(rawRecord, `agentRun ${taskId}/${runId}`);
|
|
2339
|
+
const { providerRetry: _retry, ...retained } = record;
|
|
2340
|
+
void _retry;
|
|
2341
|
+
nextRecords[runId] = {
|
|
2342
|
+
...retained,
|
|
2343
|
+
schemaVersion: AGENT_RUN_ERROR_FACTS_TO_VERSION
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
nextTasks[taskId] = { ...task, agentRuns: nextRecords };
|
|
2347
|
+
}
|
|
2348
|
+
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
2349
|
+
}
|
|
2248
2350
|
function requiredMigrationPositiveInteger(value, label) {
|
|
2249
2351
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
2250
2352
|
throw new Error(`${label} must be a positive integer.`);
|
|
@@ -2683,6 +2785,77 @@ function migrateWorkMailboxV1ToV2(snapshot) {
|
|
|
2683
2785
|
state: { ...snapshot.state, mailboxes: nextMailboxes }
|
|
2684
2786
|
};
|
|
2685
2787
|
}
|
|
2788
|
+
/** A Host launch reservation belongs to the Role owner and launchId, never a Run. */
|
|
2789
|
+
function hostOwnedWorkMailboxStep() {
|
|
2790
|
+
return {
|
|
2791
|
+
axis: "record",
|
|
2792
|
+
recordKind: "workMailbox",
|
|
2793
|
+
fromVersion: HOST_OWNED_WORK_MAILBOX_FROM_VERSION,
|
|
2794
|
+
toVersion: HOST_OWNED_WORK_MAILBOX_TO_VERSION,
|
|
2795
|
+
preconditions: requireWorkMailboxV2Family,
|
|
2796
|
+
transform: migrateWorkMailboxV2ToV3,
|
|
2797
|
+
declaredEffects: []
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
function requireWorkMailboxV2Family(snapshot) {
|
|
2801
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2802
|
+
if (versions.workMailbox !== HOST_OWNED_WORK_MAILBOX_FROM_VERSION) {
|
|
2803
|
+
throw new Error(`Record workMailbox migration requires manifest version ${HOST_OWNED_WORK_MAILBOX_FROM_VERSION}.`);
|
|
2804
|
+
}
|
|
2805
|
+
if (snapshot.state === null)
|
|
2806
|
+
return;
|
|
2807
|
+
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
2808
|
+
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
2809
|
+
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
2810
|
+
if (mailbox.schemaVersion !== HOST_OWNED_WORK_MAILBOX_FROM_VERSION) {
|
|
2811
|
+
throw new Error(`WorkMailbox ${key} must use schemaVersion ${HOST_OWNED_WORK_MAILBOX_FROM_VERSION}.`);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
function migrateWorkMailboxV2ToV3(snapshot) {
|
|
2816
|
+
requireWorkMailboxV2Family(snapshot);
|
|
2817
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2818
|
+
const schemaManifest = {
|
|
2819
|
+
...snapshot.schemaManifest,
|
|
2820
|
+
recordVersions: { ...versions, workMailbox: HOST_OWNED_WORK_MAILBOX_TO_VERSION }
|
|
2821
|
+
};
|
|
2822
|
+
if (snapshot.state === null)
|
|
2823
|
+
return { schemaManifest, state: null };
|
|
2824
|
+
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
2825
|
+
const nextMailboxes = {};
|
|
2826
|
+
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
2827
|
+
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
2828
|
+
const target = asObject(mailbox.target, `WorkMailbox ${key} target`);
|
|
2829
|
+
const processing = mailbox.processing === null
|
|
2830
|
+
? null
|
|
2831
|
+
: asObject(mailbox.processing, `WorkMailbox ${key} processing`);
|
|
2832
|
+
const batch = processing === null
|
|
2833
|
+
? null
|
|
2834
|
+
: asObject(processing.batch, `WorkMailbox ${key} processing batch`);
|
|
2835
|
+
const lifecycleReservation = (target.kind === "role-runtime"
|
|
2836
|
+
|| target.kind === "global-role-runtime")
|
|
2837
|
+
&& Array.isArray(batch?.reasons)
|
|
2838
|
+
&& batch.reasons.includes("runtime-launch-reserved");
|
|
2839
|
+
const nextProcessing = processing === null
|
|
2840
|
+
? null
|
|
2841
|
+
: lifecycleReservation
|
|
2842
|
+
? (() => {
|
|
2843
|
+
const { executionRef: _run, ...retained } = processing;
|
|
2844
|
+
void _run;
|
|
2845
|
+
return retained;
|
|
2846
|
+
})()
|
|
2847
|
+
: processing;
|
|
2848
|
+
nextMailboxes[key] = {
|
|
2849
|
+
...mailbox,
|
|
2850
|
+
schemaVersion: HOST_OWNED_WORK_MAILBOX_TO_VERSION,
|
|
2851
|
+
processing: nextProcessing
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
return {
|
|
2855
|
+
schemaManifest,
|
|
2856
|
+
state: { ...snapshot.state, mailboxes: nextMailboxes }
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2686
2859
|
/**
|
|
2687
2860
|
* v1 used `processing` for both internal/initial dispatch ownership and later
|
|
2688
2861
|
* model input. A crashed later input may already have reached the provider;
|
|
@@ -2861,6 +3034,165 @@ function migrateTaskRoleSessionSetV6ToV7(snapshot) {
|
|
|
2861
3034
|
}
|
|
2862
3035
|
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
2863
3036
|
}
|
|
3037
|
+
/** Session stores existence only; Host readiness and Turn activity are runtime facts. */
|
|
3038
|
+
function unifiedTaskSessionSetStep() {
|
|
3039
|
+
return {
|
|
3040
|
+
axis: "record",
|
|
3041
|
+
recordKind: "taskRoleSessionSet",
|
|
3042
|
+
fromVersion: UNIFIED_TASK_SESSION_SET_FROM_VERSION,
|
|
3043
|
+
toVersion: UNIFIED_TASK_SESSION_SET_TO_VERSION,
|
|
3044
|
+
preconditions: requireTaskRoleSessionSetV7Family,
|
|
3045
|
+
transform: migrateTaskRoleSessionSetV7ToV8,
|
|
3046
|
+
declaredEffects: []
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
function requireTaskRoleSessionSetV7Family(snapshot) {
|
|
3050
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3051
|
+
if (versions.taskRoleSessionSet !== UNIFIED_TASK_SESSION_SET_FROM_VERSION) {
|
|
3052
|
+
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${UNIFIED_TASK_SESSION_SET_FROM_VERSION}.`);
|
|
3053
|
+
}
|
|
3054
|
+
if (snapshot.state === null)
|
|
3055
|
+
return;
|
|
3056
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3057
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3058
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3059
|
+
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3060
|
+
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3061
|
+
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3062
|
+
if (set.schemaVersion !== UNIFIED_TASK_SESSION_SET_FROM_VERSION) {
|
|
3063
|
+
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${UNIFIED_TASK_SESSION_SET_FROM_VERSION}.`);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
function migrateTaskRoleSessionSetV7ToV8(snapshot) {
|
|
3069
|
+
requireTaskRoleSessionSetV7Family(snapshot);
|
|
3070
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3071
|
+
const schemaManifest = {
|
|
3072
|
+
...snapshot.schemaManifest,
|
|
3073
|
+
recordVersions: {
|
|
3074
|
+
...versions,
|
|
3075
|
+
taskRoleSessionSet: UNIFIED_TASK_SESSION_SET_TO_VERSION
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
if (snapshot.state === null)
|
|
3079
|
+
return { schemaManifest, state: null };
|
|
3080
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3081
|
+
const nextTasks = {};
|
|
3082
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3083
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3084
|
+
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3085
|
+
const nextSets = {};
|
|
3086
|
+
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3087
|
+
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3088
|
+
const sessions = migrateRoleAgentSessionMap(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
3089
|
+
const history = set.history === undefined
|
|
3090
|
+
? undefined
|
|
3091
|
+
: migrateRoleAgentSessionArray(set.history, `Task Role session history ${taskId}/${roleName}`);
|
|
3092
|
+
nextSets[roleName] = {
|
|
3093
|
+
...set,
|
|
3094
|
+
schemaVersion: UNIFIED_TASK_SESSION_SET_TO_VERSION,
|
|
3095
|
+
sessions,
|
|
3096
|
+
...(history === undefined ? {} : { history })
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
|
|
3100
|
+
}
|
|
3101
|
+
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
3102
|
+
}
|
|
3103
|
+
function unifiedGlobalSessionSetStep() {
|
|
3104
|
+
return {
|
|
3105
|
+
axis: "record",
|
|
3106
|
+
recordKind: "globalRoleSessionSet",
|
|
3107
|
+
fromVersion: UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION,
|
|
3108
|
+
toVersion: UNIFIED_GLOBAL_SESSION_SET_TO_VERSION,
|
|
3109
|
+
preconditions: requireGlobalRoleSessionSetV3Family,
|
|
3110
|
+
transform: migrateGlobalRoleSessionSetV3ToV4,
|
|
3111
|
+
declaredEffects: []
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
function requireGlobalRoleSessionSetV3Family(snapshot) {
|
|
3115
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3116
|
+
if (versions.globalRoleSessionSet !== UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION) {
|
|
3117
|
+
throw new Error(`Record globalRoleSessionSet migration requires manifest version ${UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION}.`);
|
|
3118
|
+
}
|
|
3119
|
+
if (snapshot.state === null)
|
|
3120
|
+
return;
|
|
3121
|
+
const sets = asObject(snapshot.state.globalRoleSessionSets, "Global Role session sets");
|
|
3122
|
+
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3123
|
+
const set = asObject(rawSet, `Global Role session set ${roleName}`);
|
|
3124
|
+
if (set.schemaVersion !== UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION) {
|
|
3125
|
+
throw new Error(`Global Role session set ${roleName} must use schemaVersion ${UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION}.`);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
function migrateGlobalRoleSessionSetV3ToV4(snapshot) {
|
|
3130
|
+
requireGlobalRoleSessionSetV3Family(snapshot);
|
|
3131
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3132
|
+
const schemaManifest = {
|
|
3133
|
+
...snapshot.schemaManifest,
|
|
3134
|
+
recordVersions: {
|
|
3135
|
+
...versions,
|
|
3136
|
+
globalRoleSessionSet: UNIFIED_GLOBAL_SESSION_SET_TO_VERSION
|
|
3137
|
+
}
|
|
3138
|
+
};
|
|
3139
|
+
if (snapshot.state === null)
|
|
3140
|
+
return { schemaManifest, state: null };
|
|
3141
|
+
const sets = asObject(snapshot.state.globalRoleSessionSets, "Global Role session sets");
|
|
3142
|
+
const nextSets = {};
|
|
3143
|
+
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3144
|
+
const set = asObject(rawSet, `Global Role session set ${roleName}`);
|
|
3145
|
+
const history = set.history === undefined
|
|
3146
|
+
? undefined
|
|
3147
|
+
: migrateRoleAgentSessionMap(set.history, `Global Role session history ${roleName}`);
|
|
3148
|
+
nextSets[roleName] = {
|
|
3149
|
+
...set,
|
|
3150
|
+
schemaVersion: UNIFIED_GLOBAL_SESSION_SET_TO_VERSION,
|
|
3151
|
+
sessions: migrateRoleAgentSessionMap(set.sessions, `Global Role sessions ${roleName}`),
|
|
3152
|
+
...(history === undefined ? {} : { history })
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
return {
|
|
3156
|
+
schemaManifest,
|
|
3157
|
+
state: { ...snapshot.state, globalRoleSessionSets: nextSets }
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
function migrateRoleAgentSessionMap(value, label) {
|
|
3161
|
+
const sessions = asObject(value, label);
|
|
3162
|
+
return Object.fromEntries(Object.entries(sessions).map(([key, session]) => [
|
|
3163
|
+
key,
|
|
3164
|
+
migrateRoleAgentSessionV3(session, `${label}/${key}`)
|
|
3165
|
+
]));
|
|
3166
|
+
}
|
|
3167
|
+
function migrateRoleAgentSessionArray(value, label) {
|
|
3168
|
+
if (!Array.isArray(value))
|
|
3169
|
+
throw new Error(`${label} must be an array.`);
|
|
3170
|
+
return value.map((session, index) => migrateRoleAgentSessionV3(session, `${label}/${String(index)}`));
|
|
3171
|
+
}
|
|
3172
|
+
function migrateRoleAgentSessionV3(value, label) {
|
|
3173
|
+
const session = asObject(value, label);
|
|
3174
|
+
if (session.schemaVersion !== 3) {
|
|
3175
|
+
throw new Error(`${label} must use schemaVersion 3.`);
|
|
3176
|
+
}
|
|
3177
|
+
const status = session.status;
|
|
3178
|
+
if (status !== "reserved"
|
|
3179
|
+
&& status !== "ready"
|
|
3180
|
+
&& status !== "running"
|
|
3181
|
+
&& status !== "stopped"
|
|
3182
|
+
&& status !== "broken") {
|
|
3183
|
+
throw new Error(`${label} has an invalid Session status.`);
|
|
3184
|
+
}
|
|
3185
|
+
const { status: _status, ...retained } = session;
|
|
3186
|
+
void _status;
|
|
3187
|
+
return {
|
|
3188
|
+
...retained,
|
|
3189
|
+
schemaVersion: 4,
|
|
3190
|
+
status: status === "stopped" || status === "broken" ? "ended" : "active",
|
|
3191
|
+
...(status === "stopped"
|
|
3192
|
+
? { endReason: "stopped" }
|
|
3193
|
+
: status === "broken" ? { endReason: "failed" } : {})
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
2864
3196
|
function requireTaskRoleSessionSetV5Family(snapshot) {
|
|
2865
3197
|
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2866
3198
|
if (versions.taskRoleSessionSet !== STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION) {
|
|
@@ -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 = 18;
|
|
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;
|
|
@@ -934,6 +934,57 @@ CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
|
934
934
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_context_snapshots_scope_sequence
|
|
935
935
|
ON context_snapshots(task_id, scope, COALESCE(scope_ref, ''), sequence);
|
|
936
936
|
`;
|
|
937
|
+
/**
|
|
938
|
+
* Migration 18: align the current-Session hot projection with Session v4.
|
|
939
|
+
*
|
|
940
|
+
* Session existence now has one lifecycle: active or ended. Ended Sessions do
|
|
941
|
+
* not belong in this bounded projection, so row presence itself means active;
|
|
942
|
+
* the projection carries no independently writable lifecycle status. Host
|
|
943
|
+
* cleanup is derived solely from whether the Session still names a Host
|
|
944
|
+
* activation. Valid earlier live rows are projected into that contract;
|
|
945
|
+
* terminal broken rows disappear with the rest of ended Session history.
|
|
946
|
+
*/
|
|
947
|
+
const MIGRATION_18_SQL = `
|
|
948
|
+
DROP INDEX idx_runtime_session_cleanup_required;
|
|
949
|
+
ALTER TABLE runtime_session_candidates RENAME TO runtime_session_candidates_v11;
|
|
950
|
+
|
|
951
|
+
CREATE TABLE runtime_session_candidates (
|
|
952
|
+
scope TEXT NOT NULL CHECK (scope IN ('task','global')),
|
|
953
|
+
task_id TEXT NOT NULL,
|
|
954
|
+
role_name TEXT NOT NULL,
|
|
955
|
+
agent_id TEXT NOT NULL,
|
|
956
|
+
adapter_id TEXT NOT NULL,
|
|
957
|
+
native_session_id TEXT NOT NULL,
|
|
958
|
+
launch_id TEXT,
|
|
959
|
+
session_updated_at TEXT NOT NULL,
|
|
960
|
+
cleanup_required INTEGER NOT NULL CHECK (cleanup_required IN (0,1)),
|
|
961
|
+
PRIMARY KEY (scope, task_id, role_name),
|
|
962
|
+
CHECK (
|
|
963
|
+
(scope = 'task' AND length(task_id) > 0)
|
|
964
|
+
OR (scope = 'global' AND task_id = '')
|
|
965
|
+
),
|
|
966
|
+
CHECK (
|
|
967
|
+
cleanup_required = CASE WHEN launch_id IS NOT NULL THEN 1 ELSE 0 END
|
|
968
|
+
)
|
|
969
|
+
);
|
|
970
|
+
|
|
971
|
+
INSERT INTO runtime_session_candidates (
|
|
972
|
+
scope, task_id, role_name, agent_id, adapter_id, native_session_id,
|
|
973
|
+
launch_id, session_updated_at, cleanup_required
|
|
974
|
+
)
|
|
975
|
+
SELECT
|
|
976
|
+
scope, task_id, role_name, agent_id, adapter_id, native_session_id,
|
|
977
|
+
launch_id, session_updated_at,
|
|
978
|
+
CASE WHEN launch_id IS NOT NULL THEN 1 ELSE 0 END
|
|
979
|
+
FROM runtime_session_candidates_v11
|
|
980
|
+
WHERE status IN ('reserved','ready','running');
|
|
981
|
+
|
|
982
|
+
DROP TABLE runtime_session_candidates_v11;
|
|
983
|
+
|
|
984
|
+
CREATE INDEX idx_runtime_session_cleanup_required
|
|
985
|
+
ON runtime_session_candidates(scope, task_id, role_name)
|
|
986
|
+
WHERE cleanup_required = 1;
|
|
987
|
+
`;
|
|
937
988
|
/**
|
|
938
989
|
* The single forward migration history for current SQLite Homes. New durable
|
|
939
990
|
* layout, aggregate, and record changes belong here and must be expressible as
|
|
@@ -957,7 +1008,8 @@ const MIGRATIONS = [
|
|
|
957
1008
|
{ version: 14, axis: "record", recordKind: "workMailbox", sql: MIGRATION_14_SQL },
|
|
958
1009
|
{ version: 15, axis: "record", recordKind: "publicationReference", sql: MIGRATION_15_SQL },
|
|
959
1010
|
{ version: 16, axis: "record", recordKind: "taskWake", sql: MIGRATION_16_SQL },
|
|
960
|
-
{ version: 17, axis: "record", recordKind: "contextSnapshot", sql: MIGRATION_17_SQL }
|
|
1011
|
+
{ version: 17, axis: "record", recordKind: "contextSnapshot", sql: MIGRATION_17_SQL },
|
|
1012
|
+
{ version: 18, axis: "layout", sql: MIGRATION_18_SQL }
|
|
961
1013
|
];
|
|
962
1014
|
/** Current hot-path indexes whose absence would invalidate a current Home. */
|
|
963
1015
|
const REQUIRED_SCHEMA_INDEXES = [
|
|
@@ -1150,7 +1150,7 @@ export class SqliteTaskStore {
|
|
|
1150
1150
|
? ""
|
|
1151
1151
|
: ` WHERE ${predicates.join(" AND ")}`;
|
|
1152
1152
|
const rows = this.#db.prepare(`SELECT scope, task_id, role_name, agent_id, adapter_id,
|
|
1153
|
-
native_session_id, launch_id,
|
|
1153
|
+
native_session_id, launch_id, session_updated_at,
|
|
1154
1154
|
cleanup_required
|
|
1155
1155
|
FROM runtime_session_candidates${where}`).all(...parameters);
|
|
1156
1156
|
const candidates = rows.map((row) => ({
|
|
@@ -1161,7 +1161,6 @@ export class SqliteTaskStore {
|
|
|
1161
1161
|
adapterId: row.adapter_id,
|
|
1162
1162
|
nativeSessionId: row.native_session_id,
|
|
1163
1163
|
...(row.launch_id === null ? {} : { launchId: row.launch_id }),
|
|
1164
|
-
status: row.status,
|
|
1165
1164
|
sessionUpdatedAt: row.session_updated_at,
|
|
1166
1165
|
cleanupRequired: row.cleanup_required === 1
|
|
1167
1166
|
})).sort(compareRuntimeSessionCandidates);
|
|
@@ -1217,14 +1216,14 @@ export class SqliteTaskStore {
|
|
|
1217
1216
|
json_extract(active_session, '$.adapterId') AS adapter_id,
|
|
1218
1217
|
json_extract(active_session, '$.nativeSessionId') AS native_session_id,
|
|
1219
1218
|
json_extract(active_session, '$.launchId') AS launch_id,
|
|
1220
|
-
|
|
1219
|
+
CASE
|
|
1220
|
+
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1221
|
+
THEN 1 ELSE 0
|
|
1222
|
+
END AS is_active,
|
|
1221
1223
|
json_extract(active_session, '$.updatedAt') AS session_updated_at,
|
|
1222
1224
|
CASE
|
|
1223
|
-
WHEN json_extract(active_session, '$.status')
|
|
1224
|
-
AND (
|
|
1225
|
-
json_extract(active_session, '$.status') = 'running'
|
|
1226
|
-
OR json_type(active_session, '$.launchId') = 'text'
|
|
1227
|
-
)
|
|
1225
|
+
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1226
|
+
AND json_type(active_session, '$.launchId') = 'text'
|
|
1228
1227
|
THEN 1 ELSE 0
|
|
1229
1228
|
END AS cleanup_required
|
|
1230
1229
|
FROM active`).get(...source.parameters);
|
|
@@ -1245,7 +1244,7 @@ export class SqliteTaskStore {
|
|
|
1245
1244
|
&& row.adapter_id === candidate.adapterId
|
|
1246
1245
|
&& row.native_session_id === candidate.nativeSessionId
|
|
1247
1246
|
&& (row.launch_id ?? undefined) === candidate.launchId
|
|
1248
|
-
&& row.
|
|
1247
|
+
&& row.is_active === 1
|
|
1249
1248
|
&& row.session_updated_at === candidate.sessionUpdatedAt
|
|
1250
1249
|
&& row.cleanup_required === (candidate.cleanupRequired ? 1 : 0);
|
|
1251
1250
|
}
|
|
@@ -1277,16 +1276,15 @@ export class SqliteTaskStore {
|
|
|
1277
1276
|
const taskId = candidate.owner.scope === "task" ? candidate.owner.taskId : "";
|
|
1278
1277
|
this.#db.prepare(`INSERT INTO runtime_session_candidates (
|
|
1279
1278
|
scope, task_id, role_name, agent_id, adapter_id, native_session_id,
|
|
1280
|
-
launch_id,
|
|
1281
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
1279
|
+
launch_id, session_updated_at, cleanup_required
|
|
1280
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1282
1281
|
ON CONFLICT(scope, task_id, role_name) DO UPDATE SET
|
|
1283
1282
|
agent_id = excluded.agent_id,
|
|
1284
1283
|
adapter_id = excluded.adapter_id,
|
|
1285
1284
|
native_session_id = excluded.native_session_id,
|
|
1286
1285
|
launch_id = excluded.launch_id,
|
|
1287
|
-
status = excluded.status,
|
|
1288
1286
|
session_updated_at = excluded.session_updated_at,
|
|
1289
|
-
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.launchId ?? null, candidate.
|
|
1287
|
+
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.launchId ?? null, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
|
|
1290
1288
|
}
|
|
1291
1289
|
#deleteRuntimeSessionCandidate(owner) {
|
|
1292
1290
|
this.#db.prepare(`DELETE FROM runtime_session_candidates
|
|
@@ -1585,37 +1583,6 @@ export class SqliteTaskStore {
|
|
|
1585
1583
|
payload = excluded.payload, updated_at = excluded.updated_at`).run(run.taskId, run.id, run.roleName, run.status, this.#json(run), this.#now());
|
|
1586
1584
|
});
|
|
1587
1585
|
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Issue 04: SQLite-native pending retry query. A single indexed scan
|
|
1590
|
-
* replaces the adapter's per-Task in-memory sweep, so Controller deadline
|
|
1591
|
-
* arming no longer materializes every Task and Run in JavaScript.
|
|
1592
|
-
*/
|
|
1593
|
-
listPendingProviderRetries(taskIds) {
|
|
1594
|
-
const selectedTaskIds = taskIds === undefined
|
|
1595
|
-
? undefined
|
|
1596
|
-
: [...new Set(taskIds)].sort(numericCompare);
|
|
1597
|
-
if (selectedTaskIds?.length === 0)
|
|
1598
|
-
return [];
|
|
1599
|
-
const taskPredicate = selectedTaskIds === undefined
|
|
1600
|
-
? ""
|
|
1601
|
-
: ` AND tc.task_id IN (${selectedTaskIds.map(() => "?").join(", ")})`;
|
|
1602
|
-
const rows = this.#db.prepare(`SELECT DISTINCT ar.task_id AS taskId, ar.run_id AS runId, ar.role_name AS roleName,
|
|
1603
|
-
json_extract(ar.payload, '$.providerRetry.state') AS state,
|
|
1604
|
-
CASE json_extract(ar.payload, '$.providerRetry.state')
|
|
1605
|
-
WHEN 'scheduled' THEN json_extract(ar.payload, '$.providerRetry.nextAttemptAt')
|
|
1606
|
-
ELSE json_extract(ar.payload, '$.providerRetry.episodeDeadlineAt')
|
|
1607
|
-
END AS dueAt
|
|
1608
|
-
FROM tasks_catalog tc INDEXED BY idx_tasks_active
|
|
1609
|
-
JOIN active_runs ap ON ap.task_id = tc.task_id
|
|
1610
|
-
JOIN agent_runs ar ON ar.task_id = ap.task_id AND ar.run_id = ap.run_id
|
|
1611
|
-
WHERE tc.is_active = 1
|
|
1612
|
-
AND ar.status = 'active'
|
|
1613
|
-
AND json_extract(ar.payload, '$.providerRetry.state') IN
|
|
1614
|
-
('scheduled', 'dispatching', 'awaiting-progress')${taskPredicate}`).all(...(selectedTaskIds ?? []));
|
|
1615
|
-
return rows.sort((left, right) => (numericCompare(left.taskId, right.taskId)
|
|
1616
|
-
|| numericCompare(left.roleName, right.roleName)
|
|
1617
|
-
|| numericCompare(left.runId, right.runId)));
|
|
1618
|
-
}
|
|
1619
1586
|
// -- review rounds ----------------------------------------------------------
|
|
1620
1587
|
nextReviewRoundId(taskId) { return this.#nextTaskRecordId(taskId, "reviewRound"); }
|
|
1621
1588
|
getReviewRound(taskId, reviewRoundId) {
|