@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/bin/byok-agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID, createHash, randomBytes, timingSafeEqual, createHmac, creat
|
|
|
3
3
|
import { readFileSync, promises, linkSync, fstatSync, lstatSync, unlinkSync, constants, readSync, openSync, writeFileSync, fchmodSync, fsyncSync, closeSync, opendirSync, existsSync, realpathSync, mkdirSync, renameSync, chmodSync, statSync, readdirSync } from 'fs';
|
|
4
4
|
import * as path3 from 'path';
|
|
5
5
|
import path3__default, { isAbsolute, join } from 'path';
|
|
6
|
-
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, AgentHomeProjectionPayloadSchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, 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
|
|
6
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, AgentHomeProjectionPayloadSchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, 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';
|
|
7
7
|
import net, { createServer, createConnection } from 'net';
|
|
8
8
|
import * as os from 'os';
|
|
9
9
|
import os__default from 'os';
|
|
@@ -768,6 +768,120 @@ var AgentHomeLeaseManager = class _AgentHomeLeaseManager {
|
|
|
768
768
|
}
|
|
769
769
|
}
|
|
770
770
|
};
|
|
771
|
+
function executionKey(input) {
|
|
772
|
+
const value = input.sessionRef === void 0 ? input.taskId : input.sessionRef;
|
|
773
|
+
const label = input.sessionRef === void 0 ? "taskId" : "sessionRef";
|
|
774
|
+
if (typeof value !== "string" || value.length === 0 || /[\u0000\r\n]/u.test(value)) {
|
|
775
|
+
throw new AgentHomeResolutionError(`Agent execution ${label} must be a non-empty single-line string`);
|
|
776
|
+
}
|
|
777
|
+
return `${input.sessionRef === void 0 ? "task" : "session"}\0${value}`;
|
|
778
|
+
}
|
|
779
|
+
var AgentHomeExecutionLeaseManager = class _AgentHomeExecutionLeaseManager {
|
|
780
|
+
constructor(manager) {
|
|
781
|
+
this.manager = manager;
|
|
782
|
+
}
|
|
783
|
+
manager;
|
|
784
|
+
static groups = /* @__PURE__ */ new Map();
|
|
785
|
+
static queues = /* @__PURE__ */ new Map();
|
|
786
|
+
async acquire(resolution, input) {
|
|
787
|
+
const initialKey = executionKey(input);
|
|
788
|
+
return this.exclusive(resolution.canonicalHome, async () => {
|
|
789
|
+
let group = _AgentHomeExecutionLeaseManager.groups.get(resolution.canonicalHome);
|
|
790
|
+
if (group === void 0) {
|
|
791
|
+
const baseLease = await this.manager.acquire(resolution);
|
|
792
|
+
group = {
|
|
793
|
+
manager: this.manager,
|
|
794
|
+
baseLease,
|
|
795
|
+
agentId: resolution.agentRef.agentId,
|
|
796
|
+
leasesByKey: /* @__PURE__ */ new Map()
|
|
797
|
+
};
|
|
798
|
+
_AgentHomeExecutionLeaseManager.groups.set(resolution.canonicalHome, group);
|
|
799
|
+
} else if (group.manager !== this.manager || group.agentId !== resolution.agentRef.agentId) {
|
|
800
|
+
throw new AgentHomeBusyError(`Agent home ${resolution.canonicalHome} is active under another execution owner`);
|
|
801
|
+
}
|
|
802
|
+
if (group.leasesByKey.has(initialKey)) {
|
|
803
|
+
throw new AgentHomeBusyError(`Agent session already has an active execution lease in ${resolution.canonicalHome}`);
|
|
804
|
+
}
|
|
805
|
+
const leaseId = randomUUID();
|
|
806
|
+
group.leasesByKey.set(initialKey, leaseId);
|
|
807
|
+
let currentKey = initialKey;
|
|
808
|
+
let sessionBound = input.sessionRef !== void 0;
|
|
809
|
+
let released = false;
|
|
810
|
+
return Object.freeze({
|
|
811
|
+
leaseId,
|
|
812
|
+
agentRef: resolution.agentRef,
|
|
813
|
+
canonicalHome: resolution.canonicalHome,
|
|
814
|
+
cwd: resolution.canonicalHome,
|
|
815
|
+
homeIdentity: group.baseLease.homeIdentity,
|
|
816
|
+
bindSession: async (sessionRef) => {
|
|
817
|
+
const nextKey = executionKey({ taskId: input.taskId, sessionRef });
|
|
818
|
+
await this.exclusive(resolution.canonicalHome, async () => {
|
|
819
|
+
if (released) throw new AgentHomeBusyError(`Agent execution lease ${leaseId} is already released`);
|
|
820
|
+
const currentGroup = _AgentHomeExecutionLeaseManager.groups.get(resolution.canonicalHome);
|
|
821
|
+
if (currentGroup !== group || currentGroup.leasesByKey.get(currentKey) !== leaseId) {
|
|
822
|
+
throw new AgentHomeBusyError(`Agent execution lease ${leaseId} is no longer owned by this process`);
|
|
823
|
+
}
|
|
824
|
+
if (nextKey === currentKey) return;
|
|
825
|
+
if (sessionBound) {
|
|
826
|
+
throw new AgentHomeBusyError(
|
|
827
|
+
`Agent session execution lease ${leaseId} cannot rebind to a different runtime session`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
if (currentGroup.leasesByKey.has(nextKey)) {
|
|
831
|
+
throw new AgentHomeBusyError(`Agent session already has an active execution lease in ${resolution.canonicalHome}`);
|
|
832
|
+
}
|
|
833
|
+
currentGroup.leasesByKey.set(nextKey, leaseId);
|
|
834
|
+
currentGroup.leasesByKey.delete(currentKey);
|
|
835
|
+
currentKey = nextKey;
|
|
836
|
+
sessionBound = true;
|
|
837
|
+
});
|
|
838
|
+
},
|
|
839
|
+
release: async () => {
|
|
840
|
+
await this.exclusive(resolution.canonicalHome, async () => {
|
|
841
|
+
if (released) return;
|
|
842
|
+
const currentGroup = _AgentHomeExecutionLeaseManager.groups.get(resolution.canonicalHome);
|
|
843
|
+
if (currentGroup !== group || currentGroup.leasesByKey.get(currentKey) !== leaseId) {
|
|
844
|
+
released = true;
|
|
845
|
+
throw new AgentHomeBusyError(`Agent execution lease ${leaseId} is no longer owned by this process`);
|
|
846
|
+
}
|
|
847
|
+
currentGroup.leasesByKey.delete(currentKey);
|
|
848
|
+
released = true;
|
|
849
|
+
if (currentGroup.leasesByKey.size === 0) {
|
|
850
|
+
_AgentHomeExecutionLeaseManager.groups.delete(resolution.canonicalHome);
|
|
851
|
+
await currentGroup.baseLease.release();
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
async mutate(binding, operation) {
|
|
859
|
+
return this.exclusive(binding.resolution.canonicalHome, async () => {
|
|
860
|
+
const group = _AgentHomeExecutionLeaseManager.groups.get(binding.resolution.canonicalHome);
|
|
861
|
+
if (group === void 0 || group.manager !== this.manager || ![...group.leasesByKey.values()].includes(binding.lease.leaseId)) {
|
|
862
|
+
throw new AgentHomeBusyError("Agent execution lease does not own this home mutation");
|
|
863
|
+
}
|
|
864
|
+
return operation();
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
async exclusive(canonicalHome, operation) {
|
|
868
|
+
const prior = _AgentHomeExecutionLeaseManager.queues.get(canonicalHome) ?? Promise.resolve();
|
|
869
|
+
let release;
|
|
870
|
+
const tail = new Promise((resolve) => {
|
|
871
|
+
release = resolve;
|
|
872
|
+
});
|
|
873
|
+
_AgentHomeExecutionLeaseManager.queues.set(canonicalHome, tail);
|
|
874
|
+
await prior;
|
|
875
|
+
try {
|
|
876
|
+
return await operation();
|
|
877
|
+
} finally {
|
|
878
|
+
release();
|
|
879
|
+
if (_AgentHomeExecutionLeaseManager.queues.get(canonicalHome) === tail) {
|
|
880
|
+
_AgentHomeExecutionLeaseManager.queues.delete(canonicalHome);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
771
885
|
async function initializeAgentHome(resolution) {
|
|
772
886
|
await ensureDirectoryNoSymlink(
|
|
773
887
|
resolution.canonicalHome,
|
|
@@ -843,10 +957,12 @@ var AgentHomeManager = class {
|
|
|
843
957
|
layout;
|
|
844
958
|
projection;
|
|
845
959
|
leaseManager;
|
|
960
|
+
executionLeaseManager;
|
|
846
961
|
constructor(options) {
|
|
847
962
|
this.layout = new AgentHomeLayout(options.hostStorageRoot);
|
|
848
963
|
this.projection = options.projection;
|
|
849
964
|
this.leaseManager = options.leaseManager ?? new AgentHomeLeaseManager();
|
|
965
|
+
this.executionLeaseManager = new AgentHomeExecutionLeaseManager(this.leaseManager);
|
|
850
966
|
}
|
|
851
967
|
async prepare(agentRef) {
|
|
852
968
|
const binding = await this.acquire(agentRef);
|
|
@@ -873,12 +989,25 @@ var AgentHomeManager = class {
|
|
|
873
989
|
const lease = await this.leaseManager.acquire(resolution);
|
|
874
990
|
return Object.freeze({ resolution, lease });
|
|
875
991
|
}
|
|
992
|
+
async acquireExecution(agentRef, input) {
|
|
993
|
+
const resolution = await this.layout.resolve(agentRef);
|
|
994
|
+
const lease = await this.executionLeaseManager.acquire(resolution, input);
|
|
995
|
+
return Object.freeze({ resolution, lease });
|
|
996
|
+
}
|
|
876
997
|
/** Initialize only after any requested session exact-match has succeeded. */
|
|
877
998
|
async initialize(binding) {
|
|
878
|
-
|
|
999
|
+
await this.initializeResolved(binding.resolution, binding.lease.cwd);
|
|
1000
|
+
}
|
|
1001
|
+
async initializeExecution(binding) {
|
|
1002
|
+
await this.mutateExecution(binding, () => this.initializeResolved(binding.resolution, binding.lease.cwd));
|
|
1003
|
+
}
|
|
1004
|
+
async mutateExecution(binding, operation) {
|
|
1005
|
+
return this.executionLeaseManager.mutate(binding, operation);
|
|
1006
|
+
}
|
|
1007
|
+
async initializeResolved(resolution, cwd) {
|
|
879
1008
|
await initializeAgentHome(resolution);
|
|
880
1009
|
const prepare = this.projection?.prepare;
|
|
881
|
-
if (prepare !== void 0) await prepare({ ...resolution, cwd
|
|
1010
|
+
if (prepare !== void 0) await prepare({ ...resolution, cwd });
|
|
882
1011
|
if (await promises.realpath(resolution.homeDir) !== resolution.canonicalHome) {
|
|
883
1012
|
throw new AgentHomeResolutionError("Agent projection changed the canonical home path");
|
|
884
1013
|
}
|
|
@@ -6371,9 +6500,18 @@ function assertRecord(value) {
|
|
|
6371
6500
|
throw new DeviceCredentialStoreError("OS credential entry has an incomplete device credential");
|
|
6372
6501
|
}
|
|
6373
6502
|
}
|
|
6374
|
-
function
|
|
6375
|
-
|
|
6376
|
-
|
|
6503
|
+
function isFirstPairingAttempt(value) {
|
|
6504
|
+
return "kind" in value && value.kind === "first-pairing-attempt-v1";
|
|
6505
|
+
}
|
|
6506
|
+
function assertFirstPairingAttempt(value) {
|
|
6507
|
+
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) {
|
|
6508
|
+
throw new DeviceCredentialStoreError("OS credential entry has an invalid first-pairing attempt shape");
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
function encode(authority) {
|
|
6512
|
+
if (isFirstPairingAttempt(authority)) assertFirstPairingAttempt(authority);
|
|
6513
|
+
else assertRecord(authority);
|
|
6514
|
+
const encoded = Buffer.from(JSON.stringify(authority), "utf8").toString("base64");
|
|
6377
6515
|
const value = `${ENCODED_PREFIX}${encoded}`;
|
|
6378
6516
|
if (Buffer.byteLength(value, "utf8") > 2400) {
|
|
6379
6517
|
throw new DeviceCredentialStoreError("device credential exceeds the OS credential entry bound");
|
|
@@ -6402,6 +6540,16 @@ function decode(value) {
|
|
|
6402
6540
|
} catch {
|
|
6403
6541
|
throw new DeviceCredentialStoreError("OS credential entry is not valid JSON");
|
|
6404
6542
|
}
|
|
6543
|
+
if (typeof parsed === "object" && parsed !== null && parsed.kind === "first-pairing-attempt-v1") {
|
|
6544
|
+
assertFirstPairingAttempt(parsed);
|
|
6545
|
+
return Object.freeze({
|
|
6546
|
+
kind: parsed.kind,
|
|
6547
|
+
deviceName: parsed.deviceName,
|
|
6548
|
+
devicePublicKey: parsed.devicePublicKey,
|
|
6549
|
+
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
6550
|
+
...parsed.machineId === void 0 ? {} : { machineId: parsed.machineId }
|
|
6551
|
+
});
|
|
6552
|
+
}
|
|
6405
6553
|
assertRecord(parsed);
|
|
6406
6554
|
return Object.freeze({
|
|
6407
6555
|
deviceId: parsed.deviceId,
|
|
@@ -6432,6 +6580,18 @@ var DeviceCredentialStore = class {
|
|
|
6432
6580
|
this.#run = options.commandRunner ?? runDeviceCommand;
|
|
6433
6581
|
}
|
|
6434
6582
|
async read() {
|
|
6583
|
+
const authority = await this.#readAuthority();
|
|
6584
|
+
return authority === void 0 || isFirstPairingAttempt(authority) ? void 0 : authority;
|
|
6585
|
+
}
|
|
6586
|
+
async readFirstPairingAttempt() {
|
|
6587
|
+
const authority = await this.#readAuthority();
|
|
6588
|
+
return authority !== void 0 && isFirstPairingAttempt(authority) ? authority : void 0;
|
|
6589
|
+
}
|
|
6590
|
+
async saveFirstPairingAttempt(attempt) {
|
|
6591
|
+
assertFirstPairingAttempt(attempt);
|
|
6592
|
+
await this.#replaceAuthority(attempt);
|
|
6593
|
+
}
|
|
6594
|
+
async #readAuthority() {
|
|
6435
6595
|
const result = await this.#invoke("read");
|
|
6436
6596
|
if (result.exitCode === NOT_FOUND || this.#platform === "linux" && result.exitCode === 1 && result.stderr.trim().length === 0) return void 0;
|
|
6437
6597
|
if (result.exitCode === 127) throw new DeviceCredentialStoreUnavailableError();
|
|
@@ -6444,6 +6604,12 @@ var DeviceCredentialStore = class {
|
|
|
6444
6604
|
}
|
|
6445
6605
|
async replace(record) {
|
|
6446
6606
|
const encoded = encode(record);
|
|
6607
|
+
await this.#replaceEncoded(encoded);
|
|
6608
|
+
}
|
|
6609
|
+
async #replaceAuthority(authority) {
|
|
6610
|
+
await this.#replaceEncoded(encode(authority));
|
|
6611
|
+
}
|
|
6612
|
+
async #replaceEncoded(encoded) {
|
|
6447
6613
|
const result = await this.#invoke("replace", encoded);
|
|
6448
6614
|
if (result.exitCode === 127) throw new DeviceCredentialStoreUnavailableError();
|
|
6449
6615
|
if (result.exitCode !== 0) {
|
|
@@ -6454,7 +6620,7 @@ var DeviceCredentialStore = class {
|
|
|
6454
6620
|
}
|
|
6455
6621
|
/** Returns true only after the sole secret authority is confirmed absent. */
|
|
6456
6622
|
async clear() {
|
|
6457
|
-
const before = await this
|
|
6623
|
+
const before = await this.#readAuthority();
|
|
6458
6624
|
if (before === void 0) return false;
|
|
6459
6625
|
const result = await this.#invoke("clear");
|
|
6460
6626
|
if (result.exitCode === 127) throw new DeviceCredentialStoreUnavailableError();
|
|
@@ -6463,7 +6629,7 @@ var DeviceCredentialStore = class {
|
|
|
6463
6629
|
`operating-system credential provider could not clear device credentials${providerDiagnostic(result.stderr)}`
|
|
6464
6630
|
);
|
|
6465
6631
|
}
|
|
6466
|
-
if (await this
|
|
6632
|
+
if (await this.#readAuthority() !== void 0) {
|
|
6467
6633
|
throw new DeviceCredentialStoreError("operating-system credential provider reported deletion but device credentials remain");
|
|
6468
6634
|
}
|
|
6469
6635
|
return true;
|
|
@@ -6538,17 +6704,24 @@ var DeviceCredentialStore = class {
|
|
|
6538
6704
|
}
|
|
6539
6705
|
};
|
|
6540
6706
|
var InMemoryDeviceCredentialStore = class {
|
|
6541
|
-
#
|
|
6707
|
+
#authority;
|
|
6542
6708
|
async read() {
|
|
6543
|
-
return this.#
|
|
6709
|
+
return this.#authority === void 0 || isFirstPairingAttempt(this.#authority) ? void 0 : Object.freeze({ ...this.#authority });
|
|
6710
|
+
}
|
|
6711
|
+
async readFirstPairingAttempt() {
|
|
6712
|
+
return this.#authority !== void 0 && isFirstPairingAttempt(this.#authority) ? Object.freeze({ ...this.#authority }) : void 0;
|
|
6713
|
+
}
|
|
6714
|
+
async saveFirstPairingAttempt(attempt) {
|
|
6715
|
+
assertFirstPairingAttempt(attempt);
|
|
6716
|
+
this.#authority = Object.freeze({ ...attempt });
|
|
6544
6717
|
}
|
|
6545
6718
|
async replace(record) {
|
|
6546
6719
|
assertRecord(record);
|
|
6547
|
-
this.#
|
|
6720
|
+
this.#authority = Object.freeze({ ...record });
|
|
6548
6721
|
}
|
|
6549
6722
|
async clear() {
|
|
6550
|
-
const had = this.#
|
|
6551
|
-
this.#
|
|
6723
|
+
const had = this.#authority !== void 0;
|
|
6724
|
+
this.#authority = void 0;
|
|
6552
6725
|
return had;
|
|
6553
6726
|
}
|
|
6554
6727
|
};
|
|
@@ -6820,6 +6993,10 @@ function describeEndpoint(transport, url) {
|
|
|
6820
6993
|
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
6821
6994
|
return { transport, host: parsed.host, path: parsed.pathname };
|
|
6822
6995
|
}
|
|
6996
|
+
function formatServerUrl(value) {
|
|
6997
|
+
const url = typeof value === "string" ? new URL(value) : value;
|
|
6998
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
6999
|
+
}
|
|
6823
7000
|
var InsecureServerUrlError = class extends Error {
|
|
6824
7001
|
constructor(message) {
|
|
6825
7002
|
super(message);
|
|
@@ -6838,9 +7015,10 @@ function assertServerUrlAllowed(rawUrl, opts = {}) {
|
|
|
6838
7015
|
let url;
|
|
6839
7016
|
try {
|
|
6840
7017
|
url = new URL(rawUrl);
|
|
6841
|
-
} catch
|
|
6842
|
-
throw new InsecureServerUrlError(
|
|
7018
|
+
} catch {
|
|
7019
|
+
throw new InsecureServerUrlError("invalid server URL");
|
|
6843
7020
|
}
|
|
7021
|
+
const endpoint = formatServerUrl(url);
|
|
6844
7022
|
switch (url.protocol) {
|
|
6845
7023
|
case "https:":
|
|
6846
7024
|
case "wss:":
|
|
@@ -6849,11 +7027,11 @@ function assertServerUrlAllowed(rawUrl, opts = {}) {
|
|
|
6849
7027
|
case "ws:":
|
|
6850
7028
|
if (opts.dangerouslyAllowInsecureRemote || isLoopbackHostname(url.hostname)) return;
|
|
6851
7029
|
throw new InsecureServerUrlError(
|
|
6852
|
-
`refusing to connect to "${
|
|
7030
|
+
`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.`
|
|
6853
7031
|
);
|
|
6854
7032
|
default:
|
|
6855
7033
|
throw new InsecureServerUrlError(
|
|
6856
|
-
`refusing to connect to "${
|
|
7034
|
+
`refusing to connect to "${endpoint}" \u2014 unsupported scheme "${url.protocol}" (expected http:, https:, ws:, or wss:).`
|
|
6857
7035
|
);
|
|
6858
7036
|
}
|
|
6859
7037
|
}
|
|
@@ -6865,12 +7043,22 @@ var DeviceRevokedError = class extends Error {
|
|
|
6865
7043
|
this.name = "DeviceRevokedError";
|
|
6866
7044
|
}
|
|
6867
7045
|
};
|
|
7046
|
+
var AuthRequestAbortedError = class extends Error {
|
|
7047
|
+
constructor(reason) {
|
|
7048
|
+
super(reason === "deadline" ? "authentication request exceeded its deadline" : "authentication request was cancelled during shutdown");
|
|
7049
|
+
this.reason = reason;
|
|
7050
|
+
this.name = "AuthRequestAbortedError";
|
|
7051
|
+
}
|
|
7052
|
+
reason;
|
|
7053
|
+
};
|
|
6868
7054
|
var ASSUMED_PAIR_TOKEN_TTL_MS = 45 * 60 * 1e3;
|
|
6869
7055
|
var RENEW_MARGIN_MS = 60 * 1e3;
|
|
7056
|
+
var DEFAULT_AUTH_REQUEST_DEADLINE_MS = 15e3;
|
|
6870
7057
|
var AuthManager = class {
|
|
6871
7058
|
constructor(opts) {
|
|
6872
7059
|
this.opts = opts;
|
|
6873
7060
|
this.credentials = opts.credentials ?? opts.store.credentials;
|
|
7061
|
+
this.requestDeadlineMs = resolveRequestDeadlineMs(opts.authRequestDeadlineMs);
|
|
6874
7062
|
}
|
|
6875
7063
|
opts;
|
|
6876
7064
|
record;
|
|
@@ -6880,7 +7068,10 @@ var AuthManager = class {
|
|
|
6880
7068
|
stopped = false;
|
|
6881
7069
|
pairing = false;
|
|
6882
7070
|
credentialMutationTail = Promise.resolve();
|
|
7071
|
+
/** The sole cancellation authority for the request currently inside the serialized credential mutation. */
|
|
7072
|
+
activeRequest;
|
|
6883
7073
|
credentials;
|
|
7074
|
+
requestDeadlineMs;
|
|
6884
7075
|
get deviceId() {
|
|
6885
7076
|
return this.record?.deviceId;
|
|
6886
7077
|
}
|
|
@@ -6916,23 +7107,51 @@ var AuthManager = class {
|
|
|
6916
7107
|
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
6917
7108
|
}
|
|
6918
7109
|
}
|
|
6919
|
-
const
|
|
7110
|
+
const observedDeviceName = this.opts.deviceName ?? os__default.hostname();
|
|
7111
|
+
const observedMachineId = await this.opts.machineId?.();
|
|
7112
|
+
let keyPair;
|
|
7113
|
+
let pairingDeviceName = observedDeviceName;
|
|
7114
|
+
let pairingMachineId = observedMachineId;
|
|
7115
|
+
if (existing) {
|
|
7116
|
+
keyPair = { privateKey: importPrivateKeyPem(existing.devicePrivateKeyPem), publicKeyBase64Url: existing.devicePublicKey };
|
|
7117
|
+
} else {
|
|
7118
|
+
const firstAttempt = await this.credentials.readFirstPairingAttempt();
|
|
7119
|
+
if (firstAttempt) {
|
|
7120
|
+
keyPair = {
|
|
7121
|
+
privateKey: importPrivateKeyPem(firstAttempt.devicePrivateKeyPem),
|
|
7122
|
+
publicKeyBase64Url: firstAttempt.devicePublicKey
|
|
7123
|
+
};
|
|
7124
|
+
pairingDeviceName = firstAttempt.deviceName;
|
|
7125
|
+
pairingMachineId = firstAttempt.machineId;
|
|
7126
|
+
} else {
|
|
7127
|
+
keyPair = generateDeviceKeyPair();
|
|
7128
|
+
await this.credentials.saveFirstPairingAttempt({
|
|
7129
|
+
kind: "first-pairing-attempt-v1",
|
|
7130
|
+
deviceName: pairingDeviceName,
|
|
7131
|
+
devicePublicKey: keyPair.publicKeyBase64Url,
|
|
7132
|
+
devicePrivateKeyPem: exportPrivateKeyPem(keyPair.privateKey),
|
|
7133
|
+
...pairingMachineId === void 0 ? {} : { machineId: pairingMachineId }
|
|
7134
|
+
});
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
6920
7137
|
const url = new URL(BYOK_PAIR_PATH, toHttpBase(this.opts.serverUrl));
|
|
6921
|
-
const
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
7138
|
+
const body = await this.runRequest(async (signal) => {
|
|
7139
|
+
const res = await fetch(url, {
|
|
7140
|
+
method: "POST",
|
|
7141
|
+
headers: { "content-type": "application/json" },
|
|
7142
|
+
body: JSON.stringify({
|
|
7143
|
+
pairingCode,
|
|
7144
|
+
deviceName: pairingDeviceName,
|
|
7145
|
+
devicePublicKey: keyPair.publicKeyBase64Url,
|
|
7146
|
+
...pairingMachineId === void 0 ? {} : { machineId: pairingMachineId }
|
|
7147
|
+
}),
|
|
7148
|
+
signal
|
|
7149
|
+
});
|
|
7150
|
+
if (!res.ok) {
|
|
7151
|
+
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
7152
|
+
}
|
|
7153
|
+
return PairResponseSchema.parse(await res.json());
|
|
6931
7154
|
});
|
|
6932
|
-
if (!res.ok) {
|
|
6933
|
-
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
6934
|
-
}
|
|
6935
|
-
const body = PairResponseSchema.parse(await res.json());
|
|
6936
7155
|
const metadata = {
|
|
6937
7156
|
deviceId: body.deviceId,
|
|
6938
7157
|
tenantId: body.tenantId,
|
|
@@ -6971,6 +7190,7 @@ var AuthManager = class {
|
|
|
6971
7190
|
this.stopped = true;
|
|
6972
7191
|
if (this.proactiveTimer) clearTimeout(this.proactiveTimer);
|
|
6973
7192
|
this.proactiveTimer = void 0;
|
|
7193
|
+
this.activeRequest?.abort();
|
|
6974
7194
|
await this.credentialMutationTail;
|
|
6975
7195
|
}
|
|
6976
7196
|
async renew() {
|
|
@@ -6987,29 +7207,35 @@ var AuthManager = class {
|
|
|
6987
7207
|
const record = this.record;
|
|
6988
7208
|
const base = toHttpBase(this.opts.serverUrl);
|
|
6989
7209
|
const privateKey = importPrivateKeyPem(record.devicePrivateKeyPem);
|
|
6990
|
-
const
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
7210
|
+
const { nonce } = await this.runRequest(async (signal) => {
|
|
7211
|
+
const challengeRes = await fetch(new URL(BYOK_CHALLENGE_PATH, base), {
|
|
7212
|
+
method: "POST",
|
|
7213
|
+
headers: { "content-type": "application/json" },
|
|
7214
|
+
body: JSON.stringify({ deviceId: record.deviceId }),
|
|
7215
|
+
signal
|
|
7216
|
+
});
|
|
7217
|
+
if (challengeRes.status === 401) this.markRevoked();
|
|
7218
|
+
if (!challengeRes.ok) {
|
|
7219
|
+
throw new Error(
|
|
7220
|
+
`token renewal (challenge) failed: HTTP ${challengeRes.status} ${await safeErrorText(challengeRes)}`.trimEnd()
|
|
7221
|
+
);
|
|
7222
|
+
}
|
|
7223
|
+
return await challengeRes.json();
|
|
6994
7224
|
});
|
|
6995
|
-
if (challengeRes.status === 401) this.markRevoked();
|
|
6996
|
-
if (!challengeRes.ok) {
|
|
6997
|
-
throw new Error(
|
|
6998
|
-
`token renewal (challenge) failed: HTTP ${challengeRes.status} ${await safeErrorText(challengeRes)}`.trimEnd()
|
|
6999
|
-
);
|
|
7000
|
-
}
|
|
7001
|
-
const { nonce } = await challengeRes.json();
|
|
7002
7225
|
const signature = signNonce(privateKey, nonce);
|
|
7003
|
-
const
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7226
|
+
const body = await this.runRequest(async (signal) => {
|
|
7227
|
+
const tokenRes = await fetch(new URL(BYOK_TOKEN_PATH, base), {
|
|
7228
|
+
method: "POST",
|
|
7229
|
+
headers: { "content-type": "application/json" },
|
|
7230
|
+
body: JSON.stringify({ deviceId: record.deviceId, nonce, signature }),
|
|
7231
|
+
signal
|
|
7232
|
+
});
|
|
7233
|
+
if (tokenRes.status === 401) this.markRevoked();
|
|
7234
|
+
if (!tokenRes.ok) {
|
|
7235
|
+
throw new Error(`token renewal (token) failed: HTTP ${tokenRes.status} ${await safeErrorText(tokenRes)}`.trimEnd());
|
|
7236
|
+
}
|
|
7237
|
+
return await tokenRes.json();
|
|
7007
7238
|
});
|
|
7008
|
-
if (tokenRes.status === 401) this.markRevoked();
|
|
7009
|
-
if (!tokenRes.ok) {
|
|
7010
|
-
throw new Error(`token renewal (token) failed: HTTP ${tokenRes.status} ${await safeErrorText(tokenRes)}`.trimEnd());
|
|
7011
|
-
}
|
|
7012
|
-
const body = await tokenRes.json();
|
|
7013
7239
|
const updated = {
|
|
7014
7240
|
...record,
|
|
7015
7241
|
accessToken: body.accessToken,
|
|
@@ -7039,6 +7265,37 @@ var AuthManager = class {
|
|
|
7039
7265
|
timer.unref?.();
|
|
7040
7266
|
this.proactiveTimer = timer;
|
|
7041
7267
|
}
|
|
7268
|
+
/**
|
|
7269
|
+
* Bounds one complete auth exchange rather than fetch alone. Keeping the
|
|
7270
|
+
* controller active through `json()`/`text()` makes a non-cooperative or
|
|
7271
|
+
* partial response body cancellable by the same authority that owns fetch.
|
|
7272
|
+
*/
|
|
7273
|
+
async runRequest(operation) {
|
|
7274
|
+
if (this.stopped) throw new AuthRequestAbortedError("stopped");
|
|
7275
|
+
const controller = new AbortController();
|
|
7276
|
+
this.activeRequest = controller;
|
|
7277
|
+
let deadlineElapsed = false;
|
|
7278
|
+
const deadline = setTimeout(() => {
|
|
7279
|
+
deadlineElapsed = true;
|
|
7280
|
+
controller.abort();
|
|
7281
|
+
}, this.requestDeadlineMs);
|
|
7282
|
+
let rejectOnAbort;
|
|
7283
|
+
const aborted = new Promise((_, reject) => {
|
|
7284
|
+
rejectOnAbort = reject;
|
|
7285
|
+
});
|
|
7286
|
+
const onAbort = () => rejectOnAbort(new AuthRequestAbortedError(deadlineElapsed ? "deadline" : "stopped"));
|
|
7287
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
7288
|
+
try {
|
|
7289
|
+
return await Promise.race([operation(controller.signal), aborted]);
|
|
7290
|
+
} catch (error) {
|
|
7291
|
+
if (controller.signal.aborted) throw new AuthRequestAbortedError(deadlineElapsed ? "deadline" : "stopped");
|
|
7292
|
+
throw error;
|
|
7293
|
+
} finally {
|
|
7294
|
+
clearTimeout(deadline);
|
|
7295
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
7296
|
+
if (this.activeRequest === controller) this.activeRequest = void 0;
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7042
7299
|
async runCredentialMutation(operation) {
|
|
7043
7300
|
const predecessor = this.credentialMutationTail;
|
|
7044
7301
|
let release;
|
|
@@ -7081,6 +7338,13 @@ function resolvePairExpiry(refreshHint) {
|
|
|
7081
7338
|
}
|
|
7082
7339
|
return new Date(Date.now() + ASSUMED_PAIR_TOKEN_TTL_MS).toISOString();
|
|
7083
7340
|
}
|
|
7341
|
+
function resolveRequestDeadlineMs(value) {
|
|
7342
|
+
const deadline = value ?? DEFAULT_AUTH_REQUEST_DEADLINE_MS;
|
|
7343
|
+
if (!Number.isSafeInteger(deadline) || deadline <= 0) {
|
|
7344
|
+
throw new Error("authRequestDeadlineMs must be a positive safe integer");
|
|
7345
|
+
}
|
|
7346
|
+
return deadline;
|
|
7347
|
+
}
|
|
7084
7348
|
async function safeErrorText(res) {
|
|
7085
7349
|
try {
|
|
7086
7350
|
return await res.text();
|
|
@@ -7102,37 +7366,48 @@ function withAuth(init, token) {
|
|
|
7102
7366
|
}
|
|
7103
7367
|
|
|
7104
7368
|
// src/daemon/blob-client.ts
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7369
|
+
var BlobRequestAbortedError = class extends Error {
|
|
7370
|
+
constructor(reason) {
|
|
7371
|
+
super(reason === "deadline" ? "blob request deadline elapsed" : "blob request cancelled");
|
|
7372
|
+
this.reason = reason;
|
|
7373
|
+
this.name = "BlobRequestAbortedError";
|
|
7110
7374
|
}
|
|
7111
|
-
|
|
7375
|
+
reason;
|
|
7376
|
+
};
|
|
7112
7377
|
var BlobClient = class {
|
|
7113
|
-
constructor(serverUrl, auth) {
|
|
7378
|
+
constructor(serverUrl, auth, options = {}) {
|
|
7114
7379
|
this.serverUrl = serverUrl;
|
|
7115
7380
|
this.auth = auth;
|
|
7381
|
+
this.options = options;
|
|
7382
|
+
const requestDeadlineMs = options.requestDeadlineMs ?? 15e3;
|
|
7383
|
+
if (!Number.isSafeInteger(requestDeadlineMs) || requestDeadlineMs <= 0) {
|
|
7384
|
+
throw new Error("BlobClient requestDeadlineMs must be a positive safe integer");
|
|
7385
|
+
}
|
|
7386
|
+
this.requestDeadlineMs = requestDeadlineMs;
|
|
7116
7387
|
}
|
|
7117
7388
|
serverUrl;
|
|
7118
7389
|
auth;
|
|
7390
|
+
options;
|
|
7391
|
+
requestDeadlineMs;
|
|
7119
7392
|
/** `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. */
|
|
7120
|
-
async resolveInstruction(blobRef) {
|
|
7393
|
+
async resolveInstruction(blobRef, options = {}) {
|
|
7121
7394
|
const base = toHttpBase(this.serverUrl);
|
|
7122
|
-
const urlRes = await
|
|
7123
|
-
new URL(byokBlobUrlPath(blobRef.blobId), base),
|
|
7124
|
-
|
|
7125
|
-
this.auth
|
|
7395
|
+
const urlRes = await this.#request(
|
|
7396
|
+
(signal) => authedFetch(new URL(byokBlobUrlPath(blobRef.blobId), base), { method: "GET", signal }, this.auth),
|
|
7397
|
+
options.signal
|
|
7126
7398
|
);
|
|
7127
7399
|
if (!urlRes.ok) {
|
|
7128
|
-
throw new Error(`failed to resolve blob download url: HTTP ${urlRes.status} ${await
|
|
7400
|
+
throw new Error(`failed to resolve blob download url: HTTP ${urlRes.status} ${await this.#safeErrorText(urlRes, options.signal)}`.trimEnd());
|
|
7401
|
+
}
|
|
7402
|
+
const { downloadUrl } = await this.#readJson(urlRes, options.signal);
|
|
7403
|
+
if (typeof downloadUrl !== "string" || downloadUrl.length === 0) {
|
|
7404
|
+
throw new Error("failed to resolve blob download url: response omitted downloadUrl");
|
|
7129
7405
|
}
|
|
7130
|
-
const
|
|
7131
|
-
const contentRes = await fetch(new URL(downloadUrl, base));
|
|
7406
|
+
const contentRes = await this.#request((signal) => fetch(new URL(downloadUrl, base), { signal }), options.signal);
|
|
7132
7407
|
if (!contentRes.ok) {
|
|
7133
7408
|
throw new Error(`failed to download blob content: HTTP ${contentRes.status}`);
|
|
7134
7409
|
}
|
|
7135
|
-
const bytes =
|
|
7410
|
+
const bytes = await this.#readBody(contentRes, options.signal);
|
|
7136
7411
|
const observedHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
7137
7412
|
if (observedHash !== blobRef.contentHash || bytes.length !== blobRef.size) {
|
|
7138
7413
|
throw new Error(
|
|
@@ -7141,63 +7416,71 @@ var BlobClient = class {
|
|
|
7141
7416
|
}
|
|
7142
7417
|
return new TextDecoder().decode(bytes);
|
|
7143
7418
|
}
|
|
7144
|
-
/** `POST /byok/blobs`
|
|
7419
|
+
/** `POST /byok/blobs` -> PUT the bytes to the presigned URL -> finalize into a `BlobRef`. */
|
|
7145
7420
|
async uploadArtifact(content, contentType, options = {}) {
|
|
7146
7421
|
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
7147
7422
|
const contentHash2 = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
7148
7423
|
const base = toHttpBase(this.serverUrl);
|
|
7149
7424
|
const reservationId = options.idempotencyKey ?? `blob_${randomUUID()}`;
|
|
7150
|
-
const createRes = await
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
"content-type": "application/json",
|
|
7156
|
-
|
|
7425
|
+
const createRes = await this.#request(
|
|
7426
|
+
(signal) => authedFetch(
|
|
7427
|
+
new URL(BYOK_BLOBS_PATH, base),
|
|
7428
|
+
{
|
|
7429
|
+
method: "POST",
|
|
7430
|
+
headers: { "content-type": "application/json", "idempotency-key": reservationId },
|
|
7431
|
+
body: JSON.stringify({ size: bytes.length, contentType, contentHash: contentHash2 }),
|
|
7432
|
+
signal
|
|
7157
7433
|
},
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7434
|
+
this.auth
|
|
7435
|
+
),
|
|
7436
|
+
options.signal
|
|
7161
7437
|
);
|
|
7162
7438
|
if (!createRes.ok) {
|
|
7163
|
-
throw new Error(`failed to create blob: HTTP ${createRes.status} ${await
|
|
7439
|
+
throw new Error(`failed to create blob: HTTP ${createRes.status} ${await this.#safeErrorText(createRes, options.signal)}`.trimEnd());
|
|
7440
|
+
}
|
|
7441
|
+
const { blobId, uploadUrl } = await this.#readJson(createRes, options.signal);
|
|
7442
|
+
if (typeof blobId !== "string" || blobId.length === 0 || typeof uploadUrl !== "string" || uploadUrl.length === 0) {
|
|
7443
|
+
throw new Error("failed to create blob: response omitted blobId or uploadUrl");
|
|
7164
7444
|
}
|
|
7165
|
-
const { blobId, uploadUrl } = await createRes.json();
|
|
7166
7445
|
const blobRef = { blobId, contentHash: contentHash2, size: bytes.length, contentType };
|
|
7167
|
-
if (options.idempotencyKey !== void 0 && await this.#hasExactCommittedBlob(base, blobRef)) {
|
|
7446
|
+
if (options.idempotencyKey !== void 0 && await this.#hasExactCommittedBlob(base, blobRef, options.signal)) {
|
|
7168
7447
|
return blobRef;
|
|
7169
7448
|
}
|
|
7170
|
-
const putRes = await
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7449
|
+
const putRes = await this.#request(
|
|
7450
|
+
(signal) => fetch(new URL(uploadUrl, base), {
|
|
7451
|
+
method: "PUT",
|
|
7452
|
+
headers: { "content-type": contentType },
|
|
7453
|
+
body: bytes,
|
|
7454
|
+
signal
|
|
7455
|
+
}),
|
|
7456
|
+
options.signal
|
|
7457
|
+
);
|
|
7175
7458
|
if (!putRes.ok) {
|
|
7176
7459
|
throw new Error(`failed to upload blob content: HTTP ${putRes.status}`);
|
|
7177
7460
|
}
|
|
7178
|
-
|
|
7461
|
+
this.#throwIfAborted(options.signal);
|
|
7462
|
+
await this.#finalize(base, blobId, reservationId, options.signal);
|
|
7179
7463
|
return blobRef;
|
|
7180
7464
|
}
|
|
7181
|
-
async #hasExactCommittedBlob(base, blobRef) {
|
|
7182
|
-
const urlRes = await
|
|
7183
|
-
new URL(byokBlobUrlPath(blobRef.blobId), base),
|
|
7184
|
-
|
|
7185
|
-
this.auth
|
|
7465
|
+
async #hasExactCommittedBlob(base, blobRef, signal) {
|
|
7466
|
+
const urlRes = await this.#request(
|
|
7467
|
+
(requestSignal) => authedFetch(new URL(byokBlobUrlPath(blobRef.blobId), base), { method: "GET", signal: requestSignal }, this.auth),
|
|
7468
|
+
signal
|
|
7186
7469
|
);
|
|
7187
7470
|
if (urlRes.status === 404) return false;
|
|
7188
7471
|
if (!urlRes.ok) {
|
|
7189
|
-
throw new Error(`failed to read back idempotent blob: HTTP ${urlRes.status} ${await
|
|
7472
|
+
throw new Error(`failed to read back idempotent blob: HTTP ${urlRes.status} ${await this.#safeErrorText(urlRes, signal)}`.trimEnd());
|
|
7190
7473
|
}
|
|
7191
|
-
const { downloadUrl } = await urlRes
|
|
7474
|
+
const { downloadUrl } = await this.#readJson(urlRes, signal);
|
|
7192
7475
|
if (typeof downloadUrl !== "string" || downloadUrl.length === 0) {
|
|
7193
7476
|
throw new Error("failed to read back idempotent blob: response omitted downloadUrl");
|
|
7194
7477
|
}
|
|
7195
|
-
const contentRes = await fetch(new URL(downloadUrl, base));
|
|
7478
|
+
const contentRes = await this.#request((requestSignal) => fetch(new URL(downloadUrl, base), { signal: requestSignal }), signal);
|
|
7196
7479
|
if (contentRes.status === 404) return false;
|
|
7197
7480
|
if (!contentRes.ok) {
|
|
7198
7481
|
throw new Error(`failed to read back idempotent blob content: HTTP ${contentRes.status}`);
|
|
7199
7482
|
}
|
|
7200
|
-
const bytes =
|
|
7483
|
+
const bytes = await this.#readBody(contentRes, signal);
|
|
7201
7484
|
const observedHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
7202
7485
|
if (observedHash !== blobRef.contentHash || bytes.length !== blobRef.size) {
|
|
7203
7486
|
throw new Error(
|
|
@@ -7206,18 +7489,19 @@ var BlobClient = class {
|
|
|
7206
7489
|
}
|
|
7207
7490
|
return true;
|
|
7208
7491
|
}
|
|
7209
|
-
async #finalize(base, blobId, reservationId) {
|
|
7492
|
+
async #finalize(base, blobId, reservationId, signal) {
|
|
7210
7493
|
let lastFailure;
|
|
7211
7494
|
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
7495
|
+
this.#throwIfAborted(signal);
|
|
7212
7496
|
let response;
|
|
7213
7497
|
try {
|
|
7214
|
-
response = await
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
method: "POST",
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7498
|
+
response = await this.#request(
|
|
7499
|
+
(requestSignal) => authedFetch(
|
|
7500
|
+
new URL(byokBlobFinalizePath(blobId), base),
|
|
7501
|
+
{ method: "POST", headers: { "idempotency-key": reservationId }, signal: requestSignal },
|
|
7502
|
+
this.auth
|
|
7503
|
+
),
|
|
7504
|
+
signal
|
|
7221
7505
|
);
|
|
7222
7506
|
} catch (error) {
|
|
7223
7507
|
lastFailure = error;
|
|
@@ -7227,13 +7511,105 @@ var BlobClient = class {
|
|
|
7227
7511
|
if (response.ok) return;
|
|
7228
7512
|
if (response.status < 500 || attempt === 2) {
|
|
7229
7513
|
throw new Error(
|
|
7230
|
-
`failed to finalize blob: HTTP ${response.status} ${await
|
|
7514
|
+
`failed to finalize blob: HTTP ${response.status} ${await this.#safeErrorText(response, signal)}`.trimEnd()
|
|
7231
7515
|
);
|
|
7232
7516
|
}
|
|
7233
7517
|
lastFailure = new Error(`failed to finalize blob: HTTP ${response.status}`);
|
|
7234
7518
|
}
|
|
7235
7519
|
throw lastFailure;
|
|
7236
7520
|
}
|
|
7521
|
+
async #readJson(res, signal) {
|
|
7522
|
+
return JSON.parse(await this.#readText(res, signal));
|
|
7523
|
+
}
|
|
7524
|
+
async #safeErrorText(res, signal) {
|
|
7525
|
+
try {
|
|
7526
|
+
return await this.#readText(res, signal);
|
|
7527
|
+
} catch (error) {
|
|
7528
|
+
if (error instanceof BlobRequestAbortedError) throw error;
|
|
7529
|
+
return "";
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
async #readText(res, signal) {
|
|
7533
|
+
return new TextDecoder().decode(await this.#readBody(res, signal));
|
|
7534
|
+
}
|
|
7535
|
+
/**
|
|
7536
|
+
* `Response.arrayBuffer()` only leaves cancellation observable through the
|
|
7537
|
+
* Fetch implementation. Read the body directly so our lifecycle/deadline
|
|
7538
|
+
* authority cancels the actual stream even when a fetch implementation has
|
|
7539
|
+
* already resolved at headers.
|
|
7540
|
+
*/
|
|
7541
|
+
async #readBody(res, signal) {
|
|
7542
|
+
return this.#request(async (requestSignal) => {
|
|
7543
|
+
if (res.body === null) return new Uint8Array();
|
|
7544
|
+
const reader = res.body.getReader();
|
|
7545
|
+
const cancelBody = () => {
|
|
7546
|
+
void reader.cancel().catch(() => void 0);
|
|
7547
|
+
};
|
|
7548
|
+
requestSignal.addEventListener("abort", cancelBody, { once: true });
|
|
7549
|
+
try {
|
|
7550
|
+
const chunks = [];
|
|
7551
|
+
let length = 0;
|
|
7552
|
+
for (; ; ) {
|
|
7553
|
+
const { done, value } = await reader.read();
|
|
7554
|
+
if (done) break;
|
|
7555
|
+
chunks.push(value);
|
|
7556
|
+
length += value.byteLength;
|
|
7557
|
+
}
|
|
7558
|
+
const bytes = new Uint8Array(length);
|
|
7559
|
+
let offset = 0;
|
|
7560
|
+
for (const chunk of chunks) {
|
|
7561
|
+
bytes.set(chunk, offset);
|
|
7562
|
+
offset += chunk.byteLength;
|
|
7563
|
+
}
|
|
7564
|
+
return bytes;
|
|
7565
|
+
} finally {
|
|
7566
|
+
requestSignal.removeEventListener("abort", cancelBody);
|
|
7567
|
+
reader.releaseLock();
|
|
7568
|
+
}
|
|
7569
|
+
}, signal);
|
|
7570
|
+
}
|
|
7571
|
+
#throwIfAborted(signal) {
|
|
7572
|
+
if (this.options.signal?.aborted || signal?.aborted) {
|
|
7573
|
+
throw new BlobRequestAbortedError("cancelled");
|
|
7574
|
+
}
|
|
7575
|
+
}
|
|
7576
|
+
async #request(request, signal) {
|
|
7577
|
+
this.#throwIfAborted(signal);
|
|
7578
|
+
const controller = new AbortController();
|
|
7579
|
+
let abortReason = "cancelled";
|
|
7580
|
+
const abort = (reason) => {
|
|
7581
|
+
if (controller.signal.aborted) return;
|
|
7582
|
+
abortReason = reason;
|
|
7583
|
+
controller.abort();
|
|
7584
|
+
};
|
|
7585
|
+
const inheritedSignals = [this.options.signal, signal].filter((value) => value !== void 0);
|
|
7586
|
+
const abortForCancellation = () => abort("cancelled");
|
|
7587
|
+
for (const inheritedSignal of inheritedSignals) {
|
|
7588
|
+
inheritedSignal.addEventListener("abort", abortForCancellation, { once: true });
|
|
7589
|
+
}
|
|
7590
|
+
const deadline = setTimeout(() => abort("deadline"), this.requestDeadlineMs);
|
|
7591
|
+
deadline.unref?.();
|
|
7592
|
+
let rejectAbort = () => {
|
|
7593
|
+
};
|
|
7594
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
7595
|
+
rejectAbort = reject;
|
|
7596
|
+
});
|
|
7597
|
+
const rejectOnAbort = () => rejectAbort(new BlobRequestAbortedError(abortReason));
|
|
7598
|
+
controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
|
|
7599
|
+
try {
|
|
7600
|
+
return await Promise.race([request(controller.signal), aborted]);
|
|
7601
|
+
} catch (error) {
|
|
7602
|
+
if (error instanceof BlobRequestAbortedError) throw error;
|
|
7603
|
+
if (controller.signal.aborted) throw new BlobRequestAbortedError(abortReason);
|
|
7604
|
+
throw error;
|
|
7605
|
+
} finally {
|
|
7606
|
+
clearTimeout(deadline);
|
|
7607
|
+
controller.signal.removeEventListener("abort", rejectOnAbort);
|
|
7608
|
+
for (const inheritedSignal of inheritedSignals) {
|
|
7609
|
+
inheritedSignal.removeEventListener("abort", abortForCancellation);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
}
|
|
7237
7613
|
};
|
|
7238
7614
|
var DEFAULT_TIMEOUT_MS2 = 2e3;
|
|
7239
7615
|
var DARWIN_UUID_RE = /"IOPlatformUUID"\s*=\s*"([^"]+)"/u;
|
|
@@ -7480,6 +7856,7 @@ var AnotherControlServerRunningError = class extends Error {
|
|
|
7480
7856
|
}
|
|
7481
7857
|
};
|
|
7482
7858
|
var MAX_HALF_OPEN_CONNECTIONS = 8;
|
|
7859
|
+
var MAX_OUTBOUND_QUEUE_BYTES = 1024 * 1024;
|
|
7483
7860
|
function errorMessage4(err) {
|
|
7484
7861
|
return err instanceof Error ? err.message : String(err);
|
|
7485
7862
|
}
|
|
@@ -7556,11 +7933,12 @@ async function bindControlEndpoint(server, endpoint) {
|
|
|
7556
7933
|
}
|
|
7557
7934
|
function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshakeSettled) {
|
|
7558
7935
|
const reader = new NdjsonLineReader();
|
|
7559
|
-
const
|
|
7936
|
+
const activeRequests = /* @__PURE__ */ new Map();
|
|
7560
7937
|
let phase = "client-hello";
|
|
7561
7938
|
let serverNonce = "";
|
|
7562
7939
|
let destroyed = false;
|
|
7563
7940
|
let handshakeSettled = false;
|
|
7941
|
+
let disposeOutboundWriter;
|
|
7564
7942
|
function settleHandshake() {
|
|
7565
7943
|
if (handshakeSettled) return;
|
|
7566
7944
|
handshakeSettled = true;
|
|
@@ -7570,8 +7948,83 @@ function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshak
|
|
|
7570
7948
|
if (phase !== "ready") socket.destroy();
|
|
7571
7949
|
}, handshakeTimeoutMs);
|
|
7572
7950
|
handshakeTimer.unref?.();
|
|
7951
|
+
function terminateConnection() {
|
|
7952
|
+
if (destroyed) return;
|
|
7953
|
+
destroyed = true;
|
|
7954
|
+
clearTimeout(handshakeTimer);
|
|
7955
|
+
settleHandshake();
|
|
7956
|
+
disposeOutboundWriter?.();
|
|
7957
|
+
const requests = [...activeRequests.values()];
|
|
7958
|
+
activeRequests.clear();
|
|
7959
|
+
for (const request of requests) {
|
|
7960
|
+
if (request.kind === "stream" && !request.controller.signal.aborted) request.controller.abort();
|
|
7961
|
+
}
|
|
7962
|
+
if (!socket.destroyed) socket.destroy();
|
|
7963
|
+
}
|
|
7964
|
+
const queuedFrames = [];
|
|
7965
|
+
let queuedBytes = 0;
|
|
7966
|
+
let blocked = false;
|
|
7967
|
+
let terminal = false;
|
|
7968
|
+
let drainListening = false;
|
|
7969
|
+
function disposeQueuedFrames() {
|
|
7970
|
+
terminal = true;
|
|
7971
|
+
blocked = false;
|
|
7972
|
+
queuedFrames.length = 0;
|
|
7973
|
+
queuedBytes = 0;
|
|
7974
|
+
if (drainListening) {
|
|
7975
|
+
socket.removeListener("drain", onDrain);
|
|
7976
|
+
drainListening = false;
|
|
7977
|
+
}
|
|
7978
|
+
}
|
|
7979
|
+
function waitForDrain() {
|
|
7980
|
+
if (drainListening || terminal) return;
|
|
7981
|
+
drainListening = true;
|
|
7982
|
+
socket.once("drain", onDrain);
|
|
7983
|
+
}
|
|
7984
|
+
function writeEncodedFrame(encoded) {
|
|
7985
|
+
if (terminal) return;
|
|
7986
|
+
if (!socket.writable) {
|
|
7987
|
+
terminateConnection();
|
|
7988
|
+
return;
|
|
7989
|
+
}
|
|
7990
|
+
try {
|
|
7991
|
+
if (!socket.write(encoded)) {
|
|
7992
|
+
blocked = true;
|
|
7993
|
+
waitForDrain();
|
|
7994
|
+
}
|
|
7995
|
+
} catch {
|
|
7996
|
+
terminateConnection();
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
function onDrain() {
|
|
8000
|
+
drainListening = false;
|
|
8001
|
+
if (terminal) return;
|
|
8002
|
+
blocked = false;
|
|
8003
|
+
while (!blocked && queuedFrames.length > 0) {
|
|
8004
|
+
const frame = queuedFrames.shift();
|
|
8005
|
+
queuedBytes -= frame.bytes;
|
|
8006
|
+
writeEncodedFrame(frame.encoded);
|
|
8007
|
+
}
|
|
8008
|
+
}
|
|
8009
|
+
disposeOutboundWriter = disposeQueuedFrames;
|
|
7573
8010
|
function sendFrame(frame) {
|
|
7574
|
-
if (
|
|
8011
|
+
if (destroyed || terminal) return;
|
|
8012
|
+
const encoded = encodeFrame(frame);
|
|
8013
|
+
const bytes = Buffer.byteLength(encoded);
|
|
8014
|
+
if (bytes > MAX_OUTBOUND_QUEUE_BYTES) {
|
|
8015
|
+
terminateConnection();
|
|
8016
|
+
return;
|
|
8017
|
+
}
|
|
8018
|
+
if (blocked) {
|
|
8019
|
+
if (queuedBytes + bytes > MAX_OUTBOUND_QUEUE_BYTES) {
|
|
8020
|
+
terminateConnection();
|
|
8021
|
+
return;
|
|
8022
|
+
}
|
|
8023
|
+
queuedFrames.push({ encoded, bytes });
|
|
8024
|
+
queuedBytes += bytes;
|
|
8025
|
+
return;
|
|
8026
|
+
}
|
|
8027
|
+
writeEncodedFrame(encoded);
|
|
7575
8028
|
}
|
|
7576
8029
|
function handleClientHello(parsed) {
|
|
7577
8030
|
const hello = parseClientHello(parsed);
|
|
@@ -7594,21 +8047,51 @@ function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshak
|
|
|
7594
8047
|
settleHandshake();
|
|
7595
8048
|
sendFrame({ v: 1, ready: true });
|
|
7596
8049
|
}
|
|
8050
|
+
function rejectDuplicateRequest(id) {
|
|
8051
|
+
sendFrame({
|
|
8052
|
+
v: 1,
|
|
8053
|
+
id,
|
|
8054
|
+
ok: false,
|
|
8055
|
+
error: { code: "duplicate_request_id", message: `request id "${id}" is already active` }
|
|
8056
|
+
});
|
|
8057
|
+
}
|
|
8058
|
+
function registerRequest(id, record) {
|
|
8059
|
+
if (activeRequests.has(id)) {
|
|
8060
|
+
rejectDuplicateRequest(id);
|
|
8061
|
+
return false;
|
|
8062
|
+
}
|
|
8063
|
+
activeRequests.set(id, record);
|
|
8064
|
+
return true;
|
|
8065
|
+
}
|
|
8066
|
+
function releaseRequest(id, record) {
|
|
8067
|
+
if (activeRequests.get(id) !== record) return false;
|
|
8068
|
+
activeRequests.delete(id);
|
|
8069
|
+
return true;
|
|
8070
|
+
}
|
|
7597
8071
|
function dispatch(id, method, params) {
|
|
7598
8072
|
const unary = methods.unary[method];
|
|
7599
8073
|
if (unary) {
|
|
7600
|
-
|
|
8074
|
+
const record = { kind: "unary" };
|
|
8075
|
+
if (!registerRequest(id, record)) return;
|
|
8076
|
+
Promise.resolve().then(() => unary(params)).then((result) => {
|
|
8077
|
+
if (!releaseRequest(id, record)) return;
|
|
8078
|
+
sendFrame({ v: 1, id, ok: true, result });
|
|
8079
|
+
}).catch((err) => {
|
|
8080
|
+
if (!releaseRequest(id, record)) return;
|
|
8081
|
+
sendFrame({ v: 1, id, ok: false, error: toControlErrorShape(err) });
|
|
8082
|
+
});
|
|
7601
8083
|
return;
|
|
7602
8084
|
}
|
|
7603
8085
|
const stream = methods.stream[method];
|
|
7604
8086
|
if (stream) {
|
|
7605
8087
|
const controller = new AbortController();
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
8088
|
+
const record = { kind: "stream", controller };
|
|
8089
|
+
if (!registerRequest(id, record)) return;
|
|
8090
|
+
Promise.resolve().then(() => stream(params, { emit: (event) => sendFrame({ v: 1, id, event }), signal: controller.signal })).then(() => {
|
|
8091
|
+
if (!releaseRequest(id, record)) return;
|
|
7609
8092
|
if (!controller.signal.aborted) sendFrame({ v: 1, id, ok: true, done: true });
|
|
7610
8093
|
}).catch((err) => {
|
|
7611
|
-
|
|
8094
|
+
if (!releaseRequest(id, record)) return;
|
|
7612
8095
|
sendFrame({ v: 1, id, ok: false, error: toControlErrorShape(err) });
|
|
7613
8096
|
});
|
|
7614
8097
|
return;
|
|
@@ -7650,13 +8133,10 @@ function handleConnection(socket, token, methods, handshakeTimeoutMs, onHandshak
|
|
|
7650
8133
|
for (const line of lines) handleLine(line);
|
|
7651
8134
|
});
|
|
7652
8135
|
socket.on("error", () => {
|
|
8136
|
+
terminateConnection();
|
|
7653
8137
|
});
|
|
7654
8138
|
socket.on("close", () => {
|
|
7655
|
-
|
|
7656
|
-
clearTimeout(handshakeTimer);
|
|
7657
|
-
settleHandshake();
|
|
7658
|
-
for (const controller of activeStreams.values()) controller.abort();
|
|
7659
|
-
activeStreams.clear();
|
|
8139
|
+
terminateConnection();
|
|
7660
8140
|
});
|
|
7661
8141
|
}
|
|
7662
8142
|
async function startControlServer(opts) {
|
|
@@ -8574,6 +9054,20 @@ function createFleetJitter(productId, deviceId) {
|
|
|
8574
9054
|
delay: (domain, sequence, baseMs) => deterministicJitterMs({ seed, domain, sequence, baseMs })
|
|
8575
9055
|
};
|
|
8576
9056
|
}
|
|
9057
|
+
|
|
9058
|
+
// src/daemon/replay-cursor.ts
|
|
9059
|
+
var ReplayCursorTooOldError = class extends Error {
|
|
9060
|
+
constructor(recoverableFrom) {
|
|
9061
|
+
super(
|
|
9062
|
+
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}`
|
|
9063
|
+
);
|
|
9064
|
+
this.recoverableFrom = recoverableFrom;
|
|
9065
|
+
this.name = "ReplayCursorTooOldError";
|
|
9066
|
+
}
|
|
9067
|
+
recoverableFrom;
|
|
9068
|
+
};
|
|
9069
|
+
|
|
9070
|
+
// src/daemon/long-poll-transport.ts
|
|
8577
9071
|
var LongPollRouteError = class extends Error {
|
|
8578
9072
|
constructor(endpoint, status, cause) {
|
|
8579
9073
|
super(
|
|
@@ -8753,6 +9247,12 @@ var LongPollClient = class {
|
|
|
8753
9247
|
throw err;
|
|
8754
9248
|
}
|
|
8755
9249
|
if (!res.ok) {
|
|
9250
|
+
const replayCursorTooOld = await parseReplayCursorTooOld(res);
|
|
9251
|
+
if (replayCursorTooOld) {
|
|
9252
|
+
this.running = false;
|
|
9253
|
+
this.opts.onReplayCursorTooOld?.(replayCursorTooOld);
|
|
9254
|
+
return;
|
|
9255
|
+
}
|
|
8756
9256
|
this.warnRouteFailure(this.eventsEndpoint, res.status, void 0);
|
|
8757
9257
|
this.opts.onServerCapabilities?.([]);
|
|
8758
9258
|
this.opts.onOperationalOutcome?.("failure");
|
|
@@ -8821,6 +9321,21 @@ var LongPollClient = class {
|
|
|
8821
9321
|
}
|
|
8822
9322
|
}
|
|
8823
9323
|
};
|
|
9324
|
+
async function parseReplayCursorTooOld(res) {
|
|
9325
|
+
if (res.status !== 409) return void 0;
|
|
9326
|
+
let body;
|
|
9327
|
+
try {
|
|
9328
|
+
body = await res.json();
|
|
9329
|
+
} catch {
|
|
9330
|
+
return void 0;
|
|
9331
|
+
}
|
|
9332
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
9333
|
+
const { error, recoverableFrom } = body;
|
|
9334
|
+
if (error !== "cursor_too_old" || typeof recoverableFrom !== "number" || !Number.isSafeInteger(recoverableFrom) || recoverableFrom < 0) {
|
|
9335
|
+
return void 0;
|
|
9336
|
+
}
|
|
9337
|
+
return new ReplayCursorTooOldError(recoverableFrom);
|
|
9338
|
+
}
|
|
8824
9339
|
function sleep(ms) {
|
|
8825
9340
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8826
9341
|
}
|
|
@@ -8965,16 +9480,17 @@ var WsTransport = class {
|
|
|
8965
9480
|
}
|
|
8966
9481
|
this.opts.onEnvelope(envelope);
|
|
8967
9482
|
});
|
|
8968
|
-
socket.on("close", () => {
|
|
9483
|
+
socket.on("close", (code, reason) => {
|
|
8969
9484
|
this.socket = void 0;
|
|
8970
9485
|
this.stopLivenessCheck();
|
|
8971
9486
|
this.opts.onStateChange?.("closed");
|
|
8972
9487
|
const acked = this.everAckedThisAttempt;
|
|
8973
9488
|
const status = this.lastUnexpectedStatus;
|
|
8974
9489
|
this.lastUnexpectedStatus = void 0;
|
|
9490
|
+
const replayCursorTooOld = code === 1008 && reason.toString("utf8") === "cursor_too_old" ? new ReplayCursorTooOldError() : void 0;
|
|
8975
9491
|
this.opts.onConnectOutcome?.(
|
|
8976
9492
|
acked,
|
|
8977
|
-
status !== void 0 ? new WsUnexpectedStatusError(status, endpoint) : void 0,
|
|
9493
|
+
replayCursorTooOld ?? (status !== void 0 ? new WsUnexpectedStatusError(status, endpoint) : void 0),
|
|
8978
9494
|
endpoint
|
|
8979
9495
|
);
|
|
8980
9496
|
if (!this.closedByUser && this.autoReconnect) this.scheduleReconnect();
|
|
@@ -9060,6 +9576,7 @@ var ConnectionManager = class {
|
|
|
9060
9576
|
if (this.mode === "long-poll") this.serverCapabilities = capabilities;
|
|
9061
9577
|
},
|
|
9062
9578
|
onRevoked: () => this.enterRevoked(),
|
|
9579
|
+
onReplayCursorTooOld: (error) => this.enterReplayCursorTooOld(error),
|
|
9063
9580
|
// M4 Phase 4 (version-negotiation drill fix): a batch entry
|
|
9064
9581
|
// LongPollClient couldn't parse into a known Envelope at all (an
|
|
9065
9582
|
// unrecognized message type) still needs its cursor/watermark
|
|
@@ -9148,6 +9665,7 @@ var ConnectionManager = class {
|
|
|
9148
9665
|
draining = false;
|
|
9149
9666
|
stopped = false;
|
|
9150
9667
|
revoked = false;
|
|
9668
|
+
terminalError;
|
|
9151
9669
|
settledWaiters = [];
|
|
9152
9670
|
pendingCursorSave = Promise.resolve();
|
|
9153
9671
|
/**
|
|
@@ -9220,6 +9738,7 @@ var ConnectionManager = class {
|
|
|
9220
9738
|
*/
|
|
9221
9739
|
serverCapabilities = [];
|
|
9222
9740
|
async start() {
|
|
9741
|
+
if (this.terminalError) throw this.terminalError;
|
|
9223
9742
|
this.cursor = await this.opts.cursorStore.load(this.opts.serverUrl, this.opts.deviceId);
|
|
9224
9743
|
this.ws.connect({ auto: true });
|
|
9225
9744
|
}
|
|
@@ -9335,6 +9854,12 @@ var ConnectionManager = class {
|
|
|
9335
9854
|
getServerCapabilities() {
|
|
9336
9855
|
return this.serverCapabilities;
|
|
9337
9856
|
}
|
|
9857
|
+
getTerminalError() {
|
|
9858
|
+
return this.terminalError;
|
|
9859
|
+
}
|
|
9860
|
+
getMode() {
|
|
9861
|
+
return this.mode;
|
|
9862
|
+
}
|
|
9338
9863
|
isConnected() {
|
|
9339
9864
|
return this.mode === "ws" && this.ws.isOpen;
|
|
9340
9865
|
}
|
|
@@ -9355,6 +9880,7 @@ var ConnectionManager = class {
|
|
|
9355
9880
|
*/
|
|
9356
9881
|
waitForAck(timeoutMs = 1e4) {
|
|
9357
9882
|
if (this.ws.isOpen || this.mode === "long-poll") return Promise.resolve();
|
|
9883
|
+
if (this.terminalError) return Promise.reject(this.terminalError);
|
|
9358
9884
|
if (this.revoked) return Promise.reject(new DeviceRevokedError());
|
|
9359
9885
|
return new Promise((resolve, reject) => {
|
|
9360
9886
|
let settle = () => {
|
|
@@ -9683,6 +10209,7 @@ var ConnectionManager = class {
|
|
|
9683
10209
|
* (which is close-only) at all.
|
|
9684
10210
|
*/
|
|
9685
10211
|
onAcked(capabilities) {
|
|
10212
|
+
if (this.terminalError) return;
|
|
9686
10213
|
this.serverCapabilities = capabilities;
|
|
9687
10214
|
this.consecutiveFailures = 0;
|
|
9688
10215
|
this.notifySettled();
|
|
@@ -9691,6 +10218,10 @@ var ConnectionManager = class {
|
|
|
9691
10218
|
void this.drainOutbox();
|
|
9692
10219
|
}
|
|
9693
10220
|
onWsOutcome(acked, err) {
|
|
10221
|
+
if (err instanceof ReplayCursorTooOldError) {
|
|
10222
|
+
this.enterReplayCursorTooOld(err);
|
|
10223
|
+
return;
|
|
10224
|
+
}
|
|
9694
10225
|
if (this.stopped || this.revoked) return;
|
|
9695
10226
|
if (acked) this.serverCapabilities = [];
|
|
9696
10227
|
if (err instanceof WsUnexpectedStatusError && err.status === 401) {
|
|
@@ -9727,6 +10258,20 @@ var ConnectionManager = class {
|
|
|
9727
10258
|
void this.drainOutbox();
|
|
9728
10259
|
this.scheduleWsProbe();
|
|
9729
10260
|
}
|
|
10261
|
+
enterReplayCursorTooOld(error) {
|
|
10262
|
+
if (this.terminalError) return;
|
|
10263
|
+
this.terminalError = error;
|
|
10264
|
+
this.stopped = true;
|
|
10265
|
+
this.serverCapabilities = [];
|
|
10266
|
+
if (this.wsRetryTimer) clearInterval(this.wsRetryTimer);
|
|
10267
|
+
this.longPoll.stop();
|
|
10268
|
+
this.ws.stopAutoReconnect();
|
|
10269
|
+
this.ws.close();
|
|
10270
|
+
this.cancelPendingDrainRetry?.();
|
|
10271
|
+
this.notifySettled(error);
|
|
10272
|
+
this.opts.onStateChange?.("closed");
|
|
10273
|
+
this.opts.onTerminalError?.(error);
|
|
10274
|
+
}
|
|
9730
10275
|
exitLongPoll() {
|
|
9731
10276
|
if (this.wsRetryTimer) {
|
|
9732
10277
|
clearInterval(this.wsRetryTimer);
|
|
@@ -12490,21 +13035,28 @@ async function recordAuditWarning(context, kind, values) {
|
|
|
12490
13035
|
}
|
|
12491
13036
|
}
|
|
12492
13037
|
var agentMemoryHomeQueues = /* @__PURE__ */ new Map();
|
|
12493
|
-
|
|
12494
|
-
|
|
13038
|
+
var agentMemoryProjectionTransactionQueues = /* @__PURE__ */ new Map();
|
|
13039
|
+
async function exclusiveAgentMemoryHomeQueue(queues2, home, fn) {
|
|
13040
|
+
const previous = queues2.get(home) ?? Promise.resolve();
|
|
12495
13041
|
let release;
|
|
12496
13042
|
const next = new Promise((resolve) => {
|
|
12497
13043
|
release = resolve;
|
|
12498
13044
|
});
|
|
12499
|
-
|
|
13045
|
+
queues2.set(home, next);
|
|
12500
13046
|
await previous;
|
|
12501
13047
|
try {
|
|
12502
13048
|
return await fn();
|
|
12503
13049
|
} finally {
|
|
12504
13050
|
release();
|
|
12505
|
-
if (
|
|
13051
|
+
if (queues2.get(home) === next) queues2.delete(home);
|
|
12506
13052
|
}
|
|
12507
13053
|
}
|
|
13054
|
+
async function exclusiveAgentMemoryHome(home, fn) {
|
|
13055
|
+
return exclusiveAgentMemoryHomeQueue(agentMemoryHomeQueues, home, fn);
|
|
13056
|
+
}
|
|
13057
|
+
async function exclusiveAgentMemoryProjectionTransaction(home, fn) {
|
|
13058
|
+
return exclusiveAgentMemoryHomeQueue(agentMemoryProjectionTransactionQueues, home, fn);
|
|
13059
|
+
}
|
|
12508
13060
|
var AgentMemoryService = class {
|
|
12509
13061
|
constructor(input) {
|
|
12510
13062
|
this.input = input;
|
|
@@ -12808,13 +13360,16 @@ var AgentMemoryRedactedOutbox = class _AgentMemoryRedactedOutbox {
|
|
|
12808
13360
|
async function snapshotAndProjectAgentMemory(input, projection) {
|
|
12809
13361
|
if (projection?.capability !== AGENT_MEMORY_PROJECTION_CAPABILITY || !projection.grant || !projection.redactor || !projection.port) return;
|
|
12810
13362
|
const context = taskContext(input);
|
|
12811
|
-
const
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
13363
|
+
const { grant, redactor, port } = projection;
|
|
13364
|
+
await exclusiveAgentMemoryProjectionTransaction(context.canonicalHome, async () => {
|
|
13365
|
+
const outbox = await AgentMemoryRedactedOutbox.open(context, grant);
|
|
13366
|
+
const initialReplay = await outbox.replay(port);
|
|
13367
|
+
if (initialReplay.status === "pending") throw new AgentMemoryProjectionReplayPendingError(initialReplay);
|
|
13368
|
+
const snapshot = await captureAgentMemorySnapshot(context);
|
|
13369
|
+
await outbox.append(redactedBytes(snapshot, await redactor.redact(snapshot)));
|
|
13370
|
+
const trailingReplay = await outbox.replay(port);
|
|
13371
|
+
if (trailingReplay.status === "pending") throw new AgentMemoryProjectionReplayPendingError(trailingReplay);
|
|
13372
|
+
});
|
|
12818
13373
|
}
|
|
12819
13374
|
var AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL = 2;
|
|
12820
13375
|
var AGENT_MEMORY_FILESYSTEM_HELPER_VERSION = "2";
|
|
@@ -13222,6 +13777,7 @@ var TaskRunner = class {
|
|
|
13222
13777
|
deps;
|
|
13223
13778
|
tasks = /* @__PURE__ */ new Map();
|
|
13224
13779
|
pendingMessageTasks = /* @__PURE__ */ new Map();
|
|
13780
|
+
messageOutboxesByHome = /* @__PURE__ */ new Map();
|
|
13225
13781
|
messageContextByToken = /* @__PURE__ */ new Map();
|
|
13226
13782
|
messageContextByTask = /* @__PURE__ */ new Map();
|
|
13227
13783
|
memoryContextByToken = /* @__PURE__ */ new Map();
|
|
@@ -13282,6 +13838,8 @@ var TaskRunner = class {
|
|
|
13282
13838
|
* costs nothing.
|
|
13283
13839
|
*/
|
|
13284
13840
|
inFlightOffers = /* @__PURE__ */ new Set();
|
|
13841
|
+
/** Blob I/O before an offer becomes an active task still belongs to that offer's cancellation authority. */
|
|
13842
|
+
inFlightBlobAborts = /* @__PURE__ */ new Map();
|
|
13285
13843
|
/**
|
|
13286
13844
|
* Finding P2 (Fix 2c): taskIds that have reached a terminal outcome
|
|
13287
13845
|
* (Complete/Failed/Cancelled) this session — populated in `finish()`.
|
|
@@ -13408,6 +13966,7 @@ var TaskRunner = class {
|
|
|
13408
13966
|
async recoverAgentMessageOutboxes(agentsRoot) {
|
|
13409
13967
|
if (this.deps.tenantId === void 0) throw new Error("Agent message recovery requires authenticated tenant enrollment");
|
|
13410
13968
|
for (const outbox of await AgentMessageOutbox.recover(agentsRoot, this.deps.tenantId)) {
|
|
13969
|
+
this.messageOutboxesByHome.set(outbox.homeDir, Promise.resolve(outbox));
|
|
13411
13970
|
for (const record of outbox.retryableRecords()) {
|
|
13412
13971
|
if (record.sessionRef === void 0) continue;
|
|
13413
13972
|
const existing = this.recoveredMessageOutboxes.get(record.taskId);
|
|
@@ -13416,6 +13975,16 @@ var TaskRunner = class {
|
|
|
13416
13975
|
}
|
|
13417
13976
|
}
|
|
13418
13977
|
}
|
|
13978
|
+
agentMessageOutbox(homeDir) {
|
|
13979
|
+
const existing = this.messageOutboxesByHome.get(homeDir);
|
|
13980
|
+
if (existing !== void 0) return existing;
|
|
13981
|
+
const opened = AgentMessageOutbox.open(homeDir);
|
|
13982
|
+
this.messageOutboxesByHome.set(homeDir, opened);
|
|
13983
|
+
void opened.catch(() => {
|
|
13984
|
+
if (this.messageOutboxesByHome.get(homeDir) === opened) this.messageOutboxesByHome.delete(homeDir);
|
|
13985
|
+
});
|
|
13986
|
+
return opened;
|
|
13987
|
+
}
|
|
13419
13988
|
/** Retry stable recovered records after a transport handshake/re-handshake. */
|
|
13420
13989
|
retryRecoveredAgentMessages() {
|
|
13421
13990
|
for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
|
|
@@ -13571,6 +14140,7 @@ var TaskRunner = class {
|
|
|
13571
14140
|
if (active.finalizationStarted) return this.finish(active.taskId);
|
|
13572
14141
|
if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
|
|
13573
14142
|
active.beingTornDown = true;
|
|
14143
|
+
active.blobAbort.abort();
|
|
13574
14144
|
await this.observeGit(active, "salvage");
|
|
13575
14145
|
const timeoutMs = this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS;
|
|
13576
14146
|
await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
|
|
@@ -13717,6 +14287,8 @@ var TaskRunner = class {
|
|
|
13717
14287
|
return;
|
|
13718
14288
|
}
|
|
13719
14289
|
this.inFlightOffers.add(taskId);
|
|
14290
|
+
const blobAbort = new AbortController();
|
|
14291
|
+
this.inFlightBlobAborts.set(taskId, blobAbort);
|
|
13720
14292
|
let agentBinding;
|
|
13721
14293
|
let agentLeaseTransferred = false;
|
|
13722
14294
|
try {
|
|
@@ -13875,7 +14447,7 @@ var TaskRunner = class {
|
|
|
13875
14447
|
}
|
|
13876
14448
|
if (agentRef !== void 0) {
|
|
13877
14449
|
try {
|
|
13878
|
-
agentBinding = await this.deps.agentHome.
|
|
14450
|
+
agentBinding = await this.deps.agentHome.acquireExecution(agentRef, { taskId, sessionRef });
|
|
13879
14451
|
} catch (error) {
|
|
13880
14452
|
decline(
|
|
13881
14453
|
`Agent home admission failed: ${errorMessage5(error)}`,
|
|
@@ -13910,7 +14482,7 @@ var TaskRunner = class {
|
|
|
13910
14482
|
}
|
|
13911
14483
|
}
|
|
13912
14484
|
try {
|
|
13913
|
-
await this.deps.agentHome.
|
|
14485
|
+
await this.deps.agentHome.initializeExecution(agentBinding);
|
|
13914
14486
|
} catch (error) {
|
|
13915
14487
|
await agentBinding.lease.release().catch(() => {
|
|
13916
14488
|
});
|
|
@@ -13920,7 +14492,7 @@ var TaskRunner = class {
|
|
|
13920
14492
|
}
|
|
13921
14493
|
if (messageRequirement !== void 0) {
|
|
13922
14494
|
try {
|
|
13923
|
-
const outbox = await
|
|
14495
|
+
const outbox = await this.agentMessageOutbox(agentBinding.resolution.canonicalHome);
|
|
13924
14496
|
this.pendingMessageTasks.set(taskId, {
|
|
13925
14497
|
taskId,
|
|
13926
14498
|
agentRef: agentBinding.resolution.agentRef,
|
|
@@ -14039,7 +14611,7 @@ var TaskRunner = class {
|
|
|
14039
14611
|
);
|
|
14040
14612
|
let resolvedInstruction;
|
|
14041
14613
|
try {
|
|
14042
|
-
resolvedInstruction = await this.resolveInstruction(payload.instruction);
|
|
14614
|
+
resolvedInstruction = await this.resolveInstruction(payload.instruction, blobAbort.signal);
|
|
14043
14615
|
if (plainWorkspaceNeedsResolve) workspaceDir = await this.resolveWorkspaceDir(taskId, known?.workspaceDir);
|
|
14044
14616
|
} catch (err) {
|
|
14045
14617
|
gitLease?.release();
|
|
@@ -14152,9 +14724,29 @@ var TaskRunner = class {
|
|
|
14152
14724
|
}
|
|
14153
14725
|
return;
|
|
14154
14726
|
}
|
|
14727
|
+
if (agentBinding !== void 0) {
|
|
14728
|
+
try {
|
|
14729
|
+
await agentBinding.lease.bindSession(session.sessionRef);
|
|
14730
|
+
} catch (error) {
|
|
14731
|
+
await session.close().catch(() => {
|
|
14732
|
+
});
|
|
14733
|
+
await this.failClaimedAgent(
|
|
14734
|
+
taskId,
|
|
14735
|
+
`Agent session execution lease could not bind the runtime session: ${errorMessage5(error)}`,
|
|
14736
|
+
false,
|
|
14737
|
+
{
|
|
14738
|
+
binding: agentBinding,
|
|
14739
|
+
runtimeId: pick.descriptor.id,
|
|
14740
|
+
sessionRef: session.sessionRef
|
|
14741
|
+
}
|
|
14742
|
+
);
|
|
14743
|
+
return;
|
|
14744
|
+
}
|
|
14745
|
+
}
|
|
14155
14746
|
let active;
|
|
14156
14747
|
active = {
|
|
14157
14748
|
taskId,
|
|
14749
|
+
blobAbort,
|
|
14158
14750
|
egressEnabled: "egressPolicy" in payload,
|
|
14159
14751
|
adapter: pick.adapter,
|
|
14160
14752
|
session,
|
|
@@ -14195,15 +14787,16 @@ var TaskRunner = class {
|
|
|
14195
14787
|
...terminalProjection === void 0 ? {} : { terminalProjection }
|
|
14196
14788
|
};
|
|
14197
14789
|
if (agentBinding !== void 0) {
|
|
14790
|
+
const binding = agentBinding;
|
|
14198
14791
|
try {
|
|
14199
|
-
await this.deps.agentSessionHandoffs.record({
|
|
14200
|
-
agentRef:
|
|
14792
|
+
await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
|
|
14793
|
+
agentRef: binding.resolution.agentRef,
|
|
14201
14794
|
taskId,
|
|
14202
14795
|
sessionRef: session.sessionRef,
|
|
14203
14796
|
runtimeId: pick.descriptor.id,
|
|
14204
14797
|
cwd: workspaceDir,
|
|
14205
|
-
leaseId:
|
|
14206
|
-
});
|
|
14798
|
+
leaseId: binding.lease.leaseId
|
|
14799
|
+
}));
|
|
14207
14800
|
} catch (error) {
|
|
14208
14801
|
await session.close().catch(() => {
|
|
14209
14802
|
});
|
|
@@ -14245,6 +14838,7 @@ var TaskRunner = class {
|
|
|
14245
14838
|
}
|
|
14246
14839
|
this.deps.send(createEnvelope("task.started", {}, { taskId }));
|
|
14247
14840
|
agentLeaseTransferred = agentBinding !== void 0;
|
|
14841
|
+
this.inFlightBlobAborts.delete(taskId);
|
|
14248
14842
|
this.tasks.set(taskId, active);
|
|
14249
14843
|
this.pendingMessageTasks.delete(taskId);
|
|
14250
14844
|
if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
|
|
@@ -14276,6 +14870,7 @@ var TaskRunner = class {
|
|
|
14276
14870
|
await agentBinding.lease.release().catch(() => {
|
|
14277
14871
|
});
|
|
14278
14872
|
}
|
|
14873
|
+
this.inFlightBlobAborts.delete(taskId);
|
|
14279
14874
|
this.inFlightOffers.delete(taskId);
|
|
14280
14875
|
}
|
|
14281
14876
|
}
|
|
@@ -14427,9 +15022,9 @@ var TaskRunner = class {
|
|
|
14427
15022
|
void this.closeAgentMemoryFilesystem(taskId);
|
|
14428
15023
|
}
|
|
14429
15024
|
/** Protocol §7: an instruction too large to inline arrives as a `blobRef` — resolve it via the blob client rather than failing closed. */
|
|
14430
|
-
async resolveInstruction(instruction) {
|
|
15025
|
+
async resolveInstruction(instruction, signal) {
|
|
14431
15026
|
if (typeof instruction === "string") return instruction;
|
|
14432
|
-
return this.deps.blobClient.resolveInstruction(instruction.blobRef);
|
|
15027
|
+
return this.deps.blobClient.resolveInstruction(instruction.blobRef, { signal });
|
|
14433
15028
|
}
|
|
14434
15029
|
/** Resolve every requested logical id locally and reject missing/colliding server authority before claim. */
|
|
14435
15030
|
resolveMcpServers(requiredToolsets) {
|
|
@@ -14661,7 +15256,9 @@ var TaskRunner = class {
|
|
|
14661
15256
|
}
|
|
14662
15257
|
}
|
|
14663
15258
|
try {
|
|
14664
|
-
const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType
|
|
15259
|
+
const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType, {
|
|
15260
|
+
signal: active.blobAbort.signal
|
|
15261
|
+
});
|
|
14665
15262
|
this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
|
|
14666
15263
|
} catch (err) {
|
|
14667
15264
|
this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage5(err)}`);
|
|
@@ -14676,6 +15273,7 @@ var TaskRunner = class {
|
|
|
14676
15273
|
const active = this.tasks.get(taskId);
|
|
14677
15274
|
if (!active) {
|
|
14678
15275
|
this.setPendingCancelled(taskId, reason);
|
|
15276
|
+
this.inFlightBlobAborts.get(taskId)?.abort();
|
|
14679
15277
|
return;
|
|
14680
15278
|
}
|
|
14681
15279
|
if (active.finalizationStarted) {
|
|
@@ -14686,6 +15284,7 @@ var TaskRunner = class {
|
|
|
14686
15284
|
await active.semanticTerminalSettled;
|
|
14687
15285
|
return;
|
|
14688
15286
|
}
|
|
15287
|
+
active.blobAbort.abort();
|
|
14689
15288
|
try {
|
|
14690
15289
|
await active.session.interrupt();
|
|
14691
15290
|
} catch {
|
|
@@ -15185,7 +15784,7 @@ var TaskRunner = class {
|
|
|
15185
15784
|
async failClaimedAgent(taskId, reason, retryable, context) {
|
|
15186
15785
|
const agentRef = context.binding.resolution.agentRef;
|
|
15187
15786
|
const result = await this.retryAgentTerminalEvidence(
|
|
15188
|
-
() => this.deps.agentSessionHandoffs.recordTaskTerminal({
|
|
15787
|
+
() => this.deps.agentHome.mutateExecution(context.binding, () => this.deps.agentSessionHandoffs.recordTaskTerminal({
|
|
15189
15788
|
agentRef,
|
|
15190
15789
|
taskId,
|
|
15191
15790
|
runtimeId: context.runtimeId,
|
|
@@ -15193,7 +15792,7 @@ var TaskRunner = class {
|
|
|
15193
15792
|
leaseId: context.binding.lease.leaseId,
|
|
15194
15793
|
...context.sessionRef === void 0 ? {} : { sessionRef: context.sessionRef },
|
|
15195
15794
|
terminalReason: reason
|
|
15196
|
-
})
|
|
15795
|
+
}))
|
|
15197
15796
|
);
|
|
15198
15797
|
if (!result.ok) {
|
|
15199
15798
|
this.reportAgentTerminalEvidenceFailure({
|
|
@@ -16182,6 +16781,8 @@ var AgentEgressController = class {
|
|
|
16182
16781
|
options;
|
|
16183
16782
|
latest = new AgentLatestValueState();
|
|
16184
16783
|
spools = /* @__PURE__ */ new Map();
|
|
16784
|
+
spoolOpens = /* @__PURE__ */ new Map();
|
|
16785
|
+
reliableAppendTail = Promise.resolve();
|
|
16185
16786
|
latestStatus = emptyLane();
|
|
16186
16787
|
reliableStatus = emptyLane();
|
|
16187
16788
|
drops = [];
|
|
@@ -16236,7 +16837,8 @@ var AgentEgressController = class {
|
|
|
16236
16837
|
return latest === void 0 ? [] : Object.freeze([latest]);
|
|
16237
16838
|
}
|
|
16238
16839
|
async appendReliable(input) {
|
|
16239
|
-
|
|
16840
|
+
const tenantId = this.options.tenantId;
|
|
16841
|
+
if (!this.active || tenantId === void 0) {
|
|
16240
16842
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
16241
16843
|
return { ok: false, reason: "policy_denied" };
|
|
16242
16844
|
}
|
|
@@ -16251,17 +16853,21 @@ var AgentEgressController = class {
|
|
|
16251
16853
|
return { ok: false, reason: "sanitizer_rejected" };
|
|
16252
16854
|
}
|
|
16253
16855
|
try {
|
|
16254
|
-
const
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16856
|
+
const spoolOpen = this.spoolFor(input.homeDir, input.agentRef);
|
|
16857
|
+
return await this.withAppendTail(async () => {
|
|
16858
|
+
const spool = await spoolOpen;
|
|
16859
|
+
this.bindSpool(input.agentRef, spool, spoolOpen);
|
|
16860
|
+
const record = await spool.append({
|
|
16861
|
+
agentRef: input.agentRef,
|
|
16862
|
+
tenantId,
|
|
16863
|
+
policyRevision: this.options.policy.policyRevision,
|
|
16864
|
+
payload,
|
|
16865
|
+
sessionRef: input.sessionRef,
|
|
16866
|
+
...input.taskId === void 0 ? {} : { taskId: input.taskId },
|
|
16867
|
+
...input.eventId === void 0 ? {} : { eventId: input.eventId }
|
|
16868
|
+
}, this.options.policy, this.tenantPendingBytes());
|
|
16869
|
+
return { ok: true, record };
|
|
16870
|
+
});
|
|
16265
16871
|
} catch (error) {
|
|
16266
16872
|
const reason = error instanceof AgentReliableQuotaError ? error.reason : "backpressure";
|
|
16267
16873
|
this.noteDrop("reliable", reason, input.agentRef);
|
|
@@ -16274,21 +16880,26 @@ var AgentEgressController = class {
|
|
|
16274
16880
|
* with `wireType: agent.content.receipt` before any transport attempt.
|
|
16275
16881
|
*/
|
|
16276
16882
|
async appendContentReceipt(input) {
|
|
16277
|
-
|
|
16883
|
+
const tenantId = this.options.tenantId;
|
|
16884
|
+
if (!this.active || tenantId === void 0) {
|
|
16278
16885
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
16279
16886
|
return { ok: false, reason: "policy_denied" };
|
|
16280
16887
|
}
|
|
16281
16888
|
try {
|
|
16282
|
-
const
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16889
|
+
const spoolOpen = this.spoolFor(input.homeDir, input.agentRef);
|
|
16890
|
+
return await this.withAppendTail(async () => {
|
|
16891
|
+
const spool = await spoolOpen;
|
|
16892
|
+
this.bindSpool(input.agentRef, spool, spoolOpen);
|
|
16893
|
+
const record = await spool.appendContentReceipt({
|
|
16894
|
+
agentRef: input.agentRef,
|
|
16895
|
+
tenantId,
|
|
16896
|
+
policyRevision: this.options.policy.policyRevision,
|
|
16897
|
+
sessionRef: input.payload.sessionRef,
|
|
16898
|
+
payload: input.payload,
|
|
16899
|
+
...input.taskId === void 0 ? {} : { taskId: input.taskId }
|
|
16900
|
+
}, this.options.policy, this.tenantPendingBytes());
|
|
16901
|
+
return { ok: true, record };
|
|
16902
|
+
});
|
|
16292
16903
|
} catch (error) {
|
|
16293
16904
|
const reason = error instanceof AgentReliableQuotaError ? error.reason : "backpressure";
|
|
16294
16905
|
this.noteDrop("reliable", reason, input.agentRef);
|
|
@@ -16363,20 +16974,59 @@ var AgentEgressController = class {
|
|
|
16363
16974
|
}
|
|
16364
16975
|
return this.reliableRecords();
|
|
16365
16976
|
}
|
|
16366
|
-
|
|
16977
|
+
spoolFor(homeDir, agentRef) {
|
|
16367
16978
|
const key = agentKey(agentRef);
|
|
16368
16979
|
const existing = this.spools.get(key);
|
|
16369
|
-
if (existing)
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16980
|
+
if (existing) {
|
|
16981
|
+
if (existing.homeDir !== homeDir) throw new Error("Agent reliable spool is already bound to a different home");
|
|
16982
|
+
return Promise.resolve(existing);
|
|
16983
|
+
}
|
|
16984
|
+
const inFlight = this.spoolOpens.get(key);
|
|
16985
|
+
if (inFlight !== void 0) {
|
|
16986
|
+
if (inFlight.homeDir !== homeDir) throw new Error("Agent reliable spool opening is already bound to a different home");
|
|
16987
|
+
return inFlight.promise;
|
|
16988
|
+
}
|
|
16989
|
+
const opened = (async () => {
|
|
16990
|
+
const spool = await AgentReliableSpool.open(homeDir);
|
|
16991
|
+
if (spool.records().some((record) => !sameAgent(record.agentRef, agentRef))) {
|
|
16992
|
+
throw new Error("Agent-local reliable spool has a different AgentRef");
|
|
16993
|
+
}
|
|
16994
|
+
return spool;
|
|
16995
|
+
})();
|
|
16996
|
+
const slot = { homeDir, promise: opened };
|
|
16997
|
+
this.spoolOpens.set(key, slot);
|
|
16998
|
+
void opened.catch(() => {
|
|
16999
|
+
if (this.spoolOpens.get(key) === slot) this.spoolOpens.delete(key);
|
|
17000
|
+
});
|
|
17001
|
+
return opened;
|
|
17002
|
+
}
|
|
17003
|
+
bindSpool(agentRef, spool, spoolOpen) {
|
|
17004
|
+
const key = agentKey(agentRef);
|
|
17005
|
+
const existing = this.spools.get(key);
|
|
17006
|
+
try {
|
|
17007
|
+
if (existing && existing !== spool) throw new Error("multiple Agent egress spools claim the same AgentRef");
|
|
17008
|
+
this.spools.set(key, spool);
|
|
17009
|
+
} finally {
|
|
17010
|
+
const slot = this.spoolOpens.get(key);
|
|
17011
|
+
if (slot?.promise === spoolOpen) this.spoolOpens.delete(key);
|
|
16373
17012
|
}
|
|
16374
|
-
this.spools.set(key, spool);
|
|
16375
|
-
return spool;
|
|
16376
17013
|
}
|
|
16377
17014
|
tenantPendingBytes() {
|
|
16378
17015
|
return this.reliableRecords().reduce((total, record) => total + record.byteCount, 0);
|
|
16379
17016
|
}
|
|
17017
|
+
async withAppendTail(operation) {
|
|
17018
|
+
const previous = this.reliableAppendTail;
|
|
17019
|
+
let release;
|
|
17020
|
+
this.reliableAppendTail = new Promise((resolve) => {
|
|
17021
|
+
release = resolve;
|
|
17022
|
+
});
|
|
17023
|
+
try {
|
|
17024
|
+
await previous;
|
|
17025
|
+
return await operation();
|
|
17026
|
+
} finally {
|
|
17027
|
+
release();
|
|
17028
|
+
}
|
|
17029
|
+
}
|
|
16380
17030
|
noteDrop(lane, reason, agentRef, countDrop = true) {
|
|
16381
17031
|
const laneStatus = lane === "latest-value" ? this.latestStatus : this.reliableStatus;
|
|
16382
17032
|
if (countDrop) laneStatus.dropped += 1;
|
|
@@ -17648,6 +18298,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
17648
18298
|
const observer = new DaemonObserver();
|
|
17649
18299
|
const approvalRegistry = new ApprovalRegistry();
|
|
17650
18300
|
let connection;
|
|
18301
|
+
let blobLifecycleAbort;
|
|
17651
18302
|
let connectionState = "closed";
|
|
17652
18303
|
let daemonStarted = false;
|
|
17653
18304
|
let tenantRebinding = false;
|
|
@@ -17666,6 +18317,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
17666
18317
|
function buildAuthManager() {
|
|
17667
18318
|
return new AuthManager({
|
|
17668
18319
|
serverUrl: config.serverUrl,
|
|
18320
|
+
authRequestDeadlineMs: config.authRequestDeadlineMs,
|
|
17669
18321
|
store,
|
|
17670
18322
|
deviceName: config.deviceName,
|
|
17671
18323
|
machineId: config.machineId ?? (() => resolveMachineId({ productId: config.productId })),
|
|
@@ -17683,7 +18335,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
17683
18335
|
assertServerUrlAllowed(config.serverUrl, { dangerouslyAllowInsecureRemote: true });
|
|
17684
18336
|
const reason = err instanceof Error ? err.message : String(err);
|
|
17685
18337
|
console.warn(
|
|
17686
|
-
`[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})`
|
|
18338
|
+
`[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})`
|
|
17687
18339
|
);
|
|
17688
18340
|
}
|
|
17689
18341
|
}
|
|
@@ -17843,9 +18495,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
17843
18495
|
await gitWorkspaceStore?.initialize();
|
|
17844
18496
|
await gitWorkspaceStore?.reconcile();
|
|
17845
18497
|
}
|
|
18498
|
+
blobLifecycleAbort = new AbortController();
|
|
17846
18499
|
const [runtimes, blobClient] = await Promise.all([
|
|
17847
18500
|
detectRuntimes(adapters),
|
|
17848
|
-
Promise.resolve(new BlobClient(config.serverUrl, auth))
|
|
18501
|
+
Promise.resolve(new BlobClient(config.serverUrl, auth, { signal: blobLifecycleAbort.signal }))
|
|
17849
18502
|
]);
|
|
17850
18503
|
observer.noteRuntimesDetected(runtimes);
|
|
17851
18504
|
detectedRuntimeFacts = runtimes;
|
|
@@ -18274,6 +18927,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
18274
18927
|
}
|
|
18275
18928
|
async function runShutdownSequence(reason, opts = {}) {
|
|
18276
18929
|
shuttingDown = true;
|
|
18930
|
+
blobLifecycleAbort?.abort();
|
|
18931
|
+
blobLifecycleAbort = void 0;
|
|
18277
18932
|
const errors = [];
|
|
18278
18933
|
let mutationBarrierComplete = hostedStorageInitializationBarrierComplete;
|
|
18279
18934
|
if (!hostedStorageInitializationBarrierComplete) {
|
|
@@ -19253,7 +19908,7 @@ function createServiceLifecycle(def, opts = {}) {
|
|
|
19253
19908
|
|
|
19254
19909
|
// src/bin/official-release.ts
|
|
19255
19910
|
var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
|
|
19256
|
-
version: "0.
|
|
19911
|
+
version: "0.12.0"
|
|
19257
19912
|
});
|
|
19258
19913
|
|
|
19259
19914
|
// src/bin/config.ts
|