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.
- package/dist/cli-core.js +11 -0
- package/dist/runtime-host-cli.js +44 -0
- package/dist/runtime-host-installed-update-activator.js +70 -33
- package/dist/runtime-host-installed-update-coordinator.js +11 -96
- package/dist/runtime-host-lifecycle-transaction.js +2 -0
- package/dist/runtime-host-local-handoff.js +169 -38
- package/dist/runtime-host-local-source-retirement.js +103 -0
- package/dist/runtime-host-local-target-activation.js +141 -0
- package/dist/runtime-host-peer-management-command.js +10 -4
- package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
- package/node_modules/@maka/core/dist/agent-run.js +0 -2
- package/node_modules/@maka/core/dist/artifacts.js +23 -15
- package/node_modules/@maka/core/dist/durable-tool-result-projection.js +124 -0
- package/node_modules/@maka/core/dist/foreign-session.js +1 -1
- package/node_modules/@maka/core/dist/llm-connections.js +20 -0
- package/node_modules/@maka/core/dist/runtime-event.js +28 -3
- package/node_modules/@maka/core/dist/session-name.js +0 -1
- package/node_modules/@maka/core/dist/task-ledger.js +6 -134
- package/node_modules/@maka/core/dist/usage-record-schema.js +1 -0
- package/node_modules/@maka/core/package.json +1 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +97 -133
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +4 -18
- package/node_modules/@maka/runtime/dist/context-budget.js +0 -1
- package/node_modules/@maka/runtime/dist/conversation-copy.js +67 -0
- package/node_modules/@maka/runtime/dist/durable-tool-result-projection.js +390 -0
- package/node_modules/@maka/runtime/dist/model-history.js +12 -39
- package/node_modules/@maka/runtime/dist/plan-mode.js +0 -65
- package/node_modules/@maka/runtime/dist/recovery-resolver.js +18 -14
- package/node_modules/@maka/runtime/dist/run-trace.js +0 -10
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +0 -1
- package/node_modules/@maka/runtime/dist/sandbox/index.js +0 -1
- package/node_modules/@maka/runtime/dist/sandbox/linux-sandbox.js +0 -12
- package/node_modules/@maka/runtime/dist/sandbox/macos-seatbelt.js +0 -12
- package/node_modules/@maka/runtime/dist/sandbox/sandbox-manager.js +0 -35
- package/node_modules/@maka/runtime/dist/sandbox/windows-sandbox.js +0 -12
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +20 -11
- package/node_modules/@maka/runtime/dist/session-manager.js +0 -170
- package/node_modules/@maka/runtime/dist/shell-detect.js +3 -7
- package/node_modules/@maka/runtime/dist/shell-run-contract.js +0 -1
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +1 -39
- package/node_modules/@maka/runtime/dist/shell-tools.js +1 -1
- package/node_modules/@maka/runtime/dist/task-ledger-tools.js +2 -2
- package/node_modules/@maka/runtime/dist/tool-runtime.js +118 -71
- package/node_modules/@maka/runtime/package.json +1 -1
- package/node_modules/@maka/runtime-host/dist/candidate-entry.js +6 -1
- package/node_modules/@maka/runtime-host/dist/candidate-launch-owner-guard.js +13 -0
- package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +4 -1
- package/node_modules/@maka/runtime-host/dist/client/host-profile.js +27 -1
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/launcher.js +8 -2
- package/node_modules/@maka/runtime-host/dist/client/peer-client.js +11 -1
- package/node_modules/@maka/runtime-host/dist/peer-mesh/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +394 -113
- package/node_modules/@maka/runtime-host/dist/peer-mesh/store.js +90 -21
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +8 -1
- package/node_modules/@maka/runtime-host/dist/protocol/oauth.js +60 -7
- package/node_modules/@maka/runtime-host/dist/protocol/usage-pricing.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/artifact-coordinator.js +2 -4
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +10 -7
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +6 -9
- package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +9 -7
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +10 -0
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +2 -58
- package/node_modules/@maka/runtime-host/dist/server/oauth-coordinator.js +91 -42
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +10 -1
- package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +45 -5
- package/node_modules/@maka/storage/dist/artifact-attachments.js +62 -11
- package/node_modules/@maka/storage/dist/artifact-store.js +11 -0
- package/node_modules/@maka/storage/dist/artifact-stores.js +4 -1
- package/node_modules/@maka/storage/dist/quiescent-session-snapshot.js +1154 -0
- package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +36 -0
- package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +213 -46
- package/node_modules/@maka/storage/dist/runtime-policy/document-io.js +1 -1
- package/node_modules/@maka/storage/dist/runtime-policy/oauth-login-receipt-document.js +186 -0
- package/node_modules/@maka/storage/dist/runtime-policy/onboarding-transaction.js +120 -1
- package/node_modules/@maka/storage/dist/runtime-policy-stores.js +2 -1
- package/node_modules/@maka/storage/dist/session-bundle-ustar.js +12 -0
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +5 -0
- package/package.json +1 -1
- package/node_modules/@maka/runtime/dist/sandbox/diagnostics.js +0 -285
- package/node_modules/@maka/runtime/dist/system-prompt/sandbox-context-prompt.js +0 -77
- 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 {
|
|
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.
|
|
49
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
const
|
|
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
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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 (
|
|
110
|
-
|
|
111
|
-
|
|
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 (
|
|
114
|
-
|
|
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
|
-
|
|
200
|
+
return { kind: 'target_present' };
|
|
117
201
|
};
|
|
118
202
|
const unreachable = async () => {
|
|
119
|
-
throw new Error('
|
|
203
|
+
throw new Error('Local restart must converge through its exact target activator');
|
|
120
204
|
};
|
|
205
|
+
let result;
|
|
121
206
|
try {
|
|
122
|
-
|
|
207
|
+
result = await reconcilePreparedRuntimeHostNpmGlobalDeployment({
|
|
123
208
|
rootId: input.registration.rootId,
|
|
124
209
|
transactionId,
|
|
125
210
|
target,
|
|
126
|
-
activeWorkPolicy
|
|
127
|
-
|
|
211
|
+
activeWorkPolicy,
|
|
212
|
+
installation,
|
|
213
|
+
staged,
|
|
128
214
|
...(input.deploymentPathOptions
|
|
129
215
|
? { deploymentPathOptions: input.deploymentPathOptions }
|
|
130
216
|
: {}),
|
|
131
217
|
}, {
|
|
132
|
-
prepareUnownedHostCutover: (rootId, _target,
|
|
133
|
-
prepareHostCutover: (rootId,
|
|
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(
|
|
137
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
97
|
-
|
|
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);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
91
|
-
tool_result: true,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 ||
|
|
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
|
}
|