@crouton-kit/humanloop 0.3.12 → 0.3.14
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/api.js +2 -1
- package/dist/cli.js +37 -14
- package/dist/inbox/convention.d.ts +10 -1
- package/dist/inbox/convention.js +17 -0
- package/dist/inbox/deck-schema.d.ts +1 -0
- package/dist/inbox/deck-schema.js +1 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { resolveInteractionDir } from './tui/app.js';
|
|
5
5
|
import { scanInbox } from './inbox/scan.js';
|
|
6
6
|
import { pickFromInbox } from './inbox/tui.js';
|
|
7
|
-
import { deckPath, atomicWriteJson, readJson } from './inbox/convention.js';
|
|
7
|
+
import { deckPath, atomicWriteJson, readJson, stampCanvasNode } from './inbox/convention.js';
|
|
8
8
|
import { getTerminalSize } from './tui/terminal.js';
|
|
9
9
|
import { approveDeck, notifyDeck } from './inbox/deck-factories.js';
|
|
10
10
|
const RESPONSE_SCHEMA_ID = 'humanloop.response/v2';
|
|
@@ -61,6 +61,7 @@ function buildSummary(deck, responses) {
|
|
|
61
61
|
export async function ask(deck, opts = {}) {
|
|
62
62
|
const dir = opts.dir ?? managedDir();
|
|
63
63
|
mkdirSync(dir, { recursive: true });
|
|
64
|
+
stampCanvasNode(deck);
|
|
64
65
|
atomicWriteJson(deckPath(dir), deck);
|
|
65
66
|
const { responses, completedAt, responsePath, deck: answeredDeck } = await resolveInteractionDir(dir, deck, {
|
|
66
67
|
sessionId: opts.sessionId,
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ask, inbox } from './api.js';
|
|
|
11
11
|
import { display } from './surfaces/display.js';
|
|
12
12
|
import { renderMarkdown, checkMarkdown } from './render/termrender.js';
|
|
13
13
|
import { scanInbox } from './inbox/scan.js';
|
|
14
|
-
import { deckPath, atomicWriteJson, readJson, responsePath, } from './inbox/convention.js';
|
|
14
|
+
import { deckPath, atomicWriteJson, readJson, responsePath, stampCanvasNode, } from './inbox/convention.js';
|
|
15
15
|
// ── Version ───────────────────────────────────────────────────────────────────
|
|
16
16
|
const HL_VERSION = '0.2.1';
|
|
17
17
|
function emitError(err, exitCode = 1) {
|
|
@@ -372,6 +372,7 @@ deckCmd
|
|
|
372
372
|
}
|
|
373
373
|
const dir = input.dir ? resolve(input.dir) : mkdtempSync(join(tmpdir(), 'hl-ix-'));
|
|
374
374
|
mkdirSync(dir, { recursive: true });
|
|
375
|
+
stampCanvasNode(deck);
|
|
375
376
|
atomicWriteJson(deckPath(dir), deck);
|
|
376
377
|
const jobId = basename(dir);
|
|
377
378
|
const visuals = input.visuals !== false;
|
|
@@ -558,19 +559,20 @@ function parseValidationErrors(e) {
|
|
|
558
559
|
const reviewCmd = program.command('review').description('Markdown document review with anchored comments.');
|
|
559
560
|
reviewCmd
|
|
560
561
|
.command('open')
|
|
561
|
-
.description('
|
|
562
|
+
.description('Open a read-only editor review and BLOCK until the human submits.\n' +
|
|
562
563
|
'\n' +
|
|
563
564
|
'stdin { file: string (required, .md), output?: string|null,\n' +
|
|
564
565
|
' editor?: string|null, tmux?: bool=true }\n' +
|
|
565
|
-
'stdout { job_id: string, output: string (absolute)
|
|
566
|
+
'stdout { job_id: string, output: string (absolute),\n' +
|
|
567
|
+
' status: "done"|"failed"|"canceled", result?: FeedbackResult }\n' +
|
|
566
568
|
'\n' +
|
|
567
|
-
'Effects: spawns nvim/vim read-only
|
|
568
|
-
'
|
|
569
|
+
'Effects: spawns nvim/vim read-only in a tmux pane when tmux=true and $TMUX\n' +
|
|
570
|
+
' set, then blocks until the human finishes and submits. Writes\n' +
|
|
569
571
|
' <dir>/review.vim, <dir>/feedback.json (on finish), <dir>/job.log.\n' +
|
|
570
572
|
' autosaves feedback JSON; the open pane live-reloads the source on\n' +
|
|
571
573
|
' disk edits. The review is open-ended (a human may take many\n' +
|
|
572
|
-
' minutes) —
|
|
573
|
-
'
|
|
574
|
+
' minutes) — if you want to keep working, run this BACKGROUNDED; your\n' +
|
|
575
|
+
' harness notifies you when it returns with the result.\n')
|
|
574
576
|
.helpOption('-h, --help', 'Show help')
|
|
575
577
|
.action(async () => {
|
|
576
578
|
const input = parseStdinJson();
|
|
@@ -613,12 +615,32 @@ reviewCmd
|
|
|
613
615
|
appendJobLog(jobDir, { level: 'error', event: 'job_failed', message: `tmux spawn failed: ${msg}` });
|
|
614
616
|
emitError({ error: 'internal', message: `tmux spawn failed: ${msg}`, next: 'Check that $TMUX is set. Or pass tmux:false.' });
|
|
615
617
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
618
|
+
// BLOCK until the human submits (or the job ends). The review is
|
|
619
|
+
// open-ended — a human may take many minutes — so callers that want to
|
|
620
|
+
// keep working should background this invocation; their harness notifies
|
|
621
|
+
// them when it returns. Poll the shared job dir for feedback.json the
|
|
622
|
+
// same way `hl job result --wait` does.
|
|
623
|
+
await new Promise((resolvePromise) => {
|
|
624
|
+
const poll = setInterval(() => {
|
|
625
|
+
const fp = join(jobDir, 'feedback.json');
|
|
626
|
+
if (existsSync(fp)) {
|
|
627
|
+
const result = tryParseJson(readFileSync(fp, 'utf8'));
|
|
628
|
+
if (result !== null) {
|
|
629
|
+
clearInterval(poll);
|
|
630
|
+
process.stdout.write(JSON.stringify({ job_id: jobId, output, status: 'done', result }) + '\n');
|
|
631
|
+
process.exit(0);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const state = detectJobState(jobDir);
|
|
635
|
+
if (state === 'failed' || state === 'canceled') {
|
|
636
|
+
clearInterval(poll);
|
|
637
|
+
process.stdout.write(JSON.stringify({ job_id: jobId, output, status: state }) + '\n');
|
|
638
|
+
process.exit(state === 'canceled' ? 0 : 1);
|
|
639
|
+
}
|
|
640
|
+
}, 200);
|
|
641
|
+
void resolvePromise;
|
|
642
|
+
});
|
|
643
|
+
return;
|
|
622
644
|
}
|
|
623
645
|
// In-process path: the detached child (this pane is its TTY), or a degraded
|
|
624
646
|
// top-level call with no tmux. launchReview blocks until the editor exits.
|
|
@@ -634,7 +656,8 @@ reviewCmd
|
|
|
634
656
|
process.stdout.write(JSON.stringify({
|
|
635
657
|
job_id: jobId,
|
|
636
658
|
output,
|
|
637
|
-
|
|
659
|
+
status: 'done',
|
|
660
|
+
result,
|
|
638
661
|
...(input.dir ? {} : { _note: 'No tmux: launchReview blocked synchronously. Result is already available.' }),
|
|
639
662
|
}) + '\n');
|
|
640
663
|
process.exit(0);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InteractionResponse } from '../types.js';
|
|
1
|
+
import type { Deck, InteractionResponse } from '../types.js';
|
|
2
2
|
export declare function deckPath(dir: string): string;
|
|
3
3
|
export declare function responsePath(dir: string): string;
|
|
4
4
|
export declare function progressPath(dir: string): string;
|
|
@@ -10,6 +10,15 @@ export declare function interactionState(dir: string): InteractionState;
|
|
|
10
10
|
export declare function isResolved(dir: string): boolean;
|
|
11
11
|
/** Returns true if a live resolver owns this dir (progress.json mtime < 300s). */
|
|
12
12
|
export declare function isClaimed(dir: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Stamp the originating canvas node id onto a deck's `source` so per-node
|
|
15
|
+
* attention scoping (crouter's nav chrome) can attribute the ask to the node
|
|
16
|
+
* that raised it rather than every sibling node sharing the same cwd.
|
|
17
|
+
*
|
|
18
|
+
* No-op when not inside a canvas node (CRTR_NODE_ID unset) or when the deck
|
|
19
|
+
* already carries a nodeId. Mutates `deck` in place.
|
|
20
|
+
*/
|
|
21
|
+
export declare function stampCanvasNode(deck: Deck): void;
|
|
13
22
|
export declare function atomicWriteJson(path: string, value: unknown): void;
|
|
14
23
|
export declare function readJson<T>(path: string): T | null;
|
|
15
24
|
export declare function writeResponse(dir: string, responses: InteractionResponse[], completedAt: string): string;
|
package/dist/inbox/convention.js
CHANGED
|
@@ -47,6 +47,23 @@ export function isClaimed(dir) {
|
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
// ── Canvas-node attribution ─────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Stamp the originating canvas node id onto a deck's `source` so per-node
|
|
53
|
+
* attention scoping (crouter's nav chrome) can attribute the ask to the node
|
|
54
|
+
* that raised it rather than every sibling node sharing the same cwd.
|
|
55
|
+
*
|
|
56
|
+
* No-op when not inside a canvas node (CRTR_NODE_ID unset) or when the deck
|
|
57
|
+
* already carries a nodeId. Mutates `deck` in place.
|
|
58
|
+
*/
|
|
59
|
+
export function stampCanvasNode(deck) {
|
|
60
|
+
const id = process.env['CRTR_NODE_ID'];
|
|
61
|
+
if (id === undefined || id.trim() === '')
|
|
62
|
+
return;
|
|
63
|
+
if (deck.source?.nodeId != null && deck.source.nodeId !== '')
|
|
64
|
+
return;
|
|
65
|
+
deck.source = { ...(deck.source ?? {}), nodeId: id };
|
|
66
|
+
}
|
|
50
67
|
// ── Atomic I/O ────────────────────────────────────────────────────────────────
|
|
51
68
|
export function atomicWriteJson(path, value) {
|
|
52
69
|
const payload = JSON.stringify(value, null, 2);
|
|
@@ -18,6 +18,7 @@ export declare const deckSchema: z.ZodObject<{
|
|
|
18
18
|
sessionName: z.ZodOptional<z.ZodString>;
|
|
19
19
|
askedBy: z.ZodOptional<z.ZodString>;
|
|
20
20
|
blockedSince: z.ZodOptional<z.ZodString>;
|
|
21
|
+
nodeId: z.ZodOptional<z.ZodString>;
|
|
21
22
|
}, z.core.$strip>>;
|
|
22
23
|
interactions: z.ZodArray<z.ZodObject<{
|
|
23
24
|
id: z.ZodString;
|
|
@@ -33,6 +33,7 @@ const deckSourceSchema = z.object({
|
|
|
33
33
|
sessionName: z.string().optional(),
|
|
34
34
|
askedBy: z.string().optional(),
|
|
35
35
|
blockedSince: z.string().optional(),
|
|
36
|
+
nodeId: z.string().optional(),
|
|
36
37
|
});
|
|
37
38
|
export const deckSchema = z.object({
|
|
38
39
|
title: z.string().optional(),
|
package/dist/types.d.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface DeckSource {
|
|
|
54
54
|
sessionName?: string;
|
|
55
55
|
askedBy?: string;
|
|
56
56
|
blockedSince?: string;
|
|
57
|
+
/** Originating canvas node id (CRTR_NODE_ID) when the ask was raised inside
|
|
58
|
+
* a crouter canvas node. Lets per-node attention scoping attribute the ask
|
|
59
|
+
* to the node that raised it rather than every sibling sharing the cwd. */
|
|
60
|
+
nodeId?: string;
|
|
57
61
|
}
|
|
58
62
|
export interface Deck {
|
|
59
63
|
title?: string;
|