@byok-sdk/client 0.14.0 → 0.15.0
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/README.md +15 -0
- package/dist/adapters/codex/codex-adapter.d.ts +2 -0
- package/dist/adapters/codex/process-runner.d.ts +16 -17
- package/dist/adapters/detect-outcome.d.ts +18 -0
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +189 -50
- package/dist/adapters/index.js.map +1 -1
- package/dist/bin/byok-agent-memory-mcp.js +29 -0
- package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
- package/dist/bin/byok-agent-message-mcp.js +29 -0
- package/dist/bin/byok-agent-message-mcp.js.map +1 -1
- package/dist/bin/byok-agent-team-mcp.js +29 -0
- package/dist/bin/byok-agent-team-mcp.js.map +1 -1
- package/dist/bin/byok-agent.js +1832 -932
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js +29 -0
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/byok-mcp-env.d.ts +2 -0
- package/dist/bin/byok-mcp-env.js +36 -0
- package/dist/bin/byok-mcp-env.js.map +1 -0
- package/dist/bin/commands/doctor.d.ts +3 -0
- package/dist/bin/mcp-env-launcher.d.ts +5 -0
- package/dist/bin/runtime-probe.d.ts +6 -7
- package/dist/daemon/admission-wait.d.ts +2 -0
- package/dist/daemon/agent-egress-spool.d.ts +1 -0
- package/dist/daemon/agent-message-outbox.d.ts +15 -3
- package/dist/daemon/artifact-read.d.ts +7 -0
- package/dist/daemon/connection-manager.d.ts +18 -77
- package/dist/daemon/create-daemon.d.ts +11 -1
- package/dist/daemon/runtime-start.d.ts +3 -0
- package/dist/daemon/store.d.ts +2 -0
- package/dist/daemon/task-runner.d.ts +23 -0
- package/dist/daemon/terminal-commit-queue.d.ts +21 -0
- package/dist/daemon/terminal-identity.d.ts +6 -0
- package/dist/diagnostics/device-doctor.d.ts +46 -0
- package/dist/diagnostics/diagnostics.d.ts +3 -80
- package/dist/diagnostics/types.d.ts +82 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1942 -423
- package/dist/index.js.map +1 -1
- package/dist/runtime-detection.d.ts +3 -0
- package/dist/runtime-failure.d.ts +6 -0
- package/dist/sdk-reserved-helper-host.d.ts +1 -1
- package/dist/types.d.ts +20 -10
- package/dist/util/durable-jsonl.d.ts +12 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { randomUUID, createHash, randomBytes, createPrivateKey, generateKeyPairSync, sign, timingSafeEqual, createHmac } from 'crypto';
|
|
2
|
-
import { promises, mkdirSync, constants, existsSync, linkSync, fstatSync, lstatSync, unlinkSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, readFileSync, realpathSync } from 'fs';
|
|
2
|
+
import { promises, mkdirSync, constants, existsSync, linkSync, fstatSync, lstatSync, unlinkSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, readFileSync, opendirSync, realpathSync, openSync, readSync, closeSync } from 'fs';
|
|
3
3
|
import * as path3 from 'path';
|
|
4
4
|
import path3__default, { join, isAbsolute } from 'path';
|
|
5
|
-
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, AgentHomeProjectionPayloadSchema, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, isTaskOfferType, partitionAgentEvents, BYOK_SKILL_PACKS_PATH, byokSkillPackFilePath, BYOK_RECORDS_PATH, byokRecordPath, TASK_TRANSITIONS, AgentEgressPolicySchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentContentReceiptPayloadSchema, decodeEnvelope, createEnvelope, encodeEnvelope, PROTOCOL_VERSION, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, PROVIDER_PROFILE_BINDING_CAPABILITY, STRICT_AGENT_ONLY_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, AgentHomeProjectionCompletionRequestSchema, byokAgentHomeProjectionCompletionPath, AgentHomeProjectionReadbackSchema, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, isServerToDaemonType, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema,
|
|
5
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, AgentHomeProjectionPayloadSchema, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, isTaskOfferType, partitionAgentEvents, BYOK_SKILL_PACKS_PATH, byokSkillPackFilePath, BYOK_RECORDS_PATH, byokRecordPath, TASK_TRANSITIONS, AgentEgressPolicySchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentContentReceiptPayloadSchema, decodeEnvelope, createEnvelope, encodeEnvelope, PROTOCOL_VERSION, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, RuntimeIdSchema, HarnessIdSchema, TERMINAL_PROJECTION_SELECTION_CAPABILITY, PROVIDER_PROFILE_BINDING_CAPABILITY, STRICT_AGENT_ONLY_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, AgentHomeProjectionCompletionRequestSchema, byokAgentHomeProjectionCompletionPath, AgentHomeProjectionReadbackSchema, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, isServerToDaemonType, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, AGENT_MEMORY_PROJECTION_CAPABILITY, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, MAILBOX_READ_AHEAD_MAX_SEQS, MAILBOX_READ_AHEAD_CAPABILITY, MESSAGE_TYPES, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionMutationSchema, AGENT_MEMORY_PROJECTION_MAX_REDACTED_BYTES } from '@byok-sdk/protocol';
|
|
6
6
|
import net2, { createServer, createConnection } from 'net';
|
|
7
7
|
import * as os4 from 'os';
|
|
8
8
|
import os4__default from 'os';
|
|
9
9
|
import { execFile, spawn, spawnSync } from 'child_process';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { createInterface } from 'readline';
|
|
12
|
-
import { parseDeviceAssertionEnvelope, SKILL_PACK_MAX_BYTES, hasCapability, parseSkillPackManifest, checkSkillPackManifest, skillPackContentHashInput, checkSkillPackFileContent, SKILL_PACK_ENTRY_PATH, checkSkillPackEntry, isSkillPackPathSafe, DEVICE_PROOF_HEADER, contentHash as contentHash$1, TRUTH_RECORD_KINDS, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, CONTENT_HASH_PATTERN,
|
|
12
|
+
import { parseDeviceAssertionEnvelope, SKILL_PACK_MAX_BYTES, hasCapability, parseSkillPackManifest, checkSkillPackManifest, skillPackContentHashInput, checkSkillPackFileContent, SKILL_PACK_ENTRY_PATH, checkSkillPackEntry, isSkillPackPathSafe, DEVICE_PROOF_HEADER, contentHash as contentHash$1, TRUTH_RECORD_KINDS, isTenantId, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, CONTENT_HASH_PATTERN, nonceSigningBytes, CapabilityDeclarationSchema, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
14
|
import * as fs14 from 'fs/promises';
|
|
15
15
|
import { createRequire } from 'module';
|
|
@@ -2211,6 +2211,7 @@ async function acquire(input) {
|
|
|
2211
2211
|
var localStateRelocation = Object.freeze({ acquire });
|
|
2212
2212
|
|
|
2213
2213
|
// src/types.ts
|
|
2214
|
+
var RUNTIME_DETECTION_FAILURE_KINDS = ["not-found", "not-executable", "timeout", "probe-failed"];
|
|
2214
2215
|
function frozenStrings(values) {
|
|
2215
2216
|
return values === void 0 ? void 0 : Object.freeze([...values]);
|
|
2216
2217
|
}
|
|
@@ -2307,6 +2308,31 @@ function resolveLocalAgentReleaseIdentity(input) {
|
|
|
2307
2308
|
...input.buildId === void 0 ? {} : { buildId: input.buildId }
|
|
2308
2309
|
});
|
|
2309
2310
|
}
|
|
2311
|
+
async function runMcpEnvLauncher() {
|
|
2312
|
+
const key = process.env.BYOK_MCP_ENV_KEY;
|
|
2313
|
+
if (!key || !/^BYOK_MCP_PAYLOAD_[A-F0-9]{32}$/.test(key)) throw new Error("invalid MCP environment binding");
|
|
2314
|
+
const encoded = process.env[key];
|
|
2315
|
+
if (!encoded) throw new Error("missing sealed MCP environment");
|
|
2316
|
+
let config;
|
|
2317
|
+
try {
|
|
2318
|
+
config = JSON.parse(encoded);
|
|
2319
|
+
if (!config || typeof config.command !== "string" || !config.command || config.args !== void 0 && (!Array.isArray(config.args) || config.args.some((value) => typeof value !== "string")) || config.env !== void 0 && (typeof config.env !== "object" || config.env === null || Array.isArray(config.env) || Object.values(config.env).some((value) => typeof value !== "string"))) throw new Error();
|
|
2320
|
+
} catch {
|
|
2321
|
+
throw new Error("invalid sealed MCP environment");
|
|
2322
|
+
}
|
|
2323
|
+
const env = { ...process.env };
|
|
2324
|
+
delete env.BYOK_MCP_ENV_KEY;
|
|
2325
|
+
for (const name of Object.keys(env)) if (name.startsWith("BYOK_MCP_PAYLOAD_")) delete env[name];
|
|
2326
|
+
Object.assign(env, config.env);
|
|
2327
|
+
await new Promise((resolve, reject) => {
|
|
2328
|
+
const child = spawn(config.command, config.args ?? [], { env, stdio: "inherit", windowsHide: true });
|
|
2329
|
+
child.once("error", () => reject(new Error("MCP child could not be spawned")));
|
|
2330
|
+
child.once("close", (code) => {
|
|
2331
|
+
if (code === 0) resolve();
|
|
2332
|
+
else reject(new Error("MCP child exited unsuccessfully"));
|
|
2333
|
+
});
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2310
2336
|
var CONTROL_PROTOCOL_VERSION = 1;
|
|
2311
2337
|
var HANDSHAKE_TIMEOUT_MS = 3e3;
|
|
2312
2338
|
var UNIX_SOCKET_PATH_SOFT_LIMIT2 = 100;
|
|
@@ -3174,6 +3200,9 @@ async function runAgentTeamMcp() {
|
|
|
3174
3200
|
}
|
|
3175
3201
|
async function runSdkReservedHelper(kind) {
|
|
3176
3202
|
switch (kind) {
|
|
3203
|
+
case "mcp-env":
|
|
3204
|
+
await runMcpEnvLauncher();
|
|
3205
|
+
return;
|
|
3177
3206
|
case "agent-message-mcp":
|
|
3178
3207
|
await runAgentMessageMcp();
|
|
3179
3208
|
return;
|
|
@@ -3195,7 +3224,8 @@ var DIST_SCRIPT_BY_KIND = Object.freeze({
|
|
|
3195
3224
|
"agent-message-mcp": "byok-agent-message-mcp.js",
|
|
3196
3225
|
"agent-memory-mcp": "byok-agent-memory-mcp.js",
|
|
3197
3226
|
"approval-mcp": "byok-approval-mcp.js",
|
|
3198
|
-
"agent-team-mcp": "byok-agent-team-mcp.js"
|
|
3227
|
+
"agent-team-mcp": "byok-agent-team-mcp.js",
|
|
3228
|
+
"mcp-env": "byok-mcp-env.js"
|
|
3199
3229
|
});
|
|
3200
3230
|
function assertExecutable(executable) {
|
|
3201
3231
|
if (!path3__default.isAbsolute(executable) || /[\u0000\r\n]/u.test(executable)) {
|
|
@@ -3213,8 +3243,11 @@ function resolveSdkReservedHelperBin(kind, host) {
|
|
|
3213
3243
|
source: "self-executable"
|
|
3214
3244
|
});
|
|
3215
3245
|
}
|
|
3246
|
+
const entryDir = path3__default.dirname(fileURLToPath(import.meta.url));
|
|
3247
|
+
const entryKind = path3__default.basename(entryDir);
|
|
3248
|
+
const distDir = entryKind === "adapters" || entryKind === "bin" ? path3__default.dirname(entryDir) : entryDir;
|
|
3216
3249
|
const script = path3__default.join(
|
|
3217
|
-
|
|
3250
|
+
distDir,
|
|
3218
3251
|
"bin",
|
|
3219
3252
|
DIST_SCRIPT_BY_KIND[kind]
|
|
3220
3253
|
);
|
|
@@ -3225,7 +3258,7 @@ function resolveSdkReservedHelperBin(kind, host) {
|
|
|
3225
3258
|
});
|
|
3226
3259
|
}
|
|
3227
3260
|
function isHelperKind(value) {
|
|
3228
|
-
return value === "agent-message-mcp" || value === "agent-memory-mcp" || value === "approval-mcp" || value === "agent-team-mcp";
|
|
3261
|
+
return value === "agent-message-mcp" || value === "agent-memory-mcp" || value === "approval-mcp" || value === "agent-team-mcp" || value === "mcp-env";
|
|
3229
3262
|
}
|
|
3230
3263
|
async function runSdkReservedHelperCommand(argv = process.argv.slice(2)) {
|
|
3231
3264
|
if (argv[0] !== BYOK_SDK_HELPER_SUBCOMMAND) return false;
|
|
@@ -3319,6 +3352,19 @@ function projectRuntimeBoundaryFailure(value, expectedPhase) {
|
|
|
3319
3352
|
contractViolation: true
|
|
3320
3353
|
};
|
|
3321
3354
|
}
|
|
3355
|
+
var RuntimeStartupDisposalFailure = class extends Error {
|
|
3356
|
+
retryDisposal;
|
|
3357
|
+
constructor(retryDisposal, options) {
|
|
3358
|
+
super("runtime startup failed and process ownership remains quarantined", options);
|
|
3359
|
+
this.name = "RuntimeStartupDisposalFailure";
|
|
3360
|
+
this.retryDisposal = retryDisposal;
|
|
3361
|
+
Object.defineProperty(this, /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1"), { value: true });
|
|
3362
|
+
Object.freeze(this);
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
function isRuntimeStartupDisposalFailure(value) {
|
|
3366
|
+
return typeof value === "object" && value !== null && value[/* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1")] === true && typeof value.retryDisposal === "function";
|
|
3367
|
+
}
|
|
3322
3368
|
var GitWorkspaceError = class extends Error {
|
|
3323
3369
|
constructor(category, message = category) {
|
|
3324
3370
|
super(message);
|
|
@@ -3824,6 +3870,132 @@ var GitWorkspaceStore = class {
|
|
|
3824
3870
|
await atomicWriteFile(this.filePath, JSON.stringify(ledger, null, 2), { mode: 384 });
|
|
3825
3871
|
}
|
|
3826
3872
|
};
|
|
3873
|
+
function terminalIdentity(runtimeId, agentRef) {
|
|
3874
|
+
return {
|
|
3875
|
+
...runtimeId === void 0 || RuntimeIdSchema.safeParse(runtimeId).success ? {} : { harnessId: HarnessIdSchema.parse(runtimeId) },
|
|
3876
|
+
...agentRef === void 0 ? {} : { agentRef }
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
// src/daemon/terminal-commit-queue.ts
|
|
3881
|
+
var TerminalCommitQueue = class {
|
|
3882
|
+
constructor(committed) {
|
|
3883
|
+
this.committed = committed;
|
|
3884
|
+
}
|
|
3885
|
+
committed;
|
|
3886
|
+
pending = /* @__PURE__ */ new Map();
|
|
3887
|
+
tail = Promise.resolve();
|
|
3888
|
+
timer;
|
|
3889
|
+
stopped = false;
|
|
3890
|
+
get pendingCount() {
|
|
3891
|
+
return this.pending.size;
|
|
3892
|
+
}
|
|
3893
|
+
hasPending(taskId) {
|
|
3894
|
+
return this.pending.has(taskId);
|
|
3895
|
+
}
|
|
3896
|
+
enqueue(taskId, commit) {
|
|
3897
|
+
if (!this.pending.has(taskId)) this.pending.set(taskId, { commit, receipt: Promise.resolve(), running: false });
|
|
3898
|
+
void this.attempt(taskId).catch(() => void 0);
|
|
3899
|
+
}
|
|
3900
|
+
receipt(taskId) {
|
|
3901
|
+
return this.pending.get(taskId)?.receipt ?? Promise.resolve();
|
|
3902
|
+
}
|
|
3903
|
+
retry(taskId) {
|
|
3904
|
+
return this.attempt(taskId);
|
|
3905
|
+
}
|
|
3906
|
+
resume() {
|
|
3907
|
+
this.stopped = false;
|
|
3908
|
+
if (this.pending.size > 0) this.armRetry();
|
|
3909
|
+
}
|
|
3910
|
+
async stop() {
|
|
3911
|
+
this.stopped = true;
|
|
3912
|
+
if (this.timer) clearTimeout(this.timer);
|
|
3913
|
+
this.timer = void 0;
|
|
3914
|
+
await Promise.all([...this.pending.keys()].map((taskId) => this.attempt(taskId)));
|
|
3915
|
+
}
|
|
3916
|
+
attempt(taskId) {
|
|
3917
|
+
const entry = this.pending.get(taskId);
|
|
3918
|
+
if (!entry) return Promise.resolve();
|
|
3919
|
+
if (entry.running) return entry.receipt;
|
|
3920
|
+
entry.running = true;
|
|
3921
|
+
const receipt = this.tail.catch(() => void 0).then(entry.commit);
|
|
3922
|
+
entry.receipt = receipt.then(() => {
|
|
3923
|
+
if (this.pending.get(taskId) === entry) this.pending.delete(taskId);
|
|
3924
|
+
this.committed(taskId);
|
|
3925
|
+
}, (error) => {
|
|
3926
|
+
entry.running = false;
|
|
3927
|
+
this.armRetry();
|
|
3928
|
+
throw error;
|
|
3929
|
+
});
|
|
3930
|
+
this.tail = entry.receipt;
|
|
3931
|
+
void entry.receipt.catch(() => void 0);
|
|
3932
|
+
return entry.receipt;
|
|
3933
|
+
}
|
|
3934
|
+
armRetry() {
|
|
3935
|
+
if (this.timer || this.stopped) return;
|
|
3936
|
+
this.timer = setTimeout(() => {
|
|
3937
|
+
this.timer = void 0;
|
|
3938
|
+
for (const taskId of this.pending.keys()) void this.attempt(taskId).catch(() => void 0);
|
|
3939
|
+
}, 1e3);
|
|
3940
|
+
this.timer.unref?.();
|
|
3941
|
+
}
|
|
3942
|
+
};
|
|
3943
|
+
|
|
3944
|
+
// src/runtime-detection.ts
|
|
3945
|
+
function validateRuntimeDetectResult(value) {
|
|
3946
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3947
|
+
throw new TypeError("invalid runtime detection result");
|
|
3948
|
+
}
|
|
3949
|
+
const result = value;
|
|
3950
|
+
if (!Object.hasOwn(result, "kind")) throw new TypeError("invalid runtime detection result");
|
|
3951
|
+
const { kind, version, authPresent } = result;
|
|
3952
|
+
if (kind === "available") {
|
|
3953
|
+
if (Object.keys(result).some((key) => !["kind", "version", "authPresent"].includes(key)) || version !== void 0 && typeof version !== "string" || authPresent !== void 0 && typeof authPresent !== "boolean") {
|
|
3954
|
+
throw new TypeError("invalid runtime detection result");
|
|
3955
|
+
}
|
|
3956
|
+
return Object.freeze({
|
|
3957
|
+
kind,
|
|
3958
|
+
...version === void 0 ? {} : { version },
|
|
3959
|
+
...authPresent === void 0 ? {} : { authPresent }
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
if (RUNTIME_DETECTION_FAILURE_KINDS.some((candidate) => candidate === kind) && Object.keys(result).every((key) => key === "kind")) {
|
|
3963
|
+
return Object.freeze({ kind });
|
|
3964
|
+
}
|
|
3965
|
+
throw new TypeError("invalid runtime detection result");
|
|
3966
|
+
}
|
|
3967
|
+
function classifyDetectError(error) {
|
|
3968
|
+
const code = error !== null && typeof error === "object" ? error.code : void 0;
|
|
3969
|
+
if (code === "ENOENT") return { kind: "not-found" };
|
|
3970
|
+
if (code === "EACCES" || code === "EPERM" || code === "ENOEXEC") return { kind: "not-executable" };
|
|
3971
|
+
return { kind: "probe-failed" };
|
|
3972
|
+
}
|
|
3973
|
+
async function probeRuntimeVersion(command, timeoutMs) {
|
|
3974
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("invalid runtime probe timeout");
|
|
3975
|
+
return new Promise((resolve) => {
|
|
3976
|
+
let timer;
|
|
3977
|
+
let timedOut = false;
|
|
3978
|
+
try {
|
|
3979
|
+
const child = execFile(command, ["--version"], { encoding: "utf8" }, (error, stdout, stderr) => {
|
|
3980
|
+
if (timer) clearTimeout(timer);
|
|
3981
|
+
if (error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") resolve({ kind: "probe-failed" });
|
|
3982
|
+
else if (timedOut) resolve({ kind: "timeout" });
|
|
3983
|
+
else if (error) resolve(classifyDetectError(error));
|
|
3984
|
+
else resolve({ kind: "available", stdout, stderr });
|
|
3985
|
+
});
|
|
3986
|
+
timer = setTimeout(() => {
|
|
3987
|
+
timedOut = true;
|
|
3988
|
+
child.stdout?.destroy();
|
|
3989
|
+
child.stderr?.destroy();
|
|
3990
|
+
child.kill("SIGKILL");
|
|
3991
|
+
}, timeoutMs);
|
|
3992
|
+
timer.unref?.();
|
|
3993
|
+
} catch (error) {
|
|
3994
|
+
if (timer) clearTimeout(timer);
|
|
3995
|
+
resolve(classifyDetectError(error));
|
|
3996
|
+
}
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3827
3999
|
|
|
3828
4000
|
// src/adapters/pi/mcp-config.ts
|
|
3829
4001
|
var BYOK_PI_MCP_CONFIG_PATH = "BYOK_PI_MCP_CONFIG_PATH";
|
|
@@ -4834,12 +5006,13 @@ var PiAdapter = class {
|
|
|
4834
5006
|
async detect() {
|
|
4835
5007
|
try {
|
|
4836
5008
|
const bin = this.resolveBin();
|
|
4837
|
-
const
|
|
4838
|
-
|
|
5009
|
+
const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS);
|
|
5010
|
+
if (probe.kind !== "available") return probe;
|
|
5011
|
+
const version = probe.stdout.trim() || probe.stderr.trim();
|
|
4839
5012
|
const authPresent = PROVIDER_CREDENTIAL_ENV_NAMES.some((name) => process.env[name] !== void 0);
|
|
4840
|
-
return {
|
|
4841
|
-
} catch {
|
|
4842
|
-
return
|
|
5013
|
+
return { kind: "available", version, authPresent };
|
|
5014
|
+
} catch (error) {
|
|
5015
|
+
return classifyDetectError(error);
|
|
4843
5016
|
}
|
|
4844
5017
|
}
|
|
4845
5018
|
async prepare(input) {
|
|
@@ -5969,14 +6142,15 @@ var ClaudeAdapter = class {
|
|
|
5969
6142
|
environmentRequirements: { credentialNames: [] }
|
|
5970
6143
|
});
|
|
5971
6144
|
async detect() {
|
|
5972
|
-
const bin = this.resolveBin();
|
|
5973
6145
|
try {
|
|
5974
|
-
const
|
|
5975
|
-
const
|
|
6146
|
+
const bin = this.resolveBin();
|
|
6147
|
+
const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS2);
|
|
6148
|
+
if (probe.kind !== "available") return probe;
|
|
6149
|
+
const version = probe.stdout.trim();
|
|
5976
6150
|
const authPresent = await this.probeAuthPresent(bin.command);
|
|
5977
|
-
return {
|
|
5978
|
-
} catch {
|
|
5979
|
-
return
|
|
6151
|
+
return { kind: "available", version, authPresent };
|
|
6152
|
+
} catch (error) {
|
|
6153
|
+
return classifyDetectError(error);
|
|
5980
6154
|
}
|
|
5981
6155
|
}
|
|
5982
6156
|
async prepare(input) {
|
|
@@ -6595,10 +6769,18 @@ function unmappedFrameKey(evt) {
|
|
|
6595
6769
|
return evt.type;
|
|
6596
6770
|
}
|
|
6597
6771
|
var STDERR_RING_CAPACITY3 = 20;
|
|
6772
|
+
var CODEX_MAX_FRAME_BYTES = 1024 * 1024;
|
|
6773
|
+
var CODEX_MAX_DEFERRED_BYTES = 4 * 1024 * 1024;
|
|
6774
|
+
var CODEX_MAX_STDERR_BYTES = 64 * 1024;
|
|
6598
6775
|
var CodexProcessRunner = class {
|
|
6599
6776
|
child;
|
|
6600
6777
|
onEvent;
|
|
6601
|
-
|
|
6778
|
+
frameChunks = [];
|
|
6779
|
+
frameBytes = 0;
|
|
6780
|
+
deferredBytes = 0;
|
|
6781
|
+
stderrBytes = 0;
|
|
6782
|
+
transportFailure;
|
|
6783
|
+
onFailure;
|
|
6602
6784
|
stderrRing = [];
|
|
6603
6785
|
closed = false;
|
|
6604
6786
|
exitCode = null;
|
|
@@ -6621,11 +6803,12 @@ var CodexProcessRunner = class {
|
|
|
6621
6803
|
deferredEvents = [];
|
|
6622
6804
|
constructor(options) {
|
|
6623
6805
|
this.onEvent = options.onEvent;
|
|
6806
|
+
this.onFailure = options.onFailure;
|
|
6624
6807
|
const spawnFn = options.spawnFn ?? spawn;
|
|
6625
6808
|
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
6626
6809
|
cwd: options.cwd,
|
|
6627
6810
|
env: options.env,
|
|
6628
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
6811
|
+
stdio: [options.instruction === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
6629
6812
|
}));
|
|
6630
6813
|
this.closedPromise = new Promise((resolve) => {
|
|
6631
6814
|
this.resolveClosed = resolve;
|
|
@@ -6633,7 +6816,6 @@ var CodexProcessRunner = class {
|
|
|
6633
6816
|
this.adopted = this.adoptOwnedTree(options);
|
|
6634
6817
|
this.adopted.catch(() => {
|
|
6635
6818
|
});
|
|
6636
|
-
this.child.stdout.setEncoding("utf8");
|
|
6637
6819
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
6638
6820
|
this.child.stderr.setEncoding("utf8");
|
|
6639
6821
|
this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
|
|
@@ -6642,6 +6824,12 @@ var CodexProcessRunner = class {
|
|
|
6642
6824
|
this.exitSignal = signal;
|
|
6643
6825
|
this.finishClosing();
|
|
6644
6826
|
});
|
|
6827
|
+
if (options.instruction !== void 0) {
|
|
6828
|
+
this.child.stdin.on("error", () => {
|
|
6829
|
+
this.failTransport("codex prompt stdin failed before delivery");
|
|
6830
|
+
});
|
|
6831
|
+
this.child.stdin.end(options.instruction, "utf8");
|
|
6832
|
+
}
|
|
6645
6833
|
this.child.on("error", () => {
|
|
6646
6834
|
this.finishClosing();
|
|
6647
6835
|
});
|
|
@@ -6727,12 +6915,19 @@ var CodexProcessRunner = class {
|
|
|
6727
6915
|
throw failure;
|
|
6728
6916
|
}
|
|
6729
6917
|
this.adoption = "adopted";
|
|
6918
|
+
this.deferredBytes = 0;
|
|
6730
6919
|
for (const evt of this.deferredEvents.splice(0)) this.onEvent(evt);
|
|
6731
6920
|
}
|
|
6732
6921
|
/** Arrival-order delivery, held back until the tree is backstopped (see `deferredEvents`). */
|
|
6733
|
-
deliver(evt) {
|
|
6922
|
+
deliver(evt, bytes) {
|
|
6923
|
+
if (this.transportFailure) return;
|
|
6734
6924
|
if (this.adoption === "failed") return;
|
|
6735
6925
|
if (this.adoption === "pending") {
|
|
6926
|
+
if (this.deferredBytes + bytes > CODEX_MAX_DEFERRED_BYTES) {
|
|
6927
|
+
this.failTransport("codex pre-adoption event buffer exceeded its byte budget");
|
|
6928
|
+
return;
|
|
6929
|
+
}
|
|
6930
|
+
this.deferredBytes += bytes;
|
|
6736
6931
|
this.deferredEvents.push(evt);
|
|
6737
6932
|
return;
|
|
6738
6933
|
}
|
|
@@ -6749,6 +6944,7 @@ var CodexProcessRunner = class {
|
|
|
6749
6944
|
* plus an empty stderr tail would bury the only reason anyone can act on.
|
|
6750
6945
|
*/
|
|
6751
6946
|
buildExitError(context) {
|
|
6947
|
+
if (this.transportFailure) return this.transportFailure;
|
|
6752
6948
|
if (this.adoptionFailure !== void 0) return this.adoptionFailure;
|
|
6753
6949
|
const parts = [`${context} (exit code=${this.exitCode}, signal=${this.exitSignal})`];
|
|
6754
6950
|
if (this.stderrRing.length > 0) {
|
|
@@ -6756,18 +6952,45 @@ var CodexProcessRunner = class {
|
|
|
6756
6952
|
}
|
|
6757
6953
|
return new Error(parts.join("; "));
|
|
6758
6954
|
}
|
|
6955
|
+
failTransport(reason) {
|
|
6956
|
+
if (this.transportFailure) return;
|
|
6957
|
+
this.transportFailure = new RuntimeExecutionFailure({
|
|
6958
|
+
phase: "run",
|
|
6959
|
+
category: "infrastructure",
|
|
6960
|
+
retry: "non-retryable",
|
|
6961
|
+
reason
|
|
6962
|
+
});
|
|
6963
|
+
this.frameChunks.length = 0;
|
|
6964
|
+
this.frameBytes = 0;
|
|
6965
|
+
this.deferredEvents.length = 0;
|
|
6966
|
+
this.deferredBytes = 0;
|
|
6967
|
+
this.onFailure?.(this.transportFailure);
|
|
6968
|
+
this.kill();
|
|
6969
|
+
}
|
|
6759
6970
|
onData(chunk) {
|
|
6760
|
-
this.
|
|
6761
|
-
let
|
|
6762
|
-
while (
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
if (
|
|
6767
|
-
|
|
6971
|
+
if (this.transportFailure) return;
|
|
6972
|
+
let offset = 0;
|
|
6973
|
+
while (offset < chunk.length) {
|
|
6974
|
+
const newline = chunk.indexOf(10, offset);
|
|
6975
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
6976
|
+
const bytes = end - offset;
|
|
6977
|
+
if (this.frameBytes + bytes > CODEX_MAX_FRAME_BYTES) {
|
|
6978
|
+
this.failTransport("codex raw JSONL frame exceeded its byte budget");
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
if (bytes > 0) this.frameChunks.push(Buffer.from(chunk.subarray(offset, end)));
|
|
6982
|
+
this.frameBytes += bytes;
|
|
6983
|
+
if (newline === -1) return;
|
|
6984
|
+
const frame = Buffer.concat(this.frameChunks, this.frameBytes);
|
|
6985
|
+
this.frameChunks.length = 0;
|
|
6986
|
+
this.frameBytes = 0;
|
|
6987
|
+
const line = frame.toString("utf8").replace(/\r$/, "");
|
|
6988
|
+
if (line.length > 0) this.parseLine(line, frame.length);
|
|
6989
|
+
if (this.transportFailure) return;
|
|
6990
|
+
offset = newline + 1;
|
|
6768
6991
|
}
|
|
6769
6992
|
}
|
|
6770
|
-
parseLine(line) {
|
|
6993
|
+
parseLine(line, bytes) {
|
|
6771
6994
|
let parsed;
|
|
6772
6995
|
try {
|
|
6773
6996
|
parsed = JSON.parse(line);
|
|
@@ -6777,14 +7000,18 @@ var CodexProcessRunner = class {
|
|
|
6777
7000
|
if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
|
|
6778
7001
|
return;
|
|
6779
7002
|
}
|
|
6780
|
-
this.deliver(parsed);
|
|
7003
|
+
this.deliver(parsed, bytes);
|
|
6781
7004
|
}
|
|
6782
7005
|
onStderr(chunk) {
|
|
6783
|
-
|
|
7006
|
+
const bounded2 = Buffer.from(chunk).subarray(-CODEX_MAX_STDERR_BYTES).toString("utf8");
|
|
7007
|
+
for (const rawLine of bounded2.split("\n")) {
|
|
6784
7008
|
const line = rawLine.trim();
|
|
6785
7009
|
if (line.length === 0) continue;
|
|
6786
7010
|
this.stderrRing.push(line);
|
|
6787
|
-
|
|
7011
|
+
this.stderrBytes += Buffer.byteLength(line);
|
|
7012
|
+
while (this.stderrRing.length > STDERR_RING_CAPACITY3 || this.stderrBytes > CODEX_MAX_STDERR_BYTES) {
|
|
7013
|
+
this.stderrBytes -= Buffer.byteLength(this.stderrRing.shift());
|
|
7014
|
+
}
|
|
6788
7015
|
}
|
|
6789
7016
|
}
|
|
6790
7017
|
};
|
|
@@ -6817,14 +7044,15 @@ var CodexAdapter = class {
|
|
|
6817
7044
|
environmentRequirements: { credentialNames: [] }
|
|
6818
7045
|
});
|
|
6819
7046
|
async detect() {
|
|
6820
|
-
const bin = this.resolveBin();
|
|
6821
7047
|
try {
|
|
6822
|
-
const
|
|
6823
|
-
const
|
|
7048
|
+
const bin = this.resolveBin();
|
|
7049
|
+
const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS3);
|
|
7050
|
+
if (probe.kind !== "available") return probe;
|
|
7051
|
+
const version = probe.stdout.trim() || probe.stderr.trim();
|
|
6824
7052
|
const authPresent = await this.probeAuthPresent(bin);
|
|
6825
|
-
return {
|
|
6826
|
-
} catch {
|
|
6827
|
-
return
|
|
7053
|
+
return { kind: "available", version, authPresent };
|
|
7054
|
+
} catch (error) {
|
|
7055
|
+
return classifyDetectError(error);
|
|
6828
7056
|
}
|
|
6829
7057
|
}
|
|
6830
7058
|
/**
|
|
@@ -6967,7 +7195,7 @@ ${withStreams.stderr ?? ""}`);
|
|
|
6967
7195
|
reason: "prepared codex operation received a manifest with different runtime selection"
|
|
6968
7196
|
});
|
|
6969
7197
|
}
|
|
6970
|
-
const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, preparedMcpGrants);
|
|
7198
|
+
const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, runtimeEnv, this.options.sdkHelperHost, preparedMcpGrants);
|
|
6971
7199
|
const { sessionRef, runner } = await runCodexTurn({
|
|
6972
7200
|
command,
|
|
6973
7201
|
resumeRef: startInput.manifest.sessionRef,
|
|
@@ -6983,6 +7211,7 @@ ${withStreams.stderr ?? ""}`);
|
|
|
6983
7211
|
expectedSessionRef: startInput.manifest.sessionRef,
|
|
6984
7212
|
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
6985
7213
|
failurePhase: "start",
|
|
7214
|
+
signal: startInput.signal,
|
|
6986
7215
|
terminal
|
|
6987
7216
|
});
|
|
6988
7217
|
return new CodexSession({
|
|
@@ -6990,6 +7219,7 @@ ${withStreams.stderr ?? ""}`);
|
|
|
6990
7219
|
command,
|
|
6991
7220
|
workspaceDir,
|
|
6992
7221
|
env: startInput.env,
|
|
7222
|
+
mcpEnvironment: Object.fromEntries(Object.entries(runtimeEnv).filter(([key]) => key.startsWith("BYOK_MCP_PAYLOAD_"))),
|
|
6993
7223
|
spawnFn: this.options.spawnFn,
|
|
6994
7224
|
queue,
|
|
6995
7225
|
recordUnmapped,
|
|
@@ -7042,16 +7272,18 @@ function codexMcpToolApprovalArgs(name, tools) {
|
|
|
7042
7272
|
}
|
|
7043
7273
|
return args;
|
|
7044
7274
|
}
|
|
7045
|
-
function codexMcpConfigArgs(servers, grants = []) {
|
|
7275
|
+
function codexMcpConfigArgs(servers, env, helperHost, grants = []) {
|
|
7046
7276
|
if (servers === void 0 || Object.keys(servers).length === 0) return [];
|
|
7047
7277
|
const grantedTools = new Map(grants.map((grant) => [grant.server, grant.tools]));
|
|
7048
7278
|
const args = ["--ignore-user-config"];
|
|
7049
7279
|
for (const [name, server] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
}
|
|
7280
|
+
const key = `BYOK_MCP_PAYLOAD_${randomBytes(16).toString("hex").toUpperCase()}`;
|
|
7281
|
+
env[key] = JSON.stringify(server);
|
|
7282
|
+
const helper = resolveSdkReservedHelperBin("mcp-env", helperHost);
|
|
7283
|
+
args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(helper.command)}`);
|
|
7284
|
+
args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...helper.args])}`);
|
|
7285
|
+
args.push("-c", `mcp_servers.${name}.env.BYOK_MCP_ENV_KEY=${JSON.stringify(key)}`);
|
|
7286
|
+
args.push("-c", `mcp_servers.${name}.env_vars=${JSON.stringify([key])}`);
|
|
7055
7287
|
const granted = grantedTools.get(name);
|
|
7056
7288
|
if (granted !== void 0) args.push(...codexMcpToolApprovalArgs(name, granted));
|
|
7057
7289
|
}
|
|
@@ -7079,7 +7311,7 @@ function buildArgv(resumeRef, policyArgs, instruction, modelId, preparedGit = fa
|
|
|
7079
7311
|
...modelId ? ["--model", modelId] : [],
|
|
7080
7312
|
...preparedGit ? [] : ["--skip-git-repo-check"],
|
|
7081
7313
|
...policyArgs,
|
|
7082
|
-
|
|
7314
|
+
"-"
|
|
7083
7315
|
];
|
|
7084
7316
|
}
|
|
7085
7317
|
async function runCodexTurn(params) {
|
|
@@ -7103,9 +7335,18 @@ async function runCodexTurn(params) {
|
|
|
7103
7335
|
runner = new CodexProcessRunner({
|
|
7104
7336
|
command: params.command,
|
|
7105
7337
|
args: argv,
|
|
7338
|
+
instruction: params.instruction,
|
|
7106
7339
|
cwd: params.cwd,
|
|
7107
7340
|
env: params.env,
|
|
7108
7341
|
spawnFn: params.spawnFn,
|
|
7342
|
+
onFailure: (failure) => {
|
|
7343
|
+
if (!firstLineSettled) {
|
|
7344
|
+
firstLineSettled = true;
|
|
7345
|
+
rejectFirstLine(failure);
|
|
7346
|
+
}
|
|
7347
|
+
params.terminal.failure = failure;
|
|
7348
|
+
params.queue.end();
|
|
7349
|
+
},
|
|
7109
7350
|
onEvent: (evt) => {
|
|
7110
7351
|
if (!firstLineSettled) {
|
|
7111
7352
|
firstLineSettled = true;
|
|
@@ -7172,6 +7413,16 @@ async function runCodexTurn(params) {
|
|
|
7172
7413
|
params.queue.end();
|
|
7173
7414
|
});
|
|
7174
7415
|
let sessionRef;
|
|
7416
|
+
const abortStartup = () => rejectFirstLine(new RuntimeExecutionFailure({
|
|
7417
|
+
phase: params.failurePhase,
|
|
7418
|
+
category: "infrastructure",
|
|
7419
|
+
retry: "non-retryable",
|
|
7420
|
+
reason: "codex startup cancelled or exceeded its deadline"
|
|
7421
|
+
}));
|
|
7422
|
+
const startupTimer = setTimeout(abortStartup, 3e4);
|
|
7423
|
+
startupTimer.unref?.();
|
|
7424
|
+
params.signal?.addEventListener("abort", abortStartup, { once: true });
|
|
7425
|
+
if (params.signal?.aborted) abortStartup();
|
|
7175
7426
|
try {
|
|
7176
7427
|
sessionRef = await new Promise((resolve, reject) => {
|
|
7177
7428
|
let settled = false;
|
|
@@ -7203,20 +7454,31 @@ async function runCodexTurn(params) {
|
|
|
7203
7454
|
});
|
|
7204
7455
|
});
|
|
7205
7456
|
} catch (err) {
|
|
7206
|
-
runner
|
|
7457
|
+
await disposeFailedStartup(runner, err);
|
|
7207
7458
|
throw err;
|
|
7459
|
+
} finally {
|
|
7460
|
+
clearTimeout(startupTimer);
|
|
7461
|
+
params.signal?.removeEventListener("abort", abortStartup);
|
|
7208
7462
|
}
|
|
7209
7463
|
if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
|
|
7210
|
-
|
|
7211
|
-
throw new RuntimeExecutionFailure({
|
|
7464
|
+
const failure = new RuntimeExecutionFailure({
|
|
7212
7465
|
phase: params.failurePhase,
|
|
7213
7466
|
category: "authority",
|
|
7214
7467
|
retry: "non-retryable",
|
|
7215
7468
|
reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
|
|
7216
7469
|
});
|
|
7470
|
+
await disposeFailedStartup(runner, failure);
|
|
7471
|
+
throw failure;
|
|
7217
7472
|
}
|
|
7218
7473
|
return { sessionRef, runner };
|
|
7219
7474
|
}
|
|
7475
|
+
async function disposeFailedStartup(runner, cause) {
|
|
7476
|
+
try {
|
|
7477
|
+
await runner.dispose();
|
|
7478
|
+
} catch (disposalFailure) {
|
|
7479
|
+
throw new RuntimeStartupDisposalFailure(() => runner.dispose(), { cause: new AggregateError([cause, disposalFailure]) });
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7220
7482
|
var CodexSession = class {
|
|
7221
7483
|
/**
|
|
7222
7484
|
* NOT `readonly` (cross-model review finding): `followUp()` below
|
|
@@ -7252,6 +7514,7 @@ var CodexSession = class {
|
|
|
7252
7514
|
* recomputed from anything a later turn supplies, so a follow-up can never
|
|
7253
7515
|
* widen this session's MCP authority.
|
|
7254
7516
|
*/
|
|
7517
|
+
mcpEnvironment;
|
|
7255
7518
|
mcpConfigArgs;
|
|
7256
7519
|
terminal;
|
|
7257
7520
|
currentRunner;
|
|
@@ -7271,6 +7534,7 @@ var CodexSession = class {
|
|
|
7271
7534
|
this.modelId = options.modelId;
|
|
7272
7535
|
this.terminal = options.terminal;
|
|
7273
7536
|
this.mcpConfigArgs = options.mcpConfigArgs;
|
|
7537
|
+
this.mcpEnvironment = Object.freeze({ ...options.mcpEnvironment });
|
|
7274
7538
|
this.currentRunner = options.initialRunner;
|
|
7275
7539
|
this.ownedRunners.add(options.initialRunner);
|
|
7276
7540
|
void this.forgetRunnerOnceClosed(options.initialRunner);
|
|
@@ -7383,7 +7647,7 @@ var CodexSession = class {
|
|
|
7383
7647
|
// frozen MCP config replayed verbatim — see `mcpConfigArgs`.
|
|
7384
7648
|
policyArgs: [...mapping.args, ...this.mcpConfigArgs],
|
|
7385
7649
|
cwd: this.workspaceDir,
|
|
7386
|
-
env: withoutProviderCredentials(this.env),
|
|
7650
|
+
env: { ...withoutProviderCredentials(this.env), ...this.mcpEnvironment },
|
|
7387
7651
|
spawnFn: this.spawnFn,
|
|
7388
7652
|
workspaceDir: this.workspaceDir,
|
|
7389
7653
|
queue: this.queue,
|
|
@@ -8015,6 +8279,20 @@ var DeviceStore = class _DeviceStore {
|
|
|
8015
8279
|
await opened.handle.close();
|
|
8016
8280
|
}
|
|
8017
8281
|
}
|
|
8282
|
+
/** Rebuild only the non-secret projection, while the caller owns the store lease. */
|
|
8283
|
+
async reconcileMetadata(authority) {
|
|
8284
|
+
const projection = {
|
|
8285
|
+
deviceId: authority.deviceId,
|
|
8286
|
+
tenantId: authority.tenantId,
|
|
8287
|
+
devicePublicKey: authority.devicePublicKey
|
|
8288
|
+
};
|
|
8289
|
+
const current = await this.load();
|
|
8290
|
+
if (current !== void 0 && current.deviceId === projection.deviceId && current.tenantId === projection.tenantId && current.devicePublicKey === projection.devicePublicKey) {
|
|
8291
|
+
return false;
|
|
8292
|
+
}
|
|
8293
|
+
await this.save(projection);
|
|
8294
|
+
return true;
|
|
8295
|
+
}
|
|
8018
8296
|
async save(record3) {
|
|
8019
8297
|
assertDeviceMetadata(record3);
|
|
8020
8298
|
const storeDir = path3__default.dirname(this.filePath);
|
|
@@ -8433,15 +8711,7 @@ var AuthManager = class {
|
|
|
8433
8711
|
if (await this.opts.store.load() === void 0) return void 0;
|
|
8434
8712
|
throw new DeviceRecordRePairRequiredError();
|
|
8435
8713
|
}
|
|
8436
|
-
|
|
8437
|
-
deviceId: authority.deviceId,
|
|
8438
|
-
tenantId: authority.tenantId,
|
|
8439
|
-
devicePublicKey: authority.devicePublicKey
|
|
8440
|
-
};
|
|
8441
|
-
const current = await this.opts.store.load();
|
|
8442
|
-
if (current === void 0 || current.deviceId !== projection.deviceId || current.tenantId !== projection.tenantId || current.devicePublicKey !== projection.devicePublicKey) {
|
|
8443
|
-
await this.opts.store.save(projection);
|
|
8444
|
-
}
|
|
8714
|
+
await this.opts.store.reconcileMetadata(authority);
|
|
8445
8715
|
return Object.freeze({ ...authority });
|
|
8446
8716
|
}
|
|
8447
8717
|
};
|
|
@@ -8738,9 +9008,9 @@ async function probeDarwin(run) {
|
|
|
8738
9008
|
return DARWIN_UUID_RE.exec(result.stdout)?.[1];
|
|
8739
9009
|
}
|
|
8740
9010
|
async function probeLinux(readFile3) {
|
|
8741
|
-
for (const
|
|
9011
|
+
for (const path40 of LINUX_MACHINE_ID_PATHS) {
|
|
8742
9012
|
try {
|
|
8743
|
-
const contents = (await readFile3(
|
|
9013
|
+
const contents = (await readFile3(path40)).trim();
|
|
8744
9014
|
if (contents.length > 0) return contents;
|
|
8745
9015
|
} catch {
|
|
8746
9016
|
}
|
|
@@ -8772,7 +9042,7 @@ async function withTimeout2(probe, timeoutMs) {
|
|
|
8772
9042
|
async function resolveMachineId(options) {
|
|
8773
9043
|
const platform = options.platform ?? process.platform;
|
|
8774
9044
|
const run = options.run ?? runDeviceCommand;
|
|
8775
|
-
const readFile3 = options.readFile ?? ((
|
|
9045
|
+
const readFile3 = options.readFile ?? ((path40) => fs14.readFile(path40, "utf8"));
|
|
8776
9046
|
let probe;
|
|
8777
9047
|
if (platform === "darwin") probe = probeDarwin(run);
|
|
8778
9048
|
else if (platform === "linux") probe = probeLinux(readFile3);
|
|
@@ -10410,13 +10680,35 @@ var LongPollClient = class {
|
|
|
10410
10680
|
}
|
|
10411
10681
|
async loop(signal) {
|
|
10412
10682
|
let retryAttempt = 0;
|
|
10683
|
+
let readCursor;
|
|
10684
|
+
let readAheadSupported = false;
|
|
10685
|
+
let warnedWindowAt;
|
|
10413
10686
|
while (this.running && !signal.aborted) {
|
|
10414
10687
|
try {
|
|
10415
10688
|
await this.opts.auth.getValidAccessToken();
|
|
10416
10689
|
const base = toHttpBase(this.opts.serverUrl);
|
|
10417
10690
|
const url = new URL(BYOK_EVENTS_PATH, base);
|
|
10418
10691
|
const cursor = this.opts.getCursor();
|
|
10692
|
+
const ack = cursor ?? 0;
|
|
10693
|
+
if (readCursor !== void 0 && readCursor - ack >= MAILBOX_READ_AHEAD_MAX_SEQS) {
|
|
10694
|
+
if (warnedWindowAt !== ack) {
|
|
10695
|
+
warnedWindowAt = ack;
|
|
10696
|
+
console.warn("[byok/client] mailbox read-ahead window full; acknowledgement is stalled");
|
|
10697
|
+
}
|
|
10698
|
+
readCursor = ack;
|
|
10699
|
+
await sleep(this.opts.retryDelayMs ?? 2e3, signal);
|
|
10700
|
+
continue;
|
|
10701
|
+
}
|
|
10702
|
+
const afterSeq = Math.max(ack, readCursor ?? ack);
|
|
10419
10703
|
if (cursor !== void 0) url.searchParams.set("cursor", String(cursor));
|
|
10704
|
+
if (afterSeq > ack) {
|
|
10705
|
+
if (!readAheadSupported) {
|
|
10706
|
+
const error = new Error("server does not advertise mailbox-read-ahead; cannot read past unacknowledged work");
|
|
10707
|
+
this.warnRouteFailure(this.eventsEndpoint, void 0, error);
|
|
10708
|
+
throw error;
|
|
10709
|
+
}
|
|
10710
|
+
url.searchParams.set("afterSeq", String(afterSeq));
|
|
10711
|
+
}
|
|
10420
10712
|
let res;
|
|
10421
10713
|
try {
|
|
10422
10714
|
res = await authedFetch(url, { method: "GET", signal }, this.opts.auth);
|
|
@@ -10435,6 +10727,8 @@ var LongPollClient = class {
|
|
|
10435
10727
|
return;
|
|
10436
10728
|
}
|
|
10437
10729
|
this.warnRouteFailure(this.eventsEndpoint, res.status, void 0);
|
|
10730
|
+
readCursor = void 0;
|
|
10731
|
+
readAheadSupported = false;
|
|
10438
10732
|
this.opts.onServerCapabilitiesInvalidated?.();
|
|
10439
10733
|
this.opts.onPollFailure?.();
|
|
10440
10734
|
this.opts.onOperationalOutcome?.("failure");
|
|
@@ -10444,12 +10738,16 @@ var LongPollClient = class {
|
|
|
10444
10738
|
}
|
|
10445
10739
|
let parsed;
|
|
10446
10740
|
try {
|
|
10447
|
-
parsed = parseLooseEventsPollResponse(await res.json(),
|
|
10448
|
-
validateTrustedEventsPage(parsed.events,
|
|
10741
|
+
parsed = parseLooseEventsPollResponse(await res.json(), afterSeq);
|
|
10742
|
+
validateTrustedEventsPage(parsed.events, afterSeq, parsed.cursor);
|
|
10743
|
+
if (afterSeq > ack && !parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY)) throw new Error("mailbox read-ahead capability was withdrawn");
|
|
10449
10744
|
} catch (err) {
|
|
10450
10745
|
this.warnRouteFailure(this.eventsEndpoint, res.status, err);
|
|
10451
10746
|
throw err;
|
|
10452
10747
|
}
|
|
10748
|
+
readAheadSupported = parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY);
|
|
10749
|
+
const madeReadProgress = parsed.cursor > afterSeq;
|
|
10750
|
+
readCursor = parsed.cursor;
|
|
10453
10751
|
this.opts.onServerCapabilities?.(parsed.capabilities);
|
|
10454
10752
|
let hadValidationFailureThisBatch = false;
|
|
10455
10753
|
let acceptedAnyEntry = false;
|
|
@@ -10473,10 +10771,11 @@ var LongPollClient = class {
|
|
|
10473
10771
|
if (this.opts.onEnvelope(envelope) !== false) acceptedAnyEntry = true;
|
|
10474
10772
|
}
|
|
10475
10773
|
if (parsed.events.length === 0) {
|
|
10774
|
+
readCursor = this.opts.getCursor() ?? 0;
|
|
10476
10775
|
retryAttempt = 0;
|
|
10477
10776
|
this.opts.onOperationalOutcome?.("success");
|
|
10478
10777
|
await sleep(this.opts.idleDelayMs ?? 250, signal);
|
|
10479
|
-
} else if (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor) {
|
|
10778
|
+
} else if (!madeReadProgress && (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor)) {
|
|
10480
10779
|
this.opts.onOperationalOutcome?.("failure");
|
|
10481
10780
|
const baseMs = this.opts.retryDelayMs ?? 2e3;
|
|
10482
10781
|
await sleep(this.opts.retryDelayForAttempt?.(retryAttempt++, baseMs) ?? baseMs, signal);
|
|
@@ -10485,6 +10784,8 @@ var LongPollClient = class {
|
|
|
10485
10784
|
this.opts.onOperationalOutcome?.("success");
|
|
10486
10785
|
}
|
|
10487
10786
|
} catch (err) {
|
|
10787
|
+
readCursor = void 0;
|
|
10788
|
+
readAheadSupported = false;
|
|
10488
10789
|
this.opts.onServerCapabilitiesInvalidated?.();
|
|
10489
10790
|
if (this.noteRevoked(err)) return;
|
|
10490
10791
|
if (!this.running || signal.aborted) return;
|
|
@@ -10537,6 +10838,7 @@ function createConnectionHelloEnvelope(opts, getCursor) {
|
|
|
10537
10838
|
productId: opts.productId,
|
|
10538
10839
|
clientVersion: opts.clientVersion,
|
|
10539
10840
|
runtimes: opts.runtimes,
|
|
10841
|
+
harnesses: opts.harnesses,
|
|
10540
10842
|
configuredToolsets: configuredToolsets === void 0 ? void 0 : [...configuredToolsets],
|
|
10541
10843
|
cursor: getCursor()
|
|
10542
10844
|
});
|
|
@@ -10550,8 +10852,8 @@ var ConnectionManager = class {
|
|
|
10550
10852
|
serverUrl: opts.serverUrl,
|
|
10551
10853
|
auth: opts.auth,
|
|
10552
10854
|
// The long-poll query cursor is also the kernel's irreversible ack.
|
|
10553
|
-
// Only report the successfully
|
|
10554
|
-
//
|
|
10855
|
+
// Only report the successfully persisted cursor; using received work
|
|
10856
|
+
// on the wire would ack an
|
|
10555
10857
|
// in-flight envelope before its handler settles and make a later
|
|
10556
10858
|
// failure impossible to redeliver.
|
|
10557
10859
|
getCursor: () => this.cursor,
|
|
@@ -10600,25 +10902,11 @@ var ConnectionManager = class {
|
|
|
10600
10902
|
* guarantees in docs/protocol.md §9.
|
|
10601
10903
|
*/
|
|
10602
10904
|
stalledAtSeq;
|
|
10603
|
-
/**
|
|
10604
|
-
* Design A (Wave 2, F3-on-long-poll): the second, in-memory watermark
|
|
10605
|
-
* alongside the durable `cursor`. `cursor` only ever advances AFTER a
|
|
10606
|
-
* `task.*` handler's side effects resolve successfully, and is persisted
|
|
10607
|
-
* (see `advanceCursor`) — that semantics is unchanged. `deliveredSeq`
|
|
10608
|
-
* advances eagerly, the instant a `task.*` envelope is admitted past
|
|
10609
|
-
* dedup (see `deliver`/`noteDelivered`), independent of whether its
|
|
10610
|
-
* handler has even started, let alone succeeded. It exists so a repeated
|
|
10611
|
-
* read at the durable cursor does not re-dispatch an envelope already in
|
|
10612
|
-
* flight — `handleOffer` must not start a second adapter session while a
|
|
10613
|
-
* first attempt is still running. On WS this same field is written the
|
|
10614
|
-
* same way, but since a live WS connection only ever pushes a given `seq`
|
|
10615
|
-
* once, it never has an observable effect there beyond mirroring
|
|
10616
|
-
* `cursor` (see `dedupWatermark`'s doc comment for why redelivery
|
|
10617
|
-
* correctness doesn't depend on resetting it anywhere).
|
|
10618
|
-
*/
|
|
10619
|
-
deliveredSeq;
|
|
10620
|
-
/** Finding F3: serializes `onEnvelope` calls into a per-connection FIFO — one envelope's handler always fully settles before the next one starts. */
|
|
10905
|
+
/** Drain barrier for independently running handlers; not an admission lock. */
|
|
10621
10906
|
processingChain = Promise.resolve();
|
|
10907
|
+
cursorInitialization;
|
|
10908
|
+
completionTail = Promise.resolve();
|
|
10909
|
+
controlTails = /* @__PURE__ */ new Map();
|
|
10622
10910
|
/**
|
|
10623
10911
|
* Design B (finding N4): the ONE outbound queue holds `Envelope` OBJECTS,
|
|
10624
10912
|
* never re-encoded/rebuilt strings, so a
|
|
@@ -10644,38 +10932,13 @@ var ConnectionManager = class {
|
|
|
10644
10932
|
terminalError;
|
|
10645
10933
|
settledWaiters = [];
|
|
10646
10934
|
pendingCursorSave = Promise.resolve();
|
|
10647
|
-
/**
|
|
10648
|
-
* Finding P2 (Fix 2b): seqs currently admitted into `processingChain` but
|
|
10649
|
-
* not yet settled — added in `deliver()` the moment a `task.*` envelope is
|
|
10650
|
-
* accepted past the ordinary watermark check, removed in `process()`'s
|
|
10651
|
-
* `finally` once that specific attempt resolves (success OR failure).
|
|
10652
|
-
* While stalled, `dedupWatermark()` deliberately stays frozen below
|
|
10653
|
-
* already-delivered seqs (see its own doc comment) so the failed seq's own
|
|
10654
|
-
* redelivery can get through — but that same frozen watermark also means
|
|
10655
|
-
* every OTHER seq above it rides along on every re-poll too. Without this,
|
|
10656
|
-
* a seq already mid-flight (e.g. a `task.offer` whose prepared operation start()
|
|
10657
|
-
* hasn't resolved yet) would be re-enqueued into `processingChain` on
|
|
10658
|
-
* every such re-poll, piling up duplicate copies that — once the first
|
|
10659
|
-
* finally resolves and the chain unwinds through them — run its handler
|
|
10660
|
-
* again; for `task.offer` specifically, a second adapter session
|
|
10661
|
-
* orphaning the first (`TaskRunner`'s own `this.tasks.has` guard, finding
|
|
10662
|
-
* P2c, is the second, independent layer against exactly that).
|
|
10663
|
-
*/
|
|
10935
|
+
/** Admission dedup while an individual handler is running. */
|
|
10664
10936
|
inFlightSeqs = /* @__PURE__ */ new Set();
|
|
10665
|
-
/**
|
|
10666
|
-
* Finding P2 (Fix 2b): seqs whose handler has already resolved
|
|
10667
|
-
* successfully at least once this session, tracked only while a stall is
|
|
10668
|
-
* in effect — cleared the moment `stalledAtSeq` itself clears (see
|
|
10669
|
-
* `process()`), since once unstalled the ordinary watermark check via
|
|
10670
|
-
* `deliveredSeq` already covers everything delivered so far, making this
|
|
10671
|
-
* redundant. Needed because the stall-gap-prevention rule in `process()`
|
|
10672
|
-
* deliberately does NOT advance `cursor` past a seq above the
|
|
10673
|
-
* still-unresolved `stalledAtSeq`, even once that seq's own handler
|
|
10674
|
-
* succeeds — so `dedupWatermark()` alone can't distinguish "already
|
|
10675
|
-
* succeeded, don't re-run" from "never yet attempted" for anything in
|
|
10676
|
-
* that gap.
|
|
10677
|
-
*/
|
|
10937
|
+
/** Successful side effects retained until their durable acknowledgement. */
|
|
10678
10938
|
processedSeqs = /* @__PURE__ */ new Set();
|
|
10939
|
+
failedEnvelopes = /* @__PURE__ */ new Map();
|
|
10940
|
+
/** Received work lacking a successful handler receipt, including malformed frames. */
|
|
10941
|
+
unresolvedSeqs = /* @__PURE__ */ new Set();
|
|
10679
10942
|
/**
|
|
10680
10943
|
* Finding P3: the pending `drainOutbox` long-poll retry backoff, if any —
|
|
10681
10944
|
* cancellable so `enterRevoked()` can unblock it immediately instead of
|
|
@@ -10968,8 +11231,8 @@ var ConnectionManager = class {
|
|
|
10968
11231
|
* - F3 (at-most-once): the old code persisted the cursor advance BEFORE
|
|
10969
11232
|
* `onEnvelope` even ran (fire-and-forget) — a handler that then failed
|
|
10970
11233
|
* left a redelivery-proof envelope permanently marked processed. Inbound
|
|
10971
|
-
*
|
|
10972
|
-
*
|
|
11234
|
+
* handlers may run independently; their receipt commits are serialized.
|
|
11235
|
+
* The cursor only advances
|
|
10973
11236
|
* AFTER the handler resolves successfully; a rejection leaves the
|
|
10974
11237
|
* cursor where it was (see `stalledAtSeq`), so a future reconnect's
|
|
10975
11238
|
* redelivery re-attempts it — safe because every server->daemon type is
|
|
@@ -10985,62 +11248,72 @@ var ConnectionManager = class {
|
|
|
10985
11248
|
if (tracked && watermark !== void 0 && envelope.seq <= watermark) return false;
|
|
10986
11249
|
if (tracked) {
|
|
10987
11250
|
const seq = envelope.seq;
|
|
10988
|
-
if (this.inFlightSeqs.has(seq)
|
|
11251
|
+
if (this.inFlightSeqs.has(seq)) return false;
|
|
11252
|
+
if (!this.processedSeqs.has(seq)) this.unresolvedSeqs.add(seq);
|
|
10989
11253
|
this.inFlightSeqs.add(seq);
|
|
10990
|
-
this.noteDelivered(seq);
|
|
10991
11254
|
}
|
|
10992
|
-
|
|
11255
|
+
const controlKey = envelope.task_id !== void 0 && !envelope.type.startsWith("task.offer") ? envelope.task_id : void 0;
|
|
11256
|
+
const prior = controlKey === void 0 ? Promise.resolve() : this.controlTails.get(controlKey) ?? Promise.resolve();
|
|
11257
|
+
const operation = prior.then(() => this.process(envelope, tracked));
|
|
11258
|
+
if (controlKey !== void 0) {
|
|
11259
|
+
this.controlTails.set(controlKey, operation);
|
|
11260
|
+
void operation.finally(() => {
|
|
11261
|
+
if (this.controlTails.get(controlKey) === operation) this.controlTails.delete(controlKey);
|
|
11262
|
+
});
|
|
11263
|
+
}
|
|
11264
|
+
this.processingChain = Promise.all([this.processingChain, operation]).then(() => void 0);
|
|
10993
11265
|
return true;
|
|
10994
11266
|
}
|
|
10995
|
-
/**
|
|
10996
|
-
*
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
* reaches `advanceCursor` already passed through `noteDelivered` first,
|
|
11002
|
-
* see `deliver`) — so this is the literal `max(cursor, deliveredSeq)` the
|
|
11003
|
-
* design calls for, just expressed via that invariant rather than an
|
|
11004
|
-
* explicit `Math.max`.
|
|
11005
|
-
*
|
|
11006
|
-
* While `stalledAtSeq` is set, this collapses to the durable `cursor`
|
|
11007
|
-
* alone, deliberately ignoring however far `deliveredSeq` had already run
|
|
11008
|
-
* ahead before the failure was known: that's what lets the stalled
|
|
11009
|
-
* envelope's own redelivery (and everything after it, right up to a
|
|
11010
|
-
* fresh success) get past this same dedup check instead of being
|
|
11011
|
-
* self-deduped by the client's own earlier eager tracking of envelopes
|
|
11012
|
-
* whose outcome wasn't known yet. No separate "reset deliveredSeq on
|
|
11013
|
-
* reconnect" step is needed for this to be correct — collapsing to
|
|
11014
|
-
* `cursor` exactly while stalled already produces the right answer on
|
|
11015
|
-
* every long-poll retry path. NOT resetting it unconditionally on every
|
|
11016
|
-
* retry lets `deliveredSeq` keep doing its job of not re-dispatching
|
|
11017
|
-
* something already in flight while a handler is still running.
|
|
11018
|
-
*/
|
|
11019
|
-
dedupWatermark() {
|
|
11020
|
-
return this.stalledAtSeq !== void 0 ? this.cursor : this.deliveredSeq ?? this.cursor;
|
|
11267
|
+
/** A committed terminal can settle a failed offer receipt even if cloud
|
|
11268
|
+
* cancellation has since filtered that offer from mailbox replay. */
|
|
11269
|
+
retryTaskReceipts(taskId) {
|
|
11270
|
+
for (const envelope of this.failedEnvelopes.values()) {
|
|
11271
|
+
if (envelope.task_id === taskId) this.deliver(envelope);
|
|
11272
|
+
}
|
|
11021
11273
|
}
|
|
11022
|
-
/**
|
|
11023
|
-
|
|
11024
|
-
|
|
11274
|
+
/** Only a durable acknowledgement can deduplicate a whole prefix. */
|
|
11275
|
+
dedupWatermark() {
|
|
11276
|
+
return this.cursor;
|
|
11025
11277
|
}
|
|
11026
11278
|
async process(envelope, tracked) {
|
|
11027
11279
|
const seq = tracked ? envelope.seq : void 0;
|
|
11028
11280
|
try {
|
|
11281
|
+
if (tracked && seq <= (this.cursor ?? 0)) return;
|
|
11029
11282
|
if (tracked && this.cursor === void 0) {
|
|
11030
|
-
|
|
11031
|
-
|
|
11283
|
+
this.cursorInitialization ??= this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0).then(() => {
|
|
11284
|
+
this.cursor = 0;
|
|
11285
|
+
}).catch((error) => {
|
|
11286
|
+
this.cursorInitialization = void 0;
|
|
11287
|
+
throw error;
|
|
11288
|
+
});
|
|
11289
|
+
await this.cursorInitialization;
|
|
11032
11290
|
}
|
|
11033
|
-
await this.opts.onEnvelope(envelope);
|
|
11291
|
+
if (!tracked || !this.processedSeqs.has(seq)) await this.opts.onEnvelope(envelope);
|
|
11034
11292
|
if (!tracked) return;
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11293
|
+
this.failedEnvelopes.delete(seq);
|
|
11294
|
+
this.processedSeqs.add(seq);
|
|
11295
|
+
this.unresolvedSeqs.delete(seq);
|
|
11296
|
+
const completion = this.completionTail.catch(() => void 0).then(async () => {
|
|
11297
|
+
let firstUnresolved = Infinity;
|
|
11298
|
+
for (const value of this.unresolvedSeqs) firstUnresolved = Math.min(firstUnresolved, value);
|
|
11299
|
+
let prefix = this.cursor ?? 0;
|
|
11300
|
+
for (const value of this.processedSeqs) if (value < firstUnresolved) prefix = Math.max(prefix, value);
|
|
11301
|
+
if (prefix > (this.cursor ?? 0)) await this.advanceCursor(prefix);
|
|
11302
|
+
for (const value of this.processedSeqs) {
|
|
11303
|
+
if (value <= (this.cursor ?? 0)) {
|
|
11304
|
+
this.processedSeqs.delete(value);
|
|
11305
|
+
this.failedEnvelopes.delete(value);
|
|
11306
|
+
}
|
|
11307
|
+
}
|
|
11308
|
+
this.stalledAtSeq = this.unresolvedSeqs.size > 0 ? firstUnresolved : void 0;
|
|
11309
|
+
});
|
|
11310
|
+
this.completionTail = completion;
|
|
11311
|
+
await completion;
|
|
11042
11312
|
} catch (err) {
|
|
11043
|
-
if (tracked
|
|
11313
|
+
if (tracked) {
|
|
11314
|
+
this.failedEnvelopes.set(seq, envelope);
|
|
11315
|
+
this.stalledAtSeq = Math.min(envelope.seq, this.stalledAtSeq ?? Infinity);
|
|
11316
|
+
}
|
|
11044
11317
|
console.error(
|
|
11045
11318
|
`[byok/client] envelope handler failed for ${envelope.type}${typeof envelope.seq === "number" ? ` (seq=${envelope.seq})` : ""}; cursor left unadvanced so a reconnect redelivers it:`,
|
|
11046
11319
|
err
|
|
@@ -11049,11 +11322,10 @@ var ConnectionManager = class {
|
|
|
11049
11322
|
if (tracked) this.inFlightSeqs.delete(seq);
|
|
11050
11323
|
}
|
|
11051
11324
|
}
|
|
11052
|
-
/**
|
|
11325
|
+
/** Recorded synchronously at receive, before a later successful receipt can commit. */
|
|
11053
11326
|
noteValidationFailure(seq) {
|
|
11054
|
-
this.
|
|
11055
|
-
|
|
11056
|
-
});
|
|
11327
|
+
this.unresolvedSeqs.add(seq);
|
|
11328
|
+
this.stalledAtSeq = Math.min(seq, this.stalledAtSeq ?? seq);
|
|
11057
11329
|
}
|
|
11058
11330
|
async advanceCursor(seq) {
|
|
11059
11331
|
if (this.cursor !== void 0 && seq <= this.cursor) return;
|
|
@@ -11288,6 +11560,29 @@ var OperationalHealthTracker = class {
|
|
|
11288
11560
|
}
|
|
11289
11561
|
}
|
|
11290
11562
|
};
|
|
11563
|
+
async function inspectOperationalHealthHandle(handle, expected) {
|
|
11564
|
+
const read = await readOperationalHealthHandle(handle, expected);
|
|
11565
|
+
if (read.status !== "read") return read;
|
|
11566
|
+
const { raw, sizeBytes } = read;
|
|
11567
|
+
let parsed;
|
|
11568
|
+
try {
|
|
11569
|
+
parsed = JSON.parse(raw);
|
|
11570
|
+
} catch {
|
|
11571
|
+
return { status: "corrupt", sizeBytes, reason: "operational health state is corrupt JSON" };
|
|
11572
|
+
}
|
|
11573
|
+
if (!isHealthFile(parsed)) {
|
|
11574
|
+
return { status: "corrupt", sizeBytes, reason: "operational health state has an invalid shape" };
|
|
11575
|
+
}
|
|
11576
|
+
return {
|
|
11577
|
+
status: "valid",
|
|
11578
|
+
sizeBytes,
|
|
11579
|
+
state: parsed.state,
|
|
11580
|
+
failureCount: parsed.failures.length,
|
|
11581
|
+
crashCount: parsed.crashes.length,
|
|
11582
|
+
...parsed.currentRun ? { currentRunStartedAt: new Date(Date.parse(parsed.currentRun.startedAt)).toISOString() } : {},
|
|
11583
|
+
...parsed.crashes.at(-1) ? { lastCrashAt: new Date(Date.parse(parsed.crashes.at(-1).detectedAt)).toISOString() } : {}
|
|
11584
|
+
};
|
|
11585
|
+
}
|
|
11291
11586
|
async function readOperationalHealthHandle(handle, expected) {
|
|
11292
11587
|
let before;
|
|
11293
11588
|
try {
|
|
@@ -11321,6 +11616,20 @@ async function readOperationalHealthHandle(handle, expected) {
|
|
|
11321
11616
|
return { status: "unavailable", sizeBytes, reason: "operational health state could not be read" };
|
|
11322
11617
|
}
|
|
11323
11618
|
}
|
|
11619
|
+
async function inspectOperationalHealthFile(storeDir) {
|
|
11620
|
+
let opened;
|
|
11621
|
+
try {
|
|
11622
|
+
opened = await openOperationalHealthFile(storeDir);
|
|
11623
|
+
} catch {
|
|
11624
|
+
return { status: "unavailable", reason: "operational health state could not be opened safely" };
|
|
11625
|
+
}
|
|
11626
|
+
if (!opened) return { status: "missing" };
|
|
11627
|
+
try {
|
|
11628
|
+
return await inspectOperationalHealthHandle(opened.handle, opened.stat);
|
|
11629
|
+
} finally {
|
|
11630
|
+
await opened.handle.close();
|
|
11631
|
+
}
|
|
11632
|
+
}
|
|
11324
11633
|
function isHealthFile(value) {
|
|
11325
11634
|
if (typeof value !== "object" || value === null) return false;
|
|
11326
11635
|
const candidate = value;
|
|
@@ -11386,7 +11695,7 @@ var CursorStore = class {
|
|
|
11386
11695
|
async save(serverUrl, deviceId, cursor) {
|
|
11387
11696
|
const file = this.fileFor(serverUrl, deviceId);
|
|
11388
11697
|
await promises.mkdir(path3__default.dirname(file), { recursive: true, mode: 448 });
|
|
11389
|
-
await atomicWriteFile(file, JSON.stringify({ cursor }));
|
|
11698
|
+
await atomicWriteFile(file, JSON.stringify({ cursor }), { fsync: true });
|
|
11390
11699
|
}
|
|
11391
11700
|
/** Remove any persisted cursor for (serverUrl, deviceId) — a no-op if none exists. Called from `pair()` (finding F5) so a device that's about to be replaced never leaves a cursor a future, unrelated device could somehow inherit. */
|
|
11392
11701
|
async clear(serverUrl, deviceId) {
|
|
@@ -11879,9 +12188,9 @@ function isSqliteAvailable() {
|
|
|
11879
12188
|
}
|
|
11880
12189
|
}
|
|
11881
12190
|
var SECURE_FILE_MODE = 384;
|
|
11882
|
-
function openJournalDatabase(
|
|
12191
|
+
function openJournalDatabase(path40, busyTimeoutMs, faults) {
|
|
11883
12192
|
const { DatabaseSync } = loadSqliteModule();
|
|
11884
|
-
const db = new DatabaseSync(
|
|
12193
|
+
const db = new DatabaseSync(path40, { timeout: busyTimeoutMs });
|
|
11885
12194
|
try {
|
|
11886
12195
|
faults?.onStep?.("after-open");
|
|
11887
12196
|
db.exec("PRAGMA auto_vacuum = INCREMENTAL;");
|
|
@@ -12025,9 +12334,9 @@ var RECEIVED_STATE = "received";
|
|
|
12025
12334
|
function byteLength2(value) {
|
|
12026
12335
|
return Buffer.byteLength(value, "utf8");
|
|
12027
12336
|
}
|
|
12028
|
-
function fileBytes(
|
|
12337
|
+
function fileBytes(path40) {
|
|
12029
12338
|
try {
|
|
12030
|
-
return statSync(
|
|
12339
|
+
return statSync(path40).size;
|
|
12031
12340
|
} catch {
|
|
12032
12341
|
return 0;
|
|
12033
12342
|
}
|
|
@@ -13370,6 +13679,109 @@ async function spillOversizedEvent(event, deps) {
|
|
|
13370
13679
|
return bounded2.result;
|
|
13371
13680
|
}
|
|
13372
13681
|
|
|
13682
|
+
// src/daemon/admission-wait.ts
|
|
13683
|
+
async function awaitAdmission(operation, signal) {
|
|
13684
|
+
signal.throwIfAborted();
|
|
13685
|
+
let onAbort;
|
|
13686
|
+
const aborted = new Promise((_, reject) => {
|
|
13687
|
+
onAbort = () => reject(signal.reason);
|
|
13688
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13689
|
+
});
|
|
13690
|
+
try {
|
|
13691
|
+
return await Promise.race([Promise.resolve().then(() => {
|
|
13692
|
+
signal.throwIfAborted();
|
|
13693
|
+
return operation();
|
|
13694
|
+
}), aborted]);
|
|
13695
|
+
} finally {
|
|
13696
|
+
signal.removeEventListener("abort", onAbort);
|
|
13697
|
+
}
|
|
13698
|
+
}
|
|
13699
|
+
|
|
13700
|
+
// src/daemon/runtime-start.ts
|
|
13701
|
+
async function startOwnedRuntime(start, input, signal, timeoutMs) {
|
|
13702
|
+
const controller = new AbortController();
|
|
13703
|
+
let session;
|
|
13704
|
+
let failure;
|
|
13705
|
+
let settled = false;
|
|
13706
|
+
const retryDisposal = async () => {
|
|
13707
|
+
if (session) {
|
|
13708
|
+
let timer2;
|
|
13709
|
+
try {
|
|
13710
|
+
await Promise.race([
|
|
13711
|
+
Promise.resolve().then(() => session.interrupt()).catch(() => void 0),
|
|
13712
|
+
new Promise((resolve) => {
|
|
13713
|
+
timer2 = setTimeout(resolve, 1e3);
|
|
13714
|
+
timer2.unref?.();
|
|
13715
|
+
})
|
|
13716
|
+
]);
|
|
13717
|
+
} finally {
|
|
13718
|
+
if (timer2) clearTimeout(timer2);
|
|
13719
|
+
}
|
|
13720
|
+
return session.close();
|
|
13721
|
+
}
|
|
13722
|
+
if (isRuntimeStartupDisposalFailure(failure)) return failure.retryDisposal();
|
|
13723
|
+
if (settled && isRuntimeExecutionFailure(failure)) return;
|
|
13724
|
+
throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup has not returned a cleanup receipt" });
|
|
13725
|
+
};
|
|
13726
|
+
let rejectAbort;
|
|
13727
|
+
const aborted = new Promise((_, reject) => {
|
|
13728
|
+
rejectAbort = reject;
|
|
13729
|
+
});
|
|
13730
|
+
const abort = () => {
|
|
13731
|
+
controller.abort();
|
|
13732
|
+
rejectAbort(new RuntimeStartupDisposalFailure(retryDisposal));
|
|
13733
|
+
};
|
|
13734
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
13735
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
13736
|
+
timer.unref?.();
|
|
13737
|
+
try {
|
|
13738
|
+
if (signal.aborted) {
|
|
13739
|
+
throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup was cancelled before invocation" });
|
|
13740
|
+
}
|
|
13741
|
+
const startup = Promise.resolve().then(() => start({ ...input, signal: controller.signal })).then((value) => {
|
|
13742
|
+
session = value;
|
|
13743
|
+
settled = true;
|
|
13744
|
+
return value;
|
|
13745
|
+
}, (error) => {
|
|
13746
|
+
failure = error;
|
|
13747
|
+
settled = true;
|
|
13748
|
+
throw error;
|
|
13749
|
+
});
|
|
13750
|
+
return await Promise.race([startup, aborted]);
|
|
13751
|
+
} finally {
|
|
13752
|
+
clearTimeout(timer);
|
|
13753
|
+
signal.removeEventListener("abort", abort);
|
|
13754
|
+
}
|
|
13755
|
+
}
|
|
13756
|
+
|
|
13757
|
+
// src/daemon/artifact-read.ts
|
|
13758
|
+
var DEFAULT_ARTIFACT_LIMITS = Object.freeze({
|
|
13759
|
+
maxFileBytes: 16 * 1024 * 1024,
|
|
13760
|
+
maxTaskBytes: 64 * 1024 * 1024
|
|
13761
|
+
});
|
|
13762
|
+
async function readArtifactBytes(handle, maxBytes, signal, account) {
|
|
13763
|
+
signal.throwIfAborted();
|
|
13764
|
+
const stat = await handle.stat();
|
|
13765
|
+
if (stat.size > maxBytes) throw new Error("artifact local byte budget exceeded");
|
|
13766
|
+
const chunks = [];
|
|
13767
|
+
let total = 0;
|
|
13768
|
+
const stream = handle.createReadStream({ autoClose: false, start: 0, signal, highWaterMark: 64 * 1024 });
|
|
13769
|
+
try {
|
|
13770
|
+
for await (const data of stream) {
|
|
13771
|
+
signal.throwIfAborted();
|
|
13772
|
+
const chunk = data;
|
|
13773
|
+
account(chunk.length);
|
|
13774
|
+
total += chunk.length;
|
|
13775
|
+
if (total > maxBytes) throw new Error("artifact local byte budget exceeded");
|
|
13776
|
+
chunks.push(chunk);
|
|
13777
|
+
}
|
|
13778
|
+
signal.throwIfAborted();
|
|
13779
|
+
return Buffer.concat(chunks, total);
|
|
13780
|
+
} finally {
|
|
13781
|
+
stream.destroy();
|
|
13782
|
+
}
|
|
13783
|
+
}
|
|
13784
|
+
|
|
13373
13785
|
// src/daemon/environment.ts
|
|
13374
13786
|
var BASE_PLATFORM_ALLOWLIST = [
|
|
13375
13787
|
"PATH",
|
|
@@ -13569,6 +13981,78 @@ var ProgressBatcher = class {
|
|
|
13569
13981
|
}
|
|
13570
13982
|
}
|
|
13571
13983
|
};
|
|
13984
|
+
var DurableJsonlFile = class {
|
|
13985
|
+
constructor(filePath) {
|
|
13986
|
+
this.filePath = filePath;
|
|
13987
|
+
}
|
|
13988
|
+
filePath;
|
|
13989
|
+
failure;
|
|
13990
|
+
assertWritable() {
|
|
13991
|
+
if (this.failure !== void 0) {
|
|
13992
|
+
throw new Error("durable JSONL writer is quarantined after uncertain I/O; reopen and validate the log before retrying", { cause: this.failure });
|
|
13993
|
+
}
|
|
13994
|
+
}
|
|
13995
|
+
/** Replay is not a durability receipt: confirm the recovered bytes before use. */
|
|
13996
|
+
async confirmRecovered() {
|
|
13997
|
+
let handle;
|
|
13998
|
+
try {
|
|
13999
|
+
handle = await promises.open(this.filePath, "r+");
|
|
14000
|
+
} catch (error) {
|
|
14001
|
+
if (error.code === "ENOENT") return;
|
|
14002
|
+
throw error;
|
|
14003
|
+
}
|
|
14004
|
+
try {
|
|
14005
|
+
await handle.sync();
|
|
14006
|
+
} finally {
|
|
14007
|
+
await handle.close();
|
|
14008
|
+
}
|
|
14009
|
+
await this.syncDirectory();
|
|
14010
|
+
}
|
|
14011
|
+
async append(json) {
|
|
14012
|
+
this.assertWritable();
|
|
14013
|
+
try {
|
|
14014
|
+
const handle = await promises.open(this.filePath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
|
|
14015
|
+
try {
|
|
14016
|
+
const bytes = Buffer.from(`${json}
|
|
14017
|
+
`, "utf8");
|
|
14018
|
+
let offset = 0;
|
|
14019
|
+
while (offset < bytes.length) {
|
|
14020
|
+
const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset);
|
|
14021
|
+
if (bytesWritten <= 0) throw new Error("durable JSONL append made no progress");
|
|
14022
|
+
offset += bytesWritten;
|
|
14023
|
+
}
|
|
14024
|
+
await handle.sync();
|
|
14025
|
+
} finally {
|
|
14026
|
+
await handle.close();
|
|
14027
|
+
}
|
|
14028
|
+
await this.syncDirectory();
|
|
14029
|
+
} catch (error) {
|
|
14030
|
+
this.failure = error;
|
|
14031
|
+
throw error;
|
|
14032
|
+
}
|
|
14033
|
+
}
|
|
14034
|
+
async replace(lines) {
|
|
14035
|
+
this.assertWritable();
|
|
14036
|
+
try {
|
|
14037
|
+
await atomicWriteFile(this.filePath, lines.map((line) => `${line}
|
|
14038
|
+
`).join(""), { mode: 384, fsync: true });
|
|
14039
|
+
} catch (error) {
|
|
14040
|
+
this.failure = error;
|
|
14041
|
+
throw error;
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
async syncDirectory() {
|
|
14045
|
+
if (process.platform === "win32") return;
|
|
14046
|
+
const directory = await promises.open(path3__default.dirname(this.filePath), "r");
|
|
14047
|
+
try {
|
|
14048
|
+
await directory.sync();
|
|
14049
|
+
} finally {
|
|
14050
|
+
await directory.close();
|
|
14051
|
+
}
|
|
14052
|
+
}
|
|
14053
|
+
};
|
|
14054
|
+
|
|
14055
|
+
// src/daemon/agent-message-outbox.ts
|
|
13572
14056
|
var AGENT_MESSAGE_DIRECTORY = path3__default.join(".byok", "messages");
|
|
13573
14057
|
var AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
|
|
13574
14058
|
var AgentMessageOutboxError = class extends Error {
|
|
@@ -13595,11 +14079,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13595
14079
|
constructor(homeDir, outboxPath) {
|
|
13596
14080
|
this.homeDir = homeDir;
|
|
13597
14081
|
this.outboxPath = outboxPath;
|
|
14082
|
+
this.file = new DurableJsonlFile(outboxPath);
|
|
13598
14083
|
}
|
|
13599
14084
|
homeDir;
|
|
13600
14085
|
outboxPath;
|
|
13601
14086
|
pendingByTask = /* @__PURE__ */ new Map();
|
|
14087
|
+
revokedTasks = /* @__PURE__ */ new Set();
|
|
13602
14088
|
dispositionByTask = /* @__PURE__ */ new Map();
|
|
14089
|
+
file;
|
|
13603
14090
|
nextCursor = 1;
|
|
13604
14091
|
logEntries = 0;
|
|
13605
14092
|
writeTail = Promise.resolve();
|
|
@@ -13608,6 +14095,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13608
14095
|
await ensureSecureDir(directory);
|
|
13609
14096
|
const outbox = new _AgentMessageOutbox(homeDir, path3__default.join(directory, AGENT_MESSAGE_OUTBOX_FILENAME));
|
|
13610
14097
|
await outbox.load();
|
|
14098
|
+
await outbox.file.confirmRecovered();
|
|
13611
14099
|
return outbox;
|
|
13612
14100
|
}
|
|
13613
14101
|
/** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
|
|
@@ -13648,9 +14136,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13648
14136
|
records() {
|
|
13649
14137
|
return Object.freeze([...this.pendingByTask.values()].sort((a, b) => a.cursor - b.cursor));
|
|
13650
14138
|
}
|
|
13651
|
-
/**
|
|
14139
|
+
/** Records without disposition or local revoke; transport replay additionally requires session binding. */
|
|
13652
14140
|
retryableRecords() {
|
|
13653
|
-
return Object.freeze(this.records().filter((record3) => !this.dispositionByTask.has(record3.taskId)));
|
|
14141
|
+
return Object.freeze(this.records().filter((record3) => !this.dispositionByTask.has(record3.taskId) && !this.revokedTasks.has(record3.taskId)));
|
|
13654
14142
|
}
|
|
13655
14143
|
get(taskId) {
|
|
13656
14144
|
return this.pendingByTask.get(taskId);
|
|
@@ -13662,13 +14150,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13662
14150
|
if (byteCount < 1 || byteCount > input.requirement.maxBytes) throw new AgentMessageOutboxError("message body exceeds the offer byte contract");
|
|
13663
14151
|
const existing = this.pendingByTask.get(input.taskId);
|
|
13664
14152
|
if (existing !== void 0) {
|
|
13665
|
-
if (existing.tenantId !== input.tenantId || existing.contentType !== input.contentType || existing.body !== input.body || existing.contract !== input.requirement.contract || !sameAgentRef(existing.agentRef, input.agentRef)) {
|
|
14153
|
+
if (existing.tenantId !== input.tenantId || existing.contentType !== input.contentType || existing.body !== input.body || existing.contract !== input.requirement.contract || input.sessionRef !== void 0 && existing.sessionRef !== input.sessionRef || !sameAgentRef(existing.agentRef, input.agentRef)) {
|
|
13666
14154
|
throw new AgentMessageOutboxError("required-message task already has a different immutable draft");
|
|
13667
14155
|
}
|
|
13668
14156
|
return existing;
|
|
13669
14157
|
}
|
|
13670
|
-
|
|
13671
|
-
|
|
14158
|
+
const sending = this.records().filter((record4) => !this.isTerminalEvidence(record4.taskId));
|
|
14159
|
+
if (sending.length >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
|
|
14160
|
+
const pendingBytes = sending.reduce((sum, record4) => sum + record4.byteCount, 0);
|
|
13672
14161
|
if (pendingBytes + byteCount > input.maxPendingBytes) throw new AgentMessageOutboxError("message outbox byte quota is exhausted");
|
|
13673
14162
|
const record3 = Object.freeze({
|
|
13674
14163
|
schema: 1,
|
|
@@ -13690,8 +14179,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13690
14179
|
return record3;
|
|
13691
14180
|
});
|
|
13692
14181
|
}
|
|
13693
|
-
async activate(taskId, sessionRef) {
|
|
14182
|
+
async activate(taskId, sessionRef, isCurrent = () => true) {
|
|
13694
14183
|
return this.exclusive(async () => {
|
|
14184
|
+
if (!isCurrent() || this.revokedTasks.has(taskId)) return void 0;
|
|
13695
14185
|
const record3 = this.pendingByTask.get(taskId);
|
|
13696
14186
|
if (record3 === void 0) return void 0;
|
|
13697
14187
|
if (record3.sessionRef !== void 0) {
|
|
@@ -13704,6 +14194,18 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13704
14194
|
return activated;
|
|
13705
14195
|
});
|
|
13706
14196
|
}
|
|
14197
|
+
/** Local lifecycle cancellation, never a synthesized consumer disposition. */
|
|
14198
|
+
async revoke(taskId) {
|
|
14199
|
+
return this.exclusive(async () => {
|
|
14200
|
+
const record3 = this.pendingByTask.get(taskId);
|
|
14201
|
+
if (record3 === void 0 || this.revokedTasks.has(taskId)) return;
|
|
14202
|
+
await this.appendEntry({ schema: 1, kind: "revoke", taskId, messageId: record3.messageId });
|
|
14203
|
+
this.revokedTasks.add(taskId);
|
|
14204
|
+
});
|
|
14205
|
+
}
|
|
14206
|
+
isTerminalEvidence(taskId) {
|
|
14207
|
+
return this.revokedTasks.has(taskId) || this.dispositionByTask.get(taskId)?.outcome === "refused";
|
|
14208
|
+
}
|
|
13707
14209
|
publishPayload(record3) {
|
|
13708
14210
|
if (record3.sessionRef === void 0) throw new AgentMessageOutboxError("message draft is not bound to a durable session");
|
|
13709
14211
|
return AgentMessagePublishPayloadSchema.parse({
|
|
@@ -13727,6 +14229,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13727
14229
|
await this.appendEntry({ schema: 1, kind: "disposition", taskId, disposition });
|
|
13728
14230
|
if (disposition.outcome === "accepted") {
|
|
13729
14231
|
this.pendingByTask.delete(taskId);
|
|
14232
|
+
this.revokedTasks.delete(taskId);
|
|
13730
14233
|
this.dispositionByTask.delete(taskId);
|
|
13731
14234
|
} else {
|
|
13732
14235
|
this.dispositionByTask.set(taskId, disposition);
|
|
@@ -13745,6 +14248,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13745
14248
|
if (error.code === "ENOENT") return;
|
|
13746
14249
|
throw error;
|
|
13747
14250
|
}
|
|
14251
|
+
if (raw.length > 0 && !raw.endsWith("\n")) throw new AgentMessageOutboxError("durable JSONL log has an incomplete trailing frame; preserve the file for explicit repair");
|
|
13748
14252
|
for (const line of raw.split("\n")) {
|
|
13749
14253
|
if (line.length === 0) continue;
|
|
13750
14254
|
const entry = JSON.parse(line);
|
|
@@ -13759,11 +14263,16 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13759
14263
|
if (record3 === void 0 || record3.messageId !== entry.messageId) throw new AgentMessageOutboxError("message activation has no exact draft");
|
|
13760
14264
|
if (record3.sessionRef !== void 0 && record3.sessionRef !== entry.sessionRef) throw new AgentMessageOutboxError("message activation session conflicts");
|
|
13761
14265
|
this.pendingByTask.set(entry.taskId, Object.freeze({ ...record3, sessionRef: entry.sessionRef }));
|
|
14266
|
+
} else if (entry.kind === "revoke") {
|
|
14267
|
+
const record3 = this.pendingByTask.get(entry.taskId);
|
|
14268
|
+
if (record3 === void 0 || record3.messageId !== entry.messageId) throw new AgentMessageOutboxError("message revocation has no exact draft");
|
|
14269
|
+
this.revokedTasks.add(entry.taskId);
|
|
13762
14270
|
} else if (entry.kind === "disposition") {
|
|
13763
14271
|
const record3 = this.pendingByTask.get(entry.taskId);
|
|
13764
14272
|
if (record3 === void 0 || !exactDisposition(record3, entry.disposition)) throw new AgentMessageOutboxError("message outbox contains a mismatched disposition");
|
|
13765
14273
|
if (entry.disposition.outcome === "accepted") {
|
|
13766
14274
|
this.pendingByTask.delete(entry.taskId);
|
|
14275
|
+
this.revokedTasks.delete(entry.taskId);
|
|
13767
14276
|
this.dispositionByTask.delete(entry.taskId);
|
|
13768
14277
|
} else {
|
|
13769
14278
|
this.dispositionByTask.set(entry.taskId, entry.disposition);
|
|
@@ -13774,23 +14283,47 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13774
14283
|
}
|
|
13775
14284
|
}
|
|
13776
14285
|
async appendEntry(entry) {
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
await handle.write(`${stableJson(entry)}
|
|
13780
|
-
`, void 0, "utf8");
|
|
13781
|
-
await handle.sync();
|
|
13782
|
-
this.logEntries += 1;
|
|
13783
|
-
} finally {
|
|
13784
|
-
await handle.close();
|
|
13785
|
-
}
|
|
14286
|
+
await this.file.append(stableJson(entry));
|
|
14287
|
+
this.logEntries += 1;
|
|
13786
14288
|
}
|
|
13787
|
-
|
|
13788
|
-
|
|
14289
|
+
/**
|
|
14290
|
+
* Explicit operator maintenance under the Agent-home single-writer lease.
|
|
14291
|
+
* Copy complete terminal evidence durably before removing it from the live log.
|
|
14292
|
+
* Archives are audit artifacts, never another replay input. held remains live.
|
|
14293
|
+
*/
|
|
14294
|
+
async archiveTerminalRecords(archiveDirectory) {
|
|
14295
|
+
return this.exclusive(async () => {
|
|
14296
|
+
if (!path3__default.isAbsolute(archiveDirectory)) throw new AgentMessageOutboxError("message archive directory must be absolute");
|
|
14297
|
+
const terminal = this.records().filter((record3) => this.isTerminalEvidence(record3.taskId));
|
|
14298
|
+
if (terminal.length === 0) return void 0;
|
|
14299
|
+
await ensureSecureDir(archiveDirectory);
|
|
14300
|
+
const archivePath = path3__default.join(archiveDirectory, `message-terminal-${randomUUID()}.jsonl`);
|
|
14301
|
+
await atomicWriteFile(archivePath, this.snapshotEntries(terminal).map((entry) => `${stableJson(entry)}
|
|
14302
|
+
`).join(""), { mode: 384, fsync: true });
|
|
14303
|
+
const retained = this.records().filter((record3) => !this.isTerminalEvidence(record3.taskId));
|
|
14304
|
+
const entries = this.snapshotEntries(retained);
|
|
14305
|
+
await this.file.replace(entries.map((entry) => stableJson(entry)));
|
|
14306
|
+
for (const record3 of terminal) {
|
|
14307
|
+
this.pendingByTask.delete(record3.taskId);
|
|
14308
|
+
this.dispositionByTask.delete(record3.taskId);
|
|
14309
|
+
this.revokedTasks.delete(record3.taskId);
|
|
14310
|
+
}
|
|
14311
|
+
this.logEntries = entries.length;
|
|
14312
|
+
return archivePath;
|
|
14313
|
+
});
|
|
14314
|
+
}
|
|
14315
|
+
snapshotEntries(records) {
|
|
14316
|
+
return records.flatMap((record3) => {
|
|
13789
14317
|
const append = { schema: 1, kind: "append", record: record3 };
|
|
13790
14318
|
const disposition = this.dispositionByTask.get(record3.taskId);
|
|
13791
|
-
|
|
14319
|
+
const entries = disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record3.taskId, disposition }];
|
|
14320
|
+
if (this.revokedTasks.has(record3.taskId)) entries.push({ schema: 1, kind: "revoke", taskId: record3.taskId, messageId: record3.messageId });
|
|
14321
|
+
return entries;
|
|
13792
14322
|
});
|
|
13793
|
-
|
|
14323
|
+
}
|
|
14324
|
+
async compact() {
|
|
14325
|
+
const entries = this.snapshotEntries(this.records());
|
|
14326
|
+
await this.file.replace(entries.map((entry) => stableJson(entry)));
|
|
13794
14327
|
this.logEntries = entries.length;
|
|
13795
14328
|
}
|
|
13796
14329
|
async exclusive(fn) {
|
|
@@ -13801,6 +14334,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
|
13801
14334
|
});
|
|
13802
14335
|
await prior;
|
|
13803
14336
|
try {
|
|
14337
|
+
this.file.assertWritable();
|
|
13804
14338
|
return await fn();
|
|
13805
14339
|
} finally {
|
|
13806
14340
|
release();
|
|
@@ -14981,7 +15515,7 @@ var TaskRunner = class {
|
|
|
14981
15515
|
*/
|
|
14982
15516
|
stoppingOffers = false;
|
|
14983
15517
|
get activeTaskCount() {
|
|
14984
|
-
return this.tasks.size;
|
|
15518
|
+
return this.tasks.size + this.startupOwners.size;
|
|
14985
15519
|
}
|
|
14986
15520
|
/**
|
|
14987
15521
|
* Transport-boundary classification for the currently active task. Legacy
|
|
@@ -15027,6 +15561,12 @@ var TaskRunner = class {
|
|
|
15027
15561
|
const active = this.tasks.get(taskId);
|
|
15028
15562
|
const context = pending ?? (active?.messageRequirement && active.agentRef && active.messageOutbox ? { agentRef: active.agentRef, requirement: active.messageRequirement, outbox: active.messageOutbox } : void 0);
|
|
15029
15563
|
if (context === void 0) throw new Error("task has no active required Agent message contract");
|
|
15564
|
+
const isCurrent = () => {
|
|
15565
|
+
if (this.messageContextByToken.get(input.contextToken) !== taskId || this.messageContextByTask.get(taskId) !== input.contextToken || this.pendingCancelled.has(taskId)) return false;
|
|
15566
|
+
const current2 = this.tasks.get(taskId);
|
|
15567
|
+
return current2 === void 0 ? active === void 0 && this.pendingMessageTasks.get(taskId) === pending : (active === void 0 || current2 === active) && current2.messageOutbox === context.outbox && this.canPublishAgentMessage(current2);
|
|
15568
|
+
};
|
|
15569
|
+
if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
|
|
15030
15570
|
const record3 = await context.outbox.appendDraft({
|
|
15031
15571
|
taskId,
|
|
15032
15572
|
tenantId: this.deps.tenantId,
|
|
@@ -15037,8 +15577,17 @@ var TaskRunner = class {
|
|
|
15037
15577
|
maxPendingEvents: 64,
|
|
15038
15578
|
maxPendingBytes: 4 * 1024 * 1024
|
|
15039
15579
|
});
|
|
15040
|
-
if (
|
|
15041
|
-
const
|
|
15580
|
+
if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
|
|
15581
|
+
const current = this.tasks.get(taskId);
|
|
15582
|
+
if (current === void 0) return { messageId: record3.messageId, state: "staged" };
|
|
15583
|
+
const activated = await this.activateAgentMessage(
|
|
15584
|
+
context.outbox,
|
|
15585
|
+
taskId,
|
|
15586
|
+
current.session.sessionRef,
|
|
15587
|
+
isCurrent,
|
|
15588
|
+
() => current.messageSendAttempted === true
|
|
15589
|
+
);
|
|
15590
|
+
if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
|
|
15042
15591
|
if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
|
|
15043
15592
|
this.sendAgentMessageRecord(context.outbox, activated);
|
|
15044
15593
|
return { messageId: activated.messageId, state: "pending" };
|
|
@@ -15074,19 +15623,39 @@ var TaskRunner = class {
|
|
|
15074
15623
|
});
|
|
15075
15624
|
return opened;
|
|
15076
15625
|
}
|
|
15626
|
+
/** A recovery terminal must not close first-message admission before this durable draft has a disposition. */
|
|
15627
|
+
hasPendingRecoveredAgentMessage(taskId) {
|
|
15628
|
+
return this.recoveredMessageOutboxes.get(taskId)?.retryableRecords().some((record3) => record3.taskId === taskId && record3.sessionRef !== void 0) === true;
|
|
15629
|
+
}
|
|
15077
15630
|
/** Retry stable recovered records after a transport handshake/re-handshake. */
|
|
15078
15631
|
retryRecoveredAgentMessages() {
|
|
15079
15632
|
for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
|
|
15080
15633
|
const record3 = outbox.get(taskId);
|
|
15081
|
-
if (record3?.sessionRef !== void 0) this.sendAgentMessageRecord(outbox, record3);
|
|
15634
|
+
if (record3?.sessionRef !== void 0 && this.hasPendingRecoveredAgentMessage(taskId)) this.sendAgentMessageRecord(outbox, record3);
|
|
15082
15635
|
}
|
|
15083
15636
|
}
|
|
15637
|
+
canPublishAgentMessage(active) {
|
|
15638
|
+
return this.tasks.get(active.taskId) === active && !active.beingTornDown;
|
|
15639
|
+
}
|
|
15640
|
+
/** All authors and startup use the same queue-time and post-I/O authority check. */
|
|
15641
|
+
async activateAgentMessage(outbox, taskId, sessionRef, isCurrent, wasSendAttempted = () => false) {
|
|
15642
|
+
if (!isCurrent()) return void 0;
|
|
15643
|
+
const record3 = await outbox.activate(taskId, sessionRef, isCurrent);
|
|
15644
|
+
if (!isCurrent()) {
|
|
15645
|
+
if (!wasSendAttempted()) await outbox.revoke(taskId);
|
|
15646
|
+
return void 0;
|
|
15647
|
+
}
|
|
15648
|
+
return record3;
|
|
15649
|
+
}
|
|
15084
15650
|
sendAgentMessageRecord(outbox, record3) {
|
|
15651
|
+
const active = this.tasks.get(record3.taskId);
|
|
15652
|
+
const authorized = active === void 0 ? this.recoveredMessageOutboxes.get(record3.taskId) === outbox : active.messageOutbox === outbox && this.canPublishAgentMessage(active);
|
|
15653
|
+
if (!authorized || record3.sessionRef === void 0 || !outbox.retryableRecords().some((pending) => pending.messageId === record3.messageId)) return;
|
|
15654
|
+
if (active !== void 0) active.messageSendAttempted = true;
|
|
15085
15655
|
this.deps.send(createEnvelope("agent.message.publish", outbox.publishPayload(record3), {
|
|
15086
15656
|
taskId: record3.taskId,
|
|
15087
15657
|
sessionRef: record3.sessionRef
|
|
15088
15658
|
}));
|
|
15089
|
-
const active = this.tasks.get(record3.taskId);
|
|
15090
15659
|
if (active?.messageOutbox === outbox) {
|
|
15091
15660
|
if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
|
|
15092
15661
|
active.messageRetryTimer = setTimeout(() => {
|
|
@@ -15126,6 +15695,7 @@ var TaskRunner = class {
|
|
|
15126
15695
|
if (outcome === "accepted") this.recoveredMessageOutboxes.delete(taskId);
|
|
15127
15696
|
return;
|
|
15128
15697
|
}
|
|
15698
|
+
if (this.tasks.get(taskId) !== active) return;
|
|
15129
15699
|
if (outcome === "held") {
|
|
15130
15700
|
if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
|
|
15131
15701
|
active.messageRetryTimer = void 0;
|
|
@@ -15135,6 +15705,8 @@ var TaskRunner = class {
|
|
|
15135
15705
|
if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
|
|
15136
15706
|
active.messageRetryTimer = void 0;
|
|
15137
15707
|
this.revokeAgentMessageContext(taskId);
|
|
15708
|
+
active.pendingMessageCompletion = void 0;
|
|
15709
|
+
await this.fail(taskId, "required Agent message was refused", false);
|
|
15138
15710
|
return;
|
|
15139
15711
|
}
|
|
15140
15712
|
if (outcome !== "accepted") return;
|
|
@@ -15153,6 +15725,9 @@ var TaskRunner = class {
|
|
|
15153
15725
|
/** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
|
|
15154
15726
|
stopAcceptingOffers() {
|
|
15155
15727
|
this.stoppingOffers = true;
|
|
15728
|
+
if (this.startupRetryTimer) clearTimeout(this.startupRetryTimer);
|
|
15729
|
+
this.startupRetryTimer = void 0;
|
|
15730
|
+
for (const abort of this.inFlightBlobAborts.values()) abort.abort();
|
|
15156
15731
|
}
|
|
15157
15732
|
/**
|
|
15158
15733
|
* Shutdown of every currently ACTIVE task for the control socket's
|
|
@@ -15191,9 +15766,65 @@ var TaskRunner = class {
|
|
|
15191
15766
|
* ahead of this method — see `daemon-control-socket.test.ts`'s dedicated
|
|
15192
15767
|
* regression test for the exact scenario.
|
|
15193
15768
|
*/
|
|
15769
|
+
/** Startup resources have an owner even before a Session can become active. */
|
|
15770
|
+
startupRetryTimer;
|
|
15771
|
+
claimedHarnesses = /* @__PURE__ */ new Map();
|
|
15772
|
+
homeReservations = /* @__PURE__ */ new Map();
|
|
15773
|
+
startupOwners = /* @__PURE__ */ new Map();
|
|
15774
|
+
startupDisposals = /* @__PURE__ */ new Map();
|
|
15775
|
+
disposeStartupOwner(taskId) {
|
|
15776
|
+
const existing = this.startupDisposals.get(taskId);
|
|
15777
|
+
if (existing) return existing;
|
|
15778
|
+
const attempt = this.disposeStartupOwnerOnce(taskId).finally(() => this.startupDisposals.delete(taskId));
|
|
15779
|
+
this.startupDisposals.set(taskId, attempt);
|
|
15780
|
+
return attempt;
|
|
15781
|
+
}
|
|
15782
|
+
async disposeStartupOwnerOnce(taskId) {
|
|
15783
|
+
const owner = this.startupOwners.get(taskId);
|
|
15784
|
+
if (!owner) return true;
|
|
15785
|
+
try {
|
|
15786
|
+
await owner.settle?.();
|
|
15787
|
+
await owner.dispose();
|
|
15788
|
+
await this.deps.awaitTerminalCommit?.(taskId);
|
|
15789
|
+
await owner.release();
|
|
15790
|
+
} catch (caught) {
|
|
15791
|
+
const failure = isRuntimeDisposalFailure(caught) ? caught : new RuntimeDisposalFailure({
|
|
15792
|
+
stage: "quiescence",
|
|
15793
|
+
reason: "startup runtime disposal has not been confirmed"
|
|
15794
|
+
}, { cause: caught });
|
|
15795
|
+
this.deps.onRuntimeDisposalFailure?.({
|
|
15796
|
+
taskId,
|
|
15797
|
+
runtimeId: owner.runtimeId,
|
|
15798
|
+
stage: failure.stage,
|
|
15799
|
+
reason: failure.message
|
|
15800
|
+
});
|
|
15801
|
+
console.error(`[byok/client] startup ownership retained for ${taskId}: ${failure.message}`);
|
|
15802
|
+
if (!this.startupRetryTimer && !this.stoppingOffers) {
|
|
15803
|
+
this.startupRetryTimer = setTimeout(() => {
|
|
15804
|
+
this.startupRetryTimer = void 0;
|
|
15805
|
+
for (const id of this.startupOwners.keys()) {
|
|
15806
|
+
if (!this.inFlightOffers.has(id)) void this.disposeStartupOwner(id);
|
|
15807
|
+
}
|
|
15808
|
+
}, 1e3);
|
|
15809
|
+
this.startupRetryTimer.unref?.();
|
|
15810
|
+
}
|
|
15811
|
+
return false;
|
|
15812
|
+
}
|
|
15813
|
+
if (this.startupOwners.get(taskId) === owner) this.startupOwners.delete(taskId);
|
|
15814
|
+
this.pendingMessageTasks.delete(taskId);
|
|
15815
|
+
this.revokeAgentMessageContext(taskId);
|
|
15816
|
+
this.revokeAgentMemoryContext(taskId);
|
|
15817
|
+
return true;
|
|
15818
|
+
}
|
|
15194
15819
|
async shutdownActiveTasks(reason) {
|
|
15195
15820
|
const active = [...this.tasks.values()];
|
|
15196
15821
|
await Promise.all(active.map((task) => this.shutdownTask(task, reason)));
|
|
15822
|
+
for (const taskId of this.startupOwners.keys()) {
|
|
15823
|
+
if (!await this.disposeStartupOwner(taskId)) throw new RuntimeDisposalFailure({
|
|
15824
|
+
stage: "quiescence",
|
|
15825
|
+
reason: "startup runtime ownership remains quarantined"
|
|
15826
|
+
});
|
|
15827
|
+
}
|
|
15197
15828
|
}
|
|
15198
15829
|
/**
|
|
15199
15830
|
* M5 batch-3 (workstream 2): the ONE shared per-task teardown sequence —
|
|
@@ -15225,14 +15856,19 @@ var TaskRunner = class {
|
|
|
15225
15856
|
* a genuine protocol bug, not a benign race — mirrors `pump()`'s own
|
|
15226
15857
|
* identity-check guard for the same class of race.
|
|
15227
15858
|
*/
|
|
15859
|
+
async interruptBounded(session) {
|
|
15860
|
+
await raceSettleFirst(
|
|
15861
|
+
() => session.interrupt(),
|
|
15862
|
+
this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS
|
|
15863
|
+
);
|
|
15864
|
+
}
|
|
15228
15865
|
async teardownActiveTask(active, reason, retryable) {
|
|
15229
15866
|
if (active.finalizationStarted) return this.finish(active.taskId);
|
|
15230
15867
|
if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
|
|
15231
15868
|
active.beingTornDown = true;
|
|
15232
15869
|
active.blobAbort.abort();
|
|
15233
15870
|
await this.observeGit(active, "salvage");
|
|
15234
|
-
|
|
15235
|
-
await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
|
|
15871
|
+
await this.interruptBounded(active.session);
|
|
15236
15872
|
if (this.tasks.get(active.taskId) !== active) return true;
|
|
15237
15873
|
await this.persistAgentTerminalEvidence(active, "failed", reason);
|
|
15238
15874
|
this.deps.send(
|
|
@@ -15334,7 +15970,7 @@ var TaskRunner = class {
|
|
|
15334
15970
|
}
|
|
15335
15971
|
}
|
|
15336
15972
|
async handleOffer(taskId, payload, strictAgentOffer) {
|
|
15337
|
-
if (this.tasks.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
|
|
15973
|
+
if (this.tasks.has(taskId) || this.startupOwners.has(taskId) || this.inFlightOffers.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
|
|
15338
15974
|
return;
|
|
15339
15975
|
}
|
|
15340
15976
|
let agentRef;
|
|
@@ -15377,9 +16013,29 @@ var TaskRunner = class {
|
|
|
15377
16013
|
}
|
|
15378
16014
|
this.inFlightOffers.add(taskId);
|
|
15379
16015
|
const blobAbort = new AbortController();
|
|
16016
|
+
const startupDeadline = Date.now() + (this.deps.startupTimeoutMs ?? 3e4);
|
|
16017
|
+
const startupTimer = setTimeout(
|
|
16018
|
+
() => blobAbort.abort(new Error("runtime startup deadline exceeded during admission/start")),
|
|
16019
|
+
this.deps.startupTimeoutMs ?? 3e4
|
|
16020
|
+
);
|
|
16021
|
+
startupTimer.unref?.();
|
|
16022
|
+
const admissionWithdrawn = () => {
|
|
16023
|
+
if (!blobAbort.signal.aborted) return false;
|
|
16024
|
+
const cancelled = this.pendingCancelled.has(taskId);
|
|
16025
|
+
decline(cancelled ? "cancelled before claim" : this.stoppingOffers ? "daemon is shutting down" : "runtime startup deadline exceeded", !cancelled);
|
|
16026
|
+
return true;
|
|
16027
|
+
};
|
|
15380
16028
|
this.inFlightBlobAborts.set(taskId, blobAbort);
|
|
15381
16029
|
let agentBinding;
|
|
15382
16030
|
let agentLeaseTransferred = false;
|
|
16031
|
+
let reservedHome;
|
|
16032
|
+
const releaseReservation = () => {
|
|
16033
|
+
if (reservedHome === void 0) return;
|
|
16034
|
+
const remaining = this.homeReservations.get(reservedHome) - 1;
|
|
16035
|
+
if (remaining === 0) this.homeReservations.delete(reservedHome);
|
|
16036
|
+
else this.homeReservations.set(reservedHome, remaining);
|
|
16037
|
+
reservedHome = void 0;
|
|
16038
|
+
};
|
|
15383
16039
|
try {
|
|
15384
16040
|
if (this.pendingCancelled.has(taskId)) {
|
|
15385
16041
|
const reason = this.pendingCancelled.get(taskId);
|
|
@@ -15404,11 +16060,14 @@ var TaskRunner = class {
|
|
|
15404
16060
|
);
|
|
15405
16061
|
return;
|
|
15406
16062
|
}
|
|
15407
|
-
|
|
15408
|
-
|
|
15409
|
-
|
|
16063
|
+
if (admissionWithdrawn()) return;
|
|
16064
|
+
const active = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome) + (this.homeReservations.get(canonicalHome) ?? 0);
|
|
16065
|
+
if (active >= limit) {
|
|
16066
|
+
decline(`agent home busy: ${active} active attempt(s)`, true);
|
|
15410
16067
|
return;
|
|
15411
16068
|
}
|
|
16069
|
+
this.homeReservations.set(canonicalHome, (this.homeReservations.get(canonicalHome) ?? 0) + 1);
|
|
16070
|
+
reservedHome = canonicalHome;
|
|
15412
16071
|
}
|
|
15413
16072
|
const guarded = this.deps.admissionGuard?.({ taskId, payload });
|
|
15414
16073
|
if (guarded !== void 0 && !guarded.admit) {
|
|
@@ -15440,6 +16099,14 @@ var TaskRunner = class {
|
|
|
15440
16099
|
);
|
|
15441
16100
|
return;
|
|
15442
16101
|
}
|
|
16102
|
+
if (payload.harnessId !== void 0 && (payload.runtime !== void 0 || payload.dispatchSelection !== void 0)) {
|
|
16103
|
+
decline("harnessId cannot be combined with a built-in runtime selection", false);
|
|
16104
|
+
return;
|
|
16105
|
+
}
|
|
16106
|
+
if (payload.harnessId !== void 0 && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
|
|
16107
|
+
decline("server does not support custom-harness identity", false);
|
|
16108
|
+
return;
|
|
16109
|
+
}
|
|
15443
16110
|
const requiredToolsets = "requiredToolsets" in payload ? payload.requiredToolsets : void 0;
|
|
15444
16111
|
const resolvedMcp = requiredToolsets ? this.resolveMcpServers(requiredToolsets) : void 0;
|
|
15445
16112
|
if (resolvedMcp && !resolvedMcp.ok) {
|
|
@@ -15452,17 +16119,29 @@ var TaskRunner = class {
|
|
|
15452
16119
|
return;
|
|
15453
16120
|
}
|
|
15454
16121
|
const offered = withoutRequiredToolsets(payload);
|
|
15455
|
-
const requestedRuntime = payload.dispatchSelection?.runtimeId ?? payload.runtime;
|
|
16122
|
+
const requestedRuntime = payload.harnessId ?? payload.dispatchSelection?.runtimeId ?? payload.runtime;
|
|
15456
16123
|
const requiresAgentMemoryMcp = agentRef !== void 0 && this.deps.agentMemoryMcpBin !== void 0 && isAgentMemorySecureFilesystemAvailable(this.deps.agentMemoryFilesystemHelperBin !== void 0);
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
16124
|
+
let pick;
|
|
16125
|
+
try {
|
|
16126
|
+
pick = await this.pickAdapter(
|
|
16127
|
+
requestedRuntime,
|
|
16128
|
+
payload.policy.mode,
|
|
16129
|
+
requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp,
|
|
16130
|
+
blobAbort.signal
|
|
16131
|
+
);
|
|
16132
|
+
} catch (error) {
|
|
16133
|
+
if (!admissionWithdrawn()) decline(`runtime detection failed: ${errorMessage6(error)}`, true);
|
|
16134
|
+
return;
|
|
16135
|
+
}
|
|
16136
|
+
if (admissionWithdrawn()) return;
|
|
15462
16137
|
if (!pick.ok) {
|
|
15463
16138
|
decline(pick.reason, pick.retryable);
|
|
15464
16139
|
return;
|
|
15465
16140
|
}
|
|
16141
|
+
if (!isKnownRuntimeId(pick.descriptor.id) && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
|
|
16142
|
+
decline("server does not support the selected custom harness identity", false);
|
|
16143
|
+
return;
|
|
16144
|
+
}
|
|
15466
16145
|
const env = buildRuntimeEnv({
|
|
15467
16146
|
ambient: process.env,
|
|
15468
16147
|
requirements: pick.descriptor.environmentRequirements,
|
|
@@ -15536,18 +16215,20 @@ var TaskRunner = class {
|
|
|
15536
16215
|
}
|
|
15537
16216
|
let prepared;
|
|
15538
16217
|
try {
|
|
15539
|
-
prepared = await pick.adapter.prepare({
|
|
16218
|
+
prepared = await awaitAdmission(() => pick.adapter.prepare({
|
|
16219
|
+
signal: blobAbort.signal,
|
|
15540
16220
|
offer: offered,
|
|
15541
16221
|
policy: decision.policy,
|
|
15542
16222
|
descriptor: pick.descriptor,
|
|
15543
16223
|
requiredToolsetIds: requiredToolsets ?? [],
|
|
15544
16224
|
...taskMcpServers === void 0 ? {} : { mcpServers: taskMcpServers },
|
|
15545
16225
|
...mcpToolsetTools === void 0 ? {} : { mcpToolsetTools }
|
|
15546
|
-
});
|
|
16226
|
+
}), blobAbort.signal);
|
|
15547
16227
|
} catch (error) {
|
|
15548
|
-
decline(`runtime preparation failed: ${errorMessage6(error)}`, true);
|
|
16228
|
+
if (!admissionWithdrawn()) decline(`runtime preparation failed: ${errorMessage6(error)}`, true);
|
|
15549
16229
|
return;
|
|
15550
16230
|
}
|
|
16231
|
+
if (admissionWithdrawn()) return;
|
|
15551
16232
|
if (prepared.kind === "reject") {
|
|
15552
16233
|
decline(prepared.reason, prepared.retryable);
|
|
15553
16234
|
return;
|
|
@@ -15555,6 +16236,7 @@ var TaskRunner = class {
|
|
|
15555
16236
|
if (agentRef !== void 0) {
|
|
15556
16237
|
try {
|
|
15557
16238
|
agentBinding = await this.deps.agentHome.acquireExecution(agentRef, { taskId, sessionRef });
|
|
16239
|
+
releaseReservation();
|
|
15558
16240
|
} catch (error) {
|
|
15559
16241
|
decline(
|
|
15560
16242
|
`Agent home admission failed: ${errorMessage6(error)}`,
|
|
@@ -15679,7 +16361,16 @@ var TaskRunner = class {
|
|
|
15679
16361
|
},
|
|
15680
16362
|
forwardedEnvironmentNames: Object.freeze(Object.keys(env).sort())
|
|
15681
16363
|
});
|
|
16364
|
+
if (admissionWithdrawn()) {
|
|
16365
|
+
gitLease?.release();
|
|
16366
|
+
return;
|
|
16367
|
+
}
|
|
15682
16368
|
await this.deps.beforeClaim?.(taskId, manifest.descriptor.id);
|
|
16369
|
+
if (admissionWithdrawn()) {
|
|
16370
|
+
gitLease?.release();
|
|
16371
|
+
return;
|
|
16372
|
+
}
|
|
16373
|
+
if (!isKnownRuntimeId(manifest.descriptor.id)) this.claimedHarnesses.set(taskId, manifest.descriptor.id);
|
|
15683
16374
|
this.deps.send(
|
|
15684
16375
|
createEnvelope(
|
|
15685
16376
|
"task.claim",
|
|
@@ -15697,6 +16388,7 @@ var TaskRunner = class {
|
|
|
15697
16388
|
// where an auto-selected task left the server never learning
|
|
15698
16389
|
// which runtime actually ran.
|
|
15699
16390
|
runtime: isKnownRuntimeId(manifest.descriptor.id) ? manifest.descriptor.id : void 0,
|
|
16391
|
+
...!isKnownRuntimeId(manifest.descriptor.id) ? { harnessId: manifest.descriptor.id } : {},
|
|
15700
16392
|
// S0/D-4 (`task.claim.capabilities`, docs/protocol.md §2.4): the
|
|
15701
16393
|
// selected adapter's own capability self-report, carried on the
|
|
15702
16394
|
// same message that establishes the task↔runtime binding. The
|
|
@@ -15814,8 +16506,57 @@ var TaskRunner = class {
|
|
|
15814
16506
|
};
|
|
15815
16507
|
let session;
|
|
15816
16508
|
try {
|
|
15817
|
-
session = await
|
|
16509
|
+
session = await startOwnedRuntime(
|
|
16510
|
+
(input) => prepared.operation.start(input),
|
|
16511
|
+
startInput,
|
|
16512
|
+
blobAbort.signal,
|
|
16513
|
+
Math.max(0, startupDeadline - Date.now())
|
|
16514
|
+
);
|
|
15818
16515
|
} catch (err) {
|
|
16516
|
+
const disposalFailure = isRuntimeStartupDisposalFailure(err);
|
|
16517
|
+
const cancelled = this.pendingCancelled.has(taskId);
|
|
16518
|
+
if (disposalFailure || cancelled) {
|
|
16519
|
+
if (cancelled) this.revokeAgentMessageContext(taskId);
|
|
16520
|
+
const ownedBinding2 = agentBinding;
|
|
16521
|
+
const cancellation = createEnvelope("task.cancelled", {
|
|
16522
|
+
reason: this.pendingCancelled.get(taskId),
|
|
16523
|
+
...terminalIdentity(this.claimedHarnesses.get(taskId)),
|
|
16524
|
+
...agentBinding === void 0 ? {} : { agentRef: agentBinding.resolution.agentRef }
|
|
16525
|
+
}, { taskId });
|
|
16526
|
+
let cancellationSettled = false;
|
|
16527
|
+
this.startupOwners.set(taskId, {
|
|
16528
|
+
runtimeId: pick.descriptor.id,
|
|
16529
|
+
dispose: disposalFailure ? err.retryDisposal : async () => {
|
|
16530
|
+
},
|
|
16531
|
+
...cancelled ? { settle: async () => {
|
|
16532
|
+
if (cancellationSettled) return;
|
|
16533
|
+
await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
|
|
16534
|
+
this.deps.send(cancellation);
|
|
16535
|
+
cancellationSettled = true;
|
|
16536
|
+
this.pendingCancelled.delete(taskId);
|
|
16537
|
+
this.addFinishedTaskId(taskId);
|
|
16538
|
+
} } : {},
|
|
16539
|
+
release: async () => {
|
|
16540
|
+
await ownedBinding2?.lease.release();
|
|
16541
|
+
gitLease?.release();
|
|
16542
|
+
}
|
|
16543
|
+
});
|
|
16544
|
+
agentLeaseTransferred = true;
|
|
16545
|
+
if (disposalFailure) this.deps.onRuntimeDisposalFailure?.({
|
|
16546
|
+
taskId,
|
|
16547
|
+
runtimeId: pick.descriptor.id,
|
|
16548
|
+
stage: "quiescence",
|
|
16549
|
+
reason: err.message
|
|
16550
|
+
});
|
|
16551
|
+
if (!cancelled) {
|
|
16552
|
+
if (agentBinding === void 0) await this.fail(taskId, errorMessage6(err), false);
|
|
16553
|
+
else await this.failClaimedAgent(taskId, errorMessage6(err), false, {
|
|
16554
|
+
binding: agentBinding,
|
|
16555
|
+
runtimeId: pick.descriptor.id
|
|
16556
|
+
});
|
|
16557
|
+
}
|
|
16558
|
+
return;
|
|
16559
|
+
}
|
|
15819
16560
|
const failure = projectRuntimeBoundaryFailure(err, "start");
|
|
15820
16561
|
if (failure.contractViolation) {
|
|
15821
16562
|
console.error("[byok/client] runtime adapter start() returned an untyped failure", err);
|
|
@@ -15832,143 +16573,169 @@ var TaskRunner = class {
|
|
|
15832
16573
|
}
|
|
15833
16574
|
return;
|
|
15834
16575
|
}
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
taskId,
|
|
15843
|
-
`Agent session execution lease could not bind the runtime session: ${errorMessage6(error)}`,
|
|
15844
|
-
false,
|
|
15845
|
-
{
|
|
15846
|
-
binding: agentBinding,
|
|
15847
|
-
runtimeId: pick.descriptor.id,
|
|
15848
|
-
sessionRef: session.sessionRef
|
|
15849
|
-
}
|
|
15850
|
-
);
|
|
15851
|
-
return;
|
|
16576
|
+
const ownedBinding = agentBinding;
|
|
16577
|
+
this.startupOwners.set(taskId, {
|
|
16578
|
+
runtimeId: pick.descriptor.id,
|
|
16579
|
+
dispose: () => session.close(),
|
|
16580
|
+
release: async () => {
|
|
16581
|
+
await ownedBinding?.lease.release();
|
|
16582
|
+
gitLease?.release();
|
|
15852
16583
|
}
|
|
15853
|
-
}
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
session,
|
|
15861
|
-
workspaceDir,
|
|
15862
|
-
...agentBinding === void 0 ? {} : {
|
|
15863
|
-
agentBinding,
|
|
15864
|
-
agentRef: agentBinding.resolution.agentRef,
|
|
15865
|
-
agentHandoff: {
|
|
15866
|
-
sessionRef: session.sessionRef,
|
|
15867
|
-
runtimeId: pick.descriptor.id,
|
|
15868
|
-
cwd: workspaceDir
|
|
15869
|
-
}
|
|
15870
|
-
},
|
|
15871
|
-
gitWorkspaceId,
|
|
15872
|
-
gitLease,
|
|
15873
|
-
gitBaseline,
|
|
15874
|
-
summaryParts: [],
|
|
15875
|
-
finalTextParts: [],
|
|
15876
|
-
batcher: new ProgressBatcher(
|
|
15877
|
-
(seq, events) => {
|
|
15878
|
-
const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
|
|
15879
|
-
agentRef: active.agentRef,
|
|
16584
|
+
});
|
|
16585
|
+
try {
|
|
16586
|
+
if (agentBinding !== void 0) {
|
|
16587
|
+
try {
|
|
16588
|
+
await agentBinding.lease.bindSession(session.sessionRef);
|
|
16589
|
+
} catch (error) {
|
|
16590
|
+
await this.failClaimedAgent(
|
|
15880
16591
|
taskId,
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
cwd: workspaceDir,
|
|
15906
|
-
leaseId: binding.lease.leaseId
|
|
15907
|
-
}));
|
|
15908
|
-
} catch (error) {
|
|
15909
|
-
await session.close().catch(() => {
|
|
15910
|
-
});
|
|
15911
|
-
await this.failClaimedAgent(
|
|
15912
|
-
taskId,
|
|
15913
|
-
`Agent session handoff could not be durably written before start: ${errorMessage6(error)}`,
|
|
15914
|
-
false,
|
|
15915
|
-
{
|
|
15916
|
-
binding: agentBinding,
|
|
16592
|
+
`Agent session execution lease could not bind the runtime session: ${errorMessage6(error)}`,
|
|
16593
|
+
false,
|
|
16594
|
+
{
|
|
16595
|
+
binding: agentBinding,
|
|
16596
|
+
runtimeId: pick.descriptor.id,
|
|
16597
|
+
sessionRef: session.sessionRef
|
|
16598
|
+
}
|
|
16599
|
+
);
|
|
16600
|
+
return;
|
|
16601
|
+
}
|
|
16602
|
+
}
|
|
16603
|
+
let active;
|
|
16604
|
+
active = {
|
|
16605
|
+
taskId,
|
|
16606
|
+
blobAbort,
|
|
16607
|
+
egressEnabled: "egressPolicy" in payload,
|
|
16608
|
+
adapter: pick.adapter,
|
|
16609
|
+
session,
|
|
16610
|
+
workspaceDir,
|
|
16611
|
+
...agentBinding === void 0 ? {} : {
|
|
16612
|
+
agentBinding,
|
|
16613
|
+
agentRef: agentBinding.resolution.agentRef,
|
|
16614
|
+
agentHandoff: {
|
|
16615
|
+
sessionRef: session.sessionRef,
|
|
15917
16616
|
runtimeId: pick.descriptor.id,
|
|
15918
|
-
|
|
16617
|
+
cwd: workspaceDir
|
|
15919
16618
|
}
|
|
16619
|
+
},
|
|
16620
|
+
gitWorkspaceId,
|
|
16621
|
+
gitLease,
|
|
16622
|
+
gitBaseline,
|
|
16623
|
+
summaryParts: [],
|
|
16624
|
+
finalTextParts: [],
|
|
16625
|
+
batcher: new ProgressBatcher(
|
|
16626
|
+
(seq, events) => {
|
|
16627
|
+
const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
|
|
16628
|
+
agentRef: active.agentRef,
|
|
16629
|
+
taskId,
|
|
16630
|
+
events,
|
|
16631
|
+
serverCapabilities: this.deps.getServerCapabilities?.() ?? []
|
|
16632
|
+
}) ?? [] : events;
|
|
16633
|
+
if (projected.length > 0) this.deps.send(createEnvelope("task.progress", { seq, events: [...projected] }, { taskId, seq }));
|
|
16634
|
+
},
|
|
16635
|
+
this.deps.batcherOptions
|
|
16636
|
+
),
|
|
16637
|
+
approvalQueue: [],
|
|
16638
|
+
outputBytesSoFar: 0,
|
|
16639
|
+
startedAtMs: Date.now(),
|
|
16640
|
+
...messageRequirement === void 0 ? {} : {
|
|
16641
|
+
messageRequirement,
|
|
16642
|
+
messageOutbox: this.pendingMessageTasks.get(taskId).outbox
|
|
16643
|
+
},
|
|
16644
|
+
...terminalProjection === void 0 ? {} : { terminalProjection }
|
|
16645
|
+
};
|
|
16646
|
+
if (agentBinding !== void 0) {
|
|
16647
|
+
const binding = agentBinding;
|
|
16648
|
+
try {
|
|
16649
|
+
await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
|
|
16650
|
+
agentRef: binding.resolution.agentRef,
|
|
16651
|
+
taskId,
|
|
16652
|
+
sessionRef: session.sessionRef,
|
|
16653
|
+
runtimeId: pick.descriptor.id,
|
|
16654
|
+
cwd: workspaceDir,
|
|
16655
|
+
leaseId: binding.lease.leaseId
|
|
16656
|
+
}));
|
|
16657
|
+
} catch (error) {
|
|
16658
|
+
await this.failClaimedAgent(
|
|
16659
|
+
taskId,
|
|
16660
|
+
`Agent session handoff could not be durably written before start: ${errorMessage6(error)}`,
|
|
16661
|
+
false,
|
|
16662
|
+
{
|
|
16663
|
+
binding: agentBinding,
|
|
16664
|
+
runtimeId: pick.descriptor.id,
|
|
16665
|
+
sessionRef: session.sessionRef
|
|
16666
|
+
}
|
|
16667
|
+
);
|
|
16668
|
+
return;
|
|
16669
|
+
}
|
|
16670
|
+
}
|
|
16671
|
+
const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await this.activateAgentMessage(
|
|
16672
|
+
active.messageOutbox,
|
|
16673
|
+
taskId,
|
|
16674
|
+
session.sessionRef,
|
|
16675
|
+
() => !this.pendingCancelled.has(taskId) && !blobAbort.signal.aborted
|
|
16676
|
+
);
|
|
16677
|
+
if (this.pendingCancelled.has(taskId)) {
|
|
16678
|
+
const reason = this.pendingCancelled.get(taskId);
|
|
16679
|
+
this.pendingCancelled.delete(taskId);
|
|
16680
|
+
agentLeaseTransferred = agentBinding !== void 0;
|
|
16681
|
+
this.startupOwners.delete(taskId);
|
|
16682
|
+
this.tasks.set(taskId, active);
|
|
16683
|
+
this.reserveSemanticTerminal(active);
|
|
16684
|
+
await this.interruptBounded(session);
|
|
16685
|
+
await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
|
|
16686
|
+
await this.persistAgentTerminalEvidence(active, "cancelled", reason);
|
|
16687
|
+
this.deps.send(
|
|
16688
|
+
createEnvelope(
|
|
16689
|
+
"task.cancelled",
|
|
16690
|
+
{ reason, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
|
|
16691
|
+
{ taskId }
|
|
16692
|
+
)
|
|
15920
16693
|
);
|
|
16694
|
+
await this.finish(taskId);
|
|
15921
16695
|
return;
|
|
15922
16696
|
}
|
|
15923
|
-
|
|
15924
|
-
const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await active.messageOutbox.activate(taskId, session.sessionRef);
|
|
15925
|
-
if (this.pendingCancelled.has(taskId)) {
|
|
15926
|
-
const reason = this.pendingCancelled.get(taskId);
|
|
15927
|
-
this.pendingCancelled.delete(taskId);
|
|
16697
|
+
this.deps.send(createEnvelope("task.started", {}, { taskId }));
|
|
15928
16698
|
agentLeaseTransferred = agentBinding !== void 0;
|
|
16699
|
+
this.inFlightBlobAborts.delete(taskId);
|
|
16700
|
+
this.startupOwners.delete(taskId);
|
|
15929
16701
|
this.tasks.set(taskId, active);
|
|
15930
|
-
this.
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
} catch {
|
|
16702
|
+
this.pendingMessageTasks.delete(taskId);
|
|
16703
|
+
if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
|
|
16704
|
+
this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
|
|
15934
16705
|
}
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
if (agentBinding === void 0) {
|
|
15960
|
-
void this.deps.sessionWorkspaces.record(session.sessionRef, {
|
|
15961
|
-
workspaceDir,
|
|
15962
|
-
runtimeSessionId: session.sessionRef,
|
|
15963
|
-
...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
|
|
15964
|
-
}).catch(() => {
|
|
15965
|
-
});
|
|
15966
|
-
}
|
|
15967
|
-
if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
|
|
15968
|
-
void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
|
|
16706
|
+
if (payload.limits?.maxDurationMs !== void 0) {
|
|
16707
|
+
this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
|
|
16708
|
+
}
|
|
16709
|
+
void this.pump(active);
|
|
16710
|
+
if (agentBinding === void 0) {
|
|
16711
|
+
void this.deps.sessionWorkspaces.record(session.sessionRef, {
|
|
16712
|
+
workspaceDir,
|
|
16713
|
+
runtimeSessionId: session.sessionRef,
|
|
16714
|
+
...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
|
|
16715
|
+
}).catch(() => {
|
|
16716
|
+
});
|
|
16717
|
+
}
|
|
16718
|
+
if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
|
|
16719
|
+
void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
|
|
16720
|
+
});
|
|
16721
|
+
}
|
|
16722
|
+
} catch (error) {
|
|
16723
|
+
if (this.tasks.has(taskId)) throw error;
|
|
16724
|
+
const reason = `runtime activation failed: ${errorMessage6(error)}`;
|
|
16725
|
+
if (agentBinding === void 0) await this.fail(taskId, reason, false);
|
|
16726
|
+
else await this.failClaimedAgent(taskId, reason, false, {
|
|
16727
|
+
binding: agentBinding,
|
|
16728
|
+
runtimeId: pick.descriptor.id,
|
|
16729
|
+
sessionRef: session.sessionRef
|
|
15969
16730
|
});
|
|
15970
16731
|
}
|
|
15971
16732
|
} finally {
|
|
16733
|
+
clearTimeout(startupTimer);
|
|
16734
|
+
releaseReservation();
|
|
16735
|
+
if (this.startupOwners.has(taskId)) {
|
|
16736
|
+
agentLeaseTransferred = true;
|
|
16737
|
+
await this.disposeStartupOwner(taskId);
|
|
16738
|
+
}
|
|
15972
16739
|
if (!agentLeaseTransferred) {
|
|
15973
16740
|
this.pendingMessageTasks.delete(taskId);
|
|
15974
16741
|
this.revokeAgentMessageContext(taskId);
|
|
@@ -15980,6 +16747,7 @@ var TaskRunner = class {
|
|
|
15980
16747
|
}
|
|
15981
16748
|
this.inFlightBlobAborts.delete(taskId);
|
|
15982
16749
|
this.inFlightOffers.delete(taskId);
|
|
16750
|
+
this.claimedHarnesses.delete(taskId);
|
|
15983
16751
|
}
|
|
15984
16752
|
}
|
|
15985
16753
|
withAgentMessageMcp(existing, taskId, requirement) {
|
|
@@ -16249,7 +17017,13 @@ var TaskRunner = class {
|
|
|
16249
17017
|
maxPendingBytes: 4 * 1024 * 1024
|
|
16250
17018
|
});
|
|
16251
17019
|
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
16252
|
-
const activated = await
|
|
17020
|
+
const activated = await this.activateAgentMessage(
|
|
17021
|
+
outbox,
|
|
17022
|
+
active.taskId,
|
|
17023
|
+
active.session.sessionRef,
|
|
17024
|
+
() => this.canPublishAgentMessage(active),
|
|
17025
|
+
() => active.messageSendAttempted === true
|
|
17026
|
+
);
|
|
16253
17027
|
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
16254
17028
|
if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
|
|
16255
17029
|
this.sendAgentMessageRecord(outbox, activated);
|
|
@@ -16356,7 +17130,16 @@ var TaskRunner = class {
|
|
|
16356
17130
|
}
|
|
16357
17131
|
let bytes;
|
|
16358
17132
|
try {
|
|
16359
|
-
|
|
17133
|
+
const limits = this.deps.artifactLimits ?? DEFAULT_ARTIFACT_LIMITS;
|
|
17134
|
+
const remaining = limits.maxTaskBytes - (active.artifactBytesSoFar ?? 0);
|
|
17135
|
+
bytes = await readArtifactBytes(
|
|
17136
|
+
opened.handle,
|
|
17137
|
+
Math.min(limits.maxFileBytes, remaining),
|
|
17138
|
+
active.blobAbort.signal,
|
|
17139
|
+
(count) => {
|
|
17140
|
+
active.artifactBytesSoFar = (active.artifactBytesSoFar ?? 0) + count;
|
|
17141
|
+
}
|
|
17142
|
+
);
|
|
16360
17143
|
} catch (err) {
|
|
16361
17144
|
this.reportArtifactError(active, name, `failed to read artifact "${name}": ${errorMessage6(err)}`);
|
|
16362
17145
|
return;
|
|
@@ -16364,6 +17147,7 @@ var TaskRunner = class {
|
|
|
16364
17147
|
await opened.handle.close().catch(() => {
|
|
16365
17148
|
});
|
|
16366
17149
|
}
|
|
17150
|
+
if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
|
|
16367
17151
|
if (bytes.length <= MAX_INLINE_ARTIFACT_BYTES) {
|
|
16368
17152
|
const inline = bytes.toString("base64");
|
|
16369
17153
|
if (new TextEncoder().encode(inline).length <= MAX_INLINE_ARTIFACT_BYTES) {
|
|
@@ -16375,6 +17159,7 @@ var TaskRunner = class {
|
|
|
16375
17159
|
const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType, {
|
|
16376
17160
|
signal: active.blobAbort.signal
|
|
16377
17161
|
});
|
|
17162
|
+
if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
|
|
16378
17163
|
this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
|
|
16379
17164
|
} catch (err) {
|
|
16380
17165
|
this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage6(err)}`);
|
|
@@ -16382,12 +17167,17 @@ var TaskRunner = class {
|
|
|
16382
17167
|
}
|
|
16383
17168
|
/** Loud, non-silent artifact failure (finding F7): logged, and folded into this task's own progress stream as an `error` AgentEvent rather than swallowed — the task itself can still complete normally, but the omission is now visible. */
|
|
16384
17169
|
reportArtifactError(active, name, reason) {
|
|
17170
|
+
if (active.blobAbort.signal.aborted || active.beingTornDown) return;
|
|
16385
17171
|
console.error(`[byok/client] artifact "${name}" for task ${active.taskId} dropped: ${reason}`);
|
|
16386
17172
|
active.batcher.push({ type: "error", message: reason });
|
|
16387
17173
|
}
|
|
16388
17174
|
async handleCancel(taskId, reason) {
|
|
16389
17175
|
const active = this.tasks.get(taskId);
|
|
16390
17176
|
if (!active) {
|
|
17177
|
+
if (this.startupOwners.has(taskId) && !this.inFlightOffers.has(taskId)) {
|
|
17178
|
+
await this.disposeStartupOwner(taskId);
|
|
17179
|
+
return;
|
|
17180
|
+
}
|
|
16391
17181
|
this.setPendingCancelled(taskId, reason);
|
|
16392
17182
|
this.inFlightBlobAborts.get(taskId)?.abort();
|
|
16393
17183
|
return;
|
|
@@ -16401,10 +17191,7 @@ var TaskRunner = class {
|
|
|
16401
17191
|
return;
|
|
16402
17192
|
}
|
|
16403
17193
|
active.blobAbort.abort();
|
|
16404
|
-
|
|
16405
|
-
await active.session.interrupt();
|
|
16406
|
-
} catch {
|
|
16407
|
-
}
|
|
17194
|
+
await this.interruptBounded(active.session);
|
|
16408
17195
|
await this.observeGit(active, "salvage");
|
|
16409
17196
|
await this.persistAgentTerminalEvidence(active, "cancelled", reason);
|
|
16410
17197
|
this.deps.send(
|
|
@@ -16846,10 +17633,7 @@ var TaskRunner = class {
|
|
|
16846
17633
|
await active.semanticTerminalSettled;
|
|
16847
17634
|
return;
|
|
16848
17635
|
}
|
|
16849
|
-
|
|
16850
|
-
await active.session.interrupt();
|
|
16851
|
-
} catch {
|
|
16852
|
-
}
|
|
17636
|
+
await this.interruptBounded(active.session);
|
|
16853
17637
|
await this.observeGit(active, "salvage");
|
|
16854
17638
|
await this.persistAgentTerminalEvidence(active, "failed", reason ?? "rejected");
|
|
16855
17639
|
this.deps.send(
|
|
@@ -16884,7 +17668,7 @@ var TaskRunner = class {
|
|
|
16884
17668
|
this.deps.send(
|
|
16885
17669
|
createEnvelope(
|
|
16886
17670
|
"task.fail",
|
|
16887
|
-
active === void 0 ? { reason, retryable } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
|
|
17671
|
+
active === void 0 ? { reason, retryable, ...terminalIdentity(this.claimedHarnesses.get(taskId)) } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
|
|
16888
17672
|
{ taskId }
|
|
16889
17673
|
)
|
|
16890
17674
|
);
|
|
@@ -16898,6 +17682,7 @@ var TaskRunner = class {
|
|
|
16898
17682
|
* the wire and handleOffer's finally block releases the lease.
|
|
16899
17683
|
*/
|
|
16900
17684
|
async failClaimedAgent(taskId, reason, retryable, context) {
|
|
17685
|
+
await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
|
|
16901
17686
|
const agentRef = context.binding.resolution.agentRef;
|
|
16902
17687
|
const result = await this.retryAgentTerminalEvidence(
|
|
16903
17688
|
() => this.deps.agentHome.mutateExecution(context.binding, () => this.deps.agentSessionHandoffs.recordTaskTerminal({
|
|
@@ -16923,7 +17708,7 @@ var TaskRunner = class {
|
|
|
16923
17708
|
}
|
|
16924
17709
|
this.deps.send(createEnvelope(
|
|
16925
17710
|
"task.fail",
|
|
16926
|
-
{ reason, retryable, agentRef },
|
|
17711
|
+
{ reason, retryable, ...terminalIdentity(context.runtimeId, agentRef) },
|
|
16927
17712
|
{ taskId }
|
|
16928
17713
|
));
|
|
16929
17714
|
return result.ok;
|
|
@@ -16941,7 +17726,8 @@ var TaskRunner = class {
|
|
|
16941
17726
|
terminalInferenceUsagePayload(active) {
|
|
16942
17727
|
const release = this.deps.localAgentRelease;
|
|
16943
17728
|
const runtimeId = active.adapter.descriptor.id;
|
|
16944
|
-
if (
|
|
17729
|
+
if (!isKnownRuntimeId(runtimeId)) return {};
|
|
17730
|
+
if (release === void 0 || active.lastUsage === void 0) return {};
|
|
16945
17731
|
const nowMs = Date.now();
|
|
16946
17732
|
const durationMs = terminalUsageNumber(nowMs - active.startedAtMs, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS);
|
|
16947
17733
|
const promptTokens = terminalUsageNumber(active.lastUsage?.inputTokens, TERMINAL_INFERENCE_USAGE_MAX_TOKENS);
|
|
@@ -16959,7 +17745,7 @@ var TaskRunner = class {
|
|
|
16959
17745
|
}
|
|
16960
17746
|
/** Exact Agent identity projection for claim/terminal wire payloads. */
|
|
16961
17747
|
agentTerminalPayload(active) {
|
|
16962
|
-
return active.
|
|
17748
|
+
return terminalIdentity(active.adapter.descriptor.id, active.agentRef);
|
|
16963
17749
|
}
|
|
16964
17750
|
/**
|
|
16965
17751
|
* additive-minor (`task.complete.document`): the whole daemon-side gate
|
|
@@ -17116,6 +17902,14 @@ var TaskRunner = class {
|
|
|
17116
17902
|
}
|
|
17117
17903
|
/** Persist Agent terminal truth before wire when local storage is available. */
|
|
17118
17904
|
async persistAgentTerminalEvidence(active, cause, reason) {
|
|
17905
|
+
if (cause === "cancelled" || cause === "failed" && !active.messageSendAttempted) {
|
|
17906
|
+
try {
|
|
17907
|
+
await active.messageOutbox?.revoke(active.taskId);
|
|
17908
|
+
} catch (error) {
|
|
17909
|
+
active.resolveSemanticTerminalSettled?.(false);
|
|
17910
|
+
throw error;
|
|
17911
|
+
}
|
|
17912
|
+
}
|
|
17119
17913
|
active.terminalCause = cause;
|
|
17120
17914
|
active.terminalReason = reason;
|
|
17121
17915
|
if (active.agentBinding === void 0 || active.agentRef === void 0 || active.agentHandoff === void 0) {
|
|
@@ -17178,7 +17972,19 @@ var TaskRunner = class {
|
|
|
17178
17972
|
error
|
|
17179
17973
|
});
|
|
17180
17974
|
}
|
|
17181
|
-
async
|
|
17975
|
+
async retryTerminalFinalization(taskId) {
|
|
17976
|
+
const active = this.tasks.get(taskId);
|
|
17977
|
+
if (active?.finalizationStarted) await this.finish(taskId);
|
|
17978
|
+
}
|
|
17979
|
+
finalizationAttempts = /* @__PURE__ */ new Map();
|
|
17980
|
+
finish(taskId) {
|
|
17981
|
+
const existing = this.finalizationAttempts.get(taskId);
|
|
17982
|
+
if (existing) return existing;
|
|
17983
|
+
const attempt = this.finishOnce(taskId).finally(() => this.finalizationAttempts.delete(taskId));
|
|
17984
|
+
this.finalizationAttempts.set(taskId, attempt);
|
|
17985
|
+
return attempt;
|
|
17986
|
+
}
|
|
17987
|
+
async finishOnce(taskId) {
|
|
17182
17988
|
const active = this.tasks.get(taskId);
|
|
17183
17989
|
if (!active) return true;
|
|
17184
17990
|
if (!active.finalizationStarted) {
|
|
@@ -17229,6 +18035,13 @@ var TaskRunner = class {
|
|
|
17229
18035
|
return false;
|
|
17230
18036
|
}
|
|
17231
18037
|
if (this.tasks.get(taskId) !== active) return true;
|
|
18038
|
+
try {
|
|
18039
|
+
await this.deps.awaitTerminalCommit?.(taskId);
|
|
18040
|
+
} catch {
|
|
18041
|
+
active.resolveSemanticTerminalSettled?.(false);
|
|
18042
|
+
return false;
|
|
18043
|
+
}
|
|
18044
|
+
if (this.tasks.get(taskId) !== active) return true;
|
|
17232
18045
|
if (active.agentBinding !== void 0 && active.agentRef !== void 0 && active.agentHandoff !== void 0) {
|
|
17233
18046
|
const agentRef = active.agentRef;
|
|
17234
18047
|
const handoff = active.agentHandoff;
|
|
@@ -17340,7 +18153,7 @@ var TaskRunner = class {
|
|
|
17340
18153
|
* is device-specific (which runtimes happen to be installed here), so a
|
|
17341
18154
|
* different device's installed runtime set might satisfy it.
|
|
17342
18155
|
*/
|
|
17343
|
-
async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets) {
|
|
18156
|
+
async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets, signal) {
|
|
17344
18157
|
const allowlist = this.deps.runtimeAllowlist;
|
|
17345
18158
|
if (requestedRuntime) {
|
|
17346
18159
|
if (allowlist && !allowlist.includes(requestedRuntime)) {
|
|
@@ -17369,8 +18182,8 @@ var TaskRunner = class {
|
|
|
17369
18182
|
retryable: false
|
|
17370
18183
|
};
|
|
17371
18184
|
}
|
|
17372
|
-
const detected = await adapter.detect();
|
|
17373
|
-
if (
|
|
18185
|
+
const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
|
|
18186
|
+
if (detected.kind !== "available") {
|
|
17374
18187
|
return {
|
|
17375
18188
|
ok: false,
|
|
17376
18189
|
reason: `runtime "${requestedRuntime}" is not installed/available on this device`,
|
|
@@ -17385,8 +18198,8 @@ var TaskRunner = class {
|
|
|
17385
18198
|
const descriptor = freezeRuntimeAdapterDescriptor(adapter.descriptor);
|
|
17386
18199
|
if (!adapterSupportsMode(descriptor, policyMode)) continue;
|
|
17387
18200
|
if (requiresMcpToolsets && !adapterSupportsMcpToolsets(descriptor)) continue;
|
|
17388
|
-
const detected = await adapter.detect();
|
|
17389
|
-
if (detected.
|
|
18201
|
+
const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
|
|
18202
|
+
if (detected.kind === "available") return { ok: true, adapter, descriptor };
|
|
17390
18203
|
}
|
|
17391
18204
|
return {
|
|
17392
18205
|
ok: false,
|
|
@@ -17550,10 +18363,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17550
18363
|
constructor(homeDir, spoolPath) {
|
|
17551
18364
|
this.homeDir = homeDir;
|
|
17552
18365
|
this.spoolPath = spoolPath;
|
|
18366
|
+
this.file = new DurableJsonlFile(spoolPath);
|
|
17553
18367
|
}
|
|
17554
18368
|
homeDir;
|
|
17555
18369
|
spoolPath;
|
|
17556
18370
|
pending = /* @__PURE__ */ new Map();
|
|
18371
|
+
file;
|
|
17557
18372
|
nextCursor = 1;
|
|
17558
18373
|
logEntries = 0;
|
|
17559
18374
|
writeTail = Promise.resolve();
|
|
@@ -17562,6 +18377,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17562
18377
|
await ensureSecureDir(directory);
|
|
17563
18378
|
const spool = new _AgentReliableSpool(homeDir, path3__default.join(directory, AGENT_RELIABLE_SPOOL_FILENAME));
|
|
17564
18379
|
await spool.load();
|
|
18380
|
+
await spool.file.confirmRecovered();
|
|
17565
18381
|
return spool;
|
|
17566
18382
|
}
|
|
17567
18383
|
get pendingEvents() {
|
|
@@ -17581,6 +18397,25 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17581
18397
|
const payloadJson = stableRecordJson(input.payload);
|
|
17582
18398
|
const byteCount = Buffer.byteLength(payloadJson, "utf8");
|
|
17583
18399
|
if (byteCount <= 0) throw new AgentReliableSpoolError("reliable payload must have positive serialized bytes");
|
|
18400
|
+
const eventId = input.eventId ?? randomUUID();
|
|
18401
|
+
const existing = this.pending.get(eventId);
|
|
18402
|
+
if (existing !== void 0) {
|
|
18403
|
+
if (!sameRecord(existing, {
|
|
18404
|
+
wireType: "agent.egress.reliable",
|
|
18405
|
+
agentRef: input.agentRef,
|
|
18406
|
+
tenantId: input.tenantId,
|
|
18407
|
+
policyRevision: input.policyRevision,
|
|
18408
|
+
eventId,
|
|
18409
|
+
cursor: existing.cursor,
|
|
18410
|
+
payloadHash: contentHash(payloadJson),
|
|
18411
|
+
byteCount,
|
|
18412
|
+
sessionRef: input.sessionRef,
|
|
18413
|
+
taskId: input.taskId
|
|
18414
|
+
}) || stableRecordJson(existing.payload) !== payloadJson || input.createdAt !== void 0 && input.createdAt !== existing.createdAt) {
|
|
18415
|
+
throw new AgentReliableSpoolError(`reliable eventId ${eventId} differs from its existing record`);
|
|
18416
|
+
}
|
|
18417
|
+
return existing;
|
|
18418
|
+
}
|
|
17584
18419
|
if (this.pending.size >= policy.reliable.maxPendingEventsPerAgent) {
|
|
17585
18420
|
throw new AgentReliableQuotaError("quota_exceeded", "reliable per-Agent event quota is exhausted");
|
|
17586
18421
|
}
|
|
@@ -17590,8 +18425,6 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17590
18425
|
if (tenantPendingBytes + byteCount > policy.reliable.maxPendingBytesPerTenant) {
|
|
17591
18426
|
throw new AgentReliableQuotaError("quota_exceeded", "reliable tenant byte quota is exhausted");
|
|
17592
18427
|
}
|
|
17593
|
-
const eventId = input.eventId ?? randomUUID();
|
|
17594
|
-
if (this.pending.has(eventId)) throw new AgentReliableSpoolError(`reliable eventId ${eventId} already exists`);
|
|
17595
18428
|
const record3 = Object.freeze({
|
|
17596
18429
|
schema: 1,
|
|
17597
18430
|
wireType: "agent.egress.reliable",
|
|
@@ -17700,6 +18533,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17700
18533
|
if (error.code === "ENOENT") return;
|
|
17701
18534
|
throw error;
|
|
17702
18535
|
}
|
|
18536
|
+
if (raw.length > 0 && !raw.endsWith("\n")) throw new AgentReliableSpoolError("durable JSONL log has an incomplete trailing frame; preserve the file for explicit repair");
|
|
17703
18537
|
for (const line of raw.split("\n")) {
|
|
17704
18538
|
if (line.length === 0) continue;
|
|
17705
18539
|
let entry;
|
|
@@ -17722,19 +18556,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17722
18556
|
}
|
|
17723
18557
|
}
|
|
17724
18558
|
async appendEntry(entry) {
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
await handle.write(`${stableRecordJson(entry)}
|
|
17728
|
-
`, void 0, "utf8");
|
|
17729
|
-
await handle.sync();
|
|
17730
|
-
this.logEntries += 1;
|
|
17731
|
-
} finally {
|
|
17732
|
-
await handle.close();
|
|
17733
|
-
}
|
|
18559
|
+
await this.file.append(stableRecordJson(entry));
|
|
18560
|
+
this.logEntries += 1;
|
|
17734
18561
|
}
|
|
17735
18562
|
async compact() {
|
|
17736
18563
|
const entries = this.records().map((record3) => ({ schema: 1, kind: "append", record: record3 }));
|
|
17737
|
-
await
|
|
18564
|
+
await this.file.replace(entries.map((entry) => stableRecordJson(entry)));
|
|
17738
18565
|
this.logEntries = entries.length;
|
|
17739
18566
|
}
|
|
17740
18567
|
async exclusive(fn) {
|
|
@@ -17745,6 +18572,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
17745
18572
|
});
|
|
17746
18573
|
await prior;
|
|
17747
18574
|
try {
|
|
18575
|
+
this.file.assertWritable();
|
|
17748
18576
|
return await fn();
|
|
17749
18577
|
} finally {
|
|
17750
18578
|
release();
|
|
@@ -19119,17 +19947,26 @@ function isRuntimeId(id) {
|
|
|
19119
19947
|
return id === "pi" || id === "claude" || id === "codex";
|
|
19120
19948
|
}
|
|
19121
19949
|
async function detectRuntimes(adapters) {
|
|
19122
|
-
const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: await adapter.detect() })));
|
|
19950
|
+
const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: validateRuntimeDetectResult(await adapter.detect()) })));
|
|
19123
19951
|
const runtimes = [];
|
|
19952
|
+
const harnesses = [];
|
|
19124
19953
|
for (const { adapter, detected } of detections) {
|
|
19125
|
-
if (
|
|
19954
|
+
if (detected.kind !== "available") continue;
|
|
19955
|
+
if (!isRuntimeId(adapter.descriptor.id)) {
|
|
19956
|
+
harnesses.push({
|
|
19957
|
+
id: HarnessIdSchema.parse(adapter.descriptor.id),
|
|
19958
|
+
...detected.version === void 0 ? {} : { version: detected.version },
|
|
19959
|
+
capabilities: toRuntimeInfoCapabilities(adapter.descriptor.capabilities)
|
|
19960
|
+
});
|
|
19961
|
+
continue;
|
|
19962
|
+
}
|
|
19126
19963
|
const info = { id: adapter.descriptor.id };
|
|
19127
19964
|
if (detected.version !== void 0) info.version = detected.version;
|
|
19128
19965
|
if (detected.authPresent !== void 0) info.authPresent = detected.authPresent;
|
|
19129
19966
|
info.capabilities = toRuntimeInfoCapabilities(adapter.descriptor.capabilities);
|
|
19130
19967
|
runtimes.push(info);
|
|
19131
19968
|
}
|
|
19132
|
-
return runtimes;
|
|
19969
|
+
return { runtimes, harnesses };
|
|
19133
19970
|
}
|
|
19134
19971
|
function computeCapabilities(adapters, agentHomeConfigured = false, strictAgentOnly = false, agentHomeProjectionConfigured = false, agentEgressConfigured = false, contentReadPolicies, providerProfileBindingConfigured = false) {
|
|
19135
19972
|
const flags = [];
|
|
@@ -19219,7 +20056,7 @@ function buildAdapter(id, config) {
|
|
|
19219
20056
|
resolveApprovalMcpBin: () => resolveApprovalMcpBin(config.sdkHelperHost)
|
|
19220
20057
|
});
|
|
19221
20058
|
case "codex":
|
|
19222
|
-
return new CodexAdapter();
|
|
20059
|
+
return new CodexAdapter({ sdkHelperHost: config.sdkHelperHost });
|
|
19223
20060
|
}
|
|
19224
20061
|
}
|
|
19225
20062
|
function buildDefaultAdapters(config) {
|
|
@@ -19296,6 +20133,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19296
20133
|
const localAgentRelease = resolveLocalAgentReleaseIdentity(config.localAgentRelease);
|
|
19297
20134
|
const toolsetRegistry = new McpToolsetRegistry(config.mcpToolsets);
|
|
19298
20135
|
validatePiByokLauncherConfig(config.piByokLauncher);
|
|
20136
|
+
if (config.startupTimeoutMs !== void 0 && (!Number.isSafeInteger(config.startupTimeoutMs) || config.startupTimeoutMs <= 0)) throw new Error("startupTimeoutMs must be a positive safe integer");
|
|
20137
|
+
if (config.artifactLimits !== void 0) {
|
|
20138
|
+
for (const value of [config.artifactLimits.maxFileBytes, config.artifactLimits.maxTaskBytes]) {
|
|
20139
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error("artifactLimits must contain positive safe integer byte budgets");
|
|
20140
|
+
}
|
|
20141
|
+
}
|
|
19299
20142
|
if (config.maxTaskOutputBytes !== void 0 && !(config.maxTaskOutputBytes > 0)) {
|
|
19300
20143
|
throw new Error(
|
|
19301
20144
|
`DaemonConfig.maxTaskOutputBytes must be a positive number (or omitted to use the ${DEFAULT_MAX_TASK_OUTPUT_BYTES}-byte default) \u2014 got ${config.maxTaskOutputBytes}. Pass Number.POSITIVE_INFINITY to explicitly disable the cap; 0 or a negative number is rejected rather than silently treated as "disabled".`
|
|
@@ -19425,7 +20268,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19425
20268
|
pressureEngine = ownedPressureEngine;
|
|
19426
20269
|
}
|
|
19427
20270
|
}
|
|
19428
|
-
|
|
20271
|
+
const terminalCommits = new TerminalCommitQueue((taskId) => {
|
|
20272
|
+
connection?.retryTaskReceipts(taskId);
|
|
20273
|
+
void runner?.retryTerminalFinalization(taskId).catch((error) => {
|
|
20274
|
+
console.error("[byok/client] terminal finalization still requires recovery", error);
|
|
20275
|
+
});
|
|
20276
|
+
});
|
|
19429
20277
|
const observer = new DaemonObserver();
|
|
19430
20278
|
const approvalRegistry = new ApprovalRegistry();
|
|
19431
20279
|
let connection;
|
|
@@ -19560,6 +20408,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19560
20408
|
await runLifecycleMutation(startUnderLease);
|
|
19561
20409
|
}
|
|
19562
20410
|
async function startUnderLease() {
|
|
20411
|
+
terminalCommits.resume();
|
|
19563
20412
|
if (!daemonOwnerLease) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
|
|
19564
20413
|
try {
|
|
19565
20414
|
let record3;
|
|
@@ -19611,7 +20460,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19611
20460
|
const terminal = createEnvelope("task.fail", {
|
|
19612
20461
|
reason: "daemon_interrupted",
|
|
19613
20462
|
retryable: false,
|
|
19614
|
-
...
|
|
20463
|
+
...terminalIdentity(task.claimedRuntime, payload.agentRef),
|
|
20464
|
+
recovery: { kind: "daemon_interrupted", offerId: offer.id }
|
|
19615
20465
|
}, { taskId: task.taskId });
|
|
19616
20466
|
const bytes = encodeEnvelope(terminal);
|
|
19617
20467
|
await activeJournal.recordTerminal({
|
|
@@ -19639,7 +20489,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19639
20489
|
await gitWorkspaceStore?.reconcile();
|
|
19640
20490
|
}
|
|
19641
20491
|
blobLifecycleAbort = new AbortController();
|
|
19642
|
-
const [runtimes, blobClient] = await Promise.all([
|
|
20492
|
+
const [{ runtimes, harnesses }, blobClient] = await Promise.all([
|
|
19643
20493
|
detectRuntimes(adapters),
|
|
19644
20494
|
Promise.resolve(new BlobClient(config.serverUrl, auth, { signal: blobLifecycleAbort.signal }))
|
|
19645
20495
|
]);
|
|
@@ -19660,6 +20510,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19660
20510
|
tenantId: record3.tenantId,
|
|
19661
20511
|
deviceId: record3.deviceId
|
|
19662
20512
|
}) : void 0;
|
|
20513
|
+
capabilities.push("custom-harness");
|
|
19663
20514
|
const sendSanitizedEnvelope = (envelope) => {
|
|
19664
20515
|
const terminalKind = terminalKindOf(envelope.type);
|
|
19665
20516
|
if (!activeJournal || !journalIdentity || terminalKind === void 0 || envelope.task_id === void 0) {
|
|
@@ -19669,7 +20520,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19669
20520
|
}
|
|
19670
20521
|
const taskId = envelope.task_id;
|
|
19671
20522
|
const bytes = encodeEnvelope(envelope);
|
|
19672
|
-
|
|
20523
|
+
terminalCommits.enqueue(taskId, async () => {
|
|
19673
20524
|
try {
|
|
19674
20525
|
await activeJournal.recordTerminal({
|
|
19675
20526
|
taskId,
|
|
@@ -19697,7 +20548,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19697
20548
|
const failure = createEnvelope("task.fail", {
|
|
19698
20549
|
reason: "terminal_result_too_large",
|
|
19699
20550
|
retryable: false,
|
|
19700
|
-
...
|
|
20551
|
+
...terminalIdentity(task.claimedRuntime, offerPayload.agentRef)
|
|
19701
20552
|
}, { taskId });
|
|
19702
20553
|
const failureBytes = encodeEnvelope(failure);
|
|
19703
20554
|
await activeJournal.recordTerminal({
|
|
@@ -19715,8 +20566,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19715
20566
|
observer.handleOutboundEnvelope(decoded);
|
|
19716
20567
|
connection?.send(decoded);
|
|
19717
20568
|
}
|
|
19718
|
-
}).catch((error) => {
|
|
19719
|
-
console.error("[byok/client] terminal durability failed; execution remains unsettled", error);
|
|
19720
20569
|
});
|
|
19721
20570
|
};
|
|
19722
20571
|
const sendEnvelope = (candidate) => {
|
|
@@ -19798,7 +20647,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19798
20647
|
// Finding F5(a): see TaskRunnerDeps.shutdownInterruptTimeoutMs's own doc comment.
|
|
19799
20648
|
shutdownInterruptTimeoutMs: overrides.shutdown?.taskInterruptTimeoutMs,
|
|
19800
20649
|
// M5 batch-3 (workstream 2): see DaemonConfig.maxTaskOutputBytes's own doc comment — already validated above.
|
|
20650
|
+
awaitTerminalCommit: (taskId) => terminalCommits.receipt(taskId),
|
|
19801
20651
|
maxTaskOutputBytes: config.maxTaskOutputBytes,
|
|
20652
|
+
artifactLimits: config.artifactLimits,
|
|
20653
|
+
startupTimeoutMs: config.startupTimeoutMs,
|
|
19802
20654
|
// See DaemonConfig.maxInlineEventBytes's own doc comment — already validated above.
|
|
19803
20655
|
maxInlineEventBytes: config.maxInlineEventBytes,
|
|
19804
20656
|
// additive-minor (`task.complete.document`): passed through verbatim,
|
|
@@ -19846,6 +20698,20 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19846
20698
|
if (config.agentHome !== void 0 && config.agentEgress !== void 0) {
|
|
19847
20699
|
await runner.recoverAgentMessageOutboxes(path3__default.join(config.agentHome.hostStorageRoot, "agents"));
|
|
19848
20700
|
}
|
|
20701
|
+
const replayRecoveryTerminals = async (taskId) => {
|
|
20702
|
+
if (!activeJournal || !journalIdentity) return;
|
|
20703
|
+
for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
|
|
20704
|
+
if (terminal.truthState !== "pending" || taskId !== void 0 && terminal.taskId !== taskId) continue;
|
|
20705
|
+
if (runner?.hasPendingRecoveredAgentMessage(terminal.taskId)) continue;
|
|
20706
|
+
const envelope = decodeEnvelope(terminal.bytes);
|
|
20707
|
+
if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
|
|
20708
|
+
throw new Error("durable terminal execution binding mismatch");
|
|
20709
|
+
}
|
|
20710
|
+
overrides.executionRecoveryFault?.("terminal:before-send");
|
|
20711
|
+
observer.handleOutboundEnvelope(envelope);
|
|
20712
|
+
connection?.send(envelope);
|
|
20713
|
+
}
|
|
20714
|
+
};
|
|
19849
20715
|
const handleAgentHomeProjectionEnvelope = async (envelope) => {
|
|
19850
20716
|
if (envelope.type !== "agent.home.projection") return false;
|
|
19851
20717
|
if (agentHomeManager === void 0 || agentHomeProjectionCompletion === void 0) {
|
|
@@ -19980,6 +20846,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
19980
20846
|
capabilities,
|
|
19981
20847
|
clientVersion: localAgentRelease.version,
|
|
19982
20848
|
runtimes,
|
|
20849
|
+
harnesses,
|
|
19983
20850
|
getConfiguredToolsets: () => toolsetRegistry.snapshot().configuredToolsets,
|
|
19984
20851
|
auth,
|
|
19985
20852
|
cursorStore,
|
|
@@ -20021,12 +20888,22 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
20021
20888
|
activePressureEngine?.assertAckCriticalAllowed();
|
|
20022
20889
|
await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
|
|
20023
20890
|
if (isTaskOfferType(envelope.type) && envelope.task_id !== void 0) {
|
|
20891
|
+
if (terminalCommits.hasPending(envelope.task_id)) {
|
|
20892
|
+
await terminalCommits.retry(envelope.task_id);
|
|
20893
|
+
return;
|
|
20894
|
+
}
|
|
20024
20895
|
const task = await activeJournal.readTask(envelope.task_id, journalIdentity);
|
|
20025
20896
|
if (!task) throw new Error("durable offer has no execution record");
|
|
20026
|
-
if (task.localState !== "received")
|
|
20897
|
+
if (task.localState !== "received") {
|
|
20898
|
+
await terminalCommits.retry(envelope.task_id);
|
|
20899
|
+
return;
|
|
20900
|
+
}
|
|
20027
20901
|
}
|
|
20028
20902
|
await runner?.handleEnvelope(envelope);
|
|
20029
|
-
|
|
20903
|
+
if (envelope.type === "agent.message.disposition") {
|
|
20904
|
+
await replayRecoveryTerminals(envelope.task_id);
|
|
20905
|
+
}
|
|
20906
|
+
if (envelope.task_id !== void 0) await terminalCommits.receipt(envelope.task_id);
|
|
20030
20907
|
} : (envelope) => {
|
|
20031
20908
|
if (tenantRebinding) {
|
|
20032
20909
|
return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
|
|
@@ -20074,18 +20951,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
20074
20951
|
});
|
|
20075
20952
|
}
|
|
20076
20953
|
});
|
|
20077
|
-
|
|
20078
|
-
for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
|
|
20079
|
-
if (terminal.truthState !== "pending") continue;
|
|
20080
|
-
const envelope = decodeEnvelope(terminal.bytes);
|
|
20081
|
-
if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
|
|
20082
|
-
throw new Error("durable terminal execution binding mismatch");
|
|
20083
|
-
}
|
|
20084
|
-
overrides.executionRecoveryFault?.("terminal:before-send");
|
|
20085
|
-
observer.handleOutboundEnvelope(envelope);
|
|
20086
|
-
connection.send(envelope);
|
|
20087
|
-
}
|
|
20088
|
-
}
|
|
20954
|
+
await replayRecoveryTerminals();
|
|
20089
20955
|
await connection.start();
|
|
20090
20956
|
await connection.waitForConnection();
|
|
20091
20957
|
runner.retryRecoveredAgentMessages();
|
|
@@ -20200,7 +21066,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
20200
21066
|
pendingLateMutationBarriers.add(observedActiveTeardown);
|
|
20201
21067
|
void observedActiveTeardown.finally(() => pendingLateMutationBarriers.delete(observedActiveTeardown)).catch(() => void 0);
|
|
20202
21068
|
}
|
|
20203
|
-
const journalTailSettled = journal ? await attempt(() =>
|
|
21069
|
+
const journalTailSettled = journal ? await attempt(() => terminalCommits.stop(), true) : true;
|
|
20204
21070
|
const maintenanceSettled = await attempt(() => maintenanceStopped, true);
|
|
20205
21071
|
const drainTimeoutMs = opts.drainTimeoutMs ?? overrides.shutdown?.outboxDrainTimeoutMs ?? DEFAULT_SHUTDOWN_OUTBOX_DRAIN_TIMEOUT_MS;
|
|
20206
21072
|
const connectionStopped = await attempt(() => connection?.stop(drainTimeoutMs), true);
|
|
@@ -20720,6 +21586,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
20720
21586
|
revoked: connection?.isRevoked() ?? auth.isRevoked(),
|
|
20721
21587
|
deviceId: auth.deviceId,
|
|
20722
21588
|
activeTaskCount: runner?.activeTaskCount ?? 0,
|
|
21589
|
+
pendingTerminalCommits: terminalCommits.pendingCount,
|
|
20723
21590
|
branding: config.branding,
|
|
20724
21591
|
operationalHealth: operationalHealth.snapshot(),
|
|
20725
21592
|
toolsets: toolsetRegistry.status(),
|
|
@@ -21185,8 +22052,8 @@ var TruthMemoryClient = class {
|
|
|
21185
22052
|
#requestId;
|
|
21186
22053
|
#allowedObjectDownloadOrigins;
|
|
21187
22054
|
async listManifest(query = {}) {
|
|
21188
|
-
const
|
|
21189
|
-
const response = await this.#proofFetch(
|
|
22055
|
+
const path40 = manifestPath(query);
|
|
22056
|
+
const response = await this.#proofFetch(path40, {
|
|
21190
22057
|
method: "GET",
|
|
21191
22058
|
operation: "truth.list",
|
|
21192
22059
|
resource: "records",
|
|
@@ -21293,9 +22160,9 @@ var TruthMemoryClient = class {
|
|
|
21293
22160
|
}
|
|
21294
22161
|
async #write(kind, recordKey, requestId, payload, expectedPrimary, expectedSnapshots) {
|
|
21295
22162
|
assertDistinctExpectedWrites([expectedPrimary, ...expectedSnapshots]);
|
|
21296
|
-
const
|
|
22163
|
+
const path40 = recordPath(kind, recordKey);
|
|
21297
22164
|
const body = new TextEncoder().encode(JSON.stringify(payload));
|
|
21298
|
-
const response = await this.#proofFetch(
|
|
22165
|
+
const response = await this.#proofFetch(path40, {
|
|
21299
22166
|
method: "PUT",
|
|
21300
22167
|
operation: "truth.write",
|
|
21301
22168
|
resource: `${kind}/${recordKey}`,
|
|
@@ -21321,8 +22188,8 @@ var TruthMemoryClient = class {
|
|
|
21321
22188
|
};
|
|
21322
22189
|
}
|
|
21323
22190
|
async #readVerified(listed) {
|
|
21324
|
-
const
|
|
21325
|
-
const response = await this.#proofFetch(
|
|
22191
|
+
const path40 = recordPath(listed.kind, listed.recordKey);
|
|
22192
|
+
const response = await this.#proofFetch(path40, {
|
|
21326
22193
|
method: "GET",
|
|
21327
22194
|
operation: "truth.read",
|
|
21328
22195
|
resource: `${listed.kind}/${listed.recordKey}`,
|
|
@@ -21383,16 +22250,16 @@ var TruthMemoryClient = class {
|
|
|
21383
22250
|
}
|
|
21384
22251
|
return { ...listed, bytes };
|
|
21385
22252
|
}
|
|
21386
|
-
async #proofFetch(
|
|
22253
|
+
async #proofFetch(path40, request) {
|
|
21387
22254
|
const proof = await this.options.signer.sign({
|
|
21388
22255
|
method: request.method,
|
|
21389
|
-
path:
|
|
22256
|
+
path: path40,
|
|
21390
22257
|
operation: request.operation,
|
|
21391
22258
|
resource: request.resource,
|
|
21392
22259
|
requestId: request.requestId,
|
|
21393
22260
|
body: request.body
|
|
21394
22261
|
});
|
|
21395
|
-
const response = await this.#fetch(new URL(
|
|
22262
|
+
const response = await this.#fetch(new URL(path40, this.#base), {
|
|
21396
22263
|
method: request.method,
|
|
21397
22264
|
headers: {
|
|
21398
22265
|
...request.headers,
|
|
@@ -21403,7 +22270,7 @@ var TruthMemoryClient = class {
|
|
|
21403
22270
|
if (!response.ok) {
|
|
21404
22271
|
throw new TruthMemoryClientError(
|
|
21405
22272
|
"truth_http_failed",
|
|
21406
|
-
`truth request ${request.method} ${
|
|
22273
|
+
`truth request ${request.method} ${path40} failed with HTTP ${response.status}`,
|
|
21407
22274
|
response.status
|
|
21408
22275
|
);
|
|
21409
22276
|
}
|
|
@@ -21695,7 +22562,7 @@ ${args.map((a) => ` ${plistString(a)}`).join("\n")}
|
|
|
21695
22562
|
}
|
|
21696
22563
|
function createLaunchdLifecycle(def, deps = {}) {
|
|
21697
22564
|
const run = deps.run ?? defaultRunner;
|
|
21698
|
-
const
|
|
22565
|
+
const fs33 = deps.fs ?? promises;
|
|
21699
22566
|
const homedir = deps.homedir ?? (() => os4__default.homedir());
|
|
21700
22567
|
const getuid = deps.getuid ?? (() => {
|
|
21701
22568
|
if (typeof process.getuid !== "function") {
|
|
@@ -21709,7 +22576,7 @@ function createLaunchdLifecycle(def, deps = {}) {
|
|
|
21709
22576
|
const serviceTarget = () => `${domainTarget()}/${label}`;
|
|
21710
22577
|
async function fileExists(p) {
|
|
21711
22578
|
try {
|
|
21712
|
-
await
|
|
22579
|
+
await fs33.stat(p);
|
|
21713
22580
|
return true;
|
|
21714
22581
|
} catch {
|
|
21715
22582
|
return false;
|
|
@@ -21717,9 +22584,9 @@ function createLaunchdLifecycle(def, deps = {}) {
|
|
|
21717
22584
|
}
|
|
21718
22585
|
async function writePlist(program) {
|
|
21719
22586
|
const xml = generateLaunchdPlist({ label, program, logDir: def.logDir });
|
|
21720
|
-
await
|
|
21721
|
-
await
|
|
21722
|
-
await
|
|
22587
|
+
await fs33.mkdir(path3__default.dirname(plistPath()), { recursive: true });
|
|
22588
|
+
await fs33.mkdir(def.logDir, { recursive: true });
|
|
22589
|
+
await fs33.writeFile(plistPath(), xml, "utf8");
|
|
21723
22590
|
}
|
|
21724
22591
|
async function install(opts = {}) {
|
|
21725
22592
|
await writePlist(opts.program ?? def.program);
|
|
@@ -21730,7 +22597,7 @@ function createLaunchdLifecycle(def, deps = {}) {
|
|
|
21730
22597
|
}
|
|
21731
22598
|
async function uninstall() {
|
|
21732
22599
|
await runIdempotent(run, "launchctl", ["bootout", serviceTarget()], "launchctl bootout", LAUNCHD_NOT_LOADED);
|
|
21733
|
-
await
|
|
22600
|
+
await fs33.rm(plistPath(), { force: true });
|
|
21734
22601
|
}
|
|
21735
22602
|
async function start() {
|
|
21736
22603
|
if (!await fileExists(plistPath())) {
|
|
@@ -21814,14 +22681,14 @@ WantedBy=default.target
|
|
|
21814
22681
|
}
|
|
21815
22682
|
function createSystemdLifecycle(def, deps = {}) {
|
|
21816
22683
|
const run = deps.run ?? defaultRunner;
|
|
21817
|
-
const
|
|
22684
|
+
const fs33 = deps.fs ?? promises;
|
|
21818
22685
|
const homedir = deps.homedir ?? (() => os4__default.homedir());
|
|
21819
22686
|
const name = sanitizeServiceName(def.name);
|
|
21820
22687
|
const unitName = `${name}.service`;
|
|
21821
22688
|
const unitPath = () => path3__default.join(homedir(), ".config", "systemd", "user", unitName);
|
|
21822
22689
|
async function fileExists(p) {
|
|
21823
22690
|
try {
|
|
21824
|
-
await
|
|
22691
|
+
await fs33.stat(p);
|
|
21825
22692
|
return true;
|
|
21826
22693
|
} catch {
|
|
21827
22694
|
return false;
|
|
@@ -21829,9 +22696,9 @@ function createSystemdLifecycle(def, deps = {}) {
|
|
|
21829
22696
|
}
|
|
21830
22697
|
async function writeUnit(program) {
|
|
21831
22698
|
const unit = generateSystemdUnit({ name, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
|
|
21832
|
-
await
|
|
21833
|
-
await
|
|
21834
|
-
await
|
|
22699
|
+
await fs33.mkdir(path3__default.dirname(unitPath()), { recursive: true });
|
|
22700
|
+
await fs33.mkdir(def.logDir, { recursive: true });
|
|
22701
|
+
await fs33.writeFile(unitPath(), unit, "utf8");
|
|
21835
22702
|
}
|
|
21836
22703
|
async function install(opts = {}) {
|
|
21837
22704
|
await writeUnit(opts.program ?? def.program);
|
|
@@ -21840,7 +22707,7 @@ function createSystemdLifecycle(def, deps = {}) {
|
|
|
21840
22707
|
}
|
|
21841
22708
|
async function uninstall() {
|
|
21842
22709
|
await runIdempotent(run, "systemctl", ["--user", "disable", "--now", unitName], "systemctl disable --now", SYSTEMD_NOT_LOADED);
|
|
21843
|
-
await
|
|
22710
|
+
await fs33.rm(unitPath(), { force: true });
|
|
21844
22711
|
await run("systemctl", ["--user", "daemon-reload"]);
|
|
21845
22712
|
}
|
|
21846
22713
|
async function start() {
|
|
@@ -21903,7 +22770,7 @@ ${argXml}${cwdXml}
|
|
|
21903
22770
|
}
|
|
21904
22771
|
function createWinswLifecycle(def, deps = {}) {
|
|
21905
22772
|
const run = deps.run ?? defaultRunner;
|
|
21906
|
-
const
|
|
22773
|
+
const fs33 = deps.fs ?? promises;
|
|
21907
22774
|
const windows = def.windows;
|
|
21908
22775
|
if (!windows) {
|
|
21909
22776
|
throw new Error("WinSW service lifecycle requires `ServiceDefinition.windows.winswBin` (the product-bundled WinSW executable path)");
|
|
@@ -21915,7 +22782,7 @@ function createWinswLifecycle(def, deps = {}) {
|
|
|
21915
22782
|
const xmlPath = path3__default.join(installDir, `${id}.xml`);
|
|
21916
22783
|
async function fileExists(p) {
|
|
21917
22784
|
try {
|
|
21918
|
-
await
|
|
22785
|
+
await fs33.stat(p);
|
|
21919
22786
|
return true;
|
|
21920
22787
|
} catch {
|
|
21921
22788
|
return false;
|
|
@@ -21923,10 +22790,10 @@ function createWinswLifecycle(def, deps = {}) {
|
|
|
21923
22790
|
}
|
|
21924
22791
|
async function writeFiles(program) {
|
|
21925
22792
|
const xml = generateWinswXml({ id, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
|
|
21926
|
-
await
|
|
21927
|
-
await
|
|
21928
|
-
await
|
|
21929
|
-
await
|
|
22793
|
+
await fs33.mkdir(installDir, { recursive: true });
|
|
22794
|
+
await fs33.mkdir(def.logDir, { recursive: true });
|
|
22795
|
+
await fs33.copyFile(winswBin, exePath);
|
|
22796
|
+
await fs33.writeFile(xmlPath, xml, "utf8");
|
|
21930
22797
|
}
|
|
21931
22798
|
async function install(opts = {}) {
|
|
21932
22799
|
await writeFiles(opts.program ?? def.program);
|
|
@@ -21936,8 +22803,8 @@ function createWinswLifecycle(def, deps = {}) {
|
|
|
21936
22803
|
async function uninstall() {
|
|
21937
22804
|
await runIdempotent(run, exePath, ["stop"], "winsw stop", WINSW_NOT_INSTALLED);
|
|
21938
22805
|
await runIdempotent(run, exePath, ["uninstall"], "winsw uninstall", WINSW_NOT_INSTALLED);
|
|
21939
|
-
await
|
|
21940
|
-
await
|
|
22806
|
+
await fs33.rm(exePath, { force: true });
|
|
22807
|
+
await fs33.rm(xmlPath, { force: true });
|
|
21941
22808
|
}
|
|
21942
22809
|
async function start() {
|
|
21943
22810
|
if (!await fileExists(xmlPath)) {
|
|
@@ -21980,6 +22847,658 @@ function createServiceLifecycle(def, opts = {}) {
|
|
|
21980
22847
|
}
|
|
21981
22848
|
}
|
|
21982
22849
|
|
|
21983
|
-
|
|
22850
|
+
// src/bin/runtime-probe.ts
|
|
22851
|
+
var ALL_RUNTIME_IDS2 = ["pi", "claude", "codex"];
|
|
22852
|
+
var RUNTIME_PROBE_TIMEOUT_MS = 5e3;
|
|
22853
|
+
var MAX_RUNTIME_ID_CHARS = 128;
|
|
22854
|
+
var MAX_RUNTIME_VERSION_CHARS = 256;
|
|
22855
|
+
var MAX_PERMISSION_MODES = 32;
|
|
22856
|
+
var MAX_PERMISSION_MODE_CHARS = 64;
|
|
22857
|
+
function boundedSingleLine(value, maxChars) {
|
|
22858
|
+
return value.replace(/[\r\n\t]/g, " ").slice(0, maxChars);
|
|
22859
|
+
}
|
|
22860
|
+
var RuntimeProbeTimeout = class extends Error {
|
|
22861
|
+
};
|
|
22862
|
+
async function detectWithTimeout(adapter, timeoutMs) {
|
|
22863
|
+
let timer;
|
|
22864
|
+
try {
|
|
22865
|
+
return await Promise.race([
|
|
22866
|
+
adapter.detect(),
|
|
22867
|
+
new Promise((_resolve, reject) => {
|
|
22868
|
+
timer = setTimeout(() => reject(new RuntimeProbeTimeout()), timeoutMs);
|
|
22869
|
+
timer.unref?.();
|
|
22870
|
+
})
|
|
22871
|
+
]);
|
|
22872
|
+
} finally {
|
|
22873
|
+
if (timer) clearTimeout(timer);
|
|
22874
|
+
}
|
|
22875
|
+
}
|
|
22876
|
+
function defaultRuntimeAdapters(runtimeAllowlist) {
|
|
22877
|
+
const ids = runtimeAllowlist ? ALL_RUNTIME_IDS2.filter((id) => runtimeAllowlist.includes(id)) : ALL_RUNTIME_IDS2;
|
|
22878
|
+
return ids.map((id) => {
|
|
22879
|
+
switch (id) {
|
|
22880
|
+
case "pi":
|
|
22881
|
+
return new PiAdapter();
|
|
22882
|
+
case "claude":
|
|
22883
|
+
return new ClaudeAdapter();
|
|
22884
|
+
case "codex":
|
|
22885
|
+
return new CodexAdapter();
|
|
22886
|
+
}
|
|
22887
|
+
});
|
|
22888
|
+
}
|
|
22889
|
+
async function probeRuntimes(adapters, options = {}) {
|
|
22890
|
+
const timeoutMs = options.timeoutMs ?? RUNTIME_PROBE_TIMEOUT_MS;
|
|
22891
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error("runtime probe timeout must be a positive integer");
|
|
22892
|
+
return Promise.all(
|
|
22893
|
+
adapters.map(async (adapter) => {
|
|
22894
|
+
let id = "unavailable";
|
|
22895
|
+
let steer = false;
|
|
22896
|
+
let resume = false;
|
|
22897
|
+
let permissionModes = [];
|
|
22898
|
+
try {
|
|
22899
|
+
id = boundedSingleLine(adapter.descriptor.id, MAX_RUNTIME_ID_CHARS);
|
|
22900
|
+
const caps = adapter.descriptor.capabilities;
|
|
22901
|
+
steer = caps.steer === true;
|
|
22902
|
+
resume = caps.resume === true;
|
|
22903
|
+
permissionModes = caps.permissionModes.slice(0, MAX_PERMISSION_MODES).map((mode) => boundedSingleLine(mode, MAX_PERMISSION_MODE_CHARS));
|
|
22904
|
+
const detected = validateRuntimeDetectResult(await detectWithTimeout(adapter, timeoutMs));
|
|
22905
|
+
return {
|
|
22906
|
+
id,
|
|
22907
|
+
present: detected.kind === "available",
|
|
22908
|
+
outcome: detected.kind,
|
|
22909
|
+
...detected.kind !== "available" || detected.version === void 0 ? {} : { version: boundedSingleLine(detected.version, MAX_RUNTIME_VERSION_CHARS) },
|
|
22910
|
+
...detected.kind === "available" && typeof detected.authPresent === "boolean" ? { authPresent: detected.authPresent } : {},
|
|
22911
|
+
steer,
|
|
22912
|
+
resume,
|
|
22913
|
+
permissionModes
|
|
22914
|
+
};
|
|
22915
|
+
} catch (error) {
|
|
22916
|
+
return { id, present: false, outcome: error instanceof RuntimeProbeTimeout ? "timeout" : "probe-failed", steer, resume, permissionModes };
|
|
22917
|
+
}
|
|
22918
|
+
})
|
|
22919
|
+
);
|
|
22920
|
+
}
|
|
22921
|
+
|
|
22922
|
+
// src/diagnostics/diagnostics.ts
|
|
22923
|
+
var MAX_QUARANTINE_ENTRIES = 100;
|
|
22924
|
+
var MAX_QUARANTINE_SCAN_ENTRIES = MAX_QUARANTINE_ENTRIES * 2;
|
|
22925
|
+
var MAX_QUARANTINE_READ_BYTES = 128 * 1024 * 1024;
|
|
22926
|
+
var MAX_DEVICE_RECORD_BYTES2 = 64 * 1024;
|
|
22927
|
+
var MAX_JOURNAL_COPY_BYTES = 512 * 1024 * 1024;
|
|
22928
|
+
var MAX_QUARANTINE_MANIFEST_BYTES = 64 * 1024;
|
|
22929
|
+
function safeProtocol(serverUrl) {
|
|
22930
|
+
try {
|
|
22931
|
+
const protocol = new URL(serverUrl).protocol.replace(/:$/, "");
|
|
22932
|
+
return protocol === "http" || protocol === "https" || protocol === "ws" || protocol === "wss" ? protocol : "unsupported";
|
|
22933
|
+
} catch {
|
|
22934
|
+
return "invalid";
|
|
22935
|
+
}
|
|
22936
|
+
}
|
|
22937
|
+
async function inspectDevice(storeDir) {
|
|
22938
|
+
const filePath = path3__default.join(storeDir, "device.json");
|
|
22939
|
+
let pathStat;
|
|
22940
|
+
try {
|
|
22941
|
+
pathStat = await promises.lstat(filePath);
|
|
22942
|
+
if (!pathStat.isFile()) return { status: "unavailable" };
|
|
22943
|
+
} catch (err) {
|
|
22944
|
+
if (err.code === "ENOENT") return { status: "unpaired" };
|
|
22945
|
+
return { status: "unavailable" };
|
|
22946
|
+
}
|
|
22947
|
+
let handle;
|
|
22948
|
+
try {
|
|
22949
|
+
handle = await promises.open(
|
|
22950
|
+
filePath,
|
|
22951
|
+
constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
|
|
22952
|
+
);
|
|
22953
|
+
} catch (err) {
|
|
22954
|
+
if (err.code === "ENOENT") return { status: "unpaired" };
|
|
22955
|
+
return { status: "unavailable" };
|
|
22956
|
+
}
|
|
22957
|
+
try {
|
|
22958
|
+
const stat = await handle.stat();
|
|
22959
|
+
const namedAfterOpen = await promises.lstat(filePath);
|
|
22960
|
+
if (!stat.isFile() || !namedAfterOpen.isFile() || namedAfterOpen.isSymbolicLink() || stat.dev !== pathStat.dev || stat.ino !== pathStat.ino || stat.size !== pathStat.size || stat.mtimeMs !== pathStat.mtimeMs || stat.ctimeMs !== pathStat.ctimeMs || stat.dev !== namedAfterOpen.dev || stat.ino !== namedAfterOpen.ino || stat.size !== namedAfterOpen.size || stat.mtimeMs !== namedAfterOpen.mtimeMs || stat.ctimeMs !== namedAfterOpen.ctimeMs || stat.size <= 0 || stat.size > MAX_DEVICE_RECORD_BYTES2) return { status: "unavailable" };
|
|
22961
|
+
const bytes = Buffer.alloc(stat.size);
|
|
22962
|
+
const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
|
|
22963
|
+
const after = await handle.stat();
|
|
22964
|
+
const namedAfterRead = await promises.lstat(filePath);
|
|
22965
|
+
if (bytesRead !== bytes.length || after.dev !== stat.dev || after.ino !== stat.ino || after.size !== stat.size || after.mtimeMs !== stat.mtimeMs || after.ctimeMs !== stat.ctimeMs || namedAfterRead.dev !== stat.dev || namedAfterRead.ino !== stat.ino || namedAfterRead.size !== stat.size || namedAfterRead.mtimeMs !== stat.mtimeMs || namedAfterRead.ctimeMs !== stat.ctimeMs) {
|
|
22966
|
+
return { status: "unavailable" };
|
|
22967
|
+
}
|
|
22968
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
22969
|
+
return typeof parsed.deviceId === "string" && parsed.deviceId.length > 0 ? { status: "paired", deviceIdHash: stableIdentifierHash(parsed.deviceId) } : { status: "unavailable" };
|
|
22970
|
+
} catch {
|
|
22971
|
+
return { status: "unavailable" };
|
|
22972
|
+
} finally {
|
|
22973
|
+
await handle.close();
|
|
22974
|
+
}
|
|
22975
|
+
}
|
|
22976
|
+
async function regularFileIdentity(filePath) {
|
|
22977
|
+
try {
|
|
22978
|
+
const stat = await promises.lstat(filePath, { bigint: true });
|
|
22979
|
+
if (!stat.isFile()) throw new Error("journal component is not a regular file");
|
|
22980
|
+
return { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs };
|
|
22981
|
+
} catch (err) {
|
|
22982
|
+
if (err.code === "ENOENT") return void 0;
|
|
22983
|
+
throw err;
|
|
22984
|
+
}
|
|
22985
|
+
}
|
|
22986
|
+
function sameIdentity(left, right) {
|
|
22987
|
+
if (!left || !right) return left === right;
|
|
22988
|
+
return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
22989
|
+
}
|
|
22990
|
+
function identityFromBigIntStat(stat) {
|
|
22991
|
+
return { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs };
|
|
22992
|
+
}
|
|
22993
|
+
async function copyOpenFileBounded(source, expected, destinationPath) {
|
|
22994
|
+
const destination = await promises.open(destinationPath, "wx", 384);
|
|
22995
|
+
try {
|
|
22996
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
22997
|
+
const expectedBytes = Number(expected.size);
|
|
22998
|
+
let position = 0;
|
|
22999
|
+
while (position < expectedBytes) {
|
|
23000
|
+
const length = Math.min(buffer.length, expectedBytes - position);
|
|
23001
|
+
const { bytesRead } = await source.read(buffer, 0, length, position);
|
|
23002
|
+
if (bytesRead === 0) return false;
|
|
23003
|
+
let written = 0;
|
|
23004
|
+
while (written < bytesRead) {
|
|
23005
|
+
const result = await destination.write(buffer, written, bytesRead - written, position + written);
|
|
23006
|
+
if (result.bytesWritten === 0) throw new Error("diagnostics snapshot destination stopped accepting bytes");
|
|
23007
|
+
written += result.bytesWritten;
|
|
23008
|
+
}
|
|
23009
|
+
position += bytesRead;
|
|
23010
|
+
}
|
|
23011
|
+
await destination.sync();
|
|
23012
|
+
return sameIdentity(expected, identityFromBigIntStat(await source.stat({ bigint: true })));
|
|
23013
|
+
} finally {
|
|
23014
|
+
await destination.close();
|
|
23015
|
+
}
|
|
23016
|
+
}
|
|
23017
|
+
async function inspectJournal(storeDir) {
|
|
23018
|
+
const journalPath = path3__default.join(storeDir, JOURNAL_DB_FILENAME);
|
|
23019
|
+
try {
|
|
23020
|
+
const mainIdentity = await regularFileIdentity(journalPath);
|
|
23021
|
+
if (mainIdentity === void 0) return { status: "missing" };
|
|
23022
|
+
const walIdentity = await regularFileIdentity(path3__default.join(storeDir, `${JOURNAL_DB_FILENAME}-wal`));
|
|
23023
|
+
let sizeBytes = Number(mainIdentity.size);
|
|
23024
|
+
let walBytes = walIdentity === void 0 ? void 0 : Number(walIdentity.size);
|
|
23025
|
+
if (!isSqliteAvailable()) {
|
|
23026
|
+
return { status: "present", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, integrity: "not-checked" };
|
|
23027
|
+
}
|
|
23028
|
+
const componentNames = [JOURNAL_DB_FILENAME, `${JOURNAL_DB_FILENAME}-wal`, `${JOURNAL_DB_FILENAME}-shm`];
|
|
23029
|
+
const initial = /* @__PURE__ */ new Map();
|
|
23030
|
+
for (const name of componentNames) initial.set(name, await regularFileIdentity(path3__default.join(storeDir, name)));
|
|
23031
|
+
const snapshotMain = initial.get(JOURNAL_DB_FILENAME);
|
|
23032
|
+
if (!snapshotMain) return { status: "unavailable", reason: "journal changed during diagnostics snapshot" };
|
|
23033
|
+
sizeBytes = Number(snapshotMain.size);
|
|
23034
|
+
walBytes = initial.get(`${JOURNAL_DB_FILENAME}-wal`) === void 0 ? void 0 : Number(initial.get(`${JOURNAL_DB_FILENAME}-wal`).size);
|
|
23035
|
+
const opened = /* @__PURE__ */ new Map();
|
|
23036
|
+
let totalBytes = 0n;
|
|
23037
|
+
try {
|
|
23038
|
+
for (const name of componentNames) {
|
|
23039
|
+
let handle;
|
|
23040
|
+
try {
|
|
23041
|
+
handle = await promises.open(
|
|
23042
|
+
path3__default.join(storeDir, name),
|
|
23043
|
+
constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
|
|
23044
|
+
);
|
|
23045
|
+
} catch (err) {
|
|
23046
|
+
if (err.code === "ENOENT" && name !== JOURNAL_DB_FILENAME) continue;
|
|
23047
|
+
throw err;
|
|
23048
|
+
}
|
|
23049
|
+
const stat = await handle.stat({ bigint: true });
|
|
23050
|
+
if (!stat.isFile()) {
|
|
23051
|
+
await handle.close();
|
|
23052
|
+
throw new Error("journal component is not a regular file");
|
|
23053
|
+
}
|
|
23054
|
+
const identity = identityFromBigIntStat(stat);
|
|
23055
|
+
if (!sameIdentity(initial.get(name), identity)) {
|
|
23056
|
+
await handle.close();
|
|
23057
|
+
return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
|
|
23058
|
+
}
|
|
23059
|
+
totalBytes += identity.size;
|
|
23060
|
+
opened.set(name, { handle, identity });
|
|
23061
|
+
}
|
|
23062
|
+
if (totalBytes > BigInt(MAX_JOURNAL_COPY_BYTES)) {
|
|
23063
|
+
return {
|
|
23064
|
+
status: "present",
|
|
23065
|
+
sizeBytes,
|
|
23066
|
+
...walBytes === void 0 ? {} : { walBytes },
|
|
23067
|
+
integrity: "not-checked",
|
|
23068
|
+
reason: "journal exceeds the bounded diagnostics copy limit"
|
|
23069
|
+
};
|
|
23070
|
+
}
|
|
23071
|
+
const tempDir = await promises.mkdtemp(path3__default.join(os4__default.tmpdir(), "byok-journal-inspect-"));
|
|
23072
|
+
const { DatabaseSync } = loadSqliteModule();
|
|
23073
|
+
let db;
|
|
23074
|
+
try {
|
|
23075
|
+
for (const name of componentNames) {
|
|
23076
|
+
const component = opened.get(name);
|
|
23077
|
+
if (component && !await copyOpenFileBounded(component.handle, component.identity, path3__default.join(tempDir, name))) {
|
|
23078
|
+
return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
|
|
23079
|
+
}
|
|
23080
|
+
}
|
|
23081
|
+
for (const [name, component] of opened) {
|
|
23082
|
+
if (!sameIdentity(component.identity, identityFromBigIntStat(await component.handle.stat({ bigint: true }))) || !sameIdentity(component.identity, await regularFileIdentity(path3__default.join(storeDir, name)))) {
|
|
23083
|
+
return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
|
|
23084
|
+
}
|
|
23085
|
+
}
|
|
23086
|
+
for (const name of componentNames) {
|
|
23087
|
+
if (!opened.has(name) && await regularFileIdentity(path3__default.join(storeDir, name)) !== void 0) {
|
|
23088
|
+
return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
|
|
23089
|
+
}
|
|
23090
|
+
}
|
|
23091
|
+
const header = Buffer.alloc(16);
|
|
23092
|
+
const copiedHandle = await promises.open(path3__default.join(tempDir, JOURNAL_DB_FILENAME), "r");
|
|
23093
|
+
try {
|
|
23094
|
+
const { bytesRead } = await copiedHandle.read(header, 0, header.length, 0);
|
|
23095
|
+
if (bytesRead !== 16 || header.toString("binary") !== "SQLite format 3\0") {
|
|
23096
|
+
return { status: "corrupt", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal has an invalid SQLite header" };
|
|
23097
|
+
}
|
|
23098
|
+
} finally {
|
|
23099
|
+
await copiedHandle.close();
|
|
23100
|
+
}
|
|
23101
|
+
db = new DatabaseSync(path3__default.join(tempDir, JOURNAL_DB_FILENAME), { readOnly: true });
|
|
23102
|
+
const result = db.prepare("PRAGMA quick_check(1)").get();
|
|
23103
|
+
if (result?.quick_check !== "ok") {
|
|
23104
|
+
return { status: "corrupt", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal quick_check failed" };
|
|
23105
|
+
}
|
|
23106
|
+
return { status: "present", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, integrity: "ok" };
|
|
23107
|
+
} catch {
|
|
23108
|
+
return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal snapshot could not be checked" };
|
|
23109
|
+
} finally {
|
|
23110
|
+
db?.close();
|
|
23111
|
+
await promises.rm(tempDir, { recursive: true, force: true });
|
|
23112
|
+
}
|
|
23113
|
+
} finally {
|
|
23114
|
+
await Promise.all([...opened.values()].map(({ handle }) => handle.close().catch(() => void 0)));
|
|
23115
|
+
}
|
|
23116
|
+
} catch {
|
|
23117
|
+
return { status: "unavailable", reason: "journal files could not be inspected" };
|
|
23118
|
+
}
|
|
23119
|
+
}
|
|
23120
|
+
async function inspectWorkspace(workspaceRoot) {
|
|
23121
|
+
try {
|
|
23122
|
+
const stat = await promises.stat(workspaceRoot);
|
|
23123
|
+
if (!stat.isDirectory()) return { status: "unavailable", reason: "workspace root is not a directory" };
|
|
23124
|
+
try {
|
|
23125
|
+
await promises.access(workspaceRoot, constants.R_OK | constants.W_OK);
|
|
23126
|
+
return { status: "available", writable: true };
|
|
23127
|
+
} catch {
|
|
23128
|
+
return { status: "available", writable: false };
|
|
23129
|
+
}
|
|
23130
|
+
} catch (err) {
|
|
23131
|
+
if (err.code === "ENOENT") return { status: "missing" };
|
|
23132
|
+
return { status: "unavailable", reason: "workspace root could not be inspected" };
|
|
23133
|
+
}
|
|
23134
|
+
}
|
|
23135
|
+
function readPinnedQuarantineFile(name, maxBytes, includeBytes, budget) {
|
|
23136
|
+
if (path3__default.basename(name) !== name || name === "." || name === "..") {
|
|
23137
|
+
throw new Error("quarantine manifest contains an invalid evidence name");
|
|
23138
|
+
}
|
|
23139
|
+
const namedBefore = lstatSync(name, { bigint: true });
|
|
23140
|
+
if (!namedBefore.isFile() || namedBefore.isSymbolicLink()) {
|
|
23141
|
+
throw new Error("quarantine entry is not a real regular file");
|
|
23142
|
+
}
|
|
23143
|
+
const fd = openSync(name, constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0));
|
|
23144
|
+
try {
|
|
23145
|
+
const opened = fstatSync(fd, { bigint: true });
|
|
23146
|
+
if (!opened.isFile() || !sameInode2(opened, namedBefore) || opened.size < 0 || opened.size > BigInt(maxBytes)) {
|
|
23147
|
+
throw new Error("quarantine entry could not be opened as a bounded regular file");
|
|
23148
|
+
}
|
|
23149
|
+
let bytes;
|
|
23150
|
+
if (includeBytes) {
|
|
23151
|
+
const sizeBytes = Number(opened.size);
|
|
23152
|
+
if (sizeBytes > budget.remainingBytes) {
|
|
23153
|
+
throw new Error("quarantine validation exceeds the 128 MiB cumulative read limit");
|
|
23154
|
+
}
|
|
23155
|
+
budget.remainingBytes -= sizeBytes;
|
|
23156
|
+
bytes = Buffer.alloc(sizeBytes);
|
|
23157
|
+
let position = 0;
|
|
23158
|
+
while (position < bytes.length) {
|
|
23159
|
+
const bytesRead = readSync(fd, bytes, position, bytes.length - position, position);
|
|
23160
|
+
if (bytesRead === 0) throw new Error("quarantine entry ended during bounded read");
|
|
23161
|
+
position += bytesRead;
|
|
23162
|
+
}
|
|
23163
|
+
}
|
|
23164
|
+
const openedAfter = fstatSync(fd, { bigint: true });
|
|
23165
|
+
const namedAfter = lstatSync(name, { bigint: true });
|
|
23166
|
+
if (!sameFileState5(openedAfter, opened) || !sameFileState5(namedAfter, opened)) {
|
|
23167
|
+
throw new Error("quarantine entry changed during inspection");
|
|
23168
|
+
}
|
|
23169
|
+
return {
|
|
23170
|
+
sizeBytes: Number(opened.size),
|
|
23171
|
+
modifiedAt: new Date(Number(opened.mtimeMs)).toISOString(),
|
|
23172
|
+
...bytes === void 0 ? {} : { bytes }
|
|
23173
|
+
};
|
|
23174
|
+
} finally {
|
|
23175
|
+
closeSync(fd);
|
|
23176
|
+
}
|
|
23177
|
+
}
|
|
23178
|
+
function isHealthQuarantineManifest(value) {
|
|
23179
|
+
if (typeof value !== "object" || value === null) return false;
|
|
23180
|
+
const candidate = value;
|
|
23181
|
+
return candidate.version === 1 && typeof candidate.quarantinedAt === "string" && Number.isFinite(Date.parse(candidate.quarantinedAt)) && typeof candidate.reason === "string" && typeof candidate.sourcePath === "string" && typeof candidate.evidenceFile === "string" && typeof candidate.sha256 === "string" && /^[0-9a-f]{64}$/.test(candidate.sha256) && Number.isSafeInteger(candidate.sizeBytes) && candidate.sizeBytes >= 0 && candidate.sizeBytes <= MAX_OPERATIONAL_HEALTH_FILE_BYTES;
|
|
23182
|
+
}
|
|
23183
|
+
function isJournalQuarantineManifest(value) {
|
|
23184
|
+
if (typeof value !== "object" || value === null) return false;
|
|
23185
|
+
const candidate = value;
|
|
23186
|
+
return typeof candidate.quarantinedAt === "string" && Number.isFinite(Date.parse(candidate.quarantinedAt)) && typeof candidate.reason === "string" && typeof candidate.originalPath === "string" && Array.isArray(candidate.files) && candidate.files.length > 0 && candidate.files.length <= 3 && candidate.files.every((file) => typeof file === "string");
|
|
23187
|
+
}
|
|
23188
|
+
function inspectQuarantinePinned(dir, expectedDirectory) {
|
|
23189
|
+
const previousCwd = process.cwd();
|
|
23190
|
+
try {
|
|
23191
|
+
process.chdir(dir);
|
|
23192
|
+
const pinned = lstatSync(".", { bigint: true });
|
|
23193
|
+
if (!pinned.isDirectory() || !sameInode2(pinned, expectedDirectory)) {
|
|
23194
|
+
throw new Error("quarantine directory changed during inspection");
|
|
23195
|
+
}
|
|
23196
|
+
const selected = [];
|
|
23197
|
+
let scanTruncated = false;
|
|
23198
|
+
const directory = opendirSync(".");
|
|
23199
|
+
try {
|
|
23200
|
+
for (; ; ) {
|
|
23201
|
+
const entry = directory.readSync();
|
|
23202
|
+
if (!entry) break;
|
|
23203
|
+
if (selected.length === MAX_QUARANTINE_SCAN_ENTRIES) {
|
|
23204
|
+
scanTruncated = true;
|
|
23205
|
+
break;
|
|
23206
|
+
}
|
|
23207
|
+
selected.push(entry);
|
|
23208
|
+
}
|
|
23209
|
+
} finally {
|
|
23210
|
+
directory.closeSync();
|
|
23211
|
+
}
|
|
23212
|
+
const evidence = /* @__PURE__ */ new Map();
|
|
23213
|
+
const processedManifests = /* @__PURE__ */ new Set();
|
|
23214
|
+
const readBudget = { remainingBytes: MAX_QUARANTINE_READ_BYTES };
|
|
23215
|
+
const processManifest = (manifestName) => {
|
|
23216
|
+
if (processedManifests.has(manifestName)) return;
|
|
23217
|
+
processedManifests.add(manifestName);
|
|
23218
|
+
const manifestFile = readPinnedQuarantineFile(manifestName, MAX_QUARANTINE_MANIFEST_BYTES, true, readBudget);
|
|
23219
|
+
let parsed;
|
|
23220
|
+
try {
|
|
23221
|
+
parsed = JSON.parse(manifestFile.bytes?.toString("utf8") ?? "");
|
|
23222
|
+
} catch {
|
|
23223
|
+
throw new Error("quarantine manifest is invalid JSON");
|
|
23224
|
+
}
|
|
23225
|
+
if (isHealthQuarantineManifest(parsed)) {
|
|
23226
|
+
if (`${parsed.evidenceFile}.manifest.json` !== manifestName) {
|
|
23227
|
+
throw new Error("health quarantine manifest is not bound to its evidence name");
|
|
23228
|
+
}
|
|
23229
|
+
const file = readPinnedQuarantineFile(parsed.evidenceFile, MAX_OPERATIONAL_HEALTH_FILE_BYTES, true, readBudget);
|
|
23230
|
+
if (file.sizeBytes !== parsed.sizeBytes || createHash("sha256").update(file.bytes ?? Buffer.alloc(0)).digest("hex") !== parsed.sha256) {
|
|
23231
|
+
throw new Error("health quarantine evidence does not match its manifest");
|
|
23232
|
+
}
|
|
23233
|
+
if (evidence.has(parsed.evidenceFile)) throw new Error("quarantine evidence is referenced more than once");
|
|
23234
|
+
evidence.set(parsed.evidenceFile, file);
|
|
23235
|
+
return;
|
|
23236
|
+
}
|
|
23237
|
+
if (isJournalQuarantineManifest(parsed)) {
|
|
23238
|
+
const manifestBase = manifestName.slice(0, -".manifest.json".length);
|
|
23239
|
+
const boundNames = parsed.files.map((file) => {
|
|
23240
|
+
if (path3__default.dirname(path3__default.resolve(file)) !== path3__default.resolve(".")) {
|
|
23241
|
+
throw new Error("journal quarantine manifest points outside quarantine");
|
|
23242
|
+
}
|
|
23243
|
+
return path3__default.basename(file);
|
|
23244
|
+
});
|
|
23245
|
+
if (!boundNames.includes(manifestBase)) {
|
|
23246
|
+
throw new Error("journal quarantine manifest is not bound to its primary database evidence");
|
|
23247
|
+
}
|
|
23248
|
+
for (const name of boundNames) {
|
|
23249
|
+
if (evidence.has(name)) throw new Error("quarantine evidence is referenced more than once");
|
|
23250
|
+
evidence.set(name, readPinnedQuarantineFile(name, MAX_JOURNAL_COPY_BYTES, false, readBudget));
|
|
23251
|
+
}
|
|
23252
|
+
return;
|
|
23253
|
+
}
|
|
23254
|
+
throw new Error("quarantine manifest shape is invalid");
|
|
23255
|
+
};
|
|
23256
|
+
for (const entry of selected) {
|
|
23257
|
+
if (entry.name.endsWith(".manifest.json")) processManifest(entry.name);
|
|
23258
|
+
}
|
|
23259
|
+
for (const entry of selected) {
|
|
23260
|
+
if (!entry.name.endsWith(".manifest.json") && !evidence.has(entry.name)) {
|
|
23261
|
+
const primaryName = entry.name.endsWith("-wal") || entry.name.endsWith("-shm") ? entry.name.slice(0, -4) : entry.name;
|
|
23262
|
+
try {
|
|
23263
|
+
processManifest(`${primaryName}.manifest.json`);
|
|
23264
|
+
} catch {
|
|
23265
|
+
throw new Error("quarantine evidence has no valid manifest binding");
|
|
23266
|
+
}
|
|
23267
|
+
if (!evidence.has(entry.name)) throw new Error("quarantine evidence has no valid manifest binding");
|
|
23268
|
+
}
|
|
23269
|
+
}
|
|
23270
|
+
const entries = [...evidence.entries()].slice(0, MAX_QUARANTINE_ENTRIES).map(([name, file]) => ({ nameHash: stableIdentifierHash(name), sizeBytes: file.sizeBytes, modifiedAt: file.modifiedAt })).sort((a, b) => a.nameHash.localeCompare(b.nameHash));
|
|
23271
|
+
return {
|
|
23272
|
+
status: "available",
|
|
23273
|
+
count: evidence.size,
|
|
23274
|
+
scannedCount: selected.length,
|
|
23275
|
+
truncated: scanTruncated || evidence.size > entries.length,
|
|
23276
|
+
entries
|
|
23277
|
+
};
|
|
23278
|
+
} finally {
|
|
23279
|
+
process.chdir(previousCwd);
|
|
23280
|
+
}
|
|
23281
|
+
}
|
|
23282
|
+
async function inspectQuarantine(storeDir) {
|
|
23283
|
+
const dir = path3__default.join(storeDir, JOURNAL_QUARANTINE_DIRNAME);
|
|
23284
|
+
let directory;
|
|
23285
|
+
try {
|
|
23286
|
+
directory = await promises.lstat(dir, { bigint: true });
|
|
23287
|
+
} catch (err) {
|
|
23288
|
+
if (err.code === "ENOENT") {
|
|
23289
|
+
return { status: "missing", count: 0, scannedCount: 0, truncated: false, entries: [] };
|
|
23290
|
+
}
|
|
23291
|
+
return {
|
|
23292
|
+
status: "unavailable",
|
|
23293
|
+
count: 0,
|
|
23294
|
+
scannedCount: 0,
|
|
23295
|
+
truncated: false,
|
|
23296
|
+
entries: [],
|
|
23297
|
+
reason: "quarantine directory could not be inspected"
|
|
23298
|
+
};
|
|
23299
|
+
}
|
|
23300
|
+
if (!directory.isDirectory() || directory.isSymbolicLink()) {
|
|
23301
|
+
return { status: "unavailable", count: 0, scannedCount: 0, truncated: false, entries: [], reason: "quarantine path is not a real directory" };
|
|
23302
|
+
}
|
|
23303
|
+
try {
|
|
23304
|
+
return inspectQuarantinePinned(dir, directory);
|
|
23305
|
+
} catch {
|
|
23306
|
+
return { status: "unavailable", count: 0, scannedCount: 0, truncated: false, entries: [], reason: "quarantine manifest or evidence binding is invalid" };
|
|
23307
|
+
}
|
|
23308
|
+
}
|
|
23309
|
+
function checksFor(snapshot) {
|
|
23310
|
+
return [
|
|
23311
|
+
{
|
|
23312
|
+
id: "config",
|
|
23313
|
+
status: snapshot.config.serverProtocol === "invalid" ? "fail" : "pass",
|
|
23314
|
+
summary: `server protocol=${snapshot.config.serverProtocol}; hostedJournal=${snapshot.config.hostedJournal}`
|
|
23315
|
+
},
|
|
23316
|
+
{
|
|
23317
|
+
id: "device",
|
|
23318
|
+
status: snapshot.device.status === "unavailable" ? "fail" : snapshot.device.status === "unpaired" ? "warn" : "pass",
|
|
23319
|
+
summary: snapshot.device.status
|
|
23320
|
+
},
|
|
23321
|
+
{
|
|
23322
|
+
id: "runtimes",
|
|
23323
|
+
status: snapshot.runtimes.some((runtime) => runtime.present) ? "pass" : "warn",
|
|
23324
|
+
summary: `${snapshot.runtimes.filter((runtime) => runtime.present).length}/${snapshot.runtimes.length} present; ` + RUNTIME_DETECTION_FAILURE_KINDS.map((kind) => `${kind}=${snapshot.runtimes.filter((runtime) => runtime.outcome === kind).length}`).join(" ")
|
|
23325
|
+
},
|
|
23326
|
+
{
|
|
23327
|
+
id: "control",
|
|
23328
|
+
status: snapshot.control.status === "online" ? "pass" : "warn",
|
|
23329
|
+
summary: snapshot.control.status === "online" ? `online transport=${snapshot.control.transport}` : "daemon offline"
|
|
23330
|
+
},
|
|
23331
|
+
{
|
|
23332
|
+
id: "health",
|
|
23333
|
+
status: snapshot.health.status === "corrupt" || snapshot.health.status === "unavailable" ? "fail" : snapshot.health.status === "missing" ? "warn" : "pass",
|
|
23334
|
+
summary: snapshot.health.status
|
|
23335
|
+
},
|
|
23336
|
+
{
|
|
23337
|
+
id: "journal",
|
|
23338
|
+
status: snapshot.journal.status === "unavailable" || snapshot.journal.status === "corrupt" ? "fail" : snapshot.journal.status === "missing" ? "warn" : "pass",
|
|
23339
|
+
summary: snapshot.journal.status
|
|
23340
|
+
},
|
|
23341
|
+
{
|
|
23342
|
+
id: "workspace",
|
|
23343
|
+
status: snapshot.workspace.status === "unavailable" ? "fail" : snapshot.workspace.status === "missing" || snapshot.workspace.writable === false ? "warn" : "pass",
|
|
23344
|
+
summary: `${snapshot.workspace.status}${snapshot.workspace.writable === false ? "; read-only" : ""}`
|
|
23345
|
+
},
|
|
23346
|
+
{
|
|
23347
|
+
id: "quarantine",
|
|
23348
|
+
status: snapshot.quarantine.status === "unavailable" ? "fail" : snapshot.quarantine.count > 0 ? "warn" : "pass",
|
|
23349
|
+
summary: `${snapshot.quarantine.count} evidence file(s)`
|
|
23350
|
+
}
|
|
23351
|
+
];
|
|
23352
|
+
}
|
|
23353
|
+
async function collectDiagnostics(config, storeDir, options = {}) {
|
|
23354
|
+
const resolvedStoreDir = path3__default.resolve(storeDir);
|
|
23355
|
+
const adapters = options.adapters ?? defaultRuntimeAdapters(config.runtimeAllowlist);
|
|
23356
|
+
const connectControl = options.connectControl ?? connectControlClient;
|
|
23357
|
+
const [device, probedRuntimes, health, journal, workspace, quarantine, controlConnection] = await Promise.all([
|
|
23358
|
+
inspectDevice(resolvedStoreDir),
|
|
23359
|
+
probeRuntimes(adapters, { timeoutMs: options.runtimeProbeTimeoutMs }),
|
|
23360
|
+
inspectOperationalHealthFile(resolvedStoreDir),
|
|
23361
|
+
inspectJournal(resolvedStoreDir),
|
|
23362
|
+
inspectWorkspace(config.workspaceRoot),
|
|
23363
|
+
inspectQuarantine(resolvedStoreDir),
|
|
23364
|
+
connectControl({ storeDir: resolvedStoreDir, productId: config.productId })
|
|
23365
|
+
]);
|
|
23366
|
+
const runtimes = probedRuntimes.map((runtime) => ({
|
|
23367
|
+
idHash: stableIdentifierHash(runtime.id),
|
|
23368
|
+
present: runtime.present,
|
|
23369
|
+
outcome: runtime.outcome,
|
|
23370
|
+
versionPresent: runtime.version !== void 0,
|
|
23371
|
+
...runtime.authPresent === void 0 ? {} : { authPresent: runtime.authPresent },
|
|
23372
|
+
steer: runtime.steer,
|
|
23373
|
+
resume: runtime.resume,
|
|
23374
|
+
permissionModeCount: runtime.permissionModes.length
|
|
23375
|
+
}));
|
|
23376
|
+
let control;
|
|
23377
|
+
if (!controlConnection.ok) {
|
|
23378
|
+
control = { status: "offline", reason: "daemon control socket unavailable" };
|
|
23379
|
+
} else {
|
|
23380
|
+
try {
|
|
23381
|
+
const live = await controlConnection.client.request("status");
|
|
23382
|
+
control = {
|
|
23383
|
+
status: "online",
|
|
23384
|
+
pid: live.pid,
|
|
23385
|
+
uptimeMs: live.uptimeMs,
|
|
23386
|
+
transport: live.transport,
|
|
23387
|
+
activeTaskCount: live.activeTasks.length,
|
|
23388
|
+
pendingApprovalCount: live.approvalsPending,
|
|
23389
|
+
operationalHealth: live.operationalHealth,
|
|
23390
|
+
...live.storage ? { storage: live.storage } : {}
|
|
23391
|
+
};
|
|
23392
|
+
} catch {
|
|
23393
|
+
control = { status: "offline", reason: "daemon status request failed" };
|
|
23394
|
+
} finally {
|
|
23395
|
+
controlConnection.client.close();
|
|
23396
|
+
}
|
|
23397
|
+
}
|
|
23398
|
+
const withoutChecks = {
|
|
23399
|
+
version: 1,
|
|
23400
|
+
generatedAt: (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
23401
|
+
product: { nameHash: stableIdentifierHash(config.productName), idHash: stableIdentifierHash(config.productId) },
|
|
23402
|
+
system: { node: process.versions.node, platform: process.platform, arch: process.arch, sqliteAvailable: isSqliteAvailable() },
|
|
23403
|
+
config: {
|
|
23404
|
+
serverProtocol: safeProtocol(config.serverUrl),
|
|
23405
|
+
customStoreDir: config.storeDir !== void 0,
|
|
23406
|
+
hostedJournal: config.hostedJournal !== void 0,
|
|
23407
|
+
...config.runtimeAllowlist ? { runtimeAllowlistCount: config.runtimeAllowlist.length } : {}
|
|
23408
|
+
},
|
|
23409
|
+
device,
|
|
23410
|
+
runtimes,
|
|
23411
|
+
control,
|
|
23412
|
+
health,
|
|
23413
|
+
journal,
|
|
23414
|
+
workspace,
|
|
23415
|
+
quarantine
|
|
23416
|
+
};
|
|
23417
|
+
return { ...withoutChecks, checks: checksFor(withoutChecks) };
|
|
23418
|
+
}
|
|
23419
|
+
function stableIdentifierHash(value) {
|
|
23420
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
23421
|
+
}
|
|
23422
|
+
function sameInode2(left, right) {
|
|
23423
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
23424
|
+
}
|
|
23425
|
+
function sameFileState5(left, right) {
|
|
23426
|
+
return sameInode2(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
23427
|
+
}
|
|
23428
|
+
|
|
23429
|
+
// src/diagnostics/device-doctor.ts
|
|
23430
|
+
function diagnoseDevice(config, options = {}) {
|
|
23431
|
+
return collectDiagnostics(config, DeviceStore.resolveDir(config.productId, config.storeDir), {
|
|
23432
|
+
adapters: options.adapters,
|
|
23433
|
+
runtimeProbeTimeoutMs: options.runtimeProbeTimeoutMs
|
|
23434
|
+
});
|
|
23435
|
+
}
|
|
23436
|
+
var MESSAGES = {
|
|
23437
|
+
"confirmation-required": "Explicit confirmation is required for enrollment metadata repair.",
|
|
23438
|
+
"invalid-target": "An explicit expected tenant and device are required.",
|
|
23439
|
+
"daemon-running": "Stop the daemon before enrollment metadata repair.",
|
|
23440
|
+
"store-busy": "The store is owned by another operation; enrollment metadata repair refused.",
|
|
23441
|
+
"authority-unavailable": "The OS enrollment authority could not be read.",
|
|
23442
|
+
"authority-missing": "No complete OS enrollment exists; use explicit authenticated pairing.",
|
|
23443
|
+
"target-mismatch": "The OS enrollment does not match the expected tenant and device.",
|
|
23444
|
+
"projection-unavailable": "The enrollment metadata could not be read safely; repair refused.",
|
|
23445
|
+
"repair-failed": "Enrollment metadata repair did not complete; inspect the state before retrying."
|
|
23446
|
+
};
|
|
23447
|
+
var DeviceMetadataRepairError = class extends Error {
|
|
23448
|
+
constructor(code) {
|
|
23449
|
+
super(MESSAGES[code]);
|
|
23450
|
+
this.code = code;
|
|
23451
|
+
this.name = "DeviceMetadataRepairError";
|
|
23452
|
+
}
|
|
23453
|
+
code;
|
|
23454
|
+
};
|
|
23455
|
+
async function repairDeviceEnrollmentMetadata(config, input) {
|
|
23456
|
+
if (input?.confirmed !== true) throw new DeviceMetadataRepairError("confirmation-required");
|
|
23457
|
+
if (typeof input.expectedDeviceId !== "string" || input.expectedDeviceId.trim().length === 0 || !isTenantId(input.expectedTenantId)) {
|
|
23458
|
+
throw new DeviceMetadataRepairError("invalid-target");
|
|
23459
|
+
}
|
|
23460
|
+
const { expectedDeviceId, expectedTenantId } = input;
|
|
23461
|
+
const productId = config.productId;
|
|
23462
|
+
let failureCode = "repair-failed";
|
|
23463
|
+
try {
|
|
23464
|
+
const storeDir = DeviceStore.resolveDir(productId, config.storeDir);
|
|
23465
|
+
const control = await connectControlClient({ storeDir, productId });
|
|
23466
|
+
if (control.ok) {
|
|
23467
|
+
control.client.close();
|
|
23468
|
+
throw new DeviceMetadataRepairError("daemon-running");
|
|
23469
|
+
}
|
|
23470
|
+
const owner = await acquireDaemonOwner(storeDir, "doctor");
|
|
23471
|
+
try {
|
|
23472
|
+
const store = new DeviceStore(storeDir, void 0, productId);
|
|
23473
|
+
failureCode = "projection-unavailable";
|
|
23474
|
+
await store.load();
|
|
23475
|
+
failureCode = "authority-unavailable";
|
|
23476
|
+
const authority = await store.credentials.read();
|
|
23477
|
+
if (authority === void 0) throw new DeviceMetadataRepairError("authority-missing");
|
|
23478
|
+
if (authority.deviceId !== expectedDeviceId || authority.tenantId !== expectedTenantId) {
|
|
23479
|
+
throw new DeviceMetadataRepairError("target-mismatch");
|
|
23480
|
+
}
|
|
23481
|
+
failureCode = "repair-failed";
|
|
23482
|
+
const changed = await store.reconcileMetadata(authority);
|
|
23483
|
+
const readback = await store.load();
|
|
23484
|
+
if (readback === void 0 || readback.deviceId !== authority.deviceId || readback.tenantId !== authority.tenantId || readback.devicePublicKey !== authority.devicePublicKey) {
|
|
23485
|
+
throw new DeviceMetadataRepairError("repair-failed");
|
|
23486
|
+
}
|
|
23487
|
+
return { action: "restore-enrollment-metadata", scope: "device", status: changed ? "repaired" : "not-needed" };
|
|
23488
|
+
} catch (error) {
|
|
23489
|
+
if (error instanceof DeviceMetadataRepairError) throw error;
|
|
23490
|
+
throw new DeviceMetadataRepairError(failureCode);
|
|
23491
|
+
} finally {
|
|
23492
|
+
failureCode = "repair-failed";
|
|
23493
|
+
await owner.release();
|
|
23494
|
+
}
|
|
23495
|
+
} catch (error) {
|
|
23496
|
+
if (error instanceof DeviceMetadataRepairError) throw error;
|
|
23497
|
+
if (error instanceof DaemonOwnerActiveError) throw new DeviceMetadataRepairError("store-busy");
|
|
23498
|
+
throw new DeviceMetadataRepairError(failureCode);
|
|
23499
|
+
}
|
|
23500
|
+
}
|
|
23501
|
+
|
|
23502
|
+
export { AGENT_CONTENT_READ_CAPABILITIES, AGENT_CONTENT_READ_CAPABILITY_ARTIFACT, AGENT_CONTENT_READ_CAPABILITY_TRANSCRIPT, AGENT_CONTENT_READ_CAPABILITY_WORKSPACE, AGENT_HOME_PROJECTION_STATE_FILE, AGENT_MEMORY_AUDIT_FILENAME, AGENT_MEMORY_OUTBOX_FILENAME, AgentHomeBusyError, AgentHomeCollisionError, AgentHomeError, AgentHomeLayout, AgentHomeLeaseCorruptError, AgentHomeLeaseManager, AgentHomeManager, AgentHomeResolutionError, AgentMemoryError, AgentMemoryRevisionConflictError, AgentRefValidationError, AgentSessionHandoffCorruptError, AgentSessionHandoffMismatchError, AgentSessionHandoffStore, AgentSessionHandoffStoreError, BYOK_SDK_HELPER_SUBCOMMAND, BlobClient, BlobRequestAbortedError, ClaudeAdapter, CodexAdapter, DEFAULT_ACK_CRITICAL_RESERVE_BYTES, DEFAULT_CLEANUP_BATCH_LIMIT, DEFAULT_HARD_BUDGET_RATIO, DEFAULT_INCREMENTAL_VACUUM_PAGES, DEFAULT_LOG_ROTATION, DEFAULT_NORMAL_COMPACTION_INTERVAL_MS, DEFAULT_PRESSURE_COMPACTION_INTERVAL_MS, DEFAULT_RETENTION_MS, DEFAULT_SOFT_BUDGET_RATIO, DaemonObserver, DeviceMetadataRepairError, GitWorkspaceError, GitWorkspaceManager, GitWorkspaceStore, JOURNAL_DB_FILENAME, JOURNAL_QUARANTINE_DIRNAME, JOURNAL_TASK_REF_PREFIX, JournalClosedError, JournalCorruptError, JournalRecordTooLargeError, JournalUnavailableError, JournalUnknownTaskError, LocalStateRelocationBusyError, LocalStateRelocationError, LocalStateRelocationIntegrityError, LocalStorageEmergencyError, LocalStoragePolicyError, LocalStoragePressureEngine, LocalTeamWorkspace, LocalTeamWorkspace as LocalTeamWorkspaceService, McpToolsetDefinitionRevisionConflictError, McpToolsetRevisionConflictError, PI_PACKAGE_NAME, PiAdapter, PolicyUnsupportedError, RUNTIME_ADAPTER_CONTRACT_VIOLATION_REASON, ReplayCursorTooOldError, RuntimeDisposalFailure, RuntimeExecutionFailure, RuntimeStartupDisposalFailure, SKILL_PACKS_CAPABILITY, SKILL_PACKS_DIRNAME, SKILL_PACK_AUDIT_FILENAME, SKILL_PACK_INSTALL_ERROR_CODES, SKILL_PACK_LOCK_FILENAME, SKILL_PACK_LOCK_SCHEMA, SKILL_PACK_RESPONSE_MAX_BYTES, SecureDirHardeningError, SkillPackInstallError, SqliteLocalTaskJournal, SteerUnsupportedError, TeamTmuxViewError, TeamWorkspaceConflictError, TeamWorkspaceCorruptError, TeamWorkspaceError, TeamWorkspaceLeaseError, TeamWorkspaceNotFoundError, TeamWorkspaceQuotaError, TeamWorkspaceReceiptError, TeamWorkspaceValidationError, TruthMemoryClient, TruthMemoryClientError, UnsupportedServicePlatformError, buildIcaclsArgs, cleanupEligibleAt, cleanupOrderFor, computePressureState, createAgentHomeProjection, createAgentHomeProjectionConsumer, createDaemon, createDaemonWithAdapters, createFilesystemCleanupExecutor, createServiceLifecycle, createStatfsFreeBytesProvider, decodeTeamMemberContext, diagnoseDevice, encodeTeamMemberContext, ensureSecureDir, freezeRuntimeAdapterDescriptor, generateLaunchdPlist, generateSystemdUnit, generateWinswXml, installSkillPacks, isAgentMemorySecureFilesystemAvailable, isGitWorkspaceConfig, isRuntimeDisposalFailure, isRuntimeExecutionFailure, isRuntimeStartupDisposalFailure, journalHash, listInstalledSkillPacks, localStateRelocation, nodeAgentProgram, openTeamTmuxView, prependGitWorkspaceGuidance, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, projectSkillPack, readDeviceEnrollmentStatus, repairDeviceEnrollmentMetadata, requestDeviceAssertion, resolveLocalAgentReleaseIdentity, resolveLocalStoragePolicy, resolveSdkReservedHelperBin, runSdkReservedHelperCommand, sanitizeServiceName, sealRuntimeOperationManifest, skillPacksRoot, stableAgentHomeOwnerId, validateAgentRef };
|
|
21984
23503
|
//# sourceMappingURL=index.js.map
|
|
21985
23504
|
//# sourceMappingURL=index.js.map
|