mixdog 1.0.5 → 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 +206 -475
- 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
|
@@ -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({
|
|
@@ -32,8 +32,13 @@ export const SETUP_STATUS_DOMAINS = Object.freeze([
|
|
|
32
32
|
...SETUP_EXTENDED_DOMAINS,
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
|
+
/** Settings sections without a slash command. Each UI routes them with its
|
|
36
|
+
* own setup-open table (Desktop app-shell-ui-open-request.ts, TUI
|
|
37
|
+
* use-ui-open-request.mjs). */
|
|
38
|
+
export const SETUP_SECTION_OPEN_TARGETS = Object.freeze(['developer', 'voice', 'connection']);
|
|
39
|
+
|
|
35
40
|
/** Slash-command names shared by TUI (slash-commands.mjs) and Desktop
|
|
36
|
-
* (slash-commands.ts)
|
|
41
|
+
* (slash-commands.ts), then the settings sections above. */
|
|
37
42
|
export const SETUP_OPEN_TARGETS = Object.freeze([
|
|
38
43
|
'settings',
|
|
39
44
|
'providers',
|
|
@@ -53,6 +58,7 @@ export const SETUP_OPEN_TARGETS = Object.freeze([
|
|
|
53
58
|
'usage',
|
|
54
59
|
'doctor',
|
|
55
60
|
'context',
|
|
61
|
+
...SETUP_SECTION_OPEN_TARGETS,
|
|
56
62
|
]);
|
|
57
63
|
|
|
58
64
|
// Runtime dispatch contract. A trailing ? denotes an optional field. Keep
|
|
@@ -86,7 +92,8 @@ export const SETUP_ACTION_FIELDS = Object.freeze({
|
|
|
86
92
|
delete_local_model: 'confirmationToken',
|
|
87
93
|
set_system_shell: 'command',
|
|
88
94
|
set_auto_update: 'enabled',
|
|
89
|
-
forget_provider_auth: 'name',
|
|
95
|
+
forget_provider_auth: 'name accountId?',
|
|
96
|
+
set_provider_account: 'name providerAccount',
|
|
90
97
|
add_mcp_server: 'server',
|
|
91
98
|
save_mcp_server: 'server',
|
|
92
99
|
remove_mcp_server: 'name',
|
|
@@ -98,6 +105,7 @@ export const SETUP_ACTION_FIELDS = Object.freeze({
|
|
|
98
105
|
update_plugin: 'name',
|
|
99
106
|
set_plugin_enabled: 'name enabled',
|
|
100
107
|
remove_plugin: 'name',
|
|
108
|
+
enable_plugin_mcp: 'name',
|
|
101
109
|
...SETUP_EXTENDED_ACTION_FIELDS,
|
|
102
110
|
});
|
|
103
111
|
|
|
@@ -160,7 +168,7 @@ export const SETUP_TOOL_DEFS = Object.freeze([
|
|
|
160
168
|
target: {
|
|
161
169
|
type: 'string',
|
|
162
170
|
enum: [...SETUP_OPEN_TARGETS],
|
|
163
|
-
description: 'open: settings surface, named by its slash command.',
|
|
171
|
+
description: 'open: settings surface, named by its slash command or settings section.',
|
|
164
172
|
},
|
|
165
173
|
route: {
|
|
166
174
|
...ROUTE_SCHEMA,
|
|
@@ -174,7 +182,8 @@ export const SETUP_TOOL_DEFS = Object.freeze([
|
|
|
174
182
|
type: 'object',
|
|
175
183
|
additionalProperties: false,
|
|
176
184
|
properties: { title: { type: 'string' }, language: { type: 'string' }, experienceLevel: { type: 'string' } },
|
|
177
|
-
description:
|
|
185
|
+
description:
|
|
186
|
+
'set_profile: only the given fields change. language and experienceLevel take ids from status profile; experienceLevel "" clears it.',
|
|
178
187
|
},
|
|
179
188
|
autoclear: {
|
|
180
189
|
type: 'object',
|
|
@@ -204,7 +213,28 @@ export const SETUP_TOOL_DEFS = Object.freeze([
|
|
|
204
213
|
},
|
|
205
214
|
name: {
|
|
206
215
|
type: 'string',
|
|
207
|
-
description: `set_builtin_enabled: ${SETUP_BUILTIN_TOGGLE_FEATURES.join('|')}; install_builtin also accepts memory. Memory toggles use set_memory_enabled. set_first_use_approval: browser|computer; revoke_linked_device: client id from status connection, not its display name; else MCP/plugin/provider id.`,
|
|
216
|
+
description: `set_builtin_enabled: ${SETUP_BUILTIN_TOGGLE_FEATURES.join('|')}; install_builtin also accepts memory. Memory toggles use set_memory_enabled. set_first_use_approval: browser|computer; revoke_linked_device: client id from status connection, not its display name; set_developer_option: option id from status developer; else MCP/plugin/provider id.`,
|
|
217
|
+
},
|
|
218
|
+
accountId: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
description:
|
|
221
|
+
'forget_provider_auth: OAuth account id from status providers; required when the provider has several accounts.',
|
|
222
|
+
},
|
|
223
|
+
providerAccount: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
additionalProperties: false,
|
|
226
|
+
properties: {
|
|
227
|
+
selectedId: { type: 'string', description: 'Account to use now.' },
|
|
228
|
+
auto: { type: 'boolean', description: 'Automatic switching to another account when one is exhausted.' },
|
|
229
|
+
order: { type: 'array', items: { type: 'string' }, description: 'Every connected account id exactly once.' },
|
|
230
|
+
rename: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
additionalProperties: false,
|
|
233
|
+
required: ['id', 'label'],
|
|
234
|
+
properties: { id: { type: 'string' }, label: { type: 'string', maxLength: 80 } },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
description: 'set_provider_account: OAuth account change; account ids from status providers.',
|
|
208
238
|
},
|
|
209
239
|
modelId: {
|
|
210
240
|
type: 'string',
|
|
@@ -36,7 +36,6 @@ export async function runWebhookSession({
|
|
|
36
36
|
cwd = null,
|
|
37
37
|
workflow = null,
|
|
38
38
|
attachments = null,
|
|
39
|
-
delivery = null,
|
|
40
39
|
signal = null,
|
|
41
40
|
}) {
|
|
42
41
|
const endpoint = String(name || '').trim();
|
|
@@ -53,7 +52,6 @@ export async function runWebhookSession({
|
|
|
53
52
|
owner: 'user',
|
|
54
53
|
sourceType: 'webhook',
|
|
55
54
|
sourceName: endpoint,
|
|
56
|
-
sourceDelivery: delivery || null,
|
|
57
55
|
tools: WEBHOOK_SESSION_TOOLS,
|
|
58
56
|
...(projectCwd ? { cwd: projectCwd } : {}),
|
|
59
57
|
desktopSession: projectCwd
|
|
@@ -5,7 +5,11 @@ import { hasOwn } from '../../runtime/shared/object.mjs';
|
|
|
5
5
|
import { serializeFrontmatterDoc } from '../../runtime/shared/markdown-frontmatter.mjs';
|
|
6
6
|
import { isHiddenAgent } from '../../runtime/agent/orchestrator/internal-agents.mjs';
|
|
7
7
|
import { AGENT_DELETED_MARKER, FIXED_AGENT_SLOTS, availableAgentId, clearAgentDefinitionCache } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
canonicalizeAgentRouteStorage,
|
|
10
|
+
isAgentDisabled,
|
|
11
|
+
withAgentDisabled,
|
|
12
|
+
} from '../../runtime/shared/agent-route-config.mjs';
|
|
9
13
|
import { agentEditorId, effectiveAgentRoute, oneLine, resolveDataDir } from './shared.mjs';
|
|
10
14
|
|
|
11
15
|
const isFixedAgent = (id) => FIXED_AGENT_SLOTS.some((agent) => agent.id === id);
|
|
@@ -95,15 +99,21 @@ export function createAgentEditorApi(deps) {
|
|
|
95
99
|
if (!body) throw new Error('AGENT.md body must not be empty');
|
|
96
100
|
const dataDir = resolveDataDir(deps);
|
|
97
101
|
const { id, name, description } = resolveSaveTarget(payload, dataDir);
|
|
102
|
+
const existed = Boolean(loadAgentDefinition(dataDir, id));
|
|
98
103
|
writeAgentFiles(join(dataDir, 'agents', id), { name: name || id, description, body });
|
|
99
104
|
clearAgentDefinitionCache(id);
|
|
105
|
+
// A newly created agent starts enabled; a stale roster entry left by an
|
|
106
|
+
// earlier agent of the same id must not disable it.
|
|
107
|
+
if (!existed && isAgentDisabled(getConfig(), id)) saveConfigAndAdopt(withAgentDisabled(getConfig(), id, false));
|
|
100
108
|
if (payload.route) await setAgentRoute(id, payload.route);
|
|
101
109
|
return getAgentDefinition(id);
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
// A removed custom agent must not leave a dangling route/preset
|
|
112
|
+
// A removed custom agent must not leave a dangling route/preset or a
|
|
113
|
+
// disabled-roster entry that would silently disable a later agent of the
|
|
114
|
+
// same id.
|
|
105
115
|
function dropAgentRoute(id) {
|
|
106
|
-
const nextConfig =
|
|
116
|
+
const nextConfig = withAgentDisabled(getConfig(), id, false);
|
|
107
117
|
if (nextConfig.agents && id in nextConfig.agents) {
|
|
108
118
|
const agents = { ...nextConfig.agents };
|
|
109
119
|
delete agents[id];
|
|
@@ -29,6 +29,10 @@ export function createStoredSessionViews({ desktopRuntime, dataDir }) {
|
|
|
29
29
|
const store = await desktopRuntime.loadSessionStore();
|
|
30
30
|
store.forgetStoredSessionTranscript?.(sessionId);
|
|
31
31
|
},
|
|
32
|
+
releaseStoredSession: async (sessionId) => {
|
|
33
|
+
const store = await desktopRuntime.loadSessionStore();
|
|
34
|
+
store.releaseStoredSessionTranscript?.(sessionId);
|
|
35
|
+
},
|
|
32
36
|
readStoredGoal: async (sessionId) =>
|
|
33
37
|
readStoredGoalSnapshot({
|
|
34
38
|
dataDir,
|
|
@@ -38,8 +38,13 @@ export function createSessionViewCalls(ctx) {
|
|
|
38
38
|
bindExternalSessionView,
|
|
39
39
|
destroy,
|
|
40
40
|
} = ctx.entries;
|
|
41
|
-
const {
|
|
42
|
-
|
|
41
|
+
const {
|
|
42
|
+
storedSessionProjection,
|
|
43
|
+
storedProjectionUnchanged,
|
|
44
|
+
requestedMessageSlice,
|
|
45
|
+
forgetStoredSession,
|
|
46
|
+
releaseStoredSession,
|
|
47
|
+
} = ctx.storedReader;
|
|
43
48
|
|
|
44
49
|
/** A read/subscribe names the transcript window its view wants (none: the
|
|
45
50
|
* whole transcript). A window grown past what a resumed runtime holds is
|
|
@@ -229,8 +234,9 @@ export function createSessionViewCalls(ctx) {
|
|
|
229
234
|
if (token) external.subscribers?.delete(token);
|
|
230
235
|
if ((external.subscribers?.size || 0) === 0) externalViewEntries.delete(id);
|
|
231
236
|
}
|
|
232
|
-
// The last cold view left: its disk projection
|
|
233
|
-
|
|
237
|
+
// The last cold view left: its disk projection turns idle, kept within
|
|
238
|
+
// the store's idle budget so a revisit skips the parse.
|
|
239
|
+
if (!coldViewers) releaseStoredSession(id);
|
|
234
240
|
return { sessionId: id, unsubscribed: true };
|
|
235
241
|
}
|
|
236
242
|
const entry = owner;
|