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.
- package/LICENSE +21 -0
- package/README.md +100 -34
- package/package.json +3 -2
- package/scripts/build-tui.mjs +6 -0
- package/scripts/hook-bus-test.mjs +8 -0
- package/scripts/log-writer-guard-smoke.mjs +131 -0
- package/scripts/reactive-compact-persist-smoke.mjs +22 -6
- package/scripts/recall-bench-cases.json +0 -1
- package/scripts/recall-quality-cases.json +1 -2
- package/scripts/session-ingest-compaction-smoke.mjs +241 -0
- package/scripts/tool-smoke.mjs +150 -45
- package/src/defaults/skills/setup/SKILL.md +327 -0
- package/src/help.mjs +2 -5
- package/src/lib/mixdog-debug.cjs +13 -0
- package/src/mixdog-session-runtime.mjs +7 -3328
- package/src/runtime/agent/orchestrator/context/collect.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -2
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -10
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +40 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +73 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +663 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop.mjs +8 -1860
- package/src/runtime/agent/orchestrator/session/manager/agent-runtime-singleton.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +686 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +60 -12
- package/src/runtime/agent/orchestrator/session/manager/env-utils.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +159 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +143 -0
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +124 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +258 -0
- package/src/runtime/agent/orchestrator/session/manager/session-errors.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-id.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +475 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +88 -2285
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +440 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +642 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +23 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +108 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +24 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/shell-policy-danger-target.mjs +5 -1
- package/src/runtime/channels/index.mjs +6 -2183
- package/src/runtime/channels/lib/inbound-handler.mjs +328 -0
- package/src/runtime/channels/lib/interaction-handlers.mjs +260 -0
- package/src/runtime/channels/lib/network-retry.mjs +23 -0
- package/src/runtime/channels/lib/owned-runtime.mjs +627 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +20 -9
- package/src/runtime/channels/lib/transcript-binding.mjs +336 -0
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +39 -2
- package/src/runtime/channels/lib/worker-bootstrap.mjs +97 -0
- package/src/runtime/channels/lib/worker-ipc.mjs +201 -0
- package/src/runtime/channels/lib/worker-main.mjs +777 -0
- package/src/runtime/memory/index.mjs +163 -1725
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -2
- package/src/runtime/memory/lib/embedding-worker.mjs +47 -6
- package/src/runtime/memory/lib/http-router.mjs +811 -0
- package/src/runtime/memory/lib/memory-action-handlers.mjs +901 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +6 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +46 -9
- package/src/runtime/memory/lib/memory-cycle2.mjs +87 -11
- package/src/runtime/memory/lib/memory-embed.mjs +28 -7
- package/src/runtime/memory/lib/memory-recall-store.mjs +4 -4
- package/src/runtime/memory/lib/memory.mjs +39 -0
- package/src/runtime/memory/lib/query-handlers.mjs +2 -2
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +401 -0
- package/src/runtime/shared/atomic-file.mjs +138 -80
- package/src/runtime/shared/child-guardian.mjs +61 -3
- package/src/session-runtime/boot-profile.mjs +36 -0
- package/src/session-runtime/channel-config-api.mjs +70 -0
- package/src/session-runtime/context-status.mjs +181 -0
- package/src/session-runtime/env.mjs +17 -0
- package/src/session-runtime/lifecycle-api.mjs +242 -0
- package/src/session-runtime/model-route-api.mjs +198 -0
- package/src/session-runtime/provider-auth-api.mjs +135 -0
- package/src/session-runtime/resource-api.mjs +282 -0
- package/src/session-runtime/runtime-core.mjs +2104 -0
- package/src/session-runtime/session-turn-api.mjs +274 -0
- package/src/session-runtime/tool-catalog.mjs +18 -264
- package/src/session-runtime/tool-defs.mjs +2 -2
- package/src/session-runtime/workflow-agents-api.mjs +238 -0
- package/src/standalone/agent-tool.mjs +2 -2
- package/src/standalone/channel-worker.mjs +67 -7
- package/src/standalone/folder-dialog.mjs +4 -1
- package/src/standalone/memory-runtime-proxy.mjs +154 -17
- package/src/standalone/seeds.mjs +28 -1
- package/src/tui/App.jsx +40 -28
- package/src/tui/app/core-memory-picker.mjs +1 -1
- package/src/tui/app/doctor.mjs +175 -0
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +9 -0
- package/src/tui/app/use-mouse-input.mjs +6 -0
- package/src/tui/app/use-transcript-scroll.mjs +77 -3
- package/src/tui/dist/index.mjs +2851 -2162
- package/src/tui/engine/context-state.mjs +145 -0
- package/src/tui/engine/prompt-history.mjs +27 -0
- package/src/tui/engine/session-api-ext.mjs +478 -0
- package/src/tui/engine/session-api.mjs +564 -0
- package/src/tui/engine/session-flow.mjs +485 -0
- package/src/tui/engine/turn.mjs +1078 -0
- package/src/tui/engine.mjs +68 -2620
- package/src/tui/index.jsx +7 -0
- package/vendor/ink/build/ink.js +16 -1
- package/vendor/ink/build/output.js +30 -4
- package/vendor/ink/build/render.js +5 -0
- 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
|
+
}
|