blun-king-cli 9.1.49 → 9.1.51
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 +30 -6
- package/README.md +30 -6
- package/blun.mjs +538 -81
- package/package.json +1 -1
- package/telegram-plugin/dist/mcp-server.mjs +40 -3
package/blun.mjs
CHANGED
|
@@ -30172,7 +30172,8 @@ function parseEnvBudget(raw) {
|
|
|
30172
30172
|
*/
|
|
30173
30173
|
function computeCompletionBudgetCap(args) {
|
|
30174
30174
|
const maxCtx = args.capability?.max_context_tokens ?? 0;
|
|
30175
|
-
const
|
|
30175
|
+
const fallback = args.budget.fallback ?? DEFAULT_UNKNOWN_CONTEXT_FALLBACK;
|
|
30176
|
+
const cap = args.budget.hardCap ?? (maxCtx > 0 ? Math.min(maxCtx, fallback) : fallback);
|
|
30176
30177
|
return Math.max(MIN_FLOOR, cap);
|
|
30177
30178
|
}
|
|
30178
30179
|
/**
|
|
@@ -30197,7 +30198,7 @@ function applyCompletionBudgetWithDetails(args) {
|
|
|
30197
30198
|
});
|
|
30198
30199
|
if (args.retry !== void 0) cap = Math.max(args.retry.minimumCompletionTokens, Math.ceil(cap * args.retry.multiplier));
|
|
30199
30200
|
const maxContextTokens = args.capability?.max_context_tokens;
|
|
30200
|
-
if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens));
|
|
30201
|
+
if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens - COMPLETION_CONTEXT_SAFETY_MARGIN));
|
|
30201
30202
|
return {
|
|
30202
30203
|
provider: args.provider.withMaxCompletionTokens(cap, {
|
|
30203
30204
|
usedContextTokens: args.usedContextTokens,
|
|
@@ -30206,11 +30207,12 @@ function applyCompletionBudgetWithDetails(args) {
|
|
|
30206
30207
|
maxCompletionTokens: cap
|
|
30207
30208
|
};
|
|
30208
30209
|
}
|
|
30209
|
-
var MIN_FLOOR, DEFAULT_UNKNOWN_CONTEXT_FALLBACK, MIN_THINKING_COMPLETION_TOKENS;
|
|
30210
|
+
var MIN_FLOOR, DEFAULT_UNKNOWN_CONTEXT_FALLBACK, MIN_THINKING_COMPLETION_TOKENS, COMPLETION_CONTEXT_SAFETY_MARGIN;
|
|
30210
30211
|
var init_completion_budget = __esmMin((() => {
|
|
30211
30212
|
MIN_FLOOR = 1;
|
|
30212
30213
|
DEFAULT_UNKNOWN_CONTEXT_FALLBACK = 32e3;
|
|
30213
30214
|
MIN_THINKING_COMPLETION_TOKENS = 1024;
|
|
30215
|
+
COMPLETION_CONTEXT_SAFETY_MARGIN = 1e4;
|
|
30214
30216
|
}));
|
|
30215
30217
|
//#endregion
|
|
30216
30218
|
//#region ../../packages/agent-core/src/loop/retry.ts
|
|
@@ -30390,7 +30392,7 @@ var init_input_schema = __esmMin((() => {
|
|
|
30390
30392
|
//#region ../../packages/agent-core/src/tools/builtin/state/todo-list.md?raw
|
|
30391
30393
|
var todo_list_default;
|
|
30392
30394
|
var init_todo_list$1 = __esmMin((() => {
|
|
30393
|
-
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";
|
|
30394
30396
|
}));
|
|
30395
30397
|
//#endregion
|
|
30396
30398
|
//#region ../../packages/agent-core/src/tools/builtin/state/todo-list.ts
|
|
@@ -30405,6 +30407,9 @@ function statusMarker(status) {
|
|
|
30405
30407
|
case "pending": return "[pending]";
|
|
30406
30408
|
case "in_progress": return "[in_progress]";
|
|
30407
30409
|
case "done": return "[done]";
|
|
30410
|
+
case "blocked": return "[blocked]";
|
|
30411
|
+
case "waiting_approval": return "[waiting_approval]";
|
|
30412
|
+
case "aborted": return "[aborted]";
|
|
30408
30413
|
default: return status;
|
|
30409
30414
|
}
|
|
30410
30415
|
}
|
|
@@ -30415,13 +30420,16 @@ var init_todo_list = __esmMin((() => {
|
|
|
30415
30420
|
init_todo_list$1();
|
|
30416
30421
|
TODO_LIST_TOOL_NAME = "TodoList";
|
|
30417
30422
|
TODO_STORE_KEY = "todo";
|
|
30418
|
-
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.";
|
|
30419
30424
|
TodoItemSchema = object({
|
|
30420
30425
|
title: string().min(1).describe("Short, actionable title for the todo."),
|
|
30421
30426
|
status: _enum([
|
|
30422
30427
|
"pending",
|
|
30423
30428
|
"in_progress",
|
|
30424
|
-
"done"
|
|
30429
|
+
"done",
|
|
30430
|
+
"blocked",
|
|
30431
|
+
"waiting_approval",
|
|
30432
|
+
"aborted"
|
|
30425
30433
|
]).describe("Current status of the todo.")
|
|
30426
30434
|
});
|
|
30427
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.") });
|
|
@@ -76040,6 +76048,16 @@ function renderCronFireXml(origin, prompt) {
|
|
|
76040
76048
|
"</cron-fire>"
|
|
76041
76049
|
].join("\n");
|
|
76042
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
|
+
}
|
|
76043
76061
|
function stringAttr(value, fallback) {
|
|
76044
76062
|
if (typeof value !== "string" || value.length === 0) return fallback;
|
|
76045
76063
|
return value.replaceAll("&", "&").replaceAll("\"", """);
|
|
@@ -77101,7 +77119,7 @@ var init_manager$3 = __esmMin((() => {
|
|
|
77101
77119
|
};
|
|
77102
77120
|
const content = [{
|
|
77103
77121
|
type: "text",
|
|
77104
|
-
text: renderCronFireXml(origin, task.prompt)
|
|
77122
|
+
text: task.owner === "session-loop" ? renderSessionLoopWakeupXml(origin, task.prompt, task.loopInterval ?? "auto") : renderCronFireXml(origin, task.prompt)
|
|
77105
77123
|
}];
|
|
77106
77124
|
this.agent.emitEvent({
|
|
77107
77125
|
type: "cron.fired",
|
|
@@ -262017,8 +262035,9 @@ var init_blun_media$1 = __esmMin((() => {
|
|
|
262017
262035
|
GenerateImageInputSchema = object({ prompt: PromptSchema.describe("A complete visual description of the image to generate.") });
|
|
262018
262036
|
GenerateVideoInputSchema = object({
|
|
262019
262037
|
prompt: PromptSchema.describe("A complete visual description of the video or motion to generate."),
|
|
262020
|
-
image_id: MediaIdSchema.optional().describe("An optional completed PNG or JPEG media job id to animate into the video.")
|
|
262021
|
-
|
|
262038
|
+
image_id: MediaIdSchema.optional().describe("An optional completed PNG or JPEG media job id to animate into the video."),
|
|
262039
|
+
path: string().trim().min(1).optional().describe("An optional path to a local PNG or JPEG file to upload and animate. Relative paths resolve against the working directory.")
|
|
262040
|
+
}).refine((args) => !(args.image_id && args.path), { message: "Provide either image_id or path, not both." });
|
|
262022
262041
|
GenerateSpeechInputSchema = object({ input: PromptSchema.describe("The exact text to synthesize as speech.") });
|
|
262023
262042
|
GetMediaInputSchema = object({ id: MediaIdSchema.describe("The media job id returned by a generation tool.") });
|
|
262024
262043
|
UnderstandImageInputSchema = object({
|
|
@@ -262092,11 +262111,79 @@ var init_blun_media$1 = __esmMin((() => {
|
|
|
262092
262111
|
}
|
|
262093
262112
|
};
|
|
262094
262113
|
GenerateVideoTool = class extends MediaGenerationTool {
|
|
262114
|
+
kaos;
|
|
262115
|
+
workspace;
|
|
262095
262116
|
name = "GenerateVideo";
|
|
262096
|
-
description = "Generate a new video from text
|
|
262117
|
+
description = "Generate a new video from text, animate an existing completed image job, or animate a local PNG/JPEG file with BLUN media models. Pass image_id for an existing media job or path for a local or Telegram-provided image. Do not use this for understanding an existing video. This starts a billed asynchronous media job.";
|
|
262097
262118
|
parameters = toInputJsonSchema(GenerateVideoInputSchema);
|
|
262119
|
+
constructor(provider, kaos, workspace) {
|
|
262120
|
+
super(provider);
|
|
262121
|
+
this.kaos = kaos;
|
|
262122
|
+
this.workspace = workspace;
|
|
262123
|
+
}
|
|
262098
262124
|
subject(args) {
|
|
262099
|
-
return args.image_id ? `${args.image_id}: ${args.prompt}` : args.prompt;
|
|
262125
|
+
return args.path ? `${args.path}: ${args.prompt}` : args.image_id ? `${args.image_id}: ${args.prompt}` : args.prompt;
|
|
262126
|
+
}
|
|
262127
|
+
resolveExecution(args) {
|
|
262128
|
+
if (!args.path) return super.resolveExecution(args);
|
|
262129
|
+
const path = resolvePathAccessPath(args.path, {
|
|
262130
|
+
kaos: this.kaos,
|
|
262131
|
+
workspace: this.workspace,
|
|
262132
|
+
operation: "read"
|
|
262133
|
+
});
|
|
262134
|
+
return {
|
|
262135
|
+
accesses: ToolAccesses.readFile(path),
|
|
262136
|
+
description: `${this.name}: ${args.path}`,
|
|
262137
|
+
display: {
|
|
262138
|
+
kind: "file_io",
|
|
262139
|
+
operation: "read",
|
|
262140
|
+
path
|
|
262141
|
+
},
|
|
262142
|
+
approvalRule: literalRulePattern(this.name, path),
|
|
262143
|
+
matchesRule: (ruleArgs) => matchesPathRuleSubject(ruleArgs, path, {
|
|
262144
|
+
cwd: this.workspace.workspaceDir,
|
|
262145
|
+
pathClass: this.kaos.pathClass(),
|
|
262146
|
+
homeDir: this.kaos.gethome()
|
|
262147
|
+
}),
|
|
262148
|
+
execute: (ctx) => this.executionFromPath(args, path, ctx)
|
|
262149
|
+
};
|
|
262150
|
+
}
|
|
262151
|
+
async executionFromPath(args, safePath, ctx) {
|
|
262152
|
+
emitMediaProgress(ctx, this.name, "uploading");
|
|
262153
|
+
try {
|
|
262154
|
+
const fileType = detectFileType(safePath, await this.kaos.readBytes(safePath, 512), "media");
|
|
262155
|
+
if (fileType.mimeType !== "image/png" && fileType.mimeType !== "image/jpeg") return {
|
|
262156
|
+
isError: true,
|
|
262157
|
+
output: "Image-to-video accepts only PNG or JPEG files."
|
|
262158
|
+
};
|
|
262159
|
+
const data = await this.kaos.readBytes(safePath);
|
|
262160
|
+
if (data.byteLength === 0) return {
|
|
262161
|
+
isError: true,
|
|
262162
|
+
output: `"${args.path}" is empty.`
|
|
262163
|
+
};
|
|
262164
|
+
const options = {
|
|
262165
|
+
signal: ctx.signal,
|
|
262166
|
+
toolCallId: ctx.toolCallId
|
|
262167
|
+
};
|
|
262168
|
+
const upload = await this.provider.uploadMedia(data, fileType.mimeType, options);
|
|
262169
|
+
emitMediaProgress(ctx, this.name, "submitting", { sourceId: upload.id });
|
|
262170
|
+
const job = await this.provider.generateVideo(args.prompt, options, upload.id);
|
|
262171
|
+
emitMediaProgress(ctx, this.name, job.status, {
|
|
262172
|
+
id: job.id,
|
|
262173
|
+
sourceId: upload.id,
|
|
262174
|
+
...job.progress
|
|
262175
|
+
});
|
|
262176
|
+
return {
|
|
262177
|
+
output: `Media job ${job.id} accepted with status ${job.status}. ${this.requestNote}`,
|
|
262178
|
+
isError: false
|
|
262179
|
+
};
|
|
262180
|
+
} catch (error) {
|
|
262181
|
+
emitMediaProgress(ctx, this.name, "failed", { error: errorMessage$10(error) });
|
|
262182
|
+
return {
|
|
262183
|
+
isError: true,
|
|
262184
|
+
output: `Media request failed: ${errorMessage$10(error)}`
|
|
262185
|
+
};
|
|
262186
|
+
}
|
|
262100
262187
|
}
|
|
262101
262188
|
submit(args, options) {
|
|
262102
262189
|
return this.provider.generateVideo(args.prompt, options, args.image_id);
|
|
@@ -262764,7 +262851,7 @@ var init_tool$1 = __esmMin((() => {
|
|
|
262764
262851
|
toolServices?.webSearcher && new WebSearchTool(toolServices.webSearcher),
|
|
262765
262852
|
toolServices?.urlFetcher && new FetchURLTool(toolServices.urlFetcher),
|
|
262766
262853
|
toolServices?.media && new GenerateImageTool(toolServices.media),
|
|
262767
|
-
toolServices?.media && new GenerateVideoTool(toolServices.media),
|
|
262854
|
+
toolServices?.media && new GenerateVideoTool(toolServices.media, kaos, workspace),
|
|
262768
262855
|
toolServices?.media && new GenerateSpeechTool(toolServices.media),
|
|
262769
262856
|
toolServices?.media && new UnderstandImageTool(toolServices.media, kaos, workspace),
|
|
262770
262857
|
toolServices?.media && new UnderstandVideoTool(toolServices.media),
|
|
@@ -310846,31 +310933,36 @@ function mediaPayloadNumberArray(payload, keys) {
|
|
|
310846
310933
|
}
|
|
310847
310934
|
}
|
|
310848
310935
|
function mediaProgressFromPayload(payload) {
|
|
310849
|
-
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"]);
|
|
310850
310942
|
const percent = rawPercent === void 0 ? void 0 : rawPercent <= 1 ? rawPercent * 100 : rawPercent;
|
|
310851
310943
|
const progress = {
|
|
310852
|
-
phaseLabel: mediaPayloadText(
|
|
310944
|
+
phaseLabel: mediaPayloadText(source, ["phase", "stage", "step_name", "stepName"]),
|
|
310853
310945
|
percent: percent === void 0 ? void 0 : Math.min(100, Math.max(0, percent)),
|
|
310854
|
-
currentFrame: mediaPayloadNumber(
|
|
310855
|
-
totalFrames: mediaPayloadNumber(
|
|
310856
|
-
fps: mediaPayloadNumber(
|
|
310857
|
-
currentStep: mediaPayloadNumber(
|
|
310858
|
-
totalSteps: mediaPayloadNumber(
|
|
310859
|
-
currentSegment: mediaPayloadNumber(
|
|
310860
|
-
totalSegments: mediaPayloadNumber(
|
|
310861
|
-
generatedSeconds: mediaPayloadNumber(
|
|
310862
|
-
queuePosition: mediaPayloadNumber(
|
|
310863
|
-
elapsedSeconds: mediaPayloadNumber(
|
|
310864
|
-
remainingSeconds: mediaPayloadNumber(
|
|
310865
|
-
durationSeconds: mediaPayloadNumber(
|
|
310866
|
-
width: mediaPayloadNumber(
|
|
310867
|
-
height: mediaPayloadNumber(
|
|
310868
|
-
previewUrl: mediaPayloadText(
|
|
310869
|
-
previewDataBase64: mediaPayloadText(
|
|
310870
|
-
previewMimeType: mediaPayloadText(
|
|
310871
|
-
sampleUrl: mediaPayloadText(
|
|
310872
|
-
localPath: mediaPayloadText(
|
|
310873
|
-
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"])
|
|
310874
310966
|
};
|
|
310875
310967
|
return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
|
|
310876
310968
|
}
|
|
@@ -334380,8 +334472,9 @@ function toolResultToSessionUpdate(sessionId, event) {
|
|
|
334380
334472
|
* Mapping rules (anchored at types.gen.d.ts:3530-3569 / :4849):
|
|
334381
334473
|
* - The `todo_list` input-display block carries
|
|
334382
334474
|
* `items: { title, status }[]` (schemas.ts:60). The status is the
|
|
334383
|
-
*
|
|
334384
|
-
* `pending` | `in_progress` | `done
|
|
334475
|
+
* TodoStatus union (todo-list.ts:26):
|
|
334476
|
+
* `pending` | `in_progress` | `done` | `blocked` |
|
|
334477
|
+
* `waiting_approval` | `aborted`.
|
|
334385
334478
|
* - ACP {@link PlanEntryStatus} is `pending` | `in_progress` | `completed`,
|
|
334386
334479
|
* so `done` rewrites to `completed`. Anything outside the known
|
|
334387
334480
|
* enum lands on `pending` as a safe default — we never want a
|
|
@@ -334415,6 +334508,9 @@ function mapTodoStatus(status) {
|
|
|
334415
334508
|
case "in_progress": return "in_progress";
|
|
334416
334509
|
case "done":
|
|
334417
334510
|
case "completed": return "completed";
|
|
334511
|
+
case "blocked":
|
|
334512
|
+
case "waiting_approval":
|
|
334513
|
+
case "aborted": return "pending";
|
|
334418
334514
|
default: return "pending";
|
|
334419
334515
|
}
|
|
334420
334516
|
}
|
|
@@ -401290,6 +401386,21 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
|
|
|
401290
401386
|
return "idle-only";
|
|
401291
401387
|
}
|
|
401292
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
|
+
},
|
|
401293
401404
|
{
|
|
401294
401405
|
name: "loop",
|
|
401295
401406
|
aliases: [],
|
|
@@ -410038,6 +410149,16 @@ function trimPartialClosingFences(tokens) {
|
|
|
410038
410149
|
if (!marker || !lastLine || lastLine.length >= marker.length || lastLine !== marker[0]?.repeat(lastLine.length)) return;
|
|
410039
410150
|
token.text = token.text.slice(0, -lastLine.length).replace(/\n$/, "");
|
|
410040
410151
|
}
|
|
410152
|
+
function terminalHyperlinkTarget(value) {
|
|
410153
|
+
const candidate = value.trim();
|
|
410154
|
+
if (/^https?:\/\//iu.test(candidate) || /^file:\/\//iu.test(candidate)) try {
|
|
410155
|
+
const parsed = new URL(candidate);
|
|
410156
|
+
if (["http:", "https:", "file:"].includes(parsed.protocol)) return parsed.href;
|
|
410157
|
+
} catch {
|
|
410158
|
+
return;
|
|
410159
|
+
}
|
|
410160
|
+
if (/^[A-Za-z]:[\\/]/u.test(candidate)) return pathToFileURL(candidate).href;
|
|
410161
|
+
}
|
|
410041
410162
|
const markdownParser = new q();
|
|
410042
410163
|
markdownParser.setOptions({ tokenizer: new StrictStrikethroughTokenizer() });
|
|
410043
410164
|
var Markdown = class {
|
|
@@ -410291,7 +410412,11 @@ var Markdown = class {
|
|
|
410291
410412
|
break;
|
|
410292
410413
|
}
|
|
410293
410414
|
case "codespan":
|
|
410294
|
-
|
|
410415
|
+
{
|
|
410416
|
+
const styledCode = this.theme.code(token.text);
|
|
410417
|
+
const linkTarget = terminalHyperlinkTarget(token.text);
|
|
410418
|
+
result += (linkTarget !== void 0 && getCapabilities().hyperlinks ? hyperlink(styledCode, linkTarget) : styledCode) + stylePrefix;
|
|
410419
|
+
}
|
|
410295
410420
|
break;
|
|
410296
410421
|
case "link": {
|
|
410297
410422
|
const linkText = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);
|
|
@@ -411788,7 +411913,7 @@ function isTodoItemShape(value) {
|
|
|
411788
411913
|
if (typeof value !== "object" || value === null) return false;
|
|
411789
411914
|
const rec = value;
|
|
411790
411915
|
if (typeof rec.title !== "string" || rec.title.length === 0) return false;
|
|
411791
|
-
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";
|
|
411792
411917
|
}
|
|
411793
411918
|
const PUBLIC_IDENTITY_ASSIGNMENT_RE = /\b(provider|model)(["']?\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^,;\s)\]}]+)/giu;
|
|
411794
411919
|
/** Project structured runtime identity fields onto the public BLUN identity. */
|
|
@@ -412574,6 +412699,7 @@ var ChoicePickerComponent = class extends Container {
|
|
|
412574
412699
|
const descriptionColor = isSelected && opt.descriptionTone !== void 0 ? opt.descriptionTone : "textMuted";
|
|
412575
412700
|
for (const descLine of wrapDescription$1(opt.description, descriptionWidth)) lines.push(currentTheme.fg(descriptionColor, ` ${descLine}`));
|
|
412576
412701
|
}
|
|
412702
|
+
if (this.opts.spaced === true && i < view.page.end - 1) lines.push("");
|
|
412577
412703
|
}
|
|
412578
412704
|
lines.push("");
|
|
412579
412705
|
if (view.page.pageCount > 1) lines.push(currentTheme.fg("textMuted", ` ${uiText("common.pageCount", {
|
|
@@ -416564,6 +416690,19 @@ function bufferContext(state, chatId, tag) {
|
|
|
416564
416690
|
function channelOrigin(envelope) {
|
|
416565
416691
|
return `Telegram · ${envelope.meta.user ?? envelope.meta.chat_id}`;
|
|
416566
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
|
+
}
|
|
416567
416706
|
/**
|
|
416568
416707
|
* Route one envelope into the visible session. Unaddressed group traffic is
|
|
416569
416708
|
* shown and buffered per chat without a model turn. Addressed traffic queues
|
|
@@ -419991,6 +420130,51 @@ async function handleGoalCommand(host, args) {
|
|
|
419991
420130
|
return;
|
|
419992
420131
|
}
|
|
419993
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
|
+
}
|
|
419994
420178
|
function parseNextGoalCommand(tokens) {
|
|
419995
420179
|
if (tokens.length === 2 && tokens[1]?.toLowerCase() === "manage") return { kind: "next-manage" };
|
|
419996
420180
|
let index = 1;
|
|
@@ -420133,7 +420317,7 @@ async function createGoal(host, parsed, rawArgs, options = {}) {
|
|
|
420133
420317
|
return startGoal(host, parsed, options);
|
|
420134
420318
|
}
|
|
420135
420319
|
function showGoalStartPermissionPrompt(host, parsed, rawArgs, options) {
|
|
420136
|
-
const commandText =
|
|
420320
|
+
const commandText = `/${options.commandName ?? "goal"} ${rawArgs.trim()}`;
|
|
420137
420321
|
const cancelStart = () => {
|
|
420138
420322
|
host.restoreInputText(commandText);
|
|
420139
420323
|
host.showStatus(uiText("goal.start.notStarted"));
|
|
@@ -420170,6 +420354,7 @@ async function startGoal(host, parsed, options) {
|
|
|
420170
420354
|
try {
|
|
420171
420355
|
await host.requireSession().createGoal({
|
|
420172
420356
|
objective: parsed.objective,
|
|
420357
|
+
completionCriterion: parsed.completionCriterion,
|
|
420173
420358
|
replace: parsed.replace
|
|
420174
420359
|
});
|
|
420175
420360
|
} catch (error) {
|
|
@@ -420244,6 +420429,11 @@ async function showGoalStatus(host) {
|
|
|
420244
420429
|
host.showStatus(uiText("goal.status.noneSet"));
|
|
420245
420430
|
return;
|
|
420246
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
|
+
}
|
|
420247
420437
|
host.state.transcriptContainer.addChild(new GoalStatusMessageComponent(goal));
|
|
420248
420438
|
host.state.ui.requestRender();
|
|
420249
420439
|
}
|
|
@@ -420395,6 +420585,7 @@ async function handleLoopCommand(host, args) {
|
|
|
420395
420585
|
switch (parsed.kind) {
|
|
420396
420586
|
case "status": {
|
|
420397
420587
|
const { loop } = await session.getLoop();
|
|
420588
|
+
host.setAppState?.({ loop });
|
|
420398
420589
|
host.track("loop_status", { status: loop?.status ?? "none" });
|
|
420399
420590
|
host.showStatus(`/loop ${loop?.status ?? "none"}`);
|
|
420400
420591
|
return;
|
|
@@ -420404,6 +420595,7 @@ async function handleLoopCommand(host, args) {
|
|
|
420404
420595
|
interval: parsed.interval,
|
|
420405
420596
|
prompt: parsed.prompt
|
|
420406
420597
|
});
|
|
420598
|
+
host.setAppState?.({ loop });
|
|
420407
420599
|
host.track("loop_start", { interval: loop.interval });
|
|
420408
420600
|
host.showStatus(`/loop ${loop.status}`);
|
|
420409
420601
|
host.sendNormalUserInput(loop.prompt);
|
|
@@ -420411,6 +420603,7 @@ async function handleLoopCommand(host, args) {
|
|
|
420411
420603
|
}
|
|
420412
420604
|
case "pause": {
|
|
420413
420605
|
const loop = await session.pauseLoop();
|
|
420606
|
+
host.setAppState?.({ loop });
|
|
420414
420607
|
if (isStreaming(host)) await session.cancel();
|
|
420415
420608
|
host.track("loop_pause");
|
|
420416
420609
|
host.showStatus(`/loop ${loop.status}`);
|
|
@@ -420418,12 +420611,14 @@ async function handleLoopCommand(host, args) {
|
|
|
420418
420611
|
}
|
|
420419
420612
|
case "resume": {
|
|
420420
420613
|
const loop = await session.resumeLoop();
|
|
420614
|
+
host.setAppState?.({ loop });
|
|
420421
420615
|
host.track("loop_resume");
|
|
420422
420616
|
host.showStatus(`/loop ${loop.status}`);
|
|
420423
420617
|
return;
|
|
420424
420618
|
}
|
|
420425
420619
|
case "stop":
|
|
420426
420620
|
await session.stopLoop();
|
|
420621
|
+
host.setAppState?.({ loop: null });
|
|
420427
420622
|
if (isStreaming(host)) await session.cancel();
|
|
420428
420623
|
host.track("loop_stop");
|
|
420429
420624
|
host.showStatus("/loop stopped");
|
|
@@ -492994,6 +493189,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
492994
493189
|
case "goal":
|
|
492995
493190
|
await handleGoalCommand(host, args);
|
|
492996
493191
|
return;
|
|
493192
|
+
case "idea":
|
|
493193
|
+
await handleIdeaCommand(host, args);
|
|
493194
|
+
return;
|
|
492997
493195
|
case "loop":
|
|
492998
493196
|
await handleLoopCommand(host, args);
|
|
492999
493197
|
return;
|
|
@@ -498171,22 +498369,25 @@ function mediaActivityCanonicalPhase(value) {
|
|
|
498171
498369
|
}
|
|
498172
498370
|
function mediaActivityPhaseSequence(mediaKind) {
|
|
498173
498371
|
switch (mediaActivityStatusKey(mediaKind)) {
|
|
498174
|
-
case "image": return ["queued", "generating", "upscaling", "saving"];
|
|
498175
|
-
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"];
|
|
498176
498374
|
case "voice": return ["queued", "processing", "saving"];
|
|
498177
|
-
case "dubbing": return ["queued", "audio-analysis", "rendering", "saving"];
|
|
498178
|
-
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"];
|
|
498179
498377
|
case "image-analysis":
|
|
498180
|
-
case "video-analysis": return ["queued", "analyzing"];
|
|
498378
|
+
case "video-analysis": return ["queued", "processing", "analyzing"];
|
|
498181
498379
|
default: return ["queued", "processing", "saving"];
|
|
498182
498380
|
}
|
|
498183
498381
|
}
|
|
498184
498382
|
function mediaActivityChainStates(job) {
|
|
498185
|
-
const
|
|
498186
|
-
const
|
|
498187
|
-
|
|
498383
|
+
const sequence = mediaActivityPhaseSequence(job.mediaKind);
|
|
498384
|
+
const normalize = (value) => mediaActivityCanonicalPhase(value);
|
|
498385
|
+
const current = normalize(job.phaseLabel ?? job.phase);
|
|
498386
|
+
const currentIndex = sequence.indexOf(current);
|
|
498387
|
+
const history = new Set((job.phaseHistory ?? []).map(normalize));
|
|
498388
|
+
return sequence.map((phase, index) => ({
|
|
498188
498389
|
phase,
|
|
498189
|
-
state: phase === current ? "active" : history.has(phase) ? "done" : "pending"
|
|
498390
|
+
state: phase === current ? "active" : history.has(phase) || currentIndex > index ? "done" : "pending"
|
|
498190
498391
|
}));
|
|
498191
498392
|
}
|
|
498192
498393
|
function renderMediaWaveform(samples, width) {
|
|
@@ -498340,25 +498541,42 @@ var MediaActivityComponent = class {
|
|
|
498340
498541
|
const percent = Math.min(100, Math.max(0, job.percent));
|
|
498341
498542
|
progress = includeBar ? ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %` : ` · ${String(Math.round(percent))} %`;
|
|
498342
498543
|
}
|
|
498343
|
-
const line = `${currentTheme.boldFg("primary", kind)} · ${phase}${details.length === 0 ? "" : ` · ${details.join(" · ")}`}${progress}`;
|
|
498544
|
+
const line = `${currentTheme.boldFg("primary", "●")} ${currentTheme.boldFg("primary", kind)} · ${phase}${details.length === 0 ? "" : ` · ${details.join(" · ")}`}${progress}`;
|
|
498344
498545
|
return truncateToWidth(line, Math.max(1, width), "…");
|
|
498345
498546
|
}
|
|
498346
498547
|
chainLine(job, width) {
|
|
498347
498548
|
const chain = mediaActivityChainStates(job).map(({ phase, state }) => {
|
|
498348
|
-
const symbol = state === "done" ? "
|
|
498549
|
+
const symbol = state === "done" ? "■" : state === "active" ? "▶" : "□";
|
|
498349
498550
|
const label = mediaActivityLabel("media.phase", phase, phase);
|
|
498350
|
-
|
|
498551
|
+
const text = `${symbol} ${label}`;
|
|
498552
|
+
if (state === "done") return currentTheme.fg("success", text);
|
|
498553
|
+
if (state === "active") return currentTheme.boldFg("primary", text);
|
|
498554
|
+
return currentTheme.fg("textDim", text);
|
|
498351
498555
|
});
|
|
498352
|
-
return truncateToWidth(` ${mediaUiText("media.activity.chain")} · ${chain.join(" · ")}`, Math.max(1, width), "…");
|
|
498556
|
+
return truncateToWidth(` ${currentTheme.fg("textDim", mediaUiText("media.activity.chain"))} · ${chain.join(currentTheme.fg("textDim", " · "))}`, Math.max(1, width), "…");
|
|
498353
498557
|
}
|
|
498354
498558
|
previewImage(job) {
|
|
498355
|
-
|
|
498356
|
-
|
|
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;
|
|
498357
498569
|
if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(mimeType)) return;
|
|
498358
|
-
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)}`;
|
|
498359
498573
|
let image = this.previewImages.get(key);
|
|
498360
498574
|
if (image === void 0) {
|
|
498361
|
-
|
|
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) }, {
|
|
498362
498580
|
maxHeightCells: 5,
|
|
498363
498581
|
maxWidthCells: 24,
|
|
498364
498582
|
filename: mediaUiText("media.detail.preview")
|
|
@@ -504742,6 +504960,9 @@ var SessionEventHandler = class {
|
|
|
504742
504960
|
stale: event.origin.stale
|
|
504743
504961
|
}
|
|
504744
504962
|
});
|
|
504963
|
+
setTimeout(() => {
|
|
504964
|
+
this.host.refreshLoopState();
|
|
504965
|
+
}, 0);
|
|
504745
504966
|
}
|
|
504746
504967
|
handleTurnEnd(event, sendQueued) {
|
|
504747
504968
|
this.turnWatchdog.stop();
|
|
@@ -507190,6 +507411,30 @@ function formatGoalBadge(goal, colors, wallClockMs) {
|
|
|
507190
507411
|
const label = `${uiText(`goal.panel.status.${goal.status}`)} · ${formatBadgeElapsed(wallClockMs ?? goal.wallClockMs)} · ${turns}`;
|
|
507191
507412
|
return chalk.hex(colors.textMuted)(`[${uiText("footer.goal.label")} `) + chalk.hex(dotColor)("●") + chalk.hex(colors.textMuted)(` ${label}]`);
|
|
507192
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
|
+
}
|
|
507193
507438
|
function formatBadgeElapsed(ms) {
|
|
507194
507439
|
const totalSeconds = Math.round(ms / 1e3);
|
|
507195
507440
|
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
@@ -507368,6 +507613,7 @@ var FooterComponent = class {
|
|
|
507368
507613
|
goalSnapshotKey = null;
|
|
507369
507614
|
goalObservedAtMs = Date.now();
|
|
507370
507615
|
goalTimer = null;
|
|
507616
|
+
loopTimer = null;
|
|
507371
507617
|
compactionAttemptStartedAtMs = null;
|
|
507372
507618
|
compactionEstimatedInputTokens;
|
|
507373
507619
|
compactionProgress;
|
|
@@ -507389,6 +507635,7 @@ var FooterComponent = class {
|
|
|
507389
507635
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
|
|
507390
507636
|
this.syncGoalClock(state.goal);
|
|
507391
507637
|
this.syncGoalTimer(state.goal);
|
|
507638
|
+
this.syncLoopTimer(state.loop);
|
|
507392
507639
|
if (state.isCompacting) this.startCompaction();
|
|
507393
507640
|
}
|
|
507394
507641
|
setState(state) {
|
|
@@ -507398,6 +507645,7 @@ var FooterComponent = class {
|
|
|
507398
507645
|
}
|
|
507399
507646
|
this.syncGoalClock(state.goal);
|
|
507400
507647
|
this.syncGoalTimer(state.goal);
|
|
507648
|
+
this.syncLoopTimer(state.loop);
|
|
507401
507649
|
this.state = state;
|
|
507402
507650
|
if (state.isCompacting && this.compactionAttemptStartedAtMs === null) this.startCompaction();
|
|
507403
507651
|
else if (!state.isCompacting) this.finishCompaction();
|
|
@@ -507464,6 +507712,8 @@ var FooterComponent = class {
|
|
|
507464
507712
|
if (modes.length > 0) left.push(modes.join(" "));
|
|
507465
507713
|
const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
|
|
507466
507714
|
if (goalBadge !== null) left.push(goalBadge);
|
|
507715
|
+
const loopBadge = formatLoopBadge(state.loop, colors);
|
|
507716
|
+
if (loopBadge !== null) left.push(loopBadge);
|
|
507467
507717
|
const model = modelDisplayName(state);
|
|
507468
507718
|
if (model) {
|
|
507469
507719
|
const sep = chalk.hex(colors.textMuted)(" · ");
|
|
@@ -507528,11 +507778,29 @@ var FooterComponent = class {
|
|
|
507528
507778
|
this.goalTimer = null;
|
|
507529
507779
|
}
|
|
507530
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
|
+
}
|
|
507531
507795
|
dispose() {
|
|
507532
507796
|
if (this.goalTimer !== null) {
|
|
507533
507797
|
clearInterval(this.goalTimer);
|
|
507534
507798
|
this.goalTimer = null;
|
|
507535
507799
|
}
|
|
507800
|
+
if (this.loopTimer !== null) {
|
|
507801
|
+
clearInterval(this.loopTimer);
|
|
507802
|
+
this.loopTimer = null;
|
|
507803
|
+
}
|
|
507536
507804
|
this.finishCompaction();
|
|
507537
507805
|
}
|
|
507538
507806
|
goalWallClockMs(goal) {
|
|
@@ -510301,6 +510569,8 @@ const MAX_VISIBLE = 5;
|
|
|
510301
510569
|
const BLUN_BLUE = "#0261ff";
|
|
510302
510570
|
const BLUN_BRIGHT = "#4f8dff";
|
|
510303
510571
|
const DONE_GREEN = "#22c55e";
|
|
510572
|
+
const BLOCKED_RED = "#ef4444";
|
|
510573
|
+
const WAITING_AMBER = "#f59e0b";
|
|
510304
510574
|
const BOX_EMPTY = "#2a3550";
|
|
510305
510575
|
const PENDING_TEXT = "#aeb8c6";
|
|
510306
510576
|
const LEFT_BAR = "▎";
|
|
@@ -510314,7 +510584,9 @@ const LEFT_BAR = "▎";
|
|
|
510314
510584
|
*
|
|
510315
510585
|
* Strategy:
|
|
510316
510586
|
* 1. Include every `in_progress` item (capped at MAX_VISIBLE).
|
|
510317
|
-
* 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`
|
|
510318
510590
|
* items in their original positions — while reserving one slot for
|
|
510319
510591
|
* "what just finished" — the latest `done` item — when both kinds
|
|
510320
510592
|
* exist. If one side has too few candidates, the other expands.
|
|
@@ -510328,17 +510600,26 @@ function selectVisibleTodos(todos) {
|
|
|
510328
510600
|
hiddenCounts: {
|
|
510329
510601
|
done: 0,
|
|
510330
510602
|
in_progress: 0,
|
|
510331
|
-
pending: 0
|
|
510603
|
+
pending: 0,
|
|
510604
|
+
blocked: 0,
|
|
510605
|
+
waiting_approval: 0,
|
|
510606
|
+
aborted: 0
|
|
510332
510607
|
}
|
|
510333
510608
|
};
|
|
510334
510609
|
const inProgress = [];
|
|
510335
510610
|
const pending = [];
|
|
510336
510611
|
const done = [];
|
|
510612
|
+
const attention = [];
|
|
510337
510613
|
for (const [i, todo] of todos.entries()) if (todo.status === "in_progress") inProgress.push(i);
|
|
510338
510614
|
else if (todo.status === "pending") pending.push(i);
|
|
510339
|
-
else done.push(i);
|
|
510615
|
+
else if (todo.status === "done") done.push(i);
|
|
510616
|
+
else attention.push(i);
|
|
510340
510617
|
const picked = /* @__PURE__ */ new Set();
|
|
510341
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
|
+
}
|
|
510342
510623
|
if (picked.size < MAX_VISIBLE) {
|
|
510343
510624
|
const doneCandidates = done.toReversed();
|
|
510344
510625
|
const pendingCandidates = pending;
|
|
@@ -510363,7 +510644,10 @@ function selectVisibleTodos(todos) {
|
|
|
510363
510644
|
const hiddenCounts = {
|
|
510364
510645
|
done: 0,
|
|
510365
510646
|
in_progress: 0,
|
|
510366
|
-
pending: 0
|
|
510647
|
+
pending: 0,
|
|
510648
|
+
blocked: 0,
|
|
510649
|
+
waiting_approval: 0,
|
|
510650
|
+
aborted: 0
|
|
510367
510651
|
};
|
|
510368
510652
|
for (const [i, todo] of todos.entries()) if (!picked.has(i)) hiddenCounts[todo.status] += 1;
|
|
510369
510653
|
return {
|
|
@@ -510440,6 +510724,9 @@ function renderRow(todo, width) {
|
|
|
510440
510724
|
return truncateToWidth(leftPart + chalk.hex("#ffffff")(title) + " ".repeat(gap) + chalk.hex(currentTheme.palette.textDim)(runningLabel), width);
|
|
510441
510725
|
}
|
|
510442
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);
|
|
510443
510730
|
return " " + chalk.hex(BOX_EMPTY)("☐") + " " + chalk.hex(PENDING_TEXT)(todo.title);
|
|
510444
510731
|
}
|
|
510445
510732
|
//#endregion
|
|
@@ -511394,6 +511681,22 @@ function outboxGrewForChat(marker, chatId) {
|
|
|
511394
511681
|
} catch {}
|
|
511395
511682
|
return false;
|
|
511396
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
|
+
}
|
|
511397
511700
|
const TELEGRAM_TEXT_LIMIT = 4096;
|
|
511398
511701
|
const TELEGRAM_ATTACHMENT_LIMIT = 50 * 1024 * 1024;
|
|
511399
511702
|
function mediaTelegramTarget(filePath) {
|
|
@@ -512121,19 +512424,20 @@ function promptStartupWorkspaceChoice(host, currentPath, copy) {
|
|
|
512121
512424
|
};
|
|
512122
512425
|
host.mountEditorReplacement(new ChoicePickerComponent({
|
|
512123
512426
|
title: copy.choiceTitle,
|
|
512427
|
+
spaced: true,
|
|
512124
512428
|
options: [
|
|
512125
512429
|
{
|
|
512126
512430
|
value: "use",
|
|
512127
|
-
label: copy.useCurrent
|
|
512431
|
+
label: `1. ${copy.useCurrent}`,
|
|
512128
512432
|
description: currentPath
|
|
512129
512433
|
},
|
|
512130
512434
|
{
|
|
512131
512435
|
value: "change",
|
|
512132
|
-
label: copy.chooseOther
|
|
512436
|
+
label: `2. ${copy.chooseOther}`
|
|
512133
512437
|
},
|
|
512134
512438
|
{
|
|
512135
512439
|
value: "exit",
|
|
512136
|
-
label: copy.exit
|
|
512440
|
+
label: `3. ${copy.exit}`,
|
|
512137
512441
|
tone: "danger"
|
|
512138
512442
|
}
|
|
512139
512443
|
],
|
|
@@ -512547,6 +512851,7 @@ function createInitialAppState(input) {
|
|
|
512547
512851
|
availableProviders: {},
|
|
512548
512852
|
sessionTitle: null,
|
|
512549
512853
|
goal: null,
|
|
512854
|
+
loop: null,
|
|
512550
512855
|
mcpServersSummary: null,
|
|
512551
512856
|
banner: void 0
|
|
512552
512857
|
};
|
|
@@ -512588,6 +512893,7 @@ var BlunTUI = class {
|
|
|
512588
512893
|
startupLoginRequired = false;
|
|
512589
512894
|
startupWorkspaceSelectionPending = false;
|
|
512590
512895
|
startupGoalPromptedSessionId;
|
|
512896
|
+
startupPhaseMs = {};
|
|
512591
512897
|
lastActivityMode;
|
|
512592
512898
|
currentLoadingTip = void 0;
|
|
512593
512899
|
mediaActivityStore = new MediaActivityStore();
|
|
@@ -512621,6 +512927,17 @@ var BlunTUI = class {
|
|
|
512621
512927
|
track(event, properties) {
|
|
512622
512928
|
this.harness.track(event, properties);
|
|
512623
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
|
+
}
|
|
512624
512941
|
constructor(harness, startupInput) {
|
|
512625
512942
|
this.harness = harness;
|
|
512626
512943
|
const initialAppState = createInitialAppState(startupInput);
|
|
@@ -512982,9 +513299,12 @@ var BlunTUI = class {
|
|
|
512982
513299
|
this.showStatus(warning, "warning");
|
|
512983
513300
|
}
|
|
512984
513301
|
async init() {
|
|
512985
|
-
|
|
512986
|
-
await this.
|
|
512987
|
-
|
|
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());
|
|
512988
513308
|
if (accountContext.kind !== "ready") {
|
|
512989
513309
|
if (accountContext.kind === "unauthenticated") {
|
|
512990
513310
|
this.authFlow.enterLoginRequiredStartupState();
|
|
@@ -512992,8 +513312,7 @@ var BlunTUI = class {
|
|
|
512992
513312
|
} else this.authFlow.enterManagedAccountUnavailableStartupState(accountContext.kind);
|
|
512993
513313
|
return false;
|
|
512994
513314
|
}
|
|
512995
|
-
|
|
512996
|
-
await this.refreshProviderModelsBeforeSession();
|
|
513315
|
+
await this.measureStartupPhase("provider_models_ms", () => this.refreshProviderModelsBeforeSession());
|
|
512997
513316
|
const { startup } = this.options;
|
|
512998
513317
|
const { workDir } = this.state.appState;
|
|
512999
513318
|
let session;
|
|
@@ -513008,6 +513327,7 @@ var BlunTUI = class {
|
|
|
513008
513327
|
const channelMcpServers = sessionMcpServers();
|
|
513009
513328
|
if (channelMcpServers !== void 0) createSessionOptions.mcpServers = channelMcpServers;
|
|
513010
513329
|
if (this.state.appState.additionalDirs.length > 0) createSessionOptions.additionalDirs = [...this.state.appState.additionalDirs];
|
|
513330
|
+
const sessionStartedAt = Date.now();
|
|
513011
513331
|
try {
|
|
513012
513332
|
if (isResumeStartup) {
|
|
513013
513333
|
if (startup.sessionFlag === "") {
|
|
@@ -513053,10 +513373,12 @@ var BlunTUI = class {
|
|
|
513053
513373
|
this.authFlow.enterLoginRequiredStartupState();
|
|
513054
513374
|
this.startupLoginRequired = true;
|
|
513055
513375
|
return false;
|
|
513376
|
+
} finally {
|
|
513377
|
+
this.startupPhaseMs["session_open_ms"] = Date.now() - sessionStartedAt;
|
|
513056
513378
|
}
|
|
513057
513379
|
if (session === void 0) throw new Error(uiText("blunTui.startup.sessionNotInitialized"));
|
|
513058
|
-
await this.setSession(session);
|
|
513059
|
-
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));
|
|
513060
513382
|
this.applyStartupPermissionAndPlanToAppState();
|
|
513061
513383
|
this.state.startupState = "ready";
|
|
513062
513384
|
return shouldReplayHistory;
|
|
@@ -513339,6 +513661,7 @@ var BlunTUI = class {
|
|
|
513339
513661
|
}
|
|
513340
513662
|
queueDrainTimer;
|
|
513341
513663
|
queueCommandRunning = false;
|
|
513664
|
+
telegramRemoteCommandContext;
|
|
513342
513665
|
queueFlushBatchRemaining = 0;
|
|
513343
513666
|
queueSteerInFlight;
|
|
513344
513667
|
editorReplacementActive = false;
|
|
@@ -513589,14 +513912,20 @@ var BlunTUI = class {
|
|
|
513589
513912
|
}
|
|
513590
513913
|
/**
|
|
513591
513914
|
* Remote channel inbound (Telegram) into THIS visible session.
|
|
513592
|
-
*
|
|
513593
|
-
* "/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
|
|
513594
513918
|
* shows a clean user line with a muted origin prefix; the
|
|
513595
513919
|
* model receives the full <channel …> payload. Busy turns queue via the same
|
|
513596
513920
|
* queuedMessages mechanic as typed input; a completed tool/step boundary
|
|
513597
513921
|
* steers one FIFO head into the active turn without interrupting it.
|
|
513598
513922
|
*/
|
|
513599
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
|
+
}
|
|
513600
513929
|
injectChannelEnvelope({
|
|
513601
513930
|
canDeliver: () => this.session !== void 0 && this.state.appState.model.trim().length > 0,
|
|
513602
513931
|
isBusy: () => this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting,
|
|
@@ -513637,6 +513966,99 @@ var BlunTUI = class {
|
|
|
513637
513966
|
}
|
|
513638
513967
|
}, envelope, this.channelPreamble);
|
|
513639
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
|
+
}
|
|
513640
514062
|
sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false, acknowledge) {
|
|
513641
514063
|
if (!transcriptRendered) this.appendTranscriptEntry({
|
|
513642
514064
|
id: nextTranscriptId(),
|
|
@@ -513696,6 +514118,7 @@ var BlunTUI = class {
|
|
|
513696
514118
|
pendingChannelReplyGuard;
|
|
513697
514119
|
/** See SessionEventHost.runChannelReplyFallback — called at turn end. */
|
|
513698
514120
|
channelMediaDeliveries = /* @__PURE__ */ new Set();
|
|
514121
|
+
channelMediaDeliveryFailures = /* @__PURE__ */ new Set();
|
|
513699
514122
|
/** Deliver completed media at tool-result time so later queued work cannot hide it. */
|
|
513700
514123
|
runChannelMediaFallback(output) {
|
|
513701
514124
|
const guard = this.pendingChannelReplyGuard;
|
|
@@ -513705,9 +514128,12 @@ var BlunTUI = class {
|
|
|
513705
514128
|
if (this.channelMediaDeliveries.has(deliveryKey)) return;
|
|
513706
514129
|
this.channelMediaDeliveries.add(deliveryKey);
|
|
513707
514130
|
sendMediaReplyFallback(guard.chatId, filePath).then((sent) => {
|
|
513708
|
-
if (sent)
|
|
514131
|
+
if (sent) {
|
|
514132
|
+
this.channelMediaDeliveryFailures.delete(deliveryKey);
|
|
514133
|
+
return;
|
|
514134
|
+
}
|
|
513709
514135
|
this.channelMediaDeliveries.delete(deliveryKey);
|
|
513710
|
-
this.
|
|
514136
|
+
this.channelMediaDeliveryFailures.add(deliveryKey);
|
|
513711
514137
|
});
|
|
513712
514138
|
}
|
|
513713
514139
|
sendChannelTurnStatus(message) {
|
|
@@ -513721,6 +514147,13 @@ var BlunTUI = class {
|
|
|
513721
514147
|
const guard = this.pendingChannelReplyGuard;
|
|
513722
514148
|
if (guard === void 0) return;
|
|
513723
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
|
+
}
|
|
513724
514157
|
if (reason !== "completed") return;
|
|
513725
514158
|
if (this.state.transcriptEntries.slice(guard.transcriptStart).some((entry) => entry.kind === "tool_call" && /reply|edit_message/i.test(entry.toolCallData?.name ?? ""))) return;
|
|
513726
514159
|
if (outboxGrewForChat(guard.outboxMarker, guard.chatId)) return;
|
|
@@ -513826,7 +514259,7 @@ var BlunTUI = class {
|
|
|
513826
514259
|
recallLastQueued() {
|
|
513827
514260
|
if (this.state.queuedMessages.length === 0) return void 0;
|
|
513828
514261
|
const last = this.state.queuedMessages.at(-1);
|
|
513829
|
-
if (last.mode === "channel") return void 0;
|
|
514262
|
+
if (last.mode === "channel" || last.mode === "channel-command") return void 0;
|
|
513830
514263
|
const queuedBatchCount = Math.max(0, this.queueFlushBatchRemaining - (this.queueSteerInFlight?.items.length ?? 0));
|
|
513831
514264
|
const removesBatchItem = this.state.queuedMessages.length <= queuedBatchCount;
|
|
513832
514265
|
this.state.queuedMessages = this.state.queuedMessages.slice(0, -1);
|
|
@@ -513884,6 +514317,10 @@ var BlunTUI = class {
|
|
|
513884
514317
|
this.runSlashCommand(item.text);
|
|
513885
514318
|
return;
|
|
513886
514319
|
}
|
|
514320
|
+
if (item.mode === "channel-command") {
|
|
514321
|
+
this.runTelegramRemoteCommand(item);
|
|
514322
|
+
return;
|
|
514323
|
+
}
|
|
513887
514324
|
const activeSession = this.session ?? session;
|
|
513888
514325
|
if (item.mode === "channel") {
|
|
513889
514326
|
this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
|
|
@@ -513956,6 +514393,11 @@ var BlunTUI = class {
|
|
|
513956
514393
|
});
|
|
513957
514394
|
}
|
|
513958
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
|
+
}
|
|
513959
514401
|
if (this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting) {
|
|
513960
514402
|
this.enqueueMessage(input, options);
|
|
513961
514403
|
return;
|
|
@@ -514119,7 +514561,7 @@ var BlunTUI = class {
|
|
|
514119
514561
|
if (this.state.appState.additionalDirs.length > 0) options.additionalDirs = [...this.state.appState.additionalDirs];
|
|
514120
514562
|
return this.harness.createSession(options);
|
|
514121
514563
|
}
|
|
514122
|
-
async setSession(session) {
|
|
514564
|
+
async setSession(session, options = {}) {
|
|
514123
514565
|
await session.setModel(BLUN_KING_MODEL_ALIAS, { allowFallback: false });
|
|
514124
514566
|
await this.personalMemoryController.clear(this.session);
|
|
514125
514567
|
const previous = this.unloadCurrentSession(approvalCancellationFeedback("switching_session"));
|
|
@@ -514137,10 +514579,10 @@ var BlunTUI = class {
|
|
|
514137
514579
|
this.syncAdditionalDirs(session);
|
|
514138
514580
|
await this.refreshManagedImageReaderAvailability(session);
|
|
514139
514581
|
await this.authFlow.applyManagedAccountContextToSession();
|
|
514140
|
-
await this.personalMemoryController.refresh();
|
|
514582
|
+
if (options.refreshPersonalMemory !== false) await this.personalMemoryController.refresh();
|
|
514141
514583
|
}
|
|
514142
514584
|
async syncRuntimeState(session = this.requireSession()) {
|
|
514143
|
-
const [status, goalResult] = await Promise.all([session.getStatus(), session.getGoal()]);
|
|
514585
|
+
const [status, goalResult, loopResult] = await Promise.all([session.getStatus(), session.getGoal(), session.getLoop()]);
|
|
514144
514586
|
this.setAppState({
|
|
514145
514587
|
sessionId: session.id,
|
|
514146
514588
|
model: BLUN_KING_MODEL_ALIAS,
|
|
@@ -514157,11 +514599,22 @@ var BlunTUI = class {
|
|
|
514157
514599
|
compactionBudgetTokens: status.compactionBudgetTokens ?? 0,
|
|
514158
514600
|
contextUsage: status.contextUsage,
|
|
514159
514601
|
sessionTitle: session.summary?.title ?? null,
|
|
514160
|
-
goal: goalResult.goal
|
|
514602
|
+
goal: goalResult.goal,
|
|
514603
|
+
loop: loopResult.loop
|
|
514161
514604
|
});
|
|
514162
514605
|
this.syncAdditionalDirs(session);
|
|
514163
514606
|
this.startTelegramChannel();
|
|
514164
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
|
+
}
|
|
514165
514618
|
async applyStartupModesToResumedSession(session) {
|
|
514166
514619
|
const { startup } = this.options;
|
|
514167
514620
|
if (startup.auto) await session.setPermission("auto");
|
|
@@ -514202,6 +514655,7 @@ var BlunTUI = class {
|
|
|
514202
514655
|
this.harness.setTelemetryContext({ sessionId: null });
|
|
514203
514656
|
this.setAppState({
|
|
514204
514657
|
goal: null,
|
|
514658
|
+
loop: null,
|
|
514205
514659
|
swarmModeEntry: void 0
|
|
514206
514660
|
});
|
|
514207
514661
|
return previous;
|
|
@@ -514248,7 +514702,7 @@ var BlunTUI = class {
|
|
|
514248
514702
|
streamingPhase: "idle"
|
|
514249
514703
|
});
|
|
514250
514704
|
if (!this.preserveQueueAcrossSessionReset) {
|
|
514251
|
-
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");
|
|
514252
514706
|
this.queueFlushBatchRemaining = 0;
|
|
514253
514707
|
this.queueSteerInFlight = void 0;
|
|
514254
514708
|
}
|
|
@@ -514677,10 +515131,12 @@ var BlunTUI = class {
|
|
|
514677
515131
|
children.splice(0, children.length, ...newChildren);
|
|
514678
515132
|
}
|
|
514679
515133
|
showStatus(message, color) {
|
|
515134
|
+
this.telegramRemoteCommandContext?.responses.push(message);
|
|
514680
515135
|
this.state.transcriptContainer.addChild(new StatusMessageComponent(message, color));
|
|
514681
515136
|
this.state.ui.requestRender();
|
|
514682
515137
|
}
|
|
514683
515138
|
showNotice(title, detail) {
|
|
515139
|
+
this.telegramRemoteCommandContext?.responses.push(detail === void 0 ? title : `${title}\n${detail}`);
|
|
514684
515140
|
this.state.transcriptContainer.addChild(new NoticeMessageComponent(title, detail));
|
|
514685
515141
|
this.state.ui.requestRender();
|
|
514686
515142
|
}
|
|
@@ -515669,7 +516125,8 @@ async function runShell(opts, version, updateStartupNotice) {
|
|
|
515669
516125
|
duration_ms: Date.now() - startedAt,
|
|
515670
516126
|
config_ms: configMs,
|
|
515671
516127
|
init_ms: initMs,
|
|
515672
|
-
mcp_ms: mcpMs
|
|
516128
|
+
mcp_ms: mcpMs,
|
|
516129
|
+
...tui.getStartupPhaseMs()
|
|
515673
516130
|
});
|
|
515674
516131
|
} catch (error) {
|
|
515675
516132
|
removeCrashHandlers();
|