maka-agent 0.2.0-dev.6.20260830 → 0.2.0-dev.9.20260831

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 (86) hide show
  1. package/dist/cli-core.js +11 -0
  2. package/dist/runtime-host-cli.js +44 -0
  3. package/dist/runtime-host-installed-update-activator.js +70 -33
  4. package/dist/runtime-host-installed-update-coordinator.js +11 -96
  5. package/dist/runtime-host-lifecycle-transaction.js +2 -0
  6. package/dist/runtime-host-local-handoff.js +169 -38
  7. package/dist/runtime-host-local-source-retirement.js +103 -0
  8. package/dist/runtime-host-local-target-activation.js +141 -0
  9. package/dist/runtime-host-peer-management-command.js +10 -4
  10. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  11. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  12. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  13. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  14. package/node_modules/@maka/core/dist/agent-run.js +0 -2
  15. package/node_modules/@maka/core/dist/artifacts.js +23 -15
  16. package/node_modules/@maka/core/dist/durable-tool-result-projection.js +124 -0
  17. package/node_modules/@maka/core/dist/foreign-session.js +1 -1
  18. package/node_modules/@maka/core/dist/llm-connections.js +20 -0
  19. package/node_modules/@maka/core/dist/runtime-event.js +28 -3
  20. package/node_modules/@maka/core/dist/session-name.js +0 -1
  21. package/node_modules/@maka/core/dist/task-ledger.js +6 -134
  22. package/node_modules/@maka/core/dist/usage-record-schema.js +1 -0
  23. package/node_modules/@maka/core/package.json +1 -0
  24. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +97 -133
  25. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +4 -18
  26. package/node_modules/@maka/runtime/dist/context-budget.js +0 -1
  27. package/node_modules/@maka/runtime/dist/conversation-copy.js +67 -0
  28. package/node_modules/@maka/runtime/dist/durable-tool-result-projection.js +390 -0
  29. package/node_modules/@maka/runtime/dist/model-history.js +12 -39
  30. package/node_modules/@maka/runtime/dist/plan-mode.js +0 -65
  31. package/node_modules/@maka/runtime/dist/recovery-resolver.js +18 -14
  32. package/node_modules/@maka/runtime/dist/run-trace.js +0 -10
  33. package/node_modules/@maka/runtime/dist/runtime-kernel.js +0 -1
  34. package/node_modules/@maka/runtime/dist/sandbox/index.js +0 -1
  35. package/node_modules/@maka/runtime/dist/sandbox/linux-sandbox.js +0 -12
  36. package/node_modules/@maka/runtime/dist/sandbox/macos-seatbelt.js +0 -12
  37. package/node_modules/@maka/runtime/dist/sandbox/sandbox-manager.js +0 -35
  38. package/node_modules/@maka/runtime/dist/sandbox/windows-sandbox.js +0 -12
  39. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +20 -11
  40. package/node_modules/@maka/runtime/dist/session-manager.js +0 -170
  41. package/node_modules/@maka/runtime/dist/shell-detect.js +3 -7
  42. package/node_modules/@maka/runtime/dist/shell-run-contract.js +0 -1
  43. package/node_modules/@maka/runtime/dist/shell-run-manager.js +1 -39
  44. package/node_modules/@maka/runtime/dist/shell-tools.js +1 -1
  45. package/node_modules/@maka/runtime/dist/task-ledger-tools.js +2 -2
  46. package/node_modules/@maka/runtime/dist/tool-runtime.js +118 -71
  47. package/node_modules/@maka/runtime/package.json +1 -1
  48. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +6 -1
  49. package/node_modules/@maka/runtime-host/dist/candidate-launch-owner-guard.js +13 -0
  50. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +4 -1
  51. package/node_modules/@maka/runtime-host/dist/client/host-profile.js +27 -1
  52. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  53. package/node_modules/@maka/runtime-host/dist/client/launcher.js +8 -2
  54. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +11 -1
  55. package/node_modules/@maka/runtime-host/dist/peer-mesh/index.js +1 -1
  56. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +394 -113
  57. package/node_modules/@maka/runtime-host/dist/peer-mesh/store.js +90 -21
  58. package/node_modules/@maka/runtime-host/dist/protocol/index.js +8 -1
  59. package/node_modules/@maka/runtime-host/dist/protocol/oauth.js +60 -7
  60. package/node_modules/@maka/runtime-host/dist/protocol/usage-pricing.js +4 -0
  61. package/node_modules/@maka/runtime-host/dist/server/artifact-coordinator.js +2 -4
  62. package/node_modules/@maka/runtime-host/dist/server/candidate.js +3 -0
  63. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +10 -7
  64. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +6 -9
  65. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +9 -7
  66. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +10 -0
  67. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +2 -58
  68. package/node_modules/@maka/runtime-host/dist/server/oauth-coordinator.js +91 -42
  69. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +10 -1
  70. package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +45 -5
  71. package/node_modules/@maka/storage/dist/artifact-attachments.js +62 -11
  72. package/node_modules/@maka/storage/dist/artifact-store.js +11 -0
  73. package/node_modules/@maka/storage/dist/artifact-stores.js +4 -1
  74. package/node_modules/@maka/storage/dist/quiescent-session-snapshot.js +1154 -0
  75. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +36 -0
  76. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +213 -46
  77. package/node_modules/@maka/storage/dist/runtime-policy/document-io.js +1 -1
  78. package/node_modules/@maka/storage/dist/runtime-policy/oauth-login-receipt-document.js +186 -0
  79. package/node_modules/@maka/storage/dist/runtime-policy/onboarding-transaction.js +120 -1
  80. package/node_modules/@maka/storage/dist/runtime-policy-stores.js +2 -1
  81. package/node_modules/@maka/storage/dist/session-bundle-ustar.js +12 -0
  82. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +5 -0
  83. package/package.json +1 -1
  84. package/node_modules/@maka/runtime/dist/sandbox/diagnostics.js +0 -285
  85. package/node_modules/@maka/runtime/dist/system-prompt/sandbox-context-prompt.js +0 -77
  86. package/node_modules/@maka/runtime/dist/system-prompt/session-environment-prompt.js +0 -47
@@ -19,14 +19,17 @@
19
19
  import { createHash, randomUUID } from 'node:crypto';
20
20
  import { realpath, stat } from 'node:fs/promises';
21
21
  import { homedir } from 'node:os';
22
- import { join, posix, resolve, win32 } from 'node:path';
22
+ import { dirname, join, posix, resolve, win32 } from 'node:path';
23
23
  import { claimLocalHostProcessDeployment, handoffLocalHostProcessDeployment, readLocalHostDeploymentRecord, } from '@maka/runtime-host/operator';
24
- import { connectOrSpawnRuntimeHost, runtimeHostStartupError } from '@maka/runtime-host/client';
24
+ import { compareProductReleaseVersions } from '@maka/runtime-host/operator/update-package-evidence';
25
+ import { connectExistingRuntimeHost } from '@maka/runtime-host/client';
25
26
  import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
26
27
  import { resolveRuntimeHostNpmGlobalInstallation, } from './runtime-host-cli-installation.js';
27
- import { prepareRuntimeHostPackageDeployment, } from './runtime-host-package-deployment.js';
28
+ import { openRuntimeHostPackageDeployment, prepareRuntimeHostPackageDeployment, resolveRuntimeHostPackageCliPath, } from './runtime-host-package-deployment.js';
28
29
  import { RuntimeHostUpdatePackageError, withRuntimeHostRegistryUpdatePackage, } from './runtime-host-update-package.js';
29
30
  import { resolveRuntimeHostRegistryUpdateCandidate } from './runtime-host-update-discovery.js';
31
+ import { launchRuntimeHostTargetActivator, } from './runtime-host-local-target-activation.js';
32
+ import { launchRuntimeHostLocalSourceRetirement } from './runtime-host-local-source-retirement.js';
30
33
  const ROOT_ID = /^[a-f0-9]{64}$/u;
31
34
  const CANDIDATE_RELATIVE_PATH = [
32
35
  'node_modules',
@@ -45,8 +48,9 @@ export class RuntimeHostLocalHandoffError extends Error {
45
48
  }
46
49
  /**
47
50
  * Explicitly restarts one local ephemeral Host from the exact artifact matching
48
- * the installed npm-global CLI. The released-Host takeover is a bounded adapter:
49
- * it can replace only the observed exact Host when that Host reports true idle.
51
+ * the installed npm-global CLI. A durable selected source may retire its exact
52
+ * Host through its own compatible client; released Hosts without that helper
53
+ * retain only the bounded true-idle takeover adapter.
50
54
  */
51
55
  export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOptions = {}, overrides = {}) {
52
56
  if (input.registration.lifecycleMode !== 'ephemeral') {
@@ -63,15 +67,21 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
63
67
  claim: claimLocalHostProcessDeployment,
64
68
  handoff: handoffLocalHostProcessDeployment,
65
69
  resolveCandidate: resolveRuntimeHostRegistryUpdateCandidate,
66
- connectOrSpawn: connectOrSpawnRuntimeHost,
70
+ connectExisting: connectExistingRuntimeHost,
71
+ activateTarget: launchRuntimeHostTargetActivator,
72
+ retireSource: launchRuntimeHostLocalSourceRetirement,
67
73
  ...overrides,
68
74
  };
69
75
  const installation = await deps.resolveInstallation(input.installationOptions);
76
+ const current = await deps.readRecord(input.registration.rootId, authorityOptions);
77
+ const sourceOwner = current?.state.kind === 'handoff' ? current.state.from : current?.state.owner;
78
+ if (sourceOwner && !sameOwner(sourceOwner, installation.owner)) {
79
+ throw new RuntimeHostLocalHandoffError('source_owner_mismatch', 'External npm replacement can reconcile only the same durable npm-global installation owner');
80
+ }
70
81
  const target = await deps.resolveCandidate({
71
82
  kind: 'exact',
72
83
  version: installation.observedRelease.version,
73
84
  });
74
- const current = await deps.readRecord(input.registration.rootId, authorityOptions);
75
85
  if (current?.state.kind === 'owned' &&
76
86
  current.state.owner.kind === installation.owner.kind &&
77
87
  current.state.owner.installationId === installation.owner.installationId &&
@@ -80,70 +90,158 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
80
90
  current.state.selected.integrity === target.integrity) {
81
91
  throw new RuntimeHostLocalHandoffError('selected_target_observation_conflict', 'The active Runtime Host conflicts with the deployment already committed for this installation');
82
92
  }
93
+ if (current &&
94
+ compareProductReleaseVersions(target.version, current.state.selected.version) < 0) {
95
+ throw new RuntimeHostLocalHandoffError('unsupported_downgrade', `Downgrading the local Runtime Host from ${current.state.selected.version} to ${target.version} is not supported`);
96
+ }
83
97
  const transactionId = restartTransactionId(input.registration.rootId, installation.owner, target);
84
- let connectedTarget;
85
- const prepare = async (rootId, staged) => {
98
+ const activeWorkPolicy = input.activeWorkPolicy ?? 'refuse_active_work';
99
+ const staged = await stageRuntimeHostNpmGlobalDeploymentTarget({
100
+ rootId: input.registration.rootId,
101
+ owner: installation.owner,
102
+ target,
103
+ transactionId,
104
+ }, input.deploymentPathOptions, deps);
105
+ const selected = current?.state.selected;
106
+ const sourceIdentity = selected && !sameDeployment(selected, target) ? selected : undefined;
107
+ const source = sourceIdentity
108
+ ? await openRuntimeHostNpmGlobalStagedDeployment({
109
+ rootId: input.registration.rootId,
110
+ owner: installation.owner,
111
+ target: sourceIdentity,
112
+ transactionId: `${transactionId}:source`,
113
+ }, input.deploymentPathOptions)
114
+ : undefined;
115
+ const sourceSupportsRetirement = source ? await hasSourceRetirementHelper(source) : false;
116
+ let targetActivator;
117
+ const activateExactTarget = async (rootId, stagedTarget, inheritableAuthorityLeaseFd, takeoverHostEpoch) => {
118
+ const activated = await deps.activateTarget({
119
+ rootPath: input.rootPath,
120
+ rootId,
121
+ staged: stagedTarget,
122
+ ownerInstallationId: installation.owner.installationId,
123
+ target,
124
+ inheritableAuthorityLeaseFd,
125
+ ...(takeoverHostEpoch ? { takeoverHostEpoch } : {}),
126
+ });
127
+ if (activated.kind !== 'ready')
128
+ return activated.kind;
129
+ targetActivator = activated;
130
+ return 'target_present';
131
+ };
132
+ const prepare = async (rootId, selectedDeployment, stagedTarget, inheritableAuthorityLeaseFd) => {
86
133
  if (rootId !== input.registration.rootId) {
87
134
  throw new RuntimeHostLocalHandoffError('root_changed', 'The local Runtime Host State Root changed before restart');
88
135
  }
89
- const result = await deps.connectOrSpawn({
136
+ const observed = await deps.connectExisting({
90
137
  rootPath: input.rootPath,
91
138
  protocol: { min: RUNTIME_HOST_PROTOCOL_VERSION, max: RUNTIME_HOST_PROTOCOL_VERSION },
92
139
  compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
93
- generation: staged.launchGeneration,
94
- takeoverHostEpoch: input.registration.hostEpoch,
95
140
  clientInstanceId: randomUUID(),
96
- candidateEntrypoint: staged.candidateEntrypoint,
97
141
  });
98
- if (result.kind === 'connected') {
99
- if (result.registration.rootId !== rootId ||
100
- result.registration.generation !== staged.launchGeneration ||
101
- (result.spawnedProcess !== undefined &&
102
- result.spawnedProcess.pid !== result.registration.pid)) {
103
- await result.connection.close().catch(() => undefined);
104
- throw new Error('The restarted Runtime Host does not match the exact staged process');
142
+ const registration = observed.registration;
143
+ if (registration?.rootId !== undefined && registration.rootId !== rootId) {
144
+ if (observed.kind === 'connected')
145
+ await observed.connection.close().catch(() => undefined);
146
+ throw new RuntimeHostLocalHandoffError('root_changed', 'The local Runtime Host State Root changed during restart');
147
+ }
148
+ if (observed.kind === 'connected')
149
+ await observed.connection.close();
150
+ if (!registration) {
151
+ const activated = await activateExactTarget(rootId, stagedTarget, inheritableAuthorityLeaseFd);
152
+ if (activated !== 'target_present') {
153
+ throw new Error('The exact target could not start after the source Host disappeared');
105
154
  }
106
- connectedTarget = result;
107
155
  return { kind: 'target_present' };
108
156
  }
109
- if ((result.kind === 'incompatible' || result.kind === 'upgrade_required') &&
110
- result.registration.hostEpoch === input.registration.hostEpoch) {
111
- return { kind: 'active_work' };
157
+ if (registration.lifecycleMode !== 'ephemeral') {
158
+ throw new Error('The observed Runtime Host requires its operator to perform replacement');
159
+ }
160
+ if (registration.generation === stagedTarget.launchGeneration) {
161
+ const activated = await activateExactTarget(rootId, stagedTarget, inheritableAuthorityLeaseFd);
162
+ if (activated !== 'target_present') {
163
+ throw new Error('The exact staged Runtime Host could not be reattached for recovery');
164
+ }
165
+ return { kind: 'target_present' };
166
+ }
167
+ if (registration.hostEpoch !== input.registration.hostEpoch) {
168
+ throw new Error('The observed Runtime Host changed after restart confirmation');
112
169
  }
113
- if (result.kind === 'failed') {
114
- throw runtimeHostStartupError(result.reason, result.diagnostic);
170
+ if (source &&
171
+ sourceSupportsRetirement &&
172
+ selectedDeployment &&
173
+ sourceIdentity &&
174
+ sameDeployment(selectedDeployment, sourceIdentity)) {
175
+ const retired = await deps.retireSource({
176
+ sourceCliPath: source.cliPath,
177
+ rootPath: input.rootPath,
178
+ expectedRootId: rootId,
179
+ expectedHostEpoch: registration.hostEpoch,
180
+ activeWorkPolicy,
181
+ inheritableAuthorityLeaseFd,
182
+ });
183
+ if (retired === 'active_work')
184
+ return { kind: 'active_work' };
185
+ if (retired === 'operator_required') {
186
+ throw new Error('The observed Runtime Host requires its operator to perform replacement');
187
+ }
188
+ const activated = await activateExactTarget(rootId, stagedTarget, inheritableAuthorityLeaseFd, registration.hostEpoch);
189
+ if (activated !== 'target_present') {
190
+ throw new Error('The exact target did not activate after source retirement began');
191
+ }
192
+ return { kind: 'target_present' };
193
+ }
194
+ const activated = await activateExactTarget(rootId, stagedTarget, inheritableAuthorityLeaseFd, registration.hostEpoch);
195
+ if (activated === 'active_work')
196
+ return { kind: 'active_work' };
197
+ if (activated === 'operator_required') {
198
+ throw new Error('The observed Runtime Host requires its operator to perform replacement');
115
199
  }
116
- throw new Error('The observed Runtime Host changed before exact local restart completed');
200
+ return { kind: 'target_present' };
117
201
  };
118
202
  const unreachable = async () => {
119
- throw new Error('Legacy local restart must converge during exact takeover');
203
+ throw new Error('Local restart must converge through its exact target activator');
120
204
  };
205
+ let result;
121
206
  try {
122
- return await reconcileRuntimeHostNpmGlobalDeployment({
207
+ result = await reconcilePreparedRuntimeHostNpmGlobalDeployment({
123
208
  rootId: input.registration.rootId,
124
209
  transactionId,
125
210
  target,
126
- activeWorkPolicy: 'refuse_active_work',
127
- ...(input.installationOptions ? { installationOptions: input.installationOptions } : {}),
211
+ activeWorkPolicy,
212
+ installation,
213
+ staged,
128
214
  ...(input.deploymentPathOptions
129
215
  ? { deploymentPathOptions: input.deploymentPathOptions }
130
216
  : {}),
131
217
  }, {
132
- prepareUnownedHostCutover: (rootId, _target, staged) => prepare(rootId, staged),
133
- prepareHostCutover: (rootId, _selected, _target, staged) => prepare(rootId, staged),
218
+ prepareUnownedHostCutover: (rootId, _target, stagedTarget, _policy, leaseFd) => prepare(rootId, undefined, stagedTarget, leaseFd),
219
+ prepareHostCutover: (rootId, selectedDeployment, _target, stagedTarget, _policy, leaseFd) => prepare(rootId, selectedDeployment, stagedTarget, leaseFd),
134
220
  observeWriterRelease: unreachable,
135
221
  activateTarget: unreachable,
136
- async verifyTargetReady(rootId, _target, staged) {
137
- if (connectedTarget?.registration.rootId !== rootId ||
138
- connectedTarget.registration.generation !== staged.launchGeneration) {
222
+ async verifyTargetReady() {
223
+ if (targetActivator?.kind !== 'ready') {
139
224
  throw new Error('Exact restarted Runtime Host Ready evidence is unavailable');
140
225
  }
141
- await connectedTarget.connection.close();
226
+ },
227
+ async finalizeTarget() {
228
+ const observedInstallation = await deps.resolveInstallation(input.installationOptions);
229
+ if (observedInstallation.owner.installationId !== installation.owner.installationId ||
230
+ observedInstallation.observedRelease.version !== target.version) {
231
+ throw new RuntimeHostLocalHandoffError('installed_release_mismatch', 'The installed Maka release changed again before local Host ownership committed');
232
+ }
142
233
  },
143
234
  }, authorityOptions, deps);
235
+ return result;
144
236
  }
145
237
  finally {
146
- await connectedTarget?.connection.close().catch(() => undefined);
238
+ if (targetActivator) {
239
+ const settlement = targetActivator.settle();
240
+ if (result?.kind === 'completed')
241
+ await settlement;
242
+ else
243
+ await settlement.catch(() => undefined);
244
+ }
147
245
  }
148
246
  }
149
247
  /**
@@ -233,6 +331,20 @@ export async function prepareRuntimeHostNpmGlobalStagedDeployment(input, pathOpt
233
331
  launchGeneration: launchGeneration(input.transactionId, input.target),
234
332
  };
235
333
  }
334
+ async function openRuntimeHostNpmGlobalStagedDeployment(input, pathOptions = {}) {
335
+ const deploymentRoot = resolveRuntimeHostLocalCliDeploymentRoot(input.rootId, input.owner, pathOptions);
336
+ const staged = await openRuntimeHostPackageDeployment({
337
+ deploymentRoot,
338
+ cliPath: resolveRuntimeHostPackageCliPath(deploymentRoot, input.target.version, input.target.integrity),
339
+ version: input.target.version,
340
+ });
341
+ const candidateEntrypoint = await requireCandidateEntrypoint(staged.packageRoot);
342
+ return {
343
+ ...staged,
344
+ candidateEntrypoint,
345
+ launchGeneration: launchGeneration(input.transactionId, input.target),
346
+ };
347
+ }
236
348
  export function resolveRuntimeHostLocalCliDeploymentRoot(rootId, owner, options = {}) {
237
349
  if (!ROOT_ID.test(rootId) || owner.kind !== 'cli' || owner.installationId.length === 0) {
238
350
  throw new TypeError('Invalid local Runtime Host CLI deployment identity');
@@ -267,6 +379,22 @@ async function requireCandidateEntrypoint(packageRoot) {
267
379
  }
268
380
  return candidate;
269
381
  }
382
+ async function hasSourceRetirementHelper(source) {
383
+ try {
384
+ return (await stat(join(dirname(source.cliPath), 'runtime-host-local-source-retirement.js'))).isFile();
385
+ }
386
+ catch (error) {
387
+ if (isNodeError(error, 'ENOENT'))
388
+ return false;
389
+ throw error;
390
+ }
391
+ }
392
+ function sameDeployment(left, right) {
393
+ return (left.kind === right.kind && left.version === right.version && left.integrity === right.integrity);
394
+ }
395
+ function sameOwner(left, right) {
396
+ return left.kind === right.kind && left.installationId === right.installationId;
397
+ }
270
398
  function launchGeneration(transactionId, target) {
271
399
  return `npm-global-handoff:${createHash('sha256')
272
400
  .update(transactionId)
@@ -292,3 +420,6 @@ function restartTransactionId(rootId, owner, target) {
292
420
  function invalidStagedPackage(message, cause) {
293
421
  return new RuntimeHostUpdatePackageError('invalid_package', message, { cause });
294
422
  }
423
+ function isNodeError(error, code) {
424
+ return error instanceof Error && 'code' in error && error.code === code;
425
+ }
@@ -0,0 +1,103 @@
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 { spawn } from 'node:child_process';
20
+ import { connectExistingRuntimeHost, prepareConnectedRuntimeHostRetirement, } from '@maka/runtime-host/client';
21
+ import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
22
+ const SOURCE_RETIREMENT_TIMEOUT_MS = 60_000;
23
+ /**
24
+ * Runs inside the exact selected source package so its client protocol remains
25
+ * compatible with the Host that package launched. The parent owns deployment
26
+ * authority; this helper only exercises the authenticated retirement contract.
27
+ */
28
+ export async function runRuntimeHostLocalSourceRetirement(input, overrides = {}) {
29
+ const connectExisting = overrides.connectExisting ?? connectExistingRuntimeHost;
30
+ const prepareRetirement = overrides.prepareRetirement ?? prepareConnectedRuntimeHostRetirement;
31
+ const connected = await connectExisting({
32
+ rootPath: input.rootPath,
33
+ protocol: { min: RUNTIME_HOST_PROTOCOL_VERSION, max: RUNTIME_HOST_PROTOCOL_VERSION },
34
+ compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
35
+ });
36
+ if (connected.kind !== 'connected') {
37
+ if (connected.registration && connected.registration.lifecycleMode !== 'ephemeral')
38
+ return 4;
39
+ throw new Error('The selected source package cannot control the observed Runtime Host');
40
+ }
41
+ try {
42
+ if (connected.registration.rootId !== input.expectedRootId ||
43
+ connected.registration.hostEpoch !== input.expectedHostEpoch) {
44
+ throw new Error('The Runtime Host changed before source-package retirement');
45
+ }
46
+ if (connected.registration.lifecycleMode !== 'ephemeral')
47
+ return 4;
48
+ const prepared = await prepareRetirement(connected.connection, input.activeWorkPolicy);
49
+ if (prepared.kind === 'active_tasks')
50
+ return 2;
51
+ if (prepared.pid !== connected.registration.pid) {
52
+ throw new Error('The Runtime Host process changed while source retirement was prepared');
53
+ }
54
+ return 0;
55
+ }
56
+ finally {
57
+ await connected.connection.close().catch(() => undefined);
58
+ }
59
+ }
60
+ export function launchRuntimeHostLocalSourceRetirement(input) {
61
+ const args = [
62
+ input.sourceCliPath,
63
+ 'runtime-host',
64
+ 'local-source-retire',
65
+ '--root',
66
+ input.rootPath,
67
+ '--expected-root-id',
68
+ input.expectedRootId,
69
+ '--expected-host-epoch',
70
+ input.expectedHostEpoch,
71
+ ...(input.activeWorkPolicy === 'interrupt_active_work'
72
+ ? ['--allow-interrupt-active-tasks']
73
+ : []),
74
+ ];
75
+ return new Promise((resolve, reject) => {
76
+ const child = spawn(process.execPath, args, {
77
+ // fd 4 inherits the existing owner-authority lease. The source helper
78
+ // holds it only while the authenticated retirement request is in flight.
79
+ stdio: ['ignore', 'ignore', 'inherit', 'ignore', input.inheritableAuthorityLeaseFd],
80
+ timeout: SOURCE_RETIREMENT_TIMEOUT_MS,
81
+ killSignal: 'SIGKILL',
82
+ windowsHide: false,
83
+ });
84
+ child.once('error', reject);
85
+ child.once('close', (code, signal) => {
86
+ if (signal) {
87
+ reject(new Error(`Maka source retirement helper exited on ${signal}`));
88
+ }
89
+ else if (code === 0) {
90
+ resolve('prepared');
91
+ }
92
+ else if (code === 2) {
93
+ resolve('active_work');
94
+ }
95
+ else if (code === 4) {
96
+ resolve('operator_required');
97
+ }
98
+ else {
99
+ reject(new Error('The selected Maka source package could not retire its Runtime Host'));
100
+ }
101
+ });
102
+ });
103
+ }
@@ -0,0 +1,141 @@
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 { spawn } from 'node:child_process';
20
+ const TARGET_ACTIVATOR_SETTLEMENT_TIMEOUT_MS = 15_000;
21
+ export class RuntimeHostTargetActivationError extends Error {
22
+ code;
23
+ constructor(code, message, options) {
24
+ super(message, options);
25
+ this.code = code;
26
+ this.name = 'RuntimeHostTargetActivationError';
27
+ }
28
+ }
29
+ /**
30
+ * Launches the exact staged target through its own release and keeps the
31
+ * existing owner-authority lease in the activator until durable commit.
32
+ */
33
+ export function launchRuntimeHostTargetActivator(input, options = {}) {
34
+ const args = [
35
+ input.staged.cliPath,
36
+ 'runtime-host',
37
+ 'local-update-activate',
38
+ '--root',
39
+ input.rootPath,
40
+ '--expected-root-id',
41
+ input.rootId,
42
+ '--generation',
43
+ input.staged.launchGeneration,
44
+ '--candidate-entrypoint',
45
+ input.staged.candidateEntrypoint,
46
+ '--await-coordinator-commit',
47
+ 'true',
48
+ '--expected-owner-installation-id',
49
+ input.ownerInstallationId,
50
+ '--target-version',
51
+ input.target.version,
52
+ '--target-integrity',
53
+ input.target.integrity,
54
+ ...(input.takeoverHostEpoch ? ['--takeover-host-epoch', input.takeoverHostEpoch] : []),
55
+ ];
56
+ return new Promise((resolve, reject) => {
57
+ const child = spawn(process.execPath, args, {
58
+ // fd 3 is the coordinator channel; fd 4 is the inherited authority
59
+ // lease. The activator, not the long-lived target, owns that lease.
60
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc', input.inheritableAuthorityLeaseFd],
61
+ windowsHide: false,
62
+ });
63
+ let ready = false;
64
+ let settled = false;
65
+ const closed = new Promise((resolveClosed) => {
66
+ child.once('close', (code, signal) => resolveClosed({ code, signal }));
67
+ });
68
+ const closeError = async () => {
69
+ const { code, signal } = await closed;
70
+ if (signal)
71
+ throw new Error(`Maka target activator exited on ${signal}`);
72
+ if (code === 3)
73
+ throw new Error('The activated Runtime Host still owns active work');
74
+ if (code === 4)
75
+ throw new Error('The observed Runtime Host requires its operator');
76
+ throw new Error('The exact Maka target could not be activated');
77
+ };
78
+ const settle = async () => {
79
+ if (settled)
80
+ return;
81
+ settled = true;
82
+ let settlementTimedOut = false;
83
+ // This parent owns the activator process and therefore the hard bound.
84
+ // Killing and then awaiting close releases its inherited lease even when
85
+ // an individual authority read in the child never settles.
86
+ const settlementTimer = setTimeout(() => {
87
+ settlementTimedOut = true;
88
+ child.kill('SIGKILL');
89
+ }, options.settlementTimeoutMs ?? TARGET_ACTIVATOR_SETTLEMENT_TIMEOUT_MS);
90
+ settlementTimer.unref();
91
+ if (child.connected) {
92
+ try {
93
+ child.send({ kind: 'settle' });
94
+ }
95
+ catch {
96
+ // The close result below remains the only settlement evidence.
97
+ }
98
+ }
99
+ try {
100
+ const exited = await closed;
101
+ if (settlementTimedOut) {
102
+ throw new RuntimeHostTargetActivationError('recovery_required', 'The local Runtime Host update requires recovery because its target activator exceeded the durable settlement deadline');
103
+ }
104
+ if (!exited.signal && exited.code === 0)
105
+ return;
106
+ throw new RuntimeHostTargetActivationError('recovery_required', exited.signal
107
+ ? `The local Runtime Host update requires recovery because its target activator exited on ${exited.signal}`
108
+ : 'The local Runtime Host update requires recovery because its target activator could not confirm durable ownership');
109
+ }
110
+ finally {
111
+ clearTimeout(settlementTimer);
112
+ }
113
+ };
114
+ child.once('error', reject);
115
+ child.on('message', (message) => {
116
+ if (ready || !isTargetActivatorReadyMessage(message))
117
+ return;
118
+ ready = true;
119
+ resolve({ kind: 'ready', settle });
120
+ });
121
+ void closed.then(({ code, signal }) => {
122
+ if (ready)
123
+ return;
124
+ if (signal) {
125
+ reject(new Error(`Maka target activator exited on ${signal}`));
126
+ }
127
+ else if (code === 3) {
128
+ resolve({ kind: 'active_work', settle: async () => undefined });
129
+ }
130
+ else if (code === 4) {
131
+ resolve({ kind: 'operator_required', settle: async () => undefined });
132
+ }
133
+ else {
134
+ void closeError().catch(reject);
135
+ }
136
+ });
137
+ });
138
+ }
139
+ function isTargetActivatorReadyMessage(value) {
140
+ return (typeof value === 'object' && value !== null && value.kind === 'ready');
141
+ }
@@ -23,7 +23,7 @@ import { isDeepStrictEqual } from 'node:util';
23
23
  import { networkInterfaces } from 'node:os';
24
24
  import { encodeRuntimeHostPeerManagementFrame, resolveRuntimeHostNpmDeploymentLayout, } from '@maka/runtime-host/operator';
25
25
  import { ensureRuntimeHostPeerIdentity } from '@maka/runtime-host/client';
26
- import { hasActivePeerMeshMembership } from '@maka/runtime-host/peer-mesh';
26
+ import { hasPeerMeshIdentityObligations } from '@maka/runtime-host/peer-mesh';
27
27
  import { allocateRuntimeHostPeerPort, RuntimeHostServiceManagerError, withRuntimeHostManagedServiceDeploymentLock, withRuntimeHostManagedServiceLifecycleLock, } from './runtime-host-service-manager.js';
28
28
  import { resolveRuntimeHostLifecycleProvider } from './runtime-host-service-management-command.js';
29
29
  import { canDiscardRuntimeHostLifecycleDesiredArtifacts, replaceRuntimeHostLifecycle, resolveRecoverableRuntimeHostManagedDeployment, activateRuntimeHostLifecycle, verifyRuntimeHostLifecycleReady, } from './runtime-host-lifecycle-transaction.js';
@@ -67,6 +67,7 @@ async function runCanonicalRuntimeHostPeerManagementLocked(options, deps) {
67
67
  let desired = config;
68
68
  let stagedKeyPath;
69
69
  let previousPeerId;
70
+ let validateRetiredState;
70
71
  let restarted;
71
72
  if (options.action === 'enable') {
72
73
  const peer = await prepareCanonicalPeer(options, config, config.listeners.directPeer);
@@ -93,9 +94,13 @@ async function runCanonicalRuntimeHostPeerManagementLocked(options, deps) {
93
94
  if (!current?.enabled) {
94
95
  throw new RuntimeHostServiceManagerError('not_installed', 'Direct peer is not enabled for the managed Runtime Host deployment');
95
96
  }
96
- if (await hasActivePeerMeshMembership(join(config.deploymentRoot, 'peer-mesh', current.peerId), current.peerId)) {
97
- throw new RuntimeHostServiceManagerError('invalid_config', 'Close or leave every active Peer Mesh before rotating the Direct peer identity');
98
- }
97
+ validateRetiredState = async () => {
98
+ if (!(await hasPeerMeshIdentityObligations(join(config.deploymentRoot, 'peer-mesh', current.peerId), current.peerId))) {
99
+ return;
100
+ }
101
+ throw new RuntimeHostServiceManagerError('invalid_config', 'Close or finish leaving every Peer Mesh before rotating the Direct peer identity');
102
+ };
103
+ await validateRetiredState();
99
104
  previousPeerId = current.peerId;
100
105
  stagedKeyPath = join(dirname(current.keyPath), `runtime-host-peer.${randomUUID()}.key`);
101
106
  const layout = resolveRuntimeHostNpmDeploymentLayout(config.deploymentRoot, config.launch.package.integrity);
@@ -119,6 +124,7 @@ async function runCanonicalRuntimeHostPeerManagementLocked(options, deps) {
119
124
  desired,
120
125
  allowInterruptActiveTasks: options.allowInterruptActiveTasks ?? false,
121
126
  deps: lifecycleDeps,
127
+ ...(validateRetiredState ? { validateRetiredState } : {}),
122
128
  }).catch(async (error) => {
123
129
  if (stagedKeyPath && canDiscardRuntimeHostLifecycleDesiredArtifacts(error)) {
124
130
  await rm(stagedKeyPath, { force: true }).catch(() => undefined);
@@ -161,8 +161,6 @@ export const AGENT_RUN_EVENT_TYPES = [
161
161
  'run_created',
162
162
  'run_started',
163
163
  'turn_started',
164
- 'sandbox_context_resolved',
165
- 'sandbox_context_failed',
166
164
  'plan_context_resolved',
167
165
  'plan_submitted',
168
166
  'plan_execution_started',
@@ -60,6 +60,7 @@ export function resolveArtifactImagePreview(input) {
60
60
  }
61
61
  export const ARTIFACT_SOURCES = [
62
62
  'tool_result',
63
+ 'tool_result_projection',
63
64
  'tool_result_archive',
64
65
  'synthesis_cache_block',
65
66
  'history_compact_block',
@@ -87,21 +88,28 @@ export function isArtifactTurnKey(value) {
87
88
  value.length <= ARTIFACT_TURN_KEY_MAX_CHARS &&
88
89
  !ARTIFACT_TURN_KEY_CONTROL_CHARACTERS.test(value));
89
90
  }
90
- const ARTIFACT_USER_DELETE_ALLOWED_BY_SOURCE = {
91
- tool_result: true,
92
- tool_result_archive: false,
93
- synthesis_cache_block: true,
94
- history_compact_block: true,
95
- history_compact_source: true,
96
- provider_request_capture: true,
97
- subagent_writeback: false,
98
- deep_research: false,
99
- user_upload: true,
100
- export: true,
101
- snapshot: true,
102
- session_effect: false,
103
- fixture: true,
91
+ const ARTIFACT_SOURCE_POLICIES = {
92
+ tool_result: { userDeletable: true, userVisible: false, sharedReadable: true },
93
+ tool_result_projection: { userDeletable: false, userVisible: false, sharedReadable: true },
94
+ tool_result_archive: { userDeletable: false, userVisible: false, sharedReadable: false },
95
+ synthesis_cache_block: { userDeletable: true, userVisible: false, sharedReadable: false },
96
+ history_compact_block: { userDeletable: true, userVisible: false, sharedReadable: false },
97
+ history_compact_source: { userDeletable: true, userVisible: false, sharedReadable: false },
98
+ provider_request_capture: { userDeletable: true, userVisible: false, sharedReadable: false },
99
+ subagent_writeback: { userDeletable: false, userVisible: true, sharedReadable: false },
100
+ deep_research: { userDeletable: false, userVisible: true, sharedReadable: false },
101
+ user_upload: { userDeletable: true, userVisible: false, sharedReadable: true },
102
+ export: { userDeletable: true, userVisible: true, sharedReadable: false },
103
+ snapshot: { userDeletable: true, userVisible: true, sharedReadable: false },
104
+ session_effect: { userDeletable: false, userVisible: false, sharedReadable: false },
105
+ fixture: { userDeletable: true, userVisible: true, sharedReadable: false },
104
106
  };
105
107
  export function canUserDeleteArtifact(record) {
106
- return record.source === undefined || ARTIFACT_USER_DELETE_ALLOWED_BY_SOURCE[record.source];
108
+ return record.source === undefined || ARTIFACT_SOURCE_POLICIES[record.source].userDeletable;
109
+ }
110
+ export function isArtifactUserVisible(record) {
111
+ return record.source === undefined || ARTIFACT_SOURCE_POLICIES[record.source].userVisible;
112
+ }
113
+ export function isArtifactSharedSessionReadable(record) {
114
+ return record.source !== undefined && ARTIFACT_SOURCE_POLICIES[record.source].sharedReadable;
107
115
  }