maka-agent 0.2.0-dev.26.20260909 → 0.2.0-dev.28.20260911

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 (125) hide show
  1. package/LICENSE +106 -0
  2. package/dist/cli-core.js +28 -0
  3. package/dist/runtime-host-cli.js +43 -3
  4. package/dist/runtime-host-handoff-surface.js +10 -8
  5. package/dist/runtime-host-lifecycle-transaction.js +28 -3
  6. package/dist/runtime-host-local-source-retirement.js +1 -1
  7. package/dist/runtime-host-run-command.js +15 -7
  8. package/dist/runtime-host-setup-command.js +29 -6
  9. package/dist/runtime-host-update-command.js +49 -5
  10. package/dist/session-export-command.js +72 -0
  11. package/dist/session-import-command.js +69 -0
  12. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  13. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  14. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  15. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  16. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  17. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -4
  18. package/node_modules/@maka/core/dist/diagnostic-log.js +18 -7
  19. package/node_modules/@maka/core/dist/text-sanitize.js +8 -2
  20. package/node_modules/@maka/core/dist/usage-stats/types.js +2 -0
  21. package/node_modules/@maka/core/dist/workhub-routing.js +126 -0
  22. package/node_modules/@maka/core/package.json +2 -1
  23. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +15 -11
  24. package/node_modules/@maka/runtime/dist/archive-read-tool.js +3 -1
  25. package/node_modules/@maka/runtime/dist/bots/bot-test.js +2 -1
  26. package/node_modules/@maka/runtime/dist/bots/feishu-bridge.js +24 -20
  27. package/node_modules/@maka/runtime/dist/bots/slack-bridge.js +5 -3
  28. package/node_modules/@maka/runtime/dist/bots/wechat-bridge.js +87 -29
  29. package/node_modules/@maka/runtime/dist/bots/wecom-bridge.js +40 -36
  30. package/node_modules/@maka/runtime/dist/builtin-tools.js +21 -2
  31. package/node_modules/@maka/runtime/dist/computer-use-tools.js +4 -1
  32. package/node_modules/@maka/runtime/dist/deep-research-tools.js +11 -5
  33. package/node_modules/@maka/runtime/dist/edit-replace.js +2 -2
  34. package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +5 -6
  35. package/node_modules/@maka/runtime/dist/memory-extraction-evidence.js +7 -1
  36. package/node_modules/@maka/runtime/dist/memory-extraction.js +12 -7
  37. package/node_modules/@maka/runtime/dist/openai-responses-websocket.js +2 -5
  38. package/node_modules/@maka/runtime/dist/plugin-agent-service.js +131 -0
  39. package/node_modules/@maka/runtime/dist/plugin-approval-service.js +33 -0
  40. package/node_modules/@maka/runtime/dist/plugin-attachment-service.js +53 -0
  41. package/node_modules/@maka/runtime/dist/plugin-command-service.js +100 -0
  42. package/node_modules/@maka/runtime/dist/plugin-composition-loader.js +11 -2
  43. package/node_modules/@maka/runtime/dist/plugin-data-services.js +332 -0
  44. package/node_modules/@maka/runtime/dist/plugin-fs-service.js +62 -0
  45. package/node_modules/@maka/runtime/dist/plugin-goal-service.js +61 -0
  46. package/node_modules/@maka/runtime/dist/plugin-invocation-signal.js +24 -0
  47. package/node_modules/@maka/runtime/dist/plugin-llm-service.js +85 -0
  48. package/node_modules/@maka/runtime/dist/plugin-lsp-service.js +94 -0
  49. package/node_modules/@maka/runtime/dist/plugin-scope-registry.js +91 -0
  50. package/node_modules/@maka/runtime/dist/plugin-session-query-service.js +71 -0
  51. package/node_modules/@maka/runtime/dist/plugin-shell-env-service.js +87 -0
  52. package/node_modules/@maka/runtime/dist/plugin-shell-service.js +74 -0
  53. package/node_modules/@maka/runtime/dist/plugin-skill-service.js +93 -0
  54. package/node_modules/@maka/runtime/dist/plugin-system-prompt-service.js +346 -0
  55. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +18 -52
  56. package/node_modules/@maka/runtime/dist/plugin-user-question-service.js +43 -0
  57. package/node_modules/@maka/runtime/dist/plugin-web-service.js +69 -0
  58. package/node_modules/@maka/runtime/dist/request-customization-fetch.js +14 -7
  59. package/node_modules/@maka/runtime/dist/session-export.js +178 -0
  60. package/node_modules/@maka/runtime/dist/session-import.js +100 -0
  61. package/node_modules/@maka/runtime/dist/session-manager.js +6 -1
  62. package/node_modules/@maka/runtime/dist/shell-run-manager.js +9 -6
  63. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +13 -6
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +43 -10
  65. package/node_modules/@maka/runtime/dist/stream-graph-handoff.js +6 -1
  66. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +1 -4
  67. package/node_modules/@maka/runtime/dist/system-prompt/workspace-instructions.js +11 -6
  68. package/node_modules/@maka/runtime/dist/tavily-search.js +8 -2
  69. package/node_modules/@maka/runtime/dist/text-line-window.js +49 -0
  70. package/node_modules/@maka/runtime/dist/tool-output.js +35 -12
  71. package/node_modules/@maka/runtime/dist/unified-diff.js +40 -10
  72. package/node_modules/@maka/runtime/dist/web-fetch-tool.js +4 -2
  73. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +82 -33
  74. package/node_modules/@maka/runtime/dist/workspace-executor.js +2 -6
  75. package/node_modules/@maka/runtime/package.json +20 -1
  76. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +73 -24
  77. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +46 -12
  78. package/node_modules/@maka/runtime-host/dist/operator/setup-frame.js +31 -21
  79. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +7 -4
  80. package/node_modules/@maka/runtime-host/dist/protocol/artifact.js +2 -2
  81. package/node_modules/@maka/runtime-host/dist/protocol/index.js +10 -1
  82. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +30 -5
  83. package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +3 -2
  84. package/node_modules/@maka/runtime-host/dist/protocol/skill-catalog.js +2 -1
  85. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +25 -23
  86. package/node_modules/@maka/runtime-host/dist/server/artifact-coordinator.js +2 -0
  87. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +62 -4
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +481 -11
  89. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +74 -0
  90. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +6 -1
  91. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +2 -2
  92. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +30 -0
  93. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +114 -20
  94. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +49 -12
  95. package/node_modules/@maka/runtime-host/dist/server/plugin-data-runtime.js +234 -0
  96. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  97. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +12 -0
  98. package/node_modules/@maka/runtime-host/dist/server/root-admission-owner.js +17 -8
  99. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +189 -92
  100. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-projection.js +3 -9
  101. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +20 -21
  102. package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +24 -2
  103. package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +18 -2
  104. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -35
  105. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +92 -10
  106. package/node_modules/@maka/runtime-host/dist/transport/framed-byte-stream-transport.js +4 -1
  107. package/node_modules/@maka/runtime-host/dist/transport/local-ipc-framing.js +4 -2
  108. package/node_modules/@maka/runtime-host/dist/transport/peer-native.js +9 -8
  109. package/node_modules/@maka/runtime-host/dist/transport/resumable-peer-stream.js +14 -9
  110. package/node_modules/@maka/storage/dist/agent-run-store.js +54 -10
  111. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +32 -10
  112. package/node_modules/@maka/storage/dist/context-value-mutation-gate.js +59 -0
  113. package/node_modules/@maka/storage/dist/execution-stores.js +1 -1
  114. package/node_modules/@maka/storage/dist/session-bundle-policy.js +909 -34
  115. package/node_modules/@maka/storage/dist/session-message-projection.js +8 -4
  116. package/node_modules/@maka/storage/dist/session-store.js +2 -2
  117. package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +18 -4
  118. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +12 -5
  119. package/node_modules/@maka/storage/dist/stable-storage.js +2 -1
  120. package/node_modules/@maka/storage/package.json +2 -0
  121. package/node_modules/@modelcontextprotocol/client/dist/src-D_zzAWoS.mjs +22 -5
  122. package/node_modules/@modelcontextprotocol/client/dist/src-NAgB4Mp8.cjs +22 -5
  123. package/node_modules/zod/v4/core/memoizer.cjs +12 -16
  124. package/node_modules/zod/v4/core/memoizer.js +12 -16
  125. package/package.json +1 -1
package/LICENSE CHANGED
@@ -261,6 +261,112 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
261
261
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
262
262
  SOFTWARE.
263
263
 
264
+ Pi TUI dependency patch
265
+
266
+ Source: https://www.npmjs.com/package/@earendil-works/pi-tui/v/0.84.4
267
+ Repository: https://github.com/earendil-works/pi/tree/v0.84.4/packages/tui
268
+ Version: 0.84.4
269
+ Dependency patch: patches/@earendil-works+pi-tui+0.84.4.patch
270
+ Copyright (c) 2025 Mario Zechner
271
+ License: MIT
272
+
273
+ Maka redistributes a source patch that copies mutable editor undo state while
274
+ sharing immutable pasted strings. The following MIT License applies to that
275
+ material:
276
+
277
+ MIT License
278
+
279
+ Copyright (c) 2025 Mario Zechner
280
+
281
+ Permission is hereby granted, free of charge, to any person obtaining a copy
282
+ of this software and associated documentation files (the "Software"), to deal
283
+ in the Software without restriction, including without limitation the rights
284
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
285
+ copies of the Software, and to permit persons to whom the Software is
286
+ furnished to do so, subject to the following conditions:
287
+
288
+ The above copyright notice and this permission notice shall be included in all
289
+ copies or substantial portions of the Software.
290
+
291
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
292
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
293
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
294
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
295
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
296
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
297
+ SOFTWARE.
298
+
299
+ Zod dependency patch
300
+
301
+ Source: https://www.npmjs.com/package/zod/v/4.5.4
302
+ Repository: https://github.com/colinhacks/zod
303
+ Version: 4.5.4
304
+ Dependency patch: patches/zod+4.5.4.patch
305
+ Copyright (c) 2025 Colin McDonnell
306
+ License: MIT
307
+
308
+ Maka redistributes a source patch that releases recursive parse state after
309
+ completion or failure. The following MIT License applies to that material:
310
+
311
+ MIT License
312
+
313
+ Copyright (c) 2025 Colin McDonnell
314
+
315
+ Permission is hereby granted, free of charge, to any person obtaining a copy
316
+ of this software and associated documentation files (the "Software"), to deal
317
+ in the Software without restriction, including without limitation the rights
318
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
319
+ copies of the Software, and to permit persons to whom the Software is
320
+ furnished to do so, subject to the following conditions:
321
+
322
+ The above copyright notice and this permission notice shall be included in all
323
+ copies or substantial portions of the Software.
324
+
325
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
326
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
327
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
328
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
329
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
330
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
331
+ SOFTWARE.
332
+
333
+ Model Context Protocol client dependency patch
334
+
335
+ Source: https://www.npmjs.com/package/@modelcontextprotocol/client/v/2.0.0
336
+ Repository: https://github.com/modelcontextprotocol/typescript-sdk
337
+ Version: 2.0.0
338
+ Dependency patch: patches/@modelcontextprotocol+client+2.0.0.patch
339
+ Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
340
+ Declared package license: MIT
341
+
342
+ Maka redistributes a source patch that releases settled request observers under
343
+ transport backpressure. The upstream package's LICENSE describes a transition
344
+ from MIT to Apache-2.0: contributions with relicensing consent use Apache-2.0,
345
+ while contributions without that consent retain their original MIT license.
346
+ The Apache License, Version 2.0 is reproduced above. The upstream MIT notice is:
347
+
348
+ MIT License
349
+
350
+ Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
351
+
352
+ Permission is hereby granted, free of charge, to any person obtaining a copy
353
+ of this software and associated documentation files (the "Software"), to deal
354
+ in the Software without restriction, including without limitation the rights
355
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
356
+ copies of the Software, and to permit persons to whom the Software is
357
+ furnished to do so, subject to the following conditions:
358
+
359
+ The above copyright notice and this permission notice shall be included in all
360
+ copies or substantial portions of the Software.
361
+
362
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
363
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
364
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
365
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
366
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
367
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
368
+ SOFTWARE.
369
+
264
370
  Sigstore dependency patch
265
371
 
266
372
  Source: https://www.npmjs.com/package/@sigstore/core/v/4.0.1
package/dist/cli-core.js CHANGED
@@ -52,6 +52,10 @@ export function parseMakaCliArgs(argv, version, cliCommand = RELEASE_MAKA_CLI_LA
52
52
  return { kind: 'run', args: argv.slice(1) };
53
53
  if (first === 'activate')
54
54
  return { kind: 'activate', args: argv.slice(1) };
55
+ if (first === 'session-export')
56
+ return { kind: 'session-export', args: argv.slice(1) };
57
+ if (first === 'session-import')
58
+ return { kind: 'session-import', args: argv.slice(1) };
55
59
  if (first === 'eval')
56
60
  return { kind: 'eval', args: argv.slice(1) };
57
61
  if (first === 'update')
@@ -97,6 +101,8 @@ function helpText(cliCommand) {
97
101
  ` ${cliCommand} --acp Serve ACP v1 over stdio (initialize, session/new, session/list)`,
98
102
  ` ${cliCommand} run ... Run one non-interactive model turn`,
99
103
  ` ${cliCommand} activate ... Run one Cloud Session activation and emit JSONL`,
104
+ ` ${cliCommand} session-export --workspace-root <dir> --session <id> --out <file.maka-session>`,
105
+ ` ${cliCommand} session-import --workspace-root <dir> --bundle <file.maka-session>`,
100
106
  ` ${cliCommand} -p ... Alias for ${cliCommand} run`,
101
107
  ` ${cliCommand} eval ... Run one declarative multi-arm experiment`,
102
108
  ` ${cliCommand} update --target <latest|next|version> Update this npm-global CLI and its local Runtime Host`,
@@ -243,6 +249,14 @@ export async function runMakaCli(argv = process.argv.slice(2), options = RELEASE
243
249
  const { runMakaActivationCli } = await import('./activation-command.js');
244
250
  return runMakaActivationCli(command.args);
245
251
  }
252
+ case 'session-export': {
253
+ const { runMakaSessionExportCli } = await import('./session-export-command.js');
254
+ return runMakaSessionExportCli(command.args);
255
+ }
256
+ case 'session-import': {
257
+ const { runMakaSessionImportCli } = await import('./session-import-command.js');
258
+ return runMakaSessionImportCli(command.args);
259
+ }
246
260
  case 'eval': {
247
261
  const { configureInstalledEvalBundle } = await import('./eval-bundle-path.js');
248
262
  configureInstalledEvalBundle();
@@ -386,6 +400,8 @@ export async function runMakaCli(argv = process.argv.slice(2), options = RELEASE
386
400
  bindPairingToClient: command.bindPairingToClient,
387
401
  ...(command.repairRootAfterRemount ? { repairRootAfterRemount: true } : {}),
388
402
  updateExisting: command.updateExisting,
403
+ reuseExistingEnvironment: command.reuseExistingEnvironment,
404
+ allowInterruptActiveTasks: command.allowInterruptActiveTasks,
389
405
  ...(command.rootPath ? { rootPath: command.rootPath } : {}),
390
406
  ...(command.projectDirectoryRoots
391
407
  ? { projectDirectoryRoots: command.projectDirectoryRoots }
@@ -485,7 +501,13 @@ export async function runMakaCli(argv = process.argv.slice(2), options = RELEASE
485
501
  defaultRootPath: serviceDataRoots.workspaceRoot,
486
502
  selector: command.selector,
487
503
  expectedTarget: command.expectedTarget,
504
+ ...(command.expectedSourceVersion
505
+ ? { expectedSourceVersion: command.expectedSourceVersion }
506
+ : {}),
488
507
  ...(command.expectedHost ? { expectedHost: command.expectedHost } : {}),
508
+ ...(command.expectedConfigFingerprint
509
+ ? { expectedConfigFingerprint: command.expectedConfigFingerprint }
510
+ : {}),
489
511
  ...(command.managedRootId ? { managedRootId: command.managedRootId } : {}),
490
512
  ...(command.operatorDeploymentId
491
513
  ? { operatorDeploymentId: command.operatorDeploymentId }
@@ -503,7 +525,13 @@ export async function runMakaCli(argv = process.argv.slice(2), options = RELEASE
503
525
  ...(sourcePackageIntegrity ? { sourcePackageIntegrity } : {}),
504
526
  version,
505
527
  expectedTarget: command.expectedTarget,
528
+ ...(command.expectedSourceVersion
529
+ ? { expectedSourceVersion: command.expectedSourceVersion }
530
+ : {}),
506
531
  ...(command.expectedHost ? { expectedHost: command.expectedHost } : {}),
532
+ ...(command.expectedConfigFingerprint
533
+ ? { expectedConfigFingerprint: command.expectedConfigFingerprint }
534
+ : {}),
507
535
  ...(command.managedRootId ? { managedRootId: command.managedRootId } : {}),
508
536
  ...(command.operatorDeploymentId
509
537
  ? { operatorDeploymentId: command.operatorDeploymentId }
@@ -363,6 +363,8 @@ function parseSetupCommand(argv) {
363
363
  let bindPairingToClient = false;
364
364
  let repairRootAfterRemount = false;
365
365
  let updateExisting = false;
366
+ let reuseExistingEnvironment = false;
367
+ let allowInterruptActiveTasks = false;
366
368
  let clientDataRoot;
367
369
  let enableDirectPeer = false;
368
370
  const coordinationRelays = [];
@@ -418,6 +420,16 @@ function parseSetupCommand(argv) {
418
420
  return error('Duplicate --repair-root-after-remount');
419
421
  repairRootAfterRemount = true;
420
422
  },
423
+ '--allow-interrupt-active-tasks': () => {
424
+ if (allowInterruptActiveTasks)
425
+ return error('Duplicate --allow-interrupt-active-tasks');
426
+ allowInterruptActiveTasks = true;
427
+ },
428
+ '--reuse-existing-environment': () => {
429
+ if (reuseExistingEnvironment)
430
+ return error('Duplicate --reuse-existing-environment');
431
+ reuseExistingEnvironment = true;
432
+ },
421
433
  '--update-existing': () => {
422
434
  if (updateExisting)
423
435
  return error('Duplicate --update-existing');
@@ -427,6 +439,17 @@ function parseSetupCommand(argv) {
427
439
  });
428
440
  if ('kind' in options)
429
441
  return options;
442
+ if (reuseExistingEnvironment &&
443
+ (lifecycle !== 'on_demand' ||
444
+ updateExisting ||
445
+ enableDirectPeer ||
446
+ deferPairingCommit ||
447
+ bindPairingToClient)) {
448
+ return error('--reuse-existing-environment requires on-demand local setup without update or remote pairing options');
449
+ }
450
+ if (allowInterruptActiveTasks && !updateExisting) {
451
+ return error('--allow-interrupt-active-tasks requires --update-existing');
452
+ }
430
453
  if (!principalId || !/^[A-Za-z0-9_.:-]{1,128}$/u.test(principalId)) {
431
454
  return error('runtime-host setup requires a valid --principal');
432
455
  }
@@ -448,6 +471,8 @@ function parseSetupCommand(argv) {
448
471
  ...(bindPairingToClient ? { bindPairingToClient: true } : {}),
449
472
  ...(repairRootAfterRemount ? { repairRootAfterRemount: true } : {}),
450
473
  ...(updateExisting ? { updateExisting: true } : {}),
474
+ ...(reuseExistingEnvironment ? { reuseExistingEnvironment: true } : {}),
475
+ ...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}),
451
476
  ...(clientDataRoot ? { clientDataRoot } : {}),
452
477
  ...(enableDirectPeer ? { directPeer: { coordinationRelays } } : {}),
453
478
  };
@@ -522,6 +547,7 @@ function parseServiceManagementCommand(argv) {
522
547
  let updateTarget;
523
548
  let expectedHost;
524
549
  let expectedConfigFingerprint;
550
+ let expectedSourceVersion;
525
551
  const flagOptions = action === 'uninstall'
526
552
  ? {
527
553
  '--retain-managed-deployment': () => {
@@ -580,6 +606,17 @@ function parseServiceManagementCommand(argv) {
580
606
  },
581
607
  }
582
608
  : {}),
609
+ ...(action === 'update'
610
+ ? {
611
+ '--expected-source-version': (value) => {
612
+ if (expectedSourceVersion !== undefined)
613
+ return error('Duplicate --expected-source-version');
614
+ if (!isProductReleaseVersion(value))
615
+ return error('--expected-source-version must be an exact package version');
616
+ expectedSourceVersion = value;
617
+ },
618
+ }
619
+ : {}),
583
620
  ...(action === 'update' || action === 'restart'
584
621
  ? {
585
622
  '--expected-host-json': (value) => {
@@ -592,7 +629,7 @@ function parseServiceManagementCommand(argv) {
592
629
  },
593
630
  }
594
631
  : {}),
595
- ...(action === 'configure'
632
+ ...(action === 'configure' || action === 'update'
596
633
  ? {
597
634
  '--expected-config-fingerprint': (value) => {
598
635
  if (expectedConfigFingerprint !== undefined) {
@@ -674,8 +711,9 @@ function parseServiceManagementCommand(argv) {
674
711
  };
675
712
  }
676
713
  if (action === 'update') {
677
- if (expectedHost && !options.managedRootId) {
678
- return error('--expected-host-json requires --managed-root-id');
714
+ if ((expectedHost || expectedSourceVersion || expectedConfigFingerprint) &&
715
+ !options.managedRootId) {
716
+ return error('Observed Host and deployment fences require --managed-root-id');
679
717
  }
680
718
  const selector = updateTarget === undefined ? undefined : parseUpdateSelector(updateTarget, 'update');
681
719
  if (selector && 'kind' in selector && selector.kind === 'error')
@@ -697,6 +735,8 @@ function parseServiceManagementCommand(argv) {
697
735
  : {}),
698
736
  expectedTarget: options.expectedTarget,
699
737
  ...(expectedHost ? { expectedHost } : {}),
738
+ ...(expectedConfigFingerprint ? { expectedConfigFingerprint } : {}),
739
+ ...(expectedSourceVersion ? { expectedSourceVersion } : {}),
700
740
  ...(selector ? { selector } : {}),
701
741
  ...(allowManualUpdate ? { allowManualUpdate: true } : {}),
702
742
  ...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}),
@@ -60,7 +60,7 @@ export function createCliHostHandoffSurface(locale, input = process.stdin, outpu
60
60
  '\n');
61
61
  if (view.actions.length === 0)
62
62
  return;
63
- const options = copy.actions.map(({ action, label }) => `${action === 'interrupt' ? 'stop' : action === 'retry' ? 'r' : 'Enter'}: ${label}`);
63
+ const options = copy.actions.map(({ action, label }) => `${action === 'interrupt' ? 'stop' : action === 'retry' ? 'r' : action === 'replace' ? 'update' : 'Enter'}: ${label}`);
64
64
  // Create the reader only after the explanation is visible, with the
65
65
  // final action prompt already installed. Never draw readline's default >.
66
66
  const reader = createInterface({
@@ -82,13 +82,15 @@ export function createCliHostHandoffSurface(locale, input = process.stdin, outpu
82
82
  if (readline !== reader || current?.revision !== view.revision)
83
83
  return;
84
84
  const answer = line.trim().toLowerCase();
85
- const action = answer === 'r'
86
- ? 'retry'
87
- : answer === 'stop'
88
- ? 'interrupt'
89
- : answer === ''
90
- ? 'cancel'
91
- : undefined;
85
+ const action = answer === 'update'
86
+ ? 'replace'
87
+ : answer === 'r'
88
+ ? 'retry'
89
+ : answer === 'stop'
90
+ ? 'interrupt'
91
+ : answer === ''
92
+ ? 'cancel'
93
+ : undefined;
92
94
  if (!action || !view.actions.includes(action)) {
93
95
  reader.prompt();
94
96
  return;
@@ -178,9 +178,6 @@ export async function retireRuntimeHostLifecycleOwner(input) {
178
178
  }
179
179
  async function retireRuntimeHostLifecycleOwnerWithSignal(input, signal) {
180
180
  signal?.throwIfAborted();
181
- if (input.expectedOwner && !input.supervisor) {
182
- throw new RuntimeHostLifecycleTransactionError('owner_changed', 'A Runtime Host identity fence requires a supervised deployment');
183
- }
184
181
  const capability = await resolveExistingStorageRoot({
185
182
  path: input.rootPath,
186
183
  kind: 'interactive',
@@ -189,6 +186,9 @@ async function retireRuntimeHostLifecycleOwnerWithSignal(input, signal) {
189
186
  const idleOwner = await tryAcquireStateRootOwner(capability);
190
187
  if (idleOwner) {
191
188
  try {
189
+ if (input.expectedOwner && !input.supervisor) {
190
+ throw new RuntimeHostLifecycleTransactionError('owner_changed', 'The observed Runtime Host has exited');
191
+ }
192
192
  if (input.expectedOwner && input.supervisor) {
193
193
  const status = await input.supervisor.status();
194
194
  assertExpectedSupervisorOwner(input.expectedOwner, status);
@@ -212,6 +212,21 @@ async function retireRuntimeHostLifecycleOwnerWithSignal(input, signal) {
212
212
  });
213
213
  assertExpectedRuntimeHostOwner(input.expectedOwner, 'registration' in connected ? connected.registration : undefined);
214
214
  if (connected.kind !== 'connected') {
215
+ if (connected.kind === 'incompatible' &&
216
+ connected.registration?.rootId === input.rootId &&
217
+ connected.registration.lifecycleMode === 'ephemeral' &&
218
+ input.expectedOwner &&
219
+ !input.supervisor &&
220
+ input.prepareSourceRetirement) {
221
+ signal?.throwIfAborted();
222
+ const prepared = await input.prepareSourceRetirement(signal);
223
+ if (prepared === 'active_work')
224
+ return { kind: 'active_tasks' };
225
+ if (prepared === 'prepared') {
226
+ return waitForRuntimeHostLifecycleOwner(capability, input.timeoutMs ?? 45_000);
227
+ }
228
+ throw new RuntimeHostLifecycleTransactionError('transition_failed', 'The installed source package cannot retire this Runtime Host');
229
+ }
215
230
  if (input.supervisor) {
216
231
  const status = await input.supervisor.status();
217
232
  assertExpectedSupervisorOwner(input.expectedOwner, status);
@@ -316,6 +331,10 @@ export async function replaceRuntimeHostLifecycle(input) {
316
331
  const retirement = await retireRuntimeHostLifecycleOwner({
317
332
  rootPath: desired.root.path,
318
333
  rootId: desired.root.id,
334
+ ...(input.deps.connectExisting ? { connectExisting: input.deps.connectExisting } : {}),
335
+ ...(input.prepareSourceRetirement
336
+ ? { prepareSourceRetirement: input.prepareSourceRetirement }
337
+ : {}),
319
338
  ...(input.retirementSupervisor
320
339
  ? { supervisor: input.retirementSupervisor }
321
340
  : currentProvider
@@ -345,6 +364,12 @@ export async function replaceRuntimeHostLifecycle(input) {
345
364
  }
346
365
  }
347
366
  catch (activationError) {
367
+ // An on-demand update may have allowed its successor to open the State Root
368
+ // before readiness failed. Keep that successor authoritative; configuring an
369
+ // existing package and supervised updates retain their rollback behavior.
370
+ if (input.operation === 'update' && desired.lifecycle.mode === 'on_demand') {
371
+ throw new RuntimeHostLifecycleTransactionError('recovery_failed', 'The updated Runtime Host could not become ready. Its deployment is retained; retry activation or repair it without restoring older code.', { cause: activationError });
372
+ }
348
373
  const rollback = current
349
374
  ? {
350
375
  ...current,
@@ -74,7 +74,7 @@ export function launchRuntimeHostLocalSourceRetirement(input) {
74
74
  : []),
75
75
  ];
76
76
  return new Promise((resolve, reject) => {
77
- const child = spawn(process.execPath, args, {
77
+ const child = spawn(input.sourceNodePath ?? process.execPath, args, {
78
78
  // fd 4 inherits the existing owner-authority lease. The source helper
79
79
  // holds it only while the authenticated retirement request is in flight.
80
80
  stdio: ['ignore', 'ignore', 'inherit', 'ignore', input.inheritableAuthorityLeaseFd],
@@ -569,19 +569,25 @@ class NonInteractiveInteractionController {
569
569
  #tasks = new Set();
570
570
  #unsubscribe;
571
571
  #failure;
572
- #failureSignal;
573
- #publishFailure;
572
+ #failureWaiters = new Set();
574
573
  constructor(driver, stop) {
575
574
  this.#driver = driver;
576
575
  this.#stop = stop;
577
- this.#failureSignal = new Promise((resolve) => {
578
- this.#publishFailure = resolve;
579
- });
580
576
  this.#unsubscribe = driver.subscribePendingInteractions((pending) => this.#accept(pending));
581
577
  }
582
578
  race(operation) {
583
579
  this.throwIfFailed();
584
- return Promise.race([operation, this.#failureSignal.then((error) => Promise.reject(error))]);
580
+ return new Promise((resolve, reject) => {
581
+ this.#failureWaiters.add(reject);
582
+ // Detach completed waits so the controller does not retain consumed event payloads.
583
+ operation.then((value) => {
584
+ this.#failureWaiters.delete(reject);
585
+ resolve(value);
586
+ }, (error) => {
587
+ this.#failureWaiters.delete(reject);
588
+ reject(error);
589
+ });
590
+ });
585
591
  }
586
592
  async settle() {
587
593
  await Promise.all([...this.#tasks]);
@@ -623,7 +629,9 @@ class NonInteractiveInteractionController {
623
629
  if (this.#failure)
624
630
  return;
625
631
  this.#failure = error;
626
- this.#publishFailure(error);
632
+ for (const reject of this.#failureWaiters)
633
+ reject(error);
634
+ this.#failureWaiters.clear();
627
635
  }
628
636
  }
629
637
  function delay(ms) {
@@ -23,7 +23,7 @@ import { isDeepStrictEqual } from 'node:util';
23
23
  import { truncateUtf8 } from '@maka/core/diagnostic-log';
24
24
  import { generalizedErrorMessage } from '@maka/core/redaction';
25
25
  import { activateRuntimeHostManagedDeployment, connectRemoteRuntimeHost, ensureRuntimeHostPeerIdentity, RuntimeHostOperationError, } from '@maka/runtime-host/client';
26
- import { RuntimeHostManagedDeploymentError as RuntimeHostDeploymentAuthorityError, encodeRuntimeHostSetupFrame, isSha512PackageIntegrity, resolveRuntimeHostManagedDeployment, runtimeHostManagedOperatorCommand, RUNTIME_HOST_SETUP_ERROR_CODE_MAX_BYTES, RUNTIME_HOST_SETUP_ERROR_MESSAGE_MAX_BYTES, } from '@maka/runtime-host/operator';
26
+ import { RuntimeHostManagedDeploymentError as RuntimeHostDeploymentAuthorityError, encodeRuntimeHostSetupFrame, isSha512PackageIntegrity, resolveRuntimeHostManagedDeployment, resolveRuntimeHostManagedDeploymentAuthority, runtimeHostManagedOperatorCommand, RUNTIME_HOST_SETUP_ERROR_CODE_MAX_BYTES, RUNTIME_HOST_SETUP_ERROR_MESSAGE_MAX_BYTES, } from '@maka/runtime-host/operator';
27
27
  import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
28
28
  import { prepareRuntimeHostAccessCredential, replaceRuntimeHostAccessCredential, revokeRuntimeHostAccessCredential, RuntimeHostAccessUnavailableError, } from './runtime-host-access-command.js';
29
29
  import { convergeRuntimeHostManagedOperator, verifyRuntimeHostManagedOperator, isRuntimeHostDevelopmentPackageVersion, openRuntimeHostManagedPackageDeployment, prepareRuntimeHostManagedPackageDeployment, pruneRuntimeHostManagedPackages, removeRuntimeHostManagedDeployment, resolveRuntimeHostManagedPackageCliPath, resolveRuntimeHostManagedControlRoot, resolveRuntimeHostManagedDeploymentRoot, restoreRuntimeHostLegacyManagedOperator, RuntimeHostManagedDeploymentError, } from './runtime-host-managed-deployment.js';
@@ -107,7 +107,7 @@ export async function runRuntimeHostSetupCli(options, overrides = {}) {
107
107
  try {
108
108
  const rootId = await resolveRuntimeHostSetupRootId(options);
109
109
  const controlRoot = resolveRuntimeHostManagedControlRoot(rootId);
110
- await withRuntimeHostManagedServiceDeploymentLock(options.clientDataRoot, () => withRuntimeHostManagedServiceLifecycleLock(options.clientDataRoot, () => withRuntimeHostManagedServiceDeploymentLock(controlRoot, () => withRuntimeHostManagedServiceLifecycleLock(controlRoot, () => runRuntimeHostSetupLocked(options, deps, emit), SETUP_LOCK_TIMEOUT_MS), SETUP_LOCK_TIMEOUT_MS), SETUP_LOCK_TIMEOUT_MS), SETUP_LOCK_TIMEOUT_MS);
110
+ await withRuntimeHostManagedServiceDeploymentLock(options.clientDataRoot, () => withRuntimeHostManagedServiceLifecycleLock(options.clientDataRoot, () => withRuntimeHostManagedServiceDeploymentLock(controlRoot, () => withRuntimeHostManagedServiceLifecycleLock(controlRoot, () => runRuntimeHostSetupLocked(options, deps, emit, rootId), SETUP_LOCK_TIMEOUT_MS), SETUP_LOCK_TIMEOUT_MS), SETUP_LOCK_TIMEOUT_MS), SETUP_LOCK_TIMEOUT_MS);
111
111
  return 0;
112
112
  }
113
113
  catch (error) {
@@ -138,7 +138,30 @@ async function readOptionalLegacyServiceConfig(clientDataRoot) {
138
138
  throw error;
139
139
  }
140
140
  }
141
- async function runRuntimeHostSetupLocked(options, deps, emit) {
141
+ async function runRuntimeHostSetupLocked(options, deps, emit, rootId) {
142
+ if (options.reuseExistingEnvironment) {
143
+ if (options.lifecycle !== 'on_demand' || options.updateExisting) {
144
+ throw new RuntimeHostSetupError('invalid_setup', 'Environment discovery cannot replace a deployment');
145
+ }
146
+ const existing = await resolveRuntimeHostManagedDeploymentAuthority(rootId);
147
+ if (existing) {
148
+ const { config, capability } = await resolveRuntimeHostManagedDeployment(rootId);
149
+ assertCanonicalSetupTarget(options.expectedTarget, rootId, capability.canonicalPath);
150
+ assertExpectedDeploymentGeneration(options.expectedTarget, config);
151
+ // The binding comes from canonical authority. The installed operator validates
152
+ // its own projection when connected; discovery must not rewrite an older launcher.
153
+ emit({
154
+ kind: 'existing_environment',
155
+ version: config.launch.package.version,
156
+ serviceId: rootId,
157
+ deploymentId: config.deploymentId,
158
+ rootId,
159
+ rootPath: capability.canonicalPath,
160
+ operator: runtimeHostManagedOperatorCommand(config, process.platform === 'win32' ? 'win32' : 'posix'),
161
+ });
162
+ return;
163
+ }
164
+ }
142
165
  if (options.lifecycle === 'on_demand') {
143
166
  await runRuntimeHostOnDemandSetupLocked(options, deps, emit);
144
167
  return;
@@ -260,7 +283,7 @@ async function runRuntimeHostSupervisedSetupLocked(options, deps, emit) {
260
283
  .then(() => undefined),
261
284
  }
262
285
  : {}),
263
- allowInterruptActiveTasks: Boolean(current && packageChanged && options.updateExisting),
286
+ allowInterruptActiveTasks: options.allowInterruptActiveTasks === true,
264
287
  deps: lifecycleDeps,
265
288
  });
266
289
  if (replacement.kind === 'active_tasks') {
@@ -561,7 +584,7 @@ async function runRuntimeHostOnDemandSetupLocked(options, deps, emit) {
561
584
  activateDesired: async () => {
562
585
  await deps.activateDesired({ rootId: capability.rootId });
563
586
  },
564
- allowInterruptActiveTasks: Boolean(current && packageChanged && options.updateExisting),
587
+ allowInterruptActiveTasks: options.allowInterruptActiveTasks === true,
565
588
  deps: lifecycleDeps,
566
589
  });
567
590
  if (replacement.kind === 'active_tasks') {
@@ -826,7 +849,7 @@ function createEmitter(json, deps) {
826
849
  if (frame.kind === 'progress') {
827
850
  deps.writeOutput(`${humanPhase(frame.phase)}\n`);
828
851
  }
829
- else if (frame.kind === 'complete') {
852
+ else if (frame.kind === 'complete' || frame.kind === 'existing_environment') {
830
853
  deps.writeOutput(`${JSON.stringify(frame, null, 2)}\n`);
831
854
  }
832
855
  else {
@@ -20,13 +20,14 @@ import { spawn } from 'node:child_process';
20
20
  import { dirname, join, resolve } from 'node:path';
21
21
  import { truncateUtf8 } from '@maka/core/diagnostic-log';
22
22
  import { activateRuntimeHostManagedDeployment } from '@maka/runtime-host/client';
23
- import { createRuntimeHostLegacyPosixOperatorCommand, decodeRuntimeHostServiceManagementFrame, encodeRuntimeHostServiceManagementFrame, RUNTIME_HOST_SERVICE_ERROR_CODE_MAX_BYTES, RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES, RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY, RUNTIME_HOST_OPERATOR_CAPABILITY_REQUEST_ENV, RUNTIME_HOST_OPERATOR_PROCESS_LIFETIME_LOCK_CAPABILITY, runtimeHostOperatorInvocation, RuntimeHostManagedDeploymentError as RuntimeHostDeploymentAuthorityError, } from '@maka/runtime-host/operator';
24
- import { assertRuntimeHostManagedOperatorConfig, assertRuntimeHostManagedOperatorDeployment, convergeRuntimeHostManagedOperator, verifyRuntimeHostManagedOperator, openRuntimeHostManagedPackageDeployment, prepareRuntimeHostManagedPackageDeployment, pruneRuntimeHostManagedPackages, resolveRuntimeHostManagedControlRoot, resolveRuntimeHostManagedPackageCliPath, RuntimeHostManagedDeploymentError, } from './runtime-host-managed-deployment.js';
25
- import { manageRuntimeHostService, replaceRuntimeHostManagedService, resolveRuntimeHostManagedServiceId, RuntimeHostServiceManagerError, verifyRuntimeHostManagedServiceReady, withRuntimeHostManagedServiceDeploymentLock, withRuntimeHostManagedServiceLegacyOperatorLeases, withRuntimeHostManagedServiceLifecycleLock, } from './runtime-host-service-manager.js';
23
+ import { compareProductReleaseVersions, createRuntimeHostLegacyPosixOperatorCommand, decodeRuntimeHostServiceManagementFrame, encodeRuntimeHostServiceManagementFrame, RUNTIME_HOST_SERVICE_ERROR_CODE_MAX_BYTES, RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES, RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY, RUNTIME_HOST_OPERATOR_CAPABILITY_REQUEST_ENV, RUNTIME_HOST_OPERATOR_PROCESS_LIFETIME_LOCK_CAPABILITY, runtimeHostOperatorInvocation, RuntimeHostManagedDeploymentError as RuntimeHostDeploymentAuthorityError, } from '@maka/runtime-host/operator';
24
+ import { isRuntimeHostDevelopmentPackageVersion, assertRuntimeHostManagedOperatorConfig, assertRuntimeHostManagedOperatorDeployment, convergeRuntimeHostManagedOperator, verifyRuntimeHostManagedOperator, openRuntimeHostManagedPackageDeployment, prepareRuntimeHostManagedPackageDeployment, pruneRuntimeHostManagedPackages, resolveRuntimeHostManagedControlRoot, resolveRuntimeHostManagedPackageCliPath, RuntimeHostManagedDeploymentError, } from './runtime-host-managed-deployment.js';
25
+ import { runtimeHostManagedServiceConfigFingerprint, manageRuntimeHostService, replaceRuntimeHostManagedService, resolveRuntimeHostManagedServiceId, RuntimeHostServiceManagerError, verifyRuntimeHostManagedServiceReady, withRuntimeHostManagedServiceDeploymentLock, withRuntimeHostManagedServiceLegacyOperatorLeases, withRuntimeHostManagedServiceLifecycleLock, } from './runtime-host-service-manager.js';
26
26
  import { createPlatformRuntimeHostServiceBackend, resolveRuntimeHostLifecycleProvider, runtimeHostServiceSummary, } from './runtime-host-service-management-command.js';
27
27
  import { resolveManagedRuntimeHostUpdateSelection, RuntimeHostUpdateDiscoveryError, } from './runtime-host-update-discovery.js';
28
28
  import { RuntimeHostUpdatePackageError, withRuntimeHostRegistryUpdatePackage, } from './runtime-host-update-package.js';
29
29
  import { canDiscardRuntimeHostLifecycleDesiredArtifacts, convergeRuntimeHostLifecycleControlProjection, replaceRuntimeHostLifecycle, resolveRecoverableRuntimeHostManagedDeployment, RuntimeHostLifecycleTransactionError, verifyRuntimeHostLifecycleProjection, } from './runtime-host-lifecycle-transaction.js';
30
+ import { launchRuntimeHostLocalSourceRetirement } from './runtime-host-local-source-retirement.js';
30
31
  import { manageRuntimeHostManagedLifecycle } from './runtime-host-managed-lifecycle-manager.js';
31
32
  const OPERATOR_TIMEOUT_MS = 2 * 60_000;
32
33
  const OPERATOR_OUTPUT_MAX_BYTES = 256 * 1024;
@@ -53,6 +54,7 @@ export async function runManagedRuntimeHostUpdateCli(options, overrides = {}, fr
53
54
  openDeployment: openRuntimeHostManagedPackageDeployment,
54
55
  prepareDeployment: prepareRuntimeHostManagedPackageDeployment,
55
56
  prunePackages: pruneRuntimeHostManagedPackages,
57
+ retireSource: launchRuntimeHostLocalSourceRetirement,
56
58
  activateDesired: (input) => activateRuntimeHostManagedDeployment(input, {
57
59
  reconcileActivation: async () => undefined,
58
60
  }),
@@ -379,7 +381,7 @@ export async function runManagedRuntimeHostUpdateCli(options, overrides = {}, fr
379
381
  async function runCanonicalRuntimeHostUpdate(options, deps, emit) {
380
382
  let staged;
381
383
  try {
382
- return await deps.withDeploymentLock(resolveRuntimeHostManagedControlRoot(options.managedRootId), async () => {
384
+ return await deps.withDeploymentLock(resolveRuntimeHostManagedControlRoot(options.managedRootId), async (inheritableAuthorityLeaseFd) => {
383
385
  await deps.canonical.assertOperatorDeployment(options.managedRootId, options.operatorDeploymentId, process.argv[1] ?? '');
384
386
  const rejection = await deps.revalidateSelection();
385
387
  if (rejection) {
@@ -395,6 +397,10 @@ async function runCanonicalRuntimeHostUpdate(options, deps, emit) {
395
397
  throw new RuntimeHostServiceManagerError('not_installed', 'The managed Runtime Host deployment is not installed');
396
398
  }
397
399
  const current = recovered.config;
400
+ if (options.expectedSourceVersion &&
401
+ current.launch.package.version !== options.expectedSourceVersion) {
402
+ throw new RuntimeHostServiceManagerError('target_mismatch', 'The installed Runtime Host package changed; check it again before updating');
403
+ }
398
404
  await deps.canonical.convergeControlProjection(current, lifecycleDeps);
399
405
  await deps.canonical.verifyProjection(current, lifecycleDeps);
400
406
  deps.canonical.assertOperatorConfig(current, options.operatorDeploymentId, process.argv[1] ?? '');
@@ -406,6 +412,21 @@ async function runCanonicalRuntimeHostUpdate(options, deps, emit) {
406
412
  cliPath: process.argv[1] ?? '',
407
413
  expectedTarget: options.expectedTarget,
408
414
  }, { resolveProvider: resolveRuntimeHostLifecycleProvider });
415
+ if (options.expectedConfigFingerprint &&
416
+ (!currentStatus.service.config ||
417
+ runtimeHostManagedServiceConfigFingerprint(currentStatus.service.config) !==
418
+ options.expectedConfigFingerprint)) {
419
+ throw new RuntimeHostServiceManagerError('target_mismatch', 'The managed Runtime Host configuration changed; check it again before updating');
420
+ }
421
+ const developmentArtifact = isRuntimeHostDevelopmentPackageVersion(options.version) ||
422
+ isRuntimeHostDevelopmentPackageVersion(current.launch.package.version);
423
+ if (developmentArtifact && !options.sourcePackageIntegrity) {
424
+ throw new RuntimeHostServiceManagerError('target_mismatch', 'Development deployments require an explicitly selected source artifact; source hashes do not establish release order');
425
+ }
426
+ if (!developmentArtifact &&
427
+ compareProductReleaseVersions(options.version, current.launch.package.version) < 0) {
428
+ throw new RuntimeHostServiceManagerError('target_mismatch', 'This update would downgrade the shared Runtime Host. Update the Client instead.');
429
+ }
409
430
  const targetIntegrity = options.sourcePackageIntegrity ??
410
431
  options.registrySelection?.integrity ??
411
432
  (options.version === current.launch.package.version
@@ -468,15 +489,38 @@ async function runCanonicalRuntimeHostUpdate(options, deps, emit) {
468
489
  },
469
490
  };
470
491
  emit(progress('retiring', current.launch.package.version, options.version));
471
- emit(progress('replacing', current.launch.package.version, options.version));
472
492
  const replacement = await deps.canonical.replaceLifecycle({
473
493
  operation: 'update',
494
+ validateRetiredState: async () => {
495
+ emit(progress('replacing', current.launch.package.version, options.version));
496
+ },
474
497
  current,
475
498
  desired,
476
499
  allowInterruptActiveTasks: options.allowInterruptActiveTasks ?? false,
477
500
  ...(options.expectedHost ? { expectedOwner: options.expectedHost } : {}),
478
501
  ...(desired.lifecycle.mode === 'on_demand'
479
502
  ? {
503
+ ...(options.expectedHost
504
+ ? {
505
+ prepareSourceRetirement: (signal) => {
506
+ if (inheritableAuthorityLeaseFd === undefined) {
507
+ throw new RuntimeHostServiceManagerError('target_mismatch', 'Source-package retirement requires the deployment authority lease');
508
+ }
509
+ return deps.retireSource({
510
+ sourceCliPath: currentCliPath,
511
+ sourceNodePath: current.launch.nodePath,
512
+ rootPath: current.root.path,
513
+ expectedRootId: current.root.id,
514
+ expectedHostEpoch: options.expectedHost.hostEpoch,
515
+ activeWorkPolicy: options.allowInterruptActiveTasks
516
+ ? 'interrupt_active_work'
517
+ : 'refuse_active_work',
518
+ inheritableAuthorityLeaseFd,
519
+ ...(signal ? { signal } : {}),
520
+ });
521
+ },
522
+ }
523
+ : {}),
480
524
  activateDesired: async () => {
481
525
  await deps.activateDesired({ rootId: options.managedRootId });
482
526
  },