@north-light/crouter 0.3.161 → 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.
Files changed (130) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +6 -0
  3. package/dist/api/dto/modelauth.d.ts +32 -0
  4. package/dist/api/dto/modelauth.js +2 -1
  5. package/dist/api/routes.d.ts +1 -0
  6. package/dist/api/routes.js +1 -0
  7. package/dist/builtin-memory/00-runtime-base.md +1 -0
  8. package/dist/builtin-memory/04-orchestration-kernel.md +1 -0
  9. package/dist/builtin-memory/init.md +38 -0
  10. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +2 -2
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +0 -1
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +88 -396
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +215 -419
  14. package/dist/clients/attach/__tests__/crtr-output.test.js +14 -9
  15. package/dist/clients/attach/__tests__/edit-diff.test.d.ts +1 -0
  16. package/dist/clients/attach/__tests__/edit-diff.test.js +34 -0
  17. package/dist/clients/attach/__tests__/editor-frame-title.test.d.ts +1 -0
  18. package/dist/clients/attach/__tests__/editor-frame-title.test.js +19 -0
  19. package/dist/clients/attach/render/chat-view.js +8 -3
  20. package/dist/clients/attach/render/crtr-output.js +31 -2
  21. package/dist/clients/attach/render/edit-diff.js +26 -2
  22. package/dist/clients/attach/render/tool-calls.d.ts +7 -2
  23. package/dist/clients/attach/render/tool-calls.js +25 -4
  24. package/dist/clients/attach/session/connection.d.ts +1 -1
  25. package/dist/clients/attach/session/editor-frame.d.ts +5 -1
  26. package/dist/clients/attach/session/editor-frame.js +21 -15
  27. package/dist/clients/attach/session/identity.d.ts +1 -1
  28. package/dist/clients/attach/viewer.js +441 -439
  29. package/dist/commands/__tests__/api-canvas-source.test.d.ts +1 -0
  30. package/dist/commands/__tests__/api-canvas-source.test.js +37 -0
  31. package/dist/commands/__tests__/human.test.js +2 -0
  32. package/dist/commands/__tests__/search-contents.test.d.ts +1 -0
  33. package/dist/commands/__tests__/search-contents.test.js +52 -0
  34. package/dist/commands/api-client.js +1 -0
  35. package/dist/commands/human/prompts.js +2 -2
  36. package/dist/commands/human/shared.d.ts +1 -0
  37. package/dist/commands/human/shared.js +7 -4
  38. package/dist/commands/memory/lint.d.ts +6 -0
  39. package/dist/commands/memory/lint.js +177 -35
  40. package/dist/commands/memory/read.js +22 -1
  41. package/dist/commands/memory/write.js +18 -4
  42. package/dist/commands/memory.js +1 -1
  43. package/dist/commands/search/contents.js +2 -2
  44. package/dist/commands/sys/__tests__/sync-deps.test.js +12 -21
  45. package/dist/commands/sys/__tests__/sync-import.test.js +30 -29
  46. package/dist/commands/sys/setup-wizard.d.ts +8 -1
  47. package/dist/commands/sys/setup-wizard.js +209 -58
  48. package/dist/commands/sys/sync-deps.js +14 -18
  49. package/dist/commands/sys/sync-project-guidance.js +16 -19
  50. package/dist/core/__tests__/broker-launch-candidates.test.js +13 -3
  51. package/dist/core/__tests__/broker-sdk-wiring.test.js +53 -25
  52. package/dist/core/__tests__/full/broker-provider-retry.test.js +6 -122
  53. package/dist/core/__tests__/migration.test.js +8 -3
  54. package/dist/core/__tests__/model-pin-durability.test.js +7 -5
  55. package/dist/core/__tests__/model-routes.test.d.ts +1 -0
  56. package/dist/core/__tests__/model-routes.test.js +41 -0
  57. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +7 -10
  58. package/dist/core/__tests__/on-read-dedup-resume.test.js +13 -25
  59. package/dist/core/__tests__/on-read-identity.test.js +8 -15
  60. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  61. package/dist/core/__tests__/revive.test.js +2 -2
  62. package/dist/core/__tests__/tmux-surface.test.js +10 -4
  63. package/dist/core/__tests__/worktree.test.js +3 -3
  64. package/dist/core/auth-file.d.ts +3 -2
  65. package/dist/core/auth-file.js +5 -3
  66. package/dist/core/canvas/extensions.d.ts +1 -1
  67. package/dist/core/canvas/extensions.js +18 -11
  68. package/dist/core/canvas/labels.d.ts +4 -5
  69. package/dist/core/canvas/labels.js +9 -9
  70. package/dist/core/canvas/types.d.ts +6 -1
  71. package/dist/core/command-manifests/schema.js +18 -5
  72. package/dist/core/command.js +17 -7
  73. package/dist/core/config.d.ts +3 -1
  74. package/dist/core/config.js +88 -2
  75. package/dist/core/configured-clis/invoker.js +2 -0
  76. package/dist/core/help.d.ts +3 -1
  77. package/dist/core/help.js +5 -2
  78. package/dist/core/keybindings/__tests__/resolve.test.js +5 -2
  79. package/dist/core/keybindings/catalog.d.ts +3 -2
  80. package/dist/core/keybindings/catalog.js +34 -25
  81. package/dist/core/keybindings/index.d.ts +1 -1
  82. package/dist/core/keybindings/resolve.js +8 -0
  83. package/dist/core/keybindings/types.d.ts +6 -1
  84. package/dist/core/memory/doc-link-grammar.d.ts +20 -0
  85. package/dist/core/memory/doc-link-grammar.js +110 -0
  86. package/dist/core/memory-resolver.d.ts +5 -0
  87. package/dist/core/memory-resolver.js +12 -1
  88. package/dist/core/model-routes.d.ts +37 -0
  89. package/dist/core/model-routes.js +140 -0
  90. package/dist/core/runtime/bearings.d.ts +8 -8
  91. package/dist/core/runtime/bearings.js +20 -16
  92. package/dist/core/runtime/broker.d.ts +0 -4
  93. package/dist/core/runtime/broker.js +119 -385
  94. package/dist/core/runtime/canvas-extensions.d.ts +1 -0
  95. package/dist/core/runtime/canvas-extensions.js +2 -0
  96. package/dist/core/runtime/launch.d.ts +10 -12
  97. package/dist/core/runtime/launch.js +16 -22
  98. package/dist/core/runtime/managed-provider-cooling.d.ts +0 -14
  99. package/dist/core/runtime/managed-provider-cooling.js +0 -19
  100. package/dist/core/runtime/model-swap.js +7 -1
  101. package/dist/core/runtime/promote.js +1 -0
  102. package/dist/core/runtime/tmux.js +136 -100
  103. package/dist/core/scope.js +4 -5
  104. package/dist/core/subscription-state.d.ts +12 -36
  105. package/dist/core/subscription-state.js +19 -203
  106. package/dist/core/substrate/ceiling.d.ts +3 -6
  107. package/dist/core/substrate/ceiling.js +13 -15
  108. package/dist/core/substrate/index.d.ts +2 -3
  109. package/dist/core/substrate/index.js +2 -2
  110. package/dist/core/substrate/on-read.d.ts +4 -13
  111. package/dist/core/substrate/on-read.js +142 -262
  112. package/dist/core/substrate/render.js +4 -4
  113. package/dist/core/substrate/schema.d.ts +3 -2
  114. package/dist/core/substrate/schema.js +5 -5
  115. package/dist/daemon/api/__tests__/full/api-server.test.js +9 -6
  116. package/dist/daemon/api/__tests__/full/b10-attach-modelauth.test.js +123 -1
  117. package/dist/daemon/api/handlers/modelauth.js +51 -2
  118. package/dist/daemon/api/handlers/nodes.js +2 -0
  119. package/dist/pi-extensions/__tests__/canvas-tool-guide.test.d.ts +1 -0
  120. package/dist/pi-extensions/__tests__/canvas-tool-guide.test.js +96 -0
  121. package/dist/pi-extensions/canvas-doc-substrate.js +7 -8
  122. package/dist/pi-extensions/canvas-tool-guide.d.ts +14 -0
  123. package/dist/pi-extensions/canvas-tool-guide.js +74 -0
  124. package/dist/types.d.ts +17 -0
  125. package/dist/web-client/assets/{index-B76ZKfT_.js → index-NIuSCOHM.js} +1 -1
  126. package/dist/web-client/index.html +1 -1
  127. package/dist/web-client/sw.js +1 -1
  128. package/package.json +1 -1
  129. package/runtime.lock.json +2 -2
  130. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +0 -95
@@ -1,5 +1,6 @@
1
1
  import type { BindingDefinition, SafetyRequirement } from './types.js';
2
2
  type TmuxMenuAction = 'open' | 'show' | 'view-settings' | 'fold-tools' | 'metadata' | 'promote' | 'resume' | 'fork' | 'demote' | 'detach' | 'close-subtree' | 'background-bash' | 'file-review' | 'pin' | 'reload-viewer' | 'context' | 'providers' | 'graph' | 'focus-manager' | 'copy-id' | 'issues';
3
+ type TmuxMenuEntryAction = Exclude<TmuxMenuAction, 'open'> | `issue.${'idea' | 'bug'}`;
3
4
  type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'revive' | 'focus-manager' | 'close-subtree' | 'dismiss';
4
5
  type InboxAction = 'down' | 'up' | 'open' | 'reply' | 'react' | 'filter' | 'refresh' | 'connect-linkedin' | 'connect-gmail' | 'quit' | 'submit-reply' | 'cancel' | 'reaction-previous' | 'reaction-next' | 'submit-reaction';
5
6
  type LinkedinAction = Exclude<InboxAction, 'filter' | 'connect-linkedin' | 'connect-gmail'>;
@@ -8,8 +9,8 @@ type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' |
8
9
  type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance' | 'cell-previous' | 'cell-next';
9
10
  type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
10
11
  type SetupModelsAction = 'open' | 'commit' | 'cancel' | 'scope-next';
11
- export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | `crtr.tmux.node.${'next' | 'previous'}` | `crtr.tmux.view.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.${'detach' | 'clear-or-detach' | 'graph.toggle' | 'model-ladder.next' | 'model-ladder.previous' | 'command.inspect' | 'file-review'}` | 'crtr.mode.cycle' | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.view.host.${'force-quit' | 'editor.backspace'}` | `crtr.view.canvas.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.chat.${'submit' | 'contextual-cancel' | 'reconnect' | 'quit'}` | `crtr.view.git-pr.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.inbox.${InboxAction}` | `crtr.view.linkedin.${LinkedinAction}` | `crtr.view.prompt-review.${'down' | 'up' | 'compare-previous' | 'compare-next' | 'toggle-mode' | 'expand' | 'export' | 'refresh' | 'quit'}` | `crtr.view.workspace-sidebar.${'down' | 'up' | 'open' | 'refresh' | 'quit'}` | `crtr.view.inspector.${'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.view-picker.${'down' | 'up' | 'run' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
12
- export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.promote">, BindingDefinition<"crtr.tmux.menu.resume">, BindingDefinition<"crtr.tmux.menu.fork">, BindingDefinition<"crtr.tmux.menu.demote">, BindingDefinition<"crtr.tmux.menu.detach">, BindingDefinition<"crtr.tmux.menu.close-subtree">, BindingDefinition<"crtr.tmux.menu.background-bash">, BindingDefinition<"crtr.tmux.menu.file-review">, BindingDefinition<"crtr.tmux.menu.pin">, BindingDefinition<"crtr.tmux.menu.show">, BindingDefinition<"crtr.tmux.menu.view-settings">, BindingDefinition<"crtr.tmux.menu.fold-tools">, BindingDefinition<"crtr.tmux.menu.metadata">, BindingDefinition<"crtr.tmux.menu.context">, BindingDefinition<"crtr.tmux.menu.providers">, BindingDefinition<"crtr.tmux.menu.graph">, BindingDefinition<"crtr.tmux.menu.focus-manager">, BindingDefinition<"crtr.tmux.menu.copy-id">, BindingDefinition<"crtr.tmux.menu.reload-viewer">, BindingDefinition<"crtr.tmux.menu.issues">, BindingDefinition<"crtr.tmux.menu.issue.idea">, BindingDefinition<"crtr.tmux.menu.issue.bug">, BindingDefinition<"crtr.tmux.node.next">, BindingDefinition<"crtr.tmux.node.previous">, BindingDefinition<"crtr.tmux.inbox.toggle">, BindingDefinition<"crtr.tmux.view.next">, BindingDefinition<"crtr.tmux.view.previous">, BindingDefinition<"crtr.attach.detach">, BindingDefinition<"crtr.attach.clear-or-detach">, BindingDefinition<"crtr.attach.graph.toggle">, BindingDefinition<"crtr.attach.model-ladder.next">, BindingDefinition<"crtr.attach.model-ladder.previous">, BindingDefinition<"crtr.attach.command.inspect">, BindingDefinition<"crtr.attach.file-review">, BindingDefinition<"crtr.mode.cycle">, BindingDefinition<"crtr.graph.down">, BindingDefinition<"crtr.graph.up">, BindingDefinition<"crtr.graph.first">, BindingDefinition<"crtr.graph.last">, BindingDefinition<"crtr.graph.collapse-or-manager">, BindingDefinition<"crtr.graph.expand-or-child">, BindingDefinition<"crtr.graph.focus">, BindingDefinition<"crtr.graph.revive">, BindingDefinition<"crtr.graph.focus-manager">, BindingDefinition<"crtr.graph.close-subtree">, BindingDefinition<"crtr.graph.dismiss">, BindingDefinition<"crtr.graph.confirm.accept">, BindingDefinition<"crtr.graph.confirm.cancel">, BindingDefinition<"crtr.view.host.force-quit">, BindingDefinition<"crtr.view.host.editor.backspace">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<"crtr.view.chat.submit">, BindingDefinition<"crtr.view.chat.contextual-cancel">, BindingDefinition<"crtr.view.chat.reconnect">, BindingDefinition<"crtr.view.chat.quit">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view.inspector.down"> | BindingDefinition<"crtr.view.inspector.up"> | BindingDefinition<"crtr.view.inspector.section-next"> | BindingDefinition<"crtr.view.inspector.section-prev"> | BindingDefinition<"crtr.view.inspector.page-down"> | BindingDefinition<"crtr.view.inspector.page-up"> | BindingDefinition<"crtr.view.inspector.toggle-detail"> | BindingDefinition<"crtr.view.inspector.cancel-item"> | BindingDefinition<"crtr.view.inspector.open-log"> | BindingDefinition<"crtr.view.inspector.confirm"> | BindingDefinition<"crtr.view.inspector.dismiss"> | BindingDefinition<"crtr.view.inspector.retarget"> | BindingDefinition<"crtr.view.inspector.refresh"> | BindingDefinition<"crtr.view.inspector.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.9" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.6">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
12
+ export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | `crtr.tmux.view.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.${'detach' | 'clear-or-detach' | 'graph.toggle' | 'model-ladder.next' | 'model-ladder.previous' | 'command.inspect' | 'file-review'}` | 'crtr.mode.cycle' | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.view.host.${'force-quit' | 'editor.backspace'}` | `crtr.view.canvas.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.chat.${'submit' | 'contextual-cancel' | 'reconnect' | 'quit'}` | `crtr.view.git-pr.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.inbox.${InboxAction}` | `crtr.view.linkedin.${LinkedinAction}` | `crtr.view.prompt-review.${'down' | 'up' | 'compare-previous' | 'compare-next' | 'toggle-mode' | 'expand' | 'export' | 'refresh' | 'quit'}` | `crtr.view.workspace-sidebar.${'down' | 'up' | 'open' | 'refresh' | 'quit'}` | `crtr.view.inspector.${'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.view-picker.${'down' | 'up' | 'run' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
13
+ export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.tmux.view.next"> | BindingDefinition<"crtr.tmux.view.previous"> | BindingDefinition<"crtr.attach.detach"> | BindingDefinition<"crtr.attach.clear-or-detach"> | BindingDefinition<"crtr.attach.graph.toggle"> | BindingDefinition<"crtr.attach.model-ladder.next"> | BindingDefinition<"crtr.attach.model-ladder.previous"> | BindingDefinition<"crtr.attach.command.inspect"> | BindingDefinition<"crtr.attach.file-review"> | BindingDefinition<"crtr.mode.cycle"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.view.host.force-quit"> | BindingDefinition<"crtr.view.host.editor.backspace"> | BindingDefinition<"crtr.view.chat.submit"> | BindingDefinition<"crtr.view.chat.contextual-cancel"> | BindingDefinition<"crtr.view.chat.reconnect"> | BindingDefinition<"crtr.view.chat.quit"> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view.inspector.down"> | BindingDefinition<"crtr.view.inspector.up"> | BindingDefinition<"crtr.view.inspector.section-next"> | BindingDefinition<"crtr.view.inspector.section-prev"> | BindingDefinition<"crtr.view.inspector.page-down"> | BindingDefinition<"crtr.view.inspector.page-up"> | BindingDefinition<"crtr.view.inspector.toggle-detail"> | BindingDefinition<"crtr.view.inspector.cancel-item"> | BindingDefinition<"crtr.view.inspector.open-log"> | BindingDefinition<"crtr.view.inspector.confirm"> | BindingDefinition<"crtr.view.inspector.dismiss"> | BindingDefinition<"crtr.view.inspector.retarget"> | BindingDefinition<"crtr.view.inspector.refresh"> | BindingDefinition<"crtr.view.inspector.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.9" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.6">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
13
14
  export declare const BINDING_IDS: ReadonlySet<string>;
14
15
  export declare const SAFETY_REQUIREMENTS: readonly SafetyRequirement[];
15
16
  export {};
@@ -1,4 +1,4 @@
1
- function binding(id, group, name, description, defaults, inputKind, contexts, safety) {
1
+ function binding(id, group, name, description, defaults, inputKind, contexts, safety, pair) {
2
2
  return Object.freeze({
3
3
  id,
4
4
  group,
@@ -8,9 +8,18 @@ function binding(id, group, name, description, defaults, inputKind, contexts, sa
8
8
  inputKind,
9
9
  contexts: Object.freeze([...contexts]),
10
10
  ...(safety === undefined ? {} : { safety }),
11
+ ...(pair === undefined ? {} : { pair: Object.freeze({ ...pair }) }),
11
12
  });
12
13
  }
13
14
  const b = binding;
15
+ function menuEntry(action, name, description, defaults, contexts) {
16
+ const menuId = `crtr.tmux.menu.${action}`;
17
+ const globalId = `crtr.tmux.menu-global.${action}`;
18
+ return [
19
+ b(menuId, 'Global tmux', name, description, defaults, 'tmux-menu-selector', contexts, undefined, { field: 'menu', pairedId: globalId }),
20
+ b(globalId, 'Global tmux', name, description, [], 'tmux-global-action', ['tmux.root'], undefined, { field: 'global', pairedId: menuId }),
21
+ ];
22
+ }
14
23
  const ATTACH_CONTEXTS = ['attach', 'graph', 'graph.confirm'];
15
24
  const VIEW_CONTEXTS = [
16
25
  'view.canvas', 'view.chat', 'view.git-pr',
@@ -27,28 +36,28 @@ const SETUP_CANCEL_CONTEXTS = [
27
36
  const SETUP_TAB_CONTEXTS = ['setup.login', 'setup.list', 'setup.keybindings', 'setup.models'];
28
37
  export const BINDING_CATALOG = Object.freeze([
29
38
  b('crtr.tmux.menu.open', 'Global tmux', 'Open crouter menu', 'Open the crouter action menu.', ['alt+c'], 'host-global', ['tmux.root']),
30
- b('crtr.tmux.menu.promote', 'Global tmux', 'Promote node', 'Promote the focused node to orchestrator.', ['o'], 'tmux-menu-selector', ['tmux.menu']),
31
- b('crtr.tmux.menu.resume', 'Global tmux', 'Resume node', 'Open the node resume picker.', ['r'], 'tmux-menu-selector', ['tmux.menu']),
32
- b('crtr.tmux.menu.fork', 'Global tmux', 'Fork node', 'Fork the focused node into an independent idle root.', ['f'], 'tmux-menu-selector', ['tmux.menu']),
33
- b('crtr.tmux.menu.demote', 'Global tmux', 'Demote node', 'Demote the focused node to terminal.', ['d'], 'tmux-menu-selector', ['tmux.menu']),
34
- b('crtr.tmux.menu.detach', 'Global tmux', 'Detach node', 'Detach the focused node to the background.', ['shift+d'], 'tmux-menu-selector', ['tmux.menu']),
35
- b('crtr.tmux.menu.close-subtree', 'Global tmux', 'Close subtree', 'Close the focused node and its subtree.', ['x'], 'tmux-menu-selector', ['tmux.menu']),
36
- b('crtr.tmux.menu.background-bash', 'Global tmux', 'Background bash', 'Hand the focused node’s running bash command to its background job system.', ['b'], 'tmux-menu-selector', ['tmux.menu']),
37
- b('crtr.tmux.menu.file-review', 'Global tmux', 'Review a file', 'Open a line-anchored review of a file named in the transcript.', ['v'], 'tmux-menu-selector', ['tmux.menu']),
38
- b('crtr.tmux.menu.pin', 'Global tmux', 'Pin conversation', 'Pin or unpin this conversation in the canvas navigator.', ['shift+f'], 'tmux-menu-selector', ['tmux.menu']),
39
- b('crtr.tmux.menu.show', 'Global tmux', 'Show node', 'Open the focused node’s inspection menu.', ['s'], 'tmux-menu-selector', ['tmux.menu']),
40
- b('crtr.tmux.menu.view-settings', 'Global tmux', 'View settings', 'Open the viewer display-settings menu.', ['w'], 'tmux-menu-selector', ['tmux.menu']),
41
- b('crtr.tmux.menu.fold-tools', 'Global tmux', 'Fold finished tool calls', 'Fold every finished tool call in the transcript to its single call line.', ['f'], 'tmux-menu-selector', ['tmux.menu.view-settings']),
42
- b('crtr.tmux.menu.metadata', 'Global tmux', 'Show metadata', 'Show the focused node’s metadata dossier.', ['m'], 'tmux-menu-selector', ['tmux.menu.show']),
43
- b('crtr.tmux.menu.context', 'Global tmux', 'Browse context', 'Browse the focused node context.', ['c'], 'tmux-menu-selector', ['tmux.menu.show']),
44
- b('crtr.tmux.menu.providers', 'Global tmux', 'Open settings', 'Open the crouter settings panel.', ['p'], 'tmux-menu-selector', ['tmux.menu']),
45
- b('crtr.tmux.menu.graph', 'Global tmux', 'Open graph', 'Open the canvas graph.', ['g'], 'tmux-menu-selector', ['tmux.menu']),
46
- b('crtr.tmux.menu.focus-manager', 'Global tmux', 'Focus manager', 'Focus the node manager.', ['m'], 'tmux-menu-selector', ['tmux.menu']),
47
- b('crtr.tmux.menu.copy-id', 'Global tmux', 'Copy node id', 'Copy the focused node id to the system clipboard.', ['y'], 'tmux-menu-selector', ['tmux.menu']),
48
- b('crtr.tmux.menu.reload-viewer', 'Global tmux', 'Reload viewer', 'Respawn this pane’s viewer onto the current runtime generation.', ['l'], 'tmux-menu-selector', ['tmux.menu']),
49
- b('crtr.tmux.menu.issues', 'Global tmux', 'Open issue menu', 'Open the issue type submenu.', ['i'], 'tmux-menu-selector', ['tmux.menu']),
50
- b('crtr.tmux.menu.issue.idea', 'Global tmux', 'Create idea', 'Create an idea issue.', ['i'], 'tmux-menu-selector', ['tmux.menu.issue']),
51
- b('crtr.tmux.menu.issue.bug', 'Global tmux', 'Create bug', 'Create a bug issue.', ['b'], 'tmux-menu-selector', ['tmux.menu.issue']),
39
+ ...menuEntry('promote', 'Promote node', 'Promote the focused node to orchestrator.', ['o'], ['tmux.menu']),
40
+ ...menuEntry('resume', 'Resume node', 'Open the node resume picker.', ['r'], ['tmux.menu']),
41
+ ...menuEntry('fork', 'Fork node', 'Fork the focused node into an independent idle root.', ['f'], ['tmux.menu']),
42
+ ...menuEntry('demote', 'Demote node', 'Demote the focused node to terminal.', ['d'], ['tmux.menu']),
43
+ ...menuEntry('detach', 'Detach node', 'Detach the focused node to the background.', ['shift+d'], ['tmux.menu']),
44
+ ...menuEntry('close-subtree', 'Close subtree', 'Close the focused node and its subtree.', ['x'], ['tmux.menu']),
45
+ ...menuEntry('background-bash', 'Background bash', 'Hand the focused node’s running bash command to its background job system.', ['b'], ['tmux.menu']),
46
+ ...menuEntry('file-review', 'Review a file', 'Open a line-anchored review of a file named in the transcript.', ['v'], ['tmux.menu']),
47
+ ...menuEntry('pin', 'Pin conversation', 'Pin or unpin this conversation in the canvas navigator.', ['shift+f'], ['tmux.menu']),
48
+ ...menuEntry('show', 'Show node', 'Open the focused node’s inspection menu.', ['s'], ['tmux.menu']),
49
+ ...menuEntry('view-settings', 'View settings', 'Open the viewer display-settings menu.', ['w'], ['tmux.menu']),
50
+ ...menuEntry('fold-tools', 'Fold finished tool calls', 'Fold every finished tool call in the transcript to its single call line.', ['f'], ['tmux.menu.view-settings']),
51
+ ...menuEntry('metadata', 'Show metadata', 'Show the focused node’s metadata dossier.', ['m'], ['tmux.menu.show']),
52
+ ...menuEntry('context', 'Browse context', 'Browse the focused node context.', ['c'], ['tmux.menu.show']),
53
+ ...menuEntry('providers', 'Open settings', 'Open the crouter settings panel.', ['p'], ['tmux.menu']),
54
+ ...menuEntry('graph', 'Open graph', 'Open the canvas graph.', ['g'], ['tmux.menu']),
55
+ ...menuEntry('focus-manager', 'Focus manager', 'Focus the node manager.', ['m'], ['tmux.menu']),
56
+ ...menuEntry('copy-id', 'Copy node id', 'Copy the focused node id to the system clipboard.', ['y'], ['tmux.menu']),
57
+ ...menuEntry('reload-viewer', 'Reload viewer', 'Respawn this pane’s viewer onto the current runtime generation.', ['l'], ['tmux.menu']),
58
+ ...menuEntry('issues', 'Open issue menu', 'Open the issue type submenu.', ['i'], ['tmux.menu']),
59
+ ...menuEntry('issue.idea', 'Create idea', 'Create an idea issue.', ['i'], ['tmux.menu.issue']),
60
+ ...menuEntry('issue.bug', 'Create bug', 'Create a bug issue.', ['b'], ['tmux.menu.issue']),
52
61
  b('crtr.tmux.node.next', 'Global tmux', 'Next node', 'Focus the next canvas node.', ['alt+]'], 'host-global', ['tmux.root']),
53
62
  b('crtr.tmux.node.previous', 'Global tmux', 'Previous node', 'Focus the previous canvas node.', ['alt+['], 'host-global', ['tmux.root']),
54
63
  b('crtr.tmux.inbox.toggle', 'Global tmux', 'Toggle inbox', 'Open or close the human inbox popup.', ['alt+i'], 'host-global', ['tmux.root']),
@@ -244,8 +253,8 @@ function setupBindings() {
244
253
  b('crtr.setup.keybindings.filter', 'Setup', 'Filter bindings', 'Open the keybinding filter.', ['/'], 'terminal', ['setup.keybindings']),
245
254
  b('crtr.setup.keybindings.filter-delete', 'Setup', 'Delete filter text', 'Delete the previous filter character.', ['backspace'], 'terminal', ['setup.keybindings.filter']),
246
255
  b('crtr.setup.keybindings.filter-clear', 'Setup', 'Clear filter', 'Clear the keybinding filter.', ['escape'], 'terminal', ['setup.keybindings.filter'], 'dismiss-modal'),
247
- b('crtr.setup.cell-previous', 'Setup', 'Previous cell', 'Select the previous cell (strength-ladder provider column or a kind base/orchestrator provider/strength column).', ['h'], 'terminal', ['setup.models']),
248
- b('crtr.setup.cell-next', 'Setup', 'Next cell', 'Select the next cell (strength-ladder provider column or a kind base/orchestrator provider/strength column).', ['l'], 'terminal', ['setup.models']),
256
+ b('crtr.setup.cell-previous', 'Setup', 'Previous field', 'Select the previous field or setup table cell.', ['h'], 'terminal', ['setup.models', 'setup.keybindings']),
257
+ b('crtr.setup.cell-next', 'Setup', 'Next field', 'Select the next field or setup table cell.', ['l'], 'terminal', ['setup.models', 'setup.keybindings']),
249
258
  b('crtr.setup.models.open', 'Setup', 'Edit model', 'Edit the selected model cell.', ['enter'], 'terminal', ['setup.models']),
250
259
  b('crtr.setup.models.scope-next', 'Setup', 'Next model config scope', 'Edit the next applicable user, profile, or project config.', ['s'], 'terminal', ['setup.models']),
251
260
  b('crtr.setup.models.commit', 'Setup', 'Commit pick', 'Commit the provider, strength, model, or thinking-level pick.', ['enter'], 'terminal', ['setup.models.picker', 'setup.models.dimension-picker']),
@@ -4,4 +4,4 @@ export { canonicalTerminalStroke, formatBinding, formatGesture, formatStroke, ma
4
4
  export { inboxOpenHint, inboxOpenInstruction, inboxPopupHint, inboxShortcut, } from './inbox.js';
5
5
  export { backgroundBashHint, backgroundBashShortcut, } from './background-bash.js';
6
6
  export { KeybindingConcurrentEditError, persistUserKeybindings, type PersistedKeybindings, type PersistKeybindingsOptions, } from './persistence.js';
7
- export type { BindingDefinition, BindingInputKind, BindingResolution, BindingSafetyCapability, EffectiveBinding, KeybindingDiagnostic, KeybindingDiagnosticCode, SafetyRequirement, SparseKeybindingOverrides, } from './types.js';
7
+ export type { BindingDefinition, BindingInputKind, BindingPair, BindingResolution, BindingSafetyCapability, EffectiveBinding, KeybindingDiagnostic, KeybindingDiagnosticCode, SafetyRequirement, SparseKeybindingOverrides, } from './types.js';
@@ -121,6 +121,14 @@ export function normalizeGesture(raw, definition) {
121
121
  throw new GestureSyntaxError('a menu selector must be one printable character');
122
122
  }
123
123
  }
124
+ if (definition?.inputKind === 'tmux-global-action') {
125
+ if (normalized.length !== 1)
126
+ throw new GestureSyntaxError('a global menu shortcut must be one stroke');
127
+ const modifiers = normalized[0].split('+').slice(0, -1);
128
+ if (!modifiers.includes('ctrl') && !modifiers.includes('alt')) {
129
+ throw new GestureSyntaxError('a global menu shortcut must include Ctrl or Alt');
130
+ }
131
+ }
124
132
  return normalized.join(' > ');
125
133
  }
126
134
  function normalizeList(raw, definition) {
@@ -1,4 +1,8 @@
1
- export type BindingInputKind = 'terminal' | 'tmux-sequence' | 'tmux-menu-selector' | 'host-global';
1
+ export type BindingInputKind = 'terminal' | 'tmux-sequence' | 'tmux-menu-selector' | 'tmux-global-action' | 'host-global';
2
+ export interface BindingPair {
3
+ readonly field: 'menu' | 'global';
4
+ readonly pairedId: string;
5
+ }
2
6
  export type BindingSafetyCapability = 'dismiss-modal' | 'leave-screen' | 'save-keybindings';
3
7
  export interface BindingDefinition<Id extends string = string> {
4
8
  readonly id: Id;
@@ -9,6 +13,7 @@ export interface BindingDefinition<Id extends string = string> {
9
13
  readonly inputKind: BindingInputKind;
10
14
  readonly contexts: readonly string[];
11
15
  readonly safety?: BindingSafetyCapability;
16
+ readonly pair?: BindingPair;
12
17
  }
13
18
  export interface EffectiveBinding<Id extends string = string> {
14
19
  readonly definition: BindingDefinition<Id>;
@@ -0,0 +1,20 @@
1
+ /** One `[[name]]` link occurrence found in a document body. */
2
+ export interface DocLink {
3
+ /** Character index of the first `[` of `[[`. */
4
+ start: number;
5
+ /** Character index just past the closing `]]`. */
6
+ end: number;
7
+ /** The canonical document name between the brackets. */
8
+ name: string;
9
+ }
10
+ /** Is `name` a well-formed canonical doc-link name? */
11
+ export declare function isDocLinkName(name: string): boolean;
12
+ /**
13
+ * Find every `[[name]]` doc link in a markdown body, in order of appearance,
14
+ * skipping fenced code blocks and inline code spans (a link mentioned inside
15
+ * code is an example, not a reference). Occurrences are NOT deduplicated —
16
+ * callers collapse as needed.
17
+ */
18
+ export declare function findDocLinks(body: string): DocLink[];
19
+ /** The distinct link names in a body, first-appearance order. */
20
+ export declare function docLinkNames(body: string): string[];
@@ -0,0 +1,110 @@
1
+ // doc-link-grammar.ts — the single source of truth for the `[[canonical/name]]`
2
+ // memory-document link grammar. Zero imports, pure string ops, browser-safe —
3
+ // any surface that highlights, resolves, or lints doc links imports THIS module
4
+ // rather than re-implementing the bracket scan or the name shape.
5
+ //
6
+ // A doc link is a durable cross-reference written INSIDE a memory document's
7
+ // body, pointing at another memory document by its exact canonical name (the
8
+ // same identifier `crtr memory read <name>` takes; a directory INDEX is linked
9
+ // by its folded bare-directory name). It is distinct from the interactive
10
+ // `/name` inline-reference grammar (`inline-ref-grammar.ts`), which scans a
11
+ // human's live composer text — links live in stored markdown and survive there.
12
+ //
13
+ // Links are POINTERS, never transclusion: nothing in the runtime auto-loads a
14
+ // linked body into context. `crtr memory lint` fails a link that resolves to no
15
+ // document; `crtr memory read` surfaces the resolved links of the doc it loads.
16
+ //
17
+ // This module never resolves anything: `findDocLinks` returns candidate links;
18
+ // a caller decides a link is live by testing `name` against the corpus of
19
+ // resolvable canonical names.
20
+ // A canonical doc name: one or more `[A-Za-z0-9_-]+` segments joined by `/`.
21
+ // Anything else between `[[` `]]` (spaces, `|` aliases, empty segments) is NOT
22
+ // a doc link — the grammar has no alias/label form, by design.
23
+ const NAME_RE = /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/;
24
+ const LINK_RE = /\[\[([^\[\]\n]+)\]\]/g;
25
+ /** Is `name` a well-formed canonical doc-link name? */
26
+ export function isDocLinkName(name) {
27
+ return NAME_RE.test(name);
28
+ }
29
+ /**
30
+ * Find every `[[name]]` doc link in a markdown body, in order of appearance,
31
+ * skipping fenced code blocks and inline code spans (a link mentioned inside
32
+ * code is an example, not a reference). Occurrences are NOT deduplicated —
33
+ * callers collapse as needed.
34
+ */
35
+ export function findDocLinks(body) {
36
+ const links = [];
37
+ for (const segment of proseSegments(body)) {
38
+ LINK_RE.lastIndex = 0;
39
+ let m;
40
+ while ((m = LINK_RE.exec(segment.text)) !== null) {
41
+ const name = m[1];
42
+ if (!isDocLinkName(name))
43
+ continue;
44
+ links.push({ start: segment.offset + m.index, end: segment.offset + m.index + m[0].length, name });
45
+ }
46
+ }
47
+ return links;
48
+ }
49
+ /** The distinct link names in a body, first-appearance order. */
50
+ export function docLinkNames(body) {
51
+ const seen = new Set();
52
+ const out = [];
53
+ for (const link of findDocLinks(body)) {
54
+ if (seen.has(link.name))
55
+ continue;
56
+ seen.add(link.name);
57
+ out.push(link.name);
58
+ }
59
+ return out;
60
+ }
61
+ const FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
62
+ function* proseSegments(body) {
63
+ let offset = 0;
64
+ let fence = null; // the opening fence marker while inside a block
65
+ for (const line of body.split('\n')) {
66
+ const lineLen = line.length + 1; // + '\n' (harmless overcount on last line)
67
+ const fenceMatch = line.match(FENCE_RE);
68
+ if (fence !== null) {
69
+ // Inside a fenced block: only a closing fence of the same char and at
70
+ // least the same length ends it. Nothing in here is prose.
71
+ if (fenceMatch && fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) {
72
+ fence = null;
73
+ }
74
+ offset += lineLen;
75
+ continue;
76
+ }
77
+ if (fenceMatch) {
78
+ fence = fenceMatch[1];
79
+ offset += lineLen;
80
+ continue;
81
+ }
82
+ // Prose line: split around inline code spans (single-backtick pairs).
83
+ yield* proseParts(line, offset);
84
+ offset += lineLen;
85
+ }
86
+ }
87
+ function* proseParts(line, offset) {
88
+ let i = 0;
89
+ while (i < line.length) {
90
+ const open = line.indexOf('`', i);
91
+ if (open === -1) {
92
+ yield { offset: offset + i, text: line.slice(i) };
93
+ return;
94
+ }
95
+ // Backtick run: an inline span is delimited by runs of equal length.
96
+ let runEnd = open;
97
+ while (runEnd < line.length && line[runEnd] === '`')
98
+ runEnd++;
99
+ const run = line.slice(open, runEnd);
100
+ const close = line.indexOf(run, runEnd);
101
+ if (close === -1) {
102
+ // Unbalanced backticks: treat the rest as prose past the run.
103
+ yield { offset: offset + i, text: line.slice(i, open) };
104
+ i = runEnd;
105
+ continue;
106
+ }
107
+ yield { offset: offset + i, text: line.slice(i, open) };
108
+ i = close + run.length;
109
+ }
110
+ }
@@ -70,6 +70,11 @@ export declare function listMemoryDocs(scope: MemoryScope, quiet?: boolean): Mem
70
70
  * doc's name exactly as `listMemoryDocs` does (path-relative, no extension,
71
71
  * slash-separated) then prefixing the plugin name. Builtin has no plugins. */
72
72
  export declare function listPluginMemoryDocs(plugin: InstalledPlugin, scope: MemoryScope, quiet?: boolean): MemoryDoc[];
73
+ /** All project-scoped docs visible from an explicit node workspace/profile.
74
+ * This target-addressed form is used when one process serves several nodes
75
+ * (notably warm-spare claims), where ambient cwd/profile belong to the host
76
+ * process rather than the node whose first-message context is being built. */
77
+ export declare function listProjectMemoryDocs(startDir?: string, profileId?: string | null, quiet?: boolean): MemoryDoc[];
73
78
  /** All memory docs across the resolved sources, in precedence order: each
74
79
  * ancestor project `.crouter/` from nearest to farthest, then the selected
75
80
  * profile's memory (if any), then user, then builtin. Within each source,
@@ -158,7 +158,7 @@ function listMemoryDocsInDir(scope, dir, quiet = false) {
158
158
  * through `loadProfileManifest`; user and builtin are singleton stores. */
159
159
  export function listMemoryDocs(scope, quiet = false) {
160
160
  if (scope === 'project') {
161
- return projectScopeRoots().flatMap((root) => listMemoryDocsInDir('project', join(root, 'memory'), quiet));
161
+ return listProjectMemoryDocs(process.cwd(), selectedProfileId() || null, quiet);
162
162
  }
163
163
  if (scope === 'profile') {
164
164
  const profileId = selectedProfileId();
@@ -215,6 +215,17 @@ function sourceMemoryDocs(source, quiet = false) {
215
215
  ...source.plugins.flatMap((p) => listPluginMemoryDocs(p, source.scope, quiet)),
216
216
  ];
217
217
  }
218
+ /** All project-scoped docs visible from an explicit node workspace/profile.
219
+ * This target-addressed form is used when one process serves several nodes
220
+ * (notably warm-spare claims), where ambient cwd/profile belong to the host
221
+ * process rather than the node whose first-message context is being built. */
222
+ export function listProjectMemoryDocs(startDir = process.cwd(), profileId = selectedProfileId() || null, quiet = false) {
223
+ return projectScopeRoots(startDir, profileId).flatMap((root) => sourceMemoryDocs({
224
+ scope: 'project',
225
+ memoryDir: join(root, 'memory'),
226
+ plugins: listInstalledPluginsInRoot('project', root).filter((plugin) => plugin.enabled),
227
+ }, quiet));
228
+ }
218
229
  /** All memory docs across the resolved sources, in precedence order: each
219
230
  * ancestor project `.crouter/` from nearest to farthest, then the selected
220
231
  * profile's memory (if any), then user, then builtin. Within each source,
@@ -0,0 +1,37 @@
1
+ import type { ModelStrength } from '../types.js';
2
+ export type ModelRequest = {
3
+ kind: 'logical';
4
+ family: string;
5
+ strength: ModelStrength;
6
+ spec: string;
7
+ } | {
8
+ kind: 'concrete';
9
+ spec: string;
10
+ } | {
11
+ kind: 'exact';
12
+ spec: string;
13
+ };
14
+ export type RoutedCandidate = {
15
+ routeId: string;
16
+ credentialSource: string;
17
+ family: string;
18
+ strength: ModelStrength;
19
+ providerId: string;
20
+ modelId: string;
21
+ thinkingLevel?: string;
22
+ rank: number;
23
+ };
24
+ export type RouteAvailability = {
25
+ authenticated: boolean;
26
+ coolingUntil?: number;
27
+ registered: boolean;
28
+ };
29
+ export interface ModelRegistryLike {
30
+ find(providerId: string, modelId: string): unknown;
31
+ hasConfiguredAuth(model: unknown): boolean;
32
+ }
33
+ /** Expand config-derived route order only. Availability never affects rank. */
34
+ export declare function expandModelCandidates(request: ModelRequest, cwd?: string, profileId?: string | null): RoutedCandidate[];
35
+ /** Probe the common availability facts consumed unchanged by resolveProviderCandidates. */
36
+ export declare function probeRouteAvailability(candidate: RoutedCandidate, registry: ModelRegistryLike, now?: number): RouteAvailability;
37
+ export declare function modelRequestFromConfig(model: string | undefined, modelIntent: string | undefined, exact: boolean): ModelRequest | undefined;
@@ -0,0 +1,140 @@
1
+ import { readMergedLaunchConfig } from './config.js';
2
+ import { isManagedProvider, readRotationConfig, readSubscriptionPool } from './subscription-state.js';
3
+ const STRENGTHS = ['ultra', 'strong', 'medium', 'light'];
4
+ const THINKING_SUFFIXES = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
5
+ function parseSpec(spec) {
6
+ const slash = spec.indexOf('/');
7
+ if (slash <= 0 || slash === spec.length - 1)
8
+ return undefined;
9
+ const providerId = spec.slice(0, slash);
10
+ const rawId = spec.slice(slash + 1);
11
+ const colon = rawId.lastIndexOf(':');
12
+ const suffix = colon > 0 ? rawId.slice(colon + 1).toLowerCase() : undefined;
13
+ return {
14
+ providerId,
15
+ modelId: suffix !== undefined && THINKING_SUFFIXES.has(suffix) ? rawId.slice(0, colon) : rawId,
16
+ ...(suffix !== undefined && THINKING_SUFFIXES.has(suffix) && suffix !== 'off' ? { thinkingLevel: suffix } : {}),
17
+ };
18
+ }
19
+ function synthesizeRoutes(ladders) {
20
+ const route = (family) => {
21
+ const providerId = ladders[family].strong.slice(0, ladders[family].strong.indexOf('/'));
22
+ return { family, credentialSource: 'default', providerId, models: ladders[family] };
23
+ };
24
+ return { anthropic: route('anthropic'), openai: route('openai') };
25
+ }
26
+ function ordered(first, rest) {
27
+ return [first, ...rest.filter((item) => item !== first)];
28
+ }
29
+ function strengths(requested, configured) {
30
+ return ordered(requested, configured).filter((value, index, all) => all.indexOf(value) === index);
31
+ }
32
+ /** Expand config-derived route order only. Availability never affects rank. */
33
+ export function expandModelCandidates(request, cwd, profileId) {
34
+ const cfg = readMergedLaunchConfig(cwd, profileId ?? null);
35
+ const routes = cfg.modelRoutes && Object.keys(cfg.modelRoutes).length > 0 ? cfg.modelRoutes : synthesizeRoutes(cfg.modelLadders);
36
+ const routeEntries = Object.entries(routes);
37
+ const fallback = cfg.modelRouting?.strengthFallback ?? [readRotationConfig().defaultFallbackStrength ?? 'strong'];
38
+ const declarationFamilies = routeEntries.map(([, route]) => route.family).filter((value, index, all) => all.indexOf(value) === index);
39
+ const declarationSources = routeEntries.map(([, route]) => route.credentialSource).filter((value, index, all) => all.indexOf(value) === index);
40
+ if (request.kind === 'exact') {
41
+ const parsed = parseSpec(request.spec);
42
+ return parsed ? [{ routeId: 'exact', credentialSource: 'exact', family: parsed.providerId, strength: 'strong', ...parsed, rank: 0 }] : [];
43
+ }
44
+ const concrete = request.kind === 'concrete' ? parseSpec(request.spec) : undefined;
45
+ const logical = request.kind === 'logical' ? request : undefined;
46
+ const matchesConcrete = (spec, exactThinking) => {
47
+ const parsed = parseSpec(spec);
48
+ return parsed !== undefined
49
+ && concrete !== undefined
50
+ && parsed.providerId === concrete.providerId
51
+ && parsed.modelId === concrete.modelId
52
+ && (!exactThinking || parsed.thinkingLevel === concrete.thinkingLevel);
53
+ };
54
+ const concreteRoute = concrete === undefined ? undefined : routeEntries.find(([, route]) => Object.values(route.models).some((spec) => matchesConcrete(spec, false)));
55
+ const requestedStrength = logical?.strength ?? (() => {
56
+ if (!concrete)
57
+ return 'strong';
58
+ // Mirrors the former ladder back-map: preserve a precise thinking-level
59
+ // match, then map a drifting/omitted suffix to the weakest same-model cell.
60
+ for (const exactThinking of [true, false]) {
61
+ for (const strength of exactThinking ? STRENGTHS : [...STRENGTHS].reverse()) {
62
+ if (routeEntries.some(([, route]) => {
63
+ const spec = route.models[strength];
64
+ return spec !== undefined && matchesConcrete(spec, exactThinking);
65
+ }))
66
+ return strength;
67
+ }
68
+ }
69
+ return 'strong';
70
+ })();
71
+ const requestedFamily = logical?.family ?? concreteRoute?.[1].family ?? concrete?.providerId ?? 'anthropic';
72
+ const sourceOrder = concrete ? declarationSources : (cfg.modelRouting?.credentialSourceOrder ?? declarationSources);
73
+ const familyOrder = concrete ? [requestedFamily] : (cfg.modelRouting?.familyOrder ?? declarationFamilies);
74
+ const emitted = [];
75
+ const seen = new Set();
76
+ const append = (routeId, route, strength, spec) => {
77
+ const parsed = parseSpec(spec);
78
+ if (!parsed)
79
+ return;
80
+ if (concrete && parsed.modelId !== concrete.modelId)
81
+ return;
82
+ const key = `${parsed.providerId}/${parsed.modelId}:${parsed.thinkingLevel ?? ''}`;
83
+ if (seen.has(key))
84
+ return;
85
+ seen.add(key);
86
+ emitted.push({ routeId, credentialSource: route.credentialSource, family: route.family, strength, ...parsed, rank: emitted.length });
87
+ };
88
+ // Concrete requests retain their exact model/provider first even if a route has
89
+ // not been authored for it; declared routes then offer that identical model in
90
+ // other credential sources only.
91
+ if (concrete) {
92
+ const [routeId, route] = concreteRoute ?? [
93
+ 'concrete',
94
+ { family: requestedFamily, credentialSource: 'default', providerId: concrete.providerId, models: { [requestedStrength]: request.spec } },
95
+ ];
96
+ append(routeId, route, requestedStrength, request.spec);
97
+ }
98
+ for (const credentialSource of sourceOrder) {
99
+ for (const strength of concrete ? [requestedStrength] : strengths(requestedStrength, fallback)) {
100
+ for (const family of ordered(requestedFamily, familyOrder)) {
101
+ for (const [routeId, route] of routeEntries) {
102
+ if (route.credentialSource !== credentialSource || route.family !== family)
103
+ continue;
104
+ const spec = route.models[strength];
105
+ if (spec)
106
+ append(routeId, route, strength, spec);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return emitted.map((candidate, rank) => ({ ...candidate, rank }));
112
+ }
113
+ /** Probe the common availability facts consumed unchanged by resolveProviderCandidates. */
114
+ export function probeRouteAvailability(candidate, registry, now = Date.now()) {
115
+ const model = registry.find(candidate.providerId, candidate.modelId);
116
+ if (model === undefined)
117
+ return { registered: false, authenticated: false };
118
+ if (!isManagedProvider(candidate.providerId))
119
+ return { registered: true, authenticated: registry.hasConfiguredAuth(model) };
120
+ const pool = readSubscriptionPool(candidate.providerId).filter((entry) => entry.authFailure === undefined);
121
+ const cooling = pool.length > 0 && pool.every((entry) => (entry.rateLimitedUntil || 0) > now);
122
+ return {
123
+ registered: true,
124
+ authenticated: pool.length > 0,
125
+ ...(cooling ? { coolingUntil: Math.min(...pool.map((entry) => entry.rateLimitedUntil || 0)) } : {}),
126
+ };
127
+ }
128
+ export function modelRequestFromConfig(model, modelIntent, exact) {
129
+ if (!model)
130
+ return undefined;
131
+ if (exact)
132
+ return { kind: 'exact', spec: model };
133
+ if (modelIntent) {
134
+ const slash = modelIntent.indexOf('/');
135
+ const strength = slash > 0 ? modelIntent.slice(slash + 1) : '';
136
+ if (STRENGTHS.includes(strength))
137
+ return { kind: 'logical', family: modelIntent.slice(0, slash), strength: strength, spec: model };
138
+ }
139
+ return { kind: 'concrete', spec: model };
140
+ }
@@ -88,12 +88,12 @@ export declare function buildWakeBearings(origin: WakeOrigin): string;
88
88
  * note. The across-cycles context-dir framing is added ONLY for an
89
89
  * orchestrator (by mode) — the one node whose dir a future cycle resumes
90
90
  * from.
91
- * 2. `<memory kind="knowledge">` — the consultable catalog, a SIBLING of the
92
- * bearings (never nested under the context dir). Dropped when empty.
93
- * 3. `<project_context>` — the cwd/git environment snapshot. pi's own
94
- * AGENTS.md/CLAUDE.md system-prompt injection stays suppressed
95
- * (noContextFiles in broker.ts); project guidance instead rides the
96
- * `<memory kind="knowledge">` block above via the document substrate
97
- * (`crtr sys sync project-guidance` migrates each CLAUDE.md/AGENTS.md into
98
- * that dir's own `.crouter/memory/AGENTS.md`). */
91
+ * 2. `<memory kind="knowledge">` — the consultable boot catalog, a SIBLING of
92
+ * the bearings (never nested under the context dir). Dropped when empty.
93
+ * 3. `<auto-loaded-context>` — memories whose `applies-to: "."` fires because
94
+ * cwd/profile mounted their owning project store. Dropped when empty.
95
+ * 4. `<project_context>` — the cwd/git environment snapshot. pi's own
96
+ * AGENTS.md/CLAUDE.md injection stays suppressed (`noContextFiles` in
97
+ * broker.ts); `crtr sys sync project-guidance` imports that guidance into
98
+ * the directory's `.crouter/memory/INDEX.md` front door. */
99
99
  export declare function buildContextBearings(nodeId: string): string;