pikiclaw 0.3.31 → 0.3.32
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.
|
@@ -8,7 +8,7 @@ import { createInterface } from 'node:readline';
|
|
|
8
8
|
import { registerDriver } from '../driver.js';
|
|
9
9
|
import {
|
|
10
10
|
// shared helpers
|
|
11
|
-
Q, run, agentError, agentLog, agentWarn, buildStreamPreviewMeta, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, joinErrorMessages, parseTodoWriteAsPlan, IMAGE_EXTS, mimeForExt, listPikiclawSessions, mergeManagedAndNativeSessions, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, applyTurnWindow, shortValue, roundPercent, modelFamily, normalizeClaudeModelId, emptyUsage, normalizeUsageStatus, } from '../index.js';
|
|
11
|
+
Q, run, agentError, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, joinErrorMessages, parseTodoWriteAsPlan, IMAGE_EXTS, mimeForExt, listPikiclawSessions, mergeManagedAndNativeSessions, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, applyTurnWindow, shortValue, roundPercent, modelFamily, normalizeClaudeModelId, emptyUsage, normalizeUsageStatus, } from '../index.js';
|
|
12
12
|
import { AGENT_STREAM_HARD_KILL_GRACE_MS, AGENT_GRACEFUL_ABORT_GRACE_MS, SESSION_RUNNING_THRESHOLD_MS } from '../../core/constants.js';
|
|
13
13
|
import { terminateProcessTree } from '../../core/process-control.js';
|
|
14
14
|
import { getHome, IS_MAC, encodePathAsDirName } from '../../core/platform.js';
|
|
@@ -714,7 +714,11 @@ async function doClaudeInteractiveStream(opts) {
|
|
|
714
714
|
cacheCreationInputTokens: s.cacheCreationInputTokens,
|
|
715
715
|
contextWindow: s.contextWindow,
|
|
716
716
|
contextUsedTokens: s.contextUsedTokens,
|
|
717
|
-
|
|
717
|
+
// Reuse the same calc as the live preview (computeContext) so the final
|
|
718
|
+
// footer % matches the running %. Previously this passed a fraction
|
|
719
|
+
// (used/window) into roundPercent, which expects a percent — divide-by-100
|
|
720
|
+
// bug that made the final read ~12% as ~0.1%.
|
|
721
|
+
contextPercent: computeContext(s).contextPercent,
|
|
718
722
|
codexCumulative: null,
|
|
719
723
|
error,
|
|
720
724
|
plan: s.plan,
|
|
@@ -451,11 +451,13 @@ function findGeminiSessionFile(workdir, sessionId) {
|
|
|
451
451
|
return null;
|
|
452
452
|
}
|
|
453
453
|
for (const entry of entries) {
|
|
454
|
-
if (!entry.isFile() || !entry.name.startsWith('session-')
|
|
454
|
+
if (!entry.isFile() || !entry.name.startsWith('session-'))
|
|
455
|
+
continue;
|
|
456
|
+
if (!entry.name.endsWith('.json') && !entry.name.endsWith('.jsonl'))
|
|
455
457
|
continue;
|
|
456
458
|
const filePath = path.join(chatsDir, entry.name);
|
|
457
459
|
try {
|
|
458
|
-
const data =
|
|
460
|
+
const data = loadGeminiSessionData(filePath);
|
|
459
461
|
if (data?.sessionId === sessionId)
|
|
460
462
|
return filePath;
|
|
461
463
|
}
|
|
@@ -463,12 +465,46 @@ function findGeminiSessionFile(workdir, sessionId) {
|
|
|
463
465
|
}
|
|
464
466
|
return null;
|
|
465
467
|
}
|
|
468
|
+
function loadGeminiSessionData(filePath) {
|
|
469
|
+
try {
|
|
470
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
471
|
+
if (filePath.endsWith('.json'))
|
|
472
|
+
return JSON.parse(content);
|
|
473
|
+
// JSONL format: first line is metadata, subsequent lines are messages or $set updates
|
|
474
|
+
const lines = content.split('\n');
|
|
475
|
+
let data = {};
|
|
476
|
+
const messages = [];
|
|
477
|
+
for (const line of lines) {
|
|
478
|
+
if (!line.trim() || line[0] !== '{')
|
|
479
|
+
continue;
|
|
480
|
+
try {
|
|
481
|
+
const obj = JSON.parse(line);
|
|
482
|
+
if (obj.sessionId && !data.sessionId) {
|
|
483
|
+
data = { ...obj };
|
|
484
|
+
}
|
|
485
|
+
else if (obj.$set) {
|
|
486
|
+
if (obj.$set.lastUpdated)
|
|
487
|
+
data.lastUpdated = obj.$set.lastUpdated;
|
|
488
|
+
}
|
|
489
|
+
else if (obj.type === 'user' || obj.type === 'gemini' || obj.type === 'model' || obj.type === 'assistant') {
|
|
490
|
+
messages.push(obj);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch { /* skip */ }
|
|
494
|
+
}
|
|
495
|
+
data.messages = messages;
|
|
496
|
+
return data;
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
466
502
|
/** Read native Gemini CLI sessions from ~/.gemini/tmp/{projectName}/chats/ */
|
|
467
503
|
function getNativeGeminiSessionsFromFiles(workdir) {
|
|
468
504
|
const chatsDir = geminiChatsDir(workdir);
|
|
469
505
|
if (!chatsDir || !fs.existsSync(chatsDir))
|
|
470
506
|
return [];
|
|
471
|
-
const
|
|
507
|
+
const sessionsById = new Map();
|
|
472
508
|
let entries;
|
|
473
509
|
try {
|
|
474
510
|
entries = fs.readdirSync(chatsDir, { withFileTypes: true });
|
|
@@ -477,13 +513,22 @@ function getNativeGeminiSessionsFromFiles(workdir) {
|
|
|
477
513
|
return [];
|
|
478
514
|
}
|
|
479
515
|
for (const entry of entries) {
|
|
480
|
-
if (!entry.isFile() || !entry.name.startsWith('session-')
|
|
516
|
+
if (!entry.isFile() || !entry.name.startsWith('session-'))
|
|
517
|
+
continue;
|
|
518
|
+
if (!entry.name.endsWith('.json') && !entry.name.endsWith('.jsonl'))
|
|
481
519
|
continue;
|
|
482
520
|
const filePath = path.join(chatsDir, entry.name);
|
|
483
521
|
try {
|
|
484
|
-
const data =
|
|
485
|
-
if (!data
|
|
522
|
+
const data = loadGeminiSessionData(filePath);
|
|
523
|
+
if (!data?.sessionId)
|
|
486
524
|
continue;
|
|
525
|
+
const sessionId = String(data.sessionId);
|
|
526
|
+
const updatedAt = data.lastUpdated || data.startTime || data.createdAt || null;
|
|
527
|
+
// If we already saw this sessionId, only replace it if this file is newer
|
|
528
|
+
const existing = sessionsById.get(sessionId);
|
|
529
|
+
if (existing && updatedAt && existing.runUpdatedAt && Date.parse(updatedAt) <= Date.parse(existing.runUpdatedAt)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
487
532
|
// Extract title from first user message + last Q&A from tail
|
|
488
533
|
let title = null;
|
|
489
534
|
let lastQuestion = null;
|
|
@@ -500,7 +545,7 @@ function getNativeGeminiSessionsFromFiles(workdir) {
|
|
|
500
545
|
lastMessageText = shortValue(text, 500);
|
|
501
546
|
}
|
|
502
547
|
}
|
|
503
|
-
else if (msg.type === 'model' || msg.type === 'assistant') {
|
|
548
|
+
else if (msg.type === 'model' || msg.type === 'assistant' || msg.type === 'gemini') {
|
|
504
549
|
const text = extractGeminiText(msg.content);
|
|
505
550
|
if (text) {
|
|
506
551
|
lastAnswer = shortValue(text, 500);
|
|
@@ -509,18 +554,18 @@ function getNativeGeminiSessionsFromFiles(workdir) {
|
|
|
509
554
|
}
|
|
510
555
|
}
|
|
511
556
|
const numTurns = messages.filter((m) => m.type === 'user' && extractGeminiText(m.content)).length;
|
|
512
|
-
|
|
513
|
-
sessionId
|
|
557
|
+
sessionsById.set(sessionId, {
|
|
558
|
+
sessionId,
|
|
514
559
|
agent: 'gemini',
|
|
515
560
|
workdir,
|
|
516
561
|
workspacePath: null,
|
|
517
562
|
model: null,
|
|
518
|
-
createdAt: data.startTime || null,
|
|
563
|
+
createdAt: data.startTime || data.createdAt || null,
|
|
519
564
|
title,
|
|
520
565
|
running: data.lastUpdated ? Date.now() - Date.parse(data.lastUpdated) < SESSION_RUNNING_THRESHOLD_MS : false,
|
|
521
566
|
runState: data.lastUpdated && Date.now() - Date.parse(data.lastUpdated) < SESSION_RUNNING_THRESHOLD_MS ? 'running' : 'completed',
|
|
522
567
|
runDetail: null,
|
|
523
|
-
runUpdatedAt:
|
|
568
|
+
runUpdatedAt: updatedAt || null,
|
|
524
569
|
classification: null,
|
|
525
570
|
userStatus: null,
|
|
526
571
|
userNote: null,
|
|
@@ -535,7 +580,7 @@ function getNativeGeminiSessionsFromFiles(workdir) {
|
|
|
535
580
|
}
|
|
536
581
|
catch { /* skip */ }
|
|
537
582
|
}
|
|
538
|
-
return
|
|
583
|
+
return [...sessionsById.values()];
|
|
539
584
|
}
|
|
540
585
|
function getNativeGeminiSessions(workdir) {
|
|
541
586
|
return getNativeGeminiSessionsFromFiles(workdir);
|
|
@@ -583,12 +628,12 @@ function getGeminiSessionTail(opts) {
|
|
|
583
628
|
if (!filePath)
|
|
584
629
|
return { ok: false, messages: [], error: 'Session file not found' };
|
|
585
630
|
try {
|
|
586
|
-
const data =
|
|
631
|
+
const data = loadGeminiSessionData(filePath);
|
|
587
632
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
588
633
|
const allMsgs = [];
|
|
589
634
|
for (const msg of messages) {
|
|
590
635
|
const type = typeof msg?.type === 'string' ? msg.type.trim().toLowerCase() : '';
|
|
591
|
-
const role = type === 'user' ? 'user' : type === 'gemini' ? 'assistant' : null;
|
|
636
|
+
const role = type === 'user' ? 'user' : (type === 'gemini' || type === 'model' || type === 'assistant') ? 'assistant' : null;
|
|
592
637
|
if (!role)
|
|
593
638
|
continue;
|
|
594
639
|
const text = extractGeminiText(msg?.content);
|
|
@@ -609,12 +654,12 @@ function getGeminiSessionMessages(opts) {
|
|
|
609
654
|
if (!filePath)
|
|
610
655
|
return { ok: false, messages: [], totalTurns: 0, error: 'Session file not found' };
|
|
611
656
|
try {
|
|
612
|
-
const data =
|
|
657
|
+
const data = loadGeminiSessionData(filePath);
|
|
613
658
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
614
659
|
const allMsgs = [];
|
|
615
660
|
for (const msg of messages) {
|
|
616
661
|
const type = typeof msg?.type === 'string' ? msg.type.trim().toLowerCase() : '';
|
|
617
|
-
const role = type === 'user' ? 'user' : type === 'gemini' ? 'assistant' : null;
|
|
662
|
+
const role = type === 'user' ? 'user' : (type === 'gemini' || type === 'model' || type === 'assistant') ? 'assistant' : null;
|
|
618
663
|
if (!role)
|
|
619
664
|
continue;
|
|
620
665
|
const text = extractGeminiText(msg?.content);
|
|
@@ -62,17 +62,27 @@ function compactModelLabel(model) {
|
|
|
62
62
|
const slashIdx = trimmed.indexOf('/');
|
|
63
63
|
return slashIdx > 0 ? trimmed.slice(slashIdx + 1) : trimmed;
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Split footer fields into a primary identity line (agent + model) and a
|
|
67
|
+
* secondary runtime line (effort, context%, elapsed). Channel renderers
|
|
68
|
+
* compose the two lines with their own visual styling so narrow IM clients
|
|
69
|
+
* never have to soft-wrap a single dense line.
|
|
70
|
+
*/
|
|
71
|
+
export function formatFooterParts(agent, elapsedMs, meta, contextPercent, decorations) {
|
|
72
|
+
const identityParts = [agent];
|
|
67
73
|
if (decorations?.model)
|
|
68
|
-
|
|
74
|
+
identityParts.push(compactModelLabel(decorations.model));
|
|
75
|
+
const runtimeParts = [];
|
|
69
76
|
if (decorations?.effort)
|
|
70
|
-
|
|
77
|
+
runtimeParts.push(decorations.effort);
|
|
71
78
|
const ctx = contextPercent ?? meta?.contextPercent ?? null;
|
|
72
79
|
if (ctx != null)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
80
|
+
runtimeParts.push(`${ctx}%`);
|
|
81
|
+
runtimeParts.push(fmtCompactUptime(Math.max(0, Math.round(elapsedMs))));
|
|
82
|
+
return {
|
|
83
|
+
identity: identityParts.join(' · '),
|
|
84
|
+
runtime: runtimeParts.join(' · '),
|
|
85
|
+
};
|
|
76
86
|
}
|
|
77
87
|
// ---------------------------------------------------------------------------
|
|
78
88
|
// Activity trimming
|
|
@@ -139,10 +149,6 @@ export function buildProviderUsageLines(usage) {
|
|
|
139
149
|
export function extractFinalReplyData(agent, result) {
|
|
140
150
|
const footerStatus = result.incomplete || !result.ok ? 'failed' : 'done';
|
|
141
151
|
const elapsedMs = result.elapsedS * 1000;
|
|
142
|
-
const footerSummary = formatFooterSummary(agent, elapsedMs, null, result.contextPercent ?? null, {
|
|
143
|
-
model: result.model,
|
|
144
|
-
effort: result.thinkingEffort,
|
|
145
|
-
});
|
|
146
152
|
let activityNarrative = null;
|
|
147
153
|
let activityCommandSummary = null;
|
|
148
154
|
if (result.activity) {
|
|
@@ -177,7 +183,6 @@ export function extractFinalReplyData(agent, result) {
|
|
|
177
183
|
}
|
|
178
184
|
return {
|
|
179
185
|
footerStatus,
|
|
180
|
-
footerSummary,
|
|
181
186
|
activityNarrative,
|
|
182
187
|
activityCommandSummary,
|
|
183
188
|
thinkingDisplay,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { encodeCommandAction } from '../../bot/command-ui.js';
|
|
8
8
|
import { fmtUptime, fmtTokens, fmtBytes } from '../../bot/bot.js';
|
|
9
9
|
import { summarizePromptForStatus } from '../../bot/commands.js';
|
|
10
|
-
import { footerStatusSymbol,
|
|
10
|
+
import { footerStatusSymbol, formatFooterParts, trimActivityForPreview, buildProviderUsageLines, extractFinalReplyData, extractStreamPreviewData, } from '../../bot/render-shared.js';
|
|
11
11
|
import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from '../../bot/human-loop.js';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { listSubdirs } from '../../bot/bot.js';
|
|
@@ -15,10 +15,12 @@ import { listSubdirs } from '../../bot/bot.js';
|
|
|
15
15
|
// Helpers
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
function formatPreviewFooter(agent, elapsedMs, meta, decorations) {
|
|
18
|
-
|
|
18
|
+
const parts = formatFooterParts(agent, elapsedMs, meta, null, decorations);
|
|
19
|
+
return `${footerStatusSymbol('running')} ${parts.identity}\n*${parts.runtime}*`;
|
|
19
20
|
}
|
|
20
21
|
function formatFinalFooter(status, agent, elapsedMs, contextPercent, decorations) {
|
|
21
|
-
|
|
22
|
+
const parts = formatFooterParts(agent, elapsedMs, null, contextPercent ?? null, decorations);
|
|
23
|
+
return `${footerStatusSymbol(status)} ${parts.identity}\n*${parts.runtime}*`;
|
|
22
24
|
}
|
|
23
25
|
function truncateLabel(label, maxChars = 24) {
|
|
24
26
|
return label.length > maxChars ? `${label.slice(0, Math.max(1, maxChars - 1))}…` : label;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { encodeCommandAction } from '../../bot/command-ui.js';
|
|
5
5
|
import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from '../../bot/human-loop.js';
|
|
6
|
-
import { footerStatusSymbol,
|
|
6
|
+
import { footerStatusSymbol, formatFooterParts, trimActivityForPreview, buildProviderUsageLines, extractFinalReplyData, extractStreamPreviewData, parseGfmTable, } from '../../bot/render-shared.js';
|
|
7
7
|
export function escapeHtml(t) {
|
|
8
8
|
return t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
9
9
|
}
|
|
@@ -256,10 +256,14 @@ export function renderSkillsListHtml(d) {
|
|
|
256
256
|
return lines.join('\n');
|
|
257
257
|
}
|
|
258
258
|
export function formatPreviewFooterHtml(agent, elapsedMs, meta, decorations) {
|
|
259
|
-
|
|
259
|
+
const parts = formatFooterParts(agent, elapsedMs, meta, null, decorations);
|
|
260
|
+
const primary = escapeHtml(`${footerStatusSymbol('running')} ${parts.identity}`);
|
|
261
|
+
return `${primary}\n<i>${escapeHtml(parts.runtime)}</i>`;
|
|
260
262
|
}
|
|
261
263
|
function formatFinalFooterHtml(status, agent, elapsedMs, contextPercent, decorations) {
|
|
262
|
-
|
|
264
|
+
const parts = formatFooterParts(agent, elapsedMs, null, contextPercent ?? null, decorations);
|
|
265
|
+
const primary = escapeHtml(`${footerStatusSymbol(status)} ${parts.identity}`);
|
|
266
|
+
return `${primary}\n<i>${escapeHtml(parts.runtime)}</i>`;
|
|
263
267
|
}
|
|
264
268
|
export function formatProviderUsageLines(usage) {
|
|
265
269
|
return buildProviderUsageLines(usage).map(line => line.bold ? `<b>${escapeHtml(line.text)}</b>` : escapeHtml(line.text));
|
package/package.json
CHANGED