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/CHANGELOG.md +9 -0
- package/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/bin/core-bootstrap.js +24 -8
- package/bin/launcher-runtime.js +124 -48
- package/bin/profile-handoff.cjs +152 -0
- package/bin/update-lease.js +3 -1
- package/blun.mjs +3031 -1030
- package/package.json +2 -2
- package/worker-host.mjs +2185 -680
package/worker-host.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// BLUN_BUILD_INPUT_SHA256:
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
10947
|
-
|
|
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(
|
|
11148
|
+
this.windowsAcl.hardenAndVerify(directory, "directory");
|
|
10953
11149
|
return;
|
|
10954
11150
|
}
|
|
10955
|
-
chmodSync(
|
|
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(
|
|
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(
|
|
11167
|
+
this.windowsAcl.hardenAndVerify(directory, "directory");
|
|
10972
11168
|
this.windowsAcl.hardenAndVerify(file, "file");
|
|
10973
11169
|
} else {
|
|
10974
|
-
chmodSync(
|
|
11170
|
+
chmodSync(directory, 448);
|
|
10975
11171
|
chmodSync(file, 384);
|
|
10976
11172
|
}
|
|
10977
11173
|
return true;
|
|
10978
11174
|
}
|
|
10979
11175
|
async load(name) {
|
|
10980
|
-
|
|
10981
|
-
|
|
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
|
-
|
|
11000
|
-
this.
|
|
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(
|
|
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/
|
|
14949
|
-
const
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
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
|
|
15005
|
-
|
|
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
|
|
15010
|
-
if (
|
|
15011
|
-
|
|
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
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
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
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
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
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15090
|
-
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
|
|
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
|
|
15114
|
-
|
|
15115
|
-
|
|
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
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
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
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
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
|
-
|
|
16511
|
-
|
|
16512
|
-
const
|
|
16513
|
-
|
|
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
|
-
|
|
16516
|
-
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
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
|
-
|
|
16534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183339
|
-
|
|
183681
|
+
flush() {
|
|
183682
|
+
return this.operations.run("flush", () => this.persistence?.flush() ?? Promise.resolve());
|
|
183340
183683
|
}
|
|
183341
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
185581
|
-
|
|
185582
|
-
|
|
185583
|
-
|
|
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.
|
|
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
|
-
|
|
185790
|
-
|
|
185791
|
-
|
|
185792
|
-
|
|
185793
|
-
|
|
185794
|
-
|
|
185795
|
-
|
|
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
|
-
|
|
185822
|
-
|
|
185823
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
},
|
|
230745
|
-
|
|
230746
|
-
|
|
230747
|
-
|
|
230748
|
-
|
|
230749
|
-
|
|
230750
|
-
|
|
230751
|
-
|
|
230752
|
-
|
|
230753
|
-
|
|
230754
|
-
|
|
230755
|
-
|
|
230756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233111
|
-
|
|
233112
|
-
|
|
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
|
-
|
|
233117
|
-
|
|
233118
|
-
|
|
233119
|
-
|
|
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
|
-
|
|
233135
|
-
|
|
233136
|
-
|
|
233137
|
-
|
|
233138
|
-
|
|
233139
|
-
|
|
233140
|
-
|
|
233141
|
-
|
|
233142
|
-
|
|
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
|
-
|
|
233146
|
-
|
|
233147
|
-
|
|
233148
|
-
|
|
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
|
-
|
|
233182
|
-
|
|
233183
|
-
|
|
233184
|
-
|
|
233185
|
-
|
|
233186
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
233338
|
-
|
|
233339
|
-
|
|
233340
|
-
|
|
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.
|
|
233788
|
+
this.admission.close();
|
|
233345
233789
|
}
|
|
233346
233790
|
/** Drive one scheduler tick synchronously. Used by tests + P1.8 SIGUSR1. */
|
|
233347
233791
|
tick() {
|
|
233348
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
233527
|
-
|
|
233528
|
-
|
|
233529
|
-
|
|
233530
|
-
|
|
233531
|
-
|
|
233532
|
-
|
|
233533
|
-
|
|
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
|
|
235615
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
236570
|
+
if (this.admission.closed) return Promise.resolve([]);
|
|
236115
236571
|
try {
|
|
236116
236572
|
const id = `${event}:${++this.executionCounter}`;
|
|
236117
|
-
|
|
236118
|
-
this.executions.track({
|
|
236573
|
+
return this.executions.run({
|
|
236119
236574
|
kind: "trigger",
|
|
236120
236575
|
id
|
|
236121
|
-
},
|
|
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.
|
|
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.
|
|
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
|
-
|
|
241815
|
-
|
|
241816
|
-
roots
|
|
241817
|
-
|
|
241818
|
-
|
|
241819
|
-
|
|
241820
|
-
this.
|
|
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.
|
|
241827
|
-
|
|
241828
|
-
|
|
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
|
-
|
|
241833
|
-
|
|
241834
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
268081
|
-
this.executions.track({
|
|
268594
|
+
const promise = this.executions.run({
|
|
268082
268595
|
kind: "turn",
|
|
268083
268596
|
id: String(turnId)
|
|
268084
|
-
},
|
|
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.
|
|
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
|
-
|
|
268665
|
-
|
|
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
|
-
|
|
303558
|
-
|
|
303559
|
-
|
|
303560
|
-
|
|
303561
|
-
|
|
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
|
-
|
|
303686
|
-
|
|
303687
|
-
|
|
303688
|
-
|
|
303689
|
-
|
|
303690
|
-
|
|
303691
|
-
|
|
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
|
-
|
|
303755
|
-
|
|
303756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
304292
|
-
|
|
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
|
-
|
|
304419
|
-
|
|
304420
|
-
|
|
304421
|
-
|
|
304422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
304909
|
-
|
|
304910
|
-
|
|
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.
|
|
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
|
-
|
|
305068
|
-
|
|
305069
|
-
|
|
305070
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
305188
|
-
|
|
305189
|
-
|
|
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
|
-
|
|
305214
|
-
|
|
305215
|
-
|
|
305216
|
-
|
|
305217
|
-
|
|
305218
|
-
|
|
305219
|
-
|
|
305220
|
-
|
|
305221
|
-
|
|
305222
|
-
|
|
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
|
-
|
|
305226
|
-
this.metadata
|
|
305227
|
-
|
|
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
|
-
|
|
305231
|
-
|
|
305232
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
349169
|
-
|
|
349170
|
-
|
|
349171
|
-
|
|
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 =
|
|
349957
|
-
const profile =
|
|
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
|
-
|
|
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
|