@north-light/crouter 0.3.159 → 0.3.160

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 (70) hide show
  1. package/dist/api/dto/nodes.d.ts +3 -0
  2. package/dist/builtin-memory/internal/INDEX.md +1 -0
  3. package/dist/builtin-memory/internal/workflow-codification.md +82 -0
  4. package/dist/clients/attach/__tests__/bash-call-width.test.d.ts +1 -0
  5. package/dist/clients/attach/__tests__/bash-call-width.test.js +38 -0
  6. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +20 -0
  7. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +3 -1
  8. package/dist/clients/attach/chrome/bash-jobs.d.ts +5 -3
  9. package/dist/clients/attach/chrome/bash-jobs.js +15 -8
  10. package/dist/clients/attach/chrome/canvas-panels.js +10 -3
  11. package/dist/clients/attach/chrome/roster.d.ts +7 -2
  12. package/dist/clients/attach/chrome/roster.js +87 -3
  13. package/dist/clients/attach/input/controller.d.ts +3 -0
  14. package/dist/clients/attach/input/controller.js +1 -0
  15. package/dist/clients/attach/render/assistant-message.d.ts +13 -0
  16. package/dist/clients/attach/render/assistant-message.js +55 -0
  17. package/dist/clients/attach/render/chat-view.d.ts +29 -0
  18. package/dist/clients/attach/render/chat-view.js +233 -12
  19. package/dist/clients/attach/render/context-message.js +1 -1
  20. package/dist/clients/attach/render/edit-diff.js +20 -1
  21. package/dist/clients/attach/render/tool-calls.d.ts +32 -0
  22. package/dist/clients/attach/render/tool-calls.js +251 -0
  23. package/dist/clients/attach/session/input-wiring.d.ts +3 -0
  24. package/dist/clients/attach/session/input-wiring.js +3 -0
  25. package/dist/clients/attach/slash/dispatch.d.ts +4 -0
  26. package/dist/clients/attach/slash/dispatch.js +8 -0
  27. package/dist/clients/attach/viewer.js +512 -508
  28. package/dist/commands/cron.js +2 -2
  29. package/dist/commands/node.js +3 -3
  30. package/dist/commands/search.js +1 -1
  31. package/dist/commands/sys/config.js +10 -2
  32. package/dist/commands/sys/doctor.js +22 -1
  33. package/dist/commands/sys/setup-core.d.ts +5 -1
  34. package/dist/commands/sys/setup-core.js +16 -2
  35. package/dist/commands/sys/setup-wizard.d.ts +11 -0
  36. package/dist/commands/sys/setup-wizard.js +111 -6
  37. package/dist/core/__tests__/tmux-surface.test.js +5 -2
  38. package/dist/core/canvas/canvas.js +1 -1
  39. package/dist/core/canvas/labels.d.ts +1 -0
  40. package/dist/core/canvas/labels.js +3 -1
  41. package/dist/core/canvas/nav-render.d.ts +6 -0
  42. package/dist/core/canvas/nav-render.js +8 -0
  43. package/dist/core/canvas/types.d.ts +6 -0
  44. package/dist/core/config.js +3 -2
  45. package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
  46. package/dist/core/keybindings/catalog.d.ts +2 -2
  47. package/dist/core/keybindings/catalog.js +2 -0
  48. package/dist/core/preview-registry.d.ts +5 -0
  49. package/dist/core/preview-registry.js +10 -0
  50. package/dist/core/runtime/canvas-extensions.d.ts +4 -0
  51. package/dist/core/runtime/canvas-extensions.js +4 -0
  52. package/dist/core/runtime/naming-persist.d.ts +4 -3
  53. package/dist/core/runtime/naming-persist.js +10 -9
  54. package/dist/core/runtime/naming.d.ts +28 -8
  55. package/dist/core/runtime/naming.js +168 -32
  56. package/dist/core/runtime/nerd-font.d.ts +28 -0
  57. package/dist/core/runtime/nerd-font.js +127 -0
  58. package/dist/core/runtime/tmux.js +16 -0
  59. package/dist/daemon/api/map.js +2 -0
  60. package/dist/pi-extensions/naming-tool.d.ts +24 -0
  61. package/dist/pi-extensions/naming-tool.js +67 -0
  62. package/dist/types.d.ts +5 -0
  63. package/dist/types.js +1 -0
  64. package/dist/web-client/assets/index-B76ZKfT_.js +79 -0
  65. package/dist/web-client/assets/{index-CpEl9LTS.css → index-CqLKj8Xu.css} +1 -1
  66. package/dist/web-client/index.html +2 -2
  67. package/dist/web-client/sw.js +1 -1
  68. package/package.json +1 -1
  69. package/runtime.lock.json +2 -2
  70. package/dist/web-client/assets/index-CsuwzlcQ.js +0 -79
@@ -1,5 +1,5 @@
1
1
  import type { BindingDefinition, SafetyRequirement } from './types.js';
2
- type TmuxMenuAction = 'open' | 'show' | 'metadata' | 'promote' | 'resume' | 'fork' | 'demote' | 'detach' | 'close-subtree' | 'background-bash' | 'file-review' | 'pin' | 'reload-viewer' | 'context' | 'providers' | 'graph' | 'focus-manager' | 'copy-id' | 'issues';
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
3
  type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'revive' | 'focus-manager' | 'close-subtree' | 'dismiss';
4
4
  type InboxAction = 'down' | 'up' | 'open' | 'reply' | 'react' | 'filter' | 'refresh' | 'connect-linkedin' | 'connect-gmail' | 'quit' | 'submit-reply' | 'cancel' | 'reaction-previous' | 'reaction-next' | 'submit-reaction';
5
5
  type LinkedinAction = Exclude<InboxAction, 'filter' | 'connect-linkedin' | 'connect-gmail'>;
@@ -9,7 +9,7 @@ type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down'
9
9
  type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
10
10
  type SetupModelsAction = 'open' | 'commit' | 'cancel' | 'scope-next';
11
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.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 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">];
13
13
  export declare const BINDING_IDS: ReadonlySet<string>;
14
14
  export declare const SAFETY_REQUIREMENTS: readonly SafetyRequirement[];
15
15
  export {};
@@ -37,6 +37,8 @@ export const BINDING_CATALOG = Object.freeze([
37
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
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
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']),
40
42
  b('crtr.tmux.menu.metadata', 'Global tmux', 'Show metadata', 'Show the focused node’s metadata dossier.', ['m'], 'tmux-menu-selector', ['tmux.menu.show']),
41
43
  b('crtr.tmux.menu.context', 'Global tmux', 'Browse context', 'Browse the focused node context.', ['c'], 'tmux-menu-selector', ['tmux.menu.show']),
42
44
  b('crtr.tmux.menu.providers', 'Global tmux', 'Open settings', 'Open the crouter settings panel.', ['p'], 'tmux-menu-selector', ['tmux.menu']),
@@ -9,6 +9,11 @@ export declare function suppressOutputForLeaf(helpName: string): boolean;
9
9
  * abc`) don't prevent the match. */
10
10
  export declare function suppressOutputForPath(tokens: readonly string[]): boolean;
11
11
  export declare const HELP_ICON: string;
12
+ /** Glyphs for the plain agent tools the attach viewer re-skins (render/tool-calls.ts).
13
+ * Same registry as the crtr command icons so every Nerd Font codepoint the viewer
14
+ * paints lives in one file: terminal for `bash`, file-text for `read`, floppy for
15
+ * `write` (content committed to disk), pencil-square for `edit`. */
16
+ export declare const TOOL_ICON: Readonly<Record<'bash' | 'read' | 'write' | 'edit', string>>;
12
17
  /** Resolve the Nerd Font glyph for a command's argv tokens: the longest
13
18
  * matching LEAF_ICON_CP prefix, else the tokens[0] family glyph, else the
14
19
  * generic ◆ fallback for an unmapped family. */
@@ -53,6 +53,16 @@ export function suppressOutputForPath(tokens) {
53
53
  * regardless of which command's help was requested. */
54
54
  const HELP_ICON_CP = 0xf059;
55
55
  export const HELP_ICON = String.fromCodePoint(HELP_ICON_CP);
56
+ /** Glyphs for the plain agent tools the attach viewer re-skins (render/tool-calls.ts).
57
+ * Same registry as the crtr command icons so every Nerd Font codepoint the viewer
58
+ * paints lives in one file: terminal for `bash`, file-text for `read`, floppy for
59
+ * `write` (content committed to disk), pencil-square for `edit`. */
60
+ export const TOOL_ICON = {
61
+ bash: String.fromCodePoint(0xf120),
62
+ read: String.fromCodePoint(0xf15c),
63
+ write: String.fromCodePoint(0xf0c7),
64
+ edit: String.fromCodePoint(0xf044),
65
+ };
56
66
  /** One glyph per root subtree (`crtr <family> ...`) — the fallback for any
57
67
  * leaf not listed in LEAF_ICON_CP below. Codepoints are Nerd Font
58
68
  * private-use-area icons chosen per family concept (see command-icons.md). */
@@ -7,6 +7,10 @@ export declare const CANVAS_CONTEXT_INTRO_PATH: string;
7
7
  export declare const CANVAS_DOC_SUBSTRATE_PATH: string;
8
8
  export declare const CANVAS_STRUCTURED_OUTPUT_PATH: string;
9
9
  export declare const CANVAS_BASH_VALVE_PATH: string;
10
+ /** NOT a canvas extension — loaded only by the headless namer subprocess
11
+ * (`runtime/naming.ts`) to give it a constrained-schema `name_session` tool.
12
+ * Deliberately absent from CANVAS_EXTENSIONS: a node must never carry it. */
13
+ export declare const NAMING_TOOL_PATH: string;
10
14
  /** The canvas extensions every node loads, in order. The inbox watcher commits
11
15
  * its cursor at agent_settled before the stophook checks it for idle release. */
12
16
  export declare const CANVAS_EXTENSIONS: string[];
@@ -19,6 +19,10 @@ export const CANVAS_CONTEXT_INTRO_PATH = resolveExtension('canvas-context-intro'
19
19
  export const CANVAS_DOC_SUBSTRATE_PATH = resolveExtension('canvas-doc-substrate');
20
20
  export const CANVAS_STRUCTURED_OUTPUT_PATH = resolveExtension('canvas-structured-output');
21
21
  export const CANVAS_BASH_VALVE_PATH = resolveExtension('canvas-bash-valve');
22
+ /** NOT a canvas extension — loaded only by the headless namer subprocess
23
+ * (`runtime/naming.ts`) to give it a constrained-schema `name_session` tool.
24
+ * Deliberately absent from CANVAS_EXTENSIONS: a node must never carry it. */
25
+ export const NAMING_TOOL_PATH = resolveExtension('naming-tool');
22
26
  /** The canvas extensions every node loads, in order. The inbox watcher commits
23
27
  * its cursor at agent_settled before the stophook checks it for idle release. */
24
28
  export const CANVAS_EXTENSIONS = [
@@ -1,8 +1,9 @@
1
1
  import type { NodeMeta } from '../canvas/index.js';
2
2
  /** Asynchronously generate a name for `prompt` and persist it to the node's
3
- * meta as `description` — only if the node has none yet (so a later message
4
- * never clobbers it). Non-blocking: safe to call from inside a live pi event
5
- * loop. Best-effort; swallows all errors.
3
+ * meta as `description` + `icon` — only if the node has none yet (so a later
4
+ * message never clobbers it). Non-blocking: safe to call from inside a live pi
5
+ * event loop. Best-effort; swallows all errors. The slug fallback carries no
6
+ * icon: a locally derived name has no judgment behind it to pick a glyph from.
6
7
  *
7
8
  * `onNamed` (optional) fires with the freshly-persisted meta the moment the
8
9
  * name lands — the canvas-goal-capture naming hook passes a callback that calls
@@ -8,9 +8,10 @@
8
8
  import { headlessName, sanitizeSessionName, slugFromPrompt } from './naming.js';
9
9
  import { getNode, updateNode } from '../canvas/index.js';
10
10
  /** Asynchronously generate a name for `prompt` and persist it to the node's
11
- * meta as `description` — only if the node has none yet (so a later message
12
- * never clobbers it). Non-blocking: safe to call from inside a live pi event
13
- * loop. Best-effort; swallows all errors.
11
+ * meta as `description` + `icon` — only if the node has none yet (so a later
12
+ * message never clobbers it). Non-blocking: safe to call from inside a live pi
13
+ * event loop. Best-effort; swallows all errors. The slug fallback carries no
14
+ * icon: a locally derived name has no judgment behind it to pick a glyph from.
14
15
  *
15
16
  * `onNamed` (optional) fires with the freshly-persisted meta the moment the
16
17
  * name lands — the canvas-goal-capture naming hook passes a callback that calls
@@ -20,22 +21,22 @@ export function generateAndPersistName(nodeId, prompt, onNamed) {
20
21
  const body = (prompt ?? '').trim();
21
22
  if (body === '')
22
23
  return;
23
- const persist = (name) => {
24
+ const persist = (generated) => {
24
25
  try {
25
26
  const meta = getNode(nodeId);
26
27
  if (meta === null)
27
28
  return;
28
29
  if ((meta.description ?? '').trim() !== '')
29
30
  return; // already named
30
- const clean = sanitizeSessionName(name);
31
- const updated = updateNode(nodeId, { description: clean !== '' ? clean : slugFromPrompt(body) });
31
+ const clean = sanitizeSessionName(generated.name);
32
+ const description = clean !== '' ? clean : slugFromPrompt(body);
33
+ const icon = generated.icon.trim();
34
+ const updated = updateNode(nodeId, { description, ...(icon !== '' ? { icon } : {}) });
32
35
  onNamed?.(updated);
33
36
  }
34
37
  catch {
35
38
  // best-effort
36
39
  }
37
40
  };
38
- void headlessName(body).then((name) => {
39
- persist(name !== '' ? name : slugFromPrompt(body));
40
- });
41
+ void headlessName(body).then(persist);
41
42
  }
@@ -5,18 +5,38 @@ export declare function sanitizeSessionName(raw: string): string;
5
5
  /** Local fallback: derive a name straight from the prompt (no pi call). Drops
6
6
  * stop-words, takes the first few content words. */
7
7
  export declare function slugFromPrompt(prompt: string): string;
8
- /** Ask pi headlessly for a kebab-case name for `body`, async. Resolves to the
9
- * sanitized name, or '' on any failure (non-zero exit, timeout, empty/garbled
10
- * output) so the caller can fall back to a local slug. Owns the subprocess
11
- * mechanics — crucially it hands pi an immediate stdin EOF: `pi -p` reads
12
- * stdin, and execFile's default stdin is an OPEN pipe that never closes, so
13
- * without this pi blocks waiting for EOF and the call exits non-zero (the
14
- * regression that silently lost every LLM name to the slug fallback).
8
+ /** A generated session name: the kebab-case handle plus the Nerd Font glyph the
9
+ * namer chose for it. `icon` is '' when the model returned nothing usable —
10
+ * every surface renders the name alone in that case. */
11
+ export interface SessionName {
12
+ name: string;
13
+ icon: string;
14
+ }
15
+ /** Normalize a model-supplied icon to a single renderable glyph, or '' when
16
+ * nothing usable came back.
17
+ *
18
+ * The model is asked for a `U+XXXX` codepoint rather than the literal
19
+ * character: Nerd Font glyphs sit in the private-use areas, which models
20
+ * reproduce unreliably as literal text (they tend to substitute an emoji), but
21
+ * name accurately as codepoints. A literal glyph is still accepted — including
22
+ * an emoji, which renders fine even if it is not the house style — so a model
23
+ * that ignores the format still contributes. Rejects a spelled-out
24
+ * `nf-fa-bug`, a bare ASCII letter, and anything multi-glyph, all of which
25
+ * would render as noise or tofu in a label. */
26
+ export declare function sanitizeIcon(raw: string): string;
27
+ /** Ask pi headlessly for a structured {name, icon} for `body`, async. Resolves
28
+ * to a sanitized SessionName, or `{name:'',icon:''}` on any failure (non-zero
29
+ * exit, timeout, no tool call, garbled emission) so the caller can fall back to
30
+ * a local slug. Owns the subprocess mechanics — crucially it hands pi an
31
+ * immediate stdin EOF: `pi -p` reads stdin, and execFile's default stdin is an
32
+ * OPEN pipe that never closes, so without this pi blocks waiting for EOF and
33
+ * the call exits non-zero (the regression that silently lost every LLM name to
34
+ * the slug fallback).
15
35
  *
16
36
  * Exported (not just for tests): the canvas-coupled persistence wrapper
17
37
  * generateAndPersistName lives in naming-persist.ts to keep this module
18
38
  * canvas-free, and imports this pure helper. */
19
- export declare function headlessName(body: string): Promise<string>;
39
+ export declare function headlessName(body: string): Promise<SessionName>;
20
40
  /** A filesystem-safe slug for the `.todos/<slug>.md` fallback path: lowercase,
21
41
  * [a-z0-9] kept, everything else → single hyphen, clamped to 8 words. */
22
42
  export declare function titleSlug(title: string): string;
@@ -6,6 +6,12 @@
6
6
  // the first prompt by asking pi headlessly (`pi -p`), persisted on the node's
7
7
  // meta so it survives revives and updates the live session name.
8
8
  //
9
+ // The namer returns a STRUCTURED result — {name, icon} — produced by constrained
10
+ // sampling against the `name_session` tool in `pi-extensions/naming-tool.ts`,
11
+ // not by parsing prose. The icon is a Nerd Font glyph the model picks freely
12
+ // (guided by example blocks, not a closed enum) and is stored as its own meta
13
+ // field, so each surface decides whether to render it.
14
+ //
9
15
  // One entry point: generateAndPersistName — async (execFile, non-blocking).
10
16
  // Naming happens INSIDE the named node's own pi process, off the first real
11
17
  // message (the kickoff task or a human's first line), never on the spawn path:
@@ -16,23 +22,59 @@
16
22
  // Best-effort: a failed/slow/garbled pi call falls back to a local slug of the
17
23
  // prompt, so a node always gets a sane name.
18
24
  import { execFile } from 'node:child_process';
25
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
26
+ import { tmpdir } from 'node:os';
27
+ import { join } from 'node:path';
19
28
  import { bundledPiSubprocessEnv, resolveBundledPiCliPath } from './pi-cli.js';
29
+ import { NAMING_TOOL_PATH } from './canvas-extensions.js';
30
+ import { defaultProvider, modelLadders, stripThinkingSuffix } from './model-selection.js';
20
31
  /** Cap on prompt text fed to the namer — a name needs only the gist. */
21
32
  const PROMPT_CAP = 2000;
22
- /** Wall-clock budget for the headless pi call before we fall back to a slug. */
23
- const NAME_TIMEOUT_MS = 20_000;
24
- const NAME_SYSTEM_PROMPT = 'You name coding-agent work sessions. This name is a label used to identify the ' +
33
+ /** Wall-clock budget for the headless pi call before we fall back to a slug.
34
+ * Generous because the structured path costs a tool round-trip on top of the
35
+ * provider's own cold start; naming is async and off the spawn path, so a slow
36
+ * call delays only the label, never the node. */
37
+ const NAME_TIMEOUT_MS = 45_000;
38
+ /** Nerd Font glyphs offered to the namer as EXAMPLES, never as a closed set —
39
+ * the model may pick any Nerd Font character it thinks fits better. Grouped by
40
+ * the kind of work they read as, so the model generalizes from the grouping
41
+ * instead of pattern-matching a lookup table. */
42
+ const ICON_EXAMPLES = [
43
+ 'fixing/diagnosis: U+F188 bug, U+F0F1 stethoscope, U+F06D fire, U+F071 warning',
44
+ 'building/changing code: U+F0AD wrench, U+F067 plus, U+F121 code, U+F085 cogs, U+F021 refresh',
45
+ 'testing/verifying: U+F0C3 flask, U+F00C check, U+F24E balance',
46
+ 'writing/docs/research: U+F02D book, U+F15C document, U+F002 magnifier, U+F0EB lightbulb',
47
+ 'data/infra: U+F1C0 database, U+F0C2 cloud, U+F120 terminal, U+F013 gear, U+F1B3 packages',
48
+ 'ship/perf/scale: U+F135 rocket, U+F0E7 bolt, U+F080 chart, U+F201 trend line',
49
+ 'security/access: U+F132 shield, U+F023 lock, U+F084 key',
50
+ 'interface/design: U+F1FC paintbrush, U+F03E image, U+F108 display',
51
+ 'communication/coordination: U+F1D8 paper plane, U+F0E6 comments, U+F0E8 sitemap, U+F0C1 link',
52
+ ].join('\n');
53
+ const NAME_SYSTEM_PROMPT = 'You name coding-agent work sessions. The name is a label used to identify the ' +
25
54
  'session at a glance among many other concurrent programming sessions, so it must ' +
26
- 'describe what the task is about. Reply with ONLY a concise 3-8 word name in ' +
27
- 'kebab-case: lowercase words joined by single hyphens (e.g. `refactor-auth-token-flow`, ' +
28
- '`add-csv-export-endpoint`). No punctuation, quotes, prose, or trailing text. ' +
29
- 'Output JUST the name, nothing else.';
55
+ 'describe what the task is about.\n\n' +
56
+ 'Call the `name_session` tool exactly once with:\n' +
57
+ '- `name`: a concise 3-8 word kebab-case name — lowercase words joined by single ' +
58
+ 'hyphens (e.g. `refactor-auth-token-flow`, `add-csv-export-endpoint`). No punctuation, ' +
59
+ 'quotes, or prose.\n' +
60
+ '- `icon`: ONE Nerd Font glyph, written as its codepoint in `U+XXXX` form (e.g. ' +
61
+ '`U+F188`). ANY Nerd Font codepoint is allowed — the whole Font Awesome, Material, ' +
62
+ 'Devicons, Octicons and Codicons ranges are available, so reach for the glyph that ' +
63
+ 'actually depicts this work rather than settling for one of the examples below. ' +
64
+ 'Emit a codepoint, not a name like `nf-fa-bug`, and not an emoji.\n\n' +
65
+ 'Pick the glyph whose depicted object a person would associate with this work at a ' +
66
+ 'glance. Prefer the concrete subject (a database, a lock, a browser, a specific ' +
67
+ "technology's logo) over a generic verb glyph when the task has one; fall back to the " +
68
+ 'activity (fixing, building, testing) otherwise. Never reuse a generic gear for ' +
69
+ 'everything.\n\n' +
70
+ 'Examples of what reads well, by kind of work:\n' +
71
+ ICON_EXAMPLES;
30
72
  /** Put the raw task text FIRST in a delimited block, then the instruction, so the
31
73
  * model reads the content before being told what to do and never mistakes the
32
74
  * prompt's own text for the instruction. The prompt is capped first, so the
33
75
  * closing tag is always present. */
34
76
  function nameUserPrompt(prompt) {
35
- return `<prompt>\n${prompt.slice(0, PROMPT_CAP)}\n</prompt>\n\nName this session based on the task above. The name should describe what the task is about, so it can be identified among many other programming sessions. Output JUST the name, nothing else.`;
77
+ return `<prompt>\n${prompt.slice(0, PROMPT_CAP)}\n</prompt>\n\nName this session based on the task above, and pick an icon depicting it. Call \`name_session\` once, then stop.`;
36
78
  }
37
79
  /** A short stop-word set so the local-slug fallback skips filler words. */
38
80
  const STOPWORDS = new Set([
@@ -64,18 +106,24 @@ export function slugFromPrompt(prompt) {
64
106
  .slice(0, 3);
65
107
  return sanitizeSessionName(picked.join('-')) || 'session';
66
108
  }
67
- /** Default namer model — Anthropic's small/fast model. Naming is a one-line
68
- * classification, so we pin Haiku (cheap, quick) instead of inheriting the
69
- * node's heavyweight default. Override with CRTR_NAME_MODEL. */
70
- const DEFAULT_NAME_MODEL = 'anthropic/claude-haiku-4-5';
71
- /** The pi argv for a headless name request. Stripped down (no tools, session,
72
- * context files, extensions, skills, templates, themes) so it's fast and
73
- * side-effect free. Pinned to Haiku with thinking off — naming is a trivial
74
- * classification that never needs a reasoning budget. Override the model with
75
- * CRTR_NAME_MODEL. */
76
- function piTextArgs(systemPrompt, userPrompt) {
109
+ /** The namer's model: the `light` rung of the DEFAULT provider's ladder, with
110
+ * any thinking suffix stripped (we pass `--thinking off` explicitly — naming is
111
+ * a trivial classification that never needs a reasoning budget). Reading the
112
+ * ladder rather than pinning a concrete model id keeps naming working for a
113
+ * user who never configured Anthropic: an OpenAI default provider resolves to
114
+ * its own light rung. `CRTR_NAME_MODEL` still overrides. */
115
+ function nameModel() {
77
116
  const override = process.env['CRTR_NAME_MODEL'];
78
- const model = override !== undefined && override.trim() !== '' ? override.trim() : DEFAULT_NAME_MODEL;
117
+ if (override !== undefined && override.trim() !== '')
118
+ return override.trim();
119
+ const ladders = modelLadders();
120
+ return stripThinkingSuffix(ladders[defaultProvider(ladders)].light);
121
+ }
122
+ /** The pi argv for a headless TEXT request (issue titles). Stripped down (no
123
+ * tools, session, context files, extensions, skills, templates, themes) so
124
+ * it's fast and side-effect free. */
125
+ function piTextArgs(systemPrompt, userPrompt) {
126
+ const model = nameModel();
79
127
  return [
80
128
  '-p',
81
129
  '--no-session',
@@ -93,32 +141,120 @@ function piTextArgs(systemPrompt, userPrompt) {
93
141
  userPrompt,
94
142
  ];
95
143
  }
144
+ /** The pi argv for a headless STRUCTURED name request: same stripped-down run,
145
+ * but with the naming tool loaded explicitly (`-e` still works under
146
+ * `--no-extensions`) and the tool allowlist narrowed to it, so the model's only
147
+ * possible action is one constrained-schema emission. */
96
148
  function nameArgs(prompt) {
97
- return piTextArgs(NAME_SYSTEM_PROMPT, nameUserPrompt(prompt));
149
+ return [
150
+ '-p',
151
+ '--no-session',
152
+ '--no-context-files',
153
+ '--no-extensions',
154
+ '--no-skills',
155
+ '--no-prompt-templates',
156
+ '--no-themes',
157
+ '-e', NAMING_TOOL_PATH,
158
+ '--tools', 'name_session',
159
+ '--mode', 'text',
160
+ // A trivial one-shot classification — no thinking budget, ever.
161
+ '--thinking', 'off',
162
+ '--model', nameModel(),
163
+ '--system-prompt', NAME_SYSTEM_PROMPT,
164
+ nameUserPrompt(prompt),
165
+ ];
98
166
  }
99
- /** Ask pi headlessly for a kebab-case name for `body`, async. Resolves to the
100
- * sanitized name, or '' on any failure (non-zero exit, timeout, empty/garbled
101
- * output) so the caller can fall back to a local slug. Owns the subprocess
102
- * mechanics — crucially it hands pi an immediate stdin EOF: `pi -p` reads
103
- * stdin, and execFile's default stdin is an OPEN pipe that never closes, so
104
- * without this pi blocks waiting for EOF and the call exits non-zero (the
105
- * regression that silently lost every LLM name to the slug fallback).
167
+ /** Normalize a model-supplied icon to a single renderable glyph, or '' when
168
+ * nothing usable came back.
169
+ *
170
+ * The model is asked for a `U+XXXX` codepoint rather than the literal
171
+ * character: Nerd Font glyphs sit in the private-use areas, which models
172
+ * reproduce unreliably as literal text (they tend to substitute an emoji), but
173
+ * name accurately as codepoints. A literal glyph is still accepted — including
174
+ * an emoji, which renders fine even if it is not the house style — so a model
175
+ * that ignores the format still contributes. Rejects a spelled-out
176
+ * `nf-fa-bug`, a bare ASCII letter, and anything multi-glyph, all of which
177
+ * would render as noise or tofu in a label. */
178
+ export function sanitizeIcon(raw) {
179
+ const trimmed = (raw ?? '').trim();
180
+ if (trimmed === '')
181
+ return '';
182
+ const codepoint = /^(?:u\+|0x|\\u\{?|&#x)?([0-9a-f]{4,6})\}?;?$/i.exec(trimmed);
183
+ if (codepoint !== null) {
184
+ const value = Number.parseInt(codepoint[1], 16);
185
+ // Anything below the symbol blocks is a letter/digit/control — not an icon.
186
+ if (value >= 0x2000 && value <= 0x10ffff) {
187
+ try {
188
+ return String.fromCodePoint(value);
189
+ }
190
+ catch {
191
+ return '';
192
+ }
193
+ }
194
+ return '';
195
+ }
196
+ const points = [...trimmed];
197
+ // A glyph, plus at most a variation selector.
198
+ if (points.length > 2)
199
+ return '';
200
+ return points[0].codePointAt(0) >= 0x2000 ? trimmed : '';
201
+ }
202
+ /** Ask pi headlessly for a structured {name, icon} for `body`, async. Resolves
203
+ * to a sanitized SessionName, or `{name:'',icon:''}` on any failure (non-zero
204
+ * exit, timeout, no tool call, garbled emission) so the caller can fall back to
205
+ * a local slug. Owns the subprocess mechanics — crucially it hands pi an
206
+ * immediate stdin EOF: `pi -p` reads stdin, and execFile's default stdin is an
207
+ * OPEN pipe that never closes, so without this pi blocks waiting for EOF and
208
+ * the call exits non-zero (the regression that silently lost every LLM name to
209
+ * the slug fallback).
106
210
  *
107
211
  * Exported (not just for tests): the canvas-coupled persistence wrapper
108
212
  * generateAndPersistName lives in naming-persist.ts to keep this module
109
213
  * canvas-free, and imports this pure helper. */
110
214
  export function headlessName(body) {
215
+ const empty = { name: '', icon: '' };
111
216
  return new Promise((resolve) => {
217
+ let dir = null;
112
218
  try {
113
- const child = execFile(process.execPath, [resolveBundledPiCliPath(), ...nameArgs(body)], { encoding: 'utf8', timeout: NAME_TIMEOUT_MS, env: bundledPiSubprocessEnv() }, (err, stdout) => {
114
- if (err || typeof stdout !== 'string')
115
- return resolve('');
116
- resolve(sanitizeSessionName(stdout));
219
+ dir = mkdtempSync(join(tmpdir(), 'crtr-name-'));
220
+ const resultFile = join(dir, 'name.json');
221
+ const done = (value) => {
222
+ if (dir !== null) {
223
+ try {
224
+ rmSync(dir, { recursive: true, force: true });
225
+ }
226
+ catch { /* temp */ }
227
+ }
228
+ resolve(value);
229
+ };
230
+ const child = execFile(process.execPath, [resolveBundledPiCliPath(), ...nameArgs(body)], {
231
+ encoding: 'utf8',
232
+ timeout: NAME_TIMEOUT_MS,
233
+ env: { ...bundledPiSubprocessEnv(), CRTR_NAME_RESULT_FILE: resultFile },
234
+ }, () => {
235
+ // The tool call is the ONLY channel: a non-zero exit still counts when
236
+ // the emission already landed (pi print-mode can exit oddly after a
237
+ // terminating tool), and a clean exit without the file is a failure.
238
+ try {
239
+ const parsed = JSON.parse(readFileSync(resultFile, 'utf8'));
240
+ const name = sanitizeSessionName(typeof parsed['name'] === 'string' ? parsed['name'] : '');
241
+ const icon = sanitizeIcon(typeof parsed['icon'] === 'string' ? parsed['icon'] : '');
242
+ done(name !== '' ? { name, icon } : empty);
243
+ }
244
+ catch {
245
+ done(empty);
246
+ }
117
247
  });
118
248
  child.stdin?.end(); // immediate EOF — see the doc above
119
249
  }
120
250
  catch {
121
- resolve('');
251
+ if (dir !== null) {
252
+ try {
253
+ rmSync(dir, { recursive: true, force: true });
254
+ }
255
+ catch { /* temp */ }
256
+ }
257
+ resolve(empty);
122
258
  }
123
259
  });
124
260
  }
@@ -0,0 +1,28 @@
1
+ export interface NerdFontDetection {
2
+ /** A Nerd Font face is installed somewhere on this system. */
3
+ installed: boolean;
4
+ /** The family or filename that matched, for the doctor message. */
5
+ evidence: string | null;
6
+ /** How the match was found — fontconfig query, or a font-directory scan. */
7
+ source: 'fc-list' | 'font-dir' | null;
8
+ }
9
+ export declare function detectNerdFont(platform?: NodeJS.Platform, home?: string): NerdFontDetection;
10
+ /** Download the upstream symbols-only patch into the user's font dir. Works on
11
+ * every Linux regardless of package manager — Debian/Ubuntu and Fedora ship no
12
+ * Nerd Font package, so there is nothing native to install there. Needs no
13
+ * sudo: it writes to `~/.local/share/fonts`. */
14
+ export declare const NERD_FONT_MANUAL_LINUX_COMMAND: string;
15
+ export interface NerdFontInstallCommand {
16
+ command: string | null;
17
+ hint: string;
18
+ autoInstall: boolean;
19
+ }
20
+ /** The install command for this platform/package manager. `packageManager`
21
+ * matches setup's detection: only brew and pacman have a real Nerd Font
22
+ * package, everything else on Linux gets the upstream download. A non-brew
23
+ * package manager only ever exists on Linux, so it settles the platform on its
24
+ * own and `platform` decides only the `manual` case. */
25
+ export declare function nerdFontInstallCommand(packageManager: 'brew' | 'apt-get' | 'dnf' | 'pacman' | 'manual', platform?: NodeJS.Platform): NerdFontInstallCommand;
26
+ /** Every install path ends the same way: the font is on disk but the terminal
27
+ * emulator still has to be pointed at it, and crtr cannot do that. */
28
+ export declare const NERD_FONT_SELECT_HINT = "then set your terminal profile's font to a Nerd Font (e.g. add \"Symbols Nerd Font Mono\" as the non-ASCII/fallback font) \u2014 installing it does not select it";