@shanepadgett/tau-agent 0.33.0 → 0.34.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.
- package/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +130 -93
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
package/extensions/tau/index.ts
CHANGED
|
@@ -64,6 +64,52 @@ async function init(ctx: ExtensionCommandContext, scope: "global" | "project"):
|
|
|
64
64
|
ctx.ui.notify(`tau init wrote ${path}`, "info");
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function pushExtensionFindings(
|
|
68
|
+
findings: Finding[],
|
|
69
|
+
level: Finding["level"],
|
|
70
|
+
path: string,
|
|
71
|
+
extensions: unknown,
|
|
72
|
+
specs: Awaited<ReturnType<typeof discoverTauSettingsSpecs>>,
|
|
73
|
+
): void {
|
|
74
|
+
if (extensions !== undefined && (!extensions || typeof extensions !== "object" || Array.isArray(extensions))) {
|
|
75
|
+
findings.push({ level, message: `extensions must be an object: ${path}`, startup: true });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const extensionRecord = (extensions ?? {}) as Record<string, unknown>;
|
|
79
|
+
for (const spec of specs) {
|
|
80
|
+
const section = extensionRecord[spec.key];
|
|
81
|
+
if (section === undefined) continue;
|
|
82
|
+
if (!Value.Check(spec.schema, section)) {
|
|
83
|
+
findings.push({ level, message: `invalid extensions.${spec.key}: ${path}`, startup: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function inspectPath(
|
|
89
|
+
findings: Finding[],
|
|
90
|
+
item: { level: Finding["level"]; path: string; trusted: boolean },
|
|
91
|
+
specs: Awaited<ReturnType<typeof discoverTauSettingsSpecs>>,
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
const status = await readJsonStatus(item.path);
|
|
94
|
+
if (!status.exists) return;
|
|
95
|
+
if (!item.trusted) {
|
|
96
|
+
findings.push({
|
|
97
|
+
level: item.level,
|
|
98
|
+
message: `project Tau settings ignored because project is not trusted: ${item.path}. Run /trust, then /reload.`,
|
|
99
|
+
startup: true,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!status.ok) {
|
|
104
|
+
findings.push({ level: item.level, message: `malformed JSON: ${item.path}: ${status.error}`, startup: true });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (typeof status.value.$schema !== "string") {
|
|
108
|
+
findings.push({ level: item.level, message: `missing $schema: ${item.path}`, startup: false });
|
|
109
|
+
}
|
|
110
|
+
pushExtensionFindings(findings, item.level, item.path, status.value.extensions, specs);
|
|
111
|
+
}
|
|
112
|
+
|
|
67
113
|
async function inspect(ctx: Pick<ExtensionContext, "cwd" | "isProjectTrusted">): Promise<Finding[]> {
|
|
68
114
|
const findings: Finding[] = [];
|
|
69
115
|
const specs = await discoverTauSettingsSpecs(ctx.cwd);
|
|
@@ -71,44 +117,7 @@ async function inspect(ctx: Pick<ExtensionContext, "cwd" | "isProjectTrusted">):
|
|
|
71
117
|
{ level: "global" as const, path: globalTauSettingsPath(), trusted: true },
|
|
72
118
|
{ level: "project" as const, path: await projectTauSettingsPath(ctx.cwd), trusted: ctx.isProjectTrusted() },
|
|
73
119
|
];
|
|
74
|
-
|
|
75
|
-
for (const item of paths) {
|
|
76
|
-
const status = await readJsonStatus(item.path);
|
|
77
|
-
if (!status.exists) continue;
|
|
78
|
-
if (!item.trusted) {
|
|
79
|
-
findings.push({
|
|
80
|
-
level: item.level,
|
|
81
|
-
message: `project Tau settings ignored because project is not trusted: ${item.path}. Run /trust, then /reload.`,
|
|
82
|
-
startup: true,
|
|
83
|
-
});
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (!status.ok) {
|
|
87
|
-
findings.push({ level: item.level, message: `malformed JSON: ${item.path}: ${status.error}`, startup: true });
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (typeof status.value.$schema !== "string") {
|
|
91
|
-
findings.push({ level: item.level, message: `missing $schema: ${item.path}`, startup: false });
|
|
92
|
-
}
|
|
93
|
-
const extensions = status.value.extensions;
|
|
94
|
-
if (extensions !== undefined && (!extensions || typeof extensions !== "object" || Array.isArray(extensions))) {
|
|
95
|
-
findings.push({ level: item.level, message: `extensions must be an object: ${item.path}`, startup: true });
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
const extensionRecord = (extensions ?? {}) as Record<string, unknown>;
|
|
99
|
-
for (const spec of specs) {
|
|
100
|
-
const section = extensionRecord[spec.key];
|
|
101
|
-
if (section === undefined) continue;
|
|
102
|
-
if (!Value.Check(spec.schema, section)) {
|
|
103
|
-
findings.push({
|
|
104
|
-
level: item.level,
|
|
105
|
-
message: `invalid extensions.${spec.key}: ${item.path}`,
|
|
106
|
-
startup: true,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
120
|
+
for (const item of paths) await inspectPath(findings, item, specs);
|
|
112
121
|
return findings;
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -6,6 +6,10 @@ Tau is a coding agent built to be a reliable partner during software work. It ca
|
|
|
6
6
|
|
|
7
7
|
Gives the agent macOS window discovery, screenshots, and app activation tools for visual validation. Requires macOS 14 and Screen & System Audio Recording permission.
|
|
8
8
|
|
|
9
|
+
## aside
|
|
10
|
+
|
|
11
|
+
Adds `/aside <question>` for a one-off question to the current model without putting the question or answer in the conversation. Choose the current conversation branch or no context. A thinking widget clears when the answer opens. Run `/aside` to reopen the latest answer and `/aside clear` to cancel or clear it.
|
|
12
|
+
|
|
9
13
|
## attention
|
|
10
14
|
|
|
11
15
|
Shows attention state when Tau needs the user to look at the chat, finishes compacting a session, or summarizes an abandoned branch.
|
|
@@ -32,15 +36,15 @@ Adds `/commit` for semantic commit grouping, review, and committing selected rep
|
|
|
32
36
|
|
|
33
37
|
## context
|
|
34
38
|
|
|
35
|
-
Adds `/context` to
|
|
39
|
+
Adds `/context` to inject reusable repository work scopes from `.pi/contexts`, and `/context-sync` or `/context-sync <nudge>` for human-driven catalog sync. 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. Manual sync replaces the editor with a status panel; Escape or Ctrl+C cancels. When `sync.automation` is on, coding agent can also run `context-sync` after meaningful uncommitted work. Sync catalogs durable code and long-lived documentation; recurring scratch, planning, interview, and rough-idea paths belong in `validation.ignoreGlobs`. `sync.enabled` is master switch for command, automation, and validation auto-run. Context validation is off by default; when on (and sync enabled), Tau auto-runs context-sync on failure. 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.
|
|
36
40
|
|
|
37
|
-
##
|
|
41
|
+
## checkpoint
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Adds the `checkpoint` tool for retaining exact user and assistant messages, current file reads and outlines, durable work state, an agent-written resume directive, and deferred file paths as a rolling continuation context. Checkpoint rows are hidden by default. Set `extensions.checkpoint.showToolRows` to `true` before `/reload` to watch checkpoint and newly injected-file rows while working; existing injected-file rows keep their saved display state. The agent receives hidden checkpoint nudges at 50% and 75% of `extensions.checkpoint.checkpointTokenLimit` (150,000 by default); non-checkpoint tools are blocked at the limit until checkpoint succeeds.
|
|
40
44
|
|
|
41
45
|
## effort
|
|
42
46
|
|
|
43
|
-
Adds `/effort [
|
|
47
|
+
Adds `/effort [quick|standard|deep]` to select effort and a provider from current logins. Tau selects provider’s best available model for tier, then tries its configured model fallback. `Ctrl+Shift+E` cycles tiers on current provider. Footer derives effort from current provider, model, and thinking level, and hides it when no configured tier matches.
|
|
44
48
|
|
|
45
49
|
## explore
|
|
46
50
|
|
|
@@ -74,6 +78,10 @@ Replaces separate edit/write operations with one multi-file `patch` tool. It can
|
|
|
74
78
|
|
|
75
79
|
Adds `/qna` for when the agent has asked you several questions in chat and you want a friendly UI for answering them on your own terms. It is only active when you manually run the command.
|
|
76
80
|
|
|
81
|
+
## ready
|
|
82
|
+
|
|
83
|
+
Adds `/ready` to scan agent-readiness rails (cold start, toolchain, verify, lint/entropy, policy, standards, context, and related signals). Choose Markdown or HTML; Tau writes a timestamped report under `.pi/tau/ready/` and notifies with the path. Scan-only in v1 — no model judgment and no scores.
|
|
84
|
+
|
|
77
85
|
## review
|
|
78
86
|
|
|
79
87
|
Adds `/review` for explicit isolated review of current Git changes. Choose `simplify`, `architecture`, or `correctness`, or run a mode directly. Results stay outside agent context until you send them from result view, and can be exported under `.pi/tau/reviews/`. `/review show` reopens latest result on current session branch.
|
|
@@ -92,7 +100,7 @@ Supplies the agent with the current local date and an initial root directory sna
|
|
|
92
100
|
|
|
93
101
|
## script-runner
|
|
94
102
|
|
|
95
|
-
Gives the agent a first-class `script_runner` tool to execute Python and
|
|
103
|
+
Gives the agent a first-class `script_runner` tool to execute Python 3, Node.js, and Deno scripts instead of bash. On failure it returns a `scriptId`; the agent retries with targeted `{oldText,newText}` edits against the script it already wrote rather than resending the whole script. Runtimes are detected from the environment (Python 3 via `python3`; `node` is the local Node.js runtime with `--experimental-strip-types`, Node 22.6+ — full Node APIs, TypeScript with erasable syntax or plain JavaScript; `deno` via `deno run -A` — full permissions, native TypeScript/JavaScript, Deno APIs). The tool registers only available runtimes and is hidden from the prompt if none are present.
|
|
96
104
|
|
|
97
105
|
## silent-command-runner
|
|
98
106
|
|
|
@@ -120,7 +128,7 @@ Adds `/tau`, `/tau init [--global|--project]`, and `/tau doctor` for Tau setup a
|
|
|
120
128
|
|
|
121
129
|
## tool-loader
|
|
122
130
|
|
|
123
|
-
Progressively exposes specialist
|
|
131
|
+
Progressively exposes registered specialist tool groups through `load_tools`. Tau registers `web`, `image`, and `appshot`; project or global package extensions can add groups with `registerDeferredToolGroup()` from `@shanepadgett/tau-agent`. Supported providers can preserve more prompt-cache reuse.
|
|
124
132
|
|
|
125
133
|
## web
|
|
126
134
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Tool Loader
|
|
2
2
|
|
|
3
|
-
Tau progressively exposes specialist
|
|
3
|
+
Tau progressively exposes registered specialist tool groups. Most coding turns do not need web, image, macOS application, or application-specific schemas, so Pi can load those tools later without discarding supported provider cache prefixes.
|
|
4
4
|
|
|
5
|
-
The agent normally calls `load_tools` itself.
|
|
5
|
+
The agent normally calls `load_tools` itself. 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 description is included in the loader catalog so the agent can select it when a task needs that capability.
|
|
12
|
+
|
|
11
13
|
Supported models optimize prompt caching when a group loads. Other models keep the same functional behavior.
|
|
12
14
|
|
|
13
15
|
After changing this extension during development, run `/reload` before testing.
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
1
|
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
2
|
import { type Static, Type } from "typebox";
|
|
4
|
-
|
|
5
|
-
const CAPABILITIES = ["web", "image", "appshot"] as const;
|
|
6
|
-
type Capability = (typeof CAPABILITIES)[number];
|
|
7
|
-
|
|
8
|
-
const CAPABILITY_TOOLS: Record<Capability, readonly string[]> = {
|
|
9
|
-
web: ["webfetch", "websearch", "codesearch"],
|
|
10
|
-
image: ["image_gen"],
|
|
11
|
-
appshot: ["list_windows", "screenshot_window", "activate_app"],
|
|
12
|
-
};
|
|
13
|
-
const SPECIALIST_TOOLS = CAPABILITIES.flatMap((capability) => CAPABILITY_TOOLS[capability]);
|
|
3
|
+
import { registeredDeferredToolGroups, type DeferredToolGroupInfo } from "../../src/tool-loading/index.ts";
|
|
14
4
|
|
|
15
5
|
const loadToolsSchema = Type.Object(
|
|
16
6
|
{
|
|
17
|
-
capability:
|
|
18
|
-
|
|
7
|
+
capability: Type.String({
|
|
8
|
+
minLength: 1,
|
|
9
|
+
description: "Registered specialist group ID, such as web, image, appshot, or a package-provided group",
|
|
19
10
|
}),
|
|
20
11
|
},
|
|
21
12
|
{ additionalProperties: false },
|
|
@@ -25,50 +16,85 @@ type LoadToolsParams = Static<typeof loadToolsSchema>;
|
|
|
25
16
|
|
|
26
17
|
interface LoadToolsDetails {
|
|
27
18
|
version: 1;
|
|
28
|
-
capability:
|
|
19
|
+
capability: string;
|
|
29
20
|
requestedToolNames: string[];
|
|
30
21
|
addedToolNames: string[];
|
|
31
22
|
}
|
|
32
23
|
|
|
33
24
|
export default function toolLoaderExtension(pi: ExtensionAPI): void {
|
|
34
25
|
let managed = false;
|
|
35
|
-
let
|
|
26
|
+
let allowedToolNames = new Map<string, ReadonlySet<string>>();
|
|
27
|
+
let managedToolNames = new Set<string>();
|
|
28
|
+
|
|
29
|
+
pi.registerTool(createLoadToolsTool(pi, []));
|
|
30
|
+
|
|
31
|
+
pi.on("session_start", (_event, ctx) => {
|
|
32
|
+
const groups = registeredDeferredToolGroups(pi);
|
|
33
|
+
pi.registerTool(createLoadToolsTool(pi, groups));
|
|
34
|
+
|
|
35
|
+
const initial = pi.getActiveTools();
|
|
36
|
+
const initialSet = new Set(initial);
|
|
37
|
+
allowedToolNames = new Map(
|
|
38
|
+
groups.map((group) => [group.id, new Set(group.toolNames.filter((name) => initialSet.has(name)))]),
|
|
39
|
+
);
|
|
40
|
+
managedToolNames = new Set(groups.flatMap((group) => group.toolNames));
|
|
41
|
+
managed = initialSet.has("load_tools") && groups.length > 0;
|
|
42
|
+
if (managed) restoreActiveTools(pi, initial, loadedCapabilities(ctx.sessionManager.getBranch()), groups);
|
|
43
|
+
});
|
|
36
44
|
|
|
37
|
-
pi.
|
|
38
|
-
|
|
45
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
46
|
+
if (managed) {
|
|
47
|
+
restoreActiveTools(
|
|
48
|
+
pi,
|
|
49
|
+
pi.getActiveTools(),
|
|
50
|
+
loadedCapabilities(ctx.sessionManager.getBranch()),
|
|
51
|
+
registeredDeferredToolGroups(pi),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function createLoadToolsTool(pi: ExtensionAPI, groups: readonly DeferredToolGroupInfo[]) {
|
|
57
|
+
return defineTool<typeof loadToolsSchema, LoadToolsDetails>({
|
|
39
58
|
name: "load_tools",
|
|
40
59
|
label: "Load Tools",
|
|
41
|
-
description:
|
|
42
|
-
|
|
43
|
-
promptSnippet: "Load a specialist Tau tool group for web research, image generation, or macOS app inspection",
|
|
60
|
+
description: `Load one registered Tau specialist tool group for the current session.${formatGroupCatalog(groups)}`,
|
|
61
|
+
promptSnippet: "Load a registered specialist tool group when the current tools cannot perform the task",
|
|
44
62
|
promptGuidelines: [
|
|
45
|
-
"Use load_tools before attempting a specialist capability whose tools are not currently available.",
|
|
63
|
+
"Use load_tools before attempting a registered specialist capability whose tools are not currently available.",
|
|
46
64
|
],
|
|
47
65
|
parameters: loadToolsSchema,
|
|
48
66
|
async execute(_toolCallId, params: LoadToolsParams) {
|
|
67
|
+
const group = registeredDeferredToolGroups(pi).find((candidate) => candidate.id === params.capability);
|
|
68
|
+
if (group === undefined) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Unknown specialist tool group: ${params.capability}.${formatGroupCatalog(registeredDeferredToolGroups(pi))}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
const before = pi.getActiveTools();
|
|
50
|
-
const requested = [...
|
|
75
|
+
const requested = [...group.toolNames];
|
|
51
76
|
const registered = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
52
|
-
const
|
|
77
|
+
const allowed = allowedToolNames.get(group.id) ?? new Set<string>();
|
|
78
|
+
const loadable = requested.filter((name) => registered.has(name) && allowed.has(name));
|
|
53
79
|
if (loadable.length === 0) {
|
|
54
80
|
throw new Error(`No ${params.capability} tools are available in this session's tool configuration.`);
|
|
55
81
|
}
|
|
82
|
+
|
|
56
83
|
const beforeSet = new Set(before);
|
|
57
|
-
|
|
58
|
-
pi.setActiveTools(next);
|
|
84
|
+
pi.setActiveTools([...before, ...loadable.filter((name) => !beforeSet.has(name))]);
|
|
59
85
|
const after = pi.getActiveTools();
|
|
60
|
-
const addedToolNames =
|
|
86
|
+
const addedToolNames = requested.filter((name) => !beforeSet.has(name) && after.includes(name));
|
|
61
87
|
const available = requested.filter((name) => after.includes(name));
|
|
62
88
|
const unavailable = requested.filter((name) => !after.includes(name));
|
|
63
|
-
const label = `${params.capability[0]?.toUpperCase()}${params.capability.slice(1)}`;
|
|
64
89
|
const text =
|
|
65
90
|
addedToolNames.length > 0
|
|
66
91
|
? `Loaded ${params.capability} tools: ${addedToolNames.join(", ")}.`
|
|
67
|
-
: `${
|
|
92
|
+
: `${params.capability} tools are already loaded: ${available.join(", ")}.`;
|
|
93
|
+
|
|
68
94
|
return {
|
|
69
95
|
content: [
|
|
70
96
|
{
|
|
71
|
-
type: "text",
|
|
97
|
+
type: "text" as const,
|
|
72
98
|
text: unavailable.length ? `${text} Unavailable: ${unavailable.join(", ")}.` : text,
|
|
73
99
|
},
|
|
74
100
|
],
|
|
@@ -80,33 +106,33 @@ export default function toolLoaderExtension(pi: ExtensionAPI): void {
|
|
|
80
106
|
},
|
|
81
107
|
};
|
|
82
108
|
},
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
pi.on("session_start", (_event, ctx) => {
|
|
87
|
-
const initial = pi.getActiveTools();
|
|
88
|
-
const initialSet = new Set(initial);
|
|
89
|
-
allowedSpecialistNames = new Set(SPECIALIST_TOOLS.filter((name) => initialSet.has(name)));
|
|
90
|
-
managed = initialSet.has("load_tools") && SPECIALIST_TOOLS.every((name) => initialSet.has(name));
|
|
91
|
-
if (managed) restoreActiveTools(pi, initial, loadedCapabilities(ctx.sessionManager.getBranch()));
|
|
92
|
-
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
93
111
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
function restoreActiveTools(
|
|
113
|
+
pi: ExtensionAPI,
|
|
114
|
+
current: readonly string[],
|
|
115
|
+
loaded: ReadonlySet<string>,
|
|
116
|
+
groups: readonly DeferredToolGroupInfo[],
|
|
117
|
+
): void {
|
|
118
|
+
const next = current.filter((name) => !managedToolNames.has(name));
|
|
119
|
+
for (const group of groups) {
|
|
120
|
+
if (!loaded.has(group.id)) continue;
|
|
121
|
+
const allowed = allowedToolNames.get(group.id) ?? new Set<string>();
|
|
122
|
+
next.push(...group.toolNames.filter((name) => allowed.has(name)));
|
|
123
|
+
}
|
|
124
|
+
pi.setActiveTools([...new Set(next)]);
|
|
125
|
+
}
|
|
97
126
|
}
|
|
98
127
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
if (loaded.has(capability)) next.push(...CAPABILITY_TOOLS[capability]);
|
|
104
|
-
}
|
|
105
|
-
pi.setActiveTools([...new Set(next)]);
|
|
128
|
+
function formatGroupCatalog(groups: readonly DeferredToolGroupInfo[]): string {
|
|
129
|
+
if (groups.length === 0) return " No specialist groups are registered.";
|
|
130
|
+
const catalog = groups.map((group) => `${group.id}: ${group.description}`).join("; ");
|
|
131
|
+
return ` Registered groups: ${catalog}.`;
|
|
106
132
|
}
|
|
107
133
|
|
|
108
|
-
function loadedCapabilities(entries: readonly unknown[]): Set<
|
|
109
|
-
const loaded = new Set<
|
|
134
|
+
function loadedCapabilities(entries: readonly unknown[]): Set<string> {
|
|
135
|
+
const loaded = new Set<string>();
|
|
110
136
|
for (const value of entries) {
|
|
111
137
|
if (!value || typeof value !== "object") continue;
|
|
112
138
|
const entry = value as Record<string, unknown>;
|
|
@@ -125,7 +151,6 @@ function isLoadToolsDetails(value: unknown): value is LoadToolsDetails {
|
|
|
125
151
|
return (
|
|
126
152
|
details.version === 1 &&
|
|
127
153
|
typeof details.capability === "string" &&
|
|
128
|
-
CAPABILITIES.includes(details.capability as Capability) &&
|
|
129
154
|
Array.isArray(details.requestedToolNames) &&
|
|
130
155
|
details.requestedToolNames.every((name) => typeof name === "string") &&
|
|
131
156
|
Array.isArray(details.addedToolNames) &&
|
package/extensions/web/index.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { createToolRowStateStore } from "../../shared/tool-row-state.js";
|
|
3
|
+
import { registerDeferredToolGroup } from "../../src/tool-loading/index.ts";
|
|
3
4
|
import { createCodeSearchTool } from "./codesearch.ts";
|
|
4
5
|
import { createWebFetchTool } from "./webfetch.ts";
|
|
5
6
|
import { createWebSearchTool } from "./websearch.ts";
|
|
6
7
|
|
|
7
8
|
export default function webExtension(pi: ExtensionAPI): void {
|
|
8
9
|
const rowState = createToolRowStateStore(pi, "web.tool-row-state");
|
|
9
|
-
pi
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
registerDeferredToolGroup(pi, {
|
|
11
|
+
id: "web",
|
|
12
|
+
description: "Public web and implementation research",
|
|
13
|
+
tools: [createWebFetchTool(rowState), createWebSearchTool(rowState), createCodeSearchTool(rowState)],
|
|
14
|
+
});
|
|
12
15
|
pi.on("session_start", () => rowState.clear());
|
|
13
16
|
}
|
|
@@ -27,14 +27,22 @@ const webFetchParams = Type.Object(
|
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
type WebFetchParams = Static<typeof webFetchParams>;
|
|
30
|
+
type WebFetchFormat = "markdown" | "text" | "html";
|
|
30
31
|
interface WebFetchDetails {
|
|
31
32
|
url: string;
|
|
32
|
-
format:
|
|
33
|
+
format: WebFetchFormat;
|
|
33
34
|
mime: string;
|
|
34
35
|
bytes: number;
|
|
35
36
|
truncation?: TruncationResult;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
type WebFetchContent = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
|
|
40
|
+
|
|
41
|
+
interface WebFetchResult {
|
|
42
|
+
content: WebFetchContent[];
|
|
43
|
+
details: WebFetchDetails;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
async function readResponseBody(response: FetchResponse): Promise<Uint8Array> {
|
|
39
47
|
if (!response.body) return new Uint8Array();
|
|
40
48
|
const reader = response.body.getReader();
|
|
@@ -61,6 +69,110 @@ function renderCallSummary(args: WebFetchParams): string {
|
|
|
61
69
|
return truncateCallSummary((args.url ?? "").trim());
|
|
62
70
|
}
|
|
63
71
|
|
|
72
|
+
function parseFetchUrl(raw: string): URL {
|
|
73
|
+
let url: URL;
|
|
74
|
+
try {
|
|
75
|
+
url = new URL(raw);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error(`Invalid URL: ${raw}`);
|
|
78
|
+
}
|
|
79
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
80
|
+
throw new Error("URL must use http:// or https://");
|
|
81
|
+
}
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function acceptHeaderForFormat(format: WebFetchFormat): string {
|
|
86
|
+
if (format === "markdown") return "text/markdown;q=1.0, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1";
|
|
87
|
+
if (format === "text") return "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1";
|
|
88
|
+
return "text/html;q=1.0, application/xhtml+xml;q=0.9, */*;q=0.1";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function fetchWithChallengeRetry(
|
|
92
|
+
url: string,
|
|
93
|
+
headers: Record<string, string>,
|
|
94
|
+
signal: AbortSignal,
|
|
95
|
+
): Promise<FetchResponse> {
|
|
96
|
+
const first = (await fetch(url, { method: "GET", headers, signal })) as FetchResponse;
|
|
97
|
+
if (first.status !== 403 || first.headers.get("cf-mitigated")?.toLowerCase() !== "challenge") return first;
|
|
98
|
+
await first.body?.cancel().catch(() => undefined);
|
|
99
|
+
return (await fetch(url, {
|
|
100
|
+
method: "GET",
|
|
101
|
+
headers: { ...headers, "User-Agent": "pi" },
|
|
102
|
+
signal,
|
|
103
|
+
})) as FetchResponse;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function assertDeclaredBodySizeOk(response: FetchResponse): Promise<void> {
|
|
107
|
+
const declaredLength = response.headers.get("content-length");
|
|
108
|
+
if (declaredLength === null) return;
|
|
109
|
+
const bytes = Number.parseInt(declaredLength, 10);
|
|
110
|
+
if (!Number.isFinite(bytes) || bytes <= MAX_RESPONSE_BYTES) return;
|
|
111
|
+
await response.body?.cancel().catch(() => undefined);
|
|
112
|
+
throw new Error("Response too large (limit is 5MB)");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function formatFetchedText(raw: string, format: WebFetchFormat, mime: string): string {
|
|
116
|
+
if (format === "html") return raw;
|
|
117
|
+
const isHtml = mime === "text/html" || mime === "application/xhtml+xml";
|
|
118
|
+
if (!isHtml) return raw;
|
|
119
|
+
return format === "text" ? htmlToText(raw) : htmlToMarkdown(raw);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildWebFetchResult(url: string, format: WebFetchFormat, mime: string, body: Uint8Array): WebFetchResult {
|
|
123
|
+
const details = { url, format, mime, bytes: body.byteLength } satisfies WebFetchDetails;
|
|
124
|
+
if (mime.startsWith("image/") && mime !== "image/svg+xml") {
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{ type: "text", text: `Fetched image from ${url} (${mime})` },
|
|
128
|
+
{ type: "image", data: Buffer.from(body).toString("base64"), mimeType: mime },
|
|
129
|
+
],
|
|
130
|
+
details,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const truncated = truncateToolOutput(formatFetchedText(new TextDecoder().decode(body), format, mime));
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: truncated.text }],
|
|
136
|
+
details: {
|
|
137
|
+
...details,
|
|
138
|
+
...(truncated.truncation ? { truncation: truncated.truncation } : {}),
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function executeWebFetch(
|
|
144
|
+
params: WebFetchParams,
|
|
145
|
+
signal: AbortSignal | undefined,
|
|
146
|
+
onUpdate: ((update: { content: WebFetchContent[]; details: undefined }) => void | Promise<void>) | undefined,
|
|
147
|
+
): Promise<WebFetchResult> {
|
|
148
|
+
const url = parseFetchUrl(params.url);
|
|
149
|
+
const format = params.format ?? "markdown";
|
|
150
|
+
const timeout = normalizeTimeout(params.timeout, 30);
|
|
151
|
+
await onUpdate?.({ content: [{ type: "text", text: "Fetching page..." }], details: undefined });
|
|
152
|
+
const timeoutSignal = AbortSignal.timeout(timeout * 1000);
|
|
153
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const headers = {
|
|
157
|
+
"User-Agent":
|
|
158
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
|
159
|
+
Accept: acceptHeaderForFormat(format),
|
|
160
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
161
|
+
};
|
|
162
|
+
const response = await fetchWithChallengeRetry(url.toString(), headers, requestSignal);
|
|
163
|
+
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
|
|
164
|
+
await assertDeclaredBodySizeOk(response);
|
|
165
|
+
const body = await readResponseBody(response);
|
|
166
|
+
const mime = (response.headers.get("content-type")?.split(";", 1)[0] ?? "").trim().toLowerCase();
|
|
167
|
+
return buildWebFetchResult(url.toString(), format, mime, body);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (timeoutSignal.aborted && signal?.aborted !== true) {
|
|
170
|
+
throw new Error(`Web fetch timed out after ${timeout}s`);
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
64
176
|
export function createWebFetchTool(rowState: ToolRowStateStore) {
|
|
65
177
|
return defineTool<typeof webFetchParams, WebFetchDetails | undefined>({
|
|
66
178
|
name: "webfetch",
|
|
@@ -69,91 +181,7 @@ export function createWebFetchTool(rowState: ToolRowStateStore) {
|
|
|
69
181
|
"Fetch a known HTTP(S) URL as Markdown, text, or HTML. Use webfetch when you already have a URL; use websearch for broad discovery and codesearch for implementation-oriented lookups. Use a separate research workflow when several searches, fetches, and synthesis are needed. Supports inline images, limits response bodies to 5 MB, and truncates text to 2,000 lines or 50 KB.",
|
|
70
182
|
parameters: webFetchParams,
|
|
71
183
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
url = new URL(params.url);
|
|
75
|
-
} catch {
|
|
76
|
-
throw new Error(`Invalid URL: ${params.url}`);
|
|
77
|
-
}
|
|
78
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
79
|
-
throw new Error("URL must use http:// or https://");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const format = params.format ?? "markdown";
|
|
83
|
-
const timeout = normalizeTimeout(params.timeout, 30);
|
|
84
|
-
await onUpdate?.({ content: [{ type: "text", text: "Fetching page..." }], details: undefined });
|
|
85
|
-
const timeoutSignal = AbortSignal.timeout(timeout * 1000);
|
|
86
|
-
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const accept =
|
|
90
|
-
format === "markdown"
|
|
91
|
-
? "text/markdown;q=1.0, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1"
|
|
92
|
-
: format === "text"
|
|
93
|
-
? "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1"
|
|
94
|
-
: "text/html;q=1.0, application/xhtml+xml;q=0.9, */*;q=0.1";
|
|
95
|
-
const headers = {
|
|
96
|
-
"User-Agent":
|
|
97
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
|
98
|
-
Accept: accept,
|
|
99
|
-
"Accept-Language": "en-US,en;q=0.9",
|
|
100
|
-
};
|
|
101
|
-
const first = (await fetch(url.toString(), {
|
|
102
|
-
method: "GET",
|
|
103
|
-
headers,
|
|
104
|
-
signal: requestSignal,
|
|
105
|
-
})) as FetchResponse;
|
|
106
|
-
let response = first;
|
|
107
|
-
if (first.status === 403 && first.headers.get("cf-mitigated")?.toLowerCase() === "challenge") {
|
|
108
|
-
await first.body?.cancel().catch(() => undefined);
|
|
109
|
-
response = (await fetch(url.toString(), {
|
|
110
|
-
method: "GET",
|
|
111
|
-
headers: { ...headers, "User-Agent": "pi" },
|
|
112
|
-
signal: requestSignal,
|
|
113
|
-
})) as FetchResponse;
|
|
114
|
-
}
|
|
115
|
-
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
|
|
116
|
-
|
|
117
|
-
const declaredLength = response.headers.get("content-length");
|
|
118
|
-
if (declaredLength !== null) {
|
|
119
|
-
const bytes = Number.parseInt(declaredLength, 10);
|
|
120
|
-
if (Number.isFinite(bytes) && bytes > MAX_RESPONSE_BYTES) {
|
|
121
|
-
await response.body?.cancel().catch(() => undefined);
|
|
122
|
-
throw new Error("Response too large (limit is 5MB)");
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const body = await readResponseBody(response);
|
|
127
|
-
const mime = (response.headers.get("content-type")?.split(";", 1)[0] ?? "").trim().toLowerCase();
|
|
128
|
-
const details = { url: url.toString(), format, mime, bytes: body.byteLength } satisfies WebFetchDetails;
|
|
129
|
-
if (mime.startsWith("image/") && mime !== "image/svg+xml") {
|
|
130
|
-
return {
|
|
131
|
-
content: [
|
|
132
|
-
{ type: "text", text: `Fetched image from ${url.toString()} (${mime})` },
|
|
133
|
-
{ type: "image", data: Buffer.from(body).toString("base64"), mimeType: mime },
|
|
134
|
-
],
|
|
135
|
-
details,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const raw = new TextDecoder().decode(body);
|
|
140
|
-
const isHtml = mime === "text/html" || mime === "application/xhtml+xml";
|
|
141
|
-
const output =
|
|
142
|
-
format === "html" ? raw : isHtml ? (format === "text" ? htmlToText(raw) : htmlToMarkdown(raw)) : raw;
|
|
143
|
-
const truncated = truncateToolOutput(output);
|
|
144
|
-
return {
|
|
145
|
-
content: [{ type: "text", text: truncated.text }],
|
|
146
|
-
details: {
|
|
147
|
-
...details,
|
|
148
|
-
...(truncated.truncation ? { truncation: truncated.truncation } : {}),
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
} catch (error) {
|
|
152
|
-
if (timeoutSignal.aborted && signal?.aborted !== true) {
|
|
153
|
-
throw new Error(`Web fetch timed out after ${timeout}s`);
|
|
154
|
-
}
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
184
|
+
return executeWebFetch(params, signal, onUpdate);
|
|
157
185
|
},
|
|
158
186
|
renderCall(args, theme, context) {
|
|
159
187
|
rowState.watch(context.toolCallId, context.invalidate);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@ast-grep/wasm": "0.45.0",
|
|
38
|
-
"@shanepadgett/tau-tui": "0.
|
|
38
|
+
"@shanepadgett/tau-tui": "0.34.0",
|
|
39
39
|
"@vscode/tree-sitter-wasm": "0.3.1",
|
|
40
40
|
"image-size": "2.0.2",
|
|
41
|
-
"smol-toml": "1.7.
|
|
41
|
+
"smol-toml": "1.7.1",
|
|
42
42
|
"web-tree-sitter": "0.26.11"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|