@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
|
@@ -8,12 +8,13 @@ import { tmpdir } from 'node:os';
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { createApiServer } from '../../daemon/api/server.js';
|
|
10
10
|
import { cancelHumanTicket, deliverTerminalResult, resolvePageTicket } from '../../daemon/human/finish.js';
|
|
11
|
-
import {
|
|
11
|
+
import { humanSend } from '../../commands/human/prompts.js';
|
|
12
12
|
import { getNode, listNodes, closeDb } from '../canvas/index.js';
|
|
13
13
|
import { readInboxSince } from '../feed/inbox.js';
|
|
14
14
|
import { resolveRef } from '../canvas/history.js';
|
|
15
15
|
import { apiSocketPath } from '../canvas/paths.js';
|
|
16
16
|
import { spawnNode } from '../runtime/nodes.js';
|
|
17
|
+
import { bindTicketReplyRoute } from '../human/convention.js';
|
|
17
18
|
import { ticketDir } from '../human/root.js';
|
|
18
19
|
import { readTicketResult, submitPage } from '../human/tickets.js';
|
|
19
20
|
let home;
|
|
@@ -39,16 +40,17 @@ async function withMutedStdout(fn) {
|
|
|
39
40
|
}
|
|
40
41
|
function bridgeUnder() {
|
|
41
42
|
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
42
|
-
const bridge = spawnNode({ kind: 'human', parent, cwd: home, name: 'human-
|
|
43
|
+
const bridge = spawnNode({ kind: 'human', parent, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
43
44
|
return { parent, bridge };
|
|
44
45
|
}
|
|
45
46
|
function submitBridgePage(bridge) {
|
|
46
47
|
const dir = ticketDir(bridge);
|
|
47
48
|
const sourceDir = mkdtempSync(join(tmpdir(), 'crtr-deliver-page-'));
|
|
48
|
-
const sourceFile = join(sourceDir, 'page.
|
|
49
|
-
writeFileSync(sourceFile, '
|
|
49
|
+
const sourceFile = join(sourceDir, 'page.tsx');
|
|
50
|
+
writeFileSync(sourceFile, 'export default () => (\n <Page title="Ship it?" subtitle="Decide whether to ship now so the release can proceed.">\n <UserText id="question" initialText="" />\n </Page>\n);\n');
|
|
50
51
|
try {
|
|
51
|
-
|
|
52
|
+
bindTicketReplyRoute(dir, bridge);
|
|
53
|
+
submitPage({ dir, sourceFile, delivery: { placement: 'inline', inbox: true, reply: true } });
|
|
52
54
|
}
|
|
53
55
|
finally {
|
|
54
56
|
rmSync(sourceDir, { recursive: true, force: true });
|
|
@@ -87,40 +89,41 @@ after(() => {
|
|
|
87
89
|
delete process.env['CRTR_NODE_ID'];
|
|
88
90
|
});
|
|
89
91
|
describe('enqueue is producer-only', () => {
|
|
90
|
-
test('
|
|
92
|
+
test('send preserves title and subtitle from the authored page', async () => {
|
|
91
93
|
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
92
94
|
process.env['CRTR_NODE_ID'] = parent;
|
|
93
|
-
const page = join(home, 'release-decision.
|
|
94
|
-
writeFileSync(page, '
|
|
95
|
-
await assert.rejects(() =>
|
|
96
|
-
await assert.rejects(() =>
|
|
97
|
-
const result = (await
|
|
98
|
-
assert.equal(typeof result['
|
|
95
|
+
const page = join(home, 'release-decision.tsx');
|
|
96
|
+
writeFileSync(page, 'export default () => (\n <Page title="Release decision" subtitle="Decide whether to release now so the launch can proceed.">\n <p>This first line stays page content.</p>\n <UserText id="question" initialText="" />\n </Page>\n);\n');
|
|
97
|
+
await assert.rejects(() => humanSend.run({}), /a page source is required/);
|
|
98
|
+
await assert.rejects(() => humanSend.run({ page, document: 'export default () => <Page title="Dup" />;' }), /received both --page and a piped page document/);
|
|
99
|
+
const result = (await humanSend.run({ page, document: '' }));
|
|
100
|
+
assert.equal(typeof result['page_id'], 'string');
|
|
101
|
+
assert.equal(result['reply'], true);
|
|
99
102
|
assert.equal(typeof result['dir'], 'string');
|
|
100
103
|
for (const forbidden of ['pane_id', 'render_pane_id', 'pid', 'worker_pid'])
|
|
101
|
-
assert.ok(!(forbidden in result), `
|
|
104
|
+
assert.ok(!(forbidden in result), `send result must not expose ${forbidden}`);
|
|
102
105
|
const manifest = JSON.parse(readFileSync(join(result['dir'], 'page.json'), 'utf8'));
|
|
103
106
|
assert.equal(manifest.title, 'Release decision');
|
|
104
107
|
assert.equal(manifest.subtitle, 'Decide whether to release now so the launch can proceed.');
|
|
105
|
-
const document = readFileSync(join(result['dir'], 'page.
|
|
108
|
+
const document = readFileSync(join(result['dir'], 'page.tsx'), 'utf8');
|
|
106
109
|
assert.ok(document.includes('This first line stays page content.'));
|
|
107
110
|
assert.equal(manifest.slots.filter((slot) => slot.kind === 'text').length, 1);
|
|
108
111
|
});
|
|
109
|
-
test('
|
|
112
|
+
test('send materializes a piped page before validation and publication', async () => {
|
|
110
113
|
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
111
114
|
process.env['CRTR_NODE_ID'] = parent;
|
|
112
|
-
const document = '
|
|
113
|
-
const result = (await
|
|
115
|
+
const document = 'export default () => (\n <Page title="Piped question" subtitle="Answer the question to unblock this task.">\n <UserText id="question" initialText="" />\n </Page>\n);\n';
|
|
116
|
+
const result = (await humanSend.run({ document }));
|
|
114
117
|
const manifest = JSON.parse(readFileSync(join(result['dir'], 'page.json'), 'utf8'));
|
|
115
118
|
assert.equal(manifest.title, 'Piped question');
|
|
116
|
-
assert.equal(readFileSync(join(result['dir'], 'page.
|
|
119
|
+
assert.equal(readFileSync(join(result['dir'], 'page.tsx'), 'utf8'), document);
|
|
117
120
|
});
|
|
118
121
|
});
|
|
119
122
|
describe('terminal ticket delivery', () => {
|
|
120
123
|
test('resolvePageTicket pushFinals the readable answer once and wakes the subscribed parent', async () => {
|
|
121
124
|
const { parent, bridge } = bridgeUnder();
|
|
122
125
|
const dir = submitBridgePage(bridge);
|
|
123
|
-
const result = await resolvePageTicket(bridge, { question: { text: 'yes, ship', edited: true
|
|
126
|
+
const result = await resolvePageTicket(bridge, { question: { text: 'yes, ship', edited: true } });
|
|
124
127
|
assert.equal(result.kind, 'page');
|
|
125
128
|
closeDb();
|
|
126
129
|
assert.equal(getNode(bridge)?.status, 'done');
|
|
@@ -133,7 +136,7 @@ describe('terminal ticket delivery', () => {
|
|
|
133
136
|
test('a replay against a terminal bridge is a no-op', async () => {
|
|
134
137
|
const { parent, bridge } = bridgeUnder();
|
|
135
138
|
submitBridgePage(bridge);
|
|
136
|
-
await resolvePageTicket(bridge, { question: { text: 'yes', edited: true
|
|
139
|
+
await resolvePageTicket(bridge, { question: { text: 'yes', edited: true } });
|
|
137
140
|
assert.equal(await deliverTerminalResult(bridge), true);
|
|
138
141
|
closeDb();
|
|
139
142
|
const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
|
|
@@ -141,7 +144,7 @@ describe('terminal ticket delivery', () => {
|
|
|
141
144
|
});
|
|
142
145
|
test('cancelHumanTicket finishes the bridge and notifies non-actor subscribers', async () => {
|
|
143
146
|
const actor = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'actor' }).node_id;
|
|
144
|
-
const bridge = spawnNode({ kind: 'human', parent: actor, cwd: home, name: 'human-
|
|
147
|
+
const bridge = spawnNode({ kind: 'human', parent: actor, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
145
148
|
const other = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'other' }).node_id;
|
|
146
149
|
const { subscribe } = await import('../canvas/canvas.js');
|
|
147
150
|
subscribe(other, bridge, true);
|
|
@@ -156,12 +159,14 @@ describe('terminal ticket delivery', () => {
|
|
|
156
159
|
assert.match(notes[0].label, /canceled/);
|
|
157
160
|
});
|
|
158
161
|
});
|
|
159
|
-
test('
|
|
160
|
-
const page = join(home, 'build-done.
|
|
161
|
-
writeFileSync(page, '
|
|
162
|
+
test('a display-only page publishes passively and creates no canvas node', async () => {
|
|
163
|
+
const page = join(home, 'build-done.tsx');
|
|
164
|
+
writeFileSync(page, 'export default () => (\n <Page title="Build done" subtitle="The build passed, so the change is ready to review.">\n <p>Build output is ready.</p>\n </Page>\n);\n');
|
|
162
165
|
const before = listNodes().length;
|
|
163
|
-
const result = (await
|
|
164
|
-
assert.equal(result['
|
|
166
|
+
const result = (await humanSend.run({ page }));
|
|
167
|
+
assert.equal(result['reply'], false);
|
|
168
|
+
assert.equal(result['inbox'], false);
|
|
169
|
+
assert.equal(typeof result['page_id'], 'string');
|
|
165
170
|
assert.equal(typeof result['dir'], 'string');
|
|
166
171
|
assert.ok(existsSync(join(result['dir'], 'page.json')));
|
|
167
172
|
assert.ok(readTicketResult(result['dir']) === null);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Run with: node --import tsx/esm --test src/core/__tests__/human-node-not-supervised.test.ts
|
|
2
2
|
//
|
|
3
3
|
// BUG REGRESSION (issue #2 — "Headless node can't reach human-ask: human-kind
|
|
4
|
-
// TUI dies at boot"): a headless orchestrator's every `crtr human
|
|
4
|
+
// TUI dies at boot"): a headless orchestrator's every reply-bearing `crtr human send` bridge died
|
|
5
5
|
// at boot with "pi vehicle exited before the session came up".
|
|
6
6
|
//
|
|
7
7
|
// ROOT CAUSE: a kind:'human' row is NOT a broker-hosted agent node — it is a
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Run with: node --conditions=crtr-src --import tsx/esm --test src/core/__tests__/plugin-page-components.test.ts
|
|
2
|
+
//
|
|
3
|
+
// Wiring pins for plugin-contributed page components — the layer whose absence
|
|
4
|
+
// is SILENT: every authoring, validation, daemon, inbox, and TUI consumer takes
|
|
5
|
+
// the catalog as a parameter, so if `resolvePageComponents` stopped reading
|
|
6
|
+
// installed plugins' manifest `page_components` blocks, every plugin-shipped
|
|
7
|
+
// component would simply stop being authorable with all suites green.
|
|
8
|
+
// Plus the archive path: `validatePluginBundle` must carry a valid bundle.json
|
|
9
|
+
// `page_components` block into `ValidatedBundle.page_components` (what the
|
|
10
|
+
// installer copies into the synthesized manifest) and reject an invalid one.
|
|
11
|
+
import { test, before, beforeEach, afterEach, after } from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, realpathSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import * as tar from 'tar';
|
|
17
|
+
import { resetScopeCache } from '../scope.js';
|
|
18
|
+
import { resolvePageComponents } from '../config.js';
|
|
19
|
+
import { validatePluginBundle } from '../command-plugins/bundle.js';
|
|
20
|
+
let isolatedHome;
|
|
21
|
+
let isolatedCwd;
|
|
22
|
+
let prevHome;
|
|
23
|
+
let prevCwd;
|
|
24
|
+
before(() => {
|
|
25
|
+
prevHome = process.env.HOME;
|
|
26
|
+
prevCwd = process.cwd();
|
|
27
|
+
});
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
isolatedHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-page-components-home-')));
|
|
30
|
+
isolatedCwd = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-page-components-cwd-')));
|
|
31
|
+
process.env.HOME = isolatedHome;
|
|
32
|
+
process.chdir(isolatedCwd);
|
|
33
|
+
resetScopeCache();
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
process.chdir(prevCwd);
|
|
37
|
+
rmSync(isolatedHome, { recursive: true, force: true });
|
|
38
|
+
rmSync(isolatedCwd, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
after(() => {
|
|
41
|
+
process.chdir(prevCwd);
|
|
42
|
+
process.env.HOME = prevHome;
|
|
43
|
+
resetScopeCache();
|
|
44
|
+
});
|
|
45
|
+
/** Plant a manifest-only plugin (page components need no commands or executable). */
|
|
46
|
+
function plantPlugin(scopeRootPath, name, pageComponents) {
|
|
47
|
+
const root = join(scopeRootPath, 'plugins', name, '.crouter-plugin');
|
|
48
|
+
mkdirSync(root, { recursive: true });
|
|
49
|
+
writeFileSync(join(root, 'plugin.json'), JSON.stringify({ name, version: '0.1.0', page_components: pageComponents }));
|
|
50
|
+
}
|
|
51
|
+
function userRoot() {
|
|
52
|
+
const root = join(isolatedHome, '.crouter');
|
|
53
|
+
mkdirSync(root, { recursive: true });
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
test('an enabled user-scope plugin contributes its page components; a disabled one contributes nothing', () => {
|
|
57
|
+
const root = userRoot();
|
|
58
|
+
plantPlugin(root, 'northlight', [
|
|
59
|
+
{ kind: 'connector', description: 'Connector decision', useWhen: 'Use for a connector decision.', doc: 'Product contract.' },
|
|
60
|
+
{ kind: 'applet-card', display: true },
|
|
61
|
+
]);
|
|
62
|
+
plantPlugin(root, 'zzz-disabled', ['ghost']);
|
|
63
|
+
writeFileSync(join(root, 'config.json'), JSON.stringify({ plugins: { 'zzz-disabled': { enabled: false } } }));
|
|
64
|
+
const components = resolvePageComponents();
|
|
65
|
+
assert.deepEqual(components, [
|
|
66
|
+
{ kind: 'connector', description: 'Connector decision', useWhen: 'Use for a connector decision.', doc: 'Product contract.' },
|
|
67
|
+
{ kind: 'applet-card', display: true },
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
test('user config entries and plugin contributions resolve together, and a colliding kind is rejected', () => {
|
|
71
|
+
const root = userRoot();
|
|
72
|
+
plantPlugin(root, 'northlight', ['connector']);
|
|
73
|
+
writeFileSync(join(root, 'config.json'), JSON.stringify({ page_components: ['approval'] }));
|
|
74
|
+
assert.deepEqual(resolvePageComponents().map((component) => component.kind), ['approval', 'connector']);
|
|
75
|
+
writeFileSync(join(root, 'config.json'), JSON.stringify({ page_components: ['connector'] }));
|
|
76
|
+
assert.throws(() => resolvePageComponents(), /page component kind "connector" is registered by both config.json and plugin "northlight"/);
|
|
77
|
+
});
|
|
78
|
+
test('two plugins whose kinds derive one JSX tag are rejected, naming both sources', () => {
|
|
79
|
+
const root = userRoot();
|
|
80
|
+
plantPlugin(root, 'alpha', ['foo-bar']);
|
|
81
|
+
plantPlugin(root, 'beta', ['foo--bar']);
|
|
82
|
+
assert.throws(() => resolvePageComponents(), /kind "foo--bar" \(plugin "beta"\) derives JSX tag "FooBar", which duplicates kind "foo-bar" from plugin "alpha"/);
|
|
83
|
+
});
|
|
84
|
+
// --- archive path -----------------------------------------------------------
|
|
85
|
+
function minimalCommandsJson() {
|
|
86
|
+
return {
|
|
87
|
+
schemaVersion: 1,
|
|
88
|
+
mounts: [
|
|
89
|
+
{
|
|
90
|
+
parent: [],
|
|
91
|
+
node: {
|
|
92
|
+
kind: 'branch',
|
|
93
|
+
name: 'app',
|
|
94
|
+
description: 'application lifecycle and inspection',
|
|
95
|
+
whenToUse: 'you need to inspect an application',
|
|
96
|
+
rootEntry: {
|
|
97
|
+
concept: 'applications hosted by a crouter home',
|
|
98
|
+
description: 'application lifecycle and inspection',
|
|
99
|
+
whenToUse: 'you need to inspect an application',
|
|
100
|
+
},
|
|
101
|
+
summary: 'application lifecycle and inspection',
|
|
102
|
+
model: 'Applications are product resources owned by the selected home.',
|
|
103
|
+
children: [
|
|
104
|
+
{
|
|
105
|
+
kind: 'leaf',
|
|
106
|
+
name: 'show',
|
|
107
|
+
description: 'show one application',
|
|
108
|
+
whenToUse: 'inspect a single application by id',
|
|
109
|
+
tier: 'important',
|
|
110
|
+
summary: 'show one application details',
|
|
111
|
+
params: [{ kind: 'positional', name: 'app-id', required: true, constraint: 'the application id' }],
|
|
112
|
+
output: [{ name: 'app_id', type: 'string', required: true, constraint: 'the echoed application id' }],
|
|
113
|
+
effects: ['None. Read-only.'],
|
|
114
|
+
rest: { method: 'GET', path: '/v1/apps/{app-id}', params: { 'app-id': { in: 'path' } } },
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function makeArchive(bundleJson) {
|
|
123
|
+
const dir = mkdtempSync(join(tmpdir(), 'crtr-page-components-tar-'));
|
|
124
|
+
try {
|
|
125
|
+
writeFileSync(join(dir, 'bundle.json'), JSON.stringify(bundleJson));
|
|
126
|
+
writeFileSync(join(dir, 'commands.json'), JSON.stringify(minimalCommandsJson()));
|
|
127
|
+
const pack = tar.create({ sync: true, cwd: dir, portable: true }, ['bundle.json', 'commands.json']);
|
|
128
|
+
const chunks = [];
|
|
129
|
+
let chunk;
|
|
130
|
+
while ((chunk = pack.read()) !== null)
|
|
131
|
+
chunks.push(chunk);
|
|
132
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
rmSync(dir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
test('validatePluginBundle carries a valid bundle.json page_components block into ValidatedBundle', async () => {
|
|
139
|
+
const archive = makeArchive({
|
|
140
|
+
bundleVersion: 1,
|
|
141
|
+
page_components: ['connector', { kind: 'applet-card', description: 'Applet card', display: true }],
|
|
142
|
+
});
|
|
143
|
+
const validated = await validatePluginBundle(archive, { reservedCoreNames: new Set(['node']) });
|
|
144
|
+
assert.deepEqual(validated.issues, []);
|
|
145
|
+
assert.deepEqual(validated.bundle?.page_components, [
|
|
146
|
+
{ kind: 'connector' },
|
|
147
|
+
{ kind: 'applet-card', description: 'Applet card', display: true },
|
|
148
|
+
]);
|
|
149
|
+
});
|
|
150
|
+
test('validatePluginBundle rejects an invalid page_components block loudly', async () => {
|
|
151
|
+
const archive = makeArchive({ bundleVersion: 1, page_components: [{ kind: 'connector', use_when: 'typo field' }] });
|
|
152
|
+
const validated = await validatePluginBundle(archive, { reservedCoreNames: new Set(['node']) });
|
|
153
|
+
assert.equal(validated.bundle, undefined);
|
|
154
|
+
assert.equal(validated.issues.length, 1);
|
|
155
|
+
assert.match(validated.issues[0].message, /page_components block is invalid/);
|
|
156
|
+
assert.match(validated.issues[0].received, /use_when/);
|
|
157
|
+
});
|
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
// lifecycle close` then tears the replacement down (shutdown frame → exit,
|
|
11
11
|
// socket unlinked, status canceled, daemon leaves it).
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// exit drives policy automatically — no synthetic delivery, no tick, no grace.
|
|
13
|
+
// spawnHeadlessChild calls the production birth path in the harness process, so
|
|
14
|
+
// every broker below is harness-fleet-owned from its first and only boot and
|
|
15
|
+
// every SIGKILL's REAL ChildProcess exit drives policy automatically — no
|
|
16
|
+
// synthetic delivery, no tick, no grace.
|
|
18
17
|
// Each crash has its own real broker setup, so either top-level test remains a
|
|
19
18
|
// complete journey under name filtering and pays only its own first respawn
|
|
20
19
|
// backoff.
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
// they belong to the compiled seam tier; neither needs tmux.
|
|
20
20
|
import { after, before, test } from 'node:test';
|
|
21
21
|
import assert from 'node:assert/strict';
|
|
22
|
+
import { writeFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
22
24
|
import { appendInbox, readCursor, readInboxSince } from '../../feed/inbox.js';
|
|
23
25
|
import { registerWarmSpare, subscribe, updateNode } from '../../canvas/index.js';
|
|
24
26
|
import { isPidAlive } from '../../canvas/pid.js';
|
|
@@ -176,6 +178,44 @@ test('an unattended resident with no live obligation completes on the daemon clo
|
|
|
176
178
|
process.env['CRTR_TEST_UNATTENDED_PARK_MS'] = priorParkMs;
|
|
177
179
|
}
|
|
178
180
|
});
|
|
181
|
+
test('a connected broker viewer keeps an obligation-free resident live', { timeout: 30_000 }, async () => {
|
|
182
|
+
const priorParkMs = process.env['CRTR_TEST_UNATTENDED_PARK_MS'];
|
|
183
|
+
process.env['CRTR_TEST_UNATTENDED_PARK_MS'] = '1500';
|
|
184
|
+
const nodeId = h.fabricateBrokerNode({
|
|
185
|
+
id: 'settled-with-viewer',
|
|
186
|
+
parent: root,
|
|
187
|
+
lifecycle: 'resident',
|
|
188
|
+
status: 'idle',
|
|
189
|
+
intent: 'idle-release',
|
|
190
|
+
});
|
|
191
|
+
updateNode(nodeId, {
|
|
192
|
+
launch: {
|
|
193
|
+
extensions: [],
|
|
194
|
+
tools: [],
|
|
195
|
+
systemPrompt: '',
|
|
196
|
+
env: {},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
reviveNode(nodeId, { resume: true });
|
|
200
|
+
const boot = await h.awaitBoot(nodeId);
|
|
201
|
+
// This is the presence written by a helloed direct attach, which has no
|
|
202
|
+
// focuses-table row because it did not pass through the focus opener.
|
|
203
|
+
writeFileSync(join(h.home, 'nodes', nodeId, 'job', 'attach.json'), JSON.stringify({ viewers: 1 }));
|
|
204
|
+
try {
|
|
205
|
+
const seenAt = 15_000;
|
|
206
|
+
await h.tick(seenAt);
|
|
207
|
+
await h.tick(seenAt + 1500);
|
|
208
|
+
assert.equal(h.fleet.has(nodeId), true, 'an attached viewer keeps the broker fleet-owned');
|
|
209
|
+
assert.equal(isPidAlive(boot.pid), true, 'an attached viewer keeps the broker process alive');
|
|
210
|
+
assert.ok(['active', 'idle'].includes(h.node(nodeId)?.status ?? ''), 'an attached viewer keeps the node live');
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
if (priorParkMs === undefined)
|
|
214
|
+
delete process.env['CRTR_TEST_UNATTENDED_PARK_MS'];
|
|
215
|
+
else
|
|
216
|
+
process.env['CRTR_TEST_UNATTENDED_PARK_MS'] = priorParkMs;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
179
219
|
test('an already idle terminal node with no live obligation reconciles into canceled', async () => {
|
|
180
220
|
const nodeId = h.fabricateBrokerNode({
|
|
181
221
|
id: 'idle-terminal-without-obligation',
|
|
@@ -150,7 +150,7 @@ test('flagship: full node lifecycle — spawn, dormancy, one-hop wake, promote,
|
|
|
150
150
|
// ('natural stop while awaiting a live worker → idle-release …').
|
|
151
151
|
// ===================================================================
|
|
152
152
|
{
|
|
153
|
-
const stop = evaluateStop(B, { pushedFinal: false,
|
|
153
|
+
const stop = evaluateStop(B, { pushedFinal: false, requestedHumanReply: false });
|
|
154
154
|
assert.equal(stop.action, 'allow', 'awaiting terminal B → allow (legit idle-release)');
|
|
155
155
|
assert.equal(stop.reason, 'awaiting', "B classified awaiting on its ACTIVE live sub to C");
|
|
156
156
|
}
|
|
@@ -225,7 +225,7 @@ test('flagship: full node lifecycle — spawn, dormancy, one-hop wake, promote,
|
|
|
225
225
|
assert.deepEqual(drift?.from, { mode: 'base', lifecycle: 'terminal' }, 'drift.from = the acked base×terminal');
|
|
226
226
|
assert.deepEqual(drift?.to, { mode: 'orchestrator', lifecycle: 'terminal' }, 'drift.to = the new orchestrator persona');
|
|
227
227
|
assert.match(drift?.guidance ?? '', /ORCHESTRATOR/i, 'the steer carries the base→orchestrator guidance');
|
|
228
|
-
const stop = evaluateStop(B, { pushedFinal: false,
|
|
228
|
+
const stop = evaluateStop(B, { pushedFinal: false, requestedHumanReply: false });
|
|
229
229
|
assert.equal(stop.action, 'reprompt', 'orchestrator B with no live sub, no final → reprompt');
|
|
230
230
|
assert.equal(stop.reason, 'stalled', 'classified stalled');
|
|
231
231
|
assert.equal(stop.action === 'reprompt' ? stop.message : '', STALL_REPROMPT, 'the stall reprompt nudge');
|
|
@@ -11,6 +11,7 @@ import { ApiError, CrtrClient } from '../../../api/index.js';
|
|
|
11
11
|
import { createApiServer } from '../../../daemon/api/server.js';
|
|
12
12
|
import { apiSocketPath } from '../../canvas/paths.js';
|
|
13
13
|
import { claimTicket, readTicketClaim } from '../../human/claim.js';
|
|
14
|
+
import { bindTicketReplyRoute } from '../../human/convention.js';
|
|
14
15
|
import { ticketDir } from '../../human/root.js';
|
|
15
16
|
import { submitPage, submitReview } from '../../human/tickets.js';
|
|
16
17
|
import { spawnNode } from '../../runtime/nodes.js';
|
|
@@ -66,10 +67,14 @@ async function expectReviewSubmitGuidance(nodeId, body) {
|
|
|
66
67
|
function submitBridgePage(bridge) {
|
|
67
68
|
const dir = ticketDir(bridge);
|
|
68
69
|
const sourceDir = mkdtempSync(join(tmpdir(), 'crtr-e2e-page-'));
|
|
69
|
-
const sourceFile = join(sourceDir, 'page.
|
|
70
|
-
writeFileSync(sourceFile, '
|
|
70
|
+
const sourceFile = join(sourceDir, 'page.tsx');
|
|
71
|
+
writeFileSync(sourceFile, 'export default () => (\n <Page title="Ship it?" subtitle="Decide whether to ship now so the release can proceed.">\n <UserText id="question" initialText="" />\n </Page>\n);\n');
|
|
71
72
|
try {
|
|
72
|
-
|
|
73
|
+
bindTicketReplyRoute(dir, bridge);
|
|
74
|
+
// The delivery `human send` derives for a terminal bridge page: a
|
|
75
|
+
// response-bearing slot gives it a reply route, and a terminal sender's page
|
|
76
|
+
// always projects to the inbox.
|
|
77
|
+
submitPage({ dir, sourceFile, delivery: { placement: 'inline', inbox: true, reply: true } });
|
|
73
78
|
}
|
|
74
79
|
finally {
|
|
75
80
|
rmSync(sourceDir, { recursive: true, force: true });
|
|
@@ -100,6 +105,7 @@ test('malformed resolves leave the mounted ticket claim in place', async () => {
|
|
|
100
105
|
const reviewDir = ticketDir(reviewBridge);
|
|
101
106
|
const file = join(home, 'review.md');
|
|
102
107
|
writeFileSync(file, '# Review\n');
|
|
108
|
+
bindTicketReplyRoute(reviewDir, reviewBridge);
|
|
103
109
|
submitReview({ dir: reviewDir, review: { title: 'Review', subtitle: 'Review this file so implementation can proceed safely.', source: {}, file } });
|
|
104
110
|
const reviewClaim = claimTicket(reviewDir);
|
|
105
111
|
assert.ok(reviewClaim !== null);
|
|
@@ -108,10 +114,10 @@ test('malformed resolves leave the mounted ticket claim in place', async () => {
|
|
|
108
114
|
});
|
|
109
115
|
test('the ticket resolve route pushFinals the readable answer to the parent', async () => {
|
|
110
116
|
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
111
|
-
const bridge = spawnNode({ kind: 'human', parent, cwd: home, name: 'human-
|
|
117
|
+
const bridge = spawnNode({ kind: 'human', parent, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
112
118
|
const dir = submitBridgePage(bridge);
|
|
113
|
-
const result = await client.resolveHumanTicket(bridge, { responses: { question: { text: 'yes, ship', edited: true
|
|
114
|
-
assert.deepEqual(result, {
|
|
119
|
+
const result = await client.resolveHumanTicket(bridge, { responses: { question: { text: 'yes, ship', edited: true } } });
|
|
120
|
+
assert.deepEqual(result, { completed: true, reply_delivered: true, kind: 'page' });
|
|
115
121
|
assert.equal(getNode(bridge)?.status, 'done', 'bridge finalized by crtrd');
|
|
116
122
|
const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
|
|
117
123
|
assert.equal(finals.length, 1, 'exactly one pushFinal reached the parent');
|
|
@@ -48,7 +48,7 @@ import { evaluateStop } from '../../runtime/stop-guard.js';
|
|
|
48
48
|
function persona(m) {
|
|
49
49
|
return { mode: m.mode, lifecycle: m.lifecycle };
|
|
50
50
|
}
|
|
51
|
-
const SIGNALS = { pushedFinal: false,
|
|
51
|
+
const SIGNALS = { pushedFinal: false, requestedHumanReply: false };
|
|
52
52
|
// ===========================================================================
|
|
53
53
|
// (a) LIFECYCLE FLIP — `crtr node config --lifecycle` on a LIVE node, both directions,
|
|
54
54
|
// observing the idle-release DECISION change. A round-trip on ONE node
|
|
@@ -186,7 +186,7 @@ test('tmux installer remaps owned pairs and preserves occupied user bindings', (
|
|
|
186
186
|
const defaultMenu = tmuxBinding(socket, 'root', 'M-c') ?? '';
|
|
187
187
|
assert.match(defaultMenu, /display-menu/);
|
|
188
188
|
// The inbox toggle installs at its default key (alt+i → M-i) in the same sweep.
|
|
189
|
-
assert.match(tmuxBinding(socket, 'root', 'M-i') ?? '', /crtr
|
|
189
|
+
assert.match(tmuxBinding(socket, 'root', 'M-i') ?? '', /crtr surface inbox/);
|
|
190
190
|
const remapped = installTmuxBindings(resolveKeybindings({
|
|
191
191
|
'crtr.tmux.menu.open': ['alt+shift+z'],
|
|
192
192
|
'crtr.tmux.menu.graph': ['shift+z'],
|
|
@@ -242,11 +242,13 @@ test('tmux installer reads pair-array manifests and replaces owned pairs', (t) =
|
|
|
242
242
|
try {
|
|
243
243
|
// Current-scope releases wrote the pair array before wrappers were added.
|
|
244
244
|
isolatedTmux(socket, ['set-option', '-s', '@crtr_keybindings_manifest', JSON.stringify([{ table: 'root', key: 'M-i' }])]);
|
|
245
|
+
// Seeded with the pre-move command path, so the assertion below distinguishes a
|
|
246
|
+
// genuine replacement from a no-op.
|
|
245
247
|
isolatedTmux(socket, ['bind-key', '-T', 'root', 'M-i', 'run-shell', '-b', 'crtr human inbox toggle --quiet']);
|
|
246
248
|
const result = installTmuxBindings(resolveKeybindings());
|
|
247
249
|
assert.equal(result.ok, true, JSON.stringify(result.diagnostics));
|
|
248
250
|
assert.ok(!result.diagnostics.some((d) => d.bindingId === 'crtr.tmux.inbox.toggle'), JSON.stringify(result.diagnostics));
|
|
249
|
-
assert.match(tmuxBinding(socket, 'root', 'M-i') ?? '', /crtr
|
|
251
|
+
assert.match(tmuxBinding(socket, 'root', 'M-i') ?? '', /crtr surface inbox/);
|
|
250
252
|
assert.match(tmuxBinding(socket, 'root', 'M-c') ?? '', /background running bash/);
|
|
251
253
|
}
|
|
252
254
|
finally {
|