@byok-sdk/client 0.11.0 → 0.12.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 -3
- package/dist/agent-home.d.ts +33 -0
- package/dist/agent-memory/index.js +8 -5
- package/dist/agent-memory/index.js.map +1 -1
- package/dist/bin/byok-agent.js +832 -177
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/daemon/agent-egress-controller.d.ts +4 -0
- package/dist/daemon/auth-manager.d.ts +20 -0
- package/dist/daemon/blob-client.d.ts +24 -6
- package/dist/daemon/connection-manager.d.ts +7 -0
- package/dist/daemon/create-daemon.d.ts +2 -0
- package/dist/daemon/device-credential-store.d.ts +16 -0
- package/dist/daemon/long-poll-transport.d.ts +3 -0
- package/dist/daemon/replay-cursor.d.ts +9 -0
- package/dist/daemon/task-runner.d.ts +4 -0
- package/dist/daemon/url.d.ts +7 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +832 -177
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID, createHash, randomBytes, createPrivateKey, generateKeyPairS
|
|
|
2
2
|
import { promises, mkdirSync, constants, existsSync, linkSync, fstatSync, lstatSync, unlinkSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, readFileSync, realpathSync } 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, 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, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, TERMINAL_PROJECTION_SELECTION_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, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, RuntimeIdSchema, AGENT_MEMORY_PROJECTION_CAPABILITY, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionMutationSchema, AGENT_MEMORY_PROJECTION_MAX_REDACTED_BYTES
|
|
5
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, AgentHomeProjectionPayloadSchema, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, 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, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, TERMINAL_PROJECTION_SELECTION_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, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, RuntimeIdSchema, AGENT_MEMORY_PROJECTION_CAPABILITY, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, BYOK_WS_PATH, 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';
|
|
@@ -746,6 +746,120 @@ var AgentHomeLeaseManager = class _AgentHomeLeaseManager {
|
|
|
746
746
|
}
|
|
747
747
|
}
|
|
748
748
|
};
|
|
749
|
+
function executionKey(input) {
|
|
750
|
+
const value = input.sessionRef === void 0 ? input.taskId : input.sessionRef;
|
|
751
|
+
const label = input.sessionRef === void 0 ? "taskId" : "sessionRef";
|
|
752
|
+
if (typeof value !== "string" || value.length === 0 || /[\u0000\r\n]/u.test(value)) {
|
|
753
|
+
throw new AgentHomeResolutionError(`Agent execution ${label} must be a non-empty single-line string`);
|
|
754
|
+
}
|
|
755
|
+
return `${input.sessionRef === void 0 ? "task" : "session"}\0${value}`;
|
|
756
|
+
}
|
|
757
|
+
var AgentHomeExecutionLeaseManager = class _AgentHomeExecutionLeaseManager {
|
|
758
|
+
constructor(manager) {
|
|
759
|
+
this.manager = manager;
|
|
760
|
+
}
|
|
761
|
+
manager;
|
|
762
|
+
static groups = /* @__PURE__ */ new Map();
|
|
763
|
+
static queues = /* @__PURE__ */ new Map();
|
|
764
|
+
async acquire(resolution, input) {
|
|
765
|
+
const initialKey = executionKey(input);
|
|
766
|
+
return this.exclusive(resolution.canonicalHome, async () => {
|
|
767
|
+
let group = _AgentHomeExecutionLeaseManager.groups.get(resolution.canonicalHome);
|
|
768
|
+
if (group === void 0) {
|
|
769
|
+
const baseLease = await this.manager.acquire(resolution);
|
|
770
|
+
group = {
|
|
771
|
+
manager: this.manager,
|
|
772
|
+
baseLease,
|
|
773
|
+
agentId: resolution.agentRef.agentId,
|
|
774
|
+
leasesByKey: /* @__PURE__ */ new Map()
|
|
775
|
+
};
|
|
776
|
+
_AgentHomeExecutionLeaseManager.groups.set(resolution.canonicalHome, group);
|
|
777
|
+
} else if (group.manager !== this.manager || group.agentId !== resolution.agentRef.agentId) {
|
|
778
|
+
throw new AgentHomeBusyError(`Agent home ${resolution.canonicalHome} is active under another execution owner`);
|
|
779
|
+
}
|
|
780
|
+
if (group.leasesByKey.has(initialKey)) {
|
|
781
|
+
throw new AgentHomeBusyError(`Agent session already has an active execution lease in ${resolution.canonicalHome}`);
|
|
782
|
+
}
|
|
783
|
+
const leaseId = randomUUID();
|
|
784
|
+
group.leasesByKey.set(initialKey, leaseId);
|
|
785
|
+
let currentKey = initialKey;
|
|
786
|
+
let sessionBound = input.sessionRef !== void 0;
|
|
787
|
+
let released = false;
|
|
788
|
+
return Object.freeze({
|
|
789
|
+
leaseId,
|
|
790
|
+
agentRef: resolution.agentRef,
|
|
791
|
+
canonicalHome: resolution.canonicalHome,
|
|
792
|
+
cwd: resolution.canonicalHome,
|
|
793
|
+
homeIdentity: group.baseLease.homeIdentity,
|
|
794
|
+
bindSession: async (sessionRef) => {
|
|
795
|
+
const nextKey = executionKey({ taskId: input.taskId, sessionRef });
|
|
796
|
+
await this.exclusive(resolution.canonicalHome, async () => {
|
|
797
|
+
if (released) throw new AgentHomeBusyError(`Agent execution lease ${leaseId} is already released`);
|
|
798
|
+
const currentGroup = _AgentHomeExecutionLeaseManager.groups.get(resolution.canonicalHome);
|
|
799
|
+
if (currentGroup !== group || currentGroup.leasesByKey.get(currentKey) !== leaseId) {
|
|
800
|
+
throw new AgentHomeBusyError(`Agent execution lease ${leaseId} is no longer owned by this process`);
|
|
801
|
+
}
|
|
802
|
+
if (nextKey === currentKey) return;
|
|
803
|
+
if (sessionBound) {
|
|
804
|
+
throw new AgentHomeBusyError(
|
|
805
|
+
`Agent session execution lease ${leaseId} cannot rebind to a different runtime session`
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
if (currentGroup.leasesByKey.has(nextKey)) {
|
|
809
|
+
throw new AgentHomeBusyError(`Agent session already has an active execution lease in ${resolution.canonicalHome}`);
|
|
810
|
+
}
|
|
811
|
+
currentGroup.leasesByKey.set(nextKey, leaseId);
|
|
812
|
+
currentGroup.leasesByKey.delete(currentKey);
|
|
813
|
+
currentKey = nextKey;
|
|
814
|
+
sessionBound = true;
|
|
815
|
+
});
|
|
816
|
+
},
|
|
817
|
+
release: async () => {
|
|
818
|
+
await this.exclusive(resolution.canonicalHome, async () => {
|
|
819
|
+
if (released) return;
|
|
820
|
+
const currentGroup = _AgentHomeExecutionLeaseManager.groups.get(resolution.canonicalHome);
|
|
821
|
+
if (currentGroup !== group || currentGroup.leasesByKey.get(currentKey) !== leaseId) {
|
|
822
|
+
released = true;
|
|
823
|
+
throw new AgentHomeBusyError(`Agent execution lease ${leaseId} is no longer owned by this process`);
|
|
824
|
+
}
|
|
825
|
+
currentGroup.leasesByKey.delete(currentKey);
|
|
826
|
+
released = true;
|
|
827
|
+
if (currentGroup.leasesByKey.size === 0) {
|
|
828
|
+
_AgentHomeExecutionLeaseManager.groups.delete(resolution.canonicalHome);
|
|
829
|
+
await currentGroup.baseLease.release();
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
async mutate(binding, operation) {
|
|
837
|
+
return this.exclusive(binding.resolution.canonicalHome, async () => {
|
|
838
|
+
const group = _AgentHomeExecutionLeaseManager.groups.get(binding.resolution.canonicalHome);
|
|
839
|
+
if (group === void 0 || group.manager !== this.manager || ![...group.leasesByKey.values()].includes(binding.lease.leaseId)) {
|
|
840
|
+
throw new AgentHomeBusyError("Agent execution lease does not own this home mutation");
|
|
841
|
+
}
|
|
842
|
+
return operation();
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
async exclusive(canonicalHome, operation) {
|
|
846
|
+
const prior = _AgentHomeExecutionLeaseManager.queues.get(canonicalHome) ?? Promise.resolve();
|
|
847
|
+
let release;
|
|
848
|
+
const tail = new Promise((resolve) => {
|
|
849
|
+
release = resolve;
|
|
850
|
+
});
|
|
851
|
+
_AgentHomeExecutionLeaseManager.queues.set(canonicalHome, tail);
|
|
852
|
+
await prior;
|
|
853
|
+
try {
|
|
854
|
+
return await operation();
|
|
855
|
+
} finally {
|
|
856
|
+
release();
|
|
857
|
+
if (_AgentHomeExecutionLeaseManager.queues.get(canonicalHome) === tail) {
|
|
858
|
+
_AgentHomeExecutionLeaseManager.queues.delete(canonicalHome);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
};
|
|
749
863
|
async function initializeAgentHome(resolution) {
|
|
750
864
|
await ensureDirectoryNoSymlink(
|
|
751
865
|
resolution.canonicalHome,
|
|
@@ -821,10 +935,12 @@ var AgentHomeManager = class {
|
|
|
821
935
|
layout;
|
|
822
936
|
projection;
|
|
823
937
|
leaseManager;
|
|
938
|
+
executionLeaseManager;
|
|
824
939
|
constructor(options) {
|
|
825
940
|
this.layout = new AgentHomeLayout(options.hostStorageRoot);
|
|
826
941
|
this.projection = options.projection;
|
|
827
942
|
this.leaseManager = options.leaseManager ?? new AgentHomeLeaseManager();
|
|
943
|
+
this.executionLeaseManager = new AgentHomeExecutionLeaseManager(this.leaseManager);
|
|
828
944
|
}
|
|
829
945
|
async prepare(agentRef) {
|
|
830
946
|
const binding = await this.acquire(agentRef);
|
|
@@ -851,12 +967,25 @@ var AgentHomeManager = class {
|
|
|
851
967
|
const lease = await this.leaseManager.acquire(resolution);
|
|
852
968
|
return Object.freeze({ resolution, lease });
|
|
853
969
|
}
|
|
970
|
+
async acquireExecution(agentRef, input) {
|
|
971
|
+
const resolution = await this.layout.resolve(agentRef);
|
|
972
|
+
const lease = await this.executionLeaseManager.acquire(resolution, input);
|
|
973
|
+
return Object.freeze({ resolution, lease });
|
|
974
|
+
}
|
|
854
975
|
/** Initialize only after any requested session exact-match has succeeded. */
|
|
855
976
|
async initialize(binding) {
|
|
856
|
-
|
|
977
|
+
await this.initializeResolved(binding.resolution, binding.lease.cwd);
|
|
978
|
+
}
|
|
979
|
+
async initializeExecution(binding) {
|
|
980
|
+
await this.mutateExecution(binding, () => this.initializeResolved(binding.resolution, binding.lease.cwd));
|
|
981
|
+
}
|
|
982
|
+
async mutateExecution(binding, operation) {
|
|
983
|
+
return this.executionLeaseManager.mutate(binding, operation);
|
|
984
|
+
}
|
|
985
|
+
async initializeResolved(resolution, cwd) {
|
|
857
986
|
await initializeAgentHome(resolution);
|
|
858
987
|
const prepare = this.projection?.prepare;
|
|
859
|
-
if (prepare !== void 0) await prepare({ ...resolution, cwd
|
|
988
|
+
if (prepare !== void 0) await prepare({ ...resolution, cwd });
|
|
860
989
|
if (await promises.realpath(resolution.homeDir) !== resolution.canonicalHome) {
|
|
861
990
|
throw new AgentHomeResolutionError("Agent projection changed the canonical home path");
|
|
862
991
|
}
|
|
@@ -6983,9 +7112,18 @@ function assertRecord(value) {
|
|
|
6983
7112
|
throw new DeviceCredentialStoreError("OS credential entry has an incomplete device credential");
|
|
6984
7113
|
}
|
|
6985
7114
|
}
|
|
6986
|
-
function
|
|
6987
|
-
|
|
6988
|
-
|
|
7115
|
+
function isFirstPairingAttempt(value) {
|
|
7116
|
+
return "kind" in value && value.kind === "first-pairing-attempt-v1";
|
|
7117
|
+
}
|
|
7118
|
+
function assertFirstPairingAttempt(value) {
|
|
7119
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value.kind !== "first-pairing-attempt-v1" || typeof value.deviceName !== "string" || typeof value.devicePublicKey !== "string" || typeof value.devicePrivateKeyPem !== "string" || value.machineId !== void 0 && typeof value.machineId !== "string" || value.deviceName.length === 0 || value.devicePublicKey.length === 0 || value.devicePrivateKeyPem.length === 0) {
|
|
7120
|
+
throw new DeviceCredentialStoreError("OS credential entry has an invalid first-pairing attempt shape");
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
function encode(authority) {
|
|
7124
|
+
if (isFirstPairingAttempt(authority)) assertFirstPairingAttempt(authority);
|
|
7125
|
+
else assertRecord(authority);
|
|
7126
|
+
const encoded = Buffer.from(JSON.stringify(authority), "utf8").toString("base64");
|
|
6989
7127
|
const value = `${ENCODED_PREFIX}${encoded}`;
|
|
6990
7128
|
if (Buffer.byteLength(value, "utf8") > 2400) {
|
|
6991
7129
|
throw new DeviceCredentialStoreError("device credential exceeds the OS credential entry bound");
|
|
@@ -7014,6 +7152,16 @@ function decode(value) {
|
|
|
7014
7152
|
} catch {
|
|
7015
7153
|
throw new DeviceCredentialStoreError("OS credential entry is not valid JSON");
|
|
7016
7154
|
}
|
|
7155
|
+
if (typeof parsed === "object" && parsed !== null && parsed.kind === "first-pairing-attempt-v1") {
|
|
7156
|
+
assertFirstPairingAttempt(parsed);
|
|
7157
|
+
return Object.freeze({
|
|
7158
|
+
kind: parsed.kind,
|
|
7159
|
+
deviceName: parsed.deviceName,
|
|
7160
|
+
devicePublicKey: parsed.devicePublicKey,
|
|
7161
|
+
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
7162
|
+
...parsed.machineId === void 0 ? {} : { machineId: parsed.machineId }
|
|
7163
|
+
});
|
|
7164
|
+
}
|
|
7017
7165
|
assertRecord(parsed);
|
|
7018
7166
|
return Object.freeze({
|
|
7019
7167
|
deviceId: parsed.deviceId,
|
|
@@ -7044,6 +7192,18 @@ var DeviceCredentialStore = class {
|
|
|
7044
7192
|
this.#run = options.commandRunner ?? runDeviceCommand;
|
|
7045
7193
|
}
|
|
7046
7194
|
async read() {
|
|
7195
|
+
const authority = await this.#readAuthority();
|
|
7196
|
+
return authority === void 0 || isFirstPairingAttempt(authority) ? void 0 : authority;
|
|
7197
|
+
}
|
|
7198
|
+
async readFirstPairingAttempt() {
|
|
7199
|
+
const authority = await this.#readAuthority();
|
|
7200
|
+
return authority !== void 0 && isFirstPairingAttempt(authority) ? authority : void 0;
|
|
7201
|
+
}
|
|
7202
|
+
async saveFirstPairingAttempt(attempt) {
|
|
7203
|
+
assertFirstPairingAttempt(attempt);
|
|
7204
|
+
await this.#replaceAuthority(attempt);
|
|
7205
|
+
}
|
|
7206
|
+
async #readAuthority() {
|
|
7047
7207
|
const result = await this.#invoke("read");
|
|
7048
7208
|
if (result.exitCode === NOT_FOUND || this.#platform === "linux" && result.exitCode === 1 && result.stderr.trim().length === 0) return void 0;
|
|
7049
7209
|
if (result.exitCode === 127) throw new DeviceCredentialStoreUnavailableError();
|
|
@@ -7056,6 +7216,12 @@ var DeviceCredentialStore = class {
|
|
|
7056
7216
|
}
|
|
7057
7217
|
async replace(record3) {
|
|
7058
7218
|
const encoded = encode(record3);
|
|
7219
|
+
await this.#replaceEncoded(encoded);
|
|
7220
|
+
}
|
|
7221
|
+
async #replaceAuthority(authority) {
|
|
7222
|
+
await this.#replaceEncoded(encode(authority));
|
|
7223
|
+
}
|
|
7224
|
+
async #replaceEncoded(encoded) {
|
|
7059
7225
|
const result = await this.#invoke("replace", encoded);
|
|
7060
7226
|
if (result.exitCode === 127) throw new DeviceCredentialStoreUnavailableError();
|
|
7061
7227
|
if (result.exitCode !== 0) {
|
|
@@ -7066,7 +7232,7 @@ var DeviceCredentialStore = class {
|
|
|
7066
7232
|
}
|
|
7067
7233
|
/** Returns true only after the sole secret authority is confirmed absent. */
|
|
7068
7234
|
async clear() {
|
|
7069
|
-
const before = await this
|
|
7235
|
+
const before = await this.#readAuthority();
|
|
7070
7236
|
if (before === void 0) return false;
|
|
7071
7237
|
const result = await this.#invoke("clear");
|
|
7072
7238
|
if (result.exitCode === 127) throw new DeviceCredentialStoreUnavailableError();
|
|
@@ -7075,7 +7241,7 @@ var DeviceCredentialStore = class {
|
|
|
7075
7241
|
`operating-system credential provider could not clear device credentials${providerDiagnostic(result.stderr)}`
|
|
7076
7242
|
);
|
|
7077
7243
|
}
|
|
7078
|
-
if (await this
|
|
7244
|
+
if (await this.#readAuthority() !== void 0) {
|
|
7079
7245
|
throw new DeviceCredentialStoreError("operating-system credential provider reported deletion but device credentials remain");
|
|
7080
7246
|
}
|
|
7081
7247
|
return true;
|
|
@@ -7150,17 +7316,24 @@ var DeviceCredentialStore = class {
|
|
|
7150
7316
|
}
|
|
7151
7317
|
};
|
|
7152
7318
|
var InMemoryDeviceCredentialStore = class {
|
|
7153
|
-
#
|
|
7319
|
+
#authority;
|
|
7154
7320
|
async read() {
|
|
7155
|
-
return this.#
|
|
7321
|
+
return this.#authority === void 0 || isFirstPairingAttempt(this.#authority) ? void 0 : Object.freeze({ ...this.#authority });
|
|
7322
|
+
}
|
|
7323
|
+
async readFirstPairingAttempt() {
|
|
7324
|
+
return this.#authority !== void 0 && isFirstPairingAttempt(this.#authority) ? Object.freeze({ ...this.#authority }) : void 0;
|
|
7325
|
+
}
|
|
7326
|
+
async saveFirstPairingAttempt(attempt) {
|
|
7327
|
+
assertFirstPairingAttempt(attempt);
|
|
7328
|
+
this.#authority = Object.freeze({ ...attempt });
|
|
7156
7329
|
}
|
|
7157
7330
|
async replace(record3) {
|
|
7158
7331
|
assertRecord(record3);
|
|
7159
|
-
this.#
|
|
7332
|
+
this.#authority = Object.freeze({ ...record3 });
|
|
7160
7333
|
}
|
|
7161
7334
|
async clear() {
|
|
7162
|
-
const had = this.#
|
|
7163
|
-
this.#
|
|
7335
|
+
const had = this.#authority !== void 0;
|
|
7336
|
+
this.#authority = void 0;
|
|
7164
7337
|
return had;
|
|
7165
7338
|
}
|
|
7166
7339
|
};
|
|
@@ -7447,6 +7620,10 @@ function describeEndpoint(transport, url) {
|
|
|
7447
7620
|
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
7448
7621
|
return { transport, host: parsed.host, path: parsed.pathname };
|
|
7449
7622
|
}
|
|
7623
|
+
function formatServerUrl(value) {
|
|
7624
|
+
const url = typeof value === "string" ? new URL(value) : value;
|
|
7625
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
7626
|
+
}
|
|
7450
7627
|
var InsecureServerUrlError = class extends Error {
|
|
7451
7628
|
constructor(message) {
|
|
7452
7629
|
super(message);
|
|
@@ -7465,9 +7642,10 @@ function assertServerUrlAllowed(rawUrl, opts = {}) {
|
|
|
7465
7642
|
let url;
|
|
7466
7643
|
try {
|
|
7467
7644
|
url = new URL(rawUrl);
|
|
7468
|
-
} catch
|
|
7469
|
-
throw new InsecureServerUrlError(
|
|
7645
|
+
} catch {
|
|
7646
|
+
throw new InsecureServerUrlError("invalid server URL");
|
|
7470
7647
|
}
|
|
7648
|
+
const endpoint = formatServerUrl(url);
|
|
7471
7649
|
switch (url.protocol) {
|
|
7472
7650
|
case "https:":
|
|
7473
7651
|
case "wss:":
|
|
@@ -7476,11 +7654,11 @@ function assertServerUrlAllowed(rawUrl, opts = {}) {
|
|
|
7476
7654
|
case "ws:":
|
|
7477
7655
|
if (opts.dangerouslyAllowInsecureRemote || isLoopbackHostname(url.hostname)) return;
|
|
7478
7656
|
throw new InsecureServerUrlError(
|
|
7479
|
-
`refusing to connect to "${
|
|
7657
|
+
`refusing to connect to "${endpoint}" over plaintext ${url.protocol.replace(":", "")} \u2014 "${url.hostname}" is not a loopback host. Use wss:/https: for any non-loopback server, or pass { dangerouslyAllowInsecureRemote: true } (DaemonConfig) if you understand and accept the risk of sending device credentials over an unencrypted connection.`
|
|
7480
7658
|
);
|
|
7481
7659
|
default:
|
|
7482
7660
|
throw new InsecureServerUrlError(
|
|
7483
|
-
`refusing to connect to "${
|
|
7661
|
+
`refusing to connect to "${endpoint}" \u2014 unsupported scheme "${url.protocol}" (expected http:, https:, ws:, or wss:).`
|
|
7484
7662
|
);
|
|
7485
7663
|
}
|
|
7486
7664
|
}
|
|
@@ -7492,12 +7670,22 @@ var DeviceRevokedError = class extends Error {
|
|
|
7492
7670
|
this.name = "DeviceRevokedError";
|
|
7493
7671
|
}
|
|
7494
7672
|
};
|
|
7673
|
+
var AuthRequestAbortedError = class extends Error {
|
|
7674
|
+
constructor(reason) {
|
|
7675
|
+
super(reason === "deadline" ? "authentication request exceeded its deadline" : "authentication request was cancelled during shutdown");
|
|
7676
|
+
this.reason = reason;
|
|
7677
|
+
this.name = "AuthRequestAbortedError";
|
|
7678
|
+
}
|
|
7679
|
+
reason;
|
|
7680
|
+
};
|
|
7495
7681
|
var ASSUMED_PAIR_TOKEN_TTL_MS = 45 * 60 * 1e3;
|
|
7496
7682
|
var RENEW_MARGIN_MS = 60 * 1e3;
|
|
7683
|
+
var DEFAULT_AUTH_REQUEST_DEADLINE_MS = 15e3;
|
|
7497
7684
|
var AuthManager = class {
|
|
7498
7685
|
constructor(opts) {
|
|
7499
7686
|
this.opts = opts;
|
|
7500
7687
|
this.credentials = opts.credentials ?? opts.store.credentials;
|
|
7688
|
+
this.requestDeadlineMs = resolveRequestDeadlineMs(opts.authRequestDeadlineMs);
|
|
7501
7689
|
}
|
|
7502
7690
|
opts;
|
|
7503
7691
|
record;
|
|
@@ -7507,7 +7695,10 @@ var AuthManager = class {
|
|
|
7507
7695
|
stopped = false;
|
|
7508
7696
|
pairing = false;
|
|
7509
7697
|
credentialMutationTail = Promise.resolve();
|
|
7698
|
+
/** The sole cancellation authority for the request currently inside the serialized credential mutation. */
|
|
7699
|
+
activeRequest;
|
|
7510
7700
|
credentials;
|
|
7701
|
+
requestDeadlineMs;
|
|
7511
7702
|
get deviceId() {
|
|
7512
7703
|
return this.record?.deviceId;
|
|
7513
7704
|
}
|
|
@@ -7543,23 +7734,51 @@ var AuthManager = class {
|
|
|
7543
7734
|
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
7544
7735
|
}
|
|
7545
7736
|
}
|
|
7546
|
-
const
|
|
7737
|
+
const observedDeviceName = this.opts.deviceName ?? os4__default.hostname();
|
|
7738
|
+
const observedMachineId = await this.opts.machineId?.();
|
|
7739
|
+
let keyPair;
|
|
7740
|
+
let pairingDeviceName = observedDeviceName;
|
|
7741
|
+
let pairingMachineId = observedMachineId;
|
|
7742
|
+
if (existing) {
|
|
7743
|
+
keyPair = { privateKey: importPrivateKeyPem(existing.devicePrivateKeyPem), publicKeyBase64Url: existing.devicePublicKey };
|
|
7744
|
+
} else {
|
|
7745
|
+
const firstAttempt = await this.credentials.readFirstPairingAttempt();
|
|
7746
|
+
if (firstAttempt) {
|
|
7747
|
+
keyPair = {
|
|
7748
|
+
privateKey: importPrivateKeyPem(firstAttempt.devicePrivateKeyPem),
|
|
7749
|
+
publicKeyBase64Url: firstAttempt.devicePublicKey
|
|
7750
|
+
};
|
|
7751
|
+
pairingDeviceName = firstAttempt.deviceName;
|
|
7752
|
+
pairingMachineId = firstAttempt.machineId;
|
|
7753
|
+
} else {
|
|
7754
|
+
keyPair = generateDeviceKeyPair();
|
|
7755
|
+
await this.credentials.saveFirstPairingAttempt({
|
|
7756
|
+
kind: "first-pairing-attempt-v1",
|
|
7757
|
+
deviceName: pairingDeviceName,
|
|
7758
|
+
devicePublicKey: keyPair.publicKeyBase64Url,
|
|
7759
|
+
devicePrivateKeyPem: exportPrivateKeyPem(keyPair.privateKey),
|
|
7760
|
+
...pairingMachineId === void 0 ? {} : { machineId: pairingMachineId }
|
|
7761
|
+
});
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7547
7764
|
const url = new URL(BYOK_PAIR_PATH, toHttpBase(this.opts.serverUrl));
|
|
7548
|
-
const
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7765
|
+
const body = await this.runRequest(async (signal) => {
|
|
7766
|
+
const res = await fetch(url, {
|
|
7767
|
+
method: "POST",
|
|
7768
|
+
headers: { "content-type": "application/json" },
|
|
7769
|
+
body: JSON.stringify({
|
|
7770
|
+
pairingCode,
|
|
7771
|
+
deviceName: pairingDeviceName,
|
|
7772
|
+
devicePublicKey: keyPair.publicKeyBase64Url,
|
|
7773
|
+
...pairingMachineId === void 0 ? {} : { machineId: pairingMachineId }
|
|
7774
|
+
}),
|
|
7775
|
+
signal
|
|
7776
|
+
});
|
|
7777
|
+
if (!res.ok) {
|
|
7778
|
+
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
7779
|
+
}
|
|
7780
|
+
return PairResponseSchema.parse(await res.json());
|
|
7558
7781
|
});
|
|
7559
|
-
if (!res.ok) {
|
|
7560
|
-
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
7561
|
-
}
|
|
7562
|
-
const body = PairResponseSchema.parse(await res.json());
|
|
7563
7782
|
const metadata = {
|
|
7564
7783
|
deviceId: body.deviceId,
|
|
7565
7784
|
tenantId: body.tenantId,
|
|
@@ -7598,6 +7817,7 @@ var AuthManager = class {
|
|
|
7598
7817
|
this.stopped = true;
|
|
7599
7818
|
if (this.proactiveTimer) clearTimeout(this.proactiveTimer);
|
|
7600
7819
|
this.proactiveTimer = void 0;
|
|
7820
|
+
this.activeRequest?.abort();
|
|
7601
7821
|
await this.credentialMutationTail;
|
|
7602
7822
|
}
|
|
7603
7823
|
async renew() {
|
|
@@ -7614,29 +7834,35 @@ var AuthManager = class {
|
|
|
7614
7834
|
const record3 = this.record;
|
|
7615
7835
|
const base = toHttpBase(this.opts.serverUrl);
|
|
7616
7836
|
const privateKey = importPrivateKeyPem(record3.devicePrivateKeyPem);
|
|
7617
|
-
const
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7837
|
+
const { nonce } = await this.runRequest(async (signal) => {
|
|
7838
|
+
const challengeRes = await fetch(new URL(BYOK_CHALLENGE_PATH, base), {
|
|
7839
|
+
method: "POST",
|
|
7840
|
+
headers: { "content-type": "application/json" },
|
|
7841
|
+
body: JSON.stringify({ deviceId: record3.deviceId }),
|
|
7842
|
+
signal
|
|
7843
|
+
});
|
|
7844
|
+
if (challengeRes.status === 401) this.markRevoked();
|
|
7845
|
+
if (!challengeRes.ok) {
|
|
7846
|
+
throw new Error(
|
|
7847
|
+
`token renewal (challenge) failed: HTTP ${challengeRes.status} ${await safeErrorText(challengeRes)}`.trimEnd()
|
|
7848
|
+
);
|
|
7849
|
+
}
|
|
7850
|
+
return await challengeRes.json();
|
|
7621
7851
|
});
|
|
7622
|
-
if (challengeRes.status === 401) this.markRevoked();
|
|
7623
|
-
if (!challengeRes.ok) {
|
|
7624
|
-
throw new Error(
|
|
7625
|
-
`token renewal (challenge) failed: HTTP ${challengeRes.status} ${await safeErrorText(challengeRes)}`.trimEnd()
|
|
7626
|
-
);
|
|
7627
|
-
}
|
|
7628
|
-
const { nonce } = await challengeRes.json();
|
|
7629
7852
|
const signature = signNonce(privateKey, nonce);
|
|
7630
|
-
const
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7853
|
+
const body = await this.runRequest(async (signal) => {
|
|
7854
|
+
const tokenRes = await fetch(new URL(BYOK_TOKEN_PATH, base), {
|
|
7855
|
+
method: "POST",
|
|
7856
|
+
headers: { "content-type": "application/json" },
|
|
7857
|
+
body: JSON.stringify({ deviceId: record3.deviceId, nonce, signature }),
|
|
7858
|
+
signal
|
|
7859
|
+
});
|
|
7860
|
+
if (tokenRes.status === 401) this.markRevoked();
|
|
7861
|
+
if (!tokenRes.ok) {
|
|
7862
|
+
throw new Error(`token renewal (token) failed: HTTP ${tokenRes.status} ${await safeErrorText(tokenRes)}`.trimEnd());
|
|
7863
|
+
}
|
|
7864
|
+
return await tokenRes.json();
|
|
7634
7865
|
});
|
|
7635
|
-
if (tokenRes.status === 401) this.markRevoked();
|
|
7636
|
-
if (!tokenRes.ok) {
|
|
7637
|
-
throw new Error(`token renewal (token) failed: HTTP ${tokenRes.status} ${await safeErrorText(tokenRes)}`.trimEnd());
|
|
7638
|
-
}
|
|
7639
|
-
const body = await tokenRes.json();
|
|
7640
7866
|
const updated = {
|
|
7641
7867
|
...record3,
|
|
7642
7868
|
accessToken: body.accessToken,
|
|
@@ -7666,6 +7892,37 @@ var AuthManager = class {
|
|
|
7666
7892
|
timer.unref?.();
|
|
7667
7893
|
this.proactiveTimer = timer;
|
|
7668
7894
|
}
|
|
7895
|
+
/**
|
|
7896
|
+
* Bounds one complete auth exchange rather than fetch alone. Keeping the
|
|
7897
|
+
* controller active through `json()`/`text()` makes a non-cooperative or
|
|
7898
|
+
* partial response body cancellable by the same authority that owns fetch.
|
|
7899
|
+
*/
|
|
7900
|
+
async runRequest(operation) {
|
|
7901
|
+
if (this.stopped) throw new AuthRequestAbortedError("stopped");
|
|
7902
|
+
const controller = new AbortController();
|
|
7903
|
+
this.activeRequest = controller;
|
|
7904
|
+
let deadlineElapsed = false;
|
|
7905
|
+
const deadline = setTimeout(() => {
|
|
7906
|
+
deadlineElapsed = true;
|
|
7907
|
+
controller.abort();
|
|
7908
|
+
}, this.requestDeadlineMs);
|
|
7909
|
+
let rejectOnAbort;
|
|
7910
|
+
const aborted = new Promise((_, reject) => {
|
|
7911
|
+
rejectOnAbort = reject;
|
|
7912
|
+
});
|
|
7913
|
+
const onAbort = () => rejectOnAbort(new AuthRequestAbortedError(deadlineElapsed ? "deadline" : "stopped"));
|
|
7914
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
7915
|
+
try {
|
|
7916
|
+
return await Promise.race([operation(controller.signal), aborted]);
|
|
7917
|
+
} catch (error) {
|
|
7918
|
+
if (controller.signal.aborted) throw new AuthRequestAbortedError(deadlineElapsed ? "deadline" : "stopped");
|
|
7919
|
+
throw error;
|
|
7920
|
+
} finally {
|
|
7921
|
+
clearTimeout(deadline);
|
|
7922
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
7923
|
+
if (this.activeRequest === controller) this.activeRequest = void 0;
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7669
7926
|
async runCredentialMutation(operation) {
|
|
7670
7927
|
const predecessor = this.credentialMutationTail;
|
|
7671
7928
|
let release;
|
|
@@ -7708,6 +7965,13 @@ function resolvePairExpiry(refreshHint) {
|
|
|
7708
7965
|
}
|
|
7709
7966
|
return new Date(Date.now() + ASSUMED_PAIR_TOKEN_TTL_MS).toISOString();
|
|
7710
7967
|
}
|
|
7968
|
+
function resolveRequestDeadlineMs(value) {
|
|
7969
|
+
const deadline = value ?? DEFAULT_AUTH_REQUEST_DEADLINE_MS;
|
|
7970
|
+
if (!Number.isSafeInteger(deadline) || deadline <= 0) {
|
|
7971
|
+
throw new Error("authRequestDeadlineMs must be a positive safe integer");
|
|
7972
|
+
}
|
|
7973
|
+
return deadline;
|
|
7974
|
+
}
|
|
7711
7975
|
async function safeErrorText(res) {
|
|
7712
7976
|
try {
|
|
7713
7977
|
return await res.text();
|
|
@@ -7729,37 +7993,48 @@ function withAuth(init, token) {
|
|
|
7729
7993
|
}
|
|
7730
7994
|
|
|
7731
7995
|
// src/daemon/blob-client.ts
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7996
|
+
var BlobRequestAbortedError = class extends Error {
|
|
7997
|
+
constructor(reason) {
|
|
7998
|
+
super(reason === "deadline" ? "blob request deadline elapsed" : "blob request cancelled");
|
|
7999
|
+
this.reason = reason;
|
|
8000
|
+
this.name = "BlobRequestAbortedError";
|
|
7737
8001
|
}
|
|
7738
|
-
|
|
8002
|
+
reason;
|
|
8003
|
+
};
|
|
7739
8004
|
var BlobClient = class {
|
|
7740
|
-
constructor(serverUrl, auth) {
|
|
8005
|
+
constructor(serverUrl, auth, options = {}) {
|
|
7741
8006
|
this.serverUrl = serverUrl;
|
|
7742
8007
|
this.auth = auth;
|
|
8008
|
+
this.options = options;
|
|
8009
|
+
const requestDeadlineMs = options.requestDeadlineMs ?? 15e3;
|
|
8010
|
+
if (!Number.isSafeInteger(requestDeadlineMs) || requestDeadlineMs <= 0) {
|
|
8011
|
+
throw new Error("BlobClient requestDeadlineMs must be a positive safe integer");
|
|
8012
|
+
}
|
|
8013
|
+
this.requestDeadlineMs = requestDeadlineMs;
|
|
7743
8014
|
}
|
|
7744
8015
|
serverUrl;
|
|
7745
8016
|
auth;
|
|
8017
|
+
options;
|
|
8018
|
+
requestDeadlineMs;
|
|
7746
8019
|
/** `blobRef` -> `GET /byok/blobs/:id/url` -> fetch the presigned download URL -> text content. Always resolves fresh rather than trusting any inlined `BlobRef.url`, per docs/protocol.md §7. */
|
|
7747
|
-
async resolveInstruction(blobRef) {
|
|
8020
|
+
async resolveInstruction(blobRef, options = {}) {
|
|
7748
8021
|
const base = toHttpBase(this.serverUrl);
|
|
7749
|
-
const urlRes = await
|
|
7750
|
-
new URL(byokBlobUrlPath(blobRef.blobId), base),
|
|
7751
|
-
|
|
7752
|
-
this.auth
|
|
8022
|
+
const urlRes = await this.#request(
|
|
8023
|
+
(signal) => authedFetch(new URL(byokBlobUrlPath(blobRef.blobId), base), { method: "GET", signal }, this.auth),
|
|
8024
|
+
options.signal
|
|
7753
8025
|
);
|
|
7754
8026
|
if (!urlRes.ok) {
|
|
7755
|
-
throw new Error(`failed to resolve blob download url: HTTP ${urlRes.status} ${await
|
|
8027
|
+
throw new Error(`failed to resolve blob download url: HTTP ${urlRes.status} ${await this.#safeErrorText(urlRes, options.signal)}`.trimEnd());
|
|
8028
|
+
}
|
|
8029
|
+
const { downloadUrl } = await this.#readJson(urlRes, options.signal);
|
|
8030
|
+
if (typeof downloadUrl !== "string" || downloadUrl.length === 0) {
|
|
8031
|
+
throw new Error("failed to resolve blob download url: response omitted downloadUrl");
|
|
7756
8032
|
}
|
|
7757
|
-
const
|
|
7758
|
-
const contentRes = await fetch(new URL(downloadUrl, base));
|
|
8033
|
+
const contentRes = await this.#request((signal) => fetch(new URL(downloadUrl, base), { signal }), options.signal);
|
|
7759
8034
|
if (!contentRes.ok) {
|
|
7760
8035
|
throw new Error(`failed to download blob content: HTTP ${contentRes.status}`);
|
|
7761
8036
|
}
|
|
7762
|
-
const bytes =
|
|
8037
|
+
const bytes = await this.#readBody(contentRes, options.signal);
|
|
7763
8038
|
const observedHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
7764
8039
|
if (observedHash !== blobRef.contentHash || bytes.length !== blobRef.size) {
|
|
7765
8040
|
throw new Error(
|
|
@@ -7768,63 +8043,71 @@ var BlobClient = class {
|
|
|
7768
8043
|
}
|
|
7769
8044
|
return new TextDecoder().decode(bytes);
|
|
7770
8045
|
}
|
|
7771
|
-
/** `POST /byok/blobs`
|
|
8046
|
+
/** `POST /byok/blobs` -> PUT the bytes to the presigned URL -> finalize into a `BlobRef`. */
|
|
7772
8047
|
async uploadArtifact(content, contentType, options = {}) {
|
|
7773
8048
|
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
7774
8049
|
const contentHash4 = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
7775
8050
|
const base = toHttpBase(this.serverUrl);
|
|
7776
8051
|
const reservationId = options.idempotencyKey ?? `blob_${randomUUID()}`;
|
|
7777
|
-
const createRes = await
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
"content-type": "application/json",
|
|
7783
|
-
|
|
8052
|
+
const createRes = await this.#request(
|
|
8053
|
+
(signal) => authedFetch(
|
|
8054
|
+
new URL(BYOK_BLOBS_PATH, base),
|
|
8055
|
+
{
|
|
8056
|
+
method: "POST",
|
|
8057
|
+
headers: { "content-type": "application/json", "idempotency-key": reservationId },
|
|
8058
|
+
body: JSON.stringify({ size: bytes.length, contentType, contentHash: contentHash4 }),
|
|
8059
|
+
signal
|
|
7784
8060
|
},
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
8061
|
+
this.auth
|
|
8062
|
+
),
|
|
8063
|
+
options.signal
|
|
7788
8064
|
);
|
|
7789
8065
|
if (!createRes.ok) {
|
|
7790
|
-
throw new Error(`failed to create blob: HTTP ${createRes.status} ${await
|
|
8066
|
+
throw new Error(`failed to create blob: HTTP ${createRes.status} ${await this.#safeErrorText(createRes, options.signal)}`.trimEnd());
|
|
8067
|
+
}
|
|
8068
|
+
const { blobId, uploadUrl } = await this.#readJson(createRes, options.signal);
|
|
8069
|
+
if (typeof blobId !== "string" || blobId.length === 0 || typeof uploadUrl !== "string" || uploadUrl.length === 0) {
|
|
8070
|
+
throw new Error("failed to create blob: response omitted blobId or uploadUrl");
|
|
7791
8071
|
}
|
|
7792
|
-
const { blobId, uploadUrl } = await createRes.json();
|
|
7793
8072
|
const blobRef = { blobId, contentHash: contentHash4, size: bytes.length, contentType };
|
|
7794
|
-
if (options.idempotencyKey !== void 0 && await this.#hasExactCommittedBlob(base, blobRef)) {
|
|
8073
|
+
if (options.idempotencyKey !== void 0 && await this.#hasExactCommittedBlob(base, blobRef, options.signal)) {
|
|
7795
8074
|
return blobRef;
|
|
7796
8075
|
}
|
|
7797
|
-
const putRes = await
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
8076
|
+
const putRes = await this.#request(
|
|
8077
|
+
(signal) => fetch(new URL(uploadUrl, base), {
|
|
8078
|
+
method: "PUT",
|
|
8079
|
+
headers: { "content-type": contentType },
|
|
8080
|
+
body: bytes,
|
|
8081
|
+
signal
|
|
8082
|
+
}),
|
|
8083
|
+
options.signal
|
|
8084
|
+
);
|
|
7802
8085
|
if (!putRes.ok) {
|
|
7803
8086
|
throw new Error(`failed to upload blob content: HTTP ${putRes.status}`);
|
|
7804
8087
|
}
|
|
7805
|
-
|
|
8088
|
+
this.#throwIfAborted(options.signal);
|
|
8089
|
+
await this.#finalize(base, blobId, reservationId, options.signal);
|
|
7806
8090
|
return blobRef;
|
|
7807
8091
|
}
|
|
7808
|
-
async #hasExactCommittedBlob(base, blobRef) {
|
|
7809
|
-
const urlRes = await
|
|
7810
|
-
new URL(byokBlobUrlPath(blobRef.blobId), base),
|
|
7811
|
-
|
|
7812
|
-
this.auth
|
|
8092
|
+
async #hasExactCommittedBlob(base, blobRef, signal) {
|
|
8093
|
+
const urlRes = await this.#request(
|
|
8094
|
+
(requestSignal) => authedFetch(new URL(byokBlobUrlPath(blobRef.blobId), base), { method: "GET", signal: requestSignal }, this.auth),
|
|
8095
|
+
signal
|
|
7813
8096
|
);
|
|
7814
8097
|
if (urlRes.status === 404) return false;
|
|
7815
8098
|
if (!urlRes.ok) {
|
|
7816
|
-
throw new Error(`failed to read back idempotent blob: HTTP ${urlRes.status} ${await
|
|
8099
|
+
throw new Error(`failed to read back idempotent blob: HTTP ${urlRes.status} ${await this.#safeErrorText(urlRes, signal)}`.trimEnd());
|
|
7817
8100
|
}
|
|
7818
|
-
const { downloadUrl } = await urlRes
|
|
8101
|
+
const { downloadUrl } = await this.#readJson(urlRes, signal);
|
|
7819
8102
|
if (typeof downloadUrl !== "string" || downloadUrl.length === 0) {
|
|
7820
8103
|
throw new Error("failed to read back idempotent blob: response omitted downloadUrl");
|
|
7821
8104
|
}
|
|
7822
|
-
const contentRes = await fetch(new URL(downloadUrl, base));
|
|
8105
|
+
const contentRes = await this.#request((requestSignal) => fetch(new URL(downloadUrl, base), { signal: requestSignal }), signal);
|
|
7823
8106
|
if (contentRes.status === 404) return false;
|
|
7824
8107
|
if (!contentRes.ok) {
|
|
7825
8108
|
throw new Error(`failed to read back idempotent blob content: HTTP ${contentRes.status}`);
|
|
7826
8109
|
}
|
|
7827
|
-
const bytes =
|
|
8110
|
+
const bytes = await this.#readBody(contentRes, signal);
|
|
7828
8111
|
const observedHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
7829
8112
|
if (observedHash !== blobRef.contentHash || bytes.length !== blobRef.size) {
|
|
7830
8113
|
throw new Error(
|
|
@@ -7833,18 +8116,19 @@ var BlobClient = class {
|
|
|
7833
8116
|
}
|
|
7834
8117
|
return true;
|
|
7835
8118
|
}
|
|
7836
|
-
async #finalize(base, blobId, reservationId) {
|
|
8119
|
+
async #finalize(base, blobId, reservationId, signal) {
|
|
7837
8120
|
let lastFailure;
|
|
7838
8121
|
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
8122
|
+
this.#throwIfAborted(signal);
|
|
7839
8123
|
let response;
|
|
7840
8124
|
try {
|
|
7841
|
-
response = await
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
method: "POST",
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
8125
|
+
response = await this.#request(
|
|
8126
|
+
(requestSignal) => authedFetch(
|
|
8127
|
+
new URL(byokBlobFinalizePath(blobId), base),
|
|
8128
|
+
{ method: "POST", headers: { "idempotency-key": reservationId }, signal: requestSignal },
|
|
8129
|
+
this.auth
|
|
8130
|
+
),
|
|
8131
|
+
signal
|
|
7848
8132
|
);
|
|
7849
8133
|
} catch (error) {
|
|
7850
8134
|
lastFailure = error;
|
|
@@ -7854,13 +8138,105 @@ var BlobClient = class {
|
|
|
7854
8138
|
if (response.ok) return;
|
|
7855
8139
|
if (response.status < 500 || attempt === 2) {
|
|
7856
8140
|
throw new Error(
|
|
7857
|
-
`failed to finalize blob: HTTP ${response.status} ${await
|
|
8141
|
+
`failed to finalize blob: HTTP ${response.status} ${await this.#safeErrorText(response, signal)}`.trimEnd()
|
|
7858
8142
|
);
|
|
7859
8143
|
}
|
|
7860
8144
|
lastFailure = new Error(`failed to finalize blob: HTTP ${response.status}`);
|
|
7861
8145
|
}
|
|
7862
8146
|
throw lastFailure;
|
|
7863
8147
|
}
|
|
8148
|
+
async #readJson(res, signal) {
|
|
8149
|
+
return JSON.parse(await this.#readText(res, signal));
|
|
8150
|
+
}
|
|
8151
|
+
async #safeErrorText(res, signal) {
|
|
8152
|
+
try {
|
|
8153
|
+
return await this.#readText(res, signal);
|
|
8154
|
+
} catch (error) {
|
|
8155
|
+
if (error instanceof BlobRequestAbortedError) throw error;
|
|
8156
|
+
return "";
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
async #readText(res, signal) {
|
|
8160
|
+
return new TextDecoder().decode(await this.#readBody(res, signal));
|
|
8161
|
+
}
|
|
8162
|
+
/**
|
|
8163
|
+
* `Response.arrayBuffer()` only leaves cancellation observable through the
|
|
8164
|
+
* Fetch implementation. Read the body directly so our lifecycle/deadline
|
|
8165
|
+
* authority cancels the actual stream even when a fetch implementation has
|
|
8166
|
+
* already resolved at headers.
|
|
8167
|
+
*/
|
|
8168
|
+
async #readBody(res, signal) {
|
|
8169
|
+
return this.#request(async (requestSignal) => {
|
|
8170
|
+
if (res.body === null) return new Uint8Array();
|
|
8171
|
+
const reader = res.body.getReader();
|
|
8172
|
+
const cancelBody = () => {
|
|
8173
|
+
void reader.cancel().catch(() => void 0);
|
|
8174
|
+
};
|
|
8175
|
+
requestSignal.addEventListener("abort", cancelBody, { once: true });
|
|
8176
|
+
try {
|
|
8177
|
+
const chunks = [];
|
|
8178
|
+
let length = 0;
|
|
8179
|
+
for (; ; ) {
|
|
8180
|
+
const { done, value } = await reader.read();
|
|
8181
|
+
if (done) break;
|
|
8182
|
+
chunks.push(value);
|
|
8183
|
+
length += value.byteLength;
|
|
8184
|
+
}
|
|
8185
|
+
const bytes = new Uint8Array(length);
|
|
8186
|
+
let offset = 0;
|
|
8187
|
+
for (const chunk of chunks) {
|
|
8188
|
+
bytes.set(chunk, offset);
|
|
8189
|
+
offset += chunk.byteLength;
|
|
8190
|
+
}
|
|
8191
|
+
return bytes;
|
|
8192
|
+
} finally {
|
|
8193
|
+
requestSignal.removeEventListener("abort", cancelBody);
|
|
8194
|
+
reader.releaseLock();
|
|
8195
|
+
}
|
|
8196
|
+
}, signal);
|
|
8197
|
+
}
|
|
8198
|
+
#throwIfAborted(signal) {
|
|
8199
|
+
if (this.options.signal?.aborted || signal?.aborted) {
|
|
8200
|
+
throw new BlobRequestAbortedError("cancelled");
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8203
|
+
async #request(request, signal) {
|
|
8204
|
+
this.#throwIfAborted(signal);
|
|
8205
|
+
const controller = new AbortController();
|
|
8206
|
+
let abortReason = "cancelled";
|
|
8207
|
+
const abort = (reason) => {
|
|
8208
|
+
if (controller.signal.aborted) return;
|
|
8209
|
+
abortReason = reason;
|
|
8210
|
+
controller.abort();
|
|
8211
|
+
};
|
|
8212
|
+
const inheritedSignals = [this.options.signal, signal].filter((value) => value !== void 0);
|
|
8213
|
+
const abortForCancellation = () => abort("cancelled");
|
|
8214
|
+
for (const inheritedSignal of inheritedSignals) {
|
|
8215
|
+
inheritedSignal.addEventListener("abort", abortForCancellation, { once: true });
|
|
8216
|
+
}
|
|
8217
|
+
const deadline = setTimeout(() => abort("deadline"), this.requestDeadlineMs);
|
|
8218
|
+
deadline.unref?.();
|
|
8219
|
+
let rejectAbort = () => {
|
|
8220
|
+
};
|
|
8221
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
8222
|
+
rejectAbort = reject;
|
|
8223
|
+
});
|
|
8224
|
+
const rejectOnAbort = () => rejectAbort(new BlobRequestAbortedError(abortReason));
|
|
8225
|
+
controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
|
|
8226
|
+
try {
|
|
8227
|
+
return await Promise.race([request(controller.signal), aborted]);
|
|
8228
|
+
} catch (error) {
|
|
8229
|
+
if (error instanceof BlobRequestAbortedError) throw error;
|
|
8230
|
+
if (controller.signal.aborted) throw new BlobRequestAbortedError(abortReason);
|
|
8231
|
+
throw error;
|
|
8232
|
+
} finally {
|
|
8233
|
+
clearTimeout(deadline);
|
|
8234
|
+
controller.signal.removeEventListener("abort", rejectOnAbort);
|
|
8235
|
+
for (const inheritedSignal of inheritedSignals) {
|
|
8236
|
+
inheritedSignal.removeEventListener("abort", abortForCancellation);
|
|
8237
|
+
}
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
7864
8240
|
};
|
|
7865
8241
|
var DEFAULT_TIMEOUT_MS2 = 2e3;
|
|
7866
8242
|
var DARWIN_UUID_RE = /"IOPlatformUUID"\s*=\s*"([^"]+)"/u;
|
|
@@ -8107,6 +8483,7 @@ var AnotherControlServerRunningError = class extends Error {
|
|
|
8107
8483
|
}
|
|
8108
8484
|
};
|
|
8109
8485
|
var MAX_HALF_OPEN_CONNECTIONS = 8;
|
|
8486
|
+
var MAX_OUTBOUND_QUEUE_BYTES = 1024 * 1024;
|
|
8110
8487
|
function errorMessage5(err) {
|
|
8111
8488
|
return err instanceof Error ? err.message : String(err);
|
|
8112
8489
|
}
|
|
@@ -8183,11 +8560,12 @@ async function bindControlEndpoint(server, endpoint) {
|
|
|
8183
8560
|
}
|
|
8184
8561
|
function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshakeSettled) {
|
|
8185
8562
|
const reader = new NdjsonLineReader();
|
|
8186
|
-
const
|
|
8563
|
+
const activeRequests = /* @__PURE__ */ new Map();
|
|
8187
8564
|
let phase = "client-hello";
|
|
8188
8565
|
let serverNonce = "";
|
|
8189
8566
|
let destroyed = false;
|
|
8190
8567
|
let handshakeSettled = false;
|
|
8568
|
+
let disposeOutboundWriter;
|
|
8191
8569
|
function settleHandshake() {
|
|
8192
8570
|
if (handshakeSettled) return;
|
|
8193
8571
|
handshakeSettled = true;
|
|
@@ -8197,8 +8575,83 @@ function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshak
|
|
|
8197
8575
|
if (phase !== "ready") socket.destroy();
|
|
8198
8576
|
}, handshakeTimeoutMs);
|
|
8199
8577
|
handshakeTimer.unref?.();
|
|
8578
|
+
function terminateConnection() {
|
|
8579
|
+
if (destroyed) return;
|
|
8580
|
+
destroyed = true;
|
|
8581
|
+
clearTimeout(handshakeTimer);
|
|
8582
|
+
settleHandshake();
|
|
8583
|
+
disposeOutboundWriter?.();
|
|
8584
|
+
const requests = [...activeRequests.values()];
|
|
8585
|
+
activeRequests.clear();
|
|
8586
|
+
for (const request of requests) {
|
|
8587
|
+
if (request.kind === "stream" && !request.controller.signal.aborted) request.controller.abort();
|
|
8588
|
+
}
|
|
8589
|
+
if (!socket.destroyed) socket.destroy();
|
|
8590
|
+
}
|
|
8591
|
+
const queuedFrames = [];
|
|
8592
|
+
let queuedBytes = 0;
|
|
8593
|
+
let blocked = false;
|
|
8594
|
+
let terminal = false;
|
|
8595
|
+
let drainListening = false;
|
|
8596
|
+
function disposeQueuedFrames() {
|
|
8597
|
+
terminal = true;
|
|
8598
|
+
blocked = false;
|
|
8599
|
+
queuedFrames.length = 0;
|
|
8600
|
+
queuedBytes = 0;
|
|
8601
|
+
if (drainListening) {
|
|
8602
|
+
socket.removeListener("drain", onDrain);
|
|
8603
|
+
drainListening = false;
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
function waitForDrain() {
|
|
8607
|
+
if (drainListening || terminal) return;
|
|
8608
|
+
drainListening = true;
|
|
8609
|
+
socket.once("drain", onDrain);
|
|
8610
|
+
}
|
|
8611
|
+
function writeEncodedFrame(encoded) {
|
|
8612
|
+
if (terminal) return;
|
|
8613
|
+
if (!socket.writable) {
|
|
8614
|
+
terminateConnection();
|
|
8615
|
+
return;
|
|
8616
|
+
}
|
|
8617
|
+
try {
|
|
8618
|
+
if (!socket.write(encoded)) {
|
|
8619
|
+
blocked = true;
|
|
8620
|
+
waitForDrain();
|
|
8621
|
+
}
|
|
8622
|
+
} catch {
|
|
8623
|
+
terminateConnection();
|
|
8624
|
+
}
|
|
8625
|
+
}
|
|
8626
|
+
function onDrain() {
|
|
8627
|
+
drainListening = false;
|
|
8628
|
+
if (terminal) return;
|
|
8629
|
+
blocked = false;
|
|
8630
|
+
while (!blocked && queuedFrames.length > 0) {
|
|
8631
|
+
const frame = queuedFrames.shift();
|
|
8632
|
+
queuedBytes -= frame.bytes;
|
|
8633
|
+
writeEncodedFrame(frame.encoded);
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
disposeOutboundWriter = disposeQueuedFrames;
|
|
8200
8637
|
function sendFrame(frame) {
|
|
8201
|
-
if (
|
|
8638
|
+
if (destroyed || terminal) return;
|
|
8639
|
+
const encoded = encodeFrame(frame);
|
|
8640
|
+
const bytes = Buffer.byteLength(encoded);
|
|
8641
|
+
if (bytes > MAX_OUTBOUND_QUEUE_BYTES) {
|
|
8642
|
+
terminateConnection();
|
|
8643
|
+
return;
|
|
8644
|
+
}
|
|
8645
|
+
if (blocked) {
|
|
8646
|
+
if (queuedBytes + bytes > MAX_OUTBOUND_QUEUE_BYTES) {
|
|
8647
|
+
terminateConnection();
|
|
8648
|
+
return;
|
|
8649
|
+
}
|
|
8650
|
+
queuedFrames.push({ encoded, bytes });
|
|
8651
|
+
queuedBytes += bytes;
|
|
8652
|
+
return;
|
|
8653
|
+
}
|
|
8654
|
+
writeEncodedFrame(encoded);
|
|
8202
8655
|
}
|
|
8203
8656
|
function handleClientHello(parsed) {
|
|
8204
8657
|
const hello = parseClientHello(parsed);
|
|
@@ -8221,21 +8674,51 @@ function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshak
|
|
|
8221
8674
|
settleHandshake();
|
|
8222
8675
|
sendFrame({ v: 1, ready: true });
|
|
8223
8676
|
}
|
|
8677
|
+
function rejectDuplicateRequest(id) {
|
|
8678
|
+
sendFrame({
|
|
8679
|
+
v: 1,
|
|
8680
|
+
id,
|
|
8681
|
+
ok: false,
|
|
8682
|
+
error: { code: "duplicate_request_id", message: `request id "${id}" is already active` }
|
|
8683
|
+
});
|
|
8684
|
+
}
|
|
8685
|
+
function registerRequest(id, record3) {
|
|
8686
|
+
if (activeRequests.has(id)) {
|
|
8687
|
+
rejectDuplicateRequest(id);
|
|
8688
|
+
return false;
|
|
8689
|
+
}
|
|
8690
|
+
activeRequests.set(id, record3);
|
|
8691
|
+
return true;
|
|
8692
|
+
}
|
|
8693
|
+
function releaseRequest(id, record3) {
|
|
8694
|
+
if (activeRequests.get(id) !== record3) return false;
|
|
8695
|
+
activeRequests.delete(id);
|
|
8696
|
+
return true;
|
|
8697
|
+
}
|
|
8224
8698
|
function dispatch(id, method, params) {
|
|
8225
8699
|
const unary = methods.unary[method];
|
|
8226
8700
|
if (unary) {
|
|
8227
|
-
|
|
8701
|
+
const record3 = { kind: "unary" };
|
|
8702
|
+
if (!registerRequest(id, record3)) return;
|
|
8703
|
+
Promise.resolve().then(() => unary(params)).then((result) => {
|
|
8704
|
+
if (!releaseRequest(id, record3)) return;
|
|
8705
|
+
sendFrame({ v: 1, id, ok: true, result });
|
|
8706
|
+
}).catch((err) => {
|
|
8707
|
+
if (!releaseRequest(id, record3)) return;
|
|
8708
|
+
sendFrame({ v: 1, id, ok: false, error: toControlErrorShape(err) });
|
|
8709
|
+
});
|
|
8228
8710
|
return;
|
|
8229
8711
|
}
|
|
8230
8712
|
const stream = methods.stream[method];
|
|
8231
8713
|
if (stream) {
|
|
8232
8714
|
const controller = new AbortController();
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8715
|
+
const record3 = { kind: "stream", controller };
|
|
8716
|
+
if (!registerRequest(id, record3)) return;
|
|
8717
|
+
Promise.resolve().then(() => stream(params, { emit: (event) => sendFrame({ v: 1, id, event }), signal: controller.signal })).then(() => {
|
|
8718
|
+
if (!releaseRequest(id, record3)) return;
|
|
8236
8719
|
if (!controller.signal.aborted) sendFrame({ v: 1, id, ok: true, done: true });
|
|
8237
8720
|
}).catch((err) => {
|
|
8238
|
-
|
|
8721
|
+
if (!releaseRequest(id, record3)) return;
|
|
8239
8722
|
sendFrame({ v: 1, id, ok: false, error: toControlErrorShape(err) });
|
|
8240
8723
|
});
|
|
8241
8724
|
return;
|
|
@@ -8277,13 +8760,10 @@ function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshak
|
|
|
8277
8760
|
for (const line of lines) handleLine(line);
|
|
8278
8761
|
});
|
|
8279
8762
|
socket.on("error", () => {
|
|
8763
|
+
terminateConnection();
|
|
8280
8764
|
});
|
|
8281
8765
|
socket.on("close", () => {
|
|
8282
|
-
|
|
8283
|
-
clearTimeout(handshakeTimer);
|
|
8284
|
-
settleHandshake();
|
|
8285
|
-
for (const controller of activeStreams.values()) controller.abort();
|
|
8286
|
-
activeStreams.clear();
|
|
8766
|
+
terminateConnection();
|
|
8287
8767
|
});
|
|
8288
8768
|
}
|
|
8289
8769
|
async function startControlServer(opts) {
|
|
@@ -9201,6 +9681,20 @@ function createFleetJitter(productId, deviceId) {
|
|
|
9201
9681
|
delay: (domain, sequence, baseMs) => deterministicJitterMs({ seed, domain, sequence, baseMs })
|
|
9202
9682
|
};
|
|
9203
9683
|
}
|
|
9684
|
+
|
|
9685
|
+
// src/daemon/replay-cursor.ts
|
|
9686
|
+
var ReplayCursorTooOldError = class extends Error {
|
|
9687
|
+
constructor(recoverableFrom) {
|
|
9688
|
+
super(
|
|
9689
|
+
recoverableFrom === void 0 ? "server cannot replay the acknowledged cursor; re-pair or operator recovery is required" : `server cannot replay the acknowledged cursor; retained history starts at ${recoverableFrom}`
|
|
9690
|
+
);
|
|
9691
|
+
this.recoverableFrom = recoverableFrom;
|
|
9692
|
+
this.name = "ReplayCursorTooOldError";
|
|
9693
|
+
}
|
|
9694
|
+
recoverableFrom;
|
|
9695
|
+
};
|
|
9696
|
+
|
|
9697
|
+
// src/daemon/long-poll-transport.ts
|
|
9204
9698
|
var LongPollRouteError = class extends Error {
|
|
9205
9699
|
constructor(endpoint, status, cause) {
|
|
9206
9700
|
super(
|
|
@@ -9380,6 +9874,12 @@ var LongPollClient = class {
|
|
|
9380
9874
|
throw err;
|
|
9381
9875
|
}
|
|
9382
9876
|
if (!res.ok) {
|
|
9877
|
+
const replayCursorTooOld = await parseReplayCursorTooOld(res);
|
|
9878
|
+
if (replayCursorTooOld) {
|
|
9879
|
+
this.running = false;
|
|
9880
|
+
this.opts.onReplayCursorTooOld?.(replayCursorTooOld);
|
|
9881
|
+
return;
|
|
9882
|
+
}
|
|
9383
9883
|
this.warnRouteFailure(this.eventsEndpoint, res.status, void 0);
|
|
9384
9884
|
this.opts.onServerCapabilities?.([]);
|
|
9385
9885
|
this.opts.onOperationalOutcome?.("failure");
|
|
@@ -9448,6 +9948,21 @@ var LongPollClient = class {
|
|
|
9448
9948
|
}
|
|
9449
9949
|
}
|
|
9450
9950
|
};
|
|
9951
|
+
async function parseReplayCursorTooOld(res) {
|
|
9952
|
+
if (res.status !== 409) return void 0;
|
|
9953
|
+
let body;
|
|
9954
|
+
try {
|
|
9955
|
+
body = await res.json();
|
|
9956
|
+
} catch {
|
|
9957
|
+
return void 0;
|
|
9958
|
+
}
|
|
9959
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
9960
|
+
const { error, recoverableFrom } = body;
|
|
9961
|
+
if (error !== "cursor_too_old" || typeof recoverableFrom !== "number" || !Number.isSafeInteger(recoverableFrom) || recoverableFrom < 0) {
|
|
9962
|
+
return void 0;
|
|
9963
|
+
}
|
|
9964
|
+
return new ReplayCursorTooOldError(recoverableFrom);
|
|
9965
|
+
}
|
|
9451
9966
|
function sleep(ms) {
|
|
9452
9967
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9453
9968
|
}
|
|
@@ -9592,16 +10107,17 @@ var WsTransport = class {
|
|
|
9592
10107
|
}
|
|
9593
10108
|
this.opts.onEnvelope(envelope);
|
|
9594
10109
|
});
|
|
9595
|
-
socket.on("close", () => {
|
|
10110
|
+
socket.on("close", (code, reason) => {
|
|
9596
10111
|
this.socket = void 0;
|
|
9597
10112
|
this.stopLivenessCheck();
|
|
9598
10113
|
this.opts.onStateChange?.("closed");
|
|
9599
10114
|
const acked = this.everAckedThisAttempt;
|
|
9600
10115
|
const status = this.lastUnexpectedStatus;
|
|
9601
10116
|
this.lastUnexpectedStatus = void 0;
|
|
10117
|
+
const replayCursorTooOld = code === 1008 && reason.toString("utf8") === "cursor_too_old" ? new ReplayCursorTooOldError() : void 0;
|
|
9602
10118
|
this.opts.onConnectOutcome?.(
|
|
9603
10119
|
acked,
|
|
9604
|
-
status !== void 0 ? new WsUnexpectedStatusError(status, endpoint) : void 0,
|
|
10120
|
+
replayCursorTooOld ?? (status !== void 0 ? new WsUnexpectedStatusError(status, endpoint) : void 0),
|
|
9605
10121
|
endpoint
|
|
9606
10122
|
);
|
|
9607
10123
|
if (!this.closedByUser && this.autoReconnect) this.scheduleReconnect();
|
|
@@ -9687,6 +10203,7 @@ var ConnectionManager = class {
|
|
|
9687
10203
|
if (this.mode === "long-poll") this.serverCapabilities = capabilities;
|
|
9688
10204
|
},
|
|
9689
10205
|
onRevoked: () => this.enterRevoked(),
|
|
10206
|
+
onReplayCursorTooOld: (error) => this.enterReplayCursorTooOld(error),
|
|
9690
10207
|
// M4 Phase 4 (version-negotiation drill fix): a batch entry
|
|
9691
10208
|
// LongPollClient couldn't parse into a known Envelope at all (an
|
|
9692
10209
|
// unrecognized message type) still needs its cursor/watermark
|
|
@@ -9775,6 +10292,7 @@ var ConnectionManager = class {
|
|
|
9775
10292
|
draining = false;
|
|
9776
10293
|
stopped = false;
|
|
9777
10294
|
revoked = false;
|
|
10295
|
+
terminalError;
|
|
9778
10296
|
settledWaiters = [];
|
|
9779
10297
|
pendingCursorSave = Promise.resolve();
|
|
9780
10298
|
/**
|
|
@@ -9847,6 +10365,7 @@ var ConnectionManager = class {
|
|
|
9847
10365
|
*/
|
|
9848
10366
|
serverCapabilities = [];
|
|
9849
10367
|
async start() {
|
|
10368
|
+
if (this.terminalError) throw this.terminalError;
|
|
9850
10369
|
this.cursor = await this.opts.cursorStore.load(this.opts.serverUrl, this.opts.deviceId);
|
|
9851
10370
|
this.ws.connect({ auto: true });
|
|
9852
10371
|
}
|
|
@@ -9962,6 +10481,12 @@ var ConnectionManager = class {
|
|
|
9962
10481
|
getServerCapabilities() {
|
|
9963
10482
|
return this.serverCapabilities;
|
|
9964
10483
|
}
|
|
10484
|
+
getTerminalError() {
|
|
10485
|
+
return this.terminalError;
|
|
10486
|
+
}
|
|
10487
|
+
getMode() {
|
|
10488
|
+
return this.mode;
|
|
10489
|
+
}
|
|
9965
10490
|
isConnected() {
|
|
9966
10491
|
return this.mode === "ws" && this.ws.isOpen;
|
|
9967
10492
|
}
|
|
@@ -9982,6 +10507,7 @@ var ConnectionManager = class {
|
|
|
9982
10507
|
*/
|
|
9983
10508
|
waitForAck(timeoutMs = 1e4) {
|
|
9984
10509
|
if (this.ws.isOpen || this.mode === "long-poll") return Promise.resolve();
|
|
10510
|
+
if (this.terminalError) return Promise.reject(this.terminalError);
|
|
9985
10511
|
if (this.revoked) return Promise.reject(new DeviceRevokedError());
|
|
9986
10512
|
return new Promise((resolve, reject) => {
|
|
9987
10513
|
let settle = () => {
|
|
@@ -10310,6 +10836,7 @@ var ConnectionManager = class {
|
|
|
10310
10836
|
* (which is close-only) at all.
|
|
10311
10837
|
*/
|
|
10312
10838
|
onAcked(capabilities) {
|
|
10839
|
+
if (this.terminalError) return;
|
|
10313
10840
|
this.serverCapabilities = capabilities;
|
|
10314
10841
|
this.consecutiveFailures = 0;
|
|
10315
10842
|
this.notifySettled();
|
|
@@ -10318,6 +10845,10 @@ var ConnectionManager = class {
|
|
|
10318
10845
|
void this.drainOutbox();
|
|
10319
10846
|
}
|
|
10320
10847
|
onWsOutcome(acked, err) {
|
|
10848
|
+
if (err instanceof ReplayCursorTooOldError) {
|
|
10849
|
+
this.enterReplayCursorTooOld(err);
|
|
10850
|
+
return;
|
|
10851
|
+
}
|
|
10321
10852
|
if (this.stopped || this.revoked) return;
|
|
10322
10853
|
if (acked) this.serverCapabilities = [];
|
|
10323
10854
|
if (err instanceof WsUnexpectedStatusError && err.status === 401) {
|
|
@@ -10354,6 +10885,20 @@ var ConnectionManager = class {
|
|
|
10354
10885
|
void this.drainOutbox();
|
|
10355
10886
|
this.scheduleWsProbe();
|
|
10356
10887
|
}
|
|
10888
|
+
enterReplayCursorTooOld(error) {
|
|
10889
|
+
if (this.terminalError) return;
|
|
10890
|
+
this.terminalError = error;
|
|
10891
|
+
this.stopped = true;
|
|
10892
|
+
this.serverCapabilities = [];
|
|
10893
|
+
if (this.wsRetryTimer) clearInterval(this.wsRetryTimer);
|
|
10894
|
+
this.longPoll.stop();
|
|
10895
|
+
this.ws.stopAutoReconnect();
|
|
10896
|
+
this.ws.close();
|
|
10897
|
+
this.cancelPendingDrainRetry?.();
|
|
10898
|
+
this.notifySettled(error);
|
|
10899
|
+
this.opts.onStateChange?.("closed");
|
|
10900
|
+
this.opts.onTerminalError?.(error);
|
|
10901
|
+
}
|
|
10357
10902
|
exitLongPoll() {
|
|
10358
10903
|
if (this.wsRetryTimer) {
|
|
10359
10904
|
clearInterval(this.wsRetryTimer);
|
|
@@ -13083,21 +13628,28 @@ async function recordAuditWarning(context, kind, values) {
|
|
|
13083
13628
|
}
|
|
13084
13629
|
}
|
|
13085
13630
|
var agentMemoryHomeQueues = /* @__PURE__ */ new Map();
|
|
13086
|
-
|
|
13087
|
-
|
|
13631
|
+
var agentMemoryProjectionTransactionQueues = /* @__PURE__ */ new Map();
|
|
13632
|
+
async function exclusiveAgentMemoryHomeQueue(queues2, home, fn) {
|
|
13633
|
+
const previous = queues2.get(home) ?? Promise.resolve();
|
|
13088
13634
|
let release;
|
|
13089
13635
|
const next = new Promise((resolve) => {
|
|
13090
13636
|
release = resolve;
|
|
13091
13637
|
});
|
|
13092
|
-
|
|
13638
|
+
queues2.set(home, next);
|
|
13093
13639
|
await previous;
|
|
13094
13640
|
try {
|
|
13095
13641
|
return await fn();
|
|
13096
13642
|
} finally {
|
|
13097
13643
|
release();
|
|
13098
|
-
if (
|
|
13644
|
+
if (queues2.get(home) === next) queues2.delete(home);
|
|
13099
13645
|
}
|
|
13100
13646
|
}
|
|
13647
|
+
async function exclusiveAgentMemoryHome(home, fn) {
|
|
13648
|
+
return exclusiveAgentMemoryHomeQueue(agentMemoryHomeQueues, home, fn);
|
|
13649
|
+
}
|
|
13650
|
+
async function exclusiveAgentMemoryProjectionTransaction(home, fn) {
|
|
13651
|
+
return exclusiveAgentMemoryHomeQueue(agentMemoryProjectionTransactionQueues, home, fn);
|
|
13652
|
+
}
|
|
13101
13653
|
var AgentMemoryService = class {
|
|
13102
13654
|
constructor(input) {
|
|
13103
13655
|
this.input = input;
|
|
@@ -13401,13 +13953,16 @@ var AgentMemoryRedactedOutbox = class _AgentMemoryRedactedOutbox {
|
|
|
13401
13953
|
async function snapshotAndProjectAgentMemory(input, projection) {
|
|
13402
13954
|
if (projection?.capability !== AGENT_MEMORY_PROJECTION_CAPABILITY || !projection.grant || !projection.redactor || !projection.port) return;
|
|
13403
13955
|
const context = taskContext(input);
|
|
13404
|
-
const
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13956
|
+
const { grant, redactor, port } = projection;
|
|
13957
|
+
await exclusiveAgentMemoryProjectionTransaction(context.canonicalHome, async () => {
|
|
13958
|
+
const outbox = await AgentMemoryRedactedOutbox.open(context, grant);
|
|
13959
|
+
const initialReplay = await outbox.replay(port);
|
|
13960
|
+
if (initialReplay.status === "pending") throw new AgentMemoryProjectionReplayPendingError(initialReplay);
|
|
13961
|
+
const snapshot = await captureAgentMemorySnapshot(context);
|
|
13962
|
+
await outbox.append(redactedBytes(snapshot, await redactor.redact(snapshot)));
|
|
13963
|
+
const trailingReplay = await outbox.replay(port);
|
|
13964
|
+
if (trailingReplay.status === "pending") throw new AgentMemoryProjectionReplayPendingError(trailingReplay);
|
|
13965
|
+
});
|
|
13411
13966
|
}
|
|
13412
13967
|
var AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL = 2;
|
|
13413
13968
|
var AGENT_MEMORY_FILESYSTEM_HELPER_VERSION = "2";
|
|
@@ -13815,6 +14370,7 @@ var TaskRunner = class {
|
|
|
13815
14370
|
deps;
|
|
13816
14371
|
tasks = /* @__PURE__ */ new Map();
|
|
13817
14372
|
pendingMessageTasks = /* @__PURE__ */ new Map();
|
|
14373
|
+
messageOutboxesByHome = /* @__PURE__ */ new Map();
|
|
13818
14374
|
messageContextByToken = /* @__PURE__ */ new Map();
|
|
13819
14375
|
messageContextByTask = /* @__PURE__ */ new Map();
|
|
13820
14376
|
memoryContextByToken = /* @__PURE__ */ new Map();
|
|
@@ -13875,6 +14431,8 @@ var TaskRunner = class {
|
|
|
13875
14431
|
* costs nothing.
|
|
13876
14432
|
*/
|
|
13877
14433
|
inFlightOffers = /* @__PURE__ */ new Set();
|
|
14434
|
+
/** Blob I/O before an offer becomes an active task still belongs to that offer's cancellation authority. */
|
|
14435
|
+
inFlightBlobAborts = /* @__PURE__ */ new Map();
|
|
13878
14436
|
/**
|
|
13879
14437
|
* Finding P2 (Fix 2c): taskIds that have reached a terminal outcome
|
|
13880
14438
|
* (Complete/Failed/Cancelled) this session — populated in `finish()`.
|
|
@@ -14001,6 +14559,7 @@ var TaskRunner = class {
|
|
|
14001
14559
|
async recoverAgentMessageOutboxes(agentsRoot) {
|
|
14002
14560
|
if (this.deps.tenantId === void 0) throw new Error("Agent message recovery requires authenticated tenant enrollment");
|
|
14003
14561
|
for (const outbox of await AgentMessageOutbox.recover(agentsRoot, this.deps.tenantId)) {
|
|
14562
|
+
this.messageOutboxesByHome.set(outbox.homeDir, Promise.resolve(outbox));
|
|
14004
14563
|
for (const record3 of outbox.retryableRecords()) {
|
|
14005
14564
|
if (record3.sessionRef === void 0) continue;
|
|
14006
14565
|
const existing = this.recoveredMessageOutboxes.get(record3.taskId);
|
|
@@ -14009,6 +14568,16 @@ var TaskRunner = class {
|
|
|
14009
14568
|
}
|
|
14010
14569
|
}
|
|
14011
14570
|
}
|
|
14571
|
+
agentMessageOutbox(homeDir) {
|
|
14572
|
+
const existing = this.messageOutboxesByHome.get(homeDir);
|
|
14573
|
+
if (existing !== void 0) return existing;
|
|
14574
|
+
const opened = AgentMessageOutbox.open(homeDir);
|
|
14575
|
+
this.messageOutboxesByHome.set(homeDir, opened);
|
|
14576
|
+
void opened.catch(() => {
|
|
14577
|
+
if (this.messageOutboxesByHome.get(homeDir) === opened) this.messageOutboxesByHome.delete(homeDir);
|
|
14578
|
+
});
|
|
14579
|
+
return opened;
|
|
14580
|
+
}
|
|
14012
14581
|
/** Retry stable recovered records after a transport handshake/re-handshake. */
|
|
14013
14582
|
retryRecoveredAgentMessages() {
|
|
14014
14583
|
for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
|
|
@@ -14164,6 +14733,7 @@ var TaskRunner = class {
|
|
|
14164
14733
|
if (active.finalizationStarted) return this.finish(active.taskId);
|
|
14165
14734
|
if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
|
|
14166
14735
|
active.beingTornDown = true;
|
|
14736
|
+
active.blobAbort.abort();
|
|
14167
14737
|
await this.observeGit(active, "salvage");
|
|
14168
14738
|
const timeoutMs = this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS;
|
|
14169
14739
|
await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
|
|
@@ -14310,6 +14880,8 @@ var TaskRunner = class {
|
|
|
14310
14880
|
return;
|
|
14311
14881
|
}
|
|
14312
14882
|
this.inFlightOffers.add(taskId);
|
|
14883
|
+
const blobAbort = new AbortController();
|
|
14884
|
+
this.inFlightBlobAborts.set(taskId, blobAbort);
|
|
14313
14885
|
let agentBinding;
|
|
14314
14886
|
let agentLeaseTransferred = false;
|
|
14315
14887
|
try {
|
|
@@ -14468,7 +15040,7 @@ var TaskRunner = class {
|
|
|
14468
15040
|
}
|
|
14469
15041
|
if (agentRef !== void 0) {
|
|
14470
15042
|
try {
|
|
14471
|
-
agentBinding = await this.deps.agentHome.
|
|
15043
|
+
agentBinding = await this.deps.agentHome.acquireExecution(agentRef, { taskId, sessionRef });
|
|
14472
15044
|
} catch (error) {
|
|
14473
15045
|
decline(
|
|
14474
15046
|
`Agent home admission failed: ${errorMessage6(error)}`,
|
|
@@ -14503,7 +15075,7 @@ var TaskRunner = class {
|
|
|
14503
15075
|
}
|
|
14504
15076
|
}
|
|
14505
15077
|
try {
|
|
14506
|
-
await this.deps.agentHome.
|
|
15078
|
+
await this.deps.agentHome.initializeExecution(agentBinding);
|
|
14507
15079
|
} catch (error) {
|
|
14508
15080
|
await agentBinding.lease.release().catch(() => {
|
|
14509
15081
|
});
|
|
@@ -14513,7 +15085,7 @@ var TaskRunner = class {
|
|
|
14513
15085
|
}
|
|
14514
15086
|
if (messageRequirement !== void 0) {
|
|
14515
15087
|
try {
|
|
14516
|
-
const outbox = await
|
|
15088
|
+
const outbox = await this.agentMessageOutbox(agentBinding.resolution.canonicalHome);
|
|
14517
15089
|
this.pendingMessageTasks.set(taskId, {
|
|
14518
15090
|
taskId,
|
|
14519
15091
|
agentRef: agentBinding.resolution.agentRef,
|
|
@@ -14632,7 +15204,7 @@ var TaskRunner = class {
|
|
|
14632
15204
|
);
|
|
14633
15205
|
let resolvedInstruction;
|
|
14634
15206
|
try {
|
|
14635
|
-
resolvedInstruction = await this.resolveInstruction(payload.instruction);
|
|
15207
|
+
resolvedInstruction = await this.resolveInstruction(payload.instruction, blobAbort.signal);
|
|
14636
15208
|
if (plainWorkspaceNeedsResolve) workspaceDir = await this.resolveWorkspaceDir(taskId, known?.workspaceDir);
|
|
14637
15209
|
} catch (err) {
|
|
14638
15210
|
gitLease?.release();
|
|
@@ -14745,9 +15317,29 @@ var TaskRunner = class {
|
|
|
14745
15317
|
}
|
|
14746
15318
|
return;
|
|
14747
15319
|
}
|
|
15320
|
+
if (agentBinding !== void 0) {
|
|
15321
|
+
try {
|
|
15322
|
+
await agentBinding.lease.bindSession(session.sessionRef);
|
|
15323
|
+
} catch (error) {
|
|
15324
|
+
await session.close().catch(() => {
|
|
15325
|
+
});
|
|
15326
|
+
await this.failClaimedAgent(
|
|
15327
|
+
taskId,
|
|
15328
|
+
`Agent session execution lease could not bind the runtime session: ${errorMessage6(error)}`,
|
|
15329
|
+
false,
|
|
15330
|
+
{
|
|
15331
|
+
binding: agentBinding,
|
|
15332
|
+
runtimeId: pick.descriptor.id,
|
|
15333
|
+
sessionRef: session.sessionRef
|
|
15334
|
+
}
|
|
15335
|
+
);
|
|
15336
|
+
return;
|
|
15337
|
+
}
|
|
15338
|
+
}
|
|
14748
15339
|
let active;
|
|
14749
15340
|
active = {
|
|
14750
15341
|
taskId,
|
|
15342
|
+
blobAbort,
|
|
14751
15343
|
egressEnabled: "egressPolicy" in payload,
|
|
14752
15344
|
adapter: pick.adapter,
|
|
14753
15345
|
session,
|
|
@@ -14788,15 +15380,16 @@ var TaskRunner = class {
|
|
|
14788
15380
|
...terminalProjection === void 0 ? {} : { terminalProjection }
|
|
14789
15381
|
};
|
|
14790
15382
|
if (agentBinding !== void 0) {
|
|
15383
|
+
const binding = agentBinding;
|
|
14791
15384
|
try {
|
|
14792
|
-
await this.deps.agentSessionHandoffs.record({
|
|
14793
|
-
agentRef:
|
|
15385
|
+
await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
|
|
15386
|
+
agentRef: binding.resolution.agentRef,
|
|
14794
15387
|
taskId,
|
|
14795
15388
|
sessionRef: session.sessionRef,
|
|
14796
15389
|
runtimeId: pick.descriptor.id,
|
|
14797
15390
|
cwd: workspaceDir,
|
|
14798
|
-
leaseId:
|
|
14799
|
-
});
|
|
15391
|
+
leaseId: binding.lease.leaseId
|
|
15392
|
+
}));
|
|
14800
15393
|
} catch (error) {
|
|
14801
15394
|
await session.close().catch(() => {
|
|
14802
15395
|
});
|
|
@@ -14838,6 +15431,7 @@ var TaskRunner = class {
|
|
|
14838
15431
|
}
|
|
14839
15432
|
this.deps.send(createEnvelope("task.started", {}, { taskId }));
|
|
14840
15433
|
agentLeaseTransferred = agentBinding !== void 0;
|
|
15434
|
+
this.inFlightBlobAborts.delete(taskId);
|
|
14841
15435
|
this.tasks.set(taskId, active);
|
|
14842
15436
|
this.pendingMessageTasks.delete(taskId);
|
|
14843
15437
|
if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
|
|
@@ -14869,6 +15463,7 @@ var TaskRunner = class {
|
|
|
14869
15463
|
await agentBinding.lease.release().catch(() => {
|
|
14870
15464
|
});
|
|
14871
15465
|
}
|
|
15466
|
+
this.inFlightBlobAborts.delete(taskId);
|
|
14872
15467
|
this.inFlightOffers.delete(taskId);
|
|
14873
15468
|
}
|
|
14874
15469
|
}
|
|
@@ -15020,9 +15615,9 @@ var TaskRunner = class {
|
|
|
15020
15615
|
void this.closeAgentMemoryFilesystem(taskId);
|
|
15021
15616
|
}
|
|
15022
15617
|
/** Protocol §7: an instruction too large to inline arrives as a `blobRef` — resolve it via the blob client rather than failing closed. */
|
|
15023
|
-
async resolveInstruction(instruction) {
|
|
15618
|
+
async resolveInstruction(instruction, signal) {
|
|
15024
15619
|
if (typeof instruction === "string") return instruction;
|
|
15025
|
-
return this.deps.blobClient.resolveInstruction(instruction.blobRef);
|
|
15620
|
+
return this.deps.blobClient.resolveInstruction(instruction.blobRef, { signal });
|
|
15026
15621
|
}
|
|
15027
15622
|
/** Resolve every requested logical id locally and reject missing/colliding server authority before claim. */
|
|
15028
15623
|
resolveMcpServers(requiredToolsets) {
|
|
@@ -15254,7 +15849,9 @@ var TaskRunner = class {
|
|
|
15254
15849
|
}
|
|
15255
15850
|
}
|
|
15256
15851
|
try {
|
|
15257
|
-
const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType
|
|
15852
|
+
const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType, {
|
|
15853
|
+
signal: active.blobAbort.signal
|
|
15854
|
+
});
|
|
15258
15855
|
this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
|
|
15259
15856
|
} catch (err) {
|
|
15260
15857
|
this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage6(err)}`);
|
|
@@ -15269,6 +15866,7 @@ var TaskRunner = class {
|
|
|
15269
15866
|
const active = this.tasks.get(taskId);
|
|
15270
15867
|
if (!active) {
|
|
15271
15868
|
this.setPendingCancelled(taskId, reason);
|
|
15869
|
+
this.inFlightBlobAborts.get(taskId)?.abort();
|
|
15272
15870
|
return;
|
|
15273
15871
|
}
|
|
15274
15872
|
if (active.finalizationStarted) {
|
|
@@ -15279,6 +15877,7 @@ var TaskRunner = class {
|
|
|
15279
15877
|
await active.semanticTerminalSettled;
|
|
15280
15878
|
return;
|
|
15281
15879
|
}
|
|
15880
|
+
active.blobAbort.abort();
|
|
15282
15881
|
try {
|
|
15283
15882
|
await active.session.interrupt();
|
|
15284
15883
|
} catch {
|
|
@@ -15778,7 +16377,7 @@ var TaskRunner = class {
|
|
|
15778
16377
|
async failClaimedAgent(taskId, reason, retryable, context) {
|
|
15779
16378
|
const agentRef = context.binding.resolution.agentRef;
|
|
15780
16379
|
const result = await this.retryAgentTerminalEvidence(
|
|
15781
|
-
() => this.deps.agentSessionHandoffs.recordTaskTerminal({
|
|
16380
|
+
() => this.deps.agentHome.mutateExecution(context.binding, () => this.deps.agentSessionHandoffs.recordTaskTerminal({
|
|
15782
16381
|
agentRef,
|
|
15783
16382
|
taskId,
|
|
15784
16383
|
runtimeId: context.runtimeId,
|
|
@@ -15786,7 +16385,7 @@ var TaskRunner = class {
|
|
|
15786
16385
|
leaseId: context.binding.lease.leaseId,
|
|
15787
16386
|
...context.sessionRef === void 0 ? {} : { sessionRef: context.sessionRef },
|
|
15788
16387
|
terminalReason: reason
|
|
15789
|
-
})
|
|
16388
|
+
}))
|
|
15790
16389
|
);
|
|
15791
16390
|
if (!result.ok) {
|
|
15792
16391
|
this.reportAgentTerminalEvidenceFailure({
|
|
@@ -16775,6 +17374,8 @@ var AgentEgressController = class {
|
|
|
16775
17374
|
options;
|
|
16776
17375
|
latest = new AgentLatestValueState();
|
|
16777
17376
|
spools = /* @__PURE__ */ new Map();
|
|
17377
|
+
spoolOpens = /* @__PURE__ */ new Map();
|
|
17378
|
+
reliableAppendTail = Promise.resolve();
|
|
16778
17379
|
latestStatus = emptyLane();
|
|
16779
17380
|
reliableStatus = emptyLane();
|
|
16780
17381
|
drops = [];
|
|
@@ -16829,7 +17430,8 @@ var AgentEgressController = class {
|
|
|
16829
17430
|
return latest === void 0 ? [] : Object.freeze([latest]);
|
|
16830
17431
|
}
|
|
16831
17432
|
async appendReliable(input) {
|
|
16832
|
-
|
|
17433
|
+
const tenantId = this.options.tenantId;
|
|
17434
|
+
if (!this.active || tenantId === void 0) {
|
|
16833
17435
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
16834
17436
|
return { ok: false, reason: "policy_denied" };
|
|
16835
17437
|
}
|
|
@@ -16844,17 +17446,21 @@ var AgentEgressController = class {
|
|
|
16844
17446
|
return { ok: false, reason: "sanitizer_rejected" };
|
|
16845
17447
|
}
|
|
16846
17448
|
try {
|
|
16847
|
-
const
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
17449
|
+
const spoolOpen = this.spoolFor(input.homeDir, input.agentRef);
|
|
17450
|
+
return await this.withAppendTail(async () => {
|
|
17451
|
+
const spool = await spoolOpen;
|
|
17452
|
+
this.bindSpool(input.agentRef, spool, spoolOpen);
|
|
17453
|
+
const record3 = await spool.append({
|
|
17454
|
+
agentRef: input.agentRef,
|
|
17455
|
+
tenantId,
|
|
17456
|
+
policyRevision: this.options.policy.policyRevision,
|
|
17457
|
+
payload,
|
|
17458
|
+
sessionRef: input.sessionRef,
|
|
17459
|
+
...input.taskId === void 0 ? {} : { taskId: input.taskId },
|
|
17460
|
+
...input.eventId === void 0 ? {} : { eventId: input.eventId }
|
|
17461
|
+
}, this.options.policy, this.tenantPendingBytes());
|
|
17462
|
+
return { ok: true, record: record3 };
|
|
17463
|
+
});
|
|
16858
17464
|
} catch (error) {
|
|
16859
17465
|
const reason = error instanceof AgentReliableQuotaError ? error.reason : "backpressure";
|
|
16860
17466
|
this.noteDrop("reliable", reason, input.agentRef);
|
|
@@ -16867,21 +17473,26 @@ var AgentEgressController = class {
|
|
|
16867
17473
|
* with `wireType: agent.content.receipt` before any transport attempt.
|
|
16868
17474
|
*/
|
|
16869
17475
|
async appendContentReceipt(input) {
|
|
16870
|
-
|
|
17476
|
+
const tenantId = this.options.tenantId;
|
|
17477
|
+
if (!this.active || tenantId === void 0) {
|
|
16871
17478
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
16872
17479
|
return { ok: false, reason: "policy_denied" };
|
|
16873
17480
|
}
|
|
16874
17481
|
try {
|
|
16875
|
-
const
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
17482
|
+
const spoolOpen = this.spoolFor(input.homeDir, input.agentRef);
|
|
17483
|
+
return await this.withAppendTail(async () => {
|
|
17484
|
+
const spool = await spoolOpen;
|
|
17485
|
+
this.bindSpool(input.agentRef, spool, spoolOpen);
|
|
17486
|
+
const record3 = await spool.appendContentReceipt({
|
|
17487
|
+
agentRef: input.agentRef,
|
|
17488
|
+
tenantId,
|
|
17489
|
+
policyRevision: this.options.policy.policyRevision,
|
|
17490
|
+
sessionRef: input.payload.sessionRef,
|
|
17491
|
+
payload: input.payload,
|
|
17492
|
+
...input.taskId === void 0 ? {} : { taskId: input.taskId }
|
|
17493
|
+
}, this.options.policy, this.tenantPendingBytes());
|
|
17494
|
+
return { ok: true, record: record3 };
|
|
17495
|
+
});
|
|
16885
17496
|
} catch (error) {
|
|
16886
17497
|
const reason = error instanceof AgentReliableQuotaError ? error.reason : "backpressure";
|
|
16887
17498
|
this.noteDrop("reliable", reason, input.agentRef);
|
|
@@ -16956,20 +17567,59 @@ var AgentEgressController = class {
|
|
|
16956
17567
|
}
|
|
16957
17568
|
return this.reliableRecords();
|
|
16958
17569
|
}
|
|
16959
|
-
|
|
17570
|
+
spoolFor(homeDir, agentRef) {
|
|
16960
17571
|
const key = agentKey(agentRef);
|
|
16961
17572
|
const existing = this.spools.get(key);
|
|
16962
|
-
if (existing)
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
17573
|
+
if (existing) {
|
|
17574
|
+
if (existing.homeDir !== homeDir) throw new Error("Agent reliable spool is already bound to a different home");
|
|
17575
|
+
return Promise.resolve(existing);
|
|
17576
|
+
}
|
|
17577
|
+
const inFlight = this.spoolOpens.get(key);
|
|
17578
|
+
if (inFlight !== void 0) {
|
|
17579
|
+
if (inFlight.homeDir !== homeDir) throw new Error("Agent reliable spool opening is already bound to a different home");
|
|
17580
|
+
return inFlight.promise;
|
|
17581
|
+
}
|
|
17582
|
+
const opened = (async () => {
|
|
17583
|
+
const spool = await AgentReliableSpool.open(homeDir);
|
|
17584
|
+
if (spool.records().some((record3) => !sameAgent(record3.agentRef, agentRef))) {
|
|
17585
|
+
throw new Error("Agent-local reliable spool has a different AgentRef");
|
|
17586
|
+
}
|
|
17587
|
+
return spool;
|
|
17588
|
+
})();
|
|
17589
|
+
const slot = { homeDir, promise: opened };
|
|
17590
|
+
this.spoolOpens.set(key, slot);
|
|
17591
|
+
void opened.catch(() => {
|
|
17592
|
+
if (this.spoolOpens.get(key) === slot) this.spoolOpens.delete(key);
|
|
17593
|
+
});
|
|
17594
|
+
return opened;
|
|
17595
|
+
}
|
|
17596
|
+
bindSpool(agentRef, spool, spoolOpen) {
|
|
17597
|
+
const key = agentKey(agentRef);
|
|
17598
|
+
const existing = this.spools.get(key);
|
|
17599
|
+
try {
|
|
17600
|
+
if (existing && existing !== spool) throw new Error("multiple Agent egress spools claim the same AgentRef");
|
|
17601
|
+
this.spools.set(key, spool);
|
|
17602
|
+
} finally {
|
|
17603
|
+
const slot = this.spoolOpens.get(key);
|
|
17604
|
+
if (slot?.promise === spoolOpen) this.spoolOpens.delete(key);
|
|
16966
17605
|
}
|
|
16967
|
-
this.spools.set(key, spool);
|
|
16968
|
-
return spool;
|
|
16969
17606
|
}
|
|
16970
17607
|
tenantPendingBytes() {
|
|
16971
17608
|
return this.reliableRecords().reduce((total, record3) => total + record3.byteCount, 0);
|
|
16972
17609
|
}
|
|
17610
|
+
async withAppendTail(operation) {
|
|
17611
|
+
const previous = this.reliableAppendTail;
|
|
17612
|
+
let release;
|
|
17613
|
+
this.reliableAppendTail = new Promise((resolve) => {
|
|
17614
|
+
release = resolve;
|
|
17615
|
+
});
|
|
17616
|
+
try {
|
|
17617
|
+
await previous;
|
|
17618
|
+
return await operation();
|
|
17619
|
+
} finally {
|
|
17620
|
+
release();
|
|
17621
|
+
}
|
|
17622
|
+
}
|
|
16973
17623
|
noteDrop(lane, reason, agentRef, countDrop = true) {
|
|
16974
17624
|
const laneStatus = lane === "latest-value" ? this.latestStatus : this.reliableStatus;
|
|
16975
17625
|
if (countDrop) laneStatus.dropped += 1;
|
|
@@ -18241,6 +18891,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
18241
18891
|
const observer = new DaemonObserver();
|
|
18242
18892
|
const approvalRegistry = new ApprovalRegistry();
|
|
18243
18893
|
let connection;
|
|
18894
|
+
let blobLifecycleAbort;
|
|
18244
18895
|
let connectionState = "closed";
|
|
18245
18896
|
let daemonStarted = false;
|
|
18246
18897
|
let tenantRebinding = false;
|
|
@@ -18259,6 +18910,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
18259
18910
|
function buildAuthManager() {
|
|
18260
18911
|
return new AuthManager({
|
|
18261
18912
|
serverUrl: config.serverUrl,
|
|
18913
|
+
authRequestDeadlineMs: config.authRequestDeadlineMs,
|
|
18262
18914
|
store,
|
|
18263
18915
|
deviceName: config.deviceName,
|
|
18264
18916
|
machineId: config.machineId ?? (() => resolveMachineId({ productId: config.productId })),
|
|
@@ -18276,7 +18928,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
18276
18928
|
assertServerUrlAllowed(config.serverUrl, { dangerouslyAllowInsecureRemote: true });
|
|
18277
18929
|
const reason = err instanceof Error ? err.message : String(err);
|
|
18278
18930
|
console.warn(
|
|
18279
|
-
`[byok/client] WARNING: dangerouslyAllowInsecureRemote is set \u2014 proceeding with an insecure server URL (${config.serverUrl}). Device credentials and task data will be sent in the clear to a non-loopback host. This should never be used in a real deployment. (${reason})`
|
|
18931
|
+
`[byok/client] WARNING: dangerouslyAllowInsecureRemote is set \u2014 proceeding with an insecure server URL (${formatServerUrl(config.serverUrl)}). Device credentials and task data will be sent in the clear to a non-loopback host. This should never be used in a real deployment. (${reason})`
|
|
18280
18932
|
);
|
|
18281
18933
|
}
|
|
18282
18934
|
}
|
|
@@ -18436,9 +19088,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
18436
19088
|
await gitWorkspaceStore?.initialize();
|
|
18437
19089
|
await gitWorkspaceStore?.reconcile();
|
|
18438
19090
|
}
|
|
19091
|
+
blobLifecycleAbort = new AbortController();
|
|
18439
19092
|
const [runtimes, blobClient] = await Promise.all([
|
|
18440
19093
|
detectRuntimes(adapters),
|
|
18441
|
-
Promise.resolve(new BlobClient(config.serverUrl, auth))
|
|
19094
|
+
Promise.resolve(new BlobClient(config.serverUrl, auth, { signal: blobLifecycleAbort.signal }))
|
|
18442
19095
|
]);
|
|
18443
19096
|
observer.noteRuntimesDetected(runtimes);
|
|
18444
19097
|
detectedRuntimeFacts = runtimes;
|
|
@@ -18867,6 +19520,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
18867
19520
|
}
|
|
18868
19521
|
async function runShutdownSequence(reason, opts = {}) {
|
|
18869
19522
|
shuttingDown = true;
|
|
19523
|
+
blobLifecycleAbort?.abort();
|
|
19524
|
+
blobLifecycleAbort = void 0;
|
|
18870
19525
|
const errors = [];
|
|
18871
19526
|
let mutationBarrierComplete = hostedStorageInitializationBarrierComplete;
|
|
18872
19527
|
if (!hostedStorageInitializationBarrierComplete) {
|
|
@@ -20687,6 +21342,6 @@ function createServiceLifecycle(def, opts = {}) {
|
|
|
20687
21342
|
}
|
|
20688
21343
|
}
|
|
20689
21344
|
|
|
20690
|
-
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, 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, 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, RuntimeDisposalFailure, RuntimeExecutionFailure, 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, encodeTeamMemberContext, ensureSecureDir, freezeRuntimeAdapterDescriptor, generateLaunchdPlist, generateSystemdUnit, generateWinswXml, installSkillPacks, isAgentMemorySecureFilesystemAvailable, isGitWorkspaceConfig, isRuntimeDisposalFailure, isRuntimeExecutionFailure, journalHash, listInstalledSkillPacks, localStateRelocation, nodeAgentProgram, openTeamTmuxView, prependGitWorkspaceGuidance, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, projectSkillPack, readDeviceEnrollmentStatus, requestDeviceAssertion, resolveLocalAgentReleaseIdentity, resolveLocalStoragePolicy, resolveSdkReservedHelperBin, runSdkReservedHelperCommand, sanitizeServiceName, sealRuntimeOperationManifest, skillPacksRoot, stableAgentHomeOwnerId, validateAgentRef };
|
|
21345
|
+
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, 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, 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, encodeTeamMemberContext, ensureSecureDir, freezeRuntimeAdapterDescriptor, generateLaunchdPlist, generateSystemdUnit, generateWinswXml, installSkillPacks, isAgentMemorySecureFilesystemAvailable, isGitWorkspaceConfig, isRuntimeDisposalFailure, isRuntimeExecutionFailure, journalHash, listInstalledSkillPacks, localStateRelocation, nodeAgentProgram, openTeamTmuxView, prependGitWorkspaceGuidance, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, projectSkillPack, readDeviceEnrollmentStatus, requestDeviceAssertion, resolveLocalAgentReleaseIdentity, resolveLocalStoragePolicy, resolveSdkReservedHelperBin, runSdkReservedHelperCommand, sanitizeServiceName, sealRuntimeOperationManifest, skillPacksRoot, stableAgentHomeOwnerId, validateAgentRef };
|
|
20691
21346
|
//# sourceMappingURL=index.js.map
|
|
20692
21347
|
//# sourceMappingURL=index.js.map
|