tau-coding-agent 0.1.4 → 0.1.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 (51) hide show
  1. package/README.md +1 -1
  2. package/extensions/answer.ts +59 -47
  3. package/extensions/branch-term.ts +8 -13
  4. package/extensions/btw.ts +77 -6
  5. package/extensions/ghostty.ts +64 -11
  6. package/extensions/insights.ts +42 -4
  7. package/extensions/loop.ts +51 -12
  8. package/extensions/memory.ts +35 -17
  9. package/extensions/notify.ts +24 -5
  10. package/extensions/openai-fast.ts +6 -4
  11. package/extensions/openai-verbosity.ts +6 -4
  12. package/extensions/review/fix.ts +239 -0
  13. package/extensions/review/git.ts +517 -0
  14. package/extensions/review/index.ts +232 -0
  15. package/extensions/review/message-queue.ts +249 -0
  16. package/extensions/review/models.ts +589 -0
  17. package/extensions/review/prompts.ts +337 -0
  18. package/extensions/review/renderers/inline.ts +126 -0
  19. package/extensions/review/request.ts +504 -0
  20. package/extensions/review/review.ts +1305 -0
  21. package/extensions/review/runner.ts +522 -0
  22. package/extensions/review/runtime.ts +275 -0
  23. package/extensions/review/schema.ts +160 -0
  24. package/extensions/review/submit-review-tool.ts +62 -0
  25. package/extensions/review/submit-triage-tool.ts +60 -0
  26. package/extensions/review/triage.ts +746 -0
  27. package/extensions/sandbox/index.ts +976 -185
  28. package/extensions/sandbox/macos-sandbox-shell.mjs +202 -0
  29. package/extensions/{interlude.ts → stash.ts} +13 -11
  30. package/extensions/tool-display-mode.ts +2 -3
  31. package/extensions/usage/anthropic.ts +5 -6
  32. package/extensions/usage/github-copilot.ts +30 -9
  33. package/extensions/usage/index.ts +9 -5
  34. package/extensions/usage/openai-codex.ts +2 -2
  35. package/extensions/websearch/README.md +1 -1
  36. package/extensions/websearch/browser/chromium.ts +42 -31
  37. package/extensions/websearch/browser/discovery.ts +11 -9
  38. package/extensions/websearch/browser/firefox.ts +10 -7
  39. package/extensions/websearch/browser/fs.ts +10 -0
  40. package/extensions/websearch/browser/sqlite.ts +22 -15
  41. package/extensions/websearch/config.ts +2 -2
  42. package/extensions/websearch/index.ts +6 -6
  43. package/extensions/websearch/providers/pi-model.shared.ts +19 -1
  44. package/extensions/worktree.ts +33 -33
  45. package/package.json +4 -4
  46. package/skills/git-clean-history/SKILL.md +1 -0
  47. package/skills/git-commit/SKILL.md +2 -2
  48. package/skills/oracle/SKILL.md +3 -3
  49. package/skills/oracle/scripts/oracle +82 -61
  50. package/themes/tau-dark.json +2 -1
  51. package/extensions/review.ts +0 -4420
@@ -1,4 +1,5 @@
1
- import { complete, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
1
+ import { complete } from "@earendil-works/pi-ai/compat";
2
+ import type { Api, Model, UserMessage } from "@earendil-works/pi-ai";
2
3
  import {
3
4
  BorderedLoader,
4
5
  SessionManager,
@@ -26,6 +27,7 @@ import {
26
27
  } from "@earendil-works/pi-tui";
27
28
  import { createHash, randomBytes } from "node:crypto";
28
29
  import fs from "node:fs/promises";
30
+ import os from "node:os";
29
31
  import path from "node:path";
30
32
 
31
33
  // --- Constants ---
@@ -140,6 +142,7 @@ type ConfiguredModelSelection = {
140
142
  model: Model<Api>;
141
143
  apiKey?: string;
142
144
  headers?: Record<string, string>;
145
+ env?: Record<string, string>;
143
146
  };
144
147
 
145
148
  type ReadonlySessionManager = Pick<
@@ -480,8 +483,8 @@ async function runInsightsCommand(
480
483
  return;
481
484
  }
482
485
 
483
- if (!ctx.hasUI) {
484
- ctx.ui.notify("/insights currently requires interactive mode", "warning");
486
+ if (ctx.mode !== "tui") {
487
+ ctx.ui.notify("/insights currently requires TUI mode", "warning");
485
488
  return;
486
489
  }
487
490
 
@@ -561,9 +564,37 @@ async function runInsightsCommand(
561
564
  return;
562
565
  }
563
566
 
567
+ let reportPath: string | undefined;
568
+ try {
569
+ reportPath = await saveInsightsReport(result.reportMarkdown, result.generatedAt);
570
+ } catch (error) {
571
+ const message = error instanceof Error ? error.message : "Unknown error";
572
+ ctx.ui.notify(`Failed to save insights report: ${message}`, "warning");
573
+ }
574
+
564
575
  await ctx.ui.custom<void>((tui, theme, _kb, done) => {
565
576
  return new InsightsReportComponent(result, tui, theme, done);
566
577
  });
578
+
579
+ if (reportPath) {
580
+ ctx.ui.notify(`Insights report saved to ${reportPath}`, "info");
581
+ }
582
+ }
583
+
584
+ async function saveInsightsReport(reportMarkdown: string, generatedAt: string): Promise<string> {
585
+ const timestamp = generatedAt.replace(/[:.]/g, "-");
586
+ const reportPath = path.join(os.tmpdir(), `tau-insights-${timestamp}.md`);
587
+ const reportFile = await fs.open(reportPath, "wx", 0o600);
588
+
589
+ try {
590
+ await reportFile.writeFile(`${reportMarkdown.trimEnd()}\n`, "utf8");
591
+ await reportFile.close();
592
+ return reportPath;
593
+ } catch (error) {
594
+ await reportFile.close().catch(() => undefined);
595
+ await fs.rm(reportPath, { force: true }).catch(() => undefined);
596
+ throw error;
597
+ }
567
598
  }
568
599
 
569
600
  async function runInsightsPipeline(
@@ -748,7 +779,12 @@ async function getConfiguredModelSelection(
748
779
 
749
780
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
750
781
  if (!auth.ok) return null;
751
- return { model: ctx.model, apiKey: auth.apiKey, headers: auth.headers };
782
+ return {
783
+ model: ctx.model,
784
+ apiKey: auth.apiKey,
785
+ headers: auth.headers,
786
+ env: auth.env,
787
+ };
752
788
  }
753
789
 
754
790
  async function listTargets(
@@ -1341,6 +1377,7 @@ async function extractFacet(
1341
1377
  {
1342
1378
  apiKey: selection.apiKey,
1343
1379
  headers: selection.headers,
1380
+ env: selection.env,
1344
1381
  signal,
1345
1382
  },
1346
1383
  );
@@ -1513,6 +1550,7 @@ async function synthesizeReport(
1513
1550
  {
1514
1551
  apiKey: selection.apiKey,
1515
1552
  headers: selection.headers,
1553
+ env: selection.env,
1516
1554
  signal,
1517
1555
  },
1518
1556
  );
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import { Type } from "typebox";
10
- import { complete, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
10
+ import { complete } from "@earendil-works/pi-ai/compat";
11
+ import type { Api, Model, UserMessage } from "@earendil-works/pi-ai";
11
12
  import {
12
13
  compact,
13
14
  defineTool,
@@ -53,6 +54,7 @@ type ModelRequestAuth = {
53
54
  model: Model<Api>;
54
55
  apiKey?: string;
55
56
  headers?: Record<string, string>;
57
+ env?: Record<string, string>;
56
58
  };
57
59
 
58
60
  async function withPromptSignal<T>(pi: ExtensionAPI, run: () => Promise<T>): Promise<T> {
@@ -136,13 +138,20 @@ async function selectSummaryModel(ctx: ExtensionContext): Promise<ModelRequestAu
136
138
 
137
139
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(candidate);
138
140
  if (auth.ok) {
139
- return { model: candidate, apiKey: auth.apiKey, headers: auth.headers };
141
+ return {
142
+ model: candidate,
143
+ apiKey: auth.apiKey,
144
+ headers: auth.headers,
145
+ env: auth.env,
146
+ };
140
147
  }
141
148
  }
142
149
  }
143
150
 
144
151
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
145
- return auth.ok ? { model: ctx.model, apiKey: auth.apiKey, headers: auth.headers } : null;
152
+ return auth.ok
153
+ ? { model: ctx.model, apiKey: auth.apiKey, headers: auth.headers, env: auth.env }
154
+ : null;
146
155
  }
147
156
 
148
157
  async function summarizeBreakoutCondition(
@@ -164,7 +173,7 @@ async function summarizeBreakoutCondition(
164
173
  const response = await complete(
165
174
  selection.model,
166
175
  { systemPrompt: SUMMARY_SYSTEM_PROMPT, messages: [userMessage] },
167
- { apiKey: selection.apiKey, headers: selection.headers },
176
+ { apiKey: selection.apiKey, headers: selection.headers, env: selection.env },
168
177
  );
169
178
 
170
179
  if (response.stopReason === "aborted" || response.stopReason === "error") {
@@ -213,6 +222,8 @@ async function loadState(ctx: ExtensionContext): Promise<LoopStateData> {
213
222
 
214
223
  export default function loopExtension(pi: ExtensionAPI): void {
215
224
  let loopState: LoopStateData = { active: false };
225
+ let lastAgentEndMessages: Array<{ role?: string; stopReason?: string }> = [];
226
+ let pendingLoopPrompt: ReturnType<typeof setImmediate> | undefined;
216
227
 
217
228
  function persistState(state: LoopStateData): void {
218
229
  pi.appendEntry(LOOP_STATE_ENTRY, state);
@@ -271,6 +282,23 @@ export default function loopExtension(pi: ExtensionAPI): void {
271
282
  );
272
283
  }
273
284
 
285
+ function scheduleLoopPrompt(ctx: ExtensionContext): void {
286
+ if (pendingLoopPrompt) return;
287
+
288
+ pendingLoopPrompt = setImmediate(() => {
289
+ pendingLoopPrompt = undefined;
290
+ if (!loopState.active || !ctx.isIdle()) return;
291
+ triggerLoopPrompt(ctx);
292
+ });
293
+ pendingLoopPrompt.unref?.();
294
+ }
295
+
296
+ function cancelScheduledLoopPrompt(): void {
297
+ if (!pendingLoopPrompt) return;
298
+ clearImmediate(pendingLoopPrompt);
299
+ pendingLoopPrompt = undefined;
300
+ }
301
+
274
302
  async function showLoopSelector(ctx: ExtensionContext): Promise<LoopStateData | null> {
275
303
  const items: SelectItem[] = LOOP_PRESETS.map((preset) => ({
276
304
  value: preset.value,
@@ -385,11 +413,11 @@ export default function loopExtension(pi: ExtensionAPI): void {
385
413
  name: "signal_loop_success",
386
414
  label: "Signal Loop Success",
387
415
  description:
388
- "Stop the active loop when the breakout condition is satisfied. Only call this tool when explicitly instructed to do so by the user, tool or system prompt.",
416
+ "Stop the active loop when the breakout condition is satisfied. Only call signal_loop_success when explicitly instructed to do so by the user, tool, or system prompt.",
389
417
  promptSnippet: "Signal that the active /loop breakout condition has been satisfied",
390
418
  promptGuidelines: [
391
- "Call this tool only when the active loop's breakout condition is actually satisfied.",
392
- "Do not call this tool unless a loop is currently active.",
419
+ "Call signal_loop_success only when the active loop's breakout condition is actually satisfied.",
420
+ "Do not call signal_loop_success unless a loop is currently active.",
393
421
  ],
394
422
  parameters: Type.Object({}),
395
423
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
@@ -416,7 +444,7 @@ export default function loopExtension(pi: ExtensionAPI): void {
416
444
  handler: async (args, ctx) => {
417
445
  let nextState = parseArgs(args);
418
446
  if (!nextState) {
419
- if (!ctx.hasUI) {
447
+ if (ctx.mode !== "tui") {
420
448
  ctx.ui.notify("Usage: /loop tests | /loop custom <condition> | /loop self", "warning");
421
449
  return;
422
450
  }
@@ -458,10 +486,14 @@ export default function loopExtension(pi: ExtensionAPI): void {
458
486
  },
459
487
  });
460
488
 
461
- pi.on("agent_end", async (event, ctx) => {
489
+ pi.on("agent_end", async (event) => {
490
+ lastAgentEndMessages = event.messages;
491
+ });
492
+
493
+ pi.on("agent_settled", async (_event, ctx) => {
462
494
  if (!loopState.active) return;
463
495
 
464
- if (ctx.hasUI && wasLastAssistantAborted(event.messages)) {
496
+ if (ctx.hasUI && wasLastAssistantAborted(lastAgentEndMessages)) {
465
497
  const confirm = await withPromptSignal(pi, () =>
466
498
  ctx.ui.confirm("Break active loop?", "Operation aborted. Break out of the loop?"),
467
499
  );
@@ -471,13 +503,13 @@ export default function loopExtension(pi: ExtensionAPI): void {
471
503
  }
472
504
  }
473
505
 
474
- triggerLoopPrompt(ctx);
506
+ scheduleLoopPrompt(ctx);
475
507
  });
476
508
 
477
509
  pi.on("session_before_compact", async (event, ctx) => {
478
510
  if (!loopState.active || !loopState.mode || !ctx.model) return;
479
511
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
480
- if (!auth.ok || !auth.apiKey) return;
512
+ if (!auth.ok) return;
481
513
 
482
514
  const instructionParts = [
483
515
  event.customInstructions,
@@ -494,6 +526,9 @@ export default function loopExtension(pi: ExtensionAPI): void {
494
526
  auth.headers,
495
527
  instructionParts,
496
528
  event.signal,
529
+ ctx.thinkingLevel,
530
+ undefined, // Use Pi's default summarization stream.
531
+ auth.env,
497
532
  );
498
533
  return { compaction };
499
534
  } catch (error) {
@@ -526,4 +561,8 @@ export default function loopExtension(pi: ExtensionAPI): void {
526
561
  pi.on("session_start", async (_event, ctx) => {
527
562
  await restoreLoopState(ctx);
528
563
  });
564
+
565
+ pi.on("session_shutdown", async () => {
566
+ cancelScheduledLoopPrompt();
567
+ });
529
568
  }
@@ -1,8 +1,9 @@
1
+ import { complete } from "@earendil-works/pi-ai/compat";
1
2
  import {
2
- complete,
3
3
  StringEnum,
4
4
  type Api,
5
5
  type Model,
6
+ type Usage,
6
7
  type UserMessage,
7
8
  } from "@earendil-works/pi-ai";
8
9
  import {
@@ -176,6 +177,11 @@ type DreamResult = {
176
177
  summaryPath: string | null;
177
178
  };
178
179
 
180
+ type DreamExecution = {
181
+ result: DreamResult;
182
+ usage?: Usage;
183
+ };
184
+
179
185
  type AutoDreamStatus = {
180
186
  coreLines: number;
181
187
  coreChars: number;
@@ -194,6 +200,7 @@ type ModelSelection = {
194
200
  model: Model<Api>;
195
201
  apiKey?: string;
196
202
  headers?: Record<string, string>;
203
+ env?: Record<string, string>;
197
204
  };
198
205
 
199
206
  class MemoryReadmeMissingError extends Error {
@@ -209,12 +216,17 @@ async function toolDream(
209
216
  cwd: string,
210
217
  ctx: ExtensionContext,
211
218
  reason?: string,
212
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details: DreamResult }> {
219
+ ): Promise<{
220
+ content: Array<{ type: "text"; text: string }>;
221
+ details: DreamResult;
222
+ usage?: Usage;
223
+ }> {
213
224
  try {
214
- const result = await runMemoryDream(cwd, ctx, reason);
225
+ const execution = await runMemoryDream(cwd, ctx, reason);
215
226
  return {
216
- content: [{ type: "text", text: result.summary }],
217
- details: result,
227
+ content: [{ type: "text", text: execution.result.summary }],
228
+ details: execution.result,
229
+ usage: execution.usage,
218
230
  };
219
231
  } catch (error) {
220
232
  if (error instanceof MemoryReadmeMissingError) {
@@ -374,7 +386,7 @@ async function runMemoryDream(
374
386
  cwd: string,
375
387
  ctx: ExtensionContext,
376
388
  reason?: string,
377
- ): Promise<DreamResult> {
389
+ ): Promise<DreamExecution> {
378
390
  const paths = getMemoryPaths(cwd);
379
391
  const lockPaths = [
380
392
  paths.readmeFile,
@@ -399,13 +411,15 @@ async function runMemoryDream(
399
411
  const summary = "Memory dream: nothing to consolidate.";
400
412
  notify(ctx, summary, "info");
401
413
  return {
402
- summary,
403
- updatedBlocks: [],
404
- consumedLogs: 0,
405
- consumedCompactions: 0,
406
- lastDreamAt: snapshot.state?.lastDreamAt ?? nowIso(),
407
- lastDreamedLogAt: snapshot.state?.lastDreamedLogAt ?? null,
408
- summaryPath: null,
414
+ result: {
415
+ summary,
416
+ updatedBlocks: [],
417
+ consumedLogs: 0,
418
+ consumedCompactions: 0,
419
+ lastDreamAt: snapshot.state?.lastDreamAt ?? nowIso(),
420
+ lastDreamedLogAt: snapshot.state?.lastDreamedLogAt ?? null,
421
+ summaryPath: null,
422
+ },
409
423
  };
410
424
  }
411
425
 
@@ -419,6 +433,7 @@ async function runMemoryDream(
419
433
  {
420
434
  apiKey: selection.apiKey,
421
435
  headers: selection.headers,
436
+ env: selection.env,
422
437
  signal: ctx.signal,
423
438
  },
424
439
  );
@@ -469,7 +484,7 @@ async function runMemoryDream(
469
484
  );
470
485
  }
471
486
 
472
- return withMemoryMutationQueue(lockPaths, async () => {
487
+ const result = await withMemoryMutationQueue(lockPaths, async () => {
473
488
  ensureMemoryInitialized(await pathExists(paths.memoryRoot));
474
489
 
475
490
  const currentReplay = await collectDreamReplay(cwd);
@@ -517,6 +532,8 @@ async function runMemoryDream(
517
532
  summaryPath,
518
533
  };
519
534
  });
535
+
536
+ return { result, usage: response.usage };
520
537
  }
521
538
 
522
539
  async function loadMemoryPrompt(cwd: string): Promise<string | undefined> {
@@ -967,6 +984,7 @@ async function selectDreamModel(ctx: ExtensionContext): Promise<ModelSelection>
967
984
  model: ctx.model,
968
985
  apiKey: auth.apiKey,
969
986
  headers: auth.headers,
987
+ env: auth.env,
970
988
  };
971
989
  }
972
990
 
@@ -1710,7 +1728,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
1710
1728
  description: `Update one .agents/memory/core block while enforcing the shared ${CORE_LINE_CAP}-line and ${CORE_CHAR_CAP}-character caps`,
1711
1729
  promptSnippet: `Update one core memory block in .agents/memory/core with ${CORE_LINE_CAP}-line and ${CORE_CHAR_CAP}-character cap enforcement`,
1712
1730
  promptGuidelines: [
1713
- "Use this tool when updating .agents/memory/core/directives.md, context.md, focus.md, or pending.md.",
1731
+ "Use memory_update_block when updating .agents/memory/core/directives.md, context.md, focus.md, or pending.md.",
1714
1732
  `If a write would exceed the ${CORE_LINE_CAP}-line or ${CORE_CHAR_CAP}-character core cap, run memory_dream or remove content first.`,
1715
1733
  ],
1716
1734
  parameters: MEMORY_UPDATE_BLOCK_PARAMS,
@@ -1741,7 +1759,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
1741
1759
  description: "Append an entry to .agents/memory/log.md using the repo memory log format",
1742
1760
  promptSnippet: "Append an entry to the repo memory log at .agents/memory/log.md",
1743
1761
  promptGuidelines: [
1744
- "Use this tool for important decisions, discoveries, plans, experiments, prompt ingests, and attachment additions worth remembering.",
1762
+ "Use memory_append_log for important decisions, discoveries, plans, experiments, prompt ingests, and attachment additions worth remembering.",
1745
1763
  "Use importance labels high, medium, or low.",
1746
1764
  "If this entry replaces or corrects prior memory, set supersedes and/or invalidates links explicitly.",
1747
1765
  "The memory log is append-only. Do not rewrite or truncate older entries.",
@@ -1779,7 +1797,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
1779
1797
  promptSnippet: "Consolidate repo memory with dream-based core compression",
1780
1798
  promptGuidelines: [
1781
1799
  "Dream is the only consolidation mechanism for .agents/memory/core.",
1782
- "Use this tool when logs have accumulated, core needs compression, or recent compaction context should be folded into memory.",
1800
+ "Use memory_dream when logs have accumulated, core needs compression, or recent compaction context should be folded into memory.",
1783
1801
  ],
1784
1802
  parameters: MEMORY_DREAM_PARAMS,
1785
1803
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -10,6 +10,7 @@
10
10
  * - Windows toast: Windows Terminal (WSL)
11
11
  */
12
12
 
13
+ import { execFile } from "node:child_process";
13
14
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
15
 
15
16
  const REVIEW_EVENT_START = "review:start";
@@ -39,7 +40,6 @@ function notifyOSC99(title: string, body: string): void {
39
40
  }
40
41
 
41
42
  function notifyWindows(title: string, body: string): void {
42
- const { execFile } = require("child_process");
43
43
  execFile("powershell.exe", ["-NoProfile", "-Command", windowsToastScript(title, body)]);
44
44
  }
45
45
 
@@ -92,13 +92,33 @@ export default function (pi: ExtensionAPI) {
92
92
  let pendingPromptCount = 0;
93
93
  const activeReviewSessions = new Set<string>();
94
94
  let currentSessionKey: string | undefined;
95
+ let pendingReadyNotification: ReturnType<typeof setImmediate> | undefined;
95
96
 
96
97
  const hasCurrentSessionReviewRun = () => {
97
98
  if (!currentSessionKey) return false;
98
99
  return activeReviewSessions.has(currentSessionKey);
99
100
  };
100
101
 
102
+ const cancelReadyNotification = () => {
103
+ if (!pendingReadyNotification) return;
104
+ clearImmediate(pendingReadyNotification);
105
+ pendingReadyNotification = undefined;
106
+ };
107
+
108
+ const scheduleReadyNotification = (ctx: ExtensionContext) => {
109
+ cancelReadyNotification();
110
+ pendingReadyNotification = setImmediate(() => {
111
+ pendingReadyNotification = undefined;
112
+ if (!ctx.isIdle()) return;
113
+ if (pendingPromptCount > 0) return;
114
+ if (hasCurrentSessionReviewRun()) return;
115
+ notify("Pi", "Ready for input");
116
+ });
117
+ pendingReadyNotification.unref?.();
118
+ };
119
+
101
120
  pi.on("session_start", async (_event, ctx) => {
121
+ cancelReadyNotification();
102
122
  currentSessionKey = getSessionKey(ctx);
103
123
  });
104
124
 
@@ -143,14 +163,13 @@ export default function (pi: ExtensionAPI) {
143
163
  notify("Pi", "Review failed");
144
164
  });
145
165
 
146
- pi.on("agent_end", async (_event, ctx) => {
166
+ pi.on("agent_settled", async (_event, ctx) => {
147
167
  currentSessionKey = getSessionKey(ctx);
148
- if (pendingPromptCount > 0) return;
149
- if (hasCurrentSessionReviewRun()) return;
150
- notify("Pi", "Ready for input");
168
+ scheduleReadyNotification(ctx);
151
169
  });
152
170
 
153
171
  pi.on("session_shutdown", async (_event, ctx) => {
172
+ cancelReadyNotification();
154
173
  pendingPromptCount = 0;
155
174
  const sessionKey = getSessionKey(ctx);
156
175
  activeReviewSessions.delete(sessionKey);
@@ -1,7 +1,10 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import {
4
+ getAgentDir,
5
+ type ExtensionAPI,
6
+ type ExtensionContext,
7
+ } from "@earendil-works/pi-coding-agent";
5
8
 
6
9
  const STATUS_KEY = "openai-fast";
7
10
  const SUPPORTED_APIS = new Set([
@@ -23,8 +26,7 @@ function emptyConfig(): FastConfig {
23
26
  }
24
27
 
25
28
  function getConfigPath(): string {
26
- const agentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent");
27
- return path.join(agentDir, "openai-fast.json");
29
+ return path.join(getAgentDir(), "openai-fast.json");
28
30
  }
29
31
 
30
32
  function isObject(value: unknown): value is JsonObject {
@@ -1,7 +1,10 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import {
4
+ getAgentDir,
5
+ type ExtensionAPI,
6
+ type ExtensionContext,
7
+ } from "@earendil-works/pi-coding-agent";
5
8
 
6
9
  const STATUS_KEY = "openai-verbosity";
7
10
  const SUPPORTED_APIS = new Set([
@@ -23,8 +26,7 @@ function emptyConfig(): VerbosityConfig {
23
26
  }
24
27
 
25
28
  function getConfigPath(): string {
26
- const agentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent");
27
- return path.join(agentDir, "openai-verbosity.json");
29
+ return path.join(getAgentDir(), "openai-verbosity.json");
28
30
  }
29
31
 
30
32
  function isObject(value: unknown): value is JsonObject {