pikiclaw 0.2.71 → 0.2.73
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/dist/bot-command-ui.js +5 -11
- package/dist/bot-commands.js +10 -10
- package/dist/bot-feishu.js +55 -13
- package/dist/bot-handler.js +1 -1
- package/dist/bot-orchestration.js +9 -1
- package/dist/bot-telegram-live-preview.js +3 -0
- package/dist/bot-telegram.js +54 -14
- package/dist/bot.js +89 -30
- package/dist/driver-claude.js +307 -8
- package/dist/driver-codex.js +134 -11
- package/package.json +1 -1
package/dist/bot-command-ui.js
CHANGED
|
@@ -134,7 +134,7 @@ export function buildAgentsCommandView(bot, chatId) {
|
|
|
134
134
|
const actions = data.agents
|
|
135
135
|
.filter(agent => agent.installed)
|
|
136
136
|
.map(agent => ({
|
|
137
|
-
label: agent.agent,
|
|
137
|
+
label: agent.version ? `${agent.agent} ${agent.version}` : agent.agent,
|
|
138
138
|
action: { kind: 'agent.switch', agent: agent.agent },
|
|
139
139
|
state: buttonStateFromFlags({ isCurrent: agent.isCurrent }),
|
|
140
140
|
primary: agent.isCurrent,
|
|
@@ -143,15 +143,9 @@ export function buildAgentsCommandView(bot, chatId) {
|
|
|
143
143
|
kind: 'agents',
|
|
144
144
|
title: 'Agents',
|
|
145
145
|
metaLines: [],
|
|
146
|
-
items:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
? (agent.version ? `Version ${agent.version}` : 'Installed')
|
|
150
|
-
: 'Not installed',
|
|
151
|
-
state: buttonStateFromFlags({ isCurrent: agent.isCurrent, unavailable: !agent.installed }),
|
|
152
|
-
})),
|
|
153
|
-
helperText: 'Use the controls below to switch agents.',
|
|
154
|
-
rows: chunkRows(actions, 3),
|
|
146
|
+
items: [],
|
|
147
|
+
emptyText: actions.length ? undefined : 'No installed agents.',
|
|
148
|
+
rows: actions.map(action => [action]),
|
|
155
149
|
};
|
|
156
150
|
}
|
|
157
151
|
const modelsDrafts = new Map();
|
|
@@ -265,7 +259,7 @@ export async function executeCommandAction(bot, chatId, action, opts = {}) {
|
|
|
265
259
|
};
|
|
266
260
|
case 'session.switch': {
|
|
267
261
|
const chat = bot.chat(chatId);
|
|
268
|
-
const result = await bot.fetchSessions(chat.agent);
|
|
262
|
+
const result = await bot.fetchSessions(chat.agent, bot.chatWorkdir(chatId));
|
|
269
263
|
if (!result.ok)
|
|
270
264
|
return { kind: 'noop', message: 'Failed to load sessions' };
|
|
271
265
|
const session = result.sessions.find(entry => entry.sessionId === action.sessionId);
|
package/dist/bot-commands.js
CHANGED
|
@@ -32,7 +32,7 @@ export function getStartData(bot, chatId) {
|
|
|
32
32
|
return {
|
|
33
33
|
...intro,
|
|
34
34
|
agent: cs.agent,
|
|
35
|
-
workdir: bot.
|
|
35
|
+
workdir: bot.chatWorkdir(chatId),
|
|
36
36
|
agentDetails,
|
|
37
37
|
commands,
|
|
38
38
|
};
|
|
@@ -61,7 +61,7 @@ export function summarizeSessionRun(session) {
|
|
|
61
61
|
}
|
|
62
62
|
export async function getSessionsPageData(bot, chatId, page, pageSize = 5) {
|
|
63
63
|
const cs = bot.chat(chatId);
|
|
64
|
-
const res = await bot.fetchSessions(cs.agent);
|
|
64
|
+
const res = await bot.fetchSessions(cs.agent, bot.chatWorkdir(chatId));
|
|
65
65
|
const sessions = res.ok ? res.sessions : [];
|
|
66
66
|
const total = sessions.length;
|
|
67
67
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
@@ -139,7 +139,7 @@ export function getAgentsListData(bot, chatId) {
|
|
|
139
139
|
}
|
|
140
140
|
export function getSkillsListData(bot, chatId) {
|
|
141
141
|
const cs = bot.chat(chatId);
|
|
142
|
-
const skills = bot.fetchSkills().skills
|
|
142
|
+
const skills = bot.fetchSkills(bot.chatWorkdir(chatId)).skills
|
|
143
143
|
.map(skill => {
|
|
144
144
|
const command = buildSkillCommandName(skill.name);
|
|
145
145
|
if (!command)
|
|
@@ -153,7 +153,7 @@ export function getSkillsListData(bot, chatId) {
|
|
|
153
153
|
};
|
|
154
154
|
})
|
|
155
155
|
.filter((skill) => !!skill);
|
|
156
|
-
return { agent: cs.agent, workdir: bot.
|
|
156
|
+
return { agent: cs.agent, workdir: bot.chatWorkdir(chatId), skills };
|
|
157
157
|
}
|
|
158
158
|
function claudeModelSelectionKey(modelId) {
|
|
159
159
|
const value = normalizeClaudeModelId(modelId).toLowerCase();
|
|
@@ -204,7 +204,7 @@ function buildEffortData(bot, agent) {
|
|
|
204
204
|
export async function getModelsListData(bot, chatId) {
|
|
205
205
|
const cs = bot.chat(chatId);
|
|
206
206
|
const currentModel = bot.modelForAgent(cs.agent);
|
|
207
|
-
const res = await bot.fetchModels(cs.agent);
|
|
207
|
+
const res = await bot.fetchModels(cs.agent, bot.chatWorkdir(chatId));
|
|
208
208
|
return {
|
|
209
209
|
agent: cs.agent,
|
|
210
210
|
currentModel,
|
|
@@ -242,22 +242,22 @@ function relSkillPath(workdir, filePath) {
|
|
|
242
242
|
return relative && !relative.startsWith('..') ? relative : filePath;
|
|
243
243
|
}
|
|
244
244
|
export function resolveSkillPrompt(bot, chatId, cmd, args) {
|
|
245
|
-
const
|
|
245
|
+
const wd = bot.chatWorkdir(chatId);
|
|
246
|
+
const skills = bot.fetchSkills(wd).skills;
|
|
246
247
|
const skill = indexSkillsByCommand(skills).get(cmd);
|
|
247
248
|
if (!skill)
|
|
248
249
|
return null;
|
|
249
|
-
const cs = bot.chat(chatId);
|
|
250
250
|
const extra = args.trim();
|
|
251
251
|
const suffix = extra ? ` Additional context: ${extra}` : '';
|
|
252
|
-
const workdirHint = `[Project directory: ${
|
|
252
|
+
const workdirHint = `[Project directory: ${wd}]\n\n`;
|
|
253
253
|
let prompt;
|
|
254
|
-
const paths = getProjectSkillPaths(
|
|
254
|
+
const paths = getProjectSkillPaths(wd, skill.name);
|
|
255
255
|
const skillFile = paths.claudeSkillFile || paths.sharedSkillFile || paths.agentsSkillFile;
|
|
256
256
|
if (skillFile) {
|
|
257
257
|
prompt = `${workdirHint}Read the skill definition at \`${skillFile}\` and execute the instructions defined there.${suffix}`;
|
|
258
258
|
}
|
|
259
259
|
else {
|
|
260
|
-
const fallbackPath = `${
|
|
260
|
+
const fallbackPath = `${wd}/.pikiclaw/skills/${skill.name}/SKILL.md`;
|
|
261
261
|
prompt = `${workdirHint}Read the skill definition at \`${fallbackPath}\` and execute the instructions defined there.${suffix}`;
|
|
262
262
|
}
|
|
263
263
|
return { prompt, skillName: skill.name };
|
package/dist/bot-feishu.js
CHANGED
|
@@ -134,7 +134,6 @@ export class FeishuBot extends Bot {
|
|
|
134
134
|
this.log(`menu: ${commands.length} commands (${skillCount} skills)`);
|
|
135
135
|
}
|
|
136
136
|
afterSwitchWorkdir(_oldPath, _newPath) {
|
|
137
|
-
this.sessionMessages.clear();
|
|
138
137
|
if (!this.channel)
|
|
139
138
|
return;
|
|
140
139
|
void this.setupMenu().catch(err => this.log(`menu refresh failed: ${err}`));
|
|
@@ -187,14 +186,17 @@ export class FeishuBot extends Bot {
|
|
|
187
186
|
return buildSessionTaskId(session, this.nextTaskId++);
|
|
188
187
|
}
|
|
189
188
|
registerSessionMessage(chatId, messageId, session) {
|
|
190
|
-
this.sessionMessages.register(chatId, messageId, session,
|
|
189
|
+
this.sessionMessages.register(chatId, messageId, session, session.workdir);
|
|
191
190
|
}
|
|
192
191
|
registerSessionMessages(chatId, messageIds, session) {
|
|
193
|
-
this.sessionMessages.registerMany(chatId, messageIds, session,
|
|
192
|
+
this.sessionMessages.registerMany(chatId, messageIds, session, session.workdir);
|
|
194
193
|
}
|
|
195
194
|
sessionFromMessage(chatId, messageId) {
|
|
196
|
-
const
|
|
197
|
-
|
|
195
|
+
const sessionRef = this.sessionMessages.resolve(chatId, messageId);
|
|
196
|
+
if (!sessionRef)
|
|
197
|
+
return null;
|
|
198
|
+
return this.getSessionRuntimeByKey(sessionRef.key, { allowAnyWorkdir: true })
|
|
199
|
+
|| this.hydrateSessionRuntime(sessionRef);
|
|
198
200
|
}
|
|
199
201
|
ensureSession(chatId, title, files) {
|
|
200
202
|
return this.ensureSessionForChat(chatId, title, files);
|
|
@@ -373,8 +375,9 @@ export class FeishuBot extends Bot {
|
|
|
373
375
|
await ctx.reply(`**Workdir switched**\n\n\`${oldPath}\`\n↓\n\`${resolvedPath}\``);
|
|
374
376
|
return;
|
|
375
377
|
}
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
+
const wd = this.chatWorkdir(ctx.chatId);
|
|
379
|
+
const browsePath = path.dirname(wd);
|
|
380
|
+
const view = buildSwitchWorkdirCard(wd, browsePath);
|
|
378
381
|
await ctx.channel.sendCard(ctx.chatId, view);
|
|
379
382
|
}
|
|
380
383
|
async cmdRestart(ctx) {
|
|
@@ -533,7 +536,7 @@ export class FeishuBot extends Bot {
|
|
|
533
536
|
}
|
|
534
537
|
const staged = stageSessionFiles({
|
|
535
538
|
agent: session.agent,
|
|
536
|
-
workdir:
|
|
539
|
+
workdir: session.workdir,
|
|
537
540
|
files: msg.files,
|
|
538
541
|
sessionId: session.sessionId,
|
|
539
542
|
title: undefined,
|
|
@@ -571,6 +574,7 @@ export class FeishuBot extends Bot {
|
|
|
571
574
|
agent: session.agent,
|
|
572
575
|
sessionKey: session.key,
|
|
573
576
|
prompt,
|
|
577
|
+
attachments: files,
|
|
574
578
|
startedAt: start,
|
|
575
579
|
sourceMessageId: ctx.messageId,
|
|
576
580
|
});
|
|
@@ -588,9 +592,10 @@ export class FeishuBot extends Bot {
|
|
|
588
592
|
this.registerTaskPlaceholders(taskId, [placeholderId]);
|
|
589
593
|
void this.queueSessionTask(session, async () => {
|
|
590
594
|
let livePreview = null;
|
|
595
|
+
let task = null;
|
|
591
596
|
const abortController = new AbortController();
|
|
592
597
|
try {
|
|
593
|
-
|
|
598
|
+
task = this.markTaskRunning(taskId, () => abortController.abort());
|
|
594
599
|
if (!task || task.cancelled) {
|
|
595
600
|
if (placeholderId) {
|
|
596
601
|
try {
|
|
@@ -633,8 +638,19 @@ export class FeishuBot extends Bot {
|
|
|
633
638
|
const mcpSendFile = this.createMcpSendFileCallback(ctx);
|
|
634
639
|
const result = await this.runStream(prompt, session, files, (nextText, nextThinking, nextActivity = '', meta, plan) => {
|
|
635
640
|
livePreview?.update(nextText, nextThinking, nextActivity, meta, plan);
|
|
636
|
-
}, undefined, mcpSendFile, abortController.signal, this.createCodexHumanLoopHandler(ctx, taskId))
|
|
641
|
+
}, undefined, mcpSendFile, abortController.signal, this.createCodexHumanLoopHandler(ctx, taskId), (steer) => {
|
|
642
|
+
const currentTask = this.activeTasks.get(taskId);
|
|
643
|
+
if (!currentTask || currentTask.cancelled || currentTask.status !== 'running')
|
|
644
|
+
return;
|
|
645
|
+
currentTask.steer = steer;
|
|
646
|
+
});
|
|
637
647
|
await livePreview?.settle();
|
|
648
|
+
if (task?.freezePreviewOnAbort && result.stopReason === 'interrupted') {
|
|
649
|
+
const frozenMessageIds = await this.freezeSteerHandoffPreview(ctx, placeholderId, livePreview);
|
|
650
|
+
this.registerSessionMessages(ctx.chatId, frozenMessageIds, session);
|
|
651
|
+
this.log(`[handleMessage] steer handoff preserved previous preview chat=${ctx.chatId} task=${taskId}`);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
638
654
|
const finalReplyIds = await this.sendFinalReply(ctx, placeholderId, session.agent, result);
|
|
639
655
|
this.registerSessionMessages(ctx.chatId, finalReplyIds, session);
|
|
640
656
|
this.log(`[handleMessage] end chat=${ctx.chatId} agent=${session.agent} ok=${result.ok} session=${result.sessionId || session.sessionId || '(new)'} ` +
|
|
@@ -642,6 +658,12 @@ export class FeishuBot extends Bot {
|
|
|
642
658
|
`tools=${formatToolLog(result.activity)}`);
|
|
643
659
|
}
|
|
644
660
|
catch (e) {
|
|
661
|
+
if (task?.freezePreviewOnAbort && abortController.signal.aborted) {
|
|
662
|
+
const frozenMessageIds = await this.freezeSteerHandoffPreview(ctx, placeholderId, livePreview);
|
|
663
|
+
this.registerSessionMessages(ctx.chatId, frozenMessageIds, session);
|
|
664
|
+
this.log(`[handleMessage] steer handoff preserved preview after abort chat=${ctx.chatId} task=${taskId}`);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
645
667
|
const msgText = String(e?.message || e || 'Unknown error');
|
|
646
668
|
this.log(`[handleMessage] end chat=${ctx.chatId} agent=${session.agent} ok=false session=${session.sessionId || '(new)'} ` +
|
|
647
669
|
`elapsed=${((Date.now() - start) / 1000).toFixed(1)}s error="${msgText.slice(0, 240)}" tools=-`);
|
|
@@ -671,6 +693,25 @@ export class FeishuBot extends Bot {
|
|
|
671
693
|
this.finishTask(taskId);
|
|
672
694
|
});
|
|
673
695
|
}
|
|
696
|
+
async freezeSteerHandoffPreview(ctx, placeholderId, livePreview) {
|
|
697
|
+
if (!placeholderId)
|
|
698
|
+
return [];
|
|
699
|
+
const previewMarkdown = livePreview?.getRenderedPreview()?.trim() || '';
|
|
700
|
+
if (!previewMarkdown)
|
|
701
|
+
return [placeholderId];
|
|
702
|
+
try {
|
|
703
|
+
if (this.channel.isStreamingCard(placeholderId)) {
|
|
704
|
+
await this.channel.endStreaming(placeholderId, 'Steered to a new reply.');
|
|
705
|
+
}
|
|
706
|
+
await this.channel.editMessage(ctx.chatId, placeholderId, previewMarkdown, {
|
|
707
|
+
keyboard: { rows: [] },
|
|
708
|
+
});
|
|
709
|
+
return [placeholderId];
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
return [];
|
|
713
|
+
}
|
|
714
|
+
}
|
|
674
715
|
async sendFinalReply(ctx, placeholderId, agent, result) {
|
|
675
716
|
const rendered = buildFinalReplyRender(agent, result);
|
|
676
717
|
const messageIds = [];
|
|
@@ -808,7 +849,7 @@ export class FeishuBot extends Bot {
|
|
|
808
849
|
async cmdSkill(cmd, args, ctx) {
|
|
809
850
|
const resolved = resolveSkillPrompt(this, ctx.chatId, cmd, args);
|
|
810
851
|
if (!resolved) {
|
|
811
|
-
await ctx.reply(`Skill not found for command /${cmd} in:\n\`${this.
|
|
852
|
+
await ctx.reply(`Skill not found for command /${cmd} in:\n\`${this.chatWorkdir(ctx.chatId)}\``);
|
|
812
853
|
return;
|
|
813
854
|
}
|
|
814
855
|
this.log(`skill: ${resolved.skillName} agent=${this.chat(ctx.chatId).agent}${args.trim() ? ` args="${args.trim()}"` : ''}`);
|
|
@@ -917,7 +958,7 @@ export class FeishuBot extends Bot {
|
|
|
917
958
|
if (!data.startsWith('tsk:steer:'))
|
|
918
959
|
return false;
|
|
919
960
|
const actionId = data.slice('tsk:steer:'.length).trim();
|
|
920
|
-
const result = this.steerTaskByActionId(actionId);
|
|
961
|
+
const result = await this.steerTaskByActionId(actionId);
|
|
921
962
|
if (!result.task)
|
|
922
963
|
return true;
|
|
923
964
|
// The queued task will naturally run next after the running task is interrupted
|
|
@@ -930,7 +971,8 @@ export class FeishuBot extends Bot {
|
|
|
930
971
|
const browsePath = resolveFeishuRegisteredPath(parseInt(pathId, 10));
|
|
931
972
|
if (!browsePath)
|
|
932
973
|
return true;
|
|
933
|
-
const
|
|
974
|
+
const wd = this.chatWorkdir(ctx.chatId);
|
|
975
|
+
const view = buildSwitchWorkdirCard(wd, browsePath, parseInt(pageRaw, 10) || 0);
|
|
934
976
|
await ctx.channel.editCard(ctx.chatId, ctx.messageId, view);
|
|
935
977
|
return true;
|
|
936
978
|
}
|
package/dist/bot-handler.js
CHANGED
|
@@ -16,7 +16,7 @@ import { stageSessionFiles } from './code-agent.js';
|
|
|
16
16
|
export async function stageFilesIntoSession(bot, session, files) {
|
|
17
17
|
const staged = stageSessionFiles({
|
|
18
18
|
agent: session.agent,
|
|
19
|
-
workdir:
|
|
19
|
+
workdir: session.workdir,
|
|
20
20
|
files,
|
|
21
21
|
sessionId: session.sessionId,
|
|
22
22
|
title: undefined,
|
|
@@ -40,7 +40,15 @@ export class SessionMessageRegistry {
|
|
|
40
40
|
chatMessages = new Map();
|
|
41
41
|
this.messages.set(chatId, chatMessages);
|
|
42
42
|
}
|
|
43
|
-
chatMessages.set(messageId,
|
|
43
|
+
chatMessages.set(messageId, {
|
|
44
|
+
key: session.key,
|
|
45
|
+
workdir: session.workdir,
|
|
46
|
+
agent: session.agent,
|
|
47
|
+
sessionId: session.sessionId,
|
|
48
|
+
workspacePath: session.workspacePath ?? null,
|
|
49
|
+
codexCumulative: session.codexCumulative,
|
|
50
|
+
modelId: session.modelId ?? null,
|
|
51
|
+
});
|
|
44
52
|
while (chatMessages.size > this.maxPerChat) {
|
|
45
53
|
const oldest = chatMessages.keys().next();
|
|
46
54
|
if (oldest.done)
|
package/dist/bot-telegram.js
CHANGED
|
@@ -82,7 +82,6 @@ export class TelegramBot extends Bot {
|
|
|
82
82
|
this.log(`menu: ${commands.length} commands (${skillCount} skills)`);
|
|
83
83
|
}
|
|
84
84
|
afterSwitchWorkdir(_oldPath, _newPath) {
|
|
85
|
-
this.sessionMessages.clear();
|
|
86
85
|
if (!this.channel)
|
|
87
86
|
return;
|
|
88
87
|
void this.setupMenu().catch(err => this.log(`menu refresh failed after workdir switch: ${err}`));
|
|
@@ -159,14 +158,17 @@ export class TelegramBot extends Bot {
|
|
|
159
158
|
return buildSessionTaskId(session, this.nextTaskId++);
|
|
160
159
|
}
|
|
161
160
|
registerSessionMessage(chatId, messageId, session) {
|
|
162
|
-
this.sessionMessages.register(chatId, messageId, session,
|
|
161
|
+
this.sessionMessages.register(chatId, messageId, session, session.workdir);
|
|
163
162
|
}
|
|
164
163
|
registerSessionMessages(chatId, messageIds, session) {
|
|
165
|
-
this.sessionMessages.registerMany(chatId, messageIds, session,
|
|
164
|
+
this.sessionMessages.registerMany(chatId, messageIds, session, session.workdir);
|
|
166
165
|
}
|
|
167
166
|
sessionFromMessage(chatId, messageId) {
|
|
168
|
-
const
|
|
169
|
-
|
|
167
|
+
const sessionRef = this.sessionMessages.resolve(chatId, messageId);
|
|
168
|
+
if (!sessionRef)
|
|
169
|
+
return null;
|
|
170
|
+
return this.getSessionRuntimeByKey(sessionRef.key, { allowAnyWorkdir: true })
|
|
171
|
+
|| this.hydrateSessionRuntime(sessionRef);
|
|
170
172
|
}
|
|
171
173
|
ensureSession(chatId, title, files) {
|
|
172
174
|
return this.ensureSessionForChat(chatId, title, files);
|
|
@@ -309,8 +311,9 @@ export class TelegramBot extends Bot {
|
|
|
309
311
|
await ctx.reply(lines.join('\n'), { parseMode: 'HTML' });
|
|
310
312
|
}
|
|
311
313
|
async cmdSwitch(ctx) {
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
+
const wd = this.chatWorkdir(ctx.chatId);
|
|
315
|
+
const browsePath = path.dirname(wd);
|
|
316
|
+
const view = buildSwitchWorkdirView(wd, browsePath);
|
|
314
317
|
await ctx.reply(view.text, { parseMode: 'HTML', keyboard: view.keyboard });
|
|
315
318
|
}
|
|
316
319
|
async cmdHost(ctx) {
|
|
@@ -468,7 +471,7 @@ export class TelegramBot extends Bot {
|
|
|
468
471
|
}
|
|
469
472
|
const staged = stageSessionFiles({
|
|
470
473
|
agent: session.agent,
|
|
471
|
-
workdir:
|
|
474
|
+
workdir: session.workdir,
|
|
472
475
|
files: msg.files,
|
|
473
476
|
sessionId: session.sessionId,
|
|
474
477
|
title: undefined,
|
|
@@ -507,6 +510,7 @@ export class TelegramBot extends Bot {
|
|
|
507
510
|
agent: session.agent,
|
|
508
511
|
sessionKey: session.key,
|
|
509
512
|
prompt,
|
|
513
|
+
attachments: files,
|
|
510
514
|
startedAt: start,
|
|
511
515
|
sourceMessageId: ctx.messageId,
|
|
512
516
|
});
|
|
@@ -531,9 +535,10 @@ export class TelegramBot extends Bot {
|
|
|
531
535
|
this.registerTaskPlaceholders(taskId, [phId]);
|
|
532
536
|
void this.queueSessionTask(session, async () => {
|
|
533
537
|
let livePreview = null;
|
|
538
|
+
let task = null;
|
|
534
539
|
const abortController = new AbortController();
|
|
535
540
|
try {
|
|
536
|
-
|
|
541
|
+
task = this.markTaskRunning(taskId, () => abortController.abort());
|
|
537
542
|
if (!task || task.cancelled) {
|
|
538
543
|
if (phId != null) {
|
|
539
544
|
try {
|
|
@@ -573,8 +578,19 @@ export class TelegramBot extends Bot {
|
|
|
573
578
|
const mcpSendFile = this.createMcpSendFileCallback(ctx, messageThreadId);
|
|
574
579
|
const result = await this.runStream(prompt, session, files, (nextText, nextThinking, nextActivity = '', meta, plan) => {
|
|
575
580
|
livePreview?.update(nextText, nextThinking, nextActivity, meta, plan);
|
|
576
|
-
}, undefined, mcpSendFile, abortController.signal, this.createCodexHumanLoopHandler(ctx, taskId, messageThreadId))
|
|
581
|
+
}, undefined, mcpSendFile, abortController.signal, this.createCodexHumanLoopHandler(ctx, taskId, messageThreadId), (steer) => {
|
|
582
|
+
const currentTask = this.activeTasks.get(taskId);
|
|
583
|
+
if (!currentTask || currentTask.cancelled || currentTask.status !== 'running')
|
|
584
|
+
return;
|
|
585
|
+
currentTask.steer = steer;
|
|
586
|
+
});
|
|
577
587
|
await livePreview?.settle();
|
|
588
|
+
if (task?.freezePreviewOnAbort && result.stopReason === 'interrupted') {
|
|
589
|
+
const frozenMessageIds = await this.freezeSteerHandoffPreview(ctx, phId, livePreview);
|
|
590
|
+
this.registerSessionMessages(ctx.chatId, frozenMessageIds, session);
|
|
591
|
+
this.log(`[handleMessage] steer handoff preserved previous preview chat=${ctx.chatId} task=${taskId}`);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
578
594
|
this.log(`[handleMessage] done agent=${session.agent} ok=${result.ok} session=${result.sessionId || '?'} elapsed=${result.elapsedS.toFixed(1)}s edits=${livePreview?.getEditCount() || 0} ` +
|
|
579
595
|
`tokens=in:${fmtTokens(result.inputTokens)}/cached:${fmtTokens(result.cachedInputTokens)}/out:${fmtTokens(result.outputTokens)}`);
|
|
580
596
|
this.log(`[handleMessage] response preview: "${result.message.slice(0, 150)}"`);
|
|
@@ -583,6 +599,12 @@ export class TelegramBot extends Bot {
|
|
|
583
599
|
this.log(`[handleMessage] final reply sent to chat=${ctx.chatId}`);
|
|
584
600
|
}
|
|
585
601
|
catch (e) {
|
|
602
|
+
if (task?.freezePreviewOnAbort && abortController.signal.aborted) {
|
|
603
|
+
const frozenMessageIds = await this.freezeSteerHandoffPreview(ctx, phId, livePreview);
|
|
604
|
+
this.registerSessionMessages(ctx.chatId, frozenMessageIds, session);
|
|
605
|
+
this.log(`[handleMessage] steer handoff preserved preview after abort chat=${ctx.chatId} task=${taskId}`);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
586
608
|
const msgText = String(e?.message || e || 'Unknown error');
|
|
587
609
|
this.log(`[handleMessage] task failed chat=${ctx.chatId} session=${session.sessionId} error=${msgText}`);
|
|
588
610
|
const errorHtml = `<b>Error</b>\n\n<code>${escapeHtml(msgText.slice(0, 500))}</code>`;
|
|
@@ -612,6 +634,23 @@ export class TelegramBot extends Bot {
|
|
|
612
634
|
this.finishTask(taskId);
|
|
613
635
|
});
|
|
614
636
|
}
|
|
637
|
+
async freezeSteerHandoffPreview(ctx, phId, livePreview) {
|
|
638
|
+
if (phId == null)
|
|
639
|
+
return [];
|
|
640
|
+
const previewHtml = livePreview?.getRenderedPreview()?.trim() || '';
|
|
641
|
+
if (!previewHtml)
|
|
642
|
+
return [phId];
|
|
643
|
+
try {
|
|
644
|
+
await this.channel.editMessage(ctx.chatId, phId, previewHtml, {
|
|
645
|
+
parseMode: 'HTML',
|
|
646
|
+
keyboard: { inline_keyboard: [] },
|
|
647
|
+
});
|
|
648
|
+
return [phId];
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return [];
|
|
652
|
+
}
|
|
653
|
+
}
|
|
615
654
|
/** Create an MCP sendFile callback bound to a Telegram chat context. */
|
|
616
655
|
createMcpSendFileCallback(ctx, messageThreadId) {
|
|
617
656
|
return async (filePath, opts) => {
|
|
@@ -720,7 +759,8 @@ export class TelegramBot extends Bot {
|
|
|
720
759
|
await ctx.answerCallback('Expired, use /switch again');
|
|
721
760
|
return true;
|
|
722
761
|
}
|
|
723
|
-
const
|
|
762
|
+
const wd = this.chatWorkdir(ctx.chatId);
|
|
763
|
+
const view = buildSwitchWorkdirView(wd, browsePath, parseInt(pageRaw, 10) || 0);
|
|
724
764
|
await ctx.editReply(ctx.messageId, view.text, { parseMode: 'HTML', keyboard: view.keyboard });
|
|
725
765
|
await ctx.answerCallback();
|
|
726
766
|
return true;
|
|
@@ -780,7 +820,7 @@ export class TelegramBot extends Bot {
|
|
|
780
820
|
if (!data.startsWith('tsk:steer:'))
|
|
781
821
|
return false;
|
|
782
822
|
const actionId = data.slice('tsk:steer:'.length).trim();
|
|
783
|
-
const result = this.steerTaskByActionId(actionId);
|
|
823
|
+
const result = await this.steerTaskByActionId(actionId);
|
|
784
824
|
if (!result.task) {
|
|
785
825
|
await ctx.answerCallback('This task already finished.');
|
|
786
826
|
return true;
|
|
@@ -789,7 +829,7 @@ export class TelegramBot extends Bot {
|
|
|
789
829
|
await ctx.answerCallback('Task is already running.');
|
|
790
830
|
return true;
|
|
791
831
|
}
|
|
792
|
-
await ctx.answerCallback(result.interrupted ? 'Steering —
|
|
832
|
+
await ctx.answerCallback(result.interrupted ? 'Steering — switching to the queued reply...' : 'No running task to interrupt.');
|
|
793
833
|
return true;
|
|
794
834
|
}
|
|
795
835
|
async handleHumanLoopCallback(data, ctx) {
|
|
@@ -939,7 +979,7 @@ export class TelegramBot extends Bot {
|
|
|
939
979
|
async cmdSkill(cmd, args, ctx) {
|
|
940
980
|
const resolved = resolveSkillPrompt(this, ctx.chatId, cmd, args);
|
|
941
981
|
if (!resolved) {
|
|
942
|
-
await ctx.reply(`Skill not found for command /${cmd} in:\n<code>${escapeHtml(this.
|
|
982
|
+
await ctx.reply(`Skill not found for command /${cmd} in:\n<code>${escapeHtml(this.chatWorkdir(ctx.chatId))}</code>`, { parseMode: 'HTML' });
|
|
943
983
|
return;
|
|
944
984
|
}
|
|
945
985
|
this.log(`skill: ${resolved.skillName} agent=${this.chat(ctx.chatId).agent}${args.trim() ? ` args="${args.trim()}"` : ''}`);
|