mixdog 1.0.9 → 1.0.11
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 +3 -3
- package/src/rules/agent/40-cycle1-agent.md +19 -23
- package/src/rules/lead/LEAD.md +5 -5
- package/src/rules/shared/05-tool-calls.md +6 -5
- package/src/rules/shared/60-verification.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +37 -13
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +6 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +8 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +234 -12
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +22 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +9 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +77 -0
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -6
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-image-input.mjs +34 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +94 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +670 -198
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +9 -3
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +4 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +14 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +24 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +3 -28
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/provider-base-url.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +24 -4
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +9 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +13 -11
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +17 -9
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +40 -71
- package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -7
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +54 -12
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +27 -5
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +8 -51
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +55 -2
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +23 -5
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +79 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -4
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +7 -6
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +85 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +51 -28
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +10 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +18 -7
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-roots.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/work.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +13 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +15 -1
- package/src/runtime/attachments/limits.mjs +20 -0
- package/src/runtime/{agent/orchestrator/tools/builtin/read-office-files.mjs → attachments/office-extract.mjs} +356 -125
- package/src/runtime/attachments/pdf-extract.mjs +2 -2
- package/src/runtime/attachments/store.mjs +232 -72
- package/src/runtime/browser-bridge/client.mjs +3 -3
- package/src/runtime/effort-judge/compact-tokenizer.mjs +282 -0
- package/src/runtime/effort-judge/judge-client.mjs +194 -0
- package/src/runtime/effort-judge/judge-worker.mjs +106 -0
- package/src/runtime/local-provider/input-capabilities.mjs +30 -12
- package/src/runtime/media/jobs.mjs +0 -1
- package/src/runtime/media/media-usage.mjs +2 -1
- package/src/runtime/memory/lib/embedding-model-config.mjs +6 -6
- package/src/runtime/memory/lib/embedding-model-files.mjs +44 -0
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +253 -0
- package/src/runtime/memory/lib/embedding-worker.mjs +97 -103
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +40 -218
- package/src/runtime/memory/lib/memory-embed.mjs +0 -9
- package/src/runtime/memory/lib/memory.mjs +34 -23
- package/src/runtime/memory/tool-defs.mjs +2 -1
- package/src/runtime/shared/automation-attachments.mjs +9 -7
- package/src/runtime/shared/environment-error.mjs +14 -0
- package/src/runtime/shared/error-presentation.mjs +10 -2
- package/src/runtime/shared/inline-file-kind.mjs +28 -2
- package/src/runtime/shared/llm/model-catalog.mjs +17 -3
- package/src/runtime/shared/llm/quota-value-estimate.mjs +33 -18
- package/src/runtime/shared/llm/usage-accounting.mjs +8 -5
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +29 -10
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +47 -4
- package/src/runtime/shared/model-alias.mjs +5 -0
- package/src/runtime/shared/read-image-resize.mjs +56 -0
- package/src/runtime/shared/tool-card-model/displayed-result.mjs +7 -5
- package/src/runtime/shared/tool-card-model.mjs +2 -1
- package/src/runtime/shared/tool-result-summary.mjs +120 -59
- package/src/runtime/shared/turn-review-reply.mjs +63 -0
- package/src/runtime/shared/turn-snapshot.mjs +8 -5
- package/src/runtime/shared/turn-worktree-snapshot.mjs +56 -19
- package/src/session-runtime/boot/apis.mjs +11 -0
- package/src/session-runtime/boot/providers.mjs +2 -0
- package/src/session-runtime/boot/session.mjs +5 -0
- package/src/session-runtime/boot/shared.mjs +2 -3
- package/src/session-runtime/native-web-search/tool-shape.mjs +8 -8
- package/src/session-runtime/native-web-search.mjs +6 -8
- package/src/session-runtime/provider-auth/login.mjs +26 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +9 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +17 -2
- package/src/session-runtime/services/provider-admin.mjs +215 -3
- package/src/session-runtime/services/usage-stats-model.mjs +37 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +29 -4
- package/src/session-runtime/setup-tool/executor.mjs +10 -1
- package/src/session-runtime/webhook-session-run.mjs +0 -9
- package/src/standalone/daemon-stored-session-views.mjs +15 -0
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/entries/session-loads.mjs +11 -3
- package/src/standalone/session-service/entries.mjs +1 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +31 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +2 -1
- package/src/standalone/session-service.mjs +2 -0
- package/src/tui/app/model-options.mjs +10 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +4 -1
- package/src/tui/app/usage-context-panels.mjs +3 -1
- package/src/tui/app/use-prompt-queue-history/restore-to-prompt.mjs +1 -0
- package/src/tui/components/ContextPanel.jsx +2 -0
- package/src/tui/dist/index.mjs +144 -70
- package/src/tui/paste-attachments.mjs +2 -1
- package/src/tui/session/session-api/intake/abort.mjs +35 -3
- package/src/tui/session/session-api/integrations.mjs +14 -59
- package/src/tui/session/session-api/media.mjs +1 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +21 -11
- package/src/tui/session/session-flow/steering.mjs +17 -3
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/turn.mjs +18 -4
- package/src/vendor/statusline/src/gateway/route-meta.mjs +2 -0
- package/scripts/memory-layer2-check.mjs +0 -186
|
@@ -16,8 +16,8 @@ import { HANDOFF_TIMEOUT_MAX_MS } from '../compact/constants.mjs';
|
|
|
16
16
|
import { envFlag, envPositiveInt } from '../../../../shared/env.mjs';
|
|
17
17
|
import { positiveInt } from '../../../../shared/numbers.mjs';
|
|
18
18
|
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
19
|
-
import { providerInputExcludesCache } from '../../providers/registry.mjs';
|
|
20
|
-
import { contentMediaBytes } from '../../providers/media-normalization.mjs';
|
|
19
|
+
import { providerInputExcludesCache, providerNativePdf } from '../../providers/registry.mjs';
|
|
20
|
+
import { contentMediaBytes, contentMediaCount } from '../../providers/media-normalization.mjs';
|
|
21
21
|
|
|
22
22
|
// Unified context-share rule (compact/constants.mjs CONTEXT_SHARE_RATIO): the
|
|
23
23
|
// post-compaction target is 25% of the boundary/context window. One
|
|
@@ -547,6 +547,17 @@ export function resolveGaugeContextTokens(messageTokensEst, policy, { messages,
|
|
|
547
547
|
return resolveContextTokens(messageTokensEst, policy, { messages, sessionRef });
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/**
|
|
551
|
+
* The "before" number a compaction row shows: the context the provider last
|
|
552
|
+
* measured, as the gauge displayed it. Falls back to the pass's own value only
|
|
553
|
+
* when no live reading exists (none yet, or invalidated by a prior compaction).
|
|
554
|
+
*/
|
|
555
|
+
export function compactDisplayBeforeTokens(sessionRef, fallbackTokens) {
|
|
556
|
+
const lastContextTokens = positiveInt(sessionRef?.lastContextTokens);
|
|
557
|
+
if (lastContextTokens && sessionRef.lastContextTokensStaleAfterCompact !== true) return lastContextTokens;
|
|
558
|
+
return fallbackTokens;
|
|
559
|
+
}
|
|
560
|
+
|
|
550
561
|
export function resolveCompactionPressureTokens(messageTokensEst, policy, { messages, sessionRef } = {}) {
|
|
551
562
|
return resolveContextTokens(messageTokensEst, policy, { messages, sessionRef });
|
|
552
563
|
}
|
|
@@ -591,13 +602,20 @@ export function compactTargetBudget(policy) {
|
|
|
591
602
|
// part compaction drops, so it alone arms this trigger; attachments the user
|
|
592
603
|
// sent survive compaction and would only make it repeat.
|
|
593
604
|
const REQUEST_MEDIA_COMPACT_BYTES = 24_000_000;
|
|
605
|
+
// Providers also cap the number of images plus native documents one request
|
|
606
|
+
// may carry (100 on Anthropic); the same tool-result media arms the trigger.
|
|
607
|
+
const REQUEST_MEDIA_COMPACT_COUNT = 100;
|
|
594
608
|
|
|
595
|
-
export function shouldCompactForRequestMedia(messages) {
|
|
609
|
+
export function shouldCompactForRequestMedia(messages, provider = '') {
|
|
610
|
+
// A PDF that lowers to text is neither bytes nor a document on the wire.
|
|
611
|
+
const nativePdf = providerNativePdf(provider);
|
|
596
612
|
let bytes = 0;
|
|
613
|
+
let count = 0;
|
|
597
614
|
for (const message of messages) {
|
|
598
615
|
if (message?.role !== 'tool') continue;
|
|
599
|
-
bytes += contentMediaBytes(message.content);
|
|
600
|
-
|
|
616
|
+
bytes += contentMediaBytes(message.content, { nativePdf });
|
|
617
|
+
count += contentMediaCount(message.content, { nativePdf });
|
|
618
|
+
if (bytes >= REQUEST_MEDIA_COMPACT_BYTES || count > REQUEST_MEDIA_COMPACT_COUNT) return true;
|
|
601
619
|
}
|
|
602
620
|
return false;
|
|
603
621
|
}
|
|
@@ -48,6 +48,7 @@ export async function prepareProviderRequest(state) {
|
|
|
48
48
|
cwd,
|
|
49
49
|
opts,
|
|
50
50
|
signal,
|
|
51
|
+
crossTurnCalls,
|
|
51
52
|
loopUsageMetricsTurnId,
|
|
52
53
|
loopUsageMetricsEpoch,
|
|
53
54
|
} = state;
|
|
@@ -101,6 +102,7 @@ export async function prepareProviderRequest(state) {
|
|
|
101
102
|
firstTurnUsage,
|
|
102
103
|
providerState,
|
|
103
104
|
reactiveOverflowRetryPending,
|
|
105
|
+
crossTurnCalls,
|
|
104
106
|
loopUsageMetricsTurnId,
|
|
105
107
|
loopUsageMetricsEpoch,
|
|
106
108
|
skipProactiveCompact: compactAttempted,
|
|
@@ -64,6 +64,7 @@ export async function beginIteration(state) {
|
|
|
64
64
|
providerState: state.providerState,
|
|
65
65
|
reactiveOverflowRetryPending: state.reactiveOverflowRetryPending,
|
|
66
66
|
fixedProviderToolSurface: state.fixedProviderToolSurface,
|
|
67
|
+
crossTurnCalls: state.crossTurnCalls,
|
|
67
68
|
loopUsageMetricsTurnId: state.usageMetricsTurnId,
|
|
68
69
|
loopUsageMetricsEpoch: state.usageMetricsEpoch,
|
|
69
70
|
});
|
|
@@ -234,17 +235,78 @@ function settleImageStrip(state, round, sent) {
|
|
|
234
235
|
state.imageStrip = null;
|
|
235
236
|
}
|
|
236
237
|
|
|
238
|
+
const MESSAGES_CACHE_TTL_MS = { '5m': 5 * 60 * 1000, '1h': 60 * 60 * 1000 };
|
|
239
|
+
const MISS_RATIO = 0.5;
|
|
240
|
+
const MISS_MIN_DROP_TOKENS = 4096;
|
|
241
|
+
|
|
242
|
+
/** Usage-based prompt-cache miss detection for Anthropic sessions. The
|
|
243
|
+
* baseline (previous request's cache read + write) lives on the session
|
|
244
|
+
* record so it survives a reload, unlike the in-memory prefix guard. */
|
|
245
|
+
function traceActualCacheMiss(state, sent, response, { previousGuardState, previousSendAt, cacheBreakIntent }) {
|
|
246
|
+
const { sessionRef, sessionId, model } = state;
|
|
247
|
+
const providerName = sessionRef?.provider || state.provider?.name || null;
|
|
248
|
+
if (!sessionRef || (providerName !== 'anthropic' && providerName !== 'anthropic-oauth')) return;
|
|
249
|
+
const usage = response?.usage || {};
|
|
250
|
+
const cachedTokens = Number(usage.cachedTokens) || 0;
|
|
251
|
+
const cachePrefixTokens = cachedTokens + (Number(usage.cacheWriteTokens) || 0);
|
|
252
|
+
const baseline = Number(sessionRef.lastProviderCachedPrefixTokens);
|
|
253
|
+
const ttlMs = MESSAGES_CACHE_TTL_MS[sessionRef.providerCacheOpts?.cacheStrategy?.messages];
|
|
254
|
+
const idleMs = sent.sendStartedAt - previousSendAt;
|
|
255
|
+
const requestPrefixChanged =
|
|
256
|
+
!!previousGuardState &&
|
|
257
|
+
previousGuardState.requestPrefixHash !== sent.prefixGuardCandidate?.requestPrefixHash;
|
|
258
|
+
if (
|
|
259
|
+
baseline > 0 &&
|
|
260
|
+
ttlMs &&
|
|
261
|
+
Number.isFinite(idleMs) &&
|
|
262
|
+
idleMs >= 0 &&
|
|
263
|
+
idleMs < ttlMs &&
|
|
264
|
+
cachedTokens < baseline * MISS_RATIO &&
|
|
265
|
+
baseline - cachedTokens >= MISS_MIN_DROP_TOKENS &&
|
|
266
|
+
!cacheBreakIntent &&
|
|
267
|
+
!requestPrefixChanged
|
|
268
|
+
) {
|
|
269
|
+
traceCacheBreak(
|
|
270
|
+
{
|
|
271
|
+
sessionId,
|
|
272
|
+
iteration: state.iterations + 1,
|
|
273
|
+
classification: 'unexpected',
|
|
274
|
+
reason: 'actual_cache_miss',
|
|
275
|
+
source: 'provider_usage',
|
|
276
|
+
provider: providerName,
|
|
277
|
+
model: model || null,
|
|
278
|
+
actualCacheMiss: true,
|
|
279
|
+
cachedTokens,
|
|
280
|
+
promptTokens: Number(usage.promptTokens) || null,
|
|
281
|
+
previousCachedPrefixTokens: baseline,
|
|
282
|
+
idleMs,
|
|
283
|
+
missingPrefixGuardBaseline: !previousGuardState,
|
|
284
|
+
},
|
|
285
|
+
state.cacheBreakTraceOptions
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
sessionRef.lastProviderCachedPrefixTokens = cachePrefixTokens;
|
|
289
|
+
}
|
|
290
|
+
|
|
237
291
|
/** Fold a completed send into the state: prefix guard, image-strip
|
|
238
292
|
* rebaseline, per-request budgets, provider state, usage and diagnostics. */
|
|
239
293
|
export function settleSendResult(state, round, sent) {
|
|
240
294
|
const { opts, sessionRef, sessionId, model, messages } = state;
|
|
241
295
|
const response = sent.result.response;
|
|
242
296
|
state.response = response;
|
|
297
|
+
const previousGuardState = state.prefixGuardState;
|
|
298
|
+
const previousSendAt = Number(sessionRef?.lastProviderSendAt);
|
|
299
|
+
const cacheBreakIntent = opts.cacheBreakIntent;
|
|
243
300
|
state.prefixGuardState = sent.prefixGuardCandidate;
|
|
244
301
|
if (sessionRef) sessionRef._providerPrefixGuardState = state.prefixGuardState;
|
|
245
302
|
// The provider read and refreshed its prompt cache when this request was
|
|
246
303
|
// sent; agent cache-expiry compaction measures idle time from here.
|
|
247
304
|
if (sessionRef) sessionRef.lastProviderSendAt = sent.sendStartedAt;
|
|
305
|
+
traceActualCacheMiss(state, sent, response, {
|
|
306
|
+
previousGuardState,
|
|
307
|
+
previousSendAt,
|
|
308
|
+
cacheBreakIntent,
|
|
309
|
+
});
|
|
248
310
|
if (state.imageStrip) settleImageStrip(state, round, sent);
|
|
249
311
|
opts.onToolCall = undefined;
|
|
250
312
|
delete opts.cacheBreakIntent;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Auto effort for one user turn. The Auto effort built-in (Extensions →
|
|
2
|
+
// Built-in) turns it `on`: the judged effort applies to this turn. The
|
|
3
|
+
// MIXDOG_AUTO_EFFORT env value (off/observe/on) overrides it for diagnostics;
|
|
4
|
+
// `observe` judges and logs without applying. Applies only to user-written
|
|
5
|
+
// prompts on models whose effort can change mid-conversation without
|
|
6
|
+
// invalidating the prompt cache.
|
|
7
|
+
import { readSection } from '../../../../shared/config.mjs';
|
|
8
|
+
import { effortConfigurationMode } from '../../providers/effort-configuration.mjs';
|
|
9
|
+
import { autoEffortBase, normalizeAutoEffortMode, resolveAutoEffort } from '../../providers/auto-effort.mjs';
|
|
10
|
+
import { builtinFeatureActive } from '../../runtime-core/builtin-features.mjs';
|
|
11
|
+
import { effortOptionsFor } from '../../runtime-core/effort.mjs';
|
|
12
|
+
import { judgeTurn, recordEffortDecision } from '../../../../effort-judge/judge-client.mjs';
|
|
13
|
+
import { promptContentText } from './prompt-utils.mjs';
|
|
14
|
+
|
|
15
|
+
export function autoEffortMode() {
|
|
16
|
+
if (process.env.MIXDOG_AUTO_EFFORT) return normalizeAutoEffortMode(process.env.MIXDOG_AUTO_EFFORT);
|
|
17
|
+
try {
|
|
18
|
+
return builtinFeatureActive(readSection('agent'), 'autoEffort') ? 'on' : 'off';
|
|
19
|
+
} catch {
|
|
20
|
+
return 'off';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function lastAssistantText(messages) {
|
|
25
|
+
for (let i = (messages?.length || 0) - 1; i >= 0; i--) {
|
|
26
|
+
const message = messages[i];
|
|
27
|
+
if (message?.role !== 'assistant') continue;
|
|
28
|
+
const text = promptContentText(message.content).trim();
|
|
29
|
+
if (text) return text;
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `{ base, effort, step, level, confidence, applied }` for this turn, or null
|
|
36
|
+
* when auto effort does not apply. The judge never delays the turn: a missing,
|
|
37
|
+
* warming, or slow model yields null and the default effort.
|
|
38
|
+
*/
|
|
39
|
+
export async function resolveTurnAutoEffort({ sessionId, session, provider, input }) {
|
|
40
|
+
const mode = autoEffortMode();
|
|
41
|
+
// promptSource marks runtime-injected turns (completion notices, queued work);
|
|
42
|
+
// agent-owned sessions (workers, maintenance) receive runtime-written briefs.
|
|
43
|
+
if (mode === 'off' || input?.promptSource || session?.owner === 'agent') return null;
|
|
44
|
+
const request = promptContentText(input?.prompt).trim();
|
|
45
|
+
// Skill bodies and runtime blocks arrive as tagged text, not as a request.
|
|
46
|
+
if (!request || request.startsWith('<')) return null;
|
|
47
|
+
const opts = { ...(provider?.config || {}), modelParameters: session.modelParameters || {} };
|
|
48
|
+
if (!effortConfigurationMode(session.provider, session.model, opts)) return null;
|
|
49
|
+
const chosen = String(session.effort || '').toLowerCase();
|
|
50
|
+
const base = autoEffortBase(chosen);
|
|
51
|
+
const judged = await judgeTurn({ request, prev: lastAssistantText(session.messages) });
|
|
52
|
+
const record = {
|
|
53
|
+
at: new Date().toISOString(),
|
|
54
|
+
sessionId,
|
|
55
|
+
provider: session.provider,
|
|
56
|
+
model: session.model,
|
|
57
|
+
mode,
|
|
58
|
+
chosen,
|
|
59
|
+
base,
|
|
60
|
+
requestChars: request.length,
|
|
61
|
+
};
|
|
62
|
+
if (!judged.probs) {
|
|
63
|
+
recordEffortDecision({ ...record, skipped: judged.skipped });
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const resolved = resolveAutoEffort({
|
|
67
|
+
base,
|
|
68
|
+
options: effortOptionsFor(session.provider, { id: session.model }),
|
|
69
|
+
probs: judged.probs,
|
|
70
|
+
});
|
|
71
|
+
recordEffortDecision({
|
|
72
|
+
...record,
|
|
73
|
+
...(resolved || { skipped: 'default-outside-auto-range' }),
|
|
74
|
+
ms: judged.ms,
|
|
75
|
+
probs: judged.probs.map((value) => Number(value.toFixed(3))),
|
|
76
|
+
});
|
|
77
|
+
if (!resolved) return null;
|
|
78
|
+
return { ...resolved, applied: mode === 'on' && resolved.effort !== chosen };
|
|
79
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { getProvider } from '../../providers/registry.mjs';
|
|
8
8
|
import { prepareExplicitSkills } from '../explicit-skills.mjs';
|
|
9
9
|
import { prepareTurnEffortConfiguration } from '../../providers/effort-configuration.mjs';
|
|
10
|
+
import { resolveTurnAutoEffort } from './ask-turn-auto-effort.mjs';
|
|
10
11
|
import { saveSessionAsync } from '../store.mjs';
|
|
11
12
|
import { hasUserConversationMessage, refreshSessionBp3Environment } from './prompt-utils.mjs';
|
|
12
13
|
import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
|
|
@@ -26,8 +27,13 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
26
27
|
// Register the live session object for synchronous close snapshots.
|
|
27
28
|
runtime.session = session;
|
|
28
29
|
if (!provider) throw new Error(`Provider "${session.provider}" not available`);
|
|
29
|
-
const
|
|
30
|
-
const
|
|
30
|
+
const autoEffort = await resolveTurnAutoEffort({ sessionId, session, provider, input });
|
|
31
|
+
const turnEffort = autoEffort?.applied ? autoEffort.effort : session.effort || null;
|
|
32
|
+
const effortConfiguration = prepareTurnEffortConfiguration(
|
|
33
|
+
session,
|
|
34
|
+
provider,
|
|
35
|
+
autoEffort?.applied ? autoEffort.effort : undefined
|
|
36
|
+
);
|
|
31
37
|
applyTurnContextMeta(session, provider);
|
|
32
38
|
const effectiveCwd = cwdOverride || session.cwd;
|
|
33
39
|
// A failed-turn retry resubmits the prompt the failure left unanswered;
|
|
@@ -113,6 +119,8 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
113
119
|
effectiveCwd,
|
|
114
120
|
turnEffort,
|
|
115
121
|
effortConfiguration,
|
|
122
|
+
// The effort Auto picked for this turn, shown next to the turn's result.
|
|
123
|
+
autoEffort: autoEffort ? autoEffort.effort : null,
|
|
116
124
|
outgoing,
|
|
117
125
|
beforeCount: historyMessages.length + 1,
|
|
118
126
|
deferredToolDelta: userTurn.deferredToolDelta,
|
|
@@ -59,7 +59,12 @@ function attachAssistantTranscriptCompletion(messages, completion, turnStartedAt
|
|
|
59
59
|
...meta,
|
|
60
60
|
transcript: {
|
|
61
61
|
...transcript,
|
|
62
|
-
completion: {
|
|
62
|
+
completion: {
|
|
63
|
+
status,
|
|
64
|
+
verb,
|
|
65
|
+
elapsedMs,
|
|
66
|
+
...(typeof completion.autoEffort === 'string' && completion.autoEffort ? { autoEffort: completion.autoEffort } : {}),
|
|
67
|
+
},
|
|
63
68
|
},
|
|
64
69
|
},
|
|
65
70
|
};
|
|
@@ -122,6 +127,7 @@ export async function commitAskTurn({ sessionId, opened, prepared, result, rawTr
|
|
|
122
127
|
...result,
|
|
123
128
|
trimmed: messagesDropped > 0,
|
|
124
129
|
messagesDropped,
|
|
130
|
+
...(prepared.autoEffort ? { autoEffort: prepared.autoEffort } : {}),
|
|
125
131
|
};
|
|
126
132
|
}
|
|
127
133
|
|
|
@@ -144,6 +150,7 @@ export function publishAskTurn({ sessionId, opened, terminalResultPreview, askOp
|
|
|
144
150
|
status: 'done',
|
|
145
151
|
verb: rawTranscriptMeta?.completionVerb,
|
|
146
152
|
elapsedMs: Date.now() - turnStartedAt,
|
|
153
|
+
autoEffort: terminalResultPreview?.autoEffort,
|
|
147
154
|
},
|
|
148
155
|
turnStartedAt
|
|
149
156
|
);
|
|
@@ -23,6 +23,7 @@ import { isSessionCompactionBlocked } from './runtime-liveness.mjs';
|
|
|
23
23
|
import { resetReadStateAfterCompaction } from '../read-dedup.mjs';
|
|
24
24
|
import {
|
|
25
25
|
compactTargetBudget as compactTargetBudgetForPolicy,
|
|
26
|
+
compactDisplayBeforeTokens,
|
|
26
27
|
currentContextEstimateTokens,
|
|
27
28
|
invalidateProviderContextBaseline,
|
|
28
29
|
recordContextUsageSnapshot,
|
|
@@ -233,10 +234,12 @@ function compactionPlanNumbers({ session, messages, boundary, alignedPolicy, for
|
|
|
233
234
|
(boundary ? bufferTokens / boundary : resolveCompactBufferRatio(session.compaction || {}));
|
|
234
235
|
const budget = alignedPolicy ? compactTargetBudgetForPolicy({ ...alignedPolicy, force }) || boundary : boundary;
|
|
235
236
|
const pressureTokens = estimateTranscriptContextUsage(messages, session.tools || [], { provider: session.provider });
|
|
236
|
-
const beforeTokens =
|
|
237
|
+
const beforeTokens = compactDisplayBeforeTokens(
|
|
238
|
+
session,
|
|
237
239
|
(alignedPolicy
|
|
238
240
|
? resolveGaugeContextTokens(beforeMessageTokens, alignedPolicy, { messages, sessionRef: session })
|
|
239
|
-
: 0) || pressureTokens
|
|
241
|
+
: 0) || pressureTokens
|
|
242
|
+
);
|
|
240
243
|
return { beforeMessageTokens, triggerTokens, bufferTokens, bufferRatio, budget, pressureTokens, beforeTokens };
|
|
241
244
|
}
|
|
242
245
|
|
|
@@ -689,7 +692,7 @@ export async function runSessionCompaction(session, opts = {}) {
|
|
|
689
692
|
});
|
|
690
693
|
}
|
|
691
694
|
plan.requireReduction =
|
|
692
|
-
(plan.mode === 'auto' || opts.requireReduction === true) && !shouldCompactForRequestMedia(plan.messages);
|
|
695
|
+
(plan.mode === 'auto' || opts.requireReduction === true) && !shouldCompactForRequestMedia(plan.messages, session.provider);
|
|
693
696
|
const skipped = () =>
|
|
694
697
|
compactionResult(plan, { changed: false, skipped: true, reason: 'no_token_reduction' }, unchangedAfter(plan), {
|
|
695
698
|
freshContext: false,
|
|
@@ -9,7 +9,11 @@ import { _isActivelyOwnedElsewhere, _recoverTurnCheckpointDurably } from './sess
|
|
|
9
9
|
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
10
10
|
import { getHiddenAgent } from '../../internal-agents.mjs';
|
|
11
11
|
import { loadConfig } from '../../config.mjs';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
buildProviderCacheOpts,
|
|
14
|
+
cacheCapabilityForProvider,
|
|
15
|
+
roleProviderCacheOpts,
|
|
16
|
+
} from '../../agent-runtime/cache-strategy.mjs';
|
|
13
17
|
import { normalizeAutoClearConfig, resolveAutoClearIdleMs } from '../../runtime-core/config-helpers.mjs';
|
|
14
18
|
import { _buildBaseRules } from './rules-cache.mjs';
|
|
15
19
|
import { composeSessionSystem, seedSessionMessages } from './session-prompt-composition.mjs';
|
|
@@ -43,8 +47,11 @@ export { prefetchSession, prepareSessionProjection } from './prepared-resume-cac
|
|
|
43
47
|
function buildSessionProviderCacheOpts(providerName, sessionId, agent = null) {
|
|
44
48
|
// Keep this in sync with createSession's provider-cache policy: only
|
|
45
49
|
// explicit-breakpoint providers get BP cache opts here; OpenAI/key-prefix
|
|
46
|
-
// providers use promptCacheKey and request-time strategy instead
|
|
47
|
-
|
|
50
|
+
// providers use promptCacheKey and request-time strategy instead, plus the
|
|
51
|
+
// shared prompt-cache scope of a one-shot role.
|
|
52
|
+
if (cacheCapabilityForProvider(providerName) !== 'explicit-breakpoint') {
|
|
53
|
+
return roleProviderCacheOpts(providerName, agent);
|
|
54
|
+
}
|
|
48
55
|
try {
|
|
49
56
|
let autoClear = null;
|
|
50
57
|
if (!agent || agent === 'lead') {
|
|
@@ -88,7 +88,11 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
|
|
|
88
88
|
// Lead-only response language; closes the environment block so no English
|
|
89
89
|
// system text (session/shell/git lines) follows it.
|
|
90
90
|
const languageContext = leadOnly ? _buildLeadLanguageContext() : '';
|
|
91
|
-
|
|
91
|
+
// Cwd, shell and repository facts only serve tool calls; a tool-free role
|
|
92
|
+
// gets none, which also keeps its prompt identical across projects.
|
|
93
|
+
const shellEnvironmentContext = toolFreeRole
|
|
94
|
+
? ''
|
|
95
|
+
: buildShellEnvironmentContext(opts, ownerIsAgent, surface.toolsForRouting);
|
|
92
96
|
// Persisted env tail: refreshSessionBp3Environment rebuilds the env block
|
|
93
97
|
// as [session, bp3EnvironmentContext], so the language block must
|
|
94
98
|
// already sit at the end of this string to stay last after a refresh.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import {
|
|
6
6
|
resolveWorkerCompactPolicy,
|
|
7
7
|
compactionTelemetryPressureTokens,
|
|
8
|
+
compactDisplayBeforeTokens,
|
|
8
9
|
currentContextEstimateTokens,
|
|
9
10
|
compactTargetBudget,
|
|
10
11
|
shouldCompactForRequestMedia,
|
|
@@ -17,6 +18,8 @@ import {
|
|
|
17
18
|
} from './loop/compact-policy.mjs';
|
|
18
19
|
import { previewFreshContextCompaction, runFreshContextCompact } from './loop/fresh-context.mjs';
|
|
19
20
|
import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
|
|
21
|
+
import { textPdfAllowanceDiscount } from './context-utils.mjs';
|
|
22
|
+
import { providerNativePdf } from '../providers/registry.mjs';
|
|
20
23
|
import { messagesArrayChanged } from './loop/tool-helpers.mjs';
|
|
21
24
|
import { normalizeUsage, addUsage } from './loop/usage.mjs';
|
|
22
25
|
import { reasoningUsage } from '../../../shared/llm/reasoning-usage.mjs';
|
|
@@ -66,6 +69,13 @@ function transcriptPrefixHash(messages) {
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
// The transcript estimate prices every PDF as a native document; a provider
|
|
73
|
+
// that takes PDFs as text sends at most the extraction budget instead.
|
|
74
|
+
function providerAwareTokens(tokens, messages, sessionRef) {
|
|
75
|
+
if (tokens == null || providerNativePdf(sessionRef?.provider)) return tokens;
|
|
76
|
+
return Math.max(0, tokens - textPdfAllowanceDiscount(messages));
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
// The compaction decision for this send. `pressureTokens` is the exact
|
|
70
80
|
// canonical value the decision uses; reactive overflow recovery floors it at
|
|
71
81
|
// the trigger so the gauge, telemetry, and forced compact still describe the
|
|
@@ -76,7 +86,7 @@ function transcriptPrefixHash(messages) {
|
|
|
76
86
|
// proactive triggers still need a smaller predicted transcript.
|
|
77
87
|
function preSendCompactDecision(state, compactPolicy) {
|
|
78
88
|
const { messages, sessionRef } = state;
|
|
79
|
-
const messageTokensEst = estimateMessagesTokensSafe(messages);
|
|
89
|
+
const messageTokensEst = providerAwareTokens(estimateMessagesTokensSafe(messages), messages, sessionRef);
|
|
80
90
|
const reactivePending = state.reactiveOverflowRetryPending === true;
|
|
81
91
|
const pressureTokens = compactionTelemetryPressureTokens(messageTokensEst, compactPolicy, {
|
|
82
92
|
reactivePending,
|
|
@@ -84,7 +94,7 @@ function preSendCompactDecision(state, compactPolicy) {
|
|
|
84
94
|
sessionRef,
|
|
85
95
|
});
|
|
86
96
|
const cacheExpired = shouldCompactForExpiredAgentCache(sessionRef, state.opts);
|
|
87
|
-
const mediaPressure = shouldCompactForRequestMedia(messages);
|
|
97
|
+
const mediaPressure = shouldCompactForRequestMedia(messages, sessionRef?.provider);
|
|
88
98
|
const requireReduction = !reactivePending && !mediaPressure;
|
|
89
99
|
let shouldCompact =
|
|
90
100
|
state.skipProactiveCompact !== true &&
|
|
@@ -209,7 +219,7 @@ function emitCompact(ctx, extra) {
|
|
|
209
219
|
sessionId: state.sessionId,
|
|
210
220
|
stage: 'pre_send',
|
|
211
221
|
trigger: decision.compactTrigger,
|
|
212
|
-
beforeTokens: decision.pressureTokens,
|
|
222
|
+
beforeTokens: compactDisplayBeforeTokens(state.sessionRef, decision.pressureTokens),
|
|
213
223
|
beforeMessages: before.count,
|
|
214
224
|
afterMessages: state.messages.length,
|
|
215
225
|
pressureTokens: decision.pressureTokens,
|
|
@@ -369,11 +379,17 @@ function throwCompactFailure(ctx, run, compactErr) {
|
|
|
369
379
|
// Replaces the live transcript with the compacted one and resets everything
|
|
370
380
|
// keyed to the old transcript shape.
|
|
371
381
|
function adoptCompactedTranscript(ctx, run) {
|
|
372
|
-
const {
|
|
382
|
+
const { state } = ctx;
|
|
383
|
+
const { messages, sessionRef, sessionId, opts } = state;
|
|
373
384
|
messages.length = 0;
|
|
374
385
|
messages.push(...run.compacted);
|
|
375
386
|
acknowledgeGoalReminder(sessionRef, run);
|
|
376
387
|
resetReadStateAfterCompaction(sessionId);
|
|
388
|
+
// Cross-turn receipts reference tool results that compaction just removed
|
|
389
|
+
// from context; a stub for them would point at nothing. Clear in place so
|
|
390
|
+
// the eager dispatcher sharing this map observes it. Cached bodies stay
|
|
391
|
+
// reusable and are re-delivered (and re-recorded) on the next identical call.
|
|
392
|
+
state.crossTurnCalls?.clear();
|
|
377
393
|
// This attempt's provider-tool scope was keyed to the old transcript
|
|
378
394
|
// shape. Invalidate it synchronously before any post-compact callback or
|
|
379
395
|
// subsequent async continuation.
|
|
@@ -402,7 +418,7 @@ function reportCompactOutcome(ctx, run) {
|
|
|
402
418
|
const { messages, sessionRef } = state;
|
|
403
419
|
const changed = ctx.compactChanged || run.summaryChanged;
|
|
404
420
|
const { freshContextResult } = run;
|
|
405
|
-
const afterMessageTokensEst = estimateMessagesTokensSafe(messages);
|
|
421
|
+
const afterMessageTokensEst = providerAwareTokens(estimateMessagesTokensSafe(messages), messages, sessionRef);
|
|
406
422
|
// Same scale as the pre-compact gauge: compaction invalidated the provider
|
|
407
423
|
// baseline, so the post-compact gauge number is the calibrated transcript
|
|
408
424
|
// estimate plus the request reserve.
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
persistsConfirmedImageRejection,
|
|
26
26
|
promptHasInlineImages,
|
|
27
27
|
shouldStripImagesForRetry,
|
|
28
|
+
isImagePreparationFailed,
|
|
29
|
+
stripFailingImage,
|
|
28
30
|
stripInlineImagesFromLatestTurn,
|
|
29
31
|
} from './image-strip-recovery.mjs';
|
|
30
32
|
import {
|
|
@@ -375,7 +377,13 @@ async function recoverFromSendError(sendErr, state) {
|
|
|
375
377
|
// only when no tool was dispatched. Text exposure must be retracted first;
|
|
376
378
|
// reasoning-only high-effort streams can retry without a text reset.
|
|
377
379
|
if (
|
|
378
|
-
imageStripEligible(sendErr, outcome, {
|
|
380
|
+
imageStripEligible(sendErr, outcome, {
|
|
381
|
+
recoveryMessages,
|
|
382
|
+
imageStripUsed,
|
|
383
|
+
transportRetriesUsed,
|
|
384
|
+
relayWitness,
|
|
385
|
+
isGrok: (sessionRef?.provider || state.provider?.name) === 'grok-oauth',
|
|
386
|
+
}) &&
|
|
379
387
|
(outcome.replaySafe === true || (await retract()))
|
|
380
388
|
) {
|
|
381
389
|
const stripped = retryWithImageStrip(sendErr, recoveryMessages, state);
|
|
@@ -684,7 +692,7 @@ function repairThinkingReplay(recoveryMessages, state) {
|
|
|
684
692
|
function imageStripEligible(
|
|
685
693
|
sendErr,
|
|
686
694
|
outcome,
|
|
687
|
-
{ recoveryMessages, imageStripUsed, transportRetriesUsed, relayWitness }
|
|
695
|
+
{ recoveryMessages, imageStripUsed, transportRetriesUsed, relayWitness, isGrok }
|
|
688
696
|
) {
|
|
689
697
|
return (
|
|
690
698
|
transportRetriesUsed < TRANSPORT_RETRY_MAX &&
|
|
@@ -692,7 +700,13 @@ function imageStripEligible(
|
|
|
692
700
|
hasImages: promptHasInlineImages(recoveryMessages),
|
|
693
701
|
alreadyStripped: imageStripUsed === true,
|
|
694
702
|
}) ||
|
|
695
|
-
|
|
703
|
+
// Grok Build RetryWithImageStrip only: a bare stream error on a Grok
|
|
704
|
+
// upload may be the image body being rejected. Other providers' transient
|
|
705
|
+
// stream errors are not evidence about images and use normal transport retry.
|
|
706
|
+
(isGrok &&
|
|
707
|
+
imageStripUsed !== true &&
|
|
708
|
+
promptHasInlineImages(recoveryMessages) &&
|
|
709
|
+
isRetryableStreamErrorEvent(sendErr))) &&
|
|
696
710
|
outcome.sideEffectDispatched !== true &&
|
|
697
711
|
Number(outcome.toolCallsDispatched) === 0 &&
|
|
698
712
|
Number(outcome.toolCallsComplete) === 0 &&
|
|
@@ -702,7 +716,9 @@ function imageStripEligible(
|
|
|
702
716
|
|
|
703
717
|
function retryWithImageStrip(sendErr, recoveryMessages, state) {
|
|
704
718
|
const { opts, transportRetriesUsed = 0 } = state;
|
|
705
|
-
const stripped =
|
|
719
|
+
const stripped = isImagePreparationFailed(sendErr)
|
|
720
|
+
? stripFailingImage(recoveryMessages, sendErr)
|
|
721
|
+
: stripInlineImagesFromLatestTurn(recoveryMessages);
|
|
706
722
|
if (!(stripped.stripped > 0)) return null;
|
|
707
723
|
const attempt = transportRetriesUsed + 1;
|
|
708
724
|
logLoop(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { contentCarriesLiveMedia } from '../../providers/media-normalization.mjs';
|
|
2
2
|
|
|
3
3
|
const _lastSaveError = new Map(); // id -> { message, at }
|
|
4
4
|
|
|
@@ -153,10 +153,11 @@ export function _clearLiveSession(id) {
|
|
|
153
153
|
if (id) _liveSessions.delete(id);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
// Live snapshots that still carry
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// TTL the memory cost wins and the
|
|
156
|
+
// Live snapshots that still carry inline media bytes (the persisted JSON holds
|
|
157
|
+
// content-addressed refs, or a placeholder for media the store refused) stay
|
|
158
|
+
// resident for this long after their last use so multi-turn image recognition
|
|
159
|
+
// keeps working across an idle gap. Beyond the TTL the memory cost wins and the
|
|
160
|
+
// snapshot is reclaimed like any other.
|
|
160
161
|
export const LIVE_MEDIA_RETENTION_MS = Math.max(
|
|
161
162
|
60_000,
|
|
162
163
|
Number(process.env.MIXDOG_LIVE_MEDIA_RETENTION_MS) || 10 * 60 * 1000
|
|
@@ -166,7 +167,7 @@ export function _messagesCarryLiveMedia(messages) {
|
|
|
166
167
|
if (!Array.isArray(messages)) return false;
|
|
167
168
|
for (const m of messages) {
|
|
168
169
|
if (!m || typeof m !== 'object') continue;
|
|
169
|
-
if (
|
|
170
|
+
if (contentCarriesLiveMedia(m.content)) return true;
|
|
170
171
|
}
|
|
171
172
|
return false;
|
|
172
173
|
}
|
|
@@ -4,9 +4,12 @@ import { renameWithRetrySync } from '../../../../shared/atomic-file.mjs';
|
|
|
4
4
|
import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
|
|
5
5
|
import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from '../lifecycle-scan.mjs';
|
|
6
6
|
|
|
7
|
-
// Inline
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// Inline base64 images (tool screenshots included) are written as
|
|
8
|
+
// content-addressed refs, like prompt media, so session JSON stays small while
|
|
9
|
+
// provider lowering resolves them to the same bytes across turns and reloads.
|
|
10
|
+
// Inline documents (PDF, Office, text) are stored the same way as `file` refs.
|
|
11
|
+
// Only media the store refuses (over the size cap, disk error) or that has no
|
|
12
|
+
// inline bytes (URL/file-id images) is replaced with a placeholder on disk.
|
|
10
13
|
// Per-message disk projection shared by _sessionForDisk and the save-worker
|
|
11
14
|
// delta path (tail-only projection). Returns the SAME array reference when no
|
|
12
15
|
// message changed. Idempotent: already-sanitized content passes through
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
// and then flow through the normal post-tool steering boundary. The background
|
|
6
6
|
// task itself keeps running.
|
|
7
7
|
const waitersBySession = new Map();
|
|
8
|
+
const steeringChecks = new Set();
|
|
9
|
+
|
|
10
|
+
// Scoped to an active turn. Read the live queue at wait entry rather than
|
|
11
|
+
// retaining a wake signal that could outlive a consumed or reclaimed prompt.
|
|
12
|
+
export function registerTaskWaitSteeringCheck(check) {
|
|
13
|
+
steeringChecks.add(check);
|
|
14
|
+
return () => steeringChecks.delete(check);
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
function sessionKey(sessionId) {
|
|
10
18
|
return String(sessionId || '').trim();
|
|
@@ -45,6 +53,11 @@ export function beginInterruptibleTaskWait(sessionId, parentSignal = null) {
|
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
if (!controller.signal.aborted && [...steeringChecks].some((check) => check(key))) {
|
|
57
|
+
waiter.interruptedByUser = true;
|
|
58
|
+
controller.abort('user-message');
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
return {
|
|
49
62
|
signal: controller.signal,
|
|
50
63
|
get interruptedByUser() {
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
import { markSessionToolCall } from '../manager/runtime-liveness.mjs';
|
|
5
5
|
import { classifyResultKind } from '../result-classification.mjs';
|
|
6
6
|
import { normalizeToolEnvelope } from '../tool-envelope.mjs';
|
|
7
|
-
import { captureReadCacheState
|
|
7
|
+
import { captureReadCacheState } from '../read-dedup.mjs';
|
|
8
|
+
import { lookupToolResultReuse } from '../cache/tool-result-reuse.mjs';
|
|
8
9
|
import { isInvalidToolArgsMarker, formatInvalidToolArgsResult } from '../../providers/openai-compat-stream.mjs';
|
|
9
|
-
import {
|
|
10
|
+
import { _isReadTool } from '../loop/tool-classify.mjs';
|
|
10
11
|
import { preDispatchDenyForSession } from '../loop/pre-dispatch-deny.mjs';
|
|
11
12
|
import { getToolKind, isEagerDispatchable, isParallelDispatchable } from '../loop/tool-helpers.mjs';
|
|
12
13
|
import { scopedCacheGeneration } from '../cache/scoped-cache.mjs';
|
|
@@ -73,24 +74,13 @@ export async function executeBatchCall(batch, call, callIndex) {
|
|
|
73
74
|
return exec;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// (toolName, canonical args) without per-file stat, since these tools scan
|
|
80
|
-
// many files; write-class tools evict entries whose registered dependency
|
|
81
|
-
// root contains the touched path.
|
|
77
|
+
// Use the same freshness policy as eager admission and cross-turn references.
|
|
78
|
+
// Read entries are stat-validated; scoped entries retain dependency
|
|
79
|
+
// invalidation and their bounded TTL. A miss proceeds to execution.
|
|
82
80
|
function lookupCaches(batch, call, exec) {
|
|
83
|
-
const { sessionId, cwd }
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
} else if (sessionId && _isScopedCacheableTool(call.name)) {
|
|
87
|
-
exec.scopedCacheHit = tryScopedToolCached({
|
|
88
|
-
sessionId,
|
|
89
|
-
toolName: _stripMcpPrefix(call.name),
|
|
90
|
-
args: call.arguments,
|
|
91
|
-
cwd,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
81
|
+
const cached = lookupToolResultReuse({ sessionId: batch.sessionId, call, cwd: batch.cwd });
|
|
82
|
+
if (cached?.kind === 'read') exec.readCacheHit = cached.entry;
|
|
83
|
+
else if (cached?.kind === 'scoped') exec.scopedCacheHit = cached.entry;
|
|
94
84
|
}
|
|
95
85
|
|
|
96
86
|
function markInstant(exec) {
|