@oh-my-pi/pi-coding-agent 15.13.3 → 16.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 (93) hide show
  1. package/CHANGELOG.md +155 -133
  2. package/dist/cli.js +621 -530
  3. package/dist/types/advisor/__tests__/advisor.test.d.ts +1 -0
  4. package/dist/types/advisor/advise-tool.d.ts +58 -0
  5. package/dist/types/advisor/index.d.ts +3 -0
  6. package/dist/types/advisor/runtime.d.ts +52 -0
  7. package/dist/types/advisor/watchdog.d.ts +5 -0
  8. package/dist/types/config/model-roles.d.ts +1 -1
  9. package/dist/types/config/settings-schema.d.ts +66 -5
  10. package/dist/types/discovery/helpers.d.ts +7 -0
  11. package/dist/types/eval/__tests__/prelude-agent.test.d.ts +1 -0
  12. package/dist/types/extensibility/plugins/runtime-config.d.ts +3 -0
  13. package/dist/types/modes/components/advisor-message.d.ts +9 -0
  14. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  15. package/dist/types/modes/controllers/command-controller.d.ts +3 -1
  16. package/dist/types/modes/interactive-mode.d.ts +3 -1
  17. package/dist/types/modes/types.d.ts +8 -1
  18. package/dist/types/sdk.d.ts +3 -3
  19. package/dist/types/session/agent-session.d.ts +81 -2
  20. package/dist/types/session/session-history-format.d.ts +4 -0
  21. package/dist/types/session/session-manager.d.ts +4 -1
  22. package/dist/types/session/yield-queue.d.ts +2 -0
  23. package/dist/types/task/index.d.ts +21 -0
  24. package/dist/types/tools/github-cache.d.ts +5 -4
  25. package/dist/types/tools/job.d.ts +1 -0
  26. package/dist/types/tools/path-utils.d.ts +1 -0
  27. package/dist/types/tools/report-tool-issue.d.ts +0 -1
  28. package/dist/types/web/search/index.d.ts +2 -2
  29. package/dist/types/web/search/provider.d.ts +2 -0
  30. package/package.json +13 -13
  31. package/src/advisor/__tests__/advisor.test.ts +586 -0
  32. package/src/advisor/advise-tool.ts +87 -0
  33. package/src/advisor/index.ts +3 -0
  34. package/src/advisor/runtime.ts +248 -0
  35. package/src/advisor/watchdog.ts +83 -0
  36. package/src/cli/args.ts +1 -0
  37. package/src/collab/host.ts +1 -1
  38. package/src/config/model-roles.ts +13 -1
  39. package/src/config/settings-schema.ts +65 -6
  40. package/src/discovery/claude-plugins.ts +3 -42
  41. package/src/discovery/github.ts +101 -6
  42. package/src/discovery/helpers.ts +11 -0
  43. package/src/eval/__tests__/prelude-agent.test.ts +73 -0
  44. package/src/eval/js/shared/prelude.txt +12 -3
  45. package/src/eval/py/prelude.py +26 -2
  46. package/src/extensibility/custom-commands/bundled/review/index.ts +289 -80
  47. package/src/extensibility/plugins/loader.ts +3 -2
  48. package/src/extensibility/plugins/manager.ts +4 -3
  49. package/src/extensibility/plugins/marketplace/fetcher.ts +32 -34
  50. package/src/extensibility/plugins/runtime-config.ts +9 -0
  51. package/src/internal-urls/docs-index.generated.ts +10 -9
  52. package/src/internal-urls/issue-pr-protocol.ts +8 -4
  53. package/src/main.ts +9 -1
  54. package/src/modes/acp/acp-agent.ts +3 -3
  55. package/src/modes/components/advisor-message.ts +99 -0
  56. package/src/modes/components/agent-hub.ts +7 -0
  57. package/src/modes/components/assistant-message.ts +86 -0
  58. package/src/modes/components/settings-defs.ts +7 -0
  59. package/src/modes/components/status-line/segments.ts +20 -7
  60. package/src/modes/components/tips.txt +1 -1
  61. package/src/modes/controllers/command-controller.ts +69 -2
  62. package/src/modes/controllers/extension-ui-controller.ts +4 -3
  63. package/src/modes/controllers/input-controller.ts +1 -0
  64. package/src/modes/controllers/selector-controller.ts +7 -0
  65. package/src/modes/interactive-mode.ts +59 -2
  66. package/src/modes/rpc/rpc-mode.ts +3 -3
  67. package/src/modes/runtime-init.ts +2 -1
  68. package/src/modes/types.ts +8 -1
  69. package/src/modes/utils/ui-helpers.ts +9 -0
  70. package/src/prompts/advisor/advise-tool.md +1 -0
  71. package/src/prompts/advisor/system.md +31 -0
  72. package/src/prompts/agents/designer.md +8 -0
  73. package/src/prompts/review-request.md +1 -1
  74. package/src/prompts/system/subagent-system-prompt.md +4 -1
  75. package/src/prompts/tools/eval.md +13 -3
  76. package/src/prompts/tools/irc.md +1 -1
  77. package/src/sdk.ts +61 -14
  78. package/src/session/agent-session.ts +667 -13
  79. package/src/session/session-dump-format.ts +15 -131
  80. package/src/session/session-history-format.ts +30 -11
  81. package/src/session/session-manager.ts +3 -1
  82. package/src/session/yield-queue.ts +5 -1
  83. package/src/slash-commands/builtin-registry.ts +105 -4
  84. package/src/system-prompt.ts +1 -1
  85. package/src/task/executor.ts +5 -4
  86. package/src/task/index.ts +70 -9
  87. package/src/tools/github-cache.ts +32 -7
  88. package/src/tools/job.ts +14 -1
  89. package/src/tools/path-utils.ts +33 -2
  90. package/src/tools/report-tool-issue.ts +2 -7
  91. package/src/web/scrapers/docs-rs.ts +2 -3
  92. package/src/web/search/index.ts +2 -2
  93. package/src/web/search/provider.ts +14 -2
package/src/tools/job.ts CHANGED
@@ -372,6 +372,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
372
372
  interface JobRenderArgs {
373
373
  poll?: string[];
374
374
  cancel?: string[];
375
+ list?: boolean;
375
376
  }
376
377
 
377
378
  const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
@@ -433,6 +434,7 @@ function flattenStructuredPreview(text: string): string {
433
434
  }
434
435
 
435
436
  function describeTarget(args: JobRenderArgs | undefined): string {
437
+ if (args?.list) return "background jobs";
436
438
  const poll = args?.poll ?? [];
437
439
  const cancel = args?.cancel ?? [];
438
440
  const parts: string[] = [];
@@ -460,7 +462,7 @@ export const jobToolRenderer = {
460
462
  uiTheme: Theme,
461
463
  args?: JobRenderArgs,
462
464
  ): Component {
463
- const jobs = result.details?.jobs ?? [];
465
+ let jobs = result.details?.jobs ?? [];
464
466
 
465
467
  if (jobs.length === 0) {
466
468
  const fallback = result.content?.find(c => c.type === "text")?.text || "No jobs to process";
@@ -468,6 +470,17 @@ export const jobToolRenderer = {
468
470
  return new Text([header, formatEmptyMessage(fallback, uiTheme)].join("\n"), 0, 0);
469
471
  }
470
472
 
473
+ const isPollCall = args
474
+ ? !args.list && (!args.cancel || args.cancel.length === 0 || args.poll !== undefined)
475
+ : true;
476
+
477
+ if (!options.isPartial && isPollCall) {
478
+ jobs = jobs.filter(job => job.status !== "running");
479
+ if (jobs.length === 0) {
480
+ return new Text("", 0, 0);
481
+ }
482
+ }
483
+
471
484
  const counts = { completed: 0, failed: 0, cancelled: 0, running: 0 };
472
485
  for (const job of jobs) counts[job.status]++;
473
486
 
@@ -143,6 +143,37 @@ export function expandPath(filePath: string): string {
143
143
  const normalized = stripFileUrl(normalizeUnicodeSpaces(normalizeAtPrefix(filePath)));
144
144
  return expandTilde(normalized);
145
145
  }
146
+
147
+ function isAsciiDriveLetter(value: string): boolean {
148
+ if (value.length !== 1) return false;
149
+ const code = value.charCodeAt(0);
150
+ return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
151
+ }
152
+
153
+ function windowsDriveAliasPath(filePath: string): string | undefined {
154
+ if (!filePath.startsWith("/")) return undefined;
155
+ const parts = filePath.split("/");
156
+ if (parts[0] !== "") return undefined;
157
+
158
+ let drive: string | undefined;
159
+ let tailStart = 2;
160
+ if (parts.length >= 2 && isAsciiDriveLetter(parts[1] ?? "")) {
161
+ drive = parts[1]!.toUpperCase();
162
+ } else if (parts.length >= 3 && (parts[1] ?? "").toLowerCase() === "mnt" && isAsciiDriveLetter(parts[2] ?? "")) {
163
+ drive = parts[2]!.toUpperCase();
164
+ tailStart = 3;
165
+ }
166
+ if (!drive) return undefined;
167
+
168
+ const tail = parts.slice(tailStart).filter(Boolean).join("\\");
169
+ return tail ? `${drive}:\\${tail}` : `${drive}:\\`;
170
+ }
171
+
172
+ export function normalizeWindowsDriveAliasPath(filePath: string, platform: NodeJS.Platform = process.platform): string {
173
+ if (platform !== "win32") return filePath;
174
+ return windowsDriveAliasPath(filePath) ?? filePath;
175
+ }
176
+
146
177
  /**
147
178
  * Inclusive line range describing one selector segment (e.g. `50-100`,
148
179
  * `301-`, or `50+10`). `endLine` is `undefined` for open-ended ranges.
@@ -353,7 +384,7 @@ export function isInternalUrlPath(filePath: string): boolean {
353
384
  */
354
385
  export function resolveToCwd(filePath: string, cwd: string): string {
355
386
  const normalized = normalizeLocalScheme(filePath);
356
- const expanded = expandPath(normalized);
387
+ const expanded = normalizeWindowsDriveAliasPath(expandPath(normalized));
357
388
  const expandedAndNormalized = normalizeLocalScheme(expanded);
358
389
 
359
390
  assertNotInternalUrl(expandedAndNormalized, normalized);
@@ -530,8 +561,8 @@ export async function splitDelimitedPathEntry(
530
561
  }
531
562
 
532
563
  return (
533
- (await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "comma", false)) ??
534
564
  (await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "semicolon", false)) ??
565
+ (await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "comma", false)) ??
535
566
  (await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "whitespace", true)) ??
536
567
  (await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "mixed", true))
537
568
  );
@@ -20,10 +20,9 @@
20
20
  * never blocked on the network and never throws.
21
21
  */
22
22
  import { Database } from "bun:sqlite";
23
- import path from "node:path";
24
23
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
25
24
  import type { FetchImpl } from "@oh-my-pi/pi-ai";
26
- import { $env, $flag, getAgentDir, getInstallId, logger, VERSION } from "@oh-my-pi/pi-utils";
25
+ import { $env, $flag, getAutoQaDbDir, getInstallId, logger, VERSION } from "@oh-my-pi/pi-utils";
27
26
  import { z } from "zod/v4";
28
27
  import type { Settings } from "..";
29
28
  import type { ToolSession } from "./index";
@@ -184,10 +183,6 @@ export async function resolveAutoQaConsent(settings: Settings | undefined): Prom
184
183
  return consentInFlight;
185
184
  }
186
185
 
187
- export function getAutoQaDbPath(): string {
188
- return path.join(getAgentDir(), "autoqa.db");
189
- }
190
-
191
186
  let cachedDb: Database | null = null;
192
187
 
193
188
  /**
@@ -205,7 +200,7 @@ let cachedDb: Database | null = null;
205
200
  export function openAutoQaDb(): Database | null {
206
201
  if (cachedDb) return cachedDb;
207
202
  try {
208
- const db = new Database(getAutoQaDbPath());
203
+ const db = new Database(getAutoQaDbDir());
209
204
  // Install the busy handler BEFORE any lock-taking statement. See #2421.
210
205
  db.run("PRAGMA busy_timeout = 5000");
211
206
  db.run(`
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { gunzipSync } from "node:zlib";
4
- import { getAgentDir, isEnoent, logger, ptree, tryParseJson } from "@oh-my-pi/pi-utils";
4
+ import { getDocsRsCacheDir, isEnoent, logger, ptree, tryParseJson } from "@oh-my-pi/pi-utils";
5
5
  import { ToolAbortError } from "../../tools/tool-errors";
6
6
  import type { RenderResult, SpecialHandler } from "./types";
7
7
  import { buildResult, MAX_BYTES } from "./types";
@@ -276,7 +276,6 @@ function findItemInModule(mod_: RustdocItem, name: string, index: Record<string,
276
276
  return null;
277
277
  }
278
278
 
279
- const DOCS_RS_CACHE_ROOT = "webcache";
280
279
  const DOCS_RS_CACHE_FILENAME = "rustdoc.json";
281
280
 
282
281
  function sanitizeCacheSegment(value: string): string {
@@ -291,7 +290,7 @@ function getDocsRsCacheVersionSegment(version: string, now = new Date()): string
291
290
  function getDocsRsCachePath(target: DocsRsTarget, now = new Date()): string {
292
291
  const crate = sanitizeCacheSegment(target.crateName);
293
292
  const version = getDocsRsCacheVersionSegment(target.version, now);
294
- return path.join(getAgentDir(), DOCS_RS_CACHE_ROOT, `docsrs_${crate}_${version}`, DOCS_RS_CACHE_FILENAME);
293
+ return path.join(getDocsRsCacheDir(), `docsrs_${crate}_${version}`, DOCS_RS_CACHE_FILENAME);
295
294
  }
296
295
 
297
296
  async function readCachedRustdocCrate(
@@ -300,6 +300,6 @@ export function getSearchTools(): CustomTool<any, any>[] {
300
300
  return [webSearchCustomTool];
301
301
  }
302
302
 
303
- export { getSearchProvider, setPreferredSearchProvider } from "./provider";
303
+ export { getSearchProvider, setExcludedSearchProviders, setPreferredSearchProvider } from "./provider";
304
304
  export type { SearchProviderId as SearchProvider, SearchResponse } from "./types";
305
- export { isSearchProviderPreference } from "./types";
305
+ export { isSearchProviderId, isSearchProviderPreference } from "./types";
@@ -127,6 +127,18 @@ export function setPreferredSearchProvider(provider: SearchProviderId | "auto"):
127
127
  preferredProvId = provider;
128
128
  }
129
129
 
130
+ /** Providers excluded from web search resolution via settings. */
131
+ let excludedProvIds = new Set<SearchProviderId>();
132
+
133
+ /** Set providers that web search should never use, including fallbacks. */
134
+ export function setExcludedSearchProviders(providers: readonly SearchProviderId[]): void {
135
+ excludedProvIds = new Set(providers);
136
+ }
137
+
138
+ function isSearchProviderExcluded(id: SearchProviderId): boolean {
139
+ return excludedProvIds.has(id);
140
+ }
141
+
130
142
  /**
131
143
  * Determine which providers are configured and currently available.
132
144
  * Each candidate is loaded (and its `isAvailable()` called) only as the chain
@@ -138,7 +150,7 @@ export async function resolveProviderChain(
138
150
  ): Promise<SearchProvider[]> {
139
151
  const providers: SearchProvider[] = [];
140
152
 
141
- if (preferredProvider !== "auto") {
153
+ if (preferredProvider !== "auto" && !isSearchProviderExcluded(preferredProvider)) {
142
154
  const provider = await getSearchProvider(preferredProvider);
143
155
  if (await provider.isExplicitlyAvailable(authStorage)) {
144
156
  providers.push(provider);
@@ -146,7 +158,7 @@ export async function resolveProviderChain(
146
158
  }
147
159
 
148
160
  for (const id of SEARCH_PROVIDER_ORDER) {
149
- if (id === preferredProvider) continue;
161
+ if (id === preferredProvider || isSearchProviderExcluded(id)) continue;
150
162
  const provider = await getSearchProvider(id);
151
163
  if (await provider.isAvailable(authStorage)) {
152
164
  providers.push(provider);