mixdog 1.0.6 → 1.0.7
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/README.md +158 -105
- package/package.json +11 -4
- package/src/defaults/skills/setup/references/actions.md +26 -6
- package/src/defaults/skills/setup/references/surfaces.md +8 -3
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +16 -7
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +24 -2
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +25 -0
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +14 -1
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +3 -45
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +15 -56
- package/src/runtime/channels/lib/worker-main.mjs +2 -9
- package/src/runtime/shared/config.mjs +66 -1
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +13 -2
- package/src/runtime/shared/path-executable.mjs +58 -0
- package/src/session-runtime/boot/apis.mjs +6 -2
- package/src/session-runtime/boot/begin.mjs +0 -2
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +0 -1
- package/src/session-runtime/config-lifecycle.mjs +11 -0
- package/src/session-runtime/internal-tool-executor.mjs +0 -2
- package/src/session-runtime/runtime-feature-gates.mjs +2 -3
- package/src/session-runtime/schedule-session-run.mjs +0 -1
- package/src/session-runtime/services/channel-admin.mjs +8 -26
- package/src/session-runtime/settings-compaction-api.mjs +5 -1
- package/src/session-runtime/settings-system-api.mjs +2 -20
- package/src/session-runtime/setup-tool/executor.mjs +61 -3
- package/src/session-runtime/setup-tool/extended-actions.mjs +31 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +27 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +35 -5
- package/src/session-runtime/webhook-session-run.mjs +0 -2
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +13 -3
- package/src/standalone/daemon-stored-session-views.mjs +4 -0
- package/src/standalone/session-protocol.mjs +0 -1
- package/src/standalone/session-service/session-calls/session-views.mjs +10 -4
- package/src/standalone/session-service/stored-reader.mjs +20 -2
- package/src/standalone/session-service/viewers.mjs +3 -3
- package/src/standalone/session-service.mjs +3 -1
- package/src/tui/App.jsx +6 -1
- package/src/tui/app/create-app-pickers.mjs +1 -0
- package/src/tui/app/doctor.mjs +6 -14
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +8 -3
- package/src/tui/app/settings-picker/settings-rows.mjs +5 -1
- package/src/tui/app/use-ui-open-request.mjs +13 -3
- package/src/tui/dist/index.mjs +41 -9
- package/src/tui/session/session-api/settings.mjs +0 -6
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
normalizeSystemShellCommand,
|
|
21
21
|
normalizeAutoClearConfig,
|
|
22
22
|
autoClearIdleMsForProvider,
|
|
23
|
+
resolveAutoClearIdleMs,
|
|
23
24
|
autoClearProviderDefaults,
|
|
24
25
|
normalizeCompactionConfig,
|
|
25
26
|
setModuleEnabledInConfig,
|
|
@@ -75,7 +76,10 @@ export function wireApis(boot) {
|
|
|
75
76
|
});
|
|
76
77
|
boot.lifecycleApi = lifecycleApiFor(boot);
|
|
77
78
|
boot.resourceApi = resourceApiFor(boot);
|
|
78
|
-
boot.disposeGlobalExtensionSubscription = () =>
|
|
79
|
+
boot.disposeGlobalExtensionSubscription = () => {
|
|
80
|
+
boot.disposeConfigSync?.();
|
|
81
|
+
boot.resourceApi.disposeGlobalExtensionSubscription?.();
|
|
82
|
+
};
|
|
79
83
|
boot.modelRouteApi = modelRouteApiFor(boot);
|
|
80
84
|
boot.workflowAgentsApi = workflowAgentsApiFor(boot);
|
|
81
85
|
boot.sessionTurnApi = sessionTurnApiFor(boot);
|
|
@@ -207,6 +211,7 @@ function settingsApiFor(boot) {
|
|
|
207
211
|
hasOwn,
|
|
208
212
|
normalizeAutoClearConfig,
|
|
209
213
|
autoClearIdleMsForProvider,
|
|
214
|
+
resolveAutoClearIdleMs,
|
|
210
215
|
autoClearProviderDefaults,
|
|
211
216
|
normalizeCompactionConfig,
|
|
212
217
|
normalizeSystemShellConfig,
|
|
@@ -229,7 +234,6 @@ function settingsApiFor(boot) {
|
|
|
229
234
|
tidyToolEnabledFn: boot.tidyToolEnabledFn,
|
|
230
235
|
localProviderEnabledFn: boot.localProviderEnabledFn,
|
|
231
236
|
webSearchEnabled: boot.webSearchEnabled,
|
|
232
|
-
channelsEnabled: boot.channelsEnabled,
|
|
233
237
|
autoUpdateEnabled: selfUpdate.autoUpdateEnabled,
|
|
234
238
|
getUpdateCheckState: () => selfUpdate.getCheckState(),
|
|
235
239
|
getUpdateProcessState: () => selfUpdate.getProcessState(),
|
|
@@ -86,7 +86,6 @@ export async function loadModules(boot) {
|
|
|
86
86
|
localProviderEnabledFn,
|
|
87
87
|
mediaToolEnabledFn,
|
|
88
88
|
tidyToolEnabledFn,
|
|
89
|
-
channelsEnabled,
|
|
90
89
|
featureDisallowedTools,
|
|
91
90
|
} = createRuntimeFeatureGates({
|
|
92
91
|
getConfig: () => rt.config,
|
|
@@ -114,7 +113,6 @@ export async function loadModules(boot) {
|
|
|
114
113
|
localProviderEnabledFn,
|
|
115
114
|
mediaToolEnabledFn,
|
|
116
115
|
tidyToolEnabledFn,
|
|
117
|
-
channelsEnabled,
|
|
118
116
|
featureDisallowedTools,
|
|
119
117
|
getMemoryModule,
|
|
120
118
|
getWebSearchModule,
|
|
@@ -71,9 +71,11 @@ function wireConfigLifecycle(boot) {
|
|
|
71
71
|
LAZY_SECRET_PROVIDERS,
|
|
72
72
|
clean,
|
|
73
73
|
resolve,
|
|
74
|
+
onConfigReloaded: () => boot.invalidateContextStatusCache?.(),
|
|
74
75
|
STANDALONE_DATA_DIR,
|
|
75
76
|
});
|
|
76
77
|
const {
|
|
78
|
+
disposeConfigSync,
|
|
77
79
|
getOutputStyleStatusCached,
|
|
78
80
|
invalidateOutputStyleStatusCache,
|
|
79
81
|
seedOutputStyleStatusCache,
|
|
@@ -89,6 +91,7 @@ function wireConfigLifecycle(boot) {
|
|
|
89
91
|
ensureConfigForRouteProvider,
|
|
90
92
|
} = lifecycle;
|
|
91
93
|
Object.assign(boot, {
|
|
94
|
+
disposeConfigSync,
|
|
92
95
|
getOutputStyleStatusCached,
|
|
93
96
|
invalidateOutputStyleStatusCache,
|
|
94
97
|
seedOutputStyleStatusCache,
|
|
@@ -232,7 +232,6 @@ function wireInternalTools(boot) {
|
|
|
232
232
|
officeToolsEnabled: boot.officeToolsEnabledFn,
|
|
233
233
|
mediaToolEnabled: boot.mediaToolEnabledFn,
|
|
234
234
|
tidyToolEnabled: boot.tidyToolEnabledFn,
|
|
235
|
-
channelsEnabled: boot.channelsEnabled,
|
|
236
235
|
getWebSearchModule: boot.getWebSearchModule,
|
|
237
236
|
getMemoryModule: boot.getMemoryModule,
|
|
238
237
|
getCodeGraphModule: boot.getCodeGraphModule,
|
|
@@ -66,6 +66,7 @@ export function createConfigLifecycle({
|
|
|
66
66
|
clean,
|
|
67
67
|
resolve,
|
|
68
68
|
performanceNow = () => performance.now(),
|
|
69
|
+
onConfigReloaded,
|
|
69
70
|
STANDALONE_DATA_DIR,
|
|
70
71
|
}) {
|
|
71
72
|
const outputStyleCache = createOutputStyleStatusCache({
|
|
@@ -160,7 +161,17 @@ export function createConfigLifecycle({
|
|
|
160
161
|
return ensureFullConfig();
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
// Settings persisted by any other runtime (or process) reach this runtime's
|
|
165
|
+
// in-memory config. reloadFullConfig keeps our own pending edits, and the
|
|
166
|
+
// shared layer coalesces bursts into one pass per subscriber.
|
|
167
|
+
const unsubscribeConfigChange = sharedCfgMod?.subscribeConfigChange?.(() => {
|
|
168
|
+
reloadFullConfig();
|
|
169
|
+
outputStyleCache.invalidateOutputStyleStatusCache();
|
|
170
|
+
onConfigReloaded?.();
|
|
171
|
+
});
|
|
172
|
+
|
|
163
173
|
return {
|
|
174
|
+
disposeConfigSync: () => unsubscribeConfigChange?.(),
|
|
164
175
|
// output-style cache
|
|
165
176
|
...outputStyleCache,
|
|
166
177
|
// adopt / save
|
|
@@ -22,7 +22,6 @@ export function createInternalToolExecutor({
|
|
|
22
22
|
officeToolsEnabled,
|
|
23
23
|
mediaToolEnabled,
|
|
24
24
|
tidyToolEnabled = () => true,
|
|
25
|
-
channelsEnabled,
|
|
26
25
|
getWebSearchModule,
|
|
27
26
|
getMemoryModule,
|
|
28
27
|
getCodeGraphModule,
|
|
@@ -68,7 +67,6 @@ export function createInternalToolExecutor({
|
|
|
68
67
|
if (callerCtx?.invocationSource === 'model-tool') guardModelCall(name);
|
|
69
68
|
if (Object.hasOwn(handlers, name)) return handlers[name](args, { name, callerCtx, callerCwd });
|
|
70
69
|
if (channels.isChannelTool(name)) {
|
|
71
|
-
if (!channelsEnabled()) throw new Error('channels are disabled in settings');
|
|
72
70
|
return await channels.execute(name, args || {});
|
|
73
71
|
}
|
|
74
72
|
throw new Error(`unknown standalone internal tool: ${name}`);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Key names mirror the dependency names of settings-api.mjs / the session-build
|
|
6
6
|
// deps, so the runtime can hand them straight through.
|
|
7
7
|
import { builtinFeatureActive, featureDisallowedToolsFor, localGitToolsActive } from '../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import { recapEnabled } from '../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
|
|
9
9
|
import { browserBridgeAvailableSync } from '../runtime/browser-bridge/client.mjs';
|
|
10
10
|
import { computerBridgeAvailableSync } from '../runtime/computer-bridge/client.mjs';
|
|
11
11
|
|
|
@@ -17,12 +17,11 @@ export function createRuntimeFeatureGates({ getConfig, getToolProfile }) {
|
|
|
17
17
|
recapEnabledFn: () => recapEnabled(getConfig(), true),
|
|
18
18
|
memoryToolsEnabledFn: () => builtinFeatureActive(getConfig(), 'memory'),
|
|
19
19
|
webSearchEnabled: () => builtinFeatureActive(getConfig(), 'webSearch'),
|
|
20
|
-
gitToolsEnabledFn: () => localGitToolsActive(getConfig()
|
|
20
|
+
gitToolsEnabledFn: () => localGitToolsActive(getConfig()),
|
|
21
21
|
officeToolsEnabledFn: () => builtinFeatureActive(getConfig(), 'office'),
|
|
22
22
|
localProviderEnabledFn: () => builtinFeatureActive(getConfig(), 'localProvider'),
|
|
23
23
|
mediaToolEnabledFn: () => builtinFeatureActive(getConfig(), 'media'),
|
|
24
24
|
tidyToolEnabledFn: () => builtinFeatureActive(getConfig(), 'tidy'),
|
|
25
|
-
channelsEnabled: () => moduleEnabled(getConfig(), 'channels', true),
|
|
26
25
|
// Browser/Computer activate on live desktop-bridge presence, so the probe
|
|
27
26
|
// runs per call instead of being captured at boot.
|
|
28
27
|
featureDisallowedTools: () =>
|
|
@@ -48,7 +48,6 @@ export async function runScheduleSession(schedule, { config = null, prompt: prom
|
|
|
48
48
|
owner: 'user',
|
|
49
49
|
sourceType: 'schedule',
|
|
50
50
|
sourceName: schedule.name,
|
|
51
|
-
sourceDelivery: schedule.delivery || null,
|
|
52
51
|
...(cwd ? { cwd } : {}),
|
|
53
52
|
desktopSession: cwd
|
|
54
53
|
? { classification: 'project', projectPath: cwd }
|
|
@@ -188,17 +188,13 @@ function scheduleToDisplay(s) {
|
|
|
188
188
|
whenAt: s.whenAt || undefined,
|
|
189
189
|
whenCron: s.whenCron || undefined,
|
|
190
190
|
timezone: s.timezone || undefined,
|
|
191
|
-
channel: s.channelId || undefined,
|
|
192
191
|
model: s.model || undefined,
|
|
193
192
|
cwd: s.cwd || undefined,
|
|
194
193
|
workflow: s.workflow || undefined,
|
|
195
194
|
attachments: s.attachments || undefined,
|
|
196
|
-
// Delivery mode: legacy channel-target rows (pre-delivery) behaved as
|
|
197
|
-
// relay+visible, which maps to 'both'.
|
|
198
|
-
delivery: s.delivery || (s.target === 'channel' ? 'both' : 'app'),
|
|
199
195
|
enabled: s.enabled !== false,
|
|
200
196
|
instructions: s.prompt,
|
|
201
|
-
route:
|
|
197
|
+
route: 'session',
|
|
202
198
|
};
|
|
203
199
|
}
|
|
204
200
|
|
|
@@ -210,7 +206,7 @@ export async function listSchedules() {
|
|
|
210
206
|
// Register or update a schedule in the PG store. Recurring input maps `time`
|
|
211
207
|
// (+ optional `days`) to a cron; one-shot input maps an `at` datetime; the two
|
|
212
208
|
// are mutually exclusive (also enforced by the store's when_at/when_cron XOR).
|
|
213
|
-
//
|
|
209
|
+
// Every schedule runs as a visible app session and requires a model.
|
|
214
210
|
export async function saveSchedule({
|
|
215
211
|
name,
|
|
216
212
|
description = '',
|
|
@@ -218,12 +214,10 @@ export async function saveSchedule({
|
|
|
218
214
|
at,
|
|
219
215
|
timezone,
|
|
220
216
|
days,
|
|
221
|
-
channel,
|
|
222
217
|
model,
|
|
223
218
|
cwd,
|
|
224
219
|
workflow,
|
|
225
220
|
attachments,
|
|
226
|
-
delivery,
|
|
227
221
|
enabled,
|
|
228
222
|
instructions,
|
|
229
223
|
overwrite = false,
|
|
@@ -233,11 +227,6 @@ export async function saveSchedule({
|
|
|
233
227
|
if (!body) throw new Error('schedule instructions are required');
|
|
234
228
|
const scheduleModel = String(model || '').trim();
|
|
235
229
|
if (!scheduleModel) throw new Error('schedule model is required — choose a model before saving');
|
|
236
|
-
// 'app' → session-only; 'channel'/'both' → the run result relays to the
|
|
237
|
-
// main channel (target 'channel', channelId resolved at fire time).
|
|
238
|
-
const requestedMode = String(delivery || '').trim();
|
|
239
|
-
let mode = channel ? 'both' : 'app';
|
|
240
|
-
if (['app', 'channel', 'both'].includes(requestedMode)) mode = requestedMode;
|
|
241
230
|
const hasTime = time != null && String(time).trim() !== '';
|
|
242
231
|
const hasAt = at != null && String(at).trim() !== '';
|
|
243
232
|
if (hasTime && hasAt) throw new Error('provide either `time` (recurring) or `at` (one-shot), not both');
|
|
@@ -254,13 +243,13 @@ export async function saveSchedule({
|
|
|
254
243
|
whenCron,
|
|
255
244
|
whenAt,
|
|
256
245
|
timezone: scheduleTimezone,
|
|
257
|
-
target:
|
|
258
|
-
channelId:
|
|
246
|
+
target: 'session',
|
|
247
|
+
channelId: null,
|
|
259
248
|
model: scheduleModel,
|
|
260
249
|
cwd: cwd ? String(cwd).trim() : null,
|
|
261
250
|
workflow: workflow ? String(workflow).trim() : null,
|
|
262
251
|
attachments: normalizeAutomationAttachments(attachments),
|
|
263
|
-
delivery:
|
|
252
|
+
delivery: 'app',
|
|
264
253
|
prompt: body,
|
|
265
254
|
enabled: enabled !== false,
|
|
266
255
|
nextFireAt: whenAt,
|
|
@@ -294,19 +283,17 @@ async function listWebhooks() {
|
|
|
294
283
|
name: ep.name,
|
|
295
284
|
description: ep.description || '',
|
|
296
285
|
parser: ep.parser || 'github',
|
|
297
|
-
...(ep.channelId ? { channel: ep.channelId } : {}),
|
|
298
286
|
...(ep.model ? { model: ep.model } : {}),
|
|
299
287
|
...(ep.cwd ? { cwd: ep.cwd } : {}),
|
|
300
288
|
...(ep.workflow ? { workflow: ep.workflow } : {}),
|
|
301
289
|
...(ep.attachments ? { attachments: ep.attachments } : {}),
|
|
302
|
-
delivery: ep.delivery || 'app',
|
|
303
290
|
enabled: ep.enabled,
|
|
304
291
|
// The store never projects the plaintext secret through list paths; it
|
|
305
292
|
// exposes a presence flag (secretSet) instead.
|
|
306
293
|
secretSet: ep.secretSet === true,
|
|
307
294
|
secret: undefined,
|
|
308
295
|
instructions: ep.instructions,
|
|
309
|
-
route:
|
|
296
|
+
route: 'session',
|
|
310
297
|
}));
|
|
311
298
|
}
|
|
312
299
|
|
|
@@ -315,12 +302,10 @@ export async function saveWebhook({
|
|
|
315
302
|
description = '',
|
|
316
303
|
parser = 'github',
|
|
317
304
|
secret,
|
|
318
|
-
channel,
|
|
319
305
|
model,
|
|
320
306
|
cwd,
|
|
321
307
|
workflow,
|
|
322
308
|
attachments,
|
|
323
|
-
delivery,
|
|
324
309
|
enabled,
|
|
325
310
|
instructions,
|
|
326
311
|
overwrite = false,
|
|
@@ -334,7 +319,6 @@ export async function saveWebhook({
|
|
|
334
319
|
}
|
|
335
320
|
const body = String(instructions || '').trim();
|
|
336
321
|
if (!body) throw new Error('webhook instructions are required');
|
|
337
|
-
if (channel && !model) throw new Error('model is required when channel is set');
|
|
338
322
|
if (overwrite !== true && (await dbLoadEndpoint(id))) {
|
|
339
323
|
throw new Error(`webhook "${id}" already exists`);
|
|
340
324
|
}
|
|
@@ -350,12 +334,12 @@ export async function saveWebhook({
|
|
|
350
334
|
name: id,
|
|
351
335
|
description: String(description || '').trim(),
|
|
352
336
|
parser: nextParser,
|
|
353
|
-
channelId:
|
|
337
|
+
channelId: null,
|
|
354
338
|
model: model ? String(model).trim() : null,
|
|
355
339
|
cwd: cwd ? String(cwd).trim() : null,
|
|
356
340
|
workflow: workflow ? String(workflow).trim() : null,
|
|
357
341
|
attachments: normalizeAutomationAttachments(attachments),
|
|
358
|
-
delivery:
|
|
342
|
+
delivery: 'app',
|
|
359
343
|
secret: secretValue,
|
|
360
344
|
instructions: body,
|
|
361
345
|
enabled: enabled !== false,
|
|
@@ -365,12 +349,10 @@ export async function saveWebhook({
|
|
|
365
349
|
name: id,
|
|
366
350
|
description: saved.description,
|
|
367
351
|
parser: saved.parser,
|
|
368
|
-
...(saved.channelId ? { channel: saved.channelId } : {}),
|
|
369
352
|
...(saved.model ? { model: saved.model } : {}),
|
|
370
353
|
...(saved.cwd ? { cwd: saved.cwd } : {}),
|
|
371
354
|
...(saved.workflow ? { workflow: saved.workflow } : {}),
|
|
372
355
|
...(saved.attachments ? { attachments: saved.attachments } : {}),
|
|
373
|
-
delivery: saved.delivery || 'app',
|
|
374
356
|
...(enabled === false ? { enabled: false } : {}),
|
|
375
357
|
secret: secretValue,
|
|
376
358
|
instructions: body,
|
|
@@ -39,6 +39,7 @@ export function createCompactionSettings({
|
|
|
39
39
|
hasOwn,
|
|
40
40
|
normalizeAutoClearConfig,
|
|
41
41
|
autoClearIdleMsForProvider,
|
|
42
|
+
resolveAutoClearIdleMs,
|
|
42
43
|
normalizeCompactionConfig,
|
|
43
44
|
autoClearProviderDefaults,
|
|
44
45
|
parseDurationMs,
|
|
@@ -96,7 +97,9 @@ export function createCompactionSettings({
|
|
|
96
97
|
const normalized = normalizeAutoClearConfig(config.autoClear);
|
|
97
98
|
const provider = route?.provider || null;
|
|
98
99
|
const providerDefault = autoClearIdleMsForProvider(provider, normalized.providerIdleMs);
|
|
99
|
-
const idleMs =
|
|
100
|
+
const idleMs = resolveAutoClearIdleMs(config, provider);
|
|
101
|
+
const key = String(provider || '').toLowerCase();
|
|
102
|
+
const providerCustom = Boolean(key && key !== 'default' && normalized.providerIdleMs?.[key]);
|
|
100
103
|
// Advanced picker shows only providers the user actually has enabled
|
|
101
104
|
// (config.providers[*].enabled), plus the active route provider, any
|
|
102
105
|
// provider with a custom override, and the 'default' fallback row —
|
|
@@ -114,6 +117,7 @@ export function createCompactionSettings({
|
|
|
114
117
|
enabled: normalized.enabled,
|
|
115
118
|
idleMs,
|
|
116
119
|
custom: normalized.custom,
|
|
120
|
+
providerCustom,
|
|
117
121
|
providerDefault,
|
|
118
122
|
provider,
|
|
119
123
|
providerDefaults,
|
|
@@ -1,39 +1,21 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Automation worker status, the system shell and self-update settings.
|
|
2
2
|
export function createSystemSettings({
|
|
3
3
|
getConfig,
|
|
4
4
|
saveConfigAndAdopt,
|
|
5
5
|
normalizeSystemShellConfig,
|
|
6
6
|
normalizeSystemShellCommand,
|
|
7
7
|
setConfiguredShell,
|
|
8
|
-
setModuleEnabledInConfig,
|
|
9
8
|
localPackageVersion,
|
|
10
|
-
channelsEnabled,
|
|
11
9
|
autoUpdateEnabled,
|
|
12
10
|
getUpdateCheckState,
|
|
13
11
|
getUpdateProcessState,
|
|
14
|
-
invalidatePreSessionToolSurface,
|
|
15
12
|
channels,
|
|
16
|
-
clearChannelStartTimer,
|
|
17
13
|
checkForUpdateInternal,
|
|
18
14
|
runUpdateNowInternal,
|
|
19
15
|
}) {
|
|
20
16
|
return {
|
|
21
17
|
getChannelSettings(options = {}) {
|
|
22
|
-
return {
|
|
23
|
-
enabled: channelsEnabled(),
|
|
24
|
-
...(options?.includeStatus === false ? {} : { status: channels.status() }),
|
|
25
|
-
};
|
|
26
|
-
},
|
|
27
|
-
async setChannelsEnabled(enabled) {
|
|
28
|
-
const config = getConfig();
|
|
29
|
-
const nextConfig = setModuleEnabledInConfig({ ...config }, 'channels', enabled !== false);
|
|
30
|
-
saveConfigAndAdopt(nextConfig);
|
|
31
|
-
if (!channelsEnabled()) {
|
|
32
|
-
clearChannelStartTimer();
|
|
33
|
-
await channels.stop('settings-disabled', { waitForExit: false }).catch(() => {});
|
|
34
|
-
}
|
|
35
|
-
invalidatePreSessionToolSurface();
|
|
36
|
-
return this.getChannelSettings();
|
|
18
|
+
return options?.includeStatus === false ? {} : { status: channels.status() };
|
|
37
19
|
},
|
|
38
20
|
getSystemShell() {
|
|
39
21
|
const config = getConfig();
|
|
@@ -18,6 +18,12 @@ import {
|
|
|
18
18
|
} from './tool-defs.mjs';
|
|
19
19
|
import { schemaValueError } from '../../runtime/shared/schema-value-error.mjs';
|
|
20
20
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
21
|
+
import profileConfig from '../../runtime/shared/profile-config.cjs';
|
|
22
|
+
|
|
23
|
+
const PROFILE_VALUE_IDS = Object.freeze({
|
|
24
|
+
language: profileConfig.PROFILE_LANGUAGES.map((entry) => entry.id),
|
|
25
|
+
experienceLevel: ['', ...profileConfig.PROFILE_EXPERIENCE_LEVELS.map((entry) => entry.id)],
|
|
26
|
+
});
|
|
21
27
|
|
|
22
28
|
const ACTION_APPLIES_TO = {
|
|
23
29
|
set_recap_enabled: 'background Memory cycles; no restart required',
|
|
@@ -49,6 +55,9 @@ const OPEN_TARGET_HINTS = Object.freeze({
|
|
|
49
55
|
usage: 'Desktop: /usage in the composer · TUI: /usage',
|
|
50
56
|
doctor: 'Desktop: Settings → System → Doctor, or /doctor in the composer · TUI: /doctor',
|
|
51
57
|
context: 'Desktop: /context in the composer · TUI: /context',
|
|
58
|
+
developer: 'Desktop: Settings → Developer options · TUI: /setting → Developer',
|
|
59
|
+
voice: 'Desktop: Extensions → Plugin → Built-in (Voice) · TUI: /setting → Voice',
|
|
60
|
+
connection: 'Desktop only: Settings → Connection',
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
function requireEnum(value, allowed, label) {
|
|
@@ -123,6 +132,20 @@ function publicProviderRows(setup) {
|
|
|
123
132
|
};
|
|
124
133
|
}
|
|
125
134
|
|
|
135
|
+
/** OAuth account roster without usage meters or provider-side identities. */
|
|
136
|
+
function publicAccounts(pool) {
|
|
137
|
+
return {
|
|
138
|
+
selectedId: pool.selectedId,
|
|
139
|
+
auto: pool.auto,
|
|
140
|
+
accounts: pool.accounts.map(({ id, label, authenticated, reauthRequired }) => ({
|
|
141
|
+
id,
|
|
142
|
+
label,
|
|
143
|
+
authenticated,
|
|
144
|
+
reauthRequired,
|
|
145
|
+
})),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
function mcpRows(status) {
|
|
127
150
|
return {
|
|
128
151
|
connectedCount: Number(status?.connectedCount || 0),
|
|
@@ -241,7 +264,14 @@ const SETUP_STATUS_READERS = {
|
|
|
241
264
|
};
|
|
242
265
|
},
|
|
243
266
|
shell: (rt) => rt.getSystemShell?.() || {},
|
|
244
|
-
providers: async (rt) =>
|
|
267
|
+
providers: async (rt) => {
|
|
268
|
+
const rows = publicProviderRows(await rt.getProviderSetup?.({}));
|
|
269
|
+
rows.oauth = rows.oauth.map((row) =>
|
|
270
|
+
row.authenticated ? { ...row, accounts: publicAccounts(rt.getProviderAccounts(row.id)) } : row
|
|
271
|
+
);
|
|
272
|
+
return rows;
|
|
273
|
+
},
|
|
274
|
+
developer: (rt) => rt.getDeveloperSettings(),
|
|
245
275
|
mcp: (rt) => mcpRows(rt.mcpStatus?.()),
|
|
246
276
|
skills: (rt) => ({ ...(rt.skillsStatus?.() || {}), disabled: rt.getDisabledSkills?.()?.disabled || [] }),
|
|
247
277
|
plugins: (rt) => rt.pluginsStatus?.() || {},
|
|
@@ -274,7 +304,7 @@ function validateSetupInput(args) {
|
|
|
274
304
|
throw new Error('route.contextPercent is only accepted by set_route');
|
|
275
305
|
}
|
|
276
306
|
}
|
|
277
|
-
for (const field of ['desktop', 'appearance', 'webhook', 'compaction']) {
|
|
307
|
+
for (const field of ['desktop', 'appearance', 'webhook', 'compaction', 'providerAccount']) {
|
|
278
308
|
if (args[field] && !Object.keys(args[field]).length) throw new Error(`${field} requires at least one setting`);
|
|
279
309
|
}
|
|
280
310
|
return action;
|
|
@@ -326,6 +356,11 @@ const SETUP_ACTION_HANDLERS = {
|
|
|
326
356
|
const profile = args.profile && typeof args.profile === 'object' ? args.profile : null;
|
|
327
357
|
if (!profile || !Object.keys(profile).length)
|
|
328
358
|
throw new Error('profile with title, language, or experienceLevel is required');
|
|
359
|
+
for (const [field, ids] of Object.entries(PROFILE_VALUE_IDS)) {
|
|
360
|
+
if (Object.hasOwn(profile, field) && !ids.includes(profile[field])) {
|
|
361
|
+
throw new Error(`profile.${field} must be one of ${ids.map((id) => JSON.stringify(id)).join(', ')}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
329
364
|
const result = rt.setProfile(profile);
|
|
330
365
|
return {
|
|
331
366
|
title: result.title || '',
|
|
@@ -414,7 +449,23 @@ const SETUP_ACTION_HANDLERS = {
|
|
|
414
449
|
rt.deleteLocalProviderModel(requireText(args.confirmationToken, 'confirmationToken')),
|
|
415
450
|
set_system_shell: (rt, args) => rt.setSystemShell({ command: clean(args.command) }),
|
|
416
451
|
set_auto_update: (rt, args) => rt.setAutoUpdate(requireBoolean(args.enabled)),
|
|
417
|
-
forget_provider_auth: (rt, args) =>
|
|
452
|
+
forget_provider_auth: async (rt, args) => {
|
|
453
|
+
const name = requireText(args.name, 'name');
|
|
454
|
+
const setup = await rt.getProviderSetup({});
|
|
455
|
+
if (!(setup?.oauth || []).some((row) => row.id === name)) {
|
|
456
|
+
if (Object.hasOwn(args, 'accountId')) throw new Error('accountId applies only to OAuth providers');
|
|
457
|
+
return rt.forgetProviderAuth(name);
|
|
458
|
+
}
|
|
459
|
+
const { accounts } = rt.getProviderAccounts(name);
|
|
460
|
+
if (!accounts.length) throw new Error(`${name} has no connected account`);
|
|
461
|
+
if (!Object.hasOwn(args, 'accountId') && accounts.length > 1) {
|
|
462
|
+
throw new Error(`${name} has ${accounts.length} accounts; pass accountId from status providers`);
|
|
463
|
+
}
|
|
464
|
+
const accountId = Object.hasOwn(args, 'accountId') ? requireText(args.accountId, 'accountId') : accounts[0].id;
|
|
465
|
+
return { ...(await rt.forgetProviderAuth(name, accountId)), accountId };
|
|
466
|
+
},
|
|
467
|
+
set_provider_account: async (rt, args) =>
|
|
468
|
+
publicAccounts(await rt.updateProviderAccounts(requireText(args.name, 'name'), args.providerAccount)),
|
|
418
469
|
add_mcp_server: async (rt, args) => {
|
|
419
470
|
const server = mcpServerInput(args);
|
|
420
471
|
requireText(server.name, 'server.name');
|
|
@@ -456,6 +507,13 @@ const SETUP_ACTION_HANDLERS = {
|
|
|
456
507
|
remove_plugin: async (rt, args) => ({
|
|
457
508
|
plugin: (await rt.removePlugin(requireText(args.name, 'name')))?.plugin || null,
|
|
458
509
|
}),
|
|
510
|
+
enable_plugin_mcp: async (rt, args) => {
|
|
511
|
+
const name = requireText(args.name, 'name');
|
|
512
|
+
const plugin = ((await rt.pluginsStatus())?.plugins || []).find((row) => row.name === name || row.id === name);
|
|
513
|
+
if (!plugin) throw new Error(`plugin "${name}" not found`);
|
|
514
|
+
const result = await rt.enablePluginMcp(plugin);
|
|
515
|
+
return { serverName: result.serverName, mcp: mcpRows(result.status) };
|
|
516
|
+
},
|
|
459
517
|
};
|
|
460
518
|
|
|
461
519
|
const READ_ONLY_SETUP_ACTIONS = new Set([
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { parseSkillDocument } from '../../runtime/shared/skill-document.mjs';
|
|
2
1
|
import { SETUP_EXTENDED_PROPERTIES, SETUP_DESKTOP_ACTIONS } from './settings-contract.mjs';
|
|
3
2
|
|
|
4
3
|
const own = (value, key) => Object.hasOwn(value || {}, key);
|
|
@@ -76,15 +75,16 @@ export function validateMcpInput(server) {
|
|
|
76
75
|
async function readDefinition(rt, kind, name) {
|
|
77
76
|
if (kind === 'workflow') return rt.getWorkflowPack(name);
|
|
78
77
|
if (kind === 'agent') return rt.getAgentDefinition(name);
|
|
78
|
+
// skillContent already returns the SKILL.md body without frontmatter; the
|
|
79
|
+
// frontmatter fields come from the catalog row.
|
|
79
80
|
const resource = await rt.skillContent(name);
|
|
80
81
|
const metadata = (await rt.skillsStatus()).skills.find((row) => row.name === name) || {};
|
|
81
|
-
const parsed = parseSkillDocument(resource.content);
|
|
82
82
|
return {
|
|
83
83
|
name,
|
|
84
84
|
originalName: name,
|
|
85
|
-
description: metadata.description ||
|
|
86
|
-
whenToUse: metadata.whenToUse ||
|
|
87
|
-
body:
|
|
85
|
+
description: metadata.description || '',
|
|
86
|
+
whenToUse: metadata.whenToUse || '',
|
|
87
|
+
body: resource.content,
|
|
88
88
|
toolDependencies: metadata.toolDependencies || [],
|
|
89
89
|
editable: metadata.editable === true,
|
|
90
90
|
};
|
|
@@ -103,6 +103,19 @@ async function saveDefinition(rt, args) {
|
|
|
103
103
|
const id = input.originalName || input.id || input.name;
|
|
104
104
|
if (!creating && !String(id || '').trim()) throw new Error('An existing definition identity is required');
|
|
105
105
|
const current = creating ? {} : await readDefinition(rt, kind, id);
|
|
106
|
+
if (kind === 'skill' && !creating && !current.editable) {
|
|
107
|
+
// Built-in and plugin skills keep their document; like the Skills editor,
|
|
108
|
+
// only their tool dependencies are user-editable (null restores them).
|
|
109
|
+
const edits = Object.keys(input).filter((key) => key !== 'originalName' && key !== 'name');
|
|
110
|
+
if (edits.length !== 1 || edits[0] !== 'toolDependencies' || (own(input, 'name') && input.name !== current.name)) {
|
|
111
|
+
throw new Error('Built-in and plugin skills accept only toolDependencies edits');
|
|
112
|
+
}
|
|
113
|
+
return rt.saveSkill({
|
|
114
|
+
originalName: current.originalName,
|
|
115
|
+
dependenciesOnly: true,
|
|
116
|
+
toolDependencies: input.toolDependencies,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
106
119
|
const next = { ...pick(current, definitionKeys), ...input };
|
|
107
120
|
if (!String(next.body || '').trim()) throw new Error('definition.body must not be empty');
|
|
108
121
|
if (kind === 'workflow') {
|
|
@@ -115,7 +128,6 @@ async function saveDefinition(rt, args) {
|
|
|
115
128
|
}
|
|
116
129
|
return rt.saveAgentDefinition(creating ? next : { ...next, id: current.id });
|
|
117
130
|
}
|
|
118
|
-
if (!creating && !current.editable) throw new Error('Only machine-global user skills can be edited');
|
|
119
131
|
return creating ? rt.addSkill(next) : rt.saveSkill({ ...next, originalName: current.originalName });
|
|
120
132
|
}
|
|
121
133
|
|
|
@@ -188,6 +200,19 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
|
|
|
188
200
|
args.automationKind === 'schedule'
|
|
189
201
|
? rt.setScheduleEnabled(args.name, args.enabled)
|
|
190
202
|
: rt.setWebhookEnabled(args.name, args.enabled),
|
|
203
|
+
async set_developer_option(rt, args) {
|
|
204
|
+
const option = rt
|
|
205
|
+
.getDeveloperSettings()
|
|
206
|
+
.sections.flatMap((section) => section.options)
|
|
207
|
+
.find((row) => row.id === args.name);
|
|
208
|
+
if (!option) throw new Error(`Unknown developer option "${args.name}"; read status developer`);
|
|
209
|
+
if (args.enabled && option.warning && args.riskAccepted !== true) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Enabling ${option.label} requires riskAccepted:true after the user explicitly accepts: ${option.warning}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return { developer: await rt.setDeveloperOption(option.id, args.enabled) };
|
|
215
|
+
},
|
|
191
216
|
async set_webhook_config(rt, args) {
|
|
192
217
|
await rt.setWebhookConfig(args.webhook);
|
|
193
218
|
const status = await rt.getChannelSetup();
|
|
@@ -31,10 +31,26 @@ export const SETUP_EXTENDED_ACTION_FIELDS = Object.freeze({
|
|
|
31
31
|
save_project: 'project',
|
|
32
32
|
remove_project: 'projectPath',
|
|
33
33
|
revoke_linked_device: 'name',
|
|
34
|
+
set_activity_rail_pins: 'activityRailPins',
|
|
35
|
+
set_developer_option: 'name enabled riskAccepted?',
|
|
34
36
|
});
|
|
35
37
|
|
|
38
|
+
/** Mirrors ACTIVITY_RAIL_PIN_IDS in apps/desktop/src/shared/activity-rail-pins.ts. */
|
|
39
|
+
export const SETUP_ACTIVITY_RAIL_PIN_IDS = Object.freeze([
|
|
40
|
+
'sessions',
|
|
41
|
+
'agents',
|
|
42
|
+
'schedules',
|
|
43
|
+
'workflows',
|
|
44
|
+
'projects',
|
|
45
|
+
'extensions',
|
|
46
|
+
'source-control',
|
|
47
|
+
'search',
|
|
48
|
+
'webhooks',
|
|
49
|
+
]);
|
|
50
|
+
|
|
36
51
|
export const SETUP_EXTENDED_DOMAINS = Object.freeze([
|
|
37
52
|
'capabilities',
|
|
53
|
+
'developer',
|
|
38
54
|
'desktop',
|
|
39
55
|
'appearance',
|
|
40
56
|
'projects',
|
|
@@ -49,6 +65,7 @@ export const SETUP_DESKTOP_ACTIONS = Object.freeze([
|
|
|
49
65
|
'save_project',
|
|
50
66
|
'remove_project',
|
|
51
67
|
'revoke_linked_device',
|
|
68
|
+
'set_activity_rail_pins',
|
|
52
69
|
]);
|
|
53
70
|
export const SETUP_DESKTOP_DOMAINS = Object.freeze(['desktop', 'appearance', 'projects', 'connection']);
|
|
54
71
|
|
|
@@ -128,7 +145,6 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
|
|
|
128
145
|
timezone: text,
|
|
129
146
|
days: { ...text, description: 'Schedule day selector, e.g. weekdays or mon,wed,fri.' },
|
|
130
147
|
parser: { type: 'string', enum: ['github', 'generic', 'stripe', 'sentry'] },
|
|
131
|
-
channel: text,
|
|
132
148
|
model: {
|
|
133
149
|
...text,
|
|
134
150
|
description:
|
|
@@ -153,7 +169,6 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
|
|
|
153
169
|
['kind', 'data']
|
|
154
170
|
),
|
|
155
171
|
},
|
|
156
|
-
delivery: { type: 'string', enum: ['app', 'channel', 'both'] },
|
|
157
172
|
enabled: boolean,
|
|
158
173
|
overwrite: { ...boolean, description: 'Explicitly update an existing entry; omitted fields are preserved.' },
|
|
159
174
|
},
|
|
@@ -178,10 +193,19 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
|
|
|
178
193
|
description: 'Desktop UI language id from status appearance; independent of profile language.',
|
|
179
194
|
},
|
|
180
195
|
sidePanels: { type: 'string', enum: ['close-left', 'close-right', 'close-both', 'keep-open'] },
|
|
181
|
-
zoom: { type: 'number', minimum: 0.5, maximum: 2 },
|
|
182
196
|
}),
|
|
183
197
|
project: object({ path: text, alias: text }, ['path']),
|
|
184
198
|
projectPath: { ...text, description: 'Exact registered Project root.' },
|
|
199
|
+
activityRailPins: {
|
|
200
|
+
type: 'array',
|
|
201
|
+
items: { type: 'string', enum: [...SETUP_ACTIVITY_RAIL_PIN_IDS] },
|
|
202
|
+
description: 'set_activity_rail_pins: complete ordered list of pinned activity rail items; it replaces the current list.',
|
|
203
|
+
},
|
|
204
|
+
riskAccepted: {
|
|
205
|
+
...boolean,
|
|
206
|
+
description:
|
|
207
|
+
"set_developer_option: the user's explicit acceptance of the option warning; required to enable an option that has one.",
|
|
208
|
+
},
|
|
185
209
|
});
|
|
186
210
|
|
|
187
211
|
export const SETUP_HANDOFFS = Object.freeze({
|