@north-light/crouter 0.3.162 → 0.3.163
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/builtin-memory/00-runtime-base.md +1 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -0
- package/dist/builtin-memory/init.md +38 -0
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +0 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +34 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +14 -3
- package/dist/clients/attach/__tests__/crtr-output.test.js +14 -9
- package/dist/clients/attach/__tests__/edit-diff.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/edit-diff.test.js +34 -0
- package/dist/clients/attach/__tests__/editor-frame-title.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/editor-frame-title.test.js +19 -0
- package/dist/clients/attach/render/chat-view.js +8 -3
- package/dist/clients/attach/render/crtr-output.js +31 -2
- package/dist/clients/attach/render/edit-diff.js +26 -2
- package/dist/clients/attach/render/tool-calls.d.ts +7 -2
- package/dist/clients/attach/render/tool-calls.js +25 -4
- package/dist/clients/attach/session/connection.d.ts +1 -1
- package/dist/clients/attach/session/editor-frame.d.ts +5 -1
- package/dist/clients/attach/session/editor-frame.js +21 -15
- package/dist/clients/attach/session/identity.d.ts +1 -1
- package/dist/clients/attach/viewer.js +621 -619
- package/dist/commands/__tests__/api-canvas-source.test.d.ts +1 -0
- package/dist/commands/__tests__/api-canvas-source.test.js +37 -0
- package/dist/commands/__tests__/human.test.js +2 -0
- package/dist/commands/__tests__/search-contents.test.d.ts +1 -0
- package/dist/commands/__tests__/search-contents.test.js +52 -0
- package/dist/commands/api-client.js +1 -0
- package/dist/commands/human/prompts.js +2 -2
- package/dist/commands/human/shared.d.ts +1 -0
- package/dist/commands/human/shared.js +7 -4
- package/dist/commands/memory/lint.d.ts +6 -0
- package/dist/commands/memory/lint.js +177 -35
- package/dist/commands/memory/read.js +22 -1
- package/dist/commands/memory/write.js +18 -4
- package/dist/commands/memory.js +1 -1
- package/dist/commands/search/contents.js +2 -2
- package/dist/commands/sys/__tests__/sync-deps.test.js +12 -21
- package/dist/commands/sys/__tests__/sync-import.test.js +30 -29
- package/dist/commands/sys/setup-wizard.d.ts +8 -1
- package/dist/commands/sys/setup-wizard.js +209 -58
- package/dist/commands/sys/sync-deps.js +14 -18
- package/dist/commands/sys/sync-project-guidance.js +16 -19
- package/dist/core/__tests__/migration.test.js +8 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +7 -10
- package/dist/core/__tests__/on-read-dedup-resume.test.js +13 -25
- package/dist/core/__tests__/on-read-identity.test.js +8 -15
- package/dist/core/__tests__/revive.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -4
- package/dist/core/__tests__/worktree.test.js +3 -3
- package/dist/core/canvas/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +18 -11
- package/dist/core/canvas/labels.d.ts +4 -5
- package/dist/core/canvas/labels.js +9 -9
- package/dist/core/command-manifests/schema.js +18 -5
- package/dist/core/command.js +17 -7
- package/dist/core/configured-clis/invoker.js +2 -0
- package/dist/core/help.d.ts +3 -1
- package/dist/core/help.js +5 -2
- package/dist/core/keybindings/__tests__/resolve.test.js +5 -2
- package/dist/core/keybindings/catalog.d.ts +3 -2
- package/dist/core/keybindings/catalog.js +34 -25
- package/dist/core/keybindings/index.d.ts +1 -1
- package/dist/core/keybindings/resolve.js +8 -0
- package/dist/core/keybindings/types.d.ts +6 -1
- package/dist/core/memory/doc-link-grammar.d.ts +20 -0
- package/dist/core/memory/doc-link-grammar.js +110 -0
- package/dist/core/memory-resolver.d.ts +5 -0
- package/dist/core/memory-resolver.js +12 -1
- package/dist/core/runtime/bearings.d.ts +8 -8
- package/dist/core/runtime/bearings.js +20 -16
- package/dist/core/runtime/broker.js +3 -13
- package/dist/core/runtime/canvas-extensions.d.ts +1 -0
- package/dist/core/runtime/canvas-extensions.js +2 -0
- package/dist/core/runtime/launch.d.ts +1 -1
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/tmux.js +136 -100
- package/dist/core/scope.js +4 -5
- package/dist/core/substrate/ceiling.d.ts +3 -6
- package/dist/core/substrate/ceiling.js +13 -15
- package/dist/core/substrate/index.d.ts +2 -3
- package/dist/core/substrate/index.js +2 -2
- package/dist/core/substrate/on-read.d.ts +4 -13
- package/dist/core/substrate/on-read.js +142 -262
- package/dist/core/substrate/render.js +4 -4
- package/dist/core/substrate/schema.d.ts +3 -2
- package/dist/core/substrate/schema.js +5 -5
- package/dist/pi-extensions/__tests__/canvas-tool-guide.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-tool-guide.test.js +96 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +7 -8
- package/dist/pi-extensions/canvas-tool-guide.d.ts +14 -0
- package/dist/pi-extensions/canvas-tool-guide.js +74 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +0 -95
|
@@ -11,7 +11,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
11
11
|
import { readFileSync, rmSync } from 'node:fs';
|
|
12
12
|
import { homedir } from 'node:os';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
-
import { formatGesture, resolveUserKeybindings, } from '../keybindings/index.js';
|
|
14
|
+
import { BINDING_CATALOG, formatGesture, resolveUserKeybindings, } from '../keybindings/index.js';
|
|
15
15
|
import { bundledPiCommandForShell } from './pi-cli.js';
|
|
16
16
|
import { surfaceTmuxStyleArgs } from './surface-bg.js';
|
|
17
17
|
import { inboxToggleTmuxCommand } from '@crouton-kit/humanloop';
|
|
@@ -470,7 +470,7 @@ export function sendKeysEnter(pane, text) {
|
|
|
470
470
|
// ---------------------------------------------------------------------------
|
|
471
471
|
const TMUX_BINDING_MANIFEST = '@crtr_keybindings_manifest';
|
|
472
472
|
const ROOT_TABLE = 'root';
|
|
473
|
-
const
|
|
473
|
+
const LEAF_MENU_ACTIONS = {
|
|
474
474
|
'crtr.tmux.menu.promote': { description: 'promote to orchestrator', action: { kind: 'keys', keys: '/promote' } },
|
|
475
475
|
'crtr.tmux.menu.resume': { description: 'resume node', action: { kind: 'keys', keys: '/resume-node' } },
|
|
476
476
|
'crtr.tmux.menu.fork': { description: 'fork into idle root', action: { kind: 'direct', run: 'node fork --pane {pane}' } },
|
|
@@ -484,7 +484,6 @@ const MENU_ACTIONS = {
|
|
|
484
484
|
'crtr.tmux.menu.file-review': { description: 'review a file', action: { kind: 'raw-keys', keys: 'M-S-r' } },
|
|
485
485
|
'crtr.tmux.menu.metadata': { description: 'metadata', action: { kind: 'keys', keys: '/node-metadata' } },
|
|
486
486
|
'crtr.tmux.menu.fold-tools': { description: 'fold finished tool calls', action: { kind: 'keys', keys: '/fold-tools' } },
|
|
487
|
-
'crtr.tmux.menu.view-settings': undefined,
|
|
488
487
|
'crtr.tmux.menu.context': { description: 'context + reports', action: { kind: 'keys', keys: '/context' } },
|
|
489
488
|
'crtr.tmux.menu.providers': { description: 'settings', action: { kind: 'popup', run: 'sys setup' } },
|
|
490
489
|
'crtr.tmux.menu.graph': { description: 'graph view', action: { kind: 'keys', keys: '/graph' } },
|
|
@@ -492,16 +491,8 @@ const MENU_ACTIONS = {
|
|
|
492
491
|
'crtr.tmux.menu.copy-id': { description: 'copy node id', action: { kind: 'command', run: 'surface node id --pane {pane}' } },
|
|
493
492
|
'crtr.tmux.menu.reload-viewer': { description: 'reload viewer', action: { kind: 'direct', run: 'surface reload --pane {pane}' } },
|
|
494
493
|
'crtr.tmux.menu.pin': { description: 'pin conversation', action: { kind: 'direct', run: 'surface node pin --pane {pane}' } },
|
|
495
|
-
'crtr.tmux.menu.
|
|
496
|
-
'crtr.tmux.menu.
|
|
497
|
-
'crtr.tmux.menu.issues': undefined,
|
|
498
|
-
'crtr.tmux.menu.issue.idea': undefined,
|
|
499
|
-
'crtr.tmux.menu.issue.bug': undefined,
|
|
500
|
-
'crtr.tmux.node.next': undefined,
|
|
501
|
-
'crtr.tmux.node.previous': undefined,
|
|
502
|
-
'crtr.tmux.inbox.toggle': undefined,
|
|
503
|
-
'crtr.tmux.view.next': undefined,
|
|
504
|
-
'crtr.tmux.view.previous': undefined,
|
|
494
|
+
'crtr.tmux.menu.issue.idea': { description: 'idea', action: { kind: 'popup', run: 'canvas issue new --type idea' } },
|
|
495
|
+
'crtr.tmux.menu.issue.bug': { description: 'bug', action: { kind: 'popup', run: 'canvas issue new --type bug' } },
|
|
505
496
|
};
|
|
506
497
|
function escapeTmuxFormats(value) {
|
|
507
498
|
return value.replace(/#/g, '##');
|
|
@@ -512,34 +503,69 @@ function tmuxCommand(args) {
|
|
|
512
503
|
function chordCommand(run, interactive = false) {
|
|
513
504
|
const command = `crtr canvas chord --pane '#{pane_id}' --run ${shellQuote(escapeTmuxFormats(run))}${interactive ? ' --interactive' : ''}`;
|
|
514
505
|
return interactive
|
|
515
|
-
?
|
|
516
|
-
|
|
506
|
+
? {
|
|
507
|
+
argv: ['display-popup', '-E', '-w', '60%', '-h', '50%', '-d', '#{pane_current_path}', command],
|
|
508
|
+
menu: `display-popup -E -w 60% -h 50% -d "#{pane_current_path}" ${shellQuote(command)}`,
|
|
509
|
+
}
|
|
510
|
+
: {
|
|
511
|
+
argv: ['run-shell', `${command} >/dev/null 2>&1`],
|
|
512
|
+
menu: `run-shell ${shellQuote(`${command} >/dev/null 2>&1`)}`,
|
|
513
|
+
};
|
|
517
514
|
}
|
|
518
|
-
/** Dispatch
|
|
515
|
+
/** Dispatch an action straight to a `crtr` invocation, skipping the
|
|
519
516
|
* `canvas chord` hop entirely. Only sound for an action whose sole
|
|
520
517
|
* interpolation need is `{pane}` — tmux expands it natively as `#{pane_id}` at
|
|
521
|
-
*
|
|
518
|
+
* keypress time (a real tmux format, substituted AFTER escaping the static
|
|
522
519
|
* run text so the `#` here is never doubled), so chord's node-resolution
|
|
523
|
-
* round trip buys nothing. `-b` keeps
|
|
520
|
+
* round trip buys nothing. `-b` keeps dispatch non-blocking. */
|
|
524
521
|
function directCommand(run) {
|
|
525
522
|
const argv = escapeTmuxFormats(run).replace('{pane}', '#{pane_id}');
|
|
526
|
-
|
|
523
|
+
const command = `crtr ${argv} >/dev/null 2>&1`;
|
|
524
|
+
return {
|
|
525
|
+
argv: ['run-shell', '-b', command],
|
|
526
|
+
menu: `run-shell -b ${shellQuote(command)}`,
|
|
527
|
+
};
|
|
527
528
|
}
|
|
528
|
-
|
|
529
|
+
/** One command constructor serves both display-menu selections and direct root
|
|
530
|
+
* shortcuts. Each result carries the argv bind-key needs plus the stable menu
|
|
531
|
+
* serialization existing Alt+C installs already use. */
|
|
532
|
+
function menuActionCommand(action) {
|
|
529
533
|
switch (action.kind) {
|
|
530
|
-
case 'disabled': return
|
|
531
|
-
case 'separator': return
|
|
534
|
+
case 'disabled': return null;
|
|
535
|
+
case 'separator': return null;
|
|
532
536
|
// Choosing any menu item closes it; refresh-client is the deliberately
|
|
533
537
|
// inert command that lets the prefix shortcut choose this close row.
|
|
534
|
-
case 'dismiss': return 'refresh-client';
|
|
535
|
-
case 'keys':
|
|
536
|
-
|
|
538
|
+
case 'dismiss': return { argv: ['refresh-client'], menu: 'refresh-client' };
|
|
539
|
+
case 'keys': {
|
|
540
|
+
const keys = escapeTmuxFormats(action.keys);
|
|
541
|
+
return {
|
|
542
|
+
// A root binding already runs in the pressed key's pane context. Its
|
|
543
|
+
// target must stay implicit: bind-key does not expand `#{pane_id}` in
|
|
544
|
+
// send-keys' target argument. A menu command is serialized later and
|
|
545
|
+
// does need the pane captured when the menu opened.
|
|
546
|
+
argv: ['send-keys', keys, 'Enter'],
|
|
547
|
+
menu: `send-keys -t '#{pane_id}' ${shellQuote(keys)} Enter`,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
case 'raw-keys': {
|
|
551
|
+
const keys = escapeTmuxFormats(action.keys);
|
|
552
|
+
return {
|
|
553
|
+
argv: ['send-keys', keys],
|
|
554
|
+
menu: `send-keys -t '#{pane_id}' ${shellQuote(keys)}`,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
537
557
|
case 'command': return chordCommand(action.run);
|
|
538
558
|
case 'direct': return directCommand(action.run);
|
|
539
559
|
case 'popup': return chordCommand(action.run, true);
|
|
540
|
-
case 'submenu':
|
|
560
|
+
case 'submenu': {
|
|
561
|
+
const argv = buildMenuArgs(action.title, action.items);
|
|
562
|
+
return { argv, menu: tmuxCommand(argv) };
|
|
563
|
+
}
|
|
541
564
|
}
|
|
542
565
|
}
|
|
566
|
+
function renderMenuAction(action) {
|
|
567
|
+
return menuActionCommand(action)?.menu ?? '';
|
|
568
|
+
}
|
|
543
569
|
/** Visible width of a menu item name: style markup (`#[...]`) takes no cells. */
|
|
544
570
|
function menuItemWidth(description) {
|
|
545
571
|
return description.replace(/#\[[^\]]*\]/g, '').length;
|
|
@@ -573,8 +599,12 @@ function tmuxKey(stroke) {
|
|
|
573
599
|
let key = parts.pop();
|
|
574
600
|
const modifiers = new Set(parts);
|
|
575
601
|
if (modifiers.has('shift') && key.length === 1 && /[a-z]/.test(key)) {
|
|
602
|
+
// tmux names a shifted printable with its uppercase character even when an
|
|
603
|
+
// extended-key terminal also preserves Shift as a modifier: Alt+Shift+F is
|
|
604
|
+
// `M-S-F`. Shift by itself needs only the uppercase character (`F`).
|
|
576
605
|
key = key.toUpperCase();
|
|
577
|
-
modifiers.
|
|
606
|
+
if (!modifiers.has('ctrl') && !modifiers.has('alt'))
|
|
607
|
+
modifiers.delete('shift');
|
|
578
608
|
}
|
|
579
609
|
key = TMUX_NAMED_KEYS[key] ?? key;
|
|
580
610
|
const prefix = [
|
|
@@ -589,34 +619,56 @@ function menuSelector(stroke) {
|
|
|
589
619
|
const key = parts.at(-1);
|
|
590
620
|
return parts.includes('shift') && /[a-z]/.test(key) ? key.toUpperCase() : key === 'space' ? ' ' : key;
|
|
591
621
|
}
|
|
592
|
-
function
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
continue;
|
|
599
|
-
for (const gesture of bindings.gestures(id)) {
|
|
600
|
-
items.push({ description: entry.description, selector: menuSelector(gesture), action: entry.action });
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return items;
|
|
604
|
-
};
|
|
605
|
-
const issueItems = [];
|
|
606
|
-
for (const [id, description, type] of [
|
|
607
|
-
['crtr.tmux.menu.issue.idea', 'idea', 'idea'],
|
|
608
|
-
['crtr.tmux.menu.issue.bug', 'bug', 'bug'],
|
|
609
|
-
]) {
|
|
622
|
+
function boundMenuItems(ids, entries, bindings) {
|
|
623
|
+
const items = [];
|
|
624
|
+
for (const id of ids) {
|
|
625
|
+
const entry = entries.get(id);
|
|
626
|
+
if (entry === undefined)
|
|
627
|
+
continue;
|
|
610
628
|
for (const gesture of bindings.gestures(id)) {
|
|
611
|
-
|
|
629
|
+
items.push({ description: entry.description, selector: menuSelector(gesture), action: entry.action });
|
|
612
630
|
}
|
|
613
631
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
632
|
+
return items;
|
|
633
|
+
}
|
|
634
|
+
/** Resolve the complete action descriptor set once. Submenu openers point at
|
|
635
|
+
* the same child MenuItems the prefix menu renders, so their direct global
|
|
636
|
+
* shortcuts cannot drift from the menu path. */
|
|
637
|
+
function resolvedMenuActions(bindings) {
|
|
638
|
+
const entries = new Map();
|
|
639
|
+
for (const [id, entry] of Object.entries(LEAF_MENU_ACTIONS)) {
|
|
640
|
+
if (entry !== undefined)
|
|
641
|
+
entries.set(id, entry);
|
|
642
|
+
}
|
|
643
|
+
const showItems = boundMenuItems([
|
|
644
|
+
'crtr.tmux.menu.metadata',
|
|
645
|
+
'crtr.tmux.menu.context',
|
|
646
|
+
], entries, bindings);
|
|
647
|
+
entries.set('crtr.tmux.menu.show', {
|
|
648
|
+
description: 'show ›',
|
|
649
|
+
action: { kind: 'submenu', title: ' show node ', items: showItems },
|
|
650
|
+
});
|
|
651
|
+
// How this viewer PAINTS the transcript, as opposed to what it shows. Each item
|
|
652
|
+
// is a toggle the viewer applies to its own render state (a `/`-command sent to
|
|
653
|
+
// the pane); a tmux menu is built once and cannot show live state, so the item
|
|
654
|
+
// reads as the action and the viewer answers with a notice.
|
|
655
|
+
const viewSettingsItems = boundMenuItems(['crtr.tmux.menu.fold-tools'], entries, bindings);
|
|
656
|
+
entries.set('crtr.tmux.menu.view-settings', {
|
|
657
|
+
description: 'view settings ›',
|
|
658
|
+
action: { kind: 'submenu', title: ' view settings ', items: viewSettingsItems },
|
|
659
|
+
});
|
|
660
|
+
const issueItems = boundMenuItems([
|
|
661
|
+
'crtr.tmux.menu.issue.idea',
|
|
662
|
+
'crtr.tmux.menu.issue.bug',
|
|
663
|
+
], entries, bindings);
|
|
664
|
+
entries.set('crtr.tmux.menu.issues', {
|
|
665
|
+
description: 'create ›',
|
|
666
|
+
action: { kind: 'submenu', title: ' create ', items: issueItems },
|
|
667
|
+
});
|
|
668
|
+
return entries;
|
|
669
|
+
}
|
|
670
|
+
function menuItems(bindings, entries) {
|
|
671
|
+
const boundItems = (ids) => boundMenuItems(ids, entries, bindings);
|
|
620
672
|
const items = [];
|
|
621
673
|
const addGroup = (heading, groupItems) => {
|
|
622
674
|
if (groupItems.length === 0)
|
|
@@ -625,41 +677,15 @@ function menuItems(bindings) {
|
|
|
625
677
|
items.push({ description: '', selector: '', action: { kind: 'separator' } });
|
|
626
678
|
items.push({ description: `-#[bold]${heading}`, selector: '', action: { kind: 'disabled' } }, ...groupItems);
|
|
627
679
|
};
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
]);
|
|
632
|
-
// How this viewer PAINTS the transcript, as opposed to what it shows. Each item
|
|
633
|
-
// is a toggle the viewer applies to its own render state (a `/`-command sent to
|
|
634
|
-
// the pane); a tmux menu is built once and cannot show live state, so the item
|
|
635
|
-
// reads as the action and the viewer answers with a notice.
|
|
636
|
-
const viewSettingsItems = boundItems(['crtr.tmux.menu.fold-tools']);
|
|
637
|
-
const exploreItems = boundItems([
|
|
680
|
+
// Explore changes what is in view or opens a canvas-level browser. Current
|
|
681
|
+
// node acts on the agent in this pane without changing its lifecycle.
|
|
682
|
+
addGroup('Explore', boundItems([
|
|
638
683
|
'crtr.tmux.menu.resume',
|
|
639
684
|
'crtr.tmux.menu.graph',
|
|
640
685
|
'crtr.tmux.menu.focus-manager',
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
exploreItems.push({
|
|
645
|
-
description: 'show ›',
|
|
646
|
-
selector: menuSelector(gesture),
|
|
647
|
-
action: { kind: 'submenu', title: ' show node ', items: showItems },
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
if (viewSettingsItems.length > 0) {
|
|
652
|
-
for (const gesture of bindings.gestures('crtr.tmux.menu.view-settings')) {
|
|
653
|
-
exploreItems.push({
|
|
654
|
-
description: 'view settings ›',
|
|
655
|
-
selector: menuSelector(gesture),
|
|
656
|
-
action: { kind: 'submenu', title: ' view settings ', items: viewSettingsItems },
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// Explore changes what is in view or opens a canvas-level browser. Current
|
|
661
|
-
// node acts on the agent in this pane without changing its lifecycle.
|
|
662
|
-
addGroup('Explore', exploreItems);
|
|
686
|
+
'crtr.tmux.menu.show',
|
|
687
|
+
'crtr.tmux.menu.view-settings',
|
|
688
|
+
]));
|
|
663
689
|
addGroup('Current node', boundItems([
|
|
664
690
|
'crtr.tmux.menu.background-bash',
|
|
665
691
|
'crtr.tmux.menu.file-review',
|
|
@@ -667,21 +693,17 @@ function menuItems(bindings) {
|
|
|
667
693
|
'crtr.tmux.menu.copy-id',
|
|
668
694
|
'crtr.tmux.menu.reload-viewer',
|
|
669
695
|
]));
|
|
670
|
-
addGroup('Lifecycle', [
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
addGroup('System', systemItems);
|
|
696
|
+
addGroup('Lifecycle', boundItems([
|
|
697
|
+
'crtr.tmux.menu.fork',
|
|
698
|
+
'crtr.tmux.menu.promote',
|
|
699
|
+
'crtr.tmux.menu.demote',
|
|
700
|
+
'crtr.tmux.menu.detach',
|
|
701
|
+
'crtr.tmux.menu.close-subtree',
|
|
702
|
+
]));
|
|
703
|
+
addGroup('System', boundItems([
|
|
704
|
+
'crtr.tmux.menu.providers',
|
|
705
|
+
'crtr.tmux.menu.issues',
|
|
706
|
+
]));
|
|
685
707
|
const prefixGestures = bindings.gestures('crtr.tmux.menu.open');
|
|
686
708
|
if (prefixGestures.length > 0 && items.length > 0) {
|
|
687
709
|
items.push({ description: '', selector: '', action: { kind: 'separator' } });
|
|
@@ -835,10 +857,24 @@ export function installTmuxBindings(bindings = resolveUserKeybindings()) {
|
|
|
835
857
|
installed.push({ table, key });
|
|
836
858
|
return true;
|
|
837
859
|
};
|
|
838
|
-
const
|
|
860
|
+
const menuActions = resolvedMenuActions(bindings);
|
|
861
|
+
const menuCommand = buildMenuArgs(' crtr ', menuItems(bindings, menuActions));
|
|
839
862
|
for (const gesture of bindings.gestures('crtr.tmux.menu.open')) {
|
|
840
863
|
install(ROOT_TABLE, tmuxKey(gesture), menuCommand, 'crtr.tmux.menu.open', gesture);
|
|
841
864
|
}
|
|
865
|
+
for (const definition of BINDING_CATALOG) {
|
|
866
|
+
if (definition.pair?.field !== 'menu')
|
|
867
|
+
continue;
|
|
868
|
+
const menuId = definition.id;
|
|
869
|
+
const globalId = definition.pair.pairedId;
|
|
870
|
+
const entry = menuActions.get(menuId);
|
|
871
|
+
const command = entry === undefined ? null : menuActionCommand(entry.action);
|
|
872
|
+
if (command === null)
|
|
873
|
+
throw new Error(`no direct tmux action for paired menu binding ${menuId}`);
|
|
874
|
+
for (const gesture of bindings.gestures(globalId)) {
|
|
875
|
+
install(ROOT_TABLE, tmuxKey(gesture), command.argv, globalId, gesture);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
842
878
|
for (const id of ['crtr.tmux.node.next', 'crtr.tmux.node.previous', 'crtr.tmux.inbox.toggle']) {
|
|
843
879
|
for (const gesture of bindings.gestures(id))
|
|
844
880
|
install(ROOT_TABLE, tmuxKey(gesture), actionCommand(id), id, gesture);
|
package/dist/core/scope.js
CHANGED
|
@@ -87,11 +87,10 @@ export function findProjectScopeRoot(startDir = process.cwd()) {
|
|
|
87
87
|
* nested under a directory tree that itself contains a `.crouter/` (e.g. a
|
|
88
88
|
* mock-VM home under the real host home) would sweep the host's `~/.crouter`
|
|
89
89
|
* (and any other above-HOME `.crouter`) in as a bogus project ancestor,
|
|
90
|
-
* leaking one machine's user-global memory into a nested-HOME node.
|
|
91
|
-
*
|
|
92
|
-
* whose start dir is outside HOME (e.g. a production VM
|
|
93
|
-
* ancestor `.crouter`)
|
|
94
|
-
* before. */
|
|
90
|
+
* leaking one machine's user-global memory into a nested-HOME node. The
|
|
91
|
+
* explicit file-context store discovery in substrate/on-read.ts applies the
|
|
92
|
+
* same fence. A read whose start dir is outside HOME (e.g. a production VM
|
|
93
|
+
* with HOME=/root and no ancestor `.crouter`) walks to the filesystem root. */
|
|
95
94
|
function collectAncestorScopeRoots(startDir, userRoot) {
|
|
96
95
|
const roots = [];
|
|
97
96
|
const home = realpathOrSelf(homedir());
|
|
@@ -8,13 +8,10 @@ export declare function isIndexName(name: string): boolean;
|
|
|
8
8
|
export declare function indexDirOf(name: string): string;
|
|
9
9
|
/** Returns the display path corresponding to a canonical logical identifier. */
|
|
10
10
|
export declare function displayName(name: string): string;
|
|
11
|
-
/** The two visibility surfaces an INDEX can cap. */
|
|
12
|
-
export type Surface = 'systemPromptVisibility' | 'fileReadVisibility';
|
|
13
11
|
/** Build the dir → governing-INDEX map. Precedence is first-occurrence-wins, so
|
|
14
12
|
* callers MUST pass docs precedence-ordered (project > user > builtin) — the
|
|
15
13
|
* highest-precedence INDEX governs a dir, mirroring the memory resolver. */
|
|
16
14
|
export declare function buildCeilingIndex(docs: SubstrateDoc[]): Map<string, SubstrateDoc>;
|
|
17
|
-
/** A doc's effective rung
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
export declare function effectiveRung(doc: SubstrateDoc, ceil: Map<string, SubstrateDoc>, surface: Surface): Rung;
|
|
15
|
+
/** A doc's effective system-prompt rung after applying every ancestor INDEX
|
|
16
|
+
* ceiling. A `none` ancestor hides the whole subtree from the boot catalog. */
|
|
17
|
+
export declare function effectiveSystemPromptRung(doc: SubstrateDoc, ceil: Map<string, SubstrateDoc>): Rung;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
// ceiling.ts — canonical INDEX identifiers and the subtree
|
|
1
|
+
// ceiling.ts — canonical INDEX identifiers and the boot-catalog subtree ceiling.
|
|
2
2
|
//
|
|
3
|
-
// A directory
|
|
4
|
-
// (`INDEX` at the root)
|
|
5
|
-
//
|
|
3
|
+
// A directory INDEX has the canonical logical identifier `<directory>/INDEX`
|
|
4
|
+
// (`INDEX` at the root). Its system-prompt rung caps every document in that
|
|
5
|
+
// subtree while the boot catalog renders:
|
|
6
6
|
//
|
|
7
7
|
// effective rung of a descendant = min(own rung, every ancestor INDEX's rung)
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// taste: `crtr memory read taste/document-substrate`.
|
|
9
|
+
// A `preview` INDEX therefore lets its descendants surface at most at preview;
|
|
10
|
+
// a `none` INDEX hides the subtree. File context does not use this ceiling:
|
|
11
|
+
// every workspace/file match is an explicit `applies-to` route and renders at
|
|
12
|
+
// the matched document's own file-read rung.
|
|
14
13
|
import { rungRank } from './schema.js';
|
|
15
14
|
/** The reserved basename (no extension) that marks a directory's index doc. */
|
|
16
15
|
export const INDEX_NAME = 'INDEX';
|
|
@@ -55,15 +54,14 @@ function ancestorDirs(name) {
|
|
|
55
54
|
out.push(parts.slice(0, i).join('/'));
|
|
56
55
|
return out;
|
|
57
56
|
}
|
|
58
|
-
/** A doc's effective rung
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let rung = doc[surface];
|
|
57
|
+
/** A doc's effective system-prompt rung after applying every ancestor INDEX
|
|
58
|
+
* ceiling. A `none` ancestor hides the whole subtree from the boot catalog. */
|
|
59
|
+
export function effectiveSystemPromptRung(doc, ceil) {
|
|
60
|
+
let rung = doc.systemPromptVisibility;
|
|
63
61
|
for (const dir of ancestorDirs(doc.name)) {
|
|
64
62
|
const idx = ceil.get(dir);
|
|
65
63
|
if (idx)
|
|
66
|
-
rung = minRung(rung, idx
|
|
64
|
+
rung = minRung(rung, idx.systemPromptVisibility);
|
|
67
65
|
}
|
|
68
66
|
return rung;
|
|
69
67
|
}
|
|
@@ -4,8 +4,7 @@ export { scopeForCwd, profileNameFor } from './subject-fields.js';
|
|
|
4
4
|
export type { NodeConfigSubject } from './subject-fields.js';
|
|
5
5
|
export { spineDepth, assembleNodeSubject } from './subject.js';
|
|
6
6
|
export { gatePasses } from './gate.js';
|
|
7
|
-
export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex,
|
|
8
|
-
export type { Surface } from './ceiling.js';
|
|
7
|
+
export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveSystemPromptRung, } from './ceiling.js';
|
|
9
8
|
export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
|
|
10
9
|
export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
|
|
11
|
-
export { renderOnReadDocs } from './on-read.js';
|
|
10
|
+
export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read.js';
|
|
@@ -19,7 +19,7 @@ resolveDocName, } from './schema.js';
|
|
|
19
19
|
export { scopeForCwd, profileNameFor } from './subject-fields.js';
|
|
20
20
|
export { spineDepth, assembleNodeSubject } from './subject.js';
|
|
21
21
|
export { gatePasses } from './gate.js';
|
|
22
|
-
export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex,
|
|
22
|
+
export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveSystemPromptRung, } from './ceiling.js';
|
|
23
23
|
export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
|
|
24
24
|
export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
|
|
25
|
-
export { renderOnReadDocs } from './on-read.js';
|
|
25
|
+
export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read.js';
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Render the substrate docs that should surface alongside a just-read file.
|
|
3
|
-
*
|
|
4
|
-
* @param nodeId the canvas node whose subject gates the docs.
|
|
5
|
-
* @param readFilePath the path the `read` tool returned (absolute or not — it
|
|
6
|
-
* is resolved to a realpath internally).
|
|
7
|
-
* @param seen the CALLER-owned, per-session set of already-injected
|
|
8
|
-
* doc realpaths. Docs already present are skipped; newly
|
|
9
|
-
* injected docs are added. Pass the same set across reads
|
|
10
|
-
* within a session (clear it on session_start) to get the
|
|
11
|
-
* once-per-session dedup; omit it for a standalone render.
|
|
12
|
-
* @returns the `<auto-loaded-context>` envelope, or '' when nothing surfaces.
|
|
13
|
-
*/
|
|
1
|
+
/** Surface docs matched by a successful read tool call. */
|
|
14
2
|
export declare function renderOnReadDocs(nodeId: string, readFilePath: string, seen?: Set<string>): string;
|
|
3
|
+
/** Surface workspace-wide docs during first-message assembly. `.` is a reserved
|
|
4
|
+
* applies-to target meaning “when this project store is mounted by cwd/profile”. */
|
|
5
|
+
export declare function renderWorkspaceOpenDocs(nodeId: string): string;
|