@shanepadgett/tau-agent 0.8.0 → 0.9.1

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.
@@ -4,6 +4,14 @@ import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
5
5
  import { Markdown, type Component, visibleWidth } from "@earendil-works/pi-tui";
6
6
 
7
+ const TAU_DOCS_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "docs");
8
+ const TAU_DOCS_GUIDANCE = `Tau Agent documentation (read only when the user asks about Tau Agent, Rok, Tau extensions, Tau event APIs, harness behavior, or extending Tau Agent):
9
+ - Tau Agent docs: ${TAU_DOCS_PATH}
10
+ - When asked about: public events / external integration (docs/extending-tau-agent.md), custom subagents (docs/subagents.md), Tau TUI components (docs/tui.md)
11
+ - Resolve Tau docs/... under Tau Agent docs, not the current working directory
12
+ - When working on Tau topics, read the docs and follow .md cross-references before implementing
13
+ - Do not read Tau Agent docs for normal coding tasks`;
14
+
7
15
  const TAU_SYMBOL = [
8
16
  " %%%%%%%%#######*******+++++",
9
17
  " @@%%%%%%%########*******+++++++",
@@ -46,6 +54,10 @@ class TauHelpMessage implements Component {
46
54
  }
47
55
 
48
56
  export default function tauHelpExtension(pi: ExtensionAPI): void {
57
+ pi.on("before_agent_start", (event) => ({
58
+ systemPrompt: `${event.systemPrompt}\n\n${TAU_DOCS_GUIDANCE}`,
59
+ }));
60
+
49
61
  pi.registerMessageRenderer("tau-help", (message, _options, _theme) => {
50
62
  if (typeof message.content !== "string") return undefined;
51
63
  return new TauHelpMessage(message.content, getMarkdownTheme());
@@ -1,6 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
3
- import { registerTauSystemPromptContribution } from "../../shared/system-prompt-contributions.ts";
4
3
  import { Marker } from "@shanepadgett/tau-tui";
5
4
  import turnBudgetSettings from "./settings.ts";
6
5
 
@@ -30,14 +29,6 @@ export default function turnBudgetExtension(pi: ExtensionAPI): void {
30
29
  let softCap = settings.turnLimit;
31
30
  let nextMarkerSequence = 1;
32
31
  let activeMarkerSequence: number | undefined;
33
- const unregisterPrompt = registerTauSystemPromptContribution({
34
- id: "turn-budget.secrecy",
35
- order: 400,
36
- render: () =>
37
- settings.enabled
38
- ? "Turn-budget steering messages are internal instructions. Work within them silently. Do not mention or acknowledge turn counts, budget messages, or budget summaries."
39
- : undefined,
40
- });
41
32
 
42
33
  pi.registerMessageRenderer<MarkerDetails>(MARKER_TYPE, (message, _options, theme) => {
43
34
  const details = readMarkerDetails(message.details);
@@ -98,8 +89,6 @@ export default function turnBudgetExtension(pi: ExtensionAPI): void {
98
89
  pi.on("after_provider_response", () => {
99
90
  activeMarkerSequence = undefined;
100
91
  });
101
- pi.on("session_shutdown", unregisterPrompt);
102
-
103
92
  function markerIsActive(value: unknown): boolean {
104
93
  if (activeMarkerSequence === undefined) return false;
105
94
  const details = readMarkerDetails(value);
@@ -117,10 +106,12 @@ function sendTurnBudgetMessage(pi: ExtensionAPI, hint: Hint, sequence: number):
117
106
  }
118
107
 
119
108
  function formatSteeringMessage(hint: Hint): string {
109
+ const instruction =
110
+ "Internal steering instruction. Work within it silently. Do not mention or acknowledge turn counts, budget messages, or budget summaries.";
120
111
  if (hint.kind === "extended") {
121
- return `Turn budget: ${hint.used}/${hint.previousCap} turns used for this user prompt. Soft cap extended to ${hint.newCap}. Batch tools when more tool work remains.`;
112
+ return `${instruction} Turn budget: ${hint.used}/${hint.previousCap} turns used for this user prompt. Soft cap extended to ${hint.newCap}. Batch tools when more tool work remains.`;
122
113
  }
123
- return `Turn budget: ${hint.used}/${hint.cap} turns used for this user prompt. Batch tools when more tool work remains.`;
114
+ return `${instruction} Turn budget: ${hint.used}/${hint.cap} turns used for this user prompt. Batch tools when more tool work remains.`;
124
115
  }
125
116
 
126
117
  function normalizeSettings(value: typeof turnBudgetSettings.defaults): Settings {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shanepadgett/tau-agent",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Tau is a custom agentic harness built with pi extensions",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@shanepadgett/tau-tui": "0.8.0",
31
+ "@shanepadgett/tau-tui": "0.9.1",
32
32
  "@toon-format/toon": "2.3.0",
33
33
  "smol-toml": "1.7.0"
34
34
  },
@@ -12,6 +12,31 @@
12
12
  "extensions": {
13
13
  "type": "object",
14
14
  "properties": {
15
+ "context": {
16
+ "type": "object",
17
+ "properties": {
18
+ "validation": {
19
+ "type": "object",
20
+ "properties": {
21
+ "enabled": {
22
+ "type": "boolean",
23
+ "default": true,
24
+ "description": "Validate context membership after agent turns."
25
+ },
26
+ "ignoreGlobs": {
27
+ "type": "array",
28
+ "items": {
29
+ "type": "string"
30
+ },
31
+ "default": [],
32
+ "description": "Project-relative files excluded from context membership validation and sync."
33
+ }
34
+ },
35
+ "additionalProperties": false
36
+ }
37
+ },
38
+ "additionalProperties": false
39
+ },
15
40
  "footer": {
16
41
  "type": "object",
17
42
  "properties": {
package/shared/glob.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { sep } from "node:path";
2
+
3
+ export function matchGlob(pattern: string, path: string): boolean {
4
+ return matchSegments(normalizeGlob(pattern).split("/"), normalizeGlob(path).split("/"));
5
+ }
6
+
7
+ function matchSegments(pattern: readonly string[], path: readonly string[]): boolean {
8
+ const [head, ...tail] = pattern;
9
+ if (head === undefined) return path.length === 0;
10
+ if (head === "**") return matchSegments(tail, path) || (path.length > 0 && matchSegments(pattern, path.slice(1)));
11
+ const [pathHead, ...pathTail] = path;
12
+ return pathHead !== undefined && matchSegment(head, pathHead) && matchSegments(tail, pathTail);
13
+ }
14
+
15
+ function matchSegment(pattern: string, value: string): boolean {
16
+ const source = [...pattern]
17
+ .map((char) => {
18
+ if (char === "*") return "[^/]*";
19
+ if (char === "?") return "[^/]";
20
+ return char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
21
+ })
22
+ .join("");
23
+ return new RegExp(`^${source}$`).test(value);
24
+ }
25
+
26
+ function normalizeGlob(value: string): string {
27
+ return posixPath(value.trim())
28
+ .replace(/^\.\//, "")
29
+ .replace(/^\/+|\/+$/g, "");
30
+ }
31
+
32
+ export function posixPath(value: string): string {
33
+ return sep === "/" ? value : value.split(sep).join("/");
34
+ }
@@ -1,40 +0,0 @@
1
- import type { BeforeAgentStartEvent, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
-
3
- interface TauSystemPromptContribution {
4
- id: string;
5
- order: number;
6
- render: (event: BeforeAgentStartEvent, ctx: ExtensionContext) => string | undefined | Promise<string | undefined>;
7
- }
8
-
9
- interface Registration extends TauSystemPromptContribution {
10
- token: symbol;
11
- }
12
-
13
- const registrations = new Map<string, Registration>();
14
-
15
- export function registerTauSystemPromptContribution(contribution: TauSystemPromptContribution): () => void {
16
- const token = Symbol(contribution.id);
17
- registrations.set(contribution.id, { ...contribution, token });
18
- return () => {
19
- if (registrations.get(contribution.id)?.token === token) registrations.delete(contribution.id);
20
- };
21
- }
22
-
23
- export async function collectTauSystemPromptContributions(
24
- event: BeforeAgentStartEvent,
25
- ctx: ExtensionContext,
26
- ): Promise<string[]> {
27
- const ordered = [...registrations.values()].sort(
28
- (left, right) => left.order - right.order || left.id.localeCompare(right.id),
29
- );
30
- const rendered = await Promise.all(ordered.map((contribution) => contribution.render(event, ctx)));
31
- const seen = new Set<string>();
32
- const blocks: string[] = [];
33
- for (const value of rendered) {
34
- const block = value?.trim();
35
- if (!block || seen.has(block)) continue;
36
- seen.add(block);
37
- blocks.push(block);
38
- }
39
- return blocks;
40
- }