pikiclaw 0.2.55 → 0.2.57
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 +4 -2
- package/dist/bot-commands.js +42 -9
- package/dist/bot.js +2 -2
- package/dist/code-agent.js +110 -8
- package/dist/dashboard-ui.js +7 -7
- package/dist/dashboard.js +5 -3
- package/dist/driver-claude.js +17 -12
- package/dist/driver-codex.js +15 -1
- package/dist/driver-gemini.js +251 -14
- package/dist/run.js +20 -0
- package/package.json +1 -1
package/dist/bot-command-ui.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeAgent } from './bot.js';
|
|
2
|
-
import {
|
|
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:
|
|
230
|
+
detail: summarizeSessionRun({ ...session, running: sessionStatus.isRunning }).noticeDetail,
|
|
229
231
|
valueMode: 'code',
|
|
230
232
|
},
|
|
231
233
|
session: runtime,
|
package/dist/bot-commands.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { fmtTokens, fmtUptime, fmtBytes } from './bot.js';
|
|
13
|
-
import { getProjectSkillPaths } from './code-agent.js';
|
|
13
|
+
import { getProjectSkillPaths, normalizeClaudeModelId } from './code-agent.js';
|
|
14
14
|
import { getDriver } from './agent-driver.js';
|
|
15
15
|
import { buildWelcomeIntro, buildSkillCommandName, indexSkillsByCommand, SKILL_CMD_PREFIX } from './bot-menu.js';
|
|
16
16
|
import { buildBotMenuState } from './bot-orchestration.js';
|
|
@@ -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({
|
|
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
|
}
|
|
@@ -121,15 +156,13 @@ export function getSkillsListData(bot, chatId) {
|
|
|
121
156
|
return { agent: cs.agent, workdir: bot.workdir, skills };
|
|
122
157
|
}
|
|
123
158
|
function claudeModelSelectionKey(modelId) {
|
|
124
|
-
const value =
|
|
159
|
+
const value = normalizeClaudeModelId(modelId).toLowerCase();
|
|
125
160
|
if (!value)
|
|
126
161
|
return null;
|
|
127
|
-
if (value === 'opus' || value
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return value === 'sonnet-1m' || value.endsWith('[1m]') ? 'sonnet-1m' : 'sonnet';
|
|
132
|
-
}
|
|
162
|
+
if (value === 'opus' || value.startsWith('claude-opus-'))
|
|
163
|
+
return 'opus';
|
|
164
|
+
if (value === 'sonnet' || value.startsWith('claude-sonnet-'))
|
|
165
|
+
return 'sonnet';
|
|
133
166
|
if (value === 'haiku' || value.startsWith('claude-haiku-'))
|
|
134
167
|
return 'haiku';
|
|
135
168
|
return null;
|
package/dist/bot.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { execSync, spawn } from 'node:child_process';
|
|
10
10
|
import { getActiveUserConfig, onUserConfigChange, resolveUserWorkdir, setUserWorkdir } from './user-config.js';
|
|
11
|
-
import { doStream, getSessions, getSessionTail, getUsage, initializeProjectSkills, listAgents, listModels, listSkills, stageSessionFiles, isPendingSessionId, } from './code-agent.js';
|
|
11
|
+
import { doStream, getSessions, getSessionTail, getUsage, initializeProjectSkills, listAgents, listModels, listSkills, stageSessionFiles, isPendingSessionId, normalizeClaudeModelId, } from './code-agent.js';
|
|
12
12
|
import { getDriver, hasDriver, allDriverIds } from './agent-driver.js';
|
|
13
13
|
import { terminateProcessTree } from './process-control.js';
|
|
14
14
|
import { VERSION } from './version.js';
|
|
@@ -221,7 +221,7 @@ function buildMcpDeliveryPrompt() {
|
|
|
221
221
|
}
|
|
222
222
|
function configModelValue(config, agent) {
|
|
223
223
|
switch (agent) {
|
|
224
|
-
case 'claude': return
|
|
224
|
+
case 'claude': return normalizeClaudeModelId(config.claudeModel || process.env.CLAUDE_MODEL || 'claude-opus-4-6');
|
|
225
225
|
case 'codex': return String(config.codexModel || process.env.CODEX_MODEL || 'gpt-5.4').trim();
|
|
226
226
|
case 'gemini': return String(config.geminiModel || process.env.GEMINI_MODEL || 'gemini-3.1-pro-preview').trim();
|
|
227
227
|
}
|
package/dist/code-agent.js
CHANGED
|
@@ -315,6 +315,9 @@ export function modelFamily(model) {
|
|
|
315
315
|
return 'sonnet';
|
|
316
316
|
return null;
|
|
317
317
|
}
|
|
318
|
+
export function normalizeClaudeModelId(model) {
|
|
319
|
+
return typeof model === 'string' ? model.trim() : '';
|
|
320
|
+
}
|
|
318
321
|
export function emptyUsage(agent, error) {
|
|
319
322
|
return { ok: false, agent, source: null, capturedAt: null, status: null, windows: [], error };
|
|
320
323
|
}
|
|
@@ -464,6 +467,54 @@ function nextPendingSessionId() { return `pending_${crypto.randomBytes(6).toStri
|
|
|
464
467
|
export function isPendingSessionId(sessionId) {
|
|
465
468
|
return typeof sessionId === 'string' && sessionId.startsWith('pending_');
|
|
466
469
|
}
|
|
470
|
+
function normalizeSessionRunState(rawState) {
|
|
471
|
+
const state = typeof rawState === 'string' ? rawState.trim().toLowerCase() : '';
|
|
472
|
+
if (state === 'completed' || state === 'incomplete')
|
|
473
|
+
return state;
|
|
474
|
+
if (state === 'running')
|
|
475
|
+
return 'incomplete';
|
|
476
|
+
return 'completed';
|
|
477
|
+
}
|
|
478
|
+
function normalizeSessionRunDetail(rawState, rawDetail) {
|
|
479
|
+
const detail = typeof rawDetail === 'string' ? rawDetail.trim() : '';
|
|
480
|
+
if (detail)
|
|
481
|
+
return shortValue(detail, 180);
|
|
482
|
+
const state = typeof rawState === 'string' ? rawState.trim().toLowerCase() : '';
|
|
483
|
+
if (state === 'running')
|
|
484
|
+
return 'Last run stopped before completion.';
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
function normalizeSessionRunUpdatedAt(rawUpdatedAt, fallback) {
|
|
488
|
+
return typeof rawUpdatedAt === 'string' && rawUpdatedAt.trim() ? rawUpdatedAt : fallback;
|
|
489
|
+
}
|
|
490
|
+
function setSessionRunState(record, runState, runDetail, runUpdatedAt) {
|
|
491
|
+
record.runState = runState;
|
|
492
|
+
record.runDetail = runDetail ? shortValue(runDetail, 180) : null;
|
|
493
|
+
record.runUpdatedAt = runUpdatedAt || new Date().toISOString();
|
|
494
|
+
}
|
|
495
|
+
function incompleteRunDetail(result) {
|
|
496
|
+
if (result.stopReason === 'interrupted')
|
|
497
|
+
return 'Interrupted by user.';
|
|
498
|
+
if (result.stopReason === 'timeout')
|
|
499
|
+
return 'Timed out before completion.';
|
|
500
|
+
if (result.stopReason === 'max_tokens')
|
|
501
|
+
return 'Stopped before completion: max tokens reached.';
|
|
502
|
+
const error = normalizeErrorMessage(result.error);
|
|
503
|
+
if (error)
|
|
504
|
+
return shortValue(error, 180);
|
|
505
|
+
const stopReason = normalizeErrorMessage(result.stopReason);
|
|
506
|
+
if (stopReason)
|
|
507
|
+
return `Stopped before completion: ${shortValue(stopReason, 120)}`;
|
|
508
|
+
const message = firstNonEmptyLine(result.message || '');
|
|
509
|
+
return message ? shortValue(message, 180) : 'Last run did not complete.';
|
|
510
|
+
}
|
|
511
|
+
function applySessionRunResult(record, result) {
|
|
512
|
+
if (result.ok && !result.incomplete) {
|
|
513
|
+
setSessionRunState(record, 'completed', null);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
setSessionRunState(record, 'incomplete', incompleteRunDetail(result));
|
|
517
|
+
}
|
|
467
518
|
function normalizeSessionRecord(raw, workdir) {
|
|
468
519
|
// Support both new format (sessionId) and legacy format (localSessionId + engineSessionId)
|
|
469
520
|
const sessionId = typeof raw?.sessionId === 'string' ? raw.sessionId.trim()
|
|
@@ -484,6 +535,9 @@ function normalizeSessionRecord(raw, workdir) {
|
|
|
484
535
|
title: typeof raw?.title === 'string' && raw.title.trim() ? raw.title.trim() : null,
|
|
485
536
|
model: typeof raw?.model === 'string' && raw.model.trim() ? raw.model.trim() : null,
|
|
486
537
|
stagedFiles: Array.isArray(raw?.stagedFiles) ? dedupeStrings(raw.stagedFiles.filter((v) => typeof v === 'string')) : [],
|
|
538
|
+
runState: normalizeSessionRunState(raw?.runState),
|
|
539
|
+
runDetail: normalizeSessionRunDetail(raw?.runState, raw?.runDetail),
|
|
540
|
+
runUpdatedAt: normalizeSessionRunUpdatedAt(raw?.runUpdatedAt, typeof raw?.updatedAt === 'string' && raw.updatedAt.trim() ? raw.updatedAt : new Date().toISOString()),
|
|
487
541
|
};
|
|
488
542
|
}
|
|
489
543
|
function loadSessionIndex(workdir) {
|
|
@@ -502,6 +556,7 @@ function writeSessionMeta(record) {
|
|
|
502
556
|
workspacePath: record.workspacePath,
|
|
503
557
|
createdAt: record.createdAt, updatedAt: record.updatedAt,
|
|
504
558
|
title: record.title, model: record.model, stagedFiles: record.stagedFiles,
|
|
559
|
+
runState: record.runState, runDetail: record.runDetail, runUpdatedAt: record.runUpdatedAt,
|
|
505
560
|
});
|
|
506
561
|
}
|
|
507
562
|
function copyPath(sourcePath, targetPath) {
|
|
@@ -583,8 +638,25 @@ export function promoteSessionId(workdir, agent, pendingId, nativeId) {
|
|
|
583
638
|
record.workspacePath = sessionWorkspacePath(resolvedWorkdir, agent, nativeId);
|
|
584
639
|
saveSessionRecord(resolvedWorkdir, record);
|
|
585
640
|
}
|
|
641
|
+
function syncManagedSessionIdentity(session, workdir, nativeId) {
|
|
642
|
+
const resolvedId = nativeId.trim();
|
|
643
|
+
if (!resolvedId || session.sessionId === resolvedId)
|
|
644
|
+
return false;
|
|
645
|
+
const resolvedWorkdir = path.resolve(workdir);
|
|
646
|
+
const previousId = session.sessionId;
|
|
647
|
+
if (isPendingSessionId(previousId)) {
|
|
648
|
+
promoteSessionId(resolvedWorkdir, session.record.agent, previousId, resolvedId);
|
|
649
|
+
}
|
|
650
|
+
session.sessionId = resolvedId;
|
|
651
|
+
session.workspacePath = sessionWorkspacePath(resolvedWorkdir, session.record.agent, resolvedId);
|
|
652
|
+
session.record.sessionId = resolvedId;
|
|
653
|
+
session.record.workspacePath = session.workspacePath;
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
586
656
|
function summarizePromptTitle(prompt) {
|
|
587
|
-
const
|
|
657
|
+
const raw = String(prompt || '').replace(/\r\n?/g, '\n');
|
|
658
|
+
const text = firstNonEmptyLine(raw).replace(/\s+/g, ' ').trim()
|
|
659
|
+
|| raw.replace(/\s+/g, ' ').trim();
|
|
588
660
|
if (!text)
|
|
589
661
|
return null;
|
|
590
662
|
return text.length <= 120 ? text : `${text.slice(0, 117).trimEnd()}...`;
|
|
@@ -635,6 +707,7 @@ function ensureSessionWorkspace(opts) {
|
|
|
635
707
|
workspacePath: sessionWorkspacePath(workdir, opts.agent, sessionId),
|
|
636
708
|
createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
|
|
637
709
|
title: summarizePromptTitle(opts.title) || null, model: null, stagedFiles: [],
|
|
710
|
+
runState: 'completed', runDetail: null, runUpdatedAt: new Date().toISOString(),
|
|
638
711
|
};
|
|
639
712
|
}
|
|
640
713
|
if (!record.title && opts.title)
|
|
@@ -799,6 +872,7 @@ function prepareStreamOpts(opts) {
|
|
|
799
872
|
session.record.stagedFiles = [];
|
|
800
873
|
if (!session.record.title)
|
|
801
874
|
session.record.title = summarizePromptTitle(opts.prompt) || importedFiles[0] || null;
|
|
875
|
+
setSessionRunState(session.record, 'running', null);
|
|
802
876
|
saveSessionRecord(opts.workdir, session.record);
|
|
803
877
|
const attachmentPaths = attachmentRelPaths.map(relPath => path.join(session.workspacePath, relPath));
|
|
804
878
|
// For pending sessions, pass null sessionId to the CLI so it creates a new session
|
|
@@ -811,20 +885,21 @@ function prepareStreamOpts(opts) {
|
|
|
811
885
|
...opts,
|
|
812
886
|
sessionId: effectiveSessionId,
|
|
813
887
|
attachments: attachmentPaths.length ? attachmentPaths : undefined,
|
|
888
|
+
onSessionId: (nativeSessionId) => {
|
|
889
|
+
if (!syncManagedSessionIdentity(session, opts.workdir, nativeSessionId))
|
|
890
|
+
return;
|
|
891
|
+
saveSessionRecord(opts.workdir, session.record);
|
|
892
|
+
},
|
|
814
893
|
},
|
|
815
894
|
};
|
|
816
895
|
}
|
|
817
896
|
function finalizeStreamResult(result, workdir, prompt, session) {
|
|
818
|
-
|
|
819
|
-
|
|
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;
|
|
897
|
+
if (result.sessionId)
|
|
898
|
+
syncManagedSessionIdentity(session, workdir, result.sessionId);
|
|
825
899
|
session.record.model = result.model || session.record.model;
|
|
826
900
|
if (!session.record.title)
|
|
827
901
|
session.record.title = summarizePromptTitle(prompt);
|
|
902
|
+
applySessionRunResult(session.record, result);
|
|
828
903
|
saveSessionRecord(workdir, session.record);
|
|
829
904
|
return { ...result, sessionId: session.sessionId, workspacePath: session.workspacePath };
|
|
830
905
|
}
|
|
@@ -885,6 +960,33 @@ export async function doStream(opts) {
|
|
|
885
960
|
const result = await driver.doStream(prepared);
|
|
886
961
|
return finalizeStreamResult(result, opts.workdir, opts.prompt, session);
|
|
887
962
|
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
const failedResult = {
|
|
965
|
+
ok: false,
|
|
966
|
+
message: normalizeErrorMessage(error) || 'Agent stream failed.',
|
|
967
|
+
thinking: null,
|
|
968
|
+
sessionId: session.sessionId,
|
|
969
|
+
workspacePath: session.workspacePath,
|
|
970
|
+
model: session.record.model,
|
|
971
|
+
thinkingEffort: prepared.thinkingEffort,
|
|
972
|
+
elapsedS: 0,
|
|
973
|
+
inputTokens: null,
|
|
974
|
+
outputTokens: null,
|
|
975
|
+
cachedInputTokens: null,
|
|
976
|
+
cacheCreationInputTokens: null,
|
|
977
|
+
contextWindow: null,
|
|
978
|
+
contextUsedTokens: null,
|
|
979
|
+
contextPercent: null,
|
|
980
|
+
codexCumulative: null,
|
|
981
|
+
error: normalizeErrorMessage(error) || 'Agent stream failed.',
|
|
982
|
+
stopReason: null,
|
|
983
|
+
incomplete: true,
|
|
984
|
+
activity: null,
|
|
985
|
+
};
|
|
986
|
+
applySessionRunResult(session.record, failedResult);
|
|
987
|
+
saveSessionRecord(opts.workdir, session.record);
|
|
988
|
+
throw error;
|
|
989
|
+
}
|
|
888
990
|
finally {
|
|
889
991
|
if (bridge) {
|
|
890
992
|
await bridge.stop().catch(() => { });
|