mixdog 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -3
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +34 -56
- package/src/mixdog-session-runtime.mjs +710 -319
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -4
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1254
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +54 -3
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +187 -43
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +116 -7
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/seeds.mjs +1 -11
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2137 -750
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +146 -9
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +177 -100
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +312 -40
- package/src/tui/components/tool-output-format.test.mjs +180 -1
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +7324 -2393
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +354 -142
- package/src/tui/markdown/format-token.test.mjs +155 -17
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -647
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +154 -20
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -12,17 +12,16 @@ import { fetchOAuthUsageSnapshot } from '../providers/oauth-usage.mjs';
|
|
|
12
12
|
import {
|
|
13
13
|
recallFastTrackCompactMessages,
|
|
14
14
|
semanticCompactMessages,
|
|
15
|
+
pruneToolOutputsUnanchored,
|
|
16
|
+
effectiveBudget as compactEffectiveBudget,
|
|
15
17
|
compactTypeIsRecallFastTrack,
|
|
16
18
|
compactTypeIsSemantic,
|
|
17
19
|
normalizeCompactType,
|
|
18
20
|
DEFAULT_COMPACT_TYPE,
|
|
19
21
|
SUMMARY_PREFIX,
|
|
20
|
-
DEFAULT_COMPACTION_BUFFER_RATIO,
|
|
21
|
-
compactionBufferTokensForBoundary,
|
|
22
|
-
normalizeCompactionBufferRatio,
|
|
23
22
|
drainSessionCycle1,
|
|
24
23
|
} from './compact.mjs';
|
|
25
|
-
import { estimateMessagesTokens, estimateRequestReserveTokens, estimateTranscriptContextUsage } from './context-utils.mjs';
|
|
24
|
+
import { estimateMessagesTokens, estimateRequestReserveTokens, estimateTranscriptContextUsage, resolveCompactTriggerTokens } from './context-utils.mjs';
|
|
26
25
|
import { getMcpTools } from '../mcp/client.mjs';
|
|
27
26
|
import { getInternalTools, executeInternalTool } from '../internal-tools.mjs';
|
|
28
27
|
import { BUILTIN_TOOLS } from '../tools/builtin/builtin-tools.mjs';
|
|
@@ -41,7 +40,7 @@ import { updateJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
|
41
40
|
import { appendAgentTrace } from '../agent-trace.mjs';
|
|
42
41
|
import { isAgentOwner } from '../agent-owner.mjs';
|
|
43
42
|
import { maxMtimeRecursive } from '../cache-mtime.mjs';
|
|
44
|
-
import {
|
|
43
|
+
import { getHiddenAgent, getAgentInstructionDir, listHiddenAgentNames } from '../internal-agents.mjs';
|
|
45
44
|
import { DEFAULT_ACTIVITY_HEARTBEAT_MS } from '../stall-policy.mjs';
|
|
46
45
|
import {
|
|
47
46
|
buildGatewayLimits,
|
|
@@ -185,34 +184,34 @@ function _buildLeadMetaContext() {
|
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
186
|
|
|
188
|
-
// BP4-adjacent
|
|
189
|
-
//
|
|
190
|
-
const _roleSpecificCache = new Map(); //
|
|
191
|
-
function
|
|
187
|
+
// BP4-adjacent agent-specific data cache — keyed by agent. webhook / schedule
|
|
188
|
+
// agents each have their own scoped instruction set; other agents return ''.
|
|
189
|
+
const _roleSpecificCache = new Map(); // agent → { value, mtime }
|
|
190
|
+
function _buildAgentSpecific(currentAgent) {
|
|
192
191
|
if (!_rulesBuilder || typeof _rulesBuilder.buildAgentRoleSpecificContent !== 'function') return '';
|
|
193
|
-
if (!
|
|
192
|
+
if (!currentAgent) return '';
|
|
194
193
|
const PLUGIN_ROOT = mixdogRoot();
|
|
195
194
|
const DATA_DIR = resolvePluginData();
|
|
196
195
|
const RULES_DIR = join(PLUGIN_ROOT, 'rules');
|
|
197
|
-
const roleInstructionDir =
|
|
196
|
+
const roleInstructionDir = getAgentInstructionDir(currentAgent);
|
|
198
197
|
const mtime = maxMtimeRecursive([
|
|
199
198
|
join(RULES_DIR, 'shared'),
|
|
200
199
|
join(DATA_DIR, 'mixdog-config.json'),
|
|
201
200
|
join(DATA_DIR, 'webhooks'),
|
|
202
201
|
join(DATA_DIR, 'schedules'),
|
|
203
202
|
...(roleInstructionDir ? [join(DATA_DIR, roleInstructionDir)] : []),
|
|
204
|
-
join(PLUGIN_ROOT, 'defaults', '
|
|
203
|
+
join(PLUGIN_ROOT, 'defaults', 'agents.json'),
|
|
205
204
|
]);
|
|
206
|
-
const entry = _roleSpecificCache.get(
|
|
205
|
+
const entry = _roleSpecificCache.get(currentAgent);
|
|
207
206
|
if (entry && mtime <= entry.mtime) {
|
|
208
207
|
return entry.value;
|
|
209
208
|
}
|
|
210
209
|
try {
|
|
211
|
-
const built = _rulesBuilder.buildAgentRoleSpecificContent({ PLUGIN_ROOT, DATA_DIR,
|
|
212
|
-
_roleSpecificCache.set(
|
|
210
|
+
const built = _rulesBuilder.buildAgentRoleSpecificContent({ PLUGIN_ROOT, DATA_DIR, currentAgent });
|
|
211
|
+
_roleSpecificCache.set(currentAgent, { mtime, value: built });
|
|
213
212
|
return built;
|
|
214
213
|
} catch (e) {
|
|
215
|
-
throw new Error(`[session]
|
|
214
|
+
throw new Error(`[session] agent-specific rules build failed (agent: ${currentAgent}): ${e.message}`);
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
217
|
|
|
@@ -349,6 +348,7 @@ const AGENT_STRING_PERMISSION_READ_ALLOW = Object.freeze([
|
|
|
349
348
|
'explore',
|
|
350
349
|
'search',
|
|
351
350
|
'web_fetch',
|
|
351
|
+
'Skill',
|
|
352
352
|
]);
|
|
353
353
|
|
|
354
354
|
function stringToolPermissionAllowList(toolPermission) {
|
|
@@ -369,6 +369,7 @@ const AGENT_STRING_PERMISSION_READ_WRITE_ALLOW = Object.freeze([
|
|
|
369
369
|
'explore',
|
|
370
370
|
'search',
|
|
371
371
|
'web_fetch',
|
|
372
|
+
'Skill',
|
|
372
373
|
]);
|
|
373
374
|
|
|
374
375
|
function applyToolPermissionNarrowing(tools, toolPermission, warnRole = null) {
|
|
@@ -401,12 +402,14 @@ function applyToolPermissionNarrowing(tools, toolPermission, warnRole = null) {
|
|
|
401
402
|
return tools;
|
|
402
403
|
}
|
|
403
404
|
|
|
404
|
-
function
|
|
405
|
-
if (!
|
|
406
|
-
const key = String(
|
|
407
|
-
|
|
408
|
-
|
|
405
|
+
function recursiveWrapperToolNameForPublicAgent(agent) {
|
|
406
|
+
if (!agent) return null;
|
|
407
|
+
const key = String(agent).trim();
|
|
408
|
+
if (key === 'explore') return 'explore';
|
|
409
|
+
for (const hiddenName of listHiddenAgentNames()) {
|
|
410
|
+
const def = getHiddenAgent(hiddenName);
|
|
409
411
|
const invokedBy = typeof def?.invokedBy === 'string' ? def.invokedBy.trim() : '';
|
|
412
|
+
if (hiddenName === key && invokedBy) return invokedBy;
|
|
410
413
|
if (invokedBy && invokedBy === key) return invokedBy;
|
|
411
414
|
}
|
|
412
415
|
return null;
|
|
@@ -416,7 +419,7 @@ function finalizeSessionToolList(tools, {
|
|
|
416
419
|
schemaAllowedTools = null,
|
|
417
420
|
disallowedTools = null,
|
|
418
421
|
ownerIsAgent = false,
|
|
419
|
-
|
|
422
|
+
resolvedAgent = null,
|
|
420
423
|
} = {}) {
|
|
421
424
|
let out = Array.isArray(tools) ? tools : [];
|
|
422
425
|
const hasCallerAllow = Array.isArray(schemaAllowedTools);
|
|
@@ -429,7 +432,7 @@ function finalizeSessionToolList(tools, {
|
|
|
429
432
|
const denySet = new Set(callerDeny.map(n => n.toLowerCase()));
|
|
430
433
|
out = out.filter(t => !denySet.has(String(t?.name || '').toLowerCase()));
|
|
431
434
|
}
|
|
432
|
-
const recursiveDeny = ownerIsAgent ?
|
|
435
|
+
const recursiveDeny = ownerIsAgent ? recursiveWrapperToolNameForPublicAgent(resolvedAgent) : null;
|
|
433
436
|
if (recursiveDeny) {
|
|
434
437
|
const deny = recursiveDeny.toLowerCase();
|
|
435
438
|
out = out.filter(t => String(t?.name || '').toLowerCase() !== deny);
|
|
@@ -639,38 +642,9 @@ function providerNameOf(provider) {
|
|
|
639
642
|
if (typeof provider === 'string') return provider.toLowerCase();
|
|
640
643
|
return String(provider?.name || provider?.id || '').toLowerCase();
|
|
641
644
|
}
|
|
642
|
-
// Buffer-percent parsing trap: normalizeCompactionBufferRatio treats any value
|
|
643
|
-
// > 1 as a percent (n/100) and any value <= 1 as a literal ratio. That is wrong
|
|
644
|
-
// for percent-NAMED inputs: bufferPercent / bufferPct / *_BUFFER_PERCENT = 1
|
|
645
|
-
// means 1% (0.01), but the shared normalizer would read it as the literal ratio
|
|
646
|
-
// 1.0 (100%). Resolve percent-named and ratio-named inputs with the correct
|
|
647
|
-
// semantics here, before handing a finished ratio to the shared helper:
|
|
648
|
-
// percent inputs: n -> n/100 (1 -> 0.01, 10 -> 0.10)
|
|
649
|
-
// ratio inputs: n -> n>1 ? n/100 : n (0.01 -> 0.01, 10 -> 0.10 legacy)
|
|
650
|
-
function resolveBufferRatioCandidate(percentInputs, ratioInputs) {
|
|
651
|
-
for (const raw of percentInputs) {
|
|
652
|
-
const n = Number(raw);
|
|
653
|
-
if (Number.isFinite(n) && n > 0) return Math.min(1, n / 100);
|
|
654
|
-
}
|
|
655
|
-
for (const raw of ratioInputs) {
|
|
656
|
-
const n = Number(raw);
|
|
657
|
-
if (Number.isFinite(n) && n > 0) return n > 1 ? n / 100 : n;
|
|
658
|
-
}
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
function compactBufferRatioForConfig(cfg = {}) {
|
|
662
|
-
const resolved = resolveBufferRatioCandidate(
|
|
663
|
-
[cfg.bufferPercent, cfg.bufferPct, process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT],
|
|
664
|
-
[cfg.bufferRatio, cfg.bufferFraction, process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO],
|
|
665
|
-
);
|
|
666
|
-
return normalizeCompactionBufferRatio(resolved, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
667
|
-
}
|
|
668
|
-
function compactBufferRatioForSession(session) {
|
|
669
|
-
return compactBufferRatioForConfig(session?.compaction || {});
|
|
670
|
-
}
|
|
671
645
|
// Carry the percent/ratio-named buffer config from a compaction config object
|
|
672
|
-
// onto session.compaction so
|
|
673
|
-
//
|
|
646
|
+
// onto session.compaction so the shared compact-policy parser honors configured
|
|
647
|
+
// buffer
|
|
674
648
|
// percent/ratio. Only finite positive values are copied; absent fields stay
|
|
675
649
|
// undefined so the default-ratio fallback still applies.
|
|
676
650
|
function preserveBufferConfigFields(cfg = {}) {
|
|
@@ -681,69 +655,6 @@ function preserveBufferConfigFields(cfg = {}) {
|
|
|
681
655
|
}
|
|
682
656
|
return out;
|
|
683
657
|
}
|
|
684
|
-
const LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
685
|
-
function isPersistedZeroBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
686
|
-
const boundary = positiveContextWindow(boundaryTokens);
|
|
687
|
-
if (!boundary) return false;
|
|
688
|
-
if (positiveContextWindow(process.env.MIXDOG_AGENT_COMPACT_BUFFER_TOKENS)) return false;
|
|
689
|
-
if (Number.isFinite(Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT)) && Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT) > 0) return false;
|
|
690
|
-
if (Number.isFinite(Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO)) && Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO) > 0) return false;
|
|
691
|
-
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
692
|
-
const n = Number(cfg?.[key]);
|
|
693
|
-
if (Number.isFinite(n) && n > 0) return false;
|
|
694
|
-
}
|
|
695
|
-
const ratio = Number(cfg?.bufferRatio);
|
|
696
|
-
if (Number.isFinite(ratio) && ratio > 0) return false;
|
|
697
|
-
const explicitTokens = Number(cfg?.bufferTokens ?? cfg?.buffer);
|
|
698
|
-
if (!Number.isFinite(explicitTokens) || explicitTokens !== 0) return false;
|
|
699
|
-
return true;
|
|
700
|
-
}
|
|
701
|
-
function isLegacyDefaultBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
702
|
-
const boundary = positiveContextWindow(boundaryTokens);
|
|
703
|
-
if (!boundary) return false;
|
|
704
|
-
if (positiveContextWindow(process.env.MIXDOG_AGENT_COMPACT_BUFFER_TOKENS)) return false;
|
|
705
|
-
if (Number.isFinite(Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT)) && Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT) > 0) return false;
|
|
706
|
-
if (Number.isFinite(Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO)) && Number(process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO) > 0) return false;
|
|
707
|
-
// Percent/fraction-named fields are treated as operator config. The legacy
|
|
708
|
-
// default telemetry always persisted bufferTokens + bufferRatio after a
|
|
709
|
-
// check/compact pass, so only that shape is migrated away.
|
|
710
|
-
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
711
|
-
const n = Number(cfg?.[key]);
|
|
712
|
-
if (Number.isFinite(n) && n > 0) return false;
|
|
713
|
-
}
|
|
714
|
-
const explicitTokens = positiveContextWindow(cfg?.bufferTokens ?? cfg?.buffer);
|
|
715
|
-
const ratio = Number(cfg?.bufferRatio);
|
|
716
|
-
if (!explicitTokens || !Number.isFinite(ratio) || Math.abs(ratio - LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO) > 1e-9) return false;
|
|
717
|
-
const expectedTokens = Math.floor(boundary * LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
718
|
-
const cfgBoundary = positiveContextWindow(cfg?.boundaryTokens);
|
|
719
|
-
const cfgTrigger = positiveContextWindow(cfg?.triggerTokens);
|
|
720
|
-
return explicitTokens === expectedTokens
|
|
721
|
-
|| (cfgBoundary === boundary && cfgTrigger > 0 && explicitTokens === Math.max(0, boundary - cfgTrigger));
|
|
722
|
-
}
|
|
723
|
-
function compactBufferConfigForBoundary(cfg = {}, boundaryTokens = 0) {
|
|
724
|
-
const base = cfg || {};
|
|
725
|
-
if (!isLegacyDefaultBufferTelemetry(base, boundaryTokens)
|
|
726
|
-
&& !isPersistedZeroBufferTelemetry(base, boundaryTokens)) {
|
|
727
|
-
return base;
|
|
728
|
-
}
|
|
729
|
-
return {
|
|
730
|
-
...base,
|
|
731
|
-
bufferTokens: null,
|
|
732
|
-
buffer: null,
|
|
733
|
-
bufferRatio: null,
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
function compactBufferTokensForSession(session, boundaryTokens) {
|
|
737
|
-
const cfg = compactBufferConfigForBoundary(session?.compaction || {}, boundaryTokens);
|
|
738
|
-
const explicit = positiveContextWindow(cfg.bufferTokens ?? cfg.buffer)
|
|
739
|
-
|| positiveContextWindow(process.env.MIXDOG_AGENT_COMPACT_BUFFER_TOKENS)
|
|
740
|
-
|| 0;
|
|
741
|
-
return compactionBufferTokensForBoundary(boundaryTokens, {
|
|
742
|
-
explicitTokens: explicit,
|
|
743
|
-
ratio: compactBufferRatioForConfig(cfg),
|
|
744
|
-
maxRatio: 0.25,
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
658
|
const COMPACT_TARGET_RATIO = 0.02;
|
|
748
659
|
const COMPACT_TARGET_MIN_TOKENS = 4_000;
|
|
749
660
|
const COMPACT_TARGET_MAX_TOKENS = 16_000;
|
|
@@ -774,15 +685,32 @@ function defaultEffectiveContextWindowPercent(provider) {
|
|
|
774
685
|
// capacity so /context, the TUI statusline, and gateway telemetry agree.
|
|
775
686
|
return 90;
|
|
776
687
|
}
|
|
688
|
+
const PROVIDER_SYNTHETIC_CONTEXT_DEFAULT = 1_000_000;
|
|
689
|
+
function providerRawContextWindow(info, catalogInfo) {
|
|
690
|
+
if (!info || typeof info !== 'object') return null;
|
|
691
|
+
const fromApiFields = positiveContextWindow(info.context_window)
|
|
692
|
+
|| positiveContextWindow(info.max_context_window);
|
|
693
|
+
if (fromApiFields) return fromApiFields;
|
|
694
|
+
const fromCache = positiveContextWindow(info.contextWindow)
|
|
695
|
+
|| positiveContextWindow(info.maxContextWindow);
|
|
696
|
+
if (!fromCache) return null;
|
|
697
|
+
const catalogWindow = positiveContextWindow(catalogInfo?.contextWindow)
|
|
698
|
+
|| positiveContextWindow(catalogInfo?.maxContextWindow)
|
|
699
|
+
|| positiveContextWindow(catalogInfo?.context_window)
|
|
700
|
+
|| positiveContextWindow(catalogInfo?.max_context_window);
|
|
701
|
+
if (fromCache === PROVIDER_SYNTHETIC_CONTEXT_DEFAULT
|
|
702
|
+
&& catalogWindow
|
|
703
|
+
&& fromCache !== catalogWindow) {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
return fromCache;
|
|
707
|
+
}
|
|
777
708
|
function resolveSessionContextMeta(provider, model, seed = {}) {
|
|
778
709
|
const info = typeof provider?.getCachedModelInfo === 'function'
|
|
779
710
|
? provider.getCachedModelInfo(model)
|
|
780
711
|
: null;
|
|
781
712
|
const catalogInfo = getModelMetadataSync(model, providerNameOf(provider));
|
|
782
|
-
const rawContextWindow =
|
|
783
|
-
|| positiveContextWindow(info?.maxContextWindow)
|
|
784
|
-
|| positiveContextWindow(info?.context_window)
|
|
785
|
-
|| positiveContextWindow(info?.max_context_window)
|
|
713
|
+
const rawContextWindow = providerRawContextWindow(info, catalogInfo)
|
|
786
714
|
|| positiveContextWindow(catalogInfo?.contextWindow)
|
|
787
715
|
|| positiveContextWindow(catalogInfo?.maxContextWindow)
|
|
788
716
|
|| positiveContextWindow(catalogInfo?.context_window)
|
|
@@ -841,16 +769,7 @@ function resolveSessionContextMeta(provider, model, seed = {}) {
|
|
|
841
769
|
};
|
|
842
770
|
}
|
|
843
771
|
function compactTriggerForSession(session, boundaryTokens) {
|
|
844
|
-
|
|
845
|
-
if (!boundary) return null;
|
|
846
|
-
const autoLimit = positiveContextWindow(session?.autoCompactTokenLimit ?? session?.compaction?.autoCompactTokenLimit);
|
|
847
|
-
// Only honor an explicit auto-compact limit that sits STRICTLY BELOW the
|
|
848
|
-
// boundary. A persisted value == boundary (or >=) is a legacy derived
|
|
849
|
-
// full-window artifact; honoring it collapses the compaction buffer to 0,
|
|
850
|
-
// so fall through to the default boundary trigger instead.
|
|
851
|
-
if (autoLimit && autoLimit < boundary) return Math.max(1, autoLimit);
|
|
852
|
-
const buffer = compactBufferTokensForSession(session, boundary);
|
|
853
|
-
return Math.max(1, boundary - buffer);
|
|
772
|
+
return resolveCompactTriggerTokens(session, boundaryTokens);
|
|
854
773
|
}
|
|
855
774
|
// Test-only exports for the legacy auto-compact-limit migration + buffer-config
|
|
856
775
|
// preservation (see scripts/compact-trigger-migration-smoke.mjs).
|
|
@@ -904,10 +823,12 @@ function addCompactUsageToSession(session, usage) {
|
|
|
904
823
|
const outputTokens = usage.outputTokens || 0;
|
|
905
824
|
const cachedTokens = usage.cachedTokens || 0;
|
|
906
825
|
const cacheWriteTokens = usage.cacheWriteTokens || 0;
|
|
826
|
+
const uncachedInputTokens = uncachedInputTokensForProvider(session.provider, inputTokens, cachedTokens, cacheWriteTokens);
|
|
907
827
|
session.totalInputTokens = (session.totalInputTokens || 0) + inputTokens;
|
|
908
828
|
session.totalOutputTokens = (session.totalOutputTokens || 0) + outputTokens;
|
|
909
829
|
session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + cachedTokens;
|
|
910
830
|
session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + cacheWriteTokens;
|
|
831
|
+
session.totalUncachedInputTokens = (session.totalUncachedInputTokens || 0) + uncachedInputTokens;
|
|
911
832
|
session.tokensCumulative = (session.tokensCumulative || 0) + inputTokens + outputTokens;
|
|
912
833
|
}
|
|
913
834
|
async function runRecallFastTrackForSession(session, messages, opts = {}) {
|
|
@@ -977,6 +898,18 @@ async function runRecallFastTrackForSession(session, messages, opts = {}) {
|
|
|
977
898
|
}
|
|
978
899
|
return { query, querySha, recallText: [`session_id=${sessionId}`, cycle1Text, recallText].map(v => String(v || '').trim()).filter(Boolean).join('\n\n') };
|
|
979
900
|
}
|
|
901
|
+
// Element-identity change detection (mirrors loop.mjs messagesArrayChanged): two
|
|
902
|
+
// arrays are "unchanged" only when same length AND every slot is the same object
|
|
903
|
+
// reference. Used to reject a no-op prune (which returns a fresh array whose
|
|
904
|
+
// elements are the untouched originals) from being accepted as a recovery.
|
|
905
|
+
function messagesChanged(before, after) {
|
|
906
|
+
if (!Array.isArray(before) || !Array.isArray(after)) return before !== after;
|
|
907
|
+
if (before.length !== after.length) return true;
|
|
908
|
+
for (let i = 0; i < before.length; i += 1) {
|
|
909
|
+
if (before[i] !== after[i]) return true;
|
|
910
|
+
}
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
980
913
|
async function runSessionCompaction(session, opts = {}) {
|
|
981
914
|
if (!session || session.closed === true) return null;
|
|
982
915
|
const mode = opts.mode === 'auto' ? 'auto' : 'manual';
|
|
@@ -991,7 +924,16 @@ async function runSessionCompaction(session, opts = {}) {
|
|
|
991
924
|
if (force) throw new Error('compact: no context window is available for this session');
|
|
992
925
|
return null;
|
|
993
926
|
}
|
|
994
|
-
|
|
927
|
+
// Reserve must mirror loop.mjs (buildCompactPolicy): request reserve (tool
|
|
928
|
+
// schema) PLUS the configured reserve (session.compaction.reservedTokens or
|
|
929
|
+
// MIXDOG_AGENT_COMPACT_RESERVED_TOKENS env). The old request-only value left
|
|
930
|
+
// the manual / auto-clear compact budget without the configured headroom the
|
|
931
|
+
// loop path reserves, so a compacted transcript could overflow on next send.
|
|
932
|
+
const requestReserveTokens = estimateRequestReserveTokens(session.tools || []);
|
|
933
|
+
const configuredReserveTokens = positiveContextWindow(session.compaction?.reservedTokens)
|
|
934
|
+
|| positiveContextWindow(process.env.MIXDOG_AGENT_COMPACT_RESERVED_TOKENS)
|
|
935
|
+
|| 0;
|
|
936
|
+
const reserveTokens = requestReserveTokens + configuredReserveTokens;
|
|
995
937
|
const beforeMessageTokens = estimateMessagesTokens(messages);
|
|
996
938
|
const triggerTokens = compactTriggerForSession(session, boundary)
|
|
997
939
|
|| positiveContextWindow(session.compaction?.triggerTokens)
|
|
@@ -1041,7 +983,11 @@ async function runSessionCompaction(session, opts = {}) {
|
|
|
1041
983
|
recallText: recallPayload.recallText,
|
|
1042
984
|
query: recallPayload.query,
|
|
1043
985
|
querySha: recallPayload.querySha,
|
|
1044
|
-
|
|
986
|
+
// Ingest just ran on the live transcript, so an empty recall dump
|
|
987
|
+
// means the memory pipeline is broken — do NOT erase history
|
|
988
|
+
// behind an empty summary shell. Empty recall now throws and is
|
|
989
|
+
// handled by the semantic fallback below (or recorded failure).
|
|
990
|
+
allowEmptyRecall: false,
|
|
1045
991
|
tailTurns: positiveContextWindow(session.compaction?.tailTurns) || 2,
|
|
1046
992
|
keepTokens: positiveContextWindow(session.compaction?.keepTokens ?? session.compaction?.keep?.tokens),
|
|
1047
993
|
preserveRecentTokens: positiveContextWindow(session.compaction?.preserveRecentTokens),
|
|
@@ -1055,6 +1001,48 @@ async function runSessionCompaction(session, opts = {}) {
|
|
|
1055
1001
|
try {
|
|
1056
1002
|
process.stderr.write(`[session] recall-fasttrack ${mode} compact failed (sess=${session.id || 'unknown'}): ${err?.message || err}\n`);
|
|
1057
1003
|
} catch { /* best-effort */ }
|
|
1004
|
+
// Degraded-compact fallback: recall-fasttrack failed (empty recall,
|
|
1005
|
+
// ingest error, fit failure). Before recording a hard failure, try
|
|
1006
|
+
// the semantic path once so auto-clear/manual compaction still makes
|
|
1007
|
+
// progress WITHOUT shipping an empty-recall summary. History is only
|
|
1008
|
+
// replaced when the semantic summary actually succeeds.
|
|
1009
|
+
if (semanticCompactionEnabledForSession(session)
|
|
1010
|
+
&& provider && typeof provider.send === 'function') {
|
|
1011
|
+
try {
|
|
1012
|
+
semanticCompactResult = await semanticCompactMessages(
|
|
1013
|
+
provider,
|
|
1014
|
+
messages,
|
|
1015
|
+
opts.model || session.model,
|
|
1016
|
+
budget,
|
|
1017
|
+
{
|
|
1018
|
+
reserveTokens,
|
|
1019
|
+
providerName: session.provider || provider?.name || null,
|
|
1020
|
+
sessionId: opts.sessionId || session.id || null,
|
|
1021
|
+
signal: opts.signal || null,
|
|
1022
|
+
promptCacheKey: session.promptCacheKey || null,
|
|
1023
|
+
providerCacheKey: session.promptCacheKey || null,
|
|
1024
|
+
timeoutMs: positiveContextWindow(session.compaction?.timeoutMs) || 30_000,
|
|
1025
|
+
tailTurns: positiveContextWindow(session.compaction?.tailTurns) || 2,
|
|
1026
|
+
keepTokens: positiveContextWindow(session.compaction?.keepTokens ?? session.compaction?.keep?.tokens),
|
|
1027
|
+
preserveRecentTokens: positiveContextWindow(session.compaction?.preserveRecentTokens),
|
|
1028
|
+
force: true,
|
|
1029
|
+
},
|
|
1030
|
+
);
|
|
1031
|
+
if (Array.isArray(semanticCompactResult?.messages)) {
|
|
1032
|
+
compacted = semanticCompactResult.messages;
|
|
1033
|
+
compactError = null;
|
|
1034
|
+
addCompactUsageToSession(session, semanticCompactResult.usage);
|
|
1035
|
+
try {
|
|
1036
|
+
process.stderr.write(`[session] degraded compact: recall-fasttrack failed, semantic fallback succeeded (sess=${session.id || 'unknown'}, mode=${mode})\n`);
|
|
1037
|
+
} catch { /* best-effort */ }
|
|
1038
|
+
}
|
|
1039
|
+
} catch (fallbackErr) {
|
|
1040
|
+
semanticCompactError = fallbackErr;
|
|
1041
|
+
try {
|
|
1042
|
+
process.stderr.write(`[session] degraded compact: semantic fallback also failed (sess=${session.id || 'unknown'}): ${fallbackErr?.message || fallbackErr}\n`);
|
|
1043
|
+
} catch { /* best-effort */ }
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1058
1046
|
}
|
|
1059
1047
|
} else if (compactTypeIsSemantic(compactType)) {
|
|
1060
1048
|
try {
|
|
@@ -1098,6 +1086,32 @@ async function runSessionCompaction(session, opts = {}) {
|
|
|
1098
1086
|
if (!compacted && !compactError) {
|
|
1099
1087
|
compactError = new Error(`${compactType} compact produced no messages`);
|
|
1100
1088
|
}
|
|
1089
|
+
// Anchor-independent prune safety net (mirror loop.mjs compact catch): when a
|
|
1090
|
+
// non-recall (semantic) compact failed, try one non-LLM prune that needs no
|
|
1091
|
+
// user anchor before recording failure, so Lead manual / auto-clear paths
|
|
1092
|
+
// recover the same transcripts the loop path does. Gated off the recall
|
|
1093
|
+
// path — a recall failure keeps its original contract (no silent prune).
|
|
1094
|
+
if (!compacted && !recallFastTrackError) {
|
|
1095
|
+
try {
|
|
1096
|
+
const acceptThreshold = compactEffectiveBudget(budget, { reserveTokens });
|
|
1097
|
+
const salvaged = pruneToolOutputsUnanchored(messages, budget, { reserveTokens });
|
|
1098
|
+
// pruneToolOutputsUnanchored ALWAYS returns a fresh reconciled array
|
|
1099
|
+
// (never the input identity), so `salvaged !== messages` is always
|
|
1100
|
+
// true and cannot detect a no-op. Compare by element identity so a
|
|
1101
|
+
// transcript that already fit (nothing pruned) is NOT falsely accepted
|
|
1102
|
+
// as a recovery — that would clear compactError and unconditionally
|
|
1103
|
+
// invalidate providerState for an unchanged transcript.
|
|
1104
|
+
if (Array.isArray(salvaged)
|
|
1105
|
+
&& messagesChanged(messages, salvaged)
|
|
1106
|
+
&& estimateMessagesTokens(salvaged) <= acceptThreshold) {
|
|
1107
|
+
compacted = salvaged;
|
|
1108
|
+
compactError = null;
|
|
1109
|
+
try {
|
|
1110
|
+
process.stderr.write(`[session] compact fallback prune recovered (sess=${session.id || 'unknown'}, mode=${mode})\n`);
|
|
1111
|
+
} catch { /* best-effort */ }
|
|
1112
|
+
}
|
|
1113
|
+
} catch { /* fall through to failure record */ }
|
|
1114
|
+
}
|
|
1101
1115
|
if (!compacted) {
|
|
1102
1116
|
const now = Date.now();
|
|
1103
1117
|
session.compaction = {
|
|
@@ -1506,7 +1520,7 @@ async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
1506
1520
|
// Preset shape: { name, provider, model, effort?, fast?, tools? }
|
|
1507
1521
|
//
|
|
1508
1522
|
// Agent Runtime integration:
|
|
1509
|
-
// opts.taskType / opts.
|
|
1523
|
+
// opts.taskType / opts.agent / opts.profileId — enables profile-aware routing.
|
|
1510
1524
|
// Rule-based SmartRouter resolves these synchronously; the resolved
|
|
1511
1525
|
// profile controls context filtering (skip.skills/memory/etc) and cache
|
|
1512
1526
|
// strategy. If no rule matches, falls back to classic preset behavior.
|
|
@@ -1519,13 +1533,13 @@ export function createSession(opts) {
|
|
|
1519
1533
|
// --- Agent Runtime profile resolution (best-effort, sync) ---
|
|
1520
1534
|
let profile = opts.profile || null;
|
|
1521
1535
|
let providerCacheOpts = opts.providerCacheOpts || null;
|
|
1522
|
-
if (!profile && (opts.taskType || opts.
|
|
1536
|
+
if (!profile && (opts.taskType || opts.agent || opts.profileId)) {
|
|
1523
1537
|
const agentRuntime = getAgentRuntimeSync();
|
|
1524
1538
|
if (agentRuntime) {
|
|
1525
1539
|
try {
|
|
1526
1540
|
const resolved = agentRuntime.resolveSync({
|
|
1527
1541
|
taskType: opts.taskType,
|
|
1528
|
-
|
|
1542
|
+
agent: opts.agent,
|
|
1529
1543
|
profileId: opts.profileId,
|
|
1530
1544
|
preset: presetObj?.name || (typeof opts.preset === 'string' ? opts.preset : null),
|
|
1531
1545
|
provider: opts.provider || presetObj?.provider,
|
|
@@ -1567,25 +1581,26 @@ export function createSession(opts) {
|
|
|
1567
1581
|
const id = `sess_${process.pid}_${nextId++}_${Date.now()}_${randomBytes(16).toString('hex')}`;
|
|
1568
1582
|
const messages = [];
|
|
1569
1583
|
const ownerIsAgent = isAgentOwner(opts.owner);
|
|
1570
|
-
const
|
|
1571
|
-
const
|
|
1572
|
-
const
|
|
1573
|
-
// Skill schema is fixed
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
|
|
1584
|
+
const resolvedAgent = opts.agent || opts.role || profile?.taskType || null;
|
|
1585
|
+
const hiddenAgent = getHiddenAgent(resolvedAgent);
|
|
1586
|
+
const isRetrievalAgent = hiddenAgent?.kind === 'retrieval';
|
|
1587
|
+
// Skill schema is fixed for public agent sessions, but hidden retrieval /
|
|
1588
|
+
// maintenance roles are deliberately narrowed away from the Skill tool.
|
|
1589
|
+
// Do not leak a Skill manifest into those hidden prompts when no Skill()
|
|
1590
|
+
// loader is available.
|
|
1591
|
+
const skills = (opts.skipSkills || hiddenAgent) ? [] : collectSkillsCached(opts.cwd);
|
|
1577
1592
|
|
|
1578
1593
|
// BP1 is shared tool policy (+ compact skill manifest in compose). BP2 is
|
|
1579
1594
|
// role/system rules. User-defined schedules/webhooks ride as normal user
|
|
1580
1595
|
// context below so event data does not rewrite BP3 memory/meta.
|
|
1581
|
-
const
|
|
1582
|
-
const agentRulesProfile =
|
|
1596
|
+
const agentRulesAgent = opts.agent || opts.role || profile?.taskType || null;
|
|
1597
|
+
const agentRulesProfile = isRetrievalAgent ? 'retrieval' : 'full';
|
|
1583
1598
|
const skipAgentRules = opts.skipAgentRules === true;
|
|
1584
1599
|
// Retrieval roles already inject a compact # Tool Use via BP2; skip the full BP1 shared tool policy to avoid duplicating it.
|
|
1585
|
-
const injectedRules = (skipAgentRules ||
|
|
1600
|
+
const injectedRules = (skipAgentRules || isRetrievalAgent) ? '' : _buildSharedRules();
|
|
1586
1601
|
const roleRules = skipAgentRules ? '' : (ownerIsAgent ? _buildAgentRules(agentRulesProfile) : _buildLeadRules());
|
|
1587
1602
|
const metaContext = skipAgentRules ? '' : (ownerIsAgent ? '' : _buildLeadMetaContext());
|
|
1588
|
-
const roleSpecific = ownerIsAgent && !skipAgentRules ?
|
|
1603
|
+
const roleSpecific = ownerIsAgent && !skipAgentRules ? _buildAgentSpecific(agentRulesAgent) : '';
|
|
1589
1604
|
// Agent sessions must not inherit role/profile/preset tool narrowing: Pool
|
|
1590
1605
|
// B and Pool C share one bit-identical tool schema to maximize provider
|
|
1591
1606
|
// prefix reuse, and permission differences are enforced only at call time. Raw
|
|
@@ -1612,7 +1627,7 @@ export function createSession(opts) {
|
|
|
1612
1627
|
// fail closed (zero tools) rather than silently falling back to the full
|
|
1613
1628
|
// preset, which would grant the role more surface than declared.
|
|
1614
1629
|
if (ownerIsAgent) {
|
|
1615
|
-
toolsForRouting = applyToolPermissionNarrowing(toolsForRouting, toolPermission, opts.
|
|
1630
|
+
toolsForRouting = applyToolPermissionNarrowing(toolsForRouting, toolPermission, opts.agent || null);
|
|
1616
1631
|
}
|
|
1617
1632
|
|
|
1618
1633
|
const { baseRules, stableSystemContext, sessionMarker, volatileTail } = composeSystemPrompt({
|
|
@@ -1622,7 +1637,7 @@ export function createSession(opts) {
|
|
|
1622
1637
|
metaContext: metaContext || undefined,
|
|
1623
1638
|
skipRoleCatalog: !ownerIsAgent,
|
|
1624
1639
|
profile: profile || undefined,
|
|
1625
|
-
|
|
1640
|
+
agent: resolvedAgent,
|
|
1626
1641
|
workflowContext: opts.workflowContext || null,
|
|
1627
1642
|
workspaceContext: opts.workspaceContext || null,
|
|
1628
1643
|
coreMemoryContext: opts.coreMemoryContext || null,
|
|
@@ -1678,17 +1693,17 @@ export function createSession(opts) {
|
|
|
1678
1693
|
const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
|
|
1679
1694
|
const tools = finalizeSessionToolList(toolsForRouting, {
|
|
1680
1695
|
schemaAllowedTools: hasCallerAllow ? opts.schemaAllowedTools : null,
|
|
1681
|
-
disallowedTools: opts.disallowedTools,
|
|
1696
|
+
disallowedTools: hiddenAgent ? [...(Array.isArray(opts.disallowedTools) ? opts.disallowedTools : []), 'Skill'] : opts.disallowedTools,
|
|
1682
1697
|
ownerIsAgent,
|
|
1683
|
-
|
|
1698
|
+
resolvedAgent,
|
|
1684
1699
|
});
|
|
1685
1700
|
|
|
1686
1701
|
// Unified-shard policy — no broad role-specific schema filter. Keep
|
|
1687
1702
|
// agent schemas shared unless a hidden-role schema profile explicitly
|
|
1688
1703
|
// passes schemaAllowedTools for a small specialist; broad role
|
|
1689
1704
|
// whitelists would fragment the cache shard.
|
|
1690
|
-
if (
|
|
1691
|
-
process.stderr.write(`[session]
|
|
1705
|
+
if (resolvedAgent && process.env.MIXDOG_DEBUG_SESSION_LOG) {
|
|
1706
|
+
process.stderr.write(`[session] agent=${resolvedAgent} permission=${permission || 'full'} toolPermission=${toolPermission || 'full'} tools=${tools.length}\n`);
|
|
1692
1707
|
}
|
|
1693
1708
|
const contextMeta = resolveSessionContextMeta(provider, modelName);
|
|
1694
1709
|
const workflowMeta = opts.workflow && typeof opts.workflow === 'object' && String(opts.workflow.id || '').trim()
|
|
@@ -1758,7 +1773,6 @@ export function createSession(opts) {
|
|
|
1758
1773
|
// agent sessions past RUNNING_STALL_MS.
|
|
1759
1774
|
lastUsedAt: Date.now(),
|
|
1760
1775
|
tokensCumulative: 0,
|
|
1761
|
-
role: opts.role || null,
|
|
1762
1776
|
taskType: opts.taskType || null,
|
|
1763
1777
|
maxLoopIterations: Number.isFinite(opts.maxLoopIterations) ? opts.maxLoopIterations : null,
|
|
1764
1778
|
// Agent tag (auto worker{n} on spawn) persisted so the forked status
|
|
@@ -2203,6 +2217,18 @@ export function usageMetricsIdempotencyKey(sessionId, session, delta = {}) {
|
|
|
2203
2217
|
return `${sessionId}:${turnId}:${epoch}:${delta.iterationIndex}:${source}`;
|
|
2204
2218
|
}
|
|
2205
2219
|
|
|
2220
|
+
function uncachedInputTokensForProvider(provider, inputTokens, cachedReadTokens = 0, cacheWriteTokens = 0) {
|
|
2221
|
+
const input = Number(inputTokens) || 0;
|
|
2222
|
+
if (input <= 0) return 0;
|
|
2223
|
+
// Anthropic-style providers report input_tokens excluding cache reads; OpenAI
|
|
2224
|
+
// Responses/Gemini-style providers report input_tokens inclusive of cached
|
|
2225
|
+
// prefix tokens. Keep both views so UI can show the real context footprint
|
|
2226
|
+
// and the fresh/new token portion without mistaking cache hits for a cache
|
|
2227
|
+
// break.
|
|
2228
|
+
if (providerInputExcludesCache(provider)) return input + (Number(cacheWriteTokens) || 0);
|
|
2229
|
+
return Math.max(input - (Number(cachedReadTokens) || 0) - (Number(cacheWriteTokens) || 0), 0);
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2206
2232
|
/**
|
|
2207
2233
|
* Apply terminal ask usage to session totals. Skips lifetime totals when incremental
|
|
2208
2234
|
* per-iteration persistence already counted this turn (askSession path).
|
|
@@ -2211,23 +2237,38 @@ export function applyAskTerminalUsageTotals(session, result, options = {}) {
|
|
|
2211
2237
|
if (!session || !result?.usage) return;
|
|
2212
2238
|
const skipTotals = options.skipTotalsIfIncremental === true;
|
|
2213
2239
|
if (!skipTotals) {
|
|
2214
|
-
|
|
2215
|
-
|
|
2240
|
+
const inputTokens = result.usage.inputTokens || 0;
|
|
2241
|
+
const outputTokens = result.usage.outputTokens || 0;
|
|
2242
|
+
const cachedTokens = result.usage.cachedTokens || 0;
|
|
2243
|
+
const cacheWriteTokens = result.usage.cacheWriteTokens || 0;
|
|
2244
|
+
const uncachedInputTokens = uncachedInputTokensForProvider(session.provider, inputTokens, cachedTokens, cacheWriteTokens);
|
|
2245
|
+
session.totalInputTokens = (session.totalInputTokens || 0) + inputTokens;
|
|
2246
|
+
session.totalOutputTokens = (session.totalOutputTokens || 0) + outputTokens;
|
|
2216
2247
|
session.tokensCumulative = (session.tokensCumulative || 0)
|
|
2217
|
-
+
|
|
2218
|
-
+
|
|
2219
|
-
session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) +
|
|
2220
|
-
session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) +
|
|
2248
|
+
+ inputTokens
|
|
2249
|
+
+ outputTokens;
|
|
2250
|
+
session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + cachedTokens;
|
|
2251
|
+
session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + cacheWriteTokens;
|
|
2252
|
+
session.totalUncachedInputTokens = (session.totalUncachedInputTokens || 0) + uncachedInputTokens;
|
|
2221
2253
|
}
|
|
2222
2254
|
const _lastTurn = result.lastTurnUsage || result.usage || {};
|
|
2223
|
-
|
|
2255
|
+
const _lastInputTokens = _lastTurn.inputTokens || 0;
|
|
2256
|
+
const _lastCachedReadTokens = _lastTurn.cachedTokens || 0;
|
|
2257
|
+
const _lastCacheWriteTokens = _lastTurn.cacheWriteTokens || 0;
|
|
2258
|
+
session.lastInputTokens = _lastInputTokens;
|
|
2224
2259
|
session.lastOutputTokens = _lastTurn.outputTokens || 0;
|
|
2225
|
-
session.lastCachedReadTokens =
|
|
2226
|
-
session.lastCacheWriteTokens =
|
|
2260
|
+
session.lastCachedReadTokens = _lastCachedReadTokens;
|
|
2261
|
+
session.lastCacheWriteTokens = _lastCacheWriteTokens;
|
|
2262
|
+
session.lastUncachedInputTokens = uncachedInputTokensForProvider(
|
|
2263
|
+
session.provider,
|
|
2264
|
+
_lastInputTokens,
|
|
2265
|
+
_lastCachedReadTokens,
|
|
2266
|
+
_lastCacheWriteTokens,
|
|
2267
|
+
);
|
|
2227
2268
|
const _inputExcludesCache = providerInputExcludesCache(session.provider);
|
|
2228
2269
|
session.lastContextTokens = _inputExcludesCache
|
|
2229
|
-
?
|
|
2230
|
-
:
|
|
2270
|
+
? _lastInputTokens + _lastCachedReadTokens + _lastCacheWriteTokens
|
|
2271
|
+
: _lastInputTokens;
|
|
2231
2272
|
session.lastContextTokensUpdatedAt = Date.now();
|
|
2232
2273
|
session.lastContextTokensStaleAfterCompact = false;
|
|
2233
2274
|
}
|
|
@@ -2257,6 +2298,9 @@ export async function persistIterationMetrics(delta) {
|
|
|
2257
2298
|
seen.add(ikey);
|
|
2258
2299
|
if (!isReplay) {
|
|
2259
2300
|
if (runtimeEntry) runtimeEntry.usageMetricsTurnIncremental = true;
|
|
2301
|
+
const deltaUncachedInput = delta.deltaUncachedInput != null
|
|
2302
|
+
? Number(delta.deltaUncachedInput) || 0
|
|
2303
|
+
: uncachedInputTokensForProvider(session.provider, deltaInput, deltaCachedRead, deltaCacheWrite);
|
|
2260
2304
|
session.totalInputTokens = (session.totalInputTokens || 0) + (deltaInput || 0);
|
|
2261
2305
|
session.totalOutputTokens = (session.totalOutputTokens || 0) + (deltaOutput || 0);
|
|
2262
2306
|
session.tokensCumulative = (session.tokensCumulative || 0) + (deltaInput || 0) + (deltaOutput || 0);
|
|
@@ -2266,12 +2310,15 @@ export async function persistIterationMetrics(delta) {
|
|
|
2266
2310
|
// includes cached_read / cache_write in its terminal usage rollup).
|
|
2267
2311
|
session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + (deltaCachedRead || 0);
|
|
2268
2312
|
session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + (deltaCacheWrite || 0);
|
|
2313
|
+
session.totalUncachedInputTokens = (session.totalUncachedInputTokens || 0) + deltaUncachedInput;
|
|
2269
2314
|
// Window snapshot updated per iteration so agent type=list reflects the
|
|
2270
2315
|
// most-recent provider-reported input size even for short dispatches
|
|
2271
2316
|
// that finish before askSession's terminal save lands.
|
|
2272
2317
|
session.lastInputTokens = deltaInput || 0;
|
|
2273
2318
|
session.lastOutputTokens = deltaOutput || 0;
|
|
2274
2319
|
session.lastCachedReadTokens = deltaCachedRead || 0;
|
|
2320
|
+
session.lastCacheWriteTokens = deltaCacheWrite || 0;
|
|
2321
|
+
session.lastUncachedInputTokens = deltaUncachedInput;
|
|
2275
2322
|
// Normalized last-call context footprint: how many prompt tokens the
|
|
2276
2323
|
// model actually saw on the most-recent send, comparable ACROSS
|
|
2277
2324
|
// providers. Anthropic reports input_tokens EXCLUDING cache (cache_read
|
|
@@ -2280,7 +2327,7 @@ export async function persistIterationMetrics(delta) {
|
|
|
2280
2327
|
// tokens INTO the input count, so input alone is the footprint.
|
|
2281
2328
|
const _inputExcludesCache = providerInputExcludesCache(session.provider);
|
|
2282
2329
|
session.lastContextTokens = _inputExcludesCache
|
|
2283
|
-
? (deltaInput || 0) + (deltaCachedRead || 0)
|
|
2330
|
+
? (deltaInput || 0) + (deltaCachedRead || 0) + (deltaCacheWrite || 0)
|
|
2284
2331
|
: (deltaInput || 0);
|
|
2285
2332
|
session.lastContextTokensUpdatedAt = ts || Date.now();
|
|
2286
2333
|
session.lastContextTokensStaleAfterCompact = false;
|
|
@@ -3439,7 +3486,7 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
3439
3486
|
logLlmCall({
|
|
3440
3487
|
ts: new Date().toISOString(),
|
|
3441
3488
|
sourceType: session.sourceType || 'lead',
|
|
3442
|
-
sourceName: session.sourceName || session.
|
|
3489
|
+
sourceName: session.sourceName || session.agent || null,
|
|
3443
3490
|
preset: session.presetName || null,
|
|
3444
3491
|
model: session.model,
|
|
3445
3492
|
provider: session.provider,
|
|
@@ -3660,13 +3707,13 @@ export async function resumeSession(sessionId, preset) {
|
|
|
3660
3707
|
}
|
|
3661
3708
|
let toolsForRouting = resolveSessionTools(toolSpec, skills, { ownerIsAgentSession: ownerIsAgent });
|
|
3662
3709
|
if (ownerIsAgent) {
|
|
3663
|
-
toolsForRouting = applyToolPermissionNarrowing(toolsForRouting, session.toolPermission, session.
|
|
3710
|
+
toolsForRouting = applyToolPermissionNarrowing(toolsForRouting, session.toolPermission, session.agent || null);
|
|
3664
3711
|
}
|
|
3665
3712
|
session.tools = finalizeSessionToolList(toolsForRouting, {
|
|
3666
3713
|
schemaAllowedTools: Array.isArray(session.schemaAllowedTools) ? session.schemaAllowedTools : null,
|
|
3667
|
-
disallowedTools: null,
|
|
3714
|
+
disallowedTools: getHiddenAgent(session.agent || null) ? ['Skill'] : null,
|
|
3668
3715
|
ownerIsAgent,
|
|
3669
|
-
|
|
3716
|
+
resolvedAgent: session.agent || null,
|
|
3670
3717
|
});
|
|
3671
3718
|
const newTools = session.tools;
|
|
3672
3719
|
const missing = oldTools.filter(t => !newTools.find(n => n.name === t.name));
|