@oh-my-pi/pi-coding-agent 17.2.1 → 17.2.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 (114) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/{CHANGELOG-dj46zzrm.md → CHANGELOG-xfpkakrn.md} +47 -0
  3. package/dist/cli.js +3891 -3885
  4. package/dist/types/config/keybindings.d.ts +6 -1
  5. package/dist/types/config/settings-schema.d.ts +16 -5
  6. package/dist/types/edit/hashline/block-resolver.d.ts +5 -5
  7. package/dist/types/edit/hashline/diff.d.ts +4 -4
  8. package/dist/types/eval/executor-base.d.ts +27 -0
  9. package/dist/types/eval/js/context-manager.d.ts +7 -0
  10. package/dist/types/eval/js/executor.d.ts +2 -0
  11. package/dist/types/extensibility/extensions/runner.d.ts +64 -5
  12. package/dist/types/extensibility/extensions/types.d.ts +15 -0
  13. package/dist/types/extensibility/extensions/wrapper.d.ts +1 -1
  14. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  15. package/dist/types/live/visualizer.d.ts +3 -1
  16. package/dist/types/modes/acp/acp-agent.d.ts +3 -3
  17. package/dist/types/modes/components/keybinding-hints.d.ts +1 -1
  18. package/dist/types/modes/controllers/mcp-command-controller.d.ts +13 -0
  19. package/dist/types/modes/interactive-mode.d.ts +1 -0
  20. package/dist/types/modes/theme/theme.d.ts +1 -1
  21. package/dist/types/modes/types.d.ts +2 -0
  22. package/dist/types/secrets/index.d.ts +22 -0
  23. package/dist/types/secrets/obfuscator.d.ts +1 -1
  24. package/dist/types/session/agent-session-types.d.ts +4 -0
  25. package/dist/types/session/agent-session.d.ts +9 -0
  26. package/dist/types/session/codex-auto-reset.d.ts +182 -64
  27. package/dist/types/session/session-storage.d.ts +5 -2
  28. package/dist/types/session/session-tools.d.ts +2 -1
  29. package/dist/types/session/turn-recovery.d.ts +7 -1
  30. package/dist/types/system-prompt.d.ts +8 -0
  31. package/dist/types/task/executor.d.ts +27 -0
  32. package/dist/types/task/persisted-revive.d.ts +7 -0
  33. package/dist/types/tools/bash-interceptor.d.ts +1 -1
  34. package/dist/types/tools/browser/attach.d.ts +9 -1
  35. package/dist/types/tools/browser/launch.d.ts +20 -1
  36. package/dist/types/tools/browser/registry.d.ts +2 -0
  37. package/dist/types/tools/browser/tab-protocol.d.ts +8 -0
  38. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -0
  39. package/dist/types/tools/browser/tab-worker.d.ts +3 -1
  40. package/dist/types/tools/shell-tokenize.d.ts +11 -0
  41. package/dist/types/utils/image-loading.d.ts +2 -0
  42. package/dist/types/web/search/providers/duckduckgo.d.ts +9 -0
  43. package/dist/types/web/search/types.d.ts +2 -2
  44. package/package.json +12 -12
  45. package/src/commit/agentic/index.ts +2 -1
  46. package/src/commit/pipeline.ts +3 -2
  47. package/src/config/keybindings.ts +6 -1
  48. package/src/config/settings-schema.ts +16 -3
  49. package/src/edit/hashline/block-resolver.ts +5 -5
  50. package/src/edit/hashline/diff.ts +22 -15
  51. package/src/edit/hashline/execute.ts +14 -13
  52. package/src/edit/renderer.ts +3 -3
  53. package/src/eval/executor-base.ts +39 -0
  54. package/src/eval/jl/executor.ts +13 -2
  55. package/src/eval/js/context-manager.ts +97 -14
  56. package/src/eval/js/executor.ts +3 -0
  57. package/src/eval/js/index.ts +1 -0
  58. package/src/eval/py/executor.ts +55 -15
  59. package/src/eval/rb/executor.ts +8 -1
  60. package/src/extensibility/extensions/runner.ts +121 -4
  61. package/src/extensibility/extensions/types.ts +15 -0
  62. package/src/extensibility/extensions/wrapper.ts +19 -2
  63. package/src/extensibility/legacy-pi-coding-agent-shim.ts +5 -5
  64. package/src/live/visualizer.ts +8 -1
  65. package/src/lsp/index.ts +51 -11
  66. package/src/main.ts +3 -0
  67. package/src/modes/acp/acp-agent.ts +16 -5
  68. package/src/modes/components/assistant-message.ts +5 -10
  69. package/src/modes/components/custom-editor.ts +1 -1
  70. package/src/modes/components/keybinding-hints.ts +4 -13
  71. package/src/modes/components/status-line/segments.ts +4 -5
  72. package/src/modes/components/tool-execution.ts +4 -5
  73. package/src/modes/controllers/input-controller.ts +18 -40
  74. package/src/modes/controllers/live-command-controller.ts +1 -0
  75. package/src/modes/controllers/mcp-command-controller.ts +28 -11
  76. package/src/modes/interactive-mode.ts +59 -2
  77. package/src/modes/theme/theme.ts +13 -6
  78. package/src/modes/types.ts +2 -0
  79. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  80. package/src/prompts/system/plan-mode-active.md +5 -5
  81. package/src/sdk.ts +47 -16
  82. package/src/secrets/index.ts +83 -3
  83. package/src/secrets/obfuscator.ts +70 -15
  84. package/src/session/agent-session-types.ts +7 -1
  85. package/src/session/agent-session.ts +393 -112
  86. package/src/session/codex-auto-reset.ts +594 -123
  87. package/src/session/eval-runner.ts +2 -0
  88. package/src/session/messages.ts +1 -1
  89. package/src/session/session-maintenance.ts +48 -1
  90. package/src/session/session-manager.ts +4 -3
  91. package/src/session/session-stats.ts +14 -3
  92. package/src/session/session-storage.ts +47 -15
  93. package/src/session/session-tools.ts +30 -2
  94. package/src/session/turn-recovery.ts +15 -2
  95. package/src/slash-commands/builtin-registry.ts +21 -15
  96. package/src/slash-commands/helpers/reset-usage.ts +2 -0
  97. package/src/system-prompt.ts +13 -1
  98. package/src/task/executor.ts +168 -15
  99. package/src/task/persisted-revive.ts +26 -1
  100. package/src/telemetry-export.ts +8 -4
  101. package/src/tools/bash-interceptor.ts +85 -8
  102. package/src/tools/bash.ts +1 -1
  103. package/src/tools/browser/attach.ts +35 -10
  104. package/src/tools/browser/launch.ts +60 -11
  105. package/src/tools/browser/registry.ts +18 -2
  106. package/src/tools/browser/tab-protocol.ts +8 -0
  107. package/src/tools/browser/tab-supervisor.ts +31 -4
  108. package/src/tools/browser/tab-worker.ts +32 -2
  109. package/src/tools/output-meta.ts +16 -1
  110. package/src/tools/shell-tokenize.ts +101 -0
  111. package/src/utils/image-loading.ts +8 -3
  112. package/src/utils/shell-snapshot.ts +31 -21
  113. package/src/web/search/providers/duckduckgo.ts +183 -15
  114. package/src/web/search/types.ts +6 -1
@@ -2,8 +2,8 @@ import type { AuthStorage } from "@oh-my-pi/pi-ai";
2
2
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
3
3
  import { SearchProviderError } from "../../../web/search/types";
4
4
  import type { QuerySyntax } from "../query";
5
- import { formatScraperQuery } from "../query";
6
- import { clampNumResults } from "../utils";
5
+ import { formatScraperQuery, parseSearchQuery } from "../query";
6
+ import { clampNumResults, dateToAgeSeconds } from "../utils";
7
7
  import type { SearchParams } from "./base";
8
8
  import { SearchProvider } from "./base";
9
9
  import { browserFetch } from "./browser-page";
@@ -35,6 +35,8 @@ interface ParsedResult {
35
35
  title: string;
36
36
  url: string;
37
37
  snippet?: string;
38
+ /** ISO timestamp lifted from the result row, when DDG emits one. */
39
+ publishedDate?: string;
38
40
  }
39
41
 
40
42
  /**
@@ -81,6 +83,23 @@ function unwrapResultUrl(href: string): string | undefined {
81
83
  return undefined;
82
84
  }
83
85
 
86
+ /**
87
+ * Lift a result row's publication timestamp from DDG markup. Recent DDG HTML
88
+ * renders it as a bare `<span>&nbsp; &nbsp; 2026-07-30T20:19:00.0000000</span>`
89
+ * inside `result__extras__url`. Restrict the scan to that container so a
90
+ * date-shaped value in a `<span class="result__snippet">` is not attributed
91
+ * as publication metadata.
92
+ */
93
+ function extractPublishedDate(block: string): string | undefined {
94
+ const extrasUrl = /<div\b[^>]*\bclass="[^"]*\bresult__extras__url\b[^"]*"[^>]*>([\s\S]*?)<\/div>/i.exec(block)?.[1];
95
+ if (!extrasUrl) return undefined;
96
+ for (const match of extrasUrl.matchAll(/<span\b[^>]*>([\s\S]*?)<\/span>/gi)) {
97
+ const text = decodeHtmlText(match[1]);
98
+ if (/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}|$)/.test(text)) return text;
99
+ }
100
+ return undefined;
101
+ }
102
+
84
103
  /**
85
104
  * Walk the HTML page and pull out result blocks in document order.
86
105
  *
@@ -106,10 +125,34 @@ function parseHtmlResults(html: string): ParsedResult[] {
106
125
  if (!titleText) continue;
107
126
  const snippet = snippetRe.exec(block);
108
127
  const snippetText = snippet ? decodeHtmlText(snippet[1]) : undefined;
109
- results.push({ title: titleText, url, snippet: snippetText || undefined });
128
+ results.push({
129
+ title: titleText,
130
+ url,
131
+ snippet: snippetText || undefined,
132
+ publishedDate: extractPublishedDate(block),
133
+ });
110
134
  }
111
135
  return results;
112
136
  }
137
+ /**
138
+ * Extract the hidden fields from DDG's next-page form.
139
+ *
140
+ * Attribute order varies across responses, so each input tag is parsed
141
+ * independently instead of matching one fixed HTML layout.
142
+ */
143
+ function parseContinuationForm(html: string): URLSearchParams | undefined {
144
+ for (const formMatch of html.matchAll(/<form\b[^>]*>([\s\S]*?)<\/form>/gi)) {
145
+ const form = new URLSearchParams();
146
+ for (const inputMatch of formMatch[1].matchAll(/<input\b[^>]*>/gi)) {
147
+ const input = inputMatch[0];
148
+ const name = /\bname\s*=\s*(["'])(.*?)\1/i.exec(input)?.[2];
149
+ const value = /\bvalue\s*=\s*(["'])(.*?)\1/i.exec(input)?.[2];
150
+ if (name && value !== undefined) form.append(decodeHtmlText(name), decodeHtmlText(value));
151
+ }
152
+ if (form.has("s") && form.has("vqd")) return form;
153
+ }
154
+ return undefined;
155
+ }
113
156
 
114
157
  /**
115
158
  * `true` when the page DDG returned is the bot-challenge modal instead of
@@ -137,19 +180,131 @@ const DDG_QUERY_SYNTAX: QuerySyntax = {
137
180
  filetype: true,
138
181
  };
139
182
 
140
- async function callDuckDuckGoHtml(params: SearchParams): Promise<string> {
183
+ /**
184
+ * DuckDuckGo's documented `kl` values.
185
+ *
186
+ * The codes resemble `region-language` locales but contain provider-specific
187
+ * identifiers (`jp-jp`, `tw-tzh`, `uk-en`) that cannot be derived mechanically.
188
+ */
189
+ const DDG_KL_CODES = new Set([
190
+ "xa-ar",
191
+ "xa-en",
192
+ "ar-es",
193
+ "au-en",
194
+ "at-de",
195
+ "be-fr",
196
+ "be-nl",
197
+ "br-pt",
198
+ "bg-bg",
199
+ "ca-en",
200
+ "ca-fr",
201
+ "ct-ca",
202
+ "cl-es",
203
+ "cn-zh",
204
+ "co-es",
205
+ "hr-hr",
206
+ "cz-cs",
207
+ "dk-da",
208
+ "ee-et",
209
+ "fi-fi",
210
+ "fr-fr",
211
+ "de-de",
212
+ "gr-el",
213
+ "hk-tzh",
214
+ "hu-hu",
215
+ "in-en",
216
+ "id-id",
217
+ "id-en",
218
+ "ie-en",
219
+ "il-he",
220
+ "it-it",
221
+ "jp-jp",
222
+ "kr-kr",
223
+ "lv-lv",
224
+ "lt-lt",
225
+ "xl-es",
226
+ "my-ms",
227
+ "my-en",
228
+ "mx-es",
229
+ "nl-nl",
230
+ "nz-en",
231
+ "no-no",
232
+ "pe-es",
233
+ "ph-en",
234
+ "ph-tl",
235
+ "pl-pl",
236
+ "pt-pt",
237
+ "ro-ro",
238
+ "ru-ru",
239
+ "sg-en",
240
+ "sk-sk",
241
+ "sl-sl",
242
+ "za-en",
243
+ "es-es",
244
+ "se-sv",
245
+ "ch-de",
246
+ "ch-fr",
247
+ "ch-it",
248
+ "tw-tzh",
249
+ "th-th",
250
+ "tr-tr",
251
+ "ua-uk",
252
+ "uk-en",
253
+ "us-en",
254
+ "ue-es",
255
+ "ve-es",
256
+ "vn-vi",
257
+ "wt-wt",
258
+ ]);
259
+
260
+ /** BCP 47 locales whose DDG code does not follow a simple component swap. */
261
+ const DDG_LOCALE_ALIASES: Record<string, string> = {
262
+ "ca-es": "ct-ca",
263
+ "en-gb": "uk-en",
264
+ "es-419": "xl-es",
265
+ "es-us": "ue-es",
266
+ "ja-jp": "jp-jp",
267
+ "ko-kr": "kr-kr",
268
+ "zh-hk": "hk-tzh",
269
+ "zh-tw": "tw-tzh",
270
+ };
271
+
272
+ /**
273
+ * Map a parsed `lang:` locale onto DuckDuckGo's documented `kl` values.
274
+ *
275
+ * Shared queries use `language-region` order while DDG generally uses
276
+ * `region-language`. Provider-specific exceptions resolve through
277
+ * {@link DDG_LOCALE_ALIASES}; all other values must survive the documented
278
+ * allowlist after swapping or the caller keeps its default region.
279
+ */
280
+ export function localeToKl(lang: string | undefined): string | undefined {
281
+ if (!lang) return undefined;
282
+ const locale = lang.toLowerCase().replaceAll("_", "-");
283
+ const alias = DDG_LOCALE_ALIASES[locale];
284
+ if (alias) return alias;
285
+ const match = /^([a-z]{2})-([a-z]{2})$/.exec(locale);
286
+ if (!match) return undefined;
287
+ const candidate = `${match[2]}-${match[1]}`;
288
+ return DDG_KL_CODES.has(candidate) ? candidate : undefined;
289
+ }
290
+
291
+ function createDuckDuckGoForm(params: SearchParams): URLSearchParams {
292
+ const parsed = params.parsedQuery ?? parseSearchQuery(params.query);
141
293
  const form = new URLSearchParams({
142
- q: formatScraperQuery(params.query, params.parsedQuery, DDG_QUERY_SYNTAX),
143
- kl: "us-en",
294
+ q: formatScraperQuery(params.query, parsed, DDG_QUERY_SYNTAX),
295
+ kl: localeToKl(parsed.lang) ?? "us-en",
144
296
  });
145
297
  const df = params.recency ? RECENCY_TO_DDG_DF[params.recency] : undefined;
146
298
  if (df) form.set("df", df);
147
299
  // Add b: "" parameter as specified in the browser fetch template to match real browser form submission
148
300
  form.set("b", "");
301
+ return form;
302
+ }
149
303
 
304
+ async function callDuckDuckGoHtml(params: SearchParams, form: URLSearchParams, signal: AbortSignal): Promise<string> {
150
305
  const page = await browserFetch(DUCKDUCKGO_HTML_URL, {
151
306
  fetch: params.fetch ?? fetch,
152
- signal: withHardTimeout(params.signal),
307
+ signal,
153
308
  referer: "https://html.duckduckgo.com/",
154
309
  init: {
155
310
  method: "POST",
@@ -179,16 +334,29 @@ async function callDuckDuckGoHtml(params: SearchParams): Promise<string> {
179
334
  /** Execute a DuckDuckGo web search via the no-JS HTML frontend. */
180
335
  export async function searchDuckDuckGo(params: SearchParams): Promise<SearchResponse> {
181
336
  const numResults = clampNumResults(params.numSearchResults ?? params.limit, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
182
- const html = await callDuckDuckGoHtml(params);
183
- const parsed = parseHtmlResults(html);
184
-
337
+ const signal = withHardTimeout(params.signal);
185
338
  const sources: SearchSource[] = [];
186
339
  const seen = new Set<string>();
187
- for (const result of parsed) {
188
- if (seen.has(result.url)) continue;
189
- seen.add(result.url);
190
- sources.push({ title: result.title, url: result.url, snippet: result.snippet });
191
- if (sources.length >= numResults) break;
340
+ let form: URLSearchParams | undefined = createDuckDuckGoForm(params);
341
+
342
+ while (form && sources.length < numResults) {
343
+ const html = await callDuckDuckGoHtml(params, form, signal);
344
+ const sourceCount = sources.length;
345
+ for (const result of parseHtmlResults(html)) {
346
+ if (seen.has(result.url)) continue;
347
+ seen.add(result.url);
348
+ sources.push({
349
+ title: result.title,
350
+ url: result.url,
351
+ snippet: result.snippet,
352
+ publishedDate: result.publishedDate,
353
+ ageSeconds: dateToAgeSeconds(result.publishedDate),
354
+ });
355
+ if (sources.length >= numResults) break;
356
+ }
357
+
358
+ if (sources.length === sourceCount) break;
359
+ form = parseContinuationForm(html);
192
360
  }
193
361
 
194
362
  return { provider: "duckduckgo", sources };
@@ -30,7 +30,12 @@ export const SEARCH_PROVIDER_OPTIONS = [
30
30
  label: "OpenAI",
31
31
  description: "OpenAI's native web_search (uses ChatGPT OAuth via /login openai-codex)",
32
32
  },
33
- { value: "xai", label: "xAI", description: "Grok web search via xAI Responses API (requires XAI_API_KEY)" },
33
+ {
34
+ value: "xai",
35
+ label: "xAI",
36
+ description:
37
+ "Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)",
38
+ },
34
39
  { value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
35
40
  { value: "exa", label: "Exa", description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP" },
36
41
  { value: "tinyfish", label: "TinyFish", description: "Requires TINYFISH_API_KEY" },