mixdog 1.3.2 → 1.3.4

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 (182) hide show
  1. package/package.json +8 -8
  2. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  3. package/src/agents/reviewer/AGENT.md +5 -7
  4. package/src/agents/reviewer/agent.json +1 -1
  5. package/src/defaults/shipped-history.json +4 -0
  6. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  7. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  8. package/src/rules/lead/LEAD.md +5 -8
  9. package/src/rules/shared/45-destructive.md +5 -1
  10. package/src/rules/shared/60-verification.md +6 -4
  11. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +4 -1
  12. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  13. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  14. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  15. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +17 -4
  16. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  17. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  18. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  19. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  20. package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
  21. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  22. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +40 -5
  25. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +32 -11
  26. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -3
  27. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  28. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +13 -0
  29. package/src/runtime/agent/orchestrator/session/agent-room.mjs +68 -12
  30. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  31. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +31 -8
  32. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  33. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +117 -19
  34. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +1 -1
  35. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +3 -0
  36. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +147 -137
  37. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +37 -74
  38. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +0 -2
  39. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +2 -2
  40. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +13 -7
  41. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  42. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  43. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  45. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  46. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -0
  47. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +12 -20
  48. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  49. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +11 -5
  50. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  51. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  52. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  53. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +24 -15
  54. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +8 -0
  55. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  57. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +17 -1
  58. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  59. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  60. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  61. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +7 -5
  63. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  64. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/session/routine-result.mjs +7 -4
  66. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  67. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +11 -2
  68. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  69. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  70. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  71. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +7 -2
  72. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +13 -0
  73. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +76 -0
  74. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  75. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  76. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  77. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  78. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +18 -7
  79. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +2 -0
  80. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  81. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  82. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -0
  83. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +8 -2
  84. package/src/runtime/attachments/legacy-office.mjs +110 -0
  85. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  86. package/src/runtime/attachments/store.mjs +60 -3
  87. package/src/runtime/effort-judge/judge-client.mjs +61 -14
  88. package/src/runtime/effort-judge/model-install.mjs +34 -2
  89. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  90. package/src/runtime/office/portable/portable-soffice.mjs +132 -30
  91. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  92. package/src/runtime/shared/agent-avatar.mjs +3 -2
  93. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  94. package/src/runtime/shared/agent-room-host.mjs +58 -0
  95. package/src/runtime/shared/bot-hops.mjs +16 -11
  96. package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
  97. package/src/runtime/shared/redact-directories.mjs +18 -0
  98. package/src/runtime/shared/remove-directory.mjs +28 -0
  99. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  100. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  101. package/src/runtime/shared/tool-execution-contract.mjs +4 -0
  102. package/src/runtime/shared/tool-work-units.mjs +12 -9
  103. package/src/runtime/shared/turn-snapshot.mjs +233 -122
  104. package/src/runtime/shared/user-data-guard.mjs +51 -19
  105. package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
  106. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  107. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  108. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  109. package/src/runtime/shared/worktrees/registry.mjs +3 -1
  110. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  111. package/src/runtime/tidy/process.mjs +41 -8
  112. package/src/runtime/tidy/resolve.mjs +10 -7
  113. package/src/session-runtime/agent-home-delivery.mjs +73 -91
  114. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  115. package/src/session-runtime/agent-room-delivery.mjs +2 -63
  116. package/src/session-runtime/agent-task-sessions.mjs +194 -64
  117. package/src/session-runtime/agent-tool-routing.mjs +49 -4
  118. package/src/session-runtime/boot/apis.mjs +3 -0
  119. package/src/session-runtime/boot/facade.mjs +5 -6
  120. package/src/session-runtime/boot/tools.mjs +23 -1
  121. package/src/session-runtime/held-requests.mjs +20 -25
  122. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
  123. package/src/session-runtime/model-recency.mjs +1 -0
  124. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  125. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  126. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  127. package/src/session-runtime/runtime-review-api.mjs +4 -1
  128. package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
  129. package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
  130. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +44 -20
  131. package/src/session-runtime/services/agent-tool/render.mjs +3 -1
  132. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
  133. package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
  134. package/src/session-runtime/services/provider-admin.mjs +12 -2
  135. package/src/session-runtime/services/worktree-access.mjs +27 -0
  136. package/src/session-runtime/session-hooks.mjs +9 -17
  137. package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
  138. package/src/session-runtime/setup-tool/executor.mjs +4 -0
  139. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
  140. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
  141. package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
  142. package/src/session-runtime/tool-policy-refresh.mjs +1 -0
  143. package/src/session-runtime/tool-surface.mjs +9 -5
  144. package/src/session-runtime/turn/session-ops.mjs +8 -0
  145. package/src/session-runtime/turn/turn-run.mjs +5 -1
  146. package/src/session-runtime/usage-stats-api.mjs +1 -1
  147. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
  148. package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
  149. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  150. package/src/session-runtime/workspace/transitions.mjs +279 -67
  151. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  152. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  153. package/src/standalone/daemon-session-control.mjs +140 -23
  154. package/src/standalone/daemon-telemetry.mjs +9 -0
  155. package/src/standalone/daemon.mjs +5 -0
  156. package/src/standalone/local-session-runtime.mjs +11 -2
  157. package/src/standalone/session-protocol.mjs +1 -0
  158. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  159. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  160. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  161. package/src/standalone/session-service.mjs +10 -0
  162. package/src/standalone/session-tool-output.mjs +1 -0
  163. package/src/standalone/staged-import.mjs +47 -0
  164. package/src/tui/App.jsx +2 -0
  165. package/src/tui/app/message-selector.mjs +15 -2
  166. package/src/tui/app/model-options.mjs +16 -2
  167. package/src/tui/app/model-picker.mjs +2 -1
  168. package/src/tui/app/route-pickers.mjs +3 -1
  169. package/src/tui/dist/index.mjs +403 -210
  170. package/src/tui/paste-attachments.mjs +0 -1
  171. package/src/tui/paste-text-policy.mjs +4 -5
  172. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  173. package/src/tui/session/notice-surface.mjs +3 -0
  174. package/src/tui/session/session-api/intake/abort.mjs +33 -17
  175. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  176. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  177. package/src/tui/session/tool-approval.mjs +11 -48
  178. package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
  179. package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
  180. package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
  181. package/src/session-runtime/room-member-gate.mjs +0 -61
  182. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
@@ -9,6 +9,7 @@ import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator
9
9
  import { agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
10
10
  import { turnHopOf } from '../../runtime/shared/bot-hops.mjs';
11
11
  import { scheduleCallerScope } from '../services/schedule-access.mjs';
12
+ import { worktreeCallerScope } from '../services/worktree-access.mjs';
12
13
  import { resolveHandoffAttachments, sessionRoots } from '../handoff-attachments.mjs';
13
14
 
14
15
  const USER_TITLE_ERROR = 'session: the user already titled this session; the title was not changed';
@@ -44,9 +45,6 @@ export function createWorkspaceToolHandlers({
44
45
  getLifecycleApi = () => null,
45
46
  executeSessionControl = null,
46
47
  }) {
47
- // The Lead's own worktree, on the user's request: the same authority the
48
- // branch chip menu drives, tolerating the calling turn but nothing else
49
- // running (workers, background shells).
50
48
  const assertLead = (action, callerCtx) => {
51
49
  const callerSessionId = callerCtx?.callerSessionId || rt.session?.id || null;
52
50
  if (!rt.session?.id || callerSessionId !== rt.session.id) {
@@ -55,24 +53,6 @@ export function createWorkspaceToolHandlers({
55
53
  return rt.session.id;
56
54
  };
57
55
 
58
- const worktreeAction = async (action, args, callerCtx) => {
59
- const workspace = getWorkspaceApi();
60
- if (!workspace) throw new Error(`session: ${action} is unavailable in this runtime`);
61
- assertLead(action, callerCtx);
62
- let result;
63
- if (action === 'merge-worktree') {
64
- result = await workspace.mergeWorkspace({ confirmed: true }, { ownTurn: true });
65
- } else if (action === 'remove-worktree') {
66
- result = await workspace.removeWorktree({ force: args?.force === true }, { ownTurn: true });
67
- } else if (action === 'list-worktrees') {
68
- result = await workspace.listWorktrees();
69
- } else {
70
- result = await workspace.pruneWorktrees();
71
- }
72
- return JSON.stringify({ action, cwd: rt.currentCwd, result }, null, 2);
73
- };
74
-
75
- const WORKTREE_ACTIONS = new Set(['merge-worktree', 'remove-worktree', 'list-worktrees', 'prune-worktrees']);
76
56
  const SIDEBAR_ACTIONS = new Set(['favorite', 'archive']);
77
57
 
78
58
  // Sidebar flags only (the desktop's favorite/archive metadata).
@@ -127,6 +107,8 @@ export function createWorkspaceToolHandlers({
127
107
  callerAgentId,
128
108
  callerCwd,
129
109
  ...(action === 'send' || action === 'create' ? { callerHop: turnHopOf(rt.session) } : {}),
110
+ // Whether a create may provision a worktree (worktree-access.mjs).
111
+ ...(action === 'create' ? { callerScope: worktreeCallerScope(rt.session) } : {}),
130
112
  // Which bots and sessions it may see (bot-status.mjs).
131
113
  ...(action === 'bots' || action === 'list' ? { callerScope: scheduleCallerScope(rt.session) } : {}),
132
114
  ...(attachmentParts.length ? { attachmentParts } : {}),
@@ -137,7 +119,6 @@ export function createWorkspaceToolHandlers({
137
119
  if (target && target !== assertLead(action, callerCtx)) {
138
120
  throw new Error(`session: action=${action} only applies to the current session`);
139
121
  }
140
- if (WORKTREE_ACTIONS.has(action)) return worktreeAction(action, args, callerCtx);
141
122
  if (SIDEBAR_ACTIONS.has(action)) return sidebarAction(action, args, callerCtx);
142
123
  let currentCwd = callerCwd;
143
124
  if (action === 'status' || action === 'list-projects' || action === 'set-project') assertLead(action, callerCtx);
@@ -265,6 +246,8 @@ export function createWorkspaceToolHandlers({
265
246
  clientHostPid: callerCtx?.clientHostPid || rt.session?.clientHostPid || process.pid,
266
247
  signal: callerCtx?.signal,
267
248
  notifyFn: notifyFnForSession(callerSessionId),
249
+ // The jobs it starts report back at this turn's bot-chain hop (bot-hops.mjs).
250
+ ...(turnHopOf(rt.session) > 0 ? { hop: turnHopOf(rt.session) } : {}),
268
251
  // The jobs this call starts are recorded under it (agent-task-starts.mjs).
269
252
  ...(callerCtx?.toolCallId ? { toolCallId: callerCtx.toolCallId } : {}),
270
253
  });
@@ -11,6 +11,7 @@ function customRowFields(name, m) {
11
11
  if (!isCustomProviderName(name)) return {};
12
12
  return {
13
13
  ...(typeof m.name === 'string' && m.name ? { name: m.name } : {}),
14
+ ...(typeof m.providerName === 'string' && m.providerName ? { providerName: m.providerName } : {}),
14
15
  ...(typeof m.hidden === 'boolean' ? { hidden: m.hidden } : {}),
15
16
  ...(typeof m.manual === 'boolean' ? { manual: m.manual } : {}),
16
17
  ...(m.customReasoning && typeof m.customReasoning === 'object' ? { customReasoning: m.customReasoning } : {}),
@@ -45,8 +45,15 @@ export function hydrateProviderModelRow(cfg, row) {
45
45
  const modelParameterOptions = addedOptions.length
46
46
  ? [...existingOptions.filter((option) => !addedOptions.some((added) => added.id === option?.id)), ...addedOptions]
47
47
  : row.modelParameterOptions;
48
+ const isCustom = row.provider?.startsWith?.('custom-');
49
+ const customName = isCustom ? String(cfg?.providers?.[row.provider]?.name || '').trim() : '';
50
+ // A custom provider's name always comes from the current configuration; a
51
+ // row's own (possibly renamed or removed) name is dropped.
52
+ const base = { ...row };
53
+ if (isCustom) delete base.providerName;
48
54
  return {
49
- ...row,
55
+ ...base,
56
+ ...(customName ? { providerName: customName } : {}),
50
57
  ...(alias ? { display: alias, displayAlias: alias } : {}),
51
58
  ...(modelParameterOptions ? { modelParameterOptions } : {}),
52
59
  effortOptions: effortItemsFor(row.provider, row, null),
@@ -5,10 +5,12 @@ import { getModelMetadataSync } from '../../runtime/agent/orchestrator/providers
5
5
  import { isCustomProviderName } from '../../runtime/agent/orchestrator/providers/custom-reasoning.mjs';
6
6
 
7
7
  const CUSTOM_DECLARED_FIELDS = [
8
+ 'supportsVision',
8
9
  'supportsReasoning',
9
10
  'reasoningOptions',
10
11
  'reasoningLevels',
11
12
  'reasoningContentField',
13
+ 'providerName',
12
14
  'hidden',
13
15
  'manual',
14
16
  'customReasoning',
@@ -22,7 +22,10 @@ function customProviderModels(provider, providerConfig, discovered = providerCac
22
22
  } catch {
23
23
  return [];
24
24
  }
25
- return effectiveCustomModelRows(config, discovered, { providerId: provider });
25
+ const providerName = clean(config.name);
26
+ return effectiveCustomModelRows(config, discovered, { providerId: provider }).map((row) =>
27
+ providerName ? { ...row, providerName } : row
28
+ );
26
29
  }
27
30
 
28
31
  export function createQuickProviderRows({
@@ -53,6 +56,8 @@ export function createQuickProviderRows({
53
56
  display: model.display || model.name || modelId,
54
57
  contextWindow: model.contextWindow || meta.contextWindow || null,
55
58
  outputTokens: model.outputTokens || meta.outputTokens || null,
59
+ // An explicit value (an override's false included) wins; the catalog fills the rest.
60
+ supportsVision: model.supportsVision ?? meta.supportsVision,
56
61
  supportsReasoning: model.supportsReasoning === true || meta.supportsReasoning === true,
57
62
  supportsFunctionCalling: model.supportsFunctionCalling === true || meta.supportsFunctionCalling === true,
58
63
  supportsPromptCaching: model.supportsPromptCaching === true || meta.supportsPromptCaching === true,
@@ -78,6 +83,7 @@ export function createQuickProviderRows({
78
83
  contextWindow: meta.contextWindow || null,
79
84
  outputTokens: meta.outputTokens || null,
80
85
  latest: routeLike.latest === true,
86
+ supportsVision: meta.supportsVision,
81
87
  supportsReasoning: !!routeLike.effort || meta.supportsReasoning === true,
82
88
  supportsFunctionCalling: meta.supportsFunctionCalling === true,
83
89
  supportsPromptCaching: meta.supportsPromptCaching === true,
@@ -2,6 +2,7 @@ import { registerCacheInvalidationListener } from '../runtime/agent/orchestrator
2
2
  import { turnReviewReply } from '../runtime/shared/turn-review-reply.mjs';
3
3
  import {
4
4
  clearSessionChangeHistory,
5
+ forgetTurnSnapshotSession,
5
6
  getSessionReviewDiff,
6
7
  getSessionReviewFilePatch,
7
8
  getTurnChanges,
@@ -19,8 +20,10 @@ import { registerSessionPurgeHook } from '../runtime/agent/orchestrator/session/
19
20
 
20
21
  // A deleted history stays deleted for as long as its session exists; the
21
22
  // session's own deletion takes the marker — and its stored first-seen file
22
- // contents — with it.
23
+ // contents — with it, and its in-memory turn, parked worker reviews and
24
+ // bound worker trackers.
23
25
  registerSessionPurgeHook((sessionId) => {
26
+ forgetTurnSnapshotSession(sessionId);
24
27
  void forgetSessionChangeHistoryMarker(sessionId);
25
28
  void forgetSessionBaselines(sessionId);
26
29
  });
@@ -5,7 +5,7 @@ import {
5
5
  listBackgroundTasks,
6
6
  taskIdFromArgs,
7
7
  } from '../../../runtime/shared/background-tasks.mjs';
8
- import { agentScope, clean, clearAgentStatuslineRoute } from './helpers.mjs';
8
+ import { agentScope, clean, clearAgentStatuslineRoute, sessionMatchesContext } from './helpers.mjs';
9
9
  import { isLeadPoolAgent } from './worker-rows.mjs';
10
10
  import { createCloseAll } from './close-flow/close-all.mjs';
11
11
  import { closeManagedSession } from '../../lifecycle/shared.mjs';
@@ -20,6 +20,16 @@ export function createCloseFlow({ mgr, registry, views }) {
20
20
  // Stale local metadata only: resolveTag found no session in this
21
21
  // terminal/scope, so there is no sessionId-safe worker row to delete. Never
22
22
  // turn it into a tag-wide persisted-row removal.
23
+ // `tags`/`tagAgents` are shared by every caller on the canonical agent, so a
24
+ // binding alone does not prove the caller owns it: only a live session or a
25
+ // persisted row inside the caller's scope does. Another session's tag must
26
+ // stay untouched.
27
+ function tagBoundByCaller(target, scopedContext) {
28
+ const live = registry.getLiveSession(tags.get(target));
29
+ if (live) return sessionMatchesContext(live, scopedContext);
30
+ return registry.readWorkerRows(scopedContext).some((row) => clean(row.tag) === target);
31
+ }
32
+
23
33
  function forgetStaleLocalTag(target, task) {
24
34
  registry.unbindTag(target);
25
35
  if (task?.taskId) cancelBackgroundTask(task.taskId, CLOSE_REASON);
@@ -55,7 +65,9 @@ export function createCloseFlow({ mgr, registry, views }) {
55
65
  }
56
66
  const sessionId = registry.resolveTag(target, scopedContext, { scanSessions: registry.wantsSessionScan(args) });
57
67
  if (!sessionId) {
58
- if (!target.startsWith('sess_') && tagAgents.has(target)) return forgetStaleLocalTag(target, task);
68
+ if (!target.startsWith('sess_') && tagAgents.has(target) && tagBoundByCaller(target, scopedContext)) {
69
+ return forgetStaleLocalTag(target, task);
70
+ }
59
71
  throw new Error(`agent close: target "${target}" not found`);
60
72
  }
61
73
  if (isLeadPoolAgent(registry.getLiveSession(sessionId)?.agent)) {
@@ -67,7 +67,12 @@ export function createAgentExecute({
67
67
  }
68
68
  // Targetless status is the same compact overview as list.
69
69
  return renderResult({
70
- workers: views.list({ scanSessions: registry.wantsSessionScan(args), context: scopedContext }),
70
+ // Idle workers stay open for follow-ups; the model must see them to close them.
71
+ workers: views.list({
72
+ scanSessions: registry.wantsSessionScan(args),
73
+ context: scopedContext,
74
+ includeIdle: true,
75
+ }),
71
76
  jobs: views.listJobs(scopedContext),
72
77
  });
73
78
  case 'read':
@@ -25,11 +25,24 @@ export function isActiveWorkerRow(row = {}) {
25
25
  return !isTerminalWorkerStatus(effective);
26
26
  }
27
27
 
28
- function agentRow({ tag, session, runtime, progress, now, indexRow = null }) {
28
+ // A finished worker that is still open: it takes follow-ups and can be closed.
29
+ function isOpenIdleRow(row = {}) {
30
+ const status = String(row.status ?? '').trim().toLowerCase();
31
+ const stage = String(row.stage ?? '').trim().toLowerCase();
32
+ if (CANCELLED_WORKER_STATUS.test(stage) || CANCELLED_WORKER_STATUS.test(status)) return false;
33
+ return (status || stage) === 'idle';
34
+ }
35
+
36
+ function rowState(session, runtime) {
29
37
  const status = session.closed === true ? 'closed' : session.status || 'idle';
30
38
  const stage =
31
39
  session.stage ||
32
40
  (status === 'idle' || status === 'error' || status === 'closed' ? status : runtime?.stage || status);
41
+ return { status, stage };
42
+ }
43
+
44
+ function agentRow({ tag, session, runtime, progress, now, indexRow = null }) {
45
+ const { status, stage } = rowState(session, runtime);
33
46
  // Additive: the current turn's start (worker index, stamped at dispatch) so
34
47
  // surfaces time a continued worker from its turn, not from createdAt.
35
48
  const turnStartedAt = indexRow?.turnStartedAt || session.turnStartedAt || null;
@@ -73,27 +86,38 @@ function agentRow({ tag, session, runtime, progress, now, indexRow = null }) {
73
86
  }
74
87
 
75
88
  export function createAgentList({ mgr, refreshTagsFromSessions, agentSessionEntries, progress }) {
76
- return function list({ scanSessions = false, context = {}, includeTerminal = false } = {}) {
89
+ return function list({ scanSessions = false, context = {}, includeTerminal = false, includeIdle = false } = {}) {
77
90
  refreshTagsFromSessions({ scanSessions, context });
78
91
  const now = Date.now();
79
- const rows = agentSessionEntries({ scanSessions, context }).map(({ tag, session, indexRow, loaded }) =>
80
- agentRow({
81
- tag,
82
- session,
83
- indexRow,
84
- runtime: mgr.getSessionRuntime?.(session.id),
85
- // A tag-bound entry is the getSession answer itself; an index row or
86
- // a scanned summary is only a projection and still reads the session.
87
- progress: progress.sessionProgressExtras(
88
- session.id,
89
- session.agent || null,
92
+ const rows = [];
93
+ for (const { tag, session, indexRow, loaded } of agentSessionEntries({ scanSessions, context })) {
94
+ const runtime = mgr.getSessionRuntime?.(session.id);
95
+ // Whether a row is listed depends on its status and stage alone, so a
96
+ // finished worker is dropped before its progress is read: that read
97
+ // loads the worker's stored session, and a Lead's status pulse (every
98
+ // 2 s) would otherwise read every worker it ever ran, whole transcripts
99
+ // on first sight, synchronously on the daemon loop.
100
+ const state = rowState(session, runtime);
101
+ if (!includeTerminal && !isActiveWorkerRow(state) && !(includeIdle && isOpenIdleRow(state))) continue;
102
+ rows.push(
103
+ agentRow({
104
+ tag,
105
+ session,
106
+ indexRow,
107
+ runtime,
108
+ // A tag-bound entry is the getSession answer itself; an index row or
109
+ // a scanned summary is only a projection and still reads the session.
110
+ progress: progress.sessionProgressExtras(
111
+ session.id,
112
+ session.agent || null,
113
+ now,
114
+ null,
115
+ loaded ? { loaded: session } : {}
116
+ ),
90
117
  now,
91
- null,
92
- loaded ? { loaded: session } : {}
93
- ),
94
- now,
95
- })
96
- );
97
- return includeTerminal ? rows : rows.filter((row) => isActiveWorkerRow(row));
118
+ })
119
+ );
120
+ }
121
+ return rows;
98
122
  };
99
123
  }
@@ -156,9 +156,11 @@ export function renderResult(value, { includeDiagnostics = false } = {}) {
156
156
  if (typeof value === 'string') return value;
157
157
  if (value && typeof value === 'object') {
158
158
  if (Array.isArray(value.workers) || Array.isArray(value.jobs)) return renderRoster(value);
159
+ // close-flow results carry `closed` plus task_id: render the close outcome.
160
+ // close-flow results carry `closed` plus task_id: render the close outcome.
161
+ if (value.closed !== undefined) return renderClosed(value);
159
162
  if (value.task_id) return renderTask(value, includeDiagnostics);
160
163
  if (value.queued) return renderQueued(value);
161
- if (value.closed !== undefined) return renderClosed(value);
162
164
  if (value.content !== undefined) return renderContent(value);
163
165
  }
164
166
  return JSON.stringify(value, null, 2);
@@ -9,6 +9,7 @@ import { resourceAdmission } from '../../../../runtime/shared/resource-admission
9
9
  import { clean, terminalPidForContext } from '../helpers.mjs';
10
10
  import { renderResult } from '../render.mjs';
11
11
  import { closeManagedSession } from '../../../lifecycle/shared.mjs';
12
+ import { hopOf } from '../../../../runtime/shared/bot-hops.mjs';
12
13
 
13
14
  /** The completion callback that hands a worker's result back to its caller session; null without a handoff. */
14
15
  function handoffNotifier(handoffSessionId, notifySessionCompletion) {
@@ -30,6 +31,8 @@ export function createJobStarter({ mgr, notifyStatusChange, notifySessionComplet
30
31
  ownerSessionId: ownerSessionId || null,
31
32
  clientHostPid: clientHostPid || null,
32
33
  notifyFn: handoffNotifier(handoffSessionId, notifySessionCompletion),
34
+ // The turn that started the job: its completion returns at that bot-chain hop (bot-hops.mjs).
35
+ ...(hopOf(notifyContext?.hop) ? { hop: notifyContext.hop } : {}),
33
36
  };
34
37
  const jobMeta = sanitizeTaskMeta({
35
38
  ...(meta || {}),
@@ -28,7 +28,7 @@ export const AGENT_TOOL = {
28
28
  agentHidden: false,
29
29
  },
30
30
  description:
31
- 'Run scoped agent work as background tasks. spawn/send return task_id immediately. Reuse one tag for one scope for its whole lifetime; give distinct tags only to independent scopes. Never mint a new tag because a session expired or was lost — same-tag spawn respawns it with the full brief. Wait for completion notifications; use status/read only for manual recovery.',
31
+ 'Run scoped agent work as background tasks. spawn/send return task_id immediately. Reuse one tag for one scope for its whole lifetime; give distinct tags only to independent scopes. Never mint a new tag because a session expired or was lost — same-tag spawn respawns it with the full brief. Wait for completion notifications; use status/read only for manual recovery. Close finished agents yourself.',
32
32
  inputSchema: {
33
33
  type: 'object',
34
34
  properties: {
@@ -442,6 +442,7 @@ function publicCustomModelRows(rows) {
442
442
  model.hidden = item.hidden === true;
443
443
  model.manual = item.manual === true;
444
444
  for (const key of [
445
+ 'supportsVision',
445
446
  'supportsReasoning',
446
447
  'reasoningOptions',
447
448
  'reasoningLevels',
@@ -588,7 +589,8 @@ async function modelProxyProviderSetup(options) {
588
589
  });
589
590
  const custom = Object.entries(providers)
590
591
  .filter(([id, entry]) => isCustomProviderId(id) && entry?.type === 'custom')
591
- .map(([id]) => ({ id, name: id }));
592
+ // Older desktops advertise no name: the id is the fallback.
593
+ .map(([id, entry]) => ({ id, name: (typeof entry.name === 'string' && entry.name.trim()) || id }));
592
594
  return {
593
595
  managedByClient: true,
594
596
  api: [...project(API_PROVIDERS, 'api', 'api-key'), ...project(custom, 'api', 'custom')],
@@ -1111,7 +1113,15 @@ export async function saveCustomProvider(cfgMod, rawInput) {
1111
1113
  return typed ? serializeCustomKeySave(id, commit) : commit();
1112
1114
  }
1113
1115
 
1114
- const MODEL_PATCH_KEYS = new Set(['manual', 'hidden', 'name', 'contextWindow', 'maxOutputTokens', 'reasoning']);
1116
+ const MODEL_PATCH_KEYS = new Set([
1117
+ 'manual',
1118
+ 'hidden',
1119
+ 'name',
1120
+ 'contextWindow',
1121
+ 'maxOutputTokens',
1122
+ 'supportsVision',
1123
+ 'reasoning',
1124
+ ]);
1115
1125
  const REASONING_PATCH_KEYS = new Set(['levels', 'wireFormat', 'wireValues', 'replay']);
1116
1126
 
1117
1127
  // Tri-state merge: undefined preserves, null deletes the property, a value replaces it.
@@ -0,0 +1,27 @@
1
+ // Who may change worktrees from a chat (the git tool's managed-worktree
2
+ // operations, the daemon's cross-session `worktree` control). A worktree
3
+ // mutation — entering, returning, merging, removing, pruning, `git worktree
4
+ // add/remove`, a snapshot — runs only in a conversation session: a Lead
5
+ // session of the desktop or TUI, or a bot's chat. Unattended runs (schedules,
6
+ // webhooks, routines, room seats) are refused by the run's trusted identity;
7
+ // delegated workers have no worktree host at all. Listing stays open.
8
+ //
9
+ // The scope is the session-level caller scope (schedule-access.mjs
10
+ // scheduleCallerScope: the session's provenance and its unattended-run mark),
11
+ // never anything the model or a message supplies.
12
+ import { scheduleCallerScope } from './schedule-access.mjs';
13
+
14
+ /** `{ kind, agentId?, unattended?, attended }` of a calling session record. */
15
+ export function worktreeCallerScope(session) {
16
+ const scope = scheduleCallerScope(session);
17
+ const attended = (scope.kind === 'human' || scope.kind === 'agent') && scope.unattended !== true;
18
+ return { ...scope, attended };
19
+ }
20
+
21
+ /** Throws unless `scope` (worktreeCallerScope) may change worktrees; `what` names the refused action. */
22
+ export function assertWorktreeMutationAllowed(scope, what) {
23
+ if (scope?.attended === true) return;
24
+ throw new Error(
25
+ `${what} changes worktrees only in a conversation session (a Lead session or a bot chat); schedule, webhook, routine and room runs may only list them`
26
+ );
27
+ }
@@ -5,9 +5,6 @@
5
5
  // facade's hookCommonPayload / currentCwd accessor + the session itself. All
6
6
  // properties are non-enumerable/configurable/writable so the loop/manager can
7
7
  // read them by name without changing session enumeration.
8
- import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
9
- import { roomMemberToolDecision } from './room-member-gate.mjs';
10
-
11
8
  export function attachSessionHooks(session, { hooks, hookCommonPayload, getCwd }) {
12
9
  const identity = (input) => ({
13
10
  session_id: input?.sessionId || input?.session_id || session?.id,
@@ -28,26 +25,21 @@ export function attachSessionHooks(session, { hooks, hookCommonPayload, getCwd }
28
25
  // rejection cannot escape the tool loop or the compaction flow.
29
26
  const dispatcher = (event, payload) => (input) => hooks.dispatch(event, hookCommonPayload(payload(input)));
30
27
 
31
- // PreToolUse bridge. A group-room member's mutating calls always ask the
32
- // user (room-member-gate.mjs), whatever the hooks decided short of a deny.
33
- define('beforeToolHook', async (input, options = {}) => {
34
- const name = input?.name || input?.tool_name;
35
- const args = input?.args || input?.tool_input;
36
- const cwd = input?.cwd || getCwd();
37
- const decision = await hooks.beforeTool(
28
+ // PreToolUse bridge. A group-room member follows the same rules as its
29
+ // agent's home thread: no room-only approval on top.
30
+ define('beforeToolHook', async (input, options = {}) =>
31
+ hooks.beforeTool(
38
32
  hookCommonPayload({
39
33
  ...input,
40
34
  session_id: input?.sessionId || input?.session_id || session?.id,
41
- tool_name: name,
42
- tool_input: args,
35
+ tool_name: input?.name || input?.tool_name,
36
+ tool_input: input?.args || input?.tool_input,
43
37
  tool_use_id: input?.toolCallId || input?.tool_use_id,
44
- cwd,
38
+ cwd: input?.cwd || getCwd(),
45
39
  }),
46
40
  options
47
- );
48
- if (!roomMemberOfSession(session?.id)) return decision;
49
- return roomMemberToolDecision({ name, args, cwd }, decision);
50
- });
41
+ )
42
+ );
51
43
  // PostToolUse: bridge runtime tool completions to the standard hook bus.
52
44
  define('afterToolHook', dispatcher('PostToolUse', toolPayload));
53
45
  // PostToolUseFailure: dispatched by loop.mjs only when a tool execution
@@ -19,6 +19,7 @@ import {
19
19
  effortJudgeAvailable,
20
20
  effortJudgeInfo,
21
21
  effortJudgeInstalling,
22
+ effortJudgeLastError,
22
23
  effortJudgeReady,
23
24
  shutdownEffortJudge,
24
25
  } from '../runtime/effort-judge/judge-client.mjs';
@@ -178,6 +179,7 @@ export function createBuiltinToolSettings(
178
179
  installed: effortJudgeAvailable(),
179
180
  installing: effortJudgeInstalling(),
180
181
  ready: effortJudgeReady(),
182
+ lastError: effortJudgeLastError(),
181
183
  info: effortJudgeInfo(),
182
184
  },
183
185
  };
@@ -38,6 +38,10 @@ const PROFILE_VALUE_IDS = Object.freeze({
38
38
  });
39
39
 
40
40
  const ACTION_APPLIES_TO = {
41
+ set_agent_route:
42
+ "the bot's chats from their next turn (a running turn finishes on the old model); an agent switched on or off reaches open conversations from their next turn as an agent list reminder",
43
+ set_orchestration_mode:
44
+ "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",
41
45
  set_recap_enabled: 'background Memory cycles; no restart required',
42
46
  set_auto_update: 'subsequent automatic update checks',
43
47
  set_worktree_settings: 'new tasks and worktrees created afterwards; existing worktrees are unchanged',
@@ -223,6 +223,9 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
223
223
  turnNotifications: boolean,
224
224
  usagePinned: boolean,
225
225
  usageRailShortLong: boolean,
226
+ usageAccountsExpanded: boolean,
227
+ usageRailSlotsThree: boolean,
228
+ usageRailSlotsAll: boolean,
226
229
  computerObserveOnly: boolean,
227
230
  browserAutoSignIn: boolean,
228
231
  notificationVolume: {