mixdog 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/package.json +2 -1
  2. package/scripts/lib/run-node-tests.mjs +13 -0
  3. package/scripts/session-efficiency-diag.mjs +283 -0
  4. package/src/defaults/skills/computer-use/SKILL.md +3 -3
  5. package/src/defaults/skills/local-provider/SKILL.md +2 -1
  6. package/src/defaults/skills/setup/SKILL.md +4 -4
  7. package/src/defaults/skills/setup/references/actions.md +1 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +2 -3
  9. package/src/headless-exec.mjs +1 -0
  10. package/src/rules/shared/70-delivery.md +2 -2
  11. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +10 -0
  12. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +1 -1
  13. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  14. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +8 -4
  15. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +10 -1
  16. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +18 -3
  18. package/src/runtime/agent/orchestrator/providers/client-version-store.mjs +50 -0
  19. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +27 -4
  20. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +9 -5
  21. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +18 -4
  22. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +2 -2
  23. package/src/runtime/agent/orchestrator/providers/lib/anthropic-models.mjs +7 -1
  24. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +25 -2
  25. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +7 -1
  26. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +4 -0
  27. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +8 -0
  28. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +17 -3
  29. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +7 -1
  30. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +16 -0
  31. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +0 -20
  32. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +12 -10
  33. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -1
  34. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +1 -1
  35. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +15 -3
  36. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +11 -2
  37. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +5 -1
  38. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +23 -9
  39. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +15 -6
  40. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +4 -4
  41. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +7 -0
  42. package/src/runtime/channels/lib/voice-transcription.mjs +30 -4
  43. package/src/runtime/computer-bridge/action-schema.mjs +3 -3
  44. package/src/runtime/computer-bridge/actions.mjs +4 -0
  45. package/src/runtime/computer-bridge/bridge-env.test-support.mjs +22 -0
  46. package/src/runtime/computer-bridge/client.mjs +10 -183
  47. package/src/runtime/computer-bridge/core-actions.mjs +2 -1
  48. package/src/runtime/computer-bridge/error-recovery.mjs +46 -34
  49. package/src/runtime/computer-bridge/result-canonical.mjs +181 -0
  50. package/src/runtime/local-provider/asset-installer.mjs +20 -10
  51. package/src/runtime/local-provider/catalog.mjs +4 -7
  52. package/src/runtime/local-provider/data/manifest.json +14 -1
  53. package/src/runtime/local-provider/hardware.mjs +36 -5
  54. package/src/runtime/local-provider/resumable-installations.mjs +2 -2
  55. package/src/runtime/local-provider/server.mjs +1 -1
  56. package/src/runtime/memory/index.mjs +2 -4
  57. package/src/runtime/memory/lib/core-memory-file.mjs +3 -8
  58. package/src/runtime/memory/lib/core-memory-store.mjs +14 -61
  59. package/src/runtime/memory/lib/cycle-scheduler/backlog-probe.mjs +5 -9
  60. package/src/runtime/memory/lib/cycle1/cycle1-plan.mjs +7 -0
  61. package/src/runtime/memory/lib/cycle1/cycle1-rows.mjs +43 -16
  62. package/src/runtime/memory/lib/cycle1/cycle1-window.mjs +11 -2
  63. package/src/runtime/memory/lib/embedding-provider.mjs +2 -5
  64. package/src/runtime/memory/lib/embedding-reindex.mjs +1 -5
  65. package/src/runtime/memory/lib/http-router/lifecycle-routes.mjs +0 -1
  66. package/src/runtime/memory/lib/memory-action-handlers/maintenance-actions.mjs +1 -1
  67. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +4 -22
  68. package/src/runtime/memory/lib/memory.mjs +79 -32
  69. package/src/runtime/memory/lib/query-maintenance-handlers.mjs +0 -2
  70. package/src/runtime/office/com/com-adapter.mjs +3 -0
  71. package/src/runtime/office/pdf/pdf-adapter.mjs +50 -14
  72. package/src/runtime/office/pdf/pdf-render.mjs +2 -6
  73. package/src/runtime/office/pdf/pdf-security.mjs +8 -2
  74. package/src/runtime/office/portable/font-provisioner.mjs +28 -0
  75. package/src/runtime/office/portable/portable-docx-edits.mjs +28 -1
  76. package/src/runtime/office/portable/portable-soffice.mjs +44 -7
  77. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -1
  78. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +1 -1
  79. package/src/runtime/shared/llm/default-context-window.mjs +42 -0
  80. package/src/runtime/shared/llm/model-catalog-projection.mjs +2 -0
  81. package/src/runtime/shared/llm/model-catalog.mjs +2 -0
  82. package/src/runtime/shared/plugin-paths.mjs +9 -2
  83. package/src/runtime/shared/worker-exec-argv.mjs +25 -0
  84. package/src/runtime/web-search/lib/formatter.mjs +43 -18
  85. package/src/session-runtime/automation-agents.mjs +88 -0
  86. package/src/session-runtime/automation-workflow.mjs +19 -6
  87. package/src/session-runtime/boot/apis.mjs +0 -1
  88. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +14 -12
  89. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +4 -25
  90. package/src/session-runtime/model-recency.mjs +10 -2
  91. package/src/session-runtime/model-route/set-route.mjs +7 -1
  92. package/src/session-runtime/resource-api.mjs +8 -2
  93. package/src/session-runtime/resource-plugins-api.mjs +14 -2
  94. package/src/session-runtime/route-state.mjs +6 -2
  95. package/src/session-runtime/schedule-session-run.mjs +7 -1
  96. package/src/session-runtime/services/agent-tool/render.mjs +0 -5
  97. package/src/session-runtime/settings-builtin-tools-api.mjs +0 -16
  98. package/src/session-runtime/setup-tool/executor.mjs +7 -13
  99. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -3
  100. package/src/session-runtime/tool-policy-refresh.mjs +0 -2
  101. package/src/session-runtime/webhook-session-run.mjs +10 -11
  102. package/src/session-runtime/workflow-agents-api/agent-route.mjs +5 -0
  103. package/src/standalone/daemon-agent-control.mjs +8 -1
  104. package/src/standalone/daemon.mjs +4 -0
  105. package/src/tui/dist/index.mjs +3 -2
  106. package/src/tui/session/agent-job-feed/notification-router.mjs +7 -0
  107. package/src/tui/session/live-share.mjs +16 -1
  108. package/src/tui/session/notification-plan.mjs +4 -0
  109. package/src/session-runtime/bridge-first-use-gate.mjs +0 -72
@@ -1,41 +1,22 @@
1
- // Browser Use and Computer Use: the environment kill switch, the once-per-
2
- // session first-use approval, then the bridge client.
1
+ // Browser Use and Computer Use: the environment kill switch, then the bridge
2
+ // client. Neither asks the user before a call.
3
3
  import { executeBrowserTool } from '../../runtime/browser-bridge/client.mjs';
4
4
  import { executeComputerTool } from '../../runtime/computer-bridge/client.mjs';
5
- import { createBridgeFirstUseGate } from '../bridge-first-use-gate.mjs';
6
5
  import { featureEnvOverride } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
7
6
  import { createCallerContextResolvers } from './caller-context.mjs';
8
7
 
9
8
  export function createBridgeToolHandlers({ rt }) {
10
- const bridgeFirstUseGate = createBridgeFirstUseGate({ getConfig: () => rt.config });
11
9
  const { sessionIdFor, signalFor } = createCallerContextResolvers(rt);
12
10
 
13
- // Browser Use and Computer Use ask the user once per session before their
14
- // first live call; the answer is the tool result when it is no.
15
- const firstUseDenial = async (name, args, callerCtx, callerCwd) => {
16
- const denial = await bridgeFirstUseGate({
17
- name,
18
- args,
19
- cwd: callerCwd,
20
- sessionId: sessionIdFor(callerCtx),
21
- toolCallId: callerCtx?.toolCallId || null,
22
- toolApprovalHook: callerCtx?.toolApprovalHook,
23
- invocationSource: callerCtx?.invocationSource,
24
- });
25
- return denial ? { content: [{ type: 'text', text: denial }], isError: true } : null;
26
- };
27
-
28
11
  const environmentDisabled = (callerCtx, feature) =>
29
12
  callerCtx?.invocationSource === 'model-tool' && featureEnvOverride(feature) === false;
30
13
 
31
14
  // `browser` and `browser_devtools` are one bridge; the tool name only
32
15
  // scopes which actions the validator admits.
33
- const browser = async (args, { name, callerCtx, callerCwd }) => {
16
+ const browser = async (args, { name, callerCtx }) => {
34
17
  if (environmentDisabled(callerCtx, 'MIXDOG_FEATURE_BROWSER')) {
35
18
  throw new Error('the browser tool is disabled in this environment');
36
19
  }
37
- const denied = await firstUseDenial(name, args, callerCtx, callerCwd);
38
- if (denied) return denied;
39
20
  return await executeBrowserTool(args, {
40
21
  tool: name,
41
22
  sessionId: sessionIdFor(callerCtx),
@@ -44,12 +25,10 @@ export function createBridgeToolHandlers({ rt }) {
44
25
  });
45
26
  };
46
27
 
47
- const computer = async (args, { name, callerCtx, callerCwd }) => {
28
+ const computer = async (args, { callerCtx, callerCwd }) => {
48
29
  if (environmentDisabled(callerCtx, 'MIXDOG_FEATURE_COMPUTER')) {
49
30
  throw new Error('the computer tool is disabled in this environment');
50
31
  }
51
- const denied = await firstUseDenial(name, args, callerCtx, callerCwd);
52
- if (denied) return denied;
53
32
  return await executeComputerTool(args, {
54
33
  sessionId: sessionIdFor(callerCtx),
55
34
  cwd: callerCwd,
@@ -2,6 +2,7 @@
2
2
  // construction. Pure except for the
3
3
  // injected route provider (for sort priority) and webSearchCapableFor predicate.
4
4
  import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
+ import { contextWindowRange } from '../runtime/shared/llm/default-context-window.mjs';
5
6
 
6
7
  function parsedProviderModelVersion(id) {
7
8
  const text = clean(id).toLowerCase();
@@ -106,14 +107,21 @@ export function isSelectableLlmModel(model) {
106
107
  }
107
108
 
108
109
  export function providerModelCacheRow(name, m, webSearchCapableFor) {
110
+ // The pickers read the default window and the slider's range straight off
111
+ // this row, so a default lowered by range has to be stated here.
112
+ const range = contextWindowRange({
113
+ provider: name,
114
+ contextWindow: m.contextWindow,
115
+ maxContextWindow: m.maxContextWindow,
116
+ });
109
117
  return {
110
118
  id: m.id,
111
119
  provider: name,
112
120
  display: m.display || m.name || m.id,
113
121
  created: typeof m.created === 'number' ? m.created : null,
114
122
  releaseDate: m.releaseDate || null,
115
- contextWindow: m.contextWindow,
116
- maxContextWindow: m.maxContextWindow || null,
123
+ contextWindow: range.defaultWindow || m.contextWindow,
124
+ maxContextWindow: range.maxWindow > range.defaultWindow ? range.maxWindow : m.maxContextWindow || null,
117
125
  outputTokens: m.outputTokens || null,
118
126
  family: m.family || null,
119
127
  tier: m.tier || null,
@@ -13,6 +13,7 @@ import {
13
13
  } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
14
14
  import { getModelMetadataSync } from '../../runtime/agent/orchestrator/providers/model-catalog.mjs';
15
15
  import { workflowPresetId } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
16
+ import { resolveRouteContextState } from '../route-state.mjs';
16
17
  import { writeStatuslineRoute } from '../statusline-route.mjs';
17
18
  import { sessionHasRouteHistory, shouldRecreateEmptySessionForRouteChange } from '../../runtime/agent/orchestrator/runtime-core/session-route-policy.mjs';
18
19
  import { rebuildDeferredToolSurfaceForProvider } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog.mjs';
@@ -52,7 +53,12 @@ async function resolveSelectedRoute(deps, next) {
52
53
  selectedRoute.modelParameters
53
54
  );
54
55
  return {
55
- selectedRoute: { ...selectedRoute, fast: fastCapable ? selectedRoute.fast === true : false },
56
+ selectedRoute: {
57
+ ...selectedRoute,
58
+ fast: fastCapable ? selectedRoute.fast === true : false,
59
+ // Lets the saved model settings tell a chosen window from the default.
60
+ contextDefaultPercent: resolveRouteContextState(selectedRoute, modelMeta).contextDefaultPercent,
61
+ },
56
62
  fastCapable,
57
63
  modelMeta,
58
64
  };
@@ -8,7 +8,7 @@ import { createExtensionSync } from './resource-extension-sync.mjs';
8
8
  import { createScopeDecorators } from './resource-scope-decorate.mjs';
9
9
  import { createMcpResourceApi } from './resource-mcp-api.mjs';
10
10
  import { createSkillsResourceApi } from './resource-skills-api.mjs';
11
- import { createPluginsResourceApi } from './resource-plugins-api.mjs';
11
+ import { createPluginsResourceApi, findRegisteredPlugin } from './resource-plugins-api.mjs';
12
12
  import { createRecallResourceApi } from './resource-recall-api.mjs';
13
13
 
14
14
  export function createResourceApi(deps) {
@@ -27,8 +27,14 @@ export function createResourceApi(deps) {
27
27
  if (!EXTENSION_SCOPE_KINDS.includes(scopeKind)) {
28
28
  throw new Error(`extension scope kind must be one of ${EXTENSION_SCOPE_KINDS.join(', ')}`);
29
29
  }
30
- const key = clean(name);
30
+ let key = clean(name);
31
31
  if (!key) throw new Error('extension name is required');
32
+ // Plugin scopes are keyed by registry id; a name must resolve to it.
33
+ if (scopeKind === 'plugins') {
34
+ const plugin = findRegisteredPlugin(pluginsStatus, key);
35
+ if (!plugin) throw new Error(`plugin not registered: ${key}`);
36
+ key = clean(plugin.id || plugin.name);
37
+ }
32
38
  let list = [projects];
33
39
  if (Array.isArray(projects)) list = projects;
34
40
  else if (projects == null) list = [];
@@ -10,6 +10,15 @@ import { pluginMcpServerName, pluginServerMatcher } from '../runtime/agent/orche
10
10
 
11
11
  const pluginKey = (plugin) => clean(plugin.id || plugin.name || plugin);
12
12
 
13
+ /** The registered status row a caller means by `plugin` (row, id, name, or
14
+ * title), or null. Callers such as the setup tool pass only a name, while
15
+ * MCP entry names and scope keys come from the registered record. */
16
+ export function findRegisteredPlugin(pluginsStatus, plugin) {
17
+ const key = pluginKey(plugin);
18
+ if (!key) return null;
19
+ return (pluginsStatus()?.plugins || []).find((row) => [row.id, row.name, row.title].map(clean).includes(key)) || null;
20
+ }
21
+
13
22
  export function createPluginsResourceApi({ deps, sync, decorate }) {
14
23
  const {
15
24
  getConfig,
@@ -52,8 +61,9 @@ export function createPluginsResourceApi({ deps, sync, decorate }) {
52
61
  return { plugin: updated, status: pluginsStatus() };
53
62
  },
54
63
  async setPluginEnabled(plugin = {}, enabled = true) {
64
+ const owner = findRegisteredPlugin(pluginsStatus, plugin) || plugin;
55
65
  const updated = registrySetPluginEnabled(pluginKey(plugin), enabled, { dataDir: pluginData() });
56
- await rewritePluginServers(plugin, (servers, owned) => {
66
+ await rewritePluginServers(owner, (servers, owned) => {
57
67
  const next = {};
58
68
  for (const [name, value] of Object.entries(servers)) {
59
69
  const config = value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : value;
@@ -69,8 +79,10 @@ export function createPluginsResourceApi({ deps, sync, decorate }) {
69
79
  return { plugin: updated, status: pluginsStatus() };
70
80
  },
71
81
  async removePlugin(plugin = {}) {
82
+ // Resolve before the registry forgets the plugin and its manifest name.
83
+ const owner = findRegisteredPlugin(pluginsStatus, plugin) || plugin;
72
84
  const removed = registryRemovePlugin(pluginKey(plugin), { dataDir: pluginData() });
73
- await rewritePluginServers(plugin, (servers, owned) => {
85
+ await rewritePluginServers(owner, (servers, owned) => {
74
86
  const next = { ...servers };
75
87
  for (const name of Object.keys(next)) {
76
88
  if (owned(name)) delete next[name];
@@ -5,6 +5,7 @@ import { clean, hasOwn } from '../runtime/agent/orchestrator/runtime-core/sessio
5
5
  import { coerceEffortFor } from '../runtime/agent/orchestrator/runtime-core/effort.mjs';
6
6
  import { fastCapableFor } from '../runtime/agent/orchestrator/runtime-core/model-capabilities.mjs';
7
7
  import { providerCachedModelMetadataSync } from '../runtime/agent/orchestrator/providers/provider-catalog-cache.mjs';
8
+ import { contextWindowRange } from '../runtime/shared/llm/default-context-window.mjs';
8
9
 
9
10
  export function resolveRouteEffortState(targetRoute = {}, modelMeta = null) {
10
11
  const requested = hasOwn(targetRoute, 'effort') ? targetRoute.effort : targetRoute.preset?.effort || null;
@@ -49,8 +50,11 @@ export function resolveRouteContextState(targetRoute = {}, modelMeta = null, win
49
50
  Number(modelMeta?.contextWindow) > 0 || Number(modelMeta?.maxContextWindow) > 0
50
51
  ? modelMeta
51
52
  : windowLookup?.(clean(targetRoute?.provider), clean(targetRoute?.model)) || modelMeta;
52
- const defaultWindow = Math.max(0, Number(windowMeta?.contextWindow) || 0);
53
- const maxWindow = Math.max(defaultWindow, Number(windowMeta?.maxContextWindow) || 0);
53
+ const { defaultWindow, maxWindow } = contextWindowRange({
54
+ provider: clean(targetRoute?.provider),
55
+ contextWindow: windowMeta?.contextWindow,
56
+ maxContextWindow: windowMeta?.maxContextWindow,
57
+ });
54
58
  if (!maxWindow) {
55
59
  return { contextPercent: undefined, contextDefaultPercent: undefined, selectedContextWindow: undefined };
56
60
  }
@@ -11,6 +11,7 @@ import { createSession } from '../runtime/agent/orchestrator/session/manager/ses
11
11
  import { askSession } from '../runtime/agent/orchestrator/session/manager/ask-session.mjs';
12
12
  import { modelRouteFields, parseScheduleModelRef } from '../runtime/shared/schedule-model-ref.mjs';
13
13
  import { automationWorkflowOpts } from './automation-workflow.mjs';
14
+ import { runAutomationTurns } from './automation-agents.mjs';
14
15
  import { automationPromptContent } from '../runtime/shared/automation-attachments.mjs';
15
16
 
16
17
  /** Resolve schedule.model into a {provider,model,effort?,fast?} route. */
@@ -58,6 +59,11 @@ export async function runScheduleSession(schedule, { config = null, prompt: prom
58
59
  // title/preview derive from it, so no "[Scheduled task: …]" header noise.
59
60
  // Stored attachments ride along as composer-style content parts.
60
61
  const content = automationPromptContent(prompt, schedule.attachments);
61
- const result = await askSession(session.id, content, null, null, cwd || undefined);
62
+ // Agents the run delegates to report back; each result is the next turn.
63
+ const result = await runAutomationTurns(
64
+ session.id,
65
+ () => askSession(session.id, content, null, null, cwd || undefined),
66
+ (completions) => askSession(session.id, completions, null, null, cwd || undefined)
67
+ );
62
68
  return { sessionId: session.id, result: String(result?.content || '') };
63
69
  }
@@ -17,13 +17,8 @@ export function abnormalEmptyFinishError(result, agent) {
17
17
  if (!reason) return null;
18
18
  const iterations = result?.iterations ?? 0;
19
19
  const toolCallsTotal = result?.toolCallsTotal ?? 0;
20
- const maxLoopIterations = result?.maxLoopIterations ?? 0;
21
20
  const stopReason = result?.stopReason ?? result?.stop_reason ?? null;
22
21
  switch (reason) {
23
- case 'iteration_cap':
24
- // Preserve failure reporting for stored results from older runtimes.
25
- // Current execution no longer applies a loop iteration ceiling.
26
- return `agent '${agent}' hit the loop iteration ceiling (${maxLoopIterations} iterations, ${toolCallsTotal} tool calls) without producing a final answer`;
27
22
  case 'truncated':
28
23
  return `agent '${agent}' response was truncated (stopReason=${stopReason}) before a final answer (${iterations} iterations, ${toolCallsTotal} tool calls)`;
29
24
  case 'empty':
@@ -1,12 +1,9 @@
1
1
  // Built-in tool modules: the enable/install toggles (web search, memory, git,
2
2
  // office, tidy, local provider), the recap switch and the Code Tidy card.
3
3
  import {
4
- BRIDGE_FIRST_USE_IDS,
5
4
  INSTALLABLE_BUILTIN_IDS,
6
5
  builtinFeatureActive,
7
- builtinFirstUseApproval,
8
6
  builtinInstalled,
9
- setBuiltinFirstUseApprovalInConfig,
10
7
  setBuiltinInstalledInConfig,
11
8
  } from '../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
12
9
  import { LOCAL_PROVIDER_ID } from '../runtime/local-provider/managed-runtime.mjs';
@@ -115,19 +112,6 @@ export function createBuiltinToolSettings(
115
112
  await refreshEmptySessionToolPolicy?.();
116
113
  return this.getToolModuleSettings();
117
114
  },
118
- /** Once-per-session approval before the first Browser Use or Computer Use
119
- * call. The setting is read at each call, so it applies at once. */
120
- async setBridgeFirstUseApproval(name, enabled) {
121
- if (!BRIDGE_FIRST_USE_IDS.includes(name)) {
122
- throw new TypeError('First-use approval applies to browser or computer.');
123
- }
124
- saveConfigAndAdopt(setBuiltinFirstUseApprovalInConfig({ ...getConfig() }, name, enabled !== false));
125
- return {
126
- name,
127
- firstUseApproval: builtinFirstUseApproval(getConfig(), name),
128
- appliesTo: 'the next first use in any session, including this one',
129
- };
130
- },
131
115
  async setBuiltinToolEnabled(name, enabled) {
132
116
  if (name !== 'git' && name !== 'office' && name !== 'tidy' && name !== 'localProvider') {
133
117
  throw new TypeError('Built-in tool must be git, office, tidy, or localProvider.');
@@ -3,7 +3,7 @@
3
3
  * reconnects, and the empty-session tool-policy refresh all apply exactly as
4
4
  * they do for a UI click. The facade is read lazily because runtime-core
5
5
  * registers the tool executor before it finishes assembling the API object. */
6
- import { builtinFeatureActive, builtinFirstUseApproval } from '../../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
6
+ import { builtinFeatureActive } from '../../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
7
7
  import { ORCHESTRATION_MODES } from '../../runtime/shared/orchestration.mjs';
8
8
  import { SETUP_DESKTOP_DOMAINS, SETUP_HANDOFFS } from './settings-contract.mjs';
9
9
  import { executeExtendedSetupAction, publicAutomation, validateMcpInput } from './extended-actions.mjs';
@@ -253,14 +253,8 @@ const SETUP_STATUS_READERS = {
253
253
  const config = getConfig();
254
254
  return {
255
255
  ...featureFlags(rt),
256
- browser: {
257
- active: builtinFeatureActive(config, 'browser'),
258
- firstUseApproval: builtinFirstUseApproval(config, 'browser'),
259
- },
260
- computer: {
261
- active: builtinFeatureActive(config, 'computer'),
262
- firstUseApproval: builtinFirstUseApproval(config, 'computer'),
263
- },
256
+ browser: { active: builtinFeatureActive(config, 'browser') },
257
+ computer: { active: builtinFeatureActive(config, 'computer') },
264
258
  };
265
259
  },
266
260
  shell: (rt) => rt.getSystemShell?.() || {},
@@ -398,10 +392,6 @@ const SETUP_ACTION_HANDLERS = {
398
392
  if (DESKTOP_HOSTED_FEATURES.includes(name)) return requestDesktop(args);
399
393
  return rt.setBuiltinToolEnabled(name, requireBoolean(args.enabled));
400
394
  },
401
- set_first_use_approval: (rt, args) => {
402
- const name = requireEnum(args.name, ['browser', 'computer'], 'name');
403
- return rt.setBridgeFirstUseApproval(name, requireBoolean(args.enabled));
404
- },
405
395
  install_builtin: async (rt, args, { requestDesktop }) => {
406
396
  const name = requireEnum(args.name, SETUP_INSTALLABLE_FEATURES, 'name');
407
397
  if (DESKTOP_HOSTED_FEATURES.includes(name)) return requestDesktop(args);
@@ -596,6 +586,10 @@ export function createSetupToolExecutor({ getApi, getConfig, notifySessionUi, ge
596
586
  const result = await execute(args, options);
597
587
  if (READ_ONLY_SETUP_ACTIONS.has(args.action)) return JSON.stringify(result ?? {}, null, 2);
598
588
  await flushSettings?.();
589
+ // Attached surfaces re-read what changed instead of waiting for their
590
+ // next open or cache expiry.
591
+ const sessionId = clean(getSessionId?.());
592
+ if (sessionId) notifySessionUi?.(sessionId, 'Settings changed', { kind: 'setup-changed', action: args.action });
599
593
  return setupMutationReceipt(args.action, result);
600
594
  },
601
595
  };
@@ -78,7 +78,6 @@ export const SETUP_ACTION_FIELDS = Object.freeze({
78
78
  set_recap_enabled: 'enabled',
79
79
  set_web_search_enabled: 'enabled',
80
80
  set_builtin_enabled: 'name enabled',
81
- set_first_use_approval: 'name enabled',
82
81
  install_builtin: 'name',
83
82
  install_local_model: 'modelId',
84
83
  start_local_installation: 'phase modelId?',
@@ -209,11 +208,11 @@ export const SETUP_TOOL_DEFS = Object.freeze([
209
208
  enabled: {
210
209
  type: 'boolean',
211
210
  description:
212
- 'Boolean setters (set_*_enabled, set_first_use_approval, set_compaction, set_auto_update, set_mcp_enabled, set_plugin_enabled).',
211
+ 'Boolean setters (set_*_enabled, set_compaction, set_auto_update, set_mcp_enabled, set_plugin_enabled).',
213
212
  },
214
213
  name: {
215
214
  type: 'string',
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.`,
215
+ description: `set_builtin_enabled: ${SETUP_BUILTIN_TOGGLE_FEATURES.join('|')}; install_builtin also accepts memory. Memory toggles use set_memory_enabled. revoke_linked_device: client id from status connection, not its display name; set_developer_option: option id from status developer; read_definition/delete_definition: the definition id; else MCP/plugin/provider id.`,
217
216
  },
218
217
  accountId: {
219
218
  type: 'string',
@@ -71,7 +71,6 @@ export function createToolPolicyRefresh({
71
71
  getDataDir,
72
72
  modelStandaloneTools,
73
73
  featureDisallowedTools,
74
- memoryToolsEnabled,
75
74
  loadCoreMemoryContext,
76
75
  activeWorkflowContext,
77
76
  invalidatePreSessionToolSurface,
@@ -94,7 +93,6 @@ export function createToolPolicyRefresh({
94
93
  }
95
94
 
96
95
  async function leadCoreMemoryContext() {
97
- if (!memoryToolsEnabled()) return '';
98
96
  try {
99
97
  return await loadCoreMemoryContext();
100
98
  } catch {
@@ -10,12 +10,16 @@ import { createSession } from '../runtime/agent/orchestrator/session/manager/ses
10
10
  import { askSession } from '../runtime/agent/orchestrator/session/manager/ask-session.mjs';
11
11
  import { modelRouteFields, parseScheduleModelRef } from '../runtime/shared/schedule-model-ref.mjs';
12
12
  import { automationWorkflowOpts } from './automation-workflow.mjs';
13
+ import { runAutomationTurns } from './automation-agents.mjs';
13
14
  import { automationPromptContent } from '../runtime/shared/automation-attachments.mjs';
14
15
 
15
16
  // Webhook payloads are authenticated transport data, not trusted user intent.
16
17
  // Keep their sessions on the deterministic read-only bundle even when a
17
- // preset or workflow would otherwise resolve to full tools.
18
- export const WEBHOOK_SESSION_TOOLS = Object.freeze(['tools:readonly']);
18
+ // preset or workflow would otherwise resolve to full tools. `tools:mcp` adds
19
+ // only the automation scope's internal tools — the agent tool, offered when the
20
+ // user's orchestration mode allows delegation (user decision: automations may
21
+ // delegate); the scope connects no MCP servers.
22
+ export const WEBHOOK_SESSION_TOOLS = Object.freeze(['tools:readonly', 'tools:mcp']);
19
23
 
20
24
  /** Endpoint model ref wins; the maintenance.webhook route is the fallback. */
21
25
  function webhookRoute(modelRef) {
@@ -66,14 +70,9 @@ export async function runWebhookSession({
66
70
  // askOpts.signal is the parent-abort link: it is cascaded onto the turn
67
71
  // controller, so aborting it stops the in-flight provider/tool work instead
68
72
  // of leaving a timed-out delivery's session running with live side effects.
69
- const result = await askSession(
70
- session.id,
71
- content,
72
- null,
73
- null,
74
- projectCwd || undefined,
75
- undefined,
76
- signal ? { signal } : {}
77
- );
73
+ const ask = (turnContent) =>
74
+ askSession(session.id, turnContent, null, null, projectCwd || undefined, undefined, signal ? { signal } : {});
75
+ // Agents the run delegates to report back; each result is the next turn.
76
+ const result = await runAutomationTurns(session.id, () => ask(content), ask);
78
77
  return { sessionId: session.id, result: String(result?.content || '') };
79
78
  }
@@ -73,6 +73,11 @@ export function createAgentRouteApi(deps) {
73
73
  const keepOff = requested.disabled === true;
74
74
  if (keepOff && !(clean(requested.provider) && clean(requested.model))) {
75
75
  saveDisabled(id, true);
76
+ // An explicitly empty provider still asks to drop the override, as it
77
+ // does for an agent that stays on: off and inheriting Main.
78
+ if (hasOwn(requested, 'provider') && !clean(requested.provider)) {
79
+ return { ...clearOverride(id), id, disabled: true };
80
+ }
76
81
  return { ...stored, id, disabled: true };
77
82
  }
78
83
  if (requested.disabled === false && Object.keys(requested).length === 1) {
@@ -8,6 +8,8 @@
8
8
  // this control exists — the host takes `execute` as a constructor option), and
9
9
  // `cwd`. Output: { canonicalAgentTool, execute }.
10
10
 
11
+ import { deliverAutomationCompletion } from '../session-runtime/automation-agents.mjs';
12
+
11
13
  export function createCanonicalAgentControl({ getSessionService, getSessionRuntimeHost, cwd }) {
12
14
  let canonicalAgentToolPromise = null;
13
15
 
@@ -32,7 +34,12 @@ export function createCanonicalAgentControl({ getSessionService, getSessionRunti
32
34
  isKeychainPrewarmReady: () => true,
33
35
  sessionSurface: sessionService.agentSurface,
34
36
  notifySessionCompletion(ownerSessionId, text, meta = {}) {
35
- return getSessionRuntimeHost()?.notifySessionCompletion?.(ownerSessionId, text, meta) === true;
37
+ // A schedule/webhook run owns no session runtime; its own
38
+ // agent-result waiter takes the completions it is owed.
39
+ return (
40
+ getSessionRuntimeHost()?.notifySessionCompletion?.(ownerSessionId, text, meta) === true ||
41
+ deliverAutomationCompletion(ownerSessionId, text, meta)
42
+ );
36
43
  },
37
44
  });
38
45
  })
@@ -71,6 +71,7 @@ import { createLagProfiler } from './daemon-lag-profiler.mjs';
71
71
  import { createChannelsRuntimeLoader } from './daemon-channels-loader.mjs';
72
72
  import { createDesktopRuntime } from './daemon-desktop-runtime.mjs';
73
73
  import { createCanonicalAgentControl } from './daemon-agent-control.mjs';
74
+ import { registerAutomationAgentTool } from '../session-runtime/automation-agents.mjs';
74
75
  import {
75
76
  compareRuntimeVersions,
76
77
  SESSION_CAPABILITY_FINGERPRINT,
@@ -666,6 +667,9 @@ async function main() {
666
667
  getSessionRuntimeHost: () => sessionRuntimeHost,
667
668
  cwd: CWD,
668
669
  });
670
+ // Schedules and webhooks run headless here; their sessions reach the same
671
+ // canonical agent control through the automation tool scope.
672
+ registerAutomationAgentTool(agentControl.execute);
669
673
  const requestedRuntimeMode = String(process.env.MIXDOG_SESSION_RUNTIME_MODE || '')
670
674
  .trim()
671
675
  .toLowerCase();
@@ -44194,7 +44194,6 @@ import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:pa
44194
44194
  import { homedir } from "node:os";
44195
44195
  import { dirname, join } from "node:path";
44196
44196
  import { fileURLToPath } from "node:url";
44197
- var DEFAULT_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
44198
44197
  var homeBase = null;
44199
44198
  var homePath = null;
44200
44199
  var dataBase = null;
@@ -47687,7 +47686,9 @@ var LITELLM_FLAG_FIELDS = [
47687
47686
  "supports_web_search",
47688
47687
  "supports_websearch",
47689
47688
  "supports_prompt_caching",
47690
- "supports_reasoning"
47689
+ "supports_reasoning",
47690
+ // Reader: supportsAnthropicFastMode (anthropic-betas.mjs).
47691
+ "supports_fast_mode"
47691
47692
  ];
47692
47693
  var LITELLM_STRING_FIELDS = ["mode", "litellm_provider", "reasoning_content_field"];
47693
47694
  function projectLitellmRow(row) {
@@ -15,8 +15,15 @@ export function createNotificationRouter({
15
15
  modelNotification,
16
16
  }) {
17
17
  let uiOpenSeq = 0;
18
+ let setupChangeSeq = 0;
18
19
  const uiActions = {
19
20
  'setup-ui': (delivery) => set({ setupUiRequest: { id: delivery.id, at: Date.now() } }),
21
+ 'setup-changed': (delivery) => {
22
+ // Monotonic like ui-open: consecutive changes by the same action must
23
+ // each refresh the attached surfaces.
24
+ setupChangeSeq += 1;
25
+ set({ setupChanged: { action: delivery.setupAction, seq: setupChangeSeq, at: Date.now() } });
26
+ },
20
27
  'ui-open': (delivery) => {
21
28
  // Monotonic seq: the same command requested twice must fire twice, and
22
29
  // the consuming App effect keys on the object identity + seq.
@@ -13,9 +13,17 @@
13
13
  * pipe is down. Ownership/promotion semantics are unchanged — the pipe is a
14
14
  * transport, never a second writer.
15
15
  */
16
+ import { createHash } from 'node:crypto';
17
+ import { join } from 'node:path';
16
18
  import { createOwnerLeg } from './live-share/owner.mjs';
17
19
  import { createViewerLeg } from './live-share/viewer.mjs';
18
20
 
21
+ // A unix socket path is a fixed sun_path buffer: 104 bytes on macOS and the
22
+ // BSDs, 108 on Linux, NUL included. A desktop session id alone is 64 hex, so
23
+ // the path beside its session file runs past the macOS limit and listen()
24
+ // failed there for every desktop session.
25
+ const UNIX_SOCKET_PATH_MAX_BYTES = 103;
26
+
19
27
  // Session entry is latency-sensitive: the owner pipe can be a few event-loop
20
28
  // turns behind the viewer resume. Retry locally instead of waiting for the
21
29
  // coarse 3s session runtime safety tick.
@@ -23,7 +31,14 @@ const LIVE_CONNECT_RETRY_MIN_MS = 10;
23
31
  const LIVE_CONNECT_RETRY_MAX_MS = 160;
24
32
 
25
33
  export function liveSharePipePath(sessionId, sessionFilePath) {
26
- return process.platform === 'win32' ? `\\\\.\\pipe\\mixdog-live-${sessionId}` : `${sessionFilePath}.live.sock`;
34
+ if (process.platform === 'win32') return `\\\\.\\pipe\\mixdog-live-${sessionId}`;
35
+ const beside = `${sessionFilePath}.live.sock`;
36
+ if (Buffer.byteLength(beside) <= UNIX_SOCKET_PATH_MAX_BYTES) return beside;
37
+ // Owner and viewer derive the same short name from the same session file.
38
+ // /tmp, not os.tmpdir(): TMPDIR is itself long on macOS and may differ
39
+ // between the desktop daemon and a terminal, which would split the pair.
40
+ const digest = createHash('sha256').update(beside).digest('hex').slice(0, 24);
41
+ return join('/tmp', `mixdog-live-${process.getuid()}-${digest}.sock`);
27
42
  }
28
43
 
29
44
  export function createLiveShare({
@@ -89,6 +89,10 @@ export function resolveTuiRuntimeNotificationDelivery(event, text) {
89
89
  if (meta.kind === 'setup-ui') {
90
90
  return meta.id ? { action: 'setup-ui', id: String(meta.id) } : { action: 'ignore' };
91
91
  }
92
+ if (meta.kind === 'setup-changed') {
93
+ const setupAction = String(meta.action || '').trim();
94
+ return setupAction ? { action: 'setup-changed', setupAction } : { action: 'ignore' };
95
+ }
92
96
  if (meta.kind === 'ui-open') {
93
97
  const command = String(meta.command || '')
94
98
  .trim()
@@ -1,72 +0,0 @@
1
- // One approval per session before the first live browser or desktop call.
2
- //
3
- // Claude Code asks per app and per session before it controls a screen; here
4
- // the ask is per capability (Browser Use, Computer Use), answered once, and
5
- // remembered for the session in memory only, so a restart asks again. Without
6
- // an approval UI (headless runs, agent-owned sessions) nobody can answer, so
7
- // the gate stands aside and the workflow's own policies govern.
8
- import { approvalGranted, approvalReason } from '../runtime/agent/orchestrator/session/loop/tool-helpers.mjs';
9
- import { builtinFirstUseApproval } from '../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
10
-
11
- const FAMILY_BY_TOOL = Object.freeze({
12
- browser: 'browser',
13
- browser_devtools: 'browser',
14
- computer: 'computer',
15
- });
16
- const FAMILY_LABEL = Object.freeze({
17
- browser: 'Browser Use',
18
- computer: 'Computer Use',
19
- });
20
-
21
- export function bridgeToolFamily(name) {
22
- return FAMILY_BY_TOOL[String(name || '')] || null;
23
- }
24
-
25
- function callSummary(args) {
26
- const action = typeof args?.action === 'string' ? args.action : '';
27
- const input = args?.input && typeof args.input === 'object' ? args.input : args;
28
- const target = input?.url || input?.app || input?.window_id || input?.tab || '';
29
- return (
30
- [action, target]
31
- .filter(Boolean)
32
- .map((part) => String(part).slice(0, 120))
33
- .join(' ') || 'call'
34
- );
35
- }
36
-
37
- /** `getConfig` reads the live profile; the returned `gate` resolves to null
38
- * when the call may proceed, or to the error text the model receives. */
39
- export function createBridgeFirstUseGate({ getConfig }) {
40
- const granted = new Set();
41
- return async function gate({ name, args, cwd, sessionId, toolCallId, toolApprovalHook, invocationSource }) {
42
- const family = bridgeToolFamily(name);
43
- if (!family || invocationSource !== 'model-tool') return null;
44
- if (typeof toolApprovalHook !== 'function') return null;
45
- if (!builtinFirstUseApproval(getConfig?.(), family)) return null;
46
- const key = `${String(sessionId || '')}:${family}`;
47
- if (granted.has(key)) return null;
48
- const label = FAMILY_LABEL[family];
49
- const reason = `first ${label} call in this session (${callSummary(args)}); allowing it covers the rest of the session`;
50
- let approval;
51
- try {
52
- approval = await toolApprovalHook({
53
- name,
54
- args,
55
- cwd,
56
- sessionId: sessionId || null,
57
- toolCallId: toolCallId || null,
58
- reason,
59
- });
60
- } catch (error) {
61
- return `Error: ${label} first-use approval failed: ${error?.message || String(error || 'approval failed')}`;
62
- }
63
- if (!approvalGranted(approval)) {
64
- return (
65
- `Error: ${label} was not approved for this session: ${approvalReason(approval, 'the user declined')}. ` +
66
- 'Do not retry it; reach the goal another way or ask the user.'
67
- );
68
- }
69
- granted.add(key);
70
- return null;
71
- };
72
- }