tinker-agent 1.0.65

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 (110) hide show
  1. package/README.md +173 -0
  2. package/package.json +78 -0
  3. package/patches/markdansi@0.3.2.patch +37 -0
  4. package/src/agent/context-builder.ts +43 -0
  5. package/src/agent/context-meter.ts +310 -0
  6. package/src/agent/loop.ts +525 -0
  7. package/src/agent/runtime-session.ts +1212 -0
  8. package/src/agent/session-ledger.ts +828 -0
  9. package/src/agent/turn-cancellation.ts +44 -0
  10. package/src/agent/types.ts +77 -0
  11. package/src/cli/config.ts +283 -0
  12. package/src/cli/index.ts +29 -0
  13. package/src/cli/model-profiles.ts +289 -0
  14. package/src/cli/run-runner.ts +107 -0
  15. package/src/cli/tui-runner.tsx +290 -0
  16. package/src/context/compiled-context-hash.ts +138 -0
  17. package/src/context/compiled-context-validator.ts +209 -0
  18. package/src/context/context-manager.ts +362 -0
  19. package/src/context/context-policy.ts +8 -0
  20. package/src/context/context-protocol-validator.ts +463 -0
  21. package/src/context/context-revision-compiler.ts +281 -0
  22. package/src/context/context-revision.ts +111 -0
  23. package/src/context/context-source.ts +30 -0
  24. package/src/context/context-swap-renderer.ts +272 -0
  25. package/src/context/protocol-frame.ts +240 -0
  26. package/src/context/swap-planner.ts +725 -0
  27. package/src/events/append-private-file.ts +16 -0
  28. package/src/events/bash-result-detail.ts +70 -0
  29. package/src/events/composite-event-sink.ts +82 -0
  30. package/src/events/event-sink.ts +16 -0
  31. package/src/events/jsonl-event-log.ts +13 -0
  32. package/src/events/observation-text-log.ts +195 -0
  33. package/src/events/stdout-event-printer.ts +396 -0
  34. package/src/events/types.ts +263 -0
  35. package/src/ids/runtime-id.ts +68 -0
  36. package/src/ids/uuid-v7.ts +5 -0
  37. package/src/instructions/project-instructions.ts +242 -0
  38. package/src/mcp/mcp-config.ts +144 -0
  39. package/src/mcp/mcp-manager.ts +216 -0
  40. package/src/mcp/mcp-tool-executor.ts +178 -0
  41. package/src/model/committed-prefix-auditor.ts +68 -0
  42. package/src/model/fake-model-client.ts +280 -0
  43. package/src/model/model-client.ts +64 -0
  44. package/src/model/model-context-profile.ts +134 -0
  45. package/src/model/model-request-preflight.ts +120 -0
  46. package/src/model/openai-chat-mapping.ts +444 -0
  47. package/src/model/openai-chat-model-client.ts +190 -0
  48. package/src/model/prompt-prefix-hash.ts +47 -0
  49. package/src/model/token-estimator.ts +148 -0
  50. package/src/observation/observation-builder.ts +481 -0
  51. package/src/session/resume-projection.ts +616 -0
  52. package/src/session/session-catalog.ts +270 -0
  53. package/src/session/session-errors.ts +121 -0
  54. package/src/session/session-history-reader.ts +535 -0
  55. package/src/session/session-lock.ts +291 -0
  56. package/src/session/session-schema.ts +741 -0
  57. package/src/session/session-store.ts +3067 -0
  58. package/src/session/sqlite-session-ledger.ts +153 -0
  59. package/src/tools/bash-task.ts +617 -0
  60. package/src/tools/bash.ts +450 -0
  61. package/src/tools/cwd-state.ts +22 -0
  62. package/src/tools/edit.ts +428 -0
  63. package/src/tools/file-diff.ts +116 -0
  64. package/src/tools/glob.ts +202 -0
  65. package/src/tools/grep.ts +550 -0
  66. package/src/tools/hash.ts +9 -0
  67. package/src/tools/path-safety.ts +33 -0
  68. package/src/tools/read.ts +319 -0
  69. package/src/tools/recall.ts +400 -0
  70. package/src/tools/registry.ts +213 -0
  71. package/src/tools/ripgrep.ts +220 -0
  72. package/src/tools/task-list.ts +59 -0
  73. package/src/tools/task-output-snapshot.ts +47 -0
  74. package/src/tools/task-output-tool.ts +62 -0
  75. package/src/tools/task-output.ts +159 -0
  76. package/src/tools/task-stop.ts +59 -0
  77. package/src/tools/task-tool-args.ts +29 -0
  78. package/src/tools/types.ts +330 -0
  79. package/src/tools/web-fetch/backend.ts +27 -0
  80. package/src/tools/web-fetch/browser-backend.ts +126 -0
  81. package/src/tools/web-fetch/exa-backend.ts +172 -0
  82. package/src/tools/web-fetch/index.ts +298 -0
  83. package/src/tools/web-fetch/local-backend.ts +267 -0
  84. package/src/tools/web-fetch/refiner.ts +78 -0
  85. package/src/tools/web-fetch/route.ts +95 -0
  86. package/src/tools/web-search.ts +300 -0
  87. package/src/tools/write.ts +244 -0
  88. package/src/tui/app.tsx +497 -0
  89. package/src/tui/components/assistant-markdown.tsx +47 -0
  90. package/src/tui/components/background-tasks.tsx +92 -0
  91. package/src/tui/components/bash-result-view.tsx +47 -0
  92. package/src/tui/components/context-status.tsx +127 -0
  93. package/src/tui/components/diff-view.tsx +151 -0
  94. package/src/tui/components/file-viewer.tsx +212 -0
  95. package/src/tui/components/footer.tsx +60 -0
  96. package/src/tui/components/header.tsx +21 -0
  97. package/src/tui/components/model-picker.tsx +142 -0
  98. package/src/tui/components/prompt-input.tsx +432 -0
  99. package/src/tui/components/resume-session-picker.tsx +273 -0
  100. package/src/tui/components/timeline.tsx +121 -0
  101. package/src/tui/context-format.ts +24 -0
  102. package/src/tui/event-store.ts +865 -0
  103. package/src/tui/git-branch.ts +23 -0
  104. package/src/tui/line-editor.ts +157 -0
  105. package/src/tui/prompt-history.ts +94 -0
  106. package/src/tui/slash-commands.ts +126 -0
  107. package/src/tui/tui-projection-policy.ts +35 -0
  108. package/src/tui/tui-projection-store.ts +123 -0
  109. package/src/tui/tui-session-controller.ts +170 -0
  110. package/src/tui/view-file.ts +122 -0
@@ -0,0 +1,16 @@
1
+ import path from "node:path";
2
+ import { chmod, mkdir, open } from "node:fs/promises";
3
+
4
+ export async function appendPrivateFile(
5
+ filePath: string,
6
+ content: string,
7
+ ): Promise<void> {
8
+ await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
9
+ const handle = await open(filePath, "a", 0o600);
10
+ try {
11
+ await handle.writeFile(content, "utf8");
12
+ } finally {
13
+ await handle.close();
14
+ }
15
+ await chmod(filePath, 0o600);
16
+ }
@@ -0,0 +1,70 @@
1
+ const successPreviewLines = 5;
2
+ const failurePreviewLines = 15;
3
+
4
+ export type BashDisplayDetail = {
5
+ command: string;
6
+ outputPreview?: string[];
7
+ omittedOutputLines?: number;
8
+ outputFilePath?: string;
9
+ };
10
+
11
+ export function bashCommandFromArgs(args: unknown): string | undefined {
12
+ return nonEmptyString(asRecord(args).command);
13
+ }
14
+
15
+ export function bashResultDetail(raw: unknown): BashDisplayDetail | undefined {
16
+ const rawRecord = asRecord(raw);
17
+ const taskRecord = asRecord(rawRecord.task);
18
+ const command =
19
+ nonEmptyString(rawRecord.command) ?? nonEmptyString(taskRecord.command);
20
+
21
+ if (command === undefined) {
22
+ return undefined;
23
+ }
24
+
25
+ const preview = typeof rawRecord.preview === "string" ? rawRecord.preview : "";
26
+ const previewLines = preview === "" ? [] : preview.split("\n");
27
+ const maxLines = rawRecord.ok === true ? successPreviewLines : failurePreviewLines;
28
+ const outputPreview = previewLines.slice(-maxLines).map(sanitizeOutputLine);
29
+ const totalLines =
30
+ typeof rawRecord.outputLines === "number"
31
+ ? rawRecord.outputLines
32
+ : previewLines.length;
33
+ const omittedOutputLines = Math.max(0, totalLines - outputPreview.length);
34
+
35
+ return {
36
+ command,
37
+ outputPreview,
38
+ omittedOutputLines,
39
+ outputFilePath:
40
+ nonEmptyString(rawRecord.outputFilePath) ??
41
+ nonEmptyString(taskRecord.outputFilePath),
42
+ };
43
+ }
44
+
45
+ // Terminal output routinely carries ANSI escapes and control characters that
46
+ // would corrupt the Ink layout, so both patterns intentionally match them.
47
+ /* eslint-disable no-control-regex */
48
+ const ansiEscapePattern = new RegExp(
49
+ "\\u001b(?:\\[[0-9;?]*[@-~]|\\][^\\u0007\\u001b]*(?:\\u0007|\\u001b\\\\)?|[@-Z\\\\-_])",
50
+ "g",
51
+ );
52
+ const controlCharPattern = new RegExp("[\\u0000-\\u0008\\u000b-\\u001f\\u007f]", "g");
53
+ /* eslint-enable no-control-regex */
54
+
55
+ export function sanitizeOutputLine(line: string): string {
56
+ return line
57
+ .replace(ansiEscapePattern, "")
58
+ .replace(controlCharPattern, "")
59
+ .replaceAll("\t", " ");
60
+ }
61
+
62
+ function nonEmptyString(value: unknown): string | undefined {
63
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
64
+ }
65
+
66
+ function asRecord(value: unknown): Record<string, unknown> {
67
+ return typeof value === "object" && value !== null && !Array.isArray(value)
68
+ ? (value as Record<string, unknown>)
69
+ : {};
70
+ }
@@ -0,0 +1,82 @@
1
+ import type {
2
+ EventSink,
3
+ EventSinkAppendResult,
4
+ EventSinkDiagnostic,
5
+ } from "./event-sink";
6
+ import type { AgentEvent } from "./types";
7
+
8
+ export class CompositeEventSink implements EventSink {
9
+ private tail: Promise<void> = Promise.resolve();
10
+ private readonly requiredSinks: EventSink[];
11
+ private readonly auxiliarySinks: EventSink[];
12
+ private readonly disabledAuxiliarySinks = new Set<EventSink>();
13
+
14
+ constructor(input: {
15
+ requiredSinks: EventSink[];
16
+ auxiliarySinks: EventSink[];
17
+ }) {
18
+ this.requiredSinks = input.requiredSinks;
19
+ this.auxiliarySinks = input.auxiliarySinks;
20
+ }
21
+
22
+ append(event: AgentEvent): Promise<void | EventSinkAppendResult> {
23
+ const write = this.tail.then(() => this.appendToSinks(event));
24
+ this.tail = write.then(
25
+ () => undefined,
26
+ () => undefined,
27
+ );
28
+ return write;
29
+ }
30
+
31
+ private async appendToSinks(
32
+ event: AgentEvent,
33
+ ): Promise<void | EventSinkAppendResult> {
34
+ const requiredErrors: Error[] = [];
35
+ for (const sink of this.requiredSinks) {
36
+ try {
37
+ await sink.append(event);
38
+ } catch (error) {
39
+ requiredErrors.push(
40
+ new Error(
41
+ `Required event sink ${sinkName(sink)} failed while appending ${event.type}.`,
42
+ { cause: error },
43
+ ),
44
+ );
45
+ }
46
+ }
47
+
48
+ const diagnostics: EventSinkDiagnostic[] = [];
49
+ for (const sink of this.auxiliarySinks) {
50
+ if (this.disabledAuxiliarySinks.has(sink)) {
51
+ continue;
52
+ }
53
+ try {
54
+ await sink.append(event);
55
+ } catch (error) {
56
+ this.disabledAuxiliarySinks.add(sink);
57
+ diagnostics.push({
58
+ sinkName: sinkName(sink),
59
+ failedEventType: event.type,
60
+ error: error instanceof Error ? error.message : String(error),
61
+ });
62
+ }
63
+ }
64
+
65
+ if (requiredErrors.length === 1) {
66
+ throw requiredErrors[0];
67
+ }
68
+ if (requiredErrors.length > 1) {
69
+ throw new AggregateError(
70
+ requiredErrors,
71
+ `Multiple required event sinks failed while appending ${event.type}.`,
72
+ );
73
+ }
74
+ if (diagnostics.length > 0) {
75
+ return { diagnostics };
76
+ }
77
+ }
78
+ }
79
+
80
+ function sinkName(sink: EventSink): string {
81
+ return sink.name ?? sink.constructor.name ?? "anonymous";
82
+ }
@@ -0,0 +1,16 @@
1
+ import type { AgentEvent, AgentEventType } from "./types";
2
+
3
+ export type EventSinkDiagnostic = {
4
+ sinkName: string;
5
+ failedEventType: AgentEventType;
6
+ error: string;
7
+ };
8
+
9
+ export type EventSinkAppendResult = {
10
+ diagnostics: EventSinkDiagnostic[];
11
+ };
12
+
13
+ export interface EventSink {
14
+ readonly name?: string;
15
+ append(event: AgentEvent): Promise<void | EventSinkAppendResult>;
16
+ }
@@ -0,0 +1,13 @@
1
+ import type { EventSink } from "./event-sink";
2
+ import type { AgentEvent } from "./types";
3
+ import { appendPrivateFile } from "./append-private-file";
4
+
5
+ export class JsonlEventLog implements EventSink {
6
+ readonly name = "jsonl-event-log";
7
+
8
+ constructor(private readonly filePath: string) {}
9
+
10
+ async append(event: AgentEvent): Promise<void> {
11
+ await appendPrivateFile(this.filePath, `${JSON.stringify(event)}\n`);
12
+ }
13
+ }
@@ -0,0 +1,195 @@
1
+ import type { ToolCall } from "../agent/types";
2
+ import type { ToolObservation } from "../observation/observation-builder";
3
+ import type { EventSink } from "./event-sink";
4
+ import type { AgentEvent, TurnFinishedData } from "./types";
5
+ import { appendPrivateFile } from "./append-private-file";
6
+
7
+ export class ObservationTextLog implements EventSink {
8
+ readonly name = "observation-text-log";
9
+
10
+ constructor(private readonly filePath: string) {}
11
+
12
+ async append(event: AgentEvent): Promise<void> {
13
+ const text = renderObservationLogBlock(event);
14
+ if (text === undefined) {
15
+ return;
16
+ }
17
+
18
+ await appendPrivateFile(this.filePath, text);
19
+ }
20
+ }
21
+
22
+ function renderObservationLogBlock(event: AgentEvent): string | undefined {
23
+ switch (event.type) {
24
+ case "session.started":
25
+ return renderSessionStarted(event);
26
+ case "session.resumed":
27
+ return [
28
+ "---",
29
+ "",
30
+ `Resumed: ${event.timestamp}`,
31
+ `Open count: ${event.data.openCount}`,
32
+ `Recovered synthetic completions: ${event.data.syntheticCompletionCount}`,
33
+ `Recall index rebuilt: ${event.data.recallIndexRebuilt}`,
34
+ "",
35
+ ].join("\n");
36
+ case "turn.started":
37
+ return renderTurnStarted(event);
38
+ case "assistant.progress":
39
+ return renderAssistantProgress(event);
40
+ case "tool.observation":
41
+ return renderToolObservation(event);
42
+ case "turn.finished":
43
+ return renderTurnFinished(event.data);
44
+ case "turn.cancelled":
45
+ return renderTurnCancelled(event);
46
+ case "turn.failed":
47
+ return renderTurnFailed(event.data.error);
48
+ default:
49
+ return undefined;
50
+ }
51
+ }
52
+
53
+ function renderSessionStarted(
54
+ event: Extract<AgentEvent, { type: "session.started" }>,
55
+ ): string {
56
+ return [
57
+ `# Tinker Session ${event.sessionId}`,
58
+ "",
59
+ `Started: ${event.timestamp}`,
60
+ `Workspace: ${event.data.workspaceRoot}`,
61
+ `Model: ${event.data.model}`,
62
+ `Max iterations: ${event.data.maxIterations}`,
63
+ "",
64
+ "---",
65
+ "",
66
+ ].join("\n");
67
+ }
68
+
69
+ function renderTurnStarted(
70
+ event: Extract<AgentEvent, { type: "turn.started" }>,
71
+ ): string {
72
+ return [
73
+ `## Turn ${event.turnNumber ?? ""} - Prompt`,
74
+ "",
75
+ event.data.userPrompt,
76
+ "",
77
+ "---",
78
+ "",
79
+ ].join("\n");
80
+ }
81
+
82
+ function renderAssistantProgress(
83
+ event: Extract<AgentEvent, { type: "assistant.progress" }>,
84
+ ): string {
85
+ return [
86
+ `## Iteration ${event.iterationId} - Assistant`,
87
+ "",
88
+ event.data.content,
89
+ "",
90
+ "---",
91
+ "",
92
+ ].join("\n");
93
+ }
94
+
95
+ function renderToolObservation(
96
+ event: Extract<AgentEvent, { type: "tool.observation" }>,
97
+ ): string {
98
+ return [
99
+ `## Iteration ${event.iterationId} - ${event.data.call.name}`,
100
+ "",
101
+ toolCallSummary(event.data.call),
102
+ "",
103
+ observationContent(event.data.observation),
104
+ "",
105
+ "---",
106
+ "",
107
+ ].join("\n");
108
+ }
109
+
110
+ function renderTurnFinished(data: TurnFinishedData): string {
111
+ return [
112
+ "## Final",
113
+ "",
114
+ data.finalText.trim() === "" ? "(no final response)" : data.finalText,
115
+ "",
116
+ ].join("\n");
117
+ }
118
+
119
+ function renderTurnFailed(error: string): string {
120
+ return ["## Failed", "", error, ""].join("\n");
121
+ }
122
+
123
+ function renderTurnCancelled(
124
+ event: Extract<AgentEvent, { type: "turn.cancelled" }>,
125
+ ): string {
126
+ return [
127
+ "## Cancelled",
128
+ "",
129
+ `Cancelled: ${event.timestamp}`,
130
+ `Phase: ${event.data.cancellation.phase}`,
131
+ `Iteration: ${event.data.cancellation.iterationNumber}`,
132
+ event.data.cancellation.toolName === undefined
133
+ ? undefined
134
+ : `Tool: ${event.data.cancellation.toolName}`,
135
+ event.data.cancellation.toolCallId === undefined
136
+ ? undefined
137
+ : `Call ID: ${event.data.cancellation.toolCallId}`,
138
+ "",
139
+ ]
140
+ .filter((line): line is string => line !== undefined)
141
+ .join("\n");
142
+ }
143
+
144
+ function toolCallSummary(call: ToolCall): string {
145
+ const args = asRecord(call.args);
146
+
147
+ if (call.name === "Bash") {
148
+ const description = stringProperty(args, "description");
149
+ const command = stringProperty(args, "command");
150
+ return [
151
+ `Call ID: ${call.toolCallId}`,
152
+ description === undefined ? undefined : `Description: ${description}`,
153
+ command === undefined ? undefined : `Command: ${command}`,
154
+ ]
155
+ .filter((line): line is string => line !== undefined)
156
+ .join("\n");
157
+ }
158
+
159
+ if (call.name === "TaskOutput" || call.name === "TaskStop") {
160
+ const taskId = stringProperty(args, "task_id");
161
+ return [
162
+ `Call ID: ${call.toolCallId}`,
163
+ taskId === undefined ? undefined : `Task ID: ${taskId}`,
164
+ ]
165
+ .filter((line): line is string => line !== undefined)
166
+ .join("\n");
167
+ }
168
+
169
+ const filePath = stringProperty(args, "file_path");
170
+ const pattern = stringProperty(args, "pattern");
171
+ return [
172
+ `Call ID: ${call.toolCallId}`,
173
+ filePath === undefined ? undefined : `Path: ${filePath}`,
174
+ pattern === undefined ? undefined : `Pattern: ${pattern}`,
175
+ ]
176
+ .filter((line): line is string => line !== undefined)
177
+ .join("\n");
178
+ }
179
+
180
+ function observationContent(observation: ToolObservation): string {
181
+ return observation.content;
182
+ }
183
+
184
+ function asRecord(value: unknown): Record<string, unknown> {
185
+ return typeof value === "object" && value !== null && !Array.isArray(value)
186
+ ? (value as Record<string, unknown>)
187
+ : {};
188
+ }
189
+
190
+ function stringProperty(
191
+ record: Record<string, unknown>,
192
+ property: string,
193
+ ): string | undefined {
194
+ return typeof record[property] === "string" ? record[property] : undefined;
195
+ }