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
@@ -91,6 +91,40 @@ export function createPrewarmSchedulers({
91
91
  timer.unref?.();
92
92
  }
93
93
 
94
+ // Tool-runtime warmup: the per-call cold starts a session's FIRST tool call
95
+ // would otherwise pay — pwsh standby processes (~300ms each) and the o200k
96
+ // encoder + token-count worker (~100ms). Each part is independent and
97
+ // best-effort; failures only mark the boot profile.
98
+ function scheduleToolRuntimeWarmup(delayMs = 2500) {
99
+ if (envFlag('MIXDOG_DISABLE_TOOL_PREWARM')) {
100
+ bootProfile('tool-runtime:prewarm-skipped');
101
+ return;
102
+ }
103
+ const timer = setTimeout(() => void (async () => {
104
+ if (isCloseRequested()) return;
105
+ try {
106
+ const { prewarmShellStandbys } = await import('../runtime/agent/orchestrator/tools/builtin/bash-tool.mjs');
107
+ bootProfile('tool-runtime:shell-standby', { warmed: prewarmShellStandbys() === true });
108
+ } catch (error) {
109
+ bootProfile('tool-runtime:shell-standby-failed', { error: error?.message || String(error) });
110
+ }
111
+ try {
112
+ const { prewarmTokenEstimator } = await import('../runtime/agent/orchestrator/session/context-utils.mjs');
113
+ bootProfile('tool-runtime:token-estimator', { warmed: prewarmTokenEstimator() === true });
114
+ } catch (error) {
115
+ bootProfile('tool-runtime:token-estimator-failed', { error: error?.message || String(error) });
116
+ }
117
+ try {
118
+ const { prewarmAgentShard } = await import('../standalone/agent-shard/shard-pool.mjs');
119
+ const warmed = await prewarmAgentShard();
120
+ if (warmed) bootProfile('tool-runtime:agent-shard', { warmed: true });
121
+ } catch (error) {
122
+ bootProfile('tool-runtime:agent-shard-failed', { error: error?.message || String(error) });
123
+ }
124
+ })(), delayMs);
125
+ timer.unref?.();
126
+ }
127
+
94
128
  function invokeChannelStart() {
95
129
  if (state.channelStartPromise) return state.channelStartPromise;
96
130
  const startedAt = performance.now();
@@ -142,6 +176,7 @@ export function createPrewarmSchedulers({
142
176
  return {
143
177
  scheduleCodeGraphPrewarm,
144
178
  scheduleLeadSessionPrewarm,
179
+ scheduleToolRuntimeWarmup,
145
180
  invokeChannelStart,
146
181
  scheduleChannelStart,
147
182
  };
@@ -31,6 +31,7 @@ export function createProviderAuthApi({
31
31
  refreshProviderCatalogs,
32
32
  cachedProviderSetup,
33
33
  getUsageDashboard,
34
+ consumeCodexRateLimitResetCredit,
34
35
  collectProviderModels,
35
36
  }) {
36
37
  function refreshProviderCatalogsSoon() {
@@ -79,6 +80,13 @@ export function createProviderAuthApi({
79
80
  async getUsageDashboard(options = {}) {
80
81
  return await getUsageDashboard(options);
81
82
  },
83
+ async consumeCodexRateLimitResetCredit(options = {}) {
84
+ await awaitKeychainPrewarm();
85
+ if (typeof consumeCodexRateLimitResetCredit !== 'function') {
86
+ throw new Error('Codex reset is unavailable');
87
+ }
88
+ return await consumeCodexRateLimitResetCredit(options);
89
+ },
82
90
  async authenticateProvider(providerId, secret) {
83
91
  await awaitKeychainPrewarm();
84
92
  const result = String(secret || '').trim()
@@ -14,6 +14,7 @@ export function createProviderUsage({
14
14
  providerSetup,
15
15
  createUsageDashboard,
16
16
  fetchOAuthUsageSnapshot,
17
+ consumeOpenAICodexResetCredit,
17
18
  isCloseRequested,
18
19
  getProviderSetupWarmupTimer,
19
20
  scheduleProviderSetupWarmup,
@@ -64,8 +65,9 @@ export function createProviderUsage({
64
65
  }
65
66
 
66
67
  async function getUsageDashboard(options = {}) {
67
- const forceSetup = options?.force === true || options?.refresh === true;
68
- if (!forceSetup && caches.usageDashboardCache.dashboard) {
68
+ const refreshUsage = options?.refresh === true;
69
+ const forceSetup = options?.force === true || (refreshUsage && options?.refreshSetup !== false);
70
+ if (!forceSetup && !refreshUsage && caches.usageDashboardCache.dashboard) {
69
71
  const cached = {
70
72
  ...caches.usageDashboardCache.dashboard,
71
73
  refresh: false,
@@ -78,7 +80,7 @@ export function createProviderUsage({
78
80
  }
79
81
  return cached;
80
82
  }
81
- if (!forceSetup && caches.usageDashboardPromise) return await caches.usageDashboardPromise;
83
+ if (!forceSetup && !refreshUsage && caches.usageDashboardPromise) return await caches.usageDashboardPromise;
82
84
  const quickSetup = options?.quickSetup !== false;
83
85
  const getProvider = (providerId) => reg().getProvider(providerId);
84
86
  const log = (message) => {
@@ -98,16 +100,26 @@ export function createProviderUsage({
98
100
  });
99
101
  }
100
102
  const buildDashboard = async () => {
103
+ let setup;
104
+ try {
105
+ setup = await cachedProviderSetup({ force: forceSetup, quick: false });
106
+ } catch {
107
+ // One unavailable keychain/provider descriptor must not take down the
108
+ // whole dashboard. The no-secrets/no-local snapshot still lets cached
109
+ // and provider-native quota windows refresh.
110
+ log('provider setup failed; falling back to quick setup');
111
+ setup = await cachedProviderSetup({ force: forceSetup, quick: true });
112
+ }
101
113
  const dashboard = await createUsageDashboard(displayConfig(), {
102
114
  ...(options || {}),
103
- setup: await cachedProviderSetup({ force: forceSetup, quick: false }),
115
+ setup,
104
116
  getProvider,
105
117
  log,
106
118
  });
107
119
  caches.usageDashboardCache = { dashboard, at: Date.now() };
108
120
  return dashboard;
109
121
  };
110
- if (forceSetup) return await buildDashboard();
122
+ if (forceSetup || refreshUsage) return await buildDashboard();
111
123
  caches.usageDashboardPromise = buildDashboard()
112
124
  .finally(() => {
113
125
  caches.usageDashboardPromise = null;
@@ -115,6 +127,18 @@ export function createProviderUsage({
115
127
  return await caches.usageDashboardPromise;
116
128
  }
117
129
 
130
+ async function consumeCodexRateLimitResetCredit(options = {}) {
131
+ if (typeof consumeOpenAICodexResetCredit !== 'function') {
132
+ throw new Error('Codex reset is unavailable');
133
+ }
134
+ const providerObj = reg().getProvider('openai-oauth');
135
+ if (!providerObj) throw new Error('Codex is not signed in');
136
+ const result = await consumeOpenAICodexResetCredit(providerObj, options);
137
+ caches.usageDashboardCache = {};
138
+ const dashboard = await getUsageDashboard({ refresh: true, refreshSetup: false });
139
+ return { ...result, dashboard };
140
+ }
141
+
118
142
  return {
119
143
  refreshStatuslineUsageSnapshot,
120
144
  cachedProviderSetup,
@@ -122,5 +146,6 @@ export function createProviderUsage({
122
146
  // (settings hydration) can serve the quick snapshot until then.
123
147
  hasProviderSetupCached: () => Boolean(caches.providerSetupCache.setup),
124
148
  getUsageDashboard,
149
+ consumeCodexRateLimitResetCredit,
125
150
  };
126
151
  }
@@ -10,8 +10,6 @@ export function createRemoteControl({
10
10
  setRemoteEnabled,
11
11
  getRemoteSessionId,
12
12
  setRemoteSessionId,
13
- setRemoteClaimPending,
14
- isRemoteClaimPending,
15
13
  isCloseRequested,
16
14
  clearChannelStartTimer,
17
15
  remoteTransitions,
@@ -28,22 +26,11 @@ export function createRemoteControl({
28
26
  bootProfile,
29
27
  envFlag,
30
28
  }) {
31
- // FORCE-TAKEOVER for an explicit claim: it always (re)claims the seat and
32
- // rebinds forwarding to this session, even when the worker already runs
33
- // (`/remote` re-issued after another session took the seat).
34
- function startRemote(options = {}) {
35
- const intent = options?.intent === 'auto' ? 'auto' : 'explicit';
36
- if (intent === 'auto') {
37
- // Auto-start attempts a claim-if-vacant: remoteEnabled flips only when the
38
- // worker reports it actually acquired the seat, so a live owner elsewhere
39
- // leaves this session non-remote.
40
- setRemoteClaimPending(true);
41
- } else {
42
- setRemoteEnabled(true);
43
- // Explicit toggle: the CURRENT session owns the relay (lazy create adopts
44
- // inside ensureRemoteTranscriptWriter when no session exists yet).
45
- setRemoteSessionId(getSession()?.id || null);
46
- }
29
+ // Manual ON always targets the current session and explicitly overrides any
30
+ // previous manual owner. There is no auto/claim-if-vacant path.
31
+ function startRemote() {
32
+ setRemoteEnabled(true);
33
+ setRemoteSessionId(getSession()?.id || null);
47
34
  // The worker forwards transcript ingests to the memory service, so its port
48
35
  // must be live before the first ingest.
49
36
  startMemoryDaemonEagerly({ getMemoryModule, bootProfile });
@@ -65,7 +52,6 @@ export function createRemoteControl({
65
52
  // boots the worker, which runs headless when messaging is off.
66
53
  if (!channelsEnabled() && !(await hasActiveAutomation().catch(() => false))) {
67
54
  bootProfile('channels:start-disabled');
68
- setRemoteClaimPending(false);
69
55
  return isRemoteEnabled();
70
56
  }
71
57
  // A backend switch may still be writing; drain it before the worker
@@ -83,39 +69,16 @@ export function createRemoteControl({
83
69
  ensureRemoteTranscriptWriter();
84
70
  // Re-check after the awaits: stop/supersede or runtime close may have
85
71
  // landed mid-chain — never boot for a session that turned remote off.
86
- if ((!isRemoteEnabled() && !isRemoteClaimPending()) || isCloseRequested()) {
87
- setRemoteClaimPending(false);
88
- return false;
89
- }
90
- // The fork reads process.env synchronously, so set the inherited intent
91
- // immediately before it and restore right after: the pollution window is
92
- // this fork instead of the whole boot chain.
93
- const previousIntent = process.env.MIXDOG_REMOTE_INTENT;
94
- try {
95
- process.env.MIXDOG_REMOTE_INTENT = intent;
96
- await invokeChannelStart();
97
- } finally {
98
- if (previousIntent === undefined) delete process.env.MIXDOG_REMOTE_INTENT;
99
- else process.env.MIXDOG_REMOTE_INTENT = previousIntent;
100
- }
101
- if ((!isRemoteEnabled() && !isRemoteClaimPending()) || isCloseRequested()) {
102
- setRemoteClaimPending(false);
103
- return false;
104
- }
105
- // Explicit claims are unconditional; auto-start dispatches the same
106
- // activation as claim-if-vacant so it promotes an automation-only daemon
107
- // without stealing a live remote owner.
72
+ if (!isRemoteEnabled() || isCloseRequested()) return false;
73
+ await invokeChannelStart();
74
+ if (!isRemoteEnabled() || isCloseRequested()) return false;
108
75
  await channels.execute('activate_channel_bridge', {
109
76
  active: true,
110
- claimIfVacant: intent === 'auto',
111
77
  sessionId: getSession()?.id || getRemoteSessionId() || null,
112
78
  transcriptPath: getTranscriptPath(),
113
79
  });
114
- // The worker's acquire/supersede notification now owns the transition.
115
- setRemoteClaimPending(false);
116
80
  return isRemoteEnabled();
117
81
  } catch (error) {
118
- setRemoteClaimPending(false);
119
82
  if (isRemoteEnabled()) {
120
83
  setRemoteEnabled(false);
121
84
  setRemoteSessionId(null);
@@ -135,7 +98,6 @@ export function createRemoteControl({
135
98
  const releasingSessionId = getRemoteSessionId() || getSession()?.id || null;
136
99
  setRemoteEnabled(false);
137
100
  setRemoteSessionId(null);
138
- setRemoteClaimPending(false);
139
101
  clearChannelStartTimer();
140
102
  return remoteTransitions.run(async () => {
141
103
  try {
@@ -156,7 +118,6 @@ export function createRemoteControl({
156
118
  function stopRemote(reason) {
157
119
  setRemoteEnabled(false);
158
120
  setRemoteSessionId(null);
159
- setRemoteClaimPending(false);
160
121
  clearChannelStartTimer();
161
122
  channels.stop(reason || 'remote-disabled').catch(() => {});
162
123
  return true;
@@ -17,7 +17,6 @@ export function createRemoteTranscript({
17
17
  isCloseRequested,
18
18
  channelsEnabled,
19
19
  channels,
20
- mgr,
21
20
  bootProfile,
22
21
  }) {
23
22
  let writer = null;
@@ -36,9 +35,8 @@ export function createRemoteTranscript({
36
35
  flushPendingTranscriptRebind,
37
36
  };
38
37
 
39
- // Bind (or refresh) the writer for the CURRENT session+cwd. The owner gate
40
- // covers lazy create: an unset owner adopts this session, a LIVE owner blocks
41
- // every other session, and a dead owner hands the relay over.
38
+ // Bind (or refresh) only the manually selected session. Session creation,
39
+ // close, or liveness never transfers Remote ownership.
42
40
  function ensureRemoteTranscriptWriter() {
43
41
  const session = getSession();
44
42
  if (!isRemoteEnabled() || !session?.id) return false;
@@ -46,9 +44,7 @@ export function createRemoteTranscript({
46
44
  if (!owner) {
47
45
  setRemoteSessionId(session.id);
48
46
  } else if (session.id !== owner) {
49
- const ownerSession = mgr.getSession(owner);
50
- if (ownerSession && ownerSession.closed !== true && ownerSession.status !== 'closed') return false;
51
- setRemoteSessionId(session.id);
47
+ return false;
52
48
  }
53
49
  const cwd = getCwd();
54
50
  const key = `${session.id}\u0000${cwd}`;
@@ -0,0 +1,24 @@
1
+ // One background route preparation can be associated with the currently
2
+ // resumed session. Resume/navigation reads do not wait for it; the next real
3
+ // turn does. Clearing replaces the visible gate without attempting to cancel
4
+ // provider initialization that may already be shared elsewhere.
5
+ export function createRoutePreparationGate({ onError } = {}) {
6
+ let pending = Promise.resolve();
7
+
8
+ return {
9
+ clear() {
10
+ pending = Promise.resolve();
11
+ },
12
+ start(task) {
13
+ const operation = Promise.resolve().then(task);
14
+ pending = operation;
15
+ // Keep the original rejecting promise for waiters while still marking a
16
+ // background failure as observed until a submit reaches the same gate.
17
+ void operation.catch((error) => onError?.(error));
18
+ return operation;
19
+ },
20
+ wait() {
21
+ return pending;
22
+ },
23
+ };
24
+ }