maka-agent 0.2.0-dev.12.20260901 → 0.2.0-dev.14.20260902
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/THIRD_PARTY_NOTICES.txt +2 -2
- package/dist/acp/maka-acp-agent.js +6 -3
- package/dist/acp/session-registry.js +307 -0
- package/dist/acp/stdio-server.js +20 -1
- package/dist/cli-core.js +15 -2
- package/dist/pi-transcript.js +1 -1
- package/dist/pi-tui-layout.js +1 -1
- package/dist/pi-tui-mcp-status.js +1 -1
- package/dist/pi-tui-runner.js +1 -1
- package/dist/runtime-host-access-command.js +37 -4
- package/dist/runtime-host-capability-provider-command.js +16 -2
- package/dist/runtime-host-cli-context.js +73 -3
- package/dist/runtime-host-cli.js +37 -6
- package/dist/runtime-host-run-command.js +3 -1
- package/dist/runtime-host-update-command.js +59 -31
- package/dist/tui-copy-catalog.js +4 -2
- 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/attachments.js +12 -0
- package/node_modules/@maka/core/dist/connection-error-copy.js +17 -34
- package/node_modules/@maka/core/dist/events.js +0 -13
- package/node_modules/@maka/core/dist/maka-wordmark.js +23 -0
- package/node_modules/@maka/core/dist/model-catalog.js +22 -1
- package/node_modules/@maka/core/dist/runtime-event.js +1 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +50 -1
- package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +9 -1
- package/node_modules/@maka/core/dist/runtime-policy/model-catalog-entry-codec.js +10 -1
- package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +75 -0
- package/node_modules/@maka/core/dist/runtime-policy.js +4 -2
- package/node_modules/@maka/core/dist/session.js +1 -0
- package/node_modules/@maka/core/dist/settings.js +10 -2
- package/node_modules/@maka/core/dist/usage-record-schema.js +11 -1
- package/node_modules/@maka/core/package.json +2 -1
- package/node_modules/@maka/mcp/dist/index.js +1 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +52 -80
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +178 -140
- package/node_modules/@maka/runtime/dist/archive-read-tool.js +26 -5
- package/node_modules/@maka/runtime/dist/builtin-tools.js +27 -2
- package/node_modules/@maka/runtime/dist/context-budget-policy.js +0 -12
- package/node_modules/@maka/runtime/dist/conversation-copy.js +66 -43
- package/node_modules/@maka/runtime/dist/durable-tool-result-projection.js +31 -0
- package/node_modules/@maka/runtime/dist/history-compact-error.js +10 -2
- package/node_modules/@maka/runtime/dist/history-compact-input-fit.js +7 -1
- package/node_modules/@maka/runtime/dist/history-compaction.js +13 -22
- package/node_modules/@maka/runtime/dist/model-history.js +41 -16
- package/node_modules/@maka/runtime/dist/provider-image-overflow-recovery.js +12 -31
- package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +3 -0
- package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +13 -2
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -6
- package/node_modules/@maka/runtime/dist/shell-tools.js +77 -1
- package/node_modules/@maka/runtime/dist/tool-availability.js +74 -5
- package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +333 -52
- package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +34 -3
- package/node_modules/@maka/runtime/dist/tool-runtime.js +12 -0
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +0 -3
- package/node_modules/@maka/runtime-host/dist/client/catalog-reader.js +40 -13
- package/node_modules/@maka/runtime-host/dist/client/connection.js +50 -31
- package/node_modules/@maka/runtime-host/dist/client/host-profile.js +48 -4
- package/node_modules/@maka/runtime-host/dist/client/index.js +2 -2
- package/node_modules/@maka/runtime-host/dist/client/owner-connection-code.js +32 -1
- package/node_modules/@maka/runtime-host/dist/client/peer-client.js +22 -4
- package/node_modules/@maka/runtime-host/dist/operator/access-management-frame.js +10 -1
- package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +56 -12
- package/node_modules/@maka/runtime-host/dist/peer-mesh/owner.js +3 -0
- package/node_modules/@maka/runtime-host/dist/protocol/configuration.js +55 -9
- package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +37 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +17 -1
- package/node_modules/@maka/runtime-host/dist/protocol/network-proxy.js +1 -7
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +2 -0
- package/node_modules/@maka/runtime-host/dist/protocol/runtime-policy.js +48 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +4 -2
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +2 -12
- package/node_modules/@maka/runtime-host/dist/server/access-authority.js +2 -1
- package/node_modules/@maka/runtime-host/dist/server/agent-graph-execution-coordinator.js +1 -2
- package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +0 -9
- package/node_modules/@maka/runtime-host/dist/server/configuration-coordinator.js +20 -1
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +46 -24
- package/node_modules/@maka/runtime-host/dist/server/execution-service.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +15 -0
- package/node_modules/@maka/runtime-host/dist/server/listener-set.js +8 -4
- package/node_modules/@maka/runtime-host/dist/server/network-proxy-coordinator.js +0 -1
- package/node_modules/@maka/runtime-host/dist/server/peer-listener.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +28 -1
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +3 -3
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +35 -3
- package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +16 -0
- package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +189 -6
- package/node_modules/@maka/storage/dist/runtime-policy-stores.js +4 -1
- package/node_modules/@maka/storage/dist/settings-store.js +16 -25
- package/node_modules/fast-uri/index.js +215 -47
- package/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/fast-uri/lib/utils.js +396 -92
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
- package/node_modules/qs/CHANGELOG.md +15 -0
- package/node_modules/qs/README.md +19 -1
- package/node_modules/qs/dist/qs.js +21 -21
- package/node_modules/qs/eslint.config.mjs +1 -1
- package/node_modules/qs/lib/parse.js +3 -4
- package/node_modules/qs/lib/stringify.js +23 -8
- package/node_modules/qs/lib/utils.js +10 -3
- package/node_modules/qs/package.json +4 -4
- package/package.json +1 -1
|
@@ -16,10 +16,33 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* The one writer that turns a Tool Result prune decision into durable truth
|
|
21
|
+
* (#4283).
|
|
22
|
+
*
|
|
23
|
+
* Both prune paths — the current Turn's active prune before the next provider
|
|
24
|
+
* step, and the prior Turn's stale prune before compaction — come through here.
|
|
25
|
+
* They used to keep their replacement in a Turn-local map and a policy-carried
|
|
26
|
+
* ref table respectively, so each owned a private recovery contract and neither
|
|
27
|
+
* survived a restart. Now each records one `ModelProjectionTransition`, and the
|
|
28
|
+
* Session reducer is the only thing that decides what the model sees.
|
|
29
|
+
*
|
|
30
|
+
* Write order is the whole safety argument:
|
|
31
|
+
*
|
|
32
|
+
* 1. Archive the replaced body. A failure here leaves the projection untouched.
|
|
33
|
+
* 2. Append the transition. A failure here leaves an artifact nothing points
|
|
34
|
+
* at — unreachable by the reducer, so safe to reclaim once something does —
|
|
35
|
+
* and again leaves the projection untouched.
|
|
36
|
+
* 3. Only then may a caller show the replacement.
|
|
37
|
+
*
|
|
38
|
+
* There is no state in which the model has lost content the ledger cannot
|
|
39
|
+
* explain, and none in which a completed tool effect is repeated.
|
|
40
|
+
*/
|
|
41
|
+
import { MATERIALIZED_IMAGE_TOKENS } from '@maka/core/attachments';
|
|
19
42
|
import { DURABLE_TOOL_RESULT_PROJECTION_VERSION } from '@maka/core/durable-tool-result-projection';
|
|
20
43
|
import { buildModelProjectionTransition, } from '@maka/core/model-projection-transition';
|
|
21
44
|
import { estimateTokens, finitePositive, sha256, turnKey, utf8ByteLength, } from './context-budget-helpers.js';
|
|
22
|
-
import { durableProjectionToToolResultOutput } from './durable-tool-result-projection.js';
|
|
45
|
+
import { durableProjectionToToolResultOutput, projectionArtifactMedia, } from './durable-tool-result-projection.js';
|
|
23
46
|
import { baseToolResultProjection, nextInChain } from './model-projection-transition-ledger.js';
|
|
24
47
|
import { ARCHIVED_TOOL_RESULT_REWRITE_VERSION, buildArchivedToolResultPlaceholder, isArchivedToolResultPlaceholder, serializeToolResultForArchive, } from './tool-result-archive.js';
|
|
25
48
|
const DEFAULT_MAX_TOOL_RESULT_ESTIMATED_TOKENS = 2048;
|
|
@@ -168,8 +191,16 @@ export function collectStaleToolResultArchiveCandidates(events, prunePolicy, cha
|
|
|
168
191
|
continue;
|
|
169
192
|
const serializedResult = serializedToolResultProjection(sourceProjection);
|
|
170
193
|
const originalBytes = utf8ByteLength(serializedResult);
|
|
171
|
-
const
|
|
172
|
-
|
|
194
|
+
const media = projectionArtifactMedia(sourceProjection);
|
|
195
|
+
// An artifact serializes to a short reference and materializes to real
|
|
196
|
+
// image bytes, so the string alone would price a screenshot at nothing.
|
|
197
|
+
const originalEstimatedTokens = estimateTokens(serializedResult.length, charsPerToken) +
|
|
198
|
+
media.length * MATERIALIZED_IMAGE_TOKENS;
|
|
199
|
+
// A result that carries media is always a candidate: archiving it drops
|
|
200
|
+
// whole images from the request, which is worth doing whatever the
|
|
201
|
+
// reference text around them happens to weigh. The size gate is there to
|
|
202
|
+
// spare small text results, so it only decides those.
|
|
203
|
+
if (media.length === 0 && originalEstimatedTokens <= maxResultEstimatedTokens)
|
|
173
204
|
continue;
|
|
174
205
|
candidates.push({
|
|
175
206
|
runtimeEventId: event.id,
|
|
@@ -1571,6 +1571,18 @@ export class ToolRuntime {
|
|
|
1571
1571
|
});
|
|
1572
1572
|
}
|
|
1573
1573
|
catch (error) {
|
|
1574
|
+
try {
|
|
1575
|
+
await input.tool.compensateDurableOutcomeCommitFailure?.({
|
|
1576
|
+
result,
|
|
1577
|
+
isError,
|
|
1578
|
+
sessionId: this.input.sessionId,
|
|
1579
|
+
operationId,
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
catch {
|
|
1583
|
+
// T2 remains authoritative. Compensation is deliberately best-effort
|
|
1584
|
+
// and must never replace the persistence failure that triggered it.
|
|
1585
|
+
}
|
|
1574
1586
|
throw new RuntimeCommitBoundaryError('T2', error);
|
|
1575
1587
|
}
|
|
1576
1588
|
committedOutcome = {
|
|
@@ -410,9 +410,6 @@ export class RuntimeHostSessionProjector {
|
|
|
410
410
|
recoverable: false,
|
|
411
411
|
reason: root.failureClass,
|
|
412
412
|
message: root.failureMessage ?? `Turn failed: ${root.failureClass}`,
|
|
413
|
-
...(root.contextBudgetExhaustedDetail
|
|
414
|
-
? { details: { contextBudgetExhaustedDetail: root.contextBudgetExhaustedDetail } }
|
|
415
|
-
: {}),
|
|
416
413
|
});
|
|
417
414
|
}
|
|
418
415
|
else {
|
|
@@ -30,6 +30,16 @@ export class RuntimeHostCatalogReadError extends Error {
|
|
|
30
30
|
this.name = 'RuntimeHostCatalogReadError';
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
export class RuntimeHostSessionCatalogRevisionChangedError extends Error {
|
|
34
|
+
expectedRevision;
|
|
35
|
+
actualRevision;
|
|
36
|
+
constructor(expectedRevision, actualRevision) {
|
|
37
|
+
super('Runtime Host Session catalog revision changed');
|
|
38
|
+
this.expectedRevision = expectedRevision;
|
|
39
|
+
this.actualRevision = actualRevision;
|
|
40
|
+
this.name = 'RuntimeHostSessionCatalogRevisionChangedError';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
33
43
|
export async function readRuntimeHostConnectionCatalog(connection) {
|
|
34
44
|
const { first, pages } = await collectStablePages('connection', async () => {
|
|
35
45
|
const result = await connection.request('connection.catalog.query', { kind: 'start' });
|
|
@@ -105,21 +115,38 @@ export async function readRuntimeHostInvocableSkills(connection, target) {
|
|
|
105
115
|
return pages.flatMap((page) => page.items);
|
|
106
116
|
}
|
|
107
117
|
export async function readRuntimeHostSessions(connection) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return result.kind === 'page' ? result : null;
|
|
120
|
-
});
|
|
118
|
+
const readPageOrRestart = async (cursor) => {
|
|
119
|
+
try {
|
|
120
|
+
return await readRuntimeHostSessionCatalogPage(connection, cursor);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof RuntimeHostSessionCatalogRevisionChangedError)
|
|
124
|
+
return null;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const { pages } = await collectStablePages('session', () => readPageOrRestart(), (_revision, cursor) => readPageOrRestart(cursor));
|
|
121
129
|
return pages.flatMap((page) => page.sessions);
|
|
122
130
|
}
|
|
131
|
+
export async function readRuntimeHostSessionCatalogPage(connection, cursor) {
|
|
132
|
+
const result = await connection.request('session.catalog.query', cursor
|
|
133
|
+
? { kind: 'list_continue', revision: cursor.revision, cursor: cursor.cursor }
|
|
134
|
+
: { kind: 'list_start' });
|
|
135
|
+
if (result.kind === 'revision_changed') {
|
|
136
|
+
throw new RuntimeHostSessionCatalogRevisionChangedError(result.expectedRevision, result.actualRevision);
|
|
137
|
+
}
|
|
138
|
+
if (result.kind !== 'page' || (cursor && result.revision !== cursor.revision)) {
|
|
139
|
+
throw new RuntimeHostCatalogReadError('session', 'invalid_projection');
|
|
140
|
+
}
|
|
141
|
+
if (cursor && result.nextCursor === cursor.cursor) {
|
|
142
|
+
throw new RuntimeHostCatalogReadError('session', 'repeated_cursor');
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
revision: result.revision,
|
|
146
|
+
sessions: result.sessions,
|
|
147
|
+
nextCursor: result.nextCursor === null ? null : { revision: result.revision, cursor: result.nextCursor },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
123
150
|
export async function readRuntimeHostProjects(connection) {
|
|
124
151
|
return readRuntimeHostProjectCatalog(connection, 'summary');
|
|
125
152
|
}
|
|
@@ -22,7 +22,7 @@ import { performance } from 'node:perf_hooks';
|
|
|
22
22
|
import WebSocket from 'ws';
|
|
23
23
|
import { discoverMarkedStorageRoot, prepareStorageRootControlDirectory, resolveExistingStorageRootControlDirectory, resolveStorageRoot, } from '@maka/storage/root-authority';
|
|
24
24
|
import { readHostRegistration, RuntimeHostRegistrationError } from '../control/registration.js';
|
|
25
|
-
import { decodeHostFrame, encodeProtocolMessage, isClientCapabilityHostFrameKind, HOST_OPERATION_SPECS, INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_COMPATIBILITY_EPOCH, RUNTIME_HOST_MAX_IN_FLIGHT_DOMAIN_REQUESTS, RUNTIME_HOST_MAX_MESSAGE_BYTES, requireClientInstanceId, requireHostCompositionId, requireHostGeneration, requireHostRootId, validateProtocolRange, } from '../protocol/index.js';
|
|
25
|
+
import { decodeHostFrame, encodeProtocolMessage, isClientCapabilityHostFrameKind, isHostActivityIdle, HOST_OPERATION_SPECS, INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_COMPATIBILITY_EPOCH, RUNTIME_HOST_MAX_IN_FLIGHT_DOMAIN_REQUESTS, RUNTIME_HOST_MAX_MESSAGE_BYTES, requireClientInstanceId, requireHostCompositionId, requireHostGeneration, requireHostRootId, validateProtocolRange, } from '../protocol/index.js';
|
|
26
26
|
import { FramedTransport, RuntimeHostTransportError } from '../transport/framed-transport.js';
|
|
27
27
|
import { WebSocketTransport } from '../transport/websocket-transport.js';
|
|
28
28
|
import { ClientSessionSubscription, RuntimeHostSubscriptionError, } from './session-subscription.js';
|
|
@@ -94,17 +94,20 @@ class RuntimeHostConnectionImpl {
|
|
|
94
94
|
#sessionCatalogChangeListeners = new Set();
|
|
95
95
|
#scheduledTaskChangeListeners = new Set();
|
|
96
96
|
#livenessTimer;
|
|
97
|
+
#livenessProbeDeadline;
|
|
97
98
|
#livenessProbePending = false;
|
|
98
99
|
#inFlightDomainRequests = 0;
|
|
99
100
|
#terminalError;
|
|
100
101
|
#livenessIntervalMs;
|
|
101
102
|
#onLivenessProbe;
|
|
103
|
+
#onHostStatus;
|
|
102
104
|
constructor(transport, accepted,
|
|
103
105
|
// livenessIntervalMs is validated by connectResolvedRuntimeHost alongside
|
|
104
106
|
// the other connect timeouts, before any transport work happens.
|
|
105
107
|
options) {
|
|
106
108
|
this.#livenessIntervalMs = options?.livenessIntervalMs ?? DEFAULT_LIVENESS_INTERVAL_MS;
|
|
107
109
|
this.#onLivenessProbe = options?.onLivenessProbe;
|
|
110
|
+
this.#onHostStatus = options?.onHostStatus;
|
|
108
111
|
this.#transport = transport;
|
|
109
112
|
this.rootId = accepted.rootId;
|
|
110
113
|
this.hostEpoch = accepted.hostEpoch;
|
|
@@ -131,6 +134,7 @@ class RuntimeHostConnectionImpl {
|
|
|
131
134
|
onFailure: (error) => this.#fail(error),
|
|
132
135
|
});
|
|
133
136
|
void this.#readResponses();
|
|
137
|
+
this.#scheduleLivenessCheck();
|
|
134
138
|
}
|
|
135
139
|
request(operation, input, timeoutMs) {
|
|
136
140
|
if (isHostStatusOperation(operation)) {
|
|
@@ -180,7 +184,6 @@ class RuntimeHostConnectionImpl {
|
|
|
180
184
|
...(isDomainRequest ? { domainState: 'queued' } : {}),
|
|
181
185
|
timer,
|
|
182
186
|
});
|
|
183
|
-
this.#scheduleLivenessCheck();
|
|
184
187
|
});
|
|
185
188
|
const frame = {
|
|
186
189
|
requestId,
|
|
@@ -221,6 +224,12 @@ class RuntimeHostConnectionImpl {
|
|
|
221
224
|
this.#fail(error);
|
|
222
225
|
throw error;
|
|
223
226
|
}
|
|
227
|
+
try {
|
|
228
|
+
this.#onHostStatus?.(status);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Observation cannot control the authenticated connection it watches.
|
|
232
|
+
}
|
|
224
233
|
return status;
|
|
225
234
|
}
|
|
226
235
|
openSessionSubscription(input, timeoutMs) {
|
|
@@ -333,7 +342,6 @@ class RuntimeHostConnectionImpl {
|
|
|
333
342
|
if (retired?.operation === frame.operation) {
|
|
334
343
|
this.#retiredRequests.delete(frame.requestId);
|
|
335
344
|
this.#releaseDomainSlot(retired);
|
|
336
|
-
this.#scheduleLivenessCheck();
|
|
337
345
|
return;
|
|
338
346
|
}
|
|
339
347
|
this.#fail(new Error('Runtime Host returned an unmatched operation response'));
|
|
@@ -346,7 +354,6 @@ class RuntimeHostConnectionImpl {
|
|
|
346
354
|
this.#pendingRequests.delete(frame.requestId);
|
|
347
355
|
if (pending.timer)
|
|
348
356
|
clearTimeout(pending.timer);
|
|
349
|
-
this.#scheduleLivenessCheck();
|
|
350
357
|
if (frame.ok) {
|
|
351
358
|
try {
|
|
352
359
|
const accepted = pending.accept(frame.result);
|
|
@@ -423,7 +430,6 @@ class RuntimeHostConnectionImpl {
|
|
|
423
430
|
if (index !== -1)
|
|
424
431
|
this.#queuedDomainFrames.splice(index, 1);
|
|
425
432
|
pending.reject(interruptedRequestError(pending.operation, 'not_dispatched', 'timeout', error));
|
|
426
|
-
this.#scheduleLivenessCheck();
|
|
427
433
|
return;
|
|
428
434
|
}
|
|
429
435
|
this.#retiredRequests.set(requestId, {
|
|
@@ -431,7 +437,6 @@ class RuntimeHostConnectionImpl {
|
|
|
431
437
|
...(pending.domainState === 'in_flight' ? { domainState: pending.domainState } : {}),
|
|
432
438
|
});
|
|
433
439
|
pending.reject(interruptedRequestError(pending.operation, 'dispatched', 'timeout', error));
|
|
434
|
-
this.#scheduleLivenessCheck();
|
|
435
440
|
}
|
|
436
441
|
#releaseDomainSlot(request) {
|
|
437
442
|
if (request.domainState !== 'in_flight')
|
|
@@ -441,20 +446,20 @@ class RuntimeHostConnectionImpl {
|
|
|
441
446
|
this.#drainDomainRequests();
|
|
442
447
|
}
|
|
443
448
|
#resetLivenessCheck() {
|
|
449
|
+
// Any authenticated Host frame proves the transport is still making
|
|
450
|
+
// progress, even when a periodic status observation is slow.
|
|
451
|
+
this.#resetLivenessProbeDeadline();
|
|
452
|
+
// A status observer needs periodic route observations even while other
|
|
453
|
+
// Session traffic keeps the connection active.
|
|
454
|
+
if (this.#onHostStatus)
|
|
455
|
+
return;
|
|
444
456
|
if (this.#livenessTimer)
|
|
445
457
|
clearTimeout(this.#livenessTimer);
|
|
446
458
|
this.#livenessTimer = undefined;
|
|
447
459
|
this.#scheduleLivenessCheck();
|
|
448
460
|
}
|
|
449
461
|
#scheduleLivenessCheck() {
|
|
450
|
-
if (this.#terminalError ||
|
|
451
|
-
this.#livenessTimer ||
|
|
452
|
-
this.#livenessProbePending ||
|
|
453
|
-
!this.#hasOutstandingDomainRequest()) {
|
|
454
|
-
if (!this.#hasOutstandingDomainRequest() && this.#livenessTimer) {
|
|
455
|
-
clearTimeout(this.#livenessTimer);
|
|
456
|
-
this.#livenessTimer = undefined;
|
|
457
|
-
}
|
|
462
|
+
if (this.#terminalError || this.#livenessTimer || this.#livenessProbePending) {
|
|
458
463
|
return;
|
|
459
464
|
}
|
|
460
465
|
this.#livenessTimer = setTimeout(() => {
|
|
@@ -462,21 +467,12 @@ class RuntimeHostConnectionImpl {
|
|
|
462
467
|
this.#startLivenessProbe();
|
|
463
468
|
}, this.#livenessIntervalMs);
|
|
464
469
|
}
|
|
465
|
-
#hasOutstandingDomainRequest() {
|
|
466
|
-
if (this.#retiredRequests.size > 0)
|
|
467
|
-
return true;
|
|
468
|
-
for (const pending of this.#pendingRequests.values()) {
|
|
469
|
-
if (pending.operation !== 'host.status')
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
470
|
#startLivenessProbe() {
|
|
475
|
-
if (this.#terminalError || this.#livenessProbePending
|
|
471
|
+
if (this.#terminalError || this.#livenessProbePending)
|
|
476
472
|
return;
|
|
477
|
-
}
|
|
478
473
|
this.#livenessProbePending = true;
|
|
479
|
-
|
|
474
|
+
this.#resetLivenessProbeDeadline();
|
|
475
|
+
void this.#requestOperation('host.status', {}, undefined, (status) => {
|
|
480
476
|
this.#validateHostStatusIdentity(status);
|
|
481
477
|
try {
|
|
482
478
|
this.#onLivenessProbe?.();
|
|
@@ -485,13 +481,26 @@ class RuntimeHostConnectionImpl {
|
|
|
485
481
|
// Diagnostics hook: an observer exception must never fail the
|
|
486
482
|
// connection it is watching.
|
|
487
483
|
}
|
|
488
|
-
}, '
|
|
484
|
+
}, 'request')
|
|
489
485
|
.catch((error) => this.#fail(asError(error)))
|
|
490
486
|
.finally(() => {
|
|
487
|
+
if (this.#livenessProbeDeadline)
|
|
488
|
+
clearTimeout(this.#livenessProbeDeadline);
|
|
489
|
+
this.#livenessProbeDeadline = undefined;
|
|
491
490
|
this.#livenessProbePending = false;
|
|
492
491
|
this.#scheduleLivenessCheck();
|
|
493
492
|
});
|
|
494
493
|
}
|
|
494
|
+
#resetLivenessProbeDeadline() {
|
|
495
|
+
if (!this.#livenessProbePending || this.#terminalError)
|
|
496
|
+
return;
|
|
497
|
+
if (this.#livenessProbeDeadline)
|
|
498
|
+
clearTimeout(this.#livenessProbeDeadline);
|
|
499
|
+
this.#livenessProbeDeadline = setTimeout(() => {
|
|
500
|
+
this.#livenessProbeDeadline = undefined;
|
|
501
|
+
this.#fail(requestTimeoutError('host.status'));
|
|
502
|
+
}, DEFAULT_LIVENESS_TIMEOUT_MS);
|
|
503
|
+
}
|
|
495
504
|
#acceptSubscriptionFrame(frame) {
|
|
496
505
|
const subscription = this.#subscriptions.get(frame.subscriptionId);
|
|
497
506
|
if (!subscription) {
|
|
@@ -545,6 +554,9 @@ class RuntimeHostConnectionImpl {
|
|
|
545
554
|
if (this.#livenessTimer)
|
|
546
555
|
clearTimeout(this.#livenessTimer);
|
|
547
556
|
this.#livenessTimer = undefined;
|
|
557
|
+
if (this.#livenessProbeDeadline)
|
|
558
|
+
clearTimeout(this.#livenessProbeDeadline);
|
|
559
|
+
this.#livenessProbeDeadline = undefined;
|
|
548
560
|
this.#queuedDomainFrames.length = 0;
|
|
549
561
|
this.#inFlightDomainRequests = 0;
|
|
550
562
|
for (const pending of this.#pendingRequests.values()) {
|
|
@@ -628,6 +640,7 @@ export async function connectRemoteRuntimeHost(input) {
|
|
|
628
640
|
handshakeTimeoutMs: normalized.handshakeTimeoutMs,
|
|
629
641
|
livenessIntervalMs: normalized.livenessIntervalMs,
|
|
630
642
|
onLivenessProbe: input.onLivenessProbe,
|
|
643
|
+
onHostStatus: input.onHostStatus,
|
|
631
644
|
connectionResource: input.connectionResource,
|
|
632
645
|
});
|
|
633
646
|
}
|
|
@@ -639,11 +652,13 @@ export async function connectRemoteRuntimeHost(input) {
|
|
|
639
652
|
export async function connectRuntimeHostMessageTransport(input) {
|
|
640
653
|
let resourceTransferred = false;
|
|
641
654
|
let timer;
|
|
655
|
+
let handshakeTimedOut = false;
|
|
642
656
|
try {
|
|
643
657
|
const normalized = normalizeConnectRuntimeHostInput(input);
|
|
644
658
|
const compositionId = requireHostCompositionId(input.compositionId);
|
|
645
659
|
const expectedRootId = requireHostRootId(input.expectedRootId);
|
|
646
660
|
timer = setTimeout(() => {
|
|
661
|
+
handshakeTimedOut = true;
|
|
647
662
|
input.transport.abort(new Error('Timed out handshaking with Runtime Host'));
|
|
648
663
|
}, normalized.handshakeTimeoutMs);
|
|
649
664
|
const result = await exchangeRuntimeHostHandshake({
|
|
@@ -654,6 +669,7 @@ export async function connectRuntimeHostMessageTransport(input) {
|
|
|
654
669
|
expectedRootId,
|
|
655
670
|
livenessIntervalMs: normalized.livenessIntervalMs,
|
|
656
671
|
onLivenessProbe: input.onLivenessProbe,
|
|
672
|
+
onHostStatus: input.onHostStatus,
|
|
657
673
|
connectionResource: input.connectionResource,
|
|
658
674
|
...(input.peerPath ? { peerPath: input.peerPath } : {}),
|
|
659
675
|
});
|
|
@@ -672,7 +688,10 @@ export async function connectRuntimeHostMessageTransport(input) {
|
|
|
672
688
|
if (error instanceof RuntimeHostCompositionMismatchError) {
|
|
673
689
|
return { kind: 'unavailable', reason: 'composition_mismatch' };
|
|
674
690
|
}
|
|
675
|
-
return {
|
|
691
|
+
return {
|
|
692
|
+
kind: 'unavailable',
|
|
693
|
+
reason: handshakeTimedOut ? 'handshake_timed_out' : 'handshake_failed',
|
|
694
|
+
};
|
|
676
695
|
}
|
|
677
696
|
finally {
|
|
678
697
|
if (timer)
|
|
@@ -819,6 +838,7 @@ export async function connectResolvedRuntimeHost(input) {
|
|
|
819
838
|
hostProtocol: { min: registration.protocolMin, max: registration.protocolMax },
|
|
820
839
|
livenessIntervalMs,
|
|
821
840
|
onLivenessProbe: input.onLivenessProbe,
|
|
841
|
+
onHostStatus: input.onHostStatus,
|
|
822
842
|
});
|
|
823
843
|
if (result.kind === 'connected') {
|
|
824
844
|
if (generation !== undefined &&
|
|
@@ -838,9 +858,7 @@ export async function connectResolvedRuntimeHost(input) {
|
|
|
838
858
|
return registration.lifecycleMode === 'ephemeral' &&
|
|
839
859
|
result.handshake.state === 'ready' &&
|
|
840
860
|
result.handshake.activity !== undefined &&
|
|
841
|
-
result.handshake.activity
|
|
842
|
-
result.handshake.activity.activeOperations === 0 &&
|
|
843
|
-
result.handshake.activity.residencies.length === 0
|
|
861
|
+
isHostActivityIdle(result.handshake.activity)
|
|
844
862
|
? {
|
|
845
863
|
kind: 'upgrade_required',
|
|
846
864
|
registration,
|
|
@@ -954,6 +972,7 @@ async function exchangeRuntimeHostHandshake(input) {
|
|
|
954
972
|
connection: new RuntimeHostConnectionImpl(input.transport, handshake, {
|
|
955
973
|
livenessIntervalMs: input.livenessIntervalMs,
|
|
956
974
|
onLivenessProbe: input.onLivenessProbe,
|
|
975
|
+
onHostStatus: input.onHostStatus,
|
|
957
976
|
connectionResource: input.connectionResource,
|
|
958
977
|
...(input.peerPath ? { peerPath: input.peerPath } : {}),
|
|
959
978
|
}),
|
|
@@ -41,6 +41,7 @@ const PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
|
41
41
|
const PEER_ID_MAX_BYTES = 160;
|
|
42
42
|
const PEER_ADDRESS_MAX_BYTES = 2 * 1024;
|
|
43
43
|
const PEER_ROUTE_MAX = 16;
|
|
44
|
+
const DEFAULT_PEER_HANDSHAKE_TIMEOUT_MS = 5_000;
|
|
44
45
|
const PROFILE_CREDENTIAL_RECORD_PREFIX = 'maka-runtime-host-profile-credential-v1:';
|
|
45
46
|
const PROFILE_INCARNATION_ID_MAX_BYTES = 128;
|
|
46
47
|
export const RUNTIME_HOST_ACCESS_CREDENTIAL_MAX_BYTES = 8 * 1024;
|
|
@@ -173,6 +174,9 @@ export async function connectRemoteRuntimeHostProfile(input, overrides = {}) {
|
|
|
173
174
|
expectedRootId: input.profile.rootId,
|
|
174
175
|
clientInstanceId: input.clientInstanceId,
|
|
175
176
|
peerClient: requireRuntimeHostPeerClient(input.peerClient),
|
|
177
|
+
...(input.refreshPeerRoutes === undefined
|
|
178
|
+
? {}
|
|
179
|
+
: { refreshPeerRoutes: input.refreshPeerRoutes }),
|
|
176
180
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
177
181
|
...(input.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: input.connectTimeoutMs }),
|
|
178
182
|
...(input.handshakeTimeoutMs === undefined
|
|
@@ -181,6 +185,7 @@ export async function connectRemoteRuntimeHostProfile(input, overrides = {}) {
|
|
|
181
185
|
...(input.onConnectionPhase === undefined
|
|
182
186
|
? {}
|
|
183
187
|
: { onConnectionPhase: input.onConnectionPhase }),
|
|
188
|
+
...(input.onHostStatus === undefined ? {} : { onHostStatus: input.onHostStatus }),
|
|
184
189
|
});
|
|
185
190
|
}
|
|
186
191
|
else {
|
|
@@ -224,6 +229,7 @@ export async function connectRemoteRuntimeHostProfile(input, overrides = {}) {
|
|
|
224
229
|
...(input.handshakeTimeoutMs === undefined
|
|
225
230
|
? {}
|
|
226
231
|
: { handshakeTimeoutMs: input.handshakeTimeoutMs }),
|
|
232
|
+
...(input.onHostStatus === undefined ? {} : { onHostStatus: input.onHostStatus }),
|
|
227
233
|
});
|
|
228
234
|
try {
|
|
229
235
|
input.signal?.throwIfAborted();
|
|
@@ -272,11 +278,13 @@ function requireConnectOnlySshEndpoint(transport) {
|
|
|
272
278
|
}
|
|
273
279
|
export async function connectPeerRuntimeHost(input) {
|
|
274
280
|
input.signal?.throwIfAborted();
|
|
281
|
+
const handshakeTimeoutMs = input.handshakeTimeoutMs ?? DEFAULT_PEER_HANDSHAKE_TIMEOUT_MS;
|
|
275
282
|
const stream = await input.peerClient.connect({
|
|
276
283
|
peerId: input.transport.peerId,
|
|
277
284
|
routeHints: input.transport.routeHints,
|
|
278
285
|
coordinationRelays: input.transport.coordinationRelays,
|
|
279
286
|
directDeadlineMs: Math.min(input.connectTimeoutMs ?? 40_000, 120_000),
|
|
287
|
+
...(input.refreshPeerRoutes === undefined ? {} : { refreshRoutes: input.refreshPeerRoutes }),
|
|
280
288
|
}, input.signal, input.onConnectionPhase);
|
|
281
289
|
const abort = () => stream.abort();
|
|
282
290
|
input.signal?.addEventListener('abort', abort, { once: true });
|
|
@@ -287,7 +295,7 @@ export async function connectPeerRuntimeHost(input) {
|
|
|
287
295
|
input.signal?.throwIfAborted();
|
|
288
296
|
notifyConnectionPhase(input.onConnectionPhase, 'authenticating');
|
|
289
297
|
await writeRuntimeHostPeerAuthentication(stream, input.credential);
|
|
290
|
-
const authentication = await readRuntimeHostPeerAuthenticationResult(stream,
|
|
298
|
+
const authentication = await readRuntimeHostPeerAuthenticationResult(stream, handshakeTimeoutMs);
|
|
291
299
|
if (!authentication.accepted) {
|
|
292
300
|
throw new RuntimeHostProfileConnectionError('credential_rejected', `Runtime Host profile ${input.profileId} rejected its access credential`);
|
|
293
301
|
}
|
|
@@ -301,9 +309,14 @@ export async function connectPeerRuntimeHost(input) {
|
|
|
301
309
|
max: RUNTIME_HOST_PROTOCOL_VERSION,
|
|
302
310
|
},
|
|
303
311
|
clientInstanceId: input.clientInstanceId,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
312
|
+
handshakeTimeoutMs,
|
|
313
|
+
onHostStatus: (status) => {
|
|
314
|
+
const endpoint = status.peerEndpoint;
|
|
315
|
+
if (endpoint?.peerId === input.transport.peerId) {
|
|
316
|
+
input.peerClient.observeAuthenticatedRoutes(endpoint);
|
|
317
|
+
}
|
|
318
|
+
input.onHostStatus?.(status);
|
|
319
|
+
},
|
|
307
320
|
...(stream.path ? { peerPath: stream.path } : {}),
|
|
308
321
|
});
|
|
309
322
|
input.signal?.throwIfAborted();
|
|
@@ -357,6 +370,9 @@ export function remoteRuntimeHostUnavailableError(subject, reason) {
|
|
|
357
370
|
case 'unreachable':
|
|
358
371
|
message = `${subject} could not reach its endpoint`;
|
|
359
372
|
break;
|
|
373
|
+
case 'handshake_timed_out':
|
|
374
|
+
message = `${subject} timed out while establishing its protocol session`;
|
|
375
|
+
break;
|
|
360
376
|
default:
|
|
361
377
|
message = `${subject} is unavailable (${reason})`;
|
|
362
378
|
}
|
|
@@ -589,6 +605,34 @@ class FileRuntimeHostProfileCatalog {
|
|
|
589
605
|
return { rebound: true, document: next };
|
|
590
606
|
});
|
|
591
607
|
}
|
|
608
|
+
updateRemoteProfileIfCurrent(target, update) {
|
|
609
|
+
const expectedProfile = decodeRemoteRuntimeHostProfile(target.profile);
|
|
610
|
+
const expectedIncarnationId = requireProfileIncarnationId(target.profileIncarnationId);
|
|
611
|
+
return this.#exclusive(async () => {
|
|
612
|
+
const current = await this.#readSnapshot();
|
|
613
|
+
const profile = current.profiles.find((candidate) => candidate.id === expectedProfile.id && candidate.kind === 'remote');
|
|
614
|
+
if (!profile || !sameRemoteRuntimeHostProfileTarget(profile, expectedProfile))
|
|
615
|
+
return false;
|
|
616
|
+
const credential = await this.credentials.get(profile);
|
|
617
|
+
if (credential?.profileIncarnationId !== expectedIncarnationId)
|
|
618
|
+
return false;
|
|
619
|
+
const value = update(profile);
|
|
620
|
+
if (value === profile)
|
|
621
|
+
return true;
|
|
622
|
+
const updated = decodeRemoteRuntimeHostProfile(value);
|
|
623
|
+
if (updated.id !== profile.id ||
|
|
624
|
+
!sameRemoteRuntimeHostProfileTarget(updated, profile) ||
|
|
625
|
+
runtimeHostProfileAccess(updated) !== runtimeHostProfileAccess(profile)) {
|
|
626
|
+
throw new Error('A Runtime Host profile metadata update must retain its connection');
|
|
627
|
+
}
|
|
628
|
+
const next = decodeRuntimeHostProfileDocument({
|
|
629
|
+
schemaVersion: PROFILE_SCHEMA_VERSION,
|
|
630
|
+
profiles: current.profiles.map((candidate) => candidate.id === updated.id ? updated : candidate),
|
|
631
|
+
});
|
|
632
|
+
await writeProfileDocument(this.path, next);
|
|
633
|
+
return true;
|
|
634
|
+
});
|
|
635
|
+
}
|
|
592
636
|
mutateRemoteProfileIfCurrent(target, mutation) {
|
|
593
637
|
const expectedProfile = decodeRemoteRuntimeHostProfile(target.profile);
|
|
594
638
|
const expectedIncarnationId = requireProfileIncarnationId(target.profileIncarnationId);
|
|
@@ -30,7 +30,7 @@ export { RUNTIME_HOST_REMOTE_INCOMPATIBLE_CODE, RuntimeHostRemoteCompatibilityEr
|
|
|
30
30
|
export { RuntimeHostSubscriptionError, } from './session-subscription.js';
|
|
31
31
|
export { RuntimeHostStartupError, runtimeHostStartupError, } from './startup-error.js';
|
|
32
32
|
export { connectRuntimeHostWslEnvironment, listRuntimeHostWslDistributions, normalizeRuntimeHostWslDistribution, normalizeRuntimeHostWslOperatorPath, resolveSystemRuntimeHostWslExecutable, } from './wsl-environment.js';
|
|
33
|
-
export { RuntimeHostCatalogReadError, readRuntimeHostConnectionCatalog, readRuntimeHostInvocableSkills, readRuntimeHostProjectDetails, readRuntimeHostResources, readRuntimeHostProjects, readRuntimeHostSessions, readRuntimeHostSkillCatalog, } from './catalog-reader.js';
|
|
33
|
+
export { RuntimeHostCatalogReadError, RuntimeHostSessionCatalogRevisionChangedError, readRuntimeHostConnectionCatalog, readRuntimeHostInvocableSkills, readRuntimeHostProjectDetails, readRuntimeHostResources, readRuntimeHostProjects, readRuntimeHostSessionCatalogPage, readRuntimeHostSessions, readRuntimeHostSkillCatalog, } from './catalog-reader.js';
|
|
34
34
|
export { connectOrSpawnRuntimeHost, } from './connect-or-spawn.js';
|
|
35
35
|
export { abortable, waitForRuntimeHostReady } from './wait-for-ready.js';
|
|
36
36
|
export { createRuntimeHostCandidateLaunchBarrier, } from './candidate-launch-barrier.js';
|
|
@@ -40,7 +40,7 @@ export { startRuntimeHostCapabilityProviderService, } from './capability-provide
|
|
|
40
40
|
export { loadOrCreateRuntimeHostClientInstanceId } from './client-instance-identity.js';
|
|
41
41
|
export { projectSessionCatalogSummary } from './session-catalog-summary.js';
|
|
42
42
|
export { consumeAccessCredentialDelivery } from '../control/access-credential-delivery.js';
|
|
43
|
-
export { decodeRuntimeHostOwnerConnectionCode, encodeRuntimeHostOwnerConnectionCode, } from './owner-connection-code.js';
|
|
43
|
+
export { decodeRuntimeHostOwnerConnectionCode, encodeRuntimeHostOwnerConnectionCode, issueRuntimeHostOwnerConnectionCode, REMOTE_DESKTOP_OWNER_ACCESS_POLICY, } from './owner-connection-code.js';
|
|
44
44
|
export { ensureRuntimeHostPeerIdentity } from '../transport/peer-native.js';
|
|
45
45
|
export { createRuntimeHostPeerClient, createRuntimeHostPeerClientFromEnvironment, } from './peer-client.js';
|
|
46
46
|
export { createOAuthPresentationClientProvider, } from './oauth-presentation.js';
|
|
@@ -17,14 +17,22 @@
|
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { z } from 'zod';
|
|
20
|
-
import {
|
|
20
|
+
import { consumeAccessCredentialDelivery } from '../control/access-credential-delivery.js';
|
|
21
|
+
import { REMOTE_OWNER_OPERATION_GRANTS, requireHostRootId } from '../protocol/index.js';
|
|
21
22
|
import { decodeRuntimeHostRemoteTransport, RUNTIME_HOST_ACCESS_CREDENTIAL_MAX_BYTES, } from './host-profile.js';
|
|
22
23
|
const PREFIX = 'maka-runtime-host:connect:v1:';
|
|
23
24
|
const ENCODED_MAX_BYTES = 48 * 1024;
|
|
25
|
+
export const REMOTE_DESKTOP_OWNER_ACCESS_POLICY = Object.freeze({
|
|
26
|
+
principalKind: 'remote_owner',
|
|
27
|
+
operationGrants: REMOTE_OWNER_OPERATION_GRANTS,
|
|
28
|
+
canPublishClientCapabilities: true,
|
|
29
|
+
canUseHostPaths: false,
|
|
30
|
+
});
|
|
24
31
|
const boundedString = (maxBytes) => z
|
|
25
32
|
.string()
|
|
26
33
|
.min(1)
|
|
27
34
|
.refine((value) => Buffer.byteLength(value, 'utf8') <= maxBytes);
|
|
35
|
+
const nameSchema = boundedString(128);
|
|
28
36
|
const payloadSchema = z
|
|
29
37
|
.object({
|
|
30
38
|
schemaVersion: z.literal(1),
|
|
@@ -42,6 +50,29 @@ const payloadSchema = z
|
|
|
42
50
|
credential: boundedString(RUNTIME_HOST_ACCESS_CREDENTIAL_MAX_BYTES),
|
|
43
51
|
})
|
|
44
52
|
.strict();
|
|
53
|
+
/**
|
|
54
|
+
* Issue one pending Owner credential and bind it to the Host's current,
|
|
55
|
+
* authenticated Direct peer endpoint. The credential remains one-time and
|
|
56
|
+
* short-lived until the importing Client finalizes it.
|
|
57
|
+
*/
|
|
58
|
+
export async function issueRuntimeHostOwnerConnectionCode(input) {
|
|
59
|
+
const name = nameSchema.parse(input.name);
|
|
60
|
+
const rootId = requireHostRootId(input.client.rootId);
|
|
61
|
+
const endpoint = (await input.client.status()).peerEndpoint;
|
|
62
|
+
if (!endpoint)
|
|
63
|
+
throw new Error('Runtime Host Direct peer is not available');
|
|
64
|
+
if (input.expectedPeerId && endpoint.peerId !== input.expectedPeerId) {
|
|
65
|
+
throw new Error('Runtime Host Direct peer identity changed');
|
|
66
|
+
}
|
|
67
|
+
const transport = requireDirectPeerTransport({ kind: 'libp2p-direct', ...endpoint });
|
|
68
|
+
const prepared = await input.client.request('access.credential.prepare', {
|
|
69
|
+
...REMOTE_DESKTOP_OWNER_ACCESS_POLICY,
|
|
70
|
+
principalId: input.principalId,
|
|
71
|
+
bindClientInstance: true,
|
|
72
|
+
});
|
|
73
|
+
const credential = await consumeAccessCredentialDelivery(input.rootPath, prepared.deliveryId, prepared.credentialId);
|
|
74
|
+
return encodeRuntimeHostOwnerConnectionCode({ name, rootId, transport, credential });
|
|
75
|
+
}
|
|
45
76
|
export function encodeRuntimeHostOwnerConnectionCode(input) {
|
|
46
77
|
const transport = requireDirectPeerTransport(input.transport);
|
|
47
78
|
const payload = payloadSchema.parse({ schemaVersion: 1, ...input, transport });
|
|
@@ -38,6 +38,7 @@ class RuntimeHostPeerClientImpl {
|
|
|
38
38
|
#automaticRelayDiscovery;
|
|
39
39
|
#webRtcStunUrls;
|
|
40
40
|
#routeResolver;
|
|
41
|
+
#authenticatedRoutes = new Map();
|
|
41
42
|
#endpoint;
|
|
42
43
|
#draining;
|
|
43
44
|
#meshDraining;
|
|
@@ -95,9 +96,19 @@ class RuntimeHostPeerClientImpl {
|
|
|
95
96
|
throw normalizePeerError(error);
|
|
96
97
|
});
|
|
97
98
|
}
|
|
99
|
+
observeAuthenticatedRoutes(input) {
|
|
100
|
+
if (input.routeHints.length === 0 && input.coordinationRelays.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
this.#authenticatedRoutes.set(input.peerId, Object.freeze({
|
|
103
|
+
routeHints: Object.freeze([...input.routeHints]),
|
|
104
|
+
coordinationRelays: Object.freeze([...input.coordinationRelays]),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
98
107
|
async connect(input, signal, onPhase) {
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
if (input.refreshRoutes !== false) {
|
|
109
|
+
notifyPhase(onPhase, 'discovering');
|
|
110
|
+
await this.#prepareRoutes(input, signal);
|
|
111
|
+
}
|
|
101
112
|
notifyPhase(onPhase, 'connecting');
|
|
102
113
|
return this.#connect(input, signal, 'application');
|
|
103
114
|
}
|
|
@@ -193,10 +204,17 @@ class RuntimeHostPeerClientImpl {
|
|
|
193
204
|
const endpoint = this.#requireEndpoint();
|
|
194
205
|
const requestId = this.#allocateRequestId();
|
|
195
206
|
const discovered = kind === 'application' ? this.#routeResolver?.resolveRoutes(input.peerId) : undefined;
|
|
207
|
+
const authenticated = kind === 'application' ? this.#authenticatedRoutes.get(input.peerId) : undefined;
|
|
196
208
|
const connection = endpoint[kind === 'application' ? 'connect' : 'connectMeshControl']({
|
|
197
209
|
...input,
|
|
198
|
-
routeHints: mergeAddresses(discovered?.routeHints ?? [],
|
|
199
|
-
|
|
210
|
+
routeHints: mergeAddresses(discovered?.routeHints ?? [], [
|
|
211
|
+
...(authenticated?.routeHints ?? []),
|
|
212
|
+
...input.routeHints,
|
|
213
|
+
]),
|
|
214
|
+
coordinationRelays: mergeAddresses(discovered?.coordinationRelays ?? [], [
|
|
215
|
+
...(authenticated?.coordinationRelays ?? []),
|
|
216
|
+
...(input.coordinationRelays ?? []),
|
|
217
|
+
]),
|
|
200
218
|
transitRelayPeerIds: mergeValues(discovered?.transitRelayPeerIds ?? [], input.transitRelayPeerIds, 64),
|
|
201
219
|
requestId,
|
|
202
220
|
});
|