mixdog 1.3.3 → 1.3.5

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 (210) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +15 -0
  3. package/README.md +11 -0
  4. package/package.json +3 -2
  5. package/scripts/embed-google-oauth-client.mjs +49 -0
  6. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  7. package/scripts/test.mjs +5 -0
  8. package/src/defaults/shipped-history.json +8 -0
  9. package/src/defaults/skills/browser-use/SKILL.md +21 -9
  10. package/src/defaults/skills/setup/SKILL.md +7 -2
  11. package/src/defaults/skills/setup/references/actions.md +82 -27
  12. package/src/defaults/skills/setup/references/surfaces.md +67 -34
  13. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  14. package/src/lib/keychain-cjs.cjs +359 -29
  15. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  16. package/src/rules/agent-mode/MIXY.md +12 -0
  17. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +18 -0
  19. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
  20. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  21. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  22. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  23. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  24. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  25. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  26. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  27. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
  28. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  35. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
  38. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  41. package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
  42. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  43. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  44. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  45. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  46. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  47. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  48. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  49. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  50. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  51. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  52. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  53. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  55. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  58. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  60. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  61. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  63. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  64. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
  65. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  69. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  71. package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
  72. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  75. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  78. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  83. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  84. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
  86. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
  87. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
  88. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
  90. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  91. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  92. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  93. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
  94. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
  96. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
  97. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  98. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  99. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  101. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  102. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  103. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  104. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  105. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  106. package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
  107. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  109. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
  110. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  111. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  112. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  113. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
  114. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  115. package/src/runtime/channels/lib/scheduler.mjs +109 -14
  116. package/src/runtime/channels/lib/schedules-db.mjs +49 -0
  117. package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
  118. package/src/runtime/channels/lib/worker-main.mjs +9 -0
  119. package/src/runtime/office/com/com-adapter.mjs +17 -1
  120. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  121. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  122. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  123. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  124. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  125. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  126. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  127. package/src/runtime/shared/agent-room-host.mjs +1 -1
  128. package/src/runtime/shared/bot-hops.mjs +62 -2
  129. package/src/runtime/shared/config.mjs +49 -22
  130. package/src/runtime/shared/connector-catalog.d.mts +4 -2
  131. package/src/runtime/shared/connector-catalog.mjs +16 -8
  132. package/src/runtime/shared/developer-options.mjs +32 -1
  133. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  134. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  135. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  136. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  137. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  138. package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
  139. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  140. package/src/runtime/shared/provider-accounts.mjs +224 -58
  141. package/src/runtime/shared/provider-api-key.mjs +19 -8
  142. package/src/runtime/shared/routine-trigger.mjs +42 -0
  143. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  144. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  145. package/src/runtime/shared/user-data-guard.mjs +30 -2
  146. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  147. package/src/session-runtime/agent-home-delivery.mjs +57 -13
  148. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  149. package/src/session-runtime/agent-task-sessions.mjs +292 -79
  150. package/src/session-runtime/agent-tool-routing.mjs +49 -4
  151. package/src/session-runtime/boot/begin.mjs +3 -2
  152. package/src/session-runtime/boot/tools.mjs +4 -0
  153. package/src/session-runtime/bot-chain.mjs +32 -0
  154. package/src/session-runtime/bot-status.mjs +7 -4
  155. package/src/session-runtime/channel-config-api.mjs +63 -17
  156. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  157. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  158. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  159. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
  160. package/src/session-runtime/mixy-heartbeat.mjs +807 -0
  161. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  162. package/src/session-runtime/owned-automation-run.mjs +33 -12
  163. package/src/session-runtime/provider-auth/login.mjs +7 -0
  164. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  165. package/src/session-runtime/routine-home-turn.mjs +33 -6
  166. package/src/session-runtime/schedule-session-run.mjs +7 -3
  167. package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
  168. package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
  169. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +11 -2
  170. package/src/session-runtime/services/agent-tool/render.mjs +3 -1
  171. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  172. package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
  173. package/src/session-runtime/services/channel-admin.mjs +66 -18
  174. package/src/session-runtime/services/channel-worker.mjs +1 -1
  175. package/src/session-runtime/services/provider-admin.mjs +128 -25
  176. package/src/session-runtime/services/schedule-access.mjs +14 -0
  177. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  178. package/src/session-runtime/session-create/session-options.mjs +7 -4
  179. package/src/session-runtime/settings-system-api.mjs +16 -1
  180. package/src/session-runtime/setup-tool/executor.mjs +49 -14
  181. package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
  182. package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
  183. package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
  184. package/src/session-runtime/task-session-route.mjs +99 -0
  185. package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
  186. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  187. package/src/session-runtime/tool-surface.mjs +4 -0
  188. package/src/session-runtime/usage-stats-api.mjs +1 -1
  189. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  190. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  191. package/src/session-runtime/workspace/transitions.mjs +10 -2
  192. package/src/standalone/daemon-session-control.mjs +88 -20
  193. package/src/standalone/daemon.mjs +8 -0
  194. package/src/standalone/session-protocol.mjs +5 -0
  195. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  196. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  197. package/src/standalone/session-service.mjs +13 -0
  198. package/src/standalone/session-tool-output.mjs +8 -5
  199. package/src/tui/app/model-options.mjs +2 -0
  200. package/src/tui/dist/index.mjs +335 -172
  201. package/src/tui/session/notice-surface.mjs +17 -1
  202. package/src/tui/session/session-api/integrations.mjs +1 -0
  203. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  204. package/src/tui/session/session-api/settings.mjs +14 -2
  205. package/src/tui/session/turn-stream.mjs +17 -1
  206. package/src/tui/session/turn.mjs +22 -2
  207. package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
  208. package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
  209. package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
  210. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
@@ -4,7 +4,15 @@ import { createAgentTaskSessions } from '../session-runtime/agent-task-sessions.
4
4
  import { assertWorktreeMutationAllowed } from '../session-runtime/services/worktree-access.mjs';
5
5
  import { createBotStatus, scanCatalog, scopedBot, sessionBotOf } from '../session-runtime/bot-status.mjs';
6
6
  import { handoffContent } from '../session-runtime/handoff-attachments.mjs';
7
- import { BOT_HOP_LIMIT_ERROR, MAX_BOT_HOPS } from '../runtime/shared/bot-hops.mjs';
7
+ import { checkEffort, readModelCatalog } from '../session-runtime/task-session-route.mjs';
8
+ import {
9
+ BOT_HOP_LIMIT_ERROR,
10
+ MAX_BOT_HOPS,
11
+ assertBotChainTarget,
12
+ botChainOf,
13
+ rememberSubmissionBotChain,
14
+ } from '../runtime/shared/bot-hops.mjs';
15
+ import { agentIdOfHomeSession } from '../runtime/agent/orchestrator/session/agent-home.mjs';
8
16
  import { rebindDelegation } from '../runtime/agent/orchestrator/session/human-gate/delegation.mjs';
9
17
  import { isAgentOnlySession } from '../runtime/agent/orchestrator/session/store-summary-visibility.mjs';
10
18
  import { readSessionSidebarState } from '../runtime/shared/session-sidebar-host.mjs';
@@ -19,7 +27,13 @@ const TARGET_ACTIONS = new Set(['status', 'read', 'send', 'cancel', 'delete']);
19
27
  const MUTATING_ACTIONS = new Set(['send', 'cancel', 'delete']);
20
28
  // The workspace methods `worktree` reaches on another session (all on the
21
29
  // session protocol's read/configure allowlists).
22
- const WORKTREE_METHODS = new Set(['getBranchMenu', 'requestWorktree', 'selectBranch', 'mergeWorkspace', 'removeWorktree']);
30
+ const WORKTREE_METHODS = new Set([
31
+ 'getBranchMenu',
32
+ 'requestWorktree',
33
+ 'selectBranch',
34
+ 'mergeWorkspace',
35
+ 'removeWorktree',
36
+ ]);
23
37
  // Why a send from a conversation that is no bot's reports nothing back.
24
38
  const NOT_BOT_REASON = 'only a bot conversation gets session results reported back; check that session for the result';
25
39
 
@@ -120,22 +134,25 @@ export function createSessionControl({
120
134
  }
121
135
  }
122
136
  // Scoped like bots (`callerScope`, bot-status.mjs): the human's sessions
123
- // list everything, a bot (or its unattended run) only its own sessions,
124
- // anything else the plain rows without previews. Sub-agent sessions (a
137
+ // list everything with previews, a bot (or its unattended run) lists the
138
+ // same rows (so it can find a session to send to) but only its own carry
139
+ // the bot, activity, preview and working state, anything else the plain
140
+ // rows without previews. Sub-agent sessions (a
125
141
  // parent finds its own through the agent tool) and archived ones (the
126
142
  // desktop's flag) only on request; the scan pages past the rows left
127
143
  // out, so a page is never short while more rows match.
128
144
  const own = scopedBot(context.callerScope);
129
145
  const archived = (await readSessionSidebarState())?.archived ?? {};
130
146
  const keep = (row) =>
131
- (!own || sessionBotOf(row) === own) &&
132
147
  (args.include_agents === true || !isAgentOnlySession(row)) &&
133
148
  (args.include_archived === true || !Object.hasOwn(archived, row.id));
134
149
  // Bounded: past 10000 rows scanned with fewer than `limit` matching, the
135
150
  // page ends (possibly empty) with a nextCursor to continue from.
136
151
  const scan = await scanCatalog(service, keep, { cursor: args.cursor || null, want: limit, maxRows: 10_000 });
137
152
  const sessions = scan.rows.map((row) => (Object.hasOwn(archived, row.id) ? { ...row, archived: true } : row));
138
- return sessionListOutput({ sessions, nextCursor: scan.nextCursor }, limit, { rich: own !== null });
153
+ return sessionListOutput({ sessions, nextCursor: scan.nextCursor }, limit, {
154
+ rich: own ? (row) => sessionBotOf(row) === own : own !== null,
155
+ });
139
156
  }
140
157
  if (action === 'send') {
141
158
  if (typeof args.prompt !== 'string' || !args.prompt.trim()) {
@@ -153,6 +170,15 @@ export function createSessionControl({
153
170
  // its member session is the turn that answers and reports.
154
171
  const room = roomIdOfSession(sessionId) ? await roomAddressee(sessionId, args) : null;
155
172
  if (room?.sessionId === caller) throw new Error('session: send must target another session');
173
+ // Work goes on to a bot (a room seat's, or a bot's home) only outside
174
+ // the caller's bot chain (`callerBotChain`, bot-chain.mjs), never back
175
+ // to the caller's own bot; the turn it starts carries that chain on.
176
+ const botChain = botChainOf(context.callerBotChain);
177
+ assertBotChainTarget(
178
+ room ? room.agentId : agentIdOfHomeSession(sessionId),
179
+ { chain: botChain, self: context.callerBotId || context.callerAgentId || '' },
180
+ 'session'
181
+ );
156
182
  // An agent conversation's send reports its turn back to the agent's
157
183
  // home: a task session it created by its stored origin, any other
158
184
  // session by an origin for this send only.
@@ -161,23 +187,58 @@ export function createSessionControl({
161
187
  const turnSessionId = room ? room.sessionId : sessionId;
162
188
  const from = { sessionId: caller, ...(context.callerAgentId ? { agentId: context.callerAgentId } : {}) };
163
189
  // The member's turn carries the send's hop (`transcriptMeta.hop`), so a
164
- // chain through the room counts on toward MAX_BOT_HOPS.
190
+ // chain through the room counts on toward MAX_BOT_HOPS; its bot chain is
191
+ // recorded under every submission id that turn may take.
165
192
  const submit = room
166
- ? async ({ id, onSettled, follow, transcriptMeta } = {}) => ({
167
- accepted:
168
- (await room.host.ask(sessionId, {
169
- agentId: room.agentId,
170
- prompt,
171
- from,
172
- ...(id ? { id } : {}),
173
- ...(transcriptMeta?.hop > 0 ? { hop: transcriptMeta.hop } : {}),
174
- ...(onSettled ? { onSettled } : {}),
175
- ...(follow ? { follow } : {}),
176
- })) === true,
177
- })
193
+ ? async ({ id, onSettled, follow, transcriptMeta } = {}) => {
194
+ rememberSubmissionBotChain(id, transcriptMeta?.botChain);
195
+ const followed = follow
196
+ ? (submissionId) => {
197
+ rememberSubmissionBotChain(submissionId, transcriptMeta?.botChain);
198
+ return follow(submissionId);
199
+ }
200
+ : null;
201
+ return {
202
+ accepted:
203
+ (await room.host.ask(sessionId, {
204
+ agentId: room.agentId,
205
+ prompt,
206
+ from,
207
+ ...(id ? { id } : {}),
208
+ ...(transcriptMeta?.hop > 0 ? { hop: transcriptMeta.hop } : {}),
209
+ ...(onSettled ? { onSettled } : {}),
210
+ ...(followed ? { follow: followed } : {}),
211
+ })) === true,
212
+ };
213
+ }
178
214
  : null;
179
215
  const addressed = room ? { to: room.agentId } : {};
180
216
  let origin = await agentTasks.reportOrigin(turnSessionId, context.callerAgentId, caller);
217
+ // `effort` is the menu's effort pick on the target: checked now against the
218
+ // levels of its model, applied just before the prompt is queued.
219
+ let applyEffort = async () => {};
220
+ if (args.effort !== undefined) {
221
+ if (room) throw new Error('session: effort applies to a session, not a group room');
222
+ // Any bot, attended or not (`callerBotId`), changes only the task
223
+ // sessions it created; reports go only to a bot conversation.
224
+ const botId = context.callerBotId || context.callerAgentId || '';
225
+ if (botId && (await agentTasks.reportOrigin(sessionId, botId, caller))?.target === 'session') {
226
+ throw new Error('session: a bot may set the effort only of the task sessions it created');
227
+ }
228
+ const target = (await service.readSession({ sessionId, transcriptItemLimit: 1 })).full;
229
+ if (!target) throw new Error(`session: ${sessionId} is unavailable`);
230
+ const level = checkEffort(args.effort, target, await readModelCatalog(service, caller));
231
+ // sessionOnly: the session's route changes, never the user's saved model settings.
232
+ applyEffort = async () => {
233
+ const applied = await service.configureSession(
234
+ { sessionId, action: 'setEffort', args: [level, { sessionOnly: true }] },
235
+ { revision: 1 }
236
+ );
237
+ if (applied?.value === false) {
238
+ throw new Error(`session: ${sessionId} is busy with another command; effort not changed`);
239
+ }
240
+ };
241
+ }
181
242
  // A send made in a bot-descended turn (`callerHop` > 0), or by a bot to
182
243
  // another session, extends the chain by one; a bot's own task session
183
244
  // only continues it. Refused past the limit, whatever the target.
@@ -189,13 +250,17 @@ export function createSessionControl({
189
250
  if (origin) {
190
251
  const hop = chained(origin.target === 'session' ? turnHop + 1 : turnHop);
191
252
  if (hop > 0) origin = { ...origin, hop };
253
+ if (botChain.length) origin = { ...origin, botChain };
192
254
  // The task session a bot created acts as the sending conversation does
193
255
  // from now on (attended chat or run); another's session keeps its own.
194
256
  if (origin.target !== 'session') await rebindDelegation(sessionId, caller);
257
+ await applyEffort();
195
258
  const sent = await agentTasks.send(turnSessionId, prompt, origin, submit);
196
259
  return { sessionId, ...addressed, ...sent, ...(hop > 0 ? { hop } : {}) };
197
260
  }
198
261
  const hop = turnHop > 0 ? chained(turnHop + 1) : 0;
262
+ await applyEffort();
263
+ const chainMeta = { ...(hop > 0 ? { hop } : {}), ...(botChain.length ? { botChain } : {}) };
199
264
  let accepted;
200
265
  if (submit) accepted = (await submit(hop > 0 ? { transcriptMeta: { hop } } : {})).accepted;
201
266
  else {
@@ -204,7 +269,7 @@ export function createSessionControl({
204
269
  const result = await service.submitSession({
205
270
  sessionId,
206
271
  prompt,
207
- options: { mode: 'prompt', transcriptMeta: { sender: 'lead', ...(hop > 0 ? { hop } : {}) } },
272
+ options: { mode: 'prompt', transcriptMeta: { sender: 'lead', ...chainMeta } },
208
273
  });
209
274
  accepted = result.accepted === true;
210
275
  }
@@ -256,6 +321,9 @@ export function createSessionControl({
256
321
  cwd: snapshot.cwd ?? null,
257
322
  provider: snapshot.provider ?? null,
258
323
  model: snapshot.model ?? null,
324
+ effort: snapshot.effort ?? null,
325
+ autoEffort: snapshot.autoEffort ?? null,
326
+ fast: snapshot.fast ?? null,
259
327
  busy: snapshot.busy === true,
260
328
  quotaResume: snapshot.quotaResume ?? null,
261
329
  };
@@ -1001,6 +1001,8 @@ async function main() {
1001
1001
  setAgentHomeLiveSurface({
1002
1002
  busy: (sessionId) => sessionService.liveSessionBusy(sessionId),
1003
1003
  notice: (sessionId, notice) => sessionService.pushLiveSessionNotice(sessionId, notice),
1004
+ // A bot's emoji reaction on a user message (message-reactions.mjs).
1005
+ react: (sessionId, messageId, reactions) => sessionService.setLiveMessageReactions(sessionId, messageId, reactions),
1004
1006
  // A task session's result (session create): the agent relays it in one
1005
1007
  // turn. Resolves false when the turn was refused, else `{ settled }`, the
1006
1008
  // turn's end status ('done' when it finished).
@@ -1027,6 +1029,12 @@ async function main() {
1027
1029
  (await sessionService.submitSession({ sessionId, prompt, options }))?.accepted === true,
1028
1030
  abort: (sessionId) => sessionService.abortSession({ sessionId }),
1029
1031
  });
1032
+ // Mixy's 30-minute heartbeat check (mixy-heartbeat.mjs), on an unref'd timer.
1033
+ import('../session-runtime/mixy-heartbeat.mjs')
1034
+ .then(({ startMixyHeartbeat }) =>
1035
+ startMixyHeartbeat({ getSessionService: () => sessionService, log: (line) => log(`heartbeat: ${line}`) })
1036
+ )
1037
+ .catch((error) => log(`heartbeat start failed: ${error?.message || error}`));
1030
1038
  // Results queued before a restart are posted now; results a finished run
1031
1039
  // could not queue (kept on its routine's record) are queued again.
1032
1040
  recoverScheduleResults().catch((error) => log(`routine results recovery failed: ${error?.message || error}`));
@@ -47,6 +47,8 @@ export const SESSION_READ_ACTIONS = Object.freeze([
47
47
  'getQuotaHistory',
48
48
  'getRecapSettings',
49
49
  'getSessionTitleSettings',
50
+ 'getHeartbeatSettings',
51
+ 'getReactionSettings',
50
52
  'getLeadAppearance',
51
53
  'getBotOrder',
52
54
  'getWebSearchRoute',
@@ -134,6 +136,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
134
136
  'deleteMediaAssets',
135
137
  'deleteSchedule',
136
138
  'saveCustomProvider',
139
+ 'saveCustomProviderKey',
137
140
  'removeCustomProvider',
138
141
  'updateCustomProviderModel',
139
142
  'setModelPreferences',
@@ -238,6 +241,8 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
238
241
  'setProgressHint',
239
242
  'setRecapEnabled',
240
243
  'setAiSessionTitles',
244
+ 'setHeartbeatEnabled',
245
+ 'setReactionsEnabled',
241
246
  'setLeadAppearance',
242
247
  'setBotOrder',
243
248
  'setRoute',
@@ -64,9 +64,10 @@ export function createAgentCancellation({
64
64
  if (typeof closeCanonical !== 'function') {
65
65
  throw new TypeError('session runtime must implement closeCanonicalSession');
66
66
  }
67
+ const wasRunning = descriptor.status === 'running';
67
68
  // The close clears the transcript: capture a running turn's transcript
68
69
  // first so the cancelled turn still hands off its partial output.
69
- if (descriptor.status === 'running') {
70
+ if (wasRunning) {
70
71
  try {
71
72
  const { messages } = await entry.runtime.readModelMessages(0);
72
73
  rememberCancelTranscript(id, messages);
@@ -77,7 +78,10 @@ export function createAgentCancellation({
77
78
  const closed = await closeCanonical.call(entry.runtime, reason, { tombstone: options.tombstone === true });
78
79
  if (closed !== true) throw new Error(`agent session ${id} could not be durably closed`);
79
80
  descriptor.closed = options.tombstone === true;
80
- descriptor.status = descriptor.closed ? 'closed' : 'cancelled';
81
+ // Only a close that stopped a running turn is a cancellation; closing a
82
+ // finished agent (cleanup after its handoff) keeps its last status.
83
+ if (descriptor.closed) descriptor.status = 'closed';
84
+ else if (wasRunning) descriptor.status = 'cancelled';
81
85
  descriptor.stage = descriptor.status;
82
86
  descriptor.updatedAt = Date.now();
83
87
  return true;
@@ -128,6 +128,12 @@ export function createSessionTurnCalls(ctx) {
128
128
  const accepted = await Promise.resolve(target.call(entry.runtime, intake.prompt, submissionOptions));
129
129
  if (accepted === true) {
130
130
  entry.reservedOnly = false;
131
+ if (isUserTurn(submissionOptions)) {
132
+ // The user is active: Mixy's due heartbeat check waits for quiet.
133
+ void import('../../../session-runtime/mixy-heartbeat.mjs')
134
+ .then(({ noteUserTurn }) => noteUserTurn())
135
+ .catch((error) => log(`heartbeat activity note failed: ${error?.message || error}`));
136
+ }
131
137
  if (isAgentHomeSessionId(id) && isUserTurn(submissionOptions)) {
132
138
  // The user spoke in this agent's home: its round trips with other
133
139
  // sessions count from zero again, so a capped thread wakes again.
@@ -274,6 +274,18 @@ export function createSessionService({
274
274
  return true;
275
275
  }
276
276
 
277
+ /** Show the reactions now on message `messageId` in the hosted runtime of
278
+ * `sessionId` and publish them to its views; false when no runtime hosts
279
+ * it or its live rows do not hold that message. */
280
+ function setLiveMessageReactions(sessionId, messageId, reactions) {
281
+ const owner = projection.sessionOwner(sessionId);
282
+ if (typeof owner?.runtime?.setMessageReactions !== 'function') return false;
283
+ if (!owner.runtime.setMessageReactions(messageId, reactions)) return false;
284
+ const step = projection.advance(owner);
285
+ if (step.changed) projection.publishStep(owner, step);
286
+ return true;
287
+ }
288
+
277
289
  function getStatus() {
278
290
  const busy = retention.liveBusyCount();
279
291
  let watched = 0;
@@ -322,6 +334,7 @@ export function createSessionService({
322
334
  liveSessionBusy,
323
335
  liveSessionAwaitingApproval,
324
336
  pushLiveSessionNotice,
337
+ setLiveMessageReactions,
325
338
  stop,
326
339
  startQuotaResume: () => quotaResume?.start(),
327
340
  runDueQuotaResumes: () => quotaResume?.tick() ?? Promise.resolve(),
@@ -13,20 +13,23 @@ const outputBytes = (value) => Buffer.byteLength(JSON.stringify(value), 'utf8');
13
13
 
14
14
  /** Keep catalog paging authoritative even when the output budget shortens a page.
15
15
  * `rich` adds the owning bot, last activity, latest line and working state
16
- * (never for a background caller: bot-status.mjs scopedBot). */
16
+ * (never for a background caller: bot-status.mjs scopedBot). `rich` may be a
17
+ * per-row predicate: a bot gets them only for its own sessions. */
17
18
  export function sessionListOutput(page, limit, { rich = true } = {}) {
18
19
  const raw = page.sessions;
19
20
  const sessions = raw.slice(0, limit).map((row) => {
21
+ // Decided first: a session that is not the caller's carries its real
22
+ // title only, never a preview standing in for it.
23
+ const own = typeof rich === 'function' ? rich(row) : rich;
24
+ const title = own ? row.title || row.preview : row.title;
20
25
  const base = {
21
26
  id: row.id,
22
- ...(row.title || row.preview
23
- ? { title: escapeHarnessTags(truncateToolText(row.title || row.preview, 160)) }
24
- : {}),
27
+ ...(title ? { title: escapeHarnessTags(truncateToolText(title, 160)) } : {}),
25
28
  ...(row.status ? { status: row.status } : {}),
26
29
  ...(row.cwd ? { cwd: row.cwd } : {}),
27
30
  ...(row.archived === true ? { archived: true } : {}),
28
31
  };
29
- if (!rich) return base;
32
+ if (!own) return base;
30
33
  // The bot it belongs to: its chat, room seat or the task's creator.
31
34
  const bot = sessionBotOf(row);
32
35
  const lastUsedAt = Number(row.lastUsedAt) || 0;
@@ -173,6 +173,7 @@ const PROVIDER_DISPLAY_NAMES = new Map([
173
173
  ['command-code-oauth', 'Command Code'],
174
174
  ['mimo-oauth', 'MiMo'],
175
175
  ['cursor-oauth', 'Cursor OAuth'],
176
+ ['devin-oauth', 'Devin'],
176
177
  ['antigravity-oauth', 'Antigravity OAuth'],
177
178
  ['openai', 'OpenAI API'],
178
179
  ['openai-api', 'OpenAI API'],
@@ -279,6 +280,7 @@ const PROVIDER_DISPLAY_RANKS = {
279
280
  'mimo-oauth': 34,
280
281
  'antigravity-oauth': 31,
281
282
  'cursor-oauth': 32,
283
+ 'devin-oauth': 32.5,
282
284
  'opencode-go-oauth': 34.5,
283
285
  openai: 40,
284
286
  'openai-api': 40,