@oh-my-pi/pi-coding-agent 15.5.6 → 15.5.8

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 (76) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/types/cli/auth-gateway-cli.d.ts +8 -0
  3. package/dist/types/commands/auth-gateway.d.ts +3 -0
  4. package/dist/types/config/settings-schema.d.ts +60 -12
  5. package/dist/types/edit/file-snapshot-store.d.ts +9 -6
  6. package/dist/types/edit/hashline/diff.d.ts +4 -5
  7. package/dist/types/edit/streaming.d.ts +2 -1
  8. package/dist/types/eval/py/index.d.ts +1 -0
  9. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  10. package/dist/types/extensibility/shared-events.d.ts +1 -1
  11. package/dist/types/internal-urls/index.d.ts +1 -0
  12. package/dist/types/internal-urls/vault-protocol.d.ts +93 -0
  13. package/dist/types/lib/xai-http.d.ts +40 -0
  14. package/dist/types/mcp/transports/http.d.ts +9 -0
  15. package/dist/types/modes/components/tool-execution.d.ts +2 -1
  16. package/dist/types/session/agent-session.d.ts +4 -1
  17. package/dist/types/tools/fetch.d.ts +16 -0
  18. package/dist/types/tools/image-gen.d.ts +6 -2
  19. package/dist/types/tools/index.d.ts +1 -0
  20. package/dist/types/tools/match-line-format.d.ts +2 -2
  21. package/dist/types/tools/plan-mode-guard.d.ts +5 -6
  22. package/dist/types/tools/render-utils.d.ts +3 -1
  23. package/dist/types/tools/tts.d.ts +18 -0
  24. package/dist/types/tools/write.d.ts +2 -0
  25. package/dist/types/utils/file-mentions.d.ts +2 -0
  26. package/package.json +8 -8
  27. package/src/cli/args.ts +2 -0
  28. package/src/cli/auth-broker-cli.ts +2 -1
  29. package/src/cli/auth-gateway-cli.ts +210 -9
  30. package/src/commands/auth-gateway.ts +7 -1
  31. package/src/config/model-registry.ts +41 -9
  32. package/src/config/settings-schema.ts +55 -13
  33. package/src/edit/file-snapshot-store.ts +9 -6
  34. package/src/edit/hashline/diff.ts +26 -13
  35. package/src/edit/hashline/execute.ts +13 -9
  36. package/src/edit/renderer.ts +9 -9
  37. package/src/edit/streaming.ts +4 -6
  38. package/src/eval/py/index.ts +1 -1
  39. package/src/extensibility/custom-tools/types.ts +1 -1
  40. package/src/extensibility/shared-events.ts +1 -1
  41. package/src/internal-urls/docs-index.generated.ts +7 -7
  42. package/src/internal-urls/index.ts +1 -0
  43. package/src/internal-urls/router.ts +2 -0
  44. package/src/internal-urls/vault-protocol.ts +936 -0
  45. package/src/lib/xai-http.ts +124 -0
  46. package/src/main.ts +1 -2
  47. package/src/mcp/transports/http.ts +29 -2
  48. package/src/modes/components/tool-execution.ts +6 -4
  49. package/src/modes/controllers/event-controller.ts +10 -3
  50. package/src/modes/controllers/selector-controller.ts +7 -2
  51. package/src/modes/interactive-mode.ts +11 -3
  52. package/src/modes/utils/ui-helpers.ts +2 -1
  53. package/src/prompts/system/system-prompt.md +3 -0
  54. package/src/prompts/tools/ast-edit.md +1 -1
  55. package/src/prompts/tools/ast-grep.md +1 -1
  56. package/src/prompts/tools/read.md +3 -3
  57. package/src/prompts/tools/search.md +1 -1
  58. package/src/sdk.ts +41 -10
  59. package/src/session/agent-session.ts +112 -14
  60. package/src/system-prompt.ts +2 -0
  61. package/src/tools/ast-edit.ts +10 -7
  62. package/src/tools/ast-grep.ts +12 -11
  63. package/src/tools/eval.ts +28 -3
  64. package/src/tools/fetch.ts +52 -24
  65. package/src/tools/image-gen.ts +205 -7
  66. package/src/tools/index.ts +1 -0
  67. package/src/tools/match-line-format.ts +2 -2
  68. package/src/tools/path-utils.ts +2 -0
  69. package/src/tools/plan-mode-guard.ts +20 -7
  70. package/src/tools/read.ts +70 -55
  71. package/src/tools/render-utils.ts +15 -0
  72. package/src/tools/search.ts +14 -14
  73. package/src/tools/tts.ts +133 -0
  74. package/src/tools/write.ts +61 -6
  75. package/src/utils/file-mentions.ts +11 -5
  76. package/src/web/search/providers/codex.ts +2 -1
@@ -1568,16 +1568,6 @@ export const SETTINGS_SCHEMA = {
1568
1568
  },
1569
1569
  },
1570
1570
 
1571
- "edit.hashlineAutoDropPureInsertDuplicates": {
1572
- type: "boolean",
1573
- default: false,
1574
- ui: {
1575
- tab: "editing",
1576
- label: "Hashline Duplicate Insert Drop",
1577
- description:
1578
- "Drop payload lines that duplicate adjacent file context — 2+-line context echoes on `↑`/`↓` inserts, and a single boundary line at either edge of an `A-B:` replacement",
1579
- },
1580
- },
1581
1571
  "edit.blockAutoGenerated": {
1582
1572
  type: "boolean",
1583
1573
  default: true,
@@ -1605,7 +1595,7 @@ export const SETTINGS_SCHEMA = {
1605
1595
  tab: "editing",
1606
1596
  label: "Hash Lines",
1607
1597
  description:
1608
- "Include file-hash headers and line numbers in read output for hashline edit mode (¶PATH#hash plus LINE:content)",
1598
+ "Include snapshot-tag headers and line numbers in read output for hashline edit mode (¶PATH#tag plus LINE:content)",
1609
1599
  },
1610
1600
  },
1611
1601
 
@@ -2036,6 +2026,15 @@ export const SETTINGS_SCHEMA = {
2036
2026
  },
2037
2027
  },
2038
2028
 
2029
+ "tts.enabled": {
2030
+ type: "boolean",
2031
+ default: false,
2032
+ ui: {
2033
+ tab: "tools",
2034
+ label: "Text-to-Speech",
2035
+ description: "Enable the tts tool for xAI Grok Voice speech synthesis",
2036
+ },
2037
+ },
2039
2038
  "recipe.enabled": {
2040
2039
  type: "boolean",
2041
2040
  default: true,
@@ -2074,6 +2073,17 @@ export const SETTINGS_SCHEMA = {
2074
2073
  ui: { tab: "tools", label: "Read URLs", description: "Allow the read tool to fetch and process URLs" },
2075
2074
  },
2076
2075
 
2076
+ "vault.enabled": {
2077
+ type: "boolean",
2078
+ default: false,
2079
+ ui: {
2080
+ tab: "tools",
2081
+ label: "Obsidian Vault",
2082
+ description:
2083
+ "Enable the vault:// internal URL for reading and editing Obsidian vault content via the Obsidian CLI. When disabled, vault:// resolution is refused and the vault:// entry is omitted from the system prompt.",
2084
+ },
2085
+ },
2086
+
2077
2087
  "github.enabled": {
2078
2088
  type: "boolean",
2079
2089
  default: false,
@@ -2698,7 +2708,7 @@ export const SETTINGS_SCHEMA = {
2698
2708
  },
2699
2709
  "providers.image": {
2700
2710
  type: "enum",
2701
- values: ["auto", "openai", "gemini", "openrouter"] as const,
2711
+ values: ["auto", "openai", "antigravity", "xai", "gemini", "openrouter"] as const,
2702
2712
  default: "auto",
2703
2713
  ui: {
2704
2714
  tab: "providers",
@@ -2708,9 +2718,19 @@ export const SETTINGS_SCHEMA = {
2708
2718
  {
2709
2719
  value: "auto",
2710
2720
  label: "Auto",
2711
- description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini",
2721
+ description: "Priority: GPT model image tool > Antigravity > xAI > OpenRouter > Gemini",
2712
2722
  },
2713
2723
  { value: "openai", label: "OpenAI", description: "Uses the active GPT Responses/Codex model" },
2724
+ {
2725
+ value: "antigravity",
2726
+ label: "Antigravity",
2727
+ description: "Requires google-antigravity OAuth",
2728
+ },
2729
+ {
2730
+ value: "xai",
2731
+ label: "xAI Grok Imagine",
2732
+ description: "Requires xAI Grok OAuth or XAI_API_KEY",
2733
+ },
2714
2734
  { value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
2715
2735
  { value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
2716
2736
  ],
@@ -2748,6 +2768,28 @@ export const SETTINGS_SCHEMA = {
2748
2768
  },
2749
2769
  },
2750
2770
 
2771
+ "providers.openrouterVariant": {
2772
+ type: "enum",
2773
+ values: ["default", "nitro", "floor", "online", "exacto"] as const,
2774
+ default: "default",
2775
+ ui: {
2776
+ tab: "providers",
2777
+ label: "OpenRouter Routing",
2778
+ description:
2779
+ "Default routing-variant suffix appended to OpenRouter model IDs (overridden when the selector already names a variant)",
2780
+ options: [
2781
+ { value: "default", label: "Default", description: "No suffix; use OpenRouter's default routing" },
2782
+ { value: "nitro", label: ":nitro", description: "Prioritize throughput / lowest latency" },
2783
+ { value: "floor", label: ":floor", description: "Prioritize cheapest available provider" },
2784
+ { value: "online", label: ":online", description: "Enable OpenRouter's web-search plugin" },
2785
+ {
2786
+ value: "exacto",
2787
+ label: ":exacto",
2788
+ description: "Cherry-picked high-quality providers (only defined for select models)",
2789
+ },
2790
+ ],
2791
+ },
2792
+ },
2751
2793
  "providers.parallelFetch": {
2752
2794
  type: "boolean",
2753
2795
  default: true,
@@ -2,21 +2,24 @@
2
2
  * Session-bound file snapshot store.
3
3
  *
4
4
  * Used by `read` and `search` to record exactly what the model saw, and by
5
- * the hashline patcher to recover from stale section hashes (file changed
6
- * externally between read and edit, or a prior in-session edit advanced
7
- * the hash). The store is the {@link InMemorySnapshotStore} implementation
5
+ * the hashline patcher to verify or recover from stale section tags (file
6
+ * changed externally between read and edit, or a prior in-session edit
7
+ * advanced the tag). The store is the {@link InMemorySnapshotStore}
8
8
  * from `@oh-my-pi/hashline`; the only coding-agent-specific concern here
9
- * is wiring it onto the per-session {@link ToolSession} object.
9
+ * is wiring it onto the per-session owner object.
10
10
  */
11
11
  import { InMemorySnapshotStore } from "@oh-my-pi/hashline";
12
- import type { ToolSession } from "../tools";
12
+
13
+ interface FileSnapshotStoreOwner {
14
+ fileSnapshotStore?: InMemorySnapshotStore;
15
+ }
13
16
 
14
17
  /**
15
18
  * Look up (or lazily create) the file snapshot store attached to a session.
16
19
  * Storage lives on `session.fileSnapshotStore` so it ages out exactly with
17
20
  * the session itself.
18
21
  */
19
- export function getFileSnapshotStore(session: ToolSession): InMemorySnapshotStore {
22
+ export function getFileSnapshotStore(session: FileSnapshotStoreOwner): InMemorySnapshotStore {
20
23
  if (!session.fileSnapshotStore) session.fileSnapshotStore = new InMemorySnapshotStore();
21
24
  return session.fileSnapshotStore;
22
25
  }
@@ -5,16 +5,17 @@
5
5
  * pair to {@link generateDiffString} so the renderer can show the diff
6
6
  * while the tool call is still streaming.
7
7
  *
8
- * Validation is intentionally light: only the section file hash is checked
8
+ * Validation is intentionally light: only the section snapshot tag is checked
9
9
  * (so the preview goes red when anchors are stale), no plan-mode guards
10
10
  * and no auto-generated-file refusal — those belong on the write path.
11
11
  */
12
12
  import {
13
- computeFileHash,
14
13
  Patch as HashlinePatch,
15
14
  normalizeToLF,
16
15
  type Patch,
17
16
  type PatchSection,
17
+ type Snapshot,
18
+ type SnapshotStore,
18
19
  stripBom,
19
20
  } from "@oh-my-pi/hashline";
20
21
  import { resolveToCwd } from "../../tools/path-utils";
@@ -22,7 +23,6 @@ import { generateDiffString } from "../diff";
22
23
  import { readEditFileText } from "../read-file";
23
24
 
24
25
  export interface HashlineDiffOptions {
25
- autoDropPureInsertDuplicates?: boolean;
26
26
  /**
27
27
  * Use the streaming-tolerant applier ({@link PatchSection.applyPartialTo})
28
28
  * so trailing in-flight ops do not throw or emit phantom edits. Streaming
@@ -44,20 +44,34 @@ function hasAnchorScoped(section: PatchSection): boolean {
44
44
  return section.hasAnchorScopedEdit;
45
45
  }
46
46
 
47
- function validateSectionHash(section: PatchSection, text: string): string | null {
47
+ function snapshotMatchesCurrent(snapshot: Snapshot, currentText: string, anchorLines: readonly number[]): boolean {
48
+ if (snapshot.fullText !== undefined) return snapshot.fullText === currentText;
49
+ for (const lineNumber of anchorLines) {
50
+ if (snapshot.get(lineNumber) === undefined) return false;
51
+ }
52
+ return snapshot.matchesLiveFile(currentText.split("\n"));
53
+ }
54
+
55
+ function validateSectionHash(
56
+ section: PatchSection,
57
+ absolutePath: string,
58
+ text: string,
59
+ snapshots: SnapshotStore,
60
+ ): string | null {
48
61
  if (section.fileHash === undefined) {
49
62
  return hasAnchorScoped(section)
50
- ? `Missing hashline file hash for anchored edit to ${section.path}; use \`¶${section.path}#hash\` from your latest read.`
63
+ ? `Missing hashline snapshot tag for anchored edit to ${section.path}; use \`¶${section.path}#tag\` from your latest read.`
51
64
  : null;
52
65
  }
53
- const currentHash = computeFileHash(text);
54
- if (currentHash === section.fileHash) return null;
55
- return `Hashline file hash mismatch for ${section.path}: section is bound to #${section.fileHash}, but current file hashes to #${currentHash}; re-read and try again.`;
66
+ const snapshot = snapshots.byHash(absolutePath, section.fileHash);
67
+ if (snapshot && snapshotMatchesCurrent(snapshot, text, section.collectAnchorLines())) return null;
68
+ return `Hashline snapshot tag mismatch for ${section.path}: section is bound to #${section.fileHash}, but current file does not match that snapshot; re-read and try again.`;
56
69
  }
57
70
 
58
71
  export async function computeHashlineSectionDiff(
59
72
  section: PatchSection,
60
73
  cwd: string,
74
+ snapshots: SnapshotStore,
61
75
  options: HashlineDiffOptions = {},
62
76
  ): Promise<{ diff: string; firstChangedLine: number | undefined } | { error: string }> {
63
77
  try {
@@ -65,11 +79,9 @@ export async function computeHashlineSectionDiff(
65
79
  const rawContent = await readSectionText(absolutePath, section.path);
66
80
  const { text: content } = stripBom(rawContent);
67
81
  const normalized = normalizeToLF(content);
68
- const hashError = validateSectionHash(section, normalized);
82
+ const hashError = validateSectionHash(section, absolutePath, normalized, snapshots);
69
83
  if (hashError) return { error: hashError };
70
- const result = options.streaming
71
- ? section.applyPartialTo(normalized, options)
72
- : section.applyTo(normalized, options);
84
+ const result = options.streaming ? section.applyPartialTo(normalized) : section.applyTo(normalized);
73
85
  if (normalized === result.text) return { error: `No changes would be made to ${section.path}.` };
74
86
  return generateDiffString(normalized, result.text);
75
87
  } catch (err) {
@@ -80,6 +92,7 @@ export async function computeHashlineSectionDiff(
80
92
  export async function computeHashlineDiff(
81
93
  input: { input: string },
82
94
  cwd: string,
95
+ snapshots: SnapshotStore,
83
96
  options: HashlineDiffOptions = {},
84
97
  ): Promise<{ diff: string; firstChangedLine: number | undefined } | { error: string }> {
85
98
  let patch: Patch;
@@ -91,5 +104,5 @@ export async function computeHashlineDiff(
91
104
  if (patch.sections.length !== 1) {
92
105
  return { error: "Streaming diff preview supports exactly one hashline section." };
93
106
  }
94
- return computeHashlineSectionDiff(patch.sections[0], cwd, options);
107
+ return computeHashlineSectionDiff(patch.sections[0], cwd, snapshots, options);
95
108
  }
@@ -37,14 +37,19 @@ export interface ExecuteHashlineSingleOptions {
37
37
  beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
38
38
  }
39
39
 
40
- function getHashlineApplyOptions(session: ToolSession): { autoDropPureInsertDuplicates: boolean } {
41
- return {
42
- autoDropPureInsertDuplicates: session.settings.get("edit.hashlineAutoDropPureInsertDuplicates"),
43
- };
44
- }
45
-
46
40
  function noChangeDiagnostic(path: string): string {
47
- return `Edits to ${path} resulted in no changes being made.`;
41
+ // The patch parsed and applied cleanly but produced no change the
42
+ // `|literal` body rows matched the file content at the targeted lines
43
+ // byte-for-byte. The model usually misreads this as "wrong anchor, try
44
+ // again with a bigger payload" and starts duplicating content; the
45
+ // message below names the cause directly so the next turn can re-read
46
+ // instead of expanding the patch.
47
+ return (
48
+ `Edits to ${path} parsed and applied cleanly, but produced no change: ` +
49
+ `your body row(s) are byte-identical to the file at the targeted lines. ` +
50
+ `The bug is somewhere else — re-read the file before issuing another edit. ` +
51
+ `Do NOT widen the payload or add lines; verify the anchor first.`
52
+ );
48
53
  }
49
54
 
50
55
  function assertUniqueCanonicalPaths(prepared: readonly PreparedSection[]): void {
@@ -128,8 +133,7 @@ export async function executeHashlineSingle(
128
133
  batchRequest: options.batchRequest,
129
134
  });
130
135
  const snapshots = getFileSnapshotStore(options.session);
131
- const applyOptions = getHashlineApplyOptions(options.session);
132
- const patcher = new Patcher({ fs, snapshots, applyOptions });
136
+ const patcher = new Patcher({ fs, snapshots });
133
137
 
134
138
  // Single-section fast path: prepare, commit, render.
135
139
  if (patch.sections.length === 1) {
@@ -235,24 +235,24 @@ function renderPlainTextPreview(text: string, uiTheme: Theme, filePath?: string)
235
235
 
236
236
  function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, label = "streaming"): string {
237
237
  if (!diff) return "";
238
- // Hunk-aware truncation keeps the change rows themselves visible and
239
- // trims surrounding context proportionally so a multi-hunk diff doesn't
240
- // turn into just the tail of the last hunk while streaming.
238
+ // Hunk-aware truncation keeps the change rows themselves visible. Tail-mode
239
+ // pins the visible window to the bottom of the diff so newly streamed
240
+ // hunks stay on screen as more arrives, instead of leaving the user stuck
241
+ // staring at the head of the file while the tail scrolls offscreen.
241
242
  const {
242
243
  text: truncatedDiff,
243
244
  hiddenHunks,
244
245
  hiddenLines,
245
- } = truncateDiffByHunk(diff, PREVIEW_LIMITS.DIFF_COLLAPSED_HUNKS, EDIT_STREAMING_PREVIEW_LINES);
246
+ } = truncateDiffByHunk(diff, PREVIEW_LIMITS.DIFF_COLLAPSED_HUNKS, EDIT_STREAMING_PREVIEW_LINES, { fromTail: true });
246
247
  let text = "\n\n";
247
- text += renderDiffColored(truncatedDiff, { filePath: rawPath });
248
248
  if (hiddenHunks > 0 || hiddenLines > 0) {
249
249
  const remainder: string[] = [];
250
250
  if (hiddenHunks > 0) remainder.push(`${hiddenHunks} more hunks`);
251
251
  if (hiddenLines > 0) remainder.push(`${hiddenLines} more lines`);
252
- text += uiTheme.fg("dim", `\n… (${label} +${remainder.join(", ")})`);
253
- } else {
254
- text += uiTheme.fg("dim", `\n(${label})`);
252
+ text += `${uiTheme.fg("dim", `… (${remainder.join(", ")} above)`)}\n`;
255
253
  }
254
+ text += renderDiffColored(truncatedDiff, { filePath: rawPath });
255
+ text += uiTheme.fg("dim", `\n(${label})`);
256
256
  return text;
257
257
  }
258
258
 
@@ -312,7 +312,7 @@ const MISSING_APPLY_PATCH_END_ERROR = "The last line of the patch must be '*** E
312
312
 
313
313
  function normalizeHashlineInputPreviewPath(rawPath: string): string {
314
314
  const trimmed = rawPath.trim();
315
- const hashStart = /#[0-9a-f]{4}$/u.exec(trimmed)?.index;
315
+ const hashStart = /#[0-9a-fA-F]{3}$/u.exec(trimmed)?.index;
316
316
  const withoutHash = hashStart === undefined ? trimmed : trimmed.slice(0, hashStart);
317
317
  if (withoutHash.length < 2) return withoutHash;
318
318
  const first = withoutHash[0];
@@ -20,6 +20,7 @@ import {
20
20
  END_PATCH_MARKER,
21
21
  type PatchSection as HashlineInputSection,
22
22
  Patch as HashlinePatch,
23
+ type SnapshotStore,
23
24
  } from "@oh-my-pi/hashline";
24
25
  import type { Theme } from "../modes/theme/theme";
25
26
  import { type EditMode, resolveEditMode } from "../utils/edit-mode";
@@ -39,9 +40,9 @@ export interface PerFileDiffPreview {
39
40
  export interface StreamingDiffContext {
40
41
  cwd: string;
41
42
  signal: AbortSignal;
43
+ snapshots: SnapshotStore;
42
44
  fuzzyThreshold?: number;
43
45
  allowFuzzy?: boolean;
44
- hashlineAutoDropPureInsertDuplicates?: boolean;
45
46
  /**
46
47
  * True while the tool's arguments are still streaming in. Strategies that
47
48
  * accept free-form text input (apply_patch, hashline) trim the trailing
@@ -325,9 +326,7 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
325
326
  // to parse; suppress until the next chunk arrives. Once args are
326
327
  // complete, surface the error so the model sees what went wrong.
327
328
  if (ctx.isStreaming) return null;
328
- const result = await computeHashlineDiff({ input }, ctx.cwd, {
329
- autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
330
- });
329
+ const result = await computeHashlineDiff({ input }, ctx.cwd, ctx.snapshots);
331
330
  ctx.signal.throwIfAborted();
332
331
  return [toPerFilePreview("", result)];
333
332
  }
@@ -346,8 +345,7 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
346
345
  for (let i = 0; i < sectionsToProcess.length; i++) {
347
346
  ctx.signal.throwIfAborted();
348
347
  const section = sectionsToProcess[i];
349
- const result = await computeHashlineSectionDiff(section, ctx.cwd, {
350
- autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
348
+ const result = await computeHashlineSectionDiff(section, ctx.cwd, ctx.snapshots, {
351
349
  streaming: ctx.isStreaming,
352
350
  });
353
351
  ctx.signal.throwIfAborted();
@@ -5,7 +5,7 @@ import { checkPythonKernelAvailability } from "./kernel";
5
5
 
6
6
  const PYTHON_SESSION_PREFIX = "python:";
7
7
 
8
- function namespaceSessionId(sessionId: string): string {
8
+ export function namespaceSessionId(sessionId: string): string {
9
9
  return sessionId.startsWith(PYTHON_SESSION_PREFIX) ? sessionId : `${PYTHON_SESSION_PREFIX}${sessionId}`;
10
10
  }
11
11
 
@@ -100,7 +100,7 @@ export type CustomToolSessionEvent =
100
100
  }
101
101
  | {
102
102
  reason: "auto_compaction_start";
103
- trigger: "threshold" | "overflow" | "idle";
103
+ trigger: "threshold" | "overflow" | "idle" | "incomplete";
104
104
  action: "context-full" | "handoff";
105
105
  }
106
106
  | {
@@ -203,7 +203,7 @@ export interface TurnEndEvent {
203
203
  /** Fired when auto-compaction starts */
204
204
  export interface AutoCompactionStartEvent {
205
205
  type: "auto_compaction_start";
206
- reason: "threshold" | "overflow" | "idle";
206
+ reason: "threshold" | "overflow" | "idle" | "incomplete";
207
207
  action: "context-full" | "handoff";
208
208
  }
209
209