@shanepadgett/tau-agent 0.33.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/bash-approval/README.md +20 -0
- package/extensions/bash-approval/index.ts +235 -0
- package/extensions/bash-approval/settings.ts +24 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +66 -0
- package/extensions/checkpoint/checkpoint.ts +279 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +170 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/commit/commit-plan.ts +2 -2
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/index.ts +2 -0
- package/extensions/reference/panel.ts +94 -31
- package/extensions/review/README.md +14 -6
- package/extensions/review/index.ts +58 -99
- package/extensions/review/model.ts +13 -33
- package/extensions/review/session.ts +9 -23
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/soul/README.md +2 -2
- package/extensions/soul/index.ts +4 -4
- package/extensions/soul/prompt.ts +6 -10
- package/extensions/soul/settings.ts +6 -3
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents/web-research.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +308 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +20 -8
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +38 -35
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +72 -30
- package/shared/model-effort.ts +23 -22
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/review/panel.ts +0 -128
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineTool,
|
|
3
|
-
type ExtensionAPI,
|
|
4
|
-
type ExtensionContext,
|
|
5
|
-
type SessionEntry,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { emitTauEvent, onTauEvent } from "../../shared/events.ts";
|
|
8
|
-
import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
|
|
9
|
-
import { createToolRowStateStore } from "../../shared/tool-row-state.ts";
|
|
10
|
-
import { executeWorkingMemory, workingMemoryParameters } from "./checkpoint.ts";
|
|
11
|
-
import { projectWorkingMemory } from "./memory.ts";
|
|
12
|
-
import {
|
|
13
|
-
parseWorkingMemoryNudge,
|
|
14
|
-
renderWorkingMemoryCall,
|
|
15
|
-
renderWorkingMemoryNudge,
|
|
16
|
-
renderWorkingMemoryResult,
|
|
17
|
-
type WorkingMemoryNudgeDetails,
|
|
18
|
-
} from "./render.ts";
|
|
19
|
-
import workingMemorySettings from "./settings.ts";
|
|
20
|
-
import { replayWorkingMemoryState, WORKING_MEMORY_TOOL, type WorkingMemoryCheckpointDetailsV2 } from "./state.ts";
|
|
21
|
-
|
|
22
|
-
const NUDGE_TYPE = "tau.working-memory.nudge";
|
|
23
|
-
const BASELINE_TYPE = "tau.working-memory.nudge-baseline";
|
|
24
|
-
const TOOL_DESCRIPTION =
|
|
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
|
-
|
|
27
|
-
interface NudgeState {
|
|
28
|
-
anchorToolCallId: string | undefined;
|
|
29
|
-
suppressedThroughTokens: number | undefined;
|
|
30
|
-
highestBoundaryTokens: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default function workingMemoryExtension(pi: ExtensionAPI): void {
|
|
34
|
-
let enabled = false;
|
|
35
|
-
let generation = 0;
|
|
36
|
-
let interval = workingMemorySettings.defaults.nudgeEveryTokens;
|
|
37
|
-
let instructions = workingMemorySettings.defaults.nudgeInstructions;
|
|
38
|
-
let toolRegistered = false;
|
|
39
|
-
let commandRegistered = false;
|
|
40
|
-
let visualRows = new Set<string>();
|
|
41
|
-
let nudgeState: NudgeState = { anchorToolCallId: undefined, suppressedThroughTokens: 0, highestBoundaryTokens: 0 };
|
|
42
|
-
const rowState = createToolRowStateStore(pi, "working-memory.tool-row-state");
|
|
43
|
-
|
|
44
|
-
pi.registerMessageRenderer<WorkingMemoryNudgeDetails>(NUDGE_TYPE, (message, _options, theme) =>
|
|
45
|
-
renderWorkingMemoryNudge(message.details, theme),
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const pushVisualSnapshot = () => {
|
|
49
|
-
emitTauEvent(pi, "tau:tool-row-state.snapshot", {
|
|
50
|
-
states: [...visualRows].map((rowId) => ({ rowId, state: "pruned" as const })),
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
onTauEvent(
|
|
54
|
-
pi,
|
|
55
|
-
"working-memory.tool-row-state-producer",
|
|
56
|
-
"tau:tool-row-state.snapshot.requested",
|
|
57
|
-
pushVisualSnapshot,
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const invalidate = () => {
|
|
61
|
-
generation += 1;
|
|
62
|
-
};
|
|
63
|
-
const setToolActive = (active: boolean) => {
|
|
64
|
-
if (!toolRegistered) return;
|
|
65
|
-
const activeTools = pi.getActiveTools();
|
|
66
|
-
const currentlyActive = activeTools.includes(WORKING_MEMORY_TOOL);
|
|
67
|
-
if (active === currentlyActive) return;
|
|
68
|
-
pi.setActiveTools(
|
|
69
|
-
active ? [...activeTools, WORKING_MEMORY_TOOL] : activeTools.filter((name) => name !== WORKING_MEMORY_TOOL),
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
const syncBranch = (ctx: ExtensionContext) => {
|
|
73
|
-
const branch = ctx.sessionManager.getBranch();
|
|
74
|
-
const state = replayWorkingMemoryState(branch, enabled);
|
|
75
|
-
visualRows = new Set(state.prunedRowIds);
|
|
76
|
-
nudgeState = reconstructNudgeState(branch, state.latestAnchorToolCallId);
|
|
77
|
-
pushVisualSnapshot();
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
81
|
-
invalidate();
|
|
82
|
-
enabled = false;
|
|
83
|
-
setToolActive(false);
|
|
84
|
-
visualRows.clear();
|
|
85
|
-
pushVisualSnapshot();
|
|
86
|
-
const current = generation;
|
|
87
|
-
const settings = await loadTauExtensionSettings(ctx, workingMemorySettings);
|
|
88
|
-
if (current !== generation) return;
|
|
89
|
-
enabled = settings.enabled;
|
|
90
|
-
interval = settings.nudgeEveryTokens;
|
|
91
|
-
instructions = settings.nudgeInstructions;
|
|
92
|
-
if (enabled && !toolRegistered) {
|
|
93
|
-
pi.registerTool(
|
|
94
|
-
defineTool<typeof workingMemoryParameters, WorkingMemoryCheckpointDetailsV2>({
|
|
95
|
-
name: WORKING_MEMORY_TOOL,
|
|
96
|
-
label: WORKING_MEMORY_TOOL,
|
|
97
|
-
description: TOOL_DESCRIPTION,
|
|
98
|
-
promptSnippet: "Reassess and selectively checkpoint active working memory",
|
|
99
|
-
promptGuidelines: [
|
|
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
|
-
"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
|
-
"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.",
|
|
106
|
-
],
|
|
107
|
-
parameters: workingMemoryParameters,
|
|
108
|
-
executionMode: "sequential",
|
|
109
|
-
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
110
|
-
const execution = await executeWorkingMemory({
|
|
111
|
-
pi,
|
|
112
|
-
toolCallId,
|
|
113
|
-
params,
|
|
114
|
-
signal,
|
|
115
|
-
ctx,
|
|
116
|
-
generation,
|
|
117
|
-
currentGeneration: () => generation,
|
|
118
|
-
});
|
|
119
|
-
for (const outline of execution.outlines) pi.sendMessage(outline, { deliverAs: "steer" });
|
|
120
|
-
return execution.result;
|
|
121
|
-
},
|
|
122
|
-
renderCall(args, theme, context) {
|
|
123
|
-
return renderWorkingMemoryCall(args, theme, {
|
|
124
|
-
rowState,
|
|
125
|
-
rowId: context.toolCallId,
|
|
126
|
-
invalidate: context.invalidate,
|
|
127
|
-
lastComponent: context.lastComponent,
|
|
128
|
-
executionStarted: context.executionStarted,
|
|
129
|
-
});
|
|
130
|
-
},
|
|
131
|
-
renderResult(result, options, theme, context) {
|
|
132
|
-
return renderWorkingMemoryResult(result, options.expanded, theme, context.lastComponent);
|
|
133
|
-
},
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
136
|
-
toolRegistered = true;
|
|
137
|
-
}
|
|
138
|
-
setToolActive(enabled);
|
|
139
|
-
if (enabled && !commandRegistered) {
|
|
140
|
-
pi.registerCommand("prune", {
|
|
141
|
-
description: "Ask agent to reassess working memory and continue",
|
|
142
|
-
async handler(args, commandContext) {
|
|
143
|
-
if (!enabled) {
|
|
144
|
-
commandContext.ui.notify("Working memory is disabled.", "info");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (args.trim().length > 0) {
|
|
148
|
-
commandContext.ui.notify("Usage: /prune", "info");
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const anchor = replayWorkingMemoryState(
|
|
152
|
-
commandContext.sessionManager.getBranch(),
|
|
153
|
-
true,
|
|
154
|
-
).latestAnchorToolCallId;
|
|
155
|
-
pi.sendMessage<WorkingMemoryNudgeDetails>(
|
|
156
|
-
{
|
|
157
|
-
customType: NUDGE_TYPE,
|
|
158
|
-
content: manualInstruction(),
|
|
159
|
-
display: true,
|
|
160
|
-
details: {
|
|
161
|
-
v: 1,
|
|
162
|
-
kind: "manual",
|
|
163
|
-
tokens: null,
|
|
164
|
-
boundaryTokens: null,
|
|
165
|
-
reminder: null,
|
|
166
|
-
tier: null,
|
|
167
|
-
tierCount: null,
|
|
168
|
-
anchorToolCallId: anchor ?? null,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
{ deliverAs: "steer", triggerTurn: true },
|
|
172
|
-
);
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
commandRegistered = true;
|
|
176
|
-
}
|
|
177
|
-
syncBranch(ctx);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
pi.on("before_agent_start", (_event, ctx) => {
|
|
181
|
-
if (!enabled) return undefined;
|
|
182
|
-
const usage = ctx.getContextUsage();
|
|
183
|
-
if (!usage || usage.tokens === null || !Number.isFinite(usage.tokens)) return undefined;
|
|
184
|
-
const tokens = Math.max(0, Math.floor(usage.tokens));
|
|
185
|
-
const reminder = Math.floor(tokens / interval);
|
|
186
|
-
if (reminder < 1) return undefined;
|
|
187
|
-
const instruction = instructions[Math.min(reminder, instructions.length) - 1] ?? instructions[0];
|
|
188
|
-
return {
|
|
189
|
-
message: {
|
|
190
|
-
customType: NUDGE_TYPE,
|
|
191
|
-
content: automaticInstruction(instruction),
|
|
192
|
-
display: false,
|
|
193
|
-
details: {
|
|
194
|
-
v: 1,
|
|
195
|
-
kind: "automatic",
|
|
196
|
-
tokens,
|
|
197
|
-
boundaryTokens: reminder * interval,
|
|
198
|
-
reminder,
|
|
199
|
-
tier: Math.min(reminder, instructions.length),
|
|
200
|
-
tierCount: instructions.length,
|
|
201
|
-
anchorToolCallId:
|
|
202
|
-
replayWorkingMemoryState(ctx.sessionManager.getBranch(), true).latestAnchorToolCallId ?? null,
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
pi.on("turn_end", (event, ctx) => {
|
|
209
|
-
if (!enabled || event.toolResults.length === 0) return undefined;
|
|
210
|
-
const usage = ctx.getContextUsage();
|
|
211
|
-
if (!usage || usage.tokens === null || !Number.isFinite(usage.tokens)) return undefined;
|
|
212
|
-
const tokens = Math.max(0, Math.floor(usage.tokens));
|
|
213
|
-
const branch = ctx.sessionManager.getBranch();
|
|
214
|
-
const anchor = replayWorkingMemoryState(branch, true).latestAnchorToolCallId;
|
|
215
|
-
if (anchor !== nudgeState.anchorToolCallId) nudgeState = reconstructNudgeState(branch, anchor);
|
|
216
|
-
if (anchor !== undefined && nudgeState.suppressedThroughTokens === undefined) {
|
|
217
|
-
const floor = Math.floor(tokens / interval) * interval;
|
|
218
|
-
pi.appendEntry(BASELINE_TYPE, { v: 1, anchorToolCallId: anchor, suppressedThroughTokens: floor });
|
|
219
|
-
nudgeState.suppressedThroughTokens = floor;
|
|
220
|
-
nudgeState.highestBoundaryTokens = floor;
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
223
|
-
const reminder = Math.floor(tokens / interval);
|
|
224
|
-
if (reminder < 1) return undefined;
|
|
225
|
-
const boundaryTokens = reminder * interval;
|
|
226
|
-
if (boundaryTokens <= nudgeState.highestBoundaryTokens) return undefined;
|
|
227
|
-
const tier = Math.min(reminder, instructions.length);
|
|
228
|
-
const instruction = instructions[tier - 1] ?? instructions[0];
|
|
229
|
-
pi.sendMessage<WorkingMemoryNudgeDetails>(
|
|
230
|
-
{
|
|
231
|
-
customType: NUDGE_TYPE,
|
|
232
|
-
content: automaticInstruction(instruction),
|
|
233
|
-
display: true,
|
|
234
|
-
details: {
|
|
235
|
-
v: 1,
|
|
236
|
-
kind: "automatic",
|
|
237
|
-
tokens,
|
|
238
|
-
boundaryTokens,
|
|
239
|
-
reminder,
|
|
240
|
-
tier,
|
|
241
|
-
tierCount: instructions.length,
|
|
242
|
-
anchorToolCallId: anchor ?? null,
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
{ deliverAs: "steer" },
|
|
246
|
-
);
|
|
247
|
-
nudgeState.highestBoundaryTokens = boundaryTokens;
|
|
248
|
-
return undefined;
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
pi.on("context", (event, ctx) => {
|
|
252
|
-
if (!enabled) return undefined;
|
|
253
|
-
const branch = ctx.sessionManager.getBranch();
|
|
254
|
-
const state = replayWorkingMemoryState(branch, true);
|
|
255
|
-
const messages = projectWorkingMemory(event.messages, state, branch, ctx.sessionManager.buildContextEntries());
|
|
256
|
-
if (!setsEqual(visualRows, state.prunedRowIds)) {
|
|
257
|
-
visualRows = new Set(state.prunedRowIds);
|
|
258
|
-
pushVisualSnapshot();
|
|
259
|
-
}
|
|
260
|
-
return { messages };
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
pi.on("session_tree", (_event, ctx) => {
|
|
264
|
-
invalidate();
|
|
265
|
-
syncBranch(ctx);
|
|
266
|
-
});
|
|
267
|
-
pi.on("session_compact", (_event, ctx) => {
|
|
268
|
-
invalidate();
|
|
269
|
-
syncBranch(ctx);
|
|
270
|
-
});
|
|
271
|
-
pi.on("session_shutdown", () => {
|
|
272
|
-
invalidate();
|
|
273
|
-
enabled = false;
|
|
274
|
-
setToolActive(false);
|
|
275
|
-
visualRows.clear();
|
|
276
|
-
pushVisualSnapshot();
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function reconstructNudgeState(branch: readonly SessionEntry[], anchor: string | undefined): NudgeState {
|
|
281
|
-
let suppressedThroughTokens = anchor === undefined ? 0 : undefined;
|
|
282
|
-
let highestBoundaryTokens = 0;
|
|
283
|
-
let anchorResultIndex = -1;
|
|
284
|
-
if (anchor !== undefined) {
|
|
285
|
-
anchorResultIndex = branch.findIndex(
|
|
286
|
-
(entry) =>
|
|
287
|
-
entry.type === "message" &&
|
|
288
|
-
entry.message.role === "toolResult" &&
|
|
289
|
-
entry.message.toolName === WORKING_MEMORY_TOOL &&
|
|
290
|
-
entry.message.toolCallId === anchor,
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
for (let index = anchorResultIndex + 1; index < branch.length; index += 1) {
|
|
294
|
-
const entry = branch[index];
|
|
295
|
-
if (!entry) continue;
|
|
296
|
-
if (entry.type === "custom" && entry.customType === BASELINE_TYPE) {
|
|
297
|
-
const baseline = parseBaseline(entry.data);
|
|
298
|
-
if (baseline !== undefined && baseline.anchorToolCallId === anchor) {
|
|
299
|
-
suppressedThroughTokens = baseline.suppressedThroughTokens;
|
|
300
|
-
highestBoundaryTokens = Math.max(highestBoundaryTokens, baseline.suppressedThroughTokens);
|
|
301
|
-
}
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
if (entry.type !== "custom_message" || entry.customType !== NUDGE_TYPE) continue;
|
|
305
|
-
const details = parseWorkingMemoryNudge(entry.details);
|
|
306
|
-
if (details?.kind !== "automatic" || details.anchorToolCallId !== (anchor ?? null)) continue;
|
|
307
|
-
highestBoundaryTokens = Math.max(highestBoundaryTokens, details.boundaryTokens);
|
|
308
|
-
}
|
|
309
|
-
return { anchorToolCallId: anchor, suppressedThroughTokens, highestBoundaryTokens };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function parseBaseline(value: unknown): { anchorToolCallId: string; suppressedThroughTokens: number } | undefined {
|
|
313
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
314
|
-
const record = value as Record<string, unknown>;
|
|
315
|
-
if (
|
|
316
|
-
record.v !== 1 ||
|
|
317
|
-
typeof record.anchorToolCallId !== "string" ||
|
|
318
|
-
!Number.isSafeInteger(record.suppressedThroughTokens) ||
|
|
319
|
-
(record.suppressedThroughTokens as number) < 0
|
|
320
|
-
) {
|
|
321
|
-
return undefined;
|
|
322
|
-
}
|
|
323
|
-
return {
|
|
324
|
-
anchorToolCallId: record.anchorToolCallId,
|
|
325
|
-
suppressedThroughTokens: record.suppressedThroughTokens as number,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function automaticInstruction(instruction: string): string {
|
|
330
|
-
return `Internal working-memory instruction. Follow silently without mentioning token counts or context management. ${instruction} Use working_memory only when a selective checkpoint improves future work; never prune useful evidence merely to reread it.`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function manualInstruction(): string {
|
|
334
|
-
return "Internal working-memory instruction. Follow silently. Reassess active evidence, use working_memory if a selective checkpoint improves future work, then continue unfinished work. Do not mention this request.";
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function setsEqual(left: ReadonlySet<string>, right: ReadonlySet<string>): boolean {
|
|
338
|
-
if (left.size !== right.size) return false;
|
|
339
|
-
for (const value of left) if (!right.has(value)) return false;
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { sessionEntryToContextMessages, type ContextEvent, type SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { isContextProjectionMessage, isLegacyContextMessage } from "../../shared/context-messages.ts";
|
|
3
|
-
import type { ActiveWorkingMemoryState } from "./state.ts";
|
|
4
|
-
|
|
5
|
-
type ContextMessage = ContextEvent["messages"][number];
|
|
6
|
-
|
|
7
|
-
export interface MemoryUnit {
|
|
8
|
-
ref: string;
|
|
9
|
-
label: string;
|
|
10
|
-
preview: string;
|
|
11
|
-
order: number;
|
|
12
|
-
messages: ContextMessage[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const REFERENCE_CATALOG_TYPE = "tau.working-memory.references";
|
|
16
|
-
|
|
17
|
-
export function buildMemoryCatalog(branch: readonly SessionEntry[]): Map<string, MemoryUnit> {
|
|
18
|
-
const catalog = new Map<string, MemoryUnit>();
|
|
19
|
-
for (let order = 0; order < branch.length; order += 1) {
|
|
20
|
-
const entry = branch[order];
|
|
21
|
-
if (!entry) continue;
|
|
22
|
-
const message = sessionEntryToContextMessages(entry)[0];
|
|
23
|
-
if (!message) continue;
|
|
24
|
-
if (message.role === "assistant") {
|
|
25
|
-
const calls = message.content.filter((block) => block.type === "toolCall");
|
|
26
|
-
const frameworkCall = calls.some((call) => call.name === "working_memory");
|
|
27
|
-
if (frameworkCall) continue;
|
|
28
|
-
const text = message.content.filter((block) => block.type === "text");
|
|
29
|
-
if (text.length > 0) {
|
|
30
|
-
const ref = `m:${entry.id}`;
|
|
31
|
-
catalog.set(ref, {
|
|
32
|
-
ref,
|
|
33
|
-
label: "assistant",
|
|
34
|
-
preview: previewAssistant(text),
|
|
35
|
-
order,
|
|
36
|
-
messages: [{ ...message, content: text }],
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (message.role !== "user") continue;
|
|
42
|
-
const ref = `m:${entry.id}`;
|
|
43
|
-
catalog.set(ref, {
|
|
44
|
-
ref,
|
|
45
|
-
label: "user",
|
|
46
|
-
preview: previewUser(message),
|
|
47
|
-
order,
|
|
48
|
-
messages: [message],
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
return catalog;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function collectPrunedRowIds(branch: readonly SessionEntry[], before: number): string[] {
|
|
55
|
-
const rowIds = new Set<string>();
|
|
56
|
-
for (let index = 0; index < before; index += 1) {
|
|
57
|
-
const entry = branch[index];
|
|
58
|
-
if (!entry) continue;
|
|
59
|
-
for (const message of sessionEntryToContextMessages(entry)) {
|
|
60
|
-
if (message.role === "assistant") {
|
|
61
|
-
for (const block of message.content) {
|
|
62
|
-
if (block.type === "toolCall" && block.name !== "working_memory") rowIds.add(block.id);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (
|
|
66
|
-
message.role === "custom" &&
|
|
67
|
-
(message.customType === "tau.explore.outline" || message.customType === "tau.autoread") &&
|
|
68
|
-
isRecord(message.details) &&
|
|
69
|
-
typeof message.details.rowId === "string"
|
|
70
|
-
) {
|
|
71
|
-
rowIds.add(message.details.rowId);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return [...rowIds];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function projectWorkingMemory(
|
|
79
|
-
messages: readonly ContextMessage[],
|
|
80
|
-
state: ActiveWorkingMemoryState,
|
|
81
|
-
branch: readonly SessionEntry[],
|
|
82
|
-
contextEntries: readonly SessionEntry[],
|
|
83
|
-
): ContextMessage[] {
|
|
84
|
-
const catalog = buildMemoryCatalog(branch);
|
|
85
|
-
const refsByMessage = referenceCurrentMessages(messages, contextEntries, catalog);
|
|
86
|
-
if (state.latestAnchorToolCallId === undefined) {
|
|
87
|
-
return addReferenceCatalog(messages, refsByMessage);
|
|
88
|
-
}
|
|
89
|
-
const anchorIndex = findAnchor(messages, state.latestAnchorToolCallId);
|
|
90
|
-
if (anchorIndex < 0) return addReferenceCatalog(messages, refsByMessage);
|
|
91
|
-
|
|
92
|
-
const retained = state.retainedRefs
|
|
93
|
-
.flatMap((ref) => {
|
|
94
|
-
const unit = catalog.get(ref);
|
|
95
|
-
return unit ? [unit] : [];
|
|
96
|
-
})
|
|
97
|
-
.sort((left, right) => left.order - right.order);
|
|
98
|
-
const projected: ContextMessage[] = [];
|
|
99
|
-
const projectedRefs: MemoryUnit[][] = [];
|
|
100
|
-
for (const unit of retained) {
|
|
101
|
-
projected.push(...unit.messages);
|
|
102
|
-
projectedRefs.push(...unit.messages.map((_, index) => (index === unit.messages.length - 1 ? [unit] : [])));
|
|
103
|
-
}
|
|
104
|
-
for (let index = anchorIndex; index < messages.length; index += 1) {
|
|
105
|
-
const message = messages[index];
|
|
106
|
-
if (!message) continue;
|
|
107
|
-
projected.push(index === anchorIndex ? sanitizeAnchor(message, state.latestAnchorToolCallId) : message);
|
|
108
|
-
projectedRefs.push(refsByMessage[index] ?? []);
|
|
109
|
-
}
|
|
110
|
-
return addReferenceCatalog(projected, projectedRefs);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function referenceCurrentMessages(
|
|
114
|
-
messages: readonly ContextMessage[],
|
|
115
|
-
entries: readonly SessionEntry[],
|
|
116
|
-
catalog: ReadonlyMap<string, MemoryUnit>,
|
|
117
|
-
): MemoryUnit[][] {
|
|
118
|
-
const generated = entries
|
|
119
|
-
.flatMap((entry) => sessionEntryToContextMessages(entry).map((message) => ({ entry, message })))
|
|
120
|
-
.filter(({ message }) => !isLegacyContextMessage(message));
|
|
121
|
-
const sessionMessages = messages.filter(
|
|
122
|
-
(message) => !isContextProjectionMessage(message) && !isLegacyContextMessage(message),
|
|
123
|
-
);
|
|
124
|
-
if (generated.length !== sessionMessages.length) return messages.map(() => []);
|
|
125
|
-
let generatedIndex = 0;
|
|
126
|
-
return messages.map((currentMessage) => {
|
|
127
|
-
if (isContextProjectionMessage(currentMessage) || isLegacyContextMessage(currentMessage)) return [];
|
|
128
|
-
const current = generated[generatedIndex];
|
|
129
|
-
generatedIndex += 1;
|
|
130
|
-
if (!current) return [];
|
|
131
|
-
const { entry, message } = current;
|
|
132
|
-
if (message.role !== "user" && message.role !== "assistant") return [];
|
|
133
|
-
const unit = catalog.get(`m:${entry.id}`);
|
|
134
|
-
return unit && currentMessage.role === message.role ? [unit] : [];
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function addReferenceCatalog(
|
|
139
|
-
messages: readonly ContextMessage[],
|
|
140
|
-
unitsByMessage: readonly MemoryUnit[][],
|
|
141
|
-
): ContextMessage[] {
|
|
142
|
-
const projected: ContextMessage[] = [
|
|
143
|
-
{
|
|
144
|
-
role: "custom",
|
|
145
|
-
customType: REFERENCE_CATALOG_TYPE,
|
|
146
|
-
content:
|
|
147
|
-
"Working-memory references follow their source messages. Use these exact refs in working_memory.keep; do not repeat them in responses.",
|
|
148
|
-
display: false,
|
|
149
|
-
timestamp: 0,
|
|
150
|
-
},
|
|
151
|
-
];
|
|
152
|
-
let deferredAssistantUnits: MemoryUnit[] = [];
|
|
153
|
-
let pendingToolCallIds = new Set<string>();
|
|
154
|
-
for (let index = 0; index < messages.length; index += 1) {
|
|
155
|
-
const message = messages[index];
|
|
156
|
-
if (!message) continue;
|
|
157
|
-
projected.push(message);
|
|
158
|
-
const unique = new Map((unitsByMessage[index] ?? []).map((unit) => [unit.ref, unit]));
|
|
159
|
-
if (message.role === "assistant") {
|
|
160
|
-
pendingToolCallIds = new Set(
|
|
161
|
-
message.content.filter((block) => block.type === "toolCall").map((block) => block.id),
|
|
162
|
-
);
|
|
163
|
-
if (pendingToolCallIds.size > 0) {
|
|
164
|
-
deferredAssistantUnits = [...unique.values()];
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (message.role === "toolResult" && pendingToolCallIds.delete(message.toolCallId)) {
|
|
169
|
-
if (pendingToolCallIds.size > 0) continue;
|
|
170
|
-
for (const unit of deferredAssistantUnits) unique.set(unit.ref, unit);
|
|
171
|
-
deferredAssistantUnits = [];
|
|
172
|
-
}
|
|
173
|
-
if (unique.size === 0) continue;
|
|
174
|
-
projected.push({
|
|
175
|
-
role: "custom",
|
|
176
|
-
customType: REFERENCE_CATALOG_TYPE,
|
|
177
|
-
content: [...unique.values()]
|
|
178
|
-
.sort((left, right) => left.order - right.order)
|
|
179
|
-
.map((unit) => `${unit.ref} (${unit.label}): ${unit.preview}`)
|
|
180
|
-
.join("\n"),
|
|
181
|
-
display: false,
|
|
182
|
-
timestamp: 0,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
return projected;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function sanitizeAnchor(message: ContextMessage, id: string): ContextMessage {
|
|
189
|
-
if (message.role !== "assistant") return message;
|
|
190
|
-
return {
|
|
191
|
-
...message,
|
|
192
|
-
content: message.content.map((block) =>
|
|
193
|
-
block.type === "toolCall" && block.id === id && block.name === "working_memory"
|
|
194
|
-
? { ...block, arguments: { checkpoint: true } }
|
|
195
|
-
: block,
|
|
196
|
-
),
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function findAnchor(messages: readonly ContextMessage[], id: string): number {
|
|
201
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
202
|
-
const message = messages[index];
|
|
203
|
-
if (
|
|
204
|
-
message?.role === "assistant" &&
|
|
205
|
-
message.content.some(
|
|
206
|
-
(block) => block.type === "toolCall" && block.id === id && block.name === "working_memory",
|
|
207
|
-
)
|
|
208
|
-
) {
|
|
209
|
-
return index;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return -1;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function previewAssistant(blocks: ReadonlyArray<{ type: string }>): string {
|
|
216
|
-
for (const block of blocks) {
|
|
217
|
-
if (block.type === "text" && "text" in block && typeof block.text === "string") return compact(block.text);
|
|
218
|
-
}
|
|
219
|
-
return "";
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function previewUser(message: Extract<ContextMessage, { role: "user" }>): string {
|
|
223
|
-
if (typeof message.content === "string") return compact(message.content);
|
|
224
|
-
const text = message.content.find((part) => part.type === "text");
|
|
225
|
-
return text?.type === "text" ? compact(text.text) : "";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function compact(value: string): string {
|
|
229
|
-
const text = value.replaceAll(/\s+/g, " ").trim();
|
|
230
|
-
return text.length <= 120 ? text : `${text.slice(0, 119)}…`;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
234
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
235
|
-
}
|