@north-light/crouter 0.3.198 → 0.3.200
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 +18 -18
- 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 +81 -34
- 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 +6 -6
- 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 +2 -2
- 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/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +26 -1
- package/dist/clients/attach/render/chat-view.js +61 -11
- 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/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -993
- 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 -21
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +1 -3
- 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 +71 -27
- package/dist/clients/inbox/tui/slots.d.ts +3 -1
- package/dist/clients/inbox/tui/slots.js +16 -12
- package/dist/clients/inbox/tui/types.d.ts +2 -7
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +71 -114
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- 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/{html.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 +13 -5
- package/dist/commands/human/prompts.js +160 -97
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +2 -12
- package/dist/commands/human/shared.js +17 -11
- package/dist/commands/human.js +29 -21
- 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__/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__/helpers/harness.js +17 -55
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +31 -26
- 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 +4 -5
- package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
- 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/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +37 -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 +2 -1
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +137 -4
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.js +4 -0
- package/dist/core/human/__tests__/page-tickets.test.js +65 -52
- package/dist/core/human/__tests__/page.test.js +56 -98
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
- package/dist/core/human/answer-text.js +3 -3
- package/dist/core/human/answer.d.ts +6 -4
- package/dist/core/human/answer.js +4 -10
- package/dist/core/human/component-docs.d.ts +11 -7
- package/dist/core/human/component-docs.js +361 -88
- 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 +40 -5
- package/dist/core/human/page-catalog.js +102 -30
- 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 +23 -191
- package/dist/core/human/page-schema.js +60 -89
- package/dist/core/human/page-synth.d.ts +1 -1
- package/dist/core/human/page-synth.js +15 -18
- package/dist/core/human/page.d.ts +12 -27
- package/dist/core/human/page.js +142 -474
- 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 -4
- package/dist/core/human/scan.js +16 -15
- package/dist/core/human/summary.d.ts +1 -2
- package/dist/core/human/summary.js +2 -2
- package/dist/core/human/tickets.d.ts +20 -14
- package/dist/core/human/tickets.js +91 -31
- package/dist/core/human/types.d.ts +10 -2
- 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 +3 -3
- 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/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 +17 -6
- 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/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 +21 -2
- package/dist/core/user-settings.js +32 -11
- 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 +39 -115
- 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 +14 -16
- package/dist/daemon/human/finish.d.ts +12 -7
- package/dist/daemon/human/finish.js +101 -38
- package/dist/daemon/human/sweep.js +5 -1
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +3 -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/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.js +19 -5
- 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 +44 -2
- package/dist/types.js +7 -2
- package/package.json +6 -5
- package/runtime.lock.json +5 -33
- 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/commands/human/doc.d.ts +0 -2
- package/dist/commands/human/doc.js +0 -91
- package/dist/commands/human/html.js +0 -81
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/html-markdown.test.js +0 -52
- package/dist/core/human/__tests__/page-render.test.js +0 -66
- package/dist/core/human/html-markdown.d.ts +0 -3
- package/dist/core/human/html-markdown.js +0 -288
- package/dist/core/human/page-render.d.ts +0 -4
- package/dist/core/human/page-render.js +0 -105
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1778
- package/dist/pages/comments.d.ts +0 -72
- package/dist/pages/comments.js +0 -176
- package/dist/pages/controls.d.ts +0 -98
- package/dist/pages/controls.js +0 -261
- package/dist/pages/elements/cards.d.ts +0 -28
- package/dist/pages/elements/cards.js +0 -915
- package/dist/pages/elements/chart.d.ts +0 -23
- package/dist/pages/elements/chart.js +0 -664
- package/dist/pages/elements/options.d.ts +0 -30
- package/dist/pages/elements/options.js +0 -742
- package/dist/pages/elements/pages.d.ts +0 -30
- package/dist/pages/elements/pages.js +0 -377
- package/dist/pages/elements/slot.d.ts +0 -26
- package/dist/pages/elements/slot.js +0 -112
- package/dist/pages/elements/table.d.ts +0 -36
- package/dist/pages/elements/table.js +0 -1043
- package/dist/pages/elements/text.d.ts +0 -31
- package/dist/pages/elements/text.js +0 -1175
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -134
- package/dist/pages/host.js +0 -165
- package/dist/pages/readonly.d.ts +0 -24
- package/dist/pages/readonly.js +0 -31
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/responses.d.ts +0 -37
- package/dist/pages/responses.js +0 -56
- 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__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -15,7 +15,7 @@ import { renderTreeFromSource, renderForestFromSource, dashboardRowsFromSource,
|
|
|
15
15
|
export const dashboardLeaf = defineLeaf({
|
|
16
16
|
name: 'dashboard',
|
|
17
17
|
description: 'render the canvas as a subscription tree',
|
|
18
|
-
whenToUse: 'you want the whole graph at a glance as a rendered tree — the subscription forest drawn in ASCII so you can read its SHAPE: who reports to whom, how deep each branch runs, plus each node\'s status and context size. Scope to one root or show the full forest. Use `node inspect list` instead for a flat roster without the tree, `node inspect show` to drill into one node\'s neighbors, and `canvas attention` to find which nodes are blocked on
|
|
18
|
+
whenToUse: 'you want the whole graph at a glance as a rendered tree — the subscription forest drawn in ASCII so you can read its SHAPE: who reports to whom, how deep each branch runs, plus each node\'s status and context size. Scope to one root or show the full forest. Use `node inspect list` instead for a flat roster without the tree, `node inspect show` to drill into one node\'s neighbors, and `canvas attention` to find which nodes are blocked on the user',
|
|
19
19
|
help: {
|
|
20
20
|
name: 'canvas dashboard',
|
|
21
21
|
summary: 'render the canvas as an ASCII subscription tree — scoped to a root or the full forest',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type BranchDef } from '../../core/command.js';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const humanComponents: BranchDef;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
2
|
+
import { stateBlock } from '../../core/help.js';
|
|
3
|
+
import { InputError } from '../../core/io.js';
|
|
4
|
+
import { resolvePageComponents } from '../../core/config.js';
|
|
5
|
+
import { componentNames, renderComponentCatalog, renderComponentContract } from '../../core/human/component-docs.js';
|
|
6
|
+
function productComponents() {
|
|
7
|
+
return resolvePageComponents();
|
|
8
|
+
}
|
|
9
|
+
function componentNameState() {
|
|
10
|
+
const names = componentNames(productComponents());
|
|
11
|
+
return stateBlock('components', { count: names.length }, names.join(', '));
|
|
12
|
+
}
|
|
13
|
+
const humanComponentsList = defineLeaf({
|
|
14
|
+
name: 'list',
|
|
15
|
+
description: 'list page components for selection',
|
|
16
|
+
whenToUse: 'choosing which components to put in an authored page module.',
|
|
17
|
+
help: {
|
|
18
|
+
name: 'human components list',
|
|
19
|
+
summary: 'list page components for selection',
|
|
20
|
+
inputNote: 'No input parameters.',
|
|
21
|
+
output: [
|
|
22
|
+
{ name: 'catalog', type: 'markdown', required: true, constraint: 'One concise selection entry per registry and product-registered component, grouped structural/response/display; no props or response schemas. A product component is written with the PascalCase tag derived from its registered kind.' },
|
|
23
|
+
{ name: 'follow_up', type: 'string', required: true, constraint: 'Points to `crtr human components show <component>` for one selected component contract.' },
|
|
24
|
+
],
|
|
25
|
+
outputKind: 'object',
|
|
26
|
+
effects: ['None. Read-only.'],
|
|
27
|
+
},
|
|
28
|
+
run: async () => ({
|
|
29
|
+
catalog: renderComponentCatalog(productComponents()),
|
|
30
|
+
follow_up: 'Use `crtr human components show <component>` for one component contract.',
|
|
31
|
+
}),
|
|
32
|
+
render: (result) => `${String(result['catalog'])}\n\n${String(result['follow_up'])}`,
|
|
33
|
+
});
|
|
34
|
+
const humanComponentsShow = defineLeaf({
|
|
35
|
+
name: 'show',
|
|
36
|
+
description: 'show one page component contract',
|
|
37
|
+
whenToUse: 'authoring one selected page component and needing its exact props, response contract, and shape in a page module.',
|
|
38
|
+
help: {
|
|
39
|
+
name: 'human components show',
|
|
40
|
+
summary: 'show one page component contract',
|
|
41
|
+
params: [
|
|
42
|
+
{ kind: 'positional', name: 'component', type: 'string', required: true, constraint: 'Component name from the live <components> catalog below. Case-sensitive: registry components are PascalCase.' },
|
|
43
|
+
],
|
|
44
|
+
output: [
|
|
45
|
+
{ name: 'component', type: 'string', required: true, constraint: 'The selected component name.' },
|
|
46
|
+
{ name: 'contract', type: 'markdown', required: true, constraint: 'Hand-written props, response contract, and authoring example for that component. A shadcn family (Card, CardHeader, …) shares one contract covering every member.' },
|
|
47
|
+
],
|
|
48
|
+
outputKind: 'object',
|
|
49
|
+
effects: ['None. Read-only.'],
|
|
50
|
+
dynamicState: componentNameState,
|
|
51
|
+
},
|
|
52
|
+
run: async (input) => {
|
|
53
|
+
const component = input['component'];
|
|
54
|
+
const contract = renderComponentContract(component, productComponents());
|
|
55
|
+
if (contract === undefined) {
|
|
56
|
+
throw new InputError({
|
|
57
|
+
error: 'unknown_component',
|
|
58
|
+
message: `unknown page component "${component}"`,
|
|
59
|
+
field: 'component',
|
|
60
|
+
next: 'Run `crtr human components show -h` and choose a component from its live catalog.',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return { component, contract };
|
|
64
|
+
},
|
|
65
|
+
render: (result) => String(result['contract']),
|
|
66
|
+
});
|
|
67
|
+
export const humanComponents = defineBranch({
|
|
68
|
+
name: 'components',
|
|
69
|
+
description: 'discover page components and their contracts',
|
|
70
|
+
whenToUse: 'authoring a page module and choosing components, or needing one component’s exact props and response contract.',
|
|
71
|
+
help: {
|
|
72
|
+
name: 'human components',
|
|
73
|
+
summary: 'discover page components and their contracts',
|
|
74
|
+
model: 'These are the components already in scope in a page module — no import, ever. List is the selection catalog; show owns the full props, response contract, and authoring shape for one selected component.',
|
|
75
|
+
},
|
|
76
|
+
children: [humanComponentsList, humanComponentsShow],
|
|
77
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// `crtr human feedback` — the feedback companion's surface for a page ticket's
|
|
2
|
+
// feedback comments. list shows every item with its id and status; resolve is
|
|
3
|
+
// the companion's terminal report that an open item has been dealt with.
|
|
4
|
+
// Creating comments is the person's channel (the page surface); no agent
|
|
5
|
+
// authors, edits, or deletes them.
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { ApiError } from '../../api/index.js';
|
|
8
|
+
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
9
|
+
import { pageManifestPath } from '../../core/human/convention.js';
|
|
10
|
+
import { opaqueInboxTicketId, ticketDir } from '../../core/human/root.js';
|
|
11
|
+
import { InputError } from '../../core/io.js';
|
|
12
|
+
import { cliClient, rethrowAsCliError } from '../api-client.js';
|
|
13
|
+
/** Feedback tickets are addressed by the same raw ids as `human resolve`:
|
|
14
|
+
* the page_id from human send, which is also the ticket's directory name. */
|
|
15
|
+
function requirePageTicketId(raw) {
|
|
16
|
+
const ticketId = raw;
|
|
17
|
+
if (!existsSync(pageManifestPath(ticketDir(ticketId)))) {
|
|
18
|
+
throw new InputError({ error: 'not_found', message: `no page ticket: ${ticketId}`, field: 'ticket_id', next: 'Pass the ticket id from your bearings, or a page ticket_id from `crtr human list`.' });
|
|
19
|
+
}
|
|
20
|
+
return ticketId;
|
|
21
|
+
}
|
|
22
|
+
const humanFeedbackList = defineLeaf({
|
|
23
|
+
name: 'list',
|
|
24
|
+
description: "list a page ticket's feedback comments",
|
|
25
|
+
whenToUse: 'you are the feedback companion for a page ticket and need each comment\'s id and open/resolved status.',
|
|
26
|
+
help: {
|
|
27
|
+
name: 'human feedback list',
|
|
28
|
+
summary: "every feedback comment on one page ticket, with ids and open/resolved status",
|
|
29
|
+
params: [
|
|
30
|
+
{ kind: 'positional', name: 'ticket_id', type: 'string', required: true, constraint: 'The page ticket id from your bearings (also the page_id from human send).' },
|
|
31
|
+
],
|
|
32
|
+
output: [
|
|
33
|
+
{ name: 'ticket_id', type: 'string', required: true, constraint: 'The page ticket id.' },
|
|
34
|
+
{ name: 'open_count', type: 'integer', required: true, constraint: 'How many comments remain open. Zero once feedback exists means all of it has been dealt with.' },
|
|
35
|
+
{ name: 'comments', type: 'object[]', required: true, constraint: 'Each: {id, quote, context?, note, status, created_at, resolved_at?}; status is open or resolved. Oldest first.' },
|
|
36
|
+
],
|
|
37
|
+
outputKind: 'object',
|
|
38
|
+
effects: ['None. Read-only.'],
|
|
39
|
+
},
|
|
40
|
+
run: async (input) => {
|
|
41
|
+
const ticketId = requirePageTicketId(input['ticket_id']);
|
|
42
|
+
try {
|
|
43
|
+
const page = await cliClient().getHumanInboxPage(opaqueInboxTicketId(ticketId));
|
|
44
|
+
return { ticket_id: ticketId, open_count: page.feedback.open_count, comments: page.feedback.comments };
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
rethrowAsCliError(e);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const humanFeedbackResolve = defineLeaf({
|
|
52
|
+
name: 'resolve',
|
|
53
|
+
description: 'mark one dealt-with feedback comment resolved',
|
|
54
|
+
whenToUse: 'you are the feedback companion and have dealt with an open comment — by revising the page or answering it in conversation.',
|
|
55
|
+
help: {
|
|
56
|
+
name: 'human feedback resolve',
|
|
57
|
+
summary: 'mark one open feedback comment resolved — terminal, and only after the feedback has actually been dealt with',
|
|
58
|
+
guide: 'Resolution is your report that the work is done, not a triage state: revise the page or answer the comment in conversation first, then resolve. It appends no chat turn — briefly report the result in the conversation yourself. A resolved comment never reopens; a renewed concern arrives as a new comment.',
|
|
59
|
+
params: [
|
|
60
|
+
{ kind: 'positional', name: 'comment_id', type: 'string', required: true, constraint: 'A comment id from `crtr human feedback list`. The ticket is your own: it comes from the companion binding of the calling node.' },
|
|
61
|
+
],
|
|
62
|
+
output: [
|
|
63
|
+
{ name: 'resolved', type: 'boolean', required: true, constraint: 'True when this call resolved the comment; false when it was already resolved.' },
|
|
64
|
+
{ name: 'ticket_id', type: 'string', required: true, constraint: 'The page ticket id.' },
|
|
65
|
+
{ name: 'comment_id', type: 'string', required: true, constraint: 'The comment id.' },
|
|
66
|
+
{ name: 'open_count', type: 'integer', required: false, constraint: 'Comments still open after this resolution. Present when resolved is true.' },
|
|
67
|
+
{ name: 'reason', type: 'string', required: false, constraint: 'comment_already_resolved when resolved is false.' },
|
|
68
|
+
],
|
|
69
|
+
outputKind: 'object',
|
|
70
|
+
effects: ["Marks the comment resolved in your own ticket's feedback record — the ticket comes from the calling companion's binding. Only the ticket's bound companion may resolve — any other node refuses with not_companion. A terminal ticket refuses with ticket_terminal — settlement already closed this collaboration."],
|
|
71
|
+
},
|
|
72
|
+
run: async (input) => {
|
|
73
|
+
const commentId = input['comment_id'];
|
|
74
|
+
const callerNodeId = process.env['CRTR_NODE_ID'];
|
|
75
|
+
if (callerNodeId === undefined || callerNodeId === '') {
|
|
76
|
+
throw new InputError({ error: 'no_node', message: 'resolve runs inside the ticket\u2019s feedback companion (CRTR_NODE_ID unset)', next: 'Run this from the companion conversation bound to the ticket.' });
|
|
77
|
+
}
|
|
78
|
+
const binding = (await cliClient().getNode(callerNodeId)).review_binding;
|
|
79
|
+
if (binding === undefined || binding === null || binding.kind !== 'page_feedback') {
|
|
80
|
+
throw new InputError({ error: 'not_companion', message: 'this node is not a page-feedback companion — only the ticket\u2019s bound companion resolves its comments', next: 'Resolve from the companion conversation bound to the ticket.' });
|
|
81
|
+
}
|
|
82
|
+
const ticketId = requirePageTicketId(binding.review_id);
|
|
83
|
+
try {
|
|
84
|
+
const result = await cliClient().resolvePageFeedbackComment(opaqueInboxTicketId(ticketId), commentId, callerNodeId);
|
|
85
|
+
return { resolved: true, ticket_id: ticketId, comment_id: commentId, open_count: result.feedback.open_count };
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
if (e instanceof ApiError && e.status === 409 && e.code === 'comment_already_resolved') {
|
|
89
|
+
return { resolved: false, ticket_id: ticketId, comment_id: commentId, reason: 'comment_already_resolved' };
|
|
90
|
+
}
|
|
91
|
+
rethrowAsCliError(e);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
render: (r) => r['resolved'] === true
|
|
95
|
+
? `Resolved feedback comment ${r['comment_id']} — ${r['open_count']} still open on ${r['ticket_id']}.`
|
|
96
|
+
: `Nothing to resolve for ${r['comment_id']} — ${r['reason']}.`,
|
|
97
|
+
});
|
|
98
|
+
export const humanFeedbackBranch = defineBranch({
|
|
99
|
+
name: 'feedback',
|
|
100
|
+
description: "operate on a page ticket's feedback comments",
|
|
101
|
+
whenToUse: "you are a feedback companion working a page ticket's feedback comments.",
|
|
102
|
+
help: {
|
|
103
|
+
name: 'human feedback',
|
|
104
|
+
summary: "a page ticket's feedback comments — the person creates them on the page; the bound companion lists and resolves them",
|
|
105
|
+
model: 'Each comment is immutable once sent and open until resolved. resolve is terminal and companion-only, taken after the feedback has been dealt with by revising the page or answering in conversation.',
|
|
106
|
+
},
|
|
107
|
+
children: [humanFeedbackList, humanFeedbackResolve],
|
|
108
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
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
|
|
@@ -6,8 +6,16 @@ import type { PageSource } from '../../core/human/page-schema.js';
|
|
|
6
6
|
* consumers resolve the applet or worker identity behind the ticket. Review
|
|
7
7
|
* tickets stamp the same provenance (`core/review/realize.ts`), so pages match
|
|
8
8
|
* them. `nodeId` is stamped even when the daemon is unreachable — losing the
|
|
9
|
-
* labels is survivable, losing machine attribution is not.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
* labels is survivable, losing machine attribution is not.
|
|
10
|
+
*
|
|
11
|
+
* The same read answers who is sending: `lifecycle` decides whether the sender
|
|
12
|
+
* has a transcript a human is plausibly reading, which is what makes skipping
|
|
13
|
+
* the inbox safe. It is null for a bare shell (no node at all) and when the
|
|
14
|
+
* daemon cannot answer — neither is a terminal sender, so neither restricts
|
|
15
|
+
* delivery. */
|
|
16
|
+
export declare function sender(): Promise<{
|
|
17
|
+
source: PageSource;
|
|
18
|
+
lifecycle: 'terminal' | 'resident' | null;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const humanSend: import("../../core/command.js").LeafDef;
|
|
13
21
|
export declare const humanShow: import("../../core/command.js").LeafDef;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { defineLeaf } from '../../core/command.js';
|
|
2
2
|
import { InputError } from '../../core/io.js';
|
|
3
3
|
import { cliClient } from '../api-client.js';
|
|
4
|
-
import {
|
|
4
|
+
import { join } from 'node:path';
|
|
5
5
|
import { randomBytes } from 'node:crypto';
|
|
6
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
|
-
import {
|
|
8
|
+
import { resolvePageComponents } from '../../core/config.js';
|
|
9
9
|
import { markStopSignal } from '../../core/runtime/stop-signals.js';
|
|
10
|
-
import {
|
|
10
|
+
import { bindTicketReplyRoute } from '../../core/human/convention.js';
|
|
11
11
|
import { ticketDir } from '../../core/human/root.js';
|
|
12
|
-
import { preparePage, publishPage } from '../../core/human/tickets.js';
|
|
13
|
-
import { PageAuthoringError } from '../../core/human/page.js';
|
|
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';
|
|
14
16
|
import { display } from '../../core/termrender/display.js';
|
|
15
17
|
import { pageHelpText, resolveMaxPanes } from './shared.js';
|
|
16
18
|
const PAGE_HELP = pageHelpText();
|
|
@@ -25,11 +27,17 @@ function askingNode() {
|
|
|
25
27
|
* consumers resolve the applet or worker identity behind the ticket. Review
|
|
26
28
|
* tickets stamp the same provenance (`core/review/realize.ts`), so pages match
|
|
27
29
|
* them. `nodeId` is stamped even when the daemon is unreachable — losing the
|
|
28
|
-
* labels is survivable, losing machine attribution is not.
|
|
29
|
-
|
|
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() {
|
|
30
38
|
const nodeId = askingNode();
|
|
31
39
|
if (nodeId === null || nodeId === '')
|
|
32
|
-
return {};
|
|
40
|
+
return { source: {}, lifecycle: null };
|
|
33
41
|
try {
|
|
34
42
|
const client = cliClient();
|
|
35
43
|
const node = await client.getNode(nodeId);
|
|
@@ -38,28 +46,40 @@ export async function sourceStamp() {
|
|
|
38
46
|
? undefined
|
|
39
47
|
: (await client.getProfile(node.profile_id)).name;
|
|
40
48
|
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
source: {
|
|
50
|
+
nodeId,
|
|
51
|
+
...(sessionName === '' ? {} : { sessionName }),
|
|
52
|
+
...(profileName === undefined ? {} : { profileName }),
|
|
53
|
+
},
|
|
54
|
+
lifecycle: node.lifecycle,
|
|
44
55
|
};
|
|
45
56
|
}
|
|
46
57
|
catch {
|
|
47
|
-
return { nodeId };
|
|
58
|
+
return { source: { nodeId }, lifecycle: null };
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
|
-
function pageDialect(sourceFile) {
|
|
51
|
-
const extension = extname(sourceFile).toLowerCase();
|
|
52
|
-
if (extension === '.html' || extension === '.htm')
|
|
53
|
-
return 'html';
|
|
54
|
-
throw new InputError({
|
|
55
|
-
error: 'invalid_page_dialect',
|
|
56
|
-
message: `--page must name an HTML page; received "${sourceFile}"`,
|
|
57
|
-
field: 'page',
|
|
58
|
-
next: 'Write the page at `$CRTR_CONTEXT_DIR/pages/<name>.html`, then retry with --page PATH.',
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
61
|
function pageProductKinds() {
|
|
62
|
-
return
|
|
62
|
+
return resolvePageComponents();
|
|
63
|
+
}
|
|
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 };
|
|
63
83
|
}
|
|
64
84
|
function submitAuthoredPage(submit) {
|
|
65
85
|
try {
|
|
@@ -72,13 +92,26 @@ function submitAuthoredPage(submit) {
|
|
|
72
92
|
error: 'invalid_page',
|
|
73
93
|
message: error.message,
|
|
74
94
|
field: 'page',
|
|
75
|
-
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.',
|
|
76
108
|
});
|
|
77
109
|
}
|
|
110
|
+
return parsePage(dir);
|
|
78
111
|
}
|
|
79
112
|
function writePipedPage(document) {
|
|
80
113
|
const dir = mkdtempSync(join(tmpdir(), 'crtr-human-page-'));
|
|
81
|
-
const path = join(dir, 'page.
|
|
114
|
+
const path = join(dir, 'page.tsx');
|
|
82
115
|
writeFileSync(path, document, { encoding: 'utf8', mode: 0o600 });
|
|
83
116
|
return { dir, path };
|
|
84
117
|
}
|
|
@@ -121,90 +154,120 @@ function pageInput(input) {
|
|
|
121
154
|
* agents to repeat a screen-specific gesture back to a reader who may be on a
|
|
122
155
|
* different surface entirely. */
|
|
123
156
|
function queuedFollowUp() {
|
|
124
|
-
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.';
|
|
125
158
|
}
|
|
126
159
|
// ---------------------------------------------------------------------------
|
|
127
|
-
//
|
|
160
|
+
// send
|
|
128
161
|
// ---------------------------------------------------------------------------
|
|
129
|
-
export const
|
|
130
|
-
name: '
|
|
131
|
-
description: '
|
|
132
|
-
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.',
|
|
133
166
|
help: {
|
|
134
|
-
name: 'human
|
|
135
|
-
summary:
|
|
167
|
+
name: 'human send',
|
|
168
|
+
summary: 'send a durable TSX or HTML page',
|
|
136
169
|
guide: 'Page authoring: `crtr human -h`.',
|
|
137
170
|
params: [
|
|
138
|
-
{ kind: 'stdin', name: 'document', required: false, allowPositional: false, constraint: '
|
|
139
|
-
{ kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Provide exactly one of --page or stdin.` },
|
|
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.' },
|
|
140
177
|
],
|
|
141
178
|
output: [
|
|
142
|
-
{ name: '
|
|
143
|
-
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.html, page.
|
|
144
|
-
{ 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.' },
|
|
145
185
|
],
|
|
146
186
|
outputKind: 'object',
|
|
147
187
|
effects: [
|
|
148
|
-
'
|
|
149
|
-
'
|
|
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.',
|
|
150
192
|
],
|
|
151
193
|
},
|
|
152
|
-
run: async (input) => {
|
|
194
|
+
run: async (input, context) => {
|
|
153
195
|
const { sourceFile, tempDir } = pageInput(input);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const jobId = (await cliClient().createHumanBridge({ kind: 'human', parent: askingNode(), cwd, name: 'human-ask' })).node_id;
|
|
159
|
-
const idir = ticketDir(jobId);
|
|
160
|
-
const rc = { mode: 'ask', job_id: jobId };
|
|
161
|
-
atomicWriteJson(join(idir, 'run.json'), rc);
|
|
162
|
-
const { dir } = publishPage(ticketDir(jobId), prepared);
|
|
163
|
-
const originNodeId = askingNode();
|
|
164
|
-
if (originNodeId !== null && originNodeId !== '')
|
|
165
|
-
markStopSignal(originNodeId, 'askedHuman');
|
|
166
|
-
return { job_id: jobId, dir, follow_up: queuedFollowUp() };
|
|
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.' });
|
|
167
200
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
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.' });
|
|
171
203
|
}
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// notify (no job)
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
export const humanNotify = defineLeaf({
|
|
178
|
-
name: 'notify',
|
|
179
|
-
description: 'fire-and-forget acknowledgement, no reply expected',
|
|
180
|
-
whenToUse: 'informing a person without blocking or expecting an answer',
|
|
181
|
-
help: {
|
|
182
|
-
name: 'human notify',
|
|
183
|
-
summary: 'enqueue a fire-and-forget acknowledgement in the human inbox; creates no job',
|
|
184
|
-
guide: 'Page authoring: `crtr human -h`.',
|
|
185
|
-
params: [
|
|
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.` },
|
|
188
|
-
],
|
|
189
|
-
output: [
|
|
190
|
-
{ name: 'queued', type: 'boolean', required: true, constraint: 'Always true — the notify page is durable inbox state the human acknowledges from the inbox.' },
|
|
191
|
-
{ name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.html, page.json, and run.json.' },
|
|
192
|
-
],
|
|
193
|
-
outputKind: 'object',
|
|
194
|
-
effects: [
|
|
195
|
-
'Enqueues a durable display page in the human inbox. Creates no node; nothing opens on screen.',
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
run: async (input) => {
|
|
199
|
-
const { sourceFile, tempDir } = pageInput(input);
|
|
200
204
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
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);
|
|
268
|
+
if (originNodeId !== null && originNodeId !== '')
|
|
269
|
+
markStopSignal(originNodeId, 'requestedHumanReply');
|
|
270
|
+
return { page_id: bridgeNodeId, dir, ...delivery, follow_up: queuedFollowUp() };
|
|
208
271
|
}
|
|
209
272
|
finally {
|
|
210
273
|
if (tempDir !== undefined)
|
|
@@ -219,12 +282,12 @@ export const humanNotify = defineLeaf({
|
|
|
219
282
|
// ---------------------------------------------------------------------------
|
|
220
283
|
export const humanShow = defineLeaf({
|
|
221
284
|
name: 'show',
|
|
222
|
-
description: "put a file live on the
|
|
223
|
-
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',
|
|
224
287
|
help: {
|
|
225
288
|
name: 'human show',
|
|
226
289
|
summary: "put a file live on screen in a tmux pane with crouter's renderer",
|
|
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
|
|
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.',
|
|
228
291
|
params: [
|
|
229
292
|
{ kind: 'positional', name: 'path', type: 'path', required: true, constraint: 'Path to the file to render.' },
|
|
230
293
|
{ kind: 'flag', name: 'window', type: 'enum', choices: ['auto', 'split', 'new'], required: false, default: 'auto', constraint: 'Placement. Default auto.' },
|