mixdog 0.9.52 → 0.9.53
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 +1 -1
- package/scripts/bench-run.mjs +2 -2
- package/scripts/compact-pressure-test.mjs +104 -0
- package/scripts/desktop-session-bridge-test.mjs +704 -0
- package/scripts/freevar-smoke.mjs +7 -4
- package/scripts/lifecycle-api-test.mjs +65 -4
- package/scripts/max-output-recovery-test.mjs +31 -0
- package/scripts/memory-core-input-test.mjs +10 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +63 -3
- package/scripts/openai-ws-early-settle-test.mjs +40 -0
- package/scripts/parent-abort-link-test.mjs +24 -0
- package/scripts/process-lifecycle-test.mjs +80 -22
- package/scripts/provider-contract-test.mjs +257 -0
- package/scripts/provider-toolcall-test.mjs +172 -10
- package/scripts/session-orphan-sweep-test.mjs +27 -1
- package/src/lib/keychain-cjs.cjs +36 -23
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -13
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +7 -8
- package/src/runtime/agent/orchestrator/agent-trace.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -300
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +18 -266
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +15 -130
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +5 -115
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +224 -0
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +119 -0
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +109 -0
- package/src/runtime/agent/orchestrator/providers/lib/openai-tool-args.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +14 -71
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +47 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +72 -77
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +10 -10
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +5 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +20 -223
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +11 -17
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +3 -58
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +57 -16
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +305 -127
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +46 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-grep-chunks.mjs +173 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +117 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-insights.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-spawn-helpers.mjs +107 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -40
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +19 -277
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +22 -298
- package/src/runtime/agent/orchestrator/tools/lib/shell-spawn-retry.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -71
- package/src/runtime/channels/backends/discord-gateway.mjs +1 -1
- package/src/runtime/channels/backends/discord.mjs +6 -6
- package/src/runtime/channels/lib/config.mjs +15 -2
- package/src/runtime/channels/lib/memory-client.mjs +20 -3
- package/src/runtime/channels/lib/owned-runtime.mjs +19 -12
- package/src/runtime/channels/lib/status-snapshot.mjs +9 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +9 -5
- package/src/runtime/channels/lib/worker-main.mjs +16 -5
- package/src/runtime/memory/index.mjs +24 -198
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -53
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +115 -0
- package/src/runtime/memory/lib/memory-port-advertiser.mjs +105 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +0 -4
- package/src/runtime/memory/lib/query-handlers.mjs +2 -122
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +126 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +57 -0
- package/src/runtime/search/lib/http-fetch.mjs +1 -1
- package/src/runtime/shared/config.mjs +58 -13
- package/src/runtime/shared/llm/cost.mjs +14 -4
- package/src/runtime/shared/memory-snapshot.mjs +236 -0
- package/src/runtime/shared/process-lifecycle.mjs +92 -19
- package/src/runtime/shared/process-shutdown.mjs +6 -0
- package/src/runtime/shared/resource-admission.mjs +7 -2
- package/src/session-runtime/channel-config-api.mjs +7 -7
- package/src/session-runtime/config-lifecycle.mjs +20 -17
- package/src/session-runtime/cwd-plugins.mjs +9 -7
- package/src/session-runtime/env.mjs +1 -2
- package/src/session-runtime/hitch-profile.mjs +45 -0
- package/src/session-runtime/lifecycle-api.mjs +36 -9
- package/src/session-runtime/mcp-glue.mjs +6 -11
- package/src/session-runtime/provider-init-key.mjs +17 -0
- package/src/session-runtime/runtime-core.mjs +44 -103
- package/src/session-runtime/runtime-paths.mjs +20 -0
- package/src/session-runtime/runtime-tool-routing.mjs +55 -0
- package/src/session-runtime/tool-catalog-data.mjs +51 -0
- package/src/session-runtime/tool-catalog.mjs +15 -89
- package/src/standalone/agent-tool/spawn-preset.mjs +73 -0
- package/src/standalone/agent-tool/worker-rows.mjs +93 -0
- package/src/standalone/agent-tool.mjs +12 -162
- package/src/standalone/channel-admin.mjs +29 -0
- package/src/tui/App.jsx +11 -5
- package/src/tui/dist/index.mjs +202 -65
- package/src/tui/engine/session-api-ext.mjs +37 -4
- package/src/tui/index.jsx +1 -0
- package/src/tui/lib/voice-setup.mjs +5 -5
- package/scripts/_devtools-stub.mjs +0 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -288
package/src/tui/dist/index.mjs
CHANGED
|
@@ -14888,6 +14888,79 @@ function writeFileAtomicSync(filePath, data, opts = {}) {
|
|
|
14888
14888
|
function writeJsonAtomicSync(filePath, value, opts = {}) {
|
|
14889
14889
|
return writeFileAtomicSync(filePath, JSON.stringify(value, null, opts.compact ? 0 : 2) + "\n", opts);
|
|
14890
14890
|
}
|
|
14891
|
+
async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
14892
|
+
const run = async () => {
|
|
14893
|
+
const dir = dirname4(filePath);
|
|
14894
|
+
mkdirSync3(dir, { recursive: true });
|
|
14895
|
+
const tmp = join4(dir, `.${basename4(filePath)}.${randomBytes2(12).toString("hex")}.tmp`);
|
|
14896
|
+
try {
|
|
14897
|
+
const writeOpts = { encoding: opts.encoding || "utf8", flag: "wx", mode: opts.mode !== void 0 ? opts.mode : 384 };
|
|
14898
|
+
writeFileSync3(tmp, data, writeOpts);
|
|
14899
|
+
if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(tmp);
|
|
14900
|
+
if (opts.fsync !== false) {
|
|
14901
|
+
let fd = null;
|
|
14902
|
+
try {
|
|
14903
|
+
fd = openSync(tmp, "r");
|
|
14904
|
+
fsyncSync(fd);
|
|
14905
|
+
} catch (err) {
|
|
14906
|
+
if (!["EPERM", "ENOTSUP", "EINVAL"].includes(err?.code)) throw err;
|
|
14907
|
+
} finally {
|
|
14908
|
+
try {
|
|
14909
|
+
if (fd !== null) closeSync(fd);
|
|
14910
|
+
} catch {
|
|
14911
|
+
}
|
|
14912
|
+
}
|
|
14913
|
+
}
|
|
14914
|
+
if (opts.createOnly === true) {
|
|
14915
|
+
try {
|
|
14916
|
+
linkSync(tmp, filePath);
|
|
14917
|
+
} catch (err) {
|
|
14918
|
+
try {
|
|
14919
|
+
unlinkSync2(tmp);
|
|
14920
|
+
} catch {
|
|
14921
|
+
}
|
|
14922
|
+
if (err?.code === "EEXIST") return false;
|
|
14923
|
+
throw err;
|
|
14924
|
+
}
|
|
14925
|
+
try {
|
|
14926
|
+
unlinkSync2(tmp);
|
|
14927
|
+
} catch {
|
|
14928
|
+
}
|
|
14929
|
+
} else {
|
|
14930
|
+
renameWithRetrySync(tmp, filePath, opts);
|
|
14931
|
+
}
|
|
14932
|
+
if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(filePath);
|
|
14933
|
+
if (opts.fsyncDir === true) {
|
|
14934
|
+
let dfd = null;
|
|
14935
|
+
try {
|
|
14936
|
+
dfd = openSync(dir, "r");
|
|
14937
|
+
fsyncSync(dfd);
|
|
14938
|
+
} catch (err) {
|
|
14939
|
+
if (!["EPERM", "ENOTSUP", "EINVAL", "EACCES"].includes(err?.code)) throw err;
|
|
14940
|
+
} finally {
|
|
14941
|
+
try {
|
|
14942
|
+
if (dfd !== null) closeSync(dfd);
|
|
14943
|
+
} catch {
|
|
14944
|
+
}
|
|
14945
|
+
}
|
|
14946
|
+
}
|
|
14947
|
+
return true;
|
|
14948
|
+
} catch (err) {
|
|
14949
|
+
try {
|
|
14950
|
+
if (existsSync4(tmp)) unlinkSync2(tmp);
|
|
14951
|
+
} catch {
|
|
14952
|
+
}
|
|
14953
|
+
throw err;
|
|
14954
|
+
}
|
|
14955
|
+
};
|
|
14956
|
+
if (opts.lock === true) {
|
|
14957
|
+
return withFileLock(`${filePath}.lock`, run, opts);
|
|
14958
|
+
}
|
|
14959
|
+
return run();
|
|
14960
|
+
}
|
|
14961
|
+
function writeJsonAtomicAsync(filePath, value, opts = {}) {
|
|
14962
|
+
return writeFileAtomicAsync(filePath, JSON.stringify(value, null, opts.compact ? 0 : 2) + "\n", opts);
|
|
14963
|
+
}
|
|
14891
14964
|
async function updateJsonAtomic(filePath, mutator, opts = {}) {
|
|
14892
14965
|
const { lock: _lock, ...writeOpts } = opts;
|
|
14893
14966
|
return withFileLock(`${filePath}.lock`, () => {
|
|
@@ -14915,6 +14988,13 @@ import {
|
|
|
14915
14988
|
statSync as statSync3,
|
|
14916
14989
|
writeFileSync as writeFileSync4
|
|
14917
14990
|
} from "fs";
|
|
14991
|
+
import {
|
|
14992
|
+
copyFile as copyFileP,
|
|
14993
|
+
mkdir as mkdirP,
|
|
14994
|
+
readdir as readdirP,
|
|
14995
|
+
rm as rmP,
|
|
14996
|
+
stat as statP
|
|
14997
|
+
} from "fs/promises";
|
|
14918
14998
|
import { dirname as dirname5, join as join5, resolve as resolve3 } from "path";
|
|
14919
14999
|
import { homedir as homedir3 } from "os";
|
|
14920
15000
|
import { createHash } from "crypto";
|
|
@@ -14977,44 +15057,44 @@ function loadLatestMixdogConfigFromBackup(_dataDir) {
|
|
|
14977
15057
|
}
|
|
14978
15058
|
return null;
|
|
14979
15059
|
}
|
|
14980
|
-
function
|
|
14981
|
-
|
|
15060
|
+
function markUserDataInitialized(dataDir) {
|
|
15061
|
+
try {
|
|
15062
|
+
mkdirSync4(getBackupRoot(), { recursive: true });
|
|
15063
|
+
writeFileSync4(initMarkerPath(dataDir), JSON.stringify({
|
|
15064
|
+
dataDir,
|
|
15065
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15066
|
+
}, null, 2) + "\n", "utf8");
|
|
15067
|
+
} catch {
|
|
15068
|
+
}
|
|
15069
|
+
}
|
|
15070
|
+
async function copyTreeAsync(src, dst, copied) {
|
|
15071
|
+
const st = await statP(src);
|
|
14982
15072
|
if (st.isDirectory()) {
|
|
14983
|
-
for (const name of
|
|
14984
|
-
|
|
15073
|
+
for (const name of await readdirP(src)) {
|
|
15074
|
+
await copyTreeAsync(join5(src, name), join5(dst, name), copied);
|
|
14985
15075
|
}
|
|
14986
15076
|
return;
|
|
14987
15077
|
}
|
|
14988
15078
|
if (!st.isFile()) return;
|
|
14989
|
-
|
|
14990
|
-
|
|
15079
|
+
await mkdirP(dirname5(dst), { recursive: true });
|
|
15080
|
+
await copyFileP(src, dst);
|
|
14991
15081
|
copied.push(dst);
|
|
14992
15082
|
}
|
|
14993
|
-
function
|
|
15083
|
+
async function pruneBackupsAsync(keep = 40) {
|
|
14994
15084
|
let entries = [];
|
|
14995
15085
|
try {
|
|
14996
|
-
entries =
|
|
15086
|
+
entries = (await readdirP(getBackupRoot(), { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().reverse();
|
|
14997
15087
|
} catch {
|
|
14998
15088
|
return;
|
|
14999
15089
|
}
|
|
15000
15090
|
for (const name of entries.slice(keep)) {
|
|
15001
15091
|
try {
|
|
15002
|
-
|
|
15092
|
+
await rmP(join5(getBackupRoot(), name), { recursive: true, force: true });
|
|
15003
15093
|
} catch {
|
|
15004
15094
|
}
|
|
15005
15095
|
}
|
|
15006
15096
|
}
|
|
15007
|
-
function
|
|
15008
|
-
try {
|
|
15009
|
-
mkdirSync4(getBackupRoot(), { recursive: true });
|
|
15010
|
-
writeFileSync4(initMarkerPath(dataDir), JSON.stringify({
|
|
15011
|
-
dataDir,
|
|
15012
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15013
|
-
}, null, 2) + "\n", "utf8");
|
|
15014
|
-
} catch {
|
|
15015
|
-
}
|
|
15016
|
-
}
|
|
15017
|
-
function backupUserData(dataDir, reason = "snapshot") {
|
|
15097
|
+
async function backupUserDataAsync(dataDir, reason = "snapshot") {
|
|
15018
15098
|
if (process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "1" || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "true") {
|
|
15019
15099
|
return { dir: null, copied: [] };
|
|
15020
15100
|
}
|
|
@@ -15023,15 +15103,15 @@ function backupUserData(dataDir, reason = "snapshot") {
|
|
|
15023
15103
|
const copied = [];
|
|
15024
15104
|
for (const rel of USER_DATA_FILES) {
|
|
15025
15105
|
const src = join5(dataDir, rel);
|
|
15026
|
-
if (existsSync5(src))
|
|
15106
|
+
if (existsSync5(src)) await copyTreeAsync(src, join5(backupDir, rel), copied);
|
|
15027
15107
|
}
|
|
15028
15108
|
for (const rel of USER_DATA_DIRS) {
|
|
15029
15109
|
const src = join5(dataDir, rel);
|
|
15030
|
-
if (existsSync5(src))
|
|
15110
|
+
if (existsSync5(src)) await copyTreeAsync(src, join5(backupDir, rel), copied);
|
|
15031
15111
|
}
|
|
15032
15112
|
if (copied.length > 0) {
|
|
15033
15113
|
markUserDataInitialized(dataDir);
|
|
15034
|
-
|
|
15114
|
+
await pruneBackupsAsync();
|
|
15035
15115
|
if (process.env.MIXDOG_SETUP_QUIET !== "1") {
|
|
15036
15116
|
process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}
|
|
15037
15117
|
`);
|
|
@@ -15059,7 +15139,13 @@ var AGENT_PROVIDER_ENV_ALIASES = Object.freeze({
|
|
|
15059
15139
|
|
|
15060
15140
|
// src/runtime/shared/config.mjs
|
|
15061
15141
|
var _require = createRequire3(import.meta.url);
|
|
15062
|
-
var {
|
|
15142
|
+
var {
|
|
15143
|
+
getSecret: _getSecret,
|
|
15144
|
+
setSecret: _setSecret,
|
|
15145
|
+
deleteSecret: _deleteSecret,
|
|
15146
|
+
hasSecret: _hasSecret,
|
|
15147
|
+
invalidateSecretCache: _invalidateSecretCache
|
|
15148
|
+
} = _require("../../lib/keychain-cjs.cjs");
|
|
15063
15149
|
var DATA_DIR = resolvePluginData();
|
|
15064
15150
|
var CONFIG_PATH = join6(DATA_DIR, "mixdog-config.json");
|
|
15065
15151
|
var GENERATED_KEY = "_generated";
|
|
@@ -15152,27 +15238,6 @@ function readJsonFile(path2) {
|
|
|
15152
15238
|
return null;
|
|
15153
15239
|
}
|
|
15154
15240
|
}
|
|
15155
|
-
function writeJsonFile(path2, data) {
|
|
15156
|
-
mkdirSync5(dirname6(path2), { recursive: true, mode: 448 });
|
|
15157
|
-
if (path2 === CONFIG_PATH) {
|
|
15158
|
-
try {
|
|
15159
|
-
backupUserData(DATA_DIR, "pre-config-write");
|
|
15160
|
-
} catch {
|
|
15161
|
-
}
|
|
15162
|
-
}
|
|
15163
|
-
writeJsonAtomicSync(path2, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
|
|
15164
|
-
if (path2 === CONFIG_PATH) {
|
|
15165
|
-
invalidateConfigReadCache();
|
|
15166
|
-
try {
|
|
15167
|
-
markUserDataInitialized(DATA_DIR);
|
|
15168
|
-
} catch {
|
|
15169
|
-
}
|
|
15170
|
-
try {
|
|
15171
|
-
backupUserData(DATA_DIR, "post-config-write");
|
|
15172
|
-
} catch {
|
|
15173
|
-
}
|
|
15174
|
-
}
|
|
15175
|
-
}
|
|
15176
15241
|
function readAll() {
|
|
15177
15242
|
const parsed = readJsonFile(CONFIG_PATH);
|
|
15178
15243
|
if (parsed != null) return parsed;
|
|
@@ -15231,22 +15296,58 @@ function readAllForRmW() {
|
|
|
15231
15296
|
return restoreAllForRmWOrThrow(parseErr.message);
|
|
15232
15297
|
}
|
|
15233
15298
|
}
|
|
15234
|
-
function writeAll(data) {
|
|
15235
|
-
writeJsonFile(CONFIG_PATH, data);
|
|
15236
|
-
}
|
|
15237
|
-
function withConfigLock(fn) {
|
|
15238
|
-
return withFileLockSync(`${CONFIG_PATH}.lock`, fn, { secret: true });
|
|
15239
|
-
}
|
|
15240
15299
|
function readSection(section) {
|
|
15241
15300
|
return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
|
|
15242
15301
|
}
|
|
15243
|
-
function
|
|
15244
|
-
|
|
15302
|
+
async function writeJsonFileAsync(path2, data) {
|
|
15303
|
+
mkdirSync5(dirname6(path2), { recursive: true, mode: 448 });
|
|
15304
|
+
if (path2 === CONFIG_PATH) {
|
|
15305
|
+
try {
|
|
15306
|
+
await backupUserDataAsync(DATA_DIR, "pre-config-write");
|
|
15307
|
+
} catch {
|
|
15308
|
+
}
|
|
15309
|
+
}
|
|
15310
|
+
await writeJsonAtomicAsync(path2, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
|
|
15311
|
+
if (path2 === CONFIG_PATH) {
|
|
15312
|
+
invalidateConfigReadCache();
|
|
15313
|
+
try {
|
|
15314
|
+
markUserDataInitialized(DATA_DIR);
|
|
15315
|
+
} catch {
|
|
15316
|
+
}
|
|
15317
|
+
try {
|
|
15318
|
+
await backupUserDataAsync(DATA_DIR, "post-config-write");
|
|
15319
|
+
} catch {
|
|
15320
|
+
}
|
|
15321
|
+
}
|
|
15322
|
+
}
|
|
15323
|
+
async function writeAllAsync(data) {
|
|
15324
|
+
await writeJsonFileAsync(CONFIG_PATH, data);
|
|
15325
|
+
}
|
|
15326
|
+
function withConfigLockAsync(fn) {
|
|
15327
|
+
return withFileLock(`${CONFIG_PATH}.lock`, fn, { secret: true });
|
|
15328
|
+
}
|
|
15329
|
+
var _pendingConfigWrites = /* @__PURE__ */ new Set();
|
|
15330
|
+
function trackConfigWrite(work) {
|
|
15331
|
+
let promise;
|
|
15332
|
+
try {
|
|
15333
|
+
promise = Promise.resolve(work());
|
|
15334
|
+
} catch (error) {
|
|
15335
|
+
promise = Promise.reject(error);
|
|
15336
|
+
}
|
|
15337
|
+
_pendingConfigWrites.add(promise);
|
|
15338
|
+
const remove = () => {
|
|
15339
|
+
_pendingConfigWrites.delete(promise);
|
|
15340
|
+
};
|
|
15341
|
+
promise.then(remove, remove);
|
|
15342
|
+
return promise;
|
|
15343
|
+
}
|
|
15344
|
+
function updateSectionAsync(section, updater) {
|
|
15345
|
+
return trackConfigWrite(() => withConfigLockAsync(async () => {
|
|
15245
15346
|
const all = readAllForRmW();
|
|
15246
15347
|
const current = stripGeneratedMarker(all[section] || {});
|
|
15247
15348
|
all[section] = stripGeneratedMarker(typeof updater === "function" ? updater(current) : updater);
|
|
15248
|
-
|
|
15249
|
-
});
|
|
15349
|
+
await writeAllAsync(all);
|
|
15350
|
+
}));
|
|
15250
15351
|
}
|
|
15251
15352
|
var CAPABILITY_DEFAULTS = Object.freeze({ homeAccess: false });
|
|
15252
15353
|
var SECRET_ACCOUNTS = Object.freeze({
|
|
@@ -15348,7 +15449,7 @@ async function toggleVoice({ pushNotice, setProgressHint } = {}) {
|
|
|
15348
15449
|
const dataDir = resolvePluginData();
|
|
15349
15450
|
if (isVoiceEnabled()) {
|
|
15350
15451
|
try {
|
|
15351
|
-
|
|
15452
|
+
await updateSectionAsync("voice", (current) => ({ ...current, enabled: false }));
|
|
15352
15453
|
} catch (err) {
|
|
15353
15454
|
pushNotice?.(`Voice OFF failed: ${err?.message || err}`, "error");
|
|
15354
15455
|
return { ok: false, error: err?.message || String(err) };
|
|
@@ -15363,7 +15464,7 @@ async function toggleVoice({ pushNotice, setProgressHint } = {}) {
|
|
|
15363
15464
|
_voiceInstallBusy = true;
|
|
15364
15465
|
try {
|
|
15365
15466
|
await ensureVoiceRuntimeReady({ dataDir, pushNotice, setProgressHint });
|
|
15366
|
-
|
|
15467
|
+
await updateSectionAsync("voice", (current) => ({ ...current, enabled: true }));
|
|
15367
15468
|
setProgressHint?.("");
|
|
15368
15469
|
pushNotice?.("Voice ON \u2014 channel voice messages will be transcribed", "info");
|
|
15369
15470
|
return true;
|
|
@@ -20860,19 +20961,21 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
|
|
|
20860
20961
|
}
|
|
20861
20962
|
if (channelPrompt.kind === "webhook-domain") {
|
|
20862
20963
|
if (!commandText) return false;
|
|
20863
|
-
store.setWebhookConfig?.({ ngrokDomain: commandText })
|
|
20864
|
-
|
|
20964
|
+
Promise.resolve(store.setWebhookConfig?.({ ngrokDomain: commandText })).then(() => resumeAfterChannelPrompt(channelPrompt)).catch((e) => {
|
|
20965
|
+
store.pushNotice(`webhook config update failed: ${e?.message || e}`, "error");
|
|
20966
|
+
});
|
|
20865
20967
|
return true;
|
|
20866
20968
|
}
|
|
20867
20969
|
const parts = commandText.split("|").map((part) => part.trim());
|
|
20868
20970
|
if (channelPrompt.kind === "channel-add") {
|
|
20869
20971
|
const isPipe = parts.length > 1;
|
|
20870
20972
|
const channelId = isPipe ? parts[1] : parts[0];
|
|
20871
|
-
store.setChannel({
|
|
20973
|
+
Promise.resolve(store.setChannel({
|
|
20872
20974
|
channelId,
|
|
20873
20975
|
backend: channelPrompt.backend
|
|
20976
|
+
})).then(() => resumeAfterChannelPrompt(channelPrompt)).catch((e) => {
|
|
20977
|
+
store.pushNotice(`channel save failed: ${e?.message || e}`, "error");
|
|
20874
20978
|
});
|
|
20875
|
-
resumeAfterChannelPrompt(channelPrompt);
|
|
20876
20979
|
return true;
|
|
20877
20980
|
}
|
|
20878
20981
|
if (channelPrompt.kind === "schedule-add") {
|
|
@@ -25982,13 +26085,13 @@ function createEngineApiB(bag) {
|
|
|
25982
26085
|
pushNotice("webhook/ngrok authtoken forgotten", "info");
|
|
25983
26086
|
return result;
|
|
25984
26087
|
},
|
|
25985
|
-
setChannel: (entry) => {
|
|
25986
|
-
const result = runtime.setChannel(entry);
|
|
26088
|
+
setChannel: async (entry) => {
|
|
26089
|
+
const result = await runtime.setChannel(entry);
|
|
25987
26090
|
pushNotice("channel saved", "info");
|
|
25988
26091
|
return result;
|
|
25989
26092
|
},
|
|
25990
|
-
setWebhookConfig: (patch) => {
|
|
25991
|
-
const result = runtime.setWebhookConfig(patch);
|
|
26093
|
+
setWebhookConfig: async (patch) => {
|
|
26094
|
+
const result = await runtime.setWebhookConfig(patch);
|
|
25992
26095
|
pushNotice("webhook config updated", "info");
|
|
25993
26096
|
return result;
|
|
25994
26097
|
},
|
|
@@ -26076,6 +26179,39 @@ function createEngineApiB(bag) {
|
|
|
26076
26179
|
listSessions: (options) => {
|
|
26077
26180
|
return runtime.listSessions(options);
|
|
26078
26181
|
},
|
|
26182
|
+
switchContext: async (options) => {
|
|
26183
|
+
if (getState().commandBusy) return false;
|
|
26184
|
+
set({ commandBusy: true });
|
|
26185
|
+
clearToastTimers();
|
|
26186
|
+
resetAllStreamingMarkdownStablePrefixes();
|
|
26187
|
+
const rollbackSnapshot = snapshotTuiBeforeSessionReset();
|
|
26188
|
+
resetTuiForPendingSessionReset();
|
|
26189
|
+
try {
|
|
26190
|
+
await runtime.switchContext(options);
|
|
26191
|
+
clearUiActivityBeforeContextSync();
|
|
26192
|
+
flags.pendingSessionReset = false;
|
|
26193
|
+
resetStatsAndSyncContext();
|
|
26194
|
+
set({
|
|
26195
|
+
items: replaceItems([]),
|
|
26196
|
+
toasts: [],
|
|
26197
|
+
queued: [],
|
|
26198
|
+
thinking: null,
|
|
26199
|
+
spinner: null,
|
|
26200
|
+
lastTurn: null,
|
|
26201
|
+
sessionId: null,
|
|
26202
|
+
cwd: runtime.cwd,
|
|
26203
|
+
...routeState(),
|
|
26204
|
+
stats: { ...getState().stats }
|
|
26205
|
+
});
|
|
26206
|
+
return true;
|
|
26207
|
+
} catch (error) {
|
|
26208
|
+
restoreTuiAfterFailedSessionReset(rollbackSnapshot);
|
|
26209
|
+
throw error;
|
|
26210
|
+
} finally {
|
|
26211
|
+
flags.pendingSessionReset = false;
|
|
26212
|
+
set({ commandBusy: false });
|
|
26213
|
+
}
|
|
26214
|
+
},
|
|
26079
26215
|
newSession: async () => {
|
|
26080
26216
|
if (getState().commandBusy) return false;
|
|
26081
26217
|
set({ commandBusy: true });
|
|
@@ -27972,6 +28108,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
27972
28108
|
name: "mixdog-tui",
|
|
27973
28109
|
signals: ["SIGINT", "SIGTERM", "SIGHUP"],
|
|
27974
28110
|
timeoutMs: EXIT_WAIT_TIMEOUT_MS + 1e3,
|
|
28111
|
+
restoreTerminal,
|
|
27975
28112
|
beforeCleanup: restoreTerminal,
|
|
27976
28113
|
cleanup: disposeStoreOnce,
|
|
27977
28114
|
afterCleanup: (reason) => {
|
|
@@ -280,13 +280,13 @@ export function createEngineApiB(bag) {
|
|
|
280
280
|
pushNotice('webhook/ngrok authtoken forgotten', 'info');
|
|
281
281
|
return result;
|
|
282
282
|
},
|
|
283
|
-
setChannel: (entry) => {
|
|
284
|
-
const result = runtime.setChannel(entry);
|
|
283
|
+
setChannel: async (entry) => {
|
|
284
|
+
const result = await runtime.setChannel(entry);
|
|
285
285
|
pushNotice('channel saved', 'info');
|
|
286
286
|
return result;
|
|
287
287
|
},
|
|
288
|
-
setWebhookConfig: (patch) => {
|
|
289
|
-
const result = runtime.setWebhookConfig(patch);
|
|
288
|
+
setWebhookConfig: async (patch) => {
|
|
289
|
+
const result = await runtime.setWebhookConfig(patch);
|
|
290
290
|
pushNotice('webhook config updated', 'info');
|
|
291
291
|
return result;
|
|
292
292
|
},
|
|
@@ -376,6 +376,39 @@ export function createEngineApiB(bag) {
|
|
|
376
376
|
listSessions: (options) => {
|
|
377
377
|
return runtime.listSessions(options);
|
|
378
378
|
},
|
|
379
|
+
switchContext: async (options) => {
|
|
380
|
+
if (getState().commandBusy) return false;
|
|
381
|
+
set({ commandBusy: true });
|
|
382
|
+
clearToastTimers();
|
|
383
|
+
resetAllStreamingMarkdownStablePrefixes();
|
|
384
|
+
const rollbackSnapshot = snapshotTuiBeforeSessionReset();
|
|
385
|
+
resetTuiForPendingSessionReset();
|
|
386
|
+
try {
|
|
387
|
+
await runtime.switchContext(options);
|
|
388
|
+
clearUiActivityBeforeContextSync();
|
|
389
|
+
flags.pendingSessionReset = false;
|
|
390
|
+
resetStatsAndSyncContext();
|
|
391
|
+
set({
|
|
392
|
+
items: replaceItems([]),
|
|
393
|
+
toasts: [],
|
|
394
|
+
queued: [],
|
|
395
|
+
thinking: null,
|
|
396
|
+
spinner: null,
|
|
397
|
+
lastTurn: null,
|
|
398
|
+
sessionId: null,
|
|
399
|
+
cwd: runtime.cwd,
|
|
400
|
+
...routeState(),
|
|
401
|
+
stats: { ...getState().stats },
|
|
402
|
+
});
|
|
403
|
+
return true;
|
|
404
|
+
} catch (error) {
|
|
405
|
+
restoreTuiAfterFailedSessionReset(rollbackSnapshot);
|
|
406
|
+
throw error;
|
|
407
|
+
} finally {
|
|
408
|
+
flags.pendingSessionReset = false;
|
|
409
|
+
set({ commandBusy: false });
|
|
410
|
+
}
|
|
411
|
+
},
|
|
379
412
|
newSession: async () => {
|
|
380
413
|
if (getState().commandBusy) return false;
|
|
381
414
|
set({ commandBusy: true });
|
package/src/tui/index.jsx
CHANGED
|
@@ -530,6 +530,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
530
530
|
name: 'mixdog-tui',
|
|
531
531
|
signals: ['SIGINT', 'SIGTERM', 'SIGHUP'],
|
|
532
532
|
timeoutMs: EXIT_WAIT_TIMEOUT_MS + 1000,
|
|
533
|
+
restoreTerminal,
|
|
533
534
|
beforeCleanup: restoreTerminal,
|
|
534
535
|
cleanup: disposeStoreOnce,
|
|
535
536
|
afterCleanup: (reason) => {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* loaded lazily via dynamic import — it should not load into memory unless a
|
|
19
19
|
* user actually asks for voice.
|
|
20
20
|
*/
|
|
21
|
-
import { readSection,
|
|
21
|
+
import { readSection, updateSectionAsync } from '../../runtime/shared/config.mjs';
|
|
22
22
|
import { resolvePluginData } from '../../runtime/shared/plugin-paths.mjs';
|
|
23
23
|
|
|
24
24
|
let _voiceRuntimeFetcherPromise = null;
|
|
@@ -148,12 +148,12 @@ export async function ensureVoiceRuntimeReady({ dataDir = resolvePluginData(), p
|
|
|
148
148
|
export async function toggleVoice({ pushNotice, setProgressHint } = {}) {
|
|
149
149
|
const dataDir = resolvePluginData();
|
|
150
150
|
if (isVoiceEnabled()) {
|
|
151
|
-
//
|
|
152
|
-
//
|
|
151
|
+
// The async RMW preserves the shared config lock/ACL protocol without
|
|
152
|
+
// blocking the TUI event loop. It can still throw — a locked/corrupt config,
|
|
153
153
|
// permissions error, etc. Guard it the same way the ON path is guarded and
|
|
154
154
|
// report failure via pushNotice instead of silently pretending success.
|
|
155
155
|
try {
|
|
156
|
-
|
|
156
|
+
await updateSectionAsync('voice', (current) => ({ ...current, enabled: false }));
|
|
157
157
|
} catch (err) {
|
|
158
158
|
pushNotice?.(`Voice OFF failed: ${err?.message || err}`, 'error');
|
|
159
159
|
return { ok: false, error: err?.message || String(err) };
|
|
@@ -168,7 +168,7 @@ export async function toggleVoice({ pushNotice, setProgressHint } = {}) {
|
|
|
168
168
|
_voiceInstallBusy = true;
|
|
169
169
|
try {
|
|
170
170
|
await ensureVoiceRuntimeReady({ dataDir, pushNotice, setProgressHint });
|
|
171
|
-
|
|
171
|
+
await updateSectionAsync('voice', (current) => ({ ...current, enabled: true }));
|
|
172
172
|
setProgressHint?.('');
|
|
173
173
|
pushNotice?.('Voice ON — channel voice messages will be transcribed', 'info');
|
|
174
174
|
return true;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default {connectToDevTools(){}};
|