@oh-my-pi/pi-coding-agent 16.1.7 → 16.1.9
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/CHANGELOG.md +71 -0
- package/dist/cli.js +4266 -3446
- package/dist/types/cli/flag-tables.d.ts +17 -0
- package/dist/types/cli/session-picker.d.ts +0 -1
- package/dist/types/cli/tiny-models-cli.d.ts +2 -0
- package/dist/types/cli-commands.d.ts +4 -2
- package/dist/types/collab/protocol.d.ts +20 -1
- package/dist/types/config/settings-schema.d.ts +57 -6
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +5 -1
- package/dist/types/internal-urls/filesystem-resource.d.ts +9 -0
- package/dist/types/mcp/loader.d.ts +3 -2
- package/dist/types/mcp/manager.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +21 -4
- package/dist/types/mnemopi/config.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +1 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +1 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/btw-panel.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +0 -2
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/controllers/streaming-reveal.d.ts +3 -2
- package/dist/types/modes/interactive-mode.d.ts +4 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/session/agent-session.d.ts +18 -3
- package/dist/types/session/session-history-format.d.ts +25 -0
- package/dist/types/session/snapcompact-inline.d.ts +1 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +7 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +1 -0
- package/dist/types/tiny/models.d.ts +11 -7
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/todo.d.ts +1 -0
- package/dist/types/utils/thinking-display.d.ts +2 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +104 -0
- package/src/advisor/runtime.ts +38 -2
- package/src/cli/args.ts +0 -1
- package/src/cli/flag-tables.ts +42 -0
- package/src/cli/profile-bootstrap.ts +1 -11
- package/src/cli/session-picker.ts +1 -2
- package/src/cli/tiny-models-cli.ts +7 -2
- package/src/cli-commands.ts +48 -3
- package/src/collab/guest.ts +172 -20
- package/src/collab/host.ts +47 -5
- package/src/collab/protocol.ts +16 -1
- package/src/config/model-registry.ts +6 -7
- package/src/config/settings-schema.ts +59 -5
- package/src/edit/renderer.ts +8 -12
- package/src/extensibility/legacy-pi-ai-shim.ts +16 -4
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/local-protocol.ts +7 -1
- package/src/internal-urls/memory-protocol.ts +5 -1
- package/src/internal-urls/skill-protocol.ts +20 -4
- package/src/internal-urls/vault-protocol.ts +5 -2
- package/src/lsp/client.ts +39 -25
- package/src/main.ts +8 -8
- package/src/mcp/loader.ts +4 -3
- package/src/mcp/manager.ts +35 -15
- package/src/mcp/startup-events.ts +106 -11
- package/src/mnemopi/config.ts +2 -0
- package/src/mnemopi/state.ts +3 -1
- package/src/modes/components/agent-hub.ts +4 -0
- package/src/modes/components/agent-transcript-viewer.ts +2 -0
- package/src/modes/components/assistant-message.ts +217 -18
- package/src/modes/components/btw-panel.ts +15 -3
- package/src/modes/components/chat-transcript-builder.ts +8 -2
- package/src/modes/components/model-selector.ts +72 -9
- package/src/modes/components/omfg-panel.ts +1 -1
- package/src/modes/components/session-selector.ts +4 -9
- package/src/modes/components/snapcompact-shape-preview.ts +1 -1
- package/src/modes/components/tool-execution.ts +10 -2
- package/src/modes/controllers/btw-controller.ts +32 -7
- package/src/modes/controllers/command-controller.ts +18 -3
- package/src/modes/controllers/event-controller.ts +24 -4
- package/src/modes/controllers/input-controller.ts +43 -21
- package/src/modes/controllers/mcp-command-controller.ts +40 -12
- package/src/modes/controllers/selector-controller.ts +23 -13
- package/src/modes/controllers/streaming-reveal.ts +78 -20
- package/src/modes/controllers/todo-command-controller.ts +9 -10
- package/src/modes/interactive-mode.ts +84 -9
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/advisor/system.md +1 -1
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- package/src/sdk.ts +12 -8
- package/src/session/agent-session.ts +385 -85
- package/src/session/history-storage.ts +15 -16
- package/src/session/session-history-format.ts +41 -1
- package/src/session/snapcompact-inline.ts +9 -6
- package/src/slash-commands/builtin-registry.ts +171 -87
- package/src/slash-commands/helpers/todo.ts +12 -6
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +6 -11
- package/src/tiny/models.ts +7 -3
- package/src/tiny/title-client.ts +8 -2
- package/src/tiny/worker.ts +1 -0
- package/src/tools/browser/launch.ts +107 -31
- package/src/tools/puppeteer/00_stealth_tampering.txt +16 -35
- package/src/tools/puppeteer/01_stealth_activity.txt +79 -19
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -11
- package/src/tools/puppeteer/03_stealth_botd.txt +10 -14
- package/src/tools/puppeteer/04_stealth_iframe.txt +156 -63
- package/src/tools/puppeteer/05_stealth_webgl.txt +222 -64
- package/src/tools/puppeteer/06_stealth_screen.txt +255 -67
- package/src/tools/puppeteer/07_stealth_fonts.txt +17 -15
- package/src/tools/puppeteer/08_stealth_audio.txt +32 -20
- package/src/tools/puppeteer/09_stealth_locale.txt +45 -40
- package/src/tools/puppeteer/10_stealth_plugins.txt +12 -8
- package/src/tools/puppeteer/11_stealth_hardware.txt +57 -6
- package/src/tools/puppeteer/12_stealth_codecs.txt +20 -18
- package/src/tools/puppeteer/13_stealth_worker.txt +206 -45
- package/src/tools/search.ts +10 -1
- package/src/tools/sqlite-reader.ts +59 -3
- package/src/tools/todo.ts +6 -0
- package/src/tools/write.ts +4 -6
- package/src/utils/thinking-display.ts +78 -0
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
collectEntriesForBranchSummary,
|
|
50
50
|
collectShakeRegions,
|
|
51
51
|
compact,
|
|
52
|
+
compactionContextTokens,
|
|
52
53
|
createCompactionSummaryMessage,
|
|
53
54
|
DEFAULT_SHAKE_CONFIG,
|
|
54
55
|
effectiveReserveTokens,
|
|
@@ -74,7 +75,6 @@ import {
|
|
|
74
75
|
import type { ProtectedToolMatcher } from "@oh-my-pi/pi-agent-core/compaction/tool-protection";
|
|
75
76
|
import type {
|
|
76
77
|
AssistantMessage,
|
|
77
|
-
Context,
|
|
78
78
|
ImageContent,
|
|
79
79
|
Message,
|
|
80
80
|
MessageAttribution,
|
|
@@ -103,8 +103,9 @@ import {
|
|
|
103
103
|
resolveServiceTier,
|
|
104
104
|
streamSimple,
|
|
105
105
|
} from "@oh-my-pi/pi-ai";
|
|
106
|
-
import { stripToolDescriptions } from "@oh-my-pi/pi-ai/utils/schema";
|
|
106
|
+
import { stripToolDescriptions, toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
107
107
|
import { THINKING_LOOP_ERROR_MARKER } from "@oh-my-pi/pi-ai/utils/thinking-loop";
|
|
108
|
+
import { isFireworksFastModelId, toFireworksBaseModelId } from "@oh-my-pi/pi-catalog/fireworks-model-id";
|
|
108
109
|
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
109
110
|
import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
110
111
|
import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
@@ -158,7 +159,7 @@ import {
|
|
|
158
159
|
import { MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
|
|
159
160
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
160
161
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
161
|
-
import { onAppendOnlyModeChanged } from "../config/settings";
|
|
162
|
+
import { getDefault, onAppendOnlyModeChanged } from "../config/settings";
|
|
162
163
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
163
164
|
import { loadCapability } from "../discovery";
|
|
164
165
|
import { expandApplyPatchToEntries, normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
|
@@ -228,6 +229,7 @@ import planModeReferencePrompt from "../prompts/system/plan-mode-reference.md" w
|
|
|
228
229
|
import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool-decision-reminder.md" with {
|
|
229
230
|
type: "text",
|
|
230
231
|
};
|
|
232
|
+
import sideChannelNoToolsReminder from "../prompts/system/side-channel-no-tools.md" with { type: "text" };
|
|
231
233
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
232
234
|
import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" with { type: "text" };
|
|
233
235
|
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
@@ -311,6 +313,22 @@ import { YieldQueue } from "./yield-queue";
|
|
|
311
313
|
|
|
312
314
|
const SESSION_STOP_CONTINUATION_CAP = 8;
|
|
313
315
|
|
|
316
|
+
// A side-channel assistant response is signed for the hidden prompt/history that
|
|
317
|
+
// produced it. If we persist that response under a different user turn, native
|
|
318
|
+
// replay anchors become invalid; keep only visible, non-cryptographic content.
|
|
319
|
+
function sanitizeAssistantForReparentedHistory(message: AssistantMessage): AssistantMessage {
|
|
320
|
+
const content: AssistantMessage["content"] = [];
|
|
321
|
+
for (const block of message.content) {
|
|
322
|
+
if (block.type === "redactedThinking") continue;
|
|
323
|
+
if (block.type === "thinking") {
|
|
324
|
+
content.push({ type: "thinking", thinking: block.thinking });
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
content.push(block);
|
|
328
|
+
}
|
|
329
|
+
return { ...message, content, providerPayload: undefined };
|
|
330
|
+
}
|
|
331
|
+
|
|
314
332
|
/** Session-specific events that extend the core AgentEvent */
|
|
315
333
|
export type AgentSessionEvent =
|
|
316
334
|
| AgentEvent
|
|
@@ -1251,6 +1269,7 @@ export class AgentSession {
|
|
|
1251
1269
|
#disconnectOwnedMcpManager: (() => Promise<void>) | undefined;
|
|
1252
1270
|
#requestedToolNames: ReadonlySet<string> | undefined;
|
|
1253
1271
|
#baseSystemPrompt: string[];
|
|
1272
|
+
#baseSystemPromptBeforeMemoryPromotion: string[] | undefined;
|
|
1254
1273
|
/**
|
|
1255
1274
|
* Signature of the (toolNames, tool descriptions) tuple passed to the most
|
|
1256
1275
|
* recent successful `rebuildSystemPrompt` call. Used to skip redundant rebuilds
|
|
@@ -1337,6 +1356,7 @@ export class AgentSession {
|
|
|
1337
1356
|
#pruneToolDescriptions = false;
|
|
1338
1357
|
#checkpointState: CheckpointState | undefined = undefined;
|
|
1339
1358
|
#pendingRewindReport: string | undefined = undefined;
|
|
1359
|
+
#rewoundToolResultIds = new Set<string>();
|
|
1340
1360
|
#lastSuccessfulYieldToolCallId: string | undefined = undefined;
|
|
1341
1361
|
#providerSessionState = new Map<string, ProviderSessionState>();
|
|
1342
1362
|
#hindsightSessionState: HindsightSessionState | undefined = undefined;
|
|
@@ -1537,6 +1557,10 @@ export class AgentSession {
|
|
|
1537
1557
|
this.#customCommands = config.customCommands ?? [];
|
|
1538
1558
|
this.#skillsSettings = config.skillsSettings;
|
|
1539
1559
|
this.#modelRegistry = config.modelRegistry;
|
|
1560
|
+
// Resolve the wire service-tier per request so the Fireworks Priority
|
|
1561
|
+
// toggle scopes priority to Fireworks alone, without mutating the shared
|
|
1562
|
+
// session `serviceTier` that drives `/fast` and OpenAI/Anthropic priority.
|
|
1563
|
+
this.agent.serviceTierResolver = model => this.#effectiveServiceTier(model);
|
|
1540
1564
|
this.#advisorReadOnlyTools = config.advisorReadOnlyTools;
|
|
1541
1565
|
this.#advisorWatchdogPrompt = config.advisorWatchdogPrompt;
|
|
1542
1566
|
this.#pruneToolDescriptions = config.pruneToolDescriptions === true;
|
|
@@ -1574,6 +1598,11 @@ export class AgentSession {
|
|
|
1574
1598
|
this.agent.setRawSseEventInterceptor(this.#onSseEvent);
|
|
1575
1599
|
this.agent.setOnTurnEnd(async (messages, signal) => {
|
|
1576
1600
|
if (signal?.aborted) return;
|
|
1601
|
+
const rewindReport = this.#extractRewindReport(messages);
|
|
1602
|
+
if (rewindReport) {
|
|
1603
|
+
this.#pendingRewindReport = undefined;
|
|
1604
|
+
await this.#applyRewind(rewindReport, messages);
|
|
1605
|
+
}
|
|
1577
1606
|
this.#advisorPrimaryTurnsCompleted++;
|
|
1578
1607
|
if (this.#advisorRuntime && !this.#advisorRuntime.disposed) {
|
|
1579
1608
|
this.#advisorRuntime.onTurnEnd(messages);
|
|
@@ -2516,11 +2545,6 @@ export class AgentSession {
|
|
|
2516
2545
|
if (event.type === "tool_execution_end" && event.toolName === "yield" && !event.isError) {
|
|
2517
2546
|
this.#lastSuccessfulYieldToolCallId = event.toolCallId;
|
|
2518
2547
|
}
|
|
2519
|
-
if (event.type === "turn_end" && this.#pendingRewindReport) {
|
|
2520
|
-
const report = this.#pendingRewindReport;
|
|
2521
|
-
this.#pendingRewindReport = undefined;
|
|
2522
|
-
await this.#applyRewind(report);
|
|
2523
|
-
}
|
|
2524
2548
|
|
|
2525
2549
|
// TTSR: Check for pattern matches on assistant text/thinking and tool argument deltas
|
|
2526
2550
|
if (event.type === "message_update" && this.#ttsrManager?.hasRules()) {
|
|
@@ -2603,7 +2627,13 @@ export class AgentSession {
|
|
|
2603
2627
|
};
|
|
2604
2628
|
}
|
|
2605
2629
|
}
|
|
2606
|
-
|
|
2630
|
+
const skipPersistedRewindResult =
|
|
2631
|
+
event.message.role === "toolResult" &&
|
|
2632
|
+
event.message.toolName === "rewind" &&
|
|
2633
|
+
this.#rewoundToolResultIds.delete(event.message.toolCallId);
|
|
2634
|
+
if (!skipPersistedRewindResult) {
|
|
2635
|
+
this.sessionManager.appendMessage(event.message);
|
|
2636
|
+
}
|
|
2607
2637
|
}
|
|
2608
2638
|
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
2609
2639
|
|
|
@@ -2719,8 +2749,9 @@ export class AgentSession {
|
|
|
2719
2749
|
|
|
2720
2750
|
// Check auto-retry and auto-compaction after agent completes
|
|
2721
2751
|
if (event.type === "agent_end") {
|
|
2752
|
+
const settledMessages = this.agent.state.messages;
|
|
2722
2753
|
const emitAgentEndNotification = async () => {
|
|
2723
|
-
await this.#emitAgentEndNotification(
|
|
2754
|
+
await this.#emitAgentEndNotification(settledMessages);
|
|
2724
2755
|
};
|
|
2725
2756
|
const usage = this.getSessionStats().tokens;
|
|
2726
2757
|
await this.#goalRuntime.onAgentEnd({
|
|
@@ -2731,7 +2762,7 @@ export class AgentSession {
|
|
|
2731
2762
|
cacheWrite: usage.cacheWrite,
|
|
2732
2763
|
},
|
|
2733
2764
|
});
|
|
2734
|
-
const fallbackAssistant = [...
|
|
2765
|
+
const fallbackAssistant = [...settledMessages]
|
|
2735
2766
|
.reverse()
|
|
2736
2767
|
.find((message): message is AssistantMessage => message.role === "assistant");
|
|
2737
2768
|
const msg = this.#lastAssistantMessage ?? fallbackAssistant;
|
|
@@ -2761,6 +2792,11 @@ export class AgentSession {
|
|
|
2761
2792
|
|
|
2762
2793
|
if (this.#assistantEndedWithSuccessfulYield(msg)) {
|
|
2763
2794
|
this.#lastSuccessfulYieldToolCallId = undefined;
|
|
2795
|
+
if (this.#goalModeState?.enabled && this.#goalModeState.goal.status === "active") {
|
|
2796
|
+
const compactionTask = this.#checkCompaction(msg);
|
|
2797
|
+
this.#trackPostPromptTask(compactionTask);
|
|
2798
|
+
await compactionTask;
|
|
2799
|
+
}
|
|
2764
2800
|
await emitAgentEndNotification();
|
|
2765
2801
|
return;
|
|
2766
2802
|
}
|
|
@@ -2790,6 +2826,16 @@ export class AgentSession {
|
|
|
2790
2826
|
await emitAgentEndNotification();
|
|
2791
2827
|
return;
|
|
2792
2828
|
}
|
|
2829
|
+
// Fireworks Fast variants degrade to their base model on a failed turn —
|
|
2830
|
+
// including hard router errors the generic retry classifier rejects — so
|
|
2831
|
+
// run this gate before the standard retryability check.
|
|
2832
|
+
if (this.#isFireworksFastFallbackEligible(msg)) {
|
|
2833
|
+
const didRetry = await this.#handleRetryableError(msg, { fireworksFastFallback: true });
|
|
2834
|
+
if (didRetry) {
|
|
2835
|
+
await emitAgentEndNotification();
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2793
2839
|
// Check for retryable errors first (overloaded, rate limit, server errors)
|
|
2794
2840
|
if (this.#isRetryableError(msg)) {
|
|
2795
2841
|
const didRetry = await this.#handleRetryableError(msg);
|
|
@@ -2828,7 +2874,7 @@ export class AgentSession {
|
|
|
2828
2874
|
return;
|
|
2829
2875
|
}
|
|
2830
2876
|
}
|
|
2831
|
-
await this.#emitSessionStopEvent(
|
|
2877
|
+
await this.#emitSessionStopEvent(settledMessages);
|
|
2832
2878
|
await emitAgentEndNotification();
|
|
2833
2879
|
}
|
|
2834
2880
|
};
|
|
@@ -4084,18 +4130,34 @@ export class AgentSession {
|
|
|
4084
4130
|
}
|
|
4085
4131
|
|
|
4086
4132
|
/** New session file: reset auto-recall / retain-threshold counters for the new transcript. */
|
|
4087
|
-
#resetHindsightConversationTrackingIfHindsight():
|
|
4088
|
-
if (this.settings.get("memory.backend") !== "hindsight") return;
|
|
4133
|
+
#resetHindsightConversationTrackingIfHindsight(): boolean {
|
|
4134
|
+
if (this.settings.get("memory.backend") !== "hindsight") return false;
|
|
4089
4135
|
const state = this.getHindsightSessionState();
|
|
4090
|
-
if (!state || state.aliasOf) return;
|
|
4136
|
+
if (!state || state.aliasOf) return false;
|
|
4091
4137
|
state.resetConversationTracking();
|
|
4138
|
+
return true;
|
|
4092
4139
|
}
|
|
4093
4140
|
|
|
4094
|
-
#resetMnemopiConversationTrackingIfMnemopi():
|
|
4095
|
-
if (this.settings.get("memory.backend") !== "mnemopi") return;
|
|
4141
|
+
#resetMnemopiConversationTrackingIfMnemopi(): boolean {
|
|
4142
|
+
if (this.settings.get("memory.backend") !== "mnemopi") return false;
|
|
4096
4143
|
const state = this.getMnemopiSessionState();
|
|
4097
|
-
if (!state || state.aliasOf) return;
|
|
4144
|
+
if (!state || state.aliasOf) return false;
|
|
4098
4145
|
state.resetConversationTracking();
|
|
4146
|
+
return true;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
async #resetMemoryContextForNewTranscript(): Promise<void> {
|
|
4150
|
+
const hadPromotedMemoryPrompt = this.#baseSystemPromptBeforeMemoryPromotion !== undefined;
|
|
4151
|
+
const resetHindsight = this.#resetHindsightConversationTrackingIfHindsight();
|
|
4152
|
+
const resetMnemopi = this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
4153
|
+
if (hadPromotedMemoryPrompt) {
|
|
4154
|
+
this.#baseSystemPrompt = this.#baseSystemPromptBeforeMemoryPromotion!;
|
|
4155
|
+
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
4156
|
+
this.#baseSystemPromptBeforeMemoryPromotion = undefined;
|
|
4157
|
+
}
|
|
4158
|
+
if (resetHindsight || resetMnemopi || hadPromotedMemoryPrompt) {
|
|
4159
|
+
await this.refreshBaseSystemPrompt();
|
|
4160
|
+
}
|
|
4099
4161
|
}
|
|
4100
4162
|
|
|
4101
4163
|
/** True once dispose() has begun; deferred background work (e.g. the deferred
|
|
@@ -4783,6 +4845,7 @@ export class AgentSession {
|
|
|
4783
4845
|
if (signature !== this.#lastAppliedToolSignature) {
|
|
4784
4846
|
const built = await this.#rebuildSystemPrompt(validToolNames, this.#toolRegistry);
|
|
4785
4847
|
this.#baseSystemPrompt = built.systemPrompt;
|
|
4848
|
+
this.#baseSystemPromptBeforeMemoryPromotion = undefined;
|
|
4786
4849
|
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
4787
4850
|
this.#lastAppliedToolSignature = signature;
|
|
4788
4851
|
this.#promptModelKey = this.#currentPromptModelKey();
|
|
@@ -4867,6 +4930,7 @@ export class AgentSession {
|
|
|
4867
4930
|
const activeToolNames = this.getActiveToolNames();
|
|
4868
4931
|
const built = await this.#rebuildSystemPrompt(activeToolNames, this.#toolRegistry);
|
|
4869
4932
|
this.#baseSystemPrompt = built.systemPrompt;
|
|
4933
|
+
this.#baseSystemPromptBeforeMemoryPromotion = undefined;
|
|
4870
4934
|
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
4871
4935
|
this.#promptModelKey = this.#currentPromptModelKey();
|
|
4872
4936
|
// Refresh the cached signature so a subsequent `#applyActiveToolsByName` with
|
|
@@ -4885,7 +4949,29 @@ export class AgentSession {
|
|
|
4885
4949
|
try {
|
|
4886
4950
|
const injected = await backend.beforeAgentStartPrompt(this, promptText);
|
|
4887
4951
|
if (!injected) return this.#baseSystemPrompt;
|
|
4888
|
-
|
|
4952
|
+
|
|
4953
|
+
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
|
4954
|
+
try {
|
|
4955
|
+
await this.refreshBaseSystemPrompt();
|
|
4956
|
+
} catch (refreshErr) {
|
|
4957
|
+
logger.debug("Memory backend prompt refresh after beforeAgentStartPrompt failed", {
|
|
4958
|
+
backend: backend.id,
|
|
4959
|
+
error: String(refreshErr),
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
if (
|
|
4964
|
+
this.#baseSystemPrompt.length !== previousBaseSystemPrompt.length ||
|
|
4965
|
+
this.#baseSystemPrompt.some((part, index) => part !== previousBaseSystemPrompt[index])
|
|
4966
|
+
) {
|
|
4967
|
+
return this.#baseSystemPrompt;
|
|
4968
|
+
}
|
|
4969
|
+
|
|
4970
|
+
this.#baseSystemPromptBeforeMemoryPromotion ??= previousBaseSystemPrompt;
|
|
4971
|
+
const stablePrompt = [...previousBaseSystemPrompt, injected];
|
|
4972
|
+
this.#baseSystemPrompt = stablePrompt;
|
|
4973
|
+
this.agent.setSystemPrompt(stablePrompt);
|
|
4974
|
+
return stablePrompt;
|
|
4889
4975
|
} catch (err) {
|
|
4890
4976
|
logger.debug("Memory backend beforeAgentStartPrompt failed", {
|
|
4891
4977
|
backend: backend.id,
|
|
@@ -6745,8 +6831,7 @@ export class AgentSession {
|
|
|
6745
6831
|
this.#syncAgentSessionId();
|
|
6746
6832
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
6747
6833
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
6748
|
-
this.#
|
|
6749
|
-
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
6834
|
+
await this.#resetMemoryContextForNewTranscript();
|
|
6750
6835
|
this.#pendingNextTurnMessages = [];
|
|
6751
6836
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
6752
6837
|
|
|
@@ -6843,7 +6928,7 @@ export class AgentSession {
|
|
|
6843
6928
|
this.#syncAgentSessionId();
|
|
6844
6929
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
6845
6930
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
6846
|
-
this.#
|
|
6931
|
+
await this.#resetMemoryContextForNewTranscript();
|
|
6847
6932
|
|
|
6848
6933
|
// Emit session_switch event with reason "fork" to hooks
|
|
6849
6934
|
if (this.#extensionRunner) {
|
|
@@ -7279,7 +7364,25 @@ export class AgentSession {
|
|
|
7279
7364
|
* no model is selected.
|
|
7280
7365
|
*/
|
|
7281
7366
|
isFastModeActive(): boolean {
|
|
7282
|
-
return resolveServiceTier(this
|
|
7367
|
+
return resolveServiceTier(this.#effectiveServiceTier(), this.model?.provider) === "priority";
|
|
7368
|
+
}
|
|
7369
|
+
|
|
7370
|
+
/**
|
|
7371
|
+
* Effective wire service-tier for a request to `model`. Fireworks models
|
|
7372
|
+
* take the Priority serving path only when the Providers › Fireworks Tier
|
|
7373
|
+
* setting is `"priority"` — that toggle is the sole opt-in, so a global
|
|
7374
|
+
* `serviceTier: "priority"` (for OpenAI/Anthropic) never silently incurs
|
|
7375
|
+
* Fireworks priority costs — and never for `-fast` variants, whose Fast
|
|
7376
|
+
* serving path is mutually exclusive with Priority. Every other provider
|
|
7377
|
+
* uses the session `serviceTier` unchanged.
|
|
7378
|
+
*/
|
|
7379
|
+
#effectiveServiceTier(model: Model | undefined = this.model): ServiceTier | undefined {
|
|
7380
|
+
if (model?.provider === "fireworks") {
|
|
7381
|
+
return this.settings.get("providers.fireworksTier") === "priority" && !isFireworksFastModelId(model.id)
|
|
7382
|
+
? "priority"
|
|
7383
|
+
: undefined;
|
|
7384
|
+
}
|
|
7385
|
+
return this.serviceTier;
|
|
7283
7386
|
}
|
|
7284
7387
|
|
|
7285
7388
|
setServiceTier(serviceTier: ServiceTier | undefined): void {
|
|
@@ -7991,8 +8094,7 @@ export class AgentSession {
|
|
|
7991
8094
|
this.#syncAgentSessionId();
|
|
7992
8095
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
7993
8096
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
7994
|
-
this.#
|
|
7995
|
-
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
8097
|
+
await this.#resetMemoryContextForNewTranscript();
|
|
7996
8098
|
this.#pendingNextTurnMessages = [];
|
|
7997
8099
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
7998
8100
|
this.#todoReminderCount = 0;
|
|
@@ -8039,14 +8141,39 @@ export class AgentSession {
|
|
|
8039
8141
|
}
|
|
8040
8142
|
}
|
|
8041
8143
|
|
|
8042
|
-
|
|
8043
|
-
|
|
8144
|
+
/**
|
|
8145
|
+
* Local token estimate of the stored conversation (plus any pending messages),
|
|
8146
|
+
* independent of provider-reported usage. A `before_provider_request` hook
|
|
8147
|
+
* (e.g. a compression extension such as Headroom) or other on-wire payload
|
|
8148
|
+
* transform can shrink the request below the real stored conversation; the
|
|
8149
|
+
* provider then reports deflated prompt tokens, so anchoring the compaction
|
|
8150
|
+
* decision purely on that usage lets the real history grow unbounded until it
|
|
8151
|
+
* overflows and native compaction can no longer run. This estimate is the
|
|
8152
|
+
* floor the compaction decision respects so on-wire compression can never
|
|
8153
|
+
* suppress it.
|
|
8154
|
+
*/
|
|
8155
|
+
#estimateStoredContextTokens(pendingMessages: AgentMessage[] = []): number {
|
|
8156
|
+
// Exclude encrypted reasoning (thinkingSignature / redactedThinking): its
|
|
8157
|
+
// local byte size diverges from what the provider bills, so counting it here
|
|
8158
|
+
// would let a thinking-heavy turn falsely trip the floor. The provider usage
|
|
8159
|
+
// (the other arm of compactionContextTokens) already accounts for it.
|
|
8160
|
+
const opts = { excludeEncryptedReasoning: true } as const;
|
|
8044
8161
|
return (
|
|
8045
|
-
|
|
8046
|
-
|
|
8162
|
+
computeNonMessageTokens(this) +
|
|
8163
|
+
this.messages.reduce((sum, msg) => sum + estimateTokens(msg, opts), 0) +
|
|
8164
|
+
pendingMessages.reduce((sum, msg) => sum + estimateTokens(msg, opts), 0)
|
|
8047
8165
|
);
|
|
8048
8166
|
}
|
|
8049
8167
|
|
|
8168
|
+
#estimatePrePromptContextTokens(messages: AgentMessage[], contextWindow: number): number {
|
|
8169
|
+
const breakdown = this.getContextBreakdown({ contextWindow, pendingMessages: messages });
|
|
8170
|
+
const localEstimate = this.#estimateStoredContextTokens(messages);
|
|
8171
|
+
// Floor by the local estimate: a payload-shrinking before_provider_request
|
|
8172
|
+
// hook deflates the provider-anchored breakdown, which must not suppress
|
|
8173
|
+
// pre-prompt compaction (see #estimateStoredContextTokens).
|
|
8174
|
+
return compactionContextTokens(breakdown?.usedTokens ?? 0, localEstimate);
|
|
8175
|
+
}
|
|
8176
|
+
|
|
8050
8177
|
async #runPrePromptCompactionIfNeeded(messages: AgentMessage[]): Promise<void> {
|
|
8051
8178
|
const model = this.model;
|
|
8052
8179
|
if (!model) return;
|
|
@@ -8074,7 +8201,10 @@ export class AgentSession {
|
|
|
8074
8201
|
contextWindow,
|
|
8075
8202
|
model: `${model.provider}/${model.id}`,
|
|
8076
8203
|
});
|
|
8077
|
-
await this.#runAutoCompaction("threshold", false, false, false, {
|
|
8204
|
+
await this.#runAutoCompaction("threshold", false, false, false, {
|
|
8205
|
+
autoContinue: false,
|
|
8206
|
+
triggerContextTokens: contextTokens,
|
|
8207
|
+
});
|
|
8078
8208
|
}
|
|
8079
8209
|
|
|
8080
8210
|
/**
|
|
@@ -8209,6 +8339,12 @@ export class AgentSession {
|
|
|
8209
8339
|
if (pruneResult) {
|
|
8210
8340
|
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
8211
8341
|
}
|
|
8342
|
+
// Floor by the real stored-conversation estimate so a payload-shrinking
|
|
8343
|
+
// before_provider_request hook (e.g. a compression extension such as
|
|
8344
|
+
// Headroom) can't deflate the provider-reported usage below the true
|
|
8345
|
+
// history size and skip the threshold. The estimate runs after the prune
|
|
8346
|
+
// passes above, so it reflects the post-prune message set.
|
|
8347
|
+
contextTokens = compactionContextTokens(contextTokens, this.#estimateStoredContextTokens());
|
|
8212
8348
|
if (shouldCompact(contextTokens, contextWindow, compactionSettings)) {
|
|
8213
8349
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
8214
8350
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
@@ -8427,14 +8563,29 @@ export class AgentSession {
|
|
|
8427
8563
|
return true;
|
|
8428
8564
|
}
|
|
8429
8565
|
|
|
8430
|
-
|
|
8566
|
+
#extractRewindReport(messages: AgentMessage[]): string | undefined {
|
|
8567
|
+
if (!this.#checkpointState) return undefined;
|
|
8568
|
+
if (this.#pendingRewindReport) return this.#pendingRewindReport;
|
|
8569
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
8570
|
+
const message = messages[i];
|
|
8571
|
+
if (message?.role !== "toolResult" || message.toolName !== "rewind" || message.isError) continue;
|
|
8572
|
+
const details = message.details;
|
|
8573
|
+
const detailReport =
|
|
8574
|
+
details && typeof details === "object" && "report" in details && typeof details.report === "string"
|
|
8575
|
+
? details.report.trim()
|
|
8576
|
+
: "";
|
|
8577
|
+
const textReport = message.content.find(part => part.type === "text")?.text.trim() ?? "";
|
|
8578
|
+
const report = detailReport || textReport;
|
|
8579
|
+
return report.length > 0 ? report : undefined;
|
|
8580
|
+
}
|
|
8581
|
+
return undefined;
|
|
8582
|
+
}
|
|
8583
|
+
|
|
8584
|
+
async #applyRewind(report: string, activeMessages?: AgentMessage[]): Promise<void> {
|
|
8431
8585
|
const checkpointState = this.#checkpointState;
|
|
8432
8586
|
if (!checkpointState) {
|
|
8433
8587
|
return;
|
|
8434
8588
|
}
|
|
8435
|
-
const safeCount = Math.max(0, Math.min(checkpointState.checkpointMessageCount, this.agent.state.messages.length));
|
|
8436
|
-
this.agent.replaceMessages(this.agent.state.messages.slice(0, safeCount));
|
|
8437
|
-
this.#advisorRuntime?.reset();
|
|
8438
8589
|
try {
|
|
8439
8590
|
this.sessionManager.branchWithSummary(checkpointState.checkpointEntryId, report, {
|
|
8440
8591
|
startedAt: checkpointState.startedAt,
|
|
@@ -8446,16 +8597,24 @@ export class AgentSession {
|
|
|
8446
8597
|
this.sessionManager.branchWithSummary(null, report, { startedAt: checkpointState.startedAt });
|
|
8447
8598
|
}
|
|
8448
8599
|
const details = { startedAt: checkpointState.startedAt, rewoundAt: new Date().toISOString() };
|
|
8449
|
-
this.agent.appendMessage({
|
|
8450
|
-
role: "custom",
|
|
8451
|
-
customType: "rewind-report",
|
|
8452
|
-
content: report,
|
|
8453
|
-
display: false,
|
|
8454
|
-
details,
|
|
8455
|
-
attribution: "agent",
|
|
8456
|
-
timestamp: Date.now(),
|
|
8457
|
-
});
|
|
8458
8600
|
this.sessionManager.appendCustomMessageEntry("rewind-report", report, false, details, "agent");
|
|
8601
|
+
|
|
8602
|
+
if (activeMessages) {
|
|
8603
|
+
for (const message of activeMessages) {
|
|
8604
|
+
if (message.role === "toolResult" && message.toolName === "rewind") {
|
|
8605
|
+
this.#rewoundToolResultIds.add(message.toolCallId);
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
}
|
|
8609
|
+
const sessionContext = this.buildDisplaySessionContext();
|
|
8610
|
+
if (activeMessages) {
|
|
8611
|
+
activeMessages.splice(0, activeMessages.length, ...sessionContext.messages);
|
|
8612
|
+
}
|
|
8613
|
+
await this.#restoreMCPSelectionsForSessionContext(sessionContext);
|
|
8614
|
+
this.agent.replaceMessages(activeMessages ?? sessionContext.messages);
|
|
8615
|
+
this.#resetAdvisorSessionState();
|
|
8616
|
+
this.#syncTodoPhasesFromBranch();
|
|
8617
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
8459
8618
|
this.#checkpointState = undefined;
|
|
8460
8619
|
this.#pendingRewindReport = undefined;
|
|
8461
8620
|
}
|
|
@@ -9495,29 +9654,50 @@ export class AgentSession {
|
|
|
9495
9654
|
// summarizer path.
|
|
9496
9655
|
let snapcompactResult: snapcompact.CompactionResult | undefined;
|
|
9497
9656
|
if (action === "snapcompact" && compactionPrep.kind !== "fromHook") {
|
|
9498
|
-
|
|
9499
|
-
convertToLlm,
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
:
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
logger.warn("Snapcompact
|
|
9657
|
+
const text = snapcompact.serializeConversation(
|
|
9658
|
+
convertToLlm(preparation.messagesToSummarize.concat(preparation.turnPrefixMessages)),
|
|
9659
|
+
);
|
|
9660
|
+
const renderScan = snapcompact.scanRenderability(text);
|
|
9661
|
+
if (renderScan.isSafe) {
|
|
9662
|
+
snapcompactResult = await snapcompact.compact(preparation, {
|
|
9663
|
+
convertToLlm,
|
|
9664
|
+
model: this.model,
|
|
9665
|
+
shape: snapcompact.resolveShape(this.model, this.settings.get("snapcompact.shape")),
|
|
9666
|
+
});
|
|
9667
|
+
} else {
|
|
9668
|
+
logger.warn("Snapcompact disabled: high non-ASCII rate detected; falling back to an LLM summary", {
|
|
9510
9669
|
model: this.model?.id,
|
|
9511
|
-
|
|
9512
|
-
budget,
|
|
9670
|
+
unrenderableRatio: renderScan.unrenderableRatio,
|
|
9513
9671
|
});
|
|
9514
9672
|
this.emitNotice(
|
|
9515
9673
|
"warning",
|
|
9516
|
-
|
|
9674
|
+
`snapcompact disabled: high non-ASCII rate detected (${(renderScan.unrenderableRatio * 100).toFixed(1)}%). Falling back to an LLM summary to prevent data loss.`,
|
|
9517
9675
|
"compaction",
|
|
9518
9676
|
);
|
|
9519
9677
|
action = "context-full";
|
|
9520
|
-
|
|
9678
|
+
}
|
|
9679
|
+
|
|
9680
|
+
if (snapcompactResult) {
|
|
9681
|
+
const ctxWindow = this.model?.contextWindow ?? 0;
|
|
9682
|
+
const budget =
|
|
9683
|
+
ctxWindow > 0
|
|
9684
|
+
? ctxWindow - effectiveReserveTokens(ctxWindow, compactionSettings)
|
|
9685
|
+
: Number.POSITIVE_INFINITY;
|
|
9686
|
+
const projected = this.#projectSnapcompactContextTokens(preparation, snapcompactResult);
|
|
9687
|
+
if (projected > budget) {
|
|
9688
|
+
logger.warn("Snapcompact still overflows the window; falling back to an LLM summary", {
|
|
9689
|
+
model: this.model?.id,
|
|
9690
|
+
projected,
|
|
9691
|
+
budget,
|
|
9692
|
+
});
|
|
9693
|
+
this.emitNotice(
|
|
9694
|
+
"warning",
|
|
9695
|
+
"snapcompact could not bring the context under the limit — using an LLM summary instead",
|
|
9696
|
+
"compaction",
|
|
9697
|
+
);
|
|
9698
|
+
action = "context-full";
|
|
9699
|
+
snapcompactResult = undefined;
|
|
9700
|
+
}
|
|
9521
9701
|
}
|
|
9522
9702
|
}
|
|
9523
9703
|
|
|
@@ -9922,7 +10102,8 @@ export class AgentSession {
|
|
|
9922
10102
|
result: undefined,
|
|
9923
10103
|
aborted: false,
|
|
9924
10104
|
willRetry: false,
|
|
9925
|
-
errorMessage:
|
|
10105
|
+
errorMessage: message,
|
|
10106
|
+
skipped: false,
|
|
9926
10107
|
});
|
|
9927
10108
|
// Overflow still needs recovery even if shake threw.
|
|
9928
10109
|
return reason === "overflow" ? "fallback" : COMPACTION_CHECK_NONE;
|
|
@@ -9939,7 +10120,8 @@ export class AgentSession {
|
|
|
9939
10120
|
setAutoCompactionEnabled(enabled: boolean): void {
|
|
9940
10121
|
this.settings.set("compaction.enabled", enabled);
|
|
9941
10122
|
if (enabled && this.settings.get("compaction.strategy") === "off") {
|
|
9942
|
-
|
|
10123
|
+
const defaultStrategy = getDefault("compaction.strategy");
|
|
10124
|
+
this.settings.set("compaction.strategy", defaultStrategy === "off" ? "context-full" : defaultStrategy);
|
|
9943
10125
|
}
|
|
9944
10126
|
}
|
|
9945
10127
|
|
|
@@ -10277,6 +10459,68 @@ export class AgentSession {
|
|
|
10277
10459
|
return false;
|
|
10278
10460
|
}
|
|
10279
10461
|
|
|
10462
|
+
/** The active model when it is a Fireworks Fast (`-fast`) variant, else undefined. */
|
|
10463
|
+
#activeFireworksFastModel(): Model | undefined {
|
|
10464
|
+
const model = this.model;
|
|
10465
|
+
return model?.provider === "fireworks" && isFireworksFastModelId(model.id) ? model : undefined;
|
|
10466
|
+
}
|
|
10467
|
+
|
|
10468
|
+
/**
|
|
10469
|
+
* True when the current turn failed on a Fireworks Fast (`-fast`) model in a
|
|
10470
|
+
* way that should degrade to the reliable base (Standard) model. Fast is a
|
|
10471
|
+
* speed-optimized router with no SLA, so any *pre-content* failure — a
|
|
10472
|
+
* transient overload/5xx or a hard "router/model not found / unsupported" —
|
|
10473
|
+
* is worth retrying on the base id. Skips failures the base model shares:
|
|
10474
|
+
* context overflow (compaction's job), usage limits and auth errors (same
|
|
10475
|
+
* account/key), and turns that already emitted a tool call (replaying would
|
|
10476
|
+
* duplicate work). Requires the base model to exist in the registry.
|
|
10477
|
+
*/
|
|
10478
|
+
#isFireworksFastFallbackEligible(message: AssistantMessage): boolean {
|
|
10479
|
+
const model = this.#activeFireworksFastModel();
|
|
10480
|
+
if (!model) return false;
|
|
10481
|
+
if (message.stopReason !== "error" || !message.errorMessage) return false;
|
|
10482
|
+
if (message.content.some(block => block.type === "toolCall")) return false;
|
|
10483
|
+
// A content refusal/sensitivity stop is the model's decision, not a route
|
|
10484
|
+
// failure — switching to the base model would just re-trigger it.
|
|
10485
|
+
if (this.#isClassifierRefusal(message)) return false;
|
|
10486
|
+
if (isContextOverflow(message, model.contextWindow ?? 0)) return false;
|
|
10487
|
+
const err = message.errorMessage;
|
|
10488
|
+
if (isUsageLimitError(err)) return false;
|
|
10489
|
+
if (
|
|
10490
|
+
/\b(?:401|403|unauthorized|forbidden|authentication|auth[_ ]?unavailable|no auth available|(?:invalid|no)[_ ]?api[_ ]?key)\b/i.test(
|
|
10491
|
+
err,
|
|
10492
|
+
)
|
|
10493
|
+
)
|
|
10494
|
+
return false;
|
|
10495
|
+
return this.#modelRegistry.find("fireworks", toFireworksBaseModelId(model.id)) !== undefined;
|
|
10496
|
+
}
|
|
10497
|
+
|
|
10498
|
+
/**
|
|
10499
|
+
* Switch the active model from a Fireworks Fast (`-fast`) variant to its base
|
|
10500
|
+
* (Standard) id and stick there for the rest of the session — the auto
|
|
10501
|
+
* fallback that makes Fast a safe default. Returns false when the current
|
|
10502
|
+
* model is not a fast variant, the base id is missing, or it has no key.
|
|
10503
|
+
*/
|
|
10504
|
+
async #tryFireworksFastFallback(currentSelector: string): Promise<boolean> {
|
|
10505
|
+
const model = this.#activeFireworksFastModel();
|
|
10506
|
+
if (!model) return false;
|
|
10507
|
+
const baseModel = this.#modelRegistry.find("fireworks", toFireworksBaseModelId(model.id));
|
|
10508
|
+
if (!baseModel) return false;
|
|
10509
|
+
const apiKey = await this.#modelRegistry.getApiKey(baseModel, this.sessionId);
|
|
10510
|
+
if (!apiKey) return false;
|
|
10511
|
+
const baseSelector = formatModelStringWithRouting(baseModel);
|
|
10512
|
+
this.#setModelWithProviderSessionReset(baseModel);
|
|
10513
|
+
this.sessionManager.appendModelChange(baseSelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
10514
|
+
this.settings.getStorage()?.recordModelUsage(baseSelector);
|
|
10515
|
+
await this.#emitSessionEvent({
|
|
10516
|
+
type: "retry_fallback_applied",
|
|
10517
|
+
from: currentSelector,
|
|
10518
|
+
to: baseSelector,
|
|
10519
|
+
role: "fireworks-fast",
|
|
10520
|
+
});
|
|
10521
|
+
return true;
|
|
10522
|
+
}
|
|
10523
|
+
|
|
10280
10524
|
async #maybeRestoreRetryFallbackPrimary(): Promise<void> {
|
|
10281
10525
|
if (!this.#activeRetryFallback) return;
|
|
10282
10526
|
if (this.#activeRetryFallback.pinned) return;
|
|
@@ -10379,10 +10623,13 @@ export class AgentSession {
|
|
|
10379
10623
|
*/
|
|
10380
10624
|
async #handleRetryableError(
|
|
10381
10625
|
message: AssistantMessage,
|
|
10382
|
-
options?: { allowModelFallback?: boolean },
|
|
10626
|
+
options?: { allowModelFallback?: boolean; fireworksFastFallback?: boolean },
|
|
10383
10627
|
): Promise<boolean> {
|
|
10384
10628
|
const retrySettings = this.settings.getGroup("retry");
|
|
10385
|
-
|
|
10629
|
+
// The Fireworks Fast→base degrade is an intrinsic model-selection safety net,
|
|
10630
|
+
// not a retry loop, so it runs even when the user disabled retries: it switches
|
|
10631
|
+
// the model once and lets the base turn proceed.
|
|
10632
|
+
if (!retrySettings.enabled && !options?.fireworksFastFallback) return false;
|
|
10386
10633
|
const classifierRefusal = this.#isClassifierRefusal(message);
|
|
10387
10634
|
|
|
10388
10635
|
const generation = this.#promptGeneration;
|
|
@@ -10477,6 +10724,13 @@ export class AgentSession {
|
|
|
10477
10724
|
}
|
|
10478
10725
|
switchedModel = await this.#tryRetryModelFallback(currentSelector, { pinFallback: classifierRefusal });
|
|
10479
10726
|
}
|
|
10727
|
+
// Auto fallback from a Fireworks Fast variant to its base model. Independent
|
|
10728
|
+
// of the role-fallback setting: it's intrinsic to the Fast contract (speed
|
|
10729
|
+
// best-effort, degrade to Standard on failure) and triggers on hard router
|
|
10730
|
+
// errors the generic retry classifier would otherwise reject.
|
|
10731
|
+
if (!switchedModel && allowModelFallback && options?.fireworksFastFallback) {
|
|
10732
|
+
switchedModel = await this.#tryFireworksFastFallback(currentSelector);
|
|
10733
|
+
}
|
|
10480
10734
|
if (switchedModel) {
|
|
10481
10735
|
delayMs = 0;
|
|
10482
10736
|
} else if (usageLimitWaitMs === undefined && parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
|
|
@@ -10488,6 +10742,15 @@ export class AgentSession {
|
|
|
10488
10742
|
this.#resolveRetry();
|
|
10489
10743
|
return false;
|
|
10490
10744
|
}
|
|
10745
|
+
// Fast→base was requested but the base switch could not happen (e.g. the
|
|
10746
|
+
// base model has no credential). Don't fall through to backing-off and
|
|
10747
|
+
// retrying the failing fast model for a hard router error that the generic
|
|
10748
|
+
// classifier wouldn't retry — surface it instead.
|
|
10749
|
+
if (options?.fireworksFastFallback && !switchedModel && !this.#isRetryableError(message)) {
|
|
10750
|
+
this.#retryAttempt = 0;
|
|
10751
|
+
this.#resolveRetry();
|
|
10752
|
+
return false;
|
|
10753
|
+
}
|
|
10491
10754
|
|
|
10492
10755
|
// Fail-fast cap: if the provider asks us to wait longer than
|
|
10493
10756
|
// retry.maxDelayMs and we have no fallback credential or model to
|
|
@@ -11040,12 +11303,14 @@ export class AgentSession {
|
|
|
11040
11303
|
|
|
11041
11304
|
/**
|
|
11042
11305
|
* Run a single ephemeral side-channel turn against this session's current
|
|
11043
|
-
* model + system prompt + history.
|
|
11044
|
-
*
|
|
11306
|
+
* model + system prompt + history. The main turn's tool catalog is sent
|
|
11307
|
+
* to preserve the prompt cache, but the model is reminded not to call
|
|
11308
|
+
* tools and any tool calls are discarded. The side request
|
|
11309
|
+
* does not block on, or interfere with, any in-flight main turn. The
|
|
11045
11310
|
* session's history and persisted state are NOT modified by this call.
|
|
11046
11311
|
*
|
|
11047
11312
|
* Used by `BtwController` (`/btw`) and `OmfgController` (`/omfg`) to share
|
|
11048
|
-
* the snapshot + stream pipeline.
|
|
11313
|
+
* the snapshot + stream pipeline. The snapshot includes any in-flight
|
|
11049
11314
|
* streaming assistant text so the model sees the half-finished response
|
|
11050
11315
|
* rather than missing context.
|
|
11051
11316
|
*/
|
|
@@ -11062,15 +11327,7 @@ export class AgentSession {
|
|
|
11062
11327
|
const cacheSessionId = this.sessionId;
|
|
11063
11328
|
const snapshot = this.#buildEphemeralSnapshot(args.promptText);
|
|
11064
11329
|
const llmMessages = await this.convertMessagesToLlm(snapshot, args.signal);
|
|
11065
|
-
const context
|
|
11066
|
-
systemPrompt: this.systemPrompt,
|
|
11067
|
-
messages: llmMessages,
|
|
11068
|
-
// Empty tools array: with toolChoice="none" some encoders still serialize the
|
|
11069
|
-
// recipient's tool catalog and the model leaks raw call markup
|
|
11070
|
-
// (<function_calls>, DSML envelopes) into IRC replies. Stripping tools here
|
|
11071
|
-
// removes the surface entirely.
|
|
11072
|
-
tools: [],
|
|
11073
|
-
};
|
|
11330
|
+
const context = await this.agent.buildSideRequestContext(llmMessages);
|
|
11074
11331
|
const options = this.prepareSimpleStreamOptions(
|
|
11075
11332
|
{
|
|
11076
11333
|
apiKey: this.#modelRegistry.resolver(model, cacheSessionId),
|
|
@@ -11084,9 +11341,8 @@ export class AgentSession {
|
|
|
11084
11341
|
reasoning: toReasoningEffort(this.thinkingLevel),
|
|
11085
11342
|
disableReasoning: shouldDisableReasoning(this.thinkingLevel),
|
|
11086
11343
|
hideThinkingSummary: this.agent.hideThinkingSummary,
|
|
11087
|
-
serviceTier: this
|
|
11344
|
+
serviceTier: this.#effectiveServiceTier(model),
|
|
11088
11345
|
signal: args.signal,
|
|
11089
|
-
toolChoice: "none",
|
|
11090
11346
|
},
|
|
11091
11347
|
model.provider,
|
|
11092
11348
|
);
|
|
@@ -11126,9 +11382,13 @@ export class AgentSession {
|
|
|
11126
11382
|
if (args.onTextDelta && replyText.length > emittedReplyText.length) {
|
|
11127
11383
|
args.onTextDelta(replyText.slice(emittedReplyText.length));
|
|
11128
11384
|
}
|
|
11385
|
+
const sanitizedMessage: AssistantMessage = {
|
|
11386
|
+
...assistantMessage,
|
|
11387
|
+
content: assistantMessage.content.filter(block => block.type !== "toolCall"),
|
|
11388
|
+
};
|
|
11129
11389
|
return {
|
|
11130
11390
|
replyText: args.dedupeReply === false ? replyText.trim() : dedupeEphemeralReply(replyText.trim()),
|
|
11131
|
-
assistantMessage,
|
|
11391
|
+
assistantMessage: sanitizedMessage,
|
|
11132
11392
|
};
|
|
11133
11393
|
}
|
|
11134
11394
|
|
|
@@ -11169,6 +11429,12 @@ export class AgentSession {
|
|
|
11169
11429
|
}
|
|
11170
11430
|
}
|
|
11171
11431
|
}
|
|
11432
|
+
messages.push({
|
|
11433
|
+
role: "developer",
|
|
11434
|
+
content: [{ type: "text", text: sideChannelNoToolsReminder }],
|
|
11435
|
+
attribution: "agent",
|
|
11436
|
+
timestamp: Date.now(),
|
|
11437
|
+
});
|
|
11172
11438
|
messages.push({
|
|
11173
11439
|
role: "user",
|
|
11174
11440
|
content: [{ type: "text", text: promptText }],
|
|
@@ -11260,6 +11526,7 @@ export class AgentSession {
|
|
|
11260
11526
|
const previousTools = [...this.agent.state.tools];
|
|
11261
11527
|
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
|
11262
11528
|
const previousSystemPrompt = this.agent.state.systemPrompt;
|
|
11529
|
+
const previousBaseSystemPromptBeforeMemoryPromotion = this.#baseSystemPromptBeforeMemoryPromotion;
|
|
11263
11530
|
const previousFreshProviderSessionId = this.#freshProviderSessionId;
|
|
11264
11531
|
const previousFallbackSelectedMCPToolNames = previousSessionFile
|
|
11265
11532
|
? this.#getSessionDefaultSelectedMCPToolNames(previousSessionFile)
|
|
@@ -11368,8 +11635,7 @@ export class AgentSession {
|
|
|
11368
11635
|
: configuredServiceTier;
|
|
11369
11636
|
|
|
11370
11637
|
if (switchingToDifferentSession) {
|
|
11371
|
-
this.#
|
|
11372
|
-
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
11638
|
+
await this.#resetMemoryContextForNewTranscript();
|
|
11373
11639
|
}
|
|
11374
11640
|
this.#reconnectToAgent();
|
|
11375
11641
|
try {
|
|
@@ -11405,6 +11671,7 @@ export class AgentSession {
|
|
|
11405
11671
|
this.agent.setSystemPrompt(previousSystemPrompt);
|
|
11406
11672
|
}
|
|
11407
11673
|
this.#baseSystemPrompt = previousBaseSystemPrompt;
|
|
11674
|
+
this.#baseSystemPromptBeforeMemoryPromotion = previousBaseSystemPromptBeforeMemoryPromotion;
|
|
11408
11675
|
this.agent.setSystemPrompt(previousSystemPrompt);
|
|
11409
11676
|
this.agent.replaceMessages(previousAgentMessages);
|
|
11410
11677
|
this.agent.replaceQueues(previousSteeringMessages, previousFollowUpMessages);
|
|
@@ -11483,8 +11750,7 @@ export class AgentSession {
|
|
|
11483
11750
|
this.#syncAgentSessionId();
|
|
11484
11751
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
11485
11752
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
11486
|
-
this.#
|
|
11487
|
-
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
11753
|
+
await this.#resetMemoryContextForNewTranscript();
|
|
11488
11754
|
|
|
11489
11755
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
11490
11756
|
const sessionContext = this.buildDisplaySessionContext();
|
|
@@ -11570,14 +11836,13 @@ export class AgentSession {
|
|
|
11570
11836
|
content: [{ type: "text", text: question }],
|
|
11571
11837
|
timestamp: Date.now(),
|
|
11572
11838
|
});
|
|
11573
|
-
this.sessionManager.appendMessage(assistantMessage);
|
|
11839
|
+
this.sessionManager.appendMessage(sanitizeAssistantForReparentedHistory(assistantMessage));
|
|
11574
11840
|
this.#syncTodoPhasesFromBranch();
|
|
11575
11841
|
this.#freshProviderSessionId = undefined;
|
|
11576
11842
|
this.#syncAgentSessionId();
|
|
11577
11843
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
11578
11844
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
11579
|
-
this.#
|
|
11580
|
-
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
11845
|
+
await this.#resetMemoryContextForNewTranscript();
|
|
11581
11846
|
|
|
11582
11847
|
const sessionContext = this.buildDisplaySessionContext();
|
|
11583
11848
|
await this.#restoreMCPSelectionsForSessionContext(sessionContext);
|
|
@@ -12341,6 +12606,41 @@ export class AgentSession {
|
|
|
12341
12606
|
});
|
|
12342
12607
|
}
|
|
12343
12608
|
|
|
12609
|
+
/**
|
|
12610
|
+
* Dump the current session's LLM-facing request context as JSON to a
|
|
12611
|
+
* auto-named file in `os.tmpdir()`. This is the synchronous
|
|
12612
|
+
* `convertToLlm`-boundary snapshot — system prompt, tools (wire schemas),
|
|
12613
|
+
* thinking/service tier, and converted messages — with no network round-trip
|
|
12614
|
+
* and no arming flag, so advisor/side requests cannot intercept it.
|
|
12615
|
+
*
|
|
12616
|
+
* The file persists on disk and may contain the same raw context/secrets
|
|
12617
|
+
* as `/dump`; treat the path accordingly.
|
|
12618
|
+
*
|
|
12619
|
+
* @returns the written file path, or `undefined` when there are no messages.
|
|
12620
|
+
*/
|
|
12621
|
+
async dumpLlmRequestToTmpDir(): Promise<string | undefined> {
|
|
12622
|
+
const messages = this.messages;
|
|
12623
|
+
if (messages.length === 0) return undefined;
|
|
12624
|
+
const llmMessages = await this.convertMessagesToLlm(messages);
|
|
12625
|
+
const payload = {
|
|
12626
|
+
model: this.agent.state.model ?? null,
|
|
12627
|
+
thinkingLevel: this.#thinkingLevel ?? null,
|
|
12628
|
+
serviceTier: this.agent.serviceTier ?? null,
|
|
12629
|
+
systemPrompt: this.agent.state.systemPrompt,
|
|
12630
|
+
tools: this.agent.state.tools.map(tool => ({
|
|
12631
|
+
name: tool.name,
|
|
12632
|
+
description: tool.description,
|
|
12633
|
+
parameters: toolWireSchema(tool),
|
|
12634
|
+
...(tool.strict !== undefined ? { strict: tool.strict } : {}),
|
|
12635
|
+
...(tool.customWireName ? { customWireName: tool.customWireName } : {}),
|
|
12636
|
+
})),
|
|
12637
|
+
messages: llmMessages,
|
|
12638
|
+
};
|
|
12639
|
+
const filePath = path.join(os.tmpdir(), `omp-llm-request-${Snowflake.next()}.json`);
|
|
12640
|
+
await Bun.write(filePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
12641
|
+
return filePath;
|
|
12642
|
+
}
|
|
12643
|
+
|
|
12344
12644
|
/**
|
|
12345
12645
|
* Enable or disable the advisor for this session. The setting is overridden for the session,
|
|
12346
12646
|
* and the runtime is started or stopped to match.
|