@visorcraft/idlehands 1.1.7 → 1.1.9

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 (120) hide show
  1. package/README.md +46 -0
  2. package/dist/agent/formatting.js +273 -0
  3. package/dist/agent/formatting.js.map +1 -0
  4. package/dist/agent/review-artifact.js +147 -0
  5. package/dist/agent/review-artifact.js.map +1 -0
  6. package/dist/agent/tool-calls.js +411 -0
  7. package/dist/agent/tool-calls.js.map +1 -0
  8. package/dist/agent.js +285 -684
  9. package/dist/agent.js.map +1 -1
  10. package/dist/anton/controller.js +1 -1
  11. package/dist/anton/controller.js.map +1 -1
  12. package/dist/anton/lock.js +0 -3
  13. package/dist/anton/lock.js.map +1 -1
  14. package/dist/anton/parser.js +6 -6
  15. package/dist/anton/parser.js.map +1 -1
  16. package/dist/anton/reporter.js +1 -1
  17. package/dist/anton/reporter.js.map +1 -1
  18. package/dist/bot/commands.js +3 -2
  19. package/dist/bot/commands.js.map +1 -1
  20. package/dist/bot/confirm-telegram.js +2 -1
  21. package/dist/bot/confirm-telegram.js.map +1 -1
  22. package/dist/bot/discord-routing.js +186 -0
  23. package/dist/bot/discord-routing.js.map +1 -0
  24. package/dist/bot/discord-streaming.js +107 -0
  25. package/dist/bot/discord-streaming.js.map +1 -0
  26. package/dist/bot/discord.js +49 -237
  27. package/dist/bot/discord.js.map +1 -1
  28. package/dist/bot/format.js +2 -25
  29. package/dist/bot/format.js.map +1 -1
  30. package/dist/bot/session-manager.js +22 -11
  31. package/dist/bot/session-manager.js.map +1 -1
  32. package/dist/bot/telegram.js +83 -94
  33. package/dist/bot/telegram.js.map +1 -1
  34. package/dist/cli/build-repl-context.js.map +1 -1
  35. package/dist/cli/command-registry.js +2 -1
  36. package/dist/cli/command-registry.js.map +1 -1
  37. package/dist/cli/command-utils.js +27 -0
  38. package/dist/cli/command-utils.js.map +1 -0
  39. package/dist/cli/commands/anton.js +3 -2
  40. package/dist/cli/commands/anton.js.map +1 -1
  41. package/dist/cli/commands/model.js +8 -7
  42. package/dist/cli/commands/model.js.map +1 -1
  43. package/dist/cli/commands/project.js +5 -4
  44. package/dist/cli/commands/project.js.map +1 -1
  45. package/dist/cli/commands/session.js +9 -8
  46. package/dist/cli/commands/session.js.map +1 -1
  47. package/dist/cli/commands/tools.js +4 -3
  48. package/dist/cli/commands/tools.js.map +1 -1
  49. package/dist/cli/input.js +2 -1
  50. package/dist/cli/input.js.map +1 -1
  51. package/dist/cli/repl-dispatch.js +85 -0
  52. package/dist/cli/repl-dispatch.js.map +1 -0
  53. package/dist/cli/runtime-cmds.js +148 -20
  54. package/dist/cli/runtime-cmds.js.map +1 -1
  55. package/dist/cli/service.js +0 -14
  56. package/dist/cli/service.js.map +1 -1
  57. package/dist/cli/setup.js +3 -3
  58. package/dist/cli/setup.js.map +1 -1
  59. package/dist/cli/watch.js +2 -1
  60. package/dist/cli/watch.js.map +1 -1
  61. package/dist/client.js +24 -7
  62. package/dist/client.js.map +1 -1
  63. package/dist/context.js +101 -10
  64. package/dist/context.js.map +1 -1
  65. package/dist/harnesses.js +1 -1
  66. package/dist/harnesses.js.map +1 -1
  67. package/dist/hooks/manager.js +5 -0
  68. package/dist/hooks/manager.js.map +1 -1
  69. package/dist/index.js +13 -64
  70. package/dist/index.js.map +1 -1
  71. package/dist/progress/agent-hooks.js +37 -0
  72. package/dist/progress/agent-hooks.js.map +1 -0
  73. package/dist/progress/ir.js +10 -0
  74. package/dist/progress/ir.js.map +1 -0
  75. package/dist/progress/message-edit-scheduler.js +97 -0
  76. package/dist/progress/message-edit-scheduler.js.map +1 -0
  77. package/dist/progress/progress-message-renderer.js +120 -0
  78. package/dist/progress/progress-message-renderer.js.map +1 -0
  79. package/dist/progress/progress-presenter.js +137 -0
  80. package/dist/progress/progress-presenter.js.map +1 -0
  81. package/dist/progress/serialize-discord.js +72 -0
  82. package/dist/progress/serialize-discord.js.map +1 -0
  83. package/dist/progress/serialize-telegram.js +67 -0
  84. package/dist/progress/serialize-telegram.js.map +1 -0
  85. package/dist/progress/serialize-tui.js +52 -0
  86. package/dist/progress/serialize-tui.js.map +1 -0
  87. package/dist/progress/tool-summary.js +58 -0
  88. package/dist/progress/tool-summary.js.map +1 -0
  89. package/dist/progress/tool-tail.js +48 -0
  90. package/dist/progress/tool-tail.js.map +1 -0
  91. package/dist/progress/turn-progress.js +215 -0
  92. package/dist/progress/turn-progress.js.map +1 -0
  93. package/dist/replay.js +2 -5
  94. package/dist/replay.js.map +1 -1
  95. package/dist/runtime/executor.js +58 -10
  96. package/dist/runtime/executor.js.map +1 -1
  97. package/dist/runtime/planner.js +19 -6
  98. package/dist/runtime/planner.js.map +1 -1
  99. package/dist/runtime/store.js +2 -1
  100. package/dist/runtime/store.js.map +1 -1
  101. package/dist/safety.js +0 -1
  102. package/dist/safety.js.map +1 -1
  103. package/dist/spinner.js +8 -0
  104. package/dist/spinner.js.map +1 -1
  105. package/dist/tools/tool-error.js +97 -0
  106. package/dist/tools/tool-error.js.map +1 -0
  107. package/dist/tools.js +471 -41
  108. package/dist/tools.js.map +1 -1
  109. package/dist/tui/branch-picker.js.map +1 -1
  110. package/dist/tui/command-handler.js.map +1 -1
  111. package/dist/tui/controller.js +91 -28
  112. package/dist/tui/controller.js.map +1 -1
  113. package/dist/tui/render.js +15 -2
  114. package/dist/tui/render.js.map +1 -1
  115. package/dist/tui/state.js +13 -0
  116. package/dist/tui/state.js.map +1 -1
  117. package/dist/upgrade.js.map +1 -1
  118. package/dist/utils.js +17 -0
  119. package/dist/utils.js.map +1 -1
  120. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-routing.js","sourceRoot":"","sources":["../../src/bot/discord-routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { splitDiscord, safeContent } from './discord-routing.js';
2
+ import { formatToolCallSummary } from '../progress/tool-summary.js';
3
+ import { ProgressPresenter } from '../progress/progress-presenter.js';
4
+ import { MessageEditScheduler, classifyDiscordEditError } from '../progress/message-edit-scheduler.js';
5
+ export class DiscordStreamingMessage {
6
+ placeholder;
7
+ channel;
8
+ opts;
9
+ banner = null;
10
+ finalized = false;
11
+ presenter = new ProgressPresenter({
12
+ maxToolLines: 8,
13
+ maxTailLines: 4,
14
+ maxDiffLines: 32,
15
+ maxAssistantChars: 1200,
16
+ discordMaxLen: 1900,
17
+ toolCallSummary: (c) => formatToolCallSummary({ name: c.name, args: c.args }),
18
+ });
19
+ scheduler = null;
20
+ constructor(placeholder, channel, opts) {
21
+ this.placeholder = placeholder;
22
+ this.channel = channel;
23
+ this.opts = opts;
24
+ }
25
+ start() {
26
+ if (this.scheduler || this.finalized)
27
+ return;
28
+ this.presenter.start();
29
+ if (this.placeholder) {
30
+ const every = Math.max(500, Math.floor(this.opts?.editIntervalMs ?? 1500));
31
+ this.scheduler = new MessageEditScheduler({
32
+ intervalMs: every,
33
+ render: () => this.presenter.renderDiscordMarkdown(),
34
+ apply: async (text) => {
35
+ if (!this.placeholder)
36
+ return;
37
+ await this.placeholder.edit(text);
38
+ },
39
+ isDirty: () => this.presenter.isDirty(),
40
+ clearDirty: () => this.presenter.clearDirty(),
41
+ classifyError: classifyDiscordEditError,
42
+ });
43
+ this.scheduler.start();
44
+ }
45
+ }
46
+ stop() {
47
+ if (this.scheduler) {
48
+ this.scheduler.stop();
49
+ this.scheduler = null;
50
+ }
51
+ this.presenter.stop();
52
+ }
53
+ setBanner(text) {
54
+ this.banner = text?.trim() ? text.trim() : null;
55
+ this.presenter.setBanner(this.banner);
56
+ }
57
+ hooks() {
58
+ return this.presenter.hooks();
59
+ }
60
+ async finalize(finalText) {
61
+ this.finalized = true;
62
+ this.stop();
63
+ const snap = this.presenter.snapshot('stop');
64
+ const toolLines = snap.toolLines.slice(-8);
65
+ // Build combined text with informative fallback
66
+ let combined = '';
67
+ if (toolLines.length)
68
+ combined += toolLines.join('\n') + '\n\n';
69
+ if (finalText && finalText.trim()) {
70
+ combined += finalText;
71
+ }
72
+ else if (finalText) {
73
+ combined += '*(response contained only protocol artifacts - no user-visible content)*';
74
+ }
75
+ else {
76
+ combined += '*(no response generated - task may be complete or awaiting further input)*';
77
+ }
78
+ const chunks = splitDiscord(safeContent(combined));
79
+ if (this.placeholder && chunks.length > 0) {
80
+ await this.placeholder.edit(chunks[0]).catch(() => { });
81
+ }
82
+ else if (chunks.length > 0) {
83
+ await this.channel.send(chunks[0]).catch(() => { });
84
+ }
85
+ for (let i = 1; i < chunks.length && i < 10; i++) {
86
+ await this.channel.send(chunks[i]).catch(() => { });
87
+ }
88
+ if (chunks.length > 10) {
89
+ await this.channel.send('[truncated — response too long]').catch(() => { });
90
+ }
91
+ }
92
+ async finalizeError(errMsg) {
93
+ this.finalized = true;
94
+ this.stop();
95
+ const snap = this.presenter.snapshot('stop');
96
+ const toolLines = snap.toolLines.slice(-8);
97
+ const combined = safeContent((toolLines.length ? toolLines.join('\n') + '\n\n' : '') + `❌ ${errMsg}`);
98
+ const chunks = splitDiscord(combined);
99
+ if (this.placeholder && chunks.length > 0) {
100
+ await this.placeholder.edit(chunks[0]).catch(() => { });
101
+ }
102
+ else if (chunks.length > 0) {
103
+ await this.channel.send(chunks[0]).catch(() => { });
104
+ }
105
+ }
106
+ }
107
+ //# sourceMappingURL=discord-streaming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-streaming.js","sourceRoot":"","sources":["../../src/bot/discord-streaming.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { Client, Events, GatewayIntentBits, Partials, REST, Routes, SlashCommandBuilder, } from 'discord.js';
2
2
  import { createSession } from '../agent.js';
3
3
  import { DiscordConfirmProvider } from './confirm-discord.js';
4
- import { sanitizeBotOutputText } from './format.js';
4
+ import { parseAllowedUsers, normalizeApprovalMode, splitDiscord, safeContent, detectEscalation, checkKeywordEscalation, resolveAgentForMessage, sessionKeyForMessage, } from './discord-routing.js';
5
+ import { firstToken } from '../cli/command-utils.js';
5
6
  import { projectDir } from '../utils.js';
6
7
  import { WATCHDOG_RECOMMENDED_TUNING_TEXT, formatWatchdogCancelMessage, resolveWatchdogSettings, shouldRecommendWatchdogTuning } from '../watchdog.js';
7
8
  import path from 'node:path';
@@ -9,183 +10,8 @@ import fs from 'node:fs/promises';
9
10
  import { runAnton } from '../anton/controller.js';
10
11
  import { parseTaskFile } from '../anton/parser.js';
11
12
  import { formatRunSummary, formatProgressBar, formatTaskStart, formatTaskEnd, formatTaskSkip } from '../anton/reporter.js';
12
- function parseAllowedUsers(cfg) {
13
- const fromEnv = process.env.IDLEHANDS_DISCORD_ALLOWED_USERS;
14
- if (fromEnv && fromEnv.trim()) {
15
- return new Set(fromEnv
16
- .split(',')
17
- .map((s) => s.trim())
18
- .filter(Boolean));
19
- }
20
- const values = Array.isArray(cfg.allowed_users) ? cfg.allowed_users : [];
21
- return new Set(values.map((v) => String(v).trim()).filter(Boolean));
22
- }
23
- function normalizeApprovalMode(mode, fallback) {
24
- const m = String(mode ?? '').trim().toLowerCase();
25
- if (m === 'plan' || m === 'default' || m === 'auto-edit' || m === 'yolo')
26
- return m;
27
- return fallback;
28
- }
29
- function splitDiscord(text, limit = 1900) {
30
- if (text.length <= limit)
31
- return [text];
32
- const chunks = [];
33
- let i = 0;
34
- while (i < text.length) {
35
- chunks.push(text.slice(i, i + limit));
36
- i += limit;
37
- }
38
- return chunks;
39
- }
40
- function safeContent(text) {
41
- const t = sanitizeBotOutputText(text).trim();
42
- return t.length ? t : '(empty response)';
43
- }
44
- /**
45
- * Check if the model response contains an escalation request.
46
- * Returns { escalate: true, reason: string } if escalation marker found at start of response.
47
- */
48
- function detectEscalation(text) {
49
- const trimmed = text.trim();
50
- const match = trimmed.match(/^\[ESCALATE:\s*([^\]]+)\]/i);
51
- if (match) {
52
- return { escalate: true, reason: match[1].trim() };
53
- }
54
- return { escalate: false };
55
- }
56
- /** Keyword presets for common escalation triggers */
57
- const KEYWORD_PRESETS = {
58
- coding: ['build', 'implement', 'create', 'develop', 'architect', 'refactor', 'debug', 'fix', 'code', 'program', 'write'],
59
- planning: ['plan', 'design', 'roadmap', 'strategy', 'analyze', 'research', 'evaluate', 'compare'],
60
- complex: ['full', 'complete', 'comprehensive', 'multi-step', 'integrate', 'migration', 'overhaul', 'entire', 'whole'],
61
- };
62
- /**
63
- * Check if text matches a set of keywords.
64
- * Returns matched keywords or empty array if none match.
65
- */
66
- function matchKeywords(text, keywords, presets) {
67
- const allKeywords = [...keywords];
68
- // Add preset keywords
69
- if (presets) {
70
- for (const preset of presets) {
71
- const presetWords = KEYWORD_PRESETS[preset];
72
- if (presetWords)
73
- allKeywords.push(...presetWords);
74
- }
75
- }
76
- if (allKeywords.length === 0)
77
- return [];
78
- const lowerText = text.toLowerCase();
79
- const matched = [];
80
- for (const kw of allKeywords) {
81
- if (kw.startsWith('re:')) {
82
- // Regex pattern
83
- try {
84
- const regex = new RegExp(kw.slice(3), 'i');
85
- if (regex.test(text))
86
- matched.push(kw);
87
- }
88
- catch {
89
- // Invalid regex, skip
90
- }
91
- }
92
- else {
93
- // Word boundary match (case-insensitive)
94
- const wordRegex = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
95
- if (wordRegex.test(lowerText))
96
- matched.push(kw);
97
- }
98
- }
99
- return matched;
100
- }
101
- /**
102
- * Check if user message matches keyword escalation triggers.
103
- * Returns { escalate: true, tier: number, reason: string } if keywords match.
104
- * Tier indicates which model index to escalate to (highest matching tier wins).
105
- */
106
- function checkKeywordEscalation(text, escalation) {
107
- if (!escalation)
108
- return { escalate: false };
109
- // Tiered keyword escalation
110
- if (escalation.tiers && escalation.tiers.length > 0) {
111
- let highestTier = -1;
112
- let highestReason = '';
113
- // Check each tier, highest matching tier wins
114
- for (let i = 0; i < escalation.tiers.length; i++) {
115
- const tier = escalation.tiers[i];
116
- const matched = matchKeywords(text, tier.keywords || [], tier.keyword_presets);
117
- if (matched.length > 0 && i > highestTier) {
118
- highestTier = i;
119
- highestReason = `tier ${i} keyword match: ${matched.slice(0, 3).join(', ')}${matched.length > 3 ? '...' : ''}`;
120
- }
121
- }
122
- if (highestTier >= 0) {
123
- return { escalate: true, tier: highestTier, reason: highestReason };
124
- }
125
- return { escalate: false };
126
- }
127
- // Legacy flat keywords (treated as tier 0)
128
- const matched = matchKeywords(text, escalation.keywords || [], escalation.keyword_presets);
129
- if (matched.length > 0) {
130
- return {
131
- escalate: true,
132
- tier: 0,
133
- reason: `keyword match: ${matched.slice(0, 3).join(', ')}${matched.length > 3 ? '...' : ''}`
134
- };
135
- }
136
- return { escalate: false };
137
- }
138
- /**
139
- * Resolve which agent persona should handle a message.
140
- * Priority: user > channel > guild > default > first agent > null
141
- */
142
- function resolveAgentForMessage(msg, agents, routing) {
143
- const agentMap = agents ?? {};
144
- const agentIds = Object.keys(agentMap);
145
- // No agents configured — return null persona (use global config)
146
- if (agentIds.length === 0) {
147
- return { agentId: '_default', persona: null };
148
- }
149
- const route = routing ?? {};
150
- let resolvedId;
151
- // Priority 1: User-specific routing
152
- if (route.users && route.users[msg.author.id]) {
153
- resolvedId = route.users[msg.author.id];
154
- }
155
- // Priority 2: Channel-specific routing
156
- else if (route.channels && route.channels[msg.channelId]) {
157
- resolvedId = route.channels[msg.channelId];
158
- }
159
- // Priority 3: Guild-specific routing
160
- else if (msg.guildId && route.guilds && route.guilds[msg.guildId]) {
161
- resolvedId = route.guilds[msg.guildId];
162
- }
163
- // Priority 4: Default agent
164
- else if (route.default) {
165
- resolvedId = route.default;
166
- }
167
- // Priority 5: First defined agent
168
- else {
169
- resolvedId = agentIds[0];
170
- }
171
- // Validate the resolved agent exists
172
- const persona = agentMap[resolvedId];
173
- if (!persona) {
174
- // Fallback to first agent if routing points to non-existent agent
175
- const fallbackId = agentIds[0];
176
- return { agentId: fallbackId, persona: agentMap[fallbackId] ?? null };
177
- }
178
- return { agentId: resolvedId, persona };
179
- }
180
- function sessionKeyForMessage(msg, allowGuilds, agentId) {
181
- // Include agentId in session key so switching agents creates a new session
182
- if (allowGuilds) {
183
- // Per-agent+channel+user session in guilds
184
- return `${agentId}:${msg.channelId}:${msg.author.id}`;
185
- }
186
- // DM-only mode: per-agent+user session
187
- return `${agentId}:${msg.author.id}`;
188
- }
13
+ import { DiscordStreamingMessage } from './discord-streaming.js';
14
+ import { chainAgentHooks } from '../progress/agent-hooks.js';
189
15
  export async function startDiscordBot(config, botConfig) {
190
16
  const token = process.env.IDLEHANDS_DISCORD_TOKEN || botConfig.token;
191
17
  if (!token) {
@@ -395,20 +221,31 @@ When you escalate, your request will be re-run on a more capable model.`;
395
221
  managed.lastActivity = Date.now();
396
222
  }
397
223
  function cancelActive(managed) {
398
- if (!managed.inFlight)
399
- return { ok: false, message: 'Nothing running.' };
400
- managed.state = 'canceling';
401
- managed.pendingQueue = [];
402
- try {
403
- managed.activeAbortController?.abort();
224
+ const wasRunning = managed.inFlight;
225
+ const queueSize = managed.pendingQueue.length;
226
+ if (!wasRunning && queueSize === 0) {
227
+ return { ok: false, message: 'Nothing to cancel.' };
404
228
  }
405
- catch { }
406
- try {
407
- managed.session.cancel();
229
+ // Always clear queued work.
230
+ managed.pendingQueue = [];
231
+ if (wasRunning) {
232
+ managed.state = 'canceling';
233
+ try {
234
+ managed.activeAbortController?.abort();
235
+ }
236
+ catch { }
237
+ try {
238
+ managed.session.cancel();
239
+ }
240
+ catch { }
408
241
  }
409
- catch { }
410
242
  managed.lastActivity = Date.now();
411
- return { ok: true, message: '⏹ Cancel requested. Stopping current turn...' };
243
+ const parts = [];
244
+ if (wasRunning)
245
+ parts.push('stopping current task');
246
+ if (queueSize > 0)
247
+ parts.push(`cleared ${queueSize} queued task${queueSize > 1 ? 's' : ''}`);
248
+ return { ok: true, message: `⏹ Cancelled: ${parts.join(', ')}.` };
412
249
  }
413
250
  async function processMessage(managed, msg) {
414
251
  let turn = beginTurn(managed);
@@ -489,14 +326,14 @@ When you escalate, your request will be re-run on a more capable model.`;
489
326
  }
490
327
  }
491
328
  const placeholder = await sendUserVisible(msg, '⏳ Thinking...').catch(() => null);
492
- let streamed = '';
493
- const hooks = {
494
- onToken: (t) => {
329
+ const streamer = new DiscordStreamingMessage(placeholder, msg.channel, { editIntervalMs: 1500 });
330
+ streamer.start();
331
+ const baseHooks = {
332
+ onToken: () => {
495
333
  if (!isTurnActive(managed, turnId))
496
334
  return;
497
335
  markProgress(managed, turnId);
498
336
  watchdogGraceUsed = 0;
499
- streamed += t;
500
337
  },
501
338
  onToolCall: () => {
502
339
  if (!isTurnActive(managed, turnId))
@@ -504,6 +341,12 @@ When you escalate, your request will be re-run on a more capable model.`;
504
341
  markProgress(managed, turnId);
505
342
  watchdogGraceUsed = 0;
506
343
  },
344
+ onToolStream: () => {
345
+ if (!isTurnActive(managed, turnId))
346
+ return;
347
+ markProgress(managed, turnId);
348
+ watchdogGraceUsed = 0;
349
+ },
507
350
  onToolResult: () => {
508
351
  if (!isTurnActive(managed, turnId))
509
352
  return;
@@ -530,9 +373,7 @@ When you escalate, your request will be re-run on a more capable model.`;
530
373
  watchdogGraceUsed += 1;
531
374
  managed.lastProgressAt = Date.now();
532
375
  console.error(`[bot:discord] ${managed.userId} watchdog inactivity on turn ${turnId} — applying grace period (${watchdogGraceUsed}/${watchdogIdleGraceTimeouts})`);
533
- if (placeholder) {
534
- void placeholder.edit('⏳ Still working... model is taking longer than usual.').catch(() => { });
535
- }
376
+ streamer.setBanner('⏳ Still working... model is taking longer than usual.');
536
377
  return;
537
378
  }
538
379
  if (managed.watchdogCompactAttempts < maxWatchdogCompacts) {
@@ -568,17 +409,17 @@ When you escalate, your request will be re-run on a more capable model.`;
568
409
  const attemptController = new AbortController();
569
410
  managed.activeAbortController = attemptController;
570
411
  turn.controller = attemptController;
571
- streamed = '';
572
412
  const askText = isRetryAfterCompaction
573
413
  ? 'Continue working on the task from where you left off. Context was compacted to free memory — do NOT restart from the beginning.'
574
414
  : msg.content;
415
+ const hooks = chainAgentHooks({ signal: attemptController.signal }, baseHooks, streamer.hooks());
575
416
  try {
576
- const result = await managed.session.ask(askText, { ...hooks, signal: attemptController.signal });
417
+ const result = await managed.session.ask(askText, hooks);
577
418
  askComplete = true;
578
419
  if (!isTurnActive(managed, turnId))
579
420
  return;
580
421
  markProgress(managed, turnId);
581
- const finalText = safeContent(streamed || result.text);
422
+ const finalText = safeContent(result.text);
582
423
  // Check for auto-escalation request in response
583
424
  const escalation = managed.agentPersona?.escalation;
584
425
  const autoEscalate = escalation?.auto !== false && escalation?.models?.length;
@@ -592,10 +433,7 @@ When you escalate, your request will be re-run on a more capable model.`;
592
433
  // Get endpoint from tier if defined
593
434
  const tierEndpoint = escalation.tiers?.[nextIndex]?.endpoint;
594
435
  console.error(`[bot:discord] ${managed.userId} auto-escalation requested: ${escResult.reason}${tierEndpoint ? ` @ ${tierEndpoint}` : ''}`);
595
- // Update placeholder with escalation notice
596
- if (placeholder) {
597
- await placeholder.edit(`⚡ Escalating to \`${targetModel}\` (${escResult.reason})...`).catch(() => { });
598
- }
436
+ await streamer.finalizeError(`⚡ Escalating to \`${targetModel}\` (${escResult.reason})...`);
599
437
  // Set up escalation for re-run
600
438
  managed.pendingEscalation = targetModel;
601
439
  managed.currentModelIndex = nextIndex + 1;
@@ -615,30 +453,14 @@ When you escalate, your request will be re-run on a more capable model.`;
615
453
  return;
616
454
  }
617
455
  }
618
- const chunks = splitDiscord(finalText);
619
- if (placeholder) {
620
- await placeholder.edit(chunks[0]).catch(() => { });
621
- }
622
- else {
623
- await sendUserVisible(msg, chunks[0]).catch(() => { });
624
- }
625
- for (let i = 1; i < chunks.length && i < 10; i++) {
626
- if (!isTurnActive(managed, turnId))
627
- break;
628
- await msg.channel.send(chunks[i]).catch(() => { });
629
- }
630
- if (chunks.length > 10 && isTurnActive(managed, turnId)) {
631
- await msg.channel.send('[truncated — response too long]').catch(() => { });
632
- }
456
+ await streamer.finalize(finalText);
633
457
  }
634
458
  catch (e) {
635
459
  const raw = String(e?.message ?? e ?? 'unknown error');
636
460
  const isAbort = raw.includes('AbortError') || raw.toLowerCase().includes('aborted');
637
461
  // If aborted by watchdog compaction, wait for compaction to finish then retry
638
462
  if (isAbort && watchdogCompactPending) {
639
- if (placeholder) {
640
- await placeholder.edit(`🔄 Context too large — compacting and retrying (attempt ${managed.watchdogCompactAttempts}/${maxWatchdogCompacts})...`).catch(() => { });
641
- }
463
+ streamer.setBanner(`🔄 Context too large — compacting and retrying (attempt ${managed.watchdogCompactAttempts}/${maxWatchdogCompacts})...`);
642
464
  // Wait for the async compaction to complete
643
465
  while (watchdogCompactPending) {
644
466
  await new Promise((r) => setTimeout(r, 500));
@@ -658,19 +480,11 @@ When you escalate, your request will be re-run on a more capable model.`;
658
480
  abortReason: raw,
659
481
  prefix: '⏹ ',
660
482
  });
661
- if (placeholder)
662
- await placeholder.edit(cancelMsg).catch(() => { });
663
- else
664
- await sendUserVisible(msg, cancelMsg).catch(() => { });
483
+ await streamer.finalizeError(cancelMsg);
665
484
  }
666
485
  else {
667
486
  const errMsg = raw.slice(0, 400);
668
- if (placeholder) {
669
- await placeholder.edit(`❌ ${errMsg}`).catch(() => { });
670
- }
671
- else {
672
- await sendUserVisible(msg, `❌ ${errMsg}`).catch(() => { });
673
- }
487
+ await streamer.finalizeError(errMsg);
674
488
  }
675
489
  askComplete = true;
676
490
  }
@@ -678,6 +492,7 @@ When you escalate, your request will be re-run on a more capable model.`;
678
492
  }
679
493
  finally {
680
494
  clearInterval(watchdog);
495
+ streamer.stop();
681
496
  finishTurn(managed, turnId);
682
497
  // Auto-deescalate back to base model after each request
683
498
  if (managed.currentModelIndex > 0 && managed.agentPersona?.escalation) {
@@ -902,12 +717,9 @@ When you escalate, your request will be re-run on a more capable model.`;
902
717
  if (!managed) {
903
718
  await interaction.reply('No active session.');
904
719
  }
905
- else if (managed.state !== 'running') {
906
- await interaction.reply('Nothing to cancel.');
907
- }
908
720
  else {
909
- cancelActive(managed);
910
- await interaction.reply('🛑 Cancelling...');
721
+ const res = cancelActive(managed);
722
+ await interaction.reply(res.message);
911
723
  }
912
724
  break;
913
725
  }
@@ -1539,7 +1351,7 @@ When you escalate, your request will be re-run on a more capable model.`;
1539
1351
  const DISCORD_RATE_LIMIT_MS = 15_000;
1540
1352
  async function handleDiscordAnton(managed, msg, content) {
1541
1353
  const args = content.replace(/^\/anton\s*/, '').trim();
1542
- const sub = args.split(/\s+/)[0]?.toLowerCase() || '';
1354
+ const sub = firstToken(args);
1543
1355
  if (!sub || sub === 'status') {
1544
1356
  if (!managed.antonActive) {
1545
1357
  await sendUserVisible(msg, 'No Anton run in progress.').catch(() => { });