@oh-my-pi/pi-coding-agent 16.4.2 → 16.4.3

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 (122) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/cli.js +17514 -17799
  3. package/dist/types/advisor/config.d.ts +4 -3
  4. package/dist/types/commit/agentic/agent.d.ts +1 -0
  5. package/dist/types/config/settings-schema.d.ts +28 -0
  6. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +10 -22
  7. package/dist/types/lsp/deferred-diagnostics.d.ts +11 -0
  8. package/dist/types/modes/components/move-overlay.d.ts +1 -1
  9. package/dist/types/modes/components/status-line/component.d.ts +3 -0
  10. package/dist/types/modes/components/status-line/types.d.ts +3 -0
  11. package/dist/types/modes/interactive-mode.d.ts +8 -0
  12. package/dist/types/modes/types.d.ts +2 -0
  13. package/dist/types/session/agent-session.d.ts +16 -0
  14. package/dist/types/task/executor.d.ts +31 -0
  15. package/dist/types/tools/__tests__/vibe-render.test.d.ts +1 -0
  16. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -1
  17. package/dist/types/tools/browser/cmux/rpc.d.ts +20 -0
  18. package/dist/types/tools/browser/run-output.d.ts +25 -0
  19. package/dist/types/tools/browser/tab-worker.d.ts +16 -0
  20. package/dist/types/tools/index.d.ts +1 -0
  21. package/dist/types/tools/vibe.d.ts +161 -0
  22. package/dist/types/utils/changelog.d.ts +35 -1
  23. package/dist/types/vibe/runtime.d.ts +124 -0
  24. package/dist/types/vibe/state.d.ts +4 -0
  25. package/dist/types/web/search/provider.d.ts +2 -0
  26. package/dist/types/web/search/providers/bing.d.ts +14 -0
  27. package/dist/types/web/search/providers/browser-headers.d.ts +9 -0
  28. package/dist/types/web/search/providers/browser-page.d.ts +32 -0
  29. package/dist/types/web/search/providers/ecosia.d.ts +14 -0
  30. package/dist/types/web/search/providers/google.d.ts +13 -0
  31. package/dist/types/web/search/providers/mojeek.d.ts +14 -0
  32. package/dist/types/web/search/providers/public.d.ts +37 -0
  33. package/dist/types/web/search/providers/startpage.d.ts +14 -0
  34. package/dist/types/web/search/providers/yahoo.d.ts +14 -0
  35. package/dist/types/web/search/types.d.ts +28 -0
  36. package/package.json +15 -18
  37. package/scripts/build-binary.ts +56 -73
  38. package/scripts/bundle-dist.ts +36 -40
  39. package/scripts/compile-binary.ts +68 -0
  40. package/scripts/generate-docs-index.ts +3 -93
  41. package/scripts/legacy-pi-virtual-module.ts +192 -0
  42. package/src/advisor/__tests__/advisor.test.ts +13 -0
  43. package/src/advisor/__tests__/config.test.ts +36 -0
  44. package/src/advisor/config.ts +11 -8
  45. package/src/commit/agentic/agent.ts +4 -0
  46. package/src/commit/agentic/index.ts +46 -21
  47. package/src/edit/index.ts +10 -76
  48. package/src/exec/non-interactive-env.ts +0 -1
  49. package/src/extensibility/plugins/legacy-pi-compat.ts +328 -162
  50. package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
  51. package/src/internal-urls/docs-index.ts +2 -1
  52. package/src/internal-urls/skill-protocol.ts +1 -1
  53. package/src/lsp/deferred-diagnostics.ts +66 -0
  54. package/src/main.ts +13 -13
  55. package/src/mcp/transports/stdio.test.ts +45 -1
  56. package/src/mcp/transports/stdio.ts +6 -3
  57. package/src/modes/acp/acp-agent.ts +65 -1
  58. package/src/modes/acp/acp-event-mapper.ts +5 -0
  59. package/src/modes/acp/acp-mode.ts +11 -0
  60. package/src/modes/components/__tests__/move-overlay.test.ts +16 -1
  61. package/src/modes/components/advisor-config.ts +15 -7
  62. package/src/modes/components/move-overlay.ts +2 -3
  63. package/src/modes/components/status-line/component.ts +6 -0
  64. package/src/modes/components/status-line/segments.ts +6 -0
  65. package/src/modes/components/status-line/types.ts +3 -0
  66. package/src/modes/controllers/command-controller.ts +8 -10
  67. package/src/modes/interactive-mode.ts +117 -1
  68. package/src/modes/types.ts +2 -0
  69. package/src/prompts/system/eager-task.md +2 -2
  70. package/src/prompts/system/system-prompt.md +9 -2
  71. package/src/prompts/system/vibe-mode-active.md +23 -0
  72. package/src/prompts/tools/browser.md +3 -3
  73. package/src/prompts/tools/grep.md +1 -1
  74. package/src/prompts/tools/vibe-kill.md +3 -0
  75. package/src/prompts/tools/vibe-list.md +3 -0
  76. package/src/prompts/tools/vibe-send.md +9 -0
  77. package/src/prompts/tools/vibe-spawn.md +10 -0
  78. package/src/prompts/tools/vibe-turn-result.md +19 -0
  79. package/src/prompts/tools/vibe-wait.md +8 -0
  80. package/src/sdk.ts +5 -0
  81. package/src/session/agent-session.ts +103 -13
  82. package/src/session/snapcompact-inline.ts +3 -19
  83. package/src/slash-commands/builtin-registry.ts +24 -8
  84. package/src/task/agents.ts +0 -2
  85. package/src/task/executor.ts +105 -0
  86. package/src/tools/__tests__/vibe-render.test.ts +210 -0
  87. package/src/tools/bash-skill-urls.ts +1 -1
  88. package/src/tools/browser/cmux/cmux-tab.ts +46 -48
  89. package/src/tools/browser/cmux/rpc.ts +50 -0
  90. package/src/tools/browser/run-output.ts +76 -0
  91. package/src/tools/browser/tab-worker.ts +264 -129
  92. package/src/tools/glob.ts +20 -6
  93. package/src/tools/index.ts +1 -0
  94. package/src/tools/read.ts +17 -9
  95. package/src/tools/renderers.ts +6 -0
  96. package/src/tools/vibe.ts +608 -0
  97. package/src/tools/write.ts +15 -2
  98. package/src/utils/changelog.ts +106 -9
  99. package/src/utils/git.ts +0 -1
  100. package/src/utils/title-generator.ts +70 -7
  101. package/src/vibe/runtime.ts +710 -0
  102. package/src/vibe/state.ts +4 -0
  103. package/src/web/search/index.ts +14 -6
  104. package/src/web/search/provider.ts +37 -1
  105. package/src/web/search/providers/bing.ts +197 -0
  106. package/src/web/search/providers/browser-headers.ts +92 -0
  107. package/src/web/search/providers/browser-page.ts +123 -0
  108. package/src/web/search/providers/duckduckgo.ts +13 -34
  109. package/src/web/search/providers/ecosia.ts +178 -0
  110. package/src/web/search/providers/google.ts +193 -0
  111. package/src/web/search/providers/mojeek.ts +206 -0
  112. package/src/web/search/providers/public.ts +201 -0
  113. package/src/web/search/providers/startpage.ts +213 -0
  114. package/src/web/search/providers/yahoo.ts +179 -0
  115. package/src/web/search/types.ts +35 -0
  116. package/dist/types/extensibility/plugins/legacy-pi-bundled-keys.d.ts +0 -10
  117. package/dist/types/extensibility/plugins/legacy-pi-bundled-registry.d.ts +0 -10
  118. package/scripts/generate-legacy-pi-bundled-registry.ts +0 -420
  119. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +0 -1011
  120. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +0 -3430
  121. package/src/internal-urls/docs-index.generated.txt +0 -2
  122. package/src/prompts/agents/plan.md +0 -47
@@ -7,6 +7,27 @@ export interface ChangelogEntry {
7
7
  content: string;
8
8
  }
9
9
 
10
+ /** Number of changelog releases shown by automatic and default recent views. */
11
+ export const RECENT_CHANGELOG_ENTRY_LIMIT = 3;
12
+ /** Maximum Markdown source bytes allowed in automatic startup release notes. */
13
+ export const STARTUP_CHANGELOG_MAX_BYTES = 64 * 1024;
14
+ /** Hint appended when automatic startup release notes are truncated. */
15
+ export const STARTUP_CHANGELOG_FULL_HINT = "Use `/changelog full` to view the complete changelog.";
16
+
17
+ /** Markdown generated from selected changelog entries and whether it hit a size cap. */
18
+ export interface RenderedChangelog {
19
+ markdown: string;
20
+ truncated: boolean;
21
+ }
22
+
23
+ /** Automatic startup changelog decision, including whether the marker should advance. */
24
+ export interface StartupChangelogSelection {
25
+ markdown: string | undefined;
26
+ persistCurrentVersion: boolean;
27
+ truncated: boolean;
28
+ selectedEntries: number;
29
+ }
30
+
10
31
  /**
11
32
  * Parse changelog entries from the file at `changelogPath`. Scans for `## [x.y.z]`
12
33
  * headings and collects each block until the next heading or EOF.
@@ -87,19 +108,95 @@ export function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number
87
108
  }
88
109
 
89
110
  /**
90
- * Get entries newer than lastVersion
111
+ * Parse an omp changelog marker version into comparable parts.
91
112
  */
92
- export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {
93
- // Parse lastVersion
94
- const parts = lastVersion.split(".").map(Number);
95
- const last: ChangelogEntry = {
96
- major: parts[0] || 0,
97
- minor: parts[1] || 0,
98
- patch: parts[2] || 0,
113
+ export function parseChangelogVersion(version: string | undefined): ChangelogEntry | undefined {
114
+ const match = version?.match(/^(\d+)\.(\d+)\.(\d+)$/);
115
+ if (!match) {
116
+ return undefined;
117
+ }
118
+
119
+ return {
120
+ major: Number.parseInt(match[1], 10),
121
+ minor: Number.parseInt(match[2], 10),
122
+ patch: Number.parseInt(match[3], 10),
99
123
  content: "",
100
124
  };
125
+ }
126
+
127
+ /**
128
+ * Get entries newer than lastVersion.
129
+ */
130
+ export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {
131
+ const parsedLastVersion = parseChangelogVersion(lastVersion);
132
+ if (!parsedLastVersion) {
133
+ return [];
134
+ }
135
+
136
+ return entries.filter(entry => compareVersions(entry, parsedLastVersion) > 0);
137
+ }
101
138
 
102
- return entries.filter(entry => compareVersions(entry, last) > 0);
139
+ /**
140
+ * Render changelog entries oldest-first by default and optionally cap the Markdown source size.
141
+ */
142
+ export function renderChangelogEntries(
143
+ entries: ChangelogEntry[],
144
+ options: { maxBytes?: number; truncationHint?: string; oldestFirst?: boolean } = {},
145
+ ): RenderedChangelog {
146
+ const orderedEntries = options.oldestFirst === false ? entries : [...entries].reverse();
147
+ const markdown = orderedEntries.map(entry => entry.content).join("\n\n");
148
+ if (options.maxBytes === undefined || Buffer.byteLength(markdown) <= options.maxBytes) {
149
+ return { markdown, truncated: false };
150
+ }
151
+
152
+ const suffix = `\n\n…\n\n${options.truncationHint ?? STARTUP_CHANGELOG_FULL_HINT}`;
153
+ let low = 0;
154
+ let high = markdown.length;
155
+ while (low < high) {
156
+ const middle = Math.floor((low + high + 1) / 2);
157
+ if (Buffer.byteLength(markdown.slice(0, middle) + suffix) <= options.maxBytes) {
158
+ low = middle;
159
+ } else {
160
+ high = middle - 1;
161
+ }
162
+ }
163
+
164
+ return { markdown: markdown.slice(0, low) + suffix, truncated: true };
165
+ }
166
+
167
+ /**
168
+ * Select bounded release notes for interactive startup.
169
+ */
170
+ export function selectStartupChangelog(
171
+ entries: ChangelogEntry[],
172
+ lastVersion: string | undefined,
173
+ currentVersion: string,
174
+ ): StartupChangelogSelection {
175
+ const parsedLastVersion = parseChangelogVersion(lastVersion);
176
+ if (!parsedLastVersion) {
177
+ return { markdown: undefined, persistCurrentVersion: true, truncated: false, selectedEntries: 0 };
178
+ }
179
+ const markerVersion = lastVersion ?? "";
180
+ if (markerVersion === currentVersion) {
181
+ return { markdown: undefined, persistCurrentVersion: false, truncated: false, selectedEntries: 0 };
182
+ }
183
+
184
+ const newEntries = getNewEntries(entries, markerVersion).slice(0, RECENT_CHANGELOG_ENTRY_LIMIT);
185
+ if (newEntries.length === 0) {
186
+ return { markdown: undefined, persistCurrentVersion: false, truncated: false, selectedEntries: 0 };
187
+ }
188
+
189
+ const rendered = renderChangelogEntries(newEntries, {
190
+ maxBytes: STARTUP_CHANGELOG_MAX_BYTES,
191
+ truncationHint: STARTUP_CHANGELOG_FULL_HINT,
192
+ oldestFirst: false,
193
+ });
194
+ return {
195
+ markdown: rendered.markdown,
196
+ persistCurrentVersion: true,
197
+ truncated: rendered.truncated,
198
+ selectedEntries: newEntries.length,
199
+ };
103
200
  }
104
201
 
105
202
  // Re-export getChangelogPath from paths.ts for convenience
package/src/utils/git.ts CHANGED
@@ -197,7 +197,6 @@ const GIT_NON_INTERACTIVE_ENV = {
197
197
  GIT_ASKPASS: "true",
198
198
  GIT_EDITOR: "true",
199
199
  GIT_TERMINAL_PROMPT: "0",
200
- GPG_TTY: "not a tty",
201
200
  SSH_ASKPASS: "/usr/bin/false",
202
201
  } satisfies Record<string, string>;
203
202
  const GH_NON_INTERACTIVE_ENV = {
@@ -4,6 +4,7 @@
4
4
  import * as path from "node:path";
5
5
 
6
6
  import { type Api, type AssistantMessage, completeSimple, type Model } from "@oh-my-pi/pi-ai";
7
+ import { StreamMarkupHealing } from "@oh-my-pi/pi-ai/utils/stream-markup-healing";
7
8
  import { isTerminalHeadless, logger, prompt } from "@oh-my-pi/pi-utils";
8
9
  import type { ModelRegistry } from "../config/model-registry";
9
10
 
@@ -32,7 +33,12 @@ const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
32
33
  const TITLE_MAX_TOKENS = 1024;
33
34
 
34
35
  /** Matches the title the model wraps in `<title>...</title>`. */
35
- const TITLE_MARKER_RE = /<title>([\s\S]*?)<\/title>/i;
36
+ const TITLE_MARKER_GLOBAL_RE = /<title>([\s\S]*?)<\/title>/gi;
37
+ const TITLE_VISIBILITY_SENTINEL = "\uE000omp-title-visible\uE000";
38
+ const THINKING_TAG_ENVELOPE_RE = /<(think|thinking|reasoning)>\s*[\s\S]*?<\/\1>/gi;
39
+ const THINKING_FENCE_ENVELOPE_RE = /```(?:thinking|reasoning)\b[\s\S]*?```/gi;
40
+ const LEADING_THINKING_TAG_RE = /^\s*<(think|thinking|reasoning)>\s*[\s\S]*?<\/\1>\s*/i;
41
+ const LEADING_THINKING_FENCE_RE = /^\s*```(?:thinking|reasoning)\b[\s\S]*?```\s*/i;
36
42
 
37
43
  function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel?: Model<Api>): Model<Api> | undefined {
38
44
  const availableModels = registry.getAvailable();
@@ -241,12 +247,69 @@ function extractGeneratedTitle(contentBlocks: AssistantMessage["content"]): stri
241
247
  textTitle += content.text;
242
248
  }
243
249
  }
244
- // Stay lenient: prefer the marker when the model closed it, otherwise
245
- // accept a plain sentence after stripping any stray/unclosed tag fragment
246
- // (e.g. output truncated before the closing tag).
247
- const marker = TITLE_MARKER_RE.exec(textTitle);
248
- const candidate = marker ? marker[1].trim() : textTitle.replace(/<\/?title>/gi, "").trim();
249
- return unwrapJsonTitle(candidate);
250
+ // Stay lenient: prefer the first closed title marker in visible text, then
251
+ // fall back to a plain sentence after stripping only known leading leaked
252
+ // thinking envelopes plus any stray/unclosed title tag fragment.
253
+ const markedTitle = extractVisibleMarkedTitle(textTitle);
254
+ const cleanedTextTitle =
255
+ markedTitle ??
256
+ stripLeadingLeakedThinkingMarkup(textTitle)
257
+ .replace(/<\/?title>/gi, "")
258
+ .trim();
259
+ return unwrapJsonTitle(cleanedTextTitle);
260
+ }
261
+
262
+ function extractVisibleMarkedTitle(text: string): string | undefined {
263
+ TITLE_MARKER_GLOBAL_RE.lastIndex = 0;
264
+ let marker: RegExpExecArray | null = TITLE_MARKER_GLOBAL_RE.exec(text);
265
+ while (marker !== null) {
266
+ const title = marker[1];
267
+ if (title !== undefined && isVisibleTitleMarker(text, marker.index)) return title.trim();
268
+ marker = TITLE_MARKER_GLOBAL_RE.exec(text);
269
+ }
270
+ return undefined;
271
+ }
272
+
273
+ function isVisibleTitleMarker(text: string, markerIndex: number): boolean {
274
+ if (isInsideKnownThinkingEnvelope(text, markerIndex)) return false;
275
+ return stripLeakedThinkingMarkup(`${text.slice(0, markerIndex)}${TITLE_VISIBILITY_SENTINEL}`).endsWith(
276
+ TITLE_VISIBILITY_SENTINEL,
277
+ );
278
+ }
279
+
280
+ function isInsideKnownThinkingEnvelope(text: string, index: number): boolean {
281
+ return (
282
+ isInsideEnvelopeMatchedBy(THINKING_TAG_ENVELOPE_RE, text, index) ||
283
+ isInsideEnvelopeMatchedBy(THINKING_FENCE_ENVELOPE_RE, text, index)
284
+ );
285
+ }
286
+
287
+ function isInsideEnvelopeMatchedBy(pattern: RegExp, text: string, index: number): boolean {
288
+ pattern.lastIndex = 0;
289
+ let marker = pattern.exec(text);
290
+ while (marker !== null) {
291
+ const start = marker.index;
292
+ const end = start + marker[0].length;
293
+ if (index > start && index < end) return true;
294
+ if (start > index) return false;
295
+ marker = pattern.exec(text);
296
+ }
297
+ return false;
298
+ }
299
+
300
+ function stripLeadingLeakedThinkingMarkup(text: string): string {
301
+ let current = text;
302
+ while (true) {
303
+ const withoutTag = current.replace(LEADING_THINKING_TAG_RE, "");
304
+ const withoutFence = withoutTag.replace(LEADING_THINKING_FENCE_RE, "");
305
+ if (withoutFence === current) return current;
306
+ current = withoutFence;
307
+ }
308
+ }
309
+
310
+ function stripLeakedThinkingMarkup(text: string): string {
311
+ const healer = new StreamMarkupHealing({ pattern: "thinking" });
312
+ return healer.feed(text) + healer.flushPending();
250
313
  }
251
314
 
252
315
  /**