blun-king-cli 9.1.38 → 9.1.40
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/LIESMICH.txt +12 -1
- package/README.md +11 -1
- package/blun.mjs +373 -21
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -9,7 +9,7 @@ Installation
|
|
|
9
9
|
------------
|
|
10
10
|
Die geprüfte Version exakt global installieren:
|
|
11
11
|
|
|
12
|
-
npm install -g blun-king-cli@9.1.
|
|
12
|
+
npm install -g blun-king-cli@9.1.40
|
|
13
13
|
|
|
14
14
|
Start
|
|
15
15
|
-----
|
|
@@ -20,6 +20,17 @@ Start
|
|
|
20
20
|
Beide Startbefehle verwenden dieselbe Version, dasselbe Konto, dasselbe Modell
|
|
21
21
|
und dieselben Befehle.
|
|
22
22
|
|
|
23
|
+
Zug-Wächter
|
|
24
|
+
-----------
|
|
25
|
+
Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
|
|
26
|
+
Stand auch im verbundenen Telegram-Kanal. Fünf aufeinanderfolgende identische
|
|
27
|
+
fehlgeschlagene Werkzeugaufrufe beenden den Zug mit einer Fehlermeldung.
|
|
28
|
+
|
|
29
|
+
Die Grenzwerte lassen sich vor dem Start über diese Umgebungsvariablen ändern:
|
|
30
|
+
|
|
31
|
+
BLUN_TURN_WATCHDOG_IDLE_MINUTES
|
|
32
|
+
BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS
|
|
33
|
+
|
|
23
34
|
Nachweisbare Arbeitsabläufe
|
|
24
35
|
---------------------------
|
|
25
36
|
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
|
|
|
9
9
|
installiert:
|
|
10
10
|
|
|
11
11
|
```powershell
|
|
12
|
-
npm install -g blun-king-cli@9.1.
|
|
12
|
+
npm install -g blun-king-cli@9.1.40
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Reproduzierbares Staging und Packen
|
|
@@ -35,6 +35,16 @@ an. Version, Konto, Modell und Befehle sind ansonsten identisch. Der Unterschied
|
|
|
35
35
|
gilt nur für den laufenden Prozess; die gespeicherte
|
|
36
36
|
Plugin-Konfiguration wird nicht umgeschrieben.
|
|
37
37
|
|
|
38
|
+
## Zug-Wächter
|
|
39
|
+
|
|
40
|
+
Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
|
|
41
|
+
Stand auch im verbundenen Telegram-Kanal. Fünf aufeinanderfolgende identische
|
|
42
|
+
fehlgeschlagene Werkzeugaufrufe beenden den Zug mit einer Fehlermeldung.
|
|
43
|
+
|
|
44
|
+
Die Grenzwerte lassen sich vor dem Start mit
|
|
45
|
+
`BLUN_TURN_WATCHDOG_IDLE_MINUTES` und
|
|
46
|
+
`BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS` ändern.
|
|
47
|
+
|
|
38
48
|
Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
|
|
39
49
|
eingerichtet. Die Anmeldung erfolgt anschließend in der
|
|
40
50
|
Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
|
package/blun.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// BLUN_BUILD_INPUT_SHA256:
|
|
2
|
+
// BLUN_BUILD_INPUT_SHA256:8bb145d6572cc54a356af3540ba025543ec8c802f66e02a12df23fcd2fcc2bc0
|
|
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);
|
|
@@ -258794,7 +258794,7 @@ var init_read_media = __esmMin((() => {
|
|
|
258794
258794
|
//#region ../../packages/agent-core/src/tools/builtin/file/write.md?raw
|
|
258795
258795
|
var write_default;
|
|
258796
258796
|
var init_write$1 = __esmMin((() => {
|
|
258797
|
-
write_default = "Create, append to, or replace a file entirely.\n\n- Missing parent directories are created automatically (like `mkdir(parents=True, exist_ok=True)`).\n- Mode defaults to overwrite; append adds content at EOF without adding a newline.\n- Write is NOT ALLOWED for incremental changes to existing files, including trivial, one-line, quick, or cosmetic edits. Use Edit instead.\n- Use Write only when the file does not exist, you intend a complete replacement, or the new contents have little continuity with the old contents.\n- Do not create unsolicited documentation files (`*.md` write-ups, `README`s, summaries) just because a task finished — write one only when the user asks for it, or when a task or project instruction requires it (e.g. the plan-mode plan file, created with Write when plan mode directs you to, or a changeset the repo mandates).\n- Read before overwriting an existing file.\n- Write ignores the Read/Edit line-number view. NEVER include line prefixes.\n- Write outputs content literally, including supplied line endings: \\n stays LF, \\r\\n stays CRLF.\n- Source files may contain at most 500 lines. Split larger implementations into focused files; append cannot bypass the limit.\n- Set `single_file_override=true` only when the latest direct user message explicitly requires one single file. The accepted override is reported visibly.\n- For
|
|
258797
|
+
write_default = "Create, append to, or replace a file entirely.\n\n- Missing parent directories are created automatically (like `mkdir(parents=True, exist_ok=True)`).\n- Mode defaults to overwrite; append adds content at EOF without adding a newline.\n- Write is NOT ALLOWED for incremental changes to existing files, including trivial, one-line, quick, or cosmetic edits. Use Edit instead.\n- Use Write only when the file does not exist, you intend a complete replacement, or the new contents have little continuity with the old contents.\n- Do not create unsolicited documentation files (`*.md` write-ups, `README`s, summaries) just because a task finished — write one only when the user asks for it, or when a task or project instruction requires it (e.g. the plan-mode plan file, created with Write when plan mode directs you to, or a changeset the repo mandates).\n- Read before overwriting an existing file.\n- Write ignores the Read/Edit line-number view. NEVER include line prefixes.\n- Write outputs content literally, including supplied line endings: \\n stays LF, \\r\\n stays CRLF.\n- Source files may contain at most 500 lines. Split larger implementations into focused files; append cannot bypass the limit.\n- Set `single_file_override=true` only when the latest direct user message explicitly requires one single file. The accepted override is reported visibly.\n- For any new or completely replaced file with content too large for one call, use `continuation`. Keep each chunk at or below the configured limit. The default is 2,048 UTF-8 bytes, a conservative safety margin for unstable model output rather than a measured hard model limit; operators can set `BLUN_WRITE_CONTINUATION_MAX_BYTES` from 512 through 16,384. End every chunk on a complete line with `\\n`. Set the exact final `expected_lines`; include `expected_sha256` when a source hash is available. Use overwrite and `start_line=1` for the first chunk, then use append for every later chunk and copy the exact `next_start_line` returned by the previous Write result; never continue from a planned boundary. Use consecutive part numbers. The target file remains unchanged until the final line-count and optional SHA-256 checks pass. To discard an unfinished sequence, restart with part 1, overwrite, `start_line=1`, and `reset=true`.\n- Never use continuation to modify an existing file incrementally. Use Edit for incremental changes.\n- Runaway generated JavaScript/TypeScript identifiers are rejected before disk I/O. Regenerate only the affected section as a smaller focused chunk.\n";
|
|
258798
258798
|
}));
|
|
258799
258799
|
//#endregion
|
|
258800
258800
|
//#region ../../packages/agent-core/src/tools/builtin/file/generated-source-health.ts
|
|
@@ -258904,7 +258904,22 @@ var init_generated_source_health = __esmMin((() => {
|
|
|
258904
258904
|
".ts",
|
|
258905
258905
|
".tsx"
|
|
258906
258906
|
]);
|
|
258907
|
-
}))
|
|
258907
|
+
}));
|
|
258908
|
+
//#endregion
|
|
258909
|
+
//#region ../../packages/agent-core/src/tools/builtin/file/write.ts
|
|
258910
|
+
function resolveWriteContinuationChunkBytes(env = process.env) {
|
|
258911
|
+
const raw = env[CONTINUATION_CHUNK_BYTES_ENV]?.trim();
|
|
258912
|
+
if (raw === void 0 || raw === "") return DEFAULT_CONTINUATION_CHUNK_BYTES;
|
|
258913
|
+
const parsed = Number(raw);
|
|
258914
|
+
if (!Number.isSafeInteger(parsed) || parsed < MIN_CONTINUATION_CHUNK_BYTES || parsed > MAX_CONTINUATION_CHUNK_BYTES) throw new Error(`${CONTINUATION_CHUNK_BYTES_ENV} must be an integer between ${String(MIN_CONTINUATION_CHUNK_BYTES)} and ${String(MAX_CONTINUATION_CHUNK_BYTES)} bytes; received ${JSON.stringify(raw)}.`);
|
|
258915
|
+
return parsed;
|
|
258916
|
+
}
|
|
258917
|
+
function countCompletedLines(content) {
|
|
258918
|
+
let count = 0;
|
|
258919
|
+
for (const character of content) if (character === "\n") count += 1;
|
|
258920
|
+
return count;
|
|
258921
|
+
}
|
|
258922
|
+
var S_IFMT, S_IFDIR, DEFAULT_CONTINUATION_CHUNK_BYTES, MIN_CONTINUATION_CHUNK_BYTES, MAX_CONTINUATION_CHUNK_BYTES, CONTINUATION_CHUNK_BYTES_ENV, WriteContinuationSchema, WriteInputSchema, WriteTool;
|
|
258908
258923
|
var init_write = __esmMin((() => {
|
|
258909
258924
|
init_dist$6();
|
|
258910
258925
|
init_zod$1();
|
|
@@ -258918,11 +258933,24 @@ var init_write = __esmMin((() => {
|
|
|
258918
258933
|
init_lsp_diagnostics();
|
|
258919
258934
|
S_IFMT = 61440;
|
|
258920
258935
|
S_IFDIR = 16384;
|
|
258936
|
+
DEFAULT_CONTINUATION_CHUNK_BYTES = 2048;
|
|
258937
|
+
MIN_CONTINUATION_CHUNK_BYTES = 512;
|
|
258938
|
+
MAX_CONTINUATION_CHUNK_BYTES = 16384;
|
|
258939
|
+
CONTINUATION_CHUNK_BYTES_ENV = "BLUN_WRITE_CONTINUATION_MAX_BYTES";
|
|
258940
|
+
WriteContinuationSchema = object({
|
|
258941
|
+
part: number$1().int().positive().describe("One-based chunk number. Start with 1 and increment by exactly one."),
|
|
258942
|
+
start_line: number$1().int().positive().describe("One-based line where this chunk starts. Use 1 for part 1; for every later part, copy next_start_line from the preceding tool result instead of using a planned boundary."),
|
|
258943
|
+
expected_lines: number$1().int().positive().describe("Exact completed-line count expected in the final file."),
|
|
258944
|
+
final: boolean$1().describe("Set true only for the last chunk, when the complete file is present."),
|
|
258945
|
+
expected_sha256: string().regex(/^[0-9a-f]{64}$/i).optional().describe("Optional SHA-256 of the complete expected file for byte-for-byte verification."),
|
|
258946
|
+
reset: boolean$1().optional().describe("Set true only with part 1 and overwrite to discard an unfinished staged write.")
|
|
258947
|
+
});
|
|
258921
258948
|
WriteInputSchema = object({
|
|
258922
258949
|
path: string().describe("Path to the file to create, append to, or completely overwrite. Relative paths resolve against the working directory; a path outside the working directory must be absolute. Missing parent directories are created automatically."),
|
|
258923
258950
|
content: string().describe("Raw full file content to write exactly as provided. This does not use the Read/Edit text view."),
|
|
258924
258951
|
mode: _enum(["overwrite", "append"]).optional().describe("Write mode. Defaults to overwrite. append adds content to the end exactly as provided and does not add a newline."),
|
|
258925
|
-
single_file_override: boolean$1().optional().describe("Set true only when the latest direct user message explicitly requires one single source file. The tool verifies that request and reports the override visibly.")
|
|
258952
|
+
single_file_override: boolean$1().optional().describe("Set true only when the latest direct user message explicitly requires one single source file. The tool verifies that request and reports the override visibly."),
|
|
258953
|
+
continuation: WriteContinuationSchema.optional().describe("Safe long-write protocol. Split generated files into complete-line chunks within the configured limit (2,048 UTF-8 bytes by default; operator override BLUN_WRITE_CONTINUATION_MAX_BYTES). Part 1 uses overwrite and start_line=1; later parts use append and the exact next_start_line returned by the previous call. The target file is changed only after the final line-count and optional SHA-256 checks pass.")
|
|
258926
258954
|
});
|
|
258927
258955
|
object({
|
|
258928
258956
|
/** Number of UTF-8 bytes written to disk by this call. */
|
|
@@ -258932,14 +258960,17 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
258932
258960
|
workspace;
|
|
258933
258961
|
history;
|
|
258934
258962
|
lsp;
|
|
258963
|
+
maxContinuationChunkBytes;
|
|
258935
258964
|
name = "Write";
|
|
258936
258965
|
description = write_default;
|
|
258937
258966
|
parameters = toInputJsonSchema(WriteInputSchema);
|
|
258938
|
-
|
|
258967
|
+
pendingContinuations = /* @__PURE__ */ new Map();
|
|
258968
|
+
constructor(kaos, workspace, history, lsp, maxContinuationChunkBytes = resolveWriteContinuationChunkBytes()) {
|
|
258939
258969
|
this.kaos = kaos;
|
|
258940
258970
|
this.workspace = workspace;
|
|
258941
258971
|
this.history = history;
|
|
258942
258972
|
this.lsp = lsp;
|
|
258973
|
+
this.maxContinuationChunkBytes = maxContinuationChunkBytes;
|
|
258943
258974
|
}
|
|
258944
258975
|
resolveExecution(args) {
|
|
258945
258976
|
const path = resolvePathAccessPath(args.path, {
|
|
@@ -258962,10 +258993,133 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
258962
258993
|
pathClass: this.kaos.pathClass(),
|
|
258963
258994
|
homeDir: this.kaos.gethome()
|
|
258964
258995
|
}),
|
|
258965
|
-
execute: () => this.execution(args, path)
|
|
258996
|
+
execute: (ctx) => this.execution(args, path, ctx)
|
|
258966
258997
|
};
|
|
258967
258998
|
}
|
|
258968
|
-
async execution(args, safePath) {
|
|
258999
|
+
async execution(args, safePath, ctx) {
|
|
259000
|
+
if (args.continuation !== void 0) return this.continueWrite(args, safePath, ctx);
|
|
259001
|
+
if (this.pendingContinuations.has(safePath)) return {
|
|
259002
|
+
isError: true,
|
|
259003
|
+
output: `Refused to write ${args.path}: a long-write continuation is active for this path. Resume it with the next numbered continuation part. No file was written.`
|
|
259004
|
+
};
|
|
259005
|
+
return this.writeContent(args, safePath);
|
|
259006
|
+
}
|
|
259007
|
+
async continueWrite(args, safePath, ctx) {
|
|
259008
|
+
const continuation = args.continuation;
|
|
259009
|
+
if (continuation === void 0) throw new Error("continuation is required");
|
|
259010
|
+
const mode = args.mode ?? "overwrite";
|
|
259011
|
+
const chunkBytes = Buffer.byteLength(args.content, "utf8");
|
|
259012
|
+
if (chunkBytes > this.maxContinuationChunkBytes) return {
|
|
259013
|
+
isError: true,
|
|
259014
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: chunks may contain at most ${this.maxContinuationChunkBytes.toLocaleString("en-US")} UTF-8 bytes (${String(chunkBytes)} received). No content was staged and no file was written.`
|
|
259015
|
+
};
|
|
259016
|
+
if (args.content.length === 0 || !args.content.endsWith("\n")) return {
|
|
259017
|
+
isError: true,
|
|
259018
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: every chunk must end on a complete line with a newline. No content was staged and no file was written.`
|
|
259019
|
+
};
|
|
259020
|
+
if (continuation.reset === true && (continuation.part !== 1 || mode !== "overwrite")) return {
|
|
259021
|
+
isError: true,
|
|
259022
|
+
output: `Refused continuation reset for ${args.path}: reset requires part 1 with mode=overwrite. No content was staged and no file was written.`
|
|
259023
|
+
};
|
|
259024
|
+
if (continuation.reset === true) this.pendingContinuations.delete(safePath);
|
|
259025
|
+
const current = this.pendingContinuations.get(safePath);
|
|
259026
|
+
const expectedPart = current === void 0 ? 1 : current.chunks.length + 1;
|
|
259027
|
+
if (continuation.part !== expectedPart) return {
|
|
259028
|
+
isError: true,
|
|
259029
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: expected part ${String(expectedPart)}. No content was staged and no file was written.`
|
|
259030
|
+
};
|
|
259031
|
+
const expectedStartLine = (current?.completedLines ?? 0) + 1;
|
|
259032
|
+
if (continuation.start_line !== expectedStartLine) return {
|
|
259033
|
+
isError: true,
|
|
259034
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: expected start_line ${String(expectedStartLine)} from the actually staged content, received ${String(continuation.start_line)}. No content was staged and no file was written.`
|
|
259035
|
+
};
|
|
259036
|
+
if (continuation.part === 1 && mode !== "overwrite" || continuation.part > 1 && mode !== "append") return {
|
|
259037
|
+
isError: true,
|
|
259038
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: part 1 must use overwrite and later parts must use append. No content was staged and no file was written.`
|
|
259039
|
+
};
|
|
259040
|
+
if (current !== void 0 && continuation.expected_lines !== current.expectedLines) return {
|
|
259041
|
+
isError: true,
|
|
259042
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: expected_lines changed from ${String(current.expectedLines)} to ${String(continuation.expected_lines)}. No content was staged and no file was written.`
|
|
259043
|
+
};
|
|
259044
|
+
const expectedSha256 = continuation.expected_sha256?.toLowerCase();
|
|
259045
|
+
if (current?.expectedSha256 !== void 0 && expectedSha256 !== void 0 && expectedSha256 !== current.expectedSha256) return {
|
|
259046
|
+
isError: true,
|
|
259047
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: expected_sha256 changed between parts. No content was staged and no file was written.`
|
|
259048
|
+
};
|
|
259049
|
+
const chunkLines = countCompletedLines(args.content);
|
|
259050
|
+
const completedBefore = current?.completedLines ?? 0;
|
|
259051
|
+
const completedLines = completedBefore + chunkLines;
|
|
259052
|
+
if (completedLines > continuation.expected_lines) return {
|
|
259053
|
+
isError: true,
|
|
259054
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: it would stage ${String(completedLines)} lines, exceeding the expected ${String(continuation.expected_lines)}. No content was staged and no file was written.`
|
|
259055
|
+
};
|
|
259056
|
+
if (!continuation.final && completedLines === continuation.expected_lines) return {
|
|
259057
|
+
isError: true,
|
|
259058
|
+
output: `Refused continuation part ${String(continuation.part)} for ${args.path}: all ${String(completedLines)} expected lines are present but final is false. Retry this part with final=true. No content was staged and no file was written.`
|
|
259059
|
+
};
|
|
259060
|
+
if (continuation.final && completedLines !== continuation.expected_lines) return {
|
|
259061
|
+
isError: true,
|
|
259062
|
+
output: `Refused final continuation part ${String(continuation.part)} for ${args.path}: expected ${String(continuation.expected_lines)} lines but would have ${String(completedLines)}. No content was staged and no file was written.`
|
|
259063
|
+
};
|
|
259064
|
+
const startLine = completedBefore + 1;
|
|
259065
|
+
const boundary = `${String(startLine)}-${String(completedLines)}`;
|
|
259066
|
+
const chunks = [...current?.chunks ?? [], args.content];
|
|
259067
|
+
const boundaries = [...current?.boundaries ?? [], boundary];
|
|
259068
|
+
const totalBytes = (current?.bytes ?? 0) + chunkBytes;
|
|
259069
|
+
const stableExpectedSha256 = current?.expectedSha256 ?? expectedSha256;
|
|
259070
|
+
const staged = {
|
|
259071
|
+
expectedLines: continuation.expected_lines,
|
|
259072
|
+
expectedSha256: stableExpectedSha256,
|
|
259073
|
+
chunks,
|
|
259074
|
+
boundaries,
|
|
259075
|
+
completedLines,
|
|
259076
|
+
bytes: totalBytes
|
|
259077
|
+
};
|
|
259078
|
+
const percent = Math.floor(completedLines / continuation.expected_lines * 100);
|
|
259079
|
+
ctx?.onUpdate?.({
|
|
259080
|
+
kind: "progress",
|
|
259081
|
+
text: `Write continuation ${String(continuation.part)}: lines ${boundary}`,
|
|
259082
|
+
percent
|
|
259083
|
+
});
|
|
259084
|
+
if (!continuation.final) {
|
|
259085
|
+
this.pendingContinuations.set(safePath, staged);
|
|
259086
|
+
return { output: `Write continuation part ${String(continuation.part)} staged lines ${boundary} of ${String(continuation.expected_lines)} (${String(percent)}%, ${String(chunkBytes)} bytes). Target unchanged. Continue with mode=append, part=${String(continuation.part + 1)}, next_start_line=${String(completedLines + 1)}.` };
|
|
259087
|
+
}
|
|
259088
|
+
const content = chunks.join("");
|
|
259089
|
+
const actualSha256 = createHash("sha256").update(content).digest("hex");
|
|
259090
|
+
if (stableExpectedSha256 !== void 0 && actualSha256 !== stableExpectedSha256) return {
|
|
259091
|
+
isError: true,
|
|
259092
|
+
output: `Refused final continuation part ${String(continuation.part)} for ${args.path}: SHA-256 mismatch (expected ${stableExpectedSha256}, got ${actualSha256}). No file was written; the first ${String(current?.completedLines ?? 0)} staged lines remain available for retry.`
|
|
259093
|
+
};
|
|
259094
|
+
const result = await this.writeContent({
|
|
259095
|
+
...args,
|
|
259096
|
+
content,
|
|
259097
|
+
mode: "overwrite",
|
|
259098
|
+
continuation: void 0
|
|
259099
|
+
}, safePath, true);
|
|
259100
|
+
if (result.isError === true) return result;
|
|
259101
|
+
let persistedSha256;
|
|
259102
|
+
try {
|
|
259103
|
+
const persistedContent = await this.kaos.readText(safePath);
|
|
259104
|
+
persistedSha256 = createHash("sha256").update(persistedContent).digest("hex");
|
|
259105
|
+
} catch (error) {
|
|
259106
|
+
return {
|
|
259107
|
+
isError: true,
|
|
259108
|
+
output: `Write continuation wrote ${args.path}, but could not verify the persisted bytes: ${error instanceof Error ? error.message : String(error)}. Inspect the target before continuing.`
|
|
259109
|
+
};
|
|
259110
|
+
}
|
|
259111
|
+
if (persistedSha256 !== actualSha256) return {
|
|
259112
|
+
isError: true,
|
|
259113
|
+
output: `Write continuation integrity failure for ${args.path}: staged SHA-256 ${actualSha256}, persisted SHA-256 ${persistedSha256}. The target may be incomplete; inspect it before continuing.`
|
|
259114
|
+
};
|
|
259115
|
+
this.pendingContinuations.delete(safePath);
|
|
259116
|
+
const writeOutput = typeof result.output === "string" ? result.output : "Write completed.";
|
|
259117
|
+
return {
|
|
259118
|
+
...result,
|
|
259119
|
+
output: `Write continuation complete: ${String(completedLines)} lines, ${String(totalBytes)} bytes, SHA-256 ${actualSha256}. Chunk boundaries: ${boundaries.join(", ")}. ${writeOutput}`
|
|
259120
|
+
};
|
|
259121
|
+
}
|
|
259122
|
+
async writeContent(args, safePath, allowLargeContent = false) {
|
|
258969
259123
|
const mode = args.mode ?? "overwrite";
|
|
258970
259124
|
const degenerateIdentifier = findDegenerateGeneratedIdentifier(safePath, args.content);
|
|
258971
259125
|
if (degenerateIdentifier !== null) return {
|
|
@@ -258991,6 +259145,11 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
258991
259145
|
isError: true,
|
|
258992
259146
|
output: lineLimit.error
|
|
258993
259147
|
};
|
|
259148
|
+
const contentBytes = Buffer.byteLength(args.content, "utf8");
|
|
259149
|
+
if (!allowLargeContent && contentBytes > this.maxContinuationChunkBytes) return {
|
|
259150
|
+
isError: true,
|
|
259151
|
+
output: `Refused to write ${args.path}: the content is ${String(contentBytes)} UTF-8 bytes. Long generated writes must use the continuation protocol with complete-line chunks of at most ${this.maxContinuationChunkBytes.toLocaleString("en-US")} bytes. No file was written.`
|
|
259152
|
+
};
|
|
258994
259153
|
const parentError = await this.ensureParentDirectory(safePath);
|
|
258995
259154
|
if (parentError !== void 0) return {
|
|
258996
259155
|
isError: true,
|
|
@@ -370083,7 +370242,7 @@ var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
370083
370242
|
};
|
|
370084
370243
|
}));
|
|
370085
370244
|
//#endregion
|
|
370086
|
-
//#region
|
|
370245
|
+
//#region ../../../node_modules/node-gyp-build/node-gyp-build.js
|
|
370087
370246
|
var require_node_gyp_build$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
370088
370247
|
var fs$3 = __require("fs");
|
|
370089
370248
|
var path$5 = __require("path");
|
|
@@ -370239,14 +370398,14 @@ var require_node_gyp_build$1 = /* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
370239
370398
|
load.compareTuples = compareTuples;
|
|
370240
370399
|
}));
|
|
370241
370400
|
//#endregion
|
|
370242
|
-
//#region
|
|
370401
|
+
//#region ../../../node_modules/node-gyp-build/index.js
|
|
370243
370402
|
var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
370244
370403
|
const runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
370245
370404
|
if (typeof runtimeRequire.addon === "function") module.exports = runtimeRequire.addon.bind(runtimeRequire);
|
|
370246
370405
|
else module.exports = require_node_gyp_build$1();
|
|
370247
370406
|
}));
|
|
370248
370407
|
//#endregion
|
|
370249
|
-
//#region
|
|
370408
|
+
//#region ../../../node_modules/bufferutil/fallback.js
|
|
370250
370409
|
var require_fallback$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
370251
370410
|
/**
|
|
370252
370411
|
* Masks a buffer using the given mask.
|
|
@@ -370278,7 +370437,7 @@ var require_fallback$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
370278
370437
|
};
|
|
370279
370438
|
}));
|
|
370280
370439
|
//#endregion
|
|
370281
|
-
//#region
|
|
370440
|
+
//#region ../../../node_modules/bufferutil/index.js
|
|
370282
370441
|
var require_bufferutil = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
370283
370442
|
try {
|
|
370284
370443
|
module.exports = require_node_gyp_build()(__dirname);
|
|
@@ -370771,7 +370930,7 @@ var require_permessage_deflate = /* @__PURE__ */ __commonJSMin(((exports, module
|
|
|
370771
370930
|
}
|
|
370772
370931
|
}));
|
|
370773
370932
|
//#endregion
|
|
370774
|
-
//#region
|
|
370933
|
+
//#region ../../../node_modules/utf-8-validate/fallback.js
|
|
370775
370934
|
var require_fallback = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
370776
370935
|
/**
|
|
370777
370936
|
* Checks if a given buffer contains only correct UTF-8.
|
|
@@ -370801,7 +370960,7 @@ var require_fallback = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
370801
370960
|
module.exports = isValidUTF8;
|
|
370802
370961
|
}));
|
|
370803
370962
|
//#endregion
|
|
370804
|
-
//#region
|
|
370963
|
+
//#region ../../../node_modules/utf-8-validate/index.js
|
|
370805
370964
|
var require_utf_8_validate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
370806
370965
|
try {
|
|
370807
370966
|
module.exports = require_node_gyp_build()(__dirname);
|
|
@@ -419855,6 +420014,15 @@ function isBusy(host) {
|
|
|
419855
420014
|
//#endregion
|
|
419856
420015
|
//#region src/tui/commands/loop.ts
|
|
419857
420016
|
const LOOP_ARGUMENT_HINT = "/loop [interval] [prompt]";
|
|
420017
|
+
const LOOP_CREATE_FILLERS = new Set([
|
|
420018
|
+
"mit",
|
|
420019
|
+
"alle",
|
|
420020
|
+
"jede",
|
|
420021
|
+
"jeden",
|
|
420022
|
+
"every",
|
|
420023
|
+
"each"
|
|
420024
|
+
]);
|
|
420025
|
+
const LOOP_INTERVAL_TOKEN = /^\d+(?:m|h|d)$/i;
|
|
419858
420026
|
function parseLoopCommand(rawArgs) {
|
|
419859
420027
|
const args = rawArgs.trim();
|
|
419860
420028
|
if (args.length === 0 || args.toLowerCase() === "status") return { kind: "status" };
|
|
@@ -419865,10 +420033,11 @@ function parseLoopCommand(rawArgs) {
|
|
|
419865
420033
|
if (action === "resume") return { kind: "resume" };
|
|
419866
420034
|
if (action === "stop") return { kind: "stop" };
|
|
419867
420035
|
}
|
|
419868
|
-
|
|
420036
|
+
let startIndex = action === "start" ? 1 : 0;
|
|
420037
|
+
if (LOOP_CREATE_FILLERS.has(tokens[startIndex]?.toLowerCase() ?? "")) startIndex += 1;
|
|
419869
420038
|
const interval = tokens[startIndex];
|
|
419870
420039
|
const prompt = tokens.slice(startIndex + 1).join(" ").trim();
|
|
419871
|
-
if (interval === void 0 || prompt.length === 0) return { kind: "error" };
|
|
420040
|
+
if (interval === void 0 || !LOOP_INTERVAL_TOKEN.test(interval) || prompt.length === 0) return { kind: "error" };
|
|
419872
420041
|
return {
|
|
419873
420042
|
kind: "create",
|
|
419874
420043
|
interval,
|
|
@@ -502227,7 +502396,9 @@ registerUiCatalogFragment({
|
|
|
502227
502396
|
"sessionEvent.mcp.needsAuth": "MCP server \"{name}\" needs OAuth — run /mcp-config login {name}",
|
|
502228
502397
|
"sessionEvent.mcp.disabled": "MCP server \"{name}\" disabled",
|
|
502229
502398
|
"sessionEvent.mcp.connecting": "MCP server \"{name}\" connecting…",
|
|
502230
|
-
"sessionEvent.skill.activated": "Activated skill: {skillName}"
|
|
502399
|
+
"sessionEvent.skill.activated": "Activated skill: {skillName}",
|
|
502400
|
+
"sessionEvent.watchdog.idle": "Turn running for {minutes} min; last tool: {tool}; repeated failures: {repetitions}.",
|
|
502401
|
+
"sessionEvent.watchdog.aborted": "Turn stopped after {repetitions} repeated failed calls to {tool}."
|
|
502231
502402
|
},
|
|
502232
502403
|
de: {
|
|
502233
502404
|
"sessionEvent.error.mcpSync": "Der MCP-Serverstatus konnte nicht synchronisiert werden: {error}",
|
|
@@ -502254,7 +502425,9 @@ registerUiCatalogFragment({
|
|
|
502254
502425
|
"sessionEvent.mcp.needsAuth": "MCP-Server „{name}“ benötigt OAuth — /mcp-config login {name} ausführen",
|
|
502255
502426
|
"sessionEvent.mcp.disabled": "MCP-Server „{name}“ deaktiviert",
|
|
502256
502427
|
"sessionEvent.mcp.connecting": "MCP-Server „{name}“ wird verbunden…",
|
|
502257
|
-
"sessionEvent.skill.activated": "Skill aktiviert: {skillName}"
|
|
502428
|
+
"sessionEvent.skill.activated": "Skill aktiviert: {skillName}",
|
|
502429
|
+
"sessionEvent.watchdog.idle": "Zug läuft seit {minutes} Min.; letztes Werkzeug: {tool}; wiederholte Fehler: {repetitions}.",
|
|
502430
|
+
"sessionEvent.watchdog.aborted": "Zug nach {repetitions} wiederholten fehlgeschlagenen Aufrufen von {tool} beendet."
|
|
502258
502431
|
},
|
|
502259
502432
|
es: {
|
|
502260
502433
|
"sessionEvent.error.mcpSync": "No se pudo sincronizar el estado de los servidores MCP: {error}",
|
|
@@ -502281,7 +502454,9 @@ registerUiCatalogFragment({
|
|
|
502281
502454
|
"sessionEvent.mcp.needsAuth": "El servidor MCP «{name}» requiere OAuth; ejecuta /mcp-config login {name}",
|
|
502282
502455
|
"sessionEvent.mcp.disabled": "Servidor MCP «{name}» desactivado",
|
|
502283
502456
|
"sessionEvent.mcp.connecting": "Conectando el servidor MCP «{name}»…",
|
|
502284
|
-
"sessionEvent.skill.activated": "Skill activado: {skillName}"
|
|
502457
|
+
"sessionEvent.skill.activated": "Skill activado: {skillName}",
|
|
502458
|
+
"sessionEvent.watchdog.idle": "El turno lleva {minutes} min en curso; última herramienta: {tool}; fallos repetidos: {repetitions}.",
|
|
502459
|
+
"sessionEvent.watchdog.aborted": "Turno detenido tras {repetitions} llamadas fallidas repetidas a {tool}."
|
|
502285
502460
|
},
|
|
502286
502461
|
fr: {
|
|
502287
502462
|
"sessionEvent.error.mcpSync": "Impossible de synchroniser l’état des serveurs MCP : {error}",
|
|
@@ -502308,7 +502483,9 @@ registerUiCatalogFragment({
|
|
|
502308
502483
|
"sessionEvent.mcp.needsAuth": "Le serveur MCP « {name} » requiert OAuth — exécutez /mcp-config login {name}",
|
|
502309
502484
|
"sessionEvent.mcp.disabled": "Serveur MCP « {name} » désactivé",
|
|
502310
502485
|
"sessionEvent.mcp.connecting": "Connexion du serveur MCP « {name} »…",
|
|
502311
|
-
"sessionEvent.skill.activated": "Skill activé
|
|
502486
|
+
"sessionEvent.skill.activated": "Skill activé : {skillName}",
|
|
502487
|
+
"sessionEvent.watchdog.idle": "Tour en cours depuis {minutes} min ; dernier outil : {tool} ; échecs répétés : {repetitions}.",
|
|
502488
|
+
"sessionEvent.watchdog.aborted": "Tour arrêté après {repetitions} appels répétés ayant échoué pour l'outil {tool}."
|
|
502312
502489
|
},
|
|
502313
502490
|
sv: {
|
|
502314
502491
|
"sessionEvent.error.mcpSync": "Det gick inte att synkronisera MCP-serverstatus: {error}",
|
|
@@ -502335,7 +502512,9 @@ registerUiCatalogFragment({
|
|
|
502335
502512
|
"sessionEvent.mcp.needsAuth": "MCP-servern ”{name}” behöver OAuth – kör /mcp-config login {name}",
|
|
502336
502513
|
"sessionEvent.mcp.disabled": "MCP-servern ”{name}” är inaktiverad",
|
|
502337
502514
|
"sessionEvent.mcp.connecting": "Ansluter MCP-servern ”{name}”…",
|
|
502338
|
-
"sessionEvent.skill.activated": "Skill aktiverad: {skillName}"
|
|
502515
|
+
"sessionEvent.skill.activated": "Skill aktiverad: {skillName}",
|
|
502516
|
+
"sessionEvent.watchdog.idle": "Körningen har pågått i {minutes} min. Senaste verktyg: {tool}. Upprepade fel: {repetitions}.",
|
|
502517
|
+
"sessionEvent.watchdog.aborted": "Körningen stoppades efter {repetitions} upprepade misslyckade anrop till {tool}."
|
|
502339
502518
|
},
|
|
502340
502519
|
cs: {
|
|
502341
502520
|
"sessionEvent.error.mcpSync": "Selhala synchronizace stavu serveru MCP: {error}",
|
|
@@ -502362,7 +502541,9 @@ registerUiCatalogFragment({
|
|
|
502362
502541
|
"sessionEvent.mcp.needsAuth": "Server MCP \"{name}\" vyžaduje přihlášení přes OAuth — spusťte /mcp-config login {name}",
|
|
502363
502542
|
"sessionEvent.mcp.disabled": "Server MCP \"{name}\" zakázán",
|
|
502364
502543
|
"sessionEvent.mcp.connecting": "Server MCP \"{name}\" se připojuje…",
|
|
502365
|
-
"sessionEvent.skill.activated": "Aktivována dovednost: {skillName}"
|
|
502544
|
+
"sessionEvent.skill.activated": "Aktivována dovednost: {skillName}",
|
|
502545
|
+
"sessionEvent.watchdog.idle": "Kolo běží {minutes} min; poslední nástroj: {tool}; opakovaná selhání: {repetitions}.",
|
|
502546
|
+
"sessionEvent.watchdog.aborted": "Kolo bylo zastaveno po {repetitions} opakovaných neúspěšných voláních nástroje {tool}."
|
|
502366
502547
|
}
|
|
502367
502548
|
});
|
|
502368
502549
|
//#endregion
|
|
@@ -503504,6 +503685,140 @@ function isUserCancelledSubagentError(error) {
|
|
|
503504
503685
|
}
|
|
503505
503686
|
}
|
|
503506
503687
|
//#endregion
|
|
503688
|
+
//#region src/tui/controllers/turn-watchdog.ts
|
|
503689
|
+
const DEFAULT_IDLE_MINUTES = 20;
|
|
503690
|
+
const DEFAULT_MAX_FAILED_REPETITIONS = 5;
|
|
503691
|
+
const MIN_IDLE_MINUTES = 1;
|
|
503692
|
+
const MAX_IDLE_MINUTES = 1440;
|
|
503693
|
+
const MIN_FAILED_REPETITIONS = 2;
|
|
503694
|
+
const MAX_FAILED_REPETITIONS = 100;
|
|
503695
|
+
const TURN_WATCHDOG_IDLE_MINUTES_ENV = "BLUN_TURN_WATCHDOG_IDLE_MINUTES";
|
|
503696
|
+
const TURN_WATCHDOG_MAX_FAILED_REPETITIONS_ENV = "BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS";
|
|
503697
|
+
function resolveTurnWatchdogConfig(env = process.env) {
|
|
503698
|
+
const idleMinutes = parseBoundedInteger(env[TURN_WATCHDOG_IDLE_MINUTES_ENV], DEFAULT_IDLE_MINUTES, MIN_IDLE_MINUTES, MAX_IDLE_MINUTES);
|
|
503699
|
+
const maxFailedRepetitions = parseBoundedInteger(env[TURN_WATCHDOG_MAX_FAILED_REPETITIONS_ENV], DEFAULT_MAX_FAILED_REPETITIONS, MIN_FAILED_REPETITIONS, MAX_FAILED_REPETITIONS);
|
|
503700
|
+
return {
|
|
503701
|
+
idleMs: idleMinutes * 6e4,
|
|
503702
|
+
maxFailedRepetitions
|
|
503703
|
+
};
|
|
503704
|
+
}
|
|
503705
|
+
var TurnWatchdogController = class {
|
|
503706
|
+
config;
|
|
503707
|
+
now;
|
|
503708
|
+
setTimer;
|
|
503709
|
+
clearTimer;
|
|
503710
|
+
onIdle;
|
|
503711
|
+
onAbort;
|
|
503712
|
+
toolCalls = /* @__PURE__ */ new Map();
|
|
503713
|
+
active = false;
|
|
503714
|
+
startedAtMs = 0;
|
|
503715
|
+
lastProgressAtMs = 0;
|
|
503716
|
+
lastToolName = "none";
|
|
503717
|
+
lastFailedKey;
|
|
503718
|
+
failedRepetitions = 0;
|
|
503719
|
+
idleReportedForProgressAtMs;
|
|
503720
|
+
timer;
|
|
503721
|
+
constructor(options) {
|
|
503722
|
+
this.config = options.config ?? resolveTurnWatchdogConfig();
|
|
503723
|
+
this.now = options.now ?? Date.now;
|
|
503724
|
+
this.setTimer = options.setTimer ?? setTimeout;
|
|
503725
|
+
this.clearTimer = options.clearTimer ?? clearTimeout;
|
|
503726
|
+
this.onIdle = options.onIdle;
|
|
503727
|
+
this.onAbort = options.onAbort;
|
|
503728
|
+
}
|
|
503729
|
+
start() {
|
|
503730
|
+
this.stop();
|
|
503731
|
+
const now = this.now();
|
|
503732
|
+
this.active = true;
|
|
503733
|
+
this.startedAtMs = now;
|
|
503734
|
+
this.lastProgressAtMs = now;
|
|
503735
|
+
this.lastToolName = "none";
|
|
503736
|
+
this.lastFailedKey = void 0;
|
|
503737
|
+
this.failedRepetitions = 0;
|
|
503738
|
+
this.idleReportedForProgressAtMs = void 0;
|
|
503739
|
+
this.scheduleIdleCheck();
|
|
503740
|
+
}
|
|
503741
|
+
stop() {
|
|
503742
|
+
this.active = false;
|
|
503743
|
+
this.toolCalls.clear();
|
|
503744
|
+
if (this.timer !== void 0) {
|
|
503745
|
+
this.clearTimer(this.timer);
|
|
503746
|
+
this.timer = void 0;
|
|
503747
|
+
}
|
|
503748
|
+
}
|
|
503749
|
+
recordToolCall(toolCallId, name, args) {
|
|
503750
|
+
if (!this.active) return;
|
|
503751
|
+
this.lastToolName = name;
|
|
503752
|
+
this.toolCalls.set(toolCallId, {
|
|
503753
|
+
name,
|
|
503754
|
+
key: `${name}\0${canonicalJson(args)}`
|
|
503755
|
+
});
|
|
503756
|
+
}
|
|
503757
|
+
recordToolResult(toolCallId, isError) {
|
|
503758
|
+
if (!this.active) return;
|
|
503759
|
+
const toolCall = this.toolCalls.get(toolCallId);
|
|
503760
|
+
this.toolCalls.delete(toolCallId);
|
|
503761
|
+
if (toolCall !== void 0) this.lastToolName = toolCall.name;
|
|
503762
|
+
this.lastProgressAtMs = this.now();
|
|
503763
|
+
this.idleReportedForProgressAtMs = void 0;
|
|
503764
|
+
this.scheduleIdleCheck();
|
|
503765
|
+
if (!isError || toolCall === void 0) {
|
|
503766
|
+
this.lastFailedKey = void 0;
|
|
503767
|
+
this.failedRepetitions = 0;
|
|
503768
|
+
return;
|
|
503769
|
+
}
|
|
503770
|
+
if (toolCall.key === this.lastFailedKey) this.failedRepetitions += 1;
|
|
503771
|
+
else {
|
|
503772
|
+
this.lastFailedKey = toolCall.key;
|
|
503773
|
+
this.failedRepetitions = 1;
|
|
503774
|
+
}
|
|
503775
|
+
if (this.failedRepetitions < this.config.maxFailedRepetitions) return;
|
|
503776
|
+
const snapshot = this.snapshot();
|
|
503777
|
+
this.stop();
|
|
503778
|
+
this.onAbort(snapshot);
|
|
503779
|
+
}
|
|
503780
|
+
scheduleIdleCheck() {
|
|
503781
|
+
if (!this.active) return;
|
|
503782
|
+
if (this.timer !== void 0) this.clearTimer(this.timer);
|
|
503783
|
+
const remaining = Math.max(0, this.config.idleMs - (this.now() - this.lastProgressAtMs));
|
|
503784
|
+
this.timer = this.setTimer(() => {
|
|
503785
|
+
this.timer = void 0;
|
|
503786
|
+
this.checkIdle();
|
|
503787
|
+
}, remaining);
|
|
503788
|
+
}
|
|
503789
|
+
checkIdle() {
|
|
503790
|
+
if (!this.active) return;
|
|
503791
|
+
if (this.now() - this.lastProgressAtMs >= this.config.idleMs && this.idleReportedForProgressAtMs !== this.lastProgressAtMs) {
|
|
503792
|
+
this.idleReportedForProgressAtMs = this.lastProgressAtMs;
|
|
503793
|
+
this.onIdle(this.snapshot());
|
|
503794
|
+
return;
|
|
503795
|
+
}
|
|
503796
|
+
this.scheduleIdleCheck();
|
|
503797
|
+
}
|
|
503798
|
+
snapshot() {
|
|
503799
|
+
return {
|
|
503800
|
+
elapsedMinutes: Math.max(1, Math.floor((this.now() - this.startedAtMs) / 6e4)),
|
|
503801
|
+
lastToolName: this.lastToolName,
|
|
503802
|
+
failedRepetitions: this.failedRepetitions
|
|
503803
|
+
};
|
|
503804
|
+
}
|
|
503805
|
+
};
|
|
503806
|
+
function parseBoundedInteger(raw, fallback, minimum, maximum) {
|
|
503807
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
503808
|
+
const value = Number(raw);
|
|
503809
|
+
return Number.isSafeInteger(value) && value >= minimum && value <= maximum ? value : fallback;
|
|
503810
|
+
}
|
|
503811
|
+
function canonicalJson(value) {
|
|
503812
|
+
const normalized = normalizeJson(value);
|
|
503813
|
+
return JSON.stringify(normalized) ?? "undefined";
|
|
503814
|
+
}
|
|
503815
|
+
function normalizeJson(value) {
|
|
503816
|
+
if (Array.isArray(value)) return value.map(normalizeJson);
|
|
503817
|
+
if (value === null || typeof value !== "object") return value;
|
|
503818
|
+
const record = value;
|
|
503819
|
+
return Object.fromEntries(Object.keys(record).toSorted().map((key) => [key, normalizeJson(record[key])]));
|
|
503820
|
+
}
|
|
503821
|
+
//#endregion
|
|
503507
503822
|
//#region src/tui/controllers/session-event-handler.ts
|
|
503508
503823
|
const MANAGED_TELEGRAM_MCP_SERVER = "plugin-telegram:telegram";
|
|
503509
503824
|
function isManagedTelegramMissingTokenFailure(server) {
|
|
@@ -503512,6 +503827,7 @@ function isManagedTelegramMissingTokenFailure(server) {
|
|
|
503512
503827
|
var SessionEventHandler = class {
|
|
503513
503828
|
host;
|
|
503514
503829
|
subAgentEventHandler;
|
|
503830
|
+
turnWatchdog;
|
|
503515
503831
|
constructor(host) {
|
|
503516
503832
|
this.host = host;
|
|
503517
503833
|
this.subAgentEventHandler = new SubAgentEventHandler(host, {
|
|
@@ -503521,6 +503837,10 @@ var SessionEventHandler = class {
|
|
|
503521
503837
|
this.syncBackgroundTaskBadge();
|
|
503522
503838
|
}
|
|
503523
503839
|
});
|
|
503840
|
+
this.turnWatchdog = new TurnWatchdogController({
|
|
503841
|
+
onIdle: (snapshot) => this.reportWatchdogIdle(snapshot),
|
|
503842
|
+
onAbort: (snapshot) => this.abortWatchdogLoop(snapshot)
|
|
503843
|
+
});
|
|
503524
503844
|
}
|
|
503525
503845
|
backgroundTasks = /* @__PURE__ */ new Map();
|
|
503526
503846
|
backgroundTaskTranscriptedTerminal = /* @__PURE__ */ new Set();
|
|
@@ -503540,6 +503860,7 @@ var SessionEventHandler = class {
|
|
|
503540
503860
|
queuedGoalPromotionTimer;
|
|
503541
503861
|
lastCompactionInstruction;
|
|
503542
503862
|
resetRuntimeState() {
|
|
503863
|
+
this.turnWatchdog.stop();
|
|
503543
503864
|
this.backgroundTasks.clear();
|
|
503544
503865
|
this.backgroundTaskTranscriptedTerminal.clear();
|
|
503545
503866
|
this.subAgentEventHandler.resetRuntimeState();
|
|
@@ -503725,6 +504046,7 @@ var SessionEventHandler = class {
|
|
|
503725
504046
|
this.mcpServerStatusSpinners.clear();
|
|
503726
504047
|
}
|
|
503727
504048
|
handleTurnBegin(event) {
|
|
504049
|
+
this.turnWatchdog.start();
|
|
503728
504050
|
const sessionId = this.host.session?.id;
|
|
503729
504051
|
if (sessionId !== void 0) bindPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
|
|
503730
504052
|
this.currentTurnHasAssistantText = false;
|
|
@@ -503763,6 +504085,7 @@ var SessionEventHandler = class {
|
|
|
503763
504085
|
});
|
|
503764
504086
|
}
|
|
503765
504087
|
handleTurnEnd(event, sendQueued) {
|
|
504088
|
+
this.turnWatchdog.stop();
|
|
503766
504089
|
this.host.restoreQueuedSteerAtTurnEnd(event.turnId);
|
|
503767
504090
|
const sessionId = this.host.session?.id;
|
|
503768
504091
|
if (sessionId !== void 0) finishPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
|
|
@@ -503907,6 +504230,7 @@ var SessionEventHandler = class {
|
|
|
503907
504230
|
});
|
|
503908
504231
|
}
|
|
503909
504232
|
handleToolCall(event) {
|
|
504233
|
+
this.turnWatchdog.recordToolCall(event.toolCallId, event.name, event.args);
|
|
503910
504234
|
const { streamingUI } = this.host;
|
|
503911
504235
|
streamingUI.flushNow();
|
|
503912
504236
|
const { turnId, step } = streamingUI.getTurnContext();
|
|
@@ -503956,6 +504280,7 @@ var SessionEventHandler = class {
|
|
|
503956
504280
|
if (event.update.kind === "stdout" || event.update.kind === "stderr") tc.appendLiveOutput(text);
|
|
503957
504281
|
}
|
|
503958
504282
|
handleToolResult(event) {
|
|
504283
|
+
this.turnWatchdog.recordToolResult(event.toolCallId, event.isError === true);
|
|
503959
504284
|
const { streamingUI } = this.host;
|
|
503960
504285
|
streamingUI.flushNow();
|
|
503961
504286
|
const resultData = {
|
|
@@ -503979,6 +504304,26 @@ var SessionEventHandler = class {
|
|
|
503979
504304
|
}
|
|
503980
504305
|
this.host.patchLivePane({ mode: "waiting" });
|
|
503981
504306
|
}
|
|
504307
|
+
reportWatchdogIdle(snapshot) {
|
|
504308
|
+
const message = uiText("sessionEvent.watchdog.idle", {
|
|
504309
|
+
minutes: snapshot.elapsedMinutes,
|
|
504310
|
+
tool: snapshot.lastToolName,
|
|
504311
|
+
repetitions: snapshot.failedRepetitions
|
|
504312
|
+
});
|
|
504313
|
+
this.host.showStatus(message, "warning");
|
|
504314
|
+
this.host.sendChannelTurnStatus?.(message);
|
|
504315
|
+
}
|
|
504316
|
+
abortWatchdogLoop(snapshot) {
|
|
504317
|
+
const message = uiText("sessionEvent.watchdog.aborted", {
|
|
504318
|
+
tool: snapshot.lastToolName,
|
|
504319
|
+
repetitions: snapshot.failedRepetitions
|
|
504320
|
+
});
|
|
504321
|
+
this.host.showError(message);
|
|
504322
|
+
this.host.sendChannelTurnStatus?.(message);
|
|
504323
|
+
this.host.session?.cancel().catch((error) => {
|
|
504324
|
+
this.host.showError(formatErrorMessage$2(error));
|
|
504325
|
+
});
|
|
504326
|
+
}
|
|
503982
504327
|
handleStatusUpdate(event) {
|
|
503983
504328
|
const shouldRenderSwarmEnded = event.swarmMode === false && this.host.state.appState.swarmMode && this.host.state.appState.swarmModeEntry === "task";
|
|
503984
504329
|
const patch = {};
|
|
@@ -512661,6 +513006,13 @@ var BlunTUI = class {
|
|
|
512661
513006
|
this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
|
|
512662
513007
|
});
|
|
512663
513008
|
}
|
|
513009
|
+
sendChannelTurnStatus(message) {
|
|
513010
|
+
const guard = this.pendingChannelReplyGuard;
|
|
513011
|
+
if (guard === void 0) return;
|
|
513012
|
+
sendReplyFallback(guard.chatId, message, guard.contextOnly).then((sent) => {
|
|
513013
|
+
if (sent && this.pendingChannelReplyGuard === guard) guard.outboxMarker = outboxMarker();
|
|
513014
|
+
});
|
|
513015
|
+
}
|
|
512664
513016
|
runChannelReplyFallback(reason) {
|
|
512665
513017
|
const guard = this.pendingChannelReplyGuard;
|
|
512666
513018
|
if (guard === void 0) return;
|