@shanepadgett/tau-agent 0.31.1 → 0.33.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.
@@ -2,17 +2,38 @@ import { resolve } from "node:path";
2
2
  import { type ExtensionAPI, type ExtensionContext, type SessionEntry } from "@earendil-works/pi-coding-agent";
3
3
  import { type Static, Type } from "typebox";
4
4
  import { truncateBoundedHead } from "../../shared/bounded-text-result.ts";
5
+ import { emitTauEvent } from "../../shared/events.ts";
5
6
  import { requestOutlineInjections, type PreparedOutlineInjection } from "../../shared/outline-injection.ts";
6
7
  import { buildMemoryCatalog, collectPrunedRowIds } from "./memory.ts";
7
- import { WORKING_MEMORY_TOOL, type DeferredFile, type WorkingMemoryCheckpointDetailsV1 } from "./state.ts";
8
+ import {
9
+ replayWorkingMemoryState,
10
+ WORKING_MEMORY_TOOL,
11
+ type DeferredFile,
12
+ type WorkingMemoryCheckpointDetailsV2,
13
+ } from "./state.ts";
8
14
 
9
15
  const PATH = Type.String({ minLength: 1, maxLength: 500, pattern: "\\S" });
10
16
 
11
17
  export const workingMemoryParameters = Type.Object(
12
18
  {
13
- continuation: Type.String({ minLength: 1, maxLength: 8_000, pattern: "\\S" }),
14
- keep: Type.Array(Type.String({ minLength: 3, maxLength: 100 }), { maxItems: 100 }),
15
- outlineFiles: Type.Array(PATH, { maxItems: 12 }),
19
+ continuation: Type.String({
20
+ minLength: 1,
21
+ maxLength: 8_000,
22
+ pattern: "\\S",
23
+ description:
24
+ "Working note for resuming mid-task. Carry durable decisions, concrete findings, live reasoning, unresolved questions, remaining work, and next action; include enough detail to continue without rereading discarded results.",
25
+ }),
26
+ keep: Type.Array(Type.String({ minLength: 3, maxLength: 100 }), { minItems: 1, maxItems: 100 }),
27
+ readFiles: Type.Array(PATH, {
28
+ maxItems: 12,
29
+ description:
30
+ "Files whose source should be auto-read into the next turn. Use when the next work needs the complete file; do not also outline or defer these files.",
31
+ }),
32
+ outlineFiles: Type.Array(PATH, {
33
+ maxItems: 12,
34
+ description:
35
+ "Files whose structural outline should be carried into the next turn. Use when symbols and locations will guide later scoped inspection; do not use when the full source is still needed.",
36
+ }),
16
37
  deferFiles: Type.Array(
17
38
  Type.Object(
18
39
  {
@@ -22,7 +43,11 @@ export const workingMemoryParameters = Type.Object(
22
43
  },
23
44
  { additionalProperties: false },
24
45
  ),
25
- { maxItems: 8 },
46
+ {
47
+ maxItems: 8,
48
+ description:
49
+ "Files to leave out of active context. Record why they are inactive and the concrete condition that would make them relevant.",
50
+ },
26
51
  ),
27
52
  },
28
53
  { additionalProperties: false },
@@ -43,7 +68,7 @@ interface ExecuteWorkingMemoryOptions {
43
68
  export interface WorkingMemoryExecution {
44
69
  result: {
45
70
  content: Array<{ type: "text"; text: string }>;
46
- details: WorkingMemoryCheckpointDetailsV1;
71
+ details: WorkingMemoryCheckpointDetailsV2;
47
72
  };
48
73
  outlines: PreparedOutlineInjection[];
49
74
  }
@@ -52,6 +77,12 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
52
77
  assertCurrent(options);
53
78
  const branch = options.ctx.sessionManager.getBranch();
54
79
  const catalog = buildMemoryCatalog(branch);
80
+ const anchorIndex = findAnchorEntry(branch, options.toolCallId);
81
+ const previousState = replayWorkingMemoryState(branch, true);
82
+ const previousAnchorIndex =
83
+ previousState.latestAnchorToolCallId === undefined
84
+ ? -1
85
+ : findAnchorEntry(branch, previousState.latestAnchorToolCallId);
55
86
  const requestedRefs = [...new Set(options.params.keep)];
56
87
  const retained = requestedRefs
57
88
  .flatMap((ref) => {
@@ -60,11 +91,32 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
60
91
  })
61
92
  .sort((left, right) => left.order - right.order);
62
93
  const retainedRefs = retained.map((unit) => unit.ref);
94
+ if (retained.length === 0) {
95
+ throw new Error("working_memory requires at least one valid keep reference");
96
+ }
63
97
  const warnings = requestedRefs
64
98
  .filter((ref) => !catalog.has(ref))
65
99
  .map((ref) => `${ref}: memory reference is unavailable and was not retained`);
66
100
 
67
- const outlinePaths = dedupePaths(options.params.outlineFiles, options.ctx.cwd);
101
+ const rootInstructionsKey = resolve(options.ctx.cwd, "AGENTS.md");
102
+ const requestedRootInstructions = [
103
+ ...options.params.readFiles,
104
+ ...options.params.outlineFiles,
105
+ ...options.params.deferFiles.map((file) => file.path),
106
+ ].some((path) => resolve(options.ctx.cwd, normalizePath(path)) === rootInstructionsKey);
107
+ if (requestedRootInstructions) {
108
+ warnings.push(
109
+ "AGENTS.md: omitted from working-memory checkpoint because it is already in your context window. Read it directly only when actively changing it.",
110
+ );
111
+ }
112
+
113
+ const readPaths = dedupePaths(options.params.readFiles, options.ctx.cwd).filter(
114
+ (path) => resolve(options.ctx.cwd, path) !== rootInstructionsKey,
115
+ );
116
+ const readKeys = new Set(readPaths.map((path) => resolve(options.ctx.cwd, path)));
117
+ const outlinePaths = dedupePaths(options.params.outlineFiles, options.ctx.cwd).filter(
118
+ (path) => resolve(options.ctx.cwd, path) !== rootInstructionsKey && !readKeys.has(resolve(options.ctx.cwd, path)),
119
+ );
68
120
  const outlineResponse = await requestOutlineInjections(options.pi, {
69
121
  cwd: options.ctx.cwd,
70
122
  batchId: options.toolCallId,
@@ -75,34 +127,50 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
75
127
  warnings.push(...outlineResponse.warnings.map((warning) => boundedWarning(warning)));
76
128
  assertCurrent(options);
77
129
 
78
- const outlinedKeys = new Set(
79
- outlineResponse.messages.map((message) => resolve(options.ctx.cwd, message.details.path)),
80
- );
130
+ const carriedKeys = new Set([
131
+ ...readKeys,
132
+ ...outlineResponse.messages.map((message) => resolve(options.ctx.cwd, message.details.path)),
133
+ ]);
81
134
  const deferredFiles: DeferredFile[] = [];
82
135
  const deferredKeys = new Set<string>();
83
136
  for (const file of options.params.deferFiles) {
84
137
  const path = normalizePath(file.path);
85
138
  const key = resolve(options.ctx.cwd, path);
86
- if (outlinedKeys.has(key) || deferredKeys.has(key)) continue;
139
+ if (key === rootInstructionsKey || carriedKeys.has(key) || deferredKeys.has(key)) continue;
87
140
  deferredKeys.add(key);
88
141
  deferredFiles.push({ path, reason: file.reason.trim(), relevantWhen: file.relevantWhen.trim() });
89
142
  }
90
143
 
91
- const anchorIndex = findAnchorEntry(branch, options.toolCallId);
92
- const preAnchorUnits = [...catalog.values()].filter((unit) => unit.order < anchorIndex);
144
+ if (readPaths.length > 0) {
145
+ emitTauEvent(options.pi, "tau:autoread.requested", {
146
+ source: WORKING_MEMORY_TOOL,
147
+ title: "Working-memory checkpoint",
148
+ cwd: options.ctx.cwd,
149
+ batchId: options.toolCallId,
150
+ files: readPaths.map((path) => ({ path })),
151
+ });
152
+ }
153
+
93
154
  const prunedRowIds = collectPrunedRowIds(branch, anchorIndex);
94
- const details: WorkingMemoryCheckpointDetailsV1 = {
95
- v: 1,
155
+ const activeRefs = new Set(previousState.retainedRefs.filter((ref) => catalog.has(ref)));
156
+ for (const unit of catalog.values()) {
157
+ if (unit.order > previousAnchorIndex && unit.order < anchorIndex) activeRefs.add(unit.ref);
158
+ }
159
+ const newlyPrunedRows = prunedRowIds.filter((rowId) => !previousState.prunedRowIds.has(rowId));
160
+ const droppedBlocks = [...activeRefs].filter((ref) => !retainedRefs.includes(ref)).length + newlyPrunedRows.length;
161
+ const details: WorkingMemoryCheckpointDetailsV2 = {
162
+ v: 2,
96
163
  anchorToolCallId: options.toolCallId,
97
164
  retainedRefs,
98
165
  retainedLabels: retained.map((unit) => ({ ref: unit.ref, label: unit.label, preview: unit.preview })),
99
166
  prunedRowIds,
167
+ readFiles: readPaths,
100
168
  outlinedFiles: outlineResponse.messages.map((message) => ({
101
169
  path: message.details.path,
102
170
  rowId: message.details.rowId,
103
171
  })),
104
172
  deferredFiles,
105
- removedUnits: Math.max(0, preAnchorUnits.length - retained.length),
173
+ removedUnits: droppedBlocks,
106
174
  warnings,
107
175
  };
108
176
  const markdown = formatResult(options.params.continuation.trim(), deferredFiles, warnings);
@@ -17,12 +17,12 @@ import {
17
17
  type WorkingMemoryNudgeDetails,
18
18
  } from "./render.ts";
19
19
  import workingMemorySettings from "./settings.ts";
20
- import { replayWorkingMemoryState, WORKING_MEMORY_TOOL, type WorkingMemoryCheckpointDetailsV1 } from "./state.ts";
20
+ import { replayWorkingMemoryState, WORKING_MEMORY_TOOL, type WorkingMemoryCheckpointDetailsV2 } from "./state.ts";
21
21
 
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 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.";
25
+ "Create a selective hard checkpoint for future model context. Retain one or more valuable user or visible assistant messages, auto-read full source or carry file structure as needed, defer conditionally relevant files, and distill exploration findings into one compact continuation note.";
26
26
 
27
27
  interface NudgeState {
28
28
  anchorToolCallId: string | undefined;
@@ -91,7 +91,7 @@ export default function workingMemoryExtension(pi: ExtensionAPI): void {
91
91
  instructions = settings.nudgeInstructions;
92
92
  if (enabled && !toolRegistered) {
93
93
  pi.registerTool(
94
- defineTool<typeof workingMemoryParameters, WorkingMemoryCheckpointDetailsV1>({
94
+ defineTool<typeof workingMemoryParameters, WorkingMemoryCheckpointDetailsV2>({
95
95
  name: WORKING_MEMORY_TOOL,
96
96
  label: WORKING_MEMORY_TOOL,
97
97
  description: TOOL_DESCRIPTION,
@@ -99,8 +99,10 @@ export default function workingMemoryExtension(pi: ExtensionAPI): void {
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
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.",
102
+ "Every checkpoint must retain at least one relevant referenced message. Keep task framing, constraints, decisions, and immediate work chain when needed; continuation and file tiers supplement retained conversation and cannot replace it.",
103
+ "Choose one file tier: readFiles auto-reads source into the next turn when its body is needed; outlineFiles carries symbols and locations for later scoped inspection; deferFiles records inactive conditional paths. Do not read a file merely to decide whether to outline it.",
104
+ "Choose readFiles instead of outlineFiles when next work will require the complete file. Do not list a path in more than one file tier.",
105
+ "Use continuation as a working note for resuming mid-task. Carry durable decisions, concrete findings, live reasoning, unresolved questions, remaining work, and next action in as much detail as needed to continue without rereading discarded results. Do not make it a user-facing status update or narrate the checkpoint.",
104
106
  ],
105
107
  parameters: workingMemoryParameters,
106
108
  executionMode: "sequential",
@@ -123,6 +125,7 @@ export default function workingMemoryExtension(pi: ExtensionAPI): void {
123
125
  rowId: context.toolCallId,
124
126
  invalidate: context.invalidate,
125
127
  lastComponent: context.lastComponent,
128
+ executionStarted: context.executionStarted,
126
129
  });
127
130
  },
128
131
  renderResult(result, options, theme, context) {
@@ -64,7 +64,7 @@ export function collectPrunedRowIds(branch: readonly SessionEntry[], before: num
64
64
  }
65
65
  if (
66
66
  message.role === "custom" &&
67
- message.customType === "tau.explore.outline" &&
67
+ (message.customType === "tau.explore.outline" || message.customType === "tau.autoread") &&
68
68
  isRecord(message.details) &&
69
69
  typeof message.details.rowId === "string"
70
70
  ) {
@@ -99,15 +99,21 @@ export function renderWorkingMemoryCall(
99
99
  rowId: string;
100
100
  invalidate: () => void;
101
101
  lastComponent: unknown;
102
+ executionStarted: boolean;
102
103
  },
103
104
  ): Text {
104
105
  context.rowState.watch(context.rowId, context.invalidate);
105
106
  const component = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
107
+ if (context.executionStarted) {
108
+ component.setText("");
109
+ return component;
110
+ }
106
111
  const kept = Array.isArray(args.keep) ? args.keep.length : 0;
112
+ const read = Array.isArray(args.readFiles) ? args.readFiles.length : 0;
107
113
  const outlined = Array.isArray(args.outlineFiles) ? args.outlineFiles.length : 0;
108
114
  const deferred = Array.isArray(args.deferFiles) ? args.deferFiles.length : 0;
109
115
  component.setText(
110
- `${formatToolRowTitle(context.rowState, context.rowId, "working_memory", theme)} ${theme.fg("muted", `${kept} kept · ${outlined} outline · ${deferred} deferred`)}`,
116
+ `${formatToolRowTitle(context.rowState, context.rowId, "working_memory", theme)} ${theme.fg("muted", `${kept} kept · ${read} read · ${outlined} outline · ${deferred} deferred`)}`,
111
117
  );
112
118
  return component;
113
119
  }
@@ -128,12 +134,13 @@ export function renderWorkingMemoryResult(
128
134
  }
129
135
  let text = theme.fg(
130
136
  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}`}`,
137
+ `Checkpoint · kept ${details.retainedRefs.length} · read ${details.readFiles.length} · outlined ${details.outlinedFiles.length} · deferred ${details.deferredFiles.length} · pruned ${details.removedUnits}${details.warnings.length === 0 ? "" : ` · warnings ${details.warnings.length}`}`,
132
138
  );
133
139
  if (expanded) {
134
140
  const lines = [
135
141
  bounded(firstText(result), 1_500),
136
142
  ...details.retainedLabels.map((item) => `${item.ref} ${item.label}: ${item.preview}`),
143
+ ...details.readFiles.map((path) => `read: ${path}`),
137
144
  ...details.outlinedFiles.map((file) => `outlined: ${file.path}`),
138
145
  ...details.deferredFiles.map((file) => `deferred: ${file.path} — ${file.reason}; when ${file.relevantWhen}`),
139
146
  ...details.warnings.map((warning) => `warning: ${warning}`),
@@ -14,12 +14,13 @@ export interface RetainedLabel {
14
14
  preview: string;
15
15
  }
16
16
 
17
- export interface WorkingMemoryCheckpointDetailsV1 {
18
- v: 1;
17
+ export interface WorkingMemoryCheckpointDetailsV2 {
18
+ v: 2;
19
19
  anchorToolCallId: string;
20
20
  retainedRefs: string[];
21
21
  retainedLabels: RetainedLabel[];
22
22
  prunedRowIds: string[];
23
+ readFiles: string[];
23
24
  outlinedFiles: Array<{ path: string; rowId: string }>;
24
25
  deferredFiles: DeferredFile[];
25
26
  removedUnits: number;
@@ -32,14 +33,15 @@ export interface ActiveWorkingMemoryState {
32
33
  prunedRowIds: ReadonlySet<string>;
33
34
  }
34
35
 
35
- export function parseWorkingMemoryDetails(value: unknown): WorkingMemoryCheckpointDetailsV1 | undefined {
36
- if (!isRecord(value) || value.v !== 1 || !nonempty(value.anchorToolCallId)) return undefined;
36
+ export function parseWorkingMemoryDetails(value: unknown): WorkingMemoryCheckpointDetailsV2 | undefined {
37
+ if (!isRecord(value) || value.v !== 2 || !nonempty(value.anchorToolCallId)) return undefined;
37
38
  const keys = [
38
39
  "v",
39
40
  "anchorToolCallId",
40
41
  "retainedRefs",
41
42
  "retainedLabels",
42
43
  "prunedRowIds",
44
+ "readFiles",
43
45
  "outlinedFiles",
44
46
  "deferredFiles",
45
47
  "removedUnits",
@@ -48,13 +50,16 @@ export function parseWorkingMemoryDetails(value: unknown): WorkingMemoryCheckpoi
48
50
  if (Object.keys(value).length !== keys.length || !keys.every((key) => Object.hasOwn(value, key))) return undefined;
49
51
  const retainedRefs = uniqueStrings(value.retainedRefs);
50
52
  const prunedRowIds = uniqueStrings(value.prunedRowIds);
53
+ const readFiles = uniqueStrings(value.readFiles);
51
54
  const warnings = strings(value.warnings);
52
55
  const retainedLabels = parseRetainedLabels(value.retainedLabels);
53
56
  const outlinedFiles = parseOutlinedFiles(value.outlinedFiles);
54
57
  const deferredFiles = parseDeferredFiles(value.deferredFiles);
55
58
  if (
56
59
  !retainedRefs ||
60
+ retainedRefs.length === 0 ||
57
61
  !prunedRowIds ||
62
+ !readFiles ||
58
63
  !warnings ||
59
64
  !retainedLabels ||
60
65
  retainedLabels.length !== retainedRefs.length ||
@@ -67,11 +72,12 @@ export function parseWorkingMemoryDetails(value: unknown): WorkingMemoryCheckpoi
67
72
  return undefined;
68
73
  }
69
74
  return {
70
- v: 1,
75
+ v: 2,
71
76
  anchorToolCallId: value.anchorToolCallId,
72
77
  retainedRefs,
73
78
  retainedLabels,
74
79
  prunedRowIds,
80
+ readFiles,
75
81
  outlinedFiles,
76
82
  deferredFiles,
77
83
  removedUnits: value.removedUnits as number,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shanepadgett/tau-agent",
3
- "version": "0.31.1",
3
+ "version": "0.33.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.31.1",
38
+ "@shanepadgett/tau-tui": "0.33.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/shared/events.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { ModelEffort } from "./model-effort.ts";
2
3
  import type { OutlineInjectionRequest, OutlineInjectionResponse } from "./outline-injection.js";
3
4
  import type { ToolRowVisualState } from "./tool-row-state.js";
4
5
 
@@ -46,6 +47,10 @@ export type TauAgentEvents = {
46
47
  text?: string;
47
48
  priority?: number;
48
49
  };
50
+ "tau:model-effort.changed": {
51
+ effort: ModelEffort | undefined;
52
+ };
53
+ "tau:model-effort.snapshot.requested": Record<string, never>;
49
54
  "tau:tool-row-state.set": {
50
55
  rowId: string;
51
56
  state?: ToolRowVisualState;
@@ -0,0 +1,116 @@
1
+ import type { Api, Model, ThinkingLevel } from "@earendil-works/pi-ai";
2
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import { resolveCandidates } from "./model-fallback/index.ts";
4
+ import type { ModelCandidate } from "./model-fallback/types.ts";
5
+
6
+ export type ModelEffort = "low" | "medium" | "high";
7
+
8
+ interface ModelPreference {
9
+ model: string;
10
+ reasoning: ThinkingLevel;
11
+ }
12
+
13
+ interface ProviderPreference {
14
+ provider: string;
15
+ models: readonly ModelPreference[];
16
+ }
17
+
18
+ export interface EffortProviderCandidates {
19
+ provider: string;
20
+ label: string;
21
+ candidates: ReadonlyArray<{
22
+ model: Model<Api>;
23
+ reasoning: ThinkingLevel;
24
+ }>;
25
+ }
26
+
27
+ const MODEL_PREFERENCES: Record<ModelEffort, readonly ProviderPreference[]> = {
28
+ low: [
29
+ {
30
+ provider: "openai-codex",
31
+ models: [
32
+ { model: "gpt-5.6-luna", reasoning: "high" },
33
+ { model: "gpt-5.5", reasoning: "low" },
34
+ ],
35
+ },
36
+ { provider: "xai", models: [{ model: "grok-4.5", reasoning: "medium" }] },
37
+ { provider: "anthropic", models: [{ model: "claude-haiku-4-5", reasoning: "high" }] },
38
+ ],
39
+ medium: [
40
+ {
41
+ provider: "openai-codex",
42
+ models: [
43
+ { model: "gpt-5.6-terra", reasoning: "high" },
44
+ { model: "gpt-5.5", reasoning: "medium" },
45
+ ],
46
+ },
47
+ { provider: "xai", models: [{ model: "grok-4.5", reasoning: "high" }] },
48
+ { provider: "anthropic", models: [{ model: "claude-sonnet-5", reasoning: "high" }] },
49
+ ],
50
+ high: [
51
+ {
52
+ provider: "openai-codex",
53
+ models: [
54
+ { model: "gpt-5.6-sol", reasoning: "high" },
55
+ { model: "gpt-5.5", reasoning: "high" },
56
+ ],
57
+ },
58
+ { provider: "xai", models: [{ model: "grok-4.5", reasoning: "high" }] },
59
+ {
60
+ provider: "anthropic",
61
+ models: [
62
+ { model: "claude-opus-5", reasoning: "high" },
63
+ { model: "claude-opus-4-8", reasoning: "high" },
64
+ ],
65
+ },
66
+ ],
67
+ };
68
+
69
+ export function effortForSelection(
70
+ provider: string | undefined,
71
+ model: string | undefined,
72
+ reasoning: string | undefined,
73
+ ): ModelEffort | undefined {
74
+ if (!provider || !model || !reasoning) return undefined;
75
+ for (const effort of ["high", "medium", "low"] as const) {
76
+ const preference = MODEL_PREFERENCES[effort].find((item) => item.provider === provider);
77
+ if (preference?.models.some((item) => item.model === model && item.reasoning === reasoning)) return effort;
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ export function resolveEffortProviders(
83
+ ctx: Pick<ExtensionContext, "modelRegistry">,
84
+ effort: ModelEffort,
85
+ ): EffortProviderCandidates[] {
86
+ const available = new Map(ctx.modelRegistry.getAvailable().map((model) => [`${model.provider}/${model.id}`, model]));
87
+ return MODEL_PREFERENCES[effort].flatMap((preference) => {
88
+ const candidates = preference.models.flatMap(({ model, reasoning }) => {
89
+ const availableModel = available.get(`${preference.provider}/${model}`);
90
+ return availableModel ? [{ model: availableModel, reasoning }] : [];
91
+ });
92
+ return candidates.length
93
+ ? [
94
+ {
95
+ provider: preference.provider,
96
+ label: ctx.modelRegistry.getProviderDisplayName(preference.provider),
97
+ candidates,
98
+ },
99
+ ]
100
+ : [];
101
+ });
102
+ }
103
+
104
+ export function resolveEffortCandidates(
105
+ ctx: Pick<ExtensionContext, "modelRegistry" | "model" | "cwd" | "isProjectTrusted">,
106
+ effort: ModelEffort,
107
+ includeParentModel: boolean,
108
+ ): Promise<ModelCandidate[]> {
109
+ return resolveCandidates(
110
+ ctx,
111
+ MODEL_PREFERENCES[effort].flatMap((preference) =>
112
+ preference.models.map((model) => ({ provider: preference.provider, ...model })),
113
+ ),
114
+ includeParentModel,
115
+ );
116
+ }
@@ -1,13 +0,0 @@
1
- # Publish
2
-
3
- Create a tagged Tau release and monitor its GitHub Actions npm publish run.
4
-
5
- ## Usage
6
-
7
- ```text
8
- /publish
9
- ```
10
-
11
- `/publish` requires a clean Git working tree, authenticated GitHub CLI, and public approved npm package sources. It recommends a semantic version bump from commits since the previous release tag, asks for confirmation, then pushes a release tag. GitHub Actions publishes the TUI package before the agent package through npm trusted publishing.
12
-
13
- If publishing fails, the agent runs read-only diagnostics and recommends a solution. It does not apply the fix or retry the release.