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

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 (140) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/examples/extensions/plan-mode.ts +0 -1
  3. package/package.json +9 -9
  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/commit/agentic/tools/propose-changelog.ts +8 -1
  14. package/src/commit/analysis/conventional.ts +8 -66
  15. package/src/commit/map-reduce/reduce-phase.ts +6 -65
  16. package/src/commit/pipeline.ts +2 -2
  17. package/src/commit/shared-llm.ts +89 -0
  18. package/src/config/config-file.ts +210 -0
  19. package/src/config/model-equivalence.ts +8 -11
  20. package/src/config/model-registry.ts +13 -2
  21. package/src/config/model-resolver.ts +1 -4
  22. package/src/config/settings-schema.ts +71 -1
  23. package/src/config/settings.ts +1 -1
  24. package/src/config.ts +3 -219
  25. package/src/edit/renderer.ts +7 -1
  26. package/src/eval/js/executor.ts +3 -0
  27. package/src/eval/js/shared/rewrite-imports.ts +2 -2
  28. package/src/eval/py/executor.ts +5 -0
  29. package/src/exa/factory.ts +2 -2
  30. package/src/exa/mcp-client.ts +74 -1
  31. package/src/exec/bash-executor.ts +5 -1
  32. package/src/export/html/template.generated.ts +1 -1
  33. package/src/export/html/template.js +0 -11
  34. package/src/extensibility/extensions/runner.ts +1 -1
  35. package/src/extensibility/extensions/types.ts +89 -223
  36. package/src/extensibility/hooks/types.ts +89 -314
  37. package/src/extensibility/shared-events.ts +343 -0
  38. package/src/extensibility/skills.ts +9 -0
  39. package/src/goals/index.ts +3 -0
  40. package/src/goals/runtime.ts +500 -0
  41. package/src/goals/state.ts +37 -0
  42. package/src/goals/tools/goal-tool.ts +237 -0
  43. package/src/hashline/anchors.ts +2 -2
  44. package/src/hindsight/mental-models.ts +1 -1
  45. package/src/internal-urls/agent-protocol.ts +1 -20
  46. package/src/internal-urls/artifact-protocol.ts +1 -19
  47. package/src/internal-urls/docs-index.generated.ts +5 -6
  48. package/src/internal-urls/registry-helpers.ts +25 -0
  49. package/src/main.ts +11 -2
  50. package/src/mcp/oauth-flow.ts +20 -0
  51. package/src/modes/acp/acp-agent.ts +79 -45
  52. package/src/modes/components/assistant-message.ts +14 -8
  53. package/src/modes/components/bash-execution.ts +24 -63
  54. package/src/modes/components/custom-message.ts +14 -40
  55. package/src/modes/components/eval-execution.ts +27 -57
  56. package/src/modes/components/execution-shared.ts +102 -0
  57. package/src/modes/components/hook-message.ts +17 -49
  58. package/src/modes/components/mcp-add-wizard.ts +26 -5
  59. package/src/modes/components/message-frame.ts +88 -0
  60. package/src/modes/components/model-selector.ts +1 -1
  61. package/src/modes/components/session-observer-overlay.ts +6 -2
  62. package/src/modes/components/session-selector.ts +1 -1
  63. package/src/modes/components/status-line/segments.ts +55 -4
  64. package/src/modes/components/status-line/types.ts +4 -0
  65. package/src/modes/components/status-line.ts +28 -10
  66. package/src/modes/components/tool-execution.ts +7 -8
  67. package/src/modes/controllers/command-controller-shared.ts +108 -0
  68. package/src/modes/controllers/command-controller.ts +13 -4
  69. package/src/modes/controllers/event-controller.ts +36 -7
  70. package/src/modes/controllers/input-controller.ts +13 -0
  71. package/src/modes/controllers/mcp-command-controller.ts +56 -61
  72. package/src/modes/controllers/ssh-command-controller.ts +18 -57
  73. package/src/modes/interactive-mode.ts +624 -52
  74. package/src/modes/print-mode.ts +16 -86
  75. package/src/modes/rpc/rpc-mode.ts +14 -87
  76. package/src/modes/runtime-init.ts +115 -0
  77. package/src/modes/theme/defaults/dark-poimandres.json +2 -0
  78. package/src/modes/theme/defaults/light-poimandres.json +2 -0
  79. package/src/modes/theme/theme.ts +18 -6
  80. package/src/modes/types.ts +14 -3
  81. package/src/modes/utils/context-usage.ts +13 -13
  82. package/src/modes/utils/ui-helpers.ts +10 -3
  83. package/src/plan-mode/approved-plan.ts +35 -1
  84. package/src/prompts/goals/goal-budget-limit.md +16 -0
  85. package/src/prompts/goals/goal-continuation.md +28 -0
  86. package/src/prompts/goals/goal-mode-active.md +23 -0
  87. package/src/prompts/system/plan-mode-active.md +5 -5
  88. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  89. package/src/prompts/tools/bash.md +6 -0
  90. package/src/prompts/tools/goal.md +13 -0
  91. package/src/prompts/tools/hashline.md +102 -114
  92. package/src/prompts/tools/read.md +1 -0
  93. package/src/prompts/tools/resolve.md +6 -5
  94. package/src/sdk.ts +12 -5
  95. package/src/session/agent-session.ts +428 -106
  96. package/src/session/blob-store.ts +36 -3
  97. package/src/session/messages.ts +67 -2
  98. package/src/session/session-manager.ts +131 -12
  99. package/src/session/session-storage.ts +33 -15
  100. package/src/session/streaming-output.ts +309 -13
  101. package/src/slash-commands/builtin-registry.ts +18 -0
  102. package/src/ssh/ssh-executor.ts +5 -0
  103. package/src/system-prompt.ts +4 -2
  104. package/src/task/executor.ts +17 -7
  105. package/src/task/index.ts +3 -0
  106. package/src/task/render.ts +21 -15
  107. package/src/task/types.ts +4 -0
  108. package/src/tools/ast-edit.ts +21 -120
  109. package/src/tools/ast-grep.ts +21 -119
  110. package/src/tools/bash-interactive.ts +9 -1
  111. package/src/tools/bash.ts +27 -4
  112. package/src/tools/browser/attach.ts +3 -3
  113. package/src/tools/browser/launch.ts +81 -18
  114. package/src/tools/browser/registry.ts +1 -5
  115. package/src/tools/browser/tab-supervisor.ts +51 -14
  116. package/src/tools/conflict-detect.ts +15 -4
  117. package/src/tools/eval.ts +3 -1
  118. package/src/tools/find.ts +20 -38
  119. package/src/tools/gh.ts +7 -6
  120. package/src/tools/index.ts +22 -11
  121. package/src/tools/inspect-image.ts +3 -10
  122. package/src/tools/output-meta.ts +176 -37
  123. package/src/tools/path-utils.ts +125 -2
  124. package/src/tools/read.ts +516 -233
  125. package/src/tools/render-utils.ts +92 -0
  126. package/src/tools/renderers.ts +2 -0
  127. package/src/tools/resolve.ts +72 -44
  128. package/src/tools/search.ts +120 -186
  129. package/src/tools/write.ts +44 -9
  130. package/src/utils/file-mentions.ts +1 -1
  131. package/src/utils/image-loading.ts +7 -3
  132. package/src/utils/image-resize.ts +32 -43
  133. package/src/vim/parser.ts +0 -17
  134. package/src/vim/render.ts +1 -1
  135. package/src/vim/types.ts +1 -1
  136. package/src/web/search/providers/gemini.ts +35 -95
  137. package/src/prompts/tools/exit-plan-mode.md +0 -6
  138. package/src/tools/exit-plan-mode.ts +0 -97
  139. package/src/utils/fuzzy.ts +0 -108
  140. package/src/utils/image-convert.ts +0 -27
@@ -0,0 +1,237 @@
1
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
+ import type { Component } from "@oh-my-pi/pi-tui";
3
+ import { Text } from "@oh-my-pi/pi-tui";
4
+ import { formatNumber, prompt } from "@oh-my-pi/pi-utils";
5
+ import { type Static, Type } from "@sinclair/typebox";
6
+ import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
7
+ import type { Theme, ThemeColor } from "../../modes/theme/theme";
8
+ import goalDescription from "../../prompts/tools/goal.md" with { type: "text" };
9
+ import { formatDuration } from "../../slash-commands/helpers/format";
10
+ import type { ToolSession } from "../../tools";
11
+ import { formatErrorMessage, TRUNCATE_LENGTHS } from "../../tools/render-utils";
12
+ import { ToolError } from "../../tools/tool-errors";
13
+ import { renderStatusLine, truncateToWidth } from "../../tui";
14
+ import { completionBudgetReport, remainingTokens } from "../runtime";
15
+ import type { Goal, GoalStatus, GoalToolDetails } from "../state";
16
+
17
+ const goalSchema = Type.Object({
18
+ op: Type.Union([Type.Literal("create"), Type.Literal("get"), Type.Literal("complete")], {
19
+ description: "Goal operation.",
20
+ }),
21
+ objective: Type.Optional(Type.String({ description: "Goal objective. Required when op=create." })),
22
+ token_budget: Type.Optional(
23
+ Type.Integer({
24
+ description: "Optional positive token budget. Only honored when op=create.",
25
+ }),
26
+ ),
27
+ });
28
+
29
+ export type GoalToolInput = Static<typeof goalSchema>;
30
+
31
+ export interface GoalToolResponse {
32
+ goal: Goal | null;
33
+ remainingTokens: number | null;
34
+ completionBudgetReport: string | null;
35
+ }
36
+
37
+ export function buildGoalToolResponse(
38
+ goal: Goal | null | undefined,
39
+ options?: { includeCompletionReport?: boolean },
40
+ ): GoalToolResponse {
41
+ const resolvedGoal = goal ?? null;
42
+ return {
43
+ goal: resolvedGoal,
44
+ remainingTokens: remainingTokens(resolvedGoal),
45
+ completionBudgetReport:
46
+ options?.includeCompletionReport && resolvedGoal?.status === "complete"
47
+ ? completionBudgetReport(resolvedGoal)
48
+ : null,
49
+ };
50
+ }
51
+
52
+ function validateCreateParams(params: GoalToolInput): { objective: string; tokenBudget?: number } {
53
+ const objective = params.objective?.trim();
54
+ if (!objective) {
55
+ throw new ToolError("objective is required when op=create");
56
+ }
57
+ const tokenBudget = params.token_budget;
58
+ if (tokenBudget !== undefined && (!Number.isInteger(tokenBudget) || tokenBudget <= 0)) {
59
+ throw new ToolError("token_budget must be a positive integer when provided");
60
+ }
61
+ return { objective, tokenBudget };
62
+ }
63
+
64
+ export class GoalTool implements AgentTool<typeof goalSchema, GoalToolDetails> {
65
+ readonly name = "goal";
66
+ readonly label = "Goal";
67
+ readonly description = prompt.render(goalDescription);
68
+ readonly parameters = goalSchema;
69
+ readonly strict = true;
70
+ readonly intent = "omit" as const;
71
+ readonly #session: ToolSession;
72
+
73
+ constructor(session: ToolSession) {
74
+ this.#session = session;
75
+ }
76
+
77
+ async execute(
78
+ _toolCallId: string,
79
+ params: GoalToolInput,
80
+ _signal?: AbortSignal,
81
+ _onUpdate?: AgentToolUpdateCallback<GoalToolDetails>,
82
+ _context?: AgentToolContext,
83
+ ): Promise<AgentToolResult<GoalToolDetails>> {
84
+ const runtime = this.#session.getGoalRuntime?.();
85
+ if (!runtime) {
86
+ throw new ToolError("Goal mode is not active.");
87
+ }
88
+
89
+ let response: GoalToolResponse;
90
+ if (params.op === "create") {
91
+ const created = await runtime.createGoal(validateCreateParams(params));
92
+ response = buildGoalToolResponse(created.goal);
93
+ } else if (params.op === "get") {
94
+ const state = this.#session.getGoalModeState?.();
95
+ response = buildGoalToolResponse(state?.enabled ? state.goal : null);
96
+ } else {
97
+ const completed = await runtime.completeGoalFromTool();
98
+ response = buildGoalToolResponse(completed, { includeCompletionReport: true });
99
+ }
100
+ let text: string;
101
+ if (response.goal) {
102
+ text = `Goal: ${response.goal.objective}\nStatus: ${response.goal.status}\nTokens: ${response.goal.tokensUsed} used`;
103
+ if (response.goal.tokenBudget !== undefined) {
104
+ text += ` / ${response.goal.tokenBudget} budget`;
105
+ }
106
+ if (response.remainingTokens !== null) {
107
+ text += `\nRemaining tokens: ${response.remainingTokens}`;
108
+ }
109
+ if (response.completionBudgetReport) {
110
+ text += `\n\n${response.completionBudgetReport}`;
111
+ }
112
+ } else {
113
+ text = "No active goal.";
114
+ }
115
+ return {
116
+ content: [{ type: "text", text }],
117
+ details: {
118
+ op: params.op,
119
+ goal: response.goal,
120
+ remainingTokens: response.remainingTokens,
121
+ completionBudgetReport: response.completionBudgetReport,
122
+ },
123
+ };
124
+ }
125
+ }
126
+
127
+ function describeOp(op: string | undefined): string {
128
+ switch (op) {
129
+ case "create":
130
+ return "set";
131
+ case "complete":
132
+ return "complete";
133
+ case "get":
134
+ return "check";
135
+ default:
136
+ return op ?? "?";
137
+ }
138
+ }
139
+
140
+ function goalBadgeColor(status: GoalStatus): ThemeColor {
141
+ switch (status) {
142
+ case "complete":
143
+ return "success";
144
+ case "budget-limited":
145
+ return "warning";
146
+ case "paused":
147
+ case "dropped":
148
+ return "muted";
149
+ default:
150
+ return "accent";
151
+ }
152
+ }
153
+
154
+ interface GoalRenderArgs {
155
+ op?: GoalToolInput["op"];
156
+ objective?: string;
157
+ token_budget?: number;
158
+ }
159
+
160
+ export const goalToolRenderer = {
161
+ renderCall(args: GoalRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
162
+ const description = describeOp(args.op);
163
+ const meta: string[] = [];
164
+ const trimmedObjective = args.objective?.trim();
165
+ if (args.op === "create" && trimmedObjective) {
166
+ const objective = truncateToWidth(trimmedObjective, TRUNCATE_LENGTHS.TITLE);
167
+ meta.push(uiTheme.italic(uiTheme.fg("muted", `"${objective}"`)));
168
+ }
169
+ if (args.op === "create" && args.token_budget !== undefined) {
170
+ meta.push(`budget ${formatNumber(args.token_budget)}`);
171
+ }
172
+ const text = renderStatusLine({ icon: "pending", title: "Goal", description, meta }, uiTheme);
173
+ return new Text(text, 0, 0);
174
+ },
175
+
176
+ renderResult(
177
+ result: { content: Array<{ type: string; text?: string }>; details?: GoalToolDetails; isError?: boolean },
178
+ _options: RenderResultOptions,
179
+ uiTheme: Theme,
180
+ args?: GoalRenderArgs,
181
+ ): Component {
182
+ const fallbackText = result.content?.find(c => c.type === "text")?.text ?? "";
183
+ const details = result.details;
184
+ const op = details?.op ?? args?.op;
185
+ const description = describeOp(op);
186
+
187
+ if (result.isError) {
188
+ const header = renderStatusLine({ icon: "error", title: "Goal", description }, uiTheme);
189
+ const body = formatErrorMessage(fallbackText || "Goal tool failed", uiTheme);
190
+ return new Text([header, body].join("\n"), 0, 0);
191
+ }
192
+
193
+ const goal = details?.goal ?? null;
194
+ if (!goal) {
195
+ const header = renderStatusLine({ icon: "warning", title: "Goal", description }, uiTheme);
196
+ const body = uiTheme.fg("muted", "No active goal.");
197
+ return new Text([header, body].join("\n"), 0, 0);
198
+ }
199
+
200
+ const lines: string[] = [];
201
+ lines.push(
202
+ renderStatusLine(
203
+ {
204
+ icon: "success",
205
+ title: "Goal",
206
+ description,
207
+ badge: { label: goal.status, color: goalBadgeColor(goal.status) },
208
+ },
209
+ uiTheme,
210
+ ),
211
+ );
212
+
213
+ const objectiveText = truncateToWidth(goal.objective.trim(), TRUNCATE_LENGTHS.LONG);
214
+ lines.push(` ${uiTheme.italic(uiTheme.fg("muted", `"${objectiveText}"`))}`);
215
+
216
+ const used = formatNumber(goal.tokensUsed);
217
+ const tokensLine =
218
+ goal.tokenBudget !== undefined
219
+ ? `${used} / ${formatNumber(goal.tokenBudget)} tokens (${formatNumber(Math.max(0, goal.tokenBudget - goal.tokensUsed))} left)`
220
+ : `${used} tokens`;
221
+ lines.push(` ${uiTheme.fg("dim", tokensLine)}`);
222
+
223
+ if (goal.timeUsedSeconds > 0) {
224
+ lines.push(` ${uiTheme.fg("dim", `${formatDuration(goal.timeUsedSeconds * 1000)} elapsed`)}`);
225
+ }
226
+
227
+ const report = details?.completionBudgetReport;
228
+ if (report) {
229
+ lines.push("");
230
+ lines.push(uiTheme.italic(uiTheme.fg("muted", report)));
231
+ }
232
+
233
+ return new Text(lines.join("\n"), 0, 0);
234
+ },
235
+
236
+ mergeCallAndResult: true,
237
+ };
@@ -63,9 +63,9 @@ export class HashlineMismatchError extends Error {
63
63
  }
64
64
 
65
65
  private static rejectionHeader(mismatches: HashMismatch[]): string[] {
66
- const noun = mismatches.length > 1 ? "lines have" : "line has";
66
+ const noun = mismatches.length > 1 ? "anchors do" : "anchor does";
67
67
  return [
68
- `Edit rejected: ${mismatches.length} ${noun} changed since the last read (marked *).`,
68
+ `Edit rejected: ${mismatches.length} ${noun} not match the current file (marked *).`,
69
69
  "The edit was NOT applied, please use the updated file content shown below, and issue another edit tool-call.",
70
70
  ];
71
71
  }
@@ -379,4 +379,4 @@ export const MENTAL_MODEL_FIRST_TURN_DEADLINE_MS = 1500;
379
379
  export const MENTAL_MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
380
380
 
381
381
  /** Need-only export of the raw seed list for tests. */
382
- export const __builtinSeedsForTest: ReadonlyArray<Readonly<RawSeed>> = BUILTIN_SEEDS;
382
+ export const builtinSeedsForTest: ReadonlyArray<Readonly<RawSeed>> = BUILTIN_SEEDS;
@@ -14,29 +14,10 @@
14
14
  import * as fs from "node:fs/promises";
15
15
  import * as path from "node:path";
16
16
  import { isEnoent } from "@oh-my-pi/pi-utils";
17
- import { AgentRegistry } from "../registry/agent-registry";
18
17
  import { applyQuery, pathToQuery } from "./json-query";
18
+ import { artifactsDirsFromRegistry } from "./registry-helpers";
19
19
  import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
20
20
 
21
- /**
22
- * Snapshot of artifacts dirs for every registered session, deduped.
23
- *
24
- * Prefers `sessionManager.getArtifactsDir()` because subagents adopt the
25
- * parent's manager and report the parent's dir there; dedup then collapses
26
- * the whole agent tree to one entry. Falls back to the raw session file
27
- * when no live session reference is attached.
28
- */
29
- function artifactsDirsFromRegistry(): string[] {
30
- const dirs: string[] = [];
31
- for (const ref of AgentRegistry.global().list()) {
32
- const dir =
33
- ref.session?.sessionManager.getArtifactsDir() ?? (ref.sessionFile ? ref.sessionFile.slice(0, -6) : null);
34
- if (!dir) continue;
35
- if (!dirs.includes(dir)) dirs.push(dir);
36
- }
37
- return dirs;
38
- }
39
-
40
21
  /**
41
22
  * Handler for agent:// URLs.
42
23
  *
@@ -12,27 +12,9 @@
12
12
  import * as fs from "node:fs/promises";
13
13
  import * as path from "node:path";
14
14
  import { isEnoent } from "@oh-my-pi/pi-utils";
15
- import { AgentRegistry } from "../registry/agent-registry";
15
+ import { artifactsDirsFromRegistry } from "./registry-helpers";
16
16
  import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
17
17
 
18
- /**
19
- * Snapshot of artifacts dirs across all registered sessions, deduped.
20
- *
21
- * Subagents adopt their parent's `ArtifactManager`, so their
22
- * `sessionManager.getArtifactsDir()` returns the parent's dir; dedup
23
- * collapses parent + N subagents to a single entry.
24
- */
25
- function artifactsDirsFromRegistry(): string[] {
26
- const dirs: string[] = [];
27
- for (const ref of AgentRegistry.global().list()) {
28
- const dir =
29
- ref.session?.sessionManager.getArtifactsDir() ?? (ref.sessionFile ? ref.sessionFile.slice(0, -6) : null);
30
- if (!dir) continue;
31
- if (!dirs.includes(dir)) dirs.push(dir);
32
- }
33
- return dirs;
34
- }
35
-
36
18
  export class ArtifactProtocolHandler implements ProtocolHandler {
37
19
  readonly scheme = "artifact";
38
20
  readonly immutable = true;