@visorcraft/idlehands 1.1.6 → 1.1.8
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/README.md +32 -0
- package/dist/agent/formatting.js +251 -0
- package/dist/agent/formatting.js.map +1 -0
- package/dist/agent/review-artifact.js +147 -0
- package/dist/agent/review-artifact.js.map +1 -0
- package/dist/agent/tool-calls.js +226 -0
- package/dist/agent/tool-calls.js.map +1 -0
- package/dist/agent.js +314 -695
- package/dist/agent.js.map +1 -1
- package/dist/anton/controller.js +1 -1
- package/dist/anton/controller.js.map +1 -1
- package/dist/anton/lock.js +0 -3
- package/dist/anton/lock.js.map +1 -1
- package/dist/anton/parser.js +0 -1
- package/dist/anton/parser.js.map +1 -1
- package/dist/anton/reporter.js +1 -1
- package/dist/anton/reporter.js.map +1 -1
- package/dist/bot/commands.js +3 -2
- package/dist/bot/commands.js.map +1 -1
- package/dist/bot/confirm-telegram.js +2 -1
- package/dist/bot/confirm-telegram.js.map +1 -1
- package/dist/bot/discord-routing.js +179 -0
- package/dist/bot/discord-routing.js.map +1 -0
- package/dist/bot/discord-streaming.js +171 -0
- package/dist/bot/discord-streaming.js.map +1 -0
- package/dist/bot/discord.js +25 -221
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/format.js +2 -25
- package/dist/bot/format.js.map +1 -1
- package/dist/bot/telegram.js +56 -12
- package/dist/bot/telegram.js.map +1 -1
- package/dist/cli/args.js +4 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/build-repl-context.js.map +1 -1
- package/dist/cli/command-registry.js +2 -1
- package/dist/cli/command-registry.js.map +1 -1
- package/dist/cli/command-utils.js +27 -0
- package/dist/cli/command-utils.js.map +1 -0
- package/dist/cli/commands/anton.js +3 -2
- package/dist/cli/commands/anton.js.map +1 -1
- package/dist/cli/commands/model.js +8 -7
- package/dist/cli/commands/model.js.map +1 -1
- package/dist/cli/commands/project.js +5 -4
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/session.js +118 -8
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/tools.js +4 -3
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/cli/input.js +2 -1
- package/dist/cli/input.js.map +1 -1
- package/dist/cli/repl-dispatch.js +85 -0
- package/dist/cli/repl-dispatch.js.map +1 -0
- package/dist/cli/runtime-cmds.js +7 -7
- package/dist/cli/runtime-cmds.js.map +1 -1
- package/dist/cli/service.js +0 -14
- package/dist/cli/service.js.map +1 -1
- package/dist/cli/setup.js +25 -5
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/watch.js +2 -1
- package/dist/cli/watch.js.map +1 -1
- package/dist/client.js +51 -4
- package/dist/client.js.map +1 -1
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -1
- package/dist/context.js +101 -10
- package/dist/context.js.map +1 -1
- package/dist/harnesses.js +1 -1
- package/dist/harnesses.js.map +1 -1
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/loader.js +58 -0
- package/dist/hooks/loader.js.map +1 -0
- package/dist/hooks/manager.js +180 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/plugins/example-console.js +24 -0
- package/dist/hooks/plugins/example-console.js.map +1 -0
- package/dist/hooks/scaffold.js +53 -0
- package/dist/hooks/scaffold.js.map +1 -0
- package/dist/hooks/types.js +8 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.js +16 -64
- package/dist/index.js.map +1 -1
- package/dist/progress/agent-hooks.js +37 -0
- package/dist/progress/agent-hooks.js.map +1 -0
- package/dist/progress/ir.js +7 -0
- package/dist/progress/ir.js.map +1 -0
- package/dist/progress/progress-message-renderer.js +63 -0
- package/dist/progress/progress-message-renderer.js.map +1 -0
- package/dist/progress/serialize-discord.js +60 -0
- package/dist/progress/serialize-discord.js.map +1 -0
- package/dist/progress/serialize-telegram.js +55 -0
- package/dist/progress/serialize-telegram.js.map +1 -0
- package/dist/progress/serialize-tui.js +39 -0
- package/dist/progress/serialize-tui.js.map +1 -0
- package/dist/progress/tool-summary.js +58 -0
- package/dist/progress/tool-summary.js.map +1 -0
- package/dist/progress/tool-tail.js +48 -0
- package/dist/progress/tool-tail.js.map +1 -0
- package/dist/progress/turn-progress.js +215 -0
- package/dist/progress/turn-progress.js.map +1 -0
- package/dist/replay.js +2 -5
- package/dist/replay.js.map +1 -1
- package/dist/safety.js +0 -1
- package/dist/safety.js.map +1 -1
- package/dist/spinner.js +8 -0
- package/dist/spinner.js.map +1 -1
- package/dist/tools.js +422 -29
- package/dist/tools.js.map +1 -1
- package/dist/tui/branch-picker.js.map +1 -1
- package/dist/tui/command-handler.js.map +1 -1
- package/dist/tui/controller.js +417 -33
- package/dist/tui/controller.js.map +1 -1
- package/dist/tui/keymap.js +15 -0
- package/dist/tui/keymap.js.map +1 -1
- package/dist/tui/render.js +115 -3
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/state.js +82 -1
- package/dist/tui/state.js.map +1 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { splitDiscord, safeContent } from './discord-routing.js';
|
|
2
|
+
import { formatToolCallSummary } from '../progress/tool-summary.js';
|
|
3
|
+
import { TurnProgressController } from '../progress/turn-progress.js';
|
|
4
|
+
import { ToolTailBuffer } from '../progress/tool-tail.js';
|
|
5
|
+
import { ProgressMessageRenderer } from '../progress/progress-message-renderer.js';
|
|
6
|
+
import { renderDiscordMarkdown } from '../progress/serialize-discord.js';
|
|
7
|
+
export class DiscordStreamingMessage {
|
|
8
|
+
placeholder;
|
|
9
|
+
channel;
|
|
10
|
+
opts;
|
|
11
|
+
buffer = '';
|
|
12
|
+
toolLines = [];
|
|
13
|
+
lastToolLine = '';
|
|
14
|
+
lastToolRepeat = 0;
|
|
15
|
+
statusLine = '⏳ Thinking...';
|
|
16
|
+
banner = null;
|
|
17
|
+
tails = new ToolTailBuffer({ maxChars: 4096, maxLines: 4 });
|
|
18
|
+
activeToolId = null;
|
|
19
|
+
timer = null;
|
|
20
|
+
dirty = true;
|
|
21
|
+
finalized = false;
|
|
22
|
+
progress = new TurnProgressController((snap) => {
|
|
23
|
+
this.statusLine = snap.statusLine;
|
|
24
|
+
this.dirty = true;
|
|
25
|
+
}, {
|
|
26
|
+
heartbeatMs: 1000,
|
|
27
|
+
bucketMs: 5000,
|
|
28
|
+
maxToolLines: 8,
|
|
29
|
+
toolCallSummary: (c) => formatToolCallSummary({ name: c.name, args: c.args }),
|
|
30
|
+
});
|
|
31
|
+
renderer = new ProgressMessageRenderer({
|
|
32
|
+
maxToolLines: 6,
|
|
33
|
+
maxTailLines: 4,
|
|
34
|
+
maxAssistantChars: 1200,
|
|
35
|
+
});
|
|
36
|
+
constructor(placeholder, channel, opts) {
|
|
37
|
+
this.placeholder = placeholder;
|
|
38
|
+
this.channel = channel;
|
|
39
|
+
this.opts = opts;
|
|
40
|
+
}
|
|
41
|
+
start() {
|
|
42
|
+
if (this.timer)
|
|
43
|
+
return;
|
|
44
|
+
this.progress.start();
|
|
45
|
+
const every = Math.max(500, Math.floor(this.opts?.editIntervalMs ?? 1500));
|
|
46
|
+
this.timer = setInterval(() => void this.flush(), every);
|
|
47
|
+
}
|
|
48
|
+
stop() {
|
|
49
|
+
if (this.timer) {
|
|
50
|
+
clearInterval(this.timer);
|
|
51
|
+
this.timer = null;
|
|
52
|
+
}
|
|
53
|
+
this.progress.stop();
|
|
54
|
+
}
|
|
55
|
+
setBanner(text) {
|
|
56
|
+
this.banner = text?.trim() ? text.trim() : null;
|
|
57
|
+
this.dirty = true;
|
|
58
|
+
}
|
|
59
|
+
hooks() {
|
|
60
|
+
return {
|
|
61
|
+
onToken: (t) => {
|
|
62
|
+
this.buffer += t;
|
|
63
|
+
this.progress.hooks.onToken?.(t);
|
|
64
|
+
this.dirty = true;
|
|
65
|
+
},
|
|
66
|
+
onToolCall: (call) => {
|
|
67
|
+
this.progress.hooks.onToolCall?.(call);
|
|
68
|
+
this.activeToolId = call.id;
|
|
69
|
+
this.tails.reset(call.id, call.name);
|
|
70
|
+
const line = `◆ ${formatToolCallSummary({ name: call.name, args: call.args })}...`;
|
|
71
|
+
if (this.lastToolLine === line && this.toolLines.length > 0) {
|
|
72
|
+
this.lastToolRepeat += 1;
|
|
73
|
+
this.toolLines[this.toolLines.length - 1] = `${line} (x${this.lastToolRepeat + 1})`;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.lastToolLine = line;
|
|
77
|
+
this.lastToolRepeat = 0;
|
|
78
|
+
this.toolLines.push(line);
|
|
79
|
+
if (this.toolLines.length > 8)
|
|
80
|
+
this.toolLines.splice(0, this.toolLines.length - 8);
|
|
81
|
+
}
|
|
82
|
+
this.dirty = true;
|
|
83
|
+
},
|
|
84
|
+
onToolStream: (ev) => {
|
|
85
|
+
if (!this.activeToolId || ev.id !== this.activeToolId)
|
|
86
|
+
return;
|
|
87
|
+
this.tails.push(ev);
|
|
88
|
+
this.dirty = true;
|
|
89
|
+
},
|
|
90
|
+
onToolResult: (result) => {
|
|
91
|
+
this.progress.hooks.onToolResult?.(result);
|
|
92
|
+
this.lastToolLine = '';
|
|
93
|
+
this.lastToolRepeat = 0;
|
|
94
|
+
if (this.toolLines.length > 0) {
|
|
95
|
+
const icon = result.success ? '✓' : '✗';
|
|
96
|
+
this.toolLines[this.toolLines.length - 1] = `${icon} ${result.name}: ${result.summary}`;
|
|
97
|
+
}
|
|
98
|
+
if (this.activeToolId === result.id) {
|
|
99
|
+
this.tails.clear(result.id);
|
|
100
|
+
this.activeToolId = null;
|
|
101
|
+
}
|
|
102
|
+
this.dirty = true;
|
|
103
|
+
},
|
|
104
|
+
onTurnEnd: (stats) => {
|
|
105
|
+
this.progress.hooks.onTurnEnd?.(stats);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
renderProgressText() {
|
|
110
|
+
const tail = this.activeToolId ? this.tails.get(this.activeToolId) : null;
|
|
111
|
+
const doc = this.renderer.render({
|
|
112
|
+
banner: this.banner,
|
|
113
|
+
statusLine: this.statusLine || '⏳ Thinking...',
|
|
114
|
+
toolLines: this.toolLines,
|
|
115
|
+
toolTail: tail ? { name: tail.name, stream: tail.stream, lines: tail.lines } : null,
|
|
116
|
+
assistantMarkdown: this.buffer.trim() ? safeContent(this.buffer) : null,
|
|
117
|
+
});
|
|
118
|
+
return renderDiscordMarkdown(doc, { maxLen: 1900 });
|
|
119
|
+
}
|
|
120
|
+
async flush() {
|
|
121
|
+
if (this.finalized)
|
|
122
|
+
return;
|
|
123
|
+
if (!this.dirty)
|
|
124
|
+
return;
|
|
125
|
+
this.dirty = false;
|
|
126
|
+
const text = this.renderProgressText();
|
|
127
|
+
try {
|
|
128
|
+
if (this.placeholder) {
|
|
129
|
+
await this.placeholder.edit(text);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// ignore edit failures
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async finalize(finalText) {
|
|
137
|
+
this.finalized = true;
|
|
138
|
+
this.stop();
|
|
139
|
+
const snap = this.progress.snapshot('stop');
|
|
140
|
+
const toolLines = snap.toolLines.slice(-8);
|
|
141
|
+
const combined = safeContent((toolLines.length ? toolLines.join('\n') + '\n\n' : '') + (finalText ?? ''));
|
|
142
|
+
const chunks = splitDiscord(combined);
|
|
143
|
+
if (this.placeholder && chunks.length > 0) {
|
|
144
|
+
await this.placeholder.edit(chunks[0]).catch(() => { });
|
|
145
|
+
}
|
|
146
|
+
else if (chunks.length > 0) {
|
|
147
|
+
await this.channel.send(chunks[0]).catch(() => { });
|
|
148
|
+
}
|
|
149
|
+
for (let i = 1; i < chunks.length && i < 10; i++) {
|
|
150
|
+
await this.channel.send(chunks[i]).catch(() => { });
|
|
151
|
+
}
|
|
152
|
+
if (chunks.length > 10) {
|
|
153
|
+
await this.channel.send('[truncated — response too long]').catch(() => { });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async finalizeError(errMsg) {
|
|
157
|
+
this.finalized = true;
|
|
158
|
+
this.stop();
|
|
159
|
+
const snap = this.progress.snapshot('stop');
|
|
160
|
+
const toolLines = snap.toolLines.slice(-8);
|
|
161
|
+
const combined = safeContent((toolLines.length ? toolLines.join('\n') + '\n\n' : '') + `❌ ${errMsg}`);
|
|
162
|
+
const chunks = splitDiscord(combined);
|
|
163
|
+
if (this.placeholder && chunks.length > 0) {
|
|
164
|
+
await this.placeholder.edit(chunks[0]).catch(() => { });
|
|
165
|
+
}
|
|
166
|
+
else if (chunks.length > 0) {
|
|
167
|
+
await this.channel.send(chunks[0]).catch(() => { });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# 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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"}
|
package/dist/bot/discord.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
13
|
-
|
|
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) {
|
|
@@ -489,14 +315,14 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
489
315
|
}
|
|
490
316
|
}
|
|
491
317
|
const placeholder = await sendUserVisible(msg, '⏳ Thinking...').catch(() => null);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
318
|
+
const streamer = new DiscordStreamingMessage(placeholder, msg.channel, { editIntervalMs: 1500 });
|
|
319
|
+
streamer.start();
|
|
320
|
+
const baseHooks = {
|
|
321
|
+
onToken: () => {
|
|
495
322
|
if (!isTurnActive(managed, turnId))
|
|
496
323
|
return;
|
|
497
324
|
markProgress(managed, turnId);
|
|
498
325
|
watchdogGraceUsed = 0;
|
|
499
|
-
streamed += t;
|
|
500
326
|
},
|
|
501
327
|
onToolCall: () => {
|
|
502
328
|
if (!isTurnActive(managed, turnId))
|
|
@@ -504,6 +330,12 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
504
330
|
markProgress(managed, turnId);
|
|
505
331
|
watchdogGraceUsed = 0;
|
|
506
332
|
},
|
|
333
|
+
onToolStream: () => {
|
|
334
|
+
if (!isTurnActive(managed, turnId))
|
|
335
|
+
return;
|
|
336
|
+
markProgress(managed, turnId);
|
|
337
|
+
watchdogGraceUsed = 0;
|
|
338
|
+
},
|
|
507
339
|
onToolResult: () => {
|
|
508
340
|
if (!isTurnActive(managed, turnId))
|
|
509
341
|
return;
|
|
@@ -530,9 +362,7 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
530
362
|
watchdogGraceUsed += 1;
|
|
531
363
|
managed.lastProgressAt = Date.now();
|
|
532
364
|
console.error(`[bot:discord] ${managed.userId} watchdog inactivity on turn ${turnId} — applying grace period (${watchdogGraceUsed}/${watchdogIdleGraceTimeouts})`);
|
|
533
|
-
|
|
534
|
-
void placeholder.edit('⏳ Still working... model is taking longer than usual.').catch(() => { });
|
|
535
|
-
}
|
|
365
|
+
streamer.setBanner('⏳ Still working... model is taking longer than usual.');
|
|
536
366
|
return;
|
|
537
367
|
}
|
|
538
368
|
if (managed.watchdogCompactAttempts < maxWatchdogCompacts) {
|
|
@@ -568,17 +398,17 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
568
398
|
const attemptController = new AbortController();
|
|
569
399
|
managed.activeAbortController = attemptController;
|
|
570
400
|
turn.controller = attemptController;
|
|
571
|
-
streamed = '';
|
|
572
401
|
const askText = isRetryAfterCompaction
|
|
573
402
|
? 'Continue working on the task from where you left off. Context was compacted to free memory — do NOT restart from the beginning.'
|
|
574
403
|
: msg.content;
|
|
404
|
+
const hooks = chainAgentHooks({ signal: attemptController.signal }, baseHooks, streamer.hooks());
|
|
575
405
|
try {
|
|
576
|
-
const result = await managed.session.ask(askText,
|
|
406
|
+
const result = await managed.session.ask(askText, hooks);
|
|
577
407
|
askComplete = true;
|
|
578
408
|
if (!isTurnActive(managed, turnId))
|
|
579
409
|
return;
|
|
580
410
|
markProgress(managed, turnId);
|
|
581
|
-
const finalText = safeContent(
|
|
411
|
+
const finalText = safeContent(result.text);
|
|
582
412
|
// Check for auto-escalation request in response
|
|
583
413
|
const escalation = managed.agentPersona?.escalation;
|
|
584
414
|
const autoEscalate = escalation?.auto !== false && escalation?.models?.length;
|
|
@@ -592,10 +422,7 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
592
422
|
// Get endpoint from tier if defined
|
|
593
423
|
const tierEndpoint = escalation.tiers?.[nextIndex]?.endpoint;
|
|
594
424
|
console.error(`[bot:discord] ${managed.userId} auto-escalation requested: ${escResult.reason}${tierEndpoint ? ` @ ${tierEndpoint}` : ''}`);
|
|
595
|
-
|
|
596
|
-
if (placeholder) {
|
|
597
|
-
await placeholder.edit(`⚡ Escalating to \`${targetModel}\` (${escResult.reason})...`).catch(() => { });
|
|
598
|
-
}
|
|
425
|
+
await streamer.finalizeError(`⚡ Escalating to \`${targetModel}\` (${escResult.reason})...`);
|
|
599
426
|
// Set up escalation for re-run
|
|
600
427
|
managed.pendingEscalation = targetModel;
|
|
601
428
|
managed.currentModelIndex = nextIndex + 1;
|
|
@@ -615,30 +442,14 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
615
442
|
return;
|
|
616
443
|
}
|
|
617
444
|
}
|
|
618
|
-
|
|
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
|
-
}
|
|
445
|
+
await streamer.finalize(finalText);
|
|
633
446
|
}
|
|
634
447
|
catch (e) {
|
|
635
448
|
const raw = String(e?.message ?? e ?? 'unknown error');
|
|
636
449
|
const isAbort = raw.includes('AbortError') || raw.toLowerCase().includes('aborted');
|
|
637
450
|
// If aborted by watchdog compaction, wait for compaction to finish then retry
|
|
638
451
|
if (isAbort && watchdogCompactPending) {
|
|
639
|
-
|
|
640
|
-
await placeholder.edit(`🔄 Context too large — compacting and retrying (attempt ${managed.watchdogCompactAttempts}/${maxWatchdogCompacts})...`).catch(() => { });
|
|
641
|
-
}
|
|
452
|
+
streamer.setBanner(`🔄 Context too large — compacting and retrying (attempt ${managed.watchdogCompactAttempts}/${maxWatchdogCompacts})...`);
|
|
642
453
|
// Wait for the async compaction to complete
|
|
643
454
|
while (watchdogCompactPending) {
|
|
644
455
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -658,19 +469,11 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
658
469
|
abortReason: raw,
|
|
659
470
|
prefix: '⏹ ',
|
|
660
471
|
});
|
|
661
|
-
|
|
662
|
-
await placeholder.edit(cancelMsg).catch(() => { });
|
|
663
|
-
else
|
|
664
|
-
await sendUserVisible(msg, cancelMsg).catch(() => { });
|
|
472
|
+
await streamer.finalizeError(cancelMsg);
|
|
665
473
|
}
|
|
666
474
|
else {
|
|
667
475
|
const errMsg = raw.slice(0, 400);
|
|
668
|
-
|
|
669
|
-
await placeholder.edit(`❌ ${errMsg}`).catch(() => { });
|
|
670
|
-
}
|
|
671
|
-
else {
|
|
672
|
-
await sendUserVisible(msg, `❌ ${errMsg}`).catch(() => { });
|
|
673
|
-
}
|
|
476
|
+
await streamer.finalizeError(errMsg);
|
|
674
477
|
}
|
|
675
478
|
askComplete = true;
|
|
676
479
|
}
|
|
@@ -678,6 +481,7 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
678
481
|
}
|
|
679
482
|
finally {
|
|
680
483
|
clearInterval(watchdog);
|
|
484
|
+
streamer.stop();
|
|
681
485
|
finishTurn(managed, turnId);
|
|
682
486
|
// Auto-deescalate back to base model after each request
|
|
683
487
|
if (managed.currentModelIndex > 0 && managed.agentPersona?.escalation) {
|
|
@@ -1539,7 +1343,7 @@ When you escalate, your request will be re-run on a more capable model.`;
|
|
|
1539
1343
|
const DISCORD_RATE_LIMIT_MS = 15_000;
|
|
1540
1344
|
async function handleDiscordAnton(managed, msg, content) {
|
|
1541
1345
|
const args = content.replace(/^\/anton\s*/, '').trim();
|
|
1542
|
-
const sub = args
|
|
1346
|
+
const sub = firstToken(args);
|
|
1543
1347
|
if (!sub || sub === 'status') {
|
|
1544
1348
|
if (!managed.antonActive) {
|
|
1545
1349
|
await sendUserVisible(msg, 'No Anton run in progress.').catch(() => { });
|