talon-agent 5.2.2 → 5.3.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/package.json +1 -1
- package/src/app.ts +78 -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/events.ts +14 -0
- package/src/cli/index.ts +64 -45
- 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 +52 -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
package/src/cli/index.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - `chat` — terminal chat mode
|
|
15
15
|
* - `daemon` — start/stop/restart outcome rendering
|
|
16
16
|
* - `menu` — interactive main menu (no-subcommand default)
|
|
17
|
+
* - `commands/` — one module per command; new commands land here
|
|
18
|
+
* (docs/structure.md worklist item 8)
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import pc from "picocolors";
|
|
@@ -36,6 +38,7 @@ import { runPluginCommand } from "./plugin.js";
|
|
|
36
38
|
import { runSkillCommand } from "./skill.js";
|
|
37
39
|
import { runMemoryCommand } from "./memory.js";
|
|
38
40
|
import { mainMenu } from "./menu.js";
|
|
41
|
+
import { runBackupCommand } from "./commands/backup.js";
|
|
39
42
|
|
|
40
43
|
export * from "./context.js";
|
|
41
44
|
export * from "./config.js";
|
|
@@ -58,8 +61,63 @@ const CLI_COMMANDS = [
|
|
|
58
61
|
"plugin",
|
|
59
62
|
"skill",
|
|
60
63
|
"memory",
|
|
64
|
+
"backup",
|
|
61
65
|
];
|
|
62
66
|
|
|
67
|
+
/** `talon events [-f] [--history [N]]` → the tail options. */
|
|
68
|
+
function eventsOptions(args: string[]): {
|
|
69
|
+
follow: boolean;
|
|
70
|
+
history?: number;
|
|
71
|
+
} {
|
|
72
|
+
const historyAt = args.findIndex((a) => a === "--history");
|
|
73
|
+
const next = historyAt >= 0 ? Number(args[historyAt + 1]) : NaN;
|
|
74
|
+
return {
|
|
75
|
+
follow: args.includes("-f") || args.includes("--follow"),
|
|
76
|
+
...(historyAt >= 0
|
|
77
|
+
? { history: Number.isInteger(next) && next > 0 ? next : 100 }
|
|
78
|
+
: {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The `talon --help` sheet. Its own function: `runCli` is a router. */
|
|
83
|
+
function printHelp(): void {
|
|
84
|
+
printBanner();
|
|
85
|
+
console.log(" Usage: talon [command]\n");
|
|
86
|
+
console.log(" Commands:");
|
|
87
|
+
console.log(` ${pc.cyan("setup")} Guided setup wizard`);
|
|
88
|
+
console.log(` ${pc.cyan("start")} Start as background daemon`);
|
|
89
|
+
console.log(` ${pc.cyan("stop")} Stop the daemon`);
|
|
90
|
+
console.log(` ${pc.cyan("restart")} Restart the daemon`);
|
|
91
|
+
console.log(` ${pc.cyan("run")} Run in foreground (attached)`);
|
|
92
|
+
console.log(` ${pc.cyan("chat")} Terminal chat mode`);
|
|
93
|
+
console.log(` ${pc.cyan("status")} Show bot health`);
|
|
94
|
+
console.log(
|
|
95
|
+
` ${pc.cyan("ps")} List agent tasks (--all includes journal history)`,
|
|
96
|
+
);
|
|
97
|
+
console.log(` ${pc.cyan("kill")} Abort a killable task by id`);
|
|
98
|
+
console.log(
|
|
99
|
+
` ${pc.cyan("events")} Tail the event bus (-f follows, --history [N] reads the journal)`,
|
|
100
|
+
);
|
|
101
|
+
console.log(
|
|
102
|
+
` ${pc.cyan("plugin")} Manage plugins (install/enable/disable)`,
|
|
103
|
+
);
|
|
104
|
+
console.log(
|
|
105
|
+
` ${pc.cyan("skill")} Manage skills (install/enable/disable)`,
|
|
106
|
+
);
|
|
107
|
+
console.log(
|
|
108
|
+
` ${pc.cyan("memory")} Read/edit the memory store (list/search/import/render)`,
|
|
109
|
+
);
|
|
110
|
+
console.log(
|
|
111
|
+
` ${pc.cyan("backup")} Snapshots and checkpoints (now/list/show/pin/restore)`,
|
|
112
|
+
);
|
|
113
|
+
console.log(` ${pc.cyan("config")} View/edit configuration`);
|
|
114
|
+
console.log(` ${pc.cyan("logs")} Tail log file`);
|
|
115
|
+
console.log(` ${pc.cyan("doctor")} Validate environment`);
|
|
116
|
+
console.log(` ${pc.cyan("--version")} Print the package version`);
|
|
117
|
+
console.log();
|
|
118
|
+
console.log(` Run ${pc.cyan("talon")} with no args for interactive menu.\n`);
|
|
119
|
+
}
|
|
120
|
+
|
|
63
121
|
/** Route a `talon <command>` invocation. Called by the entry point. */
|
|
64
122
|
export async function runCli(): Promise<void> {
|
|
65
123
|
const command = process.argv[2];
|
|
@@ -105,18 +163,12 @@ export async function runCli(): Promise<void> {
|
|
|
105
163
|
case "kill":
|
|
106
164
|
await killTask(process.argv[3]);
|
|
107
165
|
break;
|
|
108
|
-
case "events":
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await
|
|
113
|
-
follow: args.includes("-f") || args.includes("--follow"),
|
|
114
|
-
...(historyAt >= 0
|
|
115
|
-
? { history: Number.isInteger(next) && next > 0 ? next : 100 }
|
|
116
|
-
: {}),
|
|
117
|
-
});
|
|
166
|
+
case "events":
|
|
167
|
+
await showEvents(eventsOptions(process.argv.slice(3)));
|
|
168
|
+
break;
|
|
169
|
+
case "backup":
|
|
170
|
+
await runBackupCommand(process.argv.slice(3));
|
|
118
171
|
break;
|
|
119
|
-
}
|
|
120
172
|
case "plugin":
|
|
121
173
|
await runPluginCommand(process.argv.slice(3));
|
|
122
174
|
break;
|
|
@@ -133,40 +185,7 @@ export async function runCli(): Promise<void> {
|
|
|
133
185
|
}
|
|
134
186
|
case "--help":
|
|
135
187
|
case "-h":
|
|
136
|
-
|
|
137
|
-
console.log(" Usage: talon [command]\n");
|
|
138
|
-
console.log(" Commands:");
|
|
139
|
-
console.log(` ${pc.cyan("setup")} Guided setup wizard`);
|
|
140
|
-
console.log(` ${pc.cyan("start")} Start as background daemon`);
|
|
141
|
-
console.log(` ${pc.cyan("stop")} Stop the daemon`);
|
|
142
|
-
console.log(` ${pc.cyan("restart")} Restart the daemon`);
|
|
143
|
-
console.log(` ${pc.cyan("run")} Run in foreground (attached)`);
|
|
144
|
-
console.log(` ${pc.cyan("chat")} Terminal chat mode`);
|
|
145
|
-
console.log(` ${pc.cyan("status")} Show bot health`);
|
|
146
|
-
console.log(
|
|
147
|
-
` ${pc.cyan("ps")} List agent tasks (--all includes journal history)`,
|
|
148
|
-
);
|
|
149
|
-
console.log(` ${pc.cyan("kill")} Abort a killable task by id`);
|
|
150
|
-
console.log(
|
|
151
|
-
` ${pc.cyan("events")} Tail the event bus (-f follows, --history [N] reads the journal)`,
|
|
152
|
-
);
|
|
153
|
-
console.log(
|
|
154
|
-
` ${pc.cyan("plugin")} Manage plugins (install/enable/disable)`,
|
|
155
|
-
);
|
|
156
|
-
console.log(
|
|
157
|
-
` ${pc.cyan("skill")} Manage skills (install/enable/disable)`,
|
|
158
|
-
);
|
|
159
|
-
console.log(
|
|
160
|
-
` ${pc.cyan("memory")} Read/edit the memory store (list/search/import/render)`,
|
|
161
|
-
);
|
|
162
|
-
console.log(` ${pc.cyan("config")} View/edit configuration`);
|
|
163
|
-
console.log(` ${pc.cyan("logs")} Tail log file`);
|
|
164
|
-
console.log(` ${pc.cyan("doctor")} Validate environment`);
|
|
165
|
-
console.log(` ${pc.cyan("--version")} Print the package version`);
|
|
166
|
-
console.log();
|
|
167
|
-
console.log(
|
|
168
|
-
` Run ${pc.cyan("talon")} with no args for interactive menu.\n`,
|
|
169
|
-
);
|
|
188
|
+
printHelp();
|
|
170
189
|
break;
|
|
171
190
|
case undefined:
|
|
172
191
|
mainMenu();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digests — what makes a snapshot verifiable and the palace part reusable.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs:
|
|
5
|
+
* - `Sha256Tap` sits in the write pipeline and hashes the COMPRESSED
|
|
6
|
+
* bytes on their way to disk, so the manifest's sha256 is produced
|
|
7
|
+
* without a second pass over a multi-GB part. The restore path checks
|
|
8
|
+
* the same digest before it unpacks anything.
|
|
9
|
+
* - `treeHash` fingerprints a directory (path + size + mtime + content
|
|
10
|
+
* digest of every file) so an unchanged memory palace can be carried
|
|
11
|
+
* forward from the previous snapshot instead of recompressed and
|
|
12
|
+
* re-uploaded every six hours.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { createReadStream } from "node:fs";
|
|
17
|
+
import { Transform, type TransformCallback } from "node:stream";
|
|
18
|
+
|
|
19
|
+
/** Pass-through stream that hashes (and counts) everything crossing it. */
|
|
20
|
+
export class Sha256Tap extends Transform {
|
|
21
|
+
private readonly hash = createHash("sha256");
|
|
22
|
+
private digested: string | null = null;
|
|
23
|
+
private seen = 0;
|
|
24
|
+
|
|
25
|
+
override _transform(
|
|
26
|
+
chunk: Buffer,
|
|
27
|
+
_encoding: BufferEncoding,
|
|
28
|
+
done: TransformCallback,
|
|
29
|
+
): void {
|
|
30
|
+
this.hash.update(chunk);
|
|
31
|
+
this.seen += chunk.length;
|
|
32
|
+
done(null, chunk);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Hex digest of everything written. Valid once the stream has finished. */
|
|
36
|
+
digest(): string {
|
|
37
|
+
if (this.digested === null) this.digested = this.hash.digest("hex");
|
|
38
|
+
return this.digested;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Bytes seen so far — the part's size, without a stat() race. */
|
|
42
|
+
get byteLength(): number {
|
|
43
|
+
return this.seen;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Streaming sha256 of a file on disk. */
|
|
48
|
+
export async function sha256File(path: string): Promise<string> {
|
|
49
|
+
const hash = createHash("sha256");
|
|
50
|
+
for await (const chunk of createReadStream(path))
|
|
51
|
+
hash.update(chunk as Buffer);
|
|
52
|
+
return hash.digest("hex");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One file's identity inside a tree fingerprint. */
|
|
56
|
+
export type TreeFile = {
|
|
57
|
+
/** Path relative to the tree root, POSIX separators. */
|
|
58
|
+
path: string;
|
|
59
|
+
size: number;
|
|
60
|
+
/** Modification time in epoch SECONDS (tar's resolution — stable). */
|
|
61
|
+
mtime: number;
|
|
62
|
+
sha256: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fingerprint a set of files. Sorted by path so the value depends on the
|
|
67
|
+
* content of the tree and not on readdir order.
|
|
68
|
+
*/
|
|
69
|
+
export function treeHash(files: readonly TreeFile[]): string {
|
|
70
|
+
const hash = createHash("sha256");
|
|
71
|
+
for (const file of [...files].sort((a, b) =>
|
|
72
|
+
a.path < b.path ? -1 : a.path > b.path ? 1 : 0,
|
|
73
|
+
)) {
|
|
74
|
+
hash.update(`${file.path}\0${file.size}\0${file.mtime}\0${file.sha256}\n`);
|
|
75
|
+
}
|
|
76
|
+
return hash.digest("hex");
|
|
77
|
+
}
|