mixdog 0.7.11 → 0.7.12
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/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/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 +1 -1
- package/scripts/builtin-utils-smoke.mjs +14 -8
- package/scripts/bump.mjs +80 -0
- package/scripts/doctor.mjs +8 -3
- package/scripts/mutation-io-smoke.mjs +17 -1
- package/scripts/permission-eval-smoke.mjs +18 -1
- 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/setup-server.mjs +10 -2
- package/setup/setup.html +43 -8
- package/src/agent/orchestrator/providers/openai-oauth.mjs +9 -2
- package/src/agent/orchestrator/providers/openai-ws.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +29 -8
- package/src/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/channels/index.mjs +27 -8
- 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 +142 -20
- package/src/memory/lib/memory-cycle1.mjs +7 -3
- 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/scripts/test-config-rmw-restore.mjs +0 -122
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
|
}
|
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';
|
|
@@ -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
|
|
@@ -19,21 +19,35 @@ export function nativeEditMode() {
|
|
|
19
19
|
return String(process.env.MIXDOG_EDIT_NATIVE || 'auto').toLowerCase();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
function nativeEditBinCandidate() {
|
|
23
23
|
const override = process.env.MIXDOG_EDIT_NATIVE_BIN || process.env.MIXDOG_PATCH_NATIVE_BIN;
|
|
24
|
-
if (override) return override;
|
|
25
|
-
if (existsSync(NATIVE_EDIT_DEFAULT_BIN)) return NATIVE_EDIT_DEFAULT_BIN;
|
|
26
|
-
|
|
24
|
+
if (override) return { path: override, kind: 'override' };
|
|
25
|
+
if (existsSync(NATIVE_EDIT_DEFAULT_BIN)) return { path: NATIVE_EDIT_DEFAULT_BIN, kind: 'local' };
|
|
26
|
+
const cached = findCachedPatchBinary(getPluginData());
|
|
27
|
+
if (cached) return { path: cached, kind: 'cached' };
|
|
28
|
+
return { path: NATIVE_EDIT_DEFAULT_BIN, kind: 'missing' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function nativeEditBinPath() {
|
|
32
|
+
return nativeEditBinCandidate().path;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
export function nativeEditShouldAttempt({ editSnapshot, oldStr, newStr, preloadedContent, preloadedRawBuf }) {
|
|
30
36
|
const mode = nativeEditMode();
|
|
31
37
|
if (/^(0|false|no|off|js|legacy)$/i.test(mode)) return false;
|
|
32
|
-
|
|
38
|
+
const forcedNative = /^(1|true|yes|on|native)$/i.test(mode);
|
|
39
|
+
const candidate = nativeEditBinCandidate();
|
|
40
|
+
if (!existsSync(candidate.path)) return false;
|
|
41
|
+
// Cached release prebuilds are guaranteed valid for apply_patch, but older
|
|
42
|
+
// manifests (currently v0.6.5 in clean CI) predate the EDIT server protocol.
|
|
43
|
+
// In auto mode, native edit is only an acceleration, so require either a
|
|
44
|
+
// local cargo build or an explicit override. If a user forces native mode,
|
|
45
|
+
// still try the cached binary and surface any protocol failure.
|
|
46
|
+
if (candidate.kind === 'cached' && !forcedNative) return false;
|
|
33
47
|
if (!snapshotCoversFullFile(editSnapshot)) return false;
|
|
34
48
|
if (preloadedContent !== null || preloadedRawBuf !== null) return false;
|
|
35
49
|
if (typeof oldStr !== 'string' || oldStr.length === 0 || typeof newStr !== 'string') return false;
|
|
36
|
-
if (
|
|
50
|
+
if (forcedNative) return true;
|
|
37
51
|
// auto: the persistent server removed per-call spawn cost, so route edits to
|
|
38
52
|
// native edit2 by default (B3). Same-size edits keep the JS in-place partial
|
|
39
53
|
// write, which rewrites bytes in place instead of the whole file.
|
|
@@ -44,6 +58,7 @@ export function nativeEditShouldAttempt({ editSnapshot, oldStr, newStr, preloade
|
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
export async function runNativeExactEdit({ fullPath, oldStr, newStr, replaceAll, signal = null }) {
|
|
61
|
+
const forcedNative = /^(1|true|yes|on|native)$/i.test(nativeEditMode());
|
|
47
62
|
if (signal?.aborted) {
|
|
48
63
|
return { ok: false, fallback: false, error: signal.reason?.message || signal.reason || 'native edit aborted' };
|
|
49
64
|
}
|
|
@@ -82,8 +97,14 @@ export async function runNativeExactEdit({ fullPath, oldStr, newStr, replaceAll,
|
|
|
82
97
|
}
|
|
83
98
|
const msg = String(err?.message || err);
|
|
84
99
|
// Tier misses and not-found map to a JS fallback; transport/spawn errors
|
|
85
|
-
// also fall back so a server hiccup never blocks an edit.
|
|
86
|
-
|
|
100
|
+
// also fall back so a server hiccup never blocks an edit. Older cached
|
|
101
|
+
// mixdog-patch binaries (for example the v0.6.5 release prebuilds used
|
|
102
|
+
// by clean CI before a local cargo build exists) support APPLY but not
|
|
103
|
+
// the EDIT server protocol, and answer EDIT with the APPLY parser's
|
|
104
|
+
// "bad header" error. In auto mode that means "native edit unavailable",
|
|
105
|
+
// not "the edit is invalid", so fall through to the JS editor. When the
|
|
106
|
+
// user explicitly forces native mode, keep surfacing the native failure.
|
|
107
|
+
const fallback = !forcedNative && /old_string (?:not found|found \d+ times)|not valid UTF-8|no exact match|not found|server|bad header|bad edit header/i.test(msg);
|
|
87
108
|
return { ok: false, fallback, error: msg };
|
|
88
109
|
}
|
|
89
110
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.7.12",
|
|
3
3
|
"_comment": "Rewritten by .github/workflows/graph-release.yml on each tagged release. assets maps platformKey (process.platform-process.arch, e.g. win32-x64, linux-x64, darwin-arm64) to { url, sha256 } of the mixdog-graph binary on the GitHub release. A local cargo build under native/mixdog-graph/target/release always takes precedence at runtime. (v0.5.236 entries were filled manually after CI's commit step hit detached HEAD; the workflow now checks out ref: main so future releases self-update.)",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-graph-darwin-arm64",
|
|
7
|
+
"sha256": "75bfdd200b2f8553b72dc877ec2637208f581800083d1ee5f9caf33f87792bf7"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-graph-darwin-x64",
|
|
11
|
+
"sha256": "04742fbb4cbe09bb76943f312ee129c05814543e7bc9d37e1241fb4e65b97137"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-graph-linux-arm64",
|
|
15
|
+
"sha256": "4b3edcd7be1ffec7184c48fe6bc7d6bce42f2ea67d4709f44d4402e6b48564f2"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-graph-linux-x64",
|
|
19
|
+
"sha256": "4394bb7884a8706dd6a4eea55f8755c76ba584cd02248863802d94acc3e1413c"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-graph-win32-x64.exe",
|
|
23
|
+
"sha256": "cbfe189d690085aee1dfd70f5c0b9c26c260d0a080914cbeb504c84510ec3a5a"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.7.12",
|
|
3
3
|
"_comment": "Rewritten by .github/workflows/patch-release.yml on each tagged release. assets maps platformKey (process.platform-process.arch, e.g. win32-x64, linux-x64, darwin-arm64) to { url, sha256 } of the mixdog-patch binary on the GitHub release. A local cargo build under native/mixdog-patch/target/release always takes precedence; otherwise the binary is fetched per this manifest into the data dir (apply is native-only — no JS apply engine).",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-patch-darwin-arm64",
|
|
7
|
+
"sha256": "836a0b60a443b0a6a8c1bbe24d15a79ed70ee92c2f0fbc05374c4e9ed2536415"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-patch-darwin-x64",
|
|
11
|
+
"sha256": "cab10c4e1e8b72d3958241dffdff764712ed74f4861d105bafa8258961215c98"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-patch-linux-arm64",
|
|
15
|
+
"sha256": "a90c32ce3417a7d853f2723f82f3613cf2cd030fe885cf710cfc9f8e4b193264"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-patch-linux-x64",
|
|
19
|
+
"sha256": "0fea40ab98acd35bfb47515756024d1882a2abbaddce8a0b51642d20ac405577"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.12/mixdog-patch-win32-x64.exe",
|
|
23
|
+
"sha256": "f8a74fb9bb7bf333fa441b800b76b28d83a2a7b4795e4a769122faf47a59d1f1"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/channels/index.mjs
CHANGED
|
@@ -183,10 +183,18 @@ const _bootLogEarly = path.join(
|
|
|
183
183
|
process.env.CLAUDE_PLUGIN_DATA || path.join(os.tmpdir(), "mixdog"),
|
|
184
184
|
"boot.log"
|
|
185
185
|
);
|
|
186
|
+
const {
|
|
187
|
+
isMixdogDebugEnabled: isMixdogDebug,
|
|
188
|
+
pruneStalePluginDataLogSiblings,
|
|
189
|
+
appendSessionStartCriticalLog,
|
|
190
|
+
DEFAULT_STALE_LOG_SIBLING_MAX,
|
|
191
|
+
} = _require("../../lib/mixdog-debug.cjs");
|
|
186
192
|
// One-shot log rotation at worker boot (10 MB threshold, .1 suffix overwrite).
|
|
187
|
-
|
|
188
|
-
fs.
|
|
193
|
+
if (isMixdogDebug()) {
|
|
194
|
+
try { if (fs.statSync(_bootLogEarly).size > 10 * 1024 * 1024) fs.renameSync(_bootLogEarly, _bootLogEarly + '.1') } catch {}
|
|
195
|
+
fs.appendFileSync(_bootLogEarly, `[${localTimestamp()}] bootstrap start pid=${process.pid}
|
|
189
196
|
`);
|
|
197
|
+
}
|
|
190
198
|
const _bootLog = path.join(DATA_DIR, "boot.log");
|
|
191
199
|
let config = loadConfig();
|
|
192
200
|
let backend = createBackend(config);
|
|
@@ -238,6 +246,11 @@ try {
|
|
|
238
246
|
try { if (_now - fs.statSync(_p).mtimeMs > _STALE_SESSION_TTL_MS) fs.unlinkSync(_p); } catch {}
|
|
239
247
|
}
|
|
240
248
|
} catch {}
|
|
249
|
+
// Count-based cap: drop oldest *.log siblings when plugin-data accumulates
|
|
250
|
+
// hundreds of per-process files (doctor warns above 300).
|
|
251
|
+
try {
|
|
252
|
+
pruneStalePluginDataLogSiblings(DATA_DIR, DEFAULT_STALE_LOG_SIBLING_MAX);
|
|
253
|
+
} catch {}
|
|
241
254
|
|
|
242
255
|
// ── Buffered drop-trace writer (channels/index) ──────────────────────────────
|
|
243
256
|
// Flushes every 1 s OR when buffer reaches 64 KB — whichever fires first.
|
|
@@ -2030,8 +2043,12 @@ backend.onInteraction = (interaction) => {
|
|
|
2030
2043
|
const [, uuid, action] = match;
|
|
2031
2044
|
const access = config.access;
|
|
2032
2045
|
if (!access) {
|
|
2033
|
-
|
|
2034
|
-
|
|
2046
|
+
const _permDropLine = `[${localTimestamp()}] perm interaction dropped: no access config\n`;
|
|
2047
|
+
if (isMixdogDebug()) {
|
|
2048
|
+
fs.appendFileSync(_bootLog, _permDropLine);
|
|
2049
|
+
} else {
|
|
2050
|
+
appendSessionStartCriticalLog(DATA_DIR, `[channels] ${_permDropLine}`);
|
|
2051
|
+
}
|
|
2035
2052
|
return;
|
|
2036
2053
|
}
|
|
2037
2054
|
if (access.allowFrom?.length > 0 && !access.allowFrom.includes(interaction.userId)) {
|
|
@@ -3016,12 +3033,14 @@ async function stop() {
|
|
|
3016
3033
|
return false;
|
|
3017
3034
|
};
|
|
3018
3035
|
_channelFlagDetected = detectChannelFlag();
|
|
3019
|
-
|
|
3020
|
-
`);
|
|
3036
|
+
if (isMixdogDebug()) {
|
|
3037
|
+
fs.appendFileSync(_bootLog, `[${localTimestamp()}] channelFlag: ${_channelFlagDetected}\n`);
|
|
3038
|
+
if (_channelFlagDetected) {
|
|
3039
|
+
fs.appendFileSync(_bootLog, `[${localTimestamp()}] channel mode detected — bridge auto-activated\n`);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3021
3042
|
if (_channelFlagDetected) {
|
|
3022
3043
|
channelBridgeActive = true;
|
|
3023
|
-
fs.appendFileSync(_bootLog, `[${localTimestamp()}] channel mode detected \u2014 bridge auto-activated
|
|
3024
|
-
`);
|
|
3025
3044
|
}
|
|
3026
3045
|
writeBridgeState(channelBridgeActive);
|
|
3027
3046
|
const previousOwner = readActiveInstance();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, existsSync as fsExistsSync } from "fs";
|
|
1
|
+
import { readdirSync, readFileSync, existsSync as fsExistsSync, statSync, unlinkSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { DATA_DIR } from "./config.mjs";
|
|
4
4
|
import { ensureDir } from "./state-file.mjs";
|
|
@@ -7,6 +7,27 @@ import { renameWithRetrySync, writeJsonAtomicSync } from "../../shared/atomic-fi
|
|
|
7
7
|
const QUEUE_DIR = join(DATA_DIR, "events", "queue");
|
|
8
8
|
const IN_PROGRESS_DIR = join(DATA_DIR, "events", "in-progress");
|
|
9
9
|
const PROCESSED_DIR = join(DATA_DIR, "events", "processed");
|
|
10
|
+
const PROCESSED_DIR_MAX_ENTRIES = 200;
|
|
11
|
+
function pruneProcessedDir() {
|
|
12
|
+
try {
|
|
13
|
+
if (!fsExistsSync(PROCESSED_DIR)) return;
|
|
14
|
+
const names = readdirSync(PROCESSED_DIR);
|
|
15
|
+
if (names.length <= PROCESSED_DIR_MAX_ENTRIES) return;
|
|
16
|
+
const ranked = [];
|
|
17
|
+
for (const name of names) {
|
|
18
|
+
try {
|
|
19
|
+
const st = statSync(join(PROCESSED_DIR, name));
|
|
20
|
+
ranked.push({ name, mtime: st.mtimeMs });
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
ranked.sort((a, b) => b.mtime - a.mtime);
|
|
24
|
+
for (let i = PROCESSED_DIR_MAX_ENTRIES; i < ranked.length; i++) {
|
|
25
|
+
try {
|
|
26
|
+
unlinkSync(join(PROCESSED_DIR, ranked[i].name));
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
10
31
|
function finiteInt(value, { min, max, def }) {
|
|
11
32
|
const n = Number(value);
|
|
12
33
|
if (!Number.isFinite(n)) return def;
|
|
@@ -290,6 +311,7 @@ ${p.item.prompt}`).join("\n\n")}`;
|
|
|
290
311
|
const fromQueue = join(QUEUE_DIR, file);
|
|
291
312
|
const src = this.existsSync(fromInProgress) ? fromInProgress : fromQueue;
|
|
292
313
|
renameWithRetrySync(src, join(PROCESSED_DIR, `${status}-${file}`));
|
|
314
|
+
pruneProcessedDir();
|
|
293
315
|
} catch {
|
|
294
316
|
}
|
|
295
317
|
}
|
|
@@ -297,6 +319,7 @@ ${p.item.prompt}`).join("\n\n")}`;
|
|
|
297
319
|
try {
|
|
298
320
|
ensureDir(PROCESSED_DIR);
|
|
299
321
|
renameWithRetrySync(join(IN_PROGRESS_DIR, file), join(PROCESSED_DIR, `${status}-${file}`));
|
|
322
|
+
pruneProcessedDir();
|
|
300
323
|
} catch {
|
|
301
324
|
}
|
|
302
325
|
}
|
|
@@ -24,6 +24,11 @@ import { request as httpsRequest } from 'node:https'
|
|
|
24
24
|
import { createRequire } from 'node:module'
|
|
25
25
|
|
|
26
26
|
const moduleRequire = createRequire(import.meta.url)
|
|
27
|
+
const {
|
|
28
|
+
isMixdogDebugEnabled,
|
|
29
|
+
pruneStalePluginDataLogSiblings,
|
|
30
|
+
DEFAULT_STALE_LOG_SIBLING_MAX,
|
|
31
|
+
} = moduleRequire('../../../lib/mixdog-debug.cjs')
|
|
27
32
|
|
|
28
33
|
// IPC transport path. Windows uses a named pipe (`\\.\pipe\…`); Unix uses a
|
|
29
34
|
// Unix domain socket under XDG_RUNTIME_DIR (or /tmp as fallback). Node's
|
|
@@ -57,9 +62,13 @@ const POLL_INTERVAL_MS = 2000
|
|
|
57
62
|
const SUBAGENT_TIMEOUT_MS = 120_000
|
|
58
63
|
const DEFAULT_DISPATCH_TIMEOUT_MS = 15_000
|
|
59
64
|
const SESSION_START_MEMORY_DISPATCH_TIMEOUT_MS = 125_000
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
const MIXDOG_DEBUG_ENABLED = isMixdogDebugEnabled()
|
|
66
|
+
let _hookPipeLogsPruned = false
|
|
67
|
+
|
|
68
|
+
function hookPipeDebugStderr(line) {
|
|
69
|
+
if (!MIXDOG_DEBUG_ENABLED) return
|
|
70
|
+
try { process.stderr.write(line) } catch {}
|
|
71
|
+
}
|
|
63
72
|
|
|
64
73
|
let _started = false
|
|
65
74
|
let _server = null
|
|
@@ -82,13 +91,17 @@ function formatError(err) {
|
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
function traceSessionStart(message) {
|
|
85
|
-
if (!
|
|
94
|
+
if (!MIXDOG_DEBUG_ENABLED) return
|
|
86
95
|
const line = `[${new Date().toISOString()}] [hook-pipe][session-start] ${message}\n`
|
|
87
96
|
try { process.stderr.write(line) } catch {}
|
|
88
97
|
try {
|
|
89
98
|
const dataDir = process.env.CLAUDE_PLUGIN_DATA ||
|
|
90
99
|
join(homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin')
|
|
91
100
|
mkdirSync(dataDir, { recursive: true })
|
|
101
|
+
if (!_hookPipeLogsPruned) {
|
|
102
|
+
_hookPipeLogsPruned = true
|
|
103
|
+
pruneStalePluginDataLogSiblings(dataDir, DEFAULT_STALE_LOG_SIBLING_MAX)
|
|
104
|
+
}
|
|
92
105
|
appendFileSync(join(dataDir, 'session-start.log'), line)
|
|
93
106
|
} catch {}
|
|
94
107
|
}
|
|
@@ -320,10 +333,10 @@ async function handlePreToolSubagent(payload) {
|
|
|
320
333
|
}
|
|
321
334
|
const route = routeMod.shouldRoutePermissionToDiscord()
|
|
322
335
|
if (route.route !== 'discord') {
|
|
323
|
-
|
|
336
|
+
hookPipeDebugStderr(`[hook-pipe] pre-tool-subagent discord-route=off agent=${agentIdRaw || 'unknown'} tool=${toolName} reason=${route.reason || 'inactive'}\n`)
|
|
324
337
|
return null
|
|
325
338
|
}
|
|
326
|
-
|
|
339
|
+
hookPipeDebugStderr(`[hook-pipe] pre-tool-subagent discord-route=on agent=${agentIdRaw || 'unknown'} tool=${toolName}\n`)
|
|
327
340
|
|
|
328
341
|
let getDiscordToken
|
|
329
342
|
try {
|
|
@@ -715,7 +728,7 @@ export function startHookPipeServer() {
|
|
|
715
728
|
_server.on('error', (err) => {
|
|
716
729
|
const msg = String(err?.message || err || '')
|
|
717
730
|
if (err?.code === 'EADDRINUSE' || msg.includes('EADDRINUSE') || msg.includes('Failed to listen')) {
|
|
718
|
-
|
|
731
|
+
hookPipeDebugStderr(`[hook-pipe] ${PIPE_PATH} already owned by a peer daemon; standby for hook IPC\n`)
|
|
719
732
|
_server = null
|
|
720
733
|
_started = false
|
|
721
734
|
return
|
|
@@ -727,7 +740,7 @@ export function startHookPipeServer() {
|
|
|
727
740
|
try {
|
|
728
741
|
_server.listen(PIPE_PATH, () => {
|
|
729
742
|
_started = true
|
|
730
|
-
|
|
743
|
+
hookPipeDebugStderr(`[hook-pipe] listening on ${PIPE_PATH}\n`)
|
|
731
744
|
})
|
|
732
745
|
} catch (err) {
|
|
733
746
|
process.stderr.write(`[hook-pipe] listen failed: ${err?.message || err}\n`)
|