tinker-agent 1.4.0 → 1.5.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.
- package/CHANGELOG.md +40 -1
- package/README.md +54 -0
- package/package.json +6 -1
- package/src/agent/runtime-session.ts +79 -0
- package/src/cli/config.ts +29 -0
- package/src/cli/model-profiles.ts +84 -1
- package/src/cli/public-config-contract.ts +82 -0
- package/src/cli/runner-dependencies.ts +8 -0
- package/src/cli/tui-memory.ts +67 -0
- package/src/cli/tui-runner.tsx +31 -0
- package/src/context/context-policy.ts +2 -2
- package/src/events/stdout-event-printer.ts +1 -0
- package/src/memory/contracts.ts +148 -0
- package/src/memory/embedding-client.ts +105 -0
- package/src/memory/memory-coordinator.ts +556 -0
- package/src/memory/memory-extractor.ts +231 -0
- package/src/memory/memory-log.ts +88 -0
- package/src/memory/memory-search-tool.ts +100 -0
- package/src/memory/memory-store.ts +687 -0
- package/src/memory/vector.ts +153 -0
- package/src/model/fake-model-client.ts +1068 -3
- package/src/observation/observation-builder.ts +20 -0
- package/src/session/session-store.ts +123 -0
- package/src/tools/registry.ts +4 -0
- package/src/tools/types.ts +16 -0
- package/src/tui/app.tsx +211 -93
- package/src/tui/clipboard.ts +22 -0
- package/src/tui/components/assistant-markdown.tsx +27 -26
- package/src/tui/components/background-tasks.tsx +7 -2
- package/src/tui/components/file-viewer.tsx +2 -2
- package/src/tui/components/footer.tsx +5 -6
- package/src/tui/components/memory-browser.tsx +151 -0
- package/src/tui/components/resume-session-picker.tsx +3 -1
- package/src/tui/components/timeline.tsx +9 -11
- package/src/tui/event-store.ts +22 -2
- package/src/tui/shiki-highlighter.ts +104 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/tui-projection-store.ts +33 -0
- package/src/tui/tui-session-controller.ts +14 -8
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
GenericToolRawResult,
|
|
6
6
|
GlobRawResult,
|
|
7
7
|
GrepRawResult,
|
|
8
|
+
MemorySearchRawResult,
|
|
8
9
|
McpToolRawResult,
|
|
9
10
|
ReadFileRawResult,
|
|
10
11
|
RecallRawResult,
|
|
@@ -33,6 +34,8 @@ export class ObservationBuilder {
|
|
|
33
34
|
return { content: renderReadObservation(input.raw) };
|
|
34
35
|
case "recall":
|
|
35
36
|
return { content: renderRecallObservation(input.raw) };
|
|
37
|
+
case "memory_search":
|
|
38
|
+
return { content: renderMemorySearchObservation(input.raw) };
|
|
36
39
|
case "skill":
|
|
37
40
|
return { content: renderSkillObservation(input.raw) };
|
|
38
41
|
case "write":
|
|
@@ -227,6 +230,23 @@ function renderRecallObservation(raw: RecallRawResult): string {
|
|
|
227
230
|
return [header, ...hits].join("\n\n");
|
|
228
231
|
}
|
|
229
232
|
|
|
233
|
+
function renderMemorySearchObservation(raw: MemorySearchRawResult): string {
|
|
234
|
+
if (!raw.ok) {
|
|
235
|
+
return `MemorySearch unavailable: ${raw.error}`;
|
|
236
|
+
}
|
|
237
|
+
if (raw.matches.length === 0) {
|
|
238
|
+
return "MemorySearch found no stored memories.";
|
|
239
|
+
}
|
|
240
|
+
const header = `MemorySearch returned ${raw.matches.length} derived memories. They may be stale or wrong; verify current workspace facts.`;
|
|
241
|
+
return [
|
|
242
|
+
header,
|
|
243
|
+
...raw.matches.map(
|
|
244
|
+
(match, index) =>
|
|
245
|
+
`${index + 1}. score=${match.score.toFixed(3)} created_at=${match.createdAt} workspace=${match.sourceWorkspace}\n ${match.text}`,
|
|
246
|
+
),
|
|
247
|
+
].join("\n\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
230
250
|
export function renderSkillObservation(raw: SkillRawResult): string {
|
|
231
251
|
if (!raw.ok) {
|
|
232
252
|
return `Skill failed for ${raw.name || "(unknown skill)"} (${raw.errorCode}): ${raw.error}`;
|
|
@@ -144,6 +144,29 @@ export type SessionImageInputCompatibility = {
|
|
|
144
144
|
readonly tokenEstimator?: InputTokenEstimatorCompatibility;
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
+
export type CompletedTurnMessageSnapshot =
|
|
148
|
+
| {
|
|
149
|
+
readonly ordinal: number;
|
|
150
|
+
readonly role: "user";
|
|
151
|
+
readonly content: string;
|
|
152
|
+
}
|
|
153
|
+
| {
|
|
154
|
+
readonly ordinal: number;
|
|
155
|
+
readonly role: "assistant";
|
|
156
|
+
readonly content: string | null;
|
|
157
|
+
readonly reasoningContent?: string | null;
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
readonly ordinal: number;
|
|
161
|
+
readonly role: "tool";
|
|
162
|
+
readonly name: string;
|
|
163
|
+
readonly content: string;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export type CompletedTurnSnapshot = {
|
|
167
|
+
readonly messages: readonly CompletedTurnMessageSnapshot[];
|
|
168
|
+
};
|
|
169
|
+
|
|
147
170
|
export type SessionCompatibilityContract = {
|
|
148
171
|
modelName: string;
|
|
149
172
|
profileName?: string;
|
|
@@ -2082,6 +2105,105 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2082
2105
|
});
|
|
2083
2106
|
}
|
|
2084
2107
|
|
|
2108
|
+
readCompletedTurnSnapshot(turnId: TurnId): CompletedTurnSnapshot {
|
|
2109
|
+
this.requireOpen();
|
|
2110
|
+
const turnRow = this.database
|
|
2111
|
+
.query("SELECT status FROM turns WHERE turn_id = ?")
|
|
2112
|
+
.get(turnId);
|
|
2113
|
+
const status = enumFromSql(
|
|
2114
|
+
recordFromSql(turnRow, "completed turn").status,
|
|
2115
|
+
["open", "completed", "failed", "cancelled", "interrupted"] as const,
|
|
2116
|
+
"turn status",
|
|
2117
|
+
);
|
|
2118
|
+
if (status !== "completed") {
|
|
2119
|
+
throw new Error(`Turn ${turnId} is not completed.`);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const rows = this.database
|
|
2123
|
+
.query(
|
|
2124
|
+
`SELECT ordinal, role, content, reasoning_content,
|
|
2125
|
+
reasoning_content_present, name
|
|
2126
|
+
FROM messages
|
|
2127
|
+
WHERE turn_id = ?
|
|
2128
|
+
ORDER BY ordinal`,
|
|
2129
|
+
)
|
|
2130
|
+
.all(turnId);
|
|
2131
|
+
if (rows.length === 0) {
|
|
2132
|
+
throw new Error(`Completed turn ${turnId} has no messages.`);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
let previousOrdinal = 0;
|
|
2136
|
+
const messages = rows.map((value): CompletedTurnMessageSnapshot => {
|
|
2137
|
+
const row = recordFromSql(value, "completed turn message");
|
|
2138
|
+
const ordinal = numberFromSql(row.ordinal, "completed turn ordinal");
|
|
2139
|
+
if (ordinal < 1 || ordinal <= previousOrdinal) {
|
|
2140
|
+
throw new Error("Completed turn message ordinals are invalid.");
|
|
2141
|
+
}
|
|
2142
|
+
previousOrdinal = ordinal;
|
|
2143
|
+
const role = enumFromSql(
|
|
2144
|
+
row.role,
|
|
2145
|
+
["user", "assistant", "tool"] as const,
|
|
2146
|
+
"completed turn message role",
|
|
2147
|
+
);
|
|
2148
|
+
if (role === "user") {
|
|
2149
|
+
if (
|
|
2150
|
+
row.reasoning_content !== null ||
|
|
2151
|
+
numberFromSql(row.reasoning_content_present, "reasoning_content_present") !==
|
|
2152
|
+
0 ||
|
|
2153
|
+
row.name !== null
|
|
2154
|
+
) {
|
|
2155
|
+
throw new Error("Completed user message fields are invalid.");
|
|
2156
|
+
}
|
|
2157
|
+
return Object.freeze({
|
|
2158
|
+
ordinal,
|
|
2159
|
+
role,
|
|
2160
|
+
content: stringFromSql(row.content, "completed user content"),
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
if (role === "assistant") {
|
|
2164
|
+
if (row.name !== null) {
|
|
2165
|
+
throw new Error("Completed assistant message name must be null.");
|
|
2166
|
+
}
|
|
2167
|
+
const reasoningPresent = numberFromSql(
|
|
2168
|
+
row.reasoning_content_present,
|
|
2169
|
+
"reasoning_content_present",
|
|
2170
|
+
);
|
|
2171
|
+
if (reasoningPresent !== 0 && reasoningPresent !== 1) {
|
|
2172
|
+
throw new Error("reasoning_content_present must be 0 or 1.");
|
|
2173
|
+
}
|
|
2174
|
+
if (reasoningPresent === 0 && row.reasoning_content !== null) {
|
|
2175
|
+
throw new Error("Absent assistant reasoning content must be null.");
|
|
2176
|
+
}
|
|
2177
|
+
return Object.freeze({
|
|
2178
|
+
ordinal,
|
|
2179
|
+
role,
|
|
2180
|
+
content: nullableTextFromSql(row.content, "completed assistant content"),
|
|
2181
|
+
...(reasoningPresent === 0
|
|
2182
|
+
? {}
|
|
2183
|
+
: {
|
|
2184
|
+
reasoningContent: nullableTextFromSql(
|
|
2185
|
+
row.reasoning_content,
|
|
2186
|
+
"completed assistant reasoning content",
|
|
2187
|
+
),
|
|
2188
|
+
}),
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
if (
|
|
2192
|
+
row.reasoning_content !== null ||
|
|
2193
|
+
numberFromSql(row.reasoning_content_present, "reasoning_content_present") !== 0
|
|
2194
|
+
) {
|
|
2195
|
+
throw new Error("Completed tool message reasoning fields are invalid.");
|
|
2196
|
+
}
|
|
2197
|
+
return Object.freeze({
|
|
2198
|
+
ordinal,
|
|
2199
|
+
role,
|
|
2200
|
+
name: stringFromSql(row.name, "completed tool name"),
|
|
2201
|
+
content: stringFromSql(row.content, "completed tool content"),
|
|
2202
|
+
});
|
|
2203
|
+
});
|
|
2204
|
+
return Object.freeze({ messages: Object.freeze(messages) });
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2085
2207
|
loadProtocolView(): ProtocolContextView {
|
|
2086
2208
|
this.requireOpen();
|
|
2087
2209
|
const imageAttachments = loadMessageImageAttachments(this.database);
|
|
@@ -4725,6 +4847,7 @@ export function decodeStoredToolRawResult(value: unknown): ToolRawResult {
|
|
|
4725
4847
|
"web_search",
|
|
4726
4848
|
"web_fetch",
|
|
4727
4849
|
"recall",
|
|
4850
|
+
"memory_search",
|
|
4728
4851
|
"skill",
|
|
4729
4852
|
"mcp",
|
|
4730
4853
|
"generic",
|
package/src/tools/registry.ts
CHANGED
|
@@ -133,6 +133,7 @@ export function createDefaultTooling(options: {
|
|
|
133
133
|
skillCatalog?: SkillCatalogSnapshot;
|
|
134
134
|
skillCoordinator?: SkillActivationCoordinator;
|
|
135
135
|
toolingConfig?: PublicToolingConfig;
|
|
136
|
+
memorySearch?: ToolExecutor;
|
|
136
137
|
}): DefaultTooling {
|
|
137
138
|
const snapshots: FileSnapshotStore = new Map();
|
|
138
139
|
const registry = new ToolRegistry();
|
|
@@ -170,6 +171,9 @@ export function createDefaultTooling(options: {
|
|
|
170
171
|
}),
|
|
171
172
|
);
|
|
172
173
|
registry.register(createRecallToolExecutor({ historyReader: options.historyReader }));
|
|
174
|
+
if (options.memorySearch !== undefined) {
|
|
175
|
+
registry.register(options.memorySearch);
|
|
176
|
+
}
|
|
173
177
|
if (options.skillCatalog !== undefined) {
|
|
174
178
|
if (options.skillCatalog.skills.size === 0) {
|
|
175
179
|
throw new Error("An empty Agent Skill catalog must not register tooling.");
|
package/src/tools/types.ts
CHANGED
|
@@ -245,6 +245,21 @@ export type RecallGetRawResult =
|
|
|
245
245
|
|
|
246
246
|
export type RecallRawResult = RecallSearchRawResult | RecallGetRawResult;
|
|
247
247
|
|
|
248
|
+
export type MemorySearchRawResult =
|
|
249
|
+
| {
|
|
250
|
+
ok: true;
|
|
251
|
+
matches: readonly {
|
|
252
|
+
text: string;
|
|
253
|
+
score: number;
|
|
254
|
+
sourceWorkspace: string;
|
|
255
|
+
createdAt: string;
|
|
256
|
+
}[];
|
|
257
|
+
}
|
|
258
|
+
| {
|
|
259
|
+
ok: false;
|
|
260
|
+
error: string;
|
|
261
|
+
};
|
|
262
|
+
|
|
248
263
|
export type SkillRawResult =
|
|
249
264
|
| {
|
|
250
265
|
ok: true;
|
|
@@ -307,6 +322,7 @@ export type ToolRawResultByKind = {
|
|
|
307
322
|
web_search: WebSearchRawResult;
|
|
308
323
|
web_fetch: WebFetchRawResult;
|
|
309
324
|
recall: RecallRawResult;
|
|
325
|
+
memory_search: MemorySearchRawResult;
|
|
310
326
|
skill: SkillRawResult;
|
|
311
327
|
mcp: McpToolRawResult;
|
|
312
328
|
generic: GenericToolRawResult;
|