@shanepadgett/tau-agent 0.28.1 → 0.30.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 +14 -9
- package/docs/subagents.md +7 -5
- package/extensions/cache-diagnostics/index.ts +4 -5
- package/extensions/context/README.md +8 -5
- package/extensions/context/definitions.ts +28 -17
- package/extensions/context/evidence.ts +4 -3
- package/extensions/context/index.ts +57 -35
- package/extensions/context/panel.ts +10 -9
- package/extensions/context/projection.ts +141 -0
- package/extensions/context/state.ts +30 -0
- package/extensions/explore/README.md +1 -1
- package/extensions/explore/ast/read/hook.ts +5 -35
- package/extensions/explore/ast/read/policy.ts +0 -32
- package/extensions/explore/index.ts +2 -0
- package/extensions/explore/outline-injection.ts +151 -0
- package/extensions/explore/settings.ts +0 -8
- package/extensions/ideas/browser.ts +27 -16
- package/extensions/review/README.md +11 -0
- package/extensions/review/index.ts +135 -0
- package/extensions/review/model.ts +144 -0
- package/extensions/review/panel.ts +128 -0
- package/extensions/review/session.ts +106 -0
- package/extensions/runtime-context/README.md +1 -1
- package/extensions/runtime-context/index.ts +3 -66
- package/extensions/script-runner/README.md +7 -0
- package/extensions/script-runner/index.ts +275 -0
- package/extensions/stash/browser.ts +30 -18
- package/extensions/subagent/README.md +17 -4
- package/extensions/subagent/agents/context-sync.md +2 -2
- package/extensions/subagent/agents/scout.md +49 -52
- package/extensions/subagent/agents.ts +0 -1
- package/extensions/subagent/cmux-dashboard.ts +7 -3
- package/extensions/subagent/index.ts +105 -4
- package/extensions/subagent/panel.ts +124 -0
- package/extensions/subagent/render.ts +2 -1
- package/extensions/subagent/run.ts +9 -9
- package/extensions/subagent/runtime.ts +5 -5
- package/extensions/subagent/session-resource.ts +19 -127
- package/extensions/subagent/settings.ts +18 -0
- package/extensions/tau-help/help.md +12 -8
- package/extensions/working-memory/README.md +9 -0
- package/extensions/working-memory/checkpoint.ts +175 -0
- package/extensions/working-memory/index.ts +338 -0
- package/extensions/working-memory/memory.ts +266 -0
- package/extensions/working-memory/render.ts +178 -0
- package/extensions/working-memory/settings.ts +38 -0
- package/extensions/working-memory/state.ts +152 -0
- package/package.json +2 -2
- package/schemas/tau.schema.json +35 -55
- package/shared/context-messages.ts +19 -0
- package/shared/events.ts +5 -0
- package/shared/full-file-knowledge.ts +0 -1
- package/shared/injected-context.ts +2 -2
- package/shared/isolated-session.ts +151 -0
- package/shared/outline-injection.ts +56 -0
- package/extensions/context-pruning/README.md +0 -39
- package/extensions/context-pruning/index.ts +0 -382
- package/extensions/context-pruning/projection.ts +0 -60
- package/extensions/context-pruning/prune.ts +0 -199
- package/extensions/context-pruning/render.ts +0 -251
- package/extensions/context-pruning/settings.ts +0 -39
- package/extensions/subagent/agents/review.md +0 -68
- package/extensions/turn-budget/README.md +0 -14
- package/extensions/turn-budget/index.ts +0 -116
- package/extensions/turn-budget/settings.ts +0 -35
- package/shared/context-pruning-state.ts +0 -152
|
@@ -8,17 +8,30 @@ Each fresh child also gets a display name from its agent definition. The name st
|
|
|
8
8
|
|
|
9
9
|
Tau includes these built-in agents:
|
|
10
10
|
|
|
11
|
-
- `
|
|
12
|
-
- `scout` finds local files, symbols, data flow, constraints, and unknowns without changing anything.
|
|
11
|
+
- `scout` does substantial multi-hop local code lookup that would chew parent context; paths, declarations, imports, references, call edges; facts only. Skip small digs.
|
|
13
12
|
- `web-research` researches web and code sources with `websearch`, `codesearch`, and `webfetch`.
|
|
14
13
|
- `context-sync` maps meaningful uncommitted work into `.pi/contexts`. Agent-driven use is `extensions.context.sync.automation` (requires `sync.enabled`). `/context-sync` is the manual/nudge path when sync is enabled. Validation can auto-run it when validation and sync are enabled.
|
|
15
14
|
|
|
16
15
|
Ask Tau to delegate a task, or let it call `subagent` with an agent name and task. Children use the parent's current working directory and inherit its model and thinking level unless their definition overrides either value. They do not receive the parent conversation. Tau loads only the extensions that own a child's declared tools, so unrelated extension hooks do not run in child sessions. When a child must inspect another repository, put its exact absolute path in the delegated task.
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
Run `/agents` to enable or disable agents for the current session. Press Space to stage each toggle, then Enter to apply the changes. Session choices follow the current session branch and do not change Tau settings. Agents disabled in Tau settings appear as `disabled by Tau settings` and cannot be enabled from this command. Disable agents persistently with `extensions.subagent.disabled`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"extensions": {
|
|
22
|
+
"subagent": {
|
|
23
|
+
"disabled": ["web-research"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Disabled agents are hidden from the parent prompt and cannot start or continue a child thread.
|
|
30
|
+
|
|
31
|
+
When relevant files are already known, pass them with the call so Tau can autoread them into that child turn:
|
|
19
32
|
|
|
20
33
|
```text
|
|
21
|
-
subagent({ agent: "
|
|
34
|
+
subagent({ agent: "scout", task: "Trace the runtime change", files: ["src/runtime.ts", "test/runtime.test.ts"] })
|
|
22
35
|
```
|
|
23
36
|
|
|
24
37
|
Paths may be relative to the parent's current working directory or absolute. Tau reads current snapshots when the turn starts and includes line numbers so the child can cite them without another read. Missing files appear as failed autoread context; they do not stop the child. Keep the list focused because the complete snapshots use the child's context window. Files can also be supplied on a retained-thread follow-up.
|
|
@@ -23,7 +23,7 @@ thinking: high
|
|
|
23
23
|
|
|
24
24
|
You maintain the living repository context map under `.pi/contexts`.
|
|
25
25
|
|
|
26
|
-
Tabs/folders are domains. TOML files are concepts. TOML sections are selectable work-scope entries.
|
|
26
|
+
Tabs/folders are domains. TOML files are concepts. TOML sections are selectable work-scope entries. Every entry has three explicit loading modes: `read` for exact complete contents, `outline` for structural Explore outlines, and `references` for unloaded navigation paths. Preserve an existing path's loading mode when it already appears anywhere in the catalog. New paths default to `references`. Promote recurring source entry points to `outline`. Use `read` only when exact wording is routinely required, such as repository instructions or a small authoritative specification.
|
|
27
27
|
|
|
28
28
|
## Tools
|
|
29
29
|
|
|
@@ -65,7 +65,7 @@ Before placing any path, answer out loud in order:
|
|
|
65
65
|
2. **Concept** — Inside that domain, which subsystem TOML? Reuse, new, split, or merge?
|
|
66
66
|
3. **Entry** — Which work scope? Update, new, split, delete, or move between concepts/domains?
|
|
67
67
|
4. **Bloat** — Did this touch make an entry/concept a junk drawer? Split now if yes.
|
|
68
|
-
5. **Membership** — Assign
|
|
68
|
+
5. **Membership** — Assign read/outline/references only under the winners. Every entry must contain all three arrays, even when an array is empty. Every eligible changed non-deleted file must belong somewhere. Remove every stale catalog path.
|
|
69
69
|
|
|
70
70
|
Path stuffing into the nearest feature bucket without climbing the ladder is failure.
|
|
71
71
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scout
|
|
3
|
-
description:
|
|
3
|
+
description: "Substantial multi-hop local code lookup that would chew parent context; paths, declarations, imports, references, call edges; facts only. Skip small digs"
|
|
4
4
|
tools:
|
|
5
5
|
- read
|
|
6
6
|
- bash
|
|
@@ -14,8 +14,7 @@ tools:
|
|
|
14
14
|
- callees
|
|
15
15
|
- references
|
|
16
16
|
- implementations
|
|
17
|
-
-
|
|
18
|
-
- context
|
|
17
|
+
- working_memory
|
|
19
18
|
names:
|
|
20
19
|
- Pathfinder
|
|
21
20
|
- Trailblazer
|
|
@@ -26,33 +25,43 @@ model: openai-codex/gpt-5.6-luna
|
|
|
26
25
|
thinking: high
|
|
27
26
|
---
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
You are a read-only repository retrieval worker. Locate requested source evidence and return exact cited facts.
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
Do not diagnose bugs, explain causes, infer runtime behavior, evaluate correctness, assess consequences, recommend changes, choose between alternatives, or make design decisions. The parent agent owns all interpretation and judgment.
|
|
31
|
+
|
|
32
|
+
If a task mixes lookup with judgment, perform only its concrete lookup portion and list the unanswered judgment under `Parent question`. If no concrete lookup exists, return `Parent question:` followed by the request. Do not attempt to answer it.
|
|
33
|
+
|
|
34
|
+
Stay inside task. No mutations, side quests, background sweeps, or unasked advice.
|
|
35
|
+
|
|
36
|
+
## Allowed work
|
|
37
|
+
|
|
38
|
+
- Find files, declarations, literals, configuration values, registrations, and tests.
|
|
39
|
+
- List imports, references, callers, callees, implementations, and other direct syntactic relationships.
|
|
40
|
+
- Retrieve exact signatures or declaration bodies requested by parent.
|
|
41
|
+
- Confirm whether an exact source pattern exists within a stated scope.
|
|
42
|
+
- Report ambiguity or missing evidence without resolving it through inference.
|
|
32
43
|
|
|
33
44
|
## Evidence ladder
|
|
34
45
|
|
|
35
|
-
Use cheapest source that proves each
|
|
46
|
+
Use cheapest source that proves each returned fact. Skip steps when task supplies exact path or declaration. Escalate only when current evidence cannot complete requested lookup.
|
|
36
47
|
|
|
37
48
|
1. **Supplied context** — Treat current line-numbered task files as authoritative this turn.
|
|
38
49
|
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
|
|
40
|
-
4. **Exact declarations** — Use `show` with path + name (+ line when needed). Prefer `signature`; add docs, body, imports, or context lines only when
|
|
41
|
-
5. **
|
|
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.
|
|
50
|
+
3. **Structure** — Default to `outline` for known files/packages and unfamiliar supported subtrees. Use `discover` when requested declaration path or exact name is unknown. Use `ast_search` for source shapes.
|
|
51
|
+
4. **Exact declarations** — Use `show` with path + name (+ line when needed). Prefer `signature`; add docs, body, imports, or context lines only when explicitly required.
|
|
52
|
+
5. **Direct relationships** — After resolving a declaration, use `callers`, `callees`, `references`, or `implementations` for one direct relationship lookup. Use `deps` and `reverse_deps` for file imports, not declaration calls.
|
|
43
53
|
|
|
44
|
-
Structural results prove bounded syntax, not runtime dispatch. Preserve exact, inferred, and ambiguous labels.
|
|
54
|
+
Structural results prove bounded syntax, not runtime dispatch. Preserve exact, inferred, and ambiguous labels emitted by tools. Never convert an ambiguous result into a fact.
|
|
45
55
|
|
|
46
|
-
## Search
|
|
56
|
+
## Search discipline
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
8. Stop when requested claims are supported. Put material gaps under `Unknowns`.
|
|
58
|
+
- Extract concrete target, lookup type, scope, and requested output shape.
|
|
59
|
+
- Narrow each call around one missing fact. Prefer structural summaries and signatures over full source.
|
|
60
|
+
- Start from supplied paths and names. Search outward only as needed to locate requested evidence.
|
|
61
|
+
- Batch only independent lookups whose results will stay small.
|
|
62
|
+
- Do not fan out across plausible explanations or collect evidence for a theory.
|
|
63
|
+
- Stop when requested evidence has been found or bounded search cannot find it.
|
|
64
|
+
- During a long inventory, use `working_memory` only when stale evidence would burden the remaining lookup. Do not checkpoint a small search.
|
|
56
65
|
|
|
57
66
|
Absolute paths may point to read-only reference repositories outside cwd.
|
|
58
67
|
|
|
@@ -62,49 +71,37 @@ Use relevant sections only. Omit empty sections.
|
|
|
62
71
|
|
|
63
72
|
### Locate
|
|
64
73
|
|
|
65
|
-
`path:start-end` — declaration
|
|
66
|
-
|
|
67
|
-
### Explain behavior
|
|
74
|
+
`path:start-end` — declaration or match — exact reason it matches
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
- `Flow:` ordered steps; one cited fact each
|
|
71
|
-
- `Result:` observed outcome
|
|
72
|
-
|
|
73
|
-
### Trace data
|
|
74
|
-
|
|
75
|
-
- `Source:` cited origin
|
|
76
|
-
- `Transforms:` ordered, cited transformations
|
|
77
|
-
- `Consumers:` cited uses
|
|
78
|
-
|
|
79
|
-
### Find references or impact
|
|
76
|
+
### Inventory
|
|
80
77
|
|
|
81
|
-
-
|
|
82
|
-
- `Editable scopes:` declarations requiring inspection or change
|
|
83
|
-
- `Behavior affected:` evidence-backed consequences
|
|
84
|
-
- `Unknowns:` remaining uncertainty
|
|
78
|
+
`path:start-end` — declaration or match — source-defined role
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
State searched scope when completeness matters.
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
- `Evidence:` cited facts
|
|
90
|
-
- `Qualification:` only when needed
|
|
82
|
+
### Direct relationships
|
|
91
83
|
|
|
92
|
-
|
|
84
|
+
- `Relationship:` caller, callee, import, reference, or implementation
|
|
85
|
+
- `Source:` cited declaration
|
|
86
|
+
- `Target:` cited declaration
|
|
87
|
+
- `Certainty:` exact or ambiguous
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
- `Differences:` cited by aspect
|
|
96
|
-
- `Relevant consequence:` requested consequences only
|
|
89
|
+
### Exact pattern check
|
|
97
90
|
|
|
98
|
-
|
|
91
|
+
- `Found:` yes or no within searched scope
|
|
92
|
+
- `Scope:` paths or subtree searched
|
|
93
|
+
- `Matches:` exact citations when found
|
|
99
94
|
|
|
100
|
-
|
|
95
|
+
### Unresolved
|
|
101
96
|
|
|
102
|
-
|
|
97
|
+
- `Missing evidence:` requested lookup that could not be found
|
|
98
|
+
- `Ambiguity:` competing exact matches the tools could not disambiguate
|
|
99
|
+
- `Parent question:` diagnosis, explanation, evaluation, consequence, recommendation, or decision left to parent
|
|
103
100
|
|
|
104
101
|
## Reporting rules
|
|
105
102
|
|
|
106
|
-
- Every
|
|
103
|
+
- Every returned code fact needs exact path and line range. Include declaration name when one exists.
|
|
107
104
|
- Cite ranges returned by tools. Never estimate line numbers.
|
|
108
|
-
- Separate fact from inference. Label inference.
|
|
109
105
|
- Quote smallest useful fragment.
|
|
110
|
-
-
|
|
106
|
+
- Describe only what source directly contains or what a structural tool directly reports.
|
|
107
|
+
- No preamble, search log, repository summary, causal explanation, conclusions, or next-step advice.
|
|
@@ -138,7 +138,6 @@ async function loadScope(
|
|
|
138
138
|
if (!required) return new Map();
|
|
139
139
|
const reason = error instanceof Error ? error.message : "directory unavailable";
|
|
140
140
|
return new Map([
|
|
141
|
-
["review", [{ path: directory, name: "review", reason: `packaged agents unavailable: ${reason}` }]],
|
|
142
141
|
[
|
|
143
142
|
"web-research",
|
|
144
143
|
[{ path: directory, name: "web-research", reason: `packaged agents unavailable: ${reason}` }],
|
|
@@ -124,13 +124,17 @@ export function formatDashboardMarkdown(snapshots: readonly SubagentInvocationSn
|
|
|
124
124
|
lines.push("_No subagents._", "");
|
|
125
125
|
return lines.join("\n");
|
|
126
126
|
}
|
|
127
|
-
lines.push(
|
|
127
|
+
lines.push(
|
|
128
|
+
"| Agent | State | Last tool | Calls | Cost | Ctx | Time |",
|
|
129
|
+
"| --- | --- | --- | ---: | ---: | ---: | ---: |",
|
|
130
|
+
);
|
|
128
131
|
for (const details of ordered) {
|
|
129
132
|
const latest = details.actions.at(-1);
|
|
130
133
|
const currentTool = details.currentActivity?.match(/^\S+/)?.[0];
|
|
131
134
|
const lastTool = currentTool ?? latest?.tool ?? "";
|
|
135
|
+
const ctx = typeof details.contextPercent === "number" ? `${details.contextPercent.toFixed(1)}%` : "—";
|
|
132
136
|
lines.push(
|
|
133
|
-
`| ${tableCell(`${details.displayName} (${details.agent})`, 48)} | ${dashboardState(details.status)} | ${tableCell(lastTool, 32) || "—"} | ${details.toolCalls} | ${elapsed(details.durationMs)} |`,
|
|
137
|
+
`| ${tableCell(`${details.displayName} (${details.agent})`, 48)} | ${dashboardState(details.status)} | ${tableCell(lastTool, 32) || "—"} | ${details.toolCalls} | $${details.usage.cost.toFixed(4)} | ${ctx} | ${elapsed(details.durationMs)} |`,
|
|
134
138
|
);
|
|
135
139
|
}
|
|
136
140
|
lines.push("", "## Inputs", "");
|
|
@@ -138,7 +142,7 @@ export function formatDashboardMarkdown(snapshots: readonly SubagentInvocationSn
|
|
|
138
142
|
lines.push(
|
|
139
143
|
`### ${tableCell(details.displayName, 80)}`,
|
|
140
144
|
"",
|
|
141
|
-
|
|
145
|
+
tableCell(details.agent, 80),
|
|
142
146
|
"",
|
|
143
147
|
quote(details.task) || "> _(empty)_",
|
|
144
148
|
);
|
|
@@ -6,9 +6,17 @@ import { createToolRowStateStore } from "../../shared/tool-row-state.js";
|
|
|
6
6
|
import contextSettings from "../context/settings.ts";
|
|
7
7
|
import { discoverAgents, type AgentDiscovery } from "./agents.ts";
|
|
8
8
|
import { createCmuxDashboard, type CmuxDashboard, type DashboardOrphan } from "./cmux-dashboard.ts";
|
|
9
|
+
import { createAgentsPanel } from "./panel.ts";
|
|
9
10
|
import { renderSubagentCall, renderSubagentResult } from "./render.ts";
|
|
10
11
|
import type { SubagentDetails } from "./run.ts";
|
|
11
12
|
import { failedToolResult, SubagentRuntime } from "./runtime.ts";
|
|
13
|
+
import subagentSettings from "./settings.ts";
|
|
14
|
+
|
|
15
|
+
interface SubagentSessionState {
|
|
16
|
+
disabled: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SUBAGENT_SESSION_STATE_TYPE = "tau.subagent.disabled";
|
|
12
20
|
|
|
13
21
|
const params = Type.Union([
|
|
14
22
|
Type.Object(
|
|
@@ -39,6 +47,7 @@ const params = Type.Union([
|
|
|
39
47
|
|
|
40
48
|
export default function subagentExtension(pi: ExtensionAPI): void {
|
|
41
49
|
const runtime = new SubagentRuntime(pi);
|
|
50
|
+
let sessionDisabled = new Set<string>();
|
|
42
51
|
let failureNotify: ((message: string) => void) | undefined;
|
|
43
52
|
const orphans: DashboardOrphan[] = [];
|
|
44
53
|
const retryOrphans = async () => {
|
|
@@ -122,13 +131,74 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
122
131
|
}
|
|
123
132
|
for (const path of fingerprints.keys()) if (!current.has(path)) fingerprints.delete(path);
|
|
124
133
|
};
|
|
134
|
+
const disabledAgentNames = async (ctx: ExtensionContext) => {
|
|
135
|
+
const configured = new Set((await loadTauExtensionSettings(ctx, subagentSettings)).disabled);
|
|
136
|
+
return { configured, effective: new Set([...configured, ...sessionDisabled]) };
|
|
137
|
+
};
|
|
125
138
|
const parentVisibleAgents = async (ctx: ExtensionContext, discovery: AgentDiscovery) => {
|
|
126
|
-
const sync =
|
|
139
|
+
const [sync, disabled] = await Promise.all([
|
|
140
|
+
loadTauExtensionSettings(ctx, contextSettings).then((settings) => settings.sync),
|
|
141
|
+
disabledAgentNames(ctx),
|
|
142
|
+
]);
|
|
127
143
|
const parentVisible = sync.enabled && sync.automation;
|
|
128
144
|
return [...discovery.agents.values()]
|
|
129
|
-
.filter((agent) => agent.name !== "context-sync" || parentVisible)
|
|
145
|
+
.filter((agent) => !disabled.effective.has(agent.name) && (agent.name !== "context-sync" || parentVisible))
|
|
130
146
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
131
147
|
};
|
|
148
|
+
const restoreSessionState = (ctx: ExtensionContext) => {
|
|
149
|
+
let saved: string[] | undefined;
|
|
150
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
151
|
+
if (entry.type !== "custom" || entry.customType !== SUBAGENT_SESSION_STATE_TYPE) continue;
|
|
152
|
+
const data = entry.data as { disabled?: unknown } | undefined;
|
|
153
|
+
if (Array.isArray(data?.disabled)) {
|
|
154
|
+
saved = data.disabled.filter((name): name is string => typeof name === "string" && name.length > 0);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
sessionDisabled = new Set(saved ?? []);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
pi.registerCommand("agents", {
|
|
161
|
+
description: "Enable/disable subagents for this session",
|
|
162
|
+
handler: async (_args, ctx) => {
|
|
163
|
+
if (ctx.mode !== "tui") {
|
|
164
|
+
ctx.ui.notify("/agents requires TUI mode", "error");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const [discovery, disabled] = await Promise.all([
|
|
169
|
+
discoverAgents(ctx.cwd, ctx.isProjectTrusted()),
|
|
170
|
+
disabledAgentNames(ctx),
|
|
171
|
+
]);
|
|
172
|
+
warn(discovery, ctx);
|
|
173
|
+
const agents = [...discovery.agents.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
174
|
+
if (agents.length === 0) {
|
|
175
|
+
ctx.ui.notify("No valid subagents found", "warning");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await ctx.ui.custom((tui, theme, _keybindings, done) =>
|
|
180
|
+
createAgentsPanel(
|
|
181
|
+
tui,
|
|
182
|
+
theme,
|
|
183
|
+
agents.map((agent) => ({
|
|
184
|
+
id: agent.name,
|
|
185
|
+
disabled: disabled.effective.has(agent.name),
|
|
186
|
+
configured: disabled.configured.has(agent.name),
|
|
187
|
+
})),
|
|
188
|
+
(disabledNames) => {
|
|
189
|
+
for (const agent of agents) {
|
|
190
|
+
if (!disabled.configured.has(agent.name)) sessionDisabled.delete(agent.name);
|
|
191
|
+
}
|
|
192
|
+
for (const name of disabledNames) sessionDisabled.add(name);
|
|
193
|
+
pi.appendEntry<SubagentSessionState>(SUBAGENT_SESSION_STATE_TYPE, {
|
|
194
|
+
disabled: [...sessionDisabled].sort(),
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
() => done(undefined),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
132
202
|
|
|
133
203
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
134
204
|
if (!pi.getActiveTools().includes("subagent")) return undefined;
|
|
@@ -147,7 +217,7 @@ Pass \`files\` when exact relevant files are already known. Tau autoreads curren
|
|
|
147
217
|
|
|
148
218
|
Delegate one focused task per call. Children do not inherit parent messages. Include exact absolute reference paths when a child must inspect a repository outside the current working directory.
|
|
149
219
|
|
|
150
|
-
|
|
220
|
+
Scout only for substantial multi-hop lookup that would flood parent context. Skip few known-path reads, single declaration lookups, or small digs with most evidence already in hand; use tools directly. When uncertain, dig yourself.`;
|
|
151
221
|
return { systemPrompt: `${event.systemPrompt}\n\n${prompt}` };
|
|
152
222
|
});
|
|
153
223
|
pi.registerTool(
|
|
@@ -180,6 +250,27 @@ Review limit: for one user task or coherent implementation batch, call the \`rev
|
|
|
180
250
|
return failedToolResult(agent, task, "queue", parentModel, parentThinking, error, threadKey);
|
|
181
251
|
}
|
|
182
252
|
|
|
253
|
+
if (continuing) {
|
|
254
|
+
const thread = runtime.listThreads(ctx.cwd).find((item) => item.id === threadKey);
|
|
255
|
+
if (thread) {
|
|
256
|
+
const disabled = await disabledAgentNames(ctx);
|
|
257
|
+
if (disabled.effective.has(thread.definition.name)) {
|
|
258
|
+
const source = disabled.configured.has(thread.definition.name)
|
|
259
|
+
? "in Tau settings"
|
|
260
|
+
: "for this session";
|
|
261
|
+
return failedToolResult(
|
|
262
|
+
thread.definition.name,
|
|
263
|
+
task,
|
|
264
|
+
"discovery",
|
|
265
|
+
parentModel,
|
|
266
|
+
parentThinking,
|
|
267
|
+
`Agent ${thread.definition.name} is disabled ${source}.`,
|
|
268
|
+
threadKey,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
183
274
|
return runtime.execute({
|
|
184
275
|
agent,
|
|
185
276
|
task,
|
|
@@ -215,6 +306,11 @@ Review limit: for one user task or coherent implementation batch, call the \`rev
|
|
|
215
306
|
error: `Agent ${agent} discovery failed: ${reason}. Runnable agents: ${names}`,
|
|
216
307
|
};
|
|
217
308
|
}
|
|
309
|
+
const disabled = await disabledAgentNames(ctx);
|
|
310
|
+
if (disabled.effective.has(definition.name)) {
|
|
311
|
+
const source = disabled.configured.has(definition.name) ? "in Tau settings" : "for this session";
|
|
312
|
+
return { ok: false, phase: "discovery", error: `Agent ${definition.name} is disabled ${source}.` };
|
|
313
|
+
}
|
|
218
314
|
if (definition.name === "context-sync") {
|
|
219
315
|
const sync = (await loadTauExtensionSettings(ctx, contextSettings)).sync;
|
|
220
316
|
if (!sync.enabled) {
|
|
@@ -257,13 +353,17 @@ Review limit: for one user task or coherent implementation batch, call the \`rev
|
|
|
257
353
|
const details = event.details as SubagentDetails | undefined;
|
|
258
354
|
if (details?.status === "failed" || details?.status === "aborted") return { isError: true };
|
|
259
355
|
});
|
|
260
|
-
pi.on("session_start", async () => {
|
|
356
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
357
|
+
restoreSessionState(ctx);
|
|
261
358
|
await runtime.reset();
|
|
262
359
|
await replaceDashboard();
|
|
263
360
|
rowState.clear();
|
|
264
361
|
fingerprints.clear();
|
|
265
362
|
failureNotify = undefined;
|
|
266
363
|
});
|
|
364
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
365
|
+
restoreSessionState(ctx);
|
|
366
|
+
});
|
|
267
367
|
pi.on("session_shutdown", async () => {
|
|
268
368
|
unsubscribeDashboard();
|
|
269
369
|
await runtime.shutdown();
|
|
@@ -273,5 +373,6 @@ Review limit: for one user task or coherent implementation batch, call the \`rev
|
|
|
273
373
|
rowState.clear();
|
|
274
374
|
fingerprints.clear();
|
|
275
375
|
failureNotify = undefined;
|
|
376
|
+
sessionDisabled.clear();
|
|
276
377
|
});
|
|
277
378
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Component, getKeybindings, Key, matchesKey, truncateToWidth, type TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import {
|
|
4
|
+
rawHint,
|
|
5
|
+
SelectableList,
|
|
6
|
+
type SelectableListResult,
|
|
7
|
+
ToolPanel,
|
|
8
|
+
type ToolPanelConfig,
|
|
9
|
+
} from "@shanepadgett/tau-tui";
|
|
10
|
+
|
|
11
|
+
export interface AgentPanelItem {
|
|
12
|
+
id: string;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
configured: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createAgentsPanel(
|
|
18
|
+
tui: TUI,
|
|
19
|
+
theme: Theme,
|
|
20
|
+
initial: readonly AgentPanelItem[],
|
|
21
|
+
onApply: (disabled: readonly string[]) => void,
|
|
22
|
+
done: () => void,
|
|
23
|
+
): Component {
|
|
24
|
+
return new AgentsPanel(tui, theme, initial, onApply, done);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class AgentsPanel implements Component {
|
|
28
|
+
private readonly tui: TUI;
|
|
29
|
+
private readonly onApply: (disabled: readonly string[]) => void;
|
|
30
|
+
private readonly done: () => void;
|
|
31
|
+
private readonly list: SelectableList<AgentPanelItem>;
|
|
32
|
+
private readonly panelConfig: ToolPanelConfig;
|
|
33
|
+
private readonly panel: ToolPanel;
|
|
34
|
+
private items: readonly AgentPanelItem[];
|
|
35
|
+
private closed = false;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
tui: TUI,
|
|
39
|
+
theme: Theme,
|
|
40
|
+
initial: readonly AgentPanelItem[],
|
|
41
|
+
onApply: (disabled: readonly string[]) => void,
|
|
42
|
+
done: () => void,
|
|
43
|
+
) {
|
|
44
|
+
this.tui = tui;
|
|
45
|
+
this.items = initial;
|
|
46
|
+
this.onApply = onApply;
|
|
47
|
+
this.done = done;
|
|
48
|
+
this.list = new SelectableList(theme, {
|
|
49
|
+
items: initial,
|
|
50
|
+
emptyMessage: "No valid subagents found.",
|
|
51
|
+
selection: { kind: "single", primaryLabel: "apply" },
|
|
52
|
+
actions: [],
|
|
53
|
+
cancelLabel: "close",
|
|
54
|
+
maxVisible: 10,
|
|
55
|
+
renderItem: (item, state, width) => {
|
|
56
|
+
const label = state.active ? theme.bold(item.id) : item.id;
|
|
57
|
+
const status = item.configured
|
|
58
|
+
? theme.fg("muted", "disabled by Tau settings")
|
|
59
|
+
: item.disabled
|
|
60
|
+
? theme.fg("warning", "disabled")
|
|
61
|
+
: theme.fg("success", "enabled");
|
|
62
|
+
return [truncateToWidth(`${label} ${status}`, width, "…")];
|
|
63
|
+
},
|
|
64
|
+
onResult: (result) => this.handleResult(result),
|
|
65
|
+
});
|
|
66
|
+
this.panelConfig = {
|
|
67
|
+
title: "Subagents",
|
|
68
|
+
secondary: "Changes apply to this session.",
|
|
69
|
+
body: this.list,
|
|
70
|
+
footer: { kind: "hints", hints: this.hints() },
|
|
71
|
+
border: "box",
|
|
72
|
+
};
|
|
73
|
+
this.panel = new ToolPanel(theme, this.panelConfig);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
render(width: number): string[] {
|
|
77
|
+
return this.panel.render(width);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
invalidate(): void {
|
|
81
|
+
this.panel.invalidate();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
handleInput(data: string): void {
|
|
85
|
+
if (matchesKey(data, Key.space) || data === " ") {
|
|
86
|
+
const current = this.list.getCurrentItem();
|
|
87
|
+
if (!current || current.configured) return;
|
|
88
|
+
this.items = this.items.map((item) => (item.id === current.id ? { ...item, disabled: !item.disabled } : item));
|
|
89
|
+
this.list.setItems(this.items, current.id);
|
|
90
|
+
this.syncPanel();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (getKeybindings().matches(data, "tui.select.confirm")) {
|
|
94
|
+
this.onApply(this.items.filter((item) => item.disabled && !item.configured).map((item) => item.id));
|
|
95
|
+
this.closed = true;
|
|
96
|
+
this.done();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.list.handleInput(data);
|
|
100
|
+
if (!this.closed) this.syncPanel();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private handleResult(result: SelectableListResult<AgentPanelItem>): void {
|
|
104
|
+
if (result.kind === "cancel") {
|
|
105
|
+
this.closed = true;
|
|
106
|
+
this.done();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private syncPanel(): void {
|
|
112
|
+
this.panelConfig.footer = { kind: "hints", hints: this.hints() };
|
|
113
|
+
this.tui.requestRender();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private hints() {
|
|
117
|
+
const hints = this.list.getKeyHints();
|
|
118
|
+
const current = this.list.getCurrentItem();
|
|
119
|
+
if (current && !current.configured) {
|
|
120
|
+
hints.splice(1, 0, rawHint("Space", "toggle"));
|
|
121
|
+
}
|
|
122
|
+
return hints;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -48,7 +48,8 @@ export function renderSubagentResult(
|
|
|
48
48
|
return text;
|
|
49
49
|
}
|
|
50
50
|
const identity = `${details.displayName} (${details.agent})`;
|
|
51
|
-
const
|
|
51
|
+
const ctx = typeof details.contextPercent === "number" ? ` · ${details.contextPercent.toFixed(1)}% ctx` : "";
|
|
52
|
+
const header = `${title(theme, context.rowState, context.rowId, context.invalidate)} ${theme.fg("accent", identity)} ${theme.fg("muted", `$${details.usage.cost.toFixed(4)} · ${(details.durationMs / 1000).toFixed(1)}s · ${details.toolCalls} tools${ctx}`)}`;
|
|
52
53
|
if (!expanded) {
|
|
53
54
|
text.setText(`${header} ${theme.fg("muted", details.task.replace(/\s+/g, " ").trim())}`);
|
|
54
55
|
return text;
|
|
@@ -13,14 +13,10 @@ import {
|
|
|
13
13
|
type ExtensionContext,
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { createCompleteFileMeta } from "../../shared/full-file-knowledge.ts";
|
|
16
|
+
import { createIsolatedSessionResource, type IsolatedSessionResource } from "../../shared/isolated-session.ts";
|
|
16
17
|
import type { AgentDefinition } from "./agents.ts";
|
|
17
18
|
import { emptySubagentResumeState, type RetainedTurnOutcome, type SubagentResumeState } from "./resume.ts";
|
|
18
|
-
import {
|
|
19
|
-
createSubagentSessionResource,
|
|
20
|
-
resolveSubagentSessionInputs,
|
|
21
|
-
type SubagentSessionInputs,
|
|
22
|
-
type SubagentSessionResource,
|
|
23
|
-
} from "./session-resource.ts";
|
|
19
|
+
import { resolveSubagentSessionInputs, type SubagentSessionInputs } from "./session-resource.ts";
|
|
24
20
|
|
|
25
21
|
const PREVIEW_LIMIT = 600;
|
|
26
22
|
const VALUE_LIMIT = 180;
|
|
@@ -62,6 +58,7 @@ export interface SubagentDetails {
|
|
|
62
58
|
omittedActions: number;
|
|
63
59
|
omittedErrors: number;
|
|
64
60
|
usage: SubagentUsage;
|
|
61
|
+
contextPercent?: number;
|
|
65
62
|
durationMs: number;
|
|
66
63
|
truncation?: {
|
|
67
64
|
truncated: boolean;
|
|
@@ -85,7 +82,7 @@ export interface SubagentThread {
|
|
|
85
82
|
displayName: string;
|
|
86
83
|
definition: AgentDefinition;
|
|
87
84
|
sessionInputs: SubagentSessionInputs;
|
|
88
|
-
resource:
|
|
85
|
+
resource: IsolatedSessionResource;
|
|
89
86
|
cwd: string;
|
|
90
87
|
model: string;
|
|
91
88
|
thinkingLevel: string;
|
|
@@ -199,7 +196,7 @@ export async function createSubagentThread(options: {
|
|
|
199
196
|
extensionPaths: readonly string[];
|
|
200
197
|
initialTask: string;
|
|
201
198
|
ctx: ExtensionContext;
|
|
202
|
-
thinkingLevel:
|
|
199
|
+
thinkingLevel: NonNullable<ExtensionContext["thinkingLevel"]>;
|
|
203
200
|
signal: AbortSignal;
|
|
204
201
|
onWarning?: (warning: string) => void;
|
|
205
202
|
}): Promise<SubagentThread> {
|
|
@@ -222,7 +219,7 @@ export async function createSubagentThread(options: {
|
|
|
222
219
|
signal,
|
|
223
220
|
...(onWarning === undefined ? {} : { onWarning }),
|
|
224
221
|
});
|
|
225
|
-
const resource = await
|
|
222
|
+
const resource = await createIsolatedSessionResource(sessionInputs, signal);
|
|
226
223
|
try {
|
|
227
224
|
return {
|
|
228
225
|
id,
|
|
@@ -307,6 +304,9 @@ export async function runSubagentTurn(options: {
|
|
|
307
304
|
if (!force && now - lastTextUpdate < 100) return;
|
|
308
305
|
lastTextUpdate = now;
|
|
309
306
|
details.durationMs = now - started;
|
|
307
|
+
const context = session.getContextUsage();
|
|
308
|
+
if (typeof context?.percent === "number") details.contextPercent = context.percent;
|
|
309
|
+
else delete details.contextPercent;
|
|
310
310
|
const snapshot = cloneSnapshot(details);
|
|
311
311
|
snapshot.actions = snapshot.actions.slice(-5);
|
|
312
312
|
// Detached observer chain — must not delay prompt completion.
|