@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
|
@@ -296,10 +296,10 @@ function rootProfileMessage(cwd) {
|
|
|
296
296
|
/** The one synchronous (pre-boot) decision point: no profile covers cwd, and
|
|
297
297
|
* the session IS interactive. Ask on the controlling terminal — create a
|
|
298
298
|
* profile pointing at cwd, or proceed as root — and block until answered.
|
|
299
|
-
* Deliberately a minimal `readline` prompt, not `crtr human
|
|
299
|
+
* Deliberately a minimal `readline` prompt, not `crtr human send`: that bridge
|
|
300
300
|
* is asynchronous (registers an interaction in the ticket store, then waits for
|
|
301
301
|
* the human to answer it from a separate inbox) and cannot gate pi's boot; those
|
|
302
|
-
* entry points (`human
|
|
302
|
+
* entry points (`human send`/`review`) are built for a queued,
|
|
303
303
|
* asynchronously-answered interaction, not a two-choice gate on the same TTY pi
|
|
304
304
|
* is about to take over — a raw `readline` question is the actual minimal
|
|
305
305
|
* fit here. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { basename
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
3
|
import { getNode, subscribersOf, subscribe } from '../canvas/index.js';
|
|
4
4
|
import { withCanvasWrite } from '../canvas/db.js';
|
|
5
|
-
import {
|
|
5
|
+
import { bindTicketReplyRoute } 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';
|
|
@@ -108,7 +108,7 @@ function realizeBridge(review) {
|
|
|
108
108
|
profile_id: origin.profile_id ?? null,
|
|
109
109
|
name: 'human-review',
|
|
110
110
|
});
|
|
111
|
-
|
|
111
|
+
bindTicketReplyRoute(ticketDir(bridge.node_id), bridge.node_id);
|
|
112
112
|
if (!bindBridgeNode(current.review_id, bridge.node_id)) {
|
|
113
113
|
cancelInvisibleBridge(bridge.node_id);
|
|
114
114
|
current = requireReview(current.review_id);
|
|
@@ -28,6 +28,12 @@ export declare function listReviews(filter: {
|
|
|
28
28
|
origin_node_id?: string;
|
|
29
29
|
file?: string;
|
|
30
30
|
}): ReviewRecord[];
|
|
31
|
+
/** Review ids whose immutable origin is one of `nodeIds`, including binding
|
|
32
|
+
* rows that normal user-facing review listings intentionally hide. */
|
|
33
|
+
export declare function reviewIdsByOriginNodes(nodeIds: readonly string[]): string[];
|
|
34
|
+
/** Delete review audit state for the selected origins in dependency order.
|
|
35
|
+
* Filesystem cleanup is deliberately left to the daemon lifecycle owner. */
|
|
36
|
+
export declare function deleteReviewsByOriginNodes(nodeIds: readonly string[]): string[];
|
|
31
37
|
export declare function listBindingReviews(): ReviewRecord[];
|
|
32
38
|
/** Open reviews the human has already submitted, still waiting on their companion. */
|
|
33
39
|
export declare function listSubmitRequestedReviews(): ReviewRecord[];
|
|
@@ -116,6 +116,37 @@ export function listReviews(filter) {
|
|
|
116
116
|
}
|
|
117
117
|
return readRecords(openDb(), clauses.join(' AND '), ...params);
|
|
118
118
|
}
|
|
119
|
+
/** Review ids whose immutable origin is one of `nodeIds`, including binding
|
|
120
|
+
* rows that normal user-facing review listings intentionally hide. */
|
|
121
|
+
export function reviewIdsByOriginNodes(nodeIds) {
|
|
122
|
+
const ids = [...new Set(nodeIds)];
|
|
123
|
+
if (ids.length === 0)
|
|
124
|
+
return [];
|
|
125
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
126
|
+
return openDb()
|
|
127
|
+
.prepare(`SELECT review_id FROM reviews WHERE origin_node_id IN (${placeholders}) ORDER BY created, review_id`)
|
|
128
|
+
.all(...ids).map(({ review_id }) => review_id);
|
|
129
|
+
}
|
|
130
|
+
/** Delete review audit state for the selected origins in dependency order.
|
|
131
|
+
* Filesystem cleanup is deliberately left to the daemon lifecycle owner. */
|
|
132
|
+
export function deleteReviewsByOriginNodes(nodeIds) {
|
|
133
|
+
const ids = [...new Set(nodeIds)];
|
|
134
|
+
if (ids.length === 0)
|
|
135
|
+
return [];
|
|
136
|
+
const originPlaceholders = ids.map(() => '?').join(',');
|
|
137
|
+
return withCanvasWrite((db) => {
|
|
138
|
+
const reviewIds = db
|
|
139
|
+
.prepare(`SELECT review_id FROM reviews WHERE origin_node_id IN (${originPlaceholders}) ORDER BY created, review_id`)
|
|
140
|
+
.all(...ids).map(({ review_id }) => review_id);
|
|
141
|
+
if (reviewIds.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
const reviewPlaceholders = reviewIds.map(() => '?').join(',');
|
|
144
|
+
db.prepare(`DELETE FROM review_comment_events WHERE review_id IN (${reviewPlaceholders})`).run(...reviewIds);
|
|
145
|
+
db.prepare(`DELETE FROM review_comments WHERE review_id IN (${reviewPlaceholders})`).run(...reviewIds);
|
|
146
|
+
db.prepare(`DELETE FROM reviews WHERE review_id IN (${reviewPlaceholders})`).run(...reviewIds);
|
|
147
|
+
return reviewIds;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
119
150
|
export function listBindingReviews() {
|
|
120
151
|
return readRecords(openDb(), "state = 'binding'");
|
|
121
152
|
}
|
|
@@ -61,10 +61,6 @@ export declare function cronWakeOrigin(c: Cron, kind: WakeOrigin['kind']): WakeO
|
|
|
61
61
|
* carry the signal, and a raw ISO instant is noise an agent cannot cheaply turn
|
|
62
62
|
* into an elapsed delta. */
|
|
63
63
|
export declare function buildWakeBearings(origin: WakeOrigin): string;
|
|
64
|
-
/** The node-scoped update for a fork whose copied branch already contains a
|
|
65
|
-
* full crouter baseline. Shared project and operating context stays in the
|
|
66
|
-
* inherited branch; this block replaces only source-owned facts. */
|
|
67
|
-
export declare function buildForkBearings(nodeId: string): string;
|
|
68
64
|
/** The full boot intro, four top-level sections:
|
|
69
65
|
*
|
|
70
66
|
* 1. `<crtr-bearings>` — the orienting facts about THIS node: the identity
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// A conversation with no inherited crouter bearings receives the full baseline:
|
|
4
4
|
// node identity and graph position, context-directory guidance, routed knowledge,
|
|
5
5
|
// auto-loaded project docs, situational context, and the current environment.
|
|
6
|
-
// A fork whose copied branch already carries that baseline receives only
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// A fork whose copied branch already carries that baseline receives only the
|
|
7
|
+
// new node-scoped facts that supersede its source's identity — rendered by
|
|
8
|
+
// buildForkBearingsFromState() in broker-extension-render.ts, the broker-side
|
|
9
|
+
// mirror. The copied baseline remains useful reference without being repeated.
|
|
9
10
|
//
|
|
10
11
|
// promote.ts separately delivers orchestratorContextNote() when a base node
|
|
11
12
|
// becomes an orchestrator after birth.
|
|
@@ -329,65 +330,6 @@ function managedWorktreeNote(node) {
|
|
|
329
330
|
return '';
|
|
330
331
|
return `Branch: \`${wt.branch}\`\nWorktree: \`${wt.path}\`\nBase: \`${wt.base_ref}\` @ \`${wt.base_sha}\`\nCommit work here. Land it with \`crtr node worktree close\` before finishing. Auto-drop applies only when the worktree is clean (no uncommitted changes) and nothing remains to land (zero commits ahead of the base, or its commits are already contained in the base); unlanded commits (commits not already contained in the base) or uncommitted changes require an explicit close/landing decision.`;
|
|
331
332
|
}
|
|
332
|
-
/** The review-companion block — rendered ONLY when a node carries its immutable
|
|
333
|
-
* review binding. Names the target file (the document under review), clarifies
|
|
334
|
-
* that inherited context belongs to the origin node and is not visible to the
|
|
335
|
-
* person, and notes that the visible transcript begins at the boundary marker
|
|
336
|
-
* above. */
|
|
337
|
-
function reviewCompanionBlock(meta) {
|
|
338
|
-
if (meta?.review_binding === undefined || meta?.review_binding === null)
|
|
339
|
-
return '';
|
|
340
|
-
const targetFile = meta.review_binding.target_file;
|
|
341
|
-
return (`You exist for one human review of \`${targetFile}\`. The inherited conversation belongs to ` +
|
|
342
|
-
`the origin node, not you — carry only the context needed for this review. The person's visible ` +
|
|
343
|
-
`transcript begins at the boundary marker above; everything earlier is context they cannot see.`);
|
|
344
|
-
}
|
|
345
|
-
/** The node-scoped update for a fork whose copied branch already contains a
|
|
346
|
-
* full crouter baseline. Shared project and operating context stays in the
|
|
347
|
-
* inherited branch; this block replaces only source-owned facts. */
|
|
348
|
-
export function buildForkBearings(nodeId) {
|
|
349
|
-
const node = getNode(nodeId);
|
|
350
|
-
const bearings = [
|
|
351
|
-
'<crtr-bearings>',
|
|
352
|
-
buildIdentityAssertion(nodeId),
|
|
353
|
-
'',
|
|
354
|
-
'The source node’s earlier task, graph position, lifecycle, children, schedules, worktree, situational context, and context-directory path do not carry over. Shared conversation, project, and operating context remain reference material.',
|
|
355
|
-
'',
|
|
356
|
-
'## Your node now',
|
|
357
|
-
`Lifecycle: ${node?.lifecycle ?? 'terminal'}.`,
|
|
358
|
-
];
|
|
359
|
-
const map = buildGraphMap(nodeId);
|
|
360
|
-
if (map === '') {
|
|
361
|
-
bearings.push('Canvas: independent root with no parent or inherited graph edges.');
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
bearings.push('', map);
|
|
365
|
-
}
|
|
366
|
-
bearings.push(`Context directory: \`${contextDir(nodeId)}\` (\`$CRTR_CONTEXT_DIR\`). Use this path in place of every source-node context path above.`);
|
|
367
|
-
const forkFrom = node?.fork_from;
|
|
368
|
-
const source = forkFrom !== undefined && forkFrom !== null && isSafeNodeId(forkFrom)
|
|
369
|
-
? getNode(forkFrom)
|
|
370
|
-
: null;
|
|
371
|
-
if (node !== null && (source === null || source.cwd !== node.cwd)) {
|
|
372
|
-
bearings.push(`Working directory: \`${node.cwd}\`.`);
|
|
373
|
-
}
|
|
374
|
-
if (node !== null && (source === null || source.profile_id !== node.profile_id)) {
|
|
375
|
-
const profileSection = profileNote(nodeId);
|
|
376
|
-
if (profileSection !== '')
|
|
377
|
-
bearings.push(profileSection);
|
|
378
|
-
}
|
|
379
|
-
const worktree = managedWorktreeNote(node);
|
|
380
|
-
if (worktree !== '')
|
|
381
|
-
bearings.push('', '## Your managed worktree', worktree);
|
|
382
|
-
if (node?.mode === 'orchestrator')
|
|
383
|
-
bearings.push('', orchestratorContextNote(nodeId));
|
|
384
|
-
const review = reviewCompanionBlock(node);
|
|
385
|
-
if (review !== '')
|
|
386
|
-
bearings.push('', review);
|
|
387
|
-
bearings.push('', 'The first real user message after these bearings is your mandate.', '</crtr-bearings>');
|
|
388
|
-
const situational = situationalContextBlock(nodeId);
|
|
389
|
-
return [bearings.join('\n'), situational].filter((s) => s !== '').join('\n');
|
|
390
|
-
}
|
|
391
333
|
/** The full boot intro, four top-level sections:
|
|
392
334
|
*
|
|
393
335
|
* 1. `<crtr-bearings>` — the orienting facts about THIS node: the identity
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Scope } from '../../types.js';
|
|
2
|
+
/** One resolved bare command: the name a node's bash sees, and the absolute
|
|
3
|
+
* real path the shim links to. */
|
|
4
|
+
export interface ResolvedBin {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Absolute target validated inside the contributing root when this set resolved. */
|
|
7
|
+
target: string;
|
|
8
|
+
/** Human-readable contributor, e.g. `plugin "deploy-tools"` or
|
|
9
|
+
* `config.json (/repo/.crouter)`. */
|
|
10
|
+
origin: string;
|
|
11
|
+
scope: Scope;
|
|
12
|
+
}
|
|
13
|
+
/** A declaration that did NOT become a bare command, with why. Surfaced by
|
|
14
|
+
* `crtr sys doctor`; never thrown, so one bad entry can never stop a node
|
|
15
|
+
* from launching. */
|
|
16
|
+
export interface BinIssue {
|
|
17
|
+
name: string;
|
|
18
|
+
origin: string;
|
|
19
|
+
scope: Scope;
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
export interface BinResolution {
|
|
23
|
+
/** Effective name → resolved bin, after layering. */
|
|
24
|
+
bins: Map<string, ResolvedBin>;
|
|
25
|
+
issues: BinIssue[];
|
|
26
|
+
}
|
|
27
|
+
/** The effective bare-command set for one node's cwd/profile, plus every
|
|
28
|
+
* declaration that failed to become one.
|
|
29
|
+
*
|
|
30
|
+
* Layers ascend in precedence (see `contributionLayers`). A name claimed by
|
|
31
|
+
* two contributors in the SAME layer selects no winner: it is reported and
|
|
32
|
+
* removed, so an ambiguous name resolves to nothing rather than to whichever
|
|
33
|
+
* plugin sorted first. A higher layer may still claim that name explicitly —
|
|
34
|
+
* that is an unambiguous choice, not a collision. */
|
|
35
|
+
export declare function resolveBinContributions(targetCwd: string, targetProfileId: string | null): BinResolution;
|
|
36
|
+
/** Materialize (idempotently) the shim directory for one effective set and
|
|
37
|
+
* return its absolute path, or null when the set is empty. Built in a staging
|
|
38
|
+
* dir and renamed into place, so a concurrent launch either sees no directory
|
|
39
|
+
* or sees a complete one. */
|
|
40
|
+
export declare function ensureBinShimDir(bins: Map<string, ResolvedBin>): string | null;
|
|
41
|
+
/** The shim directory to prepend to a target node's PATH, or null when that
|
|
42
|
+
* node's scopes declare no bare command. The one entry point the spawn-env
|
|
43
|
+
* boundary calls. */
|
|
44
|
+
export declare function binShimDirFor(targetCwd: string, targetProfileId: string | null): string | null;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Bare-binary contributions — the plain executables a node's bash can call by
|
|
2
|
+
// name, with no `crtr` command leaf and no exec-command JSON protocol between
|
|
3
|
+
// them and the agent.
|
|
4
|
+
//
|
|
5
|
+
// A plugin declares them in its manifest (`bin`), a user or project scope in
|
|
6
|
+
// its own `config.json` (`bin`); both are a small name → root-relative-path
|
|
7
|
+
// map. This module resolves those declarations across the same layering the
|
|
8
|
+
// rest of the config uses (project stack > user, and within one root: that
|
|
9
|
+
// root's own config > its enabled plugins), materializes one crouter-owned
|
|
10
|
+
// shim directory of symlinks, and hands its path to the spawn-env boundary
|
|
11
|
+
// (`spawn-env.ts`), which PREPENDS it to every broker child's PATH while
|
|
12
|
+
// leaving the host PATH intact behind it.
|
|
13
|
+
//
|
|
14
|
+
// The shim directory exists so the declaration stays a per-name grant: crouter
|
|
15
|
+
// links exactly the names that were declared, rather than putting a
|
|
16
|
+
// contributor's whole `bin/` directory on the agent's PATH and admitting every
|
|
17
|
+
// sibling file in it.
|
|
18
|
+
//
|
|
19
|
+
// These binaries are trusted local code running with the caller's authority —
|
|
20
|
+
// the same authority the agent's bash tool already has. There is no sandbox,
|
|
21
|
+
// no install hook, and no shell evaluation anywhere in this path. Containment
|
|
22
|
+
// is checked while resolving the effective set; a contributor can change later
|
|
23
|
+
// because it is trusted local code, not a sandboxed artifact.
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync } from 'node:fs';
|
|
26
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
27
|
+
import { binDeclarationIssues, normalizeBin, readRawConfigAtRoot } from '../config.js';
|
|
28
|
+
import { listInstalledPluginsInRoot } from '../installed-plugins.js';
|
|
29
|
+
import { crtrHome } from '../canvas/paths.js';
|
|
30
|
+
import { findProjectScopeRoots, scopeRoot } from '../scope.js';
|
|
31
|
+
function contributorIssues(origin, scope, raw) {
|
|
32
|
+
return binDeclarationIssues(raw).map((issue) => ({ name: issue.name, origin, scope, message: issue.message }));
|
|
33
|
+
}
|
|
34
|
+
function pluginContributors(scope, root) {
|
|
35
|
+
return listInstalledPluginsInRoot(scope, root)
|
|
36
|
+
.filter((plugin) => plugin.enabled && plugin.manifest.bin !== undefined)
|
|
37
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
38
|
+
.map((plugin) => {
|
|
39
|
+
const origin = `plugin "${plugin.name}"`;
|
|
40
|
+
return { origin, scope, root: plugin.root, bin: normalizeBin(plugin.manifest.bin), issues: contributorIssues(origin, scope, plugin.manifest.bin) };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** A scope's own `config.json` contributions. Paths resolve against the scope's
|
|
44
|
+
* AUTHORING root: the project directory for a project scope (so a repo writes
|
|
45
|
+
* `tools/repoctl`, where its tools actually live, not `../tools/repoctl` out
|
|
46
|
+
* of `.crouter/`), and `~/.crouter` itself for the user scope, which has no
|
|
47
|
+
* enclosing project. */
|
|
48
|
+
function configContributor(scope, scopeRootPath) {
|
|
49
|
+
const raw = readRawConfigAtRoot(scopeRootPath)?.bin;
|
|
50
|
+
if (raw === undefined)
|
|
51
|
+
return [];
|
|
52
|
+
const origin = `config.json (${scopeRootPath})`;
|
|
53
|
+
const bin = normalizeBin(raw);
|
|
54
|
+
const root = scope === 'project' ? dirname(scopeRootPath) : scopeRootPath;
|
|
55
|
+
return [{ origin, scope, root, bin, issues: contributorIssues(origin, scope, raw) }];
|
|
56
|
+
}
|
|
57
|
+
/** The contribution LAYERS in ascending precedence: a later layer's entry
|
|
58
|
+
* replaces an earlier layer's same name. Within one root the root's own
|
|
59
|
+
* `config.json` outranks its plugins (the rule `layerPluginKinds` follows for
|
|
60
|
+
* `kinds`), and the project stack — nearest root last — outranks the user
|
|
61
|
+
* scope entirely. Contributors WITHIN one layer never outrank each other:
|
|
62
|
+
* two of them claiming one name is a collision, not a precedence question. */
|
|
63
|
+
function contributionLayers(targetCwd, targetProfileId) {
|
|
64
|
+
const layers = [];
|
|
65
|
+
const userRoot = scopeRoot('user');
|
|
66
|
+
if (userRoot !== null) {
|
|
67
|
+
layers.push(pluginContributors('user', userRoot), configContributor('user', userRoot));
|
|
68
|
+
}
|
|
69
|
+
const projectRootsNearestFirst = findProjectScopeRoots(targetCwd, targetProfileId);
|
|
70
|
+
for (const root of [...projectRootsNearestFirst].reverse()) {
|
|
71
|
+
layers.push(pluginContributors('project', root), configContributor('project', root));
|
|
72
|
+
}
|
|
73
|
+
return layers.filter((layer) => layer.length > 0);
|
|
74
|
+
}
|
|
75
|
+
/** Resolve one declared path against its contributing root: it must stay
|
|
76
|
+
* inside that root, be a regular file, and carry a POSIX exec bit when the
|
|
77
|
+
* effective set is resolved. Returns the absolute real path, or the reason it
|
|
78
|
+
* is not usable. */
|
|
79
|
+
function resolveTarget(root, relative) {
|
|
80
|
+
let rootReal;
|
|
81
|
+
let target;
|
|
82
|
+
try {
|
|
83
|
+
rootReal = realpathSync(root);
|
|
84
|
+
target = realpathSync(resolve(root, relative));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return { reason: `target does not exist: ${join(root, relative)}` };
|
|
88
|
+
}
|
|
89
|
+
if (target !== rootReal && !target.startsWith(rootReal + sep)) {
|
|
90
|
+
return { reason: `target escapes its contributing root: ${relative}` };
|
|
91
|
+
}
|
|
92
|
+
const stat = statSync(target);
|
|
93
|
+
if (!stat.isFile())
|
|
94
|
+
return { reason: `target is not a regular file: ${target}` };
|
|
95
|
+
if ((stat.mode & 0o111) === 0)
|
|
96
|
+
return { reason: `target lacks the POSIX exec bit: ${target}` };
|
|
97
|
+
return { target };
|
|
98
|
+
}
|
|
99
|
+
/** The effective bare-command set for one node's cwd/profile, plus every
|
|
100
|
+
* declaration that failed to become one.
|
|
101
|
+
*
|
|
102
|
+
* Layers ascend in precedence (see `contributionLayers`). A name claimed by
|
|
103
|
+
* two contributors in the SAME layer selects no winner: it is reported and
|
|
104
|
+
* removed, so an ambiguous name resolves to nothing rather than to whichever
|
|
105
|
+
* plugin sorted first. A higher layer may still claim that name explicitly —
|
|
106
|
+
* that is an unambiguous choice, not a collision. */
|
|
107
|
+
export function resolveBinContributions(targetCwd, targetProfileId) {
|
|
108
|
+
const bins = new Map();
|
|
109
|
+
const issues = [];
|
|
110
|
+
for (const layer of contributionLayers(targetCwd, targetProfileId)) {
|
|
111
|
+
const claims = new Map();
|
|
112
|
+
for (const contributor of layer) {
|
|
113
|
+
issues.push(...contributor.issues);
|
|
114
|
+
for (const name of Object.keys(contributor.bin)) {
|
|
115
|
+
const existing = claims.get(name);
|
|
116
|
+
if (existing === undefined)
|
|
117
|
+
claims.set(name, [contributor]);
|
|
118
|
+
else
|
|
119
|
+
existing.push(contributor);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const [name, claimants] of claims) {
|
|
123
|
+
if (claimants.length > 1) {
|
|
124
|
+
const origins = claimants.map((claimant) => claimant.origin).join(', ');
|
|
125
|
+
bins.delete(name);
|
|
126
|
+
for (const claimant of claimants) {
|
|
127
|
+
issues.push({ name, origin: claimant.origin, scope: claimant.scope, message: `bare command "${name}" is claimed by ${origins} at the same precedence — no winner is selected and the name is not installed` });
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const contributor = claimants[0];
|
|
132
|
+
const resolved = resolveTarget(contributor.root, contributor.bin[name]);
|
|
133
|
+
if ('reason' in resolved) {
|
|
134
|
+
issues.push({ name, origin: contributor.origin, scope: contributor.scope, message: resolved.reason });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
bins.set(name, { name, target: resolved.target, origin: contributor.origin, scope: contributor.scope });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { bins, issues };
|
|
141
|
+
}
|
|
142
|
+
/** Content address of one effective bare-command set — the shim directory's
|
|
143
|
+
* name. Because the address covers every name and target, an existing
|
|
144
|
+
* directory is already correct: nothing is ever rewritten in place, a changed
|
|
145
|
+
* set simply materializes beside the old one, and two processes racing the
|
|
146
|
+
* same set produce byte-identical trees. */
|
|
147
|
+
function shimAddress(bins) {
|
|
148
|
+
const lines = [...bins.values()]
|
|
149
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
150
|
+
.map((bin) => `${bin.name}\u0000${bin.target}`)
|
|
151
|
+
.join('\n');
|
|
152
|
+
return createHash('sha256').update(lines).digest('hex').slice(0, 32);
|
|
153
|
+
}
|
|
154
|
+
/** Materialize (idempotently) the shim directory for one effective set and
|
|
155
|
+
* return its absolute path, or null when the set is empty. Built in a staging
|
|
156
|
+
* dir and renamed into place, so a concurrent launch either sees no directory
|
|
157
|
+
* or sees a complete one. */
|
|
158
|
+
export function ensureBinShimDir(bins) {
|
|
159
|
+
if (bins.size === 0)
|
|
160
|
+
return null;
|
|
161
|
+
const dir = join(crtrHome(), 'bin-shims', shimAddress(bins));
|
|
162
|
+
if (existsSync(dir))
|
|
163
|
+
return dir;
|
|
164
|
+
const staging = `${dir}.staging-${process.pid}-${Date.now().toString(36)}`;
|
|
165
|
+
mkdirSync(staging, { recursive: true });
|
|
166
|
+
try {
|
|
167
|
+
for (const bin of bins.values())
|
|
168
|
+
symlinkSync(bin.target, join(staging, bin.name));
|
|
169
|
+
renameSync(staging, dir);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
rmSync(staging, { recursive: true, force: true });
|
|
173
|
+
// Another launch materializing the same address won the rename — its tree
|
|
174
|
+
// is identical by construction, so the address is still correct.
|
|
175
|
+
if (existsSync(dir))
|
|
176
|
+
return dir;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
return dir;
|
|
180
|
+
}
|
|
181
|
+
/** The shim directory to prepend to a target node's PATH, or null when that
|
|
182
|
+
* node's scopes declare no bare command. The one entry point the spawn-env
|
|
183
|
+
* boundary calls. */
|
|
184
|
+
export function binShimDirFor(targetCwd, targetProfileId) {
|
|
185
|
+
return ensureBinShimDir(resolveBinContributions(targetCwd, targetProfileId).bins);
|
|
186
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Socket } from 'node:net';
|
|
2
2
|
import { FrameDecoder, type BrokerSnapshot, type BrokerToClient, type ClientRole } from '../broker-protocol.js';
|
|
3
|
+
/** True when the live broker has at least one helloed terminal or web viewer.
|
|
4
|
+
* Callers establish broker liveness separately because a crash can leave the
|
|
5
|
+
* last persisted count behind. */
|
|
6
|
+
export declare function hasAttachedBrokerViewers(nodeId: string): boolean;
|
|
3
7
|
export interface BrokerClient {
|
|
4
8
|
id: string;
|
|
5
9
|
role: ClientRole;
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { jobDir } from '../../canvas/paths.js';
|
|
3
3
|
import { emitEvent } from '../../events/emit.js';
|
|
4
4
|
import { encodeFrame, FrameDecoder, BROKER_READ_CAPS, } from '../broker-protocol.js';
|
|
5
5
|
/** Per-viewer outbound high-water mark. Slow viewers are shed rather than allowed to backpressure the shared engine indefinitely. */
|
|
6
6
|
const MAX_QUEUED_FRAMES = 1000;
|
|
7
7
|
const MAX_PENDING_BYTES = 32 * 1024 * 1024;
|
|
8
|
+
/** True when the live broker has at least one helloed terminal or web viewer.
|
|
9
|
+
* Callers establish broker liveness separately because a crash can leave the
|
|
10
|
+
* last persisted count behind. */
|
|
11
|
+
export function hasAttachedBrokerViewers(nodeId) {
|
|
12
|
+
try {
|
|
13
|
+
const record = JSON.parse(readFileSync(`${jobDir(nodeId)}/attach.json`, 'utf8'));
|
|
14
|
+
return typeof record.viewers === 'number' && record.viewers > 0;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
/** Owns connected viewers, their outbound backpressure, and durable attach presence. */
|
|
9
21
|
export class BrokerClientRegistry {
|
|
10
22
|
nodeId;
|
|
@@ -5,5 +5,5 @@ interface AbortableSession {
|
|
|
5
5
|
/** Mark one synchronous ExtensionContext.abort() call as a refresh boundary. */
|
|
6
6
|
export declare function runRefreshAbort(abort: () => void): void;
|
|
7
7
|
/** Bound into Pi's extension context by the broker for every session generation. */
|
|
8
|
-
export declare function abortSessionFromExtension(session: AbortableSession): void;
|
|
8
|
+
export declare function abortSessionFromExtension(session: AbortableSession, stageRefreshAbort?: () => void): void;
|
|
9
9
|
export {};
|
|
@@ -16,8 +16,10 @@ export function runRefreshAbort(abort) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/** Bound into Pi's extension context by the broker for every session generation. */
|
|
19
|
-
export function abortSessionFromExtension(session) {
|
|
20
|
-
if (refreshAbortScope)
|
|
19
|
+
export function abortSessionFromExtension(session, stageRefreshAbort) {
|
|
20
|
+
if (refreshAbortScope) {
|
|
21
21
|
session.clearQueue();
|
|
22
|
+
stageRefreshAbort?.();
|
|
23
|
+
}
|
|
22
24
|
void session.abort();
|
|
23
25
|
}
|
|
@@ -48,9 +48,11 @@ export class FaultRetry {
|
|
|
48
48
|
publishSettledProviderFault(generation) {
|
|
49
49
|
const agentEnd = generation.stagedProviderAgentEnd;
|
|
50
50
|
const watchdogAbort = generation.stagedWatchdogAbort;
|
|
51
|
+
const refreshAbort = generation.stagedRefreshAbort;
|
|
51
52
|
const overflowFailure = generation.stagedOverflowFailure;
|
|
52
53
|
generation.stagedProviderAgentEnd = null;
|
|
53
54
|
generation.stagedWatchdogAbort = false;
|
|
55
|
+
generation.stagedRefreshAbort = false;
|
|
54
56
|
generation.stagedOverflowFailure = null;
|
|
55
57
|
const settledSession = generation.session;
|
|
56
58
|
// A failed daemon retry settles here with the episode's fault marker still
|
|
@@ -84,6 +86,8 @@ export class FaultRetry {
|
|
|
84
86
|
}
|
|
85
87
|
if (last?.stopReason !== 'error')
|
|
86
88
|
return;
|
|
89
|
+
if (refreshAbort)
|
|
90
|
+
return;
|
|
87
91
|
const raw = typeof last.errorMessage === 'string' ? last.errorMessage : 'engine error (no errorMessage recorded)';
|
|
88
92
|
const uncompactedOverflow = isContextOverflow(last, settledSession.model?.contextWindow);
|
|
89
93
|
const classified = classify('pi→provider', last);
|
|
@@ -181,7 +181,9 @@ export type FrameDispatchDeps = {
|
|
|
181
181
|
toolGroups: ToolGroupTracker;
|
|
182
182
|
projection: EventProjection;
|
|
183
183
|
rebind: RebindDriver;
|
|
184
|
-
|
|
184
|
+
/** The bound review id whose boundary marker opens this companion's visible
|
|
185
|
+
* transcript; undefined for ordinary nodes. */
|
|
186
|
+
boundaryReviewId: string | undefined;
|
|
185
187
|
pendingDialogs: Map<string, PendingDialog>;
|
|
186
188
|
sendWelcome: (client: BrokerClient) => void;
|
|
187
189
|
replayExtraPendingDialogsTo: (client: BrokerClient) => void;
|
|
@@ -14,7 +14,7 @@ import { clearFault, readFault } from '../fault.js';
|
|
|
14
14
|
import { isManagedProvider, readSubscriptionPool, } from '../../subscription-state.js';
|
|
15
15
|
import { SessionListCache } from '../session-list-cache.js';
|
|
16
16
|
import { sessionListCachePath } from '../../canvas/paths.js';
|
|
17
|
-
import {
|
|
17
|
+
import { isReviewBoundary } from '../session-visibility.js';
|
|
18
18
|
import { isUnknownModel, parseModelSpec } from '../model-selection.js';
|
|
19
19
|
import { buildCommandList, buildGetTreeData, buildListMemoryRefsData, buildListModelsData, buildScopedModelsData, buildSessionsData, buildSettingsData, modelKey, } from './read-ops.js';
|
|
20
20
|
export const REVIEW_BOUNDARY_NAVIGATION_CODE = 'review_boundary_navigation';
|
|
@@ -279,7 +279,7 @@ export function stripHiddenEntriesFromJsonlExport(filePath) {
|
|
|
279
279
|
}
|
|
280
280
|
/** Owns frame-local input state while dispatching through explicit broker collaborators. */
|
|
281
281
|
export function createFrameDispatchContext(deps) {
|
|
282
|
-
const { nodeId, cfg, registry, toolGroups, projection, rebind,
|
|
282
|
+
const { nodeId, cfg, registry, toolGroups, projection, rebind, boundaryReviewId, pendingDialogs, sendWelcome, replayExtraPendingDialogsTo, reWelcomeAll, disposeAndExit, persistModelChoice, broadcastModelChanged, registryOf, formatModelSpec, formatExactModelSpec, resolveLaunchModel, } = deps;
|
|
283
283
|
const currentSession = () => rebind.session();
|
|
284
284
|
const currentServices = () => rebind.services();
|
|
285
285
|
let kickoffPending;
|
|
@@ -657,11 +657,10 @@ export function createFrameDispatchContext(deps) {
|
|
|
657
657
|
// pass no id (unchanged behavior).
|
|
658
658
|
...(id !== undefined ? { id } : {}),
|
|
659
659
|
});
|
|
660
|
-
const isPreBoundaryReviewEntry = (entryId) =>
|
|
660
|
+
const isPreBoundaryReviewEntry = (entryId) => boundaryReviewId !== undefined &&
|
|
661
661
|
!currentSession()
|
|
662
662
|
.sessionManager.getBranch(entryId)
|
|
663
|
-
.some((entry) => entry
|
|
664
|
-
entry.customType === REVIEW_BOUNDARY_CUSTOM_TYPE);
|
|
663
|
+
.some((entry) => isReviewBoundary(entry, boundaryReviewId));
|
|
665
664
|
const reviewBoundaryRefusalTo = (client, targetId) => {
|
|
666
665
|
const refusal = new ReviewBoundaryNavigationError(targetId);
|
|
667
666
|
registry.sendFrame(client, {
|
|
@@ -1112,7 +1111,7 @@ export function createFrameDispatchContext(deps) {
|
|
|
1112
1111
|
}
|
|
1113
1112
|
case 'get_tree': {
|
|
1114
1113
|
try {
|
|
1115
|
-
registry.sendFrame(client, buildGetTreeData(frame.id, currentSession(),
|
|
1114
|
+
registry.sendFrame(client, buildGetTreeData(frame.id, currentSession(), boundaryReviewId));
|
|
1116
1115
|
}
|
|
1117
1116
|
catch (err) {
|
|
1118
1117
|
engineErrorTo(client, frame.id)(err);
|
|
@@ -18,6 +18,6 @@ export declare function buildCommandList(session: BrokerSession): CommandDisclos
|
|
|
18
18
|
export declare function buildListMemoryRefsData(id: string, refs: RefMeta[], engineLeadingCommandNames: string[]): ListMemoryRefsData;
|
|
19
19
|
export declare function buildListModelsData(id: string, session: BrokerSession): ListModelsData;
|
|
20
20
|
export declare function buildScopedModelsData(id: string, session: BrokerSession): ListScopedModelsData;
|
|
21
|
-
export declare function buildGetTreeData(id: string, session: BrokerSession,
|
|
21
|
+
export declare function buildGetTreeData(id: string, session: BrokerSession, boundaryReviewId: string | undefined): GetTreeData;
|
|
22
22
|
export declare function buildSettingsData(id: string, session: BrokerSession): GetSettingsData;
|
|
23
23
|
export declare function buildSessionsData(id: string, scope: 'cwd' | 'all', session: BrokerSession, cache: SessionListCache): Promise<ListSessionsData>;
|
|
@@ -67,9 +67,9 @@ export function buildScopedModelsData(id, session) {
|
|
|
67
67
|
enabledModelIds: session.settingsManager.getEnabledModels() ?? null,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
export function buildGetTreeData(id, session,
|
|
70
|
+
export function buildGetTreeData(id, session, boundaryReviewId) {
|
|
71
71
|
const manager = session.sessionManager;
|
|
72
|
-
const tree =
|
|
72
|
+
const tree = boundaryReviewId !== undefined ? boundaryRootedTree(manager.getTree(), boundaryReviewId) : manager.getTree();
|
|
73
73
|
const visibleEntryIds = new Set();
|
|
74
74
|
const collectEntryIds = (nodes) => {
|
|
75
75
|
for (const node of nodes) {
|
|
@@ -56,7 +56,11 @@ export class RebindDriver {
|
|
|
56
56
|
await candidateSession.bindExtensions({
|
|
57
57
|
uiContext: this.deps.uiContext(),
|
|
58
58
|
mode: 'rpc',
|
|
59
|
-
abortHandler: () => abortSessionFromExtension(candidateSession)
|
|
59
|
+
abortHandler: () => abortSessionFromExtension(candidateSession, () => {
|
|
60
|
+
const generation = this.installed;
|
|
61
|
+
if (generation?.session === candidateSession)
|
|
62
|
+
generation.stagedRefreshAbort = true;
|
|
63
|
+
}),
|
|
60
64
|
shutdownHandler: () => this.requestShutdown(),
|
|
61
65
|
});
|
|
62
66
|
if (ticket !== this.latestRebindRequest)
|
|
@@ -92,6 +96,7 @@ export class RebindDriver {
|
|
|
92
96
|
fallback: { blocked: false },
|
|
93
97
|
stagedProviderAgentEnd: null,
|
|
94
98
|
stagedWatchdogAbort: false,
|
|
99
|
+
stagedRefreshAbort: false,
|
|
95
100
|
stagedOverflowFailure: null,
|
|
96
101
|
executingTools: new Set(),
|
|
97
102
|
};
|
|
@@ -114,10 +114,29 @@ function worktreeNote(node) {
|
|
|
114
114
|
return '';
|
|
115
115
|
return `Branch: \`${worktree.branch}\`\nWorktree: \`${worktree.path}\`\nBase: \`${worktree.base_ref}\` @ \`${worktree.base_sha}\`\nCommit work here. Land it with \`crtr node worktree close\` before finishing. Auto-drop applies only when the worktree is clean (no uncommitted changes) and nothing remains to land (zero commits ahead of the base, or its commits are already contained in the base); unlanded commits (commits not already contained in the base) or uncommitted changes require an explicit close/landing decision.`;
|
|
116
116
|
}
|
|
117
|
+
/** The companion note — rendered ONLY when a node carries its immutable
|
|
118
|
+
* companion binding. Names the target (the document under review, or the page
|
|
119
|
+
* the feedback is about), and for a page-feedback companion carries the whole
|
|
120
|
+
* behavioral contract — the companion is an ordinary fork of the sending node,
|
|
121
|
+
* so this note is the only place it learns the role. */
|
|
117
122
|
function reviewNote(node) {
|
|
118
123
|
const review = node.review_binding;
|
|
119
124
|
if (review === null || review === undefined)
|
|
120
125
|
return '';
|
|
126
|
+
if (review.kind === 'page_feedback') {
|
|
127
|
+
const ticketId = review.review_id;
|
|
128
|
+
const targetFile = review.target_file;
|
|
129
|
+
return (`You exist for one page's feedback conversation — ticket \`${ticketId}\`, page source \`${targetFile}\`. ` +
|
|
130
|
+
`Think with the person about this one page and answer in a few plain sentences from the context you already carry. ` +
|
|
131
|
+
`Revise the page in place with \`crtr human send --replace ${ticketId} --page ${targetFile}\` — it swaps in on their screen immediately — and briefly say what changed; a silent swap leaves them guessing. ` +
|
|
132
|
+
`After dealing with an open feedback item — by revising the page or answering it here — resolve it with \`crtr human feedback resolve <comment_id>\`; ` +
|
|
133
|
+
`\`crtr human feedback list ${ticketId}\` shows each item's id and status. ` +
|
|
134
|
+
`Never author feedback or resolve an item before dealing with it — a sent comment is immutable, and resolution is your report that the work is done. ` +
|
|
135
|
+
`Asked to act beyond this page, give your advice and point the person at submitting it: the submitted answer is what reaches the task's owner, while work done here is invisible to it and ends when the ticket settles. ` +
|
|
136
|
+
`When a question needs real exploration, delegate it to a child node and keep talking while it runs. ` +
|
|
137
|
+
`The inherited conversation belongs to the origin node, not you — its task stays with it; carry only the context needed for this page. ` +
|
|
138
|
+
`The person's visible transcript begins at the boundary marker above; everything earlier is context they cannot see — never treat it as shared ground; when they ask about it, answer plainly in your own words rather than replaying it.`);
|
|
139
|
+
}
|
|
121
140
|
return `You exist for one human review of \`${review.target_file}\`. The inherited conversation belongs to the origin node, not you — carry only the context needed for this review. The person's visible transcript begins at the boundary marker above; everything earlier is context they cannot see.`;
|
|
122
141
|
}
|
|
123
142
|
export function buildContextBearingsFromState(state, projectContext, situational = '', seen) {
|