@shanepadgett/tau-agent 0.42.3 → 0.44.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/README.md +0 -1
- package/docs/context.md +1 -1
- package/docs/extending-tau-agent.md +1 -2
- package/extensions/attention/README.md +1 -1
- package/extensions/cache-diagnostics/index.ts +4 -0
- package/extensions/context/README.md +1 -24
- package/extensions/context/definitions.ts +1 -52
- package/extensions/context/index.ts +1 -150
- package/extensions/context/panel.ts +0 -72
- package/extensions/explore/guidance.ts +9 -1
- package/extensions/footer/README.md +1 -1
- package/extensions/footer/index.ts +3 -25
- package/extensions/qna/choice-question-body.ts +7 -2
- package/extensions/run-summary/README.md +1 -1
- package/extensions/run-summary/index.ts +18 -25
- package/extensions/runtime-context/README.md +1 -1
- package/extensions/runtime-context/context.ts +1 -1
- package/extensions/runtime-context/index.ts +10 -16
- package/extensions/script-runner/index.ts +21 -29
- package/extensions/silent-command-runner/README.md +0 -2
- package/extensions/silent-command-runner/index.ts +12 -41
- package/extensions/soul/README.md +5 -8
- package/extensions/soul/context.ts +115 -0
- package/extensions/soul/index.ts +141 -7
- package/extensions/soul/prompt.ts +47 -102
- package/extensions/soul/state.ts +114 -0
- package/extensions/soul/tools.ts +31 -0
- package/extensions/tau-help/help.md +6 -18
- package/extensions/tau-help/index.ts +10 -4
- package/extensions/tool-approval/README.md +4 -2
- package/extensions/tool-approval/index.ts +141 -53
- package/extensions/tool-approval/panel.ts +153 -0
- package/extensions/tool-loader/README.md +1 -1
- package/extensions/tool-loader/index.ts +91 -22
- package/package.json +2 -2
- package/schemas/tau.schema.json +0 -104
- package/shared/bounded-text-result.ts +0 -1
- package/shared/events.ts +19 -15
- package/shared/isolated-session.ts +1 -2
- package/shared/model-effort.ts +15 -21
- package/shared/prompt-contributions.ts +24 -0
- package/src/index.ts +1 -1
- package/docs/subagents.md +0 -92
- package/extensions/auto-compact/README.md +0 -9
- package/extensions/auto-compact/index.ts +0 -122
- package/extensions/auto-compact/settings.ts +0 -30
- package/extensions/context/settings.ts +0 -62
- package/extensions/context/sync.ts +0 -276
- package/extensions/context/validation.ts +0 -88
- package/extensions/effort/README.md +0 -7
- package/extensions/effort/index.ts +0 -134
- package/extensions/effort/state.ts +0 -18
- package/extensions/qna/inline-editor-row.ts +0 -56
- package/extensions/soul/overseer.ts +0 -273
- package/extensions/soul/settings.ts +0 -34
- package/extensions/subagent/README.md +0 -67
- package/extensions/subagent/agents/context-sync.md +0 -244
- package/extensions/subagent/agents/dormant/generalist.md +0 -33
- package/extensions/subagent/agents/scout.md +0 -104
- package/extensions/subagent/agents/web-research.md +0 -101
- package/extensions/subagent/agents.ts +0 -261
- package/extensions/subagent/cmux-dashboard.ts +0 -495
- package/extensions/subagent/index.ts +0 -425
- package/extensions/subagent/panel.ts +0 -124
- package/extensions/subagent/render.ts +0 -74
- package/extensions/subagent/resume.ts +0 -78
- package/extensions/subagent/run.ts +0 -641
- package/extensions/subagent/runtime.ts +0 -1296
- package/extensions/subagent/session-resource.ts +0 -61
- package/extensions/subagent/settings.ts +0 -18
package/README.md
CHANGED
|
@@ -45,5 +45,4 @@ pi -e .
|
|
|
45
45
|
|
|
46
46
|
- [Context management](./docs/context.md) — repository context structure and taxonomy
|
|
47
47
|
- [Extending Tau Agent](./docs/extending-tau-agent.md) — public events and integration
|
|
48
|
-
- [Subagents](./docs/subagents.md) — custom agent definitions
|
|
49
48
|
- [TUI](./docs/tui.md) — shared UI components
|
package/docs/context.md
CHANGED
|
@@ -73,4 +73,4 @@ Do not store raw line ranges in the catalog — they drift. `show` stores durabl
|
|
|
73
73
|
|
|
74
74
|
Inspect the existing catalog before placing new paths. Re-evaluate domain, concept, and entry boundaries after moves, ownership changes, or a coherent batch of new work; avoid stuffing paths into the nearest existing bucket.
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Edit the TOML files in `.pi/contexts` when code or work scopes change. `/context` reads the current catalog when opened; selecting an entry injects its current files and declaration slices. The catalog is not generated or checked after agent turns.
|
|
@@ -108,7 +108,6 @@ Only events documented in this file are public. Extensions run trusted in-proces
|
|
|
108
108
|
|
|
109
109
|
Related:
|
|
110
110
|
|
|
111
|
-
- [Custom subagents](./subagents.md)
|
|
112
111
|
- [TUI components](./tui.md)
|
|
113
112
|
|
|
114
113
|
## `tau:footer-item`
|
|
@@ -187,4 +186,4 @@ Fields:
|
|
|
187
186
|
- `rowId`: tool row id.
|
|
188
187
|
- `state`: optional visual state. Omit to clear.
|
|
189
188
|
|
|
190
|
-
Most extenders do not need this; it is for coordinating tool-row rendering with Tau's explore/patch
|
|
189
|
+
Most extenders do not need this; it is for coordinating tool-row rendering with Tau's explore/patch tooling.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Attention
|
|
2
2
|
|
|
3
|
-
Sends a terminal-driven attention notification when Tau is ready for input, finishes a compaction
|
|
3
|
+
Sends a terminal-driven attention notification when Tau is ready for input, finishes a compaction, or summarizes an abandoned branch during tree navigation.
|
|
4
4
|
|
|
5
5
|
## Behavior
|
|
6
6
|
|
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { appendFile, type FileHandle, mkdir, open, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getAgentDir, type BuildSystemPromptOptions, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { onTauEventImmediately } from "../../shared/events.ts";
|
|
5
6
|
|
|
6
7
|
const RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
7
8
|
const CACHE_NOISE_FLOOR = 1_024;
|
|
@@ -329,6 +330,9 @@ export default function cacheDiagnosticsExtension(pi: ExtensionAPI): void {
|
|
|
329
330
|
pi.on("before_agent_start", (event) => {
|
|
330
331
|
latestPromptState = fingerprintPromptState(event.systemPrompt, event.systemPromptOptions);
|
|
331
332
|
});
|
|
333
|
+
onTauEventImmediately(pi, "cache-diagnostics.prompt", "tau:prompt.snapshot", ({ text }) => {
|
|
334
|
+
if (latestPromptState) latestPromptState = { ...latestPromptState, systemPromptHash: hashJson(text).hash };
|
|
335
|
+
});
|
|
332
336
|
|
|
333
337
|
pi.on("turn_start", (event) => {
|
|
334
338
|
turnIndex = event.turnIndex;
|
|
@@ -6,30 +6,7 @@ See [Context management](../../docs/context.md) for catalog structure and taxono
|
|
|
6
6
|
|
|
7
7
|
Use `/context` to inject entries into the conversation. Selecting entries reads `read` paths in full, resolves `show` targets to current declaration slices, outlines `outline` paths, and adds one hidden note listing `references` and instructing the agent to treat the injected material as current. Injection happens once, immediately, before your next prompt; nothing is rebuilt on later model calls. Prefer complete reads for bounded work files; use `show` for thin contracts from larger neighbors; reserve `outline` for large or noisy files.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Sync surface is configurable:
|
|
12
|
-
|
|
13
|
-
- `sync.enabled` (default true) — master switch. Off: no `/context-sync`, parent cannot call `context-sync`, validation does not auto-run sync.
|
|
14
|
-
- `sync.automation` (default true) — when false with sync still enabled: manual `/context-sync` only (coding agent does not see context-sync). Validation auto-run still works if validation is enabled.
|
|
15
|
-
- `validation.enabled` (default false) — after agent turns, check membership and auto-run context-sync on failure (requires `sync.enabled`).
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"extensions": {
|
|
20
|
-
"context": {
|
|
21
|
-
"sync": {
|
|
22
|
-
"enabled": true,
|
|
23
|
-
"automation": true
|
|
24
|
-
},
|
|
25
|
-
"validation": {
|
|
26
|
-
"enabled": true,
|
|
27
|
-
"ignoreGlobs": ["generated/**"]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
```
|
|
9
|
+
Edit `.pi/contexts` files to maintain the catalog. Keep entries as work packs for recurring jobs; leave scratch files and plans out of the catalog. `/context` reads current catalog files when opened.
|
|
33
10
|
|
|
34
11
|
```toml
|
|
35
12
|
name = "Player"
|
|
@@ -2,7 +2,6 @@ import { access, readFile, readdir } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parse } from "smol-toml";
|
|
5
|
-
import { matchGlob } from "../../shared/glob.ts";
|
|
6
5
|
|
|
7
6
|
export type ContextShowView = "signature" | "signatureWithDocs" | "declaration" | "declarationWithImports";
|
|
8
7
|
|
|
@@ -27,26 +26,6 @@ export interface ContextEntry {
|
|
|
27
26
|
path: string;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
const CONTEXT_IGNORED_FILENAMES = new Set([
|
|
31
|
-
"bun.lock",
|
|
32
|
-
"bun.lockb",
|
|
33
|
-
"Cargo.lock",
|
|
34
|
-
"composer.lock",
|
|
35
|
-
"flake.lock",
|
|
36
|
-
"Gemfile.lock",
|
|
37
|
-
"go.sum",
|
|
38
|
-
"mix.lock",
|
|
39
|
-
"npm-shrinkwrap.json",
|
|
40
|
-
"package-lock.json",
|
|
41
|
-
"Package.resolved",
|
|
42
|
-
"Pipfile.lock",
|
|
43
|
-
"pnpm-lock.yaml",
|
|
44
|
-
"Podfile.lock",
|
|
45
|
-
"poetry.lock",
|
|
46
|
-
"pubspec.lock",
|
|
47
|
-
"uv.lock",
|
|
48
|
-
"yarn.lock",
|
|
49
|
-
]);
|
|
50
29
|
const CONTEXT_ENTRY_FIELDS = new Set(["description", "read", "show", "outline", "references"]);
|
|
51
30
|
const CONTEXT_SHOW_VIEWS = new Set<ContextShowView>([
|
|
52
31
|
"signature",
|
|
@@ -56,28 +35,7 @@ const CONTEXT_SHOW_VIEWS = new Set<ContextShowView>([
|
|
|
56
35
|
]);
|
|
57
36
|
const CONTEXT_SHOW_TARGET_FIELDS = new Set(["path", "name", "view"]);
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
path !== "LICENSE" &&
|
|
62
|
-
!CONTEXT_IGNORED_FILENAMES.has(basename(path)) &&
|
|
63
|
-
!ignoreGlobs.some((glob) => matchGlob(glob, path)) &&
|
|
64
|
-
path !== ".pi/tau/ideas.jsonl" &&
|
|
65
|
-
path !== ".pi/tau/reviews" &&
|
|
66
|
-
!path.startsWith(".pi/tau/reviews/") &&
|
|
67
|
-
path !== ".working" &&
|
|
68
|
-
!path.startsWith(".working/") &&
|
|
69
|
-
path !== ".pi/contexts" &&
|
|
70
|
-
!path.startsWith(".pi/contexts/")
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function isSensitiveContextPath(path: string): boolean {
|
|
75
|
-
const name = basename(path);
|
|
76
|
-
if (name === ".env.example" || name === ".env.sample") return false;
|
|
77
|
-
return name === ".env" || name.startsWith(".env.") || /\.(?:pem|key|crt|p12|pfx)$/i.test(name);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function pathExists(path: string): Promise<boolean> {
|
|
38
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
81
39
|
try {
|
|
82
40
|
await access(path);
|
|
83
41
|
return true;
|
|
@@ -154,15 +112,6 @@ function sortedUnique(values: readonly string[]): string[] {
|
|
|
154
112
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
155
113
|
}
|
|
156
114
|
|
|
157
|
-
export function contextEntryPaths(entry: Pick<ContextEntry, "read" | "show" | "outline" | "references">): string[] {
|
|
158
|
-
return sortedUnique([
|
|
159
|
-
...entry.read,
|
|
160
|
-
...entry.show.map((target) => target.path),
|
|
161
|
-
...entry.outline,
|
|
162
|
-
...entry.references,
|
|
163
|
-
]);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
115
|
function parseOneShowTarget(root: string, catalogPath: string, entryName: string, item: unknown): ContextShowTarget {
|
|
167
116
|
const record = item as Record<string, unknown>;
|
|
168
117
|
const unknownField = Object.keys(record).find((field) => !CONTEXT_SHOW_TARGET_FIELDS.has(field));
|
|
@@ -2,13 +2,8 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { prepareFileInjection } from "@shanepadgett/tau-agent";
|
|
5
|
-
import { createGitRunner, loadRepoStatus } from "../../shared/git.ts";
|
|
6
|
-
import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
|
|
7
5
|
import { findProjectRoot, loadContextEntries, type ContextEntry, type ContextShowTarget } from "./definitions.ts";
|
|
8
|
-
import { ContextPanel
|
|
9
|
-
import contextSettings from "./settings.ts";
|
|
10
|
-
import { type ContextSyncDetails, runContextSync } from "./sync.ts";
|
|
11
|
-
import { formatContextValidationFailure, validateContextCatalog } from "./validation.ts";
|
|
6
|
+
import { ContextPanel } from "./panel.ts";
|
|
12
7
|
|
|
13
8
|
const CONTEXT_BRIEF_TYPE = "tau.context.brief";
|
|
14
9
|
|
|
@@ -103,91 +98,7 @@ async function injectSelectedContext(
|
|
|
103
98
|
}
|
|
104
99
|
}
|
|
105
100
|
|
|
106
|
-
function hasAbortedAssistant(messages: readonly unknown[]): boolean {
|
|
107
|
-
return messages.some(
|
|
108
|
-
(message) =>
|
|
109
|
-
typeof message === "object" &&
|
|
110
|
-
message !== null &&
|
|
111
|
-
"role" in message &&
|
|
112
|
-
message.role === "assistant" &&
|
|
113
|
-
"stopReason" in message &&
|
|
114
|
-
message.stopReason === "aborted",
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function handleAgentEndValidation(
|
|
119
|
-
pi: ExtensionAPI,
|
|
120
|
-
ctx: ExtensionContext,
|
|
121
|
-
settings: typeof contextSettings.defaults,
|
|
122
|
-
lastValidationFailure: string | undefined,
|
|
123
|
-
): Promise<string | undefined> {
|
|
124
|
-
if (!settings.sync.enabled || !settings.validation.enabled || !ctx.isProjectTrusted()) return undefined;
|
|
125
|
-
const root = await findProjectRoot(ctx.cwd);
|
|
126
|
-
const git = createGitRunner(pi, ctx);
|
|
127
|
-
if (!(await loadRepoStatus(git))) return lastValidationFailure;
|
|
128
|
-
const failure = formatContextValidationFailure(
|
|
129
|
-
await validateContextCatalog(git, root, settings.validation.ignoreGlobs),
|
|
130
|
-
);
|
|
131
|
-
if (!failure) return undefined;
|
|
132
|
-
if (failure === lastValidationFailure) return lastValidationFailure;
|
|
133
|
-
ctx.ui.notify("Context catalog validation failed; running context-sync", "error");
|
|
134
|
-
const result = ctx.mode === "tui" ? await runContextSyncWithEditor(pi, ctx, {}) : await runContextSync(pi, ctx, {});
|
|
135
|
-
if (!result || result.outcome === "cancelled") {
|
|
136
|
-
ctx.ui.notify("Context sync cancelled", "info");
|
|
137
|
-
return failure;
|
|
138
|
-
}
|
|
139
|
-
const afterFailure = formatContextValidationFailure(
|
|
140
|
-
await validateContextCatalog(git, root, settings.validation.ignoreGlobs),
|
|
141
|
-
);
|
|
142
|
-
if (result.outcome === "failed" || afterFailure) {
|
|
143
|
-
ctx.ui.notify(
|
|
144
|
-
result.outcome === "failed" ? result.summary : "Context catalog still invalid after context-sync",
|
|
145
|
-
"error",
|
|
146
|
-
);
|
|
147
|
-
return afterFailure ?? `${failure}\n${result.reason}`;
|
|
148
|
-
}
|
|
149
|
-
ctx.ui.notify(result.summary, "info");
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
101
|
export default function contextExtension(pi: ExtensionAPI): void {
|
|
154
|
-
let settings = contextSettings.defaults;
|
|
155
|
-
let lastValidationFailure: string | undefined;
|
|
156
|
-
let syncCommandRegistered = false;
|
|
157
|
-
|
|
158
|
-
const refreshSettings = async (ctx: { cwd: string; isProjectTrusted(): boolean }) => {
|
|
159
|
-
settings = await loadTauExtensionSettings(ctx, contextSettings);
|
|
160
|
-
if (settings.sync.enabled) registerContextSyncCommand();
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const registerContextSyncCommand = () => {
|
|
164
|
-
if (syncCommandRegistered) return;
|
|
165
|
-
syncCommandRegistered = true;
|
|
166
|
-
pi.registerCommand("context-sync", {
|
|
167
|
-
description: "Synchronize repository context from current Git changes via context-sync subagent",
|
|
168
|
-
handler: async (args, ctx) => {
|
|
169
|
-
if (!(await loadTauExtensionSettings(ctx, contextSettings)).sync.enabled) {
|
|
170
|
-
ctx.ui.notify("Context sync is disabled in settings", "warning");
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (ctx.mode !== "tui" || !ctx.isProjectTrusted()) {
|
|
174
|
-
ctx.ui.notify("/context-sync requires a trusted TUI project", "warning");
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
await ctx.waitForIdle();
|
|
178
|
-
const result = await runContextSyncWithEditor(pi, ctx, {
|
|
179
|
-
nudge: args.trim() || undefined,
|
|
180
|
-
});
|
|
181
|
-
if (!result) return;
|
|
182
|
-
if (result.outcome === "cancelled") {
|
|
183
|
-
ctx.ui.notify(result.summary, "info");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
ctx.ui.notify(result.summary, result.outcome === "failed" ? "error" : "info");
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
};
|
|
190
|
-
|
|
191
102
|
pi.registerCommand("context", {
|
|
192
103
|
description: "Inject repository context entries into the conversation",
|
|
193
104
|
handler: async (_args, ctx) => {
|
|
@@ -213,64 +124,4 @@ export default function contextExtension(pi: ExtensionAPI): void {
|
|
|
213
124
|
await injectSelectedContext(pi, ctx, root, selected);
|
|
214
125
|
},
|
|
215
126
|
});
|
|
216
|
-
|
|
217
|
-
// Default on: register immediately so /context-sync exists before session_start in tests and early UI.
|
|
218
|
-
if (settings.sync.enabled) registerContextSyncCommand();
|
|
219
|
-
|
|
220
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
221
|
-
await refreshSettings(ctx);
|
|
222
|
-
});
|
|
223
|
-
pi.on("agent_start", async (_event, ctx) => {
|
|
224
|
-
await refreshSettings(ctx);
|
|
225
|
-
});
|
|
226
|
-
pi.on("agent_end", async (event, ctx) => {
|
|
227
|
-
if (hasAbortedAssistant(event.messages)) return;
|
|
228
|
-
settings = await loadTauExtensionSettings(ctx, contextSettings);
|
|
229
|
-
try {
|
|
230
|
-
lastValidationFailure = await handleAgentEndValidation(pi, ctx, settings, lastValidationFailure);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
ctx.ui.notify(`Context validation failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async function runContextSyncWithEditor(
|
|
238
|
-
pi: ExtensionAPI,
|
|
239
|
-
ctx: ExtensionContext,
|
|
240
|
-
options: { nudge?: string },
|
|
241
|
-
): Promise<ContextSyncDetails | undefined> {
|
|
242
|
-
return ctx.ui.custom<ContextSyncDetails | undefined>((tui, theme, _keys, done) => {
|
|
243
|
-
const panel = new ContextSyncStatusPanel(tui, theme, "Synchronizing repository context");
|
|
244
|
-
void (async () => {
|
|
245
|
-
try {
|
|
246
|
-
const result = await runContextSync(pi, ctx, {
|
|
247
|
-
nudge: options.nudge,
|
|
248
|
-
signal: panel.signal,
|
|
249
|
-
onStatus: (status) => {
|
|
250
|
-
panel.update(status);
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
done(result);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
if (panel.signal.aborted) {
|
|
256
|
-
done({
|
|
257
|
-
outcome: "cancelled",
|
|
258
|
-
summary: "Context sync cancelled",
|
|
259
|
-
reason: "Cancelled by user.",
|
|
260
|
-
changedContextFiles: [],
|
|
261
|
-
});
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
done({
|
|
265
|
-
outcome: "failed",
|
|
266
|
-
summary: `Context sync failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
267
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
268
|
-
changedContextFiles: [],
|
|
269
|
-
});
|
|
270
|
-
} finally {
|
|
271
|
-
panel.dispose();
|
|
272
|
-
}
|
|
273
|
-
})();
|
|
274
|
-
return panel;
|
|
275
|
-
});
|
|
276
127
|
}
|
|
@@ -196,75 +196,3 @@ export class ContextPanel implements Component {
|
|
|
196
196
|
this.tui.requestRender();
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
export class ContextSyncStatusPanel implements Component {
|
|
201
|
-
private readonly tui: TUI;
|
|
202
|
-
private readonly panel: ToolPanel;
|
|
203
|
-
private readonly config: ToolPanelConfig;
|
|
204
|
-
private readonly controller = new AbortController();
|
|
205
|
-
private readonly startedAt = Date.now();
|
|
206
|
-
private readonly timer: ReturnType<typeof setInterval>;
|
|
207
|
-
private lines: string[] = [];
|
|
208
|
-
private lineCount = 0;
|
|
209
|
-
|
|
210
|
-
constructor(tui: TUI, theme: Theme, status: string) {
|
|
211
|
-
this.tui = tui;
|
|
212
|
-
this.config = {
|
|
213
|
-
title: "Context sync",
|
|
214
|
-
body: {
|
|
215
|
-
render: (width) => {
|
|
216
|
-
const visible =
|
|
217
|
-
this.lineCount > 5
|
|
218
|
-
? [`${this.lineCount - 4} earlier lines omitted`, ...this.lines.slice(-4)]
|
|
219
|
-
: this.lines;
|
|
220
|
-
return visible.map((line, index) =>
|
|
221
|
-
truncateToWidth(theme.fg(index === 0 && this.lineCount > 5 ? "dim" : "muted", line), width, "…"),
|
|
222
|
-
);
|
|
223
|
-
},
|
|
224
|
-
invalidate: () => {},
|
|
225
|
-
},
|
|
226
|
-
footer: { kind: "hints", hints: [bindingHint("tui.select.cancel", "cancel")] },
|
|
227
|
-
border: "horizontal",
|
|
228
|
-
};
|
|
229
|
-
this.panel = new ToolPanel(theme, this.config);
|
|
230
|
-
this.update(status);
|
|
231
|
-
this.timer = setInterval(() => this.tui.requestRender(), 1_000);
|
|
232
|
-
this.timer.unref();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
get signal(): AbortSignal {
|
|
236
|
-
return this.controller.signal;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
handleInput(data: string): void {
|
|
240
|
-
if (!getKeybindings().matches(data, "tui.select.cancel")) return;
|
|
241
|
-
if (this.controller.signal.aborted) return;
|
|
242
|
-
this.update("Cancelling context sync");
|
|
243
|
-
this.controller.abort();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
update(status: string): void {
|
|
247
|
-
const added = status
|
|
248
|
-
.split(/\r?\n/)
|
|
249
|
-
.map((line) => line.trim())
|
|
250
|
-
.filter(Boolean);
|
|
251
|
-
this.lineCount += added.length;
|
|
252
|
-
this.lines = [...this.lines, ...added].slice(-5);
|
|
253
|
-
this.tui.requestRender();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
render(width: number): string[] {
|
|
257
|
-
const elapsed = Math.floor((Date.now() - this.startedAt) / 1_000);
|
|
258
|
-
this.config.title =
|
|
259
|
-
elapsed < 60 ? `Context sync · ${elapsed}s` : `Context sync · ${Math.floor(elapsed / 60)}m ${elapsed % 60}s`;
|
|
260
|
-
return this.panel.render(width);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
invalidate(): void {
|
|
264
|
-
this.panel.invalidate();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
dispose(): void {
|
|
268
|
-
clearInterval(this.timer);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { registerPromptSource } from "../../shared/prompt-contributions.ts";
|
|
2
3
|
|
|
3
4
|
const GUIDANCE = `## Explore
|
|
4
5
|
Shape-backed languages: \`markdown\`, \`typescript\`, \`tsx\`, \`go\`, \`rust\`, \`c_sharp\`, \`java\`, \`kotlin\`, \`swift\`.
|
|
@@ -8,5 +9,12 @@ Full \`read\` of a large registered source returns outline + follow-up hint, not
|
|
|
8
9
|
\`show\` always takes a top-level \`targets\` array, even for one declaration: \`{"targets":[{"path":"...","name":"..."}],"view":"declaration"}\`.`;
|
|
9
10
|
|
|
10
11
|
export function registerExploreGuidance(pi: ExtensionAPI): void {
|
|
11
|
-
pi
|
|
12
|
+
registerPromptSource(pi, {
|
|
13
|
+
key: "explore/guidance",
|
|
14
|
+
section: "tool-guidance",
|
|
15
|
+
refresh: "compaction",
|
|
16
|
+
async read() {
|
|
17
|
+
return GUIDANCE;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
12
20
|
}
|
|
@@ -6,8 +6,7 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import { join, relative } from "node:path";
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext, Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { type Component, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
|
-
import {
|
|
10
|
-
import { effortForSelection, type ModelEffort } from "../../shared/model-effort.ts";
|
|
9
|
+
import { onTauEvent } from "../../shared/events.ts";
|
|
11
10
|
import { loadTauExtensionSettings, updateTauExtensionSettings } from "../../shared/settings/load.ts";
|
|
12
11
|
import footerSettings from "./settings.ts";
|
|
13
12
|
|
|
@@ -40,8 +39,6 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
40
39
|
let footerInstalled = false;
|
|
41
40
|
let requestRender: (() => void) | undefined;
|
|
42
41
|
let unsubscribeFooterItems: (() => void) | undefined;
|
|
43
|
-
let unsubscribeModelEffort: (() => void) | undefined;
|
|
44
|
-
let activeEffort: ModelEffort | undefined;
|
|
45
42
|
const gitByCwd = new Map<string, GitSummary | undefined>();
|
|
46
43
|
let gitRefresh: Promise<void> | undefined;
|
|
47
44
|
let dailyCost: number | undefined;
|
|
@@ -80,8 +77,7 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
80
77
|
const model = currentCtx.model ? `${currentCtx.model.provider}/${currentCtx.model.id}` : "no-model";
|
|
81
78
|
const thinking = pi.getThinkingLevel();
|
|
82
79
|
const separator = theme.fg("dim", " • ");
|
|
83
|
-
const
|
|
84
|
-
const topLeft = [theme.fg("dim", gitText(git)), effort, theme.fg("dim", `${model} (${thinking})`)]
|
|
80
|
+
const topLeft = [theme.fg("dim", gitText(git)), theme.fg("dim", `${model} (${thinking})`)]
|
|
85
81
|
.filter(Boolean)
|
|
86
82
|
.join(separator);
|
|
87
83
|
const sessionUsage = sessionCost(currentCtx);
|
|
@@ -172,11 +168,6 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
172
168
|
items.set(item.id, next);
|
|
173
169
|
render();
|
|
174
170
|
});
|
|
175
|
-
unsubscribeModelEffort = onTauEvent(pi, "footer.model-effort", "tau:model-effort.changed", (state) => {
|
|
176
|
-
activeEffort = state.effort;
|
|
177
|
-
render();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
171
|
pi.registerCommand(COMMAND, {
|
|
181
172
|
description: "Toggle Tau footer",
|
|
182
173
|
handler: async (args, ctx) => {
|
|
@@ -213,20 +204,15 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
213
204
|
pi.on("session_start", async (_event, ctx) => {
|
|
214
205
|
const settings = await loadTauExtensionSettings(ctx, footerSettings);
|
|
215
206
|
setEnabled(ctx, settings.enabled);
|
|
216
|
-
activeEffort = effortForSelection(ctx.model?.provider, ctx.model?.id, pi.getThinkingLevel());
|
|
217
207
|
onStateChange(ctx);
|
|
218
|
-
emitTauEvent(pi, "tau:model-effort.snapshot.requested", {});
|
|
219
208
|
});
|
|
220
209
|
pi.on("session_tree", (_event, ctx) => {
|
|
221
|
-
activeEffort = effortForSelection(ctx.model?.provider, ctx.model?.id, pi.getThinkingLevel());
|
|
222
210
|
onStateChange(ctx, false);
|
|
223
211
|
});
|
|
224
212
|
pi.on("model_select", (_event, ctx) => {
|
|
225
|
-
activeEffort = effortForSelection(ctx.model?.provider, ctx.model?.id, pi.getThinkingLevel());
|
|
226
213
|
rerender(ctx);
|
|
227
214
|
});
|
|
228
|
-
pi.on("thinking_level_select", (
|
|
229
|
-
activeEffort = effortForSelection(ctx.model?.provider, ctx.model?.id, event.level);
|
|
215
|
+
pi.on("thinking_level_select", (_event, ctx) => {
|
|
230
216
|
rerender(ctx);
|
|
231
217
|
});
|
|
232
218
|
pi.on("agent_start", (_event, ctx) => onStateChange(ctx, false));
|
|
@@ -236,9 +222,6 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
236
222
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
237
223
|
unsubscribeFooterItems?.();
|
|
238
224
|
unsubscribeFooterItems = undefined;
|
|
239
|
-
unsubscribeModelEffort?.();
|
|
240
|
-
unsubscribeModelEffort = undefined;
|
|
241
|
-
activeEffort = undefined;
|
|
242
225
|
requestRender = undefined;
|
|
243
226
|
activeCtx = undefined;
|
|
244
227
|
footerInstalled = false;
|
|
@@ -251,11 +234,6 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
251
234
|
}
|
|
252
235
|
}
|
|
253
236
|
|
|
254
|
-
function effortText(theme: Theme, effort: ModelEffort | undefined): string {
|
|
255
|
-
if (!effort) return "";
|
|
256
|
-
return theme.fg("dim", theme.bold(effort.toUpperCase()));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
237
|
async function saveEnabled(ctx: ExtensionContext, enabled: boolean): Promise<void> {
|
|
260
238
|
await updateTauExtensionSettings("global", ctx, footerSettings, (current) => ({ ...current, enabled }));
|
|
261
239
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Editor, type Focusable, Key, matchesKey, type TUI } from "@earendil-works/pi-tui";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
editorTheme,
|
|
5
|
+
pushSavedNote,
|
|
6
|
+
renderInlineEditor,
|
|
7
|
+
renderNoteEditor,
|
|
8
|
+
wrapWithPrefix,
|
|
9
|
+
} from "@shanepadgett/tau-tui";
|
|
4
10
|
import { bindingHint, bindingsHint, rawHint, type ToolKeyHint } from "@shanepadgett/tau-tui";
|
|
5
11
|
import { renderQuestionPrompt } from "./body-render.ts";
|
|
6
|
-
import { pushSavedNote, renderInlineEditor, renderNoteEditor, wrapWithPrefix } from "./inline-editor-row.ts";
|
|
7
12
|
import { renderRecommendation } from "./input-question-body.ts";
|
|
8
13
|
import {
|
|
9
14
|
getAnswer,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Run Summary
|
|
2
2
|
|
|
3
|
-
Run Summary adds a compact marker after the agent settles with no automatic continuation pending. It shows wall time
|
|
3
|
+
Run Summary adds a compact marker after the agent settles with no automatic continuation pending. It shows wall time and model cost across the full continuation chain. Older markers also display the delegated cost recorded for those runs.
|
|
4
4
|
|
|
5
5
|
The marker is stored as a display-only session entry. It does not enter model context or trigger another agent turn.
|
|
@@ -7,6 +7,9 @@ const ENTRY_TYPE = "tau.run-summary";
|
|
|
7
7
|
interface RunSummary {
|
|
8
8
|
wallMs: number;
|
|
9
9
|
runCost: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface PreviousRunSummary extends RunSummary {
|
|
10
13
|
subagentCost: number;
|
|
11
14
|
totalCost: number;
|
|
12
15
|
}
|
|
@@ -14,7 +17,6 @@ interface RunSummary {
|
|
|
14
17
|
export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
15
18
|
let startedAt: number | undefined;
|
|
16
19
|
let runCost = 0;
|
|
17
|
-
let subagentCost = 0;
|
|
18
20
|
|
|
19
21
|
pi.registerEntryRenderer<RunSummary>(ENTRY_TYPE, (entry, _options, theme) => {
|
|
20
22
|
const summary = readRunSummary(entry.data);
|
|
@@ -26,8 +28,9 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
26
28
|
parts: [
|
|
27
29
|
`Wall ${formatDuration(summary.wallMs)}`,
|
|
28
30
|
`Run ${formatCost(summary.runCost)}`,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
...("subagentCost" in summary
|
|
32
|
+
? [`Subagents ${formatCost(summary.subagentCost)}`, `Total ${formatCost(summary.totalCost)}`]
|
|
33
|
+
: []),
|
|
31
34
|
],
|
|
32
35
|
});
|
|
33
36
|
});
|
|
@@ -35,7 +38,6 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
35
38
|
pi.on("session_start", () => {
|
|
36
39
|
startedAt = undefined;
|
|
37
40
|
runCost = 0;
|
|
38
|
-
subagentCost = 0;
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
pi.on("agent_start", () => {
|
|
@@ -44,12 +46,7 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
44
46
|
|
|
45
47
|
pi.on("agent_end", (event) => {
|
|
46
48
|
for (const message of event.messages) {
|
|
47
|
-
if (message.role === "assistant")
|
|
48
|
-
runCost += finiteNonNegative((message as AssistantMessage).usage.cost.total);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (message.role !== "toolResult" || message.toolName !== "subagent") continue;
|
|
52
|
-
subagentCost += readUsageCost(message.usage);
|
|
49
|
+
if (message.role === "assistant") runCost += finiteNonNegative((message as AssistantMessage).usage.cost.total);
|
|
53
50
|
}
|
|
54
51
|
});
|
|
55
52
|
|
|
@@ -60,31 +57,27 @@ export default function runSummaryExtension(pi: ExtensionAPI): void {
|
|
|
60
57
|
pi.appendEntry<RunSummary>(ENTRY_TYPE, {
|
|
61
58
|
wallMs,
|
|
62
59
|
runCost,
|
|
63
|
-
subagentCost,
|
|
64
|
-
totalCost: runCost + subagentCost,
|
|
65
60
|
});
|
|
66
61
|
runCost = 0;
|
|
67
|
-
subagentCost = 0;
|
|
68
62
|
});
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
function
|
|
72
|
-
if (!value || typeof value !== "object") return 0;
|
|
73
|
-
const cost = (value as Record<string, unknown>).cost;
|
|
74
|
-
if (!cost || typeof cost !== "object") return 0;
|
|
75
|
-
return finiteNonNegative((cost as Record<string, unknown>).total);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function readRunSummary(value: unknown): RunSummary | undefined {
|
|
65
|
+
function readRunSummary(value: unknown): RunSummary | PreviousRunSummary | undefined {
|
|
79
66
|
if (!value || typeof value !== "object") return undefined;
|
|
80
67
|
const record = value as Record<string, unknown>;
|
|
81
|
-
if (![record.wallMs, record.runCost
|
|
82
|
-
|
|
68
|
+
if (![record.wallMs, record.runCost].every(isFiniteNonNegative)) return undefined;
|
|
69
|
+
if ("subagentCost" in record || "totalCost" in record) {
|
|
70
|
+
if (![record.subagentCost, record.totalCost].every(isFiniteNonNegative)) return undefined;
|
|
71
|
+
return {
|
|
72
|
+
wallMs: record.wallMs as number,
|
|
73
|
+
runCost: record.runCost as number,
|
|
74
|
+
subagentCost: record.subagentCost as number,
|
|
75
|
+
totalCost: record.totalCost as number,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
83
78
|
return {
|
|
84
79
|
wallMs: record.wallMs as number,
|
|
85
80
|
runCost: record.runCost as number,
|
|
86
|
-
subagentCost: record.subagentCost as number,
|
|
87
|
-
totalCost: record.totalCost as number,
|
|
88
81
|
};
|
|
89
82
|
}
|
|
90
83
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Runtime Context
|
|
2
2
|
|
|
3
|
-
Supplies
|
|
3
|
+
Supplies Soul with the local date and root directory snapshot. Both are captured together when a prompt baseline is created and refreshed after successful compaction. Reload, resume, and midnight do not change an existing baseline.
|
|
4
4
|
|
|
5
5
|
After changing this extension, run `/reload` before testing the new behavior.
|