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.
- package/app/dist/main.cjs +485 -353
- package/app/renderer/popover.css +1 -0
- package/app/renderer/popover.js +98 -2
- package/dist/harlay.mjs +453 -314
- package/package.json +1 -1
package/app/renderer/popover.css
CHANGED
|
@@ -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; }
|
package/app/renderer/popover.js
CHANGED
|
@@ -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) =>
|
|
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,
|