mixdog 0.9.85 → 0.9.87

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 (102) hide show
  1. package/README.md +7 -1
  2. package/package.json +4 -3
  3. package/src/defaults/agents.json +12 -0
  4. package/src/defaults/skills/setup/SKILL.md +44 -12
  5. package/src/help.mjs +40 -9
  6. package/src/lib/keychain-cjs.cjs +11 -1
  7. package/src/rules/agent/43-title-agent.md +22 -0
  8. package/src/rules/shared/01-tool.md +3 -3
  9. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
  10. package/src/runtime/agent/orchestrator/mcp/client.mjs +24 -0
  11. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +84 -3
  12. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +125 -16
  13. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
  14. package/src/runtime/agent/orchestrator/session/context-utils.mjs +73 -45
  15. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -18
  16. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -0
  18. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +135 -9
  19. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
  20. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +4 -0
  21. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
  22. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
  23. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
  24. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  25. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +26 -0
  26. package/src/runtime/agent/orchestrator/session/token-bpe.mjs +42 -0
  27. package/src/runtime/agent/orchestrator/session/token-native.mjs +186 -0
  28. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +15 -5
  29. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +20 -0
  30. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -2
  31. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
  32. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
  33. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  34. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +286 -0
  35. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  36. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -3
  37. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
  38. package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
  39. package/src/runtime/agent/orchestrator/tools/token-manifest.json +26 -0
  40. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
  41. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  42. package/src/runtime/media/renditions.mjs +1 -1
  43. package/src/runtime/media/store.mjs +1 -1
  44. package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
  45. package/src/runtime/memory/tool-defs.mjs +19 -3
  46. package/src/runtime/shared/atomic-file.mjs +25 -14
  47. package/src/runtime/shared/automation-attachments.mjs +3 -3
  48. package/src/runtime/shared/child-guardian.mjs +15 -5
  49. package/src/runtime/shared/child-spawn-gate.mjs +6 -12
  50. package/src/runtime/shared/resource-admission.mjs +7 -3
  51. package/src/runtime/shared/turn-snapshot.mjs +0 -3
  52. package/src/session-runtime/lifecycle-api.mjs +49 -9
  53. package/src/session-runtime/prewarm.mjs +35 -0
  54. package/src/session-runtime/provider-auth-api.mjs +8 -0
  55. package/src/session-runtime/provider-usage.mjs +30 -5
  56. package/src/session-runtime/remote-control.mjs +8 -47
  57. package/src/session-runtime/remote-transcript.mjs +3 -7
  58. package/src/session-runtime/route-preparation.mjs +24 -0
  59. package/src/session-runtime/runtime-core.mjs +286 -548
  60. package/src/session-runtime/session-lifecycle.mjs +378 -0
  61. package/src/session-runtime/session-turn-api.mjs +9 -2
  62. package/src/session-runtime/workflow-agents-api.mjs +3 -0
  63. package/src/standalone/agent-shard/shard-child.mjs +300 -0
  64. package/src/standalone/agent-shard/shard-pool.mjs +443 -0
  65. package/src/standalone/agent-tool/job-views.mjs +329 -0
  66. package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
  67. package/src/standalone/agent-tool/tag-registry.mjs +340 -0
  68. package/src/standalone/agent-tool.mjs +112 -941
  69. package/src/standalone/channel-daemon-transport.mjs +126 -185
  70. package/src/standalone/channel-daemon.mjs +9 -10
  71. package/src/standalone/usage-dashboard.mjs +23 -1
  72. package/src/tui/App.jsx +362 -2569
  73. package/src/tui/app/app-view.jsx +504 -0
  74. package/src/tui/app/channel-pickers.mjs +3 -8
  75. package/src/tui/app/create-app-pickers.mjs +313 -0
  76. package/src/tui/app/prompt-submit.mjs +501 -0
  77. package/src/tui/app/route-pickers.mjs +117 -152
  78. package/src/tui/app/settings-picker.mjs +90 -89
  79. package/src/tui/app/shell-layout.mjs +563 -0
  80. package/src/tui/app/slash-commands.mjs +7 -7
  81. package/src/tui/app/slash-dispatch.mjs +0 -45
  82. package/src/tui/app/usage-context-panels.mjs +288 -0
  83. package/src/tui/app/use-copy-selection.mjs +56 -0
  84. package/src/tui/app/use-global-key-input.mjs +151 -0
  85. package/src/tui/app/use-pasted-buffers.mjs +115 -0
  86. package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
  87. package/src/tui/app/use-prompt-hint.mjs +58 -0
  88. package/src/tui/app/use-prompt-queue-history.mjs +100 -0
  89. package/src/tui/app/use-terminal-chrome.mjs +72 -0
  90. package/src/tui/app/use-transcript-activity.mjs +131 -0
  91. package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
  92. package/src/tui/components/StatusLine.jsx +1 -1
  93. package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
  94. package/src/tui/dist/index.mjs +9857 -9159
  95. package/src/tui/engine/session-api-ext.mjs +47 -9
  96. package/src/tui/engine.mjs +7 -1
  97. package/src/tui/figures.mjs +0 -1
  98. package/src/ui/statusline-format.mjs +0 -1
  99. package/src/hooks/lib/permission-evaluator.cjs +0 -24
  100. package/src/lib/config-cjs.cjs +0 -61
  101. package/src/runtime/shared/launcher-control.mjs +0 -258
  102. package/src/runtime/shared/workspace-router.mjs +0 -259
@@ -0,0 +1,378 @@
1
+ // Session lifecycle, extracted from runtime-core.mjs: first-turn route
2
+ // resolution, route-effort refresh, and createCurrentSession (provider
3
+ // session construction with MCP wiring and reset handling). Shared mutable
4
+ // runtime state flows through the rt bag.
5
+ import { ensureProviderEnabled, normalizeCompactionConfig } from './config-helpers.mjs';
6
+ import { clean, hasOwn } from './session-text.mjs';
7
+ import { coerceEffortFor, deferredSurfaceModeForLead, effortItemsFor, toolSpecForMode } from './effort.mjs';
8
+ import { fastCapableFor } from './model-capabilities.mjs';
9
+ import { bootProfile } from './boot-profile.mjs';
10
+ import { STANDALONE_DATA_DIR } from './runtime-paths.mjs';
11
+ import { LEAD_DISALLOWED_TOOLS } from './tool-defs.mjs';
12
+ import { attachSessionHooks } from './session-hooks.mjs';
13
+ import { applyDeferredToolSurface } from './tool-catalog.mjs';
14
+ import { writeStatuslineRoute } from './statusline-route.mjs';
15
+ import { createWarmupSchedulers } from './warmup-schedulers.mjs';
16
+ import { warmCatalogsInBackground } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
17
+ import { envFlag } from './env.mjs';
18
+ import { createPrewarmSchedulers } from './prewarm.mjs';
19
+ import { hasActiveAutomation } from '../standalone/channel-admin.mjs';
20
+ import { createRemoteTranscript } from './remote-transcript.mjs';
21
+ export function createSessionLifecycle({
22
+ rt,
23
+ collectProviderModels,
24
+ ensureProvidersReady,
25
+ lookupModelMeta,
26
+ mgr,
27
+ loadCoreMemoryContext,
28
+ awaitKeychainPrewarm,
29
+ ensureConfigForRouteProvider,
30
+ reg,
31
+ cfgMod,
32
+ activeWorkflowContext,
33
+ memoryEnabled,
34
+ hooks,
35
+ hookCommonPayload,
36
+ mcpClient,
37
+ modelStandaloneTools,
38
+ applyPreSessionToolSelection,
39
+ statusRoutes,
40
+ warmupTimers,
41
+ providerModelCaches,
42
+ reloadFullConfig,
43
+ refreshStatuslineUsageSnapshot,
44
+ warmProviderModelCache,
45
+ cachedProviderSetup,
46
+ providerWarmupDelayMs,
47
+ providerSetupWarmupDelayMs,
48
+ providerModelWarmupDelayMs,
49
+ modelCatalogWarmupDelayMs,
50
+ statuslineUsageWarmupDelayMs,
51
+ statuslineUsageRefreshDelayMs,
52
+ backgroundBusyRetryMs,
53
+ providerWarmupEnabled,
54
+ modelPrefetchEnabled,
55
+ modelCatalogWarmupEnabled,
56
+ prewarmTimers,
57
+ channelsEnabled,
58
+ getCodeGraphModule,
59
+ channels,
60
+ codeGraphPrewarmDelayMs,
61
+ channelStartDelayMs,
62
+ sessionPrewarmDelayMs,
63
+ codeGraphPrewarmEnabled,
64
+ sessionPrewarmEnabled,
65
+ prewarmState,
66
+ }) {
67
+ async function resolveMissingRouteModelForFirstTurn() {
68
+ if (routeHasModel()) return rt.route;
69
+ const models = await collectProviderModels();
70
+ const picked = models[0] || null;
71
+ if (!picked) {
72
+ throw new Error('No provider models available. Open /providers to sign in, then /model to choose a model.');
73
+ }
74
+ rt.route = {
75
+ ...rt.route,
76
+ provider: picked.provider,
77
+ model: picked.id,
78
+ preset: null,
79
+ };
80
+ return rt.route;
81
+ }
82
+
83
+ async function refreshRouteEffort(modelMetaOverride = null, expectedRoute = null) {
84
+ const targetRoute = expectedRoute || rt.route;
85
+ await ensureProvidersReady(ensureProviderEnabled(rt.config, targetRoute.provider));
86
+ const modelMeta = modelMetaOverride || await lookupModelMeta(targetRoute.provider, targetRoute.model);
87
+ // A rapid second resume/model change can replace the route while provider
88
+ // metadata is loading. Never let the older completion overwrite it.
89
+ if (expectedRoute && rt.route !== expectedRoute) return null;
90
+ const requested = hasOwn(targetRoute, 'effort')
91
+ ? targetRoute.effort
92
+ : (targetRoute.preset?.effort || null);
93
+ const effectiveEffort = coerceEffortFor(targetRoute.provider, modelMeta, requested);
94
+ const fastCapable = fastCapableFor(targetRoute.provider, modelMeta);
95
+ // Carry the catalog display name onto the route so the statusline shows a
96
+ // human label (e.g. "Claude Fable 5") for preset-less direct models instead
97
+ // of the raw id. `name` is only trusted when it differs from the raw model
98
+ // id (some providers echo the id as `name`), so it can't clobber a better
99
+ // already-resolved label. Falls back to existing route.modelDisplay, then unset.
100
+ const metaName = clean(modelMeta?.name);
101
+ const modelDisplay = clean(modelMeta?.display) || clean(modelMeta?.displayName)
102
+ || (metaName && metaName !== clean(targetRoute.model) ? metaName : '')
103
+ || clean(targetRoute.modelDisplay);
104
+ rt.route = {
105
+ ...targetRoute,
106
+ fast: fastCapable ? targetRoute.fast === true : false,
107
+ fastCapable,
108
+ effectiveEffort,
109
+ effortOptions: effortItemsFor(rt.route.provider, modelMeta, effectiveEffort),
110
+ ...(modelDisplay ? { modelDisplay } : {}),
111
+ };
112
+ return rt.route;
113
+ }
114
+
115
+ function routeHasModel() {
116
+ return !!clean(rt.route?.model);
117
+ }
118
+
119
+ function requireModelRoute() {
120
+ if (routeHasModel()) return;
121
+ throw new Error('No model configured. Open /providers to sign in, then /model to choose a model.');
122
+ }
123
+
124
+ async function recreateCurrentSessionIfReady() {
125
+ if (!routeHasModel()) {
126
+ rt.session = null;
127
+ return null;
128
+ }
129
+ return await createCurrentSession();
130
+ }
131
+
132
+ async function createCurrentSession(reason = 'demand') {
133
+ if (rt.sessionCreatePromise) return await rt.sessionCreatePromise;
134
+ if (rt.session?.id && !rt.sessionNeedsCwdRefresh) {
135
+ const liveSession = mgr.getSession(rt.session.id);
136
+ if (liveSession && liveSession.closed !== true && liveSession.status !== 'closed') {
137
+ rt.session = liveSession;
138
+ return rt.session;
139
+ }
140
+ rt.session = null;
141
+ }
142
+
143
+ const startedAt = performance.now();
144
+ bootProfile('session:create:start', { mode: rt.mode, reason });
145
+ const promise = (async () => {
146
+ // Demand-only: this starts only after the user submits (unless an
147
+ // explicitly enabled prewarm caller asks for a session). Core-memory
148
+ // startup does not depend on keychain/provider readiness, so overlap the
149
+ // two cold paths instead of paying their bounded waits serially.
150
+ const coreMemoryContextPromise = loadCoreMemoryContext();
151
+ await awaitKeychainPrewarm();
152
+ ensureConfigForRouteProvider();
153
+ await resolveMissingRouteModelForFirstTurn();
154
+ requireModelRoute();
155
+ bootProfile('session:create:route-ready', { ms: (performance.now() - startedAt).toFixed(1) });
156
+ // Route effort waits on provider readiness while the already-started
157
+ // memory load continues independently.
158
+ const [, coreMemoryContext] = await Promise.all([
159
+ refreshRouteEffort(),
160
+ coreMemoryContextPromise,
161
+ ]);
162
+ bootProfile('session:create:effort-ready', { ms: (performance.now() - startedAt).toFixed(1) });
163
+ const providerImpl = reg.getProvider(rt.route.provider);
164
+ if (!providerImpl) {
165
+ throw new Error(`Provider "${rt.route.provider}" is not configured.`);
166
+ }
167
+ bootProfile('session:create:provider-ready', { ms: (performance.now() - startedAt).toFixed(1) });
168
+ if (rt.closeRequested) throw new Error('runtime is closing');
169
+ const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
170
+ // Load the active WORKFLOW.md pack once for both summary + context block.
171
+ const { summary: workflow, context: workflowContext } = activeWorkflowContext(rt.config, dataDir);
172
+ const sessionOpts = {
173
+ provider: rt.route.provider,
174
+ model: rt.route.model,
175
+ preset: rt.route.preset || undefined,
176
+ tools: toolSpecForMode(rt.mode),
177
+ owner: 'cli',
178
+ agent: 'lead',
179
+ lane: 'cli',
180
+ sourceType: 'lead',
181
+ sourceName: 'main',
182
+ clientHostPid: process.pid,
183
+ disallowedTools: LEAD_DISALLOWED_TOOLS,
184
+ cwd: rt.currentCwd,
185
+ ...(rt.desktopSession && typeof rt.desktopSession === 'object' ? { desktopSession: rt.desktopSession } : {}),
186
+ coreMemoryContext,
187
+ workflow,
188
+ workflowContext,
189
+ fast: rt.route.fast === true,
190
+ compaction: rt.config.compaction && typeof rt.config.compaction === 'object'
191
+ ? normalizeCompactionConfig(rt.config.compaction, { memoryEnabled: memoryEnabled() })
192
+ : undefined,
193
+ };
194
+ if (hasOwn(rt.route, 'effort') || rt.route.effectiveEffort) {
195
+ sessionOpts.effort = rt.route.effectiveEffort || null;
196
+ }
197
+ rt.session = mgr.createSession(sessionOpts);
198
+ rt.sessionNeedsCwdRefresh = false;
199
+ attachSessionHooks(rt.session, { hooks, hookCommonPayload, getCwd: () => rt.currentCwd });
200
+ // Every-create MCP fold (NO blocking): seed the INITIAL provider-visible
201
+ // surface (and native BP1 manifest) from MCP servers connected at create
202
+ // time. There is no await — a boot connect still mid-handshake is caught on
203
+ // the first user turn by refreshInitialDeferredMcpSurface (session-turn-api),
204
+ // which re-folds the live registry into the first-turn surface before the
205
+ // prompt renders. This fold keeps recreate paths (cwd change with MCP
206
+ // already connected) seeding their manifest instead of re-announcing late.
207
+ let connectedMcpTools = [];
208
+ try { connectedMcpTools = mcpClient.getMcpTools?.() || []; }
209
+ catch { connectedMcpTools = []; }
210
+ applyDeferredToolSurface(
211
+ rt.session,
212
+ deferredSurfaceModeForLead(rt.mode),
213
+ connectedMcpTools.length ? [...modelStandaloneTools(), ...connectedMcpTools] : modelStandaloneTools(),
214
+ { provider: rt.route.provider },
215
+ );
216
+ // Session-local one-shot: mark this FRESH session eligible for the
217
+ // first-turn deferred-surface refresh (session-turn-api). A resumed
218
+ // session (prior transcript) is NEVER marked, so its already-baked BP1 is
219
+ // never rebuilt or re-announced — the gate is per-session, not the
220
+ // process-wide firstTurnCompleted.
221
+ rt.session.deferredInitialRefreshPending = !/resume/i.test(String(reason || ''));
222
+ applyPreSessionToolSelection();
223
+ writeStatuslineRoute(statusRoutes, rt.session, rt.route);
224
+ hooks.emit('session:create', { sessionId: rt.session.id, provider: rt.route.provider, model: rt.route.model, toolMode: rt.mode, cwd: rt.currentCwd });
225
+ // SessionStart: bridge to the standard project hook bus. Best-effort;
226
+ // a hook error must never break session creation. additionalContext is
227
+ // injected before the first user turn as a system-reminder context pair.
228
+ try {
229
+ const startSource = /resume/i.test(String(reason || ''))
230
+ ? 'resume'
231
+ : (/clear/i.test(String(reason || '')) ? 'clear' : 'startup');
232
+ const startDispatch = await hooks.dispatch('SessionStart', hookCommonPayload({ session_id: rt.session.id, source: startSource, model: rt.route.model }));
233
+ const startContext = Array.isArray(startDispatch?.additionalContext)
234
+ ? startDispatch.additionalContext.join('\n\n')
235
+ : String(startDispatch?.additionalContext || '');
236
+ if (startContext.trim()) {
237
+ rt.session.messages.push({ role: 'user', content: `<system-reminder>\n# SessionStart Hook Context\n${startContext.trim()}\n</system-reminder>` });
238
+ rt.session.messages.push({ role: 'assistant', content: '.' });
239
+ rt.session.updatedAt = Date.now();
240
+ }
241
+ } catch { /* best-effort: never break session create */ }
242
+ bootProfile('session:create:ready', {
243
+ ms: (performance.now() - startedAt).toFixed(1),
244
+ reason,
245
+ tools: Array.isArray(rt.session.tools) ? rt.session.tools.length : 0,
246
+ catalog: Array.isArray(rt.session.deferredToolCatalog) ? rt.session.deferredToolCatalog.length : 0,
247
+ });
248
+ // A rebind push may have been deferred (e.g. 'acquired' landed before this
249
+ // session existed). The writer is now bindable — flush it exactly once.
250
+ remoteTranscript.flushPendingTranscriptRebind();
251
+ return rt.session;
252
+ })();
253
+
254
+ rt.sessionCreatePromise = promise;
255
+ try {
256
+ return await promise;
257
+ } finally {
258
+ if (rt.sessionCreatePromise === promise) rt.sessionCreatePromise = null;
259
+ }
260
+ }
261
+
262
+ const {
263
+ scheduleProviderWarmup,
264
+ scheduleProviderSetupWarmup,
265
+ scheduleProviderModelWarmup,
266
+ scheduleModelCatalogWarmup,
267
+ scheduleStatuslineUsageWarmup,
268
+ scheduleStatuslineUsageRefresh,
269
+ } = createWarmupSchedulers({
270
+ timers: warmupTimers,
271
+ bootProfile,
272
+ getRoute: () => rt.route,
273
+ getConfig: () => rt.config,
274
+ isCloseRequested: () => rt.closeRequested,
275
+ getActiveTurnCount: () => rt.activeTurnCount,
276
+ getSessionCreatePromise: () => rt.sessionCreatePromise,
277
+ getProviderModelsCache: () => providerModelCaches.providerModelsCache,
278
+ getProviderModelsPromise: () => providerModelCaches.providerModelsPromise,
279
+ reloadFullConfig,
280
+ ensureConfigForRouteProvider,
281
+ awaitKeychainPrewarm,
282
+ ensureProvidersReady,
283
+ ensureProviderEnabled,
284
+ refreshStatuslineUsageSnapshot,
285
+ warmProviderModelCache,
286
+ cachedProviderSetup,
287
+ warmCatalogsInBackground,
288
+ isFirstTurnCompleted: () => rt.firstTurnCompleted,
289
+ isCatalogRefreshPending: () => rt.startupProviderCatalogRefreshPending,
290
+ envFlag,
291
+ delays: {
292
+ providerWarmupDelayMs,
293
+ providerSetupWarmupDelayMs,
294
+ providerModelWarmupDelayMs,
295
+ modelCatalogWarmupDelayMs,
296
+ statuslineUsageWarmupDelayMs,
297
+ statuslineUsageRefreshDelayMs,
298
+ backgroundBusyRetryMs,
299
+ },
300
+ flags: {
301
+ providerWarmupEnabled,
302
+ modelPrefetchEnabled,
303
+ modelCatalogWarmupEnabled,
304
+ },
305
+ });
306
+ rt.scheduleProviderModelWarmupRef = scheduleProviderModelWarmup;
307
+ rt.scheduleProviderSetupWarmupRef = scheduleProviderSetupWarmup;
308
+
309
+ const {
310
+ scheduleCodeGraphPrewarm,
311
+ scheduleLeadSessionPrewarm,
312
+ scheduleToolRuntimeWarmup,
313
+ invokeChannelStart,
314
+ scheduleChannelStart,
315
+ } = createPrewarmSchedulers({
316
+ timers: prewarmTimers,
317
+ bootProfile,
318
+ getCurrentCwd: () => rt.currentCwd,
319
+ isCloseRequested: () => rt.closeRequested,
320
+ getActiveTurnCount: () => rt.activeTurnCount,
321
+ getSessionCreatePromise: () => rt.sessionCreatePromise,
322
+ getSession: () => rt.session,
323
+ isRemoteEnabled: () => rt.remoteEnabled,
324
+ channelsEnabled,
325
+ hasActiveAutomation,
326
+ getCodeGraphModule,
327
+ createCurrentSession,
328
+ channels,
329
+ envFlag,
330
+ delays: {
331
+ codeGraphPrewarmDelayMs,
332
+ channelStartDelayMs,
333
+ sessionPrewarmDelayMs,
334
+ backgroundBusyRetryMs,
335
+ },
336
+ flags: {
337
+ codeGraphPrewarmEnabled,
338
+ sessionPrewarmEnabled,
339
+ },
340
+ state: prewarmState,
341
+ });
342
+
343
+ // Remote transcript binding + worker rebind pushes live in
344
+ // remote-transcript.mjs; the facade injects the mutable session/cwd/remote
345
+ // state it needs.
346
+ const remoteTranscript = createRemoteTranscript({
347
+ getSession: () => rt.session,
348
+ getCwd: () => rt.currentCwd,
349
+ isRemoteEnabled: () => rt.remoteEnabled,
350
+ getRemoteSessionId: () => rt.remoteSessionId,
351
+ setRemoteSessionId: (next) => { rt.remoteSessionId = next; },
352
+ isCloseRequested: () => rt.closeRequested,
353
+ channelsEnabled,
354
+ channels,
355
+ bootProfile,
356
+ });
357
+
358
+ return {
359
+ resolveMissingRouteModelForFirstTurn,
360
+ scheduleProviderWarmup,
361
+ scheduleProviderSetupWarmup,
362
+ scheduleProviderModelWarmup,
363
+ scheduleModelCatalogWarmup,
364
+ scheduleStatuslineUsageWarmup,
365
+ scheduleStatuslineUsageRefresh,
366
+ scheduleCodeGraphPrewarm,
367
+ scheduleLeadSessionPrewarm,
368
+ scheduleToolRuntimeWarmup,
369
+ invokeChannelStart,
370
+ scheduleChannelStart,
371
+ refreshRouteEffort,
372
+ routeHasModel,
373
+ requireModelRoute,
374
+ recreateCurrentSessionIfReady,
375
+ createCurrentSession,
376
+ remoteTranscript,
377
+ };
378
+ }
@@ -43,7 +43,7 @@ export function createSessionTurnApi(deps) {
43
43
  scheduleProviderWarmup, scheduleProviderModelWarmup, invalidateContextStatusCache,
44
44
  agentTool, recreateCurrentSessionIfReady, invalidatePreSessionToolSurface,
45
45
  activeToolSurface, applyResolvedCwd, resolveCwdPath, agentStatusState, notificationListeners,
46
- awaitInitialMcpConnect,
46
+ awaitInitialMcpConnect, awaitRoutePreparation,
47
47
  } = deps;
48
48
  const enqueueRemoteAttachedPrompt = (prompt) => {
49
49
  const attachedSession = getSession();
@@ -84,6 +84,10 @@ export function createSessionTurnApi(deps) {
84
84
  session: attachedSession,
85
85
  };
86
86
  }
87
+ // Historical-session resume publishes its transcript immediately while
88
+ // provider/model metadata initializes in the background. Only the next
89
+ // actual turn waits, guaranteeing that it cannot run on a stale route.
90
+ await awaitRoutePreparation?.();
87
91
  setActiveTurnCount(getActiveTurnCount() + 1);
88
92
  // Lazy code-graph prewarm: kick off the build ONCE, on the first real
89
93
  // turn, so a likely code lookup hits a warm cache.
@@ -115,7 +119,10 @@ export function createSessionTurnApi(deps) {
115
119
  void invokeChannelStart()
116
120
  .then(() => {
117
121
  if (!getRemoteEnabled() || getCloseRequested()) return undefined;
118
- return channels.execute('activate_channel_bridge', { active: true });
122
+ // A turn may refresh the current owner's transcript, but it
123
+ // must never acquire/override Remote implicitly.
124
+ pushTranscriptRebind();
125
+ return undefined;
119
126
  })
120
127
  .catch((error) => bootProfile('channels:turn-rebind-failed', { error: error?.message || String(error) }));
121
128
  }
@@ -165,6 +165,9 @@ export function createWorkflowAgentsApi(deps) {
165
165
  description: workflow.description,
166
166
  source: workflow.source,
167
167
  active: workflow.id === active,
168
+ // Distinguishes "agents: (empty) = Lead only" from "no agents key =
169
+ // all fixed agents" for surfaces that render the pack's agent set.
170
+ agentsConfigured: workflow.agentsConfigured === true,
168
171
  agents: workflow.agents,
169
172
  }));
170
173
  },