mixdog 0.9.19 → 0.9.21

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -34
  3. package/package.json +3 -2
  4. package/scripts/build-tui.mjs +6 -0
  5. package/scripts/hook-bus-test.mjs +8 -0
  6. package/scripts/log-writer-guard-smoke.mjs +131 -0
  7. package/scripts/reactive-compact-persist-smoke.mjs +22 -6
  8. package/scripts/recall-bench-cases.json +0 -1
  9. package/scripts/recall-quality-cases.json +1 -2
  10. package/scripts/session-ingest-compaction-smoke.mjs +241 -0
  11. package/scripts/tool-smoke.mjs +150 -45
  12. package/src/defaults/skills/setup/SKILL.md +327 -0
  13. package/src/help.mjs +2 -5
  14. package/src/lib/mixdog-debug.cjs +13 -0
  15. package/src/mixdog-session-runtime.mjs +7 -3328
  16. package/src/runtime/agent/orchestrator/context/collect.mjs +33 -9
  17. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -2
  18. package/src/runtime/agent/orchestrator/providers/gemini.mjs +3 -0
  19. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -10
  20. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +3 -0
  21. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +40 -3
  22. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +73 -3
  23. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +663 -0
  24. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +6 -0
  25. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +153 -0
  26. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +49 -0
  27. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -1
  28. package/src/runtime/agent/orchestrator/session/loop.mjs +8 -1860
  29. package/src/runtime/agent/orchestrator/session/manager/agent-runtime-singleton.mjs +29 -0
  30. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +686 -0
  31. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +60 -12
  32. package/src/runtime/agent/orchestrator/session/manager/env-utils.mjs +8 -0
  33. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +159 -0
  34. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +143 -0
  35. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +268 -0
  36. package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +22 -0
  37. package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +26 -0
  38. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +124 -0
  39. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +258 -0
  40. package/src/runtime/agent/orchestrator/session/manager/session-errors.mjs +20 -0
  41. package/src/runtime/agent/orchestrator/session/manager/session-id.mjs +9 -0
  42. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +475 -0
  43. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +23 -0
  44. package/src/runtime/agent/orchestrator/session/manager.mjs +88 -2285
  45. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +440 -0
  46. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +153 -0
  47. package/src/runtime/agent/orchestrator/session/store.mjs +4 -1
  48. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +642 -0
  49. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +23 -3
  50. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +108 -2
  51. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +15 -3
  52. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +7 -0
  53. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +24 -2
  54. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
  55. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +72 -8
  56. package/src/runtime/agent/orchestrator/tools/shell-policy-danger-target.mjs +5 -1
  57. package/src/runtime/channels/index.mjs +6 -2183
  58. package/src/runtime/channels/lib/inbound-handler.mjs +328 -0
  59. package/src/runtime/channels/lib/interaction-handlers.mjs +260 -0
  60. package/src/runtime/channels/lib/network-retry.mjs +23 -0
  61. package/src/runtime/channels/lib/owned-runtime.mjs +627 -0
  62. package/src/runtime/channels/lib/runtime-paths.mjs +20 -9
  63. package/src/runtime/channels/lib/transcript-binding.mjs +336 -0
  64. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +39 -2
  65. package/src/runtime/channels/lib/worker-bootstrap.mjs +97 -0
  66. package/src/runtime/channels/lib/worker-ipc.mjs +201 -0
  67. package/src/runtime/channels/lib/worker-main.mjs +777 -0
  68. package/src/runtime/memory/index.mjs +163 -1725
  69. package/src/runtime/memory/lib/embedding-provider.mjs +4 -2
  70. package/src/runtime/memory/lib/embedding-worker.mjs +47 -6
  71. package/src/runtime/memory/lib/http-router.mjs +811 -0
  72. package/src/runtime/memory/lib/memory-action-handlers.mjs +901 -0
  73. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +6 -0
  74. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +46 -9
  75. package/src/runtime/memory/lib/memory-cycle2.mjs +87 -11
  76. package/src/runtime/memory/lib/memory-embed.mjs +28 -7
  77. package/src/runtime/memory/lib/memory-recall-store.mjs +4 -4
  78. package/src/runtime/memory/lib/memory.mjs +39 -0
  79. package/src/runtime/memory/lib/query-handlers.mjs +2 -2
  80. package/src/runtime/memory/lib/session-ingest-runtime.mjs +401 -0
  81. package/src/runtime/shared/atomic-file.mjs +138 -80
  82. package/src/runtime/shared/child-guardian.mjs +61 -3
  83. package/src/session-runtime/boot-profile.mjs +36 -0
  84. package/src/session-runtime/channel-config-api.mjs +70 -0
  85. package/src/session-runtime/context-status.mjs +181 -0
  86. package/src/session-runtime/env.mjs +17 -0
  87. package/src/session-runtime/lifecycle-api.mjs +242 -0
  88. package/src/session-runtime/model-route-api.mjs +198 -0
  89. package/src/session-runtime/provider-auth-api.mjs +135 -0
  90. package/src/session-runtime/resource-api.mjs +282 -0
  91. package/src/session-runtime/runtime-core.mjs +2104 -0
  92. package/src/session-runtime/session-turn-api.mjs +274 -0
  93. package/src/session-runtime/tool-catalog.mjs +18 -264
  94. package/src/session-runtime/tool-defs.mjs +2 -2
  95. package/src/session-runtime/workflow-agents-api.mjs +238 -0
  96. package/src/standalone/agent-tool.mjs +2 -2
  97. package/src/standalone/channel-worker.mjs +67 -7
  98. package/src/standalone/folder-dialog.mjs +4 -1
  99. package/src/standalone/memory-runtime-proxy.mjs +154 -17
  100. package/src/standalone/seeds.mjs +28 -1
  101. package/src/tui/App.jsx +40 -28
  102. package/src/tui/app/core-memory-picker.mjs +1 -1
  103. package/src/tui/app/doctor.mjs +175 -0
  104. package/src/tui/app/slash-commands.mjs +1 -0
  105. package/src/tui/app/slash-dispatch.mjs +9 -0
  106. package/src/tui/app/use-mouse-input.mjs +6 -0
  107. package/src/tui/app/use-transcript-scroll.mjs +77 -3
  108. package/src/tui/dist/index.mjs +2851 -2162
  109. package/src/tui/engine/context-state.mjs +145 -0
  110. package/src/tui/engine/prompt-history.mjs +27 -0
  111. package/src/tui/engine/session-api-ext.mjs +478 -0
  112. package/src/tui/engine/session-api.mjs +564 -0
  113. package/src/tui/engine/session-flow.mjs +485 -0
  114. package/src/tui/engine/turn.mjs +1078 -0
  115. package/src/tui/engine.mjs +68 -2620
  116. package/src/tui/index.jsx +7 -0
  117. package/vendor/ink/build/ink.js +16 -1
  118. package/vendor/ink/build/output.js +30 -4
  119. package/vendor/ink/build/render.js +5 -0
  120. package/src/workflows/sequential/WORKFLOW.md +0 -51
@@ -0,0 +1,201 @@
1
+ import { performance } from "perf_hooks";
2
+ // IPC worker-mode message loop extracted from channels/index.mjs
3
+ // (behavior-preserving). Installs shutdown handlers, the parent->worker message
4
+ // router, and the retrying start() bootstrap. Call once from the worker entry
5
+ // when _isWorkerMode && process.send.
6
+ export function runWorkerIpc({
7
+ start,
8
+ stop,
9
+ stopVoiceWhisperServer,
10
+ cleanupInstanceRuntimeFiles,
11
+ clearServerPid,
12
+ instanceId,
13
+ statusState,
14
+ getBackend,
15
+ getConfig,
16
+ pendingPermRequests,
17
+ refreshToolExecConsumerMarker,
18
+ handleMemoryCallResponse,
19
+ handleToolCallWithBridgeRetry,
20
+ bootProfile,
21
+ }) {
22
+ // SIGTERM/SIGINT/IPC shutdown handler — mirrors src/memory/index.mjs pattern.
23
+ // Cleans up in-progress webhook/scheduler state, removes runtime files, then exits.
24
+ let _channelsStopInFlight = false
25
+ let _channelsForceExitTimer = null
26
+ const _channelsShutdownHandler = async (sig) => {
27
+ if (_channelsStopInFlight) {
28
+ process.stderr.write(`[channels-worker] ${sig} — shutdown already in flight, ignoring\n`)
29
+ return
30
+ }
31
+ _channelsStopInFlight = true
32
+ process.stderr.write(`[channels-worker] received ${sig} — shutting down cleanly\n`)
33
+ _channelsForceExitTimer = setTimeout(() => {
34
+ process.stderr.write(`[channels-worker] stop() timed out after 6000ms — forcing exit(2)\n`)
35
+ process.exit(2)
36
+ }, 6000)
37
+ try { await stopVoiceWhisperServer() } catch (e) {
38
+ process.stderr.write(`[channels-worker] stopVoiceWhisperServer() error on ${sig}: ${e && (e.message || e)}\n`)
39
+ }
40
+ try { await stop() } catch (e) {
41
+ process.stderr.write(`[channels-worker] stop() error on ${sig}: ${e && (e.message || e)}\n`)
42
+ }
43
+ if (_channelsForceExitTimer) clearTimeout(_channelsForceExitTimer)
44
+ try { cleanupInstanceRuntimeFiles(instanceId) } catch {}
45
+ try { clearServerPid() } catch {}
46
+ process.exit(0)
47
+ }
48
+ process.on('SIGTERM', () => _channelsShutdownHandler('SIGTERM'))
49
+ process.on('SIGINT', () => _channelsShutdownHandler('SIGINT'))
50
+
51
+ // Map of callId → AbortController for in-flight IPC calls.
52
+ const _inFlightChannelCalls = new Map()
53
+
54
+ process.on('message', async (msg) => {
55
+ // Parent-initiated graceful shutdown — mirrors memory worker IPC pattern.
56
+ if (msg && msg.type === 'shutdown') {
57
+ process.stderr.write('[channels-worker] received IPC shutdown — calling stop()\n')
58
+ _channelsShutdownHandler('IPC:shutdown')
59
+ return
60
+ }
61
+ // Silent-to-agent lifecycle forward — parent (server.mjs) asks the
62
+ // channels worker to post status pings to the active bridge Discord
63
+ // channel without the Lead-notify hop. Best-effort: unknown channel or
64
+ // getBackend() failure is swallowed; lifecycle pings are non-critical.
65
+ if (msg && msg.type === 'forward_to_discord') {
66
+ try {
67
+ const target = msg.channelId
68
+ || (statusState?.read?.().channelId)
69
+ || null;
70
+ if (target && getBackend()?.sendMessage && typeof msg.content === 'string' && msg.content) {
71
+ await getBackend().sendMessage(target, msg.content).catch(() => {});
72
+ }
73
+ } catch { /* best-effort */ }
74
+ return;
75
+ }
76
+ // Host permission request → Discord Allow/Deny prompt.
77
+ // Parent (server.mjs) receives notifications/claude/channel/permission_request
78
+ // from the MCP host and forwards the params here. We post a buttoned message;
79
+ // button clicks are handled in getBackend().onInteraction and sent back to CC as
80
+ // notifications/claude/channel/permission via sendNotifyToParent.
81
+ if (msg && msg.type === 'permission_request_inbound') {
82
+ try {
83
+ const { request_id, tool_name, description, input_preview } = msg.params || {};
84
+ // tool_input arrives via the passthrough() schema in server.mjs when
85
+ // The host includes it in the permission_request notification.
86
+ // Used to bind the pendingPermRequest to a specific file so two
87
+ // concurrent Edit/Write requests cannot cross-approve via the
88
+ // terminal signal.
89
+ const toolInputParam = (msg.params && (msg.params.tool_input || msg.params.toolInput)) || {};
90
+ const filePathParam = toolInputParam.file_path || '';
91
+ if (!request_id || !tool_name) return;
92
+ if (pendingPermRequests.size > 100) {
93
+ const cutoff = Date.now() - 30 * 60 * 1000;
94
+ for (const [k, v] of pendingPermRequests) {
95
+ if (v.createdAt < cutoff) pendingPermRequests.delete(k);
96
+ }
97
+ refreshToolExecConsumerMarker();
98
+ }
99
+ const target = (statusState?.read?.().channelId)
100
+ || getConfig()?.channelId
101
+ || null;
102
+ if (!target || !getBackend()?.sendMessage) {
103
+ process.stderr.write(`mixdog channels: permission_request dropped, no target channel (request_id=${request_id})\n`);
104
+ return;
105
+ }
106
+ const lines = [`🔐 **Permission Request**`, `Tool: \`${tool_name}\``];
107
+ if (description) lines.push(description);
108
+ if (input_preview) lines.push('```\n' + String(input_preview).slice(0, 800) + '\n```');
109
+ const content = lines.join('\n');
110
+ const components = [{
111
+ type: 1,
112
+ components: [
113
+ { type: 2, style: 3, label: 'Allow', custom_id: `perm-ch-${request_id}-allow` },
114
+ { type: 2, style: 1, label: 'Session Allow', custom_id: `perm-ch-${request_id}-session` },
115
+ { type: 2, style: 4, label: 'Deny', custom_id: `perm-ch-${request_id}-deny` },
116
+ ],
117
+ }];
118
+ let sentIds = null;
119
+ try {
120
+ const sendResult = await getBackend().sendMessage(target, content, { components });
121
+ sentIds = sendResult?.sentIds;
122
+ } catch (err) {
123
+ process.stderr.write(`mixdog channels: permission_request Discord send failed: ${err && err.message || err}\n`);
124
+ return;
125
+ }
126
+ const messageId = Array.isArray(sentIds) && sentIds.length > 0 ? sentIds[0] : null;
127
+ pendingPermRequests.set(request_id, {
128
+ toolName: tool_name,
129
+ filePath: filePathParam,
130
+ createdAt: Date.now(),
131
+ channelId: target,
132
+ messageId,
133
+ });
134
+ refreshToolExecConsumerMarker();
135
+ } catch (err) {
136
+ try { process.stderr.write(`mixdog channels: permission_request handler error: ${err && err.message || err}\n`); } catch {}
137
+ }
138
+ return;
139
+ }
140
+ if (handleMemoryCallResponse(msg)) return;
141
+ if (msg.type === 'cancel' && msg.callId) {
142
+ const entry = _inFlightChannelCalls.get(msg.callId)
143
+ if (entry) {
144
+ entry.abort()
145
+ _inFlightChannelCalls.delete(msg.callId)
146
+ }
147
+ process.send({ type: 'result', callId: msg.callId, error: 'cancelled' })
148
+ return
149
+ }
150
+ if (msg.type !== 'call' || !msg.callId) return
151
+ try {
152
+ const ac = new AbortController()
153
+ _inFlightChannelCalls.set(msg.callId, ac)
154
+ let result
155
+ try {
156
+ result = await handleToolCallWithBridgeRetry(msg.name, msg.args || {}, ac.signal)
157
+ } finally {
158
+ _inFlightChannelCalls.delete(msg.callId)
159
+ }
160
+ process.send({ type: 'result', callId: msg.callId, result })
161
+ } catch (e) {
162
+ process.send({ type: 'result', callId: msg.callId, error: e.message })
163
+ }
164
+ })
165
+ void (async () => {
166
+ const startedAt = performance.now()
167
+ const MAX_START_ATTEMPTS = 3
168
+ const BASE_BACKOFF_MS = 250
169
+ const isTransientStartErr = (err) =>
170
+ err?.code === 'ELOCKTIMEOUT' || /atomic lock timeout/i.test(err?.message || '')
171
+ let lastErr
172
+ for (let attempt = 1; attempt <= MAX_START_ATTEMPTS; attempt++) {
173
+ if (_channelsStopInFlight) return
174
+ try {
175
+ await start()
176
+ bootProfile("worker:ready", { ms: (performance.now() - startedAt).toFixed(1), attempt })
177
+ process.send({ type: 'ready' })
178
+ return
179
+ } catch (e) {
180
+ lastErr = e
181
+ const transient = isTransientStartErr(e)
182
+ bootProfile("worker:start-failed", { attempt, transient, error: e?.message || String(e) })
183
+ process.stderr.write(`[channels-worker] start() failed (attempt ${attempt}/${MAX_START_ATTEMPTS}, transient=${transient}): ${e && (e.message || e)}\n`)
184
+ if (!transient || attempt >= MAX_START_ATTEMPTS) break
185
+ const backoff = BASE_BACKOFF_MS * attempt + Math.floor(Math.random() * BASE_BACKOFF_MS)
186
+ await new Promise((r) => setTimeout(r, backoff))
187
+ if (_channelsStopInFlight) return
188
+ }
189
+ }
190
+ // A stop landed while we were failing — let clean shutdown proceed, never exit over it.
191
+ if (_channelsStopInFlight) return
192
+ // Terminal failure: do NOT mask as a (degraded) ready. Exit non-zero so the
193
+ // parent's exit-before-ready path respawns or rejects startRemote instead of
194
+ // silently losing remote output forwarding.
195
+ bootProfile("worker:failed", { ms: (performance.now() - startedAt).toFixed(1), error: lastErr?.message || String(lastErr) })
196
+ process.stderr.write(`[channels-worker] start() giving up after ${MAX_START_ATTEMPTS} attempts: ${lastErr && (lastErr.message || lastErr)}\n`)
197
+ // Exit 2 = terminal (non-transient) start failure: parent must reject, not respawn.
198
+ // Exit 1 = exhausted transient retries: parent may respawn.
199
+ process.exit(isTransientStartErr(lastErr) ? 1 : 2)
200
+ })()
201
+ }