@north-light/crouter 0.3.192 → 0.3.194
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 +24 -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/02-lifecycle/01-resident.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
- 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 +18 -30
- package/dist/commands/node/lifecycle.js +2 -2
- 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__/seam/dormancy-release.test.js +64 -10
- 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 +43 -0
- package/dist/core/bash-jobs.js +96 -2
- 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/canvas/types.d.ts +2 -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/inspector/model.d.ts +5 -0
- package/dist/core/inspector/model.js +5 -0
- package/dist/core/inspector/text.js +2 -2
- package/dist/core/inspector/tui.js +10 -3
- package/dist/core/keybindings/catalog.d.ts +1 -1
- package/dist/core/keybindings/catalog.js +4 -2
- 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/runtime/persona.js +2 -1
- package/dist/core/runtime/tmux-bindings.js +8 -1
- package/dist/core/termrender/termrender.d.ts +1 -1
- package/dist/core/termrender/termrender.js +2 -2
- package/dist/core/termrender/version.d.ts +1 -1
- package/dist/core/termrender/version.js +1 -1
- 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 +90 -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/daemon/reconcilers/broker-supervision.js +7 -1
- package/dist/daemon/reconcilers/dormant-inbox.js +8 -0
- package/dist/daemon/reconcilers/live-obligation.d.ts +2 -0
- package/dist/daemon/reconcilers/live-obligation.js +11 -0
- 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/pi-extensions/canvas-bash-valve.d.ts +4 -2
- package/dist/pi-extensions/canvas-bash-valve.js +25 -7
- 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/scripts/install-runtime.mjs +172 -5
- 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 {};
|
|
@@ -4,6 +4,8 @@ export type InspectorSection = 'overview' | 'schedule' | 'jobs' | 'raw';
|
|
|
4
4
|
export interface BashJobRow {
|
|
5
5
|
job_id: string;
|
|
6
6
|
command: string;
|
|
7
|
+
/** Human-readable label supplied with the bash call, or null when absent. */
|
|
8
|
+
purpose: string | null;
|
|
7
9
|
started_at: string;
|
|
8
10
|
elapsed_ms: number;
|
|
9
11
|
log_path: string;
|
|
@@ -67,6 +69,9 @@ export declare function jobElapsedMs(job: BashJobRow, now?: number): number;
|
|
|
67
69
|
export declare function jobElapsed(job: BashJobRow, now?: number): string;
|
|
68
70
|
/** First line of a command, whitespace-collapsed and capped for a one-row cell. */
|
|
69
71
|
export declare function commandCell(command: string, cap?: number): string;
|
|
72
|
+
/** A person-readable purpose leads when present; legacy jobs retain their
|
|
73
|
+
* command label. */
|
|
74
|
+
export declare function jobLabel(job: BashJobRow, cap?: number): string;
|
|
70
75
|
export declare function relativeTime(iso: string | null, now?: number): string;
|
|
71
76
|
/** How long ago an ISO timestamp was, e.g. `3d ago`. `—` when absent. */
|
|
72
77
|
export declare function ago(iso: unknown, now?: number): string;
|
|
@@ -81,6 +81,11 @@ export function commandCell(command, cap = 60) {
|
|
|
81
81
|
const oneLine = (command.split('\n')[0] ?? '').replace(/\s+/g, ' ').trim();
|
|
82
82
|
return oneLine.length > cap ? `${oneLine.slice(0, cap - 1)}…` : oneLine;
|
|
83
83
|
}
|
|
84
|
+
/** A person-readable purpose leads when present; legacy jobs retain their
|
|
85
|
+
* command label. */
|
|
86
|
+
export function jobLabel(job, cap = 60) {
|
|
87
|
+
return commandCell(job.purpose ?? job.command, cap);
|
|
88
|
+
}
|
|
84
89
|
export function relativeTime(iso, now = Date.now()) {
|
|
85
90
|
if (iso === null)
|
|
86
91
|
return '—';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jobElapsed, overviewGroups, relativeTime, scheduledItems, value } from './model.js';
|
|
1
|
+
import { jobElapsed, jobLabel, overviewGroups, relativeTime, scheduledItems, value } from './model.js';
|
|
2
2
|
/** The piped path: same information and grouping as the TUI, minus the glyphs
|
|
3
3
|
* and hue a pipe cannot carry. */
|
|
4
4
|
export const inspectorText = {
|
|
@@ -36,7 +36,7 @@ export const inspectorText = {
|
|
|
36
36
|
if (jobs.length === 0)
|
|
37
37
|
lines.push(' nothing running');
|
|
38
38
|
for (const job of jobs) {
|
|
39
|
-
lines.push('', ` ${job
|
|
39
|
+
lines.push('', ` ${jobLabel(job)} · ${jobElapsed(job)}`, ` id ${job.job_id}`, ...(job.purpose === null ? [] : [` command ${job.command.split('\n')[0]}`]), ` started ${job.started_at}`, ` log ${job.log_path}`);
|
|
40
40
|
for (const line of job.log_tail.slice(-5))
|
|
41
41
|
lines.push(` | ${line}`);
|
|
42
42
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICON, commandCell, jobElapsed, jobElapsedMs, jobTone, overviewGroups, relativeTime, scheduledItems, shortPath, value, } from './model.js';
|
|
1
|
+
import { ICON, commandCell, jobElapsed, jobElapsedMs, jobLabel, jobTone, overviewGroups, relativeTime, scheduledItems, shortPath, value, } from './model.js';
|
|
2
2
|
// ── Visual grammar ───────────────────────────────────────────────────────────
|
|
3
3
|
//
|
|
4
4
|
// Three primitives carry the whole screen: a TAB STRIP (where am I / where can
|
|
@@ -162,7 +162,14 @@ function jobItem(job, expanded, width) {
|
|
|
162
162
|
lines.push([{ text: `${INDENT}${INDENT}${last === undefined ? 'no output yet' : logCell(last, width - 6)}`, style: { dim: true } }]);
|
|
163
163
|
}
|
|
164
164
|
else {
|
|
165
|
-
|
|
165
|
+
const details = [
|
|
166
|
+
['job', job.job_id],
|
|
167
|
+
...(job.purpose === null ? [] : [['command', commandCell(job.command, Math.max(20, width - 26))]]),
|
|
168
|
+
['started', job.started_at],
|
|
169
|
+
['log', shortPath(job.log_path)],
|
|
170
|
+
['watch', `tail -f ${shortPath(job.log_path)}`],
|
|
171
|
+
];
|
|
172
|
+
for (const [label, text] of details) {
|
|
166
173
|
lines.push([{ text: `${INDENT}${INDENT}${label.padEnd(LABEL_COL)}`, style: LABEL_STYLE }, { text, style: label === 'watch' ? { fg: '36' } : undefined }]);
|
|
167
174
|
}
|
|
168
175
|
lines.push([]);
|
|
@@ -176,7 +183,7 @@ function jobItem(job, expanded, width) {
|
|
|
176
183
|
return {
|
|
177
184
|
spans: [
|
|
178
185
|
{ text: ` ${ICON.job} `, style: tone },
|
|
179
|
-
{ text:
|
|
186
|
+
{ text: jobLabel(job, Math.max(20, width - 26)), style: { bold: true } },
|
|
180
187
|
],
|
|
181
188
|
right: [{ text: `${jobElapsed(job)} `, style: tone }],
|
|
182
189
|
lines,
|