@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.
- package/dist/api/dto/nodes.d.ts +3 -0
- package/dist/builtin-memory/internal/INDEX.md +1 -0
- package/dist/builtin-memory/internal/workflow-codification.md +82 -0
- package/dist/clients/attach/__tests__/bash-call-width.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/bash-call-width.test.js +38 -0
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +20 -0
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +3 -1
- package/dist/clients/attach/__tests__/markdown-headings.test.js +8 -0
- package/dist/clients/attach/chrome/bash-jobs.d.ts +5 -3
- package/dist/clients/attach/chrome/bash-jobs.js +15 -8
- package/dist/clients/attach/chrome/canvas-panels.js +10 -3
- package/dist/clients/attach/chrome/roster.d.ts +7 -2
- package/dist/clients/attach/chrome/roster.js +87 -3
- package/dist/clients/attach/input/controller.d.ts +3 -0
- package/dist/clients/attach/input/controller.js +1 -0
- package/dist/clients/attach/render/assistant-message.d.ts +13 -0
- package/dist/clients/attach/render/assistant-message.js +55 -0
- package/dist/clients/attach/render/chat-view.d.ts +29 -0
- package/dist/clients/attach/render/chat-view.js +233 -12
- package/dist/clients/attach/render/context-message.js +1 -1
- package/dist/clients/attach/render/edit-diff.js +20 -1
- package/dist/clients/attach/render/glyph-codepoints.d.ts +3 -0
- package/dist/clients/attach/render/glyph-codepoints.js +42 -0
- package/dist/clients/attach/render/markdown-headings.js +15 -4
- package/dist/clients/attach/render/tool-calls.d.ts +32 -0
- package/dist/clients/attach/render/tool-calls.js +251 -0
- package/dist/clients/attach/session/input-wiring.d.ts +3 -0
- package/dist/clients/attach/session/input-wiring.js +3 -0
- package/dist/clients/attach/slash/dispatch.d.ts +4 -0
- package/dist/clients/attach/slash/dispatch.js +8 -0
- package/dist/clients/attach/viewer.js +699 -695
- package/dist/commands/cron.js +2 -2
- package/dist/commands/node.js +3 -3
- package/dist/commands/search.js +1 -1
- package/dist/commands/sys/config.js +10 -2
- package/dist/commands/sys/doctor.js +22 -1
- package/dist/commands/sys/setup-core.d.ts +5 -1
- package/dist/commands/sys/setup-core.js +16 -2
- package/dist/commands/sys/setup-wizard.d.ts +11 -0
- package/dist/commands/sys/setup-wizard.js +111 -6
- package/dist/core/__tests__/tmux-surface.test.js +5 -2
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/labels.d.ts +6 -3
- package/dist/core/canvas/labels.js +9 -4
- package/dist/core/canvas/nav-render.d.ts +6 -0
- package/dist/core/canvas/nav-render.js +8 -0
- package/dist/core/canvas/types.d.ts +6 -0
- package/dist/core/config.js +3 -2
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -0
- package/dist/core/preview-registry.d.ts +5 -0
- package/dist/core/preview-registry.js +10 -0
- package/dist/core/runtime/canvas-extensions.d.ts +4 -0
- package/dist/core/runtime/canvas-extensions.js +4 -0
- package/dist/core/runtime/naming-persist.d.ts +4 -3
- package/dist/core/runtime/naming-persist.js +10 -9
- package/dist/core/runtime/naming.d.ts +28 -8
- package/dist/core/runtime/naming.js +175 -32
- package/dist/core/runtime/nerd-font.d.ts +28 -0
- package/dist/core/runtime/nerd-font.js +127 -0
- package/dist/core/runtime/tmux.js +16 -0
- package/dist/daemon/api/map.js +2 -0
- package/dist/pi-extensions/naming-tool.d.ts +24 -0
- package/dist/pi-extensions/naming-tool.js +67 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +1 -0
- package/dist/web-client/assets/index-B76ZKfT_.js +79 -0
- package/dist/web-client/assets/{index-CpEl9LTS.css → index-CqLKj8Xu.css} +1 -1
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/package.json +8 -8
- package/runtime.lock.json +161 -161
- 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
|
|
4
|
-
* never clobbers it). Non-blocking: safe to call from inside a live pi
|
|
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
|
|
12
|
-
* never clobbers it). Non-blocking: safe to call from inside a live pi
|
|
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 = (
|
|
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
|
|
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(
|
|
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
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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<
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
27
|
-
'
|
|
28
|
-
'`
|
|
29
|
-
'
|
|
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
|
|
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
|
-
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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";
|