blun-king-cli 9.1.599 → 9.1.601
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 +16 -2
- package/LIESMICH.txt +1 -1
- package/README.md +13 -3
- package/agent-spine-plugin/CHANGELOG.md +1 -1
- package/agent-spine-plugin/docs/host-integration.md +1 -15
- package/agent-spine-plugin/docs/preflight-recall.md +1 -1
- package/agent-spine-plugin/scripts/check-install.js +1 -6
- package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
- package/agent-spine-plugin/scripts/release-check.js +2 -3
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
- package/agent-spine-plugin/src/hook.js +40 -40
- package/agent-spine-plugin/src/lib/briefing.js +6 -3
- package/agent-spine-plugin/src/lib/hook-context.js +63 -64
- package/agent-spine-plugin/src/lib/hook-output.js +115 -94
- package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
- package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
- package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
- package/agent-spine-plugin/src/lib/source-roots.js +26 -16
- package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
- package/bin/core-bootstrap.js +18 -10
- package/bin/launcher-mode.js +5 -0
- package/bin/launcher-runtime.js +27 -0
- package/bin/verify-bundled-agent-sources.cjs +28 -2
- package/blun.mjs +1628 -1194
- package/bundled-agent-sources.json +192 -155
- package/package.json +1 -1
- package/standard-skills/translate-native/README.md +1334 -1
- package/standard-skills/translate-native/SKILL.md +1 -1
- package/standard-skills/translate-native/VERSION +1 -1
- package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
- package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
- package/standard-skills/translate-native/integrations/website_localization.py +234 -10
- package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
- package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
- package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
- package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
- package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
- package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
- package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
- package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
- package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
- package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
- package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
- package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
- package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
- package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
- package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
- package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
- package/standard-skills/translate-native/provenance.json +2 -2
- package/standard-skills/translate-native/references/commercial-localization.md +96 -17
- package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
- package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
- package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
- package/standard-tools/language-guard/VERSION +1 -1
- package/standard-tools/language-guard/blun_language_guard.py +3 -1
- package/standard-tools/language-guard/check_commercial_review.py +69 -9
- package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
- package/standard-tools/language-guard/provenance.json +2 -2
- package/standard-tools/manifest.json +1 -1
- package/worker-host.mjs +1951 -1585
- package/agent-spine-plugin/scripts/check-install-king.js +0 -37
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:b5b19f76ccc092b4eba456b9da34bbda10f5c0717713caa03a28337eab2cac02
|
|
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);
|
|
@@ -9,14 +9,14 @@ import crypto$1, { createHash, randomBytes, randomInt, randomUUID } from "node:c
|
|
|
9
9
|
import * as fs$12 from "node:fs";
|
|
10
10
|
import Kt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, opendirSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
11
11
|
import * as path$11 from "node:path";
|
|
12
|
-
import path, { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
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 * as nodeOs from "node:os";
|
|
15
15
|
import os, { arch, homedir, hostname, release, tmpdir, type, userInfo } from "node:os";
|
|
16
16
|
import fs, { access, appendFile, chmod, copyFile, cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
17
17
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
18
18
|
import * as sysPath from "path";
|
|
19
|
-
import path$1, { dirname as dirname$1, parse } from "path";
|
|
19
|
+
import path$1, { dirname as dirname$1, parse as parse$1 } from "path";
|
|
20
20
|
import I, { existsSync as existsSync$1, promises as promises$1, stat as stat$1, unwatchFile, watch, watchFile } from "fs";
|
|
21
21
|
import zi from "assert";
|
|
22
22
|
import Qr, { EventEmitter } from "events";
|
|
@@ -469,7 +469,7 @@ const basename$1 = function(p, extension) {
|
|
|
469
469
|
}
|
|
470
470
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
471
471
|
};
|
|
472
|
-
const parse$
|
|
472
|
+
const parse$9 = function(p) {
|
|
473
473
|
const root = _PATH_ROOT_RE.exec(p)?.[0]?.replace(/\\/g, "/") || "";
|
|
474
474
|
const base = basename$1(p);
|
|
475
475
|
const extension = extname(base);
|
|
@@ -495,7 +495,7 @@ const _path = {
|
|
|
495
495
|
matchesGlob,
|
|
496
496
|
normalize: normalize$3,
|
|
497
497
|
normalizeString,
|
|
498
|
-
parse: parse$
|
|
498
|
+
parse: parse$9,
|
|
499
499
|
relative: relative$1,
|
|
500
500
|
resolve: resolve$2,
|
|
501
501
|
sep: "/",
|
|
@@ -8984,7 +8984,7 @@ const ZodError$1 = $constructor("ZodError", initializer);
|
|
|
8984
8984
|
const ZodRealError = $constructor("ZodError", initializer, { Parent: Error });
|
|
8985
8985
|
//#endregion
|
|
8986
8986
|
//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/parse.js
|
|
8987
|
-
const parse$
|
|
8987
|
+
const parse$8 = /* @__PURE__ */ _parse(ZodRealError);
|
|
8988
8988
|
const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
8989
8989
|
const safeParse$1 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
8990
8990
|
const safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -9022,7 +9022,7 @@ const ZodType$1 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
9022
9022
|
reg.add(inst, meta);
|
|
9023
9023
|
return inst;
|
|
9024
9024
|
});
|
|
9025
|
-
inst.parse = (data, params) => parse$
|
|
9025
|
+
inst.parse = (data, params) => parse$8(inst, data, params, { callee: inst.parse });
|
|
9026
9026
|
inst.safeParse = (data, params) => safeParse$1(inst, data, params);
|
|
9027
9027
|
inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
|
|
9028
9028
|
inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
|
|
@@ -15233,7 +15233,7 @@ function peekTable(key, table, meta, type) {
|
|
|
15233
15233
|
state.c
|
|
15234
15234
|
];
|
|
15235
15235
|
}
|
|
15236
|
-
function parse$
|
|
15236
|
+
function parse$7(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
15237
15237
|
let res = {};
|
|
15238
15238
|
let meta = {};
|
|
15239
15239
|
let tbl = res;
|
|
@@ -15486,7 +15486,7 @@ function loadRuntimeConfigSafe(filePath, env = process.env) {
|
|
|
15486
15486
|
if (text !== void 0 && text.trim().length > 0) {
|
|
15487
15487
|
let data;
|
|
15488
15488
|
try {
|
|
15489
|
-
data = parse$
|
|
15489
|
+
data = parse$7(text);
|
|
15490
15490
|
} catch (error) {
|
|
15491
15491
|
fileError = new BlunError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${filePath}: ${describeUnknownError(error)}`, { cause: error });
|
|
15492
15492
|
fileWarnings.push(`Invalid TOML in ${filePath}: ${describeTomlSyntaxError(error)}.`);
|
|
@@ -15568,7 +15568,7 @@ function parseConfigString(tomlText, filePath = "config.toml") {
|
|
|
15568
15568
|
if (tomlText.trim().length === 0) return getDefaultConfig();
|
|
15569
15569
|
let data;
|
|
15570
15570
|
try {
|
|
15571
|
-
data = parse$
|
|
15571
|
+
data = parse$7(tomlText);
|
|
15572
15572
|
} catch (error) {
|
|
15573
15573
|
throw new BlunError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
15574
15574
|
}
|
|
@@ -15967,7 +15967,7 @@ async function readWorkspaceLocalToml(kaos, configPath) {
|
|
|
15967
15967
|
};
|
|
15968
15968
|
let raw;
|
|
15969
15969
|
try {
|
|
15970
|
-
raw = parse$
|
|
15970
|
+
raw = parse$7(text);
|
|
15971
15971
|
} catch (error) {
|
|
15972
15972
|
throw new BlunError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${configPath}: ${describeError$1(error)}`, { cause: error });
|
|
15973
15973
|
}
|
|
@@ -180434,7 +180434,10 @@ function applyCompletionBudgetWithDetails(args) {
|
|
|
180434
180434
|
});
|
|
180435
180435
|
if (args.retry !== void 0) cap = Math.max(args.retry.minimumCompletionTokens, Math.ceil(cap * args.retry.multiplier));
|
|
180436
180436
|
const maxContextTokens = args.capability?.max_context_tokens;
|
|
180437
|
-
if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0)
|
|
180437
|
+
if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) {
|
|
180438
|
+
const headroom = Math.min(2048, Math.ceil(maxContextTokens * .01));
|
|
180439
|
+
cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens - headroom));
|
|
180440
|
+
}
|
|
180438
180441
|
return {
|
|
180439
180442
|
provider: args.provider.withMaxCompletionTokens(cap, {
|
|
180440
180443
|
usedContextTokens: args.usedContextTokens,
|
|
@@ -229582,7 +229585,7 @@ var FullCompaction = class {
|
|
|
229582
229585
|
this.agent.log.error("failed to refresh system prompt after compaction", { error });
|
|
229583
229586
|
}
|
|
229584
229587
|
await this.agent.injection.injectAfterCompaction();
|
|
229585
|
-
this.fitRetainedInput();
|
|
229588
|
+
this.fitRetainedInput({}, result.keptHeadUserMessageCount !== void 0);
|
|
229586
229589
|
this.lastCompactedState = {
|
|
229587
229590
|
tokenCount: this.tokenCountWithPending,
|
|
229588
229591
|
modelAlias: this.agent.activeResponderModel ?? this.agent.config.modelAlias,
|
|
@@ -229618,17 +229621,18 @@ var FullCompaction = class {
|
|
|
229618
229621
|
this.agent.turn.onCompactionFinished();
|
|
229619
229622
|
}
|
|
229620
229623
|
}
|
|
229621
|
-
fitRetainedInput(context = {}) {
|
|
229624
|
+
fitRetainedInput(context = {}, freshBudgetedSelection = false) {
|
|
229622
229625
|
const budget = this.getCompactionBudgetTokens();
|
|
229623
229626
|
const window = this.getEffectiveMaxContextTokens();
|
|
229624
229627
|
const limit = window > 0 ? window - 1 : Infinity;
|
|
229628
|
+
const hardContextLimit = window > 0 && !freshBudgetedSelection ? window : void 0;
|
|
229625
229629
|
const estimate = (messages) => this.estimateProjectedRequestTokens({
|
|
229626
229630
|
...context,
|
|
229627
229631
|
messages
|
|
229628
229632
|
});
|
|
229629
229633
|
const preserveLatestUser = this.agent.turn.hasActiveTurn;
|
|
229630
|
-
if (budget === void 0) return this.agent.context.fitCompactionPrefix(limit, estimate, preserveLatestUser);
|
|
229631
|
-
return this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .8), limit), estimate, preserveLatestUser) || this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .9) - 1, limit), estimate, preserveLatestUser);
|
|
229634
|
+
if (budget === void 0) return this.agent.context.fitCompactionPrefix(limit, estimate, preserveLatestUser, hardContextLimit);
|
|
229635
|
+
return this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .8), limit), estimate, preserveLatestUser, hardContextLimit) || this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .9) - 1, limit), estimate, preserveLatestUser, hardContextLimit);
|
|
229632
229636
|
}
|
|
229633
229637
|
buildInstruction(customInstruction) {
|
|
229634
229638
|
return renderPrompt(compaction_instruction_default, { customInstruction: customInstruction?.trim() ?? "" }).trimEnd();
|
|
@@ -232498,7 +232502,7 @@ function expandUserPath(value, pathClass, homeDir) {
|
|
|
232498
232502
|
}
|
|
232499
232503
|
function defaultCwdForPath(value) {
|
|
232500
232504
|
if (!isAbsolute$1(value)) return void 0;
|
|
232501
|
-
return parse$
|
|
232505
|
+
return parse$9(value).root;
|
|
232502
232506
|
}
|
|
232503
232507
|
function pathMatchSemantics(value, pattern, pathOptions) {
|
|
232504
232508
|
return { pathClass: pathOptions?.pathClass ?? ([value, pattern].some((candidate) => {
|
|
@@ -233588,7 +233592,7 @@ var ContextMemory = class {
|
|
|
233588
233592
|
this.agent.microCompaction.reset();
|
|
233589
233593
|
this.agent.emitStatusUpdated();
|
|
233590
233594
|
}
|
|
233591
|
-
fitCompactionPrefix(maxRequestTokens, estimateRequest, preserveLatestUser = false) {
|
|
233595
|
+
fitCompactionPrefix(maxRequestTokens, estimateRequest, preserveLatestUser = false, hardContextLimit) {
|
|
233592
233596
|
const prefixLength = this._history.findIndex((message) => message.origin?.kind === "compaction_summary");
|
|
233593
233597
|
if (prefixLength <= 0 || !Number.isSafeInteger(maxRequestTokens) || maxRequestTokens <= 0) return false;
|
|
233594
233598
|
const suffix = this._history.slice(prefixLength);
|
|
@@ -233600,7 +233604,9 @@ var ContextMemory = class {
|
|
|
233600
233604
|
];
|
|
233601
233605
|
return Math.max(estimateTokensForMessages(messages), estimateRequest(project(messages, { dropOrphanResults: true })));
|
|
233602
233606
|
};
|
|
233603
|
-
|
|
233607
|
+
const requestTokens = estimate(this._history.slice(0, prefixLength));
|
|
233608
|
+
if (requestTokens <= maxRequestTokens) return false;
|
|
233609
|
+
if (hardContextLimit !== void 0 && requestTokens >= hardContextLimit) return false;
|
|
233604
233610
|
const sourceKey = Symbol("compaction source");
|
|
233605
233611
|
const sources = collectCompactableUserMessages(this._history.slice(0, prefixLength)).map((message) => ({
|
|
233606
233612
|
...message,
|
|
@@ -234490,6 +234496,9 @@ var HookEngine = class {
|
|
|
234490
234496
|
for (const [event, hooks] of this.byEvent.entries()) result[event] = hooks.length;
|
|
234491
234497
|
return result;
|
|
234492
234498
|
}
|
|
234499
|
+
hasMatchingHook(event, matcherValue) {
|
|
234500
|
+
return this.matchingHooks(event, matcherValue).length > 0;
|
|
234501
|
+
}
|
|
234493
234502
|
closeAdmission() {
|
|
234494
234503
|
this.admissionClosed = true;
|
|
234495
234504
|
}
|
|
@@ -258561,7 +258570,7 @@ var vn = (s) => {
|
|
|
258561
258570
|
}, Qi = (s, t) => {
|
|
258562
258571
|
let e = new Map(t.map((o) => [mt(o), !0])), i = s.filter, r = 100, n = (o, h = "", a = 0) => {
|
|
258563
258572
|
if (a >= r) return e.set(o, !1), !1;
|
|
258564
|
-
let l = h || parse(o).root || ".", c;
|
|
258573
|
+
let l = h || parse$1(o).root || ".", c;
|
|
258565
258574
|
if (o === l) c = !1;
|
|
258566
258575
|
else {
|
|
258567
258576
|
let d = e.get(o);
|
|
@@ -264005,450 +264014,1899 @@ function safeToolResultFileStem(toolName, toolCallId) {
|
|
|
264005
264014
|
return `${toolName}-${toolCallId}`.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80) || "tool-result";
|
|
264006
264015
|
}
|
|
264007
264016
|
//#endregion
|
|
264008
|
-
//#region ../../packages/
|
|
264009
|
-
|
|
264010
|
-
|
|
264011
|
-
|
|
264012
|
-
|
|
264013
|
-
|
|
264014
|
-
|
|
264015
|
-
|
|
264016
|
-
|
|
264017
|
-
|
|
264018
|
-
|
|
264019
|
-
|
|
264020
|
-
|
|
264021
|
-
|
|
264022
|
-
|
|
264023
|
-
|
|
264024
|
-
|
|
264025
|
-
|
|
264026
|
-
|
|
264027
|
-
|
|
264028
|
-
|
|
264029
|
-
|
|
264030
|
-
|
|
264031
|
-
|
|
264032
|
-
|
|
264033
|
-
|
|
264034
|
-
|
|
264035
|
-
|
|
264036
|
-
|
|
264037
|
-
|
|
264038
|
-
|
|
264039
|
-
|
|
264040
|
-
|
|
264041
|
-
|
|
264042
|
-
|
|
264043
|
-
|
|
264044
|
-
|
|
264045
|
-
|
|
264046
|
-
|
|
264047
|
-
|
|
264048
|
-
|
|
264049
|
-
|
|
264050
|
-
|
|
264051
|
-
|
|
264017
|
+
//#region ../../packages/kaos/src/errors.ts
|
|
264018
|
+
/**
|
|
264019
|
+
* Base error class for the kaos package.
|
|
264020
|
+
*/
|
|
264021
|
+
var KaosError = class extends Error {
|
|
264022
|
+
constructor(message) {
|
|
264023
|
+
super(message);
|
|
264024
|
+
this.name = "KaosError";
|
|
264025
|
+
}
|
|
264026
|
+
};
|
|
264027
|
+
/**
|
|
264028
|
+
* Equivalent to Python's FileExistsError — indicates a file or directory already exists.
|
|
264029
|
+
*/
|
|
264030
|
+
var KaosFileExistsError = class extends KaosError {
|
|
264031
|
+
constructor(message) {
|
|
264032
|
+
super(message);
|
|
264033
|
+
this.name = "KaosFileExistsError";
|
|
264034
|
+
}
|
|
264035
|
+
};
|
|
264036
|
+
/**
|
|
264037
|
+
* Thrown by `detectEnvironment` on Windows when no Git Bash install can be
|
|
264038
|
+
* located. Carries the list of paths that were probed so callers can include
|
|
264039
|
+
* them in install hints.
|
|
264040
|
+
*/
|
|
264041
|
+
var KaosShellNotFoundError = class extends KaosError {
|
|
264042
|
+
constructor(message) {
|
|
264043
|
+
super(message);
|
|
264044
|
+
this.name = "KaosShellNotFoundError";
|
|
264045
|
+
}
|
|
264046
|
+
};
|
|
264047
|
+
//#endregion
|
|
264048
|
+
//#region ../../packages/kaos/src/environment.ts
|
|
264049
|
+
/**
|
|
264050
|
+
* Environment — cross-platform probe of OS / shell.
|
|
264051
|
+
*
|
|
264052
|
+
* Detection is a pure function of injected probes (`platform` / `arch` /
|
|
264053
|
+
* `release` / `env` / `isFile` / `execFileText`) so the same suite runs
|
|
264054
|
+
* identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
|
|
264055
|
+
* defaults for production callers.
|
|
264056
|
+
*
|
|
264057
|
+
* On Windows the probe expects Git Bash (the canonical POSIX shell that
|
|
264058
|
+
* ships with Git for Windows). If it cannot be located the function
|
|
264059
|
+
* throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
|
|
264060
|
+
* user-facing install hint. Set `BLUN_SHELL_PATH` to override.
|
|
264061
|
+
*/
|
|
264062
|
+
const GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
|
|
264063
|
+
function resolveOsKind(platform) {
|
|
264064
|
+
switch (platform) {
|
|
264065
|
+
case "darwin": return "macOS";
|
|
264066
|
+
case "linux": return "Linux";
|
|
264067
|
+
case "win32": return "Windows";
|
|
264068
|
+
default: return platform;
|
|
264069
|
+
}
|
|
264052
264070
|
}
|
|
264053
|
-
function
|
|
264054
|
-
|
|
264055
|
-
const
|
|
264056
|
-
|
|
264071
|
+
async function detectEnvironment(deps) {
|
|
264072
|
+
const osKind = resolveOsKind(deps.platform);
|
|
264073
|
+
const osArch = deps.arch;
|
|
264074
|
+
const osVersion = deps.release;
|
|
264075
|
+
if (deps.platform === "win32") return {
|
|
264076
|
+
osKind,
|
|
264077
|
+
osArch,
|
|
264078
|
+
osVersion,
|
|
264079
|
+
shellName: "bash",
|
|
264080
|
+
shellPath: await locateWindowsGitBash(deps)
|
|
264081
|
+
};
|
|
264082
|
+
const candidates = [
|
|
264083
|
+
"/bin/bash",
|
|
264084
|
+
"/usr/bin/bash",
|
|
264085
|
+
"/usr/local/bin/bash"
|
|
264086
|
+
];
|
|
264087
|
+
let found;
|
|
264088
|
+
for (const p of candidates) if (await deps.isFile(p)) {
|
|
264089
|
+
found = p;
|
|
264090
|
+
break;
|
|
264091
|
+
}
|
|
264092
|
+
if (found !== void 0) return {
|
|
264093
|
+
osKind,
|
|
264094
|
+
osArch,
|
|
264095
|
+
osVersion,
|
|
264096
|
+
shellName: "bash",
|
|
264097
|
+
shellPath: found
|
|
264098
|
+
};
|
|
264099
|
+
return {
|
|
264100
|
+
osKind,
|
|
264101
|
+
osArch,
|
|
264102
|
+
osVersion,
|
|
264103
|
+
shellName: "sh",
|
|
264104
|
+
shellPath: "/bin/sh"
|
|
264105
|
+
};
|
|
264057
264106
|
}
|
|
264058
|
-
|
|
264059
|
-
|
|
264060
|
-
const
|
|
264061
|
-
|
|
264062
|
-
|
|
264063
|
-
|
|
264064
|
-
const MAX_REVIEW_ATTEMPTS = 2;
|
|
264065
|
-
function createReviewedOutputDispatcher(options) {
|
|
264066
|
-
let pending = [];
|
|
264067
|
-
let bytes = 0;
|
|
264068
|
-
let stepUuid;
|
|
264069
|
-
let epoch = 0;
|
|
264070
|
-
let reviewing = false;
|
|
264071
|
-
let turnId;
|
|
264072
|
-
let stepNumber = 0;
|
|
264073
|
-
let toolTextWithheld = false;
|
|
264074
|
-
const toolCalls = /* @__PURE__ */ new Map();
|
|
264075
|
-
const toolResults = /* @__PURE__ */ new Set();
|
|
264076
|
-
let invalidToolExchange = false;
|
|
264077
|
-
function clear() {
|
|
264078
|
-
pending = [];
|
|
264079
|
-
bytes = 0;
|
|
264080
|
-
stepUuid = void 0;
|
|
264081
|
-
epoch += 1;
|
|
264082
|
-
reviewing = false;
|
|
264083
|
-
toolCalls.clear();
|
|
264084
|
-
toolResults.clear();
|
|
264085
|
-
invalidToolExchange = false;
|
|
264107
|
+
async function locateWindowsGitBash(deps) {
|
|
264108
|
+
const checked = [];
|
|
264109
|
+
const override = deps.env["BLUN_SHELL_PATH"]?.trim();
|
|
264110
|
+
if (override !== void 0 && override.length > 0) {
|
|
264111
|
+
checked.push(override);
|
|
264112
|
+
if (await deps.isFile(override)) return override;
|
|
264086
264113
|
}
|
|
264087
|
-
|
|
264088
|
-
|
|
264089
|
-
|
|
264090
|
-
|
|
264091
|
-
|
|
264092
|
-
|
|
264093
|
-
clear();
|
|
264094
|
-
if (turnId !== event.turnId) toolTextWithheld = false;
|
|
264095
|
-
turnId = event.turnId;
|
|
264096
|
-
stepNumber = event.step;
|
|
264097
|
-
stepUuid = event.uuid;
|
|
264098
|
-
return options.dispatch(event);
|
|
264099
|
-
}
|
|
264100
|
-
if (event.type === "content.part" && event.part.type === "text") {
|
|
264101
|
-
if (reviewing) {
|
|
264102
|
-
clear();
|
|
264103
|
-
return Promise.reject(/* @__PURE__ */ new Error("Response review candidate changed during verification"));
|
|
264104
|
-
}
|
|
264105
|
-
if (stepUuid !== event.stepUuid) return Promise.reject(/* @__PURE__ */ new Error("Response review step mismatch"));
|
|
264106
|
-
bytes += Buffer.byteLength(event.part.text);
|
|
264107
|
-
if (bytes > MAX_CANDIDATE_BYTES || pending.length >= MAX_CANDIDATE_PARTS) {
|
|
264108
|
-
clear();
|
|
264109
|
-
return Promise.reject(/* @__PURE__ */ new Error("Response review candidate exceeds limit"));
|
|
264110
|
-
}
|
|
264111
|
-
pending.push({
|
|
264112
|
-
...event,
|
|
264113
|
-
part: { ...event.part }
|
|
264114
|
-
});
|
|
264115
|
-
return Promise.resolve();
|
|
264116
|
-
}
|
|
264117
|
-
if (event.type === "step.end") return finish(event);
|
|
264118
|
-
if (event.type === "turn.interrupted") clear();
|
|
264119
|
-
if (event.type === "step.retrying") {
|
|
264120
|
-
clear();
|
|
264121
|
-
stepUuid = event.stepUuid;
|
|
264122
|
-
}
|
|
264123
|
-
if (event.type === "tool.call") {
|
|
264124
|
-
const recordedEpoch = epoch;
|
|
264125
|
-
const { toolCallId, uuid } = event;
|
|
264126
|
-
const matchesStep = event.stepUuid === stepUuid && event.turnId === turnId && event.step === stepNumber;
|
|
264127
|
-
return options.dispatch(event).then(() => {
|
|
264128
|
-
if (recordedEpoch !== epoch) return;
|
|
264129
|
-
if (!matchesStep || toolCalls.has(toolCallId)) invalidToolExchange = true;
|
|
264130
|
-
else toolCalls.set(toolCallId, uuid);
|
|
264131
|
-
});
|
|
264114
|
+
const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
|
|
264115
|
+
for (const gitExe of gitExecutables) {
|
|
264116
|
+
const inferred = gitBashCandidatesFromGitExe(gitExe);
|
|
264117
|
+
if (inferred !== void 0) for (const candidate of inferred) {
|
|
264118
|
+
checked.push(candidate);
|
|
264119
|
+
if (await deps.isFile(candidate)) return candidate;
|
|
264132
264120
|
}
|
|
264133
|
-
|
|
264134
|
-
|
|
264135
|
-
|
|
264136
|
-
|
|
264137
|
-
|
|
264138
|
-
if (toolCalls.get(toolCallId) !== parentUuid || toolResults.has(toolCallId)) invalidToolExchange = true;
|
|
264139
|
-
else toolResults.add(toolCallId);
|
|
264140
|
-
});
|
|
264121
|
+
const gitExecPath = await readGitExecPath(deps, gitExe);
|
|
264122
|
+
if (gitExecPath === void 0) continue;
|
|
264123
|
+
for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
|
|
264124
|
+
checked.push(candidate);
|
|
264125
|
+
if (await deps.isFile(candidate)) return candidate;
|
|
264141
264126
|
}
|
|
264142
|
-
if (event.type === "content.part") return options.dispatch(event);
|
|
264143
|
-
options.dispatch(event);
|
|
264144
264127
|
}
|
|
264145
|
-
|
|
264146
|
-
|
|
264147
|
-
|
|
264148
|
-
|
|
264149
|
-
|
|
264150
|
-
|
|
264151
|
-
|
|
264152
|
-
|
|
264153
|
-
|
|
264154
|
-
|
|
264155
|
-
|
|
264156
|
-
|
|
264157
|
-
|
|
264158
|
-
|
|
264159
|
-
|
|
264160
|
-
|
|
264161
|
-
|
|
264162
|
-
|
|
264163
|
-
|
|
264164
|
-
|
|
264165
|
-
|
|
264166
|
-
|
|
264167
|
-
|
|
264168
|
-
|
|
264169
|
-
|
|
264170
|
-
|
|
264171
|
-
|
|
264172
|
-
|
|
264173
|
-
|
|
264174
|
-
|
|
264175
|
-
|
|
264176
|
-
|
|
264177
|
-
|
|
264178
|
-
|
|
264179
|
-
|
|
264180
|
-
|
|
264181
|
-
|
|
264182
|
-
|
|
264183
|
-
|
|
264184
|
-
|
|
264185
|
-
|
|
264186
|
-
if (attemptRejection !== void 0) throw new Error(attemptRejection);
|
|
264187
|
-
if (deadline.timedOut() && attempt < MAX_REVIEW_ATTEMPTS && requested !== void 0 && epoch === currentEpoch && await waitForCancelledReview(requested)) continue;
|
|
264188
|
-
throw new Error(deadline.timedOut() ? "Response review timed out" : "Response review failed");
|
|
264189
|
-
} finally {
|
|
264190
|
-
deadline.clear();
|
|
264191
|
-
}
|
|
264192
|
-
}
|
|
264193
|
-
options.signal.throwIfAborted();
|
|
264194
|
-
if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
|
|
264195
|
-
if (nativeRejection && !toolTextWithheld && event.finishReason === "tool_use" && event.turnId === turnId && event.step === stepNumber && !invalidToolExchange && toolCalls.size > 0 && toolResults.size === toolCalls.size) {
|
|
264196
|
-
toolTextWithheld = true;
|
|
264197
|
-
await options.dispatch(event);
|
|
264198
|
-
try {
|
|
264199
|
-
options.onToolTextWithheld?.({
|
|
264200
|
-
turnId: event.turnId,
|
|
264201
|
-
step: event.step
|
|
264202
|
-
});
|
|
264203
|
-
} catch {}
|
|
264204
|
-
return;
|
|
264205
|
-
}
|
|
264206
|
-
if (rejection !== void 0) throw new Error(rejection);
|
|
264207
|
-
if (epoch !== currentEpoch || approval?.targetSha256 !== createHash("sha256").update(text).digest("hex") || !Number.isSafeInteger(approval.expiresAt) || approval.expiresAt <= Date.now()) throw new Error("Response review did not approve the exact current candidate");
|
|
264208
|
-
for (const part of parts) {
|
|
264209
|
-
options.signal.throwIfAborted();
|
|
264210
|
-
if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
|
|
264211
|
-
await options.dispatch(part);
|
|
264212
|
-
}
|
|
264213
|
-
options.signal.throwIfAborted();
|
|
264214
|
-
if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
|
|
264215
|
-
options.dispatch({
|
|
264216
|
-
type: "text.delta",
|
|
264217
|
-
delta: text
|
|
264218
|
-
});
|
|
264219
|
-
await options.dispatch(event);
|
|
264220
|
-
} finally {
|
|
264221
|
-
if (epoch === currentEpoch) clear();
|
|
264128
|
+
const candidates = [
|
|
264129
|
+
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
264130
|
+
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
264131
|
+
"C:\\Program Files (x86)\\Git\\bin\\bash.exe",
|
|
264132
|
+
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
|
|
264133
|
+
];
|
|
264134
|
+
const localAppData = deps.env["LOCALAPPDATA"]?.trim();
|
|
264135
|
+
if (localAppData !== void 0 && localAppData.length > 0) {
|
|
264136
|
+
candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
|
|
264137
|
+
candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
|
|
264138
|
+
}
|
|
264139
|
+
for (const candidate of candidates) {
|
|
264140
|
+
checked.push(candidate);
|
|
264141
|
+
if (await deps.isFile(candidate)) return candidate;
|
|
264142
|
+
}
|
|
264143
|
+
throw new KaosShellNotFoundError(`Git Bash was not found on this Windows host. Install Git for Windows from https://gitforwindows.org/ or set BLUN_SHELL_PATH to a bash.exe. Checked: ${checked.join(", ")}.`);
|
|
264144
|
+
}
|
|
264145
|
+
async function readGitExecPath(deps, gitExe) {
|
|
264146
|
+
if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
|
|
264147
|
+
const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
|
|
264148
|
+
if (stdout === void 0) return void 0;
|
|
264149
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
264150
|
+
const execPath = line.trim();
|
|
264151
|
+
if (execPath.length > 0) return execPath;
|
|
264152
|
+
}
|
|
264153
|
+
}
|
|
264154
|
+
function gitBashCandidatesFromGitExe(gitExe) {
|
|
264155
|
+
const normalizedGitExe = path$11.win32.normalize(normalizeWindowsPath(gitExe));
|
|
264156
|
+
const gitDir = path$11.win32.dirname(normalizedGitExe);
|
|
264157
|
+
const gitDirName = path$11.win32.basename(gitDir).toLowerCase();
|
|
264158
|
+
if (gitDirName !== "cmd" && gitDirName !== "bin") return;
|
|
264159
|
+
return gitBashCandidatesFromGitRoot(path$11.win32.dirname(gitDir));
|
|
264160
|
+
}
|
|
264161
|
+
function gitBashCandidatesFromGitExecPath(execPath) {
|
|
264162
|
+
const normalized = path$11.win32.normalize(normalizeWindowsPath(execPath));
|
|
264163
|
+
const parts = normalized.split("\\");
|
|
264164
|
+
for (let i = parts.length - 1; i >= 0; i -= 1) {
|
|
264165
|
+
const segment = parts[i]?.toLowerCase();
|
|
264166
|
+
if (segment === "mingw32" || segment === "mingw64") {
|
|
264167
|
+
const root = parts.slice(0, i).join("\\");
|
|
264168
|
+
if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
|
|
264222
264169
|
}
|
|
264223
264170
|
}
|
|
264224
|
-
|
|
264225
|
-
|
|
264171
|
+
return gitBashCandidatesFromGitRoot(path$11.win32.join(normalized, "..", ".."));
|
|
264172
|
+
}
|
|
264173
|
+
function gitBashCandidatesFromGitRoot(root) {
|
|
264174
|
+
return [path$11.win32.normalize(path$11.win32.join(root, "bin", "bash.exe")), path$11.win32.normalize(path$11.win32.join(root, "usr", "bin", "bash.exe"))];
|
|
264175
|
+
}
|
|
264176
|
+
function normalizeWindowsPath(path) {
|
|
264177
|
+
return path.replaceAll("/", "\\");
|
|
264178
|
+
}
|
|
264179
|
+
function isAbsoluteWindowsPath(path) {
|
|
264180
|
+
return path$11.win32.isAbsolute(normalizeWindowsPath(path));
|
|
264181
|
+
}
|
|
264182
|
+
function dedupeWindowsPaths(paths) {
|
|
264183
|
+
const deduped = [];
|
|
264184
|
+
const seen = /* @__PURE__ */ new Set();
|
|
264185
|
+
for (const path of paths) {
|
|
264186
|
+
const key = normalizeWindowsPath(path).toLowerCase();
|
|
264187
|
+
if (seen.has(key)) continue;
|
|
264188
|
+
seen.add(key);
|
|
264189
|
+
deduped.push(path);
|
|
264190
|
+
}
|
|
264191
|
+
return deduped;
|
|
264192
|
+
}
|
|
264193
|
+
/**
|
|
264194
|
+
* Production convenience — derive the deps bag from Node's ambient surface.
|
|
264195
|
+
*
|
|
264196
|
+
* The result is memoised: subsequent calls return the original promise.
|
|
264197
|
+
* `Environment` is immutable for the lifetime of the process (it derives
|
|
264198
|
+
* from `process.platform`, `process.arch`, `os.release()`, and one-time
|
|
264199
|
+
* shell-path discovery), so caching is sound. Tests that need to probe
|
|
264200
|
+
* with different inputs should call {@link detectEnvironment} directly
|
|
264201
|
+
* with an injected deps bag.
|
|
264202
|
+
*/
|
|
264203
|
+
let detectedEnvironment;
|
|
264204
|
+
function detectEnvironmentFromNode() {
|
|
264205
|
+
if (detectedEnvironment !== void 0) return detectedEnvironment;
|
|
264206
|
+
const platform = process.platform;
|
|
264207
|
+
const env = process.env;
|
|
264208
|
+
const isFile = async (path) => {
|
|
264226
264209
|
try {
|
|
264227
|
-
await
|
|
264228
|
-
options.signal.throwIfAborted();
|
|
264210
|
+
await access(path, constants.F_OK);
|
|
264229
264211
|
return true;
|
|
264230
264212
|
} catch {
|
|
264231
|
-
options.signal.throwIfAborted();
|
|
264232
264213
|
return false;
|
|
264233
|
-
} finally {
|
|
264234
|
-
deadline.clear();
|
|
264235
264214
|
}
|
|
264215
|
+
};
|
|
264216
|
+
detectedEnvironment = detectEnvironment({
|
|
264217
|
+
platform,
|
|
264218
|
+
arch: process.arch,
|
|
264219
|
+
release: nodeOs.release(),
|
|
264220
|
+
env,
|
|
264221
|
+
isFile,
|
|
264222
|
+
execFileText
|
|
264223
|
+
});
|
|
264224
|
+
return detectedEnvironment;
|
|
264225
|
+
}
|
|
264226
|
+
async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
|
|
264227
|
+
if (pathEnv === void 0 || pathEnv.length === 0) return [];
|
|
264228
|
+
const listSep = platform === "win32" ? ";" : ":";
|
|
264229
|
+
const dirSep = platform === "win32" ? "\\" : "/";
|
|
264230
|
+
const paths = [];
|
|
264231
|
+
for (const rawDir of pathEnv.split(listSep)) {
|
|
264232
|
+
const dir = rawDir.trim();
|
|
264233
|
+
if (dir.length === 0) continue;
|
|
264234
|
+
if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
|
|
264235
|
+
const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
|
|
264236
|
+
if (await isFile(candidate)) paths.push(candidate);
|
|
264236
264237
|
}
|
|
264237
|
-
return
|
|
264238
|
+
return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
|
|
264239
|
+
}
|
|
264240
|
+
async function execFileText(file, args, timeoutMs) {
|
|
264241
|
+
return new Promise((resolve) => {
|
|
264242
|
+
execFile(file, [...args], {
|
|
264243
|
+
encoding: "utf8",
|
|
264244
|
+
timeout: timeoutMs,
|
|
264245
|
+
windowsHide: true
|
|
264246
|
+
}, (error, stdout) => {
|
|
264247
|
+
if (error !== null) {
|
|
264248
|
+
resolve(void 0);
|
|
264249
|
+
return;
|
|
264250
|
+
}
|
|
264251
|
+
resolve(stdout);
|
|
264252
|
+
});
|
|
264253
|
+
});
|
|
264238
264254
|
}
|
|
264239
264255
|
//#endregion
|
|
264240
|
-
//#region ../../packages/
|
|
264241
|
-
function
|
|
264242
|
-
|
|
264243
|
-
if (Array.isArray(content)) return content.map((c) => typeof c === "string" ? c : c && typeof c === "object" && "text" in c && typeof c.text === "string" ? c.text : "").join(" ");
|
|
264244
|
-
return "";
|
|
264256
|
+
//#region ../../packages/kaos/src/internal.ts
|
|
264257
|
+
function isUtf8Continuation(byte) {
|
|
264258
|
+
return byte >= 128 && byte <= 191;
|
|
264245
264259
|
}
|
|
264246
|
-
|
|
264247
|
-
|
|
264248
|
-
|
|
264249
|
-
|
|
264250
|
-
|
|
264251
|
-
|
|
264252
|
-
|
|
264253
|
-
|
|
264254
|
-
|
|
264255
|
-
|
|
264256
|
-
|
|
264257
|
-
|
|
264260
|
+
function decodeUtf8Ignore(data) {
|
|
264261
|
+
let output = "";
|
|
264262
|
+
let i = 0;
|
|
264263
|
+
while (i < data.length) {
|
|
264264
|
+
const b0 = data[i];
|
|
264265
|
+
if (b0 === void 0) break;
|
|
264266
|
+
if (b0 <= 127) {
|
|
264267
|
+
output += String.fromCodePoint(b0);
|
|
264268
|
+
i += 1;
|
|
264269
|
+
continue;
|
|
264270
|
+
}
|
|
264271
|
+
if (b0 >= 194 && b0 <= 223) {
|
|
264272
|
+
const b1 = data[i + 1];
|
|
264273
|
+
if (b1 !== void 0 && isUtf8Continuation(b1)) {
|
|
264274
|
+
output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
|
|
264275
|
+
i += 2;
|
|
264276
|
+
continue;
|
|
264277
|
+
}
|
|
264278
|
+
i += 1;
|
|
264279
|
+
continue;
|
|
264280
|
+
}
|
|
264281
|
+
if (b0 >= 224 && b0 <= 239) {
|
|
264282
|
+
const b1 = data[i + 1];
|
|
264283
|
+
const b2 = data[i + 2];
|
|
264284
|
+
if (b1 !== void 0 && (b0 === 224 && b1 >= 160 && b1 <= 191 || b0 >= 225 && b0 <= 236 && isUtf8Continuation(b1) || b0 === 237 && b1 >= 128 && b1 <= 159 || b0 >= 238 && b0 <= 239 && isUtf8Continuation(b1)) && b2 !== void 0 && isUtf8Continuation(b2)) {
|
|
264285
|
+
output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
|
|
264286
|
+
i += 3;
|
|
264287
|
+
continue;
|
|
264288
|
+
}
|
|
264289
|
+
i += 1;
|
|
264290
|
+
continue;
|
|
264291
|
+
}
|
|
264292
|
+
if (b0 >= 240 && b0 <= 244) {
|
|
264293
|
+
const b1 = data[i + 1];
|
|
264294
|
+
const b2 = data[i + 2];
|
|
264295
|
+
const b3 = data[i + 3];
|
|
264296
|
+
if (b1 !== void 0 && (b0 === 240 && b1 >= 144 && b1 <= 191 || b0 >= 241 && b0 <= 243 && isUtf8Continuation(b1) || b0 === 244 && b1 >= 128 && b1 <= 143) && b2 !== void 0 && b3 !== void 0 && isUtf8Continuation(b2) && isUtf8Continuation(b3)) {
|
|
264297
|
+
output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
|
|
264298
|
+
i += 4;
|
|
264299
|
+
continue;
|
|
264300
|
+
}
|
|
264301
|
+
i += 1;
|
|
264302
|
+
continue;
|
|
264303
|
+
}
|
|
264304
|
+
i += 1;
|
|
264305
|
+
}
|
|
264306
|
+
return output;
|
|
264258
264307
|
}
|
|
264259
|
-
function
|
|
264260
|
-
|
|
264261
|
-
|
|
264262
|
-
|
|
264263
|
-
|
|
264264
|
-
|
|
264308
|
+
function decodeUtf16LeIgnore(data) {
|
|
264309
|
+
let output = "";
|
|
264310
|
+
let i = 0;
|
|
264311
|
+
while (i + 1 < data.length) {
|
|
264312
|
+
const first = data[i];
|
|
264313
|
+
const second = data[i + 1];
|
|
264314
|
+
if (first === void 0 || second === void 0) break;
|
|
264315
|
+
const codeUnit = first | second << 8;
|
|
264316
|
+
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
264317
|
+
const lowFirst = data[i + 2];
|
|
264318
|
+
const lowSecond = data[i + 3];
|
|
264319
|
+
if (lowFirst !== void 0 && lowSecond !== void 0) {
|
|
264320
|
+
const low = lowFirst | lowSecond << 8;
|
|
264321
|
+
if (low >= 56320 && low <= 57343) {
|
|
264322
|
+
const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
|
|
264323
|
+
output += String.fromCodePoint(codePoint);
|
|
264324
|
+
i += 4;
|
|
264325
|
+
continue;
|
|
264326
|
+
}
|
|
264327
|
+
}
|
|
264328
|
+
i += 2;
|
|
264329
|
+
continue;
|
|
264330
|
+
}
|
|
264331
|
+
if (codeUnit >= 56320 && codeUnit <= 57343) {
|
|
264332
|
+
i += 2;
|
|
264333
|
+
continue;
|
|
264334
|
+
}
|
|
264335
|
+
output += String.fromCodePoint(codeUnit);
|
|
264336
|
+
i += 2;
|
|
264337
|
+
}
|
|
264338
|
+
return output;
|
|
264265
264339
|
}
|
|
264266
|
-
|
|
264267
|
-
|
|
264340
|
+
/**
|
|
264341
|
+
* Decode a Buffer into a string with Python-compatible `errors` handling.
|
|
264342
|
+
*
|
|
264343
|
+
* - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
|
|
264344
|
+
* - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
|
|
264345
|
+
* - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
|
|
264346
|
+
*
|
|
264347
|
+
* Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
|
|
264348
|
+
* support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
|
|
264349
|
+
* byte-to-character mappings so `errors` has no effect.
|
|
264350
|
+
* @internal
|
|
264351
|
+
*/
|
|
264352
|
+
function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
|
|
264353
|
+
let webLabel;
|
|
264354
|
+
switch (encoding) {
|
|
264355
|
+
case "utf-8":
|
|
264356
|
+
case "utf8":
|
|
264357
|
+
webLabel = "utf-8";
|
|
264358
|
+
break;
|
|
264359
|
+
case "utf16le":
|
|
264360
|
+
case "ucs2":
|
|
264361
|
+
case "ucs-2":
|
|
264362
|
+
webLabel = "utf-16le";
|
|
264363
|
+
break;
|
|
264364
|
+
default: webLabel = void 0;
|
|
264365
|
+
}
|
|
264366
|
+
if (webLabel === void 0) return data.toString(encoding);
|
|
264367
|
+
if (errors === "strict") return new TextDecoder(webLabel, {
|
|
264368
|
+
fatal: true,
|
|
264369
|
+
ignoreBOM
|
|
264370
|
+
}).decode(data);
|
|
264371
|
+
if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
|
|
264372
|
+
return new TextDecoder(webLabel, {
|
|
264373
|
+
fatal: false,
|
|
264374
|
+
ignoreBOM
|
|
264375
|
+
}).decode(data);
|
|
264268
264376
|
}
|
|
264269
|
-
|
|
264270
|
-
|
|
264271
|
-
|
|
264272
|
-
|
|
264273
|
-
|
|
264274
|
-
|
|
264275
|
-
|
|
264276
|
-
|
|
264277
|
-
|
|
264278
|
-
|
|
264279
|
-
|
|
264280
|
-
|
|
264281
|
-
|
|
264377
|
+
/**
|
|
264378
|
+
* Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
|
|
264379
|
+
* Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
|
|
264380
|
+
* @internal
|
|
264381
|
+
*/
|
|
264382
|
+
function globPatternToRegex(pattern, caseSensitive) {
|
|
264383
|
+
let regex = "^";
|
|
264384
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
264385
|
+
const ch = pattern[i];
|
|
264386
|
+
if (ch === void 0) break;
|
|
264387
|
+
switch (ch) {
|
|
264388
|
+
case "*":
|
|
264389
|
+
regex += "[^/]*";
|
|
264390
|
+
break;
|
|
264391
|
+
case "?":
|
|
264392
|
+
regex += "[^/]";
|
|
264393
|
+
break;
|
|
264394
|
+
case "[": {
|
|
264395
|
+
const end = pattern.indexOf("]", i + 1);
|
|
264396
|
+
if (end === -1) regex += "\\[";
|
|
264397
|
+
else {
|
|
264398
|
+
let charClass = pattern.slice(i + 1, end);
|
|
264399
|
+
charClass = charClass.replace(/\\/g, "\\\\");
|
|
264400
|
+
if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
|
|
264401
|
+
else if (charClass.startsWith("^")) charClass = "\\" + charClass;
|
|
264402
|
+
regex += "[" + charClass + "]";
|
|
264403
|
+
i = end;
|
|
264404
|
+
}
|
|
264405
|
+
break;
|
|
264406
|
+
}
|
|
264407
|
+
case "\\":
|
|
264408
|
+
if (i + 1 < pattern.length) {
|
|
264409
|
+
const next = pattern.charAt(i + 1);
|
|
264410
|
+
regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
|
|
264411
|
+
i++;
|
|
264412
|
+
} else regex += "\\\\";
|
|
264413
|
+
break;
|
|
264414
|
+
default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
|
|
264415
|
+
}
|
|
264282
264416
|
}
|
|
264283
|
-
|
|
264284
|
-
return
|
|
264417
|
+
regex += "$";
|
|
264418
|
+
return new RegExp(regex, caseSensitive ? "" : "i");
|
|
264285
264419
|
}
|
|
264286
264420
|
/**
|
|
264287
|
-
*
|
|
264288
|
-
*
|
|
264289
|
-
*
|
|
264290
|
-
* that pushes a single task turn to ~82% context and into a compaction
|
|
264291
|
-
* death-loop (compaction can only shrink conversation, never the fixed tool
|
|
264292
|
-
* floor, so it re-triggers every turn).
|
|
264421
|
+
* A Readable wrapper that preserves source backpressure while still allowing
|
|
264422
|
+
* consumers to read buffered output after the source has ended.
|
|
264423
|
+
* @internal
|
|
264293
264424
|
*/
|
|
264294
|
-
|
|
264295
|
-
|
|
264296
|
-
|
|
264297
|
-
|
|
264298
|
-
|
|
264299
|
-
|
|
264300
|
-
|
|
264301
|
-
|
|
264302
|
-
|
|
264303
|
-
|
|
264304
|
-
|
|
264305
|
-
|
|
264306
|
-
|
|
264307
|
-
|
|
264308
|
-
|
|
264309
|
-
|
|
264310
|
-
|
|
264311
|
-
|
|
264312
|
-
|
|
264425
|
+
var BufferedReadable = class extends Readable {
|
|
264426
|
+
_source;
|
|
264427
|
+
_ended = false;
|
|
264428
|
+
constructor(source) {
|
|
264429
|
+
super({ highWaterMark: 128 * 1024 });
|
|
264430
|
+
this._source = source;
|
|
264431
|
+
this._source.on("data", this._onData);
|
|
264432
|
+
this._source.on("end", this._onEnd);
|
|
264433
|
+
this._source.on("close", this._onClose);
|
|
264434
|
+
this._source.on("error", this._onError);
|
|
264435
|
+
}
|
|
264436
|
+
_read() {
|
|
264437
|
+
if (!this._ended && !this.destroyed) this._source.resume();
|
|
264438
|
+
}
|
|
264439
|
+
_destroy(error, callback) {
|
|
264440
|
+
this._source.off("data", this._onData);
|
|
264441
|
+
this._source.off("end", this._onEnd);
|
|
264442
|
+
this._source.off("close", this._onClose);
|
|
264443
|
+
this._source.off("error", this._onError);
|
|
264444
|
+
this._source.destroy();
|
|
264445
|
+
callback(error);
|
|
264446
|
+
}
|
|
264447
|
+
_onData = (chunk) => {
|
|
264448
|
+
if (!this.push(chunk)) this._source.pause();
|
|
264449
|
+
};
|
|
264450
|
+
_onEnd = () => {
|
|
264451
|
+
this._ended = true;
|
|
264452
|
+
this.push(null);
|
|
264453
|
+
};
|
|
264454
|
+
_onClose = () => {
|
|
264455
|
+
if (!this._ended) {
|
|
264456
|
+
this._ended = true;
|
|
264457
|
+
this.push(null);
|
|
264458
|
+
}
|
|
264459
|
+
};
|
|
264460
|
+
_onError = (error) => {
|
|
264461
|
+
this.destroy(error);
|
|
264462
|
+
};
|
|
264463
|
+
};
|
|
264464
|
+
//#endregion
|
|
264465
|
+
//#region ../../packages/kaos/src/login-shell-path.ts
|
|
264313
264466
|
/**
|
|
264314
|
-
*
|
|
264315
|
-
*
|
|
264316
|
-
*
|
|
264317
|
-
*
|
|
264318
|
-
*
|
|
264467
|
+
* Login-shell PATH probe — enrich `process.env.PATH` with entries from the
|
|
264468
|
+
* user's login shell.
|
|
264469
|
+
*
|
|
264470
|
+
* When blun is launched from a context that skipped the user's shell
|
|
264471
|
+
* profile (GUI launchers, non-login parent shells), `process.env.PATH`
|
|
264472
|
+
* misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
|
|
264473
|
+
* tool can't find tools the user has in their interactive shell (e.g.
|
|
264474
|
+
* `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
|
|
264475
|
+
* extract its PATH, and append the entries the current PATH lacks. Existing
|
|
264476
|
+
* entries keep their order and priority; failures (no resolvable shell,
|
|
264477
|
+
* hung or broken profile) silently leave PATH untouched.
|
|
264319
264478
|
*
|
|
264479
|
+
* launchd/daemon launches can leave `$SHELL` unset or blank (see
|
|
264480
|
+
* `defaultShell()` in agent-core's terminalService for the same case), so
|
|
264481
|
+
* the probe falls back to the OS account's login shell from the user
|
|
264482
|
+
* database before giving up.
|
|
264483
|
+
*
|
|
264484
|
+
* Like `detectEnvironment`, the probe is a pure function of injected deps
|
|
264485
|
+
* so the suite runs identically on any host. Windows is skipped: the
|
|
264486
|
+
* problem is specific to POSIX login-shell profiles.
|
|
264320
264487
|
*/
|
|
264321
|
-
const
|
|
264322
|
-
/**
|
|
264323
|
-
|
|
264324
|
-
|
|
264325
|
-
|
|
264326
|
-
|
|
264488
|
+
const LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
|
|
264489
|
+
/**
|
|
264490
|
+
* Run the user's login shell and return its PATH, or `undefined` when the
|
|
264491
|
+
* probe does not apply (Windows, no resolvable shell) or fails (spawn
|
|
264492
|
+
* error, timeout, no PATH in the output).
|
|
264493
|
+
*/
|
|
264494
|
+
async function probeLoginShellPath(deps) {
|
|
264495
|
+
if (deps.platform === "win32") return void 0;
|
|
264496
|
+
const envShell = deps.env["SHELL"]?.trim();
|
|
264497
|
+
const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
|
|
264498
|
+
if (shell === void 0 || shell.length === 0) return void 0;
|
|
264499
|
+
const stdout = await deps.execFileText(shell, [
|
|
264500
|
+
"-l",
|
|
264501
|
+
"-c",
|
|
264502
|
+
"/usr/bin/env"
|
|
264503
|
+
], LOGIN_SHELL_ENV_TIMEOUT_MS);
|
|
264504
|
+
if (stdout === void 0) return void 0;
|
|
264505
|
+
let path;
|
|
264506
|
+
for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
|
|
264507
|
+
if (path === void 0 || path.length === 0) return void 0;
|
|
264508
|
+
return path;
|
|
264327
264509
|
}
|
|
264328
264510
|
/**
|
|
264329
|
-
*
|
|
264330
|
-
*
|
|
264331
|
-
* the
|
|
264511
|
+
* Union of the current PATH and the login-shell PATH: the current PATH
|
|
264512
|
+
* string is kept verbatim — including empty components, which POSIX
|
|
264513
|
+
* command lookup treats as the current directory — and login-shell
|
|
264514
|
+
* entries the current PATH lacks are appended in their own order. When
|
|
264515
|
+
* nothing is missing the current string is returned unchanged. Only
|
|
264516
|
+
* absolute login-shell entries are imported: empty, `.`, and relative
|
|
264517
|
+
* components are all cwd-dependent lookup, and appending one the user
|
|
264518
|
+
* did not already have would widen their search path — LocalKaos runs
|
|
264519
|
+
* commands from arbitrary workspace directories.
|
|
264332
264520
|
*/
|
|
264333
|
-
function
|
|
264334
|
-
|
|
264335
|
-
|
|
264336
|
-
|
|
264521
|
+
function mergeLoginShellPath(currentPath, loginShellPath) {
|
|
264522
|
+
const current = currentPath ?? "";
|
|
264523
|
+
const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
|
|
264524
|
+
const additions = [];
|
|
264525
|
+
for (const entry of loginShellPath.split(":")) {
|
|
264526
|
+
if (!entry.startsWith("/") || seen.has(entry)) continue;
|
|
264527
|
+
seen.add(entry);
|
|
264528
|
+
additions.push(entry);
|
|
264337
264529
|
}
|
|
264338
|
-
|
|
264339
|
-
|
|
264340
|
-
|
|
264341
|
-
if (deferred.length > 0) selected.push((0, import_turn_tool_performance_policy.createDeferredToolLoader)(selected, deferred, loadedToolNames));
|
|
264342
|
-
return selected;
|
|
264530
|
+
if (additions.length === 0) return current;
|
|
264531
|
+
if (currentPath === void 0) return additions.join(":");
|
|
264532
|
+
return `${current}:${additions.join(":")}`;
|
|
264343
264533
|
}
|
|
264344
|
-
|
|
264345
|
-
|
|
264346
|
-
const
|
|
264347
|
-
|
|
264348
|
-
|
|
264349
|
-
|
|
264350
|
-
|
|
264351
|
-
|
|
264352
|
-
return cronRunId === void 0 ? GOAL_CONTINUATION_ORIGIN : {
|
|
264353
|
-
kind: "system_trigger",
|
|
264354
|
-
name: "goal_continuation",
|
|
264355
|
-
cronRunId
|
|
264356
|
-
};
|
|
264534
|
+
/** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
|
|
264535
|
+
async function applyLoginShellPath(deps) {
|
|
264536
|
+
const loginShellPath = await probeLoginShellPath(deps);
|
|
264537
|
+
if (loginShellPath === void 0) return;
|
|
264538
|
+
const currentPath = deps.env["PATH"];
|
|
264539
|
+
const merged = mergeLoginShellPath(currentPath, loginShellPath);
|
|
264540
|
+
if (merged === (currentPath ?? "")) return;
|
|
264541
|
+
deps.env["PATH"] = merged;
|
|
264357
264542
|
}
|
|
264358
|
-
const GOAL_COMPLETION_REMINDER_NAME = "goal_completion";
|
|
264359
|
-
const GOAL_BLOCKED_REMINDER_NAME = "goal_blocked";
|
|
264360
|
-
const GOAL_RATE_LIMIT_PAUSE_REASON = "Paused after provider rate limit";
|
|
264361
|
-
const GOAL_PROVIDER_CONNECTION_PAUSE_PREFIX = "Paused after provider connection error";
|
|
264362
|
-
const GOAL_PROVIDER_AUTH_PAUSE_PREFIX = "Paused after provider authentication error";
|
|
264363
|
-
const GOAL_PROVIDER_API_PAUSE_PREFIX = "Paused after provider API error";
|
|
264364
|
-
const GOAL_MODEL_CONFIG_PAUSE_PREFIX = "Paused after model configuration error";
|
|
264365
|
-
const GOAL_RUNTIME_PAUSE_PREFIX = "Paused after runtime error";
|
|
264366
|
-
const GOAL_PROVIDER_FILTERED_PAUSE_REASON = "Paused after provider safety policy block";
|
|
264367
264543
|
/**
|
|
264368
|
-
*
|
|
264369
|
-
*
|
|
264370
|
-
*
|
|
264544
|
+
* Production convenience — apply the probe to `process.env` once per
|
|
264545
|
+
* process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
|
|
264546
|
+
* does not change for the lifetime of the process, and repeated
|
|
264547
|
+
* `LocalKaos.create()` calls must not re-spawn the shell.
|
|
264371
264548
|
*/
|
|
264372
|
-
|
|
264373
|
-
|
|
264374
|
-
|
|
264375
|
-
|
|
264376
|
-
|
|
264377
|
-
|
|
264378
|
-
|
|
264379
|
-
|
|
264380
|
-
|
|
264381
|
-
|
|
264382
|
-
|
|
264383
|
-
|
|
264384
|
-
|
|
264385
|
-
|
|
264386
|
-
"`complete` FIRST, then give your summary. Never end a turn with the goal still active if",
|
|
264387
|
-
"the work is actually done.",
|
|
264388
|
-
"",
|
|
264389
|
-
"Do not mark complete after only producing a plan, summary, first pass, or partial result.",
|
|
264390
|
-
"Do not mark complete after announcing the next step without executing it.",
|
|
264391
|
-
"If an external condition or required user input prevents progress, or the objective cannot",
|
|
264392
|
-
"be completed as stated, call UpdateGoal with `blocked`. On error or failure, call",
|
|
264393
|
-
"UpdateGoal with `blocked`, NOT `complete`.",
|
|
264394
|
-
"Otherwise keep going — use the existing conversation context and your tools,",
|
|
264395
|
-
"and do not ask the user for input unless a real blocker prevents progress."
|
|
264396
|
-
].join(" ");
|
|
264397
|
-
var TurnFlow = class {
|
|
264398
|
-
agent;
|
|
264399
|
-
admissionClosed = false;
|
|
264400
|
-
loadedToolNames = /* @__PURE__ */ new Set();
|
|
264401
|
-
refreshSelectedTools;
|
|
264402
|
-
executions = new ExecutionLedger();
|
|
264403
|
-
executingControllers = /* @__PURE__ */ new Set();
|
|
264404
|
-
steerBuffer = [];
|
|
264405
|
-
nextSteerSequence = 0;
|
|
264406
|
-
turnId = -1;
|
|
264407
|
-
activeTurn = null;
|
|
264408
|
-
toolCallStartedAt = /* @__PURE__ */ new Map();
|
|
264409
|
-
toolCallDupType = /* @__PURE__ */ new Map();
|
|
264410
|
-
stepToolCallKeys = /* @__PURE__ */ new Map();
|
|
264411
|
-
telemetryModeByTurn = /* @__PURE__ */ new Map();
|
|
264412
|
-
currentStepByTurn = /* @__PURE__ */ new Map();
|
|
264413
|
-
interruptedTelemetryTurnIds = /* @__PURE__ */ new Set();
|
|
264414
|
-
stepFailureByTurn = /* @__PURE__ */ new Map();
|
|
264415
|
-
providerRequestFailureByTurn = /* @__PURE__ */ new Map();
|
|
264416
|
-
currentStep = 0;
|
|
264417
|
-
constructor(agent) {
|
|
264418
|
-
this.agent = agent;
|
|
264549
|
+
/**
|
|
264550
|
+
* Login shell from the OS user database (`/etc/passwd` via getpwuid on
|
|
264551
|
+
* Linux, Directory Services on macOS). `userInfo()` throws when the uid
|
|
264552
|
+
* has no database entry (e.g. containers running an arbitrary uid), and
|
|
264553
|
+
* service accounts may carry `/usr/sbin/nologin` — the latter needs no
|
|
264554
|
+
* special casing here because probing it simply fails and degrades
|
|
264555
|
+
* silently.
|
|
264556
|
+
*/
|
|
264557
|
+
function userShellFromNode() {
|
|
264558
|
+
try {
|
|
264559
|
+
const shell = userInfo().shell;
|
|
264560
|
+
return shell === null || shell.length === 0 ? void 0 : shell;
|
|
264561
|
+
} catch {
|
|
264562
|
+
return;
|
|
264419
264563
|
}
|
|
264420
|
-
|
|
264421
|
-
|
|
264422
|
-
|
|
264564
|
+
}
|
|
264565
|
+
let appliedLoginShellPath;
|
|
264566
|
+
function applyLoginShellPathFromNode() {
|
|
264567
|
+
if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
|
|
264568
|
+
appliedLoginShellPath = applyLoginShellPath({
|
|
264569
|
+
platform: process.platform,
|
|
264570
|
+
env: process.env,
|
|
264571
|
+
userShell: userShellFromNode,
|
|
264572
|
+
execFileText
|
|
264573
|
+
});
|
|
264574
|
+
return appliedLoginShellPath;
|
|
264575
|
+
}
|
|
264576
|
+
//#endregion
|
|
264577
|
+
//#region ../../packages/kaos/src/local.ts
|
|
264578
|
+
const isWindows$1 = process.platform === "win32";
|
|
264579
|
+
const READ_CHUNK_SIZE = 64 * 1024;
|
|
264580
|
+
/**
|
|
264581
|
+
* Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
|
|
264582
|
+
* visited set. Returns `null` when `ino` is 0, which Node returns on
|
|
264583
|
+
* filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
|
|
264584
|
+
* mounts). A null key signals "no reliable identity for this dir" so
|
|
264585
|
+
* the caller skips visited tracking for that descent — cycle safety
|
|
264586
|
+
* is weakened on those filesystems, but normal walking works instead
|
|
264587
|
+
* of every directory colliding on the shared key `"<dev>:0"`.
|
|
264588
|
+
*/
|
|
264589
|
+
function cycleKey(s) {
|
|
264590
|
+
if (s.ino === 0) return null;
|
|
264591
|
+
return `${String(s.dev)}:${String(s.ino)}`;
|
|
264592
|
+
}
|
|
264593
|
+
function buildLocalSpawnOptions(_isWindows, cwd, env) {
|
|
264594
|
+
return {
|
|
264595
|
+
cwd,
|
|
264596
|
+
env,
|
|
264597
|
+
stdio: [
|
|
264598
|
+
"pipe",
|
|
264599
|
+
"pipe",
|
|
264600
|
+
"pipe"
|
|
264601
|
+
],
|
|
264602
|
+
detached: true,
|
|
264603
|
+
windowsHide: true
|
|
264604
|
+
};
|
|
264605
|
+
}
|
|
264606
|
+
var LocalProcess = class {
|
|
264607
|
+
stdin;
|
|
264608
|
+
stdout;
|
|
264609
|
+
stderr;
|
|
264610
|
+
pid;
|
|
264611
|
+
_child;
|
|
264612
|
+
_exitCode = null;
|
|
264613
|
+
_exitPromise;
|
|
264614
|
+
_disposed = false;
|
|
264615
|
+
constructor(child) {
|
|
264616
|
+
if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
|
|
264617
|
+
this._child = child;
|
|
264618
|
+
this.stdin = child.stdin;
|
|
264619
|
+
this.stdout = new BufferedReadable(child.stdout);
|
|
264620
|
+
this.stderr = new BufferedReadable(child.stderr);
|
|
264621
|
+
this.pid = child.pid ?? -1;
|
|
264622
|
+
this._exitPromise = new Promise((resolve, reject) => {
|
|
264623
|
+
child.on("exit", (code) => {
|
|
264624
|
+
this._exitCode = code ?? -1;
|
|
264625
|
+
resolve(this._exitCode);
|
|
264626
|
+
});
|
|
264627
|
+
child.on("error", (error) => {
|
|
264628
|
+
reject(error);
|
|
264629
|
+
});
|
|
264630
|
+
});
|
|
264423
264631
|
}
|
|
264424
|
-
|
|
264425
|
-
|
|
264426
|
-
this.closeAdmission();
|
|
264427
|
-
const reason = /* @__PURE__ */ new Error("Turn admission is closed for quiescence");
|
|
264428
|
-
if (this.hasActiveTurn) this.cancel(void 0, reason);
|
|
264429
|
-
for (const controller of this.executingControllers) controller.abort(reason);
|
|
264430
|
-
await deadline.wait(this.executions.drain());
|
|
264431
|
-
const checkedAt = Date.now();
|
|
264432
|
-
const remaining = this.executions.pending;
|
|
264433
|
-
return {
|
|
264434
|
-
admissionClosed: true,
|
|
264435
|
-
checkedAt,
|
|
264436
|
-
remaining,
|
|
264437
|
-
status: deadline.expired || remaining.length > 0 ? "incomplete" : "quiescent",
|
|
264438
|
-
reason: deadline.expired ? "deadline" : void 0
|
|
264439
|
-
};
|
|
264632
|
+
get exitCode() {
|
|
264633
|
+
return this._exitCode;
|
|
264440
264634
|
}
|
|
264441
|
-
|
|
264442
|
-
|
|
264443
|
-
return this.executions.drain();
|
|
264635
|
+
async wait() {
|
|
264636
|
+
return this._exitPromise;
|
|
264444
264637
|
}
|
|
264445
|
-
|
|
264446
|
-
|
|
264638
|
+
kill(signal) {
|
|
264639
|
+
if (this.pid <= 0) return Promise.resolve();
|
|
264640
|
+
if (isWindows$1) {
|
|
264641
|
+
const taskkillArgs = [
|
|
264642
|
+
"/T",
|
|
264643
|
+
"/F",
|
|
264644
|
+
"/PID",
|
|
264645
|
+
String(this.pid)
|
|
264646
|
+
];
|
|
264647
|
+
return new Promise((resolve) => {
|
|
264648
|
+
const killer = spawn("taskkill", taskkillArgs, {
|
|
264649
|
+
stdio: "ignore",
|
|
264650
|
+
windowsHide: true
|
|
264651
|
+
});
|
|
264652
|
+
const done = () => {
|
|
264653
|
+
resolve();
|
|
264654
|
+
};
|
|
264655
|
+
killer.once("error", done);
|
|
264656
|
+
killer.once("close", done);
|
|
264657
|
+
});
|
|
264658
|
+
}
|
|
264659
|
+
try {
|
|
264660
|
+
process.kill(-this.pid, signal ?? "SIGTERM");
|
|
264661
|
+
} catch (error) {
|
|
264662
|
+
const err = error;
|
|
264663
|
+
if (err.code === "ESRCH") return Promise.resolve();
|
|
264664
|
+
if (err.code === "EPERM") {
|
|
264665
|
+
try {
|
|
264666
|
+
this._child.kill(signal ?? "SIGTERM");
|
|
264667
|
+
} catch {}
|
|
264668
|
+
return Promise.resolve();
|
|
264669
|
+
}
|
|
264670
|
+
throw error;
|
|
264671
|
+
}
|
|
264672
|
+
return Promise.resolve();
|
|
264447
264673
|
}
|
|
264448
|
-
|
|
264449
|
-
if (this.
|
|
264674
|
+
dispose() {
|
|
264675
|
+
if (this._disposed) return;
|
|
264676
|
+
this._disposed = true;
|
|
264677
|
+
this.stdin.destroy();
|
|
264678
|
+
this.stdout.destroy();
|
|
264679
|
+
this.stderr.destroy();
|
|
264450
264680
|
}
|
|
264451
|
-
|
|
264681
|
+
};
|
|
264682
|
+
/**
|
|
264683
|
+
* A KAOS implementation that directly interacts with the local filesystem.
|
|
264684
|
+
*
|
|
264685
|
+
* Note: LocalKaos maintains its own per-instance working directory (`_cwd`)
|
|
264686
|
+
* rather than mutating `process.cwd()`. This lets multiple LocalKaos instances
|
|
264687
|
+
* coexist with independent cwds (e.g. when switching contexts via
|
|
264688
|
+
* `runWithKaos`) without cross-polluting each other's relative-path resolution.
|
|
264689
|
+
*/
|
|
264690
|
+
var LocalKaos = class LocalKaos {
|
|
264691
|
+
name = "local";
|
|
264692
|
+
osEnv;
|
|
264693
|
+
_cwd;
|
|
264694
|
+
_envLayers;
|
|
264695
|
+
constructor(osEnv, cwd, envLayers = []) {
|
|
264696
|
+
this._cwd = normalize$3(cwd ?? process.cwd());
|
|
264697
|
+
this.osEnv = osEnv;
|
|
264698
|
+
this._envLayers = envLayers;
|
|
264699
|
+
}
|
|
264700
|
+
/**
|
|
264701
|
+
* Construct a fresh `LocalKaos` after probing the host environment.
|
|
264702
|
+
*
|
|
264703
|
+
* Each call returns a new instance with its own `_cwd`; concurrent
|
|
264704
|
+
* callers can therefore operate on independent working directories
|
|
264705
|
+
* without polluting one another.
|
|
264706
|
+
*/
|
|
264707
|
+
static async create() {
|
|
264708
|
+
const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
|
|
264709
|
+
return new LocalKaos(osEnv);
|
|
264710
|
+
}
|
|
264711
|
+
withCwd(cwd) {
|
|
264712
|
+
return new LocalKaos(this.osEnv, cwd, this._envLayers);
|
|
264713
|
+
}
|
|
264714
|
+
withEnv(env) {
|
|
264715
|
+
return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
|
|
264716
|
+
}
|
|
264717
|
+
_resolvePath(path) {
|
|
264718
|
+
if (isAbsolute$1(path)) return normalize$3(path);
|
|
264719
|
+
return join$1(this._cwd, path);
|
|
264720
|
+
}
|
|
264721
|
+
pathClass() {
|
|
264722
|
+
return isWindows$1 ? "win32" : "posix";
|
|
264723
|
+
}
|
|
264724
|
+
normpath(path) {
|
|
264725
|
+
return normalize$3(path);
|
|
264726
|
+
}
|
|
264727
|
+
gethome() {
|
|
264728
|
+
return normalize$3(homedir());
|
|
264729
|
+
}
|
|
264730
|
+
getcwd() {
|
|
264731
|
+
return this._cwd;
|
|
264732
|
+
}
|
|
264733
|
+
/**
|
|
264734
|
+
* Change the working directory of this LocalKaos instance.
|
|
264735
|
+
*
|
|
264736
|
+
* Unlike Python's `os.chdir`, this is instance-scoped and never touches
|
|
264737
|
+
* `process.cwd()`. Child processes spawned via {@link exec} inherit this
|
|
264738
|
+
* instance's `_cwd`; concurrent LocalKaos instances each carry their own
|
|
264739
|
+
* independent cwd. If you need Python-compatible process-global cwd,
|
|
264740
|
+
* call `process.chdir(x)` directly.
|
|
264741
|
+
*/
|
|
264742
|
+
async chdir(path) {
|
|
264743
|
+
const resolved = this._resolvePath(path);
|
|
264744
|
+
if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
264745
|
+
this._cwd = resolved;
|
|
264746
|
+
}
|
|
264747
|
+
async stat(path, options) {
|
|
264748
|
+
const resolved = this._resolvePath(path);
|
|
264749
|
+
const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
|
|
264750
|
+
return {
|
|
264751
|
+
stMode: s.mode,
|
|
264752
|
+
stIno: s.ino,
|
|
264753
|
+
stDev: s.dev,
|
|
264754
|
+
stNlink: s.nlink,
|
|
264755
|
+
stUid: s.uid,
|
|
264756
|
+
stGid: s.gid,
|
|
264757
|
+
stSize: s.size,
|
|
264758
|
+
stAtime: s.atimeMs / 1e3,
|
|
264759
|
+
stMtime: s.mtimeMs / 1e3,
|
|
264760
|
+
stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
|
|
264761
|
+
};
|
|
264762
|
+
}
|
|
264763
|
+
async *iterdir(path) {
|
|
264764
|
+
const resolved = this._resolvePath(path);
|
|
264765
|
+
const entries = await readdir(resolved);
|
|
264766
|
+
for (const entry of entries) yield join$1(resolved, entry);
|
|
264767
|
+
}
|
|
264768
|
+
async *glob(path, pattern, options) {
|
|
264769
|
+
const resolved = this._resolvePath(path);
|
|
264770
|
+
const caseSensitive = options?.caseSensitive ?? true;
|
|
264771
|
+
const patternParts = pattern.split("/");
|
|
264772
|
+
const initVisited = /* @__PURE__ */ new Set();
|
|
264773
|
+
try {
|
|
264774
|
+
const rootKey = cycleKey(await stat(resolved));
|
|
264775
|
+
if (rootKey !== null) initVisited.add(rootKey);
|
|
264776
|
+
} catch {}
|
|
264777
|
+
yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
|
|
264778
|
+
}
|
|
264779
|
+
async *_globWalk(basePath, patternParts, caseSensitive, visited) {
|
|
264780
|
+
if (patternParts.length === 0) return;
|
|
264781
|
+
const [currentPattern, ...remainingParts] = patternParts;
|
|
264782
|
+
if (currentPattern === "**") {
|
|
264783
|
+
if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
|
|
264784
|
+
else yield basePath;
|
|
264785
|
+
let entries;
|
|
264786
|
+
try {
|
|
264787
|
+
entries = await readdir(basePath);
|
|
264788
|
+
} catch {
|
|
264789
|
+
return;
|
|
264790
|
+
}
|
|
264791
|
+
for (const entry of entries) {
|
|
264792
|
+
const fullPath = join$1(basePath, entry);
|
|
264793
|
+
let entryStat;
|
|
264794
|
+
try {
|
|
264795
|
+
entryStat = await stat(fullPath);
|
|
264796
|
+
} catch {
|
|
264797
|
+
continue;
|
|
264798
|
+
}
|
|
264799
|
+
if (entryStat.isDirectory()) {
|
|
264800
|
+
const key = cycleKey(entryStat);
|
|
264801
|
+
if (key !== null && visited.has(key)) continue;
|
|
264802
|
+
yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
|
|
264803
|
+
} else if (remainingParts.length === 0) yield fullPath;
|
|
264804
|
+
}
|
|
264805
|
+
} else {
|
|
264806
|
+
const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
|
|
264807
|
+
let entries;
|
|
264808
|
+
try {
|
|
264809
|
+
entries = await readdir(basePath);
|
|
264810
|
+
} catch {
|
|
264811
|
+
return;
|
|
264812
|
+
}
|
|
264813
|
+
for (const entry of entries) {
|
|
264814
|
+
if (!regex.test(entry)) continue;
|
|
264815
|
+
const fullPath = join$1(basePath, entry);
|
|
264816
|
+
if (remainingParts.length === 0) yield fullPath;
|
|
264817
|
+
else {
|
|
264818
|
+
let entryStat;
|
|
264819
|
+
try {
|
|
264820
|
+
entryStat = await stat(fullPath);
|
|
264821
|
+
} catch {
|
|
264822
|
+
continue;
|
|
264823
|
+
}
|
|
264824
|
+
if (entryStat.isDirectory()) {
|
|
264825
|
+
const key = cycleKey(entryStat);
|
|
264826
|
+
if (key !== null && visited.has(key)) continue;
|
|
264827
|
+
yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
|
|
264828
|
+
}
|
|
264829
|
+
}
|
|
264830
|
+
}
|
|
264831
|
+
}
|
|
264832
|
+
}
|
|
264833
|
+
async readBytes(path, n) {
|
|
264834
|
+
const resolved = this._resolvePath(path);
|
|
264835
|
+
if (n === void 0) return Buffer.from(await readFile(resolved));
|
|
264836
|
+
const fh = await open(resolved, "r");
|
|
264837
|
+
try {
|
|
264838
|
+
const buf = Buffer.alloc(n);
|
|
264839
|
+
const { bytesRead } = await fh.read(buf, 0, n, 0);
|
|
264840
|
+
return buf.subarray(0, bytesRead);
|
|
264841
|
+
} finally {
|
|
264842
|
+
await fh.close();
|
|
264843
|
+
}
|
|
264844
|
+
}
|
|
264845
|
+
async readText(path, options) {
|
|
264846
|
+
const resolved = this._resolvePath(path);
|
|
264847
|
+
const encoding = options?.encoding ?? "utf-8";
|
|
264848
|
+
const errors = options?.errors ?? "strict";
|
|
264849
|
+
return decodeTextWithErrors(await readFile(resolved), encoding, errors);
|
|
264850
|
+
}
|
|
264851
|
+
async *readLines(path, options) {
|
|
264852
|
+
const resolved = this._resolvePath(path);
|
|
264853
|
+
const encoding = options?.encoding ?? "utf-8";
|
|
264854
|
+
const errors = options?.errors ?? "strict";
|
|
264855
|
+
if (!isUtf8Encoding(encoding)) {
|
|
264856
|
+
yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
|
|
264857
|
+
return;
|
|
264858
|
+
}
|
|
264859
|
+
yield* this._readUtf8Lines(resolved, errors);
|
|
264860
|
+
}
|
|
264861
|
+
async scanTextFile(path) {
|
|
264862
|
+
const fh = await open(this._resolvePath(path), "r");
|
|
264863
|
+
try {
|
|
264864
|
+
const buf = Buffer.alloc(READ_CHUNK_SIZE);
|
|
264865
|
+
const flags = {
|
|
264866
|
+
hasCrLf: false,
|
|
264867
|
+
hasLf: false,
|
|
264868
|
+
hasLoneCr: false
|
|
264869
|
+
};
|
|
264870
|
+
const validator = createUtf8Validator();
|
|
264871
|
+
let totalLines = 0;
|
|
264872
|
+
let totalBytes = 0;
|
|
264873
|
+
let endsWithNewline = false;
|
|
264874
|
+
let hasNul = false;
|
|
264875
|
+
let prevWasCr = false;
|
|
264876
|
+
while (true) {
|
|
264877
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, null);
|
|
264878
|
+
if (bytesRead === 0) break;
|
|
264879
|
+
const chunk = buf.subarray(0, bytesRead);
|
|
264880
|
+
validator.write(chunk);
|
|
264881
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
264882
|
+
const byte = chunk[i];
|
|
264883
|
+
if (byte === void 0) continue;
|
|
264884
|
+
if (byte === 0) hasNul = true;
|
|
264885
|
+
if (byte === 10) totalLines += 1;
|
|
264886
|
+
}
|
|
264887
|
+
prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
|
|
264888
|
+
totalBytes += bytesRead;
|
|
264889
|
+
endsWithNewline = chunk[bytesRead - 1] === 10;
|
|
264890
|
+
}
|
|
264891
|
+
if (prevWasCr) flags.hasLoneCr = true;
|
|
264892
|
+
validator.end();
|
|
264893
|
+
if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
|
|
264894
|
+
return {
|
|
264895
|
+
totalLines,
|
|
264896
|
+
endsWithNewline,
|
|
264897
|
+
hasNul,
|
|
264898
|
+
lineEndingFlags: flags
|
|
264899
|
+
};
|
|
264900
|
+
} finally {
|
|
264901
|
+
await fh.close();
|
|
264902
|
+
}
|
|
264903
|
+
}
|
|
264904
|
+
async *readLineRange(path, options) {
|
|
264905
|
+
const resolved = this._resolvePath(path);
|
|
264906
|
+
const errors = options.errors ?? "strict";
|
|
264907
|
+
yield* this._readUtf8Lines(resolved, errors, {
|
|
264908
|
+
startLine: options.startLine,
|
|
264909
|
+
maxLines: options.maxLines
|
|
264910
|
+
});
|
|
264911
|
+
}
|
|
264912
|
+
async *readTailLines(path, options) {
|
|
264913
|
+
if (options.tailCount <= 0) return;
|
|
264914
|
+
const resolved = this._resolvePath(path);
|
|
264915
|
+
const errors = options.errors ?? "strict";
|
|
264916
|
+
const fh = await open(resolved, "r");
|
|
264917
|
+
try {
|
|
264918
|
+
const s = await fh.stat();
|
|
264919
|
+
if (s.size === 0) return;
|
|
264920
|
+
let pos = s.size;
|
|
264921
|
+
let foundLf = 0;
|
|
264922
|
+
let startOffset = 0;
|
|
264923
|
+
let needLf = options.tailCount;
|
|
264924
|
+
let sawTailBlock = false;
|
|
264925
|
+
while (pos > 0 && foundLf < needLf) {
|
|
264926
|
+
const readSize = Math.min(READ_CHUNK_SIZE, pos);
|
|
264927
|
+
pos -= readSize;
|
|
264928
|
+
const buf = Buffer.alloc(readSize);
|
|
264929
|
+
await fh.read(buf, 0, readSize, pos);
|
|
264930
|
+
if (!sawTailBlock) {
|
|
264931
|
+
sawTailBlock = true;
|
|
264932
|
+
needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
|
|
264933
|
+
}
|
|
264934
|
+
for (let i = readSize - 1; i >= 0; i -= 1) {
|
|
264935
|
+
if (buf[i] !== 10) continue;
|
|
264936
|
+
foundLf += 1;
|
|
264937
|
+
if (foundLf === needLf) {
|
|
264938
|
+
startOffset = pos + i + 1;
|
|
264939
|
+
break;
|
|
264940
|
+
}
|
|
264941
|
+
}
|
|
264942
|
+
}
|
|
264943
|
+
if (foundLf < needLf) startOffset = 0;
|
|
264944
|
+
yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
|
|
264945
|
+
} finally {
|
|
264946
|
+
await fh.close();
|
|
264947
|
+
}
|
|
264948
|
+
}
|
|
264949
|
+
async *_readUtf8Lines(resolved, errors, range) {
|
|
264950
|
+
const startLine = range?.startLine ?? 1;
|
|
264951
|
+
const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
|
|
264952
|
+
const fh = await open(resolved, "r");
|
|
264953
|
+
try {
|
|
264954
|
+
const buf = Buffer.alloc(READ_CHUNK_SIZE);
|
|
264955
|
+
let pending = [];
|
|
264956
|
+
let pendingOffset = 0;
|
|
264957
|
+
let fileOffset = 0;
|
|
264958
|
+
let lineNo = 1;
|
|
264959
|
+
let yielded = 0;
|
|
264960
|
+
while (true) {
|
|
264961
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, null);
|
|
264962
|
+
if (bytesRead === 0) break;
|
|
264963
|
+
const chunk = buf.subarray(0, bytesRead);
|
|
264964
|
+
let lineStart = 0;
|
|
264965
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
264966
|
+
if (chunk[i] !== 10) continue;
|
|
264967
|
+
const piece = chunk.subarray(lineStart, i + 1);
|
|
264968
|
+
const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
|
|
264969
|
+
const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
|
|
264970
|
+
if (lineNo >= startLine) {
|
|
264971
|
+
yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
|
|
264972
|
+
yielded += 1;
|
|
264973
|
+
if (yielded >= maxLines) return;
|
|
264974
|
+
}
|
|
264975
|
+
pending = [];
|
|
264976
|
+
lineStart = i + 1;
|
|
264977
|
+
lineNo += 1;
|
|
264978
|
+
}
|
|
264979
|
+
if (lineStart < chunk.length) {
|
|
264980
|
+
const tail = Buffer.from(chunk.subarray(lineStart));
|
|
264981
|
+
if (pending.length === 0) pendingOffset = fileOffset + lineStart;
|
|
264982
|
+
pending.push(tail);
|
|
264983
|
+
}
|
|
264984
|
+
fileOffset += bytesRead;
|
|
264985
|
+
}
|
|
264986
|
+
if (pending.length > 0) {
|
|
264987
|
+
const line = Buffer.concat(pending);
|
|
264988
|
+
if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
|
|
264989
|
+
}
|
|
264990
|
+
} finally {
|
|
264991
|
+
await fh.close();
|
|
264992
|
+
}
|
|
264993
|
+
}
|
|
264994
|
+
async writeBytes(path, data) {
|
|
264995
|
+
await writeFile(this._resolvePath(path), data);
|
|
264996
|
+
return data.length;
|
|
264997
|
+
}
|
|
264998
|
+
async writeText(path, data, options) {
|
|
264999
|
+
const resolved = this._resolvePath(path);
|
|
265000
|
+
const encoding = options?.encoding ?? "utf-8";
|
|
265001
|
+
if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
|
|
265002
|
+
else await writeFile(resolved, data, encoding);
|
|
265003
|
+
return data.length;
|
|
265004
|
+
}
|
|
265005
|
+
async mkdir(path, options) {
|
|
265006
|
+
const resolved = this._resolvePath(path);
|
|
265007
|
+
const parents = options?.parents ?? false;
|
|
265008
|
+
const existOk = options?.existOk ?? false;
|
|
265009
|
+
if (parents) {
|
|
265010
|
+
if (!existOk) try {
|
|
265011
|
+
if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
|
|
265012
|
+
} catch (error) {
|
|
265013
|
+
if (error instanceof KaosFileExistsError) throw error;
|
|
265014
|
+
if (error.code !== "ENOENT") throw error;
|
|
265015
|
+
}
|
|
265016
|
+
await mkdir(resolved, { recursive: true });
|
|
265017
|
+
return;
|
|
265018
|
+
}
|
|
265019
|
+
try {
|
|
265020
|
+
await mkdir(resolved);
|
|
265021
|
+
} catch (error) {
|
|
265022
|
+
if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
265023
|
+
if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
|
|
265024
|
+
return;
|
|
265025
|
+
}
|
|
265026
|
+
throw error;
|
|
265027
|
+
}
|
|
265028
|
+
}
|
|
265029
|
+
async exec(...args) {
|
|
265030
|
+
const command = args[0];
|
|
265031
|
+
if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
|
|
265032
|
+
const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
|
|
265033
|
+
await waitForSpawn(child);
|
|
265034
|
+
return new LocalProcess(child);
|
|
265035
|
+
}
|
|
265036
|
+
async execWithEnv(args, env) {
|
|
265037
|
+
const command = args[0];
|
|
265038
|
+
if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
|
|
265039
|
+
const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
|
|
265040
|
+
await waitForSpawn(child);
|
|
265041
|
+
return new LocalProcess(child);
|
|
265042
|
+
}
|
|
265043
|
+
_buildExecEnv(invocationEnv) {
|
|
265044
|
+
if (this._envLayers.length === 0) return invocationEnv;
|
|
265045
|
+
const merged = {
|
|
265046
|
+
...process.env,
|
|
265047
|
+
...invocationEnv
|
|
265048
|
+
};
|
|
265049
|
+
for (const layer of this._envLayers) Object.assign(merged, layer);
|
|
265050
|
+
return merged;
|
|
265051
|
+
}
|
|
265052
|
+
};
|
|
265053
|
+
function isUtf8Encoding(encoding) {
|
|
265054
|
+
return encoding === "utf-8" || encoding === "utf8";
|
|
265055
|
+
}
|
|
265056
|
+
function* splitLinesKeepingTerminator(text) {
|
|
265057
|
+
if (text.length === 0) return;
|
|
265058
|
+
let start = 0;
|
|
265059
|
+
for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
|
|
265060
|
+
yield text.slice(start, i + 1);
|
|
265061
|
+
start = i + 1;
|
|
265062
|
+
}
|
|
265063
|
+
if (start < text.length) yield text.slice(start);
|
|
265064
|
+
}
|
|
265065
|
+
function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
|
|
265066
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
265067
|
+
const byte = chunk[i];
|
|
265068
|
+
if (byte === void 0) continue;
|
|
265069
|
+
if (byte === 13) {
|
|
265070
|
+
if (prevWasCr) flags.hasLoneCr = true;
|
|
265071
|
+
prevWasCr = true;
|
|
265072
|
+
} else if (byte === 10) {
|
|
265073
|
+
if (prevWasCr) flags.hasCrLf = true;
|
|
265074
|
+
else flags.hasLf = true;
|
|
265075
|
+
prevWasCr = false;
|
|
265076
|
+
} else {
|
|
265077
|
+
if (prevWasCr) flags.hasLoneCr = true;
|
|
265078
|
+
prevWasCr = false;
|
|
265079
|
+
}
|
|
265080
|
+
}
|
|
265081
|
+
return prevWasCr;
|
|
265082
|
+
}
|
|
265083
|
+
function createUtf8Validator() {
|
|
265084
|
+
let needed = 0;
|
|
265085
|
+
let lower = 128;
|
|
265086
|
+
let upper = 191;
|
|
265087
|
+
const fail = () => {
|
|
265088
|
+
throw new TypeError("Invalid UTF-8 data");
|
|
265089
|
+
};
|
|
265090
|
+
return {
|
|
265091
|
+
write(chunk) {
|
|
265092
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
265093
|
+
const byte = chunk[i];
|
|
265094
|
+
if (byte === void 0) continue;
|
|
265095
|
+
if (needed === 0) {
|
|
265096
|
+
if (byte <= 127) continue;
|
|
265097
|
+
if (byte >= 194 && byte <= 223) needed = 1;
|
|
265098
|
+
else if (byte === 224) {
|
|
265099
|
+
needed = 2;
|
|
265100
|
+
lower = 160;
|
|
265101
|
+
} else if (byte >= 225 && byte <= 236) needed = 2;
|
|
265102
|
+
else if (byte === 237) {
|
|
265103
|
+
needed = 2;
|
|
265104
|
+
upper = 159;
|
|
265105
|
+
} else if (byte >= 238 && byte <= 239) needed = 2;
|
|
265106
|
+
else if (byte === 240) {
|
|
265107
|
+
needed = 3;
|
|
265108
|
+
lower = 144;
|
|
265109
|
+
} else if (byte >= 241 && byte <= 243) needed = 3;
|
|
265110
|
+
else if (byte === 244) {
|
|
265111
|
+
needed = 3;
|
|
265112
|
+
upper = 143;
|
|
265113
|
+
} else fail();
|
|
265114
|
+
} else {
|
|
265115
|
+
if (byte < lower || byte > upper) fail();
|
|
265116
|
+
lower = 128;
|
|
265117
|
+
upper = 191;
|
|
265118
|
+
needed -= 1;
|
|
265119
|
+
}
|
|
265120
|
+
}
|
|
265121
|
+
},
|
|
265122
|
+
end() {
|
|
265123
|
+
if (needed !== 0) fail();
|
|
265124
|
+
}
|
|
265125
|
+
};
|
|
265126
|
+
}
|
|
265127
|
+
async function readRange(fh, start, length) {
|
|
265128
|
+
const data = Buffer.alloc(length);
|
|
265129
|
+
let offset = 0;
|
|
265130
|
+
while (offset < length) {
|
|
265131
|
+
const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
|
|
265132
|
+
if (bytesRead === 0) break;
|
|
265133
|
+
offset += bytesRead;
|
|
265134
|
+
}
|
|
265135
|
+
return offset === length ? data : data.subarray(0, offset);
|
|
265136
|
+
}
|
|
265137
|
+
function waitForSpawn(child) {
|
|
265138
|
+
return new Promise((resolve, reject) => {
|
|
265139
|
+
const onSpawn = () => {
|
|
265140
|
+
child.off("error", onError);
|
|
265141
|
+
resolve();
|
|
265142
|
+
};
|
|
265143
|
+
const onError = (err) => {
|
|
265144
|
+
child.off("spawn", onSpawn);
|
|
265145
|
+
reject(err);
|
|
265146
|
+
};
|
|
265147
|
+
child.once("spawn", onSpawn);
|
|
265148
|
+
child.once("error", onError);
|
|
265149
|
+
});
|
|
265150
|
+
}
|
|
265151
|
+
new AsyncLocalStorage();
|
|
265152
|
+
//#endregion
|
|
265153
|
+
//#region ../../packages/agent-core/src/agent/turn/historical-tool-projection.ts
|
|
265154
|
+
const MIN_CHARS = 3e3;
|
|
265155
|
+
const MAX_REFERENCE_CHARS = 2500;
|
|
265156
|
+
const RECOVERY_REFERENCE = /\[Tool result offloaded\]|\[Repeated tool result omitted\]|^output_path\s*:|Tool output exceeded \d+ characters/m;
|
|
265157
|
+
function identity(stat) {
|
|
265158
|
+
return `${stat.dev}:${stat.ino}:${stat.birthtimeNs}`;
|
|
265159
|
+
}
|
|
265160
|
+
function revision(stat) {
|
|
265161
|
+
return `${identity(stat)}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}:${stat.mode}`;
|
|
265162
|
+
}
|
|
265163
|
+
function canonical(path) {
|
|
265164
|
+
const normalized = resolve(path);
|
|
265165
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
265166
|
+
}
|
|
265167
|
+
async function checkedDirectory(path) {
|
|
265168
|
+
const stat = await lstat(path, { bigint: true });
|
|
265169
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || canonical(await realpath(path)) !== canonical(path)) throw new Error("Unsafe recovery directory");
|
|
265170
|
+
return stat;
|
|
265171
|
+
}
|
|
265172
|
+
async function checkedAncestors(path) {
|
|
265173
|
+
for (let current = resolve(path); current !== parse(current).root; current = dirname(current)) await checkedDirectory(current);
|
|
265174
|
+
}
|
|
265175
|
+
function persistableText(message) {
|
|
265176
|
+
if (message.isError === true || message.content.length !== 1) return void 0;
|
|
265177
|
+
const part = message.content[0];
|
|
265178
|
+
if (part?.type !== "text") return void 0;
|
|
265179
|
+
const text = part.text;
|
|
265180
|
+
if (text.length < MIN_CHARS || text.includes("\0") || RECOVERY_REFERENCE.test(text) || Buffer.from(text, "utf8").toString("utf8") !== text || text.split("\n").some((line) => line.length > 2e3)) return void 0;
|
|
265181
|
+
return text;
|
|
265182
|
+
}
|
|
265183
|
+
/** Lossless request-only view. The owning context and records never receive these references. */
|
|
265184
|
+
var HistoricalToolProjection = class {
|
|
265185
|
+
agent;
|
|
265186
|
+
files = /* @__PURE__ */ new Map();
|
|
265187
|
+
directories = /* @__PURE__ */ new Map();
|
|
265188
|
+
dirtyDirectories = /* @__PURE__ */ new Set();
|
|
265189
|
+
pending = Promise.resolve();
|
|
265190
|
+
constructor(agent) {
|
|
265191
|
+
this.agent = agent;
|
|
265192
|
+
}
|
|
265193
|
+
async project(history, turnAnchor, tools, context) {
|
|
265194
|
+
const unchanged = {
|
|
265195
|
+
history,
|
|
265196
|
+
changed: false
|
|
265197
|
+
};
|
|
265198
|
+
const boundary = turnAnchor === void 0 ? -1 : history.indexOf(turnAnchor);
|
|
265199
|
+
const read = tools.find((tool) => tool.name === "Read");
|
|
265200
|
+
if (boundary < 0 || !(read instanceof ReadTool) || !(this.agent.kaos instanceof LocalKaos) || this.agent.homedir === void 0 || this.agent.hooks?.hasMatchingHook("PreToolUse", "Read")) return unchanged;
|
|
265201
|
+
const calls = /* @__PURE__ */ new Map();
|
|
265202
|
+
const results = /* @__PURE__ */ new Map();
|
|
265203
|
+
const knownTools = new Set(this.agent.tools.loopTools.map((tool) => tool.name));
|
|
265204
|
+
history.forEach((message, index) => {
|
|
265205
|
+
for (const call of message.toolCalls) {
|
|
265206
|
+
const previous = calls.get(call.id);
|
|
265207
|
+
calls.set(call.id, {
|
|
265208
|
+
index,
|
|
265209
|
+
name: call.name,
|
|
265210
|
+
count: (previous?.count ?? 0) + 1
|
|
265211
|
+
});
|
|
265212
|
+
}
|
|
265213
|
+
if (message.role === "tool" && message.toolCallId !== void 0) {
|
|
265214
|
+
const previous = results.get(message.toolCallId);
|
|
265215
|
+
results.set(message.toolCallId, {
|
|
265216
|
+
index,
|
|
265217
|
+
count: (previous?.count ?? 0) + 1
|
|
265218
|
+
});
|
|
265219
|
+
}
|
|
265220
|
+
});
|
|
265221
|
+
const closedBatches = /* @__PURE__ */ new Set();
|
|
265222
|
+
for (let index = 0; index < boundary; index++) {
|
|
265223
|
+
const message = history[index];
|
|
265224
|
+
if (message.role === "assistant" && message.toolCalls.length > 0 && message.toolCalls.every((call) => {
|
|
265225
|
+
const result = results.get(call.id);
|
|
265226
|
+
return calls.get(call.id)?.count === 1 && result?.count === 1 && result.index > index && result.index < boundary;
|
|
265227
|
+
})) closedBatches.add(index);
|
|
265228
|
+
}
|
|
265229
|
+
const projected = [...history];
|
|
265230
|
+
let changed = false;
|
|
265231
|
+
for (let index = 0; index < boundary; index++) {
|
|
265232
|
+
context.signal.throwIfAborted();
|
|
265233
|
+
const message = history[index];
|
|
265234
|
+
if (message.role !== "tool" || !message.toolCallId) continue;
|
|
265235
|
+
const call = calls.get(message.toolCallId);
|
|
265236
|
+
if (call === void 0 || call.count !== 1 || !closedBatches.has(call.index) || !knownTools.has(call.name)) continue;
|
|
265237
|
+
const text = persistableText(message);
|
|
265238
|
+
if (text === void 0) continue;
|
|
265239
|
+
const digest = createHash("sha256").update(JSON.stringify([
|
|
265240
|
+
canonical(this.agent.homedir),
|
|
265241
|
+
call.name,
|
|
265242
|
+
message.toolCallId,
|
|
265243
|
+
text
|
|
265244
|
+
])).digest("hex");
|
|
265245
|
+
const outputPath = join(resolve(this.agent.homedir), "tool-results", "historical", `${digest}.txt`);
|
|
265246
|
+
const header = `[Tool result offloaded]\noutput_path: ${outputPath}\noutput_size_chars: ${text.length}\nsha256: ${createHash("sha256").update(text).digest("hex")}\nnext_step: Use Read with this path and line_offset/n_lines to recover the complete output.
|
|
265247
|
+
|
|
265248
|
+
[preview]
|
|
265249
|
+
`;
|
|
265250
|
+
if (header.length >= MAX_REFERENCE_CHARS) continue;
|
|
265251
|
+
try {
|
|
265252
|
+
if (!await this.canRead(read, outputPath, context)) continue;
|
|
265253
|
+
const save = this.pending.then(() => this.persist(outputPath, text));
|
|
265254
|
+
this.pending = save.catch(() => void 0);
|
|
265255
|
+
await save;
|
|
265256
|
+
const stat = await this.agent.kaos.stat(outputPath);
|
|
265257
|
+
if ((stat.stMode & 61440) !== 32768 || stat.stSize !== Buffer.byteLength(text)) continue;
|
|
265258
|
+
const bytes = await this.agent.kaos.readBytes(outputPath, Buffer.byteLength(text) + 1);
|
|
265259
|
+
if (!Buffer.from(bytes).equals(Buffer.from(text)) || detectFileType(outputPath, Buffer.from(bytes).subarray(0, 512)).kind !== "text") continue;
|
|
265260
|
+
await this.verifyFile(outputPath, text);
|
|
265261
|
+
context.signal.throwIfAborted();
|
|
265262
|
+
if (!await this.canRead(read, outputPath, context)) continue;
|
|
265263
|
+
let preview = text.slice(0, MAX_REFERENCE_CHARS - header.length);
|
|
265264
|
+
if (/[\uD800-\uDBFF]$/.test(preview)) preview = preview.slice(0, -1);
|
|
265265
|
+
projected[index] = {
|
|
265266
|
+
...message,
|
|
265267
|
+
content: [{
|
|
265268
|
+
type: "text",
|
|
265269
|
+
text: header + preview
|
|
265270
|
+
}]
|
|
265271
|
+
};
|
|
265272
|
+
changed = true;
|
|
265273
|
+
} catch {
|
|
265274
|
+
context.signal.throwIfAborted();
|
|
265275
|
+
}
|
|
265276
|
+
}
|
|
265277
|
+
try {
|
|
265278
|
+
for (const path of this.dirtyDirectories) await this.verifyDirectory(path);
|
|
265279
|
+
this.dirtyDirectories.clear();
|
|
265280
|
+
} catch {
|
|
265281
|
+
return unchanged;
|
|
265282
|
+
}
|
|
265283
|
+
return {
|
|
265284
|
+
history: changed ? projected : history,
|
|
265285
|
+
changed
|
|
265286
|
+
};
|
|
265287
|
+
}
|
|
265288
|
+
async canRead(read, path, context) {
|
|
265289
|
+
if (this.agent.tools.loopTools.find((tool) => tool.name === "Read") !== read || this.agent.hooks?.hasMatchingHook("PreToolUse", "Read")) return false;
|
|
265290
|
+
const args = {
|
|
265291
|
+
path,
|
|
265292
|
+
line_offset: 1,
|
|
265293
|
+
n_lines: 1e3
|
|
265294
|
+
};
|
|
265295
|
+
const execution = read.resolveExecution(args);
|
|
265296
|
+
if ("isError" in execution) return false;
|
|
265297
|
+
const toolCall = {
|
|
265298
|
+
type: "function",
|
|
265299
|
+
id: "historical-recovery-check",
|
|
265300
|
+
name: "Read",
|
|
265301
|
+
arguments: JSON.stringify(args)
|
|
265302
|
+
};
|
|
265303
|
+
for (const policy of this.agent.permission.policies) {
|
|
265304
|
+
const result = await policy.evaluate({
|
|
265305
|
+
...context,
|
|
265306
|
+
tool: read,
|
|
265307
|
+
toolCall,
|
|
265308
|
+
toolCalls: [toolCall],
|
|
265309
|
+
args,
|
|
265310
|
+
execution
|
|
265311
|
+
});
|
|
265312
|
+
if (result !== void 0) return result.kind === "approve" && result.executionMetadata === void 0;
|
|
265313
|
+
}
|
|
265314
|
+
return false;
|
|
265315
|
+
}
|
|
265316
|
+
async ensureDirectory(path) {
|
|
265317
|
+
let created = false;
|
|
265318
|
+
try {
|
|
265319
|
+
await mkdir(path, { mode: 448 });
|
|
265320
|
+
created = true;
|
|
265321
|
+
} catch (error) {
|
|
265322
|
+
if (error.code !== "EEXIST") throw error;
|
|
265323
|
+
}
|
|
265324
|
+
if (created && this.directories.has(dirname(path))) this.dirtyDirectories.add(dirname(path));
|
|
265325
|
+
const stat = await checkedDirectory(path);
|
|
265326
|
+
const previous = this.directories.get(path);
|
|
265327
|
+
if (previous !== void 0 && previous.identity !== identity(stat)) throw new Error("Recovery directory replaced");
|
|
265328
|
+
if (previous === void 0) {
|
|
265329
|
+
if (created) await hardenPrivatePath(path, "directory");
|
|
265330
|
+
await this.verifyDirectory(path);
|
|
265331
|
+
} else if (previous.revision !== revision(stat) && !this.dirtyDirectories.has(path)) await this.verifyDirectory(path);
|
|
265332
|
+
}
|
|
265333
|
+
async verifyDirectory(path) {
|
|
265334
|
+
const before = await checkedDirectory(path);
|
|
265335
|
+
const previous = this.directories.get(path);
|
|
265336
|
+
if (previous !== void 0 && previous.identity !== identity(before)) throw new Error("Recovery directory replaced");
|
|
265337
|
+
if (process.platform === "win32") new WindowsPrivateAcl().verify(path, "directory");
|
|
265338
|
+
else if ((before.mode & 63n) !== 0n) throw new Error("Recovery directory is not private");
|
|
265339
|
+
const after = await checkedDirectory(path);
|
|
265340
|
+
if (revision(before) !== revision(after)) throw new Error("Recovery directory changed during verification");
|
|
265341
|
+
this.directories.set(path, {
|
|
265342
|
+
identity: identity(after),
|
|
265343
|
+
revision: revision(after)
|
|
265344
|
+
});
|
|
265345
|
+
}
|
|
265346
|
+
async persist(path, text) {
|
|
265347
|
+
await checkedAncestors(dirname(dirname(dirname(path))));
|
|
265348
|
+
await this.ensureDirectory(dirname(dirname(path)));
|
|
265349
|
+
await this.ensureDirectory(dirname(path));
|
|
265350
|
+
if (this.files.has(path)) {
|
|
265351
|
+
await this.verifyFile(path, text);
|
|
265352
|
+
return;
|
|
265353
|
+
}
|
|
265354
|
+
try {
|
|
265355
|
+
await lstat(path);
|
|
265356
|
+
await this.verifyFile(path, text);
|
|
265357
|
+
if (process.platform === "win32") new WindowsPrivateAcl().verify(path, "file");
|
|
265358
|
+
this.files.set(path, revision(await lstat(path, { bigint: true })));
|
|
265359
|
+
return;
|
|
265360
|
+
} catch (error) {
|
|
265361
|
+
if (error.code !== "ENOENT") throw error;
|
|
265362
|
+
}
|
|
265363
|
+
const staging = join(dirname(path), `${randomUUID()}.staging`);
|
|
265364
|
+
this.dirtyDirectories.add(dirname(path));
|
|
265365
|
+
try {
|
|
265366
|
+
await atomicWritePrivate(staging, text);
|
|
265367
|
+
await checkedAncestors(dirname(path));
|
|
265368
|
+
await link(staging, path);
|
|
265369
|
+
} finally {
|
|
265370
|
+
await unlink(staging).catch(() => void 0);
|
|
265371
|
+
}
|
|
265372
|
+
await this.verifyFile(path, text);
|
|
265373
|
+
this.files.set(path, revision(await lstat(path, { bigint: true })));
|
|
265374
|
+
}
|
|
265375
|
+
async verifyFile(path, text) {
|
|
265376
|
+
const before = await lstat(path, { bigint: true });
|
|
265377
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size !== BigInt(Buffer.byteLength(text)) || process.platform !== "win32" && (before.mode & 63n) !== 0n || canonical(await realpath(path)) !== canonical(path)) throw new Error("Unsafe recovery artifact");
|
|
265378
|
+
const previous = this.files.get(path);
|
|
265379
|
+
if (previous !== void 0 && previous !== revision(before)) throw new Error("Recovery artifact changed");
|
|
265380
|
+
const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
265381
|
+
try {
|
|
265382
|
+
if (revision(await handle.stat({ bigint: true })) !== revision(before) || !(await handle.readFile()).equals(Buffer.from(text)) || revision(await handle.stat({ bigint: true })) !== revision(before) || revision(await lstat(path, { bigint: true })) !== revision(before) || canonical(await realpath(path)) !== canonical(path)) throw new Error("Recovery artifact changed");
|
|
265383
|
+
} finally {
|
|
265384
|
+
await handle.close();
|
|
265385
|
+
}
|
|
265386
|
+
}
|
|
265387
|
+
};
|
|
265388
|
+
//#endregion
|
|
265389
|
+
//#region ../../packages/agent-core/src/agent/response-review-decision.ts
|
|
265390
|
+
const STAGES = [
|
|
265391
|
+
"setup",
|
|
265392
|
+
"native",
|
|
265393
|
+
"release",
|
|
265394
|
+
"verification"
|
|
265395
|
+
];
|
|
265396
|
+
const REASONS = [
|
|
265397
|
+
"invalid_settings",
|
|
265398
|
+
"connection_unavailable",
|
|
265399
|
+
"connection_changed",
|
|
265400
|
+
"native_unavailable",
|
|
265401
|
+
"native_rejected",
|
|
265402
|
+
"release_denied",
|
|
265403
|
+
"release_rejected",
|
|
265404
|
+
"verification_denied",
|
|
265405
|
+
"verification_rejected",
|
|
265406
|
+
"binding_mismatch",
|
|
265407
|
+
"cancelled",
|
|
265408
|
+
"exception"
|
|
265409
|
+
];
|
|
265410
|
+
const FINDINGS = new Set([
|
|
265411
|
+
"empty-target",
|
|
265412
|
+
"unicode-not-nfc",
|
|
265413
|
+
"replacement-character",
|
|
265414
|
+
"nul-character",
|
|
265415
|
+
"script-mismatch",
|
|
265416
|
+
"missing-language-character-profile",
|
|
265417
|
+
"ascii-folding-pressure",
|
|
265418
|
+
"short-text-native-review-required",
|
|
265419
|
+
"suspected-ascii-substitution",
|
|
265420
|
+
"invalid-target-type",
|
|
265421
|
+
"exact-language-required",
|
|
265422
|
+
"missing-response-attestations",
|
|
265423
|
+
"isolated-guard-unavailable",
|
|
265424
|
+
"unrecognized-finding"
|
|
265425
|
+
]);
|
|
265426
|
+
/** Never carry provider/guard prose, signed receipts or arbitrary codes into logs. */
|
|
265427
|
+
function rejectionFindings(report) {
|
|
265428
|
+
const codes = [];
|
|
265429
|
+
if (report?.["reason"] !== void 0) codes.push(report["reason"]);
|
|
265430
|
+
if (Array.isArray(report?.["findings"])) for (const item of report["findings"].slice(0, 32)) codes.push(typeof item === "object" && item !== null && "code" in item ? item.code : void 0);
|
|
265431
|
+
const safe = [...new Set(codes.map((code) => typeof code === "string" && FINDINGS.has(code) ? code : "unrecognized-finding"))];
|
|
265432
|
+
return safe.length ? safe : void 0;
|
|
265433
|
+
}
|
|
265434
|
+
function rejectionMessage(value) {
|
|
265435
|
+
if (!STAGES.includes(value.stage) || !REASONS.includes(value.reason)) return void 0;
|
|
265436
|
+
const findings = rejectionFindings({ findings: value.findings?.map((code) => ({ code })) });
|
|
265437
|
+
return `Response review rejected candidate: ${value.stage}/${value.reason}` + (findings?.length ? ` (${findings.join(", ")})` : "");
|
|
265438
|
+
}
|
|
265439
|
+
//#endregion
|
|
265440
|
+
//#region ../../packages/agent-core/src/agent/response-review-deadline.ts
|
|
265441
|
+
const IDLE_TIMEOUT_MS = 3e4;
|
|
265442
|
+
const NATIVE_FIRST_CONTENT_MS = 9e4;
|
|
265443
|
+
const ABSOLUTE_TIMEOUT_MS = 12e4;
|
|
265444
|
+
function createResponseReviewDeadline(source) {
|
|
265445
|
+
const absoluteExpiresAt = performance.now() + ABSOLUTE_TIMEOUT_MS;
|
|
265446
|
+
const absolute = createDeadlineAbortSignal(source, ABSOLUTE_TIMEOUT_MS);
|
|
265447
|
+
const controller = new AbortController();
|
|
265448
|
+
const unlink = linkAbortSignal(absolute.signal, controller);
|
|
265449
|
+
let stage = "setup";
|
|
265450
|
+
let didTimeout = false;
|
|
265451
|
+
let closed = false;
|
|
265452
|
+
let idle;
|
|
265453
|
+
let idleExpiresAt = 0;
|
|
265454
|
+
const expire = () => {
|
|
265455
|
+
didTimeout = true;
|
|
265456
|
+
controller.abort(abortError());
|
|
265457
|
+
};
|
|
265458
|
+
const expireIfDue = () => {
|
|
265459
|
+
if (closed || controller.signal.aborted) return;
|
|
265460
|
+
const now = performance.now();
|
|
265461
|
+
if (now >= absoluteExpiresAt || now >= idleExpiresAt) expire();
|
|
265462
|
+
};
|
|
265463
|
+
const refresh = (timeoutMs) => {
|
|
265464
|
+
idleExpiresAt = performance.now() + timeoutMs;
|
|
265465
|
+
if (idle !== void 0) clearTimeout(idle);
|
|
265466
|
+
idle = setTimeout(expire, timeoutMs);
|
|
265467
|
+
};
|
|
265468
|
+
refresh(IDLE_TIMEOUT_MS);
|
|
265469
|
+
return {
|
|
265470
|
+
signal: controller.signal,
|
|
265471
|
+
timedOut: () => didTimeout || absolute.timedOut(),
|
|
265472
|
+
throwIfAborted: () => {
|
|
265473
|
+
expireIfDue();
|
|
265474
|
+
controller.signal.throwIfAborted();
|
|
265475
|
+
},
|
|
265476
|
+
progress: (event) => {
|
|
265477
|
+
expireIfDue();
|
|
265478
|
+
if (closed || controller.signal.aborted) return;
|
|
265479
|
+
if (event === "native.start" && stage === "setup") {
|
|
265480
|
+
stage = "native";
|
|
265481
|
+
refresh(NATIVE_FIRST_CONTENT_MS);
|
|
265482
|
+
} else if (event === "native.text" && stage === "native") refresh(IDLE_TIMEOUT_MS);
|
|
265483
|
+
else if (event === "release.start" && stage === "native") {
|
|
265484
|
+
stage = "release";
|
|
265485
|
+
refresh(IDLE_TIMEOUT_MS);
|
|
265486
|
+
} else if (event === "verification.start" && stage === "release") {
|
|
265487
|
+
stage = "verification";
|
|
265488
|
+
refresh(IDLE_TIMEOUT_MS);
|
|
265489
|
+
}
|
|
265490
|
+
},
|
|
265491
|
+
clear: () => {
|
|
265492
|
+
closed = true;
|
|
265493
|
+
if (idle !== void 0) clearTimeout(idle);
|
|
265494
|
+
idle = void 0;
|
|
265495
|
+
unlink();
|
|
265496
|
+
absolute.clear();
|
|
265497
|
+
}
|
|
265498
|
+
};
|
|
265499
|
+
}
|
|
265500
|
+
//#endregion
|
|
265501
|
+
//#region ../../packages/agent-core/src/agent/response-review.ts
|
|
265502
|
+
const MAX_CANDIDATE_BYTES = 1048576;
|
|
265503
|
+
const MAX_CANDIDATE_PARTS = 4096;
|
|
265504
|
+
const REVIEW_CANCEL_SETTLE_MS = 1e3;
|
|
265505
|
+
const MAX_REVIEW_ATTEMPTS = 2;
|
|
265506
|
+
function createReviewedOutputDispatcher(options) {
|
|
265507
|
+
let pending = [];
|
|
265508
|
+
let bytes = 0;
|
|
265509
|
+
let stepUuid;
|
|
265510
|
+
let epoch = 0;
|
|
265511
|
+
let reviewing = false;
|
|
265512
|
+
let turnId;
|
|
265513
|
+
let stepNumber = 0;
|
|
265514
|
+
let toolTextWithheld = false;
|
|
265515
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
265516
|
+
const toolResults = /* @__PURE__ */ new Set();
|
|
265517
|
+
let invalidToolExchange = false;
|
|
265518
|
+
function clear() {
|
|
265519
|
+
pending = [];
|
|
265520
|
+
bytes = 0;
|
|
265521
|
+
stepUuid = void 0;
|
|
265522
|
+
epoch += 1;
|
|
265523
|
+
reviewing = false;
|
|
265524
|
+
toolCalls.clear();
|
|
265525
|
+
toolResults.clear();
|
|
265526
|
+
invalidToolExchange = false;
|
|
265527
|
+
}
|
|
265528
|
+
function dispatch(event) {
|
|
265529
|
+
if (event.type === "text.delta") {
|
|
265530
|
+
options.onTextProgress?.();
|
|
265531
|
+
return;
|
|
265532
|
+
}
|
|
265533
|
+
if (event.type === "step.begin") {
|
|
265534
|
+
clear();
|
|
265535
|
+
if (turnId !== event.turnId) toolTextWithheld = false;
|
|
265536
|
+
turnId = event.turnId;
|
|
265537
|
+
stepNumber = event.step;
|
|
265538
|
+
stepUuid = event.uuid;
|
|
265539
|
+
return options.dispatch(event);
|
|
265540
|
+
}
|
|
265541
|
+
if (event.type === "content.part" && event.part.type === "text") {
|
|
265542
|
+
if (reviewing) {
|
|
265543
|
+
clear();
|
|
265544
|
+
return Promise.reject(/* @__PURE__ */ new Error("Response review candidate changed during verification"));
|
|
265545
|
+
}
|
|
265546
|
+
if (stepUuid !== event.stepUuid) return Promise.reject(/* @__PURE__ */ new Error("Response review step mismatch"));
|
|
265547
|
+
bytes += Buffer.byteLength(event.part.text);
|
|
265548
|
+
if (bytes > MAX_CANDIDATE_BYTES || pending.length >= MAX_CANDIDATE_PARTS) {
|
|
265549
|
+
clear();
|
|
265550
|
+
return Promise.reject(/* @__PURE__ */ new Error("Response review candidate exceeds limit"));
|
|
265551
|
+
}
|
|
265552
|
+
pending.push({
|
|
265553
|
+
...event,
|
|
265554
|
+
part: { ...event.part }
|
|
265555
|
+
});
|
|
265556
|
+
return Promise.resolve();
|
|
265557
|
+
}
|
|
265558
|
+
if (event.type === "step.end") return finish(event);
|
|
265559
|
+
if (event.type === "turn.interrupted") clear();
|
|
265560
|
+
if (event.type === "step.retrying") {
|
|
265561
|
+
clear();
|
|
265562
|
+
stepUuid = event.stepUuid;
|
|
265563
|
+
}
|
|
265564
|
+
if (event.type === "tool.call") {
|
|
265565
|
+
const recordedEpoch = epoch;
|
|
265566
|
+
const { toolCallId, uuid } = event;
|
|
265567
|
+
const matchesStep = event.stepUuid === stepUuid && event.turnId === turnId && event.step === stepNumber;
|
|
265568
|
+
return options.dispatch(event).then(() => {
|
|
265569
|
+
if (recordedEpoch !== epoch) return;
|
|
265570
|
+
if (!matchesStep || toolCalls.has(toolCallId)) invalidToolExchange = true;
|
|
265571
|
+
else toolCalls.set(toolCallId, uuid);
|
|
265572
|
+
});
|
|
265573
|
+
}
|
|
265574
|
+
if (event.type === "tool.result") {
|
|
265575
|
+
const recordedEpoch = epoch;
|
|
265576
|
+
const { toolCallId, parentUuid } = event;
|
|
265577
|
+
return options.dispatch(event).then(() => {
|
|
265578
|
+
if (recordedEpoch !== epoch) return;
|
|
265579
|
+
if (toolCalls.get(toolCallId) !== parentUuid || toolResults.has(toolCallId)) invalidToolExchange = true;
|
|
265580
|
+
else toolResults.add(toolCallId);
|
|
265581
|
+
});
|
|
265582
|
+
}
|
|
265583
|
+
if (event.type === "content.part") return options.dispatch(event);
|
|
265584
|
+
options.dispatch(event);
|
|
265585
|
+
}
|
|
265586
|
+
async function finish(event) {
|
|
265587
|
+
if (pending.length === 0) return options.dispatch(event);
|
|
265588
|
+
if (event.uuid !== stepUuid) throw new Error("Response review step mismatch");
|
|
265589
|
+
const currentEpoch = epoch;
|
|
265590
|
+
const isCurrentCandidate = () => epoch === currentEpoch;
|
|
265591
|
+
const parts = [...pending];
|
|
265592
|
+
reviewing = true;
|
|
265593
|
+
const text = parts.map((part) => part.part.type === "text" ? part.part.text : "").join("");
|
|
265594
|
+
try {
|
|
265595
|
+
if (event.finishReason !== "end_turn" && event.finishReason !== "tool_use") throw new Error("Response review requires a complete candidate");
|
|
265596
|
+
if (options.review === void 0) throw new Error("Response review unavailable");
|
|
265597
|
+
options.signal.throwIfAborted();
|
|
265598
|
+
let approval;
|
|
265599
|
+
let rejection;
|
|
265600
|
+
let nativeRejection = false;
|
|
265601
|
+
for (let attempt = 1; attempt <= MAX_REVIEW_ATTEMPTS; attempt += 1) {
|
|
265602
|
+
options.signal.throwIfAborted();
|
|
265603
|
+
if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
|
|
265604
|
+
const deadline = createResponseReviewDeadline(options.signal);
|
|
265605
|
+
let requested;
|
|
265606
|
+
const pendingOperations = [];
|
|
265607
|
+
let attemptRejection;
|
|
265608
|
+
let attemptNativeRejection = false;
|
|
265609
|
+
try {
|
|
265610
|
+
requested = options.review({
|
|
265611
|
+
text,
|
|
265612
|
+
turnId: event.turnId,
|
|
265613
|
+
step: event.step,
|
|
265614
|
+
signal: deadline.signal,
|
|
265615
|
+
onProgress: (progress) => {
|
|
265616
|
+
if (isCurrentCandidate()) deadline.progress(progress);
|
|
265617
|
+
},
|
|
265618
|
+
onPendingOperation: (operation) => {
|
|
265619
|
+
pendingOperations.push(operation.then(() => void 0, () => void 0));
|
|
265620
|
+
},
|
|
265621
|
+
onRejected: (decision) => {
|
|
265622
|
+
if (!deadline.signal.aborted) {
|
|
265623
|
+
const message = rejectionMessage(decision);
|
|
265624
|
+
attemptNativeRejection = attemptRejection === void 0 && message !== void 0 && decision.stage === "native" && decision.reason === "native_rejected";
|
|
265625
|
+
attemptRejection = message;
|
|
265626
|
+
}
|
|
265627
|
+
}
|
|
265628
|
+
});
|
|
265629
|
+
approval = await abortable(requested, deadline.signal);
|
|
265630
|
+
deadline.throwIfAborted();
|
|
265631
|
+
rejection = attemptRejection;
|
|
265632
|
+
nativeRejection = attemptNativeRejection;
|
|
265633
|
+
break;
|
|
265634
|
+
} catch {
|
|
265635
|
+
options.signal.throwIfAborted();
|
|
265636
|
+
if (attemptRejection !== void 0) throw new Error(attemptRejection);
|
|
265637
|
+
if (deadline.timedOut() && attempt < MAX_REVIEW_ATTEMPTS && requested !== void 0 && epoch === currentEpoch && await waitForCancelledReview(requested, pendingOperations)) continue;
|
|
265638
|
+
throw new Error(deadline.timedOut() ? "Response review timed out" : "Response review failed");
|
|
265639
|
+
} finally {
|
|
265640
|
+
deadline.clear();
|
|
265641
|
+
}
|
|
265642
|
+
}
|
|
265643
|
+
options.signal.throwIfAborted();
|
|
265644
|
+
if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
|
|
265645
|
+
if (nativeRejection && !toolTextWithheld && event.finishReason === "tool_use" && event.turnId === turnId && event.step === stepNumber && !invalidToolExchange && toolCalls.size > 0 && toolResults.size === toolCalls.size) {
|
|
265646
|
+
toolTextWithheld = true;
|
|
265647
|
+
await options.dispatch(event);
|
|
265648
|
+
try {
|
|
265649
|
+
options.onToolTextWithheld?.({
|
|
265650
|
+
turnId: event.turnId,
|
|
265651
|
+
step: event.step
|
|
265652
|
+
});
|
|
265653
|
+
} catch {}
|
|
265654
|
+
return;
|
|
265655
|
+
}
|
|
265656
|
+
if (rejection !== void 0) throw new Error(rejection);
|
|
265657
|
+
if (epoch !== currentEpoch || approval?.targetSha256 !== createHash("sha256").update(text).digest("hex") || !Number.isSafeInteger(approval.expiresAt) || approval.expiresAt <= Date.now()) throw new Error("Response review did not approve the exact current candidate");
|
|
265658
|
+
for (const part of parts) {
|
|
265659
|
+
options.signal.throwIfAborted();
|
|
265660
|
+
if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
|
|
265661
|
+
await options.dispatch(part);
|
|
265662
|
+
}
|
|
265663
|
+
options.signal.throwIfAborted();
|
|
265664
|
+
if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
|
|
265665
|
+
options.dispatch({
|
|
265666
|
+
type: "text.delta",
|
|
265667
|
+
delta: text
|
|
265668
|
+
});
|
|
265669
|
+
await options.dispatch(event);
|
|
265670
|
+
} finally {
|
|
265671
|
+
if (epoch === currentEpoch) clear();
|
|
265672
|
+
}
|
|
265673
|
+
}
|
|
265674
|
+
async function waitForCancelledReview(requested, pendingOperations) {
|
|
265675
|
+
const deadline = createDeadlineAbortSignal(options.signal, REVIEW_CANCEL_SETTLE_MS);
|
|
265676
|
+
try {
|
|
265677
|
+
await abortable(requested.then(() => void 0, () => void 0), deadline.signal);
|
|
265678
|
+
let observed = 0;
|
|
265679
|
+
while (observed < pendingOperations.length) {
|
|
265680
|
+
const batch = pendingOperations.slice(observed);
|
|
265681
|
+
observed = pendingOperations.length;
|
|
265682
|
+
await abortable(Promise.all(batch), deadline.signal);
|
|
265683
|
+
}
|
|
265684
|
+
options.signal.throwIfAborted();
|
|
265685
|
+
return true;
|
|
265686
|
+
} catch {
|
|
265687
|
+
options.signal.throwIfAborted();
|
|
265688
|
+
return false;
|
|
265689
|
+
} finally {
|
|
265690
|
+
deadline.clear();
|
|
265691
|
+
}
|
|
265692
|
+
}
|
|
265693
|
+
return dispatch;
|
|
265694
|
+
}
|
|
265695
|
+
//#endregion
|
|
265696
|
+
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
265697
|
+
function blunExtractText(content) {
|
|
265698
|
+
if (typeof content === "string") return content;
|
|
265699
|
+
if (Array.isArray(content)) return content.map((c) => typeof c === "string" ? c : c && typeof c === "object" && "text" in c && typeof c.text === "string" ? c.text : "").join(" ");
|
|
265700
|
+
return "";
|
|
265701
|
+
}
|
|
265702
|
+
/**
|
|
265703
|
+
* BLUN: decide whether a turn should carry the full tool set. Small-context models
|
|
265704
|
+
* (e.g. King on an 8k window) lose their identity/language when all tool schemas
|
|
265705
|
+
* flood the prompt. A plain greeting omits task tools but keeps any enabled
|
|
265706
|
+
* language review workflow. Conservative: only very short, clearly
|
|
265707
|
+
* conversational messages skip task tools; anything task-like keeps the full set.
|
|
265708
|
+
*/
|
|
265709
|
+
function blunTurnNeedsTools(input, origin) {
|
|
265710
|
+
if (origin.kind !== "user") return true;
|
|
265711
|
+
const text = blunExtractText(input).trim().toLowerCase();
|
|
265712
|
+
if (text.length === 0 || text.length > 48) return true;
|
|
265713
|
+
return !/^(?:hi|hallo|hey|hello|hoi|moin|servus|yo|danke|thanks|thx|bye|tschau|ciao)[.!?]*$/.test(text) && !/^(?:wer bist du|who are you|wie geht(?: es dir)?|how are you|na)[.!?]*$/.test(text);
|
|
265714
|
+
}
|
|
265715
|
+
function blunThinkingIntentInput(input) {
|
|
265716
|
+
const channelText = [...blunExtractText(input).matchAll(/(?:^|\r?\n)<channel\b[^>]*>\r?\n([\s\S]*?)\r?\n<\/channel>(?=\r?\n|$)/gi)].at(-1)?.[1];
|
|
265717
|
+
return channelText === void 0 ? input : [{
|
|
265718
|
+
type: "text",
|
|
265719
|
+
text: channelText
|
|
265720
|
+
}];
|
|
265721
|
+
}
|
|
265722
|
+
function blunTurnHasAttachment(input) {
|
|
265723
|
+
return input.some((part) => part.type !== "text") || /\b(?:image_path|attachment_file_id)\s*=/i.test(blunExtractText(input));
|
|
265724
|
+
}
|
|
265725
|
+
function greetingHistory(history, anchor) {
|
|
265726
|
+
const start = anchor === void 0 ? -1 : history.indexOf(anchor);
|
|
265727
|
+
if (start < 0) return history;
|
|
265728
|
+
const recent = [];
|
|
265729
|
+
let chars = 0;
|
|
265730
|
+
for (let i = start - 1; i >= 0 && recent.length < 6; i -= 1) {
|
|
265731
|
+
const message = history[i];
|
|
265732
|
+
if (message.role === "tool" || message.toolCalls.length > 0) break;
|
|
265733
|
+
if (message.role !== "assistant" && message.origin?.kind !== "user") continue;
|
|
265734
|
+
const size = blunExtractText(message.content).length;
|
|
265735
|
+
if (chars + size > 12e3 || message.content.some((part) => part.type !== "text")) break;
|
|
265736
|
+
recent.unshift(message);
|
|
265737
|
+
chars += size;
|
|
265738
|
+
}
|
|
265739
|
+
while (recent.length > 0 && recent[0].role !== "user") recent.shift();
|
|
265740
|
+
return [...recent, ...history.slice(start)];
|
|
265741
|
+
}
|
|
265742
|
+
/**
|
|
265743
|
+
* BLUN: builtin tools kept when the full schema set would overflow a small
|
|
265744
|
+
* context window. The essential read / edit / run / track loop — enough for
|
|
265745
|
+
* King to do real work on a 32k window without the full ~15k-token tool floor
|
|
265746
|
+
* that pushes a single task turn to ~82% context and into a compaction
|
|
265747
|
+
* death-loop (compaction can only shrink conversation, never the fixed tool
|
|
265748
|
+
* floor, so it re-triggers every turn).
|
|
265749
|
+
*/
|
|
265750
|
+
const BLUN_LEAN_TOOL_NAMES = new Set([
|
|
265751
|
+
"Read",
|
|
265752
|
+
"ReadBatch",
|
|
265753
|
+
"Write",
|
|
265754
|
+
"Edit",
|
|
265755
|
+
"Grep",
|
|
265756
|
+
"Glob",
|
|
265757
|
+
"Bash",
|
|
265758
|
+
"TodoList",
|
|
265759
|
+
"Skill",
|
|
265760
|
+
"CompactConversation",
|
|
265761
|
+
"MistakeRecord"
|
|
265762
|
+
]);
|
|
265763
|
+
const BLUN_LANGUAGE_REVIEW_TOOL_NAMES = new Set([
|
|
265764
|
+
"release_response",
|
|
265765
|
+
"release_translation",
|
|
265766
|
+
"verify_release_token",
|
|
265767
|
+
"validate_text"
|
|
265768
|
+
].map((name) => qualifyMcpToolName("blun-language-guard", name)));
|
|
265769
|
+
/**
|
|
265770
|
+
* Channel/session MCP tools that must survive the lean cut — e.g. the Telegram
|
|
265771
|
+
* reply tools. Without these a bridged session can RECEIVE a message but has no
|
|
265772
|
+
* way to answer it (King processes the turn, then has no reply tool). Their
|
|
265773
|
+
* schemas are tiny, so keeping them does not reappear the compaction floor.
|
|
265774
|
+
* Matches both bare names and MCP-prefixed variants (…__reply).
|
|
265775
|
+
*
|
|
265776
|
+
*/
|
|
265777
|
+
const BLUN_LEAN_KEEP_RE = /(^|__|:)(reply|react|edit_message|download_attachment|memory_(?:status|settings_update|remember|list|threads_list|recall))$/i;
|
|
265778
|
+
/** Telegram writes require a real inbound user turn, never an internal nudge. */
|
|
265779
|
+
const BLUN_TELEGRAM_OUTBOUND_TOOL_RE = /^mcp__[^\s]*telegram[^\s]*__(?:reply|react|edit_message)$/i;
|
|
265780
|
+
function blunToolsForOrigin(tools, origin) {
|
|
265781
|
+
if (origin.kind !== "system_trigger" && origin.kind !== "injection") return tools;
|
|
265782
|
+
return tools.filter((tool) => !BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name));
|
|
265783
|
+
}
|
|
265784
|
+
/**
|
|
265785
|
+
* Restore bounded schema disclosure even on large-context models. Only tools
|
|
265786
|
+
* already eligible for this turn enter the loader; execution still follows
|
|
265787
|
+
* the ordinary permission pipeline.
|
|
265788
|
+
*/
|
|
265789
|
+
function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = []) {
|
|
265790
|
+
if (estimateTokensForTools(tools) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens) || tools.some((tool) => tool.name === "ToolSearch")) {
|
|
265791
|
+
selected.splice(0, selected.length, ...tools);
|
|
265792
|
+
return selected;
|
|
265793
|
+
}
|
|
265794
|
+
selected.splice(0, selected.length, ...tools.filter((tool) => BLUN_LEAN_TOOL_NAMES.has(tool.name) || BLUN_LEAN_KEEP_RE.test(tool.name) || BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name) || loadedToolNames.has(tool.name) || requiredToolNames.has(tool.name)));
|
|
265795
|
+
const names = new Set(selected.map((tool) => tool.name));
|
|
265796
|
+
const deferred = tools.filter((tool) => !names.has(tool.name));
|
|
265797
|
+
if (deferred.length > 0) selected.push((0, import_turn_tool_performance_policy.createDeferredToolLoader)(selected, deferred, loadedToolNames));
|
|
265798
|
+
return selected;
|
|
265799
|
+
}
|
|
265800
|
+
const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
|
|
265801
|
+
/** Origin tag for the synthetic "continue" prompt that drives each goal turn. */
|
|
265802
|
+
const GOAL_CONTINUATION_ORIGIN = {
|
|
265803
|
+
kind: "system_trigger",
|
|
265804
|
+
name: "goal_continuation"
|
|
265805
|
+
};
|
|
265806
|
+
function goalContinuationOrigin(origin) {
|
|
265807
|
+
const cronRunId = origin.kind === "cron_job" ? origin.runId : origin.kind === "system_trigger" && origin.name === "goal_continuation" ? origin.cronRunId : void 0;
|
|
265808
|
+
return cronRunId === void 0 ? GOAL_CONTINUATION_ORIGIN : {
|
|
265809
|
+
kind: "system_trigger",
|
|
265810
|
+
name: "goal_continuation",
|
|
265811
|
+
cronRunId
|
|
265812
|
+
};
|
|
265813
|
+
}
|
|
265814
|
+
const GOAL_COMPLETION_REMINDER_NAME = "goal_completion";
|
|
265815
|
+
const GOAL_BLOCKED_REMINDER_NAME = "goal_blocked";
|
|
265816
|
+
const GOAL_RATE_LIMIT_PAUSE_REASON = "Paused after provider rate limit";
|
|
265817
|
+
const GOAL_PROVIDER_CONNECTION_PAUSE_PREFIX = "Paused after provider connection error";
|
|
265818
|
+
const GOAL_PROVIDER_AUTH_PAUSE_PREFIX = "Paused after provider authentication error";
|
|
265819
|
+
const GOAL_PROVIDER_API_PAUSE_PREFIX = "Paused after provider API error";
|
|
265820
|
+
const GOAL_MODEL_CONFIG_PAUSE_PREFIX = "Paused after model configuration error";
|
|
265821
|
+
const GOAL_RUNTIME_PAUSE_PREFIX = "Paused after runtime error";
|
|
265822
|
+
const GOAL_PROVIDER_FILTERED_PAUSE_REASON = "Paused after provider safety policy block";
|
|
265823
|
+
/**
|
|
265824
|
+
* The prompt the goal driver appends to start each continuation turn — the
|
|
265825
|
+
* autonomous stand-in for the user typing "continue". The model decides when to
|
|
265826
|
+
* stop by calling `UpdateGoal`; otherwise the driver runs another turn.
|
|
265827
|
+
*/
|
|
265828
|
+
const GOAL_CONTINUATION_PROMPT = [
|
|
265829
|
+
"Continue working toward the active goal.",
|
|
265830
|
+
"Keep the self-audit brief. Do not explore unrelated interpretations once the goal can be",
|
|
265831
|
+
"decided. If the objective is simple, already answered, impossible, unsafe, or contradictory,",
|
|
265832
|
+
"do not run another goal turn. Explain briefly if useful, then call UpdateGoal with `complete`",
|
|
265833
|
+
"or `blocked` in the same turn. Otherwise, weigh the objective and any completion criteria",
|
|
265834
|
+
"against the work done so far. Goal mode is iterative: do one coherent slice of work, then",
|
|
265835
|
+
"reassess.",
|
|
265836
|
+
"",
|
|
265837
|
+
"CRITICAL — WHEN TO END THE GOAL:",
|
|
265838
|
+
"When all required work is done — files written, code executed, tests passing, validation",
|
|
265839
|
+
"passed — you MUST call UpdateGoal with `complete` IMMEDIATELY. Do not narrerate the next step",
|
|
265840
|
+
"and stop without calling it. Do not leave the goal active when the work is finished.",
|
|
265841
|
+
"If you have completed the task and are about to end your response, call UpdateGoal with",
|
|
265842
|
+
"`complete` FIRST, then give your summary. Never end a turn with the goal still active if",
|
|
265843
|
+
"the work is actually done.",
|
|
265844
|
+
"",
|
|
265845
|
+
"Do not mark complete after only producing a plan, summary, first pass, or partial result.",
|
|
265846
|
+
"Do not mark complete after announcing the next step without executing it.",
|
|
265847
|
+
"If an external condition or required user input prevents progress, or the objective cannot",
|
|
265848
|
+
"be completed as stated, call UpdateGoal with `blocked`. On error or failure, call",
|
|
265849
|
+
"UpdateGoal with `blocked`, NOT `complete`.",
|
|
265850
|
+
"Otherwise keep going — use the existing conversation context and your tools,",
|
|
265851
|
+
"and do not ask the user for input unless a real blocker prevents progress."
|
|
265852
|
+
].join(" ");
|
|
265853
|
+
var TurnFlow = class {
|
|
265854
|
+
agent;
|
|
265855
|
+
admissionClosed = false;
|
|
265856
|
+
loadedToolNames = /* @__PURE__ */ new Set();
|
|
265857
|
+
refreshSelectedTools;
|
|
265858
|
+
executions = new ExecutionLedger();
|
|
265859
|
+
executingControllers = /* @__PURE__ */ new Set();
|
|
265860
|
+
steerBuffer = [];
|
|
265861
|
+
nextSteerSequence = 0;
|
|
265862
|
+
turnId = -1;
|
|
265863
|
+
activeTurn = null;
|
|
265864
|
+
toolCallStartedAt = /* @__PURE__ */ new Map();
|
|
265865
|
+
toolCallDupType = /* @__PURE__ */ new Map();
|
|
265866
|
+
stepToolCallKeys = /* @__PURE__ */ new Map();
|
|
265867
|
+
telemetryModeByTurn = /* @__PURE__ */ new Map();
|
|
265868
|
+
currentStepByTurn = /* @__PURE__ */ new Map();
|
|
265869
|
+
interruptedTelemetryTurnIds = /* @__PURE__ */ new Set();
|
|
265870
|
+
stepFailureByTurn = /* @__PURE__ */ new Map();
|
|
265871
|
+
providerRequestFailureByTurn = /* @__PURE__ */ new Map();
|
|
265872
|
+
currentStep = 0;
|
|
265873
|
+
historicalToolProjection;
|
|
265874
|
+
constructor(agent) {
|
|
265875
|
+
this.agent = agent;
|
|
265876
|
+
this.historicalToolProjection = new HistoricalToolProjection(agent);
|
|
265877
|
+
}
|
|
265878
|
+
/** Explicit host shutdown only; ordinary cancellation remains resumable. */
|
|
265879
|
+
closeAdmission() {
|
|
265880
|
+
this.admissionClosed = true;
|
|
265881
|
+
}
|
|
265882
|
+
async quiesce(options) {
|
|
265883
|
+
const deadline = new QuiescenceDeadline(options.deadlineAt);
|
|
265884
|
+
this.closeAdmission();
|
|
265885
|
+
const reason = /* @__PURE__ */ new Error("Turn admission is closed for quiescence");
|
|
265886
|
+
if (this.hasActiveTurn) this.cancel(void 0, reason);
|
|
265887
|
+
for (const controller of this.executingControllers) controller.abort(reason);
|
|
265888
|
+
await deadline.wait(this.executions.drain());
|
|
265889
|
+
const checkedAt = Date.now();
|
|
265890
|
+
const remaining = this.executions.pending;
|
|
265891
|
+
return {
|
|
265892
|
+
admissionClosed: true,
|
|
265893
|
+
checkedAt,
|
|
265894
|
+
remaining,
|
|
265895
|
+
status: deadline.expired || remaining.length > 0 ? "incomplete" : "quiescent",
|
|
265896
|
+
reason: deadline.expired ? "deadline" : void 0
|
|
265897
|
+
};
|
|
265898
|
+
}
|
|
265899
|
+
/** Actual execution, unlike waitForCurrentTurn(signal), is not abortable. */
|
|
265900
|
+
waitForExecutionEnd() {
|
|
265901
|
+
return this.executions.drain();
|
|
265902
|
+
}
|
|
265903
|
+
get pendingExecutions() {
|
|
265904
|
+
return this.executions.pending;
|
|
265905
|
+
}
|
|
265906
|
+
assertAdmissionOpen() {
|
|
265907
|
+
if (this.admissionClosed) throw new Error("Turn admission is closed for quiescence");
|
|
265908
|
+
}
|
|
265909
|
+
/** Best-effort agent id (main / generated id) derived from the agent homedir. */
|
|
264452
265910
|
get agentId() {
|
|
264453
265911
|
return this.agent.homedir ? basename$1(this.agent.homedir) : this.agent.type;
|
|
264454
265912
|
}
|
|
@@ -264821,7 +266279,10 @@ var TurnFlow = class {
|
|
|
264821
266279
|
turnId,
|
|
264822
266280
|
origin
|
|
264823
266281
|
});
|
|
266282
|
+
const previousLastMessage = this.agent.context.history.at(-1);
|
|
264824
266283
|
this.agent.context.appendUserMessage(input, origin);
|
|
266284
|
+
const appendedMessage = this.agent.context.history.at(-1);
|
|
266285
|
+
const turnAnchor = appendedMessage !== previousLastMessage && appendedMessage?.role === "user" && appendedMessage.origin === origin ? appendedMessage : void 0;
|
|
264825
266286
|
const startedAt = Date.now();
|
|
264826
266287
|
let ended;
|
|
264827
266288
|
let blockedByUserPromptHook = false;
|
|
@@ -264848,7 +266309,7 @@ var TurnFlow = class {
|
|
|
264848
266309
|
this.agent.log.warn("Turn-start observation failed");
|
|
264849
266310
|
}
|
|
264850
266311
|
signal.throwIfAborted();
|
|
264851
|
-
const stopReason = await this.runStepLoop(turnId, signal, input, origin, (response) => {
|
|
266312
|
+
const stopReason = await this.runStepLoop(turnId, signal, input, origin, turnAnchor, (response) => {
|
|
264852
266313
|
completedResponse = response;
|
|
264853
266314
|
});
|
|
264854
266315
|
completedStopReason = stopReason;
|
|
@@ -265013,7 +266474,7 @@ var TurnFlow = class {
|
|
|
265013
266474
|
content: hookResult.message
|
|
265014
266475
|
});
|
|
265015
266476
|
}
|
|
265016
|
-
async runStepLoop(turnId, signal, input, origin, onFinalResponse) {
|
|
266477
|
+
async runStepLoop(turnId, signal, input, origin, turnAnchor, onFinalResponse) {
|
|
265017
266478
|
let stopHookContinuationUsed = false;
|
|
265018
266479
|
let goalOutcomeMessageContinuationUsed = false;
|
|
265019
266480
|
const deduper = new ToolCallDeduplicator({ telemetry: this.agent.telemetry });
|
|
@@ -265026,11 +266487,25 @@ var TurnFlow = class {
|
|
|
265026
266487
|
let taskToolsNeeded = turnNeedsThinking || blunTurnHasAttachment(input);
|
|
265027
266488
|
const greetingAnchor = this.agent.context.history.findLast((message) => message.role === "user" && message.origin?.kind === "user");
|
|
265028
266489
|
let partialContext = false;
|
|
265029
|
-
const turnHistory = () => {
|
|
265030
|
-
const
|
|
266490
|
+
const turnHistory = async (tools) => {
|
|
266491
|
+
const raw = [...this.agent.context.history];
|
|
266492
|
+
const history = personalMemoryRecall === void 0 ? raw : [...raw, personalMemoryRecall];
|
|
265031
266493
|
const selected = taskToolsNeeded ? history : greetingHistory(history, greetingAnchor);
|
|
265032
|
-
|
|
265033
|
-
|
|
266494
|
+
const projected = await this.historicalToolProjection.project(selected, turnAnchor, tools, {
|
|
266495
|
+
turnId: String(turnId),
|
|
266496
|
+
stepNumber: this.currentStep,
|
|
266497
|
+
signal,
|
|
266498
|
+
llm: turnLLM
|
|
266499
|
+
});
|
|
266500
|
+
const current = this.agent.context.history;
|
|
266501
|
+
if (current.length !== raw.length || current.some((message, index) => message !== raw[index])) {
|
|
266502
|
+
const latest = personalMemoryRecall === void 0 ? current : [...current, personalMemoryRecall];
|
|
266503
|
+
const retained = taskToolsNeeded ? latest : greetingHistory(latest, greetingAnchor);
|
|
266504
|
+
partialContext = retained.length < latest.length;
|
|
266505
|
+
return retained;
|
|
266506
|
+
}
|
|
266507
|
+
partialContext = selected.length < history.length || projected.changed;
|
|
266508
|
+
return projected.history;
|
|
265034
266509
|
};
|
|
265035
266510
|
const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off");
|
|
265036
266511
|
const turnTools = this.agent.tools.captureTurnTools();
|
|
@@ -265066,8 +266541,8 @@ var TurnFlow = class {
|
|
|
265066
266541
|
}, completion);
|
|
265067
266542
|
},
|
|
265068
266543
|
llm: turnLLM,
|
|
265069
|
-
buildMessages: () => this.agent.context.project(turnHistory(), { dropOrphanResults: true }),
|
|
265070
|
-
buildMessagesStrict: () => this.agent.context.project(turnHistory(), {
|
|
266544
|
+
buildMessages: async () => this.agent.context.project(await turnHistory(selectedTools), { dropOrphanResults: true }),
|
|
266545
|
+
buildMessagesStrict: async () => this.agent.context.project(await turnHistory(selectedTools), {
|
|
265071
266546
|
synthesizeMissing: true,
|
|
265072
266547
|
dropOrphanResults: true,
|
|
265073
266548
|
dedupeDuplicateToolCalls: true,
|
|
@@ -268209,12 +269684,14 @@ function readPayload(result, includeError = false) {
|
|
|
268209
269684
|
/** Receipts never enter ordinary tool execution, hooks, replay or model history. */
|
|
268210
269685
|
function createGuardResponseReviewer(host) {
|
|
268211
269686
|
const { language, guardVersion } = host;
|
|
268212
|
-
return async ({ text, turnId, step, signal, onRejected }) => {
|
|
269687
|
+
return async ({ text, turnId, step, signal, onRejected, onPendingOperation, onProgress }) => {
|
|
268213
269688
|
const input = {
|
|
268214
269689
|
text,
|
|
268215
269690
|
turnId,
|
|
268216
269691
|
step,
|
|
268217
|
-
signal
|
|
269692
|
+
signal,
|
|
269693
|
+
onPendingOperation,
|
|
269694
|
+
onProgress
|
|
268218
269695
|
};
|
|
268219
269696
|
const targetSha256 = digest(text);
|
|
268220
269697
|
let stage = "setup";
|
|
@@ -268241,6 +269718,10 @@ function createGuardResponseReviewer(host) {
|
|
|
268241
269718
|
const blocked = (reason, findings) => {
|
|
268242
269719
|
record(signal.aborted ? "cancelled" : reason, findings);
|
|
268243
269720
|
};
|
|
269721
|
+
const track = (operation) => {
|
|
269722
|
+
onPendingOperation?.(operation.then(() => void 0, () => void 0));
|
|
269723
|
+
return operation;
|
|
269724
|
+
};
|
|
268244
269725
|
try {
|
|
268245
269726
|
if (!/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(language) || /^(auto|all)$/i.test(language) || guardVersion.trim().length === 0) return blocked("invalid_settings");
|
|
268246
269727
|
const resolve = () => {
|
|
@@ -268253,13 +269734,14 @@ function createGuardResponseReviewer(host) {
|
|
|
268253
269734
|
const current = () => !signal.aborted && resolve()?.client === connection.client;
|
|
268254
269735
|
if (!current()) return blocked("connection_changed");
|
|
268255
269736
|
stage = "native";
|
|
268256
|
-
|
|
269737
|
+
onProgress?.("native.start");
|
|
269738
|
+
const native = await abortable(track(host.reviewNative({ ...input })), signal);
|
|
268257
269739
|
if (!current()) return blocked("connection_changed");
|
|
268258
269740
|
if (native === void 0) return blocked("native_unavailable");
|
|
268259
269741
|
if (!passed(native.nativeness) || !passed(native.orthography)) return blocked("native_rejected");
|
|
268260
269742
|
const authorized = async (name) => {
|
|
268261
269743
|
if (!current()) return false;
|
|
268262
|
-
return passed(await abortable(host.authorize({
|
|
269744
|
+
return passed(await abortable(track(host.authorize({
|
|
268263
269745
|
toolName: qualifyMcpToolName(SERVER_NAME, name),
|
|
268264
269746
|
targetSha256,
|
|
268265
269747
|
language,
|
|
@@ -268268,11 +269750,12 @@ function createGuardResponseReviewer(host) {
|
|
|
268268
269750
|
turnId,
|
|
268269
269751
|
step,
|
|
268270
269752
|
signal
|
|
268271
|
-
}), signal)) && current();
|
|
269753
|
+
})), signal)) && current();
|
|
268272
269754
|
};
|
|
268273
269755
|
stage = "release";
|
|
269756
|
+
onProgress?.("release.start");
|
|
268274
269757
|
if (!await authorized("release_response")) return blocked("release_denied");
|
|
268275
|
-
const releaseResult = await abortable(connection.client.callTool("release_response", {
|
|
269758
|
+
const releaseResult = await abortable(track(connection.client.callTool("release_response", {
|
|
268276
269759
|
target_text: text,
|
|
268277
269760
|
language,
|
|
268278
269761
|
content_type: "prose",
|
|
@@ -268281,7 +269764,7 @@ function createGuardResponseReviewer(host) {
|
|
|
268281
269764
|
nativeness: true,
|
|
268282
269765
|
orthography: true
|
|
268283
269766
|
}
|
|
268284
|
-
}, signal), signal);
|
|
269767
|
+
}, signal)), signal);
|
|
268285
269768
|
if (!current()) return blocked("connection_changed");
|
|
268286
269769
|
if (releaseResult.isError) return blocked("release_rejected", rejectionFindings(readPayload(releaseResult, true)));
|
|
268287
269770
|
const released = readPayload(releaseResult);
|
|
@@ -268289,8 +269772,9 @@ function createGuardResponseReviewer(host) {
|
|
|
268289
269772
|
if (!current()) return blocked("connection_changed");
|
|
268290
269773
|
if (status !== "PASS" || release_allowed !== true || blocking === true || typeof release_token !== "string" || release_token.length === 0 || release_token.length > 65536) return blocked("release_rejected", rejectionFindings(released));
|
|
268291
269774
|
stage = "verification";
|
|
269775
|
+
onProgress?.("verification.start");
|
|
268292
269776
|
if (!await authorized("verify_release_token")) return blocked("verification_denied");
|
|
268293
|
-
const { status: verificationStatus, valid, checks, payload } = readPayload(await abortable(connection.client.callTool("verify_release_token", {
|
|
269777
|
+
const { status: verificationStatus, valid, checks, payload } = readPayload(await abortable(track(connection.client.callTool("verify_release_token", {
|
|
268294
269778
|
release_token,
|
|
268295
269779
|
source_text: "",
|
|
268296
269780
|
target_text: text,
|
|
@@ -268298,7 +269782,7 @@ function createGuardResponseReviewer(host) {
|
|
|
268298
269782
|
purpose: "response",
|
|
268299
269783
|
content_type: "prose",
|
|
268300
269784
|
short_text_reviewed: false
|
|
268301
|
-
}, signal), signal)) ?? {};
|
|
269785
|
+
}, signal)), signal)) ?? {};
|
|
268302
269786
|
if (!current()) return blocked("connection_changed");
|
|
268303
269787
|
if (verificationStatus !== "PASS" || valid !== true || !isPlainRecord(checks) || !CHECK_NAMES.every((name) => checks[name] === true) || !isPlainRecord(payload)) return blocked("verification_rejected");
|
|
268304
269788
|
const { v, source_sha256, target_sha256, language: verifiedLanguage, purpose, content_type, short_text_reviewed, exp, iat } = payload;
|
|
@@ -268370,6 +269854,7 @@ async function reviewNativeResponse(agent, language, input) {
|
|
|
268370
269854
|
});
|
|
268371
269855
|
};
|
|
268372
269856
|
const valueType = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
269857
|
+
let formatRetry = false;
|
|
268373
269858
|
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
268374
269859
|
let receivedBytes = 0;
|
|
268375
269860
|
let outputLimitExceeded = false;
|
|
@@ -268378,21 +269863,27 @@ async function reviewNativeResponse(agent, language, input) {
|
|
|
268378
269863
|
};
|
|
268379
269864
|
const options = {
|
|
268380
269865
|
signal,
|
|
269866
|
+
onExecution: (completion) => {
|
|
269867
|
+
input.onPendingOperation?.(completion.then(() => void 0, () => void 0));
|
|
269868
|
+
},
|
|
268381
269869
|
requestLogFields: {
|
|
268382
269870
|
turnStep: `review:${input.turnId}.${String(input.step)}`,
|
|
268383
269871
|
attempt: `${attempt}/2`
|
|
268384
269872
|
}
|
|
268385
269873
|
};
|
|
268386
|
-
const prompt =
|
|
269874
|
+
const prompt = !formatRetry ? NATIVE_REVIEW_PROMPT : `${NATIVE_REVIEW_PROMPT}\nThe previous review did not satisfy the required JSON format. Return exactly one object with the two required boolean keys nativeness and orthography, no wrapper or additional keys. Use true or false, not strings or null. Set a check to false if it fails or you are uncertain.`;
|
|
268387
269875
|
try {
|
|
268388
269876
|
signal.throwIfAborted();
|
|
268389
|
-
const
|
|
269877
|
+
const requested = agent.generate(provider, prompt, [], [createUserMessage(candidateMessage)], { onMessagePart: (part) => {
|
|
268390
269878
|
if (part.type === "text") receivedBytes += Buffer.byteLength(part.text);
|
|
268391
269879
|
if (receivedBytes > MAX_REVIEW_RESULT_BYTES) {
|
|
268392
269880
|
outputLimitExceeded = true;
|
|
268393
269881
|
throw new Error("Native review output exceeds limit");
|
|
268394
269882
|
}
|
|
268395
|
-
|
|
269883
|
+
if (!signal.aborted && part.type === "text" && part.text.trim().length > 0) input.onProgress?.("native.text");
|
|
269884
|
+
} }, options);
|
|
269885
|
+
input.onPendingOperation?.(requested.then(() => void 0, () => void 0));
|
|
269886
|
+
const response = await abortable(requested, signal);
|
|
268396
269887
|
if (response.usage !== null) agent.usage.record(modelAlias, response.usage, "turn");
|
|
268397
269888
|
signal.throwIfAborted();
|
|
268398
269889
|
if (response.message.toolCalls.length > 0) return invalid("tool_calls");
|
|
@@ -268405,7 +269896,10 @@ async function reviewNativeResponse(agent, language, input) {
|
|
|
268405
269896
|
parsed = JSON.parse(text);
|
|
268406
269897
|
} catch {
|
|
268407
269898
|
record("invalid_json", attempt);
|
|
268408
|
-
if (attempt === 1)
|
|
269899
|
+
if (attempt === 1) {
|
|
269900
|
+
formatRetry = true;
|
|
269901
|
+
continue;
|
|
269902
|
+
}
|
|
268409
269903
|
return;
|
|
268410
269904
|
}
|
|
268411
269905
|
if (!isPlainRecord(parsed) || Object.keys(parsed).length !== 2 || typeof parsed["nativeness"] !== "boolean" || typeof parsed["orthography"] !== "boolean") {
|
|
@@ -268416,7 +269910,10 @@ async function reviewNativeResponse(agent, language, input) {
|
|
|
268416
269910
|
nativenessType: fields ? valueType(fields["nativeness"]) : "missing",
|
|
268417
269911
|
orthographyType: fields ? valueType(fields["orthography"]) : "missing"
|
|
268418
269912
|
});
|
|
268419
|
-
if (attempt === 1 && !(fields && (fields["nativeness"] === false || fields["orthography"] === false)))
|
|
269913
|
+
if (attempt === 1 && !(fields && (fields["nativeness"] === false || fields["orthography"] === false))) {
|
|
269914
|
+
formatRetry = true;
|
|
269915
|
+
continue;
|
|
269916
|
+
}
|
|
268420
269917
|
return;
|
|
268421
269918
|
}
|
|
268422
269919
|
record(parsed["nativeness"] && parsed["orthography"] ? "approved" : "native_rejected", attempt, {
|
|
@@ -268427,7 +269924,12 @@ async function reviewNativeResponse(agent, language, input) {
|
|
|
268427
269924
|
nativeness: parsed["nativeness"],
|
|
268428
269925
|
orthography: parsed["orthography"]
|
|
268429
269926
|
};
|
|
268430
|
-
} catch {
|
|
269927
|
+
} catch (error) {
|
|
269928
|
+
if (!signal.aborted && !outputLimitExceeded && attempt === 1 && error instanceof APIProviderRateLimitError) {
|
|
269929
|
+
record("rate_limited", attempt);
|
|
269930
|
+
if (error.retryAfterMs === null) await agent.providerRetryWait.defer(providerRetryScope(modelAlias, provider), 1e3);
|
|
269931
|
+
continue;
|
|
269932
|
+
}
|
|
268431
269933
|
return invalid(signal.aborted ? "cancelled" : outputLimitExceeded ? "output_limit" : "generation_failed");
|
|
268432
269934
|
}
|
|
268433
269935
|
}
|
|
@@ -309746,7 +311248,7 @@ const NINE = "9".charCodeAt(0);
|
|
|
309746
311248
|
* @returns An array containing the integer step size and the integer offset of the nth rule.
|
|
309747
311249
|
* @example nthCheck.parse("2n+3"); // returns [2, 3]
|
|
309748
311250
|
*/
|
|
309749
|
-
function parse$
|
|
311251
|
+
function parse$6(formula) {
|
|
309750
311252
|
formula = formula.trim().toLowerCase();
|
|
309751
311253
|
if (formula === "even") return [2, 0];
|
|
309752
311254
|
else if (formula === "odd") return [2, 1];
|
|
@@ -309846,7 +311348,7 @@ function compile$2(parsed) {
|
|
|
309846
311348
|
* check(6); // `true`
|
|
309847
311349
|
*/
|
|
309848
311350
|
function nthCheck(formula) {
|
|
309849
|
-
return compile$2(parse$
|
|
311351
|
+
return compile$2(parse$6(formula));
|
|
309850
311352
|
}
|
|
309851
311353
|
//#endregion
|
|
309852
311354
|
//#region ../../node_modules/.pnpm/css-select@5.2.2/node_modules/css-select/lib/esm/pseudo-selectors/filters.js
|
|
@@ -315506,7 +317008,7 @@ var DOMParser = class DOMParser {
|
|
|
315506
317008
|
};
|
|
315507
317009
|
//#endregion
|
|
315508
317010
|
//#region ../../node_modules/.pnpm/linkedom@0.18.12/node_modules/linkedom/esm/shared/parse-json.js
|
|
315509
|
-
const { parse: parse$
|
|
317011
|
+
const { parse: parse$2 } = JSON;
|
|
315510
317012
|
//#endregion
|
|
315511
317013
|
//#region ../../node_modules/.pnpm/linkedom@0.18.12/node_modules/linkedom/esm/index.js
|
|
315512
317014
|
const parseHTML$1 = (html, globals = null) => new DOMParser().parseFromString(html, "text/html", globals).defaultView;
|
|
@@ -331936,1142 +333438,6 @@ async function assertSuccess(response, operation) {
|
|
|
331936
333438
|
throw new Error(`${operation} failed: HTTP ${String(response.status)}${detail ? `: ${detail}` : ""}`);
|
|
331937
333439
|
}
|
|
331938
333440
|
//#endregion
|
|
331939
|
-
//#region ../../packages/kaos/src/errors.ts
|
|
331940
|
-
/**
|
|
331941
|
-
* Base error class for the kaos package.
|
|
331942
|
-
*/
|
|
331943
|
-
var KaosError = class extends Error {
|
|
331944
|
-
constructor(message) {
|
|
331945
|
-
super(message);
|
|
331946
|
-
this.name = "KaosError";
|
|
331947
|
-
}
|
|
331948
|
-
};
|
|
331949
|
-
/**
|
|
331950
|
-
* Equivalent to Python's FileExistsError — indicates a file or directory already exists.
|
|
331951
|
-
*/
|
|
331952
|
-
var KaosFileExistsError = class extends KaosError {
|
|
331953
|
-
constructor(message) {
|
|
331954
|
-
super(message);
|
|
331955
|
-
this.name = "KaosFileExistsError";
|
|
331956
|
-
}
|
|
331957
|
-
};
|
|
331958
|
-
/**
|
|
331959
|
-
* Thrown by `detectEnvironment` on Windows when no Git Bash install can be
|
|
331960
|
-
* located. Carries the list of paths that were probed so callers can include
|
|
331961
|
-
* them in install hints.
|
|
331962
|
-
*/
|
|
331963
|
-
var KaosShellNotFoundError = class extends KaosError {
|
|
331964
|
-
constructor(message) {
|
|
331965
|
-
super(message);
|
|
331966
|
-
this.name = "KaosShellNotFoundError";
|
|
331967
|
-
}
|
|
331968
|
-
};
|
|
331969
|
-
//#endregion
|
|
331970
|
-
//#region ../../packages/kaos/src/environment.ts
|
|
331971
|
-
/**
|
|
331972
|
-
* Environment — cross-platform probe of OS / shell.
|
|
331973
|
-
*
|
|
331974
|
-
* Detection is a pure function of injected probes (`platform` / `arch` /
|
|
331975
|
-
* `release` / `env` / `isFile` / `execFileText`) so the same suite runs
|
|
331976
|
-
* identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
|
|
331977
|
-
* defaults for production callers.
|
|
331978
|
-
*
|
|
331979
|
-
* On Windows the probe expects Git Bash (the canonical POSIX shell that
|
|
331980
|
-
* ships with Git for Windows). If it cannot be located the function
|
|
331981
|
-
* throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
|
|
331982
|
-
* user-facing install hint. Set `BLUN_SHELL_PATH` to override.
|
|
331983
|
-
*/
|
|
331984
|
-
const GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
|
|
331985
|
-
function resolveOsKind(platform) {
|
|
331986
|
-
switch (platform) {
|
|
331987
|
-
case "darwin": return "macOS";
|
|
331988
|
-
case "linux": return "Linux";
|
|
331989
|
-
case "win32": return "Windows";
|
|
331990
|
-
default: return platform;
|
|
331991
|
-
}
|
|
331992
|
-
}
|
|
331993
|
-
async function detectEnvironment(deps) {
|
|
331994
|
-
const osKind = resolveOsKind(deps.platform);
|
|
331995
|
-
const osArch = deps.arch;
|
|
331996
|
-
const osVersion = deps.release;
|
|
331997
|
-
if (deps.platform === "win32") return {
|
|
331998
|
-
osKind,
|
|
331999
|
-
osArch,
|
|
332000
|
-
osVersion,
|
|
332001
|
-
shellName: "bash",
|
|
332002
|
-
shellPath: await locateWindowsGitBash(deps)
|
|
332003
|
-
};
|
|
332004
|
-
const candidates = [
|
|
332005
|
-
"/bin/bash",
|
|
332006
|
-
"/usr/bin/bash",
|
|
332007
|
-
"/usr/local/bin/bash"
|
|
332008
|
-
];
|
|
332009
|
-
let found;
|
|
332010
|
-
for (const p of candidates) if (await deps.isFile(p)) {
|
|
332011
|
-
found = p;
|
|
332012
|
-
break;
|
|
332013
|
-
}
|
|
332014
|
-
if (found !== void 0) return {
|
|
332015
|
-
osKind,
|
|
332016
|
-
osArch,
|
|
332017
|
-
osVersion,
|
|
332018
|
-
shellName: "bash",
|
|
332019
|
-
shellPath: found
|
|
332020
|
-
};
|
|
332021
|
-
return {
|
|
332022
|
-
osKind,
|
|
332023
|
-
osArch,
|
|
332024
|
-
osVersion,
|
|
332025
|
-
shellName: "sh",
|
|
332026
|
-
shellPath: "/bin/sh"
|
|
332027
|
-
};
|
|
332028
|
-
}
|
|
332029
|
-
async function locateWindowsGitBash(deps) {
|
|
332030
|
-
const checked = [];
|
|
332031
|
-
const override = deps.env["BLUN_SHELL_PATH"]?.trim();
|
|
332032
|
-
if (override !== void 0 && override.length > 0) {
|
|
332033
|
-
checked.push(override);
|
|
332034
|
-
if (await deps.isFile(override)) return override;
|
|
332035
|
-
}
|
|
332036
|
-
const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
|
|
332037
|
-
for (const gitExe of gitExecutables) {
|
|
332038
|
-
const inferred = gitBashCandidatesFromGitExe(gitExe);
|
|
332039
|
-
if (inferred !== void 0) for (const candidate of inferred) {
|
|
332040
|
-
checked.push(candidate);
|
|
332041
|
-
if (await deps.isFile(candidate)) return candidate;
|
|
332042
|
-
}
|
|
332043
|
-
const gitExecPath = await readGitExecPath(deps, gitExe);
|
|
332044
|
-
if (gitExecPath === void 0) continue;
|
|
332045
|
-
for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
|
|
332046
|
-
checked.push(candidate);
|
|
332047
|
-
if (await deps.isFile(candidate)) return candidate;
|
|
332048
|
-
}
|
|
332049
|
-
}
|
|
332050
|
-
const candidates = [
|
|
332051
|
-
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
332052
|
-
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
332053
|
-
"C:\\Program Files (x86)\\Git\\bin\\bash.exe",
|
|
332054
|
-
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
|
|
332055
|
-
];
|
|
332056
|
-
const localAppData = deps.env["LOCALAPPDATA"]?.trim();
|
|
332057
|
-
if (localAppData !== void 0 && localAppData.length > 0) {
|
|
332058
|
-
candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
|
|
332059
|
-
candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
|
|
332060
|
-
}
|
|
332061
|
-
for (const candidate of candidates) {
|
|
332062
|
-
checked.push(candidate);
|
|
332063
|
-
if (await deps.isFile(candidate)) return candidate;
|
|
332064
|
-
}
|
|
332065
|
-
throw new KaosShellNotFoundError(`Git Bash was not found on this Windows host. Install Git for Windows from https://gitforwindows.org/ or set BLUN_SHELL_PATH to a bash.exe. Checked: ${checked.join(", ")}.`);
|
|
332066
|
-
}
|
|
332067
|
-
async function readGitExecPath(deps, gitExe) {
|
|
332068
|
-
if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
|
|
332069
|
-
const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
|
|
332070
|
-
if (stdout === void 0) return void 0;
|
|
332071
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
332072
|
-
const execPath = line.trim();
|
|
332073
|
-
if (execPath.length > 0) return execPath;
|
|
332074
|
-
}
|
|
332075
|
-
}
|
|
332076
|
-
function gitBashCandidatesFromGitExe(gitExe) {
|
|
332077
|
-
const normalizedGitExe = path$11.win32.normalize(normalizeWindowsPath(gitExe));
|
|
332078
|
-
const gitDir = path$11.win32.dirname(normalizedGitExe);
|
|
332079
|
-
const gitDirName = path$11.win32.basename(gitDir).toLowerCase();
|
|
332080
|
-
if (gitDirName !== "cmd" && gitDirName !== "bin") return;
|
|
332081
|
-
return gitBashCandidatesFromGitRoot(path$11.win32.dirname(gitDir));
|
|
332082
|
-
}
|
|
332083
|
-
function gitBashCandidatesFromGitExecPath(execPath) {
|
|
332084
|
-
const normalized = path$11.win32.normalize(normalizeWindowsPath(execPath));
|
|
332085
|
-
const parts = normalized.split("\\");
|
|
332086
|
-
for (let i = parts.length - 1; i >= 0; i -= 1) {
|
|
332087
|
-
const segment = parts[i]?.toLowerCase();
|
|
332088
|
-
if (segment === "mingw32" || segment === "mingw64") {
|
|
332089
|
-
const root = parts.slice(0, i).join("\\");
|
|
332090
|
-
if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
|
|
332091
|
-
}
|
|
332092
|
-
}
|
|
332093
|
-
return gitBashCandidatesFromGitRoot(path$11.win32.join(normalized, "..", ".."));
|
|
332094
|
-
}
|
|
332095
|
-
function gitBashCandidatesFromGitRoot(root) {
|
|
332096
|
-
return [path$11.win32.normalize(path$11.win32.join(root, "bin", "bash.exe")), path$11.win32.normalize(path$11.win32.join(root, "usr", "bin", "bash.exe"))];
|
|
332097
|
-
}
|
|
332098
|
-
function normalizeWindowsPath(path) {
|
|
332099
|
-
return path.replaceAll("/", "\\");
|
|
332100
|
-
}
|
|
332101
|
-
function isAbsoluteWindowsPath(path) {
|
|
332102
|
-
return path$11.win32.isAbsolute(normalizeWindowsPath(path));
|
|
332103
|
-
}
|
|
332104
|
-
function dedupeWindowsPaths(paths) {
|
|
332105
|
-
const deduped = [];
|
|
332106
|
-
const seen = /* @__PURE__ */ new Set();
|
|
332107
|
-
for (const path of paths) {
|
|
332108
|
-
const key = normalizeWindowsPath(path).toLowerCase();
|
|
332109
|
-
if (seen.has(key)) continue;
|
|
332110
|
-
seen.add(key);
|
|
332111
|
-
deduped.push(path);
|
|
332112
|
-
}
|
|
332113
|
-
return deduped;
|
|
332114
|
-
}
|
|
332115
|
-
/**
|
|
332116
|
-
* Production convenience — derive the deps bag from Node's ambient surface.
|
|
332117
|
-
*
|
|
332118
|
-
* The result is memoised: subsequent calls return the original promise.
|
|
332119
|
-
* `Environment` is immutable for the lifetime of the process (it derives
|
|
332120
|
-
* from `process.platform`, `process.arch`, `os.release()`, and one-time
|
|
332121
|
-
* shell-path discovery), so caching is sound. Tests that need to probe
|
|
332122
|
-
* with different inputs should call {@link detectEnvironment} directly
|
|
332123
|
-
* with an injected deps bag.
|
|
332124
|
-
*/
|
|
332125
|
-
let detectedEnvironment;
|
|
332126
|
-
function detectEnvironmentFromNode() {
|
|
332127
|
-
if (detectedEnvironment !== void 0) return detectedEnvironment;
|
|
332128
|
-
const platform = process.platform;
|
|
332129
|
-
const env = process.env;
|
|
332130
|
-
const isFile = async (path) => {
|
|
332131
|
-
try {
|
|
332132
|
-
await access(path, constants.F_OK);
|
|
332133
|
-
return true;
|
|
332134
|
-
} catch {
|
|
332135
|
-
return false;
|
|
332136
|
-
}
|
|
332137
|
-
};
|
|
332138
|
-
detectedEnvironment = detectEnvironment({
|
|
332139
|
-
platform,
|
|
332140
|
-
arch: process.arch,
|
|
332141
|
-
release: nodeOs.release(),
|
|
332142
|
-
env,
|
|
332143
|
-
isFile,
|
|
332144
|
-
execFileText
|
|
332145
|
-
});
|
|
332146
|
-
return detectedEnvironment;
|
|
332147
|
-
}
|
|
332148
|
-
async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
|
|
332149
|
-
if (pathEnv === void 0 || pathEnv.length === 0) return [];
|
|
332150
|
-
const listSep = platform === "win32" ? ";" : ":";
|
|
332151
|
-
const dirSep = platform === "win32" ? "\\" : "/";
|
|
332152
|
-
const paths = [];
|
|
332153
|
-
for (const rawDir of pathEnv.split(listSep)) {
|
|
332154
|
-
const dir = rawDir.trim();
|
|
332155
|
-
if (dir.length === 0) continue;
|
|
332156
|
-
if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
|
|
332157
|
-
const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
|
|
332158
|
-
if (await isFile(candidate)) paths.push(candidate);
|
|
332159
|
-
}
|
|
332160
|
-
return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
|
|
332161
|
-
}
|
|
332162
|
-
async function execFileText(file, args, timeoutMs) {
|
|
332163
|
-
return new Promise((resolve) => {
|
|
332164
|
-
execFile(file, [...args], {
|
|
332165
|
-
encoding: "utf8",
|
|
332166
|
-
timeout: timeoutMs,
|
|
332167
|
-
windowsHide: true
|
|
332168
|
-
}, (error, stdout) => {
|
|
332169
|
-
if (error !== null) {
|
|
332170
|
-
resolve(void 0);
|
|
332171
|
-
return;
|
|
332172
|
-
}
|
|
332173
|
-
resolve(stdout);
|
|
332174
|
-
});
|
|
332175
|
-
});
|
|
332176
|
-
}
|
|
332177
|
-
//#endregion
|
|
332178
|
-
//#region ../../packages/kaos/src/internal.ts
|
|
332179
|
-
function isUtf8Continuation(byte) {
|
|
332180
|
-
return byte >= 128 && byte <= 191;
|
|
332181
|
-
}
|
|
332182
|
-
function decodeUtf8Ignore(data) {
|
|
332183
|
-
let output = "";
|
|
332184
|
-
let i = 0;
|
|
332185
|
-
while (i < data.length) {
|
|
332186
|
-
const b0 = data[i];
|
|
332187
|
-
if (b0 === void 0) break;
|
|
332188
|
-
if (b0 <= 127) {
|
|
332189
|
-
output += String.fromCodePoint(b0);
|
|
332190
|
-
i += 1;
|
|
332191
|
-
continue;
|
|
332192
|
-
}
|
|
332193
|
-
if (b0 >= 194 && b0 <= 223) {
|
|
332194
|
-
const b1 = data[i + 1];
|
|
332195
|
-
if (b1 !== void 0 && isUtf8Continuation(b1)) {
|
|
332196
|
-
output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
|
|
332197
|
-
i += 2;
|
|
332198
|
-
continue;
|
|
332199
|
-
}
|
|
332200
|
-
i += 1;
|
|
332201
|
-
continue;
|
|
332202
|
-
}
|
|
332203
|
-
if (b0 >= 224 && b0 <= 239) {
|
|
332204
|
-
const b1 = data[i + 1];
|
|
332205
|
-
const b2 = data[i + 2];
|
|
332206
|
-
if (b1 !== void 0 && (b0 === 224 && b1 >= 160 && b1 <= 191 || b0 >= 225 && b0 <= 236 && isUtf8Continuation(b1) || b0 === 237 && b1 >= 128 && b1 <= 159 || b0 >= 238 && b0 <= 239 && isUtf8Continuation(b1)) && b2 !== void 0 && isUtf8Continuation(b2)) {
|
|
332207
|
-
output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
|
|
332208
|
-
i += 3;
|
|
332209
|
-
continue;
|
|
332210
|
-
}
|
|
332211
|
-
i += 1;
|
|
332212
|
-
continue;
|
|
332213
|
-
}
|
|
332214
|
-
if (b0 >= 240 && b0 <= 244) {
|
|
332215
|
-
const b1 = data[i + 1];
|
|
332216
|
-
const b2 = data[i + 2];
|
|
332217
|
-
const b3 = data[i + 3];
|
|
332218
|
-
if (b1 !== void 0 && (b0 === 240 && b1 >= 144 && b1 <= 191 || b0 >= 241 && b0 <= 243 && isUtf8Continuation(b1) || b0 === 244 && b1 >= 128 && b1 <= 143) && b2 !== void 0 && b3 !== void 0 && isUtf8Continuation(b2) && isUtf8Continuation(b3)) {
|
|
332219
|
-
output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
|
|
332220
|
-
i += 4;
|
|
332221
|
-
continue;
|
|
332222
|
-
}
|
|
332223
|
-
i += 1;
|
|
332224
|
-
continue;
|
|
332225
|
-
}
|
|
332226
|
-
i += 1;
|
|
332227
|
-
}
|
|
332228
|
-
return output;
|
|
332229
|
-
}
|
|
332230
|
-
function decodeUtf16LeIgnore(data) {
|
|
332231
|
-
let output = "";
|
|
332232
|
-
let i = 0;
|
|
332233
|
-
while (i + 1 < data.length) {
|
|
332234
|
-
const first = data[i];
|
|
332235
|
-
const second = data[i + 1];
|
|
332236
|
-
if (first === void 0 || second === void 0) break;
|
|
332237
|
-
const codeUnit = first | second << 8;
|
|
332238
|
-
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
332239
|
-
const lowFirst = data[i + 2];
|
|
332240
|
-
const lowSecond = data[i + 3];
|
|
332241
|
-
if (lowFirst !== void 0 && lowSecond !== void 0) {
|
|
332242
|
-
const low = lowFirst | lowSecond << 8;
|
|
332243
|
-
if (low >= 56320 && low <= 57343) {
|
|
332244
|
-
const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
|
|
332245
|
-
output += String.fromCodePoint(codePoint);
|
|
332246
|
-
i += 4;
|
|
332247
|
-
continue;
|
|
332248
|
-
}
|
|
332249
|
-
}
|
|
332250
|
-
i += 2;
|
|
332251
|
-
continue;
|
|
332252
|
-
}
|
|
332253
|
-
if (codeUnit >= 56320 && codeUnit <= 57343) {
|
|
332254
|
-
i += 2;
|
|
332255
|
-
continue;
|
|
332256
|
-
}
|
|
332257
|
-
output += String.fromCodePoint(codeUnit);
|
|
332258
|
-
i += 2;
|
|
332259
|
-
}
|
|
332260
|
-
return output;
|
|
332261
|
-
}
|
|
332262
|
-
/**
|
|
332263
|
-
* Decode a Buffer into a string with Python-compatible `errors` handling.
|
|
332264
|
-
*
|
|
332265
|
-
* - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
|
|
332266
|
-
* - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
|
|
332267
|
-
* - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
|
|
332268
|
-
*
|
|
332269
|
-
* Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
|
|
332270
|
-
* support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
|
|
332271
|
-
* byte-to-character mappings so `errors` has no effect.
|
|
332272
|
-
* @internal
|
|
332273
|
-
*/
|
|
332274
|
-
function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
|
|
332275
|
-
let webLabel;
|
|
332276
|
-
switch (encoding) {
|
|
332277
|
-
case "utf-8":
|
|
332278
|
-
case "utf8":
|
|
332279
|
-
webLabel = "utf-8";
|
|
332280
|
-
break;
|
|
332281
|
-
case "utf16le":
|
|
332282
|
-
case "ucs2":
|
|
332283
|
-
case "ucs-2":
|
|
332284
|
-
webLabel = "utf-16le";
|
|
332285
|
-
break;
|
|
332286
|
-
default: webLabel = void 0;
|
|
332287
|
-
}
|
|
332288
|
-
if (webLabel === void 0) return data.toString(encoding);
|
|
332289
|
-
if (errors === "strict") return new TextDecoder(webLabel, {
|
|
332290
|
-
fatal: true,
|
|
332291
|
-
ignoreBOM
|
|
332292
|
-
}).decode(data);
|
|
332293
|
-
if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
|
|
332294
|
-
return new TextDecoder(webLabel, {
|
|
332295
|
-
fatal: false,
|
|
332296
|
-
ignoreBOM
|
|
332297
|
-
}).decode(data);
|
|
332298
|
-
}
|
|
332299
|
-
/**
|
|
332300
|
-
* Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
|
|
332301
|
-
* Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
|
|
332302
|
-
* @internal
|
|
332303
|
-
*/
|
|
332304
|
-
function globPatternToRegex(pattern, caseSensitive) {
|
|
332305
|
-
let regex = "^";
|
|
332306
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
332307
|
-
const ch = pattern[i];
|
|
332308
|
-
if (ch === void 0) break;
|
|
332309
|
-
switch (ch) {
|
|
332310
|
-
case "*":
|
|
332311
|
-
regex += "[^/]*";
|
|
332312
|
-
break;
|
|
332313
|
-
case "?":
|
|
332314
|
-
regex += "[^/]";
|
|
332315
|
-
break;
|
|
332316
|
-
case "[": {
|
|
332317
|
-
const end = pattern.indexOf("]", i + 1);
|
|
332318
|
-
if (end === -1) regex += "\\[";
|
|
332319
|
-
else {
|
|
332320
|
-
let charClass = pattern.slice(i + 1, end);
|
|
332321
|
-
charClass = charClass.replace(/\\/g, "\\\\");
|
|
332322
|
-
if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
|
|
332323
|
-
else if (charClass.startsWith("^")) charClass = "\\" + charClass;
|
|
332324
|
-
regex += "[" + charClass + "]";
|
|
332325
|
-
i = end;
|
|
332326
|
-
}
|
|
332327
|
-
break;
|
|
332328
|
-
}
|
|
332329
|
-
case "\\":
|
|
332330
|
-
if (i + 1 < pattern.length) {
|
|
332331
|
-
const next = pattern.charAt(i + 1);
|
|
332332
|
-
regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
|
|
332333
|
-
i++;
|
|
332334
|
-
} else regex += "\\\\";
|
|
332335
|
-
break;
|
|
332336
|
-
default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
|
|
332337
|
-
}
|
|
332338
|
-
}
|
|
332339
|
-
regex += "$";
|
|
332340
|
-
return new RegExp(regex, caseSensitive ? "" : "i");
|
|
332341
|
-
}
|
|
332342
|
-
/**
|
|
332343
|
-
* A Readable wrapper that preserves source backpressure while still allowing
|
|
332344
|
-
* consumers to read buffered output after the source has ended.
|
|
332345
|
-
* @internal
|
|
332346
|
-
*/
|
|
332347
|
-
var BufferedReadable = class extends Readable {
|
|
332348
|
-
_source;
|
|
332349
|
-
_ended = false;
|
|
332350
|
-
constructor(source) {
|
|
332351
|
-
super({ highWaterMark: 128 * 1024 });
|
|
332352
|
-
this._source = source;
|
|
332353
|
-
this._source.on("data", this._onData);
|
|
332354
|
-
this._source.on("end", this._onEnd);
|
|
332355
|
-
this._source.on("close", this._onClose);
|
|
332356
|
-
this._source.on("error", this._onError);
|
|
332357
|
-
}
|
|
332358
|
-
_read() {
|
|
332359
|
-
if (!this._ended && !this.destroyed) this._source.resume();
|
|
332360
|
-
}
|
|
332361
|
-
_destroy(error, callback) {
|
|
332362
|
-
this._source.off("data", this._onData);
|
|
332363
|
-
this._source.off("end", this._onEnd);
|
|
332364
|
-
this._source.off("close", this._onClose);
|
|
332365
|
-
this._source.off("error", this._onError);
|
|
332366
|
-
this._source.destroy();
|
|
332367
|
-
callback(error);
|
|
332368
|
-
}
|
|
332369
|
-
_onData = (chunk) => {
|
|
332370
|
-
if (!this.push(chunk)) this._source.pause();
|
|
332371
|
-
};
|
|
332372
|
-
_onEnd = () => {
|
|
332373
|
-
this._ended = true;
|
|
332374
|
-
this.push(null);
|
|
332375
|
-
};
|
|
332376
|
-
_onClose = () => {
|
|
332377
|
-
if (!this._ended) {
|
|
332378
|
-
this._ended = true;
|
|
332379
|
-
this.push(null);
|
|
332380
|
-
}
|
|
332381
|
-
};
|
|
332382
|
-
_onError = (error) => {
|
|
332383
|
-
this.destroy(error);
|
|
332384
|
-
};
|
|
332385
|
-
};
|
|
332386
|
-
//#endregion
|
|
332387
|
-
//#region ../../packages/kaos/src/login-shell-path.ts
|
|
332388
|
-
/**
|
|
332389
|
-
* Login-shell PATH probe — enrich `process.env.PATH` with entries from the
|
|
332390
|
-
* user's login shell.
|
|
332391
|
-
*
|
|
332392
|
-
* When blun is launched from a context that skipped the user's shell
|
|
332393
|
-
* profile (GUI launchers, non-login parent shells), `process.env.PATH`
|
|
332394
|
-
* misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
|
|
332395
|
-
* tool can't find tools the user has in their interactive shell (e.g.
|
|
332396
|
-
* `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
|
|
332397
|
-
* extract its PATH, and append the entries the current PATH lacks. Existing
|
|
332398
|
-
* entries keep their order and priority; failures (no resolvable shell,
|
|
332399
|
-
* hung or broken profile) silently leave PATH untouched.
|
|
332400
|
-
*
|
|
332401
|
-
* launchd/daemon launches can leave `$SHELL` unset or blank (see
|
|
332402
|
-
* `defaultShell()` in agent-core's terminalService for the same case), so
|
|
332403
|
-
* the probe falls back to the OS account's login shell from the user
|
|
332404
|
-
* database before giving up.
|
|
332405
|
-
*
|
|
332406
|
-
* Like `detectEnvironment`, the probe is a pure function of injected deps
|
|
332407
|
-
* so the suite runs identically on any host. Windows is skipped: the
|
|
332408
|
-
* problem is specific to POSIX login-shell profiles.
|
|
332409
|
-
*/
|
|
332410
|
-
const LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
|
|
332411
|
-
/**
|
|
332412
|
-
* Run the user's login shell and return its PATH, or `undefined` when the
|
|
332413
|
-
* probe does not apply (Windows, no resolvable shell) or fails (spawn
|
|
332414
|
-
* error, timeout, no PATH in the output).
|
|
332415
|
-
*/
|
|
332416
|
-
async function probeLoginShellPath(deps) {
|
|
332417
|
-
if (deps.platform === "win32") return void 0;
|
|
332418
|
-
const envShell = deps.env["SHELL"]?.trim();
|
|
332419
|
-
const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
|
|
332420
|
-
if (shell === void 0 || shell.length === 0) return void 0;
|
|
332421
|
-
const stdout = await deps.execFileText(shell, [
|
|
332422
|
-
"-l",
|
|
332423
|
-
"-c",
|
|
332424
|
-
"/usr/bin/env"
|
|
332425
|
-
], LOGIN_SHELL_ENV_TIMEOUT_MS);
|
|
332426
|
-
if (stdout === void 0) return void 0;
|
|
332427
|
-
let path;
|
|
332428
|
-
for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
|
|
332429
|
-
if (path === void 0 || path.length === 0) return void 0;
|
|
332430
|
-
return path;
|
|
332431
|
-
}
|
|
332432
|
-
/**
|
|
332433
|
-
* Union of the current PATH and the login-shell PATH: the current PATH
|
|
332434
|
-
* string is kept verbatim — including empty components, which POSIX
|
|
332435
|
-
* command lookup treats as the current directory — and login-shell
|
|
332436
|
-
* entries the current PATH lacks are appended in their own order. When
|
|
332437
|
-
* nothing is missing the current string is returned unchanged. Only
|
|
332438
|
-
* absolute login-shell entries are imported: empty, `.`, and relative
|
|
332439
|
-
* components are all cwd-dependent lookup, and appending one the user
|
|
332440
|
-
* did not already have would widen their search path — LocalKaos runs
|
|
332441
|
-
* commands from arbitrary workspace directories.
|
|
332442
|
-
*/
|
|
332443
|
-
function mergeLoginShellPath(currentPath, loginShellPath) {
|
|
332444
|
-
const current = currentPath ?? "";
|
|
332445
|
-
const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
|
|
332446
|
-
const additions = [];
|
|
332447
|
-
for (const entry of loginShellPath.split(":")) {
|
|
332448
|
-
if (!entry.startsWith("/") || seen.has(entry)) continue;
|
|
332449
|
-
seen.add(entry);
|
|
332450
|
-
additions.push(entry);
|
|
332451
|
-
}
|
|
332452
|
-
if (additions.length === 0) return current;
|
|
332453
|
-
if (currentPath === void 0) return additions.join(":");
|
|
332454
|
-
return `${current}:${additions.join(":")}`;
|
|
332455
|
-
}
|
|
332456
|
-
/** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
|
|
332457
|
-
async function applyLoginShellPath(deps) {
|
|
332458
|
-
const loginShellPath = await probeLoginShellPath(deps);
|
|
332459
|
-
if (loginShellPath === void 0) return;
|
|
332460
|
-
const currentPath = deps.env["PATH"];
|
|
332461
|
-
const merged = mergeLoginShellPath(currentPath, loginShellPath);
|
|
332462
|
-
if (merged === (currentPath ?? "")) return;
|
|
332463
|
-
deps.env["PATH"] = merged;
|
|
332464
|
-
}
|
|
332465
|
-
/**
|
|
332466
|
-
* Production convenience — apply the probe to `process.env` once per
|
|
332467
|
-
* process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
|
|
332468
|
-
* does not change for the lifetime of the process, and repeated
|
|
332469
|
-
* `LocalKaos.create()` calls must not re-spawn the shell.
|
|
332470
|
-
*/
|
|
332471
|
-
/**
|
|
332472
|
-
* Login shell from the OS user database (`/etc/passwd` via getpwuid on
|
|
332473
|
-
* Linux, Directory Services on macOS). `userInfo()` throws when the uid
|
|
332474
|
-
* has no database entry (e.g. containers running an arbitrary uid), and
|
|
332475
|
-
* service accounts may carry `/usr/sbin/nologin` — the latter needs no
|
|
332476
|
-
* special casing here because probing it simply fails and degrades
|
|
332477
|
-
* silently.
|
|
332478
|
-
*/
|
|
332479
|
-
function userShellFromNode() {
|
|
332480
|
-
try {
|
|
332481
|
-
const shell = userInfo().shell;
|
|
332482
|
-
return shell === null || shell.length === 0 ? void 0 : shell;
|
|
332483
|
-
} catch {
|
|
332484
|
-
return;
|
|
332485
|
-
}
|
|
332486
|
-
}
|
|
332487
|
-
let appliedLoginShellPath;
|
|
332488
|
-
function applyLoginShellPathFromNode() {
|
|
332489
|
-
if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
|
|
332490
|
-
appliedLoginShellPath = applyLoginShellPath({
|
|
332491
|
-
platform: process.platform,
|
|
332492
|
-
env: process.env,
|
|
332493
|
-
userShell: userShellFromNode,
|
|
332494
|
-
execFileText
|
|
332495
|
-
});
|
|
332496
|
-
return appliedLoginShellPath;
|
|
332497
|
-
}
|
|
332498
|
-
//#endregion
|
|
332499
|
-
//#region ../../packages/kaos/src/local.ts
|
|
332500
|
-
const isWindows$1 = process.platform === "win32";
|
|
332501
|
-
const READ_CHUNK_SIZE = 64 * 1024;
|
|
332502
|
-
/**
|
|
332503
|
-
* Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
|
|
332504
|
-
* visited set. Returns `null` when `ino` is 0, which Node returns on
|
|
332505
|
-
* filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
|
|
332506
|
-
* mounts). A null key signals "no reliable identity for this dir" so
|
|
332507
|
-
* the caller skips visited tracking for that descent — cycle safety
|
|
332508
|
-
* is weakened on those filesystems, but normal walking works instead
|
|
332509
|
-
* of every directory colliding on the shared key `"<dev>:0"`.
|
|
332510
|
-
*/
|
|
332511
|
-
function cycleKey(s) {
|
|
332512
|
-
if (s.ino === 0) return null;
|
|
332513
|
-
return `${String(s.dev)}:${String(s.ino)}`;
|
|
332514
|
-
}
|
|
332515
|
-
function buildLocalSpawnOptions(_isWindows, cwd, env) {
|
|
332516
|
-
return {
|
|
332517
|
-
cwd,
|
|
332518
|
-
env,
|
|
332519
|
-
stdio: [
|
|
332520
|
-
"pipe",
|
|
332521
|
-
"pipe",
|
|
332522
|
-
"pipe"
|
|
332523
|
-
],
|
|
332524
|
-
detached: true,
|
|
332525
|
-
windowsHide: true
|
|
332526
|
-
};
|
|
332527
|
-
}
|
|
332528
|
-
var LocalProcess = class {
|
|
332529
|
-
stdin;
|
|
332530
|
-
stdout;
|
|
332531
|
-
stderr;
|
|
332532
|
-
pid;
|
|
332533
|
-
_child;
|
|
332534
|
-
_exitCode = null;
|
|
332535
|
-
_exitPromise;
|
|
332536
|
-
_disposed = false;
|
|
332537
|
-
constructor(child) {
|
|
332538
|
-
if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
|
|
332539
|
-
this._child = child;
|
|
332540
|
-
this.stdin = child.stdin;
|
|
332541
|
-
this.stdout = new BufferedReadable(child.stdout);
|
|
332542
|
-
this.stderr = new BufferedReadable(child.stderr);
|
|
332543
|
-
this.pid = child.pid ?? -1;
|
|
332544
|
-
this._exitPromise = new Promise((resolve, reject) => {
|
|
332545
|
-
child.on("exit", (code) => {
|
|
332546
|
-
this._exitCode = code ?? -1;
|
|
332547
|
-
resolve(this._exitCode);
|
|
332548
|
-
});
|
|
332549
|
-
child.on("error", (error) => {
|
|
332550
|
-
reject(error);
|
|
332551
|
-
});
|
|
332552
|
-
});
|
|
332553
|
-
}
|
|
332554
|
-
get exitCode() {
|
|
332555
|
-
return this._exitCode;
|
|
332556
|
-
}
|
|
332557
|
-
async wait() {
|
|
332558
|
-
return this._exitPromise;
|
|
332559
|
-
}
|
|
332560
|
-
kill(signal) {
|
|
332561
|
-
if (this.pid <= 0) return Promise.resolve();
|
|
332562
|
-
if (isWindows$1) {
|
|
332563
|
-
const taskkillArgs = [
|
|
332564
|
-
"/T",
|
|
332565
|
-
"/F",
|
|
332566
|
-
"/PID",
|
|
332567
|
-
String(this.pid)
|
|
332568
|
-
];
|
|
332569
|
-
return new Promise((resolve) => {
|
|
332570
|
-
const killer = spawn("taskkill", taskkillArgs, {
|
|
332571
|
-
stdio: "ignore",
|
|
332572
|
-
windowsHide: true
|
|
332573
|
-
});
|
|
332574
|
-
const done = () => {
|
|
332575
|
-
resolve();
|
|
332576
|
-
};
|
|
332577
|
-
killer.once("error", done);
|
|
332578
|
-
killer.once("close", done);
|
|
332579
|
-
});
|
|
332580
|
-
}
|
|
332581
|
-
try {
|
|
332582
|
-
process.kill(-this.pid, signal ?? "SIGTERM");
|
|
332583
|
-
} catch (error) {
|
|
332584
|
-
const err = error;
|
|
332585
|
-
if (err.code === "ESRCH") return Promise.resolve();
|
|
332586
|
-
if (err.code === "EPERM") {
|
|
332587
|
-
try {
|
|
332588
|
-
this._child.kill(signal ?? "SIGTERM");
|
|
332589
|
-
} catch {}
|
|
332590
|
-
return Promise.resolve();
|
|
332591
|
-
}
|
|
332592
|
-
throw error;
|
|
332593
|
-
}
|
|
332594
|
-
return Promise.resolve();
|
|
332595
|
-
}
|
|
332596
|
-
dispose() {
|
|
332597
|
-
if (this._disposed) return;
|
|
332598
|
-
this._disposed = true;
|
|
332599
|
-
this.stdin.destroy();
|
|
332600
|
-
this.stdout.destroy();
|
|
332601
|
-
this.stderr.destroy();
|
|
332602
|
-
}
|
|
332603
|
-
};
|
|
332604
|
-
/**
|
|
332605
|
-
* A KAOS implementation that directly interacts with the local filesystem.
|
|
332606
|
-
*
|
|
332607
|
-
* Note: LocalKaos maintains its own per-instance working directory (`_cwd`)
|
|
332608
|
-
* rather than mutating `process.cwd()`. This lets multiple LocalKaos instances
|
|
332609
|
-
* coexist with independent cwds (e.g. when switching contexts via
|
|
332610
|
-
* `runWithKaos`) without cross-polluting each other's relative-path resolution.
|
|
332611
|
-
*/
|
|
332612
|
-
var LocalKaos = class LocalKaos {
|
|
332613
|
-
name = "local";
|
|
332614
|
-
osEnv;
|
|
332615
|
-
_cwd;
|
|
332616
|
-
_envLayers;
|
|
332617
|
-
constructor(osEnv, cwd, envLayers = []) {
|
|
332618
|
-
this._cwd = normalize$3(cwd ?? process.cwd());
|
|
332619
|
-
this.osEnv = osEnv;
|
|
332620
|
-
this._envLayers = envLayers;
|
|
332621
|
-
}
|
|
332622
|
-
/**
|
|
332623
|
-
* Construct a fresh `LocalKaos` after probing the host environment.
|
|
332624
|
-
*
|
|
332625
|
-
* Each call returns a new instance with its own `_cwd`; concurrent
|
|
332626
|
-
* callers can therefore operate on independent working directories
|
|
332627
|
-
* without polluting one another.
|
|
332628
|
-
*/
|
|
332629
|
-
static async create() {
|
|
332630
|
-
const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
|
|
332631
|
-
return new LocalKaos(osEnv);
|
|
332632
|
-
}
|
|
332633
|
-
withCwd(cwd) {
|
|
332634
|
-
return new LocalKaos(this.osEnv, cwd, this._envLayers);
|
|
332635
|
-
}
|
|
332636
|
-
withEnv(env) {
|
|
332637
|
-
return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
|
|
332638
|
-
}
|
|
332639
|
-
_resolvePath(path) {
|
|
332640
|
-
if (isAbsolute$1(path)) return normalize$3(path);
|
|
332641
|
-
return join$1(this._cwd, path);
|
|
332642
|
-
}
|
|
332643
|
-
pathClass() {
|
|
332644
|
-
return isWindows$1 ? "win32" : "posix";
|
|
332645
|
-
}
|
|
332646
|
-
normpath(path) {
|
|
332647
|
-
return normalize$3(path);
|
|
332648
|
-
}
|
|
332649
|
-
gethome() {
|
|
332650
|
-
return normalize$3(homedir());
|
|
332651
|
-
}
|
|
332652
|
-
getcwd() {
|
|
332653
|
-
return this._cwd;
|
|
332654
|
-
}
|
|
332655
|
-
/**
|
|
332656
|
-
* Change the working directory of this LocalKaos instance.
|
|
332657
|
-
*
|
|
332658
|
-
* Unlike Python's `os.chdir`, this is instance-scoped and never touches
|
|
332659
|
-
* `process.cwd()`. Child processes spawned via {@link exec} inherit this
|
|
332660
|
-
* instance's `_cwd`; concurrent LocalKaos instances each carry their own
|
|
332661
|
-
* independent cwd. If you need Python-compatible process-global cwd,
|
|
332662
|
-
* call `process.chdir(x)` directly.
|
|
332663
|
-
*/
|
|
332664
|
-
async chdir(path) {
|
|
332665
|
-
const resolved = this._resolvePath(path);
|
|
332666
|
-
if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
332667
|
-
this._cwd = resolved;
|
|
332668
|
-
}
|
|
332669
|
-
async stat(path, options) {
|
|
332670
|
-
const resolved = this._resolvePath(path);
|
|
332671
|
-
const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
|
|
332672
|
-
return {
|
|
332673
|
-
stMode: s.mode,
|
|
332674
|
-
stIno: s.ino,
|
|
332675
|
-
stDev: s.dev,
|
|
332676
|
-
stNlink: s.nlink,
|
|
332677
|
-
stUid: s.uid,
|
|
332678
|
-
stGid: s.gid,
|
|
332679
|
-
stSize: s.size,
|
|
332680
|
-
stAtime: s.atimeMs / 1e3,
|
|
332681
|
-
stMtime: s.mtimeMs / 1e3,
|
|
332682
|
-
stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
|
|
332683
|
-
};
|
|
332684
|
-
}
|
|
332685
|
-
async *iterdir(path) {
|
|
332686
|
-
const resolved = this._resolvePath(path);
|
|
332687
|
-
const entries = await readdir(resolved);
|
|
332688
|
-
for (const entry of entries) yield join$1(resolved, entry);
|
|
332689
|
-
}
|
|
332690
|
-
async *glob(path, pattern, options) {
|
|
332691
|
-
const resolved = this._resolvePath(path);
|
|
332692
|
-
const caseSensitive = options?.caseSensitive ?? true;
|
|
332693
|
-
const patternParts = pattern.split("/");
|
|
332694
|
-
const initVisited = /* @__PURE__ */ new Set();
|
|
332695
|
-
try {
|
|
332696
|
-
const rootKey = cycleKey(await stat(resolved));
|
|
332697
|
-
if (rootKey !== null) initVisited.add(rootKey);
|
|
332698
|
-
} catch {}
|
|
332699
|
-
yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
|
|
332700
|
-
}
|
|
332701
|
-
async *_globWalk(basePath, patternParts, caseSensitive, visited) {
|
|
332702
|
-
if (patternParts.length === 0) return;
|
|
332703
|
-
const [currentPattern, ...remainingParts] = patternParts;
|
|
332704
|
-
if (currentPattern === "**") {
|
|
332705
|
-
if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
|
|
332706
|
-
else yield basePath;
|
|
332707
|
-
let entries;
|
|
332708
|
-
try {
|
|
332709
|
-
entries = await readdir(basePath);
|
|
332710
|
-
} catch {
|
|
332711
|
-
return;
|
|
332712
|
-
}
|
|
332713
|
-
for (const entry of entries) {
|
|
332714
|
-
const fullPath = join$1(basePath, entry);
|
|
332715
|
-
let entryStat;
|
|
332716
|
-
try {
|
|
332717
|
-
entryStat = await stat(fullPath);
|
|
332718
|
-
} catch {
|
|
332719
|
-
continue;
|
|
332720
|
-
}
|
|
332721
|
-
if (entryStat.isDirectory()) {
|
|
332722
|
-
const key = cycleKey(entryStat);
|
|
332723
|
-
if (key !== null && visited.has(key)) continue;
|
|
332724
|
-
yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
|
|
332725
|
-
} else if (remainingParts.length === 0) yield fullPath;
|
|
332726
|
-
}
|
|
332727
|
-
} else {
|
|
332728
|
-
const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
|
|
332729
|
-
let entries;
|
|
332730
|
-
try {
|
|
332731
|
-
entries = await readdir(basePath);
|
|
332732
|
-
} catch {
|
|
332733
|
-
return;
|
|
332734
|
-
}
|
|
332735
|
-
for (const entry of entries) {
|
|
332736
|
-
if (!regex.test(entry)) continue;
|
|
332737
|
-
const fullPath = join$1(basePath, entry);
|
|
332738
|
-
if (remainingParts.length === 0) yield fullPath;
|
|
332739
|
-
else {
|
|
332740
|
-
let entryStat;
|
|
332741
|
-
try {
|
|
332742
|
-
entryStat = await stat(fullPath);
|
|
332743
|
-
} catch {
|
|
332744
|
-
continue;
|
|
332745
|
-
}
|
|
332746
|
-
if (entryStat.isDirectory()) {
|
|
332747
|
-
const key = cycleKey(entryStat);
|
|
332748
|
-
if (key !== null && visited.has(key)) continue;
|
|
332749
|
-
yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
|
|
332750
|
-
}
|
|
332751
|
-
}
|
|
332752
|
-
}
|
|
332753
|
-
}
|
|
332754
|
-
}
|
|
332755
|
-
async readBytes(path, n) {
|
|
332756
|
-
const resolved = this._resolvePath(path);
|
|
332757
|
-
if (n === void 0) return Buffer.from(await readFile(resolved));
|
|
332758
|
-
const fh = await open(resolved, "r");
|
|
332759
|
-
try {
|
|
332760
|
-
const buf = Buffer.alloc(n);
|
|
332761
|
-
const { bytesRead } = await fh.read(buf, 0, n, 0);
|
|
332762
|
-
return buf.subarray(0, bytesRead);
|
|
332763
|
-
} finally {
|
|
332764
|
-
await fh.close();
|
|
332765
|
-
}
|
|
332766
|
-
}
|
|
332767
|
-
async readText(path, options) {
|
|
332768
|
-
const resolved = this._resolvePath(path);
|
|
332769
|
-
const encoding = options?.encoding ?? "utf-8";
|
|
332770
|
-
const errors = options?.errors ?? "strict";
|
|
332771
|
-
return decodeTextWithErrors(await readFile(resolved), encoding, errors);
|
|
332772
|
-
}
|
|
332773
|
-
async *readLines(path, options) {
|
|
332774
|
-
const resolved = this._resolvePath(path);
|
|
332775
|
-
const encoding = options?.encoding ?? "utf-8";
|
|
332776
|
-
const errors = options?.errors ?? "strict";
|
|
332777
|
-
if (!isUtf8Encoding(encoding)) {
|
|
332778
|
-
yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
|
|
332779
|
-
return;
|
|
332780
|
-
}
|
|
332781
|
-
yield* this._readUtf8Lines(resolved, errors);
|
|
332782
|
-
}
|
|
332783
|
-
async scanTextFile(path) {
|
|
332784
|
-
const fh = await open(this._resolvePath(path), "r");
|
|
332785
|
-
try {
|
|
332786
|
-
const buf = Buffer.alloc(READ_CHUNK_SIZE);
|
|
332787
|
-
const flags = {
|
|
332788
|
-
hasCrLf: false,
|
|
332789
|
-
hasLf: false,
|
|
332790
|
-
hasLoneCr: false
|
|
332791
|
-
};
|
|
332792
|
-
const validator = createUtf8Validator();
|
|
332793
|
-
let totalLines = 0;
|
|
332794
|
-
let totalBytes = 0;
|
|
332795
|
-
let endsWithNewline = false;
|
|
332796
|
-
let hasNul = false;
|
|
332797
|
-
let prevWasCr = false;
|
|
332798
|
-
while (true) {
|
|
332799
|
-
const { bytesRead } = await fh.read(buf, 0, buf.length, null);
|
|
332800
|
-
if (bytesRead === 0) break;
|
|
332801
|
-
const chunk = buf.subarray(0, bytesRead);
|
|
332802
|
-
validator.write(chunk);
|
|
332803
|
-
for (let i = 0; i < chunk.length; i += 1) {
|
|
332804
|
-
const byte = chunk[i];
|
|
332805
|
-
if (byte === void 0) continue;
|
|
332806
|
-
if (byte === 0) hasNul = true;
|
|
332807
|
-
if (byte === 10) totalLines += 1;
|
|
332808
|
-
}
|
|
332809
|
-
prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
|
|
332810
|
-
totalBytes += bytesRead;
|
|
332811
|
-
endsWithNewline = chunk[bytesRead - 1] === 10;
|
|
332812
|
-
}
|
|
332813
|
-
if (prevWasCr) flags.hasLoneCr = true;
|
|
332814
|
-
validator.end();
|
|
332815
|
-
if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
|
|
332816
|
-
return {
|
|
332817
|
-
totalLines,
|
|
332818
|
-
endsWithNewline,
|
|
332819
|
-
hasNul,
|
|
332820
|
-
lineEndingFlags: flags
|
|
332821
|
-
};
|
|
332822
|
-
} finally {
|
|
332823
|
-
await fh.close();
|
|
332824
|
-
}
|
|
332825
|
-
}
|
|
332826
|
-
async *readLineRange(path, options) {
|
|
332827
|
-
const resolved = this._resolvePath(path);
|
|
332828
|
-
const errors = options.errors ?? "strict";
|
|
332829
|
-
yield* this._readUtf8Lines(resolved, errors, {
|
|
332830
|
-
startLine: options.startLine,
|
|
332831
|
-
maxLines: options.maxLines
|
|
332832
|
-
});
|
|
332833
|
-
}
|
|
332834
|
-
async *readTailLines(path, options) {
|
|
332835
|
-
if (options.tailCount <= 0) return;
|
|
332836
|
-
const resolved = this._resolvePath(path);
|
|
332837
|
-
const errors = options.errors ?? "strict";
|
|
332838
|
-
const fh = await open(resolved, "r");
|
|
332839
|
-
try {
|
|
332840
|
-
const s = await fh.stat();
|
|
332841
|
-
if (s.size === 0) return;
|
|
332842
|
-
let pos = s.size;
|
|
332843
|
-
let foundLf = 0;
|
|
332844
|
-
let startOffset = 0;
|
|
332845
|
-
let needLf = options.tailCount;
|
|
332846
|
-
let sawTailBlock = false;
|
|
332847
|
-
while (pos > 0 && foundLf < needLf) {
|
|
332848
|
-
const readSize = Math.min(READ_CHUNK_SIZE, pos);
|
|
332849
|
-
pos -= readSize;
|
|
332850
|
-
const buf = Buffer.alloc(readSize);
|
|
332851
|
-
await fh.read(buf, 0, readSize, pos);
|
|
332852
|
-
if (!sawTailBlock) {
|
|
332853
|
-
sawTailBlock = true;
|
|
332854
|
-
needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
|
|
332855
|
-
}
|
|
332856
|
-
for (let i = readSize - 1; i >= 0; i -= 1) {
|
|
332857
|
-
if (buf[i] !== 10) continue;
|
|
332858
|
-
foundLf += 1;
|
|
332859
|
-
if (foundLf === needLf) {
|
|
332860
|
-
startOffset = pos + i + 1;
|
|
332861
|
-
break;
|
|
332862
|
-
}
|
|
332863
|
-
}
|
|
332864
|
-
}
|
|
332865
|
-
if (foundLf < needLf) startOffset = 0;
|
|
332866
|
-
yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
|
|
332867
|
-
} finally {
|
|
332868
|
-
await fh.close();
|
|
332869
|
-
}
|
|
332870
|
-
}
|
|
332871
|
-
async *_readUtf8Lines(resolved, errors, range) {
|
|
332872
|
-
const startLine = range?.startLine ?? 1;
|
|
332873
|
-
const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
|
|
332874
|
-
const fh = await open(resolved, "r");
|
|
332875
|
-
try {
|
|
332876
|
-
const buf = Buffer.alloc(READ_CHUNK_SIZE);
|
|
332877
|
-
let pending = [];
|
|
332878
|
-
let pendingOffset = 0;
|
|
332879
|
-
let fileOffset = 0;
|
|
332880
|
-
let lineNo = 1;
|
|
332881
|
-
let yielded = 0;
|
|
332882
|
-
while (true) {
|
|
332883
|
-
const { bytesRead } = await fh.read(buf, 0, buf.length, null);
|
|
332884
|
-
if (bytesRead === 0) break;
|
|
332885
|
-
const chunk = buf.subarray(0, bytesRead);
|
|
332886
|
-
let lineStart = 0;
|
|
332887
|
-
for (let i = 0; i < chunk.length; i += 1) {
|
|
332888
|
-
if (chunk[i] !== 10) continue;
|
|
332889
|
-
const piece = chunk.subarray(lineStart, i + 1);
|
|
332890
|
-
const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
|
|
332891
|
-
const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
|
|
332892
|
-
if (lineNo >= startLine) {
|
|
332893
|
-
yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
|
|
332894
|
-
yielded += 1;
|
|
332895
|
-
if (yielded >= maxLines) return;
|
|
332896
|
-
}
|
|
332897
|
-
pending = [];
|
|
332898
|
-
lineStart = i + 1;
|
|
332899
|
-
lineNo += 1;
|
|
332900
|
-
}
|
|
332901
|
-
if (lineStart < chunk.length) {
|
|
332902
|
-
const tail = Buffer.from(chunk.subarray(lineStart));
|
|
332903
|
-
if (pending.length === 0) pendingOffset = fileOffset + lineStart;
|
|
332904
|
-
pending.push(tail);
|
|
332905
|
-
}
|
|
332906
|
-
fileOffset += bytesRead;
|
|
332907
|
-
}
|
|
332908
|
-
if (pending.length > 0) {
|
|
332909
|
-
const line = Buffer.concat(pending);
|
|
332910
|
-
if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
|
|
332911
|
-
}
|
|
332912
|
-
} finally {
|
|
332913
|
-
await fh.close();
|
|
332914
|
-
}
|
|
332915
|
-
}
|
|
332916
|
-
async writeBytes(path, data) {
|
|
332917
|
-
await writeFile(this._resolvePath(path), data);
|
|
332918
|
-
return data.length;
|
|
332919
|
-
}
|
|
332920
|
-
async writeText(path, data, options) {
|
|
332921
|
-
const resolved = this._resolvePath(path);
|
|
332922
|
-
const encoding = options?.encoding ?? "utf-8";
|
|
332923
|
-
if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
|
|
332924
|
-
else await writeFile(resolved, data, encoding);
|
|
332925
|
-
return data.length;
|
|
332926
|
-
}
|
|
332927
|
-
async mkdir(path, options) {
|
|
332928
|
-
const resolved = this._resolvePath(path);
|
|
332929
|
-
const parents = options?.parents ?? false;
|
|
332930
|
-
const existOk = options?.existOk ?? false;
|
|
332931
|
-
if (parents) {
|
|
332932
|
-
if (!existOk) try {
|
|
332933
|
-
if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
|
|
332934
|
-
} catch (error) {
|
|
332935
|
-
if (error instanceof KaosFileExistsError) throw error;
|
|
332936
|
-
if (error.code !== "ENOENT") throw error;
|
|
332937
|
-
}
|
|
332938
|
-
await mkdir(resolved, { recursive: true });
|
|
332939
|
-
return;
|
|
332940
|
-
}
|
|
332941
|
-
try {
|
|
332942
|
-
await mkdir(resolved);
|
|
332943
|
-
} catch (error) {
|
|
332944
|
-
if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
332945
|
-
if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
|
|
332946
|
-
return;
|
|
332947
|
-
}
|
|
332948
|
-
throw error;
|
|
332949
|
-
}
|
|
332950
|
-
}
|
|
332951
|
-
async exec(...args) {
|
|
332952
|
-
const command = args[0];
|
|
332953
|
-
if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
|
|
332954
|
-
const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
|
|
332955
|
-
await waitForSpawn(child);
|
|
332956
|
-
return new LocalProcess(child);
|
|
332957
|
-
}
|
|
332958
|
-
async execWithEnv(args, env) {
|
|
332959
|
-
const command = args[0];
|
|
332960
|
-
if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
|
|
332961
|
-
const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
|
|
332962
|
-
await waitForSpawn(child);
|
|
332963
|
-
return new LocalProcess(child);
|
|
332964
|
-
}
|
|
332965
|
-
_buildExecEnv(invocationEnv) {
|
|
332966
|
-
if (this._envLayers.length === 0) return invocationEnv;
|
|
332967
|
-
const merged = {
|
|
332968
|
-
...process.env,
|
|
332969
|
-
...invocationEnv
|
|
332970
|
-
};
|
|
332971
|
-
for (const layer of this._envLayers) Object.assign(merged, layer);
|
|
332972
|
-
return merged;
|
|
332973
|
-
}
|
|
332974
|
-
};
|
|
332975
|
-
function isUtf8Encoding(encoding) {
|
|
332976
|
-
return encoding === "utf-8" || encoding === "utf8";
|
|
332977
|
-
}
|
|
332978
|
-
function* splitLinesKeepingTerminator(text) {
|
|
332979
|
-
if (text.length === 0) return;
|
|
332980
|
-
let start = 0;
|
|
332981
|
-
for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
|
|
332982
|
-
yield text.slice(start, i + 1);
|
|
332983
|
-
start = i + 1;
|
|
332984
|
-
}
|
|
332985
|
-
if (start < text.length) yield text.slice(start);
|
|
332986
|
-
}
|
|
332987
|
-
function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
|
|
332988
|
-
for (let i = 0; i < chunk.length; i += 1) {
|
|
332989
|
-
const byte = chunk[i];
|
|
332990
|
-
if (byte === void 0) continue;
|
|
332991
|
-
if (byte === 13) {
|
|
332992
|
-
if (prevWasCr) flags.hasLoneCr = true;
|
|
332993
|
-
prevWasCr = true;
|
|
332994
|
-
} else if (byte === 10) {
|
|
332995
|
-
if (prevWasCr) flags.hasCrLf = true;
|
|
332996
|
-
else flags.hasLf = true;
|
|
332997
|
-
prevWasCr = false;
|
|
332998
|
-
} else {
|
|
332999
|
-
if (prevWasCr) flags.hasLoneCr = true;
|
|
333000
|
-
prevWasCr = false;
|
|
333001
|
-
}
|
|
333002
|
-
}
|
|
333003
|
-
return prevWasCr;
|
|
333004
|
-
}
|
|
333005
|
-
function createUtf8Validator() {
|
|
333006
|
-
let needed = 0;
|
|
333007
|
-
let lower = 128;
|
|
333008
|
-
let upper = 191;
|
|
333009
|
-
const fail = () => {
|
|
333010
|
-
throw new TypeError("Invalid UTF-8 data");
|
|
333011
|
-
};
|
|
333012
|
-
return {
|
|
333013
|
-
write(chunk) {
|
|
333014
|
-
for (let i = 0; i < chunk.length; i += 1) {
|
|
333015
|
-
const byte = chunk[i];
|
|
333016
|
-
if (byte === void 0) continue;
|
|
333017
|
-
if (needed === 0) {
|
|
333018
|
-
if (byte <= 127) continue;
|
|
333019
|
-
if (byte >= 194 && byte <= 223) needed = 1;
|
|
333020
|
-
else if (byte === 224) {
|
|
333021
|
-
needed = 2;
|
|
333022
|
-
lower = 160;
|
|
333023
|
-
} else if (byte >= 225 && byte <= 236) needed = 2;
|
|
333024
|
-
else if (byte === 237) {
|
|
333025
|
-
needed = 2;
|
|
333026
|
-
upper = 159;
|
|
333027
|
-
} else if (byte >= 238 && byte <= 239) needed = 2;
|
|
333028
|
-
else if (byte === 240) {
|
|
333029
|
-
needed = 3;
|
|
333030
|
-
lower = 144;
|
|
333031
|
-
} else if (byte >= 241 && byte <= 243) needed = 3;
|
|
333032
|
-
else if (byte === 244) {
|
|
333033
|
-
needed = 3;
|
|
333034
|
-
upper = 143;
|
|
333035
|
-
} else fail();
|
|
333036
|
-
} else {
|
|
333037
|
-
if (byte < lower || byte > upper) fail();
|
|
333038
|
-
lower = 128;
|
|
333039
|
-
upper = 191;
|
|
333040
|
-
needed -= 1;
|
|
333041
|
-
}
|
|
333042
|
-
}
|
|
333043
|
-
},
|
|
333044
|
-
end() {
|
|
333045
|
-
if (needed !== 0) fail();
|
|
333046
|
-
}
|
|
333047
|
-
};
|
|
333048
|
-
}
|
|
333049
|
-
async function readRange(fh, start, length) {
|
|
333050
|
-
const data = Buffer.alloc(length);
|
|
333051
|
-
let offset = 0;
|
|
333052
|
-
while (offset < length) {
|
|
333053
|
-
const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
|
|
333054
|
-
if (bytesRead === 0) break;
|
|
333055
|
-
offset += bytesRead;
|
|
333056
|
-
}
|
|
333057
|
-
return offset === length ? data : data.subarray(0, offset);
|
|
333058
|
-
}
|
|
333059
|
-
function waitForSpawn(child) {
|
|
333060
|
-
return new Promise((resolve, reject) => {
|
|
333061
|
-
const onSpawn = () => {
|
|
333062
|
-
child.off("error", onError);
|
|
333063
|
-
resolve();
|
|
333064
|
-
};
|
|
333065
|
-
const onError = (err) => {
|
|
333066
|
-
child.off("spawn", onSpawn);
|
|
333067
|
-
reject(err);
|
|
333068
|
-
};
|
|
333069
|
-
child.once("spawn", onSpawn);
|
|
333070
|
-
child.once("error", onError);
|
|
333071
|
-
});
|
|
333072
|
-
}
|
|
333073
|
-
new AsyncLocalStorage();
|
|
333074
|
-
//#endregion
|
|
333075
333441
|
//#region ../../packages/agent-core/src/agent/replay/build.ts
|
|
333076
333442
|
async function buildReplay(persistence, range) {
|
|
333077
333443
|
const agent = new Agent$1({
|