tau-all-agent 0.1.2 → 0.1.4
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.
- package/README.md +23 -22
- package/extensions/answer.ts +4 -4
- package/extensions/branch-term.ts +3 -3
- package/extensions/btw.ts +4 -4
- package/extensions/ghostty.ts +1 -1
- package/extensions/git-diff-stats.ts +1 -1
- package/extensions/git-pr-status.ts +1 -1
- package/extensions/insights.ts +3 -3
- package/extensions/interlude.ts +1 -1
- package/extensions/loop.ts +3 -3
- package/extensions/memory.ts +8 -2
- package/extensions/notify.ts +1 -1
- package/extensions/openai-fast.ts +1 -1
- package/extensions/openai-verbosity.ts +1 -1
- package/extensions/review.ts +111 -38
- package/extensions/sandbox/index.ts +84 -8
- package/extensions/telegram/daemon.mjs +148 -7
- package/extensions/telegram/index.ts +1 -1
- package/extensions/tool-display-mode.ts +544 -0
- package/extensions/usage/anthropic.ts +1 -1
- package/extensions/usage/github-copilot.ts +1 -1
- package/extensions/usage/index.ts +3 -3
- package/extensions/usage/minimax.ts +1 -1
- package/extensions/usage/openai-codex.ts +1 -1
- package/extensions/usage/openrouter.ts +1 -1
- package/extensions/usage/providers.ts +0 -2
- package/extensions/usage/types.ts +1 -2
- package/extensions/usage/zai.ts +1 -1
- package/extensions/websearch/index.ts +3 -3
- package/extensions/websearch/providers/anthropic.pi.ts +1 -1
- package/extensions/websearch/providers/gemini.pi.ts +1 -1
- package/extensions/websearch/providers/openai-codex.pi.ts +1 -1
- package/extensions/websearch/providers/pi-model.shared.ts +2 -2
- package/extensions/worktree.ts +3 -3
- package/package.json +23 -8
- package/skills/oracle/scripts/oracle +0 -1
- package/extensions/usage/google-gemini-cli.ts +0 -232
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# tau-all-agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All-purpose agent for [Pi](https://pi.dev).
|
|
4
4
|
|
|
5
5
|
Tau turns Pi's minimal core into an opinionated, polished agent setup with the full Tau feature set: coding workflows, web search, code review, sandboxing, local memory, usage reporting, Telegram access, Home Assistant operations, OpenSCAD modeling, and a curated skill set.
|
|
6
6
|
|
|
@@ -18,27 +18,28 @@ pi install -l npm:tau-all-agent
|
|
|
18
18
|
|
|
19
19
|
## Extensions
|
|
20
20
|
|
|
21
|
-
| Extension
|
|
22
|
-
|
|
|
23
|
-
| `answer`
|
|
24
|
-
| `branch-term`
|
|
25
|
-
| `btw`
|
|
26
|
-
| `ghostty`
|
|
27
|
-
| `git-diff-stats`
|
|
28
|
-
| `git-pr-status`
|
|
29
|
-
| `insights`
|
|
30
|
-
| `interlude`
|
|
31
|
-
| `loop`
|
|
32
|
-
| `memory`
|
|
33
|
-
| `notify`
|
|
34
|
-
| `openai-fast`
|
|
35
|
-
| `openai-verbosity`
|
|
36
|
-
| `review`
|
|
37
|
-
| `sandbox`
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
21
|
+
| Extension | Command | Description |
|
|
22
|
+
| ------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `answer` | `/answer` | Extract and interactively answer agent questions. |
|
|
24
|
+
| `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
|
|
25
|
+
| `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
|
|
26
|
+
| `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
|
|
27
|
+
| `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
|
|
28
|
+
| `git-pr-status` | — | Status bar PR number and link for the current branch. |
|
|
29
|
+
| `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
|
|
30
|
+
| `interlude` | `alt+x` | Stash the current message draft, send one interlude message, then restore the draft. |
|
|
31
|
+
| `loop` | `/loop` | Repeat a prompt until the agent signals success. |
|
|
32
|
+
| `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
|
|
33
|
+
| `notify` | — | Terminal notification when the agent is waiting for input. |
|
|
34
|
+
| `openai-fast` | `/fast` | Toggle priority service tier for supported OpenAI models. |
|
|
35
|
+
| `openai-verbosity` | `/verbosity` | Set verbosity for supported OpenAI models. |
|
|
36
|
+
| `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
|
|
37
|
+
| `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
|
|
38
|
+
| `tool-display-mode` | `ctrl+o` | Cycle tool output between Pi's default rendering, expanded output, and compact summaries. |
|
|
39
|
+
| `telegram` | `/telegram` | Interact with Pi via a Telegram bot and local daemon. |
|
|
40
|
+
| `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
|
|
41
|
+
| `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
|
|
42
|
+
| `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
|
|
42
43
|
|
|
43
44
|
## Skills
|
|
44
45
|
|
package/extensions/answer.ts
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* 4. Submits the compiled answers when done
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { complete, type Model, type Api, type UserMessage } from "@
|
|
14
|
-
import type { ExtensionAPI } from "@
|
|
15
|
-
import { BorderedLoader } from "@
|
|
13
|
+
import { complete, type Model, type Api, type UserMessage } from "@earendil-works/pi-ai";
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import {
|
|
17
17
|
type Component,
|
|
18
18
|
Editor,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type TUI,
|
|
24
24
|
visibleWidth,
|
|
25
25
|
wrapTextWithAnsi,
|
|
26
|
-
} from "@
|
|
26
|
+
} from "@earendil-works/pi-tui";
|
|
27
27
|
|
|
28
28
|
const SYSTEM_PROMPT = `You extract items that need user input from assistant text:
|
|
29
29
|
|
|
@@ -2,14 +2,14 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@
|
|
5
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import {
|
|
7
7
|
CURRENT_SESSION_VERSION,
|
|
8
8
|
SessionManager,
|
|
9
9
|
highlightCode,
|
|
10
10
|
type SessionHeader,
|
|
11
|
-
} from "@
|
|
12
|
-
import { Box, Text } from "@
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { Box, Text } from "@earendil-works/pi-tui";
|
|
13
13
|
|
|
14
14
|
const TERMINAL_FLAG = "branch-term";
|
|
15
15
|
const TMUX_LAYOUT_FLAG = "branch-tmux-layout";
|
package/extensions/btw.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AgentMessage } from "@
|
|
2
|
-
import type { AssistantMessage, ThinkingLevel } from "@
|
|
1
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { AssistantMessage, ThinkingLevel } from "@earendil-works/pi-ai";
|
|
3
3
|
import {
|
|
4
4
|
DefaultResourceLoader,
|
|
5
5
|
SessionManager,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type ExtensionCommandContext,
|
|
12
12
|
type ExtensionContext,
|
|
13
13
|
type Theme,
|
|
14
|
-
} from "@
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import {
|
|
16
16
|
Key,
|
|
17
17
|
Markdown,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type TUI,
|
|
22
22
|
visibleWidth,
|
|
23
23
|
wrapTextWithAnsi,
|
|
24
|
-
} from "@
|
|
24
|
+
} from "@earendil-works/pi-tui";
|
|
25
25
|
|
|
26
26
|
const STATUS_KEY = "0-btw";
|
|
27
27
|
const RESULT_MARKDOWN_THEME = getMarkdownTheme();
|
package/extensions/ghostty.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Pi now emits native OSC 9;4 progress indicators, so this extension only manages the title.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
13
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
|
|
16
16
|
const STATUS_SPINNER_INTERVAL_MS = 80;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
isWriteToolResult,
|
|
4
4
|
type ExtensionAPI,
|
|
5
5
|
type ExtensionContext,
|
|
6
|
-
} from "@
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { copyFile, mkdtemp, rm } from "node:fs/promises";
|
package/extensions/insights.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { complete, type Api, type Model, type UserMessage } from "@
|
|
1
|
+
import { complete, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
|
|
2
2
|
import {
|
|
3
3
|
BorderedLoader,
|
|
4
4
|
SessionManager,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type SessionEntry,
|
|
14
14
|
type SessionHeader,
|
|
15
15
|
type Theme,
|
|
16
|
-
} from "@
|
|
16
|
+
} from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import {
|
|
18
18
|
Key,
|
|
19
19
|
Markdown,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type TUI,
|
|
24
24
|
visibleWidth,
|
|
25
25
|
wrapTextWithAnsi,
|
|
26
|
-
} from "@
|
|
26
|
+
} from "@earendil-works/pi-tui";
|
|
27
27
|
import { createHash, randomBytes } from "node:crypto";
|
|
28
28
|
import fs from "node:fs/promises";
|
|
29
29
|
import path from "node:path";
|
package/extensions/interlude.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
const DEFAULT_SHORTCUTS = ["alt+x"] as const;
|
|
7
7
|
const STATUS_KEY = "interlude";
|
package/extensions/loop.ts
CHANGED
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { Type } from "typebox";
|
|
10
|
-
import { complete, type Api, type Model, type UserMessage } from "@
|
|
10
|
+
import { complete, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
|
|
11
11
|
import {
|
|
12
12
|
compact,
|
|
13
13
|
defineTool,
|
|
14
14
|
DynamicBorder,
|
|
15
15
|
type ExtensionAPI,
|
|
16
16
|
type ExtensionContext,
|
|
17
|
-
} from "@
|
|
18
|
-
import { Container, type SelectItem, SelectList, Text } from "@
|
|
17
|
+
} from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
|
|
19
19
|
|
|
20
20
|
const LOOP_PRESETS = [
|
|
21
21
|
{ value: "tests", label: "Until tests pass", description: "" },
|
package/extensions/memory.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
complete,
|
|
3
|
+
StringEnum,
|
|
4
|
+
type Api,
|
|
5
|
+
type Model,
|
|
6
|
+
type UserMessage,
|
|
7
|
+
} from "@earendil-works/pi-ai";
|
|
2
8
|
import {
|
|
3
9
|
defineTool,
|
|
4
10
|
withFileMutationQueue,
|
|
5
11
|
type ExtensionAPI,
|
|
6
12
|
type ExtensionCommandContext,
|
|
7
13
|
type ExtensionContext,
|
|
8
|
-
} from "@
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
15
|
import { Type } from "typebox";
|
|
10
16
|
import fs from "node:fs/promises";
|
|
11
17
|
import path from "node:path";
|
package/extensions/notify.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Windows toast: Windows Terminal (WSL)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
13
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
|
|
15
15
|
const REVIEW_EVENT_START = "review:start";
|
|
16
16
|
const REVIEW_EVENT_END = "review:end";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
const STATUS_KEY = "openai-fast";
|
|
7
7
|
const SUPPORTED_APIS = new Set([
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
const STATUS_KEY = "openai-verbosity";
|
|
7
7
|
const SUPPORTED_APIS = new Set([
|
package/extensions/review.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* when that message is a matching review payload, even if stale. Otherwise it
|
|
11
11
|
* runs a fresh review first. If that fresh review becomes stale while running,
|
|
12
12
|
* /fix shows the findings and waits for an explicit rerun before applying fixes.
|
|
13
|
+
* context=... guides the fix pass without invalidating reusable review payloads.
|
|
13
14
|
*
|
|
14
15
|
* Key behavior:
|
|
15
16
|
* - /review is findings-only (no direct edits).
|
|
@@ -37,9 +38,9 @@ import type {
|
|
|
37
38
|
ExtensionAPI,
|
|
38
39
|
ExtensionCommandContext,
|
|
39
40
|
ExtensionContext,
|
|
40
|
-
} from "@
|
|
41
|
-
import {
|
|
42
|
-
import { matchesKey } from "@
|
|
41
|
+
} from "@earendil-works/pi-coding-agent";
|
|
42
|
+
import { getSupportedThinkingLevels, type Api, type Model } from "@earendil-works/pi-ai";
|
|
43
|
+
import { matchesKey } from "@earendil-works/pi-tui";
|
|
43
44
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
44
45
|
import { createHash } from "node:crypto";
|
|
45
46
|
import path from "node:path";
|
|
@@ -173,7 +174,7 @@ const REVIEW_FOCUSES: Record<FocusName, FocusDefinition> = {
|
|
|
173
174
|
},
|
|
174
175
|
};
|
|
175
176
|
|
|
176
|
-
const
|
|
177
|
+
const ADDITIONAL_CONTEXT_SECTION_PROMPT = `Additional context from user:
|
|
177
178
|
{ADDITIONAL_CONTEXT}
|
|
178
179
|
`;
|
|
179
180
|
|
|
@@ -245,7 +246,7 @@ Output formatting requirements:
|
|
|
245
246
|
- Do not include the pipe character in any cell text (including inside backticks). Avoid regex alternation patterns like (a|b); rewrite checks without pipes and separate multiple checks with semicolons.
|
|
246
247
|
- Decision values must be exactly fixed or skipped.
|
|
247
248
|
|
|
248
|
-
Review findings:
|
|
249
|
+
{FIX_ADDITIONAL_CONTEXT_SECTION}Review findings:
|
|
249
250
|
|
|
250
251
|
{REVIEW_FINDINGS_JSON}
|
|
251
252
|
|
|
@@ -430,10 +431,16 @@ type ParsedRequest = {
|
|
|
430
431
|
target: ReviewTarget;
|
|
431
432
|
mode: ReviewRequestMode;
|
|
432
433
|
models: string[];
|
|
433
|
-
|
|
434
|
+
targetExplicit: boolean;
|
|
434
435
|
additionalContext?: string;
|
|
435
436
|
};
|
|
436
437
|
|
|
438
|
+
type RequestSignaturePayload = {
|
|
439
|
+
target: unknown;
|
|
440
|
+
models: string[];
|
|
441
|
+
additionalContext?: string | null;
|
|
442
|
+
};
|
|
443
|
+
|
|
437
444
|
type ReviewFingerprint = {
|
|
438
445
|
headSha: string;
|
|
439
446
|
branch: string;
|
|
@@ -878,7 +885,9 @@ Run findings-only code review in 4 parallel focuses (general, reuse, quality, ef
|
|
|
878
885
|
|
|
879
886
|
### Options
|
|
880
887
|
- \`models=<a,b>\`: run all review focuses for each listed model.
|
|
881
|
-
- \`context=<text>\`: add extra guidance
|
|
888
|
+
- \`context=<text>\`: add extra guidance.
|
|
889
|
+
- For \`/review\`, guides every review focus.
|
|
890
|
+
- For \`/fix\`, guides the fix pass; if \`/fix\` must run a fresh review, it guides review too.
|
|
882
891
|
- For spaces, quote the value, e.g. \`context="security and backpressure"\`.
|
|
883
892
|
|
|
884
893
|
### Examples
|
|
@@ -891,6 +900,7 @@ Run findings-only code review in 4 parallel focuses (general, reuse, quality, ef
|
|
|
891
900
|
- \`/triage https://github.com/owner/repo/pull/123\`
|
|
892
901
|
- \`/fix\`
|
|
893
902
|
- \`/fix help\`
|
|
903
|
+
- \`/fix context="do not test mocks"\`
|
|
894
904
|
- \`/fix branch main models=sonnet\`
|
|
895
905
|
|
|
896
906
|
### /fix behavior
|
|
@@ -931,7 +941,12 @@ function parseKeyValueOption(
|
|
|
931
941
|
function parseRequestArgs(args: string | undefined): ParsedRequest {
|
|
932
942
|
const raw = args?.trim() ?? "";
|
|
933
943
|
if (!raw) {
|
|
934
|
-
return {
|
|
944
|
+
return {
|
|
945
|
+
target: { type: "auto" },
|
|
946
|
+
mode: "auto",
|
|
947
|
+
models: [],
|
|
948
|
+
targetExplicit: false,
|
|
949
|
+
};
|
|
935
950
|
}
|
|
936
951
|
|
|
937
952
|
const tokens = tokenizeArgs(raw);
|
|
@@ -984,16 +999,16 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
|
|
|
984
999
|
return "custom";
|
|
985
1000
|
}
|
|
986
1001
|
};
|
|
987
|
-
const withMeta = (target: ReviewTarget): ParsedRequest => ({
|
|
1002
|
+
const withMeta = (target: ReviewTarget, targetExplicit: boolean): ParsedRequest => ({
|
|
988
1003
|
target,
|
|
989
1004
|
mode: toMode(target),
|
|
990
1005
|
models,
|
|
991
|
-
|
|
1006
|
+
targetExplicit,
|
|
992
1007
|
additionalContext,
|
|
993
1008
|
});
|
|
994
1009
|
|
|
995
1010
|
if (modeTokens.length === 0) {
|
|
996
|
-
return withMeta({ type: "auto" });
|
|
1011
|
+
return withMeta({ type: "auto" }, false);
|
|
997
1012
|
}
|
|
998
1013
|
|
|
999
1014
|
const mode = modeTokens[0].toLowerCase();
|
|
@@ -1004,7 +1019,7 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
|
|
|
1004
1019
|
throw new Error(
|
|
1005
1020
|
`${mode} mode does not accept positional args. Use models=... and/or context=...`,
|
|
1006
1021
|
);
|
|
1007
|
-
return withMeta({ type: mode });
|
|
1022
|
+
return withMeta({ type: mode }, true);
|
|
1008
1023
|
}
|
|
1009
1024
|
|
|
1010
1025
|
if (mode === "branch" || mode === "commit" || mode === "pr") {
|
|
@@ -1023,20 +1038,20 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
|
|
|
1023
1038
|
);
|
|
1024
1039
|
}
|
|
1025
1040
|
|
|
1026
|
-
if (mode === "branch") return withMeta({ type: "branch", branch: rest[0] });
|
|
1027
|
-
if (mode === "commit") return withMeta({ type: "commit", sha: rest[0] });
|
|
1028
|
-
return withMeta({ type: "pr", ref: rest[0] });
|
|
1041
|
+
if (mode === "branch") return withMeta({ type: "branch", branch: rest[0] }, true);
|
|
1042
|
+
if (mode === "commit") return withMeta({ type: "commit", sha: rest[0] }, true);
|
|
1043
|
+
return withMeta({ type: "pr", ref: rest[0] }, true);
|
|
1029
1044
|
}
|
|
1030
1045
|
if (mode === "folder") {
|
|
1031
1046
|
if (rest.length === 0)
|
|
1032
1047
|
throw new Error("folder mode requires at least one path (e.g. /review folder src docs)");
|
|
1033
|
-
return withMeta({ type: "folder", paths: rest.map((p) => p.trim()).filter(Boolean) });
|
|
1048
|
+
return withMeta({ type: "folder", paths: rest.map((p) => p.trim()).filter(Boolean) }, true);
|
|
1034
1049
|
}
|
|
1035
1050
|
if (mode === "custom") {
|
|
1036
1051
|
const instructions = rest.join(" ").trim();
|
|
1037
1052
|
if (!instructions)
|
|
1038
1053
|
throw new Error('custom mode requires instructions (e.g. /review custom "focus on auth")');
|
|
1039
|
-
return withMeta({ type: "custom", instructions });
|
|
1054
|
+
return withMeta({ type: "custom", instructions }, true);
|
|
1040
1055
|
}
|
|
1041
1056
|
|
|
1042
1057
|
throw new Error(
|
|
@@ -1044,7 +1059,14 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
|
|
|
1044
1059
|
);
|
|
1045
1060
|
}
|
|
1046
1061
|
|
|
1047
|
-
function
|
|
1062
|
+
function normalizeRequestModels(models: string[]): string[] {
|
|
1063
|
+
return Array.from(new Set(models)).sort((a, b) => a.localeCompare(b));
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function buildRequestSignaturePayload(
|
|
1067
|
+
request: ParsedRequest,
|
|
1068
|
+
options: { includeAdditionalContext?: boolean } = {},
|
|
1069
|
+
): RequestSignaturePayload {
|
|
1048
1070
|
const additionalContext = request.additionalContext?.trim();
|
|
1049
1071
|
const target =
|
|
1050
1072
|
request.target.type === "folder"
|
|
@@ -1062,15 +1084,55 @@ function buildRequestSignature(request: ParsedRequest): string {
|
|
|
1062
1084
|
? { type: "commit", sha: request.target.sha }
|
|
1063
1085
|
: { type: request.target.type };
|
|
1064
1086
|
|
|
1065
|
-
|
|
1087
|
+
const payload: RequestSignaturePayload = {
|
|
1066
1088
|
target,
|
|
1067
|
-
models:
|
|
1068
|
-
|
|
1069
|
-
|
|
1089
|
+
models: normalizeRequestModels(request.models),
|
|
1090
|
+
};
|
|
1091
|
+
if (options.includeAdditionalContext ?? true) {
|
|
1092
|
+
payload.additionalContext =
|
|
1093
|
+
additionalContext && additionalContext.length > 0 ? additionalContext : null;
|
|
1094
|
+
}
|
|
1095
|
+
return payload;
|
|
1070
1096
|
}
|
|
1071
1097
|
|
|
1072
|
-
function
|
|
1073
|
-
return
|
|
1098
|
+
function buildRequestSignature(request: ParsedRequest): string {
|
|
1099
|
+
return JSON.stringify(buildRequestSignaturePayload(request));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function buildContextlessRequestSignature(request: ParsedRequest): string {
|
|
1103
|
+
return JSON.stringify(buildRequestSignaturePayload(request, { includeAdditionalContext: false }));
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function parseRequestSignaturePayload(signature: string): RequestSignaturePayload | null {
|
|
1107
|
+
try {
|
|
1108
|
+
const payload: unknown = JSON.parse(signature);
|
|
1109
|
+
if (!payload || typeof payload !== "object") return null;
|
|
1110
|
+
const { target, models } = payload as { target?: unknown; models?: unknown };
|
|
1111
|
+
if (target === undefined || !Array.isArray(models)) return null;
|
|
1112
|
+
const parsedModels = models.filter((model): model is string => typeof model === "string");
|
|
1113
|
+
if (parsedModels.length !== models.length) return null;
|
|
1114
|
+
return { target, models: normalizeRequestModels(parsedModels) };
|
|
1115
|
+
} catch {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function buildContextlessStoredRequestSignature(signature: string): string | null {
|
|
1121
|
+
const payload = parseRequestSignaturePayload(signature);
|
|
1122
|
+
if (!payload) return null;
|
|
1123
|
+
return JSON.stringify({ target: payload.target, models: payload.models });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function isOpenEndedFixRequest(request: ParsedRequest): boolean {
|
|
1127
|
+
return !request.targetExplicit && request.models.length === 0;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function reviewMatchesFixRequest(details: ReviewMessageDetails, request: ParsedRequest): boolean {
|
|
1131
|
+
if (isOpenEndedFixRequest(request)) return true;
|
|
1132
|
+
return (
|
|
1133
|
+
buildContextlessStoredRequestSignature(details.request.signature) ===
|
|
1134
|
+
buildContextlessRequestSignature(request)
|
|
1135
|
+
);
|
|
1074
1136
|
}
|
|
1075
1137
|
|
|
1076
1138
|
// --- Git & fingerprinting ---
|
|
@@ -1794,17 +1856,19 @@ function buildScopeInstructions(scope: ResolvedScope): string {
|
|
|
1794
1856
|
}
|
|
1795
1857
|
}
|
|
1796
1858
|
|
|
1859
|
+
function buildAdditionalContextSection(additionalContext: string | undefined): string {
|
|
1860
|
+
const trimmed = additionalContext?.trim();
|
|
1861
|
+
if (!trimmed) return "";
|
|
1862
|
+
return ADDITIONAL_CONTEXT_SECTION_PROMPT.replace("{ADDITIONAL_CONTEXT}", () => trimmed);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1797
1865
|
function buildFocusPrompt(
|
|
1798
1866
|
focus: FocusName,
|
|
1799
1867
|
scopeInstructions: string,
|
|
1800
1868
|
projectGuidelines: string | null,
|
|
1801
1869
|
additionalContext: string | undefined,
|
|
1802
1870
|
): string {
|
|
1803
|
-
const additionalContextSection = additionalContext
|
|
1804
|
-
? REVIEW_ADDITIONAL_CONTEXT_SECTION_PROMPT.replace("{ADDITIONAL_CONTEXT}", () =>
|
|
1805
|
-
additionalContext.trim(),
|
|
1806
|
-
)
|
|
1807
|
-
: "";
|
|
1871
|
+
const additionalContextSection = buildAdditionalContextSection(additionalContext);
|
|
1808
1872
|
const projectGuidelinesSection = projectGuidelines
|
|
1809
1873
|
? REVIEW_PROJECT_GUIDELINES_SECTION_PROMPT.replace(
|
|
1810
1874
|
"{PROJECT_GUIDELINES}",
|
|
@@ -3225,7 +3289,7 @@ function createResolvedReviewProviderCandidateFromModel(
|
|
|
3225
3289
|
return createResolvedReviewProviderCandidate({
|
|
3226
3290
|
baseModelArg: `${model.provider}/${model.id}`,
|
|
3227
3291
|
supportsThinking: model.reasoning,
|
|
3228
|
-
supportsXhigh:
|
|
3292
|
+
supportsXhigh: getSupportedThinkingLevels(model).includes("xhigh"),
|
|
3229
3293
|
});
|
|
3230
3294
|
}
|
|
3231
3295
|
|
|
@@ -3353,7 +3417,9 @@ async function resolveModels(
|
|
|
3353
3417
|
createResolvedReviewProviderCandidate({
|
|
3354
3418
|
baseModelArg,
|
|
3355
3419
|
supportsThinking: ctx.model?.reasoning,
|
|
3356
|
-
supportsXhigh: ctx.model
|
|
3420
|
+
supportsXhigh: ctx.model
|
|
3421
|
+
? getSupportedThinkingLevels(ctx.model).includes("xhigh")
|
|
3422
|
+
: undefined,
|
|
3357
3423
|
}),
|
|
3358
3424
|
],
|
|
3359
3425
|
}),
|
|
@@ -4083,7 +4149,10 @@ async function runTriagePipeline(
|
|
|
4083
4149
|
|
|
4084
4150
|
// --- Command handlers ---
|
|
4085
4151
|
|
|
4086
|
-
function buildFixPrompt(
|
|
4152
|
+
function buildFixPrompt(
|
|
4153
|
+
reviewMessageDetails: ReviewMessageDetails,
|
|
4154
|
+
additionalContext: string | undefined,
|
|
4155
|
+
): string {
|
|
4087
4156
|
const worklistPayload = JSON.stringify(
|
|
4088
4157
|
{
|
|
4089
4158
|
scope: reviewMessageDetails.scope,
|
|
@@ -4101,8 +4170,12 @@ function buildFixPrompt(reviewMessageDetails: ReviewMessageDetails): string {
|
|
|
4101
4170
|
null,
|
|
4102
4171
|
2,
|
|
4103
4172
|
);
|
|
4173
|
+
const additionalContextSection = buildAdditionalContextSection(additionalContext);
|
|
4104
4174
|
|
|
4105
|
-
return FIX_PROMPT.replace(
|
|
4175
|
+
return FIX_PROMPT.replace(
|
|
4176
|
+
"{FIX_ADDITIONAL_CONTEXT_SECTION}",
|
|
4177
|
+
() => additionalContextSection,
|
|
4178
|
+
).replace("{REVIEW_FINDINGS_JSON}", () => worklistPayload);
|
|
4106
4179
|
}
|
|
4107
4180
|
|
|
4108
4181
|
function parseTriagePrRef(
|
|
@@ -4259,7 +4332,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
4259
4332
|
|
|
4260
4333
|
pi.registerCommand("fix", {
|
|
4261
4334
|
description:
|
|
4262
|
-
"Fix findings when the last message is a matching /review result. Otherwise, runs review first. If the last review is stale, /fix warns and continues; if a /fix-started review goes stale mid-run, /fix shows the findings and applies no fixes. Use /review help for modes/options.",
|
|
4335
|
+
"Fix findings when the last message is a matching /review result. Otherwise, runs review first. context=... guides the fix pass without forcing a fresh review. If the last review is stale, /fix warns and continues; if a /fix-started review goes stale mid-run, /fix shows the findings and applies no fixes. Use /review help for modes/options.",
|
|
4263
4336
|
getArgumentCompletions: getReviewArgumentCompletions,
|
|
4264
4337
|
handler: async (args, ctx) => {
|
|
4265
4338
|
const request = parseCommandRequest(pi, args, ctx);
|
|
@@ -4273,9 +4346,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
4273
4346
|
|
|
4274
4347
|
try {
|
|
4275
4348
|
let reviewDetails = getLastMessageReviewDetails(ctx);
|
|
4276
|
-
const lastReviewMatchesRequest =
|
|
4277
|
-
|
|
4278
|
-
|
|
4349
|
+
const lastReviewMatchesRequest = reviewDetails
|
|
4350
|
+
? reviewMatchesFixRequest(reviewDetails, request)
|
|
4351
|
+
: false;
|
|
4279
4352
|
const shouldRunFreshReview = !reviewDetails || !lastReviewMatchesRequest;
|
|
4280
4353
|
|
|
4281
4354
|
if (reviewDetails && !shouldRunFreshReview) {
|
|
@@ -4332,7 +4405,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
4332
4405
|
return;
|
|
4333
4406
|
}
|
|
4334
4407
|
|
|
4335
|
-
const fixPrompt = buildFixPrompt(reviewDetails);
|
|
4408
|
+
const fixPrompt = buildFixPrompt(reviewDetails, request.additionalContext);
|
|
4336
4409
|
if (ctx.isIdle()) {
|
|
4337
4410
|
pi.sendUserMessage(fixPrompt);
|
|
4338
4411
|
} else {
|