talon-agent 3.12.0 → 3.12.1
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
CHANGED
|
@@ -31,7 +31,7 @@ import { scriptHandlers } from "./scripts.js";
|
|
|
31
31
|
import { skillHandlers } from "./skills.js";
|
|
32
32
|
import { pluginHandlers } from "./plugins.js";
|
|
33
33
|
import { modelHandlers } from "./models.js";
|
|
34
|
-
import { meshHandlers } from "./mesh.js";
|
|
34
|
+
import { meshHandlers, chatFreeActions } from "./mesh.js";
|
|
35
35
|
import { nativeHandlers } from "./native.js";
|
|
36
36
|
|
|
37
37
|
// Null-prototype so a request `action` of "toString" / "constructor" / etc.
|
|
@@ -62,4 +62,29 @@ export async function handleSharedAction(
|
|
|
62
62
|
return handler(body, chatId, backend);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* True when the action needs no chat context at all (see `chatFreeActions`).
|
|
67
|
+
* The gateway checks this before resolving a chat so mesh tools stay reachable
|
|
68
|
+
* from heartbeat/background runs.
|
|
69
|
+
*/
|
|
70
|
+
export function isChatFreeAction(action: string): boolean {
|
|
71
|
+
return chatFreeActions.has(action);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Dispatch a chat-free action. The handler signature still takes a chatId
|
|
76
|
+
* (they all share one type); chat-free handlers ignore it, and `0` is passed
|
|
77
|
+
* as an explicit "no chat" sentinel rather than a real id.
|
|
78
|
+
*/
|
|
79
|
+
export async function handleChatFreeAction(
|
|
80
|
+
body: Record<string, unknown>,
|
|
81
|
+
): Promise<ActionResult | null> {
|
|
82
|
+
const action = body.action as string;
|
|
83
|
+
if (!isChatFreeAction(action)) return null;
|
|
84
|
+
const handler = handlers[action];
|
|
85
|
+
if (!handler) return null;
|
|
86
|
+
return handler(body, 0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { chatFreeActions };
|
|
65
90
|
export type { SharedActionHandler, SharedActionHandlers } from "./types.js";
|
|
@@ -78,3 +78,36 @@ export const meshHandlers: SharedActionHandlers = {
|
|
|
78
78
|
body.bridge_url,
|
|
79
79
|
),
|
|
80
80
|
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Mesh actions are chat-free: every handler above reads `body` and the
|
|
84
|
+
* daemon-wide mesh registry, and not one of them takes the `chatId` the
|
|
85
|
+
* gateway threads through. Requiring an active chat context to reach them is
|
|
86
|
+
* therefore incidental to the transport, not a property of the action — and
|
|
87
|
+
* it makes the whole mesh unreachable from heartbeat/background runs, which
|
|
88
|
+
* have no ambient chat and no `chat_id` parameter on these tools to promote.
|
|
89
|
+
*
|
|
90
|
+
* The gateway consults this set before chat resolution so `list_devices`,
|
|
91
|
+
* `device_exec` and friends work from any run mode. Kept as an explicit
|
|
92
|
+
* export (rather than `Object.keys(meshHandlers)`) so adding a handler that
|
|
93
|
+
* *does* need a chat is a deliberate opt-out rather than a silent inclusion.
|
|
94
|
+
*/
|
|
95
|
+
export const chatFreeActions: ReadonlySet<string> = new Set([
|
|
96
|
+
"list_devices",
|
|
97
|
+
"get_device_location",
|
|
98
|
+
"get_device_history",
|
|
99
|
+
"ring_device",
|
|
100
|
+
"remove_device",
|
|
101
|
+
"get_device_status",
|
|
102
|
+
"device_exec",
|
|
103
|
+
"device_list_dir",
|
|
104
|
+
"device_stat",
|
|
105
|
+
"device_read_file",
|
|
106
|
+
"device_write_file",
|
|
107
|
+
"device_pull_file",
|
|
108
|
+
"device_push_file",
|
|
109
|
+
"update_device",
|
|
110
|
+
"update_node",
|
|
111
|
+
"get_node_binary",
|
|
112
|
+
"make_node_install_link",
|
|
113
|
+
]);
|
|
@@ -20,7 +20,11 @@ import { Loom, getActiveLoom, type ContextRegistry } from "../weaver/index.js";
|
|
|
20
20
|
import { getHealthStatus } from "../../util/watchdog.js";
|
|
21
21
|
import { getActiveSessionCount } from "../../storage/sessions.js";
|
|
22
22
|
import { log, logError, logDebug } from "../../util/log.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
handleSharedAction,
|
|
25
|
+
handleChatFreeAction,
|
|
26
|
+
isChatFreeAction,
|
|
27
|
+
} from "./gateway-actions/index.js";
|
|
24
28
|
import {
|
|
25
29
|
handleHubRequest,
|
|
26
30
|
getHubSessionCount,
|
|
@@ -249,6 +253,31 @@ export class Gateway {
|
|
|
249
253
|
// active-context-required check — the action handler will reach the
|
|
250
254
|
// chat directly via the Telegram Bot API. The legacy context-required
|
|
251
255
|
// path remains for chat-mode calls where `chat_id` is absent.
|
|
256
|
+
// Chat-free actions (the device mesh) short-circuit routing entirely:
|
|
257
|
+
// they read daemon-wide state, ignore chatId, and are the only command
|
|
258
|
+
// channel a heartbeat run has to a remote box. Gating them behind an
|
|
259
|
+
// active chat made the whole mesh unreachable from background runs —
|
|
260
|
+
// and unlike send/react they carry no `chat_id` param to promote.
|
|
261
|
+
const requestedAction =
|
|
262
|
+
typeof body.action === "string" ? body.action : undefined;
|
|
263
|
+
if (requestedAction && isChatFreeAction(requestedAction)) {
|
|
264
|
+
const t0 = Date.now();
|
|
265
|
+
try {
|
|
266
|
+
const result = await handleChatFreeAction(body);
|
|
267
|
+
if (result) {
|
|
268
|
+
logDebug(
|
|
269
|
+
"gateway",
|
|
270
|
+
`${requestedAction} chat=none ${Date.now() - t0}ms (chat-free)`,
|
|
271
|
+
);
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
276
|
+
logError("gateway", `${requestedAction} (chat-free) failed: ${msg}`);
|
|
277
|
+
return { ok: false, error: `${requestedAction}: ${msg}` };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
252
281
|
const rawChatId = body._chatId ? String(body._chatId) : "";
|
|
253
282
|
const numericId = Number(rawChatId);
|
|
254
283
|
const explicitChatIdProvided = typeof body.chat_id !== "undefined";
|