@north-light/crouter 0.3.197 → 0.3.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +19 -19
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +126 -33
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +8 -8
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +29 -9
- package/dist/clients/attach/render/chat-view.js +64 -35
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -982
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +3 -4
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +101 -36
- package/dist/clients/inbox/tui/slots.d.ts +4 -4
- package/dist/clients/inbox/tui/slots.js +19 -44
- package/dist/clients/inbox/tui/types.d.ts +6 -10
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +118 -24
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +17 -8
- package/dist/commands/human/prompts.js +217 -226
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +4 -15
- package/dist/commands/human/shared.js +19 -61
- package/dist/commands/human.js +32 -19
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +41 -45
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +33 -0
- package/dist/core/canvas/canvas.js +92 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +8 -12
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +138 -4
- package/dist/core/help.d.ts +3 -0
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +20 -0
- package/dist/core/human/__tests__/page-tickets.test.js +68 -34
- package/dist/core/human/__tests__/page.test.js +60 -119
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +53 -0
- package/dist/core/human/answer.js +119 -0
- package/dist/core/human/component-docs.d.ts +18 -0
- package/dist/core/human/component-docs.js +434 -0
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +56 -5
- package/dist/core/human/page-catalog.js +151 -22
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +48 -116
- package/dist/core/human/page-schema.js +100 -72
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +20 -7
- package/dist/core/human/page.d.ts +13 -21
- package/dist/core/human/page.js +147 -391
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -3
- package/dist/core/human/scan.js +21 -21
- package/dist/core/human/summary.d.ts +2 -6
- package/dist/core/human/summary.js +3 -59
- package/dist/core/human/tickets.d.ts +22 -15
- package/dist/core/human/tickets.js +90 -28
- package/dist/core/human/types.d.ts +14 -2
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +12 -4
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +25 -12
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +36 -2
- package/dist/core/user-settings.js +56 -14
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +48 -109
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +29 -17
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +12 -6
- package/dist/daemon/human/finish.js +134 -54
- package/dist/daemon/human/sweep.js +9 -3
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +14 -9
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +31 -13
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +61 -5
- package/dist/types.js +8 -2
- package/package.json +4 -4
- package/runtime.lock.json +5 -33
- package/scripts/install-runtime.mjs +19 -24
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/commands/human/doc.js +0 -144
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/page-render.test.js +0 -49
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
- package/dist/core/human/page-render.d.ts +0 -3
- package/dist/core/human/page-render.js +0 -131
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1347
- package/dist/pages/comments.d.ts +0 -43
- package/dist/pages/comments.js +0 -125
- package/dist/pages/elements/cards.d.ts +0 -17
- package/dist/pages/elements/cards.js +0 -802
- package/dist/pages/elements/chart.js +0 -648
- package/dist/pages/elements/options.d.ts +0 -21
- package/dist/pages/elements/options.js +0 -629
- package/dist/pages/elements/pages.d.ts +0 -24
- package/dist/pages/elements/pages.js +0 -355
- package/dist/pages/elements/slot.d.ts +0 -21
- package/dist/pages/elements/slot.js +0 -105
- package/dist/pages/elements/table.d.ts +0 -19
- package/dist/pages/elements/table.js +0 -935
- package/dist/pages/elements/text.d.ts +0 -25
- package/dist/pages/elements/text.js +0 -968
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -158
- package/dist/pages/host.js +0 -182
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -4,21 +4,20 @@
|
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { ApiError } from '../../../api/index.js';
|
|
6
6
|
import { isSafeNodeId } from '../../../core/canvas/paths.js';
|
|
7
|
-
import { pageManifestPath, reviewPath } from '../../../core/human/convention.js';
|
|
7
|
+
import { pageManifestPath, readTicketReplyRoute, reviewPath } from '../../../core/human/convention.js';
|
|
8
8
|
import { ticketDir } from '../../../core/human/root.js';
|
|
9
9
|
import { readTicketResult, validatePageTicketResponses } from '../../../core/human/tickets.js';
|
|
10
|
-
import { readConfig } from '../../../core/config.js';
|
|
11
10
|
import { getReviewByBridge } from '../../../core/review/store.js';
|
|
12
11
|
import { ReviewOperationError } from '../../../core/review/types.js';
|
|
13
12
|
import { spawnNode } from '../../../core/runtime/nodes.js';
|
|
14
13
|
import { cancelHumanTicket, resolvePageTicket } from '../../human/finish.js';
|
|
15
14
|
import { cancelReview } from '../../review/finish.js';
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
throw new ApiError(400, 'invalid_request', 'invalid ticket
|
|
15
|
+
function requireInteractionTicketId(ctx) {
|
|
16
|
+
const ticketId = ctx.params['ticket_id'];
|
|
17
|
+
if (ticketId === undefined || !isSafeNodeId(ticketId)) {
|
|
18
|
+
throw new ApiError(400, 'invalid_request', 'invalid interaction ticket id');
|
|
20
19
|
}
|
|
21
|
-
return
|
|
20
|
+
return ticketId;
|
|
22
21
|
}
|
|
23
22
|
// ===========================================================================
|
|
24
23
|
// POST /v1/human/bridge — create a terminal bridge without a broker
|
|
@@ -48,7 +47,7 @@ function handleCreateBridge(ctx) {
|
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
// ===========================================================================
|
|
51
|
-
// POST /v1/human/tickets/:
|
|
50
|
+
// POST /v1/human/tickets/:ticket_id/resolve and /cancel
|
|
52
51
|
// ===========================================================================
|
|
53
52
|
function reviewSubmitGuidance() {
|
|
54
53
|
return new ApiError(400, 'invalid_request', 'review approvals use POST /v1/human/reviews/:review_id/submit');
|
|
@@ -78,6 +77,8 @@ function requirePageTicket(dir) {
|
|
|
78
77
|
}
|
|
79
78
|
function invalidResolveRequest(error) {
|
|
80
79
|
const detail = error instanceof Error ? error.message : String(error);
|
|
80
|
+
if (detail === 'page_has_no_response_lifecycle')
|
|
81
|
+
return new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
81
82
|
return new ApiError(400, 'invalid_request', `invalid ticket resolve body: ${detail}`);
|
|
82
83
|
}
|
|
83
84
|
function preflightResolve(validate) {
|
|
@@ -89,26 +90,26 @@ function preflightResolve(validate) {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
async function handleResolve(ctx) {
|
|
92
|
-
const
|
|
93
|
+
const ticketId = requireInteractionTicketId(ctx);
|
|
93
94
|
const request = requireResolveRequest(ctx);
|
|
94
|
-
const dir = ticketDir(
|
|
95
|
+
const dir = ticketDir(ticketId);
|
|
95
96
|
requirePageTicket(dir);
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
await resolvePageTicket(
|
|
99
|
-
return { status: 200, body: {
|
|
97
|
+
const responses = preflightResolve(() => validatePageTicketResponses(dir, request.responses));
|
|
98
|
+
const replyExpected = readTicketReplyRoute(dir) !== null;
|
|
99
|
+
await resolvePageTicket(ticketId, responses);
|
|
100
|
+
return { status: 200, body: { completed: true, reply_delivered: replyExpected, kind: 'page' } };
|
|
100
101
|
}
|
|
101
102
|
function hasPublishedTicket(dir) {
|
|
102
103
|
return existsSync(pageManifestPath(dir)) || existsSync(reviewPath(dir)) || readTicketResult(dir) !== null;
|
|
103
104
|
}
|
|
104
105
|
async function handleCancel(ctx) {
|
|
105
|
-
const
|
|
106
|
+
const ticketId = requireInteractionTicketId(ctx);
|
|
106
107
|
const request = ctx.body;
|
|
107
108
|
if (request !== undefined && (typeof request !== 'object' || request === null)) {
|
|
108
109
|
throw new ApiError(400, 'invalid_request', 'ticket cancel body must be an object');
|
|
109
110
|
}
|
|
110
|
-
const dir = ticketDir(
|
|
111
|
-
const review = getReviewByBridge(
|
|
111
|
+
const dir = ticketDir(ticketId);
|
|
112
|
+
const review = getReviewByBridge(ticketId);
|
|
112
113
|
try {
|
|
113
114
|
if (review !== null) {
|
|
114
115
|
if (review.state === 'approved') {
|
|
@@ -125,12 +126,15 @@ async function handleCancel(ctx) {
|
|
|
125
126
|
if (!hasPublishedTicket(dir)) {
|
|
126
127
|
throw new ApiError(404, 'ticket_not_found', 'no matching ticket');
|
|
127
128
|
}
|
|
128
|
-
await cancelHumanTicket(
|
|
129
|
+
await cancelHumanTicket(ticketId, request ?? {});
|
|
129
130
|
return { status: 200, body: { canceled: true } };
|
|
130
131
|
}
|
|
131
132
|
catch (error) {
|
|
132
133
|
if (error instanceof ApiError)
|
|
133
134
|
throw error;
|
|
135
|
+
if (error instanceof Error && error.message === 'page_has_no_response_lifecycle') {
|
|
136
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
137
|
+
}
|
|
134
138
|
// Review errors are not part of the retained ticket route's vocabulary.
|
|
135
139
|
if (error instanceof ReviewOperationError) {
|
|
136
140
|
throw new ApiError(409, 'ticket_already_resolved', 'ticket already resolved');
|
|
@@ -140,6 +144,6 @@ async function handleCancel(ctx) {
|
|
|
140
144
|
}
|
|
141
145
|
export const humanRoutes = [
|
|
142
146
|
{ method: 'POST', pattern: '/v1/human/bridge', handler: handleCreateBridge },
|
|
143
|
-
{ method: 'POST', pattern: '/v1/human/tickets/:
|
|
144
|
-
{ method: 'POST', pattern: '/v1/human/tickets/:
|
|
147
|
+
{ method: 'POST', pattern: '/v1/human/tickets/:ticket_id/resolve', handler: handleResolve },
|
|
148
|
+
{ method: 'POST', pattern: '/v1/human/tickets/:ticket_id/cancel', handler: handleCancel },
|
|
145
149
|
];
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
import type { InboxTicketIdDTO } from '../../../api/index.js';
|
|
1
2
|
import type { RouteTable } from '../router.js';
|
|
3
|
+
export interface ResolvedTicket {
|
|
4
|
+
ticketId: string;
|
|
5
|
+
dir: string;
|
|
6
|
+
kind: 'page' | 'review';
|
|
7
|
+
}
|
|
8
|
+
/** Resolve one opaque inbox ticket id to its canonical directory. Shared with
|
|
9
|
+
* the feedback-comment routes, which address tickets by the same ids. */
|
|
10
|
+
export declare function resolveTicket(ticketId: InboxTicketIdDTO): ResolvedTicket | null;
|
|
11
|
+
export declare function requireTicketId(params: Record<string, string>): InboxTicketIdDTO;
|
|
2
12
|
export declare const inboxRoutes: RouteTable;
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
// v1, inbox-contract.md §A). This public ticket-serving resource enumerates,
|
|
3
3
|
// reads, responds to, and cancels tickets while preserving its product DTOs.
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
|
-
import { existsSync,
|
|
6
|
-
import { basename, resolve
|
|
5
|
+
import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
6
|
+
import { basename, resolve } from 'node:path';
|
|
7
7
|
import { ApiError } from '../../../api/index.js';
|
|
8
|
-
import {
|
|
8
|
+
import { readPageFeedback } from '../../../core/human/feedback.js';
|
|
9
|
+
import { toPageFeedbackDTO } from '../map.js';
|
|
9
10
|
import { pageManifestPath, reviewPath } from '../../../core/human/convention.js';
|
|
10
|
-
import {
|
|
11
|
+
import { parsePage } from '../../../core/human/page.js';
|
|
11
12
|
import { ticketsRoot } from '../../../core/human/root.js';
|
|
12
13
|
import { scanInbox, scanPageHistory } from '../../../core/human/scan.js';
|
|
13
14
|
import { readPageTicket, readTicketResult, validatePageTicketResponses, writePageProgress, } from '../../../core/human/tickets.js';
|
|
14
|
-
import {
|
|
15
|
+
import { describePageAnswer } from '../../../core/human/answer.js';
|
|
15
16
|
import { cancelHumanTicket, resolvePageTicket } from '../../human/finish.js';
|
|
16
17
|
import { filterTerminalReviewTickets } from '../../../core/review/ticket-filter.js';
|
|
17
18
|
const DEFAULT_CANCEL_REASON = 'Canceled from the inbox.';
|
|
@@ -23,7 +24,9 @@ const TICKET_ID_PATTERN = /^[a-f0-9]{64}$/;
|
|
|
23
24
|
function ticketHash(canonicalRoot, ticketBasename) {
|
|
24
25
|
return createHash('sha256').update(`${canonicalRoot}\0${ticketBasename}`, 'utf8').digest('hex');
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
/** Resolve one opaque inbox ticket id to its canonical directory. Shared with
|
|
28
|
+
* the feedback-comment routes, which address tickets by the same ids. */
|
|
29
|
+
export function resolveTicket(ticketId) {
|
|
27
30
|
let canonicalRoot;
|
|
28
31
|
try {
|
|
29
32
|
canonicalRoot = realpathSync(ticketsRoot());
|
|
@@ -54,13 +57,13 @@ function resolveTicket(ticketId) {
|
|
|
54
57
|
if (resolve(canonicalDir, '..') !== canonicalRoot || basename(canonicalDir) !== entry)
|
|
55
58
|
continue;
|
|
56
59
|
if (existsSync(pageManifestPath(canonicalDir)))
|
|
57
|
-
return {
|
|
60
|
+
return { ticketId: entry, dir: canonicalDir, kind: 'page' };
|
|
58
61
|
if (existsSync(reviewPath(canonicalDir)))
|
|
59
|
-
return {
|
|
62
|
+
return { ticketId: entry, dir: canonicalDir, kind: 'review' };
|
|
60
63
|
}
|
|
61
64
|
return null;
|
|
62
65
|
}
|
|
63
|
-
function requireTicketId(params) {
|
|
66
|
+
export function requireTicketId(params) {
|
|
64
67
|
const ticketId = params['ticket_id'];
|
|
65
68
|
if (ticketId === undefined || !TICKET_ID_PATTERN.test(ticketId)) {
|
|
66
69
|
throw new ApiError(400, 'invalid_request', 'invalid ticket id');
|
|
@@ -80,14 +83,16 @@ function pageSlotToDTO(slot) {
|
|
|
80
83
|
dto.id = slot.id;
|
|
81
84
|
if (slot.unvalidated === true)
|
|
82
85
|
dto.unvalidated = true;
|
|
86
|
+
if (slot.display === true)
|
|
87
|
+
dto.display = true;
|
|
83
88
|
return dto;
|
|
84
89
|
}
|
|
85
90
|
function pageManifestToDTO(manifest) {
|
|
86
91
|
const dto = {
|
|
87
|
-
schema: 'crtr.page/
|
|
92
|
+
schema: 'crtr.page/v2',
|
|
88
93
|
dialect: manifest.dialect,
|
|
89
94
|
title: manifest.title,
|
|
90
|
-
|
|
95
|
+
delivery: manifest.delivery,
|
|
91
96
|
document: manifest.document,
|
|
92
97
|
steps: manifest.steps,
|
|
93
98
|
slots: manifest.slots.map(pageSlotToDTO),
|
|
@@ -100,8 +105,10 @@ function pageManifestToDTO(manifest) {
|
|
|
100
105
|
source.sessionName = manifest.source.sessionName;
|
|
101
106
|
if (manifest.source.askedBy !== undefined)
|
|
102
107
|
source.askedBy = manifest.source.askedBy;
|
|
103
|
-
if (manifest.source.
|
|
104
|
-
source.
|
|
108
|
+
if (manifest.source.emittedAt !== undefined)
|
|
109
|
+
source.emittedAt = manifest.source.emittedAt;
|
|
110
|
+
if (manifest.source.profileName !== undefined)
|
|
111
|
+
source.profileName = manifest.source.profileName;
|
|
105
112
|
if (manifest.source.nodeId !== undefined)
|
|
106
113
|
source.nodeId = manifest.source.nodeId;
|
|
107
114
|
if (Object.keys(source).length > 0)
|
|
@@ -163,8 +170,7 @@ function isSlotValidationError(err) {
|
|
|
163
170
|
// GET /v1/human/inbox — enumerate
|
|
164
171
|
// ===========================================================================
|
|
165
172
|
function handleList() {
|
|
166
|
-
const
|
|
167
|
-
const allItems = scanInbox(productKinds);
|
|
173
|
+
const allItems = scanInbox();
|
|
168
174
|
const filtered = filterTerminalReviewTickets(allItems);
|
|
169
175
|
const tickets = filtered.map((item) => {
|
|
170
176
|
if (item.kind === 'page') {
|
|
@@ -176,9 +182,10 @@ function handleList() {
|
|
|
176
182
|
placement: item.placement,
|
|
177
183
|
dialect: item.dialect,
|
|
178
184
|
steps: item.steps,
|
|
179
|
-
|
|
185
|
+
emitted_at: item.emittedAt,
|
|
180
186
|
source: item.source,
|
|
181
187
|
slot_kinds: item.slotKinds,
|
|
188
|
+
inbox: item.inbox,
|
|
182
189
|
awaits_response: item.awaitsResponse,
|
|
183
190
|
};
|
|
184
191
|
if (item.subtitle !== '')
|
|
@@ -197,7 +204,8 @@ function handleList() {
|
|
|
197
204
|
}
|
|
198
205
|
throw new Error(`unknown ticket kind: ${item.kind}`);
|
|
199
206
|
}).sort((a, b) => {
|
|
200
|
-
const
|
|
207
|
+
const pageTime = (item) => item.kind === 'page' ? item.emitted_at : item.blocked_since;
|
|
208
|
+
const byTime = Date.parse(pageTime(b)) - Date.parse(pageTime(a));
|
|
201
209
|
return byTime !== 0 ? byTime : a.ticket_id.localeCompare(b.ticket_id);
|
|
202
210
|
});
|
|
203
211
|
return { status: 200, body: { tickets } };
|
|
@@ -210,8 +218,7 @@ function handleHistory(ctx) {
|
|
|
210
218
|
if (!nodeId || typeof nodeId !== 'string') {
|
|
211
219
|
throw new ApiError(400, 'invalid_request', 'history requires a node_id query param');
|
|
212
220
|
}
|
|
213
|
-
const
|
|
214
|
-
const history = scanPageHistory(nodeId, productKinds);
|
|
221
|
+
const history = scanPageHistory(nodeId);
|
|
215
222
|
const canonicalRoot = realpathSync(ticketsRoot());
|
|
216
223
|
const tickets = history.map((item) => {
|
|
217
224
|
const ticketId = createHash('sha256').update(`${canonicalRoot}\0${item.id}`, 'utf8').digest('hex');
|
|
@@ -222,14 +229,17 @@ function handleHistory(ctx) {
|
|
|
222
229
|
placement: item.placement,
|
|
223
230
|
dialect: item.dialect,
|
|
224
231
|
steps: item.steps,
|
|
225
|
-
|
|
232
|
+
emitted_at: item.emittedAt,
|
|
226
233
|
source: item.source,
|
|
227
234
|
slot_kinds: item.slotKinds,
|
|
235
|
+
inbox: item.inbox,
|
|
228
236
|
awaits_response: item.awaitsResponse,
|
|
229
237
|
state: item.state,
|
|
230
238
|
};
|
|
231
239
|
if (item.subtitle !== '')
|
|
232
240
|
dto.subtitle = item.subtitle;
|
|
241
|
+
if (item.answerDigest !== undefined)
|
|
242
|
+
dto.answer_digest = item.answerDigest;
|
|
233
243
|
return dto;
|
|
234
244
|
});
|
|
235
245
|
return { status: 200, body: { tickets } };
|
|
@@ -245,10 +255,9 @@ function handleGetPage(ctx) {
|
|
|
245
255
|
if (resolved.kind === 'review') {
|
|
246
256
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have no page read operation');
|
|
247
257
|
}
|
|
248
|
-
const productKinds = readConfig('user').page_components ?? [];
|
|
249
258
|
let ticket;
|
|
250
259
|
try {
|
|
251
|
-
ticket = readPageTicket(resolved.dir
|
|
260
|
+
ticket = readPageTicket(resolved.dir);
|
|
252
261
|
}
|
|
253
262
|
catch (err) {
|
|
254
263
|
throw new ApiError(500, 'internal', 'the ticket could not be read');
|
|
@@ -262,9 +271,10 @@ function handleGetPage(ctx) {
|
|
|
262
271
|
kind: 'page',
|
|
263
272
|
state: ticket.state,
|
|
264
273
|
page: pageManifestToDTO(ticket.manifest),
|
|
265
|
-
document: ticket.document,
|
|
266
|
-
document_media_type: ticket.manifest.dialect === '
|
|
274
|
+
document: ticket.manifest.dialect === 'jsx' ? ticket.module : ticket.document,
|
|
275
|
+
document_media_type: ticket.manifest.dialect === 'jsx' ? 'text/jsx' : 'text/html',
|
|
267
276
|
progress: progressDTO,
|
|
277
|
+
feedback: toPageFeedbackDTO(readPageFeedback(resolved.dir)),
|
|
268
278
|
},
|
|
269
279
|
};
|
|
270
280
|
}
|
|
@@ -280,6 +290,8 @@ async function handleRespond(ctx) {
|
|
|
280
290
|
if (resolved.kind === 'review') {
|
|
281
291
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets submit through their own path');
|
|
282
292
|
}
|
|
293
|
+
if (readPageTicket(resolved.dir).state === 'passive')
|
|
294
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
283
295
|
const result = readTicketResult(resolved.dir);
|
|
284
296
|
if (result !== null && result.kind !== 'canceled') {
|
|
285
297
|
throw new ApiError(409, 'ticket_already_resolved', 'ticket was already resolved');
|
|
@@ -287,9 +299,8 @@ async function handleRespond(ctx) {
|
|
|
287
299
|
if (result?.kind === 'canceled') {
|
|
288
300
|
throw new ApiError(409, 'ticket_canceled', 'canceled tickets cannot be answered');
|
|
289
301
|
}
|
|
290
|
-
const productKinds = readConfig('user').page_components ?? [];
|
|
291
302
|
try {
|
|
292
|
-
validatePageTicketResponses(resolved.dir, request.responses
|
|
303
|
+
validatePageTicketResponses(resolved.dir, request.responses);
|
|
293
304
|
}
|
|
294
305
|
catch (error) {
|
|
295
306
|
if (isSlotValidationError(error)) {
|
|
@@ -298,8 +309,8 @@ async function handleRespond(ctx) {
|
|
|
298
309
|
throw new ApiError(500, 'internal', 'validation failed');
|
|
299
310
|
}
|
|
300
311
|
try {
|
|
301
|
-
// The shared completion path
|
|
302
|
-
const completed = await resolvePageTicket(resolved.
|
|
312
|
+
// The shared completion path publishes the result and follows the ticket's optional reply route.
|
|
313
|
+
const completed = await resolvePageTicket(resolved.ticketId, request.responses);
|
|
303
314
|
if (completed.kind !== 'page') {
|
|
304
315
|
throw new ApiError(409, 'ticket_kind_mismatch', 'unexpected result kind');
|
|
305
316
|
}
|
|
@@ -326,13 +337,14 @@ function handleProgress(ctx) {
|
|
|
326
337
|
if (resolved.kind === 'review') {
|
|
327
338
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets do not support progress autosave');
|
|
328
339
|
}
|
|
340
|
+
if (readPageTicket(resolved.dir).state === 'passive')
|
|
341
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
329
342
|
const result = readTicketResult(resolved.dir);
|
|
330
343
|
if (result !== null && result.kind !== 'canceled') {
|
|
331
344
|
throw new ApiError(409, 'ticket_already_resolved', 'ticket was already resolved');
|
|
332
345
|
}
|
|
333
|
-
const productKinds = readConfig('user').page_components ?? [];
|
|
334
346
|
try {
|
|
335
|
-
writePageProgress(resolved.dir, request.responses
|
|
347
|
+
writePageProgress(resolved.dir, request.responses);
|
|
336
348
|
return { status: 200, body: {} };
|
|
337
349
|
}
|
|
338
350
|
catch (error) {
|
|
@@ -354,8 +366,10 @@ async function handleCancel(ctx) {
|
|
|
354
366
|
if (resolved.kind === 'review') {
|
|
355
367
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have their own cancel path');
|
|
356
368
|
}
|
|
369
|
+
if (readPageTicket(resolved.dir).state === 'passive')
|
|
370
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
357
371
|
try {
|
|
358
|
-
const result = await cancelHumanTicket(resolved.
|
|
372
|
+
const result = await cancelHumanTicket(resolved.ticketId, {
|
|
359
373
|
reason: request.reason ?? DEFAULT_CANCEL_REASON,
|
|
360
374
|
actor: 'human',
|
|
361
375
|
});
|
|
@@ -390,93 +404,18 @@ function handleGetResponse(ctx) {
|
|
|
390
404
|
if (result.kind !== 'page') {
|
|
391
405
|
throw new ApiError(409, 'ticket_kind_mismatch', 'unexpected result kind');
|
|
392
406
|
}
|
|
393
|
-
return { status: 200, body: result };
|
|
394
|
-
}
|
|
395
|
-
// ===========================================================================
|
|
396
|
-
// GET /v1/human/inbox/:ticket_id/render — the page's HTML payload
|
|
397
|
-
// ===========================================================================
|
|
398
|
-
function handleRenderPage(ctx) {
|
|
399
|
-
const ticketId = requireTicketId(ctx.params);
|
|
400
|
-
const resolved = resolveTicket(ticketId);
|
|
401
|
-
if (resolved === null)
|
|
402
|
-
throw new ApiError(404, 'ticket_not_found', 'no matching ticket');
|
|
403
|
-
if (resolved.kind === 'review') {
|
|
404
|
-
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have no page render operation');
|
|
405
|
-
}
|
|
406
|
-
const productKinds = readConfig('user').page_components ?? [];
|
|
407
|
-
// Addressable for pending, resolved, and canceled tickets alike — the same
|
|
408
|
-
// read rule as the detail route: a page stays readable after it resolves.
|
|
409
|
-
let ticket;
|
|
410
407
|
try {
|
|
411
|
-
|
|
408
|
+
const manifest = parsePage(resolved.dir);
|
|
409
|
+
return { status: 200, body: { ...result, answer: describePageAnswer(manifest, result.responses, { completedAt: result.completedAt }) } };
|
|
412
410
|
}
|
|
413
411
|
catch {
|
|
414
|
-
throw new ApiError(500, 'internal', 'the ticket could not be read');
|
|
415
|
-
}
|
|
416
|
-
try {
|
|
417
|
-
return { status: 200, body: { html: renderPageHtml(ticket.manifest, ticket.document, productKinds) } };
|
|
412
|
+
throw new ApiError(500, 'internal', 'the ticket answer could not be read');
|
|
418
413
|
}
|
|
419
|
-
catch {
|
|
420
|
-
throw new ApiError(500, 'internal', 'the page could not be rendered');
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// ===========================================================================
|
|
424
|
-
// GET /v1/human/pages/bundle.js|.css — the page runtime assets
|
|
425
|
-
// ===========================================================================
|
|
426
|
-
const BUNDLE_MEDIA_TYPES = {
|
|
427
|
-
'bundle.js': 'text/javascript; charset=utf-8',
|
|
428
|
-
'bundle.css': 'text/css; charset=utf-8',
|
|
429
|
-
};
|
|
430
|
-
const bundleCache = new Map();
|
|
431
|
-
/** Read one built bundle asset, reusing the cached bytes while the file on disk
|
|
432
|
-
* is unchanged. A runtime generation swap changes mtime, so the next request
|
|
433
|
-
* re-reads and re-hashes. */
|
|
434
|
-
function readBundleAsset(name) {
|
|
435
|
-
let path;
|
|
436
|
-
let stat;
|
|
437
|
-
try {
|
|
438
|
-
path = join(assetRoot(), 'pages', name);
|
|
439
|
-
stat = statSync(path);
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
throw new ApiError(500, 'page_bundle_unavailable', `${name} is not present in this runtime generation; the page runtime is built by \`npm run build\``);
|
|
443
|
-
}
|
|
444
|
-
const cached = bundleCache.get(name);
|
|
445
|
-
if (cached !== undefined && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size)
|
|
446
|
-
return cached;
|
|
447
|
-
const bytes = readFileSync(path);
|
|
448
|
-
const asset = {
|
|
449
|
-
mtimeMs: stat.mtimeMs,
|
|
450
|
-
size: stat.size,
|
|
451
|
-
bytes,
|
|
452
|
-
etag: `"${createHash('sha256').update(bytes).digest('hex').slice(0, 32)}"`,
|
|
453
|
-
};
|
|
454
|
-
bundleCache.set(name, asset);
|
|
455
|
-
return asset;
|
|
456
|
-
}
|
|
457
|
-
function etagMatches(header, etag) {
|
|
458
|
-
if (header === undefined)
|
|
459
|
-
return false;
|
|
460
|
-
return header.split(',').map((candidate) => candidate.trim()).some((candidate) => candidate === '*' || candidate === etag || candidate === `W/${etag}`);
|
|
461
|
-
}
|
|
462
|
-
function handleBundleAsset(name) {
|
|
463
|
-
return (ctx) => {
|
|
464
|
-
const asset = readBundleAsset(name);
|
|
465
|
-
const headers = { etag: asset.etag, 'cache-control': 'no-cache' };
|
|
466
|
-
const ifNoneMatch = ctx.req.headers['if-none-match'];
|
|
467
|
-
if (etagMatches(Array.isArray(ifNoneMatch) ? ifNoneMatch.join(',') : ifNoneMatch, asset.etag)) {
|
|
468
|
-
return { status: 304, headers };
|
|
469
|
-
}
|
|
470
|
-
return { status: 200, rawBody: asset.bytes, headers: { ...headers, 'content-type': BUNDLE_MEDIA_TYPES[name] } };
|
|
471
|
-
};
|
|
472
414
|
}
|
|
473
415
|
export const inboxRoutes = [
|
|
474
|
-
{ method: 'GET', pattern: '/v1/human/pages/bundle.js', handler: handleBundleAsset('bundle.js') },
|
|
475
|
-
{ method: 'GET', pattern: '/v1/human/pages/bundle.css', handler: handleBundleAsset('bundle.css') },
|
|
476
416
|
{ method: 'GET', pattern: '/v1/human/inbox', handler: handleList },
|
|
477
417
|
{ method: 'GET', pattern: '/v1/human/inbox/history', handler: handleHistory },
|
|
478
418
|
{ method: 'GET', pattern: '/v1/human/inbox/:ticket_id', handler: handleGetPage },
|
|
479
|
-
{ method: 'GET', pattern: '/v1/human/inbox/:ticket_id/render', handler: handleRenderPage },
|
|
480
419
|
{ method: 'GET', pattern: '/v1/human/inbox/:ticket_id/response', handler: handleGetResponse },
|
|
481
420
|
{ method: 'POST', pattern: '/v1/human/inbox/:ticket_id/respond', handler: handleRespond },
|
|
482
421
|
{ method: 'POST', pattern: '/v1/human/inbox/:ticket_id/progress', handler: handleProgress },
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
// Profile handlers (spec §6.6).
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// deliberately multi-writer fs store serialized by `withProfileManifestLock`
|
|
5
|
-
// (spec §12), so crtrd installing a profile here coexists with the CLI verbs.
|
|
1
|
+
// Profile handlers (spec §6.6). Manifest-only operations share the locked
|
|
2
|
+
// profile filesystem store. Deletion crosses canvas, cron, inbox, review, and
|
|
3
|
+
// broker state, so it is one daemon-owned lifecycle operation.
|
|
6
4
|
//
|
|
7
5
|
// PUT /v1/profiles/:name idempotent ensure (create-or-return)
|
|
8
6
|
// GET /v1/profiles list all profiles
|
|
9
7
|
// GET /v1/profiles/:name resolve one profile by name or id
|
|
10
|
-
// DELETE /v1/profiles/:name
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { CrtrError } from '../../../core/errors.js';
|
|
14
|
-
import { createProfile, deleteProfile, ensureRootProfile, listProfiles, loadProfileManifest, } from '../../../core/profiles/manifest.js';
|
|
8
|
+
// DELETE /v1/profiles/:name required-force cascade or detach
|
|
9
|
+
import { createProfile, ensureRootProfile, listProfiles, loadProfileManifest, } from '../../../core/profiles/manifest.js';
|
|
10
|
+
import { requestProfileDeletion } from '../../profile-delete.js';
|
|
15
11
|
import { toProfileDTO } from '../map.js';
|
|
16
12
|
const ROOT_PROFILE_NAME = 'root';
|
|
17
13
|
/** `PUT /v1/profiles/:name` — idempotent ensure. `root` maps to the singleton
|
|
@@ -41,25 +37,11 @@ function handleGet(ctx) {
|
|
|
41
37
|
const name = ctx.params['name'];
|
|
42
38
|
return { status: 200, body: toProfileDTO(loadProfileManifest(name)) };
|
|
43
39
|
}
|
|
44
|
-
/** `DELETE /v1/profiles/:name` —
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* deleted". */
|
|
50
|
-
function handleDelete(ctx) {
|
|
51
|
-
const name = ctx.params['name'];
|
|
52
|
-
let profileId;
|
|
53
|
-
try {
|
|
54
|
-
({ profileId } = loadProfileManifest(name));
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
if (err instanceof CrtrError && err.code === 'not_found')
|
|
58
|
-
return { status: 204 };
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
61
|
-
deleteProfile(profileId);
|
|
62
|
-
return { status: 204 };
|
|
40
|
+
/** `DELETE /v1/profiles/:name` — forced cascade by default, or forced
|
|
41
|
+
* identity detach when requested. Exact resolution, blockers, counts, broker
|
|
42
|
+
* teardown, and final manifest removal all live in the daemon lifecycle. */
|
|
43
|
+
async function handleDelete(ctx) {
|
|
44
|
+
return requestProfileDeletion(ctx.params['name'], ctx.body);
|
|
63
45
|
}
|
|
64
46
|
export const profileRoutes = [
|
|
65
47
|
{ method: 'PUT', pattern: '/v1/profiles/:name', handler: handleEnsure },
|
package/dist/daemon/api/map.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ import type { ProfileEntry } from '../../core/profiles/manifest.js';
|
|
|
5
5
|
import type { ReviveResult } from '../../core/runtime/revive.js';
|
|
6
6
|
import type { CloseNodeResult } from '../../core/runtime/close.js';
|
|
7
7
|
import { type PushResult } from '../../core/feed/feed.js';
|
|
8
|
-
import type {
|
|
8
|
+
import type { PageFeedback, PageFeedbackComment } from '../../core/human/feedback.js';
|
|
9
|
+
import type { CloseResultDTO, CronDTO, CronRunDTO, ErrorBody, NodeEdgesDTO, NodePathsDTO, NodeStatusDTO, NodeSummaryDTO, NodeDetailDTO, NodeWorktreeDTO, ProfileDTO, ReviewCommentDTO, ReviewCommentEventDTO, ReviewDTO, FeedbackResultDTO, PageFeedbackCommentDTO, PageFeedbackDTO, PushReportResultDTO, ReviveResultDTO, SubscriptionDTO } from '../../api/index.js';
|
|
9
10
|
/** Project the queryable row (what `listNodes` yields) into a summary DTO. */
|
|
10
11
|
export declare function toNodeSummaryDTO(row: NodeRow): NodeSummaryDTO;
|
|
11
12
|
/** The subscription/child edges a detail projection needs. The handler gathers
|
|
@@ -70,6 +71,10 @@ export interface ReviewDTOExtras {
|
|
|
70
71
|
export declare function toReviewDTO(row: ReviewRecord, extras?: ReviewDTOExtras): ReviewDTO;
|
|
71
72
|
/** Project one daemon-owned comment, omitting absent node identities on human actions. */
|
|
72
73
|
export declare function toReviewCommentDTO(comment: ReviewComment): ReviewCommentDTO;
|
|
74
|
+
/** Project one ticket-attached page feedback comment. */
|
|
75
|
+
export declare function toPageFeedbackCommentDTO(comment: PageFeedbackComment): PageFeedbackCommentDTO;
|
|
76
|
+
/** Project one ticket's page feedback state. */
|
|
77
|
+
export declare function toPageFeedbackDTO(feedback: PageFeedback): PageFeedbackDTO;
|
|
73
78
|
/** Project one immutable review-comment audit event. */
|
|
74
79
|
export declare function toReviewCommentEventDTO(event: ReviewCommentEvent): ReviewCommentEventDTO;
|
|
75
80
|
/** Project the immutable snake_case review result to the existing camelCase feedback wire shape. */
|
package/dist/daemon/api/map.js
CHANGED
|
@@ -81,6 +81,8 @@ export function toNodeWorktreeDTO(w) {
|
|
|
81
81
|
base_sha: w.base_sha,
|
|
82
82
|
created: w.created,
|
|
83
83
|
};
|
|
84
|
+
if (w.cleanup !== undefined)
|
|
85
|
+
dto.cleanup = w.cleanup;
|
|
84
86
|
if (w.closed !== undefined)
|
|
85
87
|
dto.closed = w.closed;
|
|
86
88
|
return dto;
|
|
@@ -130,6 +132,7 @@ export function toNodeDetailDTO(meta, edges) {
|
|
|
130
132
|
dto.review_binding = meta.review_binding === null
|
|
131
133
|
? null
|
|
132
134
|
: {
|
|
135
|
+
...(meta.review_binding.kind === undefined ? {} : { kind: meta.review_binding.kind }),
|
|
133
136
|
review_id: meta.review_binding.review_id,
|
|
134
137
|
origin_node_id: meta.review_binding.origin_node_id,
|
|
135
138
|
branch_file: meta.review_binding.branch_file,
|
|
@@ -331,6 +334,26 @@ export function toReviewCommentDTO(comment) {
|
|
|
331
334
|
created_seq: comment.created_seq,
|
|
332
335
|
};
|
|
333
336
|
}
|
|
337
|
+
/** Project one ticket-attached page feedback comment. */
|
|
338
|
+
export function toPageFeedbackCommentDTO(comment) {
|
|
339
|
+
return {
|
|
340
|
+
id: comment.id,
|
|
341
|
+
quote: comment.quote,
|
|
342
|
+
...(comment.context === undefined ? {} : { context: comment.context }),
|
|
343
|
+
note: comment.note,
|
|
344
|
+
status: comment.status,
|
|
345
|
+
created_at: comment.createdAt,
|
|
346
|
+
...(comment.resolvedAt === undefined ? {} : { resolved_at: comment.resolvedAt }),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/** Project one ticket's page feedback state. */
|
|
350
|
+
export function toPageFeedbackDTO(feedback) {
|
|
351
|
+
return {
|
|
352
|
+
companion_node_id: feedback.companionNodeId ?? null,
|
|
353
|
+
open_count: feedback.comments.filter((comment) => comment.status === 'open').length,
|
|
354
|
+
comments: feedback.comments.map(toPageFeedbackCommentDTO),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
334
357
|
/** Project one immutable review-comment audit event. */
|
|
335
358
|
export function toReviewCommentEventDTO(event) {
|
|
336
359
|
return {
|
|
@@ -28,6 +28,7 @@ import { focusRoutes } from './handlers/focus.js';
|
|
|
28
28
|
import { healthRoutes } from './handlers/health.js';
|
|
29
29
|
import { humanRoutes } from './handlers/human.js';
|
|
30
30
|
import { inboxRoutes } from './handlers/inbox.js';
|
|
31
|
+
import { feedbackCommentRoutes } from './handlers/feedback-comments.js';
|
|
31
32
|
import { memoryRoutes } from './handlers/memory.js';
|
|
32
33
|
import { messageRoutes } from './handlers/messages.js';
|
|
33
34
|
import { modelAuthRoutes } from './handlers/modelauth.js';
|
|
@@ -64,7 +65,8 @@ function buildRouter() {
|
|
|
64
65
|
.registerAll(humanRoutes)
|
|
65
66
|
.registerAll(reviewRoutes)
|
|
66
67
|
.registerAll(reviewCommentRoutes)
|
|
67
|
-
.registerAll(inboxRoutes)
|
|
68
|
+
.registerAll(inboxRoutes)
|
|
69
|
+
.registerAll(feedbackCommentRoutes);
|
|
68
70
|
}
|
|
69
71
|
/** Is this request an attach upgrade — `GET /v1/nodes/{id}/attach` — and if so,
|
|
70
72
|
* what is `{id}`? Returns null for any non-attach path (the caller destroys the
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Companion retirement — the one lifecycle exit shared by human-review and
|
|
2
|
+
// page-feedback companions. Lives outside review/finish.ts so human/finish.ts
|
|
3
|
+
// can retire a feedback companion without importing the review finisher (which
|
|
4
|
+
// itself imports human/finish.ts).
|
|
5
|
+
import { getNode } from '../core/canvas/canvas.js';
|
|
6
|
+
import { transition } from '../core/runtime/lifecycle.js';
|
|
7
|
+
import { headlessBrokerHost } from '../core/runtime/host.js';
|
|
8
|
+
/** Retire a companion only through a lifecycle transition legal for its current status. */
|
|
9
|
+
export function retireCompanion(nodeId, outcome) {
|
|
10
|
+
const node = getNode(nodeId);
|
|
11
|
+
if (node === null)
|
|
12
|
+
return 'missing';
|
|
13
|
+
if (node.status === 'done' || node.status === 'canceled')
|
|
14
|
+
return 'already';
|
|
15
|
+
if (outcome === 'approved' && (node.status === 'active' || node.status === 'idle')) {
|
|
16
|
+
transition(nodeId, 'finish');
|
|
17
|
+
try {
|
|
18
|
+
headlessBrokerHost.teardown(nodeId);
|
|
19
|
+
}
|
|
20
|
+
catch { /* the terminal transition is authoritative */ }
|
|
21
|
+
return 'retired';
|
|
22
|
+
}
|
|
23
|
+
// Cancellation is legal from every status. An already-dead companion cannot
|
|
24
|
+
// be finished, so it is retired as canceled even when approval won.
|
|
25
|
+
transition(nodeId, 'cancel');
|
|
26
|
+
if (node.status === 'active' || node.status === 'idle') {
|
|
27
|
+
try {
|
|
28
|
+
headlessBrokerHost.teardown(nodeId);
|
|
29
|
+
}
|
|
30
|
+
catch { /* the terminal transition is authoritative */ }
|
|
31
|
+
}
|
|
32
|
+
return 'retired';
|
|
33
|
+
}
|