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
@@ -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) : [],
@@ -1,13 +1,17 @@
1
1
  // Message selector ("jump back to a previous message"): rewind the
2
2
  // conversation to just before a user prompt and hand its text back for
3
3
  // editing. Idle-only — a live turn must be interrupted first.
4
+ import { restorableAttachmentsFromPrompt } from '../../../../runtime/attachments/prompt-restore.mjs';
4
5
  import { promptHistoryWithout } from '../../prompt-history.mjs';
6
+ import { hydrateTolerantly } from './abort.mjs';
5
7
 
6
8
  export function createRewindAction(bag) {
7
9
  const { runtime, getState, set, flushEmitImmediate, replaceItems, syncContextStats } = bag;
8
10
 
9
11
  return {
10
- rewindToItem: async (itemId) => {
12
+ // `restoreFiles: false` is a composer that cannot hold PDF/Office file
13
+ // parts (the terminal one): a prompt carrying any is refused untouched.
14
+ rewindToItem: async (itemId, { restoreFiles = true } = {}) => {
11
15
  const target = String(itemId ?? '').trim();
12
16
  if (!target) return null;
13
17
  if (getState().busy || getState().commandBusy) return null;
@@ -19,8 +23,31 @@ export function createRewindAction(bag) {
19
23
  if (!text) return null;
20
24
  set({ commandBusy: true });
21
25
  try {
22
- const rewound = await runtime.rewindMessages?.({ text });
23
- if (!rewound) return null;
26
+ // Matched by the row's stable message id (the stored prompt is the
27
+ // expanded text); rows without one fall back to the text.
28
+ const messageId = String(items[index]?.messageId || '');
29
+ const rewindOptions = { text, ...(messageId ? { messageId } : {}) };
30
+ // Resolve the prompt and build the whole restore payload BEFORE any
31
+ // history changes: a refusal or an attachment read error then leaves the
32
+ // model history and the transcript both untouched.
33
+ const preview = await runtime.rewindMessages?.({ ...rewindOptions, dryRun: true });
34
+ if (!preview) return null;
35
+ // The stored prompt's attachment references become the restore payload
36
+ // the abort/queue restores use (pastedTexts / pastedImages / file parts).
37
+ const content = preview.message?.content;
38
+ if (!restoreFiles && Array.isArray(content) && content.some((part) => part?.type === 'file')) {
39
+ throw new Error(
40
+ 'this message has PDF/Office attachments the terminal composer cannot restore; edit it in the desktop app'
41
+ );
42
+ }
43
+ const refs = restorableAttachmentsFromPrompt(content, text);
44
+ const attachments = hydrateTolerantly(refs.pastedImages, refs.pastedTexts, content);
45
+ // Refused when the history changed since the preview.
46
+ const rewound = await runtime.rewindMessages?.({
47
+ ...rewindOptions,
48
+ ...(preview.historyMarker ? { expectedHistoryMarker: preview.historyMarker } : {}),
49
+ });
50
+ if (!rewound) throw new Error('the conversation changed; select the message again');
24
51
  set({
25
52
  items: replaceItems(items.slice(0, index), {
26
53
  preserveSpill: true,
@@ -36,7 +63,15 @@ export function createRewindAction(bag) {
36
63
  syncContextStats({ allowEstimated: true });
37
64
  set({ stats: { ...getState().stats } });
38
65
  flushEmitImmediate?.();
39
- return { text, removed: items.length - index, messages: rewound.removed };
66
+ return {
67
+ text,
68
+ removed: items.length - index,
69
+ messages: rewound.removed,
70
+ ...(attachments.pastedImages ? { pastedImages: attachments.pastedImages } : {}),
71
+ ...(attachments.pastedTexts ? { pastedTexts: attachments.pastedTexts } : {}),
72
+ ...(attachments.content ? { content: attachments.content } : {}),
73
+ ...(attachments.notice ? { notice: attachments.notice } : {}),
74
+ };
40
75
  } finally {
41
76
  set({ commandBusy: false });
42
77
  }
@@ -55,6 +55,7 @@ export function createAgentDefinitionApi(runtime) {
55
55
  saveLeadAgentBody: async (text) => runtime.saveLeadAgentBody?.(text) ?? '',
56
56
  getLeadStatus: () =>
57
57
  runtime.getLeadStatus?.() ?? { created: null, name: '', avatar: null, homeChatted: false, route: null },
58
+ getLeadHomeRoute: () => runtime.getLeadHomeRoute?.() ?? null,
58
59
  saveLeadIdentity: async (payload) => runtime.saveLeadIdentity?.(payload) ?? null,
59
60
  setLeadCreated: async (created, description) => runtime.setLeadCreated?.(created, description) ?? null,
60
61
  saveAgentDefinition: async (payload, options) => runtime.saveAgentDefinition?.(payload, options) ?? null,
@@ -96,17 +96,14 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
96
96
  // Settle one entry: drop its timeout, then hand the waiter its answer. The
97
97
  // resolve stays guarded — a waiter whose caller already went away must never
98
98
  // take the rest of the queue down with it.
99
- function settleToolApproval(entry, approved, reason, answers, reply, extra, device = '', forwarded = false) {
99
+ function settleToolApproval(entry, approved, reason, answers, reply, extra, device = '') {
100
100
  if (entry?.timer) clearTimeout(entry.timer);
101
101
  const result = { approved, reason };
102
102
  if (answers !== undefined) result.answers = answers;
103
103
  if (reply !== undefined) result.reply = reply;
104
- // A human-kind request answers with its canonical response (never a value),
105
- // judged against what the answering place offered: this queue's own view,
106
- // or — answered on its copy — the full request the copy already narrowed.
107
- const answered = forwarded ? entry?.full || entry?.request : entry?.request;
108
- if (answered?.remember) {
109
- Object.assign(result, normalizeHumanResponse(answered, approved, extra));
104
+ // A human-kind request answers with its canonical response (never a value).
105
+ if (entry?.request?.remember) {
106
+ Object.assign(result, normalizeHumanResponse(entry.request, approved, extra));
110
107
  if (device) result.device = device;
111
108
  }
112
109
  if (extra?.timedOut === true) result.timedOut = true;
@@ -120,28 +117,15 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
120
117
  * owner had already settled it. */
121
118
  function forwardToOwner(entry, approved, reason, device, answers, reply, extra) {
122
119
  try {
123
- return entry.mirror.answer(approved, reason, device, answers, reply, onlyOffered(entry.request, extra)) === true;
120
+ return entry.mirror.answer(approved, reason, device, answers, reply, extra) === true;
124
121
  } catch {
125
122
  return false;
126
123
  }
127
124
  }
128
- /** `request` as this session shows it: a remembering scope is offered only
129
- * in the one session the request names for it (`rememberAt`, the bot's own
130
- * chat); everywhere else — and when it names none — it asks once. */
131
- function viewFor(request) {
132
- if (!Array.isArray(request?.remember) || request.remember.length <= 1) return request;
133
- const here = ownSessionId();
134
- return here && request.rememberAt === here ? request : { ...request, remember: ['once'] };
135
- }
136
- /** `extra` without a remember scope `view` did not offer. */
137
- function onlyOffered(view, extra) {
138
- if (!extra || extra.remember === undefined) return extra;
139
- return (view?.remember || ['once']).includes(extra.remember) ? extra : { ...extra, remember: 'once' };
140
- }
141
125
  function offerDelegatedApproval(delegated) {
142
126
  if (getDisposed() || delegated.sessionId !== ownSessionId()) return;
143
127
  if (activeToolApproval?.id === delegated.id || toolApprovalQueue.some((entry) => entry.id === delegated.id)) return;
144
- toolApprovalQueue.push({ id: delegated.id, request: viewFor(delegated.request), mirror: delegated, timer: null });
128
+ toolApprovalQueue.push({ id: delegated.id, request: delegated.request, mirror: delegated, timer: null });
145
129
  presentNextToolApproval();
146
130
  }
147
131
  function withdrawDelegatedCopy(id, result) {
@@ -183,8 +167,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
183
167
  device = '',
184
168
  answers = undefined,
185
169
  reply = undefined,
186
- extra = undefined,
187
- { forwarded = false } = {}
170
+ extra = undefined
188
171
  ) {
189
172
  const targetId = String(id || '');
190
173
  if (activeToolApproval && activeToolApproval.id === targetId) {
@@ -200,16 +183,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
200
183
  if (entry.mirror) {
201
184
  accepted = forwardToOwner(entry, approved, reason, device, answers, reply, extra);
202
185
  } else {
203
- settleToolApproval(
204
- entry,
205
- approved === true,
206
- String(reason || ''),
207
- answers,
208
- reply,
209
- extra,
210
- String(device || ''),
211
- forwarded
212
- );
186
+ settleToolApproval(entry, approved === true, String(reason || ''), answers, reply, extra, String(device || ''));
213
187
  }
214
188
  presentNextToolApproval();
215
189
  return accepted;
@@ -218,16 +192,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
218
192
  if (index >= 0) {
219
193
  const [entry] = toolApprovalQueue.splice(index, 1);
220
194
  if (entry.mirror) return forwardToOwner(entry, approved, reason, device, answers, reply, extra);
221
- settleToolApproval(
222
- entry,
223
- approved === true,
224
- String(reason || ''),
225
- answers,
226
- reply,
227
- extra,
228
- String(device || ''),
229
- forwarded
230
- );
195
+ settleToolApproval(entry, approved === true, String(reason || ''), answers, reply, extra, String(device || ''));
231
196
  return true;
232
197
  }
233
198
  return false;
@@ -265,9 +230,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
265
230
  full.chain = surface.chain;
266
231
  full.surfaceSessionId = surface.sessionId;
267
232
  }
268
- // `request` is what this session shows (and accepts answers against);
269
- // `full` what the copy in the top-level chat is made from.
270
- const entry = { id, request: viewFor(full), full, resolve, timer: null, delegated: Boolean(surface) };
233
+ const entry = { id, request: full, resolve, timer: null, delegated: Boolean(surface) };
271
234
  toolApprovalQueue.push(entry);
272
235
  armToolApprovalTimeout(entry);
273
236
  presentNextToolApproval();
@@ -277,7 +240,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
277
240
  sessionId: surface.sessionId,
278
241
  request: full,
279
242
  answer: (approved, reason, device, answers, reply, extra) =>
280
- finishToolApproval(id, approved, reason, device, answers, reply, extra, { forwarded: true }),
243
+ finishToolApproval(id, approved, reason, device, answers, reply, extra),
281
244
  });
282
245
  }
283
246
  });
@@ -5,7 +5,6 @@ import { getModelMetadataSync } from '../../../../runtime/shared/llm/model-catal
5
5
  import { providerCachedModelsSync } from '../../../../runtime/shared/llm/provider-catalog-cache.mjs';
6
6
 
7
7
  export const CLAUDE_CURRENT_MODE = 'claude-current';
8
- export const CLAUDE_CURRENT_CHOICE_ID = 'claude-current';
9
8
 
10
9
  const DEFAULT_FAMILY_MODEL = Object.freeze({
11
10
  opus: 'claude-opus-4-8',
@@ -20,7 +19,6 @@ const EFFORT_BUDGET = Object.freeze({
20
19
  xhigh: 32768,
21
20
  max: 32768,
22
21
  });
23
- const SNAPSHOT_REFRESH_MS = 30_000;
24
22
 
25
23
  function cleanString(v) {
26
24
  return typeof v === 'string' && v.trim() ? v.trim() : '';
@@ -33,15 +31,6 @@ export function isMixdogModelSelection(value) {
33
31
  return cleanString(raw).toLowerCase().startsWith('mixdog/');
34
32
  }
35
33
 
36
- export function isClaudeNativeModelSelection(route) {
37
- return !!(
38
- route &&
39
- route.provider === 'anthropic-oauth' &&
40
- cleanString(route.model) &&
41
- !isMixdogModelSelection(route)
42
- );
43
- }
44
-
45
34
  function num(v, fallback = null) {
46
35
  if (v === null || v === undefined || v === '') return fallback;
47
36
  const n = Number(v);
@@ -80,23 +69,6 @@ function readFreshJsonFile(file, maxAgeMs) {
80
69
  }
81
70
  }
82
71
 
83
- function snapshotEquivalent(a, b) {
84
- if (!a || !b) return false;
85
- const keys = [
86
- 'mode',
87
- 'source',
88
- 'provider',
89
- 'model',
90
- 'requestedModel',
91
- 'modelDisplay',
92
- 'effort',
93
- 'fast',
94
- 'thinkingBudgetTokens',
95
- 'contextWindow',
96
- ];
97
- return keys.every(k => (a[k] ?? null) === (b[k] ?? null));
98
- }
99
-
100
72
  // The newest roster model of a family, by the roster's own release stamps.
101
73
  function newestRosterModel(family) {
102
74
  const stamp = (row) => Date.parse(row.releaseDate) || (Number(row.created) > 0 ? Number(row.created) : 0);
@@ -298,33 +270,3 @@ export function readClaudeCodeCurrentRoute({ request = null, maxAgeMs = 10 * 60
298
270
  if (route.fast === null || route.fast === undefined) route.fast = false;
299
271
  return route;
300
272
  }
301
-
302
- export function writeClaudeCodeCurrentSnapshot(ccJsonInput) {
303
- if (!ccJsonInput) return null;
304
- let payload = null;
305
- try { payload = JSON.parse(String(ccJsonInput)); } catch { return null; }
306
- const route = routeFromPayload(payload, 'statusline');
307
- if (!route || (!route.model && !route.requestedModel)) return null;
308
- const file = path.join(pluginDataDir(), 'claude-current-model.json');
309
- try {
310
- fs.mkdirSync(path.dirname(file), { recursive: true });
311
- const st = fs.existsSync(file) ? fs.statSync(file) : null;
312
- const current = st ? readJsonFile(file) : null;
313
- if (
314
- st &&
315
- Date.now() - st.mtimeMs < SNAPSHOT_REFRESH_MS &&
316
- snapshotEquivalent(current, route)
317
- ) {
318
- return route;
319
- }
320
- fs.writeFileSync(file, JSON.stringify(route, null, 2) + '\n');
321
- } catch { /* best-effort statusline cache */ }
322
- return route;
323
- }
324
-
325
- export function formatClaudeCurrentChoiceLabel(route = readClaudeCodeCurrentRoute()) {
326
- const parts = [`Current selection: ${route.modelDisplay || route.model || 'current model'}`];
327
- if (route.effort) parts.push(String(route.effort).toUpperCase());
328
- if (route.fast === true) parts.push('fast');
329
- return parts.join(' · ');
330
- }