@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
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { AgentToolResult, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { Marker } from "@shanepadgett/tau-tui";
|
|
4
|
+
import { formatToolRowTitle, type ToolRowStateStore } from "../../shared/tool-row-state.ts";
|
|
5
|
+
import type { WorkingMemoryInput } from "./checkpoint.ts";
|
|
6
|
+
import { parseWorkingMemoryDetails } from "./state.ts";
|
|
7
|
+
|
|
8
|
+
const MAX_ITEMS = 20;
|
|
9
|
+
const MAX_LINE = 240;
|
|
10
|
+
const MAX_TEXT = 4_000;
|
|
11
|
+
|
|
12
|
+
export type WorkingMemoryNudgeDetails =
|
|
13
|
+
| {
|
|
14
|
+
v: 1;
|
|
15
|
+
kind: "automatic";
|
|
16
|
+
tokens: number;
|
|
17
|
+
boundaryTokens: number;
|
|
18
|
+
reminder: number;
|
|
19
|
+
tier: number;
|
|
20
|
+
tierCount: number;
|
|
21
|
+
anchorToolCallId: string | null;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
v: 1;
|
|
25
|
+
kind: "manual";
|
|
26
|
+
tokens: null;
|
|
27
|
+
boundaryTokens: null;
|
|
28
|
+
reminder: null;
|
|
29
|
+
tier: null;
|
|
30
|
+
tierCount: null;
|
|
31
|
+
anchorToolCallId: string | null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function parseWorkingMemoryNudge(value: unknown): WorkingMemoryNudgeDetails | undefined {
|
|
35
|
+
if (!isRecord(value) || value.v !== 1 || (value.kind !== "automatic" && value.kind !== "manual")) return undefined;
|
|
36
|
+
if (value.anchorToolCallId !== null && !nonempty(value.anchorToolCallId)) return undefined;
|
|
37
|
+
if (value.kind === "manual") {
|
|
38
|
+
if (
|
|
39
|
+
value.tokens !== null ||
|
|
40
|
+
value.boundaryTokens !== null ||
|
|
41
|
+
value.reminder !== null ||
|
|
42
|
+
value.tier !== null ||
|
|
43
|
+
value.tierCount !== null
|
|
44
|
+
) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
v: 1,
|
|
49
|
+
kind: "manual",
|
|
50
|
+
tokens: null,
|
|
51
|
+
boundaryTokens: null,
|
|
52
|
+
reminder: null,
|
|
53
|
+
tier: null,
|
|
54
|
+
tierCount: null,
|
|
55
|
+
anchorToolCallId: value.anchorToolCallId,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (
|
|
59
|
+
!count(value.tokens, 0) ||
|
|
60
|
+
!count(value.boundaryTokens, 1) ||
|
|
61
|
+
!count(value.reminder, 1) ||
|
|
62
|
+
!count(value.tier, 1, 5) ||
|
|
63
|
+
!count(value.tierCount, 1, 5) ||
|
|
64
|
+
value.boundaryTokens > value.tokens ||
|
|
65
|
+
value.tier > value.tierCount ||
|
|
66
|
+
value.tier !== Math.min(value.reminder, value.tierCount)
|
|
67
|
+
) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
v: 1,
|
|
72
|
+
kind: "automatic",
|
|
73
|
+
tokens: value.tokens,
|
|
74
|
+
boundaryTokens: value.boundaryTokens,
|
|
75
|
+
reminder: value.reminder,
|
|
76
|
+
tier: value.tier,
|
|
77
|
+
tierCount: value.tierCount,
|
|
78
|
+
anchorToolCallId: value.anchorToolCallId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function renderWorkingMemoryNudge(details: unknown, theme: Theme): Marker | undefined {
|
|
83
|
+
const parsed = parseWorkingMemoryNudge(details);
|
|
84
|
+
if (!parsed) return undefined;
|
|
85
|
+
return new Marker({
|
|
86
|
+
theme,
|
|
87
|
+
state: "muted",
|
|
88
|
+
label: "Memory:",
|
|
89
|
+
parts:
|
|
90
|
+
parsed.kind === "manual" ? ["Reassessment requested."] : [formatTokens(parsed.boundaryTokens), "Reassess."],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function renderWorkingMemoryCall(
|
|
95
|
+
args: WorkingMemoryInput,
|
|
96
|
+
theme: Theme,
|
|
97
|
+
context: {
|
|
98
|
+
rowState: ToolRowStateStore;
|
|
99
|
+
rowId: string;
|
|
100
|
+
invalidate: () => void;
|
|
101
|
+
lastComponent: unknown;
|
|
102
|
+
},
|
|
103
|
+
): Text {
|
|
104
|
+
context.rowState.watch(context.rowId, context.invalidate);
|
|
105
|
+
const component = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
106
|
+
const kept = Array.isArray(args.keep) ? args.keep.length : 0;
|
|
107
|
+
const outlined = Array.isArray(args.outlineFiles) ? args.outlineFiles.length : 0;
|
|
108
|
+
const deferred = Array.isArray(args.deferFiles) ? args.deferFiles.length : 0;
|
|
109
|
+
component.setText(
|
|
110
|
+
`${formatToolRowTitle(context.rowState, context.rowId, "working_memory", theme)} ${theme.fg("muted", `${kept} kept · ${outlined} outline · ${deferred} deferred`)}`,
|
|
111
|
+
);
|
|
112
|
+
return component;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function renderWorkingMemoryResult(
|
|
116
|
+
result: AgentToolResult<unknown>,
|
|
117
|
+
expanded: boolean,
|
|
118
|
+
theme: Theme,
|
|
119
|
+
lastComponent: unknown,
|
|
120
|
+
): Text {
|
|
121
|
+
const component = lastComponent instanceof Text ? lastComponent : new Text("", 0, 0);
|
|
122
|
+
const details = parseWorkingMemoryDetails(result.details);
|
|
123
|
+
if (!details) {
|
|
124
|
+
component.setText(
|
|
125
|
+
theme.fg("warning", bounded(firstText(result) || "working_memory returned invalid details", 1_000)),
|
|
126
|
+
);
|
|
127
|
+
return component;
|
|
128
|
+
}
|
|
129
|
+
let text = theme.fg(
|
|
130
|
+
details.warnings.length === 0 ? "success" : "warning",
|
|
131
|
+
`Checkpoint · kept ${details.retainedRefs.length} · outlined ${details.outlinedFiles.length} · deferred ${details.deferredFiles.length} · removed ${details.removedUnits}${details.warnings.length === 0 ? "" : ` · warnings ${details.warnings.length}`}`,
|
|
132
|
+
);
|
|
133
|
+
if (expanded) {
|
|
134
|
+
const lines = [
|
|
135
|
+
bounded(firstText(result), 1_500),
|
|
136
|
+
...details.retainedLabels.map((item) => `${item.ref} ${item.label}: ${item.preview}`),
|
|
137
|
+
...details.outlinedFiles.map((file) => `outlined: ${file.path}`),
|
|
138
|
+
...details.deferredFiles.map((file) => `deferred: ${file.path} — ${file.reason}; when ${file.relevantWhen}`),
|
|
139
|
+
...details.warnings.map((warning) => `warning: ${warning}`),
|
|
140
|
+
].filter(Boolean);
|
|
141
|
+
let remaining = MAX_TEXT - visibleWidth(text);
|
|
142
|
+
for (const line of lines.slice(0, MAX_ITEMS)) {
|
|
143
|
+
if (remaining <= 1) break;
|
|
144
|
+
const styled = theme.fg("dim", bounded(line, MAX_LINE));
|
|
145
|
+
text += `\n${truncateToWidth(styled, remaining - 1, "…")}`;
|
|
146
|
+
remaining -= 1 + Math.min(visibleWidth(styled), remaining - 1);
|
|
147
|
+
}
|
|
148
|
+
if (lines.length > MAX_ITEMS && remaining > 10)
|
|
149
|
+
text += `\n${theme.fg("dim", `… ${lines.length - MAX_ITEMS} more`)}`;
|
|
150
|
+
}
|
|
151
|
+
component.setText(text);
|
|
152
|
+
return component;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function firstText(result: AgentToolResult<unknown>): string {
|
|
156
|
+
for (const part of result.content) if (part.type === "text") return part.text;
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function bounded(text: string, maximum: number): string {
|
|
161
|
+
return text.length <= maximum ? text : `${text.slice(0, maximum - 1)}…`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatTokens(tokens: number): string {
|
|
165
|
+
return tokens < 1_000 ? `${tokens}` : `${Math.round(tokens / 1_000)}k`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function count(value: unknown, minimum: number, maximum = Number.MAX_SAFE_INTEGER): value is number {
|
|
169
|
+
return Number.isSafeInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function nonempty(value: unknown): value is string {
|
|
173
|
+
return typeof value === "string" && value.length > 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
177
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { defineTauExtensionSettings } from "../../shared/settings/define.ts";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_NUDGE_INSTRUCTIONS: [string, ...string[]] = [
|
|
5
|
+
"Reassess working memory. Continue coherent exploration when its evidence remains useful; otherwise prune known dead ends, obsolete outputs, and other context with no expected value.",
|
|
6
|
+
"Context is materially larger. Prune stale or bulky irrelevant evidence when safe, but keep active working evidence that would otherwise need to be reread.",
|
|
7
|
+
"Strongly reassess before more broad work. Remove accumulated waste and carry useful information at cheapest sufficient fidelity without scrubbing the active working set.",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export default defineTauExtensionSettings({
|
|
11
|
+
key: "workingMemory",
|
|
12
|
+
defaults: {
|
|
13
|
+
enabled: true as boolean,
|
|
14
|
+
nudgeEveryTokens: 40_000 as number,
|
|
15
|
+
nudgeInstructions: DEFAULT_NUDGE_INSTRUCTIONS,
|
|
16
|
+
},
|
|
17
|
+
schema: Type.Object(
|
|
18
|
+
{
|
|
19
|
+
enabled: Type.Optional(Type.Boolean({ default: true, description: "Enable working-memory checkpoints." })),
|
|
20
|
+
nudgeEveryTokens: Type.Optional(
|
|
21
|
+
Type.Integer({
|
|
22
|
+
default: 40_000,
|
|
23
|
+
minimum: 1,
|
|
24
|
+
description: "Active-context token interval between advisory working-memory reminders.",
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
nudgeInstructions: Type.Optional(
|
|
28
|
+
Type.Array(Type.String({ minLength: 1, maxLength: 2_000 }), {
|
|
29
|
+
default: DEFAULT_NUDGE_INSTRUCTIONS,
|
|
30
|
+
minItems: 1,
|
|
31
|
+
maxItems: 5,
|
|
32
|
+
description: "Ordered advisory working-memory instructions. Later reminders repeat final instruction.",
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{ additionalProperties: false },
|
|
37
|
+
),
|
|
38
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export const WORKING_MEMORY_TOOL = "working_memory";
|
|
4
|
+
|
|
5
|
+
export interface DeferredFile {
|
|
6
|
+
path: string;
|
|
7
|
+
reason: string;
|
|
8
|
+
relevantWhen: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RetainedLabel {
|
|
12
|
+
ref: string;
|
|
13
|
+
label: string;
|
|
14
|
+
preview: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WorkingMemoryCheckpointDetailsV1 {
|
|
18
|
+
v: 1;
|
|
19
|
+
anchorToolCallId: string;
|
|
20
|
+
retainedRefs: string[];
|
|
21
|
+
retainedLabels: RetainedLabel[];
|
|
22
|
+
prunedRowIds: string[];
|
|
23
|
+
outlinedFiles: Array<{ path: string; rowId: string }>;
|
|
24
|
+
deferredFiles: DeferredFile[];
|
|
25
|
+
removedUnits: number;
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ActiveWorkingMemoryState {
|
|
30
|
+
latestAnchorToolCallId: string | undefined;
|
|
31
|
+
retainedRefs: readonly string[];
|
|
32
|
+
prunedRowIds: ReadonlySet<string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseWorkingMemoryDetails(value: unknown): WorkingMemoryCheckpointDetailsV1 | undefined {
|
|
36
|
+
if (!isRecord(value) || value.v !== 1 || !nonempty(value.anchorToolCallId)) return undefined;
|
|
37
|
+
const keys = [
|
|
38
|
+
"v",
|
|
39
|
+
"anchorToolCallId",
|
|
40
|
+
"retainedRefs",
|
|
41
|
+
"retainedLabels",
|
|
42
|
+
"prunedRowIds",
|
|
43
|
+
"outlinedFiles",
|
|
44
|
+
"deferredFiles",
|
|
45
|
+
"removedUnits",
|
|
46
|
+
"warnings",
|
|
47
|
+
];
|
|
48
|
+
if (Object.keys(value).length !== keys.length || !keys.every((key) => Object.hasOwn(value, key))) return undefined;
|
|
49
|
+
const retainedRefs = uniqueStrings(value.retainedRefs);
|
|
50
|
+
const prunedRowIds = uniqueStrings(value.prunedRowIds);
|
|
51
|
+
const warnings = strings(value.warnings);
|
|
52
|
+
const retainedLabels = parseRetainedLabels(value.retainedLabels);
|
|
53
|
+
const outlinedFiles = parseOutlinedFiles(value.outlinedFiles);
|
|
54
|
+
const deferredFiles = parseDeferredFiles(value.deferredFiles);
|
|
55
|
+
if (
|
|
56
|
+
!retainedRefs ||
|
|
57
|
+
!prunedRowIds ||
|
|
58
|
+
!warnings ||
|
|
59
|
+
!retainedLabels ||
|
|
60
|
+
retainedLabels.length !== retainedRefs.length ||
|
|
61
|
+
retainedLabels.some((label, index) => label.ref !== retainedRefs[index]) ||
|
|
62
|
+
!outlinedFiles ||
|
|
63
|
+
!deferredFiles ||
|
|
64
|
+
!Number.isSafeInteger(value.removedUnits) ||
|
|
65
|
+
(value.removedUnits as number) < 0
|
|
66
|
+
) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
v: 1,
|
|
71
|
+
anchorToolCallId: value.anchorToolCallId,
|
|
72
|
+
retainedRefs,
|
|
73
|
+
retainedLabels,
|
|
74
|
+
prunedRowIds,
|
|
75
|
+
outlinedFiles,
|
|
76
|
+
deferredFiles,
|
|
77
|
+
removedUnits: value.removedUnits as number,
|
|
78
|
+
warnings,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function replayWorkingMemoryState(branch: readonly SessionEntry[], enabled: boolean): ActiveWorkingMemoryState {
|
|
83
|
+
let prunedRowIds = new Set<string>();
|
|
84
|
+
let latestAnchorToolCallId: string | undefined;
|
|
85
|
+
let retainedRefs: readonly string[] = [];
|
|
86
|
+
if (enabled) {
|
|
87
|
+
for (const entry of branch) {
|
|
88
|
+
if (entry.type !== "message" || entry.message.role !== "toolResult") continue;
|
|
89
|
+
if (entry.message.toolName !== WORKING_MEMORY_TOOL) continue;
|
|
90
|
+
const details = parseWorkingMemoryDetails(entry.message.details);
|
|
91
|
+
if (!details || details.anchorToolCallId !== entry.message.toolCallId) continue;
|
|
92
|
+
prunedRowIds = new Set(details.prunedRowIds);
|
|
93
|
+
latestAnchorToolCallId = details.anchorToolCallId;
|
|
94
|
+
retainedRefs = details.retainedRefs;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { latestAnchorToolCallId, retainedRefs, prunedRowIds };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseRetainedLabels(value: unknown): RetainedLabel[] | undefined {
|
|
101
|
+
if (!Array.isArray(value)) return undefined;
|
|
102
|
+
const labels: RetainedLabel[] = [];
|
|
103
|
+
for (const item of value) {
|
|
104
|
+
if (!isRecord(item) || !nonempty(item.ref) || !nonempty(item.label) || typeof item.preview !== "string") {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
labels.push({ ref: item.ref, label: item.label, preview: item.preview });
|
|
108
|
+
}
|
|
109
|
+
return labels;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseOutlinedFiles(value: unknown): Array<{ path: string; rowId: string }> | undefined {
|
|
113
|
+
if (!Array.isArray(value)) return undefined;
|
|
114
|
+
const files: Array<{ path: string; rowId: string }> = [];
|
|
115
|
+
for (const item of value) {
|
|
116
|
+
if (!isRecord(item) || !nonempty(item.path) || !nonempty(item.rowId)) return undefined;
|
|
117
|
+
files.push({ path: item.path, rowId: item.rowId });
|
|
118
|
+
}
|
|
119
|
+
return files;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseDeferredFiles(value: unknown): DeferredFile[] | undefined {
|
|
123
|
+
if (!Array.isArray(value)) return undefined;
|
|
124
|
+
const files: DeferredFile[] = [];
|
|
125
|
+
for (const item of value) {
|
|
126
|
+
if (!isRecord(item) || !nonempty(item.path) || !nonempty(item.reason) || !nonempty(item.relevantWhen)) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
files.push({ path: item.path, reason: item.reason, relevantWhen: item.relevantWhen });
|
|
130
|
+
}
|
|
131
|
+
return files;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function uniqueStrings(value: unknown): string[] | undefined {
|
|
135
|
+
const parsed = strings(value, true);
|
|
136
|
+
if (!parsed || new Set(parsed).size !== parsed.length) return undefined;
|
|
137
|
+
return parsed;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function strings(value: unknown, requireNonempty = false): string[] | undefined {
|
|
141
|
+
if (!Array.isArray(value)) return undefined;
|
|
142
|
+
if (!value.every((item) => typeof item === "string" && (!requireNonempty || item.length > 0))) return undefined;
|
|
143
|
+
return [...value];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function nonempty(value: unknown): value is string {
|
|
147
|
+
return typeof value === "string" && value.length > 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
151
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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.30.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
|
@@ -53,39 +53,6 @@
|
|
|
53
53
|
},
|
|
54
54
|
"additionalProperties": false
|
|
55
55
|
},
|
|
56
|
-
"contextPruning": {
|
|
57
|
-
"type": "object",
|
|
58
|
-
"properties": {
|
|
59
|
-
"enabled": {
|
|
60
|
-
"type": "boolean",
|
|
61
|
-
"default": true,
|
|
62
|
-
"description": "Enable context pruning."
|
|
63
|
-
},
|
|
64
|
-
"nudgeEveryTokens": {
|
|
65
|
-
"type": "integer",
|
|
66
|
-
"default": 30000,
|
|
67
|
-
"minimum": 1,
|
|
68
|
-
"description": "Active-context token interval between automatic pruning hints."
|
|
69
|
-
},
|
|
70
|
-
"nudgeInstructions": {
|
|
71
|
-
"type": "array",
|
|
72
|
-
"items": {
|
|
73
|
-
"type": "string",
|
|
74
|
-
"minLength": 1,
|
|
75
|
-
"maxLength": 2000
|
|
76
|
-
},
|
|
77
|
-
"default": [
|
|
78
|
-
"No prune is required yet unless broad exploration has converged or substantial evidence is already irrelevant. Continue coherent work.",
|
|
79
|
-
"Move toward a pruning point now. Finish the current coherent step, then prune before starting another broad exploration. Managed context is materially increasing model cost.",
|
|
80
|
-
"Prune now before further tool work. Continuing with stale managed context is wasting money."
|
|
81
|
-
],
|
|
82
|
-
"minItems": 1,
|
|
83
|
-
"maxItems": 5,
|
|
84
|
-
"description": "Ordered automatic pruning instructions. Later reminders repeat the final instruction, and the final tier requires an anchor before further tool work."
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
"additionalProperties": false
|
|
88
|
-
},
|
|
89
56
|
"explore": {
|
|
90
57
|
"type": "object",
|
|
91
58
|
"properties": {
|
|
@@ -114,12 +81,6 @@
|
|
|
114
81
|
"default": 200,
|
|
115
82
|
"minimum": 1,
|
|
116
83
|
"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
84
|
}
|
|
124
85
|
},
|
|
125
86
|
"additionalProperties": false
|
|
@@ -287,31 +248,50 @@
|
|
|
287
248
|
},
|
|
288
249
|
"additionalProperties": false
|
|
289
250
|
},
|
|
290
|
-
"
|
|
251
|
+
"subagent": {
|
|
252
|
+
"type": "object",
|
|
253
|
+
"properties": {
|
|
254
|
+
"disabled": {
|
|
255
|
+
"type": "array",
|
|
256
|
+
"items": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"minLength": 1
|
|
259
|
+
},
|
|
260
|
+
"default": [],
|
|
261
|
+
"description": "Agent names unavailable for delegation."
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"additionalProperties": false
|
|
265
|
+
},
|
|
266
|
+
"workingMemory": {
|
|
291
267
|
"type": "object",
|
|
292
268
|
"properties": {
|
|
293
269
|
"enabled": {
|
|
294
270
|
"type": "boolean",
|
|
295
271
|
"default": true,
|
|
296
|
-
"description": "Enable
|
|
272
|
+
"description": "Enable working-memory checkpoints."
|
|
297
273
|
},
|
|
298
|
-
"
|
|
274
|
+
"nudgeEveryTokens": {
|
|
299
275
|
"type": "integer",
|
|
300
|
-
"default":
|
|
276
|
+
"default": 40000,
|
|
301
277
|
"minimum": 1,
|
|
302
|
-
"description": "
|
|
278
|
+
"description": "Active-context token interval between advisory working-memory reminders."
|
|
303
279
|
},
|
|
304
|
-
"
|
|
305
|
-
"type": "
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
"
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
280
|
+
"nudgeInstructions": {
|
|
281
|
+
"type": "array",
|
|
282
|
+
"items": {
|
|
283
|
+
"type": "string",
|
|
284
|
+
"minLength": 1,
|
|
285
|
+
"maxLength": 2000
|
|
286
|
+
},
|
|
287
|
+
"default": [
|
|
288
|
+
"Reassess working memory. Continue coherent exploration when its evidence remains useful; otherwise prune known dead ends, obsolete outputs, and other context with no expected value.",
|
|
289
|
+
"Context is materially larger. Prune stale or bulky irrelevant evidence when safe, but keep active working evidence that would otherwise need to be reread.",
|
|
290
|
+
"Strongly reassess before more broad work. Remove accumulated waste and carry useful information at cheapest sufficient fidelity without scrubbing the active working set."
|
|
291
|
+
],
|
|
292
|
+
"minItems": 1,
|
|
293
|
+
"maxItems": 5,
|
|
294
|
+
"description": "Ordered advisory working-memory instructions. Later reminders repeat final instruction."
|
|
315
295
|
}
|
|
316
296
|
},
|
|
317
297
|
"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
|
+
}
|
package/shared/events.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { OutlineInjectionRequest, OutlineInjectionResponse } from "./outline-injection.js";
|
|
2
3
|
import type { ToolRowVisualState } from "./tool-row-state.js";
|
|
3
4
|
|
|
4
5
|
export type TauAgentEvents = {
|
|
@@ -36,6 +37,10 @@ export type TauAgentEvents = {
|
|
|
36
37
|
batchId: string;
|
|
37
38
|
files: Array<{ path: string }>;
|
|
38
39
|
};
|
|
40
|
+
"tau:outline-injection.requested": {
|
|
41
|
+
request: OutlineInjectionRequest;
|
|
42
|
+
accept(response: Promise<OutlineInjectionResponse>): void;
|
|
43
|
+
};
|
|
39
44
|
"tau:footer-item": {
|
|
40
45
|
id: string;
|
|
41
46
|
text?: string;
|
|
@@ -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 } : {}),
|