@oh-my-pi/pi-coding-agent 16.2.6 → 16.2.7
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 +23 -0
- package/dist/cli.js +2656 -2651
- package/dist/types/cli/bench-cli.d.ts +3 -3
- package/dist/types/commands/bench.d.ts +1 -1
- package/dist/types/config/service-tier.d.ts +39 -24
- package/dist/types/config/settings-schema.d.ts +36 -36
- package/dist/types/mcp/config-writer.d.ts +48 -0
- package/dist/types/mcp/types.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +33 -13
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/session-context.d.ts +2 -2
- package/dist/types/session/session-entries.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +2 -2
- package/dist/types/system-prompt.test.d.ts +1 -0
- package/dist/types/task/executor.d.ts +6 -6
- package/dist/types/task/types.d.ts +6 -0
- package/dist/types/task/worktree.d.ts +32 -6
- package/dist/types/tiny/title-client.d.ts +5 -1
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/utils/git.d.ts +17 -0
- package/package.json +12 -12
- package/src/cli/bench-cli.ts +19 -12
- package/src/cli/tiny-models-cli.ts +18 -4
- package/src/commands/bench.ts +3 -3
- package/src/config/mcp-schema.json +10 -1
- package/src/config/service-tier.ts +85 -56
- package/src/config/settings-schema.ts +42 -36
- package/src/config/settings.ts +47 -0
- package/src/eval/agent-bridge.ts +4 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/config-writer.ts +121 -0
- package/src/mcp/config.ts +10 -6
- package/src/mcp/types.ts +3 -0
- package/src/modes/components/extensions/extension-dashboard.ts +46 -0
- package/src/modes/components/extensions/state-manager.ts +24 -3
- package/src/modes/controllers/event-controller.ts +7 -0
- package/src/modes/utils/transcript-render-helpers.ts +2 -2
- package/src/sdk.ts +12 -11
- package/src/session/agent-session.ts +186 -76
- package/src/session/messages.ts +1 -1
- package/src/session/session-context.ts +4 -4
- package/src/session/session-entries.ts +2 -2
- package/src/session/session-manager.ts +9 -2
- package/src/slash-commands/builtin-registry.ts +2 -10
- package/src/system-prompt.test.ts +158 -0
- package/src/system-prompt.ts +69 -26
- package/src/task/executor.ts +23 -16
- package/src/task/index.ts +7 -5
- package/src/task/isolation-runner.ts +15 -1
- package/src/task/types.ts +6 -0
- package/src/task/worktree.ts +219 -38
- package/src/tiny/title-client.ts +19 -13
- package/src/tools/index.ts +3 -3
- package/src/tools/irc.ts +9 -3
- package/src/tools/read.ts +28 -28
- package/src/utils/file-mentions.ts +10 -1
- package/src/utils/git.ts +38 -0
- package/src/web/search/providers/duckduckgo.ts +17 -3
|
@@ -92,6 +92,8 @@ import type {
|
|
|
92
92
|
ResetCreditRedeemOutcome,
|
|
93
93
|
ResetCreditTarget,
|
|
94
94
|
ServiceTier,
|
|
95
|
+
ServiceTierByFamily,
|
|
96
|
+
ServiceTierFamily,
|
|
95
97
|
SimpleStreamOptions,
|
|
96
98
|
TextContent,
|
|
97
99
|
ToolCall,
|
|
@@ -105,7 +107,9 @@ import {
|
|
|
105
107
|
deriveClaudeDeviceId,
|
|
106
108
|
Effort,
|
|
107
109
|
parseRateLimitReason,
|
|
108
|
-
|
|
110
|
+
realizesPriorityServiceTier,
|
|
111
|
+
resolveModelServiceTier,
|
|
112
|
+
serviceTierFamily,
|
|
109
113
|
streamSimple,
|
|
110
114
|
} from "@oh-my-pi/pi-ai";
|
|
111
115
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
@@ -168,7 +172,7 @@ import {
|
|
|
168
172
|
} from "../config/model-resolver";
|
|
169
173
|
import { MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
|
|
170
174
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
171
|
-
import {
|
|
175
|
+
import { buildServiceTierByFamily, serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier";
|
|
172
176
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
173
177
|
import { getDefault, onAppendOnlyModeChanged, validateProviderMaxInFlightRequests } from "../config/settings";
|
|
174
178
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
@@ -506,6 +510,8 @@ export interface AgentSessionConfig {
|
|
|
506
510
|
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
507
511
|
/** Initial session thinking selector. */
|
|
508
512
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
513
|
+
/** Initial per-family service tiers (OpenAI / Anthropic / Google) for the live session. */
|
|
514
|
+
serviceTierByFamily?: ServiceTierByFamily;
|
|
509
515
|
/** Prompt templates for expansion */
|
|
510
516
|
promptTemplates?: PromptTemplate[];
|
|
511
517
|
/** File-based slash commands for expansion */
|
|
@@ -1787,6 +1793,7 @@ export class AgentSession {
|
|
|
1787
1793
|
// toggle scopes priority to Fireworks alone, without mutating the shared
|
|
1788
1794
|
// session `serviceTier` that drives `/fast` and OpenAI/Anthropic priority.
|
|
1789
1795
|
this.agent.serviceTierResolver = model => this.#effectiveServiceTier(model);
|
|
1796
|
+
this.#serviceTierByFamily = config.serviceTierByFamily ?? {};
|
|
1790
1797
|
this.#advisorTools = config.advisorTools;
|
|
1791
1798
|
this.#advisorWatchdogPrompt = config.advisorWatchdogPrompt;
|
|
1792
1799
|
this.#advisorSharedInstructions = config.advisorSharedInstructions;
|
|
@@ -2045,15 +2052,20 @@ export class AgentSession {
|
|
|
2045
2052
|
const legacy = !this.#advisorConfigs?.length;
|
|
2046
2053
|
const roster: AdvisorConfig[] = legacy ? [{ name: "default" }] : this.#advisorConfigs!;
|
|
2047
2054
|
|
|
2048
|
-
// Advisor service tier (`
|
|
2049
|
-
//
|
|
2050
|
-
// per request (like the main agent, including /fast toggles)
|
|
2051
|
-
//
|
|
2052
|
-
|
|
2053
|
-
const
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2055
|
+
// Advisor service tier (`tier.advisor`): "none" (default) runs the advisor
|
|
2056
|
+
// on standard processing; "inherit" tracks the session's live per-family
|
|
2057
|
+
// tiers per request (like the main agent, including /fast toggles); a
|
|
2058
|
+
// concrete value is broadcast across families and applied to the advisor
|
|
2059
|
+
// model's family. One value for all advisors.
|
|
2060
|
+
const advisorTierSetting = this.settings.get("tier.advisor");
|
|
2061
|
+
const advisorTierMap =
|
|
2062
|
+
advisorTierSetting === "inherit"
|
|
2063
|
+
? undefined
|
|
2064
|
+
: serviceTierForAllFamilies(serviceTierSettingToTier(advisorTierSetting));
|
|
2065
|
+
const advisorServiceTierResolver = (model: Model): ServiceTier | undefined =>
|
|
2066
|
+
advisorTierSetting === "inherit"
|
|
2067
|
+
? this.#effectiveServiceTier(model)
|
|
2068
|
+
: resolveModelServiceTier(advisorTierMap, model);
|
|
2057
2069
|
|
|
2058
2070
|
const usedSlugs = new Set<string>();
|
|
2059
2071
|
for (const config of roster) {
|
|
@@ -2152,7 +2164,7 @@ export class AgentSession {
|
|
|
2152
2164
|
transformProviderContext: this.#transformProviderContext,
|
|
2153
2165
|
intentTracing: false,
|
|
2154
2166
|
telemetry: advisorTelemetry,
|
|
2155
|
-
serviceTier:
|
|
2167
|
+
serviceTier: undefined,
|
|
2156
2168
|
serviceTierResolver: advisorServiceTierResolver,
|
|
2157
2169
|
});
|
|
2158
2170
|
advisorAgent.setDisableReasoning(shouldDisableReasoning(advisorThinkingLevel));
|
|
@@ -3215,10 +3227,11 @@ export class AgentSession {
|
|
|
3215
3227
|
if (event.message.role === "assistant") {
|
|
3216
3228
|
this.#lastAssistantMessage = event.message;
|
|
3217
3229
|
const assistantMsg = event.message as AssistantMessage;
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3230
|
+
if (
|
|
3231
|
+
assistantMsg.disabledFeatures?.includes("priority") &&
|
|
3232
|
+
this.#serviceTierByFamily.anthropic === "priority"
|
|
3233
|
+
) {
|
|
3234
|
+
this.setServiceTierFamily("anthropic", undefined);
|
|
3222
3235
|
this.emitNotice(
|
|
3223
3236
|
"warning",
|
|
3224
3237
|
"Priority/fast mode rejected for this model; retried without it. Fast mode is now off.",
|
|
@@ -5203,8 +5216,11 @@ export class AgentSession {
|
|
|
5203
5216
|
return this.#autoResolvedLevel;
|
|
5204
5217
|
}
|
|
5205
5218
|
|
|
5206
|
-
|
|
5207
|
-
|
|
5219
|
+
#serviceTierByFamily: ServiceTierByFamily = {};
|
|
5220
|
+
|
|
5221
|
+
/** Live per-family service tiers (OpenAI / Anthropic / Google). */
|
|
5222
|
+
get serviceTierByFamily(): ServiceTierByFamily {
|
|
5223
|
+
return this.#serviceTierByFamily;
|
|
5208
5224
|
}
|
|
5209
5225
|
|
|
5210
5226
|
/** Whether agent is currently streaming a response */
|
|
@@ -7892,7 +7908,7 @@ export class AgentSession {
|
|
|
7892
7908
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
7893
7909
|
|
|
7894
7910
|
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel, this.configuredThinkingLevel());
|
|
7895
|
-
this.sessionManager.appendServiceTierChange(this
|
|
7911
|
+
this.sessionManager.appendServiceTierChange(this.#serviceTierEntry());
|
|
7896
7912
|
if (nextDiscoverySessionToolNames) {
|
|
7897
7913
|
await this.#applyActiveToolsByName(nextDiscoverySessionToolNames, { persistMCPSelection: false });
|
|
7898
7914
|
if (this.getSelectedMCPToolNames().length > 0) {
|
|
@@ -8434,38 +8450,36 @@ export class AgentSession {
|
|
|
8434
8450
|
}
|
|
8435
8451
|
|
|
8436
8452
|
/**
|
|
8437
|
-
* True when
|
|
8438
|
-
*
|
|
8439
|
-
*
|
|
8440
|
-
*
|
|
8453
|
+
* True when the currently selected model's family is set to `priority` — the
|
|
8454
|
+
* `/fast` on/off state for the active model. Returns false when no model is
|
|
8455
|
+
* selected or the model exposes no service-tier family (e.g. Fireworks, which
|
|
8456
|
+
* has its own Providers › Fireworks Tier toggle).
|
|
8441
8457
|
*
|
|
8442
|
-
* For "is
|
|
8443
|
-
* {@link isFastModeActive} instead
|
|
8458
|
+
* For "is priority actually applied to the next request?" use
|
|
8459
|
+
* {@link isFastModeActive} instead.
|
|
8444
8460
|
*/
|
|
8445
8461
|
isFastModeEnabled(): boolean {
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
);
|
|
8462
|
+
const family = this.model ? serviceTierFamily(this.model) : undefined;
|
|
8463
|
+
return family ? this.#serviceTierByFamily[family] === "priority" : false;
|
|
8449
8464
|
}
|
|
8450
8465
|
|
|
8451
8466
|
/**
|
|
8452
|
-
* True when
|
|
8453
|
-
*
|
|
8454
|
-
*
|
|
8455
|
-
* no model is selected.
|
|
8467
|
+
* True when `priority` is actually realized on the wire for the currently
|
|
8468
|
+
* selected model (OpenAI/Google `service_tier`, direct Anthropic fast mode,
|
|
8469
|
+
* or Fireworks priority). Returns false for tiers the active model can't
|
|
8470
|
+
* realize and when no model is selected.
|
|
8456
8471
|
*/
|
|
8457
8472
|
isFastModeActive(): boolean {
|
|
8458
|
-
|
|
8473
|
+
const model = this.model;
|
|
8474
|
+
return !!model && realizesPriorityServiceTier(this.#effectiveServiceTier(model), model);
|
|
8459
8475
|
}
|
|
8460
8476
|
|
|
8461
8477
|
/**
|
|
8462
|
-
* Effective wire service-tier for a request to `model`. Fireworks models
|
|
8463
|
-
*
|
|
8464
|
-
*
|
|
8465
|
-
*
|
|
8466
|
-
*
|
|
8467
|
-
* serving path is mutually exclusive with Priority. Every other provider
|
|
8468
|
-
* uses the session `serviceTier` unchanged.
|
|
8478
|
+
* Effective wire service-tier for a request to `model`. Fireworks models take
|
|
8479
|
+
* the Priority serving path only when the Providers › Fireworks Tier setting
|
|
8480
|
+
* is `"priority"` (and never for `-fast` variants, whose Fast serving path is
|
|
8481
|
+
* mutually exclusive with Priority). Every other model resolves the live
|
|
8482
|
+
* per-family tier map down to the entry for its family.
|
|
8469
8483
|
*/
|
|
8470
8484
|
#effectiveServiceTier(model: Model | undefined = this.model): ServiceTier | undefined {
|
|
8471
8485
|
if (model?.provider === "fireworks") {
|
|
@@ -8473,40 +8487,56 @@ export class AgentSession {
|
|
|
8473
8487
|
? "priority"
|
|
8474
8488
|
: undefined;
|
|
8475
8489
|
}
|
|
8476
|
-
return
|
|
8490
|
+
if (!model) return undefined;
|
|
8491
|
+
return resolveModelServiceTier(this.#serviceTierByFamily, model);
|
|
8492
|
+
}
|
|
8493
|
+
|
|
8494
|
+
/** The live per-family tier map, or `null` when empty (for session persistence). */
|
|
8495
|
+
#serviceTierEntry(): ServiceTierByFamily | null {
|
|
8496
|
+
return Object.keys(this.#serviceTierByFamily).length > 0 ? this.#serviceTierByFamily : null;
|
|
8477
8497
|
}
|
|
8478
8498
|
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8499
|
+
/** Set one family's tier (or clear it with `undefined`); persists the change. */
|
|
8500
|
+
setServiceTierFamily(family: ServiceTierFamily, tier: ServiceTier | undefined): void {
|
|
8501
|
+
if (this.#serviceTierByFamily[family] === tier) return;
|
|
8502
|
+
const next: ServiceTierByFamily = { ...this.#serviceTierByFamily };
|
|
8503
|
+
if (tier) next[family] = tier;
|
|
8504
|
+
else delete next[family];
|
|
8505
|
+
this.#applyServiceTierByFamily(next);
|
|
8506
|
+
}
|
|
8507
|
+
|
|
8508
|
+
/** Replace the whole per-family tier map; persists + re-arms Anthropic fast mode. */
|
|
8509
|
+
#applyServiceTierByFamily(next: ServiceTierByFamily): void {
|
|
8510
|
+
// Re-arming Anthropic priority clears the per-session fast-mode auto-disable
|
|
8511
|
+
// so the next request actually carries `speed: "fast"` again.
|
|
8512
|
+
if (next.anthropic === "priority" && this.#serviceTierByFamily.anthropic !== "priority") {
|
|
8487
8513
|
clearAnthropicFastModeFallback(this.#providerSessionState);
|
|
8488
8514
|
}
|
|
8489
|
-
this
|
|
8490
|
-
this.sessionManager.appendServiceTierChange(
|
|
8515
|
+
this.#serviceTierByFamily = next;
|
|
8516
|
+
this.sessionManager.appendServiceTierChange(this.#serviceTierEntry());
|
|
8491
8517
|
}
|
|
8492
8518
|
|
|
8519
|
+
/**
|
|
8520
|
+
* `/fast on|off` targets the family of the currently selected model: it sets
|
|
8521
|
+
* (or clears) that family's `priority` tier. Models without a service-tier
|
|
8522
|
+
* family (Fireworks, or providers with no tier knob) have nothing to toggle.
|
|
8523
|
+
*/
|
|
8493
8524
|
setFastMode(enabled: boolean): void {
|
|
8494
|
-
|
|
8495
|
-
|
|
8525
|
+
const family = this.model ? serviceTierFamily(this.model) : undefined;
|
|
8526
|
+
if (!family) {
|
|
8527
|
+
this.emitNotice("info", "The current model has no service-tier control for /fast to toggle.", "priority");
|
|
8496
8528
|
return;
|
|
8497
8529
|
}
|
|
8498
8530
|
if (!enabled) {
|
|
8499
|
-
this.
|
|
8531
|
+
if (this.#serviceTierByFamily[family] === "priority") this.setServiceTierFamily(family, undefined);
|
|
8500
8532
|
return;
|
|
8501
8533
|
}
|
|
8502
|
-
|
|
8503
|
-
this.setServiceTier(scope === "openai" ? "openai-only" : scope === "claude" ? "claude-only" : "priority");
|
|
8534
|
+
this.setServiceTierFamily(family, "priority");
|
|
8504
8535
|
}
|
|
8505
8536
|
|
|
8506
8537
|
toggleFastMode(): boolean {
|
|
8507
|
-
|
|
8508
|
-
this.
|
|
8509
|
-
return enabled;
|
|
8538
|
+
this.setFastMode(!this.isFastModeEnabled());
|
|
8539
|
+
return this.isFastModeEnabled();
|
|
8510
8540
|
}
|
|
8511
8541
|
|
|
8512
8542
|
/**
|
|
@@ -8871,6 +8901,8 @@ export class AgentSession {
|
|
|
8871
8901
|
const wantsSnapcompact =
|
|
8872
8902
|
compactionPrep.kind !== "fromHook" && effectiveSettings.strategy === "snapcompact" && !customInstructions;
|
|
8873
8903
|
const snapcompactReady = wantsSnapcompact;
|
|
8904
|
+
const snapcompactShapeSetting = this.settings.get("snapcompact.shape");
|
|
8905
|
+
let snapcompactShape: snapcompact.Shape | undefined;
|
|
8874
8906
|
if (wantsSnapcompact && !this.model.input.includes("image")) {
|
|
8875
8907
|
this.emitNotice(
|
|
8876
8908
|
"warning",
|
|
@@ -8879,8 +8911,16 @@ export class AgentSession {
|
|
|
8879
8911
|
);
|
|
8880
8912
|
throw new Error(`snapcompact cannot run locally: ${this.model.id} is text-only.`);
|
|
8881
8913
|
} else if (snapcompactReady) {
|
|
8882
|
-
const text = snapcompact.serializeConversation(
|
|
8883
|
-
|
|
8914
|
+
const text = snapcompact.serializeConversation(
|
|
8915
|
+
convertToLlm(preparation.messagesToSummarize.concat(preparation.turnPrefixMessages)),
|
|
8916
|
+
);
|
|
8917
|
+
const probeText = snapcompact.renderabilityProbeText(
|
|
8918
|
+
text,
|
|
8919
|
+
preparation.previousPreserveData,
|
|
8920
|
+
preparation.previousSummary,
|
|
8921
|
+
);
|
|
8922
|
+
snapcompactShape = snapcompact.resolveShapeForText(probeText, this.model, snapcompactShapeSetting);
|
|
8923
|
+
const renderScan = snapcompact.scanRenderability(probeText, { shape: snapcompactShape });
|
|
8884
8924
|
if (!renderScan.isSafe) {
|
|
8885
8925
|
const percent = (renderScan.unrenderableRatio * 100).toFixed(1);
|
|
8886
8926
|
this.emitNotice(
|
|
@@ -8918,10 +8958,14 @@ export class AgentSession {
|
|
|
8918
8958
|
);
|
|
8919
8959
|
throw new Error("snapcompact cannot run locally: kept history alone exceeds the context budget.");
|
|
8920
8960
|
} else {
|
|
8961
|
+
const shape = snapcompactShape;
|
|
8962
|
+
if (!shape) {
|
|
8963
|
+
throw new Error("snapcompact shape was not resolved before rendering.");
|
|
8964
|
+
}
|
|
8921
8965
|
snapcompactResult = await snapcompact.compact(preparation, {
|
|
8922
8966
|
convertToLlm,
|
|
8923
8967
|
model: this.model,
|
|
8924
|
-
|
|
8968
|
+
...(snapcompactShapeSetting === "auto" ? {} : { shape }),
|
|
8925
8969
|
maxFrames,
|
|
8926
8970
|
});
|
|
8927
8971
|
const ctxWindow = this.model?.contextWindow ?? 0;
|
|
@@ -11273,7 +11317,14 @@ export class AgentSession {
|
|
|
11273
11317
|
const text = snapcompact.serializeConversation(
|
|
11274
11318
|
convertToLlm(preparation.messagesToSummarize.concat(preparation.turnPrefixMessages)),
|
|
11275
11319
|
);
|
|
11276
|
-
const
|
|
11320
|
+
const probeText = snapcompact.renderabilityProbeText(
|
|
11321
|
+
text,
|
|
11322
|
+
preparation.previousPreserveData,
|
|
11323
|
+
preparation.previousSummary,
|
|
11324
|
+
);
|
|
11325
|
+
const shapeSetting = this.settings.get("snapcompact.shape");
|
|
11326
|
+
const shape = snapcompact.resolveShapeForText(probeText, this.model, shapeSetting);
|
|
11327
|
+
const renderScan = snapcompact.scanRenderability(probeText, { shape });
|
|
11277
11328
|
if (!renderScan.isSafe) {
|
|
11278
11329
|
const percent = (renderScan.unrenderableRatio * 100).toFixed(1);
|
|
11279
11330
|
logger.warn("Snapcompact disabled: high non-ASCII rate detected", {
|
|
@@ -11293,7 +11344,7 @@ export class AgentSession {
|
|
|
11293
11344
|
snapcompactResult = await snapcompact.compact(preparation, {
|
|
11294
11345
|
convertToLlm,
|
|
11295
11346
|
model: this.model,
|
|
11296
|
-
|
|
11347
|
+
...(shapeSetting === "auto" ? {} : { shape }),
|
|
11297
11348
|
maxFrames,
|
|
11298
11349
|
});
|
|
11299
11350
|
if (snapcompactResult) {
|
|
@@ -12839,6 +12890,50 @@ export class AgentSession {
|
|
|
12839
12890
|
// IRC Delivery
|
|
12840
12891
|
// =========================================================================
|
|
12841
12892
|
|
|
12893
|
+
/**
|
|
12894
|
+
* Surfaces (and consumes) IRC incoming asides that have reached this running
|
|
12895
|
+
* session but have not yet been folded into the next model step.
|
|
12896
|
+
*
|
|
12897
|
+
* The inbox tool injects the formatted body into the tool result, so the
|
|
12898
|
+
* model sees it once via the result. Leaving the record in
|
|
12899
|
+
* {@link #pendingIrcAsides} would let the aside provider deliver it a second
|
|
12900
|
+
* time at the next step boundary — including on `peek`, which is why peek
|
|
12901
|
+
* also drains here.
|
|
12902
|
+
*/
|
|
12903
|
+
drainPendingIrcInboxMessages(agentId: string): IrcMessage[] {
|
|
12904
|
+
const messages: IrcMessage[] = [];
|
|
12905
|
+
const remaining: CustomMessage[] = [];
|
|
12906
|
+
for (const record of this.#pendingIrcAsides) {
|
|
12907
|
+
if (record.customType !== "irc:incoming") {
|
|
12908
|
+
remaining.push(record);
|
|
12909
|
+
continue;
|
|
12910
|
+
}
|
|
12911
|
+
const details = record.details;
|
|
12912
|
+
if (!details || typeof details !== "object") {
|
|
12913
|
+
remaining.push(record);
|
|
12914
|
+
continue;
|
|
12915
|
+
}
|
|
12916
|
+
const id = Reflect.get(details, "id");
|
|
12917
|
+
const from = Reflect.get(details, "from");
|
|
12918
|
+
const body = Reflect.get(details, "message");
|
|
12919
|
+
const replyTo = Reflect.get(details, "replyTo");
|
|
12920
|
+
if (typeof id !== "string" || typeof from !== "string" || typeof body !== "string") {
|
|
12921
|
+
remaining.push(record);
|
|
12922
|
+
continue;
|
|
12923
|
+
}
|
|
12924
|
+
messages.push({
|
|
12925
|
+
id,
|
|
12926
|
+
from,
|
|
12927
|
+
to: agentId,
|
|
12928
|
+
body,
|
|
12929
|
+
ts: record.timestamp,
|
|
12930
|
+
...(typeof replyTo === "string" ? { replyTo } : {}),
|
|
12931
|
+
});
|
|
12932
|
+
}
|
|
12933
|
+
this.#pendingIrcAsides = remaining;
|
|
12934
|
+
return messages;
|
|
12935
|
+
}
|
|
12936
|
+
|
|
12842
12937
|
/**
|
|
12843
12938
|
* Deliver an IRC message into this session (recipient side; called by the
|
|
12844
12939
|
* IrcBus). Emits the `irc_message` session event for UI cards and injects
|
|
@@ -13150,7 +13245,15 @@ export class AgentSession {
|
|
|
13150
13245
|
// Flush pending writes before switching so restore snapshots reflect committed state.
|
|
13151
13246
|
await this.sessionManager.flush();
|
|
13152
13247
|
const previousSessionState = this.sessionManager.captureState();
|
|
13153
|
-
|
|
13248
|
+
// Only same-session reloads compare against the prior context to detect
|
|
13249
|
+
// rollback edits (`#didSessionMessagesChange` below). Building it for a
|
|
13250
|
+
// different-session switch is a pure waste — and on huge pre-fix sessions
|
|
13251
|
+
// it materializes every persisted snapcompact frame plus the
|
|
13252
|
+
// `openaiRemoteCompaction.replacementHistory` payload into messages,
|
|
13253
|
+
// blowing the heap before the new session even loads (issue #3846). The
|
|
13254
|
+
// error-recovery path rebuilds the context on demand from the restored
|
|
13255
|
+
// state instead.
|
|
13256
|
+
const previousSessionContext = switchingToDifferentSession ? undefined : this.buildDisplaySessionContext();
|
|
13154
13257
|
// switchSession replaces these arrays wholesale during load/rollback, so retaining
|
|
13155
13258
|
// the existing message objects is sufficient and avoids structured-clone failures for
|
|
13156
13259
|
// extension/custom metadata that is valid to persist but not cloneable.
|
|
@@ -13163,7 +13266,7 @@ export class AgentSession {
|
|
|
13163
13266
|
const previousThinkingLevel = this.#thinkingLevel;
|
|
13164
13267
|
const previousAutoThinking = this.#autoThinking;
|
|
13165
13268
|
const previousAutoResolvedLevel = this.#autoResolvedLevel;
|
|
13166
|
-
const
|
|
13269
|
+
const previousServiceTierByFamily = this.#serviceTierByFamily;
|
|
13167
13270
|
const previousSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
|
|
13168
13271
|
const previousTools = [...this.agent.state.tools];
|
|
13169
13272
|
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
|
@@ -13189,7 +13292,7 @@ export class AgentSession {
|
|
|
13189
13292
|
|
|
13190
13293
|
const sessionContext = this.buildDisplaySessionContext();
|
|
13191
13294
|
const didReloadConversationChange =
|
|
13192
|
-
|
|
13295
|
+
previousSessionContext !== undefined &&
|
|
13193
13296
|
this.#didSessionMessagesChange(previousSessionContext.messages, sessionContext.messages);
|
|
13194
13297
|
const fallbackSelectedMCPToolNames = this.#getSessionDefaultSelectedMCPToolNames(sessionPath);
|
|
13195
13298
|
await this.#restoreMCPSelectionsForSessionContext(sessionContext, { fallbackSelectedMCPToolNames });
|
|
@@ -13249,7 +13352,11 @@ export class AgentSession {
|
|
|
13249
13352
|
.getBranch()
|
|
13250
13353
|
.some(entry => entry.type === "service_tier_change");
|
|
13251
13354
|
const defaultThinkingLevel = parseConfiguredThinkingLevel(this.settings.get("defaultThinkingLevel"));
|
|
13252
|
-
const
|
|
13355
|
+
const configuredServiceTierByFamily = buildServiceTierByFamily(
|
|
13356
|
+
this.settings.get("tier.openai"),
|
|
13357
|
+
this.settings.get("tier.anthropic"),
|
|
13358
|
+
this.settings.get("tier.google"),
|
|
13359
|
+
);
|
|
13253
13360
|
// Restore the thinking selector. Each change persists the configured
|
|
13254
13361
|
// selector (`auto` or a concrete level), so prefer it: an `auto` session
|
|
13255
13362
|
// resumes in auto mode (reclassifying the next turn) instead of freezing at
|
|
@@ -13278,11 +13385,9 @@ export class AgentSession {
|
|
|
13278
13385
|
this.#thinkingLevel = resolveThinkingLevelForModel(this.model, restoredThinkingLevel);
|
|
13279
13386
|
}
|
|
13280
13387
|
this.#applyThinkingLevelToAgent(this.#thinkingLevel);
|
|
13281
|
-
this
|
|
13282
|
-
? sessionContext.serviceTier
|
|
13283
|
-
:
|
|
13284
|
-
? undefined
|
|
13285
|
-
: configuredServiceTier;
|
|
13388
|
+
this.#serviceTierByFamily = hasServiceTierEntry
|
|
13389
|
+
? (sessionContext.serviceTier ?? {})
|
|
13390
|
+
: configuredServiceTierByFamily;
|
|
13286
13391
|
|
|
13287
13392
|
if (switchingToDifferentSession) {
|
|
13288
13393
|
await this.#resetMemoryContextForNewTranscript();
|
|
@@ -13305,7 +13410,12 @@ export class AgentSession {
|
|
|
13305
13410
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
13306
13411
|
let restoreMcpError: unknown;
|
|
13307
13412
|
try {
|
|
13308
|
-
|
|
13413
|
+
// `previousSessionContext` was skipped on different-session switches to
|
|
13414
|
+
// avoid materializing the previous session's heavy compaction payload
|
|
13415
|
+
// in the success path; rebuild it here on demand from the restored
|
|
13416
|
+
// state so MCP selection restoration still has its inputs.
|
|
13417
|
+
const mcpRestoreContext = previousSessionContext ?? this.buildDisplaySessionContext();
|
|
13418
|
+
await this.#restoreMCPSelectionsForSessionContext(mcpRestoreContext, {
|
|
13309
13419
|
fallbackSelectedMCPToolNames: previousFallbackSelectedMCPToolNames,
|
|
13310
13420
|
});
|
|
13311
13421
|
} catch (mcpError) {
|
|
@@ -13334,7 +13444,7 @@ export class AgentSession {
|
|
|
13334
13444
|
this.#autoThinking = previousAutoThinking;
|
|
13335
13445
|
this.#autoResolvedLevel = previousAutoResolvedLevel;
|
|
13336
13446
|
this.#applyThinkingLevelToAgent(previousThinkingLevel);
|
|
13337
|
-
this
|
|
13447
|
+
this.#serviceTierByFamily = previousServiceTierByFamily;
|
|
13338
13448
|
this.#syncTodoPhasesFromBranch();
|
|
13339
13449
|
this.#resetAllAdvisorRuntimes();
|
|
13340
13450
|
this.#reconnectToAgent();
|
|
@@ -14288,7 +14398,7 @@ export class AgentSession {
|
|
|
14288
14398
|
const payload = {
|
|
14289
14399
|
model: this.agent.state.model ?? null,
|
|
14290
14400
|
thinkingLevel: this.#thinkingLevel ?? null,
|
|
14291
|
-
serviceTier: this
|
|
14401
|
+
serviceTier: this.#serviceTierEntry(),
|
|
14292
14402
|
systemPrompt: this.agent.state.systemPrompt,
|
|
14293
14403
|
tools: this.agent.state.tools.map(tool => ({
|
|
14294
14404
|
name: tool.name,
|
package/src/session/messages.ts
CHANGED
|
@@ -488,7 +488,7 @@ export interface FileMentionMessage {
|
|
|
488
488
|
/** File size in bytes, if known. */
|
|
489
489
|
byteSize?: number;
|
|
490
490
|
/** Why the file contents were omitted from auto-read. */
|
|
491
|
-
skippedReason?: "tooLarge";
|
|
491
|
+
skippedReason?: "tooLarge" | "binary";
|
|
492
492
|
image?: ImageContent;
|
|
493
493
|
}>;
|
|
494
494
|
timestamp: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type
|
|
2
|
+
import { coerceServiceTierByFamily, type ProviderPayload, type ServiceTierByFamily } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import * as snapcompact from "@oh-my-pi/snapcompact";
|
|
4
4
|
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from "./messages";
|
|
5
5
|
import { type CompactionEntry, EPHEMERAL_MODEL_CHANGE_ROLE, type SessionEntry } from "./session-entries";
|
|
@@ -9,7 +9,7 @@ export interface SessionContext {
|
|
|
9
9
|
thinkingLevel?: string;
|
|
10
10
|
/** Configured thinking selector (`"auto"` or a concrete level) from the latest change. */
|
|
11
11
|
configuredThinkingLevel?: string;
|
|
12
|
-
serviceTier?:
|
|
12
|
+
serviceTier?: ServiceTierByFamily;
|
|
13
13
|
/** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */
|
|
14
14
|
models: Record<string, string>;
|
|
15
15
|
/** Names of TTSR rules that have been injected this session */
|
|
@@ -138,7 +138,7 @@ export function buildSessionContext(
|
|
|
138
138
|
// Extract settings and find compaction
|
|
139
139
|
let thinkingLevel: string | undefined = "off";
|
|
140
140
|
let configuredThinkingLevel: string | undefined;
|
|
141
|
-
let serviceTier:
|
|
141
|
+
let serviceTier: ServiceTierByFamily | undefined;
|
|
142
142
|
const models: Record<string, string> = {};
|
|
143
143
|
let compaction: CompactionEntry | null = null;
|
|
144
144
|
const injectedTtsrRulesSet = new Set<string>();
|
|
@@ -169,7 +169,7 @@ export function buildSessionContext(
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
} else if (entry.type === "service_tier_change") {
|
|
172
|
-
serviceTier = entry.serviceTier
|
|
172
|
+
serviceTier = coerceServiceTierByFamily(entry.serviceTier);
|
|
173
173
|
} else if (entry.type === "message" && entry.message.role === "assistant") {
|
|
174
174
|
// Legacy fallback: infer default model from assistant messages only
|
|
175
175
|
// when no explicit `model_change` (role=default) entry has been
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { ImageContent, MessageAttribution,
|
|
2
|
+
import type { ImageContent, MessageAttribution, ServiceTierByFamily, TextContent } from "@oh-my-pi/pi-ai";
|
|
3
3
|
|
|
4
4
|
export const CURRENT_SESSION_VERSION = 3;
|
|
5
5
|
|
|
@@ -73,7 +73,7 @@ export interface ModelChangeEntry extends SessionEntryBase {
|
|
|
73
73
|
|
|
74
74
|
export interface ServiceTierChangeEntry extends SessionEntryBase {
|
|
75
75
|
type: "service_tier_change";
|
|
76
|
-
serviceTier:
|
|
76
|
+
serviceTier: ServiceTierByFamily | null;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export interface CompactionEntry<T = unknown> extends SessionEntryBase {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ImageContent,
|
|
5
|
+
Message,
|
|
6
|
+
MessageAttribution,
|
|
7
|
+
ServiceTierByFamily,
|
|
8
|
+
TextContent,
|
|
9
|
+
Usage,
|
|
10
|
+
} from "@oh-my-pi/pi-ai";
|
|
4
11
|
import {
|
|
5
12
|
directoryExists,
|
|
6
13
|
getBlobsDir,
|
|
@@ -1286,7 +1293,7 @@ export class SessionManager {
|
|
|
1286
1293
|
return entry.id;
|
|
1287
1294
|
}
|
|
1288
1295
|
|
|
1289
|
-
appendServiceTierChange(serviceTier:
|
|
1296
|
+
appendServiceTierChange(serviceTier: ServiceTierByFamily | null): string {
|
|
1290
1297
|
const entry: ServiceTierChangeEntry = { type: "service_tier_change", ...this.#freshEntryFields(), serviceTier };
|
|
1291
1298
|
this.#recordEntry(entry);
|
|
1292
1299
|
return entry.id;
|
|
@@ -73,17 +73,9 @@ function refreshStatusLine(ctx: InteractiveModeContext): void {
|
|
|
73
73
|
ctx.ui.requestRender();
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/** `/fast status` label: "
|
|
76
|
+
/** `/fast status` label for the active model: "on" when its family is priority, else "off". */
|
|
77
77
|
function formatFastModeStatus(session: AgentSession): string {
|
|
78
|
-
|
|
79
|
-
switch (session.serviceTier) {
|
|
80
|
-
case "openai-only":
|
|
81
|
-
return "on (OpenAI only)";
|
|
82
|
-
case "claude-only":
|
|
83
|
-
return "on (Claude only)";
|
|
84
|
-
default:
|
|
85
|
-
return "on";
|
|
86
|
-
}
|
|
78
|
+
return session.isFastModeEnabled() ? "on" : "off";
|
|
87
79
|
}
|
|
88
80
|
|
|
89
81
|
const AUTOCOMPLETE_DETAIL_LIMIT = 48;
|