@north-light/crouter 0.3.198 → 0.3.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +18 -18
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +81 -34
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +6 -6
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +26 -1
- package/dist/clients/attach/render/chat-view.js +61 -11
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -993
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -21
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +1 -3
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +71 -27
- package/dist/clients/inbox/tui/slots.d.ts +3 -1
- package/dist/clients/inbox/tui/slots.js +16 -12
- package/dist/clients/inbox/tui/types.d.ts +2 -7
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +71 -114
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{html.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +13 -5
- package/dist/commands/human/prompts.js +160 -97
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +2 -12
- package/dist/commands/human/shared.js +17 -11
- package/dist/commands/human.js +29 -21
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- package/dist/core/__tests__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +31 -26
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +37 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +2 -1
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +137 -4
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.js +4 -0
- package/dist/core/human/__tests__/page-tickets.test.js +65 -52
- package/dist/core/human/__tests__/page.test.js +56 -98
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
- package/dist/core/human/answer-text.js +3 -3
- package/dist/core/human/answer.d.ts +6 -4
- package/dist/core/human/answer.js +4 -10
- package/dist/core/human/component-docs.d.ts +11 -7
- package/dist/core/human/component-docs.js +361 -88
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +40 -5
- package/dist/core/human/page-catalog.js +102 -30
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +23 -191
- package/dist/core/human/page-schema.js +60 -89
- package/dist/core/human/page-synth.d.ts +1 -1
- package/dist/core/human/page-synth.js +15 -18
- package/dist/core/human/page.d.ts +12 -27
- package/dist/core/human/page.js +142 -474
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -4
- package/dist/core/human/scan.js +16 -15
- package/dist/core/human/summary.d.ts +1 -2
- package/dist/core/human/summary.js +2 -2
- package/dist/core/human/tickets.d.ts +20 -14
- package/dist/core/human/tickets.js +91 -31
- package/dist/core/human/types.d.ts +10 -2
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +3 -3
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +17 -6
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +21 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +39 -115
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +14 -16
- package/dist/daemon/human/finish.d.ts +12 -7
- package/dist/daemon/human/finish.js +101 -38
- package/dist/daemon/human/sweep.js +5 -1
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +3 -1
- package/dist/daemon/review/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.js +19 -5
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +44 -2
- package/dist/types.js +7 -2
- package/package.json +4 -4
- package/runtime.lock.json +5 -33
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/commands/human/doc.d.ts +0 -2
- package/dist/commands/human/doc.js +0 -91
- package/dist/commands/human/html.js +0 -81
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/html-markdown.test.js +0 -52
- package/dist/core/human/__tests__/page-render.test.js +0 -66
- package/dist/core/human/html-markdown.d.ts +0 -3
- package/dist/core/human/html-markdown.js +0 -288
- package/dist/core/human/page-render.d.ts +0 -4
- package/dist/core/human/page-render.js +0 -105
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1778
- package/dist/pages/comments.d.ts +0 -72
- package/dist/pages/comments.js +0 -176
- package/dist/pages/controls.d.ts +0 -98
- package/dist/pages/controls.js +0 -261
- package/dist/pages/elements/cards.d.ts +0 -28
- package/dist/pages/elements/cards.js +0 -915
- package/dist/pages/elements/chart.d.ts +0 -23
- package/dist/pages/elements/chart.js +0 -664
- package/dist/pages/elements/options.d.ts +0 -30
- package/dist/pages/elements/options.js +0 -742
- package/dist/pages/elements/pages.d.ts +0 -30
- package/dist/pages/elements/pages.js +0 -377
- package/dist/pages/elements/slot.d.ts +0 -26
- package/dist/pages/elements/slot.js +0 -112
- package/dist/pages/elements/table.d.ts +0 -36
- package/dist/pages/elements/table.js +0 -1043
- package/dist/pages/elements/text.d.ts +0 -31
- package/dist/pages/elements/text.js +0 -1175
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -134
- package/dist/pages/host.js +0 -165
- package/dist/pages/readonly.d.ts +0 -24
- package/dist/pages/readonly.js +0 -31
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/responses.d.ts +0 -37
- package/dist/pages/responses.js +0 -56
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -46,6 +46,20 @@ export interface BrokerSnapshot {
|
|
|
46
46
|
}>;
|
|
47
47
|
title: string | undefined;
|
|
48
48
|
};
|
|
49
|
+
/** The engine's UNRUN queue — the steer/follow-up texts a writable client
|
|
50
|
+
* sent mid-turn that have not started yet (pi's `_steeringMessages` +
|
|
51
|
+
* `_followUpMessages`, read non-destructively). `state.pendingMessageCount`
|
|
52
|
+
* is the same queue's SIZE; without the texts a (re)attaching viewer can say
|
|
53
|
+
* how many messages are waiting but not which, so it cannot render the
|
|
54
|
+
* queued rows it renders from `queue_update` while attached — the welcome
|
|
55
|
+
* would no longer fully determine viewer state.
|
|
56
|
+
*
|
|
57
|
+
* Ingest defensively: a broker pinned to a pre-`queued` runtime generation is
|
|
58
|
+
* still live and sends `welcome` without it. */
|
|
59
|
+
queued?: {
|
|
60
|
+
steering: string[];
|
|
61
|
+
followUp: string[];
|
|
62
|
+
};
|
|
49
63
|
/** Broker-owned summaries keyed by the first tool-call id in each group.
|
|
50
64
|
* Structured objects with bullets and filesDeleted.
|
|
51
65
|
* Optional so snapshots from an older live runtime remain readable. */
|
|
@@ -29,7 +29,7 @@ interface PendingDialog {
|
|
|
29
29
|
* bash children. No-op before the session is built or after a clean dispose. */
|
|
30
30
|
export declare function disposeActiveSession(): void;
|
|
31
31
|
export declare function runBroker(nodeId: string, startupAt?: bigint, startupTs?: string): Promise<void>;
|
|
32
|
-
export declare function snapshotMessages(session: CreateAgentSessionResult['session'],
|
|
32
|
+
export declare function snapshotMessages(session: CreateAgentSessionResult['session'], boundaryReviewId?: string): BrokerSnapshot['messages'];
|
|
33
33
|
export declare function buildBrokerSession(engine: BrokerEngine, cfg: BrokerSdkConfig): Promise<{
|
|
34
34
|
session: CreateAgentSessionResult['session'];
|
|
35
35
|
services: AgentSessionServices;
|
|
@@ -216,8 +216,11 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
216
216
|
let { session, services, resuming, runtime } = await buildBrokerSession(engine, cfg);
|
|
217
217
|
// Review identity is immutable for the broker's lifetime. Snapshot producers
|
|
218
218
|
// close over this one construction-time classification rather than re-reading
|
|
219
|
-
// canvas metadata for every welcome or session replacement.
|
|
220
|
-
|
|
219
|
+
// canvas metadata for every welcome or session replacement. The BOUND review
|
|
220
|
+
// id (not a mere boolean) is what selects this companion's own boundary
|
|
221
|
+
// marker — a fork of another companion inherits that companion's earlier
|
|
222
|
+
// marker, and slicing at any marker would reveal its transcript.
|
|
223
|
+
const boundaryReviewId = (await brokerExtensionState(nodeId)).node.review_binding?.review_id;
|
|
221
224
|
// M3: name the boot session as the process-fatal disposal target immediately —
|
|
222
225
|
// enqueueRebind reassigns this on every later replacement, but without this
|
|
223
226
|
// initial assignment `activeSession` stays null (and disposeActiveSession() a
|
|
@@ -355,7 +358,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
355
358
|
const buildSnapshot = () => {
|
|
356
359
|
const liveSession = rebind.session();
|
|
357
360
|
return ({
|
|
358
|
-
messages: snapshotMessages(liveSession,
|
|
361
|
+
messages: snapshotMessages(liveSession, boundaryReviewId),
|
|
359
362
|
stats: liveSession.getSessionStats(),
|
|
360
363
|
// pi's `get_state` (rpc-mode.js), field for field, from the same getters.
|
|
361
364
|
// The ONE divergence: a model-less session (undefined OR the SDK
|
|
@@ -382,6 +385,14 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
382
385
|
},
|
|
383
386
|
toolGroupSummaries: toolGroups.summaries(),
|
|
384
387
|
workingActivity: projection.currentWorkingActivity(),
|
|
388
|
+
// The UNRUN queue's texts, read non-destructively (`clearQueue()` would
|
|
389
|
+
// consume them). Same two arrays a live `queue_update` carries, so a client
|
|
390
|
+
// that attaches mid-queue lands on the state its next `queue_update` would
|
|
391
|
+
// have produced.
|
|
392
|
+
queued: {
|
|
393
|
+
steering: [...liveSession.getSteeringMessages()],
|
|
394
|
+
followUp: [...liveSession.getFollowUpMessages()],
|
|
395
|
+
},
|
|
385
396
|
});
|
|
386
397
|
};
|
|
387
398
|
// Send a client its catch-up snapshot. welcome.pending_dialog carries a single
|
|
@@ -509,7 +520,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
509
520
|
rebind.setRebindSession();
|
|
510
521
|
await rebind.enqueueRebind(session, services);
|
|
511
522
|
const frameDispatch = createFrameDispatchContext({
|
|
512
|
-
nodeId, cfg, registry, toolGroups, projection, rebind,
|
|
523
|
+
nodeId, cfg, registry, toolGroups, projection, rebind, boundaryReviewId,
|
|
513
524
|
pendingDialogs, sendWelcome, replayExtraPendingDialogsTo, reWelcomeAll, disposeAndExit,
|
|
514
525
|
persistModelChoice, broadcastModelChanged, registryOf, formatModelSpec,
|
|
515
526
|
formatExactModelSpec, resolveLaunchModel,
|
|
@@ -673,13 +684,13 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
673
684
|
// conversation instead of just the post-yield window; single-cycle sessions and
|
|
674
685
|
// the fake-engine fixture (no tree accessors) fall through to plain
|
|
675
686
|
// `buildSessionContext().messages` unchanged.
|
|
676
|
-
export function snapshotMessages(session,
|
|
687
|
+
export function snapshotMessages(session, boundaryReviewId) {
|
|
677
688
|
// Cast: the real SDK's `SessionEntry.data` is `unknown` (untyped custom-entry
|
|
678
689
|
// payload), narrower than `CycleSessionManagerLike`'s structural read of the
|
|
679
690
|
// `crtr-cycle` marker's `{cycle, fromLeaf}` shape — the fields we read off it
|
|
680
691
|
// are ones ONLY this module's own `appendCustomEntry` call (above) ever writes.
|
|
681
692
|
const messages = cycleAwareMessages(session.sessionManager);
|
|
682
|
-
return visibleMessages(messages, {
|
|
693
|
+
return visibleMessages(messages, { boundaryReviewId });
|
|
683
694
|
}
|
|
684
695
|
// ---------------------------------------------------------------------------
|
|
685
696
|
// resolveLaunchModel — the SOLE launch model-selection policy, module-scoped
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Env marker set on every pi the front door boots. Its presence means we are
|
|
2
|
+
* already inside a front-door-booted root, so a nested front-door launch must
|
|
3
|
+
* be refused — otherwise a removed/renamed subcommand that a child pi re-runs
|
|
4
|
+
* (e.g. `crtr node -h`) fork-bombs pi until the machine must be rebooted. */
|
|
5
|
+
export declare const FRONT_DOOR_ENV = "CRTR_FRONT_DOOR";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The front-door recursion-guard env name, alone in a dependency-free module.
|
|
2
|
+
//
|
|
3
|
+
// `front-door.ts` owns the boot path and reaches the canvas through the
|
|
4
|
+
// dynamic imports inside `maybeBootRoot`; every module that only needs the
|
|
5
|
+
// MARKER NAME must not inherit that reach. The shipped broker extensions are
|
|
6
|
+
// gated on their static import graph, which cannot tell a lazily-imported boot
|
|
7
|
+
// dependency from an eagerly-loaded one, so importing the constant from
|
|
8
|
+
// `front-door.js` reads as an extension reaching canvas.db.
|
|
9
|
+
//
|
|
10
|
+
// `front-door.ts` re-exports this, so `FRONT_DOOR_ENV` stays importable from
|
|
11
|
+
// either module and every pi-booting path keeps setting the same marker.
|
|
12
|
+
/** Env marker set on every pi the front door boots. Its presence means we are
|
|
13
|
+
* already inside a front-door-booted root, so a nested front-door launch must
|
|
14
|
+
* be refused — otherwise a removed/renamed subcommand that a child pi re-runs
|
|
15
|
+
* (e.g. `crtr node -h`) fork-bombs pi until the machine must be rebooted. */
|
|
16
|
+
export const FRONT_DOOR_ENV = 'CRTR_FRONT_DOOR';
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* already inside a front-door-booted root, so a nested front-door launch must
|
|
3
|
-
* be refused — otherwise a removed/renamed subcommand that a child pi re-runs
|
|
4
|
-
* (e.g. `crtr node -h`) fork-bombs pi until the machine must be rebooted. */
|
|
5
|
-
export declare const FRONT_DOOR_ENV = "CRTR_FRONT_DOOR";
|
|
1
|
+
export { FRONT_DOOR_ENV } from './front-door-env.js';
|
|
6
2
|
/** If this invocation is a front-door (root) launch, boot it and never return.
|
|
7
3
|
* Returns false when it's a recognized subcommand / help / unknown token (let
|
|
8
4
|
* the dispatcher handle it — for unknown tokens it errors cleanly).
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { existsSync, statSync } from 'node:fs';
|
|
22
22
|
import { resolve as resolvePath } from 'node:path';
|
|
23
23
|
import { SUBTREE_NAMES } from '../../build-root.js';
|
|
24
|
+
import { FRONT_DOOR_ENV } from './front-door-env.js';
|
|
24
25
|
// NOTE: the boot-path deps — ./boot-root.js (bootRoot), ./resume-root.js (resumeRoot),
|
|
25
26
|
// ./package-health.js (missingPackageWarning + piAgentDir) — are DYNAMICALLY
|
|
26
27
|
// imported inside maybeBootRoot, NOT statically here. `cli.ts` calls
|
|
@@ -89,11 +90,7 @@ function parseRootArgs(tokens) {
|
|
|
89
90
|
const prompt = positionals.length > 0 ? positionals.join(' ') : undefined;
|
|
90
91
|
return { cwd, prompt, name, kind, profile, pickProfile, resume };
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
-
* already inside a front-door-booted root, so a nested front-door launch must
|
|
94
|
-
* be refused — otherwise a removed/renamed subcommand that a child pi re-runs
|
|
95
|
-
* (e.g. `crtr node -h`) fork-bombs pi until the machine must be rebooted. */
|
|
96
|
-
export const FRONT_DOOR_ENV = 'CRTR_FRONT_DOOR';
|
|
93
|
+
export { FRONT_DOOR_ENV } from './front-door-env.js';
|
|
97
94
|
/** If this invocation is a front-door (root) launch, boot it and never return.
|
|
98
95
|
* Returns false when it's a recognized subcommand / help / unknown token (let
|
|
99
96
|
* the dispatcher handle it — for unknown tokens it errors cleanly).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type NodeRow } from '../canvas/index.js';
|
|
2
|
-
import type { PiInvocation } from './
|
|
2
|
+
import type { PiInvocation } from './invocation.js';
|
|
3
3
|
/** View-side launch hints the broker host needs beyond the PiInvocation. The
|
|
4
4
|
* broker uses `cwd` + `resuming`; `name` is carried for the node's display. */
|
|
5
5
|
export interface LaunchPlacement {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The broker-launch recipe shape, alone in a dependency-free module.
|
|
2
|
+
//
|
|
3
|
+
// `launch.ts` builds a PiInvocation and therefore reaches canvas state; the
|
|
4
|
+
// modules that only need to READ the recipe shape (spawn-env, host) must not.
|
|
5
|
+
// A type-only import is erased at compile time, but the shipped broker
|
|
6
|
+
// extensions are gated on their STATIC import graph — the gate cannot tell an
|
|
7
|
+
// erased edge from a live one, so an extension that reaches spawn-env would
|
|
8
|
+
// read as reaching canvas.db through launch.ts. Keeping the interface here
|
|
9
|
+
// makes the graph say what the runtime already does.
|
|
10
|
+
export {};
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, readdirSync, } from 'node:fs';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { contextDir, reportsDir, getNode, subscriptionsOf, subscribersOf, } from '../canvas/index.js';
|
|
23
|
+
import { activeBackgroundBashJobs, bashJobCommandLine, formatBashElapsed } from '../bash-jobs.js';
|
|
23
24
|
import { readRoadmap, roadmapPath } from './roadmap.js';
|
|
24
25
|
import { buildWakeBearings } from './bearings.js';
|
|
25
26
|
import { personaDrift, commitPersonaAck } from './persona.js';
|
|
@@ -205,6 +206,33 @@ function feedBlock(nodeId, unreadDigest) {
|
|
|
205
206
|
}
|
|
206
207
|
return `<feed>\n${lines.join('\n')}\n</feed>`;
|
|
207
208
|
}
|
|
209
|
+
/** One-line label for a job: its purpose when the caller gave one, else its real
|
|
210
|
+
* command line, clipped so a long pipeline stays scannable. */
|
|
211
|
+
function jobLabel(purpose, command) {
|
|
212
|
+
const raw = (purpose ?? bashJobCommandLine(command)).trim() || '(no command)';
|
|
213
|
+
return raw.length > 120 ? `${raw.slice(0, 117)}…` : raw;
|
|
214
|
+
}
|
|
215
|
+
/** Live file-backed background bash jobs this node owns. A fresh revive has no
|
|
216
|
+
* memory of handing anything off, so without this block the node cannot tell a
|
|
217
|
+
* command it started is still running: it can neither read the log nor decide
|
|
218
|
+
* whether to wait, and may relaunch the same work. Returns null — and the
|
|
219
|
+
* kickoff omits the block entirely — when nothing is live. */
|
|
220
|
+
function backgroundJobsBlock(nodeId) {
|
|
221
|
+
const jobs = activeBackgroundBashJobs(contextDir(nodeId));
|
|
222
|
+
if (jobs.length === 0)
|
|
223
|
+
return null;
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
const one = jobs.length === 1;
|
|
226
|
+
const lines = [
|
|
227
|
+
`${jobs.length} background bash job${one ? '' : 's'} you started ${one ? 'is' : 'are'} STILL RUNNING, detached from any turn and unaffected by your refresh. ` +
|
|
228
|
+
`Each one sends you a message when it exits — that wake is automatic, so do not poll it and do not relaunch the same command.`,
|
|
229
|
+
];
|
|
230
|
+
for (const job of jobs) {
|
|
231
|
+
lines.push(` - ${job.jobId} — ${jobLabel(job.purpose, job.command)} · running ${formatBashElapsed(now - job.startedAtMs)} · log ${job.logPath}`);
|
|
232
|
+
}
|
|
233
|
+
lines.push('', 'Read output so far: `tail -n 50 <log>` (or `crtr node bash list --tail 20`). Abandon one: `crtr node bash kill <job-id>`.');
|
|
234
|
+
return `<background-jobs>\n${lines.join('\n')}\n</background-jobs>`;
|
|
235
|
+
}
|
|
208
236
|
// ---------------------------------------------------------------------------
|
|
209
237
|
// buildReviveKickoff — assemble the full fresh-revive first message.
|
|
210
238
|
// ---------------------------------------------------------------------------
|
|
@@ -257,6 +285,12 @@ export function buildReviveKickoff(meta, bearings, wakeReason) {
|
|
|
257
285
|
// revive (a new pi process boot) and re-injects buildContextIntro(), which
|
|
258
286
|
// already carries the sidecar as a hidden sibling of <crtr-bearings>; that seam
|
|
259
287
|
// covers this path, so duplicating it into the visible prompt would leak it.
|
|
288
|
+
// Live background bash jobs sit beside the context-dir listing: both describe
|
|
289
|
+
// this node's OWN in-flight state, and both precede <feed>, which is about the
|
|
290
|
+
// other nodes it awaits. Absent when nothing is running.
|
|
291
|
+
const jobsBlock = backgroundJobsBlock(nodeId);
|
|
292
|
+
if (jobsBlock !== null)
|
|
293
|
+
parts.push(jobsBlock);
|
|
260
294
|
parts.push(feedBlock(nodeId, bearings.unreadDigest));
|
|
261
295
|
parts.push(bearings.yieldMsg !== null
|
|
262
296
|
? `<yield-message>\n${bearings.yieldMsg.trim()}\n</yield-message>`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PiInvocation } from './invocation.js';
|
|
1
2
|
export { CANVAS_EXTENSIONS, CANVAS_STOPHOOK_PATH, CANVAS_INBOX_WATCHER_PATH, CANVAS_RECAP_PATH, CANVAS_GOAL_CAPTURE_PATH, CANVAS_PASSIVE_CONTEXT_PATH, CANVAS_CONTEXT_INTRO_PATH, CANVAS_TOOL_GUIDE_PATH, CANVAS_DOC_SUBSTRATE_PATH, CANVAS_STRUCTURED_OUTPUT_PATH, CANVAS_BASH_VALVE_PATH, } from './canvas-extensions.js';
|
|
2
3
|
import type { NodeMeta, LaunchSpec, Mode, Lifecycle } from '../canvas/index.js';
|
|
3
4
|
import type { ModelProvider, ModelStrength, ScopeConfig } from '../../types.js';
|
|
@@ -104,12 +105,7 @@ export declare function buildLaunchSpec(kind: string, mode: Mode, opts: {
|
|
|
104
105
|
launch: LaunchSpec;
|
|
105
106
|
lifecycle: 'terminal' | 'resident';
|
|
106
107
|
};
|
|
107
|
-
export
|
|
108
|
-
/** argv after the `pi` binary. */
|
|
109
|
-
argv: string[];
|
|
110
|
-
/** env to merge into the process. */
|
|
111
|
-
env: Record<string, string>;
|
|
112
|
-
}
|
|
108
|
+
export type { PiInvocation } from './invocation.js';
|
|
113
109
|
/** Construct the pi invocation for a node.
|
|
114
110
|
* - fresh start: pass `prompt` (the node's first user message), no resume.
|
|
115
111
|
* - fork start: pass `forkFrom` (absolute .jsonl path or partial uuid) to `--fork`
|
|
@@ -152,7 +152,7 @@ export async function readNodeSnapshot(nodeId) {
|
|
|
152
152
|
copyFileSync(sessionFile, copy);
|
|
153
153
|
const manager = SessionManager.open(copy);
|
|
154
154
|
const context = manager.buildSessionContext();
|
|
155
|
-
const messages = visibleMessages(cycleAwareMessages(manager), {
|
|
155
|
+
const messages = visibleMessages(cycleAwareMessages(manager), { boundaryReviewId: node.review_binding?.review_id });
|
|
156
156
|
const sessionId = manager.getSessionId();
|
|
157
157
|
const sessionName = manager.getSessionName();
|
|
158
158
|
const snapshot = {
|
|
@@ -179,6 +179,9 @@ export async function readNodeSnapshot(nodeId) {
|
|
|
179
179
|
// process state (last setStatus/setWidget/setTitle) and is not persisted,
|
|
180
180
|
// so an offline read has none.
|
|
181
181
|
display: { statuses: {}, widgets: {}, title: undefined },
|
|
182
|
+
// The unrun queue lives in the engine's memory; a dormant node has no
|
|
183
|
+
// engine, so nothing is waiting to run.
|
|
184
|
+
queued: { steering: [], followUp: [] },
|
|
182
185
|
// Sidecar summaries survive broker exit so a dormant transcript receives
|
|
183
186
|
// the same completed-group data as a live welcome snapshot. Malformed or
|
|
184
187
|
// absent sidecars are deliberately ignored: tool calls remain visible.
|
|
@@ -19,6 +19,7 @@ import { memoryDir } from './memory.js';
|
|
|
19
19
|
import { resolveInstallId } from '../canvas/install-id.js';
|
|
20
20
|
import { canvasDbPath } from '../canvas/paths.js';
|
|
21
21
|
import { usage } from '../errors.js';
|
|
22
|
+
import { assertProfileAvailableForBirth } from '../profiles/deletion-reservation.js';
|
|
22
23
|
// crtrd sets this once at boot (setInstallId) and it wins unconditionally —
|
|
23
24
|
// a daemon process only ever serves one canvas home for its whole lifetime,
|
|
24
25
|
// so no keying is needed there. Every OTHER in-process caller (tests,
|
|
@@ -259,6 +260,7 @@ export function spawnNode(opts) {
|
|
|
259
260
|
if (parent !== null && getNode(parent) === null) {
|
|
260
261
|
throw new Error(`cannot spawn under unknown parent node: ${parent}`);
|
|
261
262
|
}
|
|
263
|
+
assertProfileAvailableForBirth(meta.profile_id ?? null);
|
|
262
264
|
createNode(meta);
|
|
263
265
|
// Create the node-local memory directory so substrate docs can be written
|
|
264
266
|
// directly into it without a separate mkdir. Skip for ephemeral human-bridge
|
|
@@ -19,6 +19,7 @@ export declare function reshapeNode(nodeId: string, opts?: {
|
|
|
19
19
|
model?: string;
|
|
20
20
|
mode?: Mode;
|
|
21
21
|
lifecycle?: Lifecycle;
|
|
22
|
+
profileId?: string | null;
|
|
22
23
|
}, beforePersist?: () => void): NodeMeta;
|
|
23
24
|
/** Promote a node to an orchestrator (mode→orchestrator), optionally
|
|
24
25
|
* specializing its kind (e.g. a `general` worker becoming a
|
|
@@ -42,6 +42,7 @@ export function reshapeNode(nodeId, opts = {}, beforePersist) {
|
|
|
42
42
|
const targetModel = opts.model ?? node.model_override ?? undefined;
|
|
43
43
|
const targetMode = opts.mode ?? node.mode;
|
|
44
44
|
const targetLifecycle = opts.lifecycle ?? node.lifecycle;
|
|
45
|
+
const targetProfileId = opts.profileId === undefined ? node.profile_id : opts.profileId;
|
|
45
46
|
// Rewrite the launch spec to the target kind/mode's persona so the *next*
|
|
46
47
|
// revive comes back as this exact shape (polymorph stage 2). nodeEnv reads
|
|
47
48
|
// meta.{kind,mode}, so CRTR_KIND/CRTR_MODE flip immediately for the live
|
|
@@ -66,7 +67,7 @@ export function reshapeNode(nodeId, opts = {}, beforePersist) {
|
|
|
66
67
|
// Resolve kind/ladder config against the NODE's own cwd + profile, not
|
|
67
68
|
// this process's ambient scope.
|
|
68
69
|
cwd: node.cwd,
|
|
69
|
-
profileId:
|
|
70
|
+
profileId: targetProfileId,
|
|
70
71
|
});
|
|
71
72
|
// Promotion seeds after its target launch has been built but before the
|
|
72
73
|
// mutation is persisted, preserving the original promote() failure order.
|
|
@@ -81,6 +82,7 @@ export function reshapeNode(nodeId, opts = {}, beforePersist) {
|
|
|
81
82
|
kind: targetKind,
|
|
82
83
|
mode: targetMode,
|
|
83
84
|
lifecycle: targetLifecycle,
|
|
85
|
+
profile_id: targetProfileId,
|
|
84
86
|
launch,
|
|
85
87
|
...(carriesDefaultHandle ? { name: targetKind } : {}),
|
|
86
88
|
// Persist a newly-chosen tier so it is durable across future revives; omit
|
|
@@ -20,6 +20,12 @@ export interface CycleSessionManagerLike {
|
|
|
20
20
|
* path root-first. */
|
|
21
21
|
getBranch?(fromId?: string): SessionEntry[];
|
|
22
22
|
}
|
|
23
|
+
/** True when an assistant turn ended because its own abort fired — an ESC, a
|
|
24
|
+
* critical-tier interrupt, or the self-abort a refresh-yield performs. */
|
|
25
|
+
export declare function endedByAbort(message: {
|
|
26
|
+
stopReason?: string;
|
|
27
|
+
errorMessage?: string;
|
|
28
|
+
}): boolean;
|
|
23
29
|
/** A refresh-yield deliberately aborts its own turn before the next cycle is
|
|
24
30
|
* rooted. Preserve that assistant message (including tool calls and prose), but
|
|
25
31
|
* omit the failure annotation: the cycle divider is the truthful visible seam. */
|
|
@@ -32,11 +32,25 @@ function cycleDivider(cycle, timestamp) {
|
|
|
32
32
|
timestamp: new Date(timestamp).getTime(),
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/** Text a provider attaches when the request's own AbortSignal fired. Pi's
|
|
36
|
+
* providers stamp `stopReason: 'aborted'` when they observe the signal
|
|
37
|
+
* themselves, but an abort that lands while the next request is starting
|
|
38
|
+
* reaches their catch as a bare AbortError instead, and is recorded as
|
|
39
|
+
* `stopReason: 'error'` carrying this text. Both spellings are the same healthy
|
|
40
|
+
* event — a turn that stopped because something asked it to stop. */
|
|
41
|
+
const ABORT_ERROR_TEXT = /\b(?:request|operation) was aborted\b|\bthe user aborted\b/i;
|
|
42
|
+
/** True when an assistant turn ended because its own abort fired — an ESC, a
|
|
43
|
+
* critical-tier interrupt, or the self-abort a refresh-yield performs. */
|
|
44
|
+
export function endedByAbort(message) {
|
|
45
|
+
if (message.stopReason === 'aborted')
|
|
46
|
+
return true;
|
|
47
|
+
return message.stopReason === 'error' && ABORT_ERROR_TEXT.test(message.errorMessage ?? '');
|
|
48
|
+
}
|
|
35
49
|
/** A refresh-yield deliberately aborts its own turn before the next cycle is
|
|
36
50
|
* rooted. Preserve that assistant message (including tool calls and prose), but
|
|
37
51
|
* omit the failure annotation: the cycle divider is the truthful visible seam. */
|
|
38
52
|
export function withoutYieldAbort(message) {
|
|
39
|
-
if (message.role !== 'assistant' || message
|
|
53
|
+
if (message.role !== 'assistant' || !endedByAbort(message))
|
|
40
54
|
return message;
|
|
41
55
|
const { stopReason: _stopReason, errorMessage: _errorMessage, ...visible } = message;
|
|
42
56
|
return visible;
|
|
@@ -12,18 +12,23 @@ export declare function isReviewBoundary(entry: unknown, reviewId?: string): boo
|
|
|
12
12
|
/**
|
|
13
13
|
* Project snapshot messages to the visible window for a review companion.
|
|
14
14
|
*
|
|
15
|
+
* A companion passes its binding's review id so the slice starts at ITS OWN
|
|
16
|
+
* boundary marker: a node forked from another companion inherits that
|
|
17
|
+
* companion's earlier marker, and slicing at the first marker of any review
|
|
18
|
+
* would reveal the origin companion's transcript.
|
|
19
|
+
*
|
|
15
20
|
* Contract (in evaluation order):
|
|
16
|
-
* 1.
|
|
17
|
-
*
|
|
18
|
-
* the person.
|
|
19
|
-
* 2. Not a
|
|
20
|
-
* nodes are not filtered.
|
|
21
|
-
* 3.
|
|
21
|
+
* 1. This binding's marker present (any marker when the node has no binding) →
|
|
22
|
+
* slice from the **first** occurrence (marker included) to the end.
|
|
23
|
+
* Everything before the marker is inherited context, hidden from the person.
|
|
24
|
+
* 2. Not a companion (`boundaryReviewId` undefined) → return input unchanged
|
|
25
|
+
* (same reference). Ordinary nodes are not filtered.
|
|
26
|
+
* 3. Bound but no matching marker → a leading compaction summary is replaced
|
|
22
27
|
* with a synthetic boundary while its retained tail remains visible. Every
|
|
23
28
|
* other markerless shape fails closed with the synthetic boundary alone.
|
|
24
29
|
*/
|
|
25
30
|
export declare function visibleMessages(messages: BrokerSnapshot['messages'], opts: {
|
|
26
|
-
|
|
31
|
+
boundaryReviewId: string | undefined;
|
|
27
32
|
}): BrokerSnapshot['messages'];
|
|
28
33
|
/**
|
|
29
34
|
* Project a session tree to the review companion's visible transcript boundary.
|
|
@@ -33,5 +38,9 @@ export declare function visibleMessages(messages: BrokerSnapshot['messages'], op
|
|
|
33
38
|
* boundary is crossed, its complete descendant subtree remains navigable. An
|
|
34
39
|
* absent boundary produces an empty tree, so picker previews never disclose an
|
|
35
40
|
* inherited entry before a mutation guard can reject its selection.
|
|
41
|
+
*
|
|
42
|
+
* `boundaryReviewId` selects only this binding's marker as a root, so a
|
|
43
|
+
* companion forked from another companion never navigates into the inherited
|
|
44
|
+
* subtree rooted at the origin's earlier marker.
|
|
36
45
|
*/
|
|
37
|
-
export declare function boundaryRootedTree(tree: readonly WireSessionTreeNode[]): WireSessionTreeNode[];
|
|
46
|
+
export declare function boundaryRootedTree(tree: readonly WireSessionTreeNode[], boundaryReviewId?: string): WireSessionTreeNode[];
|
|
@@ -38,21 +38,26 @@ function syntheticBoundary(timestamp) {
|
|
|
38
38
|
/**
|
|
39
39
|
* Project snapshot messages to the visible window for a review companion.
|
|
40
40
|
*
|
|
41
|
+
* A companion passes its binding's review id so the slice starts at ITS OWN
|
|
42
|
+
* boundary marker: a node forked from another companion inherits that
|
|
43
|
+
* companion's earlier marker, and slicing at the first marker of any review
|
|
44
|
+
* would reveal the origin companion's transcript.
|
|
45
|
+
*
|
|
41
46
|
* Contract (in evaluation order):
|
|
42
|
-
* 1.
|
|
43
|
-
*
|
|
44
|
-
* the person.
|
|
45
|
-
* 2. Not a
|
|
46
|
-
* nodes are not filtered.
|
|
47
|
-
* 3.
|
|
47
|
+
* 1. This binding's marker present (any marker when the node has no binding) →
|
|
48
|
+
* slice from the **first** occurrence (marker included) to the end.
|
|
49
|
+
* Everything before the marker is inherited context, hidden from the person.
|
|
50
|
+
* 2. Not a companion (`boundaryReviewId` undefined) → return input unchanged
|
|
51
|
+
* (same reference). Ordinary nodes are not filtered.
|
|
52
|
+
* 3. Bound but no matching marker → a leading compaction summary is replaced
|
|
48
53
|
* with a synthetic boundary while its retained tail remains visible. Every
|
|
49
54
|
* other markerless shape fails closed with the synthetic boundary alone.
|
|
50
55
|
*/
|
|
51
56
|
export function visibleMessages(messages, opts) {
|
|
52
|
-
const markerIndex = messages.findIndex((message) => isReviewBoundary(message));
|
|
57
|
+
const markerIndex = messages.findIndex((message) => isReviewBoundary(message, opts.boundaryReviewId));
|
|
53
58
|
if (markerIndex !== -1)
|
|
54
59
|
return messages.slice(markerIndex);
|
|
55
|
-
if (
|
|
60
|
+
if (opts.boundaryReviewId === undefined)
|
|
56
61
|
return messages;
|
|
57
62
|
const [first, ...retainedTail] = messages;
|
|
58
63
|
if (first?.role === 'compactionSummary') {
|
|
@@ -68,14 +73,18 @@ export function visibleMessages(messages, opts) {
|
|
|
68
73
|
* boundary is crossed, its complete descendant subtree remains navigable. An
|
|
69
74
|
* absent boundary produces an empty tree, so picker previews never disclose an
|
|
70
75
|
* inherited entry before a mutation guard can reject its selection.
|
|
76
|
+
*
|
|
77
|
+
* `boundaryReviewId` selects only this binding's marker as a root, so a
|
|
78
|
+
* companion forked from another companion never navigates into the inherited
|
|
79
|
+
* subtree rooted at the origin's earlier marker.
|
|
71
80
|
*/
|
|
72
|
-
export function boundaryRootedTree(tree) {
|
|
81
|
+
export function boundaryRootedTree(tree, boundaryReviewId) {
|
|
73
82
|
const retainSubtree = (node) => ({
|
|
74
83
|
...node,
|
|
75
84
|
children: node.children.map(retainSubtree),
|
|
76
85
|
});
|
|
77
86
|
const project = (node) => {
|
|
78
|
-
if (isReviewBoundary(node.entry))
|
|
87
|
+
if (isReviewBoundary(node.entry, boundaryReviewId))
|
|
79
88
|
return [retainSubtree(node)];
|
|
80
89
|
return node.children.flatMap(project);
|
|
81
90
|
};
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type { PiInvocation } from './
|
|
1
|
+
import type { PiInvocation } from './invocation.js';
|
|
2
|
+
/** The unshimmed PATH admitted from the host. Broker and cron shells resolve
|
|
3
|
+
* crouter-owned executables from this value, while commands those shells run
|
|
4
|
+
* still receive the shim-prepended PATH. */
|
|
5
|
+
export declare const ADMITTED_HOST_PATH_ENV = "CRTR_ADMITTED_HOST_PATH";
|
|
6
|
+
/** Resolve a regular executable against a shell PATH without executing it.
|
|
7
|
+
* The result is absolute, or null when no PATH entry supplies the command. */
|
|
8
|
+
export declare function resolvePathExecutable(name: string, env: NodeJS.ProcessEnv, cwd: string): string | null;
|
|
9
|
+
export declare function resolveAdmittedHostCommand(name: string, env: NodeJS.ProcessEnv, cwd: string): string;
|
|
2
10
|
/** Sources A–C: the operational base shared by the broker boundary (this
|
|
3
11
|
* module's `buildBrokerEnv`) and cron executor — the single shared definition
|
|
4
12
|
* of "non-secret host state the runtime exposes to any spawned shell." Does not include
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
// `spawnEnv.allow` entry. `inv.env` (D) is crtr-constructed and trusted, so
|
|
16
16
|
// it overlays unconditionally, layered after E.
|
|
17
17
|
//
|
|
18
|
+
// One name IS rewritten rather than merely admitted: `PATH`. Bare-binary
|
|
19
|
+
// contributions (`bin` in a plugin manifest or a scope `config.json`) are
|
|
20
|
+
// materialized into a crouter-owned shim directory by
|
|
21
|
+
// `core/runtime/bin-contributions.ts`, and that directory is PREPENDED here so
|
|
22
|
+
// a declared bare command wins its name in every node's bash. The admitted
|
|
23
|
+
// host PATH stays intact behind it.
|
|
24
|
+
//
|
|
18
25
|
// `GIT_*` is deliberately absent from the allowlist: the whole namespace is an
|
|
19
26
|
// auth/config-injection surface (`GIT_CONFIG_KEY_*`/`GIT_CONFIG_VALUE_*`,
|
|
20
27
|
// `GIT_ASKPASS`, `GIT_SSH_COMMAND`), not a non-secret operational family, and
|
|
@@ -22,9 +29,12 @@
|
|
|
22
29
|
// `~/.gitconfig`, reached through the allowlisted `HOME`; SSH remotes via the
|
|
23
30
|
// allowlisted `SSH_AUTH_SOCK`). A deliberate `GIT_*` need goes through
|
|
24
31
|
// `spawnEnv.allow`.
|
|
32
|
+
import { statSync } from 'node:fs';
|
|
33
|
+
import { delimiter, resolve } from 'node:path';
|
|
25
34
|
import { readMergedLaunchConfig } from '../config.js';
|
|
26
35
|
import { readProfileEnvVars } from '../profiles/env-store.js';
|
|
27
|
-
import {
|
|
36
|
+
import { binShimDirFor } from './bin-contributions.js';
|
|
37
|
+
import { FRONT_DOOR_ENV } from './front-door-env.js';
|
|
28
38
|
/** A1 — literal env-var names copied verbatim (no value inspection): a login
|
|
29
39
|
* shell plus dev toolchain needs exactly these, and none of them can carry a
|
|
30
40
|
* credential. */
|
|
@@ -54,6 +64,39 @@ const OPERATIONAL_LITERAL_NAMES = new Set([
|
|
|
54
64
|
* file-backed in `auth.json`, never an env value). `GIT_` is NOT here — see
|
|
55
65
|
* module comment. */
|
|
56
66
|
const OPERATIONAL_PREFIXES = ['LC_', 'XDG_', 'PI_'];
|
|
67
|
+
/** The unshimmed PATH admitted from the host. Broker and cron shells resolve
|
|
68
|
+
* crouter-owned executables from this value, while commands those shells run
|
|
69
|
+
* still receive the shim-prepended PATH. */
|
|
70
|
+
export const ADMITTED_HOST_PATH_ENV = 'CRTR_ADMITTED_HOST_PATH';
|
|
71
|
+
/** Resolve a regular executable against a shell PATH without executing it.
|
|
72
|
+
* The result is absolute, or null when no PATH entry supplies the command. */
|
|
73
|
+
export function resolvePathExecutable(name, env, cwd) {
|
|
74
|
+
const pathValue = env['PATH'];
|
|
75
|
+
if (pathValue === undefined || pathValue === '')
|
|
76
|
+
return null;
|
|
77
|
+
for (const directory of pathValue.split(delimiter)) {
|
|
78
|
+
const candidate = resolve(cwd, directory === '' ? '.' : directory, name);
|
|
79
|
+
try {
|
|
80
|
+
const stat = statSync(candidate);
|
|
81
|
+
if (stat.isFile() && (stat.mode & 0o111) !== 0)
|
|
82
|
+
return candidate;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Keep searching exactly as PATH resolution does.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
export function resolveAdmittedHostCommand(name, env, cwd) {
|
|
91
|
+
const pathValue = env[ADMITTED_HOST_PATH_ENV] ?? env['PATH'];
|
|
92
|
+
if (pathValue === undefined || pathValue === '') {
|
|
93
|
+
throw new Error(`cannot resolve crouter-owned ${name}: the admitted host PATH is empty`);
|
|
94
|
+
}
|
|
95
|
+
const command = resolvePathExecutable(name, { PATH: pathValue }, cwd);
|
|
96
|
+
if (command !== null)
|
|
97
|
+
return command;
|
|
98
|
+
throw new Error(`cannot resolve crouter-owned ${name} from the admitted host PATH`);
|
|
99
|
+
}
|
|
57
100
|
/** A2 — proxy var names. Admitted only when the value's URL authority carries
|
|
58
101
|
* no userinfo component (no `user@` / `user:pass@`) — the one value-inspected
|
|
59
102
|
* entry in the whole builder, because a proxy var is the single operational
|
|
@@ -151,6 +194,21 @@ export function buildOperationalEnvBase(opts) {
|
|
|
151
194
|
out[key] = value;
|
|
152
195
|
}
|
|
153
196
|
}
|
|
197
|
+
// Preserve the unshimmed admitted host PATH across broker and cron children.
|
|
198
|
+
// A nested `crtr` launched from an agent inherits this constructed value,
|
|
199
|
+
// rather than mistaking its shim-prepended PATH for the host path.
|
|
200
|
+
const admittedHostPath = host[ADMITTED_HOST_PATH_ENV] ?? out['PATH'];
|
|
201
|
+
if (admittedHostPath !== undefined && admittedHostPath !== '')
|
|
202
|
+
out[ADMITTED_HOST_PATH_ENV] = admittedHostPath;
|
|
203
|
+
// Bare-binary contributions (`core/runtime/bin-contributions.ts`): the shim
|
|
204
|
+
// dir holding one symlink per declared bare command, PREPENDED so a
|
|
205
|
+
// contribution wins its name, with the host PATH admitted above left intact
|
|
206
|
+
// behind it. Resolved from the TARGET's own cwd/profile like source C.
|
|
207
|
+
const shimDir = binShimDirFor(opts.targetCwd, opts.targetProfileId);
|
|
208
|
+
if (shimDir !== null) {
|
|
209
|
+
const hostPath = out['PATH'];
|
|
210
|
+
out['PATH'] = hostPath === undefined || hostPath === '' ? shimDir : `${shimDir}:${hostPath}`;
|
|
211
|
+
}
|
|
154
212
|
return out;
|
|
155
213
|
}
|
|
156
214
|
/** The one authoritative broker child env: sources A–C (the operational
|
|
@@ -170,10 +228,12 @@ export function buildOperationalEnvBase(opts) {
|
|
|
170
228
|
export function buildBrokerEnv(inv) {
|
|
171
229
|
const targetCwd = inv.env['CRTR_NODE_CWD'] ?? process.cwd();
|
|
172
230
|
const targetProfileId = inv.env['CRTR_PROFILE_ID'] || null;
|
|
231
|
+
const operational = buildOperationalEnvBase({ targetCwd, targetProfileId });
|
|
173
232
|
return {
|
|
174
|
-
...
|
|
233
|
+
...operational,
|
|
175
234
|
...readProfileEnvVars(targetProfileId),
|
|
176
235
|
...inv.env,
|
|
236
|
+
[ADMITTED_HOST_PATH_ENV]: operational[ADMITTED_HOST_PATH_ENV],
|
|
177
237
|
[FRONT_DOOR_ENV]: '1',
|
|
178
238
|
};
|
|
179
239
|
}
|
|
@@ -2,8 +2,8 @@ export { STALL_REPROMPT } from '../../shared/generated-context.js';
|
|
|
2
2
|
export interface StopSignals {
|
|
3
3
|
/** Did the node call `push --final` (finish) this turn? */
|
|
4
4
|
pushedFinal: boolean;
|
|
5
|
-
/** Did the node call `crtr ask` (escalate to the
|
|
6
|
-
|
|
5
|
+
/** Did the node call `crtr ask` (escalate to the user) this turn? */
|
|
6
|
+
requestedHumanReply: boolean;
|
|
7
7
|
}
|
|
8
8
|
export type StopAction = {
|
|
9
9
|
action: 'allow';
|
|
@@ -37,7 +37,7 @@ function formatInvalidStructuredOutputReprompt(error) {
|
|
|
37
37
|
`1. Remove the invalid file (the output-schema.json in your node directory has been corrupted)\n` +
|
|
38
38
|
`2. Fix the corruption if you know the original request schema\n\n` +
|
|
39
39
|
`After resolving the file, retry the submission. ` +
|
|
40
|
-
`If this is unexpected, escalate to the
|
|
40
|
+
`If this is unexpected, escalate to the user using \`crtr human send\`.`);
|
|
41
41
|
}
|
|
42
42
|
/** Decide what to do when a node stops. crtrd supplies every current-canvas
|
|
43
43
|
* fact; the broker contributes only whether its Pi-owned background job registry
|
|
@@ -45,7 +45,7 @@ function formatInvalidStructuredOutputReprompt(error) {
|
|
|
45
45
|
export function evaluateStop(nodeId, signals, backgroundJobsRunning = activeBackgroundBashJobs(contextDir(nodeId)).length > 0) {
|
|
46
46
|
if (signals.pushedFinal)
|
|
47
47
|
return { action: 'allow', reason: 'finished' };
|
|
48
|
-
if (signals.
|
|
48
|
+
if (signals.requestedHumanReply)
|
|
49
49
|
return { action: 'allow', reason: 'escalated' };
|
|
50
50
|
// A pending structured-output request overrides every stored-state dormancy
|
|
51
51
|
// path: a post-final one-off follow-up still owes `submit` before it may stop.
|