@north-light/crouter 0.3.198 → 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 +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__/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/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 +4 -4
- 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
|
@@ -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,18 +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
11
|
import { parsePage } from '../../../core/human/page.js';
|
|
11
|
-
import { renderPageHtml } from '../../../core/human/page-render.js';
|
|
12
12
|
import { ticketsRoot } from '../../../core/human/root.js';
|
|
13
13
|
import { scanInbox, scanPageHistory } from '../../../core/human/scan.js';
|
|
14
14
|
import { readPageTicket, readTicketResult, validatePageTicketResponses, writePageProgress, } from '../../../core/human/tickets.js';
|
|
15
15
|
import { describePageAnswer } from '../../../core/human/answer.js';
|
|
16
|
-
import { readConfig } from '../../../core/config.js';
|
|
17
16
|
import { cancelHumanTicket, resolvePageTicket } from '../../human/finish.js';
|
|
18
17
|
import { filterTerminalReviewTickets } from '../../../core/review/ticket-filter.js';
|
|
19
18
|
const DEFAULT_CANCEL_REASON = 'Canceled from the inbox.';
|
|
@@ -25,7 +24,9 @@ const TICKET_ID_PATTERN = /^[a-f0-9]{64}$/;
|
|
|
25
24
|
function ticketHash(canonicalRoot, ticketBasename) {
|
|
26
25
|
return createHash('sha256').update(`${canonicalRoot}\0${ticketBasename}`, 'utf8').digest('hex');
|
|
27
26
|
}
|
|
28
|
-
|
|
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) {
|
|
29
30
|
let canonicalRoot;
|
|
30
31
|
try {
|
|
31
32
|
canonicalRoot = realpathSync(ticketsRoot());
|
|
@@ -56,13 +57,13 @@ function resolveTicket(ticketId) {
|
|
|
56
57
|
if (resolve(canonicalDir, '..') !== canonicalRoot || basename(canonicalDir) !== entry)
|
|
57
58
|
continue;
|
|
58
59
|
if (existsSync(pageManifestPath(canonicalDir)))
|
|
59
|
-
return {
|
|
60
|
+
return { ticketId: entry, dir: canonicalDir, kind: 'page' };
|
|
60
61
|
if (existsSync(reviewPath(canonicalDir)))
|
|
61
|
-
return {
|
|
62
|
+
return { ticketId: entry, dir: canonicalDir, kind: 'review' };
|
|
62
63
|
}
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
65
|
-
function requireTicketId(params) {
|
|
66
|
+
export function requireTicketId(params) {
|
|
66
67
|
const ticketId = params['ticket_id'];
|
|
67
68
|
if (ticketId === undefined || !TICKET_ID_PATTERN.test(ticketId)) {
|
|
68
69
|
throw new ApiError(400, 'invalid_request', 'invalid ticket id');
|
|
@@ -88,10 +89,10 @@ function pageSlotToDTO(slot) {
|
|
|
88
89
|
}
|
|
89
90
|
function pageManifestToDTO(manifest) {
|
|
90
91
|
const dto = {
|
|
91
|
-
schema: 'crtr.page/
|
|
92
|
+
schema: 'crtr.page/v2',
|
|
92
93
|
dialect: manifest.dialect,
|
|
93
94
|
title: manifest.title,
|
|
94
|
-
|
|
95
|
+
delivery: manifest.delivery,
|
|
95
96
|
document: manifest.document,
|
|
96
97
|
steps: manifest.steps,
|
|
97
98
|
slots: manifest.slots.map(pageSlotToDTO),
|
|
@@ -104,8 +105,8 @@ function pageManifestToDTO(manifest) {
|
|
|
104
105
|
source.sessionName = manifest.source.sessionName;
|
|
105
106
|
if (manifest.source.askedBy !== undefined)
|
|
106
107
|
source.askedBy = manifest.source.askedBy;
|
|
107
|
-
if (manifest.source.
|
|
108
|
-
source.
|
|
108
|
+
if (manifest.source.emittedAt !== undefined)
|
|
109
|
+
source.emittedAt = manifest.source.emittedAt;
|
|
109
110
|
if (manifest.source.profileName !== undefined)
|
|
110
111
|
source.profileName = manifest.source.profileName;
|
|
111
112
|
if (manifest.source.nodeId !== undefined)
|
|
@@ -169,8 +170,7 @@ function isSlotValidationError(err) {
|
|
|
169
170
|
// GET /v1/human/inbox — enumerate
|
|
170
171
|
// ===========================================================================
|
|
171
172
|
function handleList() {
|
|
172
|
-
const
|
|
173
|
-
const allItems = scanInbox(productKinds);
|
|
173
|
+
const allItems = scanInbox();
|
|
174
174
|
const filtered = filterTerminalReviewTickets(allItems);
|
|
175
175
|
const tickets = filtered.map((item) => {
|
|
176
176
|
if (item.kind === 'page') {
|
|
@@ -182,9 +182,10 @@ function handleList() {
|
|
|
182
182
|
placement: item.placement,
|
|
183
183
|
dialect: item.dialect,
|
|
184
184
|
steps: item.steps,
|
|
185
|
-
|
|
185
|
+
emitted_at: item.emittedAt,
|
|
186
186
|
source: item.source,
|
|
187
187
|
slot_kinds: item.slotKinds,
|
|
188
|
+
inbox: item.inbox,
|
|
188
189
|
awaits_response: item.awaitsResponse,
|
|
189
190
|
};
|
|
190
191
|
if (item.subtitle !== '')
|
|
@@ -203,7 +204,8 @@ function handleList() {
|
|
|
203
204
|
}
|
|
204
205
|
throw new Error(`unknown ticket kind: ${item.kind}`);
|
|
205
206
|
}).sort((a, b) => {
|
|
206
|
-
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));
|
|
207
209
|
return byTime !== 0 ? byTime : a.ticket_id.localeCompare(b.ticket_id);
|
|
208
210
|
});
|
|
209
211
|
return { status: 200, body: { tickets } };
|
|
@@ -216,8 +218,7 @@ function handleHistory(ctx) {
|
|
|
216
218
|
if (!nodeId || typeof nodeId !== 'string') {
|
|
217
219
|
throw new ApiError(400, 'invalid_request', 'history requires a node_id query param');
|
|
218
220
|
}
|
|
219
|
-
const
|
|
220
|
-
const history = scanPageHistory(nodeId, productKinds);
|
|
221
|
+
const history = scanPageHistory(nodeId);
|
|
221
222
|
const canonicalRoot = realpathSync(ticketsRoot());
|
|
222
223
|
const tickets = history.map((item) => {
|
|
223
224
|
const ticketId = createHash('sha256').update(`${canonicalRoot}\0${item.id}`, 'utf8').digest('hex');
|
|
@@ -228,9 +229,10 @@ function handleHistory(ctx) {
|
|
|
228
229
|
placement: item.placement,
|
|
229
230
|
dialect: item.dialect,
|
|
230
231
|
steps: item.steps,
|
|
231
|
-
|
|
232
|
+
emitted_at: item.emittedAt,
|
|
232
233
|
source: item.source,
|
|
233
234
|
slot_kinds: item.slotKinds,
|
|
235
|
+
inbox: item.inbox,
|
|
234
236
|
awaits_response: item.awaitsResponse,
|
|
235
237
|
state: item.state,
|
|
236
238
|
};
|
|
@@ -253,10 +255,9 @@ function handleGetPage(ctx) {
|
|
|
253
255
|
if (resolved.kind === 'review') {
|
|
254
256
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have no page read operation');
|
|
255
257
|
}
|
|
256
|
-
const productKinds = readConfig('user').page_components;
|
|
257
258
|
let ticket;
|
|
258
259
|
try {
|
|
259
|
-
ticket = readPageTicket(resolved.dir
|
|
260
|
+
ticket = readPageTicket(resolved.dir);
|
|
260
261
|
}
|
|
261
262
|
catch (err) {
|
|
262
263
|
throw new ApiError(500, 'internal', 'the ticket could not be read');
|
|
@@ -270,9 +271,10 @@ function handleGetPage(ctx) {
|
|
|
270
271
|
kind: 'page',
|
|
271
272
|
state: ticket.state,
|
|
272
273
|
page: pageManifestToDTO(ticket.manifest),
|
|
273
|
-
document: ticket.document,
|
|
274
|
-
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',
|
|
275
276
|
progress: progressDTO,
|
|
277
|
+
feedback: toPageFeedbackDTO(readPageFeedback(resolved.dir)),
|
|
276
278
|
},
|
|
277
279
|
};
|
|
278
280
|
}
|
|
@@ -288,6 +290,8 @@ async function handleRespond(ctx) {
|
|
|
288
290
|
if (resolved.kind === 'review') {
|
|
289
291
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets submit through their own path');
|
|
290
292
|
}
|
|
293
|
+
if (readPageTicket(resolved.dir).state === 'passive')
|
|
294
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
291
295
|
const result = readTicketResult(resolved.dir);
|
|
292
296
|
if (result !== null && result.kind !== 'canceled') {
|
|
293
297
|
throw new ApiError(409, 'ticket_already_resolved', 'ticket was already resolved');
|
|
@@ -295,9 +299,8 @@ async function handleRespond(ctx) {
|
|
|
295
299
|
if (result?.kind === 'canceled') {
|
|
296
300
|
throw new ApiError(409, 'ticket_canceled', 'canceled tickets cannot be answered');
|
|
297
301
|
}
|
|
298
|
-
const productKinds = readConfig('user').page_components;
|
|
299
302
|
try {
|
|
300
|
-
validatePageTicketResponses(resolved.dir, request.responses
|
|
303
|
+
validatePageTicketResponses(resolved.dir, request.responses);
|
|
301
304
|
}
|
|
302
305
|
catch (error) {
|
|
303
306
|
if (isSlotValidationError(error)) {
|
|
@@ -306,8 +309,8 @@ async function handleRespond(ctx) {
|
|
|
306
309
|
throw new ApiError(500, 'internal', 'validation failed');
|
|
307
310
|
}
|
|
308
311
|
try {
|
|
309
|
-
// The shared completion path
|
|
310
|
-
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);
|
|
311
314
|
if (completed.kind !== 'page') {
|
|
312
315
|
throw new ApiError(409, 'ticket_kind_mismatch', 'unexpected result kind');
|
|
313
316
|
}
|
|
@@ -334,13 +337,14 @@ function handleProgress(ctx) {
|
|
|
334
337
|
if (resolved.kind === 'review') {
|
|
335
338
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets do not support progress autosave');
|
|
336
339
|
}
|
|
340
|
+
if (readPageTicket(resolved.dir).state === 'passive')
|
|
341
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
337
342
|
const result = readTicketResult(resolved.dir);
|
|
338
343
|
if (result !== null && result.kind !== 'canceled') {
|
|
339
344
|
throw new ApiError(409, 'ticket_already_resolved', 'ticket was already resolved');
|
|
340
345
|
}
|
|
341
|
-
const productKinds = readConfig('user').page_components;
|
|
342
346
|
try {
|
|
343
|
-
writePageProgress(resolved.dir, request.responses
|
|
347
|
+
writePageProgress(resolved.dir, request.responses);
|
|
344
348
|
return { status: 200, body: {} };
|
|
345
349
|
}
|
|
346
350
|
catch (error) {
|
|
@@ -362,8 +366,10 @@ async function handleCancel(ctx) {
|
|
|
362
366
|
if (resolved.kind === 'review') {
|
|
363
367
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have their own cancel path');
|
|
364
368
|
}
|
|
369
|
+
if (readPageTicket(resolved.dir).state === 'passive')
|
|
370
|
+
throw new ApiError(409, 'page_has_no_response_lifecycle', 'this page has no response lifecycle');
|
|
365
371
|
try {
|
|
366
|
-
const result = await cancelHumanTicket(resolved.
|
|
372
|
+
const result = await cancelHumanTicket(resolved.ticketId, {
|
|
367
373
|
reason: request.reason ?? DEFAULT_CANCEL_REASON,
|
|
368
374
|
actor: 'human',
|
|
369
375
|
});
|
|
@@ -398,100 +404,18 @@ function handleGetResponse(ctx) {
|
|
|
398
404
|
if (result.kind !== 'page') {
|
|
399
405
|
throw new ApiError(409, 'ticket_kind_mismatch', 'unexpected result kind');
|
|
400
406
|
}
|
|
401
|
-
const productKinds = readConfig('user').page_components;
|
|
402
407
|
try {
|
|
403
|
-
const manifest = parsePage(resolved.dir
|
|
404
|
-
return { status: 200, body: { ...result, answer: describePageAnswer(manifest, result.responses, { completedAt: result.completedAt
|
|
408
|
+
const manifest = parsePage(resolved.dir);
|
|
409
|
+
return { status: 200, body: { ...result, answer: describePageAnswer(manifest, result.responses, { completedAt: result.completedAt }) } };
|
|
405
410
|
}
|
|
406
411
|
catch {
|
|
407
412
|
throw new ApiError(500, 'internal', 'the ticket answer could not be read');
|
|
408
413
|
}
|
|
409
414
|
}
|
|
410
|
-
// ===========================================================================
|
|
411
|
-
// GET /v1/human/inbox/:ticket_id/render — the page's HTML payload
|
|
412
|
-
// ===========================================================================
|
|
413
|
-
function handleRenderPage(ctx) {
|
|
414
|
-
const ticketId = requireTicketId(ctx.params);
|
|
415
|
-
const resolved = resolveTicket(ticketId);
|
|
416
|
-
if (resolved === null)
|
|
417
|
-
throw new ApiError(404, 'ticket_not_found', 'no matching ticket');
|
|
418
|
-
if (resolved.kind === 'review') {
|
|
419
|
-
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have no page render operation');
|
|
420
|
-
}
|
|
421
|
-
const productKinds = readConfig('user').page_components;
|
|
422
|
-
// Addressable for pending, resolved, and canceled tickets alike — the same
|
|
423
|
-
// read rule as the detail route: a page stays readable after it resolves.
|
|
424
|
-
let ticket;
|
|
425
|
-
try {
|
|
426
|
-
ticket = readPageTicket(resolved.dir, productKinds);
|
|
427
|
-
}
|
|
428
|
-
catch {
|
|
429
|
-
throw new ApiError(500, 'internal', 'the ticket could not be read');
|
|
430
|
-
}
|
|
431
|
-
try {
|
|
432
|
-
return { status: 200, body: { html: renderPageHtml(ticket.manifest, ticket.document, productKinds) } };
|
|
433
|
-
}
|
|
434
|
-
catch {
|
|
435
|
-
throw new ApiError(500, 'internal', 'the page could not be rendered');
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// ===========================================================================
|
|
439
|
-
// GET /v1/human/pages/bundle.js|.css — the page runtime assets
|
|
440
|
-
// ===========================================================================
|
|
441
|
-
const BUNDLE_MEDIA_TYPES = {
|
|
442
|
-
'bundle.js': 'text/javascript; charset=utf-8',
|
|
443
|
-
'bundle.css': 'text/css; charset=utf-8',
|
|
444
|
-
};
|
|
445
|
-
const bundleCache = new Map();
|
|
446
|
-
/** Read one built bundle asset, reusing the cached bytes while the file on disk
|
|
447
|
-
* is unchanged. A runtime generation swap changes mtime, so the next request
|
|
448
|
-
* re-reads and re-hashes. */
|
|
449
|
-
function readBundleAsset(name) {
|
|
450
|
-
let path;
|
|
451
|
-
let stat;
|
|
452
|
-
try {
|
|
453
|
-
path = join(assetRoot(), 'pages', name);
|
|
454
|
-
stat = statSync(path);
|
|
455
|
-
}
|
|
456
|
-
catch {
|
|
457
|
-
throw new ApiError(500, 'page_bundle_unavailable', `${name} is not present in this runtime generation; the page runtime is built by \`npm run build\``);
|
|
458
|
-
}
|
|
459
|
-
const cached = bundleCache.get(name);
|
|
460
|
-
if (cached !== undefined && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size)
|
|
461
|
-
return cached;
|
|
462
|
-
const bytes = readFileSync(path);
|
|
463
|
-
const asset = {
|
|
464
|
-
mtimeMs: stat.mtimeMs,
|
|
465
|
-
size: stat.size,
|
|
466
|
-
bytes,
|
|
467
|
-
etag: `"${createHash('sha256').update(bytes).digest('hex').slice(0, 32)}"`,
|
|
468
|
-
};
|
|
469
|
-
bundleCache.set(name, asset);
|
|
470
|
-
return asset;
|
|
471
|
-
}
|
|
472
|
-
function etagMatches(header, etag) {
|
|
473
|
-
if (header === undefined)
|
|
474
|
-
return false;
|
|
475
|
-
return header.split(',').map((candidate) => candidate.trim()).some((candidate) => candidate === '*' || candidate === etag || candidate === `W/${etag}`);
|
|
476
|
-
}
|
|
477
|
-
function handleBundleAsset(name) {
|
|
478
|
-
return (ctx) => {
|
|
479
|
-
const asset = readBundleAsset(name);
|
|
480
|
-
const headers = { etag: asset.etag, 'cache-control': 'no-cache' };
|
|
481
|
-
const ifNoneMatch = ctx.req.headers['if-none-match'];
|
|
482
|
-
if (etagMatches(Array.isArray(ifNoneMatch) ? ifNoneMatch.join(',') : ifNoneMatch, asset.etag)) {
|
|
483
|
-
return { status: 304, headers };
|
|
484
|
-
}
|
|
485
|
-
return { status: 200, rawBody: asset.bytes, headers: { ...headers, 'content-type': BUNDLE_MEDIA_TYPES[name] } };
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
415
|
export const inboxRoutes = [
|
|
489
|
-
{ method: 'GET', pattern: '/v1/human/pages/bundle.js', handler: handleBundleAsset('bundle.js') },
|
|
490
|
-
{ method: 'GET', pattern: '/v1/human/pages/bundle.css', handler: handleBundleAsset('bundle.css') },
|
|
491
416
|
{ method: 'GET', pattern: '/v1/human/inbox', handler: handleList },
|
|
492
417
|
{ method: 'GET', pattern: '/v1/human/inbox/history', handler: handleHistory },
|
|
493
418
|
{ method: 'GET', pattern: '/v1/human/inbox/:ticket_id', handler: handleGetPage },
|
|
494
|
-
{ method: 'GET', pattern: '/v1/human/inbox/:ticket_id/render', handler: handleRenderPage },
|
|
495
419
|
{ method: 'GET', pattern: '/v1/human/inbox/:ticket_id/response', handler: handleGetResponse },
|
|
496
420
|
{ method: 'POST', pattern: '/v1/human/inbox/:ticket_id/respond', handler: handleRespond },
|
|
497
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
|
+
}
|