@north-light/crouter 0.3.196 → 0.3.198
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 +2 -2
- package/dist/api/dto/inbox.d.ts +46 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/render/chat-view.d.ts +3 -8
- package/dist/clients/attach/render/chat-view.js +3 -24
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/viewer.js +613 -602
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +2 -2
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +11 -4
- package/dist/clients/inbox/controller.js +4 -4
- package/dist/clients/inbox/page-adapter.d.ts +2 -1
- package/dist/clients/inbox/tui/render.js +46 -25
- package/dist/clients/inbox/tui/slots.d.ts +1 -3
- package/dist/clients/inbox/tui/slots.js +8 -37
- package/dist/clients/inbox/tui/types.d.ts +4 -3
- package/dist/commands/__tests__/human.test.js +160 -23
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/human/doc.js +21 -74
- package/dist/commands/human/html.d.ts +2 -0
- package/dist/commands/human/html.js +81 -0
- package/dist/commands/human/prompts.d.ts +5 -4
- package/dist/commands/human/prompts.js +90 -168
- package/dist/commands/human/shared.d.ts +5 -6
- package/dist/commands/human/shared.js +13 -61
- package/dist/commands/human.js +7 -2
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/human-deliver.test.js +28 -37
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +63 -4
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +3 -13
- package/dist/core/canvas/__tests__/attention.test.js +2 -2
- package/dist/core/canvas/__tests__/render-remote.test.js +2 -2
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +26 -0
- package/dist/core/canvas/canvas.js +55 -0
- package/dist/core/command.js +6 -11
- package/dist/core/config.js +3 -2
- package/dist/core/help.d.ts +3 -0
- package/dist/core/human/__tests__/html-markdown.test.d.ts +1 -0
- package/dist/core/human/__tests__/html-markdown.test.js +52 -0
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +16 -0
- package/dist/core/human/__tests__/page-render.test.js +40 -23
- package/dist/core/human/__tests__/page-tickets.test.js +48 -27
- package/dist/core/human/__tests__/page.test.js +48 -65
- package/dist/core/human/__tests__/serial/inbox-core.test.js +5 -15
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +51 -0
- package/dist/core/human/answer.js +125 -0
- package/dist/core/human/component-docs.d.ts +14 -0
- package/dist/core/human/component-docs.js +161 -0
- package/dist/core/human/html-markdown.d.ts +3 -0
- package/dist/core/human/html-markdown.js +288 -0
- package/dist/core/human/page-catalog.d.ts +19 -3
- package/dist/core/human/page-catalog.js +73 -16
- package/dist/core/human/page-render.d.ts +3 -2
- package/dist/core/human/page-render.js +19 -45
- package/dist/core/human/page-schema.d.ts +108 -8
- package/dist/core/human/page-schema.js +88 -31
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +23 -7
- package/dist/core/human/page.d.ts +14 -7
- package/dist/core/human/page.js +178 -90
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/scan.d.ts +3 -2
- package/dist/core/human/scan.js +7 -8
- package/dist/core/human/summary.d.ts +4 -7
- package/dist/core/human/summary.js +4 -60
- package/dist/core/human/tickets.d.ts +10 -9
- package/dist/core/human/tickets.js +5 -3
- package/dist/core/human/types.d.ts +4 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/review/realize.js +9 -1
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker.js +8 -6
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/termrender/display.d.ts +7 -3
- package/dist/core/termrender/display.js +45 -9
- package/dist/core/termrender/termrender.d.ts +3 -12
- package/dist/core/termrender/termrender.js +6 -37
- package/dist/core/user-settings.d.ts +17 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/daemon/api/handlers/human.js +1 -1
- package/dist/daemon/api/handlers/inbox.js +22 -7
- package/dist/daemon/cron-run.js +16 -2
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +2 -1
- package/dist/daemon/human/finish.js +45 -28
- package/dist/daemon/human/sweep.js +4 -2
- package/dist/daemon/reconcilers/broker-supervision.js +11 -8
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pages/bundle.css +1 -1
- package/dist/pages/bundle.js +1008 -577
- package/dist/pages/comments.d.ts +29 -0
- package/dist/pages/comments.js +51 -0
- package/dist/pages/controls.d.ts +98 -0
- package/dist/pages/controls.js +261 -0
- package/dist/pages/elements/cards.d.ts +15 -4
- package/dist/pages/elements/cards.js +299 -186
- package/dist/pages/elements/chart.d.ts +22 -0
- package/dist/pages/elements/chart.js +107 -91
- package/dist/pages/elements/options.d.ts +9 -0
- package/dist/pages/elements/options.js +268 -155
- package/dist/pages/elements/pages.d.ts +6 -0
- package/dist/pages/elements/pages.js +60 -38
- package/dist/pages/elements/slot.d.ts +5 -0
- package/dist/pages/elements/slot.js +17 -10
- package/dist/pages/elements/table.d.ts +17 -0
- package/dist/pages/elements/table.js +336 -228
- package/dist/pages/elements/text.d.ts +9 -3
- package/dist/pages/elements/text.js +350 -143
- package/dist/pages/host.d.ts +0 -24
- package/dist/pages/host.js +0 -17
- package/dist/pages/readonly.d.ts +24 -0
- package/dist/pages/readonly.js +31 -0
- package/dist/pages/responses.d.ts +37 -0
- package/dist/pages/responses.js +56 -0
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +12 -8
- package/dist/types.d.ts +19 -5
- package/dist/types.js +2 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/scripts/install-runtime.mjs +19 -24
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
|
@@ -5,7 +5,6 @@ import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
|
5
5
|
import { InputError } from '../../core/io.js';
|
|
6
6
|
import { readConfig } from '../../core/config.js';
|
|
7
7
|
import { derivePageManifest } from '../../core/human/page.js';
|
|
8
|
-
import { checkMarkdown, renderMarkdown } from '../../core/termrender/termrender.js';
|
|
9
8
|
function documentSource(input, context) {
|
|
10
9
|
const path = input['path'];
|
|
11
10
|
const body = input['body'];
|
|
@@ -13,8 +12,8 @@ function documentSource(input, context) {
|
|
|
13
12
|
if (path === undefined && !stdinProvided) {
|
|
14
13
|
throw new InputError({
|
|
15
14
|
error: 'missing_parameter',
|
|
16
|
-
message: 'provide exactly one
|
|
17
|
-
next: 'Run `crtr human doc check -h`
|
|
15
|
+
message: 'provide exactly one HTML page source: PATH or stdin',
|
|
16
|
+
next: 'Run `crtr human doc check -h` and provide a complete HTML page path or pipe one on stdin.',
|
|
18
17
|
});
|
|
19
18
|
}
|
|
20
19
|
if (path !== undefined && path !== '-' && stdinProvided && body.trim() !== '') {
|
|
@@ -22,7 +21,7 @@ function documentSource(input, context) {
|
|
|
22
21
|
error: 'bad_invocation',
|
|
23
22
|
message: 'both a positional path and piped stdin were provided',
|
|
24
23
|
field: 'path, body',
|
|
25
|
-
next: 'Pass exactly one
|
|
24
|
+
next: 'Pass exactly one page source: either PATH or piped stdin, not both.',
|
|
26
25
|
});
|
|
27
26
|
}
|
|
28
27
|
if (path === undefined || path === '-')
|
|
@@ -33,38 +32,22 @@ function documentSource(input, context) {
|
|
|
33
32
|
error: 'file_not_found',
|
|
34
33
|
message: `file not found: ${abs}`,
|
|
35
34
|
field: 'path',
|
|
36
|
-
next: 'Provide a path to an existing
|
|
35
|
+
next: 'Provide a path to an existing complete HTML page or pipe one on stdin.',
|
|
37
36
|
});
|
|
38
37
|
}
|
|
39
38
|
return { body: readFileSync(abs, 'utf8'), path: abs };
|
|
40
39
|
}
|
|
41
|
-
function
|
|
42
|
-
return documentSource(input, context).body;
|
|
43
|
-
}
|
|
44
|
-
function pageDialect(source) {
|
|
45
|
-
const extension = source.path?.toLowerCase();
|
|
46
|
-
if (extension?.endsWith('.html') || extension?.endsWith('.htm'))
|
|
47
|
-
return 'html';
|
|
48
|
-
if (/^\s*<!doctype\b/i.test(source.body) || /^\s*<html(?:\s|>)/i.test(source.body))
|
|
49
|
-
return 'html';
|
|
50
|
-
if (source.body.startsWith('---'))
|
|
51
|
-
return 'md';
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
function checkPage(source, dialect) {
|
|
40
|
+
function checkPage(source) {
|
|
55
41
|
const config = readConfig('user');
|
|
56
|
-
if (dialect === 'html' && !config.html_pages) {
|
|
57
|
-
return { ok: false, error: 'HTML page validation requires the user-scope html_pages capability flag to be enabled (html_pages is false).' };
|
|
58
|
-
}
|
|
59
42
|
let tempDir;
|
|
60
43
|
try {
|
|
61
44
|
const sourcePath = source.path ?? (() => {
|
|
62
45
|
tempDir = mkdtempSync(join(tmpdir(), 'crtr-human-doc-'));
|
|
63
|
-
const path = join(tempDir,
|
|
46
|
+
const path = join(tempDir, 'page.html');
|
|
64
47
|
writeFileSync(path, source.body, 'utf8');
|
|
65
48
|
return path;
|
|
66
49
|
})();
|
|
67
|
-
derivePageManifest(sourcePath,
|
|
50
|
+
derivePageManifest(sourcePath, 'html', config.page_components);
|
|
68
51
|
return { ok: true };
|
|
69
52
|
}
|
|
70
53
|
catch (error) {
|
|
@@ -77,68 +60,32 @@ function checkPage(source, dialect) {
|
|
|
77
60
|
}
|
|
78
61
|
const humanDocCheck = defineLeaf({
|
|
79
62
|
name: 'check',
|
|
80
|
-
description: 'validate a
|
|
81
|
-
whenToUse: 'validating
|
|
63
|
+
description: 'validate a complete HTML page source',
|
|
64
|
+
whenToUse: 'validating an authored page before submitting it.',
|
|
82
65
|
help: {
|
|
83
66
|
name: 'human doc check',
|
|
84
|
-
summary: 'validate
|
|
85
|
-
guide: 'Plain markdown is checked by the pinned renderer. A file with page front matter or an HTML document is parsed as a page; HTML page validation requires the user-scope html_pages capability flag.',
|
|
67
|
+
summary: 'validate a complete HTML page through page schemas',
|
|
86
68
|
params: [
|
|
87
|
-
{ kind: 'positional', name: 'path', type: 'path', required: false, constraint: '
|
|
88
|
-
{ kind: 'stdin', name: 'body', required: false, constraint: '
|
|
69
|
+
{ kind: 'positional', name: 'path', type: 'path', required: false, constraint: 'Complete HTML page to validate. Provide exactly one of PATH or stdin; use - to read stdin.' },
|
|
70
|
+
{ kind: 'stdin', name: 'body', required: false, constraint: 'Complete HTML page to validate. Provide exactly one of piped stdin or PATH.' },
|
|
89
71
|
],
|
|
90
72
|
output: [
|
|
91
|
-
{ name: 'ok', type: 'boolean', required: true, constraint: 'True when the
|
|
92
|
-
{ name: 'error', type: 'string', required: false, constraint: 'The
|
|
73
|
+
{ name: 'ok', type: 'boolean', required: true, constraint: 'True when the complete HTML page is valid.' },
|
|
74
|
+
{ name: 'error', type: 'string', required: false, constraint: 'The page-parser validation error when ok is false.' },
|
|
93
75
|
],
|
|
94
76
|
outputKind: 'object',
|
|
95
|
-
effects: ['Read-only: invokes the
|
|
96
|
-
},
|
|
97
|
-
run: async (input, context) => {
|
|
98
|
-
const source = documentSource(input, context);
|
|
99
|
-
const dialect = pageDialect(source);
|
|
100
|
-
if (dialect !== undefined)
|
|
101
|
-
return checkPage(source, dialect);
|
|
102
|
-
return checkMarkdown(source.body);
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
const humanDocRender = defineLeaf({
|
|
106
|
-
name: 'render',
|
|
107
|
-
description: 'render a directive-flavored markdown body',
|
|
108
|
-
whenToUse: 'previewing how a page or review body renders before submitting it.',
|
|
109
|
-
help: {
|
|
110
|
-
name: 'human doc render',
|
|
111
|
-
summary: 'render directive-flavored markdown through the pinned managed renderer',
|
|
112
|
-
guide: 'The pinned renderer runs from a private managed venv and is not on PATH, so this is the only way to render a page or review body before submitting it.',
|
|
113
|
-
params: [
|
|
114
|
-
{ kind: 'positional', name: 'path', type: 'path', required: false, constraint: 'Markdown file to render. Provide exactly one of PATH or stdin; use - to read stdin.' },
|
|
115
|
-
{ kind: 'stdin', name: 'body', required: false, constraint: 'Markdown to render. Provide exactly one of piped stdin or PATH.' },
|
|
116
|
-
{ kind: 'flag', name: 'width', type: 'int', required: false, default: process.stdout.columns || 100, constraint: 'Render width in columns. Default: current stdout width, or 100.' },
|
|
117
|
-
{ kind: 'flag', name: 'color', type: 'bool', required: false, default: true, constraint: 'Emit ANSI color. Default true; false strips ANSI escape sequences.' },
|
|
118
|
-
],
|
|
119
|
-
output: [
|
|
120
|
-
{ name: 'lines', type: 'string[]', required: true, constraint: 'Rendered terminal lines in display order.' },
|
|
121
|
-
{ name: 'width', type: 'integer', required: true, constraint: 'The render width in columns.' },
|
|
122
|
-
],
|
|
123
|
-
outputKind: 'object',
|
|
124
|
-
effects: ['Read-only: invokes the private managed renderer to render the supplied body.'],
|
|
125
|
-
},
|
|
126
|
-
run: async (input, context) => {
|
|
127
|
-
const width = input['width'];
|
|
128
|
-
const color = input['color'];
|
|
129
|
-
const lines = renderMarkdown(markdownSource(input, context), width);
|
|
130
|
-
return { lines: color ? lines : lines.map((line) => line.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')), width };
|
|
77
|
+
effects: ['Read-only: invokes the page parser to validate the supplied complete HTML page.'],
|
|
131
78
|
},
|
|
132
|
-
|
|
79
|
+
run: async (input, context) => checkPage(documentSource(input, context)),
|
|
133
80
|
});
|
|
134
81
|
export const humanDoc = defineBranch({
|
|
135
82
|
name: 'doc',
|
|
136
|
-
description: 'validate
|
|
137
|
-
whenToUse: 'validating
|
|
83
|
+
description: 'validate authored HTML pages',
|
|
84
|
+
whenToUse: 'validating an authored HTML page before submitting it.',
|
|
138
85
|
help: {
|
|
139
86
|
name: 'human doc',
|
|
140
|
-
summary: 'validate
|
|
141
|
-
model: '
|
|
87
|
+
summary: 'validate authored HTML pages',
|
|
88
|
+
model: 'The check leaf reads exactly one complete HTML page from a path or stdin.',
|
|
142
89
|
},
|
|
143
|
-
children: [humanDocCheck
|
|
90
|
+
children: [humanDocCheck],
|
|
144
91
|
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
2
|
+
import { stateBlock } from '../../core/help.js';
|
|
3
|
+
import { InputError } from '../../core/io.js';
|
|
4
|
+
import { readConfig } from '../../core/config.js';
|
|
5
|
+
import { componentKinds, renderComponentCatalog, renderComponentContract } from '../../core/human/component-docs.js';
|
|
6
|
+
import { pageComponentKinds } from '../../core/human/page-catalog.js';
|
|
7
|
+
function productComponents() {
|
|
8
|
+
return readConfig('user').page_components;
|
|
9
|
+
}
|
|
10
|
+
function productKinds() {
|
|
11
|
+
return pageComponentKinds(productComponents());
|
|
12
|
+
}
|
|
13
|
+
function componentKindState() {
|
|
14
|
+
const kinds = componentKinds(productKinds());
|
|
15
|
+
return stateBlock('kinds', { count: kinds.length }, kinds.join('\n'));
|
|
16
|
+
}
|
|
17
|
+
const humanHtmlList = defineLeaf({
|
|
18
|
+
name: 'list',
|
|
19
|
+
description: 'list page components for selection',
|
|
20
|
+
whenToUse: 'choosing which page component to put in an authored HTML page.',
|
|
21
|
+
help: {
|
|
22
|
+
name: 'human html list',
|
|
23
|
+
summary: 'list page components for selection',
|
|
24
|
+
inputNote: 'No input parameters.',
|
|
25
|
+
output: [
|
|
26
|
+
{ name: 'catalog', type: 'markdown', required: true, constraint: 'One concise selection entry per built-in and product-registered component; no component field schemas.' },
|
|
27
|
+
{ name: 'follow_up', type: 'string', required: true, constraint: 'Points to `crtr human html show <kind>` for one selected component contract.' },
|
|
28
|
+
],
|
|
29
|
+
outputKind: 'object',
|
|
30
|
+
effects: ['None. Read-only.'],
|
|
31
|
+
},
|
|
32
|
+
run: async () => ({
|
|
33
|
+
catalog: renderComponentCatalog(productComponents()),
|
|
34
|
+
follow_up: 'Use `crtr human html show <kind>` for one component contract.',
|
|
35
|
+
}),
|
|
36
|
+
render: (result) => `${String(result['catalog'])}\n\n${String(result['follow_up'])}`,
|
|
37
|
+
});
|
|
38
|
+
const humanHtmlShow = defineLeaf({
|
|
39
|
+
name: 'show',
|
|
40
|
+
description: 'show one page component contract',
|
|
41
|
+
whenToUse: 'authoring or validating one selected page component and needing its exact config or response contract.',
|
|
42
|
+
help: {
|
|
43
|
+
name: 'human html show',
|
|
44
|
+
summary: 'show one page component contract',
|
|
45
|
+
params: [
|
|
46
|
+
{ kind: 'positional', name: 'kind', type: 'string', required: true, constraint: 'Component kind from the live <kinds> catalog below.' },
|
|
47
|
+
],
|
|
48
|
+
output: [
|
|
49
|
+
{ name: 'kind', type: 'string', required: true, constraint: 'The selected component kind.' },
|
|
50
|
+
{ name: 'contract', type: 'markdown', required: true, constraint: 'Hand-written config, response, and display/response-bearing contract for that component.' },
|
|
51
|
+
],
|
|
52
|
+
outputKind: 'object',
|
|
53
|
+
effects: ['None. Read-only.'],
|
|
54
|
+
dynamicState: componentKindState,
|
|
55
|
+
},
|
|
56
|
+
run: async (input) => {
|
|
57
|
+
const kind = input['kind'];
|
|
58
|
+
const contract = renderComponentContract(kind, productComponents());
|
|
59
|
+
if (contract === undefined) {
|
|
60
|
+
throw new InputError({
|
|
61
|
+
error: 'unknown_component',
|
|
62
|
+
message: `unknown page component kind "${kind}"`,
|
|
63
|
+
field: 'kind',
|
|
64
|
+
next: 'Run `crtr human html show -h` and choose a kind from its live catalog.',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { kind, contract };
|
|
68
|
+
},
|
|
69
|
+
render: (result) => String(result['contract']),
|
|
70
|
+
});
|
|
71
|
+
export const humanHtml = defineBranch({
|
|
72
|
+
name: 'html',
|
|
73
|
+
description: 'discover HTML page components and contracts',
|
|
74
|
+
whenToUse: 'authoring an HTML page and choosing a component or needing one component’s exact contract.',
|
|
75
|
+
help: {
|
|
76
|
+
name: 'human html',
|
|
77
|
+
summary: 'discover HTML page components and contracts',
|
|
78
|
+
model: 'List is the selection catalog; show owns the full config and response contract for one selected component.',
|
|
79
|
+
},
|
|
80
|
+
children: [humanHtmlList, humanHtmlShow],
|
|
81
|
+
});
|
|
@@ -2,10 +2,11 @@ import type { PageSource } from '../../core/human/page-schema.js';
|
|
|
2
2
|
/** Provenance for crouter tickets. Ticket *identity* comes from the bridge node
|
|
3
3
|
* directory, never ticket data; `nodeId` carries the originating node so a
|
|
4
4
|
* consumer can relate a ticket back to whatever that node belongs to, and
|
|
5
|
-
* `sessionName` is a presentation label only
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* `sessionName` is a presentation label only, while `profileName` lets product
|
|
6
|
+
* consumers resolve the applet or worker identity behind the ticket. Review
|
|
7
|
+
* tickets stamp the same provenance (`core/review/realize.ts`), so pages match
|
|
8
|
+
* them. `nodeId` is stamped even when the daemon is unreachable — losing the
|
|
9
|
+
* labels is survivable, losing machine attribution is not. */
|
|
9
10
|
export declare function sourceStamp(): Promise<PageSource>;
|
|
10
11
|
export declare const humanAsk: import("../../core/command.js").LeafDef;
|
|
11
12
|
export declare const humanNotify: import("../../core/command.js").LeafDef;
|
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt-design gap: a person opens asks from background agents without the
|
|
3
|
-
* task or conversation context. Human-ask help therefore teaches agents to
|
|
4
|
-
* make every interruption self-contained and answerable before its evidence.
|
|
5
|
-
*/
|
|
6
1
|
import { defineLeaf } from '../../core/command.js';
|
|
7
2
|
import { InputError } from '../../core/io.js';
|
|
8
3
|
import { cliClient } from '../api-client.js';
|
|
9
4
|
import { extname, join } from 'node:path';
|
|
10
5
|
import { randomBytes } from 'node:crypto';
|
|
11
|
-
import { rmSync } from 'node:fs';
|
|
6
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
12
8
|
import { readConfig } from '../../core/config.js';
|
|
13
|
-
import { tmuxServerReachable } from '../../core/spawn.js';
|
|
14
9
|
import { markStopSignal } from '../../core/runtime/stop-signals.js';
|
|
15
10
|
import { atomicWriteJson } from '../../core/human/convention.js';
|
|
16
11
|
import { ticketDir } from '../../core/human/root.js';
|
|
17
12
|
import { preparePage, publishPage } from '../../core/human/tickets.js';
|
|
18
|
-
import { synthesizedPageDocument, writeSynthesizedPage } from '../../core/human/page-synth.js';
|
|
19
13
|
import { PageAuthoringError } from '../../core/human/page.js';
|
|
20
14
|
import { display } from '../../core/termrender/display.js';
|
|
21
15
|
import { pageHelpText, resolveMaxPanes } from './shared.js';
|
|
@@ -27,76 +21,45 @@ function askingNode() {
|
|
|
27
21
|
/** Provenance for crouter tickets. Ticket *identity* comes from the bridge node
|
|
28
22
|
* directory, never ticket data; `nodeId` carries the originating node so a
|
|
29
23
|
* consumer can relate a ticket back to whatever that node belongs to, and
|
|
30
|
-
* `sessionName` is a presentation label only
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
24
|
+
* `sessionName` is a presentation label only, while `profileName` lets product
|
|
25
|
+
* consumers resolve the applet or worker identity behind the ticket. Review
|
|
26
|
+
* tickets stamp the same provenance (`core/review/realize.ts`), so pages match
|
|
27
|
+
* them. `nodeId` is stamped even when the daemon is unreachable — losing the
|
|
28
|
+
* labels is survivable, losing machine attribution is not. */
|
|
34
29
|
export async function sourceStamp() {
|
|
35
30
|
const nodeId = askingNode();
|
|
36
31
|
if (nodeId === null || nodeId === '')
|
|
37
32
|
return {};
|
|
38
33
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
34
|
+
const client = cliClient();
|
|
35
|
+
const node = await client.getNode(nodeId);
|
|
36
|
+
const sessionName = node.name.trim();
|
|
37
|
+
const profileName = node.profile_id === null
|
|
38
|
+
? undefined
|
|
39
|
+
: (await client.getProfile(node.profile_id)).name;
|
|
40
|
+
return {
|
|
41
|
+
nodeId,
|
|
42
|
+
...(sessionName === '' ? {} : { sessionName }),
|
|
43
|
+
...(profileName === undefined ? {} : { profileName }),
|
|
44
|
+
};
|
|
41
45
|
}
|
|
42
46
|
catch {
|
|
43
47
|
return { nodeId };
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
|
-
function requireAskTitle(title) {
|
|
47
|
-
if (title !== undefined)
|
|
48
|
-
return title;
|
|
49
|
-
throw new InputError({
|
|
50
|
-
error: 'missing_parameter',
|
|
51
|
-
message: 'stdin questions require a non-empty --title',
|
|
52
|
-
field: 'title',
|
|
53
|
-
next: 'Retry with a short --title naming the topic being decided.',
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function requireSubtitle(subtitle, field = 'subtitle') {
|
|
57
|
-
if (subtitle !== undefined && subtitle.trim() !== '')
|
|
58
|
-
return subtitle.trim();
|
|
59
|
-
throw new InputError({
|
|
60
|
-
error: 'missing_parameter',
|
|
61
|
-
message: 'a non-empty subtitle is required',
|
|
62
|
-
field,
|
|
63
|
-
next: 'Write one plain-English sentence stating the decision, recommendation or status, and stakes.',
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
50
|
function pageDialect(sourceFile) {
|
|
67
51
|
const extension = extname(sourceFile).toLowerCase();
|
|
68
|
-
if (extension === '.md')
|
|
69
|
-
return 'md';
|
|
70
52
|
if (extension === '.html' || extension === '.htm')
|
|
71
53
|
return 'html';
|
|
72
54
|
throw new InputError({
|
|
73
55
|
error: 'invalid_page_dialect',
|
|
74
|
-
message: `--page must
|
|
56
|
+
message: `--page must name an HTML page; received "${sourceFile}"`,
|
|
75
57
|
field: 'page',
|
|
76
|
-
next: '
|
|
58
|
+
next: 'Write the page at `$CRTR_CONTEXT_DIR/pages/<name>.html`, then retry with --page PATH.',
|
|
77
59
|
});
|
|
78
60
|
}
|
|
79
|
-
function pageProductKinds(
|
|
80
|
-
|
|
81
|
-
if (dialect === 'html' && !config.html_pages) {
|
|
82
|
-
throw new InputError({
|
|
83
|
-
error: 'page_dialect_disabled',
|
|
84
|
-
message: 'html pages are not enabled in this home; md is the dialect to use',
|
|
85
|
-
field: 'page',
|
|
86
|
-
next: 'Use a .md page, or enable html_pages in the user scope config before submitting HTML.',
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return config.page_components;
|
|
90
|
-
}
|
|
91
|
-
function requireNotifyTitle(title) {
|
|
92
|
-
if (title !== undefined && title.trim() !== '')
|
|
93
|
-
return title.trim();
|
|
94
|
-
throw new InputError({
|
|
95
|
-
error: 'missing_parameter',
|
|
96
|
-
message: 'notify shorthand requires a non-empty title',
|
|
97
|
-
field: 'title',
|
|
98
|
-
next: 'Retry with a positional title, --subtitle, and optional --body, or submit a page with --page PATH.',
|
|
99
|
-
});
|
|
61
|
+
function pageProductKinds() {
|
|
62
|
+
return readConfig('user').page_components;
|
|
100
63
|
}
|
|
101
64
|
function submitAuthoredPage(submit) {
|
|
102
65
|
try {
|
|
@@ -113,10 +76,50 @@ function submitAuthoredPage(submit) {
|
|
|
113
76
|
});
|
|
114
77
|
}
|
|
115
78
|
}
|
|
79
|
+
function writePipedPage(document) {
|
|
80
|
+
const dir = mkdtempSync(join(tmpdir(), 'crtr-human-page-'));
|
|
81
|
+
const path = join(dir, 'page.html');
|
|
82
|
+
writeFileSync(path, document, { encoding: 'utf8', mode: 0o600 });
|
|
83
|
+
return { dir, path };
|
|
84
|
+
}
|
|
85
|
+
/** Resolve exactly one authored page input. Piped bytes are materialized so
|
|
86
|
+
* both input forms follow the same page preparation and validation path. */
|
|
87
|
+
function pageInput(input) {
|
|
88
|
+
const file = input['page'];
|
|
89
|
+
const documentRaw = input['document'];
|
|
90
|
+
// Optional stdin arrives as an empty string when a caller redirects an empty
|
|
91
|
+
// stream. It is not a page source and must not conflict with --page.
|
|
92
|
+
const document = typeof documentRaw === 'string' && documentRaw.trim() !== '' ? documentRaw : undefined;
|
|
93
|
+
if (file !== undefined && document !== undefined) {
|
|
94
|
+
throw new InputError({
|
|
95
|
+
error: 'bad_invocation',
|
|
96
|
+
message: 'received both --page and a piped page document',
|
|
97
|
+
field: 'page, document',
|
|
98
|
+
next: 'Pass exactly one page source: --page PATH or the document on stdin.',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (file === undefined && document === undefined) {
|
|
102
|
+
throw new InputError({
|
|
103
|
+
error: 'missing_parameter',
|
|
104
|
+
message: 'a page source is required',
|
|
105
|
+
field: 'page, document',
|
|
106
|
+
next: 'Pass exactly one page source: --page PATH or the document on stdin.',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (file !== undefined) {
|
|
110
|
+
if (typeof file !== 'string')
|
|
111
|
+
throw new InputError({ error: 'invalid_type', message: '--page must be a path', field: 'page', next: 'Retry with --page PATH.' });
|
|
112
|
+
return { sourceFile: file };
|
|
113
|
+
}
|
|
114
|
+
if (typeof document !== 'string')
|
|
115
|
+
throw new InputError({ error: 'invalid_type', message: 'stdin must contain a page document', field: 'document', next: 'Pipe the page document on stdin.' });
|
|
116
|
+
const temp = writePipedPage(document);
|
|
117
|
+
return { sourceFile: temp.path, tempDir: temp.dir };
|
|
118
|
+
}
|
|
116
119
|
/** The non-blocking status peek returned by ask/review. How the human reaches
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
+
* their inbox is theirs, not the agent's — naming a shortcut here only teaches
|
|
121
|
+
* agents to repeat a screen-specific gesture back to a reader who may be on a
|
|
122
|
+
* different surface entirely. */
|
|
120
123
|
function queuedFollowUp() {
|
|
121
124
|
return 'Queued for the human. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block.';
|
|
122
125
|
}
|
|
@@ -129,20 +132,15 @@ export const humanAsk = defineLeaf({
|
|
|
129
132
|
whenToUse: 'you would otherwise lay a decision, a set of options, or a question out for the user as prose — reach for this instead, for anything from a quick yes/no to a judgment-heavy call: gating a handoff, merge, deploy, destructive/irreversible operation, or real budget on an explicit human sign-off (offer Yes/No options), reviewing all the requirements before building, choosing among implementation patterns, walking a list of risks and deciding what to do about each, settling a naming or scope question, picking which of several findings to act on. Works for open-ended asks too (set `allowFreetext`, offer a few `options` as starting points).',
|
|
130
133
|
help: {
|
|
131
134
|
name: 'human ask',
|
|
132
|
-
summary: "submit a decision ticket to crouter's inbox; returns a job handle immediately
|
|
133
|
-
guide:
|
|
134
|
-
'Write a self-contained decision brief for a supervisor who has not followed the task: title names the topic; subtitle states the decision, your recommendation, and stakes; the body gives only the context and tradeoffs needed to decide. Put supporting evidence under `## Details`. Ask only what changes your next step, and offer genuine alternatives as `options`.',
|
|
135
|
-
PAGE_HELP.authoring,
|
|
136
|
-
].join('\n\n'),
|
|
135
|
+
summary: "submit a decision ticket to crouter's inbox; returns a job handle immediately",
|
|
136
|
+
guide: 'Page authoring: `crtr human -h`.',
|
|
137
137
|
params: [
|
|
138
|
-
{ kind: '
|
|
139
|
-
{ kind: 'flag', name: '
|
|
140
|
-
{ kind: 'stdin', name: 'question', required: false, constraint: "Simple open-text ask body. Pipe markdown from a single-quoted heredoc (`<<'EOF'`) to preserve literal bytes. Requires --title and --subtitle; mutually exclusive with --page." },
|
|
141
|
-
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Mutually exclusive with shorthand.` },
|
|
138
|
+
{ kind: 'stdin', name: 'document', required: false, allowPositional: false, constraint: 'Page bytes from stdin. Provide exactly one of stdin or --page PATH.' },
|
|
139
|
+
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Provide exactly one of --page or stdin.` },
|
|
142
140
|
],
|
|
143
141
|
output: [
|
|
144
142
|
{ name: 'job_id', type: 'string', required: true, constraint: 'Node id of this human interaction. Its answer is pushed to your inbox when the human responds.' },
|
|
145
|
-
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.
|
|
143
|
+
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.html, page.json, run.json, and response.json.' },
|
|
146
144
|
{ name: 'follow_up', type: 'string', required: true, constraint: 'A non-blocking status peek; the answer arrives via your inbox.' },
|
|
147
145
|
],
|
|
148
146
|
outputKind: 'object',
|
|
@@ -152,48 +150,10 @@ export const humanAsk = defineLeaf({
|
|
|
152
150
|
],
|
|
153
151
|
},
|
|
154
152
|
run: async (input) => {
|
|
155
|
-
|
|
156
|
-
// raw non-empty bytes untouched so shorthand callers retain their body.
|
|
157
|
-
const titleRaw = input['title'];
|
|
158
|
-
const title = typeof titleRaw === 'string' && titleRaw.trim() !== '' ? titleRaw.trim() : undefined;
|
|
159
|
-
const questionRaw = input['question'];
|
|
160
|
-
const question = typeof questionRaw === 'string' && questionRaw !== '' ? questionRaw : undefined;
|
|
161
|
-
const subtitleRaw = input['subtitle'];
|
|
162
|
-
const subtitle = typeof subtitleRaw === 'string' && subtitleRaw.trim() !== '' ? subtitleRaw.trim() : undefined;
|
|
163
|
-
const pageInput = input['page'];
|
|
164
|
-
if (pageInput !== undefined && (question !== undefined || titleRaw !== undefined || subtitleRaw !== undefined)) {
|
|
165
|
-
throw new InputError({
|
|
166
|
-
error: 'bad_invocation',
|
|
167
|
-
message: 'use --title and --subtitle with stdin question text, or --page, not both',
|
|
168
|
-
next: 'Retry with --title, --subtitle, and a piped question body, or with --page PATH alone.',
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
if (question === undefined && pageInput === undefined) {
|
|
172
|
-
throw new InputError({
|
|
173
|
-
error: 'missing_parameter',
|
|
174
|
-
message: 'human ask needs stdin question text or --page PATH',
|
|
175
|
-
next: 'Pipe a question body with --title and --subtitle, or pass --page PATH for an authored page.',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
const dialect = pageDialect(typeof pageInput === 'string' ? pageInput : 'page.md');
|
|
179
|
-
const productKinds = pageProductKinds(dialect);
|
|
180
|
-
let tempDir;
|
|
181
|
-
let sourceFile;
|
|
182
|
-
if (pageInput !== undefined) {
|
|
183
|
-
sourceFile = pageInput;
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
const simpleAsk = { title: requireAskTitle(title), subtitle: requireSubtitle(subtitle), question: question };
|
|
187
|
-
const synthesized = writeSynthesizedPage(synthesizedPageDocument(simpleAsk.title, simpleAsk.subtitle, simpleAsk.question, true));
|
|
188
|
-
tempDir = synthesized.dir;
|
|
189
|
-
sourceFile = synthesized.path;
|
|
190
|
-
}
|
|
153
|
+
const { sourceFile, tempDir } = pageInput(input);
|
|
191
154
|
try {
|
|
192
155
|
const source = await sourceStamp();
|
|
193
|
-
|
|
194
|
-
// must create nothing, because a bridge node born for a ticket that never
|
|
195
|
-
// got published is a live terminal node no answer can ever finish.
|
|
196
|
-
const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect, source, productKinds }));
|
|
156
|
+
const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect: pageDialect(sourceFile), source, productKinds: pageProductKinds() }));
|
|
197
157
|
const cwd = process.cwd();
|
|
198
158
|
const jobId = (await cliClient().createHumanBridge({ kind: 'human', parent: askingNode(), cwd, name: 'human-ask' })).node_id;
|
|
199
159
|
const idir = ticketDir(jobId);
|
|
@@ -221,19 +181,14 @@ export const humanNotify = defineLeaf({
|
|
|
221
181
|
help: {
|
|
222
182
|
name: 'human notify',
|
|
223
183
|
summary: 'enqueue a fire-and-forget acknowledgement in the human inbox; creates no job',
|
|
224
|
-
guide:
|
|
225
|
-
'State what happened in plain words. Shorthand bodies are directive-flavored Markdown; validate authored pages with `crtr human doc check`.',
|
|
226
|
-
PAGE_HELP.authoring,
|
|
227
|
-
].join('\n\n'),
|
|
184
|
+
guide: 'Page authoring: `crtr human -h`.',
|
|
228
185
|
params: [
|
|
229
|
-
{ kind: '
|
|
230
|
-
{ kind: 'flag', name: '
|
|
231
|
-
{ kind: 'flag', name: 'body', type: 'string', required: false, constraint: 'Optional markdown body for shorthand; mutually exclusive with --page.' },
|
|
232
|
-
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Mutually exclusive with --body.` },
|
|
186
|
+
{ kind: 'stdin', name: 'document', required: false, allowPositional: false, constraint: 'Page bytes from stdin. Provide exactly one of stdin or --page PATH.' },
|
|
187
|
+
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Provide exactly one of --page or stdin.` },
|
|
233
188
|
],
|
|
234
189
|
output: [
|
|
235
190
|
{ name: 'queued', type: 'boolean', required: true, constraint: 'Always true — the notify page is durable inbox state the human acknowledges from the inbox.' },
|
|
236
|
-
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.
|
|
191
|
+
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.html, page.json, and run.json.' },
|
|
237
192
|
],
|
|
238
193
|
outputKind: 'object',
|
|
239
194
|
effects: [
|
|
@@ -241,38 +196,10 @@ export const humanNotify = defineLeaf({
|
|
|
241
196
|
],
|
|
242
197
|
},
|
|
243
198
|
run: async (input) => {
|
|
244
|
-
const
|
|
245
|
-
const title = typeof titleRaw === 'string' && titleRaw.trim() !== '' ? titleRaw.trim() : undefined;
|
|
246
|
-
const subtitleRaw = input['subtitle'];
|
|
247
|
-
const subtitle = typeof subtitleRaw === 'string' && subtitleRaw.trim() !== '' ? subtitleRaw.trim() : undefined;
|
|
248
|
-
const body = input['body'];
|
|
249
|
-
const pageInput = input['page'];
|
|
250
|
-
if (pageInput !== undefined && body !== undefined) {
|
|
251
|
-
throw new InputError({
|
|
252
|
-
error: 'bad_invocation',
|
|
253
|
-
message: 'use --page or --body, not both',
|
|
254
|
-
next: 'Retry with --page PATH alone for an authored page, or use the title, --subtitle, and --body shorthand.',
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
const dialect = pageDialect(typeof pageInput === 'string' ? pageInput : 'page.md');
|
|
258
|
-
const productKinds = pageProductKinds(dialect);
|
|
259
|
-
let tempDir;
|
|
260
|
-
let sourceFile;
|
|
261
|
-
if (pageInput !== undefined) {
|
|
262
|
-
sourceFile = pageInput;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
const shorthandTitle = requireNotifyTitle(title);
|
|
266
|
-
const shorthandSubtitle = requireSubtitle(subtitle);
|
|
267
|
-
const synthesized = writeSynthesizedPage(synthesizedPageDocument(shorthandTitle, shorthandSubtitle, body ?? '', false));
|
|
268
|
-
tempDir = synthesized.dir;
|
|
269
|
-
sourceFile = synthesized.path;
|
|
270
|
-
}
|
|
199
|
+
const { sourceFile, tempDir } = pageInput(input);
|
|
271
200
|
try {
|
|
272
201
|
const source = await sourceStamp();
|
|
273
|
-
|
|
274
|
-
// no half-built ticket behind.
|
|
275
|
-
const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect, source, productKinds }));
|
|
202
|
+
const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect: pageDialect(sourceFile), source, productKinds: pageProductKinds() }));
|
|
276
203
|
const id = `nfy-${randomBytes(4).toString('hex')}`;
|
|
277
204
|
const rc = { mode: 'notify' };
|
|
278
205
|
atomicWriteJson(join(ticketDir(id), 'run.json'), rc);
|
|
@@ -287,8 +214,8 @@ export const humanNotify = defineLeaf({
|
|
|
287
214
|
});
|
|
288
215
|
// ---------------------------------------------------------------------------
|
|
289
216
|
// show (no job, non-blocking passthrough) — passive live document display, NOT
|
|
290
|
-
// a ticket surface.
|
|
291
|
-
//
|
|
217
|
+
// a ticket surface. `display()` puts a file live in a watch pane and never
|
|
218
|
+
// enqueues anything.
|
|
292
219
|
// ---------------------------------------------------------------------------
|
|
293
220
|
export const humanShow = defineLeaf({
|
|
294
221
|
name: 'show',
|
|
@@ -297,7 +224,7 @@ export const humanShow = defineLeaf({
|
|
|
297
224
|
help: {
|
|
298
225
|
name: 'human show',
|
|
299
226
|
summary: "put a file live on screen in a tmux pane with crouter's renderer",
|
|
300
|
-
guide: 'The pane always watches the file and live-updates on every save — a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid)
|
|
227
|
+
guide: 'The pane always watches the file and live-updates on every save — a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid). To put a rich document in front of the human as a durable inbox page, use `crtr human notify --page PATH`.',
|
|
301
228
|
params: [
|
|
302
229
|
{ kind: 'positional', name: 'path', type: 'path', required: true, constraint: 'Path to the file to render.' },
|
|
303
230
|
{ kind: 'flag', name: 'window', type: 'enum', choices: ['auto', 'split', 'new'], required: false, default: 'auto', constraint: 'Placement. Default auto.' },
|
|
@@ -313,22 +240,17 @@ export const humanShow = defineLeaf({
|
|
|
313
240
|
const path = input['path'];
|
|
314
241
|
const windowArg = input['window'];
|
|
315
242
|
const window = windowArg !== undefined ? windowArg : 'auto';
|
|
316
|
-
// `human show`
|
|
317
|
-
//
|
|
318
|
-
let paneId;
|
|
243
|
+
// `human show` always reports display failures in its result rather than
|
|
244
|
+
// turning a passive screen surface into a failed agent turn.
|
|
319
245
|
try {
|
|
320
|
-
const
|
|
321
|
-
paneId
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
paneId = undefined;
|
|
246
|
+
const result = display(path, { window, maxPanes: resolveMaxPanes() });
|
|
247
|
+
return result.paneId !== undefined
|
|
248
|
+
? { pane_id: result.paneId, reason: null }
|
|
249
|
+
: { pane_id: null, reason: result.reason };
|
|
325
250
|
}
|
|
326
|
-
|
|
327
|
-
|
|
251
|
+
catch (error) {
|
|
252
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
253
|
+
return { pane_id: null, reason: `display failed: ${reason}` };
|
|
328
254
|
}
|
|
329
|
-
const reason = tmuxServerReachable()
|
|
330
|
-
? 'renderer unavailable (termrender/uv missing)'
|
|
331
|
-
: 'not in tmux';
|
|
332
|
-
return { pane_id: null, reason };
|
|
333
255
|
},
|
|
334
256
|
});
|