@oh-my-pi/pi-coding-agent 15.0.0 → 15.0.2

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 (165) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/examples/extensions/plan-mode.ts +0 -1
  3. package/package.json +10 -10
  4. package/scripts/build-binary.ts +5 -0
  5. package/src/autoresearch/helpers.ts +17 -0
  6. package/src/autoresearch/tools/log-experiment.ts +9 -17
  7. package/src/autoresearch/tools/run-experiment.ts +2 -17
  8. package/src/capability/skill.ts +7 -0
  9. package/src/cli/list-models.ts +1 -1
  10. package/src/cli/shell-cli.ts +3 -13
  11. package/src/cli/update-cli.ts +1 -1
  12. package/src/cli.ts +10 -29
  13. package/src/commands/commit.ts +10 -0
  14. package/src/commit/agentic/tools/propose-changelog.ts +8 -1
  15. package/src/commit/analysis/conventional.ts +8 -66
  16. package/src/commit/map-reduce/reduce-phase.ts +6 -65
  17. package/src/commit/pipeline.ts +2 -2
  18. package/src/commit/shared-llm.ts +89 -0
  19. package/src/config/config-file.ts +210 -0
  20. package/src/config/model-equivalence.ts +8 -11
  21. package/src/config/model-registry.ts +44 -3
  22. package/src/config/model-resolver.ts +1 -4
  23. package/src/config/settings-schema.ts +82 -1
  24. package/src/config/settings.ts +1 -1
  25. package/src/config.ts +3 -219
  26. package/src/discovery/claude-plugins.ts +19 -7
  27. package/src/edit/renderer.ts +7 -1
  28. package/src/eval/js/executor.ts +3 -0
  29. package/src/eval/js/shared/rewrite-imports.ts +2 -2
  30. package/src/eval/py/executor.ts +5 -0
  31. package/src/eval/py/runner.py +42 -11
  32. package/src/eval/py/runtime.ts +1 -0
  33. package/src/exa/factory.ts +2 -2
  34. package/src/exa/mcp-client.ts +74 -1
  35. package/src/exec/bash-executor.ts +5 -1
  36. package/src/export/html/template.generated.ts +1 -1
  37. package/src/export/html/template.js +0 -11
  38. package/src/extensibility/extensions/get-commands-handler.ts +77 -0
  39. package/src/extensibility/extensions/runner.ts +1 -1
  40. package/src/extensibility/extensions/types.ts +89 -223
  41. package/src/extensibility/hooks/types.ts +89 -314
  42. package/src/extensibility/plugins/legacy-pi-compat.ts +48 -31
  43. package/src/extensibility/shared-events.ts +343 -0
  44. package/src/extensibility/skills.ts +9 -0
  45. package/src/goals/index.ts +3 -0
  46. package/src/goals/runtime.ts +500 -0
  47. package/src/goals/state.ts +37 -0
  48. package/src/goals/tools/goal-tool.ts +237 -0
  49. package/src/hashline/anchors.ts +2 -2
  50. package/src/hashline/input.ts +2 -1
  51. package/src/hashline/parser.ts +27 -3
  52. package/src/hindsight/mental-models.ts +1 -1
  53. package/src/internal-urls/agent-protocol.ts +1 -20
  54. package/src/internal-urls/artifact-protocol.ts +1 -19
  55. package/src/internal-urls/docs-index.generated.ts +11 -12
  56. package/src/internal-urls/registry-helpers.ts +25 -0
  57. package/src/internal-urls/router.ts +8 -0
  58. package/src/internal-urls/types.ts +21 -0
  59. package/src/lsp/config.ts +15 -6
  60. package/src/lsp/defaults.json +6 -2
  61. package/src/main.ts +11 -2
  62. package/src/mcp/oauth-flow.ts +20 -0
  63. package/src/modes/acp/acp-agent.ts +327 -95
  64. package/src/modes/components/assistant-message.ts +14 -8
  65. package/src/modes/components/bash-execution.ts +24 -63
  66. package/src/modes/components/custom-message.ts +14 -40
  67. package/src/modes/components/eval-execution.ts +27 -57
  68. package/src/modes/components/execution-shared.ts +102 -0
  69. package/src/modes/components/hook-message.ts +17 -49
  70. package/src/modes/components/mcp-add-wizard.ts +26 -5
  71. package/src/modes/components/message-frame.ts +88 -0
  72. package/src/modes/components/model-selector.ts +1 -1
  73. package/src/modes/components/session-observer-overlay.ts +6 -2
  74. package/src/modes/components/session-selector.ts +1 -1
  75. package/src/modes/components/status-line/segments.ts +93 -8
  76. package/src/modes/components/status-line/types.ts +4 -0
  77. package/src/modes/components/status-line.ts +28 -10
  78. package/src/modes/components/tool-execution.ts +7 -8
  79. package/src/modes/controllers/command-controller-shared.ts +108 -0
  80. package/src/modes/controllers/command-controller.ts +13 -4
  81. package/src/modes/controllers/event-controller.ts +36 -7
  82. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  83. package/src/modes/controllers/input-controller.ts +13 -0
  84. package/src/modes/controllers/mcp-command-controller.ts +56 -61
  85. package/src/modes/controllers/ssh-command-controller.ts +18 -57
  86. package/src/modes/interactive-mode.ts +624 -52
  87. package/src/modes/print-mode.ts +16 -86
  88. package/src/modes/rpc/host-uris.ts +235 -0
  89. package/src/modes/rpc/rpc-mode.ts +41 -88
  90. package/src/modes/rpc/rpc-types.ts +57 -0
  91. package/src/modes/runtime-init.ts +116 -0
  92. package/src/modes/theme/defaults/dark-poimandres.json +3 -0
  93. package/src/modes/theme/defaults/light-poimandres.json +3 -0
  94. package/src/modes/theme/theme.ts +24 -6
  95. package/src/modes/types.ts +14 -3
  96. package/src/modes/utils/context-usage.ts +13 -13
  97. package/src/modes/utils/ui-helpers.ts +10 -3
  98. package/src/plan-mode/approved-plan.ts +35 -1
  99. package/src/prompts/goals/goal-budget-limit.md +16 -0
  100. package/src/prompts/goals/goal-continuation.md +28 -0
  101. package/src/prompts/goals/goal-mode-active.md +23 -0
  102. package/src/prompts/system/plan-mode-active.md +5 -5
  103. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  104. package/src/prompts/tools/bash.md +6 -0
  105. package/src/prompts/tools/github.md +4 -4
  106. package/src/prompts/tools/goal.md +13 -0
  107. package/src/prompts/tools/hashline.md +101 -117
  108. package/src/prompts/tools/read.md +55 -36
  109. package/src/prompts/tools/resolve.md +6 -5
  110. package/src/sdk.ts +12 -5
  111. package/src/session/agent-session.ts +428 -106
  112. package/src/session/blob-store.ts +36 -3
  113. package/src/session/messages.ts +67 -2
  114. package/src/session/session-manager.ts +131 -12
  115. package/src/session/session-storage.ts +33 -15
  116. package/src/session/streaming-output.ts +309 -13
  117. package/src/slash-commands/builtin-registry.ts +18 -0
  118. package/src/ssh/ssh-executor.ts +5 -0
  119. package/src/system-prompt.ts +4 -2
  120. package/src/task/discovery.ts +5 -2
  121. package/src/task/executor.ts +19 -8
  122. package/src/task/index.ts +3 -0
  123. package/src/task/render.ts +21 -15
  124. package/src/task/types.ts +4 -0
  125. package/src/tools/ast-edit.ts +21 -120
  126. package/src/tools/ast-grep.ts +21 -119
  127. package/src/tools/bash-command-fixup.ts +47 -0
  128. package/src/tools/bash-interactive.ts +9 -1
  129. package/src/tools/bash.ts +66 -19
  130. package/src/tools/browser/attach.ts +3 -3
  131. package/src/tools/browser/launch.ts +81 -18
  132. package/src/tools/browser/registry.ts +1 -5
  133. package/src/tools/browser/render.ts +2 -2
  134. package/src/tools/browser/tab-supervisor.ts +51 -14
  135. package/src/tools/conflict-detect.ts +15 -4
  136. package/src/tools/eval.ts +12 -2
  137. package/src/tools/find.ts +20 -38
  138. package/src/tools/gh.ts +44 -10
  139. package/src/tools/index.ts +22 -11
  140. package/src/tools/inspect-image.ts +3 -10
  141. package/src/tools/job.ts +16 -7
  142. package/src/tools/output-meta.ts +202 -37
  143. package/src/tools/path-utils.ts +125 -2
  144. package/src/tools/read.ts +548 -237
  145. package/src/tools/render-utils.ts +92 -0
  146. package/src/tools/renderers.ts +2 -0
  147. package/src/tools/resolve.ts +72 -44
  148. package/src/tools/search.ts +120 -186
  149. package/src/tools/ssh.ts +3 -2
  150. package/src/tools/write.ts +64 -9
  151. package/src/utils/file-mentions.ts +1 -1
  152. package/src/utils/image-loading.ts +7 -3
  153. package/src/utils/image-resize.ts +32 -43
  154. package/src/vim/parser.ts +0 -17
  155. package/src/vim/render.ts +1 -1
  156. package/src/vim/types.ts +1 -1
  157. package/src/web/search/providers/anthropic.ts +5 -0
  158. package/src/web/search/providers/exa.ts +3 -0
  159. package/src/web/search/providers/gemini.ts +40 -95
  160. package/src/web/search/providers/jina.ts +5 -2
  161. package/src/web/search/providers/zai.ts +5 -2
  162. package/src/prompts/tools/exit-plan-mode.md +0 -6
  163. package/src/tools/exit-plan-mode.ts +0 -97
  164. package/src/utils/fuzzy.ts +0 -108
  165. package/src/utils/image-convert.ts +0 -27
@@ -9,10 +9,12 @@ import * as os from "node:os";
9
9
  import * as path from "node:path";
10
10
  import type { ToolCallContext } from "@oh-my-pi/pi-agent-core";
11
11
  import type { Ellipsis } from "@oh-my-pi/pi-natives";
12
+ import type { Component } from "@oh-my-pi/pi-tui";
12
13
  import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
13
14
  import { pluralize } from "@oh-my-pi/pi-utils";
14
15
  import { settings } from "../config/settings";
15
16
  import type { Theme } from "../modes/theme/theme";
17
+ import { Hasher } from "../tui/utils";
16
18
  import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
17
19
 
18
20
  export { Ellipsis } from "@oh-my-pi/pi-natives";
@@ -642,6 +644,96 @@ export function formatParseErrors(errors: string[]): string[] {
642
644
  return [header, ...capped.map(err => `- ${err}`)];
643
645
  }
644
646
 
647
+ // =============================================================================
648
+ // Renderer helpers shared by search / find / ast tools
649
+ // =============================================================================
650
+
651
+ /**
652
+ * Group `rawLines` by blank-line separators, mirroring the historical search /
653
+ * ast-grep / ast-edit renderer behavior: if any blank line is present, splits on
654
+ * runs of blank lines; otherwise collapses non-empty lines into a single group.
655
+ */
656
+ export function splitGroupsByBlankLine(rawLines: string[]): string[][] {
657
+ const hasSeparators = rawLines.some(line => line.trim().length === 0);
658
+ const groups: string[][] = [];
659
+ if (hasSeparators) {
660
+ let current: string[] = [];
661
+ for (const line of rawLines) {
662
+ if (line.trim().length === 0) {
663
+ if (current.length > 0) {
664
+ groups.push(current);
665
+ current = [];
666
+ }
667
+ continue;
668
+ }
669
+ current.push(line);
670
+ }
671
+ if (current.length > 0) groups.push(current);
672
+ } else {
673
+ const nonEmpty = rawLines.filter(line => line.trim().length > 0);
674
+ if (nonEmpty.length > 0) {
675
+ groups.push(nonEmpty);
676
+ }
677
+ }
678
+ return groups;
679
+ }
680
+
681
+ /**
682
+ * Standard width+expand keyed render cache used by every search-style tool
683
+ * renderer. `compute` re-runs only when the cache key changes; the returned
684
+ * Component is the canonical `{ render, invalidate }` pair.
685
+ */
686
+ export function createCachedComponent(
687
+ getExpanded: () => boolean,
688
+ compute: (width: number, expanded: boolean) => string[],
689
+ ): Component {
690
+ let cached: { key: bigint; lines: string[] } | undefined;
691
+ return {
692
+ render(width: number): string[] {
693
+ const expanded = getExpanded();
694
+ const key = new Hasher().bool(expanded).u32(width).digest();
695
+ if (cached?.key === key) return cached.lines;
696
+ const lines = compute(width, expanded);
697
+ cached = { key, lines };
698
+ return lines;
699
+ },
700
+ invalidate() {
701
+ cached = undefined;
702
+ },
703
+ };
704
+ }
705
+
706
+ /**
707
+ * Append the indented bullet list of parse errors (capped at
708
+ * {@link PARSE_ERRORS_LIMIT}) to `lines`, with an overflow summary line if the
709
+ * total exceeds the cap. No-op when `parseErrors` is empty.
710
+ */
711
+ export function appendParseErrorsBulletList(
712
+ lines: string[],
713
+ parseErrors: readonly string[] | undefined,
714
+ theme: Theme,
715
+ ): void {
716
+ if (!parseErrors || parseErrors.length === 0) return;
717
+ const capped = parseErrors.slice(0, PARSE_ERRORS_LIMIT);
718
+ for (const err of capped) {
719
+ lines.push(theme.fg("warning", ` - ${err}`));
720
+ }
721
+ if (parseErrors.length > PARSE_ERRORS_LIMIT) {
722
+ lines.push(theme.fg("dim", ` … ${parseErrors.length - PARSE_ERRORS_LIMIT} more`));
723
+ }
724
+ }
725
+
726
+ /**
727
+ * Human-readable summary string for the parse-issues count, capped by
728
+ * {@link PARSE_ERRORS_LIMIT}.
729
+ */
730
+ export function formatParseErrorsCountLabel(parseErrors: readonly string[]): string {
731
+ const total = parseErrors.length;
732
+ return total > PARSE_ERRORS_LIMIT
733
+ ? `${PARSE_ERRORS_LIMIT} / ${total} parse issues`
734
+ : `${total} parse issue${total !== 1 ? "s" : ""}`;
735
+ }
736
+
645
737
  // =============================================================================
646
738
  // LSP Batching
647
739
  // =============================================================================
@@ -6,6 +6,7 @@
6
6
  import type { Component } from "@oh-my-pi/pi-tui";
7
7
  import { editToolRenderer } from "../edit/renderer";
8
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
+ import { goalToolRenderer } from "../goals/tools/goal-tool";
9
10
  import { lspToolRenderer } from "../lsp/render";
10
11
  import type { Theme } from "../modes/theme/theme";
11
12
  import { taskToolRenderer } from "../task/render";
@@ -68,6 +69,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
68
69
  task: taskToolRenderer as ToolRenderer,
69
70
  todo_write: todoWriteToolRenderer as ToolRenderer,
70
71
  github: githubToolRenderer as ToolRenderer,
72
+ goal: goalToolRenderer as ToolRenderer,
71
73
  web_search: webSearchToolRenderer as ToolRenderer,
72
74
  write: writeToolRenderer as ToolRenderer,
73
75
  };
@@ -14,6 +14,12 @@ import { ToolError } from "./tool-errors";
14
14
  const resolveSchema = Type.Object({
15
15
  action: Type.Union([Type.Literal("apply"), Type.Literal("discard")]),
16
16
  reason: Type.String({ description: "reason for action", examples: ["approved by user"] }),
17
+ extra: Type.Optional(
18
+ Type.Record(Type.String(), Type.Unknown(), {
19
+ description:
20
+ 'Free-form metadata interpreted by the resolving tool (e.g. plan-mode approval requires `{ title: "<PLAN_TITLE>" }`).',
21
+ }),
22
+ ),
17
23
  });
18
24
 
19
25
  type ResolveParams = Static<typeof resolveSchema>;
@@ -21,17 +27,12 @@ type ResolveParams = Static<typeof resolveSchema>;
21
27
  export interface ResolveToolDetails {
22
28
  action: "apply" | "discard";
23
29
  reason: string;
30
+ extra?: Record<string, unknown>;
24
31
  sourceToolName?: string;
25
32
  label?: string;
26
33
  sourceResultDetails?: unknown;
27
34
  }
28
35
 
29
- function resolveReasonPreview(reason?: string): string | undefined {
30
- const trimmed = reason?.trim();
31
- if (!trimmed) return undefined;
32
- return truncateToWidth(trimmed, 72, Ellipsis.Omit);
33
- }
34
-
35
36
  /**
36
37
  * Queue a resolve-protocol handler on the tool-choice queue. Forces the next
37
38
  * LLM call to invoke the hidden `resolve` tool, wraps the caller's apply/reject
@@ -47,49 +48,25 @@ export function queueResolveHandler(
47
48
  options: {
48
49
  label: string;
49
50
  sourceToolName: string;
50
- apply(reason: string): Promise<AgentToolResult<unknown>>;
51
- reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
51
+ apply(reason: string, extra?: Record<string, unknown>): Promise<AgentToolResult<unknown>>;
52
+ reject?(reason: string, extra?: Record<string, unknown>): Promise<AgentToolResult<unknown> | undefined>;
52
53
  },
53
54
  ): void {
54
55
  const queue = session.getToolChoiceQueue?.();
55
56
  const forced = session.buildToolChoice?.("resolve");
56
57
  if (!queue || !forced || typeof forced === "string") return;
57
58
 
58
- const detailsFor = (params: ResolveParams): ResolveToolDetails => ({
59
- action: params.action,
60
- reason: params.reason,
61
- sourceToolName: options.sourceToolName,
62
- label: options.label,
63
- });
64
-
65
59
  queue.pushOnce(forced, {
66
60
  label: `pending-action:${options.sourceToolName}`,
67
61
  now: true,
68
62
  onRejected: () => "requeue",
69
- onInvoked: async (input: unknown) => {
70
- const params = input as ResolveParams;
71
- const withResolveDetails = (result: AgentToolResult<unknown>): AgentToolResult<ResolveToolDetails> => ({
72
- ...result,
73
- details: {
74
- ...detailsFor(params),
75
- ...(result.details != null ? { sourceResultDetails: result.details } : {}),
76
- },
77
- });
78
- if (params.action === "apply") {
79
- const result = await options.apply(params.reason);
80
- return withResolveDetails(result);
81
- }
82
- if (params.action === "discard" && options.reject != null) {
83
- const result = await options.reject(params.reason);
84
- if (result != null) {
85
- return withResolveDetails(result);
86
- }
87
- }
88
- return {
89
- content: [{ type: "text" as const, text: `Discarded: ${options.label}. Reason: ${params.reason}` }],
90
- details: detailsFor(params),
91
- };
92
- },
63
+ onInvoked: async (input: unknown) =>
64
+ runResolveInvocation(input as ResolveParams, {
65
+ sourceToolName: options.sourceToolName,
66
+ label: options.label,
67
+ apply: options.apply,
68
+ reject: options.reject,
69
+ }),
93
70
  });
94
71
 
95
72
  session.steer?.({
@@ -103,6 +80,57 @@ export function queueResolveHandler(
103
80
  });
104
81
  }
105
82
 
83
+ /**
84
+ * Shared invocation runner used by both queued (in-flight) handlers and
85
+ * standing handlers (e.g. plan-mode approval). Discriminates on action,
86
+ * routes through the caller's apply/reject, and wraps the resulting tool
87
+ * payload with `ResolveToolDetails` so the renderer and event-controller
88
+ * see a consistent shape.
89
+ */
90
+ export async function runResolveInvocation(
91
+ params: ResolveParams,
92
+ options: {
93
+ sourceToolName: string;
94
+ label: string;
95
+ apply(reason: string, extra?: Record<string, unknown>): Promise<AgentToolResult<unknown>>;
96
+ reject?(reason: string, extra?: Record<string, unknown>): Promise<AgentToolResult<unknown> | undefined>;
97
+ },
98
+ ): Promise<AgentToolResult<ResolveToolDetails>> {
99
+ const baseDetails: ResolveToolDetails = {
100
+ action: params.action,
101
+ reason: params.reason,
102
+ sourceToolName: options.sourceToolName,
103
+ label: options.label,
104
+ ...(params.extra != null ? { extra: params.extra } : {}),
105
+ };
106
+ if (params.action === "apply") {
107
+ const result = await options.apply(params.reason, params.extra);
108
+ return {
109
+ ...result,
110
+ details: {
111
+ ...baseDetails,
112
+ ...(result.details != null ? { sourceResultDetails: result.details } : {}),
113
+ },
114
+ };
115
+ }
116
+ if (params.action === "discard" && options.reject != null) {
117
+ const result = await options.reject(params.reason, params.extra);
118
+ if (result != null) {
119
+ return {
120
+ ...result,
121
+ details: {
122
+ ...baseDetails,
123
+ ...(result.details != null ? { sourceResultDetails: result.details } : {}),
124
+ },
125
+ };
126
+ }
127
+ }
128
+ return {
129
+ content: [{ type: "text" as const, text: `Discarded: ${options.label}. Reason: ${params.reason}` }],
130
+ details: baseDetails,
131
+ };
132
+ }
133
+
106
134
  export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolDetails> {
107
135
  readonly name = "resolve";
108
136
  readonly label = "Resolve";
@@ -112,10 +140,9 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
112
140
  readonly strict = true;
113
141
  readonly intent = (args: Partial<ResolveParams>) => {
114
142
  if (args.action === "discard") {
115
- return args.reason ? `discarding: ${args.reason}` : "aiscarding changes";
116
- } else {
117
- return args.reason ? `accepting: ${args.reason}` : "accepting changes";
143
+ return args.reason ? `discarding: ${args.reason}` : "discarding changes";
118
144
  }
145
+ return args.reason ? `accepting: ${args.reason}` : "accepting changes";
119
146
  };
120
147
 
121
148
  constructor(private readonly session: ToolSession) {
@@ -130,7 +157,7 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
130
157
  _context?: AgentToolContext,
131
158
  ): Promise<AgentToolResult<ResolveToolDetails>> {
132
159
  return untilAborted(signal, async () => {
133
- const invoker = this.session.peekQueueInvoker?.();
160
+ const invoker = this.session.peekQueueInvoker?.() ?? this.session.peekStandingResolveHandler?.();
134
161
  if (!invoker) {
135
162
  throw new ToolError("No pending action to resolve. Nothing to apply or discard.");
136
163
  }
@@ -142,7 +169,8 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
142
169
 
143
170
  export const resolveToolRenderer = {
144
171
  renderCall(args: ResolveParams, _options: RenderResultOptions, uiTheme: Theme): Component {
145
- const reason = resolveReasonPreview(args.reason);
172
+ const reasonTrimmed = args.reason?.trim();
173
+ const reason = reasonTrimmed ? truncateToWidth(reasonTrimmed, 72, Ellipsis.Omit) : undefined;
146
174
  const text = renderStatusLine(
147
175
  {
148
176
  icon: "pending",