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
@@ -0,0 +1,239 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ ExtensionContext,
5
+ } from "@earendil-works/pi-coding-agent";
6
+
7
+ import type { ReviewMessageQueue } from "./message-queue.js";
8
+ import { buildAdditionalContextSection, FIX_PROMPT } from "./prompts.js";
9
+ import { reviewMatchesFixRequest } from "./request.js";
10
+ import { computeCurrentFingerprint, fingerprintsEqual } from "./git.js";
11
+ import {
12
+ buildStalePayloadStaleness,
13
+ getLastMessageReviewDetails,
14
+ runReviewPipeline,
15
+ } from "./review.js";
16
+ import {
17
+ notify,
18
+ REVIEW_CANCELLED_ERROR,
19
+ type AgentEndMessage,
20
+ type AgentEndMessages,
21
+ type FixPassAgentTracker,
22
+ } from "./runtime.js";
23
+ import type { ParsedRequest, ReviewMessageDetails } from "./schema.js";
24
+
25
+ const FIX_PASS_START_GRACE_MS = 1_000;
26
+
27
+ const REVIEW_STALE_REUSE_WARNING =
28
+ "Last review is stale. Continuing with /fix. Run /review first to refresh it.";
29
+
30
+ function buildFixPrompt(
31
+ reviewMessageDetails: ReviewMessageDetails,
32
+ additionalContext: string | undefined,
33
+ ): string {
34
+ const worklistPayload = JSON.stringify(
35
+ {
36
+ scope: reviewMessageDetails.scope,
37
+ ...(reviewMessageDetails.staleness ? { staleness: reviewMessageDetails.staleness } : {}),
38
+ findings: reviewMessageDetails.findings.map(
39
+ ({ priority, location, finding, suggestion, focus }) => ({
40
+ priority,
41
+ location,
42
+ finding,
43
+ suggestion,
44
+ focus,
45
+ }),
46
+ ),
47
+ },
48
+ null,
49
+ 2,
50
+ );
51
+ const additionalContextSection = buildAdditionalContextSection(additionalContext);
52
+
53
+ return FIX_PROMPT.replace(
54
+ "{FIX_ADDITIONAL_CONTEXT_SECTION}",
55
+ () => additionalContextSection,
56
+ ).replace("{REVIEW_FINDINGS_JSON}", () => worklistPayload);
57
+ }
58
+
59
+ export async function prepareFixReviewDetails(
60
+ pi: ExtensionAPI,
61
+ ctx: ExtensionCommandContext,
62
+ request: ParsedRequest,
63
+ forceFreshReview = false,
64
+ ): Promise<ReviewMessageDetails | null> {
65
+ let reviewDetails = forceFreshReview ? null : getLastMessageReviewDetails(ctx);
66
+
67
+ if (reviewDetails && reviewMatchesFixRequest(reviewDetails, request)) {
68
+ const currentFingerprint = await computeCurrentFingerprint(
69
+ pi,
70
+ ctx.cwd,
71
+ reviewDetails.scope.mode === "working-tree" || reviewDetails.scope.mode === "folder",
72
+ );
73
+ const hasStalePayload = reviewDetails.staleness?.status === "stale";
74
+ const isStaleNow = !fingerprintsEqual(reviewDetails.fingerprint, currentFingerprint);
75
+ if (hasStalePayload || isStaleNow) {
76
+ reviewDetails = {
77
+ ...reviewDetails,
78
+ staleness: reviewDetails.staleness ?? buildStalePayloadStaleness(),
79
+ };
80
+ notify(ctx, REVIEW_STALE_REUSE_WARNING, "warning");
81
+ }
82
+ } else {
83
+ const reviewResult = await runReviewPipeline(pi, ctx, request, "fix");
84
+ if (!reviewResult.ok) {
85
+ if (reviewResult.error === REVIEW_CANCELLED_ERROR) {
86
+ notify(ctx, REVIEW_CANCELLED_ERROR, "error");
87
+ } else {
88
+ notify(ctx, `Cannot continue to /fix: ${reviewResult.error}`, "error");
89
+ }
90
+ return null;
91
+ }
92
+ reviewDetails = reviewResult.details;
93
+ if (reviewDetails.staleness?.status === "stale") {
94
+ notify(ctx, reviewDetails.staleness.nextStep, "warning");
95
+ return null;
96
+ }
97
+ }
98
+
99
+ if (reviewDetails.findings.length === 0) {
100
+ const failedFocusCount = countFailedFocusRuns(reviewDetails);
101
+ if (failedFocusCount > 0) {
102
+ notify(
103
+ ctx,
104
+ `Latest partial review had ${failedFocusCount} failed focus run(s) and no findings. Rerun /review before /fix.`,
105
+ "warning",
106
+ );
107
+ return null;
108
+ }
109
+
110
+ notify(ctx, "Review produced no findings. Nothing to fix.", "info");
111
+ return null;
112
+ }
113
+
114
+ return reviewDetails;
115
+ }
116
+
117
+ function countFailedFocusRuns(reviewDetails: ReviewMessageDetails): number {
118
+ return reviewDetails.focusStatus.filter((focus) => !focus.ok).length;
119
+ }
120
+
121
+ function notifyFixUsedPartialReview(ctx: ExtensionContext, failedFocusCount: number): void {
122
+ if (failedFocusCount === 0) return;
123
+ const failedLabel = `review${failedFocusCount === 1 ? "" : "s"}`;
124
+ notify(
125
+ ctx,
126
+ `Fix pass used partial review results: ${failedFocusCount} ${failedLabel} failed.`,
127
+ "warning",
128
+ );
129
+ }
130
+
131
+ function queueFixPass(
132
+ pi: ExtensionAPI,
133
+ ctx: ExtensionCommandContext,
134
+ reviewDetails: ReviewMessageDetails,
135
+ additionalContext: string | undefined,
136
+ options: { forceFollowUp?: boolean } = {},
137
+ ): { startsImmediately: boolean } {
138
+ const fixPrompt = buildFixPrompt(reviewDetails, additionalContext);
139
+ const startsImmediately = ctx.isIdle();
140
+ const delivery =
141
+ options.forceFollowUp || !startsImmediately || ctx.hasPendingMessages()
142
+ ? { deliverAs: "followUp" as const }
143
+ : undefined;
144
+ pi.sendUserMessage(fixPrompt, delivery);
145
+ notify(ctx, "Queued autonomous fix pass from review findings.", "info");
146
+ return { startsImmediately };
147
+ }
148
+
149
+ function getLastAssistantMessage(messages: AgentEndMessages): AgentEndMessage | undefined {
150
+ return messages.findLast((message) => message?.role === "assistant");
151
+ }
152
+
153
+ function wasLastAssistantAborted(messages: AgentEndMessages): boolean {
154
+ return getLastAssistantMessage(messages)?.stopReason === "aborted";
155
+ }
156
+
157
+ async function waitForPromptStartIfImmediate(
158
+ agentTracker: FixPassAgentTracker,
159
+ startCountBeforePrompt: number,
160
+ startsImmediately: boolean,
161
+ ): Promise<void> {
162
+ if (!startsImmediately) return;
163
+ await agentTracker.waitForStartAfter(startCountBeforePrompt, FIX_PASS_START_GRACE_MS);
164
+ }
165
+
166
+ async function waitForFixPassCompletion(
167
+ agentTracker: FixPassAgentTracker,
168
+ ): Promise<AgentEndMessages> {
169
+ await agentTracker.waitForNextSettled();
170
+ return agentTracker.getLastEnd()?.messages ?? [];
171
+ }
172
+
173
+ export async function runFixPassFromReview(
174
+ pi: ExtensionAPI,
175
+ ctx: ExtensionCommandContext,
176
+ reviewDetails: ReviewMessageDetails,
177
+ additionalContext: string | undefined,
178
+ agentTracker: FixPassAgentTracker,
179
+ reviewMessageQueue: ReviewMessageQueue,
180
+ ): Promise<AgentEndMessages> {
181
+ const failedFocusCount = countFailedFocusRuns(reviewDetails);
182
+ const fixPassFinished = waitForFixPassCompletion(agentTracker);
183
+
184
+ const startCountBeforeSteering = agentTracker.getStartCount();
185
+ const steeringStartsImmediately = ctx.isIdle();
186
+ const sentSteering = reviewMessageQueue.flushSteering(ctx, { forceFollowUp: true });
187
+ await waitForPromptStartIfImmediate(
188
+ agentTracker,
189
+ startCountBeforeSteering,
190
+ sentSteering && steeringStartsImmediately,
191
+ );
192
+
193
+ const startCountBeforeFix = agentTracker.getStartCount();
194
+ const fixPass = queueFixPass(pi, ctx, reviewDetails, additionalContext, {
195
+ forceFollowUp: sentSteering,
196
+ });
197
+ await waitForPromptStartIfImmediate(agentTracker, startCountBeforeFix, fixPass.startsImmediately);
198
+
199
+ const fixMessages = await fixPassFinished;
200
+ if (!wasLastAssistantAborted(fixMessages)) {
201
+ notifyFixUsedPartialReview(ctx, failedFocusCount);
202
+ }
203
+ return fixMessages;
204
+ }
205
+
206
+ export async function runFixLoop(
207
+ pi: ExtensionAPI,
208
+ ctx: ExtensionCommandContext,
209
+ request: ParsedRequest,
210
+ agentTracker: FixPassAgentTracker,
211
+ reviewMessageQueue: ReviewMessageQueue,
212
+ ): Promise<void> {
213
+ for (;;) {
214
+ const reviewDetails = await prepareFixReviewDetails(pi, ctx, request, true);
215
+ if (!reviewDetails) return;
216
+
217
+ const beforeFixFingerprint = reviewDetails.fingerprint;
218
+ const fixMessages = await runFixPassFromReview(
219
+ pi,
220
+ ctx,
221
+ reviewDetails,
222
+ request.additionalContext,
223
+ agentTracker,
224
+ reviewMessageQueue,
225
+ );
226
+ if (wasLastAssistantAborted(fixMessages)) {
227
+ notify(ctx, "Fix loop stopped: fix pass was aborted.", "warning");
228
+ return;
229
+ }
230
+
231
+ const afterFixFingerprint = await computeCurrentFingerprint(pi, ctx.cwd, true);
232
+ if (fingerprintsEqual(beforeFixFingerprint, afterFixFingerprint)) {
233
+ notify(ctx, "Fix loop stopped: fix pass made no repository changes.", "warning");
234
+ return;
235
+ }
236
+
237
+ notify(ctx, "Fix loop continuing with a fresh review...", "info");
238
+ }
239
+ }