@crouter/api 0.3.386 → 0.3.388
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/__tests__/integration/client.test.js +97 -0
- package/dist/api/client.d.ts +7 -0
- package/dist/api/client.js +40 -21
- package/dist/core/asset-root.d.ts +7 -0
- package/dist/core/asset-root.js +18 -0
- package/dist/core/canvas/boot-id.d.ts +6 -0
- package/dist/core/canvas/boot-id.js +26 -0
- package/dist/core/canvas/paths.d.ts +72 -0
- package/dist/core/canvas/paths.js +163 -0
- package/dist/core/canvas/pid.d.ts +391 -0
- package/dist/core/canvas/pid.js +948 -0
- package/dist/core/command-plugins/bundle.d.ts +149 -0
- package/dist/core/command-plugins/bundle.js +588 -0
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +51 -0
- package/dist/core/config.d.ts +233 -0
- package/dist/core/config.js +1120 -0
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/errors.d.ts +38 -0
- package/dist/core/errors.js +90 -0
- package/dist/core/events/emit.d.ts +6 -0
- package/dist/core/events/emit.js +42 -0
- package/dist/core/events/envelope.d.ts +2 -0
- package/dist/core/events/envelope.js +84 -0
- package/dist/core/events/errors.d.ts +4 -0
- package/dist/core/events/errors.js +69 -0
- package/dist/core/events/operation-id.d.ts +4 -0
- package/dist/core/events/operation-id.js +24 -0
- package/dist/core/events/serialize.d.ts +4 -0
- package/dist/core/events/serialize.js +199 -0
- package/dist/core/events/source.d.ts +16 -0
- package/dist/core/events/source.js +31 -0
- package/dist/core/events/types.d.ts +68 -0
- package/dist/core/events/types.js +11 -0
- package/dist/core/exclusive-lock.d.ts +34 -0
- package/dist/core/exclusive-lock.js +197 -0
- package/dist/core/fs-utils.d.ts +44 -0
- package/dist/core/fs-utils.js +208 -0
- package/dist/core/help.d.ts +309 -0
- package/dist/core/help.js +406 -0
- package/dist/core/human/page-catalog.d.ts +57 -0
- package/dist/core/human/page-catalog.js +172 -0
- package/dist/core/installed-plugins.d.ts +2 -0
- package/dist/core/installed-plugins.js +79 -0
- package/dist/core/io.d.ts +122 -0
- package/dist/core/io.js +373 -0
- package/dist/core/keybindings/attach-control.d.ts +49 -0
- package/dist/core/keybindings/attach-control.js +42 -0
- package/dist/core/keybindings/catalog.d.ts +18 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/types.d.ts +42 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/layout.d.ts +26 -0
- package/dist/core/layout.js +94 -0
- package/dist/core/locked-file.d.ts +27 -0
- package/dist/core/locked-file.js +118 -0
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +89 -0
- package/dist/core/manifest.d.ts +5 -0
- package/dist/core/manifest.js +15 -0
- package/dist/core/plugin-env.d.ts +8 -0
- package/dist/core/plugin-env.js +31 -0
- package/dist/core/plugin-extensions.d.ts +29 -0
- package/dist/core/plugin-extensions.js +191 -0
- package/dist/core/plugin-swap-lock.d.ts +9 -0
- package/dist/core/plugin-swap-lock.js +31 -0
- package/dist/core/preview-result-path.d.ts +4 -0
- package/dist/core/preview-result-path.js +26 -0
- package/dist/core/profiles/env-store.d.ts +22 -0
- package/dist/core/profiles/env-store.js +163 -0
- package/dist/core/profiles/fuzzy-match.d.ts +19 -0
- package/dist/core/profiles/fuzzy-match.js +92 -0
- package/dist/core/profiles/manifest.d.ts +120 -0
- package/dist/core/profiles/manifest.js +529 -0
- package/dist/core/rate-limit-scope.d.ts +25 -0
- package/dist/core/rate-limit-scope.js +64 -0
- package/dist/core/render.d.ts +12 -0
- package/dist/core/render.js +138 -0
- package/dist/core/resolver.d.ts +14 -0
- package/dist/core/resolver.js +111 -0
- package/dist/core/runtime/branded-host.d.ts +25 -0
- package/dist/core/runtime/branded-host.js +264 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
- package/dist/core/runtime/broker/daemon-ops.js +177 -0
- package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
- package/dist/core/runtime/broker/signal-stream.js +149 -0
- package/dist/core/scope.d.ts +32 -0
- package/dist/core/scope.js +184 -0
- package/dist/core/scoped-state/db.d.ts +17 -0
- package/dist/core/scoped-state/db.js +247 -0
- package/dist/core/scoped-state/migrate.d.ts +8 -0
- package/dist/core/scoped-state/migrate.js +187 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +27 -0
- package/dist/core/scoped-state/profiles.js +93 -0
- package/dist/core/scoped-state/providers.d.ts +24 -0
- package/dist/core/scoped-state/providers.js +19 -0
- package/dist/core/scoped-state/schema.d.ts +6 -0
- package/dist/core/scoped-state/schema.js +43 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +83 -0
- package/dist/core/spaces/open-beneath.d.ts +71 -0
- package/dist/core/spaces/open-beneath.js +581 -0
- package/dist/core/sqlite-statements.d.ts +4 -0
- package/dist/core/sqlite-statements.js +17 -0
- package/dist/core/subscription-state.d.ts +121 -0
- package/dist/core/subscription-state.js +287 -0
- package/dist/core/user-settings.d.ts +377 -0
- package/dist/core/user-settings.js +458 -0
- package/dist/daemon/broker-signals/bus.d.ts +30 -0
- package/dist/daemon/broker-signals/bus.js +87 -0
- package/dist/daemon/manage.d.ts +176 -0
- package/dist/daemon/manage.js +664 -0
- package/dist/daemon/pidfile.d.ts +8 -0
- package/dist/daemon/pidfile.js +37 -0
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/native/linux.d.ts +29 -0
- package/dist/native/linux.js +20 -0
- package/dist/shared/env.d.ts +116 -0
- package/dist/shared/env.js +271 -0
- package/dist/shared/inbox-entry-body.d.ts +22 -0
- package/dist/shared/inbox-entry-body.js +116 -0
- package/dist/shared/working-activity.d.ts +9 -0
- package/dist/shared/working-activity.js +27 -0
- package/dist/types.d.ts +562 -0
- package/dist/types.js +186 -0
- package/package.json +1 -1
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { CONDENSED_HISTORY_MODES, MOUSE_MODE_DEFAULTS, WHIP_MESSAGE_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 OPENAI_CODEX_PROVIDER_KEY = 'openai-codex';
|
|
40
|
+
/** The options block for one provider, created on first write so a row can
|
|
41
|
+
* toggle an option the config file has never named. */
|
|
42
|
+
function providerOptionsFor(config, providerId) {
|
|
43
|
+
const existing = config.providerOptions[providerId];
|
|
44
|
+
if (existing !== undefined)
|
|
45
|
+
return existing;
|
|
46
|
+
const created = {};
|
|
47
|
+
config.providerOptions[providerId] = created;
|
|
48
|
+
return created;
|
|
49
|
+
}
|
|
50
|
+
const LIVE_CYCLES = [1, 2, 3, 4, 5, 6];
|
|
51
|
+
/**
|
|
52
|
+
* Exhaustive settings-surface coverage for ScopeConfig. Primitive preferences
|
|
53
|
+
* are required by the type to define a Settings row; structured registries must
|
|
54
|
+
* name the specialized surface that owns them. Adding a config field therefore
|
|
55
|
+
* fails typecheck until its user-facing settings coverage is decided. The
|
|
56
|
+
* auto_update nested record is exhaustive for the same reason.
|
|
57
|
+
*/
|
|
58
|
+
export const CONFIG_SETTINGS_COVERAGE = {
|
|
59
|
+
schema_version: { surface: 'specialized', owner: 'internal schema migration' },
|
|
60
|
+
marketplaces: { surface: 'specialized', owner: 'Plugins and pkg market commands' },
|
|
61
|
+
plugins: { surface: 'specialized', owner: 'Plugins tab and pkg plugin commands' },
|
|
62
|
+
auto_update: {
|
|
63
|
+
surface: 'settings',
|
|
64
|
+
settings: {
|
|
65
|
+
crtr: {
|
|
66
|
+
key: 'auto_update.crtr',
|
|
67
|
+
label: 'crouter updates',
|
|
68
|
+
description: 'Notify about crouter releases, install them automatically, or turn checks off.',
|
|
69
|
+
valueConstraint: 'true|false|notify|apply; true stores notify',
|
|
70
|
+
applies: 'Applies on the next crouter command.',
|
|
71
|
+
group: 'updates',
|
|
72
|
+
order: 70,
|
|
73
|
+
format: (config) => config.auto_update.crtr === false ? 'off' : config.auto_update.crtr,
|
|
74
|
+
cycle: (config, delta) => { config.auto_update.crtr = cycleChoice(AUTO_UPDATE_MODES, config.auto_update.crtr, delta); },
|
|
75
|
+
setFromString: (config, raw) => {
|
|
76
|
+
const value = raw === 'true' ? 'notify' : raw;
|
|
77
|
+
if (value !== 'notify' && value !== 'apply' && value !== 'false')
|
|
78
|
+
return 'auto_update.crtr must be notify, apply, or false';
|
|
79
|
+
config.auto_update.crtr = value === 'false' ? false : value;
|
|
80
|
+
return null;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
content: {
|
|
84
|
+
key: 'auto_update.content',
|
|
85
|
+
label: 'Content updates',
|
|
86
|
+
description: 'Notify about plugin and marketplace updates, apply them automatically, or turn checks off.',
|
|
87
|
+
valueConstraint: 'false|notify|apply',
|
|
88
|
+
applies: 'Applies on the next crouter command.',
|
|
89
|
+
group: 'updates',
|
|
90
|
+
order: 80,
|
|
91
|
+
format: (config) => config.auto_update.content === false ? 'off' : config.auto_update.content,
|
|
92
|
+
cycle: (config, delta) => { config.auto_update.content = cycleChoice(AUTO_UPDATE_MODES, config.auto_update.content, delta); },
|
|
93
|
+
setFromString: (config, raw) => {
|
|
94
|
+
if (raw !== 'notify' && raw !== 'apply' && raw !== 'false')
|
|
95
|
+
return 'auto_update.content must be notify, apply, or false';
|
|
96
|
+
config.auto_update.content = raw === 'false' ? false : raw;
|
|
97
|
+
return null;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
interval_hours: {
|
|
101
|
+
key: 'auto_update.interval_hours',
|
|
102
|
+
label: 'Update interval',
|
|
103
|
+
description: 'Minimum hours between automatic update checks. Zero checks on every command.',
|
|
104
|
+
valueConstraint: 'integer >= 0',
|
|
105
|
+
applies: 'Applies on the next crouter command.',
|
|
106
|
+
group: 'updates',
|
|
107
|
+
order: 90,
|
|
108
|
+
format: (config) => `${config.auto_update.interval_hours}h`,
|
|
109
|
+
cycle: (config, delta) => { config.auto_update.interval_hours = cycleNumber(UPDATE_INTERVAL_HOURS, config.auto_update.interval_hours, delta); },
|
|
110
|
+
setFromString: (config, raw) => {
|
|
111
|
+
const value = nonNegativeIntegerFromString(raw, 'auto_update.interval_hours');
|
|
112
|
+
if (typeof value === 'string')
|
|
113
|
+
return value;
|
|
114
|
+
config.auto_update.interval_hours = value;
|
|
115
|
+
return null;
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
max_panes_per_window: {
|
|
121
|
+
surface: 'settings',
|
|
122
|
+
setting: {
|
|
123
|
+
key: 'max_panes_per_window',
|
|
124
|
+
label: 'Maximum panes',
|
|
125
|
+
description: 'Maximum human-review panes placed in one tmux window before using another window.',
|
|
126
|
+
valueConstraint: 'integer >= 1',
|
|
127
|
+
applies: 'Applies to the next pane placement.',
|
|
128
|
+
group: 'panes',
|
|
129
|
+
order: 20,
|
|
130
|
+
format: (config) => String(config.max_panes_per_window),
|
|
131
|
+
cycle: (config, delta) => { config.max_panes_per_window = cycleNumber(MAX_PANES, config.max_panes_per_window, delta); },
|
|
132
|
+
setFromString: (config, raw) => {
|
|
133
|
+
const value = positiveIntegerFromString(raw, 'max_panes_per_window');
|
|
134
|
+
if (typeof value === 'string')
|
|
135
|
+
return value;
|
|
136
|
+
config.max_panes_per_window = value;
|
|
137
|
+
return null;
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
auto_open_child_viewers: {
|
|
142
|
+
surface: 'settings',
|
|
143
|
+
setting: {
|
|
144
|
+
key: 'auto_open_child_viewers',
|
|
145
|
+
label: 'Open child panes',
|
|
146
|
+
description: 'Open each managed child beside its watched parent. Off keeps new children running in the background.',
|
|
147
|
+
valueConstraint: 'true|false',
|
|
148
|
+
applies: 'Applies to the next managed child created; independent roots still open their attended viewer.',
|
|
149
|
+
group: 'panes',
|
|
150
|
+
order: 10,
|
|
151
|
+
format: (config) => config.auto_open_child_viewers ? 'on' : 'off',
|
|
152
|
+
cycle: (config) => { config.auto_open_child_viewers = !config.auto_open_child_viewers; },
|
|
153
|
+
setFromString: (config, raw) => {
|
|
154
|
+
const value = booleanFromString(raw, 'auto_open_child_viewers');
|
|
155
|
+
if (typeof value === 'string')
|
|
156
|
+
return value;
|
|
157
|
+
config.auto_open_child_viewers = value;
|
|
158
|
+
return null;
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
brokerThresholds: { surface: 'specialized', owner: 'Broker limits tab' },
|
|
163
|
+
lifecycle: { surface: 'specialized', owner: 'sys config set lifecycle.unattendedParkMs' },
|
|
164
|
+
completion_bell: {
|
|
165
|
+
surface: 'settings',
|
|
166
|
+
setting: {
|
|
167
|
+
key: 'completion_bell',
|
|
168
|
+
label: 'Completion bell',
|
|
169
|
+
description: 'Ring a short sound when the node in an attached viewer finishes a turn.',
|
|
170
|
+
valueConstraint: 'true|false',
|
|
171
|
+
applies: 'Applies on the next turn end.',
|
|
172
|
+
group: 'display',
|
|
173
|
+
order: 41,
|
|
174
|
+
format: (config) => config.completion_bell ? 'on' : 'off',
|
|
175
|
+
cycle: (config) => { config.completion_bell = !config.completion_bell; },
|
|
176
|
+
setFromString: (config, raw) => {
|
|
177
|
+
const value = booleanFromString(raw, 'completion_bell');
|
|
178
|
+
if (typeof value === 'string')
|
|
179
|
+
return value;
|
|
180
|
+
config.completion_bell = value;
|
|
181
|
+
return null;
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
working_gerunds: { surface: 'specialized', owner: 'sys config set working_gerunds' },
|
|
186
|
+
whip_mode: {
|
|
187
|
+
surface: 'settings',
|
|
188
|
+
setting: {
|
|
189
|
+
key: 'whip_mode',
|
|
190
|
+
label: 'Whip mode',
|
|
191
|
+
description: 'Play the whip animation when you send a prompt and when a viewer opens for an agent with an initial prompt.',
|
|
192
|
+
valueConstraint: 'true|false',
|
|
193
|
+
applies: 'Applies on the next attach.',
|
|
194
|
+
group: 'display',
|
|
195
|
+
order: 39,
|
|
196
|
+
format: (config) => config.whip_mode ? 'on' : 'off',
|
|
197
|
+
cycle: (config) => { config.whip_mode = !config.whip_mode; },
|
|
198
|
+
setFromString: (config, raw) => {
|
|
199
|
+
const value = booleanFromString(raw, 'whip_mode');
|
|
200
|
+
if (typeof value === 'string')
|
|
201
|
+
return value;
|
|
202
|
+
config.whip_mode = value;
|
|
203
|
+
return null;
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
page_surface: {
|
|
208
|
+
surface: 'settings',
|
|
209
|
+
setting: {
|
|
210
|
+
key: 'page_surface',
|
|
211
|
+
label: 'Page human surface',
|
|
212
|
+
description: 'This host reads pages in a browser rather than a terminal. Agents are told the page module runs as written, HTML pages are offered, and document review and live display leave the command tree.',
|
|
213
|
+
valueConstraint: 'true|false',
|
|
214
|
+
applies: 'Applies when human command help is assembled.',
|
|
215
|
+
group: 'display',
|
|
216
|
+
order: 37,
|
|
217
|
+
format: (config) => config.page_surface ? 'on' : 'off',
|
|
218
|
+
cycle: (config) => { config.page_surface = !config.page_surface; },
|
|
219
|
+
setFromString: (config, raw) => {
|
|
220
|
+
const value = booleanFromString(raw, 'page_surface');
|
|
221
|
+
if (typeof value === 'string')
|
|
222
|
+
return value;
|
|
223
|
+
config.page_surface = value;
|
|
224
|
+
return null;
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
whip_messages: { surface: 'specialized', owner: 'sys config set whip_messages' },
|
|
229
|
+
whip_message_mode: {
|
|
230
|
+
surface: 'settings',
|
|
231
|
+
setting: {
|
|
232
|
+
key: 'whip_message_mode',
|
|
233
|
+
label: 'Whip message',
|
|
234
|
+
description: 'What the whip action sends: a random line from whip_messages, or the fixed “Go faster”.',
|
|
235
|
+
valueConstraint: WHIP_MESSAGE_MODES.join('|'),
|
|
236
|
+
applies: 'Applies on the next attach.',
|
|
237
|
+
group: 'display',
|
|
238
|
+
order: 40,
|
|
239
|
+
format: (config) => config.whip_message_mode === 'go-faster' ? 'go faster' : 'random',
|
|
240
|
+
cycle: (config, delta) => { config.whip_message_mode = cycleChoice(WHIP_MESSAGE_MODES, config.whip_message_mode, delta); },
|
|
241
|
+
setFromString: (config, raw) => {
|
|
242
|
+
if (!WHIP_MESSAGE_MODES.includes(raw))
|
|
243
|
+
return `whip_message_mode must be one of: ${WHIP_MESSAGE_MODES.join('|')}`;
|
|
244
|
+
config.whip_message_mode = raw;
|
|
245
|
+
return null;
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
mouse_mode_default: {
|
|
250
|
+
surface: 'settings',
|
|
251
|
+
setting: {
|
|
252
|
+
key: 'mouse_mode_default',
|
|
253
|
+
label: 'Default mouse mode',
|
|
254
|
+
description: 'Initial mouse wheel scrolling for each attached viewer.',
|
|
255
|
+
valueConstraint: MOUSE_MODE_DEFAULTS.join('|'),
|
|
256
|
+
applies: 'Applies on the next attach.',
|
|
257
|
+
group: 'display',
|
|
258
|
+
order: 38,
|
|
259
|
+
format: (config) => config.mouse_mode_default === 'tmux' ? 'follow tmux' : config.mouse_mode_default,
|
|
260
|
+
cycle: (config, delta) => { config.mouse_mode_default = cycleChoice(MOUSE_MODE_DEFAULTS, config.mouse_mode_default, delta); },
|
|
261
|
+
setFromString: (config, raw) => {
|
|
262
|
+
if (!MOUSE_MODE_DEFAULTS.includes(raw))
|
|
263
|
+
return `mouse_mode_default must be one of: ${MOUSE_MODE_DEFAULTS.join('|')}`;
|
|
264
|
+
config.mouse_mode_default = raw;
|
|
265
|
+
return null;
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
live_cycles: {
|
|
270
|
+
surface: 'settings',
|
|
271
|
+
setting: {
|
|
272
|
+
key: 'live_cycles',
|
|
273
|
+
label: 'Live cycles',
|
|
274
|
+
description: 'Trailing refresh cycles rendered at full fidelity; older cycles condense to text.',
|
|
275
|
+
valueConstraint: 'integer >= 1',
|
|
276
|
+
applies: 'Applies on the next attach.',
|
|
277
|
+
group: 'display',
|
|
278
|
+
order: 50,
|
|
279
|
+
format: (config) => String(config.live_cycles),
|
|
280
|
+
cycle: (config, delta) => { config.live_cycles = cycleNumber(LIVE_CYCLES, config.live_cycles, delta); },
|
|
281
|
+
setFromString: (config, raw) => {
|
|
282
|
+
const value = positiveIntegerFromString(raw, 'live_cycles');
|
|
283
|
+
if (typeof value === 'string')
|
|
284
|
+
return value;
|
|
285
|
+
config.live_cycles = value;
|
|
286
|
+
return null;
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
condensed_history: {
|
|
291
|
+
surface: 'settings',
|
|
292
|
+
setting: {
|
|
293
|
+
key: 'condensed_history',
|
|
294
|
+
label: 'Condensed history',
|
|
295
|
+
description: 'Messages retained from condensed cycles. Tool calls are never kept.',
|
|
296
|
+
valueConstraint: CONDENSED_HISTORY_MODES.join('|'),
|
|
297
|
+
applies: 'Applies on the next attach.',
|
|
298
|
+
group: 'display',
|
|
299
|
+
order: 60,
|
|
300
|
+
format: (config) => config.condensed_history,
|
|
301
|
+
cycle: (config, delta) => { config.condensed_history = cycleChoice(CONDENSED_HISTORY_MODES, config.condensed_history, delta); },
|
|
302
|
+
setFromString: (config, raw) => {
|
|
303
|
+
if (!CONDENSED_HISTORY_MODES.includes(raw))
|
|
304
|
+
return `condensed_history must be one of: ${CONDENSED_HISTORY_MODES.join('|')}`;
|
|
305
|
+
config.condensed_history = raw;
|
|
306
|
+
return null;
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
bash_tool_purpose: {
|
|
311
|
+
surface: 'settings',
|
|
312
|
+
setting: {
|
|
313
|
+
key: 'bash_tool_purpose',
|
|
314
|
+
label: 'Bash purpose field',
|
|
315
|
+
description: 'Expose the optional purpose label to agents in the bash tool schema. Off removes the field without disabling the bash valve.',
|
|
316
|
+
valueConstraint: 'true|false',
|
|
317
|
+
applies: 'Applies on the next node session.',
|
|
318
|
+
group: 'display',
|
|
319
|
+
order: 25,
|
|
320
|
+
format: (config) => config.bash_tool_purpose ? 'on' : 'off',
|
|
321
|
+
cycle: (config) => { config.bash_tool_purpose = !config.bash_tool_purpose; },
|
|
322
|
+
setFromString: (config, raw) => {
|
|
323
|
+
const value = booleanFromString(raw, 'bash_tool_purpose');
|
|
324
|
+
if (typeof value === 'string')
|
|
325
|
+
return value;
|
|
326
|
+
config.bash_tool_purpose = value;
|
|
327
|
+
return null;
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
fold_finished_tools: {
|
|
332
|
+
surface: 'settings',
|
|
333
|
+
setting: {
|
|
334
|
+
key: 'fold_finished_tools',
|
|
335
|
+
label: 'Fold finished tools',
|
|
336
|
+
description: 'Fold every settled tool call to its call line. Toggle live with /fold-tools.',
|
|
337
|
+
valueConstraint: 'true|false',
|
|
338
|
+
applies: 'Applies on the next attach.',
|
|
339
|
+
group: 'display',
|
|
340
|
+
order: 30,
|
|
341
|
+
format: (config) => config.fold_finished_tools ? 'on' : 'off',
|
|
342
|
+
cycle: (config) => { config.fold_finished_tools = !config.fold_finished_tools; },
|
|
343
|
+
setFromString: (config, raw) => {
|
|
344
|
+
const value = booleanFromString(raw, 'fold_finished_tools');
|
|
345
|
+
if (typeof value === 'string')
|
|
346
|
+
return value;
|
|
347
|
+
config.fold_finished_tools = value;
|
|
348
|
+
return null;
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
summarize_tool_calls: {
|
|
353
|
+
surface: 'settings',
|
|
354
|
+
setting: {
|
|
355
|
+
key: 'summarize_tool_calls',
|
|
356
|
+
label: 'Summarize tool calls',
|
|
357
|
+
description: 'Replace completed folded tool-call groups with a one-line broker summary when available.',
|
|
358
|
+
valueConstraint: 'true|false',
|
|
359
|
+
applies: 'Applies when the next visible message closes a tool-call group.',
|
|
360
|
+
group: 'display',
|
|
361
|
+
order: 35,
|
|
362
|
+
format: (config) => config.summarize_tool_calls ? 'on' : 'off',
|
|
363
|
+
cycle: (config) => { config.summarize_tool_calls = !config.summarize_tool_calls; },
|
|
364
|
+
setFromString: (config, raw) => {
|
|
365
|
+
const value = booleanFromString(raw, 'summarize_tool_calls');
|
|
366
|
+
if (typeof value === 'string')
|
|
367
|
+
return value;
|
|
368
|
+
config.summarize_tool_calls = value;
|
|
369
|
+
return null;
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
detailed_tool_recaps: {
|
|
374
|
+
surface: 'settings',
|
|
375
|
+
setting: {
|
|
376
|
+
key: 'detailed_tool_recaps',
|
|
377
|
+
label: 'Detailed tool recaps',
|
|
378
|
+
description: 'Show each document read and edited file in folded tool recaps. Off shows a compact count line instead.',
|
|
379
|
+
valueConstraint: 'true|false',
|
|
380
|
+
applies: 'Applies on the next attach.',
|
|
381
|
+
group: 'display',
|
|
382
|
+
order: 36,
|
|
383
|
+
format: (config) => config.detailed_tool_recaps ? 'on' : 'off',
|
|
384
|
+
cycle: (config) => { config.detailed_tool_recaps = !config.detailed_tool_recaps; },
|
|
385
|
+
setFromString: (config, raw) => {
|
|
386
|
+
const value = booleanFromString(raw, 'detailed_tool_recaps');
|
|
387
|
+
if (typeof value === 'string')
|
|
388
|
+
return value;
|
|
389
|
+
config.detailed_tool_recaps = value;
|
|
390
|
+
return null;
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
keybindings: { surface: 'specialized', owner: 'Keybindings tab' },
|
|
395
|
+
tmux_passthrough: { surface: 'specialized', owner: 'Keybindings tab' },
|
|
396
|
+
providerOptions: {
|
|
397
|
+
surface: 'settings',
|
|
398
|
+
settings: {
|
|
399
|
+
'openai-codex.fastMode': {
|
|
400
|
+
key: `providerOptions.${OPENAI_CODEX_PROVIDER_KEY}.fastMode`,
|
|
401
|
+
label: 'Codex fast mode',
|
|
402
|
+
description: 'Serve Codex turns at the ChatGPT priority tier: about 1.5x faster generation for about 2x the credits. Displayed cost does not reflect the premium.',
|
|
403
|
+
valueConstraint: 'true|false',
|
|
404
|
+
applies: "Applies on each node's next turn.",
|
|
405
|
+
group: 'providers',
|
|
406
|
+
order: 40,
|
|
407
|
+
format: (config) => config.providerOptions[OPENAI_CODEX_PROVIDER_KEY]?.fastMode ? 'on' : 'off',
|
|
408
|
+
cycle: (config) => {
|
|
409
|
+
const options = providerOptionsFor(config, OPENAI_CODEX_PROVIDER_KEY);
|
|
410
|
+
options.fastMode = !options.fastMode;
|
|
411
|
+
},
|
|
412
|
+
setFromString: (config, raw) => {
|
|
413
|
+
const value = booleanFromString(raw, `providerOptions.${OPENAI_CODEX_PROVIDER_KEY}.fastMode`);
|
|
414
|
+
if (typeof value === 'string')
|
|
415
|
+
return value;
|
|
416
|
+
providerOptionsFor(config, OPENAI_CODEX_PROVIDER_KEY).fastMode = value;
|
|
417
|
+
return null;
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
modelLadders: { surface: 'specialized', owner: 'Models tab' },
|
|
423
|
+
modelRoutes: { surface: 'specialized', owner: 'Models route declarations' },
|
|
424
|
+
modelRouting: { surface: 'specialized', owner: 'Models route declarations' },
|
|
425
|
+
kinds: { surface: 'specialized', owner: 'Models tab and kind registry' },
|
|
426
|
+
page_components: { surface: 'specialized', owner: 'Page component catalog: product-registered page component kinds' },
|
|
427
|
+
remoteCanvas: { surface: 'specialized', owner: 'canvas config commands' },
|
|
428
|
+
api: { surface: 'specialized', owner: 'sys connect' },
|
|
429
|
+
paths: { surface: 'specialized', owner: 'runtime layout' },
|
|
430
|
+
store: { surface: 'specialized', owner: 'daemon conversation store' },
|
|
431
|
+
runtime: { surface: 'specialized', owner: 'app-facing listener' },
|
|
432
|
+
spawnEnv: { surface: 'specialized', owner: 'spawn environment allow-list' },
|
|
433
|
+
bin: { surface: 'specialized', owner: 'bare-binary contributions on every node PATH' },
|
|
434
|
+
humanActions: { surface: 'specialized', owner: 'scope-configured human completion actions' },
|
|
435
|
+
};
|
|
436
|
+
/** The exhaustive top-level key list used by config persistence and help. */
|
|
437
|
+
export const SCOPE_CONFIG_KEYS = Object.keys(CONFIG_SETTINGS_COVERAGE);
|
|
438
|
+
function collectUserSettings() {
|
|
439
|
+
const out = [];
|
|
440
|
+
for (const coverage of Object.values(CONFIG_SETTINGS_COVERAGE)) {
|
|
441
|
+
if (coverage.surface !== 'settings')
|
|
442
|
+
continue;
|
|
443
|
+
if ('setting' in coverage)
|
|
444
|
+
out.push(coverage.setting);
|
|
445
|
+
else
|
|
446
|
+
out.push(...Object.values(coverage.settings));
|
|
447
|
+
}
|
|
448
|
+
return out.sort((a, b) => a.order - b.order);
|
|
449
|
+
}
|
|
450
|
+
/** The single row registry shared by `sys settings` and `sys config set`. */
|
|
451
|
+
export const USER_SETTINGS = collectUserSettings();
|
|
452
|
+
export function findUserSetting(key) {
|
|
453
|
+
return USER_SETTINGS.find((setting) => setting.key === key);
|
|
454
|
+
}
|
|
455
|
+
/** The rows one settings destination owns, in registry order. */
|
|
456
|
+
export function userSettingsInGroup(group) {
|
|
457
|
+
return USER_SETTINGS.filter((setting) => setting.group === group);
|
|
458
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { BrokerExtensionNodeDTO } from '../../api/dto/broker-ops.js';
|
|
3
|
+
/** Node-owned mutable fields in extensionNode's projection. Keep this list and
|
|
4
|
+
* the typed cache view together; object/worktree-owned fields are never cached. */
|
|
5
|
+
export declare const BROKER_SIGNAL_NODE_COLUMNS: readonly ["description", "title", "icon", "mode", "lifecycle", "status", "cwd", "parent", "fork_from", "profile_id", "review_binding_json", "persona_ack_json", "intent"];
|
|
6
|
+
type ProjectedNodeKey<C extends string> = C extends 'review_binding_json' ? 'review_binding' : C extends 'persona_ack_json' ? 'persona_ack' : C;
|
|
7
|
+
export type BrokerSignalCachedNode = Pick<BrokerExtensionNodeDTO, Extract<ProjectedNodeKey<(typeof BROKER_SIGNAL_NODE_COLUMNS)[number]>, keyof BrokerExtensionNodeDTO>>;
|
|
8
|
+
/** Only node-owned columns are suitable for P4's cache. The source list also
|
|
9
|
+
* drives the SQLite trigger, so a newly added node field has one edit point. */
|
|
10
|
+
export declare function cachedBrokerNodeFields(node: BrokerExtensionNodeDTO): BrokerSignalCachedNode;
|
|
11
|
+
export type BrokerSignalKind = 'mail' | 'node';
|
|
12
|
+
export type BrokerSignalSubscriber = (nodeId: string, kind: BrokerSignalKind) => void;
|
|
13
|
+
/** Hints, not delivery state: a rolled-back write may mark a node. The consumer
|
|
14
|
+
* always re-reads durable state. Pending marks are bounded by distinct node ids. */
|
|
15
|
+
export declare class BrokerSignalBus {
|
|
16
|
+
private readonly dirty;
|
|
17
|
+
private readonly subscribers;
|
|
18
|
+
private scheduled;
|
|
19
|
+
subscribe(listener: BrokerSignalSubscriber): () => void;
|
|
20
|
+
markMail(nodeId: string): void;
|
|
21
|
+
markNode(nodeId: string): void;
|
|
22
|
+
private mark;
|
|
23
|
+
private flush;
|
|
24
|
+
/** Reinstalled for every new daemon canvas handle (TEMP triggers vanish on close). */
|
|
25
|
+
install(db: DatabaseSync): void;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
/** One daemon-process bus; the API fixture binds this same instance. */
|
|
29
|
+
export declare const brokerSignalBus: BrokerSignalBus;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Node-owned mutable fields in extensionNode's projection. Keep this list and
|
|
2
|
+
* the typed cache view together; object/worktree-owned fields are never cached. */
|
|
3
|
+
export const BROKER_SIGNAL_NODE_COLUMNS = [
|
|
4
|
+
'description', 'title', 'icon', 'mode', 'lifecycle', 'status', 'cwd',
|
|
5
|
+
'parent', 'fork_from', 'profile_id', 'review_binding_json',
|
|
6
|
+
'persona_ack_json', 'intent',
|
|
7
|
+
];
|
|
8
|
+
const nodeField = (column) => column === 'review_binding_json' ? 'review_binding' : column === 'persona_ack_json' ? 'persona_ack' : column;
|
|
9
|
+
/** Only node-owned columns are suitable for P4's cache. The source list also
|
|
10
|
+
* drives the SQLite trigger, so a newly added node field has one edit point. */
|
|
11
|
+
export function cachedBrokerNodeFields(node) {
|
|
12
|
+
const picked = {};
|
|
13
|
+
for (const column of BROKER_SIGNAL_NODE_COLUMNS) {
|
|
14
|
+
const field = nodeField(column);
|
|
15
|
+
picked[field] = node[field];
|
|
16
|
+
}
|
|
17
|
+
return picked;
|
|
18
|
+
}
|
|
19
|
+
/** Hints, not delivery state: a rolled-back write may mark a node. The consumer
|
|
20
|
+
* always re-reads durable state. Pending marks are bounded by distinct node ids. */
|
|
21
|
+
export class BrokerSignalBus {
|
|
22
|
+
dirty = new Map();
|
|
23
|
+
subscribers = new Set();
|
|
24
|
+
scheduled;
|
|
25
|
+
subscribe(listener) {
|
|
26
|
+
this.subscribers.add(listener);
|
|
27
|
+
return () => { this.subscribers.delete(listener); };
|
|
28
|
+
}
|
|
29
|
+
markMail(nodeId) { this.mark(nodeId, 1); }
|
|
30
|
+
markNode(nodeId) { this.mark(nodeId, 2); }
|
|
31
|
+
mark(nodeId, flag) {
|
|
32
|
+
this.dirty.set(nodeId, (this.dirty.get(nodeId) ?? 0) | flag);
|
|
33
|
+
this.scheduled ??= setImmediate(() => this.flush());
|
|
34
|
+
}
|
|
35
|
+
flush() {
|
|
36
|
+
this.scheduled = undefined;
|
|
37
|
+
const batch = [...this.dirty];
|
|
38
|
+
this.dirty.clear();
|
|
39
|
+
for (const [id, flags] of batch) {
|
|
40
|
+
for (const listener of this.subscribers) {
|
|
41
|
+
try {
|
|
42
|
+
if (flags & 1)
|
|
43
|
+
listener(id, 'mail');
|
|
44
|
+
if (flags & 2)
|
|
45
|
+
listener(id, 'node');
|
|
46
|
+
}
|
|
47
|
+
catch { /* a subscriber never interrupts another subscriber */ }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Reinstalled for every new daemon canvas handle (TEMP triggers vanish on close). */
|
|
52
|
+
install(db) {
|
|
53
|
+
db.function('crtr_signal', { deterministic: false }, (kind, id) => {
|
|
54
|
+
// SQLite invokes this synchronously inside the writer's statement. A hint
|
|
55
|
+
// must never cause a transaction to fail, even for an unexpected row.
|
|
56
|
+
try {
|
|
57
|
+
if (typeof id === 'string') {
|
|
58
|
+
if (kind === 'mail')
|
|
59
|
+
this.markMail(id);
|
|
60
|
+
if (kind === 'node')
|
|
61
|
+
this.markNode(id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { /* best-effort hint only */ }
|
|
65
|
+
return null;
|
|
66
|
+
});
|
|
67
|
+
db.exec(`
|
|
68
|
+
CREATE TEMP TRIGGER crtr_sig_deliv_ins AFTER INSERT ON main.deliveries
|
|
69
|
+
WHEN NEW.wake = 1 BEGIN SELECT crtr_signal('mail', NEW.destination_id); END;
|
|
70
|
+
CREATE TEMP TRIGGER crtr_sig_deliv_upd AFTER UPDATE OF state, canceled_at, claim_owner ON main.deliveries
|
|
71
|
+
WHEN NEW.wake = 1 BEGIN SELECT crtr_signal('mail', NEW.destination_id); END;
|
|
72
|
+
CREATE TEMP TRIGGER crtr_sig_deliv_del AFTER DELETE ON main.deliveries
|
|
73
|
+
WHEN OLD.wake = 1 BEGIN SELECT crtr_signal('mail', OLD.destination_id); END;
|
|
74
|
+
CREATE TEMP TRIGGER crtr_sig_node_upd AFTER UPDATE OF ${BROKER_SIGNAL_NODE_COLUMNS.join(', ')} ON main.nodes
|
|
75
|
+
BEGIN SELECT crtr_signal('node', NEW.node_id); END;
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
if (this.scheduled !== undefined)
|
|
80
|
+
clearImmediate(this.scheduled);
|
|
81
|
+
this.scheduled = undefined;
|
|
82
|
+
this.dirty.clear();
|
|
83
|
+
this.subscribers.clear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** One daemon-process bus; the API fixture binds this same instance. */
|
|
87
|
+
export const brokerSignalBus = new BrokerSignalBus();
|