harlay 0.1.2 → 0.1.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.
@@ -186,6 +186,7 @@ kbd { margin-right: 6px; padding: 1px 5px; border-radius: 4px; border: 1px solid
186
186
  .md h3 { margin: 2px 0 8px; font: 500 16px/1.25 var(--serif); color: var(--fg); }
187
187
  .md h4 { margin: 14px 0 5px; font-size: 12.5px; font-weight: 600; color: var(--fg); }
188
188
  .md p { margin: 0 0 7px; }
189
+ .md p.muted { font-size: 12px; color: var(--fg3); }
189
190
  .md strong { color: var(--fg); font-weight: 600; }
190
191
  .md code { padding: 0 4px; border-radius: 4px; background: var(--sunk); border: 1px solid var(--line); font: 11px var(--mono); color: var(--fg); word-break: break-word; }
191
192
  .md pre { margin: 0 0 8px; padding: 8px 10px; border-radius: 8px; background: var(--sunk); border: 1px solid var(--line); font: 11px/1.45 var(--mono); color: var(--fg); white-space: pre-wrap; word-break: break-word; }
@@ -290,12 +290,73 @@ function markdown(src) {
290
290
  return out;
291
291
  }
292
292
 
293
+ /** Headings for the blocks of an inline handoff prompt (see packages/core/src/brief/prompt.ts). */
294
+ const BRIEF_BLOCKS = {
295
+ original_goal: 'Original goal',
296
+ rules_from_user: 'Rules from the user',
297
+ recent_user_messages: 'Recent messages (oldest first)',
298
+ where_it_stopped: 'Where it stopped',
299
+ files: 'Files',
300
+ last_failure: 'Last failure',
301
+ };
302
+
303
+ /** `key="value"` pairs of the opening `<handoff …>` line (the prompt never puts `"` inside a value). */
304
+ function tagAttrs(line) {
305
+ const out = {};
306
+ for (const m of line.matchAll(/([a-z_]+)="([^"]*)"/g)) out[m[1]] = m[2];
307
+ return out;
308
+ }
309
+
310
+ /**
311
+ * HANDOFF.md as Harlay writes it now: the tagged launch prompt. Block text is shown as written, never parsed as
312
+ * Markdown, so a line starting with ``` is plain text; the failure output is one verbatim block.
313
+ */
314
+ function handoffBrief(src) {
315
+ const lines = src.replace(/\r\n/g, '\n').trim().split('\n');
316
+ const out = [];
317
+ let block = null;
318
+ let list = null;
319
+ let pre = null;
320
+ const close = () => {
321
+ if (pre) out.push(h('pre', {}, pre.join('\n')));
322
+ block = list = pre = null;
323
+ };
324
+ for (const line of lines) {
325
+ const t = line.trim();
326
+ let m;
327
+ if (!block && /^<handoff\s/.test(t)) {
328
+ const a = tagAttrs(t);
329
+ out.push(h('h3', {}, `Handoff from ${a.from || 'another agent'}${a.task ? ` · ${a.task}` : ''}`));
330
+ } else if (!block && (m = /^<([a-z_]+)(?:\s[^>]*)?>$/.exec(t)) && Object.hasOwn(BRIEF_BLOCKS, m[1])) {
331
+ block = m[1];
332
+ out.push(h('h4', {}, BRIEF_BLOCKS[block]));
333
+ if (block === 'last_failure') pre = [];
334
+ } else if ((m = /^<\/([a-z_]+)>$/.exec(t)) && (m[1] === block || (!block && m[1] === 'handoff'))) {
335
+ close();
336
+ } else if (pre) {
337
+ pre.push(line);
338
+ } else if (t === '') {
339
+ list = null;
340
+ } else if (!block) {
341
+ out.push(h('p', { class: 'muted' }, t));
342
+ } else if (t.startsWith('- ')) {
343
+ if (!list) out.push((list = h('ul', {})));
344
+ list.append(h('li', {}, t.slice(2)));
345
+ } else {
346
+ list = null;
347
+ out.push(h('p', {}, t));
348
+ }
349
+ }
350
+ close();
351
+ return out;
352
+ }
353
+
293
354
  function renderBrief() {
294
355
  const session = model.sessions.find((s) => s.key === brief.key);
295
356
  let body;
296
357
  if (brief.loading) body = h('div', { class: 'busy pad' }, h('span', { class: 'spin' }), 'Building the brief…');
297
358
  else if (brief.error) body = h('p', { class: 'empty' }, `Couldn’t build the brief: ${brief.error}`);
298
- else body = h('div', { class: 'md' }, markdown(brief.text));
359
+ else body = h('div', { class: 'md' }, brief.text.trim().startsWith('<handoff ') ? handoffBrief(brief.text) : markdown(brief.text));
299
360
  const ready = !brief.loading && !brief.error;
300
361
  $('#brief').replaceChildren(
301
362
  h('div', { class: 'hero' }, h('h1', {}, 'Brief'), h('p', { class: 'one-line' }, brief.title)),
@@ -425,7 +486,42 @@ function demoApi() {
425
486
  ];
426
487
  let onModel = () => {};
427
488
  let onBrief = () => {};
428
- const sampleBrief = (title) => `---\nharlay: 1\n---\n\n# Continue: ${title}\n\n> You are continuing a coding task that another AI agent started.\n\n## 1. Goal (user's words)\n> Move auth from session cookies to JWT across \`api/\`.\n\n## 2. Next step\nMigrate the last 3 route handlers in \`api/routes/\`.\n\n## 4. Progress\n- [x] Added \`src/lib/jwt.ts\`\n- [ ] Migrate \`billing.ts\`\n\n\`\`\`text\nnpm test -- auth\n\`\`\`\n`;
489
+ const sampleBrief = (title) => [
490
+ `<handoff from="Claude Code" reason="usage_limit" task="${title.replace(/"/g, "'")}">`,
491
+ "You are taking over a coding session that Claude Code could not finish (it hit a usage limit). What you need to start is below; don't re-read the repo to rebuild it.",
492
+ '',
493
+ '<rules_from_user>',
494
+ "- Don't change the public /v1 API shape.",
495
+ '- Never push to main.',
496
+ '</rules_from_user>',
497
+ '',
498
+ '<recent_user_messages oldest_first="true">',
499
+ '- Move auth from session cookies to JWT across api/.',
500
+ '- Also migrate billing.ts before you run the tests.',
501
+ '</recent_user_messages>',
502
+ '',
503
+ '<where_it_stopped>',
504
+ '```bash npm test -- auth ``` still fails on the refresh test. Next I will migrate the last 3 route handlers in api/routes/.',
505
+ 'Open todos: [in progress] Migrate billing.ts; [pending] Delete the legacy session store',
506
+ '</where_it_stopped>',
507
+ '',
508
+ '<files>',
509
+ 'Touched: src/lib/jwt.ts, api/routes/auth.ts',
510
+ 'Uncommitted: src/lib/jwt.ts (added, +42 -0), api/routes/auth.ts (modified, +12 -30)',
511
+ '</files>',
512
+ '',
513
+ '<last_failure>',
514
+ '$ npm test -- auth (exit 1)',
515
+ 'FAIL auth.test.ts > refresh token rotates',
516
+ ' expected 401, received 200',
517
+ '</last_failure>',
518
+ '',
519
+ "Only if you need more: .harlay/handoffs/1005-1742-migrate-auth-to-jwt/transcript.md (search it, don't read it all) and .harlay/handoffs/1005-1742-migrate-auth-to-jwt/diff.patch.",
520
+ '</handoff>',
521
+ '',
522
+ 'Continue from the most recent user message. Start with the next concrete step.',
523
+ '',
524
+ ].join('\n');
429
525
  const emit = () => onModel({
430
526
  sessions: sessions.map((s) => ({ ...s, targets: targets(s.from) })),
431
527
  usage,