mixdog 0.9.20 → 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/README.md +14 -12
- package/package.json +1 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-efficiency-diag.mjs +1 -1
- package/src/defaults/skills/setup/SKILL.md +327 -0
- package/src/rules/lead/02-channels.md +3 -3
- 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/channels/lib/owned-runtime.mjs +95 -15
- package/src/runtime/channels/lib/runtime-paths.mjs +20 -9
- package/src/runtime/channels/lib/worker-main.mjs +7 -1
- package/src/runtime/memory/index.mjs +90 -0
- package/src/runtime/memory/lib/http-router.mjs +39 -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.mjs +39 -0
- package/src/runtime/shared/atomic-file.mjs +138 -80
- package/src/runtime/shared/child-guardian.mjs +61 -3
- package/src/session-runtime/runtime-core.mjs +75 -17
- package/src/standalone/channel-worker.mjs +63 -7
- package/src/standalone/folder-dialog.mjs +4 -1
- package/src/standalone/memory-runtime-proxy.mjs +98 -11
- package/src/standalone/seeds.mjs +28 -1
- package/src/tui/App.jsx +1 -0
- 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/dist/index.mjs +297 -71
- package/src/tui/engine/session-api.mjs +19 -0
package/src/tui/dist/index.mjs
CHANGED
|
@@ -158,7 +158,7 @@ import React21 from "react";
|
|
|
158
158
|
import { render } from "../../../vendor/ink/build/index.js";
|
|
159
159
|
import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync7, openSync as openSync2, readSync } from "node:fs";
|
|
160
160
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
161
|
-
import { dirname as
|
|
161
|
+
import { dirname as dirname9, join as join11 } from "node:path";
|
|
162
162
|
import { performance as performance3 } from "node:perf_hooks";
|
|
163
163
|
|
|
164
164
|
// src/tui/App.jsx
|
|
@@ -7509,7 +7509,10 @@ namespace Mixdog {
|
|
|
7509
7509
|
}
|
|
7510
7510
|
}
|
|
7511
7511
|
'@`,
|
|
7512
|
-
|
|
7512
|
+
// System.Drawing is required too: GetDialogOwnerCenter uses
|
|
7513
|
+
// Screen.PrimaryScreen.WorkingArea (System.Drawing.Rectangle) — without it
|
|
7514
|
+
// the Add-Type compile fails and the dialog silently falls back to typing.
|
|
7515
|
+
"Add-Type -TypeDefinition $code -ReferencedAssemblies System.Windows.Forms,System.Drawing | Out-Null",
|
|
7513
7516
|
// Invisible TopMost owner anchored to the TUI console (or foreground window)
|
|
7514
7517
|
// so IFileOpenDialog is modal, centered, and not detached on another monitor.
|
|
7515
7518
|
"$owner = New-Object System.Windows.Forms.Form",
|
|
@@ -7682,6 +7685,7 @@ var SLASH_COMMANDS = [
|
|
|
7682
7685
|
{ name: "settings", usage: "/setting", aliases: ["setting", "config"], aliasUsage: ["settings", "config"], showAliasUsage: false, description: "Open runtime settings" },
|
|
7683
7686
|
{ name: "profile", usage: "/profile", description: "Set your title and response language" },
|
|
7684
7687
|
{ name: "update", usage: "/update", description: "Check version and update mixdog" },
|
|
7688
|
+
{ name: "doctor", usage: "/doctor", description: "Diagnose installation health" },
|
|
7685
7689
|
{ name: "quit", usage: "/quit", aliases: ["exit", "q"], aliasUsage: ["exit", "q"], description: "Quit the TUI" }
|
|
7686
7690
|
];
|
|
7687
7691
|
function slashQuery(value) {
|
|
@@ -13733,6 +13737,7 @@ var RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "EEXIST"]
|
|
|
13733
13737
|
var LOCK_WAIT_CODES = /* @__PURE__ */ new Set(["EEXIST", "EPERM", "EACCES", "EBUSY"]);
|
|
13734
13738
|
var DEFAULT_BACKOFFS_MS = Object.freeze([25, 50, 100, 200, 400, 800, 1200, 1600]);
|
|
13735
13739
|
var DEFAULT_LOCK_TIMEOUT_MS = 8e3;
|
|
13740
|
+
var _OWNER_TOKEN = randomBytes2(12).toString("hex");
|
|
13736
13741
|
function sleepSync(ms) {
|
|
13737
13742
|
try {
|
|
13738
13743
|
const buf = new SharedArrayBuffer(4);
|
|
@@ -13780,34 +13785,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
13780
13785
|
throw contErr;
|
|
13781
13786
|
}
|
|
13782
13787
|
try {
|
|
13783
|
-
|
|
13784
|
-
const ownerPidEarly = _readLockOwnerPid(lockPath);
|
|
13785
|
-
const ownerDead = ownerPidEarly !== null && _pidIsDead(ownerPidEarly);
|
|
13786
|
-
if (ownerDead || Date.now() - st.mtimeMs > staleMs) {
|
|
13787
|
-
const deadPid = ownerPidEarly;
|
|
13788
|
-
if (ownerDead) {
|
|
13789
|
-
const reclaim = _tryAcquireReclaimGuard(lockPath, staleMs);
|
|
13790
|
-
if (reclaim !== null) {
|
|
13791
|
-
let reclaimed = false;
|
|
13792
|
-
try {
|
|
13793
|
-
const currentSt = statSync2(lockPath);
|
|
13794
|
-
if (ownerDead || Date.now() - currentSt.mtimeMs > staleMs) {
|
|
13795
|
-
const currentPid = _readLockOwnerPid(lockPath);
|
|
13796
|
-
if (currentPid === deadPid && _pidIsDead(currentPid)) {
|
|
13797
|
-
try {
|
|
13798
|
-
unlinkSync2(lockPath);
|
|
13799
|
-
reclaimed = true;
|
|
13800
|
-
} catch {
|
|
13801
|
-
}
|
|
13802
|
-
}
|
|
13803
|
-
}
|
|
13804
|
-
} finally {
|
|
13805
|
-
_releaseReclaimGuard(reclaim);
|
|
13806
|
-
}
|
|
13807
|
-
if (reclaimed) continue;
|
|
13808
|
-
}
|
|
13809
|
-
}
|
|
13810
|
-
}
|
|
13788
|
+
if (_tryReclaimStaleLock(lockPath, staleMs)) continue;
|
|
13811
13789
|
} catch {
|
|
13812
13790
|
}
|
|
13813
13791
|
if (Date.now() >= deadline) break;
|
|
@@ -13818,7 +13796,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
13818
13796
|
continue;
|
|
13819
13797
|
}
|
|
13820
13798
|
try {
|
|
13821
|
-
writeFileSync2(fd, `${process.pid} ${Date.now()}
|
|
13799
|
+
writeFileSync2(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}
|
|
13822
13800
|
`, "utf8");
|
|
13823
13801
|
} catch {
|
|
13824
13802
|
}
|
|
@@ -13831,7 +13809,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
13831
13809
|
} catch {
|
|
13832
13810
|
}
|
|
13833
13811
|
try {
|
|
13834
|
-
if (
|
|
13812
|
+
if (_lockOwnedBySelf(lockPath)) unlinkSync2(lockPath);
|
|
13835
13813
|
} catch {
|
|
13836
13814
|
}
|
|
13837
13815
|
}
|
|
@@ -13861,8 +13839,77 @@ function _pidIsDead(pid) {
|
|
|
13861
13839
|
return err?.code === "ESRCH";
|
|
13862
13840
|
}
|
|
13863
13841
|
}
|
|
13864
|
-
function
|
|
13865
|
-
|
|
13842
|
+
function _readLockOwner(lockPath) {
|
|
13843
|
+
try {
|
|
13844
|
+
const raw = readFileSync2(lockPath, "utf8");
|
|
13845
|
+
const parts = String(raw).trim().split(/\s+/);
|
|
13846
|
+
const pid = Number.parseInt(parts[0], 10);
|
|
13847
|
+
return {
|
|
13848
|
+
pid: Number.isFinite(pid) && pid > 0 ? pid : null,
|
|
13849
|
+
token: parts.length >= 3 ? parts[2] : null
|
|
13850
|
+
};
|
|
13851
|
+
} catch {
|
|
13852
|
+
return { pid: null, token: null };
|
|
13853
|
+
}
|
|
13854
|
+
}
|
|
13855
|
+
function _ownerIsLive(owner) {
|
|
13856
|
+
if (owner.pid === null) return false;
|
|
13857
|
+
if (owner.pid === process.pid) return true;
|
|
13858
|
+
try {
|
|
13859
|
+
process.kill(owner.pid, 0);
|
|
13860
|
+
return true;
|
|
13861
|
+
} catch (err) {
|
|
13862
|
+
return err?.code !== "ESRCH";
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
function _ownerIsSelfForeign(owner) {
|
|
13866
|
+
return owner.pid === process.pid && owner.token !== null && owner.token !== _OWNER_TOKEN;
|
|
13867
|
+
}
|
|
13868
|
+
function _lockOwnedBySelf(lockPath) {
|
|
13869
|
+
const owner = _readLockOwner(lockPath);
|
|
13870
|
+
if (owner.pid !== process.pid) return false;
|
|
13871
|
+
return owner.token === null ? true : owner.token === _OWNER_TOKEN;
|
|
13872
|
+
}
|
|
13873
|
+
function _tryReclaimStaleLock(lockPath, staleMs) {
|
|
13874
|
+
let st;
|
|
13875
|
+
try {
|
|
13876
|
+
st = statSync2(lockPath);
|
|
13877
|
+
} catch {
|
|
13878
|
+
return false;
|
|
13879
|
+
}
|
|
13880
|
+
const owner = _readLockOwner(lockPath);
|
|
13881
|
+
const stale = Date.now() - st.mtimeMs > staleMs;
|
|
13882
|
+
const dead = owner.pid !== null && !_ownerIsLive(owner);
|
|
13883
|
+
const pidless = owner.pid === null;
|
|
13884
|
+
const selfForeign = _ownerIsSelfForeign(owner);
|
|
13885
|
+
if (!dead && !((pidless || selfForeign) && stale)) return false;
|
|
13886
|
+
const reclaim = _tryAcquireReclaimGuard(lockPath, staleMs);
|
|
13887
|
+
if (reclaim === null) return false;
|
|
13888
|
+
try {
|
|
13889
|
+
let cur;
|
|
13890
|
+
try {
|
|
13891
|
+
cur = statSync2(lockPath);
|
|
13892
|
+
} catch {
|
|
13893
|
+
return false;
|
|
13894
|
+
}
|
|
13895
|
+
const curOwner = _readLockOwner(lockPath);
|
|
13896
|
+
if (curOwner.pid !== owner.pid || curOwner.token !== owner.token) return false;
|
|
13897
|
+
const curStale = Date.now() - cur.mtimeMs > staleMs;
|
|
13898
|
+
const curDead = curOwner.pid !== null && !_ownerIsLive(curOwner);
|
|
13899
|
+
const curPidless = curOwner.pid === null;
|
|
13900
|
+
const curSelfForeign = _ownerIsSelfForeign(curOwner);
|
|
13901
|
+
if (curDead || (curPidless || curSelfForeign) && curStale) {
|
|
13902
|
+
try {
|
|
13903
|
+
unlinkSync2(lockPath);
|
|
13904
|
+
return true;
|
|
13905
|
+
} catch {
|
|
13906
|
+
return false;
|
|
13907
|
+
}
|
|
13908
|
+
}
|
|
13909
|
+
return false;
|
|
13910
|
+
} finally {
|
|
13911
|
+
_releaseReclaimGuard(reclaim);
|
|
13912
|
+
}
|
|
13866
13913
|
}
|
|
13867
13914
|
function _reclaimGuardIsUnreadableAndStale(guardPath, staleMs) {
|
|
13868
13915
|
if (_readLockOwnerPid(guardPath) !== null) return false;
|
|
@@ -13901,7 +13948,14 @@ function _unlinkUnreadableStaleReclaimGuard(guardPath, staleMs) {
|
|
|
13901
13948
|
function _tryClearStaleReclaimGuard(guardPath, staleMs) {
|
|
13902
13949
|
const guardPid = _readLockOwnerPid(guardPath);
|
|
13903
13950
|
if (guardPid !== null) {
|
|
13904
|
-
|
|
13951
|
+
let guardStale = false;
|
|
13952
|
+
try {
|
|
13953
|
+
guardStale = Date.now() - statSync2(guardPath).mtimeMs > staleMs;
|
|
13954
|
+
} catch {
|
|
13955
|
+
}
|
|
13956
|
+
if (_pidIsDead(guardPid) || guardStale) {
|
|
13957
|
+
_unlinkReclaimGuardIfPidMatches(guardPath, guardPid);
|
|
13958
|
+
}
|
|
13905
13959
|
return;
|
|
13906
13960
|
}
|
|
13907
13961
|
_unlinkUnreadableStaleReclaimGuard(guardPath, staleMs);
|
|
@@ -13963,32 +14017,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
13963
14017
|
throw contErr;
|
|
13964
14018
|
}
|
|
13965
14019
|
try {
|
|
13966
|
-
|
|
13967
|
-
if (Date.now() - st.mtimeMs > staleMs) {
|
|
13968
|
-
const deadPid = _readLockOwnerPid(lockPath);
|
|
13969
|
-
if (deadPid !== null && _pidIsDead(deadPid)) {
|
|
13970
|
-
const reclaim = _tryAcquireReclaimGuard(lockPath, staleMs);
|
|
13971
|
-
if (reclaim !== null) {
|
|
13972
|
-
let reclaimed = false;
|
|
13973
|
-
try {
|
|
13974
|
-
const currentSt = statSync2(lockPath);
|
|
13975
|
-
if (Date.now() - currentSt.mtimeMs > staleMs) {
|
|
13976
|
-
const currentPid = _readLockOwnerPid(lockPath);
|
|
13977
|
-
if (currentPid === deadPid && _pidIsDead(currentPid)) {
|
|
13978
|
-
try {
|
|
13979
|
-
unlinkSync2(lockPath);
|
|
13980
|
-
reclaimed = true;
|
|
13981
|
-
} catch {
|
|
13982
|
-
}
|
|
13983
|
-
}
|
|
13984
|
-
}
|
|
13985
|
-
} finally {
|
|
13986
|
-
_releaseReclaimGuard(reclaim);
|
|
13987
|
-
}
|
|
13988
|
-
if (reclaimed) continue;
|
|
13989
|
-
}
|
|
13990
|
-
}
|
|
13991
|
-
}
|
|
14020
|
+
if (_tryReclaimStaleLock(lockPath, staleMs)) continue;
|
|
13992
14021
|
} catch {
|
|
13993
14022
|
}
|
|
13994
14023
|
if (Date.now() >= deadline) break;
|
|
@@ -13999,7 +14028,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
13999
14028
|
continue;
|
|
14000
14029
|
}
|
|
14001
14030
|
try {
|
|
14002
|
-
writeFileSync2(fd, `${process.pid} ${Date.now()}
|
|
14031
|
+
writeFileSync2(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}
|
|
14003
14032
|
`, "utf8");
|
|
14004
14033
|
} catch {
|
|
14005
14034
|
}
|
|
@@ -14012,7 +14041,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
14012
14041
|
} catch {
|
|
14013
14042
|
}
|
|
14014
14043
|
try {
|
|
14015
|
-
if (
|
|
14044
|
+
if (_lockOwnedBySelf(lockPath)) unlinkSync2(lockPath);
|
|
14016
14045
|
} catch {
|
|
14017
14046
|
}
|
|
14018
14047
|
}
|
|
@@ -16808,6 +16837,7 @@ function createSlashDispatch({
|
|
|
16808
16837
|
openContextPicker,
|
|
16809
16838
|
openProfilePicker,
|
|
16810
16839
|
openUpdatePicker,
|
|
16840
|
+
runDoctor,
|
|
16811
16841
|
requestExit
|
|
16812
16842
|
}) {
|
|
16813
16843
|
const runSlashCommand = (cmd, arg = "") => {
|
|
@@ -17123,6 +17153,13 @@ function createSlashDispatch({
|
|
|
17123
17153
|
case "update":
|
|
17124
17154
|
openUpdatePicker();
|
|
17125
17155
|
return true;
|
|
17156
|
+
case "doctor":
|
|
17157
|
+
if (state.commandBusy) {
|
|
17158
|
+
store.pushNotice("wait for the current command to finish before /doctor", "warn");
|
|
17159
|
+
return false;
|
|
17160
|
+
}
|
|
17161
|
+
void Promise.resolve(runDoctor?.()).catch((e) => store.pushNotice(`doctor failed: ${e?.message || e}`, "error"));
|
|
17162
|
+
return true;
|
|
17126
17163
|
case "quit":
|
|
17127
17164
|
requestExit();
|
|
17128
17165
|
return true;
|
|
@@ -18980,6 +19017,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
|
|
|
18980
19017
|
openContextPicker: (...a) => openContextPicker(...a),
|
|
18981
19018
|
openProfilePicker: (...a) => openProfilePicker(...a),
|
|
18982
19019
|
openUpdatePicker: (...a) => openUpdatePicker(...a),
|
|
19020
|
+
runDoctor: (...a) => store.runDoctor?.(...a),
|
|
18983
19021
|
requestExit: (...a) => requestExit(...a)
|
|
18984
19022
|
});
|
|
18985
19023
|
const promptHintTimerRef = useRef9(null);
|
|
@@ -24441,6 +24479,178 @@ function createEngineApiB(bag) {
|
|
|
24441
24479
|
};
|
|
24442
24480
|
}
|
|
24443
24481
|
|
|
24482
|
+
// src/runtime/shared/update-checker.mjs
|
|
24483
|
+
import { dirname as dirname7, join as join8 } from "node:path";
|
|
24484
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
24485
|
+
var PACKAGE_NAME = "mixdog";
|
|
24486
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
24487
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
24488
|
+
var _MODULE_DIR = dirname7(fileURLToPath2(import.meta.url));
|
|
24489
|
+
var _PACKAGE_JSON_PATH = join8(_MODULE_DIR, "..", "..", "..", "package.json");
|
|
24490
|
+
function parseSemver(value) {
|
|
24491
|
+
const text = String(value || "").trim().replace(/^v/i, "");
|
|
24492
|
+
const [core, ...preParts] = text.split("-");
|
|
24493
|
+
const parts = core.split(".").map((n) => {
|
|
24494
|
+
const num2 = Number.parseInt(n, 10);
|
|
24495
|
+
return Number.isFinite(num2) ? num2 : 0;
|
|
24496
|
+
});
|
|
24497
|
+
while (parts.length < 3) parts.push(0);
|
|
24498
|
+
return { parts, prerelease: preParts.join("-") };
|
|
24499
|
+
}
|
|
24500
|
+
function compareSemver(a, b) {
|
|
24501
|
+
const pa = parseSemver(a);
|
|
24502
|
+
const pb = parseSemver(b);
|
|
24503
|
+
for (let i = 0; i < 3; i++) {
|
|
24504
|
+
const diff = (pa.parts[i] || 0) - (pb.parts[i] || 0);
|
|
24505
|
+
if (diff !== 0) return diff;
|
|
24506
|
+
}
|
|
24507
|
+
if (pa.prerelease && !pb.prerelease) return -1;
|
|
24508
|
+
if (!pa.prerelease && pb.prerelease) return 1;
|
|
24509
|
+
if (pa.prerelease !== pb.prerelease) return pa.prerelease < pb.prerelease ? -1 : 1;
|
|
24510
|
+
return 0;
|
|
24511
|
+
}
|
|
24512
|
+
|
|
24513
|
+
// src/tui/app/doctor.mjs
|
|
24514
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
24515
|
+
import { dirname as dirname8, join as join9 } from "node:path";
|
|
24516
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
24517
|
+
var GLYPH = { ok: "\u2713", warn: "\u26A0", fail: "\u2717" };
|
|
24518
|
+
function readPackageJson() {
|
|
24519
|
+
try {
|
|
24520
|
+
const dir = dirname8(fileURLToPath3(import.meta.url));
|
|
24521
|
+
const raw = JSON.parse(readFileSync6(join9(dir, "..", "..", "..", "package.json"), "utf8"));
|
|
24522
|
+
return raw && typeof raw === "object" ? raw : null;
|
|
24523
|
+
} catch {
|
|
24524
|
+
return null;
|
|
24525
|
+
}
|
|
24526
|
+
}
|
|
24527
|
+
async function buildDoctorReport(runtime = {}, getState = () => ({})) {
|
|
24528
|
+
const rows = [];
|
|
24529
|
+
const check = async (label, fn) => {
|
|
24530
|
+
const row = (level, detail) => {
|
|
24531
|
+
rows.push(`${GLYPH[level] || GLYPH.warn} ${label}: ${detail}`);
|
|
24532
|
+
};
|
|
24533
|
+
try {
|
|
24534
|
+
await fn(row);
|
|
24535
|
+
} catch (e) {
|
|
24536
|
+
row("fail", `check failed: ${e?.message || e}`);
|
|
24537
|
+
}
|
|
24538
|
+
};
|
|
24539
|
+
const pkg = readPackageJson();
|
|
24540
|
+
await check("mixdog", async (row) => {
|
|
24541
|
+
const upd = await runtime.checkForUpdate?.({}) || {};
|
|
24542
|
+
const current = upd.currentVersion || pkg?.version || "unknown";
|
|
24543
|
+
const latest = upd.latestVersion;
|
|
24544
|
+
if (latest == null) {
|
|
24545
|
+
row("warn", `v${current} \xB7 update check skipped (registry unreachable)`);
|
|
24546
|
+
return;
|
|
24547
|
+
}
|
|
24548
|
+
if (upd.updateAvailable) row("warn", `v${current} \xB7 update available \u2192 v${latest}`);
|
|
24549
|
+
else row("ok", `v${current} \xB7 up to date`);
|
|
24550
|
+
});
|
|
24551
|
+
await check("node", async (row) => {
|
|
24552
|
+
const nodeVer = process.versions?.node || "0.0.0";
|
|
24553
|
+
const engines = pkg?.engines?.node;
|
|
24554
|
+
if (!engines) {
|
|
24555
|
+
row("ok", `v${nodeVer}`);
|
|
24556
|
+
return;
|
|
24557
|
+
}
|
|
24558
|
+
const m = String(engines).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
24559
|
+
if (!m) {
|
|
24560
|
+
row("warn", `v${nodeVer} \xB7 engines "${engines}" unparsed`);
|
|
24561
|
+
return;
|
|
24562
|
+
}
|
|
24563
|
+
const required = `${m[1]}.${m[2] || 0}.${m[3] || 0}`;
|
|
24564
|
+
const ok = compareSemver(nodeVer, required) >= 0;
|
|
24565
|
+
row(ok ? "ok" : "fail", `v${nodeVer} \xB7 requires node ${engines}`);
|
|
24566
|
+
});
|
|
24567
|
+
await check("providers", async (row) => {
|
|
24568
|
+
const setup = await runtime.getProviderSetup?.() || {};
|
|
24569
|
+
const lists = [...setup.api || [], ...setup.oauth || [], ...setup.local || []];
|
|
24570
|
+
const isAuthed = (p) => Boolean(p && (p.authenticated || p.enabled || p.detected));
|
|
24571
|
+
const authed = lists.filter(isAuthed);
|
|
24572
|
+
const active = getState()?.provider || "";
|
|
24573
|
+
const activeEntry = active ? lists.find((p) => p.id === active) : null;
|
|
24574
|
+
if (activeEntry && !isAuthed(activeEntry)) {
|
|
24575
|
+
row("fail", `route ${active} has no auth \xB7 ${authed.length} configured`);
|
|
24576
|
+
return;
|
|
24577
|
+
}
|
|
24578
|
+
if (active && !activeEntry) {
|
|
24579
|
+
row("warn", `${authed.length} authed \xB7 route ${active} (not listed)`);
|
|
24580
|
+
return;
|
|
24581
|
+
}
|
|
24582
|
+
row("ok", `${authed.length} authed \xB7 route ${active || "unknown"}`);
|
|
24583
|
+
});
|
|
24584
|
+
await check("mcp", async (row) => {
|
|
24585
|
+
const m = runtime.mcpStatus?.() || {};
|
|
24586
|
+
const servers = Array.isArray(m.servers) ? m.servers : [];
|
|
24587
|
+
const conn = Number(m.connectedCount || 0);
|
|
24588
|
+
const conf = Number(m.configuredCount || 0);
|
|
24589
|
+
if (conf === 0) {
|
|
24590
|
+
row("ok", "no servers configured");
|
|
24591
|
+
return;
|
|
24592
|
+
}
|
|
24593
|
+
const failed = servers.filter((s) => s && (s.error || s.status === "failed")).map((s) => s.name || s.id).filter(Boolean);
|
|
24594
|
+
const detail = `${conn}/${conf} connected${failed.length ? ` \xB7 failed: ${failed.join(", ")}` : ""}`;
|
|
24595
|
+
row(failed.length || conn < conf ? "warn" : "ok", detail);
|
|
24596
|
+
});
|
|
24597
|
+
await check("memory", async (row) => {
|
|
24598
|
+
const mem = runtime.getMemorySettings?.() || {};
|
|
24599
|
+
const enabled = mem.enabled !== false;
|
|
24600
|
+
let detail = enabled ? "enabled" : "disabled";
|
|
24601
|
+
if (mem.backend) detail += ` \xB7 backend ${mem.backend}`;
|
|
24602
|
+
const health = mem.backendHealth || mem.health;
|
|
24603
|
+
if (health != null) {
|
|
24604
|
+
detail += ` \xB7 ${typeof health === "string" ? health : health.ok ? "healthy" : "unhealthy"}`;
|
|
24605
|
+
}
|
|
24606
|
+
row(enabled ? "ok" : "warn", detail);
|
|
24607
|
+
});
|
|
24608
|
+
await check("channels", async (row) => {
|
|
24609
|
+
const settings = runtime.getChannelSettings?.({ includeStatus: true }) || {};
|
|
24610
|
+
const enabled = settings.enabled !== false;
|
|
24611
|
+
if (!enabled) {
|
|
24612
|
+
row("ok", "disabled");
|
|
24613
|
+
return;
|
|
24614
|
+
}
|
|
24615
|
+
const worker = settings.status || runtime.getChannelWorkerStatus?.() || {};
|
|
24616
|
+
const setup = runtime.getChannelSetup?.() || {};
|
|
24617
|
+
const tokens = [];
|
|
24618
|
+
if (setup.discord?.authenticated) tokens.push("discord");
|
|
24619
|
+
if (setup.telegram?.authenticated) tokens.push("telegram");
|
|
24620
|
+
if (setup.webhook?.authenticated) tokens.push("webhook");
|
|
24621
|
+
const running = worker.running === true;
|
|
24622
|
+
const detail = `enabled \xB7 worker ${running ? "running" : "stopped"} \xB7 tokens: ${tokens.length ? tokens.join(", ") : "none"}`;
|
|
24623
|
+
row(running ? "ok" : "warn", detail);
|
|
24624
|
+
});
|
|
24625
|
+
await check("skills", async (row) => {
|
|
24626
|
+
const s = runtime.skillsStatus?.() || {};
|
|
24627
|
+
const skills = Array.isArray(s.skills) ? s.skills : [];
|
|
24628
|
+
const broken = skills.filter((x) => x && (x.broken || x.error || x.invalid));
|
|
24629
|
+
const disabled = skills.filter((x) => x && x.disabled);
|
|
24630
|
+
let detail = `${s.count ?? skills.length} available`;
|
|
24631
|
+
if (disabled.length) detail += ` \xB7 ${disabled.length} disabled`;
|
|
24632
|
+
if (broken.length) detail += ` \xB7 broken: ${broken.map((x) => x.name || x.id).filter(Boolean).join(", ")}`;
|
|
24633
|
+
row(broken.length ? "warn" : "ok", detail);
|
|
24634
|
+
});
|
|
24635
|
+
await check("plugins", async (row) => {
|
|
24636
|
+
const p = runtime.pluginsStatus?.() || {};
|
|
24637
|
+
const plugins = Array.isArray(p.plugins) ? p.plugins : [];
|
|
24638
|
+
const broken = plugins.filter((x) => x && (x.broken || x.error));
|
|
24639
|
+
const disabled = plugins.filter((x) => x && x.disabled);
|
|
24640
|
+
let detail = `${p.count ?? plugins.length} detected`;
|
|
24641
|
+
if (disabled.length) detail += ` \xB7 ${disabled.length} disabled`;
|
|
24642
|
+
if (broken.length) detail += ` \xB7 broken: ${broken.map((x) => x.title || x.name || x.id).filter(Boolean).join(", ")}`;
|
|
24643
|
+
row(broken.length ? "warn" : "ok", detail);
|
|
24644
|
+
});
|
|
24645
|
+
await check("hooks", async (row) => {
|
|
24646
|
+
const h = runtime.hooksStatus?.() || {};
|
|
24647
|
+
const events = Array.isArray(h.events) ? h.events : [];
|
|
24648
|
+
const enabled = h.enabled === true;
|
|
24649
|
+
row("ok", `${enabled ? "enabled" : "disabled"} \xB7 ${events.length} event${events.length === 1 ? "" : "s"}`);
|
|
24650
|
+
});
|
|
24651
|
+
return ["mixdog doctor \u2014 installation health", ...rows].join("\n");
|
|
24652
|
+
}
|
|
24653
|
+
|
|
24444
24654
|
// src/tui/engine/session-api.mjs
|
|
24445
24655
|
function createEngineApi(bag) {
|
|
24446
24656
|
return { ...createEngineApiA(bag), ...createEngineApiB(bag) };
|
|
@@ -24883,6 +25093,22 @@ function createEngineApiA(bag) {
|
|
|
24883
25093
|
set({ commandBusy: false, commandStatus: null });
|
|
24884
25094
|
}
|
|
24885
25095
|
},
|
|
25096
|
+
runDoctor: async () => {
|
|
25097
|
+
if (getState().commandBusy) return null;
|
|
25098
|
+
const startedAt = Date.now();
|
|
25099
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: "Running diagnostics", startedAt, mode: "doctor" } });
|
|
25100
|
+
try {
|
|
25101
|
+
await new Promise((resolve6) => setTimeout(resolve6, 0));
|
|
25102
|
+
const report = await buildDoctorReport(runtime, getState);
|
|
25103
|
+
pushNotice(report, "info");
|
|
25104
|
+
return report;
|
|
25105
|
+
} catch (e) {
|
|
25106
|
+
pushNotice(`doctor failed: ${e?.message || e}`, "error");
|
|
25107
|
+
return null;
|
|
25108
|
+
} finally {
|
|
25109
|
+
set({ commandBusy: false, commandStatus: null });
|
|
25110
|
+
}
|
|
25111
|
+
},
|
|
24886
25112
|
compact: async () => {
|
|
24887
25113
|
if (getState().commandBusy) return null;
|
|
24888
25114
|
if (getState().busy) {
|
|
@@ -25512,18 +25738,18 @@ function installProcessSignalCleanup({
|
|
|
25512
25738
|
|
|
25513
25739
|
// src/runtime/channels/lib/runtime-paths.mjs
|
|
25514
25740
|
import { tmpdir as tmpdir2 } from "os";
|
|
25515
|
-
import { basename as basename4, join as
|
|
25741
|
+
import { basename as basename4, join as join10, resolve as resolve5 } from "path";
|
|
25516
25742
|
|
|
25517
25743
|
// src/runtime/channels/lib/state-file.mjs
|
|
25518
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
25744
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
25519
25745
|
function ensureDir2(dirPath) {
|
|
25520
25746
|
mkdirSync6(dirPath, { recursive: true });
|
|
25521
25747
|
}
|
|
25522
25748
|
|
|
25523
25749
|
// src/runtime/channels/lib/runtime-paths.mjs
|
|
25524
|
-
var RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT ? resolve5(process.env.MIXDOG_RUNTIME_ROOT) :
|
|
25525
|
-
var OWNER_DIR =
|
|
25526
|
-
var ACTIVE_INSTANCE_FILE =
|
|
25750
|
+
var RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT ? resolve5(process.env.MIXDOG_RUNTIME_ROOT) : join10(tmpdir2(), "mixdog");
|
|
25751
|
+
var OWNER_DIR = join10(RUNTIME_ROOT, "owners");
|
|
25752
|
+
var ACTIVE_INSTANCE_FILE = join10(RUNTIME_ROOT, "active-instance.json");
|
|
25527
25753
|
var RUNTIME_STALE_TTL = 24 * 60 * 60 * 1e3;
|
|
25528
25754
|
var STATUS_FILE_TTL = 6 * 60 * 60 * 1e3;
|
|
25529
25755
|
var TMP_FILE_TTL = 60 * 60 * 1e3;
|
|
@@ -25546,7 +25772,7 @@ function touchUiHeartbeat(instanceId) {
|
|
|
25546
25772
|
} catch {
|
|
25547
25773
|
}
|
|
25548
25774
|
}
|
|
25549
|
-
var SERVER_PID_FILE =
|
|
25775
|
+
var SERVER_PID_FILE = join10(
|
|
25550
25776
|
RUNTIME_ROOT,
|
|
25551
25777
|
`server-${sanitize(resolvePluginData() ?? "default")}.pid`
|
|
25552
25778
|
);
|
|
@@ -25766,7 +25992,7 @@ function scheduleHardExit(code = 0) {
|
|
|
25766
25992
|
timer.unref?.();
|
|
25767
25993
|
}
|
|
25768
25994
|
function resolveTuiStderrLogPath() {
|
|
25769
|
-
return process.env.MIXDOG_TUI_STDERR_LOG ||
|
|
25995
|
+
return process.env.MIXDOG_TUI_STDERR_LOG || join11(process.env.MIXDOG_RUNTIME_ROOT || join11(tmpdir3(), "mixdog"), "mixdog-tui.stderr.log");
|
|
25770
25996
|
}
|
|
25771
25997
|
function ansiFg(rgb) {
|
|
25772
25998
|
const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
|
|
@@ -25848,7 +26074,7 @@ function installTuiStderrGuard() {
|
|
|
25848
26074
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
25849
26075
|
const logPath = resolveTuiStderrLogPath();
|
|
25850
26076
|
try {
|
|
25851
|
-
mkdirSync7(
|
|
26077
|
+
mkdirSync7(dirname9(logPath), { recursive: true });
|
|
25852
26078
|
} catch {
|
|
25853
26079
|
}
|
|
25854
26080
|
(0, import_mixdog_debug.rotateBoundedLog)(logPath, import_mixdog_debug.PLUGIN_LOG_MAX_BYTES, import_mixdog_debug.PLUGIN_LOG_KEEP_BYTES);
|
|
@@ -5,6 +5,7 @@ import { compactEventDetail, projectNameFromPath } from './labels.mjs';
|
|
|
5
5
|
import { toolErrorDisplay } from './tool-result-text.mjs';
|
|
6
6
|
import { isQueuedEntryEditable, promptDisplayText } from './queue-helpers.mjs';
|
|
7
7
|
import { createEngineApiB } from './session-api-ext.mjs';
|
|
8
|
+
import { buildDoctorReport } from '../app/doctor.mjs';
|
|
8
9
|
|
|
9
10
|
export function createEngineApi(bag) {
|
|
10
11
|
return { ...createEngineApiA(bag), ...createEngineApiB(bag) };
|
|
@@ -444,6 +445,24 @@ export function createEngineApiA(bag) {
|
|
|
444
445
|
set({ commandBusy: false, commandStatus: null });
|
|
445
446
|
}
|
|
446
447
|
},
|
|
448
|
+
runDoctor: async () => {
|
|
449
|
+
if (getState().commandBusy) return null;
|
|
450
|
+
const startedAt = Date.now();
|
|
451
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: 'Running diagnostics', startedAt, mode: 'doctor' } });
|
|
452
|
+
try {
|
|
453
|
+
// Yield one event-loop turn so Ink paints the running indicator before
|
|
454
|
+
// the (mostly synchronous) health checks run — same pattern as compact.
|
|
455
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
456
|
+
const report = await buildDoctorReport(runtime, getState);
|
|
457
|
+
pushNotice(report, 'info');
|
|
458
|
+
return report;
|
|
459
|
+
} catch (e) {
|
|
460
|
+
pushNotice(`doctor failed: ${e?.message || e}`, 'error');
|
|
461
|
+
return null;
|
|
462
|
+
} finally {
|
|
463
|
+
set({ commandBusy: false, commandStatus: null });
|
|
464
|
+
}
|
|
465
|
+
},
|
|
447
466
|
compact: async () => {
|
|
448
467
|
if (getState().commandBusy) return null;
|
|
449
468
|
if (getState().busy) {
|