talon-agent 5.2.2 → 5.4.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.
- package/README.md +10 -6
- package/package.json +1 -1
- package/src/app.ts +78 -0
- package/src/backend/agy/auth.ts +128 -0
- package/src/backend/agy/constants.ts +88 -0
- package/src/backend/agy/doctor.ts +161 -0
- package/src/backend/agy/effort.ts +76 -0
- package/src/backend/agy/events.ts +402 -0
- package/src/backend/agy/factory.ts +137 -0
- package/src/backend/agy/handler/index.ts +9 -0
- package/src/backend/agy/handler/message.ts +444 -0
- package/src/backend/agy/init.ts +64 -0
- package/src/backend/agy/mcp/config.ts +336 -0
- package/src/backend/agy/mcp/register.ts +134 -0
- package/src/backend/agy/models.ts +335 -0
- package/src/backend/agy/one-shot.ts +301 -0
- package/src/backend/agy/process/child.ts +378 -0
- package/src/backend/agy/process/orphans.ts +106 -0
- package/src/backend/agy/sessions.ts +90 -0
- package/src/backend/agy/state.ts +77 -0
- package/src/backend/builtins.ts +1 -0
- package/src/backend/codex/mcp-config.ts +1 -1
- package/src/backend/openai-agents/mcp-pool.ts +1 -1
- package/src/backend/runtime/index.ts +1 -1
- package/src/cli/commands/backup.ts +396 -0
- package/src/cli/config-view.ts +5 -0
- package/src/cli/config.ts +4 -2
- package/src/cli/events.ts +14 -0
- package/src/cli/index.ts +64 -45
- package/src/cli/setup.ts +49 -0
- package/src/core/agent-runtime/model-ref.ts +1 -0
- package/src/core/backup/archive/digest.ts +77 -0
- package/src/core/backup/archive/tar.ts +567 -0
- package/src/core/backup/archive/zstd.ts +31 -0
- package/src/core/backup/index.ts +54 -0
- package/src/core/backup/plan.ts +273 -0
- package/src/core/backup/restore.ts +410 -0
- package/src/core/backup/scheduler.ts +357 -0
- package/src/core/backup/snapshot.ts +408 -0
- package/src/core/backup/status.ts +194 -0
- package/src/core/backup/store.ts +312 -0
- package/src/core/backup/targets.ts +281 -0
- package/src/core/backup/types.ts +96 -0
- package/src/core/backup/upload.ts +172 -0
- package/src/core/bus/events.ts +45 -1
- package/src/core/config/index.ts +53 -0
- package/src/core/engine/gateway-actions/backup/index.ts +129 -0
- package/src/core/engine/gateway-actions/index.ts +4 -0
- package/src/core/mcp-hub/talon-server.ts +1 -1
- package/src/core/plugin/actions.ts +34 -0
- package/src/core/plugin/index.ts +5 -1
- package/src/core/tools/{ops/bridge.ts → bridge.ts} +7 -2
- package/src/core/tools/index.ts +2 -0
- package/src/core/tools/ops/backup.ts +67 -0
- package/src/core/tools/types.ts +2 -1
- package/src/core/update/self-update.ts +3 -0
- package/src/frontend/discord/callbacks/components/index.ts +3 -0
- package/src/frontend/discord/commands/backup.ts +203 -0
- package/src/frontend/discord/commands/definitions.ts +35 -0
- package/src/frontend/discord/commands/router.ts +3 -0
- package/src/frontend/telegram/callbacks/backup.ts +55 -0
- package/src/frontend/telegram/callbacks/index.ts +8 -0
- package/src/frontend/telegram/commands/backup.ts +209 -0
- package/src/frontend/telegram/commands/definitions.ts +4 -0
- package/src/frontend/telegram/commands/index.ts +2 -0
- package/src/storage/backup/index.ts +82 -0
- package/src/storage/backup/repo.ts +164 -0
- package/src/storage/db.ts +20 -0
- package/src/storage/sql/backups.sql +46 -0
- package/src/storage/sql/db.sql +8 -0
- package/src/storage/sql/schema.sql +30 -0
- package/src/storage/sql/statements.generated.ts +60 -1
- package/src/util/log.ts +1 -0
- /package/src/core/tools/{ops/mcp-env.ts → mcp-env.ts} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot-index repository — executes the statements in
|
|
3
|
+
* sql/backups.sql against the `backups` and `backup_remotes` tables.
|
|
4
|
+
* No SQL text lives here and none lives above: `storage/backup/index.ts`
|
|
5
|
+
* holds the domain API, this module owns statement execution and the
|
|
6
|
+
* row↔domain mapping.
|
|
7
|
+
*
|
|
8
|
+
* The manifest travels as an opaque JSON string. Storage sits below the
|
|
9
|
+
* engine, so it must not know the shape core/backup gives it — it stores
|
|
10
|
+
* the bytes and the few columns the listing surfaces sort and filter on.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getDatabase } from "../db.js";
|
|
14
|
+
import { backupsSql } from "../sql/statements.generated.js";
|
|
15
|
+
|
|
16
|
+
/** One snapshot as the index holds it. */
|
|
17
|
+
export type BackupRecord = {
|
|
18
|
+
id: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
pinned: boolean;
|
|
22
|
+
/** Epoch ms. */
|
|
23
|
+
createdAt: number;
|
|
24
|
+
sizeBytes: number;
|
|
25
|
+
/** The manifest.json body, verbatim. */
|
|
26
|
+
manifestJson: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** One target's state for one snapshot. */
|
|
30
|
+
export type BackupRemoteRecord = {
|
|
31
|
+
backupId: string;
|
|
32
|
+
targetId: string;
|
|
33
|
+
status: string;
|
|
34
|
+
remoteId?: string;
|
|
35
|
+
uploadedAt?: number;
|
|
36
|
+
error?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type Row = {
|
|
40
|
+
id: string;
|
|
41
|
+
kind: string;
|
|
42
|
+
label: string | null;
|
|
43
|
+
pinned: number;
|
|
44
|
+
created_at: number;
|
|
45
|
+
size_bytes: number;
|
|
46
|
+
manifest_json: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type RemoteRow = {
|
|
50
|
+
backup_id: string;
|
|
51
|
+
target_id: string;
|
|
52
|
+
status: string;
|
|
53
|
+
remote_id: string | null;
|
|
54
|
+
uploaded_at: number | null;
|
|
55
|
+
error: string | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function toRecord(row: Row): BackupRecord {
|
|
59
|
+
return {
|
|
60
|
+
id: row.id,
|
|
61
|
+
kind: row.kind,
|
|
62
|
+
label: row.label ?? undefined,
|
|
63
|
+
pinned: row.pinned === 1,
|
|
64
|
+
createdAt: row.created_at,
|
|
65
|
+
sizeBytes: row.size_bytes,
|
|
66
|
+
manifestJson: row.manifest_json,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toRemote(row: RemoteRow): BackupRemoteRecord {
|
|
71
|
+
return {
|
|
72
|
+
backupId: row.backup_id,
|
|
73
|
+
targetId: row.target_id,
|
|
74
|
+
status: row.status,
|
|
75
|
+
remoteId: row.remote_id ?? undefined,
|
|
76
|
+
uploadedAt: row.uploaded_at ?? undefined,
|
|
77
|
+
error: row.error ?? undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function upsert(record: BackupRecord): void {
|
|
82
|
+
getDatabase()
|
|
83
|
+
.prepare(backupsSql.upsert)
|
|
84
|
+
.run(
|
|
85
|
+
record.id,
|
|
86
|
+
record.kind,
|
|
87
|
+
record.label ?? null,
|
|
88
|
+
record.pinned ? 1 : 0,
|
|
89
|
+
record.createdAt,
|
|
90
|
+
record.sizeBytes,
|
|
91
|
+
record.manifestJson,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function get(id: string): BackupRecord | undefined {
|
|
96
|
+
const row = getDatabase().prepare(backupsSql.get).get(id) as Row | undefined;
|
|
97
|
+
return row ? toRecord(row) : undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Newest first. */
|
|
101
|
+
export function all(): BackupRecord[] {
|
|
102
|
+
return (getDatabase().prepare(backupsSql.all).all() as Row[]).map(toRecord);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function ids(): string[] {
|
|
106
|
+
return (getDatabase().prepare(backupsSql.ids).all() as { id: string }[]).map(
|
|
107
|
+
(row) => row.id,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function setPinned(id: string, pinned: boolean): boolean {
|
|
112
|
+
const result = getDatabase()
|
|
113
|
+
.prepare(backupsSql.setPinned)
|
|
114
|
+
.run(pinned ? 1 : 0, id) as { changes?: number };
|
|
115
|
+
return (result.changes ?? 0) > 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function setManifest(
|
|
119
|
+
id: string,
|
|
120
|
+
manifestJson: string,
|
|
121
|
+
pinned: boolean,
|
|
122
|
+
sizeBytes: number,
|
|
123
|
+
): void {
|
|
124
|
+
getDatabase()
|
|
125
|
+
.prepare(backupsSql.setManifest)
|
|
126
|
+
.run(manifestJson, pinned ? 1 : 0, sizeBytes, id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function remove(id: string): void {
|
|
130
|
+
getDatabase().prepare(backupsSql.remove).run(id);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function upsertRemote(record: BackupRemoteRecord): void {
|
|
134
|
+
getDatabase()
|
|
135
|
+
.prepare(backupsSql.upsertRemote)
|
|
136
|
+
.run(
|
|
137
|
+
record.backupId,
|
|
138
|
+
record.targetId,
|
|
139
|
+
record.status,
|
|
140
|
+
record.remoteId ?? null,
|
|
141
|
+
record.uploadedAt ?? null,
|
|
142
|
+
record.error ?? null,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function remotesAll(): BackupRemoteRecord[] {
|
|
147
|
+
return (
|
|
148
|
+
getDatabase().prepare(backupsSql.remotesAll).all() as RemoteRow[]
|
|
149
|
+
).map(toRemote);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function remotesFor(backupId: string): BackupRemoteRecord[] {
|
|
153
|
+
return (
|
|
154
|
+
getDatabase().prepare(backupsSql.remotesFor).all(backupId) as RemoteRow[]
|
|
155
|
+
).map(toRemote);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function removeRemotes(backupId: string): void {
|
|
159
|
+
getDatabase().prepare(backupsSql.removeRemotes).run(backupId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function removeRemote(backupId: string, targetId: string): void {
|
|
163
|
+
getDatabase().prepare(backupsSql.removeRemote).run(backupId, targetId);
|
|
164
|
+
}
|
package/src/storage/db.ts
CHANGED
|
@@ -216,6 +216,26 @@ export function flushDatabase(): void {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Write a transactionally consistent copy of the database to `destPath`.
|
|
221
|
+
*
|
|
222
|
+
* The backup subsystem's only way in: a snapshot may not copy
|
|
223
|
+
* `data/talon.db` from disk, because in WAL mode the committed pages
|
|
224
|
+
* live partly in the sidecar and a byte-wise copy of the main file is a
|
|
225
|
+
* corrupt (or silently stale) database. `VACUUM INTO` asks SQLite for
|
|
226
|
+
* the copy instead — one file, no WAL, checkpointed and compacted,
|
|
227
|
+
* consistent as of the moment it runs, with concurrent readers and
|
|
228
|
+
* writers untouched.
|
|
229
|
+
*
|
|
230
|
+
* `destPath` must not exist (SQLite refuses to overwrite); its parent
|
|
231
|
+
* directory is created if needed.
|
|
232
|
+
*/
|
|
233
|
+
export function snapshotDatabase(destPath: string): void {
|
|
234
|
+
const database = getDatabase();
|
|
235
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
236
|
+
database.prepare(dbSql.vacuumInto).run(destPath);
|
|
237
|
+
}
|
|
238
|
+
|
|
219
239
|
export function closeDatabase(): void {
|
|
220
240
|
if (!db) return;
|
|
221
241
|
try {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- Statements for the snapshot index (see repositories in
|
|
2
|
+
-- storage/backup/repo.ts for parameter order and row↔domain mapping).
|
|
3
|
+
|
|
4
|
+
-- name: upsert
|
|
5
|
+
INSERT OR REPLACE INTO backups
|
|
6
|
+
(id, kind, label, pinned, created_at, size_bytes, manifest_json)
|
|
7
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
8
|
+
|
|
9
|
+
-- name: get
|
|
10
|
+
SELECT id, kind, label, pinned, created_at, size_bytes, manifest_json
|
|
11
|
+
FROM backups WHERE id = ?
|
|
12
|
+
|
|
13
|
+
-- name: all
|
|
14
|
+
SELECT id, kind, label, pinned, created_at, size_bytes, manifest_json
|
|
15
|
+
FROM backups ORDER BY created_at DESC
|
|
16
|
+
|
|
17
|
+
-- name: ids
|
|
18
|
+
SELECT id FROM backups
|
|
19
|
+
|
|
20
|
+
-- name: setPinned
|
|
21
|
+
UPDATE backups SET pinned = ? WHERE id = ?
|
|
22
|
+
|
|
23
|
+
-- name: setManifest
|
|
24
|
+
UPDATE backups SET manifest_json = ?, pinned = ?, size_bytes = ? WHERE id = ?
|
|
25
|
+
|
|
26
|
+
-- name: remove
|
|
27
|
+
DELETE FROM backups WHERE id = ?
|
|
28
|
+
|
|
29
|
+
-- name: upsertRemote
|
|
30
|
+
INSERT OR REPLACE INTO backup_remotes
|
|
31
|
+
(backup_id, target_id, status, remote_id, uploaded_at, error)
|
|
32
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
33
|
+
|
|
34
|
+
-- name: remotesAll
|
|
35
|
+
SELECT backup_id, target_id, status, remote_id, uploaded_at, error
|
|
36
|
+
FROM backup_remotes
|
|
37
|
+
|
|
38
|
+
-- name: remotesFor
|
|
39
|
+
SELECT backup_id, target_id, status, remote_id, uploaded_at, error
|
|
40
|
+
FROM backup_remotes WHERE backup_id = ?
|
|
41
|
+
|
|
42
|
+
-- name: removeRemotes
|
|
43
|
+
DELETE FROM backup_remotes WHERE backup_id = ?
|
|
44
|
+
|
|
45
|
+
-- name: removeRemote
|
|
46
|
+
DELETE FROM backup_remotes WHERE backup_id = ? AND target_id = ?
|
package/src/storage/sql/db.sql
CHANGED
|
@@ -30,3 +30,11 @@ ALTER TABLE history_messages ADD COLUMN attachments TEXT
|
|
|
30
30
|
-- Column reconciliation for databases that shipped before the cache-age
|
|
31
31
|
-- signal existed. Fresh databases get the column via schema.sql.
|
|
32
32
|
ALTER TABLE sessions ADD COLUMN last_turn_ended_at INTEGER
|
|
33
|
+
|
|
34
|
+
-- name: vacuumInto
|
|
35
|
+
-- Transactionally consistent copy of the whole database into a new file,
|
|
36
|
+
-- produced by SQLite itself (storage/db.ts snapshotDatabase). A backup
|
|
37
|
+
-- must never copy a live .db byte-wise: the WAL holds committed pages the
|
|
38
|
+
-- main file does not, so the copy would be a corrupt database or an old
|
|
39
|
+
-- one. Verified to accept a bound path on node:sqlite and bun:sqlite.
|
|
40
|
+
VACUUM INTO ?
|
|
@@ -362,3 +362,33 @@ CREATE TABLE IF NOT EXISTS whatsapp_messages (
|
|
|
362
362
|
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_msg ON whatsapp_messages(msg_id);
|
|
363
363
|
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_wa_id ON whatsapp_messages(wa_id);
|
|
364
364
|
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_time ON whatsapp_messages(timestamp);
|
|
365
|
+
|
|
366
|
+
-- Snapshot index: the listing/status view over ~/.talon/backups/. The
|
|
367
|
+
-- manifest.json next to the parts on disk stays the source of truth for
|
|
368
|
+
-- a restore (a database that needs restoring cannot also be the record
|
|
369
|
+
-- of how), so these rows are a cache — dropped rows are re-derived from
|
|
370
|
+
-- the directories on the next boot, and a row whose directory is gone is
|
|
371
|
+
-- kept because the snapshot may still exist on a remote target.
|
|
372
|
+
CREATE TABLE IF NOT EXISTS backups (
|
|
373
|
+
id TEXT PRIMARY KEY,
|
|
374
|
+
kind TEXT NOT NULL,
|
|
375
|
+
label TEXT,
|
|
376
|
+
pinned INTEGER NOT NULL DEFAULT 0,
|
|
377
|
+
created_at INTEGER NOT NULL,
|
|
378
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
379
|
+
manifest_json TEXT NOT NULL
|
|
380
|
+
);
|
|
381
|
+
CREATE INDEX IF NOT EXISTS idx_backups_created ON backups(created_at DESC);
|
|
382
|
+
|
|
383
|
+
-- Per-target upload state for one snapshot. Separate from the manifest
|
|
384
|
+
-- copy so retention can ask "what is on Drive?" without opening a file
|
|
385
|
+
-- per snapshot, and so a failed upload's error survives a restart.
|
|
386
|
+
CREATE TABLE IF NOT EXISTS backup_remotes (
|
|
387
|
+
backup_id TEXT NOT NULL,
|
|
388
|
+
target_id TEXT NOT NULL,
|
|
389
|
+
status TEXT NOT NULL,
|
|
390
|
+
remote_id TEXT,
|
|
391
|
+
uploaded_at INTEGER,
|
|
392
|
+
error TEXT,
|
|
393
|
+
PRIMARY KEY (backup_id, target_id)
|
|
394
|
+
);
|
|
@@ -366,7 +366,60 @@ CREATE TABLE IF NOT EXISTS whatsapp_messages (
|
|
|
366
366
|
);
|
|
367
367
|
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_msg ON whatsapp_messages(msg_id);
|
|
368
368
|
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_wa_id ON whatsapp_messages(wa_id);
|
|
369
|
-
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_time ON whatsapp_messages(timestamp)
|
|
369
|
+
CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_time ON whatsapp_messages(timestamp);
|
|
370
|
+
|
|
371
|
+
-- Snapshot index: the listing/status view over ~/.talon/backups/. The
|
|
372
|
+
-- manifest.json next to the parts on disk stays the source of truth for
|
|
373
|
+
-- a restore (a database that needs restoring cannot also be the record
|
|
374
|
+
-- of how), so these rows are a cache — dropped rows are re-derived from
|
|
375
|
+
-- the directories on the next boot, and a row whose directory is gone is
|
|
376
|
+
-- kept because the snapshot may still exist on a remote target.
|
|
377
|
+
CREATE TABLE IF NOT EXISTS backups (
|
|
378
|
+
id TEXT PRIMARY KEY,
|
|
379
|
+
kind TEXT NOT NULL,
|
|
380
|
+
label TEXT,
|
|
381
|
+
pinned INTEGER NOT NULL DEFAULT 0,
|
|
382
|
+
created_at INTEGER NOT NULL,
|
|
383
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
384
|
+
manifest_json TEXT NOT NULL
|
|
385
|
+
);
|
|
386
|
+
CREATE INDEX IF NOT EXISTS idx_backups_created ON backups(created_at DESC);
|
|
387
|
+
|
|
388
|
+
-- Per-target upload state for one snapshot. Separate from the manifest
|
|
389
|
+
-- copy so retention can ask "what is on Drive?" without opening a file
|
|
390
|
+
-- per snapshot, and so a failed upload's error survives a restart.
|
|
391
|
+
CREATE TABLE IF NOT EXISTS backup_remotes (
|
|
392
|
+
backup_id TEXT NOT NULL,
|
|
393
|
+
target_id TEXT NOT NULL,
|
|
394
|
+
status TEXT NOT NULL,
|
|
395
|
+
remote_id TEXT,
|
|
396
|
+
uploaded_at INTEGER,
|
|
397
|
+
error TEXT,
|
|
398
|
+
PRIMARY KEY (backup_id, target_id)
|
|
399
|
+
);`;
|
|
400
|
+
|
|
401
|
+
export const backupsSql = {
|
|
402
|
+
upsert: `INSERT OR REPLACE INTO backups
|
|
403
|
+
(id, kind, label, pinned, created_at, size_bytes, manifest_json)
|
|
404
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
405
|
+
get: `SELECT id, kind, label, pinned, created_at, size_bytes, manifest_json
|
|
406
|
+
FROM backups WHERE id = ?`,
|
|
407
|
+
all: `SELECT id, kind, label, pinned, created_at, size_bytes, manifest_json
|
|
408
|
+
FROM backups ORDER BY created_at DESC`,
|
|
409
|
+
ids: `SELECT id FROM backups`,
|
|
410
|
+
setPinned: `UPDATE backups SET pinned = ? WHERE id = ?`,
|
|
411
|
+
setManifest: `UPDATE backups SET manifest_json = ?, pinned = ?, size_bytes = ? WHERE id = ?`,
|
|
412
|
+
remove: `DELETE FROM backups WHERE id = ?`,
|
|
413
|
+
upsertRemote: `INSERT OR REPLACE INTO backup_remotes
|
|
414
|
+
(backup_id, target_id, status, remote_id, uploaded_at, error)
|
|
415
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
416
|
+
remotesAll: `SELECT backup_id, target_id, status, remote_id, uploaded_at, error
|
|
417
|
+
FROM backup_remotes`,
|
|
418
|
+
remotesFor: `SELECT backup_id, target_id, status, remote_id, uploaded_at, error
|
|
419
|
+
FROM backup_remotes WHERE backup_id = ?`,
|
|
420
|
+
removeRemotes: `DELETE FROM backup_remotes WHERE backup_id = ?`,
|
|
421
|
+
removeRemote: `DELETE FROM backup_remotes WHERE backup_id = ? AND target_id = ?`,
|
|
422
|
+
} as const;
|
|
370
423
|
|
|
371
424
|
export const chatSettingsSql = {
|
|
372
425
|
upsert: `INSERT OR REPLACE INTO chat_settings (chat_id, settings) VALUES (?, ?)`,
|
|
@@ -421,6 +474,12 @@ ALTER TABLE history_messages ADD COLUMN attachments TEXT`,
|
|
|
421
474
|
addSessionsLastTurnEndedAtColumn: `-- Column reconciliation for databases that shipped before the cache-age
|
|
422
475
|
-- signal existed. Fresh databases get the column via schema.sql.
|
|
423
476
|
ALTER TABLE sessions ADD COLUMN last_turn_ended_at INTEGER`,
|
|
477
|
+
vacuumInto: `-- Transactionally consistent copy of the whole database into a new file,
|
|
478
|
+
-- produced by SQLite itself (storage/db.ts snapshotDatabase). A backup
|
|
479
|
+
-- must never copy a live .db byte-wise: the WAL holds committed pages the
|
|
480
|
+
-- main file does not, so the copy would be a corrupt database or an old
|
|
481
|
+
-- one. Verified to accept a bound path on node:sqlite and bun:sqlite.
|
|
482
|
+
VACUUM INTO ?`,
|
|
424
483
|
} as const;
|
|
425
484
|
|
|
426
485
|
export const goalsSql = {
|
package/src/util/log.ts
CHANGED
|
File without changes
|