pikiclaw 0.2.69 → 0.2.70
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 +112 -14
- package/dist/bot-feishu-render.js +35 -8
- package/dist/bot-telegram.js +21 -5
- package/dist/cli.js +9 -4
- package/dist/driver-claude.js +3 -2
- package/dist/process-control.js +1 -0
- package/package.json +1 -1
package/dist/bot-command-ui.js
CHANGED
|
@@ -31,6 +31,12 @@ export function encodeCommandAction(action) {
|
|
|
31
31
|
return `mod:${action.modelId}`;
|
|
32
32
|
case 'effort.set':
|
|
33
33
|
return `eff:${action.effort}`;
|
|
34
|
+
case 'models.select.model':
|
|
35
|
+
return `md:${action.modelId}`;
|
|
36
|
+
case 'models.select.effort':
|
|
37
|
+
return `ed:${action.effort}`;
|
|
38
|
+
case 'models.confirm':
|
|
39
|
+
return 'mc';
|
|
34
40
|
case 'skill.run':
|
|
35
41
|
return `skr:${action.command}`;
|
|
36
42
|
}
|
|
@@ -70,6 +76,20 @@ export function decodeCommandAction(data) {
|
|
|
70
76
|
return null;
|
|
71
77
|
return { kind: 'effort.set', effort };
|
|
72
78
|
}
|
|
79
|
+
if (data.startsWith('md:')) {
|
|
80
|
+
const modelId = data.slice(3);
|
|
81
|
+
if (!modelId)
|
|
82
|
+
return null;
|
|
83
|
+
return { kind: 'models.select.model', modelId };
|
|
84
|
+
}
|
|
85
|
+
if (data.startsWith('ed:')) {
|
|
86
|
+
const effort = data.slice(3);
|
|
87
|
+
if (!effort)
|
|
88
|
+
return null;
|
|
89
|
+
return { kind: 'models.select.effort', effort };
|
|
90
|
+
}
|
|
91
|
+
if (data === 'mc')
|
|
92
|
+
return { kind: 'models.confirm' };
|
|
73
93
|
if (data.startsWith('skr:')) {
|
|
74
94
|
const command = data.slice(4);
|
|
75
95
|
if (!command)
|
|
@@ -134,24 +154,57 @@ export function buildAgentsCommandView(bot, chatId) {
|
|
|
134
154
|
rows: chunkRows(actions, 3),
|
|
135
155
|
};
|
|
136
156
|
}
|
|
137
|
-
|
|
157
|
+
const modelsDrafts = new Map();
|
|
158
|
+
async function initModelsDraft(bot, chatId) {
|
|
138
159
|
const data = await getModelsListData(bot, chatId);
|
|
139
|
-
const
|
|
160
|
+
const draft = { modelId: data.currentModel, effort: data.effort?.current ?? null };
|
|
161
|
+
modelsDrafts.set(String(chatId), draft);
|
|
162
|
+
return draft;
|
|
163
|
+
}
|
|
164
|
+
export async function buildModelsCommandView(bot, chatId, draft) {
|
|
165
|
+
const data = await getModelsListData(bot, chatId);
|
|
166
|
+
// Initialize draft from current state or use the provided one
|
|
167
|
+
const d = draft ?? {
|
|
168
|
+
modelId: data.currentModel,
|
|
169
|
+
effort: data.effort?.current ?? null,
|
|
170
|
+
};
|
|
171
|
+
modelsDrafts.set(String(chatId), d);
|
|
172
|
+
const isSelected = (modelId) => modelMatchesSelection(data.agent, modelId, d.modelId);
|
|
173
|
+
const models = [...data.models].sort((a, b) => Number(isSelected(b.id)) - Number(isSelected(a.id)));
|
|
140
174
|
const modelButtons = models.map(model => ({
|
|
141
175
|
label: model.alias || model.id,
|
|
142
|
-
action: { kind: 'model
|
|
143
|
-
state: buttonStateFromFlags({ isCurrent: model.
|
|
144
|
-
primary: model.
|
|
176
|
+
action: { kind: 'models.select.model', modelId: model.id },
|
|
177
|
+
state: buttonStateFromFlags({ isCurrent: isSelected(model.id) }),
|
|
178
|
+
primary: isSelected(model.id),
|
|
145
179
|
}));
|
|
146
|
-
const rows = chunkRows(modelButtons,
|
|
180
|
+
const rows = chunkRows(modelButtons, 1);
|
|
147
181
|
if (data.effort) {
|
|
148
|
-
|
|
182
|
+
const effortButtons = data.effort.levels.map(level => ({
|
|
149
183
|
label: level.label,
|
|
150
|
-
action: { kind: 'effort
|
|
151
|
-
state: buttonStateFromFlags({ isCurrent: level.
|
|
152
|
-
primary: level.
|
|
153
|
-
}))
|
|
184
|
+
action: { kind: 'models.select.effort', effort: level.id },
|
|
185
|
+
state: buttonStateFromFlags({ isCurrent: level.id === d.effort }),
|
|
186
|
+
primary: level.id === d.effort,
|
|
187
|
+
}));
|
|
188
|
+
// Section label — clicking it is harmless (triggers confirm, which is noop if nothing changed)
|
|
189
|
+
rows.push([{
|
|
190
|
+
label: '— Thinking Effort —',
|
|
191
|
+
action: { kind: 'models.confirm' },
|
|
192
|
+
state: 'default',
|
|
193
|
+
primary: false,
|
|
194
|
+
}]);
|
|
195
|
+
// ≤3 levels fit in one row; 4+ split into rows of 2 to avoid Feishu truncation
|
|
196
|
+
rows.push(...chunkRows(effortButtons, effortButtons.length <= 3 ? effortButtons.length : 2));
|
|
154
197
|
}
|
|
198
|
+
// Detect whether draft differs from current live values
|
|
199
|
+
const modelChanged = !modelMatchesSelection(data.agent, d.modelId, data.currentModel);
|
|
200
|
+
const effortChanged = !!(data.effort && d.effort !== data.effort.current);
|
|
201
|
+
const hasChanges = modelChanged || effortChanged;
|
|
202
|
+
rows.push([{
|
|
203
|
+
label: hasChanges ? '✓ Apply' : '✓ OK',
|
|
204
|
+
action: { kind: 'models.confirm' },
|
|
205
|
+
state: 'default',
|
|
206
|
+
primary: hasChanges,
|
|
207
|
+
}]);
|
|
155
208
|
return {
|
|
156
209
|
kind: 'models',
|
|
157
210
|
title: 'Models',
|
|
@@ -159,15 +212,15 @@ export async function buildModelsCommandView(bot, chatId) {
|
|
|
159
212
|
metaLines: [
|
|
160
213
|
...(data.sources.length ? [`Source: ${data.sources.join(', ')}`] : []),
|
|
161
214
|
...(data.note ? [data.note] : []),
|
|
162
|
-
...(data.effort ? [`Thinking Effort: ${
|
|
215
|
+
...(data.effort ? [`Thinking Effort: ${d.effort}`] : []),
|
|
163
216
|
],
|
|
164
217
|
items: models.map(model => ({
|
|
165
218
|
label: model.alias || model.id,
|
|
166
219
|
detail: model.alias ? model.id : null,
|
|
167
|
-
state: buttonStateFromFlags({ isCurrent: model.
|
|
220
|
+
state: buttonStateFromFlags({ isCurrent: isSelected(model.id) }),
|
|
168
221
|
})),
|
|
169
222
|
emptyText: 'No discoverable models found.',
|
|
170
|
-
helperText: data.models.length ? '
|
|
223
|
+
helperText: data.models.length ? 'Select model and effort, then tap Apply.' : null,
|
|
171
224
|
rows,
|
|
172
225
|
};
|
|
173
226
|
}
|
|
@@ -286,6 +339,51 @@ export async function executeCommandAction(bot, chatId, action, opts = {}) {
|
|
|
286
339
|
},
|
|
287
340
|
};
|
|
288
341
|
}
|
|
342
|
+
case 'models.select.model': {
|
|
343
|
+
const draft = modelsDrafts.get(String(chatId)) ?? await initModelsDraft(bot, chatId);
|
|
344
|
+
draft.modelId = action.modelId;
|
|
345
|
+
return { kind: 'view', view: await buildModelsCommandView(bot, chatId, draft), callbackText: '' };
|
|
346
|
+
}
|
|
347
|
+
case 'models.select.effort': {
|
|
348
|
+
const draft = modelsDrafts.get(String(chatId)) ?? await initModelsDraft(bot, chatId);
|
|
349
|
+
draft.effort = action.effort;
|
|
350
|
+
return { kind: 'view', view: await buildModelsCommandView(bot, chatId, draft), callbackText: '' };
|
|
351
|
+
}
|
|
352
|
+
case 'models.confirm': {
|
|
353
|
+
const chat = bot.chat(chatId);
|
|
354
|
+
const draft = modelsDrafts.get(String(chatId));
|
|
355
|
+
modelsDrafts.delete(String(chatId));
|
|
356
|
+
if (!draft)
|
|
357
|
+
return { kind: 'noop', message: 'No changes' };
|
|
358
|
+
const currentModel = bot.modelForAgent(chat.agent);
|
|
359
|
+
const currentEffort = bot.effortForAgent(chat.agent);
|
|
360
|
+
const modelChanged = !modelMatchesSelection(chat.agent, draft.modelId, currentModel);
|
|
361
|
+
const effortChanged = draft.effort != null && draft.effort !== currentEffort;
|
|
362
|
+
if (!modelChanged && !effortChanged) {
|
|
363
|
+
return { kind: 'noop', message: 'No changes' };
|
|
364
|
+
}
|
|
365
|
+
const parts = [];
|
|
366
|
+
if (modelChanged) {
|
|
367
|
+
bot.switchModelForChat(chatId, draft.modelId);
|
|
368
|
+
parts.push(`Model: ${draft.modelId}`);
|
|
369
|
+
}
|
|
370
|
+
if (effortChanged) {
|
|
371
|
+
bot.switchEffortForChat(chatId, draft.effort);
|
|
372
|
+
parts.push(`Effort: ${draft.effort}`);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
kind: 'notice',
|
|
376
|
+
callbackText: parts.join(', '),
|
|
377
|
+
notice: {
|
|
378
|
+
title: 'Configuration Updated',
|
|
379
|
+
value: parts.join('\n'),
|
|
380
|
+
detail: modelChanged
|
|
381
|
+
? `${chat.agent} · session reset`
|
|
382
|
+
: `${chat.agent} · takes effect on next message`,
|
|
383
|
+
valueMode: 'plain',
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
289
387
|
case 'skill.run': {
|
|
290
388
|
const resolved = resolveSkillPrompt(bot, chatId, action.command, '');
|
|
291
389
|
if (!resolved)
|
|
@@ -100,6 +100,29 @@ export function renderCommandSelectionCard(view) {
|
|
|
100
100
|
rows: view.rows.map(row => ({ actions: row.map(actionButton) })),
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Strip code-fence markers (``` / ~~~) from text that is not meant to be
|
|
105
|
+
* rendered as full markdown (thinking, activity). Truncation by
|
|
106
|
+
* extractThinkingTail can leave stray fences that open unwanted code blocks.
|
|
107
|
+
*/
|
|
108
|
+
function stripCodeFences(text) {
|
|
109
|
+
return text.replace(/^(`{3,}|~{3,}).*$/gm, '');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Ensure code fences in markdown text are balanced. If an odd number of
|
|
113
|
+
* fence markers is detected, append a closing fence so partial code blocks
|
|
114
|
+
* do not swallow the rest of the card.
|
|
115
|
+
*/
|
|
116
|
+
function ensureBalancedCodeFences(text) {
|
|
117
|
+
let inCode = false;
|
|
118
|
+
for (const line of text.split('\n')) {
|
|
119
|
+
const trimmed = line.trimStart();
|
|
120
|
+
if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) {
|
|
121
|
+
inCode = !inCode;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return inCode ? text + '\n```' : text;
|
|
125
|
+
}
|
|
103
126
|
function escapeFeishuMarkdownText(text) {
|
|
104
127
|
return text.replace(/([\\`*_{}[\]()#+\-.!|>~])/g, '\\$1');
|
|
105
128
|
}
|
|
@@ -172,19 +195,19 @@ function buildPreviewMarkdown(input, options) {
|
|
|
172
195
|
const data = extractStreamPreviewData(input);
|
|
173
196
|
const parts = [];
|
|
174
197
|
if (data.planDisplay) {
|
|
175
|
-
parts.push(`**Plan**\n${data.planDisplay}`);
|
|
198
|
+
parts.push(`**Plan**\n${stripCodeFences(data.planDisplay)}`);
|
|
176
199
|
}
|
|
177
200
|
if (data.activityDisplay) {
|
|
178
|
-
parts.push(`**Activity**\n${trimActivityForPreview(data.activityDisplay, data.maxActivity)}`);
|
|
201
|
+
parts.push(`**Activity**\n${stripCodeFences(trimActivityForPreview(data.activityDisplay, data.maxActivity))}`);
|
|
179
202
|
}
|
|
180
203
|
if (data.thinkDisplay && !data.display) {
|
|
181
|
-
parts.push(`**${data.label}**\n${data.thinkDisplay}`);
|
|
204
|
+
parts.push(`**${data.label}**\n${stripCodeFences(data.thinkDisplay)}`);
|
|
182
205
|
}
|
|
183
206
|
else if (data.display) {
|
|
184
207
|
if (data.rawThinking) {
|
|
185
|
-
parts.push(`**${data.label}**\n${data.thinkSnippet}`);
|
|
208
|
+
parts.push(`**${data.label}**\n${stripCodeFences(data.thinkSnippet)}`);
|
|
186
209
|
}
|
|
187
|
-
parts.push(data.preview);
|
|
210
|
+
parts.push(ensureBalancedCodeFences(data.preview));
|
|
188
211
|
}
|
|
189
212
|
if (options?.includeFooter !== false) {
|
|
190
213
|
parts.push(formatPreviewFooter(input.agent, input.elapsedMs, input.meta ?? null));
|
|
@@ -213,21 +236,21 @@ export function buildFinalReplyRender(agent, result) {
|
|
|
213
236
|
let activityText = '';
|
|
214
237
|
let activityNoteText = '';
|
|
215
238
|
if (data.activityNarrative) {
|
|
216
|
-
activityText = `**Activity**\n${data.activityNarrative}\n\n`;
|
|
239
|
+
activityText = `**Activity**\n${stripCodeFences(data.activityNarrative)}\n\n`;
|
|
217
240
|
}
|
|
218
241
|
if (data.activityCommandSummary) {
|
|
219
242
|
activityNoteText = `*${data.activityCommandSummary}*\n\n`;
|
|
220
243
|
}
|
|
221
244
|
let thinkingText = '';
|
|
222
245
|
if (data.thinkingDisplay) {
|
|
223
|
-
thinkingText = `**${data.thinkLabel}**\n${data.thinkingDisplay}\n\n`;
|
|
246
|
+
thinkingText = `**${data.thinkLabel}**\n${stripCodeFences(data.thinkingDisplay)}\n\n`;
|
|
224
247
|
}
|
|
225
248
|
let statusText = '';
|
|
226
249
|
if (data.statusLines) {
|
|
227
250
|
statusText = `**⚠ Incomplete Response**\n${data.statusLines.join('\n')}\n\n`;
|
|
228
251
|
}
|
|
229
252
|
const headerText = `${activityText}${activityNoteText}${statusText}${thinkingText}`;
|
|
230
|
-
const bodyText = data.bodyMessage;
|
|
253
|
+
const bodyText = ensureBalancedCodeFences(data.bodyMessage);
|
|
231
254
|
return {
|
|
232
255
|
fullText: `${headerText}${bodyText}${footerText}`,
|
|
233
256
|
headerText,
|
|
@@ -583,6 +606,10 @@ function normalizeFeishuMarkdown(lines) {
|
|
|
583
606
|
pendingBlankLine = false;
|
|
584
607
|
out.push(line);
|
|
585
608
|
}
|
|
609
|
+
// Safety: close any unclosed code block so it doesn't swallow the rest of
|
|
610
|
+
// a Feishu card element (e.g. truncated body text ending mid-fence).
|
|
611
|
+
if (inCodeBlock)
|
|
612
|
+
out.push('```');
|
|
586
613
|
return out.join('\n');
|
|
587
614
|
}
|
|
588
615
|
export function adaptMarkdownForFeishu(markdown) {
|
package/dist/bot-telegram.js
CHANGED
|
@@ -651,7 +651,8 @@ export class TelegramBot extends Bot {
|
|
|
651
651
|
finalMsgId = await replacePreview(rendered.fullHtml);
|
|
652
652
|
}
|
|
653
653
|
else {
|
|
654
|
-
|
|
654
|
+
// Split: header on first message, footer on last message
|
|
655
|
+
const maxFirst = 3900 - rendered.headerHtml.length;
|
|
655
656
|
let firstBody;
|
|
656
657
|
let remaining;
|
|
657
658
|
if (maxFirst > 200) {
|
|
@@ -665,13 +666,28 @@ export class TelegramBot extends Bot {
|
|
|
665
666
|
firstBody = '';
|
|
666
667
|
remaining = rendered.bodyHtml;
|
|
667
668
|
}
|
|
668
|
-
const firstHtml = `${rendered.headerHtml}${firstBody}${rendered.footerHtml}`;
|
|
669
|
-
finalMsgId = await replacePreview(firstHtml);
|
|
670
669
|
if (remaining.trim()) {
|
|
670
|
+
// Multi-message: header on first, footer on last
|
|
671
|
+
const firstHtml = `${rendered.headerHtml}${firstBody}`;
|
|
672
|
+
finalMsgId = await replacePreview(firstHtml);
|
|
671
673
|
const chunks = splitText(remaining, 3800);
|
|
672
|
-
for (
|
|
673
|
-
|
|
674
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
675
|
+
const isLast = i === chunks.length - 1;
|
|
676
|
+
const chunkText = isLast ? `${chunks[i]}${rendered.footerHtml}` : chunks[i];
|
|
677
|
+
remember(await sendFinalText(chunkText, finalMsgId ?? phId ?? ctx.messageId));
|
|
674
678
|
}
|
|
679
|
+
// Safety: re-clear the Stop keyboard on the placeholder in case the first edit silently failed
|
|
680
|
+
if (phId != null) {
|
|
681
|
+
try {
|
|
682
|
+
await this.channel.editMessage(ctx.chatId, phId, firstHtml || '(done)', { parseMode: 'HTML', keyboard: { inline_keyboard: [] } });
|
|
683
|
+
}
|
|
684
|
+
catch { }
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
// Body fits on first message; only footer pushes it over — keep together
|
|
689
|
+
const firstHtml = `${rendered.headerHtml}${firstBody}${rendered.footerHtml}`;
|
|
690
|
+
finalMsgId = await replacePreview(firstHtml);
|
|
675
691
|
}
|
|
676
692
|
}
|
|
677
693
|
return { primaryMessageId: finalMsgId, messageIds };
|
package/dist/cli.js
CHANGED
|
@@ -299,10 +299,15 @@ Docs: https://github.com/xiaotonng/pikiclaw
|
|
|
299
299
|
*/
|
|
300
300
|
function persistWorkdir(args, userConfig) {
|
|
301
301
|
if (!process.env.PIKICLAW_DAEMON_CHILD) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
302
|
+
// Only overwrite persisted workdir when the user explicitly passed -w.
|
|
303
|
+
// Falling back to cwd when no flag is given can clobber a valid saved
|
|
304
|
+
// workdir with a temp directory (e.g. after a non-daemon restart).
|
|
305
|
+
if (args.workdir) {
|
|
306
|
+
const cliWorkdir = path.resolve(args.workdir);
|
|
307
|
+
if (userConfig.workdir !== cliWorkdir) {
|
|
308
|
+
updateUserConfig({ workdir: cliWorkdir });
|
|
309
|
+
return loadUserConfig();
|
|
310
|
+
}
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
return userConfig;
|
package/dist/driver-claude.js
CHANGED
|
@@ -223,9 +223,10 @@ function getNativeClaudeSessions(workdir) {
|
|
|
223
223
|
const filePath = path.join(projectDir, entry.name);
|
|
224
224
|
try {
|
|
225
225
|
const stat = fs.statSync(filePath);
|
|
226
|
-
// Read
|
|
226
|
+
// Read enough bytes to get past the system_prompt line (can be 20KB+) and
|
|
227
|
+
// reach the first user/assistant events for title and model extraction.
|
|
227
228
|
const fd = fs.openSync(filePath, 'r');
|
|
228
|
-
const buf = Buffer.alloc(
|
|
229
|
+
const buf = Buffer.alloc(65536);
|
|
229
230
|
const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
230
231
|
fs.closeSync(fd);
|
|
231
232
|
const head = buf.toString('utf8', 0, bytesRead);
|
package/dist/process-control.js
CHANGED
package/package.json
CHANGED