blun-king-cli 9.1.486 → 9.1.489

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LIESMICH.txt +28 -7
  3. package/README.md +16 -3
  4. package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -0
  5. package/agent-spine-plugin/.claude-plugin/plugin.json +14 -0
  6. package/agent-spine-plugin/.codex-plugin/plugin.json +34 -0
  7. package/agent-spine-plugin/.mcp.json +8 -0
  8. package/agent-spine-plugin/CHANGELOG.md +277 -0
  9. package/agent-spine-plugin/LICENSE +186 -0
  10. package/agent-spine-plugin/README.md +342 -0
  11. package/agent-spine-plugin/assets/agentspine-banner.svg +32 -0
  12. package/agent-spine-plugin/bin/agentspine-mcp.js +4 -0
  13. package/agent-spine-plugin/bin/agentspine.js +7 -0
  14. package/agent-spine-plugin/blun.plugin.json +33 -0
  15. package/agent-spine-plugin/docs/acceptance.md +61 -0
  16. package/agent-spine-plugin/docs/architecture.md +183 -0
  17. package/agent-spine-plugin/docs/attention.md +121 -0
  18. package/agent-spine-plugin/docs/automatic-continuity.md +79 -0
  19. package/agent-spine-plugin/docs/channel-runtime.md +92 -0
  20. package/agent-spine-plugin/docs/coordination.md +138 -0
  21. package/agent-spine-plugin/docs/feed-transport.md +99 -0
  22. package/agent-spine-plugin/docs/gateway-runtime.md +109 -0
  23. package/agent-spine-plugin/docs/harness-reference.md +45 -0
  24. package/agent-spine-plugin/docs/host-integration.md +129 -0
  25. package/agent-spine-plugin/docs/https-transport.md +116 -0
  26. package/agent-spine-plugin/docs/learning.md +98 -0
  27. package/agent-spine-plugin/docs/object-transport.md +93 -0
  28. package/agent-spine-plugin/docs/peer-transport.md +88 -0
  29. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  30. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  31. package/agent-spine-plugin/docs/relationships.md +67 -0
  32. package/agent-spine-plugin/docs/releasing.md +83 -0
  33. package/agent-spine-plugin/docs/roadmap.md +307 -0
  34. package/agent-spine-plugin/docs/selfstarter.md +88 -0
  35. package/agent-spine-plugin/docs/session-briefing.md +74 -0
  36. package/agent-spine-plugin/docs/shared-memory.md +259 -0
  37. package/agent-spine-plugin/docs/source-roots.md +86 -0
  38. package/agent-spine-plugin/docs/sqlite-transport.md +76 -0
  39. package/agent-spine-plugin/hooks/hooks.json +95 -0
  40. package/agent-spine-plugin/hooks/version.json +5 -0
  41. package/agent-spine-plugin/package.json +69 -0
  42. package/agent-spine-plugin/scripts/check-hosts.js +193 -0
  43. package/agent-spine-plugin/scripts/check-install.js +551 -0
  44. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  45. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  46. package/agent-spine-plugin/scripts/release-check.js +127 -0
  47. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  48. package/agent-spine-plugin/scripts/run-checks.js +46 -0
  49. package/agent-spine-plugin/scripts/run-tests-hermetic.js +73 -0
  50. package/agent-spine-plugin/skill/SKILL.md +76 -0
  51. package/agent-spine-plugin/skills/agent-spine/SKILL.md +84 -0
  52. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  53. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  54. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  55. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  56. package/agent-spine-plugin/src/cli.js +950 -0
  57. package/agent-spine-plugin/src/hook.js +646 -0
  58. package/agent-spine-plugin/src/index.js +84 -0
  59. package/agent-spine-plugin/src/lib/acceptance.js +294 -0
  60. package/agent-spine-plugin/src/lib/attention.js +755 -0
  61. package/agent-spine-plugin/src/lib/audit.js +308 -0
  62. package/agent-spine-plugin/src/lib/authentication.js +513 -0
  63. package/agent-spine-plugin/src/lib/briefing.js +305 -0
  64. package/agent-spine-plugin/src/lib/catalog.js +142 -0
  65. package/agent-spine-plugin/src/lib/channel-runtime.js +665 -0
  66. package/agent-spine-plugin/src/lib/context.js +154 -0
  67. package/agent-spine-plugin/src/lib/continuity.js +333 -0
  68. package/agent-spine-plugin/src/lib/coordination.js +577 -0
  69. package/agent-spine-plugin/src/lib/documents.js +199 -0
  70. package/agent-spine-plugin/src/lib/feed-transport.js +497 -0
  71. package/agent-spine-plugin/src/lib/filesystem-retry.js +27 -0
  72. package/agent-spine-plugin/src/lib/gateway-runtime.js +760 -0
  73. package/agent-spine-plugin/src/lib/graph.js +284 -0
  74. package/agent-spine-plugin/src/lib/https-transport.js +392 -0
  75. package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -0
  76. package/agent-spine-plugin/src/lib/indexed-memory.js +281 -0
  77. package/agent-spine-plugin/src/lib/learning.js +645 -0
  78. package/agent-spine-plugin/src/lib/object-transport.js +206 -0
  79. package/agent-spine-plugin/src/lib/paths.js +60 -0
  80. package/agent-spine-plugin/src/lib/peer-transport.js +283 -0
  81. package/agent-spine-plugin/src/lib/persona-runtime.js +503 -0
  82. package/agent-spine-plugin/src/lib/runtime.js +13 -0
  83. package/agent-spine-plugin/src/lib/selfstarter.js +819 -0
  84. package/agent-spine-plugin/src/lib/sharing.js +969 -0
  85. package/agent-spine-plugin/src/lib/source-roots.js +451 -0
  86. package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -0
  87. package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -0
  88. package/agent-spine-plugin/src/lib/voice-runtime.js +39 -0
  89. package/agent-spine-plugin/src/mcp.js +526 -0
  90. package/agent-spine-plugin/src/version.js +1 -0
  91. package/agent-spine-plugin/src/worker.js +178 -0
  92. package/bin/identity-context-policy.cjs +51 -1
  93. package/bin/launcher-runtime.js +42 -1
  94. package/bin/plugin-bootstrap.js +68 -27
  95. package/bin/repeated-user-message-projection.cjs +6 -0
  96. package/bin/turn-thinking-policy.cjs +5 -4
  97. package/bin/turn-tool-performance-policy.cjs +16 -2
  98. package/blun.mjs +187 -35
  99. package/package.json +3 -1
package/blun.mjs CHANGED
@@ -30516,7 +30516,7 @@ var init_input_schema = __esmMin((() => {
30516
30516
  var todo_list_default;
30517
30517
  var init_todo_list$1 = __esmMin((() => {
30518
30518
  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";
30519
- todo_list_default = "Maintain a structured TODO list when it adds clarity to multi-step work; skip it for conversation or a single-shot task. In plan mode, use the plan file instead.\n\nCall with `todos: [...]` to replace the full list. Statuses are `pending`, `in_progress`, `done`, `blocked`, `waiting_approval`, and `aborted`. Omit `todos` to query the current list; use `todos: []` to clear it.\n\nKeep titles short and actionable. Capture the real requirements, keep exactly one item `in_progress` while tracked work is active, and update a status immediately after real progress instead of batching or rewriting an unchanged list. Use `done` only for fully verified work. A `blocked` item names the reason and the action that can unblock it; `waiting_approval` names the prepared result awaiting approval; `aborted` records the last reached state. Before finishing, reconcile every tracked item truthfully. If no available tool can move the work forward, report the blocker to the user instead of churning the list.";
30519
+ todo_list_default = "Maintain the agent's visible BLUN TUI task/TODO list as you work through a multi-step task. This is the Aufgaben/Task/Todo panel shown by the interface, not a file, Cron job, scheduled task, or background-agent list. Use it proactively for investigations and implementation work spanning several tool calls; skip it only for conversation or a genuinely single-shot task. In plan mode, use the plan file instead.\n\nStart multi-step work by replacing the list with the real requirements. Keep exactly one item `in_progress`, update it immediately after real progress, and move the next item to `in_progress` instead of batching status changes at the end. Call with `todos: [...]` to replace the full list. Statuses are `pending`, `in_progress`, `done`, `blocked`, `waiting_approval`, and `aborted`. Omit `todos` to query the current list; use `todos: []` to clear a finished non-goal list.\n\nKeep titles short and actionable. Use `done` only for fully verified work. A `blocked` item names the reason and the action that can unblock it; `waiting_approval` names the prepared result awaiting approval; `aborted` records the last reached state. Before finishing, reconcile every tracked item truthfully. If no available tool can move the work forward, report the blocker to the user instead of churning the list.";
30520
30520
  }));
30521
30521
  //#endregion
30522
30522
  //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.ts
@@ -261582,7 +261582,7 @@ function blunPendingMediaSystemPrompt(systemPrompt, pendingMediaJobIds) {
261582
261582
  function blunFastConversationTools(tools, input, pendingMediaJobIds = [], requiredToolNames = /* @__PURE__ */ new Set()) {
261583
261583
  const telegramTurn = BLUN_TELEGRAM_CHANNEL_RE.test(blunExtractText(input));
261584
261584
  const mediaPending = pendingMediaJobIds.length > 0;
261585
- return tools.filter((tool) => telegramTurn && BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name) || mediaPending && tool.name === "GetMedia" || requiredToolNames.has(tool.name));
261585
+ return tools.filter((tool) => tool.name === "TodoList" || telegramTurn && BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name) || mediaPending && tool.name === "GetMedia" || requiredToolNames.has(tool.name));
261586
261586
  }
261587
261587
  /**
261588
261588
  * Keep frequently used native tools resident and expose every other tool by
@@ -262128,12 +262128,14 @@ var init_turn = __esmMin((() => {
262128
262128
  return {
262129
262129
  accepted: true,
262130
262130
  buffered: false,
262131
+ duplicate: true,
262131
262132
  turnId: null
262132
262133
  };
262133
262134
  }
262134
262135
  if (this.activeTurn !== null) return {
262135
262136
  accepted: false,
262136
262137
  buffered: false,
262138
+ duplicate: false,
262137
262139
  turnId: null
262138
262140
  };
262139
262141
  this.telegramDeliveryLedger.remember(input, origin);
@@ -262147,6 +262149,7 @@ var init_turn = __esmMin((() => {
262147
262149
  return {
262148
262150
  accepted: true,
262149
262151
  buffered,
262152
+ duplicate: false,
262150
262153
  turnId
262151
262154
  };
262152
262155
  }
@@ -262471,6 +262474,9 @@ var init_turn = __esmMin((() => {
262471
262474
  this.agent.usage.endTurn();
262472
262475
  this.flushCognitiveToolEvidence(turnId, true);
262473
262476
  this.recordCognitiveStage("endTurn", { turnId, reason: ended.reason, durationMs: ended.durationMs ?? 0 });
262477
+ if (ended.reason === "cancelled" && (this.currentStepByTurn.get(turnId) ?? 0) === 0) {
262478
+ this.telegramDeliveryLedger.forget(input, origin);
262479
+ }
262474
262480
  this.agent.emitEvent(ended);
262475
262481
  return ended;
262476
262482
  }
@@ -262776,10 +262782,11 @@ var init_turn = __esmMin((() => {
262776
262782
  const steerInput = pendingSteers.flatMap((steer) => [...steer.input]);
262777
262783
  const steerThinkingEffort = "low";
262778
262784
  const steerPendingMediaJobIds = this.agent.toolServices?.media?.pendingMediaJobIds?.() ?? [];
262779
- const steerLLM = this.agent.llmForTurn(steerThinkingEffort, blunPendingMediaSystemPrompt(this.agent.fastConversationSystemPrompt, steerPendingMediaJobIds));
262780
262785
  const steerText = blunExtractText(steerInput);
262781
262786
  const steerRequiredToolNames = mediaToolNamesForTurnText(steerText);
262782
262787
  for (const name of rankedToolNamesForTurnText(eligibleTools, steerText)) steerRequiredToolNames.add(name);
262788
+ const steerNeedsActionTools = steerRequiredToolNames.size > 0;
262789
+ const steerLLM = this.agent.llmForTurn(steerThinkingEffort, blunPendingMediaSystemPrompt(steerNeedsActionTools ? this.agent.effectiveSystemPrompt : this.agent.fastConversationSystemPrompt, steerPendingMediaJobIds));
262783
262790
  const steerTools = [...blunFastConversationTools(eligibleTools, steerInput, steerPendingMediaJobIds, steerRequiredToolNames)];
262784
262791
  const steerHistory = () => blunFastConversationHistory(this.agent.context.history);
262785
262792
  const buildSteerMessages = () => this.agent.context.project(steerHistory(), { dropOrphanResults: true });
@@ -262852,6 +262859,8 @@ var init_turn = __esmMin((() => {
262852
262859
  return { continue: false };
262853
262860
  },
262854
262861
  prepareToolExecution: async (ctx) => {
262862
+ const goalTodoPolicy = enforceGoalTodoPolicy(this.agent, ctx);
262863
+ if (goalTodoPolicy !== void 0) return goalTodoPolicy;
262855
262864
  const ideaPolicy = enforceIdeaToolPolicy(this.agent, ctx);
262856
262865
  if (ideaPolicy !== void 0) return ideaPolicy;
262857
262866
  const cached = deduper.checkSameStep(ctx.toolCall.id, ctx.toolCall.name, ctx.args);
@@ -263182,6 +263191,7 @@ var init_update_goal = __esmMin((() => {
263182
263191
  }
263183
263192
  if (args.status === "complete") {
263184
263193
  const completed = await goal.markComplete({}, "model");
263194
+ if (completed !== null) this.agent.tools.updateStore(TODO_STORE_KEY, []);
263185
263195
  if (completed !== null) this.agent.context.appendSystemReminder(buildGoalCompletionSummaryPrompt(completed), {
263186
263196
  kind: "system_trigger",
263187
263197
  name: GOAL_COMPLETION_REMINDER_NAME
@@ -265802,6 +265812,7 @@ var init_agent = __esmMin((() => {
265802
265812
 
265803
265813
  - Answer the user's current conversational message directly and concisely.
265804
265814
  - Use the language of the user's latest conversational message unless the persona or current session specifies a language override.
265815
+ - When the user assigns multi-step work, start and maintain the visible BLUN TodoList before doing the work; update it after real progress and reconcile it before finishing. TodoList is not Cron, a file, or a background-task list.
265805
265816
  - Do not claim to have inspected files, run commands, used tools, or checked current external state.
265806
265817
  - If the message actually requires work or live information, say that plainly instead of inventing a result.`].filter((block) => block.length > 0).join("\n\n");
265807
265818
  }
@@ -297711,7 +297722,7 @@ var init_session$1 = __esmMin((() => {
297711
297722
  this.log = this.logHandle?.logger ?? (options.id === void 0 ? log : log.createChild({ sessionId: options.id }));
297712
297723
  this.rpc = options.rpc;
297713
297724
  this.experimentalFlags = options.experimentalFlags ?? new FlagResolver();
297714
- this.visionReader = (this.experimentalFlags.enabled("vision_reader") ? createVisionReader(options.config?.services?.visionReader) : void 0) ?? createManagedMediaVisionReader(options.toolServices?.media);
297725
+ this.visionReader = createManagedMediaVisionReader(options.toolServices?.media) ?? (this.experimentalFlags.enabled("vision_reader") ? createVisionReader(options.config?.services?.visionReader) : void 0);
297715
297726
  this.hookEngine = new HookEngine(options.hooks, {
297716
297727
  cwd: options.kaos.getcwd(),
297717
297728
  sessionId: options.id
@@ -326885,7 +326896,7 @@ var Session = class {
326885
326896
  buffered: false,
326886
326897
  turnId: null
326887
326898
  };
326888
- }, (result) => result.accepted);
326899
+ }, (result) => result.accepted && result.duplicate !== true);
326889
326900
  }
326890
326901
  /** Execute a user-initiated `!` shell command (silent — does not prompt the
326891
326902
  * model). Resolves with the command's stdout/stderr for immediate display.
@@ -329199,7 +329210,7 @@ var AsyncTransport = class {
329199
329210
  constructor(options) {
329200
329211
  this.homeDir = options.homeDir;
329201
329212
  this.deviceId = options.deviceId;
329202
- this.endpoint = options.endpoint ?? "https://telemetry-logs.blun.ai/v1/event";
329213
+ this.endpoint = options.endpoint ?? "";
329203
329214
  this.getAccessToken = options.getAccessToken ?? null;
329204
329215
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
329205
329216
  this.retryBackoffsMs = options.retryBackoffsMs ?? RETRY_BACKOFFS_MS;
@@ -329208,7 +329219,7 @@ var AsyncTransport = class {
329208
329219
  this.now = options.now ?? Date.now;
329209
329220
  }
329210
329221
  async send(events, signal) {
329211
- if (events.length === 0) return;
329222
+ if (events.length === 0 || !isBlunTelemetryHost(this.endpoint)) return;
329212
329223
  let savedToDisk = false;
329213
329224
  const saveEventsToDisk = () => {
329214
329225
  if (savedToDisk) return;
@@ -329248,7 +329259,7 @@ var AsyncTransport = class {
329248
329259
  saveEventsToDisk();
329249
329260
  }
329250
329261
  saveToDisk(events) {
329251
- if (events.length === 0) return;
329262
+ if (events.length === 0 || !isBlunTelemetryHost(this.endpoint)) return;
329252
329263
  const path = join(this.telemetryDir(), `failed_${randomBytes(6).toString("hex")}.jsonl`);
329253
329264
  writeFileSync(path, events.map((event) => JSON.stringify(event)).join("\n") + "\n", {
329254
329265
  encoding: "utf-8",
@@ -329290,6 +329301,7 @@ var AsyncTransport = class {
329290
329301
  return new Set(pruned.map((entry) => entry.name));
329291
329302
  }
329292
329303
  async retryDiskEvents() {
329304
+ if (!isBlunTelemetryHost(this.endpoint)) return;
329293
329305
  const spoolEntries = this.readSpoolEntries();
329294
329306
  const prunedNames = this.pruneDiskSpool(spoolEntries);
329295
329307
  const retryEntries = selectTelemetryRetryEntries(spoolEntries
@@ -403774,6 +403786,13 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
403774
403786
  priority: 60,
403775
403787
  availability: "always"
403776
403788
  },
403789
+ {
403790
+ name: "agent-spine",
403791
+ aliases: [],
403792
+ descriptionKey: "command.plugins.description",
403793
+ priority: 61,
403794
+ availability: "idle-only"
403795
+ },
403777
403796
  {
403778
403797
  name: "telegram-access",
403779
403798
  aliases: ["telegram-acces"],
@@ -423182,6 +423201,31 @@ function validInitialIdeaPlan(value) {
423182
423201
  }
423183
423202
  return active === 1;
423184
423203
  }
423204
+ function enforceGoalTodoPolicy(agent, context) {
423205
+ if (isIdeaGoal(agent) || agent.goal.getActiveGoal() === null) return;
423206
+ const todos = ideaTodos(agent);
423207
+ if (todos.length === 0) {
423208
+ const firstCall = context.toolCalls[0];
423209
+ if (context.toolCall.id !== firstCall?.id || context.toolCall.name !== "TodoList") return {
423210
+ block: true,
423211
+ reason: "Active multi-step goals require TodoList to be the first tool call. Publish and maintain the visible task list before research or action."
423212
+ };
423213
+ if (!validInitialIdeaPlan(context.args?.todos)) return {
423214
+ block: true,
423215
+ reason: "The first goal TodoList must contain at least one item, exactly one in_progress item, and only pending items otherwise."
423216
+ };
423217
+ return;
423218
+ }
423219
+ if (context.toolCall.name !== "UpdateGoal" || context.args?.status !== "complete" && context.args?.status !== "blocked") return;
423220
+ if (todos.some((todo) => !IDEA_TERMINAL_TODO_STATUSES.includes(todo.status))) return {
423221
+ block: true,
423222
+ reason: "The goal cannot finish while a visible TodoList item has no truthful terminal status. Reconcile the list first."
423223
+ };
423224
+ if (context.args.status === "complete" && todos.some((todo) => todo.status !== "done")) return {
423225
+ block: true,
423226
+ reason: "The goal can complete only when every visible TodoList item is done. Use blocked when an item is blocked, waiting_approval, or aborted."
423227
+ };
423228
+ }
423185
423229
  function ideaOutboundChannel(toolName, args) {
423186
423230
  if (toolName === "Bash") {
423187
423231
  const command = typeof args?.command === "string" ? args.command : "";
@@ -496448,6 +496492,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
496448
496492
  case "plugins":
496449
496493
  await handlePluginsCommand(host, args);
496450
496494
  return;
496495
+ case "agent-spine":
496496
+ await handlePluginsCommand(host, "info agent-spine");
496497
+ return;
496451
496498
  case "telegram-access":
496452
496499
  handleTelegramAccessCommand(host);
496453
496500
  return;
@@ -503961,8 +504008,8 @@ const CHANNEL_QUEUE_RETRY_MS = 5e3;
503961
504008
  //#endregion
503962
504009
  //#region src/tui/controllers/channel-queue-deadline.ts
503963
504010
  /**
503964
- * Steers queued channel work into the active turn as soon as it arrives. A busy
503965
- * runtime is retried without ever cancelling the turn or crossing a FIFO barrier.
504011
+ * Delivers channel work only after Ctrl+S or Escape releases it. A busy runtime
504012
+ * is retried without cancelling the turn or crossing the FIFO barrier.
503966
504013
  */
503967
504014
  var ChannelQueueDeadlineController = class {
503968
504015
  host;
@@ -503985,10 +504032,7 @@ var ChannelQueueDeadlineController = class {
503985
504032
  }
503986
504033
  requestDeliveryNow() {
503987
504034
  if (this.retainReleaseWhileDeliveryIsInFlight()) return;
503988
- if (this.host.hasWaitingWork() && !this.host.canDeliverWork()) {
503989
- this.scheduleRetry();
503990
- return;
503991
- }
504035
+ if (this.host.hasWaitingWork() && !this.host.canDeliverWork()) return;
503992
504036
  this.requestDelivery();
503993
504037
  }
503994
504038
  retainReleaseWhileDeliveryIsInFlight() {
@@ -504016,7 +504060,10 @@ var ChannelQueueDeadlineController = class {
504016
504060
  this.deliveryInFlight = true;
504017
504061
  this.clearRetryTimer();
504018
504062
  Promise.resolve().then(() => this.host.deliverOne()).catch(() => false).then((delivered) => {
504019
- if (!delivered && this.host.hasWaitingWork()) this.scheduleRetry();
504063
+ if (!delivered && this.host.hasWaitingWork()) {
504064
+ this.pendingReleaseCount = 0;
504065
+ this.scheduleRetry();
504066
+ }
504020
504067
  }).finally(() => {
504021
504068
  this.deliveryInFlight = false;
504022
504069
  this.pumpDelivery();
@@ -505471,7 +505518,11 @@ var EditorKeyboardController = class {
505471
505518
  return;
505472
505519
  }
505473
505520
  if (host.state.appState.isCompacting && host.streamingUI.hasActiveTurn()) {
505474
- host.discardQueuedSlashCommands();
505521
+ if (host.releaseNextQueuedMessage()) {
505522
+ this.clearPendingUndoEsc();
505523
+ return;
505524
+ }
505525
+ host.markCurrentRunCancelledByUser();
505475
505526
  this.cancelCurrentStream();
505476
505527
  this.clearPendingUndoEsc();
505477
505528
  return;
@@ -505486,7 +505537,11 @@ var EditorKeyboardController = class {
505486
505537
  return;
505487
505538
  }
505488
505539
  if (host.queueSteerInFlight !== void 0 || host.streamingUI.hasActiveTurn() || host.state.appState.streamingPhase !== "idle") {
505489
- host.discardQueuedSlashCommands();
505540
+ if (host.releaseNextQueuedMessage()) {
505541
+ this.clearPendingUndoEsc();
505542
+ return;
505543
+ }
505544
+ host.markCurrentRunCancelledByUser();
505490
505545
  this.cancelCurrentStream();
505491
505546
  this.clearPendingUndoEsc();
505492
505547
  return;
@@ -505495,10 +505550,6 @@ var EditorKeyboardController = class {
505495
505550
  this.clearPendingUndoEsc();
505496
505551
  return;
505497
505552
  }
505498
- if (host.discardQueuedSlashCommands() > 0) {
505499
- this.clearPendingUndoEsc();
505500
- return;
505501
- }
505502
505553
  if (this.pendingUndoEsc !== null) {
505503
505554
  this.clearPendingUndoEsc();
505504
505555
  host.openUndoSelector();
@@ -508757,6 +508808,7 @@ var SessionEventHandler = class {
508757
508808
  this.clearModelRetryHint();
508758
508809
  this.turnWatchdog.stop();
508759
508810
  this.host.restoreQueuedSteerAtTurnEnd(event.turnId);
508811
+ this.host.settleChannelPromptAtTurnEnd(event.turnId, event.reason);
508760
508812
  const sessionId = this.host.session?.id;
508761
508813
  if (sessionId !== void 0) finishPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
508762
508814
  this.host.streamingUI.flushNow();
@@ -508783,6 +508835,7 @@ var SessionEventHandler = class {
508783
508835
  this.turnWatchdog.recordProgress();
508784
508836
  this.clearModelRetryHint();
508785
508837
  this.host.commitQueuedSteerAtStepStart(event.turnId);
508838
+ this.host.commitChannelPromptAtStepStart(event.turnId);
508786
508839
  this.host.streamingUI.flushNow();
508787
508840
  this.host.streamingUI.setStep(event.step);
508788
508841
  this.host.streamingUI.resetToolUi();
@@ -509093,6 +509146,7 @@ var SessionEventHandler = class {
509093
509146
  const { state } = this.host;
509094
509147
  if (change.kind === "completion" && event.snapshot !== null) {
509095
509148
  this.pendingModelBlockedFallback = void 0;
509149
+ this.host.streamingUI.setTodoList([]);
509096
509150
  this.goalCompletionAwaitingClear = true;
509097
509151
  this.goalCompletionTurnEnded = false;
509098
509152
  this.host.appendTranscriptEntry({
@@ -517538,7 +517592,7 @@ var BlunTUI = class {
517538
517592
  sessionId: this.session?.id ?? "",
517539
517593
  goal: this.state.appState.goal,
517540
517594
  permissionMode: this.state.appState.permissionMode,
517541
- idle: this.session !== void 0 && !this.isShuttingDown && !this.aborted && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && !this.streamingUI.hasActiveTurn() && this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting && this.state.queuedMessages.length === 0
517595
+ idle: this.session !== void 0 && !this.isShuttingDown && !this.aborted && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && this.channelPromptInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && !this.streamingUI.hasActiveTurn() && this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting && this.state.queuedMessages.length === 0
517542
517596
  };
517543
517597
  }
517544
517598
  syncGoalTimeTrigger() {
@@ -517877,6 +517931,7 @@ var BlunTUI = class {
517877
517931
  telegramRemoteCommandContext;
517878
517932
  queueFlushBatchRemaining = 0;
517879
517933
  queueSteerInFlight;
517934
+ channelPromptInFlight;
517880
517935
  editorReplacementActive = false;
517881
517936
  editorReplacement;
517882
517937
  editorReplacementGeneration = 0;
@@ -517887,8 +517942,7 @@ var BlunTUI = class {
517887
517942
  canDeliverQueuedChannelHead() {
517888
517943
  const item = this.state.queuedMessages[0];
517889
517944
  const hasActiveTurn = this.streamingUI.hasActiveTurn() || (this.state.appState.streamingPhase !== "idle" && this.state.appState.streamingPhase !== "shell");
517890
- if (this.isShuttingDown || this.queueCommandRunning || this.queueSteerInFlight !== void 0 || this.editorReplacementActive || this.deferUserMessages || this.session === void 0 || this.state.appState.model.trim().length === 0 || !hasActiveTurn || item?.mode !== "channel") return false;
517891
- if (this.directFocusController.isPaused() && item.channelDirect !== true && item.channelUrgent !== true) return false;
517945
+ if (this.isShuttingDown || this.queueCommandRunning || this.queueSteerInFlight !== void 0 || this.queueFlushBatchRemaining === 0 || this.editorReplacementActive || this.deferUserMessages || this.session === void 0 || this.state.appState.model.trim().length === 0 || !hasActiveTurn || item?.mode !== "channel") return false;
517892
517946
  return true;
517893
517947
  }
517894
517948
  async deliverQueuedChannelHead() {
@@ -517912,10 +517966,12 @@ var BlunTUI = class {
517912
517966
  this.syncChannelQueueDeadline();
517913
517967
  this.updateQueueDisplay();
517914
517968
  this.state.ui.requestRender();
517969
+ this.steerQueueFlushPrefix();
517915
517970
  return true;
517916
517971
  }
517917
517972
  const batchEnd = item.channelAttention === true ? 1 : item.channelUrgent === true ? this.state.queuedMessages.findIndex((queued) => queued.channelUrgent !== true) : item.channelDirect === true ? this.state.queuedMessages.findIndex((queued) => queued.channelDirect !== true) : item.channelAddressed === true ? this.state.queuedMessages.findIndex((queued) => queued.channelAddressed !== true) : this.state.queuedMessages.findIndex((queued) => queued.mode !== "channel" || queued.channelContextOnly === true || queued.channelAttention === true);
517918
- const items = this.state.queuedMessages.slice(0, batchEnd < 0 ? this.state.queuedMessages.length : batchEnd);
517973
+ const candidateCount = batchEnd < 0 ? this.state.queuedMessages.length : batchEnd;
517974
+ const items = this.state.queuedMessages.slice(0, Math.min(candidateCount, this.queueFlushBatchRemaining));
517919
517975
  if (items.length === 0) return false;
517920
517976
  this.state.queuedMessages = this.state.queuedMessages.slice(items.length);
517921
517977
  this.updateQueueDisplay();
@@ -517955,6 +518011,7 @@ var BlunTUI = class {
517955
518011
  turnEnded: false,
517956
518012
  onCommit: () => {
517957
518013
  renderTranscripts();
518014
+ for (const queued of queuedItems) queued.channelTranscriptRendered = true;
517958
518015
  for (const queued of items) queued.channelAcknowledge?.();
517959
518016
  if (this.queueFlushBatchRemaining > 0) this.queueFlushBatchRemaining = Math.max(0, this.queueFlushBatchRemaining - items.length);
517960
518017
  },
@@ -518021,7 +518078,11 @@ var BlunTUI = class {
518021
518078
  }
518022
518079
  updateAddressedChannelFocus() {
518023
518080
  this.state.channelFocusContainer.clear();
518024
- const items = this.addressedChannelFocusStore.snapshot();
518081
+ const queuedFocusIds = new Set([
518082
+ ...this.state.queuedMessages,
518083
+ ...this.queueSteerInFlight?.items ?? []
518084
+ ].map((item) => item.channelFocusId).filter((id) => id !== void 0));
518085
+ const items = this.addressedChannelFocusStore.snapshot().filter((item) => !queuedFocusIds.has(item.id));
518025
518086
  if (items.length > 0) this.state.channelFocusContainer.addChild(new AddressedChannelFocusPaneComponent(items));
518026
518087
  this.state.ui.requestRender();
518027
518088
  }
@@ -518031,15 +518092,47 @@ var BlunTUI = class {
518031
518092
  inFlight.stepStarted = true;
518032
518093
  this.commitQueuedSteerIfReady(inFlight);
518033
518094
  }
518095
+ releaseNextQueuedMessage() {
518096
+ const inFlightCount = this.queueSteerInFlight?.items.length ?? 0;
518097
+ const authorizedQueuedCount = Math.max(0, this.queueFlushBatchRemaining - inFlightCount);
518098
+ if (authorizedQueuedCount >= this.state.queuedMessages.length) return false;
518099
+ this.queueFlushBatchRemaining += 1;
518100
+ this.steerQueueFlushPrefix();
518101
+ this.updateQueueDisplay();
518102
+ this.state.ui.requestRender();
518103
+ return true;
518104
+ }
518105
+ markCurrentRunCancelledByUser() {
518106
+ if (this.channelPromptInFlight !== void 0) this.channelPromptInFlight.userCancelled = true;
518107
+ if (this.queueSteerInFlight !== void 0) this.queueSteerInFlight.userCancelled = true;
518108
+ }
518034
518109
  restoreQueuedSteerAtTurnEnd(turnId) {
518110
+ const turnKey = String(turnId);
518035
518111
  const inFlight = this.queueSteerInFlight;
518036
- if (inFlight === void 0 || inFlight.turnId !== String(turnId)) return;
518112
+ if (inFlight === void 0 || inFlight.turnId !== turnKey) return;
518037
518113
  inFlight.turnEnded = true;
518038
- if (inFlight.stepStarted) {
518039
- this.commitQueuedSteer(inFlight);
518114
+ if (inFlight.stepStarted || inFlight.userCancelled === true) this.commitQueuedSteer(inFlight);
518115
+ else if (inFlight.accepted) this.restoreQueuedSteer(inFlight);
518116
+ }
518117
+ commitChannelPromptAtStepStart(turnId) {
518118
+ const inFlight = this.channelPromptInFlight;
518119
+ if (inFlight === void 0 || inFlight.turnId !== String(turnId)) return;
518120
+ this.channelPromptInFlight = void 0;
518121
+ inFlight.acknowledge();
518122
+ }
518123
+ settleChannelPromptAtTurnEnd(turnId, reason) {
518124
+ const inFlight = this.channelPromptInFlight;
518125
+ if (inFlight === void 0 || inFlight.turnId !== String(turnId)) return;
518126
+ this.channelPromptInFlight = void 0;
518127
+ if (reason !== "cancelled" || inFlight.userCancelled === true) {
518128
+ inFlight.acknowledge();
518040
518129
  return;
518041
518130
  }
518042
- if (inFlight.accepted) this.restoreQueuedSteer(inFlight);
518131
+ inFlight.onRestore?.();
518132
+ this.state.queuedMessages = [inFlight.item, ...this.state.queuedMessages];
518133
+ this.syncChannelQueueDeadline();
518134
+ this.updateQueueDisplay();
518135
+ this.state.ui.requestRender();
518043
518136
  }
518044
518137
  commitQueuedSteerIfReady(inFlight) {
518045
518138
  if (this.queueSteerInFlight !== inFlight || !inFlight.accepted || !inFlight.stepStarted || inFlight.turnEnded) return;
@@ -518056,13 +518149,24 @@ var BlunTUI = class {
518056
518149
  this.scheduleQueueDrain();
518057
518150
  }
518058
518151
  recoverRejectedActiveSteer(inFlight) {
518059
- const restored = this.restoreQueuedSteer(inFlight);
518152
+ if (this.queueSteerInFlight !== inFlight) return false;
518153
+ if (inFlight.confirmationTimer !== void 0) clearTimeout(inFlight.confirmationTimer);
518154
+ this.queueSteerInFlight = void 0;
518155
+ inFlight.onRestore?.();
518156
+ const restoredItems = inFlight.items.map((item) => {
518157
+ const copy = { ...item };
518158
+ delete copy.channelDeliveryFailures;
518159
+ return copy;
518160
+ });
518161
+ this.state.queuedMessages = [...restoredItems, ...this.state.queuedMessages];
518162
+ this.updateQueueDisplay();
518163
+ this.state.ui.requestRender();
518060
518164
  if (!this.streamingUI.hasActiveTurn()) {
518061
518165
  this.setAppState({ streamingPhase: "idle" });
518062
518166
  this.resetLivePane();
518063
518167
  this.scheduleQueueDrain();
518064
518168
  }
518065
- return restored;
518169
+ return false;
518066
518170
  }
518067
518171
  quarantineQueuedSteer(inFlight, error) {
518068
518172
  if (this.queueSteerInFlight !== inFlight) return false;
@@ -518125,7 +518229,7 @@ var BlunTUI = class {
518125
518229
  })), ...this.state.queuedMessages];
518126
518230
  this.updateQueueDisplay();
518127
518231
  this.state.ui.requestRender();
518128
- if (this.state.queuedMessages[0]?.mode === "channel") this.channelQueueDeadline.requestDeliveryNow();
518232
+ if (this.queueFlushBatchRemaining > 0 && this.state.queuedMessages[0]?.mode === "channel") this.channelQueueDeadline.requestDeliveryNow();
518129
518233
  this.scheduleQueueDrain();
518130
518234
  return false;
518131
518235
  }
@@ -518133,12 +518237,13 @@ var BlunTUI = class {
518133
518237
  this.channelQueueDeadline?.sync();
518134
518238
  }
518135
518239
  releaseChannelQueueAtSafePoint() {
518240
+ if (this.queueFlushBatchRemaining === 0) return;
518136
518241
  this.channelQueueDeadline.requestDeliveryAtSafePoint();
518137
518242
  }
518138
518243
  canDrainQueue() {
518139
518244
  const head = this.state.queuedMessages[0];
518140
518245
  const directWork = head?.channelDirect === true || head?.channelUrgent === true || head?.mode === "channel-command" && /^[1-9]\d*$/u.test(String(head.channelChatId ?? ""));
518141
- return !this.isShuttingDown && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && !this.streamingUI.hasActiveTurn() && this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting && (!this.directFocusController.isPaused() || directWork);
518246
+ return !this.isShuttingDown && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && this.channelPromptInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && !this.streamingUI.hasActiveTurn() && this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting && (!this.directFocusController.isPaused() || directWork);
518142
518247
  }
518143
518248
  scheduleQueueDrain() {
518144
518249
  if (this.queueDrainTimer !== void 0 || !this.canDrainQueue()) return;
@@ -518467,6 +518572,16 @@ var BlunTUI = class {
518467
518572
  modelFallbackAllowed: false
518468
518573
  });
518469
518574
  session.promptAccepted(options?.parts ?? input).then((result) => {
518575
+ if (result.duplicate === true) {
518576
+ if (this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
518577
+ this.setAppState({ streamingPhase: "idle" });
518578
+ this.resetLivePane();
518579
+ context.item.channelAcknowledge?.();
518580
+ this.scheduleQueueDrain();
518581
+ this.updateQueueDisplay();
518582
+ this.state.ui.requestRender();
518583
+ return;
518584
+ }
518470
518585
  if (result.accepted) {
518471
518586
  context.item.channelAcknowledge?.();
518472
518587
  return;
@@ -518520,8 +518635,44 @@ var BlunTUI = class {
518520
518635
  text: focusedModelInput
518521
518636
  }, imagePart] : focusedModelInput;
518522
518637
  session.promptAccepted(promptInput, channelPromptOrigin(channelReportSources)).then((result) => {
518523
- if (result.accepted) {
518638
+ if (result.duplicate === true) {
518639
+ finishPersonalMemoryRememberIntentTurn(session.id);
518640
+ if (installedGuard !== void 0 && this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
518641
+ this.setAppState({ streamingPhase: "idle" });
518642
+ this.resetLivePane();
518524
518643
  acknowledge?.();
518644
+ this.scheduleQueueDrain();
518645
+ this.updateQueueDisplay();
518646
+ this.state.ui.requestRender();
518647
+ return;
518648
+ }
518649
+ if (result.accepted) {
518650
+ if (acknowledge !== void 0 && result.turnId !== null && result.turnId !== void 0) {
518651
+ this.channelPromptInFlight = {
518652
+ turnId: String(result.turnId),
518653
+ acknowledge,
518654
+ item: {
518655
+ text: modelInput,
518656
+ displayText,
518657
+ origin,
518658
+ agentId: this.harness.interactiveAgentId,
518659
+ mode: "channel",
518660
+ channelChatId,
518661
+ channelReportSources: channelReportSources,
518662
+ channelContextOnly: contextOnly,
518663
+ channelTranscriptRendered: true,
518664
+ channelAcknowledge: acknowledge,
518665
+ ...channelFocusId === void 0 ? {} : { channelFocusId, channelAddressed: true },
518666
+ ...channelAttention === true ? { channelAttention: true } : {},
518667
+ ...channelDirect === true ? { channelDirect: true, channelDirectResume } : {},
518668
+ ...queueKey === void 0 ? {} : { queueKey },
518669
+ ...channelImagePath === void 0 ? {} : { channelImagePath }
518670
+ },
518671
+ onRestore: () => {
518672
+ if (installedGuard !== void 0 && this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
518673
+ }
518674
+ };
518675
+ } else acknowledge?.();
518525
518676
  return;
518526
518677
  }
518527
518678
  if (installedGuard !== void 0 && this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
@@ -519880,6 +520031,7 @@ var BlunTUI = class {
519880
520031
  return this.state.livePane.mode;
519881
520032
  }
519882
520033
  updateQueueDisplay() {
520034
+ this.updateAddressedChannelFocus();
519883
520035
  this.state.queueContainer.clear();
519884
520036
  const queued = this.state.queuedMessages;
519885
520037
  if (queued.length === 0) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.486",
3
+ "version": "9.1.489",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -37,7 +37,9 @@
37
37
  "scripts/fix-node-pty-perms.js",
38
38
  "standard-skills/",
39
39
  "standard-tools/",
40
+ "agent-spine-plugin/",
40
41
  "telegram-plugin/",
42
+ "CHANGELOG.md",
41
43
  "LIESMICH.txt"
42
44
  ],
43
45
  "keywords": [