@blockrun/franklin 3.16.2 → 3.16.4

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.
@@ -233,7 +233,7 @@ const DIRECT_COMMANDS = {
233
233
  ` **Coding:** /commit /review /test /fix /debug /explain /search /find /refactor /scaffold\n` +
234
234
  ` **Git:** /push /pr /undo /status /diff /log /branch /stash /unstash\n` +
235
235
  ` **Analysis:** /security /lint /optimize /todo /deps /clean /migrate /doc\n` +
236
- ` **Session:** /plan /ultraplan /execute /compact /retry /sessions /resume /session-search /context /tasks\n` +
236
+ ` **Session:** /plan /ultraplan /execute /compact /retry /sessions /resume /session-search /context /tasks /history /transcript\n` +
237
237
  ` **Power:** /ultrathink [query] /ultraplan /noplan /moa [query] /dump\n` +
238
238
  ` **Info:** /model /auto /wallet /cost /tokens /learnings /brain /mcp /doctor /version /bug /help\n` +
239
239
  ` **UI:** /clear /exit\n` +
@@ -259,6 +259,53 @@ const DIRECT_COMMANDS = {
259
259
  }
260
260
  }
261
261
  output += 'Use `/delete <number>` to remove exchanges (e.g., `/delete 2` or `/delete 3-5`).\n';
262
+ output += 'Use `/transcript` for the full, un-truncated transcript.\n';
263
+ ctx.onEvent({ kind: 'text_delta', text: output });
264
+ emitDone(ctx);
265
+ },
266
+ '/transcript': (ctx) => {
267
+ // Dump the FULL, un-truncated conversation as a single fresh stdout
268
+ // block. Works around the limitation that the terminal's native
269
+ // scrollback fills up faster than we can render long Franklin sessions
270
+ // — by the time you scroll up to look for the first message, the
271
+ // older Ink output has been pushed out of the terminal's ring buffer.
272
+ // The fresh emit here becomes one contiguous block, so the user can
273
+ // scroll *that* to read everything in order.
274
+ const { history, config } = ctx;
275
+ const modelName = config.model.split('/').pop() || config.model;
276
+ const exchanges = buildExchanges(history);
277
+ if (exchanges.length === 0) {
278
+ ctx.onEvent({ kind: 'text_delta', text: 'No history in the current session yet.\n' });
279
+ emitDone(ctx);
280
+ return;
281
+ }
282
+ let output = `**Full Transcript** — ${exchanges.length} exchange${exchanges.length === 1 ? '' : 's'}, session \`${ctx.sessionId}\`\n\n`;
283
+ output += '─'.repeat(70) + '\n\n';
284
+ for (let i = 0; i < exchanges.length; i++) {
285
+ const ex = exchanges[i];
286
+ // Re-read full text from raw history (buildExchanges truncated).
287
+ const userMsg = history[ex.startIdx];
288
+ const fullUser = extractText(userMsg);
289
+ // Find first assistant text in this exchange (full, not truncated).
290
+ let fullAssistant = '';
291
+ for (let j = ex.startIdx + 1; j <= ex.endIdx; j++) {
292
+ const m = history[j];
293
+ if (m.role === 'assistant') {
294
+ const t = extractText(m);
295
+ if (t && !fullAssistant)
296
+ fullAssistant = t;
297
+ }
298
+ }
299
+ output += `❯ [${i + 1}] ${fullUser}\n\n`;
300
+ if (fullAssistant)
301
+ output += `${fullAssistant}\n`;
302
+ if (ex.toolNames.length > 0) {
303
+ output += `\n _tools: ${ex.toolNames.join(', ')}_\n`;
304
+ }
305
+ output += `\n[${modelName}]\n\n`;
306
+ output += '─'.repeat(70) + '\n\n';
307
+ }
308
+ output += `End of transcript — ${history.length} raw messages, ${exchanges.length} user/assistant exchanges.\n`;
262
309
  ctx.onEvent({ kind: 'text_delta', text: output });
263
310
  emitDone(ctx);
264
311
  },
@@ -36,7 +36,20 @@ async function execute(input, _ctx) {
36
36
  const searchUrl = `https://x.com/search?q=${encodeURIComponent(search_query)}&src=typed_query&f=live`;
37
37
  await browser.open(searchUrl);
38
38
  await browser.waitForTimeout(3500);
39
- const tree = await browser.snapshot();
39
+ // Same defensive guard as SearchX — Playwright can drop the page out
40
+ // from under us during the wait. Surface a useful hint instead of
41
+ // "Cannot read properties of undefined (reading 'snapshot')".
42
+ let tree;
43
+ try {
44
+ tree = await browser.snapshot();
45
+ }
46
+ catch (snapErr) {
47
+ const snapMsg = snapErr instanceof Error ? snapErr.message : String(snapErr);
48
+ return {
49
+ output: `PostToX: Page snapshot failed (${snapMsg.slice(0, 100)}). The browser session likely closed mid-flight — retry, or run \`franklin social setup\` to refresh.`,
50
+ isError: true,
51
+ };
52
+ }
40
53
  // ── Find the article matching the given pre_key ──────────────────
41
54
  const articles = extractArticleBlocks(tree);
42
55
  let matchedTimeRef = null;
@@ -122,7 +122,22 @@ async function execute(input, _ctx) {
122
122
  return { output: `SearchX: Failed to open X.com: ${msg.slice(0, 200)}`, isError: true };
123
123
  }
124
124
  await browser.waitForTimeout(4000);
125
- const tree = await browser.snapshot();
125
+ // Defensive: snapshot() has historically thrown "Cannot read properties
126
+ // of undefined (reading 'snapshot')" when Playwright's underlying page
127
+ // closes between waitForTimeout and the snapshot call (verified in
128
+ // failures.jsonl 2026-04-20). Convert the cryptic error into a useful
129
+ // hint instead of leaking it into the audit log unchanged.
130
+ let tree;
131
+ try {
132
+ tree = await browser.snapshot();
133
+ }
134
+ catch (snapErr) {
135
+ const snapMsg = snapErr instanceof Error ? snapErr.message : String(snapErr);
136
+ return {
137
+ output: `SearchX: Page snapshot failed (${snapMsg.slice(0, 100)}). The browser session likely closed mid-flight — retry, or run \`franklin social setup\` to refresh.`,
138
+ isError: true,
139
+ };
140
+ }
126
141
  // ── Diagnose page state ───────────────────────────────────────────
127
142
  const isLoginWall = tree.includes('Sign in') && tree.includes('Create account');
128
143
  const isRateLimit = tree.includes('Rate limit') || tree.includes('Something went wrong');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.16.2",
3
+ "version": "3.16.4",
4
4
  "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {