@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.3
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 +75 -6
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/extension-flags.d.ts +36 -0
- package/dist/types/config/config-file.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +23 -0
- package/dist/types/config/keybindings.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +88 -65
- package/dist/types/edit/hashline/diff.d.ts +3 -3
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
- package/dist/types/eval/agent-bridge.d.ts +25 -0
- package/dist/types/eval/backend.d.ts +17 -2
- package/dist/types/eval/budget-bridge.d.ts +29 -0
- package/dist/types/eval/idle-timeout.d.ts +28 -0
- package/dist/types/eval/js/executor.d.ts +8 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +13 -0
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
- package/dist/types/extensibility/plugins/manager.d.ts +12 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/memory-backend/index.d.ts +1 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +3 -3
- package/dist/types/mnemopi/backend.d.ts +4 -0
- package/dist/types/mnemopi/config.d.ts +29 -0
- package/dist/types/mnemopi/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -2
- package/dist/types/modes/components/omfg-panel.d.ts +19 -0
- package/dist/types/modes/controllers/command-controller.d.ts +7 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
- package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
- package/dist/types/modes/gradient-highlight.d.ts +5 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -3
- package/dist/types/modes/magic-keywords.d.ts +14 -0
- package/dist/types/modes/markdown-prose.d.ts +27 -0
- package/dist/types/modes/orchestrate.d.ts +7 -2
- package/dist/types/modes/shared.d.ts +1 -1
- package/dist/types/modes/turn-budget.d.ts +18 -0
- package/dist/types/modes/types.d.ts +7 -3
- package/dist/types/modes/ultrathink.d.ts +7 -2
- package/dist/types/modes/workflow.d.ts +15 -0
- package/dist/types/sdk.d.ts +13 -3
- package/dist/types/session/agent-session.d.ts +40 -17
- package/dist/types/session/session-manager.d.ts +18 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/session/shake-types.d.ts +24 -0
- package/dist/types/task/executor.d.ts +2 -2
- package/dist/types/tiny/models.d.ts +15 -1
- package/dist/types/tiny/title-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +19 -3
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/package.json +10 -10
- package/src/autoresearch/tools/run-experiment.ts +45 -113
- package/src/cli/args.ts +39 -16
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/plugin-cli.ts +11 -2
- package/src/config/config-file.ts +98 -13
- package/src/config/file-lock.ts +60 -17
- package/src/config/keybindings.ts +78 -27
- package/src/config/model-registry.ts +7 -1
- package/src/config/settings-schema.ts +94 -67
- package/src/config/settings.ts +12 -0
- package/src/edit/hashline/diff.ts +81 -24
- package/src/edit/renderer.ts +16 -12
- package/src/eval/__tests__/agent-bridge.test.ts +433 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/idle-timeout.test.ts +66 -0
- package/src/eval/__tests__/shared-executors.test.ts +21 -0
- package/src/eval/agent-bridge.ts +295 -0
- package/src/eval/backend.ts +17 -2
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/idle-timeout.ts +80 -0
- package/src/eval/js/executor.ts +35 -7
- package/src/eval/js/index.ts +2 -1
- package/src/eval/js/shared/prelude.txt +85 -1
- package/src/eval/js/tool-bridge.ts +9 -0
- package/src/eval/py/executor.ts +41 -14
- package/src/eval/py/index.ts +2 -1
- package/src/eval/py/prelude.py +132 -1
- package/src/exec/bash-executor.ts +2 -3
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/runner.ts +12 -2
- package/src/extensibility/plugins/git-url.ts +90 -4
- package/src/extensibility/plugins/manager.ts +103 -7
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +88 -88
- package/src/main.ts +44 -55
- package/src/memory-backend/index.ts +1 -1
- package/src/memory-backend/resolve.ts +3 -3
- package/src/memory-backend/types.ts +3 -3
- package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
- package/src/{mnemosyne → mnemopi}/config.ts +36 -36
- package/src/{mnemosyne → mnemopi}/state.ts +67 -67
- package/src/modes/components/agent-dashboard.ts +6 -6
- package/src/modes/components/custom-editor.ts +4 -11
- package/src/modes/components/extensions/state-manager.ts +3 -4
- package/src/modes/components/footer.ts +8 -9
- package/src/modes/components/hook-selector.ts +86 -20
- package/src/modes/components/oauth-selector.ts +93 -21
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/settings-defs.ts +2 -2
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +38 -19
- package/src/modes/components/tree-selector.ts +4 -3
- package/src/modes/components/user-message-selector.ts +94 -19
- package/src/modes/components/user-message.ts +8 -1
- package/src/modes/controllers/command-controller.ts +57 -0
- package/src/modes/controllers/event-controller.ts +60 -2
- package/src/modes/controllers/input-controller.ts +14 -11
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/gradient-highlight.ts +23 -6
- package/src/modes/interactive-mode.ts +41 -7
- package/src/modes/magic-keywords.ts +20 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/orchestrate.ts +17 -11
- package/src/modes/shared.ts +3 -11
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/ultrathink.ts +16 -10
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/workflow.ts +42 -0
- package/src/prompts/system/omfg-user.md +51 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +70 -0
- package/src/prompts/tools/eval.md +13 -1
- package/src/prompts/tools/memory-edit.md +1 -1
- package/src/sdk.ts +63 -33
- package/src/session/agent-session.ts +373 -56
- package/src/session/session-manager.ts +32 -0
- package/src/session/session-storage.ts +68 -8
- package/src/session/shake-types.ts +44 -0
- package/src/slash-commands/builtin-registry.ts +41 -16
- package/src/task/executor.ts +3 -3
- package/src/task/index.ts +6 -6
- package/src/tiny/models.ts +30 -2
- package/src/tiny/title-protocol.ts +11 -1
- package/src/tiny/worker.ts +19 -7
- package/src/tools/eval.ts +202 -26
- package/src/tools/grouped-file-output.ts +9 -2
- package/src/tools/index.ts +17 -5
- package/src/tools/memory-edit.ts +4 -4
- package/src/tools/memory-recall.ts +5 -5
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +4 -4
- package/src/tools/render-utils.ts +2 -1
- package/src/tools/search.ts +480 -76
- package/dist/types/mnemosyne/backend.d.ts +0 -4
- package/dist/types/mnemosyne/config.d.ts +0 -29
- package/dist/types/mnemosyne/state.d.ts +0 -72
- /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
- /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
|
@@ -33,20 +33,29 @@ import {
|
|
|
33
33
|
ThinkingLevel,
|
|
34
34
|
} from "@oh-my-pi/pi-agent-core";
|
|
35
35
|
import {
|
|
36
|
+
AGGRESSIVE_SHAKE_CONFIG,
|
|
36
37
|
AUTO_HANDOFF_THRESHOLD_FOCUS,
|
|
38
|
+
applyShakeRegions,
|
|
37
39
|
CompactionCancelledError,
|
|
38
40
|
type CompactionPreparation,
|
|
39
41
|
type CompactionResult,
|
|
40
42
|
calculateContextTokens,
|
|
41
43
|
calculatePromptTokens,
|
|
42
44
|
collectEntriesForBranchSummary,
|
|
45
|
+
collectShakeRegions,
|
|
43
46
|
compact,
|
|
47
|
+
DEFAULT_SHAKE_CONFIG,
|
|
44
48
|
estimateTokens,
|
|
45
49
|
generateBranchSummary,
|
|
46
50
|
generateHandoff,
|
|
47
51
|
prepareCompaction,
|
|
52
|
+
type ShakeConfig,
|
|
53
|
+
type ShakeRegion,
|
|
54
|
+
type ShakeSummaryComplete,
|
|
55
|
+
type ShakeSummaryItem,
|
|
48
56
|
type SummaryOptions,
|
|
49
57
|
shouldCompact,
|
|
58
|
+
summarizeShakeRegions,
|
|
50
59
|
} from "@oh-my-pi/pi-agent-core/compaction";
|
|
51
60
|
import { DEFAULT_PRUNE_CONFIG, pruneToolOutputs } from "@oh-my-pi/pi-agent-core/compaction/pruning";
|
|
52
61
|
import type {
|
|
@@ -77,7 +86,7 @@ import {
|
|
|
77
86
|
resolveServiceTier,
|
|
78
87
|
streamSimple,
|
|
79
88
|
} from "@oh-my-pi/pi-ai";
|
|
80
|
-
import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
89
|
+
import { countTokens, MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
81
90
|
import {
|
|
82
91
|
extractRetryHint,
|
|
83
92
|
getAgentDbPath,
|
|
@@ -148,10 +157,12 @@ import type { Goal, GoalModeState } from "../goals/state";
|
|
|
148
157
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
149
158
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
150
159
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
151
|
-
import {
|
|
160
|
+
import { getMnemopiSessionState, type MnemopiSessionState, setMnemopiSessionState } from "../mnemopi/state";
|
|
152
161
|
import { containsOrchestrate, ORCHESTRATE_NOTICE } from "../modes/orchestrate";
|
|
153
162
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
163
|
+
import { parseTurnBudget } from "../modes/turn-budget";
|
|
154
164
|
import { containsUltrathink, ULTRATHINK_NOTICE } from "../modes/ultrathink";
|
|
165
|
+
import { containsWorkflow, WORKFLOW_NOTICE } from "../modes/workflow";
|
|
155
166
|
import type { PlanModeState } from "../plan-mode/state";
|
|
156
167
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
157
168
|
import eagerTodoPrompt from "../prompts/system/eager-todo.md" with { type: "text" };
|
|
@@ -174,7 +185,8 @@ import {
|
|
|
174
185
|
resolveThinkingLevelForModel,
|
|
175
186
|
toReasoningEffort,
|
|
176
187
|
} from "../thinking";
|
|
177
|
-
import {
|
|
188
|
+
import { isTinyMemoryLocalModelKey } from "../tiny/models";
|
|
189
|
+
import { shutdownTinyTitleClient, tinyModelClient } from "../tiny/title-client";
|
|
178
190
|
import {
|
|
179
191
|
buildDiscoverableToolSearchIndex,
|
|
180
192
|
collectDiscoverableTools,
|
|
@@ -219,6 +231,7 @@ import type {
|
|
|
219
231
|
SessionManager,
|
|
220
232
|
} from "./session-manager";
|
|
221
233
|
import { getLatestCompactionEntry } from "./session-manager";
|
|
234
|
+
import type { ShakeMode, ShakeResult } from "./shake-types";
|
|
222
235
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
223
236
|
import { YieldQueue } from "./yield-queue";
|
|
224
237
|
|
|
@@ -228,11 +241,11 @@ export type AgentSessionEvent =
|
|
|
228
241
|
| {
|
|
229
242
|
type: "auto_compaction_start";
|
|
230
243
|
reason: "threshold" | "overflow" | "idle" | "incomplete";
|
|
231
|
-
action: "context-full" | "handoff";
|
|
244
|
+
action: "context-full" | "handoff" | "shake" | "shake-summary";
|
|
232
245
|
}
|
|
233
246
|
| {
|
|
234
247
|
type: "auto_compaction_end";
|
|
235
|
-
action: "context-full" | "handoff";
|
|
248
|
+
action: "context-full" | "handoff" | "shake" | "shake-summary";
|
|
236
249
|
result: CompactionResult | undefined;
|
|
237
250
|
aborted: boolean;
|
|
238
251
|
willRetry: boolean;
|
|
@@ -269,6 +282,8 @@ export interface AsyncJobSnapshot {
|
|
|
269
282
|
delivery: AsyncJobDeliveryState;
|
|
270
283
|
}
|
|
271
284
|
|
|
285
|
+
export type { ShakeMode, ShakeResult };
|
|
286
|
+
|
|
272
287
|
// ============================================================================
|
|
273
288
|
// Types
|
|
274
289
|
// ============================================================================
|
|
@@ -1287,8 +1302,8 @@ export class AgentSession {
|
|
|
1287
1302
|
return previous;
|
|
1288
1303
|
}
|
|
1289
1304
|
|
|
1290
|
-
|
|
1291
|
-
return
|
|
1305
|
+
getMnemopiSessionState(): MnemopiSessionState | undefined {
|
|
1306
|
+
return getMnemopiSessionState(this);
|
|
1292
1307
|
}
|
|
1293
1308
|
|
|
1294
1309
|
/** TTSR manager for time-traveling stream rules */
|
|
@@ -2838,11 +2853,11 @@ export class AgentSession {
|
|
|
2838
2853
|
this.getHindsightSessionState()?.setSessionId(sid);
|
|
2839
2854
|
}
|
|
2840
2855
|
|
|
2841
|
-
#
|
|
2842
|
-
if (resolveMemoryBackend(this.settings).id !== "
|
|
2856
|
+
#rekeyMnemopiMemoryForCurrentSessionId(): void {
|
|
2857
|
+
if (resolveMemoryBackend(this.settings).id !== "mnemopi") return;
|
|
2843
2858
|
const sid = this.agent.sessionId;
|
|
2844
2859
|
if (!sid) return;
|
|
2845
|
-
this.
|
|
2860
|
+
this.getMnemopiSessionState()?.setSessionId(sid);
|
|
2846
2861
|
}
|
|
2847
2862
|
|
|
2848
2863
|
/** New session file: reset auto-recall / retain-threshold counters for the new transcript. */
|
|
@@ -2853,9 +2868,9 @@ export class AgentSession {
|
|
|
2853
2868
|
state.resetConversationTracking();
|
|
2854
2869
|
}
|
|
2855
2870
|
|
|
2856
|
-
#
|
|
2857
|
-
if (resolveMemoryBackend(this.settings).id !== "
|
|
2858
|
-
const state = this.
|
|
2871
|
+
#resetMnemopiConversationTrackingIfMnemopi(): void {
|
|
2872
|
+
if (resolveMemoryBackend(this.settings).id !== "mnemopi") return;
|
|
2873
|
+
const state = this.getMnemopiSessionState();
|
|
2859
2874
|
if (!state || state.aliasOf) return;
|
|
2860
2875
|
state.resetConversationTracking();
|
|
2861
2876
|
}
|
|
@@ -2923,8 +2938,8 @@ export class AgentSession {
|
|
|
2923
2938
|
const hindsightState = this.setHindsightSessionState(undefined);
|
|
2924
2939
|
await hindsightState?.flushRetainQueue();
|
|
2925
2940
|
hindsightState?.dispose();
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2941
|
+
const mnemopiState = setMnemopiSessionState(this, undefined);
|
|
2942
|
+
mnemopiState?.dispose();
|
|
2928
2943
|
this.#disconnectFromAgent();
|
|
2929
2944
|
if (this.#unsubscribeAppendOnly) {
|
|
2930
2945
|
this.#unsubscribeAppendOnly();
|
|
@@ -4101,6 +4116,8 @@ export class AgentSession {
|
|
|
4101
4116
|
const keywordNotices: CustomMessage[] = [];
|
|
4102
4117
|
if (!options?.synthetic) {
|
|
4103
4118
|
const timestamp = Date.now();
|
|
4119
|
+
const turnBudget = parseTurnBudget(expandedText);
|
|
4120
|
+
this.sessionManager.beginTurnBudget(turnBudget?.total ?? null, turnBudget?.hard ?? false);
|
|
4104
4121
|
if (containsUltrathink(expandedText)) {
|
|
4105
4122
|
keywordNotices.push({
|
|
4106
4123
|
role: "custom",
|
|
@@ -4121,6 +4138,16 @@ export class AgentSession {
|
|
|
4121
4138
|
timestamp,
|
|
4122
4139
|
});
|
|
4123
4140
|
}
|
|
4141
|
+
if (containsWorkflow(expandedText)) {
|
|
4142
|
+
keywordNotices.push({
|
|
4143
|
+
role: "custom",
|
|
4144
|
+
customType: "workflow-notice",
|
|
4145
|
+
content: WORKFLOW_NOTICE,
|
|
4146
|
+
display: false,
|
|
4147
|
+
attribution: "user",
|
|
4148
|
+
timestamp,
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4124
4151
|
}
|
|
4125
4152
|
|
|
4126
4153
|
// If streaming, queue via steer() or followUp() based on option
|
|
@@ -4973,9 +5000,9 @@ export class AgentSession {
|
|
|
4973
5000
|
this.setTodoPhases([]);
|
|
4974
5001
|
this.#syncAgentSessionId();
|
|
4975
5002
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
4976
|
-
this.#
|
|
5003
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
4977
5004
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
4978
|
-
this.#
|
|
5005
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
4979
5006
|
this.#steeringMessages = [];
|
|
4980
5007
|
this.#followUpMessages = [];
|
|
4981
5008
|
this.#pendingNextTurnMessages = [];
|
|
@@ -5070,8 +5097,8 @@ export class AgentSession {
|
|
|
5070
5097
|
// Update agent session ID
|
|
5071
5098
|
this.#syncAgentSessionId();
|
|
5072
5099
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
5073
|
-
this.#
|
|
5074
|
-
this.#
|
|
5100
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
5101
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
5075
5102
|
|
|
5076
5103
|
// Emit session_switch event with reason "fork" to hooks
|
|
5077
5104
|
if (this.#extensionRunner) {
|
|
@@ -5091,13 +5118,13 @@ export class AgentSession {
|
|
|
5091
5118
|
|
|
5092
5119
|
/**
|
|
5093
5120
|
* Set model directly.
|
|
5094
|
-
* Validates API key
|
|
5121
|
+
* Validates API key and saves to the active session. Persists settings only when requested.
|
|
5095
5122
|
* @throws Error if no API key available for the model
|
|
5096
5123
|
*/
|
|
5097
5124
|
async setModel(
|
|
5098
5125
|
model: Model,
|
|
5099
5126
|
role: string = "default",
|
|
5100
|
-
options?: { selector?: string; thinkingLevel?: ThinkingLevel },
|
|
5127
|
+
options?: { selector?: string; thinkingLevel?: ThinkingLevel; persist?: boolean },
|
|
5101
5128
|
): Promise<void> {
|
|
5102
5129
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
5103
5130
|
const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
|
|
@@ -5108,10 +5135,12 @@ export class AgentSession {
|
|
|
5108
5135
|
this.#clearActiveRetryFallback();
|
|
5109
5136
|
this.#setModelWithProviderSessionReset(model);
|
|
5110
5137
|
this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5138
|
+
if (options?.persist) {
|
|
5139
|
+
this.settings.setModelRole(
|
|
5140
|
+
role,
|
|
5141
|
+
this.#formatRoleModelValue(role, model, options.selector, options.thinkingLevel),
|
|
5142
|
+
);
|
|
5143
|
+
}
|
|
5115
5144
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
5116
5145
|
|
|
5117
5146
|
// Re-apply thinking for the newly selected model. Prefer the model's
|
|
@@ -5214,9 +5243,8 @@ export class AgentSession {
|
|
|
5214
5243
|
}
|
|
5215
5244
|
|
|
5216
5245
|
/**
|
|
5217
|
-
* Apply a resolved role model as the active model
|
|
5218
|
-
* settings
|
|
5219
|
-
* {@link cycleRoleModels} and is shared with the plan-approval model slider.
|
|
5246
|
+
* Apply a resolved role model as the active model without changing global
|
|
5247
|
+
* settings. Shared with role cycling and the plan-approval model slider.
|
|
5220
5248
|
*/
|
|
5221
5249
|
async applyRoleModel(entry: ResolvedRoleModel): Promise<void> {
|
|
5222
5250
|
await this.setModel(entry.model, entry.role);
|
|
@@ -5227,24 +5255,21 @@ export class AgentSession {
|
|
|
5227
5255
|
|
|
5228
5256
|
/**
|
|
5229
5257
|
* Cycle through configured role models in a fixed order.
|
|
5230
|
-
* Skips missing roles.
|
|
5258
|
+
* Skips missing roles and changes only the active session model.
|
|
5231
5259
|
* @param roleOrder - Order of roles to cycle through (e.g., ["slow", "default", "smol"])
|
|
5232
|
-
* @param
|
|
5260
|
+
* @param direction - "forward" (default) or "backward"
|
|
5233
5261
|
*/
|
|
5234
5262
|
async cycleRoleModels(
|
|
5235
5263
|
roleOrder: readonly string[],
|
|
5236
|
-
|
|
5264
|
+
direction: "forward" | "backward" = "forward",
|
|
5237
5265
|
): Promise<RoleModelCycleResult | undefined> {
|
|
5238
5266
|
const cycle = this.getRoleModelCycle(roleOrder);
|
|
5239
5267
|
if (!cycle || cycle.models.length <= 1) return undefined;
|
|
5240
5268
|
|
|
5241
|
-
const
|
|
5269
|
+
const step = direction === "backward" ? -1 : 1;
|
|
5270
|
+
const next = cycle.models[(cycle.currentIndex + step + cycle.models.length) % cycle.models.length];
|
|
5242
5271
|
|
|
5243
|
-
|
|
5244
|
-
await this.setModelTemporary(next.model, next.explicitThinkingLevel ? next.thinkingLevel : undefined);
|
|
5245
|
-
} else {
|
|
5246
|
-
await this.applyRoleModel(next);
|
|
5247
|
-
}
|
|
5272
|
+
await this.applyRoleModel(next);
|
|
5248
5273
|
|
|
5249
5274
|
return { model: next.model, thinkingLevel: this.thinkingLevel, role: next.role };
|
|
5250
5275
|
}
|
|
@@ -5288,7 +5313,6 @@ export class AgentSession {
|
|
|
5288
5313
|
this.#clearActiveRetryFallback();
|
|
5289
5314
|
this.#setModelWithProviderSessionReset(next.model);
|
|
5290
5315
|
this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
|
|
5291
|
-
this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model));
|
|
5292
5316
|
this.settings.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`);
|
|
5293
5317
|
|
|
5294
5318
|
// Apply the scoped model's configured thinking level, preserving auto.
|
|
@@ -5319,7 +5343,6 @@ export class AgentSession {
|
|
|
5319
5343
|
this.#clearActiveRetryFallback();
|
|
5320
5344
|
this.#setModelWithProviderSessionReset(nextModel);
|
|
5321
5345
|
this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
|
|
5322
|
-
this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel));
|
|
5323
5346
|
this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
|
|
5324
5347
|
// Re-apply the current thinking level (or auto) for the newly selected model
|
|
5325
5348
|
this.#reapplyThinkingLevel();
|
|
@@ -5340,9 +5363,10 @@ export class AgentSession {
|
|
|
5340
5363
|
// =========================================================================
|
|
5341
5364
|
|
|
5342
5365
|
/**
|
|
5343
|
-
* Set the thinking level. `auto` enables per-turn classification
|
|
5344
|
-
* never written to the session log
|
|
5345
|
-
*
|
|
5366
|
+
* Set the thinking level. `auto` enables per-turn classification; the selector
|
|
5367
|
+
* itself is never written to the session log, but resolved concrete levels are
|
|
5368
|
+
* persisted when real user turns are classified so resumed sessions keep the
|
|
5369
|
+
* last resolved effort instead of reverting to pending auto.
|
|
5346
5370
|
*/
|
|
5347
5371
|
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist: boolean = false): void {
|
|
5348
5372
|
if (level === AUTO_THINKING) {
|
|
@@ -5454,9 +5478,13 @@ export class AgentSession {
|
|
|
5454
5478
|
|
|
5455
5479
|
const effort = resolved ?? resolveProvisionalAutoLevel(model);
|
|
5456
5480
|
if (effort === undefined) return;
|
|
5481
|
+
const shouldPersistResolution = this.#autoResolvedLevel !== effort;
|
|
5457
5482
|
this.#autoResolvedLevel = effort;
|
|
5458
5483
|
this.#thinkingLevel = effort;
|
|
5459
5484
|
this.agent.setThinkingLevel(toReasoningEffort(effort));
|
|
5485
|
+
if (shouldPersistResolution) {
|
|
5486
|
+
this.sessionManager.appendThinkingLevelChange(effort);
|
|
5487
|
+
}
|
|
5460
5488
|
this.#emit({
|
|
5461
5489
|
type: "thinking_level_changed",
|
|
5462
5490
|
thinkingLevel: effort,
|
|
@@ -5625,6 +5653,165 @@ export class AgentSession {
|
|
|
5625
5653
|
return { removed };
|
|
5626
5654
|
}
|
|
5627
5655
|
|
|
5656
|
+
/**
|
|
5657
|
+
* Surgically reduce context by dropping heavy content ("shake").
|
|
5658
|
+
*
|
|
5659
|
+
* - `images` delegates to {@link dropImages}.
|
|
5660
|
+
* - `elide` replaces whole tool-call results and large fenced/XML blocks
|
|
5661
|
+
* with short placeholders that embed an `artifact://` recovery link.
|
|
5662
|
+
* - `summary` extractively compresses the same regions with the configured
|
|
5663
|
+
* local on-device model (`providers.shakeSummaryModel`), falling back to
|
|
5664
|
+
* the elide placeholder per region (or wholesale when the local model is
|
|
5665
|
+
* unavailable). Never calls a remote/cloud LLM.
|
|
5666
|
+
*
|
|
5667
|
+
* Mutates the branch in place, persists via `rewriteEntries`, replays the
|
|
5668
|
+
* rebuilt context through the agent, and tears down provider sessions that
|
|
5669
|
+
* cache message identity — same rewrite contract as {@link dropImages}.
|
|
5670
|
+
*
|
|
5671
|
+
* No-op (zero counts) when nothing is eligible.
|
|
5672
|
+
*/
|
|
5673
|
+
async shake(mode: ShakeMode, opts: { config?: ShakeConfig; signal?: AbortSignal } = {}): Promise<ShakeResult> {
|
|
5674
|
+
if (mode === "images") {
|
|
5675
|
+
const { removed } = await this.dropImages();
|
|
5676
|
+
return { mode, toolResultsDropped: 0, blocksDropped: 0, imagesDropped: removed, tokensFreed: 0 };
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
const config = opts.config ?? AGGRESSIVE_SHAKE_CONFIG;
|
|
5680
|
+
const regions = collectShakeRegions(this.sessionManager.getBranch(), config);
|
|
5681
|
+
if (regions.length === 0) {
|
|
5682
|
+
return { mode, toolResultsDropped: 0, blocksDropped: 0, tokensFreed: 0 };
|
|
5683
|
+
}
|
|
5684
|
+
|
|
5685
|
+
const artifactId = await this.#saveShakeArtifact(regions);
|
|
5686
|
+
let replacements: string[];
|
|
5687
|
+
if (mode === "summary") {
|
|
5688
|
+
// Manual `/shake summary` installs the compaction controller so Esc /
|
|
5689
|
+
// `abortCompaction()` can cancel the local-model pass; the auto-shake path
|
|
5690
|
+
// passes its own signal and manages `#autoCompactionAbortController`.
|
|
5691
|
+
let controller: AbortController | undefined;
|
|
5692
|
+
let signal = opts.signal;
|
|
5693
|
+
if (!signal) {
|
|
5694
|
+
if (this.#compactionAbortController) throw new Error("Compaction already in progress");
|
|
5695
|
+
controller = new AbortController();
|
|
5696
|
+
this.#compactionAbortController = controller;
|
|
5697
|
+
signal = controller.signal;
|
|
5698
|
+
}
|
|
5699
|
+
try {
|
|
5700
|
+
replacements = await this.#buildShakeSummaryReplacements(regions, artifactId, signal);
|
|
5701
|
+
} finally {
|
|
5702
|
+
if (controller && this.#compactionAbortController === controller) {
|
|
5703
|
+
this.#compactionAbortController = undefined;
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
} else {
|
|
5707
|
+
replacements = regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5710
|
+
let toolResultsDropped = 0;
|
|
5711
|
+
let blocksDropped = 0;
|
|
5712
|
+
let originalTokens = 0;
|
|
5713
|
+
let replacementTokens = 0;
|
|
5714
|
+
const items = regions.map((region, index) => {
|
|
5715
|
+
if (region.kind === "toolResult") toolResultsDropped++;
|
|
5716
|
+
else blocksDropped++;
|
|
5717
|
+
originalTokens += region.tokens;
|
|
5718
|
+
const replacement = replacements[index];
|
|
5719
|
+
if (replacement.length > 0) replacementTokens += countTokens(replacement);
|
|
5720
|
+
return { region, replacement };
|
|
5721
|
+
});
|
|
5722
|
+
|
|
5723
|
+
applyShakeRegions(items);
|
|
5724
|
+
|
|
5725
|
+
await this.sessionManager.rewriteEntries();
|
|
5726
|
+
const sessionContext = this.buildDisplaySessionContext();
|
|
5727
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
5728
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
5729
|
+
|
|
5730
|
+
return {
|
|
5731
|
+
mode,
|
|
5732
|
+
toolResultsDropped,
|
|
5733
|
+
blocksDropped,
|
|
5734
|
+
tokensFreed: Math.max(0, originalTokens - replacementTokens),
|
|
5735
|
+
artifactId,
|
|
5736
|
+
};
|
|
5737
|
+
}
|
|
5738
|
+
|
|
5739
|
+
#shakeElidePlaceholder(region: ShakeRegion, index: number, artifactId: string | undefined): string {
|
|
5740
|
+
if (artifactId) {
|
|
5741
|
+
return `[shaken ~${region.tokens} tokens — recover: artifact://${artifactId} (region ${index + 1})]`;
|
|
5742
|
+
}
|
|
5743
|
+
return `[shaken ~${region.tokens} tokens]`;
|
|
5744
|
+
}
|
|
5745
|
+
|
|
5746
|
+
/**
|
|
5747
|
+
* Concatenate the original region contents into one session artifact so the
|
|
5748
|
+
* agent can read them back via `artifact://<id>`. Returns `undefined` when
|
|
5749
|
+
* the session is not persisted or the write fails — callers degrade to a
|
|
5750
|
+
* bare placeholder.
|
|
5751
|
+
*/
|
|
5752
|
+
async #saveShakeArtifact(regions: ShakeRegion[]): Promise<string | undefined> {
|
|
5753
|
+
const parts: string[] = [];
|
|
5754
|
+
for (let i = 0; i < regions.length; i++) {
|
|
5755
|
+
const region = regions[i];
|
|
5756
|
+
parts.push(`### region ${i + 1} (${region.label}, ~${region.tokens} tok)`, "", region.originalText, "");
|
|
5757
|
+
}
|
|
5758
|
+
try {
|
|
5759
|
+
return await this.sessionManager.saveArtifact(parts.join("\n"), "shake");
|
|
5760
|
+
} catch {
|
|
5761
|
+
return undefined;
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
|
|
5765
|
+
/**
|
|
5766
|
+
* Build per-region replacements for summary mode using the configured local
|
|
5767
|
+
* on-device model (`providers.shakeSummaryModel`) via {@link tinyModelClient}.
|
|
5768
|
+
* Shake summary never calls a remote/cloud LLM. When the configured model is
|
|
5769
|
+
* not a known local key, every region falls back to the elide placeholder.
|
|
5770
|
+
* Otherwise compresses via {@link summarizeShakeRegions}; per region, uses
|
|
5771
|
+
* the parsed summary (with a recovery footer) or the elide placeholder when
|
|
5772
|
+
* the local model omitted it / was unavailable. Any thrown failure degrades
|
|
5773
|
+
* the whole batch to elide so the reduction still happens.
|
|
5774
|
+
*/
|
|
5775
|
+
async #buildShakeSummaryReplacements(
|
|
5776
|
+
regions: ShakeRegion[],
|
|
5777
|
+
artifactId: string | undefined,
|
|
5778
|
+
signal: AbortSignal | undefined,
|
|
5779
|
+
): Promise<string[]> {
|
|
5780
|
+
const elide = (): string[] =>
|
|
5781
|
+
regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
|
|
5782
|
+
|
|
5783
|
+
const modelKey = this.settings.get("providers.shakeSummaryModel");
|
|
5784
|
+
if (!isTinyMemoryLocalModelKey(modelKey)) return elide();
|
|
5785
|
+
|
|
5786
|
+
const items: ShakeSummaryItem[] = regions.map((region, index) => ({
|
|
5787
|
+
index,
|
|
5788
|
+
label: region.label,
|
|
5789
|
+
text: region.originalText,
|
|
5790
|
+
}));
|
|
5791
|
+
|
|
5792
|
+
const complete: ShakeSummaryComplete = (promptText, opts) =>
|
|
5793
|
+
tinyModelClient.complete(modelKey, promptText, { maxTokens: opts.maxTokens, signal: opts.signal });
|
|
5794
|
+
|
|
5795
|
+
let summaries: Map<number, string>;
|
|
5796
|
+
try {
|
|
5797
|
+
summaries = await summarizeShakeRegions(items, complete, { signal });
|
|
5798
|
+
} catch (error) {
|
|
5799
|
+
logger.warn("Shake summary compression failed; falling back to elide", {
|
|
5800
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5801
|
+
});
|
|
5802
|
+
return elide();
|
|
5803
|
+
}
|
|
5804
|
+
|
|
5805
|
+
return regions.map((region, index) => {
|
|
5806
|
+
const summary = summaries.get(index);
|
|
5807
|
+
if (!summary) return this.#shakeElidePlaceholder(region, index, artifactId);
|
|
5808
|
+
if (artifactId) {
|
|
5809
|
+
return `${summary}\n\n[recover full: artifact://${artifactId} (region ${index + 1})]`;
|
|
5810
|
+
}
|
|
5811
|
+
return summary;
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5814
|
+
|
|
5628
5815
|
/**
|
|
5629
5816
|
* Manually compact the session context.
|
|
5630
5817
|
* Aborts current agent operation first.
|
|
@@ -5935,9 +6122,9 @@ export class AgentSession {
|
|
|
5935
6122
|
this.agent.reset();
|
|
5936
6123
|
this.#syncAgentSessionId();
|
|
5937
6124
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
5938
|
-
this.#
|
|
6125
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
5939
6126
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
5940
|
-
this.#
|
|
6127
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
5941
6128
|
this.#steeringMessages = [];
|
|
5942
6129
|
this.#followUpMessages = [];
|
|
5943
6130
|
this.#pendingNextTurnMessages = [];
|
|
@@ -6837,6 +7024,14 @@ export class AgentSession {
|
|
|
6837
7024
|
if (compactionSettings.strategy === "off") return false;
|
|
6838
7025
|
if (reason !== "idle" && !compactionSettings.enabled) return false;
|
|
6839
7026
|
const generation = this.#promptGeneration;
|
|
7027
|
+
|
|
7028
|
+
// Shake strategies run inline (cheap, no remote LLM). On overflow recovery,
|
|
7029
|
+
// if shake reclaims nothing we fall through to the summary-compaction body
|
|
7030
|
+
// below so the oversized input still gets resolved.
|
|
7031
|
+
if (compactionSettings.strategy === "shake" || compactionSettings.strategy === "shake-summary") {
|
|
7032
|
+
const outcome = await this.#runAutoShake(reason, compactionSettings.strategy, willRetry, generation);
|
|
7033
|
+
if (outcome !== "fallback") return false;
|
|
7034
|
+
}
|
|
6840
7035
|
// "overflow" and "incomplete" force inline execution because they are recovery
|
|
6841
7036
|
// paths the caller wants resolved before scheduling the next turn. "idle" is
|
|
6842
7037
|
// triggered by the idle loop and does its own scheduling.
|
|
@@ -7219,6 +7414,119 @@ export class AgentSession {
|
|
|
7219
7414
|
return false;
|
|
7220
7415
|
}
|
|
7221
7416
|
|
|
7417
|
+
/**
|
|
7418
|
+
* Run a shake-strategy auto-maintenance pass. Emits the
|
|
7419
|
+
* `auto_compaction_start`/`auto_compaction_end` pair with a shake `action`,
|
|
7420
|
+
* runs {@link shake} inline against the protect-window config, and schedules
|
|
7421
|
+
* continuation exactly like the context-full tail.
|
|
7422
|
+
*
|
|
7423
|
+
* Returns `"fallback"` only for an overflow recovery where shake reclaimed
|
|
7424
|
+
* nothing (or threw) — the caller then runs the summary-compaction body so
|
|
7425
|
+
* the oversized input still gets resolved. Returns `"handled"` otherwise.
|
|
7426
|
+
*/
|
|
7427
|
+
async #runAutoShake(
|
|
7428
|
+
reason: "overflow" | "threshold" | "idle" | "incomplete",
|
|
7429
|
+
strategy: "shake" | "shake-summary",
|
|
7430
|
+
willRetry: boolean,
|
|
7431
|
+
generation: number,
|
|
7432
|
+
): Promise<"handled" | "fallback"> {
|
|
7433
|
+
const action = strategy === "shake-summary" ? "shake-summary" : "shake";
|
|
7434
|
+
const mode = strategy === "shake-summary" ? "summary" : "elide";
|
|
7435
|
+
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
7436
|
+
this.#autoCompactionAbortController?.abort();
|
|
7437
|
+
const controller = new AbortController();
|
|
7438
|
+
this.#autoCompactionAbortController = controller;
|
|
7439
|
+
const signal = controller.signal;
|
|
7440
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
7441
|
+
try {
|
|
7442
|
+
const result = await this.shake(mode, { config: DEFAULT_SHAKE_CONFIG, signal });
|
|
7443
|
+
if (signal.aborted) {
|
|
7444
|
+
await this.#emitSessionEvent({
|
|
7445
|
+
type: "auto_compaction_end",
|
|
7446
|
+
action,
|
|
7447
|
+
result: undefined,
|
|
7448
|
+
aborted: true,
|
|
7449
|
+
willRetry: false,
|
|
7450
|
+
});
|
|
7451
|
+
return "handled";
|
|
7452
|
+
}
|
|
7453
|
+
const reclaimed = result.toolResultsDropped + result.blocksDropped > 0;
|
|
7454
|
+
// Overflow needs the input to actually shrink before the retry; if shake
|
|
7455
|
+
// reclaimed nothing, summarization is the only remaining recovery.
|
|
7456
|
+
if (reason === "overflow" && !reclaimed) {
|
|
7457
|
+
await this.#emitSessionEvent({
|
|
7458
|
+
type: "auto_compaction_end",
|
|
7459
|
+
action,
|
|
7460
|
+
result: undefined,
|
|
7461
|
+
aborted: false,
|
|
7462
|
+
willRetry: false,
|
|
7463
|
+
skipped: true,
|
|
7464
|
+
});
|
|
7465
|
+
return "fallback";
|
|
7466
|
+
}
|
|
7467
|
+
await this.#emitSessionEvent({
|
|
7468
|
+
type: "auto_compaction_end",
|
|
7469
|
+
action,
|
|
7470
|
+
result: undefined,
|
|
7471
|
+
aborted: false,
|
|
7472
|
+
willRetry,
|
|
7473
|
+
skipped: !reclaimed,
|
|
7474
|
+
});
|
|
7475
|
+
|
|
7476
|
+
if (!willRetry && reason !== "idle" && compactionSettings.autoContinue !== false) {
|
|
7477
|
+
this.#scheduleAutoContinuePrompt(generation);
|
|
7478
|
+
}
|
|
7479
|
+
if (willRetry) {
|
|
7480
|
+
// The shake rebuild replays every entry, so a trailing error/length
|
|
7481
|
+
// assistant from the failed turn re-enters agent state — drop it before
|
|
7482
|
+
// retrying, same as the context-full tail.
|
|
7483
|
+
const messages = this.agent.state.messages;
|
|
7484
|
+
const lastMsg = messages[messages.length - 1];
|
|
7485
|
+
if (lastMsg?.role === "assistant") {
|
|
7486
|
+
const lastAssistant = lastMsg as AssistantMessage;
|
|
7487
|
+
const shouldDrop =
|
|
7488
|
+
lastAssistant.stopReason === "error" ||
|
|
7489
|
+
(reason === "incomplete" && lastAssistant.stopReason === "length");
|
|
7490
|
+
if (shouldDrop) this.agent.replaceMessages(messages.slice(0, -1));
|
|
7491
|
+
}
|
|
7492
|
+
this.#scheduleAgentContinue({ delayMs: 100, generation });
|
|
7493
|
+
} else if (this.agent.hasQueuedMessages()) {
|
|
7494
|
+
this.#scheduleAgentContinue({
|
|
7495
|
+
delayMs: 100,
|
|
7496
|
+
generation,
|
|
7497
|
+
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
7498
|
+
});
|
|
7499
|
+
}
|
|
7500
|
+
return "handled";
|
|
7501
|
+
} catch (error) {
|
|
7502
|
+
if (signal.aborted) {
|
|
7503
|
+
await this.#emitSessionEvent({
|
|
7504
|
+
type: "auto_compaction_end",
|
|
7505
|
+
action,
|
|
7506
|
+
result: undefined,
|
|
7507
|
+
aborted: true,
|
|
7508
|
+
willRetry: false,
|
|
7509
|
+
});
|
|
7510
|
+
return "handled";
|
|
7511
|
+
}
|
|
7512
|
+
const message = error instanceof Error ? error.message : "shake failed";
|
|
7513
|
+
await this.#emitSessionEvent({
|
|
7514
|
+
type: "auto_compaction_end",
|
|
7515
|
+
action,
|
|
7516
|
+
result: undefined,
|
|
7517
|
+
aborted: false,
|
|
7518
|
+
willRetry: false,
|
|
7519
|
+
errorMessage: `Auto-shake failed: ${message}`,
|
|
7520
|
+
});
|
|
7521
|
+
// Overflow still needs recovery even if shake threw.
|
|
7522
|
+
return reason === "overflow" ? "fallback" : "handled";
|
|
7523
|
+
} finally {
|
|
7524
|
+
if (this.#autoCompactionAbortController === controller) {
|
|
7525
|
+
this.#autoCompactionAbortController = undefined;
|
|
7526
|
+
}
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
|
|
7222
7530
|
/**
|
|
7223
7531
|
* Toggle auto-compaction setting.
|
|
7224
7532
|
*/
|
|
@@ -8181,6 +8489,7 @@ export class AgentSession {
|
|
|
8181
8489
|
promptText: string;
|
|
8182
8490
|
onTextDelta?: (delta: string) => void;
|
|
8183
8491
|
signal?: AbortSignal;
|
|
8492
|
+
dedupeReply?: boolean;
|
|
8184
8493
|
}): Promise<{ replyText: string; assistantMessage: AssistantMessage }> {
|
|
8185
8494
|
const model = this.model;
|
|
8186
8495
|
if (!model) {
|
|
@@ -8243,7 +8552,10 @@ export class AgentSession {
|
|
|
8243
8552
|
if (!assistantMessage) {
|
|
8244
8553
|
throw new Error("Ephemeral turn ended without a final message");
|
|
8245
8554
|
}
|
|
8246
|
-
return {
|
|
8555
|
+
return {
|
|
8556
|
+
replyText: args.dedupeReply === false ? replyText.trim() : dedupeIrcReply(replyText.trim()),
|
|
8557
|
+
assistantMessage,
|
|
8558
|
+
};
|
|
8247
8559
|
}
|
|
8248
8560
|
|
|
8249
8561
|
/**
|
|
@@ -8412,7 +8724,7 @@ export class AgentSession {
|
|
|
8412
8724
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
8413
8725
|
this.#syncAgentSessionId();
|
|
8414
8726
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8415
|
-
this.#
|
|
8727
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
8416
8728
|
|
|
8417
8729
|
const sessionContext = this.buildDisplaySessionContext();
|
|
8418
8730
|
const didReloadConversationChange =
|
|
@@ -8471,11 +8783,16 @@ export class AgentSession {
|
|
|
8471
8783
|
.some(entry => entry.type === "service_tier_change");
|
|
8472
8784
|
const defaultThinkingLevel = this.settings.get("defaultThinkingLevel");
|
|
8473
8785
|
const configuredServiceTier = this.settings.get("serviceTier");
|
|
8474
|
-
// Session log entries only
|
|
8475
|
-
//
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8786
|
+
// Session log entries store only concrete levels. When `auto` has resolved
|
|
8787
|
+
// for a turn, the persisted context may already carry that concrete level
|
|
8788
|
+
// even if the branch scan races a just-flushed thinking entry under isolated
|
|
8789
|
+
// parallel test workers. Prefer the concrete context value in that case;
|
|
8790
|
+
// otherwise keep the configured `auto` selector so fresh sessions still
|
|
8791
|
+
// classify their first turn.
|
|
8792
|
+
const restoredThinkingLevel: ConfiguredThinkingLevel | undefined =
|
|
8793
|
+
hasThinkingEntry || (defaultThinkingLevel === AUTO_THINKING && sessionContext.thinkingLevel !== "off")
|
|
8794
|
+
? (sessionContext.thinkingLevel as ThinkingLevel | undefined)
|
|
8795
|
+
: defaultThinkingLevel;
|
|
8479
8796
|
if (restoredThinkingLevel === AUTO_THINKING) {
|
|
8480
8797
|
this.#autoThinking = true;
|
|
8481
8798
|
this.#autoResolvedLevel = undefined;
|
|
@@ -8494,7 +8811,7 @@ export class AgentSession {
|
|
|
8494
8811
|
|
|
8495
8812
|
if (switchingToDifferentSession) {
|
|
8496
8813
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
8497
|
-
this.#
|
|
8814
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
8498
8815
|
}
|
|
8499
8816
|
this.#reconnectToAgent();
|
|
8500
8817
|
return true;
|
|
@@ -8502,7 +8819,7 @@ export class AgentSession {
|
|
|
8502
8819
|
this.sessionManager.restoreState(previousSessionState);
|
|
8503
8820
|
this.#syncAgentSessionId(previousSessionState.sessionId);
|
|
8504
8821
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8505
|
-
this.#
|
|
8822
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
8506
8823
|
let restoreMcpError: unknown;
|
|
8507
8824
|
try {
|
|
8508
8825
|
await this.#restoreMCPSelectionsForSessionContext(previousSessionContext, {
|
|
@@ -8600,9 +8917,9 @@ export class AgentSession {
|
|
|
8600
8917
|
this.#syncTodoPhasesFromBranch();
|
|
8601
8918
|
this.#syncAgentSessionId();
|
|
8602
8919
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8603
|
-
this.#
|
|
8920
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
8604
8921
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
8605
|
-
this.#
|
|
8922
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
8606
8923
|
|
|
8607
8924
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
8608
8925
|
const sessionContext = this.buildDisplaySessionContext();
|
|
@@ -8983,7 +9300,7 @@ export class AgentSession {
|
|
|
8983
9300
|
const msg = messages[i];
|
|
8984
9301
|
if (msg.role === "assistant") {
|
|
8985
9302
|
const assistantMsg = msg as AssistantMessage;
|
|
8986
|
-
if (assistantMsg.usage) {
|
|
9303
|
+
if (assistantMsg.stopReason !== "aborted" && assistantMsg.stopReason !== "error" && assistantMsg.usage) {
|
|
8987
9304
|
lastUsage = assistantMsg.usage;
|
|
8988
9305
|
lastUsageIndex = i;
|
|
8989
9306
|
break;
|