@north-light/crouter 0.3.159 → 0.3.161

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 (74) 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/__tests__/markdown-headings.test.js +8 -0
  9. package/dist/clients/attach/chrome/bash-jobs.d.ts +5 -3
  10. package/dist/clients/attach/chrome/bash-jobs.js +15 -8
  11. package/dist/clients/attach/chrome/canvas-panels.js +10 -3
  12. package/dist/clients/attach/chrome/roster.d.ts +7 -2
  13. package/dist/clients/attach/chrome/roster.js +87 -3
  14. package/dist/clients/attach/input/controller.d.ts +3 -0
  15. package/dist/clients/attach/input/controller.js +1 -0
  16. package/dist/clients/attach/render/assistant-message.d.ts +13 -0
  17. package/dist/clients/attach/render/assistant-message.js +55 -0
  18. package/dist/clients/attach/render/chat-view.d.ts +29 -0
  19. package/dist/clients/attach/render/chat-view.js +233 -12
  20. package/dist/clients/attach/render/context-message.js +1 -1
  21. package/dist/clients/attach/render/edit-diff.js +20 -1
  22. package/dist/clients/attach/render/glyph-codepoints.d.ts +3 -0
  23. package/dist/clients/attach/render/glyph-codepoints.js +42 -0
  24. package/dist/clients/attach/render/markdown-headings.js +15 -4
  25. package/dist/clients/attach/render/tool-calls.d.ts +32 -0
  26. package/dist/clients/attach/render/tool-calls.js +251 -0
  27. package/dist/clients/attach/session/input-wiring.d.ts +3 -0
  28. package/dist/clients/attach/session/input-wiring.js +3 -0
  29. package/dist/clients/attach/slash/dispatch.d.ts +4 -0
  30. package/dist/clients/attach/slash/dispatch.js +8 -0
  31. package/dist/clients/attach/viewer.js +699 -695
  32. package/dist/commands/cron.js +2 -2
  33. package/dist/commands/node.js +3 -3
  34. package/dist/commands/search.js +1 -1
  35. package/dist/commands/sys/config.js +10 -2
  36. package/dist/commands/sys/doctor.js +22 -1
  37. package/dist/commands/sys/setup-core.d.ts +5 -1
  38. package/dist/commands/sys/setup-core.js +16 -2
  39. package/dist/commands/sys/setup-wizard.d.ts +11 -0
  40. package/dist/commands/sys/setup-wizard.js +111 -6
  41. package/dist/core/__tests__/tmux-surface.test.js +5 -2
  42. package/dist/core/canvas/canvas.js +1 -1
  43. package/dist/core/canvas/labels.d.ts +6 -3
  44. package/dist/core/canvas/labels.js +9 -4
  45. package/dist/core/canvas/nav-render.d.ts +6 -0
  46. package/dist/core/canvas/nav-render.js +8 -0
  47. package/dist/core/canvas/types.d.ts +6 -0
  48. package/dist/core/config.js +3 -2
  49. package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
  50. package/dist/core/keybindings/catalog.d.ts +2 -2
  51. package/dist/core/keybindings/catalog.js +2 -0
  52. package/dist/core/preview-registry.d.ts +5 -0
  53. package/dist/core/preview-registry.js +10 -0
  54. package/dist/core/runtime/canvas-extensions.d.ts +4 -0
  55. package/dist/core/runtime/canvas-extensions.js +4 -0
  56. package/dist/core/runtime/naming-persist.d.ts +4 -3
  57. package/dist/core/runtime/naming-persist.js +10 -9
  58. package/dist/core/runtime/naming.d.ts +28 -8
  59. package/dist/core/runtime/naming.js +175 -32
  60. package/dist/core/runtime/nerd-font.d.ts +28 -0
  61. package/dist/core/runtime/nerd-font.js +127 -0
  62. package/dist/core/runtime/tmux.js +16 -0
  63. package/dist/daemon/api/map.js +2 -0
  64. package/dist/pi-extensions/naming-tool.d.ts +24 -0
  65. package/dist/pi-extensions/naming-tool.js +67 -0
  66. package/dist/types.d.ts +5 -0
  67. package/dist/types.js +1 -0
  68. package/dist/web-client/assets/index-B76ZKfT_.js +79 -0
  69. package/dist/web-client/assets/{index-CpEl9LTS.css → index-CqLKj8Xu.css} +1 -1
  70. package/dist/web-client/index.html +2 -2
  71. package/dist/web-client/sw.js +1 -1
  72. package/package.json +8 -8
  73. package/runtime.lock.json +161 -161
  74. 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,66 @@
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 a FALLBACK set for when a task has
39
+ * no concrete subject — never a closed set, and deliberately introduced to the
40
+ * model as the generic tier so it reaches for a real noun first. Measured
41
+ * against 20 real prompts, an unqualified example list anchors the model onto
42
+ * it almost totally; the two-step rule in the system prompt is what pulls picks
43
+ * back out to the actual subject. */
44
+ const ICON_EXAMPLES = [
45
+ 'fixing/diagnosis: U+F188 bug, U+F0F1 stethoscope, U+F06D fire, U+F071 warning',
46
+ 'building/changing code: U+F0AD wrench, U+F067 plus, U+F121 code, U+F085 cogs, U+F021 refresh',
47
+ 'testing/verifying: U+F0C3 flask, U+F00C check, U+F24E balance',
48
+ 'writing/docs/research: U+F02D book, U+F15C document, U+F002 magnifier, U+F0EB lightbulb',
49
+ 'data/infra: U+F1C0 database, U+F0C2 cloud, U+F120 terminal, U+F013 gear, U+F1B3 packages',
50
+ 'ship/perf/scale: U+F135 rocket, U+F0E7 bolt, U+F080 chart, U+F201 trend line',
51
+ 'security/access: U+F132 shield, U+F023 lock, U+F084 key',
52
+ 'interface/design: U+F1FC paintbrush, U+F03E image, U+F108 display',
53
+ 'communication/coordination: U+F1D8 paper plane, U+F0E6 comments, U+F0E8 sitemap, U+F0C1 link',
54
+ ].join('\n');
55
+ const NAME_SYSTEM_PROMPT = 'You name coding-agent work sessions. The name is a label used to identify the ' +
25
56
  '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.';
57
+ 'describe what the task is about.\n\n' +
58
+ 'Call the `name_session` tool exactly once with:\n' +
59
+ '- `name`: a concise 3-8 word kebab-case name — lowercase words joined by single ' +
60
+ 'hyphens (e.g. `refactor-auth-token-flow`, `add-csv-export-endpoint`). No punctuation, ' +
61
+ 'quotes, or prose.\n' +
62
+ '- `icon`: ONE Nerd Font glyph, written as its codepoint in `U+XXXX` form (e.g. ' +
63
+ '`U+F188`). Emit a codepoint, not a name like `nf-fa-bug`, and not an emoji.\n\n' +
64
+ 'Choosing the icon — work through it in this order:\n' +
65
+ '1. Name the concrete SUBJECT of the task: the thing being acted on. A font, a ' +
66
+ 'trash can, an account, a browser tab, a clock, an invoice, a map, a robot, a specific ' +
67
+ "technology's logo. If a glyph depicts that thing, use it. This is almost always the " +
68
+ 'better icon, because it is what distinguishes this session from every other one.\n' +
69
+ '2. Only when the task has no depictable subject, fall back to the ACTIVITY (fixing, ' +
70
+ 'documenting, testing) using the generic set below.\n\n' +
71
+ 'ANY Nerd Font codepoint is allowed and the set is enormous — the whole Font Awesome ' +
72
+ 'range, Devicons and Seti technology logos, Octicons, Codicons, Weather, and the ' +
73
+ 'thousands of Material Design nouns above U+F0001. Reach into it. The examples below ' +
74
+ 'are the generic tier, NOT a menu: if your pick came from them and the task had a real ' +
75
+ 'subject, you picked wrong. Never reuse a generic gear, wrench, or bug for a task that ' +
76
+ 'is actually about something.\n\n' +
77
+ 'Generic fallback set, by kind of work:\n' +
78
+ ICON_EXAMPLES;
30
79
  /** Put the raw task text FIRST in a delimited block, then the instruction, so the
31
80
  * model reads the content before being told what to do and never mistakes the
32
81
  * prompt's own text for the instruction. The prompt is capped first, so the
33
82
  * closing tag is always present. */
34
83
  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.`;
84
+ 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
85
  }
37
86
  /** A short stop-word set so the local-slug fallback skips filler words. */
38
87
  const STOPWORDS = new Set([
@@ -64,18 +113,24 @@ export function slugFromPrompt(prompt) {
64
113
  .slice(0, 3);
65
114
  return sanitizeSessionName(picked.join('-')) || 'session';
66
115
  }
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) {
116
+ /** The namer's model: the `light` rung of the DEFAULT provider's ladder, with
117
+ * any thinking suffix stripped (we pass `--thinking off` explicitly — naming is
118
+ * a trivial classification that never needs a reasoning budget). Reading the
119
+ * ladder rather than pinning a concrete model id keeps naming working for a
120
+ * user who never configured Anthropic: an OpenAI default provider resolves to
121
+ * its own light rung. `CRTR_NAME_MODEL` still overrides. */
122
+ function nameModel() {
77
123
  const override = process.env['CRTR_NAME_MODEL'];
78
- const model = override !== undefined && override.trim() !== '' ? override.trim() : DEFAULT_NAME_MODEL;
124
+ if (override !== undefined && override.trim() !== '')
125
+ return override.trim();
126
+ const ladders = modelLadders();
127
+ return stripThinkingSuffix(ladders[defaultProvider(ladders)].light);
128
+ }
129
+ /** The pi argv for a headless TEXT request (issue titles). Stripped down (no
130
+ * tools, session, context files, extensions, skills, templates, themes) so
131
+ * it's fast and side-effect free. */
132
+ function piTextArgs(systemPrompt, userPrompt) {
133
+ const model = nameModel();
79
134
  return [
80
135
  '-p',
81
136
  '--no-session',
@@ -93,32 +148,120 @@ function piTextArgs(systemPrompt, userPrompt) {
93
148
  userPrompt,
94
149
  ];
95
150
  }
151
+ /** The pi argv for a headless STRUCTURED name request: same stripped-down run,
152
+ * but with the naming tool loaded explicitly (`-e` still works under
153
+ * `--no-extensions`) and the tool allowlist narrowed to it, so the model's only
154
+ * possible action is one constrained-schema emission. */
96
155
  function nameArgs(prompt) {
97
- return piTextArgs(NAME_SYSTEM_PROMPT, nameUserPrompt(prompt));
156
+ return [
157
+ '-p',
158
+ '--no-session',
159
+ '--no-context-files',
160
+ '--no-extensions',
161
+ '--no-skills',
162
+ '--no-prompt-templates',
163
+ '--no-themes',
164
+ '-e', NAMING_TOOL_PATH,
165
+ '--tools', 'name_session',
166
+ '--mode', 'text',
167
+ // A trivial one-shot classification — no thinking budget, ever.
168
+ '--thinking', 'off',
169
+ '--model', nameModel(),
170
+ '--system-prompt', NAME_SYSTEM_PROMPT,
171
+ nameUserPrompt(prompt),
172
+ ];
98
173
  }
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).
174
+ /** Normalize a model-supplied icon to a single renderable glyph, or '' when
175
+ * nothing usable came back.
176
+ *
177
+ * The model is asked for a `U+XXXX` codepoint rather than the literal
178
+ * character: Nerd Font glyphs sit in the private-use areas, which models
179
+ * reproduce unreliably as literal text (they tend to substitute an emoji), but
180
+ * name accurately as codepoints. A literal glyph is still accepted — including
181
+ * an emoji, which renders fine even if it is not the house style — so a model
182
+ * that ignores the format still contributes. Rejects a spelled-out
183
+ * `nf-fa-bug`, a bare ASCII letter, and anything multi-glyph, all of which
184
+ * would render as noise or tofu in a label. */
185
+ export function sanitizeIcon(raw) {
186
+ const trimmed = (raw ?? '').trim();
187
+ if (trimmed === '')
188
+ return '';
189
+ const codepoint = /^(?:u\+|0x|\\u\{?|&#x)?([0-9a-f]{4,6})\}?;?$/i.exec(trimmed);
190
+ if (codepoint !== null) {
191
+ const value = Number.parseInt(codepoint[1], 16);
192
+ // Anything below the symbol blocks is a letter/digit/control — not an icon.
193
+ if (value >= 0x2000 && value <= 0x10ffff) {
194
+ try {
195
+ return String.fromCodePoint(value);
196
+ }
197
+ catch {
198
+ return '';
199
+ }
200
+ }
201
+ return '';
202
+ }
203
+ const points = [...trimmed];
204
+ // A glyph, plus at most a variation selector.
205
+ if (points.length > 2)
206
+ return '';
207
+ return points[0].codePointAt(0) >= 0x2000 ? trimmed : '';
208
+ }
209
+ /** Ask pi headlessly for a structured {name, icon} for `body`, async. Resolves
210
+ * to a sanitized SessionName, or `{name:'',icon:''}` on any failure (non-zero
211
+ * exit, timeout, no tool call, garbled emission) so the caller can fall back to
212
+ * a local slug. Owns the subprocess mechanics — crucially it hands pi an
213
+ * immediate stdin EOF: `pi -p` reads stdin, and execFile's default stdin is an
214
+ * OPEN pipe that never closes, so without this pi blocks waiting for EOF and
215
+ * the call exits non-zero (the regression that silently lost every LLM name to
216
+ * the slug fallback).
106
217
  *
107
218
  * Exported (not just for tests): the canvas-coupled persistence wrapper
108
219
  * generateAndPersistName lives in naming-persist.ts to keep this module
109
220
  * canvas-free, and imports this pure helper. */
110
221
  export function headlessName(body) {
222
+ const empty = { name: '', icon: '' };
111
223
  return new Promise((resolve) => {
224
+ let dir = null;
112
225
  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));
226
+ dir = mkdtempSync(join(tmpdir(), 'crtr-name-'));
227
+ const resultFile = join(dir, 'name.json');
228
+ const done = (value) => {
229
+ if (dir !== null) {
230
+ try {
231
+ rmSync(dir, { recursive: true, force: true });
232
+ }
233
+ catch { /* temp */ }
234
+ }
235
+ resolve(value);
236
+ };
237
+ const child = execFile(process.execPath, [resolveBundledPiCliPath(), ...nameArgs(body)], {
238
+ encoding: 'utf8',
239
+ timeout: NAME_TIMEOUT_MS,
240
+ env: { ...bundledPiSubprocessEnv(), CRTR_NAME_RESULT_FILE: resultFile },
241
+ }, () => {
242
+ // The tool call is the ONLY channel: a non-zero exit still counts when
243
+ // the emission already landed (pi print-mode can exit oddly after a
244
+ // terminating tool), and a clean exit without the file is a failure.
245
+ try {
246
+ const parsed = JSON.parse(readFileSync(resultFile, 'utf8'));
247
+ const name = sanitizeSessionName(typeof parsed['name'] === 'string' ? parsed['name'] : '');
248
+ const icon = sanitizeIcon(typeof parsed['icon'] === 'string' ? parsed['icon'] : '');
249
+ done(name !== '' ? { name, icon } : empty);
250
+ }
251
+ catch {
252
+ done(empty);
253
+ }
117
254
  });
118
255
  child.stdin?.end(); // immediate EOF — see the doc above
119
256
  }
120
257
  catch {
121
- resolve('');
258
+ if (dir !== null) {
259
+ try {
260
+ rmSync(dir, { recursive: true, force: true });
261
+ }
262
+ catch { /* temp */ }
263
+ }
264
+ resolve(empty);
122
265
  }
123
266
  });
124
267
  }
@@ -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";