maka-agent 0.2.0-dev.23.20260906 → 0.2.0-dev.24.20260907

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 (148) hide show
  1. package/dist/acp/maka-acp-agent.js +2 -1
  2. package/dist/acp/session-configuration.js +143 -0
  3. package/dist/acp/session-registry.js +90 -4
  4. package/dist/cli-core.js +2 -0
  5. package/dist/pi-transcript.js +5 -12
  6. package/dist/pi-tui-runner.js +74 -4
  7. package/dist/runtime-host-cli-context.js +167 -78
  8. package/dist/runtime-host-cli-installation.js +8 -2
  9. package/dist/runtime-host-cli.js +31 -1
  10. package/dist/runtime-host-handoff-surface.js +81 -0
  11. package/dist/runtime-host-installed-update-bootstrap.js +13 -0
  12. package/dist/runtime-host-installed-update-coordinator.js +33 -0
  13. package/dist/runtime-host-lifecycle-transaction.js +9 -1
  14. package/dist/runtime-host-local-handoff.js +37 -4
  15. package/dist/runtime-host-local-managed-activation.js +84 -0
  16. package/dist/runtime-host-local-source-retirement.js +18 -2
  17. package/dist/runtime-host-managed-lifecycle-manager.js +1 -0
  18. package/dist/runtime-host-operator-retirement.js +50 -0
  19. package/dist/runtime-host-service-manager.js +3 -0
  20. package/dist/runtime-host-session-driver.js +4 -25
  21. package/dist/runtime-host-session-update.js +62 -0
  22. package/dist/runtime-host-tui-command.js +38 -73
  23. package/dist/runtime-host-tui-context.js +1 -0
  24. package/dist/runtime-host-tui-owner.js +226 -0
  25. package/dist/tui-copy-catalog.js +140 -3
  26. package/dist/tui-host-owner-copy.js +24 -0
  27. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  28. package/node_modules/@maka/core/dist/canonical-runtime-event.js +3 -1
  29. package/node_modules/@maka/core/dist/model-failure.js +40 -0
  30. package/node_modules/@maka/core/dist/model-metadata.generated.js +4 -4
  31. package/node_modules/@maka/core/dist/model-metadata.js +3 -3
  32. package/node_modules/@maka/core/dist/model-web-search.js +5 -4
  33. package/node_modules/@maka/core/dist/provider-registry.js +45 -4
  34. package/node_modules/@maka/core/dist/record-schema.js +9 -2
  35. package/node_modules/@maka/core/dist/runtime-boundary.js +31 -11
  36. package/node_modules/@maka/core/dist/runtime-event.js +37 -6
  37. package/node_modules/@maka/core/dist/runtime-handoff.js +98 -0
  38. package/node_modules/@maka/core/dist/runtime-invocation.js +1 -1
  39. package/node_modules/@maka/core/dist/runtime-logical-execution.js +112 -0
  40. package/node_modules/@maka/core/dist/session.js +54 -52
  41. package/node_modules/@maka/core/dist/slash-command-catalog.js +2 -0
  42. package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +20 -0
  43. package/node_modules/@maka/core/dist/usage-record-schema.js +26 -35
  44. package/node_modules/@maka/core/package.json +4 -0
  45. package/node_modules/@maka/mcp/dist/index.js +131 -1
  46. package/node_modules/@maka/runtime/dist/agent-run.js +272 -105
  47. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +13 -5
  48. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +109 -34
  49. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +127 -133
  50. package/node_modules/@maka/runtime/dist/context-budget.js +14 -9
  51. package/node_modules/@maka/runtime/dist/conversation-copy.js +5 -4
  52. package/node_modules/@maka/runtime/dist/goal-continuation.js +36 -4
  53. package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +18 -9
  54. package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +30 -22
  55. package/node_modules/@maka/runtime/dist/history-compaction.js +29 -4
  56. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +106 -0
  57. package/node_modules/@maka/runtime/dist/message-authority.js +9 -0
  58. package/node_modules/@maka/runtime/dist/model-adapter.js +19 -98
  59. package/node_modules/@maka/runtime/dist/model-factory.js +19 -81
  60. package/node_modules/@maka/runtime/dist/model-fetcher.js +7 -0
  61. package/node_modules/@maka/runtime/dist/model-runtime.js +85 -111
  62. package/node_modules/@maka/runtime/dist/native-web-search-tool.js +2 -1
  63. package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +2 -2
  64. package/node_modules/@maka/runtime/dist/opencode-session-header.js +4 -2
  65. package/node_modules/@maka/runtime/dist/provider-error-classification.js +84 -123
  66. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +68 -108
  67. package/node_modules/@maka/runtime/dist/run-handoff-gate.js +97 -0
  68. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +82 -54
  69. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +117 -159
  70. package/node_modules/@maka/runtime/dist/runtime-kernel.js +150 -112
  71. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +160 -79
  72. package/node_modules/@maka/runtime/dist/runtime-read-model.js +30 -89
  73. package/node_modules/@maka/runtime/dist/runtime-resume.js +80 -4
  74. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +5 -0
  75. package/node_modules/@maka/runtime/dist/session-manager.js +171 -288
  76. package/node_modules/@maka/runtime/dist/session-projection-helpers.js +0 -27
  77. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +3 -4
  78. package/node_modules/@maka/runtime/dist/test-connection.js +16 -16
  79. package/node_modules/@maka/runtime/dist/tool-result-archive-encoding.js +38 -0
  80. package/node_modules/@maka/runtime/dist/tool-runtime.js +3 -61
  81. package/node_modules/@maka/runtime/package.json +1 -0
  82. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +35 -0
  83. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +9 -1
  84. package/node_modules/@maka/runtime-host/dist/client/connection.js +3 -0
  85. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +156 -0
  86. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +273 -0
  87. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +26 -5
  88. package/node_modules/@maka/runtime-host/dist/client/index.js +2 -0
  89. package/node_modules/@maka/runtime-host/dist/client/launcher.js +3 -0
  90. package/node_modules/@maka/runtime-host/dist/client/remote-compatibility-error.js +2 -0
  91. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +27 -5
  92. package/node_modules/@maka/runtime-host/dist/operator/index.js +2 -1
  93. package/node_modules/@maka/runtime-host/dist/operator/retirement-cancellation.js +20 -0
  94. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +21 -12
  95. package/node_modules/@maka/runtime-host/dist/protocol/index.js +25 -1
  96. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  97. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +40 -20
  98. package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +0 -5
  99. package/node_modules/@maka/runtime-host/dist/protocol/session-turns.js +29 -58
  100. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +11 -1
  101. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +1 -0
  102. package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +19 -4
  103. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +7 -6
  104. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +100 -24
  105. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +5 -5
  106. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +3 -2
  107. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +40 -1
  108. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +96 -17
  109. package/node_modules/@maka/runtime-host/dist/server/host-residency-registry.js +13 -7
  110. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-admission-registry.js +3 -1
  111. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +124 -62
  112. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +76 -6
  113. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +325 -73
  114. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +37 -2
  115. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +45 -3
  116. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +50 -7
  117. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +87 -25
  118. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +8 -55
  119. package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +1 -16
  120. package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +0 -1
  121. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +460 -70
  122. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +1 -1
  123. package/node_modules/@maka/runtime-host/dist/server/storage-maintenance.js +109 -0
  124. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +0 -1
  125. package/node_modules/@maka/storage/dist/artifact-store.js +21 -10
  126. package/node_modules/@maka/storage/dist/artifact-stores.js +1 -1
  127. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -4
  128. package/node_modules/@maka/storage/dist/codex-session-adapter.js +0 -2
  129. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +325 -0
  130. package/node_modules/@maka/storage/dist/execution-stores.js +9 -7
  131. package/node_modules/@maka/storage/dist/file-session-repository.js +1012 -0
  132. package/node_modules/@maka/storage/dist/opencode-session-adapter.js +0 -4
  133. package/node_modules/@maka/storage/dist/operational-state-backup.js +9 -5
  134. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  135. package/node_modules/@maka/storage/dist/runtime-transcript-query.js +308 -0
  136. package/node_modules/@maka/storage/dist/session-bundle-policy.js +24 -4
  137. package/node_modules/@maka/storage/dist/session-message-projection.js +15 -0
  138. package/node_modules/@maka/storage/dist/session-repository.js +767 -0
  139. package/node_modules/@maka/storage/dist/session-store.js +25 -99
  140. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +10 -3
  141. package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +24 -8
  142. package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +7 -1
  143. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +44 -1
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +129 -25
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +109 -892
  146. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +111 -0
  147. package/node_modules/@maka/storage/package.json +2 -0
  148. package/package.json +1 -1
@@ -20,7 +20,8 @@ import { createHash, randomUUID } from 'node:crypto';
20
20
  import { realpath, stat } from 'node:fs/promises';
21
21
  import { homedir } from 'node:os';
22
22
  import { dirname, join, posix, resolve, win32 } from 'node:path';
23
- import { claimLocalHostProcessDeployment, handoffLocalHostProcessDeployment, readLocalHostDeploymentRecord, } from '@maka/runtime-host/operator';
23
+ import { isDeepStrictEqual } from 'node:util';
24
+ import { claimLocalHostProcessDeployment, handoffLocalHostProcessDeployment, readLocalHostDeploymentRecord, resolveRuntimeHostManagedDeploymentAuthority, } from '@maka/runtime-host/operator';
24
25
  import { compareProductReleaseVersions } from '@maka/runtime-host/operator/update-package-evidence';
25
26
  import { connectExistingRuntimeHost } from '@maka/runtime-host/client';
26
27
  import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
@@ -53,6 +54,7 @@ export class RuntimeHostLocalHandoffError extends Error {
53
54
  * retain only the bounded true-idle takeover adapter.
54
55
  */
55
56
  export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOptions = {}, overrides = {}) {
57
+ input.signal?.throwIfAborted();
56
58
  if (input.registration.lifecycleMode !== 'ephemeral') {
57
59
  return {
58
60
  kind: 'operator_required',
@@ -64,6 +66,7 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
64
66
  withPackage: withRuntimeHostRegistryUpdatePackage,
65
67
  prepareDeployment: prepareRuntimeHostPackageDeployment,
66
68
  readRecord: readLocalHostDeploymentRecord,
69
+ resolveManagedAuthority: resolveRuntimeHostManagedDeploymentAuthority,
67
70
  claim: claimLocalHostProcessDeployment,
68
71
  handoff: handoffLocalHostProcessDeployment,
69
72
  resolveCandidate: resolveRuntimeHostRegistryUpdateCandidate,
@@ -72,7 +75,13 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
72
75
  retireSource: launchRuntimeHostLocalSourceRetirement,
73
76
  ...overrides,
74
77
  };
78
+ if (await deps.resolveManagedAuthority(input.registration.rootId)) {
79
+ return { kind: 'operator_required', reason: 'unowned_host' };
80
+ }
75
81
  const installation = await deps.resolveInstallation(input.installationOptions);
82
+ if (input.expectedInstallation && !isDeepStrictEqual(installation, input.expectedInstallation)) {
83
+ return { kind: 'changed' };
84
+ }
76
85
  const current = await deps.readRecord(input.registration.rootId, authorityOptions);
77
86
  const sourceOwner = current?.state.kind === 'handoff' ? current.state.from : current?.state.owner;
78
87
  if (sourceOwner && !sameOwner(sourceOwner, installation.owner)) {
@@ -130,9 +139,13 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
130
139
  return 'target_present';
131
140
  };
132
141
  const prepare = async (rootId, selectedDeployment, stagedTarget, inheritableAuthorityLeaseFd) => {
142
+ input.signal?.throwIfAborted();
133
143
  if (rootId !== input.registration.rootId) {
134
144
  throw new RuntimeHostLocalHandoffError('root_changed', 'The local Runtime Host State Root changed before restart');
135
145
  }
146
+ if (await deps.resolveManagedAuthority(rootId)) {
147
+ throw new Error('The managed Runtime Host requires its operator to perform replacement');
148
+ }
136
149
  const observed = await deps.connectExisting({
137
150
  rootPath: input.rootPath,
138
151
  protocol: { min: RUNTIME_HOST_PROTOCOL_VERSION, max: RUNTIME_HOST_PROTOCOL_VERSION },
@@ -179,6 +192,7 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
179
192
  expectedHostEpoch: registration.hostEpoch,
180
193
  activeWorkPolicy,
181
194
  inheritableAuthorityLeaseFd,
195
+ ...(input.signal ? { signal: input.signal } : {}),
182
196
  });
183
197
  if (retired === 'active_work')
184
198
  return { kind: 'active_work' };
@@ -286,6 +300,12 @@ export async function reconcilePreparedRuntimeHostNpmGlobalDeployment(request, l
286
300
  return request.staged;
287
301
  };
288
302
  const current = await overrides.readRecord(request.rootId, authorityOptions);
303
+ if (request.expectedOwner &&
304
+ (current?.state.kind !== 'owned' ||
305
+ current.revision !== request.expectedOwner.revision ||
306
+ !sameOwner(current.state.owner, request.expectedOwner.owner))) {
307
+ throw new RuntimeHostLocalHandoffError('source_owner_mismatch', 'The observed local Host owner changed before update reconciliation');
308
+ }
289
309
  if (!current) {
290
310
  return overrides.claim({
291
311
  rootId: request.rootId,
@@ -297,9 +317,12 @@ export async function reconcilePreparedRuntimeHostNpmGlobalDeployment(request, l
297
317
  }
298
318
  return overrides.handoff({
299
319
  rootId: request.rootId,
300
- expectedRevision: current.revision,
320
+ // The same captured CAS token is checked again by begin_handoff under
321
+ // the authority lock. Never substitute a newly observed owner revision.
322
+ expectedRevision: request.expectedOwner?.revision ?? current.revision,
301
323
  transactionId: current.state.kind === 'handoff' ? current.state.transactionId : request.transactionId,
302
- from: current.state.kind === 'handoff' ? current.state.from : current.state.owner,
324
+ from: request.expectedOwner?.owner ??
325
+ (current.state.kind === 'handoff' ? current.state.from : current.state.owner),
303
326
  to: request.installation.owner,
304
327
  target: request.target,
305
328
  activeWorkPolicy: request.activeWorkPolicy,
@@ -331,7 +354,7 @@ export async function prepareRuntimeHostNpmGlobalStagedDeployment(input, pathOpt
331
354
  launchGeneration: launchGeneration(input.transactionId, input.target),
332
355
  };
333
356
  }
334
- async function openRuntimeHostNpmGlobalStagedDeployment(input, pathOptions = {}) {
357
+ export async function openRuntimeHostNpmGlobalStagedDeployment(input, pathOptions = {}) {
335
358
  const deploymentRoot = resolveRuntimeHostLocalCliDeploymentRoot(input.rootId, input.owner, pathOptions);
336
359
  const staged = await openRuntimeHostPackageDeployment({
337
360
  deploymentRoot,
@@ -379,6 +402,16 @@ async function requireCandidateEntrypoint(packageRoot) {
379
402
  }
380
403
  return candidate;
381
404
  }
405
+ /** Read-only capability check against the same verified source package the transaction uses. */
406
+ export async function runtimeHostNpmGlobalSourceRetirementAvailable(input) {
407
+ const source = await openRuntimeHostNpmGlobalStagedDeployment({
408
+ rootId: input.rootId,
409
+ owner: input.owner,
410
+ target: input.source,
411
+ transactionId: 'source-capability-probe',
412
+ }, input.deploymentPathOptions);
413
+ return hasSourceRetirementHelper(source);
414
+ }
382
415
  async function hasSourceRetirementHelper(source) {
383
416
  try {
384
417
  return (await stat(join(dirname(source.cliPath), 'runtime-host-local-source-retirement.js'))).isFile();
@@ -0,0 +1,84 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { execFile } from 'node:child_process';
20
+ import { access } from 'node:fs/promises';
21
+ import { join } from 'node:path';
22
+ import { promisify } from 'node:util';
23
+ import { resolveStorageRoot } from '@maka/storage/root-authority';
24
+ import { runtimeHostStartupError } from '@maka/runtime-host/client';
25
+ import { decodeRuntimeHostActivationFrame, readRuntimeHostManagedDeploymentConfig, RUNTIME_HOST_ACTIVATION_FRAME_MAX_BYTES, runtimeHostManagedOperatorCommand, runtimeHostOperatorInvocation, } from '@maka/runtime-host/operator';
26
+ const run = promisify(execFile);
27
+ /** Invoke the installed owner; never launch the calling CLI's candidate as the owner. */
28
+ export async function activateLocalManagedRuntimeHost(input) {
29
+ input.signal?.throwIfAborted();
30
+ const capability = await resolveStorageRoot({ path: input.rootPath, kind: 'interactive' });
31
+ const config = await readRuntimeHostManagedDeploymentConfig(capability);
32
+ if (!config || config.lifecycle.mode !== 'on_demand') {
33
+ throw runtimeHostStartupError('managed_root_requires_operator');
34
+ }
35
+ const operator = runtimeHostManagedOperatorCommand(config, process.platform === 'win32' ? 'win32' : 'posix');
36
+ let invocation = runtimeHostOperatorInvocation(operator, [
37
+ 'activate',
38
+ '--framed',
39
+ '--root-id',
40
+ capability.rootId,
41
+ ]);
42
+ // Older installations have only the stable POSIX operator executable.
43
+ try {
44
+ await access(operator.modulePath);
45
+ }
46
+ catch (error) {
47
+ if (error.code !== 'ENOENT' || process.platform === 'win32') {
48
+ throw error;
49
+ }
50
+ invocation = {
51
+ executable: join(config.deploymentRoot, 'operator'),
52
+ args: invocation.args.slice(1),
53
+ };
54
+ }
55
+ const result = await run(invocation.executable, [...invocation.args], {
56
+ encoding: 'utf8',
57
+ timeout: 120_000,
58
+ killSignal: 'SIGKILL',
59
+ maxBuffer: RUNTIME_HOST_ACTIVATION_FRAME_MAX_BYTES + 256,
60
+ ...(input.signal ? { signal: input.signal } : {}),
61
+ }).catch((error) => {
62
+ input.signal?.throwIfAborted();
63
+ // Operators report a framed diagnostic and exit nonzero on expected failures.
64
+ if (error instanceof Error && 'stdout' in error && typeof error.stdout === 'string') {
65
+ const frame = decodeSingleActivationFrame(error.stdout);
66
+ if (frame?.kind === 'error')
67
+ throw new Error(frame.error.message, { cause: error });
68
+ }
69
+ throw error;
70
+ });
71
+ input.signal?.throwIfAborted();
72
+ const frame = decodeSingleActivationFrame(result.stdout);
73
+ if (!frame)
74
+ throw new Error('The installed Runtime Host operator returned an invalid activation result');
75
+ if (frame.kind === 'error')
76
+ throw new Error(frame.error.message);
77
+ if (frame.rootId !== capability.rootId || frame.deploymentId !== config.deploymentId) {
78
+ throw new Error('The installed Runtime Host operator activated a different deployment');
79
+ }
80
+ }
81
+ function decodeSingleActivationFrame(stdout) {
82
+ const line = stdout.replace(/\r?\n$/u, '');
83
+ return /[\r\n]/u.test(line) ? undefined : decodeRuntimeHostActivationFrame(line);
84
+ }
@@ -58,6 +58,7 @@ export async function runRuntimeHostLocalSourceRetirement(input, overrides = {})
58
58
  }
59
59
  }
60
60
  export function launchRuntimeHostLocalSourceRetirement(input) {
61
+ input.signal?.throwIfAborted();
61
62
  const args = [
62
63
  input.sourceCliPath,
63
64
  'runtime-host',
@@ -81,9 +82,24 @@ export function launchRuntimeHostLocalSourceRetirement(input) {
81
82
  killSignal: 'SIGKILL',
82
83
  windowsHide: false,
83
84
  });
84
- child.once('error', reject);
85
+ // Only this retirement client is stopped, never the parent that owns the
86
+ // durable deployment transaction. Wait for its fd/connection to close.
87
+ const cancel = () => {
88
+ child.kill('SIGKILL');
89
+ };
90
+ input.signal?.addEventListener('abort', cancel, { once: true });
91
+ if (input.signal?.aborted)
92
+ cancel();
93
+ let spawnError;
94
+ child.once('error', (error) => {
95
+ spawnError = error;
96
+ });
85
97
  child.once('close', (code, signal) => {
86
- if (signal) {
98
+ input.signal?.removeEventListener('abort', cancel);
99
+ if (spawnError) {
100
+ reject(spawnError);
101
+ }
102
+ else if (signal) {
87
103
  reject(new Error(`Maka source retirement helper exited on ${signal}`));
88
104
  }
89
105
  else if (code === 0) {
@@ -77,6 +77,7 @@ export async function manageRuntimeHostManagedLifecycle(rootId, input, dependenc
77
77
  }
78
78
  if (input.action === 'restart') {
79
79
  const retirement = await retireRuntimeHostLifecycleOwner({
80
+ ...(input.expectedHost ? { expectedOwner: input.expectedHost } : {}),
80
81
  rootPath: config.root.path,
81
82
  rootId,
82
83
  supervisor: provider.supervisor,
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { RUNTIME_HOST_OPERATOR_RETIREMENT_CANCELLATION_ENV } from '@maka/runtime-host/operator';
20
+ /** Consume the operator's one-shot cancellation channel only around reversible retirement. */
21
+ export function withOperatorRetirementCancellation(operation) {
22
+ if (process.env[RUNTIME_HOST_OPERATOR_RETIREMENT_CANCELLATION_ENV] !== '1') {
23
+ return operation(undefined);
24
+ }
25
+ // Recovery may retire another owner later. It must finish independently of
26
+ // the original user's cancellation, and activators must not inherit this channel.
27
+ delete process.env[RUNTIME_HOST_OPERATOR_RETIREMENT_CANCELLATION_ENV];
28
+ return withRetirementCancellation(process.stdin, operation);
29
+ }
30
+ export async function withRetirementCancellation(input, operation) {
31
+ const controller = new AbortController();
32
+ const cancel = () => controller.abort(new Error('Runtime Host retirement was cancelled'));
33
+ input.once('end', cancel);
34
+ input.once('error', cancel);
35
+ input.once('close', cancel);
36
+ input.resume();
37
+ if (input.readableEnded || input.destroyed)
38
+ cancel();
39
+ try {
40
+ return await operation(controller.signal);
41
+ }
42
+ finally {
43
+ input.removeListener('end', cancel);
44
+ input.removeListener('error', cancel);
45
+ input.removeListener('close', cancel);
46
+ input.pause();
47
+ // A parent's still-open cancellation pipe must not keep the completed CLI alive.
48
+ input.unref?.();
49
+ }
50
+ }
@@ -59,6 +59,9 @@ function runtimeHostServiceManagerDeps(overrides) {
59
59
  };
60
60
  }
61
61
  export async function manageRuntimeHostService(input, backend, overrides = {}) {
62
+ if (input.expectedHost) {
63
+ throw new RuntimeHostServiceManagerError('target_mismatch', 'An exact Host fence requires the canonical managed deployment operator');
64
+ }
62
65
  const deps = runtimeHostServiceManagerDeps(overrides);
63
66
  const configPath = resolveRuntimeHostManagedServiceConfigPath(input.clientDataRoot);
64
67
  const configDirectory = dirname(configPath);
@@ -30,6 +30,7 @@ import { isRuntimeHostTerminalTurn as isTerminalTurn } from '@maka/runtime-host/
30
30
  import { projectSessionCatalogSummary, readRuntimeHostResources, readRuntimeHostSessions, RuntimeHostOperationError, RuntimeHostRequestInterruptedError, } from '@maka/runtime-host/client';
31
31
  import { SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES, } from '@maka/runtime-host/protocol';
32
32
  import { RuntimeHostSessionChannel } from './runtime-host-session-channel.js';
33
+ import { getRuntimeHostSession, requireRuntimeHostSessionProjection as requireSession, updateRuntimeHostSession, } from './runtime-host-session-update.js';
33
34
  import { inspectSessionResumeAvailability, skillInvocationBlockedMessage, } from './session-driver.js';
34
35
  import { cwdRank, firstLine, inspectGitCwdChanges, resolveMoveCwd, } from './session-driver-policy.js';
35
36
  const decodeStoredMessage = (value) => decodePersistedStoredMessage(markPersisted(value));
@@ -502,7 +503,7 @@ class RuntimeHostMakaSessionDriverImpl {
502
503
  sessionId,
503
504
  expectedRevision: current.revision,
504
505
  patch: { name },
505
- }));
506
+ }), { operation: 'session.metadata.update' });
506
507
  return session.name;
507
508
  }
508
509
  async #moveSession(rawCwd) {
@@ -602,7 +603,7 @@ class RuntimeHostMakaSessionDriverImpl {
602
603
  sessionId,
603
604
  expectedRevision: current.revision,
604
605
  workspace: { kind: 'host_path', path: cwd },
605
- }));
606
+ }), { operation: 'session.workspace.relocate' });
606
607
  }
607
608
  async listRewindTargets() {
608
609
  if (!this.#sessionId)
@@ -1073,7 +1074,7 @@ class RuntimeHostMakaSessionDriverImpl {
1073
1074
  sessionId,
1074
1075
  expectedRevision: current.revision,
1075
1076
  patch,
1076
- }));
1077
+ }), { operation: 'session.configuration.update' });
1077
1078
  }
1078
1079
  #adoptConfiguration(session) {
1079
1080
  this.#model = session.model;
@@ -1369,12 +1370,6 @@ function workspaceTargetForCreate(current, input, location) {
1369
1370
  }
1370
1371
  return current.target;
1371
1372
  }
1372
- async function getRuntimeHostSession(connection, sessionId) {
1373
- const result = await connection.request('session.catalog.query', { kind: 'get', sessionId });
1374
- if (result.kind !== 'session')
1375
- throw new Error('Runtime Host returned an invalid Session lookup');
1376
- return result.session === null ? null : requireSession(result.session);
1377
- }
1378
1373
  function representableSession(item) {
1379
1374
  return 'kind' in item ? [] : [item];
1380
1375
  }
@@ -1426,11 +1421,6 @@ function visibleTranscriptMessages(messages, hiddenThroughTurnId) {
1426
1421
  }
1427
1422
  return boundary < 0 ? messages : messages.slice(boundary + 1);
1428
1423
  }
1429
- function requireSession(item) {
1430
- if (!('kind' in item))
1431
- return item;
1432
- throw new Error(`Runtime Host Session is not representable by this CLI: ${item.id}`);
1433
- }
1434
1424
  function inspectRuntimeHostSessionResumeAvailability(summary, location) {
1435
1425
  if (!summary.cwd) {
1436
1426
  return Promise.resolve({ available: false, reason: 'Missing working directory' });
@@ -1445,17 +1435,6 @@ async function assertSessionResumeAvailable(summary, location) {
1445
1435
  throw new Error(summary.cwd ? `Session cwd no longer exists: ${summary.cwd}` : availability.reason);
1446
1436
  }
1447
1437
  }
1448
- async function updateRuntimeHostSession(connection, sessionId, update) {
1449
- for (let attempt = 0; attempt < MAX_CATALOG_ATTEMPTS; attempt += 1) {
1450
- const current = await getRuntimeHostSession(connection, sessionId);
1451
- if (!current)
1452
- throw new Error(`Session not found: ${sessionId}`);
1453
- const result = await update(current);
1454
- if (result.kind === 'committed')
1455
- return requireSession(result.session);
1456
- }
1457
- throw new Error(`Session kept changing while updating: ${sessionId}`);
1458
- }
1459
1438
  async function loadCurrentMessages(connection, sessionId) {
1460
1439
  const subscription = await connection.openSessionSubscription({
1461
1440
  sessionId,
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ const MAX_UPDATE_ATTEMPTS = 3;
20
+ export class RuntimeHostSessionUpdateError extends Error {
21
+ operation;
22
+ reason;
23
+ sessionId;
24
+ attempts;
25
+ name = 'RuntimeHostSessionUpdateError';
26
+ constructor(operation, reason, sessionId, attempts) {
27
+ super(`Runtime Host Session update failed for session ${sessionId}: ${reason}`);
28
+ this.operation = operation;
29
+ this.reason = reason;
30
+ this.sessionId = sessionId;
31
+ this.attempts = attempts;
32
+ }
33
+ }
34
+ export async function getRuntimeHostSession(connection, sessionId) {
35
+ const result = await connection.request('session.catalog.query', { kind: 'get', sessionId });
36
+ if (result.kind !== 'session') {
37
+ throw new RuntimeHostSessionUpdateError('session.catalog.query', 'invalid_projection', sessionId);
38
+ }
39
+ return result.session === null
40
+ ? null
41
+ : requireRuntimeHostSessionProjection(result.session, 'session.catalog.query');
42
+ }
43
+ export async function updateRuntimeHostSession(connection, sessionId, update, options) {
44
+ for (let attempt = 0; attempt < MAX_UPDATE_ATTEMPTS; attempt += 1) {
45
+ options.assertRequestAllowed?.();
46
+ const current = await getRuntimeHostSession(connection, sessionId);
47
+ if (!current) {
48
+ throw new RuntimeHostSessionUpdateError('session.catalog.query', 'not_found', sessionId);
49
+ }
50
+ options.assertRequestAllowed?.();
51
+ const result = await update(current);
52
+ if (result.kind === 'committed') {
53
+ return requireRuntimeHostSessionProjection(result.session, options.operation);
54
+ }
55
+ }
56
+ throw new RuntimeHostSessionUpdateError(options.operation, 'revision_conflict', sessionId, MAX_UPDATE_ATTEMPTS);
57
+ }
58
+ export function requireRuntimeHostSessionProjection(session, operation = 'session.catalog.query') {
59
+ if (!('kind' in session))
60
+ return session;
61
+ throw new RuntimeHostSessionUpdateError(operation, 'unsupported_session_projection', session.id);
62
+ }
@@ -17,21 +17,23 @@
17
17
  * under the License.
18
18
  */
19
19
  import { parseNoRealConnectionError } from '@maka/core/connection-error-copy';
20
- import { createInterface } from 'node:readline/promises';
21
20
  import { SessionActivityRegistry } from '@maka/runtime/goal-turn-lifecycle';
22
- import { readRuntimeHostConnectionCatalog } from '@maka/runtime-host/client';
21
+ import { readRuntimeHostConnectionCatalog, HostHandoffCancelledError, } from '@maka/runtime-host/client';
23
22
  import { runtimeHostProfileUsesHostWorkspace } from '@maka/runtime-host/profile-kind';
24
23
  import { createForeignSessionStore } from '@maka/storage/foreign-session-store';
25
24
  import { formatMakaResumeHint } from './cli-invocation.js';
26
- import { connectRuntimeHostCli, resolveRuntimeHostCliConflictDecision, RuntimeHostCliConflictError, } from './runtime-host-cli-context.js';
27
- import { resolveRuntimeHostNpmGlobalInstallation } from './runtime-host-cli-installation.js';
28
- import { restartRuntimeHostNpmGlobalDeployment } from './runtime-host-local-handoff.js';
25
+ import { connectRuntimeHostCli } from './runtime-host-cli-context.js';
26
+ import { createCliHostHandoffSurface } from './runtime-host-handoff-surface.js';
29
27
  import { createRuntimeHostOnboardingSurface } from './runtime-host-onboarding.js';
30
28
  import { runMakaPiTui } from './pi-tui-runner.js';
31
29
  import { createRuntimeHostTuiContext } from './runtime-host-tui-context.js';
30
+ import { describeTuiHost, prepareTuiHostOwnerAction } from './runtime-host-tui-owner.js';
31
+ import { getTuiHostOwnerCopy } from './tui-host-owner-copy.js';
32
32
  export async function runRuntimeHostTui(input) {
33
+ const ownerCopy = getTuiHostOwnerCopy(input.locale);
33
34
  const foreignSessions = createForeignSessionStore();
34
35
  const contextInput = {
36
+ ...(process.stdin.isTTY ? { handoffSurface: createCliHostHandoffSurface(input.locale) } : {}),
35
37
  clientDataRoot: input.clientDataRoot,
36
38
  rootPath: input.workspaceRoot,
37
39
  cwd: input.cwd,
@@ -41,11 +43,11 @@ export async function runRuntimeHostTui(input) {
41
43
  };
42
44
  let context;
43
45
  try {
44
- context = await createTuiContextWithHostConflictPrompt(contextInput);
45
- if (!context)
46
- return 1;
46
+ context = await createRuntimeHostTuiContext(contextInput);
47
47
  }
48
48
  catch (error) {
49
+ if (error instanceof HostHandoffCancelledError)
50
+ return 1;
49
51
  if (!isMissingDefaultConnection(error) || input.resumeSessionId)
50
52
  throw error;
51
53
  const configured = await runFirstRunOnboarding(input.clientDataRoot, input.workspaceRoot, input.cwd, input.locale, input.hostProfileId);
@@ -53,6 +55,8 @@ export async function runRuntimeHostTui(input) {
53
55
  throw error;
54
56
  context = await createRuntimeHostTuiContext(contextInput);
55
57
  }
58
+ let ownerAction;
59
+ let contextOpen = true;
56
60
  try {
57
61
  await runMakaPiTui({
58
62
  driver: context.driver,
@@ -79,6 +83,27 @@ export async function runRuntimeHostTui(input) {
79
83
  onboarding: context.onboarding,
80
84
  ...(context.mcp ? { mcp: context.mcp } : {}),
81
85
  recap: context.recap,
86
+ hostControl: {
87
+ status: () => describeTuiHost({ ...context, locale: input.locale }),
88
+ prepare: async (action, confirm) => {
89
+ if (ownerAction || !contextOpen)
90
+ throw new Error(ownerCopy.pending);
91
+ const execute = await prepareTuiHostOwnerAction({
92
+ profile: context.profile,
93
+ connection: context.connection,
94
+ rootPath: input.workspaceRoot,
95
+ locale: input.locale,
96
+ action,
97
+ confirm,
98
+ });
99
+ if (!execute)
100
+ return false;
101
+ if (!contextOpen || ownerAction)
102
+ throw new Error(ownerCopy.disconnected);
103
+ ownerAction = execute;
104
+ return true;
105
+ },
106
+ },
82
107
  ...(runtimeHostProfileUsesHostWorkspace(context.profile.kind)
83
108
  ? {
84
109
  sessionListScope: 'all',
@@ -103,82 +128,22 @@ export async function runRuntimeHostTui(input) {
103
128
  });
104
129
  if (hint)
105
130
  process.stdout.write(`${hint}\n`);
106
- return 0;
107
131
  }
108
132
  finally {
133
+ contextOpen = false;
109
134
  await context.driver.cleanupOwnedSideConversations().catch(() => undefined);
110
135
  await context.close();
111
136
  }
112
- }
113
- async function createTuiContextWithHostConflictPrompt(input) {
114
- const blockedRestartEpochs = new Set();
115
- while (true) {
116
- try {
117
- return await createRuntimeHostTuiContext(input);
118
- }
119
- catch (error) {
120
- if (!(error instanceof RuntimeHostCliConflictError) || !process.stdin.isTTY)
121
- throw error;
122
- process.stderr.write(`${error.message}\n`);
123
- const canRestart = error.registration.lifecycleMode === 'ephemeral' &&
124
- !blockedRestartEpochs.has(error.registration.hostEpoch) &&
125
- (await isPersistentNpmGlobalCli());
126
- const readline = createInterface({ input: process.stdin, output: process.stderr });
127
- let decision;
128
- try {
129
- const answer = await readline.question(canRestart
130
- ? 'Restart this local Host if it is idle, wait for it to exit, or cancel? [r/w/C] '
131
- : 'Wait only if the existing Host is expected to exit, or cancel? [w/C] ');
132
- decision = resolveRuntimeHostCliConflictDecision(answer, canRestart);
133
- }
134
- finally {
135
- readline.close();
136
- }
137
- if (decision === 'cancel')
138
- return null;
139
- if (decision === 'restart') {
140
- const result = await restartRuntimeHostNpmGlobalDeployment({
141
- rootPath: input.rootPath,
142
- registration: error.registration,
143
- });
144
- if (result.kind === 'completed')
145
- continue;
146
- if (result.kind === 'active_work') {
147
- blockedRestartEpochs.add(error.registration.hostEpoch);
148
- process.stderr.write('The existing Runtime Host still owns active or durable work and was not interrupted.\n');
149
- continue;
150
- }
151
- if (result.kind === 'operator_required') {
152
- blockedRestartEpochs.add(error.registration.hostEpoch);
153
- continue;
154
- }
155
- if (result.kind === 'rejected')
156
- continue;
157
- throw new Error(`Local Runtime Host restart requires recovery at ${result.phase}`, {
158
- cause: result.cause,
159
- });
160
- }
161
- await waitForHostRetry();
162
- }
163
- }
164
- }
165
- async function isPersistentNpmGlobalCli() {
166
- try {
167
- await resolveRuntimeHostNpmGlobalInstallation();
168
- return true;
169
- }
170
- catch {
171
- return false;
172
- }
173
- }
174
- function waitForHostRetry() {
175
- return new Promise((resolve) => setTimeout(resolve, 2_000));
137
+ // No reconnecting client remains to respawn a stopped Host, and an updater
138
+ // can use the restored terminal without competing with the TUI renderer.
139
+ return ownerAction ? ownerAction() : 0;
176
140
  }
177
141
  async function runFirstRunOnboarding(clientDataRoot, rootPath, cwd, locale, hostProfileId) {
178
142
  const connected = await connectRuntimeHostCli({
179
143
  clientDataRoot,
180
144
  rootPath,
181
145
  interactiveSsh: true,
146
+ ...(process.stdin.isTTY ? { handoffSurface: createCliHostHandoffSurface(locale) } : {}),
182
147
  ...(hostProfileId ? { profileId: hostProfileId } : {}),
183
148
  });
184
149
  try {
@@ -34,6 +34,7 @@ export async function createRuntimeHostTuiContext(input) {
34
34
  clientDataRoot: input.clientDataRoot,
35
35
  rootPath: input.rootPath,
36
36
  interactiveSsh: true,
37
+ ...(input.handoffSurface ? { handoffSurface: input.handoffSurface } : {}),
37
38
  ...(input.hostProfileId ? { profileId: input.hostProfileId } : {}),
38
39
  });
39
40
  const connection = connected.connection;