@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
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { basename, join } from 'node:path';
|
|
3
2
|
import { ApiError } from '../../api/index.js';
|
|
4
3
|
import { defineLeaf } from '../../core/command.js';
|
|
5
4
|
import { ticketDir } from '../../core/human/root.js';
|
|
6
5
|
import { scanInbox } from '../../core/human/scan.js';
|
|
7
|
-
import { pageManifestPath, isResolved, reviewPath } from '../../core/human/convention.js';
|
|
6
|
+
import { pageManifestPath, isResolved, readTicketReplyRoute, reviewPath } from '../../core/human/convention.js';
|
|
8
7
|
import { InputError } from '../../core/io.js';
|
|
9
8
|
import { paginate } from '../../core/pagination.js';
|
|
10
9
|
import { cliClient, rethrowAsCliError } from '../api-client.js';
|
|
11
10
|
import { filterVisibleReviewTickets } from '../../clients/inbox/review-visibility.js';
|
|
12
|
-
import { readJsonOrNull } from '../../core/fs-utils.js';
|
|
13
11
|
function toRef(n) {
|
|
14
12
|
return { node_id: n.node_id, name: n.name, cwd: n.cwd, parent: n.parent, status: n.status };
|
|
15
13
|
}
|
|
@@ -20,17 +18,6 @@ function toRef(n) {
|
|
|
20
18
|
async function fetchNodeRefs() {
|
|
21
19
|
return (await cliClient().listNodes()).map(toRef);
|
|
22
20
|
}
|
|
23
|
-
/** Look up one node by id, mapping the API's 404 to `null`. */
|
|
24
|
-
async function getNodeOrNull(id) {
|
|
25
|
-
try {
|
|
26
|
-
return await cliClient().getNode(id);
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
if (err instanceof ApiError && err.status === 404)
|
|
30
|
-
return null;
|
|
31
|
-
rethrowAsCliError(err);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
21
|
function resolveConversation(asking, nodes) {
|
|
35
22
|
if (asking === undefined)
|
|
36
23
|
return undefined;
|
|
@@ -59,43 +46,41 @@ function askingNodeFor(jobId, byId) {
|
|
|
59
46
|
return byId.get(bridge.parent) ?? bridge;
|
|
60
47
|
return bridge;
|
|
61
48
|
}
|
|
62
|
-
/**
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return readJsonOrNull(join(dir, 'run.json'))?.mode === 'notify';
|
|
49
|
+
/** A missing reply route is the complete representation of a standalone page;
|
|
50
|
+
* a route whose bridge disappeared is an orphaned reply-bearing ticket. */
|
|
51
|
+
function replyBridgeId(dir) {
|
|
52
|
+
return readTicketReplyRoute(dir)?.bridge_node_id ?? null;
|
|
67
53
|
}
|
|
68
54
|
function summarizeTicket(summary, nodes, byId) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
// notify ticket never had one, so it renders with its node-derived fields
|
|
74
|
-
// omitted rather than being dropped as an orphan.
|
|
75
|
-
if (!workerless && !byId.has(jobId))
|
|
55
|
+
const bridgeNodeId = replyBridgeId(summary.dir);
|
|
56
|
+
// A reply-bearing ticket whose bridge was reaped is stale and absent from the
|
|
57
|
+
// canvas view. A standalone page has no bridge by construction.
|
|
58
|
+
if (bridgeNodeId !== null && !byId.has(bridgeNodeId))
|
|
76
59
|
return null;
|
|
77
|
-
const asking =
|
|
60
|
+
const asking = bridgeNodeId === null ? undefined : askingNodeFor(bridgeNodeId, byId);
|
|
78
61
|
const conversation = resolveConversation(asking, nodes);
|
|
79
62
|
const sourceLabel = readableSourceLabel(summary.source);
|
|
80
63
|
return {
|
|
81
|
-
|
|
82
|
-
...(
|
|
64
|
+
ticket_id: summary.id,
|
|
65
|
+
...(bridgeNodeId === null ? {} : { bridge_node_id: bridgeNodeId }),
|
|
83
66
|
dir: summary.dir,
|
|
84
67
|
title: summary.title,
|
|
85
68
|
subtitle: summary.subtitle,
|
|
86
69
|
kind: summary.kind,
|
|
87
|
-
blocked_since: summary.blockedSince,
|
|
70
|
+
...(summary.kind === 'page' ? { emitted_at: summary.emittedAt } : { blocked_since: summary.blockedSince }),
|
|
88
71
|
...(summary.claim !== undefined ? { claim_owner: summary.claim.owner } : {}),
|
|
89
72
|
...(sourceLabel !== undefined ? { source_label: sourceLabel } : {}),
|
|
90
73
|
...(asking !== undefined ? { asking_node_id: asking.node_id, asking_node_name: asking.name, cwd: asking.cwd } : {}),
|
|
91
74
|
...(conversation !== undefined ? { conversation_id: conversation.node_id, conversation_title: conversation.name } : {}),
|
|
92
75
|
};
|
|
93
76
|
}
|
|
94
|
-
function
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
77
|
+
function interactionDirForTicket(ticketId, byId) {
|
|
78
|
+
const dir = ticketDir(ticketId);
|
|
79
|
+
if (!existsSync(pageManifestPath(dir)) && !existsSync(reviewPath(dir))) {
|
|
80
|
+
throw new InputError({ error: 'not_found', message: `no interaction ticket: ${ticketId}`, field: 'ticket_id', next: 'Pass a ticket_id from `crtr human list`.' });
|
|
81
|
+
}
|
|
82
|
+
const bridgeNodeId = replyBridgeId(dir);
|
|
83
|
+
return { dir, bridge: bridgeNodeId === null ? null : byId.get(bridgeNodeId) ?? null };
|
|
99
84
|
}
|
|
100
85
|
function isTerminalBridge(status) {
|
|
101
86
|
return status === 'done' || status === 'dead' || status === 'canceled';
|
|
@@ -109,7 +94,7 @@ function isAlreadyResolvedConflict(error) {
|
|
|
109
94
|
export const humanList = defineLeaf({
|
|
110
95
|
name: 'list',
|
|
111
96
|
description: 'enumerate pending interactions',
|
|
112
|
-
whenToUse: 'discovering what is blocked on
|
|
97
|
+
whenToUse: 'discovering what is blocked on the user',
|
|
113
98
|
help: {
|
|
114
99
|
name: 'human list',
|
|
115
100
|
summary: 'paginated list of pending interactions, oldest first',
|
|
@@ -118,7 +103,7 @@ export const humanList = defineLeaf({
|
|
|
118
103
|
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: "Opaque token from a previous response's next_cursor. Omit on first call." },
|
|
119
104
|
],
|
|
120
105
|
output: [
|
|
121
|
-
{ name: 'items', type: 'object[]', required: true, constraint: 'Each: {
|
|
106
|
+
{ name: 'items', type: 'object[]', required: true, constraint: 'Each: {ticket_id, dir, title, subtitle, kind, emitted_at? | blocked_since?}; pages use emitted_at, reviews use blocked_since. Enriched when derivable with bridge_node_id, claim_owner, source_label, asking_node_id/name, conversation_id/title. Oldest first.' },
|
|
122
107
|
{ name: 'next_cursor', type: 'string | null', required: true, constraint: 'Pass on the next call to continue. null means no more items.' },
|
|
123
108
|
{ name: 'total', type: 'integer | null', required: true, constraint: 'Total pending interactions.' },
|
|
124
109
|
],
|
|
@@ -139,14 +124,14 @@ export const humanList = defineLeaf({
|
|
|
139
124
|
return item === null ? [] : [item];
|
|
140
125
|
})
|
|
141
126
|
.sort((a, b) => {
|
|
142
|
-
const ka = `${a.blocked_since}|${a.
|
|
143
|
-
const kb = `${b.blocked_since}|${b.
|
|
127
|
+
const ka = `${a.emitted_at ?? a.blocked_since}|${a.ticket_id}`;
|
|
128
|
+
const kb = `${b.emitted_at ?? b.blocked_since}|${b.ticket_id}`;
|
|
144
129
|
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
145
130
|
});
|
|
146
131
|
const page = paginate(items, { limit, cursor }, {
|
|
147
132
|
defaultLimit: 20,
|
|
148
133
|
maxLimit: 100,
|
|
149
|
-
keyOf: (i) => `${i.blocked_since}|${i.
|
|
134
|
+
keyOf: (i) => `${i.emitted_at ?? i.blocked_since}|${i.ticket_id}`,
|
|
150
135
|
total: 'count',
|
|
151
136
|
});
|
|
152
137
|
return { items: page.items, next_cursor: page.next_cursor, total: page.total };
|
|
@@ -162,11 +147,11 @@ function parseResolveBody(raw) {
|
|
|
162
147
|
parsed = JSON.parse(raw);
|
|
163
148
|
}
|
|
164
149
|
catch (e) {
|
|
165
|
-
throw new InputError({ error: 'invalid_json', message: `stdin is not valid JSON: ${String(e)}`, field: 'stdin', next: 'Pass {"responses":{"<
|
|
150
|
+
throw new InputError({ error: 'invalid_json', message: `stdin is not valid JSON: ${String(e)}`, field: 'stdin', next: 'Pass {"responses":{"<component_id>":{...}}} on stdin.' });
|
|
166
151
|
}
|
|
167
152
|
const responses = parsed.responses;
|
|
168
153
|
if (typeof responses !== 'object' || responses === null || Array.isArray(responses)) {
|
|
169
|
-
throw new InputError({ error: 'invalid_field', message: 'responses must be an object keyed by
|
|
154
|
+
throw new InputError({ error: 'invalid_field', message: 'responses must be an object keyed by the id of a response-bearing component', field: 'responses', next: 'Pass {"responses":{"<component_id>":{...}}}, or {"responses":{}} to acknowledge a page with no response-bearing components.' });
|
|
170
155
|
}
|
|
171
156
|
return responses;
|
|
172
157
|
}
|
|
@@ -177,101 +162,85 @@ export const humanResolve = defineLeaf({
|
|
|
177
162
|
tier: 'hidden',
|
|
178
163
|
help: {
|
|
179
164
|
name: 'human resolve',
|
|
180
|
-
summary: 'resolve a pending page with the given answers
|
|
165
|
+
summary: 'resolve a pending page with the given answers',
|
|
181
166
|
params: [
|
|
182
|
-
{ kind: 'positional', name: '
|
|
183
|
-
{ kind: 'stdin', name: 'body', required: true, constraint: 'JSON {responses:{<
|
|
167
|
+
{ kind: 'positional', name: 'ticket_id', type: 'string', required: true, constraint: 'page_id from human send, or a page ticket_id from human list.' },
|
|
168
|
+
{ kind: 'stdin', name: 'body', required: true, constraint: 'JSON {responses:{<component_id>:{...}}} — one typed response object per response-bearing component of the page.' },
|
|
184
169
|
],
|
|
185
170
|
output: [
|
|
186
|
-
{ name: 'resolved', type: 'boolean', required: true, constraint: 'True when this call
|
|
187
|
-
{ name: '
|
|
171
|
+
{ name: 'resolved', type: 'boolean', required: true, constraint: 'True when this call completed the page.' },
|
|
172
|
+
{ name: 'ticket_id', type: 'string', required: true, constraint: 'Interaction ticket id.' },
|
|
188
173
|
{ name: 'reason', type: 'string', required: false, constraint: 'already_resolved or claimed when resolved is false.' },
|
|
189
174
|
],
|
|
190
175
|
outputKind: 'object',
|
|
191
|
-
effects: ['The daemon publishes the answer,
|
|
176
|
+
effects: ['The daemon publishes the answer and, when the ticket has a reply route, retires its bridge and returns the completion to subscribers.'],
|
|
192
177
|
},
|
|
193
178
|
run: async (input) => {
|
|
194
|
-
const
|
|
179
|
+
const ticketId = input['ticket_id'];
|
|
195
180
|
const nodes = await fetchNodeRefs();
|
|
196
181
|
const byId = new Map(nodes.map((n) => [n.node_id, n]));
|
|
197
|
-
const { dir,
|
|
182
|
+
const { dir, bridge } = interactionDirForTicket(ticketId, byId);
|
|
198
183
|
if (!existsSync(pageManifestPath(dir))) {
|
|
199
|
-
throw new InputError({ error: 'not_found', message: `no page for interaction: ${
|
|
184
|
+
throw new InputError({ error: 'not_found', message: `no page for interaction: ${ticketId}`, field: 'ticket_id', next: 'Pass a pending ticket_id from `crtr human list`.' });
|
|
200
185
|
}
|
|
201
186
|
const responses = parseResolveBody(input['body']);
|
|
202
187
|
// A published result on a live bridge may be an interrupted delivery. The
|
|
203
|
-
// daemon re-drives that delivery before it returns its 409
|
|
204
|
-
//
|
|
205
|
-
if (isResolved(dir) && isTerminalBridge(
|
|
206
|
-
return { resolved: false,
|
|
188
|
+
// daemon re-drives that delivery before it returns its 409. A standalone
|
|
189
|
+
// ticket or terminal bridge has nothing left to deliver.
|
|
190
|
+
if (isResolved(dir) && (bridge === null || isTerminalBridge(bridge.status))) {
|
|
191
|
+
return { resolved: false, ticket_id: ticketId, reason: 'already_resolved' };
|
|
207
192
|
}
|
|
208
193
|
try {
|
|
209
|
-
await cliClient().resolveHumanTicket(
|
|
194
|
+
await cliClient().resolveHumanTicket(ticketId, { responses });
|
|
210
195
|
}
|
|
211
196
|
catch (e) {
|
|
212
197
|
if (isAlreadyResolvedConflict(e))
|
|
213
|
-
return { resolved: false,
|
|
198
|
+
return { resolved: false, ticket_id: ticketId, reason: 'already_resolved' };
|
|
214
199
|
rethrowAsCliError(e);
|
|
215
200
|
}
|
|
216
|
-
return { resolved: true,
|
|
201
|
+
return { resolved: true, ticket_id: ticketId };
|
|
217
202
|
},
|
|
218
203
|
});
|
|
219
204
|
// ---------------------------------------------------------------------------
|
|
220
|
-
// cancel — retract a pending
|
|
205
|
+
// cancel — retract a pending page/review
|
|
221
206
|
// ---------------------------------------------------------------------------
|
|
222
207
|
export const humanCancel = defineLeaf({
|
|
223
208
|
name: 'cancel',
|
|
224
|
-
description: 'retract a pending
|
|
225
|
-
whenToUse: 'a
|
|
209
|
+
description: 'retract a pending page/review',
|
|
210
|
+
whenToUse: 'a page or review went stale before the user completed it',
|
|
226
211
|
help: {
|
|
227
212
|
name: 'human cancel',
|
|
228
|
-
summary: 'retract a pending
|
|
229
|
-
guide: 'Pass the
|
|
213
|
+
summary: 'retract a pending page/review — drops it from the human inbox and retires its reply bridge when one exists. Reach for this the moment an interaction goes stale so the user is not left completing something that no longer matters',
|
|
214
|
+
guide: 'Pass the page_id returned by `human send`, or a ticket_id from `human list`. Best-effort and idempotent: if the user already completed or canceled it, canceled:false with reason "already_resolved" changes nothing. Cancellation races the user\'s submit; whichever wins is authoritative. For a reply-bearing ticket, only subscribers other than the caller receive the cancellation notice.',
|
|
230
215
|
params: [
|
|
231
|
-
{ kind: 'positional', name: '
|
|
216
|
+
{ kind: 'positional', name: 'ticket_id', type: 'string', required: true, constraint: 'page_id returned by human send, or ticket_id returned by human list.' },
|
|
232
217
|
{ kind: 'flag', name: 'reason', type: 'string', required: false, constraint: 'Optional short note delivered to subscribers explaining why it was retracted.' },
|
|
233
218
|
],
|
|
234
219
|
output: [
|
|
235
220
|
{ name: 'canceled', type: 'boolean', required: true, constraint: 'True when the interaction was retracted; false when there was nothing live to cancel (already answered/canceled).' },
|
|
236
|
-
{ name: '
|
|
221
|
+
{ name: 'ticket_id', type: 'string', required: true, constraint: 'The interaction ticket id.' },
|
|
237
222
|
{ name: 'reason', type: 'string', required: false, constraint: 'Why nothing was canceled (e.g. "already_resolved"), present when canceled is false.' },
|
|
238
223
|
],
|
|
239
224
|
outputKind: 'object',
|
|
240
225
|
effects: [
|
|
241
|
-
'The daemon
|
|
226
|
+
'The daemon removes the ticket from the inbox; when it has a reply bridge, the bridge retires and subscribers other than the caller are notified that no answer is coming.',
|
|
242
227
|
],
|
|
243
228
|
},
|
|
244
229
|
run: async (input) => {
|
|
245
|
-
const
|
|
230
|
+
const ticketId = input['ticket_id'];
|
|
246
231
|
const reason = input['reason'];
|
|
247
|
-
const node = await getNodeOrNull(jobId);
|
|
248
|
-
if (node === null) {
|
|
249
|
-
throw new InputError({
|
|
250
|
-
error: 'not_found',
|
|
251
|
-
message: `no interaction node: ${jobId}`,
|
|
252
|
-
field: 'job_id',
|
|
253
|
-
next: 'Pass the job_id from human ask/review, or list pending with `crtr human list`.',
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
const idir = ticketDir(jobId);
|
|
257
|
-
// Nothing live to cancel: the bridge is already terminal, or the ticket
|
|
258
|
-
// never carried a request descriptor.
|
|
259
|
-
const hasDescriptor = existsSync(pageManifestPath(idir)) || existsSync(reviewPath(idir));
|
|
260
|
-
if (isTerminalBridge(node.status) || !hasDescriptor) {
|
|
261
|
-
return { canceled: false, job_id: jobId, reason: 'already_resolved' };
|
|
262
|
-
}
|
|
263
232
|
const actor = process.env['CRTR_NODE_ID'] ?? 'human';
|
|
264
233
|
try {
|
|
265
|
-
await cliClient().cancelHumanTicket(
|
|
234
|
+
await cliClient().cancelHumanTicket(ticketId, { ...(reason !== undefined && reason !== '' ? { reason } : {}), actor });
|
|
266
235
|
}
|
|
267
236
|
catch (e) {
|
|
268
237
|
if (isAlreadyResolvedConflict(e))
|
|
269
|
-
return { canceled: false,
|
|
238
|
+
return { canceled: false, ticket_id: ticketId, reason: 'already_resolved' };
|
|
270
239
|
rethrowAsCliError(e);
|
|
271
240
|
}
|
|
272
|
-
return { canceled: true,
|
|
241
|
+
return { canceled: true, ticket_id: ticketId };
|
|
273
242
|
},
|
|
274
243
|
render: (r) => r['canceled'] === true
|
|
275
|
-
? `Canceled human interaction ${r['
|
|
276
|
-
: `Nothing to cancel for ${r['
|
|
244
|
+
? `Canceled human interaction ${r['ticket_id']} — it left the human inbox.`
|
|
245
|
+
: `Nothing to cancel for ${r['ticket_id']} — ${r['reason'] ?? 'nothing to cancel'}.`,
|
|
277
246
|
});
|
|
@@ -4,16 +4,6 @@ export interface PageHelpText {
|
|
|
4
4
|
pageHint: string;
|
|
5
5
|
rootUseWhen: string;
|
|
6
6
|
}
|
|
7
|
-
/** The one
|
|
8
|
-
export declare function pageHelpText(
|
|
9
|
-
/**
|
|
10
|
-
* Crouter's private per-interaction record. Reduced to the delivery essentials:
|
|
11
|
-
* the mode that identifies the ticket and the bridge node id it delivers back to.
|
|
12
|
-
* The ticket store owns every public request/result/output path (page.html,
|
|
13
|
-
* page.json, response.json, review.json); crouter never records pane ids or output paths.
|
|
14
|
-
*/
|
|
15
|
-
export interface RunRecord {
|
|
16
|
-
mode: 'ask' | 'notify' | 'review';
|
|
17
|
-
job_id?: string;
|
|
18
|
-
}
|
|
7
|
+
/** The one page authoring model for `human -h`. */
|
|
8
|
+
export declare function pageHelpText(pageSurface?: boolean): PageHelpText;
|
|
19
9
|
export declare function resolveMaxPanes(): number;
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { readConfig } from '../../core/config.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { CORE_COMPONENT_SELECTION_LINES } from '../../core/human/component-docs.js';
|
|
3
|
+
const PAGE_AUTHORING = 'Write one `.tsx` module to `$CRTR_CONTEXT_DIR/pages/<name>.tsx` that default-exports a component whose root element is `<Page title="…" subtitle="…">`; a multi-step page puts its content in `<Step>` children. Or send a complete `.html` file with a nonempty `<title>`; HTML is delivered verbatim and collects no response. Delivery is chosen by `human send` flags, never a Page prop. Registry components and React are already in scope, so the module contains no `import` and no `require`. Full JS is yours: state, expressions, `.map` over your data, event handlers, plain elements with Tailwind classes. `usePageHost` is a reserved hook whose product-supplied members are opaque here. Never render submit or dismiss chrome — the host owns it. Display-only pages are first-class: a page with no questions may be sent without inbox or reply delivery. The module is compiled when you submit it and a compile error rejects the submit; there is no pre-check.';
|
|
4
|
+
const DISPLAY_SET_LINE = 'The shadcn display set — `Card`, `Badge`, `Button`, `Table`, `Tabs`, `Separator`, `Alert`, `Progress`, `Accordion` and their parts — is in scope too, for presentation only.';
|
|
5
|
+
/** The one page authoring model for `human -h`. */
|
|
6
|
+
export function pageHelpText(pageSurface = false) {
|
|
5
7
|
return {
|
|
6
|
-
authoring:
|
|
7
|
-
?
|
|
8
|
-
:
|
|
9
|
-
componentSelection:
|
|
10
|
-
pageHint: 'Write the page to `$CRTR_CONTEXT_DIR/pages/<name>.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
authoring: pageSurface
|
|
9
|
+
? `Use inline pages as the default way to put structured or interactive content in front of the user; the environment renders rich components, so a page is not a special occasion. ${PAGE_AUTHORING}`
|
|
10
|
+
: PAGE_AUTHORING,
|
|
11
|
+
componentSelection: [...CORE_COMPONENT_SELECTION_LINES, DISPLAY_SET_LINE],
|
|
12
|
+
pageHint: 'Write the page module to `$CRTR_CONTEXT_DIR/pages/<name>.tsx`; pass that path.',
|
|
13
|
+
// The page-surface wording names no document review and no live display:
|
|
14
|
+
// `human review` and `human show` are terminal-only and leave the tree on
|
|
15
|
+
// such a host (see `registerHuman`).
|
|
16
|
+
// No leading "when": the root renderer already emits `use when ${useWhen}`.
|
|
17
|
+
rootUseWhen: pageSurface
|
|
18
|
+
? 'a person must decide something or receive structured content from you; use inline pages by default because the environment renders rich components.'
|
|
19
|
+
: 'a person must decide, review a document with you, or receive a live display.',
|
|
14
20
|
};
|
|
15
21
|
}
|
|
16
22
|
export function resolveMaxPanes() {
|
package/dist/commands/human.js
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
|
-
// `crtr human` creates
|
|
1
|
+
// `crtr human` creates durable pages and review tickets for human decisions.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// enqueues a durable page with no node; show is passive display.
|
|
3
|
+
// Send optionally creates a reply bridge; review creation asks crtrd to create
|
|
4
|
+
// a ticket, daemon-owned review, and companion. Show is passive display.
|
|
6
5
|
import { defineBranch } from '../core/command.js';
|
|
7
6
|
import { readConfig } from '../core/config.js';
|
|
8
|
-
import {
|
|
7
|
+
import { humanSend, humanShow } from './human/prompts.js';
|
|
9
8
|
import { humanList, humanResolve, humanCancel } from './human/queue.js';
|
|
10
9
|
import { humanReviewBranch } from './human/review.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { humanHtml } from './human/html.js';
|
|
10
|
+
import { humanFeedbackBranch } from './human/feedback.js';
|
|
11
|
+
import { humanComponents } from './human/components.js';
|
|
14
12
|
import { pageHelpText } from './human/shared.js';
|
|
15
13
|
export function registerHuman() {
|
|
16
|
-
|
|
14
|
+
// `page_surface` marks a host whose human surface is a rendered page product
|
|
15
|
+
// rather than a terminal. Review and show are terminal-only: a review is a
|
|
16
|
+
// live editor session with anchored comments driven by a companion node, and
|
|
17
|
+
// show is a passive tmux live-watch. Such a host can render neither, so both
|
|
18
|
+
// leave the surface entirely instead of filing tickets nobody can open.
|
|
19
|
+
const pageSurface = readConfig('user').page_surface;
|
|
20
|
+
const pageHelp = pageHelpText(pageSurface);
|
|
21
|
+
const concept = pageSurface
|
|
22
|
+
? 'human-in-the-loop decisions and structured pages'
|
|
23
|
+
: 'human-in-the-loop decisions, document review, and live display';
|
|
24
|
+
const ticketModel = pageSurface
|
|
25
|
+
? 'Tickets queue in the human inbox; nothing opens on screen. `send` routes completion back when the page carries response-bearing components, and publishes a standalone announcement when it does not.'
|
|
26
|
+
: 'Tickets queue in the human inbox; nothing opens on screen. `send` routes completion back when the page carries response-bearing components, and publishes a standalone announcement when it does not. `review` is for live document review with anchored comments; `show` is a passive tmux live-watch rather than a ticket.';
|
|
17
27
|
return defineBranch({
|
|
18
28
|
name: 'human',
|
|
19
29
|
rootEntry: {
|
|
20
|
-
concept
|
|
21
|
-
desc: '
|
|
30
|
+
concept,
|
|
31
|
+
desc: pageSurface ? 'send, components, feedback, cancel, list' : 'send, review new/comment, show, cancel, list',
|
|
22
32
|
useWhen: pageHelp.rootUseWhen,
|
|
23
33
|
},
|
|
24
34
|
help: {
|
|
25
35
|
name: 'human',
|
|
26
|
-
summary:
|
|
36
|
+
summary: concept,
|
|
27
37
|
model: [
|
|
28
|
-
|
|
38
|
+
ticketModel,
|
|
29
39
|
pageHelp.authoring,
|
|
30
40
|
pageHelp.componentSelection.join('\n'),
|
|
31
|
-
'
|
|
41
|
+
'Props, response contracts, and the full component catalog: `crtr human components`.',
|
|
32
42
|
].join('\n\n'),
|
|
33
43
|
},
|
|
34
44
|
children: [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
humanReviewBranch,
|
|
38
|
-
|
|
39
|
-
humanShow,
|
|
45
|
+
humanComponents,
|
|
46
|
+
humanSend,
|
|
47
|
+
...(pageSurface ? [] : [humanReviewBranch]),
|
|
48
|
+
humanFeedbackBranch,
|
|
49
|
+
...(pageSurface ? [] : [humanShow]),
|
|
40
50
|
humanCancel,
|
|
41
51
|
humanList,
|
|
42
52
|
humanResolve,
|
|
43
|
-
humanInbox,
|
|
44
|
-
humanDoc,
|
|
45
53
|
],
|
|
46
54
|
});
|
|
47
55
|
}
|
|
@@ -20,7 +20,7 @@ export const originLeaf = defineLeaf({
|
|
|
20
20
|
{ name: 'origin_recorded', type: 'boolean', required: true, constraint: 'true when the doc carries an `origin` provenance block; false for hand-authored or pre-provenance docs.' },
|
|
21
21
|
{ name: 'created', type: 'string', required: false, constraint: 'ISO timestamp the doc was created. Absent when origin_recorded is false.' },
|
|
22
22
|
{ name: 'cwd', type: 'string', required: false, constraint: 'Working directory the authoring conversation ran in.' },
|
|
23
|
-
{ name: 'node', type: 'string', required: false, constraint: 'Node id that authored the doc, or null when written by
|
|
23
|
+
{ name: 'node', type: 'string', required: false, constraint: 'Node id that authored the doc, or null when written by the user at the CLI (no node).' },
|
|
24
24
|
{ name: 'node_present', type: 'boolean', required: false, constraint: 'true when that node still exists on the canvas (deref below is live); false when it has been pruned.' },
|
|
25
25
|
{ name: 'node_kind', type: 'string', required: false, constraint: 'Kind of the authoring node (only when node_present).' },
|
|
26
26
|
{ name: 'node_mode', type: 'string', required: false, constraint: 'Mode of the authoring node (only when node_present).' },
|
|
@@ -79,7 +79,7 @@ export const originLeaf = defineLeaf({
|
|
|
79
79
|
? `Read the originating conversation at \`${sessionFile ?? '(no session captured)'}\`, browse its shared docs at \`${contextDir(nodeId)}\`, or attach live with \`crtr surface attach ${nodeId}\`.`
|
|
80
80
|
: nodeId !== null
|
|
81
81
|
? `The authoring node \`${nodeId}\` has been pruned from the canvas; its context dir may persist at \`${contextDir(nodeId)}\`.`
|
|
82
|
-
: 'Authored by
|
|
82
|
+
: 'Authored by the user at the CLI \u2014 no node conversation to deref.';
|
|
83
83
|
return {
|
|
84
84
|
name: doc.name,
|
|
85
85
|
path: doc.path,
|
|
@@ -3,7 +3,7 @@ import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
|
3
3
|
import { InputError } from '../../core/io.js';
|
|
4
4
|
import { displayMessage } from '../../core/runtime/tmux-chrome.js';
|
|
5
5
|
import { contextDir } from '../../core/canvas/paths.js';
|
|
6
|
-
import { activeBackgroundBashJobs, backgroundRunningBashJobs, bashJobPaths, formatBashElapsed, isSafeBashJobId, jobStartedAtMs, readJobPgid, stopBackgroundBashJob, tailJobLog } from '../../core/bash-jobs.js';
|
|
6
|
+
import { activeBackgroundBashJobs, backgroundRunningBashJobs, bashJobCommandLine, bashJobPaths, formatBashElapsed, isSafeBashJobId, jobStartedAtMs, readJobPgid, stopBackgroundBashJob, tailJobLog } from '../../core/bash-jobs.js';
|
|
7
7
|
import { cliClient, getNodeOrNull } from '../api-client.js';
|
|
8
8
|
import { nodeInPane } from '../surface/node/placement.js';
|
|
9
9
|
function resolveBashTarget(input, missingMessage) {
|
|
@@ -108,13 +108,13 @@ const nodeBashList = defineLeaf({
|
|
|
108
108
|
const jobs = result['jobs'];
|
|
109
109
|
if (jobs.length === 0)
|
|
110
110
|
return `No background bash jobs for ${result['node_id']}.`;
|
|
111
|
-
return jobs.map((job) => `${job.job_id} · ${formatBashElapsed(job.elapsed_ms)} · ${job.command
|
|
111
|
+
return jobs.map((job) => `${job.job_id} · ${formatBashElapsed(job.elapsed_ms)} · ${bashJobCommandLine(job.command)}${job.purpose === null ? '' : `\n purpose: ${job.purpose}`}\n log: ${job.log_path}`).join('\n');
|
|
112
112
|
},
|
|
113
113
|
});
|
|
114
114
|
const nodeBashKill = defineLeaf({
|
|
115
115
|
name: 'kill',
|
|
116
116
|
description: 'stop one of a node’s background bash jobs',
|
|
117
|
-
whenToUse: '
|
|
117
|
+
whenToUse: 'the user watching the Inspector or the attach cockpit’s bash row wants a long-running background job to stop now, or an agent is abandoning work it backgrounded',
|
|
118
118
|
tier: 'hidden',
|
|
119
119
|
help: {
|
|
120
120
|
name: 'node bash kill',
|
|
@@ -166,7 +166,7 @@ const nodeBashKill = defineLeaf({
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
await cliClient().sendMessage(node.node_id, {
|
|
169
|
-
body: `Background bash job ${jobId} was stopped by the
|
|
169
|
+
body: `Background bash job ${jobId} was stopped by the user before it finished. Log: ${paths.jobLog} Command: ${paths.cmdSh}`,
|
|
170
170
|
tier: 'urgent',
|
|
171
171
|
});
|
|
172
172
|
return { node_id: node.node_id, job_id: jobId, signaled: stopped.signaled };
|
|
@@ -17,7 +17,7 @@ import { focusViewer, nodeInPane } from '../surface/node/placement.js';
|
|
|
17
17
|
const nodeList = defineLeaf({
|
|
18
18
|
name: 'list',
|
|
19
19
|
description: 'list nodes on the canvas',
|
|
20
|
-
whenToUse: 'you want a flat roster of the nodes on the canvas, sliced by displayed name, status, kind, mode, lifecycle, profile, cwd, and graph scope (--under a node, for that node plus its subscription descendants). The filters AND together. Use `node inspect show` instead to drill into one node and its spine neighbors, `canvas dashboard` for the tree shape, and `canvas attention` to find nodes blocked on
|
|
20
|
+
whenToUse: 'you want a flat roster of the nodes on the canvas, sliced by displayed name, status, kind, mode, lifecycle, profile, cwd, and graph scope (--under a node, for that node plus its subscription descendants). The filters AND together. Use `node inspect show` instead to drill into one node and its spine neighbors, `canvas dashboard` for the tree shape, and `canvas attention` to find nodes blocked on the user',
|
|
21
21
|
help: {
|
|
22
22
|
name: 'node inspect list',
|
|
23
23
|
summary: 'list nodes on the canvas, filtered by any combination of attribute slices and a graph-scope (--under) that AND together',
|
|
@@ -239,7 +239,7 @@ const nodeShow = defineLeaf({
|
|
|
239
239
|
export const nodeFork = defineLeaf({
|
|
240
240
|
name: 'fork',
|
|
241
241
|
description: 'fork a node into an independent idle root with the same conversation and durable context',
|
|
242
|
-
whenToUse: 'a conversation has already accumulated the context you want, and you want a new
|
|
242
|
+
whenToUse: 'a conversation has already accumulated the context you want, and you want a new user-driven branch beside it without sending a prompt or inheriting any graph-management responsibility. This copies the conversation and durable context, then waits for the fork’s first real user message to supply its mandate. Use `node new --fork-from` instead when spawning a managed worker with an immediate task',
|
|
243
243
|
help: {
|
|
244
244
|
name: 'node fork',
|
|
245
245
|
summary: 'create an independent resident root from a node’s conversation and durable context, with no kickoff prompt or model turn',
|
|
@@ -293,7 +293,7 @@ export const nodeFork = defineLeaf({
|
|
|
293
293
|
export const nodeInspect = defineBranch({
|
|
294
294
|
name: 'inspect',
|
|
295
295
|
description: 'read one node — its graph position, context, and artifacts',
|
|
296
|
-
whenToUse: 'reading everything about ONE node you can name: enumerate the roster (`list`), inspect a node and its spine neighbors (`show`), browse its on-disk context dir (`context`), or list its reports/docs as refs (`artifacts`). Use `canvas dashboard` instead to render the whole tree SHAPE, or `canvas attention` to find which nodes are blocked on
|
|
296
|
+
whenToUse: 'reading everything about ONE node you can name: enumerate the roster (`list`), inspect a node and its spine neighbors (`show`), browse its on-disk context dir (`context`), or list its reports/docs as refs (`artifacts`). Use `canvas dashboard` instead to render the whole tree SHAPE, or `canvas attention` to find which nodes are blocked on the user',
|
|
297
297
|
help: {
|
|
298
298
|
name: 'node inspect',
|
|
299
299
|
summary: 'read one node (or the roster) — topology, context dir, and artifacts',
|
|
@@ -8,7 +8,7 @@ import { closePane, paneExists, respawnPaneSync, viewerEnv, viewerPaneOf, nodeIn
|
|
|
8
8
|
const nodeRecycle = defineLeaf({
|
|
9
9
|
name: 'recycle',
|
|
10
10
|
description: 'finish the agent in your pane + recycle it into a fresh root',
|
|
11
|
-
whenToUse: 'you are at an agent\'s pane and done with it: finish it cleanly and recycle the pane in one move — push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The
|
|
11
|
+
whenToUse: 'you are at an agent\'s pane and done with it: finish it cleanly and recycle the pane in one move — push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The hands-on way to end an agent and immediately start over in place. Use `node lifecycle demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node lifecycle close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn',
|
|
12
12
|
help: {
|
|
13
13
|
name: 'node lifecycle recycle',
|
|
14
14
|
summary: 'finish the agent in your current pane and recycle the pane — push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane',
|
|
@@ -49,7 +49,7 @@ function messageSendParams() {
|
|
|
49
49
|
{ kind: 'flag', name: 'self', type: 'bool', required: false, constraint: 'Target the calling node. Exactly one of --to or --self is required.' },
|
|
50
50
|
{ kind: 'flag', name: 'tier', type: 'enum', choices: ['critical', 'urgent', 'normal', 'deferred'], required: false, default: 'normal', constraint: 'Delivery urgency. Deferred never wakes an idle node.' },
|
|
51
51
|
{ kind: 'flag', name: 'situational-context', type: 'string', required: false, constraint: 'Non-empty hidden ambient context delivered beside the message, or alone.' },
|
|
52
|
-
{ kind: 'flag', name: 'reopen', type: 'bool', required: false,
|
|
52
|
+
{ kind: 'flag', name: 'reopen', type: 'bool', required: false, constraint: 'Required to re-task a finalized target: clears its finalization latch before delivery. Rejected when the target is not finalized.' },
|
|
53
53
|
];
|
|
54
54
|
}
|
|
55
55
|
function messageRequestParams() {
|
|
@@ -132,7 +132,7 @@ const nodeMessageSend = defineLeaf({
|
|
|
132
132
|
params: messageSendParams(),
|
|
133
133
|
output: messageOutput(),
|
|
134
134
|
outputKind: 'object',
|
|
135
|
-
effects: ['Appends one inbox entry. Critical, urgent, and normal delivery may revive a dormant target; deferred waits for its next natural cycle.'],
|
|
135
|
+
effects: ['Appends one inbox entry. Critical, urgent, and normal delivery may revive a dormant target; deferred waits for its next natural cycle.', '--reopen clears a finalized target\'s finalization latch before delivery, allowing it to take a new mandate. Without it, a finalized target rejects delivery before an inbox entry is appended or a revive is attempted.'],
|
|
136
136
|
},
|
|
137
137
|
run: (input) => runMessageEngine(input),
|
|
138
138
|
render: (r) => String(r['guidance']),
|
|
@@ -41,7 +41,7 @@ function mapWorktreeApiError(err) {
|
|
|
41
41
|
const worktreeClose = defineLeaf({
|
|
42
42
|
name: 'close',
|
|
43
43
|
description: 'land and close the current node-managed worktree',
|
|
44
|
-
whenToUse: 'you are inside the node that owns an open managed worktree and are ready to rebase it onto its recorded local base branch
|
|
44
|
+
whenToUse: 'you are inside the node that owns an open managed worktree and are ready to rebase it onto its recorded local base branch and fast-forward that base before finishing; its active checkout is retained for deferred cleanup',
|
|
45
45
|
help: {
|
|
46
46
|
name: 'node worktree close',
|
|
47
47
|
summary: 'land and close the current node-managed worktree',
|
|
@@ -50,15 +50,15 @@ const worktreeClose = defineLeaf({
|
|
|
50
50
|
{ name: 'node_id', type: 'string', required: true, constraint: 'The caller node id whose managed worktree was closed.' },
|
|
51
51
|
{ name: 'branch', type: 'string', required: true, constraint: 'The worktree branch that was fast-forwarded into its recorded local base branch.' },
|
|
52
52
|
{ name: 'landed_sha', type: 'string', required: true, constraint: 'The commit SHA that the recorded local base branch was fast-forwarded onto.' },
|
|
53
|
-
{ name: 'worktree_path', type: 'string', required: true, constraint: 'The managed worktree path
|
|
54
|
-
{ name: 'worktree_removed', type: 'boolean', required: true, constraint: '
|
|
55
|
-
{ name: 'worktree_remove_error', type: 'string', required:
|
|
56
|
-
{ name: 'branch_deleted', type: 'boolean', required: true, constraint: '
|
|
57
|
-
{ name: 'branch_delete_error', type: 'string', required:
|
|
53
|
+
{ name: 'worktree_path', type: 'string', required: true, constraint: 'The managed worktree path whose cleanup is deferred until its caller exits.' },
|
|
54
|
+
{ name: 'worktree_removed', type: 'boolean', required: true, constraint: 'Always false for an active caller: its checkout remains intact rather than being removed beneath the node.' },
|
|
55
|
+
{ name: 'worktree_remove_error', type: 'string', required: true, constraint: 'The named deferred-cleanup state and safe manual follow-up after the caller exits.' },
|
|
56
|
+
{ name: 'branch_deleted', type: 'boolean', required: true, constraint: 'False while the deferred-cleanup checkout still has this branch checked out.' },
|
|
57
|
+
{ name: 'branch_delete_error', type: 'string', required: true, constraint: 'The safe branch-cleanup follow-up after the checkout is removed.' }
|
|
58
58
|
],
|
|
59
59
|
outputKind: 'object',
|
|
60
60
|
effects: [
|
|
61
|
-
'Rebases the worktree onto its recorded local base branch, fast-forwards that base onto it (never pushes to origin), marks the node-managed worktree closed
|
|
61
|
+
'Rebases the worktree onto its recorded local base branch, fast-forwards that base onto it (never pushes to origin), marks the node-managed worktree closed with cleanup pending, and leaves the active caller checkout and branch intact.',
|
|
62
62
|
],
|
|
63
63
|
},
|
|
64
64
|
run: async () => {
|
|
@@ -93,7 +93,7 @@ const worktreeClose = defineLeaf({
|
|
|
93
93
|
export const nodeWorktreeBranch = defineBranch({
|
|
94
94
|
name: 'worktree',
|
|
95
95
|
description: 'manage a node-owned git worktree — close the one born by `node new --worktree <base>`',
|
|
96
|
-
whenToUse: 'you are inside a node that was spawned with `node new --worktree <base>` and are ready to land it: rebase onto its recorded local base branch, fast-forward that base onto it, and close the worktree. Symmetric with `node new --worktree <base>`, which births it',
|
|
96
|
+
whenToUse: 'you are inside a node that was spawned with `node new --worktree <base>` and are ready to land it: rebase it onto its recorded local base branch, fast-forward that base onto it, and close the worktree with cleanup deferred until the node exits. Symmetric with `node new --worktree <base>`, which births it',
|
|
97
97
|
help: {
|
|
98
98
|
name: 'node worktree',
|
|
99
99
|
summary: 'manage a node-owned git worktree — birth is `node new --worktree <base>`; this branch closes it',
|
|
@@ -8,7 +8,7 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
8
8
|
export const pkgBrowseLeaf = defineLeaf({
|
|
9
9
|
name: 'browse',
|
|
10
10
|
description: 'open the interactive plugin browser',
|
|
11
|
-
whenToUse: '
|
|
11
|
+
whenToUse: 'the user wants to browse registered marketplaces and inspect or manage plugins interactively. Agents should use pkg market browse or pkg plugin list, which are scriptable.',
|
|
12
12
|
help: {
|
|
13
13
|
name: 'pkg browse',
|
|
14
14
|
summary: 'open the interactive plugin browser',
|