@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.
- package/extensions/commit/commit-effort.ts +14 -0
- package/extensions/commit/commit-plan.ts +6 -7
- package/extensions/commit/git-change-set.ts +36 -4
- package/extensions/context/index.ts +1 -1
- package/extensions/effort/README.md +7 -0
- package/extensions/effort/index.ts +134 -0
- package/extensions/effort/state.ts +33 -0
- package/extensions/footer/README.md +1 -1
- package/extensions/footer/index.ts +36 -6
- package/extensions/reference/README.md +1 -0
- package/extensions/reference/panel.ts +34 -4
- package/extensions/tau-help/help.md +5 -5
- package/extensions/working-memory/README.md +9 -1
- package/extensions/working-memory/checkpoint.ts +84 -16
- package/extensions/working-memory/index.ts +8 -5
- package/extensions/working-memory/memory.ts +1 -1
- package/extensions/working-memory/render.ts +9 -2
- package/extensions/working-memory/state.ts +11 -5
- package/package.json +2 -2
- package/shared/events.ts +5 -0
- package/shared/model-effort.ts +116 -0
- package/extensions/publish/README.md +0 -13
- package/extensions/publish/index.ts +0 -301
- package/extensions/publish/publish-ui.ts +0 -73
|
@@ -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 {
|
|
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({
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
{
|
|
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:
|
|
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
|
|
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
|
|
79
|
-
|
|
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 (
|
|
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
|
-
|
|
92
|
-
|
|
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
|
|
95
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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,
|
|
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
|
-
"
|
|
103
|
-
"
|
|
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} ·
|
|
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
|
|
18
|
-
v:
|
|
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):
|
|
36
|
-
if (!isRecord(value) || value.v !==
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|