mixdog 1.3.4 → 1.3.5
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/LICENSES/oh-my-pi-MIT.txt +23 -0
- package/NOTICE.md +15 -0
- package/README.md +11 -0
- package/package.json +2 -1
- package/scripts/embed-google-oauth-client.mjs +49 -0
- package/scripts/lib/test-spawn-ledger.mjs +46 -1
- package/scripts/test.mjs +5 -0
- package/src/defaults/shipped-history.json +8 -0
- package/src/defaults/skills/browser-use/SKILL.md +21 -9
- package/src/defaults/skills/setup/SKILL.md +7 -2
- package/src/defaults/skills/setup/references/actions.md +82 -27
- package/src/defaults/skills/setup/references/surfaces.md +67 -34
- package/src/defaults/skills/setup-guide/SKILL.md +3 -3
- package/src/lib/keychain-cjs.cjs +359 -29
- package/src/rules/agent-mode/AGENT_MODE.md +10 -2
- package/src/rules/agent-mode/MIXY.md +12 -0
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
- package/src/runtime/agent/orchestrator/config.mjs +18 -0
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
- package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
- package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
- package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
- package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
- package/src/runtime/channels/lib/scheduler.mjs +109 -14
- package/src/runtime/channels/lib/schedules-db.mjs +49 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
- package/src/runtime/channels/lib/worker-main.mjs +9 -0
- package/src/runtime/office/com/com-adapter.mjs +17 -1
- package/src/runtime/office/com/office-com-host.ps1 +51 -1
- package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
- package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
- package/src/runtime/office/pdf/document-preview.mjs +326 -8
- package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
- package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
- package/src/runtime/office/portable/portable-soffice.mjs +45 -5
- package/src/runtime/shared/bot-hops.mjs +62 -2
- package/src/runtime/shared/config.mjs +49 -22
- package/src/runtime/shared/connector-catalog.d.mts +4 -2
- package/src/runtime/shared/connector-catalog.mjs +16 -8
- package/src/runtime/shared/developer-options.mjs +32 -1
- package/src/runtime/shared/google-oauth-client.generated.json +4 -0
- package/src/runtime/shared/google-oauth-client.mjs +34 -9
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
- package/src/runtime/shared/llm/well-formed.mjs +66 -0
- package/src/runtime/shared/provider-accounts.mjs +224 -58
- package/src/runtime/shared/provider-api-key.mjs +19 -8
- package/src/runtime/shared/routine-trigger.mjs +42 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/transcript-metadata.mjs +38 -0
- package/src/runtime/shared/user-data-guard.mjs +30 -2
- package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
- package/src/session-runtime/agent-home-delivery.mjs +57 -13
- package/src/session-runtime/agent-room-delivery.mjs +28 -1
- package/src/session-runtime/agent-task-sessions.mjs +292 -79
- package/src/session-runtime/boot/begin.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-chain.mjs +32 -0
- package/src/session-runtime/bot-status.mjs +7 -4
- package/src/session-runtime/channel-config-api.mjs +63 -17
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
- package/src/session-runtime/internal-tool-executor.mjs +4 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
- package/src/session-runtime/mixy-heartbeat.mjs +807 -0
- package/src/session-runtime/model-route/route-tuning.mjs +10 -6
- package/src/session-runtime/owned-automation-run.mjs +33 -12
- package/src/session-runtime/provider-auth/login.mjs +7 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
- package/src/session-runtime/routine-home-turn.mjs +33 -6
- package/src/session-runtime/schedule-session-run.mjs +7 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
- package/src/session-runtime/services/channel-admin.mjs +66 -18
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +128 -25
- package/src/session-runtime/services/schedule-access.mjs +14 -0
- package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
- package/src/session-runtime/session-create/session-options.mjs +7 -4
- package/src/session-runtime/settings-system-api.mjs +16 -1
- package/src/session-runtime/setup-tool/executor.mjs +49 -14
- package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
- package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
- package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
- package/src/session-runtime/task-session-route.mjs +99 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
- package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
- package/src/session-runtime/tool-surface.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
- package/src/session-runtime/workspace/transitions.mjs +10 -2
- package/src/standalone/daemon-session-control.mjs +88 -20
- package/src/standalone/daemon.mjs +8 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
- package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
- package/src/standalone/session-service.mjs +13 -0
- package/src/standalone/session-tool-output.mjs +8 -5
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/dist/index.mjs +24 -2
- package/src/tui/session/notice-surface.mjs +17 -1
- package/src/tui/session/session-api/integrations.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +8 -0
- package/src/tui/session/session-api/settings.mjs +14 -2
- package/src/tui/session/turn-stream.mjs +17 -1
- package/src/tui/session/turn.mjs +22 -2
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { renameSync } from 'node:fs';
|
|
3
3
|
import { modelProxyClientMode, serverModelCredentialsDisabledError } from '../../runtime/shared/model-proxy-mode.mjs';
|
|
4
4
|
import { sameAccountIdentity } from '../../runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs';
|
|
5
|
+
import { agentApiKeyEnvName } from '../../runtime/shared/provider-api-key.mjs';
|
|
5
6
|
import {
|
|
6
7
|
AGENT_PROVIDER_ENV,
|
|
7
8
|
SECRET_ACCOUNTS,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
getAgentApiKey,
|
|
10
11
|
getKeychainStatus,
|
|
11
12
|
hasStoredSecret,
|
|
13
|
+
isFallbackSecret,
|
|
12
14
|
saveSecretAsync,
|
|
13
15
|
} from '../../runtime/shared/config.mjs';
|
|
14
16
|
import {
|
|
@@ -80,6 +82,13 @@ import {
|
|
|
80
82
|
hasCursorOAuthCredentials,
|
|
81
83
|
loginCursorOAuth,
|
|
82
84
|
} from '../../runtime/agent/orchestrator/providers/cursor-auth.mjs';
|
|
85
|
+
import {
|
|
86
|
+
beginDevinOAuthLogin,
|
|
87
|
+
describeDevinOAuthCredentials,
|
|
88
|
+
forgetDevinOAuthCredentials,
|
|
89
|
+
hasDevinOAuthCredentials,
|
|
90
|
+
loginDevinOAuth,
|
|
91
|
+
} from '../../runtime/agent/orchestrator/providers/devin-auth.mjs';
|
|
83
92
|
import {
|
|
84
93
|
describeAntigravityOAuthCredentials,
|
|
85
94
|
forgetAntigravityOAuthCredentials,
|
|
@@ -100,6 +109,7 @@ import {
|
|
|
100
109
|
removeProviderAccount,
|
|
101
110
|
clearProviderAccountQuotaState,
|
|
102
111
|
providerAccountLimit,
|
|
112
|
+
providerAccountBlockedUntil,
|
|
103
113
|
ACCOUNT_PROVIDERS,
|
|
104
114
|
} from '../../runtime/shared/provider-accounts.mjs';
|
|
105
115
|
import { currentProviderAccountId, withProviderAccount } from '../../runtime/shared/provider-auth-binding.mjs';
|
|
@@ -214,6 +224,16 @@ const OAUTH_PROVIDERS = Object.freeze([
|
|
|
214
224
|
begin: beginCursorOAuthLogin,
|
|
215
225
|
login: loginCursorOAuth,
|
|
216
226
|
}),
|
|
227
|
+
Object.freeze({
|
|
228
|
+
id: 'devin-oauth',
|
|
229
|
+
name: 'Devin',
|
|
230
|
+
desc: 'Sign in with your Devin account',
|
|
231
|
+
has: hasDevinOAuthCredentials,
|
|
232
|
+
describe: describeDevinOAuthCredentials,
|
|
233
|
+
forget: forgetDevinOAuthCredentials,
|
|
234
|
+
begin: beginDevinOAuthLogin,
|
|
235
|
+
login: loginDevinOAuth,
|
|
236
|
+
}),
|
|
217
237
|
Object.freeze({
|
|
218
238
|
id: 'antigravity-oauth',
|
|
219
239
|
name: 'Antigravity OAuth',
|
|
@@ -231,7 +251,7 @@ const BUILTIN_PROVIDER_IDS = new Set(['mixdog-local']);
|
|
|
231
251
|
const API_PROVIDER_IDS = new Set(API_PROVIDERS.map((p) => p.id));
|
|
232
252
|
const OAUTH_BY_ID = new Map(OAUTH_PROVIDERS.map((p) => [p.id, p]));
|
|
233
253
|
|
|
234
|
-
// Dev-only entries (cursor-oauth, antigravity-oauth, mimo-oauth) are unknown to
|
|
254
|
+
// Dev-only entries (cursor-oauth, devin-oauth, antigravity-oauth, mimo-oauth) are unknown to
|
|
235
255
|
// settings/login while the Developer → Providers option is off. Filtered per
|
|
236
256
|
// call, so toggling the option takes effect without a restart.
|
|
237
257
|
function availableOAuthProviders() {
|
|
@@ -320,33 +340,71 @@ const KEYCHAIN_HINTS = Object.freeze({
|
|
|
320
340
|
error: 'The system keyring could not be read — check that it is running and unlocked.',
|
|
321
341
|
});
|
|
322
342
|
|
|
343
|
+
// The private-file store being ignored, by what is wrong with it (the repair
|
|
344
|
+
// differs: chmod cannot fix an owner or a symlink, and a save fails through a
|
|
345
|
+
// foreign or symlinked folder).
|
|
346
|
+
const UNSAFE_STORE_HINTS = Object.freeze({
|
|
347
|
+
permissions:
|
|
348
|
+
'The private secrets folder has unsafe permissions, so its keys are ignored — run chmod 700 on the folder and chmod 600 on its files, or save the key again.',
|
|
349
|
+
owner:
|
|
350
|
+
'The private secrets folder or one of its files belongs to another user, so its keys are ignored — change its owner to the current user (chown).',
|
|
351
|
+
type: 'The private secrets folder or one of its files is a symlink or not a regular file, so its keys are ignored — remove it and save the key again.',
|
|
352
|
+
});
|
|
353
|
+
|
|
323
354
|
export function keychainIssueFor(checkSecrets) {
|
|
324
355
|
if (!checkSecrets) return null;
|
|
325
|
-
|
|
356
|
+
// A host with no keychain provider keeps its keys in the private secrets
|
|
357
|
+
// file (keychain-cjs.cjs "Linux fallback file"): that works, so nothing to
|
|
358
|
+
// report — unless the store's permissions make reads ignore it, which is
|
|
359
|
+
// reported with the folder to repair. A locked keyring is still an issue.
|
|
360
|
+
const { state, fallback, unsafeStore } = getKeychainStatus();
|
|
361
|
+
if (fallback && unsafeStore) {
|
|
362
|
+
const { kind, path } = unsafeStore;
|
|
363
|
+
return { state: 'unsafe-store', kind, message: UNSAFE_STORE_HINTS[kind] ?? UNSAFE_STORE_HINTS.permissions, path };
|
|
364
|
+
}
|
|
365
|
+
if (fallback) return null;
|
|
326
366
|
const message = KEYCHAIN_HINTS[state];
|
|
327
367
|
return message ? { state, message } : null;
|
|
328
368
|
}
|
|
329
369
|
|
|
330
|
-
|
|
370
|
+
// Where a stored key lives, for a row's detail line: decided per account, so a
|
|
371
|
+
// key that is still only in the fallback file reads as such after the keychain
|
|
372
|
+
// recovers.
|
|
373
|
+
function storedSecretDetail(account) {
|
|
374
|
+
return isFallbackSecret(account) ? 'stored in private file' : 'stored in keychain';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// `keychainIssue`: the setup response's one keyring check (never asked per row).
|
|
378
|
+
function apiProviderSetup(p, providers, checkSecrets, keychainIssue) {
|
|
331
379
|
const configured = providers[p.id] || {};
|
|
332
|
-
|
|
333
|
-
|
|
380
|
+
// The environment variable the key resolver actually reads (aliases included);
|
|
381
|
+
// it wins over a stored key, so provenance follows it, not the stored secret.
|
|
382
|
+
const resolvedEnv = agentApiKeyEnvName(p.id);
|
|
383
|
+
const declaredEnv = AGENT_PROVIDER_ENV[p.id] || p.env;
|
|
384
|
+
const envName = resolvedEnv || declaredEnv;
|
|
385
|
+
const env = Boolean(resolvedEnv || (declaredEnv && process.env[declaredEnv]));
|
|
334
386
|
const configuredEnabled = configured.enabled === true;
|
|
335
387
|
const stored = checkSecrets ? hasStoredSecret(SECRET_ACCOUNTS.agentApiKey(p.id)) : false;
|
|
336
388
|
const authenticated = env || stored || (checkSecrets ? Boolean(getAgentApiKey(p.id)) : configuredEnabled);
|
|
337
389
|
let status = configuredEnabled ? 'No Key' : 'Off';
|
|
338
|
-
if (
|
|
339
|
-
else if (
|
|
340
|
-
|
|
390
|
+
if (env) status = 'Env';
|
|
391
|
+
else if (stored || authenticated) status = 'Set';
|
|
392
|
+
// Where the effective credential comes from: environment | private-file | keychain.
|
|
393
|
+
let credentialSource = null;
|
|
341
394
|
let detail = envName;
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
else if (
|
|
345
|
-
|
|
346
|
-
|
|
395
|
+
if (env) {
|
|
396
|
+
credentialSource = 'environment';
|
|
397
|
+
} else if (stored) {
|
|
398
|
+
detail = storedSecretDetail(SECRET_ACCOUNTS.agentApiKey(p.id));
|
|
399
|
+
credentialSource = detail === 'stored in private file' ? 'private-file' : 'keychain';
|
|
400
|
+
} else if (authenticated) {
|
|
401
|
+
detail = 'runtime credential';
|
|
402
|
+
}
|
|
403
|
+
const rowIssue = authenticated ? null : keychainIssue;
|
|
404
|
+
if (rowIssue) detail = rowIssue.message;
|
|
347
405
|
return {
|
|
348
406
|
...p,
|
|
349
|
-
...(
|
|
407
|
+
...(rowIssue ? { keychainIssue: rowIssue } : {}),
|
|
350
408
|
group: 'api',
|
|
351
409
|
type: 'api-key',
|
|
352
410
|
enabled: configuredEnabled || authenticated,
|
|
@@ -356,6 +414,7 @@ function apiProviderSetup(p, providers, checkSecrets) {
|
|
|
356
414
|
envName,
|
|
357
415
|
status,
|
|
358
416
|
detail,
|
|
417
|
+
...(credentialSource ? { credentialSource } : {}),
|
|
359
418
|
};
|
|
360
419
|
}
|
|
361
420
|
|
|
@@ -469,7 +528,7 @@ function customProviderRow(id, entry, checkSecrets = true) {
|
|
|
469
528
|
const stored = checkSecrets ? hasStoredSecret(SECRET_ACCOUNTS.agentApiKey(id)) : false;
|
|
470
529
|
const unstoredStatus = configuredEnabled ? 'No Key' : 'Off';
|
|
471
530
|
let status = stored ? 'Set' : unstoredStatus;
|
|
472
|
-
let detail = stored ?
|
|
531
|
+
let detail = stored ? storedSecretDetail(SECRET_ACCOUNTS.agentApiKey(id)) : 'custom endpoint';
|
|
473
532
|
if (apiKeyEnv) {
|
|
474
533
|
status = envSet ? 'Env' : unstoredStatus;
|
|
475
534
|
detail = apiKeyEnv;
|
|
@@ -533,7 +592,7 @@ export async function providerSetup(config = {}, options = {}) {
|
|
|
533
592
|
return {
|
|
534
593
|
...(keychain ? { keychain } : {}),
|
|
535
594
|
api: [
|
|
536
|
-
...API_PROVIDERS.map((p) => apiProviderSetup(p, providers, checkSecrets)),
|
|
595
|
+
...API_PROVIDERS.map((p) => apiProviderSetup(p, providers, checkSecrets, keychain)),
|
|
537
596
|
...customProviderRows(providers, checkSecrets),
|
|
538
597
|
],
|
|
539
598
|
oauth: availableOAuthProviders().map((p) => oauthProviderSetup(p, providers, checkSecrets)),
|
|
@@ -885,7 +944,8 @@ export function listProviderAccounts(provider) {
|
|
|
885
944
|
authenticated: auth.authenticated === true,
|
|
886
945
|
reauthRequired: auth.reauthRequired === true,
|
|
887
946
|
usage: row.usage || null,
|
|
888
|
-
|
|
947
|
+
// When the limit shown lifts: the latest deadline of every block.
|
|
948
|
+
blockedUntil: providerAccountBlockedUntil(row) || null,
|
|
889
949
|
// The limit holding the account now: account-wide, or only for one
|
|
890
950
|
// model family ("7D Fable") while other models still run.
|
|
891
951
|
limit: providerAccountLimit(row, now, { useCredits: pool.useCredits === true }),
|
|
@@ -1113,6 +1173,44 @@ export async function saveCustomProvider(cfgMod, rawInput) {
|
|
|
1113
1173
|
return typed ? serializeCustomKeySave(id, commit) : commit();
|
|
1114
1174
|
}
|
|
1115
1175
|
|
|
1176
|
+
// Replaces only the stored key of an existing custom provider. The provider's
|
|
1177
|
+
// settings are never read back and rewritten, so a concurrent edit of them
|
|
1178
|
+
// (or a removal) is never reverted.
|
|
1179
|
+
export async function saveCustomProviderKey(cfgMod, rawId, rawKey) {
|
|
1180
|
+
requireLocalProviderManagement();
|
|
1181
|
+
const id = String(rawId || '').trim();
|
|
1182
|
+
if (typeof rawKey !== 'string' || rawKey.length > CUSTOM_API_KEY_MAX) throw new TypeError('custom provider apiKey is invalid');
|
|
1183
|
+
const key = rawKey.trim();
|
|
1184
|
+
if (!key) throw new Error('API key is required');
|
|
1185
|
+
const takesStoredKey = () => {
|
|
1186
|
+
const entry = existingCustomEntry(cfgMod.loadConfig(), id);
|
|
1187
|
+
const config = storedCustomConfig(entry);
|
|
1188
|
+
if ((config?.auth ?? entry.auth) === 'none') throw new Error('this custom provider needs no API key');
|
|
1189
|
+
if (config?.apiKeyEnv) throw new Error(`this custom provider reads its key from ${config.apiKeyEnv}`);
|
|
1190
|
+
return entry;
|
|
1191
|
+
};
|
|
1192
|
+
takesStoredKey();
|
|
1193
|
+
// The provider's removal runs in the same queue, so it never lands between
|
|
1194
|
+
// the checks and the write below; a removal queued earlier is seen by the
|
|
1195
|
+
// check, before anything is written.
|
|
1196
|
+
return serializeCustomKeySave(id, async () => {
|
|
1197
|
+
takesStoredKey();
|
|
1198
|
+
const account = SECRET_ACCOUNTS.agentApiKey(id);
|
|
1199
|
+
const previousKey = getAgentApiKey(id);
|
|
1200
|
+
await saveSecretAsync(account, key);
|
|
1201
|
+
try {
|
|
1202
|
+
return customProviderRow(id, takesStoredKey());
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
// Switched to keyless (or the provider is gone) meanwhile: the key written
|
|
1205
|
+
// is dropped, and the previous one is only put back for a provider that exists.
|
|
1206
|
+
const exists = isCustomProviderConfig(cfgMod.loadConfig().providers?.[id]);
|
|
1207
|
+
if (exists && previousKey) await saveSecretAsync(account, previousKey);
|
|
1208
|
+
else await deleteSecretAsync(account);
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1116
1214
|
const MODEL_PATCH_KEYS = new Set([
|
|
1117
1215
|
'manual',
|
|
1118
1216
|
'hidden',
|
|
@@ -1292,14 +1390,19 @@ export async function removeCustomProvider(cfgMod, rawId) {
|
|
|
1292
1390
|
requireLocalProviderManagement();
|
|
1293
1391
|
const id = String(rawId || '').trim();
|
|
1294
1392
|
existingCustomEntry(cfgMod.loadConfig(), id);
|
|
1295
|
-
//
|
|
1296
|
-
//
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1393
|
+
// In the provider's key queue: a key write in flight finishes first and
|
|
1394
|
+
// none starts between the secret's deletion and the config's.
|
|
1395
|
+
return serializeCustomKeySave(id, async () => {
|
|
1396
|
+
existingCustomEntry(cfgMod.loadConfig(), id);
|
|
1397
|
+
// Secret first: a real keychain failure (locked, permission) leaves the
|
|
1398
|
+
// provider registered so the removal can be retried. A missing secret
|
|
1399
|
+
// (keyless provider) is not an error.
|
|
1400
|
+
await deleteSecretAsync(SECRET_ACCOUNTS.agentApiKey(id));
|
|
1401
|
+
const current = cfgMod.loadConfig();
|
|
1402
|
+
const { [id]: _removed, ...providers } = current.providers || {};
|
|
1403
|
+
cfgMod.saveConfig(withoutProviderReferences({ ...current, providers }, id), { baseConfig: current });
|
|
1404
|
+
return { provider: id, type: 'custom', removed: true };
|
|
1405
|
+
});
|
|
1303
1406
|
}
|
|
1304
1407
|
|
|
1305
1408
|
export async function testCustomProvider(cfgMod, rawInput) {
|
|
@@ -21,6 +21,7 @@ import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../../runtime/agent/orchest
|
|
|
21
21
|
import { roomMemberOfSession } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
22
22
|
import { mixyDisplayName } from '../../runtime/agent/orchestrator/session/mixy-name.mjs';
|
|
23
23
|
import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
|
|
24
|
+
import { isHeartbeatSession } from '../../runtime/shared/routine-trigger.mjs';
|
|
24
25
|
import { unattendedRunOf } from '../held-requests.mjs';
|
|
25
26
|
|
|
26
27
|
const INTERACTIVE_SOURCE_TYPES = new Set(['', 'lead', 'cli']);
|
|
@@ -40,6 +41,8 @@ function provenanceScope(session, id) {
|
|
|
40
41
|
if (member) return { kind: 'room-member', agentId: member.agentId };
|
|
41
42
|
const homeAgent = agentIdOfHomeSession(id);
|
|
42
43
|
if (homeAgent) return homeAgent === MIXY_AGENT_ID ? { kind: 'human' } : { kind: 'agent', agentId: homeAgent };
|
|
44
|
+
// Mixy's hidden heartbeat session: her own scope, as her chat's routine turns get.
|
|
45
|
+
if (isHeartbeatSession(id)) return { kind: 'agent', agentId: MIXY_AGENT_ID };
|
|
43
46
|
const headless =
|
|
44
47
|
!id ||
|
|
45
48
|
isAgentOwner(session) ||
|
|
@@ -108,6 +111,17 @@ export function assertOwnAgentSettings(scope, agentId, action) {
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
/** Throws unless Mixy's heartbeat session `sessionId` may take setup `action`
|
|
115
|
+
* with `args`: on her own she only pauses a routine or runs one again; every
|
|
116
|
+
* other change she proposes (MIXY.md). Any other session passes. */
|
|
117
|
+
export function assertHeartbeatSetupAction(sessionId, action, args) {
|
|
118
|
+
if (!isHeartbeatSession(sessionId)) return;
|
|
119
|
+
if (action === 'run_automation' || (action === 'set_automation_enabled' && args?.enabled === false)) return;
|
|
120
|
+
throw new Error(
|
|
121
|
+
`A heartbeat turn only pauses a routine or runs one again (setup ${action} is refused); propose other changes with session action suggest`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
/** Throws when `scope` is a headless run: it reads settings, never changes
|
|
112
126
|
* them; an unattended run changes only its own bot's automations and route. */
|
|
113
127
|
export function assertInteractiveCaller(scope, action) {
|
|
@@ -9,7 +9,7 @@ import { readAgentFrontmatterPermission } from '../services/agent-tool/helpers.m
|
|
|
9
9
|
import { LEAD_DISALLOWED_TOOLS } from '../../runtime/agent/orchestrator/runtime-core/tool-defs.mjs';
|
|
10
10
|
import { HEADLESS_TOOL_PROFILE } from '../../runtime/agent/orchestrator/runtime-core/tool-profile.mjs';
|
|
11
11
|
import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
|
|
12
|
-
import {
|
|
12
|
+
import { agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
13
13
|
import { roomMemberOfSession } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
14
14
|
import { unattendedApprovalPort } from '../../runtime/agent/orchestrator/session/human-gate/gate.mjs';
|
|
15
15
|
|
|
@@ -70,7 +70,7 @@ export function sessionOptions(deps, coreMemoryContext) {
|
|
|
70
70
|
const workflowContext = packContext;
|
|
71
71
|
const leadWorkflowParts = agentOwned
|
|
72
72
|
? undefined
|
|
73
|
-
: leadPromptParts({ agentMode: sessionMode === AGENT_MODE,
|
|
73
|
+
: leadPromptParts({ agentMode: sessionMode === AGENT_MODE, agentId: actingAgent });
|
|
74
74
|
const callerDisallowedTools = [
|
|
75
75
|
...(agentOwned ? [] : LEAD_DISALLOWED_TOOLS),
|
|
76
76
|
...(!agentOwned && rt.disallowDelegation ? ['agent'] : []),
|
|
@@ -88,17 +88,20 @@ export function sessionOptions(deps, coreMemoryContext) {
|
|
|
88
88
|
lane: agentOwned ? 'agent' : 'cli',
|
|
89
89
|
// One-shot `mixdog exec` runs carry their own source so session catalogs
|
|
90
90
|
// can keep them out of the user's resumable list.
|
|
91
|
+
// A hidden run of an agent (Mixy's heartbeat) is an exec run too.
|
|
91
92
|
sourceType: agentOwned
|
|
92
93
|
? sessionProfile?.sourceType || 'agent'
|
|
93
|
-
: rt.toolProfile === HEADLESS_TOOL_PROFILE
|
|
94
|
+
: rt.toolProfile === HEADLESS_TOOL_PROFILE || sessionProfile?.sourceType === 'exec'
|
|
94
95
|
? 'exec'
|
|
95
96
|
: 'lead',
|
|
96
97
|
sourceName: agentOwned
|
|
97
98
|
? sessionProfile?.sourceName || sessionProfile?.agent || 'agent'
|
|
98
99
|
: taskOrigin
|
|
99
100
|
? `agent:${taskOrigin.agentId}${taskOriginSuffix(taskOrigin)}`
|
|
100
|
-
: 'main',
|
|
101
|
+
: clean(sessionProfile?.sourceName) || 'main',
|
|
101
102
|
...(taskOrigin ? { taskOrigin } : {}),
|
|
103
|
+
// The agent a run acts for (agent mode, its memory; agent-mode.mjs).
|
|
104
|
+
...(!agentOwned && clean(sessionProfile?.ownerAgentId) ? { ownerAgentId: clean(sessionProfile.ownerAgentId) } : {}),
|
|
102
105
|
...(rt.approvalMode ? { approvalMode: rt.approvalMode } : {}),
|
|
103
106
|
clientHostPid: sessionProfile?.clientHostPid || process.pid,
|
|
104
107
|
mcpScopeId: rt.mcpScopeId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Automation worker status, the system shell
|
|
1
|
+
// Automation worker status, the system shell, self-update, Mixy's heartbeat
|
|
2
|
+
// and bots' reactions settings.
|
|
2
3
|
export function createSystemSettings({
|
|
3
4
|
getConfig,
|
|
4
5
|
saveConfigAndAdopt,
|
|
@@ -49,6 +50,20 @@ export function createSystemSettings({
|
|
|
49
50
|
});
|
|
50
51
|
return this.getUpdateSettings();
|
|
51
52
|
},
|
|
53
|
+
getHeartbeatSettings() {
|
|
54
|
+
return { heartbeat: getConfig()?.heartbeat !== false };
|
|
55
|
+
},
|
|
56
|
+
setHeartbeatEnabled(enabled) {
|
|
57
|
+
saveConfigAndAdopt({ ...getConfig(), heartbeat: enabled === true });
|
|
58
|
+
return this.getHeartbeatSettings();
|
|
59
|
+
},
|
|
60
|
+
getReactionSettings() {
|
|
61
|
+
return { reactions: getConfig()?.reactions !== false };
|
|
62
|
+
},
|
|
63
|
+
setReactionsEnabled(enabled) {
|
|
64
|
+
saveConfigAndAdopt({ ...getConfig(), reactions: enabled === true });
|
|
65
|
+
return this.getReactionSettings();
|
|
66
|
+
},
|
|
52
67
|
async checkForUpdate(options = {}) {
|
|
53
68
|
await checkForUpdateInternal({ force: options?.force === true });
|
|
54
69
|
return this.getUpdateSettings();
|
|
@@ -12,6 +12,7 @@ import { SETUP_DESKTOP_DOMAINS, SETUP_HANDOFFS } from './settings-contract.mjs';
|
|
|
12
12
|
import { executeExtendedSetupAction, publicAutomation, publicHookRule, validateMcpInput } from './extended-actions.mjs';
|
|
13
13
|
import {
|
|
14
14
|
assertAgentEditorAccess,
|
|
15
|
+
assertHeartbeatSetupAction,
|
|
15
16
|
assertInteractiveCaller,
|
|
16
17
|
assertOwnAgentSettings,
|
|
17
18
|
scheduleCallerScope,
|
|
@@ -44,6 +45,12 @@ const ACTION_APPLIES_TO = {
|
|
|
44
45
|
"new sessions and an empty current one (the default mode); a conversation keeps its own mode until it is switched with the composer's mode selector",
|
|
45
46
|
set_recap_enabled: 'background Memory cycles; no restart required',
|
|
46
47
|
set_auto_update: 'subsequent automatic update checks',
|
|
48
|
+
set_ai_session_titles: 'titles generated from now on; existing titles are unchanged',
|
|
49
|
+
set_quota_auto_resume: 'subsequent usage-limit stops; turning it off cancels pending resumes',
|
|
50
|
+
set_model_preferences: 'the /model picker and model management lists',
|
|
51
|
+
set_heartbeat_enabled: "Mixy's next heartbeat check (every 30 minutes); no restart required",
|
|
52
|
+
set_reactions_enabled:
|
|
53
|
+
"bots' chats; while off a reaction is refused at once and new chats are not offered the tool; reactions already left stay visible",
|
|
47
54
|
set_worktree_settings: 'new tasks and worktrees created afterwards; existing worktrees are unchanged',
|
|
48
55
|
set_system_shell: 'subsequent shell calls',
|
|
49
56
|
save_automation: 'the automation worker; scheduled runs follow the saved timing',
|
|
@@ -54,7 +61,7 @@ const ACTION_APPLIES_TO = {
|
|
|
54
61
|
add_hook_rule: 'subsequent tool calls; no restart required',
|
|
55
62
|
set_hook_rule_enabled: 'subsequent tool calls; no restart required',
|
|
56
63
|
delete_hook_rule: 'subsequent tool calls; no restart required',
|
|
57
|
-
set_media_default: 'media generations that
|
|
64
|
+
set_media_default: 'media generations that name no lane/model; lane "" returns the kind to Auto',
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
const OPEN_TARGET_HINTS = Object.freeze({
|
|
@@ -65,11 +72,11 @@ const OPEN_TARGET_HINTS = Object.freeze({
|
|
|
65
72
|
workflow: 'Desktop: Workflows · TUI: /workflow',
|
|
66
73
|
agents: 'Desktop: Workflows → Agents · TUI: /agents',
|
|
67
74
|
outputstyle: 'Desktop: Settings → Output style · TUI: /OutputStyle',
|
|
68
|
-
theme: 'Desktop: Settings →
|
|
75
|
+
theme: 'Desktop: Settings → Theme · TUI: /theme',
|
|
69
76
|
profile: 'Desktop: Settings → General → Profile · TUI: /profile',
|
|
70
77
|
autoclear: 'Desktop: Settings → Context · TUI: /autoclear',
|
|
71
78
|
memory:
|
|
72
|
-
'Desktop: Extensions
|
|
79
|
+
'Desktop: Extensions tab → Built-in (Memory); core memories under Projects → project → Memories · TUI: /memory',
|
|
73
80
|
mcp: 'Desktop: Extensions → Connectors tab · TUI: /mcp',
|
|
74
81
|
skills: 'Desktop: Skills (activity rail) · TUI: /skills',
|
|
75
82
|
plugins:
|
|
@@ -79,7 +86,7 @@ const OPEN_TARGET_HINTS = Object.freeze({
|
|
|
79
86
|
doctor: 'Desktop: Settings → System → Doctor, or /doctor in the composer · TUI: /doctor',
|
|
80
87
|
context: 'Desktop: /context in the composer · TUI: /context',
|
|
81
88
|
developer: 'Desktop: Settings → Developer options · TUI: /setting → Developer',
|
|
82
|
-
voice: 'Desktop: Extensions
|
|
89
|
+
voice: 'Desktop: Extensions tab → Built-in (Voice) · TUI: /setting → Voice',
|
|
83
90
|
connection: 'Desktop only: Settings → Connection',
|
|
84
91
|
});
|
|
85
92
|
|
|
@@ -277,16 +284,20 @@ async function featureStatus(rt, getConfig) {
|
|
|
277
284
|
browser: { active: builtinFeatureActive(config, 'browser') },
|
|
278
285
|
computer: { active: builtinFeatureActive(config, 'computer') },
|
|
279
286
|
terminal: { active: builtinFeatureActive(config, 'terminal') },
|
|
287
|
+
heartbeat: { enabled: config?.heartbeat !== false },
|
|
288
|
+
reactions: { enabled: config?.reactions !== false },
|
|
280
289
|
voice: rt.getVoiceStatus ? voiceFeatureStatus(await rt.getVoiceStatus()) : null,
|
|
281
290
|
};
|
|
282
291
|
}
|
|
283
292
|
|
|
284
293
|
async function mediaDefaults(rt) {
|
|
285
|
-
const rows = await Promise.all(MEDIA_KINDS.map((kind) => rt.getMediaDefault(kind)));
|
|
294
|
+
const [pins, ...rows] = await Promise.all([rt.getMediaPins(), ...MEDIA_KINDS.map((kind) => rt.getMediaDefault(kind))]);
|
|
295
|
+
const fieldsOf = (row, fields) => (row ? Object.fromEntries(fields.map((field) => [field, row[field]])) : null);
|
|
286
296
|
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
297
|
+
// The default generator (Settings → Built-in → Media generation); null = Auto.
|
|
298
|
+
pinned: Object.fromEntries(MEDIA_KINDS.map((kind) => [kind, fieldsOf(pins[kind], ['lane', 'model', 'account'])])),
|
|
299
|
+
// The last generator used; applies only to a kind that is on Auto.
|
|
300
|
+
remembered: Object.fromEntries(MEDIA_KINDS.map((kind, i) => [kind, fieldsOf(rows[i], ['lane', 'model'])])),
|
|
290
301
|
};
|
|
291
302
|
}
|
|
292
303
|
|
|
@@ -324,7 +335,15 @@ const SETUP_STATUS_READERS = {
|
|
|
324
335
|
features: await featureStatus(rt, getConfig),
|
|
325
336
|
onboarding: rt.getOnboardingStatus?.() || null,
|
|
326
337
|
}),
|
|
327
|
-
model: (rt) => ({
|
|
338
|
+
model: (rt) => ({
|
|
339
|
+
route: {
|
|
340
|
+
...mainRoute(rt),
|
|
341
|
+
autoEffort: rt.autoEffort !== false,
|
|
342
|
+
// The Lead preset (workflow-lead) holds the Lead route's fallback chain, as the Workflows page reads it.
|
|
343
|
+
fallbacks: (rt.listPresets?.() || []).find((preset) => preset?.id === 'workflow-lead')?.fallbacks || [],
|
|
344
|
+
},
|
|
345
|
+
effortOptions: rt.effortOptions || [],
|
|
346
|
+
}),
|
|
328
347
|
agents: (rt) => ({
|
|
329
348
|
agents: rt.listAgents?.() || [],
|
|
330
349
|
orchestrationMode: rt.getOrchestrationMode(),
|
|
@@ -347,19 +366,23 @@ const SETUP_STATUS_READERS = {
|
|
|
347
366
|
};
|
|
348
367
|
},
|
|
349
368
|
autoclear: (rt) => rt.getAutoClear?.() || {},
|
|
350
|
-
compaction: (rt) => rt.getCompactionSettings?.() || {},
|
|
369
|
+
compaction: (rt) => ({ ...(rt.getCompactionSettings?.() || {}), ...(rt.getSessionTitleSettings?.() || {}) }),
|
|
351
370
|
memory: (rt) => ({ ...(rt.getToolModuleSettings?.()?.memory || {}), recap: rt.getRecapSettings?.() || null }),
|
|
352
371
|
'local-provider': (rt) => rt.getToolModuleSettings().localProvider,
|
|
353
372
|
features: (rt, { getConfig }) => featureStatus(rt, getConfig),
|
|
354
373
|
shell: (rt) => rt.getSystemShell?.() || {},
|
|
355
374
|
providers: async (rt, { detail }) => {
|
|
356
375
|
const rows = publicProviderRows(await rt.getProviderSetup?.({}));
|
|
376
|
+
const quotaResume = rt.getQuotaAutoResume ? { quotaAutoResume: rt.getQuotaAutoResume() } : {};
|
|
357
377
|
// Client mode: the desktop owns accounts, so the server roster is never read.
|
|
358
378
|
if (rows.managedByClient) {
|
|
359
|
-
if (detail) return rows;
|
|
360
|
-
return { managedByClient: true, unavailable: rows.unavailable, ...configuredProviderRows(rows) };
|
|
379
|
+
if (detail) return { ...rows, ...quotaResume };
|
|
380
|
+
return { managedByClient: true, unavailable: rows.unavailable, ...configuredProviderRows(rows), ...quotaResume };
|
|
361
381
|
}
|
|
362
|
-
if (!detail)
|
|
382
|
+
if (!detail) {
|
|
383
|
+
return { ...configuredProviderRows(rows, (id) => rt.getProviderAccounts(id).accounts.length), ...quotaResume };
|
|
384
|
+
}
|
|
385
|
+
Object.assign(rows, quotaResume);
|
|
363
386
|
rows.oauth = rows.oauth.map((row) =>
|
|
364
387
|
row.authenticated ? { ...row, accounts: publicAccounts(rt.getProviderAccounts(row.id)) } : row
|
|
365
388
|
);
|
|
@@ -597,6 +620,16 @@ const SETUP_ACTION_HANDLERS = {
|
|
|
597
620
|
rt.deleteLocalProviderModel(requireText(args.confirmationToken, 'confirmationToken')),
|
|
598
621
|
set_system_shell: (rt, args) => rt.setSystemShell({ command: clean(args.command) }),
|
|
599
622
|
set_auto_update: (rt, args) => rt.setAutoUpdate(requireBoolean(args.enabled)),
|
|
623
|
+
set_ai_session_titles: (rt, args) => rt.setAiSessionTitles(requireBoolean(args.enabled)),
|
|
624
|
+
set_quota_auto_resume: async (rt, args) => ({ quotaAutoResume: await rt.setQuotaAutoResume(requireBoolean(args.enabled)) }),
|
|
625
|
+
set_model_preferences: async (rt, args) => {
|
|
626
|
+
const change = { model: requireText(args.model, 'model') };
|
|
627
|
+
for (const key of ['hidden', 'favorite']) if (Object.hasOwn(args, key)) change[key] = requireBoolean(args[key], key);
|
|
628
|
+
if (Object.keys(change).length === 1) throw new Error('hidden or favorite is required');
|
|
629
|
+
return rt.setModelPreferences(requireText(args.provider, 'provider'), [change]);
|
|
630
|
+
},
|
|
631
|
+
set_heartbeat_enabled: (rt, args) => rt.setHeartbeatEnabled(requireBoolean(args.enabled)),
|
|
632
|
+
set_reactions_enabled: (rt, args) => rt.setReactionsEnabled(requireBoolean(args.enabled)),
|
|
600
633
|
set_worktree_settings: (rt, args) => rt.setWorktreeSettings(args.worktree),
|
|
601
634
|
forget_provider_auth: async (rt, args) => {
|
|
602
635
|
const name = requireText(args.name, 'name');
|
|
@@ -666,7 +699,7 @@ const SETUP_ACTION_HANDLERS = {
|
|
|
666
699
|
// as Extensions → 커넥터. Nothing is connected here.
|
|
667
700
|
connect_connector: async (rt, args) => {
|
|
668
701
|
const entry = connectorById(requireText(args.connector, 'connector'));
|
|
669
|
-
if (!entry
|
|
702
|
+
if (!entry) throw new Error(`connector must be one of ${CONNECTOR_IDS.join(', ')}`);
|
|
670
703
|
const server = (rt.mcpStatus?.()?.servers || []).find((row) => row.connector === entry.id) || null;
|
|
671
704
|
const connected = server?.connected === true;
|
|
672
705
|
return {
|
|
@@ -786,6 +819,8 @@ export function createSetupToolExecutor({
|
|
|
786
819
|
}
|
|
787
820
|
if (action === 'set_agent_route') assertOwnAgentSettings(scope, args.agent, action);
|
|
788
821
|
}
|
|
822
|
+
// Mixy's heartbeat session takes only its two routine actions, reads included.
|
|
823
|
+
assertHeartbeatSetupAction(sessionId, action, args);
|
|
789
824
|
const rt = action === 'status' || action === 'open' ? null : api();
|
|
790
825
|
if (!Object.hasOwn(SETUP_ACTION_HANDLERS, action)) {
|
|
791
826
|
return executeExtendedSetupAction(rt, args, requestDesktop, { sessionId, caller });
|
|
@@ -33,9 +33,16 @@ function compactModelRow(row) {
|
|
|
33
33
|
efforts: (row.effortOptions || []).map((option) => option.value),
|
|
34
34
|
...(row.fastCapable === true ? { fast: true } : {}),
|
|
35
35
|
...(row.supportsVision === true ? { vision: true } : {}),
|
|
36
|
+
...modelPreferenceFields(row),
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/** The model-management choices, only when set (savedHidden is an explicit /model visibility choice). */
|
|
41
|
+
const modelPreferenceFields = (row) => ({
|
|
42
|
+
...(typeof row.savedHidden === 'boolean' ? { hidden: row.savedHidden } : {}),
|
|
43
|
+
...(row.favorite === true ? { favorite: true } : {}),
|
|
44
|
+
});
|
|
45
|
+
|
|
39
46
|
const isPresent = (value) =>
|
|
40
47
|
value != null && value !== '' && value !== false && !(typeof value === 'object' && !Object.keys(value).length);
|
|
41
48
|
|
|
@@ -64,7 +71,7 @@ function modelDetail(row) {
|
|
|
64
71
|
defaultModelParameters: row.defaultModelParameters,
|
|
65
72
|
supportsMaxMode: row.supportsMaxMode,
|
|
66
73
|
};
|
|
67
|
-
return Object.fromEntries(Object.entries(detail).filter(([, value]) => isPresent(value)));
|
|
74
|
+
return { ...Object.fromEntries(Object.entries(detail).filter(([, value]) => isPresent(value))), ...modelPreferenceFields(row) };
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
function findModel(rows, id, provider) {
|
|
@@ -283,6 +290,7 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
|
|
|
283
290
|
if (kind === 'webhook' && ['time', 'at', 'timezone', 'days'].some((key) => own(input, key))) {
|
|
284
291
|
throw new Error('Schedule fields (timing) are not accepted for webhooks');
|
|
285
292
|
}
|
|
293
|
+
if (kind === 'webhook' && own(input, 'newName')) throw new Error('newName (rename) is schedule-only');
|
|
286
294
|
if (kind === 'schedule' && own(input, 'parser')) throw new Error('parser is a webhook-only field');
|
|
287
295
|
let previous = {};
|
|
288
296
|
if (input.overwrite) {
|
|
@@ -363,6 +371,12 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
|
|
|
363
371
|
.sections.flatMap((section) => section.options)
|
|
364
372
|
.find((row) => row.id === args.name);
|
|
365
373
|
if (!option) throw new Error(`Unknown developer option "${args.name}"; read status developer`);
|
|
374
|
+
// An agent must never remove its own approval cards: only the person turns this on.
|
|
375
|
+
if (args.enabled && option.id === 'allowAllActions') {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`"${option.label}" cannot be turned on by an agent; only the person can turn it on in Settings → Developer options.`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
366
380
|
if (args.enabled && option.warning && !args.riskAccepted) {
|
|
367
381
|
throw new Error(
|
|
368
382
|
`Enabling ${option.label} requires riskAccepted:true after the user explicitly accepts: ${option.warning}`
|
|
@@ -401,7 +415,12 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
|
|
|
401
415
|
},
|
|
402
416
|
async set_media_default(rt, args) {
|
|
403
417
|
if (!MEDIA_KINDS.includes(args.kind)) throw new Error(`kind must be one of ${MEDIA_KINDS.join(', ')}`);
|
|
404
|
-
|
|
418
|
+
// A blank lane returns the kind to Auto, as the Settings control does.
|
|
419
|
+
if (!String(args.lane).trim()) {
|
|
420
|
+
if (own(args, 'model') || own(args, 'accountId')) throw new Error('model and accountId need a lane');
|
|
421
|
+
return { kind: args.kind, media: await rt.setMediaPin({ kind: args.kind, lane: '' }) };
|
|
422
|
+
}
|
|
423
|
+
const lane =(await rt.listMediaLanes()).find((row) => row.id === args.lane);
|
|
405
424
|
if (!lane) throw new Error(`Unknown media lane "${args.lane}"; list lanes with the media tool`);
|
|
406
425
|
if (!lane.authenticated) throw new Error(`${lane.label} is not signed in; open providers first`);
|
|
407
426
|
const spec = lane[args.kind];
|
|
@@ -409,7 +428,14 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
|
|
|
409
428
|
if (own(args, 'model') && !spec.models.some((model) => model.id === args.model)) {
|
|
410
429
|
throw new Error(`model "${args.model}" is not available on ${lane.id}/${args.kind}`);
|
|
411
430
|
}
|
|
412
|
-
|
|
431
|
+
if (own(args, 'accountId')) {
|
|
432
|
+
if (lane.authType !== 'oauth') throw new Error(`${lane.id} signs in with an API key; it has no accounts`);
|
|
433
|
+
if (!rt.getProviderAccounts(lane.authProvider).accounts.some((row) => row.id === args.accountId)) {
|
|
434
|
+
throw new Error(`Unknown ${lane.authProvider} account "${args.accountId}"; list accounts with status providers`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const pin = await rt.setMediaPin({ kind: args.kind, lane: args.lane, model: args.model, account: args.accountId });
|
|
438
|
+
return { kind: args.kind, media: pin };
|
|
413
439
|
},
|
|
414
440
|
});
|
|
415
441
|
|