mixdog 1.4.0 → 1.4.1
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 +1 -1
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +12 -3
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +37 -37
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +123 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -4
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test-data-dir.mjs +10 -7
- package/src/app.mjs +4 -0
- package/src/defaults/shipped-history.json +5 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -4
- package/src/defaults/skills/computer-use/SKILL.md +4 -2
- package/src/lib/rules-builder.cjs +29 -12
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +10 -5
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/70-delivery.md +7 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +1 -1
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/registry.mjs +12 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +61 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +5 -1
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +3 -0
- package/src/runtime/shared/tool-work-units.mjs +2 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +15 -0
- package/src/runtime/shared/worktrees/manager.mjs +87 -38
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/visualize-bridge/client.mjs +2 -0
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +152 -63
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +5 -1
- package/src/session-runtime/bot-status.mjs +12 -6
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +271 -25
- package/src/session-runtime/conversation-import/service.mjs +26 -5
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/provider-admin.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/transitions.mjs +25 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
- package/src/standalone/daemon-session-control.mjs +18 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/app/app-view/floating-panel.jsx +7 -4
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +9 -5
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/dist/index.mjs +75 -28
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/ui/statusline-agents.mjs +1 -3
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
export const IMPORT_WARNING_IMAGE_OMITTED = 'imageOmitted';
|
|
10
10
|
export const IMPORT_WARNING_MALFORMED = 'malformed';
|
|
11
11
|
export const IMPORT_WARNING_CWD_MISSING = 'cwdMissing';
|
|
12
|
+
/** The conversation continues a history stored in another file that was not followed. */
|
|
13
|
+
export const IMPORT_WARNING_HISTORY_PARTIAL = 'historyPartial';
|
|
14
|
+
|
|
15
|
+
/** Failure code of a record the source app writes for its own use (not a conversation). */
|
|
16
|
+
export const IMPORT_ERROR_INTERNAL = 'internalRecord';
|
|
12
17
|
|
|
13
18
|
/** Stands in for an image the importer does not carry over. */
|
|
14
19
|
export const IMAGE_OMITTED_TEXT = '[image omitted]';
|
|
@@ -17,12 +22,49 @@ export const IMPORTED_USER_STUB_TEXT = '[Imported conversation continues from an
|
|
|
17
22
|
|
|
18
23
|
const TITLE_MAX_CHARS = 100;
|
|
19
24
|
|
|
20
|
-
/**
|
|
25
|
+
/** Note for a tool call: its name only, never its arguments. A run of notes
|
|
26
|
+
* becomes one compact line in the text and one structured entry in `toolRuns`. */
|
|
21
27
|
export function toolNote(name) {
|
|
22
|
-
const clean =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const clean =
|
|
29
|
+
String(name || '')
|
|
30
|
+
.replace(/\s+/g, ' ')
|
|
31
|
+
.trim() || 'unknown';
|
|
32
|
+
return { text: `[ran tool: ${clean}]`, note: true, tool: clean };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Longest model-facing run line, and the longest tool name shown in it. */
|
|
36
|
+
export const TOOL_RUN_LINE_MAX_CHARS = 400;
|
|
37
|
+
const TOOL_RUN_NAME_MAX_CHARS = 40;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Model-facing line for one run of tool notes: `[ran tools: shell ×6, Read ×2]`.
|
|
41
|
+
* Bounded: names are clipped, and tools that do not fit are counted as
|
|
42
|
+
* `, +N more`. Deterministic in `tools`, so a stored line can be re-derived.
|
|
43
|
+
*/
|
|
44
|
+
export function toolRunLine(tools) {
|
|
45
|
+
const entries = tools.map(({ name, count }) => {
|
|
46
|
+
const chars = Array.from(String(name));
|
|
47
|
+
const shown =
|
|
48
|
+
chars.length > TOOL_RUN_NAME_MAX_CHARS ? `${chars.slice(0, TOOL_RUN_NAME_MAX_CHARS - 1).join('')}…` : name;
|
|
49
|
+
return `${shown} ×${count}`;
|
|
50
|
+
});
|
|
51
|
+
const omittedSuffix = (omitted) => (omitted > 0 ? `, +${omitted} more` : '');
|
|
52
|
+
let body = '';
|
|
53
|
+
let shownCount = 0;
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const next = shownCount ? `${body}, ${entry}` : entry;
|
|
56
|
+
const length = `[ran tools: ${next}${omittedSuffix(entries.length - shownCount - 1)}]`.length;
|
|
57
|
+
if (shownCount && length > TOOL_RUN_LINE_MAX_CHARS) break;
|
|
58
|
+
body = next;
|
|
59
|
+
shownCount += 1;
|
|
60
|
+
}
|
|
61
|
+
return `[ran tools: ${body}${omittedSuffix(entries.length - shownCount)}]`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toolRunCounts(notes) {
|
|
65
|
+
const counts = new Map();
|
|
66
|
+
for (const note of notes) counts.set(note.tool, (counts.get(note.tool) || 0) + 1);
|
|
67
|
+
return [...counts].map(([name, count]) => ({ name, count }));
|
|
26
68
|
}
|
|
27
69
|
|
|
28
70
|
export function textPart(text) {
|
|
@@ -42,18 +84,31 @@ export function timestampMs(value) {
|
|
|
42
84
|
return Number.isFinite(ms) && ms > 0 ? ms : null;
|
|
43
85
|
}
|
|
44
86
|
|
|
45
|
-
//
|
|
87
|
+
// Each run of consecutive tool notes collapses into one compact line; every
|
|
88
|
+
// boundary is a blank line. `toolRuns` locates those lines in `text`.
|
|
46
89
|
function joinParts(parts) {
|
|
47
|
-
|
|
48
|
-
let previous = null;
|
|
90
|
+
const blocks = [];
|
|
49
91
|
for (const part of parts) {
|
|
50
92
|
const piece = String(part.text || '').trim();
|
|
51
93
|
if (!piece) continue;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
const last = blocks[blocks.length - 1];
|
|
95
|
+
if (part.note && last?.notes) last.notes.push(part);
|
|
96
|
+
else blocks.push(part.note ? { notes: [part] } : { text: piece });
|
|
97
|
+
}
|
|
98
|
+
let text = '';
|
|
99
|
+
const toolRuns = [];
|
|
100
|
+
for (const block of blocks) {
|
|
101
|
+
if (text) text += '\n\n';
|
|
102
|
+
if (block.notes) {
|
|
103
|
+
const tools = toolRunCounts(block.notes);
|
|
104
|
+
const line = toolRunLine(tools);
|
|
105
|
+
toolRuns.push({ start: text.length, end: text.length + line.length, tools });
|
|
106
|
+
text += line;
|
|
107
|
+
} else {
|
|
108
|
+
text += block.text;
|
|
109
|
+
}
|
|
55
110
|
}
|
|
56
|
-
return { text,
|
|
111
|
+
return { text, toolRuns };
|
|
57
112
|
}
|
|
58
113
|
|
|
59
114
|
/**
|
|
@@ -72,9 +127,15 @@ export function normalizeTurns(rawTurns) {
|
|
|
72
127
|
else merged.push({ role: raw.role, parts: [...parts], at: timestampMs(raw.at) });
|
|
73
128
|
}
|
|
74
129
|
const turns = merged.map(({ role, parts, at }) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
if (
|
|
130
|
+
const joined = joinParts(parts);
|
|
131
|
+
const turn = { role, text: joined.text, at };
|
|
132
|
+
if (joined.toolRuns.length) turn.toolRuns = joined.toolRuns;
|
|
133
|
+
// A carried-over compaction summary never names the conversation; a slash
|
|
134
|
+
// command only when no typed prompt does.
|
|
135
|
+
if (parts.some((part) => part.summary || part.command)) {
|
|
136
|
+
turn.titleText = joinParts(parts.filter((part) => !part.summary && !part.command)).text;
|
|
137
|
+
}
|
|
138
|
+
if (parts.some((part) => part.command)) turn.commandText = joinParts(parts.filter((part) => part.command)).text;
|
|
78
139
|
return turn;
|
|
79
140
|
});
|
|
80
141
|
if (turns[0]?.role === 'assistant') {
|
|
@@ -83,19 +144,26 @@ export function normalizeTurns(rawTurns) {
|
|
|
83
144
|
return turns;
|
|
84
145
|
}
|
|
85
146
|
|
|
86
|
-
const
|
|
87
|
-
(
|
|
147
|
+
const textLines = (text) =>
|
|
148
|
+
String(text ?? '')
|
|
88
149
|
.split('\n')
|
|
89
150
|
.map((value) => value.replace(/\s+/g, ' ').trim())
|
|
90
151
|
.filter(Boolean);
|
|
91
152
|
|
|
153
|
+
const titleLines = (turn) => textLines(turn.titleText ?? turn.text);
|
|
154
|
+
|
|
155
|
+
/** First non-empty line of a title text, whitespace-normalized, ≤100 chars. */
|
|
156
|
+
export function titleFromText(text) {
|
|
157
|
+
const line = textLines(text)[0];
|
|
158
|
+
if (!line) return null;
|
|
159
|
+
return Array.from(line).slice(0, TITLE_MAX_CHARS).join('').trim() || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
92
162
|
/** First line of the first genuine user turn, whitespace-normalized, ≤100 chars
|
|
93
|
-
* (an image marker only when the turn has no text line). */
|
|
163
|
+
* (an image marker only when the turn has no text line), else the first slash command. */
|
|
94
164
|
export function conversationTitle(turns) {
|
|
95
165
|
const first = turns.find((turn) => turn.role === 'user' && !turn.stub && titleLines(turn).length);
|
|
96
|
-
if (!first) return
|
|
166
|
+
if (!first) return titleFromText(turns.find((turn) => turn.role === 'user' && turn.commandText)?.commandText);
|
|
97
167
|
const lines = titleLines(first);
|
|
98
|
-
|
|
99
|
-
if (!line) return null;
|
|
100
|
-
return Array.from(line).slice(0, TITLE_MAX_CHARS).join('').trim() || null;
|
|
168
|
+
return titleFromText(lines.find((value) => value !== IMAGE_OMITTED_TEXT) || lines[0]);
|
|
101
169
|
}
|
|
@@ -16,6 +16,9 @@ import { CONTEXT_SHARE_RATIO, COMPACT_TARGET_MIN_TOKENS, COMPACT_SAFETY_PERCENT
|
|
|
16
16
|
import { HANDOFF_TIMEOUT_MAX_MS } from '../compact/constants.mjs';
|
|
17
17
|
import { envFlag, envPositiveInt } from '../../../../shared/env.mjs';
|
|
18
18
|
import { positiveInt } from '../../../../shared/numbers.mjs';
|
|
19
|
+
import { cacheWarmUseFor, lastCacheWarmUseAt } from '../../../../shared/idle-auto-clear-gate.mjs';
|
|
20
|
+
import { ACCOUNT_PROVIDERS } from '../../../../shared/provider-accounts.mjs';
|
|
21
|
+
import { plannedAccountId } from '../../providers/account-pool.mjs';
|
|
19
22
|
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
20
23
|
import { isAgentModeSession } from '../agent-mode.mjs';
|
|
21
24
|
import { providerInputExcludesCache, providerNativePdf } from '../../providers/registry.mjs';
|
|
@@ -703,13 +706,40 @@ export function shouldCompactForRequestMedia(messages, provider = '') {
|
|
|
703
706
|
// Anthropic's 5m message-tail TTL (agent-runtime/cache-strategy.mjs gives it
|
|
704
707
|
// to agent sessions). A send this long after the previous one finds the tail
|
|
705
708
|
// cache expired and would rewrite the whole accumulated transcript; compacting
|
|
706
|
-
// first makes that cold write the compacted transcript instead.
|
|
709
|
+
// first makes that cold write the compacted transcript instead. The tail was
|
|
710
|
+
// last used by the session's own send or by a successful cache warm
|
|
711
|
+
// (cache-warm.mjs), which reads and refreshes it without moving
|
|
712
|
+
// lastProviderSendAt: a warmed tail has not expired — for a request on the
|
|
713
|
+
// warm's provider, model and account only, since the entry lives there.
|
|
707
714
|
const AGENT_MESSAGE_CACHE_TTL_MS = 5 * 60_000;
|
|
708
715
|
|
|
709
|
-
|
|
716
|
+
// The account the request would be served by: the roster's first pick for a
|
|
717
|
+
// provider with accounts (account-pool.mjs), '' for one without a roster.
|
|
718
|
+
function requestAccountId(provider, model, sessionRef, opts, messages) {
|
|
719
|
+
if (!ACCOUNT_PROVIDERS.includes(provider)) return '';
|
|
720
|
+
return plannedAccountId(provider, {
|
|
721
|
+
sessionId: opts?.sessionId || sessionRef?.id || null,
|
|
722
|
+
messages,
|
|
723
|
+
providerState: opts?.providerState,
|
|
724
|
+
model,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** Whether the agent session's 5m message tail expired before this request
|
|
729
|
+
* on `provider` / `model` (default: the session's route). */
|
|
730
|
+
export function shouldCompactForExpiredAgentCache(sessionRef, opts, { provider, model, messages = [] } = {}) {
|
|
710
731
|
if (!isAgentOwner(sessionRef) || opts?.cacheStrategy?.messages !== '5m') return false;
|
|
711
732
|
const lastSendAt = Number(sessionRef.lastProviderSendAt);
|
|
712
|
-
|
|
733
|
+
if (!(lastSendAt > 0)) return false;
|
|
734
|
+
const now = Date.now();
|
|
735
|
+
if (now - lastSendAt <= AGENT_MESSAGE_CACHE_TTL_MS) return false;
|
|
736
|
+
// No warm since the last send: the tail expired.
|
|
737
|
+
if (!(lastCacheWarmUseAt(sessionRef.id) > lastSendAt)) return true;
|
|
738
|
+
const route = { provider: provider || sessionRef.provider || '', model: model || sessionRef.model || '' };
|
|
739
|
+
const accountId = requestAccountId(route.provider, route.model, sessionRef, opts, messages);
|
|
740
|
+
if (accountId === null) return true;
|
|
741
|
+
const warmAt = cacheWarmUseFor(sessionRef.id, { ...route, accountId });
|
|
742
|
+
return now - Math.max(lastSendAt, warmAt) > AGENT_MESSAGE_CACHE_TTL_MS;
|
|
713
743
|
}
|
|
714
744
|
|
|
715
745
|
export function shouldCompactForSession(
|
|
@@ -278,14 +278,19 @@ function currentSkillResolver(sessionRef, { preview = false } = {}) {
|
|
|
278
278
|
return (name) => {
|
|
279
279
|
const delivery = currentSkillDelivery(name, cwd, config, skills);
|
|
280
280
|
if (!delivery) return null;
|
|
281
|
-
if (preview) return { body: delivery.body, native: undefined };
|
|
282
281
|
const { resource } = delivery;
|
|
282
|
+
// The tools whose use counts as use of the skill: its tool dependencies,
|
|
283
|
+
// an MCP server as `mcp:<server>`.
|
|
284
|
+
const tools = (resource.toolDependencies || []).flatMap(({ type, value }) =>
|
|
285
|
+
type === 'tool' ? [value] : type === 'mcp' ? [`mcp:${value}`] : []
|
|
286
|
+
);
|
|
287
|
+
if (preview) return { body: delivery.body, native: undefined, tools };
|
|
283
288
|
const envelope = loadSkillToolDependencies(
|
|
284
289
|
buildSkillToolEnvelope(name, resource.content, resource.dir, resource),
|
|
285
290
|
sessionRef
|
|
286
291
|
);
|
|
287
292
|
const native = parseNativeToolSearchPayload('Skill', envelope.result);
|
|
288
|
-
return { body: delivery.body, native, summary: native?.summary ?? envelope.result };
|
|
293
|
+
return { body: delivery.body, native, summary: native?.summary ?? envelope.result, tools };
|
|
289
294
|
};
|
|
290
295
|
}
|
|
291
296
|
|
|
@@ -19,7 +19,7 @@ import { providerToolExclusionReason } from './provider-tool-exclusions.mjs';
|
|
|
19
19
|
import { workflowOverlayDenial } from '../manager/workflow-switch-notice.mjs';
|
|
20
20
|
import { agentMcpToolPermission, connectorPolicyAgentOf, projectMcpToolDisabled } from '../../mcp/agent-connectors.mjs';
|
|
21
21
|
|
|
22
|
-
const WORKER_DENIED_TOOLS = new Set(['agent', 'ask_user', 'session']);
|
|
22
|
+
const WORKER_DENIED_TOOLS = new Set(['agent', 'ask_user', 'handoff_user', 'session']);
|
|
23
23
|
const BRIDGE_DISCOVERY_TOOL_NAMES = new Set(['load_tool']);
|
|
24
24
|
|
|
25
25
|
const IMAGE_PATH_RE = /\.(?:png|jpe?g|gif|webp)(?:$|[?#])/i;
|
|
@@ -31,6 +31,13 @@ function exclusionsFor(sessionRef) {
|
|
|
31
31
|
|
|
32
32
|
const isSessionRef = (value) => value !== null && typeof value === 'object';
|
|
33
33
|
|
|
34
|
+
// A schema 400 drop holds for the schema rule that refused it: a route on
|
|
35
|
+
// another wire family (a fallback hop, a model switch) sends the tool again.
|
|
36
|
+
function droppedOnRoute(sessionRef, name) {
|
|
37
|
+
const entry = isSessionRef(sessionRef) ? sessions.get(sessionRef)?.dropped.get(String(name || '')) : null;
|
|
38
|
+
return entry && entry.family === routeSchemaFamily(sessionRef) ? entry : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
/** A new provider request: exclusions describe the latest request only. */
|
|
35
42
|
export function beginProviderToolExclusions(sessionRef) {
|
|
36
43
|
if (isSessionRef(sessionRef)) sessions.get(sessionRef)?.current.clear();
|
|
@@ -64,15 +71,16 @@ export function providerToolExclusionReason(sessionRef, name) {
|
|
|
64
71
|
if (!isSessionRef(sessionRef)) return null;
|
|
65
72
|
const state = sessions.get(sessionRef);
|
|
66
73
|
const key = String(name || '');
|
|
67
|
-
const exclusion = state?.current.get(key) ||
|
|
74
|
+
const exclusion = state?.current.get(key) || droppedOnRoute(sessionRef, key);
|
|
68
75
|
return exclusion ? unavailableText(exclusion) : null;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
/** The provider rejected `exclusion.name`'s schema with an HTTP 400: leave the
|
|
72
|
-
* tool out of
|
|
79
|
+
* tool out of the session's later requests on this wire family and tell the
|
|
80
|
+
* user once. */
|
|
73
81
|
export function dropSessionTool(sessionRef, exclusion, onStageChange) {
|
|
74
82
|
if (!isSessionRef(sessionRef)) return;
|
|
75
|
-
exclusionsFor(sessionRef).dropped.set(exclusion.name, exclusion);
|
|
83
|
+
exclusionsFor(sessionRef).dropped.set(exclusion.name, { ...exclusion, family: routeSchemaFamily(sessionRef) });
|
|
76
84
|
recordProviderToolExclusion(sessionRef, exclusion, onStageChange);
|
|
77
85
|
}
|
|
78
86
|
|
|
@@ -82,7 +90,7 @@ export function withoutDroppedTools(sessionRef, { sendTools, requestToolScope })
|
|
|
82
90
|
const dropped = isSessionRef(sessionRef) ? sessions.get(sessionRef)?.dropped : null;
|
|
83
91
|
if (!dropped?.size) return { sendTools, requestToolScope };
|
|
84
92
|
const prefix = providerNativeToolPrefixCount(sendTools);
|
|
85
|
-
const kept = sendTools.filter((tool, index) => index < prefix || !
|
|
93
|
+
const kept = sendTools.filter((tool, index) => index < prefix || !droppedOnRoute(sessionRef, tool?.name));
|
|
86
94
|
if (kept.length === sendTools.length) return { sendTools, requestToolScope };
|
|
87
95
|
const next = finalizeProviderRequestTools(kept, prefix);
|
|
88
96
|
return { sendTools: next, requestToolScope: { ...requestToolScope, requestTools: next } };
|
|
@@ -128,7 +136,7 @@ function schemaExclusion(session, family, tool) {
|
|
|
128
136
|
* tools are never excluded by the schema rule. */
|
|
129
137
|
export function routeToolExclusionReason(session, tool) {
|
|
130
138
|
if (!tool) return null;
|
|
131
|
-
const dropped =
|
|
139
|
+
const dropped = droppedOnRoute(session, tool.name);
|
|
132
140
|
if (dropped) return unavailableText(dropped);
|
|
133
141
|
const exclusion = schemaExclusion(session, routeSchemaFamily(session), tool);
|
|
134
142
|
return exclusion ? unavailableText(exclusion) : null;
|
|
@@ -17,6 +17,8 @@ import { buildRequestSendOptions } from '../manager/ask-turn-loop-options.mjs';
|
|
|
17
17
|
import { prepareTurnEffortConfiguration } from '../../providers/effort-configuration.mjs';
|
|
18
18
|
import { resolveSessionContextMeta } from '../manager/context-meta.mjs';
|
|
19
19
|
import { stripProviderReplayMessages } from '../manager/provider-replay.mjs';
|
|
20
|
+
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
21
|
+
import { rebuildToolSurfaceForProviderWithMigration } from './tool-exposure-migration.mjs';
|
|
20
22
|
|
|
21
23
|
// The short reason a notice gives for leaving a model, by provider kind.
|
|
22
24
|
const HOP_REASONS = {
|
|
@@ -149,6 +151,11 @@ export async function advanceAgentRoute(state, error) {
|
|
|
149
151
|
updated.routeFallbackFailures = [...(session.routeFallbackFailures || []), failed];
|
|
150
152
|
await pinSessionWireRoute(updated, provider);
|
|
151
153
|
state.throwIfAborted();
|
|
154
|
+
// The route change replaced the session's tools with its whole resolved list.
|
|
155
|
+
// A delegated agent's surface was already re-applied by updateSessionRoute
|
|
156
|
+
// (applyAgentToolExposure); a Lead-class session gets its deferred surface
|
|
157
|
+
// restored for the new provider, as a manual model switch does.
|
|
158
|
+
if (!isAgentOwner(updated)) rebuildToolSurfaceForProviderWithMigration(updated, next.provider, state.messages);
|
|
152
159
|
// The live turn (prompt, assistant tool calls, tool results) carries on;
|
|
153
160
|
// only the previous provider's native replay state is dropped from it.
|
|
154
161
|
state.messages.splice(0, state.messages.length, ...stripProviderReplayMessages(state.messages));
|
|
@@ -164,6 +171,13 @@ export async function advanceAgentRoute(state, error) {
|
|
|
164
171
|
state.cacheBreakTraceKeys.clear();
|
|
165
172
|
state.transportRetriesUsed = 0;
|
|
166
173
|
state.transportRetryMax = 0;
|
|
174
|
+
// The one-shot recoveries the left model spent belong to its requests; the
|
|
175
|
+
// next model's first request gets its own (its window, images and schemas).
|
|
176
|
+
state.contextOverflowRetryUsed = false;
|
|
177
|
+
state.imageStripUsed = false;
|
|
178
|
+
state.imageStrip = null;
|
|
179
|
+
state.thinkingReplayRepairUsed = false;
|
|
180
|
+
state.toolSchemaDropUsed = false;
|
|
167
181
|
for (const key of Object.keys(oldOptions)) delete state.opts[key];
|
|
168
182
|
Object.assign(
|
|
169
183
|
state.opts,
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* consume a retry verdict, and settle a completed send into the loop state.
|
|
5
5
|
*/
|
|
6
6
|
import { traceCacheBreak } from '../../cache-break-trace.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
anthropicPrefixChanges,
|
|
9
|
+
latestAnthropicPrefix,
|
|
10
|
+
persistableAnthropicPrefix,
|
|
11
|
+
seedAnthropicPrefix,
|
|
12
|
+
} from '../../providers/lib/anthropic-prefix-trace.mjs';
|
|
8
13
|
import { resolveLiveToolCwd } from './tool-exec.mjs';
|
|
9
14
|
import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
|
|
10
15
|
import { addUsage, usageDeltaEvent } from './usage.mjs';
|
|
@@ -179,6 +184,9 @@ export async function sendProviderRequest(state, round) {
|
|
|
179
184
|
} catch {
|
|
180
185
|
// the send-started observer is advisory
|
|
181
186
|
}
|
|
187
|
+
// Diagnostic only: a restarted process compares its first request with the
|
|
188
|
+
// last prefix fingerprint the session record kept (anthropic-prefix-trace.mjs).
|
|
189
|
+
seedAnthropicPrefix(sessionId, sessionRef?.providerPrefixBaseline);
|
|
182
190
|
// The request a /btw side question forks: kept while it is in flight, with
|
|
183
191
|
// its reply once it proceeds, and dropped again if it fails.
|
|
184
192
|
const sideQuestion = captureSideQuestionRequest({
|
|
@@ -374,6 +382,10 @@ function traceActualCacheMiss(state, sent, response, { previousGuardState, previ
|
|
|
374
382
|
);
|
|
375
383
|
}
|
|
376
384
|
sessionRef.lastProviderCachedPrefixTokens = cachePrefixTokens;
|
|
385
|
+
// The fingerprint of this request survives a restart on the record, so the
|
|
386
|
+
// next process's first miss can still name what moved.
|
|
387
|
+
const fingerprint = persistableAnthropicPrefix(latestAnthropicPrefix(sessionId));
|
|
388
|
+
if (fingerprint) sessionRef.providerPrefixBaseline = fingerprint;
|
|
377
389
|
}
|
|
378
390
|
|
|
379
391
|
/** Fold a completed send into the state: prefix guard, image-strip
|
|
@@ -132,8 +132,8 @@ export async function applyBeforeToolHook({
|
|
|
132
132
|
mcpScopeId = null,
|
|
133
133
|
}) {
|
|
134
134
|
const call = { name, args, cwd };
|
|
135
|
-
// ask_user
|
|
136
|
-
if (name === 'ask_user') return call;
|
|
135
|
+
// ask_user and handoff_user are themselves the user prompt; they never go through the ordinary approval gate.
|
|
136
|
+
if (name === 'ask_user' || name === 'handoff_user') return call;
|
|
137
137
|
try {
|
|
138
138
|
const hookDecision = beforeToolHook
|
|
139
139
|
? await runAbortable(executeOpts.signal, () =>
|
|
@@ -46,12 +46,44 @@ export function markToolSurfaceMigrated(session, intent = TOOL_SURFACE_MIGRATION
|
|
|
46
46
|
session.pendingCacheBreakIntent = intent;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Run `apply` with `messages` (the transcript a running turn works on) as the
|
|
51
|
+
* session's messages, so a fixed-guidance rewrite lands there; the canonical
|
|
52
|
+
* transcript takes the rewritten system prefix. Without `messages`, `apply`
|
|
53
|
+
* runs on the session's own. `apply` runs synchronously.
|
|
54
|
+
*/
|
|
55
|
+
function applyOnWorkingMessages(session, messages, apply) {
|
|
56
|
+
const stored = session.messages;
|
|
57
|
+
const working = Array.isArray(messages) ? messages : stored;
|
|
58
|
+
if (Array.isArray(working)) session.messages = working;
|
|
59
|
+
try {
|
|
60
|
+
apply();
|
|
61
|
+
} finally {
|
|
62
|
+
if (Array.isArray(working) && session.messages !== working) {
|
|
63
|
+
// The rewrite may drop an emptied system message: keep the array
|
|
64
|
+
// identity the request loop holds.
|
|
65
|
+
working.splice(0, working.length, ...session.messages);
|
|
66
|
+
}
|
|
67
|
+
session.messages = stored;
|
|
68
|
+
}
|
|
69
|
+
// The rewritten prompt messages are fresh objects; the canonical transcript
|
|
70
|
+
// the save path persists takes them too, so the next save writes the new
|
|
71
|
+
// prompt together with the new exposure.
|
|
72
|
+
if (Array.isArray(stored) && Array.isArray(working) && stored !== working) syncSystemPrefix(stored, working);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Provider switch: rebuild the surface for `provider`, migrating bridge ↔
|
|
77
|
+
* native. `messages`: the running turn's transcript, when a switch happens
|
|
78
|
+
* mid-turn (a route fallback hop).
|
|
79
|
+
*/
|
|
80
|
+
export function rebuildToolSurfaceForProviderWithMigration(session, provider, messages) {
|
|
51
81
|
if (!session) return false;
|
|
52
82
|
const migrating = toolExposureMigrationNeeded(session, provider);
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
applyOnWorkingMessages(session, messages, () => {
|
|
84
|
+
rebuildDeferredToolSurfaceForProvider(session, provider);
|
|
85
|
+
if (migrating) markToolSurfaceMigrated(session);
|
|
86
|
+
});
|
|
55
87
|
return migrating;
|
|
56
88
|
}
|
|
57
89
|
|
|
@@ -75,26 +107,12 @@ function hasUnansweredToolCalls(messages) {
|
|
|
75
107
|
*/
|
|
76
108
|
export function migrateToolExposureAtBoundary(session, messages) {
|
|
77
109
|
if (!session || !toolExposureMigrationNeeded(session) || hasUnansweredToolCalls(messages)) return false;
|
|
78
|
-
|
|
79
|
-
const working = Array.isArray(messages) ? messages : stored;
|
|
80
|
-
if (Array.isArray(working)) session.messages = working;
|
|
81
|
-
try {
|
|
110
|
+
applyOnWorkingMessages(session, messages, () =>
|
|
82
111
|
applyDeferredToolSurface(session, session.deferredSurfaceMode || 'lead', [], {
|
|
83
112
|
provider: session.provider,
|
|
84
113
|
model: session.model,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
if (Array.isArray(working) && session.messages !== working) {
|
|
88
|
-
// The rewrite may drop an emptied system message: keep the request
|
|
89
|
-
// transcript array identity the loop holds.
|
|
90
|
-
working.splice(0, working.length, ...session.messages);
|
|
91
|
-
}
|
|
92
|
-
session.messages = stored;
|
|
93
|
-
}
|
|
94
|
-
// The rewritten prompt messages are fresh objects; the canonical transcript
|
|
95
|
-
// the save path persists takes them too, so the next save writes the new
|
|
96
|
-
// prompt together with the new exposure.
|
|
97
|
-
if (Array.isArray(stored) && Array.isArray(working) && stored !== working) syncSystemPrefix(stored, working);
|
|
114
|
+
})
|
|
115
|
+
);
|
|
98
116
|
invalidateForToolSurfaceMigration(session);
|
|
99
117
|
return true;
|
|
100
118
|
}
|
|
@@ -231,9 +231,9 @@ export function buildAgentLoopOptions({
|
|
|
231
231
|
},
|
|
232
232
|
onStreamDelta: (kind = 'semantic') => {
|
|
233
233
|
markSessionStreamDelta(sessionId, kind).catch(() => {});
|
|
234
|
-
// Raw transport
|
|
235
|
-
// Preserve the public callback's historical semantic-only contract.
|
|
236
|
-
if (kind !== 'transport') relay(askOpts?.onStreamDelta, kind);
|
|
234
|
+
// Raw transport and keepalives are internal health signals, not model
|
|
235
|
+
// progress. Preserve the public callback's historical semantic-only contract.
|
|
236
|
+
if (kind !== 'transport' && kind !== 'keepalive') relay(askOpts?.onStreamDelta, kind);
|
|
237
237
|
},
|
|
238
238
|
};
|
|
239
239
|
}
|
|
@@ -225,7 +225,9 @@ export function publishAskTurn({ sessionId, opened, terminalResultPreview, askOp
|
|
|
225
225
|
// (which inspects entry.emptyFinal first) classifies the
|
|
226
226
|
// terminal state correctly even if it ticks during unwind.
|
|
227
227
|
const isEmptyFinal =
|
|
228
|
-
!terminalResultPreview.content &&
|
|
228
|
+
!terminalResultPreview.content &&
|
|
229
|
+
!terminalResultPreview.reasoningContent &&
|
|
230
|
+
!terminalResultPreview.reactionCompleted;
|
|
229
231
|
if (isEmptyFinal) {
|
|
230
232
|
markSessionEmptyFinal(sessionId);
|
|
231
233
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { loadConfig } from '../../config.mjs';
|
|
14
14
|
import { findPreset } from '../../runtime-core/config-helpers.mjs';
|
|
15
15
|
import { normalizeWorkflowRoute } from '../../runtime-core/workflow.mjs';
|
|
16
|
-
import { canonicalRouteProvider } from '../../../../shared/model-route-chain.mjs';
|
|
16
|
+
import { canonicalRouteProvider, executionFallbacks } from '../../../../shared/model-route-chain.mjs';
|
|
17
17
|
import { storedBotRoute } from '../../../../shared/agent-store.mjs';
|
|
18
18
|
import { configuredAgentRouteCandidates } from '../../../../shared/agent-route-config.mjs';
|
|
19
19
|
import { CODING_MODE } from '../agent-mode.mjs';
|
|
@@ -48,5 +48,5 @@ export function ownerConfiguredRoute({ sessionMode, agentId, config = null }) {
|
|
|
48
48
|
export function ownerRouteFallbacks({ sessionMode, agentId, provider, model, config = null }) {
|
|
49
49
|
const route = ownerConfiguredRoute({ sessionMode, agentId, config });
|
|
50
50
|
if (!route?.fallbacks?.length) return [];
|
|
51
|
-
return sameRouteModel(route, { provider, model }) ? route
|
|
51
|
+
return sameRouteModel(route, { provider, model }) ? executionFallbacks(route) : [];
|
|
52
52
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { findPreset } from '../../runtime-core/config-helpers.mjs';
|
|
13
13
|
import { normalizeWorkflowRoute } from '../../runtime-core/workflow.mjs';
|
|
14
14
|
import { configuredAgentRouteCandidates } from '../../../../shared/agent-route-config.mjs';
|
|
15
|
+
import { executionFallbacks } from '../../../../shared/model-route-chain.mjs';
|
|
15
16
|
import { CODING_MODE, resolveSessionMode } from '../agent-mode.mjs';
|
|
16
17
|
import { _liveSessions } from '../store/live-state.mjs';
|
|
17
18
|
import { ownerConfiguredRoute, sameRouteModel } from './owner-route-fallbacks.mjs';
|
|
@@ -64,7 +65,7 @@ export function liveRouteFallbacks(session, config) {
|
|
|
64
65
|
if (!source)
|
|
65
66
|
return { chain: Array.isArray(session?.routeFallbacks) ? session.routeFallbacks : [], fromSettings: false };
|
|
66
67
|
const onSourcePrimary = sameRouteModel(source, sessionRoutePrimary(session));
|
|
67
|
-
return { chain: onSourcePrimary ? source
|
|
68
|
+
return { chain: onSourcePrimary ? executionFallbacks(source) : [], fromSettings: true };
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/** Index in `chain` of the next route to try: the first after the session's
|
|
@@ -59,15 +59,19 @@ function omitToolsKey(omitTools) {
|
|
|
59
59
|
.join(',');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export function _buildSharedRules({ omitTools = [], allowTools = null } = {}) {
|
|
62
|
+
export function _buildSharedRules({ omitTools = [], allowTools = null, ownerIsAgent = false } = {}) {
|
|
63
63
|
const PLUGIN_ROOT = mixdogRoot();
|
|
64
64
|
const RULES_DIR = join(PLUGIN_ROOT, 'rules');
|
|
65
65
|
return buildCachedRules(
|
|
66
66
|
'buildSharedToolContent',
|
|
67
67
|
'shared tool rules',
|
|
68
68
|
[join(RULES_DIR, 'shared')],
|
|
69
|
-
{ PLUGIN_ROOT, DATA_DIR: resolvePluginData(), omitTools, allowTools },
|
|
70
|
-
JSON.stringify([
|
|
69
|
+
{ PLUGIN_ROOT, DATA_DIR: resolvePluginData(), omitTools, allowTools, ownerIsAgent },
|
|
70
|
+
JSON.stringify([
|
|
71
|
+
omitToolsKey(omitTools),
|
|
72
|
+
Array.isArray(allowTools) ? omitToolsKey(allowTools) : null,
|
|
73
|
+
ownerIsAgent === true,
|
|
74
|
+
])
|
|
71
75
|
);
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -98,8 +102,17 @@ export function _buildRouteRules({ provider = null, model = null, omitTools = []
|
|
|
98
102
|
|
|
99
103
|
// BP1 = shared tool policy followed by this route's rules. Every site that
|
|
100
104
|
// renders or re-identifies the BP1 block must use this one composition.
|
|
101
|
-
export function _buildBaseRules({
|
|
102
|
-
|
|
105
|
+
export function _buildBaseRules({
|
|
106
|
+
provider = null,
|
|
107
|
+
model = null,
|
|
108
|
+
omitTools = [],
|
|
109
|
+
allowTools = null,
|
|
110
|
+
ownerIsAgent = false,
|
|
111
|
+
} = {}) {
|
|
112
|
+
return [
|
|
113
|
+
_buildSharedRules({ omitTools, allowTools, ownerIsAgent }),
|
|
114
|
+
_buildRouteRules({ provider, model, omitTools, allowTools }),
|
|
115
|
+
]
|
|
103
116
|
.filter(Boolean)
|
|
104
117
|
.join('\n\n---\n\n');
|
|
105
118
|
}
|
|
@@ -201,9 +214,14 @@ export function _buildLeadLanguage() {
|
|
|
201
214
|
});
|
|
202
215
|
}
|
|
203
216
|
|
|
204
|
-
export function _buildLeadLanguageContext() {
|
|
217
|
+
export function _buildLeadLanguageContext({ includeOutputStyle = true } = {}) {
|
|
218
|
+
const PLUGIN_ROOT = mixdogRoot();
|
|
205
219
|
const DATA_DIR = resolvePluginData();
|
|
206
|
-
return buildCachedRules(
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
return buildCachedRules(
|
|
221
|
+
'buildLeadLanguageContent',
|
|
222
|
+
'lead language context',
|
|
223
|
+
[join(DATA_DIR, 'mixdog-config.json'), join(PLUGIN_ROOT, 'output-styles'), join(DATA_DIR, 'output-styles')],
|
|
224
|
+
{ PLUGIN_ROOT, DATA_DIR, includeOutputStyle },
|
|
225
|
+
includeOutputStyle ? 'style' : 'no-style'
|
|
226
|
+
);
|
|
209
227
|
}
|