pikiclaw 0.2.55 → 0.2.56

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.
@@ -1,5 +1,6 @@
1
1
  import { normalizeAgent } from './bot.js';
2
- import { getAgentsListData, getModelsListData, getSessionsPageData, getSkillsListData, modelMatchesSelection, resolveSkillPrompt, } from './bot-commands.js';
2
+ import { getSessionStatusForChat } from './session-status.js';
3
+ import { getAgentsListData, getModelsListData, getSessionsPageData, getSkillsListData, summarizeSessionRun, modelMatchesSelection, resolveSkillPrompt, } from './bot-commands.js';
3
4
  function chunkRows(items, columns) {
4
5
  const rows = [];
5
6
  const size = Math.max(1, columns);
@@ -219,13 +220,14 @@ export async function executeCommandAction(bot, chatId, action, opts = {}) {
219
220
  return { kind: 'noop', message: 'Session not found' };
220
221
  const runtime = bot.adoptExistingSessionForChat(chatId, session);
221
222
  const displayId = session.sessionId || action.sessionId;
223
+ const sessionStatus = getSessionStatusForChat(bot, chat, session);
222
224
  return {
223
225
  kind: 'notice',
224
226
  callbackText: `Switched: ${displayId.slice(0, 12)}`,
225
227
  notice: {
226
228
  title: 'Session Switched',
227
229
  value: displayId,
228
- detail: 'Switched successfully',
230
+ detail: summarizeSessionRun({ ...session, running: sessionStatus.isRunning }).noticeDetail,
229
231
  valueMode: 'code',
230
232
  },
231
233
  session: runtime,
@@ -37,6 +37,28 @@ export function getStartData(bot, chatId) {
37
37
  commands,
38
38
  };
39
39
  }
40
+ export function summarizeSessionRun(session) {
41
+ if (session.running || session.runState === 'running') {
42
+ return {
43
+ state: 'running',
44
+ shortLabel: 'running',
45
+ noticeDetail: 'Status: running',
46
+ };
47
+ }
48
+ if (session.runState === 'incomplete') {
49
+ const detail = String(session.runDetail || '').trim();
50
+ return {
51
+ state: 'incomplete',
52
+ shortLabel: 'unfinished',
53
+ noticeDetail: detail ? `Status: unfinished · ${detail}` : 'Status: unfinished',
54
+ };
55
+ }
56
+ return {
57
+ state: 'completed',
58
+ shortLabel: 'done',
59
+ noticeDetail: 'Status: completed',
60
+ };
61
+ }
40
62
  export async function getSessionsPageData(bot, chatId, page, pageSize = 5) {
41
63
  const cs = bot.chat(chatId);
42
64
  const res = await bot.fetchSessions(cs.agent);
@@ -51,11 +73,24 @@ export async function getSessionsPageData(bot, chatId, page, pageSize = 5) {
51
73
  if (!sessionKey)
52
74
  continue;
53
75
  const status = getSessionStatusForChat(bot, cs, s);
76
+ const runSummary = summarizeSessionRun({
77
+ running: status.isRunning,
78
+ runState: status.isRunning ? 'running' : s.runState,
79
+ runDetail: s.runDetail,
80
+ });
54
81
  const title = s.title ? s.title.replace(/\n/g, ' ').slice(0, 10) : sessionKey.slice(0, 10);
55
82
  const time = s.createdAt
56
83
  ? new Date(s.createdAt).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
57
84
  : '?';
58
- entries.push({ key: sessionKey, title, time, isCurrent: status.isCurrent, isRunning: status.isRunning });
85
+ entries.push({
86
+ key: sessionKey,
87
+ title,
88
+ time: `${time} · ${runSummary.shortLabel}`,
89
+ isCurrent: status.isCurrent,
90
+ isRunning: status.isRunning,
91
+ runState: runSummary.state,
92
+ runDetail: s.runDetail,
93
+ });
59
94
  }
60
95
  return { agent: cs.agent, total, page: pg, totalPages, sessions: entries };
61
96
  }
@@ -464,6 +464,54 @@ function nextPendingSessionId() { return `pending_${crypto.randomBytes(6).toStri
464
464
  export function isPendingSessionId(sessionId) {
465
465
  return typeof sessionId === 'string' && sessionId.startsWith('pending_');
466
466
  }
467
+ function normalizeSessionRunState(rawState) {
468
+ const state = typeof rawState === 'string' ? rawState.trim().toLowerCase() : '';
469
+ if (state === 'completed' || state === 'incomplete')
470
+ return state;
471
+ if (state === 'running')
472
+ return 'incomplete';
473
+ return 'completed';
474
+ }
475
+ function normalizeSessionRunDetail(rawState, rawDetail) {
476
+ const detail = typeof rawDetail === 'string' ? rawDetail.trim() : '';
477
+ if (detail)
478
+ return shortValue(detail, 180);
479
+ const state = typeof rawState === 'string' ? rawState.trim().toLowerCase() : '';
480
+ if (state === 'running')
481
+ return 'Last run stopped before completion.';
482
+ return null;
483
+ }
484
+ function normalizeSessionRunUpdatedAt(rawUpdatedAt, fallback) {
485
+ return typeof rawUpdatedAt === 'string' && rawUpdatedAt.trim() ? rawUpdatedAt : fallback;
486
+ }
487
+ function setSessionRunState(record, runState, runDetail, runUpdatedAt) {
488
+ record.runState = runState;
489
+ record.runDetail = runDetail ? shortValue(runDetail, 180) : null;
490
+ record.runUpdatedAt = runUpdatedAt || new Date().toISOString();
491
+ }
492
+ function incompleteRunDetail(result) {
493
+ if (result.stopReason === 'interrupted')
494
+ return 'Interrupted by user.';
495
+ if (result.stopReason === 'timeout')
496
+ return 'Timed out before completion.';
497
+ if (result.stopReason === 'max_tokens')
498
+ return 'Stopped before completion: max tokens reached.';
499
+ const error = normalizeErrorMessage(result.error);
500
+ if (error)
501
+ return shortValue(error, 180);
502
+ const stopReason = normalizeErrorMessage(result.stopReason);
503
+ if (stopReason)
504
+ return `Stopped before completion: ${shortValue(stopReason, 120)}`;
505
+ const message = firstNonEmptyLine(result.message || '');
506
+ return message ? shortValue(message, 180) : 'Last run did not complete.';
507
+ }
508
+ function applySessionRunResult(record, result) {
509
+ if (result.ok && !result.incomplete) {
510
+ setSessionRunState(record, 'completed', null);
511
+ return;
512
+ }
513
+ setSessionRunState(record, 'incomplete', incompleteRunDetail(result));
514
+ }
467
515
  function normalizeSessionRecord(raw, workdir) {
468
516
  // Support both new format (sessionId) and legacy format (localSessionId + engineSessionId)
469
517
  const sessionId = typeof raw?.sessionId === 'string' ? raw.sessionId.trim()
@@ -484,6 +532,9 @@ function normalizeSessionRecord(raw, workdir) {
484
532
  title: typeof raw?.title === 'string' && raw.title.trim() ? raw.title.trim() : null,
485
533
  model: typeof raw?.model === 'string' && raw.model.trim() ? raw.model.trim() : null,
486
534
  stagedFiles: Array.isArray(raw?.stagedFiles) ? dedupeStrings(raw.stagedFiles.filter((v) => typeof v === 'string')) : [],
535
+ runState: normalizeSessionRunState(raw?.runState),
536
+ runDetail: normalizeSessionRunDetail(raw?.runState, raw?.runDetail),
537
+ runUpdatedAt: normalizeSessionRunUpdatedAt(raw?.runUpdatedAt, typeof raw?.updatedAt === 'string' && raw.updatedAt.trim() ? raw.updatedAt : new Date().toISOString()),
487
538
  };
488
539
  }
489
540
  function loadSessionIndex(workdir) {
@@ -502,6 +553,7 @@ function writeSessionMeta(record) {
502
553
  workspacePath: record.workspacePath,
503
554
  createdAt: record.createdAt, updatedAt: record.updatedAt,
504
555
  title: record.title, model: record.model, stagedFiles: record.stagedFiles,
556
+ runState: record.runState, runDetail: record.runDetail, runUpdatedAt: record.runUpdatedAt,
505
557
  });
506
558
  }
507
559
  function copyPath(sourcePath, targetPath) {
@@ -583,8 +635,25 @@ export function promoteSessionId(workdir, agent, pendingId, nativeId) {
583
635
  record.workspacePath = sessionWorkspacePath(resolvedWorkdir, agent, nativeId);
584
636
  saveSessionRecord(resolvedWorkdir, record);
585
637
  }
638
+ function syncManagedSessionIdentity(session, workdir, nativeId) {
639
+ const resolvedId = nativeId.trim();
640
+ if (!resolvedId || session.sessionId === resolvedId)
641
+ return false;
642
+ const resolvedWorkdir = path.resolve(workdir);
643
+ const previousId = session.sessionId;
644
+ if (isPendingSessionId(previousId)) {
645
+ promoteSessionId(resolvedWorkdir, session.record.agent, previousId, resolvedId);
646
+ }
647
+ session.sessionId = resolvedId;
648
+ session.workspacePath = sessionWorkspacePath(resolvedWorkdir, session.record.agent, resolvedId);
649
+ session.record.sessionId = resolvedId;
650
+ session.record.workspacePath = session.workspacePath;
651
+ return true;
652
+ }
586
653
  function summarizePromptTitle(prompt) {
587
- const text = String(prompt || '').replace(/\s+/g, ' ').trim();
654
+ const raw = String(prompt || '').replace(/\r\n?/g, '\n');
655
+ const text = firstNonEmptyLine(raw).replace(/\s+/g, ' ').trim()
656
+ || raw.replace(/\s+/g, ' ').trim();
588
657
  if (!text)
589
658
  return null;
590
659
  return text.length <= 120 ? text : `${text.slice(0, 117).trimEnd()}...`;
@@ -635,6 +704,7 @@ function ensureSessionWorkspace(opts) {
635
704
  workspacePath: sessionWorkspacePath(workdir, opts.agent, sessionId),
636
705
  createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
637
706
  title: summarizePromptTitle(opts.title) || null, model: null, stagedFiles: [],
707
+ runState: 'completed', runDetail: null, runUpdatedAt: new Date().toISOString(),
638
708
  };
639
709
  }
640
710
  if (!record.title && opts.title)
@@ -799,6 +869,7 @@ function prepareStreamOpts(opts) {
799
869
  session.record.stagedFiles = [];
800
870
  if (!session.record.title)
801
871
  session.record.title = summarizePromptTitle(opts.prompt) || importedFiles[0] || null;
872
+ setSessionRunState(session.record, 'running', null);
802
873
  saveSessionRecord(opts.workdir, session.record);
803
874
  const attachmentPaths = attachmentRelPaths.map(relPath => path.join(session.workspacePath, relPath));
804
875
  // For pending sessions, pass null sessionId to the CLI so it creates a new session
@@ -811,20 +882,21 @@ function prepareStreamOpts(opts) {
811
882
  ...opts,
812
883
  sessionId: effectiveSessionId,
813
884
  attachments: attachmentPaths.length ? attachmentPaths : undefined,
885
+ onSessionId: (nativeSessionId) => {
886
+ if (!syncManagedSessionIdentity(session, opts.workdir, nativeSessionId))
887
+ return;
888
+ saveSessionRecord(opts.workdir, session.record);
889
+ },
814
890
  },
815
891
  };
816
892
  }
817
893
  function finalizeStreamResult(result, workdir, prompt, session) {
818
- // If the agent returned a native session ID and our session was pending, promote it
819
- const pendingId = session.sessionId;
820
- if (result.sessionId && isPendingSessionId(pendingId)) {
821
- promoteSessionId(workdir, session.record.agent, pendingId, result.sessionId);
822
- session.sessionId = result.sessionId;
823
- }
824
- session.record.sessionId = result.sessionId || session.record.sessionId;
894
+ if (result.sessionId)
895
+ syncManagedSessionIdentity(session, workdir, result.sessionId);
825
896
  session.record.model = result.model || session.record.model;
826
897
  if (!session.record.title)
827
898
  session.record.title = summarizePromptTitle(prompt);
899
+ applySessionRunResult(session.record, result);
828
900
  saveSessionRecord(workdir, session.record);
829
901
  return { ...result, sessionId: session.sessionId, workspacePath: session.workspacePath };
830
902
  }
@@ -885,6 +957,33 @@ export async function doStream(opts) {
885
957
  const result = await driver.doStream(prepared);
886
958
  return finalizeStreamResult(result, opts.workdir, opts.prompt, session);
887
959
  }
960
+ catch (error) {
961
+ const failedResult = {
962
+ ok: false,
963
+ message: normalizeErrorMessage(error) || 'Agent stream failed.',
964
+ thinking: null,
965
+ sessionId: session.sessionId,
966
+ workspacePath: session.workspacePath,
967
+ model: session.record.model,
968
+ thinkingEffort: prepared.thinkingEffort,
969
+ elapsedS: 0,
970
+ inputTokens: null,
971
+ outputTokens: null,
972
+ cachedInputTokens: null,
973
+ cacheCreationInputTokens: null,
974
+ contextWindow: null,
975
+ contextUsedTokens: null,
976
+ contextPercent: null,
977
+ codexCumulative: null,
978
+ error: normalizeErrorMessage(error) || 'Agent stream failed.',
979
+ stopReason: null,
980
+ incomplete: true,
981
+ activity: null,
982
+ };
983
+ applySessionRunResult(session.record, failedResult);
984
+ saveSessionRecord(opts.workdir, session.record);
985
+ throw error;
986
+ }
888
987
  finally {
889
988
  if (bridge) {
890
989
  await bridge.stop().catch(() => { });