@jskit-ai/assistant-core 0.2.6 → 0.2.8
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/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +18 -12
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { closeSync, openSync } from "node:fs";
|
|
4
|
-
import { mkdtemp, open,
|
|
5
|
-
import { isRecord, normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
|
-
import { normalizeNullableText } from "./normalize.js";
|
|
4
|
+
import { mkdtemp, open, rm } from "node:fs/promises";
|
|
7
5
|
import { isAbsolute, join } from "node:path";
|
|
8
6
|
import { tmpdir } from "node:os";
|
|
9
7
|
import { setTimeout as delay } from "node:timers/promises";
|
|
10
|
-
import { promisify } from "node:util";
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const { stdout } = await execFileAsync("ps", args, {
|
|
17
|
-
env: { ...process.env, LC_ALL: "C", TZ: "UTC" },
|
|
18
|
-
maxBuffer: 64 * 1024 * 1024
|
|
19
|
-
});
|
|
20
|
-
return stdout;
|
|
21
|
-
}
|
|
9
|
+
/**
|
|
10
|
+
* The execution host that starts and stops native processes; the local one is the default.
|
|
11
|
+
* @typedef {ReturnType<typeof createLocalConversationExecution>} ConversationExecution
|
|
12
|
+
*/
|
|
22
13
|
|
|
23
14
|
/** Owned POSIX process groups for a trusted, single-user application.
|
|
24
15
|
* Managed hosts replace this facility to enforce resource and identity policy.
|
|
@@ -75,7 +66,12 @@ export function createLocalConversationExecution() {
|
|
|
75
66
|
return entry.stopping;
|
|
76
67
|
}
|
|
77
68
|
|
|
78
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Starts one owned native process in its own process group.
|
|
71
|
+
* @param {{ command: string, args?: string[], cwd: string, env: import("@jskit-ai/kernel/server/types").RuntimeEnv,
|
|
72
|
+
* stream?: boolean, limits?: object, logPath?: string }} request
|
|
73
|
+
*/
|
|
74
|
+
async function start({ command, args = [], cwd, env, stream = false, limits, logPath }) {
|
|
79
75
|
if (closing) throw new Error("The conversation execution host is closed.");
|
|
80
76
|
if (typeof command !== "string" || !command || !Array.isArray(args) || args.some((arg) => typeof arg !== "string")) {
|
|
81
77
|
throw new TypeError("An executable and string arguments are required.");
|
|
@@ -111,628 +107,89 @@ export function createLocalConversationExecution() {
|
|
|
111
107
|
if (executions.has(id)) await stop(id);
|
|
112
108
|
throw new Error("The conversation execution host closed during startup.");
|
|
113
109
|
}
|
|
114
|
-
return Object.freeze({
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!["capture", "detached"].includes(mode)) throw new TypeError("Native execution requires capture or detached mode.");
|
|
123
|
-
if (!Number.isSafeInteger(timeout) || timeout <= 0) throw new TypeError("Native execution requires a positive timeout.");
|
|
124
|
-
const env = { ...baseEnv };
|
|
125
|
-
if (credentialHome.home) {
|
|
126
|
-
if (!isAbsolute(credentialHome.home)) throw new TypeError("A credential home must be absolute.");
|
|
127
|
-
Object.assign(env, { HOME: credentialHome.home,
|
|
128
|
-
XDG_CACHE_HOME: join(credentialHome.home, ".cache"),
|
|
129
|
-
XDG_CONFIG_HOME: join(credentialHome.home, ".config"),
|
|
130
|
-
XDG_DATA_HOME: join(credentialHome.home, ".local", "share") });
|
|
131
|
-
if (credentialHome.username) Object.assign(env, { USER: credentialHome.username, LOGNAME: credentialHome.username });
|
|
132
|
-
}
|
|
133
|
-
const captureRoot = mode === "capture" ? await mkdtemp(join(tmpdir(), "jskit-capture-")) : null;
|
|
134
|
-
const outputPath = captureRoot ? join(captureRoot, "output.log") : logPath;
|
|
135
|
-
let child;
|
|
136
|
-
let timer;
|
|
137
|
-
let cleanup = false;
|
|
138
|
-
try {
|
|
139
|
-
child = await start({ command, args, cwd, env, logPath: outputPath, limits });
|
|
140
|
-
if (mode === "detached") {
|
|
141
|
-
// A shared account service outlives the application's observer. Its
|
|
142
|
-
// durable runtime owner, not this Node event loop, controls shutdown.
|
|
143
|
-
executions.get(child.id).child.unref();
|
|
144
|
-
return { ok: true, pid: child.pid, execution: { id: child.id } };
|
|
145
|
-
}
|
|
146
|
-
let timedOut = false;
|
|
147
|
-
const result = await Promise.race([child.exited, new Promise((resolve, reject) => {
|
|
148
|
-
timer = setTimeout(() => {
|
|
149
|
-
timedOut = true;
|
|
150
|
-
stop(child.id).then(() => child.exited).then(resolve, reject);
|
|
151
|
-
}, timeout);
|
|
152
|
-
})]);
|
|
153
|
-
clearTimeout(timer);
|
|
154
|
-
// The timeout may have already retired this exact execution.
|
|
155
|
-
if (executions.has(child.id)) await stop(child.id);
|
|
156
|
-
cleanup = true;
|
|
157
|
-
const file = await open(outputPath, "r");
|
|
158
|
-
let output;
|
|
159
|
-
try {
|
|
160
|
-
const { size } = await file.stat();
|
|
161
|
-
const bytes = Buffer.alloc(Math.min(size, 64 * 1024));
|
|
162
|
-
const { bytesRead } = await file.read(bytes, 0, bytes.length, Math.max(0, size - bytes.length));
|
|
163
|
-
output = bytes.subarray(0, bytesRead).toString("utf8");
|
|
164
|
-
} finally { await file.close(); }
|
|
165
|
-
return { ok: !timedOut && result.code === 0, exitCode: result.code, signal: result.signal, timedOut, output };
|
|
166
|
-
} finally {
|
|
167
|
-
clearTimeout(timer);
|
|
168
|
-
if (captureRoot && (!child || cleanup)) await rm(captureRoot, { recursive: true, force: true });
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
stop,
|
|
172
|
-
async close() {
|
|
173
|
-
closing = true;
|
|
174
|
-
const results = await Promise.allSettled([...executions.keys()].map(stop));
|
|
175
|
-
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
176
|
-
if (errors.length) throw new AggregateError(errors, "Some owned native executions could not be stopped.");
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Process-group ownership proof used by execution hosts; callers retain their metadata contract. */
|
|
182
|
-
export function createConversationProcessIdentity({
|
|
183
|
-
runtimeTokenEnvironmentName = "JSKIT_EXECUTION_RUNTIME_TOKEN",
|
|
184
|
-
commandHashEnvironmentName = "JSKIT_EXECUTION_COMMAND_HASH",
|
|
185
|
-
label = "Native",
|
|
186
|
-
unavailableCode = "native_process_identity_unavailable",
|
|
187
|
-
platform = process.platform,
|
|
188
|
-
ps = runPs
|
|
189
|
-
} = {}) {
|
|
190
|
-
const PROCESS_IDENTITY_SETTLE_TIMEOUT_MS = 1000;
|
|
191
|
-
// Darwin inspection spawns ps instead of reading /proc, so it polls less often.
|
|
192
|
-
const PROCESS_IDENTITY_SETTLE_POLL_MS = platform === "darwin" ? 100 : 25;
|
|
193
|
-
const PROCESS_IDENTITY_VERSION = 1;
|
|
194
|
-
const PROCESS_IDENTITY_PLATFORMS = Object.freeze({ linux: "linux-proc", darwin: "darwin" });
|
|
195
|
-
const hostIdentityPlatform = PROCESS_IDENTITY_PLATFORMS[platform] || "";
|
|
196
|
-
const PROCESS_STATE = Object.freeze({ RUNNING: "running", STOPPED: "stopped" });
|
|
197
|
-
const PROCESS_IDENTITY_STATUS = Object.freeze({
|
|
198
|
-
ABSENT: "absent", AMBIGUOUS: "ambiguous", EXACT: "exact", INVALID: "invalid", MISMATCH: "mismatch"
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
function processMetadataIsIdentifiable(metadata = {}) {
|
|
202
|
-
return Boolean(
|
|
203
|
-
Number.isSafeInteger(Number(metadata.pid)) && Number(metadata.pid) > 0 &&
|
|
204
|
-
processIdentityIsWellFormed(metadata.processIdentity) &&
|
|
205
|
-
[PROCESS_STATE.RUNNING, PROCESS_STATE.STOPPED].includes(metadata.processState)
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function processIsAlive(pid) {
|
|
210
|
-
const normalizedPid = Number(pid);
|
|
211
|
-
if (!Number.isSafeInteger(normalizedPid) || normalizedPid <= 0) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
process.kill(normalizedPid, 0);
|
|
216
|
-
return true;
|
|
217
|
-
} catch {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function processGroupIsAlive(processGroupId) {
|
|
223
|
-
const normalizedProcessGroupId = Number(processGroupId);
|
|
224
|
-
if (!Number.isSafeInteger(normalizedProcessGroupId) || normalizedProcessGroupId <= 0) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
if (process.platform === "win32") {
|
|
228
|
-
return processIsAlive(normalizedProcessGroupId);
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
process.kill(-normalizedProcessGroupId, 0);
|
|
232
|
-
return true;
|
|
233
|
-
} catch {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
id,
|
|
112
|
+
pid: child.pid,
|
|
113
|
+
stdin: child.stdin,
|
|
114
|
+
stdout: child.stdout,
|
|
115
|
+
exited,
|
|
116
|
+
get running() { return child.exitCode === null && child.signalCode === null; }
|
|
117
|
+
});
|
|
236
118
|
}
|
|
237
119
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Runs one native command to completion (capture) or leaves it running (detached).
|
|
122
|
+
* @param {{ command: string, args: string[], cwd: string, baseEnv: import("@jskit-ai/kernel/server/types").RuntimeEnv,
|
|
123
|
+
* credentialHome?: { home?: string, username?: string }, mode: "capture" | "detached", logPath?: string, timeout?: number,
|
|
124
|
+
* limits?: object }} request
|
|
125
|
+
*/
|
|
126
|
+
async function run({ command, args, cwd, baseEnv, credentialHome = {}, mode, logPath, timeout = 30_000, limits }) {
|
|
127
|
+
if (!["capture", "detached"].includes(mode)) throw new TypeError("Native execution requires capture or detached mode.");
|
|
128
|
+
if (!Number.isSafeInteger(timeout) || timeout <= 0) throw new TypeError("Native execution requires a positive timeout.");
|
|
129
|
+
const env = { ...baseEnv };
|
|
130
|
+
if (credentialHome.home) {
|
|
131
|
+
if (!isAbsolute(credentialHome.home)) throw new TypeError("A credential home must be absolute.");
|
|
132
|
+
Object.assign(env, { HOME: credentialHome.home,
|
|
133
|
+
XDG_CACHE_HOME: join(credentialHome.home, ".cache"),
|
|
134
|
+
XDG_CONFIG_HOME: join(credentialHome.home, ".config"),
|
|
135
|
+
XDG_DATA_HOME: join(credentialHome.home, ".local", "share") });
|
|
136
|
+
if (credentialHome.username) Object.assign(env, { USER: credentialHome.username, LOGNAME: credentialHome.username });
|
|
137
|
+
}
|
|
138
|
+
const captureRoot = mode === "capture" ? await mkdtemp(join(tmpdir(), "jskit-capture-")) : null;
|
|
139
|
+
const outputPath = captureRoot ? join(captureRoot, "output.log") : logPath;
|
|
140
|
+
let child;
|
|
141
|
+
let timer;
|
|
142
|
+
let cleanup = false;
|
|
243
143
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const startTimeTicks = String(fields[19] || "");
|
|
265
|
-
if (
|
|
266
|
-
!/^[A-Z]$/u.test(state) ||
|
|
267
|
-
!Number.isSafeInteger(parentPid) || parentPid < 0 ||
|
|
268
|
-
!Number.isSafeInteger(processGroupId) || processGroupId <= 0 ||
|
|
269
|
-
!/^\d+$/u.test(startTimeTicks)
|
|
270
|
-
) {
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
parentPid,
|
|
275
|
-
processGroupId,
|
|
276
|
-
startTimeTicks,
|
|
277
|
-
state
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function normalizeProcessIdentity(value = {}) {
|
|
282
|
-
const normalized = isRecord(value) ? value : {};
|
|
283
|
-
return {
|
|
284
|
-
commandHash: normalizeNullableText(normalized.commandHash),
|
|
285
|
-
platform: normalizeNullableText(normalized.platform),
|
|
286
|
-
runtimeToken: normalizeNullableText(normalized.runtimeToken),
|
|
287
|
-
startTimeTicks: normalizeNullableText(normalized.startTimeTicks),
|
|
288
|
-
version: Number(normalized.version || 0)
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function processIdentityIsWellFormed(value = {}) {
|
|
293
|
-
const identity = normalizeProcessIdentity(value);
|
|
294
|
-
return Boolean(
|
|
295
|
-
identity.version === PROCESS_IDENTITY_VERSION &&
|
|
296
|
-
Object.values(PROCESS_IDENTITY_PLATFORMS).includes(identity.platform) &&
|
|
297
|
-
/^[a-f0-9]{12}$/u.test(identity.commandHash) &&
|
|
298
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(identity.runtimeToken) &&
|
|
299
|
-
/^\d+$/u.test(identity.startTimeTicks)
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/** The value of `name` in a list of NAME=value environment entries. */
|
|
304
|
-
function environmentValue(entries = [], name = "") {
|
|
305
|
-
const entry = entries.find((assignment) => assignment.startsWith(`${name}=`));
|
|
306
|
-
return entry ? entry.slice(name.length + 1) : "";
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function linuxProcessRecords() {
|
|
310
|
-
const records = [];
|
|
311
|
-
const entries = await readdir("/proc", {
|
|
312
|
-
withFileTypes: true
|
|
313
|
-
}).catch(() => []);
|
|
314
|
-
for (const entry of entries) {
|
|
315
|
-
if (!entry.isDirectory() || !/^\d+$/u.test(entry.name)) {
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
const pid = Number(entry.name);
|
|
319
|
-
const statValue = await readFile(`/proc/${entry.name}/stat`, "utf8").catch(() => "");
|
|
320
|
-
const processStat = linuxProcessStat(statValue);
|
|
321
|
-
if (!processStat || processStat.state === "Z") {
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
let environment = null;
|
|
325
|
-
let environmentReadable = true;
|
|
144
|
+
child = await start({ command, args, cwd, env, logPath: outputPath, limits });
|
|
145
|
+
if (mode === "detached") {
|
|
146
|
+
// A shared account service outlives the application's observer. Its
|
|
147
|
+
// durable runtime owner, not this Node event loop, controls shutdown.
|
|
148
|
+
executions.get(child.id).child.unref();
|
|
149
|
+
return { ok: true, pid: child.pid, execution: { id: child.id } };
|
|
150
|
+
}
|
|
151
|
+
let timedOut = false;
|
|
152
|
+
const result = await Promise.race([child.exited, new Promise((resolve, reject) => {
|
|
153
|
+
timer = setTimeout(() => {
|
|
154
|
+
timedOut = true;
|
|
155
|
+
stop(child.id).then(() => child.exited).then(resolve, reject);
|
|
156
|
+
}, timeout);
|
|
157
|
+
})]);
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
// The timeout may have already retired this exact execution.
|
|
160
|
+
if (executions.has(child.id)) await stop(child.id);
|
|
161
|
+
cleanup = true;
|
|
162
|
+
const file = await open(outputPath, "r");
|
|
163
|
+
let output;
|
|
326
164
|
try {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
...processStat,
|
|
337
|
-
commandHash: environmentValue(entries, commandHashEnvironmentName),
|
|
338
|
-
environmentReadable,
|
|
339
|
-
pid,
|
|
340
|
-
runtimeToken: environmentValue(entries, runtimeTokenEnvironmentName)
|
|
341
|
-
});
|
|
165
|
+
const { size } = await file.stat();
|
|
166
|
+
const bytes = Buffer.alloc(Math.min(size, 64 * 1024));
|
|
167
|
+
const { bytesRead } = await file.read(bytes, 0, bytes.length, Math.max(0, size - bytes.length));
|
|
168
|
+
output = bytes.subarray(0, bytesRead).toString("utf8");
|
|
169
|
+
} finally { await file.close(); }
|
|
170
|
+
return { ok: !timedOut && result.code === 0, exitCode: result.code, signal: result.signal, timedOut, output };
|
|
171
|
+
} finally {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
if (captureRoot && (!child || cleanup)) await rm(captureRoot, { recursive: true, force: true });
|
|
342
174
|
}
|
|
343
|
-
return records;
|
|
344
175
|
}
|
|
345
176
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const month = match ? DARWIN_MONTHS.indexOf(match[1]) : -1;
|
|
352
|
-
if (month < 0) {
|
|
353
|
-
return "";
|
|
354
|
-
}
|
|
355
|
-
const [, , day, hours, minutes, seconds, year] = match.map(Number);
|
|
356
|
-
return String(Date.UTC(year, month, day, hours, minutes, seconds) / 1000);
|
|
177
|
+
async function close() {
|
|
178
|
+
closing = true;
|
|
179
|
+
const results = await Promise.allSettled([...executions.keys()].map(stop));
|
|
180
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
181
|
+
if (errors.length) throw new AggregateError(errors, "Some owned native executions could not be stopped.");
|
|
357
182
|
}
|
|
358
183
|
|
|
359
|
-
|
|
360
|
-
const commands = new Map();
|
|
361
|
-
for (const line of String(output).split("\n")) {
|
|
362
|
-
const match = /^\s*(\d+) (.*)$/u.exec(line);
|
|
363
|
-
if (match) {
|
|
364
|
-
commands.set(Number(match[1]), match[2]);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return commands;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** The `ps -E` text after the argument list, as its assignments, or null when no environment is shown. */
|
|
371
|
-
function darwinEnvironment(command, commandWithEnvironment) {
|
|
372
|
-
if (command === undefined || !commandWithEnvironment?.startsWith(command)) {
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
const assignments = commandWithEnvironment.slice(command.length).split(/\s+/u).filter((entry) => /^[^=\s]+=/u.test(entry));
|
|
376
|
-
return assignments.length ? assignments : null;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/** The darwin counterpart of the /proc scan. Only the current user's environments are readable;
|
|
380
|
-
* a listing without a separate environment section stays unreadable so inspection degrades to ambiguous. */
|
|
381
|
-
async function darwinProcessRecords() {
|
|
382
|
-
const currentUid = process.getuid();
|
|
383
|
-
const ownProcesses = ["-x", "-U", String(currentUid), "-ww", "-o", "pid=,command="];
|
|
384
|
-
const [table, commandOutput, environmentOutput] = await Promise.all([
|
|
385
|
-
ps(["-ax", "-o", "pid=,pgid=,uid=,stat=,lstart="]),
|
|
386
|
-
ps(ownProcesses),
|
|
387
|
-
ps(["-E", ...ownProcesses])
|
|
388
|
-
]);
|
|
389
|
-
const commands = darwinCommandsByPid(commandOutput);
|
|
390
|
-
const commandsWithEnvironment = darwinCommandsByPid(environmentOutput);
|
|
391
|
-
const records = [];
|
|
392
|
-
for (const line of String(table).split("\n")) {
|
|
393
|
-
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\w{3}\s+\w{3}\s+\d{1,2}\s+[\d:]{8}\s+\d{4})\s*$/u.exec(line);
|
|
394
|
-
if (!match || match[4].startsWith("Z")) {
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
const pid = Number(match[1]);
|
|
398
|
-
const environment = Number(match[3]) === currentUid
|
|
399
|
-
? darwinEnvironment(commands.get(pid), commandsWithEnvironment.get(pid))
|
|
400
|
-
: null;
|
|
401
|
-
records.push({
|
|
402
|
-
commandHash: environmentValue(environment || [], commandHashEnvironmentName),
|
|
403
|
-
environmentReadable: Boolean(environment),
|
|
404
|
-
pid,
|
|
405
|
-
processGroupId: Number(match[2]),
|
|
406
|
-
runtimeToken: environmentValue(environment || [], runtimeTokenEnvironmentName),
|
|
407
|
-
startTimeTicks: darwinStartTime(match[5])
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
return records;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function normalizeProcessIdentityInspection(value = {}) {
|
|
414
|
-
const status = Object.values(PROCESS_IDENTITY_STATUS).includes(value?.status)
|
|
415
|
-
? value.status
|
|
416
|
-
: PROCESS_IDENTITY_STATUS.AMBIGUOUS;
|
|
417
|
-
return {
|
|
418
|
-
processGroupIds: [...new Set((Array.isArray(value?.processGroupIds)
|
|
419
|
-
? value.processGroupIds
|
|
420
|
-
: [])
|
|
421
|
-
.map(Number)
|
|
422
|
-
.filter((processGroupId) => Number.isSafeInteger(processGroupId) && processGroupId > 0))],
|
|
423
|
-
status
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async function inspectHostProcessIdentity(metadata = {}) {
|
|
428
|
-
if (!processMetadataIsIdentifiable(metadata)) {
|
|
429
|
-
return {
|
|
430
|
-
processGroupIds: [],
|
|
431
|
-
status: PROCESS_IDENTITY_STATUS.INVALID
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
if (metadata.processState === PROCESS_STATE.STOPPED) {
|
|
435
|
-
return {
|
|
436
|
-
processGroupIds: [],
|
|
437
|
-
status: PROCESS_IDENTITY_STATUS.ABSENT
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
const identity = metadata.processIdentity;
|
|
441
|
-
const records = identity.platform !== hostIdentityPlatform
|
|
442
|
-
? null
|
|
443
|
-
: platform === "darwin"
|
|
444
|
-
? await darwinProcessRecords().catch(() => null)
|
|
445
|
-
: await linuxProcessRecords();
|
|
446
|
-
if (!records) {
|
|
447
|
-
return {
|
|
448
|
-
processGroupIds: [],
|
|
449
|
-
status: PROCESS_IDENTITY_STATUS.AMBIGUOUS
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
const rootProcessGroupId = Number(metadata.pid);
|
|
453
|
-
const rootGroupMembers = records.filter(({ processGroupId }) => processGroupId === rootProcessGroupId);
|
|
454
|
-
const exactRecords = [];
|
|
455
|
-
const rootUnreadable = rootGroupMembers.some(({ environmentReadable }) => !environmentReadable);
|
|
456
|
-
for (const record of records) {
|
|
457
|
-
if (!record.environmentReadable) {
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
if (record.runtimeToken === identity.runtimeToken && record.commandHash === identity.commandHash) {
|
|
461
|
-
exactRecords.push(record);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const exactLeader = exactRecords.find(({ pid }) => pid === rootProcessGroupId);
|
|
465
|
-
if (exactLeader && exactLeader.startTimeTicks !== identity.startTimeTicks) {
|
|
466
|
-
return {
|
|
467
|
-
processGroupIds: [],
|
|
468
|
-
status: PROCESS_IDENTITY_STATUS.AMBIGUOUS
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
if (exactRecords.length === 0) {
|
|
472
|
-
if (rootUnreadable) {
|
|
473
|
-
return {
|
|
474
|
-
processGroupIds: [],
|
|
475
|
-
status: PROCESS_IDENTITY_STATUS.AMBIGUOUS
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
return {
|
|
479
|
-
processGroupIds: [],
|
|
480
|
-
status: rootGroupMembers.length > 0 || processGroupIsAlive(rootProcessGroupId)
|
|
481
|
-
? PROCESS_IDENTITY_STATUS.MISMATCH
|
|
482
|
-
: PROCESS_IDENTITY_STATUS.ABSENT
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
const processGroupIds = [...new Set(exactRecords.map(({ processGroupId }) => processGroupId))];
|
|
486
|
-
for (const processGroupId of processGroupIds) {
|
|
487
|
-
const groupMembers = records.filter((record) => record.processGroupId === processGroupId);
|
|
488
|
-
if (groupMembers.some((record) => (
|
|
489
|
-
!record.environmentReadable || !exactRecords.some(({ pid }) => pid === record.pid)
|
|
490
|
-
))) {
|
|
491
|
-
return {
|
|
492
|
-
processGroupIds: [],
|
|
493
|
-
status: PROCESS_IDENTITY_STATUS.AMBIGUOUS
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (
|
|
498
|
-
rootGroupMembers.some((record) => !exactRecords.some(({ pid }) => pid === record.pid))
|
|
499
|
-
) {
|
|
500
|
-
return {
|
|
501
|
-
processGroupIds: [],
|
|
502
|
-
status: PROCESS_IDENTITY_STATUS.AMBIGUOUS
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
return {
|
|
506
|
-
processGroupIds,
|
|
507
|
-
status: PROCESS_IDENTITY_STATUS.EXACT
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
async function inspectProcessIdentity(metadata = {}, options = {}) {
|
|
512
|
-
const inspector = typeof options.processIdentityInspector === "function"
|
|
513
|
-
? options.processIdentityInspector
|
|
514
|
-
: inspectHostProcessIdentity;
|
|
515
|
-
return normalizeProcessIdentityInspection(await inspector(metadata));
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
async function waitForProcessIdentityExit(metadata = {}, options = {}, timeoutMs = 0) {
|
|
519
|
-
const deadline = Date.now() + timeoutMs;
|
|
520
|
-
let inspection = await inspectProcessIdentity(metadata, options);
|
|
521
|
-
while (
|
|
522
|
-
Date.now() < deadline &&
|
|
523
|
-
inspection.status === PROCESS_IDENTITY_STATUS.EXACT
|
|
524
|
-
) {
|
|
525
|
-
await delay(100);
|
|
526
|
-
inspection = await inspectProcessIdentity(metadata, options);
|
|
527
|
-
}
|
|
528
|
-
return inspection;
|
|
529
|
-
}
|
|
184
|
+
return Object.freeze({
|
|
530
185
|
|
|
531
|
-
|
|
532
|
-
const timeoutMs = normalizePositiveInteger(
|
|
533
|
-
options.processIdentitySettleTimeoutMs,
|
|
534
|
-
{ fallback: PROCESS_IDENTITY_SETTLE_TIMEOUT_MS }
|
|
535
|
-
);
|
|
536
|
-
const pollMs = normalizePositiveInteger(
|
|
537
|
-
options.processIdentitySettlePollMs,
|
|
538
|
-
{ fallback: PROCESS_IDENTITY_SETTLE_POLL_MS }
|
|
539
|
-
);
|
|
540
|
-
const deadline = Date.now() + timeoutMs;
|
|
541
|
-
let inspection = await inspectProcessIdentity(metadata, options);
|
|
542
|
-
while (
|
|
543
|
-
inspection.status === PROCESS_IDENTITY_STATUS.AMBIGUOUS &&
|
|
544
|
-
Date.now() < deadline
|
|
545
|
-
) {
|
|
546
|
-
await delay(Math.min(pollMs, Math.max(1, deadline - Date.now())));
|
|
547
|
-
inspection = await inspectProcessIdentity(metadata, options);
|
|
548
|
-
}
|
|
549
|
-
return inspection;
|
|
550
|
-
}
|
|
186
|
+
start,
|
|
551
187
|
|
|
552
|
-
|
|
553
|
-
const signalProcessGroupImpl = typeof options.signalProcessGroup === "function"
|
|
554
|
-
? options.signalProcessGroup
|
|
555
|
-
: signalProcessGroup;
|
|
556
|
-
const initial = await waitForProcessIdentityToSettle(metadata, options);
|
|
557
|
-
if (initial.status !== PROCESS_IDENTITY_STATUS.EXACT) {
|
|
558
|
-
return {
|
|
559
|
-
inspection: initial,
|
|
560
|
-
signaledProcessGroups: []
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
const rootProcessGroupId = Number(metadata.pid);
|
|
564
|
-
const orderedProcessGroupIds = [
|
|
565
|
-
...initial.processGroupIds.filter((processGroupId) => processGroupId !== rootProcessGroupId),
|
|
566
|
-
...initial.processGroupIds.filter((processGroupId) => processGroupId === rootProcessGroupId)
|
|
567
|
-
];
|
|
568
|
-
const signaledProcessGroups = [];
|
|
569
|
-
for (const processGroupId of orderedProcessGroupIds) {
|
|
570
|
-
const current = await waitForProcessIdentityToSettle(metadata, options);
|
|
571
|
-
if (
|
|
572
|
-
current.status === PROCESS_IDENTITY_STATUS.ABSENT ||
|
|
573
|
-
current.status === PROCESS_IDENTITY_STATUS.MISMATCH
|
|
574
|
-
) {
|
|
575
|
-
return {
|
|
576
|
-
inspection: current,
|
|
577
|
-
signaledProcessGroups
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
if (current.status !== PROCESS_IDENTITY_STATUS.EXACT) {
|
|
581
|
-
return {
|
|
582
|
-
inspection: current,
|
|
583
|
-
signaledProcessGroups
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
if (!current.processGroupIds.includes(processGroupId)) {
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
signalProcessGroupImpl(processGroupId, signal);
|
|
590
|
-
signaledProcessGroups.push(processGroupId);
|
|
591
|
-
}
|
|
592
|
-
return {
|
|
593
|
-
inspection: await waitForProcessIdentityToSettle(metadata, options),
|
|
594
|
-
signaledProcessGroups
|
|
595
|
-
};
|
|
596
|
-
}
|
|
188
|
+
run,
|
|
597
189
|
|
|
598
|
-
|
|
599
|
-
if (!processMetadataIsIdentifiable(metadata)) {
|
|
600
|
-
return {
|
|
601
|
-
identityStatus: PROCESS_IDENTITY_STATUS.INVALID,
|
|
602
|
-
processExitVerified: false,
|
|
603
|
-
stopped: false
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
if (
|
|
607
|
-
metadata.processState === PROCESS_STATE.STOPPED &&
|
|
608
|
-
metadata.processExitVerifiedAt
|
|
609
|
-
) {
|
|
610
|
-
return {
|
|
611
|
-
alreadyStopped: true,
|
|
612
|
-
identityStatus: PROCESS_IDENTITY_STATUS.ABSENT,
|
|
613
|
-
processExitVerified: true,
|
|
614
|
-
stopped: false
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
const before = await waitForProcessIdentityToSettle(metadata, options);
|
|
618
|
-
if (
|
|
619
|
-
before.status === PROCESS_IDENTITY_STATUS.ABSENT ||
|
|
620
|
-
before.status === PROCESS_IDENTITY_STATUS.MISMATCH
|
|
621
|
-
) {
|
|
622
|
-
return {
|
|
623
|
-
identityStatus: before.status,
|
|
624
|
-
processExitVerified: true,
|
|
625
|
-
stopped: false
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
if (before.status !== PROCESS_IDENTITY_STATUS.EXACT) {
|
|
629
|
-
return {
|
|
630
|
-
identityStatus: before.status,
|
|
631
|
-
processExitVerified: false,
|
|
632
|
-
stopped: false
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
const term = await signalVerifiedProcessGroups(metadata, "SIGTERM", options);
|
|
636
|
-
if (term.inspection.status === PROCESS_IDENTITY_STATUS.AMBIGUOUS) {
|
|
637
|
-
return {
|
|
638
|
-
identityStatus: term.inspection.status,
|
|
639
|
-
processExitVerified: false,
|
|
640
|
-
signaledProcessGroups: term.signaledProcessGroups,
|
|
641
|
-
stopped: false
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
let after = await waitForProcessIdentityExit(
|
|
645
|
-
metadata,
|
|
646
|
-
options,
|
|
647
|
-
normalizePositiveInteger(options.termTimeoutMs, { fallback: 3000 })
|
|
648
|
-
);
|
|
649
|
-
let signaledProcessGroups = [...term.signaledProcessGroups];
|
|
650
|
-
if (after.status === PROCESS_IDENTITY_STATUS.EXACT) {
|
|
651
|
-
const kill = await signalVerifiedProcessGroups(metadata, "SIGKILL", options);
|
|
652
|
-
signaledProcessGroups = [...new Set([
|
|
653
|
-
...signaledProcessGroups,
|
|
654
|
-
...kill.signaledProcessGroups
|
|
655
|
-
])];
|
|
656
|
-
if (kill.inspection.status === PROCESS_IDENTITY_STATUS.AMBIGUOUS) {
|
|
657
|
-
return {
|
|
658
|
-
identityStatus: kill.inspection.status,
|
|
659
|
-
processExitVerified: false,
|
|
660
|
-
signaledProcessGroups,
|
|
661
|
-
stopped: false
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
after = await waitForProcessIdentityExit(
|
|
665
|
-
metadata,
|
|
666
|
-
options,
|
|
667
|
-
normalizePositiveInteger(options.killTimeoutMs, { fallback: 1000 })
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
const processExitVerified = [
|
|
671
|
-
PROCESS_IDENTITY_STATUS.ABSENT,
|
|
672
|
-
PROCESS_IDENTITY_STATUS.MISMATCH
|
|
673
|
-
].includes(after.status);
|
|
674
|
-
return {
|
|
675
|
-
descendantProcessGroups: signaledProcessGroups.filter((processGroupId) => (
|
|
676
|
-
processGroupId !== Number(metadata.pid)
|
|
677
|
-
)),
|
|
678
|
-
identityStatus: after.status,
|
|
679
|
-
pid: Number(metadata.pid),
|
|
680
|
-
processExitVerified,
|
|
681
|
-
signaledProcessGroups,
|
|
682
|
-
stopped: processExitVerified && signaledProcessGroups.length > 0
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
async function captureProcessStart(pid) {
|
|
687
|
-
if (platform !== "darwin") {
|
|
688
|
-
return linuxProcessStat(await readFile(`/proc/${pid}/stat`, "utf8").catch(() => ""));
|
|
689
|
-
}
|
|
690
|
-
const output = String(await ps(["-o", "pgid=,lstart=", "-p", String(pid)]).catch(() => ""));
|
|
691
|
-
const match = /^\s*(\d+)\s+(.+?)\s*$/u.exec(output);
|
|
692
|
-
return match ? { processGroupId: Number(match[1]), startTimeTicks: darwinStartTime(match[2]) } : null;
|
|
693
|
-
}
|
|
190
|
+
stop,
|
|
694
191
|
|
|
695
|
-
|
|
696
|
-
commandHash = "",
|
|
697
|
-
pid = null,
|
|
698
|
-
reportedIdentity = null,
|
|
699
|
-
runtimeToken = ""
|
|
700
|
-
} = {}) {
|
|
701
|
-
const expectedCommandHash = normalizeNullableText(commandHash);
|
|
702
|
-
const expectedRuntimeToken = normalizeNullableText(runtimeToken);
|
|
703
|
-
const reported = normalizeProcessIdentity(reportedIdentity);
|
|
704
|
-
if (
|
|
705
|
-
processIdentityIsWellFormed(reported) &&
|
|
706
|
-
reported.commandHash === expectedCommandHash &&
|
|
707
|
-
reported.runtimeToken === expectedRuntimeToken
|
|
708
|
-
) {
|
|
709
|
-
return reported;
|
|
710
|
-
}
|
|
711
|
-
const normalizedPid = Number(pid);
|
|
712
|
-
const processStat = hostIdentityPlatform && Number.isSafeInteger(normalizedPid) && normalizedPid > 0
|
|
713
|
-
? await captureProcessStart(normalizedPid)
|
|
714
|
-
: null;
|
|
715
|
-
if (!processStat?.startTimeTicks || processStat.processGroupId !== normalizedPid) {
|
|
716
|
-
const error = new Error(`${label} process identity could not be captured.`);
|
|
717
|
-
error.code = unavailableCode;
|
|
718
|
-
throw error;
|
|
719
|
-
}
|
|
720
|
-
return {
|
|
721
|
-
commandHash: expectedCommandHash,
|
|
722
|
-
platform: hostIdentityPlatform,
|
|
723
|
-
runtimeToken: expectedRuntimeToken,
|
|
724
|
-
startTimeTicks: processStat.startTimeTicks,
|
|
725
|
-
version: PROCESS_IDENTITY_VERSION
|
|
726
|
-
};
|
|
727
|
-
}
|
|
192
|
+
close
|
|
728
193
|
|
|
729
|
-
return Object.freeze({
|
|
730
|
-
normalize: normalizeProcessIdentity,
|
|
731
|
-
isValid: processIdentityIsWellFormed,
|
|
732
|
-
normalizeInspection: normalizeProcessIdentityInspection,
|
|
733
|
-
capture: captureProcessIdentity,
|
|
734
|
-
inspect: inspectProcessIdentity,
|
|
735
|
-
stop: stopProcessGroup,
|
|
736
|
-
isProcessAlive: processIsAlive
|
|
737
194
|
});
|
|
738
195
|
}
|