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.
Files changed (39) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +54 -0
  3. package/package.json +6 -1
  4. package/src/agent/runtime-session.ts +79 -0
  5. package/src/cli/config.ts +29 -0
  6. package/src/cli/model-profiles.ts +84 -1
  7. package/src/cli/public-config-contract.ts +82 -0
  8. package/src/cli/runner-dependencies.ts +8 -0
  9. package/src/cli/tui-memory.ts +67 -0
  10. package/src/cli/tui-runner.tsx +31 -0
  11. package/src/context/context-policy.ts +2 -2
  12. package/src/events/stdout-event-printer.ts +1 -0
  13. package/src/memory/contracts.ts +148 -0
  14. package/src/memory/embedding-client.ts +105 -0
  15. package/src/memory/memory-coordinator.ts +556 -0
  16. package/src/memory/memory-extractor.ts +231 -0
  17. package/src/memory/memory-log.ts +88 -0
  18. package/src/memory/memory-search-tool.ts +100 -0
  19. package/src/memory/memory-store.ts +687 -0
  20. package/src/memory/vector.ts +153 -0
  21. package/src/model/fake-model-client.ts +1068 -3
  22. package/src/observation/observation-builder.ts +20 -0
  23. package/src/session/session-store.ts +123 -0
  24. package/src/tools/registry.ts +4 -0
  25. package/src/tools/types.ts +16 -0
  26. package/src/tui/app.tsx +211 -93
  27. package/src/tui/clipboard.ts +22 -0
  28. package/src/tui/components/assistant-markdown.tsx +27 -26
  29. package/src/tui/components/background-tasks.tsx +7 -2
  30. package/src/tui/components/file-viewer.tsx +2 -2
  31. package/src/tui/components/footer.tsx +5 -6
  32. package/src/tui/components/memory-browser.tsx +151 -0
  33. package/src/tui/components/resume-session-picker.tsx +3 -1
  34. package/src/tui/components/timeline.tsx +9 -11
  35. package/src/tui/event-store.ts +22 -2
  36. package/src/tui/shiki-highlighter.ts +104 -0
  37. package/src/tui/slash-commands.ts +12 -0
  38. package/src/tui/tui-projection-store.ts +33 -0
  39. 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",
@@ -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.");
@@ -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;