@north-light/crouter 0.3.198 → 0.3.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +18 -18
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +81 -34
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +6 -6
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +26 -1
- package/dist/clients/attach/render/chat-view.js +61 -11
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -993
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -21
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +1 -3
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +71 -27
- package/dist/clients/inbox/tui/slots.d.ts +3 -1
- package/dist/clients/inbox/tui/slots.js +16 -12
- package/dist/clients/inbox/tui/types.d.ts +2 -7
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +71 -114
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{html.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +13 -5
- package/dist/commands/human/prompts.js +160 -97
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +2 -12
- package/dist/commands/human/shared.js +17 -11
- package/dist/commands/human.js +29 -21
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- package/dist/core/__tests__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/helpers/harness.js +17 -55
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +31 -26
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +4 -5
- package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +37 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +2 -1
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +137 -4
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.js +4 -0
- package/dist/core/human/__tests__/page-tickets.test.js +65 -52
- package/dist/core/human/__tests__/page.test.js +56 -98
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
- package/dist/core/human/answer-text.js +3 -3
- package/dist/core/human/answer.d.ts +6 -4
- package/dist/core/human/answer.js +4 -10
- package/dist/core/human/component-docs.d.ts +11 -7
- package/dist/core/human/component-docs.js +361 -88
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +40 -5
- package/dist/core/human/page-catalog.js +102 -30
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +23 -191
- package/dist/core/human/page-schema.js +60 -89
- package/dist/core/human/page-synth.d.ts +1 -1
- package/dist/core/human/page-synth.js +15 -18
- package/dist/core/human/page.d.ts +12 -27
- package/dist/core/human/page.js +142 -474
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -4
- package/dist/core/human/scan.js +16 -15
- package/dist/core/human/summary.d.ts +1 -2
- package/dist/core/human/summary.js +2 -2
- package/dist/core/human/tickets.d.ts +20 -14
- package/dist/core/human/tickets.js +91 -31
- package/dist/core/human/types.d.ts +10 -2
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +3 -3
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +17 -6
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +21 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +39 -115
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +14 -16
- package/dist/daemon/human/finish.d.ts +12 -7
- package/dist/daemon/human/finish.js +101 -38
- package/dist/daemon/human/sweep.js +5 -1
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +3 -1
- package/dist/daemon/review/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.js +19 -5
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +44 -2
- package/dist/types.js +7 -2
- package/package.json +6 -5
- package/runtime.lock.json +5 -33
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/commands/human/doc.d.ts +0 -2
- package/dist/commands/human/doc.js +0 -91
- package/dist/commands/human/html.js +0 -81
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/html-markdown.test.js +0 -52
- package/dist/core/human/__tests__/page-render.test.js +0 -66
- package/dist/core/human/html-markdown.d.ts +0 -3
- package/dist/core/human/html-markdown.js +0 -288
- package/dist/core/human/page-render.d.ts +0 -4
- package/dist/core/human/page-render.js +0 -105
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1778
- package/dist/pages/comments.d.ts +0 -72
- package/dist/pages/comments.js +0 -176
- package/dist/pages/controls.d.ts +0 -98
- package/dist/pages/controls.js +0 -261
- package/dist/pages/elements/cards.d.ts +0 -28
- package/dist/pages/elements/cards.js +0 -915
- package/dist/pages/elements/chart.d.ts +0 -23
- package/dist/pages/elements/chart.js +0 -664
- package/dist/pages/elements/options.d.ts +0 -30
- package/dist/pages/elements/options.js +0 -742
- package/dist/pages/elements/pages.d.ts +0 -30
- package/dist/pages/elements/pages.js +0 -377
- package/dist/pages/elements/slot.d.ts +0 -26
- package/dist/pages/elements/slot.js +0 -112
- package/dist/pages/elements/table.d.ts +0 -36
- package/dist/pages/elements/table.js +0 -1043
- package/dist/pages/elements/text.d.ts +0 -31
- package/dist/pages/elements/text.js +0 -1175
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -134
- package/dist/pages/host.js +0 -165
- package/dist/pages/readonly.d.ts +0 -24
- package/dist/pages/readonly.js +0 -31
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/responses.d.ts +0 -37
- package/dist/pages/responses.js +0 -56
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export declare function ticketsRoot(): string;
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/** The opaque id the `/v1/human/inbox` routes address a ticket by — a hash of
|
|
3
|
+
* the canonical tickets root and the ticket directory basename. The daemon
|
|
4
|
+
* derives the same value when it resolves a route id back to a directory. */
|
|
5
|
+
export declare function opaqueInboxTicketId(ticketId: string): string;
|
|
6
|
+
/** Ticket directories share the canvas node root. Reply-bearing tickets may use their bridge id; standalone pages use an independent ticket id. */
|
|
7
|
+
export declare function ticketDir(ticketId: string): string;
|
|
4
8
|
export declare function inboxActivityPath(): string;
|
|
5
9
|
/** Signal one durable ticket mutation to every open inbox without watching every ticket directory. */
|
|
6
10
|
export declare function signalInboxActivity(): void;
|
package/dist/core/human/root.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { mkdirSync, watch, writeFileSync } from 'node:fs';
|
|
1
|
+
import { mkdirSync, realpathSync, watch, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { basename, dirname, join } from 'node:path';
|
|
3
4
|
import { crtrHome, nodeDir, nodesRoot } from '../canvas/paths.js';
|
|
4
5
|
export function ticketsRoot() {
|
|
5
6
|
return nodesRoot();
|
|
6
7
|
}
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** The opaque id the `/v1/human/inbox` routes address a ticket by — a hash of
|
|
9
|
+
* the canonical tickets root and the ticket directory basename. The daemon
|
|
10
|
+
* derives the same value when it resolves a route id back to a directory. */
|
|
11
|
+
export function opaqueInboxTicketId(ticketId) {
|
|
12
|
+
const canonicalRoot = realpathSync(ticketsRoot());
|
|
13
|
+
return createHash('sha256').update(`${canonicalRoot}\0${ticketId}`, 'utf8').digest('hex');
|
|
14
|
+
}
|
|
15
|
+
/** Ticket directories share the canvas node root. Reply-bearing tickets may use their bridge id; standalone pages use an independent ticket id. */
|
|
16
|
+
export function ticketDir(ticketId) {
|
|
17
|
+
return nodeDir(ticketId);
|
|
10
18
|
}
|
|
11
19
|
export function inboxActivityPath() {
|
|
12
20
|
return join(crtrHome(), 'signals', 'inbox-activity');
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { ProductPageComponents } from '../../types.js';
|
|
2
1
|
import type { PageTicketSummary, TicketSummary } from './types.js';
|
|
3
2
|
/** Read all unresolved page/review tickets, newest first. Progress never affects visibility. */
|
|
4
|
-
export declare function scanInbox(
|
|
3
|
+
export declare function scanInbox(): TicketSummary[];
|
|
5
4
|
export interface PageHistoryEntry extends PageTicketSummary {
|
|
6
|
-
state: 'pending' | 'resolved' | 'canceled';
|
|
5
|
+
state: 'pending' | 'resolved' | 'canceled' | 'passive';
|
|
7
6
|
}
|
|
8
7
|
/**
|
|
9
8
|
* Every page ticket raised by one node — pending, resolved or canceled — oldest
|
|
10
9
|
* first. This is the durable discovery seam for transcript placement, so it is a
|
|
11
10
|
* separate walk from the pending-only inbox scan and never filters on a terminal file.
|
|
12
11
|
*/
|
|
13
|
-
export declare function scanPageHistory(nodeId: string
|
|
12
|
+
export declare function scanPageHistory(nodeId: string): PageHistoryEntry[];
|
package/dist/core/human/scan.js
CHANGED
|
@@ -25,18 +25,18 @@ function reviewSummary(dir, id) {
|
|
|
25
25
|
}
|
|
26
26
|
return { dir, id, kind: 'review', title: review.title, subtitle: review.subtitle, file: review.file, output: review.output, source: review.source, blockedSince: review.blockedSince, claim: claimSummary(dir) };
|
|
27
27
|
}
|
|
28
|
-
function pageSummary(dir, id
|
|
28
|
+
function pageSummary(dir, id) {
|
|
29
29
|
let manifest;
|
|
30
30
|
try {
|
|
31
|
-
manifest = parsePage(dir
|
|
31
|
+
manifest = parsePage(dir);
|
|
32
32
|
}
|
|
33
33
|
catch {
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
-
let
|
|
37
|
-
if (
|
|
36
|
+
let emittedAt = manifest.source?.emittedAt;
|
|
37
|
+
if (emittedAt === undefined) {
|
|
38
38
|
try {
|
|
39
|
-
|
|
39
|
+
emittedAt = statSync(pageManifestPath(dir)).mtime.toISOString();
|
|
40
40
|
}
|
|
41
41
|
catch {
|
|
42
42
|
return null;
|
|
@@ -49,17 +49,18 @@ function pageSummary(dir, id, productKinds) {
|
|
|
49
49
|
title: manifest.title,
|
|
50
50
|
subtitle: manifest.subtitle ?? '',
|
|
51
51
|
dialect: manifest.dialect,
|
|
52
|
-
placement: manifest.placement,
|
|
52
|
+
placement: manifest.delivery.placement,
|
|
53
|
+
inbox: manifest.delivery.inbox,
|
|
53
54
|
steps: manifest.steps,
|
|
54
55
|
slotKinds: manifest.slots.map((slot) => slot.kind),
|
|
55
|
-
awaitsResponse:
|
|
56
|
+
awaitsResponse: manifest.delivery.reply,
|
|
56
57
|
source: manifest.source ?? {},
|
|
57
|
-
|
|
58
|
+
emittedAt,
|
|
58
59
|
claim: claimSummary(dir),
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
/** Read all unresolved page/review tickets, newest first. Progress never affects visibility. */
|
|
62
|
-
export function scanInbox(
|
|
63
|
+
export function scanInbox() {
|
|
63
64
|
let entries;
|
|
64
65
|
try {
|
|
65
66
|
entries = readdirSync(ticketsRoot());
|
|
@@ -79,18 +80,18 @@ export function scanInbox(productKinds = []) {
|
|
|
79
80
|
}
|
|
80
81
|
if (isResolved(dir))
|
|
81
82
|
continue;
|
|
82
|
-
const item = reviewSummary(dir, basename(dir)) ?? pageSummary(dir, basename(dir)
|
|
83
|
-
if (item !== null)
|
|
83
|
+
const item = reviewSummary(dir, basename(dir)) ?? pageSummary(dir, basename(dir));
|
|
84
|
+
if (item !== null && (item.kind !== 'page' || item.inbox))
|
|
84
85
|
items.push(item);
|
|
85
86
|
}
|
|
86
|
-
return items.sort((a, b) => b.blockedSince.localeCompare(a.blockedSince) || a.id.localeCompare(b.id));
|
|
87
|
+
return items.sort((a, b) => (b.kind === 'page' ? b.emittedAt : b.blockedSince).localeCompare(a.kind === 'page' ? a.emittedAt : a.blockedSince) || a.id.localeCompare(b.id));
|
|
87
88
|
}
|
|
88
89
|
/**
|
|
89
90
|
* Every page ticket raised by one node — pending, resolved or canceled — oldest
|
|
90
91
|
* first. This is the durable discovery seam for transcript placement, so it is a
|
|
91
92
|
* separate walk from the pending-only inbox scan and never filters on a terminal file.
|
|
92
93
|
*/
|
|
93
|
-
export function scanPageHistory(nodeId
|
|
94
|
+
export function scanPageHistory(nodeId) {
|
|
94
95
|
let entries;
|
|
95
96
|
try {
|
|
96
97
|
entries = readdirSync(ticketsRoot());
|
|
@@ -108,12 +109,12 @@ export function scanPageHistory(nodeId, productKinds = []) {
|
|
|
108
109
|
catch {
|
|
109
110
|
continue;
|
|
110
111
|
}
|
|
111
|
-
const summary = pageSummary(dir, basename(dir)
|
|
112
|
+
const summary = pageSummary(dir, basename(dir));
|
|
112
113
|
if (summary === null || summary.source.nodeId !== nodeId)
|
|
113
114
|
continue;
|
|
114
115
|
const state = pageTicketState(dir);
|
|
115
116
|
const result = state === 'resolved' ? readTicketResult(dir) : null;
|
|
116
117
|
items.push({ ...summary, state, ...(result?.kind === 'page' ? { answerDigest: result.summary.replace(/\s+/g, ' ').trim() } : {}) });
|
|
117
118
|
}
|
|
118
|
-
return items.sort((a, b) => Date.parse(a.
|
|
119
|
+
return items.sort((a, b) => Date.parse(a.emittedAt) - Date.parse(b.emittedAt) || a.id.localeCompare(b.id));
|
|
119
120
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ProductPageComponents } from '../../types.js';
|
|
2
1
|
import type { PageManifest, PageResponses } from './page-schema.js';
|
|
3
2
|
/** The stable one-line digest stored in response.json. */
|
|
4
|
-
export declare function buildPageSummary(manifest: PageManifest, responses: PageResponses
|
|
3
|
+
export declare function buildPageSummary(manifest: PageManifest, responses: PageResponses): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describePageAnswer } from './answer.js';
|
|
2
2
|
/** The stable one-line digest stored in response.json. */
|
|
3
|
-
export function buildPageSummary(manifest, responses
|
|
4
|
-
return describePageAnswer(manifest, responses, { completedAt: ''
|
|
3
|
+
export function buildPageSummary(manifest, responses) {
|
|
4
|
+
return describePageAnswer(manifest, responses, { completedAt: '' }).digest;
|
|
5
5
|
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type { ReviewDescriptor, TicketResult } from './types.js';
|
|
2
2
|
import type { ProductPageComponents } from '../../types.js';
|
|
3
|
-
import { type
|
|
4
|
-
import { type PageManifest, type PageResponses, type PageSource } from './page-schema.js';
|
|
3
|
+
import { type PageDelivery, type PageManifest, type PageResponses, type PageSource } from './page-schema.js';
|
|
5
4
|
/** Strict decoder for the only canonical final marker. */
|
|
6
5
|
export declare function readTicketResult(dir: string): TicketResult | null;
|
|
7
6
|
export interface SubmitPageOptions {
|
|
8
7
|
dir: string;
|
|
9
8
|
/** The authored page source; copied into the ticket directory as its `document`. */
|
|
10
9
|
sourceFile: string;
|
|
11
|
-
dialect: AuthoredPageDialect;
|
|
12
10
|
source?: PageSource;
|
|
13
11
|
productKinds?: ProductPageComponents;
|
|
12
|
+
delivery?: PageDelivery;
|
|
14
13
|
}
|
|
15
14
|
/** One validated page, ready to publish into a ticket directory. */
|
|
16
15
|
export interface PreparedPage {
|
|
17
|
-
dialect: AuthoredPageDialect;
|
|
18
16
|
/** The authored source, verbatim, as the ticket's `document`. */
|
|
19
17
|
document: string;
|
|
18
|
+
/** The compiled CommonJS module the Gateway evaluates for JSX pages. */
|
|
19
|
+
module?: string;
|
|
20
20
|
manifest: PageManifest;
|
|
21
21
|
}
|
|
22
22
|
/** Validate one authored page source into publishable form. Reads only: a page
|
|
@@ -25,7 +25,12 @@ export interface PreparedPage {
|
|
|
25
25
|
* The source is read exactly once, so the published document is the same bytes
|
|
26
26
|
* the manifest was derived from even if the file changes underneath. */
|
|
27
27
|
export declare function preparePage(opts: Omit<SubmitPageOptions, 'dir'>): PreparedPage;
|
|
28
|
-
/** Publish one prepared page ticket: the authored source plus its manifest
|
|
28
|
+
/** Publish one prepared page ticket: the authored source plus its manifest.
|
|
29
|
+
* These writes are overwrites. A ticket's REQUEST is whatever page the agent
|
|
30
|
+
* currently wants on screen, and `crtr human send --replace` revises it in
|
|
31
|
+
* place while the human watches; only the ANSWER is write-once, which
|
|
32
|
+
* `publishJsonExclusive` still guarantees for response.json alone. The manifest
|
|
33
|
+
* switches after all new generation files exist, then stale media is removed. */
|
|
29
34
|
export declare function publishPage(dir: string, prepared: PreparedPage): {
|
|
30
35
|
id: string;
|
|
31
36
|
dir: string;
|
|
@@ -54,26 +59,27 @@ export declare function submitReview(opts: SubmitReviewOptions): {
|
|
|
54
59
|
kind: 'review';
|
|
55
60
|
};
|
|
56
61
|
/** Validate a page resolution against its canonical ticket before claim takeover. */
|
|
57
|
-
export declare function validatePageTicketResponses(dir: string, responses: unknown
|
|
62
|
+
export declare function validatePageTicketResponses(dir: string, responses: unknown): PageResponses;
|
|
58
63
|
/** The three terminal states of a page ticket, derived from its published result. */
|
|
59
|
-
export declare function pageTicketState(dir: string): 'pending' | 'resolved' | 'canceled';
|
|
60
|
-
/** Autosave partial page work. Every supplied response is validated; omitted
|
|
61
|
-
export declare function writePageProgress(dir: string, responses: unknown
|
|
64
|
+
export declare function pageTicketState(dir: string): 'pending' | 'resolved' | 'canceled' | 'passive';
|
|
65
|
+
/** Autosave partial page work. Every supplied response is validated; omitted components are permitted. */
|
|
66
|
+
export declare function writePageProgress(dir: string, responses: unknown): PageResponses;
|
|
62
67
|
/** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
|
|
63
|
-
export declare function readPageProgress(dir: string
|
|
68
|
+
export declare function readPageProgress(dir: string): PageResponses | null;
|
|
64
69
|
export interface PageTicket {
|
|
65
70
|
dir: string;
|
|
66
71
|
id: string;
|
|
67
72
|
manifest: PageManifest;
|
|
68
|
-
/** The authored source document, verbatim. */
|
|
73
|
+
/** The authored TSX source document, verbatim. */
|
|
69
74
|
document: string;
|
|
70
|
-
|
|
75
|
+
/** The compiled module the Gateway evaluates, for JSX pages. */
|
|
76
|
+
module?: string;
|
|
77
|
+
state: 'pending' | 'resolved' | 'canceled' | 'passive';
|
|
71
78
|
progress: PageResponses | null;
|
|
72
79
|
}
|
|
73
80
|
/** Read one addressable page ticket — pending, resolved or canceled alike. */
|
|
74
|
-
export declare function readPageTicket(dir: string
|
|
81
|
+
export declare function readPageTicket(dir: string): PageTicket;
|
|
75
82
|
export interface CompletePageOptions {
|
|
76
|
-
productKinds?: ProductPageComponents;
|
|
77
83
|
completedAt?: string;
|
|
78
84
|
}
|
|
79
85
|
export declare function completePage(dir: string, responses: unknown, claimToken: string, opts?: CompletePageOptions): {
|
|
@@ -3,10 +3,10 @@ import { basename, isAbsolute, resolve } from 'node:path';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { buildPageSummary } from './summary.js';
|
|
6
|
-
import { atomicWriteJson, clearProgress, claimPath, pageManifestPath, pagePath, progressPath, publishJsonExclusive, requireTicket, responsePath, reviewPath } from './convention.js';
|
|
6
|
+
import { atomicWriteJson, atomicWriteText, clearProgress, claimPath, pageManifestPath, pageModulePath, pagePath, progressPath, publishJsonExclusive, requireTicket, responsePath, reviewPath } from './convention.js';
|
|
7
7
|
import { validateReviewDescriptor, validateReviewProjection } from './review-schema.js';
|
|
8
|
-
import { derivePageManifestFromDocument, parsePage, PageAuthoringError
|
|
9
|
-
import { validatePageResponses, validatePartialPageResponses } from './page-schema.js';
|
|
8
|
+
import { compilePage, deriveHtmlPageManifest, derivePageManifestFromDocument, parsePage, PageAuthoringError } from './page.js';
|
|
9
|
+
import { isResponseBearingSlot, validatePageResponses, validatePartialPageResponses } from './page-schema.js';
|
|
10
10
|
import { signalInboxActivity } from './root.js';
|
|
11
11
|
import { readTicketClaim, releaseClaimLocked, withTicketLock } from './claim.js';
|
|
12
12
|
import { checkReviewableFile } from './visible-paths.js';
|
|
@@ -46,7 +46,7 @@ export function readTicketResult(dir) {
|
|
|
46
46
|
return canceled.success ? canceled.data : null;
|
|
47
47
|
}
|
|
48
48
|
function hasTicketProtocolState(dir) {
|
|
49
|
-
return [pageManifestPath(dir), pagePath(dir
|
|
49
|
+
return [pageManifestPath(dir), pagePath(dir), pageModulePath(dir), reviewPath(dir), responsePath(dir), progressPath(dir), claimPath(dir)].some(existsSync);
|
|
50
50
|
}
|
|
51
51
|
function publishRequest(path, value) {
|
|
52
52
|
if (!publishJsonExclusive(path, value))
|
|
@@ -58,28 +58,58 @@ function publishRequest(path, value) {
|
|
|
58
58
|
* The source is read exactly once, so the published document is the same bytes
|
|
59
59
|
* the manifest was derived from even if the file changes underneath. */
|
|
60
60
|
export function preparePage(opts) {
|
|
61
|
+
const html = /\.html?$/i.test(opts.sourceFile);
|
|
62
|
+
if (!html && !opts.sourceFile.endsWith('.tsx'))
|
|
63
|
+
throw new PageAuthoringError(`page source file must be .tsx, .html, or .htm: ${opts.sourceFile}`);
|
|
61
64
|
if (!existsSync(opts.sourceFile))
|
|
62
65
|
throw new PageAuthoringError(`page source file does not exist: ${opts.sourceFile}`);
|
|
63
66
|
const document = readFileSync(opts.sourceFile, 'utf8');
|
|
64
|
-
const stamped = { ...(opts.source ?? {}),
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const stamped = { ...(opts.source ?? {}), emittedAt: opts.source?.emittedAt ?? new Date().toISOString() };
|
|
68
|
+
const delivery = opts.delivery ?? { placement: 'inline', inbox: false, reply: false };
|
|
69
|
+
if (html)
|
|
70
|
+
return { document, manifest: deriveHtmlPageManifest(document, stamped, delivery) };
|
|
71
|
+
return { document, module: compilePage(document), manifest: derivePageManifestFromDocument(document, opts.productKinds ?? [], stamped, delivery) };
|
|
68
72
|
}
|
|
69
|
-
/**
|
|
73
|
+
/** Remove a stale generation file. The manifest always switches first, so a
|
|
74
|
+
* failed cleanup leaves one valid current record plus harmless old source. */
|
|
75
|
+
function unlinkIfPresent(path) {
|
|
76
|
+
try {
|
|
77
|
+
unlinkSync(path);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code !== 'ENOENT')
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Publish one prepared page ticket: the authored source plus its manifest.
|
|
85
|
+
* These writes are overwrites. A ticket's REQUEST is whatever page the agent
|
|
86
|
+
* currently wants on screen, and `crtr human send --replace` revises it in
|
|
87
|
+
* place while the human watches; only the ANSWER is write-once, which
|
|
88
|
+
* `publishJsonExclusive` still guarantees for response.json alone. The manifest
|
|
89
|
+
* switches after all new generation files exist, then stale media is removed. */
|
|
70
90
|
export function publishPage(dir, prepared) {
|
|
71
91
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
72
92
|
const ticket = realpathSync(dir);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
atomicWriteText(pagePath(ticket, prepared.manifest.dialect), prepared.document);
|
|
94
|
+
if (prepared.manifest.dialect === 'jsx')
|
|
95
|
+
atomicWriteText(pageModulePath(ticket), prepared.module);
|
|
96
|
+
atomicWriteJson(pageManifestPath(ticket), prepared.manifest);
|
|
97
|
+
if (prepared.manifest.dialect === 'jsx')
|
|
98
|
+
unlinkIfPresent(pagePath(ticket, 'html'));
|
|
99
|
+
else {
|
|
100
|
+
unlinkIfPresent(pagePath(ticket, 'jsx'));
|
|
101
|
+
unlinkIfPresent(pageModulePath(ticket));
|
|
102
|
+
}
|
|
103
|
+
// Unconditional: a no-inbox page still lives in an attached viewer's transcript, and
|
|
104
|
+
// --replace promises every open surface reloads it.
|
|
77
105
|
signalInboxActivity();
|
|
78
106
|
return { id: basename(ticket), dir: ticket, kind: 'page', manifest: prepared.manifest };
|
|
79
107
|
}
|
|
80
108
|
/** Validate and publish one page ticket in a single step, for callers that own
|
|
81
109
|
* the ticket directory before they have a page. */
|
|
82
110
|
export function submitPage(opts) {
|
|
111
|
+
if (existsSync(opts.dir) && hasTicketProtocolState(opts.dir) && !existsSync(pageManifestPath(opts.dir)))
|
|
112
|
+
throw new Error(`ticket protocol state already exists: ${opts.dir}`);
|
|
83
113
|
return publishPage(opts.dir, preparePage(opts));
|
|
84
114
|
}
|
|
85
115
|
export function submitReview(opts) {
|
|
@@ -104,17 +134,40 @@ function exclusiveResult(dir, result) {
|
|
|
104
134
|
return publishJsonExclusive(responsePath(dir), result);
|
|
105
135
|
}
|
|
106
136
|
function requireReview(dir) { return validateReviewDescriptor(JSON.parse(readFileSync(reviewPath(dir), 'utf8'))); }
|
|
107
|
-
function requirePage(dir
|
|
137
|
+
function requirePage(dir) {
|
|
108
138
|
if (!existsSync(pageManifestPath(dir)))
|
|
109
139
|
throw new Error(`not a page ticket: ${dir}`);
|
|
110
|
-
return parsePage(dir
|
|
140
|
+
return parsePage(dir);
|
|
141
|
+
}
|
|
142
|
+
function requireSettleablePage(dir) {
|
|
143
|
+
const manifest = requirePage(dir);
|
|
144
|
+
if (!manifest.delivery.inbox && !manifest.delivery.reply)
|
|
145
|
+
throw new Error('page_has_no_response_lifecycle');
|
|
146
|
+
return manifest;
|
|
147
|
+
}
|
|
148
|
+
function isEmptyResponseMap(responses) {
|
|
149
|
+
return typeof responses === 'object' && responses !== null && !Array.isArray(responses) && Object.keys(responses).length === 0;
|
|
150
|
+
}
|
|
151
|
+
/** Validate a final page response against its ticket's reply contract. */
|
|
152
|
+
function validateFinalPageTicketResponses(dir, responses) {
|
|
153
|
+
const manifest = requireSettleablePage(dir);
|
|
154
|
+
// An inbox-only page accepts an empty acknowledgement without requiring an answer
|
|
155
|
+
// from every response-bearing component.
|
|
156
|
+
if (!manifest.delivery.reply && isEmptyResponseMap(responses)) {
|
|
157
|
+
return validatePartialPageResponses(manifest, responses);
|
|
158
|
+
}
|
|
159
|
+
return validatePageResponses(manifest, responses);
|
|
111
160
|
}
|
|
112
161
|
/** Validate a page resolution against its canonical ticket before claim takeover. */
|
|
113
|
-
export function validatePageTicketResponses(dir, responses
|
|
114
|
-
|
|
162
|
+
export function validatePageTicketResponses(dir, responses) {
|
|
163
|
+
const ticket = requireTicket(dir);
|
|
164
|
+
return validateFinalPageTicketResponses(ticket, responses);
|
|
115
165
|
}
|
|
116
166
|
/** The three terminal states of a page ticket, derived from its published result. */
|
|
117
167
|
export function pageTicketState(dir) {
|
|
168
|
+
const manifest = requirePage(dir);
|
|
169
|
+
if (!manifest.delivery.inbox && !manifest.delivery.reply)
|
|
170
|
+
return 'passive';
|
|
118
171
|
const result = readTicketResult(dir);
|
|
119
172
|
if (result === null)
|
|
120
173
|
return 'pending';
|
|
@@ -133,33 +186,38 @@ function readPageProgressFor(dir, manifest) {
|
|
|
133
186
|
const parsed = pageProgressSchema.safeParse(raw);
|
|
134
187
|
if (!parsed.success)
|
|
135
188
|
throw new Error(`page progress is malformed: ${progressPath(dir)}`);
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
189
|
+
// A pending page's request can be replaced under saved work, so progress may
|
|
190
|
+
// name a component the current page no longer declares. Those entries are dropped
|
|
191
|
+
// and everything still declared hydrates; what remains is validated, so a
|
|
192
|
+
// saved answer that no longer fits its own component is still an error.
|
|
193
|
+
const declared = new Set(manifest.slots.filter(isResponseBearingSlot).map((slot) => slot.id));
|
|
194
|
+
const current = Object.fromEntries(Object.entries(parsed.data.responses).filter(([id]) => declared.has(id)));
|
|
195
|
+
return validatePartialPageResponses(manifest, current);
|
|
139
196
|
}
|
|
140
|
-
/** Autosave partial page work. Every supplied response is validated; omitted
|
|
141
|
-
export function writePageProgress(dir, responses
|
|
197
|
+
/** Autosave partial page work. Every supplied response is validated; omitted components are permitted. */
|
|
198
|
+
export function writePageProgress(dir, responses) {
|
|
142
199
|
const ticket = requireTicket(dir);
|
|
143
|
-
const validated = validatePartialPageResponses(
|
|
200
|
+
const validated = validatePartialPageResponses(requireSettleablePage(ticket), responses);
|
|
144
201
|
atomicWriteJson(progressPath(ticket), { kind: 'page', responses: validated, savedAt: new Date().toISOString() });
|
|
145
202
|
return validated;
|
|
146
203
|
}
|
|
147
204
|
/** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
|
|
148
|
-
export function readPageProgress(dir
|
|
205
|
+
export function readPageProgress(dir) {
|
|
149
206
|
const ticket = requireTicket(dir);
|
|
150
|
-
return readPageProgressFor(ticket,
|
|
207
|
+
return readPageProgressFor(ticket, requireSettleablePage(ticket));
|
|
151
208
|
}
|
|
152
209
|
/** Read one addressable page ticket — pending, resolved or canceled alike. */
|
|
153
|
-
export function readPageTicket(dir
|
|
210
|
+
export function readPageTicket(dir) {
|
|
154
211
|
const ticket = requireTicket(dir);
|
|
155
|
-
const manifest = requirePage(ticket
|
|
212
|
+
const manifest = requirePage(ticket);
|
|
156
213
|
return {
|
|
157
214
|
dir: ticket,
|
|
158
215
|
id: basename(ticket),
|
|
159
216
|
manifest,
|
|
160
217
|
document: readFileSync(pagePath(ticket, manifest.dialect), 'utf8'),
|
|
218
|
+
...(manifest.dialect === 'jsx' ? { module: readFileSync(pageModulePath(ticket), 'utf8') } : { module: undefined }),
|
|
161
219
|
state: pageTicketState(ticket),
|
|
162
|
-
progress: readPageProgressFor(ticket, manifest),
|
|
220
|
+
progress: manifest.delivery.inbox || manifest.delivery.reply ? readPageProgressFor(ticket, manifest) : null,
|
|
163
221
|
};
|
|
164
222
|
}
|
|
165
223
|
function requireClaimOwnership(dir, token) {
|
|
@@ -170,9 +228,9 @@ function clearOwnedWork(dir, claimToken) { clearProgress(dir); releaseClaimLocke
|
|
|
170
228
|
/** Caller holds `.ticket-lock`. */
|
|
171
229
|
function completePageLocked(dir, responses, claimToken, opts) {
|
|
172
230
|
requireClaimOwnership(dir, claimToken);
|
|
173
|
-
const manifest = requirePage(dir
|
|
174
|
-
const parsedResponses =
|
|
175
|
-
const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses
|
|
231
|
+
const manifest = requirePage(dir);
|
|
232
|
+
const parsedResponses = validateFinalPageTicketResponses(dir, responses);
|
|
233
|
+
const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses), completedAt: opts.completedAt ?? new Date().toISOString() };
|
|
176
234
|
const won = exclusiveResult(dir, result);
|
|
177
235
|
clearOwnedWork(dir, claimToken);
|
|
178
236
|
return { won, result: won ? result : readTicketResult(dir) ?? result };
|
|
@@ -213,6 +271,8 @@ export function takeoverAndCompletePage(dir, responses, owner, opts = {}) {
|
|
|
213
271
|
export function cancelTicket(dir, opts = {}) {
|
|
214
272
|
const ticket = requireTicket(dir);
|
|
215
273
|
const canceled = withTicketLock(ticket, () => {
|
|
274
|
+
if (existsSync(pageManifestPath(ticket)))
|
|
275
|
+
requireSettleablePage(ticket);
|
|
216
276
|
const result = { schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: new Date().toISOString(), ...(opts.reason === undefined ? {} : { reason: opts.reason }), ...(opts.actor === undefined ? {} : { actor: opts.actor }) };
|
|
217
277
|
const won = exclusiveResult(ticket, result);
|
|
218
278
|
return { status: won ? 'canceled' : 'already_resolved', result: won ? result : readTicketResult(ticket) ?? result };
|
|
@@ -48,12 +48,20 @@ export interface ReviewTicketSummary extends TicketSummaryBase {
|
|
|
48
48
|
output: string;
|
|
49
49
|
}
|
|
50
50
|
/** Page triage fields: a client picks a transcript rendering without a second fetch. */
|
|
51
|
-
export interface PageTicketSummary
|
|
51
|
+
export interface PageTicketSummary {
|
|
52
|
+
dir: string;
|
|
53
|
+
id: string;
|
|
52
54
|
kind: 'page';
|
|
55
|
+
title: string;
|
|
56
|
+
subtitle: string;
|
|
57
|
+
source: import('./page-schema.js').PageSource;
|
|
58
|
+
emittedAt: string;
|
|
59
|
+
claim?: ClaimSummary;
|
|
53
60
|
/** Present on resolved history entries; the stored one-line response digest. */
|
|
54
61
|
answerDigest?: string;
|
|
55
|
-
dialect: '
|
|
62
|
+
dialect: 'jsx' | 'html';
|
|
56
63
|
placement: 'inline' | 'panel';
|
|
64
|
+
inbox: boolean;
|
|
57
65
|
steps: number;
|
|
58
66
|
slotKinds: string[];
|
|
59
67
|
awaitsResponse: boolean;
|
|
@@ -104,9 +104,8 @@ const LEAF_ICON_CP = {
|
|
|
104
104
|
'canvas issue new': 0xf188,
|
|
105
105
|
'canvas prune': 0xf0c4,
|
|
106
106
|
'canvas rebuild-index': 0xf0ad,
|
|
107
|
-
'human
|
|
107
|
+
'human send': 0xf059,
|
|
108
108
|
'human review': 0xf044,
|
|
109
|
-
'human notify': 0xf0f3,
|
|
110
109
|
'human show': 0xf108,
|
|
111
110
|
'human cancel': 0xf00d,
|
|
112
111
|
'human list': 0xf03a,
|
|
@@ -304,15 +303,14 @@ function summarizeDaemon(field, active, inactive) {
|
|
|
304
303
|
return [{ text: state ? `${active}${pid === undefined ? '' : ` · pid ${pid}`}` : inactive, tone: state ? 'success' : 'muted' }];
|
|
305
304
|
};
|
|
306
305
|
}
|
|
307
|
-
function
|
|
306
|
+
function summarizeHumanSend(input) {
|
|
308
307
|
const value = record(input);
|
|
309
|
-
if (value === undefined || stringField(value, '
|
|
308
|
+
if (value === undefined || stringField(value, 'page_id') === undefined)
|
|
310
309
|
return null;
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
];
|
|
310
|
+
const reply = booleanField(value, 'reply');
|
|
311
|
+
if (reply === undefined)
|
|
312
|
+
return null;
|
|
313
|
+
return [{ text: reply ? 'page sent → waiting on human' : 'announcement sent', tone: reply ? 'accent' : 'success' }];
|
|
316
314
|
}
|
|
317
315
|
function summarizeHumanResolve(input) {
|
|
318
316
|
const value = record(input);
|
|
@@ -352,7 +350,7 @@ const SUMMARIZERS = [
|
|
|
352
350
|
{ path: 'canvas attention count', run: summarizeCount('pending asks', true) },
|
|
353
351
|
{ path: 'canvas attention list', run: summarizeArray('pending asks') },
|
|
354
352
|
{ path: 'canvas attention map', run: summarizeAttentionMap },
|
|
355
|
-
{ path: 'human
|
|
353
|
+
{ path: 'human send', run: summarizeHumanSend },
|
|
356
354
|
{ path: 'human list', run: summarizeArray('pending') },
|
|
357
355
|
{ path: 'human resolve', run: summarizeHumanResolve },
|
|
358
356
|
{ path: 'pkg plugin list', run: summarizeArray('plugins') },
|
|
@@ -4,3 +4,7 @@ export declare function resolveBindingCwd(cwd: string): string;
|
|
|
4
4
|
export declare function getDefaultProfileId(cwd: string): string | null;
|
|
5
5
|
export declare function setDefaultProfileId(cwd: string, profileId: string): void;
|
|
6
6
|
export declare function clearDefaultProfile(cwd: string): void;
|
|
7
|
+
/** Every directory currently pinned to `profileId`, in deterministic order. */
|
|
8
|
+
export declare function defaultProfileDirs(profileId: string): string[];
|
|
9
|
+
/** Clear every per-directory default that names `profileId`. */
|
|
10
|
+
export declare function clearDefaultProfileId(profileId: string): string[];
|
|
@@ -44,3 +44,27 @@ export function clearDefaultProfile(cwd) {
|
|
|
44
44
|
else
|
|
45
45
|
writeJson(bindingsPath(), value);
|
|
46
46
|
}
|
|
47
|
+
/** Every directory currently pinned to `profileId`, in deterministic order. */
|
|
48
|
+
export function defaultProfileDirs(profileId) {
|
|
49
|
+
return Object.entries(bindings())
|
|
50
|
+
.filter(([, pinnedProfileId]) => pinnedProfileId === profileId)
|
|
51
|
+
.map(([dir]) => dir)
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
/** Clear every per-directory default that names `profileId`. */
|
|
55
|
+
export function clearDefaultProfileId(profileId) {
|
|
56
|
+
const value = bindings();
|
|
57
|
+
const cleared = Object.entries(value)
|
|
58
|
+
.filter(([, pinnedProfileId]) => pinnedProfileId === profileId)
|
|
59
|
+
.map(([dir]) => dir)
|
|
60
|
+
.sort();
|
|
61
|
+
if (cleared.length === 0)
|
|
62
|
+
return [];
|
|
63
|
+
for (const dir of cleared)
|
|
64
|
+
delete value[dir];
|
|
65
|
+
if (Object.keys(value).length === 0)
|
|
66
|
+
removePath(bindingsPath());
|
|
67
|
+
else
|
|
68
|
+
writeJson(bindingsPath(), value);
|
|
69
|
+
return cleared;
|
|
70
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Reserve one profile identity for its complete daemon-owned deletion lifecycle.
|
|
2
|
+
* The caller must release the returned lease after completion or failure. */
|
|
3
|
+
export declare function reserveProfileDeletion(profileId: string): () => void;
|
|
4
|
+
/** The synchronous birth gate. spawnNode calls this immediately before the
|
|
5
|
+
* canvas row is persisted. A reservation rejects a birth that reaches the gate
|
|
6
|
+
* during deletion; the exact manifest read rejects one whose async preflight
|
|
7
|
+
* started before a fast hard deletion and resumed after its lease was released. */
|
|
8
|
+
export declare function assertProfileAvailableForBirth(profileId: string | null): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { usage } from '../errors.js';
|
|
2
|
+
import { loadExactProfileManifest } from './manifest.js';
|
|
3
|
+
const deletingProfileIds = new Set();
|
|
4
|
+
/** Reserve one profile identity for its complete daemon-owned deletion lifecycle.
|
|
5
|
+
* The caller must release the returned lease after completion or failure. */
|
|
6
|
+
export function reserveProfileDeletion(profileId) {
|
|
7
|
+
if (deletingProfileIds.has(profileId)) {
|
|
8
|
+
throw usage(`profile deletion is already in progress: ${profileId}`, {
|
|
9
|
+
error: 'profile_deletion_in_progress',
|
|
10
|
+
profile_id: profileId,
|
|
11
|
+
next: 'Wait for the accepted deletion to finish before using this profile again.',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
deletingProfileIds.add(profileId);
|
|
15
|
+
let released = false;
|
|
16
|
+
return () => {
|
|
17
|
+
if (released)
|
|
18
|
+
return;
|
|
19
|
+
released = true;
|
|
20
|
+
deletingProfileIds.delete(profileId);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** The synchronous birth gate. spawnNode calls this immediately before the
|
|
24
|
+
* canvas row is persisted. A reservation rejects a birth that reaches the gate
|
|
25
|
+
* during deletion; the exact manifest read rejects one whose async preflight
|
|
26
|
+
* started before a fast hard deletion and resumed after its lease was released. */
|
|
27
|
+
export function assertProfileAvailableForBirth(profileId) {
|
|
28
|
+
if (profileId === null)
|
|
29
|
+
return;
|
|
30
|
+
if (deletingProfileIds.has(profileId)) {
|
|
31
|
+
throw usage(`cannot create a node while its profile is being deleted: ${profileId}`, {
|
|
32
|
+
error: 'profile_deletion_in_progress',
|
|
33
|
+
profile_id: profileId,
|
|
34
|
+
next: 'Choose another profile or wait for this deletion to finish.',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
loadExactProfileManifest(profileId);
|
|
38
|
+
}
|
|
@@ -30,6 +30,9 @@ export declare function listProfiles(): ProfileEntry[];
|
|
|
30
30
|
* function every command leaf and runtime consumer should
|
|
31
31
|
* call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
|
|
32
32
|
* safe profile id. */
|
|
33
|
+
/** Read one exact profile id without falling back to manifest-name matching.
|
|
34
|
+
* Used at persistence gates that already hold a resolved durable identity. */
|
|
35
|
+
export declare function loadExactProfileManifest(profileId: string): ProfileEntry;
|
|
33
36
|
export declare function loadProfileManifest(profileIdOrName: string): ProfileEntry;
|
|
34
37
|
/** Hold the per-profile manifest lock for the duration of `fn`. ALL manifest
|
|
35
38
|
* mutations (create/rename/add-project/remove-project/delete/last-used)
|
|
@@ -150,6 +150,18 @@ export function listProfiles() {
|
|
|
150
150
|
* function every command leaf and runtime consumer should
|
|
151
151
|
* call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
|
|
152
152
|
* safe profile id. */
|
|
153
|
+
/** Read one exact profile id without falling back to manifest-name matching.
|
|
154
|
+
* Used at persistence gates that already hold a resolved durable identity. */
|
|
155
|
+
export function loadExactProfileManifest(profileId) {
|
|
156
|
+
assertSafeProfileId(profileId);
|
|
157
|
+
const manifest = readManifestFile(profileManifestPath(profileId), profileId);
|
|
158
|
+
if (manifest !== null)
|
|
159
|
+
return { profileId, manifest };
|
|
160
|
+
throw notFound(`profile not found: ${profileId}`, {
|
|
161
|
+
received: profileId,
|
|
162
|
+
next: 'Run `crtr profile list` to see available profiles.',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
153
165
|
export function loadProfileManifest(profileIdOrName) {
|
|
154
166
|
const all = listProfiles();
|
|
155
167
|
if (ID_SHAPE.test(profileIdOrName)) {
|