blun-king-cli 9.1.412 → 9.1.414
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 +16 -0
- package/README.md +16 -0
- package/bin/cognitive-action-checkpoint.cjs +36 -2
- package/bin/telegram-truncated-reply-policy.cjs +37 -0
- package/blun.mjs +61 -7
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -467,6 +467,22 @@ Weiterarbeiten nutzen. Begrüßungen, gewöhnliche Einzelschrittanfragen und vag
|
|
|
467
467
|
Aufgaben erzeugen weiterhin kein Ziel. Der Problemrahmen bleibt beschreibender
|
|
468
468
|
Arbeitsstand und erteilt niemals Berechtigungen.
|
|
469
469
|
|
|
470
|
+
Ab BLUN King 9.1.413 werden Antworten aus Telegram-Zügen nicht mehr als fertig
|
|
471
|
+
versendet, wenn der letzte Modellschritt am Ausgabelimit max_tokens endete. Der
|
|
472
|
+
unvollständige Entwurf bleibt ausschließlich im Sitzungskontext. King erstellt
|
|
473
|
+
daraus automatisch eine kurze, vollständige Antwort und sendet erst diese.
|
|
474
|
+
Bereits über das Reply-Werkzeug zugestellte Antworten werden dabei nicht
|
|
475
|
+
wiederholt. Die Fortsetzung ist auf drei Versuche begrenzt; danach wird niemals
|
|
476
|
+
ein abgeschnittener Text als Ergebnis ausgegeben.
|
|
477
|
+
|
|
478
|
+
Ab BLUN King 9.1.414 trägt jeder neue dauerhafte Aktions-Checkpoint zusätzlich
|
|
479
|
+
den genauen Auslöser für seinen nächsten Schritt. Außerhalb einer Wartephase
|
|
480
|
+
muss dieser Auslöser „sofort“ sein. Eine Wartephase benennt stattdessen ein
|
|
481
|
+
äußeres Ereignis, einen Zeitpunkt, eine Abhängigkeit oder eine ausstehende
|
|
482
|
+
Nutzerentscheidung. Auslöser und Bedingung bleiben bei Fortsetzung und Neustart
|
|
483
|
+
erhalten, ohne Berechtigungen zu erteilen. Bereits gespeicherte ältere
|
|
484
|
+
Checkpoints bleiben lesbar.
|
|
485
|
+
|
|
470
486
|
Der eigentliche Anhang und die vollständige Nutzernachricht bleiben für den Zug
|
|
471
487
|
unverändert verfügbar. Die Begrenzung betrifft ausschließlich die kleine
|
|
472
488
|
lexikalische Vorauswahl von höchstens zwei Werkzeugschemas; ToolSearch und alle
|
package/README.md
CHANGED
|
@@ -473,6 +473,22 @@ Weiterarbeiten nutzen. Begrüßungen, gewöhnliche Einzelschrittanfragen und vag
|
|
|
473
473
|
Aufgaben erzeugen weiterhin kein Ziel. Der Problemrahmen bleibt beschreibender
|
|
474
474
|
Arbeitsstand und erteilt niemals Berechtigungen.
|
|
475
475
|
|
|
476
|
+
Ab BLUN King 9.1.413 werden Antworten aus Telegram-Zügen nicht mehr als fertig
|
|
477
|
+
versendet, wenn der letzte Modellschritt am Ausgabelimit `max_tokens` endete.
|
|
478
|
+
Der unvollständige Entwurf bleibt ausschließlich im Sitzungskontext. King
|
|
479
|
+
erstellt daraus automatisch eine kurze, vollständige Antwort und sendet erst
|
|
480
|
+
diese. Bereits über das Reply-Werkzeug zugestellte Antworten werden dabei nicht
|
|
481
|
+
wiederholt. Die Fortsetzung ist auf drei Versuche begrenzt; danach wird niemals
|
|
482
|
+
ein abgeschnittener Text als Ergebnis ausgegeben.
|
|
483
|
+
|
|
484
|
+
Ab BLUN King 9.1.414 trägt jeder neue dauerhafte Aktions-Checkpoint zusätzlich
|
|
485
|
+
den genauen Auslöser für seinen nächsten Schritt. Außerhalb einer Wartephase
|
|
486
|
+
muss dieser Auslöser „sofort“ sein. Eine Wartephase benennt stattdessen ein
|
|
487
|
+
äußeres Ereignis, einen Zeitpunkt, eine Abhängigkeit oder eine ausstehende
|
|
488
|
+
Nutzerentscheidung. Auslöser und Bedingung bleiben bei Fortsetzung und Neustart
|
|
489
|
+
erhalten, ohne Berechtigungen zu erteilen. Bereits gespeicherte ältere
|
|
490
|
+
Checkpoints bleiben lesbar.
|
|
491
|
+
|
|
476
492
|
Der eigentliche Anhang und die vollständige Nutzernachricht bleiben für den Zug
|
|
477
493
|
unverändert verfügbar. Die Begrenzung betrifft ausschließlich die kleine
|
|
478
494
|
lexikalische Vorauswahl von höchstens zwei Werkzeugschemas; ToolSearch und alle
|
|
@@ -9,14 +9,18 @@ const EVIDENCE_BASES = new Set([
|
|
|
9
9
|
const EPISTEMIC_STATES = new Set([
|
|
10
10
|
'verified', 'credible_unverified', 'hypothesis', 'uncertain_memory', 'stale', 'unknown',
|
|
11
11
|
]);
|
|
12
|
+
const TRIGGER_KINDS = new Set([
|
|
13
|
+
'immediate', 'external_event', 'time', 'dependency', 'user_decision',
|
|
14
|
+
]);
|
|
12
15
|
const MODEL_KEYS = new Set([
|
|
13
|
-
'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'problemFrame', 'updatedAt',
|
|
16
|
+
'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'nextTrigger', 'problemFrame', 'updatedAt',
|
|
14
17
|
]);
|
|
15
18
|
const RUNTIME_KEYS = new Set([...MODEL_KEYS, 'evidenceReceipt']);
|
|
16
19
|
const PROBLEM_FRAME_KEYS = new Set([
|
|
17
20
|
'successCriterion', 'missingKnowledge', 'candidateActions', 'selectedAction',
|
|
18
21
|
'selectionReason', 'supportChoice', 'risk', 'reversibility',
|
|
19
22
|
]);
|
|
23
|
+
const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition']);
|
|
20
24
|
const EVIDENCE_INPUT_KEYS = new Set([
|
|
21
25
|
'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
|
|
22
26
|
]);
|
|
@@ -77,6 +81,27 @@ function normalizeProblemFrame(input) {
|
|
|
77
81
|
});
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
function normalizeNextTrigger(input, phase) {
|
|
85
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
86
|
+
throw new TypeError('nextTrigger must be an object');
|
|
87
|
+
}
|
|
88
|
+
for (const key of Object.keys(input)) {
|
|
89
|
+
if (!NEXT_TRIGGER_KEYS.has(key)) throw new TypeError(`nextTrigger field is unsupported: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
const kind = String(input.kind ?? '').trim();
|
|
92
|
+
if (!TRIGGER_KINDS.has(kind)) throw new TypeError('nextTrigger kind is invalid');
|
|
93
|
+
if (phase === 'wait' && kind === 'immediate') {
|
|
94
|
+
throw new TypeError('wait phase requires a non-immediate nextTrigger');
|
|
95
|
+
}
|
|
96
|
+
if (phase !== 'wait' && kind !== 'immediate') {
|
|
97
|
+
throw new TypeError('non-wait phase requires an immediate nextTrigger');
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
kind,
|
|
101
|
+
condition: bounded(input.condition, 'nextTrigger condition'),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
80
105
|
function normalizedEvidenceBasis(value, allowLegacy = false) {
|
|
81
106
|
const basis = String(value ?? '').trim();
|
|
82
107
|
if (EVIDENCE_BASES.has(basis) || allowLegacy && basis === 'legacy_unknown') return basis;
|
|
@@ -235,6 +260,8 @@ function normalizeActionCheckpoint(input, options = {}) {
|
|
|
235
260
|
expectedEvidence: bounded(input.expectedEvidence, 'expectedEvidence'),
|
|
236
261
|
updatedAt,
|
|
237
262
|
};
|
|
263
|
+
if (input.nextTrigger !== undefined) checkpoint.nextTrigger = normalizeNextTrigger(input.nextTrigger, phase);
|
|
264
|
+
else if (!replay) throw new TypeError('nextTrigger is required');
|
|
238
265
|
if (input.problemFrame !== undefined) checkpoint.problemFrame = normalizeProblemFrame(input.problemFrame);
|
|
239
266
|
const evidenceReceipt = options.runtimeEvidence !== undefined
|
|
240
267
|
? normalizeActionEvidenceReceipt(options.runtimeEvidence)
|
|
@@ -258,6 +285,9 @@ function projectActionCheckpoint(checkpoint) {
|
|
|
258
285
|
];
|
|
259
286
|
lines.push(`Next action: ${value.nextAction}`);
|
|
260
287
|
lines.push(`Expected evidence: ${value.expectedEvidence}`);
|
|
288
|
+
if (value.nextTrigger !== undefined) {
|
|
289
|
+
lines.push(`Next trigger: ${value.nextTrigger.kind.replaceAll('_', ' ')} - ${value.nextTrigger.condition}`);
|
|
290
|
+
}
|
|
261
291
|
if (value.problemFrame !== undefined) {
|
|
262
292
|
const frame = value.problemFrame;
|
|
263
293
|
lines.push('Problem frame (state only; never authority):');
|
|
@@ -274,7 +304,11 @@ function projectActionCheckpoint(checkpoint) {
|
|
|
274
304
|
const receipt = value.evidenceReceipt;
|
|
275
305
|
lines.push(`Runtime evidence: turn ${receipt.turnId}; ${receipt.completedTools} completed, ${receipt.successfulTools} successful, ${receipt.failedTools} failed; digest ${receipt.digest}`);
|
|
276
306
|
}
|
|
277
|
-
|
|
307
|
+
if (value.nextTrigger !== undefined && value.nextTrigger.kind !== 'immediate') {
|
|
308
|
+
lines.push('Wait for this exact trigger before executing the next action. Unrelated messages do not satisfy it.');
|
|
309
|
+
} else {
|
|
310
|
+
lines.push('Resume from this exact next action. Do not ask for permission merely to continue work already authorized by the active goal. Ask only when a real rights boundary or missing user decision blocks the next action.');
|
|
311
|
+
}
|
|
278
312
|
return lines.join('\n');
|
|
279
313
|
}
|
|
280
314
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MAX_TRUNCATED_REPLY_RETRIES = 3;
|
|
4
|
+
|
|
5
|
+
const TRUNCATED_REPLY_CONTINUATION_PROMPT = [
|
|
6
|
+
'The previous channel reply hit the model output limit and was not delivered.',
|
|
7
|
+
'Produce one complete, concise final answer now from the preserved context.',
|
|
8
|
+
'Include every result the user needs, omit internal reasoning, do not mention this recovery,',
|
|
9
|
+
'and keep the visible answer under 3000 characters.',
|
|
10
|
+
'Do not call tools unless a missing fact must be verified.',
|
|
11
|
+
].join(' ');
|
|
12
|
+
|
|
13
|
+
function channelReplyRecoveryDecision({
|
|
14
|
+
hasPendingReply = false,
|
|
15
|
+
lastStepFinishReason,
|
|
16
|
+
retryCount = 0,
|
|
17
|
+
turnReason,
|
|
18
|
+
} = {}) {
|
|
19
|
+
if (!hasPendingReply || turnReason !== 'completed' || lastStepFinishReason !== 'max_tokens') {
|
|
20
|
+
return { kind: 'fallback' };
|
|
21
|
+
}
|
|
22
|
+
const boundedRetryCount = Number.isSafeInteger(retryCount) && retryCount > 0
|
|
23
|
+
? retryCount
|
|
24
|
+
: 0;
|
|
25
|
+
if (boundedRetryCount >= MAX_TRUNCATED_REPLY_RETRIES) return { kind: 'exhausted' };
|
|
26
|
+
return {
|
|
27
|
+
kind: 'retry',
|
|
28
|
+
nextRetryCount: boundedRetryCount + 1,
|
|
29
|
+
prompt: TRUNCATED_REPLY_CONTINUATION_PROMPT,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
MAX_TRUNCATED_REPLY_RETRIES,
|
|
35
|
+
TRUNCATED_REPLY_CONTINUATION_PROMPT,
|
|
36
|
+
channelReplyRecoveryDecision,
|
|
37
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -231135,7 +231135,8 @@ function buildGoalReminder(goal) {
|
|
|
231135
231135
|
if (checkpointProjection !== null) {
|
|
231136
231136
|
lines.push("");
|
|
231137
231137
|
lines.push(checkpointProjection);
|
|
231138
|
-
lines.push("Execute the exact checkpointed next action before exploring alternatives. Update the checkpoint only after new evidence changes the verified state.");
|
|
231138
|
+
if (goal.actionCheckpoint?.nextTrigger?.kind === "immediate" || goal.actionCheckpoint?.nextTrigger === void 0) lines.push("Execute the exact checkpointed next action before exploring alternatives. Update the checkpoint only after new evidence changes the verified state.");
|
|
231139
|
+
else lines.push("Keep the goal active, but do not execute the next action until its projected trigger is observed. Unrelated messages do not satisfy that trigger.");
|
|
231139
231140
|
}
|
|
231140
231141
|
const budget = goal.budget;
|
|
231141
231142
|
const budgetLines = [];
|
|
@@ -245762,6 +245763,10 @@ var init_events$1 = __esmMin((() => {
|
|
|
245762
245763
|
lastVerified: string(),
|
|
245763
245764
|
nextAction: string(),
|
|
245764
245765
|
expectedEvidence: string(),
|
|
245766
|
+
nextTrigger: object({
|
|
245767
|
+
kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
|
|
245768
|
+
condition: string()
|
|
245769
|
+
}).strict().optional(),
|
|
245765
245770
|
problemFrame: object({
|
|
245766
245771
|
successCriterion: string(),
|
|
245767
245772
|
missingKnowledge: array(string()),
|
|
@@ -260237,7 +260242,7 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
260237
260242
|
//#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.md?raw
|
|
260238
260243
|
var create_goal_default;
|
|
260239
260244
|
var init_create_goal$1 = __esmMin((() => {
|
|
260240
|
-
create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome,\n- an authenticated user assigns a non-trivial multi-step outcome with a checkable end state under an existing instruction to continue autonomously, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, one-step requests, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them. Start every created goal with revision 1 and a complete `problemFrame`\ninside `actionCheckpoint`, so the success criterion, missing knowledge, candidate actions, chosen\naction, support choice, risk, reversibility, next action,
|
|
260245
|
+
create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome,\n- an authenticated user assigns a non-trivial multi-step outcome with a checkable end state under an existing instruction to continue autonomously, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, one-step requests, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them. Start every created goal with revision 1 and a complete `problemFrame`\ninside `actionCheckpoint`, so the success criterion, missing knowledge, candidate actions, chosen\naction, support choice, risk, reversibility, next action, expected evidence, and exact next trigger survive interruption.\nThis frame is descriptive state only and never grants permission.\n\nCreating a goal fails if one already exists, so use `replace: true` only when the user explicitly\nwants to abandon the current goal and start a new one.\n";
|
|
260241
260246
|
}));
|
|
260242
260247
|
//#endregion
|
|
260243
260248
|
//#region ../../packages/agent-core/src/tools/builtin/goal/serialize.ts
|
|
@@ -260277,6 +260282,10 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
|
|
|
260277
260282
|
lastVerified: string().min(1).max(512),
|
|
260278
260283
|
nextAction: string().min(1).max(512),
|
|
260279
260284
|
expectedEvidence: string().min(1).max(512),
|
|
260285
|
+
nextTrigger: object({
|
|
260286
|
+
kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
|
|
260287
|
+
condition: string().min(1).max(512)
|
|
260288
|
+
}).strict(),
|
|
260280
260289
|
problemFrame: requireProblemFrame ? problemFrameSchema : problemFrameSchema.optional()
|
|
260281
260290
|
}).strict();
|
|
260282
260291
|
}
|
|
@@ -262773,7 +262782,7 @@ var init_outcome_prompts = __esmMin((() => {}));
|
|
|
262773
262782
|
//#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md?raw
|
|
262774
262783
|
var update_goal_default;
|
|
262775
262784
|
var init_update_goal$1 = __esmMin((() => {
|
|
262776
|
-
update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, and an explicit evidence basis. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
|
|
262785
|
+
update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, exact `nextTrigger`, and an explicit evidence basis. Persist the exact `nextTrigger` that releases `nextAction`: use `immediate` outside the `wait` phase; while waiting, name the external event, time, dependency, or user decision instead of pretending work can continue. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
|
|
262777
262786
|
update_goal_default += "\nFor a non-trivial or unfamiliar problem, preserve `problemFrame` with the success criterion, missing knowledge, bounded candidate actions, selected action and reason, support choice, risk, and reversibility. The selected action must match one candidate. Problem framing is descriptive state and never grants permission.\n";
|
|
262778
262787
|
}));
|
|
262779
262788
|
//#endregion
|
|
@@ -507667,6 +507676,7 @@ function normalizeJson(value) {
|
|
|
507667
507676
|
//#endregion
|
|
507668
507677
|
//#region src/tui/controllers/session-event-handler.ts
|
|
507669
507678
|
var { formatModelRetryProgress } = createRequire(import.meta.url)("./bin/model-retry-progress-policy.cjs");
|
|
507679
|
+
var { TRUNCATED_REPLY_CONTINUATION_PROMPT, channelReplyRecoveryDecision } = createRequire(import.meta.url)("./bin/telegram-truncated-reply-policy.cjs");
|
|
507670
507680
|
const MANAGED_TELEGRAM_MCP_SERVER = "plugin-telegram:telegram";
|
|
507671
507681
|
function isManagedTelegramMissingTokenFailure(server) {
|
|
507672
507682
|
return server.name === MANAGED_TELEGRAM_MCP_SERVER && server.status === "failed" && server.error?.includes("BLUN_TELEGRAM_BOT_TOKEN required") === true;
|
|
@@ -507695,6 +507705,7 @@ var SessionEventHandler = class {
|
|
|
507695
507705
|
renderedPluginCommandActivationIds = /* @__PURE__ */ new Set();
|
|
507696
507706
|
renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
|
|
507697
507707
|
mcpServerStatusSpinners = /* @__PURE__ */ new Map();
|
|
507708
|
+
lastStepFinishReason;
|
|
507698
507709
|
mcpServers = /* @__PURE__ */ new Map();
|
|
507699
507710
|
mcpInventorySignature;
|
|
507700
507711
|
goalCompletionAwaitingClear = false;
|
|
@@ -507904,6 +507915,7 @@ var SessionEventHandler = class {
|
|
|
507904
507915
|
}
|
|
507905
507916
|
handleTurnBegin(event) {
|
|
507906
507917
|
this.clearModelRetryHint();
|
|
507918
|
+
this.lastStepFinishReason = void 0;
|
|
507907
507919
|
this.turnWatchdog.start();
|
|
507908
507920
|
const sessionId = this.host.session?.id;
|
|
507909
507921
|
if (sessionId !== void 0) bindPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
|
|
@@ -507959,8 +507971,9 @@ var SessionEventHandler = class {
|
|
|
507959
507971
|
const todos = this.host.state.todoPanel.getTodos();
|
|
507960
507972
|
if (todos.length > 0 && todos.every((t) => t.status === "done")) this.host.streamingUI.setTodoList([]);
|
|
507961
507973
|
this.host.streamingUI.resetToolUi();
|
|
507962
|
-
this.host.runChannelReplyFallback(event.reason);
|
|
507974
|
+
const continueTruncatedChannelReply = this.host.runChannelReplyFallback(event.reason, this.lastStepFinishReason);
|
|
507963
507975
|
this.host.streamingUI.finalizeTurn(sendQueued);
|
|
507976
|
+
if (continueTruncatedChannelReply) this.host.continueTruncatedChannelReply();
|
|
507964
507977
|
if (shouldShowMetrics) this.appendTurnMetrics(event);
|
|
507965
507978
|
this.renderPendingModelBlockedFallback();
|
|
507966
507979
|
this.currentTurnHasAssistantText = false;
|
|
@@ -507990,6 +508003,7 @@ var SessionEventHandler = class {
|
|
|
507990
508003
|
}
|
|
507991
508004
|
handleStepCompleted(event) {
|
|
507992
508005
|
this.clearModelRetryHint();
|
|
508006
|
+
this.lastStepFinishReason = event.finishReason;
|
|
507993
508007
|
this.host.streamingUI.flushNow();
|
|
507994
508008
|
if (event.usage !== void 0) this.currentTurnTokenCount = (this.currentTurnTokenCount ?? 0) + totalTokenUsage(event.usage);
|
|
507995
508009
|
this.maybeShowDebugTiming(event);
|
|
@@ -517625,10 +517639,11 @@ var BlunTUI = class {
|
|
|
517625
517639
|
if (sent && this.pendingChannelReplyGuard === guard) guard.outboxMarker = outboxMarker();
|
|
517626
517640
|
});
|
|
517627
517641
|
}
|
|
517628
|
-
runChannelReplyFallback(reason) {
|
|
517642
|
+
runChannelReplyFallback(reason, lastStepFinishReason) {
|
|
517629
517643
|
const guards = [...this.pendingChannelReplyGuards];
|
|
517630
517644
|
this.pendingChannelReplyGuards = [];
|
|
517631
|
-
if (guards.length === 0) return;
|
|
517645
|
+
if (guards.length === 0) return false;
|
|
517646
|
+
let continueTruncatedReply = false;
|
|
517632
517647
|
const latestByChat = /* @__PURE__ */ new Map();
|
|
517633
517648
|
for (const guard of guards) latestByChat.set(guard.chatId, guard);
|
|
517634
517649
|
for (const guard of latestByChat.values()) {
|
|
@@ -517639,7 +517654,6 @@ var BlunTUI = class {
|
|
|
517639
517654
|
this.channelMediaDeliveryFailures.delete(deliveryKey);
|
|
517640
517655
|
if (!outboxDeliveredFile(guard.outboxMarker, guard.chatId, filePath)) this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
|
|
517641
517656
|
}
|
|
517642
|
-
if (reason !== "completed") continue;
|
|
517643
517657
|
if (this.state.transcriptEntries.slice(guard.transcriptStart).some((entry) => {
|
|
517644
517658
|
if (entry.kind !== "tool_call") return false;
|
|
517645
517659
|
if (!/(?:^|__|:)(?:reply|edit_message)$/i.test(entry.toolCallData?.name ?? "")) return false;
|
|
@@ -517649,6 +517663,24 @@ var BlunTUI = class {
|
|
|
517649
517663
|
if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
|
|
517650
517664
|
continue;
|
|
517651
517665
|
}
|
|
517666
|
+
const decision = channelReplyRecoveryDecision({
|
|
517667
|
+
hasPendingReply: true,
|
|
517668
|
+
lastStepFinishReason,
|
|
517669
|
+
retryCount: guard.truncationRetryCount,
|
|
517670
|
+
turnReason: reason
|
|
517671
|
+
});
|
|
517672
|
+
if (decision.kind === "retry") {
|
|
517673
|
+
guard.truncationRetryCount = decision.nextRetryCount;
|
|
517674
|
+
guard.transcriptStart = this.state.transcriptEntries.length;
|
|
517675
|
+
this.pendingChannelReplyGuards.push(guard);
|
|
517676
|
+
continueTruncatedReply = true;
|
|
517677
|
+
continue;
|
|
517678
|
+
}
|
|
517679
|
+
if (decision.kind === "exhausted") {
|
|
517680
|
+
this.showError("Telegram reply recovery exhausted after repeated max_tokens responses.");
|
|
517681
|
+
continue;
|
|
517682
|
+
}
|
|
517683
|
+
if (reason !== "completed") continue;
|
|
517652
517684
|
const finalText = this.state.transcriptEntries.slice(guard.transcriptStart).filter((entry) => entry.kind === "assistant" && entry.content.trim().length > 0).map((entry) => entry.content.trim()).at(-1) ?? "";
|
|
517653
517685
|
if (finalText.length === 0) continue;
|
|
517654
517686
|
sendReplyFallback(guard.chatId, finalText, guard.contextOnly).then((sent) => {
|
|
@@ -517657,6 +517689,28 @@ var BlunTUI = class {
|
|
|
517657
517689
|
this.showStatus(uiText("blunTui.telegram.fallbackDelivered"));
|
|
517658
517690
|
});
|
|
517659
517691
|
}
|
|
517692
|
+
return continueTruncatedReply;
|
|
517693
|
+
}
|
|
517694
|
+
continueTruncatedChannelReply() {
|
|
517695
|
+
const session = this.session;
|
|
517696
|
+
if (session === void 0) {
|
|
517697
|
+
this.failTruncatedChannelReplyRecovery(new Error("session unavailable"));
|
|
517698
|
+
return;
|
|
517699
|
+
}
|
|
517700
|
+
this.beginSessionRequest();
|
|
517701
|
+
this.setAppState({
|
|
517702
|
+
model: BLUN_KING_MODEL_ALIAS,
|
|
517703
|
+
modelFallbackAllowed: false
|
|
517704
|
+
});
|
|
517705
|
+
session.promptAccepted(TRUNCATED_REPLY_CONTINUATION_PROMPT).then((result) => {
|
|
517706
|
+
if (!result.accepted) this.failTruncatedChannelReplyRecovery(new Error("continuation prompt rejected"));
|
|
517707
|
+
}).catch((error) => {
|
|
517708
|
+
this.failTruncatedChannelReplyRecovery(error);
|
|
517709
|
+
});
|
|
517710
|
+
}
|
|
517711
|
+
failTruncatedChannelReplyRecovery(error) {
|
|
517712
|
+
this.pendingChannelReplyGuards = [];
|
|
517713
|
+
this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
|
|
517660
517714
|
}
|
|
517661
517715
|
/**
|
|
517662
517716
|
* Attach the Telegram channel to this TUI when a bot token is configured.
|