blun-king-cli 9.1.50 → 9.1.52
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 +34 -6
- package/README.md +34 -6
- package/blun.mjs +433 -71
- package/package.json +1 -1
- package/telegram-plugin/dist/mcp-server.mjs +40 -3
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.52
|
|
13
13
|
|
|
14
14
|
Start
|
|
15
15
|
-----
|
|
@@ -20,6 +20,10 @@ Start
|
|
|
20
20
|
Beide Startbefehle verwenden dieselbe Version, dasselbe Konto, dasselbe Modell
|
|
21
21
|
und dieselben Befehle.
|
|
22
22
|
|
|
23
|
+
Beim Fortsetzen mit --continue oder --session wird der Arbeitsbereich der
|
|
24
|
+
bestehenden Sitzung direkt verwendet. Die Ordnerauswahl bleibt neuen Starts
|
|
25
|
+
vorbehalten und kann eine Fortsetzung daher nicht mehr vorzeitig beenden.
|
|
26
|
+
|
|
23
27
|
Zug-Wächter
|
|
24
28
|
-----------
|
|
25
29
|
Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
|
|
@@ -41,6 +45,25 @@ Ein neuer /loop ersetzt den bisherigen Loop derselben Sitzung. Dabei bleibt
|
|
|
41
45
|
genau ein gespeicherter Loop aktiv. /loop stop, /loop pause und /loop status
|
|
42
46
|
wirken auch dann sofort, wenn der Agent gerade arbeitet.
|
|
43
47
|
|
|
48
|
+
Eine fällige Wiederholung wird als autonomer Aufwecker eingespeist und nicht
|
|
49
|
+
als neue Nutzernachricht behandelt. Die Statuszeile zeigt den nächsten
|
|
50
|
+
Aufweckzeitpunkt laufend an. Beim Fortsetzen derselben Sitzung wird der
|
|
51
|
+
gespeicherte Loop samt Zeitplan wieder geladen; /new beginnt dagegen bewusst
|
|
52
|
+
ohne den Loop der vorherigen Sitzung.
|
|
53
|
+
|
|
54
|
+
Eigenständige Ideenarbeit und Telegram-Befehle
|
|
55
|
+
-----------------------------------------------
|
|
56
|
+
/idea <Ziel> startet einen eigenständigen Arbeitsmodus. Der Agent zeigt zuerst
|
|
57
|
+
einen auftragsspezifischen Aufgabenplan, recherchiert selbstständig, setzt sichere
|
|
58
|
+
lokale Schritte um und fragt gezielt nach, wenn eine Entscheidung oder ein Zugang
|
|
59
|
+
fehlt. Blockierte, auf Freigabe wartende und abgebrochene Schritte bleiben mit
|
|
60
|
+
ihrem tatsächlichen Zustand sichtbar.
|
|
61
|
+
|
|
62
|
+
Im automatisch angebundenen Telegram-Kanal werden ausschließlich /loop, /goal,
|
|
63
|
+
/idea und /befehle als Befehle ausgeführt. Ist der Agent beschäftigt, bleiben
|
|
64
|
+
sie in der Warteschlange. Andere Slash-Eingaben werden aus Sicherheitsgründen
|
|
65
|
+
als normale Chatnachrichten behandelt.
|
|
66
|
+
|
|
44
67
|
Nachweisbare Arbeitsabläufe
|
|
45
68
|
---------------------------
|
|
46
69
|
|
|
@@ -56,11 +79,16 @@ Alle Befehle funktionieren identisch mit `king`.
|
|
|
56
79
|
|
|
57
80
|
Medienerzeugung
|
|
58
81
|
---------------
|
|
59
|
-
GenerateVideo erzeugt Videos aus einer Textbeschreibung
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
GenerateVideo erzeugt Videos aus einer Textbeschreibung, animiert einen
|
|
83
|
+
abgeschlossenen Bildauftrag oder lädt eine lokale PNG- beziehungsweise JPEG-Datei
|
|
84
|
+
hoch und animiert genau dieses Bild. Der Auftrag läuft asynchron und wird
|
|
85
|
+
anschließend mit GetMedia abgerufen.
|
|
86
|
+
|
|
87
|
+
Die Medienleiste erscheint nur bei aktiven Medienaufträgen. Sie zeigt
|
|
88
|
+
Warteschlangenplatz, Produktionsphase und Laufzeit sowie nur tatsächlich vom
|
|
89
|
+
Anbieter gemeldete Prozentwerte, Schritte, Frames, FPS, Audiolänge, Restzeit,
|
|
90
|
+
Auflösung, Wellenform und Vorschauen. Aktualisierte Vorschaubilder werden
|
|
91
|
+
erneuert; Medienpfade und Webadressen sind als Terminalverweise anklickbar.
|
|
64
92
|
|
|
65
93
|
Aktualisieren
|
|
66
94
|
-------------
|
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.52
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Reproduzierbares Staging und Packen
|
|
@@ -35,6 +35,10 @@ 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
|
+
Beim Fortsetzen mit `--continue` oder `--session` wird der Arbeitsbereich der
|
|
39
|
+
bestehenden Sitzung direkt verwendet. Die Ordnerauswahl bleibt neuen Starts
|
|
40
|
+
vorbehalten und kann eine Fortsetzung daher nicht mehr vorzeitig beenden.
|
|
41
|
+
|
|
38
42
|
## Zug-Wächter
|
|
39
43
|
|
|
40
44
|
Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
|
|
@@ -56,6 +60,25 @@ genau ein gespeicherter Loop aktiv; der neue Auftrag, das neue Intervall und
|
|
|
56
60
|
der neue Startzeitpunkt gelten vollständig. `/loop stop`, `/loop pause` und
|
|
57
61
|
`/loop status` wirken auch dann sofort, wenn der Agent gerade arbeitet.
|
|
58
62
|
|
|
63
|
+
Eine fällige Wiederholung wird als autonomer Aufwecker eingespeist und nicht
|
|
64
|
+
als neue Nutzernachricht behandelt. Die Statuszeile zeigt den nächsten
|
|
65
|
+
Aufweckzeitpunkt laufend an. Beim Fortsetzen derselben Sitzung wird der
|
|
66
|
+
gespeicherte Loop samt Zeitplan wieder geladen; `/new` beginnt dagegen bewusst
|
|
67
|
+
ohne den Loop der vorherigen Sitzung.
|
|
68
|
+
|
|
69
|
+
## Eigenständige Ideenarbeit und Telegram-Befehle
|
|
70
|
+
|
|
71
|
+
`/idea <Ziel>` startet einen eigenständigen Arbeitsmodus. Der Agent zeigt zuerst
|
|
72
|
+
einen auftragsspezifischen Aufgabenplan, recherchiert selbstständig, setzt sichere
|
|
73
|
+
lokale Schritte um und fragt gezielt nach, wenn eine Entscheidung oder ein Zugang
|
|
74
|
+
fehlt. Blockierte, auf Freigabe wartende und abgebrochene Schritte bleiben mit
|
|
75
|
+
ihrem tatsächlichen Zustand sichtbar.
|
|
76
|
+
|
|
77
|
+
Im automatisch angebundenen Telegram-Kanal werden ausschließlich `/loop`,
|
|
78
|
+
`/goal`, `/idea` und `/befehle` als Befehle ausgeführt. Ist der Agent
|
|
79
|
+
beschäftigt, bleiben sie in der Warteschlange. Andere Slash-Eingaben werden aus
|
|
80
|
+
Sicherheitsgründen als normale Chatnachrichten behandelt.
|
|
81
|
+
|
|
59
82
|
Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
|
|
60
83
|
eingerichtet. Die Anmeldung erfolgt anschließend in der
|
|
61
84
|
Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
|
|
@@ -77,11 +100,16 @@ Sie stehen unter `blun` und `king` identisch zur Verfügung.
|
|
|
77
100
|
|
|
78
101
|
## Medienerzeugung
|
|
79
102
|
|
|
80
|
-
`GenerateVideo` erzeugt Videos aus einer Textbeschreibung
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
`GenerateVideo` erzeugt Videos aus einer Textbeschreibung, animiert einen
|
|
104
|
+
abgeschlossenen Bildauftrag oder lädt eine lokale PNG- beziehungsweise JPEG-Datei
|
|
105
|
+
hoch und animiert genau dieses Bild. Der Auftrag läuft asynchron und wird
|
|
106
|
+
anschließend mit `GetMedia` abgerufen.
|
|
107
|
+
|
|
108
|
+
Die Medienleiste erscheint nur bei aktiven Medienaufträgen. Sie zeigt
|
|
109
|
+
Warteschlangenplatz, Produktionsphase und Laufzeit sowie nur tatsächlich vom
|
|
110
|
+
Anbieter gemeldete Prozentwerte, Schritte, Frames, FPS, Audiolänge, Restzeit,
|
|
111
|
+
Auflösung, Wellenform und Vorschauen. Aktualisierte Vorschaubilder werden
|
|
112
|
+
erneuert; Medienpfade und Webadressen sind als Terminalverweise anklickbar.
|
|
85
113
|
|
|
86
114
|
## Aktualisieren
|
|
87
115
|
|
package/blun.mjs
CHANGED
|
@@ -30392,7 +30392,7 @@ var init_input_schema = __esmMin((() => {
|
|
|
30392
30392
|
//#region ../../packages/agent-core/src/tools/builtin/state/todo-list.md?raw
|
|
30393
30393
|
var todo_list_default;
|
|
30394
30394
|
var init_todo_list$1 = __esmMin((() => {
|
|
30395
|
-
todo_list_default = "Use this tool to maintain a structured TODO list as you work through a multi-step task. Use it proactively and often when progress tracking helps the current work. This is especially useful in long-running investigations and implementation tasks with several tool calls; in plan mode, write the plan to the plan file rather than tracking it here.\n\n**When to use:**\n- Multi-step tasks that span several tool calls\n- Tracking investigation progress across a large codebase search\n- Planning a sequence of edits before making them\n- After receiving new multi-step instructions, capture the requirements as todos\n- Before starting a tracked task, mark exactly one item as `in_progress`\n- Immediately after finishing a tracked task,
|
|
30395
|
+
todo_list_default = "Use this tool to maintain a structured TODO list as you work through a multi-step task. Use it proactively and often when progress tracking helps the current work. This is especially useful in long-running investigations and implementation tasks with several tool calls; in plan mode, write the plan to the plan file rather than tracking it here.\n\n**When to use:**\n- Multi-step tasks that span several tool calls\n- Tracking investigation progress across a large codebase search\n- Planning a sequence of edits before making them\n- After receiving new multi-step instructions, capture the requirements as todos\n- Before starting a tracked task, mark exactly one item as `in_progress`\n- Immediately after finishing a tracked task, record its truthful terminal state; do not batch completions at the end\n\n**When NOT to use:**\n- Single-shot answers that complete in one or two tool calls\n- Trivial requests where tracking adds no clarity\n- Purely conversational or informational replies\n\n**Avoid churn:**\n- Do not re-call this tool when nothing meaningful has changed since the last call — update the list only after real progress.\n- When unsure of the current state, call query mode first (omit `todos`) to check the list before deciding what to update.\n- If no available tool can move any task forward, tell the user where you are stuck instead of repeatedly re-ordering the same todos.\n\n**How to use:**\n- Call with `todos: [...]` to replace the full list. Statuses: pending / in_progress / done / blocked / waiting_approval / aborted.\n- Call with no `todos` argument to retrieve the current list without changing it.\n- Call with `todos: []` to clear the list.\n- Keep titles short and actionable (e.g. \"Read session-control.ts\", \"Add planMode flag to TurnManager\").\n- Update statuses as you make progress.\n- When work is underway, keep exactly one task `in_progress`.\n- Use `done` only when fully accomplished. Use `blocked` with the reason and the action that would unblock it, `waiting_approval` with the prepared result that awaits approval, and `aborted` with the last reached state.\n- Never mark a task `done` if tests are failing, implementation is partial, unresolved errors remain, or required files/dependencies could not be found.\n";
|
|
30396
30396
|
}));
|
|
30397
30397
|
//#endregion
|
|
30398
30398
|
//#region ../../packages/agent-core/src/tools/builtin/state/todo-list.ts
|
|
@@ -30407,6 +30407,9 @@ function statusMarker(status) {
|
|
|
30407
30407
|
case "pending": return "[pending]";
|
|
30408
30408
|
case "in_progress": return "[in_progress]";
|
|
30409
30409
|
case "done": return "[done]";
|
|
30410
|
+
case "blocked": return "[blocked]";
|
|
30411
|
+
case "waiting_approval": return "[waiting_approval]";
|
|
30412
|
+
case "aborted": return "[aborted]";
|
|
30410
30413
|
default: return status;
|
|
30411
30414
|
}
|
|
30412
30415
|
}
|
|
@@ -30417,13 +30420,16 @@ var init_todo_list = __esmMin((() => {
|
|
|
30417
30420
|
init_todo_list$1();
|
|
30418
30421
|
TODO_LIST_TOOL_NAME = "TodoList";
|
|
30419
30422
|
TODO_STORE_KEY = "todo";
|
|
30420
|
-
TODO_LIST_WRITE_REMINDER = "Keep the task list aligned with the work.
|
|
30423
|
+
TODO_LIST_WRITE_REMINDER = "Keep the task list aligned with the work. Record a truthful terminal status for every finished item, keep one item in_progress while work is active, and clear items that no longer apply.";
|
|
30421
30424
|
TodoItemSchema = object({
|
|
30422
30425
|
title: string().min(1).describe("Short, actionable title for the todo."),
|
|
30423
30426
|
status: _enum([
|
|
30424
30427
|
"pending",
|
|
30425
30428
|
"in_progress",
|
|
30426
|
-
"done"
|
|
30429
|
+
"done",
|
|
30430
|
+
"blocked",
|
|
30431
|
+
"waiting_approval",
|
|
30432
|
+
"aborted"
|
|
30427
30433
|
]).describe("Current status of the todo.")
|
|
30428
30434
|
});
|
|
30429
30435
|
TodoListInputSchema = object({ todos: array(TodoItemSchema).optional().describe("The updated todo list. Omit to read the current todo list without making changes. Pass an empty array to clear the list.") });
|
|
@@ -76042,6 +76048,16 @@ function renderCronFireXml(origin, prompt) {
|
|
|
76042
76048
|
"</cron-fire>"
|
|
76043
76049
|
].join("\n");
|
|
76044
76050
|
}
|
|
76051
|
+
function renderSessionLoopWakeupXml(origin, prompt, interval) {
|
|
76052
|
+
return [
|
|
76053
|
+
`<session-loop-wakeup interval="${stringAttr(interval, "auto")}">`,
|
|
76054
|
+
"This is an autonomous scheduled wake-up, not a new user message.",
|
|
76055
|
+
"Resume responsibility for the recurring assignment below. Inspect the current state, research or act with the available tools, and make concrete progress independently.",
|
|
76056
|
+
"Do not ask the user what to do next unless a real decision, permission, or access blocker prevents progress. If no action is possible, report exactly what you checked and what condition you are waiting for.",
|
|
76057
|
+
"</session-loop-wakeup>",
|
|
76058
|
+
renderCronFireXml(origin, prompt)
|
|
76059
|
+
].join("\n");
|
|
76060
|
+
}
|
|
76045
76061
|
function stringAttr(value, fallback) {
|
|
76046
76062
|
if (typeof value !== "string" || value.length === 0) return fallback;
|
|
76047
76063
|
return value.replaceAll("&", "&").replaceAll("\"", """);
|
|
@@ -77103,7 +77119,7 @@ var init_manager$3 = __esmMin((() => {
|
|
|
77103
77119
|
};
|
|
77104
77120
|
const content = [{
|
|
77105
77121
|
type: "text",
|
|
77106
|
-
text: renderCronFireXml(origin, task.prompt)
|
|
77122
|
+
text: task.owner === "session-loop" ? renderSessionLoopWakeupXml(origin, task.prompt, task.loopInterval ?? "auto") : renderCronFireXml(origin, task.prompt)
|
|
77107
77123
|
}];
|
|
77108
77124
|
this.agent.emitEvent({
|
|
77109
77125
|
type: "cron.fired",
|
|
@@ -310917,31 +310933,36 @@ function mediaPayloadNumberArray(payload, keys) {
|
|
|
310917
310933
|
}
|
|
310918
310934
|
}
|
|
310919
310935
|
function mediaProgressFromPayload(payload) {
|
|
310920
|
-
const
|
|
310936
|
+
const nestedProgress = payload["progress"];
|
|
310937
|
+
const source = typeof nestedProgress === "object" && nestedProgress !== null && !Array.isArray(nestedProgress) ? {
|
|
310938
|
+
...payload,
|
|
310939
|
+
...nestedProgress
|
|
310940
|
+
} : payload;
|
|
310941
|
+
const rawPercent = mediaPayloadNumber(source, ["percent", "progress_percent", "progressPercent", "progress"]);
|
|
310921
310942
|
const percent = rawPercent === void 0 ? void 0 : rawPercent <= 1 ? rawPercent * 100 : rawPercent;
|
|
310922
310943
|
const progress = {
|
|
310923
|
-
phaseLabel: mediaPayloadText(
|
|
310944
|
+
phaseLabel: mediaPayloadText(source, ["phase", "stage", "step_name", "stepName"]),
|
|
310924
310945
|
percent: percent === void 0 ? void 0 : Math.min(100, Math.max(0, percent)),
|
|
310925
|
-
currentFrame: mediaPayloadNumber(
|
|
310926
|
-
totalFrames: mediaPayloadNumber(
|
|
310927
|
-
fps: mediaPayloadNumber(
|
|
310928
|
-
currentStep: mediaPayloadNumber(
|
|
310929
|
-
totalSteps: mediaPayloadNumber(
|
|
310930
|
-
currentSegment: mediaPayloadNumber(
|
|
310931
|
-
totalSegments: mediaPayloadNumber(
|
|
310932
|
-
generatedSeconds: mediaPayloadNumber(
|
|
310933
|
-
queuePosition: mediaPayloadNumber(
|
|
310934
|
-
elapsedSeconds: mediaPayloadNumber(
|
|
310935
|
-
remainingSeconds: mediaPayloadNumber(
|
|
310936
|
-
durationSeconds: mediaPayloadNumber(
|
|
310937
|
-
width: mediaPayloadNumber(
|
|
310938
|
-
height: mediaPayloadNumber(
|
|
310939
|
-
previewUrl: mediaPayloadText(
|
|
310940
|
-
previewDataBase64: mediaPayloadText(
|
|
310941
|
-
previewMimeType: mediaPayloadText(
|
|
310942
|
-
sampleUrl: mediaPayloadText(
|
|
310943
|
-
localPath: mediaPayloadText(
|
|
310944
|
-
waveform: mediaPayloadNumberArray(
|
|
310946
|
+
currentFrame: mediaPayloadNumber(source, ["current_frame", "currentFrame", "frames_completed", "framesCompleted"]),
|
|
310947
|
+
totalFrames: mediaPayloadNumber(source, ["total_frames", "totalFrames", "frame_count", "frameCount"]),
|
|
310948
|
+
fps: mediaPayloadNumber(source, ["fps", "frames_per_second", "framesPerSecond"]),
|
|
310949
|
+
currentStep: mediaPayloadNumber(source, ["current_step", "currentStep", "step"]),
|
|
310950
|
+
totalSteps: mediaPayloadNumber(source, ["total_steps", "totalSteps", "steps"]),
|
|
310951
|
+
currentSegment: mediaPayloadNumber(source, ["current_segment", "currentSegment", "segment"]),
|
|
310952
|
+
totalSegments: mediaPayloadNumber(source, ["total_segments", "totalSegments", "segments"]),
|
|
310953
|
+
generatedSeconds: mediaPayloadNumber(source, ["generated_seconds", "generatedSeconds", "audio_seconds", "audioSeconds"]),
|
|
310954
|
+
queuePosition: mediaPayloadNumber(source, ["queue_position", "queuePosition"]),
|
|
310955
|
+
elapsedSeconds: mediaPayloadNumber(source, ["elapsed_seconds", "elapsedSeconds"]),
|
|
310956
|
+
remainingSeconds: mediaPayloadNumber(source, ["remaining_seconds", "remainingSeconds", "eta_seconds", "etaSeconds"]),
|
|
310957
|
+
durationSeconds: mediaPayloadNumber(source, ["duration_seconds", "durationSeconds"]),
|
|
310958
|
+
width: mediaPayloadNumber(source, ["width", "width_px", "widthPx"]),
|
|
310959
|
+
height: mediaPayloadNumber(source, ["height", "height_px", "heightPx"]),
|
|
310960
|
+
previewUrl: mediaPayloadText(source, ["preview_url", "previewUrl", "thumbnail_url", "thumbnailUrl"]),
|
|
310961
|
+
previewDataBase64: mediaPayloadText(source, ["preview_base64", "previewBase64", "thumbnail_base64", "thumbnailBase64"]),
|
|
310962
|
+
previewMimeType: mediaPayloadText(source, ["preview_mime_type", "previewMimeType", "thumbnail_mime_type", "thumbnailMimeType"]),
|
|
310963
|
+
sampleUrl: mediaPayloadText(source, ["sample_url", "sampleUrl", "audio_preview_url", "audioPreviewUrl"]),
|
|
310964
|
+
localPath: mediaPayloadText(source, ["local_path", "localPath"]),
|
|
310965
|
+
waveform: mediaPayloadNumberArray(source, ["waveform", "waveform_samples", "waveformSamples"])
|
|
310945
310966
|
};
|
|
310946
310967
|
return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
|
|
310947
310968
|
}
|
|
@@ -334451,8 +334472,9 @@ function toolResultToSessionUpdate(sessionId, event) {
|
|
|
334451
334472
|
* Mapping rules (anchored at types.gen.d.ts:3530-3569 / :4849):
|
|
334452
334473
|
* - The `todo_list` input-display block carries
|
|
334453
334474
|
* `items: { title, status }[]` (schemas.ts:60). The status is the
|
|
334454
|
-
*
|
|
334455
|
-
* `pending` | `in_progress` | `done
|
|
334475
|
+
* TodoStatus union (todo-list.ts:26):
|
|
334476
|
+
* `pending` | `in_progress` | `done` | `blocked` |
|
|
334477
|
+
* `waiting_approval` | `aborted`.
|
|
334456
334478
|
* - ACP {@link PlanEntryStatus} is `pending` | `in_progress` | `completed`,
|
|
334457
334479
|
* so `done` rewrites to `completed`. Anything outside the known
|
|
334458
334480
|
* enum lands on `pending` as a safe default — we never want a
|
|
@@ -334486,6 +334508,9 @@ function mapTodoStatus(status) {
|
|
|
334486
334508
|
case "in_progress": return "in_progress";
|
|
334487
334509
|
case "done":
|
|
334488
334510
|
case "completed": return "completed";
|
|
334511
|
+
case "blocked":
|
|
334512
|
+
case "waiting_approval":
|
|
334513
|
+
case "aborted": return "pending";
|
|
334489
334514
|
default: return "pending";
|
|
334490
334515
|
}
|
|
334491
334516
|
}
|
|
@@ -401361,6 +401386,21 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
|
|
|
401361
401386
|
return "idle-only";
|
|
401362
401387
|
}
|
|
401363
401388
|
},
|
|
401389
|
+
{
|
|
401390
|
+
name: "idea",
|
|
401391
|
+
aliases: [],
|
|
401392
|
+
descriptionKey: "command.goal.description",
|
|
401393
|
+
priority: 80,
|
|
401394
|
+
argumentHint: "<objective>",
|
|
401395
|
+
availability: (args) => {
|
|
401396
|
+
const trimmed = args.trim();
|
|
401397
|
+
if (trimmed === "") return "always";
|
|
401398
|
+
const tokens = trimmed.split(/\s+/);
|
|
401399
|
+
const action = tokens[0]?.toLowerCase();
|
|
401400
|
+
if (tokens.length === 1 && (action === "status" || action === "pause" || action === "paused" || action === "resume" || action === "cancel" || action === "clear" || action === "stop")) return "always";
|
|
401401
|
+
return "idle-only";
|
|
401402
|
+
}
|
|
401403
|
+
},
|
|
401364
401404
|
{
|
|
401365
401405
|
name: "loop",
|
|
401366
401406
|
aliases: [],
|
|
@@ -411873,7 +411913,7 @@ function isTodoItemShape(value) {
|
|
|
411873
411913
|
if (typeof value !== "object" || value === null) return false;
|
|
411874
411914
|
const rec = value;
|
|
411875
411915
|
if (typeof rec.title !== "string" || rec.title.length === 0) return false;
|
|
411876
|
-
return rec.status === "pending" || rec.status === "in_progress" || rec.status === "done";
|
|
411916
|
+
return rec.status === "pending" || rec.status === "in_progress" || rec.status === "done" || rec.status === "blocked" || rec.status === "waiting_approval" || rec.status === "aborted";
|
|
411877
411917
|
}
|
|
411878
411918
|
const PUBLIC_IDENTITY_ASSIGNMENT_RE = /\b(provider|model)(["']?\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^,;\s)\]}]+)/giu;
|
|
411879
411919
|
/** Project structured runtime identity fields onto the public BLUN identity. */
|
|
@@ -412659,6 +412699,7 @@ var ChoicePickerComponent = class extends Container {
|
|
|
412659
412699
|
const descriptionColor = isSelected && opt.descriptionTone !== void 0 ? opt.descriptionTone : "textMuted";
|
|
412660
412700
|
for (const descLine of wrapDescription$1(opt.description, descriptionWidth)) lines.push(currentTheme.fg(descriptionColor, ` ${descLine}`));
|
|
412661
412701
|
}
|
|
412702
|
+
if (this.opts.spaced === true && i < view.page.end - 1) lines.push("");
|
|
412662
412703
|
}
|
|
412663
412704
|
lines.push("");
|
|
412664
412705
|
if (view.page.pageCount > 1) lines.push(currentTheme.fg("textMuted", ` ${uiText("common.pageCount", {
|
|
@@ -416649,6 +416690,19 @@ function bufferContext(state, chatId, tag) {
|
|
|
416649
416690
|
function channelOrigin(envelope) {
|
|
416650
416691
|
return `Telegram · ${envelope.meta.user ?? envelope.meta.chat_id}`;
|
|
416651
416692
|
}
|
|
416693
|
+
const TELEGRAM_REMOTE_COMMANDS = Object.freeze(["loop", "goal", "idea", "befehle"]);
|
|
416694
|
+
function telegramRemoteCommand(text) {
|
|
416695
|
+
const trimmed = text.trim();
|
|
416696
|
+
const parsed = parseSlashInput(trimmed);
|
|
416697
|
+
if (parsed === null) return;
|
|
416698
|
+
const name = parsed.name.toLowerCase().split("@", 1)[0];
|
|
416699
|
+
if (!TELEGRAM_REMOTE_COMMANDS.includes(name)) return;
|
|
416700
|
+
return {
|
|
416701
|
+
name,
|
|
416702
|
+
input: name === "befehle" ? "/befehle" : `/${name}${parsed.args.length > 0 ? ` ${parsed.args}` : ""}`,
|
|
416703
|
+
displayText: trimmed
|
|
416704
|
+
};
|
|
416705
|
+
}
|
|
416652
416706
|
/**
|
|
416653
416707
|
* Route one envelope into the visible session. Unaddressed group traffic is
|
|
416654
416708
|
* shown and buffered per chat without a model turn. Addressed traffic queues
|
|
@@ -420076,6 +420130,51 @@ async function handleGoalCommand(host, args) {
|
|
|
420076
420130
|
return;
|
|
420077
420131
|
}
|
|
420078
420132
|
}
|
|
420133
|
+
const IDEA_ALLOWED_CHANNELS = Object.freeze([]);
|
|
420134
|
+
const IDEA_TERMINAL_TODO_STATUSES = Object.freeze([
|
|
420135
|
+
"done",
|
|
420136
|
+
"blocked",
|
|
420137
|
+
"waiting_approval",
|
|
420138
|
+
"aborted"
|
|
420139
|
+
]);
|
|
420140
|
+
function buildIdeaPrompt(objective) {
|
|
420141
|
+
const allowedChannels = IDEA_ALLOWED_CHANNELS.length === 0 ? "none" : IDEA_ALLOWED_CHANNELS.join(", ");
|
|
420142
|
+
return `<idea-mode>
|
|
420143
|
+
The user's objective is:
|
|
420144
|
+
${objective}
|
|
420145
|
+
|
|
420146
|
+
Your first tool call MUST be TodoList. Publish a task-specific plan there before any research or action.
|
|
420147
|
+
|
|
420148
|
+
${buildIdeaContract(allowedChannels)}
|
|
420149
|
+
</idea-mode>`;
|
|
420150
|
+
}
|
|
420151
|
+
function buildIdeaContract(allowedChannels = IDEA_ALLOWED_CHANNELS.length === 0 ? "none" : IDEA_ALLOWED_CHANNELS.join(", ")) {
|
|
420152
|
+
return `Work as a self-directing employee: discover a useful route, research current examples and real user problems when relevant, decide, implement reversible local work, verify results, and keep going without waiting for step-by-step instructions.
|
|
420153
|
+
|
|
420154
|
+
Maintain the task-specific plan in TodoList. Keep exactly one item in_progress. Every plan item must end in exactly one truthful terminal status from: ${IDEA_TERMINAL_TODO_STATUSES.join(", ")}. A blocked title must include the reason and what would unblock it. A waiting_approval title must include the prepared result and the exact approval needed. An aborted title must include the last reached state.
|
|
420155
|
+
|
|
420156
|
+
Ask a focused question with AskUserQuestion only when the answer changes the next action or when access is missing. Otherwise act.
|
|
420157
|
+
|
|
420158
|
+
Read-only research through search and fetch tools is allowed. External publishing, messages to third parties, account creation, purchases, or other irreversible external actions are allowed only for an exact channel listed in IDEA_ALLOWED_CHANNELS and only with the user's own required access. Currently allowed channels: ${allowedChannels}. If a later step would leave the computer and is not preapproved, prepare everything possible, mark the step waiting_approval, and ask for the precise approval or access. Never skip the step silently.
|
|
420159
|
+
|
|
420160
|
+
Do not stop after proposing ideas. Carry safe local work through implementation and verification. The assignment is complete only when every plan item has a truthful terminal status and every claimed result has been verified. Report concrete results, blockers, or decisions.`;
|
|
420161
|
+
}
|
|
420162
|
+
async function handleIdeaCommand(host, args) {
|
|
420163
|
+
const parsed = parseGoalCommand(args);
|
|
420164
|
+
if (parsed.kind !== "create") {
|
|
420165
|
+
await handleGoalCommand(host, args);
|
|
420166
|
+
return;
|
|
420167
|
+
}
|
|
420168
|
+
await createGoal(host, {
|
|
420169
|
+
...parsed,
|
|
420170
|
+
completionCriterion: buildIdeaContract()
|
|
420171
|
+
}, args, {
|
|
420172
|
+
commandName: "idea",
|
|
420173
|
+
sendInput: (objective) => {
|
|
420174
|
+
host.sendMessage(host.requireSession(), objective, { parts: buildIdeaPrompt(objective) });
|
|
420175
|
+
}
|
|
420176
|
+
});
|
|
420177
|
+
}
|
|
420079
420178
|
function parseNextGoalCommand(tokens) {
|
|
420080
420179
|
if (tokens.length === 2 && tokens[1]?.toLowerCase() === "manage") return { kind: "next-manage" };
|
|
420081
420180
|
let index = 1;
|
|
@@ -420218,7 +420317,7 @@ async function createGoal(host, parsed, rawArgs, options = {}) {
|
|
|
420218
420317
|
return startGoal(host, parsed, options);
|
|
420219
420318
|
}
|
|
420220
420319
|
function showGoalStartPermissionPrompt(host, parsed, rawArgs, options) {
|
|
420221
|
-
const commandText =
|
|
420320
|
+
const commandText = `/${options.commandName ?? "goal"} ${rawArgs.trim()}`;
|
|
420222
420321
|
const cancelStart = () => {
|
|
420223
420322
|
host.restoreInputText(commandText);
|
|
420224
420323
|
host.showStatus(uiText("goal.start.notStarted"));
|
|
@@ -420255,6 +420354,7 @@ async function startGoal(host, parsed, options) {
|
|
|
420255
420354
|
try {
|
|
420256
420355
|
await host.requireSession().createGoal({
|
|
420257
420356
|
objective: parsed.objective,
|
|
420357
|
+
completionCriterion: parsed.completionCriterion,
|
|
420258
420358
|
replace: parsed.replace
|
|
420259
420359
|
});
|
|
420260
420360
|
} catch (error) {
|
|
@@ -420329,6 +420429,11 @@ async function showGoalStatus(host) {
|
|
|
420329
420429
|
host.showStatus(uiText("goal.status.noneSet"));
|
|
420330
420430
|
return;
|
|
420331
420431
|
}
|
|
420432
|
+
const telegramCommand = host.telegramRemoteCommandContext?.item.telegramCommandName;
|
|
420433
|
+
if (telegramCommand === "goal" || telegramCommand === "idea") {
|
|
420434
|
+
host.showStatus(`/${telegramCommand} ${goal.status} · ${goal.objective}`);
|
|
420435
|
+
return;
|
|
420436
|
+
}
|
|
420332
420437
|
host.state.transcriptContainer.addChild(new GoalStatusMessageComponent(goal));
|
|
420333
420438
|
host.state.ui.requestRender();
|
|
420334
420439
|
}
|
|
@@ -420480,6 +420585,7 @@ async function handleLoopCommand(host, args) {
|
|
|
420480
420585
|
switch (parsed.kind) {
|
|
420481
420586
|
case "status": {
|
|
420482
420587
|
const { loop } = await session.getLoop();
|
|
420588
|
+
host.setAppState?.({ loop });
|
|
420483
420589
|
host.track("loop_status", { status: loop?.status ?? "none" });
|
|
420484
420590
|
host.showStatus(`/loop ${loop?.status ?? "none"}`);
|
|
420485
420591
|
return;
|
|
@@ -420489,6 +420595,7 @@ async function handleLoopCommand(host, args) {
|
|
|
420489
420595
|
interval: parsed.interval,
|
|
420490
420596
|
prompt: parsed.prompt
|
|
420491
420597
|
});
|
|
420598
|
+
host.setAppState?.({ loop });
|
|
420492
420599
|
host.track("loop_start", { interval: loop.interval });
|
|
420493
420600
|
host.showStatus(`/loop ${loop.status}`);
|
|
420494
420601
|
host.sendNormalUserInput(loop.prompt);
|
|
@@ -420496,6 +420603,7 @@ async function handleLoopCommand(host, args) {
|
|
|
420496
420603
|
}
|
|
420497
420604
|
case "pause": {
|
|
420498
420605
|
const loop = await session.pauseLoop();
|
|
420606
|
+
host.setAppState?.({ loop });
|
|
420499
420607
|
if (isStreaming(host)) await session.cancel();
|
|
420500
420608
|
host.track("loop_pause");
|
|
420501
420609
|
host.showStatus(`/loop ${loop.status}`);
|
|
@@ -420503,12 +420611,14 @@ async function handleLoopCommand(host, args) {
|
|
|
420503
420611
|
}
|
|
420504
420612
|
case "resume": {
|
|
420505
420613
|
const loop = await session.resumeLoop();
|
|
420614
|
+
host.setAppState?.({ loop });
|
|
420506
420615
|
host.track("loop_resume");
|
|
420507
420616
|
host.showStatus(`/loop ${loop.status}`);
|
|
420508
420617
|
return;
|
|
420509
420618
|
}
|
|
420510
420619
|
case "stop":
|
|
420511
420620
|
await session.stopLoop();
|
|
420621
|
+
host.setAppState?.({ loop: null });
|
|
420512
420622
|
if (isStreaming(host)) await session.cancel();
|
|
420513
420623
|
host.track("loop_stop");
|
|
420514
420624
|
host.showStatus("/loop stopped");
|
|
@@ -493079,6 +493189,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
493079
493189
|
case "goal":
|
|
493080
493190
|
await handleGoalCommand(host, args);
|
|
493081
493191
|
return;
|
|
493192
|
+
case "idea":
|
|
493193
|
+
await handleIdeaCommand(host, args);
|
|
493194
|
+
return;
|
|
493082
493195
|
case "loop":
|
|
493083
493196
|
await handleLoopCommand(host, args);
|
|
493084
493197
|
return;
|
|
@@ -498256,23 +498369,19 @@ function mediaActivityCanonicalPhase(value) {
|
|
|
498256
498369
|
}
|
|
498257
498370
|
function mediaActivityPhaseSequence(mediaKind) {
|
|
498258
498371
|
switch (mediaActivityStatusKey(mediaKind)) {
|
|
498259
|
-
case "image": return ["queued", "generating", "upscaling", "saving"];
|
|
498260
|
-
case "video": return ["queued", "generating", "rendering", "saving"];
|
|
498372
|
+
case "image": return ["queued", "processing", "generating", "upscaling", "saving"];
|
|
498373
|
+
case "video": return ["queued", "processing", "generating", "rendering", "saving"];
|
|
498261
498374
|
case "voice": return ["queued", "processing", "saving"];
|
|
498262
|
-
case "dubbing": return ["queued", "audio-analysis", "rendering", "saving"];
|
|
498263
|
-
case "lipsync": return ["queued", "audio-analysis", "face-tracking", "lip-sync-running", "rendering", "exporting", "saving"];
|
|
498375
|
+
case "dubbing": return ["queued", "processing", "audio-analysis", "rendering", "saving"];
|
|
498376
|
+
case "lipsync": return ["queued", "processing", "audio-analysis", "face-tracking", "lip-sync-running", "rendering", "exporting", "saving"];
|
|
498264
498377
|
case "image-analysis":
|
|
498265
|
-
case "video-analysis": return ["queued", "analyzing"];
|
|
498378
|
+
case "video-analysis": return ["queued", "processing", "analyzing"];
|
|
498266
498379
|
default: return ["queued", "processing", "saving"];
|
|
498267
498380
|
}
|
|
498268
498381
|
}
|
|
498269
498382
|
function mediaActivityChainStates(job) {
|
|
498270
498383
|
const sequence = mediaActivityPhaseSequence(job.mediaKind);
|
|
498271
|
-
const normalize = (value) =>
|
|
498272
|
-
const phase = mediaActivityCanonicalPhase(value);
|
|
498273
|
-
if (phase === "processing" && !sequence.includes(phase)) return sequence[1] ?? phase;
|
|
498274
|
-
return phase;
|
|
498275
|
-
};
|
|
498384
|
+
const normalize = (value) => mediaActivityCanonicalPhase(value);
|
|
498276
498385
|
const current = normalize(job.phaseLabel ?? job.phase);
|
|
498277
498386
|
const currentIndex = sequence.indexOf(current);
|
|
498278
498387
|
const history = new Set((job.phaseHistory ?? []).map(normalize));
|
|
@@ -498437,7 +498546,7 @@ var MediaActivityComponent = class {
|
|
|
498437
498546
|
}
|
|
498438
498547
|
chainLine(job, width) {
|
|
498439
498548
|
const chain = mediaActivityChainStates(job).map(({ phase, state }) => {
|
|
498440
|
-
const symbol = state === "done" ? "
|
|
498549
|
+
const symbol = state === "done" ? "■" : state === "active" ? "▶" : "□";
|
|
498441
498550
|
const label = mediaActivityLabel("media.phase", phase, phase);
|
|
498442
498551
|
const text = `${symbol} ${label}`;
|
|
498443
498552
|
if (state === "done") return currentTheme.fg("success", text);
|
|
@@ -498447,13 +498556,27 @@ var MediaActivityComponent = class {
|
|
|
498447
498556
|
return truncateToWidth(` ${currentTheme.fg("textDim", mediaUiText("media.activity.chain"))} · ${chain.join(currentTheme.fg("textDim", " · "))}`, Math.max(1, width), "…");
|
|
498448
498557
|
}
|
|
498449
498558
|
previewImage(job) {
|
|
498450
|
-
|
|
498451
|
-
|
|
498559
|
+
let previewDataBase64 = typeof job.previewDataBase64 === "string" ? job.previewDataBase64 : "";
|
|
498560
|
+
let mimeType = typeof job.previewMimeType === "string" ? job.previewMimeType.toLowerCase() : "";
|
|
498561
|
+
if (previewDataBase64.length === 0 && typeof job.previewUrl === "string") {
|
|
498562
|
+
const dataUrl = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/=\r\n]+)$/iu.exec(job.previewUrl.trim());
|
|
498563
|
+
if (dataUrl !== null) {
|
|
498564
|
+
mimeType = dataUrl[1].toLowerCase();
|
|
498565
|
+
previewDataBase64 = dataUrl[2];
|
|
498566
|
+
}
|
|
498567
|
+
}
|
|
498568
|
+
if (previewDataBase64.length === 0 || previewDataBase64.length > 2 * 1024 * 1024 || !/^[A-Za-z0-9+/=\r\n]+$/u.test(previewDataBase64)) return;
|
|
498452
498569
|
if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(mimeType)) return;
|
|
498453
|
-
const
|
|
498570
|
+
const identity = String(job.id ?? job.toolCallId);
|
|
498571
|
+
const prefix = `${identity}:`;
|
|
498572
|
+
const key = `${prefix}${mimeType}:${String(job.observedAt ?? job.updatedAt ?? previewDataBase64.length)}`;
|
|
498454
498573
|
let image = this.previewImages.get(key);
|
|
498455
498574
|
if (image === void 0) {
|
|
498456
|
-
|
|
498575
|
+
for (const [previousKey, previousImage] of this.previewImages) if (previousKey.startsWith(prefix)) {
|
|
498576
|
+
previousImage.invalidate();
|
|
498577
|
+
this.previewImages.delete(previousKey);
|
|
498578
|
+
}
|
|
498579
|
+
image = new Image(previewDataBase64, mimeType, { fallbackColor: (text) => currentTheme.fg("textDim", text) }, {
|
|
498457
498580
|
maxHeightCells: 5,
|
|
498458
498581
|
maxWidthCells: 24,
|
|
498459
498582
|
filename: mediaUiText("media.detail.preview")
|
|
@@ -504837,6 +504960,9 @@ var SessionEventHandler = class {
|
|
|
504837
504960
|
stale: event.origin.stale
|
|
504838
504961
|
}
|
|
504839
504962
|
});
|
|
504963
|
+
setTimeout(() => {
|
|
504964
|
+
this.host.refreshLoopState();
|
|
504965
|
+
}, 0);
|
|
504840
504966
|
}
|
|
504841
504967
|
handleTurnEnd(event, sendQueued) {
|
|
504842
504968
|
this.turnWatchdog.stop();
|
|
@@ -507285,6 +507411,30 @@ function formatGoalBadge(goal, colors, wallClockMs) {
|
|
|
507285
507411
|
const label = `${uiText(`goal.panel.status.${goal.status}`)} · ${formatBadgeElapsed(wallClockMs ?? goal.wallClockMs)} · ${turns}`;
|
|
507286
507412
|
return chalk.hex(colors.textMuted)(`[${uiText("footer.goal.label")} `) + chalk.hex(dotColor)("●") + chalk.hex(colors.textMuted)(` ${label}]`);
|
|
507287
507413
|
}
|
|
507414
|
+
function formatLoopBadge(loop, colors, nowMs = Date.now()) {
|
|
507415
|
+
if (loop === null || loop === void 0 || loop.status !== "active" || !Number.isFinite(loop.nextFireAt)) return null;
|
|
507416
|
+
const remainingMs = Math.max(0, loop.nextFireAt - nowMs);
|
|
507417
|
+
let value;
|
|
507418
|
+
let unit;
|
|
507419
|
+
if (remainingMs < 9e4) {
|
|
507420
|
+
value = Math.ceil(remainingMs / 1e3);
|
|
507421
|
+
unit = "second";
|
|
507422
|
+
} else if (remainingMs < 54e5) {
|
|
507423
|
+
value = Math.ceil(remainingMs / 6e4);
|
|
507424
|
+
unit = "minute";
|
|
507425
|
+
} else if (remainingMs < 1296e5) {
|
|
507426
|
+
value = Math.ceil(remainingMs / 36e5);
|
|
507427
|
+
unit = "hour";
|
|
507428
|
+
} else {
|
|
507429
|
+
value = Math.ceil(remainingMs / 864e5);
|
|
507430
|
+
unit = "day";
|
|
507431
|
+
}
|
|
507432
|
+
const relative = new Intl.RelativeTimeFormat(getCurrentUiLocale(), {
|
|
507433
|
+
numeric: "always",
|
|
507434
|
+
style: "short"
|
|
507435
|
+
}).format(value, unit);
|
|
507436
|
+
return chalk.hex(colors.textMuted)(`[/loop · ${relative}]`);
|
|
507437
|
+
}
|
|
507288
507438
|
function formatBadgeElapsed(ms) {
|
|
507289
507439
|
const totalSeconds = Math.round(ms / 1e3);
|
|
507290
507440
|
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
@@ -507463,6 +507613,7 @@ var FooterComponent = class {
|
|
|
507463
507613
|
goalSnapshotKey = null;
|
|
507464
507614
|
goalObservedAtMs = Date.now();
|
|
507465
507615
|
goalTimer = null;
|
|
507616
|
+
loopTimer = null;
|
|
507466
507617
|
compactionAttemptStartedAtMs = null;
|
|
507467
507618
|
compactionEstimatedInputTokens;
|
|
507468
507619
|
compactionProgress;
|
|
@@ -507484,6 +507635,7 @@ var FooterComponent = class {
|
|
|
507484
507635
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
|
|
507485
507636
|
this.syncGoalClock(state.goal);
|
|
507486
507637
|
this.syncGoalTimer(state.goal);
|
|
507638
|
+
this.syncLoopTimer(state.loop);
|
|
507487
507639
|
if (state.isCompacting) this.startCompaction();
|
|
507488
507640
|
}
|
|
507489
507641
|
setState(state) {
|
|
@@ -507493,6 +507645,7 @@ var FooterComponent = class {
|
|
|
507493
507645
|
}
|
|
507494
507646
|
this.syncGoalClock(state.goal);
|
|
507495
507647
|
this.syncGoalTimer(state.goal);
|
|
507648
|
+
this.syncLoopTimer(state.loop);
|
|
507496
507649
|
this.state = state;
|
|
507497
507650
|
if (state.isCompacting && this.compactionAttemptStartedAtMs === null) this.startCompaction();
|
|
507498
507651
|
else if (!state.isCompacting) this.finishCompaction();
|
|
@@ -507559,6 +507712,8 @@ var FooterComponent = class {
|
|
|
507559
507712
|
if (modes.length > 0) left.push(modes.join(" "));
|
|
507560
507713
|
const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
|
|
507561
507714
|
if (goalBadge !== null) left.push(goalBadge);
|
|
507715
|
+
const loopBadge = formatLoopBadge(state.loop, colors);
|
|
507716
|
+
if (loopBadge !== null) left.push(loopBadge);
|
|
507562
507717
|
const model = modelDisplayName(state);
|
|
507563
507718
|
if (model) {
|
|
507564
507719
|
const sep = chalk.hex(colors.textMuted)(" · ");
|
|
@@ -507623,11 +507778,29 @@ var FooterComponent = class {
|
|
|
507623
507778
|
this.goalTimer = null;
|
|
507624
507779
|
}
|
|
507625
507780
|
}
|
|
507781
|
+
syncLoopTimer(loop) {
|
|
507782
|
+
if (loop?.status === "active" && Number.isFinite(loop.nextFireAt)) {
|
|
507783
|
+
if (this.loopTimer !== null) return;
|
|
507784
|
+
this.loopTimer = setInterval(() => {
|
|
507785
|
+
this.onRefresh();
|
|
507786
|
+
}, GOAL_TIMER_INTERVAL_MS);
|
|
507787
|
+
this.loopTimer.unref?.();
|
|
507788
|
+
return;
|
|
507789
|
+
}
|
|
507790
|
+
if (this.loopTimer !== null) {
|
|
507791
|
+
clearInterval(this.loopTimer);
|
|
507792
|
+
this.loopTimer = null;
|
|
507793
|
+
}
|
|
507794
|
+
}
|
|
507626
507795
|
dispose() {
|
|
507627
507796
|
if (this.goalTimer !== null) {
|
|
507628
507797
|
clearInterval(this.goalTimer);
|
|
507629
507798
|
this.goalTimer = null;
|
|
507630
507799
|
}
|
|
507800
|
+
if (this.loopTimer !== null) {
|
|
507801
|
+
clearInterval(this.loopTimer);
|
|
507802
|
+
this.loopTimer = null;
|
|
507803
|
+
}
|
|
507631
507804
|
this.finishCompaction();
|
|
507632
507805
|
}
|
|
507633
507806
|
goalWallClockMs(goal) {
|
|
@@ -510396,6 +510569,8 @@ const MAX_VISIBLE = 5;
|
|
|
510396
510569
|
const BLUN_BLUE = "#0261ff";
|
|
510397
510570
|
const BLUN_BRIGHT = "#4f8dff";
|
|
510398
510571
|
const DONE_GREEN = "#22c55e";
|
|
510572
|
+
const BLOCKED_RED = "#ef4444";
|
|
510573
|
+
const WAITING_AMBER = "#f59e0b";
|
|
510399
510574
|
const BOX_EMPTY = "#2a3550";
|
|
510400
510575
|
const PENDING_TEXT = "#aeb8c6";
|
|
510401
510576
|
const LEFT_BAR = "▎";
|
|
@@ -510409,7 +510584,9 @@ const LEFT_BAR = "▎";
|
|
|
510409
510584
|
*
|
|
510410
510585
|
* Strategy:
|
|
510411
510586
|
* 1. Include every `in_progress` item (capped at MAX_VISIBLE).
|
|
510412
|
-
* 2.
|
|
510587
|
+
* 2. Include terminal attention states (`blocked`, `waiting_approval`,
|
|
510588
|
+
* `aborted`) before ordinary pending and done work.
|
|
510589
|
+
* 3. Fill remaining slots with "what's next" — the earliest `pending`
|
|
510413
510590
|
* items in their original positions — while reserving one slot for
|
|
510414
510591
|
* "what just finished" — the latest `done` item — when both kinds
|
|
510415
510592
|
* exist. If one side has too few candidates, the other expands.
|
|
@@ -510423,17 +510600,26 @@ function selectVisibleTodos(todos) {
|
|
|
510423
510600
|
hiddenCounts: {
|
|
510424
510601
|
done: 0,
|
|
510425
510602
|
in_progress: 0,
|
|
510426
|
-
pending: 0
|
|
510603
|
+
pending: 0,
|
|
510604
|
+
blocked: 0,
|
|
510605
|
+
waiting_approval: 0,
|
|
510606
|
+
aborted: 0
|
|
510427
510607
|
}
|
|
510428
510608
|
};
|
|
510429
510609
|
const inProgress = [];
|
|
510430
510610
|
const pending = [];
|
|
510431
510611
|
const done = [];
|
|
510612
|
+
const attention = [];
|
|
510432
510613
|
for (const [i, todo] of todos.entries()) if (todo.status === "in_progress") inProgress.push(i);
|
|
510433
510614
|
else if (todo.status === "pending") pending.push(i);
|
|
510434
|
-
else done.push(i);
|
|
510615
|
+
else if (todo.status === "done") done.push(i);
|
|
510616
|
+
else attention.push(i);
|
|
510435
510617
|
const picked = /* @__PURE__ */ new Set();
|
|
510436
510618
|
for (const i of inProgress.slice(0, MAX_VISIBLE)) picked.add(i);
|
|
510619
|
+
for (const i of attention) {
|
|
510620
|
+
if (picked.size >= MAX_VISIBLE) break;
|
|
510621
|
+
picked.add(i);
|
|
510622
|
+
}
|
|
510437
510623
|
if (picked.size < MAX_VISIBLE) {
|
|
510438
510624
|
const doneCandidates = done.toReversed();
|
|
510439
510625
|
const pendingCandidates = pending;
|
|
@@ -510458,7 +510644,10 @@ function selectVisibleTodos(todos) {
|
|
|
510458
510644
|
const hiddenCounts = {
|
|
510459
510645
|
done: 0,
|
|
510460
510646
|
in_progress: 0,
|
|
510461
|
-
pending: 0
|
|
510647
|
+
pending: 0,
|
|
510648
|
+
blocked: 0,
|
|
510649
|
+
waiting_approval: 0,
|
|
510650
|
+
aborted: 0
|
|
510462
510651
|
};
|
|
510463
510652
|
for (const [i, todo] of todos.entries()) if (!picked.has(i)) hiddenCounts[todo.status] += 1;
|
|
510464
510653
|
return {
|
|
@@ -510535,6 +510724,9 @@ function renderRow(todo, width) {
|
|
|
510535
510724
|
return truncateToWidth(leftPart + chalk.hex("#ffffff")(title) + " ".repeat(gap) + chalk.hex(currentTheme.palette.textDim)(runningLabel), width);
|
|
510536
510725
|
}
|
|
510537
510726
|
if (todo.status === "done") return " " + chalk.hex(DONE_GREEN)("▪") + " " + chalk.hex(currentTheme.palette.textDim).strikethrough(todo.title);
|
|
510727
|
+
if (todo.status === "blocked") return " " + chalk.hex(BLOCKED_RED)("■") + " " + chalk.hex("#fecaca")(todo.title);
|
|
510728
|
+
if (todo.status === "waiting_approval") return " " + chalk.hex(WAITING_AMBER)("◆") + " " + chalk.hex("#fde68a")(todo.title);
|
|
510729
|
+
if (todo.status === "aborted") return " " + chalk.hex(currentTheme.palette.textDim)("×") + " " + chalk.hex(currentTheme.palette.textDim).strikethrough(todo.title);
|
|
510538
510730
|
return " " + chalk.hex(BOX_EMPTY)("☐") + " " + chalk.hex(PENDING_TEXT)(todo.title);
|
|
510539
510731
|
}
|
|
510540
510732
|
//#endregion
|
|
@@ -511489,6 +511681,22 @@ function outboxGrewForChat(marker, chatId) {
|
|
|
511489
511681
|
} catch {}
|
|
511490
511682
|
return false;
|
|
511491
511683
|
}
|
|
511684
|
+
function outboxDeliveredFile(marker, chatId, filePath) {
|
|
511685
|
+
try {
|
|
511686
|
+
if (statSync(outboxPath()).size <= marker) return false;
|
|
511687
|
+
const fresh = readFileSync(outboxPath()).subarray(marker).toString("utf8");
|
|
511688
|
+
const wanted = resolve(filePath).toLowerCase();
|
|
511689
|
+
for (const line of fresh.split(/\r?\n/)) {
|
|
511690
|
+
if (line.trim().length === 0) continue;
|
|
511691
|
+
try {
|
|
511692
|
+
const entry = JSON.parse(line);
|
|
511693
|
+
if (String(entry.chat_id) !== String(chatId) || !Array.isArray(entry.files)) continue;
|
|
511694
|
+
if (entry.files.some((file) => typeof file === "string" && resolve(file).toLowerCase() === wanted)) return true;
|
|
511695
|
+
} catch {}
|
|
511696
|
+
}
|
|
511697
|
+
} catch {}
|
|
511698
|
+
return false;
|
|
511699
|
+
}
|
|
511492
511700
|
const TELEGRAM_TEXT_LIMIT = 4096;
|
|
511493
511701
|
const TELEGRAM_ATTACHMENT_LIMIT = 50 * 1024 * 1024;
|
|
511494
511702
|
function mediaTelegramTarget(filePath) {
|
|
@@ -512216,19 +512424,20 @@ function promptStartupWorkspaceChoice(host, currentPath, copy) {
|
|
|
512216
512424
|
};
|
|
512217
512425
|
host.mountEditorReplacement(new ChoicePickerComponent({
|
|
512218
512426
|
title: copy.choiceTitle,
|
|
512427
|
+
spaced: true,
|
|
512219
512428
|
options: [
|
|
512220
512429
|
{
|
|
512221
512430
|
value: "use",
|
|
512222
|
-
label: copy.useCurrent
|
|
512431
|
+
label: `1. ${copy.useCurrent}`,
|
|
512223
512432
|
description: currentPath
|
|
512224
512433
|
},
|
|
512225
512434
|
{
|
|
512226
512435
|
value: "change",
|
|
512227
|
-
label: copy.chooseOther
|
|
512436
|
+
label: `2. ${copy.chooseOther}`
|
|
512228
512437
|
},
|
|
512229
512438
|
{
|
|
512230
512439
|
value: "exit",
|
|
512231
|
-
label: copy.exit
|
|
512440
|
+
label: `3. ${copy.exit}`,
|
|
512232
512441
|
tone: "danger"
|
|
512233
512442
|
}
|
|
512234
512443
|
],
|
|
@@ -512642,6 +512851,7 @@ function createInitialAppState(input) {
|
|
|
512642
512851
|
availableProviders: {},
|
|
512643
512852
|
sessionTitle: null,
|
|
512644
512853
|
goal: null,
|
|
512854
|
+
loop: null,
|
|
512645
512855
|
mcpServersSummary: null,
|
|
512646
512856
|
banner: void 0
|
|
512647
512857
|
};
|
|
@@ -512683,6 +512893,7 @@ var BlunTUI = class {
|
|
|
512683
512893
|
startupLoginRequired = false;
|
|
512684
512894
|
startupWorkspaceSelectionPending = false;
|
|
512685
512895
|
startupGoalPromptedSessionId;
|
|
512896
|
+
startupPhaseMs = {};
|
|
512686
512897
|
lastActivityMode;
|
|
512687
512898
|
currentLoadingTip = void 0;
|
|
512688
512899
|
mediaActivityStore = new MediaActivityStore();
|
|
@@ -512716,6 +512927,17 @@ var BlunTUI = class {
|
|
|
512716
512927
|
track(event, properties) {
|
|
512717
512928
|
this.harness.track(event, properties);
|
|
512718
512929
|
}
|
|
512930
|
+
async measureStartupPhase(name, action) {
|
|
512931
|
+
const startedAt = Date.now();
|
|
512932
|
+
try {
|
|
512933
|
+
return await action();
|
|
512934
|
+
} finally {
|
|
512935
|
+
this.startupPhaseMs[name] = Date.now() - startedAt;
|
|
512936
|
+
}
|
|
512937
|
+
}
|
|
512938
|
+
getStartupPhaseMs() {
|
|
512939
|
+
return { ...this.startupPhaseMs };
|
|
512940
|
+
}
|
|
512719
512941
|
constructor(harness, startupInput) {
|
|
512720
512942
|
this.harness = harness;
|
|
512721
512943
|
const initialAppState = createInitialAppState(startupInput);
|
|
@@ -512865,7 +513087,7 @@ var BlunTUI = class {
|
|
|
512865
513087
|
}
|
|
512866
513088
|
async start() {
|
|
512867
513089
|
this.registerSignalHandlers();
|
|
512868
|
-
this.startupWorkspaceSelectionPending =
|
|
513090
|
+
this.startupWorkspaceSelectionPending = !(this.options.startup.sessionFlag !== void 0 || this.options.startup.continueLast);
|
|
512869
513091
|
try {
|
|
512870
513092
|
const shouldReplayHistory = await this.initMainTui();
|
|
512871
513093
|
this.startEventLoop();
|
|
@@ -513077,9 +513299,12 @@ var BlunTUI = class {
|
|
|
513077
513299
|
this.showStatus(warning, "warning");
|
|
513078
513300
|
}
|
|
513079
513301
|
async init() {
|
|
513080
|
-
|
|
513081
|
-
await this.
|
|
513082
|
-
|
|
513302
|
+
if (this.startupWorkspaceSelectionPending) return false;
|
|
513303
|
+
await this.measureStartupPhase("experimental_ms", async () => {
|
|
513304
|
+
setExperimentalFeatures(await this.harness.getExperimentalFeatures());
|
|
513305
|
+
});
|
|
513306
|
+
await this.measureStartupPhase("models_ms", () => this.authFlow.refreshAvailableModels());
|
|
513307
|
+
const accountContext = await this.measureStartupPhase("account_ms", () => this.authFlow.refreshManagedAccountContextResult());
|
|
513083
513308
|
if (accountContext.kind !== "ready") {
|
|
513084
513309
|
if (accountContext.kind === "unauthenticated") {
|
|
513085
513310
|
this.authFlow.enterLoginRequiredStartupState();
|
|
@@ -513087,8 +513312,7 @@ var BlunTUI = class {
|
|
|
513087
513312
|
} else this.authFlow.enterManagedAccountUnavailableStartupState(accountContext.kind);
|
|
513088
513313
|
return false;
|
|
513089
513314
|
}
|
|
513090
|
-
|
|
513091
|
-
await this.refreshProviderModelsBeforeSession();
|
|
513315
|
+
await this.measureStartupPhase("provider_models_ms", () => this.refreshProviderModelsBeforeSession());
|
|
513092
513316
|
const { startup } = this.options;
|
|
513093
513317
|
const { workDir } = this.state.appState;
|
|
513094
513318
|
let session;
|
|
@@ -513103,6 +513327,7 @@ var BlunTUI = class {
|
|
|
513103
513327
|
const channelMcpServers = sessionMcpServers();
|
|
513104
513328
|
if (channelMcpServers !== void 0) createSessionOptions.mcpServers = channelMcpServers;
|
|
513105
513329
|
if (this.state.appState.additionalDirs.length > 0) createSessionOptions.additionalDirs = [...this.state.appState.additionalDirs];
|
|
513330
|
+
const sessionStartedAt = Date.now();
|
|
513106
513331
|
try {
|
|
513107
513332
|
if (isResumeStartup) {
|
|
513108
513333
|
if (startup.sessionFlag === "") {
|
|
@@ -513148,10 +513373,12 @@ var BlunTUI = class {
|
|
|
513148
513373
|
this.authFlow.enterLoginRequiredStartupState();
|
|
513149
513374
|
this.startupLoginRequired = true;
|
|
513150
513375
|
return false;
|
|
513376
|
+
} finally {
|
|
513377
|
+
this.startupPhaseMs["session_open_ms"] = Date.now() - sessionStartedAt;
|
|
513151
513378
|
}
|
|
513152
513379
|
if (session === void 0) throw new Error(uiText("blunTui.startup.sessionNotInitialized"));
|
|
513153
|
-
await this.setSession(session);
|
|
513154
|
-
await this.syncRuntimeState(session);
|
|
513380
|
+
await this.measureStartupPhase("session_bind_ms", () => this.setSession(session, { refreshPersonalMemory: false }));
|
|
513381
|
+
await this.measureStartupPhase("runtime_state_ms", () => this.syncRuntimeState(session));
|
|
513155
513382
|
this.applyStartupPermissionAndPlanToAppState();
|
|
513156
513383
|
this.state.startupState = "ready";
|
|
513157
513384
|
return shouldReplayHistory;
|
|
@@ -513434,6 +513661,7 @@ var BlunTUI = class {
|
|
|
513434
513661
|
}
|
|
513435
513662
|
queueDrainTimer;
|
|
513436
513663
|
queueCommandRunning = false;
|
|
513664
|
+
telegramRemoteCommandContext;
|
|
513437
513665
|
queueFlushBatchRemaining = 0;
|
|
513438
513666
|
queueSteerInFlight;
|
|
513439
513667
|
editorReplacementActive = false;
|
|
@@ -513684,14 +513912,20 @@ var BlunTUI = class {
|
|
|
513684
513912
|
}
|
|
513685
513913
|
/**
|
|
513686
513914
|
* Remote channel inbound (Telegram) into THIS visible session.
|
|
513687
|
-
*
|
|
513688
|
-
* "/new"
|
|
513915
|
+
* Only the explicit Telegram allowlist routes through slash dispatch; every
|
|
513916
|
+
* other slash-looking message (including "/new") remains plain model text.
|
|
513917
|
+
* There is no bash dispatch and no local editor history. The transcript
|
|
513689
513918
|
* shows a clean user line with a muted origin prefix; the
|
|
513690
513919
|
* model receives the full <channel …> payload. Busy turns queue via the same
|
|
513691
513920
|
* queuedMessages mechanic as typed input; a completed tool/step boundary
|
|
513692
513921
|
* steers one FIFO head into the active turn without interrupting it.
|
|
513693
513922
|
*/
|
|
513694
513923
|
injectChannelMessage(envelope, acknowledge) {
|
|
513924
|
+
const remoteCommand = channelMessageAddressed(envelope) ? telegramRemoteCommand(envelope.text) : void 0;
|
|
513925
|
+
if (remoteCommand !== void 0) {
|
|
513926
|
+
this.injectTelegramRemoteCommand(envelope, remoteCommand, acknowledge);
|
|
513927
|
+
return;
|
|
513928
|
+
}
|
|
513695
513929
|
injectChannelEnvelope({
|
|
513696
513930
|
canDeliver: () => this.session !== void 0 && this.state.appState.model.trim().length > 0,
|
|
513697
513931
|
isBusy: () => this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting,
|
|
@@ -513732,6 +513966,99 @@ var BlunTUI = class {
|
|
|
513732
513966
|
}
|
|
513733
513967
|
}, envelope, this.channelPreamble);
|
|
513734
513968
|
}
|
|
513969
|
+
injectTelegramRemoteCommand(envelope, command, acknowledge) {
|
|
513970
|
+
if (this.session === void 0 || this.state.appState.model.trim().length === 0) {
|
|
513971
|
+
this.showStatus(uiText("channelInjection.noActiveSession"), "warning");
|
|
513972
|
+
return;
|
|
513973
|
+
}
|
|
513974
|
+
const item = {
|
|
513975
|
+
text: command.input,
|
|
513976
|
+
displayText: command.displayText,
|
|
513977
|
+
origin: channelOrigin(envelope),
|
|
513978
|
+
agentId: this.harness.interactiveAgentId,
|
|
513979
|
+
mode: "channel-command",
|
|
513980
|
+
channelChatId: envelope.meta.chat_id,
|
|
513981
|
+
channelAcknowledge: acknowledge,
|
|
513982
|
+
telegramCommandName: command.name
|
|
513983
|
+
};
|
|
513984
|
+
const busy = this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.pendingChannelReplyGuard !== void 0 || this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting;
|
|
513985
|
+
if (busy) {
|
|
513986
|
+
this.state.queuedMessages.push(item);
|
|
513987
|
+
this.track("input_queue", { kind: "channel-command" });
|
|
513988
|
+
this.updateQueueDisplay();
|
|
513989
|
+
this.state.ui.requestRender();
|
|
513990
|
+
return;
|
|
513991
|
+
}
|
|
513992
|
+
this.runTelegramRemoteCommand(item);
|
|
513993
|
+
}
|
|
513994
|
+
runTelegramRemoteCommand(item) {
|
|
513995
|
+
this.queueCommandRunning = true;
|
|
513996
|
+
this.preserveQueueAcrossSessionReset = true;
|
|
513997
|
+
if (item.channelTranscriptRendered !== true) this.appendTranscriptEntry({
|
|
513998
|
+
id: nextTranscriptId(),
|
|
513999
|
+
kind: "user",
|
|
514000
|
+
turnId: void 0,
|
|
514001
|
+
renderMode: "plain",
|
|
514002
|
+
content: item.displayText ?? item.text,
|
|
514003
|
+
origin: item.origin
|
|
514004
|
+
});
|
|
514005
|
+
const context = {
|
|
514006
|
+
item,
|
|
514007
|
+
responses: [],
|
|
514008
|
+
sentModelTurn: false
|
|
514009
|
+
};
|
|
514010
|
+
this.telegramRemoteCommandContext = context;
|
|
514011
|
+
const execution = item.telegramCommandName === "befehle" ? Promise.resolve().then(() => {
|
|
514012
|
+
context.responses.push("/loop\n/goal\n/idea\n/befehle");
|
|
514013
|
+
}) : dispatchInput(this, item.text);
|
|
514014
|
+
execution.catch((error) => {
|
|
514015
|
+
this.showError(formatErrorMessage$2(error));
|
|
514016
|
+
}).finally(async () => {
|
|
514017
|
+
if (this.telegramRemoteCommandContext === context) this.telegramRemoteCommandContext = void 0;
|
|
514018
|
+
if (!context.sentModelTurn) {
|
|
514019
|
+
const response = context.responses.at(-1) ?? item.displayText ?? item.text;
|
|
514020
|
+
if (await sendReplyFallback(item.channelChatId, response, false)) item.channelAcknowledge?.();
|
|
514021
|
+
}
|
|
514022
|
+
this.queueCommandRunning = false;
|
|
514023
|
+
if (!this.editorReplacementActive) this.preserveQueueAcrossSessionReset = false;
|
|
514024
|
+
this.updateQueueDisplay();
|
|
514025
|
+
this.state.ui.requestRender();
|
|
514026
|
+
this.scheduleQueueDrain();
|
|
514027
|
+
});
|
|
514028
|
+
}
|
|
514029
|
+
sendTelegramRemoteCommandMessage(session, input, options, context) {
|
|
514030
|
+
context.sentModelTurn = true;
|
|
514031
|
+
this.beginSessionRequest();
|
|
514032
|
+
const previousGuard = this.pendingChannelReplyGuard;
|
|
514033
|
+
const installedGuard = {
|
|
514034
|
+
chatId: context.item.channelChatId,
|
|
514035
|
+
outboxMarker: outboxMarker(),
|
|
514036
|
+
transcriptStart: this.state.transcriptEntries.length,
|
|
514037
|
+
contextOnly: false
|
|
514038
|
+
};
|
|
514039
|
+
this.pendingChannelReplyGuard = installedGuard;
|
|
514040
|
+
this.setAppState({
|
|
514041
|
+
model: BLUN_KING_MODEL_ALIAS,
|
|
514042
|
+
modelFallbackAllowed: false
|
|
514043
|
+
});
|
|
514044
|
+
session.promptAccepted(options?.parts ?? input).then((result) => {
|
|
514045
|
+
if (result.accepted) {
|
|
514046
|
+
context.item.channelAcknowledge?.();
|
|
514047
|
+
return;
|
|
514048
|
+
}
|
|
514049
|
+
if (this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
|
|
514050
|
+
this.state.queuedMessages = [{
|
|
514051
|
+
...context.item,
|
|
514052
|
+
channelTranscriptRendered: true
|
|
514053
|
+
}, ...this.state.queuedMessages];
|
|
514054
|
+
this.track("input_queue", { kind: "channel-command" });
|
|
514055
|
+
this.updateQueueDisplay();
|
|
514056
|
+
this.state.ui.requestRender();
|
|
514057
|
+
}).catch((error) => {
|
|
514058
|
+
if (this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
|
|
514059
|
+
this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
|
|
514060
|
+
});
|
|
514061
|
+
}
|
|
513735
514062
|
sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false, acknowledge) {
|
|
513736
514063
|
if (!transcriptRendered) this.appendTranscriptEntry({
|
|
513737
514064
|
id: nextTranscriptId(),
|
|
@@ -513791,6 +514118,7 @@ var BlunTUI = class {
|
|
|
513791
514118
|
pendingChannelReplyGuard;
|
|
513792
514119
|
/** See SessionEventHost.runChannelReplyFallback — called at turn end. */
|
|
513793
514120
|
channelMediaDeliveries = /* @__PURE__ */ new Set();
|
|
514121
|
+
channelMediaDeliveryFailures = /* @__PURE__ */ new Set();
|
|
513794
514122
|
/** Deliver completed media at tool-result time so later queued work cannot hide it. */
|
|
513795
514123
|
runChannelMediaFallback(output) {
|
|
513796
514124
|
const guard = this.pendingChannelReplyGuard;
|
|
@@ -513800,9 +514128,12 @@ var BlunTUI = class {
|
|
|
513800
514128
|
if (this.channelMediaDeliveries.has(deliveryKey)) return;
|
|
513801
514129
|
this.channelMediaDeliveries.add(deliveryKey);
|
|
513802
514130
|
sendMediaReplyFallback(guard.chatId, filePath).then((sent) => {
|
|
513803
|
-
if (sent)
|
|
514131
|
+
if (sent) {
|
|
514132
|
+
this.channelMediaDeliveryFailures.delete(deliveryKey);
|
|
514133
|
+
return;
|
|
514134
|
+
}
|
|
513804
514135
|
this.channelMediaDeliveries.delete(deliveryKey);
|
|
513805
|
-
this.
|
|
514136
|
+
this.channelMediaDeliveryFailures.add(deliveryKey);
|
|
513806
514137
|
});
|
|
513807
514138
|
}
|
|
513808
514139
|
sendChannelTurnStatus(message) {
|
|
@@ -513816,6 +514147,13 @@ var BlunTUI = class {
|
|
|
513816
514147
|
const guard = this.pendingChannelReplyGuard;
|
|
513817
514148
|
if (guard === void 0) return;
|
|
513818
514149
|
this.pendingChannelReplyGuard = void 0;
|
|
514150
|
+
for (const deliveryKey of [...this.channelMediaDeliveryFailures]) {
|
|
514151
|
+
const separator = deliveryKey.indexOf("\0");
|
|
514152
|
+
if (separator < 0 || deliveryKey.slice(0, separator) !== guard.chatId) continue;
|
|
514153
|
+
const filePath = deliveryKey.slice(separator + 1);
|
|
514154
|
+
this.channelMediaDeliveryFailures.delete(deliveryKey);
|
|
514155
|
+
if (!outboxDeliveredFile(guard.outboxMarker, guard.chatId, filePath)) this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
|
|
514156
|
+
}
|
|
513819
514157
|
if (reason !== "completed") return;
|
|
513820
514158
|
if (this.state.transcriptEntries.slice(guard.transcriptStart).some((entry) => entry.kind === "tool_call" && /reply|edit_message/i.test(entry.toolCallData?.name ?? ""))) return;
|
|
513821
514159
|
if (outboxGrewForChat(guard.outboxMarker, guard.chatId)) return;
|
|
@@ -513921,7 +514259,7 @@ var BlunTUI = class {
|
|
|
513921
514259
|
recallLastQueued() {
|
|
513922
514260
|
if (this.state.queuedMessages.length === 0) return void 0;
|
|
513923
514261
|
const last = this.state.queuedMessages.at(-1);
|
|
513924
|
-
if (last.mode === "channel") return void 0;
|
|
514262
|
+
if (last.mode === "channel" || last.mode === "channel-command") return void 0;
|
|
513925
514263
|
const queuedBatchCount = Math.max(0, this.queueFlushBatchRemaining - (this.queueSteerInFlight?.items.length ?? 0));
|
|
513926
514264
|
const removesBatchItem = this.state.queuedMessages.length <= queuedBatchCount;
|
|
513927
514265
|
this.state.queuedMessages = this.state.queuedMessages.slice(0, -1);
|
|
@@ -513979,6 +514317,10 @@ var BlunTUI = class {
|
|
|
513979
514317
|
this.runSlashCommand(item.text);
|
|
513980
514318
|
return;
|
|
513981
514319
|
}
|
|
514320
|
+
if (item.mode === "channel-command") {
|
|
514321
|
+
this.runTelegramRemoteCommand(item);
|
|
514322
|
+
return;
|
|
514323
|
+
}
|
|
513982
514324
|
const activeSession = this.session ?? session;
|
|
513983
514325
|
if (item.mode === "channel") {
|
|
513984
514326
|
this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
|
|
@@ -514051,6 +514393,11 @@ var BlunTUI = class {
|
|
|
514051
514393
|
});
|
|
514052
514394
|
}
|
|
514053
514395
|
sendMessage(session, input, options) {
|
|
514396
|
+
const telegramContext = this.telegramRemoteCommandContext;
|
|
514397
|
+
if (telegramContext !== void 0) {
|
|
514398
|
+
this.sendTelegramRemoteCommandMessage(session, input, options, telegramContext);
|
|
514399
|
+
return;
|
|
514400
|
+
}
|
|
514054
514401
|
if (this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting) {
|
|
514055
514402
|
this.enqueueMessage(input, options);
|
|
514056
514403
|
return;
|
|
@@ -514214,7 +514561,7 @@ var BlunTUI = class {
|
|
|
514214
514561
|
if (this.state.appState.additionalDirs.length > 0) options.additionalDirs = [...this.state.appState.additionalDirs];
|
|
514215
514562
|
return this.harness.createSession(options);
|
|
514216
514563
|
}
|
|
514217
|
-
async setSession(session) {
|
|
514564
|
+
async setSession(session, options = {}) {
|
|
514218
514565
|
await session.setModel(BLUN_KING_MODEL_ALIAS, { allowFallback: false });
|
|
514219
514566
|
await this.personalMemoryController.clear(this.session);
|
|
514220
514567
|
const previous = this.unloadCurrentSession(approvalCancellationFeedback("switching_session"));
|
|
@@ -514232,10 +514579,10 @@ var BlunTUI = class {
|
|
|
514232
514579
|
this.syncAdditionalDirs(session);
|
|
514233
514580
|
await this.refreshManagedImageReaderAvailability(session);
|
|
514234
514581
|
await this.authFlow.applyManagedAccountContextToSession();
|
|
514235
|
-
await this.personalMemoryController.refresh();
|
|
514582
|
+
if (options.refreshPersonalMemory !== false) await this.personalMemoryController.refresh();
|
|
514236
514583
|
}
|
|
514237
514584
|
async syncRuntimeState(session = this.requireSession()) {
|
|
514238
|
-
const [status, goalResult] = await Promise.all([session.getStatus(), session.getGoal()]);
|
|
514585
|
+
const [status, goalResult, loopResult] = await Promise.all([session.getStatus(), session.getGoal(), session.getLoop()]);
|
|
514239
514586
|
this.setAppState({
|
|
514240
514587
|
sessionId: session.id,
|
|
514241
514588
|
model: BLUN_KING_MODEL_ALIAS,
|
|
@@ -514252,11 +514599,22 @@ var BlunTUI = class {
|
|
|
514252
514599
|
compactionBudgetTokens: status.compactionBudgetTokens ?? 0,
|
|
514253
514600
|
contextUsage: status.contextUsage,
|
|
514254
514601
|
sessionTitle: session.summary?.title ?? null,
|
|
514255
|
-
goal: goalResult.goal
|
|
514602
|
+
goal: goalResult.goal,
|
|
514603
|
+
loop: loopResult.loop
|
|
514256
514604
|
});
|
|
514257
514605
|
this.syncAdditionalDirs(session);
|
|
514258
514606
|
this.startTelegramChannel();
|
|
514259
514607
|
}
|
|
514608
|
+
async refreshLoopState(session = this.session) {
|
|
514609
|
+
if (session === void 0) {
|
|
514610
|
+
this.setAppState({ loop: null });
|
|
514611
|
+
return;
|
|
514612
|
+
}
|
|
514613
|
+
try {
|
|
514614
|
+
const { loop } = await session.getLoop();
|
|
514615
|
+
if (this.session === session) this.setAppState({ loop });
|
|
514616
|
+
} catch {}
|
|
514617
|
+
}
|
|
514260
514618
|
async applyStartupModesToResumedSession(session) {
|
|
514261
514619
|
const { startup } = this.options;
|
|
514262
514620
|
if (startup.auto) await session.setPermission("auto");
|
|
@@ -514297,6 +514655,7 @@ var BlunTUI = class {
|
|
|
514297
514655
|
this.harness.setTelemetryContext({ sessionId: null });
|
|
514298
514656
|
this.setAppState({
|
|
514299
514657
|
goal: null,
|
|
514658
|
+
loop: null,
|
|
514300
514659
|
swarmModeEntry: void 0
|
|
514301
514660
|
});
|
|
514302
514661
|
return previous;
|
|
@@ -514343,7 +514702,7 @@ var BlunTUI = class {
|
|
|
514343
514702
|
streamingPhase: "idle"
|
|
514344
514703
|
});
|
|
514345
514704
|
if (!this.preserveQueueAcrossSessionReset) {
|
|
514346
|
-
this.state.queuedMessages = this.state.queuedMessages.filter((item) => item.mode === "channel");
|
|
514705
|
+
this.state.queuedMessages = this.state.queuedMessages.filter((item) => item.mode === "channel" || item.mode === "channel-command");
|
|
514347
514706
|
this.queueFlushBatchRemaining = 0;
|
|
514348
514707
|
this.queueSteerInFlight = void 0;
|
|
514349
514708
|
}
|
|
@@ -514772,10 +515131,12 @@ var BlunTUI = class {
|
|
|
514772
515131
|
children.splice(0, children.length, ...newChildren);
|
|
514773
515132
|
}
|
|
514774
515133
|
showStatus(message, color) {
|
|
515134
|
+
this.telegramRemoteCommandContext?.responses.push(message);
|
|
514775
515135
|
this.state.transcriptContainer.addChild(new StatusMessageComponent(message, color));
|
|
514776
515136
|
this.state.ui.requestRender();
|
|
514777
515137
|
}
|
|
514778
515138
|
showNotice(title, detail) {
|
|
515139
|
+
this.telegramRemoteCommandContext?.responses.push(detail === void 0 ? title : `${title}\n${detail}`);
|
|
514779
515140
|
this.state.transcriptContainer.addChild(new NoticeMessageComponent(title, detail));
|
|
514780
515141
|
this.state.ui.requestRender();
|
|
514781
515142
|
}
|
|
@@ -515764,7 +516125,8 @@ async function runShell(opts, version, updateStartupNotice) {
|
|
|
515764
516125
|
duration_ms: Date.now() - startedAt,
|
|
515765
516126
|
config_ms: configMs,
|
|
515766
516127
|
init_ms: initMs,
|
|
515767
|
-
mcp_ms: mcpMs
|
|
516128
|
+
mcp_ms: mcpMs,
|
|
516129
|
+
...tui.getStartupPhaseMs()
|
|
515768
516130
|
});
|
|
515769
516131
|
} catch (error) {
|
|
515770
516132
|
removeCrashHandlers();
|
package/package.json
CHANGED
|
@@ -73603,8 +73603,9 @@ async function runTool(api, token, name, args) {
|
|
|
73603
73603
|
* Idempotency guard: models occasionally call reply multiple times with the
|
|
73604
73604
|
* same text. A message that
|
|
73605
73605
|
* innerhalb des Fensters bereits wortgleich an denselben Chat ging, wird nicht
|
|
73606
|
-
* erneut gesendet — das Tool meldet dem Modell "bereits zugestellt", damit es
|
|
73607
|
-
* nicht weiter retryt.
|
|
73606
|
+
* erneut gesendet — das Tool meldet dem Modell "bereits zugestellt", damit es
|
|
73607
|
+
* nicht weiter retryt. Bereits automatisch gelieferte Dateien werden aus
|
|
73608
|
+
* einem direkt folgenden manuellen Reply entfernt.
|
|
73608
73609
|
*/
|
|
73609
73610
|
const DUPLICATE_WINDOW_MS = 3e5;
|
|
73610
73611
|
const OUTBOX_TAIL_BYTES = 32768;
|
|
@@ -73629,13 +73630,49 @@ function isRecentDuplicate(chatId, text) {
|
|
|
73629
73630
|
} catch {}
|
|
73630
73631
|
return false;
|
|
73631
73632
|
}
|
|
73633
|
+
function recentlyDeliveredFiles(chatId, files) {
|
|
73634
|
+
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
73635
|
+
const wanted = new Map(files.map((file) => [resolve(file).toLowerCase(), file]));
|
|
73636
|
+
const delivered = /* @__PURE__ */ new Set();
|
|
73637
|
+
try {
|
|
73638
|
+
const file = outboxLog();
|
|
73639
|
+
const size = statSync(file).size;
|
|
73640
|
+
const raw = readFileSync(file, "utf8");
|
|
73641
|
+
const tail = size > OUTBOX_TAIL_BYTES ? raw.slice(raw.length - OUTBOX_TAIL_BYTES) : raw;
|
|
73642
|
+
const cutoff = Date.now() - DUPLICATE_WINDOW_MS;
|
|
73643
|
+
for (const line of tail.split("\n")) {
|
|
73644
|
+
if (line.trim().length === 0) continue;
|
|
73645
|
+
try {
|
|
73646
|
+
const entry = JSON.parse(line);
|
|
73647
|
+
if (String(entry.chat_id) !== String(chatId)) continue;
|
|
73648
|
+
if (entry.ts === void 0 || Date.parse(entry.ts) < cutoff || !Array.isArray(entry.files)) continue;
|
|
73649
|
+
for (const priorFile of entry.files) {
|
|
73650
|
+
if (typeof priorFile !== "string") continue;
|
|
73651
|
+
const original = wanted.get(resolve(priorFile).toLowerCase());
|
|
73652
|
+
if (original !== void 0) delivered.add(original);
|
|
73653
|
+
}
|
|
73654
|
+
} catch {}
|
|
73655
|
+
}
|
|
73656
|
+
} catch {}
|
|
73657
|
+
return delivered;
|
|
73658
|
+
}
|
|
73632
73659
|
async function runReply(api, args) {
|
|
73633
73660
|
const chatId = args["chat_id"];
|
|
73634
73661
|
const text = args["text"];
|
|
73635
73662
|
const replyTo = args["reply_to"] !== null && args["reply_to"] !== void 0 ? Number(args["reply_to"]) : void 0;
|
|
73636
|
-
|
|
73663
|
+
let files = args["files"] ?? [];
|
|
73637
73664
|
const parseMode = args["format"] === "markdownv2" ? "MarkdownV2" : void 0;
|
|
73638
73665
|
assertAllowedChat(chatId);
|
|
73666
|
+
const duplicateFiles = recentlyDeliveredFiles(chatId, files);
|
|
73667
|
+
if (duplicateFiles.size > 0) {
|
|
73668
|
+
files = files.filter((file) => !duplicateFiles.has(file));
|
|
73669
|
+
appendJsonl(outboxLog(), {
|
|
73670
|
+
direction: "out",
|
|
73671
|
+
kind: "reply-file-duplicate-suppressed",
|
|
73672
|
+
chat_id: chatId,
|
|
73673
|
+
files: [...duplicateFiles]
|
|
73674
|
+
});
|
|
73675
|
+
}
|
|
73639
73676
|
if (files.length === 0 && isGroupChat(chatId) && isGroupNoiseReply(text)) {
|
|
73640
73677
|
appendJsonl(outboxLog(), {
|
|
73641
73678
|
direction: "out",
|