@oh-my-pi/pi-coding-agent 15.7.6 → 15.8.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 (117) hide show
  1. package/CHANGELOG.md +172 -198
  2. package/dist/types/async/job-manager.d.ts +3 -3
  3. package/dist/types/cli/args.d.ts +1 -0
  4. package/dist/types/cli/claude-trace-cli.d.ts +54 -0
  5. package/dist/types/cli/session-picker.d.ts +10 -3
  6. package/dist/types/cli/update-cli.d.ts +17 -0
  7. package/dist/types/commands/launch.d.ts +3 -0
  8. package/dist/types/config/keybindings.d.ts +10 -1
  9. package/dist/types/config/settings-schema.d.ts +2 -2
  10. package/dist/types/config/settings.d.ts +13 -0
  11. package/dist/types/edit/index.d.ts +6 -0
  12. package/dist/types/edit/streaming.d.ts +8 -0
  13. package/dist/types/eval/concurrency-bridge.d.ts +26 -0
  14. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  15. package/dist/types/export/ttsr.d.ts +9 -0
  16. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +11 -0
  17. package/dist/types/main.d.ts +5 -0
  18. package/dist/types/mcp/transports/stdio.d.ts +19 -0
  19. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  20. package/dist/types/modes/components/hook-selector.d.ts +3 -0
  21. package/dist/types/modes/components/session-selector.d.ts +32 -5
  22. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  23. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -2
  24. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  25. package/dist/types/modes/interactive-mode.d.ts +9 -2
  26. package/dist/types/modes/types.d.ts +4 -2
  27. package/dist/types/registry/agent-registry.d.ts +1 -1
  28. package/dist/types/sdk.d.ts +2 -2
  29. package/dist/types/session/agent-session.d.ts +4 -2
  30. package/dist/types/session/history-storage.d.ts +16 -1
  31. package/dist/types/session/session-manager.d.ts +4 -0
  32. package/dist/types/task/output-manager.d.ts +6 -15
  33. package/dist/types/tools/find.d.ts +0 -9
  34. package/dist/types/tools/index.d.ts +1 -1
  35. package/dist/types/tools/path-utils.d.ts +16 -0
  36. package/dist/types/tools/sqlite-reader.d.ts +25 -8
  37. package/dist/types/tools/write.d.ts +2 -0
  38. package/dist/types/utils/clipboard.d.ts +4 -0
  39. package/dist/types/utils/jj.d.ts +49 -0
  40. package/dist/types/web/kagi.d.ts +76 -0
  41. package/dist/types/web/search/providers/exa.d.ts +7 -1
  42. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  43. package/package.json +9 -9
  44. package/src/async/job-manager.ts +3 -3
  45. package/src/cli/args.ts +6 -2
  46. package/src/cli/claude-trace-cli.ts +783 -0
  47. package/src/cli/session-picker.ts +36 -10
  48. package/src/cli/update-cli.ts +35 -2
  49. package/src/commands/launch.ts +3 -0
  50. package/src/config/keybindings.ts +14 -1
  51. package/src/config/model-registry.ts +18 -7
  52. package/src/config/settings-schema.ts +2 -2
  53. package/src/config/settings.ts +23 -0
  54. package/src/discovery/builtin-rules/index.ts +2 -0
  55. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  56. package/src/discovery/claude-plugins.ts +7 -9
  57. package/src/edit/index.ts +10 -0
  58. package/src/edit/streaming.ts +65 -0
  59. package/src/eval/__tests__/agent-bridge.test.ts +58 -4
  60. package/src/eval/concurrency-bridge.ts +34 -0
  61. package/src/eval/js/shared/prelude.txt +20 -17
  62. package/src/eval/js/tool-bridge.ts +5 -0
  63. package/src/eval/py/prelude.py +23 -15
  64. package/src/export/ttsr.ts +18 -1
  65. package/src/extensibility/custom-commands/bundled/review/index.ts +74 -45
  66. package/src/extensibility/plugins/legacy-pi-compat.ts +115 -131
  67. package/src/extensibility/skills.ts +0 -1
  68. package/src/internal-urls/docs-index.generated.ts +12 -11
  69. package/src/main.ts +92 -24
  70. package/src/mcp/transports/stdio.ts +55 -22
  71. package/src/modes/acp/acp-event-mapper.ts +54 -4
  72. package/src/modes/components/custom-editor.ts +10 -0
  73. package/src/modes/components/hook-selector.ts +89 -31
  74. package/src/modes/components/oauth-selector.ts +12 -6
  75. package/src/modes/components/session-selector.ts +179 -24
  76. package/src/modes/components/tool-execution.ts +16 -3
  77. package/src/modes/controllers/command-controller.ts +2 -11
  78. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  79. package/src/modes/controllers/input-controller.ts +19 -1
  80. package/src/modes/controllers/selector-controller.ts +61 -21
  81. package/src/modes/interactive-mode.ts +125 -15
  82. package/src/modes/types.ts +5 -2
  83. package/src/prompts/agents/reviewer.md +2 -2
  84. package/src/prompts/review-request.md +1 -1
  85. package/src/prompts/system/empty-stop-retry.md +6 -0
  86. package/src/prompts/system/orchestrate-notice.md +5 -3
  87. package/src/prompts/system/workflow-notice.md +2 -2
  88. package/src/prompts/tools/eval.md +5 -5
  89. package/src/prompts/tools/find.md +1 -1
  90. package/src/prompts/tools/irc.md +6 -6
  91. package/src/prompts/tools/search-tool-bm25.md +9 -2
  92. package/src/prompts/tools/search.md +1 -1
  93. package/src/prompts/tools/task.md +1 -1
  94. package/src/registry/agent-registry.ts +1 -1
  95. package/src/sdk.ts +85 -31
  96. package/src/session/agent-session.ts +209 -54
  97. package/src/session/history-storage.ts +56 -12
  98. package/src/session/session-manager.ts +34 -0
  99. package/src/task/output-manager.ts +40 -48
  100. package/src/task/render.ts +3 -8
  101. package/src/tools/browser/tab-worker.ts +8 -5
  102. package/src/tools/find.ts +5 -29
  103. package/src/tools/index.ts +1 -1
  104. package/src/tools/path-utils.ts +144 -1
  105. package/src/tools/read.ts +47 -0
  106. package/src/tools/search-tool-bm25.ts +7 -1
  107. package/src/tools/search.ts +2 -27
  108. package/src/tools/sqlite-reader.ts +92 -9
  109. package/src/tools/write.ts +6 -0
  110. package/src/utils/clipboard.ts +38 -1
  111. package/src/utils/git.ts +19 -23
  112. package/src/utils/jj.ts +225 -0
  113. package/src/utils/open.ts +37 -2
  114. package/src/web/kagi.ts +168 -49
  115. package/src/web/search/providers/anthropic.ts +1 -1
  116. package/src/web/search/providers/exa.ts +20 -86
  117. package/src/web/search/providers/kagi.ts +4 -0
@@ -8,7 +8,6 @@
8
8
  */
9
9
  import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
10
10
  import { settings } from "../../../config/settings";
11
- import { callExaTool, findApiKey, isSearchResponse } from "../../../exa/mcp-client";
12
11
 
13
12
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
14
13
  import { SearchProviderError } from "../../../web/search/types";
@@ -32,6 +31,12 @@ export interface ExaSearchParams {
32
31
  start_published_date?: string;
33
32
  end_published_date?: string;
34
33
  signal?: AbortSignal;
34
+ /**
35
+ * Credential source. Resolved before falling back to `EXA_API_KEY` so
36
+ * Exa works when the key is stored via the broker/auth pipeline.
37
+ */
38
+ authStorage?: AuthStorage;
39
+ sessionId?: string;
35
40
  }
36
41
 
37
42
  interface ExaSearchResult {
@@ -52,72 +57,6 @@ interface ExaSearchResponse {
52
57
  searchTime?: number;
53
58
  }
54
59
 
55
- function asRecord(value: unknown): Record<string, unknown> | null {
56
- if (typeof value !== "object" || value === null) return null;
57
- return value as Record<string, unknown>;
58
- }
59
-
60
- function parseOptionalField(section: string, label: string): string | null | undefined {
61
- const regex = new RegExp(`(?:^|\\n)${label}:\\s*([^\\n]*)`);
62
- const match = section.match(regex);
63
- if (!match) return undefined;
64
- const value = match[1].trim();
65
- return value.length > 0 ? value : null;
66
- }
67
-
68
- function parseTextField(section: string): string | null | undefined {
69
- const match = section.match(/(?:^|\n)Text:\s*([\s\S]*)$/);
70
- if (!match) return undefined;
71
- const value = match[1].trim();
72
- return value.length > 0 ? value : null;
73
- }
74
-
75
- function parseExaMcpTextPayload(payload: unknown): ExaSearchResponse | null {
76
- const root = asRecord(payload);
77
- if (!root) return null;
78
-
79
- const content = root.content;
80
- if (!Array.isArray(content)) return null;
81
-
82
- const textBlocks = content
83
- .map(item => {
84
- const part = asRecord(item);
85
- const text = typeof part?.text === "string" ? part.text : "";
86
- return text.replace(/\r\n?/g, "\n").trim();
87
- })
88
- .filter(text => text.length > 0);
89
-
90
- if (textBlocks.length === 0) return null;
91
-
92
- const sections = textBlocks
93
- .join("\n\n")
94
- .split(/\n{2,}(?=Title:\s*[^\n]*(?:\n(?:URL|Author|Published Date|Text):))/)
95
- .map(section => section.trim())
96
- .filter(section => section.startsWith("Title:"));
97
-
98
- const results: ExaSearchResult[] = [];
99
- for (const section of sections) {
100
- const title = parseOptionalField(section, "Title");
101
- const url = parseOptionalField(section, "URL");
102
- const author = parseOptionalField(section, "Author");
103
- const publishedDate = parseOptionalField(section, "Published Date");
104
- const text = parseTextField(section);
105
-
106
- if (!title && !url && !text) continue;
107
-
108
- results.push({
109
- title: title ?? undefined,
110
- url: url ?? undefined,
111
- author: author ?? undefined,
112
- publishedDate: publishedDate ?? undefined,
113
- text: text ?? undefined,
114
- });
115
- }
116
-
117
- if (results.length === 0) return null;
118
- return { results };
119
- }
120
-
121
60
  export function normalizeSearchType(type: ExaSearchParamType | undefined): ExaSearchType {
122
61
  if (!type) return "auto";
123
62
  if (type === "keyword") return "fast";
@@ -195,24 +134,17 @@ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<E
195
134
  return response.json() as Promise<ExaSearchResponse>;
196
135
  }
197
136
 
198
- async function callExaMcpSearch(params: ExaSearchParams): Promise<ExaSearchResponse> {
199
- const response = await callExaTool("web_search_exa", { ...params }, findApiKey());
200
- if (isSearchResponse(response)) {
201
- return response as ExaSearchResponse;
202
- }
203
-
204
- const parsed = parseExaMcpTextPayload(response);
205
- if (parsed) {
206
- return parsed;
207
- }
208
-
209
- throw new Error("Exa MCP search returned unexpected response shape.");
210
- }
211
-
212
137
  /** Execute Exa web search */
213
138
  export async function searchExa(params: ExaSearchParams): Promise<SearchResponse> {
214
- const apiKey = getEnvApiKey("exa");
215
- const response = apiKey ? await callExaSearch(apiKey, params) : await callExaMcpSearch(params);
139
+ const storedKey = params.authStorage
140
+ ? await params.authStorage.getApiKey("exa", params.sessionId, { signal: params.signal })
141
+ : undefined;
142
+ const apiKey = storedKey ?? getEnvApiKey("exa");
143
+ if (!apiKey) {
144
+ throw new Error("Exa credentials not found. Set EXA_API_KEY or login with 'omp /login exa'.");
145
+ }
146
+
147
+ const response = await callExaSearch(apiKey, params);
216
148
 
217
149
  // Convert to unified SearchResponse
218
150
  const sources: SearchSource[] = [];
@@ -250,15 +182,15 @@ export class ExaProvider extends SearchProvider {
250
182
  readonly id = "exa";
251
183
  readonly label = "Exa";
252
184
 
253
- isAvailable(_authStorage: AuthStorage): boolean {
185
+ isAvailable(authStorage: AuthStorage): boolean {
254
186
  try {
255
187
  if (settings.get("exa.enabled") === false || settings.get("exa.enableSearch") === false) {
256
188
  return false;
257
189
  }
258
190
  } catch {
259
- // Settings not initialized; fall through to public MCP availability
191
+ // Settings may be unavailable before CLI initialization; credential availability is still authoritative.
260
192
  }
261
- return true;
193
+ return authStorage.hasAuth("exa");
262
194
  }
263
195
 
264
196
  search(params: SearchParams): Promise<SearchResponse> {
@@ -266,6 +198,8 @@ export class ExaProvider extends SearchProvider {
266
198
  query: params.query,
267
199
  num_results: params.numSearchResults ?? params.limit,
268
200
  signal: params.signal,
201
+ authStorage: params.authStorage,
202
+ sessionId: params.sessionId,
269
203
  });
270
204
  }
271
205
  }
@@ -19,6 +19,7 @@ const MAX_NUM_RESULTS = 40;
19
19
  export async function searchKagi(params: {
20
20
  query: string;
21
21
  num_results?: number;
22
+ recency?: SearchParams["recency"];
22
23
  signal?: AbortSignal;
23
24
  authStorage: AuthStorage;
24
25
  sessionId?: string;
@@ -30,6 +31,7 @@ export async function searchKagi(params: {
30
31
  params.query,
31
32
  {
32
33
  limit: numResults,
34
+ recency: params.recency,
33
35
  sessionId: params.sessionId,
34
36
  signal: params.signal,
35
37
  },
@@ -41,6 +43,7 @@ export async function searchKagi(params: {
41
43
  sources: toSearchSources(result.sources, numResults),
42
44
  relatedQuestions: result.relatedQuestions.length > 0 ? result.relatedQuestions : undefined,
43
45
  requestId: result.requestId,
46
+ answer: result.answer,
44
47
  };
45
48
  } catch (err) {
46
49
  if (err instanceof KagiApiError) {
@@ -67,6 +70,7 @@ export class KagiProvider extends SearchProvider {
67
70
  return searchKagi({
68
71
  query: params.query,
69
72
  num_results: params.numSearchResults ?? params.limit,
73
+ recency: params.recency,
70
74
  signal: params.signal,
71
75
  authStorage: params.authStorage,
72
76
  sessionId: params.sessionId,