pikiloom 0.4.15 → 0.4.17

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 (41) hide show
  1. package/README.md +2 -1
  2. package/dashboard/dist/assets/{AgentTab-CKoy_-w4.js → AgentTab-CDVhy5K1.js} +1 -1
  3. package/dashboard/dist/assets/{DirBrowser-DpbuN0OL.js → DirBrowser-BElI1-4D.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-ymr7K8dU.js → ExtensionsTab-BB8ipJ77.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-CaTtCn3l.js → IMAccessTab-IZt_yXoG.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-DA-9kJxp.js → Modal-C1EAGSL1.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-BkLIRnNK.js → Modals-DYUV5yR9.js} +1 -1
  8. package/dashboard/dist/assets/{Select-B0pZtuzF.js → Select-BnsbE6Qv.js} +1 -1
  9. package/dashboard/dist/assets/SessionPanel-Ca_TVTT1.js +1 -0
  10. package/dashboard/dist/assets/{SystemTab-B9TcGMzc.js → SystemTab-Dk6k2OTt.js} +1 -1
  11. package/dashboard/dist/assets/index-CK-3CNRp.js +3 -0
  12. package/dashboard/dist/assets/index-CnJsD381.js +23 -0
  13. package/dashboard/dist/assets/index-dzfjF9Js.css +1 -0
  14. package/dashboard/dist/assets/{shared-i_XUH0xm.js → shared-CZVD0MJD.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/artifacts.js +160 -0
  17. package/dist/agent/images.js +51 -24
  18. package/dist/agent/index.js +4 -2
  19. package/dist/agent/mcp/bridge.js +201 -7
  20. package/dist/agent/mcp/extensions.js +20 -9
  21. package/dist/agent/mcp/tools/workspace.js +4 -3
  22. package/dist/agent/stream.js +3 -2
  23. package/dist/bot/bot.js +83 -4
  24. package/dist/bot/commands.js +48 -2
  25. package/dist/bot/menu.js +1 -0
  26. package/dist/bot/session-hub.js +1 -1
  27. package/dist/channels/dingtalk/bot.js +9 -1
  28. package/dist/channels/discord/bot.js +9 -1
  29. package/dist/channels/feishu/bot.js +8 -1
  30. package/dist/channels/slack/bot.js +9 -1
  31. package/dist/channels/telegram/bot.js +8 -1
  32. package/dist/channels/wecom/bot.js +9 -1
  33. package/dist/channels/weixin/bot.js +9 -1
  34. package/dist/cli/main.js +1 -0
  35. package/dist/dashboard/routes/config.js +134 -12
  36. package/dist/dashboard/routes/sessions.js +108 -27
  37. package/package.json +1 -1
  38. package/dashboard/dist/assets/SessionPanel-CYQtZZNX.js +0 -1
  39. package/dashboard/dist/assets/index-BCYshErN.js +0 -3
  40. package/dashboard/dist/assets/index-C5irxzzD.js +0 -23
  41. package/dashboard/dist/assets/index-FD86DEDF.css +0 -1
package/dist/bot/bot.js CHANGED
@@ -7,7 +7,7 @@ import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import { execSync, spawn } from 'node:child_process';
9
9
  import { getActiveUserConfig, loadWorkspaces, onUserConfigChange, resolveUserWorkdir, setUserWorkdir, updateUserConfig } from '../core/config/user-config.js';
10
- import { doStream, ensureManagedSession, findManagedThreadSession, getSessionStoredConfig, getUsage, initializeProjectSkills, listAgents, resolveAgentModels, resolveDefaultAgent, listSkills, stageSessionFiles, reconcileOrphanedRunningSessions, getAgentBoundModelId, setAgentBoundModelId, collapseSkillPrompt, readGoal, accountTurn, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, bumpContinuationCount, pauseGoal, resumeGoal, setGoal as setGoalState, clearGoal as clearGoalState, setCodexGoal, getCodexGoal, clearCodexGoal, pauseCodexGoal, resumeCodexGoal, getClaudeNativeGoal, buildClaudeSetGoalPrompt, buildClaudeClearGoalPrompt, isPendingSessionId, } from '../agent/index.js';
10
+ import { doStream, ensureManagedSession, findManagedThreadSession, getSessionStoredConfig, getUsage, initializeProjectSkills, listAgents, resolveAgentModels, resolveDefaultAgent, listSkills, stageSessionFiles, reconcileOrphanedRunningSessions, getAgentBoundModelId, setAgentBoundModelId, collapseSkillPrompt, readGoal, accountTurn, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, bumpContinuationCount, pauseGoal, resumeGoal, setGoal as setGoalState, clearGoal as clearGoalState, setCodexGoal, getCodexGoal, clearCodexGoal, pauseCodexGoal, resumeCodexGoal, getClaudeNativeGoal, buildClaudeSetGoalPrompt, buildClaudeClearGoalPrompt, deliverArtifact, attachmentUrl, isPendingSessionId, } from '../agent/index.js';
11
11
  import { compactForHandover, describeHandoverRef } from '../agent/handover.js';
12
12
  import { getActiveProfileId, setActiveProfile } from '../model/index.js';
13
13
  import { querySessions, querySessionTail, updateSession, } from './session-hub.js';
@@ -58,10 +58,14 @@ function appendExtraPrompt(base, extra) {
58
58
  return lhs;
59
59
  return `${lhs}\n\n${rhs}`;
60
60
  }
61
+ // NOTE: the `[Artifact Return]` header is a load-bearing marker — both
62
+ // `stripInjectedPrompts` (bot/streaming.ts) and Gemini's
63
+ // `GEMINI_SYSTEM_BLOCK_SENTINELS` key on it to strip this injected block from
64
+ // displayed user messages. Keep the token in sync if you ever change it.
61
65
  function buildMcpDeliveryPrompt() {
62
66
  return [
63
67
  '[Artifact Return]',
64
- 'This is an IM/chat conversation, so pay attention to the IM tools.',
68
+ 'To hand a file to the user — a screenshot, report, archive, generated asset, anything they asked you to "send" — call the `im_send_file` tool with the file path and a short caption. It is delivered through whatever terminal the user is on (an IM chat or the web dashboard) and stays retrievable even when they are connected remotely. Do NOT just print a local filesystem path: a remote user cannot open paths on this machine.',
65
69
  ].join('\n');
66
70
  }
67
71
  function buildClaudeAskUserPrompt() {
@@ -360,6 +364,14 @@ export class Bot {
360
364
  }
361
365
  break;
362
366
  }
367
+ case 'artifact': {
368
+ const snap = this.streamSnapshots.get(sessionKey);
369
+ if (snap) {
370
+ snap.artifacts = [...(snap.artifacts || []), event.artifact];
371
+ snap.updatedAt = now;
372
+ }
373
+ break;
374
+ }
363
375
  case 'done': {
364
376
  const prev = this.streamSnapshots.get(sessionKey);
365
377
  this.streamSnapshots.set(sessionKey, {
@@ -375,6 +387,7 @@ export class Bot {
375
387
  model: prev?.model ?? null,
376
388
  effort: prev?.effort ?? null,
377
389
  previewMeta: prev?.previewMeta ?? null,
390
+ artifacts: prev?.artifacts,
378
391
  startedAt: prev?.startedAt,
379
392
  queuedTaskIds: prev?.queuedTaskIds,
380
393
  updatedAt: now,
@@ -482,6 +495,58 @@ export class Bot {
482
495
  emitStreamCancelled(taskId, fallbackKey) {
483
496
  this.emitStream(this.liveSessionKey(taskId, fallbackKey), { type: 'cancelled', taskId });
484
497
  }
498
+ /**
499
+ * Wrap a per-turn `im_send_file` callback so every artifact delivery is
500
+ * recorded to the session's durable manifest (agent/artifacts.ts) and
501
+ * mirrored live into the running turn's snapshot — on top of whatever
502
+ * terminal-specific push the caller supplied (`inner`, e.g. an IM chat
503
+ * upload). The returned callback is always safe to register, so the tool is
504
+ * available for dashboard / headless turns that have no `inner` (the
505
+ * dashboard serves the recorded copy over HTTP). Recording is best-effort and
506
+ * never propagates an error back into the delivery result.
507
+ */
508
+ buildArtifactSendFile(agent, sessionKey, cs, inner) {
509
+ return async (filePath, sendOpts) => {
510
+ // Terminal-specific push first (IM chat). Dashboard / headless has no inner.
511
+ const result = inner ? await inner(filePath, sendOpts) : { ok: true };
512
+ if (!result.ok)
513
+ return result;
514
+ try {
515
+ // Resolve the freshest session id at delivery time — send_file fires
516
+ // late in a turn, after a pending→native promotion has normally already
517
+ // happened, so the manifest lands under the canonical id.
518
+ let sid = '';
519
+ if (sessionKey) {
520
+ const rt = this.getSessionRuntimeByKey(sessionKey, { allowAnyWorkdir: true });
521
+ if (rt?.sessionId)
522
+ sid = rt.sessionId;
523
+ }
524
+ if (!sid && typeof cs.sessionId === 'string')
525
+ sid = cs.sessionId;
526
+ if (!sid || isPendingSessionId(sid))
527
+ return result;
528
+ const kind = sendOpts?.kind === 'photo' ? 'photo' : 'document';
529
+ const record = deliverArtifact(agent, sid, filePath, { kind, caption: sendOpts?.caption });
530
+ if (record && sessionKey) {
531
+ this.emitStream(this.resolveSessionKey(sessionKey), {
532
+ type: 'artifact',
533
+ artifact: {
534
+ url: attachmentUrl(agent, sid, record.path, { downloadName: record.fileName }),
535
+ fileName: record.fileName,
536
+ fileSize: record.fileSize,
537
+ mime: record.fileMime,
538
+ kind: record.kind,
539
+ ...(record.caption ? { caption: record.caption } : {}),
540
+ },
541
+ });
542
+ }
543
+ }
544
+ catch (e) {
545
+ this.warn(`[runStream] artifact record failed: ${e?.message || e}`);
546
+ }
547
+ return result;
548
+ };
549
+ }
485
550
  keepAliveProc = null;
486
551
  keepAlivePulseTimer = null;
487
552
  sessionChains = new Map();
@@ -2376,7 +2441,21 @@ export class Bot {
2376
2441
  // falls back to the agent's in-memory flag (IM /mode) when unspecified.
2377
2442
  // Default off — never read from a persisted config default.
2378
2443
  const workflowEnabled = cs.agent === 'claude' && (extras?.workflowEnabled ?? this.claudeWorkflowEnabled);
2379
- const mcpSystemPrompt = appendExtraPrompt(appendExtraPrompt(appendExtraPrompt(mcpSendFile ? buildMcpDeliveryPrompt() : '', onInteraction && cs.agent === 'claude' ? buildClaudeAskUserPrompt() : ''), buildBrowserAutomationPrompt(browserEnabled)), workflowEnabled ? buildWorkflowOptInPrompt() : '');
2444
+ // ── Artifact delivery (terminal-agnostic) ──
2445
+ // `im_send_file` is the single "hand a file to the user" verb. Whatever the
2446
+ // caller wired as `mcpSendFile` (an IM channel push, or nothing for a
2447
+ // dashboard / headless turn) is wrapped so every delivery ALSO records the
2448
+ // file into the session's delivered-artifact manifest — the durable SSOT
2449
+ // that survives reload + workspace cleanup and is servable over HTTP — and
2450
+ // surfaces it live in the running turn's snapshot for any watching
2451
+ // dashboard. Always provided, so the tool + delivery prompt light up even
2452
+ // when there is no IM channel (the dashboard is the delivery surface then).
2453
+ const deliverySessionKey = ('key' in cs && typeof cs.key === 'string') ? cs.key : null;
2454
+ const wrappedSendFile = this.buildArtifactSendFile(cs.agent, deliverySessionKey, cs, mcpSendFile);
2455
+ const mcpSystemPrompt = appendExtraPrompt(appendExtraPrompt(appendExtraPrompt(
2456
+ // Always-on: `wrappedSendFile` is provided for every turn (see above),
2457
+ // so artifact delivery is available regardless of terminal.
2458
+ buildMcpDeliveryPrompt(), onInteraction && cs.agent === 'claude' ? buildClaudeAskUserPrompt() : ''), buildBrowserAutomationPrompt(browserEnabled)), workflowEnabled ? buildWorkflowOptInPrompt() : '');
2380
2459
  // mcpSystemPrompt carries behaviour directives (use im_ask_user instead of
2381
2460
  // built-in AskUserQuestion, browser automation status, artifact delivery)
2382
2461
  // that must apply on every turn, not just the first — on resume the CLI
@@ -2433,7 +2512,7 @@ export class Bot {
2433
2512
  // a Profile is bound).
2434
2513
  hermesModel: cs.agent === 'hermes' && resolvedModel ? resolvedModel : undefined,
2435
2514
  // MCP bridge
2436
- mcpSendFile,
2515
+ mcpSendFile: wrappedSendFile,
2437
2516
  abortSignal,
2438
2517
  onInteraction,
2439
2518
  onSteerReady,
@@ -247,6 +247,52 @@ export async function getSessionsPageData(bot, chatId, page, pageSize = 5) {
247
247
  sessions: entries,
248
248
  };
249
249
  }
250
+ export async function getSessionsDigestData(bot, chatId, limit = 8) {
251
+ const pageData = await getSessionsPageData(bot, chatId, 0, Math.max(1, limit));
252
+ const entries = pageData.sessions.map((session, index) => ({
253
+ index: index + 1,
254
+ agent: session.agent,
255
+ title: session.title,
256
+ time: session.time,
257
+ runState: session.runState,
258
+ runDetail: session.runDetail,
259
+ isCurrent: session.isCurrent,
260
+ sessionKey: session.key,
261
+ }));
262
+ return {
263
+ workspaceName: pageData.workspaceName,
264
+ agentTotals: pageData.agentTotals,
265
+ total: pageData.total,
266
+ entries,
267
+ };
268
+ }
269
+ export function formatSessionsDigestText(data) {
270
+ if (!data.entries.length) {
271
+ return data.workspaceName
272
+ ? `No sessions in ${data.workspaceName} yet. Send a message to start.`
273
+ : 'No sessions yet. Send a message to start.';
274
+ }
275
+ const agentBits = Object.entries(data.agentTotals)
276
+ .map(([agent, count]) => `${agent}×${count}`)
277
+ .join(' · ');
278
+ const lines = [
279
+ `Session digest — ${data.workspaceName || 'workspace'} (${data.total} total${agentBits ? ` · ${agentBits}` : ''})`,
280
+ '',
281
+ ];
282
+ for (const entry of data.entries) {
283
+ const flags = [
284
+ entry.isCurrent ? 'current' : null,
285
+ entry.runState === 'running' ? 'running' : null,
286
+ entry.runState === 'incomplete' ? 'unfinished' : null,
287
+ ].filter(Boolean).join(', ');
288
+ const flagSuffix = flags ? ` [${flags}]` : '';
289
+ lines.push(`${entry.index}. ${entry.agent} · ${entry.title}${flagSuffix}`);
290
+ const detail = entry.runDetail ? ` · ${entry.runDetail}` : '';
291
+ lines.push(` ${entry.time}${detail}`);
292
+ }
293
+ lines.push('', 'Switch: /sessions <#> · Browse: /sessions');
294
+ return lines.join('\n');
295
+ }
250
296
  export function extractLastSessionTurn(messages) {
251
297
  if (!messages.length)
252
298
  return null;
@@ -479,10 +525,10 @@ export function resolveSkillPrompt(bot, chatId, cmd, args) {
479
525
  const paths = getProjectSkillPaths(wd, skill.name);
480
526
  const skillFile = paths.claudeSkillFile || paths.sharedSkillFile || paths.agentsSkillFile;
481
527
  if (skillFile) {
482
- prompt = `${workdirHint}Read the skill definition at \`${skillFile}\` and execute the instructions defined there.${suffix}`;
528
+ prompt = `${workdirHint}Read the skill definition at \`${relSkillPath(wd, skillFile)}\` and execute the instructions defined there.${suffix}`;
483
529
  }
484
530
  else {
485
- const fallbackPath = `${wd}/.pikiloom/skills/${skill.name}/SKILL.md`;
531
+ const fallbackPath = relSkillPath(wd, path.join(wd, '.pikiloom', 'skills', skill.name, 'SKILL.md'));
486
532
  prompt = `${workdirHint}Read the skill definition at \`${fallbackPath}\` and execute the instructions defined there.${suffix}`;
487
533
  }
488
534
  return { prompt, skillName: skill.name };
package/dist/bot/menu.js CHANGED
@@ -31,6 +31,7 @@ export function indexSkillsByCommand(skills) {
31
31
  export function buildDefaultMenuCommands(agentCount, skills = []) {
32
32
  const commands = [
33
33
  { command: 'sessions', description: 'Switch sessions' },
34
+ { command: 'digest', description: 'Recent session digest' },
34
35
  ];
35
36
  if (agentCount > 1) {
36
37
  commands.push({ command: 'agents', description: 'Switch agents' });
@@ -121,7 +121,7 @@ function imageBlocksFromManagedRecord(record) {
121
121
  const abs = path.isAbsolute(rel) ? rel : path.join(record.workspacePath, rel);
122
122
  blocks.push({
123
123
  type: 'image',
124
- // `file://` sentinel — `rewriteImageBlocksForTransport` (dashboard
124
+ // `file://` sentinel — `rewriteAttachmentBlocksForTransport` (dashboard
125
125
  // response layer) converts it to a proper /attachment URL.
126
126
  content: `file://${abs}`,
127
127
  imagePath: abs,
@@ -9,7 +9,7 @@ import { BOT_SHUTDOWN_FORCE_EXIT_MS, buildSessionTaskId } from '../../bot/orches
9
9
  import { shutdownAllDrivers } from '../../agent/driver.js';
10
10
  import { expandTilde } from '../../core/platform.js';
11
11
  import { registerProcessRuntime, requestProcessRestart, } from '../../core/process-control.js';
12
- import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getStartData, getWorkspacesData, } from '../../bot/commands.js';
12
+ import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getSessionsDigestData, formatSessionsDigestText, getStartData, getWorkspacesData, } from '../../bot/commands.js';
13
13
  import { DingtalkChannel } from './channel.js';
14
14
  import { getActiveUserConfig } from '../../core/config/user-config.js';
15
15
  const SHUTDOWN_EXIT_CODE = {
@@ -127,6 +127,7 @@ export class DingtalkBot extends Bot {
127
127
  '/switch [path] - Change workdir',
128
128
  '/workspaces [#] - Pick saved workspace',
129
129
  '/sessions [new|#] - List/switch sessions',
130
+ '/digest - Recent session digest',
130
131
  '/skills - List project skills',
131
132
  '/stop - Stop current task',
132
133
  '/restart - Restart pikiloom',
@@ -160,6 +161,9 @@ export class DingtalkBot extends Bot {
160
161
  case 'sessions':
161
162
  await this.cmdSessions(ctx, args);
162
163
  return true;
164
+ case 'digest':
165
+ await this.cmdDigest(ctx);
166
+ return true;
163
167
  case 'skills':
164
168
  await this.cmdSkills(ctx);
165
169
  return true;
@@ -187,6 +191,10 @@ export class DingtalkBot extends Bot {
187
191
  lines.push('', 'Ready. Send a message to start.');
188
192
  await ctx.reply(lines.join('\n'));
189
193
  }
194
+ async cmdDigest(ctx) {
195
+ const data = await getSessionsDigestData(this, ctx.chatId);
196
+ await ctx.reply(formatSessionsDigestText(data));
197
+ }
190
198
  async cmdStatus(ctx) {
191
199
  const d = await getStatusDataAsync(this, ctx.chatId);
192
200
  const gitLine = formatGitStatusLine(d.git);
@@ -13,7 +13,7 @@ import { BOT_SHUTDOWN_FORCE_EXIT_MS, buildSessionTaskId } from '../../bot/orches
13
13
  import { shutdownAllDrivers } from '../../agent/driver.js';
14
14
  import { expandTilde } from '../../core/platform.js';
15
15
  import { registerProcessRuntime, requestProcessRestart, } from '../../core/process-control.js';
16
- import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getStartData, getWorkspacesData, } from '../../bot/commands.js';
16
+ import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getSessionsDigestData, formatSessionsDigestText, getStartData, getWorkspacesData, } from '../../bot/commands.js';
17
17
  import { DiscordChannel } from './channel.js';
18
18
  import { getActiveUserConfig } from '../../core/config/user-config.js';
19
19
  const SHUTDOWN_EXIT_CODE = {
@@ -123,6 +123,7 @@ export class DiscordBot extends Bot {
123
123
  '/switch [path] - Change workdir',
124
124
  '/workspaces [#] - Pick saved workspace',
125
125
  '/sessions [new|#] - List/switch sessions',
126
+ '/digest - Recent session digest',
126
127
  '/skills - List project skills',
127
128
  '/stop - Stop current task',
128
129
  '/restart - Restart pikiloom',
@@ -156,6 +157,9 @@ export class DiscordBot extends Bot {
156
157
  case 'sessions':
157
158
  await this.cmdSessions(ctx, args);
158
159
  return true;
160
+ case 'digest':
161
+ await this.cmdDigest(ctx);
162
+ return true;
159
163
  case 'skills':
160
164
  await this.cmdSkills(ctx);
161
165
  return true;
@@ -183,6 +187,10 @@ export class DiscordBot extends Bot {
183
187
  lines.push('', 'Ready. Send a message to start.');
184
188
  await ctx.reply(lines.join('\n'));
185
189
  }
190
+ async cmdDigest(ctx) {
191
+ const data = await getSessionsDigestData(this, ctx.chatId);
192
+ await ctx.reply(formatSessionsDigestText(data));
193
+ }
186
194
  async cmdStatus(ctx) {
187
195
  const d = await getStatusDataAsync(this, ctx.chatId);
188
196
  const gitLine = formatGitStatusLine(d.git);
@@ -15,7 +15,7 @@ import { stageSessionFiles, } from '../../agent/index.js';
15
15
  import { shutdownAllDrivers } from '../../agent/driver.js';
16
16
  import { expandTilde } from '../../core/platform.js';
17
17
  import { SKILL_CMD_PREFIX, } from '../../bot/menu.js';
18
- import { getStartData, getSessionsPageData, getModelsListData, getSessionTurnPreviewData, getStatusDataAsync, getHostDataSync, getWorkspacesData, resolveSkillPrompt, handleGoalCommand, } from '../../bot/commands.js';
18
+ import { getStartData, getSessionsPageData, getModelsListData, getSessionTurnPreviewData, getStatusDataAsync, getHostDataSync, getWorkspacesData, resolveSkillPrompt, handleGoalCommand, getSessionsDigestData, formatSessionsDigestText, } from '../../bot/commands.js';
19
19
  import { buildAgentsCommandView, buildModelsCommandView, buildModeCommandView, buildSessionsCommandView, buildSkillsCommandView, decodeCommandAction, executeCommandAction, } from '../../bot/command-ui.js';
20
20
  import { LivePreview } from '../telegram/live-preview.js';
21
21
  import { registerProcessRuntime, requestProcessRestart, } from '../../core/process-control.js';
@@ -355,6 +355,10 @@ export class FeishuBot extends Bot {
355
355
  }
356
356
  await this.sendCommandView(ctx, await buildSessionsCommandView(this, ctx.chatId, 0, this.sessionsPageSize));
357
357
  }
358
+ async cmdDigest(ctx) {
359
+ const data = await getSessionsDigestData(this, ctx.chatId);
360
+ await ctx.reply(formatSessionsDigestText(data));
361
+ }
358
362
  async cmdStatus(ctx) {
359
363
  const d = await getStatusDataAsync(this, ctx.chatId);
360
364
  await ctx.reply(renderStatus(d));
@@ -962,6 +966,9 @@ export class FeishuBot extends Bot {
962
966
  case 'sessions':
963
967
  await this.cmdSessions(ctx, args);
964
968
  return;
969
+ case 'digest':
970
+ await this.cmdDigest(ctx);
971
+ return;
965
972
  case 'agents':
966
973
  await this.cmdAgents(ctx, args);
967
974
  return;
@@ -13,7 +13,7 @@ import { BOT_SHUTDOWN_FORCE_EXIT_MS, buildSessionTaskId } from '../../bot/orches
13
13
  import { shutdownAllDrivers } from '../../agent/driver.js';
14
14
  import { expandTilde } from '../../core/platform.js';
15
15
  import { registerProcessRuntime, requestProcessRestart, } from '../../core/process-control.js';
16
- import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getStartData, getWorkspacesData, } from '../../bot/commands.js';
16
+ import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getSessionsDigestData, formatSessionsDigestText, getStartData, getWorkspacesData, } from '../../bot/commands.js';
17
17
  import { SlackChannel } from './channel.js';
18
18
  import { getActiveUserConfig } from '../../core/config/user-config.js';
19
19
  const SHUTDOWN_EXIT_CODE = {
@@ -131,6 +131,7 @@ export class SlackBot extends Bot {
131
131
  '/switch [path] - Change workdir',
132
132
  '/workspaces [#] - Pick saved workspace',
133
133
  '/sessions [new|#] - List/switch sessions',
134
+ '/digest - Recent session digest',
134
135
  '/skills - List project skills',
135
136
  '/stop - Stop current task',
136
137
  '/restart - Restart pikiloom',
@@ -165,6 +166,9 @@ export class SlackBot extends Bot {
165
166
  case 'sessions':
166
167
  await this.cmdSessions(ctx, args);
167
168
  return true;
169
+ case 'digest':
170
+ await this.cmdDigest(ctx);
171
+ return true;
168
172
  case 'skills':
169
173
  await this.cmdSkills(ctx);
170
174
  return true;
@@ -192,6 +196,10 @@ export class SlackBot extends Bot {
192
196
  lines.push('', 'Ready. Send a message to start.');
193
197
  await ctx.reply(lines.join('\n'));
194
198
  }
199
+ async cmdDigest(ctx) {
200
+ const data = await getSessionsDigestData(this, ctx.chatId);
201
+ await ctx.reply(formatSessionsDigestText(data));
202
+ }
195
203
  async cmdStatus(ctx) {
196
204
  const d = await getStatusDataAsync(this, ctx.chatId);
197
205
  const gitLine = formatGitStatusLine(d.git);
@@ -14,7 +14,7 @@ import { BOT_SHUTDOWN_FORCE_EXIT_MS, SessionMessageRegistry, buildBotMenuState,
14
14
  import { stageSessionFiles, } from '../../agent/index.js';
15
15
  import { shutdownAllDrivers } from '../../agent/driver.js';
16
16
  import { SKILL_CMD_PREFIX, } from '../../bot/menu.js';
17
- import { getStartData, getStatusDataAsync, getHostDataSync, getSessionTurnPreviewData, getWorkspacesData, resolveSkillPrompt, summarizePromptForStatus, handleGoalCommand, } from '../../bot/commands.js';
17
+ import { getStartData, getStatusDataAsync, getHostDataSync, getSessionTurnPreviewData, getWorkspacesData, resolveSkillPrompt, summarizePromptForStatus, handleGoalCommand, getSessionsDigestData, formatSessionsDigestText, } from '../../bot/commands.js';
18
18
  import { buildAgentsCommandView, buildModelsCommandView, buildModeCommandView, buildSessionsCommandView, buildSkillsCommandView, decodeCommandAction, executeCommandAction, } from '../../bot/command-ui.js';
19
19
  import { buildSwitchWorkdirView, buildWorkspacesView, resolveRegisteredPath } from './directory.js';
20
20
  import { LivePreview } from './live-preview.js';
@@ -353,6 +353,10 @@ export class TelegramBot extends Bot {
353
353
  async cmdSessions(ctx) {
354
354
  await this.sendCommandView(ctx, await buildSessionsCommandView(this, ctx.chatId, 0, this.sessionsPageSize));
355
355
  }
356
+ async cmdDigest(ctx) {
357
+ const data = await getSessionsDigestData(this, ctx.chatId);
358
+ await ctx.reply(formatSessionsDigestText(data));
359
+ }
356
360
  async cmdStatus(ctx) {
357
361
  const d = await getStatusDataAsync(this, ctx.chatId);
358
362
  const gitLine = formatGitStatusLine(d.git);
@@ -1167,6 +1171,9 @@ export class TelegramBot extends Bot {
1167
1171
  case 'sessions':
1168
1172
  await this.cmdSessions(ctx);
1169
1173
  return;
1174
+ case 'digest':
1175
+ await this.cmdDigest(ctx);
1176
+ return;
1170
1177
  case 'agents':
1171
1178
  await this.cmdAgents(ctx);
1172
1179
  return;
@@ -9,7 +9,7 @@ import { BOT_SHUTDOWN_FORCE_EXIT_MS, buildSessionTaskId } from '../../bot/orches
9
9
  import { shutdownAllDrivers } from '../../agent/driver.js';
10
10
  import { expandTilde } from '../../core/platform.js';
11
11
  import { registerProcessRuntime, requestProcessRestart, } from '../../core/process-control.js';
12
- import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getStartData, getWorkspacesData, } from '../../bot/commands.js';
12
+ import { getStatusDataAsync, getHostDataSync, getAgentsListData, getSkillsListData, getModelsListData, getSessionsPageData, getSessionsDigestData, formatSessionsDigestText, getStartData, getWorkspacesData, } from '../../bot/commands.js';
13
13
  import { WeComChannel } from './channel.js';
14
14
  import { getActiveUserConfig } from '../../core/config/user-config.js';
15
15
  const SHUTDOWN_EXIT_CODE = {
@@ -135,6 +135,7 @@ export class WeComBot extends Bot {
135
135
  '/switch [path] - Change workdir',
136
136
  '/workspaces [#] - Pick saved workspace',
137
137
  '/sessions [new|#] - List/switch sessions',
138
+ '/digest - Recent session digest',
138
139
  '/skills - List project skills',
139
140
  '/stop - Stop current task',
140
141
  '/restart - Restart pikiloom',
@@ -168,6 +169,9 @@ export class WeComBot extends Bot {
168
169
  case 'sessions':
169
170
  await this.cmdSessions(ctx, args);
170
171
  return true;
172
+ case 'digest':
173
+ await this.cmdDigest(ctx);
174
+ return true;
171
175
  case 'skills':
172
176
  await this.cmdSkills(ctx);
173
177
  return true;
@@ -195,6 +199,10 @@ export class WeComBot extends Bot {
195
199
  lines.push('', 'Ready. Send a message to start.');
196
200
  await ctx.reply(lines.join('\n'));
197
201
  }
202
+ async cmdDigest(ctx) {
203
+ const data = await getSessionsDigestData(this, ctx.chatId);
204
+ await ctx.reply(formatSessionsDigestText(data));
205
+ }
198
206
  async cmdStatus(ctx) {
199
207
  const d = await getStatusDataAsync(this, ctx.chatId);
200
208
  const gitLine = formatGitStatusLine(d.git);
@@ -11,7 +11,7 @@ import { BOT_SHUTDOWN_FORCE_EXIT_MS, buildSessionTaskId } from '../../bot/orches
11
11
  import { shutdownAllDrivers } from '../../agent/driver.js';
12
12
  import { expandTilde } from '../../core/platform.js';
13
13
  import { registerProcessRuntime, requestProcessRestart, } from '../../core/process-control.js';
14
- import { getStatusDataAsync, getHostDataSync, getModelsListData, getSessionsPageData, getStartData, getWorkspacesData, handleGoalCommand, } from '../../bot/commands.js';
14
+ import { getStatusDataAsync, getHostDataSync, getModelsListData, getSessionsPageData, getSessionsDigestData, formatSessionsDigestText, getStartData, getWorkspacesData, handleGoalCommand, } from '../../bot/commands.js';
15
15
  import { WeixinChannel } from './channel.js';
16
16
  import { getActiveUserConfig } from '../../core/config/user-config.js';
17
17
  const SHUTDOWN_EXIT_CODE = {
@@ -175,6 +175,7 @@ export class WeixinBot extends Bot {
175
175
  '/switch [path] - Change workdir',
176
176
  '/workspaces [#] - Pick saved workspace',
177
177
  '/sessions [new|#] - List/switch sessions',
178
+ '/digest - Recent session digest',
178
179
  '/skills - List & run project skills',
179
180
  '/cancel - Cancel an active interactive prompt',
180
181
  '/stop - Stop current task',
@@ -210,6 +211,9 @@ export class WeixinBot extends Bot {
210
211
  case 'sessions':
211
212
  await this.cmdSessions(ctx, args);
212
213
  return true;
214
+ case 'digest':
215
+ await this.cmdDigest(ctx);
216
+ return true;
213
217
  case 'skills':
214
218
  await this.cmdSkills(ctx);
215
219
  return true;
@@ -241,6 +245,10 @@ export class WeixinBot extends Bot {
241
245
  lines.push('', 'Ready. Send a message to start.');
242
246
  await ctx.reply(lines.join('\n'));
243
247
  }
248
+ async cmdDigest(ctx) {
249
+ const data = await getSessionsDigestData(this, ctx.chatId);
250
+ await ctx.reply(formatSessionsDigestText(data));
251
+ }
244
252
  async cmdStatus(ctx) {
245
253
  const d = await getStatusDataAsync(this, ctx.chatId);
246
254
  const gitLine = formatGitStatusLine(d.git);
package/dist/cli/main.js CHANGED
@@ -292,6 +292,7 @@ Environment variables (per agent):
292
292
 
293
293
  Bot commands (available once running):
294
294
  /sessions List or switch coding sessions
295
+ /digest Show a compact digest of recent sessions
295
296
  /agents List or switch AI agents
296
297
  /models List or switch models
297
298
  /status Bot status, uptime, and token usage