@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.5
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 +114 -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 +69 -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/internal-urls/local-protocol.d.ts +19 -9
- package/dist/types/internal-urls/types.d.ts +14 -0
- package/dist/types/lsp/client.d.ts +3 -0
- package/dist/types/mcp/manager.d.ts +14 -5
- 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 +6 -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 +36 -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/task/repair-args.d.ts +52 -0
- package/dist/types/tiny/models.d.ts +1 -1
- package/dist/types/tiny/title-client.d.ts +28 -2
- package/dist/types/tiny/title-protocol.d.ts +8 -5
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/index.d.ts +19 -3
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tui/output-block.d.ts +7 -7
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/package.json +10 -10
- package/scripts/build-binary.ts +0 -1
- 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/cli.ts +59 -0
- 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 +73 -67
- package/src/config/settings.ts +22 -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/internal-urls/local-protocol.ts +23 -11
- package/src/internal-urls/types.ts +15 -0
- package/src/lsp/client.ts +28 -5
- package/src/main.ts +44 -55
- package/src/mcp/manager.ts +87 -4
- 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 +25 -0
- package/src/modes/controllers/event-controller.ts +68 -3
- package/src/modes/controllers/input-controller.ts +14 -11
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- 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/project-prompt.md +3 -2
- package/src/prompts/system/subagent-system-prompt.md +12 -8
- package/src/prompts/system/system-prompt.md +9 -6
- 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 +290 -55
- package/src/session/session-manager.ts +32 -0
- package/src/session/session-storage.ts +68 -8
- package/src/session/shake-types.ts +43 -0
- package/src/slash-commands/builtin-registry.ts +39 -16
- package/src/task/executor.ts +17 -7
- package/src/task/index.ts +9 -8
- package/src/task/repair-args.ts +117 -0
- package/src/tiny/models.ts +2 -2
- package/src/tiny/title-client.ts +133 -43
- package/src/tiny/title-protocol.ts +10 -5
- package/src/tiny/worker.ts +3 -46
- package/src/tools/ast-edit.ts +3 -0
- package/src/tools/ast-grep.ts +3 -0
- package/src/tools/eval.ts +202 -26
- package/src/tools/find.ts +20 -6
- package/src/tools/gh.ts +1 -0
- 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/path-utils.ts +13 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/render-utils.ts +2 -1
- package/src/tools/search.ts +491 -76
- package/src/tui/output-block.ts +37 -75
- package/src/utils/git.ts +9 -3
- 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,18 +33,24 @@ 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,
|
|
48
54
|
type SummaryOptions,
|
|
49
55
|
shouldCompact,
|
|
50
56
|
} from "@oh-my-pi/pi-agent-core/compaction";
|
|
@@ -77,7 +83,7 @@ import {
|
|
|
77
83
|
resolveServiceTier,
|
|
78
84
|
streamSimple,
|
|
79
85
|
} from "@oh-my-pi/pi-ai";
|
|
80
|
-
import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
86
|
+
import { countTokens, MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
81
87
|
import {
|
|
82
88
|
extractRetryHint,
|
|
83
89
|
getAgentDbPath,
|
|
@@ -148,10 +154,12 @@ import type { Goal, GoalModeState } from "../goals/state";
|
|
|
148
154
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
149
155
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
150
156
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
151
|
-
import {
|
|
157
|
+
import { getMnemopiSessionState, type MnemopiSessionState, setMnemopiSessionState } from "../mnemopi/state";
|
|
152
158
|
import { containsOrchestrate, ORCHESTRATE_NOTICE } from "../modes/orchestrate";
|
|
153
159
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
160
|
+
import { parseTurnBudget } from "../modes/turn-budget";
|
|
154
161
|
import { containsUltrathink, ULTRATHINK_NOTICE } from "../modes/ultrathink";
|
|
162
|
+
import { containsWorkflow, WORKFLOW_NOTICE } from "../modes/workflow";
|
|
155
163
|
import type { PlanModeState } from "../plan-mode/state";
|
|
156
164
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
157
165
|
import eagerTodoPrompt from "../prompts/system/eager-todo.md" with { type: "text" };
|
|
@@ -219,6 +227,7 @@ import type {
|
|
|
219
227
|
SessionManager,
|
|
220
228
|
} from "./session-manager";
|
|
221
229
|
import { getLatestCompactionEntry } from "./session-manager";
|
|
230
|
+
import type { ShakeMode, ShakeResult } from "./shake-types";
|
|
222
231
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
223
232
|
import { YieldQueue } from "./yield-queue";
|
|
224
233
|
|
|
@@ -228,11 +237,11 @@ export type AgentSessionEvent =
|
|
|
228
237
|
| {
|
|
229
238
|
type: "auto_compaction_start";
|
|
230
239
|
reason: "threshold" | "overflow" | "idle" | "incomplete";
|
|
231
|
-
action: "context-full" | "handoff";
|
|
240
|
+
action: "context-full" | "handoff" | "shake";
|
|
232
241
|
}
|
|
233
242
|
| {
|
|
234
243
|
type: "auto_compaction_end";
|
|
235
|
-
action: "context-full" | "handoff";
|
|
244
|
+
action: "context-full" | "handoff" | "shake";
|
|
236
245
|
result: CompactionResult | undefined;
|
|
237
246
|
aborted: boolean;
|
|
238
247
|
willRetry: boolean;
|
|
@@ -269,6 +278,8 @@ export interface AsyncJobSnapshot {
|
|
|
269
278
|
delivery: AsyncJobDeliveryState;
|
|
270
279
|
}
|
|
271
280
|
|
|
281
|
+
export type { ShakeMode, ShakeResult };
|
|
282
|
+
|
|
272
283
|
// ============================================================================
|
|
273
284
|
// Types
|
|
274
285
|
// ============================================================================
|
|
@@ -1287,8 +1298,8 @@ export class AgentSession {
|
|
|
1287
1298
|
return previous;
|
|
1288
1299
|
}
|
|
1289
1300
|
|
|
1290
|
-
|
|
1291
|
-
return
|
|
1301
|
+
getMnemopiSessionState(): MnemopiSessionState | undefined {
|
|
1302
|
+
return getMnemopiSessionState(this);
|
|
1292
1303
|
}
|
|
1293
1304
|
|
|
1294
1305
|
/** TTSR manager for time-traveling stream rules */
|
|
@@ -2838,11 +2849,11 @@ export class AgentSession {
|
|
|
2838
2849
|
this.getHindsightSessionState()?.setSessionId(sid);
|
|
2839
2850
|
}
|
|
2840
2851
|
|
|
2841
|
-
#
|
|
2842
|
-
if (resolveMemoryBackend(this.settings).id !== "
|
|
2852
|
+
#rekeyMnemopiMemoryForCurrentSessionId(): void {
|
|
2853
|
+
if (resolveMemoryBackend(this.settings).id !== "mnemopi") return;
|
|
2843
2854
|
const sid = this.agent.sessionId;
|
|
2844
2855
|
if (!sid) return;
|
|
2845
|
-
this.
|
|
2856
|
+
this.getMnemopiSessionState()?.setSessionId(sid);
|
|
2846
2857
|
}
|
|
2847
2858
|
|
|
2848
2859
|
/** New session file: reset auto-recall / retain-threshold counters for the new transcript. */
|
|
@@ -2853,9 +2864,9 @@ export class AgentSession {
|
|
|
2853
2864
|
state.resetConversationTracking();
|
|
2854
2865
|
}
|
|
2855
2866
|
|
|
2856
|
-
#
|
|
2857
|
-
if (resolveMemoryBackend(this.settings).id !== "
|
|
2858
|
-
const state = this.
|
|
2867
|
+
#resetMnemopiConversationTrackingIfMnemopi(): void {
|
|
2868
|
+
if (resolveMemoryBackend(this.settings).id !== "mnemopi") return;
|
|
2869
|
+
const state = this.getMnemopiSessionState();
|
|
2859
2870
|
if (!state || state.aliasOf) return;
|
|
2860
2871
|
state.resetConversationTracking();
|
|
2861
2872
|
}
|
|
@@ -2923,8 +2934,8 @@ export class AgentSession {
|
|
|
2923
2934
|
const hindsightState = this.setHindsightSessionState(undefined);
|
|
2924
2935
|
await hindsightState?.flushRetainQueue();
|
|
2925
2936
|
hindsightState?.dispose();
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2937
|
+
const mnemopiState = setMnemopiSessionState(this, undefined);
|
|
2938
|
+
mnemopiState?.dispose();
|
|
2928
2939
|
this.#disconnectFromAgent();
|
|
2929
2940
|
if (this.#unsubscribeAppendOnly) {
|
|
2930
2941
|
this.#unsubscribeAppendOnly();
|
|
@@ -4101,6 +4112,8 @@ export class AgentSession {
|
|
|
4101
4112
|
const keywordNotices: CustomMessage[] = [];
|
|
4102
4113
|
if (!options?.synthetic) {
|
|
4103
4114
|
const timestamp = Date.now();
|
|
4115
|
+
const turnBudget = parseTurnBudget(expandedText);
|
|
4116
|
+
this.sessionManager.beginTurnBudget(turnBudget?.total ?? null, turnBudget?.hard ?? false);
|
|
4104
4117
|
if (containsUltrathink(expandedText)) {
|
|
4105
4118
|
keywordNotices.push({
|
|
4106
4119
|
role: "custom",
|
|
@@ -4121,6 +4134,16 @@ export class AgentSession {
|
|
|
4121
4134
|
timestamp,
|
|
4122
4135
|
});
|
|
4123
4136
|
}
|
|
4137
|
+
if (containsWorkflow(expandedText)) {
|
|
4138
|
+
keywordNotices.push({
|
|
4139
|
+
role: "custom",
|
|
4140
|
+
customType: "workflow-notice",
|
|
4141
|
+
content: WORKFLOW_NOTICE,
|
|
4142
|
+
display: false,
|
|
4143
|
+
attribution: "user",
|
|
4144
|
+
timestamp,
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4124
4147
|
}
|
|
4125
4148
|
|
|
4126
4149
|
// If streaming, queue via steer() or followUp() based on option
|
|
@@ -4973,9 +4996,9 @@ export class AgentSession {
|
|
|
4973
4996
|
this.setTodoPhases([]);
|
|
4974
4997
|
this.#syncAgentSessionId();
|
|
4975
4998
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
4976
|
-
this.#
|
|
4999
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
4977
5000
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
4978
|
-
this.#
|
|
5001
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
4979
5002
|
this.#steeringMessages = [];
|
|
4980
5003
|
this.#followUpMessages = [];
|
|
4981
5004
|
this.#pendingNextTurnMessages = [];
|
|
@@ -5070,8 +5093,8 @@ export class AgentSession {
|
|
|
5070
5093
|
// Update agent session ID
|
|
5071
5094
|
this.#syncAgentSessionId();
|
|
5072
5095
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
5073
|
-
this.#
|
|
5074
|
-
this.#
|
|
5096
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
5097
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
5075
5098
|
|
|
5076
5099
|
// Emit session_switch event with reason "fork" to hooks
|
|
5077
5100
|
if (this.#extensionRunner) {
|
|
@@ -5091,13 +5114,13 @@ export class AgentSession {
|
|
|
5091
5114
|
|
|
5092
5115
|
/**
|
|
5093
5116
|
* Set model directly.
|
|
5094
|
-
* Validates API key
|
|
5117
|
+
* Validates API key and saves to the active session. Persists settings only when requested.
|
|
5095
5118
|
* @throws Error if no API key available for the model
|
|
5096
5119
|
*/
|
|
5097
5120
|
async setModel(
|
|
5098
5121
|
model: Model,
|
|
5099
5122
|
role: string = "default",
|
|
5100
|
-
options?: { selector?: string; thinkingLevel?: ThinkingLevel },
|
|
5123
|
+
options?: { selector?: string; thinkingLevel?: ThinkingLevel; persist?: boolean },
|
|
5101
5124
|
): Promise<void> {
|
|
5102
5125
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
5103
5126
|
const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
|
|
@@ -5108,10 +5131,12 @@ export class AgentSession {
|
|
|
5108
5131
|
this.#clearActiveRetryFallback();
|
|
5109
5132
|
this.#setModelWithProviderSessionReset(model);
|
|
5110
5133
|
this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5134
|
+
if (options?.persist) {
|
|
5135
|
+
this.settings.setModelRole(
|
|
5136
|
+
role,
|
|
5137
|
+
this.#formatRoleModelValue(role, model, options.selector, options.thinkingLevel),
|
|
5138
|
+
);
|
|
5139
|
+
}
|
|
5115
5140
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
5116
5141
|
|
|
5117
5142
|
// Re-apply thinking for the newly selected model. Prefer the model's
|
|
@@ -5214,9 +5239,8 @@ export class AgentSession {
|
|
|
5214
5239
|
}
|
|
5215
5240
|
|
|
5216
5241
|
/**
|
|
5217
|
-
* Apply a resolved role model as the active model
|
|
5218
|
-
* settings
|
|
5219
|
-
* {@link cycleRoleModels} and is shared with the plan-approval model slider.
|
|
5242
|
+
* Apply a resolved role model as the active model without changing global
|
|
5243
|
+
* settings. Shared with role cycling and the plan-approval model slider.
|
|
5220
5244
|
*/
|
|
5221
5245
|
async applyRoleModel(entry: ResolvedRoleModel): Promise<void> {
|
|
5222
5246
|
await this.setModel(entry.model, entry.role);
|
|
@@ -5227,24 +5251,21 @@ export class AgentSession {
|
|
|
5227
5251
|
|
|
5228
5252
|
/**
|
|
5229
5253
|
* Cycle through configured role models in a fixed order.
|
|
5230
|
-
* Skips missing roles.
|
|
5254
|
+
* Skips missing roles and changes only the active session model.
|
|
5231
5255
|
* @param roleOrder - Order of roles to cycle through (e.g., ["slow", "default", "smol"])
|
|
5232
|
-
* @param
|
|
5256
|
+
* @param direction - "forward" (default) or "backward"
|
|
5233
5257
|
*/
|
|
5234
5258
|
async cycleRoleModels(
|
|
5235
5259
|
roleOrder: readonly string[],
|
|
5236
|
-
|
|
5260
|
+
direction: "forward" | "backward" = "forward",
|
|
5237
5261
|
): Promise<RoleModelCycleResult | undefined> {
|
|
5238
5262
|
const cycle = this.getRoleModelCycle(roleOrder);
|
|
5239
5263
|
if (!cycle || cycle.models.length <= 1) return undefined;
|
|
5240
5264
|
|
|
5241
|
-
const
|
|
5265
|
+
const step = direction === "backward" ? -1 : 1;
|
|
5266
|
+
const next = cycle.models[(cycle.currentIndex + step + cycle.models.length) % cycle.models.length];
|
|
5242
5267
|
|
|
5243
|
-
|
|
5244
|
-
await this.setModelTemporary(next.model, next.explicitThinkingLevel ? next.thinkingLevel : undefined);
|
|
5245
|
-
} else {
|
|
5246
|
-
await this.applyRoleModel(next);
|
|
5247
|
-
}
|
|
5268
|
+
await this.applyRoleModel(next);
|
|
5248
5269
|
|
|
5249
5270
|
return { model: next.model, thinkingLevel: this.thinkingLevel, role: next.role };
|
|
5250
5271
|
}
|
|
@@ -5288,7 +5309,6 @@ export class AgentSession {
|
|
|
5288
5309
|
this.#clearActiveRetryFallback();
|
|
5289
5310
|
this.#setModelWithProviderSessionReset(next.model);
|
|
5290
5311
|
this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
|
|
5291
|
-
this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model));
|
|
5292
5312
|
this.settings.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`);
|
|
5293
5313
|
|
|
5294
5314
|
// Apply the scoped model's configured thinking level, preserving auto.
|
|
@@ -5319,7 +5339,6 @@ export class AgentSession {
|
|
|
5319
5339
|
this.#clearActiveRetryFallback();
|
|
5320
5340
|
this.#setModelWithProviderSessionReset(nextModel);
|
|
5321
5341
|
this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
|
|
5322
|
-
this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel));
|
|
5323
5342
|
this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
|
|
5324
5343
|
// Re-apply the current thinking level (or auto) for the newly selected model
|
|
5325
5344
|
this.#reapplyThinkingLevel();
|
|
@@ -5340,9 +5359,10 @@ export class AgentSession {
|
|
|
5340
5359
|
// =========================================================================
|
|
5341
5360
|
|
|
5342
5361
|
/**
|
|
5343
|
-
* Set the thinking level. `auto` enables per-turn classification
|
|
5344
|
-
* never written to the session log
|
|
5345
|
-
*
|
|
5362
|
+
* Set the thinking level. `auto` enables per-turn classification; the selector
|
|
5363
|
+
* itself is never written to the session log, but resolved concrete levels are
|
|
5364
|
+
* persisted when real user turns are classified so resumed sessions keep the
|
|
5365
|
+
* last resolved effort instead of reverting to pending auto.
|
|
5346
5366
|
*/
|
|
5347
5367
|
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist: boolean = false): void {
|
|
5348
5368
|
if (level === AUTO_THINKING) {
|
|
@@ -5454,9 +5474,13 @@ export class AgentSession {
|
|
|
5454
5474
|
|
|
5455
5475
|
const effort = resolved ?? resolveProvisionalAutoLevel(model);
|
|
5456
5476
|
if (effort === undefined) return;
|
|
5477
|
+
const shouldPersistResolution = this.#autoResolvedLevel !== effort;
|
|
5457
5478
|
this.#autoResolvedLevel = effort;
|
|
5458
5479
|
this.#thinkingLevel = effort;
|
|
5459
5480
|
this.agent.setThinkingLevel(toReasoningEffort(effort));
|
|
5481
|
+
if (shouldPersistResolution) {
|
|
5482
|
+
this.sessionManager.appendThinkingLevelChange(effort);
|
|
5483
|
+
}
|
|
5460
5484
|
this.#emit({
|
|
5461
5485
|
type: "thinking_level_changed",
|
|
5462
5486
|
thinkingLevel: effort,
|
|
@@ -5625,6 +5649,89 @@ export class AgentSession {
|
|
|
5625
5649
|
return { removed };
|
|
5626
5650
|
}
|
|
5627
5651
|
|
|
5652
|
+
/**
|
|
5653
|
+
* Surgically reduce context by dropping heavy content ("shake").
|
|
5654
|
+
*
|
|
5655
|
+
* - `images` delegates to {@link dropImages}.
|
|
5656
|
+
* - `elide` replaces whole tool-call results and large fenced/XML blocks
|
|
5657
|
+
* with short placeholders that embed an `artifact://` recovery link.
|
|
5658
|
+
*
|
|
5659
|
+
* Mutates the branch in place, persists via `rewriteEntries`, replays the
|
|
5660
|
+
* rebuilt context through the agent, and tears down provider sessions that
|
|
5661
|
+
* cache message identity — same rewrite contract as {@link dropImages}.
|
|
5662
|
+
*
|
|
5663
|
+
* No-op (zero counts) when nothing is eligible.
|
|
5664
|
+
*/
|
|
5665
|
+
async shake(mode: ShakeMode, opts: { config?: ShakeConfig; signal?: AbortSignal } = {}): Promise<ShakeResult> {
|
|
5666
|
+
if (mode === "images") {
|
|
5667
|
+
const { removed } = await this.dropImages();
|
|
5668
|
+
return { mode, toolResultsDropped: 0, blocksDropped: 0, imagesDropped: removed, tokensFreed: 0 };
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5671
|
+
const config = opts.config ?? AGGRESSIVE_SHAKE_CONFIG;
|
|
5672
|
+
const regions = collectShakeRegions(this.sessionManager.getBranch(), config);
|
|
5673
|
+
if (regions.length === 0) {
|
|
5674
|
+
return { mode, toolResultsDropped: 0, blocksDropped: 0, tokensFreed: 0 };
|
|
5675
|
+
}
|
|
5676
|
+
|
|
5677
|
+
const artifactId = await this.#saveShakeArtifact(regions);
|
|
5678
|
+
const replacements = regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
|
|
5679
|
+
|
|
5680
|
+
let toolResultsDropped = 0;
|
|
5681
|
+
let blocksDropped = 0;
|
|
5682
|
+
let originalTokens = 0;
|
|
5683
|
+
let replacementTokens = 0;
|
|
5684
|
+
const items = regions.map((region, index) => {
|
|
5685
|
+
if (region.kind === "toolResult") toolResultsDropped++;
|
|
5686
|
+
else blocksDropped++;
|
|
5687
|
+
originalTokens += region.tokens;
|
|
5688
|
+
const replacement = replacements[index];
|
|
5689
|
+
if (replacement.length > 0) replacementTokens += countTokens(replacement);
|
|
5690
|
+
return { region, replacement };
|
|
5691
|
+
});
|
|
5692
|
+
|
|
5693
|
+
applyShakeRegions(items);
|
|
5694
|
+
|
|
5695
|
+
await this.sessionManager.rewriteEntries();
|
|
5696
|
+
const sessionContext = this.buildDisplaySessionContext();
|
|
5697
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
5698
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
5699
|
+
|
|
5700
|
+
return {
|
|
5701
|
+
mode,
|
|
5702
|
+
toolResultsDropped,
|
|
5703
|
+
blocksDropped,
|
|
5704
|
+
tokensFreed: Math.max(0, originalTokens - replacementTokens),
|
|
5705
|
+
artifactId,
|
|
5706
|
+
};
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5709
|
+
#shakeElidePlaceholder(region: ShakeRegion, index: number, artifactId: string | undefined): string {
|
|
5710
|
+
if (artifactId) {
|
|
5711
|
+
return `[shaken ~${region.tokens} tokens — recover: artifact://${artifactId} (region ${index + 1})]`;
|
|
5712
|
+
}
|
|
5713
|
+
return `[shaken ~${region.tokens} tokens]`;
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
/**
|
|
5717
|
+
* Concatenate the original region contents into one session artifact so the
|
|
5718
|
+
* agent can read them back via `artifact://<id>`. Returns `undefined` when
|
|
5719
|
+
* the session is not persisted or the write fails — callers degrade to a
|
|
5720
|
+
* bare placeholder.
|
|
5721
|
+
*/
|
|
5722
|
+
async #saveShakeArtifact(regions: ShakeRegion[]): Promise<string | undefined> {
|
|
5723
|
+
const parts: string[] = [];
|
|
5724
|
+
for (let i = 0; i < regions.length; i++) {
|
|
5725
|
+
const region = regions[i];
|
|
5726
|
+
parts.push(`### region ${i + 1} (${region.label}, ~${region.tokens} tok)`, "", region.originalText, "");
|
|
5727
|
+
}
|
|
5728
|
+
try {
|
|
5729
|
+
return await this.sessionManager.saveArtifact(parts.join("\n"), "shake");
|
|
5730
|
+
} catch {
|
|
5731
|
+
return undefined;
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5628
5735
|
/**
|
|
5629
5736
|
* Manually compact the session context.
|
|
5630
5737
|
* Aborts current agent operation first.
|
|
@@ -5935,9 +6042,9 @@ export class AgentSession {
|
|
|
5935
6042
|
this.agent.reset();
|
|
5936
6043
|
this.#syncAgentSessionId();
|
|
5937
6044
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
5938
|
-
this.#
|
|
6045
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
5939
6046
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
5940
|
-
this.#
|
|
6047
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
5941
6048
|
this.#steeringMessages = [];
|
|
5942
6049
|
this.#followUpMessages = [];
|
|
5943
6050
|
this.#pendingNextTurnMessages = [];
|
|
@@ -6837,6 +6944,14 @@ export class AgentSession {
|
|
|
6837
6944
|
if (compactionSettings.strategy === "off") return false;
|
|
6838
6945
|
if (reason !== "idle" && !compactionSettings.enabled) return false;
|
|
6839
6946
|
const generation = this.#promptGeneration;
|
|
6947
|
+
|
|
6948
|
+
// Shake runs inline (cheap, no remote LLM). On overflow recovery, if shake
|
|
6949
|
+
// reclaims nothing we fall through to the summary-compaction body below so
|
|
6950
|
+
// the oversized input still gets resolved.
|
|
6951
|
+
if (compactionSettings.strategy === "shake") {
|
|
6952
|
+
const outcome = await this.#runAutoShake(reason, willRetry, generation);
|
|
6953
|
+
if (outcome !== "fallback") return false;
|
|
6954
|
+
}
|
|
6840
6955
|
// "overflow" and "incomplete" force inline execution because they are recovery
|
|
6841
6956
|
// paths the caller wants resolved before scheduling the next turn. "idle" is
|
|
6842
6957
|
// triggered by the idle loop and does its own scheduling.
|
|
@@ -7219,6 +7334,117 @@ export class AgentSession {
|
|
|
7219
7334
|
return false;
|
|
7220
7335
|
}
|
|
7221
7336
|
|
|
7337
|
+
/**
|
|
7338
|
+
* Run a shake-strategy auto-maintenance pass. Emits the
|
|
7339
|
+
* `auto_compaction_start`/`auto_compaction_end` pair with a shake `action`,
|
|
7340
|
+
* runs {@link shake} inline against the protect-window config, and schedules
|
|
7341
|
+
* continuation exactly like the context-full tail.
|
|
7342
|
+
*
|
|
7343
|
+
* Returns `"fallback"` only for an overflow recovery where shake reclaimed
|
|
7344
|
+
* nothing (or threw) — the caller then runs the summary-compaction body so
|
|
7345
|
+
* the oversized input still gets resolved. Returns `"handled"` otherwise.
|
|
7346
|
+
*/
|
|
7347
|
+
async #runAutoShake(
|
|
7348
|
+
reason: "overflow" | "threshold" | "idle" | "incomplete",
|
|
7349
|
+
willRetry: boolean,
|
|
7350
|
+
generation: number,
|
|
7351
|
+
): Promise<"handled" | "fallback"> {
|
|
7352
|
+
const action = "shake";
|
|
7353
|
+
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
7354
|
+
this.#autoCompactionAbortController?.abort();
|
|
7355
|
+
const controller = new AbortController();
|
|
7356
|
+
this.#autoCompactionAbortController = controller;
|
|
7357
|
+
const signal = controller.signal;
|
|
7358
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
7359
|
+
try {
|
|
7360
|
+
const result = await this.shake("elide", { config: DEFAULT_SHAKE_CONFIG, signal });
|
|
7361
|
+
if (signal.aborted) {
|
|
7362
|
+
await this.#emitSessionEvent({
|
|
7363
|
+
type: "auto_compaction_end",
|
|
7364
|
+
action,
|
|
7365
|
+
result: undefined,
|
|
7366
|
+
aborted: true,
|
|
7367
|
+
willRetry: false,
|
|
7368
|
+
});
|
|
7369
|
+
return "handled";
|
|
7370
|
+
}
|
|
7371
|
+
const reclaimed = result.toolResultsDropped + result.blocksDropped > 0;
|
|
7372
|
+
// Overflow needs the input to actually shrink before the retry; if shake
|
|
7373
|
+
// reclaimed nothing, summarization is the only remaining recovery.
|
|
7374
|
+
if (reason === "overflow" && !reclaimed) {
|
|
7375
|
+
await this.#emitSessionEvent({
|
|
7376
|
+
type: "auto_compaction_end",
|
|
7377
|
+
action,
|
|
7378
|
+
result: undefined,
|
|
7379
|
+
aborted: false,
|
|
7380
|
+
willRetry: false,
|
|
7381
|
+
skipped: true,
|
|
7382
|
+
});
|
|
7383
|
+
return "fallback";
|
|
7384
|
+
}
|
|
7385
|
+
await this.#emitSessionEvent({
|
|
7386
|
+
type: "auto_compaction_end",
|
|
7387
|
+
action,
|
|
7388
|
+
result: undefined,
|
|
7389
|
+
aborted: false,
|
|
7390
|
+
willRetry,
|
|
7391
|
+
skipped: !reclaimed,
|
|
7392
|
+
});
|
|
7393
|
+
|
|
7394
|
+
if (!willRetry && reason !== "idle" && compactionSettings.autoContinue !== false) {
|
|
7395
|
+
this.#scheduleAutoContinuePrompt(generation);
|
|
7396
|
+
}
|
|
7397
|
+
if (willRetry) {
|
|
7398
|
+
// The shake rebuild replays every entry, so a trailing error/length
|
|
7399
|
+
// assistant from the failed turn re-enters agent state — drop it before
|
|
7400
|
+
// retrying, same as the context-full tail.
|
|
7401
|
+
const messages = this.agent.state.messages;
|
|
7402
|
+
const lastMsg = messages[messages.length - 1];
|
|
7403
|
+
if (lastMsg?.role === "assistant") {
|
|
7404
|
+
const lastAssistant = lastMsg as AssistantMessage;
|
|
7405
|
+
const shouldDrop =
|
|
7406
|
+
lastAssistant.stopReason === "error" ||
|
|
7407
|
+
(reason === "incomplete" && lastAssistant.stopReason === "length");
|
|
7408
|
+
if (shouldDrop) this.agent.replaceMessages(messages.slice(0, -1));
|
|
7409
|
+
}
|
|
7410
|
+
this.#scheduleAgentContinue({ delayMs: 100, generation });
|
|
7411
|
+
} else if (this.agent.hasQueuedMessages()) {
|
|
7412
|
+
this.#scheduleAgentContinue({
|
|
7413
|
+
delayMs: 100,
|
|
7414
|
+
generation,
|
|
7415
|
+
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
7416
|
+
});
|
|
7417
|
+
}
|
|
7418
|
+
return "handled";
|
|
7419
|
+
} catch (error) {
|
|
7420
|
+
if (signal.aborted) {
|
|
7421
|
+
await this.#emitSessionEvent({
|
|
7422
|
+
type: "auto_compaction_end",
|
|
7423
|
+
action,
|
|
7424
|
+
result: undefined,
|
|
7425
|
+
aborted: true,
|
|
7426
|
+
willRetry: false,
|
|
7427
|
+
});
|
|
7428
|
+
return "handled";
|
|
7429
|
+
}
|
|
7430
|
+
const message = error instanceof Error ? error.message : "shake failed";
|
|
7431
|
+
await this.#emitSessionEvent({
|
|
7432
|
+
type: "auto_compaction_end",
|
|
7433
|
+
action,
|
|
7434
|
+
result: undefined,
|
|
7435
|
+
aborted: false,
|
|
7436
|
+
willRetry: false,
|
|
7437
|
+
errorMessage: `Auto-shake failed: ${message}`,
|
|
7438
|
+
});
|
|
7439
|
+
// Overflow still needs recovery even if shake threw.
|
|
7440
|
+
return reason === "overflow" ? "fallback" : "handled";
|
|
7441
|
+
} finally {
|
|
7442
|
+
if (this.#autoCompactionAbortController === controller) {
|
|
7443
|
+
this.#autoCompactionAbortController = undefined;
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
|
|
7222
7448
|
/**
|
|
7223
7449
|
* Toggle auto-compaction setting.
|
|
7224
7450
|
*/
|
|
@@ -8181,6 +8407,7 @@ export class AgentSession {
|
|
|
8181
8407
|
promptText: string;
|
|
8182
8408
|
onTextDelta?: (delta: string) => void;
|
|
8183
8409
|
signal?: AbortSignal;
|
|
8410
|
+
dedupeReply?: boolean;
|
|
8184
8411
|
}): Promise<{ replyText: string; assistantMessage: AssistantMessage }> {
|
|
8185
8412
|
const model = this.model;
|
|
8186
8413
|
if (!model) {
|
|
@@ -8243,7 +8470,10 @@ export class AgentSession {
|
|
|
8243
8470
|
if (!assistantMessage) {
|
|
8244
8471
|
throw new Error("Ephemeral turn ended without a final message");
|
|
8245
8472
|
}
|
|
8246
|
-
return {
|
|
8473
|
+
return {
|
|
8474
|
+
replyText: args.dedupeReply === false ? replyText.trim() : dedupeIrcReply(replyText.trim()),
|
|
8475
|
+
assistantMessage,
|
|
8476
|
+
};
|
|
8247
8477
|
}
|
|
8248
8478
|
|
|
8249
8479
|
/**
|
|
@@ -8412,7 +8642,7 @@ export class AgentSession {
|
|
|
8412
8642
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
8413
8643
|
this.#syncAgentSessionId();
|
|
8414
8644
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8415
|
-
this.#
|
|
8645
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
8416
8646
|
|
|
8417
8647
|
const sessionContext = this.buildDisplaySessionContext();
|
|
8418
8648
|
const didReloadConversationChange =
|
|
@@ -8471,11 +8701,16 @@ export class AgentSession {
|
|
|
8471
8701
|
.some(entry => entry.type === "service_tier_change");
|
|
8472
8702
|
const defaultThinkingLevel = this.settings.get("defaultThinkingLevel");
|
|
8473
8703
|
const configuredServiceTier = this.settings.get("serviceTier");
|
|
8474
|
-
// Session log entries only
|
|
8475
|
-
//
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8704
|
+
// Session log entries store only concrete levels. When `auto` has resolved
|
|
8705
|
+
// for a turn, the persisted context may already carry that concrete level
|
|
8706
|
+
// even if the branch scan races a just-flushed thinking entry under isolated
|
|
8707
|
+
// parallel test workers. Prefer the concrete context value in that case;
|
|
8708
|
+
// otherwise keep the configured `auto` selector so fresh sessions still
|
|
8709
|
+
// classify their first turn.
|
|
8710
|
+
const restoredThinkingLevel: ConfiguredThinkingLevel | undefined =
|
|
8711
|
+
hasThinkingEntry || (defaultThinkingLevel === AUTO_THINKING && sessionContext.thinkingLevel !== "off")
|
|
8712
|
+
? (sessionContext.thinkingLevel as ThinkingLevel | undefined)
|
|
8713
|
+
: defaultThinkingLevel;
|
|
8479
8714
|
if (restoredThinkingLevel === AUTO_THINKING) {
|
|
8480
8715
|
this.#autoThinking = true;
|
|
8481
8716
|
this.#autoResolvedLevel = undefined;
|
|
@@ -8494,7 +8729,7 @@ export class AgentSession {
|
|
|
8494
8729
|
|
|
8495
8730
|
if (switchingToDifferentSession) {
|
|
8496
8731
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
8497
|
-
this.#
|
|
8732
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
8498
8733
|
}
|
|
8499
8734
|
this.#reconnectToAgent();
|
|
8500
8735
|
return true;
|
|
@@ -8502,7 +8737,7 @@ export class AgentSession {
|
|
|
8502
8737
|
this.sessionManager.restoreState(previousSessionState);
|
|
8503
8738
|
this.#syncAgentSessionId(previousSessionState.sessionId);
|
|
8504
8739
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8505
|
-
this.#
|
|
8740
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
8506
8741
|
let restoreMcpError: unknown;
|
|
8507
8742
|
try {
|
|
8508
8743
|
await this.#restoreMCPSelectionsForSessionContext(previousSessionContext, {
|
|
@@ -8600,9 +8835,9 @@ export class AgentSession {
|
|
|
8600
8835
|
this.#syncTodoPhasesFromBranch();
|
|
8601
8836
|
this.#syncAgentSessionId();
|
|
8602
8837
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8603
|
-
this.#
|
|
8838
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
8604
8839
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
8605
|
-
this.#
|
|
8840
|
+
this.#resetMnemopiConversationTrackingIfMnemopi();
|
|
8606
8841
|
|
|
8607
8842
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
8608
8843
|
const sessionContext = this.buildDisplaySessionContext();
|
|
@@ -8983,7 +9218,7 @@ export class AgentSession {
|
|
|
8983
9218
|
const msg = messages[i];
|
|
8984
9219
|
if (msg.role === "assistant") {
|
|
8985
9220
|
const assistantMsg = msg as AssistantMessage;
|
|
8986
|
-
if (assistantMsg.usage) {
|
|
9221
|
+
if (assistantMsg.stopReason !== "aborted" && assistantMsg.stopReason !== "error" && assistantMsg.usage) {
|
|
8987
9222
|
lastUsage = assistantMsg.usage;
|
|
8988
9223
|
lastUsageIndex = i;
|
|
8989
9224
|
break;
|
|
@@ -1837,6 +1837,12 @@ export class SessionManager {
|
|
|
1837
1837
|
premiumRequests: 0,
|
|
1838
1838
|
cost: 0,
|
|
1839
1839
|
} satisfies UsageStatistics;
|
|
1840
|
+
/** Per-turn output-token budget set by a `+Nk` directive (total null when none this turn). */
|
|
1841
|
+
#turnBudget: { total: number | null; hard: boolean } = { total: null, hard: false };
|
|
1842
|
+
/** Cumulative `output` snapshot captured when the current turn budget window opened. */
|
|
1843
|
+
#turnBaselineOutput = 0;
|
|
1844
|
+
/** Output tokens consumed by eval-spawned subagents in the current turn window. */
|
|
1845
|
+
#turnEvalOutput = 0;
|
|
1840
1846
|
#persistWriter: NdjsonFileWriter | undefined;
|
|
1841
1847
|
#persistWriterPath: string | undefined;
|
|
1842
1848
|
#persistChain: Promise<void> = Promise.resolve();
|
|
@@ -2397,6 +2403,32 @@ export class SessionManager {
|
|
|
2397
2403
|
return this.#usageStatistics;
|
|
2398
2404
|
}
|
|
2399
2405
|
|
|
2406
|
+
/**
|
|
2407
|
+
* Open a new per-turn budget window: snapshot the cumulative output baseline,
|
|
2408
|
+
* reset the eval-subagent counter, and set the (optional) ceiling. Called once
|
|
2409
|
+
* per real user message; `total` is null when no `+Nk` directive was present.
|
|
2410
|
+
*/
|
|
2411
|
+
beginTurnBudget(total: number | null, hard: boolean): void {
|
|
2412
|
+
this.#turnBudget = { total, hard };
|
|
2413
|
+
this.#turnBaselineOutput = this.#usageStatistics.output;
|
|
2414
|
+
this.#turnEvalOutput = 0;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
/** Record output tokens consumed by an eval-spawned subagent in the current turn. */
|
|
2418
|
+
recordEvalSubagentOutput(output: number): void {
|
|
2419
|
+
if (Number.isFinite(output) && output > 0) this.#turnEvalOutput += output;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/**
|
|
2423
|
+
* Current turn budget for the eval `budget` helper: the ceiling (null = none),
|
|
2424
|
+
* output tokens spent this turn (main loop + eval-spawned subagents, no
|
|
2425
|
+
* double-count), and whether the ceiling is hard.
|
|
2426
|
+
*/
|
|
2427
|
+
getTurnBudget(): { total: number | null; spent: number; hard: boolean } {
|
|
2428
|
+
const mainDelta = Math.max(0, this.#usageStatistics.output - this.#turnBaselineOutput);
|
|
2429
|
+
return { total: this.#turnBudget.total, spent: mainDelta + this.#turnEvalOutput, hard: this.#turnBudget.hard };
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2400
2432
|
getSessionDir(): string {
|
|
2401
2433
|
return this.sessionDir;
|
|
2402
2434
|
}
|