mixdog 0.9.34 → 0.9.35
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/README.md +6 -0
- package/package.json +4 -2
- package/scripts/_devtools-stub.mjs +1 -0
- package/scripts/_jitter-fuzz.jsx +42 -0
- package/scripts/_jitter-fuzz.mjs +44410 -0
- package/scripts/_jitter-fuzz2.jsx +30 -0
- package/scripts/_jitter-fuzz2.mjs +44400 -0
- package/scripts/_jitter-probe.jsx +35 -0
- package/scripts/_jitter-probe.mjs +44397 -0
- package/scripts/_jp2.jsx +16 -0
- package/scripts/_jp2.mjs +45614 -0
- package/scripts/agent-live-arg-guard-smoke.mjs +20 -0
- package/scripts/agent-live-path-suffix-smoke.mjs +34 -0
- package/scripts/agent-live-toolcall-args-smoke.mjs +45 -0
- package/scripts/apply-patch-edit-smoke.mjs +71 -0
- package/scripts/async-notify-settlement-test.mjs +99 -0
- package/scripts/forwarder-rebind-tail-test.mjs +67 -0
- package/scripts/live-worker-smoke.mjs +1 -1
- package/scripts/pending-completion-drop-test.mjs +46 -27
- package/scripts/provider-toolcall-test.mjs +1 -0
- package/scripts/recall-bench-cases.json +1 -0
- package/scripts/session-bench.mjs +44 -0
- package/scripts/shell-hardening-test.mjs +209 -0
- package/scripts/ship-mode-test.mjs +98 -0
- package/scripts/steering-drain-buckets-test.mjs +59 -0
- package/scripts/tool-smoke.mjs +46 -10
- package/scripts/worker-notify-rejection-test.mjs +51 -0
- package/src/lib/mixdog-debug.cjs +69 -0
- package/src/rules/agent/00-common.md +4 -5
- package/src/rules/agent/00-core.md +13 -20
- package/src/rules/agent/20-skip-protocol.md +3 -8
- package/src/rules/agent/30-explorer.md +31 -54
- package/src/rules/lead/01-general.md +3 -6
- package/src/rules/lead/lead-brief.md +10 -16
- package/src/rules/lead/lead-tool.md +3 -5
- package/src/rules/shared/01-tool.md +7 -9
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +19 -23
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +13 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +21 -7
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +1 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +71 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +17 -9
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +127 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +105 -28
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +7 -3
- package/src/runtime/memory/lib/memory-embed.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +25 -10
- package/src/runtime/memory/tool-defs.mjs +3 -3
- package/src/runtime/shared/background-tasks.mjs +21 -1
- package/src/runtime/shared/tool-execution-contract.mjs +29 -16
- package/src/session-runtime/model-route-api.mjs +3 -3
- package/src/session-runtime/provider-auth-api.mjs +13 -0
- package/src/session-runtime/runtime-core.mjs +40 -6
- package/src/session-runtime/tool-catalog.mjs +3 -2
- package/src/session-runtime/workflow-agents-api.mjs +2 -2
- package/src/standalone/agent-tool/notify.mjs +14 -1
- package/src/tui/dist/index.mjs +63 -25
- package/src/tui/engine/session-flow.mjs +26 -13
- package/src/tui/engine/turn.mjs +7 -8
- package/src/tui/engine.mjs +4 -2
- package/src/vendor/statusline/bin/statusline-route.mjs +60 -4
- package/src/vendor/statusline/src/gateway/route-meta.mjs +37 -8
- package/src/workflows/default/WORKFLOW.md +26 -32
- package/src/workflows/solo/WORKFLOW.md +12 -13
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
# Agent Constraints
|
|
2
2
|
|
|
3
3
|
- Use English for agent task communication.
|
|
4
|
-
-
|
|
5
|
-
turn is
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- A blocked or partial report is a valid completion: state done/missing/
|
|
18
|
-
blocker in fragments — never keep retrieving to avoid reporting.
|
|
19
|
-
- Banned as pure cost: report headings, markdown tables (unless requested),
|
|
20
|
-
prose narration, raw logs/tool traces, speculative next-checks, restated
|
|
21
|
-
brief, articles/politeness.
|
|
22
|
-
- Exception: a runtime wrap-up directive (exploration budget reached) overrides
|
|
23
|
-
this — then summarize done/remaining/blocking as instructed.
|
|
4
|
+
- One turn = one batch; any read-only call that could have ridden the prior
|
|
5
|
+
turn is wasted.
|
|
6
|
+
- Never preamble/progress before tools; call tools immediately. Text only in
|
|
7
|
+
the final handoff after tool work.
|
|
8
|
+
- Final handoff: fragments only — outcome, key `file:line`, verification
|
|
9
|
+
command+result, material risks/blockers. Follow stricter role contracts.
|
|
10
|
+
- Do not repeat the brief, process, search path, or any fact. Report done/
|
|
11
|
+
missing/blocker instead of retrieving to avoid reporting.
|
|
12
|
+
- Handoff cap ~30 lines unless `Deliver:` raises it; this is a ceiling.
|
|
13
|
+
- Ban headings, tables unless requested, prose narration, raw logs/tool traces,
|
|
14
|
+
speculative next-checks, restated brief, articles/politeness.
|
|
15
|
+
- Runtime wrap-up directives override this and may require done/remaining/
|
|
16
|
+
blocking summary.
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# Skip Protocol
|
|
2
2
|
|
|
3
|
-
For inbound-event
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
prefix the whole response with:
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
[meta:silent]
|
|
10
|
-
```
|
|
3
|
+
For inbound-event reports (`webhook-handler`, `scheduler-task`) with nothing
|
|
4
|
+
actionable to relay (label-only, duplicate/dedup, no action needed/report),
|
|
5
|
+
prefix the whole response with `[meta:silent]`.
|
|
@@ -6,66 +6,43 @@ kind: retrieval
|
|
|
6
6
|
|
|
7
7
|
# Role: explorer
|
|
8
8
|
|
|
9
|
-
Coordinate locator.
|
|
10
|
-
|
|
11
|
-
rules pointing at `explore` don't apply.
|
|
9
|
+
Coordinate locator. Deliver WHERE as `path:line`, never WHY. You ARE
|
|
10
|
+
`explore`; never call it.
|
|
12
11
|
|
|
13
|
-
Tools: grep/find/glob/code_graph ONLY. `read
|
|
14
|
-
grep
|
|
15
|
-
file to "confirm" or "understand" a hit is the WHY you must not answer.
|
|
16
|
-
This ban has NO exception: not after a hit, not for the reason field, not
|
|
17
|
-
"just one read" — a `read`/`list` call anywhere in the session is a defect.
|
|
12
|
+
Tools: grep/find/glob/code_graph ONLY. `read`/`list` are forbidden with no
|
|
13
|
+
exception; grep/code_graph lines already carry the `path:line` answer.
|
|
18
14
|
|
|
19
|
-
Turn 1 is the whole search
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
symbol_search when the query names an identifier. A single-pattern,
|
|
23
|
-
single-tool first turn is a defect.
|
|
15
|
+
Turn 1 is the whole search in ONE message: grep `pattern[]` with 3-6 code-token
|
|
16
|
+
variants, find name fragments, and code_graph symbol_search for identifiers. A
|
|
17
|
+
single-pattern or single-tool first turn is a defect.
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`content_with_context` ONLY against a `path` that appeared in an earlier
|
|
29
|
-
result THIS session, and always with `head_limit`.
|
|
19
|
+
Broad grep must use `output_mode:"files_with_matches"`. Use
|
|
20
|
+
`content_with_context` only on a path returned earlier in THIS session and with
|
|
21
|
+
`head_limit`.
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
non-
|
|
33
|
-
횟수" → maxLoop, loop-policy, iterations). Grep non-ASCII text only when
|
|
34
|
-
the query quotes a literal string.
|
|
23
|
+
Translate natural/non-English queries to probable English identifiers first;
|
|
24
|
+
grep non-ASCII only for quoted literal strings.
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
layout) is a defect — those results are always empty; on zero hits change
|
|
40
|
-
TOKENS, never guess paths.
|
|
26
|
+
Scope = session working directory. Omit `path` or use only paths returned
|
|
27
|
+
earlier in this session. Never invent directories; after zero hits change
|
|
28
|
+
TOKENS/scope, not wording or guessed paths.
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
A `code_graph` symbol hit (find_symbol/symbol_search returning `path:line`)
|
|
47
|
-
IS an anchor — emit it directly; never re-locate it with grep.
|
|
30
|
+
Hit test is mechanical: any `path:line` containing a query token or obvious
|
|
31
|
+
synonym is an anchor; generic-only words (schema/handler/config/resolver…)
|
|
32
|
+
without a specific token are zero. code_graph `path:line` hits are anchors —
|
|
33
|
+
never re-locate them with grep.
|
|
48
34
|
|
|
49
|
-
Rule zero
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ones" IS branch one. Once ANY turn yields a matching line, the only legal
|
|
53
|
-
next output is the final answer text — every further tool call is a defect.
|
|
35
|
+
Rule zero after every tool result: any matching anchor → answer NOW (mark weak
|
|
36
|
+
anchors `?`); zero → one more batch if budget remains. Further searching after
|
|
37
|
+
a matching anchor is a defect.
|
|
54
38
|
|
|
55
|
-
Turns:
|
|
56
|
-
|
|
57
|
-
must change tokens OR scope, never reword.
|
|
58
|
-
Flow/how questions: first matching entry/definition anchors ARE the answer,
|
|
59
|
-
one anchor per concept — never trace the chain.
|
|
60
|
-
Compound queries ("where is X and what value/default does it use"): the
|
|
61
|
-
definition anchor answers BOTH parts — never launch extra searches for the
|
|
62
|
-
value, threshold, or content; one anchor per concept, all from the same batch.
|
|
39
|
+
Turns: max 3, expected 1; start tool messages with `turn N/3`. Turns 2-3 are
|
|
40
|
+
miss recovery only and must change tokens or scope.
|
|
63
41
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
(`?` if weak) instead — a weak anchor beats a false miss.
|
|
42
|
+
Flow/how and compound queries: first matching entry/definition anchors answer
|
|
43
|
+
the concept/value/default; never trace chains or launch extra value searches.
|
|
44
|
+
|
|
45
|
+
Answer only: up to 3 lines `path:line — symbol — short reason` (`?` if weak),
|
|
46
|
+
choosing the most specific token matches. Emit `EXPLORATION_FAILED` only after
|
|
47
|
+
budget is spent with zero specific-token anchors; before failing, re-scan prior
|
|
48
|
+
results and prefer any weak specific-token anchor over a false miss.
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# General
|
|
2
2
|
|
|
3
|
-
- Preambles are optional: one short sentence only when
|
|
4
|
-
|
|
5
|
-
narration.
|
|
3
|
+
- Preambles are optional: one short sentence only when useful; no direct names,
|
|
4
|
+
honorifics, headings, labels, or routine lookup narration.
|
|
6
5
|
- Destructive/hard-to-reverse actions require explicit confirmation.
|
|
7
6
|
- Never push, build, or deploy without an explicit user request —
|
|
8
7
|
implementation approval is not deploy approval.
|
|
9
|
-
- Handle what you can proactively
|
|
10
|
-
only the parts needing a decision, consultatively ("Shall we proceed this
|
|
11
|
-
way?").
|
|
8
|
+
- Handle what you can proactively; ask only for decisions, consultatively.
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
# Lead Brief Contract
|
|
2
2
|
|
|
3
3
|
- Brief = one-line fragments `Goal:` `Anchors:` `Allow/Forbid:` `Deliver:`
|
|
4
|
-
`Verify:` (+`Stop:` heavy-worker). Minimum
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
- `
|
|
13
|
-
|
|
14
|
-
in the handoff itself.
|
|
15
|
-
- Full brief only on fresh spawn or `respawned: true` (dead-tag send = cold
|
|
16
|
-
session; re-supply anchors); live-session follow-ups = delta only, never
|
|
17
|
-
restating Goal/rules.
|
|
18
|
-
- Never `send` mid-run; batch all adjustments into ONE follow-up after
|
|
19
|
-
completion. Interrupt only to cancel.
|
|
4
|
+
`Verify:` (+`Stop:` heavy-worker). Minimum chars, maximum info: no
|
|
5
|
+
background/motivation, role-known rules, repeated context, or duplicate facts.
|
|
6
|
+
If it feels long, split scope instead of padding.
|
|
7
|
+
- Anchors = `file:line` + one-line conclusion; never paste log/code bodies.
|
|
8
|
+
- Instruct by outcome, not method, unless the method is the requirement.
|
|
9
|
+
- `Deliver:` states output size/shape; never request a long report in the handoff.
|
|
10
|
+
- Full brief only on fresh spawn or `respawned: true`; live-session follow-ups
|
|
11
|
+
are delta only. Dead-tag send = cold session, re-supply anchors.
|
|
12
|
+
- Never `send` mid-run; batch adjustments into ONE follow-up after completion.
|
|
13
|
+
Interrupt only to cancel.
|
|
20
14
|
- All agent communication in English.
|
|
21
15
|
- Referenced spec/test file beats its summary in the brief.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Lead Tool Use
|
|
2
2
|
|
|
3
|
-
- Write-role agents
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
- Use the session's current project/workspace. Change the work project only
|
|
7
|
-
when the user asks for another project or a tool call needs another root.
|
|
3
|
+
- Write-role agents self-verify via `shell`; Lead runs cross-scope verification,
|
|
4
|
+
benches, and all git via `shell`.
|
|
5
|
+
- Use the current project/workspace; change it only on user request or tool need.
|
|
8
6
|
- Use `agent` for scoped implementation, research, review, and debugging.
|
|
@@ -7,20 +7,18 @@
|
|
|
7
7
|
solo call. Merge variants/scopes into ONE call wherever the schema takes
|
|
8
8
|
arrays (`pattern[]`, `path[]`, `symbols[]`, `query[]`), chain shell with
|
|
9
9
|
`;`/`&&` or run them in parallel, and put every known edit in ONE patch.
|
|
10
|
-
- Route by
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
→ `glob`; known directory → `list`; known file/region → `read`.
|
|
10
|
+
- Route by verified inputs: symbol/relation → `code_graph`; exact text in a
|
|
11
|
+
verified scope → `grep`; unknown/out-of-repo/machine-wide location or concept
|
|
12
|
+
→ `explore`; name fragment → `find`; exact pattern → `glob`; verified dir →
|
|
13
|
+
`list`; verified file/span → `read`.
|
|
15
14
|
- `explore` fan-out: at task start, decompose what the task needs to know
|
|
16
15
|
into independent facets (implementation site, config/load path, tests,
|
|
17
16
|
error origin, ...) and send them as ONE `query[]` call — facets run in
|
|
18
17
|
parallel. Never fan out rephrasings of the same target; on
|
|
19
18
|
EXPLORATION_FAILED, retry once with changed tokens.
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
path outside the project — `find` from a verified broad root instead.
|
|
19
|
+
- Verified paths = user-provided or tool-returned. Guessed/unknown path →
|
|
20
|
+
`find`/`glob`/`explore` first; ENOENT → `find` basename, never retry a guess
|
|
21
|
+
or invent absolute paths outside the project.
|
|
24
22
|
- Retrieval stops when evidence covers the deliverable: single-answer tasks
|
|
25
23
|
end at the first sufficient anchor; enumeration tasks (review, audit) end
|
|
26
24
|
when the stated scope is covered. Never re-verify a hit already on screen;
|
|
@@ -4,6 +4,7 @@ import { dirname, join } from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { getPluginData } from './config.mjs';
|
|
6
6
|
import { readServicePort, markServiceUnreachable, isConnRefuseError } from '../../shared/service-discovery.mjs';
|
|
7
|
+
import { isDiagnosticIOEnabled } from '../../../lib/mixdog-debug.cjs';
|
|
7
8
|
|
|
8
9
|
const WARNED_KEYS = new Set();
|
|
9
10
|
|
|
@@ -94,8 +95,13 @@ function _rotateLocalTraceIfNeeded(path) {
|
|
|
94
95
|
function _resolveLocalTracePath() {
|
|
95
96
|
if (process.env.MIXDOG_AGENT_TRACE_LOCAL_DISABLE === '1') return null;
|
|
96
97
|
if (_localTracePath) return _localTracePath;
|
|
98
|
+
const explicit = process.env.MIXDOG_AGENT_TRACE_PATH;
|
|
99
|
+
// Ship-mode default: skip diagnostic local trace file IO unless dev/debug,
|
|
100
|
+
// MIXDOG_DIAGNOSTICS, or an explicit trace path opts back in. In-memory
|
|
101
|
+
// buffer + memory-service flush (session metrics) are unaffected.
|
|
102
|
+
if (!explicit && !isDiagnosticIOEnabled()) return null;
|
|
97
103
|
try {
|
|
98
|
-
_localTracePath =
|
|
104
|
+
_localTracePath = explicit
|
|
99
105
|
|| join(getPluginData(), 'history', 'agent-trace.jsonl');
|
|
100
106
|
// R4 data-at-rest: trace rows may carry tool payloads / prompts;
|
|
101
107
|
// clamp dir to owner-only on POSIX (advisory on Windows).
|
|
@@ -332,8 +338,12 @@ function appendAgentTrace(record = {}) {
|
|
|
332
338
|
function _resolveToolFailurePath() {
|
|
333
339
|
if (process.env.MIXDOG_TOOL_FAILURE_LOG_DISABLE === '1') return null;
|
|
334
340
|
if (_toolFailurePath) return _toolFailurePath;
|
|
341
|
+
const explicit = process.env.MIXDOG_TOOL_FAILURE_LOG_PATH;
|
|
342
|
+
// Ship-mode default: skip diagnostic tool-failure log file IO unless
|
|
343
|
+
// dev/debug, MIXDOG_DIAGNOSTICS, or an explicit path opts back in.
|
|
344
|
+
if (!explicit && !isDiagnosticIOEnabled()) return null;
|
|
335
345
|
try {
|
|
336
|
-
_toolFailurePath =
|
|
346
|
+
_toolFailurePath = explicit
|
|
337
347
|
|| join(getPluginData(), 'history', 'tool-failures.jsonl');
|
|
338
348
|
mkdirSync(dirname(_toolFailurePath), { recursive: true, mode: 0o700 });
|
|
339
349
|
return _toolFailurePath;
|
|
@@ -422,6 +432,7 @@ export {
|
|
|
422
432
|
drainAgentTrace,
|
|
423
433
|
normalizeSessionId,
|
|
424
434
|
warnAgentOnce,
|
|
435
|
+
_resolveLocalTracePath,
|
|
425
436
|
_resolveToolFailurePath,
|
|
426
437
|
_appendToolFailureRow,
|
|
427
438
|
};
|
|
@@ -784,21 +784,29 @@ export class GeminiProvider {
|
|
|
784
784
|
// exact value the trace already recorded (including the
|
|
785
785
|
// cachedFallback when cachedContentTokenCount / total_cached_tokens
|
|
786
786
|
// under-reports).
|
|
787
|
-
let
|
|
787
|
+
let resolvedUsage = null;
|
|
788
788
|
if (um) {
|
|
789
789
|
const {
|
|
790
790
|
inputTokens,
|
|
791
791
|
reportedCachedTokens,
|
|
792
792
|
cachedFallbackTokens,
|
|
793
|
-
cachedTokens
|
|
793
|
+
cachedTokens,
|
|
794
794
|
cacheTokenSource,
|
|
795
795
|
} = _resolveGeminiCacheUsage({
|
|
796
796
|
usageMetadata: um,
|
|
797
797
|
cachedContent,
|
|
798
798
|
providerState: opts.providerState,
|
|
799
799
|
});
|
|
800
|
-
|
|
801
|
-
|
|
800
|
+
const outputTokens = (um.candidatesTokenCount || um.candidates_token_count || 0)
|
|
801
|
+
+ (um.thoughtsTokenCount || um.thoughts_token_count || 0);
|
|
802
|
+
resolvedUsage = {
|
|
803
|
+
inputTokens,
|
|
804
|
+
outputTokens,
|
|
805
|
+
cachedTokens,
|
|
806
|
+
// Gemini promptTokenCount is total (cachedContentTokenCount is
|
|
807
|
+
// a subset). Alias the resolver's normalized total directly.
|
|
808
|
+
promptTokens: inputTokens,
|
|
809
|
+
};
|
|
802
810
|
if (cachedContent && inputTokens > 0 && cachedTokens <= 0) {
|
|
803
811
|
try {
|
|
804
812
|
appendAgentTrace({
|
|
@@ -820,11 +828,11 @@ export class GeminiProvider {
|
|
|
820
828
|
traceAgentUsage({
|
|
821
829
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
822
830
|
iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
|
|
823
|
-
inputTokens,
|
|
824
|
-
outputTokens,
|
|
825
|
-
cachedTokens,
|
|
831
|
+
inputTokens: resolvedUsage.inputTokens,
|
|
832
|
+
outputTokens: resolvedUsage.outputTokens,
|
|
833
|
+
cachedTokens: resolvedUsage.cachedTokens,
|
|
826
834
|
cacheWriteTokens: 0,
|
|
827
|
-
promptTokens:
|
|
835
|
+
promptTokens: resolvedUsage.promptTokens,
|
|
828
836
|
model: useModel,
|
|
829
837
|
modelDisplay: useModel,
|
|
830
838
|
rawUsage: um,
|
|
@@ -837,21 +845,9 @@ export class GeminiProvider {
|
|
|
837
845
|
toolCalls,
|
|
838
846
|
citations: citations.length ? citations : undefined,
|
|
839
847
|
providerState: opts.providerState,
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
inputTokens: input,
|
|
844
|
-
outputTokens: (um.candidatesTokenCount || 0) + (um.thoughtsTokenCount || 0),
|
|
845
|
-
// Use the already-computed cachedTokens (with
|
|
846
|
-
// cache-create fallback applied) rather than the raw
|
|
847
|
-
// metadata field, so the returned usage matches what
|
|
848
|
-
// traceAgentUsage recorded for this same call.
|
|
849
|
-
cachedTokens,
|
|
850
|
-
// Gemini promptTokenCount is total (cachedContentTokenCount
|
|
851
|
-
// is a subset). Alias directly into promptTokens.
|
|
852
|
-
promptTokens: input,
|
|
853
|
-
};
|
|
854
|
-
})() : undefined,
|
|
848
|
+
// Use the same normalized usage object traceAgentUsage recorded,
|
|
849
|
+
// including snake_case SDK aliases and cache-create fallback.
|
|
850
|
+
usage: resolvedUsage || undefined,
|
|
855
851
|
};
|
|
856
852
|
}
|
|
857
853
|
|
|
@@ -405,16 +405,32 @@ export function getModelMetadataSync(id, provider) {
|
|
|
405
405
|
// id, and add fields LiteLLM lacks, such as opencode-go reasoning_options.
|
|
406
406
|
if (mappedProvider) {
|
|
407
407
|
const md = _modelsDevMetadataSync(id, provider);
|
|
408
|
-
if (md) meta = mergeModelMetadata(meta, md, {
|
|
408
|
+
if (md) meta = mergeModelMetadata(meta, md, {
|
|
409
|
+
preserveBaseCosts: metaFromPricingOverride,
|
|
410
|
+
preserveBaseLimits: metaFromPricingOverride,
|
|
411
|
+
});
|
|
409
412
|
}
|
|
410
|
-
if (providerUsesEndpointScopedLimits(provider) && !providerNative && meta) {
|
|
413
|
+
if (providerUsesEndpointScopedLimits(provider) && !providerNative && meta && !metaFromPricingOverride) {
|
|
411
414
|
// OAuth/backend routes can expose smaller account/backend windows than
|
|
412
415
|
// the public API SKU. External catalogs remain useful for costs and
|
|
413
416
|
// capabilities, but their limits are not authoritative for these routes.
|
|
414
417
|
meta = { ...meta, contextWindow: null, outputTokens: null };
|
|
415
418
|
}
|
|
416
419
|
if (providerNative) {
|
|
417
|
-
|
|
420
|
+
// Provider cache limits are only authoritative for endpoint-scoped
|
|
421
|
+
// routes (OAuth/backend), where the cached row reflects the live
|
|
422
|
+
// account/backend window. For every other provider the cache is a
|
|
423
|
+
// best-effort snapshot that can go stale, so it must NOT override the
|
|
424
|
+
// catalog/known limits — otherwise this function returns cache limits
|
|
425
|
+
// labelled as catalog data and downstream catalog-vs-cache staleness
|
|
426
|
+
// checks (context-meta, statusline route-meta) compare stale-vs-stale
|
|
427
|
+
// and can never correct an outdated cached window. Capabilities and
|
|
428
|
+
// gap-filling (base limit null) still flow through the merge.
|
|
429
|
+
const nativeLimitsAuthoritative = providerUsesEndpointScopedLimits(provider);
|
|
430
|
+
meta = mergeModelMetadata(meta, providerNative, {
|
|
431
|
+
preserveBaseCosts: true,
|
|
432
|
+
preserveBaseLimits: metaFromPricingOverride || !nativeLimitsAuthoritative,
|
|
433
|
+
});
|
|
418
434
|
}
|
|
419
435
|
return meta;
|
|
420
436
|
}
|
|
@@ -444,8 +460,8 @@ function mergeModelMetadata(base, overlay, opts = {}) {
|
|
|
444
460
|
if (!overlay) return base;
|
|
445
461
|
return {
|
|
446
462
|
...base,
|
|
447
|
-
contextWindow: overlay.contextWindow || base.contextWindow || null,
|
|
448
|
-
outputTokens: overlay.outputTokens || base.outputTokens || null,
|
|
463
|
+
contextWindow: opts.preserveBaseLimits ? (base.contextWindow || overlay.contextWindow || null) : (overlay.contextWindow || base.contextWindow || null),
|
|
464
|
+
outputTokens: opts.preserveBaseLimits ? (base.outputTokens || overlay.outputTokens || null) : (overlay.outputTokens || base.outputTokens || null),
|
|
449
465
|
// Provider-scoped models.dev rates beat generic base (LiteLLM) rates
|
|
450
466
|
// when present — the overlay is the provider-scoped source, so a
|
|
451
467
|
// non-null overlay value always wins over the generic fallback —
|
|
@@ -275,7 +275,22 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
275
275
|
// so the statusline context gauge climbs as the turn accumulates
|
|
276
276
|
// tool output instead of freezing at the pre-turn snapshot. Cleared
|
|
277
277
|
// on turn commit (below) and in the ask finally.
|
|
278
|
+
//
|
|
279
|
+
// Also commit the user turn to the live session BEFORE the provider
|
|
280
|
+
// call. Previously the prompt only reached session.messages after
|
|
281
|
+
// agentLoop returned. If a worker/lead session was closed or aborted
|
|
282
|
+
// before first response, closeSession() wrote a tombstone from the
|
|
283
|
+
// still-system-only session and the handoff brief vanished forever
|
|
284
|
+
// (agent row showed messages=2). Pre-committing makes cancellation,
|
|
285
|
+
// close, and post-mortem files retain the exact user task; completion
|
|
286
|
+
// below overwrites this provisional transcript with the fully mutated
|
|
287
|
+
// outgoing history and appends the assistant result, so no duplicate
|
|
288
|
+
// user turn is introduced.
|
|
289
|
+
session.messages = sanitizeSessionMessagesForModel(outgoing);
|
|
278
290
|
session.liveTurnMessages = outgoing;
|
|
291
|
+
saveSessionAsync(session, { expectedGeneration: askGeneration }).catch((err) => {
|
|
292
|
+
try { process.stderr.write(`[session] preflight user-turn save failed: ${err?.message || err}\n`); } catch {}
|
|
293
|
+
});
|
|
279
294
|
// Per-turn injected-context trace row (complements kind:"usage").
|
|
280
295
|
// Cheap byte-length accounting — no hashing, no payload bodies.
|
|
281
296
|
// Honors the same MIXDOG_AGENT_TRACE_DISABLE gate as usage rows;
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
COMPACT_TYPE_SEMANTIC,
|
|
7
7
|
COMPACT_TYPE_RECALL_FASTTRACK,
|
|
8
8
|
CONTEXT_SHARE_RATIO,
|
|
9
|
-
DEFAULT_EFFECTIVE_CONTEXT_WINDOW_PERCENT,
|
|
10
9
|
COMPACT_TARGET_MIN_TOKENS,
|
|
11
10
|
} from '../compact.mjs';
|
|
12
11
|
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
@@ -116,13 +115,13 @@ function compactTargetTokensForBoundary(boundaryTokens) {
|
|
|
116
115
|
return Math.max(1, Math.min(boundary, Math.max(minTarget, byRatio)));
|
|
117
116
|
}
|
|
118
117
|
function defaultEffectiveContextWindowPercent(provider) {
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
118
|
+
// The session boundary is the model's full raw window. Headroom is applied
|
|
119
|
+
// by resolveSessionCompactPolicy instead: agent semantic sessions compact at
|
|
120
|
+
// the buffered trigger (default 90%), while main/user recall-fasttrack
|
|
121
|
+
// sessions compact on the boundary itself (100%).
|
|
122
122
|
void provider;
|
|
123
|
-
return
|
|
123
|
+
return 100;
|
|
124
124
|
}
|
|
125
|
-
const PROVIDER_SYNTHETIC_CONTEXT_DEFAULT = 1_000_000;
|
|
126
125
|
function providerRawContextWindow(info, catalogInfo) {
|
|
127
126
|
if (!info || typeof info !== 'object') return null;
|
|
128
127
|
const fromApiFields = positiveContextWindow(info.context_window)
|
|
@@ -130,17 +129,18 @@ function providerRawContextWindow(info, catalogInfo) {
|
|
|
130
129
|
if (fromApiFields) return fromApiFields;
|
|
131
130
|
const fromCache = positiveContextWindow(info.contextWindow)
|
|
132
131
|
|| positiveContextWindow(info.maxContextWindow);
|
|
133
|
-
if (!fromCache) return null;
|
|
134
132
|
const catalogWindow = positiveContextWindow(catalogInfo?.contextWindow)
|
|
135
133
|
|| positiveContextWindow(catalogInfo?.maxContextWindow)
|
|
136
134
|
|| positiveContextWindow(catalogInfo?.context_window)
|
|
137
135
|
|| positiveContextWindow(catalogInfo?.max_context_window);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
// Catalog/known metadata is authoritative for models present in the
|
|
137
|
+
// catalog. A stale provider cache can hold an outdated window (e.g. Opus
|
|
138
|
+
// 4.8 cached at 272k after its window grew to the catalog's 1M, or a
|
|
139
|
+
// synthetic 1M placeholder for a smaller real model); whenever the catalog
|
|
140
|
+
// disagrees with the cached snapshot, trust the catalog value rather than
|
|
141
|
+
// the cache. Only live API fields (handled above) outrank the catalog.
|
|
142
|
+
if (catalogWindow && fromCache !== catalogWindow) return catalogWindow;
|
|
143
|
+
return fromCache || null;
|
|
144
144
|
}
|
|
145
145
|
export function resolveSessionContextMeta(provider, model, seed = {}) {
|
|
146
146
|
const info = typeof provider?.getCachedModelInfo === 'function'
|
|
@@ -382,10 +382,18 @@ export function drainPendingMessages(sessionId) {
|
|
|
382
382
|
// first. Reversing this order (buffer before disk) delivered newer
|
|
383
383
|
// buffered sends ahead of older persisted ones after a restart.
|
|
384
384
|
const persisted = [...drainPersistedPendingMessages(sessionId), ...takeBufferedPendingMessages(sessionId)];
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
385
|
+
// Deferred completion/task notifications are dropped ONLY from the persisted
|
|
386
|
+
// (disk/buffer) path. Those are the entries a later session resume/restart
|
|
387
|
+
// would replay out-of-order into a future turn, once the in-memory queue is
|
|
388
|
+
// gone — discarding them there is the deliberate owner decision.
|
|
389
|
+
// Live in-memory completions (fallback/headless enqueues delivered within
|
|
390
|
+
// THIS process, e.g. the idle-resume kick that surfaces the model-visible
|
|
391
|
+
// body) are the intended payload and MUST survive the drain — filtering
|
|
392
|
+
// them here dropped the notification entirely. On a genuine resume the
|
|
393
|
+
// in-memory queue is empty, so keeping it only ever delivers live entries.
|
|
394
|
+
// Genuine user/steering messages carry no marker and are kept in order in
|
|
395
|
+
// both paths.
|
|
396
|
+
const memoryKept = memory;
|
|
389
397
|
const persistedKept = persisted.filter((m) => !isCompletionNotificationEntry(m));
|
|
390
398
|
const memoryVisible = modelVisiblePendingMessages(memoryKept);
|
|
391
399
|
const persistedVisible = modelVisiblePendingMessages(persistedKept);
|
|
@@ -394,9 +402,15 @@ export function drainPendingMessages(sessionId) {
|
|
|
394
402
|
const persistedTexts = persistedVisible.map(pendingMessageText);
|
|
395
403
|
const prefixMatches = memoryVisible.every((m, i) => persistedTexts[i] === pendingMessageText(m));
|
|
396
404
|
if (prefixMatches) return [...memoryVisible, ...persistedVisible.slice(memoryVisible.length)];
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
405
|
+
// The live in-memory queue holds the authoritative order — including the
|
|
406
|
+
// completion entries that were filtered out of the persisted path — so an
|
|
407
|
+
// interleaved [user, completion, user] must stay in that order and never be
|
|
408
|
+
// flattened to [user, user, completion] by rebuilding from persisted first.
|
|
409
|
+
// Start from the memory order and append only genuinely persisted-only
|
|
410
|
+
// entries; buffered duplicates of live sends are skipped by text.
|
|
411
|
+
const out = memoryVisible.slice();
|
|
412
|
+
const seen = new Set(out.map(pendingMessageText).filter(Boolean));
|
|
413
|
+
for (const m of persistedVisible) {
|
|
400
414
|
const text = pendingMessageText(m);
|
|
401
415
|
if (!text || seen.has(text)) continue;
|
|
402
416
|
out.push(m);
|
|
@@ -41,21 +41,17 @@ export function injectionBlockTargets(cmd) {
|
|
|
41
41
|
return targets;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const _unquoteSpansForPolicy = (s) => s.replace(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/g, (m) => m.slice(1, -1));
|
|
45
|
-
|
|
46
44
|
export function buildBashPolicyScanTargets(command) {
|
|
47
45
|
const cmd = String(command || '');
|
|
48
46
|
if (!cmd) return [];
|
|
49
47
|
const targets = [
|
|
50
48
|
cmd,
|
|
51
49
|
stripQuotedAndHeredoc(cmd),
|
|
52
|
-
_unquoteSpansForPolicy(cmd),
|
|
53
50
|
...extractShellCInner(cmd).map(stripQuotedAndHeredoc),
|
|
54
|
-
...extractShellCInner(cmd).map(_unquoteSpansForPolicy),
|
|
55
51
|
...injectionBlockTargets(cmd),
|
|
56
52
|
];
|
|
57
53
|
for (const inner of extractPowerShellCommandInner(cmd)) {
|
|
58
|
-
targets.push(inner, stripQuotedAndHeredoc(inner)
|
|
54
|
+
targets.push(inner, stripQuotedAndHeredoc(inner));
|
|
59
55
|
}
|
|
60
56
|
const psDecoded = decodePowerShellEncodedCommand(cmd);
|
|
61
57
|
if (psDecoded) targets.push(psDecoded);
|