@shanepadgett/tau-agent 0.26.0 → 0.28.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/subagents.md +1 -1
- package/extensions/appshot/README.md +1 -1
- package/extensions/appshot/index.ts +5 -11
- package/extensions/context/sync.ts +1 -9
- package/extensions/context-pruning/README.md +2 -2
- package/extensions/context-pruning/index.ts +58 -57
- package/extensions/context-pruning/prune.ts +2 -2
- package/extensions/context-pruning/render.ts +51 -39
- package/extensions/context-pruning/settings.ts +4 -5
- package/extensions/explore/README.md +13 -14
- package/extensions/explore/ast/adapter.ts +97 -0
- package/extensions/explore/ast/engine.ts +470 -0
- package/extensions/explore/ast/format/ast-search.ts +97 -0
- package/extensions/explore/ast/format/composite.ts +26 -0
- package/extensions/explore/ast/format/context.ts +66 -0
- package/extensions/explore/ast/format/deps.ts +86 -0
- package/extensions/explore/ast/format/discover.ts +85 -0
- package/extensions/explore/ast/format/impact.ts +105 -0
- package/extensions/explore/ast/format/outline.ts +66 -0
- package/extensions/explore/ast/format/relationships.ts +81 -0
- package/extensions/explore/ast/format/show.ts +38 -0
- package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
- package/extensions/explore/ast/grammars/manifest.json +41 -0
- package/extensions/explore/ast/grammars/manifest.ts +99 -0
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/grammars/swift.wasm +0 -0
- package/extensions/explore/ast/graph/file-graph.ts +360 -0
- package/extensions/explore/ast/graph/relationships.ts +597 -0
- package/extensions/explore/ast/identity.ts +119 -0
- package/extensions/explore/ast/ir.ts +130 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
- package/extensions/explore/ast/languages/csharp.ts +493 -0
- package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
- package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
- package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
- package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
- package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
- package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
- package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
- package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
- package/extensions/explore/ast/languages/go.ts +545 -0
- package/extensions/explore/ast/languages/java.ts +412 -0
- package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
- package/extensions/explore/ast/languages/kotlin.ts +611 -0
- package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
- package/extensions/explore/ast/languages/odin.ts +407 -0
- package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
- package/extensions/explore/ast/languages/rust.ts +506 -0
- package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
- package/extensions/explore/ast/languages/swift.ts +466 -0
- package/extensions/explore/ast/languages/tree.ts +220 -0
- package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
- package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
- package/extensions/explore/ast/languages/typescript.ts +716 -0
- package/extensions/explore/ast/markdown.ts +145 -0
- package/extensions/explore/ast/package-surface.ts +42 -0
- package/extensions/explore/ast/queries/ast-search.ts +306 -0
- package/extensions/explore/ast/queries/composite-target.ts +77 -0
- package/extensions/explore/ast/queries/context.ts +406 -0
- package/extensions/explore/ast/queries/discover.ts +427 -0
- package/extensions/explore/ast/queries/impact.ts +250 -0
- package/extensions/explore/ast/queries/outline.ts +203 -0
- package/extensions/explore/ast/queries/show.ts +262 -0
- package/extensions/explore/ast/query.ts +48 -0
- package/extensions/explore/ast/read/hook.ts +125 -0
- package/extensions/explore/ast/read/policy.ts +55 -0
- package/extensions/explore/ast/registry.ts +120 -0
- package/extensions/explore/ast/scan.ts +76 -0
- package/extensions/explore/ast/slice.ts +31 -0
- package/extensions/explore/ast/tools/ast-search.ts +138 -0
- package/extensions/explore/ast/tools/context.ts +126 -0
- package/extensions/explore/ast/tools/deps.ts +72 -0
- package/extensions/explore/ast/tools/discover.ts +208 -0
- package/extensions/explore/ast/tools/impact.ts +135 -0
- package/extensions/explore/ast/tools/outline.ts +200 -0
- package/extensions/explore/ast/tools/relationships.ts +160 -0
- package/extensions/explore/ast/tools/render.ts +121 -0
- package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
- package/extensions/explore/ast/tools/show.ts +116 -0
- package/extensions/explore/guidance.ts +52 -0
- package/extensions/explore/index.ts +91 -45
- package/extensions/explore/read/autoread.ts +118 -0
- package/extensions/explore/settings.ts +64 -0
- package/extensions/explore/traverse.ts +260 -86
- package/extensions/handoff/index.ts +1 -1
- package/extensions/patch/executor.ts +95 -1
- package/extensions/patch/index.ts +1 -0
- package/extensions/publish/index.ts +18 -85
- package/extensions/publish/publish-ui.ts +0 -4
- package/extensions/soul/README.md +7 -4
- package/extensions/soul/index.ts +11 -5
- package/extensions/soul/prompt.ts +30 -14
- package/extensions/soul/settings.ts +7 -2
- package/extensions/subagent/agents/context-sync.md +6 -10
- package/extensions/subagent/agents/review.md +44 -24
- package/extensions/subagent/agents/scout.md +53 -34
- package/extensions/subagent/run.ts +1 -1
- package/extensions/tau-help/help.md +2 -2
- package/package.json +6 -5
- package/schemas/tau.schema.json +51 -6
- package/{extensions/explore → shared}/autoread.ts +21 -7
- package/shared/bounded-text-result.ts +304 -0
- package/shared/events.ts +1 -0
- package/shared/full-file-knowledge.ts +49 -0
- package/shared/temporary-output-store.ts +275 -0
- package/extensions/explore/ast-tools.ts +0 -828
- package/extensions/explore/ast-worker.ts +0 -356
- package/extensions/explore/find.ts +0 -147
- package/extensions/explore/full-file-knowledge.ts +0 -234
- package/extensions/explore/grep.ts +0 -749
- package/extensions/explore/limits.ts +0 -22
- package/extensions/explore/ls.ts +0 -113
- package/extensions/explore/path-display.ts +0 -41
- package/extensions/explore/path-tree.ts +0 -150
- package/extensions/explore/read-cache.ts +0 -273
- package/extensions/explore/read-snapshots.ts +0 -57
- package/extensions/explore/read-stats-panel.ts +0 -154
- package/extensions/explore/read-stats.ts +0 -120
- package/extensions/explore/read.ts +0 -335
- package/extensions/explore/result.ts +0 -29
- package/native-bin/darwin-arm64/tau-ast +0 -0
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scout
|
|
3
|
-
description:
|
|
3
|
+
description: Tiered, AST-first local discovery of files, declarations, data flow, constraints, and unknowns without changes
|
|
4
4
|
tools:
|
|
5
5
|
- read
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
6
|
+
- bash
|
|
7
|
+
- outline
|
|
8
|
+
- show
|
|
9
|
+
- discover
|
|
10
|
+
- ast_search
|
|
11
|
+
- deps
|
|
12
|
+
- reverse_deps
|
|
13
|
+
- callers
|
|
14
|
+
- callees
|
|
15
|
+
- references
|
|
16
|
+
- implementations
|
|
17
|
+
- impact
|
|
18
|
+
- context
|
|
9
19
|
names:
|
|
10
20
|
- Pathfinder
|
|
11
21
|
- Trailblazer
|
|
@@ -16,36 +26,50 @@ model: openai-codex/gpt-5.6-luna
|
|
|
16
26
|
thinking: high
|
|
17
27
|
---
|
|
18
28
|
|
|
19
|
-
Stay inside
|
|
29
|
+
Stay inside task. Answer only what was asked. No mutations, side quests, background sweeps, or unasked advice.
|
|
20
30
|
|
|
21
|
-
Delegating prompt
|
|
31
|
+
Delegating prompt controls output. Otherwise use smallest matching shape below.
|
|
22
32
|
|
|
23
|
-
##
|
|
33
|
+
## Evidence ladder
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
2. Start with named paths and symbols. Use `grep` or `find` for specific evidence. Do not map repository.
|
|
27
|
-
3. Every read answers a pending question. Read smallest useful range. Follow imports, callers, or related files only when evidence requires it.
|
|
28
|
-
4. Use `lineNumbers: true` for text supporting findings. Cite exact `path:start-end` ranges from tool output. Never estimate line numbers.
|
|
29
|
-
5. Stop when every requested field has evidence. Put unresolved facts under `Unknowns`. Do not explore unrelated code for completeness.
|
|
35
|
+
Use cheapest source that proves each claim. Skip steps when task supplies exact path or declaration. Escalate only when current evidence cannot answer.
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
1. **Supplied context** — Treat current line-numbered task files as authoritative this turn.
|
|
38
|
+
2. **Paths and literals** — Use read-only `bash` (`ls`, `find`, `rg`/`grep`) for narrow path discovery, exact text, registrations, and unsupported formats. Use ranged `read` for formatting or source without structural support.
|
|
39
|
+
3. **Structure** — Default to `outline` for known files/packages and unfamiliar supported subtrees. Use `discover` when reuse intent is known but path or exact name is not. Use `ast_search` for source shapes.
|
|
40
|
+
4. **Exact declarations** — Use `show` with path + name (+ line when needed). Prefer `signature`; add docs, body, imports, or context lines only when question requires them.
|
|
41
|
+
5. **Focused relationships** — After resolving a declaration, use `callers`, `callees`, `references`, or `implementations` for one direct relationship question. Use `deps` and `reverse_deps` for file imports, not declaration calls.
|
|
42
|
+
6. **Composition** — Use `impact` for full one-hop declaration plus transitive file blast radius. Use `context` for one budgeted declaration pack when nearby bodies and relationships answer faster than separate calls.
|
|
43
|
+
|
|
44
|
+
Structural results prove bounded syntax, not runtime dispatch. Preserve exact, inferred, and ambiguous labels. Do not turn ambiguous sites into claimed impact.
|
|
45
|
+
|
|
46
|
+
## Search procedure
|
|
47
|
+
|
|
48
|
+
1. Extract target, question, scope, and requested output shape.
|
|
49
|
+
2. List required claims and select cheapest evidence for each.
|
|
50
|
+
3. Start from supplied paths and names. Search outward only for required relationships.
|
|
51
|
+
4. For reuse, run `discover`, then inspect selected candidates with `show`.
|
|
52
|
+
5. For unknown source shape, run `ast_search`, then inspect only selected enclosing declarations.
|
|
53
|
+
6. For behavior or data flow, orient target, follow focused relationships, then retrieve only declarations needed to explain flow.
|
|
54
|
+
7. For impact, use `impact`; use focused relationship tools only when one section needs closer evidence.
|
|
55
|
+
8. Stop when requested claims are supported. Put material gaps under `Unknowns`.
|
|
56
|
+
|
|
57
|
+
Absolute paths may point to read-only reference repositories outside cwd.
|
|
32
58
|
|
|
33
59
|
## Result shapes
|
|
34
60
|
|
|
35
|
-
Use
|
|
61
|
+
Use relevant sections only. Omit empty sections.
|
|
36
62
|
|
|
37
63
|
### Locate
|
|
38
64
|
|
|
39
|
-
`path:start-end` —
|
|
65
|
+
`path:start-end` — declaration — match reason
|
|
40
66
|
|
|
41
67
|
### Explain behavior
|
|
42
68
|
|
|
43
|
-
- `Entry:` `path:start-end` —
|
|
44
|
-
- `Flow:` ordered steps; one cited fact
|
|
69
|
+
- `Entry:` `path:start-end` — declaration
|
|
70
|
+
- `Flow:` ordered steps; one cited fact each
|
|
45
71
|
- `Result:` observed outcome
|
|
46
72
|
|
|
47
|
-
Only branches relevant to requested behavior.
|
|
48
|
-
|
|
49
73
|
### Trace data
|
|
50
74
|
|
|
51
75
|
- `Source:` cited origin
|
|
@@ -54,12 +78,11 @@ Only branches relevant to requested behavior.
|
|
|
54
78
|
|
|
55
79
|
### Find references or impact
|
|
56
80
|
|
|
57
|
-
- `Direct references:` cited relationships
|
|
81
|
+
- `Direct references:` cited relationships with certainty
|
|
82
|
+
- `Editable scopes:` declarations requiring inspection or change
|
|
58
83
|
- `Behavior affected:` evidence-backed consequences
|
|
59
84
|
- `Unknowns:` remaining uncertainty
|
|
60
85
|
|
|
61
|
-
No speculative blast radius.
|
|
62
|
-
|
|
63
86
|
### Verify a claim
|
|
64
87
|
|
|
65
88
|
- `Verdict:` `yes`, `no`, `partially`, or `unknown`
|
|
@@ -69,23 +92,19 @@ No speculative blast radius.
|
|
|
69
92
|
### Compare
|
|
70
93
|
|
|
71
94
|
- `Shared:` cited similarities
|
|
72
|
-
- `Differences:` cited
|
|
95
|
+
- `Differences:` cited by aspect
|
|
73
96
|
- `Relevant consequence:` requested consequences only
|
|
74
97
|
|
|
75
98
|
### Inventory
|
|
76
99
|
|
|
77
|
-
`path:start-end` —
|
|
78
|
-
|
|
79
|
-
When completeness matters, state searched scope. If completeness cannot be guaranteed, say why.
|
|
80
|
-
|
|
81
|
-
### Constraints and unknowns
|
|
100
|
+
`path:start-end` — declaration — role
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
- `Unknowns:` missing fact — evidence needed
|
|
102
|
+
When completeness matters, state searched scope. If uncertain, say why.
|
|
85
103
|
|
|
86
104
|
## Reporting rules
|
|
87
105
|
|
|
88
|
-
- Every material code claim needs exact path, line range, and
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
106
|
+
- Every material code claim needs exact path, line range, and declaration when one exists.
|
|
107
|
+
- Cite ranges returned by tools. Never estimate line numbers.
|
|
108
|
+
- Separate fact from inference. Label inference.
|
|
109
|
+
- Quote smallest useful fragment.
|
|
110
|
+
- No preamble, search log, generic repository summary, repeated evidence, or unasked next steps.
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type ExtensionAPI,
|
|
13
13
|
type ExtensionContext,
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { createCompleteFileMeta } from "
|
|
15
|
+
import { createCompleteFileMeta } from "../../shared/full-file-knowledge.ts";
|
|
16
16
|
import type { AgentDefinition } from "./agents.ts";
|
|
17
17
|
import { emptySubagentResumeState, type RetainedTurnOutcome, type SubagentResumeState } from "./resume.ts";
|
|
18
18
|
import {
|
|
@@ -40,7 +40,7 @@ Gives the agent `context_prune` for creating a hard context checkpoint after bro
|
|
|
40
40
|
|
|
41
41
|
## explore
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Structural source tools on in-process tree-sitter (WASM), available on every Node-supported platform. Registers `outline`, `show`, `discover`, `ast_search`, `deps`, `reverse_deps`, `callers`, `callees`, `references`, `implementations`, `impact`, and `context`; symbol targets use path + name (+ line when needed). Pi keeps `ls` / `find` / `grep` / `read`. Large full `read`/autoread of registered source (including Markdown) returns outline by default (`explore.read.*`); ranged `read` or `show` for bodies. Disable with `explore.read.enabled: false`.
|
|
44
44
|
|
|
45
45
|
## footer
|
|
46
46
|
|
|
@@ -92,7 +92,7 @@ Runs configured commands while keeping their output out of agent context when th
|
|
|
92
92
|
|
|
93
93
|
## soul
|
|
94
94
|
|
|
95
|
-
Adds
|
|
95
|
+
Adds two independently toggleable sections to Pi’s native assistant prompt: `ponytail` (a lazy-senior-dev build ethos) and `caveman` (a terse communication style). Both on by default.
|
|
96
96
|
|
|
97
97
|
## stash
|
|
98
98
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -31,14 +31,15 @@
|
|
|
31
31
|
"themes/**/*",
|
|
32
32
|
"docs/**/*",
|
|
33
33
|
"schemas/**/*",
|
|
34
|
-
"native-bin/**/*",
|
|
35
34
|
"README.md"
|
|
36
35
|
],
|
|
37
36
|
"dependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
37
|
+
"@ast-grep/wasm": "0.45.0",
|
|
38
|
+
"@shanepadgett/tau-tui": "0.28.0",
|
|
39
|
+
"@vscode/tree-sitter-wasm": "0.3.1",
|
|
40
40
|
"image-size": "2.0.2",
|
|
41
|
-
"smol-toml": "1.7.0"
|
|
41
|
+
"smol-toml": "1.7.0",
|
|
42
|
+
"web-tree-sitter": "0.26.11"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"@earendil-works/pi-ai": "*",
|
package/schemas/tau.schema.json
CHANGED
|
@@ -61,12 +61,11 @@
|
|
|
61
61
|
"default": true,
|
|
62
62
|
"description": "Enable context pruning."
|
|
63
63
|
},
|
|
64
|
-
"
|
|
64
|
+
"nudgeEveryTokens": {
|
|
65
65
|
"type": "integer",
|
|
66
|
-
"default":
|
|
66
|
+
"default": 30000,
|
|
67
67
|
"minimum": 1,
|
|
68
|
-
"
|
|
69
|
-
"description": "Context growth interval between automatic pruning hints."
|
|
68
|
+
"description": "Active-context token interval between automatic pruning hints."
|
|
70
69
|
},
|
|
71
70
|
"nudgeInstructions": {
|
|
72
71
|
"type": "array",
|
|
@@ -87,6 +86,47 @@
|
|
|
87
86
|
},
|
|
88
87
|
"additionalProperties": false
|
|
89
88
|
},
|
|
89
|
+
"explore": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {
|
|
92
|
+
"context": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"properties": {
|
|
95
|
+
"defaultBudgetTokens": {
|
|
96
|
+
"type": "integer",
|
|
97
|
+
"default": 8000,
|
|
98
|
+
"minimum": 1,
|
|
99
|
+
"description": "Default token budget for context when the caller omits budget."
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"additionalProperties": false
|
|
103
|
+
},
|
|
104
|
+
"read": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"enabled": {
|
|
108
|
+
"type": "boolean",
|
|
109
|
+
"default": true,
|
|
110
|
+
"description": "Master switch for Explore structural read overlay and large-source autoread outline path. When false, Pi read and autoread keep ordinary full bodies."
|
|
111
|
+
},
|
|
112
|
+
"structureThresholdLines": {
|
|
113
|
+
"type": "integer",
|
|
114
|
+
"default": 200,
|
|
115
|
+
"minimum": 1,
|
|
116
|
+
"description": "Registered source at or under this line count may be full-read. Above it, full read model-visible result is outline only."
|
|
117
|
+
},
|
|
118
|
+
"maxRangeLines": {
|
|
119
|
+
"type": "integer",
|
|
120
|
+
"default": 200,
|
|
121
|
+
"minimum": 1,
|
|
122
|
+
"description": "Maximum lines returned by one ranged read on registered source."
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"additionalProperties": false
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"additionalProperties": false
|
|
129
|
+
},
|
|
90
130
|
"footer": {
|
|
91
131
|
"type": "object",
|
|
92
132
|
"properties": {
|
|
@@ -234,10 +274,15 @@
|
|
|
234
274
|
"soul": {
|
|
235
275
|
"type": "object",
|
|
236
276
|
"properties": {
|
|
237
|
-
"
|
|
277
|
+
"ponytail": {
|
|
278
|
+
"type": "boolean",
|
|
279
|
+
"default": true,
|
|
280
|
+
"description": "Add the lazy-senior-dev build ethos to Tau's system prompt."
|
|
281
|
+
},
|
|
282
|
+
"caveman": {
|
|
238
283
|
"type": "boolean",
|
|
239
284
|
"default": true,
|
|
240
|
-
"description": "
|
|
285
|
+
"description": "Add the terse communication style to Tau's system prompt."
|
|
241
286
|
}
|
|
242
287
|
},
|
|
243
288
|
"additionalProperties": false
|
|
@@ -2,11 +2,10 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { onTauEvent, type TauAgentEvents } from "../../shared/events.js";
|
|
6
|
-
import type { ToolRowStateStore } from "../../shared/tool-row-state.js";
|
|
7
5
|
import { Marker, type MarkerState } from "@shanepadgett/tau-tui";
|
|
8
|
-
import {
|
|
9
|
-
import type
|
|
6
|
+
import { onTauEvent, type TauAgentEvents } from "./events.js";
|
|
7
|
+
import { createCompleteFileMeta, type ReadCacheMetaV1 } from "./full-file-knowledge.ts";
|
|
8
|
+
import type { ToolRowStateStore } from "./tool-row-state.js";
|
|
10
9
|
|
|
11
10
|
const AUTOREAD_MESSAGE_TYPE = "tau.autoread";
|
|
12
11
|
|
|
@@ -30,6 +29,17 @@ export interface PreparedAutoreadMessage {
|
|
|
30
29
|
details: AutoreadDetails & { status: "read"; readCache: ReadCacheMetaV1 };
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
export type PrepareAutoreadMessage = (options: {
|
|
33
|
+
rowId: string;
|
|
34
|
+
path: string;
|
|
35
|
+
cwd: string;
|
|
36
|
+
source: string;
|
|
37
|
+
batchId: string;
|
|
38
|
+
signal: AbortSignal | undefined;
|
|
39
|
+
isLifecycleCurrent: () => boolean;
|
|
40
|
+
maximumBytes?: number;
|
|
41
|
+
}) => Promise<PreparedAutoreadMessage>;
|
|
42
|
+
|
|
33
43
|
export async function prepareAutoreadMessage(options: {
|
|
34
44
|
rowId: string;
|
|
35
45
|
path: string;
|
|
@@ -76,7 +86,11 @@ export async function prepareAutoreadMessage(options: {
|
|
|
76
86
|
};
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
export function registerAutoread(
|
|
89
|
+
export function registerAutoread(
|
|
90
|
+
pi: ExtensionAPI,
|
|
91
|
+
rowState: ToolRowStateStore,
|
|
92
|
+
prepare: PrepareAutoreadMessage = prepareAutoreadMessage,
|
|
93
|
+
): void {
|
|
80
94
|
let lifecycleGeneration = 0;
|
|
81
95
|
pi.on("session_start", () => {
|
|
82
96
|
lifecycleGeneration += 1;
|
|
@@ -90,7 +104,7 @@ export function registerAutoread(pi: ExtensionAPI, rowState: ToolRowStateStore):
|
|
|
90
104
|
pi.on("session_shutdown", () => {
|
|
91
105
|
lifecycleGeneration += 1;
|
|
92
106
|
});
|
|
93
|
-
onTauEvent(pi, "
|
|
107
|
+
onTauEvent(pi, "shared.autoread", "tau:autoread.requested", async (data) => {
|
|
94
108
|
const event = readAutoreadRequestedEvent(data);
|
|
95
109
|
if (!event) return;
|
|
96
110
|
const generation = lifecycleGeneration;
|
|
@@ -105,7 +119,7 @@ export function registerAutoread(pi: ExtensionAPI, rowState: ToolRowStateStore):
|
|
|
105
119
|
batchId: event.batchId,
|
|
106
120
|
} satisfies Omit<AutoreadDetails, "status" | "error">;
|
|
107
121
|
try {
|
|
108
|
-
const message = await
|
|
122
|
+
const message = await prepare({
|
|
109
123
|
...details,
|
|
110
124
|
signal: undefined,
|
|
111
125
|
isLifecycleCurrent: () => generation === lifecycleGeneration,
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAX_BYTES,
|
|
3
|
+
DEFAULT_MAX_LINES,
|
|
4
|
+
formatSize,
|
|
5
|
+
truncateHead,
|
|
6
|
+
truncateTail,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import {
|
|
9
|
+
TemporaryOutputError,
|
|
10
|
+
type TemporaryOutputStore,
|
|
11
|
+
type TemporaryOutputWriter,
|
|
12
|
+
} from "./temporary-output-store.ts";
|
|
13
|
+
|
|
14
|
+
export type BoundedTextStrategy = "head" | "tail" | "completeBlocks";
|
|
15
|
+
|
|
16
|
+
export interface BoundedTextOverflowDetails {
|
|
17
|
+
strategy: BoundedTextStrategy;
|
|
18
|
+
truncated: boolean;
|
|
19
|
+
shownLines: number;
|
|
20
|
+
totalLines: number;
|
|
21
|
+
shownBytes: number;
|
|
22
|
+
totalBytes: number;
|
|
23
|
+
fullOutputComplete: boolean;
|
|
24
|
+
temporaryPath: string | undefined;
|
|
25
|
+
failure: "quota" | "write" | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface BoundedTextResult {
|
|
29
|
+
content: string;
|
|
30
|
+
overflow: BoundedTextOverflowDetails;
|
|
31
|
+
visibleUnitIds: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function truncateBoundedHead(content: string): ReturnType<typeof truncateHead> {
|
|
35
|
+
return truncateHead(content, { maxBytes: DEFAULT_MAX_BYTES, maxLines: DEFAULT_MAX_LINES });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface VisibleBlock {
|
|
39
|
+
id: string | undefined;
|
|
40
|
+
label: string;
|
|
41
|
+
text: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class BoundedTextResultBuilder {
|
|
45
|
+
private readonly store: TemporaryOutputStore;
|
|
46
|
+
private readonly strategy: BoundedTextStrategy;
|
|
47
|
+
private snapshot = "";
|
|
48
|
+
private blocks: VisibleBlock[] = [];
|
|
49
|
+
private requiredBlocks: VisibleBlock[] = [];
|
|
50
|
+
private totalBytes = 0;
|
|
51
|
+
private newlineCount = 0;
|
|
52
|
+
private hasContent = false;
|
|
53
|
+
private endsWithNewline = false;
|
|
54
|
+
private overflowed = false;
|
|
55
|
+
private modelClosed = false;
|
|
56
|
+
private oversizedBlockLabel: string | undefined;
|
|
57
|
+
private output: TemporaryOutputWriter | undefined;
|
|
58
|
+
private persistenceFailure: "quota" | "write" | undefined;
|
|
59
|
+
private finished = false;
|
|
60
|
+
|
|
61
|
+
constructor(store: TemporaryOutputStore, strategy: BoundedTextStrategy) {
|
|
62
|
+
this.store = store;
|
|
63
|
+
this.strategy = strategy;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async append(text: string): Promise<void> {
|
|
67
|
+
if (this.strategy === "completeBlocks") throw new Error("Complete-block results require appendBlock");
|
|
68
|
+
this.requireOpen();
|
|
69
|
+
this.count(text);
|
|
70
|
+
if (!this.overflowed) {
|
|
71
|
+
const candidate = `${this.snapshot}${text}`;
|
|
72
|
+
const truncation = this.strategy === "head" ? truncateHead(candidate) : truncateTail(candidate);
|
|
73
|
+
this.snapshot = truncation.content;
|
|
74
|
+
if (!truncation.truncated) return;
|
|
75
|
+
this.overflowed = true;
|
|
76
|
+
await this.persist(candidate);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (this.strategy === "tail") this.snapshot = truncateTail(`${this.snapshot}${text}`).content;
|
|
80
|
+
await this.persist(text);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async appendBlock(id: string | undefined, label: string, text: string): Promise<void> {
|
|
84
|
+
if (this.strategy !== "completeBlocks") throw new Error("Text results require append");
|
|
85
|
+
this.requireOpen();
|
|
86
|
+
const prefix = this.hasContent ? "\n\n" : "";
|
|
87
|
+
const chunk = `${prefix}${text}`;
|
|
88
|
+
this.count(chunk);
|
|
89
|
+
|
|
90
|
+
if (!this.modelClosed) {
|
|
91
|
+
const candidate = `${this.snapshot}${chunk}`;
|
|
92
|
+
if (!truncateHead(candidate).truncated) {
|
|
93
|
+
this.snapshot = candidate;
|
|
94
|
+
this.blocks.push({ id, label, text });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.modelClosed = true;
|
|
98
|
+
this.overflowed = true;
|
|
99
|
+
if (this.blocks.length === 0) this.oversizedBlockLabel = label.replaceAll(/[\r\n]/g, " ");
|
|
100
|
+
await this.persist(candidate);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await this.persist(chunk);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async appendRequiredBlock(label: string, text: string): Promise<void> {
|
|
107
|
+
if (this.strategy !== "completeBlocks") throw new Error("Required blocks need complete-block retention");
|
|
108
|
+
this.requireOpen();
|
|
109
|
+
const block = { id: undefined, label, text };
|
|
110
|
+
this.requiredBlocks.push(block);
|
|
111
|
+
const prefix = this.hasContent ? "\n\n" : "";
|
|
112
|
+
const chunk = `${prefix}${text}`;
|
|
113
|
+
this.count(chunk);
|
|
114
|
+
if (!this.overflowed) {
|
|
115
|
+
const candidate = `${this.snapshot}${chunk}`;
|
|
116
|
+
if (!truncateHead(candidate).truncated) {
|
|
117
|
+
this.snapshot = candidate;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.overflowed = true;
|
|
121
|
+
this.modelClosed = true;
|
|
122
|
+
await this.persist(candidate);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await this.persist(chunk);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async finish(): Promise<BoundedTextResult> {
|
|
129
|
+
this.requireOpen();
|
|
130
|
+
this.finished = true;
|
|
131
|
+
let temporaryPath: string | undefined;
|
|
132
|
+
if (this.output) {
|
|
133
|
+
try {
|
|
134
|
+
temporaryPath = await this.output.finish();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.persistenceFailure = error instanceof TemporaryOutputError ? error.code : "write";
|
|
137
|
+
}
|
|
138
|
+
this.output = undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const totalLines = lineCount(this.hasContent, this.newlineCount, this.endsWithNewline);
|
|
142
|
+
if (!this.overflowed) {
|
|
143
|
+
return {
|
|
144
|
+
content: this.snapshot,
|
|
145
|
+
overflow: {
|
|
146
|
+
strategy: this.strategy,
|
|
147
|
+
truncated: false,
|
|
148
|
+
shownLines: totalLines,
|
|
149
|
+
totalLines,
|
|
150
|
+
shownBytes: this.totalBytes,
|
|
151
|
+
totalBytes: this.totalBytes,
|
|
152
|
+
fullOutputComplete: true,
|
|
153
|
+
temporaryPath: undefined,
|
|
154
|
+
failure: undefined,
|
|
155
|
+
},
|
|
156
|
+
visibleUnitIds: this.blocks.flatMap((block) => (block.id === undefined ? [] : [block.id])),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.strategy === "completeBlocks") {
|
|
161
|
+
return this.finishBlocks(totalLines, temporaryPath);
|
|
162
|
+
}
|
|
163
|
+
return this.finishText(totalLines, temporaryPath);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async abort(): Promise<void> {
|
|
167
|
+
this.finished = true;
|
|
168
|
+
const output = this.output;
|
|
169
|
+
this.output = undefined;
|
|
170
|
+
if (output) await output.abort();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private finishBlocks(totalLines: number, temporaryPath: string | undefined): BoundedTextResult {
|
|
174
|
+
const blocks = [...this.blocks];
|
|
175
|
+
const required = this.requiredBlocks.map((block) => block.text).join("\n\n");
|
|
176
|
+
let retained = [...blocks.map((block) => block.text), required].filter(Boolean).join("\n\n");
|
|
177
|
+
let shownBytes = Buffer.byteLength(retained);
|
|
178
|
+
let shownLines = textLineCount(retained);
|
|
179
|
+
let notice = this.notice(shownLines, shownBytes, totalLines, temporaryPath);
|
|
180
|
+
const oversized = this.oversizedBlockLabel
|
|
181
|
+
? `[File block omitted because it exceeds the model-output budget: ${this.oversizedBlockLabel}. Run a non-recursive outline for that file.]`
|
|
182
|
+
: "";
|
|
183
|
+
while (blocks.length > 0 && truncateHead([retained, oversized, notice].filter(Boolean).join("\n\n")).truncated) {
|
|
184
|
+
blocks.pop();
|
|
185
|
+
retained = [...blocks.map((block) => block.text), required].filter(Boolean).join("\n\n");
|
|
186
|
+
shownBytes = Buffer.byteLength(retained);
|
|
187
|
+
shownLines = textLineCount(retained);
|
|
188
|
+
notice = this.notice(shownLines, shownBytes, totalLines, temporaryPath);
|
|
189
|
+
}
|
|
190
|
+
let content = [retained, oversized, notice].filter(Boolean).join("\n\n");
|
|
191
|
+
if (truncateHead(content).truncated) content = truncateHead(content).content;
|
|
192
|
+
return {
|
|
193
|
+
content,
|
|
194
|
+
overflow: this.overflowDetails(shownLines, shownBytes, totalLines, temporaryPath),
|
|
195
|
+
visibleUnitIds: blocks.flatMap((block) => (block.id === undefined ? [] : [block.id])),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private finishText(totalLines: number, temporaryPath: string | undefined): BoundedTextResult {
|
|
200
|
+
let retained = this.snapshot;
|
|
201
|
+
let shownBytes = Buffer.byteLength(retained);
|
|
202
|
+
let shownLines = textLineCount(retained);
|
|
203
|
+
let notice = this.notice(shownLines, shownBytes, totalLines, temporaryPath);
|
|
204
|
+
for (let iteration = 0; iteration < 3; iteration += 1) {
|
|
205
|
+
const noticeLines = textLineCount(notice) + 1;
|
|
206
|
+
const noticeBytes = Buffer.byteLength(notice) + 2;
|
|
207
|
+
const options = {
|
|
208
|
+
maxLines: Math.max(0, DEFAULT_MAX_LINES - noticeLines),
|
|
209
|
+
maxBytes: Math.max(0, DEFAULT_MAX_BYTES - noticeBytes),
|
|
210
|
+
};
|
|
211
|
+
retained = (
|
|
212
|
+
this.strategy === "head" ? truncateHead(this.snapshot, options) : truncateTail(this.snapshot, options)
|
|
213
|
+
).content;
|
|
214
|
+
shownBytes = Buffer.byteLength(retained);
|
|
215
|
+
shownLines = textLineCount(retained);
|
|
216
|
+
notice = this.notice(shownLines, shownBytes, totalLines, temporaryPath);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
content: [retained, notice].filter(Boolean).join("\n\n"),
|
|
220
|
+
overflow: this.overflowDetails(shownLines, shownBytes, totalLines, temporaryPath),
|
|
221
|
+
visibleUnitIds: [],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private overflowDetails(
|
|
226
|
+
shownLines: number,
|
|
227
|
+
shownBytes: number,
|
|
228
|
+
totalLines: number,
|
|
229
|
+
temporaryPath: string | undefined,
|
|
230
|
+
): BoundedTextOverflowDetails {
|
|
231
|
+
return {
|
|
232
|
+
strategy: this.strategy,
|
|
233
|
+
truncated: true,
|
|
234
|
+
shownLines,
|
|
235
|
+
totalLines,
|
|
236
|
+
shownBytes,
|
|
237
|
+
totalBytes: this.totalBytes,
|
|
238
|
+
fullOutputComplete: temporaryPath !== undefined && this.persistenceFailure === undefined,
|
|
239
|
+
temporaryPath,
|
|
240
|
+
failure: this.persistenceFailure,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private notice(
|
|
245
|
+
shownLines: number,
|
|
246
|
+
shownBytes: number,
|
|
247
|
+
totalLines: number,
|
|
248
|
+
temporaryPath: string | undefined,
|
|
249
|
+
): string {
|
|
250
|
+
return formatBoundedTextOverflow({
|
|
251
|
+
strategy: this.strategy,
|
|
252
|
+
truncated: true,
|
|
253
|
+
shownLines,
|
|
254
|
+
totalLines,
|
|
255
|
+
shownBytes,
|
|
256
|
+
totalBytes: this.totalBytes,
|
|
257
|
+
fullOutputComplete: temporaryPath !== undefined && this.persistenceFailure === undefined,
|
|
258
|
+
temporaryPath,
|
|
259
|
+
failure: this.persistenceFailure,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private count(text: string): void {
|
|
264
|
+
if (text.length === 0) return;
|
|
265
|
+
this.totalBytes += Buffer.byteLength(text);
|
|
266
|
+
this.newlineCount += text.split("\n").length - 1;
|
|
267
|
+
this.hasContent = true;
|
|
268
|
+
this.endsWithNewline = text.endsWith("\n");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private async persist(text: string): Promise<void> {
|
|
272
|
+
if (this.persistenceFailure) return;
|
|
273
|
+
try {
|
|
274
|
+
this.output ??= await this.store.createOutput();
|
|
275
|
+
await this.output.write(text);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this.persistenceFailure = error instanceof TemporaryOutputError ? error.code : "write";
|
|
278
|
+
const output = this.output;
|
|
279
|
+
this.output = undefined;
|
|
280
|
+
if (output) await output.abort();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private requireOpen(): void {
|
|
285
|
+
if (this.finished) throw new Error("Bounded text result is already finished");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function formatBoundedTextOverflow(details: BoundedTextOverflowDetails): string {
|
|
290
|
+
const sizes = `${details.shownLines} of ${details.totalLines} lines (${formatSize(details.shownBytes)} of ${formatSize(details.totalBytes)})`;
|
|
291
|
+
if (details.fullOutputComplete && details.temporaryPath) {
|
|
292
|
+
return `[Output truncated: showing ${sizes}. Complete output: ${details.temporaryPath}\nTemporary output is valid only for the active session. Use targeted grep or ranged read; reading the whole file will hit the same output limits.]`;
|
|
293
|
+
}
|
|
294
|
+
const reason = details.failure === "quota" ? "disk quota exceeded" : "temporary-file write failed";
|
|
295
|
+
return `[Output truncated: showing ${sizes}. Complete temporary output unavailable: ${reason}.]`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function textLineCount(text: string): number {
|
|
299
|
+
return lineCount(text.length > 0, text.split("\n").length - 1, text.endsWith("\n"));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function lineCount(hasContent: boolean, newlineCount: number, endsWithNewline: boolean): number {
|
|
303
|
+
return hasContent ? newlineCount + (endsWithNewline ? 0 : 1) : 0;
|
|
304
|
+
}
|
package/shared/events.ts
CHANGED