mixdog 0.7.11 → 0.7.13
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/.claude-plugin/marketplace.json +5 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +28 -74
- package/README.md +193 -249
- package/bin/statusline-launcher.mjs +5 -1
- package/bin/statusline-lib.mjs +14 -6
- package/bin/statusline.mjs +14 -6
- package/bun.lock +128 -3
- package/defaults/hidden-roles.json +3 -0
- package/defaults/user-workflow.json +1 -2
- package/defaults/user-workflow.md +5 -1
- package/hooks/lib/settings-loader.cjs +4 -3
- package/hooks/pre-tool-subagent.cjs +7 -2
- package/hooks/session-start.cjs +52 -24
- package/lib/mixdog-debug.cjs +163 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +9 -2
- package/scripts/builtin-utils-smoke.mjs +14 -8
- package/scripts/bump.mjs +80 -0
- package/scripts/doctor.mjs +8 -3
- package/scripts/ensure-deps.mjs +2 -2
- package/scripts/mutation-io-smoke.mjs +17 -1
- package/scripts/permission-eval-smoke.mjs +18 -1
- package/scripts/run-mcp.mjs +65 -9
- package/scripts/statusline-launcher-smoke.mjs +2 -2
- package/scripts/webhook-selfheal-smoke.mjs +1 -3
- package/server-main.mjs +57 -3
- package/setup/install.mjs +574 -574
- package/setup/launch-core.mjs +0 -1
- package/setup/setup-server.mjs +90 -35
- package/setup/setup.html +44 -11
- package/skills/setup/SKILL.md +12 -2
- package/src/agent/index.mjs +1 -1
- package/src/agent/orchestrator/config.mjs +58 -6
- package/src/agent/orchestrator/providers/model-catalog.mjs +1 -1
- package/src/agent/orchestrator/providers/openai-oauth.mjs +9 -2
- package/src/agent/orchestrator/providers/openai-ws.mjs +23 -0
- package/src/agent/orchestrator/session/loop.mjs +3 -3
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +6 -2
- package/src/agent/orchestrator/tools/bash-session.mjs +1 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +29 -6
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +8 -4
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +29 -8
- package/src/agent/orchestrator/tools/builtin.mjs +5 -2
- package/src/agent/orchestrator/tools/cwd-tool.mjs +17 -17
- package/src/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/agent/tool-defs.mjs +1 -1
- package/src/channels/index.mjs +39 -9
- package/src/channels/lib/event-queue.mjs +24 -1
- package/src/channels/lib/hook-pipe-server.mjs +21 -8
- package/src/channels/lib/webhook.mjs +159 -20
- package/src/memory/index.mjs +5 -1
- package/src/memory/lib/core-memory-store.mjs +1 -1
- package/src/memory/lib/memory-cycle1.mjs +8 -4
- package/src/memory/lib/memory-cycle2.mjs +1 -1
- package/src/memory/lib/memory-cycle3.mjs +1 -1
- package/src/memory/lib/memory-recall-store.mjs +27 -10
- package/src/search/lib/backends/openai-oauth.mjs +6 -2
- package/src/search/lib/cache.mjs +55 -7
- package/tools.json +2 -2
- package/scripts/test-config-rmw-restore.mjs +0 -122
package/setup/launch-core.mjs
CHANGED
|
@@ -285,7 +285,6 @@ async function spawnServerWithLog(pluginRoot, pluginData, { openOnStart = true }
|
|
|
285
285
|
MIXDOG_SETUP_OPEN_ON_START: openOnStart ? '1' : '0',
|
|
286
286
|
MIXDOG_SETUP_PARENT_PID: String(findAncestorPid() || ''),
|
|
287
287
|
},
|
|
288
|
-
windowsHide: true,
|
|
289
288
|
});
|
|
290
289
|
} catch (error) {
|
|
291
290
|
closeLog(launchLog.fd);
|
package/setup/setup-server.mjs
CHANGED
|
@@ -599,9 +599,17 @@ function _modelFromConfiguredId(id, provider) {
|
|
|
599
599
|
}
|
|
600
600
|
|
|
601
601
|
function _familyFromModelId(id) {
|
|
602
|
-
const
|
|
602
|
+
const s = String(id || '').toLowerCase();
|
|
603
|
+
const claude = s.match(/^claude-(opus|sonnet|haiku)/i);
|
|
603
604
|
if (claude) return claude[1].toLowerCase();
|
|
604
|
-
|
|
605
|
+
if (s.includes('nano')) return 'gpt-nano';
|
|
606
|
+
if (s.includes('mini')) return 'gpt-mini';
|
|
607
|
+
if (s.includes('codex')) return 'gpt-codex';
|
|
608
|
+
if (s.startsWith('gpt-5.5')) return 'gpt-5.5';
|
|
609
|
+
if (s.startsWith('gpt-5.4')) return 'gpt-5.4';
|
|
610
|
+
if (s.startsWith('gpt-5.2')) return 'gpt-5.2';
|
|
611
|
+
if (s.startsWith('gpt-5')) return 'gpt-5';
|
|
612
|
+
const gpt = s.match(/^(gpt-\d+(?:\.\d+)?)/i);
|
|
605
613
|
if (gpt) return gpt[1].toLowerCase();
|
|
606
614
|
return undefined;
|
|
607
615
|
}
|
|
@@ -1228,28 +1236,18 @@ async function openAppWindowSequence() {
|
|
|
1228
1236
|
// inherit so they do not allocate their own conhost instances.
|
|
1229
1237
|
const escVbs = s => String(s).replace(/"/g, '""');
|
|
1230
1238
|
const argsStr = args.join(' ');
|
|
1231
|
-
//
|
|
1232
|
-
//
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1235
|
-
//
|
|
1236
|
-
//
|
|
1237
|
-
//
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1241
|
-
//
|
|
1242
|
-
//
|
|
1243
|
-
// crash. A fresh `--app` launch then rendezvouses with that dead
|
|
1244
|
-
// instance over the singleton socket, forwards its URL, and exits
|
|
1245
|
-
// WITHOUT opening a window — the reported cold-open bug (URL
|
|
1246
|
-
// printed, no window, a later /open works once the lock is reaped).
|
|
1247
|
-
// Deleting the stale Singleton* files first guarantees chrome
|
|
1248
|
-
// boots a real window instead of IPC-forwarding to a ghost. This
|
|
1249
|
-
// is the invariant ("spawn into a clean singleton when no live
|
|
1250
|
-
// owner"), not a retry. The title-scoped taskkill is kept only as
|
|
1251
|
-
// a defensive belt for a same-title window with a non-matching
|
|
1252
|
-
// command line; it is a no-op in the common case.
|
|
1239
|
+
// Hybrid warm-focus / ghost-respawn. A chrome process can outlive its
|
|
1240
|
+
// window (closed / crashed / IPC-forwarded over the singleton socket),
|
|
1241
|
+
// so "a process exists" never proves "a window is visible" — the old
|
|
1242
|
+
// focus-if-found path then opened nothing (the "URL printed, no window"
|
|
1243
|
+
// cold-open bug), while killing+respawning on EVERY open cold-boots
|
|
1244
|
+
// chrome each time (slow). Balance: TryFocus the existing MAIN with a
|
|
1245
|
+
// short bound. If it activates → healthy window, just focus it (fast,
|
|
1246
|
+
// no respawn). If it can't (ghost) or none exists → KillMixdogChromes
|
|
1247
|
+
// for this profile (--user-data-dir match, MAIN + helpers), clear the
|
|
1248
|
+
// stale Singleton* locks, spawn one fresh --app window. The VBS finally
|
|
1249
|
+
// re-checks FindChromePid and exits 0 only if a MAIN is live (else 2 →
|
|
1250
|
+
// default-browser fallback in JS), so /open's ok reflects a real window.
|
|
1253
1251
|
//
|
|
1254
1252
|
// The taskkill → chrome chain runs under one hidden cmd.exe (one
|
|
1255
1253
|
// cmd.exe per /open). `&` runs both regardless of exit code (taskkill
|
|
@@ -1267,7 +1265,7 @@ async function openAppWindowSequence() {
|
|
|
1267
1265
|
const vbsLines = [
|
|
1268
1266
|
'Option Explicit',
|
|
1269
1267
|
'Const HIDDEN_WINDOW = 0',
|
|
1270
|
-
'Dim Wmi, Startup, Wsh, cmdLine, cmdPid, rc, appNeedle, profileNeedle, existingPid, profileDir, procName',
|
|
1268
|
+
'Dim Wmi, Startup, Wsh, cmdLine, cmdPid, rc, appNeedle, profileNeedle, existingPid, profileDir, procName, focused',
|
|
1271
1269
|
'Set Wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2")',
|
|
1272
1270
|
'Set Wsh = CreateObject("WScript.Shell")',
|
|
1273
1271
|
`appNeedle = "${escVbs(appNeedle)}"`,
|
|
@@ -1275,15 +1273,22 @@ async function openAppWindowSequence() {
|
|
|
1275
1273
|
`profileDir = "${escVbs(chromeProfile)}"`,
|
|
1276
1274
|
`procName = "${escVbs(procName)}"`,
|
|
1277
1275
|
'existingPid = FindChromePid(Wmi, appNeedle, profileNeedle)',
|
|
1278
|
-
'
|
|
1276
|
+
'focused = False',
|
|
1277
|
+
'If existingPid <> 0 Then focused = TryFocus(Wsh, existingPid, 8)',
|
|
1278
|
+
'If Not focused Then',
|
|
1279
|
+
' Call KillMixdogChromes(Wmi, profileNeedle)',
|
|
1280
|
+
' WScript.Sleep 200',
|
|
1279
1281
|
' Call ClearSingletonLocks(profileDir)',
|
|
1280
1282
|
' Set Startup = Wmi.Get("Win32_ProcessStartup").SpawnInstance_',
|
|
1281
1283
|
' Startup.ShowWindow = HIDDEN_WINDOW',
|
|
1282
1284
|
` cmdLine = "cmd.exe ${escVbs(cmdArg)}"`,
|
|
1283
1285
|
' rc = Wmi.Get("Win32_Process").Create(cmdLine, Null, Startup, cmdPid)',
|
|
1284
1286
|
' If rc <> 0 Then WScript.Quit rc',
|
|
1287
|
+
' Call FocusMixdogWindow(Wmi, Wsh, appNeedle, profileNeedle)',
|
|
1285
1288
|
'End If',
|
|
1286
|
-
'
|
|
1289
|
+
'existingPid = FindChromePid(Wmi, appNeedle, profileNeedle)',
|
|
1290
|
+
'If existingPid = 0 Then WScript.Quit 2',
|
|
1291
|
+
'WScript.Quit 0',
|
|
1287
1292
|
'',
|
|
1288
1293
|
'Sub ClearSingletonLocks(profileDir)',
|
|
1289
1294
|
' Dim Fso, names, i, p',
|
|
@@ -1297,6 +1302,32 @@ async function openAppWindowSequence() {
|
|
|
1297
1302
|
' On Error GoTo 0',
|
|
1298
1303
|
'End Sub',
|
|
1299
1304
|
'',
|
|
1305
|
+
'Sub KillMixdogChromes(Wmi, profileNeedle)',
|
|
1306
|
+
' Dim proc, commandLine',
|
|
1307
|
+
' On Error Resume Next',
|
|
1308
|
+
' For Each proc In Wmi.ExecQuery("SELECT ProcessId,CommandLine FROM Win32_Process WHERE Name = \'" & procName & "\'")',
|
|
1309
|
+
' commandLine = ""',
|
|
1310
|
+
' If Not IsNull(proc.CommandLine) Then commandLine = CStr(proc.CommandLine)',
|
|
1311
|
+
' If InStr(1, commandLine, profileNeedle, vbTextCompare) > 0 Then proc.Terminate',
|
|
1312
|
+
' Next',
|
|
1313
|
+
' On Error GoTo 0',
|
|
1314
|
+
'End Sub',
|
|
1315
|
+
'',
|
|
1316
|
+
'Function TryFocus(Wsh, pid, maxTicks)',
|
|
1317
|
+
' Dim i, ok',
|
|
1318
|
+
' ok = False',
|
|
1319
|
+
' On Error Resume Next',
|
|
1320
|
+
' For i = 1 To maxTicks',
|
|
1321
|
+
' Wsh.SendKeys "%"',
|
|
1322
|
+
' WScript.Sleep 25',
|
|
1323
|
+
' ok = Wsh.AppActivate(CLng(pid))',
|
|
1324
|
+
' If ok Then Exit For',
|
|
1325
|
+
' WScript.Sleep 120',
|
|
1326
|
+
' Next',
|
|
1327
|
+
' On Error GoTo 0',
|
|
1328
|
+
' TryFocus = ok',
|
|
1329
|
+
'End Function',
|
|
1330
|
+
'',
|
|
1300
1331
|
'Sub FocusMixdogWindow(Wmi, Wsh, appNeedle, profileNeedle)',
|
|
1301
1332
|
' Dim i, pid, activated',
|
|
1302
1333
|
' On Error Resume Next',
|
|
@@ -1362,29 +1393,37 @@ async function openAppWindowSequence() {
|
|
|
1362
1393
|
// parented to wscript, so a tree-kill of wscript reaps only the focus
|
|
1363
1394
|
// loop.
|
|
1364
1395
|
const WSCRIPT_OPEN_DEADLINE_MS = 12000;
|
|
1365
|
-
const
|
|
1396
|
+
const outcome = await new Promise(resolve => {
|
|
1366
1397
|
const wscriptChild = spawn('wscript.exe', ['//B', '//NoLogo', vbsPath], {
|
|
1367
1398
|
stdio: 'ignore', windowsHide: true,
|
|
1368
1399
|
});
|
|
1369
1400
|
let settled = false;
|
|
1370
|
-
const finish =
|
|
1401
|
+
const finish = v => { if (settled) return; settled = true; clearTimeout(timer); resolve(v); };
|
|
1371
1402
|
const timer = setTimeout(() => {
|
|
1372
1403
|
// Tree-kill the wscript launcher only; the detached browser lives on.
|
|
1373
1404
|
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(wscriptChild.pid)], { windowsHide: true, stdio: 'ignore', timeout: 4000 }); } catch {}
|
|
1374
1405
|
try { wscriptChild.kill(); } catch {}
|
|
1375
|
-
finish(true);
|
|
1406
|
+
finish({ timedOut: true });
|
|
1376
1407
|
}, WSCRIPT_OPEN_DEADLINE_MS);
|
|
1377
1408
|
if (typeof timer.unref === 'function') timer.unref();
|
|
1378
|
-
wscriptChild.once('error', () => finish(false));
|
|
1379
|
-
wscriptChild.once('exit',
|
|
1409
|
+
wscriptChild.once('error', () => finish({ timedOut: false, code: -1 }));
|
|
1410
|
+
wscriptChild.once('exit', code => finish({ timedOut: false, code }));
|
|
1380
1411
|
});
|
|
1381
|
-
if (timedOut) {
|
|
1412
|
+
if (outcome.timedOut) {
|
|
1382
1413
|
const err = `wscript launcher did not exit within ${WSCRIPT_OPEN_DEADLINE_MS}ms; killed launcher (browser left running)`;
|
|
1383
1414
|
attempts.push({ method: 'browser app mode (wscript)', ok: false, error: err });
|
|
1384
1415
|
logOpenFailure('browser app mode (wscript)', err);
|
|
1385
|
-
} else {
|
|
1416
|
+
} else if (outcome.code === 0) {
|
|
1417
|
+
// VBS exits 0 only after it re-checks FindChromePid post-spawn and a
|
|
1418
|
+
// MAIN (non-helper) chrome for this profile is live — an honest
|
|
1419
|
+
// "window materialized" signal, not just "the launcher exited". Any
|
|
1420
|
+
// other code (esp. 2 = no window) falls through to the default browser.
|
|
1386
1421
|
chromeSpawnOk = true;
|
|
1387
1422
|
attempts.push({ method: 'browser app mode (wscript)', ok: true });
|
|
1423
|
+
} else {
|
|
1424
|
+
const err = `wscript exited ${outcome.code}; config window did not materialize`;
|
|
1425
|
+
attempts.push({ method: 'browser app mode (wscript)', ok: false, error: err });
|
|
1426
|
+
logOpenFailure('browser app mode (wscript)', err);
|
|
1388
1427
|
}
|
|
1389
1428
|
} catch (error) {
|
|
1390
1429
|
attempts.push({ method: 'browser app mode (wscript)', ok: false, error: formatOpenError(error) });
|
|
@@ -2210,12 +2249,19 @@ async function handleRequest(req, res) {
|
|
|
2210
2249
|
const rawMaint = cfg.maintenance || {};
|
|
2211
2250
|
// Strip legacy keys that no longer belong in maintenance
|
|
2212
2251
|
// (classification/recap were retired with the cycle1 split;
|
|
2213
|
-
// scheduler/webhook keep their model per-entry
|
|
2252
|
+
// scheduler/webhook keep their model per-entry; the three memory-cycle
|
|
2253
|
+
// MODEL presets collapsed into a single `memory` key — fold the first
|
|
2254
|
+
// present legacy cycle value into `memory` before stripping).
|
|
2214
2255
|
// Persist back when the stored config carried any of them so the Setup
|
|
2215
2256
|
// panel and the runtime resolver stop having to dual-match name vs id.
|
|
2216
2257
|
const allowedKeys = new Set([...Object.keys(DEFAULT_MAINTENANCE), ...MAINTENANCE_SLOTS]);
|
|
2217
2258
|
const cleanMaint = {};
|
|
2218
2259
|
let changed = false;
|
|
2260
|
+
const legacyCycleKeys = ['cycle1', 'cycle2', 'cycle3'];
|
|
2261
|
+
if (!('memory' in rawMaint) && legacyCycleKeys.some(k => k in rawMaint)) {
|
|
2262
|
+
cleanMaint.memory = rawMaint.cycle1 ?? rawMaint.cycle2 ?? rawMaint.cycle3 ?? DEFAULT_MAINTENANCE.memory;
|
|
2263
|
+
changed = true;
|
|
2264
|
+
}
|
|
2219
2265
|
for (const [k, v] of Object.entries(rawMaint)) {
|
|
2220
2266
|
if (allowedKeys.has(k)) cleanMaint[k] = v;
|
|
2221
2267
|
else changed = true;
|
|
@@ -2259,6 +2305,15 @@ async function handleRequest(req, res) {
|
|
|
2259
2305
|
return;
|
|
2260
2306
|
}
|
|
2261
2307
|
const nextMaint = { ...(cfg.maintenance || {}) };
|
|
2308
|
+
// Migrate any stored legacy cycle1/2/3 model keys into `memory` (one-time
|
|
2309
|
+
// schema collapse) so the persisted config never carries them forward.
|
|
2310
|
+
{
|
|
2311
|
+
const legacyCycleKeys = ['cycle1', 'cycle2', 'cycle3'];
|
|
2312
|
+
if (!('memory' in nextMaint) && legacyCycleKeys.some(k => k in nextMaint)) {
|
|
2313
|
+
nextMaint.memory = nextMaint.cycle1 ?? nextMaint.cycle2 ?? nextMaint.cycle3 ?? DEFAULT_MAINTENANCE.memory;
|
|
2314
|
+
}
|
|
2315
|
+
for (const k of legacyCycleKeys) delete nextMaint[k];
|
|
2316
|
+
}
|
|
2262
2317
|
for (const [k, v] of Object.entries(data)) {
|
|
2263
2318
|
if (v == null || v === '') delete nextMaint[k]; // inherit → remove override
|
|
2264
2319
|
else nextMaint[k] = v;
|
package/setup/setup.html
CHANGED
|
@@ -2640,9 +2640,48 @@ const AG_EFFORT_LABEL = { none: 'None', low: 'Low', medium: 'Medium', high: 'Hig
|
|
|
2640
2640
|
// Families that don't support fast mode even when the provider does.
|
|
2641
2641
|
const AG_FAMILY_NO_FAST = new Set(['haiku', 'gpt-nano', 'gpt-codex']);
|
|
2642
2642
|
const AG_FAST_PROVIDERS = new Set(['anthropic', 'anthropic-oauth', 'openai', 'openai-oauth']);
|
|
2643
|
+
const AG_OPENAI_DIRECT_FAST_MODEL_RE = [
|
|
2644
|
+
/^gpt-5\.5(?:-\d{4}|$)/,
|
|
2645
|
+
/^gpt-5\.4(?:-\d{4}|$)/,
|
|
2646
|
+
/^gpt-5\.4-mini(?:-\d{4}|$)/,
|
|
2647
|
+
];
|
|
2643
2648
|
let agModelList = [];
|
|
2644
2649
|
const AG_ACCESS_LABELS = { full: 'Read & Write', readonly: 'Read Only', mcp: 'None' };
|
|
2645
2650
|
|
|
2651
|
+
function agNormalizeFastProvider(provider) {
|
|
2652
|
+
return provider === 'openai-api' ? 'openai' : provider;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function agOpenAIDirectSupportsFast(modelId) {
|
|
2656
|
+
const id = String(modelId || '').trim();
|
|
2657
|
+
return AG_OPENAI_DIRECT_FAST_MODEL_RE.some(re => re.test(id));
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
function agExplicitFastSupport(model) {
|
|
2661
|
+
const hasServiceTiers = Array.isArray(model?.serviceTiers);
|
|
2662
|
+
const hasSpeedTiers = Array.isArray(model?.additionalSpeedTiers);
|
|
2663
|
+
if (!hasServiceTiers && !hasSpeedTiers) return null;
|
|
2664
|
+
const serviceTiers = hasServiceTiers ? model.serviceTiers : [];
|
|
2665
|
+
const speedTiers = hasSpeedTiers ? model.additionalSpeedTiers : [];
|
|
2666
|
+
const serviceFast = serviceTiers.some(t => {
|
|
2667
|
+
const id = typeof t === 'string' ? t : t?.id;
|
|
2668
|
+
return id === 'priority' || id === 'fast';
|
|
2669
|
+
});
|
|
2670
|
+
const speedFast = speedTiers.some(t => t === 'fast' || t === 'priority');
|
|
2671
|
+
return serviceFast || speedFast;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function agModelSupportsFast(provider, model) {
|
|
2675
|
+
const normalizedProvider = agNormalizeFastProvider(provider);
|
|
2676
|
+
if (!model) return false;
|
|
2677
|
+
const explicit = agExplicitFastSupport(model);
|
|
2678
|
+
if (explicit !== null) return explicit;
|
|
2679
|
+
if (normalizedProvider === 'openai') return agOpenAIDirectSupportsFast(model.id);
|
|
2680
|
+
const providerFast = AG_FAST_PROVIDERS.has(normalizedProvider);
|
|
2681
|
+
const familyNoFast = model?.family && AG_FAMILY_NO_FAST.has(model.family);
|
|
2682
|
+
return providerFast && !familyNoFast;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2646
2685
|
async function loadAgentData() {
|
|
2647
2686
|
const r = await fetch('/agent/config').then(r => r.json()).catch(() => ({}));
|
|
2648
2687
|
agConfig = r.config || {};
|
|
@@ -2974,9 +3013,7 @@ function agUpdateEffortAndFast(provider) {
|
|
|
2974
3013
|
effortSel.innerHTML = allowed.map(v => `<option value="${v}">${AG_EFFORT_LABEL[v] || v}</option>`).join('');
|
|
2975
3014
|
}
|
|
2976
3015
|
|
|
2977
|
-
const
|
|
2978
|
-
const familyNoFast = model?.family && AG_FAMILY_NO_FAST.has(model.family);
|
|
2979
|
-
const fastAllowed = modelResolved && providerFast && !familyNoFast;
|
|
3016
|
+
const fastAllowed = modelResolved && agModelSupportsFast(provider, model);
|
|
2980
3017
|
fastRow.style.display = fastAllowed ? 'flex' : 'none';
|
|
2981
3018
|
if (!fastAllowed) document.getElementById('ag-pf-fast').classList.remove('on');
|
|
2982
3019
|
}
|
|
@@ -3479,8 +3516,8 @@ async function srRenderModelPresets() {
|
|
|
3479
3516
|
}
|
|
3480
3517
|
|
|
3481
3518
|
// Reveal/hide effort + fast based on the currently selected openai model.
|
|
3482
|
-
// Mirrors agUpdateEffortAndFast() so the rules are identical: model
|
|
3483
|
-
// drives effort options
|
|
3519
|
+
// Mirrors agUpdateEffortAndFast() so the rules are identical: provider/model
|
|
3520
|
+
// metadata drives effort options and Fast Mode availability.
|
|
3484
3521
|
function srUpdateOpenAIEffortFast() {
|
|
3485
3522
|
const body = document.getElementById('sr-model-presets-body');
|
|
3486
3523
|
if (!body) return;
|
|
@@ -3505,9 +3542,7 @@ function srUpdateOpenAIEffortFast() {
|
|
|
3505
3542
|
effortSel.style.display = '';
|
|
3506
3543
|
effortSel.innerHTML = allowed.map(v => '<option value="' + v + '"' + (v === storedEffort ? ' selected' : '') + '>' + (AG_EFFORT_LABEL[v] || v) + '</option>').join('');
|
|
3507
3544
|
}
|
|
3508
|
-
const
|
|
3509
|
-
const familyNoFast = model?.family && AG_FAMILY_NO_FAST.has(model.family);
|
|
3510
|
-
const fastAllowed = !!model && providerFast && !familyNoFast;
|
|
3545
|
+
const fastAllowed = !!model && agModelSupportsFast(normalizedProvider, model);
|
|
3511
3546
|
const fastLabel = body.querySelector('[data-fam-fast-label="openai"]');
|
|
3512
3547
|
fastEl.style.display = fastAllowed ? '' : 'none';
|
|
3513
3548
|
if (fastLabel) fastLabel.style.display = fastAllowed ? '' : 'none';
|
|
@@ -3607,9 +3642,7 @@ async function srSavePanel() {
|
|
|
3607
3642
|
// -- Agent Maintenance --
|
|
3608
3643
|
const AG_MAINT_TASKS = [
|
|
3609
3644
|
{ id: 'explore', label: 'Explore', desc: 'Filesystem exploration agent (explore tool)' },
|
|
3610
|
-
{ id: '
|
|
3611
|
-
{ id: 'cycle2', label: 'Memory Cycle 2', desc: 'Root re-scorer (core memory promotion)' },
|
|
3612
|
-
{ id: 'cycle3', label: 'Memory Cycle 3', desc: 'Core memory reviewer' },
|
|
3645
|
+
{ id: 'memory', label: 'Memory Cycles', desc: 'Chunker / re-scorer / core reviewer (cycles 1-3)' },
|
|
3613
3646
|
];
|
|
3614
3647
|
let agMaintenance = {};
|
|
3615
3648
|
let agMaintenanceDefaults = {};
|
package/skills/setup/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: setup
|
|
3
|
-
description: Invoke for ANY mixdog setup or config task — onboarding a fresh install AND editing existing config. Covers agent providers/models, bridge role→preset mapping (changing worker/reviewer/tester/debugger models), agent.presets, channels/Discord, memory, search, webhook/ngrok, quiet hours, DM access, address form (user title), launch flag, and secrets. Trigger on intents like "mixdog config", "edit/change settings", "set up", "세팅"/"설정 수정", "호칭 변경" (change how the assistant addresses the user), "조용시간"/quiet hours, DM access, "change worker (or any role) model", "add a preset", "switch provider", or first-time "what goes where". Also handles uninstall/restore intents ("remove mixdog", "restore pre-install state", "원상복구") — route those to `node scripts/uninstall.mjs` and UNINSTALL.md.
|
|
3
|
+
description: Invoke for ANY mixdog setup or config task — onboarding a fresh install AND editing existing config. Covers agent providers/models, bridge role→preset mapping (changing worker/reviewer/tester/debugger models), agent.presets, the cross-verification workflow text & role roster (add/remove a role, tune the debugger/reviewer loop prose), channels/Discord, memory, search, webhook/ngrok, quiet hours, DM access, address form (user title), launch flag, and secrets. Trigger on intents like "mixdog config", "edit/change settings", "set up", "세팅"/"설정 수정", "호칭 변경" (change how the assistant addresses the user), "조용시간"/quiet hours, DM access, "change worker (or any role) model", "add a preset", "switch provider", "remove the tester role", "tune the debugger loop", "edit the workflow prose", or first-time "what goes where". Also handles uninstall/restore intents ("remove mixdog", "restore pre-install state", "원상복구") — route those to `node scripts/uninstall.mjs` and UNINSTALL.md.
|
|
4
4
|
version: 0.1.0
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ Guided onboarding for a fresh mixdog install **and** a reference for editing exi
|
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| Any setting value (channels, memory, agent presets, search, capabilities, cwd) | `${DATA}/mixdog-config.json` | `defaults/*.template.json`, anything under `<plugin>/cache/` |
|
|
16
16
|
| Which model a role uses | `${DATA}/user-workflow.json` (`roles[].preset`) | `defaults/user-workflow.json` |
|
|
17
|
+
| Add/remove a role (e.g. drop `tester`), or tune the workflow loop prose (cross-verification / debugger fan-out) | `${DATA}/user-workflow.json` (`roles[]`) **and** `${DATA}/user-workflow.md` (loop text) | the `defaults/` + `cache/` copies — those are the shipped template, edited only to change fresh-install defaults |
|
|
17
18
|
| A secret (bot token, API key, authtoken) | OS keychain / `MIXDOG_*` env | any JSON file |
|
|
18
19
|
| Skill text / prompts / plugin code | the marketplace **source** (`<plugin>/marketplaces/trib-plugin/`; dev installs: the source repo, then sync) | the `cache/` copy — it is overwritten on update/sync |
|
|
19
20
|
|
|
@@ -51,10 +52,19 @@ Guided onboarding for a fresh mixdog install **and** a reference for editing exi
|
|
|
51
52
|
## Config structure & editing
|
|
52
53
|
|
|
53
54
|
- Config lives under the plugin data dir `~/.claude/plugins/data/mixdog-trib-plugin/` (the `CLAUDE_PLUGIN_DATA` env var overrides it). Two files matter:
|
|
54
|
-
- **user-workflow.json** — `roles[] {name, preset, permission}` maps each bridge role to a preset (the active role set is whatever this file defines — e.g. worker / reviewer / debugger
|
|
55
|
+
- **user-workflow.json** — `roles[] {name, preset, permission}` maps each bridge role to a preset (the active role set is whatever this file defines — e.g. worker / reviewer / debugger). This sets which model each role uses. To change a role's model, set its `preset` to one of the preset names below (takes effect LIVE — bridge dispatch re-reads this file on every spawn; only the Lead's injected `# Roles` list waits for the next SessionStart).
|
|
55
56
|
- **mixdog-config.json → `agent.presets[] {id, name, type, provider, model, tools, effort?, fast?, xaiCacheMaxInFlight?}`** defines each preset, e.g. opus-high → claude-opus-4-8 / high, composer-2.5 → grok-composer-2.5-fast, gpt-5.5-xhigh → gpt-5.5 / xhigh. `type` is `"bridge"` and `tools` is `"full"` for normal worker presets; `xaiCacheMaxInFlight` is a grok-oauth-only tuning knob. `agent.default` is the fallback preset for any role without its own mapping. To offer a new model, add a preset here first, then point a role at it in user-workflow.json.
|
|
56
57
|
- Edit via the Setup UI (Custom Workflow for role→preset, Agent presets for presets) or edit the JSON files directly (reloads next session). These are user data — no rebuild needed.
|
|
57
58
|
|
|
59
|
+
### Workflow files — quick-edit map (NO exploration needed)
|
|
60
|
+
|
|
61
|
+
Two sibling files under `${DATA}` (`~/.claude/plugins/data/mixdog-trib-plugin/`) define the active bridge workflow — edit them directly, do not go hunting:
|
|
62
|
+
- **`${DATA}/user-workflow.json`** — `roles[] {name, preset, permission}`. The active role set IS exactly this list. Add or remove a role here (e.g. drop `tester`); removal takes effect on the next bridge spawn (live re-read). Keep each remaining role's `preset` untouched.
|
|
63
|
+
- **`${DATA}/user-workflow.md`** — the role-assignment table + cross-verification loop prose, injected as the Lead's `# User Workflow` rule. Tune the debugger fan-out / reviewer pairing text here. The Lead's injected copy refreshes on the next SessionStart.
|
|
64
|
+
- To change the **shipped fresh-install defaults** (not just this user), edit the repo's `defaults/user-workflow.{json,md}` to match. `cache/` copies are never the edit target.
|
|
65
|
+
|
|
66
|
+
After editing `${DATA}` files, call `reload_config`; after editing repo `defaults/`, run dev-sync to propagate.
|
|
67
|
+
|
|
58
68
|
## Agent providers — supported list & onboarding
|
|
59
69
|
|
|
60
70
|
A preset's `provider` field selects the backend. Authoritative allow-list:
|
package/src/agent/index.mjs
CHANGED
|
@@ -190,7 +190,7 @@ function scheduleAgentConfigReload(reason = 'change') {
|
|
|
190
190
|
_agentConfigReloadTimer.unref?.();
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
async function reloadAgentConfig(reason = 'change') {
|
|
193
|
+
export async function reloadAgentConfig(reason = 'change') {
|
|
194
194
|
if (_agentConfigReloadRunning) {
|
|
195
195
|
_agentConfigReloadQueued = true;
|
|
196
196
|
return;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolvePluginData } from '../../shared/plugin-paths.mjs';
|
|
2
2
|
import { readSection, updateSection, getAgentApiKey } from '../../shared/config.mjs';
|
|
3
|
+
import { OPENAI_COMPAT_PRESETS } from './providers/openai-compat.mjs';
|
|
3
4
|
import { hasAnthropicOAuthCredentials } from './providers/anthropic-oauth.mjs';
|
|
4
5
|
import { hasOpenAIOAuthCredentials } from './providers/openai-oauth.mjs';
|
|
5
6
|
import { hasGrokOAuthCredentials } from './providers/grok-oauth.mjs';
|
|
@@ -23,15 +24,17 @@ const ENV_KEY_MAP = {
|
|
|
23
24
|
// resolvePresetName() (bridge-llm) always resolves a model directly from
|
|
24
25
|
// `maint[slot]` — no shared `defaultPreset` fallback is needed or used.
|
|
25
26
|
// Memory cycles + Lead helper fan-out (explore/cycle1/cycle2/cycle3) and
|
|
26
|
-
// entry-driven dispatch (scheduler/webhook) all default to `haiku`.
|
|
27
|
+
// entry-driven dispatch (scheduler/webhook) all default to `haiku`. The three
|
|
28
|
+
// memory cycles (chunker / re-scorer / core reviewer) share ONE `memory`
|
|
29
|
+
// preset knob — the cycle agents stay separate (cycle1/2/3-agent, distinct
|
|
30
|
+
// slots and invokedBy) but resolve their model from `maint.memory` via the
|
|
31
|
+
// `maintKey` override on their hidden-role entries.
|
|
27
32
|
// scheduler/webhook still let a per-entry config.json model win first (the
|
|
28
33
|
// caller passes it explicitly via opts.preset); the haiku default below only
|
|
29
34
|
// applies when an entry omits its own model.
|
|
30
35
|
export const DEFAULT_MAINTENANCE = Object.freeze({
|
|
31
36
|
explore: 'haiku',
|
|
32
|
-
|
|
33
|
-
cycle2: 'haiku',
|
|
34
|
-
cycle3: 'haiku',
|
|
37
|
+
memory: 'haiku',
|
|
35
38
|
scheduler: 'haiku',
|
|
36
39
|
webhook: 'haiku',
|
|
37
40
|
});
|
|
@@ -41,7 +44,7 @@ export const DEFAULT_MAINTENANCE = Object.freeze({
|
|
|
41
44
|
// SUBSET of DEFAULT_MAINTENANCE: scheduler/webhook carry a per-entry model and
|
|
42
45
|
// are not shown as shared rows, but still inherit the haiku default above when
|
|
43
46
|
// an entry omits its own model.
|
|
44
|
-
export const MAINTENANCE_SLOTS = Object.freeze(['explore', '
|
|
47
|
+
export const MAINTENANCE_SLOTS = Object.freeze(['explore', 'memory']);
|
|
45
48
|
|
|
46
49
|
// Map short Anthropic family labels to the full model ids used by the API.
|
|
47
50
|
// Honors ANTHROPIC_DEFAULT_{OPUS|SONNET|HAIKU}_MODEL env overrides.
|
|
@@ -135,7 +138,11 @@ export function loadConfig() {
|
|
|
135
138
|
// Provider API keys live in the OS keychain (std env / MIXDOG_AGENT_*
|
|
136
139
|
// -> keychain), never plaintext in config. Overlay them so the
|
|
137
140
|
// provider clients see config.apiKey populated.
|
|
138
|
-
|
|
141
|
+
// ENV_KEY_MAP covers first-class key providers; OPENAI_COMPAT_PRESETS
|
|
142
|
+
// covers compat providers (opencode-go, …) whose key also lives in
|
|
143
|
+
// the keychain. Without the union, a compat provider with a valid
|
|
144
|
+
// stored key still ships 'no-key' → 401.
|
|
145
|
+
for (const name of new Set([...Object.keys(ENV_KEY_MAP), ...Object.keys(OPENAI_COMPAT_PRESETS)])) {
|
|
139
146
|
const kc = getAgentApiKey(name);
|
|
140
147
|
if (kc) mergedProviders[name] = { ...(mergedProviders[name] || {}), apiKey: kc, enabled: true };
|
|
141
148
|
}
|
|
@@ -148,6 +155,19 @@ export function loadConfig() {
|
|
|
148
155
|
for (const [k, v] of Object.entries(raw.maintenance || {})) {
|
|
149
156
|
if (allowedMaintKeys.has(k)) rawMaint[k] = v;
|
|
150
157
|
}
|
|
158
|
+
// One-time schema migration: the three memory-cycle MODEL presets
|
|
159
|
+
// (cycle1/cycle2/cycle3) collapsed into a single `memory` key. If the
|
|
160
|
+
// stored config still carries any old cycle key and no `memory`, fold
|
|
161
|
+
// the first present value into `memory` (preserving the user's
|
|
162
|
+
// choice), then the old keys drop via the allow-list above. This is a
|
|
163
|
+
// schema migration, NOT a runtime fallback — the persisted config is
|
|
164
|
+
// cleaned once so runtime never has to re-migrate.
|
|
165
|
+
const legacyCycleKeys = ['cycle1', 'cycle2', 'cycle3'];
|
|
166
|
+
let migratedMaintenance = false;
|
|
167
|
+
if (!('memory' in rawMaint) && legacyCycleKeys.some(k => k in (raw.maintenance || {}))) {
|
|
168
|
+
rawMaint.memory = raw.maintenance.cycle1 ?? raw.maintenance.cycle2 ?? raw.maintenance.cycle3 ?? DEFAULT_MAINTENANCE.memory;
|
|
169
|
+
migratedMaintenance = true;
|
|
170
|
+
}
|
|
151
171
|
// Self-ref guard: mcpServers.mixdog / mcpServers["trib-plugin"]
|
|
152
172
|
// would self-spawn through the in-process tool bridge. Strip on
|
|
153
173
|
// ingress so user-edited configs cannot brick the agent boot.
|
|
@@ -180,6 +200,38 @@ export function loadConfig() {
|
|
|
180
200
|
process.stderr.write(`[config] persist sanitized config failed: ${err?.message}\n`);
|
|
181
201
|
}
|
|
182
202
|
}
|
|
203
|
+
// Persist the memory-cycle schema migration once. rawMaint already
|
|
204
|
+
// carries the folded `memory` key and excludes the dropped cycle1/2/3
|
|
205
|
+
// keys (not in the allow-list); rebase onto the in-lock current so a
|
|
206
|
+
// concurrent writer's unrelated edits survive, mirroring the
|
|
207
|
+
// mcpServers self-ref strip above.
|
|
208
|
+
if (migratedMaintenance) {
|
|
209
|
+
try {
|
|
210
|
+
persistAgentConfig((current) => {
|
|
211
|
+
const cur = { ...current };
|
|
212
|
+
const target = (cur.agent && cur.agent.providers)
|
|
213
|
+
? (cur.agent = { ...cur.agent })
|
|
214
|
+
: cur;
|
|
215
|
+
const curMaint = (target.maintenance && typeof target.maintenance === 'object') ? { ...target.maintenance } : {};
|
|
216
|
+
// Derive `memory` from the IN-LOCK current, not the
|
|
217
|
+
// pre-lock rawMaint snapshot — a concurrent writer may
|
|
218
|
+
// have set maintenance.memory or changed a legacy cycle
|
|
219
|
+
// value between this loadConfig()'s read and the lock.
|
|
220
|
+
// If `memory` is already present in-lock, preserve it
|
|
221
|
+
// (lost-update guard); otherwise fold the in-lock legacy
|
|
222
|
+
// cycle value first, with the pre-lock snapshot as the
|
|
223
|
+
// last-resort seed.
|
|
224
|
+
if (!('memory' in curMaint)) {
|
|
225
|
+
curMaint.memory = curMaint.cycle1 ?? curMaint.cycle2 ?? curMaint.cycle3 ?? rawMaint.memory;
|
|
226
|
+
}
|
|
227
|
+
for (const k of legacyCycleKeys) delete curMaint[k];
|
|
228
|
+
target.maintenance = curMaint;
|
|
229
|
+
return cur;
|
|
230
|
+
});
|
|
231
|
+
} catch (err) {
|
|
232
|
+
process.stderr.write(`[config] persist maintenance migration failed: ${err?.message}\n`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
183
235
|
const rawPresets = Array.isArray(raw.presets) ? raw.presets : [];
|
|
184
236
|
const normalizedPresets = rawPresets.map(p => normalizePreset(p)).filter(Boolean);
|
|
185
237
|
return {
|
|
@@ -32,7 +32,7 @@ const MODELSDEV_URL = 'https://models.dev/api.json';
|
|
|
32
32
|
const MODELSDEV_CACHE_FILE = 'modelsdev-catalog.json';
|
|
33
33
|
|
|
34
34
|
// mixdog provider id → models.dev provider id. Identity for ids that already
|
|
35
|
-
// match (opencode-go / deepseek / xai /
|
|
35
|
+
// match (opencode-go / deepseek / xai / openai / anthropic / groq /
|
|
36
36
|
// mistral); only the OAuth aliases and gemini→google need remapping.
|
|
37
37
|
const _MODELSDEV_PROVIDER_ALIAS = {
|
|
38
38
|
'anthropic-oauth': 'anthropic',
|
|
@@ -133,7 +133,14 @@ function _codexServiceTiers(modelInfo) {
|
|
|
133
133
|
return Array.isArray(modelInfo?.serviceTiers) ? modelInfo.serviceTiers : [];
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
function
|
|
136
|
+
function _codexModelBlocksServiceTier(id, serviceTier) {
|
|
137
|
+
if (serviceTier !== 'priority') return false;
|
|
138
|
+
const family = _codexFamily(id);
|
|
139
|
+
return family === 'gpt-mini' || family === 'gpt-nano' || family === 'gpt-codex';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function codexModelSupportsServiceTier(id, serviceTier) {
|
|
143
|
+
if (_codexModelBlocksServiceTier(id, serviceTier)) return false;
|
|
137
144
|
const info = _findCachedCodexModel(id);
|
|
138
145
|
if (!info) return true;
|
|
139
146
|
const tiers = _codexServiceTiers(info);
|
|
@@ -530,7 +537,7 @@ export function buildRequestBody(messages, model, tools, sendOpts) {
|
|
|
530
537
|
// accepts on the wire: 'fast' is hard-rejected ("Unsupported
|
|
531
538
|
// service_tier: fast", probed 2026-06-11). Match official Codex:
|
|
532
539
|
// only send the request value when the model catalog advertises it.
|
|
533
|
-
if (
|
|
540
|
+
if (codexModelSupportsServiceTier(model, 'priority')) {
|
|
534
541
|
body.service_tier = 'priority';
|
|
535
542
|
}
|
|
536
543
|
}
|
|
@@ -16,6 +16,24 @@ import { sendViaWebSocket } from './openai-oauth-ws.mjs';
|
|
|
16
16
|
import { buildRequestBody } from './openai-oauth.mjs';
|
|
17
17
|
import { resolveProviderCacheKey } from '../smart-bridge/cache-strategy.mjs';
|
|
18
18
|
|
|
19
|
+
const OPENAI_DIRECT_PRIORITY_MODEL_PATTERNS = Object.freeze([
|
|
20
|
+
/^gpt-5\.5(?:-\d{4}|$)/,
|
|
21
|
+
/^gpt-5\.4(?:-\d{4}|$)/,
|
|
22
|
+
/^gpt-5\.4-mini(?:-\d{4}|$)/,
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
export function openAiDirectSupportsPriority(model) {
|
|
26
|
+
const id = String(model || '').trim();
|
|
27
|
+
return OPENAI_DIRECT_PRIORITY_MODEL_PATTERNS.some(re => re.test(id));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function applyOpenAIDirectFastTier(body, model, opts) {
|
|
31
|
+
if (opts?.fast === true && openAiDirectSupportsPriority(model)) {
|
|
32
|
+
body.service_tier = 'priority';
|
|
33
|
+
}
|
|
34
|
+
return body;
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
export class OpenAIDirectProvider {
|
|
20
38
|
// input_tokens INCLUDES cached tokens (OpenAI convention). See registry.mjs.
|
|
21
39
|
static inputExcludesCache = false;
|
|
@@ -38,6 +56,11 @@ export class OpenAIDirectProvider {
|
|
|
38
56
|
const apiKey = this._ensureKey();
|
|
39
57
|
const useModel = model || 'gpt-5.5';
|
|
40
58
|
const body = buildRequestBody(messages, useModel, tools, sendOpts);
|
|
59
|
+
// Public OpenAI API priority support is documented separately from the
|
|
60
|
+
// Codex OAuth catalog. Keep this provider's service-tier decision local
|
|
61
|
+
// so gpt-5.4-mini can opt into Priority even when the Codex catalog does
|
|
62
|
+
// not advertise a Fast tier for its OAuth endpoint.
|
|
63
|
+
applyOpenAIDirectFastTier(body, useModel, opts);
|
|
41
64
|
// Public Responses API supports prompt_cache_retention='24h' at no
|
|
42
65
|
// extra cost (same cached_input_tokens billing as the default 5–10
|
|
43
66
|
// min in-memory cache). Codex/oauth rejects the parameter, so it's
|
|
@@ -628,7 +628,7 @@ async function executeTool(name, args, cwd, callerSessionId, sessionRef, execute
|
|
|
628
628
|
if (isBuiltinTool(name)) {
|
|
629
629
|
// clientHostPid threaded for the same per-terminal job-scope reason as
|
|
630
630
|
// the bash branch above (see resolveJobOwnerHostPid).
|
|
631
|
-
return executeBuiltinTool(name, args, cwd, { sessionId: callerSessionId, clientHostPid: sessionRef?.clientHostPid, ...toolOpts });
|
|
631
|
+
return executeBuiltinTool(name, args, cwd, { sessionId: callerSessionId, clientHostPid: sessionRef?.clientHostPid, signal: executeOpts.signal, ...toolOpts });
|
|
632
632
|
}
|
|
633
633
|
return formatUnknownBuiltinToolMessage(name, args, 'tool');
|
|
634
634
|
}
|
|
@@ -807,7 +807,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
807
807
|
try {
|
|
808
808
|
const permBlocked = _checkWorkerPermission(call.name, call.arguments, sessionRef);
|
|
809
809
|
if (permBlocked !== null) return { ok: true, value: permBlocked };
|
|
810
|
-
return { ok: true, value: await executeTool(call.name, call.arguments, cwd, sessionId, sessionRef, { toolCallId: call.id }) };
|
|
810
|
+
return { ok: true, value: await executeTool(call.name, call.arguments, cwd, sessionId, sessionRef, { toolCallId: call.id, signal }) };
|
|
811
811
|
} catch (error) {
|
|
812
812
|
return { ok: false, error };
|
|
813
813
|
}
|
|
@@ -1185,7 +1185,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
1185
1185
|
toolEndedAt = Date.now();
|
|
1186
1186
|
_resultKind = 'error';
|
|
1187
1187
|
} else {
|
|
1188
|
-
result = await executeTool(call.name, call.arguments, cwd, sessionId, sessionRef, { toolCallId: call.id });
|
|
1188
|
+
result = await executeTool(call.name, call.arguments, cwd, sessionId, sessionRef, { toolCallId: call.id, signal });
|
|
1189
1189
|
toolEndedAt = Date.now();
|
|
1190
1190
|
// Boundary: tool-return string convention → structural kind.
|
|
1191
1191
|
// The only prefix check in this codebase; downstream layers
|
|
@@ -117,13 +117,17 @@ export function resolvePresetName({ preset, optsPreset, role, config: cfgIn = nu
|
|
|
117
117
|
// Hidden roles resolve their maintenance preset by SLOT. Every slot carries
|
|
118
118
|
// a concrete default in DEFAULT_MAINTENANCE, so `maint[slot]` resolves
|
|
119
119
|
// directly; the Setup panel can still tune each slot independently.
|
|
120
|
-
// (explorer.slot = 'explore', cycle1-agent.slot = 'cycle1', …)
|
|
120
|
+
// (explorer.slot = 'explore', cycle1-agent.slot = 'cycle1', …). A hidden
|
|
121
|
+
// role may override which maintenance key it reads via `maintKey`
|
|
122
|
+
// (e.g. the cycle1/2/3 agents all read `maint.memory` instead of their
|
|
123
|
+
// own slot) so several agents can share one model knob while keeping
|
|
124
|
+
// distinct slots/identity.
|
|
121
125
|
const hidden = getHiddenRole(role);
|
|
122
126
|
if (hidden) {
|
|
123
127
|
try {
|
|
124
128
|
const config = cfgIn || loadConfig();
|
|
125
129
|
const maint = config?.maintenance || {};
|
|
126
|
-
return maint[hidden.slot] || null;
|
|
130
|
+
return maint[hidden.maintKey || hidden.slot] || null;
|
|
127
131
|
} catch { return null; }
|
|
128
132
|
}
|
|
129
133
|
try {
|
|
@@ -47,6 +47,7 @@ import { stripQuotedAndHeredoc, extractShellCInner } from './destructive-warning
|
|
|
47
47
|
import { _maybeEncodePowerShellCommand } from './shell-command.mjs';
|
|
48
48
|
import { _captureTrackedMtimes, _trackedDriftNoteAfter, _injectionBlockTargets, getDedupedDestructiveWarnings } from './builtin/bash-tool.mjs';
|
|
49
49
|
import { scrubLoaderVars, scrubProviderSecrets } from './env-scrub.mjs';
|
|
50
|
+
import { checkExecPolicyMessage } from './bash-policy-scan.mjs';
|
|
50
51
|
|
|
51
52
|
// Default 600 s (10 min), max 1800 s. Aligned with the one-shot bash tool's
|
|
52
53
|
// 600 s default (builtin/bash-tool.mjs); the persistent shell carries
|
|
@@ -125,7 +125,7 @@ export const BUILTIN_TOOLS = [
|
|
|
125
125
|
name: 'bash',
|
|
126
126
|
title: 'Mixdog Shell',
|
|
127
127
|
annotations: { title: 'Mixdog Shell', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
128
|
-
description: "Shell for git/build/test/run.
|
|
128
|
+
description: "Shell for git/build/test/run. ALWAYS set `shell` explicitly ('bash' = POSIX via Git Bash, 'powershell' = PS cmdlets); omitting defaults to the OS shell (Windows = PowerShell, POSIX = /bin/sh) and mis-parses the other syntax. run_in_background works for both shells, including Windows shell:'bash' (Git Bash). Single shell entry point; not for inline code you were asked to return.",
|
|
129
129
|
inputSchema: {
|
|
130
130
|
type: 'object',
|
|
131
131
|
properties: {
|