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
@@ -2,8 +2,14 @@
2
2
  // Periodic idle-session + tombstone sweep.
3
3
  // Drives sweepStaleSessions on an unref'd interval; closeSession is imported
4
4
  // from session-close.mjs (one-way dependency, no cycle).
5
- import { sweepStaleSessions, sweepStaleSessionsCooperative, evictIdleLiveSessions } from '../store.mjs';
5
+ import {
6
+ applySubAgentRetention,
7
+ sweepStaleSessions,
8
+ sweepStaleSessionsCooperative,
9
+ evictIdleLiveSessions,
10
+ } from '../store.mjs';
6
11
  import { sweepOrphanedPendingMessages } from './pending-messages.mjs';
12
+ import { applySessionSidebarChange, readSessionSidebarState } from '../../../../shared/session-sidebar-host.mjs';
7
13
  import {
8
14
  IN_FLIGHT_STAGES,
9
15
  _getRuntimeEntry,
@@ -83,6 +89,29 @@ function _logTombstoneSweep(tombstonesCleaned, tombstoneDetails, tombstoneErrors
83
89
  }
84
90
  }
85
91
 
92
+ // Sub-agent trees the sweep found idle: archived after 3 days (through the
93
+ // desktop's archive flag, when one is attached), deleted after 30
94
+ // (store/sweep/sub-agent-retention.mjs).
95
+ async function _retainSubAgents(members) {
96
+ if (!members?.length) return;
97
+ try {
98
+ const flags = await readSessionSidebarState();
99
+ const applied = await applySubAgentRetention(members, {
100
+ isSessionLive: _isSessionLive,
101
+ isArchived: flags && ((id) => Object.hasOwn(flags.archived, id)),
102
+ setSidebar: applySessionSidebarChange,
103
+ });
104
+ for (const id of applied.deleted) if (!_isSessionLive(id)) _clearSessionRuntime(id);
105
+ if (applied.archived.length || applied.deleted.length) {
106
+ _sweepLog(
107
+ `[session-sweep] sub-agent retention: archived ${applied.archived.length}${_previewIds(applied.archived.map((id) => ({ id })))}, deleted ${applied.deleted.length}${_previewIds(applied.deleted.map((id) => ({ id })))}\n`
108
+ );
109
+ }
110
+ } catch (e) {
111
+ process.stderr.write(`[session-sweep] sub-agent retention error: ${e?.message || e}\n`);
112
+ }
113
+ }
114
+
86
115
  async function sweepIdleSessions({ includeTombstones = true, sweepIdle = true } = {}) {
87
116
  const startedAt = Date.now();
88
117
  try {
@@ -90,8 +119,10 @@ async function sweepIdleSessions({ includeTombstones = true, sweepIdle = true }
90
119
  sweepIdle,
91
120
  tombstoneMaxAgeMs: includeTombstones ? TOMBSTONE_MAX_AGE_MS : 0,
92
121
  isSessionLive: _isSessionLive,
122
+ retainSubAgents: sweepIdle,
93
123
  });
94
124
  const { cleaned, remaining, details, tombstonesCleaned = 0, tombstoneDetails = [], tombstoneErrors = [] } = result;
125
+ await _retainSubAgents(result.subAgents);
95
126
  if (cleaned > 0) {
96
127
  for (const d of details) {
97
128
  if (!_finalizeSweptSessionRuntime(d)) continue;
@@ -15,7 +15,7 @@
15
15
  // the provider accepted it. `from` is the mode the model was last told
16
16
  // about, `to` the session's mode now.
17
17
  // Every notice says it replaces the earlier ones, so the latest one wins.
18
- import { normalizeOrchestrationMode } from '../../../../shared/orchestration.mjs';
18
+ import { normalizeOrchestrationMode, ORCHESTRATION_MODES } from '../../../../shared/orchestration.mjs';
19
19
  import { orchestrationInstructions } from '../../runtime-core/orchestration.mjs';
20
20
 
21
21
  /** The mode's name as its BP3 block titles it (`# Orchestration Mode: …`). */
@@ -31,6 +31,22 @@ export function promptOrchestrationModeOf(session) {
31
31
  return session?.orchestrationMode;
32
32
  }
33
33
 
34
+ /**
35
+ * Read a shipped coding-mode slice from the actual BP3 message, not the mode
36
+ * metadata: older profile refreshes could rewrite one without the other.
37
+ * Only a complete, unambiguous generated slice is evidence. Custom rules,
38
+ * headings quoted in another slice, and unrecognized legacy layouts are not.
39
+ */
40
+ export function orchestrationModeFromPrompt(session) {
41
+ const core = session?.messages?.find(
42
+ (message) => message?.role === 'system' && message.cacheTier === 'tier3'
43
+ )?.content;
44
+ if (typeof core !== 'string') return null;
45
+ const slices = core.split('\n\n---\n\n');
46
+ const modes = ORCHESTRATION_MODES.filter((mode) => slices.includes(orchestrationInstructions(mode)));
47
+ return modes.length === 1 ? modes[0] : null;
48
+ }
49
+
34
50
  const listed = (names) => names.map((name) => `\`${name}\``).join(', ');
35
51
 
36
52
  /** The reminder that makes the Lead follow `mode`: self-contained (the mode's
@@ -3,6 +3,7 @@ import { mergeNormalizedContentEntries } from '../loop/steering.mjs';
3
3
  import { isDeliveredCompletion, logDuplicateSkip } from './delivered-completions.mjs';
4
4
  import { promptContentText } from './prompt-utils.mjs';
5
5
  import { parseTaskNotification, taskNotificationId } from '../../../../shared/task-notification-envelope.mjs';
6
+ import { hopOf } from '../../../../shared/bot-hops.mjs';
6
7
 
7
8
  const STALE_USER_INJECTION_TTL_MS = 30 * 60 * 1000;
8
9
  const PENDING_PROCESS_START_MS = Date.now();
@@ -95,11 +96,13 @@ function executionFromCompletionMeta(meta) {
95
96
  function completionEntryFields(entry) {
96
97
  const executionId = completionExecutionId(entry);
97
98
  const execution = normalizeExecution(entry?.execution, executionId);
99
+ const hop = hopOf(entry?.hop);
98
100
  return {
99
101
  notificationKind: COMPLETION_NOTIFICATION_KIND,
100
102
  mode: PENDING_MODE_TASK_NOTIFICATION,
101
103
  ...(executionId ? { executionId } : {}),
102
104
  ...(execution ? { execution } : {}),
105
+ ...(hop ? { hop } : {}),
103
106
  };
104
107
  }
105
108
 
@@ -141,6 +144,8 @@ export function markCompletionEntry(text, options = {}) {
141
144
  mode: PENDING_MODE_TASK_NOTIFICATION,
142
145
  ...(executionId ? { executionId } : {}),
143
146
  ...(execution ? { execution } : {}),
147
+ // The bot-chain hop of the turn that launched the work (bot-hops.mjs).
148
+ ...(hopOf(options?.meta?.hop) ? { hop: options.meta.hop } : {}),
144
149
  enqueuedAt: Date.now(),
145
150
  };
146
151
  }
@@ -337,6 +342,7 @@ export function _groupPendingMessageEntries(entries) {
337
342
  ...merged,
338
343
  mode: PENDING_MODE_TASK_NOTIFICATION,
339
344
  ...(normalized.execution ? { execution: normalized.execution } : {}),
345
+ ...(normalized.hop ? { hop: normalized.hop } : {}),
340
346
  ids: idsOf([entry]),
341
347
  entries: [entry],
342
348
  });
@@ -329,6 +329,42 @@ export function withPresentedWorkspaceSlices(session, nextHead) {
329
329
  return [nextHead, instructionFiles, memory].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
330
330
  }
331
331
 
332
+ const AGENT_CATALOG_HEADING = '# Available Agents';
333
+ // One generated catalog line (workflow/context.mjs agentCatalog):
334
+ // `- <name> (<id>)` with an optional `: <description>`.
335
+ const AGENT_CATALOG_ENTRY = /^- (.+?) \(([^()\s]+)\)(?:: .*)?$/;
336
+
337
+ /** The entries of a generated agent list slice, or null when `slice` is not
338
+ * one (only the exact generated format is evidence). */
339
+ export function agentListEntries(slice) {
340
+ const text = String(slice || '').trim();
341
+ if (!text.startsWith(`${AGENT_CATALOG_HEADING}\n\n`)) return null;
342
+ const entries = [];
343
+ for (const line of text.slice(AGENT_CATALOG_HEADING.length + 2).split('\n')) {
344
+ const match = AGENT_CATALOG_ENTRY.exec(line);
345
+ if (!match) return null;
346
+ entries.push({ id: match[2], name: match[1], line });
347
+ }
348
+ return entries;
349
+ }
350
+
351
+ /**
352
+ * The "# Available Agents" slice the session's prompt presents: '' when it
353
+ * carries none, null when what it carries under that heading is not one
354
+ * complete generated list (custom text, or more than one). A coding Lead's
355
+ * rebuilt core keeps a presented list, as it keeps the workspace slices: the
356
+ * list the model saw first stays the baseline every later agent list
357
+ * reminder applies to (agent-listing-notice.mjs).
358
+ */
359
+ export function presentedAgentCatalogSlice(session) {
360
+ const core = session.bp3EnvSplit === true ? textOf(bp3SystemMessage(session)) : String(session.bp3CoreContext || '');
361
+ const slices = core
362
+ .split(BP3_PART_SEPARATOR)
363
+ .filter((slice) => slice === AGENT_CATALOG_HEADING || slice.startsWith(`${AGENT_CATALOG_HEADING}\n`));
364
+ if (!slices.length) return '';
365
+ return slices.length === 1 && agentListEntries(slices[0]) ? slices[0] : null;
366
+ }
367
+
332
368
  const WORKSPACE_SLICES = ['environmentContext', 'coreMemoryContext', 'instructionFilesContext'];
333
369
 
334
370
  /**
@@ -1,6 +1,7 @@
1
1
  // manager/session-crud.mjs
2
2
  // Session read + mutate CRUD: lookup (getSession/listSessions/findSessionByScopeKey),
3
3
  // message clear (with clear-fork), manual compaction, status update, and metric flush.
4
+ import { createHash } from 'node:crypto';
4
5
  import {
5
6
  loadSession,
6
7
  saveSessionAsync,
@@ -29,6 +30,7 @@ import { hideCompactionArchive } from '../compaction-archive-capture.mjs';
29
30
  import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
30
31
  import { forgetSideQuestion } from '../side-question-store.mjs';
31
32
  import { isAgentHomeSessionId } from '../agent-home.mjs';
33
+ import { messageIdOf } from '../message-ids.mjs';
32
34
 
33
35
  /** Force-flush session metrics to disk. */
34
36
  export async function flushSessionMetrics(sessionId) {
@@ -412,19 +414,36 @@ export async function updateSessionStatus(id, status) {
412
414
  export async function rewindSessionMessagesTo(sessionId, options = {}) {
413
415
  const session = loadSession(sessionId);
414
416
  if (!session || session.closed === true) return null;
417
+ const targetId = String(options?.messageId ?? '').trim();
415
418
  const target = String(options?.text ?? '').trim();
416
- if (!target) return null;
419
+ if (!targetId && !target) return null;
417
420
  const messages = Array.isArray(session.messages) ? session.messages : [];
418
- let start = -1;
421
+ // The stable message id is the identity: the stored prompt is the EXPANDED
422
+ // text (attachment tokens materialized), so display text cannot find it.
423
+ // Text is only the fallback for a row that carries no id.
424
+ const matches = targetId
425
+ ? (message) => messageIdOf(message) === targetId
426
+ : (message) => promptContentText(message.content).trim() === target;
427
+ const found = [];
419
428
  for (let index = messages.length - 1; index >= 0; index -= 1) {
420
- const message = messages[index];
421
- if (message?.role !== 'user') continue;
422
- if (promptContentText(message.content).trim() !== target) continue;
423
- start = index;
424
- break;
429
+ if (messages[index]?.role === 'user' && matches(messages[index])) found.push(index);
425
430
  }
426
- if (start < 0) return null;
431
+ // Without an id, text that several prompts repeat cannot say which one the
432
+ // user picked: refuse rather than rewind the wrong occurrence.
433
+ if (found.length === 0 || (!targetId && found.length > 1)) return null;
434
+ const start = found[0];
427
435
  const removed = messages.length - start;
436
+ const rewoundMessage = messages[start];
437
+ // `dryRun` resolves the target and reports what a rewind would do, changing
438
+ // nothing: the caller prepares the restore payload first and commits after.
439
+ // The marker names the exact history the dry run saw (generation alone only
440
+ // moves on lifecycle changes); the commit passes it back and is refused when
441
+ // the history differs.
442
+ const historyMarker = `${session.generation ?? 0}:${messages.length}:${createHash('sha1')
443
+ .update(JSON.stringify(messages))
444
+ .digest('hex')}`;
445
+ if (options?.dryRun === true) return { removed, remaining: start, message: rewoundMessage, historyMarker };
446
+ if (options?.expectedHistoryMarker != null && options.expectedHistoryMarker !== historyMarker) return null;
428
447
  session.messages = messages.slice(0, start);
429
448
  // The provider cache is keyed on the exact prefix we just truncated;
430
449
  // the prefix-guard snapshot and the /btw fork request describe it too and
@@ -435,5 +454,5 @@ export async function rewindSessionMessagesTo(sessionId, options = {}) {
435
454
  session.updatedAt = Date.now();
436
455
  session.lastUsedAt = Date.now();
437
456
  await saveSessionAsync(session, { expectedGeneration: session.generation });
438
- return { removed, remaining: session.messages.length };
457
+ return { removed, remaining: session.messages.length, message: rewoundMessage };
439
458
  }
@@ -159,7 +159,7 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
159
159
  provider: providerName,
160
160
  model: modelName,
161
161
  });
162
- const delegationFree = delegationDisabled(opts, ownerIsAgent);
162
+ const delegationFree = delegationDisabled({ ...opts, sessionMode }, ownerIsAgent);
163
163
  const promptIdentity = {
164
164
  systemPrompt: opts.systemPrompt || '',
165
165
  skipAgentRules: opts.skipAgentRules === true,
@@ -5,7 +5,7 @@
5
5
  // here is field derivation — no ownership, no prompt text; the only read is the
6
6
  // parent's record a delegated session takes its root from.
7
7
  import { toSessionWorkflowMeta } from '../../runtime-core/workflow.mjs';
8
- import { delegationForNewSession } from '../human-gate/delegation.mjs';
8
+ import { delegationForNewSession, runForNewSession } from '../human-gate/delegation.mjs';
9
9
  import { sessionOrchestrationMode } from '../../../../shared/orchestration.mjs';
10
10
  import { preserveBufferConfigFields } from './context-meta.mjs';
11
11
  import { positiveInt } from '../../../../shared/numbers.mjs';
@@ -103,16 +103,18 @@ export function sessionOriginFields(opts, { profile, presetObj, providerCacheOpt
103
103
  ownerSessionId: opts.ownerSessionId || null,
104
104
  visibility: opts.visibility || null,
105
105
  clientHostPid: opts.clientHostPid || null,
106
- // A sub-agent's or a bot-created task session's root (who it works for, and
107
- // how that root was acting), derived from the parent's record — the one
108
- // read here — never taken from the options (human-gate/delegation.mjs).
106
+ // A sub-agent's root and the run (if any) that started a sub-agent or a
107
+ // bot-created task session, derived from its creator — the one read here —
108
+ // never taken from the options (human-gate/delegation.mjs).
109
109
  ...delegationFields(opts),
110
110
  };
111
111
  }
112
112
 
113
113
  function delegationFields(opts) {
114
114
  const delegation = delegationForNewSession(opts);
115
- return delegation ? { delegation } : {};
115
+ // Work a schedule/webhook run started (a sub-agent, a task session) stays the run's.
116
+ const run = runForNewSession(opts);
117
+ return { ...(delegation ? { delegation } : {}), ...(run ? { run } : {}) };
116
118
  }
117
119
 
118
120
  // The persisted session record.
@@ -9,15 +9,19 @@ import { collectPromptSkillsCached } from '../../context/collect.mjs';
9
9
  import { isAgentOwner } from '../../agent-owner.mjs';
10
10
  import { getHiddenAgent } from '../../internal-agents.mjs';
11
11
  import { workflowDisallowsAgentTool } from '../../runtime-core/workflow.mjs';
12
+ import { isAgentModeSession } from '../agent-mode.mjs';
12
13
  import { finalizeSessionToolList, resolveSessionTools, permissionFromToolSpec } from './tool-resolution.mjs';
13
14
  import { unusedModelEditToolName } from '../../../../shared/edit-tool-dialect.mjs';
14
15
  import { getAgentRuntimeSync } from './agent-runtime-singleton.mjs';
15
16
 
16
17
  // A lead session may not delegate when its workflow forbids the agent tool
17
- // (no delegatable agents); Solo keeps it deferred for explicit user requests.
18
- // An agent-owned session never gets the tool anyway.
18
+ // (an agent-mode Lead without delegatable agents); Solo keeps it deferred for
19
+ // explicit user requests. An agent-owned session never gets the tool anyway.
19
20
  export function delegationDisabled(source, ownerIsAgent) {
20
- return !ownerIsAgent && workflowDisallowsAgentTool(source?.workflow);
21
+ return (
22
+ !ownerIsAgent &&
23
+ workflowDisallowsAgentTool(source?.workflow, { agentMode: Boolean(source) && isAgentModeSession(source) })
24
+ );
21
25
  }
22
26
 
23
27
  // Exactly two schema surfaces exist: Lead and Agent. Every Agent role gets
@@ -40,6 +40,7 @@ import {
40
40
  markPendingGoalReminder,
41
41
  snapshotPendingGoalReminder,
42
42
  } from '../runtime-core/goal-reminder.mjs';
43
+ import { rearmLateDeferredTools } from '../runtime-core/tool-catalog.mjs';
43
44
 
44
45
  async function setStage(opts, stage) {
45
46
  try {
@@ -552,7 +553,15 @@ async function compactBeforeSend(ctx) {
552
553
  return;
553
554
  }
554
555
  ctx.compactChanged = messagesArrayChanged(messages, run.compacted);
555
- if (ctx.compactChanged) adoptCompactedTranscript(ctx, run);
556
+ if (ctx.compactChanged) {
557
+ adoptCompactedTranscript(ctx, run);
558
+ // The adopted compaction strips late MCP tool announcements from older turns.
559
+ try {
560
+ rearmLateDeferredTools(sessionRef);
561
+ } catch {
562
+ /* best-effort, like the Goal reminder: a tool reminder never fails the compaction */
563
+ }
564
+ }
556
565
  reportCompactOutcome(ctx, run);
557
566
  // PostCompact belongs only to a completed compaction pass, never the
558
567
  // ordinary pre-send threshold check.
@@ -182,8 +182,10 @@ export function agentTaskRelayFailedMessage({ count, tries, at = Date.now(), mes
182
182
  };
183
183
  }
184
184
 
185
- /** Agent-initiated round trips on one task session before each report asks
186
- * the agent to check with the user (session create). */
185
+ /** Bot-descended round trips (sent from a turn another bot's message started)
186
+ * on one task session before each report asks the agent to check with the
187
+ * user (session create). The user's or a routine's own delegations are not
188
+ * counted (agent-home-delivery.mjs `add`). */
187
189
  export const TASK_LOOP_CAP = 3;
188
190
 
189
191
  /** A coding task session's report into the home of the agent that created it
@@ -213,7 +215,8 @@ export function agentTaskMessage({
213
215
  const summary = typeof givenSummary === 'string' ? givenSummary : routineSummary(text);
214
216
  const reply = textChars > String(text ?? '').length ? { text: String(text), chars: textChars } : boundedReply(text);
215
217
  // The files the turn produced: listed for the agent, stored ones carried as
216
- // the row's attachments (shown with the report, forwardable by id).
218
+ // the row's attachments (forwardable by id; the chat's result card does not
219
+ // show them, the task session does).
217
220
  const produced = normalizeHandoffFiles(files);
218
221
  const attachments = artifactRecords(produced);
219
222
  const label = escapeHarnessTags(String(title).replace(/["\r\n]/g, ''));
@@ -310,7 +313,7 @@ export function agentTaskWakePrompt(messages, { more = 0 } = {}) {
310
313
  'Relay the report above to the user now, briefly and in your own words; a report marked (working) is still in progress, not done.',
311
314
  ...(files
312
315
  ? [
313
- 'Its files are already shown to the user with the report; name the ones that matter by path, and pass them on to another session or agent with attachments.',
316
+ 'Its files are not shown in this chat: the user sees only the result card and can open the task session for details. Name the files that matter by path yourself, and pass them on to another session or agent with attachments.',
314
317
  ]
315
318
  : []),
316
319
  ].join('\n\n');
@@ -33,7 +33,9 @@ export function stripRuntimeUserContext(message) {
33
33
  } else {
34
34
  return message;
35
35
  }
36
- const { runtimeUserContext: _context, ...meta } = message.meta;
36
+ // `agentListing` records what the stripped reminder told (agent-listing-
37
+ // notice.mjs): without the text it must not still count as told.
38
+ const { runtimeUserContext: _context, agentListing: _agentListing, ...meta } = message.meta;
37
39
  const result = { ...message, content, meta };
38
40
  if (Object.keys(meta).length === 0) delete result.meta;
39
41
  return result;
@@ -256,7 +256,13 @@ export async function sendWithRecovery(ctx) {
256
256
  let response;
257
257
  try {
258
258
  const { timedOpts } = hooks;
259
- const admittedOpts = timedOpts?.admissionOwner ? timedOpts : { ...(timedOpts || {}), admissionOwner: sessionId };
259
+ const baseOpts = timedOpts?.admissionOwner ? timedOpts : { ...(timedOpts || {}), admissionOwner: sessionId };
260
+ // A one-shot max_tokens request rides on this send's own copy only: the
261
+ // shared opts never carry it, so no later attempt can inherit it.
262
+ const admittedOpts =
263
+ ctx.maxOutputTokensOverride > 0
264
+ ? { ...baseOpts, maxOutputTokensOverride: ctx.maxOutputTokensOverride }
265
+ : baseOpts;
260
266
  // Ephemeral tail reminder (tail-reminder-rules.mjs): added to this request's
261
267
  // copy only, never to the stored transcript.
262
268
  const requestMessages = withTailReminder(messages, { session: ctx.sessionRef, provider, model });
@@ -267,7 +273,10 @@ export async function sendWithRecovery(ctx) {
267
273
  stampStreamOutcome(sendErr, hooks.relayWitness);
268
274
  return await recoverFromSendError(sendErr, { ...ctx, relayWitness: hooks.relayWitness });
269
275
  }
270
- return { action: 'proceed', response };
276
+ // streamedChars: what this send put on screen through onTextDelta (net of
277
+ // acknowledged retractions), so the loop can withdraw it if it discards
278
+ // the response and re-sends.
279
+ return { action: 'proceed', response, streamedChars: hooks.relayWitness.text.length };
271
280
  } finally {
272
281
  hooks.restore();
273
282
  }
@@ -21,8 +21,8 @@ function* sweepStaleSessionSteps(ttlMs, options = {}) {
21
21
  // An unreadable store dir is not an empty one: sweeping (deleting,
22
22
  // closing, pruning) on a probe we could not make is never allowed.
23
23
  if (probePath(dir).state !== PROBE_PRESENT) {
24
- const { openCandidates: _candidates, ...empty } = createSweepTally();
25
- return empty;
24
+ const { openCandidates: _candidates, subAgents, ...empty } = createSweepTally();
25
+ return plan.retainSubAgents ? { ...empty, subAgents } : empty;
26
26
  }
27
27
  const rows = collectSweepRows(dir);
28
28
  pruneSweepRecordCache(new Set(rows.map((row) => row?.id).filter(Boolean)));
@@ -41,8 +41,9 @@ function* sweepStaleSessionSteps(ttlMs, options = {}) {
41
41
  yield* reapOrphanSidecars(ctx);
42
42
  queueDeletedSummaryPrune(ctx.tally);
43
43
  persistSweepRecordCache();
44
- const { openCandidates: _candidates, ...result } = ctx.tally;
45
- return result;
44
+ const { openCandidates: _candidates, subAgents, ...result } = ctx.tally;
45
+ // `subAgents` only for a caller that applies them (idle-cleanup.mjs).
46
+ return plan.retainSubAgents ? { ...result, subAgents } : result;
46
47
  }
47
48
 
48
49
  /** Synchronous compatibility surface for explicit maintenance commands/tests. */
@@ -0,0 +1,140 @@
1
+ // sweep/sub-agent-retention.mjs
2
+ // Retention of finished sub-agent sessions: the agent-only children (Worker,
3
+ // Reviewer, ...) a Lead's agents spawn, each its own stored session. The idle
4
+ // sweep names every sub-agent record with its STORED activity time (the clock
5
+ // survives restarts); a tree — a top sub-agent under a Lead with every
6
+ // sub-agent below it — is archived once each member is idle
7
+ // SUB_AGENT_ARCHIVE_AFTER_MS and deleted once each is idle
8
+ // SUB_AGENT_DELETE_AFTER_MS. Otherwise the whole tree stays as it is.
9
+ // Never a member: a root Lead, a task session from `session create`, a bot
10
+ // home chat or a group room's log/member session.
11
+ import { agentParentIds, isAgentOnlySession, isRootLeadSession } from '../../store-summary-visibility.mjs';
12
+ import { probePath, PROBE_PRESENT } from '../fs-probe.mjs';
13
+ import { sessionPath } from '../session-paths.mjs';
14
+ import { _queueSummaryIndexPrune } from '../summary-cache.mjs';
15
+ import { deleteSession } from '../session-delete.mjs';
16
+ import { readSweepRecord } from './sweep-record.mjs';
17
+
18
+ const DAY_MS = 24 * 60 * 60 * 1000;
19
+ export const SUB_AGENT_ARCHIVE_AFTER_MS = 3 * DAY_MS;
20
+ export const SUB_AGENT_DELETE_AFTER_MS = 30 * DAY_MS;
21
+ const PERMANENT_ID = /^sess_(?:home|room|rmem)_/;
22
+ // The desktop change an automatic archive applies: that one session, no
23
+ // cascade (an explicit archive of a parent takes its sub-agents along).
24
+ export const SUB_AGENT_AUTO_ARCHIVE = Object.freeze({ archived: true, cascade: false });
25
+
26
+ const positive = (value) => (Number(value) > 0 ? Number(value) : 0);
27
+
28
+ /** Last activity of a stored record: its last conversation turn (`lastUsedAt`;
29
+ * `updatedAt` also moves on detach/resume bookkeeping) or its heartbeat. */
30
+ export function subAgentActivityAt(doc) {
31
+ return Math.max(
32
+ positive(doc?.lastUsedAt) || positive(doc?.updatedAt) || positive(doc?.createdAt),
33
+ positive(doc?.lastHeartbeatAt)
34
+ );
35
+ }
36
+
37
+ /** `{ id, parentIds, lastActive }` for a sub-agent record (open or closed);
38
+ * null for anything else. `parentIds` are the owner and parent links the
39
+ * record names (agentParentIds), the same ones an archive cascade follows. */
40
+ export function subAgentMember(id, doc) {
41
+ if (!doc || PERMANENT_ID.test(id) || doc.taskOrigin) return null;
42
+ if (isRootLeadSession(doc) || !isAgentOnlySession(doc)) return null;
43
+ return { id, parentIds: agentParentIds({ ...doc, id }), lastActive: subAgentActivityAt(doc) };
44
+ }
45
+
46
+ /** The members grouped into trees: members joined by any owner/parent link
47
+ * between them (a link to a non-member joins nothing), each tree listing a
48
+ * top member (no member parent) first and its members below in link order. */
49
+ export function subAgentTrees(members) {
50
+ const byId = new Map(members.map((member) => [member.id, member]));
51
+ const links = new Map(members.map((member) => [member.id, new Set()]));
52
+ const hasMemberParent = new Set();
53
+ for (const member of members) {
54
+ for (const parent of (member.parentIds || []).filter((parentId) => byId.has(parentId))) {
55
+ links.get(member.id).add(parent);
56
+ links.get(parent).add(member.id);
57
+ hasMemberParent.add(member.id);
58
+ }
59
+ }
60
+ const seen = new Set();
61
+ const trees = [];
62
+ for (const start of [...members.filter((m) => !hasMemberParent.has(m.id)), ...members]) {
63
+ if (seen.has(start.id)) continue;
64
+ const tree = [start];
65
+ seen.add(start.id);
66
+ for (let index = 0; index < tree.length; index++) {
67
+ for (const id of links.get(tree[index].id)) {
68
+ if (seen.has(id)) continue;
69
+ seen.add(id);
70
+ tree.push(byId.get(id));
71
+ }
72
+ }
73
+ trees.push(tree);
74
+ }
75
+ return trees;
76
+ }
77
+
78
+ /** The stored activity time of `id` now; null when its record cannot be read. */
79
+ function storedActivityAt(id) {
80
+ const path = sessionPath(id);
81
+ const probe = probePath(path);
82
+ if (probe.state !== PROBE_PRESENT) return null;
83
+ const record = readSweepRecord(id, path, probe);
84
+ return record?.id === id ? subAgentActivityAt(record.doc) : null;
85
+ }
86
+
87
+ /**
88
+ * Archives or deletes each idle tree. A tree with a member running here
89
+ * (`isSessionLive`), or one whose stored activity moved since the sweep read
90
+ * it, is left alone. An archive goes through the desktop's archive flag
91
+ * (`setSidebar(id, SUB_AGENT_AUTO_ARCHIVE)`: exactly the members not yet
92
+ * archived, never a cascade past them — needs
93
+ * `isArchived`, the desktop's flags); a deleted member that was archived loses
94
+ * its flag (`{ forgotten: true }`). Returns `{ archived, deleted }` (ids).
95
+ */
96
+ export async function applySubAgentRetention(
97
+ members,
98
+ { isSessionLive = null, isArchived = null, setSidebar = null, now = Date.now() } = {}
99
+ ) {
100
+ const archived = [];
101
+ const deleted = [];
102
+ for (const tree of subAgentTrees(members)) {
103
+ // A member of unknown age (no valid stored timestamp) keeps its tree.
104
+ if (!tree.every((member) => Number.isFinite(member.lastActive) && member.lastActive > 0)) continue;
105
+ const idle = now - Math.max(...tree.map((member) => member.lastActive));
106
+ const expired = idle >= SUB_AGENT_DELETE_AFTER_MS;
107
+ if (!expired && (idle < SUB_AGENT_ARCHIVE_AFTER_MS || !isArchived)) continue;
108
+ if (tree.some((member) => isSessionLive?.(member.id))) continue;
109
+ if (tree.some((member) => storedActivityAt(member.id) !== member.lastActive)) continue;
110
+ if (expired) {
111
+ for (const { id } of tree.toReversed()) {
112
+ try {
113
+ if (deleteSession(id, { deferSummaryUpdate: true, isSessionLive, legacyOwnerVeto: true })) deleted.push(id);
114
+ } catch {
115
+ /* kept; the next pass decides again */
116
+ }
117
+ }
118
+ continue;
119
+ }
120
+ for (const { id } of tree) {
121
+ if (isArchived(id)) continue;
122
+ try {
123
+ await setSidebar(id, SUB_AGENT_AUTO_ARCHIVE);
124
+ archived.push(id);
125
+ } catch {
126
+ /* not archived; the next pass decides again */
127
+ }
128
+ }
129
+ }
130
+ if (deleted.length) _queueSummaryIndexPrune(new Set(deleted));
131
+ for (const id of deleted) {
132
+ if (!isArchived?.(id)) continue;
133
+ try {
134
+ await setSidebar(id, { forgotten: true });
135
+ } catch {
136
+ /* a stale archive stamp hides nothing */
137
+ }
138
+ }
139
+ return { archived, deleted };
140
+ }
@@ -26,6 +26,9 @@ export function resolveSweepPlan(ttlMs, options = {}) {
26
26
  retainOpen: sweepIdle && options.retainOpenSessions !== false,
27
27
  openMaxAgeMs: Number.isFinite(optAge) && optAge > 0 ? optAge : RESUMABLE_OPEN_MAX_AGE_MS,
28
28
  openMaxCount: Number.isFinite(optCount) && optCount >= 0 ? optCount : RESUMABLE_OPEN_MAX_COUNT,
29
+ // Name every sub-agent session for the tree retention its caller applies
30
+ // (sweep/sub-agent-retention.mjs). Idle sweep only.
31
+ retainSubAgents: sweepIdle && options.retainSubAgents === true,
29
32
  };
30
33
  }
31
34
 
@@ -69,5 +72,7 @@ export function createSweepTally() {
69
72
  openCandidates: [],
70
73
  openPruned: 0,
71
74
  openPrunedDetails: [],
75
+ // Sub-agent sessions with their stored activity (plan.retainSubAgents).
76
+ subAgents: [],
72
77
  };
73
78
  }
@@ -48,7 +48,7 @@ export const BLANK_SCRATCH_MAX_AGE_MS = 60 * 60 * 1000; // 1h
48
48
  // running is still parsed again.
49
49
  const sweepRecordCache = new Map();
50
50
  const SWEEP_RECORD_CACHE_FILE = 'session-sweep-records.json';
51
- const SWEEP_RECORD_CACHE_VERSION = 2;
51
+ const SWEEP_RECORD_CACHE_VERSION = 3;
52
52
  let sweepRecordCacheDataDir = null;
53
53
  let sweepRecordCacheDirty = false;
54
54
  let sweepRecordParses = 0;
@@ -82,7 +82,7 @@ function syncSweepRecordCache() {
82
82
  }
83
83
 
84
84
  // The only document fields sweep-row reads (effectiveFields, the closed
85
- // check). Caching the whole top-level document kept
85
+ // check, the sub-agent retention verdict). Caching the whole top-level document kept
86
86
  // every session's tool catalogs, schemas and provider state alive — one
87
87
  // entry per session file, measured at ~290MB of a 470MB daemon heap.
88
88
  const SWEEP_DOC_FIELDS = [
@@ -97,6 +97,11 @@ const SWEEP_DOC_FIELDS = [
97
97
  'createdAt',
98
98
  'ownerSessionId',
99
99
  'parentSessionId',
100
+ 'lastUsedAt',
101
+ 'agent',
102
+ 'visibility',
103
+ 'sessionVisibility',
104
+ 'taskOrigin',
100
105
  ];
101
106
 
102
107
  function conversationCountOf(doc) {