mixdog 1.2.6 → 1.3.0

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 (246) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +19 -0
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/isolated-test-env.mjs +16 -0
  7. package/src/agents/reviewer/AGENT.md +4 -2
  8. package/src/agents/reviewer/agent.json +1 -1
  9. package/src/defaults/shipped-history.json +7 -0
  10. package/src/defaults/skills/browser-use/SKILL.md +5 -0
  11. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  12. package/src/defaults/skills/setup/references/actions.md +11 -4
  13. package/src/defaults/skills/setup-guide/SKILL.md +175 -0
  14. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  15. package/src/lib/rules-builder.cjs +55 -6
  16. package/src/rules/agent/AGENT.md +4 -0
  17. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  18. package/src/rules/agent-mode/MIXY.md +21 -0
  19. package/src/rules/agent-mode/ROUTINE.md +6 -0
  20. package/src/rules/lead/LEAD.md +7 -3
  21. package/src/rules/shared/70-delivery.md +3 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  23. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  24. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +182 -13
  25. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
  26. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -0
  27. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  29. package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
  30. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  33. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  34. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  36. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
  39. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
  40. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  41. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  42. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  43. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  44. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
  45. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  46. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  47. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +12 -0
  48. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  50. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  51. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -4
  52. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
  53. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -20
  54. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  55. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  56. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  57. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  58. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  59. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  60. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +162 -0
  61. package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -0
  62. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  63. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  64. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +165 -0
  66. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  67. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  68. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  69. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  70. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  72. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -1
  73. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
  74. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
  75. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  76. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  77. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +7 -0
  78. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  79. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
  80. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  83. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  84. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  85. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  86. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -5
  87. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -8
  88. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  89. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  90. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  91. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  92. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +24 -0
  93. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  94. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
  95. package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -0
  96. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  97. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
  98. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  99. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  100. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  101. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  102. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +44 -1
  103. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  105. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
  106. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
  107. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
  108. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
  109. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
  110. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
  111. package/src/runtime/browser-bridge/client.mjs +24 -0
  112. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  113. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  114. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  115. package/src/runtime/channels/lib/webhook.mjs +0 -1
  116. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  117. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  118. package/src/runtime/computer-bridge/client.mjs +28 -1
  119. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  120. package/src/runtime/connectors/google-mcp.mjs +446 -0
  121. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  122. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  123. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  124. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  125. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  126. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  127. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  128. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  129. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  130. package/src/runtime/memory/lib/memory.mjs +7 -1
  131. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  132. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  133. package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
  134. package/src/runtime/memory/tool-defs.mjs +5 -0
  135. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  136. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  137. package/src/runtime/shared/agent-avatar.mjs +320 -0
  138. package/src/runtime/shared/agent-route-config.d.mts +3 -0
  139. package/src/runtime/shared/agent-route-config.mjs +16 -0
  140. package/src/runtime/shared/atomic-file.mjs +14 -1
  141. package/src/runtime/shared/config.mjs +8 -1
  142. package/src/runtime/shared/connector-accounts.mjs +15 -0
  143. package/src/runtime/shared/connector-catalog.d.mts +52 -0
  144. package/src/runtime/shared/connector-catalog.mjs +230 -0
  145. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  146. package/src/runtime/shared/connector-triggers.mjs +43 -0
  147. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  148. package/src/runtime/shared/held-request-host.d.mts +12 -0
  149. package/src/runtime/shared/held-request-host.mjs +71 -0
  150. package/src/runtime/shared/plain-preview.d.mts +1 -0
  151. package/src/runtime/shared/plain-preview.mjs +28 -0
  152. package/src/runtime/shared/routine-limits.d.mts +11 -0
  153. package/src/runtime/shared/routine-limits.mjs +21 -0
  154. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  155. package/src/runtime/shared/routine-trigger.mjs +44 -0
  156. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  157. package/src/runtime/shared/slow-span.mjs +4 -12
  158. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  159. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  160. package/src/runtime/shared/turn-changes.mjs +0 -8
  161. package/src/runtime/shared/usage-readout-totals.mjs +56 -0
  162. package/src/runtime/shared/user-data-guard.mjs +114 -36
  163. package/src/session-runtime/agent-home-delivery.mjs +459 -0
  164. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  165. package/src/session-runtime/agent-room-delivery.mjs +92 -0
  166. package/src/session-runtime/agent-task-sessions.mjs +200 -0
  167. package/src/session-runtime/boot/apis.mjs +50 -18
  168. package/src/session-runtime/boot/facade.mjs +5 -8
  169. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  170. package/src/session-runtime/boot/tools.mjs +3 -0
  171. package/src/session-runtime/channel-config-api.mjs +53 -5
  172. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  173. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  174. package/src/session-runtime/held-requests.mjs +228 -0
  175. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  176. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  177. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  178. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +31 -2
  179. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  180. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  181. package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
  182. package/src/session-runtime/lifecycle/shared.mjs +6 -0
  183. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  184. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  185. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  186. package/src/session-runtime/model-route/set-route.mjs +14 -3
  187. package/src/session-runtime/owned-automation-run.mjs +229 -0
  188. package/src/session-runtime/resource-api.mjs +5 -0
  189. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  190. package/src/session-runtime/resource-skills-api.mjs +12 -0
  191. package/src/session-runtime/room-member-gate.mjs +48 -0
  192. package/src/session-runtime/routine-home-turn.mjs +156 -0
  193. package/src/session-runtime/routine-owner.mjs +48 -0
  194. package/src/session-runtime/schedule-session-run.mjs +30 -64
  195. package/src/session-runtime/services/agent-tool/helpers.mjs +10 -0
  196. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  197. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  198. package/src/session-runtime/services/channel-admin.mjs +93 -24
  199. package/src/session-runtime/services/memory-access.mjs +37 -0
  200. package/src/session-runtime/services/provider-admin.mjs +74 -39
  201. package/src/session-runtime/services/schedule-access.mjs +91 -0
  202. package/src/session-runtime/session-create/session-options.mjs +34 -7
  203. package/src/session-runtime/session-hooks.mjs +16 -8
  204. package/src/session-runtime/setup-tool/executor.mjs +68 -6
  205. package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
  206. package/src/session-runtime/setup-tool/settings-contract.mjs +8 -6
  207. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  208. package/src/session-runtime/skills-api.mjs +16 -1
  209. package/src/session-runtime/tool-policy-refresh.mjs +238 -29
  210. package/src/session-runtime/turn/session-ops.mjs +8 -1
  211. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
  212. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +3 -0
  213. package/src/session-runtime/webhook-session-run.mjs +49 -67
  214. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  215. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +556 -22
  216. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  217. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  218. package/src/standalone/daemon-agent-control.mjs +1 -8
  219. package/src/standalone/daemon-session-control.mjs +11 -2
  220. package/src/standalone/daemon.mjs +18 -4
  221. package/src/standalone/session-protocol.mjs +27 -0
  222. package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
  223. package/src/standalone/session-service.mjs +19 -0
  224. package/src/tui/app/slash-commands.mjs +20 -0
  225. package/src/tui/dist/index.mjs +164 -86
  226. package/src/tui/session/context-state.mjs +6 -1
  227. package/src/tui/session/notice-surface.mjs +35 -4
  228. package/src/tui/session/queue-helpers.mjs +2 -1
  229. package/src/tui/session/session-api/extensions.mjs +4 -0
  230. package/src/tui/session/session-api/intake/submission.mjs +5 -0
  231. package/src/tui/session/session-api/integrations.mjs +8 -0
  232. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  233. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  234. package/src/tui/session/session-api/routes.mjs +48 -3
  235. package/src/tui/session/session-flow/drain.mjs +12 -3
  236. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  237. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  238. package/src/tui/session/session-flow/steering.mjs +58 -24
  239. package/src/tui/session/tool-approval.mjs +26 -5
  240. package/src/tui/session/transcript-intake.mjs +6 -2
  241. package/src/tui/session/transcript-spill.mjs +5 -1
  242. package/src/tui/session/tui-steering-persist.mjs +65 -42
  243. package/src/tui/session/turn-live-call.mjs +56 -0
  244. package/src/tui/session/turn.mjs +22 -6
  245. package/src/session-runtime/automation-agents.mjs +0 -94
  246. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -2,6 +2,7 @@
2
2
  // message-array change detect, tool-kind classification, skills, hook overrides,
3
3
  // native tool-search payload parsing, bash-session routing, and approval.
4
4
  import { isMcpTool } from '../../mcp/client.mjs';
5
+ import { unattendedApprovalPort } from '../human-gate/gate.mjs';
5
6
  import { isBuiltinTool } from '../../tools/builtin.mjs';
6
7
  import { isInternalTool } from '../../internal-tools.mjs';
7
8
  import { loadSkillToolDependencies } from '../../runtime-core/skill-tool-loading.mjs';
@@ -207,15 +208,24 @@ export async function resolvePreToolAskApproval({
207
208
  toolCallId,
208
209
  askReason,
209
210
  toolApprovalHook,
211
+ request = null,
210
212
  }) {
211
213
  const name = String(toolName || 'tool');
212
214
  const reason = String(askReason || 'approval requested by hook').trim();
213
- if (typeof toolApprovalHook !== 'function') {
215
+ // Unattended runs never auto-approve and have no card: a schedule or webhook
216
+ // run always holds the request for its owner; a room member does when no
217
+ // approval UI is attached (session-runtime/held-requests.mjs).
218
+ const unattended = unattendedApprovalPort();
219
+ const run = unattended?.runOf?.(sessionId) || null;
220
+ const held = run && (run.kind !== 'room' || typeof toolApprovalHook !== 'function');
221
+ const ask = held ? unattended.hold : toolApprovalHook;
222
+ if (typeof ask !== 'function') {
214
223
  return { denial: formatMissingToolApprovalUiDenial(name, reason) };
215
224
  }
216
225
  let approval;
217
226
  try {
218
- approval = await toolApprovalHook({
227
+ approval = await ask({
228
+ ...(request || {}),
219
229
  name,
220
230
  args,
221
231
  cwd,
@@ -229,7 +239,7 @@ export async function resolvePreToolAskApproval({
229
239
  }
230
240
  if (!approvalGranted(approval)) {
231
241
  const detail = approvalReason(approval, reason || 'not approved');
232
- return { denial: `Error: tool "${name}" denied by hook: ${detail}` };
242
+ return { denial: `Error: tool "${name}" denied by hook: ${detail}`, approval };
233
243
  }
234
244
  return { approval };
235
245
  }
@@ -41,6 +41,7 @@ export function usageDeltaEvent({
41
41
  usage,
42
42
  sendTools,
43
43
  toolCalls = 0,
44
+ generationMs = 0,
44
45
  }) {
45
46
  return {
46
47
  sessionId,
@@ -56,6 +57,12 @@ export function usageDeltaEvent({
56
57
  deltaToolCalls: toolCalls,
57
58
  ...reasoningUsage(usage),
58
59
  deltaPrompt: usage.promptTokens || 0,
60
+ // First visible token → end of this response; 0 when the call was not
61
+ // timed (nothing visible streamed). Only timed output enters speed figures.
62
+ generationMs,
63
+ // A provider that cannot measure the prompt or its cache split says so;
64
+ // its zeros are unknowns, not readings (no cache hit can be derived).
65
+ promptKnown: usage.inputTokensKnown !== false && usage.cacheTokensKnown !== false,
59
66
  // Cache delta carried alongside input/output so live metrics reflect
60
67
  // the same token classes the terminal aggregate adds; additive —
61
68
  // callers that ignore these fields keep working.
@@ -186,6 +186,7 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
186
186
  beforeCount: historyMessages.length + 1,
187
187
  deferredToolDelta: userTurn.deferredToolDelta,
188
188
  goalReminder: userTurn.goalReminder,
189
+ workflowNotice: userTurn.workflowNotice,
189
190
  workspaceNotice: userTurn.workspaceNotice,
190
191
  setupNotice: userTurn.setupNotice,
191
192
  };
@@ -8,6 +8,7 @@ import { createAskSteeringDrain } from './ask-turn-steering-drain.mjs';
8
8
  import { updateSessionStage, markSessionStreamDelta } from './runtime-liveness.mjs';
9
9
  import { codexWireSendOpts } from './session-id.mjs';
10
10
  import { persistIterationMetrics } from './usage-metrics.mjs';
11
+ import { isAgentModeSession } from '../agent-mode.mjs';
11
12
 
12
13
  const optionalFn = (value) => (typeof value === 'function' ? value : undefined);
13
14
  const relay = (fn, ...args) => {
@@ -184,7 +185,8 @@ export function buildAgentLoopOptions({
184
185
  onCompactEvent: (event) => {
185
186
  checkpoint.refreshContextState();
186
187
  checkpoint.schedule(true);
187
- relay(askOpts?.onCompactEvent, event);
188
+ // Agent mode compacts silently: no compaction notice reaches the host.
189
+ if (!isAgentModeSession(session)) relay(askOpts?.onCompactEvent, event);
188
190
  },
189
191
  // Pre-send gauge sync. Defined only when a host listens so the loop
190
192
  // never computes a display number nobody reads.
@@ -19,6 +19,7 @@ import {
19
19
  snapshotPendingWorkspaceNotice,
20
20
  snapshotPendingSetupNotice,
21
21
  } from './prompt-utils.mjs';
22
+ import { snapshotPendingWorkflowNotice } from './workflow-switch-notice.mjs';
22
23
  import { codexWireSendOpts } from './session-id.mjs';
23
24
  import { _tryBridgeExplicitPrefetch } from './prefetch-bridge.mjs';
24
25
  import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
@@ -180,7 +181,11 @@ export async function buildUserTurn({
180
181
  const workspaceNotice = snapshotPendingWorkspaceNotice(session);
181
182
  // What preparing a new worktree linked, installed, ran, failed or skipped.
182
183
  const setupNotice = snapshotPendingSetupNotice(session);
184
+ // A mid-conversation workflow switch: the prompt and tools were rebuilt
185
+ // for the new workflow; this turn tells the model once.
186
+ const workflowNotice = snapshotPendingWorkflowNotice(session);
183
187
  const reminderBlock = [
188
+ workflowNotice?.content || '',
184
189
  workspaceNotice?.content || '',
185
190
  setupNotice?.content || '',
186
191
  currentTimeBlock ? `<system-reminder>\n# Current Time\n${currentTimeBlock}\n</system-reminder>` : '',
@@ -209,6 +214,7 @@ export async function buildUserTurn({
209
214
  historyMessages,
210
215
  deferredToolDelta,
211
216
  goalReminder,
217
+ workflowNotice,
212
218
  workspaceNotice,
213
219
  setupNotice,
214
220
  userTurnMessage,
@@ -9,6 +9,7 @@ import { persistedAssistantTranscriptMetadata } from '../../../../shared/transcr
9
9
  import { acknowledgePendingDeferredToolDelta } from '../../runtime-core/deferred-tool-delta.mjs';
10
10
  import { acknowledgePendingGoalReminder } from '../../runtime-core/goal-reminder.mjs';
11
11
  import { acknowledgePendingSetupNotice, acknowledgePendingWorkspaceNotice } from './prompt-utils.mjs';
12
+ import { acknowledgePendingWorkflowNotice } from './workflow-switch-notice.mjs';
12
13
  import {
13
14
  finalizePendingMessageDelivery,
14
15
  drainPendingMessages,
@@ -139,6 +140,7 @@ export async function commitAskTurn({ sessionId, opened, prepared, result, rawTr
139
140
  if (prepared.goalReminder) {
140
141
  acknowledgePendingGoalReminder(session, prepared.goalReminder.revision);
141
142
  }
143
+ if (prepared.workflowNotice) acknowledgePendingWorkflowNotice(session, prepared.workflowNotice.revision);
142
144
  if (prepared.workspaceNotice) {
143
145
  acknowledgePendingWorkspaceNotice(session, prepared.workspaceNotice.revision);
144
146
  }
@@ -40,14 +40,6 @@ export function currentPendingLifecycleToken(sessionId) {
40
40
  return pendingLifecycleToken(pendingSessionLifecycle(sessionId));
41
41
  }
42
42
 
43
- // Refusal verdict of the lifecycle a token was minted from (tombstoned or
44
- // unreadable) — exactly pendingLifecycleInvalidated(sessionId) for that same
45
- // read, so a caller holding a fresh token needs no second durable read.
46
- export function lifecycleTokenClosed(token) {
47
- const { source } = parseLifecycleToken(token);
48
- return source === 'closed' || source === 'unreadable';
49
- }
50
-
51
43
  // Parses a `source:generation` epoch token.
52
44
  function parseLifecycleToken(token) {
53
45
  const text = String(token);
@@ -207,29 +207,46 @@ function bp3SliceAt(text, heading) {
207
207
 
208
208
  /**
209
209
  * Replace the workspace-scoped tail of a BP3 core (composeSystemPrompt order:
210
- * …, `# Core Memory`, `# Instruction Files`). The instruction-files slice is
211
- * always last and may itself contain separators, so it is cut by position;
212
- * the core-memory slice is one separator-free part. `undefined` keeps a slice,
213
- * an empty string drops it.
210
+ * …, `# Instruction Files`, `# Core Memory`). Core memory is the last,
211
+ * separator-free part; the instruction-files slice before it may itself
212
+ * contain separators, so both are cut by position. A core from before that
213
+ * order (memory, then instruction files) is read too and written in the
214
+ * current order. `undefined` keeps a slice, an empty string drops it.
214
215
  * @param {string} core
215
216
  * @param {{ coreMemoryContext?: string, instructionFilesContext?: string }} slices
216
217
  */
217
218
  export function replaceWorkspaceCoreSlices(core, { coreMemoryContext, instructionFilesContext } = {}) {
219
+ let { head, instructionFiles, memory } = splitWorkspaceCoreSlices(core);
220
+ if (coreMemoryContext !== undefined) {
221
+ const value = trimmedSlice(coreMemoryContext);
222
+ memory = value ? `${CORE_MEMORY_HEADING}${value}` : '';
223
+ }
224
+ if (instructionFilesContext !== undefined) instructionFiles = trimmedSlice(instructionFilesContext);
225
+ return [head, instructionFiles, memory].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
226
+ }
227
+
228
+ /** A BP3 core's `{ head, instructionFiles, memory }` (each '' when absent). */
229
+ function splitWorkspaceCoreSlices(core) {
218
230
  let head = String(core || '');
231
+ let memory = '';
232
+ const memoryAt = bp3SliceAt(head, CORE_MEMORY_HEADING);
233
+ if (memoryAt) {
234
+ memory = head.slice(memoryAt.start);
235
+ head = head.slice(0, memoryAt.cut);
236
+ const legacy = memory.indexOf(`${BP3_PART_SEPARATOR}${INSTRUCTION_FILES_HEADING}`);
237
+ if (legacy >= 0) {
238
+ const instructions = memory.slice(legacy + BP3_PART_SEPARATOR.length);
239
+ memory = memory.slice(0, legacy);
240
+ head = head ? `${head}${BP3_PART_SEPARATOR}${instructions}` : instructions;
241
+ }
242
+ }
219
243
  let instructionFiles = '';
220
244
  const instructionAt = bp3SliceAt(head, INSTRUCTION_FILES_HEADING);
221
245
  if (instructionAt) {
222
246
  instructionFiles = head.slice(instructionAt.start);
223
247
  head = head.slice(0, instructionAt.cut);
224
248
  }
225
- let parts = head ? head.split(BP3_PART_SEPARATOR) : [];
226
- if (coreMemoryContext !== undefined) {
227
- parts = parts.filter((part) => !part.startsWith(CORE_MEMORY_HEADING));
228
- const memory = trimmedSlice(coreMemoryContext);
229
- if (memory) parts.push(`${CORE_MEMORY_HEADING}${memory}`);
230
- }
231
- if (instructionFilesContext !== undefined) instructionFiles = trimmedSlice(instructionFilesContext);
232
- return [...parts, instructionFiles].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
249
+ return { head, instructionFiles, memory };
233
250
  }
234
251
 
235
252
  /**
@@ -286,9 +303,7 @@ function textOf(message) {
286
303
  */
287
304
  function presentedWorkspace(session) {
288
305
  const core = session.bp3EnvSplit === true ? textOf(bp3SystemMessage(session)) : String(session.bp3CoreContext || '');
289
- const instructionAt = bp3SliceAt(core, INSTRUCTION_FILES_HEADING);
290
- const head = instructionAt ? core.slice(0, instructionAt.cut) : core;
291
- const memory = (head ? head.split(BP3_PART_SEPARATOR) : []).find((part) => part.startsWith(CORE_MEMORY_HEADING));
306
+ const { instructionFiles, memory } = splitWorkspaceCoreSlices(core);
292
307
  const tail = typeof session.bp3EnvironmentContext === 'string' ? session.bp3EnvironmentContext : '';
293
308
  const facts = tail.split(BP3_PART_SEPARATOR)[0] || '';
294
309
  const promptText = [textOf(bpEnvSystemMessage(session)), textOf(bp3SystemMessage(session))].join('\n');
@@ -297,10 +312,23 @@ function presentedWorkspace(session) {
297
312
  cwd: cwd ? cwd.trim() : null,
298
313
  environmentContext: facts.startsWith('- ') ? facts.trim() : '',
299
314
  coreMemoryContext: memory ? memory.slice(CORE_MEMORY_HEADING.length).trim() : '',
300
- instructionFilesContext: instructionAt ? core.slice(instructionAt.start).trim() : '',
315
+ instructionFilesContext: instructionFiles.trim(),
301
316
  };
302
317
  }
303
318
 
319
+ /**
320
+ * A rebuilt BP3 core head (composed without workspace slices) followed by the
321
+ * instruction-files and core-memory slices the session's prompt presents now,
322
+ * so a rebuild of the rest of the core (a mid-conversation workflow switch)
323
+ * leaves what the model was told about its workspace — and any staged
324
+ * workspaceReroot — as it was.
325
+ */
326
+ export function withPresentedWorkspaceSlices(session, nextHead) {
327
+ const core = session.bp3EnvSplit === true ? textOf(bp3SystemMessage(session)) : String(session.bp3CoreContext || '');
328
+ const { instructionFiles, memory } = splitWorkspaceCoreSlices(core);
329
+ return [nextHead, instructionFiles, memory].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
330
+ }
331
+
304
332
  const WORKSPACE_SLICES = ['environmentContext', 'coreMemoryContext', 'instructionFilesContext'];
305
333
 
306
334
  /**
@@ -112,20 +112,36 @@ export function _buildAgentRules(profile = 'full') {
112
112
  );
113
113
  }
114
114
 
115
- export function _buildLeadRules({ includeLeadBrief = true } = {}) {
115
+ // One `part` of LEAD.md (rules-builder buildLeadRoleContent): 'common' (the
116
+ // lines every Lead shares), 'coding' / 'agent' (the mode-variant passages),
117
+ // 'identity' (the coding identity sentence).
118
+ export function _buildLeadRules({ includeLeadBrief = true, part = 'common' } = {}) {
116
119
  const PLUGIN_ROOT = mixdogRoot();
117
120
  const DATA_DIR = resolvePluginData();
118
121
  const RULES_DIR = join(PLUGIN_ROOT, 'rules');
122
+ const variant = includeLeadBrief ? 'delegating' : 'delegation-free';
119
123
  return buildCachedRules(
120
124
  'buildLeadRoleContent',
121
125
  'lead role rules',
122
126
  [join(RULES_DIR, 'lead'), join(DATA_DIR, 'mixdog-config.json')],
123
- { PLUGIN_ROOT, DATA_DIR, includeLeadBrief },
124
- includeLeadBrief ? 'delegating' : 'delegation-free'
127
+ { PLUGIN_ROOT, DATA_DIR, includeLeadBrief, part },
128
+ `${variant}\n${part}`
125
129
  );
126
130
  }
127
131
 
128
- export function _buildLeadMetaContext() {
132
+ // One agent-mode rule file (rules/agent-mode): 'conduct', 'mixy' or 'routine'.
133
+ export function _buildAgentModeRules({ part = 'conduct' } = {}) {
134
+ const PLUGIN_ROOT = mixdogRoot();
135
+ return buildCachedRules(
136
+ 'buildAgentModeContent',
137
+ 'agent mode rules',
138
+ [join(PLUGIN_ROOT, 'rules', 'agent-mode')],
139
+ { PLUGIN_ROOT, part },
140
+ part
141
+ );
142
+ }
143
+
144
+ export function _buildLeadMetaContext({ includeOutputStyle = true } = {}) {
129
145
  const PLUGIN_ROOT = mixdogRoot();
130
146
  const DATA_DIR = resolvePluginData();
131
147
  const RULES_DIR = join(PLUGIN_ROOT, 'rules');
@@ -138,7 +154,8 @@ export function _buildLeadMetaContext() {
138
154
  join(PLUGIN_ROOT, 'output-styles'),
139
155
  join(DATA_DIR, 'output-styles'),
140
156
  ],
141
- { PLUGIN_ROOT, DATA_DIR }
157
+ { PLUGIN_ROOT, DATA_DIR, includeOutputStyle },
158
+ includeOutputStyle ? 'style' : 'no-style'
142
159
  );
143
160
  }
144
161
 
@@ -28,6 +28,7 @@ import { mintSessionId } from './session-id.mjs';
28
28
  import { hideCompactionArchive } from '../compaction-archive-capture.mjs';
29
29
  import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
30
30
  import { forgetSideQuestion } from '../side-question-store.mjs';
31
+ import { isAgentHomeSessionId } from '../agent-home.mjs';
31
32
 
32
33
  /** Force-flush session metrics to disk. */
33
34
  export async function flushSessionMetrics(sessionId) {
@@ -207,9 +208,12 @@ function clearTokenAccounting(session, messages, keep, beforeMessageTokens) {
207
208
  // Fork the outgoing transcript to a separate resumable session BEFORE the wipe,
208
209
  // so the conversation about to be discarded stays reachable via /resume under a
209
210
  // fresh id. Best-effort: any failure here must never block the clear itself.
210
- function forkOutgoingTranscript(session, messages, now) {
211
+ // `archiveAs` names the fork (an agent home's "대화 새로 시작": the host
212
+ // archives that id before the clear) and makes it durable first: the returned
213
+ // promise settles once the copy is on disk, and the wipe waits on it.
214
+ function forkOutgoingTranscript(session, messages, now, archiveAs = '') {
211
215
  try {
212
- const forkId = mintSessionId();
216
+ const forkId = archiveAs || mintSessionId();
213
217
  const fork = {
214
218
  ...session,
215
219
  id: forkId,
@@ -234,6 +238,15 @@ function forkOutgoingTranscript(session, messages, now) {
234
238
  delete fork.compaction.archive;
235
239
  }
236
240
  setLiveSession(fork);
241
+ if (archiveAs) {
242
+ return saveSessionAsync(fork).then(
243
+ () => evictLiveSession(forkId),
244
+ (err) => {
245
+ evictLiveSession(forkId);
246
+ throw err;
247
+ }
248
+ );
249
+ }
237
250
  void saveSessionAsync(fork)
238
251
  .then(() => {
239
252
  // The fork is a cold snapshot kept for /resume. Once durable on
@@ -247,17 +260,33 @@ function forkOutgoingTranscript(session, messages, now) {
247
260
  writeStderrBestEffort(`[session] clear-fork save failed (sess=${forkId}): ${err?.message || err}\n`);
248
261
  });
249
262
  } catch (err) {
263
+ if (archiveAs) throw err;
250
264
  writeStderrBestEffort(`[session] clear-fork failed (sess=${session.id}): ${err?.message || err}\n`);
251
265
  }
252
266
  }
253
267
 
268
+ const ARCHIVE_ID = /^sess_[A-Za-z0-9_-]{8,200}$/;
269
+
270
+ /** The fork id a clear was asked to archive under, '' when none. */
271
+ function clearArchiveId(value, sessionId) {
272
+ if (value === undefined || value === null || value === '') return '';
273
+ const id = String(value);
274
+ if (!ARCHIVE_ID.test(id) || id === sessionId || isAgentHomeSessionId(id)) {
275
+ throw new Error('clear: archiveAs must be a fresh session id');
276
+ }
277
+ if (loadSession(id)) throw new Error(`clear: session ${id} already exists`);
278
+ return id;
279
+ }
280
+
254
281
  export async function clearSessionMessages(sessionId, options = {}) {
255
282
  const session = loadSession(sessionId);
256
283
  if (!session) return false;
257
284
  // Don't resurrect a closed session just to clear its messages.
258
285
  if (session.closed === true) return false;
259
286
  const clearOptions = options && typeof options === 'object' ? options : {};
260
- const compact = clearOptions.compact === true;
287
+ const archiveAs = clearArchiveId(clearOptions.archiveAs, sessionId);
288
+ // An archived reset keeps the whole transcript: never compacted first.
289
+ const compact = clearOptions.compact === true && !archiveAs;
261
290
  const currentMessages = Array.isArray(session.messages) ? session.messages : [];
262
291
  const beforeMessageTokens = estimateMessagesTokens(currentMessages);
263
292
  const { messages, clearCompactError } = compact
@@ -275,8 +304,14 @@ export async function clearSessionMessages(sessionId, options = {}) {
275
304
  // content the live session retains via the summary, so the fork duplicated
276
305
  // it as a confusing extra Recent row ("Re-attached after compaction…" —
277
306
  // user report). Plain /clear (no summary kept) still forks.
307
+ // `fork: false` (a room member's reset: the room's own log keeps the
308
+ // conversation) skips the copy; an `archiveAs` clear always keeps one.
278
309
  const summaryCarriedForward = keep.some(isCompactSummaryMessage);
279
- if (hasUserConversationMessage(messages) && !summaryCarriedForward) forkOutgoingTranscript(session, messages, now);
310
+ const keepCopy = clearOptions.fork !== false || Boolean(archiveAs);
311
+ if (keepCopy && hasUserConversationMessage(messages) && !summaryCarriedForward) {
312
+ const fork = forkOutgoingTranscript(session, messages, now, archiveAs);
313
+ if (archiveAs) await fork;
314
+ }
280
315
 
281
316
  session.messages = keep;
282
317
  // Clear truncates the transcript wholesale; drop the provider prefix
@@ -9,6 +9,7 @@ import { saveSession, saveSessionAsync, saveSessionAsyncDeferred, loadSession, s
9
9
  import { _liveSessions } from '../store/live-state.mjs';
10
10
  import { _recoverTurnCheckpointDurably } from './turn-checkpoint-recovery.mjs';
11
11
  import { isAgentOwner } from '../../agent-owner.mjs';
12
+ import { AGENT_MODE, resolveSessionMode, sessionModeOf } from '../agent-mode.mjs';
12
13
  import { getHiddenAgent } from '../../internal-agents.mjs';
13
14
  import { loadConfig } from '../../config.mjs';
14
15
  import {
@@ -19,6 +20,7 @@ import {
19
20
  import { normalizeAutoClearConfig, resolveAutoClearIdleMs } from '../../runtime-core/config-helpers.mjs';
20
21
  import { _buildBaseRules } from './rules-cache.mjs';
21
22
  import { composeSessionSystem, seedSessionMessages } from './session-prompt-composition.mjs';
23
+ import { agentProfileRevision, leadRosterRevision } from '../../context/role-instructions.mjs';
22
24
  import {
23
25
  _prepareResumeTools,
24
26
  delegationDisabled,
@@ -206,12 +208,22 @@ function logSessionSurface({ resolvedAgent, toolPermission, tools }) {
206
208
  }
207
209
  }
208
210
 
209
- export function createSession(opts) {
210
- const presetObj = opts.preset && typeof opts.preset === 'object' ? opts.preset : null;
211
- const resolved = resolveAgentRuntimeProfile(opts, presetObj);
211
+ export function createSession(createOpts) {
212
+ const presetObj = createOpts.preset && typeof createOpts.preset === 'object' ? createOpts.preset : null;
213
+ const resolved = resolveAgentRuntimeProfile(createOpts, presetObj);
212
214
  const { profile } = resolved;
213
- const route = resolveSessionRoute(opts, presetObj, profile);
215
+ const route = resolveSessionRoute(createOpts, presetObj, profile);
214
216
  const { providerName, modelName, provider, toolPreset, id } = route;
217
+ // Agent or coding mode (agent-mode.mjs), resolved once and persisted. An
218
+ // agent-mode Lead acts as its agent: a schedule/webhook run that names only
219
+ // its `ownerAgentId` gets that agent's role, persona and connector policy
220
+ // (all keyed by `session.agent`), as its home thread does.
221
+ const sessionMode = sessionModeOf({ ...createOpts, id });
222
+ const actingAgent = resolveSessionMode({ session: { ...createOpts, id } }).agentId;
223
+ const opts =
224
+ sessionMode === AGENT_MODE && !createOpts.agent && actingAgent
225
+ ? { ...createOpts, agent: actingAgent, sessionMode }
226
+ : { ...createOpts, sessionMode };
215
227
  const routeFallbacks = normalizeWorkflowRoute({
216
228
  provider: providerName,
217
229
  model: modelName,
@@ -227,7 +239,10 @@ export function createSession(opts) {
227
239
  // Scoped to explicit-breakpoint (Anthropic-family) providers only.
228
240
  const providerCacheOpts = resolved.providerCacheOpts || buildSessionProviderCacheOpts(providerName, id, opts.agent);
229
241
  const surface = resolveSessionToolSurface(opts, { profile, toolPreset, modelName });
230
- const prompt = composeSessionSystem(opts, { profile, providerName, modelName, surface });
242
+ // The agent profile the prompt is composed from, read first: an edit landing
243
+ // during composition leaves the stamp behind, never ahead (refreshAgentProfile).
244
+ const agentProfile = surface.ownerIsAgent ? '' : agentProfileRevision(surface.resolvedAgent);
245
+ const prompt = composeSessionSystem(opts, { profile, providerName, modelName, surface, sessionMode });
231
246
  const messages = seedSessionMessages(prompt, opts.files);
232
247
  logSessionSurface(surface);
233
248
  const contextMeta = resolveSessionContextMeta(provider, modelName, {
@@ -246,6 +261,11 @@ export function createSession(opts) {
246
261
  origin: sessionOriginFields(opts, { profile, presetObj, providerCacheOpts, surface }),
247
262
  });
248
263
  session.providerCacheOptsOverride = Boolean(resolved.providerCacheOpts);
264
+ if (agentProfile) {
265
+ session.agentProfileRevision = agentProfile;
266
+ // The bot roster its prompt lists (a Lead's catalog slices).
267
+ session.agentRosterRevision = leadRosterRevision(opts.leadWorkflowParts);
268
+ }
249
269
  if (routeFallbacks?.length) session.routeFallbacks = routeFallbacks;
250
270
  // A non-native agent sends core + bridge, built from its filtered tools.
251
271
  applyAgentToolExposure(session);
@@ -532,6 +552,8 @@ export async function resumeSession(sessionId, preset, options = {}) {
532
552
  ensureCodexWireSessionId(session);
533
553
  if (!_applyDesktopResumeScope(session, options)) return null;
534
554
  if (!session.owner) session.owner = 'user';
555
+ // Records from before the mode existed get it resolved and stamped.
556
+ session.sessionMode = sessionModeOf(session);
535
557
  if (Object.hasOwn(options, 'mcpScopeId')) {
536
558
  session.mcpScopeId = String(options.mcpScopeId || '').trim() || null;
537
559
  }
@@ -4,9 +4,13 @@
4
4
  // carry all four to the provider with their cache tiers.
5
5
  import { buildSkillManifest, composeSystemPrompt } from '../../context/collect.mjs';
6
6
  import { configuredInstructionFilesMode, loadInstructionFilesContext } from '../../context/instruction-files.mjs';
7
+ import { agentModeIdentityLine } from '../../context/role-instructions.mjs';
8
+ import { AGENT_MODE, isRoutineSource, sessionModeOf } from '../agent-mode.mjs';
9
+ import { MIXY_AGENT_ID } from '../agent-home.mjs';
7
10
  import {
8
11
  _buildBaseRules,
9
12
  _buildAgentRules,
13
+ _buildAgentModeRules,
10
14
  _buildLeadRules,
11
15
  _buildLeadMetaContext,
12
16
  _buildLeadLanguageContext,
@@ -81,8 +85,9 @@ export function buildWorkspacePromptContext(session, cwd) {
81
85
  ];
82
86
  return {
83
87
  environmentContext: buildShellEnvironmentContext({ cwd }, ownerIsAgent, tools),
88
+ // Agent mode never carries project instruction files.
84
89
  instructionFilesContext:
85
- session?.skipInstructionFiles === true
90
+ session?.skipInstructionFiles === true || sessionModeOf(session) === AGENT_MODE
86
91
  ? undefined
87
92
  : loadInstructionFilesContext({ mode: configuredInstructionFilesMode(), cwd }),
88
93
  };
@@ -90,24 +95,57 @@ export function buildWorkspacePromptContext(session, cwd) {
90
95
 
91
96
  // BP1/BP2/BP3 rule blocks plus the volatile environment, composed into the
92
97
  // four system blocks; environmentTailContext is the persisted env tail.
93
- export function sessionRolePromptFields(identity, { ownerIsAgent, toolFreeRole, delegationFree }) {
98
+ // A Lead's BP3 rule slices, ordered for prefix sharing (composeSystemPrompt):
99
+ // LEAD.md's common lines first; then the mode block (coding: LEAD.md's two
100
+ // passages; agent mode: their conversational variants and AGENT_MODE.md);
101
+ // then Mixy's MIXY.md; last the per-session tail — routine line, identity.
102
+ // Agent mode is a Lead created as an agent (agent-mode.mjs).
103
+ export function sessionRolePromptFields(
104
+ identity,
105
+ { ownerIsAgent, toolFreeRole, delegationFree, sessionMode, routine = false }
106
+ ) {
94
107
  const skipRules = identity.skipAgentRules || toolFreeRole;
108
+ const lead = !ownerIsAgent && !skipRules;
109
+ const agentMode = lead && sessionMode === AGENT_MODE;
110
+ const mixy = (identity.agent || MIXY_AGENT_ID) === MIXY_AGENT_ID;
95
111
  let roleRules = '';
96
112
  if (!skipRules) {
97
113
  roleRules = ownerIsAgent
98
114
  ? _buildAgentRules(identity.agentRulesProfile)
99
115
  : _buildLeadRules({ includeLeadBrief: !delegationFree });
100
116
  }
117
+ const identityLine = agentMode
118
+ ? agentModeIdentityLine(identity.agent || MIXY_AGENT_ID)
119
+ : lead
120
+ ? _buildLeadRules({ includeLeadBrief: !delegationFree, part: 'identity' })
121
+ : '';
101
122
  return {
102
123
  roleRules,
124
+ ...(lead
125
+ ? {
126
+ leadModeRules: _buildLeadRules({ includeLeadBrief: !delegationFree, part: agentMode ? 'agent' : 'coding' }),
127
+ identityContext: identityLine ? `# Identity\n\n${identityLine}` : '',
128
+ }
129
+ : {}),
130
+ ...(agentMode
131
+ ? {
132
+ agentModeRules: _buildAgentModeRules({ part: 'conduct' }),
133
+ ...(mixy ? { mixyRules: _buildAgentModeRules({ part: 'mixy' }) } : {}),
134
+ ...(routine ? { routineRules: _buildAgentModeRules({ part: 'routine' }) } : {}),
135
+ }
136
+ : {}),
103
137
  userPrompt: identity.systemPrompt,
104
- skipRoleCatalog: !ownerIsAgent,
138
+ // A coding Lead carries no role section; an agent's home thread (a user
139
+ // session running as that agent) carries its own AGENT.md role, and Mixy
140
+ // in agent mode its optional agents/lead/AGENT.md.
141
+ skipRoleCatalog: !ownerIsAgent && (!identity.agent || identity.agent === 'lead') && !(agentMode && mixy),
105
142
  agent: identity.agent,
106
143
  profile: { skip: identity.profileSkip },
107
144
  };
108
145
  }
109
146
 
110
- export function composeSessionSystem(opts, { profile, providerName, modelName, surface }) {
147
+ export function composeSessionSystem(opts, { profile, providerName, modelName, surface, sessionMode }) {
148
+ const agentMode = sessionMode === AGENT_MODE;
111
149
  const { ownerIsAgent, resolvedAgent, skills } = surface;
112
150
  // A tool-free hidden role (a one-shot classifier or title writer) carries
113
151
  // only its own role rules: the shared tool policy and the worker conduct
@@ -130,9 +168,15 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
130
168
  agent: resolvedAgent,
131
169
  agentRulesProfile: surface.agentRulesProfile,
132
170
  };
133
- const roleFields = sessionRolePromptFields(promptIdentity, { ownerIsAgent, toolFreeRole, delegationFree });
171
+ const roleFields = sessionRolePromptFields(promptIdentity, {
172
+ ownerIsAgent,
173
+ toolFreeRole,
174
+ delegationFree,
175
+ sessionMode,
176
+ routine: isRoutineSource(opts.sourceType),
177
+ });
134
178
  const leadOnly = !skipAgentRules && !ownerIsAgent;
135
- const metaContext = leadOnly ? _buildLeadMetaContext() : '';
179
+ const metaContext = leadOnly ? _buildLeadMetaContext({ includeOutputStyle: !agentMode }) : '';
136
180
  // Lead-only response language; closes the environment block so no English
137
181
  // system text (session/shell/git lines) follows it.
138
182
  const languageContext = leadOnly ? _buildLeadLanguageContext() : '';
@@ -146,9 +190,10 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
146
190
  // already sit at the end of this string to stay last after a refresh.
147
191
  const environmentTailContext = [shellEnvironmentContext, languageContext].filter(Boolean).join('\n\n---\n\n');
148
192
  // User instruction files (AGENTS.md / CLAUDE.md, global then the project
149
- // root's); a tool-free role and a profile skipping `claudemd` get none.
193
+ // root's); a tool-free role, a profile skipping `claudemd` and agent mode
194
+ // get none.
150
195
  const instructionFilesContext =
151
- toolFreeRole || profile?.skip?.claudemd
196
+ toolFreeRole || profile?.skip?.claudemd || agentMode
152
197
  ? ''
153
198
  : loadInstructionFilesContext({ mode: configuredInstructionFilesMode(), cwd: opts.cwd });
154
199
  const composed = composeSystemPrompt({
@@ -157,6 +202,8 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
157
202
  metaContext: metaContext || undefined,
158
203
  languageContext: languageContext || undefined,
159
204
  workflowContext: opts.workflowContext || null,
205
+ // The workflow's orchestration and agent-catalog slices (workflow/context.mjs leadPromptParts).
206
+ ...(ownerIsAgent ? {} : opts.leadWorkflowParts),
160
207
  coreMemoryContext: opts.coreMemoryContext || null,
161
208
  instructionFilesContext: instructionFilesContext || null,
162
209
  skillManifest: buildSkillManifest(skills),
@@ -70,6 +70,20 @@ export function sessionOriginFields(opts, { profile, presetObj, providerCacheOpt
70
70
  // scheduler/daily-standup, webhook/github-push, lead/worker.
71
71
  sourceType: opts.sourceType || null,
72
72
  sourceName: opts.sourceName || null,
73
+ // The agent a schedule/webhook run acts for: whose memory it may write
74
+ // (session-runtime/services/memory-access.mjs). Absent on other sessions.
75
+ ...(opts.ownerAgentId ? { ownerAgentId: String(opts.ownerAgentId) } : {}),
76
+ // A coding task session an agent created (session create): who asked, from
77
+ // which thread, the request, and the title it was given.
78
+ ...(opts.taskOrigin
79
+ ? {
80
+ taskOrigin: opts.taskOrigin,
81
+ title: opts.taskOrigin.title,
82
+ titleLocked: true,
83
+ titleSource: 'ai',
84
+ titleUpdatedAt: Date.now(),
85
+ }
86
+ : {}),
73
87
  // Provider-scoped unified cache key — one shard per provider,
74
88
  // shared across all roles / sources (agent/maintenance/mcp/
75
89
  // scheduler/webhook). Role or source-specific context must be
@@ -137,6 +151,8 @@ export function buildSessionRecord({ opts, route, presetObj, surface, prompt, me
137
151
  selectedContextWindow: opts.selectedContextWindow || null,
138
152
  agent: opts.agent,
139
153
  owner: opts.owner || 'user',
154
+ // 'agent' | 'coding' (session/agent-mode.mjs), resolved at creation.
155
+ sessionMode: opts.sessionMode,
140
156
  bp3CoreContext: prompt.sessionMarkerCore,
141
157
  promptIdentity: prompt.promptIdentity,
142
158
  callerDisallowedTools: [...(opts.callerDisallowedTools ?? opts.disallowedTools ?? [])],
@@ -22,8 +22,9 @@ import { ROUTE_TOOL_ORDER } from '../../runtime-core/tool-catalog-data.mjs';
22
22
  // the sort overhead on ~30 tools is negligible.
23
23
  function _getMcpTools(mcpScopeId = null, cwd = null) {
24
24
  // Project scope: servers limited to other project roots stay connected
25
- // but leave this session's schema (session-runtime/extension-scopes).
26
- const mcp = filterMcpToolsForSession(getMcpTools(mcpScopeId) || [], cwd);
25
+ // but leave this session's schema (session-runtime/extension-scopes). An
26
+ // agent's connectors apply at dispatch, never here.
27
+ const mcp = filterMcpToolsForSession(getMcpTools(mcpScopeId) || [], cwd, null);
27
28
  // `public:false` tools stay registered in internal-tools for runtime
28
29
  // rewrites/dispatch, but must never enter any model-visible schema (Lead
29
30
  // full/mcp included). Filter before mapping because the projection below