@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/examples/custom-tools/README.md +11 -7
  3. package/examples/custom-tools/hello/index.ts +2 -2
  4. package/examples/extensions/README.md +19 -8
  5. package/examples/extensions/api-demo.ts +15 -19
  6. package/examples/extensions/hello.ts +5 -6
  7. package/examples/extensions/plan-mode.ts +1 -1
  8. package/examples/extensions/reload-runtime.ts +4 -3
  9. package/examples/extensions/with-deps/index.ts +4 -3
  10. package/examples/sdk/06-extensions.ts +4 -2
  11. package/package.json +8 -18
  12. package/src/autoresearch/tools/init-experiment.ts +38 -41
  13. package/src/autoresearch/tools/log-experiment.ts +32 -41
  14. package/src/autoresearch/tools/run-experiment.ts +3 -3
  15. package/src/autoresearch/tools/update-notes.ts +11 -11
  16. package/src/commands/commit.ts +10 -0
  17. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  18. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  19. package/src/commit/agentic/tools/git-hunk.ts +5 -5
  20. package/src/commit/agentic/tools/git-overview.ts +4 -4
  21. package/src/commit/agentic/tools/propose-changelog.ts +13 -13
  22. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  23. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  24. package/src/commit/agentic/tools/schemas.ts +28 -28
  25. package/src/commit/agentic/tools/split-commit.ts +22 -21
  26. package/src/commit/analysis/summary.ts +4 -4
  27. package/src/commit/changelog/generate.ts +7 -11
  28. package/src/commit/shared-llm.ts +22 -34
  29. package/src/config/config-file.ts +35 -13
  30. package/src/config/model-registry.ts +40 -191
  31. package/src/config/models-config-schema.ts +166 -0
  32. package/src/config/settings-schema.ts +29 -0
  33. package/src/discovery/claude-plugins.ts +19 -7
  34. package/src/edit/index.ts +2 -2
  35. package/src/edit/modes/apply-patch.ts +7 -6
  36. package/src/edit/modes/patch.ts +18 -25
  37. package/src/edit/modes/replace.ts +18 -20
  38. package/src/eval/js/shared/rewrite-imports.ts +131 -10
  39. package/src/eval/py/executor.ts +233 -623
  40. package/src/eval/py/kernel.ts +27 -2
  41. package/src/eval/py/runner.py +42 -11
  42. package/src/eval/py/runtime.ts +1 -0
  43. package/src/exa/factory.ts +5 -4
  44. package/src/exa/mcp-client.ts +1 -1
  45. package/src/exa/researcher.ts +9 -20
  46. package/src/exa/search.ts +26 -52
  47. package/src/exa/types.ts +1 -1
  48. package/src/exa/websets.ts +54 -53
  49. package/src/exec/bash-executor.ts +2 -1
  50. package/src/extensibility/custom-commands/loader.ts +5 -3
  51. package/src/extensibility/custom-commands/types.ts +4 -2
  52. package/src/extensibility/custom-tools/loader.ts +5 -3
  53. package/src/extensibility/custom-tools/types.ts +7 -6
  54. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  55. package/src/extensibility/extensions/get-commands-handler.ts +77 -0
  56. package/src/extensibility/extensions/loader.ts +7 -3
  57. package/src/extensibility/extensions/types.ts +9 -5
  58. package/src/extensibility/extensions/wrapper.ts +1 -2
  59. package/src/extensibility/hooks/loader.ts +3 -1
  60. package/src/extensibility/hooks/tool-wrapper.ts +1 -1
  61. package/src/extensibility/hooks/types.ts +4 -2
  62. package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
  63. package/src/extensibility/shared-events.ts +1 -1
  64. package/src/extensibility/typebox.ts +391 -0
  65. package/src/goals/tools/goal-tool.ts +6 -12
  66. package/src/hashline/input.ts +2 -1
  67. package/src/hashline/parser.ts +27 -3
  68. package/src/hashline/types.ts +4 -4
  69. package/src/hindsight/state.ts +2 -2
  70. package/src/index.ts +0 -2
  71. package/src/internal-urls/docs-index.generated.ts +15 -15
  72. package/src/internal-urls/router.ts +8 -0
  73. package/src/internal-urls/types.ts +21 -0
  74. package/src/lsp/config.ts +15 -6
  75. package/src/lsp/defaults.json +6 -2
  76. package/src/lsp/types.ts +30 -38
  77. package/src/mcp/manager.ts +1 -1
  78. package/src/mcp/tool-bridge.ts +1 -1
  79. package/src/modes/acp/acp-agent.ts +248 -50
  80. package/src/modes/components/session-observer-overlay.ts +12 -1
  81. package/src/modes/components/status-line/segments.ts +39 -4
  82. package/src/modes/controllers/command-controller.ts +27 -2
  83. package/src/modes/controllers/event-controller.ts +3 -4
  84. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  85. package/src/modes/interactive-mode.ts +1 -1
  86. package/src/modes/rpc/host-tools.ts +1 -1
  87. package/src/modes/rpc/host-uris.ts +235 -0
  88. package/src/modes/rpc/rpc-client.ts +1 -1
  89. package/src/modes/rpc/rpc-mode.ts +27 -1
  90. package/src/modes/rpc/rpc-types.ts +58 -1
  91. package/src/modes/runtime-init.ts +2 -1
  92. package/src/modes/theme/defaults/dark-poimandres.json +1 -0
  93. package/src/modes/theme/defaults/light-poimandres.json +1 -0
  94. package/src/modes/theme/theme.ts +117 -117
  95. package/src/modes/types.ts +1 -1
  96. package/src/modes/utils/context-usage.ts +2 -2
  97. package/src/prompts/tools/github.md +4 -4
  98. package/src/prompts/tools/hashline.md +22 -26
  99. package/src/prompts/tools/read.md +55 -37
  100. package/src/sdk.ts +31 -8
  101. package/src/session/agent-session.ts +74 -104
  102. package/src/session/messages.ts +16 -51
  103. package/src/session/session-manager.ts +22 -2
  104. package/src/session/streaming-output.ts +16 -6
  105. package/src/task/discovery.ts +5 -2
  106. package/src/task/executor.ts +210 -87
  107. package/src/task/index.ts +15 -11
  108. package/src/task/render.ts +32 -5
  109. package/src/task/types.ts +54 -39
  110. package/src/tools/ask.ts +12 -12
  111. package/src/tools/ast-edit.ts +11 -15
  112. package/src/tools/ast-grep.ts +9 -10
  113. package/src/tools/bash-command-fixup.ts +47 -0
  114. package/src/tools/bash.ts +48 -38
  115. package/src/tools/browser/render.ts +2 -2
  116. package/src/tools/browser.ts +39 -53
  117. package/src/tools/calculator.ts +12 -11
  118. package/src/tools/checkpoint.ts +7 -7
  119. package/src/tools/debug.ts +40 -43
  120. package/src/tools/eval.ts +16 -10
  121. package/src/tools/find.ts +10 -13
  122. package/src/tools/gh.ts +108 -132
  123. package/src/tools/hindsight-recall.ts +4 -6
  124. package/src/tools/hindsight-reflect.ts +5 -5
  125. package/src/tools/hindsight-retain.ts +15 -17
  126. package/src/tools/image-gen.ts +31 -81
  127. package/src/tools/index.ts +4 -1
  128. package/src/tools/inspect-image.ts +8 -9
  129. package/src/tools/irc.ts +15 -27
  130. package/src/tools/job.ts +30 -28
  131. package/src/tools/output-meta.ts +26 -0
  132. package/src/tools/read.ts +39 -12
  133. package/src/tools/recipe/index.ts +7 -9
  134. package/src/tools/render-mermaid.ts +12 -12
  135. package/src/tools/report-tool-issue.ts +4 -4
  136. package/src/tools/resolve.ts +11 -11
  137. package/src/tools/review.ts +14 -26
  138. package/src/tools/search-tool-bm25.ts +7 -9
  139. package/src/tools/search.ts +19 -22
  140. package/src/tools/ssh.ts +10 -9
  141. package/src/tools/todo-write.ts +26 -34
  142. package/src/tools/vim.ts +10 -26
  143. package/src/tools/write.ts +25 -5
  144. package/src/tools/yield.ts +100 -54
  145. package/src/web/search/index.ts +9 -24
  146. package/src/web/search/providers/anthropic.ts +5 -0
  147. package/src/web/search/providers/exa.ts +3 -0
  148. package/src/web/search/providers/gemini.ts +5 -0
  149. package/src/web/search/providers/jina.ts +5 -2
  150. package/src/web/search/providers/zai.ts +5 -2
  151. package/src/prompts/compaction/branch-summary-context.md +0 -5
  152. package/src/prompts/compaction/branch-summary-preamble.md +0 -2
  153. package/src/prompts/compaction/branch-summary.md +0 -30
  154. package/src/prompts/compaction/compaction-short-summary.md +0 -9
  155. package/src/prompts/compaction/compaction-summary-context.md +0 -5
  156. package/src/prompts/compaction/compaction-summary.md +0 -38
  157. package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
  158. package/src/prompts/compaction/compaction-update-summary.md +0 -45
  159. package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
  160. package/src/prompts/system/file-operations.md +0 -10
  161. package/src/prompts/system/handoff-document.md +0 -49
  162. package/src/prompts/system/summarization-system.md +0 -3
  163. package/src/session/compaction/branch-summarization.ts +0 -324
  164. package/src/session/compaction/compaction.ts +0 -1420
  165. package/src/session/compaction/errors.ts +0 -31
  166. package/src/session/compaction/index.ts +0 -8
  167. package/src/session/compaction/pruning.ts +0 -91
  168. package/src/session/compaction/utils.ts +0 -184
@@ -11,9 +11,8 @@ import {
11
11
  } from "@oh-my-pi/pi-natives";
12
12
  import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
13
13
  import { adjustHsv, getCustomThemesDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
14
- import { type Static, Type } from "@sinclair/typebox";
15
- import { TypeCompiler } from "@sinclair/typebox/compiler";
16
14
  import chalk from "chalk";
15
+ import * as z from "zod/v4";
17
16
  // Embed theme JSON files at build time
18
17
  import darkThemeJson from "./dark.json" with { type: "json" };
19
18
  import { defaultThemes } from "./defaults";
@@ -95,6 +94,7 @@ export type SymbolKey =
95
94
  | "icon.pause"
96
95
  | "icon.loop"
97
96
  | "icon.folder"
97
+ | "icon.scratchFolder"
98
98
  | "icon.file"
99
99
  | "icon.git"
100
100
  | "icon.branch"
@@ -258,6 +258,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
258
258
  "icon.pause": "⏸",
259
259
  "icon.loop": "↻",
260
260
  "icon.folder": "📁",
261
+ "icon.scratchFolder": "🗑",
261
262
  "icon.file": "📄",
262
263
  "icon.git": "⎇",
263
264
  "icon.branch": "⑂",
@@ -476,6 +477,8 @@ const NERD_SYMBOLS: SymbolMap = {
476
477
  "icon.loop": "\uf021",
477
478
  // pick:  | alt:  
478
479
  "icon.folder": "\uf115",
480
+ // pick: | alt:
481
+ "icon.scratchFolder": "\uf014",
479
482
  // pick:  | alt:  
480
483
  "icon.file": "\uf15b",
481
484
  // pick:  | alt:  ⎇
@@ -678,6 +681,7 @@ const ASCII_SYMBOLS: SymbolMap = {
678
681
  "icon.pause": "||",
679
682
  "icon.loop": "loop",
680
683
  "icon.folder": "[D]",
684
+ "icon.scratchFolder": "[T]",
681
685
  "icon.file": "[F]",
682
686
  "icon.git": "git:",
683
687
  "icon.branch": "@",
@@ -802,118 +806,111 @@ const SPINNER_FRAMES: Record<SymbolPreset, Record<SpinnerType, string[]>> = {
802
806
  // Types & Schema
803
807
  // ============================================================================
804
808
 
805
- const ColorValueSchema = Type.Union([
806
- Type.String(), // hex "#ff0000", var ref "primary", or empty ""
807
- Type.Integer({ minimum: 0, maximum: 255 }), // 256-color index
809
+ const colorValueSchema = z.union([
810
+ z.string(), // hex "#ff0000", var ref "primary", or empty ""
811
+ z.number().int().min(0).max(255), // 256-color index
808
812
  ]);
809
813
 
810
- type ColorValue = Static<typeof ColorValueSchema>;
811
-
812
- // Use Type.Union here (not StringEnum) because TypeCompiler doesn't support Type.Unsafe
813
- const SymbolPresetSchema = Type.Union([Type.Literal("unicode"), Type.Literal("nerd"), Type.Literal("ascii")]);
814
-
815
- const SymbolsSchema = Type.Optional(
816
- Type.Object({
817
- preset: Type.Optional(SymbolPresetSchema),
818
- overrides: Type.Optional(Type.Record(Type.String(), Type.String())),
819
- }),
814
+ type ColorValue = z.infer<typeof colorValueSchema>;
815
+
816
+ const THEME_COLOR_KEYS = [
817
+ "accent",
818
+ "border",
819
+ "borderAccent",
820
+ "borderMuted",
821
+ "success",
822
+ "error",
823
+ "warning",
824
+ "muted",
825
+ "dim",
826
+ "text",
827
+ "thinkingText",
828
+ "selectedBg",
829
+ "userMessageBg",
830
+ "userMessageText",
831
+ "customMessageBg",
832
+ "customMessageText",
833
+ "customMessageLabel",
834
+ "toolPendingBg",
835
+ "toolSuccessBg",
836
+ "toolErrorBg",
837
+ "toolTitle",
838
+ "toolOutput",
839
+ "mdHeading",
840
+ "mdLink",
841
+ "mdLinkUrl",
842
+ "mdCode",
843
+ "mdCodeBlock",
844
+ "mdCodeBlockBorder",
845
+ "mdQuote",
846
+ "mdQuoteBorder",
847
+ "mdHr",
848
+ "mdListBullet",
849
+ "toolDiffAdded",
850
+ "toolDiffRemoved",
851
+ "toolDiffContext",
852
+ "syntaxComment",
853
+ "syntaxKeyword",
854
+ "syntaxFunction",
855
+ "syntaxVariable",
856
+ "syntaxString",
857
+ "syntaxNumber",
858
+ "syntaxType",
859
+ "syntaxOperator",
860
+ "syntaxPunctuation",
861
+ "thinkingOff",
862
+ "thinkingMinimal",
863
+ "thinkingLow",
864
+ "thinkingMedium",
865
+ "thinkingHigh",
866
+ "thinkingXhigh",
867
+ "bashMode",
868
+ "pythonMode",
869
+ "statusLineBg",
870
+ "statusLineSep",
871
+ "statusLineModel",
872
+ "statusLinePath",
873
+ "statusLineGitClean",
874
+ "statusLineGitDirty",
875
+ "statusLineContext",
876
+ "statusLineSpend",
877
+ "statusLineStaged",
878
+ "statusLineDirty",
879
+ "statusLineUntracked",
880
+ "statusLineOutput",
881
+ "statusLineCost",
882
+ "statusLineSubagents",
883
+ ] as const;
884
+
885
+ const themeColorsSchema = z.object(
886
+ Object.fromEntries(THEME_COLOR_KEYS.map(key => [key, colorValueSchema])) as unknown as {
887
+ [K in (typeof THEME_COLOR_KEYS)[number]]: typeof colorValueSchema;
888
+ },
820
889
  );
821
890
 
822
- const ThemeJsonSchema = Type.Object({
823
- $schema: Type.Optional(Type.String()),
824
- name: Type.String(),
825
- vars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),
826
- colors: Type.Object({
827
- // Core UI (10 colors)
828
- accent: ColorValueSchema,
829
- border: ColorValueSchema,
830
- borderAccent: ColorValueSchema,
831
- borderMuted: ColorValueSchema,
832
- success: ColorValueSchema,
833
- error: ColorValueSchema,
834
- warning: ColorValueSchema,
835
- muted: ColorValueSchema,
836
- dim: ColorValueSchema,
837
- text: ColorValueSchema,
838
- thinkingText: ColorValueSchema,
839
- // Backgrounds & Content Text (11 colors)
840
- selectedBg: ColorValueSchema,
841
- userMessageBg: ColorValueSchema,
842
- userMessageText: ColorValueSchema,
843
- customMessageBg: ColorValueSchema,
844
- customMessageText: ColorValueSchema,
845
- customMessageLabel: ColorValueSchema,
846
- toolPendingBg: ColorValueSchema,
847
- toolSuccessBg: ColorValueSchema,
848
- toolErrorBg: ColorValueSchema,
849
- toolTitle: ColorValueSchema,
850
- toolOutput: ColorValueSchema,
851
- // Markdown (10 colors)
852
- mdHeading: ColorValueSchema,
853
- mdLink: ColorValueSchema,
854
- mdLinkUrl: ColorValueSchema,
855
- mdCode: ColorValueSchema,
856
- mdCodeBlock: ColorValueSchema,
857
- mdCodeBlockBorder: ColorValueSchema,
858
- mdQuote: ColorValueSchema,
859
- mdQuoteBorder: ColorValueSchema,
860
- mdHr: ColorValueSchema,
861
- mdListBullet: ColorValueSchema,
862
- // Tool Diffs (3 colors)
863
- toolDiffAdded: ColorValueSchema,
864
- toolDiffRemoved: ColorValueSchema,
865
- toolDiffContext: ColorValueSchema,
866
- // Syntax Highlighting (9 colors)
867
- syntaxComment: ColorValueSchema,
868
- syntaxKeyword: ColorValueSchema,
869
- syntaxFunction: ColorValueSchema,
870
- syntaxVariable: ColorValueSchema,
871
- syntaxString: ColorValueSchema,
872
- syntaxNumber: ColorValueSchema,
873
- syntaxType: ColorValueSchema,
874
- syntaxOperator: ColorValueSchema,
875
- syntaxPunctuation: ColorValueSchema,
876
- // Thinking Level Borders (6 colors)
877
- thinkingOff: ColorValueSchema,
878
- thinkingMinimal: ColorValueSchema,
879
- thinkingLow: ColorValueSchema,
880
- thinkingMedium: ColorValueSchema,
881
- thinkingHigh: ColorValueSchema,
882
- thinkingXhigh: ColorValueSchema,
883
- // Bash Mode (1 color)
884
- bashMode: ColorValueSchema,
885
- // Python Mode (1 color)
886
- pythonMode: ColorValueSchema,
887
- // Footer Status Line
888
- statusLineBg: ColorValueSchema,
889
- statusLineSep: ColorValueSchema,
890
- statusLineModel: ColorValueSchema,
891
- statusLinePath: ColorValueSchema,
892
- statusLineGitClean: ColorValueSchema,
893
- statusLineGitDirty: ColorValueSchema,
894
- statusLineContext: ColorValueSchema,
895
- statusLineSpend: ColorValueSchema,
896
- statusLineStaged: ColorValueSchema,
897
- statusLineDirty: ColorValueSchema,
898
- statusLineUntracked: ColorValueSchema,
899
- statusLineOutput: ColorValueSchema,
900
- statusLineCost: ColorValueSchema,
901
- statusLineSubagents: ColorValueSchema,
902
- }),
903
- export: Type.Optional(
904
- Type.Object({
905
- pageBg: Type.Optional(ColorValueSchema),
906
- cardBg: Type.Optional(ColorValueSchema),
907
- infoBg: Type.Optional(ColorValueSchema),
908
- }),
909
- ),
910
- symbols: SymbolsSchema,
891
+ const symbolPresetSchema = z.enum(["unicode", "nerd", "ascii"]);
892
+
893
+ const themeJsonSchema = z.object({
894
+ $schema: z.string().optional(),
895
+ name: z.string(),
896
+ vars: z.record(z.string(), colorValueSchema).optional(),
897
+ colors: themeColorsSchema,
898
+ export: z
899
+ .object({
900
+ pageBg: colorValueSchema.optional(),
901
+ cardBg: colorValueSchema.optional(),
902
+ infoBg: colorValueSchema.optional(),
903
+ })
904
+ .optional(),
905
+ symbols: z
906
+ .object({
907
+ preset: symbolPresetSchema.optional(),
908
+ overrides: z.record(z.string(), z.string()).optional(),
909
+ })
910
+ .optional(),
911
911
  });
912
912
 
913
- type ThemeJson = Static<typeof ThemeJsonSchema>;
914
-
915
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeBox CJS/ESM type mismatch
916
- const validateThemeJson = TypeCompiler.Compile(ThemeJsonSchema as any);
913
+ type ThemeJson = z.infer<typeof themeJsonSchema>;
917
914
 
918
915
  export type ThemeColor =
919
916
  | "accent"
@@ -1457,6 +1454,7 @@ export class Theme {
1457
1454
  pause: this.#symbols["icon.pause"],
1458
1455
  loop: this.#symbols["icon.loop"],
1459
1456
  folder: this.#symbols["icon.folder"],
1457
+ scratchFolder: this.#symbols["icon.scratchFolder"],
1460
1458
  file: this.#symbols["icon.file"],
1461
1459
  git: this.#symbols["icon.git"],
1462
1460
  branch: this.#symbols["icon.branch"],
@@ -1632,18 +1630,20 @@ async function loadThemeJson(name: string): Promise<ThemeJson> {
1632
1630
  } catch (error) {
1633
1631
  throw new Error(`Failed to parse theme ${name}: ${error}`);
1634
1632
  }
1635
- if (!validateThemeJson.Check(json)) {
1636
- const errors = Array.from(validateThemeJson.Errors(json));
1633
+ const parsed = themeJsonSchema.safeParse(json);
1634
+ if (!parsed.success) {
1637
1635
  const missingColors: string[] = [];
1638
1636
  const otherErrors: string[] = [];
1639
1637
 
1640
- for (const e of errors) {
1641
- // Check for missing required color properties
1642
- const match = e.path.match(/^\/colors\/(\w+)$/);
1643
- if (match && e.message.includes("Required")) {
1644
- missingColors.push(match[1]);
1638
+ for (const issue of parsed.error.issues) {
1639
+ const parts = issue.path;
1640
+ const colorKey = parts.length === 2 && parts[0] === "colors" && typeof parts[1] === "string" ? parts[1] : null;
1641
+
1642
+ if (colorKey && issue.code === "invalid_type" && (issue as { received?: unknown }).received === undefined) {
1643
+ missingColors.push(colorKey);
1645
1644
  } else {
1646
- otherErrors.push(` - ${e.path}: ${e.message}`);
1645
+ const pathStr = parts.length === 0 ? "/" : `/${parts.map(String).join("/")}`;
1646
+ otherErrors.push(` - ${pathStr}: ${issue.message}`);
1647
1647
  }
1648
1648
  }
1649
1649
 
@@ -1660,7 +1660,7 @@ async function loadThemeJson(name: string): Promise<ThemeJson> {
1660
1660
 
1661
1661
  throw new Error(errorMessage);
1662
1662
  }
1663
- return json as ThemeJson;
1663
+ return parsed.data;
1664
1664
  }
1665
1665
 
1666
1666
  interface CreateThemeOptions {
@@ -1,4 +1,5 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
+ import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
2
3
  import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
3
4
  import type { Component, Container, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
4
5
  import type { KeybindingsManager } from "../config/keybindings";
@@ -13,7 +14,6 @@ import type { CompactOptions } from "../extensibility/extensions/types";
13
14
  import type { MCPManager } from "../mcp";
14
15
  import type { PlanApprovalDetails } from "../plan-mode/approved-plan";
15
16
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
16
- import type { CompactionOutcome } from "../session/compaction";
17
17
  import type { HistoryStorage } from "../session/history-storage";
18
18
  import type { SessionContext, SessionManager } from "../session/session-manager";
19
19
  import type { LspStartupServerInfo } from "../tools";
@@ -1,10 +1,10 @@
1
+ import type { CompactionSettings } from "@oh-my-pi/pi-agent-core/compaction";
2
+ import { effectiveReserveTokens, estimateTokens, resolveThresholdTokens } from "@oh-my-pi/pi-agent-core/compaction";
1
3
  import type { Model } from "@oh-my-pi/pi-ai";
2
4
  import { countTokens } from "@oh-my-pi/pi-natives";
3
5
  import { formatNumber } from "@oh-my-pi/pi-utils";
4
6
  import type { Skill } from "../../extensibility/skills";
5
7
  import type { AgentSession } from "../../session/agent-session";
6
- import type { CompactionSettings } from "../../session/compaction";
7
- import { effectiveReserveTokens, estimateTokens, resolveThresholdTokens } from "../../session/compaction";
8
8
  import type { Tool } from "../../tools";
9
9
  import type { theme as Theme } from "../theme/theme";
10
10
 
@@ -6,10 +6,10 @@ Pick the operation via `op`. Each op uses a subset of the parameters:
6
6
  - `pr_create` — Create a pull request. Either provide `title` (and optional `body`) or set `fill: true` to auto-fill from commits. Optional `base` (target, defaults to repo default), `head` (source, defaults to current branch), `draft`, `repo`, `reviewer[]`, `assignee[]`, `label[]`. Returns the new PR URL plus a summary.
7
7
  - `pr_checkout` — Check one or more pull requests out into dedicated git worktrees. Optional `pr` (number, URL, branch, or array of any of those — pass an array to batch-check-out multiple PRs in one call), `repo`, `force` (reset existing local branch).
8
8
  - `pr_push` — Push a checked-out PR branch back to its source branch. Requires the branch to have been checked out via `op: pr_checkout` (carries push metadata). Optional `branch`; defaults to the current checked-out git branch. Optional `forceWithLease`.
9
- - `search_issues` — Search issues using normal GitHub issue search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`.
10
- - `search_prs` — Search pull requests using normal GitHub PR search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`.
11
- - `search_code` — Search code with GitHub code search syntax. Required `query`. Optional `repo`, `limit`. Returns matching paths with surrounding fragments. Date filtering (`since`/`until`) is **not** supported by GitHub code search.
12
- - `search_commits` — Search commits across GitHub. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`. `dateField` is ignored — always uses `committer-date`.
9
+ - `search_issues` — Search issues using normal GitHub issue search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it.
10
+ - `search_prs` — Search pull requests using normal GitHub PR search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it.
11
+ - `search_code` — Search code with GitHub code search syntax. Required `query`. Optional `repo`, `limit`. Returns matching paths with surrounding fragments. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it. Date filtering (`since`/`until`) is **not** supported by GitHub code search.
12
+ - `search_commits` — Search commits across GitHub. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`. `dateField` is ignored — always uses `committer-date`. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it.
13
13
  - `search_repos` — Search repositories across GitHub. Optional `query` (required unless `since`/`until` is set), `limit`, `since`, `until`, `dateField` (use query qualifiers like `org:`, `language:` instead of `repo`).
14
14
  - Date filter format for `since` / `until`: relative duration `<n><unit>` (`m`/`h`/`d`/`w`/`mo`/`y`, e.g. `3d`, `12h`, `2w`), an ISO date `YYYY-MM-DD`, or an ISO datetime. Translated to a single GitHub-search qualifier (`created:≥…`, `created:≤…`, or `created:since..until`). `dateField: "updated"` maps to `updated:` for issues/prs and `pushed:` for repos. When you only want a date filter and no keywords, omit `query` entirely.
15
15
  - `run_watch` — Watch a GitHub Actions workflow run. Optional `run` (id or URL). Omitting `run` watches all workflow runs for the current HEAD commit; `branch` falls back to the current branch. Optional `tail` (log lines per failed job). Streams snapshots, fast-fails on the first detected job failure (with a brief grace period to capture concurrent failures), then fetches tailed logs for the failed jobs. The full failed-job logs are saved as a session artifact for on-demand reads.
@@ -66,36 +66,35 @@ When braces bound your edit, you SHOULD prefer these shapes:
66
66
  </common-failures>
67
67
 
68
68
  <case file="mod.ts">
69
- {{hline 1 "const DEF = \"guest\";"}}
70
- {{hline 2 "export function label(name) {"}}
69
+ {{hline 1 "const TITLE = \"Mr\";"}}
70
+ {{hline 2 "export function greet(name) {"}}
71
71
  {{hline 3 "\treturn ["}}
72
- {{hline 4 "\t\tname?.trim() || DEF,"}}
73
- {{hline 5 "\t\t\" \","}}
74
- {{hline 6 "\t].join(\"\");"}}
72
+ {{hline 4 "\t\tTITLE,"}}
73
+ {{hline 5 "\t\tname?.trim() || \"guest\","}}
74
+ {{hline 6 "\t].join(\" \");"}}
75
75
  {{hline 7 "}"}}
76
76
  </case>
77
77
 
78
78
  <examples>
79
79
  # Replace one line (the payload must re-emit the original indentation)
80
80
  @@ mod.ts
81
- = {{hrefr 4}}..{{hrefr 4}}
82
- {{hsep}} name?.trim().toUpperCase() || DEF,
81
+ = {{hrefr 1}}..{{hrefr 1}}
82
+ {{hsep}}const TITLE = "Mrs";
83
83
 
84
84
  # Replace a full multiline statement (widen to a self-contained boundary)
85
85
  @@ mod.ts
86
86
  = {{hrefr 3}}..{{hrefr 6}}
87
87
  {{hsep}} return [
88
- {{hsep}} name?.trim() || DEF,
89
- {{hsep}} "·",
90
- {{hsep}} " ",
91
- {{hsep}} ].join("");
88
+ {{hsep}} "Mrs",
89
+ {{hsep}} name?.trim() || "guest",
90
+ {{hsep}} ].join(" ");
92
91
 
93
92
  # Insert AFTER/BEFORE a line
94
93
  @@ mod.ts
95
- + {{hrefr 3}}
96
- {{hsep}} "·",
94
+ + {{hrefr 4}}
95
+ {{hsep}} "Dr",
97
96
  < {{hrefr 5}}
98
- {{hsep}} "·",
97
+ {{hsep}} "Dr",
99
98
 
100
99
  # Append to file
101
100
  @@ mod.ts
@@ -112,13 +111,12 @@ When braces bound your edit, you SHOULD prefer these shapes:
112
111
  </examples>
113
112
 
114
113
  <anti-pattern>
115
- # WRONG — replaces 3 lines just to add one.
114
+ # WRONG — replaces 2 lines just to add one.
116
115
  @@ mod.ts
117
- = {{hrefr 1}}..{{hrefr 3}}
118
- {{hsep}}const DEF = "guest";
116
+ = {{hrefr 1}}..{{hrefr 2}}
117
+ {{hsep}}const TITLE = "Mr";
119
118
  {{hsep}}const DEBUG = false;
120
- {{hsep}}export function label(name) {
121
- {{hsep}} return [
119
+ {{hsep}}export function greet(name) {
122
120
  # RIGHT — same effect, one-line insert
123
121
  @@ mod.ts
124
122
  + {{hrefr 1}}
@@ -127,17 +125,15 @@ When braces bound your edit, you SHOULD prefer these shapes:
127
125
  # WRONG — replace from the middle of a larger statement (error-prone)
128
126
  @@ mod.ts
129
127
  = {{hrefr 4}}..{{hrefr 5}}
130
- {{hsep}} name?.trim() || DEF,
131
- {{hsep}} "·",
132
- {{hsep}} " • ",
128
+ {{hsep}} "Dr",
129
+ {{hsep}} name?.trim() || "guest",
133
130
  # RIGHT — widen to the full statement
134
131
  @@ mod.ts
135
132
  = {{hrefr 3}}..{{hrefr 6}}
136
133
  {{hsep}} return [
137
- {{hsep}} name?.trim() || DEF,
138
- {{hsep}} "·",
139
- {{hsep}} " ",
140
- {{hsep}} ].join("");
134
+ {{hsep}} "Dr",
135
+ {{hsep}} name?.trim() || "guest",
136
+ {{hsep}} ].join(" ");
141
137
  </anti-pattern>
142
138
 
143
139
  <critical>
@@ -1,46 +1,58 @@
1
- Reads the content at the specified path or URL.
1
+ Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.
2
2
 
3
3
  <instruction>
4
- The `read` tool is multi-purpose and more capable than it looks — inspects files, directories, archives, SQLite databases, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), **and URLs**.
5
- - You MUST parallelize reads when exploring related files
6
- - For URLs, `read` fetches the page and returns clean extracted text/markdown by default (reader-mode). It handles HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs, etc. You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching and inspecting web content.
4
+ - One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).
5
+ - You SHOULD parallelize independent reads when exploring related files.
6
+ - You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.
7
+ </instruction>
7
8
 
8
9
  ## Parameters
9
- - `path` — file path or URL (required). Append `:<sel>` for line ranges or raw mode (for example `src/foo.ts:50-200` or `src/foo.ts:raw`).
10
+
11
+ - `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `memory://`, `rule://`, `local://`, `mcp://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
10
12
 
11
13
  ## Selectors
12
14
 
13
- |`path` suffix|Behavior|
14
- |---|---|
15
- |_(omitted)_|For parseable code files, return a structural summary. Otherwise read from the start (up to {{DEFAULT_LIMIT}} lines).|
16
- |`:50`|Read from line 50 onward|
17
- |`:50-200`|Read lines 50-200|
18
- |`:50+150`|Read 150 lines starting at line 50|
19
- |`:20+1`|Read exactly one line|
20
- |`:5-16,960-973`|Read multiple ranges in one call (comma-separated; ranges sort and merge automatically)|
21
- |`:raw`|Read verbatim text without anchors or summarization|
22
- |`:conflicts`|Return a one-line-per-block index of every merge conflict in the file|
15
+ Append `:<sel>` to `path`. The bare path falls back to the default mode.
16
+
17
+ - _(none)_ parseable code → structural summary (signatures kept, bodies elided); other files read from the start (up to {{DEFAULT_LIMIT}} lines).
18
+ - `:50` — read from line 50 onward.
19
+ - `:50-200` lines 50200 inclusive.
20
+ - `:50+150` 150 lines starting at line 50.
21
+ - `:20+1` exactly one line.
22
+ - `:5-16,960-973` multiple ranges in one call (sorted, overlaps merged).
23
+ - `:raw` verbatim text; no anchors, no summary, no line prefixes.
24
+ - `:2-4:raw` or `:raw:2-4` range AND verbatim; the two compose in either order.
25
+ - `:conflicts` — one-line-per-block index of every unresolved git merge conflict.
26
+
27
+ # Files
28
+
29
+ - Reading a directory path returns a depth-limited dirent listing.
30
+ {{#if IS_HL_MODE}}
31
+ - Reading a file with an explicit selector returns lines prefixed with `line+hash` anchors: `41th|def alpha():`. The 2-char hash is a content fingerprint that `edit` / `apply_patch` consume — copy it verbatim, NEVER fabricate.
32
+ {{else}}
33
+ {{#if IS_LINE_NUMBER_MODE}}
34
+ - Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`.
35
+ {{/if}}
36
+ {{/if}}
37
+ - Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:
38
+
39
+ `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`
23
40
 
24
- # Filesystem
25
- - Reading a directory path returns a list of dirents.
26
- {{#if IS_HL_MODE}}
27
- - Reading a file with an explicit selector returns lines prefixed with anchors (line+hash): `41th|def alpha():`
28
- {{else}}
29
- {{#if IS_LINE_NUMBER_MODE}}
30
- - Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`
31
- {{/if}}
32
- {{/if}}
33
- - Reading a parseable code file without a selector returns a structural summary with signatures/declarations kept and large bodies collapsed to `…`. Use `:raw` or an explicit range such as `:1-9999` for verbatim content.
41
+ If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.
34
42
 
35
- # Inspection
43
+ # Documents & Notebooks
36
44
 
37
- Extracts text from PDF, Word, PowerPoint, Excel, RTF, EPUB, and Jupyter notebook files. Notebooks are shown as editable `# %% [type] cell:N` text; edits to that text are applied back to the underlying `.ipynb` JSON while preserving notebook metadata where possible. Can inspect images.
45
+ Extracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.
38
46
 
39
- # Directories & Archives
47
+ # Images
40
48
 
41
- Directories and archive roots return a list of entries. Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read contents, and append a selector to the archive entry such as `archive.zip:dir/file.ts:50-60`.
49
+ Reading an image path returns metadata (mime, bytes, dimensions, channels, alpha). For actual visual analysis, call `inspect_image` with the path and a question describing what to inspect.
42
50
 
43
- # SQLite Databases
51
+ # Archives
52
+
53
+ Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.
54
+
55
+ # SQLite
44
56
 
45
57
  For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
46
58
  - `file.db` — list tables with row counts
@@ -52,13 +64,19 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
52
64
 
53
65
  # URLs
54
66
 
55
- Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, PDFs at URLs, and similar text-based resources. Returns clean reader-mode text/markdown — no browser required. Use a `:raw` suffix for untouched HTML. URL line selectors mirror the file form (`:50`, `:50-100`, `:50+150`, `:raw`). If a URL would otherwise look like `host:port`, add a trailing slash before the selector (e.g. `https://example.com/:80`).
56
- </instruction>
67
+ - Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs clean text/markdown.
68
+ - `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.
69
+ - Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.
70
+
71
+ # Internal URIs
72
+
73
+ `skill://<name>`, `agent://<id>`, `artifact://<id>`, `memory://root`, `rule://<name>`, `local://<name>.md`, `mcp://<uri>` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.
57
74
 
58
75
  <critical>
59
- - You MUST use `read` for every file, directory, archive, and URL read. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, and `wget` are **FORBIDDEN** for inspection — any such Bash call is a bug, regardless of how short or convenient it looks.
60
- - You MUST prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser if `read` fails to deliver reasonable content.
61
- - You MUST always include the `path` parameter — never call `read` with an empty argument object `{}`.
62
- - For specific line ranges, append the selector to `path` (e.g. `path="src/foo.ts:50-200"`, `path="src/foo.ts:50+150"`) NEVER reach for `sed -n`, `awk NR`, or `head`/`tail` pipelines.
63
- - You MAY use path suffix selectors with URL reads; the tool paginates cached fetched output.
76
+ - You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN — any such bash call is a bug, regardless of how short or convenient it looks.
77
+ - You MUST prefer `read` over a browser/puppeteer tool for URL content; only reach for a browser when `read` cannot deliver reasonable content.
78
+ - You MUST always include `path`. NEVER call `read` with `{}`.
79
+ - For line ranges, append the selector to `path` (`path="src/foo.ts:50-200"`, `path="src/foo.ts:50+150"`). NEVER substitute `sed -n`, `awk NR`, or `head`/`tail` pipelines.
80
+ - Summary footer says `read <path>:raw …`? Re-issue the exact selector it names. NEVER guess what's inside `..` / `…` markers — they carry no content.
81
+ - You MAY combine selectors with URL reads and internal URIs; both paginate the cached resolved output.
64
82
  </critical>
package/src/sdk.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  Agent,
3
3
  type AgentEvent,
4
4
  type AgentMessage,
5
+ type AgentTelemetryConfig,
5
6
  type AgentTool,
6
7
  INTENT_FIELD,
7
8
  type ThinkingLevel,
@@ -244,6 +245,17 @@ export interface CreateAgentSessionOptions {
244
245
 
245
246
  /** Whether UI is available (enables interactive tools like ask). Default: false */
246
247
  hasUI?: boolean;
248
+
249
+ /**
250
+ * Opt-in OpenTelemetry instrumentation forwarded to the underlying Agent.
251
+ * Passing `{}` enables the loop's GenAI-semantic-convention spans. See
252
+ * {@link AgentTelemetryConfig} for the full surface (hooks, content capture,
253
+ * cost estimator, agent identity).
254
+ *
255
+ * Safe to enable without an OTEL SDK registered in the host: the
256
+ * `@opentelemetry/api` package returns a no-op tracer in that case.
257
+ */
258
+ telemetry?: AgentTelemetryConfig;
247
259
  }
248
260
 
249
261
  /** Result from createAgentSession */
@@ -920,18 +932,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
920
932
  // it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
921
933
  // will re-race the same promise through its own withDeadline path. Background
922
934
  // work continues so caches still warm.
923
- const raceWithDeadline = <T>(name: string, work: Promise<T>): Promise<T | undefined> =>
924
- Promise.race([
935
+ const raceWithDeadline = async <T>(name: string, work: Promise<T>): Promise<T | undefined> => {
936
+ let timedOut = false;
937
+ const result = await Promise.race([
925
938
  work,
926
939
  Bun.sleep(STARTUP_SCAN_DEADLINE_MS).then(() => {
927
- logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
928
- name,
929
- timeoutMs: STARTUP_SCAN_DEADLINE_MS,
930
- cwd,
931
- });
940
+ timedOut = true;
932
941
  return undefined;
933
942
  }),
934
943
  ]);
944
+ if (timedOut) {
945
+ logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
946
+ name,
947
+ timeoutMs: STARTUP_SCAN_DEADLINE_MS,
948
+ cwd,
949
+ });
950
+ }
951
+ return result;
952
+ };
935
953
  const [contextFiles, resolvedWorkspaceTree] = await Promise.all([
936
954
  contextFilesPromise,
937
955
  raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
@@ -1093,6 +1111,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1093
1111
  settings,
1094
1112
  authStorage,
1095
1113
  modelRegistry,
1114
+ getTelemetry: () => agent?.telemetry,
1096
1115
  };
1097
1116
 
1098
1117
  // Wire process-wide internal URL singletons owned by their real classes.
@@ -1746,6 +1765,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1746
1765
  },
1747
1766
  intentTracing: !!intentField,
1748
1767
  getToolChoice: () => session?.nextToolChoice(),
1768
+ telemetry: options.telemetry,
1749
1769
  });
1750
1770
 
1751
1771
  cursorEventEmitter = event => agent.emitExternalEvent(event);
@@ -1754,11 +1774,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1754
1774
  if (hasExistingSession) {
1755
1775
  agent.replaceMessages(existingSession.messages);
1756
1776
  } else {
1757
- // Save initial model and thinking level for new sessions so they can be restored on resume
1777
+ // Save initial model, thinking level, and service tier for new sessions so they can be restored on resume.
1758
1778
  if (model) {
1759
1779
  sessionManager.appendModelChange(`${model.provider}/${model.id}`);
1760
1780
  }
1761
1781
  sessionManager.appendThinkingLevelChange(thinkingLevel);
1782
+ if (initialServiceTier) {
1783
+ sessionManager.appendServiceTierChange(initialServiceTier);
1784
+ }
1762
1785
  }
1763
1786
 
1764
1787
  session = new AgentSession({