@north-light/crouter 0.3.197 → 0.3.198
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 +2 -2
- package/dist/api/dto/inbox.d.ts +46 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/render/chat-view.d.ts +3 -8
- package/dist/clients/attach/render/chat-view.js +3 -24
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/viewer.js +613 -602
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +2 -2
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +11 -4
- package/dist/clients/inbox/controller.js +4 -4
- package/dist/clients/inbox/page-adapter.d.ts +2 -1
- package/dist/clients/inbox/tui/render.js +46 -25
- package/dist/clients/inbox/tui/slots.d.ts +1 -3
- package/dist/clients/inbox/tui/slots.js +8 -37
- package/dist/clients/inbox/tui/types.d.ts +4 -3
- package/dist/commands/__tests__/human.test.js +160 -23
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/human/doc.js +21 -74
- package/dist/commands/human/html.d.ts +2 -0
- package/dist/commands/human/html.js +81 -0
- package/dist/commands/human/prompts.d.ts +5 -4
- package/dist/commands/human/prompts.js +81 -153
- package/dist/commands/human/shared.d.ts +5 -6
- package/dist/commands/human/shared.js +13 -61
- package/dist/commands/human.js +7 -2
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/human-deliver.test.js +28 -37
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +63 -4
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +3 -13
- package/dist/core/canvas/__tests__/attention.test.js +2 -2
- package/dist/core/canvas/__tests__/render-remote.test.js +2 -2
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +26 -0
- package/dist/core/canvas/canvas.js +55 -0
- package/dist/core/command.js +6 -11
- package/dist/core/config.js +3 -2
- package/dist/core/help.d.ts +3 -0
- package/dist/core/human/__tests__/html-markdown.test.d.ts +1 -0
- package/dist/core/human/__tests__/html-markdown.test.js +52 -0
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +16 -0
- package/dist/core/human/__tests__/page-render.test.js +40 -23
- package/dist/core/human/__tests__/page-tickets.test.js +48 -27
- package/dist/core/human/__tests__/page.test.js +48 -65
- package/dist/core/human/__tests__/serial/inbox-core.test.js +5 -15
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +51 -0
- package/dist/core/human/answer.js +125 -0
- package/dist/core/human/component-docs.d.ts +14 -0
- package/dist/core/human/component-docs.js +161 -0
- package/dist/core/human/html-markdown.d.ts +3 -0
- package/dist/core/human/html-markdown.js +288 -0
- package/dist/core/human/page-catalog.d.ts +19 -3
- package/dist/core/human/page-catalog.js +73 -16
- package/dist/core/human/page-render.d.ts +3 -2
- package/dist/core/human/page-render.js +19 -45
- package/dist/core/human/page-schema.d.ts +108 -8
- package/dist/core/human/page-schema.js +88 -31
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +23 -7
- package/dist/core/human/page.d.ts +14 -7
- package/dist/core/human/page.js +178 -90
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/scan.d.ts +3 -2
- package/dist/core/human/scan.js +7 -8
- package/dist/core/human/summary.d.ts +4 -7
- package/dist/core/human/summary.js +4 -60
- package/dist/core/human/tickets.d.ts +10 -9
- package/dist/core/human/tickets.js +5 -3
- package/dist/core/human/types.d.ts +4 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/review/realize.js +9 -1
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker.js +8 -6
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/user-settings.d.ts +17 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/daemon/api/handlers/human.js +1 -1
- package/dist/daemon/api/handlers/inbox.js +22 -7
- package/dist/daemon/cron-run.js +16 -2
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +2 -1
- package/dist/daemon/human/finish.js +45 -28
- package/dist/daemon/human/sweep.js +4 -2
- package/dist/daemon/reconcilers/broker-supervision.js +11 -8
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pages/bundle.css +1 -1
- package/dist/pages/bundle.js +1008 -577
- package/dist/pages/comments.d.ts +29 -0
- package/dist/pages/comments.js +51 -0
- package/dist/pages/controls.d.ts +98 -0
- package/dist/pages/controls.js +261 -0
- package/dist/pages/elements/cards.d.ts +15 -4
- package/dist/pages/elements/cards.js +299 -186
- package/dist/pages/elements/chart.d.ts +22 -0
- package/dist/pages/elements/chart.js +107 -91
- package/dist/pages/elements/options.d.ts +9 -0
- package/dist/pages/elements/options.js +268 -155
- package/dist/pages/elements/pages.d.ts +6 -0
- package/dist/pages/elements/pages.js +60 -38
- package/dist/pages/elements/slot.d.ts +5 -0
- package/dist/pages/elements/slot.js +17 -10
- package/dist/pages/elements/table.d.ts +17 -0
- package/dist/pages/elements/table.js +336 -228
- package/dist/pages/elements/text.d.ts +9 -3
- package/dist/pages/elements/text.js +350 -143
- package/dist/pages/host.d.ts +0 -24
- package/dist/pages/host.js +0 -17
- package/dist/pages/readonly.d.ts +24 -0
- package/dist/pages/readonly.js +31 -0
- package/dist/pages/responses.d.ts +37 -0
- package/dist/pages/responses.js +56 -0
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +12 -8
- package/dist/types.d.ts +19 -5
- package/dist/types.js +2 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/scripts/install-runtime.mjs +19 -24
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
|
@@ -5,7 +5,7 @@ import { z } from 'zod';
|
|
|
5
5
|
import { buildPageSummary } from './summary.js';
|
|
6
6
|
import { atomicWriteJson, clearProgress, claimPath, pageManifestPath, pagePath, progressPath, publishJsonExclusive, requireTicket, responsePath, reviewPath } from './convention.js';
|
|
7
7
|
import { validateReviewDescriptor, validateReviewProjection } from './review-schema.js';
|
|
8
|
-
import { derivePageManifestFromDocument, parsePage, PageAuthoringError } from './page.js';
|
|
8
|
+
import { derivePageManifestFromDocument, parsePage, PageAuthoringError, snapshotPageSources } from './page.js';
|
|
9
9
|
import { validatePageResponses, validatePartialPageResponses } from './page-schema.js';
|
|
10
10
|
import { signalInboxActivity } from './root.js';
|
|
11
11
|
import { readTicketClaim, releaseClaimLocked, withTicketLock } from './claim.js';
|
|
@@ -62,7 +62,9 @@ export function preparePage(opts) {
|
|
|
62
62
|
throw new PageAuthoringError(`page source file does not exist: ${opts.sourceFile}`);
|
|
63
63
|
const document = readFileSync(opts.sourceFile, 'utf8');
|
|
64
64
|
const stamped = { ...(opts.source ?? {}), blockedSince: opts.source?.blockedSince ?? new Date().toISOString() };
|
|
65
|
-
|
|
65
|
+
const authored = derivePageManifestFromDocument(document, opts.dialect, opts.productKinds ?? [], stamped);
|
|
66
|
+
const manifest = snapshotPageSources(authored, opts.productKinds ?? []);
|
|
67
|
+
return { dialect: opts.dialect, document, manifest };
|
|
66
68
|
}
|
|
67
69
|
/** Publish one prepared page ticket: the authored source plus its manifest, exclusively. */
|
|
68
70
|
export function publishPage(dir, prepared) {
|
|
@@ -170,7 +172,7 @@ function completePageLocked(dir, responses, claimToken, opts) {
|
|
|
170
172
|
requireClaimOwnership(dir, claimToken);
|
|
171
173
|
const manifest = requirePage(dir, opts.productKinds ?? []);
|
|
172
174
|
const parsedResponses = validatePageResponses(manifest, responses);
|
|
173
|
-
const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses), completedAt: opts.completedAt ?? new Date().toISOString() };
|
|
175
|
+
const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses, opts.productKinds), completedAt: opts.completedAt ?? new Date().toISOString() };
|
|
174
176
|
const won = exclusiveResult(dir, result);
|
|
175
177
|
clearOwnedWork(dir, claimToken);
|
|
176
178
|
return { won, result: won ? result : readTicketResult(dir) ?? result };
|
|
@@ -4,6 +4,8 @@ export interface TicketSource {
|
|
|
4
4
|
sessionName?: string;
|
|
5
5
|
askedBy?: string;
|
|
6
6
|
blockedSince?: string;
|
|
7
|
+
/** Profile name the originating node runs under. Product consumers use this stable name to resolve the applet or worker identity behind the ticket. */
|
|
8
|
+
profileName?: string;
|
|
7
9
|
/** Originating canvas node id (CRTR_NODE_ID) when the ask was raised inside a crouter canvas node. */
|
|
8
10
|
nodeId?: string;
|
|
9
11
|
}
|
|
@@ -48,6 +50,8 @@ export interface ReviewTicketSummary extends TicketSummaryBase {
|
|
|
48
50
|
/** Page triage fields: a client picks a transcript rendering without a second fetch. */
|
|
49
51
|
export interface PageTicketSummary extends TicketSummaryBase {
|
|
50
52
|
kind: 'page';
|
|
53
|
+
/** Present on resolved history entries; the stored one-line response digest. */
|
|
54
|
+
answerDigest?: string;
|
|
51
55
|
dialect: 'md' | 'html';
|
|
52
56
|
placement: 'inline' | 'panel';
|
|
53
57
|
steps: number;
|
|
@@ -3,13 +3,13 @@ import type { AttachBindingId, AttachMenuBindingId } from './attach-control.js';
|
|
|
3
3
|
type TmuxMenuAction = 'open' | 'attach' | 'show' | 'view-settings' | 'fold-tools' | 'metadata' | 'promote' | 'demote-mode' | 'resume' | 'fork' | 'demote' | 'make-resident' | 'detach' | 'close-subtree' | 'background-bash' | 'file-review' | 'pin' | 'reload-viewer' | 'recap' | 'context' | 'providers' | 'plugins' | 'graph' | 'focus-manager' | 'copy-id' | 'issues';
|
|
4
4
|
type TmuxMenuEntryAction = Exclude<TmuxMenuAction, 'open' | 'attach'> | `issue.${'idea' | 'bug'}`;
|
|
5
5
|
type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'revive' | 'focus-manager' | 'close-subtree' | 'dismiss';
|
|
6
|
-
type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4 | 5}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'profile' | 'residents' | 'preview' | 'search' | 'revive-now' | 'focus' | 'focus-background';
|
|
6
|
+
type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4 | 5}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'profile' | 'residents' | 'preview' | 'search' | 'revive-now' | 'focus' | 'focus-background' | 'yank-id';
|
|
7
7
|
type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' | `slot.${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` | 'create' | 'search-existing' | 'use-root' | 'set-default' | 'add-directory' | 'unpin';
|
|
8
8
|
type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance' | 'cell-previous' | 'cell-next';
|
|
9
9
|
type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
|
|
10
10
|
type SetupModelsAction = 'open' | 'commit' | 'cancel' | 'scope-next';
|
|
11
11
|
export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | AttachMenuBindingId | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.scroll.${'up' | 'down' | 'page-up' | 'page-down' | 'live'}` | 'crtr.attach.mouse.toggle' | 'crtr.attach.copy.message' | 'crtr.attach.help.toggle' | AttachBindingId | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.inspector.${'force-quit' | 'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
|
|
12
|
-
export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.inspector.force-quit"> | BindingDefinition<"crtr.inspector.down"> | BindingDefinition<"crtr.inspector.up"> | BindingDefinition<"crtr.inspector.section-next"> | BindingDefinition<"crtr.inspector.section-prev"> | BindingDefinition<"crtr.inspector.page-down"> | BindingDefinition<"crtr.inspector.page-up"> | BindingDefinition<"crtr.inspector.toggle-detail"> | BindingDefinition<"crtr.inspector.cancel-item"> | BindingDefinition<"crtr.inspector.open-log"> | BindingDefinition<"crtr.inspector.confirm"> | BindingDefinition<"crtr.inspector.dismiss"> | BindingDefinition<"crtr.inspector.retarget"> | BindingDefinition<"crtr.inspector.refresh"> | BindingDefinition<"crtr.inspector.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
|
|
12
|
+
export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.inspector.force-quit"> | BindingDefinition<"crtr.inspector.down"> | BindingDefinition<"crtr.inspector.up"> | BindingDefinition<"crtr.inspector.section-next"> | BindingDefinition<"crtr.inspector.section-prev"> | BindingDefinition<"crtr.inspector.page-down"> | BindingDefinition<"crtr.inspector.page-up"> | BindingDefinition<"crtr.inspector.toggle-detail"> | BindingDefinition<"crtr.inspector.cancel-item"> | BindingDefinition<"crtr.inspector.open-log"> | BindingDefinition<"crtr.inspector.confirm"> | BindingDefinition<"crtr.inspector.dismiss"> | BindingDefinition<"crtr.inspector.retarget"> | BindingDefinition<"crtr.inspector.refresh"> | BindingDefinition<"crtr.inspector.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.yank-id"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
|
|
13
13
|
export declare const BINDING_IDS: ReadonlySet<string>;
|
|
14
14
|
/** True for an action whose direct gesture is handled inside an attach viewer.
|
|
15
15
|
* Tmux menu selectors and root shortcuts are chrome, not pane input. */
|
|
@@ -142,6 +142,7 @@ function browseBindings() {
|
|
|
142
142
|
b('crtr.browse.revive-now', 'Browse and pickers', 'Revive now', 'Revive the selected node now.', ['shift+k'], 'terminal', [nav]),
|
|
143
143
|
b('crtr.browse.focus', 'Browse and pickers', 'Focus node', 'Focus the selected node.', ['enter', 'o'], 'terminal', [nav]),
|
|
144
144
|
b('crtr.browse.focus-background', 'Browse and pickers', 'Focus node (keep browsing)', 'Focus the selected node without leaving the navigator.', ['shift+o'], 'terminal', [nav]),
|
|
145
|
+
b('crtr.browse.yank-id', 'Browse and pickers', 'Yank node id', 'Copy the selected node id to the system clipboard.', ['y'], 'terminal', [nav]),
|
|
145
146
|
b('crtr.browse.search.cancel', 'Browse and pickers', 'Cancel search', 'Cancel browse search.', ['escape'], 'terminal', ['browse.search'], 'dismiss-modal'),
|
|
146
147
|
b('crtr.browse.search.commit', 'Browse and pickers', 'Commit search', 'Apply browse search.', ['enter'], 'terminal', ['browse.search']),
|
|
147
148
|
b('crtr.browse.search.delete', 'Browse and pickers', 'Delete search text', 'Delete the previous search character.', ['backspace'], 'terminal', ['browse.search']),
|
|
@@ -6,6 +6,7 @@ import { atomicWriteJson } from '../human/convention.js';
|
|
|
6
6
|
import { ticketDir } from '../human/root.js';
|
|
7
7
|
import { submitReview } from '../human/tickets.js';
|
|
8
8
|
import { NodeIdConflictError, spawnNode } from '../runtime/nodes.js';
|
|
9
|
+
import { loadProfileManifest } from '../profiles/manifest.js';
|
|
9
10
|
import { transition } from '../runtime/lifecycle.js';
|
|
10
11
|
import { reviveNode } from '../runtime/revive.js';
|
|
11
12
|
import { awaitCompanionBind, spawnReviewCompanion } from './companion.js';
|
|
@@ -126,6 +127,10 @@ function publishTicket(review) {
|
|
|
126
127
|
throw bridgeUnsubscribed(review, '');
|
|
127
128
|
if (review.subtitle === null)
|
|
128
129
|
throw new Error(`ticket review has no subtitle: ${review.review_id}`);
|
|
130
|
+
const origin = requireOrigin(review);
|
|
131
|
+
const profileName = origin.profile_id == null
|
|
132
|
+
? undefined
|
|
133
|
+
: loadProfileManifest(origin.profile_id).manifest.name;
|
|
129
134
|
submitReview({
|
|
130
135
|
dir: ticketDir(review.bridge_node_id),
|
|
131
136
|
review: {
|
|
@@ -133,7 +138,10 @@ function publishTicket(review) {
|
|
|
133
138
|
...(review.output_path !== null ? { output: review.output_path } : {}),
|
|
134
139
|
title: basename(review.file),
|
|
135
140
|
subtitle: review.subtitle,
|
|
136
|
-
source: {
|
|
141
|
+
source: {
|
|
142
|
+
nodeId: review.origin_node_id,
|
|
143
|
+
...(profileName === undefined ? {} : { profileName }),
|
|
144
|
+
},
|
|
137
145
|
blockedSince: review.created,
|
|
138
146
|
},
|
|
139
147
|
});
|
|
@@ -89,7 +89,12 @@ export async function bootRoot(opts) {
|
|
|
89
89
|
// user's OWN pre-existing shell pane (not one crtr split open): on a
|
|
90
90
|
// detach-reap of an empty root it must delete the node WITHOUT kill-pane, else
|
|
91
91
|
// a plain ctrl+c closes the user's whole tmux pane.
|
|
92
|
-
const attachEnv = {
|
|
92
|
+
const attachEnv = {
|
|
93
|
+
...process.env,
|
|
94
|
+
...viewerSplitEnv(),
|
|
95
|
+
CRTR_ATTACH_WAIT_MS: '30000',
|
|
96
|
+
...(opts.prompt !== undefined && opts.prompt !== '' ? { CRTR_ATTACH_INITIAL_WHIP: '1' } : {}),
|
|
97
|
+
};
|
|
93
98
|
if (here !== null)
|
|
94
99
|
attachEnv['CRTR_ATTACH_BORROWED_PANE'] = here.pane;
|
|
95
100
|
mark('boot_root.viewer_spawn');
|
|
@@ -120,7 +120,8 @@ export function disposeActiveSession() {
|
|
|
120
120
|
// ---------------------------------------------------------------------------
|
|
121
121
|
export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), startupTs = new Date().toISOString()) {
|
|
122
122
|
// A clean-abort certificate belongs only to the dead broker that wrote it.
|
|
123
|
-
// Fleet classification consumes it
|
|
123
|
+
// Fleet classification consumes it as proof that the dead broker had a turn
|
|
124
|
+
// in flight even though Pi's settled abort already cleared the busy marker.
|
|
124
125
|
clearCleanAbort(nodeId);
|
|
125
126
|
// The canonical broker event source (job/log.jsonl) is bound before any other
|
|
126
127
|
// work in this function — no path/env/state setup may precede it, and the
|
|
@@ -594,9 +595,10 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
594
595
|
// A daemon fleet bounce gives a busy Pi a short, bounded chance to persist
|
|
595
596
|
// its aborted turn before teardown. `session.abort()` resolves only after Pi
|
|
596
597
|
// reaches idle (and thus after its message_end persistence); only then may we
|
|
597
|
-
// leave the durable clean-abort certificate that
|
|
598
|
-
//
|
|
599
|
-
//
|
|
598
|
+
// leave the durable clean-abort certificate that preserves the otherwise
|
|
599
|
+
// cleared evidence that this broker had a turn in flight. A timeout/rejection
|
|
600
|
+
// deliberately leaves no certificate: the surviving busy marker carries the
|
|
601
|
+
// same interrupted-turn fact through the fleet's shared SIGKILL deadline.
|
|
600
602
|
const ABORT_BEFORE_EXIT_MS = 2_500;
|
|
601
603
|
let signalExitInFlight = false;
|
|
602
604
|
const abortAndExit = async (reason) => {
|
|
@@ -633,8 +635,8 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
633
635
|
}
|
|
634
636
|
else if (frameDispatch.pendingPromptDispatches() > 0 || rebind.isAgentRunActive()) {
|
|
635
637
|
// The engine never reached an abortable persisted turn in the budget.
|
|
636
|
-
// Preserve the
|
|
637
|
-
//
|
|
638
|
+
// Preserve the dirty interrupted-turn witness so recovery resumes the
|
|
639
|
+
// saved transcript and explicitly re-drives the stopped work.
|
|
638
640
|
markBusy(nodeId);
|
|
639
641
|
}
|
|
640
642
|
}
|
|
@@ -4,12 +4,13 @@ export declare function markBusy(nodeId: string): void;
|
|
|
4
4
|
/** Clear the mid-run marker after Pi settles, however it routed. Best-effort. */
|
|
5
5
|
export declare function clearBusy(nodeId: string): void;
|
|
6
6
|
/** Certify that this broker aborted a busy turn and Pi persisted its settled
|
|
7
|
-
* aborted entry before teardown.
|
|
8
|
-
*
|
|
7
|
+
* aborted entry before teardown. Settling clears the busy marker, so the daemon
|
|
8
|
+
* consumes this durable certificate as the surviving interrupted-turn witness
|
|
9
|
+
* and resumes with a continuation. A dirty death instead leaves `busy` behind. */
|
|
9
10
|
export declare function markCleanAbort(nodeId: string): void;
|
|
10
11
|
/** Remove a consumed clean-abort certificate as a broker boots. */
|
|
11
12
|
export declare function clearCleanAbort(nodeId: string): void;
|
|
12
|
-
/** Whether the dead broker left a persisted clean-abort
|
|
13
|
+
/** Whether the dead broker left a persisted clean-abort turn witness. */
|
|
13
14
|
export declare function hasCleanAbort(nodeId: string): boolean;
|
|
14
15
|
/** Is the node currently inside a turn? AND this with `pidAlive` at the call
|
|
15
16
|
* site — a stale marker from a crashed pi is harmless because the dead pid
|
|
@@ -44,8 +44,9 @@ export function clearBusy(nodeId) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
/** Certify that this broker aborted a busy turn and Pi persisted its settled
|
|
47
|
-
* aborted entry before teardown.
|
|
48
|
-
*
|
|
47
|
+
* aborted entry before teardown. Settling clears the busy marker, so the daemon
|
|
48
|
+
* consumes this durable certificate as the surviving interrupted-turn witness
|
|
49
|
+
* and resumes with a continuation. A dirty death instead leaves `busy` behind. */
|
|
49
50
|
export function markCleanAbort(nodeId) {
|
|
50
51
|
try {
|
|
51
52
|
mkdirSync(jobDir(nodeId), { recursive: true });
|
|
@@ -64,7 +65,7 @@ export function clearCleanAbort(nodeId) {
|
|
|
64
65
|
/* best-effort */
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
/** Whether the dead broker left a persisted clean-abort
|
|
68
|
+
/** Whether the dead broker left a persisted clean-abort turn witness. */
|
|
68
69
|
export function hasCleanAbort(nodeId) {
|
|
69
70
|
return existsSync(cleanAbortPath(nodeId));
|
|
70
71
|
}
|
|
@@ -401,15 +401,15 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
|
|
|
401
401
|
// have bound view.sock yet. Hand the attach pane CRTR_ATTACH_WAIT_MS so
|
|
402
402
|
// it self-polls the socket until it accepts instead of connecting once,
|
|
403
403
|
// finding a cold socket, and closing the pane instantly — the exact
|
|
404
|
-
// mechanism bootRoot uses.
|
|
405
|
-
// viewer
|
|
406
|
-
//
|
|
404
|
+
// mechanism bootRoot uses. A child with a kickoff prompt marks its
|
|
405
|
+
// initial viewer for whip_mode; ordinary focus/attach paths stay
|
|
406
|
+
// silent. No pre-wait in this CLI subprocess.
|
|
407
407
|
const viewer = openViewerWindow(meta.node_id, parentViewer.session ?? '', {
|
|
408
408
|
name: fullName(meta),
|
|
409
409
|
cwd: opts.cwd,
|
|
410
410
|
besidePane: parentViewer.pane,
|
|
411
411
|
waitForBroker: true,
|
|
412
|
-
initialWhip:
|
|
412
|
+
initialWhip: hasKickoff,
|
|
413
413
|
});
|
|
414
414
|
if (viewer !== null && viewer.pane !== null && viewer.pane !== '') {
|
|
415
415
|
session = viewer.session;
|
|
@@ -147,6 +147,21 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
|
|
|
147
147
|
setFromString: (config: ScopeConfig, raw: string) => string | null;
|
|
148
148
|
};
|
|
149
149
|
};
|
|
150
|
+
html_surface: {
|
|
151
|
+
surface: "settings";
|
|
152
|
+
setting: {
|
|
153
|
+
key: "html_surface";
|
|
154
|
+
label: string;
|
|
155
|
+
description: string;
|
|
156
|
+
valueConstraint: string;
|
|
157
|
+
applies: string;
|
|
158
|
+
group: "display";
|
|
159
|
+
order: number;
|
|
160
|
+
format: (config: ScopeConfig) => "on" | "off";
|
|
161
|
+
cycle: (config: ScopeConfig) => void;
|
|
162
|
+
setFromString: (config: ScopeConfig, raw: string) => string | null;
|
|
163
|
+
};
|
|
164
|
+
};
|
|
150
165
|
whip_messages: {
|
|
151
166
|
surface: "specialized";
|
|
152
167
|
owner: string;
|
|
@@ -196,10 +211,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
|
|
|
196
211
|
setFromString: (config: ScopeConfig, raw: string) => string | null;
|
|
197
212
|
};
|
|
198
213
|
};
|
|
199
|
-
|
|
214
|
+
bash_tool_purpose: {
|
|
200
215
|
surface: "settings";
|
|
201
216
|
setting: {
|
|
202
|
-
key: "
|
|
217
|
+
key: "bash_tool_purpose";
|
|
203
218
|
label: string;
|
|
204
219
|
description: string;
|
|
205
220
|
valueConstraint: string;
|
|
@@ -176,7 +176,7 @@ export const CONFIG_SETTINGS_COVERAGE = {
|
|
|
176
176
|
setting: {
|
|
177
177
|
key: 'whip_mode',
|
|
178
178
|
label: 'Whip mode',
|
|
179
|
-
description: 'Play the whip animation when you send a prompt and when an
|
|
179
|
+
description: 'Play the whip animation when you send a prompt and when a viewer opens for an agent with an initial prompt.',
|
|
180
180
|
valueConstraint: 'true|false',
|
|
181
181
|
applies: 'Applies on the next attach.',
|
|
182
182
|
group: 'display',
|
|
@@ -192,6 +192,27 @@ export const CONFIG_SETTINGS_COVERAGE = {
|
|
|
192
192
|
},
|
|
193
193
|
},
|
|
194
194
|
},
|
|
195
|
+
html_surface: {
|
|
196
|
+
surface: 'settings',
|
|
197
|
+
setting: {
|
|
198
|
+
key: 'html_surface',
|
|
199
|
+
label: 'HTML human surface',
|
|
200
|
+
description: 'Prompt agents to use HTML pages by default for structured or interactive content shown to you.',
|
|
201
|
+
valueConstraint: 'true|false',
|
|
202
|
+
applies: 'Applies when human command help is assembled.',
|
|
203
|
+
group: 'display',
|
|
204
|
+
order: 37,
|
|
205
|
+
format: (config) => config.html_surface ? 'on' : 'off',
|
|
206
|
+
cycle: (config) => { config.html_surface = !config.html_surface; },
|
|
207
|
+
setFromString: (config, raw) => {
|
|
208
|
+
const value = booleanFromString(raw, 'html_surface');
|
|
209
|
+
if (typeof value === 'string')
|
|
210
|
+
return value;
|
|
211
|
+
config.html_surface = value;
|
|
212
|
+
return null;
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
195
216
|
whip_messages: { surface: 'specialized', owner: 'sys config set whip_messages' },
|
|
196
217
|
mouse_mode_default: {
|
|
197
218
|
surface: 'settings',
|
|
@@ -254,23 +275,23 @@ export const CONFIG_SETTINGS_COVERAGE = {
|
|
|
254
275
|
},
|
|
255
276
|
},
|
|
256
277
|
},
|
|
257
|
-
|
|
278
|
+
bash_tool_purpose: {
|
|
258
279
|
surface: 'settings',
|
|
259
280
|
setting: {
|
|
260
|
-
key: '
|
|
261
|
-
label: '
|
|
262
|
-
description: '
|
|
281
|
+
key: 'bash_tool_purpose',
|
|
282
|
+
label: 'Bash purpose field',
|
|
283
|
+
description: 'Expose the optional purpose label to agents in the bash tool schema. Off removes the field without disabling the bash valve.',
|
|
263
284
|
valueConstraint: 'true|false',
|
|
264
|
-
applies: 'Applies on the next
|
|
285
|
+
applies: 'Applies on the next node session.',
|
|
265
286
|
group: 'display',
|
|
266
|
-
order:
|
|
267
|
-
format: (config) => config.
|
|
268
|
-
cycle: (config) => { config.
|
|
287
|
+
order: 25,
|
|
288
|
+
format: (config) => config.bash_tool_purpose ? 'on' : 'off',
|
|
289
|
+
cycle: (config) => { config.bash_tool_purpose = !config.bash_tool_purpose; },
|
|
269
290
|
setFromString: (config, raw) => {
|
|
270
|
-
const value = booleanFromString(raw, '
|
|
291
|
+
const value = booleanFromString(raw, 'bash_tool_purpose');
|
|
271
292
|
if (typeof value === 'string')
|
|
272
293
|
return value;
|
|
273
|
-
config.
|
|
294
|
+
config.bash_tool_purpose = value;
|
|
274
295
|
return null;
|
|
275
296
|
},
|
|
276
297
|
},
|
|
@@ -93,7 +93,7 @@ async function handleResolve(ctx) {
|
|
|
93
93
|
const request = requireResolveRequest(ctx);
|
|
94
94
|
const dir = ticketDir(nodeId);
|
|
95
95
|
requirePageTicket(dir);
|
|
96
|
-
const productKinds = readConfig('user').page_components
|
|
96
|
+
const productKinds = readConfig('user').page_components;
|
|
97
97
|
const responses = preflightResolve(() => validatePageTicketResponses(dir, request.responses, productKinds));
|
|
98
98
|
await resolvePageTicket(nodeId, responses, productKinds);
|
|
99
99
|
return { status: 200, body: { delivered: true, kind: 'page' } };
|
|
@@ -7,10 +7,12 @@ import { basename, resolve, join } from 'node:path';
|
|
|
7
7
|
import { ApiError } from '../../../api/index.js';
|
|
8
8
|
import { assetRoot } from '../../../core/asset-root.js';
|
|
9
9
|
import { pageManifestPath, reviewPath } from '../../../core/human/convention.js';
|
|
10
|
+
import { parsePage } from '../../../core/human/page.js';
|
|
10
11
|
import { renderPageHtml } from '../../../core/human/page-render.js';
|
|
11
12
|
import { ticketsRoot } from '../../../core/human/root.js';
|
|
12
13
|
import { scanInbox, scanPageHistory } from '../../../core/human/scan.js';
|
|
13
14
|
import { readPageTicket, readTicketResult, validatePageTicketResponses, writePageProgress, } from '../../../core/human/tickets.js';
|
|
15
|
+
import { describePageAnswer } from '../../../core/human/answer.js';
|
|
14
16
|
import { readConfig } from '../../../core/config.js';
|
|
15
17
|
import { cancelHumanTicket, resolvePageTicket } from '../../human/finish.js';
|
|
16
18
|
import { filterTerminalReviewTickets } from '../../../core/review/ticket-filter.js';
|
|
@@ -80,6 +82,8 @@ function pageSlotToDTO(slot) {
|
|
|
80
82
|
dto.id = slot.id;
|
|
81
83
|
if (slot.unvalidated === true)
|
|
82
84
|
dto.unvalidated = true;
|
|
85
|
+
if (slot.display === true)
|
|
86
|
+
dto.display = true;
|
|
83
87
|
return dto;
|
|
84
88
|
}
|
|
85
89
|
function pageManifestToDTO(manifest) {
|
|
@@ -102,6 +106,8 @@ function pageManifestToDTO(manifest) {
|
|
|
102
106
|
source.askedBy = manifest.source.askedBy;
|
|
103
107
|
if (manifest.source.blockedSince !== undefined)
|
|
104
108
|
source.blockedSince = manifest.source.blockedSince;
|
|
109
|
+
if (manifest.source.profileName !== undefined)
|
|
110
|
+
source.profileName = manifest.source.profileName;
|
|
105
111
|
if (manifest.source.nodeId !== undefined)
|
|
106
112
|
source.nodeId = manifest.source.nodeId;
|
|
107
113
|
if (Object.keys(source).length > 0)
|
|
@@ -163,7 +169,7 @@ function isSlotValidationError(err) {
|
|
|
163
169
|
// GET /v1/human/inbox — enumerate
|
|
164
170
|
// ===========================================================================
|
|
165
171
|
function handleList() {
|
|
166
|
-
const productKinds = readConfig('user').page_components
|
|
172
|
+
const productKinds = readConfig('user').page_components;
|
|
167
173
|
const allItems = scanInbox(productKinds);
|
|
168
174
|
const filtered = filterTerminalReviewTickets(allItems);
|
|
169
175
|
const tickets = filtered.map((item) => {
|
|
@@ -210,7 +216,7 @@ function handleHistory(ctx) {
|
|
|
210
216
|
if (!nodeId || typeof nodeId !== 'string') {
|
|
211
217
|
throw new ApiError(400, 'invalid_request', 'history requires a node_id query param');
|
|
212
218
|
}
|
|
213
|
-
const productKinds = readConfig('user').page_components
|
|
219
|
+
const productKinds = readConfig('user').page_components;
|
|
214
220
|
const history = scanPageHistory(nodeId, productKinds);
|
|
215
221
|
const canonicalRoot = realpathSync(ticketsRoot());
|
|
216
222
|
const tickets = history.map((item) => {
|
|
@@ -230,6 +236,8 @@ function handleHistory(ctx) {
|
|
|
230
236
|
};
|
|
231
237
|
if (item.subtitle !== '')
|
|
232
238
|
dto.subtitle = item.subtitle;
|
|
239
|
+
if (item.answerDigest !== undefined)
|
|
240
|
+
dto.answer_digest = item.answerDigest;
|
|
233
241
|
return dto;
|
|
234
242
|
});
|
|
235
243
|
return { status: 200, body: { tickets } };
|
|
@@ -245,7 +253,7 @@ function handleGetPage(ctx) {
|
|
|
245
253
|
if (resolved.kind === 'review') {
|
|
246
254
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have no page read operation');
|
|
247
255
|
}
|
|
248
|
-
const productKinds = readConfig('user').page_components
|
|
256
|
+
const productKinds = readConfig('user').page_components;
|
|
249
257
|
let ticket;
|
|
250
258
|
try {
|
|
251
259
|
ticket = readPageTicket(resolved.dir, productKinds);
|
|
@@ -287,7 +295,7 @@ async function handleRespond(ctx) {
|
|
|
287
295
|
if (result?.kind === 'canceled') {
|
|
288
296
|
throw new ApiError(409, 'ticket_canceled', 'canceled tickets cannot be answered');
|
|
289
297
|
}
|
|
290
|
-
const productKinds = readConfig('user').page_components
|
|
298
|
+
const productKinds = readConfig('user').page_components;
|
|
291
299
|
try {
|
|
292
300
|
validatePageTicketResponses(resolved.dir, request.responses, productKinds);
|
|
293
301
|
}
|
|
@@ -330,7 +338,7 @@ function handleProgress(ctx) {
|
|
|
330
338
|
if (result !== null && result.kind !== 'canceled') {
|
|
331
339
|
throw new ApiError(409, 'ticket_already_resolved', 'ticket was already resolved');
|
|
332
340
|
}
|
|
333
|
-
const productKinds = readConfig('user').page_components
|
|
341
|
+
const productKinds = readConfig('user').page_components;
|
|
334
342
|
try {
|
|
335
343
|
writePageProgress(resolved.dir, request.responses, productKinds);
|
|
336
344
|
return { status: 200, body: {} };
|
|
@@ -390,7 +398,14 @@ function handleGetResponse(ctx) {
|
|
|
390
398
|
if (result.kind !== 'page') {
|
|
391
399
|
throw new ApiError(409, 'ticket_kind_mismatch', 'unexpected result kind');
|
|
392
400
|
}
|
|
393
|
-
|
|
401
|
+
const productKinds = readConfig('user').page_components;
|
|
402
|
+
try {
|
|
403
|
+
const manifest = parsePage(resolved.dir, productKinds);
|
|
404
|
+
return { status: 200, body: { ...result, answer: describePageAnswer(manifest, result.responses, { completedAt: result.completedAt, productKinds }) } };
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
throw new ApiError(500, 'internal', 'the ticket answer could not be read');
|
|
408
|
+
}
|
|
394
409
|
}
|
|
395
410
|
// ===========================================================================
|
|
396
411
|
// GET /v1/human/inbox/:ticket_id/render — the page's HTML payload
|
|
@@ -403,7 +418,7 @@ function handleRenderPage(ctx) {
|
|
|
403
418
|
if (resolved.kind === 'review') {
|
|
404
419
|
throw new ApiError(409, 'ticket_kind_unsupported', 'review tickets have no page render operation');
|
|
405
420
|
}
|
|
406
|
-
const productKinds = readConfig('user').page_components
|
|
421
|
+
const productKinds = readConfig('user').page_components;
|
|
407
422
|
// Addressable for pending, resolved, and canceled tickets alike — the same
|
|
408
423
|
// read rule as the detail route: a page stays readable after it resolves.
|
|
409
424
|
let ticket;
|
package/dist/daemon/cron-run.js
CHANGED
|
@@ -163,6 +163,16 @@ function resolveLiveProfile(c) {
|
|
|
163
163
|
return null;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
function resolveLiveProfileName(c) {
|
|
167
|
+
if (c.profile == null)
|
|
168
|
+
return undefined;
|
|
169
|
+
try {
|
|
170
|
+
return loadProfileManifest(c.profile).manifest.name;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
166
176
|
// ---------------------------------------------------------------------------
|
|
167
177
|
// Delivery sinks
|
|
168
178
|
// ---------------------------------------------------------------------------
|
|
@@ -223,10 +233,14 @@ function deliverHumanSink(c, stdout) {
|
|
|
223
233
|
// half-built `nfy-*` directory behind.
|
|
224
234
|
let prepared;
|
|
225
235
|
try {
|
|
236
|
+
const profileName = resolveLiveProfileName(c);
|
|
226
237
|
prepared = preparePage({
|
|
227
238
|
sourceFile: synthesized.path,
|
|
228
|
-
dialect: '
|
|
229
|
-
source: {
|
|
239
|
+
dialect: 'html',
|
|
240
|
+
source: {
|
|
241
|
+
sessionName: `cron ${c.name} (${c.cron_id})`,
|
|
242
|
+
...(profileName === undefined ? {} : { profileName }),
|
|
243
|
+
},
|
|
230
244
|
productKinds: readConfig('user').page_components,
|
|
231
245
|
});
|
|
232
246
|
}
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// The fleet registry's entry IS the node's liveness — no pid probe, no grace
|
|
9
9
|
// clock. A broker's real `exit` event enqueues an exit-policy job on the
|
|
10
10
|
// daemon's serial work lane (never inline on the Node event callback), where
|
|
11
|
-
// classifyDeadNode — one pure
|
|
11
|
+
// classifyDeadNode — one pure 8-row policy table over durable row state —
|
|
12
12
|
// decides forget / dormant / respawn / boot-failure. The startup recovery
|
|
13
13
|
// sweep classifies boot-time dead rows through the SAME table, so exit
|
|
14
14
|
// recovery and startup recovery can never diverge. Every broker's argv is
|
package/dist/daemon/fleet.d.ts
CHANGED
|
@@ -10,12 +10,16 @@ import type { HostHandle } from '../core/runtime/host.js';
|
|
|
10
10
|
* seeing a silent `dead`. */
|
|
11
11
|
export declare function surfaceBootFailure(meta: NodeMeta): Promise<void>;
|
|
12
12
|
/** Whether a dead relaunch should resume the saved session strictly, or retry
|
|
13
|
-
* as a fresh cycle instead. A cycling attempt (cycle_pending) that dies
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
13
|
+
* as a fresh cycle instead. A cycling attempt (cycle_pending) that dies before
|
|
14
|
+
* session_start must retry AS a cycle: the fresh branch it was opening is not
|
|
15
|
+
* confirmed, so resuming would silently continue the old leaf the refresh was
|
|
16
|
+
* meant to leave behind.
|
|
17
|
+
*
|
|
18
|
+
* An interrupted turn is NOT a reason to cycle. A strict reopen does restore
|
|
19
|
+
* the transcript at an idle prompt without restarting the dead turn — but the
|
|
20
|
+
* fix for that is the continuation prompt recovery delivers (see `#respawn`),
|
|
21
|
+
* not throwing the conversation away. */
|
|
22
|
+
export declare function retryResumeMode(meta: Pick<NodeMeta, 'cycle_pending'> | null): boolean;
|
|
19
23
|
export type DeadNodeAction = 'forget' | 'respawn-fresh' | 'dormant' | 'respawn-cycle' | 'respawn-resume' | 'boot-failure';
|
|
20
24
|
/** The narrow durable state the policy table reads. Structural (a Pick, not
|
|
21
25
|
* NodeMeta itself) so tests fabricate table rows directly. */
|
|
@@ -23,10 +27,18 @@ export type DeadNodeRow = Pick<NodeMeta, 'status' | 'intent' | 'pi_session_id' |
|
|
|
23
27
|
/** One pure function producing the action for a node known-dead. Used at
|
|
24
28
|
* exactly two call sites — an exit-policy job and the startup recovery sweep
|
|
25
29
|
* (design D-12) — so exit recovery and startup recovery can never diverge.
|
|
26
|
-
* Evaluated in table order; `busy` is the on-disk busy marker.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
+
* Evaluated in table order; `busy` is the on-disk busy marker.
|
|
31
|
+
*
|
|
32
|
+
* `busy` no longer selects the ACTION. Whether the engine died mid-turn or
|
|
33
|
+
* parked at an idle prompt, the saved transcript is equally resumable — pi
|
|
34
|
+
* persists whole entries (`appendFileSync` per line) so a killed generation
|
|
35
|
+
* simply leaves the file ending at its last complete entry, and the loader
|
|
36
|
+
* skips a malformed line rather than rejecting the session. A mid-turn death
|
|
37
|
+
* needs the turn re-driven, which is a continuation prompt on top of the
|
|
38
|
+
* resume (see `#respawn`), not a fresh branch. `busy` therefore survives here
|
|
39
|
+
* only to route a node that died before session_start ever recorded a session
|
|
40
|
+
* to a fresh relaunch instead of terminalizing it as a boot failure. */
|
|
41
|
+
export declare function classifyDeadNode(row: DeadNodeRow | null, busy: boolean): DeadNodeAction;
|
|
30
42
|
/** An exit with uptime at or above this resets the consecutive-failure
|
|
31
43
|
* counter before classification; a shorter-lived exit increments it. */
|
|
32
44
|
export declare const HEALTHY_UPTIME_MS = 60000;
|