talon-agent 5.28.0 → 5.30.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 +3 -2
- package/prompts/system/agent-brief.md +9 -6
- package/src/backend/claude-sdk/constants.ts +22 -0
- package/src/backend/claude-sdk/models/discovery.ts +3 -0
- package/src/backend/claude-sdk/one-shot.ts +3 -1
- package/src/backend/claude-sdk/options.ts +7 -1
- package/src/core/agents/index.ts +2 -0
- package/src/core/agents/prompt.ts +70 -2
- package/src/core/agents/registry.ts +47 -5
- package/src/core/agents/runner.ts +227 -39
- package/src/core/agents/trail.ts +141 -0
- package/src/core/agents/types.ts +43 -5
- package/src/core/agents/watchdog.ts +70 -0
- package/src/core/background/isolated-agent.ts +6 -2
- package/src/core/backup/plan.ts +4 -0
- package/src/core/config/index.ts +17 -4
- package/src/core/engine/gateway-actions/agents/control.ts +9 -2
- package/src/core/engine/gateway-actions/agents/preflight.ts +17 -1
- package/src/core/engine/gateway-actions/agents/report.ts +81 -24
- package/src/core/engine/gateway-actions/index.ts +3 -0
- package/src/core/mcp-hub/guest-scope.ts +3 -1
- package/src/core/mesh/devices/service.ts +7 -0
- package/src/core/mesh/links/bridge-links.ts +20 -0
- package/src/core/secrets/actions.ts +18 -0
- package/src/core/secrets/drop.ts +176 -0
- package/src/core/secrets/index.ts +11 -0
- package/src/core/secrets/service.ts +248 -0
- package/src/core/secrets/store.ts +100 -0
- package/src/core/tools/index.ts +2 -0
- package/src/core/tools/ops/agents.ts +30 -12
- package/src/core/tools/ops/secrets.ts +36 -0
- package/src/core/tools/types.ts +2 -1
- package/src/frontend/discord/commands/definitions.ts +21 -0
- package/src/frontend/discord/commands/router.ts +3 -0
- package/src/frontend/discord/commands/secret.ts +26 -0
- package/src/frontend/discord/handlers/messages.ts +6 -4
- package/src/frontend/native/bridge/routes/host.ts +10 -0
- package/src/frontend/native/bridge/routes/pre-auth.ts +82 -1
- package/src/frontend/native/bridge/routes/table.ts +5 -0
- package/src/frontend/native/bridge/server.ts +32 -4
- package/src/frontend/native/commands/definitions.ts +6 -0
- package/src/frontend/native/commands/index.ts +12 -0
- package/src/frontend/native/surface/handlers.ts +9 -0
- package/src/frontend/telegram/actions/chat-info.ts +22 -5
- package/src/frontend/telegram/commands/definitions.ts +4 -0
- package/src/frontend/telegram/commands/index.ts +3 -0
- package/src/frontend/telegram/commands/secret.ts +24 -0
- package/src/frontend/telegram/handlers/messages.ts +7 -5
- package/src/frontend/whatsapp/commands.ts +16 -1
- package/src/frontend/whatsapp/messages/media-store.ts +4 -2
- package/src/storage/media-index.ts +43 -11
- package/src/storage/repositories/media-index-repo.ts +12 -1
- package/src/storage/sql/media-index.sql +4 -0
- package/src/storage/sql/statements.generated.ts +2 -0
- package/src/util/log.ts +1 -0
- package/src/util/paths.ts +6 -0
|
@@ -73,7 +73,19 @@ function importLegacyMediaIndex(): void {
|
|
|
73
73
|
|
|
74
74
|
// ── CRUD ────────────────────────────────────────────────────────────────────
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Index a downloaded file and dedupe it against identical content.
|
|
78
|
+
*
|
|
79
|
+
* Resolves to the path the message's media now lives at — the
|
|
80
|
+
* canonical copy when the download duplicated one already on disk
|
|
81
|
+
* (the fresh file is then deleted), otherwise `entry.filePath`.
|
|
82
|
+
* Callers MUST use the resolved path for anything they show the
|
|
83
|
+
* model: the original path may no longer exist once this settles.
|
|
84
|
+
*
|
|
85
|
+
* The row is written synchronously, so it is queryable before the
|
|
86
|
+
* hash finishes.
|
|
87
|
+
*/
|
|
88
|
+
export async function addMedia(entry: Omit<MediaEntry, "id">): Promise<string> {
|
|
77
89
|
try {
|
|
78
90
|
repo.upsert(entry);
|
|
79
91
|
} catch (err) {
|
|
@@ -85,13 +97,32 @@ export function addMedia(entry: Omit<MediaEntry, "id">): void {
|
|
|
85
97
|
recordError(
|
|
86
98
|
`Media index write failed: ${err instanceof Error ? err.message : err}`,
|
|
87
99
|
);
|
|
88
|
-
return;
|
|
100
|
+
return entry.filePath;
|
|
89
101
|
}
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
// Serialize dedupe: two identical files hashed concurrently (e.g. an
|
|
103
|
+
// album) could otherwise each pick the other as canonical and both
|
|
104
|
+
// get unlinked.
|
|
105
|
+
const run = dedupeChain.then(() => hashAndDedupe(entry));
|
|
106
|
+
dedupeChain = run.then(
|
|
107
|
+
() => undefined,
|
|
108
|
+
() => undefined,
|
|
94
109
|
);
|
|
110
|
+
try {
|
|
111
|
+
return await run;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logError("media", `Media content hash failed for ${entry.filePath}`, err);
|
|
114
|
+
return entry.filePath;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let dedupeChain: Promise<void> = Promise.resolve();
|
|
119
|
+
|
|
120
|
+
/** The indexed entry for a message, if any. */
|
|
121
|
+
export function getMediaForMessage(
|
|
122
|
+
chatId: string,
|
|
123
|
+
msgId: number,
|
|
124
|
+
): MediaEntry | undefined {
|
|
125
|
+
return repo.byMessage(chatId, msgId);
|
|
95
126
|
}
|
|
96
127
|
|
|
97
128
|
/**
|
|
@@ -101,15 +132,15 @@ export function addMedia(entry: Omit<MediaEntry, "id">): void {
|
|
|
101
132
|
* canonical copy and drop the duplicate file, so re-posted media costs
|
|
102
133
|
* one copy on disk no matter how many messages carry it.
|
|
103
134
|
*/
|
|
104
|
-
async function hashAndDedupe(entry: Omit<MediaEntry, "id">): Promise<
|
|
105
|
-
if (!existsSync(entry.filePath)) return; // gone already (expiry, tests)
|
|
135
|
+
async function hashAndDedupe(entry: Omit<MediaEntry, "id">): Promise<string> {
|
|
136
|
+
if (!existsSync(entry.filePath)) return entry.filePath; // gone already (expiry, tests)
|
|
106
137
|
const hash = await blake3HexFile(entry.filePath);
|
|
107
138
|
repo.setContentHash(entry.chatId, entry.msgId, hash);
|
|
108
139
|
|
|
109
140
|
const canonical = repo.firstByContentHash(hash, entry.chatId, entry.msgId);
|
|
110
|
-
if (!canonical) return;
|
|
111
|
-
if (canonical.filePath === entry.filePath) return; // re-download of the same path
|
|
112
|
-
if (!existsSync(canonical.filePath)) return; // canonical copy lost — keep ours
|
|
141
|
+
if (!canonical) return entry.filePath;
|
|
142
|
+
if (canonical.filePath === entry.filePath) return entry.filePath; // re-download of the same path
|
|
143
|
+
if (!existsSync(canonical.filePath)) return entry.filePath; // canonical copy lost — keep ours
|
|
113
144
|
|
|
114
145
|
repo.setFilePath(entry.chatId, entry.msgId, canonical.filePath);
|
|
115
146
|
setMessageFilePath(entry.chatId, entry.msgId, canonical.filePath);
|
|
@@ -131,6 +162,7 @@ async function hashAndDedupe(entry: Omit<MediaEntry, "id">): Promise<void> {
|
|
|
131
162
|
);
|
|
132
163
|
}
|
|
133
164
|
}
|
|
165
|
+
return canonical.filePath;
|
|
134
166
|
}
|
|
135
167
|
|
|
136
168
|
/** Get recent media for a chat, newest first. */
|
|
@@ -33,7 +33,7 @@ export type MediaEntry = {
|
|
|
33
33
|
timestamp: number;
|
|
34
34
|
/**
|
|
35
35
|
* BLAKE3 hex digest of the file contents (native/blake3-wasm).
|
|
36
|
-
* Filled in
|
|
36
|
+
* Filled in by addMedia before it resolves; undefined until hashed.
|
|
37
37
|
*/
|
|
38
38
|
contentHash?: string;
|
|
39
39
|
};
|
|
@@ -112,6 +112,17 @@ export function firstByContentHash(
|
|
|
112
112
|
return row ? rowToEntry(row) : undefined;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/** The entry for one message, if indexed. */
|
|
116
|
+
export function byMessage(
|
|
117
|
+
chatId: string,
|
|
118
|
+
msgId: number,
|
|
119
|
+
): MediaEntry | undefined {
|
|
120
|
+
const row = getDatabase()
|
|
121
|
+
.prepare(mediaIndexSql.byMessage)
|
|
122
|
+
.get(chatId, msgId) as Row | undefined;
|
|
123
|
+
return row ? rowToEntry(row) : undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
/** Entries currently pointing at a file — dedupe makes this > 1. */
|
|
116
127
|
export function countByFilePath(filePath: string): number {
|
|
117
128
|
const row = getDatabase()
|
|
@@ -43,3 +43,7 @@ ORDER BY timestamp ASC, rowid ASC LIMIT 1
|
|
|
43
43
|
|
|
44
44
|
-- name: countByFilePath
|
|
45
45
|
SELECT COUNT(*) AS n FROM media_index WHERE file_path = ?
|
|
46
|
+
|
|
47
|
+
-- name: byMessage
|
|
48
|
+
SELECT chat_id, msg_id, sender_name, type, file_path, caption, timestamp, content_hash
|
|
49
|
+
FROM media_index WHERE chat_id = ? AND msg_id = ?
|
|
@@ -767,6 +767,8 @@ FROM media_index
|
|
|
767
767
|
WHERE content_hash = ? AND NOT (chat_id = ? AND msg_id = ?)
|
|
768
768
|
ORDER BY timestamp ASC, rowid ASC LIMIT 1`,
|
|
769
769
|
countByFilePath: `SELECT COUNT(*) AS n FROM media_index WHERE file_path = ?`,
|
|
770
|
+
byMessage: `SELECT chat_id, msg_id, sender_name, type, file_path, caption, timestamp, content_hash
|
|
771
|
+
FROM media_index WHERE chat_id = ? AND msg_id = ?`,
|
|
770
772
|
} as const;
|
|
771
773
|
|
|
772
774
|
export const memorySql = {
|
package/src/util/log.ts
CHANGED
package/src/util/paths.ts
CHANGED
|
@@ -88,6 +88,12 @@ export const dirs = {
|
|
|
88
88
|
skills: resolve(TALON_ROOT, "workspace", "skills"),
|
|
89
89
|
/** Key material (bridge TLS identity, release keys): ~/.talon/keys/ */
|
|
90
90
|
keys: resolve(TALON_ROOT, "keys"),
|
|
91
|
+
/**
|
|
92
|
+
* Operator secrets, one file per value (mode 600): ~/.talon/secrets/.
|
|
93
|
+
* Written by the secret drop (`/secret <name>`, `request_secret`) so a
|
|
94
|
+
* password never has to travel through a chat message.
|
|
95
|
+
*/
|
|
96
|
+
secrets: resolve(TALON_ROOT, "secrets"),
|
|
91
97
|
/**
|
|
92
98
|
* The talon:// namespace on disk: ~/.talon/ns/. Holds one symlink per
|
|
93
99
|
* file-backed mount (home → workspace/, skills/, …); while the daemon
|