@north-light/crouter 0.3.164 → 0.3.165

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 (155) hide show
  1. package/dist/build-root.d.ts +6 -6
  2. package/dist/build-root.js +12 -13
  3. package/dist/builtin-memory/00-runtime-base.md +3 -4
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  5. package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
  7. package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
  8. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
  9. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
  10. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
  11. package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
  12. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
  13. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
  14. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
  15. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
  16. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
  17. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
  18. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
  20. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
  21. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
  22. package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
  23. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
  24. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
  26. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
  27. package/dist/builtin-memory/advisor/council.md +40 -0
  28. package/dist/builtin-memory/design.md +8 -11
  29. package/dist/builtin-memory/development.md +6 -9
  30. package/dist/builtin-memory/internal/INDEX.md +4 -5
  31. package/dist/builtin-memory/internal/agent-shaping.md +5 -7
  32. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
  33. package/dist/builtin-memory/internal/marketplaces.md +13 -14
  34. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
  35. package/dist/builtin-memory/internal/plugins.md +61 -45
  36. package/dist/builtin-memory/planning.md +4 -7
  37. package/dist/builtin-memory/spec.md +9 -12
  38. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  39. package/dist/cli.js +1 -1
  40. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  41. package/dist/clients/attach/__tests__/context-message.test.js +31 -20
  42. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  43. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  44. package/dist/clients/attach/render/chat-view.js +42 -54
  45. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  46. package/dist/clients/attach/render/condensed-history.js +60 -0
  47. package/dist/clients/attach/render/context-message.js +9 -14
  48. package/dist/clients/attach/session/bindings.d.ts +5 -3
  49. package/dist/clients/attach/session/bindings.js +7 -13
  50. package/dist/clients/attach/session/keys.d.ts +2 -0
  51. package/dist/clients/attach/session/keys.js +36 -37
  52. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  53. package/dist/clients/attach/session/profile-files.js +157 -0
  54. package/dist/clients/attach/viewer.js +525 -525
  55. package/dist/commands/node.js +2 -2
  56. package/dist/commands/pkg/plugin-inspect.js +6 -7
  57. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  58. package/dist/commands/pkg/plugin-manage.js +131 -19
  59. package/dist/commands/pkg/plugin.js +2 -2
  60. package/dist/commands/pkg.js +6 -11
  61. package/dist/commands/profile/env.js +3 -3
  62. package/dist/commands/sys/config.js +17 -76
  63. package/dist/commands/sys/doctor.js +5 -91
  64. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  65. package/dist/commands/sys/setup-wizard.js +47 -81
  66. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  67. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  68. package/dist/core/__tests__/command-plugins.test.js +36 -16
  69. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  70. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  71. package/dist/core/command-manifests/manifest.d.ts +24 -0
  72. package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
  73. package/dist/core/command-manifests/registry.d.ts +1 -2
  74. package/dist/core/command-manifests/registry.js +2 -2
  75. package/dist/core/command-manifests/schema.d.ts +11 -13
  76. package/dist/core/command-manifests/schema.js +53 -192
  77. package/dist/core/command-plugins/compose.d.ts +0 -6
  78. package/dist/core/command-plugins/compose.js +28 -75
  79. package/dist/core/command-plugins/discovery.d.ts +25 -58
  80. package/dist/core/command-plugins/discovery.js +152 -259
  81. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  82. package/dist/core/command-plugins/endpoint.js +48 -0
  83. package/dist/core/command-plugins/store.d.ts +16 -0
  84. package/dist/core/command-plugins/store.js +64 -0
  85. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  86. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  87. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  88. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  89. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  90. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  91. package/dist/core/command.d.ts +8 -9
  92. package/dist/core/command.js +6 -9
  93. package/dist/core/config.js +6 -10
  94. package/dist/core/env-name.d.ts +6 -0
  95. package/dist/core/env-name.js +9 -0
  96. package/dist/core/io.d.ts +1 -1
  97. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  98. package/dist/core/keybindings/attach-control.d.ts +37 -0
  99. package/dist/core/keybindings/attach-control.js +38 -0
  100. package/dist/core/keybindings/catalog.d.ts +5 -4
  101. package/dist/core/keybindings/catalog.js +16 -8
  102. package/dist/core/keybindings/index.d.ts +1 -0
  103. package/dist/core/keybindings/index.js +1 -0
  104. package/dist/core/keybindings/types.d.ts +1 -1
  105. package/dist/core/preview-registry.js +41 -74
  106. package/dist/core/runtime/bearings.d.ts +2 -5
  107. package/dist/core/runtime/bearings.js +2 -5
  108. package/dist/core/runtime/front-door.d.ts +1 -1
  109. package/dist/core/runtime/front-door.js +2 -2
  110. package/dist/core/runtime/kickoff.d.ts +3 -3
  111. package/dist/core/runtime/kickoff.js +4 -3
  112. package/dist/core/runtime/situational-context.d.ts +1 -1
  113. package/dist/core/runtime/situational-context.js +1 -1
  114. package/dist/core/runtime/spawn.js +9 -9
  115. package/dist/core/runtime/tmux.js +29 -2
  116. package/dist/core/scope.d.ts +0 -5
  117. package/dist/core/scope.js +0 -10
  118. package/dist/core/user-settings.d.ts +193 -0
  119. package/dist/core/user-settings.js +252 -0
  120. package/dist/index.d.ts +4 -0
  121. package/dist/index.js +3 -0
  122. package/dist/pi-extensions/canvas-stophook.js +3 -2
  123. package/dist/shared/generated-context.d.ts +34 -0
  124. package/dist/shared/generated-context.js +98 -0
  125. package/dist/types.d.ts +23 -16
  126. package/dist/types.js +3 -14
  127. package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
  128. package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
  129. package/dist/web-client/index.html +2 -2
  130. package/dist/web-client/sw.js +1 -1
  131. package/docs/public-api.md +1 -0
  132. package/package.json +1 -1
  133. package/runtime.lock.json +2 -2
  134. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  135. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  136. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  137. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  138. package/dist/builtin-memory/product.md +0 -80
  139. package/dist/clients/attach/render/frozen-history.js +0 -100
  140. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  141. package/dist/commands/pkg/cli-inspect.js +0 -190
  142. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  143. package/dist/commands/pkg/cli-manage.js +0 -206
  144. package/dist/commands/pkg/cli.d.ts +0 -1
  145. package/dist/commands/pkg/cli.js +0 -14
  146. package/dist/core/configured-clis/cache.d.ts +0 -16
  147. package/dist/core/configured-clis/cache.js +0 -57
  148. package/dist/core/configured-clis/compose.d.ts +0 -14
  149. package/dist/core/configured-clis/compose.js +0 -60
  150. package/dist/core/configured-clis/discovery.d.ts +0 -47
  151. package/dist/core/configured-clis/discovery.js +0 -173
  152. package/dist/core/configured-clis/manifest.d.ts +0 -24
  153. package/dist/core/configured-clis/registration.d.ts +0 -40
  154. package/dist/core/configured-clis/registration.js +0 -201
  155. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -25,11 +25,6 @@ export declare function requireScopeRoot(scope: Scope): string;
25
25
  export declare function ensureProjectScopeRoot(startDir?: string): string;
26
26
  export declare function pluginsDir(scope: Scope): string | null;
27
27
  export declare function marketplacesDir(scope: Scope): string | null;
28
- export declare function configuredClisDir(scope: Scope): string | null;
29
- /** The configured-CLIs cache parent dir under an EXACT scope-root dir. Used to
30
- * address per-root caches when multiple project roots are visible; the single
31
- * source of the `configured-clis` path segment. */
32
- export declare function configuredClisDirForRoot(scopeRootDir: string): string;
33
28
  /** Where substrate memory documents live per scope. Builtin memory is the
34
29
  * special case — its own package dir (builtinMemoryRoot), since the builtin
35
30
  * scope has no scopeRoot — mirroring viewsDir's builtin handling. User and
@@ -195,16 +195,6 @@ export function marketplacesDir(scope) {
195
195
  const root = scopeRoot(scope);
196
196
  return root ? join(root, 'marketplaces') : null;
197
197
  }
198
- export function configuredClisDir(scope) {
199
- const root = scopeRoot(scope);
200
- return root ? configuredClisDirForRoot(root) : null;
201
- }
202
- /** The configured-CLIs cache parent dir under an EXACT scope-root dir. Used to
203
- * address per-root caches when multiple project roots are visible; the single
204
- * source of the `configured-clis` path segment. */
205
- export function configuredClisDirForRoot(scopeRootDir) {
206
- return join(scopeRootDir, 'configured-clis');
207
- }
208
198
  /** Where substrate memory documents live per scope. Builtin memory is the
209
199
  * special case — its own package dir (builtinMemoryRoot), since the builtin
210
200
  * scope has no scopeRoot — mirroring viewsDir's builtin handling. User and
@@ -0,0 +1,193 @@
1
+ import type { ScopeConfig } from '../types.js';
2
+ export interface UserSettingDefinition<Key extends string = string> {
3
+ key: Key;
4
+ label: string;
5
+ description: string;
6
+ valueConstraint: string;
7
+ applies: string;
8
+ order: number;
9
+ format: (config: ScopeConfig) => string;
10
+ cycle: (config: ScopeConfig, delta: number) => void;
11
+ /** Mutate `config` from a CLI string, returning a user-facing validation error on failure. */
12
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
13
+ }
14
+ /**
15
+ * Exhaustive settings-surface coverage for ScopeConfig. Primitive preferences
16
+ * are required by the type to define a Settings row; structured registries must
17
+ * name the specialized surface that owns them. Adding a config field therefore
18
+ * fails typecheck until its user-facing settings coverage is decided. The
19
+ * auto_update nested record is exhaustive for the same reason.
20
+ */
21
+ export declare const CONFIG_SETTINGS_COVERAGE: {
22
+ schema_version: {
23
+ surface: "specialized";
24
+ owner: string;
25
+ };
26
+ marketplaces: {
27
+ surface: "specialized";
28
+ owner: string;
29
+ };
30
+ plugins: {
31
+ surface: "specialized";
32
+ owner: string;
33
+ };
34
+ auto_update: {
35
+ surface: "settings";
36
+ settings: {
37
+ crtr: {
38
+ key: "auto_update.crtr";
39
+ label: string;
40
+ description: string;
41
+ valueConstraint: string;
42
+ applies: string;
43
+ order: number;
44
+ format: (config: ScopeConfig) => "notify" | "apply" | "off";
45
+ cycle: (config: ScopeConfig, delta: number) => void;
46
+ setFromString: (config: ScopeConfig, raw: string) => "auto_update.crtr must be notify, apply, or false" | null;
47
+ };
48
+ content: {
49
+ key: "auto_update.content";
50
+ label: string;
51
+ description: string;
52
+ valueConstraint: string;
53
+ applies: string;
54
+ order: number;
55
+ format: (config: ScopeConfig) => "notify" | "apply" | "off";
56
+ cycle: (config: ScopeConfig, delta: number) => void;
57
+ setFromString: (config: ScopeConfig, raw: string) => "auto_update.content must be notify, apply, or false" | null;
58
+ };
59
+ interval_hours: {
60
+ key: "auto_update.interval_hours";
61
+ label: string;
62
+ description: string;
63
+ valueConstraint: string;
64
+ applies: string;
65
+ order: number;
66
+ format: (config: ScopeConfig) => string;
67
+ cycle: (config: ScopeConfig, delta: number) => void;
68
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
69
+ };
70
+ };
71
+ };
72
+ max_panes_per_window: {
73
+ surface: "settings";
74
+ setting: {
75
+ key: "max_panes_per_window";
76
+ label: string;
77
+ description: string;
78
+ valueConstraint: string;
79
+ applies: string;
80
+ order: number;
81
+ format: (config: ScopeConfig) => string;
82
+ cycle: (config: ScopeConfig, delta: number) => void;
83
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
84
+ };
85
+ };
86
+ auto_open_child_viewers: {
87
+ surface: "settings";
88
+ setting: {
89
+ key: "auto_open_child_viewers";
90
+ label: string;
91
+ description: string;
92
+ valueConstraint: string;
93
+ applies: string;
94
+ order: number;
95
+ format: (config: ScopeConfig) => "off" | "on";
96
+ cycle: (config: ScopeConfig) => void;
97
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
98
+ };
99
+ };
100
+ brokerThresholds: {
101
+ surface: "specialized";
102
+ owner: string;
103
+ };
104
+ completion_bell: {
105
+ surface: "settings";
106
+ setting: {
107
+ key: "completion_bell";
108
+ label: string;
109
+ description: string;
110
+ valueConstraint: string;
111
+ applies: string;
112
+ order: number;
113
+ format: (config: ScopeConfig) => "off" | "on";
114
+ cycle: (config: ScopeConfig) => void;
115
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
116
+ };
117
+ };
118
+ live_cycles: {
119
+ surface: "settings";
120
+ setting: {
121
+ key: "live_cycles";
122
+ label: string;
123
+ description: string;
124
+ valueConstraint: string;
125
+ applies: string;
126
+ order: number;
127
+ format: (config: ScopeConfig) => string;
128
+ cycle: (config: ScopeConfig, delta: number) => void;
129
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
130
+ };
131
+ };
132
+ condensed_history: {
133
+ surface: "settings";
134
+ setting: {
135
+ key: "condensed_history";
136
+ label: string;
137
+ description: string;
138
+ valueConstraint: string;
139
+ applies: string;
140
+ order: number;
141
+ format: (config: ScopeConfig) => "user" | "none" | "agent" | "both";
142
+ cycle: (config: ScopeConfig, delta: number) => void;
143
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
144
+ };
145
+ };
146
+ fold_finished_tools: {
147
+ surface: "settings";
148
+ setting: {
149
+ key: "fold_finished_tools";
150
+ label: string;
151
+ description: string;
152
+ valueConstraint: string;
153
+ applies: string;
154
+ order: number;
155
+ format: (config: ScopeConfig) => "off" | "on";
156
+ cycle: (config: ScopeConfig) => void;
157
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
158
+ };
159
+ };
160
+ keybindings: {
161
+ surface: "specialized";
162
+ owner: string;
163
+ };
164
+ modelLadders: {
165
+ surface: "specialized";
166
+ owner: string;
167
+ };
168
+ modelRoutes: {
169
+ surface: "specialized";
170
+ owner: string;
171
+ };
172
+ modelRouting: {
173
+ surface: "specialized";
174
+ owner: string;
175
+ };
176
+ kinds: {
177
+ surface: "specialized";
178
+ owner: string;
179
+ };
180
+ remoteCanvas: {
181
+ surface: "specialized";
182
+ owner: string;
183
+ };
184
+ spawnEnv: {
185
+ surface: "specialized";
186
+ owner: string;
187
+ };
188
+ };
189
+ /** The exhaustive top-level key list used by config persistence and help. */
190
+ export declare const SCOPE_CONFIG_KEYS: (keyof ScopeConfig)[];
191
+ /** The single row registry shared by `sys setup` and `sys config set`. */
192
+ export declare const USER_SETTINGS: readonly UserSettingDefinition[];
193
+ export declare function findUserSetting(key: string): UserSettingDefinition | undefined;
@@ -0,0 +1,252 @@
1
+ import { CONDENSED_HISTORY_MODES } from '../types.js';
2
+ function cycleChoice(choices, current, delta) {
3
+ const index = choices.indexOf(current);
4
+ const start = index >= 0 ? index : 0;
5
+ return choices[(start + delta + choices.length) % choices.length];
6
+ }
7
+ function cycleNumber(choices, current, delta) {
8
+ const exact = choices.indexOf(current);
9
+ if (exact >= 0)
10
+ return choices[(exact + delta + choices.length) % choices.length];
11
+ if (delta >= 0)
12
+ return choices.find((choice) => choice > current) ?? choices[0];
13
+ return [...choices].reverse().find((choice) => choice < current) ?? choices[choices.length - 1];
14
+ }
15
+ function booleanFromString(raw, key) {
16
+ if (raw === 'true')
17
+ return true;
18
+ if (raw === 'false')
19
+ return false;
20
+ return `${key} must be true or false`;
21
+ }
22
+ function nonNegativeIntegerFromString(raw, key) {
23
+ if (!/^\d+$/.test(raw))
24
+ return `${key} must be an integer >= 0`;
25
+ const value = Number(raw);
26
+ if (!Number.isSafeInteger(value))
27
+ return `${key} must be an integer >= 0`;
28
+ return value;
29
+ }
30
+ function positiveIntegerFromString(raw, key) {
31
+ const value = nonNegativeIntegerFromString(raw, key);
32
+ if (typeof value === 'string' || value < 1)
33
+ return `${key} must be an integer >= 1`;
34
+ return value;
35
+ }
36
+ const AUTO_UPDATE_MODES = [false, 'notify', 'apply'];
37
+ const UPDATE_INTERVAL_HOURS = [0, 1, 6, 12, 24, 48, 72, 168];
38
+ const MAX_PANES = [1, 2, 3, 4, 5, 6, 8, 10, 12];
39
+ const LIVE_CYCLES = [1, 2, 3, 4, 5, 6];
40
+ /**
41
+ * Exhaustive settings-surface coverage for ScopeConfig. Primitive preferences
42
+ * are required by the type to define a Settings row; structured registries must
43
+ * name the specialized surface that owns them. Adding a config field therefore
44
+ * fails typecheck until its user-facing settings coverage is decided. The
45
+ * auto_update nested record is exhaustive for the same reason.
46
+ */
47
+ export const CONFIG_SETTINGS_COVERAGE = {
48
+ schema_version: { surface: 'specialized', owner: 'internal schema migration' },
49
+ marketplaces: { surface: 'specialized', owner: 'Plugins and pkg market commands' },
50
+ plugins: { surface: 'specialized', owner: 'Plugins tab and pkg plugin commands' },
51
+ auto_update: {
52
+ surface: 'settings',
53
+ settings: {
54
+ crtr: {
55
+ key: 'auto_update.crtr',
56
+ label: 'crouter updates',
57
+ description: 'Notify about crouter releases, install them automatically, or turn checks off.',
58
+ valueConstraint: 'true|false|notify|apply; true stores notify',
59
+ applies: 'Applies on the next crouter command.',
60
+ order: 70,
61
+ format: (config) => config.auto_update.crtr === false ? 'off' : config.auto_update.crtr,
62
+ cycle: (config, delta) => { config.auto_update.crtr = cycleChoice(AUTO_UPDATE_MODES, config.auto_update.crtr, delta); },
63
+ setFromString: (config, raw) => {
64
+ const value = raw === 'true' ? 'notify' : raw;
65
+ if (value !== 'notify' && value !== 'apply' && value !== 'false')
66
+ return 'auto_update.crtr must be notify, apply, or false';
67
+ config.auto_update.crtr = value === 'false' ? false : value;
68
+ return null;
69
+ },
70
+ },
71
+ content: {
72
+ key: 'auto_update.content',
73
+ label: 'Content updates',
74
+ description: 'Notify about plugin and marketplace updates, apply them automatically, or turn checks off.',
75
+ valueConstraint: 'false|notify|apply',
76
+ applies: 'Applies on the next crouter command.',
77
+ order: 80,
78
+ format: (config) => config.auto_update.content === false ? 'off' : config.auto_update.content,
79
+ cycle: (config, delta) => { config.auto_update.content = cycleChoice(AUTO_UPDATE_MODES, config.auto_update.content, delta); },
80
+ setFromString: (config, raw) => {
81
+ if (raw !== 'notify' && raw !== 'apply' && raw !== 'false')
82
+ return 'auto_update.content must be notify, apply, or false';
83
+ config.auto_update.content = raw === 'false' ? false : raw;
84
+ return null;
85
+ },
86
+ },
87
+ interval_hours: {
88
+ key: 'auto_update.interval_hours',
89
+ label: 'Update interval',
90
+ description: 'Minimum hours between automatic update checks. Zero checks on every command.',
91
+ valueConstraint: 'integer >= 0',
92
+ applies: 'Applies on the next crouter command.',
93
+ order: 90,
94
+ format: (config) => `${config.auto_update.interval_hours}h`,
95
+ cycle: (config, delta) => { config.auto_update.interval_hours = cycleNumber(UPDATE_INTERVAL_HOURS, config.auto_update.interval_hours, delta); },
96
+ setFromString: (config, raw) => {
97
+ const value = nonNegativeIntegerFromString(raw, 'auto_update.interval_hours');
98
+ if (typeof value === 'string')
99
+ return value;
100
+ config.auto_update.interval_hours = value;
101
+ return null;
102
+ },
103
+ },
104
+ },
105
+ },
106
+ max_panes_per_window: {
107
+ surface: 'settings',
108
+ setting: {
109
+ key: 'max_panes_per_window',
110
+ label: 'Maximum panes',
111
+ description: 'Maximum human-review panes placed in one tmux window before using another window.',
112
+ valueConstraint: 'integer >= 1',
113
+ applies: 'Applies to the next pane placement.',
114
+ order: 20,
115
+ format: (config) => String(config.max_panes_per_window),
116
+ cycle: (config, delta) => { config.max_panes_per_window = cycleNumber(MAX_PANES, config.max_panes_per_window, delta); },
117
+ setFromString: (config, raw) => {
118
+ const value = positiveIntegerFromString(raw, 'max_panes_per_window');
119
+ if (typeof value === 'string')
120
+ return value;
121
+ config.max_panes_per_window = value;
122
+ return null;
123
+ },
124
+ },
125
+ },
126
+ auto_open_child_viewers: {
127
+ surface: 'settings',
128
+ setting: {
129
+ key: 'auto_open_child_viewers',
130
+ label: 'Open child panes',
131
+ description: 'Open each managed child beside its watched parent. Off keeps new children running in the background.',
132
+ valueConstraint: 'true|false',
133
+ applies: 'Applies to the next managed child created; independent roots still open their attended viewer.',
134
+ order: 10,
135
+ format: (config) => config.auto_open_child_viewers ? 'on' : 'off',
136
+ cycle: (config) => { config.auto_open_child_viewers = !config.auto_open_child_viewers; },
137
+ setFromString: (config, raw) => {
138
+ const value = booleanFromString(raw, 'auto_open_child_viewers');
139
+ if (typeof value === 'string')
140
+ return value;
141
+ config.auto_open_child_viewers = value;
142
+ return null;
143
+ },
144
+ },
145
+ },
146
+ brokerThresholds: { surface: 'specialized', owner: 'Broker limits tab' },
147
+ completion_bell: {
148
+ surface: 'settings',
149
+ setting: {
150
+ key: 'completion_bell',
151
+ label: 'Completion bell',
152
+ description: 'Ring a short sound when the node in an attached viewer finishes a turn.',
153
+ valueConstraint: 'true|false',
154
+ applies: 'Applies on the next turn end.',
155
+ order: 40,
156
+ format: (config) => config.completion_bell ? 'on' : 'off',
157
+ cycle: (config) => { config.completion_bell = !config.completion_bell; },
158
+ setFromString: (config, raw) => {
159
+ const value = booleanFromString(raw, 'completion_bell');
160
+ if (typeof value === 'string')
161
+ return value;
162
+ config.completion_bell = value;
163
+ return null;
164
+ },
165
+ },
166
+ },
167
+ live_cycles: {
168
+ surface: 'settings',
169
+ setting: {
170
+ key: 'live_cycles',
171
+ label: 'Live cycles',
172
+ description: 'Trailing refresh cycles rendered at full fidelity; older cycles condense to text.',
173
+ valueConstraint: 'integer >= 1',
174
+ applies: 'Applies on the next attach.',
175
+ order: 50,
176
+ format: (config) => String(config.live_cycles),
177
+ cycle: (config, delta) => { config.live_cycles = cycleNumber(LIVE_CYCLES, config.live_cycles, delta); },
178
+ setFromString: (config, raw) => {
179
+ const value = positiveIntegerFromString(raw, 'live_cycles');
180
+ if (typeof value === 'string')
181
+ return value;
182
+ config.live_cycles = value;
183
+ return null;
184
+ },
185
+ },
186
+ },
187
+ condensed_history: {
188
+ surface: 'settings',
189
+ setting: {
190
+ key: 'condensed_history',
191
+ label: 'Condensed history',
192
+ description: 'Messages retained from condensed cycles. Tool calls are never kept.',
193
+ valueConstraint: CONDENSED_HISTORY_MODES.join('|'),
194
+ applies: 'Applies on the next attach.',
195
+ order: 60,
196
+ format: (config) => config.condensed_history,
197
+ cycle: (config, delta) => { config.condensed_history = cycleChoice(CONDENSED_HISTORY_MODES, config.condensed_history, delta); },
198
+ setFromString: (config, raw) => {
199
+ if (!CONDENSED_HISTORY_MODES.includes(raw))
200
+ return `condensed_history must be one of: ${CONDENSED_HISTORY_MODES.join('|')}`;
201
+ config.condensed_history = raw;
202
+ return null;
203
+ },
204
+ },
205
+ },
206
+ fold_finished_tools: {
207
+ surface: 'settings',
208
+ setting: {
209
+ key: 'fold_finished_tools',
210
+ label: 'Fold finished tools',
211
+ description: 'Fold every settled tool call to its call line. Toggle live with /fold-tools.',
212
+ valueConstraint: 'true|false',
213
+ applies: 'Applies on the next attach.',
214
+ order: 30,
215
+ format: (config) => config.fold_finished_tools ? 'on' : 'off',
216
+ cycle: (config) => { config.fold_finished_tools = !config.fold_finished_tools; },
217
+ setFromString: (config, raw) => {
218
+ const value = booleanFromString(raw, 'fold_finished_tools');
219
+ if (typeof value === 'string')
220
+ return value;
221
+ config.fold_finished_tools = value;
222
+ return null;
223
+ },
224
+ },
225
+ },
226
+ keybindings: { surface: 'specialized', owner: 'Keybindings tab' },
227
+ modelLadders: { surface: 'specialized', owner: 'Models tab' },
228
+ modelRoutes: { surface: 'specialized', owner: 'Models route declarations' },
229
+ modelRouting: { surface: 'specialized', owner: 'Models route declarations' },
230
+ kinds: { surface: 'specialized', owner: 'Models tab and kind registry' },
231
+ remoteCanvas: { surface: 'specialized', owner: 'canvas config commands' },
232
+ spawnEnv: { surface: 'specialized', owner: 'spawn environment allow-list' },
233
+ };
234
+ /** The exhaustive top-level key list used by config persistence and help. */
235
+ export const SCOPE_CONFIG_KEYS = Object.keys(CONFIG_SETTINGS_COVERAGE);
236
+ function collectUserSettings() {
237
+ const out = [];
238
+ for (const coverage of Object.values(CONFIG_SETTINGS_COVERAGE)) {
239
+ if (coverage.surface !== 'settings')
240
+ continue;
241
+ if ('setting' in coverage)
242
+ out.push(coverage.setting);
243
+ else
244
+ out.push(...Object.values(coverage.settings));
245
+ }
246
+ return out.sort((a, b) => a.order - b.order);
247
+ }
248
+ /** The single row registry shared by `sys setup` and `sys config set`. */
249
+ export const USER_SETTINGS = collectUserSettings();
250
+ export function findUserSetting(key) {
251
+ return USER_SETTINGS.find((setting) => setting.key === key);
252
+ }
package/dist/index.d.ts CHANGED
@@ -21,3 +21,7 @@ export type { CrtrError } from './core/errors.js';
21
21
  export { nowIso } from './core/fs-utils.js';
22
22
  export { validateOperationId, buildEnvelope, operationIdContext, errorClassCodes, serializeEventRecord, errorClassFromError, } from './core/events/index.js';
23
23
  export type { BuildEnvelopeInput, ErrorClass, ErrorClassCode, ErrorClassHints, EventComponent, EventEnvelope, EventError, EventField, EventLevel, EventOutcome, OperationId, OperationIdContext, RetryDisposition, } from './core/events/index.js';
24
+ export { validateCommandManifest } from './core/command-manifests/manifest.js';
25
+ export type { CommandManifestValidation, ValidatedCommandManifest } from './core/command-manifests/manifest.js';
26
+ export type { CommandManifestIssue, TransportKind } from './core/command-manifests/schema.js';
27
+ export { SUBTREE_NAMES } from './build-root.js';
package/dist/index.js CHANGED
@@ -42,3 +42,6 @@ export { nowIso } from './core/fs-utils.js';
42
42
  // Pure envelope helpers only. Local source binding and canonical append remain
43
43
  // internal so package consumers cannot select crouter runtime file paths.
44
44
  export { validateOperationId, buildEnvelope, operationIdContext, errorClassCodes, serializeEventRecord, errorClassFromError, } from './core/events/index.js';
45
+ // ── Command-manifest compatibility ───────────────────────────────────────
46
+ export { validateCommandManifest } from './core/command-manifests/manifest.js';
47
+ export { SUBTREE_NAMES } from './build-root.js';
@@ -44,6 +44,7 @@ import { clearStopSignals, consumeStopSignals } from '../core/runtime/stop-signa
44
44
  import { cliClient } from '../commands/api-client.js';
45
45
  import { focusOf, tearDownNode } from '../core/runtime/placement.js';
46
46
  import { readCursor, readInboxSince } from '../core/feed/inbox.js';
47
+ import { CONTEXT_NUDGE_CUSTOM_TYPE, formatContextNudge } from '../shared/generated-context.js';
47
48
  /** Universal upper bound for an unfocused broker parked without runnable work. */
48
49
  export const UNATTENDED_PARK_MS = (() => {
49
50
  const testOverride = process.env['CRTR_TEST_UNATTENDED_PARK_MS'];
@@ -696,12 +697,12 @@ export function registerCanvasStophook(pi) {
696
697
  const at = contextTokens !== null ? steerBand(contextTokens) : null;
697
698
  if (at !== null && !firedBands.has(at)) {
698
699
  firedBands.add(at);
699
- const note = `[crtr] ${steerNote(at, lifecycle, mode)}`;
700
+ const note = formatContextNudge(steerNote(at, lifecycle, mode));
700
701
  if (runContinues) {
701
702
  pi.sendUserMessage(note, { deliverAs: 'steer' });
702
703
  }
703
704
  else {
704
- pi.sendMessage({ customType: 'crtr-context-nudge', content: note, display: true }, { deliverAs: 'nextTurn' });
705
+ pi.sendMessage({ customType: CONTEXT_NUDGE_CUSTOM_TYPE, content: note, display: true }, { deliverAs: 'nextTurn' });
705
706
  }
706
707
  }
707
708
  }
@@ -0,0 +1,34 @@
1
+ /** Custom message carrying the node's session-start bearings. */
2
+ export declare const CONTEXT_INTRO_CUSTOM_TYPE = "crtr-context";
3
+ /** Custom message carrying an ambient situational-context update. */
4
+ export declare const SITUATIONAL_CONTEXT_CUSTOM_TYPE = "crtr-situational-context";
5
+ /** Custom message used when a context-size nudge waits for the next turn. */
6
+ export declare const CONTEXT_NUDGE_CUSTOM_TYPE = "crtr-context-nudge";
7
+ /** Opening text of a fresh-revive kickoff user message. */
8
+ export declare const REVIVE_KICKOFF_SENTINEL = "You have been revived fresh after a context refresh";
9
+ /** Prefix shared by the producer and the compatibility classifier for context nudges. */
10
+ export declare const CONTEXT_NUDGE_PREFIX = "[crtr] Context ~";
11
+ export interface GeneratedContextMessageLike {
12
+ role?: string;
13
+ customType?: string;
14
+ content?: unknown;
15
+ }
16
+ export interface GeneratedContextPresentation {
17
+ kind: 'bearings' | 'situational' | 'revive' | 'context-nudge' | 'inbox';
18
+ label: string;
19
+ summary: string;
20
+ body: string;
21
+ }
22
+ /** Plain text from a generated message's string or text-block content. */
23
+ export declare function generatedContextText(message: GeneratedContextMessageLike): string;
24
+ /** Format the runtime's visible context-size guidance. */
25
+ export declare function formatContextNudge(note: string): string;
26
+ export declare function isInboxDigest(text: string): boolean;
27
+ export declare function extractInboxSender(text: string): string | null;
28
+ /**
29
+ * Classify a crouter-generated context message for display. The checks are
30
+ * exact producer-owned contracts: custom messages use their customType; the two
31
+ * user-message forms use the same stable sentinel/prefix their producers own.
32
+ * Ordinary user prompts never match and remain ordinary user prompts.
33
+ */
34
+ export declare function generatedContextPresentation(message: GeneratedContextMessageLike): GeneratedContextPresentation | null;
@@ -0,0 +1,98 @@
1
+ // generated-context.ts — the display contract for crouter-authored context messages.
2
+ //
3
+ // The runtime sends these messages in the role that preserves their delivery
4
+ // semantics: fresh-revive kickoffs, mid-run nudges, and node inbox digests are user messages,
5
+ // while bearings, situational updates, and held nudges are custom messages. Attached
6
+ // views should still present all of them as generated context rather than as
7
+ // ordinary human prompts. This pure module owns their stable identifiers and
8
+ // display classification so the terminal and web renderers cannot drift.
9
+ /** Custom message carrying the node's session-start bearings. */
10
+ export const CONTEXT_INTRO_CUSTOM_TYPE = 'crtr-context';
11
+ /** Custom message carrying an ambient situational-context update. */
12
+ export const SITUATIONAL_CONTEXT_CUSTOM_TYPE = 'crtr-situational-context';
13
+ /** Custom message used when a context-size nudge waits for the next turn. */
14
+ export const CONTEXT_NUDGE_CUSTOM_TYPE = 'crtr-context-nudge';
15
+ /** Opening text of a fresh-revive kickoff user message. */
16
+ export const REVIVE_KICKOFF_SENTINEL = 'You have been revived fresh after a context refresh';
17
+ /** Prefix shared by the producer and the compatibility classifier for context nudges. */
18
+ export const CONTEXT_NUDGE_PREFIX = '[crtr] Context ~';
19
+ /** Plain text from a generated message's string or text-block content. */
20
+ export function generatedContextText(message) {
21
+ if (typeof message.content === 'string')
22
+ return message.content;
23
+ if (!Array.isArray(message.content))
24
+ return '';
25
+ return message.content
26
+ .filter((block) => typeof block === 'object' &&
27
+ block !== null &&
28
+ block.type === 'text' &&
29
+ typeof block.text === 'string')
30
+ .map((block) => block.text)
31
+ .join('');
32
+ }
33
+ /** Format the runtime's visible context-size guidance. */
34
+ export function formatContextNudge(note) {
35
+ return `[crtr] ${note}`;
36
+ }
37
+ function nudgeSummary(body) {
38
+ const size = body.match(/^\[crtr\]\s+Context\s+(~\d+k)\b/)?.[1];
39
+ return size === undefined ? 'context-window guidance' : `Context ${size}`;
40
+ }
41
+ // Stable header emitted by core/feed/inbox.ts coalesce(). Human-authored inbox
42
+ // messages are deliberately delivered verbatim and never carry this header.
43
+ const INBOX_HEADER_RE = /^From ([a-z0-9]+(?:-[a-z0-9]+)+|system|human) — (\d+) update/m;
44
+ export function isInboxDigest(text) {
45
+ return INBOX_HEADER_RE.test(text);
46
+ }
47
+ export function extractInboxSender(text) {
48
+ return INBOX_HEADER_RE.exec(text)?.[1] ?? null;
49
+ }
50
+ function inboxSummary(body) {
51
+ const headers = [...body.matchAll(/^From ([a-z0-9]+(?:-[a-z0-9]+)+|system|human) — (\d+) update/gm)];
52
+ if (headers.length === 0)
53
+ return 'node update';
54
+ const updates = headers.reduce((total, match) => total + Number(match[2] ?? 0), 0);
55
+ if (headers.length === 1) {
56
+ const sender = headers[0]?.[1] ?? 'system';
57
+ return `${updates} update${updates === 1 ? '' : 's'} from ${sender}`;
58
+ }
59
+ return `${updates} updates from ${headers.length} senders`;
60
+ }
61
+ /**
62
+ * Classify a crouter-generated context message for display. The checks are
63
+ * exact producer-owned contracts: custom messages use their customType; the two
64
+ * user-message forms use the same stable sentinel/prefix their producers own.
65
+ * Ordinary user prompts never match and remain ordinary user prompts.
66
+ */
67
+ export function generatedContextPresentation(message) {
68
+ const body = generatedContextText(message);
69
+ if (message.role === 'custom') {
70
+ if (message.customType === CONTEXT_INTRO_CUSTOM_TYPE) {
71
+ return { kind: 'bearings', label: 'crtr context', summary: 'orienting bearings', body };
72
+ }
73
+ if (message.customType === SITUATIONAL_CONTEXT_CUSTOM_TYPE) {
74
+ return {
75
+ kind: 'situational',
76
+ label: 'situational context',
77
+ summary: 'ambient context update',
78
+ body,
79
+ };
80
+ }
81
+ if (message.customType === CONTEXT_NUDGE_CUSTOM_TYPE) {
82
+ return { kind: 'context-nudge', label: 'crtr', summary: nudgeSummary(body), body };
83
+ }
84
+ return null;
85
+ }
86
+ if (message.role !== 'user')
87
+ return null;
88
+ if (body.startsWith(REVIVE_KICKOFF_SENTINEL)) {
89
+ return { kind: 'revive', label: 'crtr revive', summary: 'fresh context kickoff', body };
90
+ }
91
+ if (body.startsWith(CONTEXT_NUDGE_PREFIX)) {
92
+ return { kind: 'context-nudge', label: 'crtr', summary: nudgeSummary(body), body };
93
+ }
94
+ if (isInboxDigest(body)) {
95
+ return { kind: 'inbox', label: 'crtr inbox', summary: inboxSummary(body), body };
96
+ }
97
+ return null;
98
+ }