tinker-agent 2.5.0 → 2.7.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +30 -7
  3. package/package.json +1 -1
  4. package/src/agent/loop.ts +22 -0
  5. package/src/agent/runtime-session.ts +158 -0
  6. package/src/cli/main.ts +2 -0
  7. package/src/cli/public-config-contract.ts +1 -1
  8. package/src/cli/tui-runner.tsx +16 -1
  9. package/src/events/stdout-event-printer.ts +14 -0
  10. package/src/events/types.ts +9 -0
  11. package/src/memory/contracts.ts +46 -0
  12. package/src/memory/memory-coordinator.ts +445 -4
  13. package/src/memory/memory-create-tool.ts +117 -0
  14. package/src/memory/memory-delete-tool.ts +88 -0
  15. package/src/memory/memory-store.ts +239 -0
  16. package/src/memory/memory-update-tool.ts +142 -0
  17. package/src/observation/observation-builder.ts +70 -0
  18. package/src/session/session-clone-helpers.ts +249 -0
  19. package/src/session/session-compatibility-codec.ts +401 -0
  20. package/src/session/session-store-contracts.ts +304 -0
  21. package/src/session/session-store-filesystem.ts +245 -0
  22. package/src/session/session-store-record-codecs.ts +1064 -0
  23. package/src/session/session-store-value-codecs.ts +156 -0
  24. package/src/session/session-store.ts +234 -3023
  25. package/src/session/session-tool-result-codec.ts +594 -0
  26. package/src/tools/ask-user.ts +100 -0
  27. package/src/tools/grep.ts +1 -3
  28. package/src/tools/registry.ts +30 -0
  29. package/src/tools/types.ts +71 -0
  30. package/src/tui/app.tsx +35 -5
  31. package/src/tui/components/ask-user.tsx +61 -0
  32. package/src/tui/components/footer.tsx +14 -1
  33. package/src/tui/components/prompt-input.tsx +4 -0
  34. package/src/tui/components/resume-session-picker.tsx +118 -37
  35. package/src/tui/event-store.ts +57 -0
  36. package/src/tui/tui-session-controller.ts +8 -0
@@ -0,0 +1,156 @@
1
+ import type { MeasuredContextAnchor } from "../agent/context-meter";
2
+
3
+ export function recordFromSql(value: unknown, name: string): Record<string, unknown> {
4
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
5
+ throw new Error(`${name} must be an object.`);
6
+ }
7
+ return value as Record<string, unknown>;
8
+ }
9
+
10
+ export function assertObjectKeys(
11
+ record: Record<string, unknown>,
12
+ allowed: readonly string[],
13
+ required: readonly string[],
14
+ name: string,
15
+ ): void {
16
+ const allowedSet = new Set(allowed);
17
+ const unknown = Object.keys(record).filter((key) => !allowedSet.has(key));
18
+ const missing = required.filter((key) => !(key in record));
19
+ if (unknown.length > 0 || missing.length > 0) {
20
+ throw new Error(
21
+ `${name} has invalid keys; unknown=${unknown.join(",") || "none"} missing=${missing.join(",") || "none"}.`,
22
+ );
23
+ }
24
+ }
25
+
26
+ export function stringFromSql(value: unknown, name: string): string {
27
+ if (typeof value !== "string" || value === "") {
28
+ throw new Error(`${name} must be a non-empty string.`);
29
+ }
30
+ return value;
31
+ }
32
+
33
+ export function sha256FromSql(value: unknown, name: string): string {
34
+ const hash = stringFromSql(value, name);
35
+ if (!/^[0-9a-f]{64}$/.test(hash)) {
36
+ throw new Error(`${name} must be a lowercase SHA-256 digest.`);
37
+ }
38
+ return hash;
39
+ }
40
+
41
+ export function assertMeasuredContextAnchor(anchor: MeasuredContextAnchor): void {
42
+ for (const [name, value] of [
43
+ ["promptTokens", anchor.promptTokens],
44
+ ["completionTokens", anchor.completionTokens],
45
+ ["totalTokens", anchor.totalTokens],
46
+ ["segmentCount", anchor.segmentCount],
47
+ ] as const) {
48
+ if (!Number.isSafeInteger(value) || value < 0) {
49
+ throw new Error(
50
+ `Measured context anchor ${name} must be a non-negative safe integer; received ${value}.`,
51
+ );
52
+ }
53
+ }
54
+ if (anchor.totalTokens !== anchor.promptTokens + anchor.completionTokens) {
55
+ throw new Error(
56
+ "Measured context anchor totalTokens must equal promptTokens + completionTokens.",
57
+ );
58
+ }
59
+ for (const [name, value] of [
60
+ ["prefixHash", anchor.prefixHash],
61
+ ["requestConfigHash", anchor.requestConfigHash],
62
+ ["toolSchemaHash", anchor.toolSchemaHash],
63
+ ] as const) {
64
+ if (!/^[0-9a-f]{64}$/.test(value)) {
65
+ throw new Error(`Measured context anchor ${name} must be a SHA-256 digest.`);
66
+ }
67
+ }
68
+ }
69
+
70
+ export function nullableStringFromSql(value: unknown, name: string): string | null {
71
+ return value === null ? null : stringFromSql(value, name);
72
+ }
73
+
74
+ export function nullableTextFromSql(value: unknown, name: string): string | null {
75
+ if (value === null) {
76
+ return null;
77
+ }
78
+ if (typeof value !== "string") {
79
+ throw new Error(`${name} must be a string or null.`);
80
+ }
81
+ return value;
82
+ }
83
+
84
+ export function numberFromSql(value: unknown, name: string): number {
85
+ const number = typeof value === "bigint" ? Number(value) : value;
86
+ if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) {
87
+ throw new Error(`${name} must be a safe non-negative integer.`);
88
+ }
89
+ return number;
90
+ }
91
+
92
+ export function nullableNumberFromSql(value: unknown, name: string): number | null {
93
+ return value === null ? null : numberFromSql(value, name);
94
+ }
95
+
96
+ export function numberFromJson(value: unknown, name: string): number {
97
+ if (!Number.isSafeInteger(value) || (value as number) < 1) {
98
+ throw new Error(`${name} must be a positive safe integer.`);
99
+ }
100
+ return value as number;
101
+ }
102
+
103
+ export function safeJsonInteger(value: unknown, name: string): number {
104
+ if (!Number.isSafeInteger(value)) {
105
+ throw new Error(`${name} must be a safe integer.`);
106
+ }
107
+ return value as number;
108
+ }
109
+
110
+ export function nonNegativeJsonInteger(value: unknown, name: string): number {
111
+ const number = safeJsonInteger(value, name);
112
+ if (number < 0) throw new Error(`${name} must be non-negative.`);
113
+ return number;
114
+ }
115
+
116
+ export function positiveJsonInteger(value: unknown, name: string): number {
117
+ const number = safeJsonInteger(value, name);
118
+ if (number < 1) throw new Error(`${name} must be positive.`);
119
+ return number;
120
+ }
121
+
122
+ export function nonEmptyStringFromJson(value: unknown, name: string): string {
123
+ const text = stringFromSql(value, name);
124
+ if (text.trim() === "") throw new Error(`${name} must not be empty.`);
125
+ return text;
126
+ }
127
+
128
+ export function enumFromSql<const T extends readonly string[]>(
129
+ value: unknown,
130
+ values: T,
131
+ name: string,
132
+ ): T[number] {
133
+ if (typeof value !== "string" || !values.includes(value)) {
134
+ throw new Error(`${name} has unsupported value ${JSON.stringify(value)}.`);
135
+ }
136
+ return value;
137
+ }
138
+
139
+ export function timestampFromSql(value: unknown, name: string): string {
140
+ return timestampValue(stringFromSql(value, name), name);
141
+ }
142
+
143
+ export function timestampValue(value: string, name: string): string {
144
+ if (Number.isNaN(Date.parse(value)) || !value.endsWith("Z")) {
145
+ throw new Error(`${name} must be a UTC ISO-8601 timestamp.`);
146
+ }
147
+ return value;
148
+ }
149
+
150
+ export function parseJson(value: string, name: string): unknown {
151
+ try {
152
+ return JSON.parse(value);
153
+ } catch (error) {
154
+ throw new Error(`${name} is not valid JSON.`, { cause: error });
155
+ }
156
+ }