@oh-my-pi/pi-coding-agent 15.10.4 → 15.10.6

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 +74 -0
  2. package/dist/types/capability/rule-buckets.d.ts +1 -1
  3. package/dist/types/capability/rule.d.ts +6 -1
  4. package/dist/types/cli/update-cli.d.ts +11 -1
  5. package/dist/types/config/model-registry.d.ts +18 -1
  6. package/dist/types/discovery/at-imports.d.ts +15 -0
  7. package/dist/types/edit/diff.d.ts +3 -2
  8. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
  9. package/dist/types/eval/backend.d.ts +7 -0
  10. package/dist/types/eval/js/context-manager.d.ts +1 -0
  11. package/dist/types/eval/js/executor.d.ts +2 -0
  12. package/dist/types/eval/js/index.d.ts +1 -1
  13. package/dist/types/eval/js/shared/helpers.d.ts +6 -0
  14. package/dist/types/eval/js/shared/runtime.d.ts +5 -0
  15. package/dist/types/eval/js/worker-protocol.d.ts +6 -0
  16. package/dist/types/eval/py/executor.d.ts +7 -0
  17. package/dist/types/eval/py/index.d.ts +1 -1
  18. package/dist/types/exa/index.d.ts +1 -19
  19. package/dist/types/exa/mcp-client.d.ts +10 -3
  20. package/dist/types/exa/types.d.ts +0 -83
  21. package/dist/types/export/ttsr.d.ts +14 -0
  22. package/dist/types/extensibility/extensions/types.d.ts +8 -1
  23. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
  24. package/dist/types/internal-urls/local-protocol.d.ts +10 -0
  25. package/dist/types/mcp/oauth-flow.d.ts +2 -2
  26. package/dist/types/modes/components/custom-editor.d.ts +3 -0
  27. package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
  28. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  29. package/dist/types/modes/components/status-line/types.d.ts +31 -2
  30. package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
  31. package/dist/types/modes/image-references.d.ts +8 -3
  32. package/dist/types/modes/interactive-mode.d.ts +9 -1
  33. package/dist/types/modes/theme/theme.d.ts +2 -1
  34. package/dist/types/modes/types.d.ts +3 -1
  35. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  36. package/dist/types/session/agent-session.d.ts +0 -2
  37. package/dist/types/task/render.d.ts +1 -0
  38. package/dist/types/tools/ask.d.ts +1 -0
  39. package/dist/types/tools/browser/tab-worker.d.ts +15 -0
  40. package/dist/types/tools/index.d.ts +17 -2
  41. package/dist/types/tools/render-utils.d.ts +1 -1
  42. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  43. package/dist/types/utils/block-context.d.ts +35 -0
  44. package/dist/types/utils/git.d.ts +6 -0
  45. package/dist/types/utils/image-loading.d.ts +12 -0
  46. package/package.json +29 -9
  47. package/src/capability/rule-buckets.ts +4 -2
  48. package/src/capability/rule.ts +10 -1
  49. package/src/cli/auth-broker-cli.ts +6 -7
  50. package/src/cli/auth-gateway-cli.ts +4 -3
  51. package/src/cli/list-models.ts +5 -0
  52. package/src/cli/update-cli.ts +138 -16
  53. package/src/commit/agentic/tools/split-commit.ts +8 -1
  54. package/src/config/model-provider-priority.ts +1 -0
  55. package/src/config/model-registry.ts +81 -2
  56. package/src/debug/index.ts +4 -8
  57. package/src/discovery/at-imports.ts +273 -0
  58. package/src/discovery/builtin-rules/index.ts +4 -0
  59. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  60. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  61. package/src/discovery/helpers.ts +2 -1
  62. package/src/edit/diff.ts +114 -4
  63. package/src/edit/hashline/diff.ts +1 -1
  64. package/src/edit/hashline/execute.ts +1 -1
  65. package/src/edit/modes/patch.ts +6 -2
  66. package/src/edit/modes/replace.ts +1 -1
  67. package/src/edit/renderer.ts +12 -2
  68. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  69. package/src/eval/backend.ts +15 -0
  70. package/src/eval/js/context-manager.ts +4 -2
  71. package/src/eval/js/executor.ts +3 -0
  72. package/src/eval/js/index.ts +7 -1
  73. package/src/eval/js/shared/helpers.ts +53 -6
  74. package/src/eval/js/shared/runtime.ts +8 -0
  75. package/src/eval/js/worker-core.ts +1 -0
  76. package/src/eval/js/worker-protocol.ts +6 -0
  77. package/src/eval/py/executor.ts +12 -0
  78. package/src/eval/py/index.ts +7 -1
  79. package/src/eval/py/prelude.py +43 -4
  80. package/src/eval/py/runner.py +1 -0
  81. package/src/exa/index.ts +1 -26
  82. package/src/exa/mcp-client.ts +10 -10
  83. package/src/exa/types.ts +0 -97
  84. package/src/export/ttsr.ts +122 -1
  85. package/src/extensibility/extensions/types.ts +8 -1
  86. package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
  87. package/src/extensibility/plugins/doctor.ts +1 -1
  88. package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
  89. package/src/goals/tools/goal-tool.ts +1 -1
  90. package/src/internal-urls/docs-index.generated.ts +7 -6
  91. package/src/internal-urls/local-protocol.ts +13 -0
  92. package/src/lsp/render.ts +8 -6
  93. package/src/mcp/oauth-flow.ts +3 -3
  94. package/src/mcp/render.ts +7 -1
  95. package/src/modes/components/agent-dashboard.ts +6 -4
  96. package/src/modes/components/custom-editor.ts +12 -6
  97. package/src/modes/components/login-dialog.ts +1 -1
  98. package/src/modes/components/oauth-selector.ts +4 -4
  99. package/src/modes/components/read-tool-group.ts +10 -3
  100. package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
  101. package/src/modes/components/status-line/index.ts +1 -0
  102. package/src/modes/components/status-line/types.ts +23 -8
  103. package/src/modes/components/tool-execution.ts +1 -1
  104. package/src/modes/components/transcript-container.ts +17 -10
  105. package/src/modes/components/user-message.ts +6 -3
  106. package/src/modes/components/welcome.ts +1 -1
  107. package/src/modes/controllers/event-controller.ts +8 -0
  108. package/src/modes/controllers/extension-ui-controller.ts +143 -127
  109. package/src/modes/controllers/input-controller.ts +60 -11
  110. package/src/modes/controllers/mcp-command-controller.ts +52 -17
  111. package/src/modes/controllers/selector-controller.ts +4 -11
  112. package/src/modes/controllers/ssh-command-controller.ts +2 -2
  113. package/src/modes/image-references.ts +13 -7
  114. package/src/modes/interactive-mode.ts +35 -3
  115. package/src/modes/rpc/rpc-mode.ts +1 -1
  116. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
  117. package/src/modes/theme/theme.ts +95 -1
  118. package/src/modes/types.ts +3 -1
  119. package/src/modes/utils/ui-helpers.ts +14 -5
  120. package/src/prompts/tools/bash.md +1 -1
  121. package/src/prompts/tools/eval.md +4 -4
  122. package/src/sdk.ts +31 -14
  123. package/src/session/agent-session.ts +290 -196
  124. package/src/session/session-manager.ts +1 -1
  125. package/src/slash-commands/builtin-registry.ts +9 -1
  126. package/src/system-prompt.ts +15 -9
  127. package/src/task/index.ts +9 -1
  128. package/src/task/render.ts +36 -14
  129. package/src/tools/ask.ts +14 -5
  130. package/src/tools/bash-interactive.ts +1 -1
  131. package/src/tools/bash.ts +14 -2
  132. package/src/tools/browser/render.ts +5 -2
  133. package/src/tools/browser/tab-worker.ts +211 -91
  134. package/src/tools/debug.ts +5 -2
  135. package/src/tools/eval-render.ts +6 -3
  136. package/src/tools/eval.ts +1 -1
  137. package/src/tools/gh-renderer.ts +29 -15
  138. package/src/tools/index.ts +32 -4
  139. package/src/tools/inspect-image-renderer.ts +12 -5
  140. package/src/tools/job.ts +9 -6
  141. package/src/tools/memory-render.ts +19 -5
  142. package/src/tools/read.ts +165 -18
  143. package/src/tools/render-utils.ts +3 -1
  144. package/src/tools/resolve.ts +1 -1
  145. package/src/tools/review.ts +1 -1
  146. package/src/tools/ssh.ts +4 -1
  147. package/src/tools/todo.ts +8 -1
  148. package/src/tools/tool-timeouts.ts +1 -1
  149. package/src/tools/write.ts +1 -1
  150. package/src/tui/code-cell.ts +1 -1
  151. package/src/utils/block-context.ts +312 -0
  152. package/src/utils/git.ts +41 -0
  153. package/src/utils/image-loading.ts +31 -1
  154. package/src/web/search/providers/codex.ts +1 -1
  155. package/src/web/search/render.ts +14 -6
  156. package/dist/types/exa/factory.d.ts +0 -13
  157. package/dist/types/exa/render.d.ts +0 -19
  158. package/dist/types/exa/researcher.d.ts +0 -9
  159. package/dist/types/exa/search.d.ts +0 -9
  160. package/dist/types/exa/websets.d.ts +0 -9
  161. package/src/exa/factory.ts +0 -60
  162. package/src/exa/render.ts +0 -244
  163. package/src/exa/researcher.ts +0 -36
  164. package/src/exa/search.ts +0 -47
  165. package/src/exa/websets.ts +0 -248
@@ -5,6 +5,8 @@
5
5
  * the agent's output. When a match occurs, the stream is aborted, the rule is
6
6
  * injected as a system reminder, and the request is retried.
7
7
  */
8
+ import * as path from "node:path";
9
+ import { AstMatchStrictness, astMatch } from "@oh-my-pi/pi-natives";
8
10
  import { logger } from "@oh-my-pi/pi-utils";
9
11
  import type { Rule } from "../capability/rule";
10
12
  import type { TtsrSettings } from "../config/settings";
@@ -38,6 +40,8 @@ interface TtsrScope {
38
40
  interface TtsrEntry {
39
41
  rule: Rule;
40
42
  conditions: RegExp[];
43
+ /** ast-grep pattern strings; matched only against edit/write tool snapshots. */
44
+ astConditions: string[];
41
45
  scope: TtsrScope;
42
46
  globalPathGlobs?: Bun.Glob[];
43
47
  }
@@ -70,6 +74,8 @@ export class TtsrManager {
70
74
  readonly #rules = new Map<string, TtsrEntry>();
71
75
  readonly #injectionRecords = new Map<string, InjectionRecord>();
72
76
  readonly #buffers = new Map<string, string>();
77
+ /** Last snapshot evaluated for AST conditions, keyed by stream key, to dedupe matcher runs. */
78
+ readonly #lastAstSnapshots = new Map<string, string>();
73
79
  #messageCount = 0;
74
80
 
75
81
  constructor(settings?: TtsrSettings) {
@@ -302,7 +308,8 @@ export class TtsrManager {
302
308
  }
303
309
 
304
310
  const conditions = this.#compileConditions(rule);
305
- if (conditions.length === 0) {
311
+ const astConditions = (rule.astCondition ?? []).map(pattern => pattern.trim()).filter(p => p.length > 0);
312
+ if (conditions.length === 0 && astConditions.length === 0) {
306
313
  return false;
307
314
  }
308
315
 
@@ -318,6 +325,7 @@ export class TtsrManager {
318
325
  this.#rules.set(rule.name, {
319
326
  rule,
320
327
  conditions,
328
+ astConditions,
321
329
  scope,
322
330
  globalPathGlobs,
323
331
  });
@@ -325,6 +333,7 @@ export class TtsrManager {
325
333
  logger.debug("TTSR rule registered", {
326
334
  ruleName: rule.name,
327
335
  conditions: rule.condition,
336
+ astConditions: rule.astCondition,
328
337
  scope: rule.scope,
329
338
  globs: rule.globs,
330
339
  });
@@ -359,6 +368,112 @@ export class TtsrManager {
359
368
  return this.#matchBuffer(snapshot, context);
360
369
  }
361
370
 
371
+ /** Derive an ast-grep language alias from candidate paths (bare extension, e.g. "ts"), if any. */
372
+ #deriveLang(filePaths: string[] | undefined): string | undefined {
373
+ for (const filePath of filePaths ?? []) {
374
+ const ext = path.extname(this.#normalizePath(filePath));
375
+ if (ext.length > 1) {
376
+ return ext.slice(1).toLowerCase();
377
+ }
378
+ }
379
+ return undefined;
380
+ }
381
+
382
+ /**
383
+ * Evaluate ast-grep `astCondition` rules against a reconstructed tool snapshot.
384
+ *
385
+ * Only edit/write tool streams reach here (AST conditions need a language, which
386
+ * we infer from the file extension on the tool's path argument). The snapshot is
387
+ * matched in memory by the native engine (`astMatch`), so this is async and
388
+ * intentionally throttled: identical consecutive snapshots (the common case when
389
+ * only non-source arguments change between deltas) are skipped.
390
+ */
391
+ async checkAstSnapshot(snapshot: string, context: TtsrMatchContext): Promise<Rule[]> {
392
+ if (!this.#settings.enabled || context.source !== "tool") {
393
+ return [];
394
+ }
395
+
396
+ const lang = this.#deriveLang(context.filePaths);
397
+ if (!lang) {
398
+ return [];
399
+ }
400
+
401
+ const candidates: TtsrEntry[] = [];
402
+ for (const [name, entry] of this.#rules) {
403
+ if (entry.astConditions.length === 0) {
404
+ continue;
405
+ }
406
+ if (
407
+ !this.#canTrigger(name) ||
408
+ !this.#matchesScope(entry, context) ||
409
+ !this.#matchesGlobalPaths(entry, context)
410
+ ) {
411
+ continue;
412
+ }
413
+ candidates.push(entry);
414
+ }
415
+ if (candidates.length === 0) {
416
+ return [];
417
+ }
418
+
419
+ // Throttle: skip re-running the matcher when the source content is unchanged.
420
+ const bufferKey = this.#bufferKey(context);
421
+ if (this.#lastAstSnapshots.get(bufferKey) === snapshot) {
422
+ return [];
423
+ }
424
+ this.#lastAstSnapshots.set(bufferKey, snapshot);
425
+
426
+ const matches: Rule[] = [];
427
+ for (const entry of candidates) {
428
+ if (await this.#astConditionsMatch(entry.astConditions, snapshot, lang)) {
429
+ matches.push(entry.rule);
430
+ logger.debug("TTSR ast condition matched", {
431
+ ruleName: entry.rule.name,
432
+ astConditions: entry.rule.astCondition,
433
+ toolName: context.toolName,
434
+ filePaths: context.filePaths,
435
+ });
436
+ }
437
+ }
438
+ return matches;
439
+ }
440
+
441
+ async #astConditionsMatch(patterns: string[], source: string, lang: string): Promise<boolean> {
442
+ try {
443
+ const result = await astMatch({
444
+ patterns,
445
+ source,
446
+ lang,
447
+ strictness: AstMatchStrictness.Smart,
448
+ limit: 1,
449
+ });
450
+ if (result.parseErrors && result.parseErrors.length > 0) {
451
+ logger.debug("TTSR ast match reported parse errors", { parseErrors: result.parseErrors });
452
+ }
453
+ return result.totalMatches > 0;
454
+ } catch (error) {
455
+ logger.warn("TTSR ast match failed, treating as no match", {
456
+ patterns,
457
+ lang,
458
+ error: error instanceof Error ? error.message : String(error),
459
+ });
460
+ return false;
461
+ }
462
+ }
463
+
464
+ /** True when any registered rule carries ast-grep conditions. */
465
+ hasAstRules(): boolean {
466
+ if (!this.#settings.enabled) {
467
+ return false;
468
+ }
469
+ for (const entry of this.#rules.values()) {
470
+ if (entry.astConditions.length > 0) {
471
+ return true;
472
+ }
473
+ }
474
+ return false;
475
+ }
476
+
362
477
  #matchBuffer(buffer: string, context: TtsrMatchContext): Rule[] {
363
478
  if (!this.#settings.enabled) {
364
479
  return [];
@@ -435,6 +550,7 @@ export class TtsrManager {
435
550
  /** Reset stream buffers (called on new turn). */
436
551
  resetBuffer(): void {
437
552
  this.#buffers.clear();
553
+ this.#lastAstSnapshots.clear();
438
554
  }
439
555
 
440
556
  /** Check if any TTSR rules are registered. */
@@ -445,6 +561,11 @@ export class TtsrManager {
445
561
  return this.#rules.size > 0;
446
562
  }
447
563
 
564
+ /** All rules currently registered for TTSR monitoring, in registration order. */
565
+ getRules(): Rule[] {
566
+ return Array.from(this.#rules.values(), entry => entry.rule);
567
+ }
568
+
448
569
  /** Increment message counter (call after each turn). */
449
570
  incrementMessageCount(): void {
450
571
  this.#messageCount++;
@@ -28,7 +28,7 @@ import type {
28
28
  TextContent,
29
29
  TSchema,
30
30
  } from "@oh-my-pi/pi-ai";
31
- import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types";
31
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
32
32
  import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
33
33
  import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
34
34
  import type { logger as PiLogger } from "@oh-my-pi/pi-utils";
@@ -1134,6 +1134,13 @@ export interface ProviderConfig {
1134
1134
  /** Optional model rewrite hook for credential-aware routing (e.g., enterprise URLs). */
1135
1135
  modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
1136
1136
  };
1137
+ /**
1138
+ * Async factory that fetches the live model list from the provider endpoint.
1139
+ * Runs through the same SQLite model-cache as built-in providers (keyed by
1140
+ * provider name, default 24 h TTL). Receives the resolved API key (undefined
1141
+ * when unauthenticated). Mutually exclusive with `models`.
1142
+ */
1143
+ fetchDynamicModels?: (apiKey: string | undefined) => Promise<readonly ProviderModelConfig[]>;
1137
1144
  }
1138
1145
 
1139
1146
  /** Configuration for a model within a provider. */
@@ -8,7 +8,7 @@
8
8
  * entrypoint. Legacy extensions still author parameter schemas as
9
9
  * `Type.Object({ ... })`, so this file is served by `legacy-pi-compat.ts` in
10
10
  * place of the real pi-ai entrypoint whenever a legacy extension imports the
11
- * bare package root. Subpath imports (`@oh-my-pi/pi-ai/utils/oauth`, etc.)
11
+ * bare package root. Subpath imports (`@oh-my-pi/pi-ai/oauth`, etc.)
12
12
  * continue to resolve directly against the bundled pi-ai package.
13
13
  *
14
14
  * The `Type` runtime is borrowed from the Zod-backed TypeBox shim that
@@ -48,7 +48,7 @@ export function formatDoctorResults(checks: DoctorCheck[]): string {
48
48
  for (const check of checks) {
49
49
  const icon =
50
50
  check.status === "ok"
51
- ? theme.status.success
51
+ ? theme.status.enabled
52
52
  : check.status === "warning"
53
53
  ? theme.status.warning
54
54
  : theme.status.error;
@@ -33,10 +33,11 @@ const PI_PACKAGE_ALTERNATION = PI_PACKAGE_NAMES.join("|");
33
33
  // bundled copy. Add new entries as `pkg/from -> pkg/to` whenever a plugin
34
34
  // surfaces another upstream-only subpath that breaks resolution.
35
35
  const PI_SUBPATH_REMAPS: ReadonlyMap<string, string> = new Map<string, string>([
36
- // `@mariozechner/pi-ai/oauth` re-exported `./utils/oauth/index.js`.
37
- // Our pi-ai keeps the implementation under `utils/oauth` but never added a
38
- // root-level re-export, so map the upstream subpath onto it directly.
39
- ["pi-ai/oauth", "pi-ai/utils/oauth"],
36
+ // (currently empty) Upstream `@mariozechner/pi-ai/oauth` re-exported
37
+ // `./utils/oauth/index.js`. Our pi-ai now exposes the same surface at the
38
+ // real `@oh-my-pi/pi-ai/oauth` export, so the legacy subpath canonicalizes
39
+ // straight to it with no rewrite. Add `from -> to` entries here whenever a
40
+ // future upstream-only subpath surfaces that breaks resolution.
40
41
  ]);
41
42
 
42
43
  const LEGACY_PI_SPECIFIER_FILTER = new RegExp(`^@(?:${PI_SCOPE_ALTERNATION})/(?:${PI_PACKAGE_ALTERNATION})(?:/.*)?$`);
@@ -119,7 +120,7 @@ const TYPEBOX_SHIM_PATH = BUNFS_PACKAGE_ROOT
119
120
  // longer satisfies those imports. The override below redirects only the bare
120
121
  // pi-ai package root onto a sibling shim that re-exports the canonical surface
121
122
  // plus the borrowed `Type` runtime from the Zod-backed TypeBox shim. Subpath
122
- // imports such as `@oh-my-pi/pi-ai/utils/oauth` continue to resolve directly
123
+ // imports such as `@oh-my-pi/pi-ai/oauth` continue to resolve directly
123
124
  // against the bundled pi-ai package.
124
125
  const LEGACY_PI_AI_SHIM_PATH = BUNFS_PACKAGE_ROOT
125
126
  ? bunfsPath("coding-agent", "src", "extensibility", "legacy-pi-ai-shim.js")
@@ -209,7 +209,7 @@ export const goalToolRenderer = {
209
209
 
210
210
  const header = renderStatusLine(
211
211
  {
212
- icon: "success",
212
+ iconOverride: uiTheme.styledSymbol("tool.goal", "accent"),
213
213
  title: "Goal",
214
214
  description,
215
215
  badge: { label: goal.status, color: goalBadgeColor(goal.status) },