@crouton-kit/humanloop 0.3.11 → 0.3.13
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/dist/cli.js +39 -16
- package/dist/render/termrender.d.ts +0 -2
- package/dist/render/termrender.js +2 -3
- package/dist/surfaces/display.js +2 -2
- package/dist/types.d.ts +2 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -558,19 +558,20 @@ function parseValidationErrors(e) {
|
|
|
558
558
|
const reviewCmd = program.command('review').description('Markdown document review with anchored comments.');
|
|
559
559
|
reviewCmd
|
|
560
560
|
.command('open')
|
|
561
|
-
.description('
|
|
561
|
+
.description('Open a read-only editor review and BLOCK until the human submits.\n' +
|
|
562
562
|
'\n' +
|
|
563
563
|
'stdin { file: string (required, .md), output?: string|null,\n' +
|
|
564
564
|
' editor?: string|null, tmux?: bool=true }\n' +
|
|
565
|
-
'stdout { job_id: string, output: string (absolute)
|
|
565
|
+
'stdout { job_id: string, output: string (absolute),\n' +
|
|
566
|
+
' status: "done"|"failed"|"canceled", result?: FeedbackResult }\n' +
|
|
566
567
|
'\n' +
|
|
567
|
-
'Effects: spawns nvim/vim read-only
|
|
568
|
-
'
|
|
568
|
+
'Effects: spawns nvim/vim read-only in a tmux pane when tmux=true and $TMUX\n' +
|
|
569
|
+
' set, then blocks until the human finishes and submits. Writes\n' +
|
|
569
570
|
' <dir>/review.vim, <dir>/feedback.json (on finish), <dir>/job.log.\n' +
|
|
570
571
|
' autosaves feedback JSON; the open pane live-reloads the source on\n' +
|
|
571
572
|
' disk edits. The review is open-ended (a human may take many\n' +
|
|
572
|
-
' minutes) —
|
|
573
|
-
'
|
|
573
|
+
' minutes) — if you want to keep working, run this BACKGROUNDED; your\n' +
|
|
574
|
+
' harness notifies you when it returns with the result.\n')
|
|
574
575
|
.helpOption('-h, --help', 'Show help')
|
|
575
576
|
.action(async () => {
|
|
576
577
|
const input = parseStdinJson();
|
|
@@ -613,12 +614,32 @@ reviewCmd
|
|
|
613
614
|
appendJobLog(jobDir, { level: 'error', event: 'job_failed', message: `tmux spawn failed: ${msg}` });
|
|
614
615
|
emitError({ error: 'internal', message: `tmux spawn failed: ${msg}`, next: 'Check that $TMUX is set. Or pass tmux:false.' });
|
|
615
616
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
617
|
+
// BLOCK until the human submits (or the job ends). The review is
|
|
618
|
+
// open-ended — a human may take many minutes — so callers that want to
|
|
619
|
+
// keep working should background this invocation; their harness notifies
|
|
620
|
+
// them when it returns. Poll the shared job dir for feedback.json the
|
|
621
|
+
// same way `hl job result --wait` does.
|
|
622
|
+
await new Promise((resolvePromise) => {
|
|
623
|
+
const poll = setInterval(() => {
|
|
624
|
+
const fp = join(jobDir, 'feedback.json');
|
|
625
|
+
if (existsSync(fp)) {
|
|
626
|
+
const result = tryParseJson(readFileSync(fp, 'utf8'));
|
|
627
|
+
if (result !== null) {
|
|
628
|
+
clearInterval(poll);
|
|
629
|
+
process.stdout.write(JSON.stringify({ job_id: jobId, output, status: 'done', result }) + '\n');
|
|
630
|
+
process.exit(0);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const state = detectJobState(jobDir);
|
|
634
|
+
if (state === 'failed' || state === 'canceled') {
|
|
635
|
+
clearInterval(poll);
|
|
636
|
+
process.stdout.write(JSON.stringify({ job_id: jobId, output, status: state }) + '\n');
|
|
637
|
+
process.exit(state === 'canceled' ? 0 : 1);
|
|
638
|
+
}
|
|
639
|
+
}, 200);
|
|
640
|
+
void resolvePromise;
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
622
643
|
}
|
|
623
644
|
// In-process path: the detached child (this pane is its TTY), or a degraded
|
|
624
645
|
// top-level call with no tmux. launchReview blocks until the editor exits.
|
|
@@ -634,7 +655,8 @@ reviewCmd
|
|
|
634
655
|
process.stdout.write(JSON.stringify({
|
|
635
656
|
job_id: jobId,
|
|
636
657
|
output,
|
|
637
|
-
|
|
658
|
+
status: 'done',
|
|
659
|
+
result,
|
|
638
660
|
...(input.dir ? {} : { _note: 'No tmux: launchReview blocked synchronously. Result is already available.' }),
|
|
639
661
|
}) + '\n');
|
|
640
662
|
process.exit(0);
|
|
@@ -657,7 +679,9 @@ viewCmd
|
|
|
657
679
|
.command('show')
|
|
658
680
|
.description('Render a file live in a tmux pane — passive, no result.\n' +
|
|
659
681
|
'\n' +
|
|
660
|
-
'
|
|
682
|
+
'The pane always watches the file and live-updates on every save.\n' +
|
|
683
|
+
'\n' +
|
|
684
|
+
'stdin { path: string (required), window?: "split"|"new"="split" }\n' +
|
|
661
685
|
'stdout { pane_id: string|null, reason: string|null }\n' +
|
|
662
686
|
'exit 0 always (not-in-tmux / renderer-unavailable is NOT an error)\n')
|
|
663
687
|
.helpOption('-h, --help', 'Show help')
|
|
@@ -667,9 +691,8 @@ viewCmd
|
|
|
667
691
|
emitError({ error: 'bad_input', message: 'path is required', field: 'path', next: 'Provide: {"path": "/abs/path/file.md"}' });
|
|
668
692
|
}
|
|
669
693
|
const absPath = resolve(input.path);
|
|
670
|
-
const watch = input.watch !== false;
|
|
671
694
|
const window = input.window === 'new' ? 'new' : 'split';
|
|
672
|
-
const res = display(absPath, {
|
|
695
|
+
const res = display(absPath, { window });
|
|
673
696
|
if (res.paneId) {
|
|
674
697
|
process.stdout.write(JSON.stringify({ pane_id: res.paneId, reason: null }) + '\n');
|
|
675
698
|
}
|
|
@@ -21,8 +21,6 @@ export declare function checkMarkdown(md: string): {
|
|
|
21
21
|
error: string;
|
|
22
22
|
};
|
|
23
23
|
export interface DisplayInPaneOpts {
|
|
24
|
-
/** Pass watch so the pane live-updates on file edits. Default true. */
|
|
25
|
-
watch?: boolean;
|
|
26
24
|
/** Open in a new tmux window instead of splitting the current one. */
|
|
27
25
|
newWindow?: boolean;
|
|
28
26
|
}
|
|
@@ -263,9 +263,8 @@ export function displayInPane(path, opts = {}) {
|
|
|
263
263
|
ensureRenderer();
|
|
264
264
|
if (rendererState !== 'ready')
|
|
265
265
|
return {};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
args.push('--watch');
|
|
266
|
+
// Always watch: a displayed pane is a live view of the file by definition.
|
|
267
|
+
const args = ['pane', 'open', path, '--watch'];
|
|
269
268
|
args.push('--window', opts.newWindow ? 'new' : 'split');
|
|
270
269
|
const result = spawnSync(VENV_BIN, args, {
|
|
271
270
|
encoding: 'utf-8',
|
package/dist/surfaces/display.js
CHANGED
|
@@ -10,10 +10,10 @@ export function countPanesInCurrentWindow() {
|
|
|
10
10
|
return result.stdout.split('\n').filter((line) => line.trim() !== '').length;
|
|
11
11
|
}
|
|
12
12
|
export function display(path, opts) {
|
|
13
|
-
const watch = opts?.watch !== false;
|
|
14
13
|
const window = (opts?.window === 'split' || opts?.window === 'new') ? opts.window : 'auto';
|
|
15
14
|
const maxPanes = (opts?.maxPanes !== undefined && opts.maxPanes > 0) ? opts.maxPanes : 3;
|
|
16
15
|
const newWindow = window === 'new' ||
|
|
17
16
|
(window === 'auto' && countPanesInCurrentWindow() >= maxPanes);
|
|
18
|
-
|
|
17
|
+
// The pane always watches the file — displayed docs are live by definition.
|
|
18
|
+
return displayInPane(path, { newWindow });
|
|
19
19
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -146,10 +146,9 @@ export interface InboxItem {
|
|
|
146
146
|
blockedSince: string;
|
|
147
147
|
source?: DeckSource;
|
|
148
148
|
}
|
|
149
|
-
/** Options for `display()` — the live-watch tmux pane surface.
|
|
149
|
+
/** Options for `display()` — the live-watch tmux pane surface. The pane always
|
|
150
|
+
* watches the file and live-updates on edits; there is no non-watched mode. */
|
|
150
151
|
export interface DisplayOpts {
|
|
151
|
-
/** Pass `--watch` so the pane live-updates on edits. Default true. */
|
|
152
|
-
watch?: boolean;
|
|
153
152
|
/** `'auto'` (default) splits until the pane budget, then opens a new window. */
|
|
154
153
|
window?: 'auto' | 'split' | 'new';
|
|
155
154
|
/** Pane budget per window before `'auto'` opens a new window. Default 3. */
|