@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
@@ -0,0 +1,127 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readdirSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ /** crtr's viewer renders tool calls, command icons, node badges and the
6
+ * inspector with Nerd Font private-use-area codepoints (see
7
+ * `core/preview-registry.ts` and `clients/attach/render/tool-calls.ts`), so a
8
+ * terminal without a Nerd Font shows tofu boxes for every icon. Nothing about
9
+ * the font is auto-detectable from inside the process — the terminal emulator
10
+ * picks the face — so the best we can do is detect an INSTALLED Nerd Font on
11
+ * the system and tell the user to point their terminal profile at it. */
12
+ /** Font-family and filename fragments that only appear in Nerd Font builds.
13
+ * `symbols` alone would false-positive on system symbol faces, so the
14
+ * symbols-only patch is matched with its full distributed name. */
15
+ const NERD_FONT_PATTERN = /nerd[ _-]?font|symbolsnerdfont|symbols-only/i;
16
+ /** Font directories searched when fontconfig is unavailable (always the case
17
+ * on macOS). User-level dirs first: an install crtr suggested lands there. */
18
+ function fontDirs(platform, home) {
19
+ if (platform === 'darwin') {
20
+ return [join(home, 'Library', 'Fonts'), '/Library/Fonts', '/System/Library/Fonts'];
21
+ }
22
+ return [
23
+ join(home, '.local', 'share', 'fonts'),
24
+ join(home, '.fonts'),
25
+ '/usr/local/share/fonts',
26
+ '/usr/share/fonts',
27
+ ];
28
+ }
29
+ /** Shallow recursive filename scan. Font trees nest a couple of levels
30
+ * (`/usr/share/fonts/truetype/<family>/`), so depth 3 reaches every real
31
+ * install without walking an unbounded tree. */
32
+ function scanForNerdFont(dir, depth) {
33
+ if (depth < 0)
34
+ return null;
35
+ let entries;
36
+ try {
37
+ entries = readdirSync(dir, { withFileTypes: true });
38
+ }
39
+ catch {
40
+ return null; // absent or unreadable directory is simply not a match
41
+ }
42
+ for (const entry of entries) {
43
+ if (entry.isDirectory()) {
44
+ const nested = scanForNerdFont(join(dir, entry.name), depth - 1);
45
+ if (nested !== null)
46
+ return nested;
47
+ continue;
48
+ }
49
+ if (!/\.(ttf|otf|ttc)$/i.test(entry.name))
50
+ continue;
51
+ if (NERD_FONT_PATTERN.test(entry.name))
52
+ return entry.name;
53
+ }
54
+ return null;
55
+ }
56
+ /** Ask fontconfig for installed families. Present on Linux, absent on macOS. */
57
+ function queryFontconfig() {
58
+ const probe = spawnSync('sh', ['-lc', 'command -v fc-list >/dev/null 2>&1'], { stdio: 'ignore' });
59
+ if (probe.status !== 0)
60
+ return null;
61
+ const res = spawnSync('fc-list', [':', 'family'], { encoding: 'utf8', timeout: 5000 });
62
+ if (res.status !== 0 || typeof res.stdout !== 'string')
63
+ return null;
64
+ for (const line of res.stdout.split('\n')) {
65
+ if (NERD_FONT_PATTERN.test(line))
66
+ return line.split(',')[0].trim();
67
+ }
68
+ return ''; // fontconfig answered and knows of no Nerd Font — authoritative miss
69
+ }
70
+ export function detectNerdFont(platform = process.platform, home = homedir()) {
71
+ if (platform !== 'darwin') {
72
+ const family = queryFontconfig();
73
+ if (family !== null && family !== '')
74
+ return { installed: true, evidence: family, source: 'fc-list' };
75
+ }
76
+ for (const dir of fontDirs(platform, home)) {
77
+ const file = scanForNerdFont(dir, 3);
78
+ if (file !== null)
79
+ return { installed: true, evidence: file, source: 'font-dir' };
80
+ }
81
+ return { installed: false, evidence: null, source: null };
82
+ }
83
+ /** Download the upstream symbols-only patch into the user's font dir. Works on
84
+ * every Linux regardless of package manager — Debian/Ubuntu and Fedora ship no
85
+ * Nerd Font package, so there is nothing native to install there. Needs no
86
+ * sudo: it writes to `~/.local/share/fonts`. */
87
+ export const NERD_FONT_MANUAL_LINUX_COMMAND = 'mkdir -p ~/.local/share/fonts && '
88
+ + 'curl -fsSL -o /tmp/NerdFontsSymbolsOnly.zip '
89
+ + 'https://github.com/ryanoasis/nerd-fonts/releases/latest/download/NerdFontsSymbolsOnly.zip && '
90
+ + 'unzip -oq /tmp/NerdFontsSymbolsOnly.zip -d ~/.local/share/fonts && fc-cache -f';
91
+ /** The install command for this platform/package manager. `packageManager`
92
+ * matches setup's detection: only brew and pacman have a real Nerd Font
93
+ * package, everything else on Linux gets the upstream download. A non-brew
94
+ * package manager only ever exists on Linux, so it settles the platform on its
95
+ * own and `platform` decides only the `manual` case. */
96
+ export function nerdFontInstallCommand(packageManager, platform = process.platform) {
97
+ const onLinux = platform !== 'darwin' || packageManager === 'apt-get' || packageManager === 'dnf' || packageManager === 'pacman';
98
+ if (packageManager === 'brew') {
99
+ return {
100
+ command: 'brew install --cask font-symbols-only-nerd-font',
101
+ hint: 'Homebrew cask',
102
+ autoInstall: true,
103
+ };
104
+ }
105
+ if (packageManager === 'pacman') {
106
+ return {
107
+ command: 'sudo pacman -S --noconfirm ttf-nerd-fonts-symbols',
108
+ hint: 'pacman install',
109
+ autoInstall: true,
110
+ };
111
+ }
112
+ if (onLinux) {
113
+ return {
114
+ command: NERD_FONT_MANUAL_LINUX_COMMAND,
115
+ hint: 'download symbols-only patch to ~/.local/share/fonts (needs curl + unzip)',
116
+ autoInstall: true,
117
+ };
118
+ }
119
+ return {
120
+ command: null,
121
+ hint: 'install Homebrew, or download a Nerd Font from https://nerdfonts.com and add it in Font Book',
122
+ autoInstall: false,
123
+ };
124
+ }
125
+ /** Every install path ends the same way: the font is on disk but the terminal
126
+ * emulator still has to be pointed at it, and crtr cannot do that. */
127
+ export 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) — installing it does not select it';
@@ -483,6 +483,8 @@ const MENU_ACTIONS = {
483
483
  // M-S-r carries shift+alt and lands as the same chord the keyboard produces.
484
484
  'crtr.tmux.menu.file-review': { description: 'review a file', action: { kind: 'raw-keys', keys: 'M-S-r' } },
485
485
  'crtr.tmux.menu.metadata': { description: 'metadata', action: { kind: 'keys', keys: '/node-metadata' } },
486
+ 'crtr.tmux.menu.fold-tools': { description: 'fold finished tool calls', action: { kind: 'keys', keys: '/fold-tools' } },
487
+ 'crtr.tmux.menu.view-settings': undefined,
486
488
  'crtr.tmux.menu.context': { description: 'context + reports', action: { kind: 'keys', keys: '/context' } },
487
489
  'crtr.tmux.menu.providers': { description: 'settings', action: { kind: 'popup', run: 'sys setup' } },
488
490
  'crtr.tmux.menu.graph': { description: 'graph view', action: { kind: 'keys', keys: '/graph' } },
@@ -627,6 +629,11 @@ function menuItems(bindings) {
627
629
  'crtr.tmux.menu.metadata',
628
630
  'crtr.tmux.menu.context',
629
631
  ]);
632
+ // How this viewer PAINTS the transcript, as opposed to what it shows. Each item
633
+ // is a toggle the viewer applies to its own render state (a `/`-command sent to
634
+ // the pane); a tmux menu is built once and cannot show live state, so the item
635
+ // reads as the action and the viewer answers with a notice.
636
+ const viewSettingsItems = boundItems(['crtr.tmux.menu.fold-tools']);
630
637
  const exploreItems = boundItems([
631
638
  'crtr.tmux.menu.resume',
632
639
  'crtr.tmux.menu.graph',
@@ -641,6 +648,15 @@ function menuItems(bindings) {
641
648
  });
642
649
  }
643
650
  }
651
+ if (viewSettingsItems.length > 0) {
652
+ for (const gesture of bindings.gestures('crtr.tmux.menu.view-settings')) {
653
+ exploreItems.push({
654
+ description: 'view settings ›',
655
+ selector: menuSelector(gesture),
656
+ action: { kind: 'submenu', title: ' view settings ', items: viewSettingsItems },
657
+ });
658
+ }
659
+ }
644
660
  // Explore changes what is in view or opens a canvas-level browser. Current
645
661
  // node acts on the agent in this pane without changing its lifecycle.
646
662
  addGroup('Explore', exploreItems);
@@ -105,6 +105,8 @@ export function toNodeDetailDTO(meta, edges) {
105
105
  };
106
106
  if (meta.description !== undefined)
107
107
  dto.description = meta.description;
108
+ if (meta.icon !== undefined)
109
+ dto.icon = meta.icon;
108
110
  if (meta.cycles !== undefined)
109
111
  dto.cycles = meta.cycles;
110
112
  dto.context_tokens = readContextTokens(meta.node_id);
@@ -0,0 +1,24 @@
1
+ interface PiLike {
2
+ registerTool: (definition: Record<string, unknown>) => void;
3
+ }
4
+ /** The naming schema — the ONE definition of what a generated session name is.
5
+ * Both fields are required: a name without an icon renders a bare label, and
6
+ * an icon without a name is useless, so the model is made to decide both in a
7
+ * single constrained emission. */
8
+ export declare const NAME_SESSION_SCHEMA: {
9
+ readonly type: "object";
10
+ readonly properties: {
11
+ readonly name: {
12
+ readonly type: "string";
13
+ readonly description: "A 3-8 word kebab-case session name: lowercase words joined by single hyphens, e.g. refactor-auth-token-flow. No punctuation, quotes, or prose.";
14
+ };
15
+ readonly icon: {
16
+ readonly type: "string";
17
+ readonly description: "The Nerd Font glyph depicting this session, as a codepoint in U+XXXX form (e.g. U+F188).";
18
+ };
19
+ };
20
+ readonly required: readonly ["name", "icon"];
21
+ readonly additionalProperties: false;
22
+ };
23
+ export declare function registerNamingTool(pi: PiLike): void;
24
+ export default registerNamingTool;
@@ -0,0 +1,67 @@
1
+ // naming-tool.ts — the structured-output seam for session naming.
2
+ //
3
+ // Loaded ONLY by the headless namer subprocess (`runtime/naming.ts` passes it
4
+ // with `-e` alongside `--no-extensions`), never by a canvas node. It registers a
5
+ // single `name_session` tool whose parameters ARE the naming schema, with
6
+ // pi's constrained sampling turned on, so the light model emits a real typed
7
+ // object instead of prose we have to parse out of a text reply.
8
+ //
9
+ // The result travels back to the parent over a file: the parent creates a temp
10
+ // path, passes it as CRTR_NAME_RESULT_FILE, and reads it after the subprocess
11
+ // exits. stdout is unusable for this — pi print-mode writes its own chatter
12
+ // there.
13
+ //
14
+ // Inert (registers nothing) when CRTR_NAME_RESULT_FILE is absent, so an
15
+ // accidental load in any other context is a no-op.
16
+ import { mkdirSync, writeFileSync } from 'node:fs';
17
+ import { dirname } from 'node:path';
18
+ /** The naming schema — the ONE definition of what a generated session name is.
19
+ * Both fields are required: a name without an icon renders a bare label, and
20
+ * an icon without a name is useless, so the model is made to decide both in a
21
+ * single constrained emission. */
22
+ export const NAME_SESSION_SCHEMA = {
23
+ type: 'object',
24
+ properties: {
25
+ name: {
26
+ type: 'string',
27
+ description: 'A 3-8 word kebab-case session name: lowercase words joined by single hyphens, e.g. refactor-auth-token-flow. No punctuation, quotes, or prose.',
28
+ },
29
+ icon: {
30
+ type: 'string',
31
+ description: 'The Nerd Font glyph depicting this session, as a codepoint in U+XXXX form (e.g. U+F188).',
32
+ },
33
+ },
34
+ required: ['name', 'icon'],
35
+ additionalProperties: false,
36
+ };
37
+ export function registerNamingTool(pi) {
38
+ const out = process.env['CRTR_NAME_RESULT_FILE'];
39
+ if (out === undefined || out.trim() === '')
40
+ return;
41
+ pi.registerTool({
42
+ name: 'name_session',
43
+ label: 'Name session',
44
+ description: 'Record the name and icon for this coding session. Call exactly once, then stop.',
45
+ parameters: NAME_SESSION_SCHEMA,
46
+ // Ask the provider to constrain sampling to the schema. `prefer` degrades
47
+ // silently on a model without strict tool support (`require` would fail the
48
+ // whole request); the parent validates and falls back to a local slug
49
+ // either way, so this only makes a malformed emission rarer.
50
+ constrainedSampling: { type: 'json_schema', strict: 'prefer' },
51
+ execute(_id, params) {
52
+ try {
53
+ mkdirSync(dirname(out), { recursive: true });
54
+ writeFileSync(out, JSON.stringify(params ?? {}), 'utf8');
55
+ }
56
+ catch {
57
+ // The parent falls back to a local slug when the file never lands.
58
+ }
59
+ return {
60
+ content: [{ type: 'text', text: 'Recorded.' }],
61
+ details: { recorded: true },
62
+ terminate: true,
63
+ };
64
+ },
65
+ });
66
+ }
67
+ export default registerNamingTool;
package/dist/types.d.ts CHANGED
@@ -152,6 +152,11 @@ export interface ScopeConfig {
152
152
  * `both` keeps both. Tool calls and results are never kept. Full detail for
153
153
  * every cycle stays in the session file. */
154
154
  condensed_history: CondensedHistoryMode;
155
+ /** Fold every SETTLED tool call in the attach viewer down to its single call
156
+ * line (`/fold-tools`, Alt+C → w → f). A tool still running is never folded —
157
+ * it folds itself once it settles. Toggling it in a viewer writes back here,
158
+ * so the choice carries into later chats and later viewers. Default false. */
159
+ fold_finished_tools: boolean;
155
160
  keybindings: KeybindingOverrides;
156
161
  modelLadders: ModelLaddersConfig;
157
162
  /** The kind registry (spec §1.5): kind existence + launch knobs, keyed by
package/dist/types.js CHANGED
@@ -38,6 +38,7 @@ export function defaultScopeConfig() {
38
38
  completion_bell: true,
39
39
  live_cycles: DEFAULT_LIVE_CYCLES,
40
40
  condensed_history: DEFAULT_CONDENSED_HISTORY,
41
+ fold_finished_tools: false,
41
42
  keybindings: {},
42
43
  modelLadders: defaultModelLaddersConfig(),
43
44
  kinds: defaultKindsConfig(),