@shanepadgett/tau-agent 0.29.0 → 0.31.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 +3 -4
- 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/ast/read/hook.ts +3 -1
- 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/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 +48 -61
- 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 +11 -7
- package/extensions/working-memory/README.md +1 -1
- package/extensions/working-memory/checkpoint.ts +3 -4
- package/extensions/working-memory/index.ts +25 -7
- package/extensions/working-memory/memory.ts +53 -82
- package/package.json +2 -2
- package/schemas/tau.schema.json +9 -23
- package/shared/context-messages.ts +19 -0
- package/shared/injected-context.ts +2 -2
- package/shared/isolated-session.ts +151 -0
- package/extensions/subagent/agents/review.md +0 -75
- package/extensions/turn-budget/README.md +0 -14
- package/extensions/turn-budget/index.ts +0 -116
- package/extensions/turn-budget/settings.ts +0 -35
|
@@ -32,11 +32,11 @@ Adds `/commit` for semantic commit grouping, review, and committing selected rep
|
|
|
32
32
|
|
|
33
33
|
## context
|
|
34
34
|
|
|
35
|
-
Adds `/context` to
|
|
35
|
+
Adds `/context` to set branch-local reusable repository work scopes from `.pi/contexts`, and `/context-sync` or `/context-sync <nudge>` for human-driven catalog sync. Active entries produce one ephemeral per-call projection instead of transcript messages. Entry `read` paths supply exact contents, `outline` paths use Explore, and `references` stay unloaded. Clear all selections and confirm to remove active context. Escape cancels a running manual sync. 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. Folder names are tabs, TOML files are concepts, and TOML sections are selectable entries.
|
|
36
36
|
|
|
37
37
|
## working-memory
|
|
38
38
|
|
|
39
|
-
Gives agent `working_memory` for selective hard checkpoints. Model-only references identify useful
|
|
39
|
+
Gives agent `working_memory` for selective hard checkpoints. Model-only references identify useful user messages and visible assistant text. Requested source files return as structural outlines, deferred files remain cheap conditional reminders, and a continuation note carries conclusions extracted from exploration. Tool history and full file reads leave future model input without changing saved session. Advisory reminders begin at 40k active-context tokens. Run `/prune` to request reassessment manually.
|
|
40
40
|
|
|
41
41
|
## explore
|
|
42
42
|
|
|
@@ -74,6 +74,10 @@ Adds `/publish` to create a tagged release, trigger trusted npm publishing in Gi
|
|
|
74
74
|
|
|
75
75
|
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
76
|
|
|
77
|
+
## review
|
|
78
|
+
|
|
79
|
+
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.
|
|
80
|
+
|
|
77
81
|
## reference
|
|
78
82
|
|
|
79
83
|
Adds `/reference` to manage separate repositories kept outside the current project for inspiration or comparison. Add one with `/reference new <git-url>`, update it, switch its referenced branch, or open it in an editor. Select references and explain why they matter; Tau then puts their paths and that reason into the editor for the agent. References stay outside the project so the agent does not wander into unrelated code unless you explicitly point it there.
|
|
@@ -86,6 +90,10 @@ Shows a compact display-only marker after each run with wall time and model cost
|
|
|
86
90
|
|
|
87
91
|
Supplies the agent with the current local date and an initial root directory snapshot as hidden session context.
|
|
88
92
|
|
|
93
|
+
## script-runner
|
|
94
|
+
|
|
95
|
+
Gives the agent a first-class `script_runner` tool to execute Python and TypeScript 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. Languages are detected from the environment (Python via `python3`/`python`; TypeScript via Node `--experimental-strip-types`, Node 22.6+). The tool registers only available languages and is hidden from the prompt if neither is present.
|
|
96
|
+
|
|
89
97
|
## silent-command-runner
|
|
90
98
|
|
|
91
99
|
Runs configured commands while keeping their output out of agent context when that is useful.
|
|
@@ -100,7 +108,7 @@ Adds `Alt+S` to stash the current prompt draft and `/pop` to browse stashed draf
|
|
|
100
108
|
|
|
101
109
|
## subagent
|
|
102
110
|
|
|
103
|
-
Gives Tau a subagent delegation tool for isolated, focused work.
|
|
111
|
+
Gives Tau a subagent delegation tool for isolated, focused work. Run `/agents` to enable or disable individual agents for the current session, or set `extensions.subagent.disabled` in Tau settings for a persistent choice. `scout` is substantial multi-hop local code lookup that would chew parent context; facts only, not small digs; `web-research` handles external research. Known files can be autoread as line-numbered snapshots into a fresh or retained child turn. Tau can continue a retained child thread when follow-up work depends on its prior reads and reasoning. You can also create your own subagents in supported subagent directories. Ask Tau how to do it and have it consult extension documentation; built-in agents show pattern. Each subagent can register its own model, tools, and pool of display names. Reused pool names get numeric suffixes. In interactive cmux sessions, Tau opens one temporary Markdown dashboard for live subagent progress; it does not change how children run and closes shortly after active cohort finishes.
|
|
104
112
|
|
|
105
113
|
## tau-help
|
|
106
114
|
|
|
@@ -114,10 +122,6 @@ Adds `/tau`, `/tau init [--global|--project]`, and `/tau doctor` for Tau setup a
|
|
|
114
122
|
|
|
115
123
|
Progressively exposes specialist tools through `load_tools`. Tau normally loads the fixed `web`, `image`, and `appshot` groups itself when needed; supported providers can preserve more prompt-cache reuse.
|
|
116
124
|
|
|
117
|
-
## turn-budget
|
|
118
|
-
|
|
119
|
-
Tracks and limits agent turns to keep work bounded.
|
|
120
|
-
|
|
121
125
|
## web
|
|
122
126
|
|
|
123
127
|
Gives the agent compact `websearch`, `webfetch`, and `codesearch` tools for web and implementation research.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Working Memory gives agent selective checkpoints without changing saved conversation.
|
|
4
4
|
|
|
5
|
-
`working_memory` keeps referenced
|
|
5
|
+
`working_memory` keeps referenced user messages and visible assistant text, carries requested source files as structural outlines, and records deferred files as conditional reminders. Tool history and full file reads leave model context; useful findings from them are distilled into a compact continuation note.
|
|
6
6
|
|
|
7
7
|
Automatic reminders begin at 40,000 active-context tokens and remain advisory. Run `/prune` to request reassessment manually.
|
|
8
8
|
|
|
@@ -3,7 +3,7 @@ import { type ExtensionAPI, type ExtensionContext, type SessionEntry } from "@ea
|
|
|
3
3
|
import { type Static, Type } from "typebox";
|
|
4
4
|
import { truncateBoundedHead } from "../../shared/bounded-text-result.ts";
|
|
5
5
|
import { requestOutlineInjections, type PreparedOutlineInjection } from "../../shared/outline-injection.ts";
|
|
6
|
-
import { buildMemoryCatalog } from "./memory.ts";
|
|
6
|
+
import { buildMemoryCatalog, collectPrunedRowIds } from "./memory.ts";
|
|
7
7
|
import { WORKING_MEMORY_TOOL, type DeferredFile, type WorkingMemoryCheckpointDetailsV1 } from "./state.ts";
|
|
8
8
|
|
|
9
9
|
const PATH = Type.String({ minLength: 1, maxLength: 500, pattern: "\\S" });
|
|
@@ -58,7 +58,7 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
|
|
|
58
58
|
const unit = catalog.get(ref);
|
|
59
59
|
return unit ? [unit] : [];
|
|
60
60
|
})
|
|
61
|
-
.sort((left, right) => left.order - right.order
|
|
61
|
+
.sort((left, right) => left.order - right.order);
|
|
62
62
|
const retainedRefs = retained.map((unit) => unit.ref);
|
|
63
63
|
const warnings = requestedRefs
|
|
64
64
|
.filter((ref) => !catalog.has(ref))
|
|
@@ -89,9 +89,8 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
const anchorIndex = findAnchorEntry(branch, options.toolCallId);
|
|
92
|
-
const retainedSet = new Set(retainedRefs);
|
|
93
92
|
const preAnchorUnits = [...catalog.values()].filter((unit) => unit.order < anchorIndex);
|
|
94
|
-
const prunedRowIds =
|
|
93
|
+
const prunedRowIds = collectPrunedRowIds(branch, anchorIndex);
|
|
95
94
|
const details: WorkingMemoryCheckpointDetailsV1 = {
|
|
96
95
|
v: 1,
|
|
97
96
|
anchorToolCallId: options.toolCallId,
|
|
@@ -22,7 +22,7 @@ import { replayWorkingMemoryState, WORKING_MEMORY_TOOL, type WorkingMemoryCheckp
|
|
|
22
22
|
const NUDGE_TYPE = "tau.working-memory.nudge";
|
|
23
23
|
const BASELINE_TYPE = "tau.working-memory.nudge-baseline";
|
|
24
24
|
const TOOL_DESCRIPTION =
|
|
25
|
-
"Create a selective hard checkpoint for future model context. Keep valuable
|
|
25
|
+
"Create a selective hard checkpoint for future model context. Keep valuable user and visible assistant messages, carry file structure as outlines, defer conditionally relevant files without reading them, and distill exploration findings into one compact continuation note.";
|
|
26
26
|
|
|
27
27
|
interface NudgeState {
|
|
28
28
|
anchorToolCallId: string | undefined;
|
|
@@ -98,9 +98,9 @@ export default function workingMemoryExtension(pi: ExtensionAPI): void {
|
|
|
98
98
|
promptSnippet: "Reassess and selectively checkpoint active working memory",
|
|
99
99
|
promptGuidelines: [
|
|
100
100
|
"Use working_memory when stale evidence has accumulated or a memory reminder asks for reassessment; continue coherent exploration when current evidence remains useful.",
|
|
101
|
-
"A hidden working-memory reference catalog provides keep refs
|
|
102
|
-
"
|
|
103
|
-
"Everything before working_memory leaves future model context unless selected in keep. Put durable decisions, constraints, unresolved matters, and next action in continuation without duplicating retained
|
|
101
|
+
"A hidden working-memory reference catalog provides keep refs only for user messages and visible assistant text. Tool calls, tool results, hidden reasoning, and framework messages cannot be retained.",
|
|
102
|
+
"Outline files that remain in the repository working set, defer paths whose relevance is conditional, and discard exploration history after extracting its useful conclusions.",
|
|
103
|
+
"Everything before working_memory leaves future model context unless selected in keep. Put findings from tools, durable decisions, constraints, unresolved matters, and next action in continuation without duplicating retained messages.",
|
|
104
104
|
],
|
|
105
105
|
parameters: workingMemoryParameters,
|
|
106
106
|
executionMode: "sequential",
|
|
@@ -174,14 +174,32 @@ export default function workingMemoryExtension(pi: ExtensionAPI): void {
|
|
|
174
174
|
syncBranch(ctx);
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
pi.on("before_agent_start", (
|
|
177
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
178
178
|
if (!enabled) return undefined;
|
|
179
179
|
const usage = ctx.getContextUsage();
|
|
180
180
|
if (!usage || usage.tokens === null || !Number.isFinite(usage.tokens)) return undefined;
|
|
181
|
-
const
|
|
181
|
+
const tokens = Math.max(0, Math.floor(usage.tokens));
|
|
182
|
+
const reminder = Math.floor(tokens / interval);
|
|
182
183
|
if (reminder < 1) return undefined;
|
|
183
184
|
const instruction = instructions[Math.min(reminder, instructions.length) - 1] ?? instructions[0];
|
|
184
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
message: {
|
|
187
|
+
customType: NUDGE_TYPE,
|
|
188
|
+
content: automaticInstruction(instruction),
|
|
189
|
+
display: false,
|
|
190
|
+
details: {
|
|
191
|
+
v: 1,
|
|
192
|
+
kind: "automatic",
|
|
193
|
+
tokens,
|
|
194
|
+
boundaryTokens: reminder * interval,
|
|
195
|
+
reminder,
|
|
196
|
+
tier: Math.min(reminder, instructions.length),
|
|
197
|
+
tierCount: instructions.length,
|
|
198
|
+
anchorToolCallId:
|
|
199
|
+
replayWorkingMemoryState(ctx.sessionManager.getBranch(), true).latestAnchorToolCallId ?? null,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
185
203
|
});
|
|
186
204
|
|
|
187
205
|
pi.on("turn_end", (event, ctx) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sessionEntryToContextMessages, type ContextEvent, type SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { isContextProjectionMessage, isLegacyContextMessage } from "../../shared/context-messages.ts";
|
|
2
3
|
import type { ActiveWorkingMemoryState } from "./state.ts";
|
|
3
4
|
|
|
4
5
|
type ContextMessage = ContextEvent["messages"][number];
|
|
@@ -8,23 +9,13 @@ export interface MemoryUnit {
|
|
|
8
9
|
label: string;
|
|
9
10
|
preview: string;
|
|
10
11
|
order: number;
|
|
11
|
-
suborder: number;
|
|
12
12
|
messages: ContextMessage[];
|
|
13
|
-
rowIds: string[];
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const EXCLUDED_CUSTOM_TYPES = new Set(["tau.autoread", "tau.runtime-context", "tau.working-memory.nudge"]);
|
|
17
15
|
const REFERENCE_CATALOG_TYPE = "tau.working-memory.references";
|
|
18
16
|
|
|
19
17
|
export function buildMemoryCatalog(branch: readonly SessionEntry[]): Map<string, MemoryUnit> {
|
|
20
18
|
const catalog = new Map<string, MemoryUnit>();
|
|
21
|
-
const results = new Map<string, ContextMessage>();
|
|
22
|
-
for (const entry of branch) {
|
|
23
|
-
for (const message of sessionEntryToContextMessages(entry)) {
|
|
24
|
-
if (message.role === "toolResult") results.set(message.toolCallId, message);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
19
|
for (let order = 0; order < branch.length; order += 1) {
|
|
29
20
|
const entry = branch[order];
|
|
30
21
|
if (!entry) continue;
|
|
@@ -34,53 +25,56 @@ export function buildMemoryCatalog(branch: readonly SessionEntry[]): Map<string,
|
|
|
34
25
|
const calls = message.content.filter((block) => block.type === "toolCall");
|
|
35
26
|
const frameworkCall = calls.some((call) => call.name === "working_memory");
|
|
36
27
|
if (frameworkCall) continue;
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
28
|
+
const text = message.content.filter((block) => block.type === "text");
|
|
29
|
+
if (text.length > 0) {
|
|
39
30
|
const ref = `m:${entry.id}`;
|
|
40
31
|
catalog.set(ref, {
|
|
41
32
|
ref,
|
|
42
33
|
label: "assistant",
|
|
43
|
-
preview: previewAssistant(
|
|
44
|
-
order,
|
|
45
|
-
suborder: 0,
|
|
46
|
-
messages: [{ ...message, content: prose }],
|
|
47
|
-
rowIds: [],
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
for (let index = 0; index < calls.length; index += 1) {
|
|
51
|
-
const call = calls[index];
|
|
52
|
-
if (!call) continue;
|
|
53
|
-
const result = results.get(call.id);
|
|
54
|
-
if (result?.role !== "toolResult" || result.toolName !== call.name) continue;
|
|
55
|
-
const ref = `t:${entry.id}:${index + 1}`;
|
|
56
|
-
catalog.set(ref, {
|
|
57
|
-
ref,
|
|
58
|
-
label: `tool ${call.name}`,
|
|
59
|
-
preview: previewTool(call.arguments),
|
|
34
|
+
preview: previewAssistant(text),
|
|
60
35
|
order,
|
|
61
|
-
|
|
62
|
-
messages: [{ ...message, content: [call] }, result],
|
|
63
|
-
rowIds: [call.id],
|
|
36
|
+
messages: [{ ...message, content: text }],
|
|
64
37
|
});
|
|
65
38
|
}
|
|
66
39
|
continue;
|
|
67
40
|
}
|
|
68
|
-
if (message.role
|
|
69
|
-
if (message.role === "custom" && EXCLUDED_CUSTOM_TYPES.has(message.customType)) continue;
|
|
41
|
+
if (message.role !== "user") continue;
|
|
70
42
|
const ref = `m:${entry.id}`;
|
|
71
43
|
catalog.set(ref, {
|
|
72
44
|
ref,
|
|
73
|
-
label:
|
|
74
|
-
preview:
|
|
45
|
+
label: "user",
|
|
46
|
+
preview: previewUser(message),
|
|
75
47
|
order,
|
|
76
|
-
suborder: 0,
|
|
77
48
|
messages: [message],
|
|
78
|
-
rowIds: outlineRowIds(message),
|
|
79
49
|
});
|
|
80
50
|
}
|
|
81
51
|
return catalog;
|
|
82
52
|
}
|
|
83
53
|
|
|
54
|
+
export function collectPrunedRowIds(branch: readonly SessionEntry[], before: number): string[] {
|
|
55
|
+
const rowIds = new Set<string>();
|
|
56
|
+
for (let index = 0; index < before; index += 1) {
|
|
57
|
+
const entry = branch[index];
|
|
58
|
+
if (!entry) continue;
|
|
59
|
+
for (const message of sessionEntryToContextMessages(entry)) {
|
|
60
|
+
if (message.role === "assistant") {
|
|
61
|
+
for (const block of message.content) {
|
|
62
|
+
if (block.type === "toolCall" && block.name !== "working_memory") rowIds.add(block.id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (
|
|
66
|
+
message.role === "custom" &&
|
|
67
|
+
message.customType === "tau.explore.outline" &&
|
|
68
|
+
isRecord(message.details) &&
|
|
69
|
+
typeof message.details.rowId === "string"
|
|
70
|
+
) {
|
|
71
|
+
rowIds.add(message.details.rowId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...rowIds];
|
|
76
|
+
}
|
|
77
|
+
|
|
84
78
|
export function projectWorkingMemory(
|
|
85
79
|
messages: readonly ContextMessage[],
|
|
86
80
|
state: ActiveWorkingMemoryState,
|
|
@@ -100,7 +94,7 @@ export function projectWorkingMemory(
|
|
|
100
94
|
const unit = catalog.get(ref);
|
|
101
95
|
return unit ? [unit] : [];
|
|
102
96
|
})
|
|
103
|
-
.sort((left, right) => left.order - right.order
|
|
97
|
+
.sort((left, right) => left.order - right.order);
|
|
104
98
|
const projected: ContextMessage[] = [];
|
|
105
99
|
const projectedRefs: MemoryUnit[][] = [];
|
|
106
100
|
for (const unit of retained) {
|
|
@@ -121,24 +115,23 @@ function referenceCurrentMessages(
|
|
|
121
115
|
entries: readonly SessionEntry[],
|
|
122
116
|
catalog: ReadonlyMap<string, MemoryUnit>,
|
|
123
117
|
): MemoryUnit[][] {
|
|
124
|
-
const generated = entries
|
|
125
|
-
sessionEntryToContextMessages(entry).map((message) => ({ entry, message }))
|
|
118
|
+
const generated = entries
|
|
119
|
+
.flatMap((entry) => sessionEntryToContextMessages(entry).map((message) => ({ entry, message })))
|
|
120
|
+
.filter(({ message }) => !isLegacyContextMessage(message));
|
|
121
|
+
const sessionMessages = messages.filter(
|
|
122
|
+
(message) => !isContextProjectionMessage(message) && !isLegacyContextMessage(message),
|
|
126
123
|
);
|
|
127
|
-
if (generated.length !==
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (message.role
|
|
136
|
-
const unit = catalog.get(`m:${entry.id}`);
|
|
137
|
-
return unit && messages[index]?.role === "assistant" ? [unit] : [];
|
|
138
|
-
}
|
|
139
|
-
if (message.role === "toolResult") return toolRefs.get(message.toolCallId) ?? [];
|
|
124
|
+
if (generated.length !== sessionMessages.length) return messages.map(() => []);
|
|
125
|
+
let generatedIndex = 0;
|
|
126
|
+
return messages.map((currentMessage) => {
|
|
127
|
+
if (isContextProjectionMessage(currentMessage) || isLegacyContextMessage(currentMessage)) return [];
|
|
128
|
+
const current = generated[generatedIndex];
|
|
129
|
+
generatedIndex += 1;
|
|
130
|
+
if (!current) return [];
|
|
131
|
+
const { entry, message } = current;
|
|
132
|
+
if (message.role !== "user" && message.role !== "assistant") return [];
|
|
140
133
|
const unit = catalog.get(`m:${entry.id}`);
|
|
141
|
-
return unit ? [unit] : [];
|
|
134
|
+
return unit && currentMessage.role === message.role ? [unit] : [];
|
|
142
135
|
});
|
|
143
136
|
}
|
|
144
137
|
|
|
@@ -166,7 +159,7 @@ function addReferenceCatalog(
|
|
|
166
159
|
role: "custom",
|
|
167
160
|
customType: REFERENCE_CATALOG_TYPE,
|
|
168
161
|
content: [...unique.values()]
|
|
169
|
-
.sort((left, right) => left.order - right.order
|
|
162
|
+
.sort((left, right) => left.order - right.order)
|
|
170
163
|
.map((unit) => `${unit.ref} (${unit.label}): ${unit.preview}`)
|
|
171
164
|
.join("\n"),
|
|
172
165
|
display: false,
|
|
@@ -206,36 +199,14 @@ function findAnchor(messages: readonly ContextMessage[], id: string): number {
|
|
|
206
199
|
function previewAssistant(blocks: ReadonlyArray<{ type: string }>): string {
|
|
207
200
|
for (const block of blocks) {
|
|
208
201
|
if (block.type === "text" && "text" in block && typeof block.text === "string") return compact(block.text);
|
|
209
|
-
if (block.type === "thinking" && "thinking" in block && typeof block.thinking === "string") {
|
|
210
|
-
return compact(block.thinking);
|
|
211
|
-
}
|
|
212
202
|
}
|
|
213
203
|
return "";
|
|
214
204
|
}
|
|
215
205
|
|
|
216
|
-
function
|
|
217
|
-
return compact(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
function previewMessage(message: ContextMessage): string {
|
|
221
|
-
if (message.role === "user" || message.role === "custom") {
|
|
222
|
-
if (typeof message.content === "string") return compact(message.content);
|
|
223
|
-
const text = message.content.find((part) => part.type === "text");
|
|
224
|
-
return text?.type === "text" ? compact(text.text) : "";
|
|
225
|
-
}
|
|
226
|
-
if (message.role === "bashExecution") return compact(`${message.command}: ${message.output}`);
|
|
227
|
-
if (message.role === "assistant") return previewAssistant(message.content);
|
|
228
|
-
if (message.role === "toolResult") {
|
|
229
|
-
const text = message.content.find((part) => part.type === "text");
|
|
230
|
-
return text?.type === "text" ? compact(text.text) : "";
|
|
231
|
-
}
|
|
232
|
-
return compact(message.summary);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function outlineRowIds(message: ContextMessage): string[] {
|
|
236
|
-
if (message.role !== "custom" || message.customType !== "tau.explore.outline") return [];
|
|
237
|
-
if (!isRecord(message.details) || typeof message.details.rowId !== "string") return [];
|
|
238
|
-
return [message.details.rowId];
|
|
206
|
+
function previewUser(message: Extract<ContextMessage, { role: "user" }>): string {
|
|
207
|
+
if (typeof message.content === "string") return compact(message.content);
|
|
208
|
+
const text = message.content.find((part) => part.type === "text");
|
|
209
|
+
return text?.type === "text" ? compact(text.text) : "";
|
|
239
210
|
}
|
|
240
211
|
|
|
241
212
|
function compact(value: string): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@ast-grep/wasm": "0.45.0",
|
|
38
|
-
"@shanepadgett/tau-tui": "0.
|
|
38
|
+
"@shanepadgett/tau-tui": "0.31.0",
|
|
39
39
|
"@vscode/tree-sitter-wasm": "0.3.1",
|
|
40
40
|
"image-size": "2.0.2",
|
|
41
41
|
"smol-toml": "1.7.0",
|
package/schemas/tau.schema.json
CHANGED
|
@@ -248,31 +248,17 @@
|
|
|
248
248
|
},
|
|
249
249
|
"additionalProperties": false
|
|
250
250
|
},
|
|
251
|
-
"
|
|
251
|
+
"subagent": {
|
|
252
252
|
"type": "object",
|
|
253
253
|
"properties": {
|
|
254
|
-
"
|
|
255
|
-
"type": "
|
|
256
|
-
"
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"minimum": 1,
|
|
263
|
-
"description": "Initial soft cap for tool-using turns per user prompt."
|
|
264
|
-
},
|
|
265
|
-
"nudgeEveryTurns": {
|
|
266
|
-
"type": "integer",
|
|
267
|
-
"default": 5,
|
|
268
|
-
"minimum": 1,
|
|
269
|
-
"description": "Tool-using turn interval between turn-budget hints."
|
|
270
|
-
},
|
|
271
|
-
"softCapIncrement": {
|
|
272
|
-
"type": "integer",
|
|
273
|
-
"default": 10,
|
|
274
|
-
"minimum": 1,
|
|
275
|
-
"description": "Turns added when the soft cap is reached."
|
|
254
|
+
"disabled": {
|
|
255
|
+
"type": "array",
|
|
256
|
+
"items": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"minLength": 1
|
|
259
|
+
},
|
|
260
|
+
"default": [],
|
|
261
|
+
"description": "Agent names unavailable for delegation."
|
|
276
262
|
}
|
|
277
263
|
},
|
|
278
264
|
"additionalProperties": false
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ContextEvent } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type ContextMessage = ContextEvent["messages"][number];
|
|
4
|
+
|
|
5
|
+
export const CONTEXT_PROJECTION_TYPE = "tau.context.projection";
|
|
6
|
+
|
|
7
|
+
export function isContextProjectionMessage(message: ContextMessage): boolean {
|
|
8
|
+
return message.role === "custom" && message.customType === CONTEXT_PROJECTION_TYPE;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isLegacyContextMessage(message: ContextMessage): boolean {
|
|
12
|
+
if (message.role !== "custom") return false;
|
|
13
|
+
if (message.customType !== "tau.injected-context" && message.customType !== "tau.autoread") return false;
|
|
14
|
+
return (
|
|
15
|
+
message.details !== undefined &&
|
|
16
|
+
typeof message.details === "object" &&
|
|
17
|
+
(message.details as Record<string, unknown>).source === "context"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
export const INJECTED_CONTEXT_TYPE = "tau.injected-context";
|
|
4
4
|
|
|
5
5
|
export interface InjectedContextDetails {
|
|
6
|
-
source: "review"
|
|
6
|
+
source: "review";
|
|
7
7
|
title?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -61,7 +61,7 @@ export function getBranchInjectedContexts(entries: readonly SessionEntry[]): Inj
|
|
|
61
61
|
function readDetails(value: unknown): InjectedContextDetails | undefined {
|
|
62
62
|
if (!value || typeof value !== "object") return undefined;
|
|
63
63
|
const record = value as Record<string, unknown>;
|
|
64
|
-
if (record.source !== "review"
|
|
64
|
+
if (record.source !== "review") return undefined;
|
|
65
65
|
return {
|
|
66
66
|
source: record.source,
|
|
67
67
|
...(typeof record.title === "string" ? { title: record.title } : {}),
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAgentSession,
|
|
3
|
+
DefaultResourceLoader,
|
|
4
|
+
getAgentDir,
|
|
5
|
+
ModelRuntime,
|
|
6
|
+
SessionManager,
|
|
7
|
+
type AgentSession,
|
|
8
|
+
type ExtensionContext,
|
|
9
|
+
type ToolDefinition,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
type IsolatedSessionThinkingLevel = NonNullable<ExtensionContext["thinkingLevel"]>;
|
|
13
|
+
|
|
14
|
+
type SelectedModel = NonNullable<ExtensionContext["model"]>;
|
|
15
|
+
type SelectedProvider = NonNullable<ReturnType<ExtensionContext["modelRegistry"]["getProvider"]>>;
|
|
16
|
+
|
|
17
|
+
export interface IsolatedSessionInputs {
|
|
18
|
+
label: string;
|
|
19
|
+
extensionPaths: readonly string[];
|
|
20
|
+
cwd: string;
|
|
21
|
+
model: SelectedModel;
|
|
22
|
+
provider: SelectedProvider;
|
|
23
|
+
runtimeApiKey: string | undefined;
|
|
24
|
+
thinkingLevel: IsolatedSessionThinkingLevel;
|
|
25
|
+
tools: readonly string[];
|
|
26
|
+
customTools: readonly ToolDefinition[];
|
|
27
|
+
bindTarget: { mode: "print" } | { mode: "tui"; uiContext: ExtensionContext["ui"] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface IsolatedSessionResource {
|
|
31
|
+
readonly session: AgentSession;
|
|
32
|
+
dispose(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function resolveIsolatedSessionModel(options: {
|
|
36
|
+
label: string;
|
|
37
|
+
preferredModel: string | undefined;
|
|
38
|
+
preferredThinkingLevel: IsolatedSessionThinkingLevel | undefined;
|
|
39
|
+
usePreferredThinkingAfterModelFallback: boolean;
|
|
40
|
+
ctx: ExtensionContext;
|
|
41
|
+
parentThinkingLevel: IsolatedSessionThinkingLevel;
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
onWarning?: (warning: string) => void;
|
|
44
|
+
}): Promise<Pick<IsolatedSessionInputs, "model" | "provider" | "runtimeApiKey" | "thinkingLevel">> {
|
|
45
|
+
const { ctx, signal, onWarning } = options;
|
|
46
|
+
let model = ctx.model;
|
|
47
|
+
let thinkingLevel = options.parentThinkingLevel;
|
|
48
|
+
let preferredModelSelected = false;
|
|
49
|
+
let selectedAuth: Awaited<ReturnType<ExtensionContext["modelRegistry"]["getApiKeyAndHeaders"]>> | undefined;
|
|
50
|
+
if (options.preferredModel) {
|
|
51
|
+
const separator = options.preferredModel.indexOf("/");
|
|
52
|
+
const configured = ctx.modelRegistry.find(
|
|
53
|
+
options.preferredModel.slice(0, separator),
|
|
54
|
+
options.preferredModel.slice(separator + 1),
|
|
55
|
+
);
|
|
56
|
+
if (!configured) onWarning?.(`model ${options.preferredModel} is unavailable; using parent model`);
|
|
57
|
+
else {
|
|
58
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(configured);
|
|
59
|
+
if (!auth.ok) onWarning?.(`model ${options.preferredModel} is unavailable: ${auth.error}; using parent model`);
|
|
60
|
+
else {
|
|
61
|
+
model = configured;
|
|
62
|
+
selectedAuth = auth;
|
|
63
|
+
preferredModelSelected = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (options.preferredThinkingLevel && (preferredModelSelected || options.usePreferredThinkingAfterModelFallback)) {
|
|
68
|
+
const preferred = options.preferredThinkingLevel;
|
|
69
|
+
const mapped = model?.thinkingLevelMap?.[preferred];
|
|
70
|
+
const unsupported =
|
|
71
|
+
!model?.reasoning ||
|
|
72
|
+
mapped === null ||
|
|
73
|
+
((preferred === "xhigh" || preferred === "max") && mapped === undefined);
|
|
74
|
+
if (unsupported)
|
|
75
|
+
onWarning?.(`thinking ${preferred} is unavailable for the selected model; using parent thinking`);
|
|
76
|
+
else thinkingLevel = preferred;
|
|
77
|
+
}
|
|
78
|
+
if (!model) throw new Error(`${options.label} startup failed: parent has no model`);
|
|
79
|
+
const auth = selectedAuth ?? (await ctx.modelRegistry.getApiKeyAndHeaders(model));
|
|
80
|
+
if (!auth.ok) throw new Error(`${options.label} startup failed: ${auth.error}`);
|
|
81
|
+
const provider = ctx.modelRegistry.getProvider(model.provider);
|
|
82
|
+
if (!provider) throw new Error(`${options.label} startup failed: provider ${model.provider} is unavailable`);
|
|
83
|
+
if (signal.aborted) throw new Error(`${options.label} startup aborted`);
|
|
84
|
+
return {
|
|
85
|
+
model,
|
|
86
|
+
provider,
|
|
87
|
+
runtimeApiKey:
|
|
88
|
+
auth.apiKey && provider.auth.apiKey && !ctx.modelRegistry.isUsingOAuth(model) ? auth.apiKey : undefined,
|
|
89
|
+
thinkingLevel,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function createIsolatedSessionResource(
|
|
94
|
+
inputs: IsolatedSessionInputs,
|
|
95
|
+
signal: AbortSignal,
|
|
96
|
+
): Promise<IsolatedSessionResource> {
|
|
97
|
+
let session: AgentSession | undefined;
|
|
98
|
+
try {
|
|
99
|
+
if (signal.aborted) throw new Error(`${inputs.label} startup aborted`);
|
|
100
|
+
const modelRuntime = await ModelRuntime.create();
|
|
101
|
+
modelRuntime.registerNativeProvider(inputs.provider);
|
|
102
|
+
if (inputs.runtimeApiKey !== undefined)
|
|
103
|
+
await modelRuntime.setRuntimeApiKey(inputs.model.provider, inputs.runtimeApiKey, { allowNetwork: false });
|
|
104
|
+
if (signal.aborted) throw new Error(`${inputs.label} startup aborted`);
|
|
105
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
106
|
+
cwd: inputs.cwd,
|
|
107
|
+
agentDir: getAgentDir(),
|
|
108
|
+
noExtensions: true,
|
|
109
|
+
additionalExtensionPaths: [...inputs.extensionPaths],
|
|
110
|
+
});
|
|
111
|
+
await resourceLoader.reload();
|
|
112
|
+
if (signal.aborted) throw new Error(`${inputs.label} startup aborted`);
|
|
113
|
+
const created = await createAgentSession({
|
|
114
|
+
cwd: inputs.cwd,
|
|
115
|
+
model: inputs.model,
|
|
116
|
+
modelRuntime,
|
|
117
|
+
thinkingLevel: inputs.thinkingLevel,
|
|
118
|
+
tools: [...inputs.tools],
|
|
119
|
+
excludeTools: ["subagent"],
|
|
120
|
+
resourceLoader,
|
|
121
|
+
customTools: [...inputs.customTools],
|
|
122
|
+
sessionManager: SessionManager.inMemory(inputs.cwd),
|
|
123
|
+
});
|
|
124
|
+
session = created.session;
|
|
125
|
+
if (signal.aborted) throw new Error(`${inputs.label} startup aborted`);
|
|
126
|
+
await session.bindExtensions(inputs.bindTarget);
|
|
127
|
+
if (signal.aborted) throw new Error(`${inputs.label} startup aborted`);
|
|
128
|
+
const active = session.getActiveToolNames().sort();
|
|
129
|
+
const expected = [...inputs.tools].sort();
|
|
130
|
+
if (active.join("\0") !== expected.join("\0") || active.includes("subagent")) {
|
|
131
|
+
const missing = expected.filter((tool) => !active.includes(tool));
|
|
132
|
+
throw new Error(
|
|
133
|
+
`${inputs.label} startup failed: unavailable tools: ${missing.join(", ") || "active tool mismatch"}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
let disposed = false;
|
|
137
|
+
return {
|
|
138
|
+
session,
|
|
139
|
+
async dispose() {
|
|
140
|
+
if (disposed) return;
|
|
141
|
+
disposed = true;
|
|
142
|
+
if (session?.isStreaming) await session.abort().catch(() => undefined);
|
|
143
|
+
session?.dispose();
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (session?.isStreaming) await session.abort().catch(() => undefined);
|
|
148
|
+
session?.dispose();
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|