mixdog 1.3.3 → 1.3.4
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 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/shared/agent-room-host.mjs +1 -1
- package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
- package/src/session-runtime/agent-tool-routing.mjs +49 -4
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
- package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +11 -2
- package/src/session-runtime/services/agent-tool/render.mjs +3 -1
- package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
- package/src/session-runtime/usage-stats-api.mjs +1 -1
- package/src/tui/dist/index.mjs +311 -170
- package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
- package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
- package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
// Shared Mixdog settings.json env writer — the SINGLE source of truth for
|
|
2
|
-
// reading/mutating ~/.mixdog/settings.json gateway env entries, used by
|
|
3
|
-
// BOTH scripts/gateway-model.mjs (--enable / --disable) AND scripts/uninstall.mjs
|
|
4
|
-
// (restoreGateway). One helper guarantees enable, disable, and uninstall never
|
|
5
|
-
// diverge on path resolution or write mechanics.
|
|
6
|
-
//
|
|
7
|
-
// Node-only (node:fs / node:os / node:path) — no plugin deps — so uninstall.mjs
|
|
8
|
-
// keeps its zero-dependency restore contract.
|
|
9
|
-
//
|
|
10
|
-
// Reuses the exact safe read-merge-write injectStatusLine() uses in
|
|
11
|
-
// hooks/session-start.cjs:485-532: read settings.json -> JSON.parse -> mutate
|
|
12
|
-
// the object -> write a .mixdog-tmp sibling -> atomic rename. Touches ONLY
|
|
13
|
-
// gateway routing env and the paired auto-compact window restore marker.
|
|
14
|
-
import { readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
15
|
-
import { homedir } from 'node:os';
|
|
16
|
-
import { join, dirname } from 'node:path';
|
|
17
|
-
|
|
18
|
-
// Location: MIXDOG_CONFIG_DIR || ~/.mixdog.
|
|
19
|
-
export function resolveSettingsPath() {
|
|
20
|
-
const base = process.env.MIXDOG_CONFIG_DIR || join(homedir(), '.mixdog');
|
|
21
|
-
return join(base, 'settings.json');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function resolveGlobalClaudeConfigPath() {
|
|
25
|
-
return join(homedir(), '.mixdog.json');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const GATEWAY_ENV_KEYS = Object.freeze([
|
|
29
|
-
'ANTHROPIC_BASE_URL',
|
|
30
|
-
]);
|
|
31
|
-
const AUTO_COMPACT_WINDOW_ENV = 'CLAUDE_CODE_AUTO_COMPACT_WINDOW';
|
|
32
|
-
const PREVIOUS_AUTO_COMPACT_WINDOW_ENV = 'MIXDOG_PREVIOUS_CLAUDE_CODE_AUTO_COMPACT_WINDOW';
|
|
33
|
-
const PREVIOUS_ABSENT = '__mixdog_absent__';
|
|
34
|
-
export const ANTHROPIC_OFFICIAL_BASE_URL = 'https://api.anthropic.com';
|
|
35
|
-
|
|
36
|
-
function normalizeAutoCompactWindow(value) {
|
|
37
|
-
const n = Number(value);
|
|
38
|
-
return Number.isFinite(n) && n > 0 ? String(Math.floor(n)) : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Set (string url) or remove (url === null) gateway-owned settings.env entries.
|
|
42
|
-
// Returns { ok, changed?, path, error?, missing?, dryRun? }. `dryRun` previews
|
|
43
|
-
// without writing. A no-op (already matching / already absent) → ok:true,
|
|
44
|
-
// changed:false. Missing settings.json on remove → ok:true (treated clean);
|
|
45
|
-
// on add → ok:false (cannot create the file from scratch here).
|
|
46
|
-
export function setAnthropicBaseUrl(url, { dryRun = false, autoCompactWindow = null, preserveBaseUrl = false } = {}) {
|
|
47
|
-
const p = resolveSettingsPath();
|
|
48
|
-
let raw;
|
|
49
|
-
try {
|
|
50
|
-
raw = readFileSync(p, 'utf8');
|
|
51
|
-
} catch (e) {
|
|
52
|
-
if (e && e.code === 'ENOENT') {
|
|
53
|
-
return url === null
|
|
54
|
-
? { ok: true, changed: false, path: p, missing: true }
|
|
55
|
-
: { ok: false, error: `cannot read ${p}: ${e.message}`, path: p };
|
|
56
|
-
}
|
|
57
|
-
return { ok: false, error: `cannot read ${p}: ${e.message}`, path: p };
|
|
58
|
-
}
|
|
59
|
-
let settings;
|
|
60
|
-
try {
|
|
61
|
-
settings = JSON.parse(raw);
|
|
62
|
-
} catch (e) {
|
|
63
|
-
return { ok: false, error: `cannot parse ${p}: ${e.message}`, path: p };
|
|
64
|
-
}
|
|
65
|
-
if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
|
|
66
|
-
return { ok: false, error: `${p} is not a JSON object`, path: p };
|
|
67
|
-
}
|
|
68
|
-
const envIsObj = settings.env && typeof settings.env === 'object' && !Array.isArray(settings.env);
|
|
69
|
-
const compactWindow = normalizeAutoCompactWindow(autoCompactWindow);
|
|
70
|
-
let changed = false;
|
|
71
|
-
if (url === null) {
|
|
72
|
-
// REMOVE: only our keys; leave the rest of env untouched. No-op if absent.
|
|
73
|
-
if (!envIsObj) {
|
|
74
|
-
if (!compactWindow) return { ok: true, changed: false, path: p };
|
|
75
|
-
settings.env = {};
|
|
76
|
-
}
|
|
77
|
-
if (!compactWindow && !GATEWAY_ENV_KEYS.some((key) => key in settings.env) && !(PREVIOUS_AUTO_COMPACT_WINDOW_ENV in settings.env)) {
|
|
78
|
-
return { ok: true, changed: false, path: p };
|
|
79
|
-
}
|
|
80
|
-
if (!preserveBaseUrl && 'ANTHROPIC_BASE_URL' in settings.env) {
|
|
81
|
-
changed = true;
|
|
82
|
-
if (!dryRun) delete settings.env.ANTHROPIC_BASE_URL;
|
|
83
|
-
}
|
|
84
|
-
if (compactWindow) {
|
|
85
|
-
if (!(PREVIOUS_AUTO_COMPACT_WINDOW_ENV in settings.env)) {
|
|
86
|
-
changed = true;
|
|
87
|
-
if (!dryRun) {
|
|
88
|
-
settings.env[PREVIOUS_AUTO_COMPACT_WINDOW_ENV] = AUTO_COMPACT_WINDOW_ENV in settings.env
|
|
89
|
-
? String(settings.env[AUTO_COMPACT_WINDOW_ENV])
|
|
90
|
-
: PREVIOUS_ABSENT;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (settings.env[AUTO_COMPACT_WINDOW_ENV] !== compactWindow) {
|
|
94
|
-
changed = true;
|
|
95
|
-
if (!dryRun) settings.env[AUTO_COMPACT_WINDOW_ENV] = compactWindow;
|
|
96
|
-
}
|
|
97
|
-
} else if (PREVIOUS_AUTO_COMPACT_WINDOW_ENV in settings.env) {
|
|
98
|
-
changed = true;
|
|
99
|
-
if (!dryRun) {
|
|
100
|
-
const prev = settings.env[PREVIOUS_AUTO_COMPACT_WINDOW_ENV];
|
|
101
|
-
if (prev === PREVIOUS_ABSENT) delete settings.env[AUTO_COMPACT_WINDOW_ENV];
|
|
102
|
-
else settings.env[AUTO_COMPACT_WINDOW_ENV] = prev;
|
|
103
|
-
delete settings.env[PREVIOUS_AUTO_COMPACT_WINDOW_ENV];
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
if (!envIsObj) settings.env = {};
|
|
108
|
-
if (settings.env.ANTHROPIC_BASE_URL !== url) {
|
|
109
|
-
changed = true;
|
|
110
|
-
if (!dryRun) settings.env.ANTHROPIC_BASE_URL = url;
|
|
111
|
-
}
|
|
112
|
-
if (compactWindow) {
|
|
113
|
-
if (!(PREVIOUS_AUTO_COMPACT_WINDOW_ENV in settings.env)) {
|
|
114
|
-
changed = true;
|
|
115
|
-
if (!dryRun) {
|
|
116
|
-
settings.env[PREVIOUS_AUTO_COMPACT_WINDOW_ENV] = AUTO_COMPACT_WINDOW_ENV in settings.env
|
|
117
|
-
? String(settings.env[AUTO_COMPACT_WINDOW_ENV])
|
|
118
|
-
: PREVIOUS_ABSENT;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (settings.env[AUTO_COMPACT_WINDOW_ENV] !== compactWindow) {
|
|
122
|
-
changed = true;
|
|
123
|
-
if (!dryRun) settings.env[AUTO_COMPACT_WINDOW_ENV] = compactWindow;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (!changed) return { ok: true, changed: false, path: p };
|
|
128
|
-
if (dryRun) return { ok: true, changed: true, path: p, dryRun: true };
|
|
129
|
-
try {
|
|
130
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
131
|
-
const tmp = p + '.mixdog-tmp';
|
|
132
|
-
writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
133
|
-
renameSync(tmp, p);
|
|
134
|
-
} catch (e) {
|
|
135
|
-
return { ok: false, error: `cannot write ${p}: ${e.message}`, path: p };
|
|
136
|
-
}
|
|
137
|
-
return { ok: true, changed: true, path: p };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// RUNTIME base-url sync for live /model changes.
|
|
141
|
-
//
|
|
142
|
-
// The host client re-applies settings.env into process.env while the session is
|
|
143
|
-
// alive, but deleting ANTHROPIC_BASE_URL does not unset the already-present
|
|
144
|
-
// process env key. For native Claude choices, write the official Anthropic base
|
|
145
|
-
// URL instead. For mixdog choices, write the local gateway URL. This helper is
|
|
146
|
-
// deliberately narrower than setAnthropicBaseUrl(): it never creates restore
|
|
147
|
-
// markers and, by default, only acts when ANTHROPIC_BASE_URL already exists so
|
|
148
|
-
// it cannot enable the gateway for a user who disabled it.
|
|
149
|
-
export function syncAnthropicBaseUrl(url, { dryRun = false, requireExisting = true } = {}) {
|
|
150
|
-
const target = typeof url === 'string' && url.trim() ? url.trim() : '';
|
|
151
|
-
const p = resolveSettingsPath();
|
|
152
|
-
if (!target) return { ok: true, changed: false, path: p, skipped: 'missing-url' };
|
|
153
|
-
let raw;
|
|
154
|
-
try {
|
|
155
|
-
raw = readFileSync(p, 'utf8');
|
|
156
|
-
} catch (e) {
|
|
157
|
-
return { ok: false, error: `cannot read ${p}: ${e.message}`, path: p };
|
|
158
|
-
}
|
|
159
|
-
let settings;
|
|
160
|
-
try {
|
|
161
|
-
settings = JSON.parse(raw);
|
|
162
|
-
} catch (e) {
|
|
163
|
-
return { ok: false, error: `cannot parse ${p}: ${e.message}`, path: p };
|
|
164
|
-
}
|
|
165
|
-
if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
|
|
166
|
-
return { ok: false, error: `${p} is not a JSON object`, path: p };
|
|
167
|
-
}
|
|
168
|
-
const envIsObj = settings.env && typeof settings.env === 'object' && !Array.isArray(settings.env);
|
|
169
|
-
if (!envIsObj) {
|
|
170
|
-
if (requireExisting) return { ok: true, changed: false, path: p, skipped: 'gateway-inactive' };
|
|
171
|
-
settings.env = {};
|
|
172
|
-
}
|
|
173
|
-
if (requireExisting && !('ANTHROPIC_BASE_URL' in settings.env)) {
|
|
174
|
-
return { ok: true, changed: false, path: p, skipped: 'gateway-inactive' };
|
|
175
|
-
}
|
|
176
|
-
if (settings.env.ANTHROPIC_BASE_URL === target) {
|
|
177
|
-
return { ok: true, changed: false, path: p };
|
|
178
|
-
}
|
|
179
|
-
if (dryRun) return { ok: true, changed: true, path: p, dryRun: true };
|
|
180
|
-
settings.env.ANTHROPIC_BASE_URL = target;
|
|
181
|
-
try {
|
|
182
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
183
|
-
const tmp = p + '.mixdog-tmp';
|
|
184
|
-
writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
185
|
-
renameSync(tmp, p);
|
|
186
|
-
} catch (e) {
|
|
187
|
-
return { ok: false, error: `cannot write ${p}: ${e.message}`, path: p };
|
|
188
|
-
}
|
|
189
|
-
return { ok: true, changed: true, path: p };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// RUNTIME compact-window sync (Stage 4) — used by the gateway request path when
|
|
193
|
-
// the routed model's context window changes at runtime (/model switch). The host
|
|
194
|
-
// client watches settings.json (chokidar) and re-applies env to process.env
|
|
195
|
-
// without restart, and getEffectiveContextWindowSize reads process.env live, so
|
|
196
|
-
// rewriting CLAUDE_CODE_AUTO_COMPACT_WINDOW here makes the auto-compact
|
|
197
|
-
// threshold track the live model next turn.
|
|
198
|
-
//
|
|
199
|
-
// Deliberately NARROW vs setAnthropicBaseUrl():
|
|
200
|
-
// - NEVER touches ANTHROPIC_BASE_URL.
|
|
201
|
-
// - NEVER touches the MIXDOG_PREVIOUS_* restore marker (that is enable/disable
|
|
202
|
-
// only — runtime sync must not interfere with the restore contract).
|
|
203
|
-
// - NEVER creates settings.json (missing/unparseable → ok:false).
|
|
204
|
-
// - ONLY acts when ANTHROPIC_BASE_URL is already present (gateway active);
|
|
205
|
-
// otherwise skipped without a write.
|
|
206
|
-
// Returns { ok, changed, path, error?, skipped? }.
|
|
207
|
-
export function syncAutoCompactWindow(window, { dryRun = false } = {}) {
|
|
208
|
-
const p = resolveSettingsPath();
|
|
209
|
-
const compactWindow = normalizeAutoCompactWindow(window);
|
|
210
|
-
if (!compactWindow) return { ok: true, changed: false, path: p };
|
|
211
|
-
let raw;
|
|
212
|
-
try {
|
|
213
|
-
raw = readFileSync(p, 'utf8');
|
|
214
|
-
} catch (e) {
|
|
215
|
-
return { ok: false, error: `cannot read ${p}: ${e.message}`, path: p };
|
|
216
|
-
}
|
|
217
|
-
let settings;
|
|
218
|
-
try {
|
|
219
|
-
settings = JSON.parse(raw);
|
|
220
|
-
} catch (e) {
|
|
221
|
-
return { ok: false, error: `cannot parse ${p}: ${e.message}`, path: p };
|
|
222
|
-
}
|
|
223
|
-
if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
|
|
224
|
-
return { ok: false, error: `${p} is not a JSON object`, path: p };
|
|
225
|
-
}
|
|
226
|
-
const envIsObj = settings.env && typeof settings.env === 'object' && !Array.isArray(settings.env);
|
|
227
|
-
// Gateway must already be pointed at this file (active). Never write otherwise.
|
|
228
|
-
if (!envIsObj || !('ANTHROPIC_BASE_URL' in settings.env)) {
|
|
229
|
-
return { ok: true, changed: false, path: p, skipped: 'gateway-inactive' };
|
|
230
|
-
}
|
|
231
|
-
if (String(settings.env[AUTO_COMPACT_WINDOW_ENV]) === compactWindow) {
|
|
232
|
-
return { ok: true, changed: false, path: p };
|
|
233
|
-
}
|
|
234
|
-
if (dryRun) return { ok: true, changed: true, path: p, dryRun: true };
|
|
235
|
-
settings.env[AUTO_COMPACT_WINDOW_ENV] = compactWindow;
|
|
236
|
-
try {
|
|
237
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
238
|
-
const tmp = p + '.mixdog-tmp';
|
|
239
|
-
writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
240
|
-
renameSync(tmp, p);
|
|
241
|
-
} catch (e) {
|
|
242
|
-
return { ok: false, error: `cannot write ${p}: ${e.message}`, path: p };
|
|
243
|
-
}
|
|
244
|
-
return { ok: true, changed: true, path: p };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function syncAdditionalModelOptions(options, { dryRun = false, configPath = null } = {}) {
|
|
248
|
-
const p = configPath || resolveGlobalClaudeConfigPath();
|
|
249
|
-
const nextOptions = Array.isArray(options)
|
|
250
|
-
? options
|
|
251
|
-
.filter(opt => opt && typeof opt.value === 'string' && typeof opt.label === 'string' && typeof opt.description === 'string')
|
|
252
|
-
.map(opt => ({ value: opt.value, label: opt.label, description: opt.description }))
|
|
253
|
-
: [];
|
|
254
|
-
let raw;
|
|
255
|
-
try {
|
|
256
|
-
raw = readFileSync(p, 'utf8');
|
|
257
|
-
} catch (e) {
|
|
258
|
-
return { ok: false, changed: false, error: `cannot read ${p}: ${e.message}`, path: p };
|
|
259
|
-
}
|
|
260
|
-
let config;
|
|
261
|
-
try {
|
|
262
|
-
config = JSON.parse(raw);
|
|
263
|
-
} catch (e) {
|
|
264
|
-
return { ok: false, changed: false, error: `cannot parse ${p}: ${e.message}`, path: p };
|
|
265
|
-
}
|
|
266
|
-
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
267
|
-
return { ok: false, changed: false, error: `${p} is not a JSON object`, path: p };
|
|
268
|
-
}
|
|
269
|
-
const current = Array.isArray(config.additionalModelOptionsCache) ? config.additionalModelOptionsCache : [];
|
|
270
|
-
const preserved = current.filter(opt => !(opt && typeof opt.value === 'string' && opt.value.startsWith('mixdog/')));
|
|
271
|
-
const merged = [...preserved, ...nextOptions];
|
|
272
|
-
if (JSON.stringify(current) === JSON.stringify(merged)) {
|
|
273
|
-
return { ok: true, changed: false, path: p };
|
|
274
|
-
}
|
|
275
|
-
if (dryRun) return { ok: true, changed: true, path: p, dryRun: true };
|
|
276
|
-
config.additionalModelOptionsCache = merged;
|
|
277
|
-
try {
|
|
278
|
-
const tmp = p + '.mixdog-tmp';
|
|
279
|
-
writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
280
|
-
renameSync(tmp, p);
|
|
281
|
-
} catch (e) {
|
|
282
|
-
return { ok: false, changed: false, error: `cannot write ${p}: ${e.message}`, path: p };
|
|
283
|
-
}
|
|
284
|
-
return { ok: true, changed: true, path: p };
|
|
285
|
-
}
|