@oh-my-pi/pi-coding-agent 14.9.1 → 14.9.3

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +3 -3
  4. package/src/config/prompt-templates.ts +0 -5
  5. package/src/config/settings-schema.ts +38 -0
  6. package/src/eval/eval.lark +10 -31
  7. package/src/eval/index.ts +1 -0
  8. package/src/eval/parse.ts +156 -255
  9. package/src/eval/sniff.ts +28 -0
  10. package/src/export/html/template.css +38 -0
  11. package/src/export/html/template.generated.ts +1 -1
  12. package/src/export/html/template.js +209 -15
  13. package/src/extensibility/extensions/runner.ts +173 -177
  14. package/src/hashline/apply.ts +8 -24
  15. package/src/hashline/constants.ts +20 -0
  16. package/src/hashline/execute.ts +0 -1
  17. package/src/hashline/grammar.lark +16 -27
  18. package/src/hashline/hash.ts +4 -34
  19. package/src/hashline/input.ts +16 -2
  20. package/src/hashline/parser.ts +12 -40
  21. package/src/hashline/types.ts +1 -2
  22. package/src/internal-urls/agent-protocol.ts +1 -0
  23. package/src/internal-urls/artifact-protocol.ts +1 -0
  24. package/src/internal-urls/docs-index.generated.ts +2 -1
  25. package/src/internal-urls/jobs-protocol.ts +1 -0
  26. package/src/internal-urls/local-protocol.ts +1 -0
  27. package/src/internal-urls/mcp-protocol.ts +1 -0
  28. package/src/internal-urls/memory-protocol.ts +1 -0
  29. package/src/internal-urls/pi-protocol.ts +1 -0
  30. package/src/internal-urls/router.ts +2 -1
  31. package/src/internal-urls/rule-protocol.ts +1 -0
  32. package/src/internal-urls/skill-protocol.ts +1 -0
  33. package/src/internal-urls/types.ts +18 -2
  34. package/src/mcp/transports/http.ts +49 -47
  35. package/src/prompts/system/custom-system-prompt.md +0 -2
  36. package/src/prompts/system/now-prompt.md +7 -0
  37. package/src/prompts/system/project-prompt.md +2 -0
  38. package/src/prompts/system/subagent-system-prompt.md +18 -9
  39. package/src/prompts/system/subagent-user-prompt.md +1 -10
  40. package/src/prompts/system/system-prompt.md +154 -233
  41. package/src/prompts/tools/bash.md +0 -24
  42. package/src/prompts/tools/eval.md +26 -13
  43. package/src/prompts/tools/hashline.md +1 -4
  44. package/src/sdk.ts +12 -22
  45. package/src/session/agent-session.ts +49 -17
  46. package/src/system-prompt.ts +38 -104
  47. package/src/task/executor.ts +15 -9
  48. package/src/task/index.ts +38 -33
  49. package/src/task/render.ts +4 -2
  50. package/src/tools/bash.ts +15 -41
  51. package/src/tools/eval.ts +13 -36
  52. package/src/tools/index.ts +0 -3
  53. package/src/tools/path-utils.ts +21 -1
  54. package/src/tools/read.ts +71 -49
  55. package/src/tools/search.ts +13 -1
  56. package/src/utils/file-display-mode.ts +11 -5
  57. package/src/workspace-tree.ts +210 -410
  58. package/src/task/template.ts +0 -47
  59. package/src/tools/bash-normalize.ts +0 -107
@@ -839,9 +839,11 @@
839
839
 
840
840
  function renderEdit(name, args, result, ctx) {
841
841
  const filePath = str(args.file_path == null ? args.path : args.file_path);
842
- const pathHtml = filePath === null ? invalidArgHtml() : escapeHtml(shortenPath(filePath || ''));
842
+ const pathHtml = filePath ? escapeHtml(shortenPath(filePath)) : '';
843
843
  let html = toolHead('edit', pathHtml);
844
- if (Array.isArray(args.edits)) {
844
+ if (typeof args.input === 'string' && args.input.length) {
845
+ html += codeBlock(args.input, null);
846
+ } else if (Array.isArray(args.edits)) {
845
847
  html += '<div class="tool-args">';
846
848
  for (const e of args.edits) {
847
849
  const op = e && typeof e.op === 'string' ? e.op : '?';
@@ -867,7 +869,8 @@
867
869
 
868
870
  function renderAstEdit(name, args, result, ctx) {
869
871
  const lang = args.lang || null;
870
- const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : '';
872
+ const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (args.path ? shortenPath(String(args.path)) : '');
873
+ const pathHtml = paths ? escapeHtml(paths) : '';
871
874
  const badges = [];
872
875
  if (lang) badges.push(lang);
873
876
  if (args.glob) badges.push('glob=' + args.glob);
@@ -931,10 +934,12 @@
931
934
  }
932
935
 
933
936
  function renderFind(name, args, result, ctx) {
934
- const pattern = str(args.pattern);
935
- const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
936
- const badges = args.limit ? ['limit=' + args.limit] : null;
937
- let html = toolHead('find', '<span class="tool-pattern">' + patHtml + '</span>', badges);
937
+ const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (str(args.pattern) || '.');
938
+ const patHtml = paths ? escapeHtml(paths) : invalidArgHtml();
939
+ const badges = [];
940
+ if (args.limit) badges.push('limit=' + args.limit);
941
+ if (args.hidden === false) badges.push('no-hidden');
942
+ let html = toolHead('find', '<span class="tool-pattern">' + patHtml + '</span>', badges.length ? badges : null);
938
943
  if (result) {
939
944
  const output = ctx.getResultText();
940
945
  if (output) html += formatExpandableOutput(output, 10);
@@ -1168,14 +1173,18 @@
1168
1173
  }
1169
1174
 
1170
1175
  function renderGh(name, args, result, ctx) {
1176
+ const op = str(args.op);
1171
1177
  const badges = [];
1178
+ if (op) badges.push(op);
1172
1179
  if (args.repo) badges.push(String(args.repo));
1173
1180
  if (args.issue) badges.push('#' + args.issue);
1174
- if (args.pr) badges.push('PR ' + args.pr);
1181
+ if (args.pr) badges.push(Array.isArray(args.pr) ? 'PRs ' + args.pr.join(',') : 'PR ' + args.pr);
1175
1182
  if (args.branch) badges.push('branch=' + args.branch);
1176
- if (args.query) badges.push('query=' + args.query);
1183
+ if (args.query) badges.push('query=' + truncate(String(args.query), 60));
1177
1184
  if (args.run) badges.push('run=' + args.run);
1185
+ if (args.title) badges.push('title=' + truncate(String(args.title), 40));
1178
1186
  let html = toolHead(name, '', badges);
1187
+ if (args.body) html += '<div class="tool-output"><div>' + escapeHtml(truncate(String(args.body), 400)) + '</div></div>';
1179
1188
  if (result) {
1180
1189
  const output = ctx.getResultText();
1181
1190
  if (output) html += formatExpandableOutput(output, 12, 'markdown');
@@ -1249,6 +1258,178 @@
1249
1258
  return html;
1250
1259
  }
1251
1260
 
1261
+ // Parse `*** Begin <LANG>` cell headers (canonical) and the legacy
1262
+ // `===== <info> =====` headers used by older transcripts. Cells emitted
1263
+ // before the format cutover still need to render in HTML exports.
1264
+ function parseEvalCells(input) {
1265
+ const text = String(input);
1266
+ if (/^[*]{2,}\s*Begin\b/im.test(text)) return parseEvalCellsNew(text);
1267
+ return parseEvalCellsLegacy(text);
1268
+ }
1269
+
1270
+ function evalLangAlias(token) {
1271
+ const t = String(token || '').toUpperCase();
1272
+ if (t === 'PY' || t === 'PYTHON' || t === 'IPY' || t === 'IPYTHON') return 'py';
1273
+ if (t === 'JS' || t === 'JAVASCRIPT') return 'js';
1274
+ if (t === 'TS' || t === 'TYPESCRIPT') return 'ts';
1275
+ return null;
1276
+ }
1277
+
1278
+ function parseEvalCellsNew(text) {
1279
+ const STARS = '\\*{2,}';
1280
+ const BEGIN = new RegExp('^' + STARS + '\\s*Begin\\b\\s*(\\S+)?\\s*$', 'i');
1281
+ const END = new RegExp('^' + STARS + '\\s*End\\b.*$', 'i');
1282
+ const TITLE = new RegExp('^' + STARS + '\\s*Title\\s*:\\s*(.+?)\\s*$', 'i');
1283
+ const TIMEOUT = new RegExp('^' + STARS + '\\s*Timeout\\s*:\\s*(\\S+)\\s*$', 'i');
1284
+ const RESET = new RegExp('^' + STARS + '\\s*Reset\\s*$', 'i');
1285
+ const lines = text.split('\n');
1286
+ if (lines.length && lines[lines.length - 1] === '') lines.pop();
1287
+ const cells = [];
1288
+ let i = 0;
1289
+ while (i < lines.length && lines[i].trim() === '') i++;
1290
+ while (i < lines.length) {
1291
+ const beginMatch = BEGIN.exec(lines[i]);
1292
+ if (!beginMatch) { i++; continue; }
1293
+ const lang = evalLangAlias(beginMatch[1]) || 'py';
1294
+ i++;
1295
+ let title = '';
1296
+ const attrs = [];
1297
+ while (i < lines.length) {
1298
+ const tm = TITLE.exec(lines[i]);
1299
+ if (tm) { if (!title) title = tm[1]; i++; continue; }
1300
+ const to = TIMEOUT.exec(lines[i]);
1301
+ if (to) { attrs.push('t=' + to[1]); i++; continue; }
1302
+ if (RESET.test(lines[i])) { attrs.push('rst'); i++; continue; }
1303
+ break;
1304
+ }
1305
+ const codeLines = [];
1306
+ while (i < lines.length) {
1307
+ if (END.test(lines[i])) { i++; break; }
1308
+ if (BEGIN.test(lines[i])) break;
1309
+ codeLines.push(lines[i]);
1310
+ i++;
1311
+ }
1312
+ while (codeLines.length && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
1313
+ cells.push({ lang, title, attrs, code: codeLines.join('\n') });
1314
+ while (i < lines.length && lines[i].trim() === '') i++;
1315
+ }
1316
+ return cells;
1317
+ }
1318
+
1319
+ function parseEvalCellsLegacy(input) {
1320
+ const HEADER = /^={5,}\s*(.*?)\s*={5,}\s*$/;
1321
+ const lines = String(input).split('\n');
1322
+ const cells = [];
1323
+ let inheritedLang = 'py';
1324
+ let current = null;
1325
+ for (const line of lines) {
1326
+ const m = line.match(HEADER);
1327
+ if (m) {
1328
+ if (current) cells.push(current);
1329
+ const info = m[1] || '';
1330
+ let lang = inheritedLang;
1331
+ let title = '';
1332
+ const langMatch = info.match(/^(py|js|ts)(?::"([^"]*)")?/);
1333
+ if (langMatch) {
1334
+ lang = langMatch[1];
1335
+ if (langMatch[2]) title = langMatch[2];
1336
+ }
1337
+ if (!title) {
1338
+ const idMatch = info.match(/id:"([^"]*)"/);
1339
+ if (idMatch) title = idMatch[1];
1340
+ }
1341
+ inheritedLang = lang;
1342
+ const attrs = [];
1343
+ const tMatch = info.match(/(?:^|\s)t:(\S+)/);
1344
+ if (tMatch) attrs.push('t=' + tMatch[1]);
1345
+ if (/(?:^|\s)rst(?:\s|$)/.test(info)) attrs.push('rst');
1346
+ current = { lang, title, attrs, code: '' };
1347
+ } else {
1348
+ if (!current) current = { lang: inheritedLang, title: '', attrs: [], code: '' };
1349
+ current.code += (current.code ? '\n' : '') + line;
1350
+ }
1351
+ }
1352
+ if (current) cells.push(current);
1353
+ return cells.map(c => ({ ...c, code: c.code.replace(/\s+$/, '') }));
1354
+ }
1355
+
1356
+ function evalLangToHljs(lang) {
1357
+ return lang === 'py' ? 'python' : lang === 'js' ? 'javascript' : lang === 'ts' ? 'typescript' : null;
1358
+ }
1359
+
1360
+ function renderEval(name, args, result, ctx) {
1361
+ let html = toolHead('eval');
1362
+ if (typeof args.input !== 'string') {
1363
+ html += '<div class="tool-error">[missing input]</div>';
1364
+ } else {
1365
+ const cells = parseEvalCells(args.input);
1366
+ if (cells.length === 0) {
1367
+ html += codeBlock(args.input, 'python');
1368
+ } else {
1369
+ for (const cell of cells) {
1370
+ html += '<div class="tool-cell">';
1371
+ const titleParts = [];
1372
+ if (cell.title) titleParts.push(cell.title);
1373
+ titleParts.push(cell.lang);
1374
+ if (cell.attrs && cell.attrs.length) titleParts.push(...cell.attrs);
1375
+ html += '<div class="tool-cell-title">' + escapeHtml(titleParts.join(' · ')) + '</div>';
1376
+ html += codeBlock(cell.code, evalLangToHljs(cell.lang));
1377
+ html += '</div>';
1378
+ }
1379
+ }
1380
+ }
1381
+ if (result) {
1382
+ html += ctx.renderResultImages();
1383
+ const output = ctx.getResultText();
1384
+ if (output) html += formatExpandableOutput(output, 12);
1385
+ }
1386
+ return html;
1387
+ }
1388
+
1389
+ function renderSearch(name, args, result, ctx) {
1390
+ const pattern = str(args.pattern);
1391
+ const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (args.path ? shortenPath(String(args.path)) : '.');
1392
+ const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
1393
+ let head = '<span class="tool-name">search</span> <span class="tool-pattern">/' + patHtml + '/</span>';
1394
+ head += ' <span class="tool-arg-key">in</span> <span class="tool-path">' + escapeHtml(paths) + '</span>';
1395
+ const badges = [];
1396
+ if (args.i) badges.push('i');
1397
+ if (args.skip) badges.push('skip=' + args.skip);
1398
+ if (args.gitignore === false) badges.push('no-gitignore');
1399
+ for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(b) + '</span>';
1400
+ let html = '<div class="tool-header">' + head + '</div>';
1401
+ if (result) {
1402
+ const output = ctx.getResultText();
1403
+ if (output) html += formatExpandableOutput(output, 12);
1404
+ }
1405
+ return html;
1406
+ }
1407
+
1408
+ function renderRecipe(name, args, result, ctx) {
1409
+ const op = str(args.op) || '?';
1410
+ let html = toolHead('recipe', '<span class="tool-arg-val">' + escapeHtml(op) + '</span>');
1411
+ if (result) {
1412
+ html += ctx.renderResultImages();
1413
+ const output = ctx.getResultText();
1414
+ if (output) html += formatExpandableOutput(output, 10);
1415
+ }
1416
+ return html;
1417
+ }
1418
+
1419
+ function renderIrc(name, args, result, ctx) {
1420
+ const op = str(args.op) || '?';
1421
+ const badges = [op];
1422
+ if (args.to) badges.push('to=' + args.to);
1423
+ if (args.awaitReply === false) badges.push('no-reply');
1424
+ let html = toolHead('irc', '', badges);
1425
+ if (args.message) html += '<div class="tool-output"><div>' + escapeHtml(String(args.message)) + '</div></div>';
1426
+ if (result) {
1427
+ const output = ctx.getResultText();
1428
+ if (output) html += formatExpandableOutput(output, 8);
1429
+ }
1430
+ return html;
1431
+ }
1432
+
1252
1433
 
1253
1434
  function renderGenericTool(name, args, result, ctx) {
1254
1435
  let html = toolHead(name);
@@ -1266,6 +1447,7 @@
1266
1447
 
1267
1448
  const TOOL_RENDERERS = {
1268
1449
  bash: renderBash,
1450
+ eval: renderEval,
1269
1451
  js: renderJsLike,
1270
1452
  python: renderJsLike,
1271
1453
  notebook: renderJsLike,
@@ -1275,6 +1457,7 @@
1275
1457
  ast_edit: renderAstEdit,
1276
1458
  ast_grep: renderAstGrep,
1277
1459
  grep: renderGrep,
1460
+ search: renderSearch,
1278
1461
  find: renderFind,
1279
1462
  lsp: renderLsp,
1280
1463
  todo_write: renderTodoWrite,
@@ -1300,16 +1483,25 @@
1300
1483
  poll: renderJob,
1301
1484
  cancel_job: renderJob,
1302
1485
  job: renderJob,
1486
+ recipe: renderRecipe,
1487
+ irc: renderIrc,
1303
1488
  };
1304
1489
 
1305
1490
  function renderToolCall(call) {
1306
1491
  const result = findToolResult(call.id);
1307
1492
  const isError = result?.isError || false;
1308
1493
  const statusClass = result ? (isError ? 'error' : 'success') : 'pending';
1309
- const args = call.arguments || {};
1494
+ const rawArgs = call.arguments || {};
1495
+ const intent = typeof rawArgs._i === 'string' ? rawArgs._i.trim() : '';
1496
+ // Strip internal _i intent so renderers don't dump it as JSON.
1497
+ const args = {};
1498
+ for (const k of Object.keys(rawArgs)) {
1499
+ if (k !== '_i') args[k] = rawArgs[k];
1500
+ }
1310
1501
  const name = call.name;
1311
1502
 
1312
1503
  const ctx = {
1504
+ intent,
1313
1505
  getResultText: () => {
1314
1506
  if (!result) return '';
1315
1507
  const textBlocks = result.content.filter(c => c.type === 'text');
@@ -1331,6 +1523,7 @@
1331
1523
 
1332
1524
  const renderer = TOOL_RENDERERS[name] || renderGenericTool;
1333
1525
  let html = '<div class="tool-execution ' + statusClass + '">';
1526
+ if (intent) html += '<div class="tool-intent">' + escapeHtml(intent) + '</div>';
1334
1527
  try {
1335
1528
  html += renderer(name, args, result, ctx);
1336
1529
  } catch (err) {
@@ -1638,11 +1831,12 @@
1638
1831
  }
1639
1832
 
1640
1833
  if (tools && tools.length > 0) {
1641
- html += `<div class="tools-list">
1642
- <div class="tools-header">Available Tools</div>
1643
- <div class="tools-content">
1644
- ${tools.map(t => `<div class="tool-item"><span class="tool-item-name">${escapeHtml(t.name)}</span> - <span class="tool-item-desc">${escapeHtml(t.description)}</span></div>`).join('')}
1645
- </div>
1834
+ const namesHtml = tools.map(t => `<span class="tool-name-chip">${escapeHtml(t.name)}</span>`).join('');
1835
+ const fullHtml = tools.map(t => `<div class="tool-item"><span class="tool-item-name">${escapeHtml(t.name)}</span> - <span class="tool-item-desc">${escapeHtml(t.description)}</span></div>`).join('');
1836
+ html += `<div class="tools-list collapsed" onclick="this.classList.toggle('collapsed')">
1837
+ <div class="tools-header">Available Tools (${tools.length})</div>
1838
+ <div class="tools-collapsed">${namesHtml}</div>
1839
+ <div class="tools-content">${fullHtml}</div>
1646
1840
  </div>`;
1647
1841
  }
1648
1842