@zq-silk/yui 0.13.7 → 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.
Files changed (78) hide show
  1. package/ARCHITECTURE.md +40 -4
  2. package/README.md +23 -4
  3. package/dist/cli/commandCatalog.js +21 -2
  4. package/dist/cli/updatePorts.js +4 -4
  5. package/dist/cli.js +43 -14
  6. package/dist/commands/agentCommands.js +1 -1
  7. package/dist/commands/configCommands.js +1 -86
  8. package/dist/commands/executionAuditCommands.js +17 -16
  9. package/dist/commands/globalRoleCommands.js +4 -4
  10. package/dist/commands/sessionCommands.js +2 -6
  11. package/dist/commands/taskActor.js +1 -2
  12. package/dist/commands/taskCommands.js +93 -21
  13. package/dist/commands/taskContextCommand.js +1 -1
  14. package/dist/commands/taskExecutionCommands.js +0 -5
  15. package/dist/commands/taskOverviewCommand.js +5 -1
  16. package/dist/commands/taskRoleRuntimeStatus.js +3 -16
  17. package/dist/config/configCatalog.js +1 -6
  18. package/dist/config/yuiConfig.js +0 -79
  19. package/dist/context/sessionBootstrapManifest.js +26 -23
  20. package/dist/controller/clientRuntime.js +40 -3
  21. package/dist/controller/controller.js +29 -54
  22. package/dist/controller/fileSchedulerStoreAdapter.js +372 -1117
  23. package/dist/controller/runtime.js +12 -5
  24. package/dist/controller/runtimeHookRunFence.js +3 -9
  25. package/dist/controller/runtimeLaunchCoordinator.js +44 -67
  26. package/dist/controller/structuredProviderObservation.js +18 -5
  27. package/dist/coordination/workMailbox.js +4 -4
  28. package/dist/execution/executionHealth.js +1 -1
  29. package/dist/executor/agentExecutor.js +103 -93
  30. package/dist/executor/executorRegistry.js +8 -20
  31. package/dist/executor/fileRoleLaunchPlanner.js +26 -93
  32. package/dist/executor/turnCompletion.js +5 -5
  33. package/dist/lifecycle/exactRunTerminalization.js +2 -4
  34. package/dist/observability/executionAudit.js +40 -94
  35. package/dist/operator/operatorSessionHistory.js +7 -5
  36. package/dist/output/rolePresentation.js +0 -1
  37. package/dist/repository/taskWorkspacePreparer.js +0 -1
  38. package/dist/role/role.js +13 -21
  39. package/dist/run/agentRun.js +4 -54
  40. package/dist/runtime/agentDriver.js +2 -0
  41. package/dist/runtime/agentError.js +114 -0
  42. package/dist/runtime/agentHost.js +55 -82
  43. package/dist/runtime/builtinAgentDrivers.js +21 -9
  44. package/dist/runtime/builtinAgentErrorMappers.js +150 -0
  45. package/dist/runtime/exactControlPlane.js +6 -12
  46. package/dist/runtime/index.js +1 -2
  47. package/dist/runtime/launchBroker.js +5 -19
  48. package/dist/runtime/lifecycleReservation.js +20 -4
  49. package/dist/runtime/providerRuntimeIdentity.js +11 -19
  50. package/dist/runtime/runtimeBinding.js +0 -27
  51. package/dist/runtime/runtimeObservation.js +7 -16
  52. package/dist/runtime/runtimeSessionCandidate.js +3 -10
  53. package/dist/runtime/sessionLaunchRequest.js +1 -2
  54. package/dist/runtime/sessionReconciliation.js +2 -2
  55. package/dist/runtime/structuredProviderHost.js +44 -79
  56. package/dist/runtime/taskRuntimeIsolation.js +0 -7
  57. package/dist/runtime/tmuxAdapters.js +6 -49
  58. package/dist/scheduler/activeRoleRunDelivery.js +220 -178
  59. package/dist/scheduler/activeTaskProgress.js +1 -4
  60. package/dist/scheduler/leaderWakeupProcessor.js +123 -88
  61. package/dist/scheduler/roleRunLiveness.js +4 -1
  62. package/dist/scheduler/roleRunStall.js +10 -14
  63. package/dist/scheduler/wakeReason.js +4 -0
  64. package/dist/storage/migration/productionRegistry.js +475 -0
  65. package/dist/storage/sqliteSchema.js +54 -2
  66. package/dist/storage/sqliteStore.js +11 -44
  67. package/dist/storage/taskStore.js +8 -36
  68. package/dist/web/webSnapshot.js +3 -0
  69. package/i18n/README.zh-CN.md +11 -3
  70. package/package.json +1 -1
  71. package/skills/yui-leader/SKILL.md +30 -8
  72. package/skills/yui-operator/SKILL.md +14 -6
  73. package/skills/yui-runtime/SKILL.md +11 -8
  74. package/dist/lifecycle/providerErrorClass.js +0 -152
  75. package/dist/run/providerRetry.js +0 -226
  76. package/dist/run/providerRetryConfig.js +0 -27
  77. package/dist/runtime/providerErrorCodes.js +0 -278
  78. 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,16 +101,28 @@ 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;
106
+ const TASK_ROLE_FROM_VERSION = 3;
107
+ const TASK_ROLE_TO_VERSION = 4;
102
108
  const TASK_ROLE_SESSION_SET_FROM_VERSION = 4;
103
109
  const TASK_ROLE_SESSION_SET_TO_VERSION = 5;
104
110
  const STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION = 5;
105
111
  const STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION = 6;
112
+ const AGENT_FIRST_SESSION_SET_FROM_VERSION = 6;
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;
106
118
  const PUBLICATION_REFERENCE_FROM_VERSION = 0;
107
119
  const PUBLICATION_REFERENCE_TO_VERSION = 1;
108
120
  const CONFIG_FROM_VERSION = 1;
109
121
  const CONFIG_TO_VERSION = 2;
110
122
  const CONFIG_REVIEW_CONTROLS_FROM_VERSION = 2;
111
123
  const CONFIG_REVIEW_CONTROLS_TO_VERSION = 3;
124
+ const CONFIG_AGENT_RECOVERY_FROM_VERSION = 3;
125
+ const CONFIG_AGENT_RECOVERY_TO_VERSION = 4;
112
126
  /**
113
127
  * Build the authoritative production graph. Transition intent and executable
114
128
  * transforms are registered together here; compatible loading and offline
@@ -159,6 +173,7 @@ export function createProductionStorageRegistry() {
159
173
  .registerOfflineMigration(projectOwnershipStep())
160
174
  .registerOfflineMigration(configV2Step())
161
175
  .registerOfflineMigration(configReviewControlsRemovalStep())
176
+ .registerOfflineMigration(configAgentRecoveryStep())
162
177
  .registerCompatible(projectKnowledgeProposalsStep())
163
178
  .registerCompatible(projectLifecycleStep())
164
179
  .registerOfflineMigration(taskWorkspaceIdentityStep())
@@ -179,6 +194,7 @@ export function createProductionStorageRegistry() {
179
194
  .registerOfflineMigration(recordFamilyStep("agentRun", AGENT_RUN_OPTIONAL_FIELDS_FROM_VERSION, AGENT_RUN_OPTIONAL_FIELDS_TO_VERSION, "agentRuns"))
180
195
  .registerOfflineMigration(agentRunContextProtocolStep())
181
196
  .registerOfflineMigration(agentRunRetryEpisodeStep())
197
+ .registerOfflineMigration(agentRunErrorFactsStep())
182
198
  .registerOfflineMigration(messageWakePolicyStep())
183
199
  .registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_FROM_VERSION, REVIEW_ROUND_TO_VERSION, "reviewRounds"))
184
200
  .registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION, REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION, "reviewRounds"))
@@ -203,12 +219,71 @@ export function createProductionStorageRegistry() {
203
219
  .registerOfflineMigration(capabilityGrantIntroductionStep())
204
220
  .registerOfflineMigration(releaseWorkflowIntroductionStep())
205
221
  .registerOfflineMigration(workMailboxV2Step())
222
+ .registerOfflineMigration(hostOwnedWorkMailboxStep())
223
+ .registerOfflineMigration(agentRunOwnedTaskRoleStep())
206
224
  .registerOfflineMigration(taskRoleSessionSetV5Step())
207
225
  .registerOfflineMigration(structuredProviderSessionSetV6Step())
226
+ .registerOfflineMigration(agentFirstSessionSetV7Step())
227
+ .registerOfflineMigration(unifiedTaskSessionSetStep())
228
+ .registerOfflineMigration(unifiedGlobalSessionSetStep())
208
229
  .registerOfflineMigration(publicationReferenceIntroductionStep());
209
230
  assertRegistryCoversBaselineToCurrent(registry);
210
231
  return registry;
211
232
  }
233
+ /** Removes the independently writable Role runtime projection; AgentRun owns workflow activity. */
234
+ function agentRunOwnedTaskRoleStep() {
235
+ return {
236
+ axis: "record",
237
+ recordKind: "taskRole",
238
+ fromVersion: TASK_ROLE_FROM_VERSION,
239
+ toVersion: TASK_ROLE_TO_VERSION,
240
+ preconditions: requireTaskRoleV3Family,
241
+ transform: migrateTaskRoleV3ToV4,
242
+ declaredEffects: []
243
+ };
244
+ }
245
+ function requireTaskRoleV3Family(snapshot) {
246
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
247
+ if (versions.taskRole !== TASK_ROLE_FROM_VERSION) {
248
+ throw new Error(`Record taskRole migration requires manifest version ${TASK_ROLE_FROM_VERSION}.`);
249
+ }
250
+ if (snapshot.state === null)
251
+ return;
252
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
253
+ for (const [taskId, rawAggregate] of Object.entries(tasks)) {
254
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
255
+ const roles = asObject(aggregate.roles, `Task Roles ${taskId}`);
256
+ for (const [roleName, rawRole] of Object.entries(roles)) {
257
+ const role = asObject(rawRole, `Task Role ${taskId}/${roleName}`);
258
+ if (role.schemaVersion !== TASK_ROLE_FROM_VERSION) {
259
+ throw new Error(`Task Role ${taskId}/${roleName} must use schemaVersion ${TASK_ROLE_FROM_VERSION}.`);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ function migrateTaskRoleV3ToV4(snapshot) {
265
+ requireTaskRoleV3Family(snapshot);
266
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
267
+ const schemaManifest = {
268
+ ...snapshot.schemaManifest,
269
+ recordVersions: { ...versions, taskRole: TASK_ROLE_TO_VERSION }
270
+ };
271
+ if (snapshot.state === null)
272
+ return { schemaManifest, state: null };
273
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
274
+ const nextTasks = Object.fromEntries(Object.entries(tasks).map(([taskId, rawAggregate]) => {
275
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
276
+ const roles = asObject(aggregate.roles, `Task Roles ${taskId}`);
277
+ const nextRoles = Object.fromEntries(Object.entries(roles).map(([roleName, rawRole]) => {
278
+ const role = asObject(rawRole, `Task Role ${taskId}/${roleName}`);
279
+ const { status: _status, ...retained } = role;
280
+ void _status;
281
+ return [roleName, { ...retained, schemaVersion: TASK_ROLE_TO_VERSION }];
282
+ }));
283
+ return [taskId, { ...aggregate, roles: nextRoles }];
284
+ }));
285
+ return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
286
+ }
212
287
  function configV2Step() {
213
288
  return {
214
289
  axis: "record",
@@ -319,6 +394,53 @@ function migrateConfigV2ToV3(snapshot) {
319
394
  }
320
395
  };
321
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
+ }
322
444
  function workItemExecutionGroupHistoryStep() {
323
445
  return {
324
446
  axis: "record",
@@ -2185,6 +2307,46 @@ function migrateAgentRunsToRetryEpisodes(snapshot) {
2185
2307
  }
2186
2308
  return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
2187
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
+ }
2188
2350
  function requiredMigrationPositiveInteger(value, label) {
2189
2351
  if (!Number.isSafeInteger(value) || value < 1) {
2190
2352
  throw new Error(`${label} must be a positive integer.`);
@@ -2623,6 +2785,77 @@ function migrateWorkMailboxV1ToV2(snapshot) {
2623
2785
  state: { ...snapshot.state, mailboxes: nextMailboxes }
2624
2786
  };
2625
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
+ }
2626
2859
  /**
2627
2860
  * v1 used `processing` for both internal/initial dispatch ownership and later
2628
2861
  * model input. A crashed later input may already have reached the provider;
@@ -2718,6 +2951,248 @@ function structuredProviderSessionSetV6Step() {
2718
2951
  declaredEffects: []
2719
2952
  };
2720
2953
  }
2954
+ /**
2955
+ * A Provider binding describes one reusable Conversation. Run correlation now
2956
+ * belongs to the concrete Provider Turn instead of making the Conversation a
2957
+ * second writable projection of the current AgentRun.
2958
+ */
2959
+ function agentFirstSessionSetV7Step() {
2960
+ return {
2961
+ axis: "record",
2962
+ recordKind: "taskRoleSessionSet",
2963
+ fromVersion: AGENT_FIRST_SESSION_SET_FROM_VERSION,
2964
+ toVersion: AGENT_FIRST_SESSION_SET_TO_VERSION,
2965
+ preconditions: requireTaskRoleSessionSetV6Family,
2966
+ transform: migrateTaskRoleSessionSetV6ToV7,
2967
+ declaredEffects: []
2968
+ };
2969
+ }
2970
+ function requireTaskRoleSessionSetV6Family(snapshot) {
2971
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
2972
+ if (versions.taskRoleSessionSet !== AGENT_FIRST_SESSION_SET_FROM_VERSION) {
2973
+ throw new Error(`Record taskRoleSessionSet migration requires manifest version ${AGENT_FIRST_SESSION_SET_FROM_VERSION}.`);
2974
+ }
2975
+ if (snapshot.state === null)
2976
+ return;
2977
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
2978
+ for (const [taskId, rawAggregate] of Object.entries(tasks)) {
2979
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
2980
+ const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
2981
+ for (const [roleName, rawSet] of Object.entries(sets)) {
2982
+ const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
2983
+ if (set.schemaVersion !== AGENT_FIRST_SESSION_SET_FROM_VERSION) {
2984
+ throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${AGENT_FIRST_SESSION_SET_FROM_VERSION}.`);
2985
+ }
2986
+ if (set.providerBinding !== null) {
2987
+ const binding = asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`);
2988
+ if (binding.schemaVersion !== 2 || typeof binding.runId !== "string") {
2989
+ throw new Error(`Provider Binding ${taskId}/${roleName} must use schemaVersion 2.`);
2990
+ }
2991
+ }
2992
+ }
2993
+ }
2994
+ }
2995
+ function migrateTaskRoleSessionSetV6ToV7(snapshot) {
2996
+ requireTaskRoleSessionSetV6Family(snapshot);
2997
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
2998
+ const schemaManifest = {
2999
+ ...snapshot.schemaManifest,
3000
+ recordVersions: {
3001
+ ...versions,
3002
+ taskRoleSessionSet: AGENT_FIRST_SESSION_SET_TO_VERSION
3003
+ }
3004
+ };
3005
+ if (snapshot.state === null)
3006
+ return { schemaManifest, state: null };
3007
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
3008
+ const nextTasks = {};
3009
+ for (const [taskId, rawAggregate] of Object.entries(tasks)) {
3010
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
3011
+ const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
3012
+ const nextSets = {};
3013
+ for (const [roleName, rawSet] of Object.entries(rawSets)) {
3014
+ const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
3015
+ let providerBinding = null;
3016
+ if (set.providerBinding !== null) {
3017
+ const binding = asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`);
3018
+ const { runId, ...conversation } = binding;
3019
+ providerBinding = {
3020
+ ...conversation,
3021
+ schemaVersion: 3,
3022
+ turn: binding.turn === null
3023
+ ? null
3024
+ : { ...asObject(binding.turn, `Provider Turn ${taskId}/${roleName}`), runId }
3025
+ };
3026
+ }
3027
+ nextSets[roleName] = {
3028
+ ...set,
3029
+ schemaVersion: AGENT_FIRST_SESSION_SET_TO_VERSION,
3030
+ providerBinding
3031
+ };
3032
+ }
3033
+ nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
3034
+ }
3035
+ return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
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
+ }
2721
3196
  function requireTaskRoleSessionSetV5Family(snapshot) {
2722
3197
  const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
2723
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 = 17;
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 = [