@shanepadgett/tau-agent 0.44.0 → 0.45.0

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 (47) hide show
  1. package/docs/extending-tau-agent.md +3 -3
  2. package/extensions/appshot/index.ts +3 -0
  3. package/extensions/aside/index.ts +3 -11
  4. package/extensions/attention/README.md +2 -4
  5. package/extensions/attention/index.ts +2 -40
  6. package/extensions/auto-name/index.ts +5 -8
  7. package/extensions/cache-diagnostics/index.ts +1 -1
  8. package/extensions/codex-priority/README.md +7 -0
  9. package/extensions/codex-priority/index.ts +95 -0
  10. package/extensions/compaction/README.md +5 -0
  11. package/extensions/compaction/index.ts +47 -0
  12. package/extensions/cost-report/README.md +1 -1
  13. package/extensions/cost-report/analyze.ts +11 -60
  14. package/extensions/cost-report/html.ts +1 -37
  15. package/extensions/cost-report/types.ts +0 -9
  16. package/extensions/handoff/index.ts +1 -1
  17. package/extensions/image-gen/index.ts +1 -0
  18. package/extensions/review/README.md +1 -1
  19. package/extensions/run-summary/README.md +1 -1
  20. package/extensions/run-summary/index.ts +6 -24
  21. package/extensions/runtime-context/README.md +1 -1
  22. package/extensions/script-runner/README.md +3 -1
  23. package/extensions/script-runner/index.ts +41 -96
  24. package/extensions/silent-command-runner/index.ts +38 -69
  25. package/extensions/soul/README.md +3 -5
  26. package/extensions/soul/index.ts +59 -135
  27. package/extensions/soul/prompt.ts +2 -0
  28. package/extensions/tau-help/help.md +10 -2
  29. package/extensions/tool-approval/README.md +3 -3
  30. package/extensions/tool-approval/index.ts +86 -47
  31. package/extensions/tool-approval/panel.ts +16 -2
  32. package/extensions/tool-loader/README.md +5 -5
  33. package/extensions/tool-loader/index.ts +10 -222
  34. package/extensions/web/codesearch.ts +1 -0
  35. package/extensions/web/webfetch.ts +1 -0
  36. package/extensions/web/websearch.ts +1 -0
  37. package/package.json +2 -2
  38. package/shared/events.ts +7 -20
  39. package/shared/model-effort.ts +12 -8
  40. package/shared/model-fallback/index.ts +12 -29
  41. package/shared/model-fallback/types.ts +1 -5
  42. package/shared/prompt-contributions.ts +0 -2
  43. package/shared/script-source.ts +94 -0
  44. package/src/tool-loading/index.ts +5 -42
  45. package/extensions/soul/context.ts +0 -115
  46. package/extensions/soul/state.ts +0 -114
  47. package/extensions/soul/tools.ts +0 -31
@@ -1,115 +0,0 @@
1
- import {
2
- formatSkillsForPrompt,
3
- getDocsPath,
4
- getExamplesPath,
5
- getReadmePath,
6
- type BuildSystemPromptOptions,
7
- type ExtensionAPI,
8
- type ExtensionContext,
9
- } from "@earendil-works/pi-coding-agent";
10
- import { collectPromptSources, type PromptValue } from "../../shared/prompt-contributions.ts";
11
- import { FIXED_INSTRUCTIONS } from "./prompt.ts";
12
-
13
- export async function readPromptValues(
14
- pi: ExtensionAPI,
15
- ctx: ExtensionContext,
16
- inputs: BuildSystemPromptOptions,
17
- capture: boolean,
18
- ): Promise<PromptValue[]> {
19
- const sources = collectPromptSources(pi).filter((source) => capture || source.refresh === "append");
20
- const values = await Promise.all(
21
- sources.map(async (source) => ({
22
- key: source.key,
23
- section: source.section,
24
- refresh: source.refresh,
25
- text: await source.read(ctx),
26
- })),
27
- );
28
- const active = new Set(pi.getActiveTools());
29
- const tools = pi.getAllTools().filter((tool) => active.has(tool.name));
30
- values.push(
31
- {
32
- key: "pi/tools",
33
- section: "tools",
34
- refresh: "append",
35
- text: tools
36
- .map((tool) => `- ${tool.name}: ${inputs.toolSnippets?.[tool.name] ?? tool.description}`)
37
- .join("\n"),
38
- },
39
- {
40
- key: "pi/tool-guidance",
41
- section: "tool-guidance",
42
- refresh: "append",
43
- text: [
44
- ...new Set([
45
- ...(active.has("bash") ? ["Use bash for file operations like ls, rg, find."] : []),
46
- ...tools.flatMap((tool) => tool.promptGuidelines ?? []),
47
- ...(inputs.promptGuidelines ?? []),
48
- ]),
49
- ]
50
- .map((rule) => `- ${rule}`)
51
- .join("\n"),
52
- },
53
- );
54
- return values.sort((a, b) => a.key.localeCompare(b.key, "en"));
55
- }
56
-
57
- export function renderBaseline(inputs: BuildSystemPromptOptions, values: readonly PromptValue[]): string {
58
- const sections = new Map<string, string[]>([
59
- ["tools", []],
60
- ["tool-guidance", []],
61
- ["skills", []],
62
- ["documentation", []],
63
- ["project-context", []],
64
- ["environment", []],
65
- ["additional-instructions", []],
66
- ]);
67
- const skillTool = (["read", "bash"] as const).find((name) => inputs.selectedTools?.includes(name));
68
- if (skillTool) sections.get("skills")?.push(formatSkillsForPrompt(inputs.skills ?? [], skillTool).trim());
69
- sections.get("documentation")
70
- ?.push(`Consult Pi or Tau documentation when the request concerns their usage or extension APIs.
71
- Pi documentation:
72
- - Main documentation: ${getReadmePath()}
73
- - Additional docs: ${getDocsPath()}
74
- - Examples: ${getExamplesPath()}
75
- - Resolve docs/... and examples/... under those installed paths, not the working directory.
76
- - 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.
77
- - Read the relevant documentation and follow related Markdown references before implementing Pi integrations.`);
78
- sections
79
- .get("project-context")
80
- ?.push(
81
- ...(inputs.contextFiles ?? []).map(
82
- (file) =>
83
- `<project_instructions path=${JSON.stringify(file.path)}>\n${file.content}\n</project_instructions>`,
84
- ),
85
- );
86
- sections.get("environment")?.push(`Working directory: ${inputs.cwd}`);
87
- sections.get("additional-instructions")?.push(
88
- inputs.customPrompt ?? "",
89
- inputs.appendSystemPrompt ?? "",
90
- ...Object.entries(inputs.sections ?? {})
91
- .sort(([a], [b]) => a.localeCompare(b, "en"))
92
- .map(([key, text]) => `<${key}>\n${text}\n</${key}>`),
93
- );
94
- for (const value of values) {
95
- const content = sections.get(value.section) ?? [];
96
- content.push(value.text);
97
- sections.set(value.section, content);
98
- }
99
- return [
100
- FIXED_INSTRUCTIONS,
101
- ...[...sections].flatMap(([name, pieces]) => {
102
- const text = pieces.filter((piece) => piece.trim()).join("\n\n");
103
- return text ? [`<${name}>\n${text}\n</${name}>`] : [];
104
- }),
105
- ].join("\n\n");
106
- }
107
-
108
- export function renderUpdate(values: readonly PromptValue[]): string {
109
- return values
110
- .map(
111
- (value) =>
112
- `<context-update source=${JSON.stringify(value.key)}>\nThis replaces the previous information for this source.\n${value.text || "This source no longer supplies instructions."}\n</context-update>`,
113
- )
114
- .join("\n\n");
115
- }
@@ -1,114 +0,0 @@
1
- import type { Tool } from "@earendil-works/pi-ai";
2
- import type { ContextWithSystemEvent, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
3
- import type { PromptValue } from "../../shared/prompt-contributions.ts";
4
-
5
- export const BASELINE_TYPE = "tau.soul.baseline";
6
- export const UPDATE_TYPE = "tau.soul.update";
7
-
8
- export interface SavedBaseline {
9
- version: 1;
10
- compactionId: string | null;
11
- afterEntryId: string;
12
- text: string;
13
- values: PromptValue[];
14
- initialTools: Tool[];
15
- }
16
-
17
- export interface SavedUpdate {
18
- version: 1;
19
- baselineEntryId: string;
20
- afterEntryId: string;
21
- text: string;
22
- values: PromptValue[];
23
- tools: Tool[];
24
- }
25
-
26
- interface ActivePrompt {
27
- entryId: string;
28
- baseline: SavedBaseline;
29
- updates: Array<{ timestamp: number; data: SavedUpdate }>;
30
- }
31
-
32
- export function restorePrompt(branch: readonly SessionEntry[]): ActivePrompt | null {
33
- const newestFirst = [...branch].reverse();
34
- const compaction = newestFirst.find((entry) => entry.type === "compaction");
35
- const entry = newestFirst.find((item) => item.type === "custom" && item.customType === BASELINE_TYPE);
36
- if (entry?.type !== "custom") return null;
37
- const baseline = entry.data as SavedBaseline;
38
- if (baseline?.version !== 1 || typeof baseline.text !== "string" || !Array.isArray(baseline.values)) {
39
- throw new Error("Invalid saved Soul baseline.");
40
- }
41
- if (baseline.compactionId !== (compaction?.id ?? null)) return null;
42
- const updates: ActivePrompt["updates"] = [];
43
- for (const item of branch) {
44
- if (item.type !== "custom" || item.customType !== UPDATE_TYPE) continue;
45
- const data = item.data as SavedUpdate;
46
- if (data?.version !== 1 || typeof data.text !== "string" || !Array.isArray(data.values)) {
47
- throw new Error("Invalid saved Soul update.");
48
- }
49
- if (data.baselineEntryId === entry.id) updates.push({ timestamp: Date.parse(item.timestamp), data });
50
- }
51
- return { entryId: entry.id, baseline, updates };
52
- }
53
-
54
- /** Rebuild only Soul's text; tool declarations retain their original transcript positions. */
55
- export function projectPrompt(
56
- messages: ContextWithSystemEvent["messages"],
57
- saved: ActivePrompt,
58
- ctx: ExtensionContext,
59
- ): ContextWithSystemEvent["messages"] {
60
- const projection = ctx.sessionManager.buildSessionProjection();
61
- // Pi clones the hook input. Compare content, never object identity. A rewrite from
62
- // another extension must not silently move a saved update or tool declaration.
63
- if (JSON.stringify(messages) !== JSON.stringify(projection.messages)) {
64
- throw new Error("Soul cannot preserve the prompt prefix: another extension changed request history.");
65
- }
66
- const output: ContextWithSystemEvent["messages"] = [
67
- {
68
- role: "system",
69
- content: saved.baseline.text,
70
- toolsAdded: saved.baseline.initialTools,
71
- timestamp: 0,
72
- },
73
- ];
74
- let pastBaseline = false;
75
- const placed = new Set<SavedUpdate>();
76
- const admitted = new Set(saved.baseline.initialTools.map((tool) => tool.name));
77
- for (const entry of projection.entries) {
78
- for (const message of entry.messages) {
79
- if (message.role !== "system") {
80
- output.push(message);
81
- continue;
82
- }
83
- if (!pastBaseline) continue;
84
- if (typeof message.content === "string" ? message.content.trim() : message.content.length > 0) {
85
- throw new Error("A later system instruction bypassed Soul's prompt contributions.");
86
- }
87
- // Removed tools remain declared for cache stability; Pi still controls execution.
88
- const added = (message.toolsAdded ?? []).filter((tool) => !admitted.has(tool.name));
89
- for (const tool of added) admitted.add(tool.name);
90
- if (added.length > 0)
91
- output.push({ role: "system", content: "", toolsAdded: added, timestamp: message.timestamp });
92
- }
93
- if (entry.sourceEntry.id === saved.baseline.afterEntryId) pastBaseline = true;
94
- for (const update of saved.updates) {
95
- if (update.data.afterEntryId !== entry.sourceEntry.id) continue;
96
- if (update.data.text)
97
- output.push({
98
- role: "custom",
99
- customType: UPDATE_TYPE,
100
- content: update.data.text,
101
- display: false,
102
- timestamp: update.timestamp,
103
- });
104
- placed.add(update.data);
105
- }
106
- }
107
- if (!pastBaseline || placed.size !== saved.updates.length)
108
- throw new Error("Soul prompt history lost a saved anchor.");
109
- return output;
110
- }
111
-
112
- export function admittedTools(saved: ActivePrompt): Tool[] {
113
- return saved.updates.at(-1)?.data.tools ?? saved.baseline.initialTools;
114
- }
@@ -1,31 +0,0 @@
1
- import { declarationsEqual, type Api, type Model, type Tool } from "@earendil-works/pi-ai";
2
-
3
- /** Removals only disable execution; changed declarations wait for compaction. */
4
- export function toolChangeReason(
5
- model: Model<Api>,
6
- previous: readonly Tool[],
7
- requested: readonly Tool[],
8
- ): string | null {
9
- const existing = new Map(previous.map((tool) => [tool.name, tool]));
10
- for (const tool of requested) {
11
- const old = existing.get(tool.name);
12
- if (old && !declarationsEqual(old, tool))
13
- return `Tool ${tool.name} changed its schema or description. Compact before loading it.`;
14
- }
15
- if (requested.every((tool) => existing.has(tool.name))) return null;
16
- const compat = model.compat;
17
- const supported =
18
- compat &&
19
- "supportsMidConvoSystemMessages" in compat &&
20
- compat.supportsMidConvoSystemMessages === true &&
21
- ((model.api === "anthropic-messages" &&
22
- "supportsMidConvoToolChanges" in compat &&
23
- compat.supportsMidConvoToolChanges === true &&
24
- previous.length > 0) ||
25
- ((model.api === "openai-responses" || model.api === "openai-codex-responses") &&
26
- (("supportsAdditionalTools" in compat && compat.supportsAdditionalTools === true) ||
27
- ("supportsToolSearch" in compat && compat.supportsToolSearch === true))));
28
- return supported
29
- ? null
30
- : "This model cannot add tools without changing the cached prefix. Compact before loading them.";
31
- }