@north-light/crouter 0.3.197 → 0.3.199
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 +19 -19
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +126 -33
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +8 -8
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +29 -9
- package/dist/clients/attach/render/chat-view.js +64 -35
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -982
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +3 -4
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +101 -36
- package/dist/clients/inbox/tui/slots.d.ts +4 -4
- package/dist/clients/inbox/tui/slots.js +19 -44
- package/dist/clients/inbox/tui/types.d.ts +6 -10
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +118 -24
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +17 -8
- package/dist/commands/human/prompts.js +217 -226
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +4 -15
- package/dist/commands/human/shared.js +19 -61
- package/dist/commands/human.js +32 -19
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- 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__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +41 -45
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- 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 +33 -0
- package/dist/core/canvas/canvas.js +92 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +8 -12
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +138 -4
- package/dist/core/help.d.ts +3 -0
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +20 -0
- package/dist/core/human/__tests__/page-tickets.test.js +68 -34
- package/dist/core/human/__tests__/page.test.js +60 -119
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
- 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 +53 -0
- package/dist/core/human/answer.js +119 -0
- package/dist/core/human/component-docs.d.ts +18 -0
- package/dist/core/human/component-docs.js +434 -0
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +56 -5
- package/dist/core/human/page-catalog.js +151 -22
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +48 -116
- package/dist/core/human/page-schema.js +100 -72
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +20 -7
- package/dist/core/human/page.d.ts +13 -21
- package/dist/core/human/page.js +147 -391
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -3
- package/dist/core/human/scan.js +21 -21
- package/dist/core/human/summary.d.ts +2 -6
- package/dist/core/human/summary.js +3 -59
- package/dist/core/human/tickets.d.ts +22 -15
- package/dist/core/human/tickets.js +90 -28
- package/dist/core/human/types.d.ts +14 -2
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +12 -4
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +25 -12
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +36 -2
- package/dist/core/user-settings.js +56 -14
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +48 -109
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +29 -17
- 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 +12 -6
- package/dist/daemon/human/finish.js +134 -54
- package/dist/daemon/human/sweep.js +9 -3
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +14 -9
- 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/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +31 -13
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +61 -5
- package/dist/types.js +8 -2
- package/package.json +4 -4
- package/runtime.lock.json +5 -33
- package/scripts/install-runtime.mjs +19 -24
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/commands/human/doc.js +0 -144
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/page-render.test.js +0 -49
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
- package/dist/core/human/page-render.d.ts +0 -3
- package/dist/core/human/page-render.js +0 -131
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1347
- package/dist/pages/comments.d.ts +0 -43
- package/dist/pages/comments.js +0 -125
- package/dist/pages/elements/cards.d.ts +0 -17
- package/dist/pages/elements/cards.js +0 -802
- package/dist/pages/elements/chart.js +0 -648
- package/dist/pages/elements/options.d.ts +0 -21
- package/dist/pages/elements/options.js +0 -629
- package/dist/pages/elements/pages.d.ts +0 -24
- package/dist/pages/elements/pages.js +0 -355
- package/dist/pages/elements/slot.d.ts +0 -21
- package/dist/pages/elements/slot.js +0 -105
- package/dist/pages/elements/table.d.ts +0 -19
- package/dist/pages/elements/table.js +0 -935
- package/dist/pages/elements/text.d.ts +0 -25
- package/dist/pages/elements/text.js +0 -968
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -158
- package/dist/pages/host.js +0 -182
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -1,21 +1,18 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { join } from 'node:path';
|
|
10
5
|
import { randomBytes } from 'node:crypto';
|
|
11
|
-
import { rmSync } from 'node:fs';
|
|
12
|
-
import {
|
|
6
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { resolvePageComponents } from '../../core/config.js';
|
|
13
9
|
import { markStopSignal } from '../../core/runtime/stop-signals.js';
|
|
14
|
-
import {
|
|
10
|
+
import { bindTicketReplyRoute } from '../../core/human/convention.js';
|
|
15
11
|
import { ticketDir } from '../../core/human/root.js';
|
|
16
|
-
import { preparePage, publishPage } from '../../core/human/tickets.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
12
|
+
import { preparePage, publishPage, readTicketResult } from '../../core/human/tickets.js';
|
|
13
|
+
import { PageAuthoringError, parsePage } from '../../core/human/page.js';
|
|
14
|
+
import { pageManifestPath } from '../../core/human/convention.js';
|
|
15
|
+
import { isResponseBearingSlot, validateAuthoredPageManifest } from '../../core/human/page-schema.js';
|
|
19
16
|
import { display } from '../../core/termrender/display.js';
|
|
20
17
|
import { pageHelpText, resolveMaxPanes } from './shared.js';
|
|
21
18
|
const PAGE_HELP = pageHelpText();
|
|
@@ -26,76 +23,63 @@ function askingNode() {
|
|
|
26
23
|
/** Provenance for crouter tickets. Ticket *identity* comes from the bridge node
|
|
27
24
|
* directory, never ticket data; `nodeId` carries the originating node so a
|
|
28
25
|
* consumer can relate a ticket back to whatever that node belongs to, and
|
|
29
|
-
* `sessionName` is a presentation label only
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
26
|
+
* `sessionName` is a presentation label only, while `profileName` lets product
|
|
27
|
+
* consumers resolve the applet or worker identity behind the ticket. Review
|
|
28
|
+
* tickets stamp the same provenance (`core/review/realize.ts`), so pages match
|
|
29
|
+
* them. `nodeId` is stamped even when the daemon is unreachable — losing the
|
|
30
|
+
* labels is survivable, losing machine attribution is not.
|
|
31
|
+
*
|
|
32
|
+
* The same read answers who is sending: `lifecycle` decides whether the sender
|
|
33
|
+
* has a transcript a human is plausibly reading, which is what makes skipping
|
|
34
|
+
* the inbox safe. It is null for a bare shell (no node at all) and when the
|
|
35
|
+
* daemon cannot answer — neither is a terminal sender, so neither restricts
|
|
36
|
+
* delivery. */
|
|
37
|
+
export async function sender() {
|
|
34
38
|
const nodeId = askingNode();
|
|
35
39
|
if (nodeId === null || nodeId === '')
|
|
36
|
-
return {};
|
|
40
|
+
return { source: {}, lifecycle: null };
|
|
37
41
|
try {
|
|
38
|
-
const
|
|
39
|
-
|
|
42
|
+
const client = cliClient();
|
|
43
|
+
const node = await client.getNode(nodeId);
|
|
44
|
+
const sessionName = node.name.trim();
|
|
45
|
+
const profileName = node.profile_id === null
|
|
46
|
+
? undefined
|
|
47
|
+
: (await client.getProfile(node.profile_id)).name;
|
|
48
|
+
return {
|
|
49
|
+
source: {
|
|
50
|
+
nodeId,
|
|
51
|
+
...(sessionName === '' ? {} : { sessionName }),
|
|
52
|
+
...(profileName === undefined ? {} : { profileName }),
|
|
53
|
+
},
|
|
54
|
+
lifecycle: node.lifecycle,
|
|
55
|
+
};
|
|
40
56
|
}
|
|
41
57
|
catch {
|
|
42
|
-
return { nodeId };
|
|
58
|
+
return { source: { nodeId }, lifecycle: null };
|
|
43
59
|
}
|
|
44
60
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
return title;
|
|
48
|
-
throw new InputError({
|
|
49
|
-
error: 'missing_parameter',
|
|
50
|
-
message: 'stdin questions require a non-empty --title',
|
|
51
|
-
field: 'title',
|
|
52
|
-
next: 'Retry with a short --title naming the topic being decided.',
|
|
53
|
-
});
|
|
61
|
+
function pageProductKinds() {
|
|
62
|
+
return resolvePageComponents();
|
|
54
63
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
field: 'page',
|
|
75
|
-
next: 'Use a Markdown page (.md) or an HTML page (.html or .htm).',
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
function pageProductKinds(dialect) {
|
|
79
|
-
const config = readConfig('user');
|
|
80
|
-
if (dialect === 'html' && !config.html_pages) {
|
|
81
|
-
throw new InputError({
|
|
82
|
-
error: 'page_dialect_disabled',
|
|
83
|
-
message: 'html pages are not enabled in this home; md is the dialect to use',
|
|
84
|
-
field: 'page',
|
|
85
|
-
next: 'Use a .md page, or enable html_pages in the user scope config before submitting HTML.',
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
return config.page_components;
|
|
89
|
-
}
|
|
90
|
-
function requireNotifyTitle(title) {
|
|
91
|
-
if (title !== undefined && title.trim() !== '')
|
|
92
|
-
return title.trim();
|
|
93
|
-
throw new InputError({
|
|
94
|
-
error: 'missing_parameter',
|
|
95
|
-
message: 'notify shorthand requires a non-empty title',
|
|
96
|
-
field: 'title',
|
|
97
|
-
next: 'Retry with a positional title, --subtitle, and optional --body, or submit a page with --page PATH.',
|
|
98
|
-
});
|
|
64
|
+
/** Delivery is two authored choices and one derived fact. Placement and inbox
|
|
65
|
+
* are the sender's to pick; `reply` is not a flag, because a page routes
|
|
66
|
+
* completion back exactly when it carries response-bearing components — the
|
|
67
|
+
* page's own shape decides it, and a sender who disagreed could only be wrong
|
|
68
|
+
* (inputs with no way to submit, or a bridge no response can settle).
|
|
69
|
+
*
|
|
70
|
+
* Skipping the inbox leaves the sender's own transcript as the page's only
|
|
71
|
+
* route to a human, so a terminal sender — a node that finishes, whose
|
|
72
|
+
* transcript nobody is watching — always projects to the inbox. */
|
|
73
|
+
function deliveryFrom(input, responseBearing, senderLifecycle) {
|
|
74
|
+
const inbox = senderLifecycle === 'terminal'
|
|
75
|
+
? true
|
|
76
|
+
: input['inbox'] === true
|
|
77
|
+
? true
|
|
78
|
+
: input['noInbox'] === true
|
|
79
|
+
? false
|
|
80
|
+
: responseBearing;
|
|
81
|
+
const placement = input['placement'] ?? 'inline';
|
|
82
|
+
return { placement, inbox, reply: responseBearing };
|
|
99
83
|
}
|
|
100
84
|
function submitAuthoredPage(submit) {
|
|
101
85
|
try {
|
|
@@ -108,175 +92,182 @@ function submitAuthoredPage(submit) {
|
|
|
108
92
|
error: 'invalid_page',
|
|
109
93
|
message: error.message,
|
|
110
94
|
field: 'page',
|
|
111
|
-
next: 'Fix the authored page
|
|
95
|
+
next: 'Fix the authored page and submit again; the reported location is in the authored `.tsx` source.',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** The page a `--replace` names, read for the one thing a revision inherits. */
|
|
100
|
+
function replacedPage(ticketId) {
|
|
101
|
+
const dir = ticketDir(ticketId);
|
|
102
|
+
if (!existsSync(pageManifestPath(dir))) {
|
|
103
|
+
throw new InputError({
|
|
104
|
+
error: 'unknown_page',
|
|
105
|
+
message: `no page ${ticketId} on this canvas`,
|
|
106
|
+
field: 'replace',
|
|
107
|
+
next: 'Pass the page_id a previous send returned, or omit --replace to raise a new page.',
|
|
112
108
|
});
|
|
113
109
|
}
|
|
110
|
+
return parsePage(dir);
|
|
111
|
+
}
|
|
112
|
+
function writePipedPage(document) {
|
|
113
|
+
const dir = mkdtempSync(join(tmpdir(), 'crtr-human-page-'));
|
|
114
|
+
const path = join(dir, 'page.tsx');
|
|
115
|
+
writeFileSync(path, document, { encoding: 'utf8', mode: 0o600 });
|
|
116
|
+
return { dir, path };
|
|
117
|
+
}
|
|
118
|
+
/** Resolve exactly one authored page input. Piped bytes are materialized so
|
|
119
|
+
* both input forms follow the same page preparation and validation path. */
|
|
120
|
+
function pageInput(input) {
|
|
121
|
+
const file = input['page'];
|
|
122
|
+
const documentRaw = input['document'];
|
|
123
|
+
// Optional stdin arrives as an empty string when a caller redirects an empty
|
|
124
|
+
// stream. It is not a page source and must not conflict with --page.
|
|
125
|
+
const document = typeof documentRaw === 'string' && documentRaw.trim() !== '' ? documentRaw : undefined;
|
|
126
|
+
if (file !== undefined && document !== undefined) {
|
|
127
|
+
throw new InputError({
|
|
128
|
+
error: 'bad_invocation',
|
|
129
|
+
message: 'received both --page and a piped page document',
|
|
130
|
+
field: 'page, document',
|
|
131
|
+
next: 'Pass exactly one page source: --page PATH or the document on stdin.',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (file === undefined && document === undefined) {
|
|
135
|
+
throw new InputError({
|
|
136
|
+
error: 'missing_parameter',
|
|
137
|
+
message: 'a page source is required',
|
|
138
|
+
field: 'page, document',
|
|
139
|
+
next: 'Pass exactly one page source: --page PATH or the document on stdin.',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (file !== undefined) {
|
|
143
|
+
if (typeof file !== 'string')
|
|
144
|
+
throw new InputError({ error: 'invalid_type', message: '--page must be a path', field: 'page', next: 'Retry with --page PATH.' });
|
|
145
|
+
return { sourceFile: file };
|
|
146
|
+
}
|
|
147
|
+
if (typeof document !== 'string')
|
|
148
|
+
throw new InputError({ error: 'invalid_type', message: 'stdin must contain a page document', field: 'document', next: 'Pipe the page document on stdin.' });
|
|
149
|
+
const temp = writePipedPage(document);
|
|
150
|
+
return { sourceFile: temp.path, tempDir: temp.dir };
|
|
114
151
|
}
|
|
115
152
|
/** The non-blocking status peek returned by ask/review. How the human reaches
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
153
|
+
* their inbox is theirs, not the agent's — naming a shortcut here only teaches
|
|
154
|
+
* agents to repeat a screen-specific gesture back to a reader who may be on a
|
|
155
|
+
* different surface entirely. */
|
|
119
156
|
function queuedFollowUp() {
|
|
120
|
-
return 'Queued for the
|
|
157
|
+
return 'Queued for the user. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block.';
|
|
121
158
|
}
|
|
122
159
|
// ---------------------------------------------------------------------------
|
|
123
|
-
//
|
|
160
|
+
// send
|
|
124
161
|
// ---------------------------------------------------------------------------
|
|
125
|
-
export const
|
|
126
|
-
name: '
|
|
127
|
-
description: '
|
|
128
|
-
whenToUse: 'you
|
|
162
|
+
export const humanSend = defineLeaf({
|
|
163
|
+
name: 'send',
|
|
164
|
+
description: 'send a durable page with explicit delivery',
|
|
165
|
+
whenToUse: 'you need to put structured TSX or HTML content in a conversation and optionally project it to the inbox; a page carrying response-bearing components routes completion back to this node.',
|
|
129
166
|
help: {
|
|
130
|
-
name: 'human
|
|
131
|
-
summary:
|
|
132
|
-
guide:
|
|
133
|
-
'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`.',
|
|
134
|
-
PAGE_HELP.authoring,
|
|
135
|
-
].join('\n\n'),
|
|
167
|
+
name: 'human send',
|
|
168
|
+
summary: 'send a durable TSX or HTML page',
|
|
169
|
+
guide: 'Page authoring: `crtr human -h`.',
|
|
136
170
|
params: [
|
|
137
|
-
{ kind: '
|
|
138
|
-
{ kind: 'flag', name: '
|
|
139
|
-
{ kind: '
|
|
140
|
-
{ kind: 'flag', name: '
|
|
171
|
+
{ kind: 'stdin', name: 'document', required: false, allowPositional: false, constraint: 'The page module source (TSX) on stdin. Provide exactly one of stdin or --page PATH. HTML is file-authored.' },
|
|
172
|
+
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} A .tsx file is JSX; .html/.htm is complete HTML. Provide exactly one of --page or stdin.` },
|
|
173
|
+
{ kind: 'flag', name: 'placement', type: 'enum', choices: ['inline', 'panel'], required: false, default: 'inline', constraint: "Where the page is delivered. inline renders in this conversation's transcript; panel opens from the inbox beside it." },
|
|
174
|
+
{ kind: 'flag', name: 'inbox', type: 'bool', required: false, constraint: 'Project this page into the inbox. Mutually exclusive with --no-inbox.' },
|
|
175
|
+
{ kind: 'flag', name: 'no-inbox', type: 'bool', required: false, constraint: "Do not project this page into the inbox, leaving this conversation's transcript as its only route to the user. Mutually exclusive with --inbox, and refused from a terminal node, whose transcript nobody reads once it finishes." },
|
|
176
|
+
{ kind: 'flag', name: 'replace', type: 'string', required: false, constraint: 'Page id returned by an earlier send. Rewrites source in place while retaining its delivery and transcript anchor. Refused once the page is settled or canceled. Not combinable with delivery flags.' },
|
|
141
177
|
],
|
|
142
178
|
output: [
|
|
143
|
-
{ name: '
|
|
144
|
-
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.
|
|
145
|
-
{ name: '
|
|
179
|
+
{ name: 'page_id', type: 'string', required: true, constraint: 'Identifier for the page; pass it as --replace to revise source in place.' },
|
|
180
|
+
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.tsx or page.html, optional page.js, page.json, and response lifecycle files when delivery needs them.' },
|
|
181
|
+
{ name: 'placement', type: 'inline | panel', required: true, constraint: 'Resolved page placement.' },
|
|
182
|
+
{ name: 'inbox', type: 'boolean', required: true, constraint: 'Whether the page is projected into the inbox.' },
|
|
183
|
+
{ name: 'reply', type: 'boolean', required: true, constraint: 'Whether completion is routed back through a human bridge. Not a flag: true exactly when the page has response-bearing components.' },
|
|
184
|
+
{ name: 'follow_up', type: 'string', required: true, constraint: 'What happens next; never instructs the caller to poll.' },
|
|
146
185
|
],
|
|
147
186
|
outputKind: 'object',
|
|
148
187
|
effects: [
|
|
149
|
-
'
|
|
150
|
-
'
|
|
188
|
+
'Writes one durable page record. Inbox projection follows the delivery flags, except from a terminal node, which always projects; reply routing follows the page\'s own components.',
|
|
189
|
+
"A display-only page's inbox entry is a notice, not a task: it clears itself once the person has actually seen the page, wherever they see it.",
|
|
190
|
+
'A reply-bearing page creates a kind:"human" bridge under the caller and routes completion back.',
|
|
191
|
+
'--replace rewrites source in place while retaining delivery and emitted time; open surfaces reload it without creating another page.',
|
|
151
192
|
],
|
|
152
193
|
},
|
|
153
|
-
run: async (input) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const question = typeof questionRaw === 'string' && questionRaw !== '' ? questionRaw : undefined;
|
|
160
|
-
const subtitleRaw = input['subtitle'];
|
|
161
|
-
const subtitle = typeof subtitleRaw === 'string' && subtitleRaw.trim() !== '' ? subtitleRaw.trim() : undefined;
|
|
162
|
-
const pageInput = input['page'];
|
|
163
|
-
if (pageInput !== undefined && (question !== undefined || titleRaw !== undefined || subtitleRaw !== undefined)) {
|
|
164
|
-
throw new InputError({
|
|
165
|
-
error: 'bad_invocation',
|
|
166
|
-
message: 'use --title and --subtitle with stdin question text, or --page, not both',
|
|
167
|
-
next: 'Retry with --title, --subtitle, and a piped question body, or with --page PATH alone.',
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (question === undefined && pageInput === undefined) {
|
|
171
|
-
throw new InputError({
|
|
172
|
-
error: 'missing_parameter',
|
|
173
|
-
message: 'human ask needs stdin question text or --page PATH',
|
|
174
|
-
next: 'Pipe a question body with --title and --subtitle, or pass --page PATH for an authored page.',
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
const dialect = pageDialect(typeof pageInput === 'string' ? pageInput : 'page.md');
|
|
178
|
-
const productKinds = pageProductKinds(dialect);
|
|
179
|
-
let tempDir;
|
|
180
|
-
let sourceFile;
|
|
181
|
-
if (pageInput !== undefined) {
|
|
182
|
-
sourceFile = pageInput;
|
|
194
|
+
run: async (input, context) => {
|
|
195
|
+
const { sourceFile, tempDir } = pageInput(input);
|
|
196
|
+
const replacePage = input['replace'];
|
|
197
|
+
const deliveryFlags = ['placement', 'inbox', 'noInbox'];
|
|
198
|
+
if (input['inbox'] === true && input['noInbox'] === true) {
|
|
199
|
+
throw new InputError({ error: 'bad_invocation', message: 'received conflicting delivery flags', field: 'inbox', next: 'Pass at most one of --inbox and --no-inbox.' });
|
|
183
200
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const synthesized = writeSynthesizedPage(synthesizedPageDocument(simpleAsk.title, simpleAsk.subtitle, simpleAsk.question, true));
|
|
187
|
-
tempDir = synthesized.dir;
|
|
188
|
-
sourceFile = synthesized.path;
|
|
201
|
+
if (replacePage !== undefined && deliveryFlags.some((flag) => context?.providedParams.has(flag) === true)) {
|
|
202
|
+
throw new InputError({ error: 'bad_invocation', message: '--replace cannot change delivery', field: 'replace', next: 'Remove delivery flags: replacement retains the page placement, inbox, reply, and emitted time.' });
|
|
189
203
|
}
|
|
190
204
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
if (replacePage !== undefined) {
|
|
206
|
+
const prior = replacedPage(replacePage);
|
|
207
|
+
// The human's submit wins the race with a revision: once the ticket has
|
|
208
|
+
// any terminal outcome, replacement is refused, and that refusal is the
|
|
209
|
+
// reviser's signal that the ticket is closed.
|
|
210
|
+
if (readTicketResult(ticketDir(replacePage)) !== null) {
|
|
211
|
+
throw new InputError({
|
|
212
|
+
error: 'page_settled',
|
|
213
|
+
message: `page ${replacePage} already has a terminal outcome`,
|
|
214
|
+
field: 'replace',
|
|
215
|
+
next: 'The page was answered or canceled; nothing on it can change now. Raise a new page if more is needed.',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// A revision changes only the content. Identity, routing, and emitted
|
|
219
|
+
// time stay the original sender's, so the prior source is inherited
|
|
220
|
+
// verbatim — a fork revising the page must not repoint its provenance.
|
|
221
|
+
const revised = submitAuthoredPage(() => preparePage({ sourceFile, source: prior.source, productKinds: pageProductKinds(), delivery: prior.delivery }));
|
|
222
|
+
const { dir } = publishPage(ticketDir(replacePage), revised);
|
|
223
|
+
return { page_id: replacePage, dir, ...prior.delivery, follow_up: 'Replaced in place. Every open surface reloads it; delivery and emitted time are unchanged.' };
|
|
224
|
+
}
|
|
225
|
+
const { source, lifecycle } = await sender();
|
|
226
|
+
// A terminal node finishes and its transcript stops being read, so the
|
|
227
|
+
// inbox is the only surface its page can still reach a human on. Refuse
|
|
228
|
+
// rather than quietly overriding the flag: the caller asked for a page
|
|
229
|
+
// nobody would ever see, and that is worth knowing.
|
|
230
|
+
if (lifecycle === 'terminal' && input['noInbox'] === true) {
|
|
231
|
+
throw new InputError({
|
|
232
|
+
error: 'invalid_page_delivery',
|
|
233
|
+
message: 'a terminal node cannot send a page --no-inbox',
|
|
234
|
+
field: 'inbox',
|
|
235
|
+
next: 'Drop --no-inbox. This node finishes, and nobody reads a finished node\'s transcript, so a page kept out of the inbox reaches no one.',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// Static extraction is an evaluation. Derive and validate delivery from this
|
|
239
|
+
// exact manifest so authored code runs once and cannot change between passes.
|
|
240
|
+
const productKinds = pageProductKinds();
|
|
241
|
+
const prepared = submitAuthoredPage(() => preparePage({ sourceFile, source, productKinds }));
|
|
242
|
+
const responseBearing = prepared.manifest.slots.some(isResponseBearingSlot);
|
|
243
|
+
const delivery = deliveryFrom(input, responseBearing, lifecycle);
|
|
244
|
+
prepared.manifest = submitAuthoredPage(() => validateAuthoredPageManifest({ ...prepared.manifest, delivery }, productKinds));
|
|
245
|
+
if (!delivery.reply) {
|
|
246
|
+
const pageId = `page-${randomBytes(4).toString('hex')}`;
|
|
247
|
+
const { dir } = publishPage(ticketDir(pageId), prepared);
|
|
248
|
+
// A page with no reply route carries no response-bearing components, so what the
|
|
249
|
+
// follow-up must get right is where the person will meet it and how it
|
|
250
|
+
// settles: an inline page is already in this conversation, and its
|
|
251
|
+
// inbox entry is a notice that clears itself the moment the page has
|
|
252
|
+
// actually been seen — an agent that points the person at their inbox
|
|
253
|
+
// for a page on their screen sends them to an empty list.
|
|
254
|
+
const followUp = delivery.placement === 'inline' && delivery.inbox
|
|
255
|
+
? 'Shown in this conversation and queued in the user\'s inbox. The inbox entry clears itself the user has seen the page — someone viewing this conversation acknowledges it on sight, so do not send them to their inbox for it. They already know this, do not inform them.'
|
|
256
|
+
: delivery.placement === 'inline'
|
|
257
|
+
? 'Shown in this conversation as passive page content with no inbox entry.'
|
|
258
|
+
: delivery.inbox
|
|
259
|
+
? 'Queued in the inbox; it opens in the panel and waits until dismissed.'
|
|
260
|
+
: 'Published as passive page content with no inbox or response lifecycle.';
|
|
261
|
+
return { page_id: pageId, dir, ...delivery, follow_up: followUp };
|
|
262
|
+
}
|
|
202
263
|
const originNodeId = askingNode();
|
|
264
|
+
const bridgeNodeId = (await cliClient().createHumanBridge({ kind: 'human', parent: originNodeId, cwd: process.cwd(), name: 'human-page' })).node_id;
|
|
265
|
+
const idir = ticketDir(bridgeNodeId);
|
|
266
|
+
bindTicketReplyRoute(idir, bridgeNodeId);
|
|
267
|
+
const { dir } = publishPage(idir, prepared);
|
|
203
268
|
if (originNodeId !== null && originNodeId !== '')
|
|
204
|
-
markStopSignal(originNodeId, '
|
|
205
|
-
return {
|
|
206
|
-
}
|
|
207
|
-
finally {
|
|
208
|
-
if (tempDir !== undefined)
|
|
209
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// notify (no job)
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
export const humanNotify = defineLeaf({
|
|
217
|
-
name: 'notify',
|
|
218
|
-
description: 'fire-and-forget acknowledgement, no reply expected',
|
|
219
|
-
whenToUse: 'informing a person without blocking or expecting an answer',
|
|
220
|
-
help: {
|
|
221
|
-
name: 'human notify',
|
|
222
|
-
summary: 'enqueue a fire-and-forget acknowledgement in the human inbox; creates no job',
|
|
223
|
-
guide: [
|
|
224
|
-
'State what happened in plain words. Shorthand bodies are directive-flavored Markdown; validate authored pages with `crtr human doc check`.',
|
|
225
|
-
PAGE_HELP.authoring,
|
|
226
|
-
].join('\n\n'),
|
|
227
|
-
params: [
|
|
228
|
-
{ kind: 'positional', name: 'title', type: 'string', required: false, constraint: 'Notification headline for shorthand; required with --subtitle when --page is omitted.' },
|
|
229
|
-
{ kind: 'flag', name: 'subtitle', type: 'string', required: false, constraint: 'One plain-English sentence stating the notification status and stakes; required when --page is omitted.' },
|
|
230
|
-
{ kind: 'flag', name: 'body', type: 'string', required: false, constraint: 'Optional markdown body for shorthand; mutually exclusive with --page.' },
|
|
231
|
-
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Mutually exclusive with --body.` },
|
|
232
|
-
],
|
|
233
|
-
output: [
|
|
234
|
-
{ name: 'queued', type: 'boolean', required: true, constraint: 'Always true — the notify page is durable inbox state the human acknowledges from the inbox.' },
|
|
235
|
-
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.md or page.html, page.json, and run.json.' },
|
|
236
|
-
],
|
|
237
|
-
outputKind: 'object',
|
|
238
|
-
effects: [
|
|
239
|
-
'Enqueues a durable display page in the human inbox. Creates no node; nothing opens on screen.',
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
run: async (input) => {
|
|
243
|
-
const titleRaw = input['title'];
|
|
244
|
-
const title = typeof titleRaw === 'string' && titleRaw.trim() !== '' ? titleRaw.trim() : undefined;
|
|
245
|
-
const subtitleRaw = input['subtitle'];
|
|
246
|
-
const subtitle = typeof subtitleRaw === 'string' && subtitleRaw.trim() !== '' ? subtitleRaw.trim() : undefined;
|
|
247
|
-
const body = input['body'];
|
|
248
|
-
const pageInput = input['page'];
|
|
249
|
-
if (pageInput !== undefined && body !== undefined) {
|
|
250
|
-
throw new InputError({
|
|
251
|
-
error: 'bad_invocation',
|
|
252
|
-
message: 'use --page or --body, not both',
|
|
253
|
-
next: 'Retry with --page PATH alone for an authored page, or use the title, --subtitle, and --body shorthand.',
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
const dialect = pageDialect(typeof pageInput === 'string' ? pageInput : 'page.md');
|
|
257
|
-
const productKinds = pageProductKinds(dialect);
|
|
258
|
-
let tempDir;
|
|
259
|
-
let sourceFile;
|
|
260
|
-
if (pageInput !== undefined) {
|
|
261
|
-
sourceFile = pageInput;
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
const shorthandTitle = requireNotifyTitle(title);
|
|
265
|
-
const shorthandSubtitle = requireSubtitle(subtitle);
|
|
266
|
-
const synthesized = writeSynthesizedPage(synthesizedPageDocument(shorthandTitle, shorthandSubtitle, body ?? '', false));
|
|
267
|
-
tempDir = synthesized.dir;
|
|
268
|
-
sourceFile = synthesized.path;
|
|
269
|
-
}
|
|
270
|
-
try {
|
|
271
|
-
const source = await sourceStamp();
|
|
272
|
-
// Validate before the ticket directory exists, so a rejected page leaves
|
|
273
|
-
// no half-built ticket behind.
|
|
274
|
-
const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect, source, productKinds }));
|
|
275
|
-
const id = `nfy-${randomBytes(4).toString('hex')}`;
|
|
276
|
-
const rc = { mode: 'notify' };
|
|
277
|
-
atomicWriteJson(join(ticketDir(id), 'run.json'), rc);
|
|
278
|
-
const { dir } = publishPage(ticketDir(id), prepared);
|
|
279
|
-
return { queued: true, dir };
|
|
269
|
+
markStopSignal(originNodeId, 'requestedHumanReply');
|
|
270
|
+
return { page_id: bridgeNodeId, dir, ...delivery, follow_up: queuedFollowUp() };
|
|
280
271
|
}
|
|
281
272
|
finally {
|
|
282
273
|
if (tempDir !== undefined)
|
|
@@ -286,17 +277,17 @@ export const humanNotify = defineLeaf({
|
|
|
286
277
|
});
|
|
287
278
|
// ---------------------------------------------------------------------------
|
|
288
279
|
// show (no job, non-blocking passthrough) — passive live document display, NOT
|
|
289
|
-
// a ticket surface.
|
|
290
|
-
//
|
|
280
|
+
// a ticket surface. `display()` puts a file live in a watch pane and never
|
|
281
|
+
// enqueues anything.
|
|
291
282
|
// ---------------------------------------------------------------------------
|
|
292
283
|
export const humanShow = defineLeaf({
|
|
293
284
|
name: 'show',
|
|
294
|
-
description: "put a file live on the
|
|
295
|
-
whenToUse: 'displaying a doc on screen while
|
|
285
|
+
description: "put a file live on the user's screen",
|
|
286
|
+
whenToUse: 'displaying a doc on screen while the user comments',
|
|
296
287
|
help: {
|
|
297
288
|
name: 'human show',
|
|
298
289
|
summary: "put a file live on screen in a tmux pane with crouter's renderer",
|
|
299
|
-
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)
|
|
290
|
+
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 user as a durable inbox page, use `crtr human send --page PATH`; a page with no response-bearing components is a standalone announcement.',
|
|
300
291
|
params: [
|
|
301
292
|
{ kind: 'positional', name: 'path', type: 'path', required: true, constraint: 'Path to the file to render.' },
|
|
302
293
|
{ kind: 'flag', name: 'window', type: 'enum', choices: ['auto', 'split', 'new'], required: false, default: 'auto', constraint: 'Placement. Default auto.' },
|