@north-light/crouter 0.3.192 → 0.3.193
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/client.d.ts +31 -10
- package/dist/api/client.js +83 -24
- package/dist/api/dto/bash-jobs.d.ts +22 -0
- package/dist/api/dto/bash-jobs.js +9 -0
- package/dist/api/dto/human.d.ts +3 -4
- package/dist/api/dto/inbox.d.ts +106 -65
- package/dist/api/dto/inbox.js +2 -8
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +7 -0
- package/dist/api/routes.js +7 -0
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/serial/provider-rotation.test.ts +101 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +68 -14
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +87 -13
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +2 -2
- package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
- package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/input/overlay-owner.js +1 -1
- package/dist/clients/attach/overlays/auth.js +34 -8
- package/dist/clients/attach/overlays/pickers.d.ts +5 -8
- package/dist/clients/attach/overlays/pickers.js +37 -43
- package/dist/clients/attach/viewer.js +643 -620
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +38 -128
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +72 -148
- package/dist/clients/inbox/controller.d.ts +2 -3
- package/dist/clients/inbox/controller.js +41 -42
- package/dist/clients/inbox/page-adapter.d.ts +33 -0
- package/dist/clients/inbox/page-adapter.js +54 -0
- package/dist/clients/inbox/resolve.d.ts +2 -2
- package/dist/clients/inbox/resolve.js +1 -1
- package/dist/clients/inbox/review-visibility.d.ts +1 -1
- package/dist/clients/inbox/review-visibility.js +1 -1
- package/dist/clients/inbox/tui/ansi.d.ts +1 -1
- package/dist/clients/inbox/tui/ansi.js +2 -2
- package/dist/clients/inbox/tui/input.d.ts +1 -3
- package/dist/clients/inbox/tui/input.js +242 -553
- package/dist/clients/inbox/tui/panel.d.ts +3 -1
- package/dist/clients/inbox/tui/panel.js +59 -187
- package/dist/clients/inbox/tui/render.d.ts +0 -25
- package/dist/clients/inbox/tui/render.js +165 -416
- package/dist/clients/inbox/tui/slots.d.ts +32 -0
- package/dist/clients/inbox/tui/slots.js +156 -0
- package/dist/clients/inbox/tui/types.d.ts +25 -13
- package/dist/clients/inbox/tui.js +5 -3
- package/dist/commands/attention.js +2 -2
- package/dist/commands/human/doc.js +66 -20
- package/dist/commands/human/prompts.d.ts +3 -3
- package/dist/commands/human/prompts.js +158 -112
- package/dist/commands/human/queue.d.ts +0 -1
- package/dist/commands/human/queue.js +34 -94
- package/dist/commands/human/shared.d.ts +10 -4
- package/dist/commands/human/shared.js +62 -13
- package/dist/commands/human.js +8 -4
- package/dist/commands/memory/read.js +13 -5
- package/dist/commands/node/bash.js +15 -28
- package/dist/commands/surface/node/focus.js +1 -1
- package/dist/core/__tests__/human-deliver.test.js +40 -46
- package/dist/core/__tests__/seam/broker-attach-stream.test.js +7 -1
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +40 -31
- package/dist/core/__tests__/serial/spawn-root.test.js +3 -1
- package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +3 -1
- package/dist/core/auth-interaction.d.ts +2 -2
- package/dist/core/bash-jobs.d.ts +32 -0
- package/dist/core/bash-jobs.js +72 -1
- package/dist/core/canvas/__tests__/attention.test.js +16 -10
- package/dist/core/canvas/__tests__/render-remote.test.js +9 -3
- package/dist/core/canvas/attention.js +1 -1
- package/dist/core/canvas/browse/app.js +1 -1
- package/dist/core/canvas/nav-model.d.ts +1 -1
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/remote-canvas-source.js +1 -1
- package/dist/core/config.js +4 -1
- package/dist/core/human/__tests__/page-render.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-render.test.js +49 -0
- package/dist/core/human/__tests__/page-tickets.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-tickets.test.js +112 -0
- package/dist/core/human/__tests__/page.test.d.ts +1 -0
- package/dist/core/human/__tests__/page.test.js +139 -0
- package/dist/core/human/__tests__/serial/inbox-core.test.js +42 -32
- package/dist/core/human/claim.js +2 -2
- package/dist/core/human/convention.d.ts +4 -4
- package/dist/core/human/convention.js +6 -13
- package/dist/core/human/markdown-html.d.ts +8 -0
- package/dist/core/human/markdown-html.js +581 -0
- package/dist/core/human/page-catalog.d.ts +6 -0
- package/dist/core/human/page-catalog.js +43 -0
- package/dist/core/human/page-render.d.ts +3 -0
- package/dist/core/human/page-render.js +131 -0
- package/dist/core/human/page-schema.d.ts +312 -0
- package/dist/core/human/page-schema.js +291 -0
- package/dist/core/human/page-synth.d.ts +9 -0
- package/dist/core/human/page-synth.js +17 -0
- package/dist/core/human/page.d.ts +22 -0
- package/dist/core/human/page.js +427 -0
- package/dist/core/human/review-schema.d.ts +19 -0
- package/dist/core/human/review-schema.js +34 -0
- package/dist/core/human/scan.d.ts +12 -3
- package/dist/core/human/scan.js +69 -26
- package/dist/core/human/summary.d.ts +5 -6
- package/dist/core/human/summary.js +54 -37
- package/dist/core/human/tickets.d.ts +61 -11
- package/dist/core/human/tickets.js +100 -57
- package/dist/core/human/types.d.ts +21 -66
- package/dist/core/human/types.js +1 -5
- package/dist/core/preview-registry.js +0 -1
- package/dist/core/review/ticket-filter.d.ts +1 -1
- package/dist/core/review/ticket-filter.js +2 -8
- package/dist/core/runtime/broker/read-ops.js +3 -0
- package/dist/core/runtime/broker.js +19 -10
- package/dist/core/runtime/package-health.js +1 -1
- package/dist/core/termrender/termrender.d.ts +1 -1
- package/dist/core/termrender/termrender.js +2 -2
- package/dist/core/tui/page-host.d.ts +1 -1
- package/dist/core/tui/page-host.js +2 -2
- package/dist/core/user-settings.d.ts +19 -0
- package/dist/core/user-settings.js +22 -0
- package/dist/daemon/api/handlers/bash-jobs.d.ts +2 -0
- package/dist/daemon/api/handlers/bash-jobs.js +89 -0
- package/dist/daemon/api/handlers/human.js +16 -14
- package/dist/daemon/api/handlers/inbox.js +311 -227
- package/dist/daemon/api/router.d.ts +5 -1
- package/dist/daemon/api/router.js +5 -0
- package/dist/daemon/api/server.js +2 -0
- package/dist/daemon/cron-run.js +23 -10
- package/dist/daemon/human/finish.d.ts +8 -3
- package/dist/daemon/human/finish.js +35 -15
- package/dist/pages/bundle.css +1 -0
- package/dist/pages/bundle.js +1347 -0
- package/dist/pages/comments.d.ts +34 -0
- package/dist/pages/comments.js +72 -0
- package/dist/pages/elements/cards.d.ts +17 -0
- package/dist/pages/elements/cards.js +785 -0
- package/dist/pages/elements/chart.d.ts +1 -0
- package/dist/pages/elements/chart.js +648 -0
- package/dist/pages/elements/options.d.ts +21 -0
- package/dist/pages/elements/options.js +604 -0
- package/dist/pages/elements/pages.d.ts +24 -0
- package/dist/pages/elements/pages.js +355 -0
- package/dist/pages/elements/slot.d.ts +21 -0
- package/dist/pages/elements/slot.js +105 -0
- package/dist/pages/elements/table.d.ts +19 -0
- package/dist/pages/elements/table.js +906 -0
- package/dist/pages/elements/text.d.ts +25 -0
- package/dist/pages/elements/text.js +951 -0
- package/dist/pages/entry.d.ts +8 -0
- package/dist/pages/entry.js +8 -0
- package/dist/pages/host.d.ts +132 -0
- package/dist/pages/host.js +149 -0
- package/dist/pages/register.d.ts +2 -0
- package/dist/pages/register.js +3 -0
- package/dist/pages/slot-config.d.ts +36 -0
- package/dist/pages/slot-config.js +50 -0
- package/dist/pages/types.d.ts +98 -0
- package/dist/pages/types.js +23 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +2 -0
- package/package.json +8 -8
- package/runtime.lock.json +156 -101
- package/dist/clients/inbox/deck-adapter.d.ts +0 -29
- package/dist/clients/inbox/deck-adapter.js +0 -63
- package/dist/core/human/deck-factories.d.ts +0 -8
- package/dist/core/human/deck-factories.js +0 -15
- package/dist/core/human/deck-schema.d.ts +0 -70
- package/dist/core/human/deck-schema.js +0 -92
|
@@ -1,44 +1,61 @@
|
|
|
1
|
+
import { isResponseBearingSlot } from './page-schema.js';
|
|
2
|
+
function labelsOf(items, ids, labelKey) {
|
|
3
|
+
const byId = new Map((Array.isArray(items) ? items : [])
|
|
4
|
+
.filter((item) => typeof item === 'object' && item !== null)
|
|
5
|
+
.map((item) => [String(item['id']), typeof item[labelKey] === 'string' ? item[labelKey] : String(item['id'])]));
|
|
6
|
+
return (Array.isArray(ids) ? ids : []).map((id) => byId.get(String(id)) ?? String(id));
|
|
7
|
+
}
|
|
8
|
+
function pickedWithFreetext(picked, freetext) {
|
|
9
|
+
const text = typeof freetext === 'string' && freetext !== '' ? freetext : undefined;
|
|
10
|
+
const selection = picked.length > 0 ? picked.join(', ') : undefined;
|
|
11
|
+
if (selection !== undefined && text !== undefined)
|
|
12
|
+
return `${selection} — ${text}`;
|
|
13
|
+
return selection ?? text ?? '(no selection)';
|
|
14
|
+
}
|
|
15
|
+
function commentSuffix(response) {
|
|
16
|
+
const comments = response['comments'];
|
|
17
|
+
const count = Array.isArray(comments) ? comments.length : 0;
|
|
18
|
+
return count === 0 ? '' : ` (${count} comment${count === 1 ? '' : 's'})`;
|
|
19
|
+
}
|
|
20
|
+
function describeSlotResponse(slot, response) {
|
|
21
|
+
const value = response;
|
|
22
|
+
const config = slot.config;
|
|
23
|
+
switch (slot.kind) {
|
|
24
|
+
case 'options':
|
|
25
|
+
return `${pickedWithFreetext(labelsOf(config['options'], value['selectedOptionIds'], 'label'), value['freetext'])}${commentSuffix(value)}`;
|
|
26
|
+
case 'text': {
|
|
27
|
+
const text = typeof value['text'] === 'string' ? value['text'].replace(/\s+/g, ' ').trim() : '';
|
|
28
|
+
return `${text === '' ? '(empty)' : text}${commentSuffix(value)}`;
|
|
29
|
+
}
|
|
30
|
+
case 'table': {
|
|
31
|
+
const rows = (Array.isArray(value['selectedRowIds']) ? value['selectedRowIds'] : []).map(String);
|
|
32
|
+
const columns = labelsOf(config['columns'], value['selectedColumnIds'], 'label');
|
|
33
|
+
const parts = [...(rows.length > 0 ? [`rows ${rows.join(', ')}`] : []), ...(columns.length > 0 ? [`columns ${columns.join(', ')}`] : [])];
|
|
34
|
+
return `${parts.length === 0 ? '(no selection)' : parts.join('; ')}${commentSuffix(value)}`;
|
|
35
|
+
}
|
|
36
|
+
case 'cards': {
|
|
37
|
+
const picked = labelsOf(config['cards'], value['selectedCardIds'], 'title');
|
|
38
|
+
return `${picked.length === 0 ? '(no selection)' : picked.join(', ')}${commentSuffix(value)}`;
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
// A product kind crtr does not render: its opaque response is the summary.
|
|
42
|
+
return JSON.stringify(value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
1
45
|
/**
|
|
2
|
-
* Deterministic, no-LLM resolution summary — one line per answered
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* time).
|
|
46
|
+
* Deterministic, no-LLM page resolution summary — one line per answered slot in
|
|
47
|
+
* document order: `"<slot id>: <answer>"`. An acknowledgement (`responses: {}`)
|
|
48
|
+
* summarizes as the empty string.
|
|
6
49
|
*/
|
|
7
|
-
export function
|
|
8
|
-
const byId = new Map(responses.map((r) => [r.id, r]));
|
|
50
|
+
export function buildPageSummary(manifest, responses) {
|
|
9
51
|
const lines = [];
|
|
10
|
-
for (const
|
|
11
|
-
|
|
12
|
-
if (r === undefined)
|
|
52
|
+
for (const slot of manifest.slots) {
|
|
53
|
+
if (!isResponseBearingSlot(slot) || slot.id === undefined)
|
|
13
54
|
continue;
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const parts = r.selectedOptionIds
|
|
19
|
-
.map((id) => it.options.find((o) => o.id === id))
|
|
20
|
-
.filter((o) => o !== undefined)
|
|
21
|
-
.map((o) => {
|
|
22
|
-
const note = oc !== undefined ? oc[o.id] : undefined;
|
|
23
|
-
return typeof note === 'string' && note.length > 0
|
|
24
|
-
? `${o.label} ("${note}")`
|
|
25
|
-
: o.label;
|
|
26
|
-
});
|
|
27
|
-
picked = parts.length > 0 ? parts.join(', ') : undefined;
|
|
28
|
-
}
|
|
29
|
-
else if (r.selectedOptionId !== undefined) {
|
|
30
|
-
picked = it.options.find((o) => o.id === r.selectedOptionId)?.label;
|
|
31
|
-
}
|
|
32
|
-
let val;
|
|
33
|
-
if (picked !== undefined && ft !== undefined)
|
|
34
|
-
val = `${picked} — ${ft}`;
|
|
35
|
-
else if (picked !== undefined)
|
|
36
|
-
val = picked;
|
|
37
|
-
else if (ft !== undefined)
|
|
38
|
-
val = ft;
|
|
39
|
-
else
|
|
40
|
-
val = '(skipped)';
|
|
41
|
-
lines.push(`${it.title}: ${val}`);
|
|
55
|
+
const response = responses[slot.id];
|
|
56
|
+
if (response === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
lines.push(`${slot.id}: ${describeSlotResponse(slot, response)}`);
|
|
42
59
|
}
|
|
43
60
|
return lines.join('\n');
|
|
44
61
|
}
|
|
@@ -1,14 +1,43 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReviewDescriptor, TicketResult } from './types.js';
|
|
2
|
+
import { type PageDialect } from './page.js';
|
|
3
|
+
import { type PageManifest, type PageResponses, type PageSource } from './page-schema.js';
|
|
2
4
|
/** Strict decoder for the only canonical final marker. */
|
|
3
5
|
export declare function readTicketResult(dir: string): TicketResult | null;
|
|
4
|
-
export interface
|
|
6
|
+
export interface SubmitPageOptions {
|
|
5
7
|
dir: string;
|
|
6
|
-
|
|
8
|
+
/** The authored page source; copied into the ticket directory as its `document`. */
|
|
9
|
+
sourceFile: string;
|
|
10
|
+
dialect: PageDialect;
|
|
11
|
+
source?: PageSource;
|
|
12
|
+
productKinds?: string[];
|
|
7
13
|
}
|
|
8
|
-
|
|
14
|
+
/** One validated page, ready to publish into a ticket directory. */
|
|
15
|
+
export interface PreparedPage {
|
|
16
|
+
dialect: PageDialect;
|
|
17
|
+
/** The authored source, verbatim, as the ticket's `document`. */
|
|
18
|
+
document: string;
|
|
19
|
+
manifest: PageManifest;
|
|
20
|
+
}
|
|
21
|
+
/** Validate one authored page source into publishable form. Reads only: a page
|
|
22
|
+
* that fails authoring leaves nothing behind, so a caller that must create a
|
|
23
|
+
* bridge node prepares first and creates it only once the page is known good.
|
|
24
|
+
* The source is read exactly once, so the published document is the same bytes
|
|
25
|
+
* the manifest was derived from even if the file changes underneath. */
|
|
26
|
+
export declare function preparePage(opts: Omit<SubmitPageOptions, 'dir'>): PreparedPage;
|
|
27
|
+
/** Publish one prepared page ticket: the authored source plus its manifest, exclusively. */
|
|
28
|
+
export declare function publishPage(dir: string, prepared: PreparedPage): {
|
|
9
29
|
id: string;
|
|
10
30
|
dir: string;
|
|
11
|
-
kind: '
|
|
31
|
+
kind: 'page';
|
|
32
|
+
manifest: PageManifest;
|
|
33
|
+
};
|
|
34
|
+
/** Validate and publish one page ticket in a single step, for callers that own
|
|
35
|
+
* the ticket directory before they have a page. */
|
|
36
|
+
export declare function submitPage(opts: SubmitPageOptions): {
|
|
37
|
+
id: string;
|
|
38
|
+
dir: string;
|
|
39
|
+
kind: 'page';
|
|
40
|
+
manifest: PageManifest;
|
|
12
41
|
};
|
|
13
42
|
export interface SubmitReviewOptions {
|
|
14
43
|
dir: string;
|
|
@@ -23,17 +52,38 @@ export declare function submitReview(opts: SubmitReviewOptions): {
|
|
|
23
52
|
dir: string;
|
|
24
53
|
kind: 'review';
|
|
25
54
|
};
|
|
26
|
-
/** Validate a
|
|
27
|
-
export declare function
|
|
28
|
-
|
|
55
|
+
/** Validate a page resolution against its canonical ticket before claim takeover. */
|
|
56
|
+
export declare function validatePageTicketResponses(dir: string, responses: unknown, productKinds?: string[]): PageResponses;
|
|
57
|
+
/** The three terminal states of a page ticket, derived from its published result. */
|
|
58
|
+
export declare function pageTicketState(dir: string): 'pending' | 'resolved' | 'canceled';
|
|
59
|
+
/** Autosave partial page work. Every supplied response is validated; omitted slots are permitted. */
|
|
60
|
+
export declare function writePageProgress(dir: string, responses: unknown, productKinds?: string[]): PageResponses;
|
|
61
|
+
/** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
|
|
62
|
+
export declare function readPageProgress(dir: string, productKinds?: string[]): PageResponses | null;
|
|
63
|
+
export interface PageTicket {
|
|
64
|
+
dir: string;
|
|
65
|
+
id: string;
|
|
66
|
+
manifest: PageManifest;
|
|
67
|
+
/** The authored source document, verbatim. */
|
|
68
|
+
document: string;
|
|
69
|
+
state: 'pending' | 'resolved' | 'canceled';
|
|
70
|
+
progress: PageResponses | null;
|
|
71
|
+
}
|
|
72
|
+
/** Read one addressable page ticket — pending, resolved or canceled alike. */
|
|
73
|
+
export declare function readPageTicket(dir: string, productKinds?: string[]): PageTicket;
|
|
74
|
+
export interface CompletePageOptions {
|
|
75
|
+
productKinds?: string[];
|
|
76
|
+
completedAt?: string;
|
|
77
|
+
}
|
|
78
|
+
export declare function completePage(dir: string, responses: unknown, claimToken: string, opts?: CompletePageOptions): {
|
|
29
79
|
won: boolean;
|
|
30
80
|
result: TicketResult;
|
|
31
81
|
};
|
|
32
|
-
/** Atomically take a ticket's claim over, complete it, and release the takeover claim. */
|
|
33
|
-
export declare function
|
|
82
|
+
/** Atomically take a page ticket's claim over, complete it, and release the takeover claim. */
|
|
83
|
+
export declare function takeoverAndCompletePage(dir: string, responses: unknown, owner: {
|
|
34
84
|
host: string;
|
|
35
85
|
pid: number;
|
|
36
|
-
}): {
|
|
86
|
+
}, opts?: CompletePageOptions): {
|
|
37
87
|
won: boolean;
|
|
38
88
|
result: TicketResult;
|
|
39
89
|
};
|
|
@@ -2,13 +2,14 @@ import { existsSync, mkdirSync, readFileSync, realpathSync, unlinkSync, writeFil
|
|
|
2
2
|
import { basename, isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
6
|
-
import { atomicWriteJson, clearProgress, claimPath,
|
|
7
|
-
import {
|
|
5
|
+
import { buildPageSummary } from './summary.js';
|
|
6
|
+
import { atomicWriteJson, clearProgress, claimPath, pageManifestPath, pagePath, progressPath, publishJsonExclusive, requireTicket, responsePath, reviewPath } from './convention.js';
|
|
7
|
+
import { validateReviewDescriptor, validateReviewProjection } from './review-schema.js';
|
|
8
|
+
import { derivePageManifestFromDocument, parsePage, PageAuthoringError } from './page.js';
|
|
9
|
+
import { validatePageResponses, validatePartialPageResponses } from './page-schema.js';
|
|
8
10
|
import { signalInboxActivity } from './root.js';
|
|
9
11
|
import { readTicketClaim, releaseClaimLocked, withTicketLock } from './claim.js';
|
|
10
12
|
import { checkReviewableFile } from './visible-paths.js';
|
|
11
|
-
const responseSchema = z.object({ id: z.string().min(1), selectedOptionId: z.string().optional(), selectedOptionIds: z.array(z.string()).optional(), freetext: z.string().optional(), optionComments: z.record(z.string(), z.string()).optional() }).strict();
|
|
12
13
|
const feedbackCommentSchema = z.object({ id: z.string().min(1), line: z.number().int().positive(), endLine: z.number().int().positive(), quote: z.string().optional(), colStart: z.number().int().nonnegative().optional(), colEnd: z.number().int().nonnegative().optional(), lineText: z.string(), comment: z.string().min(1), createdAt: z.string().min(1) }).strict();
|
|
13
14
|
const iso = z.string().datetime({ offset: true });
|
|
14
15
|
const feedbackSchema = z.object({ file: z.string().min(1).refine(isAbsolute, 'file must be absolute').refine((file) => resolve(file) === file, 'file must be canonical'), submitted: z.literal(true), approved: z.boolean(), comments: z.array(feedbackCommentSchema), submittedAt: iso, savedAt: iso, reviewId: z.string().optional(), changed: z.boolean().optional(), baselineSha256: z.string().optional(), approvedSha256: z.string().optional(), companionNodeId: z.string().optional(), commentsTotal: z.number().int().nonnegative().optional(), commentsUnresolved: z.number().int().nonnegative().optional() }).strict().superRefine((result, ctx) => {
|
|
@@ -21,7 +22,9 @@ const feedbackSchema = z.object({ file: z.string().min(1).refine(isAbsolute, 'fi
|
|
|
21
22
|
ctx.addIssue({ code: 'custom', message: 'colEnd must exceed colStart', path: ['comments', index, 'colEnd'] });
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
|
-
const
|
|
25
|
+
const slotResponseSchema = z.record(z.string(), z.unknown());
|
|
26
|
+
const pageResultSchema = z.object({ schema: z.literal('crtr.page-response/v1'), kind: z.literal('page'), responses: z.record(z.string(), slotResponseSchema), summary: z.string(), completedAt: iso }).strict();
|
|
27
|
+
const pageProgressSchema = z.object({ kind: z.literal('page'), responses: z.record(z.string(), slotResponseSchema), savedAt: iso }).strict();
|
|
25
28
|
const reviewResultSchema = z.object({ schema: z.literal('humanloop.review-response/v1'), kind: z.literal('review'), result: feedbackSchema, completedAt: iso }).strict();
|
|
26
29
|
const canceledResultSchema = z.object({ schema: z.literal('humanloop.cancel/v1'), kind: z.literal('canceled'), canceledAt: iso, reason: z.string().optional(), actor: z.string().optional() }).strict();
|
|
27
30
|
/** Strict decoder for the only canonical final marker. */
|
|
@@ -33,9 +36,9 @@ export function readTicketResult(dir) {
|
|
|
33
36
|
catch {
|
|
34
37
|
return null;
|
|
35
38
|
}
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
39
|
+
const page = pageResultSchema.safeParse(raw);
|
|
40
|
+
if (page.success)
|
|
41
|
+
return page.data;
|
|
39
42
|
const review = reviewResultSchema.safeParse(raw);
|
|
40
43
|
if (review.success)
|
|
41
44
|
return review.data;
|
|
@@ -43,24 +46,39 @@ export function readTicketResult(dir) {
|
|
|
43
46
|
return canceled.success ? canceled.data : null;
|
|
44
47
|
}
|
|
45
48
|
function hasTicketProtocolState(dir) {
|
|
46
|
-
return [
|
|
49
|
+
return [pageManifestPath(dir), pagePath(dir, 'md'), pagePath(dir, 'html'), reviewPath(dir), responsePath(dir), progressPath(dir), claimPath(dir)].some(existsSync);
|
|
47
50
|
}
|
|
48
51
|
function publishRequest(path, value) {
|
|
49
52
|
if (!publishJsonExclusive(path, value))
|
|
50
53
|
throw new Error(`ticket request already exists: ${path}`);
|
|
51
54
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
55
|
+
/** Validate one authored page source into publishable form. Reads only: a page
|
|
56
|
+
* that fails authoring leaves nothing behind, so a caller that must create a
|
|
57
|
+
* bridge node prepares first and creates it only once the page is known good.
|
|
58
|
+
* The source is read exactly once, so the published document is the same bytes
|
|
59
|
+
* the manifest was derived from even if the file changes underneath. */
|
|
60
|
+
export function preparePage(opts) {
|
|
61
|
+
if (!existsSync(opts.sourceFile))
|
|
62
|
+
throw new PageAuthoringError(`page source file does not exist: ${opts.sourceFile}`);
|
|
63
|
+
const document = readFileSync(opts.sourceFile, 'utf8');
|
|
64
|
+
const stamped = { ...(opts.source ?? {}), blockedSince: opts.source?.blockedSince ?? new Date().toISOString() };
|
|
65
|
+
return { dialect: opts.dialect, document, manifest: derivePageManifestFromDocument(document, opts.dialect, opts.productKinds ?? [], stamped) };
|
|
66
|
+
}
|
|
67
|
+
/** Publish one prepared page ticket: the authored source plus its manifest, exclusively. */
|
|
68
|
+
export function publishPage(dir, prepared) {
|
|
69
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
70
|
+
const ticket = realpathSync(dir);
|
|
71
|
+
if (hasTicketProtocolState(ticket))
|
|
72
|
+
throw new Error(`ticket protocol state already exists: ${ticket}`);
|
|
73
|
+
writeFileSync(pagePath(ticket, prepared.dialect), prepared.document, { flag: 'wx', mode: 0o600 });
|
|
74
|
+
publishRequest(pageManifestPath(ticket), prepared.manifest);
|
|
62
75
|
signalInboxActivity();
|
|
63
|
-
return { id: basename(
|
|
76
|
+
return { id: basename(ticket), dir: ticket, kind: 'page', manifest: prepared.manifest };
|
|
77
|
+
}
|
|
78
|
+
/** Validate and publish one page ticket in a single step, for callers that own
|
|
79
|
+
* the ticket directory before they have a page. */
|
|
80
|
+
export function submitPage(opts) {
|
|
81
|
+
return publishPage(opts.dir, preparePage(opts));
|
|
64
82
|
}
|
|
65
83
|
export function submitReview(opts) {
|
|
66
84
|
// A review takes any reviewable text file: a .md artifact renders as a document,
|
|
@@ -83,39 +101,64 @@ export function submitReview(opts) {
|
|
|
83
101
|
function exclusiveResult(dir, result) {
|
|
84
102
|
return publishJsonExclusive(responsePath(dir), result);
|
|
85
103
|
}
|
|
86
|
-
function requireDeck(dir) { return validateDeck(JSON.parse(readFileSync(deckPath(dir), 'utf8'))); }
|
|
87
104
|
function requireReview(dir) { return validateReviewDescriptor(JSON.parse(readFileSync(reviewPath(dir), 'utf8'))); }
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
function requirePage(dir, productKinds) {
|
|
106
|
+
if (!existsSync(pageManifestPath(dir)))
|
|
107
|
+
throw new Error(`not a page ticket: ${dir}`);
|
|
108
|
+
return parsePage(dir, productKinds);
|
|
109
|
+
}
|
|
110
|
+
/** Validate a page resolution against its canonical ticket before claim takeover. */
|
|
111
|
+
export function validatePageTicketResponses(dir, responses, productKinds = []) {
|
|
112
|
+
return validatePageResponses(requirePage(requireTicket(dir), productKinds), responses);
|
|
113
|
+
}
|
|
114
|
+
/** The three terminal states of a page ticket, derived from its published result. */
|
|
115
|
+
export function pageTicketState(dir) {
|
|
116
|
+
const result = readTicketResult(dir);
|
|
117
|
+
if (result === null)
|
|
118
|
+
return 'pending';
|
|
119
|
+
return result.kind === 'canceled' ? 'canceled' : 'resolved';
|
|
120
|
+
}
|
|
121
|
+
function readPageProgressFor(dir, manifest) {
|
|
122
|
+
let raw;
|
|
123
|
+
try {
|
|
124
|
+
raw = JSON.parse(readFileSync(progressPath(dir), 'utf8'));
|
|
106
125
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error.code === 'ENOENT')
|
|
128
|
+
return null;
|
|
129
|
+
throw new Error(`page progress is not valid JSON: ${progressPath(dir)}`);
|
|
112
130
|
}
|
|
113
|
-
|
|
131
|
+
const parsed = pageProgressSchema.safeParse(raw);
|
|
132
|
+
if (!parsed.success)
|
|
133
|
+
throw new Error(`page progress is malformed: ${progressPath(dir)}`);
|
|
134
|
+
// Saved work is validated on read as well as on write: a page whose manifest
|
|
135
|
+
// changed under it must fail loudly rather than hydrate an answer it no longer has.
|
|
136
|
+
return validatePartialPageResponses(manifest, parsed.data.responses);
|
|
137
|
+
}
|
|
138
|
+
/** Autosave partial page work. Every supplied response is validated; omitted slots are permitted. */
|
|
139
|
+
export function writePageProgress(dir, responses, productKinds = []) {
|
|
140
|
+
const ticket = requireTicket(dir);
|
|
141
|
+
const validated = validatePartialPageResponses(requirePage(ticket, productKinds), responses);
|
|
142
|
+
atomicWriteJson(progressPath(ticket), { kind: 'page', responses: validated, savedAt: new Date().toISOString() });
|
|
143
|
+
return validated;
|
|
144
|
+
}
|
|
145
|
+
/** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
|
|
146
|
+
export function readPageProgress(dir, productKinds = []) {
|
|
147
|
+
const ticket = requireTicket(dir);
|
|
148
|
+
return readPageProgressFor(ticket, requirePage(ticket, productKinds));
|
|
114
149
|
}
|
|
115
|
-
/**
|
|
116
|
-
export function
|
|
150
|
+
/** Read one addressable page ticket — pending, resolved or canceled alike. */
|
|
151
|
+
export function readPageTicket(dir, productKinds = []) {
|
|
117
152
|
const ticket = requireTicket(dir);
|
|
118
|
-
|
|
153
|
+
const manifest = requirePage(ticket, productKinds);
|
|
154
|
+
return {
|
|
155
|
+
dir: ticket,
|
|
156
|
+
id: basename(ticket),
|
|
157
|
+
manifest,
|
|
158
|
+
document: readFileSync(pagePath(ticket, manifest.dialect), 'utf8'),
|
|
159
|
+
state: pageTicketState(ticket),
|
|
160
|
+
progress: readPageProgressFor(ticket, manifest),
|
|
161
|
+
};
|
|
119
162
|
}
|
|
120
163
|
function requireClaimOwnership(dir, token) {
|
|
121
164
|
if (readTicketClaim(dir)?.token !== token)
|
|
@@ -123,18 +166,18 @@ function requireClaimOwnership(dir, token) {
|
|
|
123
166
|
}
|
|
124
167
|
function clearOwnedWork(dir, claimToken) { clearProgress(dir); releaseClaimLocked(dir, claimToken); }
|
|
125
168
|
/** Caller holds `.ticket-lock`. */
|
|
126
|
-
function
|
|
169
|
+
function completePageLocked(dir, responses, claimToken, opts) {
|
|
127
170
|
requireClaimOwnership(dir, claimToken);
|
|
128
|
-
const
|
|
129
|
-
const parsedResponses =
|
|
130
|
-
const result = { schema: '
|
|
171
|
+
const manifest = requirePage(dir, opts.productKinds ?? []);
|
|
172
|
+
const parsedResponses = validatePageResponses(manifest, responses);
|
|
173
|
+
const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses), completedAt: opts.completedAt ?? new Date().toISOString() };
|
|
131
174
|
const won = exclusiveResult(dir, result);
|
|
132
175
|
clearOwnedWork(dir, claimToken);
|
|
133
176
|
return { won, result: won ? result : readTicketResult(dir) ?? result };
|
|
134
177
|
}
|
|
135
|
-
export function
|
|
178
|
+
export function completePage(dir, responses, claimToken, opts = {}) {
|
|
136
179
|
const ticket = requireTicket(dir);
|
|
137
|
-
const completed = withTicketLock(ticket, () =>
|
|
180
|
+
const completed = withTicketLock(ticket, () => completePageLocked(ticket, responses, claimToken, opts));
|
|
138
181
|
if (completed.won)
|
|
139
182
|
signalInboxActivity();
|
|
140
183
|
return completed;
|
|
@@ -148,13 +191,13 @@ function takeoverClaim(dir, owner) {
|
|
|
148
191
|
writeFileSync(claimPath(dir), `${JSON.stringify({ token, host: owner.host, pid: owner.pid, claimedAt: now, heartbeatAt: now })}\n`, { flag: 'wx', mode: 0o600 });
|
|
149
192
|
return token;
|
|
150
193
|
}
|
|
151
|
-
/** Atomically take a ticket's claim over, complete it, and release the takeover claim. */
|
|
152
|
-
export function
|
|
194
|
+
/** Atomically take a page ticket's claim over, complete it, and release the takeover claim. */
|
|
195
|
+
export function takeoverAndCompletePage(dir, responses, owner, opts = {}) {
|
|
153
196
|
const ticket = requireTicket(dir);
|
|
154
197
|
const completed = withTicketLock(ticket, () => {
|
|
155
198
|
const token = takeoverClaim(ticket, owner);
|
|
156
199
|
try {
|
|
157
|
-
return
|
|
200
|
+
return completePageLocked(ticket, responses, token, opts);
|
|
158
201
|
}
|
|
159
202
|
finally {
|
|
160
203
|
releaseClaimLocked(ticket, token);
|
|
@@ -1,51 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export interface
|
|
4
|
-
id: string;
|
|
5
|
-
label: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
/** Auto-assigned by the TUI (assignShortcuts), never author-settable. */
|
|
8
|
-
shortcut?: string;
|
|
9
|
-
}
|
|
10
|
-
export interface InteractionPreAnswer {
|
|
11
|
-
selectedOptionId?: string;
|
|
12
|
-
selectedOptionIds?: string[];
|
|
13
|
-
freetext?: string;
|
|
14
|
-
/** One-line marker shown in the answered chrome. */
|
|
15
|
-
label?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface Interaction {
|
|
18
|
-
id: string;
|
|
19
|
-
title: string;
|
|
20
|
-
subtitle: string;
|
|
21
|
-
body?: string;
|
|
22
|
-
bodyPath?: string;
|
|
23
|
-
options: InteractionOption[];
|
|
24
|
-
multiSelect?: boolean;
|
|
25
|
-
allowFreetext?: boolean;
|
|
26
|
-
freetextLabel?: string;
|
|
27
|
-
kind?: InteractionKind;
|
|
28
|
-
preAnswered?: InteractionPreAnswer;
|
|
29
|
-
}
|
|
30
|
-
export interface InteractionResponse {
|
|
31
|
-
id: string;
|
|
32
|
-
selectedOptionId?: string;
|
|
33
|
-
selectedOptionIds?: string[];
|
|
34
|
-
freetext?: string;
|
|
35
|
-
optionComments?: Record<string, string>;
|
|
36
|
-
}
|
|
37
|
-
export interface DeckSource {
|
|
1
|
+
import type { PageResponses } from './page-schema.js';
|
|
2
|
+
/** Provenance stamped onto every ticket request — page and review alike. */
|
|
3
|
+
export interface TicketSource {
|
|
38
4
|
sessionName?: string;
|
|
39
5
|
askedBy?: string;
|
|
40
6
|
blockedSince?: string;
|
|
41
7
|
/** Originating canvas node id (CRTR_NODE_ID) when the ask was raised inside a crouter canvas node. */
|
|
42
8
|
nodeId?: string;
|
|
43
9
|
}
|
|
44
|
-
export interface Deck {
|
|
45
|
-
title: string;
|
|
46
|
-
source?: DeckSource;
|
|
47
|
-
interactions: Interaction[];
|
|
48
|
-
}
|
|
49
10
|
export interface FeedbackComment {
|
|
50
11
|
id: string;
|
|
51
12
|
line: number;
|
|
@@ -65,17 +26,6 @@ export interface FeedbackResult {
|
|
|
65
26
|
submittedAt?: string;
|
|
66
27
|
savedAt: string;
|
|
67
28
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Resolution contract returned by `ask`/`inbox`. On-disk `response.json` stays
|
|
70
|
-
* `{ responses, completedAt }`; `responsePath` points at it.
|
|
71
|
-
*/
|
|
72
|
-
export interface ResolutionEnvelope {
|
|
73
|
-
summary: string;
|
|
74
|
-
responsePath: string;
|
|
75
|
-
schema: 'humanloop.response/v2';
|
|
76
|
-
responses: InteractionResponse[];
|
|
77
|
-
completedAt: string;
|
|
78
|
-
}
|
|
79
29
|
export interface ClaimSummary {
|
|
80
30
|
owner: string;
|
|
81
31
|
claimedAt: string;
|
|
@@ -87,34 +37,39 @@ interface TicketSummaryBase {
|
|
|
87
37
|
title: string;
|
|
88
38
|
subtitle: string;
|
|
89
39
|
blockedSince: string;
|
|
90
|
-
source:
|
|
40
|
+
source: TicketSource;
|
|
91
41
|
claim?: ClaimSummary;
|
|
92
42
|
}
|
|
93
|
-
export interface DeckTicketSummary extends TicketSummaryBase {
|
|
94
|
-
kind: 'deck';
|
|
95
|
-
interactionKind?: InteractionKind;
|
|
96
|
-
interactionCount: number;
|
|
97
|
-
}
|
|
98
43
|
export interface ReviewTicketSummary extends TicketSummaryBase {
|
|
99
44
|
kind: 'review';
|
|
100
45
|
file: string;
|
|
101
46
|
output: string;
|
|
102
47
|
}
|
|
48
|
+
/** Page triage fields: a client picks a transcript rendering without a second fetch. */
|
|
49
|
+
export interface PageTicketSummary extends TicketSummaryBase {
|
|
50
|
+
kind: 'page';
|
|
51
|
+
dialect: 'md' | 'html';
|
|
52
|
+
placement: 'inline' | 'panel';
|
|
53
|
+
steps: number;
|
|
54
|
+
slotKinds: string[];
|
|
55
|
+
awaitsResponse: boolean;
|
|
56
|
+
}
|
|
103
57
|
/** The only pending-ticket shape scanners expose. */
|
|
104
|
-
export type TicketSummary =
|
|
58
|
+
export type TicketSummary = ReviewTicketSummary | PageTicketSummary;
|
|
105
59
|
export interface ReviewDescriptor {
|
|
106
60
|
schema: 'humanloop.review/v1';
|
|
107
61
|
file: string;
|
|
108
62
|
output: string;
|
|
109
63
|
title: string;
|
|
110
64
|
subtitle: string;
|
|
111
|
-
source:
|
|
65
|
+
source: TicketSource;
|
|
112
66
|
blockedSince: string;
|
|
113
67
|
}
|
|
114
|
-
export interface
|
|
115
|
-
schema: '
|
|
116
|
-
kind: '
|
|
117
|
-
|
|
68
|
+
export interface PageTicketResult {
|
|
69
|
+
schema: 'crtr.page-response/v1';
|
|
70
|
+
kind: 'page';
|
|
71
|
+
/** An acknowledgement is the empty map; there is no second shape. */
|
|
72
|
+
responses: PageResponses;
|
|
118
73
|
summary: string;
|
|
119
74
|
completedAt: string;
|
|
120
75
|
}
|
|
@@ -132,5 +87,5 @@ export interface CanceledTicketResult {
|
|
|
132
87
|
actor?: string;
|
|
133
88
|
}
|
|
134
89
|
/** The sole canonical response.json union. */
|
|
135
|
-
export type TicketResult =
|
|
90
|
+
export type TicketResult = PageTicketResult | ReviewTicketResult | CanceledTicketResult;
|
|
136
91
|
export {};
|
package/dist/core/human/types.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// Single source of truth for the interaction-kind enum: the Zod deck schema
|
|
3
|
-
// (`deck-schema.ts`) and the CLI's JSON schema both derive their enum values
|
|
4
|
-
// from this array so a new kind cannot drift between validation surfaces.
|
|
5
|
-
export const INTERACTION_KINDS = ['notify', 'decision', 'context', 'error', 'review'];
|
|
1
|
+
export {};
|
|
@@ -12,6 +12,6 @@ import type { TicketSummary } from '../human/types.js';
|
|
|
12
12
|
export declare function isReviewTicketVisible(bridgeNodeId: string): boolean;
|
|
13
13
|
/**
|
|
14
14
|
* Filters a ticket summary array to remove hidden terminal-review tickets.
|
|
15
|
-
*
|
|
15
|
+
* Page tickets pass through unchanged. Review tickets are filtered by state.
|
|
16
16
|
*/
|
|
17
17
|
export declare function filterTerminalReviewTickets(items: TicketSummary[]): TicketSummary[];
|
|
@@ -19,14 +19,8 @@ export function isReviewTicketVisible(bridgeNodeId) {
|
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Filters a ticket summary array to remove hidden terminal-review tickets.
|
|
22
|
-
*
|
|
22
|
+
* Page tickets pass through unchanged. Review tickets are filtered by state.
|
|
23
23
|
*/
|
|
24
24
|
export function filterTerminalReviewTickets(items) {
|
|
25
|
-
return items.filter((item) =>
|
|
26
|
-
// Deck tickets always pass through
|
|
27
|
-
if (item.kind === 'deck')
|
|
28
|
-
return true;
|
|
29
|
-
// Review tickets are filtered by visibility
|
|
30
|
-
return isReviewTicketVisible(item.id);
|
|
31
|
-
});
|
|
25
|
+
return items.filter((item) => (item.kind === 'review' ? isReviewTicketVisible(item.id) : true));
|
|
32
26
|
}
|
|
@@ -117,6 +117,7 @@ export function buildSettingsData(id, session) {
|
|
|
117
117
|
terminalTheme: 'dark',
|
|
118
118
|
availableThemes,
|
|
119
119
|
hideThinkingBlock: manager.getHideThinkingBlock(),
|
|
120
|
+
mermaidRenderingMode: manager.getMermaidRenderingMode(),
|
|
120
121
|
collapseChangelog: manager.getCollapseChangelog(),
|
|
121
122
|
enableInstallTelemetry: manager.getEnableInstallTelemetry(),
|
|
122
123
|
doubleEscapeAction: manager.getDoubleEscapeAction(),
|
|
@@ -127,6 +128,8 @@ export function buildSettingsData(id, session) {
|
|
|
127
128
|
quietStartup: manager.getQuietStartup(),
|
|
128
129
|
clearOnShrink: manager.getClearOnShrink(),
|
|
129
130
|
showTerminalProgress: manager.getShowTerminalProgress(),
|
|
131
|
+
tuiMode: manager.getTuiMode(),
|
|
132
|
+
fullscreenScrollbar: manager.getFullscreenScrollbar(),
|
|
130
133
|
warnings: manager.getWarnings(),
|
|
131
134
|
defaultProjectTrust: manager.getDefaultProjectTrust(),
|
|
132
135
|
showCacheMissNotices: manager.getShowCacheMissNotices(),
|