@nghyane/arcane 0.1.29 → 0.1.30

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.
Files changed (50) hide show
  1. package/package.json +4 -4
  2. package/src/cli/config-cli.ts +1 -1
  3. package/src/config/settings-schema.ts +19 -27
  4. package/src/config/settings.ts +3 -4
  5. package/src/extensibility/custom-tools/types.ts +0 -12
  6. package/src/extensibility/extensions/index.ts +0 -5
  7. package/src/extensibility/extensions/runner.ts +6 -26
  8. package/src/extensibility/extensions/types.ts +1 -77
  9. package/src/extensibility/hooks/runner.ts +5 -24
  10. package/src/extensibility/hooks/types.ts +1 -77
  11. package/src/index.ts +2 -13
  12. package/src/modes/components/footer.ts +4 -11
  13. package/src/modes/components/index.ts +0 -1
  14. package/src/modes/components/status-line/segments.ts +1 -2
  15. package/src/modes/components/status-line/types.ts +0 -1
  16. package/src/modes/components/status-line.ts +0 -6
  17. package/src/modes/components/tree-selector.ts +0 -8
  18. package/src/modes/controllers/command-controller.ts +2 -98
  19. package/src/modes/controllers/event-controller.ts +46 -52
  20. package/src/modes/controllers/extension-ui-controller.ts +0 -42
  21. package/src/modes/controllers/input-controller.ts +0 -23
  22. package/src/modes/controllers/selector-controller.ts +0 -5
  23. package/src/modes/interactive-mode.ts +3 -24
  24. package/src/modes/print-mode.ts +0 -16
  25. package/src/modes/rpc/rpc-client.ts +0 -16
  26. package/src/modes/rpc/rpc-mode.ts +0 -32
  27. package/src/modes/rpc/rpc-types.ts +0 -9
  28. package/src/modes/types.ts +1 -13
  29. package/src/modes/utils/ui-helpers.ts +2 -118
  30. package/src/sdk.ts +0 -15
  31. package/src/session/agent-session.ts +89 -650
  32. package/src/session/compaction/branch-summarization.ts +5 -13
  33. package/src/session/compaction/index.ts +0 -1
  34. package/src/session/compaction/utils.ts +94 -2
  35. package/src/session/messages.ts +0 -37
  36. package/src/session/retry-utils.ts +1 -1
  37. package/src/session/session-manager.ts +8 -108
  38. package/src/session/session-types.ts +4 -25
  39. package/src/session/stats.ts +2 -39
  40. package/src/slash-commands/builtin-registry.ts +0 -11
  41. package/src/task/executor.ts +0 -8
  42. package/examples/hooks/custom-compaction.ts +0 -116
  43. package/src/modes/components/compaction-summary-message.ts +0 -59
  44. package/src/prompts/compaction/compaction-short-summary.md +0 -9
  45. package/src/prompts/compaction/compaction-summary-context.md +0 -5
  46. package/src/prompts/compaction/compaction-summary.md +0 -41
  47. package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
  48. package/src/prompts/compaction/compaction-update-summary.md +0 -45
  49. package/src/session/compaction/compaction.ts +0 -864
  50. package/src/session/compaction/pruning.ts +0 -91
@@ -10,17 +10,12 @@ import { completeSimple } from "@nghyane/arcane-ai";
10
10
  import { renderPromptTemplate } from "../../config/prompt-templates";
11
11
  import branchSummaryPrompt from "../../prompts/compaction/branch-summary.md" with { type: "text" };
12
12
  import branchSummaryPreamble from "../../prompts/compaction/branch-summary-preamble.md" with { type: "text" };
13
- import {
14
- convertToLlm,
15
- createBranchSummaryMessage,
16
- createCompactionSummaryMessage,
17
- createCustomMessage,
18
- } from "../../session/messages";
13
+ import { convertToLlm, createBranchSummaryMessage, createCustomMessage } from "../../session/messages";
19
14
  import type { ReadonlySessionManager, SessionEntry } from "../../session/session-manager";
20
- import { estimateTokens } from "./compaction";
21
15
  import {
22
16
  computeFileLists,
23
17
  createFileOps,
18
+ estimateTokens,
24
19
  extractFileOpsFromMessage,
25
20
  type FileOperations,
26
21
  SUMMARIZATION_SYSTEM_PROMPT,
@@ -85,7 +80,7 @@ export interface GenerateBranchSummaryOptions {
85
80
  * Collect entries that should be summarized when navigating from one position to another.
86
81
  *
87
82
  * Walks from oldLeafId back to the common ancestor with targetId, collecting entries
88
- * along the way. Does NOT stop at compaction boundaries - those are included and their
83
+ * along the way.
89
84
  * summaries become context.
90
85
  *
91
86
  * @param session - Session manager (read-only access)
@@ -139,7 +134,7 @@ export function collectEntriesForBranchSummary(
139
134
 
140
135
  /**
141
136
  * Extract AgentMessage from a session entry.
142
- * Similar to getMessageFromEntry in compaction.ts but also handles compaction entries.
137
+ * Extract AgentMessage from a session entry.
143
138
  */
144
139
  function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {
145
140
  switch (entry.type) {
@@ -154,9 +149,6 @@ function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {
154
149
  case "branch_summary":
155
150
  return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
156
151
 
157
- case "compaction":
158
- return createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp, entry.shortSummary);
159
-
160
152
  // These don't contribute to conversation content
161
153
  case "thinking_level_change":
162
154
  case "model_change":
@@ -216,7 +208,7 @@ export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: numbe
216
208
  // Check budget before adding
217
209
  if (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {
218
210
  // If this is a summary entry, try to fit it anyway as it's important context
219
- if (entry.type === "compaction" || entry.type === "branch_summary") {
211
+ if (entry.type === "branch_summary") {
220
212
  if (totalTokens < tokenBudget * 0.9) {
221
213
  messages.unshift(message);
222
214
  totalTokens += tokens;
@@ -3,5 +3,4 @@
3
3
  */
4
4
 
5
5
  export * from "./branch-summarization";
6
- export * from "./compaction";
7
6
  export * from "./utils";
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Shared utilities for compaction and branch summarization.
2
+ * Shared utilities for branch summarization.
3
3
  */
4
4
  import type { AgentMessage } from "@nghyane/arcane-agent";
5
- import type { Message } from "@nghyane/arcane-ai";
5
+ import type { AssistantMessage, Message, Usage } from "@nghyane/arcane-ai";
6
6
  import { renderPromptTemplate } from "../../config/prompt-templates";
7
7
  import fileOperationsTemplate from "../../prompts/system/file-operations.md" with { type: "text" };
8
8
  import summarizationSystemPrompt from "../../prompts/system/summarization-system.md" with { type: "text" };
@@ -169,3 +169,95 @@ export function serializeConversation(messages: Message[]): string {
169
169
  // ============================================================================
170
170
 
171
171
  export const SUMMARIZATION_SYSTEM_PROMPT = renderPromptTemplate(summarizationSystemPrompt);
172
+
173
+ // ============================================================================
174
+ // Token Estimation
175
+ // ============================================================================
176
+
177
+ /**
178
+ * Estimate token count for a message using chars/4 heuristic.
179
+ * This is conservative (overestimates tokens).
180
+ */
181
+ export function estimateTokens(message: AgentMessage): number {
182
+ let chars = 0;
183
+
184
+ switch (message.role) {
185
+ case "user": {
186
+ const content = (message as { content: string | Array<{ type: string; text?: string }> }).content;
187
+ if (typeof content === "string") {
188
+ chars = content.length;
189
+ } else if (Array.isArray(content)) {
190
+ for (const block of content) {
191
+ if (block.type === "text" && block.text) {
192
+ chars += block.text.length;
193
+ }
194
+ }
195
+ }
196
+ return Math.ceil(chars / 4);
197
+ }
198
+ case "assistant": {
199
+ const assistant = message as AssistantMessage;
200
+ for (const block of assistant.content) {
201
+ if (block.type === "text") {
202
+ chars += block.text.length;
203
+ } else if (block.type === "thinking") {
204
+ chars += block.thinking.length;
205
+ } else if (block.type === "toolCall") {
206
+ chars += block.name.length + JSON.stringify(block.arguments).length;
207
+ }
208
+ }
209
+ return Math.ceil(chars / 4);
210
+ }
211
+ case "hookMessage":
212
+ case "toolResult": {
213
+ if (typeof message.content === "string") {
214
+ chars = message.content.length;
215
+ } else {
216
+ for (const block of message.content) {
217
+ if (block.type === "text" && block.text) {
218
+ chars += block.text.length;
219
+ }
220
+ if (block.type === "image") {
221
+ chars += 4800; // Estimate images as 4000 chars, or 1200 tokens
222
+ }
223
+ }
224
+ }
225
+ return Math.ceil(chars / 4);
226
+ }
227
+ case "bashExecution": {
228
+ chars = message.command.length + message.output.length;
229
+ return Math.ceil(chars / 4);
230
+ }
231
+ case "branchSummary": {
232
+ chars = message.summary.length;
233
+ return Math.ceil(chars / 4);
234
+ }
235
+ }
236
+
237
+ return 0;
238
+ }
239
+
240
+ // ============================================================================
241
+ // Context Token Calculation
242
+ // ============================================================================
243
+
244
+ /**
245
+ * Calculate context tokens from usage report.
246
+ * Uses the native totalTokens field when available, falls back to computing from components.
247
+ */
248
+ export function calculateContextTokens(usage: Usage): number {
249
+ return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
250
+ }
251
+
252
+ /**
253
+ * Get the last assistant message's usage data.
254
+ */
255
+ export function getLastAssistantUsage(messages: AgentMessage[]): Usage | undefined {
256
+ for (let i = messages.length - 1; i >= 0; i--) {
257
+ const msg = messages[i];
258
+ if (msg.role === "assistant") {
259
+ return (msg as AssistantMessage).usage;
260
+ }
261
+ }
262
+ return undefined;
263
+ }
@@ -8,11 +8,9 @@ import type { AgentMessage } from "@nghyane/arcane-agent";
8
8
  import type { ImageContent, Message, TextContent, ToolResultMessage } from "@nghyane/arcane-ai";
9
9
  import { renderPromptTemplate } from "../config/prompt-templates";
10
10
  import branchSummaryContextPrompt from "../prompts/compaction/branch-summary-context.md" with { type: "text" };
11
- import compactionSummaryContextPrompt from "../prompts/compaction/compaction-summary-context.md" with { type: "text" };
12
11
  import type { OutputMeta } from "../tools/output-meta";
13
12
  import { formatOutputNotice } from "../tools/output-meta";
14
13
 
15
- const COMPACTION_SUMMARY_TEMPLATE = compactionSummaryContextPrompt;
16
14
  const BRANCH_SUMMARY_TEMPLATE = branchSummaryContextPrompt;
17
15
 
18
16
  export const SKILL_PROMPT_MESSAGE_TYPE = "skill-prompt";
@@ -97,14 +95,6 @@ export interface BranchSummaryMessage {
97
95
  timestamp: number;
98
96
  }
99
97
 
100
- export interface CompactionSummaryMessage {
101
- role: "compactionSummary";
102
- summary: string;
103
- shortSummary?: string;
104
- tokensBefore: number;
105
- timestamp: number;
106
- }
107
-
108
98
  /**
109
99
  * Message type for auto-read file mentions via @filepath syntax.
110
100
  */
@@ -132,7 +122,6 @@ declare module "@nghyane/arcane-agent" {
132
122
  custom: CustomMessage;
133
123
  hookMessage: HookMessage;
134
124
  branchSummary: BranchSummaryMessage;
135
- compactionSummary: CompactionSummaryMessage;
136
125
  fileMention: FileMentionMessage;
137
126
  }
138
127
  }
@@ -184,21 +173,6 @@ export function createBranchSummaryMessage(summary: string, fromId: string, time
184
173
  };
185
174
  }
186
175
 
187
- export function createCompactionSummaryMessage(
188
- summary: string,
189
- tokensBefore: number,
190
- timestamp: string,
191
- shortSummary?: string,
192
- ): CompactionSummaryMessage {
193
- return {
194
- role: "compactionSummary",
195
- summary,
196
- shortSummary,
197
- tokensBefore,
198
- timestamp: new Date(timestamp).getTime(),
199
- };
200
- }
201
-
202
176
  /** Convert CustomMessageEntry to AgentMessage format */
203
177
  export function createCustomMessage(
204
178
  customType: string,
@@ -267,17 +241,6 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
267
241
  ],
268
242
  timestamp: m.timestamp,
269
243
  };
270
- case "compactionSummary":
271
- return {
272
- role: "user",
273
- content: [
274
- {
275
- type: "text" as const,
276
- text: renderPromptTemplate(COMPACTION_SUMMARY_TEMPLATE, { summary: m.summary }),
277
- },
278
- ],
279
- timestamp: m.timestamp,
280
- };
281
244
  case "fileMention": {
282
245
  const fileContents = m.files
283
246
  .map(file => {
@@ -8,7 +8,7 @@
8
8
  * Matches: overloaded, rate limit, usage limit, 429, 5xx, connection errors.
9
9
  */
10
10
  export function isRetryableErrorMessage(errorMessage: string): boolean {
11
- return /overloaded|rate.?limit|usage.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|unable to connect|fetch failed|retry delay/i.test(
11
+ return /overloaded|rate.?limit|usage.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|unable to connect|fetch failed|retry delay|json parse error/i.test(
12
12
  errorMessage,
13
13
  );
14
14
  }
@@ -11,7 +11,6 @@ import {
11
11
  type BashExecutionMessage,
12
12
  type CustomMessage,
13
13
  createBranchSummaryMessage,
14
- createCompactionSummaryMessage,
15
14
  createCustomMessage,
16
15
  type FileMentionMessage,
17
16
  type HookMessage,
@@ -24,7 +23,6 @@ export * from "./session-types";
24
23
 
25
24
  import {
26
25
  type BranchSummaryEntry,
27
- type CompactionEntry,
28
26
  CURRENT_SESSION_VERSION,
29
27
  type CustomEntry,
30
28
  type CustomMessageEntry,
@@ -85,18 +83,6 @@ function migrateV1ToV2(entries: FileEntry[]): void {
85
83
  entry.id = generateId(ids);
86
84
  entry.parentId = prevId;
87
85
  prevId = entry.id;
88
-
89
- // Convert firstKeptEntryIndex to firstKeptEntryId for compaction
90
- if (entry.type === "compaction") {
91
- const comp = entry as CompactionEntry & { firstKeptEntryIndex?: number };
92
- if (typeof comp.firstKeptEntryIndex === "number") {
93
- const targetEntry = entries[comp.firstKeptEntryIndex];
94
- if (targetEntry && targetEntry.type !== "session") {
95
- comp.firstKeptEntryId = targetEntry.id;
96
- }
97
- delete comp.firstKeptEntryIndex;
98
- }
99
- }
100
86
  }
101
87
  }
102
88
 
@@ -199,20 +185,10 @@ function migrateHomeSessionDirs(): void {
199
185
  }
200
186
  }
201
187
 
202
- /** Exported for compaction.test.ts */
203
188
  export function parseSessionEntries(content: string): FileEntry[] {
204
189
  return parseJsonlLenient<FileEntry>(content);
205
190
  }
206
191
 
207
- export function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {
208
- for (let i = entries.length - 1; i >= 0; i--) {
209
- if (entries[i].type === "compaction") {
210
- return entries[i] as CompactionEntry;
211
- }
212
- }
213
- return null;
214
- }
215
-
216
192
  function toError(value: unknown): Error {
217
193
  return value instanceof Error ? value : new Error(String(value));
218
194
  }
@@ -220,7 +196,7 @@ function toError(value: unknown): Error {
220
196
  /**
221
197
  * Build the session context from entries using tree traversal.
222
198
  * If leafId is provided, walks from that entry to root.
223
- * Handles compaction and branch summaries along the path.
199
+ * Handles branch summaries along the path.
224
200
  */
225
201
  export function buildSessionContext(
226
202
  entries: SessionEntry[],
@@ -261,10 +237,9 @@ export function buildSessionContext(
261
237
  current = current.parentId ? byId.get(current.parentId) : undefined;
262
238
  }
263
239
 
264
- // Extract settings and find compaction
240
+ // Extract settings
265
241
  let thinkingLevel = "off";
266
242
  const models: Record<string, string> = {};
267
- let compaction: CompactionEntry | null = null;
268
243
  const injectedTtsrRulesSet = new Set<string>();
269
244
  let mode = "none";
270
245
  let modeData: Record<string, unknown> | undefined;
@@ -281,8 +256,6 @@ export function buildSessionContext(
281
256
  } else if (entry.type === "message" && entry.message.role === "assistant") {
282
257
  // Infer default model from assistant messages
283
258
  models.default = `${entry.message.provider}/${entry.message.model}`;
284
- } else if (entry.type === "compaction") {
285
- compaction = entry;
286
259
  } else if (entry.type === "ttsr_injection") {
287
260
  // Collect injected TTSR rule names
288
261
  for (const ruleName of entry.injectedRules) {
@@ -296,14 +269,10 @@ export function buildSessionContext(
296
269
 
297
270
  const injectedTtsrRules = Array.from(injectedTtsrRulesSet);
298
271
 
299
- // Build messages and collect corresponding entries
300
- // When there's a compaction, we need to:
301
- // 1. Emit summary first (entry = compaction)
302
- // 2. Emit kept messages (from firstKeptEntryId up to compaction)
303
- // 3. Emit messages after compaction
272
+ // Build messages
304
273
  const messages: AgentMessage[] = [];
305
274
 
306
- const appendMessage = (entry: SessionEntry) => {
275
+ for (const entry of path) {
307
276
  if (entry.type === "message") {
308
277
  messages.push(entry.message);
309
278
  } else if (entry.type === "custom_message") {
@@ -313,44 +282,6 @@ export function buildSessionContext(
313
282
  } else if (entry.type === "branch_summary" && entry.summary) {
314
283
  messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
315
284
  }
316
- };
317
-
318
- if (compaction) {
319
- // Emit summary first
320
- messages.push(
321
- createCompactionSummaryMessage(
322
- compaction.summary,
323
- compaction.tokensBefore,
324
- compaction.timestamp,
325
- compaction.shortSummary,
326
- ),
327
- );
328
-
329
- // Find compaction index in path
330
- const compactionIdx = path.findIndex(e => e.type === "compaction" && e.id === compaction.id);
331
-
332
- // Emit kept messages (before compaction, starting from firstKeptEntryId)
333
- let foundFirstKept = false;
334
- for (let i = 0; i < compactionIdx; i++) {
335
- const entry = path[i];
336
- if (entry.id === compaction.firstKeptEntryId) {
337
- foundFirstKept = true;
338
- }
339
- if (foundFirstKept) {
340
- appendMessage(entry);
341
- }
342
- }
343
-
344
- // Emit messages after compaction
345
- for (let i = compactionIdx + 1; i < path.length; i++) {
346
- const entry = path[i];
347
- appendMessage(entry);
348
- }
349
- } else {
350
- // No compaction - emit all messages, handle branch summaries and custom messages
351
- for (const entry of path) {
352
- appendMessage(entry);
353
- }
354
285
  }
355
286
 
356
287
  return { messages, thinkingLevel, models, injectedTtsrRules, mode, modeData };
@@ -858,7 +789,7 @@ export async function getRecentSessions(
858
789
  * modifying history.
859
790
  *
860
791
  * Use buildSessionContext() to get the resolved message list for the LLM, which
861
- * handles compaction summaries and follows the path from root to current leaf.
792
+ * handles branch summaries and follows the path from root to current leaf.
862
793
  */
863
794
  export interface UsageStatistics {
864
795
  input: number;
@@ -908,10 +839,6 @@ async function collectSessionsFromFiles(files: string[], storage: SessionStorage
908
839
  for (let i = 1; i < entries.length; i++) {
909
840
  const entry = entries[i] as { type?: string; message?: Message; shortSummary?: string };
910
841
 
911
- if (entry.type === "compaction" && typeof entry.shortSummary === "string") {
912
- shortSummary = entry.shortSummary;
913
- }
914
-
915
842
  if (entry.type === "message" && entry.message) {
916
843
  messageCount++;
917
844
 
@@ -1448,10 +1375,10 @@ export class SessionManager {
1448
1375
  }
1449
1376
 
1450
1377
  /** Append a message as child of current leaf, then advance leaf. Returns entry id.
1451
- * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
1378
+ * Does not allow writing BranchSummaryMessage directly.
1452
1379
  * Reason: we want these to be top-level entries in the session, not message session entries,
1453
1380
  * so it is easier to find them.
1454
- * These need to be appended via appendCompaction() and appendBranchSummary() methods.
1381
+ * These need to be appended via appendBranchSummary() methods.
1455
1382
  */
1456
1383
  appendMessage(
1457
1384
  message:
@@ -1531,33 +1458,6 @@ export class SessionManager {
1531
1458
  return entry.id;
1532
1459
  }
1533
1460
 
1534
- /** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
1535
- appendCompaction<T = unknown>(
1536
- summary: string,
1537
- shortSummary: string | undefined,
1538
- firstKeptEntryId: string,
1539
- tokensBefore: number,
1540
- details?: T,
1541
- fromExtension?: boolean,
1542
- preserveData?: Record<string, unknown>,
1543
- ): string {
1544
- const entry: CompactionEntry<T> = {
1545
- type: "compaction",
1546
- id: generateId(this.#byId),
1547
- parentId: this.#leafId,
1548
- timestamp: new Date().toISOString(),
1549
- summary,
1550
- shortSummary,
1551
- firstKeptEntryId,
1552
- tokensBefore,
1553
- details,
1554
- fromExtension,
1555
- preserveData,
1556
- };
1557
- this.#appendEntry(entry);
1558
- return entry.id;
1559
- }
1560
-
1561
1461
  /** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */
1562
1462
  appendCustomEntry(customType: string, data?: unknown): string {
1563
1463
  const entry: CustomEntry = {
@@ -1726,7 +1626,7 @@ export class SessionManager {
1726
1626
 
1727
1627
  /**
1728
1628
  * Walk from entry to root, returning all entries in path order.
1729
- * Includes all entry types (messages, compaction, model changes, etc.).
1629
+ * Includes all entry types (messages, model changes, etc.).
1730
1630
  * Use buildSessionContext() to get the resolved messages for the LLM.
1731
1631
  */
1732
1632
  getBranch(fromId?: string): SessionEntry[] {
@@ -11,7 +11,6 @@ import type { Skill, SkillWarning } from "../extensibility/skills";
11
11
  import type { FileSlashCommand } from "../extensibility/slash-commands";
12
12
  import type { SecretObfuscator } from "../secrets/obfuscator";
13
13
  import type { TodoItem } from "../tools/todo-write";
14
- import type { CompactionResult } from "./compaction";
15
14
  import type { SessionManager } from "./session-manager";
16
15
 
17
16
  export const CURRENT_SESSION_VERSION = 3;
@@ -55,20 +54,6 @@ export interface ModelChangeEntry extends SessionEntryBase {
55
54
  role?: string;
56
55
  }
57
56
 
58
- export interface CompactionEntry<T = unknown> extends SessionEntryBase {
59
- type: "compaction";
60
- summary: string;
61
- shortSummary?: string;
62
- firstKeptEntryId: string;
63
- tokensBefore: number;
64
- /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
65
- details?: T;
66
- /** Hook-provided data to persist across compaction */
67
- preserveData?: Record<string, unknown>;
68
- /** True if generated by an extension, undefined/false if pi-generated (backward compatible) */
69
- fromExtension?: boolean;
70
- }
71
-
72
57
  export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
73
58
  type: "branch_summary";
74
59
  fromId: string;
@@ -154,7 +139,6 @@ export type SessionEntry =
154
139
  | SessionMessageEntry
155
140
  | ThinkingLevelChangeEntry
156
141
  | ModelChangeEntry
157
- | CompactionEntry
158
142
  | BranchSummaryEntry
159
143
  | CustomEntry
160
144
  | CustomMessageEntry
@@ -209,18 +193,13 @@ export interface SessionInfo {
209
193
  /** Session-specific events that extend the core AgentEvent */
210
194
  export type AgentSessionEvent =
211
195
  | AgentEvent
212
- | { type: "auto_compaction_start"; reason: "threshold" | "overflow" }
213
- | {
214
- type: "auto_compaction_end";
215
- result: CompactionResult | undefined;
216
- aborted: boolean;
217
- willRetry: boolean;
218
- errorMessage?: string;
219
- }
220
196
  | { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
221
197
  | { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
222
198
  | { type: "ttsr_triggered"; rules: Rule[] }
223
- | { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number };
199
+ | { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
200
+ | { type: "context_warning"; percent: number; tokens: number; contextWindow: number }
201
+ | { type: "auto_handoff_start"; percent: number }
202
+ | { type: "auto_handoff_end"; success: boolean; error?: string };
224
203
 
225
204
  /** Listener function for agent session events */
226
205
  export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
@@ -5,9 +5,8 @@ import { exportSessionToHtml } from "../export/html";
5
5
  import type { ContextUsage } from "../extensibility/extensions/types";
6
6
  import { getCurrentThemeName } from "../theme/theme";
7
7
  import { calculateContextTokens, estimateTokens } from "./compaction";
8
- import type { CompactionSummaryMessage, FileMentionMessage } from "./messages";
8
+ import type { FileMentionMessage } from "./messages";
9
9
  import type { SessionManager } from "./session-manager";
10
- import { getLatestCompactionEntry } from "./session-manager";
11
10
  import type { SessionStats } from "./session-types";
12
11
 
13
12
  /**
@@ -82,42 +81,12 @@ export function getSessionStats(
82
81
  /**
83
82
  * Get current context usage statistics.
84
83
  */
85
- export function getContextUsage(
86
- model: Model | undefined,
87
- messages: AgentMessage[],
88
- sessionManager: SessionManager,
89
- ): ContextUsage | undefined {
84
+ export function getContextUsage(model: Model | undefined, messages: AgentMessage[]): ContextUsage | undefined {
90
85
  if (!model) return undefined;
91
86
 
92
87
  const contextWindow = model.contextWindow ?? 0;
93
88
  if (contextWindow <= 0) return undefined;
94
89
 
95
- const branchEntries = sessionManager.getBranch();
96
- const latestCompaction = getLatestCompactionEntry(branchEntries);
97
-
98
- if (latestCompaction) {
99
- const compactionIndex = branchEntries.lastIndexOf(latestCompaction);
100
- let hasPostCompactionUsage = false;
101
- for (let i = compactionIndex + 1; i < branchEntries.length; i++) {
102
- const entry = branchEntries[i];
103
- if (entry.type === "message" && entry.message.role === "assistant") {
104
- const msg = entry.message as AssistantMessage;
105
- if (msg.usage) {
106
- hasPostCompactionUsage = true;
107
- break;
108
- }
109
- }
110
- }
111
-
112
- if (!hasPostCompactionUsage) {
113
- return {
114
- tokens: 0,
115
- contextWindow,
116
- percent: 0,
117
- };
118
- }
119
- }
120
-
121
90
  const { tokens } = estimateContextTokensFromMessages(messages);
122
91
  const percent = Math.round((tokens / contextWindow) * 100);
123
92
 
@@ -374,12 +343,6 @@ export function formatCompactContext(messages: AgentMessage[]): string {
374
343
  const paths = fileMsg.files.map(f => f.path).join(", ");
375
344
  lines.push(`[Files referenced: ${paths}]`);
376
345
  lines.push("");
377
- } else if (msg.role === "compactionSummary") {
378
- const compactMsg = msg as CompactionSummaryMessage;
379
- lines.push("## Earlier Context (Summarized)");
380
- lines.push("");
381
- lines.push(compactMsg.summary);
382
- lines.push("");
383
346
  }
384
347
  }
385
348
 
@@ -302,17 +302,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
302
302
  await runtime.ctx.handleClearCommand();
303
303
  },
304
304
  },
305
- {
306
- name: "compact",
307
- description: "Manually compact the session context",
308
- inlineHint: "[focus instructions]",
309
- allowArgs: true,
310
- handle: async (command, runtime) => {
311
- const customInstructions = command.args || undefined;
312
- runtime.ctx.editor.setText("");
313
- await runtime.ctx.handleCompactCommand(customInstructions);
314
- },
315
- },
316
305
  {
317
306
  name: "handoff",
318
307
  description: "Hand off session context to a new session",
@@ -417,14 +417,6 @@ export async function runAgent(options: ExecutorOptions): Promise<SingleResult>
417
417
  shutdown: () => {},
418
418
  getContextUsage: () => session.getContextUsage(),
419
419
  getSystemPrompt: () => session.systemPrompt,
420
- compact: async instructionsOrOptions => {
421
- const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
422
- const compactOptions =
423
- instructionsOrOptions && typeof instructionsOrOptions === "object"
424
- ? instructionsOrOptions
425
- : undefined;
426
- await session.compact(instructions, compactOptions);
427
- },
428
420
  },
429
421
  );
430
422
  await extensionRunner.emit({ type: "session_start" });