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,270 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
import { parseSessionId, type SessionId } from "../ids/runtime-id";
|
|
5
|
+
import { SessionError } from "./session-errors";
|
|
6
|
+
import { inspectSessionLock } from "./session-lock";
|
|
7
|
+
import { SessionStore } from "./session-store";
|
|
8
|
+
import { verifySessionSchema } from "./session-schema";
|
|
9
|
+
|
|
10
|
+
export type SessionSummary = {
|
|
11
|
+
sessionId: SessionId;
|
|
12
|
+
modelName: string;
|
|
13
|
+
profileName?: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
turnCount: number;
|
|
17
|
+
firstUserPromptPreview?: string;
|
|
18
|
+
status:
|
|
19
|
+
| "current"
|
|
20
|
+
| "resumable"
|
|
21
|
+
| "interrupted"
|
|
22
|
+
| "active"
|
|
23
|
+
| "incomplete"
|
|
24
|
+
| "unavailable";
|
|
25
|
+
databaseBytes: number;
|
|
26
|
+
statusDetail?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class SessionCatalog {
|
|
30
|
+
private readonly workspaceRootPromise: Promise<string>;
|
|
31
|
+
|
|
32
|
+
constructor(private readonly input: { workspaceRoot: string; limit?: number }) {
|
|
33
|
+
this.workspaceRootPromise = realpath(input.workspaceRoot);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async list(currentSessionId?: SessionId): Promise<readonly SessionSummary[]> {
|
|
37
|
+
const workspaceRoot = await this.workspaceRootPromise;
|
|
38
|
+
const sessionsRoot = path.join(workspaceRoot, ".tinker", "sessions");
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = await readdir(sessionsRoot, { withFileTypes: true });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const summaries: SessionSummary[] = [];
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
let sessionId: SessionId;
|
|
55
|
+
try {
|
|
56
|
+
sessionId = parseSessionId(entry.name);
|
|
57
|
+
} catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const directory = path.join(sessionsRoot, entry.name);
|
|
61
|
+
summaries.push(
|
|
62
|
+
await readSummary(directory, sessionId, workspaceRoot, currentSessionId),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Object.freeze(
|
|
67
|
+
summaries
|
|
68
|
+
.filter(
|
|
69
|
+
(summary) =>
|
|
70
|
+
summary.turnCount > 0 ||
|
|
71
|
+
summary.status === "incomplete" ||
|
|
72
|
+
summary.status === "unavailable",
|
|
73
|
+
)
|
|
74
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
75
|
+
.slice(0, this.input.limit ?? 20),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async get(
|
|
80
|
+
sessionId: SessionId,
|
|
81
|
+
currentSessionId?: SessionId,
|
|
82
|
+
): Promise<SessionSummary> {
|
|
83
|
+
const workspaceRoot = await this.workspaceRootPromise;
|
|
84
|
+
const directory = path.join(workspaceRoot, ".tinker", "sessions", sessionId);
|
|
85
|
+
return readSummary(directory, sessionId, workspaceRoot, currentSessionId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async delete(sessionId: SessionId, currentSessionId?: SessionId): Promise<void> {
|
|
89
|
+
if (sessionId === currentSessionId) {
|
|
90
|
+
throw new SessionError(
|
|
91
|
+
"SESSION_DELETE_BLOCKED",
|
|
92
|
+
"delete_session",
|
|
93
|
+
"Cannot delete the current session.",
|
|
94
|
+
{ sessionId },
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const workspaceRoot = await this.workspaceRootPromise;
|
|
98
|
+
const store = await SessionStore.openExisting({
|
|
99
|
+
workspaceRoot,
|
|
100
|
+
sessionId,
|
|
101
|
+
allowIncomplete: true,
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
await store.deleteFromDisk();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
await store.abandon().catch(() => undefined);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function readSummary(
|
|
113
|
+
directory: string,
|
|
114
|
+
sessionId: SessionId,
|
|
115
|
+
workspaceRoot: string,
|
|
116
|
+
currentSessionId?: SessionId,
|
|
117
|
+
): Promise<SessionSummary> {
|
|
118
|
+
const databasePath = path.join(directory, "session.sqlite");
|
|
119
|
+
let database: Database | undefined;
|
|
120
|
+
try {
|
|
121
|
+
database = new Database(databasePath, {
|
|
122
|
+
readonly: true,
|
|
123
|
+
strict: true,
|
|
124
|
+
safeIntegers: true,
|
|
125
|
+
});
|
|
126
|
+
verifySessionSchema(database, sessionId);
|
|
127
|
+
const meta = database.query("SELECT * FROM session_meta").all() as Array<
|
|
128
|
+
Record<string, unknown>
|
|
129
|
+
>;
|
|
130
|
+
if (meta.length !== 1) {
|
|
131
|
+
throw new Error(`Expected one session_meta row; found ${meta.length}.`);
|
|
132
|
+
}
|
|
133
|
+
const row = meta[0];
|
|
134
|
+
if (row.session_id !== sessionId || row.workspace_root !== workspaceRoot) {
|
|
135
|
+
throw new Error("Session identity or workspace does not match.");
|
|
136
|
+
}
|
|
137
|
+
const turnCount = toSafeNumber(
|
|
138
|
+
(
|
|
139
|
+
database.query("SELECT COUNT(*) AS count FROM turns").get() as {
|
|
140
|
+
count: unknown;
|
|
141
|
+
}
|
|
142
|
+
).count,
|
|
143
|
+
);
|
|
144
|
+
const firstPrompt = database
|
|
145
|
+
.query(
|
|
146
|
+
`SELECT content FROM messages
|
|
147
|
+
WHERE role = 'user' ORDER BY ordinal LIMIT 1`,
|
|
148
|
+
)
|
|
149
|
+
.get() as { content: string } | null;
|
|
150
|
+
const openState = toSafeNumber(
|
|
151
|
+
(
|
|
152
|
+
database
|
|
153
|
+
.query(
|
|
154
|
+
`SELECT
|
|
155
|
+
(SELECT COUNT(*) FROM turns WHERE status = 'open') +
|
|
156
|
+
(SELECT COUNT(*) FROM protocol_frames WHERE state = 'open') AS count`,
|
|
157
|
+
)
|
|
158
|
+
.get() as { count: unknown }
|
|
159
|
+
).count,
|
|
160
|
+
);
|
|
161
|
+
const lock = await inspectSessionLock({ sessionDirectory: directory, sessionId });
|
|
162
|
+
const initializationState = requireString(
|
|
163
|
+
row.initialization_state,
|
|
164
|
+
"initialization_state",
|
|
165
|
+
);
|
|
166
|
+
const status =
|
|
167
|
+
sessionId === currentSessionId
|
|
168
|
+
? "current"
|
|
169
|
+
: initializationState !== "ready"
|
|
170
|
+
? "incomplete"
|
|
171
|
+
: lock === "active"
|
|
172
|
+
? "active"
|
|
173
|
+
: lock === "corrupt"
|
|
174
|
+
? "unavailable"
|
|
175
|
+
: openState > 0 || lock === "stale"
|
|
176
|
+
? "interrupted"
|
|
177
|
+
: "resumable";
|
|
178
|
+
return {
|
|
179
|
+
sessionId,
|
|
180
|
+
modelName: requireString(row.model_name, "model_name"),
|
|
181
|
+
...profileNameFromRuntimeContract(row.runtime_contract_json),
|
|
182
|
+
createdAt: requireTimestamp(row.created_at, "created_at"),
|
|
183
|
+
updatedAt: requireTimestamp(row.updated_at, "updated_at"),
|
|
184
|
+
turnCount,
|
|
185
|
+
...(firstPrompt === null
|
|
186
|
+
? {}
|
|
187
|
+
: { firstUserPromptPreview: preview(firstPrompt.content) }),
|
|
188
|
+
status,
|
|
189
|
+
databaseBytes: await sessionDatabaseBytes(databasePath),
|
|
190
|
+
...(lock === "corrupt" ? { statusDetail: "lock is corrupt" } : {}),
|
|
191
|
+
};
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const fallback = await stat(directory);
|
|
194
|
+
return {
|
|
195
|
+
sessionId,
|
|
196
|
+
modelName: "unavailable",
|
|
197
|
+
createdAt: fallback.birthtime.toISOString(),
|
|
198
|
+
updatedAt: fallback.mtime.toISOString(),
|
|
199
|
+
turnCount: 0,
|
|
200
|
+
status: "unavailable",
|
|
201
|
+
databaseBytes: await sessionDatabaseBytes(databasePath),
|
|
202
|
+
statusDetail: errorMessage(error),
|
|
203
|
+
};
|
|
204
|
+
} finally {
|
|
205
|
+
database?.close();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function profileNameFromRuntimeContract(value: unknown): { profileName?: string } {
|
|
210
|
+
if (value === null) {
|
|
211
|
+
return {};
|
|
212
|
+
}
|
|
213
|
+
if (typeof value !== "string") {
|
|
214
|
+
throw new Error("Session runtime contract must be JSON text.");
|
|
215
|
+
}
|
|
216
|
+
const parsed = JSON.parse(value) as unknown;
|
|
217
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
218
|
+
throw new Error("Session runtime contract must be an object.");
|
|
219
|
+
}
|
|
220
|
+
const profileName = (parsed as Record<string, unknown>).profileName;
|
|
221
|
+
if (profileName === undefined) {
|
|
222
|
+
return {};
|
|
223
|
+
}
|
|
224
|
+
return { profileName: requireString(profileName, "profileName") };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function sessionDatabaseBytes(databasePath: string): Promise<number> {
|
|
228
|
+
let total = 0;
|
|
229
|
+
for (const filePath of [databasePath, `${databasePath}-wal`, `${databasePath}-shm`]) {
|
|
230
|
+
try {
|
|
231
|
+
total += (await stat(filePath)).size;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return total;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function preview(content: string): string {
|
|
242
|
+
return content.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function requireString(value: unknown, name: string): string {
|
|
246
|
+
if (typeof value !== "string" || value === "") {
|
|
247
|
+
throw new Error(`${name} must be a non-empty string.`);
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function requireTimestamp(value: unknown, name: string): string {
|
|
253
|
+
const timestamp = requireString(value, name);
|
|
254
|
+
if (Number.isNaN(Date.parse(timestamp))) {
|
|
255
|
+
throw new Error(`${name} must be a timestamp.`);
|
|
256
|
+
}
|
|
257
|
+
return timestamp;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function toSafeNumber(value: unknown): number {
|
|
261
|
+
const number = typeof value === "bigint" ? Number(value) : value;
|
|
262
|
+
if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) {
|
|
263
|
+
throw new Error("SQLite count is not a safe non-negative integer.");
|
|
264
|
+
}
|
|
265
|
+
return number;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function errorMessage(error: unknown): string {
|
|
269
|
+
return error instanceof Error ? error.message : String(error);
|
|
270
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MessageId,
|
|
3
|
+
ProtocolFrameId,
|
|
4
|
+
SessionId,
|
|
5
|
+
ToolCallId,
|
|
6
|
+
} from "../ids/runtime-id";
|
|
7
|
+
|
|
8
|
+
export type SessionErrorCode =
|
|
9
|
+
| "SESSION_STORE_NOT_FOUND"
|
|
10
|
+
| "SESSION_ALREADY_EXISTS"
|
|
11
|
+
| "SESSION_ID_INVALID"
|
|
12
|
+
| "SESSION_PERMISSION_INVALID"
|
|
13
|
+
| "SESSION_LOCKED"
|
|
14
|
+
| "SESSION_LOCK_CORRUPT"
|
|
15
|
+
| "SESSION_SCHEMA_UNSUPPORTED"
|
|
16
|
+
| "SESSION_SCHEMA_INVALID"
|
|
17
|
+
| "SESSION_INTEGRITY_FAILED"
|
|
18
|
+
| "SESSION_WORKSPACE_MISMATCH"
|
|
19
|
+
| "SESSION_RUNTIME_MISMATCH"
|
|
20
|
+
| "SESSION_PROTOCOL_INVALID"
|
|
21
|
+
| "SESSION_RECALL_INDEX_INVALID"
|
|
22
|
+
| "SESSION_READ_FAILED"
|
|
23
|
+
| "SESSION_WRITE_FAILED"
|
|
24
|
+
| "SESSION_RECOVERY_FAILED"
|
|
25
|
+
| "SESSION_DELETE_BLOCKED";
|
|
26
|
+
|
|
27
|
+
export class SessionError extends Error {
|
|
28
|
+
readonly code: SessionErrorCode;
|
|
29
|
+
readonly operation: string;
|
|
30
|
+
readonly sessionId?: SessionId;
|
|
31
|
+
readonly frameId?: ProtocolFrameId;
|
|
32
|
+
readonly messageId?: MessageId;
|
|
33
|
+
readonly toolCallId?: ToolCallId;
|
|
34
|
+
readonly sqliteCode?: string;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
code: SessionErrorCode,
|
|
38
|
+
operation: string,
|
|
39
|
+
message: string,
|
|
40
|
+
details: {
|
|
41
|
+
sessionId?: SessionId;
|
|
42
|
+
frameId?: ProtocolFrameId;
|
|
43
|
+
messageId?: MessageId;
|
|
44
|
+
toolCallId?: ToolCallId;
|
|
45
|
+
sqliteCode?: string;
|
|
46
|
+
cause?: unknown;
|
|
47
|
+
} = {},
|
|
48
|
+
) {
|
|
49
|
+
super(message, details.cause === undefined ? undefined : { cause: details.cause });
|
|
50
|
+
this.name = "SessionError";
|
|
51
|
+
this.code = code;
|
|
52
|
+
this.operation = operation;
|
|
53
|
+
Object.assign(this, details);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function sessionWriteError(
|
|
58
|
+
operation: string,
|
|
59
|
+
sessionId: SessionId,
|
|
60
|
+
error: unknown,
|
|
61
|
+
): SessionError {
|
|
62
|
+
if (error instanceof SessionError) {
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
65
|
+
const sqliteCode = sqliteErrorCode(error);
|
|
66
|
+
return new SessionError(
|
|
67
|
+
"SESSION_WRITE_FAILED",
|
|
68
|
+
operation,
|
|
69
|
+
`Session store write failed during ${operation}${sqliteCode === undefined ? "" : ` (${sqliteCode})`}.`,
|
|
70
|
+
{ sessionId, sqliteCode, cause: error },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function sessionOpenError(
|
|
75
|
+
operation: string,
|
|
76
|
+
sessionId: SessionId,
|
|
77
|
+
error: unknown,
|
|
78
|
+
): SessionError {
|
|
79
|
+
if (error instanceof SessionError) {
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
const sqliteCode = sqliteErrorCode(error);
|
|
83
|
+
const code: SessionErrorCode =
|
|
84
|
+
sqliteCode?.includes("CORRUPT") === true || sqliteCode?.includes("NOTADB") === true
|
|
85
|
+
? "SESSION_INTEGRITY_FAILED"
|
|
86
|
+
: sqliteCode?.includes("CANTOPEN") === true ||
|
|
87
|
+
sqliteCode?.includes("READONLY") === true
|
|
88
|
+
? "SESSION_PERMISSION_INVALID"
|
|
89
|
+
: "SESSION_INTEGRITY_FAILED";
|
|
90
|
+
return new SessionError(
|
|
91
|
+
code,
|
|
92
|
+
operation,
|
|
93
|
+
`Session store open failed during ${operation}${sqliteCode === undefined ? "" : ` (${sqliteCode})`}.`,
|
|
94
|
+
{ sessionId, sqliteCode, cause: error },
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function sessionReadError(
|
|
99
|
+
operation: string,
|
|
100
|
+
sessionId: SessionId,
|
|
101
|
+
error: unknown,
|
|
102
|
+
): SessionError {
|
|
103
|
+
if (error instanceof SessionError) {
|
|
104
|
+
return error;
|
|
105
|
+
}
|
|
106
|
+
const sqliteCode = sqliteErrorCode(error);
|
|
107
|
+
return new SessionError(
|
|
108
|
+
"SESSION_READ_FAILED",
|
|
109
|
+
operation,
|
|
110
|
+
`Session history read failed during ${operation}${sqliteCode === undefined ? "" : ` (${sqliteCode})`}.`,
|
|
111
|
+
{ sessionId, sqliteCode, cause: error },
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function sqliteErrorCode(error: unknown): string | undefined {
|
|
116
|
+
if (typeof error !== "object" || error === null) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const code = (error as { code?: unknown }).code;
|
|
120
|
+
return typeof code === "string" ? code : undefined;
|
|
121
|
+
}
|