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.
@@ -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: data.agents.map(agent => ({
147
- label: agent.agent,
148
- detail: agent.installed
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);
@@ -32,7 +32,7 @@ export function getStartData(bot, chatId) {
32
32
  return {
33
33
  ...intro,
34
34
  agent: cs.agent,
35
- workdir: bot.workdir,
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.workdir, skills };
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 skills = bot.fetchSkills().skills;
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: ${bot.workdir}]\n\n`;
252
+ const workdirHint = `[Project directory: ${wd}]\n\n`;
253
253
  let prompt;
254
- const paths = getProjectSkillPaths(bot.workdir, skill.name);
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 = `${bot.workdir}/.pikiclaw/skills/${skill.name}/SKILL.md`;
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 };
@@ -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, this.workdir);
189
+ this.sessionMessages.register(chatId, messageId, session, session.workdir);
191
190
  }
192
191
  registerSessionMessages(chatId, messageIds, session) {
193
- this.sessionMessages.registerMany(chatId, messageIds, session, this.workdir);
192
+ this.sessionMessages.registerMany(chatId, messageIds, session, session.workdir);
194
193
  }
195
194
  sessionFromMessage(chatId, messageId) {
196
- const sessionKey = this.sessionMessages.resolve(chatId, messageId);
197
- return this.getSessionRuntimeByKey(sessionKey);
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 browsePath = path.dirname(this.workdir);
377
- const view = buildSwitchWorkdirCard(this.workdir, browsePath);
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: this.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
- const task = this.markTaskRunning(taskId, () => abortController.abort());
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.workdir}\``);
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 view = buildSwitchWorkdirCard(this.workdir, browsePath, parseInt(pageRaw, 10) || 0);
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
  }
@@ -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: bot.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, session.key);
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)
@@ -104,6 +104,9 @@ export class LivePreview {
104
104
  getEditCount() {
105
105
  return this.editCount;
106
106
  }
107
+ getRenderedPreview() {
108
+ return this.lastPreview;
109
+ }
107
110
  stopFeedback() {
108
111
  if (this.heartbeatTimer) {
109
112
  clearInterval(this.heartbeatTimer);
@@ -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, this.workdir);
161
+ this.sessionMessages.register(chatId, messageId, session, session.workdir);
163
162
  }
164
163
  registerSessionMessages(chatId, messageIds, session) {
165
- this.sessionMessages.registerMany(chatId, messageIds, session, this.workdir);
164
+ this.sessionMessages.registerMany(chatId, messageIds, session, session.workdir);
166
165
  }
167
166
  sessionFromMessage(chatId, messageId) {
168
- const sessionKey = this.sessionMessages.resolve(chatId, messageId);
169
- return this.getSessionRuntimeByKey(sessionKey);
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 browsePath = path.dirname(this.workdir);
313
- const view = buildSwitchWorkdirView(this.workdir, browsePath);
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: this.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
- const task = this.markTaskRunning(taskId, () => abortController.abort());
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 view = buildSwitchWorkdirView(this.workdir, browsePath, parseInt(pageRaw, 10) || 0);
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 — interrupting current task...' : 'No running task to interrupt.');
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.workdir)}</code>`, { parseMode: 'HTML' });
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()}"` : ''}`);