blun-king-cli 9.1.616 → 9.1.618

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/worker-host.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:8034cba54fd011bf9ce255483d60f6d12f8cdb9862b405ccf424ae40ac14b7c5
2
+ // BLUN_BUILD_INPUT_SHA256:136619430389be9d0388c81121dbf3721ce107f6a770cab9494fa3ffee3aade3
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -12,6 +12,7 @@ import * as path$11 from "node:path";
12
12
  import path, { basename, dirname, isAbsolute, join, parse, posix, relative, resolve, sep, win32 } from "node:path";
13
13
  import { Blob as Blob$1, File as File$1 } from "node:buffer";
14
14
  import fs, { access, appendFile, chmod, copyFile, cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
15
+ import { AsyncLocalStorage } from "node:async_hooks";
15
16
  import * as nodeOs from "node:os";
16
17
  import os, { arch, homedir, hostname, release, tmpdir, type, userInfo } from "node:os";
17
18
  import { execFile, execFileSync, spawn } from "node:child_process";
@@ -30,7 +31,6 @@ import en from "zlib";
30
31
  import { Buffer as Buffer$1 } from "buffer";
31
32
  import { ReadableStream as ReadableStream$1 } from "node:stream/web";
32
33
  import { isDeepStrictEqual, stripVTControlCharacters } from "node:util";
33
- import { AsyncLocalStorage } from "node:async_hooks";
34
34
  import * as posixPath from "node:path/posix";
35
35
  import * as win32Path from "node:path/win32";
36
36
  import { createServer } from "node:http";
@@ -10151,16 +10151,272 @@ function isPlainObject$3(value) {
10151
10151
  return typeof value === "object" && value !== null && !Array.isArray(value);
10152
10152
  }
10153
10153
  //#endregion
10154
- //#region ../../packages/agent-core/src/session/writer-lease.ts
10154
+ //#region ../../packages/agent-core/src/config/resolve.ts
10155
+ const TRUE_BOOLEAN_ENV_VALUES = new Set([
10156
+ "1",
10157
+ "true",
10158
+ "yes",
10159
+ "on"
10160
+ ]);
10161
+ const FALSE_BOOLEAN_ENV_VALUES = new Set([
10162
+ "0",
10163
+ "false",
10164
+ "no",
10165
+ "off"
10166
+ ]);
10167
+ function resolveConfigValue(input) {
10168
+ return input.parseEnv(input.env?.[input.envKey]) ?? input.configValue ?? input.defaultValue;
10169
+ }
10170
+ function parseBooleanEnv(value) {
10171
+ const normalized = value?.trim().toLowerCase();
10172
+ if (normalized === void 0 || normalized.length === 0) return void 0;
10173
+ if (TRUE_BOOLEAN_ENV_VALUES.has(normalized)) return true;
10174
+ if (FALSE_BOOLEAN_ENV_VALUES.has(normalized)) return false;
10175
+ }
10176
+ /**
10177
+ * Parse a floating-point environment value (e.g. `BLUN_MODEL_TEMPERATURE`).
10178
+ * Returns `undefined` when unset/blank; throws `BlunError(CONFIG_INVALID)` on a
10179
+ * non-numeric value so a typo fails fast like the other `BLUN_MODEL_*` vars.
10180
+ * No range validation — callers pass values the upstream API accepts.
10181
+ */
10182
+ function parseFloatEnv(value, varName) {
10183
+ const trimmed = value?.trim();
10184
+ if (trimmed === void 0 || trimmed.length === 0) return void 0;
10185
+ const parsed = Number(trimmed);
10186
+ if (!Number.isFinite(parsed)) throw new BlunError(ErrorCodes.CONFIG_INVALID, `${varName} must be a number, got "${value}".`);
10187
+ return parsed;
10188
+ }
10189
+ //#endregion
10190
+ //#region ../../packages/agent-core/src/config/env-model.ts
10191
+ /** Reserved keys for the env-driven synthetic provider / model alias. */
10192
+ const ENV_MODEL_PROVIDER_KEY = "__blun_env__";
10193
+ const ENV_MODEL_ALIAS_KEY = "__blun_env_model__";
10194
+ const DEFAULT_BASE_URL = "https://api.blun.ai/v1";
10195
+ /** Default context window (256K) used when BLUN_MODEL_MAX_CONTEXT_SIZE is unset. */
10196
+ const DEFAULT_MAX_CONTEXT_SIZE = 262144;
10197
+ /** Default capabilities when BLUN_MODEL_CAPABILITIES is unset (blun models support both). */
10198
+ const DEFAULT_CAPABILITIES = ["image_in", "thinking"];
10199
+ function trimmed(value) {
10200
+ const t = value?.trim();
10201
+ return t === void 0 || t.length === 0 ? void 0 : t;
10202
+ }
10203
+ function fail(message) {
10204
+ throw new BlunError(ErrorCodes.CONFIG_INVALID, message);
10205
+ }
10206
+ function parsePositiveInt$1(raw, varName) {
10207
+ if (!/^\d+$/.test(raw) || Number(raw) <= 0) fail(`${varName} must be a positive integer, got "${raw}".`);
10208
+ return Number(raw);
10209
+ }
10210
+ function parseProviderType(raw) {
10211
+ if (raw === void 0) return "blun";
10212
+ if (raw.toLowerCase() !== "blun") fail(`BLUN_MODEL_PROVIDER_TYPE must be "blun", got "${raw}".`);
10213
+ return "blun";
10214
+ }
10215
+ function parseCapabilities(raw) {
10216
+ if (raw === void 0) return void 0;
10217
+ const caps = raw.split(",").map((c) => c.trim().toLowerCase()).filter((c) => c.length > 0);
10218
+ return caps.length === 0 ? void 0 : caps;
10219
+ }
10220
+ function parseBooleanVar(raw, varName) {
10221
+ const value = trimmed(raw);
10222
+ if (value === void 0) return void 0;
10223
+ const parsed = parseBooleanEnv(value);
10224
+ if (parsed === void 0) fail(`${varName} must be a boolean (true/false/1/0/yes/no/on/off), got "${raw}".`);
10225
+ return parsed;
10226
+ }
10227
+ /**
10228
+ * When `BLUN_MODEL_NAME` is set, synthesize one provider + one model alias from
10229
+ * the `BLUN_MODEL_*` environment variables and make it the default model.
10230
+ * Returns the config unchanged when the trigger variable is absent.
10231
+ *
10232
+ * IMPORTANT: the synthesized provider/model/default_model exist ONLY in the
10233
+ * in-memory runtime config and must never be serialized back to config.toml.
10234
+ * Two layers enforce this: write paths read the raw config via `readConfigFile`,
10235
+ * and `writeConfigFile` strips the reserved entries via `stripEnvModelConfig` as
10236
+ * a final guard against patch round-trips (getConfig -> setConfig).
10237
+ */
10238
+ function applyEnvModelConfig(config, env = process.env) {
10239
+ const envRaw = (suffix) => env[`BLUN_MODEL_${suffix}`];
10240
+ const model = trimmed(envRaw("NAME"));
10241
+ if (model === void 0) return config;
10242
+ const apiKey = trimmed(envRaw("API_KEY"));
10243
+ if (apiKey === void 0) fail("BLUN_MODEL_NAME is set but BLUN_MODEL_API_KEY is missing.");
10244
+ const maxContextRaw = trimmed(envRaw("MAX_CONTEXT_SIZE"));
10245
+ const maxContextSize = maxContextRaw === void 0 ? DEFAULT_MAX_CONTEXT_SIZE : parsePositiveInt$1(maxContextRaw, "BLUN_MODEL_MAX_CONTEXT_SIZE");
10246
+ const type = parseProviderType(trimmed(envRaw("PROVIDER_TYPE")));
10247
+ const baseUrl = trimmed(envRaw("BASE_URL")) ?? DEFAULT_BASE_URL;
10248
+ const provider = {
10249
+ type,
10250
+ apiKey,
10251
+ ...baseUrl !== void 0 ? { baseUrl } : {}
10252
+ };
10253
+ const maxOutputRaw = trimmed(envRaw("MAX_OUTPUT_SIZE"));
10254
+ const maxOutputSize = maxOutputRaw !== void 0 ? parsePositiveInt$1(maxOutputRaw, "BLUN_MODEL_MAX_OUTPUT_SIZE") : void 0;
10255
+ const capabilities = parseCapabilities(envRaw("CAPABILITIES")) ?? DEFAULT_CAPABILITIES;
10256
+ const displayName = trimmed(envRaw("DISPLAY_NAME"));
10257
+ const reasoningKey = trimmed(envRaw("REASONING_KEY"));
10258
+ const adaptiveThinking = parseBooleanVar(envRaw("ADAPTIVE_THINKING"), "BLUN_MODEL_ADAPTIVE_THINKING");
10259
+ const alias = {
10260
+ provider: ENV_MODEL_PROVIDER_KEY,
10261
+ model,
10262
+ maxContextSize,
10263
+ capabilities,
10264
+ ...displayName !== void 0 ? { displayName } : {},
10265
+ ...maxOutputSize !== void 0 ? { maxOutputSize } : {},
10266
+ ...reasoningKey !== void 0 ? { reasoningKey } : {},
10267
+ ...adaptiveThinking !== void 0 ? { adaptiveThinking } : {}
10268
+ };
10269
+ const thinkingEffort = trimmed(env["BLUN_MODEL_THINKING_EFFORT"]);
10270
+ const thinking = thinkingEffort !== void 0 ? {
10271
+ ...config.thinking,
10272
+ effort: thinkingEffort
10273
+ } : config.thinking;
10274
+ return validateConfig({
10275
+ ...config,
10276
+ providers: {
10277
+ ...config.providers,
10278
+ [ENV_MODEL_PROVIDER_KEY]: provider
10279
+ },
10280
+ models: {
10281
+ ...config.models,
10282
+ [ENV_MODEL_ALIAS_KEY]: alias
10283
+ },
10284
+ defaultModel: ENV_MODEL_ALIAS_KEY,
10285
+ ...thinking !== void 0 ? { thinking } : {}
10286
+ });
10287
+ }
10288
+ /**
10289
+ * Remove the env-synthesized provider/model before a config is persisted to
10290
+ * disk. Mirror of {@link applyEnvModelConfig}: that injects the reserved entries
10291
+ * into the in-memory runtime config; this guarantees they never reach
10292
+ * config.toml — including via a `getConfig` -> `setConfig` patch round-trip,
10293
+ * where the runtime config (carrying the env provider and its shell API key)
10294
+ * would otherwise be merged back and written out. Every env-injected top-level
10295
+ * field (default_model, thinking) is restored to its on-disk
10296
+ * value from `config.raw` rather than erased, so real values already in
10297
+ * config.toml survive the round-trip.
10298
+ */
10299
+ function stripEnvModelConfig(config) {
10300
+ const hasProvider = ENV_MODEL_PROVIDER_KEY in config.providers;
10301
+ const hasModel = config.models !== void 0 && "__blun_env_model__" in config.models;
10302
+ const defaultIsEnv = config.defaultModel === ENV_MODEL_ALIAS_KEY;
10303
+ if (!hasProvider && !hasModel && !defaultIsEnv) return config;
10304
+ const providers = { ...config.providers };
10305
+ delete providers[ENV_MODEL_PROVIDER_KEY];
10306
+ let models = config.models;
10307
+ if (models !== void 0 && "__blun_env_model__" in models) {
10308
+ models = { ...models };
10309
+ delete models[ENV_MODEL_ALIAS_KEY];
10310
+ }
10311
+ return {
10312
+ ...config,
10313
+ providers,
10314
+ ...models !== void 0 ? { models } : {},
10315
+ ...defaultIsEnv ? { defaultModel: rawDefaultModel(config) } : {},
10316
+ thinking: rawThinking(config)
10317
+ };
10318
+ }
10319
+ function rawDefaultModel(config) {
10320
+ const raw = config.raw?.["default_model"];
10321
+ return typeof raw === "string" ? raw : void 0;
10322
+ }
10323
+ function rawThinking(config) {
10324
+ const raw = config.raw?.["thinking"];
10325
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : void 0;
10326
+ }
10327
+ //#endregion
10328
+ //#region ../../packages/oauth/src/errors.ts
10329
+ /**
10330
+ * OAuth error classes.
10331
+ *
10332
+ * All errors derive from {@link OAuthError}. Distinguishing subclasses let
10333
+ * callers react appropriately:
10334
+ * - `OAuthUnauthorizedError`: 401/403 from token endpoint → refresh_token
10335
+ * or credentials are bad; drive user through `/login` again.
10336
+ * - `OAuthConnectionError`: transport-level OAuth request failure; callers
10337
+ * may retry the operation.
10338
+ * - `DeviceCodeExpiredError`: device_code TTL ran out before user approved;
10339
+ * restart the device flow.
10340
+ * - `DeviceCodeTimeoutError`: local 15 min wall-clock budget exhausted
10341
+ * before the user completed approval.
10342
+ * - `RetryableRefreshError`: 429 / 5xx from token endpoint; the refresh
10343
+ * helper retries with exponential backoff before surfacing this.
10344
+ */
10345
+ var OAuthError$1 = class extends Error {
10346
+ constructor(message) {
10347
+ super(message);
10348
+ this.name = "OAuthError";
10349
+ }
10350
+ };
10351
+ var OAuthUnauthorizedError = class extends OAuthError$1 {
10352
+ constructor(message) {
10353
+ super(message);
10354
+ this.name = "OAuthUnauthorizedError";
10355
+ }
10356
+ };
10357
+ var OAuthConnectionError = class extends OAuthError$1 {
10358
+ constructor(message) {
10359
+ super(message);
10360
+ this.name = "OAuthConnectionError";
10361
+ }
10362
+ };
10363
+ var DeviceCodeTimeoutError = class extends OAuthError$1 {
10364
+ constructor(message = "Device authorization timed out locally.") {
10365
+ super(message);
10366
+ this.name = "DeviceCodeTimeoutError";
10367
+ }
10368
+ };
10369
+ var RetryableRefreshError = class extends OAuthError$1 {
10370
+ constructor(message) {
10371
+ super(message);
10372
+ this.name = "RetryableRefreshError";
10373
+ }
10374
+ };
10375
+ //#endregion
10376
+ //#region ../../packages/oauth/src/types.ts
10377
+ function tokenToWire(token) {
10378
+ return {
10379
+ access_token: token.accessToken,
10380
+ refresh_token: token.refreshToken,
10381
+ expires_at: token.expiresAt,
10382
+ scope: token.scope,
10383
+ token_type: token.tokenType,
10384
+ expires_in: token.expiresIn,
10385
+ ...token.memoryWorkspaceId === void 0 ? {} : { memory_workspace_id: token.memoryWorkspaceId }
10386
+ };
10387
+ }
10388
+ function tokenFromWire(wire) {
10389
+ return {
10390
+ accessToken: wire.access_token ?? "",
10391
+ refreshToken: wire.refresh_token ?? "",
10392
+ expiresAt: typeof wire.expires_at === "number" ? wire.expires_at : 0,
10393
+ scope: wire.scope ?? "",
10394
+ tokenType: wire.token_type ?? "",
10395
+ expiresIn: typeof wire.expires_in === "number" ? wire.expires_in : 0,
10396
+ ...typeof wire.memory_workspace_id === "string" && wire.memory_workspace_id.length > 0 ? { memoryWorkspaceId: wire.memory_workspace_id } : {}
10397
+ };
10398
+ }
10399
+ //#endregion
10400
+ //#region ../../packages/oauth/src/private-path-writer.ts
10401
+ var PrivatePathWriterError = class extends Error {
10402
+ failure;
10403
+ code;
10404
+ constructor(failure, cause) {
10405
+ super(`Private path writer ownership ${failure}`, { cause });
10406
+ this.failure = failure;
10407
+ this.name = "PrivatePathWriterError";
10408
+ this.code = `private.writer_${failure}`;
10409
+ }
10410
+ };
10155
10411
  const markerPattern = /^owner-([0-9a-f-]{36})\.json$/;
10156
10412
  function codeOf(error) {
10157
10413
  return error?.code;
10158
10414
  }
10159
10415
  function unavailable(cause) {
10160
- return new BlunError(ErrorCodes.SESSION_WRITER_UNAVAILABLE, "Session writer ownership could not be verified", { cause });
10416
+ return new PrivatePathWriterError("unavailable", cause);
10161
10417
  }
10162
10418
  /** Resolve aliases without opening a replaceable file before its writer is owned. */
10163
- async function canonicalWriterPath(target) {
10419
+ async function canonicalPrivateWriterPath(target) {
10164
10420
  let path = resolve(target);
10165
10421
  for (let depth = 0; depth < 40; depth += 1) {
10166
10422
  await mkdir(dirname(path), {
@@ -10258,7 +10514,7 @@ async function reclaimDeadOwner(lockDirectory) {
10258
10514
  await removeEmptyDirectory(lockDirectory);
10259
10515
  return;
10260
10516
  }
10261
- throw new BlunError(ErrorCodes.SESSION_WRITER_BUSY, "This session is already open for writing");
10517
+ throw new PrivatePathWriterError("busy");
10262
10518
  }
10263
10519
  await removeEmptyDirectory(lockDirectory);
10264
10520
  } catch (error) {
@@ -10266,14 +10522,14 @@ async function reclaimDeadOwner(lockDirectory) {
10266
10522
  }
10267
10523
  }
10268
10524
  /** A local-host writer claim. It never expires based on age or missed heartbeats. */
10269
- async function acquireSessionWriter(sessionDirectory, options = {}) {
10525
+ async function acquirePrivatePathWriter(targetPath, options = {}) {
10270
10526
  const waitForBusyMs = options.waitForBusyMs ?? 0;
10271
10527
  if (!Number.isFinite(waitForBusyMs) || waitForBusyMs < 0 || waitForBusyMs > 5e3) throw new RangeError("Writer contention budget must be between 0 and 5000 milliseconds");
10272
10528
  const deadline = performance.now() + waitForBusyMs;
10273
10529
  let staged;
10274
10530
  let name;
10275
10531
  try {
10276
- const canonical = await canonicalWriterPath(sessionDirectory);
10532
+ const canonical = await canonicalPrivateWriterPath(targetPath);
10277
10533
  const lockDirectory = join(dirname(canonical), `@writer-${basename(canonical)}`);
10278
10534
  const ownerId = randomUUID();
10279
10535
  name = `owner-${ownerId}.json`;
@@ -10302,7 +10558,7 @@ async function acquireSessionWriter(sessionDirectory, options = {}) {
10302
10558
  try {
10303
10559
  await reclaimDeadOwner(lockDirectory);
10304
10560
  } catch (ownershipError) {
10305
- if (!(ownershipError instanceof BlunError) || ownershipError.code !== ErrorCodes.SESSION_WRITER_BUSY) throw ownershipError;
10561
+ if (!(ownershipError instanceof PrivatePathWriterError) || ownershipError.failure !== "busy") throw ownershipError;
10306
10562
  const remaining = deadline - performance.now();
10307
10563
  if (waitForBusyMs === 0 || remaining <= 0) throw ownershipError;
10308
10564
  await new Promise((resolve) => {
@@ -10318,10 +10574,11 @@ async function acquireSessionWriter(sessionDirectory, options = {}) {
10318
10574
  let releasing;
10319
10575
  const assertOwned = async () => {
10320
10576
  const current = await readOwner(lockDirectory).catch(() => void 0);
10321
- if (released || current?.record.ownerId !== ownerId || current.record.pid !== process.pid) throw new BlunError(ErrorCodes.SESSION_WRITER_LOST, "Session writer ownership was lost");
10577
+ if (released || current?.record.ownerId !== ownerId || current.record.pid !== process.pid) throw new PrivatePathWriterError("lost");
10322
10578
  };
10323
10579
  return {
10324
10580
  lockDirectory,
10581
+ canonicalPath: canonical,
10325
10582
  assertOwned,
10326
10583
  release() {
10327
10584
  releasing ??= (async () => {
@@ -10335,7 +10592,7 @@ async function acquireSessionWriter(sessionDirectory, options = {}) {
10335
10592
  };
10336
10593
  }
10337
10594
  } catch (error) {
10338
- if (error instanceof BlunError) throw error;
10595
+ if (error instanceof PrivatePathWriterError) throw error;
10339
10596
  throw unavailable(error);
10340
10597
  } finally {
10341
10598
  if (staged !== void 0 && name !== void 0) {
@@ -10347,78 +10604,6 @@ async function acquireSessionWriter(sessionDirectory, options = {}) {
10347
10604
  }
10348
10605
  }
10349
10606
  //#endregion
10350
- //#region ../../packages/oauth/src/errors.ts
10351
- /**
10352
- * OAuth error classes.
10353
- *
10354
- * All errors derive from {@link OAuthError}. Distinguishing subclasses let
10355
- * callers react appropriately:
10356
- * - `OAuthUnauthorizedError`: 401/403 from token endpoint → refresh_token
10357
- * or credentials are bad; drive user through `/login` again.
10358
- * - `OAuthConnectionError`: transport-level OAuth request failure; callers
10359
- * may retry the operation.
10360
- * - `DeviceCodeExpiredError`: device_code TTL ran out before user approved;
10361
- * restart the device flow.
10362
- * - `DeviceCodeTimeoutError`: local 15 min wall-clock budget exhausted
10363
- * before the user completed approval.
10364
- * - `RetryableRefreshError`: 429 / 5xx from token endpoint; the refresh
10365
- * helper retries with exponential backoff before surfacing this.
10366
- */
10367
- var OAuthError$1 = class extends Error {
10368
- constructor(message) {
10369
- super(message);
10370
- this.name = "OAuthError";
10371
- }
10372
- };
10373
- var OAuthUnauthorizedError = class extends OAuthError$1 {
10374
- constructor(message) {
10375
- super(message);
10376
- this.name = "OAuthUnauthorizedError";
10377
- }
10378
- };
10379
- var OAuthConnectionError = class extends OAuthError$1 {
10380
- constructor(message) {
10381
- super(message);
10382
- this.name = "OAuthConnectionError";
10383
- }
10384
- };
10385
- var DeviceCodeTimeoutError = class extends OAuthError$1 {
10386
- constructor(message = "Device authorization timed out locally.") {
10387
- super(message);
10388
- this.name = "DeviceCodeTimeoutError";
10389
- }
10390
- };
10391
- var RetryableRefreshError = class extends OAuthError$1 {
10392
- constructor(message) {
10393
- super(message);
10394
- this.name = "RetryableRefreshError";
10395
- }
10396
- };
10397
- //#endregion
10398
- //#region ../../packages/oauth/src/types.ts
10399
- function tokenToWire(token) {
10400
- return {
10401
- access_token: token.accessToken,
10402
- refresh_token: token.refreshToken,
10403
- expires_at: token.expiresAt,
10404
- scope: token.scope,
10405
- token_type: token.tokenType,
10406
- expires_in: token.expiresIn,
10407
- ...token.memoryWorkspaceId === void 0 ? {} : { memory_workspace_id: token.memoryWorkspaceId }
10408
- };
10409
- }
10410
- function tokenFromWire(wire) {
10411
- return {
10412
- accessToken: wire.access_token ?? "",
10413
- refreshToken: wire.refresh_token ?? "",
10414
- expiresAt: typeof wire.expires_at === "number" ? wire.expires_at : 0,
10415
- scope: wire.scope ?? "",
10416
- tokenType: wire.token_type ?? "",
10417
- expiresIn: typeof wire.expires_in === "number" ? wire.expires_in : 0,
10418
- ...typeof wire.memory_workspace_id === "string" && wire.memory_workspace_id.length > 0 ? { memoryWorkspaceId: wire.memory_workspace_id } : {}
10419
- };
10420
- }
10421
- //#endregion
10422
10607
  //#region ../../packages/oauth/src/utils.ts
10423
10608
  function isRecord$9(value) {
10424
10609
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -10943,24 +11128,35 @@ var FileTokenStorage = class {
10943
11128
  systemDirectory: options.windowsSystemDirectory
10944
11129
  }) : void 0;
10945
11130
  }
10946
- ensureDir() {
10947
- mkdirSync(this.dir, {
11131
+ async withWriter(operation) {
11132
+ const writer = await acquirePrivatePathWriter(this.dir, { waitForBusyMs: 5e3 });
11133
+ try {
11134
+ await writer.assertOwned();
11135
+ if (await canonicalPrivateWriterPath(this.dir) !== writer.canonicalPath) throw new PrivatePathWriterError("lost");
11136
+ await writer.assertOwned();
11137
+ return await operation(writer.canonicalPath);
11138
+ } finally {
11139
+ await writer.release();
11140
+ }
11141
+ }
11142
+ ensureDir(directory) {
11143
+ mkdirSync(directory, {
10948
11144
  recursive: true,
10949
11145
  mode: 448
10950
11146
  });
10951
11147
  if (this.platform === "win32") {
10952
- this.windowsAcl.hardenAndVerify(this.dir, "directory");
11148
+ this.windowsAcl.hardenAndVerify(directory, "directory");
10953
11149
  return;
10954
11150
  }
10955
- chmodSync(this.dir, 448);
11151
+ chmodSync(directory, 448);
10956
11152
  }
10957
- pathFor(name) {
11153
+ pathFor(name, directory = this.dir) {
10958
11154
  const safe = basename(name);
10959
11155
  const invalidWindowsName = this.platform === "win32" && (safe.includes(":") || safe.endsWith(".") || safe.endsWith(" "));
10960
11156
  if (safe.length === 0 || safe !== name || safe.startsWith(".") || invalidWindowsName) throw new Error(`Invalid token name: "${name}"`);
10961
- return join(this.dir, `${safe}.json`);
11157
+ return join(directory, `${safe}.json`);
10962
11158
  }
10963
- hardenExistingFileForLoad(file) {
11159
+ hardenExistingFileForLoad(file, directory) {
10964
11160
  try {
10965
11161
  statSync(file);
10966
11162
  } catch (error) {
@@ -10968,17 +11164,21 @@ var FileTokenStorage = class {
10968
11164
  throw error;
10969
11165
  }
10970
11166
  if (this.platform === "win32") {
10971
- this.windowsAcl.hardenAndVerify(this.dir, "directory");
11167
+ this.windowsAcl.hardenAndVerify(directory, "directory");
10972
11168
  this.windowsAcl.hardenAndVerify(file, "file");
10973
11169
  } else {
10974
- chmodSync(this.dir, 448);
11170
+ chmodSync(directory, 448);
10975
11171
  chmodSync(file, 384);
10976
11172
  }
10977
11173
  return true;
10978
11174
  }
10979
11175
  async load(name) {
10980
- const file = this.pathFor(name);
10981
- if (!this.hardenExistingFileForLoad(file)) return void 0;
11176
+ this.pathFor(name);
11177
+ return this.withWriter((directory) => this.loadOwned(name, directory));
11178
+ }
11179
+ loadOwned(name, directory) {
11180
+ const file = this.pathFor(name, directory);
11181
+ if (!this.hardenExistingFileForLoad(file, directory)) return void 0;
10982
11182
  let raw;
10983
11183
  try {
10984
11184
  raw = readFileSync(file, "utf-8");
@@ -10996,8 +11196,12 @@ var FileTokenStorage = class {
10996
11196
  return tokenFromWire(parsed);
10997
11197
  }
10998
11198
  async save(name, token) {
10999
- const target = this.pathFor(name);
11000
- this.ensureDir();
11199
+ this.pathFor(name);
11200
+ return this.withWriter((directory) => this.saveOwned(name, token, directory));
11201
+ }
11202
+ saveOwned(name, token, directory) {
11203
+ const target = this.pathFor(name, directory);
11204
+ this.ensureDir(directory);
11001
11205
  const tmp = `${target}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
11002
11206
  const rollback = `${target}.rollback.${process.pid}.${randomBytes(4).toString("hex")}`;
11003
11207
  const data = Buffer.from(`${JSON.stringify(tokenToWire(token), null, 2)}\n`, "utf-8");
@@ -11059,16 +11263,23 @@ var FileTokenStorage = class {
11059
11263
  }
11060
11264
  }
11061
11265
  async remove(name) {
11266
+ this.pathFor(name);
11267
+ return this.withWriter((directory) => this.removeOwned(name, directory));
11268
+ }
11269
+ removeOwned(name, directory) {
11062
11270
  try {
11063
- unlinkSync(this.pathFor(name));
11271
+ unlinkSync(this.pathFor(name, directory));
11064
11272
  } catch (error) {
11065
11273
  if (error.code !== "ENOENT") throw error;
11066
11274
  }
11067
11275
  }
11068
11276
  async list() {
11277
+ return this.withWriter((directory) => this.listOwned(directory));
11278
+ }
11279
+ listOwned(directory) {
11069
11280
  let entries;
11070
11281
  try {
11071
- entries = readdirSync(this.dir);
11282
+ entries = readdirSync(directory);
11072
11283
  } catch {
11073
11284
  return [];
11074
11285
  }
@@ -12932,7 +13143,9 @@ var OAuthManager = class {
12932
13143
  async login(options = {}) {
12933
13144
  const deadlineAt = this.now() + Math.ceil(this.deviceCodeTimeoutMs / 1e3);
12934
13145
  while (true) {
13146
+ this.throwIfAborted(options.signal);
12935
13147
  const auth = await this.requestImpl(this.config);
13148
+ this.throwIfAborted(options.signal);
12936
13149
  await options.onDeviceCode?.(auth);
12937
13150
  let currentInterval = Math.max(auth.interval, 1);
12938
13151
  let deviceExpired = false;
@@ -12940,9 +13153,11 @@ var OAuthManager = class {
12940
13153
  this.throwIfAborted(options.signal);
12941
13154
  if (this.now() >= deadlineAt) throw new DeviceCodeTimeoutError(`Device authorization timed out after ${Math.ceil(this.deviceCodeTimeoutMs / 1e3)}s`);
12942
13155
  const result = await this.pollImpl(this.config, auth.deviceCode);
13156
+ this.throwIfAborted(options.signal);
12943
13157
  if (result.kind === "success") {
12944
13158
  const release = await this.acquireRefreshLock();
12945
13159
  try {
13160
+ this.throwIfAborted(options.signal);
12946
13161
  await this.storage.save(this.config.name, result.token);
12947
13162
  } finally {
12948
13163
  await release();
@@ -14783,6 +14998,43 @@ async function refreshProviderModels(host, options = {}) {
14783
14998
  };
14784
14999
  }
14785
15000
  //#endregion
15001
+ //#region ../../packages/agent-core/src/session/writer-lease.ts
15002
+ const canonicalWriterPath = canonicalPrivateWriterPath;
15003
+ function sessionError(error) {
15004
+ if (!(error instanceof PrivatePathWriterError)) return error;
15005
+ const [code, message] = {
15006
+ busy: [ErrorCodes.SESSION_WRITER_BUSY, "This session is already open for writing"],
15007
+ lost: [ErrorCodes.SESSION_WRITER_LOST, "Session writer ownership was lost"],
15008
+ unavailable: [ErrorCodes.SESSION_WRITER_UNAVAILABLE, "Session writer ownership could not be verified"]
15009
+ }[error.failure];
15010
+ return new BlunError(code, message, { cause: error.cause });
15011
+ }
15012
+ /** Keep the Core API and diagnostics while sharing the native writer protocol. */
15013
+ async function acquireSessionWriter(sessionDirectory, options = {}) {
15014
+ try {
15015
+ const lease = await acquirePrivatePathWriter(sessionDirectory, options);
15016
+ let releasing;
15017
+ return {
15018
+ lockDirectory: lease.lockDirectory,
15019
+ async assertOwned() {
15020
+ try {
15021
+ await lease.assertOwned();
15022
+ } catch (error) {
15023
+ throw sessionError(error);
15024
+ }
15025
+ },
15026
+ release() {
15027
+ releasing ??= lease.release().catch((error) => {
15028
+ throw sessionError(error);
15029
+ });
15030
+ return releasing;
15031
+ }
15032
+ };
15033
+ } catch (error) {
15034
+ throw sessionError(error);
15035
+ }
15036
+ }
15037
+ //#endregion
14786
15038
  //#region ../../packages/agent-core/src/utils/fs.ts
14787
15039
  /**
14788
15040
  * Low-level POSIX durability primitives.
@@ -14945,178 +15197,110 @@ async function atomicWriteWithMode(filePath, content, syncOverride, renameOverri
14945
15197
  }
14946
15198
  }
14947
15199
  //#endregion
14948
- //#region ../../packages/agent-core/src/config/resolve.ts
14949
- const TRUE_BOOLEAN_ENV_VALUES = new Set([
14950
- "1",
14951
- "true",
14952
- "yes",
14953
- "on"
14954
- ]);
14955
- const FALSE_BOOLEAN_ENV_VALUES = new Set([
14956
- "0",
14957
- "false",
14958
- "no",
14959
- "off"
14960
- ]);
14961
- function resolveConfigValue(input) {
14962
- return input.parseEnv(input.env?.[input.envKey]) ?? input.configValue ?? input.defaultValue;
14963
- }
14964
- function parseBooleanEnv(value) {
14965
- const normalized = value?.trim().toLowerCase();
14966
- if (normalized === void 0 || normalized.length === 0) return void 0;
14967
- if (TRUE_BOOLEAN_ENV_VALUES.has(normalized)) return true;
14968
- if (FALSE_BOOLEAN_ENV_VALUES.has(normalized)) return false;
14969
- }
14970
- /**
14971
- * Parse a floating-point environment value (e.g. `BLUN_MODEL_TEMPERATURE`).
14972
- * Returns `undefined` when unset/blank; throws `BlunError(CONFIG_INVALID)` on a
14973
- * non-numeric value so a typo fails fast like the other `BLUN_MODEL_*` vars.
14974
- * No range validation — callers pass values the upstream API accepts.
14975
- */
14976
- function parseFloatEnv(value, varName) {
14977
- const trimmed = value?.trim();
14978
- if (trimmed === void 0 || trimmed.length === 0) return void 0;
14979
- const parsed = Number(trimmed);
14980
- if (!Number.isFinite(parsed)) throw new BlunError(ErrorCodes.CONFIG_INVALID, `${varName} must be a number, got "${value}".`);
14981
- return parsed;
14982
- }
14983
- //#endregion
14984
- //#region ../../packages/agent-core/src/config/env-model.ts
14985
- /** Reserved keys for the env-driven synthetic provider / model alias. */
14986
- const ENV_MODEL_PROVIDER_KEY = "__blun_env__";
14987
- const ENV_MODEL_ALIAS_KEY = "__blun_env_model__";
14988
- const DEFAULT_BASE_URL = "https://api.blun.ai/v1";
14989
- /** Default context window (256K) used when BLUN_MODEL_MAX_CONTEXT_SIZE is unset. */
14990
- const DEFAULT_MAX_CONTEXT_SIZE = 262144;
14991
- /** Default capabilities when BLUN_MODEL_CAPABILITIES is unset (blun models support both). */
14992
- const DEFAULT_CAPABILITIES = ["image_in", "thinking"];
14993
- function trimmed(value) {
14994
- const t = value?.trim();
14995
- return t === void 0 || t.length === 0 ? void 0 : t;
14996
- }
14997
- function fail(message) {
14998
- throw new BlunError(ErrorCodes.CONFIG_INVALID, message);
14999
- }
15000
- function parsePositiveInt$1(raw, varName) {
15001
- if (!/^\d+$/.test(raw) || Number(raw) <= 0) fail(`${varName} must be a positive integer, got "${raw}".`);
15002
- return Number(raw);
15200
+ //#region ../../packages/agent-core/src/config/write-ownership.ts
15201
+ const writeScope = new AsyncLocalStorage();
15202
+ async function canonicalConfigPath(filePath, options) {
15203
+ const absolute = resolve(filePath);
15204
+ if (await mkdir(dirname(absolute), {
15205
+ recursive: true,
15206
+ mode: 448
15207
+ }) !== void 0) await hardenPrivatePath(options === void 0 ? dirname(absolute) : dirname$3(filePath), "directory", options);
15208
+ return canonicalWriterPath(absolute);
15003
15209
  }
15004
- function parseProviderType(raw) {
15005
- if (raw === void 0) return "blun";
15006
- if (raw.toLowerCase() !== "blun") fail(`BLUN_MODEL_PROVIDER_TYPE must be "blun", got "${raw}".`);
15007
- return "blun";
15210
+ function samePath(a, b) {
15211
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
15008
15212
  }
15009
- function parseCapabilities(raw) {
15010
- if (raw === void 0) return void 0;
15011
- const caps = raw.split(",").map((c) => c.trim().toLowerCase()).filter((c) => c.length > 0);
15012
- return caps.length === 0 ? void 0 : caps;
15213
+ function configWriterError(error) {
15214
+ if (error instanceof BlunError && error.code.startsWith("session.writer_")) return new BlunError(ErrorCodes.CONFIG_WRITE_UNAVAILABLE, "Configuration writer ownership could not be verified", { cause: error });
15215
+ return error;
15013
15216
  }
15014
- function parseBooleanVar(raw, varName) {
15015
- const value = trimmed(raw);
15016
- if (value === void 0) return void 0;
15017
- const parsed = parseBooleanEnv(value);
15018
- if (parsed === void 0) fail(`${varName} must be a boolean (true/false/1/0/yes/no/on/off), got "${raw}".`);
15019
- return parsed;
15217
+ async function acquireConfigWriter(filePath) {
15218
+ try {
15219
+ return await acquireSessionWriter(filePath, { waitForBusyMs: 5e3 });
15220
+ } catch (error) {
15221
+ if (!(error instanceof BlunError) || error.code !== ErrorCodes.SESSION_WRITER_BUSY) throw error;
15222
+ throw new BlunError(ErrorCodes.CONFIG_WRITE_BUSY, "Another configuration change is still being saved", { cause: error });
15223
+ }
15020
15224
  }
15021
- /**
15022
- * When `BLUN_MODEL_NAME` is set, synthesize one provider + one model alias from
15023
- * the `BLUN_MODEL_*` environment variables and make it the default model.
15024
- * Returns the config unchanged when the trigger variable is absent.
15025
- *
15026
- * IMPORTANT: the synthesized provider/model/default_model exist ONLY in the
15027
- * in-memory runtime config and must never be serialized back to config.toml.
15028
- * Two layers enforce this: write paths read the raw config via `readConfigFile`,
15029
- * and `writeConfigFile` strips the reserved entries via `stripEnvModelConfig` as
15030
- * a final guard against patch round-trips (getConfig -> setConfig).
15031
- */
15032
- function applyEnvModelConfig(config, env = process.env) {
15033
- const envRaw = (suffix) => env[`BLUN_MODEL_${suffix}`];
15034
- const model = trimmed(envRaw("NAME"));
15035
- if (model === void 0) return config;
15036
- const apiKey = trimmed(envRaw("API_KEY"));
15037
- if (apiKey === void 0) fail("BLUN_MODEL_NAME is set but BLUN_MODEL_API_KEY is missing.");
15038
- const maxContextRaw = trimmed(envRaw("MAX_CONTEXT_SIZE"));
15039
- const maxContextSize = maxContextRaw === void 0 ? DEFAULT_MAX_CONTEXT_SIZE : parsePositiveInt$1(maxContextRaw, "BLUN_MODEL_MAX_CONTEXT_SIZE");
15040
- const type = parseProviderType(trimmed(envRaw("PROVIDER_TYPE")));
15041
- const baseUrl = trimmed(envRaw("BASE_URL")) ?? DEFAULT_BASE_URL;
15042
- const provider = {
15043
- type,
15044
- apiKey,
15045
- ...baseUrl !== void 0 ? { baseUrl } : {}
15046
- };
15047
- const maxOutputRaw = trimmed(envRaw("MAX_OUTPUT_SIZE"));
15048
- const maxOutputSize = maxOutputRaw !== void 0 ? parsePositiveInt$1(maxOutputRaw, "BLUN_MODEL_MAX_OUTPUT_SIZE") : void 0;
15049
- const capabilities = parseCapabilities(envRaw("CAPABILITIES")) ?? DEFAULT_CAPABILITIES;
15050
- const displayName = trimmed(envRaw("DISPLAY_NAME"));
15051
- const reasoningKey = trimmed(envRaw("REASONING_KEY"));
15052
- const adaptiveThinking = parseBooleanVar(envRaw("ADAPTIVE_THINKING"), "BLUN_MODEL_ADAPTIVE_THINKING");
15053
- const alias = {
15054
- provider: ENV_MODEL_PROVIDER_KEY,
15055
- model,
15056
- maxContextSize,
15057
- capabilities,
15058
- ...displayName !== void 0 ? { displayName } : {},
15059
- ...maxOutputSize !== void 0 ? { maxOutputSize } : {},
15060
- ...reasoningKey !== void 0 ? { reasoningKey } : {},
15061
- ...adaptiveThinking !== void 0 ? { adaptiveThinking } : {}
15062
- };
15063
- const thinkingEffort = trimmed(env["BLUN_MODEL_THINKING_EFFORT"]);
15064
- const thinking = thinkingEffort !== void 0 ? {
15065
- ...config.thinking,
15066
- effort: thinkingEffort
15067
- } : config.thinking;
15068
- return validateConfig({
15069
- ...config,
15070
- providers: {
15071
- ...config.providers,
15072
- [ENV_MODEL_PROVIDER_KEY]: provider
15073
- },
15074
- models: {
15075
- ...config.models,
15076
- [ENV_MODEL_ALIAS_KEY]: alias
15077
- },
15078
- defaultModel: ENV_MODEL_ALIAS_KEY,
15079
- ...thinking !== void 0 ? { thinking } : {}
15080
- });
15225
+ /** The public atomic writer inherits only its enclosing mutation's native claim. */
15226
+ async function withConfigAtomicWrite(filePath, operation, options) {
15227
+ const inherited = writeScope.getStore();
15228
+ if (inherited?.active) {
15229
+ const canonical = await canonicalConfigPath(filePath, options);
15230
+ if (samePath(canonical, inherited.path)) {
15231
+ await inherited.writer.assertOwned();
15232
+ if (inherited.active) return operation(canonical);
15233
+ }
15234
+ }
15235
+ return withConfigWriter(filePath, (canonical) => operation(canonical), options);
15081
15236
  }
15082
- /**
15083
- * Remove the env-synthesized provider/model before a config is persisted to
15084
- * disk. Mirror of {@link applyEnvModelConfig}: that injects the reserved entries
15085
- * into the in-memory runtime config; this guarantees they never reach
15086
- * config.toml — including via a `getConfig` -> `setConfig` patch round-trip,
15087
- * where the runtime config (carrying the env provider and its shell API key)
15088
- * would otherwise be merged back and written out. Every env-injected top-level
15089
- * field (default_model, thinking) is restored to its on-disk
15090
- * value from `config.raw` rather than erased, so real values already in
15091
- * config.toml survive the round-trip.
15092
- */
15093
- function stripEnvModelConfig(config) {
15094
- const hasProvider = ENV_MODEL_PROVIDER_KEY in config.providers;
15095
- const hasModel = config.models !== void 0 && "__blun_env_model__" in config.models;
15096
- const defaultIsEnv = config.defaultModel === ENV_MODEL_ALIAS_KEY;
15097
- if (!hasProvider && !hasModel && !defaultIsEnv) return config;
15098
- const providers = { ...config.providers };
15099
- delete providers[ENV_MODEL_PROVIDER_KEY];
15100
- let models = config.models;
15101
- if (models !== void 0 && "__blun_env_model__" in models) {
15102
- models = { ...models };
15103
- delete models[ENV_MODEL_ALIAS_KEY];
15237
+ async function withConfigWriter(filePath, operation, options) {
15238
+ const canonical = await canonicalConfigPath(filePath, options);
15239
+ try {
15240
+ const writer = await acquireConfigWriter(canonical);
15241
+ const scope = {
15242
+ path: canonical,
15243
+ writer,
15244
+ active: true
15245
+ };
15246
+ try {
15247
+ return await writeScope.run(scope, () => operation(canonical, writer));
15248
+ } finally {
15249
+ scope.active = false;
15250
+ await writer.release();
15251
+ }
15252
+ } catch (error) {
15253
+ throw configWriterError(error);
15104
15254
  }
15105
- return {
15106
- ...config,
15107
- providers,
15108
- ...models !== void 0 ? { models } : {},
15109
- ...defaultIsEnv ? { defaultModel: rawDefaultModel(config) } : {},
15110
- thinking: rawThinking(config)
15111
- };
15112
15255
  }
15113
- function rawDefaultModel(config) {
15114
- const raw = config.raw?.["default_model"];
15115
- return typeof raw === "string" ? raw : void 0;
15256
+ async function configBytes(file) {
15257
+ try {
15258
+ return await readFile(file);
15259
+ } catch (error) {
15260
+ if (error.code === "ENOENT") return void 0;
15261
+ throw error;
15262
+ }
15116
15263
  }
15117
- function rawThinking(config) {
15118
- const raw = config.raw?.["thinking"];
15119
- return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : void 0;
15264
+ /** Claim the existing writer immediately; busy/unknown ownership is not idle. */
15265
+ async function tryReserveConfigIdleAdmission(filePath) {
15266
+ let writer;
15267
+ let retained = false;
15268
+ try {
15269
+ const canonical = await canonicalConfigPath(filePath);
15270
+ writer = await acquireSessionWriter(canonical);
15271
+ const owned = writer;
15272
+ await owned.assertOwned();
15273
+ const snapshot = await configBytes(canonical);
15274
+ let live = true;
15275
+ let releasing;
15276
+ const isValid = async () => {
15277
+ if (!live) return false;
15278
+ try {
15279
+ await owned.assertOwned();
15280
+ const current = await configBytes(canonical);
15281
+ const alias = await canonicalConfigPath(filePath);
15282
+ await owned.assertOwned();
15283
+ live = live && samePath(alias, canonical) && (snapshot === void 0 ? current === void 0 : current !== void 0 && snapshot.equals(current));
15284
+ } catch {
15285
+ live = false;
15286
+ }
15287
+ return live;
15288
+ };
15289
+ if (!await isValid()) return void 0;
15290
+ retained = true;
15291
+ return {
15292
+ isValid,
15293
+ release: () => {
15294
+ live = false;
15295
+ releasing ??= owned.release();
15296
+ return releasing;
15297
+ }
15298
+ };
15299
+ } catch {
15300
+ return;
15301
+ } finally {
15302
+ if (!retained && writer) await writer.release();
15303
+ }
15120
15304
  }
15121
15305
  //#endregion
15122
15306
  //#region ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
@@ -16027,32 +16211,35 @@ const DEFAULT_CONFIG_FILE_TEXT = `# BLUN runtime config (config.toml)
16027
16211
  # Login will populate managed BLUN provider and model entries.
16028
16212
  `;
16029
16213
  async function ensureConfigFile(filePath, privatePathOptions = {}) {
16030
- const configDirectory = dirname$3(filePath);
16031
- if (await mkdir(configDirectory, {
16032
- recursive: true,
16033
- mode: 448
16034
- }) !== void 0) await hardenPrivatePath(configDirectory, "directory", privatePathOptions);
16035
- let handle;
16036
- let created = false;
16037
- try {
16038
- handle = await open(filePath, "wx", 384);
16039
- created = true;
16040
- await hardenPrivatePath(filePath, "file", privatePathOptions);
16041
- await handle.writeFile(DEFAULT_CONFIG_FILE_TEXT, "utf-8");
16042
- } catch (error) {
16043
- if (isFileExistsError(error)) {
16044
- await hardenPrivatePath(filePath, "file", privatePathOptions);
16045
- return;
16046
- }
16047
- if (created) {
16048
- await handle?.close().catch(() => {});
16049
- handle = void 0;
16050
- await unlink(filePath).catch(() => {});
16214
+ return withConfigAtomicWrite(filePath, async (canonical) => {
16215
+ const target = normalize$3(resolve$2(filePath)) === normalize$3(canonical) ? filePath : canonical;
16216
+ const configDirectory = dirname$3(target);
16217
+ if (await mkdir(configDirectory, {
16218
+ recursive: true,
16219
+ mode: 448
16220
+ }) !== void 0) await hardenPrivatePath(configDirectory, "directory", privatePathOptions);
16221
+ let handle;
16222
+ let created = false;
16223
+ try {
16224
+ handle = await open(target, "wx", 384);
16225
+ created = true;
16226
+ await hardenPrivatePath(target, "file", privatePathOptions);
16227
+ await handle.writeFile(DEFAULT_CONFIG_FILE_TEXT, "utf-8");
16228
+ } catch (error) {
16229
+ if (isFileExistsError(error)) {
16230
+ await hardenPrivatePath(target, "file", privatePathOptions);
16231
+ return;
16232
+ }
16233
+ if (created) {
16234
+ await handle?.close().catch(() => {});
16235
+ handle = void 0;
16236
+ await unlink(target).catch(() => {});
16237
+ }
16238
+ throw error;
16239
+ } finally {
16240
+ await handle?.close();
16051
16241
  }
16052
- throw error;
16053
- } finally {
16054
- await handle?.close();
16055
- }
16242
+ }, privatePathOptions);
16056
16243
  }
16057
16244
  function readConfigFile(filePath) {
16058
16245
  if (!existsSync(filePath)) return getDefaultConfig();
@@ -16285,13 +16472,15 @@ function transformLoopControlData(data) {
16285
16472
  return out;
16286
16473
  }
16287
16474
  async function writeConfigFile(filePath, config) {
16288
- const validated = validateConfig(stripEnvModelConfig(config));
16289
- const configDirectory = dirname$3(filePath);
16290
- if (await mkdir(configDirectory, {
16291
- recursive: true,
16292
- mode: 448
16293
- }) !== void 0) await hardenPrivatePath(configDirectory, "directory");
16294
- await atomicWritePrivate(filePath, `${stringify$1(configToTomlData(validated))}\n`);
16475
+ return withConfigAtomicWrite(filePath, async (canonical) => {
16476
+ const validated = validateConfig(stripEnvModelConfig(config));
16477
+ const configDirectory = dirname$3(canonical);
16478
+ if (await mkdir(configDirectory, {
16479
+ recursive: true,
16480
+ mode: 448
16481
+ }) !== void 0) await hardenPrivatePath(configDirectory, "directory");
16482
+ await atomicWritePrivate(canonical, `${stringify$1(configToTomlData(validated))}\n`);
16483
+ });
16295
16484
  }
16296
16485
  function configToTomlData(config) {
16297
16486
  const out = cloneRecord$1(config.raw);
@@ -16485,54 +16674,26 @@ function isFileExistsError(error) {
16485
16674
  }
16486
16675
  //#endregion
16487
16676
  //#region ../../packages/agent-core/src/config/mutate.ts
16488
- async function canonicalConfigPath(filePath) {
16489
- const absolute = resolve(filePath);
16490
- if (await mkdir(dirname(absolute), {
16491
- recursive: true,
16492
- mode: 448
16493
- }) !== void 0) await hardenPrivatePath(dirname(absolute), "directory");
16494
- return canonicalWriterPath(absolute);
16495
- }
16496
- function configWriterError(error) {
16497
- if (error instanceof BlunError && error.code.startsWith("session.writer_")) return new BlunError(ErrorCodes.CONFIG_WRITE_UNAVAILABLE, "Configuration writer ownership could not be verified", { cause: error });
16498
- return error;
16499
- }
16500
- async function acquireConfigWriter(filePath) {
16501
- try {
16502
- return await acquireSessionWriter(filePath, { waitForBusyMs: 5e3 });
16503
- } catch (error) {
16504
- if (!(error instanceof BlunError) || error.code !== ErrorCodes.SESSION_WRITER_BUSY) throw error;
16505
- throw new BlunError(ErrorCodes.CONFIG_WRITE_BUSY, "Another configuration change is still being saved", { cause: error });
16506
- }
16507
- }
16508
16677
  /** Keep transforms synchronous; credential validation and discovery belong before this boundary. */
16509
16678
  async function mutateConfigFile(filePath, transform) {
16510
- const canonical = await canonicalConfigPath(filePath);
16511
- try {
16512
- const writer = await acquireConfigWriter(canonical);
16513
- try {
16679
+ return withConfigWriter(filePath, async (canonical, writer) => {
16680
+ await writer.assertOwned();
16681
+ const { config, result } = transform(readConfigFileForUpdate(canonical));
16682
+ for (let attempt = 0;; attempt += 1) {
16514
16683
  await writer.assertOwned();
16515
- const { config, result } = transform(readConfigFileForUpdate(canonical));
16516
- for (let attempt = 0;; attempt += 1) {
16517
- await writer.assertOwned();
16518
- try {
16519
- await writeConfigFile(canonical, config);
16520
- break;
16521
- } catch (error) {
16522
- const failure = error;
16523
- if (process.platform !== "win32" || attempt >= 9 || failure?.code !== "EPERM" || failure.syscall !== "rename" || typeof failure.dest !== "string" || resolve(failure.dest) !== canonical) throw error;
16524
- await new Promise((resolve) => {
16525
- setTimeout(resolve, 25);
16526
- });
16527
- }
16684
+ try {
16685
+ await writeConfigFile(canonical, config);
16686
+ break;
16687
+ } catch (error) {
16688
+ const failure = error;
16689
+ if (process.platform !== "win32" || attempt >= 9 || failure?.code !== "EPERM" || failure.syscall !== "rename" || typeof failure.dest !== "string" || resolve(failure.dest) !== canonical) throw error;
16690
+ await new Promise((resolve) => {
16691
+ setTimeout(resolve, 25);
16692
+ });
16528
16693
  }
16529
- return result;
16530
- } finally {
16531
- await writer.release();
16532
16694
  }
16533
- } catch (error) {
16534
- throw configWriterError(error);
16535
- }
16695
+ return result;
16696
+ });
16536
16697
  }
16537
16698
  //#endregion
16538
16699
  //#region ../../packages/agent-core/src/config/model.ts
@@ -29294,6 +29455,156 @@ function resettableTimeoutOutcome(initialMs, outcome) {
29294
29455
  });
29295
29456
  }
29296
29457
  //#endregion
29458
+ //#region ../../packages/agent-core/src/utils/quiescence.ts
29459
+ /** A reservation can release only itself; permanent shutdown always wins. */
29460
+ var AdmissionGate = class {
29461
+ state = null;
29462
+ get closed() {
29463
+ return this.state !== null;
29464
+ }
29465
+ get reserved() {
29466
+ return this.state !== null && this.state !== "closed";
29467
+ }
29468
+ close() {
29469
+ this.state = "closed";
29470
+ }
29471
+ tryReserve() {
29472
+ if (this.closed) return void 0;
29473
+ const reservation = {};
29474
+ this.state = reservation;
29475
+ const release = () => {
29476
+ if (this.state === reservation) this.state = null;
29477
+ };
29478
+ return Object.assign(release, {
29479
+ isValid: () => this.state === reservation,
29480
+ commit: () => {
29481
+ if (this.state !== reservation) return false;
29482
+ this.state = "closed";
29483
+ return true;
29484
+ }
29485
+ });
29486
+ }
29487
+ };
29488
+ /** Validate the whole native owner tree before closing any of its held gates. */
29489
+ var IdleAdmissionReservation = class {
29490
+ #live = true;
29491
+ #released = false;
29492
+ #groups;
29493
+ #barriers;
29494
+ #validate;
29495
+ #current;
29496
+ constructor(releases, validate, current, children = []) {
29497
+ this.#validate = validate;
29498
+ this.#current = current;
29499
+ this.#groups = [this, ...children.flatMap((child) => child.#groups)];
29500
+ this.#barriers = [...releases.map((release) => ({
29501
+ release,
29502
+ isValid: release.isValid,
29503
+ commit: release.commit
29504
+ })), ...children.flatMap((child) => child.#barriers)];
29505
+ }
29506
+ isValid = () => this.#check();
29507
+ #check() {
29508
+ if (!this.#live) return false;
29509
+ try {
29510
+ this.#live = this.#groups.every((group) => group.#live && group.#validate()) && this.#groups.every((group) => group.#current()) && this.#barriers.every((barrier) => barrier.isValid());
29511
+ } catch {
29512
+ this.#live = false;
29513
+ }
29514
+ return this.#live;
29515
+ }
29516
+ commit = () => {
29517
+ if (!this.#check()) return false;
29518
+ for (const barrier of this.#barriers) barrier.commit();
29519
+ for (const group of this.#groups) group.#live = false;
29520
+ return true;
29521
+ };
29522
+ release = () => {
29523
+ if (this.#released) return;
29524
+ for (const group of this.#groups) {
29525
+ group.#live = false;
29526
+ group.#released = true;
29527
+ }
29528
+ for (const barrier of [...this.#barriers].reverse()) barrier.release();
29529
+ };
29530
+ };
29531
+ var QuiescenceDeadline = class {
29532
+ deadlineAt;
29533
+ monotonicEnd;
29534
+ constructor(deadlineAt) {
29535
+ this.deadlineAt = deadlineAt;
29536
+ const budget = deadlineAt - Date.now();
29537
+ if (!Number.isSafeInteger(deadlineAt) || deadlineAt < 0 || budget > 2147483647) throw new Error("Quiescence deadline must be a safe timestamp within the timer range");
29538
+ this.monotonicEnd = performance.now() + Math.max(0, budget);
29539
+ }
29540
+ get remainingMs() {
29541
+ return Math.max(0, Math.min(this.deadlineAt - Date.now(), this.monotonicEnd - performance.now()));
29542
+ }
29543
+ get expired() {
29544
+ return this.remainingMs <= 0;
29545
+ }
29546
+ async wait(completion) {
29547
+ let settled = false;
29548
+ const done = completion.then(() => {
29549
+ settled = true;
29550
+ }, () => {
29551
+ settled = true;
29552
+ });
29553
+ while (!settled && !this.expired) {
29554
+ let timer;
29555
+ try {
29556
+ await Promise.race([done, new Promise((resolve) => {
29557
+ timer = setTimeout(resolve, Math.ceil(this.remainingMs));
29558
+ })]);
29559
+ } finally {
29560
+ if (timer !== void 0) clearTimeout(timer);
29561
+ }
29562
+ }
29563
+ }
29564
+ };
29565
+ /** Keeps settlement identity separate from presentation/cancellation state. */
29566
+ var ExecutionLedger = class {
29567
+ entries = /* @__PURE__ */ new Map();
29568
+ revision = 0;
29569
+ get version() {
29570
+ return this.revision;
29571
+ }
29572
+ /** Register before invoking callbacks that can reenter the owner. */
29573
+ run(detail, operation) {
29574
+ this.revision++;
29575
+ const key = {};
29576
+ let resolve;
29577
+ const settled = new Promise((yes) => {
29578
+ resolve = yes;
29579
+ });
29580
+ this.entries.set(key, {
29581
+ detail,
29582
+ settled
29583
+ });
29584
+ const finish = () => {
29585
+ this.entries.delete(key);
29586
+ resolve();
29587
+ };
29588
+ try {
29589
+ const completion = operation();
29590
+ completion.then(finish, finish);
29591
+ return completion;
29592
+ } catch (error) {
29593
+ finish();
29594
+ throw error;
29595
+ }
29596
+ }
29597
+ track(detail, completion) {
29598
+ this.run(detail, () => completion);
29599
+ }
29600
+ get pending() {
29601
+ return Array.from(this.entries.values(), (entry) => entry.detail);
29602
+ }
29603
+ async drain() {
29604
+ while (this.entries.size > 0) await Promise.all(Array.from(this.entries.values(), (entry) => entry.settled));
29605
+ }
29606
+ };
29607
+ //#endregion
29297
29608
  //#region ../../packages/agent-core/src/agent/context/notification-xml.ts
29298
29609
  /**
29299
29610
  * Notification XML rendering — produces the chat-history injection text
@@ -30012,6 +30323,9 @@ var FileSystemAgentRecordPersistence = class {
30012
30323
  this.filePath = filePath;
30013
30324
  this.options = options;
30014
30325
  }
30326
+ get hasPendingWork() {
30327
+ return this.error !== void 0 || this.flushPromise !== void 0 || this.shouldClear || this.pendingRecords.length > 0;
30328
+ }
30015
30329
  async *read() {
30016
30330
  yield* this.readFrom(0, 0);
30017
30331
  }
@@ -182196,11 +182510,24 @@ function invalidateMessageTokenEstimate(message) {
182196
182510
  * and supersedes this value. Used to keep `tokenCountWithPending`
182197
182511
  * monotonic between LLM round-trips without paying for a tokenizer.
182198
182512
  */
182199
- function estimateTokens(text) {
182513
+ /** With stopAfter, an overflowing result is only a lower bound for comparison. */
182514
+ function estimateTokens(text, stopAfter = Infinity) {
182200
182515
  let asciiCount = 0;
182201
182516
  let nonAsciiCount = 0;
182202
- for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
182203
- else nonAsciiCount++;
182517
+ let asciiBudget = Math.floor(stopAfter) * 4;
182518
+ for (let index = 0; index < text.length; index++) {
182519
+ const code = text.charCodeAt(index);
182520
+ if (code <= 127) asciiCount++;
182521
+ else {
182522
+ nonAsciiCount++;
182523
+ asciiBudget -= 4;
182524
+ if (code >= 55296 && code <= 56319) {
182525
+ const next = text.charCodeAt(index + 1);
182526
+ if (next >= 56320 && next <= 57343) index++;
182527
+ }
182528
+ }
182529
+ if (asciiCount > asciiBudget) return Math.ceil(asciiCount / 4) + nonAsciiCount;
182530
+ }
182204
182531
  return Math.ceil(asciiCount / 4) + nonAsciiCount;
182205
182532
  }
182206
182533
  function estimateTokensForMessages(messages) {
@@ -182212,9 +182539,9 @@ function estimateTokensForMessages(messages) {
182212
182539
  function estimateTokensForTools(tools, stopAfter = Infinity) {
182213
182540
  let total = 0;
182214
182541
  for (const tool of tools) {
182215
- total += estimateTokens(tool.name);
182216
- total += estimateTokens(tool.description);
182217
- total += estimateTokens(JSON.stringify(tool.parameters));
182542
+ total += estimateTokens(tool.name, stopAfter - total);
182543
+ total += estimateTokens(tool.description, stopAfter - total);
182544
+ total += estimateTokens(JSON.stringify(tool.parameters), stopAfter - total);
182218
182545
  if (total > stopAfter) return total;
182219
182546
  }
182220
182547
  return total;
@@ -183217,6 +183544,8 @@ function restoreAgentRecord(agent, input) {
183217
183544
  var AgentRecords = class {
183218
183545
  agent;
183219
183546
  persistence;
183547
+ operations = new ExecutionLedger();
183548
+ revision = 0;
183220
183549
  _restoring = null;
183221
183550
  metadataInitialized = false;
183222
183551
  persistedProtocolVersion;
@@ -183247,6 +183576,14 @@ var AgentRecords = class {
183247
183576
  protocolVersion: this.persistedProtocolVersion
183248
183577
  };
183249
183578
  }
183579
+ get version() {
183580
+ return this.revision;
183581
+ }
183582
+ get hasPendingWork() {
183583
+ if (this._restoring !== null || this.operations.pending.length > 0 || this.checkpointWrite !== void 0) return true;
183584
+ if (this.persistence === void 0 || this.persistence instanceof InMemoryAgentRecordPersistence) return false;
183585
+ return !(this.persistence instanceof FileSystemAgentRecordPersistence) || this.persistence.hasPendingWork;
183586
+ }
183250
183587
  get replaySource() {
183251
183588
  if (this.persistence === void 0) return void 0;
183252
183589
  const source = this.persistence instanceof FileSystemAgentRecordPersistence ? new FileSystemAgentRecordPersistence(this.persistence.filePath) : this.persistence;
@@ -183254,6 +183591,7 @@ var AgentRecords = class {
183254
183591
  }
183255
183592
  logRecord(record) {
183256
183593
  if (this._restoring !== null) return;
183594
+ this.revision++;
183257
183595
  const stamped = record.time !== void 0 ? record : {
183258
183596
  ...record,
183259
183597
  time: Date.now()
@@ -183276,6 +183614,7 @@ var AgentRecords = class {
183276
183614
  this.persistence?.append(stamped);
183277
183615
  }
183278
183616
  restore(record) {
183617
+ this.revision++;
183279
183618
  this.captureStateRecord(record);
183280
183619
  this._restoring = { time: record.time ?? Date.now() };
183281
183620
  try {
@@ -183285,7 +183624,11 @@ var AgentRecords = class {
183285
183624
  this._restoring = null;
183286
183625
  }
183287
183626
  }
183288
- async replay(options = {}) {
183627
+ replay(options = {}) {
183628
+ this.revision++;
183629
+ return this.operations.run("replay", () => this.replayInner(options));
183630
+ }
183631
+ async replayInner(options) {
183289
183632
  if (!this.persistence) throw new Error("No persistence provided for AgentRecords");
183290
183633
  this.persistedProtocolVersion = void 0;
183291
183634
  this.resumeCheckpointRestored = false;
@@ -183335,10 +183678,14 @@ var AgentRecords = class {
183335
183678
  if (completed && this.agent.blobStore !== void 0) for (const msg of this.agent.context.history) await this.agent.blobStore.rehydrateParts(msg.content);
183336
183679
  return { warning };
183337
183680
  }
183338
- async flush() {
183339
- await this.persistence?.flush();
183681
+ flush() {
183682
+ return this.operations.run("flush", () => this.persistence?.flush() ?? Promise.resolve());
183340
183683
  }
183341
- async writeResumeCheckpoint() {
183684
+ writeResumeCheckpoint() {
183685
+ this.revision++;
183686
+ return this.operations.run("checkpoint", () => this.writeResumeCheckpointInner());
183687
+ }
183688
+ async writeResumeCheckpointInner() {
183342
183689
  await this.checkpointWrite;
183343
183690
  const write = this.persistResumeCheckpoint();
183344
183691
  this.checkpointWrite = write;
@@ -183857,63 +184204,6 @@ var SubagentResumeReference = class SubagentResumeReference {
183857
184204
  }
183858
184205
  };
183859
184206
  //#endregion
183860
- //#region ../../packages/agent-core/src/utils/quiescence.ts
183861
- var QuiescenceDeadline = class {
183862
- deadlineAt;
183863
- monotonicEnd;
183864
- constructor(deadlineAt) {
183865
- this.deadlineAt = deadlineAt;
183866
- const budget = deadlineAt - Date.now();
183867
- if (!Number.isSafeInteger(deadlineAt) || deadlineAt < 0 || budget > 2147483647) throw new Error("Quiescence deadline must be a safe timestamp within the timer range");
183868
- this.monotonicEnd = performance.now() + Math.max(0, budget);
183869
- }
183870
- get remainingMs() {
183871
- return Math.max(0, Math.min(this.deadlineAt - Date.now(), this.monotonicEnd - performance.now()));
183872
- }
183873
- get expired() {
183874
- return this.remainingMs <= 0;
183875
- }
183876
- async wait(completion) {
183877
- let settled = false;
183878
- const done = completion.then(() => {
183879
- settled = true;
183880
- }, () => {
183881
- settled = true;
183882
- });
183883
- while (!settled && !this.expired) {
183884
- let timer;
183885
- try {
183886
- await Promise.race([done, new Promise((resolve) => {
183887
- timer = setTimeout(resolve, Math.ceil(this.remainingMs));
183888
- })]);
183889
- } finally {
183890
- if (timer !== void 0) clearTimeout(timer);
183891
- }
183892
- }
183893
- }
183894
- };
183895
- /** Keeps settlement identity separate from presentation/cancellation state. */
183896
- var ExecutionLedger = class {
183897
- entries = /* @__PURE__ */ new Map();
183898
- track(detail, completion) {
183899
- const key = {};
183900
- const finish = () => {
183901
- this.entries.delete(key);
183902
- };
183903
- const settled = completion.then(finish, finish);
183904
- this.entries.set(key, {
183905
- detail,
183906
- settled
183907
- });
183908
- }
183909
- get pending() {
183910
- return Array.from(this.entries.values(), (entry) => entry.detail);
183911
- }
183912
- async drain() {
183913
- while (this.entries.size > 0) await Promise.all(Array.from(this.entries.values(), (entry) => entry.settled));
183914
- }
183915
- };
183916
- //#endregion
183917
184207
  //#region ../../packages/agent-core/src/session/subagent-batch.ts
183918
184208
  const INITIAL_LAUNCH_LIMIT = 5;
183919
184209
  const INITIAL_LAUNCH_INTERVAL_MS = 700;
@@ -184356,14 +184646,20 @@ IMPORTANT:
184356
184646
  - If you do not know the answer, say so directly.
184357
184647
  `;
184358
184648
  const nativeExecutionCompletions = /* @__PURE__ */ new WeakMap();
184649
+ const preparedExecutionCompletions = /* @__PURE__ */ new WeakMap();
184359
184650
  /** Only handles produced by this native host can attest their actual execution end. */
184360
184651
  function getSubagentExecutionCompletion(handle) {
184361
184652
  return nativeExecutionCompletions.get(handle);
184362
184653
  }
184654
+ /** Preparation identity, not a caller's display promise, owns the execution receipt. */
184655
+ function getPreparedSubagentExecutionCompletion(prepared, owner, handle) {
184656
+ const native = preparedExecutionCompletions.get(prepared);
184657
+ return native?.owner === owner && native.agentId === handle.agentId && native.profileName === handle.profileName && handle.resumed ? native.completion : void 0;
184658
+ }
184363
184659
  var SessionSubagentHost = class {
184364
184660
  session;
184365
184661
  ownerAgentId;
184366
- admissionClosed = false;
184662
+ admission = new AdmissionGate();
184367
184663
  operations = new ExecutionLedger();
184368
184664
  childExecutions = new ExecutionLedger();
184369
184665
  preparedDisposers = /* @__PURE__ */ new Set();
@@ -184374,7 +184670,17 @@ var SessionSubagentHost = class {
184374
184670
  this.ownerAgentId = ownerAgentId;
184375
184671
  }
184376
184672
  closeAdmission() {
184377
- this.admissionClosed = true;
184673
+ this.admission.close();
184674
+ }
184675
+ get hasPendingWork() {
184676
+ return this.operations.pending.length > 0 || this.childExecutions.pending.length > 0 || this.preparedDisposers.size > 0 || this.resumeReservations.size > 0 || this.activeChildren.size > 0;
184677
+ }
184678
+ belongsTo(session, agentId) {
184679
+ return this.session === session && this.ownerAgentId === agentId;
184680
+ }
184681
+ tryReserveIdleAdmission() {
184682
+ if (this.hasPendingWork) return void 0;
184683
+ return this.admission.tryReserve();
184378
184684
  }
184379
184685
  async quiesce(options) {
184380
184686
  const deadline = new QuiescenceDeadline(options.deadlineAt);
@@ -184396,13 +184702,11 @@ var SessionSubagentHost = class {
184396
184702
  await this.childExecutions.drain();
184397
184703
  }
184398
184704
  assertAdmissionOpen() {
184399
- if (this.admissionClosed) throw new Error("Subagent admission is closed for quiescence");
184705
+ if (this.admission.closed) throw new Error("Subagent admission is closed for quiescence");
184400
184706
  }
184401
184707
  runOperation(id, operation) {
184402
184708
  this.assertAdmissionOpen();
184403
- const completion = operation();
184404
- this.operations.track(id, completion);
184405
- return completion;
184709
+ return this.operations.run(id, operation);
184406
184710
  }
184407
184711
  nativeHandle(handle) {
184408
184712
  const child = this.session.getReadyAgent(handle.agentId);
@@ -184531,6 +184835,10 @@ var SessionSubagentHost = class {
184531
184835
  const token = {};
184532
184836
  this.resumeReservations.set(agentId, token);
184533
184837
  let retainedWire;
184838
+ const execution = createControlledPromise();
184839
+ execution.catch(() => {});
184840
+ let startedExecution;
184841
+ let consumed = false;
184534
184842
  const releaseReservation = () => {
184535
184843
  if (this.resumeReservations.get(agentId) === token) this.resumeReservations.delete(agentId);
184536
184844
  };
@@ -184545,6 +184853,8 @@ var SessionSubagentHost = class {
184545
184853
  }).then(() => {
184546
184854
  if (retainedWire === closing) retainedWire = void 0;
184547
184855
  if (retainedWire === void 0) this.preparedDisposers.delete(dispose);
184856
+ if (!consumed) execution.resolve();
184857
+ else if (startedExecution !== void 0) execution.resolve(startedExecution);
184548
184858
  });
184549
184859
  disposal = completion;
184550
184860
  completion.then(() => {
@@ -184573,18 +184883,16 @@ var SessionSubagentHost = class {
184573
184883
  if (source === void 0 || resolve(source.path) !== wire || source.protocolVersion !== "1.4" || child.context.history.length === 0 || child.config.systemPrompt.length === 0) throw new Error("Preserved subagent context could not be verified.");
184574
184884
  await sourceReference.refreshAfterReplay();
184575
184885
  this.assertAdmissionOpen();
184576
- const reference = createHash("sha256").update(JSON.stringify([
184577
- sessionRoot,
184578
- this.ownerAgentId,
184579
- agentId,
184580
- home,
184581
- sourceReference.digest
184582
- ])).digest("hex");
184583
- let consumed = false;
184584
- return {
184886
+ const prepared = {
184585
184887
  agentId,
184586
184888
  profileName,
184587
- reference,
184889
+ reference: createHash("sha256").update(JSON.stringify([
184890
+ sessionRoot,
184891
+ this.ownerAgentId,
184892
+ agentId,
184893
+ home,
184894
+ sourceReference.digest
184895
+ ])).digest("hex"),
184588
184896
  dispose,
184589
184897
  start: async (options) => this.runOperation("prepared-start", async () => {
184590
184898
  await sourceReference.assertUnchanged();
@@ -184611,15 +184919,24 @@ var SessionSubagentHost = class {
184611
184919
  throw error;
184612
184920
  }
184613
184921
  });
184614
- return this.nativeHandle({
184922
+ const handle = this.nativeHandle({
184615
184923
  agentId,
184616
184924
  profileName,
184617
184925
  resumed: true,
184618
184926
  completion,
184619
184927
  update: (message) => this.updateActiveRun(agentId, completion, message)
184620
184928
  });
184929
+ startedExecution = getSubagentExecutionCompletion(handle);
184930
+ return handle;
184621
184931
  })
184622
184932
  };
184933
+ preparedExecutionCompletions.set(prepared, {
184934
+ owner: this,
184935
+ agentId,
184936
+ profileName,
184937
+ completion: execution
184938
+ });
184939
+ return prepared;
184623
184940
  } catch (error) {
184624
184941
  await dispose();
184625
184942
  throw error;
@@ -184692,7 +185009,7 @@ var SessionSubagentHost = class {
184692
185009
  async getProfileName(agentId) {
184693
185010
  const metadata = this.session.metadata.agents[agentId];
184694
185011
  if (metadata?.type !== "sub" || metadata.parentAgentId !== this.ownerAgentId) return;
184695
- return (await this.session.ensureAgentResumed(agentId)).config.profileName;
185012
+ return this.runOperation("profile-query", async () => (await this.session.ensureAgentResumed(agentId)).config.profileName);
184696
185013
  }
184697
185014
  getSwarmItem(agentId) {
184698
185015
  const metadata = this.session.metadata.agents[agentId];
@@ -184910,6 +185227,7 @@ var AgentBackgroundTask = class {
184910
185227
  return this.handle.update(message);
184911
185228
  }
184912
185229
  get executionCompletion() {
185230
+ if (this.options.preparedExecution !== void 0) return getPreparedSubagentExecutionCompletion(this.options.preparedExecution, this.subagentHost, this.handle);
184913
185231
  return getSubagentExecutionCompletion(this.handle);
184914
185232
  }
184915
185233
  async start(sink) {
@@ -185172,6 +185490,7 @@ var TaskResumeRuntime = class {
185172
185490
  return active.update(message);
185173
185491
  }
185174
185492
  }, input.description, host, controller, {
185493
+ preparedExecution: prepared,
185175
185494
  resumed: true,
185176
185495
  replacesTaskId: source.taskId,
185177
185496
  resumeRootTaskId: intent.resumeRootTaskId,
@@ -185344,7 +185663,8 @@ var BackgroundManager = class {
185344
185663
  agent;
185345
185664
  persistence;
185346
185665
  tasks = /* @__PURE__ */ new Map();
185347
- admissionClosed = false;
185666
+ admission = new AdmissionGate();
185667
+ executions = new ExecutionLedger();
185348
185668
  unregisteredProcesses = /* @__PURE__ */ new Map();
185349
185669
  reservedTaskIds = /* @__PURE__ */ new Set();
185350
185670
  taskResume = new TaskResumeRuntime(this);
@@ -185363,7 +185683,7 @@ var BackgroundManager = class {
185363
185683
  fireTerminalEffects(entry) {
185364
185684
  if (!this.isDetached(entry)) return;
185365
185685
  const info = this.toInfo(entry);
185366
- this.notifyBackgroundTask(info).catch(() => {});
185686
+ this.executions.run(`notification:${entry.taskId}`, () => this.notifyBackgroundTask(info)).catch(() => {});
185367
185687
  this.emitTaskTerminated(info);
185368
185688
  }
185369
185689
  emitTaskStarted(info) {
@@ -185393,7 +185713,10 @@ var BackgroundManager = class {
185393
185713
  throw new Error("Too many background tasks are already running.");
185394
185714
  }
185395
185715
  assertAdmissionOpen() {
185396
- if (this.admissionClosed) throw new Error("Background manager is quiescing; task admission is closed.");
185716
+ if (this.admission.closed) throw new Error("Background manager is quiescing; task admission is closed.");
185717
+ }
185718
+ assertMutationAdmissionOpen() {
185719
+ if (this.admission.reserved) throw new Error("Background task admission is reserved.");
185397
185720
  }
185398
185721
  activeBackgroundAdmissionCount() {
185399
185722
  let count = 0;
@@ -185445,7 +185768,8 @@ var BackgroundManager = class {
185445
185768
  outputPersistStarted: detached
185446
185769
  };
185447
185770
  this.tasks.set(taskId, entry);
185448
- this.runTaskLifecycle(entry);
185771
+ if (task instanceof AgentBackgroundTask && task.executionCompletion !== void 0) this.executions.track(`child:${taskId}`, task.executionCompletion);
185772
+ this.executions.run(`lifecycle:${taskId}`, () => this.runTaskLifecycle(entry));
185449
185773
  if (this.isDetached(entry)) {
185450
185774
  this.persistLive(entry);
185451
185775
  this.emitTaskStarted(this.toInfo(entry));
@@ -185475,7 +185799,7 @@ var BackgroundManager = class {
185475
185799
  }
185476
185800
  resumeAgentTask(input, host) {
185477
185801
  this.assertAdmissionOpen();
185478
- return this.taskResume.resume(input, host);
185802
+ return this.executions.run(`resume:${input.taskId}`, () => this.taskResume.resume(input, host));
185479
185803
  }
185480
185804
  async awaitTaskPersisted(taskId) {
185481
185805
  const entry = this.tasks.get(taskId);
@@ -185491,6 +185815,7 @@ var BackgroundManager = class {
185491
185815
  return this.ghosts.get(taskId);
185492
185816
  }
185493
185817
  updateAgentTask(taskId, message) {
185818
+ this.assertMutationAdmissionOpen();
185494
185819
  const text = message.trim();
185495
185820
  if (!text) throw new Error("Update message must not be empty.");
185496
185821
  const entry = this.tasks.get(taskId);
@@ -185577,12 +185902,16 @@ var BackgroundManager = class {
185577
185902
  return output;
185578
185903
  }
185579
185904
  async suppressTerminalNotification(taskId) {
185580
- const entry = this.tasks.get(taskId);
185581
- if (entry === void 0 || entry.terminalNotificationSuppressed === true) return;
185582
- entry.terminalNotificationSuppressed = true;
185583
- await this.persistLive(entry);
185905
+ this.assertMutationAdmissionOpen();
185906
+ return this.executions.run(`suppression:${taskId}`, async () => {
185907
+ const entry = this.tasks.get(taskId);
185908
+ if (entry === void 0 || entry.terminalNotificationSuppressed === true) return;
185909
+ entry.terminalNotificationSuppressed = true;
185910
+ await this.persistLive(entry);
185911
+ });
185584
185912
  }
185585
185913
  detach(taskId) {
185914
+ this.assertMutationAdmissionOpen();
185586
185915
  const entry = this.tasks.get(taskId);
185587
185916
  if (entry === void 0) return this.ghosts.get(taskId);
185588
185917
  if (TERMINAL_STATUSES.has(entry.status)) return this.toInfo(entry);
@@ -185600,12 +185929,14 @@ var BackgroundManager = class {
185600
185929
  return this.toInfo(entry);
185601
185930
  }
185602
185931
  persistOutput(taskId) {
185932
+ this.assertMutationAdmissionOpen();
185603
185933
  const entry = this.tasks.get(taskId);
185604
185934
  if (entry === void 0) return;
185605
185935
  this.startOutputPersist(entry);
185606
185936
  }
185607
185937
  /** Stop a running task. SIGTERM → 5s grace → SIGKILL. */
185608
185938
  async stop(taskId, reason) {
185939
+ this.assertMutationAdmissionOpen();
185609
185940
  const entry = this.tasks.get(taskId);
185610
185941
  if (!entry) return void 0;
185611
185942
  const trimmedReason = reason?.trim();
@@ -185621,12 +185952,27 @@ var BackgroundManager = class {
185621
185952
  return this.toInfo(entry);
185622
185953
  }
185623
185954
  async stopAll(reason) {
185955
+ this.assertMutationAdmissionOpen();
185624
185956
  const taskIds = Array.from(this.tasks.keys());
185625
185957
  return (await Promise.all(taskIds.map((taskId) => this.stop(taskId, reason)))).filter((info) => info !== void 0);
185626
185958
  }
185627
185959
  /** Explicit manager-only shutdown. Does not change ordinary stop/close policy. */
185628
185960
  closeAdmission() {
185629
- this.admissionClosed = true;
185961
+ this.admission.close();
185962
+ }
185963
+ get hasPendingWork() {
185964
+ if (this.reservedTaskIds.size > 0 || this.executions.pending.length > 0 || this.pendingRegisteredTaskExecutions.length > 0 || this.pendingUnregisteredProcessExecutions.length > 0) return true;
185965
+ for (const entry of this.tasks.values()) {
185966
+ if (!TERMINAL_STATUSES.has(entry.status)) return true;
185967
+ if (entry.task.kind === "process" && (!(entry.task instanceof ProcessBackgroundTask) || entry.task.proc.exitCode === null)) return true;
185968
+ if (entry.task.kind === "agent" && (!(entry.task instanceof AgentBackgroundTask) || entry.task.executionCompletion === void 0)) return true;
185969
+ }
185970
+ for (const ghost of this.ghosts.values()) if (!TERMINAL_STATUSES.has(ghost.status)) return true;
185971
+ return false;
185972
+ }
185973
+ tryReserveIdleAdmission() {
185974
+ if (this.hasPendingWork) return void 0;
185975
+ return this.admission.tryReserve();
185630
185976
  }
185631
185977
  /** Observe an existing process after task admission failed; this admits no new work. */
185632
185978
  trackUnregisteredProcess(proc) {
@@ -185700,7 +186046,7 @@ var BackgroundManager = class {
185700
186046
  await Promise.all([entry.persistWriteQueue, entry.outputWriteQueue]);
185701
186047
  observed.add(entry.taskId);
185702
186048
  } catch {}
185703
- })).then(() => unregistered).then(() => false);
186049
+ })).then(() => unregistered).then(() => this.executions.drain()).then(() => false);
185704
186050
  const remainingBudget = Math.min(deadlineAt - Date.now(), monotonicDeadline - performance.now());
185705
186051
  let timedOut = remainingBudget <= 0;
185706
186052
  if (!timedOut) {
@@ -185786,14 +186132,17 @@ var BackgroundManager = class {
185786
186132
  * calls overwrite the ghost map.
185787
186133
  */
185788
186134
  async loadFromDisk() {
185789
- const persistence = this.persistence;
185790
- if (persistence === void 0) return;
185791
- this.ghosts.clear();
185792
- const persisted = await persistence.listTasks();
185793
- for (const t of persisted) {
185794
- if (this.tasks.has(t.taskId)) continue;
185795
- this.ghosts.set(t.taskId, t);
185796
- }
186135
+ this.assertAdmissionOpen();
186136
+ return this.executions.run("load", async () => {
186137
+ const persistence = this.persistence;
186138
+ if (persistence === void 0) return;
186139
+ this.ghosts.clear();
186140
+ const persisted = await persistence.listTasks();
186141
+ for (const t of persisted) {
186142
+ if (this.tasks.has(t.taskId)) continue;
186143
+ this.ghosts.set(t.taskId, t);
186144
+ }
186145
+ });
185797
186146
  }
185798
186147
  /**
185799
186148
  * Reconcile loaded ghost tasks. Any ghost with status `running` is
@@ -185818,9 +186167,12 @@ var BackgroundManager = class {
185818
186167
  return lostInfo;
185819
186168
  }
185820
186169
  async reconcile() {
185821
- const lostInfo = await this.markLoadedTasksLost();
185822
- for (const info of lostInfo) this.emitTaskTerminated(info);
185823
- await this.restoreBackgroundTaskNotifications();
186170
+ this.assertAdmissionOpen();
186171
+ return this.executions.run("reconcile", async () => {
186172
+ const lostInfo = await this.markLoadedTasksLost();
186173
+ for (const info of lostInfo) this.emitTaskTerminated(info);
186174
+ await this.restoreBackgroundTaskNotifications();
186175
+ });
185824
186176
  }
185825
186177
  /**
185826
186178
  * Persist the current state of a live ManagedTask. Called from
@@ -185831,6 +186183,7 @@ var BackgroundManager = class {
185831
186183
  if (persistence === void 0) return Promise.resolve();
185832
186184
  const info = this.toInfo(entry);
185833
186185
  entry.persistWriteQueue = entry.persistWriteQueue.then(() => persistence.writeTask(info, true)).catch(() => {});
186186
+ this.executions.track(`record:${entry.taskId}`, entry.persistWriteQueue);
185834
186187
  return entry.persistWriteQueue;
185835
186188
  }
185836
186189
  appendOutput(entry, chunk) {
@@ -185861,6 +186214,7 @@ var BackgroundManager = class {
185861
186214
  const persistence = this.persistence;
185862
186215
  if (persistence === void 0) return;
185863
186216
  entry.outputWriteQueue = entry.outputWriteQueue.then(() => persistence.appendTaskOutput(entry.taskId, chunk)).catch(() => {});
186217
+ this.executions.track(`output:${entry.taskId}`, entry.outputWriteQueue);
185864
186218
  }
185865
186219
  /**
185866
186220
  * Begin persisting `output.log` for a task that buffered while foreground.
@@ -185945,6 +186299,7 @@ var BackgroundManager = class {
185945
186299
  });
185946
186300
  }
185947
186301
  markDeliveredNotification(origin) {
186302
+ this.assertMutationAdmissionOpen();
185948
186303
  this.deliveredNotificationKeys.add(notificationKey(origin));
185949
186304
  }
185950
186305
  isTerminalNotificationSuppressed(taskId) {
@@ -230534,7 +230889,7 @@ function selectCompactionHistory(history) {
230534
230889
  }
230535
230890
  var FullCompaction = class {
230536
230891
  agent;
230537
- admissionClosed = false;
230892
+ admission = new AdmissionGate();
230538
230893
  nextExecutionId = 0;
230539
230894
  executions = new ExecutionLedger();
230540
230895
  executingControllers = /* @__PURE__ */ new Set();
@@ -230566,7 +230921,14 @@ var FullCompaction = class {
230566
230921
  }
230567
230922
  /** Explicit host shutdown only; normal cancellation remains restartable. */
230568
230923
  closeAdmission() {
230569
- this.admissionClosed = true;
230924
+ this.admission.close();
230925
+ }
230926
+ get hasPendingWork() {
230927
+ return this.compacting !== null || this.executions.pending.length > 0 || Boolean(this.agent.records.restoring);
230928
+ }
230929
+ tryReserveIdleAdmission() {
230930
+ if (this.hasPendingWork) return void 0;
230931
+ return this.admission.tryReserve();
230570
230932
  }
230571
230933
  get pendingExecutions() {
230572
230934
  return this.executions.pending.map((entry) => ({ ...entry }));
@@ -230591,7 +230953,7 @@ var FullCompaction = class {
230591
230953
  return this.executions.drain();
230592
230954
  }
230593
230955
  assertAdmissionOpen() {
230594
- if (this.admissionClosed) throw new Error("Compaction admission is closed for quiescence");
230956
+ if (this.admission.closed) throw new Error("Compaction admission is closed for quiescence");
230595
230957
  }
230596
230958
  get isSessionCompactionStopped() {
230597
230959
  return this.sessionCompactionFailures >= MAX_SESSION_COMPACTION_FAILURES;
@@ -230608,11 +230970,14 @@ var FullCompaction = class {
230608
230970
  return budget !== void 0 && Number.isFinite(budget) && budget > 0 ? Math.floor(budget) : void 0;
230609
230971
  }
230610
230972
  proactiveCompactionStatus(context = {}) {
230611
- const usedTokens = this.estimateCurrentRequestTokens(context);
230973
+ const messages = context.messages ?? this.agent.context.messages;
230974
+ const usedTokens = this.estimateCurrentRequestTokens({
230975
+ ...context,
230976
+ messages
230977
+ });
230612
230978
  const budget = this.getCompactionBudgetTokens();
230613
230979
  const thresholdTokens = budget === void 0 ? void 0 : Math.max(1, Math.floor(budget * .9));
230614
230980
  let reason = "eligible";
230615
- const messages = context.messages ?? this.agent.context.messages;
230616
230981
  const projected = context.additionalMessages === void 0 ? messages : [...messages, ...context.additionalMessages];
230617
230982
  const historyBudget = this.requestHistoryByteBudget(projected);
230618
230983
  const historyThresholdReached = historyBudget !== void 0 && historyBudget.usedHistoryBytes >= historyBudget.thresholdHistoryBytes;
@@ -230719,41 +231084,43 @@ var FullCompaction = class {
230719
231084
  }
230720
231085
  if (this.agent.context.history.length === 0) throw new BlunError(ErrorCodes.COMPACTION_UNABLE, "No messages to compact in current history.");
230721
231086
  if (data.source === "manual" && this.agent.turn.hasActiveTurn) throw new BlunError(ErrorCodes.COMPACTION_UNABLE, "Cannot compact while a turn is active. Wait for it to finish, then retry.");
230722
- this.agent.records.logRecord({
230723
- type: "full_compaction.begin",
230724
- ...data
230725
- });
230726
- this.agent.emitEvent({
230727
- type: "compaction.started",
230728
- trigger: data.source,
230729
- instruction: data.instruction
230730
- });
230731
- this.assertAdmissionOpen();
230732
- const abortController = new AbortController();
230733
231087
  const executionId = ++this.nextExecutionId;
230734
- let resolve;
230735
- let reject;
230736
- const promise = new Promise((yes, no) => {
230737
- resolve = yes;
230738
- reject = no;
230739
- });
230740
- this.executions.track({
231088
+ this.executions.run({
230741
231089
  kind: "worker",
230742
231090
  id: executionId,
230743
231091
  source: data.source
230744
- }, promise);
230745
- this.executingControllers.add(abortController);
230746
- this.compacting = {
230747
- abortController,
230748
- promise,
230749
- blockedByTurn: false
230750
- };
230751
- this.compactionWorker(abortController.signal, data, executionId).then(() => {
230752
- this.executingControllers.delete(abortController);
230753
- resolve();
230754
- }, (error) => {
230755
- this.executingControllers.delete(abortController);
230756
- reject(error);
231092
+ }, () => {
231093
+ this.agent.records.logRecord({
231094
+ type: "full_compaction.begin",
231095
+ ...data
231096
+ });
231097
+ this.agent.emitEvent({
231098
+ type: "compaction.started",
231099
+ trigger: data.source,
231100
+ instruction: data.instruction
231101
+ });
231102
+ this.assertAdmissionOpen();
231103
+ const abortController = new AbortController();
231104
+ let resolve;
231105
+ let reject;
231106
+ const promise = new Promise((yes, no) => {
231107
+ resolve = yes;
231108
+ reject = no;
231109
+ });
231110
+ this.executingControllers.add(abortController);
231111
+ this.compacting = {
231112
+ abortController,
231113
+ promise,
231114
+ blockedByTurn: false
231115
+ };
231116
+ this.compactionWorker(abortController.signal, data, executionId).then(() => {
231117
+ this.executingControllers.delete(abortController);
231118
+ resolve();
231119
+ }, (error) => {
231120
+ this.executingControllers.delete(abortController);
231121
+ reject(error);
231122
+ });
231123
+ return promise;
230757
231124
  });
230758
231125
  }
230759
231126
  cancel() {
@@ -230791,6 +231158,7 @@ var FullCompaction = class {
230791
231158
  this.consecutiveOverflowCompactions += 1;
230792
231159
  const maxAttempts = this.strategy.maxOverflowCompactionAttempts;
230793
231160
  if (this.consecutiveOverflowCompactions > maxAttempts) throw new BlunError(ErrorCodes.CONTEXT_OVERFLOW, `Compaction failed to bring the context under the model window after ${String(maxAttempts)} attempts.`, { cause: error instanceof Error ? error : void 0 });
231161
+ this.diagnoseAutomaticCompaction("provider-overflow", this.tokenCountWithPending);
230794
231162
  if (!this.beginAutoCompaction() && !this.compacting) throw error;
230795
231163
  await this.block(signal);
230796
231164
  }
@@ -230805,7 +231173,7 @@ var FullCompaction = class {
230805
231173
  const historyThresholdReached = historyBudget !== void 0 && historyBudget.usedHistoryBytes >= historyBudget.thresholdHistoryBytes;
230806
231174
  const requestBudget = this.requestBodyByteBudget(projectedMessages, requestContext);
230807
231175
  const proactive = (requestBudget !== void 0 && requestBudget.usedRequestBytesEstimate >= requestBudget.thresholdRequestBytes || historyThresholdReached || !this.isCompacting && budget !== void 0 && Number.isFinite(requestTokens) && requestTokens >= Math.max(1, Math.floor(budget * .9))) && this.hasShrinkableHistory(projectedMessages);
230808
- if (this.checkAutoCompaction(true, requestTokens, projectedMessages, proactive) && (proactive || this.strategy.shouldBlock(requestTokens))) await this.block(signal);
231176
+ if (this.checkAutoCompaction(true, requestTokens, projectedMessages, proactive, "request-preflight", requestContext) && (proactive || this.strategy.shouldBlock(requestTokens))) await this.block(signal);
230809
231177
  signal.throwIfAborted();
230810
231178
  const compacted = this.agent.context.history !== historyBefore;
230811
231179
  let rebuiltMessages = compacted ? await requestContext.rebuildMessages?.() : void 0;
@@ -230835,20 +231203,47 @@ var FullCompaction = class {
230835
231203
  this.proactiveCompactionRequested = false;
230836
231204
  signal.throwIfAborted();
230837
231205
  const status = this.proactiveCompactionStatus();
230838
- if (this.isCompacting || status.eligible && this.checkAutoCompaction(true, status.usedTokens, this.agent.context.history, true)) await this.block(signal);
231206
+ if (this.isCompacting || status.eligible && this.checkAutoCompaction(true, status.usedTokens, this.agent.context.history, true, "tool-request")) await this.block(signal);
230839
231207
  return;
230840
231208
  }
230841
231209
  if (this.strategy.checkAfterStep) this.checkAutoCompaction(false);
230842
231210
  }
230843
- checkAutoCompaction(throwOnLimit = true, usedSize = this.tokenCountWithPending, messages = this.agent.context.history, proactive = false) {
231211
+ checkAutoCompaction(throwOnLimit = true, usedSize = this.tokenCountWithPending, messages = this.agent.context.history, proactive = false, entryPoint = "background", requestContext = {}) {
230844
231212
  if (this.compacting) return true;
230845
231213
  const modelAlias = this.agent.activeResponderModel ?? this.agent.config.modelAlias;
230846
231214
  const maxContextTokens = this.getEffectiveMaxContextTokens();
230847
231215
  if (this.lastCompactedState !== null && this.tokenCountWithPending <= this.lastCompactedState.tokenCount && modelAlias === this.lastCompactedState.modelAlias && maxContextTokens === this.lastCompactedState.maxContextTokens) return false;
230848
231216
  if (!proactive && !this.strategy.shouldCompact(usedSize)) return false;
230849
231217
  if (this.requiresShrinkableHistory && !this.hasShrinkableHistory(messages)) return false;
231218
+ this.diagnoseAutomaticCompaction(entryPoint, usedSize, requestContext, proactive);
230850
231219
  return this.beginAutoCompaction(throwOnLimit);
230851
231220
  }
231221
+ diagnoseAutomaticCompaction(entryPoint, usedTokens, context = {}, proactive = false) {
231222
+ if (this.agent.type !== "main" || process.env["BLUN_DIAG_PRE_REQUEST_PHASES"] !== "1") return;
231223
+ try {
231224
+ const messages = context.messages ?? this.agent.context.messages;
231225
+ const projected = context.additionalMessages === void 0 ? messages : [...messages, ...context.additionalMessages];
231226
+ const budget = this.getCompactionBudgetTokens();
231227
+ const thresholdTokens = budget === void 0 ? void 0 : proactive ? Math.max(1, Math.floor(budget * .9)) : budget;
231228
+ const historyBudget = this.requestHistoryByteBudget(projected);
231229
+ const requestBudget = this.requestBodyByteBudget(projected, context);
231230
+ this.agent.log.info("compaction automatic decision", {
231231
+ entryPoint,
231232
+ turnId: this.agent.turn.currentId,
231233
+ usedTokens,
231234
+ storedTokens: this.tokenCountWithPending,
231235
+ projectedTokens: this.estimateProjectedRequestTokens(context),
231236
+ thresholdTokens,
231237
+ ...historyBudget,
231238
+ ...requestBudget,
231239
+ tokenThresholdReached: thresholdTokens !== void 0 && Number.isFinite(usedTokens) && usedTokens >= thresholdTokens,
231240
+ historyByteThresholdReached: historyBudget !== void 0 && historyBudget.usedHistoryBytes >= historyBudget.thresholdHistoryBytes,
231241
+ requestByteThresholdReached: requestBudget !== void 0 && requestBudget.usedRequestBytesEstimate >= requestBudget.thresholdRequestBytes,
231242
+ proactive,
231243
+ alreadyCompacting: this.isCompacting
231244
+ });
231245
+ } catch {}
231246
+ }
230852
231247
  hasShrinkableHistory(messages) {
230853
231248
  if (messages.some((message) => message.role === "assistant" || message.role === "tool")) return true;
230854
231249
  return estimateTokensForMessages(messages.filter(isRealUserInput)) > COMPACT_USER_MESSAGE_MAX_TOKENS;
@@ -232329,6 +232724,10 @@ const ID_REGEX = /^[0-9a-f]{8}$/;
232329
232724
  */
232330
232725
  const MAX_ID_ATTEMPTS = 8;
232331
232726
  var SessionCronStore = class {
232727
+ assertMutationOpen;
232728
+ constructor(assertMutationOpen = () => {}) {
232729
+ this.assertMutationOpen = assertMutationOpen;
232730
+ }
232332
232731
  /**
232333
232732
  * Backing map. `Map` preserves insertion order in JS, which we rely on
232334
232733
  * for {@link list}.
@@ -232343,6 +232742,7 @@ var SessionCronStore = class {
232343
232742
  * practice; surfacing it as a throw beats silently retrying forever.
232344
232743
  */
232345
232744
  add(init, nowMs) {
232745
+ this.assertMutationOpen();
232346
232746
  const id = this.generateUniqueId();
232347
232747
  const task = {
232348
232748
  ...init,
@@ -232365,6 +232765,7 @@ var SessionCronStore = class {
232365
232765
  * semantics should clear the store first.
232366
232766
  */
232367
232767
  adopt(task) {
232768
+ this.assertMutationOpen();
232368
232769
  this.tasks.set(task.id, task);
232369
232770
  }
232370
232771
  /**
@@ -232377,6 +232778,7 @@ var SessionCronStore = class {
232377
232778
  * scheduler's fire and the cursor callback.
232378
232779
  */
232379
232780
  markFired(id, lastFiredAt) {
232781
+ this.assertMutationOpen();
232380
232782
  const existing = this.tasks.get(id);
232381
232783
  if (existing === void 0) return void 0;
232382
232784
  const updated = {
@@ -232387,6 +232789,7 @@ var SessionCronStore = class {
232387
232789
  return updated;
232388
232790
  }
232389
232791
  update(id, patch) {
232792
+ this.assertMutationOpen();
232390
232793
  const existing = this.tasks.get(id);
232391
232794
  if (existing === void 0) return void 0;
232392
232795
  const updated = {
@@ -232415,12 +232818,14 @@ var SessionCronStore = class {
232415
232818
  * order.
232416
232819
  */
232417
232820
  remove(ids) {
232821
+ this.assertMutationOpen();
232418
232822
  const removed = [];
232419
232823
  for (const id of ids) if (this.tasks.delete(id)) removed.push(id);
232420
232824
  return removed;
232421
232825
  }
232422
232826
  /** Empty the store. Convenience for tests / shutdown. */
232423
232827
  clear() {
232828
+ this.assertMutationOpen();
232424
232829
  this.tasks.clear();
232425
232830
  }
232426
232831
  generateUniqueId() {
@@ -233038,7 +233443,8 @@ var CronManager = class {
233038
233443
  * across repeated start() calls.
233039
233444
  */
233040
233445
  started = false;
233041
- admissionClosed = false;
233446
+ admission = new AdmissionGate();
233447
+ operations = new ExecutionLedger();
233042
233448
  lastUnavailableWarning = null;
233043
233449
  /**
233044
233450
  * Reference to the bound SIGUSR1 listener while the manager is
@@ -233068,7 +233474,7 @@ var CronManager = class {
233068
233474
  activeRuns = /* @__PURE__ */ new Map();
233069
233475
  constructor(agent, opts = {}) {
233070
233476
  this.agent = agent;
233071
- this.store = new SessionCronStore();
233477
+ this.store = new SessionCronStore(() => this.assertAdmissionOpen());
233072
233478
  this.clocks = opts.clocks ?? resolveClockSources(process.env["BLUN_CRON_CLOCK"]) ?? SYSTEM_CLOCKS;
233073
233479
  this.persistStore = agent.homedir === void 0 ? void 0 : createCronPersistStore(agent.homedir);
233074
233480
  this.runStore = new CronRunStore(agent.homedir, () => {
@@ -233077,10 +233483,10 @@ var CronManager = class {
233077
233483
  this.scheduler = createCronScheduler({
233078
233484
  clocks: this.clocks,
233079
233485
  source: () => this.store.list(),
233080
- isIdle: () => !agent.turn.hasActiveTurn,
233486
+ isIdle: () => !this.admission.closed && !agent.turn.hasActiveTurn,
233081
233487
  isKilled: () => process.env["BLUN_DISABLE_CRON"] === "1",
233082
233488
  onFire: (task, ctx) => {
233083
- this.handleFire(task, ctx);
233489
+ this.runSynchronous("fire", () => this.handleFire(task, ctx));
233084
233490
  },
233085
233491
  removeOneShot: (id) => {
233086
233492
  this.removeTasks([id]);
@@ -233107,16 +233513,20 @@ var CronManager = class {
233107
233513
  * the agent loop.
233108
233514
  */
233109
233515
  addTask(init) {
233110
- const task = this.store.add(init, this.clocks.wallNow());
233111
- this.persistEnqueue(task.id, () => this.persistStore.write(task.id, task));
233112
- return task;
233516
+ return this.runSynchronous("add", () => {
233517
+ const task = this.store.add(init, this.clocks.wallNow());
233518
+ this.persistEnqueue(task.id, () => this.persistStore.write(task.id, task));
233519
+ return task;
233520
+ });
233113
233521
  }
233114
233522
  /** Schedule one recurring loop per session; one-shot reminders remain independent. */
233115
233523
  scheduleTask(init) {
233116
- const replaced = init.recurring === false ? [] : this.store.list().filter((task) => task.recurring !== false).map((task) => task.id);
233117
- const task = this.addTask(init);
233118
- for (const id of this.removeTasks(replaced)) this.emitDeleted(id);
233119
- return task;
233524
+ return this.runSynchronous("schedule", () => {
233525
+ const replaced = init.recurring === false ? [] : this.store.list().filter((task) => task.recurring !== false).map((task) => task.id);
233526
+ const task = this.addTask(init);
233527
+ for (const id of this.removeTasks(replaced)) this.emitDeleted(id);
233528
+ return task;
233529
+ });
233120
233530
  }
233121
233531
  /**
233122
233532
  * Remove a batch of tasks from the in-memory store and mirror each
@@ -233131,21 +233541,25 @@ var CronManager = class {
233131
233541
  * `list()` shape-guard pass.
233132
233542
  */
233133
233543
  removeTasks(ids) {
233134
- const removed = this.store.remove(ids);
233135
- for (const id of removed) {
233136
- this.persistEnqueue(id, () => this.persistStore.remove(id));
233137
- for (const run of this.runStore.list()) if (run.receipt.jobId === id) this.runStore.put({
233138
- ...run.receipt,
233139
- jobDeleted: true
233140
- }, run.finalMessage);
233141
- }
233142
- return removed;
233544
+ return this.runSynchronous("remove", () => {
233545
+ const removed = this.store.remove(ids);
233546
+ for (const id of removed) {
233547
+ this.persistEnqueue(id, () => this.persistStore.remove(id));
233548
+ for (const run of this.runStore.list()) if (run.receipt.jobId === id) this.runStore.put({
233549
+ ...run.receipt,
233550
+ jobDeleted: true
233551
+ }, run.finalMessage);
233552
+ }
233553
+ return removed;
233554
+ });
233143
233555
  }
233144
233556
  updateTask(id, patch) {
233145
- const updated = this.store.update(id, patch);
233146
- if (updated === void 0 || this.persistStore === void 0) return updated;
233147
- this.persistEnqueue(id, () => this.persistStore.write(id, updated));
233148
- return updated;
233557
+ return this.runSynchronous("update", () => {
233558
+ const updated = this.store.update(id, patch);
233559
+ if (updated === void 0 || this.persistStore === void 0) return updated;
233560
+ this.persistEnqueue(id, () => this.persistStore.write(id, updated));
233561
+ return updated;
233562
+ });
233149
233563
  }
233150
233564
  /**
233151
233565
  * Persist the scheduler's `lastFiredAt` cursor for a recurring task
@@ -233178,12 +233592,15 @@ var CronManager = class {
233178
233592
  * input, so a regenerated value would corrupt both.
233179
233593
  */
233180
233594
  async loadFromDisk() {
233181
- if (this.persistStore === void 0) return;
233182
- const tasks = await this.persistStore.list();
233183
- this.store.clear();
233184
- for (const task of tasks) this.store.adopt(task);
233185
- this.activeRuns.clear();
233186
- this.runStore.load(this.clocks.wallNow());
233595
+ this.assertAdmissionOpen();
233596
+ return this.operations.run("load", async () => {
233597
+ if (this.persistStore === void 0) return;
233598
+ const tasks = await this.persistStore.list();
233599
+ this.store.clear();
233600
+ for (const task of tasks) this.store.adopt(task);
233601
+ this.activeRuns.clear();
233602
+ this.runStore.load(this.clocks.wallNow());
233603
+ });
233187
233604
  }
233188
233605
  listRuns(options = {}) {
233189
233606
  const limit = Math.min(100, Math.max(1, Math.trunc(options.limit ?? 20)));
@@ -233271,7 +233688,7 @@ var CronManager = class {
233271
233688
  }));
233272
233689
  }
233273
233690
  publishRun(run) {
233274
- this.agent.emitEvent({
233691
+ this.emitOwnedEvent({
233275
233692
  type: "cron.run.updated",
233276
233693
  runId: run.receipt.runId,
233277
233694
  jobId: run.receipt.jobId,
@@ -233279,6 +233696,30 @@ var CronManager = class {
233279
233696
  durable: run.durable
233280
233697
  });
233281
233698
  }
233699
+ emitOwnedEvent(event) {
233700
+ this.operations.run("event", () => Promise.resolve(this.agent.emitEvent(event))).catch((error) => {
233701
+ this.agent.log?.warn?.("cron event publication failed", error);
233702
+ });
233703
+ }
233704
+ assertAdmissionOpen() {
233705
+ if (this.admission.closed) throw new Error("Cron admission is closed for quiescence");
233706
+ }
233707
+ runSynchronous(id, operation) {
233708
+ this.assertAdmissionOpen();
233709
+ let result;
233710
+ this.operations.run(id, () => {
233711
+ result = operation();
233712
+ return Promise.resolve();
233713
+ });
233714
+ return result;
233715
+ }
233716
+ get hasPendingWork() {
233717
+ return this.operations.pending.length > 0 || this.persistQueues.size > 0 || this.activeRuns.size > 0 || this.store.list().some((task) => task.paused !== true) || this.runStore.list().some((run) => run.receipt.status === "buffered" || run.receipt.status === "running");
233718
+ }
233719
+ tryReserveIdleAdmission() {
233720
+ if (this.hasPendingWork) return void 0;
233721
+ return this.admission.tryReserve();
233722
+ }
233282
233723
  /**
233283
233724
  * Serialize per-id persistence writes. Concurrent mutations on the
233284
233725
  * same id (uncommon but reachable via `add` immediately followed by
@@ -233315,7 +233756,7 @@ var CronManager = class {
233315
233756
  * sequence and tests can opt into "ensure started" without bookkeeping.
233316
233757
  */
233317
233758
  start() {
233318
- if (this.admissionClosed) throw new Error("Cron admission is closed for quiescence");
233759
+ this.assertAdmissionOpen();
233319
233760
  if (this.started) return;
233320
233761
  this.started = true;
233321
233762
  this.scheduler.start();
@@ -233334,19 +233775,21 @@ var CronManager = class {
233334
233775
  * would be missing from the resume's `loadFromDisk()`.
233335
233776
  */
233336
233777
  async stop() {
233337
- this.unbindSigusr1();
233338
- await this.scheduler.stop();
233339
- await this.flushPersist();
233340
- this.started = false;
233778
+ if (this.admission.reserved) throw new Error("Cron admission is closed for quiescence");
233779
+ return this.operations.run("stop", async () => {
233780
+ this.unbindSigusr1();
233781
+ await this.scheduler.stop();
233782
+ await this.flushPersist();
233783
+ this.started = false;
233784
+ });
233341
233785
  }
233342
233786
  /** Host shutdown only; retain all scheduled jobs and their cursor state. */
233343
233787
  closeAdmission() {
233344
- this.admissionClosed = true;
233788
+ this.admission.close();
233345
233789
  }
233346
233790
  /** Drive one scheduler tick synchronously. Used by tests + P1.8 SIGUSR1. */
233347
233791
  tick() {
233348
- if (this.admissionClosed) throw new Error("Cron admission is closed for quiescence");
233349
- this.scheduler.tick();
233792
+ this.runSynchronous("tick", () => this.scheduler.tick());
233350
233793
  }
233351
233794
  /**
233352
233795
  * Earliest theoretical (post-jitter) next-fire across all tasks, or
@@ -233438,7 +233881,7 @@ var CronManager = class {
233438
233881
  finalMessageRef: null,
233439
233882
  unavailableReason
233440
233883
  }));
233441
- if (ctx.coalescedCount > CRON_COALESCE_WARNING_THRESHOLD) this.agent.emitEvent({
233884
+ if (ctx.coalescedCount > CRON_COALESCE_WARNING_THRESHOLD) this.emitOwnedEvent({
233442
233885
  type: "warning",
233443
233886
  code: "cron.coalesced",
233444
233887
  message: `Cron warning: ${String(ctx.coalescedCount)} scheduled runs were coalesced. Check the session before continuing.`
@@ -233447,7 +233890,7 @@ var CronManager = class {
233447
233890
  const warningKey = unavailableReason === "session_stopped" ? unavailableReason : `${unavailableReason}:${String(maxContextTokens)}`;
233448
233891
  if (this.lastUnavailableWarning !== warningKey) {
233449
233892
  this.lastUnavailableWarning = warningKey;
233450
- this.agent.emitEvent({
233893
+ this.emitOwnedEvent({
233451
233894
  type: "warning",
233452
233895
  code: `cron.${unavailableReason}`,
233453
233896
  message: unavailableReason === "session_stopped" ? "Cron stopped because compaction failed twice in this session. Start a fresh session after saving pending work." : `Cron stopped because pending history reached the ${String(maxContextTokens)} token context limit. Start a fresh session after saving pending work.`
@@ -233477,7 +233920,7 @@ var CronManager = class {
233477
233920
  type: "text",
233478
233921
  text: renderCronFireXml(origin, task.prompt)
233479
233922
  }];
233480
- this.agent.emitEvent({
233923
+ this.emitOwnedEvent({
233481
233924
  type: "cron.fired",
233482
233925
  origin,
233483
233926
  prompt: task.prompt
@@ -233523,14 +233966,16 @@ var CronManager = class {
233523
233966
  * contract simple ("safe to call with anything, no-op when empty").
233524
233967
  */
233525
233968
  handleMissed(tasks, renderMissedNotification) {
233526
- if (tasks.length === 0) return;
233527
- const content = renderMissedNotification(tasks);
233528
- const origin = {
233529
- kind: "cron_missed",
233530
- count: tasks.length
233531
- };
233532
- this.agent.turn.steer(content, origin);
233533
- this.agent.telemetry.track(CRON_MISSED, { count: tasks.length });
233969
+ this.runSynchronous("missed", () => {
233970
+ if (tasks.length === 0) return;
233971
+ const content = renderMissedNotification(tasks);
233972
+ const origin = {
233973
+ kind: "cron_missed",
233974
+ count: tasks.length
233975
+ };
233976
+ this.agent.turn.steer(content, origin);
233977
+ this.agent.telemetry.track(CRON_MISSED, { count: tasks.length });
233978
+ });
233534
233979
  }
233535
233980
  /**
233536
233981
  * Emit `cron_scheduled` for a freshly-added task. Called by
@@ -235583,6 +236028,11 @@ function formatUndoUnavailableMessage(requestedCount, undoableCount, stoppedAtCo
235583
236028
  }
235584
236029
  //#endregion
235585
236030
  //#region ../../packages/agent-core/src/agent/injection/injector.ts
236031
+ function latestMatchingSystemReminderIndex(history, variant, injection) {
236032
+ const latestIndex = history.findLastIndex((message) => message.origin?.kind === "injection" && message.origin.variant === variant);
236033
+ const latest = history[latestIndex];
236034
+ return latest?.role === "user" && latest.toolCalls.length === 0 && latest.content.length === 1 && latest.content[0]?.type === "text" && latest.content[0].text === `<system-reminder>\n${injection.trim()}\n</system-reminder>` ? latestIndex : void 0;
236035
+ }
235586
236036
  var DynamicInjector;
235587
236037
  var init_injector = __esmMin((() => {
235588
236038
  DynamicInjector = class {
@@ -235611,9 +236061,8 @@ var init_injector = __esmMin((() => {
235611
236061
  const injection = await this.getInjection();
235612
236062
  if (injection) {
235613
236063
  if (this.dedupeIdenticalWhilePresent) {
235614
- const latestIndex = this.agent.context.history.findLastIndex((message) => message.origin?.kind === "injection" && message.origin.variant === this.injectionVariant);
235615
- const latest = this.agent.context.history[latestIndex];
235616
- if (latest?.role === "user" && latest.toolCalls.length === 0 && latest.content.length === 1 && latest.content[0]?.type === "text" && latest.content[0].text === `<system-reminder>\n${injection.trim()}\n</system-reminder>`) {
236064
+ const latestIndex = latestMatchingSystemReminderIndex(this.agent.context.history, this.injectionVariant, injection);
236065
+ if (latestIndex !== void 0) {
235617
236066
  this.injectedAt = latestIndex;
235618
236067
  return;
235619
236068
  }
@@ -236069,7 +236518,7 @@ var HookEngine = class {
236069
236518
  options;
236070
236519
  byEvent = /* @__PURE__ */ new Map();
236071
236520
  pendingTriggers = /* @__PURE__ */ new Set();
236072
- admissionClosed = false;
236521
+ admission = new AdmissionGate();
236073
236522
  executionCounter = 0;
236074
236523
  executionAbort = new AbortController();
236075
236524
  executions = new ExecutionLedger();
@@ -236090,7 +236539,14 @@ var HookEngine = class {
236090
236539
  return this.matchingHooks(event, matcherValue).length > 0;
236091
236540
  }
236092
236541
  closeAdmission() {
236093
- this.admissionClosed = true;
236542
+ this.admission.close();
236543
+ }
236544
+ get hasPendingWork() {
236545
+ return this.executions.pending.length > 0 || this.pendingTriggers.size > 0;
236546
+ }
236547
+ tryReserveIdleAdmission() {
236548
+ if (this.hasPendingWork) return void 0;
236549
+ return this.admission.tryReserve();
236094
236550
  }
236095
236551
  get pendingExecutions() {
236096
236552
  return this.executions.pending.map((entry) => ({ ...entry }));
@@ -236111,15 +236567,13 @@ var HookEngine = class {
236111
236567
  };
236112
236568
  }
236113
236569
  trigger(event, args = {}) {
236114
- if (this.admissionClosed) return Promise.resolve([]);
236570
+ if (this.admission.closed) return Promise.resolve([]);
236115
236571
  try {
236116
236572
  const id = `${event}:${++this.executionCounter}`;
236117
- const completion = this.triggerInner(event, args, id).catch(() => []);
236118
- this.executions.track({
236573
+ return this.executions.run({
236119
236574
  kind: "trigger",
236120
236575
  id
236121
- }, completion);
236122
- return completion;
236576
+ }, () => this.triggerInner(event, args, id).catch(() => []));
236123
236577
  } catch {
236124
236578
  return Promise.resolve([]);
236125
236579
  }
@@ -236169,7 +236623,7 @@ var HookEngine = class {
236169
236623
  this.emitTriggered(event, matcherValue, matched.length);
236170
236624
  const startedAt = Date.now();
236171
236625
  const results = (await Promise.all(matched.map(async (hook, index) => {
236172
- if (this.admissionClosed) return void 0;
236626
+ if (this.admission.closed) return void 0;
236173
236627
  let runtimeEnv;
236174
236628
  try {
236175
236629
  runtimeEnv = this.options.runtimeEnv?.(hook);
@@ -236179,7 +236633,7 @@ var HookEngine = class {
236179
236633
  stderr: "Hook runtime environment is unavailable."
236180
236634
  };
236181
236635
  }
236182
- if (this.admissionClosed) return void 0;
236636
+ if (this.admission.closed) return void 0;
236183
236637
  let hookInput;
236184
236638
  try {
236185
236639
  hookInput = this.options.runtimeInput?.(hook, inputData) ?? inputData;
@@ -239241,6 +239695,7 @@ var init_mission_contract_bridge = __esmMin((() => {
239241
239695
  normalize = normalize$1;
239242
239696
  MissionContractInjector = class extends DynamicInjector {
239243
239697
  injectionVariant = "mission_contract";
239698
+ dedupeIdenticalWhilePresent = true;
239244
239699
  config;
239245
239700
  evidenceState = null;
239246
239701
  contractValue = null;
@@ -241798,6 +242253,8 @@ function isWithin$1(child, parent) {
241798
242253
  const LISTING_DESC_MAX = 250;
241799
242254
  const MODEL_SKILL_LISTING_MAX_CHARS = 8e3;
241800
242255
  var SessionSkillRegistry = class {
242256
+ admission = new AdmissionGate();
242257
+ operations = new ExecutionLedger();
241801
242258
  byName = /* @__PURE__ */ new Map();
241802
242259
  byPluginAndName = /* @__PURE__ */ new Map();
241803
242260
  roots = [];
@@ -241811,27 +242268,54 @@ var SessionSkillRegistry = class {
241811
242268
  this.sessionId = options.sessionId;
241812
242269
  }
241813
242270
  async loadRoots(roots) {
241814
- for (const root of roots) if (!this.roots.includes(root.path)) this.roots.push(root.path);
241815
- const skills = await this.discoverImpl({
241816
- roots,
241817
- onWarning: this.onWarning,
241818
- onSkippedByPolicy: (skill) => this.skipped.push(skill),
241819
- onDiscoveredSkill: (skill) => {
241820
- this.indexPluginSkill(skill);
241821
- }
242271
+ this.assertAdmissionOpen();
242272
+ return this.operations.run("load", async () => {
242273
+ for (const root of roots) if (!this.roots.includes(root.path)) this.roots.push(root.path);
242274
+ const skills = await this.discoverImpl({
242275
+ roots,
242276
+ onWarning: this.onWarning,
242277
+ onSkippedByPolicy: (skill) => this.skipped.push(skill),
242278
+ onDiscoveredSkill: (skill) => {
242279
+ this.indexPluginSkill(skill);
242280
+ }
242281
+ });
242282
+ for (const skill of skills) this.byName.set(normalizeSkillName(skill.name), skill);
241822
242283
  });
241823
- for (const skill of skills) this.byName.set(normalizeSkillName(skill.name), skill);
241824
242284
  }
241825
242285
  registerBuiltinSkill(skill) {
241826
- this.register(skill.source === "builtin" ? skill : {
241827
- ...skill,
241828
- source: "builtin"
242286
+ this.mutate("builtin", () => {
242287
+ this.register(skill.source === "builtin" ? skill : {
242288
+ ...skill,
242289
+ source: "builtin"
242290
+ });
241829
242291
  });
241830
242292
  }
241831
242293
  register(skill, options = {}) {
241832
- const key = normalizeSkillName(skill.name);
241833
- if (options.replace === true || !this.byName.has(key)) this.byName.set(key, skill);
241834
- this.indexPluginSkill(skill, options);
242294
+ this.mutate("register", () => {
242295
+ const key = normalizeSkillName(skill.name);
242296
+ if (options.replace === true || !this.byName.has(key)) this.byName.set(key, skill);
242297
+ this.indexPluginSkill(skill, options);
242298
+ });
242299
+ }
242300
+ get hasPendingWork() {
242301
+ return this.operations.pending.length > 0;
242302
+ }
242303
+ tryReserveIdleAdmission() {
242304
+ if (this.hasPendingWork) return void 0;
242305
+ return this.admission.tryReserve();
242306
+ }
242307
+ closeAdmission() {
242308
+ this.admission.close();
242309
+ }
242310
+ assertAdmissionOpen() {
242311
+ if (this.admission.closed) throw new Error("Skill work admission is closed");
242312
+ }
242313
+ mutate(detail, operation) {
242314
+ this.assertAdmissionOpen();
242315
+ this.operations.run(detail, () => {
242316
+ operation();
242317
+ return Promise.resolve();
242318
+ });
241835
242319
  }
241836
242320
  getSkill(name) {
241837
242321
  return this.byName.get(normalizeSkillName(name));
@@ -265778,7 +266262,11 @@ function toolResultMaxChars(maxContextTokens) {
265778
266262
  async function budgetToolResultForModel(options) {
265779
266263
  const text = persistableToolResultText(options.result.output);
265780
266264
  const maxChars = toolResultMaxChars(options.maxContextTokens);
265781
- if (text === void 0 || text.length <= maxChars) return options.result;
266265
+ if (text === void 0) return options.result;
266266
+ const tokenWindow = options.maxContextTokens;
266267
+ const exceedsChars = text.length > maxChars;
266268
+ const exceedsTokenWindow = !exceedsChars && tokenWindow !== void 0 && Number.isFinite(tokenWindow) && tokenWindow > 0 && text.length > tokenWindow && estimateTokens(text) > tokenWindow;
266269
+ if (!exceedsChars && !exceedsTokenWindow) return options.result;
265782
266270
  if (options.result.truncated === true) return options.result;
265783
266271
  if (options.homedir === void 0) return options.result;
265784
266272
  if (text.includes("\0") || Buffer.from(text, "utf8").toString("utf8") !== text || text.split("\n").some((line) => line.length > 2e3)) return options.result;
@@ -265789,7 +266277,8 @@ async function budgetToolResultForModel(options) {
265789
266277
  toolCallId: options.toolCallId
265790
266278
  }, text);
265791
266279
  if (outputPath === void 0) return options.result;
265792
- const output = renderPersistedToolResult(options.toolName, options.toolCallId, text, outputPath, maxChars);
266280
+ const output = renderPersistedToolResult(options.toolName, options.toolCallId, text, outputPath, maxChars, exceedsTokenWindow, tokenWindow);
266281
+ if (exceedsTokenWindow && tokenWindow !== void 0 && estimateTokens(output) > tokenWindow) return options.result;
265793
266282
  return options.result.isError === true ? {
265794
266283
  ...options.result,
265795
266284
  output,
@@ -265821,9 +266310,9 @@ async function saveToolResult(options, text) {
265821
266310
  return;
265822
266311
  }
265823
266312
  }
265824
- function renderPersistedToolResult(toolName, toolCallId, text, outputPath, maxChars) {
266313
+ function renderPersistedToolResult(toolName, toolCallId, text, outputPath, maxChars, exceedsTokenWindow, tokenWindow) {
265825
266314
  const lines = [
265826
- `Tool output exceeded ${String(maxChars)} characters; showing a preview only.`,
266315
+ exceedsTokenWindow ? `Tool output estimate exceeded the ${String(tokenWindow)}-token model context window; showing a preview only.` : `Tool output exceeded ${String(maxChars)} characters; showing a preview only.`,
265827
266316
  `tool_name: ${toolName}`,
265828
266317
  `tool_call_id: ${toolCallId}`,
265829
266318
  `output_size_chars: ${String(text.length)}`,
@@ -265832,6 +266321,19 @@ function renderPersistedToolResult(toolName, toolCallId, text, outputPath, maxCh
265832
266321
  "next_step: Use Read with output_path to page through the full output."
265833
266322
  ];
265834
266323
  let preview = text.slice(0, TOOL_RESULT_PREVIEW_CHARS);
266324
+ if (tokenWindow !== void 0 && Number.isFinite(tokenWindow) && tokenWindow > 0) {
266325
+ const remaining = Math.max(0, Math.floor(tokenWindow) - estimateTokens(lines.join("\n") + "\n\n[preview]\n"));
266326
+ if (estimateTokens(preview) > remaining) {
266327
+ let low = 0;
266328
+ let high = preview.length;
266329
+ while (low < high) {
266330
+ const middle = Math.ceil((low + high) / 2);
266331
+ if (estimateTokens(preview.slice(0, middle)) <= remaining) low = middle;
266332
+ else high = middle - 1;
266333
+ }
266334
+ preview = preview.slice(0, low);
266335
+ }
266336
+ }
265835
266337
  if (/[\uD800-\uDBFF]$/.test(preview)) preview = preview.slice(0, -1);
265836
266338
  lines.push("", "[preview]", preview);
265837
266339
  return lines.join("\n");
@@ -267849,7 +268351,7 @@ function blunToolsForOrigin(tools, origin) {
267849
268351
  * already eligible for this turn enter the loader; execution still follows
267850
268352
  * the ordinary permission pipeline.
267851
268353
  */
267852
- function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = []) {
268354
+ function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = [], rankedToolNames = /* @__PURE__ */ new Set()) {
267853
268355
  const schemaBudget = (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens);
267854
268356
  const estimates = /* @__PURE__ */ new Map();
267855
268357
  const estimateSummary = (tool) => {
@@ -267864,7 +268366,7 @@ function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PU
267864
268366
  let total = 0;
267865
268367
  for (const candidate of candidates) {
267866
268368
  const estimate = estimateSummary(candidate);
267867
- if (estimate.complete === void 0) estimate.complete = estimate.summary + estimateTokens(JSON.stringify(candidate.parameters));
268369
+ if (estimate.complete === void 0) estimate.complete = estimate.summary + estimateTokens(JSON.stringify(candidate.parameters), schemaBudget - estimate.summary);
267868
268370
  total += estimate.complete;
267869
268371
  if (total > schemaBudget) break;
267870
268372
  }
@@ -267895,6 +268397,10 @@ function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PU
267895
268397
  if (!remembered.delete(name)) continue;
267896
268398
  choose();
267897
268399
  }
268400
+ for (const name of rankedToolNames) {
268401
+ const tool = tools.find((candidate) => candidate.name === name);
268402
+ if (tool !== void 0 && !selected.some((candidate) => candidate.name === name) && estimateSelection([...selected, tool]) <= schemaBudget) selected.push(tool);
268403
+ }
267898
268404
  return selected;
267899
268405
  }
267900
268406
  const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
@@ -267952,7 +268458,7 @@ const GOAL_CONTINUATION_PROMPT = [
267952
268458
  ].join(" ");
267953
268459
  var TurnFlow = class {
267954
268460
  agent;
267955
- admissionClosed = false;
268461
+ admission = new AdmissionGate();
267956
268462
  loadedToolNames = /* @__PURE__ */ new Set();
267957
268463
  refreshSelectedTools;
267958
268464
  executions = new ExecutionLedger();
@@ -267977,7 +268483,15 @@ var TurnFlow = class {
267977
268483
  }
267978
268484
  /** Explicit host shutdown only; ordinary cancellation remains resumable. */
267979
268485
  closeAdmission() {
267980
- this.admissionClosed = true;
268486
+ this.admission.close();
268487
+ }
268488
+ /** Turn-local reservation only; other native owners must be reserved separately. */
268489
+ get hasPendingWork() {
268490
+ return this.activeTurn !== null || this.steerBuffer.length > 0 || this.executions.pending.length > 0 || Boolean(this.agent.records.restoring) || this.agent.fullCompaction.isCompacting;
268491
+ }
268492
+ tryReserveIdleAdmission() {
268493
+ if (this.hasPendingWork) return void 0;
268494
+ return this.admission.tryReserve();
267981
268495
  }
267982
268496
  async quiesce(options) {
267983
268497
  const deadline = new QuiescenceDeadline(options.deadlineAt);
@@ -268004,7 +268518,7 @@ var TurnFlow = class {
268004
268518
  return this.executions.pending;
268005
268519
  }
268006
268520
  assertAdmissionOpen() {
268007
- if (this.admissionClosed) throw new Error("Turn admission is closed for quiescence");
268521
+ if (this.admission.closed) throw new Error("Turn admission is closed for quiescence");
268008
268522
  }
268009
268523
  /** Best-effort agent id (main / generated id) derived from the agent homedir. */
268010
268524
  get agentId() {
@@ -268044,7 +268558,7 @@ var TurnFlow = class {
268044
268558
  }
268045
268559
  /** Buffer only into the currently active turn; never launch from an idle state. */
268046
268560
  steerActive(input, expectedTurnId, origin = USER_PROMPT_ORIGIN) {
268047
- if (this.admissionClosed) return null;
268561
+ if (this.admission.closed) return null;
268048
268562
  const active = this.activeTurn;
268049
268563
  if (active === null || active === "resuming" || !active.acceptingSteers || expectedTurnId !== void 0 && expectedTurnId !== this.currentId) return null;
268050
268564
  this.agent.records.logRecord({
@@ -268062,7 +268576,7 @@ var TurnFlow = class {
268062
268576
  });
268063
268577
  }
268064
268578
  launch(input, origin) {
268065
- if (this.admissionClosed) return null;
268579
+ if (this.admission.closed) return null;
268066
268580
  if (this.activeTurn) {
268067
268581
  this.agent.emitEvent({
268068
268582
  type: "error",
@@ -268077,11 +268591,10 @@ var TurnFlow = class {
268077
268591
  const turnId = this.allocateTurnId();
268078
268592
  const controller = new AbortController();
268079
268593
  this.executingControllers.add(controller);
268080
- const promise = this.turnWorker(turnId, input, origin, controller.signal);
268081
- this.executions.track({
268594
+ const promise = this.executions.run({
268082
268595
  kind: "turn",
268083
268596
  id: String(turnId)
268084
- }, promise);
268597
+ }, () => this.turnWorker(turnId, input, origin, controller.signal));
268085
268598
  promise.then(() => {
268086
268599
  this.executingControllers.delete(controller);
268087
268600
  }, () => {
@@ -268246,7 +268759,7 @@ var TurnFlow = class {
268246
268759
  * draining into it via `flushSteerBuffer`.
268247
268760
  */
268248
268761
  onCompactionFinished() {
268249
- if (this.admissionClosed) return;
268762
+ if (this.admission.closed) return;
268250
268763
  if (this.steerBuffer.length === 0) return;
268251
268764
  if (this.activeTurn !== null) return;
268252
268765
  const next = this.steerBuffer.shift();
@@ -268659,12 +269172,9 @@ var TurnFlow = class {
268659
269172
  const required = (0, import_turn_tool_performance_policy.mediaToolNamesForTurnText)(blunExtractText(toolInput));
268660
269173
  if (taskToolsNeeded) {
268661
269174
  const maxContextTokens = this.agent.fullCompaction.getEffectiveMaxContextTokens();
268662
- blunSelectTurnTools(eligibleTools, maxContextTokens, this.loadedToolNames, required, selectedTools);
268663
269175
  const trustedToolNames = new Set(eligibleTools.filter((tool) => this.agent.tools.isUserTool(tool)).map((tool) => tool.name));
268664
- for (const name of (0, import_turn_tool_performance_policy.rankedToolNamesForTurnText)(eligibleTools, blunExtractText(intent), { trustedToolNames })) {
268665
- const tool = eligibleTools.find((candidate) => candidate.name === name);
268666
- if (tool !== void 0 && !selectedTools.some((candidate) => candidate.name === name) && estimateTokensForTools([...selectedTools, tool]) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens)) selectedTools.push(tool);
268667
- }
269176
+ const ranked = (0, import_turn_tool_performance_policy.rankedToolNamesForTurnText)(eligibleTools, blunExtractText(intent), { trustedToolNames });
269177
+ blunSelectTurnTools(eligibleTools, maxContextTokens, this.loadedToolNames, required, selectedTools, ranked);
268668
269178
  } else selectedTools.splice(0, selectedTools.length, ...eligibleTools.filter((tool) => BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name) || BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name)));
268669
269179
  recordPreRequestPhase("tool-selection", selectionStartedAt);
268670
269180
  return selectedTools;
@@ -303285,6 +303795,9 @@ var StdioMcpClient = class {
303285
303795
  started = false;
303286
303796
  closed = false;
303287
303797
  ready = false;
303798
+ processSpawned = false;
303799
+ processClosed;
303800
+ resolveProcessClosed;
303288
303801
  hooksInstalled = false;
303289
303802
  unexpectedCloseListener;
303290
303803
  lastTransportError;
@@ -303293,6 +303806,9 @@ var StdioMcpClient = class {
303293
303806
  static stderrBufferCapacity = STDERR_BUFFER_CAPACITY;
303294
303807
  constructor(config, options = {}) {
303295
303808
  if (config.executor !== void 0 && config.executor !== "local") throw new BlunError(ErrorCodes.NOT_IMPLEMENTED, `MCP stdio executor '${config.executor}' is not yet implemented`);
303809
+ this.processClosed = new Promise((resolve) => {
303810
+ this.resolveProcessClosed = resolve;
303811
+ });
303296
303812
  this.transport = new StdioClientTransport({
303297
303813
  command: config.command,
303298
303814
  args: config.args,
@@ -303300,6 +303816,12 @@ var StdioMcpClient = class {
303300
303816
  cwd: resolveStdioCwd(config.cwd, options.defaultCwd),
303301
303817
  stderr: "pipe"
303302
303818
  });
303819
+ const start = this.transport.start.bind(this.transport);
303820
+ this.transport.start = () => {
303821
+ const completion = start();
303822
+ this.processSpawned = this.transport.pid !== null;
303823
+ return completion;
303824
+ };
303303
303825
  this.transport.stderr?.on("data", (chunk) => {
303304
303826
  this.stderrBuffer.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
303305
303827
  });
@@ -303331,6 +303853,10 @@ var StdioMcpClient = class {
303331
303853
  this.closed = true;
303332
303854
  await this.closeStartedClient();
303333
303855
  }
303856
+ /** SDK close can settle before the spawned process emits its actual close. */
303857
+ getProcessCloseCompletion() {
303858
+ return this.processSpawned ? this.processClosed : void 0;
303859
+ }
303334
303860
  /**
303335
303861
  * Register a listener that fires when the underlying transport closes on
303336
303862
  * its own — i.e. the caller has not yet invoked {@link close}. At most one
@@ -303377,6 +303903,9 @@ var StdioMcpClient = class {
303377
303903
  installTransportHooks() {
303378
303904
  if (this.hooksInstalled) return;
303379
303905
  this.hooksInstalled = true;
303906
+ this.transport.onclose = () => {
303907
+ this.resolveProcessClosed();
303908
+ };
303380
303909
  this.client.onclose = () => {
303381
303910
  if (this.closed) return;
303382
303911
  if (!this.ready) return;
@@ -303488,6 +304017,11 @@ const DEFAULT_STARTUP_TIMEOUT_MS$1 = 3e4;
303488
304017
  */
303489
304018
  var McpConnectionManager = class {
303490
304019
  options;
304020
+ admission = new AdmissionGate();
304021
+ operations = new ExecutionLedger();
304022
+ ownedClients = /* @__PURE__ */ new WeakMap();
304023
+ closingClients = /* @__PURE__ */ new WeakMap();
304024
+ unconfirmedCloses = /* @__PURE__ */ new Set();
303491
304025
  entries = /* @__PURE__ */ new Map();
303492
304026
  listeners = /* @__PURE__ */ new Set();
303493
304027
  initialLoad = Promise.resolve();
@@ -303548,22 +304082,27 @@ var McpConnectionManager = class {
303548
304082
  const entry = this.entries.get(name);
303549
304083
  if (entry?.status !== "connected" || entry.tools === void 0 || entry.client === void 0) return;
303550
304084
  return {
303551
- client: entry.client,
304085
+ client: this.ownedClient(entry.client),
303552
304086
  tools: entry.tools,
303553
304087
  enabledNames: entry.enabledNames ?? new Set(entry.tools.map((t) => t.name))
303554
304088
  };
303555
304089
  }
303556
304090
  connectAll(configs) {
303557
- const attemptId = ++this.initialLoadAttemptId;
303558
- this.initialLoadStartedAt = Date.now();
303559
- this.initialLoadFinishedAt = void 0;
303560
- const initialLoad = this.connectAllNow(configs).finally(() => {
303561
- if (this.initialLoadAttemptId === attemptId) this.initialLoadFinishedAt = Date.now();
304091
+ return this.runOperation("connect-all", () => {
304092
+ const attemptId = ++this.initialLoadAttemptId;
304093
+ this.initialLoadStartedAt = Date.now();
304094
+ this.initialLoadFinishedAt = void 0;
304095
+ const initialLoad = this.connectAllNow(configs).finally(() => {
304096
+ if (this.initialLoadAttemptId === attemptId) this.initialLoadFinishedAt = Date.now();
304097
+ });
304098
+ this.initialLoad = initialLoad;
304099
+ return initialLoad;
303562
304100
  });
303563
- this.initialLoad = initialLoad;
303564
- return initialLoad;
303565
304101
  }
303566
304102
  async connect(name, config) {
304103
+ return this.runOperation("connect", () => this.connectNow(name, config));
304104
+ }
304105
+ async connectNow(name, config) {
303567
304106
  const previous = this.entries.get(name);
303568
304107
  if (previous !== void 0) await this.closeClient(previous);
303569
304108
  const disabled = config.enabled === false;
@@ -303578,6 +304117,9 @@ var McpConnectionManager = class {
303578
304117
  if (!disabled) await this.connectOne(entry, this.beginConnectAttempt(entry));
303579
304118
  }
303580
304119
  async remove(name) {
304120
+ return this.runOperation("remove", () => this.removeNow(name));
304121
+ }
304122
+ async removeNow(name) {
303581
304123
  const entry = this.entries.get(name);
303582
304124
  if (entry === void 0) return false;
303583
304125
  await this.closeClient(entry);
@@ -303616,6 +304158,9 @@ var McpConnectionManager = class {
303616
304158
  await Promise.allSettled(tasks);
303617
304159
  }
303618
304160
  async reconnect(name) {
304161
+ return this.runOperation("reconnect", () => this.reconnectNow(name));
304162
+ }
304163
+ async reconnectNow(name) {
303619
304164
  const entry = this.entries.get(name);
303620
304165
  if (entry === void 0) throw new BlunError(ErrorCodes.MCP_SERVER_NOT_FOUND, `Unknown MCP server: ${name}`);
303621
304166
  if (entry.config.enabled === false) throw new BlunError(ErrorCodes.MCP_SERVER_DISABLED, `MCP server is disabled: ${name}`);
@@ -303636,11 +304181,45 @@ var McpConnectionManager = class {
303636
304181
  }
303637
304182
  }
303638
304183
  async shutdown() {
304184
+ if (this.admission.reserved) throw new Error("MCP work admission is closed");
304185
+ return this.operations.run("shutdown", () => this.shutdownNow());
304186
+ }
304187
+ async shutdownNow() {
303639
304188
  const entries = Array.from(this.entries.values());
303640
304189
  this.entries.clear();
303641
304190
  const tasks = entries.map((entry) => this.closeClient(entry));
303642
304191
  await Promise.allSettled(tasks);
303643
304192
  }
304193
+ get hasPendingWork() {
304194
+ return this.operations.pending.length > 0 || this.unconfirmedCloses.size > 0;
304195
+ }
304196
+ tryReserveIdleAdmission() {
304197
+ if (this.hasPendingWork) return void 0;
304198
+ return this.admission.tryReserve();
304199
+ }
304200
+ closeAdmission() {
304201
+ this.admission.close();
304202
+ }
304203
+ waitForOperations() {
304204
+ return this.operations.drain();
304205
+ }
304206
+ runOperation(detail, operation) {
304207
+ if (this.admission.closed) throw new Error("MCP work admission is closed");
304208
+ return this.operations.run(detail, operation);
304209
+ }
304210
+ ownedClient(client) {
304211
+ let owned = this.ownedClients.get(client);
304212
+ if (owned === void 0) {
304213
+ const native = client;
304214
+ owned = {
304215
+ getInstructions: () => native.getInstructions?.(),
304216
+ listTools: async () => this.runOperation("list-tools", () => client.listTools()),
304217
+ callTool: async (name, args, signal) => this.runOperation("call-tool", () => client.callTool(name, args, signal))
304218
+ };
304219
+ this.ownedClients.set(client, owned);
304220
+ }
304221
+ return owned;
304222
+ }
303644
304223
  async connectOne(entry, attemptId) {
303645
304224
  const timeoutMs = entry.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS$1;
303646
304225
  let client;
@@ -303648,7 +304227,7 @@ var McpConnectionManager = class {
303648
304227
  const startupClient = this.createClient(entry.config, entry.name);
303649
304228
  client = startupClient;
303650
304229
  entry.client = startupClient;
303651
- const tools = await withTimeout(this.connectAndDiscoverTools(startupClient), timeoutMs, () => {
304230
+ const tools = await withTimeout(this.operations.run("startup", () => this.connectAndDiscoverTools(startupClient)), timeoutMs, () => {
303652
304231
  this.closeRuntimeClient(startupClient);
303653
304232
  });
303654
304233
  if (!this.isCurrent(entry, attemptId)) {
@@ -303682,13 +304261,16 @@ var McpConnectionManager = class {
303682
304261
  client.onUnexpectedClose((reason) => {
303683
304262
  if (!this.isCurrent(entry, attemptId)) return;
303684
304263
  if (entry.client !== client) return;
303685
- entry.status = "failed";
303686
- entry.error = formatUnexpectedCloseError(entry.name, reason);
303687
- entry.tools = void 0;
303688
- entry.enabledNames = void 0;
303689
- entry.client = void 0;
303690
- this.closeRuntimeClient(client);
303691
- this.emit(entry);
304264
+ this.operations.run("transport-close", () => {
304265
+ entry.status = "failed";
304266
+ entry.error = formatUnexpectedCloseError(entry.name, reason);
304267
+ entry.tools = void 0;
304268
+ entry.enabledNames = void 0;
304269
+ entry.client = void 0;
304270
+ const completion = this.closeRuntimeClient(client);
304271
+ this.emit(entry);
304272
+ return completion;
304273
+ });
303692
304274
  });
303693
304275
  }
303694
304276
  beginConnectAttempt(entry) {
@@ -303751,9 +304333,24 @@ var McpConnectionManager = class {
303751
304333
  await this.closeRuntimeClient(client);
303752
304334
  }
303753
304335
  async closeRuntimeClient(client) {
303754
- try {
303755
- await client.close();
303756
- } catch {}
304336
+ const existing = this.closingClients.get(client);
304337
+ if (existing !== void 0) return existing;
304338
+ const processClosed = client instanceof StdioMcpClient ? client.getProcessCloseCompletion() : void 0;
304339
+ let processConfirmed = false;
304340
+ if (processClosed !== void 0) this.operations.run("process-close", async () => {
304341
+ await processClosed;
304342
+ processConfirmed = true;
304343
+ this.unconfirmedCloses.delete(client);
304344
+ });
304345
+ const completion = this.operations.run("close", async () => {
304346
+ try {
304347
+ await client.close();
304348
+ } catch {
304349
+ if (!processConfirmed) this.unconfirmedCloses.add(client);
304350
+ }
304351
+ });
304352
+ this.closingClients.set(client, completion);
304353
+ return completion;
303757
304354
  }
303758
304355
  isCurrent(entry, attemptId) {
303759
304356
  return this.entries.get(entry.name) === entry && entry.attemptId === attemptId;
@@ -304109,6 +304706,168 @@ function hasLegacyLocalMemorySystemBlock(systemPrompt) {
304109
304706
  return candidate.includes(LEGACY_LOCAL_MEMORY_LOCATION) && candidate.includes(LEGACY_LOCAL_MEMORY_SAVE) && candidate.includes("MEMORY.md");
304110
304707
  }
304111
304708
  //#endregion
304709
+ //#region ../../packages/agent-core/src/utils/host-work.ts
304710
+ function bindHostEffectMethod(methods, owner, name, asynchronous, kind, detail) {
304711
+ const method = Reflect.get(methods, name);
304712
+ Object.defineProperty(methods, name, {
304713
+ configurable: true,
304714
+ writable: true,
304715
+ value: (...args) => {
304716
+ try {
304717
+ return owner.runHostEffect(detail, () => Reflect.apply(method, methods, args), kind);
304718
+ } catch (error) {
304719
+ if (asynchronous) return Promise.reject(error);
304720
+ throw error;
304721
+ }
304722
+ }
304723
+ });
304724
+ }
304725
+ //#endregion
304726
+ //#region ../../packages/agent-core/src/session/host-work.ts
304727
+ const DIRECT_WORK = {
304728
+ setToolKaos: false,
304729
+ setAdditionalDirs: true,
304730
+ setAllowedTools: true,
304731
+ addAdditionalDir: true,
304732
+ createMain: true,
304733
+ resume: true,
304734
+ setAccountMemoryContext: false,
304735
+ setRuntimeModelContext: false,
304736
+ generateAgentsMd: true,
304737
+ appendPluginSessionStartReminder: true,
304738
+ applyMcpServerConfig: true,
304739
+ getSessionWarnings: true,
304740
+ waitForBackgroundTasksOnPrint: true,
304741
+ close: true,
304742
+ closeForReload: true
304743
+ };
304744
+ function bindDirectSessionWork(session) {
304745
+ for (const [name, asynchronous] of Object.entries(DIRECT_WORK)) bindHostEffectMethod(session, session, name, asynchronous, [
304746
+ "close",
304747
+ "closeForReload",
304748
+ "getSessionWarnings",
304749
+ "waitForBackgroundTasksOnPrint"
304750
+ ].includes(name) ? "teardown" : "work", `session:${name}`);
304751
+ }
304752
+ //#endregion
304753
+ //#region ../../packages/agent-core/src/session/idle-admission.ts
304754
+ function nativeOwners(session, id, agent) {
304755
+ if (!(agent.turn instanceof TurnFlow) || !(agent.fullCompaction instanceof FullCompaction) || !(agent.background instanceof BackgroundManager) || !(agent.records instanceof AgentRecords) || !(agent.goal instanceof GoalMode) || !(agent.subagentHost instanceof SessionSubagentHost) || !agent.subagentHost.belongsTo(session, id) || agent.cron !== null && !(agent.cron instanceof CronManager) || agent.hooks !== void 0 && !(agent.hooks instanceof HookEngine)) return void 0;
304756
+ return [
304757
+ agent.turn,
304758
+ agent.fullCompaction,
304759
+ agent.background,
304760
+ agent.subagentHost,
304761
+ ...agent.cron === null ? [] : [agent.cron],
304762
+ ...agent.hooks === void 0 ? [] : [agent.hooks]
304763
+ ];
304764
+ }
304765
+ /** Identity receipts only; the Session maps remain the sole enumerable agent catalog. */
304766
+ var SessionIdleAdmission = class {
304767
+ session;
304768
+ admission;
304769
+ sessionPending;
304770
+ sessionVersion;
304771
+ identities = /* @__PURE__ */ new WeakMap();
304772
+ shared;
304773
+ constructor(session, admission, sessionPending, sessionVersion) {
304774
+ this.session = session;
304775
+ this.admission = admission;
304776
+ this.sessionPending = sessionPending;
304777
+ this.sessionVersion = sessionVersion;
304778
+ this.shared = [
304779
+ session.skills,
304780
+ session.mcp,
304781
+ session.hookEngine,
304782
+ ...session.lsp === void 0 ? [] : [session.lsp]
304783
+ ];
304784
+ }
304785
+ register(id, agent) {
304786
+ const owners = nativeOwners(this.session, id, agent);
304787
+ if (owners) this.identities.set(agent, {
304788
+ id,
304789
+ owners,
304790
+ records: agent.records,
304791
+ goal: agent.goal
304792
+ });
304793
+ }
304794
+ snapshot() {
304795
+ if (this.sessionPending()) return void 0;
304796
+ const shared = [
304797
+ this.session.skills,
304798
+ this.session.mcp,
304799
+ this.session.hookEngine,
304800
+ ...this.session.lsp === void 0 ? [] : [this.session.lsp]
304801
+ ];
304802
+ if (shared.length !== this.shared.length || shared.some((owner, index) => owner !== this.shared[index])) return void 0;
304803
+ const agents = [...this.session.agents];
304804
+ if (!this.session.agents.has("main")) return void 0;
304805
+ const identities = [];
304806
+ const versions = [];
304807
+ for (const [id, agent] of agents) {
304808
+ if (!(agent instanceof Agent$1)) return void 0;
304809
+ const identity = this.identities.get(agent);
304810
+ const owners = nativeOwners(this.session, id, agent);
304811
+ if (!identity || identity.id !== id || !owners || owners.length !== identity.owners.length || owners.some((owner, index) => owner !== identity.owners[index]) || agent.records !== identity.records || agent.goal !== identity.goal || agent.records.hasPendingWork || agent.goal.getActiveGoal() !== null) return void 0;
304812
+ identities.push(identity);
304813
+ versions.push(agent.records.version);
304814
+ }
304815
+ if (Object.keys(this.session.metadata.agents).some((id) => !this.session.agents.has(id))) return void 0;
304816
+ return {
304817
+ agents,
304818
+ identities,
304819
+ versions,
304820
+ metadata: this.session.metadata,
304821
+ scope: JSON.stringify({
304822
+ id: this.session.options.id,
304823
+ homedir: this.session.options.homedir,
304824
+ blunHomeDir: this.session.options.blunHomeDir,
304825
+ additionalDirs: this.session.getAdditionalDirs(),
304826
+ mcp: this.session.mcpConfigSnapshot()
304827
+ }),
304828
+ text: JSON.stringify(this.session.metadata),
304829
+ version: this.sessionVersion()
304830
+ };
304831
+ }
304832
+ tryReserve() {
304833
+ const ownRelease = this.admission.tryReserve();
304834
+ if (!ownRelease) return void 0;
304835
+ const releases = [ownRelease];
304836
+ let retained = false;
304837
+ try {
304838
+ const snapshot = this.snapshot();
304839
+ if (!snapshot) return void 0;
304840
+ const owners = [...new Set([...this.shared, ...snapshot.identities.flatMap((identity) => identity.owners)])];
304841
+ for (const owner of owners) {
304842
+ if (owner.hasPendingWork) return void 0;
304843
+ const release = owner.tryReserveIdleAdmission();
304844
+ if (!release) return void 0;
304845
+ releases.push(release);
304846
+ }
304847
+ const lease = new IdleAdmissionReservation(releases, () => {
304848
+ const fresh = this.snapshot();
304849
+ return owners.every((owner) => !owner.hasPendingWork) && fresh !== void 0 && fresh.version === snapshot.version && fresh.metadata === snapshot.metadata && fresh.text === snapshot.text && fresh.scope === snapshot.scope && fresh.agents.length === snapshot.agents.length && fresh.agents.every(([id, agent], index) => id === snapshot.agents[index][0] && agent === snapshot.agents[index][1]) && fresh.versions.every((version, index) => version === snapshot.versions[index]);
304850
+ }, () => !this.sessionPending() && this.sessionVersion() === snapshot.version && this.session.metadata === snapshot.metadata && this.session.agents.size === snapshot.agents.length && snapshot.agents.every(([id, agent]) => this.session.agents.get(id) === agent) && snapshot.identities.every((identity, index) => !identity.records.hasPendingWork && identity.records.version === snapshot.versions[index]) && owners.every((owner) => !owner.hasPendingWork));
304851
+ if (!lease.isValid()) return void 0;
304852
+ retained = true;
304853
+ return lease;
304854
+ } catch {
304855
+ return;
304856
+ } finally {
304857
+ if (!retained) for (const release of releases.reverse()) release();
304858
+ }
304859
+ }
304860
+ };
304861
+ //#endregion
304862
+ //#region ../../packages/agent-core/src/session/host-events.ts
304863
+ /** Publication settlement belongs to the session, even during native teardown. */
304864
+ function withOwnedSessionEvents(rpc, operations) {
304865
+ const emitEvent = (...args) => operations.run("rpc-event", () => rpc.emitEvent(...args));
304866
+ return new Proxy(rpc, { get(target, property) {
304867
+ return property === "emitEvent" ? emitEvent : Reflect.get(target, property, target);
304868
+ } });
304869
+ }
304870
+ //#endregion
304112
304871
  //#region ../../packages/agent-core/src/lsp/client.ts
304113
304872
  const DEFAULT_STARTUP_TIMEOUT_MS = 1e4;
304114
304873
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 2e3;
@@ -304117,6 +304876,10 @@ var StdioLspClient = class {
304117
304876
  name;
304118
304877
  config;
304119
304878
  cwd;
304879
+ operations = new ExecutionLedger();
304880
+ children = /* @__PURE__ */ new Set();
304881
+ processEnds = /* @__PURE__ */ new WeakMap();
304882
+ closingProcesses = /* @__PURE__ */ new WeakSet();
304120
304883
  process;
304121
304884
  startPromise;
304122
304885
  stdout = Buffer.alloc(0);
@@ -304137,10 +304900,16 @@ var StdioLspClient = class {
304137
304900
  get generation() {
304138
304901
  return this._generation;
304139
304902
  }
304903
+ get hasPendingWork() {
304904
+ return this.operations.pending.length > 0;
304905
+ }
304906
+ waitForOperations() {
304907
+ return this.operations.drain();
304908
+ }
304140
304909
  async start() {
304141
- if (this.running) return;
304142
304910
  if (this.startPromise !== void 0) return this.startPromise;
304143
- this.startPromise = this.startInternal().catch((error) => {
304911
+ if (this.running) return;
304912
+ this.startPromise = this.operations.run("startup", () => this.startInternal()).catch((error) => {
304144
304913
  this.startPromise = void 0;
304145
304914
  throw error;
304146
304915
  });
@@ -304165,6 +304934,9 @@ var StdioLspClient = class {
304165
304934
  this.diagnosticsByUri.delete(uri);
304166
304935
  }
304167
304936
  async waitForDiagnostics(uri, timeoutMs = 300) {
304937
+ return this.operations.run("diagnostics", () => this.waitForDiagnosticsNow(uri, timeoutMs));
304938
+ }
304939
+ async waitForDiagnosticsNow(uri, timeoutMs) {
304168
304940
  const current = this.diagnosticsByUri.get(uri);
304169
304941
  if (current !== void 0) return current;
304170
304942
  let notify;
@@ -304184,8 +304956,15 @@ var StdioLspClient = class {
304184
304956
  return this.diagnosticsByUri.get(uri) ?? [];
304185
304957
  }
304186
304958
  async shutdown() {
304959
+ return this.operations.run("shutdown", () => this.shutdownNow());
304960
+ }
304961
+ async shutdownNow() {
304187
304962
  this.stopped = true;
304188
304963
  const child = this.process;
304964
+ for (const owned of this.children) {
304965
+ this.retainProcessEnd(owned);
304966
+ if (owned !== child && owned.exitCode === null) owned.kill();
304967
+ }
304189
304968
  if (child === void 0 || child.exitCode !== null) return;
304190
304969
  try {
304191
304970
  await this.sendRequest("shutdown", null, this.config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS);
@@ -304216,11 +304995,38 @@ var StdioLspClient = class {
304216
304995
  windowsHide: true
304217
304996
  });
304218
304997
  this.process = child;
304219
- child.stdout.on("data", (chunk) => this.onStdout(chunk));
304998
+ this.stdout = Buffer.alloc(0);
304999
+ this.children.add(child);
305000
+ this.processEnds.set(child, new Promise((resolve) => {
305001
+ child.once("close", () => {
305002
+ this.operations.run("process-close", () => {
305003
+ this.children.delete(child);
305004
+ for (const [id, pending] of this.pending) {
305005
+ if (pending.child !== child) continue;
305006
+ clearTimeout(pending.timer);
305007
+ this.pending.delete(id);
305008
+ pending.reject(/* @__PURE__ */ new Error(`LSP server "${this.name}" closed before its response`));
305009
+ pending.finish();
305010
+ }
305011
+ if (this.process === child) this.process = void 0;
305012
+ resolve();
305013
+ return Promise.resolve();
305014
+ });
305015
+ });
305016
+ }));
305017
+ child.stdout.on("data", (chunk) => {
305018
+ if (this.process !== child) return;
305019
+ this.operations.run("message", () => {
305020
+ this.onStdout(chunk);
305021
+ return Promise.resolve();
305022
+ });
305023
+ });
304220
305024
  child.stderr.on("data", () => {});
304221
- child.once("error", (error) => this.failProcess(error));
305025
+ child.once("error", (error) => {
305026
+ if (this.process === child) this.failProcess(error);
305027
+ });
304222
305028
  child.once("exit", (code, signal) => {
304223
- if (!this.stopped) this.failProcess(/* @__PURE__ */ new Error(`LSP server "${this.name}" exited unexpectedly (${code === null ? signal : String(code)})`));
305029
+ if (!this.stopped && this.process === child) this.failProcess(/* @__PURE__ */ new Error(`LSP server "${this.name}" exited unexpectedly (${code === null ? signal : String(code)})`));
304224
305030
  });
304225
305031
  const rootPath = this.config.workspaceFolder ?? this.cwd;
304226
305032
  const rootUri = pathToFileURL(rootPath).href;
@@ -304252,6 +305058,7 @@ var StdioLspClient = class {
304252
305058
  this._generation++;
304253
305059
  if (this.config.settings !== void 0) this.notify("workspace/didChangeConfiguration", { settings: this.config.settings });
304254
305060
  } catch (error) {
305061
+ this.retainProcessEnd(child);
304255
305062
  if (child.exitCode === null) child.kill();
304256
305063
  const message = error instanceof Error ? error.message : String(error);
304257
305064
  throw new Error(`Failed to start LSP server "${this.name}" (${this.config.command}): ${message}`, { cause: error });
@@ -304259,13 +305066,20 @@ var StdioLspClient = class {
304259
305066
  }
304260
305067
  sendRequest(method, params, timeoutMs) {
304261
305068
  const id = this.nextId++;
305069
+ const child = this.process;
305070
+ if (child === void 0) return Promise.reject(/* @__PURE__ */ new Error(`LSP server "${this.name}" is not available`));
305071
+ let finish;
305072
+ this.operations.run("request-end", () => new Promise((resolve) => {
305073
+ finish = resolve;
305074
+ }));
304262
305075
  return new Promise((resolve, reject) => {
304263
305076
  const timer = setTimeout(() => {
304264
- this.pending.delete(id);
304265
305077
  reject(/* @__PURE__ */ new Error(`LSP request "${method}" timed out after ${String(timeoutMs)} ms`));
304266
305078
  }, timeoutMs);
304267
305079
  timer.unref?.();
304268
305080
  this.pending.set(id, {
305081
+ child,
305082
+ finish,
304269
305083
  resolve,
304270
305084
  reject,
304271
305085
  timer
@@ -304280,6 +305094,7 @@ var StdioLspClient = class {
304280
305094
  } catch (error) {
304281
305095
  clearTimeout(timer);
304282
305096
  this.pending.delete(id);
305097
+ finish();
304283
305098
  reject(error instanceof Error ? error : new Error(String(error)));
304284
305099
  }
304285
305100
  });
@@ -304288,8 +305103,21 @@ var StdioLspClient = class {
304288
305103
  const stdin = this.process?.stdin;
304289
305104
  if (stdin === void 0 || stdin.destroyed) throw new Error(`LSP server "${this.name}" is not available`);
304290
305105
  const body = Buffer.from(JSON.stringify(message), "utf8");
304291
- stdin.write(`Content-Length: ${String(body.length)}\r\n\r\n`);
304292
- stdin.write(body);
305106
+ for (const chunk of [`Content-Length: ${String(body.length)}\r\n\r\n`, body]) {
305107
+ let finish;
305108
+ const completion = new Promise((resolve) => {
305109
+ finish = resolve;
305110
+ });
305111
+ this.operations.run("write", () => {
305112
+ try {
305113
+ stdin.write(chunk, () => finish());
305114
+ } catch (error) {
305115
+ finish();
305116
+ throw error;
305117
+ }
305118
+ return completion;
305119
+ });
305120
+ }
304293
305121
  }
304294
305122
  onStdout(chunk) {
304295
305123
  this.stdout = Buffer.concat([this.stdout, chunk]);
@@ -304325,6 +305153,7 @@ var StdioLspClient = class {
304325
305153
  if (pending === void 0) return;
304326
305154
  clearTimeout(pending.timer);
304327
305155
  this.pending.delete(message.id);
305156
+ pending.finish();
304328
305157
  if (message.error !== void 0) pending.reject(/* @__PURE__ */ new Error(`LSP request failed${message.error.code === void 0 ? "" : ` (${String(message.error.code)})`}: ${message.error.message ?? "unknown error"}`));
304329
305158
  else pending.resolve(message.result);
304330
305159
  return;
@@ -304378,15 +305207,25 @@ var StdioLspClient = class {
304378
305207
  });
304379
305208
  }
304380
305209
  failProcess(error) {
305210
+ const child = this.process;
305211
+ if (child === void 0) return;
305212
+ this.retainProcessEnd(child);
304381
305213
  const wrapped = new Error(`LSP server "${this.name}" (${this.config.command}) failed: ${error.message}`, { cause: error });
304382
305214
  for (const pending of this.pending.values()) {
305215
+ if (pending.child !== child) continue;
304383
305216
  clearTimeout(pending.timer);
304384
305217
  pending.reject(wrapped);
304385
305218
  }
304386
- this.pending.clear();
304387
305219
  this.process = void 0;
304388
305220
  if (this.config.restartOnCrash !== false && !this.stopped) this.startPromise = void 0;
304389
305221
  }
305222
+ retainProcessEnd(child) {
305223
+ if (this.closingProcesses.has(child)) return;
305224
+ const completion = this.processEnds.get(child);
305225
+ if (completion === void 0) return;
305226
+ this.closingProcesses.add(child);
305227
+ this.operations.run("process-end", () => completion);
305228
+ }
304390
305229
  };
304391
305230
  async function waitForExit(child, timeoutMs) {
304392
305231
  if (child.exitCode !== null) return;
@@ -304403,6 +305242,8 @@ function delay(timeoutMs) {
304403
305242
  //#region ../../packages/agent-core/src/lsp/manager.ts
304404
305243
  var LspManager = class {
304405
305244
  options;
305245
+ admission = new AdmissionGate();
305246
+ operations = new ExecutionLedger();
304406
305247
  clients = /* @__PURE__ */ new Map();
304407
305248
  openedDocuments = /* @__PURE__ */ new Map();
304408
305249
  constructor(options) {
@@ -304415,15 +305256,20 @@ var LspManager = class {
304415
305256
  return [...this.clients.entries()].filter(([, client]) => client.running).map(([name]) => name).toSorted();
304416
305257
  }
304417
305258
  async diagnosticsAfterChange(filePath) {
304418
- const resolvedPath = path.resolve(this.options.cwd, filePath);
304419
- if (!this.supportsPath(resolvedPath)) return void 0;
304420
- const diagnostics = await this.request({
304421
- operation: "diagnostics",
304422
- path: resolvedPath
305259
+ return this.runOperation("diagnostics", async () => {
305260
+ const resolvedPath = path.resolve(this.options.cwd, filePath);
305261
+ if (!this.supportsPath(resolvedPath)) return void 0;
305262
+ const diagnostics = await this.requestNow({
305263
+ operation: "diagnostics",
305264
+ path: resolvedPath
305265
+ });
305266
+ return Array.isArray(diagnostics) ? diagnostics : [];
304423
305267
  });
304424
- return Array.isArray(diagnostics) ? diagnostics : [];
304425
305268
  }
304426
305269
  async request(input) {
305270
+ return this.runOperation("request", () => this.requestNow(input));
305271
+ }
305272
+ async requestNow(input) {
304427
305273
  if (input.operation === "workspace_symbols") {
304428
305274
  if (input.query === void 0) throw new Error("workspace_symbols requires query");
304429
305275
  const results = await Promise.allSettled(this.serverNames().map(async (name) => {
@@ -304451,10 +305297,38 @@ var LspManager = class {
304451
305297
  return client.request(method, params);
304452
305298
  }
304453
305299
  async shutdown() {
304454
- await Promise.allSettled([...this.clients.values()].map((client) => client.shutdown()));
305300
+ if (this.admission.reserved) throw new Error("LSP work admission is closed");
305301
+ return this.operations.run("shutdown", () => this.shutdownNow());
305302
+ }
305303
+ async shutdownNow() {
305304
+ await Promise.allSettled([...this.clients.values()].map((client) => {
305305
+ const completion = client.shutdown();
305306
+ this.operations.run("client-end", () => client.waitForOperations());
305307
+ return completion;
305308
+ }));
304455
305309
  this.clients.clear();
304456
305310
  this.openedDocuments.clear();
304457
305311
  }
305312
+ get hasPendingWork() {
305313
+ return this.operations.pending.length > 0 || [...this.clients.values()].some((client) => client.hasPendingWork);
305314
+ }
305315
+ tryReserveIdleAdmission() {
305316
+ if (this.hasPendingWork) return void 0;
305317
+ return this.admission.tryReserve();
305318
+ }
305319
+ closeAdmission() {
305320
+ this.admission.close();
305321
+ }
305322
+ async waitForOperations() {
305323
+ do {
305324
+ await this.operations.drain();
305325
+ await Promise.all([...this.clients.values()].map((client) => client.waitForOperations()));
305326
+ } while (this.hasPendingWork);
305327
+ }
305328
+ runOperation(detail, operation) {
305329
+ if (this.admission.closed) throw new Error("LSP work admission is closed");
305330
+ return this.operations.run(detail, operation);
305331
+ }
304458
305332
  clientForPath(filePath) {
304459
305333
  const extension = path.extname(filePath).toLowerCase();
304460
305334
  for (const [name, config] of Object.entries(this.options.servers)) {
@@ -304538,6 +305412,7 @@ function positionFrom(input) {
304538
305412
  }
304539
305413
  //#endregion
304540
305414
  //#region ../../packages/agent-core/src/session/index.ts
305415
+ init_injector();
304541
305416
  const BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV = "BLUN_BACKGROUND_KEEP_ALIVE_ON_EXIT";
304542
305417
  const ACTIVE_TURN_CLOSE_TIMEOUT_MS = 8e3;
304543
305418
  const BASELINE_SKILL_NAME = "blun-design-dna";
@@ -304587,19 +305462,21 @@ var Session$1 = class {
304587
305462
  accountMemoryContext = prepareRuntimeSystemPromptAppend(void 0);
304588
305463
  agentSpineRuntime;
304589
305464
  mcpLoadStarted = false;
304590
- workAdmissionClosed = false;
305465
+ admission = new AdmissionGate();
305466
+ idleAdmission;
304591
305467
  agentOperations = new ExecutionLedger();
304592
305468
  hostOperations = new ExecutionLedger();
304593
305469
  quiescenceRecordFlushes = /* @__PURE__ */ new Map();
304594
305470
  constructingAgents = /* @__PURE__ */ new Map();
304595
305471
  constructor(options) {
304596
305472
  this.options = options;
305473
+ bindDirectSessionWork(this);
304597
305474
  this.logHandle = options.id === void 0 ? void 0 : getRootLogger().attachSession({
304598
305475
  sessionId: options.id,
304599
305476
  sessionDir: options.homedir
304600
305477
  });
304601
305478
  this.log = this.logHandle?.logger ?? (options.id === void 0 ? log : log.createChild({ sessionId: options.id }));
304602
- this.rpc = options.rpc;
305479
+ this.rpc = withOwnedSessionEvents(options.rpc, this.hostOperations);
304603
305480
  this.experimentalFlags = options.experimentalFlags ?? new FlagResolver();
304604
305481
  this.agentSpineRuntime = this.experimentalFlags.enabled("agent_spine_timeline") ? new AgentSpineSessionRuntime(options.agentSpineRuntime) : void 0;
304605
305482
  this.visionReader = (this.experimentalFlags.enabled("vision_reader") ? createVisionReader(options.config?.services?.visionReader) : void 0) ?? createManagedMediaVisionReader(options.toolServices?.media);
@@ -304641,14 +305518,15 @@ var Session$1 = class {
304641
305518
  cwd: options.kaos.getcwd(),
304642
305519
  servers: options.lspServers
304643
305520
  });
305521
+ this.idleAdmission = new SessionIdleAdmission(this, this.admission, () => this.constructingAgents.size > 0 || this.agentOperations.pending.length > 0 || this.hostOperations.pending.length > 0, () => `${this.agentOperations.version}:${this.hostOperations.version}`);
304644
305522
  this.mcp.onStatusChange((entry) => {
304645
305523
  this.onMcpServerStatusChange(entry);
304646
305524
  });
304647
- this.skillsReady = this.loadSkills().catch((error) => {
305525
+ this.skillsReady = this.hostOperations.run("initial-skills", () => this.loadSkills().catch((error) => {
304648
305526
  this.log.error("skills load failed", error);
304649
305527
  }).then(() => {
304650
305528
  this.refreshAgentBuiltinTools();
304651
- });
305529
+ }));
304652
305530
  if (!this.agentSpineRuntime?.hasRecipients) this.startMcpServers();
304653
305531
  }
304654
305532
  setToolKaos(kaos) {
@@ -304733,6 +305611,7 @@ var Session$1 = class {
304733
305611
  return { warning };
304734
305612
  }
304735
305613
  async close() {
305614
+ this.admission.close();
304736
305615
  try {
304737
305616
  await Promise.allSettled(Array.from(this.readyAgents(), async (agent) => agent.cron?.stop()));
304738
305617
  await this.cancelActiveTurnsOnClose();
@@ -304748,6 +305627,7 @@ var Session$1 = class {
304748
305627
  }
304749
305628
  }
304750
305629
  async closeForReload() {
305630
+ this.admission.close();
304751
305631
  try {
304752
305632
  await Promise.allSettled(Array.from(this.readyAgents(), async (agent) => agent.cron?.stop()));
304753
305633
  await this.flushMetadata();
@@ -304865,9 +305745,7 @@ var Session$1 = class {
304865
305745
  }
304866
305746
  async createAgent(config, options = {}) {
304867
305747
  this.assertWorkAdmissionOpen();
304868
- const completion = this.createAgentInner(config, options);
304869
- this.agentOperations.track("create", completion);
304870
- return completion;
305748
+ return this.agentOperations.run("create", () => this.createAgentInner(config, options));
304871
305749
  }
304872
305750
  async createAgentInner(config, options) {
304873
305751
  await this.skillsReady;
@@ -304901,13 +305779,44 @@ var Session$1 = class {
304901
305779
  }
304902
305780
  }
304903
305781
  assertWorkAdmissionOpen() {
304904
- if (this.workAdmissionClosed) throw new Error("Session work admission is closed for quiescence");
305782
+ if (this.admission.closed) throw new Error("Session work admission is closed for quiescence");
305783
+ }
305784
+ assertNoIdleReservation() {
305785
+ if (this.admission.reserved) throw new Error("Session work admission is reserved");
305786
+ }
305787
+ tryReserveIdleAdmission() {
305788
+ return this.idleAdmission.tryReserve();
304905
305789
  }
304906
305790
  async runHostOperation(id, operation) {
304907
305791
  this.assertWorkAdmissionOpen();
304908
- const completion = operation();
304909
- this.hostOperations.track(id, completion);
304910
- return completion;
305792
+ return this.hostOperations.run(id, operation);
305793
+ }
305794
+ runHostEffect(id, operation, kind = "work") {
305795
+ this.assertNoIdleReservation();
305796
+ if (kind === "work") this.assertWorkAdmissionOpen();
305797
+ let result;
305798
+ this.hostOperations.run(id, () => {
305799
+ result = operation();
305800
+ return Promise.resolve(result);
305801
+ });
305802
+ return result;
305803
+ }
305804
+ get pendingHostOperations() {
305805
+ return this.hostOperations.pending;
305806
+ }
305807
+ get pendingAgentOperations() {
305808
+ return this.agentOperations.pending;
305809
+ }
305810
+ pendingSessionOperations() {
305811
+ return [...this.agentOperations.pending.map((id) => ({
305812
+ kind: "agent-setup",
305813
+ id,
305814
+ reason: "execution-pending"
305815
+ })), ...this.hostOperations.pending.map((id) => ({
305816
+ kind: "host-operation",
305817
+ id,
305818
+ reason: "execution-pending"
305819
+ }))];
304911
305820
  }
304912
305821
  ownedAgents() {
304913
305822
  const result = new Map(this.constructingAgents);
@@ -304955,7 +305864,7 @@ var Session$1 = class {
304955
305864
  /** Explicit bounded host shutdown, independent of close/stop and keepAlive policy. */
304956
305865
  async quiesce(options) {
304957
305866
  const deadline = new QuiescenceDeadline(options.deadlineAt);
304958
- this.workAdmissionClosed = true;
305867
+ this.admission.close();
304959
305868
  const agents = this.ownedAgents();
304960
305869
  const hooks = new Map([[this.hookEngine, void 0]]);
304961
305870
  for (const [agentId, agent] of agents) if (agent.hooks !== void 0 && !hooks.has(agent.hooks)) hooks.set(agent.hooks, agentId);
@@ -305026,6 +305935,7 @@ var Session$1 = class {
305026
305935
  this.agentOperations.drain(),
305027
305936
  this.hostOperations.drain()
305028
305937
  ]));
305938
+ await deadline.wait(this.hostOperations.drain());
305029
305939
  const pending = [...Array.from(remaining.values()).flat(), ...hookFailures];
305030
305940
  for (const [engine, agentId] of hooks) for (const item of engine.pendingExecutions) pending.push({
305031
305941
  agentId,
@@ -305064,17 +305974,11 @@ var Session$1 = class {
305064
305974
  reason: "execution-pending"
305065
305975
  });
305066
305976
  }
305067
- for (const id of this.agentOperations.pending) pending.push({
305068
- kind: "agent-setup",
305069
- id,
305070
- reason: "execution-pending"
305071
- });
305072
- for (const id of this.hostOperations.pending) pending.push({
305073
- kind: "host-operation",
305074
- id,
305075
- reason: "execution-pending"
305076
- });
305077
- if (!deadline.expired && pending.length === 0) pending.push(...await this.flushQuiescentRecords(agents, deadline));
305977
+ pending.push(...this.pendingSessionOperations());
305978
+ if (!deadline.expired && pending.length === 0) {
305979
+ pending.push(...await this.flushQuiescentRecords(agents, deadline));
305980
+ pending.push(...this.pendingSessionOperations());
305981
+ }
305078
305982
  const checkedAt = Date.now();
305079
305983
  if (!deadline.expired && pending.length === 0) return {
305080
305984
  sessionId: this.options.id,
@@ -305164,12 +306068,12 @@ var Session$1 = class {
305164
306068
  }
305165
306069
  }
305166
306070
  /**
305167
- * Appends a fresh `<plugin_session_start>` system reminder to the main agent
306071
+ * Appends changed `<plugin_session_start>` guidance to the main agent
305168
306072
  * using the currently enabled plugins, then flushes records so the reminder is
305169
306073
  * persisted and visible on the wire. Used by the explicit `/reload` flow after
305170
306074
  * the session has been re-resumed with reloaded plugin state.
305171
306075
  *
305172
- * When no plugin session start is currently resolvable but an earlier
306076
+ * Identical latest guidance is reused without growing the replayed context.
305173
306077
  * When no plugin session start is currently resolvable but the context may still
305174
306078
  * carry stale plugin guidance — either an earlier `<plugin_session_start>`
305175
306079
  * reminder, or a compaction summary that may have folded one in — appends a
@@ -305184,15 +306088,12 @@ var Session$1 = class {
305184
306088
  registry: mainAgent.skills?.registry,
305185
306089
  log: mainAgent.log
305186
306090
  });
305187
- if (reminder !== void 0) mainAgent.context.appendSystemReminder(`${reminder}\n\nThis supersedes any earlier plugin_session_start reminder in this session.`, {
305188
- kind: "injection",
305189
- variant: "plugin_session_start"
305190
- });
305191
- else if (this.shouldNeutralizePluginSessionStart(mainAgent)) mainAgent.context.appendSystemReminder("There are currently no active plugin session starts. This supersedes any earlier plugin_session_start reminder in this session.", {
306091
+ const content = reminder !== void 0 ? `${reminder}\n\nThis supersedes any earlier plugin_session_start reminder in this session.` : this.shouldNeutralizePluginSessionStart(mainAgent) ? "There are currently no active plugin session starts. This supersedes any earlier plugin_session_start reminder in this session." : void 0;
306092
+ if (content === void 0) return;
306093
+ if (latestMatchingSystemReminderIndex(mainAgent.context.history, "plugin_session_start", content) === void 0) mainAgent.context.appendSystemReminder(content, {
305192
306094
  kind: "injection",
305193
306095
  variant: "plugin_session_start"
305194
306096
  });
305195
- else return;
305196
306097
  await mainAgent.records.flush();
305197
306098
  }
305198
306099
  shouldNeutralizePluginSessionStart(mainAgent) {
@@ -305210,26 +306111,35 @@ var Session$1 = class {
305210
306111
  return join$1(this.options.homedir, "state.json");
305211
306112
  }
305212
306113
  writeMetadata() {
305213
- const text = JSON.stringify(this.metadata, null, 2);
305214
- const write = async () => {
305215
- await this.persistenceKaos.mkdir(this.options.homedir, {
305216
- parents: true,
305217
- existOk: true
305218
- });
305219
- await this.persistenceKaos.writeText(this.metadataPath, text);
305220
- };
305221
- this.writeMetadataPromise = this.writeMetadataPromise.then(write, write);
305222
- return this.writeMetadataPromise;
306114
+ this.assertNoIdleReservation();
306115
+ return this.hostOperations.run("metadata-write", () => {
306116
+ const text = JSON.stringify(this.metadata, null, 2);
306117
+ const write = async () => {
306118
+ await this.persistenceKaos.mkdir(this.options.homedir, {
306119
+ parents: true,
306120
+ existOk: true
306121
+ });
306122
+ await this.persistenceKaos.writeText(this.metadataPath, text);
306123
+ };
306124
+ this.writeMetadataPromise = this.writeMetadataPromise.then(write, write);
306125
+ return this.writeMetadataPromise;
306126
+ });
305223
306127
  }
305224
306128
  async readMetadata() {
305225
- const text = await this.persistenceKaos.readText(this.metadataPath);
305226
- this.metadata = JSON.parse(text);
305227
- return this.metadata;
306129
+ this.assertNoIdleReservation();
306130
+ return this.hostOperations.run("metadata-read", async () => {
306131
+ const text = await this.persistenceKaos.readText(this.metadataPath);
306132
+ this.metadata = JSON.parse(text);
306133
+ return this.metadata;
306134
+ });
305228
306135
  }
305229
306136
  async flushMetadata() {
305230
- await this.skillsReady;
305231
- await this.writeMetadataPromise;
305232
- await Promise.all(Array.from(this.readyAgents()).map((agent) => agent.records.flush()));
306137
+ this.assertNoIdleReservation();
306138
+ return this.hostOperations.run("metadata-flush", async () => {
306139
+ await this.skillsReady;
306140
+ await this.writeMetadataPromise;
306141
+ await Promise.all(Array.from(this.readyAgents()).map((agent) => agent.records.flush()));
306142
+ });
305233
306143
  }
305234
306144
  async listSkills() {
305235
306145
  await this.skillsReady;
@@ -305312,9 +306222,9 @@ var Session$1 = class {
305312
306222
  startMcpServers() {
305313
306223
  if (this.mcpLoadStarted) return;
305314
306224
  this.mcpLoadStarted = true;
305315
- this.loadMcpServers().catch((error) => {
306225
+ this.hostOperations.run("initial-mcp", () => this.loadMcpServers().catch((error) => {
305316
306226
  this.emitInitialMcpLoadError(error);
305317
- });
306227
+ }));
305318
306228
  }
305319
306229
  emitInitialMcpLoadError(error) {
305320
306230
  const message = error instanceof Error ? error.message : String(error);
@@ -305385,6 +306295,7 @@ var Session$1 = class {
305385
306295
  additionalDirs: parentAgent?.getAdditionalDirs() ?? this.additionalDirs,
305386
306296
  systemPromptContextProvider: () => prepareSystemPromptContext(this.systemContextKaos(agent.kaos.getcwd()), this.options.blunHomeDir, { additionalDirs: agent.getAdditionalDirs() })
305387
306297
  });
306298
+ this.idleAdmission.register(id, agent);
305388
306299
  agent.setRuntimeSystemPromptAppend(this.accountMemoryContext.text, this.accountMemoryContext.source);
305389
306300
  return agent;
305390
306301
  }
@@ -305423,8 +306334,7 @@ var Session$1 = class {
305423
306334
  const entry = this.agents.get(id);
305424
306335
  if (entry !== void 0) return this.resolveAgentEntry(entry);
305425
306336
  this.assertWorkAdmissionOpen();
305426
- const promise = this.resumePersistedAgent(id, stack);
305427
- this.agentOperations.track(id, promise);
306337
+ const promise = this.agentOperations.run(id, () => this.resumePersistedAgent(id, stack));
305428
306338
  this.agents.set(id, promise);
305429
306339
  return promise;
305430
306340
  }
@@ -306463,13 +307373,30 @@ function decodeRefSegments(segments) {
306463
307373
  }).join("/");
306464
307374
  }
306465
307375
  //#endregion
307376
+ //#region ../../packages/agent-core/src/plugin/work.ts
307377
+ const EFFECTS = [
307378
+ "load",
307379
+ "install",
307380
+ "setEnabled",
307381
+ "setMcpServerEnabled",
307382
+ "remove",
307383
+ "reload",
307384
+ "enabledCommands"
307385
+ ];
307386
+ function bindPluginWork(manager) {
307387
+ for (const name of EFFECTS) bindHostEffectMethod(manager, manager, name, true, "work", `plugin:${name}`);
307388
+ }
307389
+ //#endregion
306466
307390
  //#region ../../packages/agent-core/src/plugin/manager.ts
306467
307391
  const BLUN_NODE_FALLBACK_SUBCOMMAND = "__plugin_run_node";
306468
307392
  var PluginManager = class {
306469
307393
  blunHomeDir;
306470
307394
  disabledPluginIds;
306471
307395
  records = /* @__PURE__ */ new Map();
307396
+ admission = new AdmissionGate();
307397
+ operations = new ExecutionLedger();
306472
307398
  constructor(options) {
307399
+ bindPluginWork(this);
306473
307400
  this.blunHomeDir = options.blunHomeDir;
306474
307401
  this.disabledPluginIds = new Set((options.disabledPluginIds ?? disabledPluginIdsFromEnv()).map(normalizePluginId));
306475
307402
  }
@@ -306479,6 +307406,24 @@ var PluginManager = class {
306479
307406
  for (const entry of file.plugins) next.set(entry.id, await this.materialize(entry));
306480
307407
  this.records = next;
306481
307408
  }
307409
+ get hasPendingWork() {
307410
+ return this.operations.pending.length > 0;
307411
+ }
307412
+ get version() {
307413
+ return this.operations.version;
307414
+ }
307415
+ tryReserveIdleAdmission() {
307416
+ return this.hasPendingWork ? void 0 : this.admission.tryReserve();
307417
+ }
307418
+ runHostEffect(id, operation) {
307419
+ if (this.admission.closed) throw new Error("Plugin work admission is closed");
307420
+ let result;
307421
+ this.operations.run(id, () => {
307422
+ result = operation();
307423
+ return Promise.resolve(result);
307424
+ });
307425
+ return result;
307426
+ }
306482
307427
  list() {
306483
307428
  return [...this.records.values()].toSorted((a, b) => a.id.localeCompare(b.id));
306484
307429
  }
@@ -337368,6 +338313,88 @@ function managedApiKeyFingerprint(apiKey) {
337368
338313
  return createHash("sha256").update(`${MANAGED_PROVIDER_NAME$1}\0${apiKey.trim()}`).digest("hex");
337369
338314
  }
337370
338315
  //#endregion
338316
+ //#region ../../packages/agent-core/src/session/rpc-work.ts
338317
+ const SESSION_RPC_WORK = {
338318
+ prompt: "work",
338319
+ promptAccepted: "work",
338320
+ runShellCommand: "work",
338321
+ cancelShellCommand: "teardown",
338322
+ steer: "work",
338323
+ cancel: "teardown",
338324
+ undoHistory: "work",
338325
+ setThinking: "work",
338326
+ setActionStyle: "work",
338327
+ setPermission: "work",
338328
+ setModel: "work",
338329
+ getModel: "read",
338330
+ enterPlan: "work",
338331
+ cancelPlan: "work",
338332
+ clearPlan: "work",
338333
+ getPlanMode: "read",
338334
+ enterSwarm: "work",
338335
+ exitSwarm: "work",
338336
+ getSwarmMode: "read",
338337
+ getSwarmModeTrigger: "read",
338338
+ beginCompaction: "work",
338339
+ cancelCompaction: "teardown",
338340
+ registerTool: "work",
338341
+ unregisterTool: "work",
338342
+ setActiveTools: "work",
338343
+ setAllowedTools: "work",
338344
+ stopBackground: "teardown",
338345
+ detachBackground: "work",
338346
+ clearContext: "work",
338347
+ activateSkill: "work",
338348
+ activatePluginCommand: "work",
338349
+ startBtw: "work",
338350
+ createGoal: "work",
338351
+ editGoal: "work",
338352
+ blockGoal: "work",
338353
+ completeGoal: "work",
338354
+ getGoal: "read",
338355
+ pauseGoal: "work",
338356
+ resumeGoal: "work",
338357
+ cancelGoal: "work",
338358
+ getBackgroundOutput: "read",
338359
+ getContext: "read",
338360
+ getConfig: "read",
338361
+ getPermission: "read",
338362
+ getPlan: "read",
338363
+ getUsage: "read",
338364
+ getTools: "read",
338365
+ getBackground: "read",
338366
+ complete: "work",
338367
+ quiesceSession: "quiesce",
338368
+ setAccountMemoryContext: "sync-work",
338369
+ setRuntimeModelContext: "sync-work",
338370
+ setPersonalMemoryHostCapability: "work",
338371
+ renameSession: "work",
338372
+ updateSessionMetadata: "work",
338373
+ getSessionMetadata: "read",
338374
+ listSkills: "read",
338375
+ listPluginCommands: "read",
338376
+ listMcpServers: "read",
338377
+ getMcpStartupMetrics: "read",
338378
+ reconnectMcpServer: "work",
338379
+ generateAgentsMd: "work",
338380
+ getSessionWarnings: "read",
338381
+ waitForBackgroundTasksOnPrint: "teardown",
338382
+ addAdditionalDir: "work",
338383
+ createLoop: "work",
338384
+ getLoop: "read",
338385
+ pauseLoop: "teardown",
338386
+ resumeLoop: "work",
338387
+ stopLoop: "teardown"
338388
+ };
338389
+ function bindSessionRpcWork(methods, session) {
338390
+ for (const [name, kind] of Object.entries(SESSION_RPC_WORK)) {
338391
+ if (kind === "read" || kind === "quiesce") continue;
338392
+ const asynchronous = kind !== "sync-work";
338393
+ const detail = name === "runShellCommand" ? "shell-command" : `rpc:${name}`;
338394
+ bindHostEffectMethod(methods, session, name, asynchronous, kind === "teardown" ? "teardown" : "work", detail);
338395
+ }
338396
+ }
338397
+ //#endregion
337371
338398
  //#region ../../packages/agent-core/src/session/prompt-metadata.ts
337372
338399
  const MAX_TITLE_LENGTH = 200;
337373
338400
  const MAX_LAST_PROMPT_LENGTH = 4e3;
@@ -337414,6 +338441,7 @@ var SessionAPIImpl = class {
337414
338441
  session;
337415
338442
  constructor(session) {
337416
338443
  this.session = session;
338444
+ bindSessionRpcWork(this, session);
337417
338445
  }
337418
338446
  quiesceSession(payload) {
337419
338447
  return this.session.quiesce(payload);
@@ -337496,7 +338524,7 @@ var SessionAPIImpl = class {
337496
338524
  return (await this.getAgent(agentId)).steer(payload);
337497
338525
  }
337498
338526
  async runShellCommand({ agentId, ...payload }) {
337499
- return this.session.runHostOperation("shell-command", async () => (await this.getAgent(agentId)).runShellCommand(payload));
338527
+ return (await this.getAgent(agentId)).runShellCommand(payload);
337500
338528
  }
337501
338529
  async cancelShellCommand({ agentId, ...payload }) {
337502
338530
  return (await this.getAgent(agentId, true)).cancelShellCommand(payload);
@@ -338183,6 +339211,169 @@ function compareSessionSummary(a, b) {
338183
339211
  return 0;
338184
339212
  }
338185
339213
  //#endregion
339214
+ //#region ../../packages/agent-core/src/rpc/core-work.ts
339215
+ const CORE_RPC_WORK = {
339216
+ ...SESSION_RPC_WORK,
339217
+ getSessionWarnings: "work",
339218
+ getCoreInfo: "read",
339219
+ getExperimentalFeatures: "read",
339220
+ getConfigDiagnostics: "read",
339221
+ setMcpServerEnabled: "work",
339222
+ removeMcpServer: "work",
339223
+ getBlunConfig: "work",
339224
+ setBlunConfig: "work",
339225
+ removeBlunProvider: "work",
339226
+ createSession: "work",
339227
+ closeSession: "teardown",
339228
+ archiveSession: "work",
339229
+ resumeSession: "work",
339230
+ getReplayPage: "work",
339231
+ reloadSession: "work",
339232
+ forkSession: "work",
339233
+ listSessions: "work",
339234
+ exportSession: "work",
339235
+ listPlugins: "work",
339236
+ installPlugin: "work",
339237
+ setPluginEnabled: "work",
339238
+ setPluginMcpServerEnabled: "work",
339239
+ removePlugin: "work",
339240
+ reloadPlugins: "work",
339241
+ getPluginInfo: "work"
339242
+ };
339243
+ function bindCoreWork(core) {
339244
+ for (const [name, kind] of Object.entries(CORE_RPC_WORK)) {
339245
+ if (kind === "read" || kind === "quiesce") continue;
339246
+ bindHostEffectMethod(core, core, name, kind !== "sync-work", kind === "teardown" ? "teardown" : "work", `core:${name}`);
339247
+ }
339248
+ for (const name of ["createSessionWithOverrides", "resumeSessionWithOverrides"]) bindHostEffectMethod(core, core, name, true, "work", `core:${name}`);
339249
+ }
339250
+ //#endregion
339251
+ //#region ../../packages/agent-core/src/rpc/idle-admission.ts
339252
+ /** The Core session map stays authoritative; receipts identify native construction only. */
339253
+ var CoreIdleAdmission = class {
339254
+ core;
339255
+ admission;
339256
+ pending;
339257
+ state;
339258
+ revision;
339259
+ identities = /* @__PURE__ */ new WeakSet();
339260
+ plugins;
339261
+ constructor(core, admission, pending, state, revision) {
339262
+ this.core = core;
339263
+ this.admission = admission;
339264
+ this.pending = pending;
339265
+ this.state = state;
339266
+ this.revision = revision;
339267
+ this.plugins = core.plugins;
339268
+ }
339269
+ register(session) {
339270
+ this.identities.add(session);
339271
+ }
339272
+ snapshot() {
339273
+ if (this.pending() || this.core.plugins !== this.plugins || !(this.plugins instanceof PluginManager) || this.plugins.hasPendingWork) return void 0;
339274
+ const sessions = [...this.core.sessions];
339275
+ if (sessions.some(([id, session]) => !(session instanceof Session$1) || !this.identities.has(session) || session.options.id !== id || session.options.blunHomeDir !== this.core.homeDir)) return void 0;
339276
+ return {
339277
+ sessions,
339278
+ state: this.state(),
339279
+ pluginVersion: this.plugins.version,
339280
+ plugins: JSON.stringify(this.plugins.list()),
339281
+ homeDir: this.core.homeDir,
339282
+ configPath: this.core.configPath
339283
+ };
339284
+ }
339285
+ tryReserve() {
339286
+ const ownRelease = this.admission.tryReserve();
339287
+ if (!ownRelease) return void 0;
339288
+ const releases = [ownRelease];
339289
+ const sessions = [];
339290
+ let retained = false;
339291
+ const releaseAll = () => {
339292
+ for (const session of [...sessions].reverse()) session.release();
339293
+ for (const release of [...releases].reverse()) release();
339294
+ };
339295
+ try {
339296
+ const snapshot = this.snapshot();
339297
+ if (!snapshot) return void 0;
339298
+ const pluginRelease = this.plugins.tryReserveIdleAdmission();
339299
+ if (!pluginRelease) return void 0;
339300
+ releases.push(pluginRelease);
339301
+ for (const [, session] of snapshot.sessions) {
339302
+ const lease = session.tryReserveIdleAdmission();
339303
+ if (!lease) return void 0;
339304
+ sessions.push(lease);
339305
+ }
339306
+ const lease = new IdleAdmissionReservation(releases, () => {
339307
+ const fresh = this.snapshot();
339308
+ return fresh !== void 0 && fresh.homeDir === snapshot.homeDir && fresh.configPath === snapshot.configPath && fresh.state.version === snapshot.state.version && fresh.state.text === snapshot.state.text && fresh.state.owners.length === snapshot.state.owners.length && fresh.state.owners.every((owner, index) => owner === snapshot.state.owners[index]) && fresh.pluginVersion === snapshot.pluginVersion && fresh.plugins === snapshot.plugins && fresh.sessions.length === snapshot.sessions.length && fresh.sessions.every(([id, session], index) => id === snapshot.sessions[index][0] && session === snapshot.sessions[index][1]);
339309
+ }, () => !this.pending() && this.revision() === snapshot.state.version && this.core.homeDir === snapshot.homeDir && this.core.configPath === snapshot.configPath && this.core.plugins === this.plugins && this.plugins.version === snapshot.pluginVersion && this.core.sessions.size === snapshot.sessions.length && snapshot.sessions.every(([id, session]) => this.core.sessions.get(id) === session), sessions);
339310
+ if (!lease.isValid()) return void 0;
339311
+ retained = true;
339312
+ return lease;
339313
+ } catch {
339314
+ return;
339315
+ } finally {
339316
+ if (!retained) releaseAll();
339317
+ }
339318
+ }
339319
+ };
339320
+ //#endregion
339321
+ //#region ../../packages/agent-core/src/rpc/config-idle-admission.ts
339322
+ /** Extend the actual Core reservation with the same native claim used by config writes. */
339323
+ async function reserveCoreConfiguration(core, configState) {
339324
+ const local = core.tryReserveIdleAdmission();
339325
+ if (!local) return void 0;
339326
+ let file;
339327
+ let retained = false;
339328
+ const releaseAll = async () => {
339329
+ await file?.release();
339330
+ local.release();
339331
+ };
339332
+ try {
339333
+ file = await tryReserveConfigIdleAdmission(core.configPath);
339334
+ if (!file) return void 0;
339335
+ const owned = file;
339336
+ let live = true;
339337
+ let releasing;
339338
+ const isValid = async () => {
339339
+ if (!live) return false;
339340
+ try {
339341
+ const loaded = loadRuntimeConfigSafe(core.configPath);
339342
+ live = loaded.fileError === void 0 && configState() === JSON.stringify({
339343
+ config: loaded.config,
339344
+ warnings: [...loaded.fileWarnings, ...loaded.envWarnings]
339345
+ }) && local.isValid() && await owned.isValid() && live && local.isValid();
339346
+ } catch {
339347
+ live = false;
339348
+ }
339349
+ return live;
339350
+ };
339351
+ if (!await isValid()) return void 0;
339352
+ retained = true;
339353
+ return {
339354
+ isValid,
339355
+ commit: async (validateLocalInput = () => true) => {
339356
+ if (!await isValid() || !live) return false;
339357
+ try {
339358
+ if (validateLocalInput() !== true) return false;
339359
+ } catch {
339360
+ return false;
339361
+ }
339362
+ if (!live || !local.commit()) return false;
339363
+ live = false;
339364
+ return true;
339365
+ },
339366
+ release: () => {
339367
+ live = false;
339368
+ releasing ??= releaseAll();
339369
+ return releasing;
339370
+ }
339371
+ };
339372
+ } finally {
339373
+ if (!retained) await releaseAll();
339374
+ }
339375
+ }
339376
+ //#endregion
338186
339377
  //#region ../../packages/agent-core/src/rpc/core-impl.ts
338187
339378
  const BLUN_PROVIDER_NAME = "managed:blun";
338188
339379
  const BLUN_BASE_URL_ENV = "BLUN_BASE_URL";
@@ -338208,6 +339399,9 @@ var BlunCore = class {
338208
339399
  sessionStore;
338209
339400
  sessionWriters = /* @__PURE__ */ new Map();
338210
339401
  sessionMutations = /* @__PURE__ */ new Map();
339402
+ admission = new AdmissionGate();
339403
+ hostOperations = new ExecutionLedger();
339404
+ idleAdmission;
338211
339405
  plugins;
338212
339406
  pluginsReady;
338213
339407
  pluginsLoadError;
@@ -338215,6 +339409,7 @@ var BlunCore = class {
338215
339409
  experimentalFlags;
338216
339410
  constructor(rpcClient, options = {}) {
338217
339411
  this.rpcClient = rpcClient;
339412
+ bindCoreWork(this);
338218
339413
  this.homeDir = resolveBlunHome$1(options.homeDir);
338219
339414
  this.userHomeDir = homedir();
338220
339415
  this.configPath = resolveConfigPath({
@@ -338238,6 +339433,19 @@ var BlunCore = class {
338238
339433
  this.experimentalFlags = new FlagResolver(process.env, FLAG_DEFINITIONS, this.config.experimental);
338239
339434
  this.sessionStore = new SessionStore(this.homeDir);
338240
339435
  this.plugins = new PluginManager({ blunHomeDir: this.homeDir });
339436
+ this.idleAdmission = new CoreIdleAdmission(this, this.admission, () => this.hostOperations.pending.length > 0 || this.sessionMutations.size > 0 || this.pluginsLoadError !== void 0, () => ({
339437
+ version: this.hostOperations.version,
339438
+ text: JSON.stringify({
339439
+ config: this.config,
339440
+ warnings: this.configWarnings
339441
+ }),
339442
+ owners: [
339443
+ this.config,
339444
+ this.runtime,
339445
+ this.kaos,
339446
+ this.pluginsReady
339447
+ ]
339448
+ }), () => this.hostOperations.version);
338241
339449
  this.pluginsReady = this.plugins.load().catch((error) => {
338242
339450
  this.pluginsLoadError = error instanceof Error ? error : new Error(String(error));
338243
339451
  });
@@ -338247,6 +339455,30 @@ var BlunCore = class {
338247
339455
  async createSession(input) {
338248
339456
  return this.createSessionWithOverrides(input, {});
338249
339457
  }
339458
+ tryReserveIdleAdmission() {
339459
+ return this.idleAdmission.tryReserve();
339460
+ }
339461
+ tryReserveConfigurationIdleAdmission() {
339462
+ return reserveCoreConfiguration(this, () => JSON.stringify({
339463
+ config: this.config,
339464
+ warnings: this.configWarnings
339465
+ }));
339466
+ }
339467
+ get pendingHostOperations() {
339468
+ return this.hostOperations.pending;
339469
+ }
339470
+ runHostEffect(id, operation, kind = "work") {
339471
+ if (this.admission.reserved || kind === "work" && this.admission.closed) throw new Error("Core work admission is closed");
339472
+ return this.runHostCallback(id, operation);
339473
+ }
339474
+ runHostCallback(id, operation) {
339475
+ let result;
339476
+ this.hostOperations.run(id, () => {
339477
+ result = operation();
339478
+ return Promise.resolve(result);
339479
+ });
339480
+ return result;
339481
+ }
338250
339482
  async createSessionWithOverrides(input, overrides) {
338251
339483
  const id = input.id ?? createSessionId();
338252
339484
  const workDir = requiredWorkDir("createSession", input.workDir);
@@ -338318,6 +339550,7 @@ var BlunCore = class {
338318
339550
  additionalDirs,
338319
339551
  missionContract: options.metadata?.["__missionContract"]
338320
339552
  });
339553
+ this.idleAdmission.register(session);
338321
339554
  try {
338322
339555
  session.metadata = {
338323
339556
  ...session.metadata,
@@ -338468,6 +339701,7 @@ var BlunCore = class {
338468
339701
  appVersion: this.appVersion,
338469
339702
  additionalDirs
338470
339703
  });
339704
+ this.idleAdmission.register(session);
338471
339705
  let warning;
338472
339706
  try {
338473
339707
  warning = (await session.resume()).warning;
@@ -347794,6 +349028,212 @@ function defaultShell() {
347794
349028
  }
347795
349029
  registerSingleton(ITerminalService, new SyncDescriptor(TerminalService, [{}], false));
347796
349030
  //#endregion
349031
+ //#region ../../packages/node-sdk/src/rpc-work.ts
349032
+ const SDK_RPC_WORK = {
349033
+ withInteractiveAgent: "callback",
349034
+ ensureConfigFile: "work",
349035
+ close: "teardown",
349036
+ quiesceSession: "quiesce",
349037
+ createSession: "work",
349038
+ createSessionWithKaos: "work",
349039
+ resumeSession: "work",
349040
+ resumeSessionWithKaos: "work",
349041
+ reloadSession: "work",
349042
+ forkSession: "work",
349043
+ closeSession: "teardown",
349044
+ getReplayPage: "work",
349045
+ listSessions: "work",
349046
+ renameSession: "work",
349047
+ exportSession: "work",
349048
+ getConfig: "work",
349049
+ getConfigDiagnostics: "read",
349050
+ getExperimentalFeatures: "read",
349051
+ setConfig: "work",
349052
+ removeProvider: "work",
349053
+ prompt: "work",
349054
+ promptAccepted: "work",
349055
+ runShellCommand: "work",
349056
+ cancelShellCommand: "teardown",
349057
+ steer: "work",
349058
+ steerActive: "work",
349059
+ generateAgentsMd: "work",
349060
+ getSessionWarnings: "work",
349061
+ getSessionMetadata: "read",
349062
+ updateSessionMetadata: "work",
349063
+ addAdditionalDir: "work",
349064
+ startBtw: "work",
349065
+ cancel: "teardown",
349066
+ registerTool: "work",
349067
+ unregisterTool: "work",
349068
+ setModel: "work",
349069
+ setRuntimeModelContext: "work",
349070
+ setAccountMemoryContext: "work",
349071
+ setPersonalMemoryHostCapability: "work",
349072
+ setThinking: "work",
349073
+ setActionStyle: "work",
349074
+ setPermission: "work",
349075
+ setAllowedTools: "work",
349076
+ setPlanMode: "work",
349077
+ setSwarmMode: "work",
349078
+ getSwarmModeTrigger: "read",
349079
+ swarm: "work",
349080
+ swarmAccepted: "work",
349081
+ getPlan: "read",
349082
+ clearPlan: "work",
349083
+ compact: "work",
349084
+ cancelCompaction: "teardown",
349085
+ undoHistory: "work",
349086
+ getContext: "read",
349087
+ getUsage: "read",
349088
+ getStatus: "read",
349089
+ listTools: "read",
349090
+ listSkills: "read",
349091
+ listPluginCommands: "read",
349092
+ listBackgroundTasks: "read",
349093
+ getBackgroundTaskOutput: "read",
349094
+ complete: "work",
349095
+ stopBackgroundTask: "teardown",
349096
+ detachBackgroundTask: "work",
349097
+ waitForBackgroundTasksOnPrint: "teardown",
349098
+ createGoal: "work",
349099
+ editGoal: "work",
349100
+ blockGoal: "work",
349101
+ completeGoal: "work",
349102
+ getGoal: "read",
349103
+ pauseGoal: "work",
349104
+ resumeGoal: "work",
349105
+ cancelGoal: "work",
349106
+ createLoop: "work",
349107
+ getLoop: "read",
349108
+ pauseLoop: "teardown",
349109
+ resumeLoop: "work",
349110
+ stopLoop: "teardown",
349111
+ listMcpServers: "read",
349112
+ getMcpStartupMetrics: "read",
349113
+ reconnectMcpServer: "work",
349114
+ setMcpServerEnabled: "work",
349115
+ removeMcpServer: "work",
349116
+ listPlugins: "work",
349117
+ installPlugin: "work",
349118
+ setPluginEnabled: "work",
349119
+ setPluginMcpServerEnabled: "work",
349120
+ removePlugin: "work",
349121
+ reloadPlugins: "work",
349122
+ getPluginInfo: "work",
349123
+ activateSkill: "work",
349124
+ activatePluginCommand: "work",
349125
+ receiveEvent: "callback",
349126
+ requestApproval: "callback",
349127
+ requestQuestion: "callback",
349128
+ toolCall: "callback"
349129
+ };
349130
+ const HARNESS_WORK = {
349131
+ tryReserveConfigurationIdleAdmission: "quiesce",
349132
+ withInteractiveAgent: "callback",
349133
+ createSession: "work",
349134
+ resumeSession: "work",
349135
+ reloadSession: "work",
349136
+ forkSession: "work",
349137
+ closeSession: "teardown",
349138
+ renameSession: "work",
349139
+ exportSession: "work",
349140
+ listSessions: "work",
349141
+ getConfig: "work",
349142
+ getConfigDiagnostics: "read",
349143
+ complete: "work",
349144
+ getExperimentalFeatures: "read",
349145
+ ensureConfigFile: "work",
349146
+ setConfig: "work",
349147
+ removeProvider: "work",
349148
+ close: "teardown"
349149
+ };
349150
+ const SDK_SESSION_WORK = {
349151
+ quiesce: "quiesce",
349152
+ reloadSession: "work",
349153
+ refreshResumeState: "work",
349154
+ registerToolAndWait: "work",
349155
+ unregisterToolAndWait: "work",
349156
+ setPersonalMemoryHostCapability: "work",
349157
+ registerTool: "sync-work",
349158
+ unregisterTool: "sync-work",
349159
+ prompt: "work",
349160
+ promptAccepted: "work",
349161
+ runShellCommand: "work",
349162
+ cancelShellCommand: "teardown",
349163
+ steer: "work",
349164
+ steerActive: "work",
349165
+ swarm: "work",
349166
+ init: "work",
349167
+ getSessionWarnings: "work",
349168
+ getSessionMetadata: "read",
349169
+ updateSessionMetadata: "work",
349170
+ addAdditionalDir: "work",
349171
+ startBtw: "work",
349172
+ cancel: "teardown",
349173
+ setModel: "work",
349174
+ setRuntimeModelContext: "work",
349175
+ setAccountMemoryContext: "work",
349176
+ setThinking: "work",
349177
+ setActionStyle: "work",
349178
+ setPermission: "work",
349179
+ setAllowedTools: "work",
349180
+ setPlanMode: "work",
349181
+ setSwarmMode: "work",
349182
+ getSwarmModeTrigger: "read",
349183
+ getPlan: "read",
349184
+ clearPlan: "work",
349185
+ compact: "work",
349186
+ cancelCompaction: "teardown",
349187
+ undoHistory: "work",
349188
+ getContext: "read",
349189
+ getReplayPage: "work",
349190
+ getUsage: "read",
349191
+ getStatus: "read",
349192
+ listTools: "read",
349193
+ listSkills: "read",
349194
+ listPluginCommands: "read",
349195
+ listBackgroundTasks: "read",
349196
+ getBackgroundTaskOutput: "read",
349197
+ complete: "work",
349198
+ stopBackgroundTask: "teardown",
349199
+ detachBackgroundTask: "work",
349200
+ waitForBackgroundTasksOnPrint: "teardown",
349201
+ createGoal: "work",
349202
+ getGoal: "read",
349203
+ editGoal: "work",
349204
+ blockGoal: "work",
349205
+ completeGoal: "work",
349206
+ pauseGoal: "work",
349207
+ resumeGoal: "work",
349208
+ cancelGoal: "work",
349209
+ createLoop: "work",
349210
+ getLoop: "read",
349211
+ pauseLoop: "teardown",
349212
+ resumeLoop: "work",
349213
+ stopLoop: "teardown",
349214
+ listMcpServers: "read",
349215
+ getMcpStartupMetrics: "read",
349216
+ reconnectMcpServer: "work",
349217
+ setMcpServerEnabled: "work",
349218
+ removeMcpServer: "work",
349219
+ listPlugins: "work",
349220
+ installPlugin: "work",
349221
+ setPluginEnabled: "work",
349222
+ setPluginMcpServerEnabled: "work",
349223
+ removePlugin: "work",
349224
+ reloadPlugins: "work",
349225
+ getPluginInfo: "work",
349226
+ activateSkill: "work",
349227
+ activatePluginCommand: "work",
349228
+ close: "teardown"
349229
+ };
349230
+ function bindSdkWork(methods, owner, classification, scope) {
349231
+ for (const [name, kind] of Object.entries(classification)) {
349232
+ if (kind !== "work" && kind !== "teardown" && kind !== "sync-work") continue;
349233
+ bindHostEffectMethod(methods, owner, name, kind !== "sync-work", kind === "teardown" ? "teardown" : "work", `${scope}:${name}`);
349234
+ }
349235
+ }
349236
+ //#endregion
347797
349237
  //#region ../../packages/node-sdk/src/session.ts
347798
349238
  const MAIN_AGENT_ID$1 = "main";
347799
349239
  var Session = class {
@@ -347802,6 +349242,7 @@ var Session = class {
347802
349242
  summary;
347803
349243
  resumeState;
347804
349244
  rpc;
349245
+ workOwner;
347805
349246
  onClose;
347806
349247
  userInputAcceptedHandler;
347807
349248
  pendingUserInputAcceptanceRequests = /* @__PURE__ */ new Set();
@@ -347814,6 +349255,8 @@ var Session = class {
347814
349255
  this.resumeState = options.resumeState ?? resumeStateFromSummary(options.summary);
347815
349256
  this.rpc = options.rpc;
347816
349257
  this.onClose = options.onClose;
349258
+ this.workOwner = options.rpc.workOwner;
349259
+ if (this.workOwner !== void 0) bindSdkWork(this, this.workOwner, SDK_SESSION_WORK, "sdk-session");
347817
349260
  }
347818
349261
  getResumeState() {
347819
349262
  this.ensureOpen();
@@ -348018,9 +349461,10 @@ var Session = class {
348018
349461
  if (handler === void 0) return;
348019
349462
  try {
348020
349463
  let pending;
348021
- pending = Promise.resolve(handler(input)).catch(() => {}).finally(() => {
349464
+ const notify = () => Promise.resolve(handler(input)).catch(() => {}).finally(() => {
348022
349465
  this.pendingUserInputAcceptedHandlers.delete(pending);
348023
349466
  });
349467
+ pending = this.workOwner === void 0 ? notify() : this.workOwner.runHostEffect("sdk-session:input-accepted", notify);
348024
349468
  this.pendingUserInputAcceptedHandlers.add(pending);
348025
349469
  } catch {}
348026
349470
  }
@@ -348525,6 +349969,7 @@ var BlunHarness = class {
348525
349969
  homeDir;
348526
349970
  configPath;
348527
349971
  auth;
349972
+ profileScope;
348528
349973
  identity;
348529
349974
  uiMode;
348530
349975
  telemetry;
@@ -348532,6 +349977,7 @@ var BlunHarness = class {
348532
349977
  ensureConfigFileImpl;
348533
349978
  closeImpl;
348534
349979
  sessionStartedProperties;
349980
+ idleCore;
348535
349981
  constructor(rpc, options) {
348536
349982
  this.rpc = rpc;
348537
349983
  this.identity = options.identity;
@@ -348540,13 +349986,23 @@ var BlunHarness = class {
348540
349986
  this.configPath = options.configPath;
348541
349987
  this.telemetry = options.telemetry;
348542
349988
  this.auth = options.auth;
349989
+ this.profileScope = Object.freeze({
349990
+ profile: options.profileScope?.profile,
349991
+ sharedHome: options.profileScope?.sharedHome
349992
+ });
348543
349993
  this.ensureConfigFileImpl = options.ensureConfigFile;
348544
349994
  this.closeImpl = options.onClose;
348545
349995
  this.sessionStartedProperties = options.sessionStartedProperties ?? {};
349996
+ const owner = options.workOwner;
349997
+ this.idleCore = owner instanceof BlunCore && owner === rpc.workOwner && owner.homeDir === options.homeDir && owner.configPath === options.configPath ? owner : void 0;
349998
+ if (options.workOwner !== void 0) bindSdkWork(this, options.workOwner, HARNESS_WORK, "harness");
348546
349999
  }
348547
350000
  get sessions() {
348548
350001
  return this.activeSessions;
348549
350002
  }
350003
+ async tryReserveConfigurationIdleAdmission() {
350004
+ return this.idleCore?.tryReserveConfigurationIdleAdmission();
350005
+ }
348550
350006
  get interactiveAgentId() {
348551
350007
  return this.rpc.interactiveAgentId;
348552
350008
  }
@@ -348777,6 +350233,28 @@ function mapOAuthTokenError(error, providerName) {
348777
350233
  if (error instanceof OAuthConnectionError || error instanceof RetryableRefreshError) return new BlunError(ErrorCodes.PROVIDER_CONNECTION_ERROR, `OAuth provider "${providerName}" failed to fetch an access token: ${error.message}`, { cause: error });
348778
350234
  }
348779
350235
  //#endregion
350236
+ //#region ../../packages/node-sdk/src/auth-work.ts
350237
+ const AUTH_WORK_METHODS = [
350238
+ "restoreMissingProfileModel",
350239
+ "status",
350240
+ "getCachedMemoryWorkspaceId",
350241
+ "cacheMemoryWorkspaceId",
350242
+ "login",
350243
+ "loginWithApiKey",
350244
+ "logout",
350245
+ "getManagedUsage",
350246
+ "getManagedQuota",
350247
+ "submitFeedback",
350248
+ "createFeedbackUploadUrl",
350249
+ "completeFeedbackUpload",
350250
+ "getCachedAccessToken",
350251
+ "resolveOAuthTokenProvider"
350252
+ ];
350253
+ /** Auth uses the existing Core admission and ledger, not a separate work queue. */
350254
+ function bindAuthWork(auth, owner) {
350255
+ for (const method of AUTH_WORK_METHODS) bindHostEffectMethod(auth, owner, method, method !== "resolveOAuthTokenProvider", "work", `auth.${method}`);
350256
+ }
350257
+ //#endregion
348780
350258
  //#region ../../packages/node-sdk/src/auth.ts
348781
350259
  var BlunAuthTransitionError = class extends Error {
348782
350260
  warnings;
@@ -348791,8 +350269,10 @@ var BlunAuthFacade = class {
348791
350269
  options;
348792
350270
  toolkit;
348793
350271
  managedApiKeyRevocations;
350272
+ workOwner;
348794
350273
  constructor(options) {
348795
350274
  this.options = options;
350275
+ this.workOwner = options.workOwner;
348796
350276
  this.managedApiKeyRevocations = new FileManagedApiKeyRevocationStore(options.homeDir);
348797
350277
  this.toolkit = new BlunOAuthToolkit({
348798
350278
  homeDir: options.homeDir,
@@ -348812,6 +350292,7 @@ var BlunAuthFacade = class {
348812
350292
  remove: applyManagedBlunCodeLogoutConfig
348813
350293
  }
348814
350294
  });
350295
+ if (this.workOwner !== void 0) bindAuthWork(this, this.workOwner);
348815
350296
  }
348816
350297
  async restoreMissingProfileModel() {
348817
350298
  const needsRepair = (config) => config.defaultModel === LEGACY_PROFILE_MODEL && config.models?.[LEGACY_PROFILE_MODEL] === void 0 && config.providers["managed:blun"] === void 0;
@@ -349165,11 +350646,14 @@ var BlunAuthFacade = class {
349165
350646
  resolveOAuthTokenProvider = (providerName, oauthRef) => {
349166
350647
  const provider = this.toolkit.tokenProvider(providerName, this.runtimeOAuthRef(providerName, oauthRef));
349167
350648
  return { getAccessToken: async (options) => {
349168
- try {
349169
- return await provider.getAccessToken(options);
349170
- } catch (error) {
349171
- throw mapOAuthTokenError(error, providerName) ?? error;
349172
- }
350649
+ const access = async () => {
350650
+ try {
350651
+ return await provider.getAccessToken(options);
350652
+ } catch (error) {
350653
+ throw mapOAuthTokenError(error, providerName) ?? error;
350654
+ }
350655
+ };
350656
+ return this.workOwner === void 0 ? access() : this.workOwner.runHostEffect("auth.getAccessToken", access);
349173
350657
  } };
349174
350658
  };
349175
350659
  resolveManagedAuth(providerName) {
@@ -349247,12 +350731,16 @@ var SDKRpcClientBase = class {
349247
350731
  approvalHandlers = /* @__PURE__ */ new Map();
349248
350732
  questionHandlers = /* @__PURE__ */ new Map();
349249
350733
  toolCallHandlers = /* @__PURE__ */ new Map();
350734
+ get workOwner() {}
349250
350735
  get interactiveAgentId() {
349251
350736
  return this.interactiveAgentScope.getStore() ?? MAIN_AGENT_ID;
349252
350737
  }
349253
350738
  withInteractiveAgent(agentId, fn) {
349254
350739
  return this.interactiveAgentScope.run(agentId, fn);
349255
350740
  }
350741
+ runCallback(_id, operation) {
350742
+ return operation();
350743
+ }
349256
350744
  async quiesceSession(input) {
349257
350745
  return (await this.getRpc()).quiesceSession(input);
349258
350746
  }
@@ -349835,7 +351323,7 @@ var SDKRpcClientBase = class {
349835
351323
  };
349836
351324
  }
349837
351325
  async receiveEvent(event) {
349838
- await Promise.all([...this.eventListeners].map((listener) => Promise.resolve(listener(event))));
351326
+ await Promise.all([...this.eventListeners].map((listener) => this.runCallback("sdk-callback:event-listener", () => Promise.resolve(listener(event)))));
349839
351327
  }
349840
351328
  setApprovalHandler(sessionId, handler) {
349841
351329
  if (handler === void 0) {
@@ -349952,9 +351440,9 @@ function errorMessage(error) {
349952
351440
  }
349953
351441
  //#endregion
349954
351442
  //#region ../../packages/node-sdk/src/sdk-rpc-client.ts
349955
- function authHomeForProfile(homeDir) {
349956
- const sharedHome = process.env["BLUN_SHARED_HOME"]?.trim();
349957
- const profile = process.env["BLUN_PROFILE"]?.trim();
351443
+ function authHomeForProfile(homeDir, scope) {
351444
+ const sharedHome = scope.sharedHome?.trim();
351445
+ const profile = scope.profile?.trim();
349958
351446
  if (!sharedHome || !profile || !/^[a-z0-9][a-z0-9_-]{0,31}$/u.test(profile)) return homeDir;
349959
351447
  const expected = resolve(join(sharedHome, "profile", profile));
349960
351448
  const actual = resolve(homeDir);
@@ -349967,10 +351455,14 @@ var SDKRpcClient = class extends SDKRpcClientBase {
349967
351455
  identity;
349968
351456
  telemetry;
349969
351457
  auth;
351458
+ profileScope;
349970
351459
  core;
349971
351460
  ready;
349972
351461
  usesDefaultConfigPath;
349973
351462
  restoreProfileModel;
351463
+ get workOwner() {
351464
+ return this.core;
351465
+ }
349974
351466
  constructor(options = {}) {
349975
351467
  super();
349976
351468
  this.identity = options.identity === void 0 ? void 0 : assertBlunHostIdentity(options.identity);
@@ -349980,14 +351472,19 @@ var SDKRpcClient = class extends SDKRpcClientBase {
349980
351472
  configPath: options.configPath
349981
351473
  });
349982
351474
  this.usesDefaultConfigPath = options.configPath === void 0;
349983
- const authHomeDir = authHomeForProfile(this.homeDir);
351475
+ this.profileScope = Object.freeze({
351476
+ profile: process.env["BLUN_PROFILE"],
351477
+ sharedHome: process.env["BLUN_SHARED_HOME"]
351478
+ });
351479
+ const authHomeDir = authHomeForProfile(this.homeDir, this.profileScope);
349984
351480
  this.restoreProfileModel = this.usesDefaultConfigPath && authHomeDir !== this.homeDir;
349985
351481
  this.telemetry = options.telemetry ?? noopTelemetryClient;
349986
351482
  this.auth = new BlunAuthFacade({
349987
351483
  homeDir: authHomeDir,
349988
351484
  configPath: this.configPath,
349989
351485
  identity: this.identity,
349990
- onRefresh: options.onOAuthRefresh
351486
+ onRefresh: options.onOAuthRefresh,
351487
+ workOwner: { runHostEffect: (id, operation) => this.core.runHostEffect(id, operation) }
349991
351488
  });
349992
351489
  getRootLogger().configure(resolveLoggingConfig({ homeDir: this.homeDir }));
349993
351490
  const [coreRpc, sdkRpc] = createRPC();
@@ -350001,6 +351498,12 @@ var SDKRpcClient = class extends SDKRpcClientBase {
350001
351498
  appVersion: this.identity?.version
350002
351499
  });
350003
351500
  this.ready = sdkRpc(new ClientAPI(this));
351501
+ bindSdkWork(this, this.core, SDK_RPC_WORK, "sdk");
351502
+ const callbackOwner = { runHostEffect: (id, operation) => this.runCallback(id, operation) };
351503
+ for (const [name, kind] of Object.entries(SDK_RPC_WORK)) if (kind === "callback" && name !== "withInteractiveAgent") bindHostEffectMethod(this, callbackOwner, name, true, "work", `sdk-callback:${name}`);
351504
+ }
351505
+ runCallback(id, operation) {
351506
+ return this.core.runHostCallback(id, operation);
350004
351507
  }
350005
351508
  async ensureConfigFile() {
350006
351509
  await ensureConfigFile(this.configPath);
@@ -350046,10 +351549,12 @@ function createBlunHarness(options) {
350046
351549
  homeDir: rpc.homeDir,
350047
351550
  configPath: rpc.configPath,
350048
351551
  auth: rpc.auth,
351552
+ profileScope: rpc.profileScope,
350049
351553
  telemetry: rpc.telemetry,
350050
351554
  ensureConfigFile: () => rpc.ensureConfigFile(),
350051
351555
  onClose: () => rpc.close(),
350052
- sessionStartedProperties: options.sessionStartedProperties
351556
+ sessionStartedProperties: options.sessionStartedProperties,
351557
+ workOwner: rpc.core
350053
351558
  });
350054
351559
  }
350055
351560
  //#endregion