@zq-silk/yui 0.11.3 → 0.12.1

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 (108) hide show
  1. package/ARCHITECTURE.md +23 -4
  2. package/README.md +44 -22
  3. package/dist/cli/commandCatalog.js +13 -10
  4. package/dist/cli/interactionPolicy.js +5 -6
  5. package/dist/cli/updateCommand.js +3 -1
  6. package/dist/cli/updateOrchestrator.js +173 -28
  7. package/dist/cli/updatePorts.js +137 -8
  8. package/dist/cli/upgradeCommand.js +19 -9
  9. package/dist/cli.js +156 -57
  10. package/dist/commands/configCommands.js +13 -46
  11. package/dist/commands/executionAuditCommands.js +3 -2
  12. package/dist/commands/jobCommands.js +4 -10
  13. package/dist/commands/taskCommands.js +234 -200
  14. package/dist/commands/taskCompletionGate.js +29 -54
  15. package/dist/commands/taskContextCommand.js +61 -13
  16. package/dist/commands/taskInputCommands.js +8 -5
  17. package/dist/commands/taskNextActionCommand.js +56 -6
  18. package/dist/commands/taskOverviewCommand.js +17 -31
  19. package/dist/commands/taskRoleRuntimeStatus.js +55 -7
  20. package/dist/commands/taskWorkspaceCommands.js +5 -2
  21. package/dist/config/configCatalog.js +3 -3
  22. package/dist/config/yuiConfig.js +3 -7
  23. package/dist/context/wakeNotification.js +20 -5
  24. package/dist/controller/agentRuntimeObserver.js +247 -51
  25. package/dist/controller/clientRuntime.js +39 -3
  26. package/dist/controller/controller.js +31 -27
  27. package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
  28. package/dist/controller/runtime.js +58 -3
  29. package/dist/controller/runtimeEventInbox.js +17 -7
  30. package/dist/controller/runtimeEventProcessor.js +12 -19
  31. package/dist/controller/runtimeHookRunFence.js +19 -4
  32. package/dist/controller/runtimeObservationHook.js +45 -0
  33. package/dist/controller/structuredProviderObservation.js +20 -3
  34. package/dist/core/controllerClient.js +20 -2
  35. package/dist/core/controllerServer.js +1 -0
  36. package/dist/execution/resourceBroker.js +5 -4
  37. package/dist/executor/agentExecutor.js +52 -50
  38. package/dist/executor/effectiveLaunch.js +8 -48
  39. package/dist/executor/fileRoleLaunchPlanner.js +129 -45
  40. package/dist/executor/workspacePreflightClassification.js +23 -2
  41. package/dist/interaction/operatorPresentation.js +33 -89
  42. package/dist/lifecycle/exactRunTerminalization.js +69 -5
  43. package/dist/observability/executionAudit.js +5 -0
  44. package/dist/observability/orchestrationMetrics.js +8 -3
  45. package/dist/profile/agentProfile.js +1 -1
  46. package/dist/release/cliHomeReleaseFence.js +123 -0
  47. package/dist/release/runtimeRelease.js +20 -0
  48. package/dist/repository/taskWorkspacePreparer.js +176 -60
  49. package/dist/resources/sqliteResourceRegistry.js +1 -1
  50. package/dist/review/deltaRecheck.js +12 -51
  51. package/dist/review/reviewAcceptance.js +26 -0
  52. package/dist/review/reviewConfig.js +0 -31
  53. package/dist/review/reviewDecision.js +113 -0
  54. package/dist/review/reviewOutcomeClassifier.js +1 -1
  55. package/dist/review/reviewRound.js +1 -1
  56. package/dist/review/reviewerAvailability.js +69 -0
  57. package/dist/run/recoveryProjection.js +45 -6
  58. package/dist/runtime/agentDriverObservation.js +24 -10
  59. package/dist/runtime/agentHost.js +159 -85
  60. package/dist/runtime/builtinAgentDrivers.js +4 -3
  61. package/dist/runtime/builtinTranscriptObserver.js +301 -64
  62. package/dist/runtime/builtinTranscriptUsage.js +9 -7
  63. package/dist/runtime/conversationSwitch.js +277 -0
  64. package/dist/runtime/index.js +2 -1
  65. package/dist/runtime/launchBroker.js +12 -0
  66. package/dist/runtime/processExitOutbox.js +88 -0
  67. package/dist/runtime/providerRuntimeIdentity.js +29 -1
  68. package/dist/runtime/runtimeHealthPolicy.js +5 -5
  69. package/dist/runtime/runtimeObservation.js +28 -0
  70. package/dist/runtime/runtimeProjection.js +22 -32
  71. package/dist/runtime/sessionTokenMetrics.js +181 -0
  72. package/dist/runtime/structuredProviderHost.js +7 -1
  73. package/dist/runtime/tmuxAdapters.js +4 -1
  74. package/dist/scheduler/activeRoleRunDelivery.js +34 -199
  75. package/dist/scheduler/activeTaskProgress.js +7 -6
  76. package/dist/scheduler/leaderWakeupProcessor.js +42 -138
  77. package/dist/scheduler/operatorEvent.js +34 -0
  78. package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
  79. package/dist/scheduler/roleRunStall.js +53 -31
  80. package/dist/scheduler/taskExecutionProjection.js +97 -76
  81. package/dist/scheduler/taskObservabilityProjection.js +10 -11
  82. package/dist/storage/migration/productionRegistry.js +379 -0
  83. package/dist/storage/sqliteSchema.js +66 -23
  84. package/dist/storage/sqliteStore.js +46 -23
  85. package/dist/storage/storeRpc.js +0 -1
  86. package/dist/storage/taskStore.js +15 -30
  87. package/dist/storage/upgrade/homeClassification.js +52 -0
  88. package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
  89. package/dist/storage/upgrade/recordVersions.js +3 -2
  90. package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
  91. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
  92. package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
  93. package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
  94. package/dist/task/completionReadiness.js +10 -6
  95. package/dist/task/nextAction.js +82 -58
  96. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  97. package/dist/web/assets/client/components.js +13 -3
  98. package/dist/web/assets/client/i18n.js +6 -2
  99. package/dist/web/webSnapshot.js +7 -1
  100. package/i18n/README.zh-CN.md +2 -2
  101. package/package.json +1 -1
  102. package/skills/yui-leader/SKILL.md +60 -20
  103. package/skills/yui-operator/SKILL.md +13 -4
  104. package/skills/yui-reviewer/SKILL.md +35 -11
  105. package/dist/context/sessionContextBudget.js +0 -71
  106. package/dist/lifecycle/contextBudgetRollover.js +0 -81
  107. package/dist/lifecycle/taskRoleSessionReset.js +0 -126
  108. package/dist/scheduler/operatorNotification.js +0 -59
@@ -34,10 +34,13 @@ import { accessSync, constants, existsSync, mkdtempSync, readFileSync, realpathS
34
34
  import { tmpdir } from "node:os";
35
35
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
36
36
  import { fileURLToPath } from "node:url";
37
+ import Database from "better-sqlite3";
37
38
  import { runtimeError } from "../errors/cliError.js";
38
39
  import { isConcreteVersion } from "../domain/validation.js";
39
40
  import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
41
+ import { acquireHandoverLock } from "../release/runtimeRelease.js";
40
42
  import { inspectStorageSchema } from "../storage/storageSchema.js";
43
+ import { placeUpgradeFence } from "../storage/upgradeFence.js";
41
44
  import { correlateUpgradeReceipt } from "../storage/upgrade/upgradeOrchestrator.js";
42
45
  import { readSwitchProgress } from "../storage/upgrade/switchProgress.js";
43
46
  const PACKAGE_NAME = "@zq-silk/yui";
@@ -91,8 +94,29 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
91
94
  // deliberately not persisted as a retry or recovery protocol.
92
95
  let verifiedActivatedBinary;
93
96
  let verifiedActivatedVersion;
97
+ let storageFenceOwnerPid;
94
98
  const stopReplacementController = (home, pid) => (stopReplacementControllerForUpdate(home, pid, environment, run));
95
99
  return {
100
+ beginControllerHandover(home) {
101
+ return acquireHandoverLock(home).release;
102
+ },
103
+ beginStorageWriteFence(home) {
104
+ const release = placeUpgradeFence(home, {
105
+ reason: "update storage activation in progress",
106
+ createdAt: new Date().toISOString(),
107
+ ownerPid: process.pid
108
+ });
109
+ storageFenceOwnerPid = process.pid;
110
+ let released = false;
111
+ return () => {
112
+ if (released)
113
+ return;
114
+ release();
115
+ released = true;
116
+ if (storageFenceOwnerPid === process.pid)
117
+ storageFenceOwnerPid = undefined;
118
+ };
119
+ },
96
120
  stage(version) {
97
121
  // A caller that names a version (the release workflow, which freezes the
98
122
  // exact version in its plan) installs THAT version — never a moving
@@ -151,7 +175,10 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
151
175
  env: {
152
176
  ...environment,
153
177
  YUI_HOME: home,
154
- YUI_UPDATE_EXTERNALLY_QUIESCED: "1"
178
+ YUI_UPDATE_EXTERNALLY_QUIESCED: "1",
179
+ ...(storageFenceOwnerPid === undefined
180
+ ? {}
181
+ : { YUI_UPDATE_HANDOVER_OWNER_PID: String(storageFenceOwnerPid) })
155
182
  },
156
183
  shell: false
157
184
  });
@@ -219,6 +246,7 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
219
246
  // on-disk schema and report `switched: false` so the caller re-probes the
220
247
  // real state instead of giving a recovery instruction from a stale receipt.
221
248
  const schema = inspectStorageSchema(home);
249
+ const sqliteSchemaHead = inspectSqliteSchemaHead(home);
222
250
  // A crash mid-switch leaves a durable progress marker. A marker of ANY phase
223
251
  // — `backing-up`, `promoting`, or `interrupted` — is only actionable as an
224
252
  // interrupted switch when the FILESYSTEM still corroborates it: the backup
@@ -243,6 +271,7 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
243
271
  switched: false,
244
272
  interrupted: true,
245
273
  schemaCurrent: false,
274
+ ...(sqliteSchemaHead === undefined ? {} : { sqliteSchemaHead }),
246
275
  ...(progress.backupPath === undefined ? {} : { backupPath: progress.backupPath })
247
276
  };
248
277
  }
@@ -252,12 +281,17 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
252
281
  }
253
282
  const correlation = correlateUpgradeReceipt(home);
254
283
  if (!correlation.corresponds) {
255
- return { switched: false, schemaCurrent: schema.status === "current" };
284
+ return {
285
+ switched: false,
286
+ schemaCurrent: schema.status === "current",
287
+ ...(sqliteSchemaHead === undefined ? {} : { sqliteSchemaHead })
288
+ };
256
289
  }
257
290
  const receipt = correlation.receipt;
258
291
  return {
259
292
  switched: true,
260
293
  schemaCurrent: schema.status === "current",
294
+ ...(sqliteSchemaHead === undefined ? {} : { sqliteSchemaHead }),
261
295
  ...(receipt.backupPath === undefined ? {} : { backupPath: receipt.backupPath })
262
296
  };
263
297
  },
@@ -291,6 +325,39 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
291
325
  }
292
326
  };
293
327
  }
328
+ /**
329
+ * Read the generic durable SQLite ledger without asking the old parent binary
330
+ * to understand the staged release's migration registry. The staged preflight
331
+ * supplies the validated source and target heads used to interpret this value.
332
+ */
333
+ function inspectSqliteSchemaHead(home) {
334
+ const path = join(home, "yui.db");
335
+ if (!existsSync(path))
336
+ return undefined;
337
+ const db = new Database(path, { readonly: true, fileMustExist: true });
338
+ try {
339
+ db.pragma("query_only = ON");
340
+ const rows = db.prepare("SELECT version, checksum FROM schema_migrations ORDER BY version").all();
341
+ if (rows.length === 0)
342
+ throw new Error("SQLite migration ledger is empty.");
343
+ for (let index = 0; index < rows.length; index += 1) {
344
+ const row = rows[index];
345
+ if (row.version !== index + 1
346
+ || typeof row.checksum !== "string"
347
+ || row.checksum.length === 0) {
348
+ throw new Error("SQLite migration ledger is not a contiguous checksummed prefix.");
349
+ }
350
+ }
351
+ const head = rows.at(-1);
352
+ return {
353
+ version: head.version,
354
+ checksum: head.checksum
355
+ };
356
+ }
357
+ finally {
358
+ db.close();
359
+ }
360
+ }
294
361
  const UPDATE_CLI_PATH = fileURLToPath(new URL("../cli.js", import.meta.url));
295
362
  const UPDATE_CLIENT_RUNTIME_PATH = fileURLToPath(new URL("../controller/clientRuntime.js", import.meta.url));
296
363
  const UPDATE_CONTROLLER_RECONCILIATION_PATH = fileURLToPath(new URL("../controller/updateReconciliation.js", import.meta.url));
@@ -581,7 +648,17 @@ function runControllerCommand(home, environment, spawn, method, cliBinary) {
581
648
  const args = cliBinary === undefined
582
649
  ? [UPDATE_CLI_PATH, "--json", "controller", method]
583
650
  : ["--json", "controller", method];
584
- const result = spawn(command, args, { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
651
+ const result = spawn(command, args, {
652
+ cwd: process.cwd(),
653
+ env: {
654
+ ...environment,
655
+ YUI_HOME: home,
656
+ // This exact lifecycle child is part of the update process that owns
657
+ // the handover lock. Managed Sessions never receive this bypass.
658
+ YUI_UPDATE_HANDOVER_OWNER_PID: String(process.pid)
659
+ },
660
+ shell: false
661
+ });
585
662
  if (result.error !== undefined || result.status !== 0) {
586
663
  const error = new Error(`Controller ${method} failed (exit ${result.status ?? "null"}).`);
587
664
  const code = controllerErrorCodeFromResult(result);
@@ -828,11 +905,14 @@ function interpretPreflight(result) {
828
905
  ...(data.sceneUnchanged === true ? { sceneUnchanged: true } : {})
829
906
  };
830
907
  }
831
- /** Strictly parse the three green states of the internal update preflight. */
908
+ /** Strictly parse the green states of the internal update preflight. */
832
909
  function parseUpdatePreflightResult(data) {
833
910
  const status = data.status;
834
911
  const stepCount = data.stepCount;
835
- if ((status !== "already-current" && status !== "compatible" && status !== "migration-required")
912
+ if ((status !== "already-current"
913
+ && status !== "compatible"
914
+ && status !== "in-place-migration"
915
+ && status !== "migration-required")
836
916
  || !Number.isSafeInteger(stepCount)
837
917
  || stepCount < 0) {
838
918
  return null;
@@ -857,11 +937,57 @@ function parseUpdatePreflightResult(data) {
857
937
  return { status };
858
938
  const evidence = status === "compatible"
859
939
  ? `${stepCount} compatible step(s) classified and the compatible source validated in memory`
860
- : `${stepCount} offline migration step(s) classified and the offline runtime inventory confirmed clear`;
940
+ : status === "in-place-migration"
941
+ ? `${stepCount} SQLite migration step(s) classified for one in-place transaction and the offline runtime inventory confirmed clear`
942
+ : `${stepCount} offline migration step(s) classified and the offline runtime inventory confirmed clear`;
943
+ const summary = `${evidence}. `
944
+ + "No staged Home or staged-output loader validation was performed during update preflight.";
945
+ if (status === "in-place-migration") {
946
+ const sqliteMigration = parseSqliteMigrationBoundary(data.sqliteMigration, stepCount);
947
+ if (sqliteMigration === null)
948
+ return null;
949
+ return {
950
+ status,
951
+ summary,
952
+ sqliteMigration
953
+ };
954
+ }
861
955
  return {
862
956
  status,
863
- summary: `${evidence}. `
864
- + "No staged Home or staged-output loader validation was performed during update preflight."
957
+ summary
958
+ };
959
+ }
960
+ function parseSqliteMigrationBoundary(value, stepCount) {
961
+ if (!isRecord(value))
962
+ return null;
963
+ const currentVersion = value.currentVersion;
964
+ const currentChecksum = value.currentChecksum;
965
+ const targetVersion = value.targetVersion;
966
+ const targetChecksum = value.targetChecksum;
967
+ const pendingVersions = value.pendingVersions;
968
+ if (!Number.isSafeInteger(currentVersion)
969
+ || currentVersion < 1
970
+ || typeof currentChecksum !== "string"
971
+ || currentChecksum.length === 0
972
+ || !Number.isSafeInteger(targetVersion)
973
+ || targetVersion <= currentVersion
974
+ || typeof targetChecksum !== "string"
975
+ || targetChecksum.length === 0
976
+ || !Array.isArray(pendingVersions)
977
+ || pendingVersions.length !== stepCount
978
+ || targetVersion !== currentVersion + stepCount
979
+ || pendingVersions.some((version, index) => !Number.isSafeInteger(version) || version !== currentVersion + index + 1)) {
980
+ return null;
981
+ }
982
+ return {
983
+ current: {
984
+ version: currentVersion,
985
+ checksum: currentChecksum
986
+ },
987
+ target: {
988
+ version: targetVersion,
989
+ checksum: targetChecksum
990
+ }
865
991
  };
866
992
  }
867
993
  function parseUpdateBlockers(value) {
@@ -933,6 +1059,9 @@ function interpretActivation(result) {
933
1059
  if (outcome === "already-current")
934
1060
  return { status: "already-current" };
935
1061
  if (outcome === "upgraded") {
1062
+ if (data.migrationMode === "in-place" && data.backupPath === undefined) {
1063
+ return { status: "migrated-in-place" };
1064
+ }
936
1065
  const backupPath = data.backupPath;
937
1066
  if (typeof backupPath !== "string"
938
1067
  || backupPath.length === 0
@@ -35,6 +35,9 @@ export async function runUpgradeCommand(args, home, options = {}) {
35
35
  ...(options.controllerLifecycle === undefined
36
36
  ? {}
37
37
  : { controllerLifecycle: options.controllerLifecycle }),
38
+ ...(options.externalUpgradeFenceOwnerPid === undefined
39
+ ? {}
40
+ : { externalUpgradeFenceOwnerPid: options.externalUpgradeFenceOwnerPid }),
38
41
  ...(options.now === undefined ? {} : { now: options.now })
39
42
  });
40
43
  return {
@@ -68,28 +71,35 @@ export function renderUpgradeResult(result, mode) {
68
71
  ? "four-state classification"
69
72
  : result.status === "compatible"
70
73
  ? "four-state classification plus compatible-source validation"
71
- : "four-state classification plus a clear offline runtime inventory";
74
+ : result.status === "in-place-migration"
75
+ ? "SQLite ledger classification plus a clear offline runtime inventory"
76
+ : "four-state classification plus a clear offline runtime inventory";
72
77
  return `${header}\nUpdate preflight: ${result.status} (${result.stepCount} step(s)); `
73
78
  + `${evidence}. No staged Home was created, `
74
79
  + "no staged-output loader validation was performed, and storage was not switched.";
75
80
  }
76
81
  case "dry-run": {
77
- const steps = result.report.outcome === "dry-run" ? result.report.steps.length : 0;
82
+ const steps = result.classification.sqliteMigration?.pendingVersions.length
83
+ ?? (result.report.outcome === "dry-run" ? result.report.steps.length : 0);
78
84
  return `${header}\nDry run: validated ${steps} migration step(s) through the loader gate. `
79
85
  + "Staged output discarded; storage was not switched.";
80
86
  }
81
87
  case "upgraded":
82
- return `${header}\nUpgraded storage. Original Home backed up at `
83
- + `${result.backupPath ?? "(unspecified)"}.`;
88
+ return result.migrationMode === "in-place"
89
+ ? `${header}\nUpgraded SQLite in place in one transaction; no database rebuild or backup copy was created.`
90
+ : `${header}\nUpgraded storage. Original Home backed up at `
91
+ + `${result.backupPath ?? "(unspecified)"}.`;
84
92
  case "blocked": {
85
93
  // Most blockers guarantee the source is untouched. A partial switch or a
86
94
  // post-switch ambiguity explicitly carries the committed boundary and
87
95
  // named recovery evidence; never print a false unchanged claim there.
88
- const unchangedNote = result.switchCommitted === true
89
- ? "The storage switch committed, but post-switch completion is ambiguous; the old Controller was not restored (see Action and recovery evidence)."
90
- : result.stage === "switch-ambiguous"
91
- ? "The switch did not complete and could not be rolled back; the authoritative Home is NOT intact (see Action)."
92
- : "Storage was not switched; the authoritative Home is unchanged.";
96
+ const unchangedNote = result.storageCommitted === true
97
+ ? "The SQLite transaction committed in place; the old Controller was not restored (see Action)."
98
+ : result.switchCommitted === true
99
+ ? "The storage switch committed, but post-switch completion is ambiguous; the old Controller was not restored (see Action and recovery evidence)."
100
+ : result.stage === "switch-ambiguous"
101
+ ? "The switch did not complete and could not be rolled back; the authoritative Home is NOT intact (see Action)."
102
+ : "Storage was not switched; the authoritative Home is unchanged.";
93
103
  return [
94
104
  header,
95
105
  `${mode === "dry-run" ? "Dry run" : mode === "update-preflight" ? "Update preflight" : "Upgrade"} blocked at ${result.stage}: ${result.message}`,