@byok-sdk/client 0.6.1 → 0.8.0-beta.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 +11 -8
- package/dist/bin/byok-agent.js +142 -47
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/daemon/agent-egress-controller.d.ts +3 -0
- package/dist/daemon/create-daemon.d.ts +4 -10
- package/dist/daemon/store.d.ts +9 -0
- package/dist/daemon/task-runner.d.ts +2 -2
- package/dist/index.js +141 -46
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -122,17 +122,19 @@ contents.
|
|
|
122
122
|
|
|
123
123
|
## Agent egress and explicit content reads
|
|
124
124
|
|
|
125
|
-
`agentEgress` is consumed configuration, not a profile
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
`agentEgress` is consumed policy configuration, not a profile or tenant
|
|
126
|
+
projection. The host selects one exact policy revision. The daemon obtains its
|
|
127
|
+
tenant binding only from the authenticated pair response persisted in the
|
|
128
|
+
atomic local `DeviceRecord`; there is no `agentEgress.tenantId` setting and no
|
|
129
|
+
Profile/config, deviceId, or access-token fallback. Omitting contentful mode
|
|
130
|
+
keeps runtime activity metadata/status-only; enabling it is an explicit product
|
|
131
|
+
decision and requires the server capability. Reliable events are fsynced under
|
|
132
|
+
the canonical Agent home and retire only after an exact ack.
|
|
130
133
|
|
|
131
134
|
```ts
|
|
132
135
|
createDaemon({
|
|
133
136
|
// ...normal device, transport and agentHome configuration
|
|
134
137
|
agentEgress: {
|
|
135
|
-
tenantId: 'tenant-authority-from-salesko',
|
|
136
138
|
policy: {
|
|
137
139
|
policyRevision: 'salesko-agent-egress-r1',
|
|
138
140
|
activity: { mode: 'metadata-status', delivery: 'latest-value' },
|
|
@@ -163,8 +165,9 @@ its local supplement. The local supplement can only narrow root, text, MIME,
|
|
|
163
165
|
size and sensitive-name behavior; it cannot enable a wire-disabled surface.
|
|
164
166
|
The SDK derives `agents/<agentId>`, `.byok/egress`, runtime-session evidence and
|
|
165
167
|
the per-Agent content-read audit path. Salesko must not compose those paths.
|
|
166
|
-
Tenant/device identity comes from authenticated
|
|
167
|
-
override it. Transcript reads
|
|
168
|
+
Tenant/device identity comes from the persisted authenticated enrollment; a
|
|
169
|
+
request or editable host configuration cannot override it. Transcript reads
|
|
170
|
+
additionally require the exact persisted
|
|
168
171
|
AgentRef/session/runtime/cwd handoff. Allowed content is uploaded through the
|
|
169
172
|
authenticated blob channel. The content-free receipt is fsynced into the
|
|
170
173
|
Agent-local reliable spool with stable event/cursor identity before send and
|
package/dist/bin/byok-agent.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { randomUUID, createHash, randomBytes, timingSafeEqual, createHmac, createPrivateKey, generateKeyPairSync, sign } from 'crypto';
|
|
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 path, { isAbsolute, join } from 'path';
|
|
5
|
-
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, AGENT_EGRESS_POLICY_CAPABILITY, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, 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 } from '@byok-sdk/protocol';
|
|
5
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, 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, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, 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 } from '@byok-sdk/protocol';
|
|
6
6
|
import { execFile, spawn } from 'child_process';
|
|
7
7
|
import os from 'os';
|
|
8
|
-
import { DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
8
|
+
import { isTenantId, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import 'readline';
|
|
@@ -4732,6 +4732,13 @@ var ApprovalRegistry = class {
|
|
|
4732
4732
|
}
|
|
4733
4733
|
};
|
|
4734
4734
|
var MAX_DEVICE_RECORD_BYTES = 256 * 1024;
|
|
4735
|
+
var REPAIR_REQUIRED_MESSAGE = "device enrollment record is missing or has an invalid authenticated tenant binding; re-pair required";
|
|
4736
|
+
var DeviceRecordRePairRequiredError = class extends Error {
|
|
4737
|
+
constructor() {
|
|
4738
|
+
super(REPAIR_REQUIRED_MESSAGE);
|
|
4739
|
+
this.name = "DeviceRecordRePairRequiredError";
|
|
4740
|
+
}
|
|
4741
|
+
};
|
|
4735
4742
|
function sameInode(left, right) {
|
|
4736
4743
|
return left.dev === right.dev && left.ino === right.ino;
|
|
4737
4744
|
}
|
|
@@ -4741,18 +4748,32 @@ function sameFileState(left, right) {
|
|
|
4741
4748
|
function sameContentState(left, right) {
|
|
4742
4749
|
return sameInode(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs;
|
|
4743
4750
|
}
|
|
4751
|
+
function assertDeviceRecord(value) {
|
|
4752
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4753
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4754
|
+
}
|
|
4755
|
+
const parsed = value;
|
|
4756
|
+
if (typeof parsed.deviceId === "string" && isTenantId(parsed.tenantId) && typeof parsed.accessToken === "string" && typeof parsed.expiresAt === "string" && typeof parsed.devicePrivateKeyPem === "string" && typeof parsed.devicePublicKey === "string") {
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4760
|
+
}
|
|
4744
4761
|
function parseDeviceRecord(raw) {
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
expiresAt: parsed.expiresAt,
|
|
4751
|
-
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
4752
|
-
devicePublicKey: parsed.devicePublicKey
|
|
4753
|
-
};
|
|
4762
|
+
let parsed;
|
|
4763
|
+
try {
|
|
4764
|
+
parsed = JSON.parse(raw);
|
|
4765
|
+
} catch {
|
|
4766
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4754
4767
|
}
|
|
4755
|
-
|
|
4768
|
+
assertDeviceRecord(parsed);
|
|
4769
|
+
return {
|
|
4770
|
+
deviceId: parsed.deviceId,
|
|
4771
|
+
tenantId: parsed.tenantId,
|
|
4772
|
+
accessToken: parsed.accessToken,
|
|
4773
|
+
expiresAt: parsed.expiresAt,
|
|
4774
|
+
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
4775
|
+
devicePublicKey: parsed.devicePublicKey
|
|
4776
|
+
};
|
|
4756
4777
|
}
|
|
4757
4778
|
var DeviceStore = class _DeviceStore {
|
|
4758
4779
|
/**
|
|
@@ -4822,6 +4843,7 @@ var DeviceStore = class _DeviceStore {
|
|
|
4822
4843
|
}
|
|
4823
4844
|
}
|
|
4824
4845
|
async save(record) {
|
|
4846
|
+
assertDeviceRecord(record);
|
|
4825
4847
|
const storeDir = path.dirname(this.filePath);
|
|
4826
4848
|
await ensureSecureDir(storeDir, this.secureDirOptions);
|
|
4827
4849
|
await atomicWriteFile(this.filePath, JSON.stringify(record, null, 2), { mode: 384 });
|
|
@@ -4992,7 +5014,14 @@ var AuthManager = class {
|
|
|
4992
5014
|
this.proactiveTimer = void 0;
|
|
4993
5015
|
try {
|
|
4994
5016
|
return await this.runCredentialMutation(async () => {
|
|
4995
|
-
|
|
5017
|
+
let existing = this.record;
|
|
5018
|
+
if (!existing) {
|
|
5019
|
+
try {
|
|
5020
|
+
existing = await this.opts.store.load();
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
4996
5025
|
const keyPair = existing ? { privateKey: importPrivateKeyPem(existing.devicePrivateKeyPem), publicKeyBase64Url: existing.devicePublicKey } : generateDeviceKeyPair();
|
|
4997
5026
|
const url = new URL(BYOK_PAIR_PATH, toHttpBase(this.opts.serverUrl));
|
|
4998
5027
|
const res = await fetch(url, {
|
|
@@ -5007,9 +5036,10 @@ var AuthManager = class {
|
|
|
5007
5036
|
if (!res.ok) {
|
|
5008
5037
|
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
5009
5038
|
}
|
|
5010
|
-
const body = await res.json();
|
|
5039
|
+
const body = PairResponseSchema.parse(await res.json());
|
|
5011
5040
|
const record = {
|
|
5012
5041
|
deviceId: body.deviceId,
|
|
5042
|
+
tenantId: body.tenantId,
|
|
5013
5043
|
accessToken: body.accessToken,
|
|
5014
5044
|
expiresAt: resolvePairExpiry(body.refreshHint),
|
|
5015
5045
|
devicePrivateKeyPem: exportPrivateKeyPem(keyPair.privateKey),
|
|
@@ -9880,6 +9910,11 @@ function offeredAgentRef(payload) {
|
|
|
9880
9910
|
if (!Object.prototype.hasOwnProperty.call(payload, "agentRef")) return void 0;
|
|
9881
9911
|
return validateAgentRef(payload.agentRef);
|
|
9882
9912
|
}
|
|
9913
|
+
function offeredSessionRef(payload) {
|
|
9914
|
+
if (!Object.prototype.hasOwnProperty.call(payload, "sessionRef")) return void 0;
|
|
9915
|
+
const value = payload.sessionRef;
|
|
9916
|
+
return typeof value === "string" ? value : void 0;
|
|
9917
|
+
}
|
|
9883
9918
|
function errorMessage4(err) {
|
|
9884
9919
|
return err instanceof Error ? err.message : String(err);
|
|
9885
9920
|
}
|
|
@@ -10248,6 +10283,9 @@ var TaskRunner = class {
|
|
|
10248
10283
|
case "task.offer_for_agent_with_egress":
|
|
10249
10284
|
await this.handleOffer(envelope.task_id, envelope.payload, true);
|
|
10250
10285
|
return;
|
|
10286
|
+
case "task.offer_for_agent_with_egress_fresh":
|
|
10287
|
+
await this.handleOffer(envelope.task_id, envelope.payload, true);
|
|
10288
|
+
return;
|
|
10251
10289
|
case "task.cancel":
|
|
10252
10290
|
await this.handleCancel(envelope.task_id, envelope.payload.reason);
|
|
10253
10291
|
return;
|
|
@@ -10289,6 +10327,7 @@ var TaskRunner = class {
|
|
|
10289
10327
|
const decline = (reason, retryable) => {
|
|
10290
10328
|
this.decline(taskId, reason, retryable, agentRef);
|
|
10291
10329
|
};
|
|
10330
|
+
const sessionRef = offeredSessionRef(payload);
|
|
10292
10331
|
if ("egressPolicy" in payload) {
|
|
10293
10332
|
if (this.deps.agentEgressPolicy === void 0 || !sameEgressPolicy(this.deps.agentEgressPolicy, payload.egressPolicy)) {
|
|
10294
10333
|
decline("Agent egress offer policy is not exactly enabled by this daemon", false);
|
|
@@ -10390,11 +10429,11 @@ var TaskRunner = class {
|
|
|
10390
10429
|
let plainWorkspaceNeedsResolve = false;
|
|
10391
10430
|
if (agentBinding !== void 0) {
|
|
10392
10431
|
workspaceDir = agentBinding.lease.cwd;
|
|
10393
|
-
if (
|
|
10432
|
+
if (sessionRef !== void 0) {
|
|
10394
10433
|
try {
|
|
10395
10434
|
await this.deps.agentSessionHandoffs.requireMatch({
|
|
10396
10435
|
agentRef: agentBinding.resolution.agentRef,
|
|
10397
|
-
sessionRef
|
|
10436
|
+
sessionRef,
|
|
10398
10437
|
runtimeId: pick.descriptor.id,
|
|
10399
10438
|
cwd: workspaceDir
|
|
10400
10439
|
});
|
|
@@ -10416,15 +10455,15 @@ var TaskRunner = class {
|
|
|
10416
10455
|
return;
|
|
10417
10456
|
}
|
|
10418
10457
|
} else if (this.deps.gitWorkspaceManager && this.deps.gitWorkspaceStore) {
|
|
10419
|
-
known =
|
|
10458
|
+
known = sessionRef ? await this.deps.sessionWorkspaces.get(sessionRef) : void 0;
|
|
10420
10459
|
const gitManager = this.deps.gitWorkspaceManager;
|
|
10421
10460
|
const gitStore = this.deps.gitWorkspaceStore;
|
|
10422
|
-
if (
|
|
10423
|
-
const ledger = await gitStore.findBySessionAnyPhase(
|
|
10461
|
+
if (sessionRef) {
|
|
10462
|
+
const ledger = await gitStore.findBySessionAnyPhase(sessionRef).catch(() => void 0);
|
|
10424
10463
|
const sameProtocolTask = ledger?.taskId === taskId;
|
|
10425
10464
|
const interruptedOldTask = ledger?.phase === "interrupted" && sameProtocolTask;
|
|
10426
10465
|
const activeDifferentTask = ledger !== void 0 && ledger.taskId !== taskId && (ledger.phase === "preparing" || ledger.phase === "active");
|
|
10427
|
-
if (!known || known.workspaceKind !== "git" || !known.gitWorkspaceId || !ledger || ledger.workspaceId !== known.gitWorkspaceId || ledger.sessionRef !==
|
|
10466
|
+
if (!known || known.workspaceKind !== "git" || !known.gitWorkspaceId || !ledger || ledger.workspaceId !== known.gitWorkspaceId || ledger.sessionRef !== sessionRef || path.resolve(ledger.workspaceDir) !== path.resolve(known.workspaceDir) || interruptedOldTask || activeDifferentTask) {
|
|
10428
10467
|
decline("session is incompatible with Git workspace mode", true);
|
|
10429
10468
|
return;
|
|
10430
10469
|
}
|
|
@@ -10443,13 +10482,13 @@ var TaskRunner = class {
|
|
|
10443
10482
|
gitWorkspaceId = randomUUID();
|
|
10444
10483
|
}
|
|
10445
10484
|
try {
|
|
10446
|
-
gitLease = await gitManager.acquireLease(workspaceDir,
|
|
10485
|
+
gitLease = await gitManager.acquireLease(workspaceDir, sessionRef);
|
|
10447
10486
|
} catch {
|
|
10448
10487
|
decline("workspace is busy or unavailable", true);
|
|
10449
10488
|
return;
|
|
10450
10489
|
}
|
|
10451
10490
|
} else if (!this.deps.gitWorkspaceManager && !this.deps.gitWorkspaceStore) {
|
|
10452
|
-
known =
|
|
10491
|
+
known = sessionRef ? await this.deps.sessionWorkspaces.get(sessionRef) : void 0;
|
|
10453
10492
|
workspaceDir = known?.workspaceDir ?? path.join(this.deps.workspaceRoot, taskId);
|
|
10454
10493
|
plainWorkspaceNeedsResolve = true;
|
|
10455
10494
|
} else {
|
|
@@ -10468,7 +10507,7 @@ var TaskRunner = class {
|
|
|
10468
10507
|
policy: decision.policy,
|
|
10469
10508
|
requiredToolsetIds: requiredToolsets ?? [],
|
|
10470
10509
|
...offered.dispatchSelection === void 0 ? {} : { dispatchSelection: offered.dispatchSelection },
|
|
10471
|
-
...
|
|
10510
|
+
...sessionRef === void 0 || known === void 0 && agentBinding === void 0 ? {} : { sessionRef },
|
|
10472
10511
|
...agentBinding === void 0 ? {} : {
|
|
10473
10512
|
agentRef: agentBinding.resolution.agentRef,
|
|
10474
10513
|
cwd: agentBinding.lease.cwd,
|
|
@@ -10571,7 +10610,7 @@ var TaskRunner = class {
|
|
|
10571
10610
|
workspaceId,
|
|
10572
10611
|
taskId,
|
|
10573
10612
|
workspaceDir,
|
|
10574
|
-
sessionRef
|
|
10613
|
+
sessionRef,
|
|
10575
10614
|
phase,
|
|
10576
10615
|
baseline: gitBaseline ?? observation.head,
|
|
10577
10616
|
current: observation.head,
|
|
@@ -12428,9 +12467,14 @@ var AgentEgressController = class {
|
|
|
12428
12467
|
latestStatus = emptyLane();
|
|
12429
12468
|
reliableStatus = emptyLane();
|
|
12430
12469
|
drops = [];
|
|
12470
|
+
active = true;
|
|
12431
12471
|
get policy() {
|
|
12432
12472
|
return this.options.policy;
|
|
12433
12473
|
}
|
|
12474
|
+
/** Permanently fail closed after its authenticated enrollment is replaced. */
|
|
12475
|
+
deactivate() {
|
|
12476
|
+
this.active = false;
|
|
12477
|
+
}
|
|
12434
12478
|
status() {
|
|
12435
12479
|
const reliable = this.reliableRecords();
|
|
12436
12480
|
return Object.freeze({
|
|
@@ -12448,6 +12492,10 @@ var AgentEgressController = class {
|
|
|
12448
12492
|
/** Project before TaskRunner builds a `task.progress` envelope. */
|
|
12449
12493
|
projectLatestValue(input) {
|
|
12450
12494
|
if (input.agentRef === void 0) return Object.freeze([...input.events]);
|
|
12495
|
+
if (!this.active) {
|
|
12496
|
+
this.noteDrop("latest-value", "policy_denied", input.agentRef);
|
|
12497
|
+
return [];
|
|
12498
|
+
}
|
|
12451
12499
|
if (this.options.policy.activity.mode === "contentful-trajectory" && !input.serverCapabilities.includes("agent-egress-policy")) {
|
|
12452
12500
|
this.noteDrop("latest-value", "capability_missing", input.agentRef);
|
|
12453
12501
|
return [];
|
|
@@ -12470,7 +12518,7 @@ var AgentEgressController = class {
|
|
|
12470
12518
|
return latest === void 0 ? [] : Object.freeze([latest]);
|
|
12471
12519
|
}
|
|
12472
12520
|
async appendReliable(input) {
|
|
12473
|
-
if (this.options.tenantId === void 0) {
|
|
12521
|
+
if (!this.active || this.options.tenantId === void 0) {
|
|
12474
12522
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
12475
12523
|
return { ok: false, reason: "policy_denied" };
|
|
12476
12524
|
}
|
|
@@ -12508,7 +12556,7 @@ var AgentEgressController = class {
|
|
|
12508
12556
|
* with `wireType: agent.content.receipt` before any transport attempt.
|
|
12509
12557
|
*/
|
|
12510
12558
|
async appendContentReceipt(input) {
|
|
12511
|
-
if (this.options.tenantId === void 0) {
|
|
12559
|
+
if (!this.active || this.options.tenantId === void 0) {
|
|
12512
12560
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
12513
12561
|
return { ok: false, reason: "policy_denied" };
|
|
12514
12562
|
}
|
|
@@ -12532,7 +12580,7 @@ var AgentEgressController = class {
|
|
|
12532
12580
|
/** Retires only the record whose full Agent/tenant/revision/id/cursor tuple matches. */
|
|
12533
12581
|
async acknowledge(ack) {
|
|
12534
12582
|
const spool = this.spools.get(agentKey(ack.agentRef));
|
|
12535
|
-
if (!spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
|
|
12583
|
+
if (!this.active || !spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
|
|
12536
12584
|
this.noteDrop("reliable", "ack_mismatch", ack.agentRef);
|
|
12537
12585
|
return false;
|
|
12538
12586
|
}
|
|
@@ -12543,6 +12591,7 @@ var AgentEgressController = class {
|
|
|
12543
12591
|
/** Re-open every existing Agent-local spool before retrying stable records after restart. */
|
|
12544
12592
|
async recover(agentsRoot) {
|
|
12545
12593
|
if (!path.isAbsolute(agentsRoot)) throw new Error("Agent egress recovery root must be absolute");
|
|
12594
|
+
if (!this.active) throw new Error("Agent egress recovery requires an active authenticated enrollment");
|
|
12546
12595
|
if (this.options.tenantId === void 0) {
|
|
12547
12596
|
throw new Error("Agent egress recovery requires one authenticated tenant authority");
|
|
12548
12597
|
}
|
|
@@ -13528,7 +13577,13 @@ function computeCapabilities(adapters, agentHomeConfigured = false, agentEgressC
|
|
|
13528
13577
|
flags.push("toolset-selection");
|
|
13529
13578
|
}
|
|
13530
13579
|
if (agentHomeConfigured) flags.push("agent-home-contract");
|
|
13531
|
-
if (agentEgressConfigured)
|
|
13580
|
+
if (agentEgressConfigured) {
|
|
13581
|
+
flags.push(
|
|
13582
|
+
AGENT_EGRESS_POLICY_CAPABILITY,
|
|
13583
|
+
AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
|
|
13584
|
+
AGENT_EGRESS_FRESH_SESSION_CAPABILITY
|
|
13585
|
+
);
|
|
13586
|
+
}
|
|
13532
13587
|
if (contentReadPolicies !== void 0) {
|
|
13533
13588
|
for (const surface of Object.keys(AGENT_CONTENT_READ_CAPABILITIES)) {
|
|
13534
13589
|
const policy = contentReadPolicies[surface];
|
|
@@ -13650,9 +13705,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13650
13705
|
if (config.agentEgress !== void 0 && config.agentHome === void 0) {
|
|
13651
13706
|
throw new Error("DaemonConfig.agentEgress requires DaemonConfig.agentHome for the per-Agent local spool");
|
|
13652
13707
|
}
|
|
13653
|
-
if (config.agentEgress !== void 0 && (config.agentEgress.tenantId.length === 0 || config.agentEgress.tenantId.trim() !== config.agentEgress.tenantId)) {
|
|
13654
|
-
throw new Error("DaemonConfig.agentEgress.tenantId must be a non-empty canonical authenticated tenant id");
|
|
13655
|
-
}
|
|
13656
13708
|
const egressPolicy = resolveAgentEgressPolicy(config.agentEgress?.policy);
|
|
13657
13709
|
const egressBatcherOptions = egressPolicy.activity.mode === "contentful-trajectory" ? {
|
|
13658
13710
|
...config.progressBatch,
|
|
@@ -13683,9 +13735,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13683
13735
|
});
|
|
13684
13736
|
const agentSessionHandoffs = config.agentHome === void 0 ? void 0 : new AgentSessionHandoffStore();
|
|
13685
13737
|
const agentContentReadPolicies = resolveContentReadPolicies(egressPolicy, config.agentEgress?.contentRead);
|
|
13686
|
-
|
|
13738
|
+
let agentEgress = new AgentEgressController({
|
|
13687
13739
|
policy: egressPolicy,
|
|
13688
|
-
...config.agentEgress?.tenantId === void 0 ? {} : { tenantId: config.agentEgress.tenantId },
|
|
13689
13740
|
...config.agentEgress?.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13690
13741
|
});
|
|
13691
13742
|
const gitWorkspaceManager = config.gitWorkspace ? overrides.gitWorkspace?.manager ?? new GitWorkspaceManager(config.workspaceRoot, { ownerId: stableGitWorkspaceOwnerId(storeDir, config.productId) }) : void 0;
|
|
@@ -13695,11 +13746,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13695
13746
|
if (config.hostedJournal.mode !== "sqlite") {
|
|
13696
13747
|
throw new Error(`DaemonConfig.hostedJournal.mode must be "sqlite" \u2014 got ${JSON.stringify(config.hostedJournal.mode)}`);
|
|
13697
13748
|
}
|
|
13698
|
-
if (typeof config.hostedJournal.tenantId !== "string" || config.hostedJournal.tenantId.trim() === "") {
|
|
13699
|
-
throw new Error(
|
|
13700
|
-
"DaemonConfig.hostedJournal.tenantId must be a non-empty tenant id \u2014 a hosted journal row with no tenant is durable evidence nobody can act on"
|
|
13701
|
-
);
|
|
13702
|
-
}
|
|
13703
13749
|
if (config.hostedJournal.storagePolicy) {
|
|
13704
13750
|
resolvedStoragePolicy = resolveLocalStoragePolicy(config.hostedJournal.storagePolicy);
|
|
13705
13751
|
}
|
|
@@ -13768,6 +13814,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13768
13814
|
const approvalRegistry = new ApprovalRegistry();
|
|
13769
13815
|
let connection;
|
|
13770
13816
|
let connectionState = "closed";
|
|
13817
|
+
let daemonStarted = false;
|
|
13818
|
+
let tenantRebinding = false;
|
|
13771
13819
|
let runner;
|
|
13772
13820
|
let controlServerHandle;
|
|
13773
13821
|
let daemonOwnerLease;
|
|
@@ -13820,11 +13868,31 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13820
13868
|
return runLifecycleMutation(() => pairUnderLease(pairingCode));
|
|
13821
13869
|
}
|
|
13822
13870
|
async function pairUnderLease(pairingCode) {
|
|
13871
|
+
const wasRunning = daemonStarted;
|
|
13872
|
+
if (wasRunning) tenantRebinding = true;
|
|
13823
13873
|
const acquiredHere = daemonOwnerLease === void 0;
|
|
13824
13874
|
if (acquiredHere) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
|
|
13875
|
+
let replacementPersisted = false;
|
|
13825
13876
|
try {
|
|
13826
|
-
|
|
13877
|
+
let previous;
|
|
13878
|
+
try {
|
|
13879
|
+
previous = await store.load();
|
|
13880
|
+
} catch (error) {
|
|
13881
|
+
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
13882
|
+
}
|
|
13827
13883
|
const record = await auth.pair(pairingCode);
|
|
13884
|
+
replacementPersisted = true;
|
|
13885
|
+
if (config.agentEgress !== void 0) {
|
|
13886
|
+
agentEgress.deactivate();
|
|
13887
|
+
agentEgress = new AgentEgressController({
|
|
13888
|
+
policy: egressPolicy,
|
|
13889
|
+
...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13890
|
+
});
|
|
13891
|
+
}
|
|
13892
|
+
if (wasRunning) {
|
|
13893
|
+
await runShutdownSequence("re-pairing enrollment binding");
|
|
13894
|
+
tenantRebinding = false;
|
|
13895
|
+
}
|
|
13828
13896
|
if (previous && previous.deviceId !== record.deviceId) {
|
|
13829
13897
|
await cursorStore.clear(config.serverUrl, previous.deviceId);
|
|
13830
13898
|
}
|
|
@@ -13835,6 +13903,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13835
13903
|
}
|
|
13836
13904
|
return record;
|
|
13837
13905
|
} catch (err) {
|
|
13906
|
+
if (wasRunning && !replacementPersisted) tenantRebinding = false;
|
|
13838
13907
|
if (acquiredHere) {
|
|
13839
13908
|
await daemonOwnerLease?.release();
|
|
13840
13909
|
daemonOwnerLease = void 0;
|
|
@@ -13857,6 +13926,13 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13857
13926
|
if (!record) {
|
|
13858
13927
|
throw new Error("device is not paired yet; call pair(pairingCode) first");
|
|
13859
13928
|
}
|
|
13929
|
+
if (config.agentEgress !== void 0) {
|
|
13930
|
+
agentEgress = new AgentEgressController({
|
|
13931
|
+
policy: egressPolicy,
|
|
13932
|
+
tenantId: record.tenantId,
|
|
13933
|
+
...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13934
|
+
});
|
|
13935
|
+
}
|
|
13860
13936
|
await agentHomeManager?.preflight();
|
|
13861
13937
|
if (config.agentEgress !== void 0 && config.agentHome !== void 0) {
|
|
13862
13938
|
await agentEgress.recover(path.join(config.agentHome.hostStorageRoot, "agents"));
|
|
@@ -13917,7 +13993,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13917
13993
|
config.agentEgress !== void 0,
|
|
13918
13994
|
agentContentReadPolicies
|
|
13919
13995
|
);
|
|
13920
|
-
const journalIdentity = config.hostedJournal ? { tenantId:
|
|
13996
|
+
const journalIdentity = config.hostedJournal ? { tenantId: record.tenantId, productId: config.productId, deviceId: record.deviceId } : void 0;
|
|
13921
13997
|
const sendSanitizedEnvelope = activeJournal && journalIdentity ? (envelope) => {
|
|
13922
13998
|
observer.handleOutboundEnvelope(envelope);
|
|
13923
13999
|
const terminalKind = terminalKindOf(envelope.type);
|
|
@@ -14055,9 +14131,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14055
14131
|
runner = new TaskRunner(deps);
|
|
14056
14132
|
const handleAgentEgressEnvelope = async (envelope) => {
|
|
14057
14133
|
if (envelope.type !== "agent.egress.ack") return false;
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
await agentEgress.acknowledge({ ...envelope.payload, tenantId });
|
|
14134
|
+
if (config.agentEgress === void 0) return true;
|
|
14135
|
+
await agentEgress.acknowledge({ ...envelope.payload, tenantId: record.tenantId });
|
|
14061
14136
|
return true;
|
|
14062
14137
|
};
|
|
14063
14138
|
const handleAgentContentReadEnvelope = async (envelope) => {
|
|
@@ -14109,7 +14184,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14109
14184
|
const result = await policyEngine.read({
|
|
14110
14185
|
requestId: payload.requestId,
|
|
14111
14186
|
actor: payload.actor,
|
|
14112
|
-
tenantId:
|
|
14187
|
+
tenantId: record.tenantId,
|
|
14113
14188
|
deviceId: record.deviceId,
|
|
14114
14189
|
agentRef: payload.agentRef,
|
|
14115
14190
|
surface: payload.surface,
|
|
@@ -14205,6 +14280,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14205
14280
|
//
|
|
14206
14281
|
// The no-journal branch is the ORIGINAL closure, unchanged.
|
|
14207
14282
|
onEnvelope: activeJournal && journalIdentity ? async (envelope) => {
|
|
14283
|
+
if (tenantRebinding) {
|
|
14284
|
+
throw new Error("tenant enrollment is being re-paired; inbound work is blocked until restart");
|
|
14285
|
+
}
|
|
14208
14286
|
observer.handleInboundEnvelope(envelope);
|
|
14209
14287
|
if (await handleAgentEgressEnvelope(envelope)) return;
|
|
14210
14288
|
if (await handleAgentContentReadEnvelope(envelope)) return;
|
|
@@ -14212,6 +14290,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14212
14290
|
await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
|
|
14213
14291
|
return runner?.handleEnvelope(envelope) ?? Promise.resolve();
|
|
14214
14292
|
} : (envelope) => {
|
|
14293
|
+
if (tenantRebinding) {
|
|
14294
|
+
return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
|
|
14295
|
+
}
|
|
14215
14296
|
observer.handleInboundEnvelope(envelope);
|
|
14216
14297
|
if (envelope.type === "agent.egress.ack") return handleAgentEgressEnvelope(envelope).then(() => void 0);
|
|
14217
14298
|
if (envelope.type === "agent.content.read") return handleAgentContentReadEnvelope(envelope).then(() => void 0);
|
|
@@ -14243,6 +14324,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14243
14324
|
dispatchReliableRecord(record2);
|
|
14244
14325
|
}
|
|
14245
14326
|
startPresenceProducer();
|
|
14327
|
+
daemonStarted = true;
|
|
14246
14328
|
} catch (err) {
|
|
14247
14329
|
try {
|
|
14248
14330
|
await runShutdownSequence("startup failed", { drainTimeoutMs: 0 });
|
|
@@ -14387,6 +14469,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14387
14469
|
if (!mutationBarrierComplete) {
|
|
14388
14470
|
throw new Error("daemon shutdown mutation barrier is incomplete; ownership lease retained");
|
|
14389
14471
|
}
|
|
14472
|
+
daemonStarted = false;
|
|
14390
14473
|
}
|
|
14391
14474
|
async function stop(opts = {}) {
|
|
14392
14475
|
shuttingDown = true;
|
|
@@ -14705,18 +14788,30 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14705
14788
|
connection.send(sanitized.envelope);
|
|
14706
14789
|
}
|
|
14707
14790
|
async function publishReliableAgentEgress(input) {
|
|
14708
|
-
if (config.agentEgress === void 0 || agentHomeManager === void 0) {
|
|
14791
|
+
if (config.agentEgress === void 0 || agentHomeManager === void 0 || agentSessionHandoffs === void 0) {
|
|
14709
14792
|
throw new Error("Agent reliable egress is not configured");
|
|
14710
14793
|
}
|
|
14794
|
+
if (tenantRebinding) {
|
|
14795
|
+
throw new Error("tenant enrollment is being re-paired; reliable egress is blocked until restart");
|
|
14796
|
+
}
|
|
14711
14797
|
const binding = await agentHomeManager.acquire(input.agentRef);
|
|
14712
14798
|
try {
|
|
14713
14799
|
await agentHomeManager.initialize(binding);
|
|
14800
|
+
const handoff = await agentSessionHandoffs.requireMatch({
|
|
14801
|
+
agentRef: binding.resolution.agentRef,
|
|
14802
|
+
sessionRef: input.sessionRef,
|
|
14803
|
+
runtimeId: input.runtimeId,
|
|
14804
|
+
cwd: binding.resolution.canonicalHome
|
|
14805
|
+
});
|
|
14806
|
+
if (handoff.taskId !== input.taskId) {
|
|
14807
|
+
throw new Error("Agent reliable egress taskId does not match the durable session handoff");
|
|
14808
|
+
}
|
|
14714
14809
|
const appended = await agentEgress.appendReliable({
|
|
14715
14810
|
homeDir: binding.resolution.canonicalHome,
|
|
14716
14811
|
agentRef: binding.resolution.agentRef,
|
|
14717
14812
|
sessionRef: input.sessionRef,
|
|
14718
14813
|
payload: input.payload,
|
|
14719
|
-
|
|
14814
|
+
taskId: input.taskId,
|
|
14720
14815
|
...input.eventId === void 0 ? {} : { eventId: input.eventId }
|
|
14721
14816
|
});
|
|
14722
14817
|
if (appended.ok) dispatchReliableRecord(appended.record);
|
|
@@ -15381,7 +15476,7 @@ function createServiceLifecycle(def, opts = {}) {
|
|
|
15381
15476
|
|
|
15382
15477
|
// src/bin/official-release.ts
|
|
15383
15478
|
var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
|
|
15384
|
-
version: "0.
|
|
15479
|
+
version: "0.8.0-beta.0"
|
|
15385
15480
|
});
|
|
15386
15481
|
|
|
15387
15482
|
// src/bin/config.ts
|