mixdog 1.3.2 → 1.3.3

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 (171) hide show
  1. package/package.json +7 -7
  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/redact-directories.mjs +18 -0
  97. package/src/runtime/shared/remove-directory.mjs +28 -0
  98. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  99. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  100. package/src/runtime/shared/tool-execution-contract.mjs +4 -0
  101. package/src/runtime/shared/tool-work-units.mjs +12 -9
  102. package/src/runtime/shared/turn-snapshot.mjs +233 -122
  103. package/src/runtime/shared/user-data-guard.mjs +51 -19
  104. package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
  105. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  106. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  107. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  108. package/src/runtime/shared/worktrees/registry.mjs +3 -1
  109. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  110. package/src/runtime/tidy/process.mjs +41 -8
  111. package/src/runtime/tidy/resolve.mjs +10 -7
  112. package/src/session-runtime/agent-home-delivery.mjs +73 -91
  113. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  114. package/src/session-runtime/agent-room-delivery.mjs +2 -63
  115. package/src/session-runtime/agent-task-sessions.mjs +194 -64
  116. package/src/session-runtime/boot/apis.mjs +3 -0
  117. package/src/session-runtime/boot/facade.mjs +5 -6
  118. package/src/session-runtime/boot/tools.mjs +21 -1
  119. package/src/session-runtime/held-requests.mjs +20 -25
  120. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
  121. package/src/session-runtime/model-recency.mjs +1 -0
  122. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  123. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  124. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  125. package/src/session-runtime/runtime-review-api.mjs +4 -1
  126. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  127. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
  128. package/src/session-runtime/services/provider-admin.mjs +12 -2
  129. package/src/session-runtime/services/worktree-access.mjs +27 -0
  130. package/src/session-runtime/session-hooks.mjs +9 -17
  131. package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
  132. package/src/session-runtime/setup-tool/executor.mjs +4 -0
  133. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
  134. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
  135. package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
  136. package/src/session-runtime/tool-policy-refresh.mjs +1 -0
  137. package/src/session-runtime/tool-surface.mjs +9 -5
  138. package/src/session-runtime/turn/session-ops.mjs +8 -0
  139. package/src/session-runtime/turn/turn-run.mjs +5 -1
  140. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
  141. package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
  142. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  143. package/src/session-runtime/workspace/transitions.mjs +279 -67
  144. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  145. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  146. package/src/standalone/daemon-session-control.mjs +140 -23
  147. package/src/standalone/daemon-telemetry.mjs +9 -0
  148. package/src/standalone/daemon.mjs +5 -0
  149. package/src/standalone/local-session-runtime.mjs +11 -2
  150. package/src/standalone/session-protocol.mjs +1 -0
  151. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  152. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  153. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  154. package/src/standalone/session-service.mjs +10 -0
  155. package/src/standalone/session-tool-output.mjs +1 -0
  156. package/src/standalone/staged-import.mjs +47 -0
  157. package/src/tui/App.jsx +2 -0
  158. package/src/tui/app/message-selector.mjs +15 -2
  159. package/src/tui/app/model-options.mjs +16 -2
  160. package/src/tui/app/model-picker.mjs +2 -1
  161. package/src/tui/app/route-pickers.mjs +3 -1
  162. package/src/tui/dist/index.mjs +92 -40
  163. package/src/tui/paste-attachments.mjs +0 -1
  164. package/src/tui/paste-text-policy.mjs +4 -5
  165. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  166. package/src/tui/session/notice-surface.mjs +3 -0
  167. package/src/tui/session/session-api/intake/abort.mjs +33 -17
  168. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  169. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  170. package/src/tui/session/tool-approval.mjs +11 -48
  171. package/src/session-runtime/room-member-gate.mjs +0 -61
@@ -23,6 +23,7 @@ import { createViewerRegistry } from './session-service/viewers.mjs';
23
23
  import { createSessionEntries } from './session-service/entries.mjs';
24
24
  import { createSessionCalls } from './session-service/session-calls.mjs';
25
25
  import { createQuotaResumeScheduler } from './session-service/quota-resume.mjs';
26
+ import { createAgentRouteFollow } from './session-service/agent-route-follow.mjs';
26
27
  import { registerDelegatedApprovalOpener } from '../runtime/shared/delegated-approval-host.mjs';
27
28
 
28
29
  const EXTERNAL_SESSION_ACTIONS = new Set([
@@ -209,6 +210,14 @@ export function createSessionService({
209
210
  // A sub-agent's approval shows in its top-level chat; one not loaded is
210
211
  // brought up so the copy (and its notifications) appear there.
211
212
  const unregisterApprovalOpener = registerDelegatedApprovalOpener((sessionId) => calls.materializeSession(sessionId));
213
+ // A bot's saved route reaches its hosted home and room-member chats from their next turn.
214
+ const agentRouteFollow = createAgentRouteFollow({
215
+ sessionsById,
216
+ sessionOwner: projection.sessionOwner,
217
+ advance: projection.advance,
218
+ publishStep: projection.publishStep,
219
+ log,
220
+ });
212
221
  const quotaUsableReader = typeof readQuotaUsable === 'function' ? readQuotaUsable : async () => false;
213
222
  const quotaResume = quotaResumeStore
214
223
  ? createQuotaResumeScheduler({
@@ -224,6 +233,7 @@ export function createSessionService({
224
233
  if (stopPromise) return stopPromise;
225
234
  closed = true;
226
235
  unregisterApprovalOpener();
236
+ agentRouteFollow.unsubscribe();
227
237
  quotaResume?.stop();
228
238
  stopPromise = Promise.resolve().then(async () => {
229
239
  try {
@@ -24,6 +24,7 @@ export function sessionListOutput(page, limit, { rich = true } = {}) {
24
24
  : {}),
25
25
  ...(row.status ? { status: row.status } : {}),
26
26
  ...(row.cwd ? { cwd: row.cwd } : {}),
27
+ ...(row.archived === true ? { archived: true } : {}),
27
28
  };
28
29
  if (!rich) return base;
29
30
  // The bot it belongs to: its chat, room seat or the task's creator.
@@ -0,0 +1,47 @@
1
+ // Staged dynamic import of a large module graph. Loading a graph (file reads,
2
+ // compilation, evaluation) runs synchronously inside one import() call, so the
3
+ // daemon's session-runtime warm-up held the event loop for ~0.5 s in one
4
+ // piece right after `daemon-ready`. Importing the graph's top-level stages one
5
+ // loop turn apart splits that block without changing what is loaded.
6
+ import { readFileSync } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ // A static `import … from './x'`, `export … from './x'` or bare `import './x'`
10
+ // at the start of a line, in source order.
11
+ const STATIC_RELATIVE_IMPORT =
12
+ /^(?:import|export)\b[^;]*?\bfrom\s*['"](\.{1,2}\/[^'"]+)['"]|^import\s*['"](\.{1,2}\/[^'"]+)['"]/gm;
13
+
14
+ /** The relative static imports of the module at `url`, resolved, in source order. */
15
+ export function staticRelativeImports(url) {
16
+ const source = readFileSync(fileURLToPath(url), 'utf8');
17
+ return [...source.matchAll(STATIC_RELATIVE_IMPORT)].map((match) => new URL(match[1] || match[2], url).href);
18
+ }
19
+
20
+ const nextTurn = () => new Promise((resolve) => setImmediate(resolve));
21
+
22
+ /**
23
+ * import(url), after importing each static relative dependency of `stagesOf`
24
+ * (in its source order, one loop turn apart). Module evaluation order is the
25
+ * one a single import gives — dependencies depth-first in source order — as
26
+ * long as no module of a stage's graph imports `stagesOf` or an importer of it
27
+ * back. Staging stops at the first stage that fails, so no later stage
28
+ * evaluates; the final import then reports the failure as a single import
29
+ * would.
30
+ */
31
+ export async function importInStages(url, stagesOf) {
32
+ let stages = [];
33
+ try {
34
+ stages = staticRelativeImports(stagesOf);
35
+ } catch {
36
+ // Unreadable source: one import, as before.
37
+ }
38
+ for (const stage of stages) {
39
+ try {
40
+ await import(stage);
41
+ } catch {
42
+ break;
43
+ }
44
+ await nextTurn();
45
+ }
46
+ return import(url);
47
+ }
package/src/tui/App.jsx CHANGED
@@ -516,6 +516,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
516
516
  syncPromptLayoutRows,
517
517
  showPromptHint,
518
518
  clearPromptHint,
519
+ installPastedImages,
520
+ installPastedTexts,
519
521
  });
520
522
 
521
523
  // PROMPT HANDLER cluster extracted to app/use-prompt-handlers.mjs.
@@ -45,6 +45,8 @@ export function useMessageSelector({
45
45
  syncPromptLayoutRows,
46
46
  showPromptHint,
47
47
  clearPromptHint,
48
+ installPastedImages,
49
+ installPastedTexts,
48
50
  }) {
49
51
  // Cheap enough to recompute only when the transcript array identity changes;
50
52
  // the prompt asks for this on every Escape, never per keystroke.
@@ -64,11 +66,22 @@ export function useMessageSelector({
64
66
  return false;
65
67
  }
66
68
  clearPromptHint();
69
+ // The prompt's pasted blocks return with it, so its tokens still expand.
70
+ if (restored.pastedImages) installPastedImages?.(restored.pastedImages, { merge: true });
71
+ if (restored.pastedTexts) installPastedTexts?.(restored.pastedTexts, { merge: true });
67
72
  syncPromptLayoutRows(text);
68
73
  setPromptDraftOverride({ id: Date.now(), value: text });
74
+ if (restored.notice) showPromptHint(restored.notice, 'error');
69
75
  return true;
70
76
  },
71
- [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]
77
+ [
78
+ clearPromptHint,
79
+ syncPromptLayoutRows,
80
+ setPromptDraftOverride,
81
+ showPromptHint,
82
+ installPastedImages,
83
+ installPastedTexts,
84
+ ]
72
85
  );
73
86
 
74
87
  const openMessageSelector = useCallback(() => {
@@ -89,7 +102,7 @@ export function useMessageSelector({
89
102
  own.close();
90
103
  let restored;
91
104
  try {
92
- restored = store.rewindToItem?.(value);
105
+ restored = store.rewindToItem?.(value, { restoreFiles: false });
93
106
  } catch (error) {
94
107
  showPromptHint(`rewind failed: ${error?.message || error}`, 'error');
95
108
  return;
@@ -221,6 +221,18 @@ export const loadProviderDisplayNames = async (store) => {
221
221
  return names;
222
222
  };
223
223
 
224
+ /** Provider names for the picker: custom rows' own `providerName` first, then
225
+ * setup names over them (setup wins). Works when setup is missing or failed. */
226
+ export const catalogProviderDisplayNames = (models, setupNames = null) => {
227
+ const names = {};
228
+ for (const model of Array.isArray(models) ? models : []) {
229
+ const provider = String(model?.provider || '').trim();
230
+ const name = String(model?.providerName || '').trim();
231
+ if (provider.startsWith('custom-') && name) names[provider] = name;
232
+ }
233
+ return { ...names, ...setupNames };
234
+ };
235
+
224
236
  /** `{ id, text }` for each provider whose model list failed, with the reason
225
237
  * (the setup row's `catalogError`) and what to do about it. */
226
238
  export const loadProviderCatalogFailures = async (store) => {
@@ -368,9 +380,11 @@ const routeModelDetails = (route) =>
368
380
  .filter(Boolean)
369
381
  .join(' · ');
370
382
 
371
- export const routeLabel = (route) => {
383
+ export const routeLabel = (route, providerNames = null) => {
372
384
  if (!route?.provider || !route?.model) return '(unset)';
373
- return [providerDisplayName(route.provider), routeModelDetails(route)].filter(Boolean).join(' · ');
385
+ return [resolveProviderDisplayName(route.provider, providerNames), routeModelDetails(route)]
386
+ .filter(Boolean)
387
+ .join(' · ');
374
388
  };
375
389
 
376
390
  export const routeModelLabel = (route) => (route?.model ? routeModelDetails(route) : '(unset)');
@@ -14,6 +14,7 @@ import {
14
14
  normalizeModelOptions,
15
15
  buildCatalogFailureItems,
16
16
  buildModelProviderItems,
17
+ catalogProviderDisplayNames,
17
18
  loadProviderCatalogFailures,
18
19
  loadProviderDisplayNames,
19
20
  } from './model-options.mjs';
@@ -83,7 +84,7 @@ export function createModelPicker({
83
84
  }
84
85
 
85
86
  const models = normalizeModelOptions(providerModels);
86
- const providerNames = await loadProviderDisplayNames(store);
87
+ const providerNames = catalogProviderDisplayNames(models, await loadProviderDisplayNames(store));
87
88
  const catalogFailures = await loadProviderCatalogFailures(store);
88
89
  const activeRoute = options.currentRoute || {
89
90
  provider: state.provider,
@@ -7,6 +7,7 @@
7
7
  * openers (openModelPicker) thread as lazy getter wrappers so they resolve the
8
8
  * live binding at call time.
9
9
  */
10
+ import { loadProviderDisplayNames } from './model-options.mjs';
10
11
  import { createAgentsPicker } from './route-pickers/agents-picker.mjs';
11
12
  import { createOutputStylePicker } from './route-pickers/output-style-picker.mjs';
12
13
  import { createWorkflowPicker } from './route-pickers/workflow-picker.mjs';
@@ -59,7 +60,8 @@ export function createRoutePickers({
59
60
  store.pushNotice('Web-search model save is already running.', 'warn');
60
61
  return;
61
62
  }
62
- store.pushNotice(`Web-search model set to ${routeLabel(result)}`, 'info');
63
+ const providerNames = await loadProviderDisplayNames(store);
64
+ store.pushNotice(`Web-search model set to ${routeLabel(result, providerNames)}`, 'info');
63
65
  return result;
64
66
  },
65
67
  onAfterSelect: null,
@@ -41529,6 +41529,14 @@ function gitStageUnit(a) {
41529
41529
  effect: true
41530
41530
  });
41531
41531
  }
41532
+ function gitWorktreeUnit(a) {
41533
+ return unitDescriptor("Git", {
41534
+ active: "Managing",
41535
+ done: "Managed",
41536
+ noun: "worktree",
41537
+ effect: String(a.worktree_action || "").toLowerCase() !== "list"
41538
+ });
41539
+ }
41532
41540
  function cwdUnit(a) {
41533
41541
  const action = String(a.action || a.type || "").toLowerCase();
41534
41542
  if (action === "set" || action === "set-project" || !action && a.path) {
@@ -41559,14 +41567,6 @@ function cwdUnit(a) {
41559
41567
  const [active, done] = words;
41560
41568
  return unitDescriptor("Setup", { active, done, noun: "session", effect: true });
41561
41569
  }
41562
- if (action.endsWith("-worktree") || action.endsWith("-worktrees")) {
41563
- return unitDescriptor("Setup", {
41564
- active: "Managing",
41565
- done: "Managed",
41566
- noun: "worktree",
41567
- effect: !action.startsWith("list")
41568
- });
41569
- }
41570
41570
  if (action === "status") {
41571
41571
  return unitDescriptor("Setup", { active: "Checking", done: "Checked", noun: "session" });
41572
41572
  }
@@ -41661,7 +41661,7 @@ var TOOL_UNITS = /* @__PURE__ */ new Map([
41661
41661
  ["job_wait", shellUnit],
41662
41662
  [
41663
41663
  "git",
41664
- (a) => a.action === "stage" ? gitStageUnit(a) : unitDescriptor("Git", { count: queryCount(a, "command", "commands") || 1, noun: "Git command" })
41664
+ (a) => a.action === "stage" ? gitStageUnit(a) : a.action === "worktree" ? gitWorktreeUnit(a) : unitDescriptor("Git", { count: queryCount(a, "command", "commands") || 1, noun: "Git command" })
41665
41665
  ],
41666
41666
  ["github", githubUnit],
41667
41667
  // Preserve the staging work unit when rendering historical transcripts.
@@ -47542,17 +47542,14 @@ async function resizeImageBuffer(buffer, ext, { maxTokens = DEFAULT_IMAGE_MAX_TO
47542
47542
  var TOOL_IMAGE_TARGET_BASE64_SIZE = 256 * 1024;
47543
47543
 
47544
47544
  // src/tui/paste-text-policy.mjs
47545
- var PASTE_TOKEN_MIN_LINES = 10;
47546
- var PASTE_TOKEN_MIN_CHARS = 200;
47545
+ var PASTE_TOKEN_MIN_CHARS = 1e3;
47547
47546
  function pastedTextLineCount(text) {
47548
47547
  const value = String(text ?? "").replace(/\r\n?/g, "\n").replace(/\n+$/, "");
47549
47548
  if (!value) return 0;
47550
47549
  return value.split("\n").length;
47551
47550
  }
47552
47551
  function shouldFoldPastedText(text) {
47553
- const value = String(text ?? "");
47554
- if (!value) return false;
47555
- return pastedTextLineCount(value) >= PASTE_TOKEN_MIN_LINES || value.length > PASTE_TOKEN_MIN_CHARS;
47552
+ return String(text ?? "").length > PASTE_TOKEN_MIN_CHARS;
47556
47553
  }
47557
47554
  function formatPastedTextRef(id, text) {
47558
47555
  return `[Pasted text #${id} +${pastedTextLineCount(text)} lines]`;
@@ -59570,6 +59567,15 @@ var loadProviderDisplayNames = async (store) => {
59570
59567
  }
59571
59568
  return names;
59572
59569
  };
59570
+ var catalogProviderDisplayNames = (models, setupNames = null) => {
59571
+ const names = {};
59572
+ for (const model of Array.isArray(models) ? models : []) {
59573
+ const provider = String(model?.provider || "").trim();
59574
+ const name = String(model?.providerName || "").trim();
59575
+ if (provider.startsWith("custom-") && name) names[provider] = name;
59576
+ }
59577
+ return { ...names, ...setupNames };
59578
+ };
59573
59579
  var loadProviderCatalogFailures = async (store) => {
59574
59580
  let setup = null;
59575
59581
  try {
@@ -59683,9 +59689,9 @@ var routeSpeedLabel = (route) => {
59683
59689
  return route.modelParameters?.serviceTier === "ultrafast" ? "Ultrafast" : "Fast";
59684
59690
  };
59685
59691
  var routeModelDetails = (route) => [routeModelDisplayName(route), route.effort ? effortDisplayLabel(route.effort) : "", routeSpeedLabel(route)].filter(Boolean).join(" \xB7 ");
59686
- var routeLabel = (route) => {
59692
+ var routeLabel = (route, providerNames = null) => {
59687
59693
  if (!route?.provider || !route?.model) return "(unset)";
59688
- return [providerDisplayName(route.provider), routeModelDetails(route)].filter(Boolean).join(" \xB7 ");
59694
+ return [resolveProviderDisplayName(route.provider, providerNames), routeModelDetails(route)].filter(Boolean).join(" \xB7 ");
59689
59695
  };
59690
59696
  var routeModelLabel = (route) => route?.model ? routeModelDetails(route) : "(unset)";
59691
59697
  var agentModelProfile = (route) => route?.model ? routeModelDetails(route) : "";
@@ -61162,7 +61168,7 @@ function createModelPicker({
61162
61168
  return;
61163
61169
  }
61164
61170
  const models = normalizeModelOptions(providerModels);
61165
- const providerNames = await loadProviderDisplayNames(store);
61171
+ const providerNames = catalogProviderDisplayNames(models, await loadProviderDisplayNames(store));
61166
61172
  const catalogFailures = await loadProviderCatalogFailures(store);
61167
61173
  const activeRoute = options.currentRoute || {
61168
61174
  provider: state.provider,
@@ -61590,7 +61596,8 @@ function createRoutePickers({
61590
61596
  store.pushNotice("Web-search model save is already running.", "warn");
61591
61597
  return;
61592
61598
  }
61593
- store.pushNotice(`Web-search model set to ${routeLabel2(result)}`, "info");
61599
+ const providerNames = await loadProviderDisplayNames(store);
61600
+ store.pushNotice(`Web-search model set to ${routeLabel2(result, providerNames)}`, "info");
61594
61601
  return result;
61595
61602
  },
61596
61603
  onAfterSelect: null
@@ -61950,8 +61957,16 @@ function copyTree(src, dst, copied) {
61950
61957
  writeFileAtomicSync(dst, readFileSync10(src), { mode: st.mode & 511 });
61951
61958
  copied.push(dst);
61952
61959
  }
61960
+ var manifestPath = (scopeDir, name) => join11(scopeDir, name, BACKUP_MANIFEST);
61953
61961
  function pruneBackups(scopeDir, names, own) {
61954
- for (const name of backupsToPrune(names, own)) {
61962
+ const completed = names.filter((name) => {
61963
+ try {
61964
+ return statSync4(manifestPath(scopeDir, name)).isFile();
61965
+ } catch {
61966
+ return false;
61967
+ }
61968
+ });
61969
+ for (const name of backupsToPrune(completed, own)) {
61955
61970
  try {
61956
61971
  rmSync(join11(scopeDir, name), { recursive: true, force: true });
61957
61972
  } catch {
@@ -62001,16 +62016,24 @@ function backupUserData(dataDir2, reason = "snapshot", { dirs = [] } = {}) {
62001
62016
  const scopeDir = scopeDirFor(dataDir2);
62002
62017
  const { backupDir, names } = reserveBackupDir(scopeDir, reason);
62003
62018
  const copied = [];
62004
- for (const rel of USER_DATA_FILES) {
62005
- const src = join11(dataDir2, rel);
62006
- if (existsSync9(src)) copyTree(src, join11(backupDir, rel), copied);
62007
- }
62008
- for (const rel of [...USER_DATA_DIRS, ...dirs]) {
62009
- const src = join11(dataDir2, rel);
62010
- if (existsSync9(src)) copyTree(src, join11(backupDir, rel), copied);
62019
+ try {
62020
+ for (const rel of USER_DATA_FILES) {
62021
+ const src = join11(dataDir2, rel);
62022
+ if (existsSync9(src)) copyTree(src, join11(backupDir, rel), copied);
62023
+ }
62024
+ for (const rel of [...USER_DATA_DIRS, ...dirs]) {
62025
+ const src = join11(dataDir2, rel);
62026
+ if (existsSync9(src)) copyTree(src, join11(backupDir, rel), copied);
62027
+ }
62028
+ if (copied.length > 0) finishBackup(dataDir2, backupDir, reason);
62029
+ } catch (error) {
62030
+ try {
62031
+ rmSync(backupDir, { recursive: true, force: true });
62032
+ } catch {
62033
+ }
62034
+ throw error;
62011
62035
  }
62012
62036
  if (copied.length > 0) {
62013
- finishBackup(dataDir2, backupDir, reason);
62014
62037
  pruneBackups(scopeDir, names, basename5(backupDir));
62015
62038
  if (process.env.MIXDOG_SETUP_QUIET !== "1") {
62016
62039
  process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}
@@ -62034,7 +62057,14 @@ async function copyTreeAsync(src, dst, copied) {
62034
62057
  copied.push(dst);
62035
62058
  }
62036
62059
  async function pruneBackupsAsync(scopeDir, names, own) {
62037
- for (const name of backupsToPrune(names, own)) {
62060
+ const completed = [];
62061
+ for (const name of names) {
62062
+ try {
62063
+ if ((await statP(manifestPath(scopeDir, name))).isFile()) completed.push(name);
62064
+ } catch {
62065
+ }
62066
+ }
62067
+ for (const name of backupsToPrune(completed, own)) {
62038
62068
  try {
62039
62069
  await rmP(join11(scopeDir, name), { recursive: true, force: true });
62040
62070
  } catch {
@@ -62047,16 +62077,24 @@ async function backupUserDataAsync(dataDir2, reason = "snapshot") {
62047
62077
  const scopeDir = scopeDirFor(dataDir2);
62048
62078
  const { backupDir, names } = await reserveBackupDirAsync(scopeDir, reason);
62049
62079
  const copied = [];
62050
- for (const rel of USER_DATA_FILES) {
62051
- const src = join11(dataDir2, rel);
62052
- if (existsSync9(src)) await copyTreeAsync(src, join11(backupDir, rel), copied);
62053
- }
62054
- for (const rel of USER_DATA_DIRS) {
62055
- const src = join11(dataDir2, rel);
62056
- if (existsSync9(src)) await copyTreeAsync(src, join11(backupDir, rel), copied);
62080
+ try {
62081
+ for (const rel of USER_DATA_FILES) {
62082
+ const src = join11(dataDir2, rel);
62083
+ if (existsSync9(src)) await copyTreeAsync(src, join11(backupDir, rel), copied);
62084
+ }
62085
+ for (const rel of USER_DATA_DIRS) {
62086
+ const src = join11(dataDir2, rel);
62087
+ if (existsSync9(src)) await copyTreeAsync(src, join11(backupDir, rel), copied);
62088
+ }
62089
+ if (copied.length > 0) finishBackup(dataDir2, backupDir, reason);
62090
+ } catch (error) {
62091
+ try {
62092
+ await rmP(backupDir, { recursive: true, force: true });
62093
+ } catch {
62094
+ }
62095
+ throw error;
62057
62096
  }
62058
62097
  if (copied.length > 0) {
62059
- finishBackup(dataDir2, backupDir, reason);
62060
62098
  await pruneBackupsAsync(scopeDir, names, basename5(backupDir));
62061
62099
  if (process.env.MIXDOG_SETUP_QUIET !== "1") {
62062
62100
  process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}
@@ -63969,7 +64007,9 @@ function useMessageSelector({
63969
64007
  setPromptDraftOverride,
63970
64008
  syncPromptLayoutRows,
63971
64009
  showPromptHint,
63972
- clearPromptHint
64010
+ clearPromptHint,
64011
+ installPastedImages,
64012
+ installPastedTexts
63973
64013
  }) {
63974
64014
  const hasUserMessages = useMemo7(
63975
64015
  () => (Array.isArray(state.items) ? state.items : []).some(
@@ -63985,11 +64025,21 @@ function useMessageSelector({
63985
64025
  return false;
63986
64026
  }
63987
64027
  clearPromptHint();
64028
+ if (restored.pastedImages) installPastedImages?.(restored.pastedImages, { merge: true });
64029
+ if (restored.pastedTexts) installPastedTexts?.(restored.pastedTexts, { merge: true });
63988
64030
  syncPromptLayoutRows(text);
63989
64031
  setPromptDraftOverride({ id: Date.now(), value: text });
64032
+ if (restored.notice) showPromptHint(restored.notice, "error");
63990
64033
  return true;
63991
64034
  },
63992
- [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]
64035
+ [
64036
+ clearPromptHint,
64037
+ syncPromptLayoutRows,
64038
+ setPromptDraftOverride,
64039
+ showPromptHint,
64040
+ installPastedImages,
64041
+ installPastedTexts
64042
+ ]
63993
64043
  );
63994
64044
  const openMessageSelector = useCallback20(() => {
63995
64045
  const items = store.getState?.().items ?? state.items;
@@ -64009,7 +64059,7 @@ function useMessageSelector({
64009
64059
  own.close();
64010
64060
  let restored;
64011
64061
  try {
64012
- restored = store.rewindToItem?.(value);
64062
+ restored = store.rewindToItem?.(value, { restoreFiles: false });
64013
64063
  } catch (error) {
64014
64064
  showPromptHint(`rewind failed: ${error?.message || error}`, "error");
64015
64065
  return;
@@ -65920,7 +65970,9 @@ function App2({ store, initialStatusLine = "", forceOnboarding = false, onboardi
65920
65970
  setPromptDraftOverride,
65921
65971
  syncPromptLayoutRows,
65922
65972
  showPromptHint,
65923
- clearPromptHint
65973
+ clearPromptHint,
65974
+ installPastedImages,
65975
+ installPastedTexts
65924
65976
  });
65925
65977
  const { handlePromptPaste, handlePromptHistoryNavigate, handlePromptEscape, handlePromptInterrupt } = usePromptHandlers({
65926
65978
  store,
@@ -86,7 +86,6 @@ export {
86
86
  formatPastedTextRef,
87
87
  pastedTextLineCount,
88
88
  PASTE_TOKEN_MIN_CHARS,
89
- PASTE_TOKEN_MIN_LINES,
90
89
  shouldFoldPastedText,
91
90
  } from './paste-text-policy.mjs';
92
91
 
@@ -3,8 +3,9 @@
3
3
  // A big paste is replaced in the prompt by a compact token so the editor does
4
4
  // not choke on thousands of raw characters; the original text lives in the
5
5
  // surface's pasted-text map and is expanded back on submit.
6
- export const PASTE_TOKEN_MIN_LINES = 10;
7
- export const PASTE_TOKEN_MIN_CHARS = 200;
6
+ // Same rule as Claude Code / Codex (LARGE_PASTE_CHAR_THRESHOLD): only a paste
7
+ // longer than this many characters folds; the line count never matters.
8
+ export const PASTE_TOKEN_MIN_CHARS = 1000;
8
9
 
9
10
  // VISIBLE lines: a copy that ends in a newline ("a\nb\n") is two lines, not
10
11
  // three. Counting the empty trailing chunk folded ordinary two-line copies
@@ -18,9 +19,7 @@ export function pastedTextLineCount(text) {
18
19
  }
19
20
 
20
21
  export function shouldFoldPastedText(text) {
21
- const value = String(text ?? '');
22
- if (!value) return false;
23
- return pastedTextLineCount(value) >= PASTE_TOKEN_MIN_LINES || value.length > PASTE_TOKEN_MIN_CHARS;
22
+ return String(text ?? '').length > PASTE_TOKEN_MIN_CHARS;
24
23
  }
25
24
 
26
25
  export function formatPastedTextRef(id, text) {
@@ -86,10 +86,13 @@ export function createExecutionDelivery({
86
86
  // active loop can attach them after the next tool batch. The immediate
87
87
  // response card was already pushed above, so the queued twin stays
88
88
  // model-visible but suppresses its drain-time transcript card.
89
- const { execution } = markCompletionEntry(resumeBody, { executionId, meta: event?.meta });
89
+ const { execution, hop } = markCompletionEntry(resumeBody, { executionId, meta: event?.meta });
90
90
  const enqueued = enqueue(resumeBody, {
91
91
  mode: 'task-notification',
92
92
  ...(execution ? { execution } : {}),
93
+ // The launching turn's bot-chain hop: the turn this wakes, or the running
94
+ // turn this joins, resumes at it (bot-hops.mjs).
95
+ ...(hop ? { transcriptMeta: { hop } } : {}),
93
96
  priority: 'next',
94
97
  key: notificationKey || undefined,
95
98
  abortDiscardOnAbort: true,
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * src/tui/session/notice-surface.mjs - toasts, transcript notices, progress hint.
3
3
  */
4
+ import { roomRowSender } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
4
5
  import { polishNoticeText } from './notice-text.mjs';
5
6
  import { nextId } from './transcript-spill.mjs';
6
7
 
@@ -59,6 +60,8 @@ export function createNoticeSurface({ getState, set, isDisposed, pushItem, repla
59
60
  // A schedule result posted into a home thread (routine-result.mjs).
60
61
  ...(options.routine ? { routine: options.routine } : {}),
61
62
  ...(options.room ? { room: options.room } : {}),
63
+ // A room message another session sent shows its sender, as restored.
64
+ ...(userRow ? roomRowSender(options.room) : {}),
62
65
  // A room user row's files, in the fields a prompt row draws them from;
63
66
  // a task report's or member speech's returned files likewise.
64
67
  ...(Array.isArray(options.attachments) && options.attachments.length ? { attachments: options.attachments } : {}),
@@ -8,7 +8,7 @@ import { promptHistoryWithout } from '../../prompt-history.mjs';
8
8
  import { isQueuedEntryEditable } from '../../queue-helpers.mjs';
9
9
 
10
10
  // An attachment whose blob is gone is dropped alone; the prompt is kept.
11
- function hydrateTolerantly(pastedImages, pastedTexts, content = null) {
11
+ export function hydrateTolerantly(pastedImages, pastedTexts, content = null) {
12
12
  let unreadable = 0;
13
13
  const onUnreadable = () => {
14
14
  unreadable += 1;
@@ -57,9 +57,13 @@ export function createAbortAction(bag) {
57
57
 
58
58
  // Pull the interrupted prompt back out of the transcript and history and
59
59
  // put its unsent requeue entries at the front of the queue.
60
- const reclaimInFlightPrompt = (restoreState, { restoreText, requeueEntries, handoff = false }) => {
61
- restoreState.reclaimed = true;
62
- const idSet = new Set((restoreState.submittedIds || []).filter((id) => id != null));
60
+ // `keepPrompt` only requeues: the submitted rows stay in the transcript.
61
+ const reclaimInFlightPrompt = (
62
+ restoreState,
63
+ { restoreText, requeueEntries, handoff = false, keepPrompt = false }
64
+ ) => {
65
+ if (!keepPrompt) restoreState.reclaimed = true;
66
+ const idSet = new Set((keepPrompt ? [] : restoreState.submittedIds || []).filter((id) => id != null));
63
67
  const patch = { spinner: null, thinking: null, lastTurn: null };
64
68
  // Another device cancelled this turn: the prompt travels to the device that
65
69
  // sent it through the state, never through the canceller's reply.
@@ -125,14 +129,26 @@ export function createAbortAction(bag) {
125
129
  const canceller = typeof options?.device === 'string' ? options.device : '';
126
130
  const crossDevice = Boolean(canceller && restoreState?.device && canceller !== restoreState.device);
127
131
  const hasAttachments = Boolean(restoreState?.pastedImages || restoreState?.pastedTexts || restoreState?.content);
128
- const canRestore =
132
+ const wantsRestore =
129
133
  options?.restorePrompt !== false &&
130
134
  restoreState?.restorable &&
131
135
  !hasPendingSteering &&
132
136
  !(crossDevice && hasAttachments);
137
+ // Hydrate BEFORE the turn is aborted or the prompt reclaimed: an attachment
138
+ // read error then cannot strand a prompt that left the transcript with no
139
+ // draft to land in. On failure the prompt is kept (like a Stop that hands
140
+ // nothing back) and the error is surfaced as the notice.
141
+ let restored = null;
142
+ let hydrateFailure = '';
143
+ if (wantsRestore) {
144
+ try {
145
+ restored = hydrateTolerantly(restoreState?.pastedImages, restoreState?.pastedTexts, restoreState?.content);
146
+ } catch (error) {
147
+ hydrateFailure = `Could not restore the attachments (${error?.message || error}); the prompt was kept.`;
148
+ }
149
+ }
150
+ const canRestore = wantsRestore && !hydrateFailure;
133
151
  const restoreText = canRestore ? restoreState.text : '';
134
- const restorePastedImages = canRestore && restoreState?.pastedImages ? restoreState.pastedImages : null;
135
- const restorePastedTexts = canRestore && restoreState?.pastedTexts ? restoreState.pastedTexts : null;
136
152
  // When steering suppresses the restore, the interrupted prompt's pasted
137
153
  // images never get committed (onCommitted won't fire) nor re-installed into
138
154
  // the draft, so hand them back for cleanup to avoid a stale `[Image #id]`
@@ -170,28 +186,28 @@ export function createAbortAction(bag) {
170
186
  discardExecutionPendingResume?.(restoreState.discardExecutionPendingResumeKeys);
171
187
  }
172
188
  if ((restoreText || requeueEntries.length > 0) && aborted !== false) {
173
- reclaimInFlightPrompt(restoreState, { restoreText, requeueEntries, handoff: crossDevice });
189
+ reclaimInFlightPrompt(restoreState, {
190
+ restoreText,
191
+ requeueEntries,
192
+ handoff: crossDevice,
193
+ keepPrompt: Boolean(hydrateFailure),
194
+ });
174
195
  }
175
196
  restoreState.restorable = false;
176
197
  restoreState.requeueEntries = [];
177
198
  restoreState.discardExecutionPendingResumeKeys = [];
178
199
  }
179
200
  kickDrainAfterAbort();
180
- const restored = hydrateTolerantly(
181
- restorePastedImages,
182
- restorePastedTexts,
183
- canRestore ? restoreState?.content : null
184
- );
185
201
  // The canceller never receives another device's prompt.
186
202
  const handedOver = crossDevice && Boolean(restoreText) && aborted !== false;
187
203
  return {
188
204
  aborted,
189
205
  restoreText: handedOver ? '' : restoreText,
190
- ...(restored.content ? { content: restored.content } : {}),
191
- ...(restored.notice ? { notice: restored.notice } : {}),
192
- pastedImages: restored.pastedImages,
206
+ ...(restored?.content ? { content: restored.content } : {}),
207
+ ...(restored?.notice || hydrateFailure ? { notice: restored?.notice || hydrateFailure } : {}),
208
+ pastedImages: restored?.pastedImages ?? null,
193
209
  discardPastedImages,
194
- pastedTexts: restored.pastedTexts,
210
+ pastedTexts: restored?.pastedTexts ?? null,
195
211
  discardPastedTexts,
196
212
  restoredSubmissionIds:
197
213
  restoreText && !handedOver ? (restoreState?.submittedIds || []).map(String).filter(Boolean) : [],