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
@@ -1,29 +1,35 @@
1
1
  // session/human-gate/delegation.mjs
2
2
  // Who a delegated session works for, recorded when it is created and re-bound
3
3
  // only when another session sends it work (rebindDelegation): a sub-agent (any
4
- // depth) and a task session a bot created carry `delegation` on their session
5
- // record —
6
- // { rootSessionId, rootAgentId, via, run?, path }
7
- // `rootSessionId` is the top-level conversation (a bot's home chat, a room
8
- // member session, a coding Lead's session); `rootAgentId` the bot or Mixy it
9
- // acts for ('' under a coding Lead); `via` how that root was acting when the
10
- // work started: 'home' (the bot's own attended chat), 'room', 'run' (a
11
- // schedule/webhook turn; `run` = { kind, name }) or 'coding'; `path` the steps
12
- // from the root down ({ kind: 'task' } | { kind: 'agent', agent }).
4
+ // depth) carries `delegation` on its session record —
5
+ // { rootSessionId, via, path }
6
+ // `rootSessionId` is the top-level conversation the work started in; `via`
7
+ // says what it is: 'coding' (a coding Lead's session or a task session a bot
8
+ // created) or 'bot' (a bot's own home chat or room member session); either way
9
+ // the agents under it mirror their ordinary approval cards into that root, but
10
+ // a 'bot' root's sub-agents are no bot: they ask no bot approval and use no
11
+ // bot grant. `path` is the agent steps from the root down
12
+ // ({ kind: 'agent', agent }).
13
+ // A task session a bot created is an ordinary coding Lead session: top-level,
14
+ // never delegated from the bot, and its sub-agents are rooted at it.
13
15
  // It is copied from the parent's record (or bound from the parent itself when
14
16
  // the parent is top-level) by the host at creation — never from tool input —
15
- // so no parent walk happens at gate time. It follows the session that drives
16
- // the work: when a session sends an existing worker (or task session) a new
17
- // request, the record is re-bound to what that sender is doing now, so work an
18
- // unattended run started asks when the attended chat later reuses it, and the
19
- // reverse. A background wake of the worker is no new request and keeps it. A
20
- // record from before this field existed is
21
- // backfilled once, from its parent by the same rules (backfillOf), and kept on
22
- // the in-memory record, which its next save persists. A session that has a
23
- // parent link but no valid or derivable root is UNRESOLVED: the gate fails
24
- // closed on it. `via: 'unknown'` — a legacy task session that names its bot
25
- // but not where it was created: acts as that bot, never remembers.
26
- import { agentHomeSessionId, agentIdOfHomeSession } from '../agent-home.mjs';
17
+ // so no parent walk happens at gate time. When a session sends an existing
18
+ // worker a new request, the record is re-bound to that sender's root. A
19
+ // record from before this field existed, or one stored in an older shape (a
20
+ // task step in its path, a home/room/run/unknown `via`), is derived again from
21
+ // its parent by the same rules (backfillOf) and kept on the in-memory record,
22
+ // which its next save persists. A session that has a parent link but no valid
23
+ // or derivable root has no delegation (it is no bot's own session either).
24
+ // Work a schedule/webhook run started (a sub-agent, a task session) carries the
25
+ // `run` it was started under on its record, stamped at creation from its
26
+ // creator (never from creation options or tool input) and re-stamped, whether
27
+ // or not a root resolves, when another session sends it work (`run: null` =
28
+ // attended). A record from before the stamp keeps the run it recorded (an old
29
+ // `via: 'run'` delegation, an `agent:<bot>@run` task source, or its parent's
30
+ // run when it recorded no delegation): that is stamped before its delegation is
31
+ // derived again, which would drop it.
32
+ import { agentIdOfHomeSession } from '../agent-home.mjs';
27
33
  import { roomMemberOfSession } from '../agent-room.mjs';
28
34
  import { loadSession } from '../store/load-session.mjs';
29
35
  import { _liveSessions } from '../store/live-state.mjs';
@@ -31,8 +37,7 @@ import { saveSessionAsync } from '../store/save-worker.mjs';
31
37
  import { isTurnInFlight, runtimeState } from '../manager/runtime-state.mjs';
32
38
  import { unattendedApprovalPort } from './unattended-port.mjs';
33
39
 
34
- const VIA = new Set(['home', 'room', 'run', 'coding', 'unknown']);
35
- const AGENT_ID = /^[A-Za-z0-9_-]+$/;
40
+ const VIA = new Set(['coding', 'bot']);
36
41
  const MAX_PATH = 32;
37
42
  const clean = (value) => (typeof value === 'string' ? value.trim() : '');
38
43
  const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
@@ -40,38 +45,91 @@ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Arra
40
45
  const agentOwned = (record) =>
41
46
  clean(record?.owner).toLowerCase() === 'agent' || clean(record?.visibility).toLowerCase() === 'agent-only';
42
47
 
43
- /** The session that started `record` (its spawn parent, or the chat that
44
- * created a task session), or '' for a top-level session. */
48
+ /** The session that spawned `record`, or '' for a top-level session (a task
49
+ * session a bot created is top-level, like any coding Lead). */
45
50
  function parentOf(record) {
46
- if (agentOwned(record)) return clean(record.parentSessionId) || clean(record.ownerSessionId);
47
- return clean(record?.taskOrigin?.sessionId);
51
+ return agentOwned(record) ? clean(record.parentSessionId) || clean(record.ownerSessionId) : '';
48
52
  }
49
53
 
50
54
  /** The step a new session adds to its parent's path. */
51
- function stepOf(record) {
52
- return agentOwned(record) ? { kind: 'agent', agent: clean(record.agent) } : { kind: 'task' };
53
- }
55
+ const stepOf = (record) => ({ kind: 'agent', agent: clean(record.agent) });
54
56
 
57
+ /** A recorded delegation, or null when it is malformed or carries a step that
58
+ * is not an agent (a stored task step: derived again, never trusted). */
55
59
  function validDelegation(value) {
56
60
  if (!isRecord(value) || !clean(value.rootSessionId) || !VIA.has(value.via)) return null;
57
61
  if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH) return null;
58
- if (value.via === 'run' && !isRecord(value.run)) return null;
62
+ if (!value.path.every((step) => step?.kind === 'agent')) return null;
59
63
  return value;
60
64
  }
61
65
 
62
- /** A failed rebind leaves `{ unresolved: true }` instead of a delegation: an
63
- * explicit "no root", so the legacy backfill cannot derive one again from a
64
- * parent that still holds a run. */
65
- const UNRESOLVED = Object.freeze({ unresolved: true });
66
- const isUnresolvedMark = (value) => isRecord(value) && value.unresolved === true;
67
-
68
66
  /** The recorded delegation of `record`, else its legacy backfill; null when
69
- * it has none or was explicitly left unresolved. */
67
+ * it has none and none can be derived. */
70
68
  function delegationOfRecord(id, record, depth) {
71
- if (isUnresolvedMark(record.delegation)) return null;
69
+ stampRun(id, record);
72
70
  return validDelegation(record.delegation) || backfillOf(id, record, depth);
73
71
  }
74
72
 
73
+ const RUN_TASK_SOURCE = /^agent:([A-Za-z0-9_-]+)@run$/;
74
+ const runStamp = (kind, agentId, name) =>
75
+ clean(agentId)
76
+ ? { kind: kind === 'webhook' ? 'webhook' : 'schedule', agentId: clean(agentId), name: String(name || '') }
77
+ : null;
78
+
79
+ /** The run a record from before the `run` stamp was started by, from what it
80
+ * recorded itself: an old `via: 'run'` delegation, a host-set `@run` task
81
+ * source, else — a sub-agent that recorded no delegation — its parent's run. */
82
+ function legacyRunOf(id, record, depth) {
83
+ const old = record.delegation;
84
+ if (isRecord(old) && old.via === 'run' && isRecord(old.run)) {
85
+ return runStamp(old.run.kind, old.rootAgentId || agentIdOfHomeSession(old.rootSessionId), old.run.name);
86
+ }
87
+ const taskBot =
88
+ !agentOwned(record) && clean(record.sourceType) === 'lead'
89
+ ? RUN_TASK_SOURCE.exec(clean(record.sourceName))?.[1]
90
+ : '';
91
+ if (taskBot) return runStamp('schedule', taskBot, '');
92
+ const parent = parentOf(record);
93
+ if (!parent || parent === id || isRecord(old) || depth >= MAX_PATH) return null;
94
+ const parentRecord = loadRecord(parent);
95
+ return parentRecord ? stampRun(parent, parentRecord, depth + 1) : null;
96
+ }
97
+
98
+ /** The `run` of `record`: its stamp (`null` = attended), else the run it
99
+ * recorded before stamps existed, kept on the live record. */
100
+ function stampRun(id, record, depth = 0) {
101
+ if (record.run !== undefined) return isRecord(record.run) ? record.run : null;
102
+ const run = legacyRunOf(id, record, depth);
103
+ if (run && _liveSessions.get(id) === record) record.run = run;
104
+ return run;
105
+ }
106
+
107
+ /** The run stamped on session `sessionId` (human-gate/delegation.mjs), or null. */
108
+ export function runStampOf(sessionId) {
109
+ const id = clean(sessionId);
110
+ const record = id ? loadRecord(id) : null;
111
+ return record ? stampRun(id, record) : null;
112
+ }
113
+
114
+ /** The schedule/webhook run `sessionId` is, or inherited from the session that
115
+ * started it, as `{ kind, agentId, name }`; null otherwise (a room member's
116
+ * own session is no run here). */
117
+ function runOfSession(sessionId) {
118
+ const run = unattendedApprovalPort()?.runOf?.(sessionId);
119
+ return run && run.kind !== 'room' ? { kind: run.kind, agentId: run.agentId, name: String(run.name || '') } : null;
120
+ }
121
+
122
+ /**
123
+ * The `run` to stamp on a session being created with `opts`: the run its
124
+ * creator (the spawning session, or the chat a task session was created from)
125
+ * is or inherited, else null. Work a run started stays the run's, however long
126
+ * ago the run ended; it is read from the creator, never from tool input.
127
+ */
128
+ export function runForNewSession(opts = {}) {
129
+ const creator = parentOf(opts) || clean(opts.taskOrigin?.sessionId);
130
+ return creator && creator !== clean(opts.id) ? runOfSession(creator) : null;
131
+ }
132
+
75
133
  function loadRecord(sessionId) {
76
134
  try {
77
135
  return loadSession(sessionId);
@@ -80,85 +138,35 @@ function loadRecord(sessionId) {
80
138
  }
81
139
  }
82
140
 
83
- /** How a top-level session `sessionId` is acting right now (no record needed
84
- * for a home or room member id, which name their agent themselves). */
85
- function bindingOf(sessionId, record, { legacy = false } = {}) {
86
- // A legacy record is classified from what was recorded, never from what the
87
- // parent is doing now (a run going or ended since says nothing about it).
88
- const run = legacy ? null : unattendedApprovalPort()?.runOf?.(sessionId);
89
- if (run && run.kind !== 'room') {
90
- return { rootAgentId: clean(run.agentId), via: 'run', run: { kind: run.kind, name: String(run.name || '') } };
91
- }
92
- const member = roomMemberOfSession(sessionId);
93
- if (member) return { rootAgentId: member.agentId, via: 'room' };
94
- const home = agentIdOfHomeSession(sessionId);
95
- if (home) return { rootAgentId: home, via: 'home' };
96
- if (!record || agentOwned(record)) return null;
97
- // A Lead with host-set bot provenance (session-options taskOriginSuffix) is
98
- // a bot's task session, never coding work: its root is that bot's chat.
99
- const source = sourceTaskOf(record);
100
- if (source === false) return null;
101
- if (source) {
102
- const task = { rootSessionId: agentHomeSessionId(source.bot), rootAgentId: source.bot, task: true };
103
- if (source.from === 'home') return { ...task, via: 'home' };
104
- if (source.from === 'run') return { ...task, via: 'run', run: { kind: 'schedule', name: '' } };
105
- if (source.from === undefined) return { ...task, via: 'unknown' };
106
- return null; // a room (which one is not recorded) or anything else: ambiguous
107
- }
108
- return { rootAgentId: '', via: 'coding' };
141
+ /** What a top-level session `sessionId` is to a child: a bot's own home chat
142
+ * or room member session ('bot', no record needed, the id names its agent),
143
+ * a record-bearing coding session ('coding'), else nothing. */
144
+ function viaOf(sessionId, record) {
145
+ if (roomMemberOfSession(sessionId) || agentIdOfHomeSession(sessionId)) return 'bot';
146
+ return record && !agentOwned(record) ? 'coding' : null;
109
147
  }
110
148
 
111
- /** A Lead's host-set bot provenance `agent:<bot>[@from]`: `{ bot, from }`,
112
- * null when it has none, false when it is malformed. */
113
- function sourceTaskOf(record) {
114
- const source = clean(record?.sourceName);
115
- if (agentOwned(record) || clean(record?.sourceType) !== 'lead' || !source.startsWith('agent:')) return null;
116
- const [bot, from, ...rest] = source.slice(6).split('@');
117
- return AGENT_ID.test(bot) && !rest.length ? { bot, from } : false;
149
+ /** The root of a top-level `parent` for a child taking `step`. */
150
+ function rootFrom(parent, record, step) {
151
+ const via = viaOf(parent, record);
152
+ return via ? { rootSessionId: parent, via, path: [step] } : null;
118
153
  }
119
154
 
120
- /** The root of a top-level `parent` for a child taking `step`. */
121
- function rootFrom(parent, record, step, options) {
122
- const binding = bindingOf(parent, record, options);
123
- if (!binding) return null;
124
- const { task, ...root } = binding;
125
- return { rootSessionId: parent, ...root, path: task ? [{ kind: 'task' }, step] : [step] };
126
- }
127
-
128
- /** The delegation a legacy `record` (id `id`, a parent link, none recorded)
129
- * is given, derived now from what was recorded — its own host-set `@run`
130
- * provenance, else its parent's record, which must exist — and kept on the
131
- * live record; null when it cannot be derived. Never `via: 'home'`: whether
132
- * an old record was started by the attended chat (or by a run that has
133
- * ended since) is not recorded, so home ancestry becomes 'unknown' — that
134
- * bot, never remembered. Only a session created with its `delegation` can be
135
- * 'home'. */
155
+ /** The delegation a legacy `record` (id `id`, a parent link, none valid
156
+ * recorded) is given, derived now from its parent, and kept on the live
157
+ * record; null when it cannot be derived. */
136
158
  function backfillOf(id, record, depth = 0) {
137
159
  const parent = parentOf(record);
138
160
  if (!parent || parent === id || depth >= MAX_PATH) return null;
139
- const own = sourceTaskOf(record);
161
+ const parentRecord = loadRecord(parent);
162
+ const step = stepOf(record);
140
163
  let delegation = null;
141
- if (own && own.from === 'run') {
142
- // A run's task session stays the run's, whatever its origin chat is doing now.
143
- delegation = {
144
- rootSessionId: agentHomeSessionId(own.bot),
145
- rootAgentId: own.bot,
146
- via: 'run',
147
- run: { kind: 'schedule', name: '' },
148
- path: [{ kind: 'task' }],
149
- };
164
+ if (parentOf(parentRecord)) {
165
+ const inherited = delegationOfRecord(parent, parentRecord, depth + 1);
166
+ if (!inherited || inherited.path.length >= MAX_PATH) return null;
167
+ delegation = { ...inherited, path: [...inherited.path, step] };
150
168
  } else {
151
- const parentRecord = loadRecord(parent);
152
- if (!parentRecord) return null;
153
- const step = stepOf(record);
154
- if (parentOf(parentRecord)) {
155
- const inherited = delegationOfRecord(parent, parentRecord, depth + 1);
156
- if (!inherited || inherited.path.length >= MAX_PATH) return null;
157
- delegation = { ...inherited, path: [...inherited.path, step] };
158
- } else {
159
- delegation = rootFrom(parent, parentRecord, step, { legacy: true });
160
- }
161
- if (delegation?.via === 'home') delegation = { ...delegation, via: 'unknown' };
169
+ delegation = rootFrom(parent, parentRecord, step);
162
170
  }
163
171
  if (delegation && _liveSessions.get(id) === record) record.delegation = delegation;
164
172
  return delegation;
@@ -166,9 +174,9 @@ function backfillOf(id, record, depth = 0) {
166
174
 
167
175
  /**
168
176
  * The `delegation` to record on a session being created with `opts` (its
169
- * owner/visibility, parentSessionId/ownerSessionId, taskOrigin and agent), or
177
+ * owner/visibility, parentSessionId/ownerSessionId and agent), or
170
178
  * null: none for a top-level session, none for one whose parent's root cannot
171
- * be established (the record then stays unresolved and the gate fails closed).
179
+ * be established (it is no bot's session: nothing is asked or mirrored for it).
172
180
  */
173
181
  export function delegationForNewSession(opts = {}) {
174
182
  const parent = parentOf(opts);
@@ -189,42 +197,44 @@ function delegationUnder(parent, step) {
189
197
  }
190
198
 
191
199
  /**
192
- * Session `callerSessionId` sends the delegated session `sessionId` new work
193
- * (`agent send`, a spawn of its tag, a task-session send): from now on it
194
- * acts as that caller does — the delegation it would have been created with
195
- * under the caller — and the record is saved, so a restart keeps it. A worker
196
- * a run started is attended when the chat sends it work, and the other way
197
- * round; only the requests of the session that sends it count, never the
198
- * session that created it. A turn already running keeps its attended status
199
- * against a run (the run's request queues behind it and asks): it is never
200
- * loosened mid-turn. Resolves true when the record changed; false when
201
- * nothing applies (no record, not delegated, no or the same caller, the same
202
- * delegation) or the running turn kept its status. A caller whose root cannot
203
- * be established leaves the record without one, which the gate fails closed on.
200
+ * Session `callerSessionId` sends the sub-agent or task session `sessionId`
201
+ * new work (`agent send`, a spawn of its tag, a task-session send): from now
202
+ * on it is run-stamped as it would have been created under the caller, and a
203
+ * sub-agent is rooted under the caller's root when one can be established; the
204
+ * record is saved, so a restart keeps it — work an unattended run started is
205
+ * attended when the chat later reuses it, and the reverse. The run is passed on
206
+ * whether or not a root resolves. A turn already running is never made a run
207
+ * mid-turn. Resolves true when the record changed; false when nothing applies
208
+ * (no record, neither a sub-agent nor a task session, no or the same caller,
209
+ * nothing different).
204
210
  */
205
211
  export async function rebindDelegation(sessionId, callerSessionId) {
206
212
  const id = clean(sessionId);
207
213
  const caller = clean(callerSessionId);
208
214
  const record = id ? loadRecord(id) : null;
209
- if (!record || record.closed === true || !caller || caller === id || !parentOf(record)) return false;
210
- const current = validDelegation(record.delegation);
211
- const next = delegationUnder(caller, stepOf(record));
212
- if (next ? JSON.stringify(current) === JSON.stringify(next) : isUnresolvedMark(record.delegation)) return false;
213
- if (next?.via === 'run' && current?.via !== 'run' && isTurnInFlight(runtimeState.get(id))) return false;
214
- record.delegation = next || { ...UNRESOLVED };
215
+ if (!record || record.closed === true || !caller || caller === id) return false;
216
+ const delegated = Boolean(parentOf(record));
217
+ if (!delegated && !record.taskOrigin) return false;
218
+ const next = delegated ? delegationUnder(caller, stepOf(record)) : null;
219
+ const run = runOfSession(caller);
220
+ const current = stampRun(id, record);
221
+ const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
222
+ const reroot = Boolean(next) && !same(validDelegation(record.delegation), next);
223
+ if (!reroot && same(current, run)) return false;
224
+ if (run && !current && isTurnInFlight(runtimeState.get(id))) return false;
225
+ if (reroot) record.delegation = next;
226
+ if (run || current) record.run = run; // null = attended: legacy provenance no longer applies
215
227
  await saveSessionAsync(record, { expectedGeneration: record.generation });
216
228
  return true;
217
229
  }
218
230
 
219
- /** `{ state: 'top' }` for a top-level session (or one with no record),
220
- * `{ state: 'resolved', ...delegation }` for a delegated one, `{ state:
221
- * 'unresolved' }` for one with a parent link and no valid recorded root. */
231
+ /** `{ state: 'resolved', ...delegation }` for a sub-agent whose root is known,
232
+ * else `{ state: 'top' }`: a top-level session, one with no record, or one
233
+ * with a parent link and no derivable root (no bot's session either). */
222
234
  export function delegationOf(sessionId) {
223
235
  const id = clean(sessionId);
224
236
  const record = id ? loadRecord(id) : null;
225
- if (!record) return { state: 'top' };
226
- const parent = parentOf(record);
227
- if (!parent) return { state: 'top' };
228
- const delegation = parent === id ? null : delegationOfRecord(id, record);
229
- return delegation ? { state: 'resolved', ...delegation } : { state: 'unresolved' };
237
+ const parent = record ? parentOf(record) : '';
238
+ const delegation = parent && parent !== id ? delegationOfRecord(id, record) : null;
239
+ return delegation ? { state: 'resolved', ...delegation } : { state: 'top' };
230
240
  }
@@ -9,10 +9,12 @@
9
9
  // actingBotId); an ordinary coding session's outbound call passes with the
10
10
  // hook decision unchanged. Other surfaces (group rooms) call it with their own
11
11
  // decision to get the same outbound rule. After the user answered,
12
- // recordHumanDecision stores a "don't ask again" grant and the audit line. Unattended runs (schedule, webhook, room member) register a
13
- // port here (session-runtime/held-requests.mjs) that turns their asks into
14
- // held requests instead of an approval card.
15
- import { MIXY_AGENT_ID, agentHomeSessionId, agentIdOfHomeSession } from '../agent-home.mjs';
12
+ // recordHumanDecision stores a "don't ask again" grant and the audit line.
13
+ // Schedule and webhook runs never ask (run-policy.mjs): ordinary actions are
14
+ // allowed, payments and permanent deletes denied. Group-room members register
15
+ // a port here (session-runtime/held-requests.mjs) that turns their asks into
16
+ // held requests when no approval card is attached.
17
+ import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../agent-home.mjs';
16
18
  import { serverConnector } from '../../mcp/server-connectors.mjs';
17
19
  import { readAgentDefinitionKind } from '../../../../shared/agent-kind.mjs';
18
20
  import { isContactAgentId } from '../../../../shared/agent-route-config.mjs';
@@ -37,8 +39,8 @@ export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action
37
39
  * payments and deletes never. */
38
40
  const outboundRemember = (category) => (isRememberableAction(category) ? ['once', 'agent'] : ['once']);
39
41
 
40
- // The unattended runs' port lives in unattended-port.mjs (delegation.mjs reads
41
- // it when a session is created); its setter and getter are re-exported here.
42
+ // The unattended runs' port lives in unattended-port.mjs; its setter and
43
+ // getter are re-exported here.
42
44
  export { setUnattendedApprovalPort, unattendedApprovalPort };
43
45
 
44
46
  /** The agent a session is bound to by itself: its unattended run's owner, a
@@ -49,36 +51,24 @@ function ownBoundAgentId(sessionId) {
49
51
  return roomMemberOfSession(sessionId)?.agentId || agentIdOfHomeSession(sessionId) || '';
50
52
  }
51
53
 
52
- /** The agent a session is bound to: its own binding, else — a sub-agent at
53
- * any depth, a task session a bot created — the root recorded on it when it
54
- * was created (delegation.mjs); '' for a coding Lead and every agent under
55
- * it, and for a delegated session whose root is unknown (see isUnresolved). */
56
- function boundAgentId(sessionId) {
57
- const own = ownBoundAgentId(sessionId);
58
- if (own) return own;
59
- const delegation = delegationOf(sessionId);
60
- return delegation.state === 'resolved' ? delegation.rootAgentId : '';
61
- }
62
-
63
- /** A delegated session whose root could not be established: it may act for a
64
- * bot, so the gate fails closed (outbound asks, nothing is remembered). */
65
- const isUnresolved = (sessionId) => !ownBoundAgentId(sessionId) && delegationOf(sessionId).state === 'unresolved';
66
-
67
54
  /** The agent a session acts for: its unattended run's owner, a room member,
68
- * a home thread's agent, else Mixy. */
55
+ * a home thread's agent, else Mixy. Work a schedule/webhook run started (a
56
+ * sub-agent, a task session) acts for the run's owner; a chat's sub-agent, a
57
+ * chat-created task session and a coding Lead are bound to nothing. */
69
58
  export function agentIdForSession(sessionId) {
70
- return boundAgentId(sessionId) || MIXY_AGENT_ID;
59
+ return ownBoundAgentId(sessionId) || MIXY_AGENT_ID;
71
60
  }
72
61
 
73
62
  /** The bot a session acts for, or '' when none: Mixy in her own home thread
74
63
  * (or her unattended run / room member session), or a contact agent (`kind:
75
64
  * bot`, isContactAgentId) in its home thread, room member session or
76
- * unattended run — or any session delegated from one of those (a sub-agent,
77
- * a task session the bot created, nested at any depth), which acts as that
78
- * root bot. A coding Lead (also id 'lead'), its workflow agents and their
79
- * sub-agent sessions are bound to nothing here, so they are no bot. */
80
- function actingBotId(sessionId) {
81
- const id = boundAgentId(sessionId);
65
+ * unattended run. Work a schedule/webhook run started (a sub-agent, a task
66
+ * session) resolves to the run's bot (ownBoundAgentId → runOf). A coding Lead
67
+ * (also id 'lead'), a task session a bot created in a chat, their workflow
68
+ * agents and every sub-agent of a chat (also a bot's) are bound to nothing
69
+ * here, so they are no bot. */
70
+ export function actingBotId(sessionId) {
71
+ const id = ownBoundAgentId(sessionId);
82
72
  if (!id) return '';
83
73
  if (id === MIXY_AGENT_ID) return id;
84
74
  const kind = readAgentDefinitionKind({ dataDir: resolvePluginData(), shippedRoot: mixdogRoot(), id });
@@ -92,44 +82,24 @@ function actingBotId(sessionId) {
92
82
  * entry: none is offered to Mixy, a Lead or a workflow agent. */
93
83
  export function actingContactBotId(sessionId) {
94
84
  const id = actingBotId(sessionId);
95
- // Only where a remembering answer can be given in the bot's OWN home thread
96
- // (rememberSurfaceOf). A room member, an unattended run (a schedule, a
97
- // webhook) and work they started act for the bot in a session that is not its chat.
98
- return id && id !== MIXY_AGENT_ID && rememberSurfaceOf(sessionId) === agentHomeSessionId(id) ? id : '';
85
+ return id && id !== MIXY_AGENT_ID ? id : '';
99
86
  }
100
87
 
101
- /** The one session where an answer to `sessionId`'s request may be
102
- * remembered ("don't ask again", "always allow"): the bot's home chat when
103
- * the request lives there, or surfaces there because the work was started
104
- * from that attended chat; '' otherwise (a room, a run, a coding Lead, an
105
- * unresolved delegation). The approval queue honours a remembering answer
106
- * only from that session (tui/session/tool-approval.mjs). */
107
- export function rememberSurfaceOf(sessionId) {
108
- // A schedule/webhook running in the home itself is not the attended chat.
109
- if (unattendedApprovalPort()?.runOf?.(sessionId)) return '';
110
- if (agentIdOfHomeSession(sessionId)) return sessionId;
111
- if (ownBoundAgentId(sessionId)) return '';
112
- const delegation = delegationOf(sessionId);
113
- return delegation.state === 'resolved' && delegation.via === 'home' ? delegation.rootSessionId : '';
114
- }
115
-
116
- // Chain tokens the desktop translates (ApprovalCard approvalChainLabel).
88
+ // Chain token the desktop translates (ApprovalCard approvalChainLabel).
117
89
  const LEAD_NAME = 'Lead';
118
- const TASK_SESSION_NAME = 'Task session';
119
90
 
120
- /** Where a delegated session's approval cards also surface, and who is
121
- * asking: `{ sessionId, chain }` — the top-level conversation the user
122
- * watches (the root bot's home chat, a room member session, a coding Lead's
123
- * session) and the names from there down to the asker ("사무관 › Worker",
124
- * "사무관 › Task session", "Lead › Reviewer"). A sub-agent at any depth and
125
- * a task session a bot created have one; null for a top-level session. */
91
+ /** Where a sub-agent's approval cards also surface, and who is asking:
92
+ * `{ sessionId, chain }` — the session it works under (a coding Lead, a
93
+ * bot-created task session, a bot's home chat or room member session) and
94
+ * the names from there down to the asker ("Lead › Reviewer", "사무관 ›
95
+ * Worker"). Only where the card shows: no bot identity, grant or remember
96
+ * scope follows from it. Null for a top-level session. */
126
97
  export function approvalSurface(sessionId) {
127
98
  const delegation = delegationOf(sessionId);
128
99
  if (delegation.state !== 'resolved') return null;
129
- const chain = [delegation.rootAgentId ? agentDisplayName(delegation.rootAgentId) : LEAD_NAME];
130
- for (const step of delegation.path) {
131
- chain.push(step.kind === 'agent' ? agentDisplayName(step.agent) || step.agent : TASK_SESSION_NAME);
132
- }
100
+ const bot = delegation.via === 'bot' ? ownBoundAgentId(delegation.rootSessionId) : '';
101
+ const chain = [bot ? agentDisplayName(bot) || bot : LEAD_NAME];
102
+ for (const step of delegation.path) chain.push(agentDisplayName(step.agent) || step.agent);
133
103
  return { sessionId: delegation.rootSessionId, chain };
134
104
  }
135
105
 
@@ -188,10 +158,7 @@ async function describeEmails(call, outbound, ctx) {
188
158
  export async function humanGateDecision(call, decision, ctx = {}) {
189
159
  const action = actionOf(decision);
190
160
  if (action === 'deny' || action === 'block') return { decision, request: null, gate: null };
191
- // Delegated, but its root is unknown: treated as a bot whose identity, grants
192
- // and remember surface are unknown — every outbound call asks, once only.
193
- const unresolved = isUnresolved(ctx.sessionId);
194
- const agentId = unresolved ? '' : agentIdForSession(ctx.sessionId);
161
+ const agentId = agentIdForSession(ctx.sessionId);
195
162
  const annotations = ctx.annotations ?? (await mcpAnnotations(call?.name, ctx.mcpScopeId));
196
163
  let classified = classifyOutboundCall({ name: call?.name, args: call?.args, annotations });
197
164
  const remoteDelete = isRemoteDeleteInRun({ name: call?.name, annotations }, classified);
@@ -215,7 +182,7 @@ export async function humanGateDecision(call, decision, ctx = {}) {
215
182
  if (remoteDelete && classified?.category !== 'delete' && classified?.category !== 'payment') {
216
183
  classified = classifyAsDelete({ name: call?.name, args: call?.args });
217
184
  }
218
- const outbound = classified && (unresolved || actingBotId(ctx.sessionId)) ? classified : null;
185
+ const outbound = classified && actingBotId(ctx.sessionId) ? classified : null;
219
186
  if (!outbound) {
220
187
  if (action !== 'ask') return { decision, request: null, gate: null };
221
188
  return {
@@ -239,14 +206,10 @@ export async function humanGateDecision(call, decision, ctx = {}) {
239
206
  },
240
207
  remember: ['once'],
241
208
  };
242
- // "Don't ask again" only where it can be answered in the bot's own chat.
243
- const rememberAt = unresolved ? '' : rememberSurfaceOf(ctx.sessionId);
244
- if (rememberAt && isRememberableAction(outbound.category)) {
245
- request.remember = outboundRemember(outbound.category);
246
- request.rememberAt = rememberAt;
247
- }
209
+ // "Don't ask again" is the bot's permission.
210
+ request.remember = outboundRemember(outbound.category);
248
211
  const store = ctx.store || (await humanGateStore());
249
- const mode = unresolved ? 'ask' : await store.modeFor({ agentId, action: outbound.category });
212
+ const mode = await store.modeFor({ agentId, action: outbound.category });
250
213
  const base = decision && typeof decision === 'object' ? decision : {};
251
214
  if (mode === 'block') {
252
215
  await store.recordAudit({ ...gate, decision: 'blocked', device: '' });
@@ -285,8 +248,8 @@ export async function recordHumanDecision(gate, approval, { store } = {}) {
285
248
  let decision = approval?.response || (approval?.approved === true ? 'approve' : 'deny');
286
249
  if (approval?.timedOut) decision = 'timeout';
287
250
  const device = typeof approval?.device === 'string' ? approval.device : '';
288
- // A scope the request did not offer (an answer from a place that may not
289
- // remember) stores nothing.
251
+ // A scope the request did not offer (a payment or permanent delete is only
252
+ // ever answered once) stores nothing.
290
253
  const offered = !Array.isArray(gate.remember) || gate.remember.includes(approval?.remember);
291
254
  if (approval?.approved === true && gate.action && offered) {
292
255
  await target.grant({ agentId: gate.agentId, action: gate.action, scope: approval.remember, device });
@@ -84,8 +84,6 @@ export function humanRequestFields(input = {}) {
84
84
  const fields = { remember: rememberScopes(input.remember) };
85
85
  // The agent the request acts for (its name on the card and the remembered grant).
86
86
  if (text(input.agentId)) fields.agentId = text(input.agentId);
87
- // The one session a remembering answer may come from (gate.mjs rememberSurfaceOf).
88
- if (text(input.rememberAt)) fields.rememberAt = text(input.rememberAt);
89
87
  if (kind === 'outbound' && isRecord(input.outbound)) {
90
88
  const outbound = input.outbound;
91
89
  const details = isRecord(outbound.details) ? outbound.details : {};
@@ -1,7 +1,7 @@
1
1
  // session/human-gate/run-policy.mjs
2
2
  // Schedule and webhook runs never ask. Saving a schedule or webhook is the
3
- // user's explicit grant for its runs (and for the work they start: sub-agents,
4
- // a bot's task sessions): outbound actions and ordinary tool approvals are
3
+ // user's explicit grant for its runs (and for the work they start: sub-agents):
4
+ // outbound actions and ordinary tool approvals are
5
5
  // allowed without a card, a held row or a push, inside the bot's own
6
6
  // permissions (a `block` is applied before any ask, gate.mjs). Payments and
7
7
  // permanent deletes (the action kinds that can never be remembered) are never
@@ -117,9 +117,9 @@ export function compactionSummaryPlan({
117
117
  return { mode, warm, steps };
118
118
  }
119
119
 
120
- // The summary request on one route: the conversation alone (earlier summary
121
- // and dialogue; tool output, skills and injections excluded), never the
122
- // cached context. Its input budget belongs to its OWN model, split into
120
+ // The summary request on one route: the conversation alone (earlier summary,
121
+ // dialogue, runtime conversation rows and one bounded line per tool call;
122
+ // skills and injections excluded), never the cached context. Its input budget belongs to its OWN model, split into
123
123
  // batches when the conversation is larger, whereas the rebuilt conversation
124
124
  // must fit the original session's target/window.
125
125
  function summarizeConversation(target, ctx) {
@@ -159,7 +159,6 @@ function summarizeConversation(target, ctx) {
159
159
  fast: target.fast,
160
160
  timeoutMs: compactPolicy.handoffTimeoutMs,
161
161
  force: true,
162
- filterOldHistoryForIngest: true,
163
162
  }
164
163
  );
165
164
  }
@@ -338,8 +337,13 @@ export async function runFreshContextCompact({
338
337
  } = {}) {
339
338
  const startedAt = Date.now();
340
339
  signal?.throwIfAborted();
341
- const { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens } =
342
- freshContextBudget({ compactPolicy, sessionRef, provider, compactBudgetTokens, messages });
340
+ const { contextWindow, hardBudget, conversationTokens, conversationThresholdTokens } = freshContextBudget({
341
+ compactPolicy,
342
+ sessionRef,
343
+ provider,
344
+ compactBudgetTokens,
345
+ messages,
346
+ });
343
347
  const summaryTriggered = conversationTokens >= conversationThresholdTokens;
344
348
  const buildOptions = freshContextBuildOptions(
345
349
  { compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
@@ -383,7 +387,9 @@ export async function runFreshContextCompact({
383
387
  compactBudgetTokens,
384
388
  hardBudget,
385
389
  contextWindow,
386
- conversationInput,
390
+ // What the summary is written from: the measured conversation plus one
391
+ // bounded line per tool call (conversationCompactionInput).
392
+ conversationInput: conversationCompactionInput(messages, { summary: true }),
387
393
  },
388
394
  plan
389
395
  );