@oh-my-pi/pi-coding-agent 15.13.3 → 16.0.0
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 +40 -0
- package/dist/cli.js +506 -443
- package/dist/types/advisor/__tests__/advisor.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +58 -0
- package/dist/types/advisor/index.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +52 -0
- package/dist/types/advisor/watchdog.d.ts +5 -0
- package/dist/types/config/model-roles.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +44 -5
- package/dist/types/modes/components/advisor-message.d.ts +9 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/sdk.d.ts +3 -3
- package/dist/types/session/agent-session.d.ts +71 -2
- package/dist/types/session/session-history-format.d.ts +4 -0
- package/dist/types/session/yield-queue.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/report-tool-issue.d.ts +0 -1
- package/package.json +13 -13
- package/src/advisor/__tests__/advisor.test.ts +586 -0
- package/src/advisor/advise-tool.ts +87 -0
- package/src/advisor/index.ts +3 -0
- package/src/advisor/runtime.ts +248 -0
- package/src/advisor/watchdog.ts +83 -0
- package/src/config/model-roles.ts +13 -1
- package/src/config/settings-schema.ts +42 -5
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/main.ts +4 -0
- package/src/modes/components/advisor-message.ts +99 -0
- package/src/modes/components/agent-hub.ts +7 -0
- package/src/modes/components/assistant-message.ts +86 -0
- package/src/modes/components/status-line/segments.ts +20 -7
- package/src/modes/controllers/command-controller.ts +69 -2
- package/src/modes/interactive-mode.ts +12 -2
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/ui-helpers.ts +9 -0
- package/src/prompts/advisor/advise-tool.md +1 -0
- package/src/prompts/advisor/system.md +31 -0
- package/src/sdk.ts +52 -13
- package/src/session/agent-session.ts +560 -13
- package/src/session/session-dump-format.ts +15 -131
- package/src/session/session-history-format.ts +30 -11
- package/src/session/yield-queue.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +102 -4
- package/src/system-prompt.ts +1 -1
- package/src/tools/path-utils.ts +33 -2
- package/src/tools/report-tool-issue.ts +2 -7
- package/src/web/scrapers/docs-rs.ts +2 -3
|
@@ -22,7 +22,7 @@ import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
|
22
22
|
import {
|
|
23
23
|
type AfterToolCallContext,
|
|
24
24
|
type AfterToolCallResult,
|
|
25
|
-
|
|
25
|
+
Agent,
|
|
26
26
|
AgentBusyError,
|
|
27
27
|
type AgentEvent,
|
|
28
28
|
type AgentMessage,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type AgentTool,
|
|
31
31
|
AppendOnlyContextManager,
|
|
32
32
|
type AsideMessage,
|
|
33
|
+
type CompactionSummaryMessage,
|
|
33
34
|
resolveTelemetry,
|
|
34
35
|
ThinkingLevel,
|
|
35
36
|
} from "@oh-my-pi/pi-agent-core";
|
|
@@ -54,6 +55,8 @@ import {
|
|
|
54
55
|
generateHandoff,
|
|
55
56
|
prepareCompaction,
|
|
56
57
|
resolveThresholdTokens,
|
|
58
|
+
type SessionEntry,
|
|
59
|
+
type SessionMessageEntry,
|
|
57
60
|
type ShakeConfig,
|
|
58
61
|
type ShakeRegion,
|
|
59
62
|
type SummaryOptions,
|
|
@@ -114,6 +117,16 @@ import {
|
|
|
114
117
|
Snowflake,
|
|
115
118
|
} from "@oh-my-pi/pi-utils";
|
|
116
119
|
import * as snapcompact from "@oh-my-pi/snapcompact";
|
|
120
|
+
import {
|
|
121
|
+
AdviseTool,
|
|
122
|
+
type AdvisorAgent,
|
|
123
|
+
type AdvisorMessageDetails,
|
|
124
|
+
type AdvisorNote,
|
|
125
|
+
AdvisorRuntime,
|
|
126
|
+
type AdvisorSeverity,
|
|
127
|
+
formatAdvisorBatchContent,
|
|
128
|
+
isInterruptingSeverity,
|
|
129
|
+
} from "../advisor";
|
|
117
130
|
import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
|
|
118
131
|
import { classifyDifficulty } from "../auto-thinking/classifier";
|
|
119
132
|
import { reset as resetCapabilities } from "../capability";
|
|
@@ -129,6 +142,7 @@ import {
|
|
|
129
142
|
parseModelString,
|
|
130
143
|
type ResolvedModelRoleValue,
|
|
131
144
|
resolveModelRoleValue,
|
|
145
|
+
resolveRoleSelection,
|
|
132
146
|
} from "../config/model-resolver";
|
|
133
147
|
import { MODEL_ROLE_IDS } from "../config/model-roles";
|
|
134
148
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
@@ -189,6 +203,7 @@ import { computeNonMessageTokens } from "../modes/utils/context-usage";
|
|
|
189
203
|
import { containsWorkflow, WORKFLOW_NOTICE } from "../modes/workflow";
|
|
190
204
|
import { createPlanReadMatcher } from "../plan-mode/plan-protection";
|
|
191
205
|
import type { PlanModeState } from "../plan-mode/state";
|
|
206
|
+
import advisorSystemPrompt from "../prompts/advisor/system.md" with { type: "text" };
|
|
192
207
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
193
208
|
import eagerTaskPrompt from "../prompts/system/eager-task.md" with { type: "text" };
|
|
194
209
|
import eagerTodoPrompt from "../prompts/system/eager-todo.md" with { type: "text" };
|
|
@@ -268,6 +283,7 @@ import { getLatestCompactionEntry, getRestorableSessionModels } from "./session-
|
|
|
268
283
|
import { formatSessionDumpText } from "./session-dump-format";
|
|
269
284
|
import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions } from "./session-entries";
|
|
270
285
|
import { EPHEMERAL_MODEL_CHANGE_ROLE } from "./session-entries";
|
|
286
|
+
import { formatSessionHistoryMarkdown } from "./session-history-format";
|
|
271
287
|
import type { SessionManager } from "./session-manager";
|
|
272
288
|
import type { ShakeMode, ShakeResult } from "./shake-types";
|
|
273
289
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
@@ -457,6 +473,15 @@ export interface AgentSessionConfig {
|
|
|
457
473
|
* so that credential sticky selection is consistent with the session's streaming calls.
|
|
458
474
|
*/
|
|
459
475
|
providerSessionId?: string;
|
|
476
|
+
/**
|
|
477
|
+
* Hard-isolated read-only tools (read/search/find) for the advisor agent,
|
|
478
|
+
* pre-built in `createAgentSession` against a distinct `ToolSession` so the
|
|
479
|
+
* advisor's reads never share the primary's snapshot/seen-lines/conflict
|
|
480
|
+
* caches. Undefined when the advisor is disabled.
|
|
481
|
+
*/
|
|
482
|
+
advisorReadOnlyTools?: AgentTool[];
|
|
483
|
+
/** Preloaded watchdog prompt content for the advisor. */
|
|
484
|
+
advisorWatchdogPrompt?: string;
|
|
460
485
|
}
|
|
461
486
|
|
|
462
487
|
/** Options for AgentSession.prompt() */
|
|
@@ -539,6 +564,28 @@ export interface SessionStats {
|
|
|
539
564
|
cost: number;
|
|
540
565
|
}
|
|
541
566
|
|
|
567
|
+
/** Advisor statistics for /advisor status command. */
|
|
568
|
+
export interface AdvisorStats {
|
|
569
|
+
configured: boolean;
|
|
570
|
+
active: boolean;
|
|
571
|
+
model?: Model;
|
|
572
|
+
contextWindow: number;
|
|
573
|
+
contextTokens: number;
|
|
574
|
+
tokens: {
|
|
575
|
+
input: number;
|
|
576
|
+
output: number;
|
|
577
|
+
cacheRead: number;
|
|
578
|
+
cacheWrite: number;
|
|
579
|
+
total: number;
|
|
580
|
+
};
|
|
581
|
+
cost: number;
|
|
582
|
+
messages: {
|
|
583
|
+
user: number;
|
|
584
|
+
assistant: number;
|
|
585
|
+
total: number;
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
542
589
|
export interface FreshSessionResult {
|
|
543
590
|
previousSessionId: string;
|
|
544
591
|
sessionId: string;
|
|
@@ -944,6 +991,12 @@ export class AgentSession {
|
|
|
944
991
|
#planModeState: PlanModeState | undefined;
|
|
945
992
|
#goalModeState: GoalModeState | undefined;
|
|
946
993
|
#goalRuntime: GoalRuntime;
|
|
994
|
+
#advisorRuntime?: AdvisorRuntime;
|
|
995
|
+
/** The advisor's own agent, retained so `/dump advisor` can serialize its transcript. Undefined when no advisor is active. */
|
|
996
|
+
#advisorAgent?: Agent;
|
|
997
|
+
#advisorReadOnlyTools?: AgentTool[];
|
|
998
|
+
#advisorWatchdogPrompt?: string;
|
|
999
|
+
#advisorYieldQueueUnsubscribe?: () => void;
|
|
947
1000
|
#goalTurnCounter = 0;
|
|
948
1001
|
#planReferenceSent = false;
|
|
949
1002
|
#planReferencePath = "local://PLAN.md";
|
|
@@ -1234,6 +1287,8 @@ export class AgentSession {
|
|
|
1234
1287
|
this.#customCommands = config.customCommands ?? [];
|
|
1235
1288
|
this.#skillsSettings = config.skillsSettings;
|
|
1236
1289
|
this.#modelRegistry = config.modelRegistry;
|
|
1290
|
+
this.#advisorReadOnlyTools = config.advisorReadOnlyTools;
|
|
1291
|
+
this.#advisorWatchdogPrompt = config.advisorWatchdogPrompt;
|
|
1237
1292
|
this.#validateRetryFallbackChains();
|
|
1238
1293
|
this.#toolRegistry = config.toolRegistry ?? new Map();
|
|
1239
1294
|
this.#requestedToolNames = config.requestedToolNames;
|
|
@@ -1266,6 +1321,17 @@ export class AgentSession {
|
|
|
1266
1321
|
};
|
|
1267
1322
|
this.agent.setProviderResponseInterceptor(this.#onResponse);
|
|
1268
1323
|
this.agent.setRawSseEventInterceptor(this.#onSseEvent);
|
|
1324
|
+
this.agent.setOnTurnEnd(async (messages, signal) => {
|
|
1325
|
+
if (signal?.aborted) return;
|
|
1326
|
+
if (this.#advisorRuntime && !this.#advisorRuntime.disposed) {
|
|
1327
|
+
this.#advisorRuntime.onTurnEnd(messages);
|
|
1328
|
+
const syncBacklog = this.settings.get("advisor.syncBacklog");
|
|
1329
|
+
if (syncBacklog !== "off") {
|
|
1330
|
+
const threshold = parseInt(syncBacklog, 10);
|
|
1331
|
+
await this.#advisorRuntime.waitForCatchup(30000, threshold, signal);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1269
1335
|
this.yieldQueue = new YieldQueue({
|
|
1270
1336
|
isStreaming: () => this.isStreaming,
|
|
1271
1337
|
injectIdle: async messages => {
|
|
@@ -1377,12 +1443,304 @@ export class AgentSession {
|
|
|
1377
1443
|
},
|
|
1378
1444
|
});
|
|
1379
1445
|
|
|
1446
|
+
if (this.settings.get("advisor.enabled")) this.#buildAdvisorRuntime();
|
|
1447
|
+
|
|
1380
1448
|
// Always subscribe to agent events for internal handling
|
|
1381
1449
|
// (session persistence, hooks, auto-compaction, retry logic)
|
|
1382
1450
|
this.#unsubscribeAgent = this.agent.subscribe(this.#handleAgentEvent);
|
|
1383
1451
|
// Re-evaluate append-only context mode when the setting changes at runtime.
|
|
1384
1452
|
this.#unsubscribeAppendOnly = onAppendOnlyModeChanged(_value => this.#syncAppendOnlyContext(this.model));
|
|
1385
1453
|
}
|
|
1454
|
+
// -------------------------------------------------------------------------
|
|
1455
|
+
// Advisor runtime lifecycle
|
|
1456
|
+
// -------------------------------------------------------------------------
|
|
1457
|
+
#buildAdvisorRuntime(seedToCurrent = false): boolean {
|
|
1458
|
+
if (this.#isDisposed) return false;
|
|
1459
|
+
if (this.#advisorRuntime) return true;
|
|
1460
|
+
if (!this.settings.get("advisor.enabled")) return false;
|
|
1461
|
+
if (this.#agentKind !== "main" && !this.settings.get("advisor.subagents")) return false;
|
|
1462
|
+
|
|
1463
|
+
const advisorSel = resolveRoleSelection(
|
|
1464
|
+
["advisor"],
|
|
1465
|
+
this.settings,
|
|
1466
|
+
this.#modelRegistry.getAvailable(),
|
|
1467
|
+
this.#modelRegistry,
|
|
1468
|
+
);
|
|
1469
|
+
if (!advisorSel) {
|
|
1470
|
+
logger.debug("advisor enabled but no model assigned to the 'advisor' role; advisor inactive");
|
|
1471
|
+
return false;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Concern and blocker interrupt the running agent through the steering
|
|
1475
|
+
// channel (aborting in-flight tools at the next steering boundary); when
|
|
1476
|
+
// the loop has already yielded, triggerTurn resumes it so the advice is
|
|
1477
|
+
// acted on immediately rather than waiting for the next user prompt. A
|
|
1478
|
+
// plain nit rides the non-interrupting YieldQueue aside.
|
|
1479
|
+
const enqueueAdvice = (note: string, severity?: AdvisorSeverity) => {
|
|
1480
|
+
if (isInterruptingSeverity(severity)) {
|
|
1481
|
+
const notes: AdvisorNote[] = [{ note, severity }];
|
|
1482
|
+
void this.sendCustomMessage(
|
|
1483
|
+
{
|
|
1484
|
+
customType: "advisor",
|
|
1485
|
+
content: formatAdvisorBatchContent(notes),
|
|
1486
|
+
display: true,
|
|
1487
|
+
attribution: "agent",
|
|
1488
|
+
details: { notes } satisfies AdvisorMessageDetails,
|
|
1489
|
+
},
|
|
1490
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
1491
|
+
).catch(err => logger.debug("advisor steer failed", { err: String(err) }));
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this.yieldQueue.enqueue("advisor", { note, severity });
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
const adviseTool = new AdviseTool(enqueueAdvice);
|
|
1498
|
+
const advisorReadOnlyTools = this.#advisorReadOnlyTools ?? [];
|
|
1499
|
+
|
|
1500
|
+
const appendOnlyContext = new AppendOnlyContextManager();
|
|
1501
|
+
const advisorThinkingLevel = advisorSel.thinkingLevel ?? ThinkingLevel.Medium;
|
|
1502
|
+
const systemPrompt = [advisorSystemPrompt];
|
|
1503
|
+
if (this.#advisorWatchdogPrompt) {
|
|
1504
|
+
systemPrompt.push(this.#advisorWatchdogPrompt);
|
|
1505
|
+
}
|
|
1506
|
+
const advisorAgent = new Agent({
|
|
1507
|
+
initialState: {
|
|
1508
|
+
systemPrompt,
|
|
1509
|
+
model: advisorSel.model,
|
|
1510
|
+
thinkingLevel: toReasoningEffort(advisorThinkingLevel),
|
|
1511
|
+
tools: [adviseTool, ...advisorReadOnlyTools],
|
|
1512
|
+
},
|
|
1513
|
+
appendOnlyContext,
|
|
1514
|
+
sessionId: this.sessionId ? `${this.sessionId}-advisor` : undefined,
|
|
1515
|
+
getApiKey: async provider => {
|
|
1516
|
+
const key = await this.#modelRegistry.getApiKeyForProvider(
|
|
1517
|
+
provider,
|
|
1518
|
+
this.sessionId ? `${this.sessionId}-advisor` : undefined,
|
|
1519
|
+
);
|
|
1520
|
+
if (!key) throw new Error(`No API key for advisor provider "${provider}"`);
|
|
1521
|
+
return key;
|
|
1522
|
+
},
|
|
1523
|
+
intentTracing: false,
|
|
1524
|
+
});
|
|
1525
|
+
advisorAgent.setDisableReasoning(shouldDisableReasoning(advisorThinkingLevel));
|
|
1526
|
+
|
|
1527
|
+
const advisorAgentFacade: AdvisorAgent = {
|
|
1528
|
+
prompt: input => advisorAgent.prompt(input),
|
|
1529
|
+
abort: reason => advisorAgent.abort(reason),
|
|
1530
|
+
reset: () => {
|
|
1531
|
+
advisorAgent.reset();
|
|
1532
|
+
appendOnlyContext.log.clear();
|
|
1533
|
+
},
|
|
1534
|
+
state: advisorAgent.state,
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
this.#advisorAgent = advisorAgent;
|
|
1538
|
+
this.#advisorRuntime = new AdvisorRuntime(advisorAgentFacade, {
|
|
1539
|
+
snapshotMessages: () => this.agent.state.messages,
|
|
1540
|
+
enqueueAdvice,
|
|
1541
|
+
maintainContext: incomingTokens => this.#maintainAdvisorContext(incomingTokens),
|
|
1542
|
+
});
|
|
1543
|
+
if (seedToCurrent) {
|
|
1544
|
+
this.#advisorRuntime.seedTo(this.agent.state.messages.length);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Batch non-blocking advisor notes into one injected custom message.
|
|
1548
|
+
this.#advisorYieldQueueUnsubscribe = this.yieldQueue.register<AdvisorNote>("advisor", {
|
|
1549
|
+
build: entries =>
|
|
1550
|
+
entries.length === 0
|
|
1551
|
+
? null
|
|
1552
|
+
: ({
|
|
1553
|
+
role: "custom",
|
|
1554
|
+
customType: "advisor",
|
|
1555
|
+
display: true,
|
|
1556
|
+
attribution: "agent",
|
|
1557
|
+
timestamp: Date.now(),
|
|
1558
|
+
content: formatAdvisorBatchContent(entries),
|
|
1559
|
+
details: { notes: entries } satisfies AdvisorMessageDetails,
|
|
1560
|
+
} satisfies CustomMessage),
|
|
1561
|
+
skipIdleFlush: true,
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
#stopAdvisorRuntime(): void {
|
|
1568
|
+
if (this.#advisorRuntime) {
|
|
1569
|
+
this.#advisorRuntime.dispose();
|
|
1570
|
+
this.#advisorRuntime = undefined;
|
|
1571
|
+
}
|
|
1572
|
+
if (this.#advisorAgent) {
|
|
1573
|
+
this.#advisorAgent = undefined;
|
|
1574
|
+
}
|
|
1575
|
+
this.#advisorYieldQueueUnsubscribe?.();
|
|
1576
|
+
this.#advisorYieldQueueUnsubscribe = undefined;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
async #promoteAdvisorContextModel(currentModel: Model): Promise<boolean> {
|
|
1580
|
+
const promotionSettings = this.settings.getGroup("contextPromotion");
|
|
1581
|
+
if (!promotionSettings.enabled) return false;
|
|
1582
|
+
const contextWindow = currentModel.contextWindow ?? 0;
|
|
1583
|
+
if (contextWindow <= 0) return false;
|
|
1584
|
+
const targetModel = await this.#resolveContextPromotionTarget(currentModel, contextWindow);
|
|
1585
|
+
if (!targetModel) return false;
|
|
1586
|
+
|
|
1587
|
+
const advisorSel = resolveRoleSelection(
|
|
1588
|
+
["advisor"],
|
|
1589
|
+
this.settings,
|
|
1590
|
+
this.#modelRegistry.getAvailable(),
|
|
1591
|
+
this.#modelRegistry,
|
|
1592
|
+
);
|
|
1593
|
+
const advisorThinkingLevel = advisorSel?.thinkingLevel ?? ThinkingLevel.Medium;
|
|
1594
|
+
|
|
1595
|
+
try {
|
|
1596
|
+
this.#advisorAgent?.setModel(targetModel);
|
|
1597
|
+
this.#advisorAgent?.setThinkingLevel(toReasoningEffort(advisorThinkingLevel));
|
|
1598
|
+
this.#advisorAgent?.setDisableReasoning(shouldDisableReasoning(advisorThinkingLevel));
|
|
1599
|
+
this.#advisorAgent?.appendOnlyContext?.invalidateForModelChange();
|
|
1600
|
+
logger.debug("Advisor context promotion switched model on overflow", {
|
|
1601
|
+
from: `${currentModel.provider}/${currentModel.id}`,
|
|
1602
|
+
to: `${targetModel.provider}/${targetModel.id}`,
|
|
1603
|
+
});
|
|
1604
|
+
return true;
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
logger.warn("Advisor context promotion failed", {
|
|
1607
|
+
from: `${currentModel.provider}/${currentModel.id}`,
|
|
1608
|
+
to: `${targetModel.provider}/${targetModel.id}`,
|
|
1609
|
+
error: String(error),
|
|
1610
|
+
});
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
async #maintainAdvisorContext(incomingTokens: number): Promise<boolean> {
|
|
1616
|
+
const advisor = this.#advisorAgent;
|
|
1617
|
+
if (!advisor) return false;
|
|
1618
|
+
|
|
1619
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
1620
|
+
if (compactionSettings.strategy === "off") return false;
|
|
1621
|
+
if (!compactionSettings.enabled) return false;
|
|
1622
|
+
|
|
1623
|
+
const advisorModel = advisor.state.model;
|
|
1624
|
+
const contextWindow = advisorModel.contextWindow ?? 0;
|
|
1625
|
+
if (contextWindow <= 0) return false;
|
|
1626
|
+
|
|
1627
|
+
const messages = advisor.state.messages;
|
|
1628
|
+
let contextTokens = incomingTokens;
|
|
1629
|
+
for (const message of messages) {
|
|
1630
|
+
contextTokens += estimateTokens(message);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings)) {
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// 1. Try promotion first
|
|
1638
|
+
if (await this.#promoteAdvisorContextModel(advisorModel)) {
|
|
1639
|
+
// Promotion succeeded, check if new model has enough space
|
|
1640
|
+
const newModel = advisor.state.model;
|
|
1641
|
+
const newWindow = newModel.contextWindow ?? 0;
|
|
1642
|
+
if (newWindow > 0) {
|
|
1643
|
+
const stillNeedsCompaction = shouldCompact(contextTokens, newWindow, compactionSettings);
|
|
1644
|
+
if (!stillNeedsCompaction) return false;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 2. Run compaction on advisor messages
|
|
1649
|
+
const pathEntries: SessionEntry[] = messages.map((message, i) => {
|
|
1650
|
+
const id = `msg-${i}`;
|
|
1651
|
+
const parentId = i > 0 ? `msg-${i - 1}` : null;
|
|
1652
|
+
const timestamp = String(message.timestamp || Date.now());
|
|
1653
|
+
|
|
1654
|
+
if (message.role === "compactionSummary") {
|
|
1655
|
+
return {
|
|
1656
|
+
type: "compaction",
|
|
1657
|
+
id,
|
|
1658
|
+
parentId,
|
|
1659
|
+
timestamp,
|
|
1660
|
+
summary: message.summary,
|
|
1661
|
+
shortSummary: message.shortSummary,
|
|
1662
|
+
firstKeptEntryId: (message as any).firstKeptEntryId || `msg-${i + 1}`,
|
|
1663
|
+
tokensBefore: message.tokensBefore,
|
|
1664
|
+
} satisfies CompactionEntry;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
return {
|
|
1668
|
+
type: "message",
|
|
1669
|
+
id,
|
|
1670
|
+
parentId,
|
|
1671
|
+
timestamp,
|
|
1672
|
+
message,
|
|
1673
|
+
} satisfies SessionMessageEntry;
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
const preparation = prepareCompaction(pathEntries, compactionSettings);
|
|
1677
|
+
if (!preparation) {
|
|
1678
|
+
// Cannot prepare compaction, fallback to re-prime
|
|
1679
|
+
return true;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
const advisorCompactionThinkingLevel: ThinkingLevel | undefined = advisor.state.disableReasoning
|
|
1683
|
+
? ThinkingLevel.Off
|
|
1684
|
+
: advisor.state.thinkingLevel;
|
|
1685
|
+
|
|
1686
|
+
// Advisor state is in-memory-only, so snapcompact's frame archive has no
|
|
1687
|
+
// stable SessionEntry preserveData slot to carry across future advisor
|
|
1688
|
+
// maintenance runs. Use an LLM summary even when the primary session is
|
|
1689
|
+
// configured for snapcompact.
|
|
1690
|
+
const availableModels = this.#modelRegistry.getAvailable();
|
|
1691
|
+
const candidates = this.#resolveCompactionModelCandidates(advisorModel, availableModels);
|
|
1692
|
+
if (candidates.length === 0) {
|
|
1693
|
+
// No compaction candidates, fallback to re-prime
|
|
1694
|
+
return true;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
let compactResult: CompactionResult | undefined;
|
|
1698
|
+
let lastError: unknown;
|
|
1699
|
+
|
|
1700
|
+
for (const candidate of candidates) {
|
|
1701
|
+
const apiKey = await this.#modelRegistry.getApiKey(
|
|
1702
|
+
candidate,
|
|
1703
|
+
this.sessionId ? `${this.sessionId}-advisor` : undefined,
|
|
1704
|
+
);
|
|
1705
|
+
if (!apiKey) continue;
|
|
1706
|
+
|
|
1707
|
+
try {
|
|
1708
|
+
compactResult = await compact(
|
|
1709
|
+
preparation,
|
|
1710
|
+
candidate,
|
|
1711
|
+
this.#modelRegistry.resolver(candidate, this.sessionId ? `${this.sessionId}-advisor` : undefined),
|
|
1712
|
+
undefined,
|
|
1713
|
+
undefined,
|
|
1714
|
+
{
|
|
1715
|
+
thinkingLevel: advisorCompactionThinkingLevel,
|
|
1716
|
+
convertToLlm: messages => this.#convertToLlmForSideRequest(messages),
|
|
1717
|
+
},
|
|
1718
|
+
);
|
|
1719
|
+
break;
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
lastError = error;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if (!compactResult) {
|
|
1726
|
+
logger.warn("Advisor compaction failed, falling back to re-prime", { error: String(lastError) });
|
|
1727
|
+
return true;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
const summary = compactResult.summary;
|
|
1731
|
+
const shortSummary = compactResult.shortSummary;
|
|
1732
|
+
const firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
1733
|
+
const tokensBefore = compactResult.tokensBefore;
|
|
1734
|
+
|
|
1735
|
+
// Rebuild messages with the compaction summary
|
|
1736
|
+
const summaryMessage = {
|
|
1737
|
+
...createCompactionSummaryMessage(summary, tokensBefore, new Date().toISOString(), shortSummary),
|
|
1738
|
+
firstKeptEntryId,
|
|
1739
|
+
} as CompactionSummaryMessage & { firstKeptEntryId?: string };
|
|
1740
|
+
|
|
1741
|
+
advisor.replaceMessages([summaryMessage, ...preparation.recentMessages]);
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1386
1744
|
|
|
1387
1745
|
/** Model registry for API key resolution and model discovery */
|
|
1388
1746
|
get modelRegistry(): ModelRegistry {
|
|
@@ -3213,6 +3571,7 @@ export class AgentSession {
|
|
|
3213
3571
|
this.#pendingIrcAsides = [];
|
|
3214
3572
|
this.yieldQueue.clear();
|
|
3215
3573
|
this.agent.setAsideMessageProvider(undefined);
|
|
3574
|
+
this.#stopAdvisorRuntime();
|
|
3216
3575
|
this.#evalExecutionDisposing = true;
|
|
3217
3576
|
}
|
|
3218
3577
|
|
|
@@ -5617,6 +5976,7 @@ export class AgentSession {
|
|
|
5617
5976
|
this.#todoReminderAwaitingProgress = false;
|
|
5618
5977
|
this.#planReferenceSent = false;
|
|
5619
5978
|
this.#planReferencePath = "local://PLAN.md";
|
|
5979
|
+
this.#advisorRuntime?.reset();
|
|
5620
5980
|
this.#reconnectToAgent();
|
|
5621
5981
|
|
|
5622
5982
|
// Emit session_switch event with reason "new" to hooks
|
|
@@ -6234,6 +6594,7 @@ export class AgentSession {
|
|
|
6234
6594
|
await this.sessionManager.rewriteEntries();
|
|
6235
6595
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6236
6596
|
this.agent.replaceMessages(sessionContext.messages);
|
|
6597
|
+
this.#advisorRuntime?.reset();
|
|
6237
6598
|
this.#syncTodoPhasesFromBranch();
|
|
6238
6599
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
6239
6600
|
return result;
|
|
@@ -6263,9 +6624,9 @@ export class AgentSession {
|
|
|
6263
6624
|
return undefined;
|
|
6264
6625
|
}
|
|
6265
6626
|
|
|
6266
|
-
await this.sessionManager.rewriteEntries();
|
|
6267
6627
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6268
6628
|
this.agent.replaceMessages(sessionContext.messages);
|
|
6629
|
+
this.#advisorRuntime?.reset();
|
|
6269
6630
|
this.#syncTodoPhasesFromBranch();
|
|
6270
6631
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
6271
6632
|
return result;
|
|
@@ -6316,6 +6677,7 @@ export class AgentSession {
|
|
|
6316
6677
|
await this.sessionManager.rewriteEntries();
|
|
6317
6678
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6318
6679
|
this.agent.replaceMessages(sessionContext.messages);
|
|
6680
|
+
this.#advisorRuntime?.reset();
|
|
6319
6681
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
6320
6682
|
return { removed };
|
|
6321
6683
|
}
|
|
@@ -6366,6 +6728,7 @@ export class AgentSession {
|
|
|
6366
6728
|
await this.sessionManager.rewriteEntries();
|
|
6367
6729
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6368
6730
|
this.agent.replaceMessages(sessionContext.messages);
|
|
6731
|
+
this.#advisorRuntime?.reset();
|
|
6369
6732
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
6370
6733
|
|
|
6371
6734
|
return {
|
|
@@ -6582,6 +6945,7 @@ export class AgentSession {
|
|
|
6582
6945
|
const newEntries = this.sessionManager.getEntries();
|
|
6583
6946
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6584
6947
|
this.agent.replaceMessages(sessionContext.messages);
|
|
6948
|
+
this.#advisorRuntime?.reset();
|
|
6585
6949
|
this.#syncTodoPhasesFromBranch();
|
|
6586
6950
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
6587
6951
|
|
|
@@ -6801,6 +7165,7 @@ export class AgentSession {
|
|
|
6801
7165
|
// Rebuild agent messages from session
|
|
6802
7166
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6803
7167
|
this.agent.replaceMessages(sessionContext.messages);
|
|
7168
|
+
this.#advisorRuntime?.reset();
|
|
6804
7169
|
this.#syncTodoPhasesFromBranch();
|
|
6805
7170
|
|
|
6806
7171
|
return { document: handoffText, savedPath };
|
|
@@ -6838,8 +7203,11 @@ export class AgentSession {
|
|
|
6838
7203
|
}
|
|
6839
7204
|
|
|
6840
7205
|
let tokens = currentUsage.tokens;
|
|
6841
|
-
|
|
6842
|
-
|
|
7206
|
+
const previousNonMessageTokens = currentEstimate.providerNonMessageTokens;
|
|
7207
|
+
if (previousNonMessageTokens !== undefined) {
|
|
7208
|
+
const currentNonMessageTokens = computeNonMessageTokens(this);
|
|
7209
|
+
const nonMessageTokenGrowth = Math.max(0, currentNonMessageTokens - previousNonMessageTokens);
|
|
7210
|
+
tokens += nonMessageTokenGrowth;
|
|
6843
7211
|
}
|
|
6844
7212
|
for (const message of messages) {
|
|
6845
7213
|
tokens += estimateTokens(message);
|
|
@@ -7235,6 +7603,7 @@ export class AgentSession {
|
|
|
7235
7603
|
}
|
|
7236
7604
|
const safeCount = Math.max(0, Math.min(checkpointState.checkpointMessageCount, this.agent.state.messages.length));
|
|
7237
7605
|
this.agent.replaceMessages(this.agent.state.messages.slice(0, safeCount));
|
|
7606
|
+
this.#advisorRuntime?.reset();
|
|
7238
7607
|
try {
|
|
7239
7608
|
this.sessionManager.branchWithSummary(checkpointState.checkpointEntryId, report, {
|
|
7240
7609
|
startedAt: checkpointState.startedAt,
|
|
@@ -7843,6 +8212,10 @@ export class AgentSession {
|
|
|
7843
8212
|
}
|
|
7844
8213
|
|
|
7845
8214
|
#getCompactionModelCandidates(availableModels: Model[]): Model[] {
|
|
8215
|
+
return this.#resolveCompactionModelCandidates(this.model, availableModels);
|
|
8216
|
+
}
|
|
8217
|
+
|
|
8218
|
+
#resolveCompactionModelCandidates(preferredModel: Model | null | undefined, availableModels: Model[]): Model[] {
|
|
7846
8219
|
const candidates: Model[] = [];
|
|
7847
8220
|
const seen = new Set<string>();
|
|
7848
8221
|
|
|
@@ -7854,15 +8227,9 @@ export class AgentSession {
|
|
|
7854
8227
|
candidates.push(model);
|
|
7855
8228
|
};
|
|
7856
8229
|
|
|
7857
|
-
|
|
7858
|
-
// Prefer the active session's model: it's what the user is actively using,
|
|
7859
|
-
// and routing compaction to a different provider (e.g. an OpenAI default
|
|
7860
|
-
// model while the chat is on Anthropic) changes provider-specific behavior
|
|
7861
|
-
// like remote compaction endpoints. Role-based candidates only kick in
|
|
7862
|
-
// as auth fallbacks when the current model has no usable credentials.
|
|
7863
|
-
addCandidate(currentModel);
|
|
8230
|
+
addCandidate(preferredModel ?? undefined);
|
|
7864
8231
|
for (const role of MODEL_ROLE_IDS) {
|
|
7865
|
-
addCandidate(this.#resolveRoleModelFull(role, availableModels,
|
|
8232
|
+
addCandidate(this.#resolveRoleModelFull(role, availableModels, preferredModel ?? undefined).model);
|
|
7866
8233
|
}
|
|
7867
8234
|
|
|
7868
8235
|
const sortedByContext = [...availableModels].sort((a, b) => (b.contextWindow ?? 0) - (a.contextWindow ?? 0));
|
|
@@ -8420,6 +8787,7 @@ export class AgentSession {
|
|
|
8420
8787
|
const newEntries = this.sessionManager.getEntries();
|
|
8421
8788
|
const sessionContext = this.buildDisplaySessionContext();
|
|
8422
8789
|
this.agent.replaceMessages(sessionContext.messages);
|
|
8790
|
+
this.#advisorRuntime?.reset();
|
|
8423
8791
|
this.#syncTodoPhasesFromBranch();
|
|
8424
8792
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
8425
8793
|
|
|
@@ -10065,6 +10433,7 @@ export class AgentSession {
|
|
|
10065
10433
|
this.#applyThinkingLevelToAgent(previousThinkingLevel);
|
|
10066
10434
|
this.agent.serviceTier = previousServiceTier;
|
|
10067
10435
|
this.#syncTodoPhasesFromBranch();
|
|
10436
|
+
this.#advisorRuntime?.reset();
|
|
10068
10437
|
this.#reconnectToAgent();
|
|
10069
10438
|
if (restoreMcpError) {
|
|
10070
10439
|
throw restoreMcpError;
|
|
@@ -10146,6 +10515,7 @@ export class AgentSession {
|
|
|
10146
10515
|
|
|
10147
10516
|
if (!skipConversationRestore) {
|
|
10148
10517
|
this.agent.replaceMessages(sessionContext.messages);
|
|
10518
|
+
this.#advisorRuntime?.reset();
|
|
10149
10519
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
10150
10520
|
}
|
|
10151
10521
|
|
|
@@ -10312,6 +10682,7 @@ export class AgentSession {
|
|
|
10312
10682
|
const displayContext = deobfuscateSessionContext(stateContext, this.#obfuscator);
|
|
10313
10683
|
await this.#restoreMCPSelectionsForSessionContext(displayContext);
|
|
10314
10684
|
this.agent.replaceMessages(displayContext.messages);
|
|
10685
|
+
this.#advisorRuntime?.reset();
|
|
10315
10686
|
this.#syncTodoPhasesFromBranch();
|
|
10316
10687
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
10317
10688
|
|
|
@@ -10808,7 +11179,10 @@ export class AgentSession {
|
|
|
10808
11179
|
* Format the entire session as plain text for clipboard export.
|
|
10809
11180
|
* Includes user messages, assistant text, thinking blocks, tool calls, and tool results.
|
|
10810
11181
|
*/
|
|
10811
|
-
formatSessionAsText(): string {
|
|
11182
|
+
formatSessionAsText(options?: { compact?: boolean }): string {
|
|
11183
|
+
if (options?.compact) {
|
|
11184
|
+
return formatSessionHistoryMarkdown(this.messages);
|
|
11185
|
+
}
|
|
10812
11186
|
return formatSessionDumpText({
|
|
10813
11187
|
messages: this.messages,
|
|
10814
11188
|
systemPrompt: this.agent.state.systemPrompt,
|
|
@@ -10818,6 +11192,179 @@ export class AgentSession {
|
|
|
10818
11192
|
});
|
|
10819
11193
|
}
|
|
10820
11194
|
|
|
11195
|
+
/**
|
|
11196
|
+
* Enable or disable the advisor for this session. The setting is persisted,
|
|
11197
|
+
* and the runtime is started or stopped to match.
|
|
11198
|
+
*
|
|
11199
|
+
* @returns true when the advisor is actively running after the call.
|
|
11200
|
+
*/
|
|
11201
|
+
setAdvisorEnabled(enabled: boolean): boolean {
|
|
11202
|
+
if (enabled) {
|
|
11203
|
+
this.settings.clearOverride("advisor.enabled");
|
|
11204
|
+
this.settings.set("advisor.enabled", true);
|
|
11205
|
+
return this.#buildAdvisorRuntime(true);
|
|
11206
|
+
}
|
|
11207
|
+
this.settings.set("advisor.enabled", false);
|
|
11208
|
+
this.#stopAdvisorRuntime();
|
|
11209
|
+
return false;
|
|
11210
|
+
}
|
|
11211
|
+
|
|
11212
|
+
/**
|
|
11213
|
+
* Toggle the advisor setting and start/stop the runtime accordingly.
|
|
11214
|
+
*
|
|
11215
|
+
* @returns true when the advisor is actively running after the call.
|
|
11216
|
+
*/
|
|
11217
|
+
toggleAdvisorEnabled(): boolean {
|
|
11218
|
+
return this.setAdvisorEnabled(!this.settings.get("advisor.enabled"));
|
|
11219
|
+
}
|
|
11220
|
+
|
|
11221
|
+
/**
|
|
11222
|
+
* Whether a live advisor agent is attached to this session. True only when
|
|
11223
|
+
* `advisor.enabled` is set AND a model resolved for the `advisor` role AND
|
|
11224
|
+
* the advisor applies to this agent kind — i.e. the actual runtime exists,
|
|
11225
|
+
* not merely the setting. Drives the status-line badge and `/dump advisor`.
|
|
11226
|
+
*/
|
|
11227
|
+
isAdvisorActive(): boolean {
|
|
11228
|
+
return this.#advisorAgent !== undefined;
|
|
11229
|
+
}
|
|
11230
|
+
|
|
11231
|
+
/**
|
|
11232
|
+
* Return structured advisor stats for the status command and TUI panel.
|
|
11233
|
+
*/
|
|
11234
|
+
getAdvisorStats(): AdvisorStats {
|
|
11235
|
+
const configured = this.settings.get("advisor.enabled") as boolean;
|
|
11236
|
+
const advisor = this.#advisorAgent;
|
|
11237
|
+
if (!advisor) {
|
|
11238
|
+
return {
|
|
11239
|
+
configured,
|
|
11240
|
+
active: false,
|
|
11241
|
+
contextWindow: 0,
|
|
11242
|
+
contextTokens: 0,
|
|
11243
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
11244
|
+
cost: 0,
|
|
11245
|
+
messages: { user: 0, assistant: 0, total: 0 },
|
|
11246
|
+
};
|
|
11247
|
+
}
|
|
11248
|
+
const model = advisor.state.model;
|
|
11249
|
+
const messages = advisor.state.messages;
|
|
11250
|
+
const contextTokens = this.#estimateAdvisorContextTokens(messages);
|
|
11251
|
+
let input = 0;
|
|
11252
|
+
let output = 0;
|
|
11253
|
+
let cacheRead = 0;
|
|
11254
|
+
let cacheWrite = 0;
|
|
11255
|
+
let cost = 0;
|
|
11256
|
+
let user = 0;
|
|
11257
|
+
let assistant = 0;
|
|
11258
|
+
for (const message of messages) {
|
|
11259
|
+
if (message.role === "user") user++;
|
|
11260
|
+
if (message.role === "assistant") {
|
|
11261
|
+
assistant++;
|
|
11262
|
+
const assistantMsg = message as AssistantMessage;
|
|
11263
|
+
input += assistantMsg.usage.input;
|
|
11264
|
+
output += assistantMsg.usage.output;
|
|
11265
|
+
cacheRead += assistantMsg.usage.cacheRead;
|
|
11266
|
+
cacheWrite += assistantMsg.usage.cacheWrite;
|
|
11267
|
+
cost += assistantMsg.usage.cost.total;
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
return {
|
|
11271
|
+
configured,
|
|
11272
|
+
active: true,
|
|
11273
|
+
model,
|
|
11274
|
+
contextWindow: model.contextWindow ?? 0,
|
|
11275
|
+
contextTokens,
|
|
11276
|
+
tokens: {
|
|
11277
|
+
input,
|
|
11278
|
+
output,
|
|
11279
|
+
cacheRead,
|
|
11280
|
+
cacheWrite,
|
|
11281
|
+
total: input + output + cacheRead + cacheWrite,
|
|
11282
|
+
},
|
|
11283
|
+
cost,
|
|
11284
|
+
messages: { user, assistant, total: messages.length },
|
|
11285
|
+
};
|
|
11286
|
+
}
|
|
11287
|
+
|
|
11288
|
+
/**
|
|
11289
|
+
* Format a concise advisor status line for ACP/text output.
|
|
11290
|
+
*/
|
|
11291
|
+
formatAdvisorStatus(): string {
|
|
11292
|
+
const stats = this.getAdvisorStats();
|
|
11293
|
+
if (!stats.active) {
|
|
11294
|
+
return stats.configured
|
|
11295
|
+
? "Advisor setting is enabled, but no model is assigned to the 'advisor' role."
|
|
11296
|
+
: "Advisor is disabled.";
|
|
11297
|
+
}
|
|
11298
|
+
const model = stats.model!;
|
|
11299
|
+
const contextLine =
|
|
11300
|
+
stats.contextWindow > 0
|
|
11301
|
+
? `Context: ${stats.contextTokens.toLocaleString()} / ${stats.contextWindow.toLocaleString()} tokens (${Math.round((stats.contextTokens / stats.contextWindow) * 100)}%)`
|
|
11302
|
+
: `Context: ${stats.contextTokens.toLocaleString()} tokens`;
|
|
11303
|
+
const spendParts = [
|
|
11304
|
+
`${stats.tokens.input.toLocaleString()} input`,
|
|
11305
|
+
`${stats.tokens.output.toLocaleString()} output`,
|
|
11306
|
+
];
|
|
11307
|
+
if (stats.tokens.cacheRead > 0) spendParts.push(`${stats.tokens.cacheRead.toLocaleString()} cache read`);
|
|
11308
|
+
if (stats.tokens.cacheWrite > 0) spendParts.push(`${stats.tokens.cacheWrite.toLocaleString()} cache write`);
|
|
11309
|
+
const spendLine = `Spend: ${spendParts.join(", ")}, $${stats.cost.toFixed(4)}`;
|
|
11310
|
+
return `Advisor is enabled (${model.provider}/${model.id}). ${contextLine}. ${spendLine}.`;
|
|
11311
|
+
}
|
|
11312
|
+
|
|
11313
|
+
/**
|
|
11314
|
+
* Estimate the advisor's current context tokens. When the advisor has a
|
|
11315
|
+
* recent non-aborted assistant message with usage, use that prompt's token
|
|
11316
|
+
* count and add a trailing estimate for messages after it. Otherwise estimate
|
|
11317
|
+
* every message.
|
|
11318
|
+
*/
|
|
11319
|
+
#estimateAdvisorContextTokens(messages: AgentMessage[]): number {
|
|
11320
|
+
let lastUsageIndex: number | null = null;
|
|
11321
|
+
let lastUsage: AssistantMessage["usage"] | undefined;
|
|
11322
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11323
|
+
const msg = messages[i];
|
|
11324
|
+
if (msg.role === "assistant") {
|
|
11325
|
+
const assistantMsg = msg as AssistantMessage;
|
|
11326
|
+
if (assistantMsg.stopReason !== "aborted" && assistantMsg.stopReason !== "error" && assistantMsg.usage) {
|
|
11327
|
+
lastUsage = assistantMsg.usage;
|
|
11328
|
+
lastUsageIndex = i;
|
|
11329
|
+
break;
|
|
11330
|
+
}
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11333
|
+
if (!lastUsage || lastUsageIndex === null) {
|
|
11334
|
+
let estimated = 0;
|
|
11335
|
+
for (const message of messages) {
|
|
11336
|
+
estimated += estimateTokens(message);
|
|
11337
|
+
}
|
|
11338
|
+
return estimated;
|
|
11339
|
+
}
|
|
11340
|
+
let trailingTokens = 0;
|
|
11341
|
+
for (let i = lastUsageIndex + 1; i < messages.length; i++) {
|
|
11342
|
+
trailingTokens += estimateTokens(messages[i]);
|
|
11343
|
+
}
|
|
11344
|
+
return calculatePromptTokens(lastUsage) + trailingTokens;
|
|
11345
|
+
}
|
|
11346
|
+
|
|
11347
|
+
/**
|
|
11348
|
+
* Format the advisor agent's own transcript (its system prompt, config,
|
|
11349
|
+
* tools, and the markdown deltas it received plus its thinking/advise/read
|
|
11350
|
+
* calls) as plain text — the advisor-side equivalent of
|
|
11351
|
+
* {@link formatSessionAsText}. Returns null when no advisor is active.
|
|
11352
|
+
*/
|
|
11353
|
+
formatAdvisorHistoryAsText(options?: { compact?: boolean }): string | null {
|
|
11354
|
+
const advisor = this.#advisorAgent;
|
|
11355
|
+
if (!advisor) return null;
|
|
11356
|
+
if (options?.compact) {
|
|
11357
|
+
return formatSessionHistoryMarkdown(advisor.state.messages);
|
|
11358
|
+
}
|
|
11359
|
+
return formatSessionDumpText({
|
|
11360
|
+
messages: advisor.state.messages,
|
|
11361
|
+
systemPrompt: advisor.state.systemPrompt,
|
|
11362
|
+
model: advisor.state.model,
|
|
11363
|
+
thinkingLevel: advisor.state.thinkingLevel,
|
|
11364
|
+
tools: advisor.state.tools,
|
|
11365
|
+
});
|
|
11366
|
+
}
|
|
11367
|
+
|
|
10821
11368
|
// =========================================================================
|
|
10822
11369
|
// Extension System
|
|
10823
11370
|
// =========================================================================
|