pikiclaw 0.3.16 → 0.3.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.
package/README.md
CHANGED
|
@@ -258,6 +258,16 @@ See also: [ARCHITECTURE.md](ARCHITECTURE.md) · [INTEGRATION.md](INTEGRATION.md)
|
|
|
258
258
|
|
|
259
259
|
---
|
|
260
260
|
|
|
261
|
+
## Contributing
|
|
262
|
+
|
|
263
|
+
Contributions are welcome! Whether it's a bug fix, new feature, or documentation improvement — we appreciate your help.
|
|
264
|
+
|
|
265
|
+
- Read the **[Contributing Guide](CONTRIBUTING.md)** to get started
|
|
266
|
+
- Check out [`good first issue`](https://github.com/xiaotonng/pikiclaw/labels/good%20first%20issue) and [`help wanted`](https://github.com/xiaotonng/pikiclaw/labels/help%20wanted) labels for contribution ideas
|
|
267
|
+
- Open an issue first for larger changes so we can discuss the approach
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
261
271
|
## License
|
|
262
272
|
|
|
263
273
|
[MIT](LICENSE)
|
|
@@ -7,6 +7,36 @@
|
|
|
7
7
|
import { fmtUptime, formatThinkingForDisplay, thinkLabel } from './bot.js';
|
|
8
8
|
import { formatActivityCommandSummary, parseActivitySummary, renderPlanForPreview, summarizeActivityForPreview } from './streaming.js';
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
|
+
// GFM table parsing
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** Parse GFM table lines into structured headers + rows. */
|
|
13
|
+
export function parseGfmTable(tableLines) {
|
|
14
|
+
if (tableLines.length < 3)
|
|
15
|
+
return null;
|
|
16
|
+
const parseRow = (line) => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(c => c.trim());
|
|
17
|
+
const isSep = (line) => {
|
|
18
|
+
const cells = parseRow(line);
|
|
19
|
+
return cells.length > 0 && cells.every(c => /^:?-{2,}:?$/.test(c));
|
|
20
|
+
};
|
|
21
|
+
let headerIdx = -1;
|
|
22
|
+
for (let i = 0; i < tableLines.length - 1; i++) {
|
|
23
|
+
if (isSep(tableLines[i + 1])) {
|
|
24
|
+
headerIdx = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (headerIdx < 0)
|
|
29
|
+
return null;
|
|
30
|
+
const headers = parseRow(tableLines[headerIdx]);
|
|
31
|
+
const rows = [];
|
|
32
|
+
for (let i = headerIdx + 2; i < tableLines.length; i++) {
|
|
33
|
+
if (isSep(tableLines[i]))
|
|
34
|
+
continue;
|
|
35
|
+
rows.push(parseRow(tableLines[i]));
|
|
36
|
+
}
|
|
37
|
+
return rows.length ? { headers, rows } : null;
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
10
40
|
// Footer helpers
|
|
11
41
|
// ---------------------------------------------------------------------------
|
|
12
42
|
export function fmtCompactUptime(ms) {
|
|
@@ -122,28 +122,42 @@ function buildCardFromView(view) {
|
|
|
122
122
|
const content = adapted.length > FEISHU_CARD_MAX
|
|
123
123
|
? adapted.slice(0, FEISHU_CARD_MAX) + '\n\n...(truncated)'
|
|
124
124
|
: adapted;
|
|
125
|
-
const
|
|
126
|
-
config: { wide_screen_mode: true, update_multi: true },
|
|
127
|
-
elements: [{ tag: 'markdown', content }],
|
|
128
|
-
};
|
|
129
|
-
if (view.title) {
|
|
130
|
-
card.header = {
|
|
131
|
-
template: view.template || 'blue',
|
|
132
|
-
title: { content: view.title, tag: 'plain_text' },
|
|
133
|
-
};
|
|
134
|
-
}
|
|
125
|
+
const actionElements = [];
|
|
135
126
|
for (const row of view.rows || []) {
|
|
136
127
|
const actions = row.actions.filter(Boolean);
|
|
137
128
|
if (!actions.length)
|
|
138
129
|
continue;
|
|
139
|
-
const element = {
|
|
140
|
-
tag: 'action',
|
|
141
|
-
actions,
|
|
142
|
-
};
|
|
130
|
+
const element = { tag: 'action', actions };
|
|
143
131
|
const layout = row.layout || inferActionLayout(actions);
|
|
144
132
|
if (layout)
|
|
145
133
|
element.layout = layout;
|
|
146
|
-
|
|
134
|
+
actionElements.push(element);
|
|
135
|
+
}
|
|
136
|
+
// Card JSON 2.0 supports tables in markdown but dropped `tag: action`.
|
|
137
|
+
// Use v2 for content-only cards; fall back to v1 when buttons are needed.
|
|
138
|
+
if (actionElements.length) {
|
|
139
|
+
const card = {
|
|
140
|
+
config: { wide_screen_mode: true, update_multi: true },
|
|
141
|
+
elements: [{ tag: 'markdown', content }, ...actionElements],
|
|
142
|
+
};
|
|
143
|
+
if (view.title) {
|
|
144
|
+
card.header = {
|
|
145
|
+
template: view.template || 'blue',
|
|
146
|
+
title: { content: view.title, tag: 'plain_text' },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return card;
|
|
150
|
+
}
|
|
151
|
+
const card = {
|
|
152
|
+
schema: '2.0',
|
|
153
|
+
config: { update_multi: true },
|
|
154
|
+
body: { elements: [{ tag: 'markdown', content }] },
|
|
155
|
+
};
|
|
156
|
+
if (view.title) {
|
|
157
|
+
card.header = {
|
|
158
|
+
template: view.template || 'blue',
|
|
159
|
+
title: { content: view.title, tag: 'plain_text' },
|
|
160
|
+
};
|
|
147
161
|
}
|
|
148
162
|
return card;
|
|
149
163
|
}
|
|
@@ -74,7 +74,7 @@ export function adaptMarkdownForFeishu(markdown) {
|
|
|
74
74
|
i++;
|
|
75
75
|
continue;
|
|
76
76
|
}
|
|
77
|
-
// Pass GFM tables through —
|
|
77
|
+
// Pass GFM tables through — rendered natively with card schema 2.0
|
|
78
78
|
if (i + 1 < lines.length && isGfmTableRow(lines[i]) && isGfmTableSeparator(lines[i + 1])) {
|
|
79
79
|
while (i < lines.length && isGfmTableRow(lines[i])) {
|
|
80
80
|
out.push(lines[i]);
|
|
@@ -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, formatFooterSummary, trimActivityForPreview, buildProviderUsageLines, extractFinalReplyData, extractStreamPreviewData, } from '../../bot/render-shared.js';
|
|
6
|
+
import { footerStatusSymbol, formatFooterSummary, 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
|
}
|
|
@@ -194,15 +194,28 @@ export function mdToTgHtml(text) {
|
|
|
194
194
|
const tableLine = lines[i].trim();
|
|
195
195
|
if (!tableLine.startsWith('|'))
|
|
196
196
|
break;
|
|
197
|
-
if (/^\|[\s\-:|]+\|$/.test(tableLine)) {
|
|
198
|
-
i++;
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
197
|
tableLines.push(tableLine);
|
|
202
198
|
i++;
|
|
203
199
|
}
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
const parsed = parseGfmTable(tableLines);
|
|
201
|
+
if (parsed && parsed.headers.length >= 2) {
|
|
202
|
+
const parts = [];
|
|
203
|
+
for (let r = 0; r < parsed.rows.length; r++) {
|
|
204
|
+
if (r > 0)
|
|
205
|
+
parts.push('');
|
|
206
|
+
const cells = parsed.rows[r];
|
|
207
|
+
parts.push(`<b>${escapeHtml(cells[0] || '')}</b>`);
|
|
208
|
+
for (let c = 1; c < parsed.headers.length; c++) {
|
|
209
|
+
parts.push(`${escapeHtml(parsed.headers[c])}: ${escapeHtml(cells[c] || '')}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
result.push(parts.join('\n'));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const plain = tableLines.filter(l => !/^\|[\s\-:|]+\|$/.test(l.trim()));
|
|
216
|
+
if (plain.length)
|
|
217
|
+
result.push(`<pre>${escapeHtml(plain.join('\n'))}</pre>`);
|
|
218
|
+
}
|
|
206
219
|
continue;
|
|
207
220
|
}
|
|
208
221
|
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
package/package.json
CHANGED