@synadia-ai/agent-fabric 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { RequestRejectedError, SenderGate } from '@synadia-ai/agent-service';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
- import { SILENT_LOGGER, NatsAgentError, SenderSignatureRequiredError, readContextFile } from '@synadia-ai/agents';
3
+ import { SILENT_LOGGER, assertValidSenderName, resolveNatsConnectionBundle, withAgentReconnectDefaults, Agents, NatsAgentError, SenderSignatureRequiredError, readContextFile } from '@synadia-ai/agents';
4
+ import { connect } from '@nats-io/transport-node';
4
5
  import { execFile } from 'node:child_process';
5
6
  import { createHash, randomUUID } from 'node:crypto';
6
7
  import { mkdir, realpath } from 'node:fs/promises';
7
8
  import { tmpdir, platform, arch, homedir } from 'node:os';
8
9
  import { join, resolve } from 'node:path';
10
+ import { mkdirSync, readFileSync, writeFileSync, renameSync, openSync, constants, fstatSync, readSync, closeSync, lstatSync, realpathSync, fsyncSync } from 'node:fs';
9
11
  import { headers } from '@nats-io/nats-core';
10
- import { mkdirSync, readFileSync, writeFileSync, renameSync, lstatSync, realpathSync, openSync, constants, fsyncSync, closeSync } from 'node:fs';
11
12
 
12
13
  // src/tracing.ts
13
14
  var EDGE_RECORD_VERSION = 1;
@@ -211,6 +212,229 @@ function lineageField(extras, wireName) {
211
212
  }
212
213
  return value;
213
214
  }
215
+ var CALLER_INBOX_PREFIX = "_INBOX.agents";
216
+ var CANNOT_ANSWER = "The caller is a command line and cannot answer; continue with your best judgement.";
217
+ var DEFAULT_DISCOVER_TIMEOUT_MS = 1e3;
218
+ var NATS_URL_VAR = "NATS_URL";
219
+ var NATS_CREDS_VAR = "NATS_CREDS";
220
+ var FabricCallerError = class extends Error {
221
+ constructor(reason, message) {
222
+ super(message);
223
+ this.reason = reason;
224
+ this.name = "FabricCallerError";
225
+ }
226
+ reason;
227
+ };
228
+ var CallerStream = class {
229
+ #inner;
230
+ #sink;
231
+ constructor(inner, sink) {
232
+ this.#inner = inner;
233
+ this.#sink = sink;
234
+ }
235
+ /** The subject the prompt is published to. */
236
+ get subject() {
237
+ return this.#inner.subject;
238
+ }
239
+ /** Stop the stream, as `PromptStream.cancel()`. */
240
+ cancel() {
241
+ this.#sink.reject(notSent("it was cancelled"));
242
+ this.#inner.cancel();
243
+ }
244
+ async *[Symbol.asyncIterator]() {
245
+ try {
246
+ for await (const msg of this.#inner) yield msg;
247
+ } catch (err) {
248
+ this.#sink.reject(err);
249
+ throw err;
250
+ } finally {
251
+ this.#sink.reject(notSent("its stream ended first"));
252
+ }
253
+ }
254
+ };
255
+ function notSent(why) {
256
+ return new FabricCallerError("not-sent", `the prompt never went out, so it has no root: ${why}`);
257
+ }
258
+ var ROOT_SINK_KEY = "agentFabricCallerRootSink";
259
+ function rootDeferred() {
260
+ let settled = false;
261
+ let resolve3;
262
+ let reject2;
263
+ const rootId = new Promise((res, rej) => {
264
+ resolve3 = res;
265
+ reject2 = rej;
266
+ });
267
+ rootId.catch(() => {
268
+ });
269
+ const once = (fn) => (v) => {
270
+ if (settled) return;
271
+ settled = true;
272
+ fn(v);
273
+ };
274
+ return { rootId, sink: { resolve: once(resolve3), reject: once(reject2) } };
275
+ }
276
+ async function fabricCaller(options) {
277
+ const creds = options.creds;
278
+ if (typeof creds !== "string" || creds.length === 0) {
279
+ throw new FabricCallerError(
280
+ "config",
281
+ `no creds file: pass creds, or set ${NATS_CREDS_VAR} to the creds file the console gave you`
282
+ );
283
+ }
284
+ const url = options.url ?? process.env[NATS_URL_VAR];
285
+ if (url === void 0 || url.length === 0) {
286
+ throw new FabricCallerError(
287
+ "config",
288
+ `no server URL: pass url, or set ${NATS_URL_VAR} to the URL the console showed next to your creds file`
289
+ );
290
+ }
291
+ const tracing = fabricTracing(
292
+ options.edgeSubject !== void 0 ? { edgeSubject: options.edgeSubject } : {}
293
+ );
294
+ if (options.name !== void 0) {
295
+ try {
296
+ assertValidSenderName(options.name);
297
+ } catch (err) {
298
+ throw new FabricCallerError("config", `name: ${errorMessage(err)}`);
299
+ }
300
+ }
301
+ let bundle;
302
+ try {
303
+ bundle = await resolveNatsConnectionBundle({ url, creds }, { identity: "signed" });
304
+ } catch (err) {
305
+ throw new FabricCallerError("config", `creds file ${creds}: ${errorMessage(err)}`);
306
+ }
307
+ let nc;
308
+ try {
309
+ nc = await connect(
310
+ withAgentReconnectDefaults({
311
+ ...bundle.connectionOptions,
312
+ inboxPrefix: CALLER_INBOX_PREFIX,
313
+ ...options.name !== void 0 ? { name: options.name } : {},
314
+ // A caller is short-lived: a wrong URL fails now, naming the URL,
315
+ // instead of retrying forever before the first connection.
316
+ waitOnFirstConnect: false
317
+ })
318
+ );
319
+ } catch (err) {
320
+ bundle.wipe();
321
+ throw new FabricCallerError("connect", `could not connect to ${url}: ${errorMessage(err)}`);
322
+ }
323
+ let agents;
324
+ try {
325
+ agents = new Agents({
326
+ nc,
327
+ identity: {
328
+ signer: bundle.signer,
329
+ ...options.name !== void 0 ? { name: options.name } : {}
330
+ },
331
+ interceptors: [rootCapturing(tracing.promptInterceptor)]
332
+ });
333
+ } catch (err) {
334
+ await nc.close();
335
+ bundle.wipe();
336
+ throw err;
337
+ }
338
+ try {
339
+ await agents.selfId();
340
+ } catch (err) {
341
+ await agents.close();
342
+ await nc.close();
343
+ bundle.wipe();
344
+ throw new FabricCallerError(
345
+ "identity",
346
+ `the server at ${url} would not tell this creds file's identity, which signing needs: ${errorMessage(err)}`
347
+ );
348
+ }
349
+ const list = (opts = {}) => agents.discover({ timeoutMs: opts.timeoutMs ?? DEFAULT_DISCOVER_TIMEOUT_MS });
350
+ const find = async (who, opts = {}) => pickAgent(await list(opts), who);
351
+ const prompt = async (who, text2, promptOptions = {}) => {
352
+ const target = typeof who === "string" ? await find(who) : who;
353
+ const { sink, rootId } = rootDeferred();
354
+ let inner;
355
+ try {
356
+ inner = await target.prompt(text2, {
357
+ ...promptOptions,
358
+ context: { ...promptOptions.context ?? {}, [ROOT_SINK_KEY]: sink }
359
+ });
360
+ } catch (err) {
361
+ sink.reject(err);
362
+ throw err;
363
+ }
364
+ return { stream: new CallerStream(inner, sink), rootId };
365
+ };
366
+ const ask = async (who, text2, askOptions = {}) => {
367
+ const { onQuery, ...promptOptions } = askOptions;
368
+ const { stream, rootId } = await prompt(who, text2, promptOptions);
369
+ let reply = "";
370
+ for await (const msg of stream) {
371
+ if (msg.type === "response") reply += msg.text;
372
+ else if (msg.type === "query") {
373
+ const answer = onQuery !== void 0 ? await onQuery(msg) : "";
374
+ await msg.reply(answer.trim() === "" ? CANNOT_ANSWER : answer);
375
+ }
376
+ }
377
+ return { text: reply, rootId: await rootId };
378
+ };
379
+ let closed = false;
380
+ const close = async () => {
381
+ if (closed) return;
382
+ closed = true;
383
+ try {
384
+ await agents.close();
385
+ await nc.close();
386
+ } finally {
387
+ bundle.wipe();
388
+ }
389
+ };
390
+ return { agents, url, list, find, prompt, ask, close };
391
+ }
392
+ function fabricCallerFromEnv(options = {}) {
393
+ return fabricCaller({ ...options, creds: process.env[NATS_CREDS_VAR] ?? "" });
394
+ }
395
+ function pickAgent(found, who) {
396
+ const byAddress = found.find((a) => a.promptSubject === who);
397
+ if (byAddress !== void 0) return byAddress;
398
+ const ofKind = distinctAddresses(found.filter((a) => a.agent === who));
399
+ if (ofKind.length === 1) return ofKind[0];
400
+ if (ofKind.length > 1) {
401
+ throw new FabricCallerError(
402
+ "ambiguous",
403
+ `${ofKind.length} agents of kind "${who}": ${ofKind.map((a) => a.promptSubject).join(", ")}; name one by its address`
404
+ );
405
+ }
406
+ const there = distinctAddresses(found).map((a) => a.promptSubject).join(", ");
407
+ throw new FabricCallerError("not-found", `no agent "${who}"; there: ${there || "nobody"}`);
408
+ }
409
+ function distinctAddresses(agents) {
410
+ const seen = /* @__PURE__ */ new Map();
411
+ for (const a of agents) if (!seen.has(a.promptSubject)) seen.set(a.promptSubject, a);
412
+ return [...seen.values()];
413
+ }
414
+ function rootCapturing(inner) {
415
+ return {
416
+ async beforePrompt(ctx) {
417
+ const sink = ctx.context[ROOT_SINK_KEY];
418
+ let extras;
419
+ try {
420
+ extras = await inner.beforePrompt(ctx);
421
+ } catch (err) {
422
+ sink?.reject(err);
423
+ throw err;
424
+ }
425
+ const root = extras?.fields?.[ROOT_ID_FIELD];
426
+ if (typeof root === "string") sink?.resolve(root);
427
+ else sink?.reject(notSent("tracing minted no root"));
428
+ return extras;
429
+ },
430
+ async beforePublish(ctx, extras) {
431
+ await inner.beforePublish?.(ctx, extras);
432
+ }
433
+ };
434
+ }
435
+ function errorMessage(err) {
436
+ return err instanceof Error ? err.message : String(err);
437
+ }
214
438
  var OUTSIDE_RUN_ID = /[^A-Za-z0-9_-]/gu;
215
439
  function durableRunId(toolCallId) {
216
440
  if (!validToolCallId(toolCallId)) {
@@ -894,6 +1118,7 @@ var MAX_READ_BYTES = CHUNK_SIZE;
894
1118
  var MAX_PREPARE_OBJECTS = 64;
895
1119
  var MAX_VOLUME_OBJECTS = 65536;
896
1120
  var MAX_VOLUME_BYTES = 8 * 2 ** 30;
1121
+ var MAX_ADMISSION_SLOT = 4294967295;
897
1122
  var WRITER_SLOTS = 8;
898
1123
  var RETENTION_SLOTS = 8;
899
1124
  var GRANT_SLOTS = 16;
@@ -908,6 +1133,7 @@ var CODES = [
908
1133
  "exists",
909
1134
  "conflict",
910
1135
  "fenced",
1136
+ "generation_retired",
911
1137
  "intent_changed",
912
1138
  "capacity",
913
1139
  "summary_limit",
@@ -1080,16 +1306,11 @@ function validateGrantHandle(value) {
1080
1306
  uint(v.generation, void 0, 1);
1081
1307
  }
1082
1308
  function validateCommand(value) {
1083
- const v = record(value, [
1084
- "creation_id",
1085
- "writer",
1086
- "number",
1087
- "operation_id",
1088
- "base",
1089
- "root",
1090
- "trace",
1091
- "retention"
1092
- ]);
1309
+ const v = record(
1310
+ value,
1311
+ ["creation_id", "writer", "number", "operation_id", "base", "root", "trace", "retention"],
1312
+ ["remove_path"]
1313
+ );
1093
1314
  validateTarget({ volume: "v", creation_id: v.creation_id });
1094
1315
  validateWriter(v.writer);
1095
1316
  uint(v.number, void 0, 1);
@@ -1105,6 +1326,10 @@ function validateCommand(value) {
1105
1326
  r.enabled || r.slot === 0 && r.generation === 0 && r.until === 0,
1106
1327
  "disabled retention must be zero"
1107
1328
  );
1329
+ if (v.remove_path !== void 0) {
1330
+ validatePath(v.remove_path);
1331
+ check(v.remove_path !== "/" && !r.enabled, "removal cannot retain or remove the root");
1332
+ }
1108
1333
  }
1109
1334
  function validateReceipt(value) {
1110
1335
  const v = record(value, ["volume", "principal", "command", "head", "retention"]);
@@ -1134,19 +1359,24 @@ function sameValue(a, b) {
1134
1359
  return Object.keys(left).length === Object.keys(right).length && Object.keys(left).every((key) => Object.hasOwn(right, key) && sameValue(left[key], right[key]));
1135
1360
  }
1136
1361
  function validateVolumeView(value) {
1137
- const v = record(value, [
1138
- "target",
1139
- "owner",
1140
- "head",
1141
- "revision",
1142
- "closed",
1143
- "epoch",
1144
- "objects",
1145
- "bytes",
1146
- "writers",
1147
- "retentions",
1148
- "grants"
1149
- ]);
1362
+ const v = record(
1363
+ value,
1364
+ [
1365
+ "target",
1366
+ "owner",
1367
+ "head",
1368
+ "revision",
1369
+ "closed",
1370
+ "epoch",
1371
+ "objects",
1372
+ "bytes",
1373
+ "writers",
1374
+ "retentions",
1375
+ "grants"
1376
+ ],
1377
+ ["lifetime"]
1378
+ );
1379
+ if (v.lifetime !== void 0) validateLifetimeView(v.lifetime);
1150
1380
  validateTarget(v.target);
1151
1381
  validatePrincipal(v.owner);
1152
1382
  validateHead(v.head);
@@ -1168,12 +1398,12 @@ function validateVolumeView(value) {
1168
1398
  if (slot.principal !== void 0) {
1169
1399
  validatePrincipal(slot.principal);
1170
1400
  check(slot.principal.account === v.owner.account, "writer belongs to another account");
1171
- } else check(!slot.write && slot.number === 0, "writer identity absent");
1401
+ } else check(!slot.write, "writer identity absent");
1172
1402
  if (slot.number === 0) check(slot.receipt === void 0, "unused writer has a receipt");
1173
1403
  else {
1174
1404
  validateReceipt(slot.receipt);
1175
1405
  check(
1176
- slot.write && sameValue(slot.receipt.principal, slot.principal) && slot.receipt.volume === v.target.volume && slot.receipt.command.creation_id === v.target.creation_id && slot.receipt.command.writer.slot === index && slot.receipt.command.writer.generation === slot.generation && slot.receipt.command.number === slot.number && slot.receipt.head.generation <= v.head.generation,
1406
+ (slot.principal === void 0 && !slot.write || slot.write && sameValue(slot.receipt.principal, slot.principal)) && slot.receipt.principal.account === v.owner.account && slot.receipt.volume === v.target.volume && slot.receipt.command.creation_id === v.target.creation_id && slot.receipt.command.writer.slot === index && slot.receipt.command.writer.generation === slot.generation && slot.receipt.command.number === slot.number && slot.receipt.head.generation <= v.head.generation,
1177
1407
  "writer receipt differs from slot"
1178
1408
  );
1179
1409
  }
@@ -1235,7 +1465,8 @@ function validateInfo(value) {
1235
1465
  check(Array.isArray(v.scopes) && v.scopes.length <= 16, "scope count exceeds bound");
1236
1466
  const seen = /* @__PURE__ */ new Set();
1237
1467
  for (const raw of v.scopes) {
1238
- const scope = record(raw, ["volume", "objects", "bytes"]);
1468
+ const scope = record(raw, ["volume", "objects", "bytes"], ["admission"]);
1469
+ if (scope.admission !== void 0) validateAdmissionBinding(scope.admission);
1239
1470
  validateVolume(scope.volume);
1240
1471
  uint(scope.objects, MAX_VOLUME_OBJECTS, 1);
1241
1472
  uint(scope.bytes, MAX_VOLUME_BYTES, 4);
@@ -1248,6 +1479,130 @@ function validateContentObject(value) {
1248
1479
  validateContentID(v.id);
1249
1480
  check(v.kind === "chnk" || v.kind === "fobj" || v.kind === "tree", "unsupported object kind");
1250
1481
  }
1482
+ function validateAdmissionBinding(value) {
1483
+ const v = record(value, ["slot", "generation", "operation_id"]);
1484
+ uint(v.slot, MAX_ADMISSION_SLOT);
1485
+ uint(v.generation, void 0, 1);
1486
+ validateAction({ operation_id: v.operation_id, trace: {} });
1487
+ }
1488
+ function validateLifetimeView(value) {
1489
+ const v = record(value, [
1490
+ "admission",
1491
+ "phase",
1492
+ "expires_at",
1493
+ "grace_until",
1494
+ "max_lease_seconds",
1495
+ "grace_seconds",
1496
+ "recovery_reads",
1497
+ "reserve_objects",
1498
+ "reserve_bytes"
1499
+ ]);
1500
+ validateAdmissionBinding(v.admission);
1501
+ for (const key of ["expires_at", "grace_until", "max_lease_seconds", "grace_seconds"])
1502
+ uint(v[key]);
1503
+ bool(v.recovery_reads);
1504
+ uint(v.reserve_objects, 128);
1505
+ uint(v.reserve_bytes, 8 * 2 ** 20);
1506
+ check(v.reserve_objects === 0 === (v.reserve_bytes === 0), "incomplete metadata reserve");
1507
+ check(
1508
+ v.phase === "active" || v.phase === "shrinking" || v.phase === "expiring",
1509
+ "invalid lifetime phase"
1510
+ );
1511
+ check(
1512
+ v.phase === "expiring" ? v.expires_at > 0 && (v.grace_until === 0 || v.grace_until >= v.expires_at) : v.grace_until === 0,
1513
+ "invalid lifetime grace"
1514
+ );
1515
+ check(
1516
+ v.expires_at === 0 ? v.grace_seconds === 0 : v.max_lease_seconds > 0 && v.grace_seconds > 0,
1517
+ "invalid lease policy"
1518
+ );
1519
+ }
1520
+ function validateRenewRequest(value) {
1521
+ const v = record(value, ["target", "until"]);
1522
+ validateTarget(v.target);
1523
+ uint(v.until, void 0, 1);
1524
+ }
1525
+ function validateRemovalRequest(value) {
1526
+ const v = record(value, ["target", "command"]);
1527
+ validateTarget(v.target);
1528
+ validateCommand(v.command);
1529
+ check(
1530
+ v.command.remove_path && v.command.creation_id === v.target.creation_id,
1531
+ "invalid removal command"
1532
+ );
1533
+ }
1534
+ function validateRemovalPlan(value) {
1535
+ const v = record(value, [
1536
+ "target",
1537
+ "slot",
1538
+ "generation",
1539
+ "sequence",
1540
+ "command",
1541
+ "added_objects",
1542
+ "added_bytes"
1543
+ ]);
1544
+ validateRemovalRequest({ target: v.target, command: v.command });
1545
+ uint(v.slot, MAX_ADMISSION_SLOT);
1546
+ uint(v.generation, void 0, 1);
1547
+ uint(v.sequence, void 0, 1);
1548
+ uint(v.added_objects, 128);
1549
+ uint(v.added_bytes, 8 * 2 ** 20);
1550
+ }
1551
+ function validateVolumeReleaseRequest(value) {
1552
+ const v = record(value, ["target", "operation_id", "end_promises"]);
1553
+ validateTarget(v.target);
1554
+ validateAction({ operation_id: v.operation_id, trace: {} });
1555
+ bool(v.end_promises);
1556
+ }
1557
+ function validateVolumeReleasePlan(value) {
1558
+ const v = record(value, [
1559
+ "request",
1560
+ "incarnation",
1561
+ "slot",
1562
+ "generation",
1563
+ "sequence",
1564
+ "head",
1565
+ "effects"
1566
+ ]);
1567
+ validateVolumeReleaseRequest(v.request);
1568
+ text(v.incarnation, 64, 64);
1569
+ check(/^[0-9a-f]{64}$/.test(v.incarnation), "invalid release incarnation");
1570
+ uint(v.slot, MAX_ADMISSION_SLOT);
1571
+ uint(v.generation, void 0, 1);
1572
+ uint(v.sequence, void 0, 1);
1573
+ validateHead(v.head);
1574
+ const effects = record(v.effects, [
1575
+ "objects",
1576
+ "bytes",
1577
+ "retentions",
1578
+ "read_grants",
1579
+ "access_slots",
1580
+ "references"
1581
+ ]);
1582
+ uint(effects.objects, MAX_VOLUME_OBJECTS);
1583
+ uint(effects.bytes, MAX_VOLUME_BYTES);
1584
+ uint(effects.retentions, RETENTION_SLOTS);
1585
+ uint(effects.read_grants, GRANT_SLOTS);
1586
+ uint(effects.access_slots, WRITER_SLOTS);
1587
+ uint(effects.references, RETENTION_SLOTS);
1588
+ }
1589
+ function validateVolumeReleaseResult(value) {
1590
+ const v = record(value, [
1591
+ "target",
1592
+ "slot",
1593
+ "generation",
1594
+ "operation_id",
1595
+ "disposed",
1596
+ "capacity_returned"
1597
+ ]);
1598
+ validateTarget(v.target);
1599
+ uint(v.slot, MAX_ADMISSION_SLOT);
1600
+ uint(v.generation, void 0, 1);
1601
+ validateAction({ operation_id: v.operation_id, trace: {} });
1602
+ bool(v.disposed);
1603
+ bool(v.capacity_returned);
1604
+ check(!v.capacity_returned || v.disposed, "capacity returned before disposal");
1605
+ }
1251
1606
  function validateObjectSpec(value) {
1252
1607
  const v = record(value, ["object", "length"]);
1253
1608
  validateContentObject(v.object);
@@ -2002,6 +2357,45 @@ function validateReferenceRetainResult(value) {
2002
2357
  previous = ref.path;
2003
2358
  }
2004
2359
  }
2360
+ var MAX_BOOTSTRAP_BYTES = 4096;
2361
+ function decodeScratchPadBootstrap(data) {
2362
+ const value = record(parseStrictJSON(data, MAX_BOOTSTRAP_BYTES), [
2363
+ "version",
2364
+ "principal",
2365
+ "volume",
2366
+ "admission",
2367
+ "creation"
2368
+ ]);
2369
+ check(value.version === 1, "unsupported ScratchPad bootstrap version");
2370
+ validatePrincipal(value.principal);
2371
+ validateVolume(value.volume);
2372
+ validateAdmissionBinding(value.admission);
2373
+ validateAction(value.creation);
2374
+ return value;
2375
+ }
2376
+ function readScratchPadBootstrapFile(path) {
2377
+ check(typeof path === "string" && path.length > 0, "bootstrap file path is required");
2378
+ let fd;
2379
+ try {
2380
+ fd = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0));
2381
+ const stat = fstatSync(fd);
2382
+ check(stat.isFile(), "bootstrap must be a regular file");
2383
+ check(stat.size <= MAX_BOOTSTRAP_BYTES, "bootstrap exceeds its byte bound");
2384
+ const bytes = Buffer.alloc(MAX_BOOTSTRAP_BYTES + 1);
2385
+ let count2 = 0;
2386
+ while (count2 < bytes.length) {
2387
+ const read = readSync(fd, bytes, count2, bytes.length - count2, count2);
2388
+ if (read === 0) break;
2389
+ count2 += read;
2390
+ }
2391
+ return decodeScratchPadBootstrap(bytes.subarray(0, count2));
2392
+ } catch (cause) {
2393
+ if (cause instanceof NativeScratchPadError) throw cause;
2394
+ throw invalid("cannot read ScratchPad bootstrap file", cause);
2395
+ } finally {
2396
+ if (fd !== void 0) closeSync(fd);
2397
+ }
2398
+ }
2005
2399
 
2006
2400
  // src/scratchpad-cli.ts
2007
2401
  var DEFAULT_SP_BIN = "sp";
@@ -2160,6 +2554,15 @@ function cleanupCannotHelp(view, at = now()) {
2160
2554
  return room ? void 0 : "every retention slot holds a live pin";
2161
2555
  }
2162
2556
  function spCliScratchPad(options) {
2557
+ const bootstrap = options.bootstrapFile === void 0 ? void 0 : readScratchPadBootstrapFile(options.bootstrapFile);
2558
+ const selectedVolume = options.volume ?? bootstrap?.volume;
2559
+ if (!selectedVolume || !VOLUME_RE.test(selectedVolume))
2560
+ throw new Error("ScratchPad: a valid volume or bootstrap file is required");
2561
+ const volumeName = selectedVolume;
2562
+ if (bootstrap && bootstrap.volume !== volumeName)
2563
+ throw new Error("ScratchPad: bootstrap differs from the configured volume");
2564
+ if (bootstrap && !options.dir)
2565
+ throw new Error("ScratchPad: bootstrap import requires a persistent dir");
2163
2566
  if (!("creds" in options.nats) || options.nats.creds === void 0) {
2164
2567
  throw new Error(
2165
2568
  "ScratchPad: the sp CLI needs NATS_URL and NATS_CREDS; a NATS context or an nkey seed alone is not supported"
@@ -2167,7 +2570,7 @@ function spCliScratchPad(options) {
2167
2570
  }
2168
2571
  const connection = ["--url", options.nats.url, "--creds", options.nats.creds];
2169
2572
  const bin = options.bin ?? DEFAULT_SP_BIN;
2170
- const configuredDir = options.dir ?? join(tmpdir(), `scratchpad-${options.volume}`);
2573
+ const configuredDir = options.dir ?? join(tmpdir(), `scratchpad-${volumeName}`);
2171
2574
  const grantTtlS = Math.min(options.grantTtlS ?? DEFAULT_GRANT_TTL_S, DEFAULT_GRANT_TTL_S);
2172
2575
  let dir;
2173
2576
  let binding;
@@ -2216,8 +2619,18 @@ function spCliScratchPad(options) {
2216
2619
  async function sp(command, args, opts = {}) {
2217
2620
  const trace = opts.trace ? ["--thread-id", opts.trace.threadId, "--root-id", opts.trace.rootId] : [];
2218
2621
  if (opts.trace && opts.toolCallId !== void 0) trace.push("--tool-call-id", opts.toolCallId);
2219
- const volume = ["read", "resolve"].includes(command) ? [] : ["--volume", options.volume];
2220
- const argv = [command, ...connection, "--dir", await workspace(), ...volume, ...args, ...trace];
2622
+ const volume = ["read", "resolve"].includes(command) ? [] : ["--volume", volumeName];
2623
+ const bootstrapArgs = command === "init" && options.bootstrapFile ? ["--bootstrap-file", options.bootstrapFile] : [];
2624
+ const argv = [
2625
+ command,
2626
+ ...connection,
2627
+ "--dir",
2628
+ await workspace(),
2629
+ ...volume,
2630
+ ...bootstrapArgs,
2631
+ ...args,
2632
+ ...trace
2633
+ ];
2221
2634
  const maxStdout = opts.maxStdout ?? MAX_STDOUT;
2222
2635
  const { code, stdout, stderr } = await new Promise((resolve3) => {
2223
2636
  const child = execFile(
@@ -2278,7 +2691,7 @@ function spCliScratchPad(options) {
2278
2691
  false
2279
2692
  );
2280
2693
  }
2281
- if (r.target.volume !== options.volume) {
2694
+ if (r.target.volume !== volumeName) {
2282
2695
  throw new ScratchPadError(
2283
2696
  `sp ${command}: the workspace is bound to another volume`,
2284
2697
  "invalid",
@@ -2573,6 +2986,7 @@ function spCliScratchPad(options) {
2573
2986
  return pad;
2574
2987
  }
2575
2988
  var SCRATCHPAD_VOLUME_VAR = "SCRATCHPAD_VOLUME";
2989
+ var SCRATCHPAD_BOOTSTRAP_FILE_VAR = "SCRATCHPAD_BOOTSTRAP_FILE";
2576
2990
  var SCRATCHPAD_BIN_VAR = "SCRATCHPAD_BIN";
2577
2991
  var SCRATCHPAD_DIR_VAR = "SCRATCHPAD_DIR";
2578
2992
  var SCRATCHPAD_GRANT_TTL_S_VAR = "SCRATCHPAD_GRANT_TTL_S";
@@ -2582,7 +2996,8 @@ function scratchPadFromEnv(env = process.env, options = {}) {
2582
2996
  return value === void 0 || value === "" ? void 0 : value;
2583
2997
  };
2584
2998
  const volume = optional(SCRATCHPAD_VOLUME_VAR);
2585
- if (volume === void 0) return void 0;
2999
+ const bootstrapFile = optional(SCRATCHPAD_BOOTSTRAP_FILE_VAR);
3000
+ if (volume === void 0 && bootstrapFile === void 0) return void 0;
2586
3001
  const ttl = optional(SCRATCHPAD_GRANT_TTL_S_VAR);
2587
3002
  let grantTtlS;
2588
3003
  if (ttl !== void 0) {
@@ -2598,7 +3013,8 @@ function scratchPadFromEnv(env = process.env, options = {}) {
2598
3013
  const bin = optional(SCRATCHPAD_BIN_VAR);
2599
3014
  const dir = optional(SCRATCHPAD_DIR_VAR);
2600
3015
  return spCliScratchPad({
2601
- volume,
3016
+ ...volume === void 0 ? {} : { volume },
3017
+ ...bootstrapFile === void 0 ? {} : { bootstrapFile },
2602
3018
  nats,
2603
3019
  ...bin !== void 0 ? { bin } : {},
2604
3020
  ...dir !== void 0 ? { dir } : {},
@@ -2693,6 +3109,12 @@ function loopGuard() {
2693
3109
  waiting
2694
3110
  };
2695
3111
  }
3112
+ function normalizeCommand(command) {
3113
+ return {
3114
+ ...command,
3115
+ trace: Object.fromEntries(Object.entries(command.trace).filter(([, value]) => value !== ""))
3116
+ };
3117
+ }
2696
3118
  function pathRequest(v) {
2697
3119
  validateTarget(v.target);
2698
3120
  validateContentID(v.root);
@@ -2706,11 +3128,27 @@ function validateRequest(verb, value) {
2706
3128
  validateEmpty(value);
2707
3129
  return;
2708
3130
  case "volume.create": {
2709
- const v = record(value, ["volume", "action"]);
3131
+ const v = record(value, ["volume", "action"], ["admission"]);
2710
3132
  validateVolume(v.volume);
2711
3133
  validateAction(v.action);
3134
+ if (v.admission !== void 0) validateAdmissionBinding(v.admission);
2712
3135
  return;
2713
3136
  }
3137
+ case "volume.renew":
3138
+ validateRenewRequest(value);
3139
+ return;
3140
+ case "volume.remove.plan":
3141
+ validateRemovalRequest(value);
3142
+ return;
3143
+ case "volume.remove.apply":
3144
+ validateRemovalPlan(value);
3145
+ return;
3146
+ case "volume.release.plan":
3147
+ validateVolumeReleaseRequest(value);
3148
+ return;
3149
+ case "volume.release.apply":
3150
+ validateVolumeReleasePlan(value);
3151
+ return;
2714
3152
  case "volume.get": {
2715
3153
  const v = record(value, ["target"]);
2716
3154
  validateTarget(v.target);
@@ -2775,6 +3213,7 @@ function validateRequest(verb, value) {
2775
3213
  const v = record(value, ["target", "command"]);
2776
3214
  validateTarget(v.target);
2777
3215
  validateCommand(v.command);
3216
+ check(v.command.remove_path === void 0, "removal requires volume.remove.apply");
2778
3217
  check(
2779
3218
  v.target.creation_id === v.command.creation_id,
2780
3219
  "command names another creation identity"
@@ -3029,6 +3468,88 @@ var ScratchPadTransport = class {
3029
3468
  options
3030
3469
  );
3031
3470
  this.matching(result.target.volume === expected.volume, true, "reply names another volume");
3471
+ this.matching(
3472
+ sameValue(result.lifetime?.admission, expected.admission),
3473
+ true,
3474
+ "creation reply names another admission"
3475
+ );
3476
+ return result;
3477
+ }
3478
+ async renewLease(request, options = {}) {
3479
+ const expected = structuredClone(request);
3480
+ const result = await this.view("volume.renew", expected.target, expected, true, options);
3481
+ this.matching(
3482
+ result.lifetime !== void 0 && result.lifetime.expires_at >= expected.until,
3483
+ true,
3484
+ "renewal reply differs from saved deadline"
3485
+ );
3486
+ return result;
3487
+ }
3488
+ async planRemoval(request, options = {}) {
3489
+ const expected = structuredClone(request);
3490
+ const result = await this.control(
3491
+ "volume.remove.plan",
3492
+ expected,
3493
+ validateRemovalPlan,
3494
+ false,
3495
+ options
3496
+ );
3497
+ this.matching(
3498
+ sameValue(result.target, expected.target) && sameValue(
3499
+ normalizeCommand(result.command),
3500
+ normalizeCommand({ ...expected.command, root: result.command.root })
3501
+ ),
3502
+ false,
3503
+ "removal plan changed requested intent"
3504
+ );
3505
+ return result;
3506
+ }
3507
+ async applyRemoval(plan, options = {}) {
3508
+ const expected = structuredClone(plan);
3509
+ const result = await this.control(
3510
+ "volume.remove.apply",
3511
+ expected,
3512
+ validateReceipt,
3513
+ true,
3514
+ options
3515
+ );
3516
+ this.matching(
3517
+ result.volume === expected.target.volume && sameValue(normalizeCommand(result.command), normalizeCommand(expected.command)),
3518
+ true,
3519
+ "removal receipt changed saved command"
3520
+ );
3521
+ return result;
3522
+ }
3523
+ async planVolumeRelease(request, options = {}) {
3524
+ const expected = structuredClone(request);
3525
+ const result = await this.control(
3526
+ "volume.release.plan",
3527
+ expected,
3528
+ validateVolumeReleasePlan,
3529
+ false,
3530
+ options
3531
+ );
3532
+ this.matching(
3533
+ sameValue(result.request, expected),
3534
+ false,
3535
+ "release plan changed requested disposition"
3536
+ );
3537
+ return result;
3538
+ }
3539
+ async applyVolumeRelease(plan, options = {}) {
3540
+ const expected = structuredClone(plan);
3541
+ const result = await this.control(
3542
+ "volume.release.apply",
3543
+ expected,
3544
+ validateVolumeReleaseResult,
3545
+ true,
3546
+ options
3547
+ );
3548
+ this.matching(
3549
+ sameValue(result.target, expected.request.target) && result.slot === expected.slot && result.generation === expected.generation && result.operation_id === expected.request.operation_id,
3550
+ true,
3551
+ "release reply changed saved lifetime or operation"
3552
+ );
3032
3553
  return result;
3033
3554
  }
3034
3555
  getVolume(target, options = {}) {
@@ -3103,12 +3624,8 @@ var ScratchPadTransport = class {
3103
3624
  true,
3104
3625
  options
3105
3626
  );
3106
- const normalize = (command) => ({
3107
- ...command,
3108
- trace: Object.fromEntries(Object.entries(command.trace).filter(([, value]) => value !== ""))
3109
- });
3110
3627
  this.matching(
3111
- result.volume === expected.target.volume && sameValue(normalize(result.command), normalize(expected.command)),
3628
+ result.volume === expected.target.volume && sameValue(normalizeCommand(result.command), normalizeCommand(expected.command)),
3112
3629
  true,
3113
3630
  "receipt names another command"
3114
3631
  );
@@ -3620,13 +4137,48 @@ function validateState(value) {
3620
4137
  const s = record(
3621
4138
  value,
3622
4139
  ["version", "principal", "volume", "creator", "creation", "next", "grants"],
3623
- ["target", "writer", "pending", "last"]
4140
+ [
4141
+ "target",
4142
+ "writer",
4143
+ "pending",
4144
+ "last",
4145
+ "admission",
4146
+ "removal",
4147
+ "disposal",
4148
+ "renewal",
4149
+ "disposal_plan",
4150
+ "release_acceptance_unknown"
4151
+ ]
3624
4152
  );
3625
4153
  check(s.version === 1, "unsupported workspace version");
3626
4154
  validatePrincipal(s.principal);
3627
4155
  validateVolume(s.volume);
3628
4156
  text(s.creator, 512, 1);
3629
4157
  validateAction(s.creation);
4158
+ if (s.admission !== void 0) validateAdmissionBinding(s.admission);
4159
+ if (s.removal !== void 0) {
4160
+ validateReceipt(s.removal);
4161
+ check(
4162
+ s.removal.command.remove_path && s.removal.volume === s.volume && sameValue(s.removal.principal, s.principal) && s.removal.command.creation_id === s.target?.creation_id,
4163
+ "saved removal differs from workspace"
4164
+ );
4165
+ }
4166
+ if (s.disposal !== void 0) {
4167
+ validateVolumeReleaseResult(s.disposal);
4168
+ check(
4169
+ s.disposal.capacity_returned && !s.pending && sameValue(s.disposal.target, s.target) && s.disposal.slot === s.admission?.slot && s.disposal.generation === s.admission?.generation,
4170
+ "unfinished or foreign disposal"
4171
+ );
4172
+ }
4173
+ if (s.disposal_plan !== void 0) {
4174
+ validateVolumeReleasePlan(s.disposal_plan);
4175
+ check(
4176
+ s.disposal && sameValue(s.disposal_plan.request.target, s.disposal.target) && s.disposal_plan.slot === s.disposal.slot && s.disposal_plan.generation === s.disposal.generation && s.disposal_plan.request.operation_id === s.disposal.operation_id,
4177
+ "completed release plan differs from disposal"
4178
+ );
4179
+ }
4180
+ if (s.release_acceptance_unknown !== void 0)
4181
+ check(s.release_acceptance_unknown === true && s.disposal, "invalid release acceptance marker");
3630
4182
  uint(s.next, void 0, 1);
3631
4183
  if (s.target !== void 0) {
3632
4184
  validateTarget(s.target);
@@ -3636,6 +4188,13 @@ function validateState(value) {
3636
4188
  validateWriter(s.writer);
3637
4189
  check(s.writer.slot === 0 && s.target, "workspace writer binding is invalid");
3638
4190
  }
4191
+ if (s.renewal !== void 0) {
4192
+ validateRenewRequest(s.renewal);
4193
+ check(
4194
+ s.admission && sameValue(s.renewal.target, s.target) && !s.disposal && !["renew", "volume_release"].includes(s.pending?.kind ?? ""),
4195
+ "saved renewal conflicts with the workspace lifetime"
4196
+ );
4197
+ }
3639
4198
  check(Array.isArray(s.grants) && s.grants.length <= 16, "workspace grant ledger exceeds bound");
3640
4199
  for (const raw of s.grants) {
3641
4200
  const g = record(raw, ["request", "handle", "kind"]);
@@ -3670,6 +4229,25 @@ function validateState(value) {
3670
4229
  check(s.target, "pending operation has no target");
3671
4230
  const p = s.pending;
3672
4231
  switch (p.kind) {
4232
+ case "renew":
4233
+ record(p, ["kind", "request"]);
4234
+ validateRenewRequest(p.request);
4235
+ break;
4236
+ case "remove":
4237
+ record(p, ["kind", "plan"]);
4238
+ validateRemovalPlan(p.plan);
4239
+ check(
4240
+ sameValue(p.plan.target, s.target) && sameValue(p.plan.command.writer, s.writer) && p.plan.command.number === s.next,
4241
+ "saved removal writer differs"
4242
+ );
4243
+ break;
4244
+ case "volume_release":
4245
+ record(p, ["kind", "plan"], ["fence_accepted"]);
4246
+ if (p.fence_accepted !== void 0)
4247
+ check(p.fence_accepted === true, "invalid release fence marker");
4248
+ validateVolumeReleasePlan(p.plan);
4249
+ check(sameValue(p.plan.request.target, s.target), "saved disposal names another target");
4250
+ break;
3673
4251
  case "publish":
3674
4252
  record(p, ["kind", "command", "path", "ttl"], ["expiry_revision", "reference"]);
3675
4253
  if (p.reference !== void 0) {
@@ -3718,6 +4296,11 @@ function validateState(value) {
3718
4296
  }
3719
4297
  if ("request" in p)
3720
4298
  check(sameValue(p.request.target, s.target), "pending operation names another target");
4299
+ if (p.kind === "remove" || p.kind === "volume_release")
4300
+ check(
4301
+ s.admission && p.plan.slot === s.admission.slot && p.plan.generation === s.admission.generation,
4302
+ "saved plan names another lifetime"
4303
+ );
3721
4304
  }
3722
4305
  }
3723
4306
  function validateGrant(value) {
@@ -3757,12 +4340,18 @@ var NativeScratchPad = class extends ScratchPadReader {
3757
4340
  tail = Promise.resolve();
3758
4341
  closing = false;
3759
4342
  closePromise;
3760
- run(work) {
4343
+ run(work, allowRenewal = false, allowDisposed = false) {
3761
4344
  if (this.closing)
3762
4345
  return Promise.reject(
3763
4346
  new NativeScratchPadError("closed", "native client is closed", { local: true })
3764
4347
  );
3765
- const result = this.tail.then(work);
4348
+ const result = this.tail.then(() => {
4349
+ if (this.state.renewal && !allowRenewal)
4350
+ refuse("busy", "recover the saved renewal before another mutation");
4351
+ if (this.state.disposal && !allowDisposed)
4352
+ refuse("fenced", "this workspace lifetime has been released");
4353
+ return work();
4354
+ });
3766
4355
  this.tail = result.then(
3767
4356
  () => {
3768
4357
  },
@@ -3796,7 +4385,8 @@ var NativeScratchPad = class extends ScratchPadReader {
3796
4385
  if (!this.state.target) {
3797
4386
  const view2 = await this.transport.createVolume({
3798
4387
  volume: this.state.volume,
3799
- action: this.state.creation
4388
+ action: this.state.creation,
4389
+ ...this.state.admission ? { admission: this.state.admission } : {}
3800
4390
  });
3801
4391
  if (!sameValue(view2.owner, this.principal))
3802
4392
  throw unknown(void 0, "volume creation names another owner");
@@ -3808,6 +4398,10 @@ var NativeScratchPad = class extends ScratchPadReader {
3808
4398
  sameValue(view.owner, this.principal),
3809
4399
  "workspace principal is no longer the volume owner"
3810
4400
  );
4401
+ check(
4402
+ sameValue(view.lifetime?.admission, this.state.admission),
4403
+ "workspace lifetime differs from volume"
4404
+ );
3811
4405
  return view;
3812
4406
  }
3813
4407
  async initialize() {
@@ -3836,6 +4430,7 @@ var NativeScratchPad = class extends ScratchPadReader {
3836
4430
  const state = { ...this.state, writer: result, next: 1 };
3837
4431
  delete state.pending;
3838
4432
  delete state.last;
4433
+ delete state.removal;
3839
4434
  this.save(state, true, true);
3840
4435
  return result;
3841
4436
  }
@@ -3867,15 +4462,228 @@ var NativeScratchPad = class extends ScratchPadReader {
3867
4462
  });
3868
4463
  }
3869
4464
  describe() {
4465
+ return this.run(
4466
+ async () => {
4467
+ const volume = await this.ensureTarget();
4468
+ const pending = this.state.pending?.kind ?? (this.state.renewal ? "renew" : void 0);
4469
+ return pending ? { volume, pending } : { volume };
4470
+ },
4471
+ true,
4472
+ true
4473
+ );
4474
+ }
4475
+ /** Save an absolute deadline before sending it. Retries never extend it implicitly. */
4476
+ renewLease(until) {
3870
4477
  return this.run(async () => {
3871
- const volume = await this.ensureTarget();
3872
- return this.state.pending ? { volume, pending: this.state.pending.kind } : { volume };
4478
+ const request = { target: this.target(), until };
4479
+ validateRenewRequest(request);
4480
+ if (this.state.disposal) refuse("fenced", "this workspace lifetime has been released");
4481
+ if (this.state.pending?.kind === "volume_release")
4482
+ refuse("busy", "the saved volume release must settle before renewal");
4483
+ if (this.state.pending?.kind === "renew") {
4484
+ if (!sameValue(this.state.pending.request, request))
4485
+ refuse("busy", "recover the saved renewal before changing its deadline");
4486
+ return (await this.lifecycle(this.state.pending, false)).volume;
4487
+ }
4488
+ const saved = this.state.renewal;
4489
+ if (saved && !sameValue(saved, request))
4490
+ refuse("busy", "recover the saved renewal before changing its deadline");
4491
+ if (!saved) this.save({ ...this.state, renewal: request });
4492
+ return this.resumeRenewal(request, !saved);
4493
+ }, true);
4494
+ }
4495
+ async resumeRenewal(request, fresh) {
4496
+ let volume;
4497
+ try {
4498
+ volume = await this.transport.renewLease(request);
4499
+ if (!sameValue(volume.lifetime?.admission, this.state.admission))
4500
+ throw unknown(void 0, "renewal names another lifetime");
4501
+ } catch (error) {
4502
+ if (error instanceof NativeScratchPadError && !error.unknown && (fresh || error.code === "expired")) {
4503
+ const state2 = { ...this.state };
4504
+ delete state2.renewal;
4505
+ this.save(state2, true);
4506
+ throw error;
4507
+ }
4508
+ throw unknown(error, "the saved renewal stays pending; call recover()");
4509
+ }
4510
+ const state = { ...this.state };
4511
+ delete state.renewal;
4512
+ this.save(state, true);
4513
+ return structuredClone(volume);
4514
+ }
4515
+ /** Read-only plan for an initialized writer. The caller chooses the path to remove. */
4516
+ planRemoval(path, options) {
4517
+ return this.run(async () => {
4518
+ if (this.state.pending)
4519
+ refuse("busy", "recover the pending operation before planning removal");
4520
+ check(
4521
+ this.state.writer && this.state.admission,
4522
+ "removal requires an initialized reusable writer"
4523
+ );
4524
+ const view = await this.ensureTarget();
4525
+ return this.transport.planRemoval({
4526
+ target: this.target(),
4527
+ command: {
4528
+ creation_id: this.target().creation_id,
4529
+ writer: this.state.writer,
4530
+ number: this.state.next,
4531
+ operation_id: options.operationId,
4532
+ base: view.head,
4533
+ root: view.head.root,
4534
+ trace: wireTrace(options.trace),
4535
+ remove_path: path,
4536
+ retention: { enabled: false, slot: 0, generation: 0, until: 0 }
4537
+ }
4538
+ });
4539
+ });
4540
+ }
4541
+ applyRemoval(plan) {
4542
+ const saved = structuredClone(plan);
4543
+ return this.run(async () => {
4544
+ validateRemovalPlan(saved);
4545
+ check(
4546
+ sameValue(saved.target, this.target()) && saved.slot === this.state.admission?.slot && saved.generation === this.state.admission.generation,
4547
+ "removal plan names another lifetime"
4548
+ );
4549
+ if (!this.state.pending && this.state.removal && sameValue(this.state.removal.command, saved.command))
4550
+ return structuredClone(this.state.removal);
4551
+ return (await this.beginLifecycle({ kind: "remove", plan: saved })).receipt;
3873
4552
  });
3874
4553
  }
4554
+ /** Lists every promise affected. EndPromises must be explicitly true to authorize release. */
4555
+ planVolumeRelease(options) {
4556
+ return this.run(async () => {
4557
+ if (this.state.pending)
4558
+ refuse("busy", "recover the pending operation before planning release");
4559
+ check(this.state.admission, "volume release requires a reusable lifetime");
4560
+ return this.transport.planVolumeRelease({
4561
+ target: this.target(),
4562
+ operation_id: options.operationId,
4563
+ end_promises: options.endPromises ?? false
4564
+ });
4565
+ });
4566
+ }
4567
+ applyVolumeRelease(plan) {
4568
+ const saved = structuredClone(plan);
4569
+ return this.run(
4570
+ async () => {
4571
+ validateVolumeReleasePlan(saved);
4572
+ if (!this.state.pending && this.state.disposal) {
4573
+ check(sameValue(this.state.disposal_plan, saved), "completed release differs from plan");
4574
+ return this.releaseOutcome();
4575
+ }
4576
+ return (await this.beginLifecycle({ kind: "volume_release", plan: saved })).release;
4577
+ },
4578
+ false,
4579
+ true
4580
+ );
4581
+ }
4582
+ async beginLifecycle(pending) {
4583
+ if (this.state.disposal) refuse("fenced", "this workspace lifetime has been released");
4584
+ if (this.state.pending) {
4585
+ const saved = this.state.pending;
4586
+ const intent = saved.kind === "volume_release" ? { kind: saved.kind, plan: saved.plan } : saved;
4587
+ if (!sameValue(intent, pending))
4588
+ refuse("busy", "recover the saved operation before changing intent");
4589
+ return this.lifecycle(saved, false);
4590
+ }
4591
+ this.change(pending);
4592
+ return this.lifecycle(pending, true);
4593
+ }
4594
+ async lifecycle(pending, fresh) {
4595
+ let result;
4596
+ try {
4597
+ if (pending.kind === "renew") {
4598
+ const volume = await this.transport.renewLease(pending.request);
4599
+ if (!sameValue(volume.lifetime?.admission, this.state.admission))
4600
+ throw unknown(void 0, "renewal names another lifetime");
4601
+ result = { kind: pending.kind, volume };
4602
+ } else if (pending.kind === "remove") {
4603
+ const receipt = await this.transport.applyRemoval(pending.plan);
4604
+ if (!sameValue(receipt.principal, this.principal))
4605
+ throw unknown(void 0, "removal names another principal");
4606
+ result = { kind: pending.kind, receipt };
4607
+ } else {
4608
+ result = {
4609
+ kind: pending.kind,
4610
+ release: await this.transport.applyVolumeRelease(pending.plan)
4611
+ };
4612
+ }
4613
+ } catch (error) {
4614
+ if (pending.kind === "volume_release" && error instanceof NativeScratchPadError && error.code === "generation_retired" && !error.unknown) {
4615
+ const release = {
4616
+ target: pending.plan.request.target,
4617
+ slot: pending.plan.slot,
4618
+ generation: pending.plan.generation,
4619
+ operation_id: pending.plan.request.operation_id,
4620
+ disposed: true,
4621
+ capacity_returned: true
4622
+ };
4623
+ return this.finishRelease(pending.plan, release, !pending.fence_accepted);
4624
+ }
4625
+ if (pending.kind === "volume_release" && !fresh && !pending.fence_accepted && error instanceof NativeScratchPadError && !error.unknown && error.code === "conflict" && await this.releaseFenced(pending.plan)) {
4626
+ const state2 = { ...this.state };
4627
+ delete state2.pending;
4628
+ this.save(state2, true);
4629
+ refuse("fenced", "the saved release cannot take effect; review a new release plan");
4630
+ }
4631
+ if (error instanceof NativeScratchPadError && !error.unknown && (fresh || pending.kind === "renew" && error.code === "expired")) {
4632
+ const state2 = { ...this.state };
4633
+ delete state2.pending;
4634
+ this.save(state2, true);
4635
+ throw error;
4636
+ }
4637
+ throw unknown(error, `the saved ${pending.kind} request stays pending; call recover()`);
4638
+ }
4639
+ if (pending.kind === "volume_release" && result.release) {
4640
+ if (result.release.capacity_returned)
4641
+ return this.finishRelease(pending.plan, result.release, false);
4642
+ this.save({ ...this.state, pending: { ...pending, fence_accepted: true } }, true);
4643
+ return structuredClone(result);
4644
+ }
4645
+ const state = { ...this.state };
4646
+ delete state.pending;
4647
+ if (result.receipt) {
4648
+ state.removal = result.receipt;
4649
+ state.next = result.receipt.command.number + 1;
4650
+ }
4651
+ this.save(state, true);
4652
+ return structuredClone(result);
4653
+ }
4654
+ async releaseFenced(plan) {
4655
+ let current;
4656
+ try {
4657
+ current = await this.transport.planVolumeRelease(plan.request);
4658
+ } catch {
4659
+ return false;
4660
+ }
4661
+ return sameValue(current.request, plan.request) && current.incarnation === plan.incarnation && current.slot === plan.slot && current.generation === plan.generation && current.sequence > plan.sequence;
4662
+ }
4663
+ releaseOutcome() {
4664
+ check(this.state.disposal, "workspace has no completed release");
4665
+ return structuredClone({
4666
+ ...this.state.disposal,
4667
+ ...this.state.release_acceptance_unknown ? { acceptance_unknown: true } : {}
4668
+ });
4669
+ }
4670
+ finishRelease(plan, disposal, acceptanceUnknown) {
4671
+ const state = { ...this.state, disposal, disposal_plan: plan };
4672
+ delete state.pending;
4673
+ if (acceptanceUnknown) state.release_acceptance_unknown = true;
4674
+ this.save(state, true);
4675
+ return { kind: "volume_release", release: this.releaseOutcome() };
4676
+ }
3875
4677
  recover() {
3876
- return this.run(() => this.recoverPending());
4678
+ return this.run(() => this.recoverPending(), true, true);
3877
4679
  }
3878
4680
  async recoverPending() {
4681
+ if (this.state.renewal)
4682
+ return { kind: "renew", volume: await this.resumeRenewal(this.state.renewal, false) };
4683
+ const lifecycle = this.state.pending;
4684
+ if (lifecycle?.kind === "renew" || lifecycle?.kind === "remove" || lifecycle?.kind === "volume_release")
4685
+ return this.lifecycle(lifecycle, false);
4686
+ if (this.state.disposal) return { kind: "volume_release", release: this.releaseOutcome() };
3879
4687
  if (this.state.pending?.kind === "reset")
3880
4688
  refuse("fenced", "reset outcome needs an explicit resetWriter with the observed generation");
3881
4689
  await this.initialize();
@@ -3890,6 +4698,8 @@ var NativeScratchPad = class extends ScratchPadReader {
3890
4698
  }
3891
4699
  }
3892
4700
  if (pending) {
4701
+ if (pending.kind === "renew" || pending.kind === "remove" || pending.kind === "volume_release")
4702
+ return this.lifecycle(pending, false);
3893
4703
  await this.control(pending, false);
3894
4704
  return { kind: pending.kind };
3895
4705
  }
@@ -3913,6 +4723,9 @@ var NativeScratchPad = class extends ScratchPadReader {
3913
4723
  validateAction({ operation_id: options.operationId, trace });
3914
4724
  if (recover) await this.recoverPending();
3915
4725
  await this.initialize();
4726
+ const scope = this.scope;
4727
+ if (!scope || !sameValue(scope.admission, this.state.admission) || this.state.disposal)
4728
+ refuse("fenced", "workspace has no matching current reservation");
3916
4729
  const pending = this.state.pending;
3917
4730
  if (pending) {
3918
4731
  if (pending.kind !== "publish" || pending.command.operation_id !== options.operationId)
@@ -3937,7 +4750,7 @@ var NativeScratchPad = class extends ScratchPadReader {
3937
4750
  let ordinal = 0, totalBytes = 0;
3938
4751
  const put = (object, data) => {
3939
4752
  totalBytes += data.length;
3940
- if (totalBytes > this.scope.bytes || ordinal + 1 > this.scope.objects)
4753
+ if (totalBytes > scope.bytes || ordinal + 1 > scope.objects)
3941
4754
  refuse("capacity", "artifact exceeds this volume's configured capacity");
3942
4755
  this.store.putObject(ordinal++, object, data);
3943
4756
  };
@@ -3967,7 +4780,7 @@ var NativeScratchPad = class extends ScratchPadReader {
3967
4780
  "capacity",
3968
4781
  "all retention slots hold live or protected handoffs; cleanup cannot free a slot"
3969
4782
  );
3970
- const needsCleanup = fullRetention || view.objects + objects.length > this.scope.objects || view.bytes + objects.reduce((n, object) => n + object.length, 0) > this.scope.bytes;
4783
+ const needsCleanup = fullRetention || view.objects + objects.length > scope.objects || view.bytes + objects.reduce((n, object) => n + object.length, 0) > scope.bytes;
3971
4784
  if (needsCleanup) view = await this.maintainInternal();
3972
4785
  const slot = view.retentions.findIndex((entry) => !entry.pin);
3973
4786
  if (slot < 0)
@@ -3975,13 +4788,20 @@ var NativeScratchPad = class extends ScratchPadReader {
3975
4788
  "capacity",
3976
4789
  "all retention slots are occupied; release finished handoffs or wait for their expiry"
3977
4790
  );
3978
- if (view.objects + objects.length > this.scope.objects || view.bytes + objects.reduce((n, object) => n + object.length, 0) > this.scope.bytes)
4791
+ if (view.objects + objects.length > scope.objects || view.bytes + objects.reduce((n, object) => n + object.length, 0) > scope.bytes)
3979
4792
  refuse("capacity", "live roots and this artifact exceed the volume's capacity");
3980
4793
  const writer = this.state.writer;
3981
4794
  const actual = view.writers[writer.slot];
3982
4795
  if (actual.generation !== writer.generation || actual.number + 1 !== this.state.next)
3983
4796
  refuse("fenced", "remote writer position differs from the durable workspace");
3984
4797
  if (view.closed) refuse("closed", "volume is closed by another maintenance interval");
4798
+ const deadlineClock = now2();
4799
+ const until = Math.min(
4800
+ deadlineClock + ttl,
4801
+ view.lifetime?.expires_at || Number.MAX_SAFE_INTEGER
4802
+ );
4803
+ if (until <= deadlineClock)
4804
+ refuse("expired", "the observed lifetime has no remaining retention window");
3985
4805
  const command = {
3986
4806
  creation_id: this.target().creation_id,
3987
4807
  writer,
@@ -3994,7 +4814,7 @@ var NativeScratchPad = class extends ScratchPadReader {
3994
4814
  enabled: true,
3995
4815
  slot,
3996
4816
  generation: view.retentions[slot].generation,
3997
- until: now2() + ttl
4817
+ until
3998
4818
  }
3999
4819
  };
4000
4820
  const publication = {
@@ -4325,6 +5145,27 @@ var NativeScratchPad = class extends ScratchPadReader {
4325
5145
  return this.transport.getVolume(this.target());
4326
5146
  }
4327
5147
  async control(control, fresh) {
5148
+ if (control.kind === "apply" && this.state.admission) {
5149
+ let view2;
5150
+ try {
5151
+ view2 = await this.transport.getVolume(control.request.target);
5152
+ } catch (error) {
5153
+ throw unknown(error, "private collection completion has not been observed");
5154
+ }
5155
+ if (view2.epoch > control.request.revision) {
5156
+ const state3 = { ...this.state };
5157
+ delete state3.pending;
5158
+ this.save(state3, true);
5159
+ refuse("fenced", "a later collection epoch superseded the saved interval");
5160
+ }
5161
+ if (view2.epoch < control.request.revision)
5162
+ throw unknown(void 0, "saved collection epoch has not been observed");
5163
+ if (view2.closed) refuse("closed", "the private worker has not reopened the saved interval");
5164
+ const state2 = { ...this.state };
5165
+ delete state2.pending;
5166
+ this.save(state2, true);
5167
+ return;
5168
+ }
4328
5169
  let failure, view, handle;
4329
5170
  let settled = false;
4330
5171
  try {
@@ -4356,13 +5197,13 @@ var NativeScratchPad = class extends ScratchPadReader {
4356
5197
  settled = true;
4357
5198
  }
4358
5199
  } else if (control.kind === "close") {
4359
- if (view.closed && view.revision === control.request.revision + 1) {
5200
+ if (this.state.admission ? view.epoch === control.request.revision + 1 : view.closed && view.revision === control.request.revision + 1) {
4360
5201
  failure = void 0;
4361
5202
  settled = true;
4362
5203
  } else if (view.revision !== control.request.revision) {
4363
5204
  settled = true;
4364
5205
  failure = new NativeScratchPadError(
4365
- "conflict",
5206
+ this.state.admission ? "fenced" : "conflict",
4366
5207
  "saved close is fenced by another revision"
4367
5208
  );
4368
5209
  }
@@ -4376,9 +5217,15 @@ var NativeScratchPad = class extends ScratchPadReader {
4376
5217
  if (error instanceof NativeScratchPadError && !error.unknown && (fresh || control.kind === "release" || control.kind === "grant" && error.code === "expired"))
4377
5218
  settled = true;
4378
5219
  }
5220
+ if (control.kind === "close" && this.state.admission && view && view.epoch > control.request.revision + 1) {
5221
+ const state2 = { ...this.state };
5222
+ delete state2.pending;
5223
+ this.save(state2, true);
5224
+ refuse("fenced", "a later collection epoch superseded the saved close");
5225
+ }
4379
5226
  if (!settled)
4380
5227
  throw unknown(failure, `the saved ${control.kind} request stays pending; call recover()`);
4381
- if (!failure && control.kind === "close" && (!view?.closed || view.revision !== control.request.revision + 1))
5228
+ if (!failure && control.kind === "close" && (this.state.admission ? view?.epoch !== control.request.revision + 1 : !view?.closed || view.revision !== control.request.revision + 1))
4382
5229
  throw unknown(void 0, "close returned another maintenance interval");
4383
5230
  const state = { ...this.state };
4384
5231
  delete state.pending;
@@ -4389,10 +5236,13 @@ var NativeScratchPad = class extends ScratchPadReader {
4389
5236
  );
4390
5237
  state.grants.push({ request: control.request, handle, kind: control.provenance });
4391
5238
  }
4392
- if (!failure && control.kind === "close")
5239
+ if (!failure && control.kind === "close" && (!this.state.admission || view.closed))
4393
5240
  state.pending = {
4394
5241
  kind: "apply",
4395
- request: { target: this.target(), revision: view.revision }
5242
+ request: {
5243
+ target: this.target(),
5244
+ revision: this.state.admission ? control.request.revision + 1 : view.revision
5245
+ }
4396
5246
  };
4397
5247
  this.save(state, true);
4398
5248
  if (failure)
@@ -4406,32 +5256,59 @@ var NativeScratchPad = class extends ScratchPadReader {
4406
5256
  }
4407
5257
  };
4408
5258
  async function nativeScratchPad(options) {
4409
- validateVolume(options.volume);
5259
+ if (options.volume !== void 0) validateVolume(options.volume);
4410
5260
  text(options.creator, 512, 1);
4411
5261
  const ttl = options.ttlSeconds ?? 600;
4412
5262
  uint(ttl, MAX_GRANT_SECONDS, 1);
4413
5263
  const transport = new ScratchPadTransport(options.nc, options);
4414
5264
  const info = await transport.info();
4415
- const scope = info.scopes.find((scope2) => scope2.volume === options.volume);
4416
- if (!scope) refuse("denied", "authenticated principal owns no provisioned scope for this volume");
5265
+ const bootstrap = options.bootstrapFile === void 0 ? void 0 : readScratchPadBootstrapFile(options.bootstrapFile);
5266
+ const volume = options.volume ?? bootstrap?.volume;
5267
+ validateVolume(volume);
5268
+ if (bootstrap) {
5269
+ check(
5270
+ sameValue(bootstrap.principal, info.principal),
5271
+ "bootstrap names another authenticated principal"
5272
+ );
5273
+ check(bootstrap.volume === volume, "bootstrap differs from the configured volume");
5274
+ }
5275
+ const scope = info.scopes.find((scope2) => scope2.volume === volume);
4417
5276
  const store = await WorkspaceStore.open(options.dir);
4418
5277
  try {
4419
5278
  let state = store.load();
4420
5279
  if (state === void 0) {
5280
+ if (!scope)
5281
+ refuse("denied", "authenticated principal owns no provisioned scope for this volume");
5282
+ if (bootstrap && !sameValue(scope.admission, bootstrap.admission))
5283
+ refuse("fenced", "bootstrap does not match the current admission");
4421
5284
  state = {
4422
5285
  version: 1,
4423
5286
  principal: info.principal,
4424
- volume: options.volume,
5287
+ volume,
4425
5288
  creator: options.creator,
4426
- creation: { operation_id: randomUUID(), trace: {} },
5289
+ creation: bootstrap ? structuredClone(bootstrap.creation) : { operation_id: randomUUID(), trace: {} },
5290
+ ...scope.admission ? { admission: structuredClone(scope.admission) } : {},
4427
5291
  next: 1,
4428
5292
  grants: []
4429
5293
  };
4430
5294
  store.save(state);
4431
5295
  }
4432
5296
  validateState(state);
5297
+ if (bootstrap) {
5298
+ check(
5299
+ sameValue(state.principal, bootstrap.principal) && state.volume === bootstrap.volume && sameValue(state.admission, bootstrap.admission) && sameValue(state.creation, bootstrap.creation),
5300
+ "workspace differs from the bootstrap creation request"
5301
+ );
5302
+ if (state.disposal || state.disposal_plan || state.pending?.kind === "volume_release")
5303
+ refuse("fenced", "bootstrap cannot reopen a workspace with a saved disposal");
5304
+ }
5305
+ if (!bootstrap && !state.target && (!scope || !sameValue(state.admission, scope.admission)))
5306
+ refuse(
5307
+ "fenced",
5308
+ "saved creation does not match the current admission; it cannot adopt a new lifetime"
5309
+ );
4433
5310
  check(
4434
- sameValue(state.principal, info.principal) && state.volume === options.volume && state.creator === options.creator,
5311
+ sameValue(state.principal, info.principal) && state.volume === volume && state.creator === options.creator,
4435
5312
  "workspace is bound to another principal, volume, or creator"
4436
5313
  );
4437
5314
  return new NativeScratchPad(transport, store, state, scope, ttl);
@@ -4442,14 +5319,16 @@ async function nativeScratchPad(options) {
4442
5319
  }
4443
5320
  async function nativeScratchPadFromEnv(env, options) {
4444
5321
  const volume = env["SCRATCHPAD_VOLUME"];
4445
- if (!volume) return void 0;
5322
+ const bootstrapFile = env["SCRATCHPAD_BOOTSTRAP_FILE"] || void 0;
5323
+ if (!volume && !bootstrapFile) return void 0;
4446
5324
  const dir = env["SCRATCHPAD_DIR"];
4447
5325
  check(dir, "SCRATCHPAD_DIR must name a persistent private directory for the native client");
4448
5326
  const value = env["SCRATCHPAD_GRANT_TTL_S"];
4449
5327
  check(value === void 0 || /^[0-9]+$/.test(value), "SCRATCHPAD_GRANT_TTL_S must be an integer");
4450
5328
  return nativeScratchPad({
4451
5329
  ...options,
4452
- volume,
5330
+ ...volume ? { volume } : {},
5331
+ ...bootstrapFile ? { bootstrapFile } : {},
4453
5332
  dir,
4454
5333
  ...value === void 0 ? {} : { ttlSeconds: Number(value) }
4455
5334
  });
@@ -4472,6 +5351,7 @@ var SP_VARS = {
4472
5351
  creds: "SP_NATS_CREDS",
4473
5352
  dir: SCRATCHPAD_DIR_VAR,
4474
5353
  volume: SCRATCHPAD_VOLUME_VAR,
5354
+ bootstrapFile: SCRATCHPAD_BOOTSTRAP_FILE_VAR,
4475
5355
  creator: "SP_CREATOR",
4476
5356
  threadId: "SP_THREAD_ID",
4477
5357
  rootId: "SP_ROOT_ID",
@@ -4489,8 +5369,13 @@ async function resolveScratchPad(ctx, env) {
4489
5369
  const fromEnv = env[variable];
4490
5370
  return fromEnv === void 0 || fromEnv === "" ? void 0 : fromEnv;
4491
5371
  };
4492
- const volume = pick("volume", SP_VARS.volume);
5372
+ const configuredBootstrap = pick("bootstrapFile", SP_VARS.bootstrapFile);
5373
+ const bootstrapFile = configuredBootstrap === void 0 ? void 0 : resolve(expandHome(configuredBootstrap));
5374
+ const bootstrap = bootstrapFile === void 0 ? void 0 : readScratchPadBootstrapFile(bootstrapFile);
5375
+ const volume = pick("volume", SP_VARS.volume) ?? bootstrap?.volume;
4493
5376
  if (volume === void 0) return void 0;
5377
+ if (bootstrap && bootstrap.volume !== volume)
5378
+ throw new Error("ScratchPad bootstrap differs from the configured volume");
4494
5379
  let url = pick("url", "NATS_URL");
4495
5380
  let creds = pick("creds", "NATS_CREDS");
4496
5381
  if (url === void 0 || creds === void 0) {
@@ -4520,6 +5405,8 @@ async function resolveScratchPad(ctx, env) {
4520
5405
  if (dir === void 0 && ctx.settings.stateDir !== void 0) {
4521
5406
  dir = join(ctx.settings.stateDir, "scratchpad");
4522
5407
  }
5408
+ if (bootstrap && dir === void 0)
5409
+ throw new Error("ScratchPad bootstrap requires a persistent workspace directory");
4523
5410
  if (dir !== void 0) {
4524
5411
  dir = resolve(expandHome(dir));
4525
5412
  try {
@@ -4534,6 +5421,7 @@ async function resolveScratchPad(ctx, env) {
4534
5421
  url,
4535
5422
  creds: expandHome(creds),
4536
5423
  volume,
5424
+ ...bootstrapFile === void 0 ? {} : { bootstrapFile },
4537
5425
  ...dir !== void 0 ? { dir } : {},
4538
5426
  ...grants !== void 0 ? { grants } : {}
4539
5427
  };
@@ -4541,7 +5429,13 @@ async function resolveScratchPad(ctx, env) {
4541
5429
  function exportScratchPad(settings, env) {
4542
5430
  env[SP_VARS.url] = settings.url;
4543
5431
  env[SP_VARS.creds] = settings.creds;
4544
- env[SP_VARS.volume] = settings.volume;
5432
+ if (settings.bootstrapFile !== void 0) {
5433
+ env[SP_VARS.bootstrapFile] = settings.bootstrapFile;
5434
+ delete env[SP_VARS.volume];
5435
+ } else {
5436
+ env[SP_VARS.volume] = settings.volume;
5437
+ delete env[SP_VARS.bootstrapFile];
5438
+ }
4545
5439
  if (settings.dir !== void 0) env[SP_VARS.dir] = settings.dir;
4546
5440
  if (settings.grants !== void 0) env[SP_VARS.grants] = settings.grants;
4547
5441
  }
@@ -4658,6 +5552,7 @@ function spPort(settings, creator, env, skillDir) {
4658
5552
  if (bin === void 0) return void 0;
4659
5553
  const port = spCliScratchPad({
4660
5554
  volume: settings.volume,
5555
+ ...settings.bootstrapFile === void 0 ? {} : { bootstrapFile: settings.bootstrapFile },
4661
5556
  nats: { url: settings.url, creds: settings.creds },
4662
5557
  bin,
4663
5558
  creator,
@@ -4728,6 +5623,6 @@ function expandHome(path) {
4728
5623
  return path;
4729
5624
  }
4730
5625
 
4731
- export { DEFAULT_EDGE_SUBJECT, DEFAULT_GRANT_REQUEST_TIMEOUT_MS, DEFAULT_GRANT_TTL_S, DEFAULT_READ_LIMIT_BYTES, DEFAULT_SP_BIN, EDGE_RECORD_VERSION, EDGE_SUBJECT_OPTION, EDGE_SUBJECT_VAR, GRANT_REQUEST_ENDPOINT, HARNESS_THREAD_ID_MAX, LOOP_GUARD_CODE, NativeScratchPad, NativeScratchPadError, READS_REFERENCES_KEY, READS_SCRATCHPAD_REFERENCES_FIELD, REFERENCE_PREFIX, ROOT_ID_FIELD, ROOT_ID_HEADER, SCRATCHPAD_BIN_VAR, SCRATCHPAD_DIR_VAR, SCRATCHPAD_GRANT_TTL_S_VAR, SCRATCHPAD_OPTION, SCRATCHPAD_VOLUME_VAR, SERVED_RECORD_REASON_VERSION, SERVED_RECORD_VERSION, SHORT_REFERENCE_PREFIX, SP_VARS, ScratchPadError, ScratchPadExtension, ScratchPadHandOn, ScratchPadReader, ServedPromptEnv, ServedPublisher, THREAD_ID_FIELD, THREAD_ID_HEADER, THREAD_ID_HEX_LEN, TOOL_CALL_ID_CONTEXT_KEY, TOOL_CALL_ID_MAX, activeTrace, answerGrantRequest, assertValidTraceOptions, bindActiveTrace, buildEdgeRecord, buildServedRecord, countTraceRecordDropped, countTraceRecordPublished, decodeReference, decodeShortReference, durableHeaders, durableRunId, durableTrace, encodeGrantReply, encodeGrantRequest, encodeReference, exportScratchPad, fabricTracing, findReferences, grantRequestEndpoint, grantSubject, granteeFromIdentity, isThreadId, loopGuard, loopGuardDescription, nativeScratchPad, nativeScratchPadFromEnv, randomThreadId, readRecordedGrants, readsReferences, referenceCreator, removeReferences, requestGrant, resolveEdgeSubject, resolveScratchPad, scratchPadExtension, scratchPadFromEnv, scratchPadReader, servedPublisher, spBinary, spCliScratchPad, spPort, traceHeaders, traceRecordCounts, unixSeconds, validHarnessThreadId, validToolCallId, withDurableTrace, withRecordedGrants };
5626
+ export { CALLER_INBOX_PREFIX, CANNOT_ANSWER, CallerStream, DEFAULT_DISCOVER_TIMEOUT_MS, DEFAULT_EDGE_SUBJECT, DEFAULT_GRANT_REQUEST_TIMEOUT_MS, DEFAULT_GRANT_TTL_S, DEFAULT_READ_LIMIT_BYTES, DEFAULT_SP_BIN, EDGE_RECORD_VERSION, EDGE_SUBJECT_OPTION, EDGE_SUBJECT_VAR, FabricCallerError, GRANT_REQUEST_ENDPOINT, HARNESS_THREAD_ID_MAX, LOOP_GUARD_CODE, MAX_BOOTSTRAP_BYTES, NATS_CREDS_VAR, NATS_URL_VAR, NativeScratchPad, NativeScratchPadError, READS_REFERENCES_KEY, READS_SCRATCHPAD_REFERENCES_FIELD, REFERENCE_PREFIX, ROOT_ID_FIELD, ROOT_ID_HEADER, SCRATCHPAD_BIN_VAR, SCRATCHPAD_BOOTSTRAP_FILE_VAR, SCRATCHPAD_DIR_VAR, SCRATCHPAD_GRANT_TTL_S_VAR, SCRATCHPAD_OPTION, SCRATCHPAD_VOLUME_VAR, SERVED_RECORD_REASON_VERSION, SERVED_RECORD_VERSION, SHORT_REFERENCE_PREFIX, SP_VARS, ScratchPadError, ScratchPadExtension, ScratchPadHandOn, ScratchPadReader, ServedPromptEnv, ServedPublisher, THREAD_ID_FIELD, THREAD_ID_HEADER, THREAD_ID_HEX_LEN, TOOL_CALL_ID_CONTEXT_KEY, TOOL_CALL_ID_MAX, activeTrace, answerGrantRequest, assertValidTraceOptions, bindActiveTrace, buildEdgeRecord, buildServedRecord, countTraceRecordDropped, countTraceRecordPublished, decodeReference, decodeScratchPadBootstrap, decodeShortReference, durableHeaders, durableRunId, durableTrace, encodeGrantReply, encodeGrantRequest, encodeReference, exportScratchPad, fabricCaller, fabricCallerFromEnv, fabricTracing, findReferences, grantRequestEndpoint, grantSubject, granteeFromIdentity, isThreadId, loopGuard, loopGuardDescription, nativeScratchPad, nativeScratchPadFromEnv, pickAgent, randomThreadId, readRecordedGrants, readsReferences, referenceCreator, removeReferences, requestGrant, resolveEdgeSubject, resolveScratchPad, scratchPadExtension, scratchPadFromEnv, scratchPadReader, servedPublisher, spBinary, spCliScratchPad, spPort, traceHeaders, traceRecordCounts, unixSeconds, validHarnessThreadId, validToolCallId, withDurableTrace, withRecordedGrants };
4732
5627
  //# sourceMappingURL=index.js.map
4733
5628
  //# sourceMappingURL=index.js.map