mixdog 0.9.20 → 0.9.22
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/src/defaults/skills/setup/SKILL.md +327 -0
- 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/tools/bash-session.mjs +10 -10
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +8 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +14 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +23 -13
- 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 +161 -85
- 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 +314 -74
- 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;
|
|
@@ -13880,8 +13927,21 @@ function _reclaimGuardMatchesToken(guardPath, token) {
|
|
|
13880
13927
|
return false;
|
|
13881
13928
|
}
|
|
13882
13929
|
}
|
|
13883
|
-
function
|
|
13884
|
-
|
|
13930
|
+
function _readGuardContent(guardPath) {
|
|
13931
|
+
try {
|
|
13932
|
+
return readFileSync2(guardPath, "utf8");
|
|
13933
|
+
} catch {
|
|
13934
|
+
return null;
|
|
13935
|
+
}
|
|
13936
|
+
}
|
|
13937
|
+
function _parseStampPid(raw) {
|
|
13938
|
+
if (raw === null) return null;
|
|
13939
|
+
const tok = String(raw).trim().split(/\s+/)[0];
|
|
13940
|
+
const pid = Number.parseInt(tok, 10);
|
|
13941
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
13942
|
+
}
|
|
13943
|
+
function _unlinkReclaimGuardIfContentMatches(guardPath, content) {
|
|
13944
|
+
if (_readGuardContent(guardPath) !== content) return false;
|
|
13885
13945
|
try {
|
|
13886
13946
|
unlinkSync2(guardPath);
|
|
13887
13947
|
return true;
|
|
@@ -13899,9 +13959,17 @@ function _unlinkUnreadableStaleReclaimGuard(guardPath, staleMs) {
|
|
|
13899
13959
|
}
|
|
13900
13960
|
}
|
|
13901
13961
|
function _tryClearStaleReclaimGuard(guardPath, staleMs) {
|
|
13902
|
-
const
|
|
13962
|
+
const guardContent = _readGuardContent(guardPath);
|
|
13963
|
+
const guardPid = _parseStampPid(guardContent);
|
|
13903
13964
|
if (guardPid !== null) {
|
|
13904
|
-
|
|
13965
|
+
let guardStale = false;
|
|
13966
|
+
try {
|
|
13967
|
+
guardStale = Date.now() - statSync2(guardPath).mtimeMs > staleMs;
|
|
13968
|
+
} catch {
|
|
13969
|
+
}
|
|
13970
|
+
if (_pidIsDead(guardPid) || guardStale) {
|
|
13971
|
+
_unlinkReclaimGuardIfContentMatches(guardPath, guardContent);
|
|
13972
|
+
}
|
|
13905
13973
|
return;
|
|
13906
13974
|
}
|
|
13907
13975
|
_unlinkUnreadableStaleReclaimGuard(guardPath, staleMs);
|
|
@@ -13963,32 +14031,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
13963
14031
|
throw contErr;
|
|
13964
14032
|
}
|
|
13965
14033
|
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
|
-
}
|
|
14034
|
+
if (_tryReclaimStaleLock(lockPath, staleMs)) continue;
|
|
13992
14035
|
} catch {
|
|
13993
14036
|
}
|
|
13994
14037
|
if (Date.now() >= deadline) break;
|
|
@@ -13999,7 +14042,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
13999
14042
|
continue;
|
|
14000
14043
|
}
|
|
14001
14044
|
try {
|
|
14002
|
-
writeFileSync2(fd, `${process.pid} ${Date.now()}
|
|
14045
|
+
writeFileSync2(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}
|
|
14003
14046
|
`, "utf8");
|
|
14004
14047
|
} catch {
|
|
14005
14048
|
}
|
|
@@ -14012,7 +14055,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
14012
14055
|
} catch {
|
|
14013
14056
|
}
|
|
14014
14057
|
try {
|
|
14015
|
-
if (
|
|
14058
|
+
if (_lockOwnedBySelf(lockPath)) unlinkSync2(lockPath);
|
|
14016
14059
|
} catch {
|
|
14017
14060
|
}
|
|
14018
14061
|
}
|
|
@@ -16808,6 +16851,7 @@ function createSlashDispatch({
|
|
|
16808
16851
|
openContextPicker,
|
|
16809
16852
|
openProfilePicker,
|
|
16810
16853
|
openUpdatePicker,
|
|
16854
|
+
runDoctor,
|
|
16811
16855
|
requestExit
|
|
16812
16856
|
}) {
|
|
16813
16857
|
const runSlashCommand = (cmd, arg = "") => {
|
|
@@ -17123,6 +17167,13 @@ function createSlashDispatch({
|
|
|
17123
17167
|
case "update":
|
|
17124
17168
|
openUpdatePicker();
|
|
17125
17169
|
return true;
|
|
17170
|
+
case "doctor":
|
|
17171
|
+
if (state.commandBusy) {
|
|
17172
|
+
store.pushNotice("wait for the current command to finish before /doctor", "warn");
|
|
17173
|
+
return false;
|
|
17174
|
+
}
|
|
17175
|
+
void Promise.resolve(runDoctor?.()).catch((e) => store.pushNotice(`doctor failed: ${e?.message || e}`, "error"));
|
|
17176
|
+
return true;
|
|
17126
17177
|
case "quit":
|
|
17127
17178
|
requestExit();
|
|
17128
17179
|
return true;
|
|
@@ -18980,6 +19031,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
|
|
|
18980
19031
|
openContextPicker: (...a) => openContextPicker(...a),
|
|
18981
19032
|
openProfilePicker: (...a) => openProfilePicker(...a),
|
|
18982
19033
|
openUpdatePicker: (...a) => openUpdatePicker(...a),
|
|
19034
|
+
runDoctor: (...a) => store.runDoctor?.(...a),
|
|
18983
19035
|
requestExit: (...a) => requestExit(...a)
|
|
18984
19036
|
});
|
|
18985
19037
|
const promptHintTimerRef = useRef9(null);
|
|
@@ -24441,6 +24493,178 @@ function createEngineApiB(bag) {
|
|
|
24441
24493
|
};
|
|
24442
24494
|
}
|
|
24443
24495
|
|
|
24496
|
+
// src/runtime/shared/update-checker.mjs
|
|
24497
|
+
import { dirname as dirname7, join as join8 } from "node:path";
|
|
24498
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
24499
|
+
var PACKAGE_NAME = "mixdog";
|
|
24500
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
24501
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
24502
|
+
var _MODULE_DIR = dirname7(fileURLToPath2(import.meta.url));
|
|
24503
|
+
var _PACKAGE_JSON_PATH = join8(_MODULE_DIR, "..", "..", "..", "package.json");
|
|
24504
|
+
function parseSemver(value) {
|
|
24505
|
+
const text = String(value || "").trim().replace(/^v/i, "");
|
|
24506
|
+
const [core, ...preParts] = text.split("-");
|
|
24507
|
+
const parts = core.split(".").map((n) => {
|
|
24508
|
+
const num2 = Number.parseInt(n, 10);
|
|
24509
|
+
return Number.isFinite(num2) ? num2 : 0;
|
|
24510
|
+
});
|
|
24511
|
+
while (parts.length < 3) parts.push(0);
|
|
24512
|
+
return { parts, prerelease: preParts.join("-") };
|
|
24513
|
+
}
|
|
24514
|
+
function compareSemver(a, b) {
|
|
24515
|
+
const pa = parseSemver(a);
|
|
24516
|
+
const pb = parseSemver(b);
|
|
24517
|
+
for (let i = 0; i < 3; i++) {
|
|
24518
|
+
const diff = (pa.parts[i] || 0) - (pb.parts[i] || 0);
|
|
24519
|
+
if (diff !== 0) return diff;
|
|
24520
|
+
}
|
|
24521
|
+
if (pa.prerelease && !pb.prerelease) return -1;
|
|
24522
|
+
if (!pa.prerelease && pb.prerelease) return 1;
|
|
24523
|
+
if (pa.prerelease !== pb.prerelease) return pa.prerelease < pb.prerelease ? -1 : 1;
|
|
24524
|
+
return 0;
|
|
24525
|
+
}
|
|
24526
|
+
|
|
24527
|
+
// src/tui/app/doctor.mjs
|
|
24528
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
24529
|
+
import { dirname as dirname8, join as join9 } from "node:path";
|
|
24530
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
24531
|
+
var GLYPH = { ok: "\u2713", warn: "\u26A0", fail: "\u2717" };
|
|
24532
|
+
function readPackageJson() {
|
|
24533
|
+
try {
|
|
24534
|
+
const dir = dirname8(fileURLToPath3(import.meta.url));
|
|
24535
|
+
const raw = JSON.parse(readFileSync6(join9(dir, "..", "..", "..", "package.json"), "utf8"));
|
|
24536
|
+
return raw && typeof raw === "object" ? raw : null;
|
|
24537
|
+
} catch {
|
|
24538
|
+
return null;
|
|
24539
|
+
}
|
|
24540
|
+
}
|
|
24541
|
+
async function buildDoctorReport(runtime = {}, getState = () => ({})) {
|
|
24542
|
+
const rows = [];
|
|
24543
|
+
const check = async (label, fn) => {
|
|
24544
|
+
const row = (level, detail) => {
|
|
24545
|
+
rows.push(`${GLYPH[level] || GLYPH.warn} ${label}: ${detail}`);
|
|
24546
|
+
};
|
|
24547
|
+
try {
|
|
24548
|
+
await fn(row);
|
|
24549
|
+
} catch (e) {
|
|
24550
|
+
row("fail", `check failed: ${e?.message || e}`);
|
|
24551
|
+
}
|
|
24552
|
+
};
|
|
24553
|
+
const pkg = readPackageJson();
|
|
24554
|
+
await check("mixdog", async (row) => {
|
|
24555
|
+
const upd = await runtime.checkForUpdate?.({}) || {};
|
|
24556
|
+
const current = upd.currentVersion || pkg?.version || "unknown";
|
|
24557
|
+
const latest = upd.latestVersion;
|
|
24558
|
+
if (latest == null) {
|
|
24559
|
+
row("warn", `v${current} \xB7 update check skipped (registry unreachable)`);
|
|
24560
|
+
return;
|
|
24561
|
+
}
|
|
24562
|
+
if (upd.updateAvailable) row("warn", `v${current} \xB7 update available \u2192 v${latest}`);
|
|
24563
|
+
else row("ok", `v${current} \xB7 up to date`);
|
|
24564
|
+
});
|
|
24565
|
+
await check("node", async (row) => {
|
|
24566
|
+
const nodeVer = process.versions?.node || "0.0.0";
|
|
24567
|
+
const engines = pkg?.engines?.node;
|
|
24568
|
+
if (!engines) {
|
|
24569
|
+
row("ok", `v${nodeVer}`);
|
|
24570
|
+
return;
|
|
24571
|
+
}
|
|
24572
|
+
const m = String(engines).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
24573
|
+
if (!m) {
|
|
24574
|
+
row("warn", `v${nodeVer} \xB7 engines "${engines}" unparsed`);
|
|
24575
|
+
return;
|
|
24576
|
+
}
|
|
24577
|
+
const required = `${m[1]}.${m[2] || 0}.${m[3] || 0}`;
|
|
24578
|
+
const ok = compareSemver(nodeVer, required) >= 0;
|
|
24579
|
+
row(ok ? "ok" : "fail", `v${nodeVer} \xB7 requires node ${engines}`);
|
|
24580
|
+
});
|
|
24581
|
+
await check("providers", async (row) => {
|
|
24582
|
+
const setup = await runtime.getProviderSetup?.() || {};
|
|
24583
|
+
const lists = [...setup.api || [], ...setup.oauth || [], ...setup.local || []];
|
|
24584
|
+
const isAuthed = (p) => Boolean(p && (p.authenticated || p.enabled || p.detected));
|
|
24585
|
+
const authed = lists.filter(isAuthed);
|
|
24586
|
+
const active = getState()?.provider || "";
|
|
24587
|
+
const activeEntry = active ? lists.find((p) => p.id === active) : null;
|
|
24588
|
+
if (activeEntry && !isAuthed(activeEntry)) {
|
|
24589
|
+
row("fail", `route ${active} has no auth \xB7 ${authed.length} configured`);
|
|
24590
|
+
return;
|
|
24591
|
+
}
|
|
24592
|
+
if (active && !activeEntry) {
|
|
24593
|
+
row("warn", `${authed.length} authed \xB7 route ${active} (not listed)`);
|
|
24594
|
+
return;
|
|
24595
|
+
}
|
|
24596
|
+
row("ok", `${authed.length} authed \xB7 route ${active || "unknown"}`);
|
|
24597
|
+
});
|
|
24598
|
+
await check("mcp", async (row) => {
|
|
24599
|
+
const m = runtime.mcpStatus?.() || {};
|
|
24600
|
+
const servers = Array.isArray(m.servers) ? m.servers : [];
|
|
24601
|
+
const conn = Number(m.connectedCount || 0);
|
|
24602
|
+
const conf = Number(m.configuredCount || 0);
|
|
24603
|
+
if (conf === 0) {
|
|
24604
|
+
row("ok", "no servers configured");
|
|
24605
|
+
return;
|
|
24606
|
+
}
|
|
24607
|
+
const failed = servers.filter((s) => s && (s.error || s.status === "failed")).map((s) => s.name || s.id).filter(Boolean);
|
|
24608
|
+
const detail = `${conn}/${conf} connected${failed.length ? ` \xB7 failed: ${failed.join(", ")}` : ""}`;
|
|
24609
|
+
row(failed.length || conn < conf ? "warn" : "ok", detail);
|
|
24610
|
+
});
|
|
24611
|
+
await check("memory", async (row) => {
|
|
24612
|
+
const mem = runtime.getMemorySettings?.() || {};
|
|
24613
|
+
const enabled = mem.enabled !== false;
|
|
24614
|
+
let detail = enabled ? "enabled" : "disabled";
|
|
24615
|
+
if (mem.backend) detail += ` \xB7 backend ${mem.backend}`;
|
|
24616
|
+
const health = mem.backendHealth || mem.health;
|
|
24617
|
+
if (health != null) {
|
|
24618
|
+
detail += ` \xB7 ${typeof health === "string" ? health : health.ok ? "healthy" : "unhealthy"}`;
|
|
24619
|
+
}
|
|
24620
|
+
row(enabled ? "ok" : "warn", detail);
|
|
24621
|
+
});
|
|
24622
|
+
await check("channels", async (row) => {
|
|
24623
|
+
const settings = runtime.getChannelSettings?.({ includeStatus: true }) || {};
|
|
24624
|
+
const enabled = settings.enabled !== false;
|
|
24625
|
+
if (!enabled) {
|
|
24626
|
+
row("ok", "disabled");
|
|
24627
|
+
return;
|
|
24628
|
+
}
|
|
24629
|
+
const worker = settings.status || runtime.getChannelWorkerStatus?.() || {};
|
|
24630
|
+
const setup = runtime.getChannelSetup?.() || {};
|
|
24631
|
+
const tokens = [];
|
|
24632
|
+
if (setup.discord?.authenticated) tokens.push("discord");
|
|
24633
|
+
if (setup.telegram?.authenticated) tokens.push("telegram");
|
|
24634
|
+
if (setup.webhook?.authenticated) tokens.push("webhook");
|
|
24635
|
+
const running = worker.running === true;
|
|
24636
|
+
const detail = `enabled \xB7 worker ${running ? "running" : "stopped"} \xB7 tokens: ${tokens.length ? tokens.join(", ") : "none"}`;
|
|
24637
|
+
row(running ? "ok" : "warn", detail);
|
|
24638
|
+
});
|
|
24639
|
+
await check("skills", async (row) => {
|
|
24640
|
+
const s = runtime.skillsStatus?.() || {};
|
|
24641
|
+
const skills = Array.isArray(s.skills) ? s.skills : [];
|
|
24642
|
+
const broken = skills.filter((x) => x && (x.broken || x.error || x.invalid));
|
|
24643
|
+
const disabled = skills.filter((x) => x && x.disabled);
|
|
24644
|
+
let detail = `${s.count ?? skills.length} available`;
|
|
24645
|
+
if (disabled.length) detail += ` \xB7 ${disabled.length} disabled`;
|
|
24646
|
+
if (broken.length) detail += ` \xB7 broken: ${broken.map((x) => x.name || x.id).filter(Boolean).join(", ")}`;
|
|
24647
|
+
row(broken.length ? "warn" : "ok", detail);
|
|
24648
|
+
});
|
|
24649
|
+
await check("plugins", async (row) => {
|
|
24650
|
+
const p = runtime.pluginsStatus?.() || {};
|
|
24651
|
+
const plugins = Array.isArray(p.plugins) ? p.plugins : [];
|
|
24652
|
+
const broken = plugins.filter((x) => x && (x.broken || x.error));
|
|
24653
|
+
const disabled = plugins.filter((x) => x && x.disabled);
|
|
24654
|
+
let detail = `${p.count ?? plugins.length} detected`;
|
|
24655
|
+
if (disabled.length) detail += ` \xB7 ${disabled.length} disabled`;
|
|
24656
|
+
if (broken.length) detail += ` \xB7 broken: ${broken.map((x) => x.title || x.name || x.id).filter(Boolean).join(", ")}`;
|
|
24657
|
+
row(broken.length ? "warn" : "ok", detail);
|
|
24658
|
+
});
|
|
24659
|
+
await check("hooks", async (row) => {
|
|
24660
|
+
const h = runtime.hooksStatus?.() || {};
|
|
24661
|
+
const events = Array.isArray(h.events) ? h.events : [];
|
|
24662
|
+
const enabled = h.enabled === true;
|
|
24663
|
+
row("ok", `${enabled ? "enabled" : "disabled"} \xB7 ${events.length} event${events.length === 1 ? "" : "s"}`);
|
|
24664
|
+
});
|
|
24665
|
+
return ["mixdog doctor \u2014 installation health", ...rows].join("\n");
|
|
24666
|
+
}
|
|
24667
|
+
|
|
24444
24668
|
// src/tui/engine/session-api.mjs
|
|
24445
24669
|
function createEngineApi(bag) {
|
|
24446
24670
|
return { ...createEngineApiA(bag), ...createEngineApiB(bag) };
|
|
@@ -24883,6 +25107,22 @@ function createEngineApiA(bag) {
|
|
|
24883
25107
|
set({ commandBusy: false, commandStatus: null });
|
|
24884
25108
|
}
|
|
24885
25109
|
},
|
|
25110
|
+
runDoctor: async () => {
|
|
25111
|
+
if (getState().commandBusy) return null;
|
|
25112
|
+
const startedAt = Date.now();
|
|
25113
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: "Running diagnostics", startedAt, mode: "doctor" } });
|
|
25114
|
+
try {
|
|
25115
|
+
await new Promise((resolve6) => setTimeout(resolve6, 0));
|
|
25116
|
+
const report = await buildDoctorReport(runtime, getState);
|
|
25117
|
+
pushNotice(report, "info");
|
|
25118
|
+
return report;
|
|
25119
|
+
} catch (e) {
|
|
25120
|
+
pushNotice(`doctor failed: ${e?.message || e}`, "error");
|
|
25121
|
+
return null;
|
|
25122
|
+
} finally {
|
|
25123
|
+
set({ commandBusy: false, commandStatus: null });
|
|
25124
|
+
}
|
|
25125
|
+
},
|
|
24886
25126
|
compact: async () => {
|
|
24887
25127
|
if (getState().commandBusy) return null;
|
|
24888
25128
|
if (getState().busy) {
|
|
@@ -25512,18 +25752,18 @@ function installProcessSignalCleanup({
|
|
|
25512
25752
|
|
|
25513
25753
|
// src/runtime/channels/lib/runtime-paths.mjs
|
|
25514
25754
|
import { tmpdir as tmpdir2 } from "os";
|
|
25515
|
-
import { basename as basename4, join as
|
|
25755
|
+
import { basename as basename4, join as join10, resolve as resolve5 } from "path";
|
|
25516
25756
|
|
|
25517
25757
|
// src/runtime/channels/lib/state-file.mjs
|
|
25518
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
25758
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
25519
25759
|
function ensureDir2(dirPath) {
|
|
25520
25760
|
mkdirSync6(dirPath, { recursive: true });
|
|
25521
25761
|
}
|
|
25522
25762
|
|
|
25523
25763
|
// 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 =
|
|
25764
|
+
var RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT ? resolve5(process.env.MIXDOG_RUNTIME_ROOT) : join10(tmpdir2(), "mixdog");
|
|
25765
|
+
var OWNER_DIR = join10(RUNTIME_ROOT, "owners");
|
|
25766
|
+
var ACTIVE_INSTANCE_FILE = join10(RUNTIME_ROOT, "active-instance.json");
|
|
25527
25767
|
var RUNTIME_STALE_TTL = 24 * 60 * 60 * 1e3;
|
|
25528
25768
|
var STATUS_FILE_TTL = 6 * 60 * 60 * 1e3;
|
|
25529
25769
|
var TMP_FILE_TTL = 60 * 60 * 1e3;
|
|
@@ -25546,7 +25786,7 @@ function touchUiHeartbeat(instanceId) {
|
|
|
25546
25786
|
} catch {
|
|
25547
25787
|
}
|
|
25548
25788
|
}
|
|
25549
|
-
var SERVER_PID_FILE =
|
|
25789
|
+
var SERVER_PID_FILE = join10(
|
|
25550
25790
|
RUNTIME_ROOT,
|
|
25551
25791
|
`server-${sanitize(resolvePluginData() ?? "default")}.pid`
|
|
25552
25792
|
);
|
|
@@ -25766,7 +26006,7 @@ function scheduleHardExit(code = 0) {
|
|
|
25766
26006
|
timer.unref?.();
|
|
25767
26007
|
}
|
|
25768
26008
|
function resolveTuiStderrLogPath() {
|
|
25769
|
-
return process.env.MIXDOG_TUI_STDERR_LOG ||
|
|
26009
|
+
return process.env.MIXDOG_TUI_STDERR_LOG || join11(process.env.MIXDOG_RUNTIME_ROOT || join11(tmpdir3(), "mixdog"), "mixdog-tui.stderr.log");
|
|
25770
26010
|
}
|
|
25771
26011
|
function ansiFg(rgb) {
|
|
25772
26012
|
const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
|
|
@@ -25848,7 +26088,7 @@ function installTuiStderrGuard() {
|
|
|
25848
26088
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
25849
26089
|
const logPath = resolveTuiStderrLogPath();
|
|
25850
26090
|
try {
|
|
25851
|
-
mkdirSync7(
|
|
26091
|
+
mkdirSync7(dirname9(logPath), { recursive: true });
|
|
25852
26092
|
} catch {
|
|
25853
26093
|
}
|
|
25854
26094
|
(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) {
|