mixdog 0.9.24 → 0.9.26
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/package.json +2 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/explore-bench-tmp.mjs +1 -1
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/steering-fold-provenance-test.mjs +71 -0
- package/scripts/tool-efficiency-diag.mjs +1 -1
- package/scripts/webhook-smoke.mjs +46 -53
- package/src/defaults/cycle3-review-prompt.md +11 -4
- package/src/defaults/memory-promote-prompt.md +9 -0
- package/src/defaults/skills/setup/SKILL.md +6 -1
- package/src/rules/lead/02-channels.md +3 -3
- package/src/rules/shared/01-tool.md +8 -4
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +17 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +28 -10
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +51 -15
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +164 -9
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +7 -3
- package/src/runtime/channels/backends/discord.mjs +10 -3
- package/src/runtime/channels/lib/config.mjs +13 -11
- package/src/runtime/channels/lib/inbound-handler.mjs +45 -1
- package/src/runtime/channels/lib/memory-client.mjs +22 -2
- package/src/runtime/channels/lib/owned-runtime.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +226 -208
- package/src/runtime/channels/lib/status-snapshot.mjs +16 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +7 -318
- package/src/runtime/channels/lib/webhook.mjs +98 -150
- package/src/runtime/channels/lib/worker-main.mjs +1 -1
- package/src/runtime/memory/index.mjs +50 -25
- package/src/runtime/memory/lib/cycle-scheduler.mjs +20 -2
- package/src/runtime/memory/lib/memory-config-flags.mjs +13 -1
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +59 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +10 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +79 -9
- package/src/runtime/memory/lib/pg/adapter.mjs +6 -1
- package/src/runtime/memory/lib/pg/process.mjs +19 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +125 -14
- package/src/runtime/memory/lib/query-handlers.mjs +102 -0
- package/src/runtime/memory/lib/recall-format.mjs +60 -22
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +20 -6
- package/src/runtime/memory/tool-defs.mjs +1 -1
- package/src/runtime/shared/child-guardian.mjs +2 -2
- package/src/runtime/shared/open-url.mjs +2 -1
- package/src/runtime/shared/schedules-db.mjs +350 -0
- package/src/runtime/shared/service-discovery.mjs +169 -0
- package/src/runtime/shared/spawn-flags.mjs +27 -0
- package/src/runtime/shared/tool-primitives.mjs +3 -1
- package/src/runtime/shared/tool-surface.mjs +19 -3
- package/src/runtime/shared/update-checker.mjs +54 -10
- package/src/runtime/shared/webhooks-db.mjs +405 -0
- package/src/session-runtime/channel-config-api.mjs +13 -13
- package/src/session-runtime/lifecycle-api.mjs +14 -0
- package/src/session-runtime/runtime-core.mjs +39 -20
- package/src/standalone/agent-tool.mjs +42 -11
- package/src/standalone/channel-admin.mjs +173 -121
- package/src/standalone/channel-worker.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +10 -5
- package/src/tui/App.jsx +32 -10
- package/src/tui/app/channel-pickers.mjs +9 -9
- package/src/tui/app/clipboard.mjs +14 -0
- package/src/tui/app/doctor.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +17 -7
- package/src/tui/app/settings-picker.mjs +2 -2
- package/src/tui/app/transcript-window.mjs +37 -1
- package/src/tui/app/use-mouse-input.mjs +19 -6
- package/src/tui/components/ToolExecution.jsx +12 -4
- package/src/tui/display-width.mjs +10 -4
- package/src/tui/dist/index.mjs +166 -58
- package/src/tui/engine/agent-job-feed.mjs +42 -3
- package/src/tui/engine/session-api-ext.mjs +12 -12
- package/src/tui/index.jsx +12 -2
- package/src/ui/statusline-segments.mjs +12 -1
- package/src/workflows/default/WORKFLOW.md +29 -38
- package/src/workflows/solo/WORKFLOW.md +15 -20
- package/vendor/ink/build/display-width.js +5 -4
- package/src/runtime/shared/schedules-store.mjs +0 -82
|
@@ -3,7 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import { DiscordBackend } from "../backends/discord.mjs";
|
|
4
4
|
import { TelegramBackend } from "../backends/telegram.mjs";
|
|
5
5
|
import { readSection, updateSection, CONFIG_PATH as MIXDOG_CONFIG_PATH, getDiscordToken, getTelegramToken, diagnoseDiscordTokenValue } from "../../shared/config.mjs";
|
|
6
|
-
import { listSchedules } from "../../shared/schedules-
|
|
6
|
+
import { listSchedules } from "../../shared/schedules-db.mjs";
|
|
7
7
|
import { resolvePluginData } from "../../shared/plugin-paths.mjs";
|
|
8
8
|
const DATA_DIR = resolvePluginData();
|
|
9
9
|
const CONFIG_FILE = MIXDOG_CONFIG_PATH;
|
|
@@ -63,19 +63,21 @@ function resolveChannelId(raw = {}, backend = "discord") {
|
|
|
63
63
|
return "";
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function loadConfig() {
|
|
66
|
+
async function loadConfig() {
|
|
67
67
|
try {
|
|
68
68
|
let raw = readSection("channels");
|
|
69
69
|
raw = raw && typeof raw === "object" ? raw : {};
|
|
70
|
-
// Schedules
|
|
71
|
-
//
|
|
72
|
-
// / `raw.interactive` arrays in mixdog-config.json
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
// Schedules are the PG `scheduler.schedules` table (single source of
|
|
71
|
+
// truth). The legacy SCHEDULE.md store and the `raw.schedules.items` /
|
|
72
|
+
// `raw.nonInteractive` / `raw.interactive` arrays in mixdog-config.json
|
|
73
|
+
// are no longer read. Done one-shots are dropped so they never re-arm.
|
|
74
|
+
const scheduleEntries = (await listSchedules())
|
|
75
|
+
.filter((s) => s.enabled !== false && s.status !== "done");
|
|
76
|
+
// `target` drives routing: 'channel' → non-interactive dispatch to the
|
|
77
|
+
// schedule's channel_id; 'session' → inject into the current (Lead)
|
|
78
|
+
// session. Mirrors the webhook model.
|
|
79
|
+
raw.nonInteractive = scheduleEntries.filter((s) => s.target === "channel");
|
|
80
|
+
raw.interactive = scheduleEntries.filter((s) => s.target === "session");
|
|
79
81
|
const accessChannels = { ...raw.access?.channels ?? {} };
|
|
80
82
|
// voice config lives at the top level of mixdog-config.json (peer of
|
|
81
83
|
// channels), so readSection("channels") never sees it. Pull it explicitly.
|
|
@@ -10,6 +10,9 @@ import { isNetworkError, retryOnNetwork } from "./network-retry.mjs";
|
|
|
10
10
|
// preserving): serial inbound queue, backend.onMessage transcript (re)bind +
|
|
11
11
|
// steal logic, and handleInbound voice-transcription + parent notify. Bound to
|
|
12
12
|
// live runtime getters and shared primitives.
|
|
13
|
+
function isImageAttachment(contentType) {
|
|
14
|
+
return typeof contentType === "string" && contentType.toLowerCase().startsWith("image/");
|
|
15
|
+
}
|
|
13
16
|
export function createInboundHandler({
|
|
14
17
|
getBackend,
|
|
15
18
|
getConfig,
|
|
@@ -283,6 +286,46 @@ async function handleInbound(msg, route, options = {}) {
|
|
|
283
286
|
}
|
|
284
287
|
}
|
|
285
288
|
const hasVoiceAtt = voiceAtts.length > 0;
|
|
289
|
+
// ── Inbound image attachments → downloaded local paths (vision) ──────
|
|
290
|
+
// Mirror the voice path: download image/* attachments to the inbox and
|
|
291
|
+
// hand their local paths downstream so the agent session can attach them
|
|
292
|
+
// as real image content blocks. A short per-attempt timeout bounds the
|
|
293
|
+
// serial inboundQueue against a slow/broken image; on failure we degrade
|
|
294
|
+
// to the metadata-only marker (attMeta below) instead of dropping.
|
|
295
|
+
const imageAtts = msg.attachments.filter((a) => isImageAttachment(a.contentType));
|
|
296
|
+
let imagePaths = [];
|
|
297
|
+
if (imageAtts.length > 0) {
|
|
298
|
+
try {
|
|
299
|
+
const files = (await retryOnNetwork(
|
|
300
|
+
() => getBackend().downloadAttachment(msg.chatId, msg.messageId, {
|
|
301
|
+
timeoutMs: 20_000,
|
|
302
|
+
filter: (a) => isImageAttachment(a.contentType),
|
|
303
|
+
}),
|
|
304
|
+
{ label: "image.download" }
|
|
305
|
+
)) || [];
|
|
306
|
+
imagePaths = imageAtts
|
|
307
|
+
.map((a) => files.find((df) => df.id === a.id) ?? null)
|
|
308
|
+
.filter(Boolean)
|
|
309
|
+
.map((f) => f.path)
|
|
310
|
+
.filter((p) => typeof p === "string" && p.length > 0);
|
|
311
|
+
if (imagePaths.length > 0) {
|
|
312
|
+
process.stderr.write(`mixdog: inbound images downloaded (${imagePaths.length})\n`);
|
|
313
|
+
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
const netFail = isNetworkError(err);
|
|
316
|
+
process.stderr.write(`mixdog: image.download error${netFail ? " (network, retries exhausted)" : ""}: ${err}\n`);
|
|
317
|
+
const marker = netFail
|
|
318
|
+
? "[attachment: image download failed (network)]"
|
|
319
|
+
: "[attachment: image download failed]";
|
|
320
|
+
text = text ? `${text} ${marker}` : marker;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// An image-only message can arrive with empty text; channelNotificationModelContent
|
|
324
|
+
// drops empty content (runtime-core), which would strip meta.image_paths and lose
|
|
325
|
+
// the image. Give it a non-empty marker so the notification (and its images) flow.
|
|
326
|
+
if (imagePaths.length > 0 && !String(text || "").trim()) {
|
|
327
|
+
text = "[image]";
|
|
328
|
+
}
|
|
286
329
|
const attMeta = msg.attachments.length > 0 && !hasVoiceAtt ? {
|
|
287
330
|
attachment_count: String(msg.attachments.length),
|
|
288
331
|
attachments: msg.attachments.map((a) => `${a.name} (${a.contentType}, ${(a.size / 1024).toFixed(0)}KB)`).join("; ")
|
|
@@ -300,7 +343,8 @@ async function handleInbound(msg, route, options = {}) {
|
|
|
300
343
|
...route.sourceLabel ? { source_label: route.sourceLabel } : {}
|
|
301
344
|
} : {},
|
|
302
345
|
...attMeta,
|
|
303
|
-
...msg.imagePath ? { image_path: msg.imagePath } : {}
|
|
346
|
+
...msg.imagePath ? { image_path: msg.imagePath } : {},
|
|
347
|
+
...imagePaths.length > 0 ? { image_paths: JSON.stringify(imagePaths) } : {}
|
|
304
348
|
};
|
|
305
349
|
const notificationContent = messageBody;
|
|
306
350
|
sendNotifyToParent("notifications/claude/channel", {
|
|
@@ -4,6 +4,7 @@ import os from 'node:os'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
import { createStandaloneMemoryRuntime } from '../../../standalone/memory-runtime-proxy.mjs'
|
|
7
|
+
import { readServicePort, markServiceUnreachable, isConnRefuseError } from '../../shared/service-discovery.mjs'
|
|
7
8
|
|
|
8
9
|
const RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT
|
|
9
10
|
? path.resolve(process.env.MIXDOG_RUNTIME_ROOT)
|
|
@@ -60,9 +61,28 @@ function isConnRefusedLike(err) {
|
|
|
60
61
|
|| /ECONNREFUSED|missing memory_port|memory-service timeout/i.test(msg)
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
// A discovery advert validates only the owner pid, which can be a recycled pid
|
|
65
|
+
// living on an unrelated process while its advertised port is dead. This client
|
|
66
|
+
// has no separate /health probe, so on a connect-level failure we distrust the
|
|
67
|
+
// port: mark it unreachable (readServicePort then skips it → legacy fallback)
|
|
68
|
+
// and drop the port cache so the next getMemoryPort re-resolves.
|
|
69
|
+
function _distrustMemoryPort(port, err) {
|
|
70
|
+
// Connection-level failures ONLY. A 'memory-service timeout' means the daemon
|
|
71
|
+
// is slow-but-alive — distrusting it would false-route to legacy/buffer.
|
|
72
|
+
if (port && isConnRefuseError(err)) {
|
|
73
|
+
markServiceUnreachable('memory', port)
|
|
74
|
+
_portCache = null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
async function getMemoryPort() {
|
|
64
79
|
const now = Date.now()
|
|
65
80
|
if (_portCache && (now - _portCache.ts) < 5_000) return _portCache.port
|
|
81
|
+
// Prefer the single-writer discovery advert (discovery/memory.json), which
|
|
82
|
+
// validates the owner pid is alive. Fall back to the legacy
|
|
83
|
+
// active-instance.json memory_port field for cross-version compat.
|
|
84
|
+
const advertPort = readServicePort('memory', { requirePid: false })
|
|
85
|
+
if (advertPort) { _portCache = { port: advertPort, mtime: 0, ts: now }; return advertPort }
|
|
66
86
|
try {
|
|
67
87
|
const stat = await fs.promises.stat(ACTIVE_INSTANCE_FILE)
|
|
68
88
|
const mtime = stat.mtimeMs
|
|
@@ -115,8 +135,8 @@ async function memoryFetch(method, endpoint, body = null, timeoutMs = 10_000, {
|
|
|
115
135
|
resolve(parsed)
|
|
116
136
|
})
|
|
117
137
|
})
|
|
118
|
-
req.on('error', reject)
|
|
119
|
-
req.on('timeout', () => { req.destroy();
|
|
138
|
+
req.on('error', err => { _distrustMemoryPort(port, err); reject(err) })
|
|
139
|
+
req.on('timeout', () => { req.destroy(); const err = new Error('memory-service timeout'); _distrustMemoryPort(port, err); reject(err) })
|
|
120
140
|
if (payload) req.write(payload)
|
|
121
141
|
req.end()
|
|
122
142
|
})
|
|
@@ -407,7 +407,7 @@ async function refreshBridgeOwnership(options = {}) {
|
|
|
407
407
|
async function reloadRuntimeConfig() {
|
|
408
408
|
const previousBackend = getBackend();
|
|
409
409
|
const previousBackendName = previousBackend?.name || "";
|
|
410
|
-
setConfig(loadConfig());
|
|
410
|
+
setConfig(await loadConfig());
|
|
411
411
|
scheduler.reloadConfig(
|
|
412
412
|
getConfig().nonInteractive ?? [],
|
|
413
413
|
getConfig().interactive ?? [],
|