@mobileaidev/ai-app-bridge 0.2.15 → 0.3.0-rc.2
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/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +306 -53
- package/bin/ai-app-bridge.js +51 -4504
- package/bin/android-permissions.js +152 -0
- package/bin/android-uia-xml.js +74 -0
- package/bin/artifact-paths.js +127 -1
- package/bin/bridge-forward.js +56 -0
- package/bin/command-discovery.js +86 -0
- package/bin/command-errors.js +60 -0
- package/bin/command-registry.js +504 -0
- package/bin/command-request.js +14 -0
- package/bin/command-router.js +80 -0
- package/bin/connection-cache.js +119 -0
- package/bin/device-provider.js +2934 -0
- package/bin/execution-host.js +596 -0
- package/bin/execution-runtime.js +101 -0
- package/bin/fact-codec.js +321 -0
- package/bin/fact-recorder.js +691 -0
- package/bin/fact-store.js +226 -0
- package/bin/feedback-probe.js +285 -0
- package/bin/intent/install-intent.js +267 -0
- package/bin/intent/intent-action-executor.js +129 -0
- package/bin/intent/intent-autonomous-adapter.js +46 -0
- package/bin/intent/intent-capture-port.js +64 -0
- package/bin/intent/intent-entry.js +226 -0
- package/bin/intent/intent-errors.js +28 -0
- package/bin/intent/intent-evidence-store.js +121 -0
- package/bin/intent/intent-lifetime.js +62 -0
- package/bin/intent/intent-observation-target.js +33 -0
- package/bin/intent/intent-observer.js +194 -0
- package/bin/intent/intent-production-adapter.js +334 -0
- package/bin/intent/intent-provider.js +27 -0
- package/bin/intent/intent-runtime.js +63 -0
- package/bin/intent/intent-worker.js +432 -0
- package/bin/intent/ios-intent-adapter.js +90 -0
- package/bin/intent/permission-intent.js +239 -0
- package/bin/intent/web-intent-adapter.js +31 -0
- package/bin/ios-device-outcome.js +47 -0
- package/bin/ios-execution.js +108 -0
- package/bin/ios-provider.js +497 -583
- package/bin/ios-runtime-binding.js +54 -0
- package/bin/ios-wda-execution.js +70 -0
- package/bin/ios-wda-port.js +98 -0
- package/bin/ios-wda-project.js +79 -0
- package/bin/mcp-server.js +83 -1040
- package/bin/mmap-scan-index.js +329 -0
- package/bin/observation-collector.js +862 -0
- package/bin/runtime-client.js +154 -0
- package/bin/runtime-directory.js +107 -0
- package/bin/runtime-protocol.js +36 -0
- package/bin/script/bounded-script-registry.js +103 -0
- package/bin/script/node-runtime-adapter.js +233 -0
- package/bin/script/progress-projector.js +63 -0
- package/bin/script/python-runtime-adapter.js +111 -0
- package/bin/script/rolling-summary.js +134 -0
- package/bin/script/script-agent-port.js +40 -0
- package/bin/script/script-assert.js +95 -0
- package/bin/script/script-capture-port.js +76 -0
- package/bin/script/script-catalog.js +85 -0
- package/bin/script/script-durable-restore.js +195 -0
- package/bin/script/script-entry-code.js +26 -0
- package/bin/script/script-entry-route.js +38 -0
- package/bin/script/script-entry.js +3 -0
- package/bin/script/script-errors.js +29 -0
- package/bin/script/script-evidence-store.js +22 -0
- package/bin/script/script-format-removed.js +26 -0
- package/bin/script/script-host-port.js +397 -0
- package/bin/script/script-ledger.js +64 -0
- package/bin/script/script-result.js +57 -0
- package/bin/script/script-sdk.js +152 -0
- package/bin/script/script-sdk.py +153 -0
- package/bin/script/script-session-channel.js +127 -0
- package/bin/script/script-spec.js +86 -0
- package/bin/script/script-supervisor.js +919 -0
- package/bin/script/templates/checkpoint-reentry.js +13 -0
- package/bin/segment-index.js +481 -0
- package/bin/segmented-fact-store.js +1571 -0
- package/bin/shared-kernel/android-h5-target.js +10 -0
- package/bin/shared-kernel/android-install-execution.js +176 -0
- package/bin/shared-kernel/android-sdk-endpoint.js +42 -0
- package/bin/shared-kernel/android-shell-execution.js +195 -0
- package/bin/shared-kernel/argument-schema.js +117 -0
- package/bin/shared-kernel/canonical-path.js +17 -0
- package/bin/shared-kernel/device-acknowledgements.js +53 -0
- package/bin/shared-kernel/device-completion-history.js +52 -0
- package/bin/shared-kernel/device-mutation-lease.js +219 -0
- package/bin/shared-kernel/device-ownership-recovery.js +95 -0
- package/bin/shared-kernel/device-ownership-store.js +94 -0
- package/bin/shared-kernel/evidence-adapters.js +251 -0
- package/bin/shared-kernel/evidence-archive.js +329 -0
- package/bin/shared-kernel/evidence-recording.js +131 -0
- package/bin/shared-kernel/evidence-schema.js +194 -0
- package/bin/shared-kernel/evidence-store.js +190 -0
- package/bin/shared-kernel/execution-admission.js +22 -0
- package/bin/shared-kernel/execution-contracts.js +171 -0
- package/bin/shared-kernel/execution-io.js +106 -0
- package/bin/shared-kernel/execution-ledger.js +125 -0
- package/bin/shared-kernel/execution-scope.js +87 -0
- package/bin/shared-kernel/execution-target.js +115 -0
- package/bin/shared-kernel/flutter-execution.js +13 -0
- package/bin/shared-kernel/flutter-h5-port.js +60 -0
- package/bin/shared-kernel/flutter-h5-target.js +9 -0
- package/bin/shared-kernel/flutter-target.js +75 -0
- package/bin/shared-kernel/h5-execution.js +11 -0
- package/bin/shared-kernel/h5-target.js +31 -0
- package/bin/shared-kernel/host-fact-store.js +49 -0
- package/bin/shared-kernel/ios-h5-target.js +9 -0
- package/bin/shared-kernel/ios-native-target.js +71 -0
- package/bin/shared-kernel/live-capture-query.js +115 -0
- package/bin/shared-kernel/managed-sdk-execution.js +78 -0
- package/bin/shared-kernel/native-execution.js +13 -0
- package/bin/shared-kernel/native-target.js +156 -0
- package/bin/shared-kernel/provider-command-contracts.js +55 -0
- package/bin/shared-kernel/recorded-payload-archive.js +195 -0
- package/bin/shared-kernel/request-context.js +35 -0
- package/bin/shared-kernel/semantic-node.js +55 -0
- package/bin/shared-kernel/summary-transformer.js +352 -0
- package/bin/shared-kernel/target-lease-protocol.js +47 -0
- package/bin/shared-kernel/text-wait.js +111 -0
- package/bin/shared-kernel/uia-execution.js +96 -0
- package/bin/shared-kernel/uia-protocol.js +214 -0
- package/bin/shared-kernel/uia-runtime-port.js +377 -0
- package/bin/shared-kernel/uia-target.js +39 -0
- package/bin/shared-kernel/web-dom-target.js +44 -0
- package/bin/shared-kernel/xml-attributes.js +25 -0
- package/bin/target-execution.js +275 -0
- package/bin/web/command-schema.js +60 -0
- package/bin/web/session-store.js +157 -0
- package/bin/web-provider.js +334 -553
- package/docs/COMMAND_CONTRACT.md +1563 -0
- package/docs/EVIDENCE_ARCHIVE.md +214 -0
- package/docs/INTENT_FOREGROUND.md +71 -0
- package/docs/INTENT_NATIVE_EDITING.md +79 -0
- package/docs/RELEASE.md +59 -0
- package/docs/SCRIPT_AUTHORING.md +489 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/LICENSE +201 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/NOTICE +7 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/binding.gyp +36 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/bindings/node/sfs_node.c +597 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/include/sfs.h +178 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/index.js +5 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/package.json +24 -0
- package/node_modules/@mobileaidev/segmented-fact-store-native/src/sfs.c +2349 -0
- package/package.json +60 -5
- package/runtime/ios-wda/AABWDABinding.h +19 -0
- package/runtime/ios-wda/AABWDABinding.m +97 -0
- package/runtime/ios-wda/AABWDAExecution.h +26 -0
- package/runtime/ios-wda/AABWDAExecution.m +172 -0
- package/runtime/ios-wda/AABWDAIntegration.h +71 -0
- package/runtime/ios-wda/AABWDAManagedRoutes.h +392 -0
- package/runtime/ios-wda/AABWDAReceiptStore.h +10 -0
- package/runtime/ios-wda/AABWDAReceiptStore.m +116 -0
- package/runtime/uia/ai-app-bridge-uia.jar +0 -0
- package/runtime/uia/manifest.json +22 -0
- package/skills/ai-app-bridge-use/SKILL.md +23 -360
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { executeProviderCommand } = require('./device-provider');
|
|
4
|
+
const { validateRunRequest } = require('./command-request');
|
|
5
|
+
const { resolveCommandPaths } = require('./shared-kernel/request-context');
|
|
6
|
+
const { commandFailure, normalizeCommandResult } = require('./command-errors');
|
|
7
|
+
const { runExecution, withoutExecution } = require('./shared-kernel/execution-scope');
|
|
8
|
+
const { getHostFactStore } = require('./shared-kernel/host-fact-store');
|
|
9
|
+
const { FactRecorder, historyDescriptor, isMobileCaptureCommand } = require('./fact-recorder');
|
|
10
|
+
const { runWithFeedbackProbe } = require('./feedback-probe');
|
|
11
|
+
const { IOSBridgeProvider } = require('./ios-provider');
|
|
12
|
+
const { ObservationCollector } = require('./observation-collector');
|
|
13
|
+
const { WebBridgeProvider } = require('./web-provider');
|
|
14
|
+
const { TargetExecution } = require('./target-execution');
|
|
15
|
+
const { createCommandRouter, isolatedCommandDefinitions } = require('./command-router');
|
|
16
|
+
const {
|
|
17
|
+
createLegacyFactStoreAdapter,
|
|
18
|
+
createSegmentedEvidenceAdapter,
|
|
19
|
+
} = require('./shared-kernel/evidence-adapters');
|
|
20
|
+
const iosProvider = new IOSBridgeProvider();
|
|
21
|
+
const webProvider = new WebBridgeProvider();
|
|
22
|
+
const targetExecution = new TargetExecution();
|
|
23
|
+
|
|
24
|
+
let sharedFactStore = null;
|
|
25
|
+
let sharedFactRecorder = null;
|
|
26
|
+
let sharedObservationCollector = null;
|
|
27
|
+
let factStoreCloseInstalled = false;
|
|
28
|
+
let internalActionSequence = 0;
|
|
29
|
+
|
|
30
|
+
let shutdownPromise = null;
|
|
31
|
+
let closing = false;
|
|
32
|
+
const activeRuns = new Set();
|
|
33
|
+
|
|
34
|
+
// Callers share one execution host. The owning process controls its lifecycle.
|
|
35
|
+
function run(args, dependencies) {
|
|
36
|
+
if (closing) return Promise.resolve(valueReply({ ok: false, error: 'runtime_stopping', dispatched: false, ambiguous: false }));
|
|
37
|
+
const task = runGeneric(args, dependencies);
|
|
38
|
+
activeRuns.add(task);
|
|
39
|
+
task.then(() => activeRuns.delete(task), () => activeRuns.delete(task));
|
|
40
|
+
return task;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function close() {
|
|
44
|
+
if (shutdownPromise) return shutdownPromise;
|
|
45
|
+
closing = true;
|
|
46
|
+
shutdownPromise = (async () => {
|
|
47
|
+
await require('./script/script-entry').cancelActiveScripts();
|
|
48
|
+
await require('./intent/intent-entry').cancelActiveIntents();
|
|
49
|
+
await webProvider.close();
|
|
50
|
+
await Promise.allSettled([...activeRuns]);
|
|
51
|
+
try { await sharedObservationCollector?.stop(); }
|
|
52
|
+
finally {
|
|
53
|
+
sharedObservationCollector = null;
|
|
54
|
+
try { await sharedFactStore?.drain?.(); }
|
|
55
|
+
finally {
|
|
56
|
+
sharedFactStore?.close();
|
|
57
|
+
sharedFactStore = null;
|
|
58
|
+
sharedFactRecorder = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
return shutdownPromise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { commandDefinitions, commandByName, isolatedByName, commandSchema, commandContract, validateCommandArguments, isMutationCommand, executionTimeoutMs } = require('./command-registry');
|
|
66
|
+
|
|
67
|
+
const commandRouter = createCommandRouter({
|
|
68
|
+
loadScript: () => wrapIsolatedEntry(require('./script/script-entry'), 'script'),
|
|
69
|
+
loadIntent: () => wrapIsolatedEntry(require('./intent/intent-entry'), 'intent'),
|
|
70
|
+
loadEvidence: () => ({
|
|
71
|
+
handle: (args) => require('./shared-kernel/evidence-archive').handle(args, {
|
|
72
|
+
getFactStore: getSharedFactStore,
|
|
73
|
+
}),
|
|
74
|
+
}),
|
|
75
|
+
dispatchCommon: runCommand,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Script and Intent capture call provider results directly. MCP text shaping,
|
|
79
|
+
// feedback probes and Host history are not part of their execution path.
|
|
80
|
+
async function dispatchProviderCommand(command, args) {
|
|
81
|
+
try {
|
|
82
|
+
const validated = resolveCommandPaths(command, validateCommandArguments(command, args));
|
|
83
|
+
return normalizeCommandResult(await targetExecution.execute(command, validated, (name, parameters) =>
|
|
84
|
+
runRawCommand(name, isMutationCommand(name, parameters)
|
|
85
|
+
? { ...parameters, runtimeActionId: actionIdForInvocation(parameters) } : parameters)), command);
|
|
86
|
+
} catch (error) { return commandFailure(error, command); }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveScriptWaitMs(value) {
|
|
90
|
+
if (value == null || value === '') return { ok: true, waitMs: 30_000 };
|
|
91
|
+
const waitMs = Number(value);
|
|
92
|
+
if (!Number.isInteger(waitMs) || waitMs < 0 || waitMs > 60_000) {
|
|
93
|
+
return { ok: false };
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, waitMs };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function wrapIsolatedEntry(entry, namespace) {
|
|
99
|
+
return {
|
|
100
|
+
handle(args) {
|
|
101
|
+
if (namespace === 'script' && args.operation === 'runtime-status') return entry.handle(args);
|
|
102
|
+
if (namespace === 'script' && args.operation === 'wait') {
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(args, 'isolatedTimeoutMs')) {
|
|
104
|
+
return require('./script/script-errors').scriptError('unsupported_argument', {
|
|
105
|
+
field: 'isolatedTimeoutMs',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const resolved = resolveScriptWaitMs(args.waitMs);
|
|
109
|
+
if (!resolved.ok) {
|
|
110
|
+
return require('./script/script-errors').scriptError('invalid_argument', {
|
|
111
|
+
field: 'waitMs',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
args.waitMs = resolved.waitMs;
|
|
115
|
+
}
|
|
116
|
+
if (!args.adapter) args.adapter = 'production';
|
|
117
|
+
if (namespace === 'intent' && args.target?.platform === 'web') args.ports = { ...args.ports, web: webProvider };
|
|
118
|
+
if (!args.store) {
|
|
119
|
+
const storeFactory = namespace === 'script'
|
|
120
|
+
? require('./script/script-evidence-store').createScriptEvidenceStore
|
|
121
|
+
: require('./intent/intent-evidence-store').createIntentEvidenceStore;
|
|
122
|
+
args.store = storeFactory({
|
|
123
|
+
adapter: createSegmentedEvidenceAdapter(getSharedFactStore()),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (namespace === 'script' && !args.host && typeof args.actions !== 'function') {
|
|
127
|
+
args.actions = dispatchProviderCommand;
|
|
128
|
+
if (typeof args.runner !== 'function') {
|
|
129
|
+
args.runner = args.actions;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (namespace === 'intent' && !args.capturePort) {
|
|
133
|
+
const { createLiveCaptureQuery } = require('./shared-kernel/live-capture-query');
|
|
134
|
+
const { createIntentCapturePort } = require('./intent/intent-capture-port');
|
|
135
|
+
args.capturePort = createIntentCapturePort({
|
|
136
|
+
query: createLiveCaptureQuery({ runner: dispatchProviderCommand }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return entry.handle(args);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function runGeneric(args = {}, dependencies = {}) {
|
|
145
|
+
try {
|
|
146
|
+
const request = validateRunRequest(args);
|
|
147
|
+
return commandRouter.route(request.command, resolveCommandPaths(request.command, request.arguments), dependencies);
|
|
148
|
+
} catch (error) { return valueReply(commandFailure(error, typeof args?.command === 'string' ? args.command : undefined)); }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function runCommand(command, args = {}, dependencies = {}) {
|
|
152
|
+
try { args = validateCommandArguments(command, args); }
|
|
153
|
+
catch (error) { return valueReply(commandFailure(error, command)); }
|
|
154
|
+
try {
|
|
155
|
+
return await runExecution({ timeoutMs: executionTimeoutMs(command, args), mutation: isMutationCommand(command, args) },
|
|
156
|
+
() => runBridgeWithinExecution(command, args, dependencies));
|
|
157
|
+
} catch (error) { return valueReply(commandFailure(error, command)); }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function runBridgeWithinExecution(command, args, dependencies) {
|
|
161
|
+
const execution = dependencies.targetExecution || targetExecution;
|
|
162
|
+
const rawRunner = dependencies.rawRunner || runRawCommand;
|
|
163
|
+
let factRecorder = null;
|
|
164
|
+
let factCacheInitializationError = null;
|
|
165
|
+
try {
|
|
166
|
+
if (Object.prototype.hasOwnProperty.call(dependencies, 'factRecorder')) {
|
|
167
|
+
factRecorder = dependencies.factRecorder;
|
|
168
|
+
} else if (typeof dependencies.factRecorderFactory === 'function') {
|
|
169
|
+
factRecorder = dependencies.factRecorderFactory();
|
|
170
|
+
} else if (!dependencies.rawRunner) {
|
|
171
|
+
factRecorder = getSharedFactRecorder();
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
factCacheInitializationError = {
|
|
175
|
+
degraded: true,
|
|
176
|
+
persistence: false,
|
|
177
|
+
error: error.code || error.name || 'fact_cache_initialization_failed',
|
|
178
|
+
message: error.message || String(error),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const historyRead = wantsFactHistory(args);
|
|
182
|
+
let observerSetupError = null;
|
|
183
|
+
let observationCollector = null;
|
|
184
|
+
if (!historyRead) {
|
|
185
|
+
try {
|
|
186
|
+
observationCollector = Object.prototype.hasOwnProperty.call(dependencies, 'observationCollector')
|
|
187
|
+
? dependencies.observationCollector
|
|
188
|
+
: (dependencies.rawRunner ? null : getSharedObservationCollector(factRecorder));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
observerSetupError = error.message || String(error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const observation = observerSetupError
|
|
194
|
+
? { collector: null, registration: null, error: observerSetupError }
|
|
195
|
+
: safeRegisterObservation(observationCollector, command, args);
|
|
196
|
+
const actionId = historyRead ? null : actionIdForInvocation(args);
|
|
197
|
+
const tracksMutation = !historyRead && isMutationCommand(command, args);
|
|
198
|
+
const requestedAtMs = Date.now();
|
|
199
|
+
if (tracksMutation) {
|
|
200
|
+
noteObservationAction(observation, actionId, { requestedAtMs });
|
|
201
|
+
}
|
|
202
|
+
let feedbackProbe = null;
|
|
203
|
+
try {
|
|
204
|
+
const result = copyFeedback(await execution.execute(command, args, async (normalizedCommand, normalizedArgs) => {
|
|
205
|
+
if (tracksMutation) {
|
|
206
|
+
noteObservationAction(observation, actionId, { startedAtMs: Date.now() });
|
|
207
|
+
}
|
|
208
|
+
if (historyRead && !isMobileCaptureCommand(normalizedCommand) && !normalizedCommand.startsWith('web-')) {
|
|
209
|
+
if (factRecorder) {
|
|
210
|
+
return factRecorder.readHistory(normalizedCommand, normalizedArgs);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
error: factCacheInitializationError ? 'fact_cache_unavailable' : 'fact_cache_disabled',
|
|
215
|
+
command: normalizedCommand,
|
|
216
|
+
...(factCacheInitializationError ? { factCache: factCacheInitializationError } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const runtimeArgs = tracksMutation
|
|
220
|
+
? { ...normalizedArgs, runtimeActionId: actionId }
|
|
221
|
+
: normalizedArgs;
|
|
222
|
+
feedbackProbe = await runWithFeedbackProbe({
|
|
223
|
+
command: normalizedCommand,
|
|
224
|
+
args: runtimeArgs,
|
|
225
|
+
runner: rawRunner,
|
|
226
|
+
});
|
|
227
|
+
return normalizeCommandResult(feedbackProbe.result, command);
|
|
228
|
+
}));
|
|
229
|
+
if (result && typeof result === 'object' && result._feedback) {
|
|
230
|
+
result._feedback.outcome = executionOutcome(result);
|
|
231
|
+
if (feedbackProbe?.observation) {
|
|
232
|
+
result._feedback.ui = feedbackProbe.observation;
|
|
233
|
+
if (
|
|
234
|
+
feedbackProbe.observation.semanticChanged === true
|
|
235
|
+
&& result._feedback.status !== 'failed'
|
|
236
|
+
) {
|
|
237
|
+
result._feedback.status = 'verified';
|
|
238
|
+
} else if (
|
|
239
|
+
feedbackProbe.observation.inconclusive === true
|
|
240
|
+
&& result._feedback.status !== 'verified'
|
|
241
|
+
&& result._feedback.status !== 'failed'
|
|
242
|
+
) {
|
|
243
|
+
result._feedback.status = 'inconclusive';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const timings = completedActionTimings(result?._feedback?.timings, requestedAtMs);
|
|
248
|
+
if (tracksMutation) noteObservationAction(observation, actionId, timings);
|
|
249
|
+
let history = historyUnavailable(actionId, factCacheInitializationError);
|
|
250
|
+
if (factRecorder) {
|
|
251
|
+
const recorded = {
|
|
252
|
+
factRecorder,
|
|
253
|
+
command,
|
|
254
|
+
args,
|
|
255
|
+
result,
|
|
256
|
+
historyRead,
|
|
257
|
+
probeEvidence: feedbackProbe?.evidence || [],
|
|
258
|
+
actionId,
|
|
259
|
+
actionTimeline: actionTimelineFor(actionId, timings),
|
|
260
|
+
};
|
|
261
|
+
try {
|
|
262
|
+
history = attachRecordedFacts(recorded);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
history = historyUnavailable(actionId, { error: error.code || 'history_recording_failed' });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (result && typeof result === 'object' && result._feedback) {
|
|
268
|
+
if (factCacheInitializationError) result._feedback.factCache = factCacheInitializationError;
|
|
269
|
+
const observerStatus = compactObservationStatus(observation);
|
|
270
|
+
if (observerStatus) result._feedback.observer = observerStatus;
|
|
271
|
+
}
|
|
272
|
+
return resultReply(result, history);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
const failure = copyFeedback(commandFailure(error, command));
|
|
275
|
+
const timings = completedActionTimings(failure._feedback?.timings, requestedAtMs);
|
|
276
|
+
if (tracksMutation) noteObservationAction(observation, actionId, timings);
|
|
277
|
+
let history = historyUnavailable(actionId, factCacheInitializationError);
|
|
278
|
+
if (factRecorder) {
|
|
279
|
+
try {
|
|
280
|
+
const actionReference = safeRecordExecution(factRecorder, {
|
|
281
|
+
command,
|
|
282
|
+
args,
|
|
283
|
+
error,
|
|
284
|
+
feedback: failure._feedback,
|
|
285
|
+
actionId,
|
|
286
|
+
});
|
|
287
|
+
history = historySummary(actionId, actionReference, []);
|
|
288
|
+
if (actionReference && failure._feedback) {
|
|
289
|
+
failure._feedback.evidence.push(actionReference);
|
|
290
|
+
failure._feedback.factCache = compactFactCacheStatus(factRecorder);
|
|
291
|
+
}
|
|
292
|
+
} catch (recordingError) {
|
|
293
|
+
history = historyUnavailable(actionId, { error: recordingError.code || 'history_recording_failed' });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (failure._feedback) {
|
|
297
|
+
if (factCacheInitializationError) failure._feedback.factCache = factCacheInitializationError;
|
|
298
|
+
const observerStatus = compactObservationStatus(observation);
|
|
299
|
+
if (observerStatus) failure._feedback.observer = observerStatus;
|
|
300
|
+
}
|
|
301
|
+
return resultReply(failure, history);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Each delivery reports its own history attempt. Do not mutate the original
|
|
306
|
+
// result/error retained by the request-id cache on every repeated read.
|
|
307
|
+
function copyFeedback(result) {
|
|
308
|
+
if (!result?._feedback) return result;
|
|
309
|
+
return { ...result, _feedback: { ...result._feedback, evidence: [...result._feedback.evidence] } };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function actionIdForInvocation(args = {}) {
|
|
313
|
+
if (args.requestId !== undefined && args.requestId !== null) return String(args.requestId);
|
|
314
|
+
internalActionSequence += 1;
|
|
315
|
+
return `host-action-${process.pid}-${Date.now()}-${internalActionSequence}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
function completedActionTimings(timings, requestedAtMs) {
|
|
320
|
+
if (timings && typeof timings === 'object') return { ...timings };
|
|
321
|
+
const completedAtMs = Date.now();
|
|
322
|
+
return {
|
|
323
|
+
requestedAtMs,
|
|
324
|
+
startedAtMs: requestedAtMs,
|
|
325
|
+
completedAtMs,
|
|
326
|
+
queueWaitMs: 0,
|
|
327
|
+
durationMs: Math.max(0, completedAtMs - requestedAtMs),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function actionTimelineFor(actionId, timings) {
|
|
332
|
+
if (actionId === undefined || actionId === null || !timings) return [];
|
|
333
|
+
return [{
|
|
334
|
+
actionId: String(actionId),
|
|
335
|
+
requestedAtMs: timings.requestedAtMs ?? null,
|
|
336
|
+
startedAtMs: timings.startedAtMs ?? null,
|
|
337
|
+
completedAtMs: timings.completedAtMs ?? null,
|
|
338
|
+
}];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function wantsFactHistory(args = {}) {
|
|
342
|
+
return args.history === true
|
|
343
|
+
|| args.history === 'true'
|
|
344
|
+
|| args.source === 'cache'
|
|
345
|
+
|| args.factCursor !== undefined
|
|
346
|
+
|| args.cursor !== undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function attachRecordedFacts({
|
|
350
|
+
factRecorder,
|
|
351
|
+
command,
|
|
352
|
+
args,
|
|
353
|
+
result,
|
|
354
|
+
historyRead,
|
|
355
|
+
probeEvidence = [],
|
|
356
|
+
actionId,
|
|
357
|
+
actionTimeline = [],
|
|
358
|
+
}) {
|
|
359
|
+
const feedback = result && typeof result === 'object' && !Buffer.isBuffer(result)
|
|
360
|
+
? result._feedback
|
|
361
|
+
: null;
|
|
362
|
+
const evidence = historyRead
|
|
363
|
+
? []
|
|
364
|
+
: safeRecordEvidence(factRecorder, command, args, result, { actionId, actionTimeline });
|
|
365
|
+
for (const captured of probeEvidence) {
|
|
366
|
+
evidence.push(...safeRecordEvidence(
|
|
367
|
+
factRecorder,
|
|
368
|
+
captured.command,
|
|
369
|
+
args,
|
|
370
|
+
captured.result,
|
|
371
|
+
{ actionId, actionTimeline },
|
|
372
|
+
));
|
|
373
|
+
}
|
|
374
|
+
const actionReference = safeRecordExecution(factRecorder, {
|
|
375
|
+
command,
|
|
376
|
+
args,
|
|
377
|
+
result,
|
|
378
|
+
feedback,
|
|
379
|
+
actionId,
|
|
380
|
+
});
|
|
381
|
+
if (feedback) {
|
|
382
|
+
feedback.evidence.push(...evidence, actionReference);
|
|
383
|
+
feedback.factCache = compactFactCacheStatus(factRecorder);
|
|
384
|
+
}
|
|
385
|
+
return historySummary(actionId, actionReference, evidence);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function historySummary(actionId, action, evidence) {
|
|
389
|
+
const errors = [action, ...evidence].filter(ref => ref.stored !== true)
|
|
390
|
+
.map(ref => ({ partition: ref.partition, error: ref.error || 'history_write_failed' }));
|
|
391
|
+
return { schemaVersion: 'aab.command-history/v1', status: errors.length ? 'partial' : 'stored',
|
|
392
|
+
actionId, action, evidence, errors, replayed: false };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function historyUnavailable(actionId, failure) {
|
|
396
|
+
return { schemaVersion: 'aab.command-history/v1', status: failure ? 'unavailable' : 'disabled',
|
|
397
|
+
actionId, action: null, evidence: [], errors: failure ? [{ error: failure.error }] : [], replayed: false };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function safeRegisterObservation(collector, command, args) {
|
|
401
|
+
if (!collector) return { collector: null, registration: null, error: null };
|
|
402
|
+
try {
|
|
403
|
+
const registration = withoutExecution(() => collector.register(command, args));
|
|
404
|
+
if (!registration || registration.ignored) {
|
|
405
|
+
return { collector, registration: null, error: null };
|
|
406
|
+
}
|
|
407
|
+
return { collector, registration, error: null };
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return {
|
|
410
|
+
collector,
|
|
411
|
+
registration: null,
|
|
412
|
+
error: error.message || String(error),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function noteObservationAction(observation, actionId, timings = {}) {
|
|
418
|
+
if (!observation.collector || !observation.registration?.target || actionId === undefined || actionId === null) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
observation.collector.noteAction(observation.registration.target, actionId, timings);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
observation.error ||= error.message || String(error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function compactObservationStatus(observation) {
|
|
429
|
+
if (observation.error) {
|
|
430
|
+
return { running: false, degraded: true, error: observation.error };
|
|
431
|
+
}
|
|
432
|
+
if (!observation.collector || !observation.registration?.target) return null;
|
|
433
|
+
try {
|
|
434
|
+
const status = observation.collector.status();
|
|
435
|
+
const key = observation.registration.target.key;
|
|
436
|
+
const target = Array.isArray(status.targets)
|
|
437
|
+
? status.targets.find((candidate) => candidate.key === key)
|
|
438
|
+
: null;
|
|
439
|
+
return {
|
|
440
|
+
running: Boolean(status.running),
|
|
441
|
+
targetCount: Number(status.targetCount || 0),
|
|
442
|
+
maxTargets: Number(status.maxTargets || 0),
|
|
443
|
+
inactiveTargetTtlMs: Number(status.inactiveTargetTtlMs || 0),
|
|
444
|
+
targetEvictions: Number(status.targetEvictions || 0),
|
|
445
|
+
targetExpirations: Number(status.targetExpirations || 0),
|
|
446
|
+
...(target ? {
|
|
447
|
+
target: {
|
|
448
|
+
key: target.key,
|
|
449
|
+
runtimeEpoch: target.runtimeEpoch ?? null,
|
|
450
|
+
failureCount: Number(target.failureCount || 0),
|
|
451
|
+
lastError: target.lastError ?? null,
|
|
452
|
+
backgroundPolling: target.backgroundPolling ?? null,
|
|
453
|
+
expiresAtMs: target.expiresAtMs ?? null,
|
|
454
|
+
lastSuccessAtMs: target.lastSuccessAtMs ?? null,
|
|
455
|
+
...(target.deviceLog ? { deviceLog: target.deviceLog } : {}),
|
|
456
|
+
},
|
|
457
|
+
} : {}),
|
|
458
|
+
...(status.dropped ? { dropped: status.dropped } : {}),
|
|
459
|
+
};
|
|
460
|
+
} catch (error) {
|
|
461
|
+
return { running: false, degraded: true, error: error.message || String(error) };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function executionOutcome(result) {
|
|
466
|
+
if (!result || typeof result !== 'object' || Buffer.isBuffer(result)) {
|
|
467
|
+
return { resultType: Buffer.isBuffer(result) ? 'buffer' : typeof result };
|
|
468
|
+
}
|
|
469
|
+
const outcome = {};
|
|
470
|
+
for (const key of [
|
|
471
|
+
'ok', 'error', 'action', 'source', 'transport', 'target', 'matched', 'activity',
|
|
472
|
+
'component', 'windowType', 'x', 'y', 'handledDown', 'handledUp', 'verified',
|
|
473
|
+
'inconclusive', 'dispatched', 'ambiguous', 'settled', 'executionReceipt', 'executionReceipts',
|
|
474
|
+
]) {
|
|
475
|
+
if (result[key] !== undefined) outcome[key] = result[key];
|
|
476
|
+
}
|
|
477
|
+
return outcome;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function safeRecordEvidence(factRecorder, command, args, result, context) {
|
|
481
|
+
try {
|
|
482
|
+
return factRecorder.recordEvidence(command, args, result, context);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
return [{ partition: 'index', stored: false, error: error.code || 'fact_evidence_write_failed' }];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function safeRecordExecution(factRecorder, input) {
|
|
489
|
+
try {
|
|
490
|
+
const recorded = factRecorder.recordExecution(input);
|
|
491
|
+
return {
|
|
492
|
+
partition: 'action',
|
|
493
|
+
globalSeq: recorded.globalSeq ?? null,
|
|
494
|
+
stored: recorded.ok !== false && recorded.stored === true,
|
|
495
|
+
actionId: recorded.actionId,
|
|
496
|
+
...(recorded.ok === false || recorded.stored !== true ? { error: recorded.error || 'fact_action_not_stored' } : {}),
|
|
497
|
+
};
|
|
498
|
+
} catch (error) {
|
|
499
|
+
return { partition: 'action', stored: false, actionId: input.actionId, error: error.code || 'fact_action_write_failed' };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function compactFactCacheStatus(factRecorder) {
|
|
504
|
+
try {
|
|
505
|
+
const status = factRecorder.status();
|
|
506
|
+
const mmap = status.sqlite?.mmap || (status.adapter === 'segmented-mmap'
|
|
507
|
+
? {
|
|
508
|
+
enabled: true,
|
|
509
|
+
requestedBytes: status.budgetBytes,
|
|
510
|
+
effectiveBytes: status.storage?.mmapBytes,
|
|
511
|
+
}
|
|
512
|
+
: undefined);
|
|
513
|
+
return {
|
|
514
|
+
adapter: status.adapter,
|
|
515
|
+
persistence: status.persistence,
|
|
516
|
+
degraded: status.degraded,
|
|
517
|
+
profile: status.profile,
|
|
518
|
+
profileSelection: status.profileSelection,
|
|
519
|
+
budgetBytes: status.budgetBytes,
|
|
520
|
+
totalBytes: status.storage?.totalBytes,
|
|
521
|
+
overQuota: status.storage?.overQuota,
|
|
522
|
+
mmap,
|
|
523
|
+
};
|
|
524
|
+
} catch (error) {
|
|
525
|
+
return { degraded: true, persistence: false, error: error.message };
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function getSharedFactStore() {
|
|
530
|
+
if (sharedFactStore) return sharedFactStore;
|
|
531
|
+
sharedFactStore = getHostFactStore();
|
|
532
|
+
return sharedFactStore;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function getSharedFactRecorder() {
|
|
536
|
+
if (String(process.env.AI_APP_BRIDGE_FACT_CACHE || '').toLowerCase() === 'off') return null;
|
|
537
|
+
if (sharedFactRecorder) return sharedFactRecorder;
|
|
538
|
+
getSharedFactStore();
|
|
539
|
+
sharedFactRecorder = new FactRecorder({
|
|
540
|
+
cache: createLegacyFactStoreAdapter(sharedFactStore),
|
|
541
|
+
});
|
|
542
|
+
if (!factStoreCloseInstalled) {
|
|
543
|
+
factStoreCloseInstalled = true;
|
|
544
|
+
process.once('exit', () => {
|
|
545
|
+
try {
|
|
546
|
+
const stopping = sharedObservationCollector?.stop();
|
|
547
|
+
stopping?.catch?.(() => {});
|
|
548
|
+
} catch (_) {
|
|
549
|
+
// Process shutdown must not be blocked by observer cleanup.
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
sharedFactStore?.close();
|
|
553
|
+
} catch (_) {
|
|
554
|
+
// Process shutdown must not be blocked by FactStore cleanup.
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return sharedFactRecorder;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function getSharedObservationCollector(factRecorder) {
|
|
562
|
+
if (!factRecorder) return null;
|
|
563
|
+
if (sharedObservationCollector) return sharedObservationCollector;
|
|
564
|
+
sharedObservationCollector = new ObservationCollector({
|
|
565
|
+
rawRunner: runRawCommand,
|
|
566
|
+
recordEvidence: (command, args, result, context) => {
|
|
567
|
+
safeRecordEvidence(factRecorder, command, args, result, context);
|
|
568
|
+
},
|
|
569
|
+
recordDeviceLog: (args, batch, context) => {
|
|
570
|
+
try {
|
|
571
|
+
factRecorder.recordDeviceLog(args, batch, context);
|
|
572
|
+
} catch (_) {
|
|
573
|
+
// Background persistence must never fail a foreground command.
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
withoutExecution(() => sharedObservationCollector.start());
|
|
578
|
+
return sharedObservationCollector;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function runRawCommand(command, args = {}) {
|
|
582
|
+
const definition = commandByName.get(command);
|
|
583
|
+
if (definition?.domain === 'ios') return iosProvider.run(command, args);
|
|
584
|
+
if (definition?.domain === 'web') return webProvider.run(command, args);
|
|
585
|
+
return executeProviderCommand(command, args);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// A protocol-neutral reply keeps payload and history distinct, including XML
|
|
589
|
+
// observations. Adapters decide how to represent them on their own wire.
|
|
590
|
+
function valueReply(value) { return { value }; }
|
|
591
|
+
|
|
592
|
+
function resultReply(result, history) {
|
|
593
|
+
return { value: normalizeCommandResult(result), ...(history ? { history } : {}) };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
module.exports = { run, close, dispatchProviderCommand, commandRouter, ...require('./command-discovery') };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const http = require('node:http');
|
|
7
|
+
const { randomBytes, randomUUID } = require('node:crypto');
|
|
8
|
+
const { CommandError, commandFailure } = require('./command-errors');
|
|
9
|
+
const { protocol, maxMessageBytes, encodeReply, readJson } = require('./runtime-protocol');
|
|
10
|
+
const { runtimeLocation, runtimeIdentity, acquireRuntimeLock, publishEndpoint } = require('./runtime-directory');
|
|
11
|
+
const { inRequestDirectory } = require('./shared-kernel/request-context');
|
|
12
|
+
|
|
13
|
+
async function start() {
|
|
14
|
+
const location = runtimeLocation();
|
|
15
|
+
const owner = acquireRuntimeLock(location);
|
|
16
|
+
if (!owner) { notify({ status: 'owned' }); return; }
|
|
17
|
+
const runtimeId = randomUUID();
|
|
18
|
+
const identity = runtimeIdentity(location);
|
|
19
|
+
const token = randomBytes(32).toString('hex');
|
|
20
|
+
const startedAtMs = Date.now();
|
|
21
|
+
const host = require('./execution-host');
|
|
22
|
+
let stopping;
|
|
23
|
+
let phase = 'running';
|
|
24
|
+
const status = () => ({ ok: true, command: 'runtime', status: phase, runtimeId, pid: process.pid, identity,
|
|
25
|
+
startedAtMs, facts: location.facts, profile: location.profile, node: { executable: process.execPath, version: process.version } });
|
|
26
|
+
const server = http.createServer(async (request, response) => {
|
|
27
|
+
const send = reply => {
|
|
28
|
+
if (response.destroyed) return;
|
|
29
|
+
let message;
|
|
30
|
+
try { message = JSON.stringify({ protocol, runtimeId, reply: encodeReply(reply) }); }
|
|
31
|
+
catch (error) { message = JSON.stringify({ protocol, runtimeId, reply: encodeReply({ value: commandFailure(error) }) }); }
|
|
32
|
+
if (Buffer.byteLength(message) > maxMessageBytes) message = JSON.stringify({ protocol, runtimeId,
|
|
33
|
+
reply: encodeReply({ value: { ok: false, error: 'runtime_message_too_large', dispatched: null, ambiguous: true } }) });
|
|
34
|
+
response.writeHead(200, { 'Content-Type': 'application/json', Connection: 'close' });
|
|
35
|
+
response.end(message);
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
if (request.headers.authorization !== `Bearer ${token}` || request.method !== 'POST' || request.url !== '/rpc') {
|
|
39
|
+
request.resume(); throw new CommandError('runtime_access_denied', 'The local runtime connection is not authenticated.');
|
|
40
|
+
}
|
|
41
|
+
const message = await readJson(request);
|
|
42
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)
|
|
43
|
+
|| Object.keys(message).some(key => !['protocol', 'runtimeId', 'method', 'identity', 'arguments', 'cwd'].includes(key))
|
|
44
|
+
|| message.protocol !== protocol || message.runtimeId !== runtimeId) throw new CommandError('runtime_protocol_error', 'Runtime request identity or fields are invalid.');
|
|
45
|
+
if (message.method === 'status') { send({ value: status() }); return; }
|
|
46
|
+
if (message.method === 'stop') { await stop(); send({ value: status() }); return; }
|
|
47
|
+
if (message.method !== 'execute') throw new CommandError('runtime_protocol_error', 'Unknown runtime method.');
|
|
48
|
+
if (phase !== 'running') throw new CommandError('runtime_stopping', 'The runtime is stopping.');
|
|
49
|
+
if (message.identity?.code !== identity.code) throw new CommandError('runtime_code_mismatch', 'The runtime and client use different code.');
|
|
50
|
+
if (message.identity?.config !== identity.config) throw new CommandError('runtime_configuration_mismatch', 'The runtime and client use different configuration.');
|
|
51
|
+
let validDirectory = false;
|
|
52
|
+
if (typeof message.cwd === 'string' && path.isAbsolute(message.cwd)) {
|
|
53
|
+
try { validDirectory = fs.statSync(message.cwd).isDirectory(); }
|
|
54
|
+
catch (error) { if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; }
|
|
55
|
+
}
|
|
56
|
+
if (!validDirectory) throw new CommandError('invalid_request_directory', 'The request working directory must exist and be absolute.', { field: 'cwd' });
|
|
57
|
+
// Closing this HTTP connection never cancels the admitted work. Its
|
|
58
|
+
// original runtime, deadline and explicit cancel operation own settlement.
|
|
59
|
+
send(await inRequestDirectory(message.cwd, () => host.run(message.arguments)));
|
|
60
|
+
} catch (error) { send({ value: commandFailure(error) }); }
|
|
61
|
+
});
|
|
62
|
+
server.requestTimeout = 30000;
|
|
63
|
+
server.headersTimeout = 10000;
|
|
64
|
+
server.keepAliveTimeout = 1000;
|
|
65
|
+
|
|
66
|
+
async function stop() {
|
|
67
|
+
if (stopping) return stopping;
|
|
68
|
+
phase = 'stopping';
|
|
69
|
+
server.close();
|
|
70
|
+
stopping = (async () => {
|
|
71
|
+
await host.close();
|
|
72
|
+
fs.unlinkSync(location.endpointFile);
|
|
73
|
+
owner.close();
|
|
74
|
+
phase = 'stopped';
|
|
75
|
+
})();
|
|
76
|
+
return stopping;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
|
|
80
|
+
publishEndpoint(location, { protocol, runtimeId, pid: process.pid, token, port: server.address().port, facts: location.facts });
|
|
81
|
+
for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => {
|
|
82
|
+
void stop().then(() => server.closeAllConnections()).catch(error => { process.stderr.write(`${error.stack || error}\n`); process.exitCode = 1; });
|
|
83
|
+
});
|
|
84
|
+
process.once('exit', () => owner.close());
|
|
85
|
+
notify({ status: 'ready', runtimeId, pid: process.pid });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
server.close(); owner.close(); throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function notify(message) {
|
|
92
|
+
if (process.send) process.send(message, () => { if (process.connected) process.disconnect(); });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (require.main === module) start().catch(error => {
|
|
96
|
+
notify({ status: 'failed', error: error.code || 'runtime_start_failed', message: error.message });
|
|
97
|
+
process.stderr.write(`${error.stack || error}\n`);
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
module.exports = { start };
|