mixdog 0.7.17 → 0.7.18
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/UNINSTALL.md +7 -4
- package/bin/statusline-lib.mjs +29 -6
- package/bin/statusline-route.mjs +273 -0
- package/bin/statusline.mjs +31 -8
- package/commands/model.md +61 -0
- package/hooks/session-start.cjs +15 -1
- 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/rules/lead/01-general.md +3 -0
- package/scripts/gateway-model.mjs +596 -0
- package/scripts/lib/gateway-inventory.mjs +178 -0
- package/scripts/lib/gateway-settings.mjs +78 -0
- package/scripts/run-mcp.mjs +26 -1
- package/scripts/statusline-launcher-smoke.mjs +155 -2
- package/scripts/uninstall.mjs +44 -7
- package/server-main.mjs +252 -11
- package/src/agent/index.mjs +96 -5
- package/src/agent/orchestrator/bridge-trace.mjs +18 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +4 -1
- package/src/agent/orchestrator/providers/anthropic.mjs +6 -2
- package/src/agent/orchestrator/session/loop.mjs +145 -4
- package/src/agent/orchestrator/session/trim.mjs +1 -1
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +62 -6
- package/src/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/agent/orchestrator/tools/patch-manifest.json +7 -7
- package/src/agent/tool-defs.mjs +10 -3
- package/src/channels/lib/runtime-paths.mjs +39 -4
- package/src/gateway/claude-current.mjs +255 -0
- package/src/gateway/oauth-usage.mjs +598 -0
- package/src/gateway/route-meta.mjs +629 -0
- package/src/gateway/server.mjs +713 -0
- package/src/shared/llm/cost.mjs +1 -1
- package/src/shared/seed.mjs +25 -0
- package/tools.json +21 -3
|
@@ -8,7 +8,7 @@ import { executeInternalTool, isInternalTool } from '../internal-tools.mjs';
|
|
|
8
8
|
import { collectSkillsCached, loadSkillContent } from '../context/collect.mjs';
|
|
9
9
|
import { traceBridgeLoop, traceBridgeTool, traceBridgeTrim, estimateProviderPayloadBytes, messagePrefixHash } from '../bridge-trace.mjs';
|
|
10
10
|
import { markSessionToolCall, updateSessionStage, SessionClosedError, getSessionAbortSignal } from './manager.mjs';
|
|
11
|
-
import { trimMessages, estimateRequestReserveTokens } from './trim.mjs';
|
|
11
|
+
import { trimMessages, estimateMessagesTokens, estimateRequestReserveTokens } from './trim.mjs';
|
|
12
12
|
import { isContextOverflowError } from '../providers/retry-classifier.mjs';
|
|
13
13
|
import { classifyBashFileLookupCommand, stripSoftWarns } from '../tool-loop-guard.mjs';
|
|
14
14
|
import { maybeOffloadToolResult } from './tool-result-offload.mjs';
|
|
@@ -145,7 +145,7 @@ const _require = createRequire(import.meta.url);
|
|
|
145
145
|
const _hooksLib = resolvePath(dirname(fileURLToPath(import.meta.url)), '../../../../hooks/lib/permission-evaluator.cjs');
|
|
146
146
|
const { evaluatePermission: _evaluatePermission } = _require(_hooksLib);
|
|
147
147
|
const MCP_TOOL_PREFIX = 'mcp__plugin_mixdog_mixdog__';
|
|
148
|
-
const SAFETY_TRIM_PERCENT =
|
|
148
|
+
const SAFETY_TRIM_PERCENT = 1.00;
|
|
149
149
|
// Stricter budget used for the one-shot retry after a provider rejects a send
|
|
150
150
|
// with a context-window-exceeded error. 0.60×contextWindow forces older
|
|
151
151
|
// non-system / non-latest turns to drop hard so the retry fits even when the
|
|
@@ -154,6 +154,47 @@ const SAFETY_TRIM_PERCENT = 0.90;
|
|
|
154
154
|
// retry block around provider.send below.
|
|
155
155
|
const OVERFLOW_RETRY_TRIM_PERCENT = 0.60;
|
|
156
156
|
|
|
157
|
+
function estimateMessagesTokensSafe(messages) {
|
|
158
|
+
try { return estimateMessagesTokens(messages); }
|
|
159
|
+
catch { return null; }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class BridgeContextOverflowError extends Error {
|
|
163
|
+
constructor({ stage, sessionId, provider, model, contextWindow, budgetTokens, reserveTokens, messageTokensEst }, cause) {
|
|
164
|
+
const target = [provider, model].filter(Boolean).join('/') || 'target model';
|
|
165
|
+
const causeMsg = cause && cause.message ? `: ${cause.message}` : '';
|
|
166
|
+
super(
|
|
167
|
+
`bridge context overflow (${target}, stage=${stage || 'trim'}): ` +
|
|
168
|
+
`latest turn cannot fit target context budget=${budgetTokens ?? 'unknown'} ` +
|
|
169
|
+
`reserve=${reserveTokens ?? 'unknown'} contextWindow=${contextWindow ?? 'unknown'} ` +
|
|
170
|
+
`messageTokensEst=${messageTokensEst ?? 'unknown'}${causeMsg}`,
|
|
171
|
+
);
|
|
172
|
+
this.name = 'BridgeContextOverflowError';
|
|
173
|
+
this.code = 'BRIDGE_CONTEXT_OVERFLOW';
|
|
174
|
+
this.sessionId = sessionId || null;
|
|
175
|
+
this.provider = provider || null;
|
|
176
|
+
this.model = model || null;
|
|
177
|
+
this.contextWindow = contextWindow ?? null;
|
|
178
|
+
this.budgetTokens = budgetTokens ?? null;
|
|
179
|
+
this.reserveTokens = reserveTokens ?? null;
|
|
180
|
+
this.messageTokensEst = messageTokensEst ?? null;
|
|
181
|
+
if (cause) this.cause = cause;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function bridgeContextOverflowError({ stage, sessionId, sessionRef, model, budgetTokens, reserveTokens, messageTokensEst }, cause) {
|
|
186
|
+
return new BridgeContextOverflowError({
|
|
187
|
+
stage,
|
|
188
|
+
sessionId,
|
|
189
|
+
provider: sessionRef?.provider || null,
|
|
190
|
+
model: sessionRef?.model || model || null,
|
|
191
|
+
contextWindow: sessionRef?.contextWindow ?? null,
|
|
192
|
+
budgetTokens,
|
|
193
|
+
reserveTokens,
|
|
194
|
+
messageTokensEst,
|
|
195
|
+
}, cause);
|
|
196
|
+
}
|
|
197
|
+
|
|
157
198
|
// Cache-hit results always inline the cached body. The earlier size-gated
|
|
158
199
|
// `[cache-hit-ref]` branch confused bridge workers whose context did not
|
|
159
200
|
// contain the referenced prior tool_result, triggering shell-cat detours.
|
|
@@ -719,7 +760,41 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
719
760
|
const beforeCount = messages.length;
|
|
720
761
|
let beforeBytes = null;
|
|
721
762
|
try { beforeBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { beforeBytes = null; }
|
|
722
|
-
const
|
|
763
|
+
const messageTokensEst = estimateMessagesTokensSafe(messages);
|
|
764
|
+
let trimmed;
|
|
765
|
+
try {
|
|
766
|
+
trimmed = trimMessages(messages, safetyBudget, { reserveTokens });
|
|
767
|
+
} catch (trimErr) {
|
|
768
|
+
traceBridgeTrim({
|
|
769
|
+
sessionId,
|
|
770
|
+
iteration: iterations + 1,
|
|
771
|
+
stage: 'pre_send',
|
|
772
|
+
prune_count: 0,
|
|
773
|
+
trim_changed: false,
|
|
774
|
+
input_prefix_hash: messagePrefixHash(messages),
|
|
775
|
+
before_count: beforeCount,
|
|
776
|
+
after_count: messages.length,
|
|
777
|
+
before_bytes: beforeBytes,
|
|
778
|
+
after_bytes: beforeBytes,
|
|
779
|
+
context_window: sessionRef.contextWindow,
|
|
780
|
+
budget_tokens: safetyBudget,
|
|
781
|
+
reserve_tokens: reserveTokens,
|
|
782
|
+
message_tokens_est: messageTokensEst,
|
|
783
|
+
provider: sessionRef.provider,
|
|
784
|
+
model: sessionRef.model || model,
|
|
785
|
+
error: trimErr && trimErr.message ? trimErr.message : String(trimErr),
|
|
786
|
+
error_code: 'BRIDGE_CONTEXT_OVERFLOW',
|
|
787
|
+
});
|
|
788
|
+
throw bridgeContextOverflowError({
|
|
789
|
+
stage: 'pre_send',
|
|
790
|
+
sessionId,
|
|
791
|
+
sessionRef,
|
|
792
|
+
model,
|
|
793
|
+
budgetTokens: safetyBudget,
|
|
794
|
+
reserveTokens,
|
|
795
|
+
messageTokensEst,
|
|
796
|
+
}, trimErr);
|
|
797
|
+
}
|
|
723
798
|
const trimChanged = messagesArrayChanged(messages, trimmed);
|
|
724
799
|
const pruneCount = Math.max(beforeCount - trimmed.length, 0);
|
|
725
800
|
if (trimChanged) {
|
|
@@ -742,6 +817,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
742
817
|
traceBridgeTrim({
|
|
743
818
|
sessionId,
|
|
744
819
|
iteration: iterations + 1,
|
|
820
|
+
stage: 'pre_send',
|
|
745
821
|
prune_count: pruneCount,
|
|
746
822
|
trim_changed: trimChanged,
|
|
747
823
|
input_prefix_hash: messagePrefixHash(messages),
|
|
@@ -749,6 +825,12 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
749
825
|
after_count: messages.length,
|
|
750
826
|
before_bytes: beforeBytes,
|
|
751
827
|
after_bytes: afterBytes,
|
|
828
|
+
context_window: sessionRef.contextWindow,
|
|
829
|
+
budget_tokens: safetyBudget,
|
|
830
|
+
reserve_tokens: reserveTokens,
|
|
831
|
+
message_tokens_est: messageTokensEst,
|
|
832
|
+
provider: sessionRef.provider,
|
|
833
|
+
model: sessionRef.model || model,
|
|
752
834
|
});
|
|
753
835
|
}
|
|
754
836
|
const nextIteration = iterations + 1;
|
|
@@ -875,9 +957,68 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
875
957
|
}
|
|
876
958
|
const overflowBudget = Math.floor(sessionRef.contextWindow * OVERFLOW_RETRY_TRIM_PERCENT);
|
|
877
959
|
const overflowReserve = estimateRequestReserveTokens(sendTools.length ? sendTools : tools);
|
|
878
|
-
const
|
|
960
|
+
const beforeCount = messages.length;
|
|
961
|
+
let beforeBytes = null;
|
|
962
|
+
try { beforeBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { beforeBytes = null; }
|
|
963
|
+
const messageTokensEst = estimateMessagesTokensSafe(messages);
|
|
964
|
+
let retrimmed;
|
|
965
|
+
try {
|
|
966
|
+
retrimmed = trimMessages(messages, overflowBudget, { reserveTokens: overflowReserve });
|
|
967
|
+
} catch (trimErr) {
|
|
968
|
+
traceBridgeTrim({
|
|
969
|
+
sessionId,
|
|
970
|
+
iteration: nextIteration,
|
|
971
|
+
stage: 'overflow_retry',
|
|
972
|
+
prune_count: 0,
|
|
973
|
+
trim_changed: false,
|
|
974
|
+
input_prefix_hash: messagePrefixHash(messages),
|
|
975
|
+
before_count: beforeCount,
|
|
976
|
+
after_count: messages.length,
|
|
977
|
+
before_bytes: beforeBytes,
|
|
978
|
+
after_bytes: beforeBytes,
|
|
979
|
+
context_window: sessionRef.contextWindow,
|
|
980
|
+
budget_tokens: overflowBudget,
|
|
981
|
+
reserve_tokens: overflowReserve,
|
|
982
|
+
message_tokens_est: messageTokensEst,
|
|
983
|
+
provider: sessionRef.provider,
|
|
984
|
+
model: sessionRef.model || model,
|
|
985
|
+
error: trimErr && trimErr.message ? trimErr.message : String(trimErr),
|
|
986
|
+
error_code: 'BRIDGE_CONTEXT_OVERFLOW',
|
|
987
|
+
});
|
|
988
|
+
throw bridgeContextOverflowError({
|
|
989
|
+
stage: 'overflow_retry',
|
|
990
|
+
sessionId,
|
|
991
|
+
sessionRef,
|
|
992
|
+
model,
|
|
993
|
+
budgetTokens: overflowBudget,
|
|
994
|
+
reserveTokens: overflowReserve,
|
|
995
|
+
messageTokensEst,
|
|
996
|
+
}, trimErr);
|
|
997
|
+
}
|
|
998
|
+
const trimChanged = messagesArrayChanged(messages, retrimmed);
|
|
999
|
+
const pruneCount = Math.max(beforeCount - retrimmed.length, 0);
|
|
879
1000
|
messages.length = 0;
|
|
880
1001
|
messages.push(...retrimmed);
|
|
1002
|
+
let afterBytes = null;
|
|
1003
|
+
try { afterBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { afterBytes = null; }
|
|
1004
|
+
traceBridgeTrim({
|
|
1005
|
+
sessionId,
|
|
1006
|
+
iteration: nextIteration,
|
|
1007
|
+
stage: 'overflow_retry',
|
|
1008
|
+
prune_count: pruneCount,
|
|
1009
|
+
trim_changed: trimChanged,
|
|
1010
|
+
input_prefix_hash: messagePrefixHash(messages),
|
|
1011
|
+
before_count: beforeCount,
|
|
1012
|
+
after_count: messages.length,
|
|
1013
|
+
before_bytes: beforeBytes,
|
|
1014
|
+
after_bytes: afterBytes,
|
|
1015
|
+
context_window: sessionRef.contextWindow,
|
|
1016
|
+
budget_tokens: overflowBudget,
|
|
1017
|
+
reserve_tokens: overflowReserve,
|
|
1018
|
+
message_tokens_est: messageTokensEst,
|
|
1019
|
+
provider: sessionRef.provider,
|
|
1020
|
+
model: sessionRef.model || model,
|
|
1021
|
+
});
|
|
881
1022
|
// The transcript prefix changed; the server-side conversation anchor
|
|
882
1023
|
// (previous_response_id / WS continuation) is now invalid. Drop
|
|
883
1024
|
// providerState so the retry starts a fresh chain instead of
|
|
@@ -18,7 +18,7 @@ function messageEstimateText(m) {
|
|
|
18
18
|
function estimateMessageTokens(m) {
|
|
19
19
|
return estimateTokens(messageEstimateText(m)) + 4;
|
|
20
20
|
}
|
|
21
|
-
function estimateMessagesTokens(messages) {
|
|
21
|
+
export function estimateMessagesTokens(messages) {
|
|
22
22
|
return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -67,6 +67,35 @@ function isNonEmptyString(v) {
|
|
|
67
67
|
return typeof v === 'string' && v.length > 0;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function hasMeaningfulWriteSingle(a) {
|
|
71
|
+
return (isNonEmptyString(a?.path) || isNonEmptyString(a?.file_path))
|
|
72
|
+
&& hasOwn(a, 'content')
|
|
73
|
+
&& typeof a.content === 'string';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isInertWriteEntry(w) {
|
|
77
|
+
return w && typeof w === 'object'
|
|
78
|
+
&& !isNonEmptyString(w.path)
|
|
79
|
+
&& !isNonEmptyString(w.file_path)
|
|
80
|
+
&& (!hasOwn(w, 'content') || w.content === '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasMeaningfulEditSingle(a) {
|
|
84
|
+
return hasOwn(a, 'old_string')
|
|
85
|
+
&& hasOwn(a, 'new_string')
|
|
86
|
+
&& isNonEmptyString(a.old_string)
|
|
87
|
+
&& typeof a.new_string === 'string';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isInertEditEntry(e) {
|
|
91
|
+
return e && typeof e === 'object'
|
|
92
|
+
&& !isNonEmptyString(e.path)
|
|
93
|
+
&& !isNonEmptyString(e.file_path)
|
|
94
|
+
&& (!hasOwn(e, 'old_string') || e.old_string === '')
|
|
95
|
+
&& (!hasOwn(e, 'new_string') || e.new_string === '')
|
|
96
|
+
&& (!hasOwn(e, 'replace_all') || e.replace_all === false || e.replace_all === null || e.replace_all === undefined);
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
function isStringOrStringArray(v) {
|
|
71
100
|
if (typeof v === 'string') return true;
|
|
72
101
|
if (!Array.isArray(v) || v.length === 0) return false;
|
|
@@ -216,8 +245,11 @@ function guardEdit(a) {
|
|
|
216
245
|
if (op === 'rename') {
|
|
217
246
|
return guardRename(a);
|
|
218
247
|
}
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
if (Array.isArray(a.edits)) {
|
|
249
|
+
a.edits = a.edits.filter((e) => !isInertEditEntry(e));
|
|
250
|
+
}
|
|
251
|
+
const hasSingle = hasMeaningfulEditSingle(a);
|
|
252
|
+
const hasBatch = Array.isArray(a.edits) && a.edits.length > 0;
|
|
221
253
|
if (hasSingle && hasBatch) {
|
|
222
254
|
return 'Error: edit requires either (old_string + new_string) OR edits[], not both';
|
|
223
255
|
}
|
|
@@ -231,16 +263,39 @@ function guardEdit(a) {
|
|
|
231
263
|
if (typeof a.old_string !== 'string') {
|
|
232
264
|
return `Error: edit arg "old_string" must be a string (got ${describeType(a.old_string)})`;
|
|
233
265
|
}
|
|
266
|
+
if (a.old_string.length === 0) {
|
|
267
|
+
return 'Error: edit arg "old_string" must be non-empty';
|
|
268
|
+
}
|
|
234
269
|
if (typeof a.new_string !== 'string') {
|
|
235
270
|
return `Error: edit arg "new_string" must be a string (got ${describeType(a.new_string)})`;
|
|
236
271
|
}
|
|
237
272
|
}
|
|
273
|
+
if (hasBatch) {
|
|
274
|
+
for (let i = 0; i < a.edits.length; i++) {
|
|
275
|
+
const e = a.edits[i];
|
|
276
|
+
if (!e || typeof e !== 'object') {
|
|
277
|
+
return `Error: edit edits[${i}] must be an object with old_string + new_string`;
|
|
278
|
+
}
|
|
279
|
+
if (typeof e.old_string !== 'string') {
|
|
280
|
+
return `Error: edit edits[${i}].old_string must be a string (got ${describeType(e.old_string)})`;
|
|
281
|
+
}
|
|
282
|
+
if (e.old_string.length === 0) {
|
|
283
|
+
return `Error: edit edits[${i}].old_string must be non-empty`;
|
|
284
|
+
}
|
|
285
|
+
if (typeof e.new_string !== 'string') {
|
|
286
|
+
return `Error: edit edits[${i}].new_string must be a string (got ${describeType(e.new_string)})`;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
238
290
|
return null;
|
|
239
291
|
}
|
|
240
292
|
|
|
241
293
|
function guardWrite(a) {
|
|
242
|
-
|
|
243
|
-
|
|
294
|
+
if (Array.isArray(a.writes)) {
|
|
295
|
+
a.writes = a.writes.filter((w) => !isInertWriteEntry(w));
|
|
296
|
+
}
|
|
297
|
+
const hasSingle = hasMeaningfulWriteSingle(a);
|
|
298
|
+
const hasBatch = Array.isArray(a.writes) && a.writes.length > 0;
|
|
244
299
|
if (hasSingle && hasBatch) {
|
|
245
300
|
return 'Error: write requires either (path + content) OR writes[], not both';
|
|
246
301
|
}
|
|
@@ -265,8 +320,9 @@ function guardWrite(a) {
|
|
|
265
320
|
if (!w || typeof w !== 'object') {
|
|
266
321
|
return `Error: write writes[${i}] must be an object with path + content`;
|
|
267
322
|
}
|
|
268
|
-
|
|
269
|
-
|
|
323
|
+
const p = hasOwn(w, 'path') ? w.path : w.file_path;
|
|
324
|
+
if (typeof p !== 'string' || !p) {
|
|
325
|
+
return `Error: write writes[${i}].path must be a non-empty string (got ${describeType(p)})`;
|
|
270
326
|
}
|
|
271
327
|
if (typeof w.content !== 'string') {
|
|
272
328
|
return `Error: write writes[${i}].content must be a string (got ${describeType(w.content)})`;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
2
|
+
"version": "0.7.18",
|
|
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.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-graph-darwin-arm64",
|
|
7
|
+
"sha256": "34653a7cdf457295c4835fa9819627e9690c323b6012e6f571259478406bbe04"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-graph-darwin-x64",
|
|
11
|
+
"sha256": "1c0648ed8a1b9fd2d8bc44d85b91262cb11e0773d2280d6ec351ee1ff1c579f0"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-graph-linux-arm64",
|
|
15
|
+
"sha256": "71c9d69125b97e93466e5a5613c00db2d797c1f25cc4237e776225574c5f6970"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-graph-linux-x64",
|
|
19
|
+
"sha256": "4800a7ac77b777ddb5862f252cdc00b55230455921e0c58416d7a77b120604a7"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-graph-win32-x64.exe",
|
|
23
|
+
"sha256": "1d0d62f5b78965c7d5b05ebc7cf7bdef1a3e14cc2600aa205ca762e56c446934"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
2
|
+
"version": "0.7.18",
|
|
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.
|
|
6
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-patch-darwin-arm64",
|
|
7
7
|
"sha256": "836a0b60a443b0a6a8c1bbe24d15a79ed70ee92c2f0fbc05374c4e9ed2536415"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
10
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-patch-darwin-x64",
|
|
11
11
|
"sha256": "cab10c4e1e8b72d3958241dffdff764712ed74f4861d105bafa8258961215c98"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
14
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-patch-linux-arm64",
|
|
15
15
|
"sha256": "a90c32ce3417a7d853f2723f82f3613cf2cd030fe885cf710cfc9f8e4b193264"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
18
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-patch-linux-x64",
|
|
19
19
|
"sha256": "0fea40ab98acd35bfb47515756024d1882a2abbaddce8a0b51642d20ac405577"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/v0.7.18/mixdog-patch-win32-x64.exe",
|
|
23
|
+
"sha256": "cf2f28f8b410a5b0b6deb500fbb332be7cba11adee4340c3433212327b14412c"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/agent/tool-defs.mjs
CHANGED
|
@@ -13,6 +13,13 @@ export const TOOL_DEFS = [
|
|
|
13
13
|
description: 'List models.',
|
|
14
14
|
inputSchema: { type: 'object', properties: {} },
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
name: 'gateway_select_model',
|
|
18
|
+
title: 'Select Gateway Model',
|
|
19
|
+
annotations: { title: 'Select Gateway Model', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
20
|
+
description: 'Interactively choose how the mixdog gateway routes Claude Code\'s main model. The first choice follows Claude Code\'s own current /model setting (including effort/fast where available); the rest list enabled providers + models. On selection, writes the gateway target/mode (+ enables the gateway module) — live on the next gateway request, no restart. If the client does not support elicitation, it falls back to listing the models and tells you to run /mixdog:model. Opt-in: only runs when you invoke it.',
|
|
21
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
22
|
+
},
|
|
16
23
|
{
|
|
17
24
|
name: 'explore',
|
|
18
25
|
title: 'Explore',
|
|
@@ -70,7 +77,7 @@ export const TOOL_DEFS = [
|
|
|
70
77
|
title: 'Bridge to External Model',
|
|
71
78
|
annotations: { title: 'Bridge to External Model', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true, bridgeHidden: true },
|
|
72
79
|
description: 'Unified worker session control. type selects the action (default "spawn"). '
|
|
73
|
-
+ 'spawn: dispatch a detached worker — requires role +
|
|
80
|
+
+ 'spawn: dispatch a detached worker — requires role + prompt; optional tag names the session for later send/close (auto "<role><n>" if omitted). '
|
|
74
81
|
+ 'send: resume a worker by tag (or raw sess_ id) — requires tag + message. To CONTINUE the same task on the same files/context — fix rounds, re-reviews, iterative refinement, or any follow-up — PREFER reusing the original tag over a new spawn: it preserves the worker\'s transcript + file context + change history, so it is faster, cheaper (the prompt cache stays warm), and more accurate than re-discovering from scratch. The return\'s respawned flag = false when the live session continued (context kept), true when a closed/cold tag was respawned fresh (context reset). '
|
|
75
82
|
+ 'close: stop a worker — requires tag. '
|
|
76
83
|
+ 'list: enumerate active worker sessions with stage/staleness — no args (optional role/status filter).',
|
|
@@ -80,8 +87,8 @@ export const TOOL_DEFS = [
|
|
|
80
87
|
type: { type: 'string', enum: ['spawn', 'send', 'close', 'list'], description: 'Action discriminator. Default "spawn".' },
|
|
81
88
|
role: { type: 'string', description: 'Worker role (from user-workflow). Required for spawn; optional filter for list.' },
|
|
82
89
|
tag: { type: 'string', description: 'Stable session handle. spawn: optional name; send/close: required target (tag or raw sess_ id).' },
|
|
83
|
-
message: { type: 'string', description: '
|
|
84
|
-
prompt: { type: 'string', description: '
|
|
90
|
+
message: { type: 'string', description: 'Follow-up text for send. For spawn, use prompt. A spawn brief must state: goal+why, mode (write-code vs research-only), anchor (file:line/symbol/command), and done (output shape + stop condition).' },
|
|
91
|
+
prompt: { type: 'string', description: 'Brief for spawn. Prefer this over message; must contain non-whitespace content unless file/ref is provided.' },
|
|
85
92
|
status: { type: 'string', description: 'list: optional status filter (e.g. running, idle, error).' },
|
|
86
93
|
provider: { type: 'string', description: 'spawn: override the worker provider.' },
|
|
87
94
|
model: { type: 'string', description: 'spawn: override the worker model.' },
|
|
@@ -193,6 +193,9 @@ function writeActiveInstance(state) {
|
|
|
193
193
|
writeJsonFile(ACTIVE_INSTANCE_FILE, state);
|
|
194
194
|
}
|
|
195
195
|
function buildActiveInstanceState(instanceId, meta) {
|
|
196
|
+
const gatewayMeta = Object.fromEntries(
|
|
197
|
+
Object.entries(meta || {}).filter(([k]) => k.startsWith('gateway_'))
|
|
198
|
+
);
|
|
196
199
|
return {
|
|
197
200
|
instanceId,
|
|
198
201
|
...buildRuntimeIdentity(),
|
|
@@ -202,10 +205,11 @@ function buildActiveInstanceState(instanceId, meta) {
|
|
|
202
205
|
turnEndFile: getTurnEndPath(instanceId),
|
|
203
206
|
statusFile: getStatusPath(instanceId),
|
|
204
207
|
...meta?.channelId ? { channelId: meta.channelId } : {},
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
...meta?.transcriptPath ? { transcriptPath: meta.transcriptPath } : {},
|
|
209
|
+
...meta?.httpPort ? { httpPort: meta.httpPort } : {},
|
|
210
|
+
...meta?.memory_port ? { memory_port: meta.memory_port } : {},
|
|
211
|
+
...gatewayMeta,
|
|
212
|
+
...typeof meta?.backendReady === "boolean" ? { backendReady: meta.backendReady } : {}
|
|
209
213
|
};
|
|
210
214
|
}
|
|
211
215
|
function refreshActiveInstance(instanceId, meta) {
|
|
@@ -229,6 +233,9 @@ function refreshActiveInstance(instanceId, meta) {
|
|
|
229
233
|
&& prevServerPid === identity.server_pid
|
|
230
234
|
&& Number.isFinite(prevServerStartedAt)
|
|
231
235
|
) ? prevServerStartedAt : Date.now();
|
|
236
|
+
const gatewayMeta = Object.fromEntries(
|
|
237
|
+
Object.entries(meta || {}).filter(([k]) => k.startsWith('gateway_'))
|
|
238
|
+
);
|
|
232
239
|
const next = {
|
|
233
240
|
...(prev?.instanceId === instanceId ? prevRest : buildActiveInstanceState(instanceId)),
|
|
234
241
|
...identity,
|
|
@@ -238,6 +245,7 @@ function refreshActiveInstance(instanceId, meta) {
|
|
|
238
245
|
...meta?.transcriptPath ? { transcriptPath: meta.transcriptPath } : {},
|
|
239
246
|
...meta?.httpPort ? { httpPort: meta.httpPort } : {},
|
|
240
247
|
...meta?.memory_port ? { memory_port: meta.memory_port } : {},
|
|
248
|
+
...gatewayMeta,
|
|
241
249
|
...typeof meta?.backendReady === "boolean" ? { backendReady: meta.backendReady } : {},
|
|
242
250
|
};
|
|
243
251
|
if (typeof meta?.transcriptPath === "string" && meta.transcriptPath) {
|
|
@@ -283,6 +291,33 @@ function refreshActiveInstance(instanceId, meta) {
|
|
|
283
291
|
preservedExtra.memory_server_pid = prevMemoryServerPid;
|
|
284
292
|
}
|
|
285
293
|
}
|
|
294
|
+
// gateway_port follows the same preservation rule as memory_port: the
|
|
295
|
+
// gateway child advertises itself independently, and active-owner
|
|
296
|
+
// heartbeats must not erase that discovery record while its owning
|
|
297
|
+
// server-main process is still alive.
|
|
298
|
+
const prevGatewayServerPid = parsePositivePid(prevForPreserve?.gateway_server_pid);
|
|
299
|
+
const prevGatewayOwnerAlive = (() => {
|
|
300
|
+
if (prevGatewayServerPid === null) return false;
|
|
301
|
+
try {
|
|
302
|
+
process.kill(prevGatewayServerPid, 0);
|
|
303
|
+
return true;
|
|
304
|
+
} catch (e) {
|
|
305
|
+
if (e && e.code === "ESRCH") return false;
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
})();
|
|
309
|
+
const sameGatewayAdvertiser =
|
|
310
|
+
prevGatewayServerPid !== null &&
|
|
311
|
+
identity.server_pid !== null &&
|
|
312
|
+
prevGatewayServerPid === identity.server_pid;
|
|
313
|
+
if (sameGatewayAdvertiser || prevGatewayOwnerAlive) {
|
|
314
|
+
if (prevForPreserve && Object.prototype.hasOwnProperty.call(prevForPreserve, 'gateway_port')) {
|
|
315
|
+
for (const [key, value] of Object.entries(prevForPreserve)) {
|
|
316
|
+
if (key.startsWith('gateway_')) preservedExtra[key] = value;
|
|
317
|
+
}
|
|
318
|
+
preservedExtra.gateway_server_pid = prevGatewayServerPid;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
286
321
|
return { ...preservedExtra, ...next };
|
|
287
322
|
}, { compact: true, fsyncDir: true });
|
|
288
323
|
}
|