@shanepadgett/tau-agent 0.44.1 → 0.45.1
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/docs/extending-tau-agent.md +3 -3
- package/extensions/appshot/index.ts +3 -0
- package/extensions/aside/index.ts +3 -11
- package/extensions/attention/README.md +2 -4
- package/extensions/attention/index.ts +2 -40
- package/extensions/auto-name/index.ts +5 -8
- package/extensions/cache-diagnostics/index.ts +1 -1
- package/extensions/codex-priority/README.md +7 -0
- package/extensions/codex-priority/index.ts +95 -0
- package/extensions/compaction/README.md +5 -0
- package/extensions/compaction/index.ts +47 -0
- package/extensions/cost-report/README.md +1 -1
- package/extensions/cost-report/analyze.ts +11 -60
- package/extensions/cost-report/html.ts +1 -37
- package/extensions/cost-report/types.ts +0 -9
- package/extensions/handoff/index.ts +1 -1
- package/extensions/image-gen/index.ts +1 -0
- package/extensions/review/README.md +1 -1
- package/extensions/run-summary/README.md +1 -1
- package/extensions/run-summary/index.ts +6 -24
- package/extensions/runtime-context/README.md +1 -1
- package/extensions/silent-command-runner/index.ts +38 -69
- package/extensions/soul/README.md +3 -5
- package/extensions/soul/index.ts +59 -135
- package/extensions/soul/prompt.ts +2 -0
- package/extensions/tau-help/help.md +10 -2
- package/extensions/tool-approval/index.ts +8 -28
- package/extensions/tool-loader/README.md +5 -5
- package/extensions/tool-loader/index.ts +10 -222
- package/extensions/web/codesearch.ts +1 -0
- package/extensions/web/webfetch.ts +1 -0
- package/extensions/web/websearch.ts +1 -0
- package/package.json +2 -2
- package/shared/events.ts +9 -22
- package/shared/model-effort.ts +12 -8
- package/shared/model-fallback/index.ts +12 -29
- package/shared/model-fallback/types.ts +1 -5
- package/shared/prompt-contributions.ts +0 -2
- package/src/tool-loading/index.ts +5 -42
- package/extensions/soul/context.ts +0 -115
- package/extensions/soul/state.ts +0 -114
- package/extensions/soul/tools.ts +0 -31
|
@@ -14,6 +14,6 @@ Choose one focused review type:
|
|
|
14
14
|
- `Architecture` reconsiders ownership, boundaries, reuse, and overall structure.
|
|
15
15
|
- `Correctness` checks concrete runtime bugs and failure paths after accepting the architecture.
|
|
16
16
|
|
|
17
|
-
Then choose which logged-in provider runs the review. OpenAI Codex uses `gpt-
|
|
17
|
+
Then choose which logged-in provider runs the review. Models come from Tau's shared `deep` effort tier: OpenAI Codex uses `gpt-6-astra` and Anthropic uses `claude-opus-5-5`, both at medium thinking. Only providers you are logged in to appear. With no logged-in provider, the review uses the current model.
|
|
18
18
|
|
|
19
19
|
Tau writes each result as Markdown under `.pi/tau/reviews/`. Review results do not enter the parent agent context. Reference the Markdown file later when you want an agent to use it.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Run Summary
|
|
2
2
|
|
|
3
|
-
Run Summary adds a compact marker after the agent settles with no automatic continuation pending. It shows wall time and model cost across the full continuation chain.
|
|
3
|
+
Run Summary adds a compact marker after the agent settles with no automatic continuation pending. It shows wall time and model cost across the full continuation chain.
|
|
4
4
|
|
|
5
5
|
The marker is stored as a display-only session entry. It does not enter model context or trigger another agent turn.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
2
|
import { Marker } from "@shanepadgett/tau-tui";
|
|
4
3
|
|
|
@@ -9,11 +8,6 @@ interface RunSummary {
|
|
|
9
8
|
runCost: number;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
interface PreviousRunSummary extends RunSummary {
|
|
13
|
-
subagentCost: number;
|
|
14
|
-
totalCost: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
18
12
|
let startedAt: number | undefined;
|
|
19
13
|
let runCost = 0;
|
|
@@ -25,13 +19,7 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
25
19
|
theme,
|
|
26
20
|
state: "muted",
|
|
27
21
|
label: "Run complete:",
|
|
28
|
-
parts: [
|
|
29
|
-
`Wall ${formatDuration(summary.wallMs)}`,
|
|
30
|
-
`Run ${formatCost(summary.runCost)}`,
|
|
31
|
-
...("subagentCost" in summary
|
|
32
|
-
? [`Subagents ${formatCost(summary.subagentCost)}`, `Total ${formatCost(summary.totalCost)}`]
|
|
33
|
-
: []),
|
|
34
|
-
],
|
|
22
|
+
parts: [`Wall ${formatDuration(summary.wallMs)}`, `Run ${formatCost(summary.runCost)}`],
|
|
35
23
|
});
|
|
36
24
|
});
|
|
37
25
|
|
|
@@ -46,7 +34,10 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
46
34
|
|
|
47
35
|
pi.on("agent_end", (event) => {
|
|
48
36
|
for (const message of event.messages) {
|
|
49
|
-
|
|
37
|
+
// Tool results carry the usage of model calls a tool made.
|
|
38
|
+
if (message.role === "assistant") runCost += finiteNonNegative(message.usage.cost.total);
|
|
39
|
+
else if (message.role === "toolResult" && message.usage)
|
|
40
|
+
runCost += finiteNonNegative(message.usage.cost.total);
|
|
50
41
|
}
|
|
51
42
|
});
|
|
52
43
|
|
|
@@ -62,19 +53,10 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
62
53
|
});
|
|
63
54
|
}
|
|
64
55
|
|
|
65
|
-
function readRunSummary(value: unknown): RunSummary |
|
|
56
|
+
function readRunSummary(value: unknown): RunSummary | undefined {
|
|
66
57
|
if (!value || typeof value !== "object") return undefined;
|
|
67
58
|
const record = value as Record<string, unknown>;
|
|
68
59
|
if (![record.wallMs, record.runCost].every(isFiniteNonNegative)) return undefined;
|
|
69
|
-
if ("subagentCost" in record || "totalCost" in record) {
|
|
70
|
-
if (![record.subagentCost, record.totalCost].every(isFiniteNonNegative)) return undefined;
|
|
71
|
-
return {
|
|
72
|
-
wallMs: record.wallMs as number,
|
|
73
|
-
runCost: record.runCost as number,
|
|
74
|
-
subagentCost: record.subagentCost as number,
|
|
75
|
-
totalCost: record.totalCost as number,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
60
|
return {
|
|
79
61
|
wallMs: record.wallMs as number,
|
|
80
62
|
runCost: record.runCost as number,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Runtime Context
|
|
2
2
|
|
|
3
|
-
Supplies Soul with the local date and root directory snapshot. Both are captured
|
|
3
|
+
Supplies Soul with the local date and root directory snapshot. Both are captured on the first prompt of a session and refreshed after successful compaction. Reload, resume, and midnight do not change them.
|
|
4
4
|
|
|
5
5
|
After changing this extension, run `/reload` before testing the new behavior.
|
|
@@ -2,7 +2,6 @@ import { readdir, stat } from "node:fs/promises";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { type ExecResult, type ExtensionAPI, keyText, type Theme } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
5
|
-
import { emitTauEvent } from "../../shared/events.ts";
|
|
6
5
|
import { registerPromptSource } from "../../shared/prompt-contributions.ts";
|
|
7
6
|
import { matchGlob, posixPath } from "../../shared/glob.ts";
|
|
8
7
|
import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
|
|
@@ -82,16 +81,6 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
82
81
|
let turnPaths = new Set<string>();
|
|
83
82
|
let abortController: AbortController | undefined;
|
|
84
83
|
let sessionActive = false;
|
|
85
|
-
let chainActive = false;
|
|
86
|
-
let attentionHoldSequence = 0;
|
|
87
|
-
let attentionHoldId: string | undefined;
|
|
88
|
-
|
|
89
|
-
function finalizeChain(): void {
|
|
90
|
-
chainActive = false;
|
|
91
|
-
const holdId = attentionHoldId;
|
|
92
|
-
attentionHoldId = undefined;
|
|
93
|
-
if (holdId) emitTauEvent(pi, "tau:attention.hold.release", { id: holdId, disposition: "notify" });
|
|
94
|
-
}
|
|
95
84
|
|
|
96
85
|
pi.registerMessageRenderer<FailureDetails>(MESSAGE_TYPE, (message, { expanded }, theme) =>
|
|
97
86
|
renderFailure(asFailureDetails(message.details), expanded, theme),
|
|
@@ -102,9 +91,6 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
102
91
|
sessionActive = true;
|
|
103
92
|
turnStart = Date.now();
|
|
104
93
|
turnPaths = new Set();
|
|
105
|
-
chainActive = false;
|
|
106
|
-
attentionHoldSequence = 0;
|
|
107
|
-
attentionHoldId = undefined;
|
|
108
94
|
});
|
|
109
95
|
|
|
110
96
|
registerPromptSource(pi, {
|
|
@@ -119,41 +105,43 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
119
105
|
});
|
|
120
106
|
|
|
121
107
|
pi.on("agent_start", async (_event, ctx) => {
|
|
122
|
-
const startingChain = !chainActive;
|
|
123
|
-
chainActive = true;
|
|
124
|
-
if (!settings.enabled || settings.commands.length === 0) {
|
|
125
|
-
if (startingChain) {
|
|
126
|
-
turnStart = Date.now();
|
|
127
|
-
turnPaths = new Set();
|
|
128
|
-
}
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (!startingChain) return;
|
|
132
|
-
attentionHoldId = `silent-command-runner:${++attentionHoldSequence}`;
|
|
133
|
-
emitTauEvent(pi, "tau:attention.hold.acquire", { id: attentionHoldId });
|
|
134
108
|
turnStart = Date.now();
|
|
135
|
-
|
|
136
|
-
|
|
109
|
+
turnPaths =
|
|
110
|
+
settings.enabled && settings.commands.length > 0
|
|
111
|
+
? new Set(await walkFiles(await resolveProjectRoot(ctx.cwd)))
|
|
112
|
+
: new Set();
|
|
137
113
|
});
|
|
138
114
|
|
|
139
|
-
|
|
140
|
-
|
|
115
|
+
// The last boundary before the run settles: failures continue the run instead of starting a new one.
|
|
116
|
+
pi.on("agent_before_settle", async (event, ctx) => {
|
|
117
|
+
if (event.outcome !== "completed") return undefined;
|
|
141
118
|
try {
|
|
142
|
-
await runChangedCommands(ctx.cwd,
|
|
119
|
+
const failures = await runChangedCommands(ctx.cwd, ctx.ui.notify);
|
|
120
|
+
if (!failures || failures.length === 0) return undefined;
|
|
121
|
+
return {
|
|
122
|
+
entries: [
|
|
123
|
+
...event.entries,
|
|
124
|
+
{
|
|
125
|
+
type: "custom_message" as const,
|
|
126
|
+
customType: MESSAGE_TYPE,
|
|
127
|
+
content: formatAgentMessage(failures),
|
|
128
|
+
display: true,
|
|
129
|
+
details: { failed: [...failures] } satisfies FailureDetails,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
continue: true,
|
|
133
|
+
};
|
|
143
134
|
} catch (error: unknown) {
|
|
144
135
|
ctx.ui.notify(`silent-command-runner: ${errorMessage(error)}`, "error");
|
|
136
|
+
return undefined;
|
|
145
137
|
}
|
|
146
138
|
});
|
|
147
139
|
|
|
148
|
-
pi.on("agent_settled", finalizeChain);
|
|
149
|
-
|
|
150
140
|
pi.on("session_shutdown", () => {
|
|
151
141
|
sessionActive = false;
|
|
152
142
|
abortController?.abort();
|
|
153
143
|
abortController = undefined;
|
|
154
144
|
turnPaths = new Set();
|
|
155
|
-
chainActive = false;
|
|
156
|
-
attentionHoldId = undefined;
|
|
157
145
|
});
|
|
158
146
|
|
|
159
147
|
async function collectCommandFailures(
|
|
@@ -183,37 +171,16 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
183
171
|
return failures;
|
|
184
172
|
}
|
|
185
173
|
|
|
186
|
-
function reportCommandResults(
|
|
187
|
-
changed: readonly CommandConfig[],
|
|
188
|
-
failures: readonly FailedCommandDetails[],
|
|
189
|
-
notify: (message: string, type?: "info" | "warning" | "error") => void,
|
|
190
|
-
): void {
|
|
191
|
-
const failedNames = new Set(failures.map((failure) => failure.name));
|
|
192
|
-
const passed = changed.filter((command) => !failedNames.has(command.name));
|
|
193
|
-
if (passed.length > 0) notify(`silent-command-runner: passed ${formatCommandNames(passed)}`, "info");
|
|
194
|
-
if (failures.length === 0) return;
|
|
195
|
-
pi.sendMessage<FailureDetails>(
|
|
196
|
-
{
|
|
197
|
-
customType: MESSAGE_TYPE,
|
|
198
|
-
content: formatAgentMessage(failures),
|
|
199
|
-
display: true,
|
|
200
|
-
details: { failed: [...failures] },
|
|
201
|
-
},
|
|
202
|
-
{ deliverAs: "followUp" },
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
174
|
async function runChangedCommands(
|
|
207
175
|
cwd: string,
|
|
208
|
-
turnStart: number,
|
|
209
176
|
notify: (message: string, type?: "info" | "warning" | "error") => void,
|
|
210
|
-
): Promise<
|
|
211
|
-
if (!settings.enabled || settings.commands.length === 0) return;
|
|
177
|
+
): Promise<FailedCommandDetails[] | undefined> {
|
|
178
|
+
if (!settings.enabled || settings.commands.length === 0) return undefined;
|
|
212
179
|
|
|
213
180
|
const projectRoot = await resolveProjectRoot(cwd);
|
|
214
181
|
const paths = await walkFiles(projectRoot);
|
|
215
182
|
const changed = await scanChangedCommands(projectRoot, settings.commands, paths, turnPaths, turnStart);
|
|
216
|
-
if (!sessionActive || changed.length === 0) return;
|
|
183
|
+
if (!sessionActive || changed.length === 0) return undefined;
|
|
217
184
|
|
|
218
185
|
notify(
|
|
219
186
|
changed.length === 1
|
|
@@ -223,8 +190,16 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
223
190
|
);
|
|
224
191
|
|
|
225
192
|
const failures = await collectCommandFailures(projectRoot, changed, notify);
|
|
226
|
-
if (failures === undefined || !sessionActive) return;
|
|
227
|
-
|
|
193
|
+
if (failures === undefined || !sessionActive) return undefined;
|
|
194
|
+
const failedNames = new Set(failures.map((failure) => failure.name));
|
|
195
|
+
const passed = changed.filter((command) => !failedNames.has(command.name));
|
|
196
|
+
if (passed.length > 0) notify(`silent-command-runner: passed ${formatCommandNames(passed)}`, "info");
|
|
197
|
+
if (failures.length > 0) {
|
|
198
|
+
// Only files the agent changes after this report can trigger another check and continuation.
|
|
199
|
+
turnStart = Date.now();
|
|
200
|
+
turnPaths = new Set(paths);
|
|
201
|
+
}
|
|
202
|
+
return failures;
|
|
228
203
|
}
|
|
229
204
|
}
|
|
230
205
|
|
|
@@ -255,7 +230,7 @@ function normalizeSettings(value: typeof silentCommandRunnerSettings.defaults):
|
|
|
255
230
|
|
|
256
231
|
function formatSilentCheckPrompt(commands: readonly CommandConfig[]): string {
|
|
257
232
|
return [
|
|
258
|
-
"Do not manually run the commands listed below. They run automatically after matching changes.
|
|
233
|
+
"Do not manually run the commands listed below. They run automatically after matching changes. Complete the work and finish normally; treat it as correct unless a failure is reported. Do not announce pending validation, hedge completion because of these commands, or tell the user that checks will run or rerun. If a failure is reported, fix it and describe the correction, then finish normally. Do not claim commands passed without evidence. Targeted checks outside this list remain allowed when needed.",
|
|
259
234
|
"Automatic commands:",
|
|
260
235
|
...commands.map(formatSilentCheckCommand),
|
|
261
236
|
].join("\n");
|
|
@@ -271,12 +246,6 @@ function formatSilentCheckCommand(command: CommandConfig): string {
|
|
|
271
246
|
].join("\n");
|
|
272
247
|
}
|
|
273
248
|
|
|
274
|
-
function hasAbortedAssistantMessage(messages: readonly unknown[]): boolean {
|
|
275
|
-
return messages.some(
|
|
276
|
-
(message) => isRecord(message) && message.role === "assistant" && message.stopReason === "aborted",
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
249
|
async function scanChangedCommands(
|
|
281
250
|
projectRoot: string,
|
|
282
251
|
commands: readonly CommandConfig[],
|
|
@@ -403,7 +372,7 @@ function formatAgentMessage(failures: readonly FailedCommandDetails[]): string {
|
|
|
403
372
|
return [
|
|
404
373
|
`silent-command-runner failed: ${failures.length} command${failures.length === 1 ? "" : "s"}`,
|
|
405
374
|
...failures.map(formatAgentFailure),
|
|
406
|
-
"
|
|
375
|
+
"Fix the reported failures without manually rerunning these commands. Describe the correction and finish normally; do not announce validation or reruns.",
|
|
407
376
|
].join("\n\n");
|
|
408
377
|
}
|
|
409
378
|
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Soul supplies Tau's system prompt: communication, discussion, planning, execution, and coding guidance. It is always on.
|
|
4
4
|
|
|
5
|
-
Soul
|
|
5
|
+
Soul adds Pi documentation pointers, tool guidance, and the context that other Tau extensions supply, such as the local date, directory snapshot, and automatic-check instructions. The date and directory snapshot are captured on the first prompt and again after successful compaction, so they stay fixed across turns, reload, resume, and tree navigation.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Everything else is read each turn. When an instruction changes, such as an edited `AGENTS.md` after `/reload`, a changed automatic-check configuration, or a different set of active tools, Pi appends the updated section without rewriting earlier instructions. Models that cannot take later system messages receive the change in the leading instructions, which costs one cache miss on the next request.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Run `/reload` after changing this extension. The first request after installation captures the new Soul baseline; later instruction edits take effect after compaction.
|
|
9
|
+
Run `/reload` after changing this extension.
|
package/extensions/soul/index.ts
CHANGED
|
@@ -1,144 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { readPromptValues, renderBaseline, renderUpdate } from "./context.ts";
|
|
6
|
-
import {
|
|
7
|
-
admittedTools,
|
|
8
|
-
BASELINE_TYPE,
|
|
9
|
-
projectPrompt,
|
|
10
|
-
restorePrompt,
|
|
11
|
-
UPDATE_TYPE,
|
|
12
|
-
type SavedBaseline,
|
|
13
|
-
type SavedUpdate,
|
|
14
|
-
} from "./state.ts";
|
|
15
|
-
import { toolChangeReason } from "./tools.ts";
|
|
1
|
+
import { getDocsPath, getExamplesPath, getReadmePath, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { emitTauEvent } from "../../shared/events.ts";
|
|
3
|
+
import { collectPromptSources } from "../../shared/prompt-contributions.ts";
|
|
4
|
+
import { FIXED_INSTRUCTIONS } from "./prompt.ts";
|
|
16
5
|
|
|
17
|
-
const
|
|
18
|
-
interface PrefixCheckpoint {
|
|
19
|
-
baselineEntryId: string;
|
|
20
|
-
count: number;
|
|
21
|
-
hash: string;
|
|
22
|
-
}
|
|
6
|
+
const CAPTURE_TYPE = "tau.soul.capture";
|
|
23
7
|
|
|
24
|
-
|
|
25
|
-
|
|
8
|
+
const DOCUMENTATION = `Consult Pi or Tau documentation when the request concerns their usage or extension APIs.
|
|
9
|
+
Pi documentation:
|
|
10
|
+
- Main documentation: ${getReadmePath()}
|
|
11
|
+
- Additional docs: ${getDocsPath()}
|
|
12
|
+
- Examples: ${getExamplesPath()}
|
|
13
|
+
- Resolve docs/... and examples/... under those installed paths, not the working directory.
|
|
14
|
+
- Extensions: docs/extensions.md and examples/extensions/; themes: docs/themes.md; skills: docs/skills.md; prompt templates: docs/prompt-templates.md; TUI: docs/tui.md; keybindings: docs/keybindings.md; SDK: docs/sdk.md; providers: docs/custom-provider.md; models: docs/models.md; packages: docs/packages.md; environment: docs/environment-variables.md.
|
|
15
|
+
- Read the relevant documentation and follow related Markdown references before implementing Pi integrations.`;
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
pi.on("session_shutdown", () => {
|
|
31
|
-
inputs = null;
|
|
32
|
-
});
|
|
33
|
-
pi.on("before_agent_start", (event) => {
|
|
34
|
-
// Keep the shared options reference until all contributors have finished.
|
|
35
|
-
inputs = event.systemPromptOptions;
|
|
36
|
-
});
|
|
17
|
+
export default function soulExtension(pi: ExtensionAPI): void {
|
|
18
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
19
|
+
const options = event.systemPromptOptions;
|
|
37
20
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
reject(String(error));
|
|
21
|
+
// Sources that refresh on compaction are read once per compaction epoch and saved on the branch.
|
|
22
|
+
const branch = ctx.sessionManager.getBranch();
|
|
23
|
+
let epochStart = 0;
|
|
24
|
+
branch.forEach((entry, index) => {
|
|
25
|
+
if (entry.type === "compaction") epochStart = index + 1;
|
|
26
|
+
});
|
|
27
|
+
let captured: Record<string, string> = {};
|
|
28
|
+
for (const entry of branch.slice(epochStart)) {
|
|
29
|
+
if (entry.type === "custom" && entry.customType === CAPTURE_TYPE)
|
|
30
|
+
captured = entry.data as Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
const sources = collectPromptSources(pi);
|
|
33
|
+
const missing = sources.filter((source) => source.refresh === "compaction" && !(source.key in captured));
|
|
34
|
+
if (missing.length > 0) {
|
|
35
|
+
const read = await Promise.all(missing.map(async (source) => [source.key, await source.read(ctx)] as const));
|
|
36
|
+
captured = { ...captured, ...Object.fromEntries(read) };
|
|
37
|
+
pi.appendEntry(CAPTURE_TYPE, captured);
|
|
56
38
|
}
|
|
57
|
-
});
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
const values = await readPromptValues(pi, ctx, inputs, saved === null);
|
|
81
|
-
if (!saved) {
|
|
82
|
-
pi.appendEntry<SavedBaseline>(BASELINE_TYPE, {
|
|
83
|
-
version: 1,
|
|
84
|
-
compactionId: newestFirst.find((entry) => entry.type === "compaction")?.id ?? null,
|
|
85
|
-
afterEntryId: anchor.sourceEntry.id,
|
|
86
|
-
text: renderBaseline(inputs, values),
|
|
87
|
-
values,
|
|
88
|
-
initialTools: getCurrentTools(event.messages),
|
|
89
|
-
});
|
|
90
|
-
} else {
|
|
91
|
-
const previous = new Map(saved.baseline.values.map((value) => [value.key, value]));
|
|
92
|
-
for (const update of saved.updates) for (const value of update.data.values) previous.set(value.key, value);
|
|
93
|
-
const currentKeys = new Set(values.map((value) => value.key));
|
|
94
|
-
for (const value of previous.values()) {
|
|
95
|
-
if (value.refresh === "append" && !currentKeys.has(value.key)) values.push({ ...value, text: "" });
|
|
96
|
-
}
|
|
97
|
-
const changed = values.filter((value) => previous.get(value.key)?.text !== value.text);
|
|
98
|
-
const tools = new Map(admittedTools(saved).map((tool) => [tool.name, tool]));
|
|
99
|
-
const added = requested.some((tool) => !tools.has(tool.name));
|
|
100
|
-
for (const tool of requested) tools.set(tool.name, tool);
|
|
101
|
-
if (changed.length > 0 || added)
|
|
102
|
-
pi.appendEntry<SavedUpdate>(UPDATE_TYPE, {
|
|
103
|
-
version: 1,
|
|
104
|
-
baselineEntryId: saved.entryId,
|
|
105
|
-
afterEntryId: anchor.sourceEntry.id,
|
|
106
|
-
text: renderUpdate(changed),
|
|
107
|
-
values: changed,
|
|
108
|
-
tools: [...tools.values()],
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
const admitted = restorePrompt(ctx.sessionManager.getBranch());
|
|
112
|
-
if (!admitted) throw new Error("Soul failed to save its baseline.");
|
|
113
|
-
const messages = projectPrompt(event.messages, admitted, ctx);
|
|
114
|
-
const checkpointEntry = newestFirst.find(
|
|
115
|
-
(entry) => entry.type === "custom" && entry.customType === CHECKPOINT_TYPE,
|
|
116
|
-
);
|
|
117
|
-
const checkpoint = checkpointEntry?.type === "custom" ? (checkpointEntry.data as PrefixCheckpoint) : null;
|
|
118
|
-
if (checkpoint?.baselineEntryId === admitted.entryId) {
|
|
119
|
-
const prefix = createHash("sha256")
|
|
120
|
-
.update(JSON.stringify(messages.slice(0, checkpoint.count)))
|
|
121
|
-
.digest("hex");
|
|
122
|
-
if (prefix !== checkpoint.hash)
|
|
123
|
-
throw new Error("Previously sent conversation content changed. Compact before continuing.");
|
|
124
|
-
}
|
|
125
|
-
const hash = createHash("sha256").update(JSON.stringify(messages)).digest("hex");
|
|
126
|
-
if (hash !== checkpoint?.hash)
|
|
127
|
-
pi.appendEntry<PrefixCheckpoint>(CHECKPOINT_TYPE, {
|
|
128
|
-
baselineEntryId: admitted.entryId,
|
|
129
|
-
count: messages.length,
|
|
130
|
-
hash,
|
|
131
|
-
});
|
|
132
|
-
emitTauEvent(pi, "tau:prompt.snapshot", {
|
|
133
|
-
text: [admitted.baseline.text, ...admitted.updates.map((update) => update.data.text)].join("\n\n"),
|
|
134
|
-
});
|
|
135
|
-
return { messages };
|
|
136
|
-
} catch (error) {
|
|
137
|
-
ctx.ui.notify(`Soul stopped this request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
138
|
-
ctx.abort();
|
|
139
|
-
// Pi currently enters the provider with an aborted signal; do not weaken that
|
|
140
|
-
// cancellation into a fallback prompt. See soul-request-check-findings.md.
|
|
141
|
-
return undefined;
|
|
40
|
+
const active = pi.getActiveTools();
|
|
41
|
+
const guidance = [
|
|
42
|
+
...new Set([
|
|
43
|
+
...(active.includes("bash") ? ["Use bash for file operations like ls, rg, find."] : []),
|
|
44
|
+
...active.flatMap((name) => options.toolGuidelines[name] ?? []),
|
|
45
|
+
...options.promptGuidelines,
|
|
46
|
+
]),
|
|
47
|
+
]
|
|
48
|
+
.map((rule) => `- ${rule}`)
|
|
49
|
+
.join("\n");
|
|
50
|
+
|
|
51
|
+
const sections = new Map<string, string[]>();
|
|
52
|
+
const add = (section: string, text: string) => {
|
|
53
|
+
if (text.trim()) sections.set(section, [...(sections.get(section) ?? []), text]);
|
|
54
|
+
};
|
|
55
|
+
add("documentation", DOCUMENTATION);
|
|
56
|
+
add("tool-guidance", guidance);
|
|
57
|
+
if (options.customPrompt) add("additional-instructions", options.customPrompt);
|
|
58
|
+
for (const source of sources) {
|
|
59
|
+
add(source.section, source.refresh === "append" ? await source.read(ctx) : (captured[source.key] ?? ""));
|
|
142
60
|
}
|
|
61
|
+
|
|
62
|
+
// Pi diffs these sections against the prompt already in the transcript and appends a patch only for
|
|
63
|
+
// sections whose text changed, so unchanged sections keep the cached prefix.
|
|
64
|
+
options.customPrompt = FIXED_INSTRUCTIONS;
|
|
65
|
+
for (const name of [...sections.keys()].sort()) options.sections[name] = (sections.get(name) ?? []).join("\n\n");
|
|
66
|
+
emitTauEvent(pi, "tau:prompt.snapshot", { text: event.systemPrompt });
|
|
143
67
|
});
|
|
144
68
|
}
|
|
@@ -9,6 +9,8 @@ Use headings or tables when they improve clarity. In conversational, personal, o
|
|
|
9
9
|
Use technical terms when they help. Keep paths, commands, API names, and errors exact.
|
|
10
10
|
State the intended action directly. Avoid adding what you won't do, what will remain unchanged, or how you'll separate or categorize results.
|
|
11
11
|
Give useful facts instead of praise, ceremony, or commentary about following instructions.
|
|
12
|
+
Talk about the user's work, not the machinery directing your behavior. Do not volunteer commentary about system prompts, tool prompts, internal instructions, the harness, or automatic validation. Discuss those mechanisms only when the user asks about them as the subject of the work.
|
|
13
|
+
Finish with the concrete result. Do not hedge completion because silent validation is pending, announce that checks will run or rerun, or explain what happens when you end the turn. When a failure is reported, fix it and describe the correction without narrating the validation process.
|
|
12
14
|
You are a partner, and the user expects you to act like one.
|
|
13
15
|
</communication>
|
|
14
16
|
|
|
@@ -32,6 +32,10 @@ Records private prompt-cache fingerprints without storing prompt content. Run `/
|
|
|
32
32
|
|
|
33
33
|
Adds `/clear-screen` to clear terminal output without changing the session.
|
|
34
34
|
|
|
35
|
+
## codex-priority
|
|
36
|
+
|
|
37
|
+
Requests Codex priority processing (Fast mode) for every `gpt-6-luna` request on the `openai-codex` provider, including the agent loop, compaction, tool approval, auto-naming, commit, and handoff. It has no command or setting. Cost uses OpenAI's 2.5x Fast mode rate for GPT-6 models. Do not run another extension that overlays `openai-codex` or sets `service_tier`.
|
|
38
|
+
|
|
35
39
|
## commit
|
|
36
40
|
|
|
37
41
|
Adds `/commit` for semantic commit grouping, review, and committing selected repository changes.
|
|
@@ -40,6 +44,10 @@ Adds `/commit` for semantic commit grouping, review, and committing selected rep
|
|
|
40
44
|
|
|
41
45
|
Adds `/cost-report` to build an HTML spend report from local session usage. Pick a time frame (past 7 days, current week, current month, year to date, or a specific month) and scope (current project or all sessions). Tau scans sessions, writes under `~/.pi/tau/cost-reports/`, opens the file, and notifies with the path. Empty windows warn without writing a file.
|
|
42
46
|
|
|
47
|
+
## compaction
|
|
48
|
+
|
|
49
|
+
Writes compaction summaries (automatic, `/compact`, and overflow recovery) with a cheaper model from the same provider as the session, so Opus, GPT-6.1 Sol, and Astra sessions do not pay their own rates to summarize themselves. Summaries never cross providers, and a notice names the model that wrote each one. When no cheaper model fits, Pi's default compaction runs.
|
|
50
|
+
|
|
43
51
|
## context
|
|
44
52
|
|
|
45
53
|
Adds `/context` to browse and inject reusable repository work scopes from `.pi/contexts`. Selecting entries injects them once into the conversation: `read` paths as complete files, `show` targets as current declaration slices, `outline` paths as Explore structures, and one hidden note listing `references` plus instructions to treat the injected material as current. Run `/context` again to inject more. Edit catalog files by hand when work scopes change. Domain folders are `NN_slug` tabs (ordered by the two-digit prefix; UI shows the slug), TOML files are concepts, and TOML sections are selectable entries.
|
|
@@ -106,7 +114,7 @@ Runs configured commands while keeping their output out of agent context when th
|
|
|
106
114
|
|
|
107
115
|
## soul
|
|
108
116
|
|
|
109
|
-
Supplies Tau's communication, discussion, planning, execution, and coding instructions
|
|
117
|
+
Supplies Tau's communication, discussion, planning, execution, and coding instructions, plus tool guidance and context from other Tau extensions. The date and directory snapshot stay fixed until successful compaction. Other changes, such as an edited `AGENTS.md` after `/reload`, arrive as appended updates without rewriting earlier instructions.
|
|
110
118
|
|
|
111
119
|
## stash
|
|
112
120
|
|
|
@@ -126,7 +134,7 @@ Reviews agent `bash` and `script_runner` requests before they run. Common read-o
|
|
|
126
134
|
|
|
127
135
|
## tool-loader
|
|
128
136
|
|
|
129
|
-
|
|
137
|
+
Keeps specialist tool groups out of every request as deferred tools and lets the agent load them with Pi's `tool_search`. Tau registers `web`, `image`, and `appshot`; project or global package extensions can add groups with `registerDeferredToolGroup()` from `@shanepadgett/tau-agent`. All models can load tools. Compatible models preserve the cached prefix; other models can incur a cache miss when tools are activated. Loading never triggers compaction.
|
|
130
138
|
|
|
131
139
|
## web
|
|
132
140
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Tool } from "@earendil-works/pi-ai";
|
|
2
2
|
import {
|
|
3
3
|
isToolCallEventType,
|
|
4
4
|
type ExtensionAPI,
|
|
@@ -9,7 +9,8 @@ import { Marker } from "@shanepadgett/tau-tui";
|
|
|
9
9
|
import { Type } from "typebox";
|
|
10
10
|
import { emitAgentBlocked } from "../../shared/agent-blocked.ts";
|
|
11
11
|
import { emitTauEvent } from "../../shared/events.ts";
|
|
12
|
-
import { generateToolValidated
|
|
12
|
+
import { generateToolValidated } from "../../shared/model-fallback/index.ts";
|
|
13
|
+
import { resolveEffortCandidates } from "../../shared/model-effort.ts";
|
|
13
14
|
import type { ScriptSourceStore } from "../../shared/script-source.ts";
|
|
14
15
|
import { errorText, truncAt } from "../../shared/text.ts";
|
|
15
16
|
import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
|
|
@@ -59,15 +60,6 @@ const REVIEW_TOOL = {
|
|
|
59
60
|
parameters: REVIEW_SCHEMA,
|
|
60
61
|
} satisfies Tool;
|
|
61
62
|
|
|
62
|
-
const REVIEW_MODELS: ReadonlyArray<{ provider: string; model: string; reasoning: ThinkingLevel }> = [
|
|
63
|
-
{ provider: "openai", model: "gpt-6-luna", reasoning: "medium" },
|
|
64
|
-
{ provider: "openai-codex", model: "gpt-6-luna", reasoning: "medium" },
|
|
65
|
-
{ provider: "anthropic", model: "claude-sonnet-5", reasoning: "medium" },
|
|
66
|
-
{ provider: "xai", model: "grok-4.5", reasoning: "low" },
|
|
67
|
-
{ provider: "openrouter", model: "deepseek/deepseek-v4.1-flash", reasoning: "high" },
|
|
68
|
-
{ provider: "opencode-go", model: "deepseek-v4.1-flash", reasoning: "high" },
|
|
69
|
-
];
|
|
70
|
-
|
|
71
63
|
type ToolReview =
|
|
72
64
|
| { decision: "approved"; summary: string }
|
|
73
65
|
| { decision: "requires_user_approval"; summary: string; reason: string };
|
|
@@ -383,23 +375,11 @@ async function reviewAssistantRequests(
|
|
|
383
375
|
|
|
384
376
|
async function reviewToolRequest(ctx: ExtensionContext, request: ToolApprovalRequest): Promise<ToolReviewResult> {
|
|
385
377
|
const requestJson = JSON.stringify(request);
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
model: ctx.model.id,
|
|
392
|
-
reasoning: "medium",
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
const candidates = await resolveCandidates(ctx, preferred, false);
|
|
396
|
-
const wanted = preferred[0];
|
|
397
|
-
if (
|
|
398
|
-
wanted &&
|
|
399
|
-
!candidates.some((item) => item.model.provider === wanted.provider && item.model.id === wanted.model)
|
|
400
|
-
) {
|
|
401
|
-
ctx.ui.notify(`Tool review skipped ${wanted.provider}/${wanted.model}; trying next model.`, "info");
|
|
402
|
-
}
|
|
378
|
+
// Requests contain shell commands and scripts, so only the session's own provider reviews them.
|
|
379
|
+
const provider = ctx.model?.provider;
|
|
380
|
+
const candidates = (
|
|
381
|
+
await resolveEffortCandidates(ctx, "quick", { includeParentModel: true, preferredProvider: provider })
|
|
382
|
+
).filter((candidate) => candidate.model.provider === provider);
|
|
403
383
|
const { value, candidate } = await generateToolValidated(
|
|
404
384
|
ctx,
|
|
405
385
|
candidates,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Tool Loader
|
|
2
2
|
|
|
3
|
-
Tau
|
|
3
|
+
Tau registers specialist tool groups as deferred tools so their schemas stay out of every request. Most coding turns do not need web, image, macOS application, or application-specific schemas. The agent finds and loads them with Pi's built-in `tool_search` tool, which Tau keeps active whenever deferred tools exist.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Tau's built-in groups are:
|
|
6
6
|
|
|
7
7
|
- `web` for public web and implementation research
|
|
8
8
|
- `image` for raster image generation and editing
|
|
9
9
|
- `appshot` for macOS window discovery, capture, and activation
|
|
10
10
|
|
|
11
|
-
Project and global package extensions can add groups with `registerDeferredToolGroup()` from `@shanepadgett/tau-agent`. The group
|
|
11
|
+
Project and global package extensions can add groups with `registerDeferredToolGroup()` from `@shanepadgett/tau-agent`. The group id and description become the tool namespace shown to the agent.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Loaded tools are recorded in the session and stay available on that branch. All models can discover and load deferred tools. Supported models preserve the cached prompt prefix; other models, including Grok and opencode-go, can incur a cache miss when tools are activated. Tau does not compact automatically.
|
|
14
14
|
|
|
15
|
-
After changing this extension during development, run `/reload` before testing.
|
|
15
|
+
Pi's built-in `tool_search` extension must be enabled. After changing this extension during development, run `/reload` before testing.
|