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.
- package/README.md +173 -0
- package/package.json +78 -0
- package/patches/markdansi@0.3.2.patch +37 -0
- package/src/agent/context-builder.ts +43 -0
- package/src/agent/context-meter.ts +310 -0
- package/src/agent/loop.ts +525 -0
- package/src/agent/runtime-session.ts +1212 -0
- package/src/agent/session-ledger.ts +828 -0
- package/src/agent/turn-cancellation.ts +44 -0
- package/src/agent/types.ts +77 -0
- package/src/cli/config.ts +283 -0
- package/src/cli/index.ts +29 -0
- package/src/cli/model-profiles.ts +289 -0
- package/src/cli/run-runner.ts +107 -0
- package/src/cli/tui-runner.tsx +290 -0
- package/src/context/compiled-context-hash.ts +138 -0
- package/src/context/compiled-context-validator.ts +209 -0
- package/src/context/context-manager.ts +362 -0
- package/src/context/context-policy.ts +8 -0
- package/src/context/context-protocol-validator.ts +463 -0
- package/src/context/context-revision-compiler.ts +281 -0
- package/src/context/context-revision.ts +111 -0
- package/src/context/context-source.ts +30 -0
- package/src/context/context-swap-renderer.ts +272 -0
- package/src/context/protocol-frame.ts +240 -0
- package/src/context/swap-planner.ts +725 -0
- package/src/events/append-private-file.ts +16 -0
- package/src/events/bash-result-detail.ts +70 -0
- package/src/events/composite-event-sink.ts +82 -0
- package/src/events/event-sink.ts +16 -0
- package/src/events/jsonl-event-log.ts +13 -0
- package/src/events/observation-text-log.ts +195 -0
- package/src/events/stdout-event-printer.ts +396 -0
- package/src/events/types.ts +263 -0
- package/src/ids/runtime-id.ts +68 -0
- package/src/ids/uuid-v7.ts +5 -0
- package/src/instructions/project-instructions.ts +242 -0
- package/src/mcp/mcp-config.ts +144 -0
- package/src/mcp/mcp-manager.ts +216 -0
- package/src/mcp/mcp-tool-executor.ts +178 -0
- package/src/model/committed-prefix-auditor.ts +68 -0
- package/src/model/fake-model-client.ts +280 -0
- package/src/model/model-client.ts +64 -0
- package/src/model/model-context-profile.ts +134 -0
- package/src/model/model-request-preflight.ts +120 -0
- package/src/model/openai-chat-mapping.ts +444 -0
- package/src/model/openai-chat-model-client.ts +190 -0
- package/src/model/prompt-prefix-hash.ts +47 -0
- package/src/model/token-estimator.ts +148 -0
- package/src/observation/observation-builder.ts +481 -0
- package/src/session/resume-projection.ts +616 -0
- package/src/session/session-catalog.ts +270 -0
- package/src/session/session-errors.ts +121 -0
- package/src/session/session-history-reader.ts +535 -0
- package/src/session/session-lock.ts +291 -0
- package/src/session/session-schema.ts +741 -0
- package/src/session/session-store.ts +3067 -0
- package/src/session/sqlite-session-ledger.ts +153 -0
- package/src/tools/bash-task.ts +617 -0
- package/src/tools/bash.ts +450 -0
- package/src/tools/cwd-state.ts +22 -0
- package/src/tools/edit.ts +428 -0
- package/src/tools/file-diff.ts +116 -0
- package/src/tools/glob.ts +202 -0
- package/src/tools/grep.ts +550 -0
- package/src/tools/hash.ts +9 -0
- package/src/tools/path-safety.ts +33 -0
- package/src/tools/read.ts +319 -0
- package/src/tools/recall.ts +400 -0
- package/src/tools/registry.ts +213 -0
- package/src/tools/ripgrep.ts +220 -0
- package/src/tools/task-list.ts +59 -0
- package/src/tools/task-output-snapshot.ts +47 -0
- package/src/tools/task-output-tool.ts +62 -0
- package/src/tools/task-output.ts +159 -0
- package/src/tools/task-stop.ts +59 -0
- package/src/tools/task-tool-args.ts +29 -0
- package/src/tools/types.ts +330 -0
- package/src/tools/web-fetch/backend.ts +27 -0
- package/src/tools/web-fetch/browser-backend.ts +126 -0
- package/src/tools/web-fetch/exa-backend.ts +172 -0
- package/src/tools/web-fetch/index.ts +298 -0
- package/src/tools/web-fetch/local-backend.ts +267 -0
- package/src/tools/web-fetch/refiner.ts +78 -0
- package/src/tools/web-fetch/route.ts +95 -0
- package/src/tools/web-search.ts +300 -0
- package/src/tools/write.ts +244 -0
- package/src/tui/app.tsx +497 -0
- package/src/tui/components/assistant-markdown.tsx +47 -0
- package/src/tui/components/background-tasks.tsx +92 -0
- package/src/tui/components/bash-result-view.tsx +47 -0
- package/src/tui/components/context-status.tsx +127 -0
- package/src/tui/components/diff-view.tsx +151 -0
- package/src/tui/components/file-viewer.tsx +212 -0
- package/src/tui/components/footer.tsx +60 -0
- package/src/tui/components/header.tsx +21 -0
- package/src/tui/components/model-picker.tsx +142 -0
- package/src/tui/components/prompt-input.tsx +432 -0
- package/src/tui/components/resume-session-picker.tsx +273 -0
- package/src/tui/components/timeline.tsx +121 -0
- package/src/tui/context-format.ts +24 -0
- package/src/tui/event-store.ts +865 -0
- package/src/tui/git-branch.ts +23 -0
- package/src/tui/line-editor.ts +157 -0
- package/src/tui/prompt-history.ts +94 -0
- package/src/tui/slash-commands.ts +126 -0
- package/src/tui/tui-projection-policy.ts +35 -0
- package/src/tui/tui-projection-store.ts +123 -0
- package/src/tui/tui-session-controller.ts +170 -0
- package/src/tui/view-file.ts +122 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { ContextCompactionResult } from "../context/context-manager";
|
|
2
|
+
import type {
|
|
3
|
+
ExecuteTurnInput,
|
|
4
|
+
RuntimeSession,
|
|
5
|
+
SessionDisposeReason,
|
|
6
|
+
} from "../agent/runtime-session";
|
|
7
|
+
import type { RunAgentResult } from "../agent/types";
|
|
8
|
+
import type { SessionId } from "../ids/runtime-id";
|
|
9
|
+
import type { ModelProfile } from "../cli/model-profiles";
|
|
10
|
+
import { SessionCatalog, type SessionSummary } from "../session/session-catalog";
|
|
11
|
+
import type { TuiProjectionStore } from "./tui-projection-store";
|
|
12
|
+
|
|
13
|
+
export type TuiSessionBinding = {
|
|
14
|
+
sessionId: SessionId;
|
|
15
|
+
modelName: string;
|
|
16
|
+
workspaceRoot: string;
|
|
17
|
+
profileName?: string;
|
|
18
|
+
projectionStore: TuiProjectionStore;
|
|
19
|
+
executeTurn(userPrompt: string, signal: AbortSignal): Promise<RunAgentResult>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type TuiSessionController = {
|
|
23
|
+
getBinding: () => TuiSessionBinding;
|
|
24
|
+
subscribe: (listener: () => void) => () => void;
|
|
25
|
+
listSessions: () => Promise<readonly SessionSummary[]>;
|
|
26
|
+
compact: () => Promise<ContextCompactionResult>;
|
|
27
|
+
resume: (sessionId: SessionId) => Promise<void>;
|
|
28
|
+
delete: (sessionId: SessionId) => Promise<void>;
|
|
29
|
+
switchModel: (profile: ModelProfile) => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ManagedTuiSessionBinding = TuiSessionBinding & {
|
|
33
|
+
runtimeSession: RuntimeSession;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class DefaultTuiSessionController implements TuiSessionController {
|
|
37
|
+
private readonly listeners = new Set<() => void>();
|
|
38
|
+
private binding: ManagedTuiSessionBinding;
|
|
39
|
+
private operation?: Promise<unknown>;
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
initial: ManagedTuiSessionBinding,
|
|
43
|
+
private readonly catalog: SessionCatalog,
|
|
44
|
+
private readonly openSession: (
|
|
45
|
+
sessionId: SessionId,
|
|
46
|
+
) => Promise<ManagedTuiSessionBinding>,
|
|
47
|
+
private readonly createSessionWithProfile: (
|
|
48
|
+
profile: ModelProfile,
|
|
49
|
+
) => Promise<ManagedTuiSessionBinding>,
|
|
50
|
+
) {
|
|
51
|
+
this.binding = initial;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
readonly getBinding = (): TuiSessionBinding => this.binding;
|
|
55
|
+
|
|
56
|
+
readonly subscribe = (listener: () => void): (() => void) => {
|
|
57
|
+
this.listeners.add(listener);
|
|
58
|
+
return () => {
|
|
59
|
+
this.listeners.delete(listener);
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
listSessions(): Promise<readonly SessionSummary[]> {
|
|
64
|
+
return this.catalog.list(this.binding.sessionId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
compact(): Promise<ContextCompactionResult> {
|
|
68
|
+
return this.serialize(() => this.binding.runtimeSession.compactContext());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
resume(sessionId: SessionId): Promise<void> {
|
|
72
|
+
return this.serialize(async () => {
|
|
73
|
+
const current = this.binding;
|
|
74
|
+
if (sessionId === current.sessionId) {
|
|
75
|
+
throw new Error(`Session ${sessionId} is already current.`);
|
|
76
|
+
}
|
|
77
|
+
if (!current.runtimeSession.canSwitchSession()) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Cannot switch sessions while a turn or background task is active.",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const target = await this.openSession(sessionId);
|
|
84
|
+
try {
|
|
85
|
+
await current.runtimeSession.dispose({ type: "session_switch" });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
await target.runtimeSession
|
|
88
|
+
.dispose({ type: "runner_failed", error: errorMessage(error) })
|
|
89
|
+
.catch(() => undefined);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
this.binding = target;
|
|
93
|
+
for (const listener of this.listeners) {
|
|
94
|
+
listener();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
delete(sessionId: SessionId): Promise<void> {
|
|
100
|
+
return this.serialize(() => this.catalog.delete(sessionId, this.binding.sessionId));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
switchModel(profile: ModelProfile): Promise<void> {
|
|
104
|
+
return this.serialize(async () => {
|
|
105
|
+
const current = this.binding;
|
|
106
|
+
if (!current.runtimeSession.canSwitchSession()) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"Cannot switch models while a turn or background task is active.",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const target = await this.createSessionWithProfile(profile);
|
|
113
|
+
try {
|
|
114
|
+
await current.runtimeSession.dispose({ type: "session_switch" });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
await target.runtimeSession
|
|
117
|
+
.dispose({ type: "runner_failed", error: errorMessage(error) })
|
|
118
|
+
.catch(() => undefined);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
this.binding = target;
|
|
122
|
+
for (const listener of this.listeners) {
|
|
123
|
+
listener();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
dispose(reason: SessionDisposeReason): Promise<void> {
|
|
129
|
+
return this.binding.runtimeSession.dispose(reason);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private serialize<T>(operation: () => Promise<T>): Promise<T> {
|
|
133
|
+
if (this.operation !== undefined) {
|
|
134
|
+
return Promise.reject(new Error("Another session operation is already running."));
|
|
135
|
+
}
|
|
136
|
+
const pending = operation().finally(() => {
|
|
137
|
+
if (this.operation === pending) {
|
|
138
|
+
this.operation = undefined;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
this.operation = pending;
|
|
142
|
+
return pending;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function managedTuiBinding(input: {
|
|
147
|
+
runtimeSession: RuntimeSession;
|
|
148
|
+
modelName: string;
|
|
149
|
+
workspaceRoot: string;
|
|
150
|
+
profileName?: string;
|
|
151
|
+
projectionStore: TuiProjectionStore;
|
|
152
|
+
}): ManagedTuiSessionBinding {
|
|
153
|
+
return {
|
|
154
|
+
sessionId: input.runtimeSession.sessionId,
|
|
155
|
+
modelName: input.modelName,
|
|
156
|
+
workspaceRoot: input.workspaceRoot,
|
|
157
|
+
profileName: input.profileName,
|
|
158
|
+
projectionStore: input.projectionStore,
|
|
159
|
+
runtimeSession: input.runtimeSession,
|
|
160
|
+
executeTurn: (userPrompt, signal) =>
|
|
161
|
+
input.runtimeSession.executeTurn({
|
|
162
|
+
userPrompt,
|
|
163
|
+
signal,
|
|
164
|
+
} satisfies ExecuteTurnInput),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function errorMessage(error: unknown): string {
|
|
169
|
+
return error instanceof Error ? error.message : String(error);
|
|
170
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export type ViewFile = {
|
|
5
|
+
absolutePath: string;
|
|
6
|
+
displayPath: string;
|
|
7
|
+
lines: readonly string[];
|
|
8
|
+
sizeBytes: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function loadViewFile(
|
|
12
|
+
workspaceRoot: string,
|
|
13
|
+
inputPath: string,
|
|
14
|
+
): Promise<ViewFile> {
|
|
15
|
+
if (inputPath.trim() === "") {
|
|
16
|
+
throw new Error("Path is required.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const root = await realpath(workspaceRoot);
|
|
20
|
+
const absoluteInput = path.isAbsolute(inputPath);
|
|
21
|
+
const resolvedPath = absoluteInput
|
|
22
|
+
? path.resolve(inputPath)
|
|
23
|
+
: path.resolve(root, inputPath);
|
|
24
|
+
|
|
25
|
+
if (!absoluteInput && !isWithin(root, resolvedPath)) {
|
|
26
|
+
throw new Error("Relative path escapes the workspace.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let absolutePath: string;
|
|
30
|
+
try {
|
|
31
|
+
absolutePath = await realpath(resolvedPath);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw viewFileSystemError(resolvedPath, error);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!absoluteInput && !isWithin(root, absolutePath)) {
|
|
37
|
+
throw new Error("Relative path resolves outside the workspace.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let info;
|
|
41
|
+
try {
|
|
42
|
+
info = await stat(absolutePath);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw viewFileSystemError(absolutePath, error);
|
|
45
|
+
}
|
|
46
|
+
if (!info.isFile()) {
|
|
47
|
+
throw new Error(`Path is not a regular file: ${absolutePath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let bytes: Uint8Array;
|
|
51
|
+
try {
|
|
52
|
+
bytes = await readFile(absolutePath);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw viewFileSystemError(absolutePath, error);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const text = decodeText(bytes, absolutePath);
|
|
58
|
+
return {
|
|
59
|
+
absolutePath,
|
|
60
|
+
displayPath: absoluteInput ? absolutePath : path.relative(root, absolutePath),
|
|
61
|
+
lines: splitLines(text),
|
|
62
|
+
sizeBytes: bytes.byteLength,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function decodeText(bytes: Uint8Array, absolutePath: string): string {
|
|
67
|
+
let text: string;
|
|
68
|
+
try {
|
|
69
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(`File is not valid UTF-8 text: ${absolutePath}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const character of text) {
|
|
75
|
+
const codePoint = character.charCodeAt(0);
|
|
76
|
+
const unsafeC0 =
|
|
77
|
+
codePoint < 32 && codePoint !== 9 && codePoint !== 10 && codePoint !== 13;
|
|
78
|
+
const unsafeC1 = codePoint >= 127 && codePoint <= 159;
|
|
79
|
+
if (unsafeC0 || unsafeC1) {
|
|
80
|
+
throw new Error(`File contains non-text control characters: ${absolutePath}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function splitLines(text: string): string[] {
|
|
88
|
+
if (text === "") {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const lines = text.split(/\r\n|\n|\r/);
|
|
93
|
+
if (text.endsWith("\n") || text.endsWith("\r")) {
|
|
94
|
+
lines.pop();
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isWithin(root: string, candidate: string): boolean {
|
|
100
|
+
return candidate === root || candidate.startsWith(root + path.sep);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function viewFileSystemError(filePath: string, error: unknown): Error {
|
|
104
|
+
const code = fileSystemErrorCode(error);
|
|
105
|
+
if (code === "ENOENT") {
|
|
106
|
+
return new Error(`File does not exist: ${filePath}`);
|
|
107
|
+
}
|
|
108
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
109
|
+
return new Error(`File is not readable: ${filePath}`);
|
|
110
|
+
}
|
|
111
|
+
return new Error(`Unable to read file ${filePath}: ${errorMessage(error)}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function fileSystemErrorCode(error: unknown): string | undefined {
|
|
115
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
116
|
+
? String(error.code)
|
|
117
|
+
: undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function errorMessage(error: unknown): string {
|
|
121
|
+
return error instanceof Error ? error.message : String(error);
|
|
122
|
+
}
|