talon-agent 5.18.2 → 5.19.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/README.md +2 -1
- package/package.json +2 -2
- package/prompts/system/agent-brief.md +20 -3
- package/src/app.ts +13 -0
- package/src/backend/claude-sdk/handler.ts +4 -4
- package/src/backend/claude-sdk/mcp-ready.ts +16 -2
- package/src/backend/claude-sdk/one-shot.ts +32 -2
- package/src/backend/claude-sdk/stream.ts +2 -2
- package/src/backend/codex/auth.ts +1 -1
- package/src/backend/codex/handler/message.ts +11 -11
- package/src/backend/codex/init.ts +4 -9
- package/src/backend/codex/mcp-config.ts +1 -2
- package/src/backend/codex/oauth-incompat.ts +8 -4
- package/src/backend/codex/one-shot.ts +1 -1
- package/src/backend/openai-agents/builtins.ts +55 -27
- package/src/backend/openai-agents/factory.ts +3 -3
- package/src/backend/openai-agents/handler/message.ts +3 -5
- package/src/backend/openai-agents/mcp-pool.ts +10 -27
- package/src/backend/remote-server/chat-turn.ts +6 -6
- package/src/backend/remote-server/events.ts +1 -5
- package/src/backend/remote-server/index.ts +0 -1
- package/src/backend/remote-server/messages.ts +3 -7
- package/src/backend/remote-server/one-shot.ts +1 -3
- package/src/backend/remote-server/session-helpers.ts +1 -4
- package/src/backend/remote-server/sse-stream.ts +8 -9
- package/src/backend/runtime/metrics.ts +7 -13
- package/src/backend/runtime/sleep.ts +1 -2
- package/src/backend/runtime/turn/handle-retry.ts +48 -2
- package/src/backend/runtime/turn/handler-to-events.ts +3 -4
- package/src/bootstrap.ts +9 -1
- package/src/cli/doctor.ts +3 -0
- package/src/cli/index.ts +8 -10
- package/src/cli/logs.ts +148 -9
- package/src/cli/setup.ts +9 -11
- package/src/cli/status.ts +29 -0
- package/src/core/agent-runtime/README.md +5 -19
- package/src/core/agent-runtime/events.ts +3 -39
- package/src/core/agent-runtime/model-ref.ts +0 -8
- package/src/core/agents/registry.ts +65 -2
- package/src/core/auth/expiry-monitor.ts +9 -1
- package/src/core/auth/login-flow.ts +9 -1
- package/src/core/auth/status.ts +31 -3
- package/src/core/background/cron/scheduler.ts +25 -5
- package/src/core/background/dream/index.ts +29 -10
- package/src/core/background/failure-backoff.ts +30 -0
- package/src/core/background/heartbeat/agent.ts +2 -27
- package/src/core/background/heartbeat/index.ts +0 -2
- package/src/core/background/heartbeat/scheduler.ts +21 -13
- package/src/core/background/heartbeat/state.ts +10 -2
- package/src/core/background/isolated-agent.ts +6 -2
- package/src/core/background/pulse/pulse.ts +9 -0
- package/src/core/background/triggers/exit.ts +54 -0
- package/src/core/background/triggers/index.ts +1 -3
- package/src/core/background/triggers/resume.ts +2 -4
- package/src/core/backup/archive/tar.ts +14 -4
- package/src/core/backup/plan.ts +1 -0
- package/src/core/backup/restore.ts +21 -13
- package/src/core/backup/scheduler.ts +28 -9
- package/src/core/backup/snapshot.ts +43 -10
- package/src/core/backup/store.ts +7 -19
- package/src/core/backup/targets.ts +69 -17
- package/src/core/config/index.ts +14 -0
- package/src/core/daemon/crash-marker.ts +141 -0
- package/src/core/daemon/crash.ts +9 -2
- package/src/core/daemon/handoff.ts +15 -0
- package/src/core/daemon/health-alerts.ts +297 -0
- package/src/core/daemon/log-reader.ts +289 -0
- package/src/core/doctor/index.ts +18 -2
- package/src/core/doctor/logs.ts +124 -0
- package/src/core/doctor/types.ts +1 -1
- package/src/core/engine/backend-controller/index.ts +1 -13
- package/src/core/engine/backend-router/router.ts +1 -1
- package/src/core/engine/dispatcher.ts +55 -2
- package/src/core/engine/fault-text.ts +40 -0
- package/src/core/engine/gateway-actions/agents/index.ts +3 -2
- package/src/core/engine/gateway-actions/agents/report.ts +62 -0
- package/src/core/engine/gateway-actions/history.ts +2 -4
- package/src/core/engine/gateway-actions/mesh.ts +26 -14
- package/src/core/engine/gateway-actions/native/exec.ts +13 -16
- package/src/core/engine/gateway-actions/native/teleport.ts +1 -1
- package/src/core/engine/gateway.ts +60 -1
- package/src/core/engine/turn-health.ts +222 -0
- package/src/core/errors.ts +2 -2
- package/src/core/frontend-runtime/admin-notify.ts +1 -1
- package/src/core/frontend-runtime/alerts.ts +130 -0
- package/src/core/mcp-hub/children.ts +78 -29
- package/src/core/mcp-hub/index.ts +21 -18
- package/src/core/mcp-hub/proxy-server.ts +8 -4
- package/src/core/mcp-hub/talon-server.ts +5 -12
- package/src/core/mesh/credentials/store.ts +16 -1
- package/src/core/mesh/devices/registry.ts +1 -17
- package/src/core/mesh/devices/service.ts +49 -17
- package/src/core/mesh/devices/teleport.ts +14 -2
- package/src/core/mesh/links/node-binaries.ts +13 -6
- package/src/core/mesh/persist.ts +22 -10
- package/src/core/mesh/transfers/device-files.ts +5 -23
- package/src/core/mesh/transfers/transfers.ts +16 -3
- package/src/core/models/active-model.ts +2 -55
- package/src/core/plugin/actions.ts +19 -20
- package/src/core/plugin/builtins.ts +80 -90
- package/src/core/plugin/index.ts +1 -4
- package/src/core/plugin/loader.ts +25 -33
- package/src/core/plugin/mcp.ts +3 -5
- package/src/core/plugin/registry.ts +19 -35
- package/src/core/plugin/types.ts +2 -5
- package/src/core/prompt/assemble.ts +15 -3
- package/src/core/scripts/lua.ts +6 -2
- package/src/core/tasks/table.ts +8 -2
- package/src/core/tools/bridge.ts +2 -4
- package/src/core/tools/chat/cross-send.ts +1 -1
- package/src/core/tools/chat/messaging.ts +1 -1
- package/src/core/tools/index.ts +2 -2
- package/src/core/tools/mcp-env.ts +2 -59
- package/src/core/tools/ops/agents.ts +22 -1
- package/src/core/tools/schemas.ts +4 -9
- package/src/core/vfs/fusefs.ts +0 -5
- package/src/core/vfs/index.ts +9 -2
- package/src/core/vfs/mounts/diagnostics.ts +109 -0
- package/src/core/vfs/mounts/proc.ts +17 -1
- package/src/core/vfs/workspace.ts +7 -3
- package/src/core/weaver/shuttle.ts +8 -1
- package/src/core/weaver/turn-log.ts +320 -0
- package/src/core/weaver/weaver.ts +48 -5
- package/src/frontend/discord/actions/index.ts +8 -1
- package/src/frontend/discord/diagnostics.ts +82 -6
- package/src/frontend/discord/handlers/index.ts +0 -2
- package/src/frontend/discord/middleware.ts +7 -13
- package/src/frontend/discord/runtime.ts +1 -3
- package/src/frontend/health/delivery.ts +115 -0
- package/src/frontend/health/outage.ts +116 -0
- package/src/frontend/native/bridge/routes/chats.ts +3 -5
- package/src/frontend/native/bridge/server.ts +106 -21
- package/src/frontend/native/index.ts +1 -1
- package/src/frontend/native/media/media.ts +5 -1
- package/src/frontend/native/runtime.ts +12 -7
- package/src/frontend/native/surface/handlers.ts +1 -1
- package/src/frontend/native/surface/memory.ts +1 -1
- package/src/frontend/native/surface/models.ts +3 -3
- package/src/frontend/native/surface/settings.ts +20 -8
- package/src/frontend/native/turn/context.ts +6 -8
- package/src/frontend/native/turn/turn-meta.ts +2 -5
- package/src/frontend/native/turn/turn.ts +8 -10
- package/src/frontend/presentation/format.ts +2 -4
- package/src/frontend/presentation/session-status.ts +2 -6
- package/src/frontend/teams/actions.ts +8 -1
- package/src/frontend/teams/graph.ts +0 -1
- package/src/frontend/teams/index.ts +1 -4
- package/src/frontend/teams/poll.ts +40 -2
- package/src/frontend/teams/runtime.ts +14 -5
- package/src/frontend/telegram/actions/index.ts +4 -1
- package/src/frontend/telegram/actions/send.ts +8 -0
- package/src/frontend/telegram/handlers/context.ts +13 -2
- package/src/frontend/telegram/handlers/delivery.ts +12 -9
- package/src/frontend/telegram/handlers/index.ts +0 -2
- package/src/frontend/telegram/index.ts +35 -9
- package/src/frontend/telegram/polling/poll-health.ts +110 -0
- package/src/frontend/telegram/userbot.ts +100 -36
- package/src/frontend/terminal/builtins/session.ts +2 -2
- package/src/frontend/terminal/index.ts +1 -3
- package/src/frontend/terminal/renderer.ts +2 -18
- package/src/frontend/whatsapp/actions/index.ts +12 -1
- package/src/frontend/whatsapp/actions/messaging.ts +7 -2
- package/src/frontend/whatsapp/connection/connection.ts +29 -9
- package/src/frontend/whatsapp/connection/health.ts +89 -0
- package/src/frontend/whatsapp/connection/identity.ts +4 -4
- package/src/frontend/whatsapp/runtime.ts +11 -5
- package/src/native/blake3.ts +28 -2
- package/src/native/fusefs.ts +23 -5
- package/src/native/registry.ts +1 -1
- package/src/native/warden.ts +33 -5
- package/src/plugins/github/index.ts +0 -1
- package/src/plugins/mempalace/index.ts +9 -3
- package/src/plugins/playwright/index.ts +2 -4
- package/src/plugins/playwright/provision.ts +12 -4
- package/src/storage/chat-settings.ts +6 -1
- package/src/storage/cron.ts +29 -4
- package/src/storage/daily-log.ts +43 -47
- package/src/storage/db.ts +61 -33
- package/src/storage/history.ts +6 -1
- package/src/storage/journal.ts +9 -2
- package/src/storage/kv.ts +19 -6
- package/src/storage/media-index.ts +28 -6
- package/src/storage/repositories/chat-settings-repo.ts +8 -2
- package/src/storage/repositories/sessions-repo.ts +10 -3
- package/src/storage/scripts.ts +24 -13
- package/src/storage/sessions.ts +11 -2
- package/src/storage/skills.ts +21 -2
- package/src/storage/stickers.ts +17 -3
- package/src/storage/triggers.ts +8 -3
- package/src/storage/turn-meta.ts +25 -7
- package/src/util/log.ts +189 -6
- package/src/util/logging/turn-scope.ts +85 -0
- package/src/util/time.ts +3 -3
- package/src/util/watchdog.ts +30 -0
- package/src/core/engine/backend-controller/legacy.ts +0 -111
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { randomUUID } from "node:crypto";
|
|
18
|
-
import { createHash } from "node:crypto";
|
|
19
|
-
import { createReadStream } from "node:fs";
|
|
20
18
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
21
19
|
import { tmpdir } from "node:os";
|
|
22
20
|
import { basename, dirname, join, resolve } from "node:path";
|
|
@@ -29,6 +27,7 @@ import {
|
|
|
29
27
|
type MeshToolResult,
|
|
30
28
|
} from "../tool-surface.js";
|
|
31
29
|
import {
|
|
30
|
+
hashFile,
|
|
32
31
|
normalizeGoarch,
|
|
33
32
|
platformToGoos,
|
|
34
33
|
type NodeBinaryResolver,
|
|
@@ -70,8 +69,8 @@ const FILE_CHUNK_BYTES = 1024 * 1024;
|
|
|
70
69
|
* and never throws, and the command result it would have posted afterwards
|
|
71
70
|
* is never posted. The dispatch timeout is the only thing that ends that
|
|
72
71
|
* wait — and since turns serialize per chat, the whole chat's message loop
|
|
73
|
-
* queues behind it. A flat
|
|
74
|
-
* over one dropped TCP connection.
|
|
72
|
+
* queues behind it. A flat budget sized for the largest transfer would
|
|
73
|
+
* take a chat offline that long over one dropped TCP connection.
|
|
75
74
|
*
|
|
76
75
|
* So the budget is sized to the payload instead: a fixed grace for the
|
|
77
76
|
* handshake and device-side setup, plus the body at a deliberately
|
|
@@ -83,9 +82,8 @@ const STREAM_TRANSFER_FLOOR_BYTES_PER_SEC = 32 * 1024;
|
|
|
83
82
|
/**
|
|
84
83
|
* Ceiling, so a payload large enough to out-scale the formula still can't
|
|
85
84
|
* block a chat indefinitely. Deliberately well above what a big transfer
|
|
86
|
-
* over a slow link needs — the common wedge is a SMALL transfer
|
|
87
|
-
*
|
|
88
|
-
* about a minute.
|
|
85
|
+
* over a slow link needs — the common wedge is a SMALL transfer, and its
|
|
86
|
+
* budget runs out in about a minute.
|
|
89
87
|
*/
|
|
90
88
|
const STREAM_TRANSFER_MAX_MS = 30 * 60 * 1000;
|
|
91
89
|
|
|
@@ -745,19 +743,3 @@ function transferRate(bytes: number, startedAtMs: number): string {
|
|
|
745
743
|
const seconds = Math.max((Date.now() - startedAtMs) / 1000, 0.001);
|
|
746
744
|
return `${formatBytes(bytes / seconds)}/s over ${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)}s`;
|
|
747
745
|
}
|
|
748
|
-
|
|
749
|
-
/** Stream a file through SHA-256 without loading it into memory (APKs are big
|
|
750
|
-
* and Buffer has a hard ceiling). Returns the hex digest and byte size. */
|
|
751
|
-
async function hashFile(
|
|
752
|
-
path: string,
|
|
753
|
-
): Promise<{ sha256: string; size: number }> {
|
|
754
|
-
const { size } = await stat(path);
|
|
755
|
-
const hash = createHash("sha256");
|
|
756
|
-
await new Promise<void>((resolve, reject) => {
|
|
757
|
-
createReadStream(path)
|
|
758
|
-
.on("data", (chunk) => hash.update(chunk))
|
|
759
|
-
.on("end", () => resolve())
|
|
760
|
-
.on("error", reject);
|
|
761
|
-
});
|
|
762
|
-
return { sha256: hash.digest("hex"), size };
|
|
763
|
-
}
|
|
@@ -55,6 +55,8 @@ type Transfer = {
|
|
|
55
55
|
resolve: (bytes: number) => void;
|
|
56
56
|
reject: (err: Error) => void;
|
|
57
57
|
};
|
|
58
|
+
/** pull only — tears down the upload once its HTTP leg is streaming. */
|
|
59
|
+
abort?: AbortController;
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
export class TransferStore {
|
|
@@ -104,12 +106,19 @@ export class TransferStore {
|
|
|
104
106
|
return { token };
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Drop a token — the arrangement failed, or the daemon gave up waiting.
|
|
111
|
+
* An upload already streaming is aborted too: otherwise a peer that
|
|
112
|
+
* stalls mid-body holds the request (and its temp file) open until the
|
|
113
|
+
* bridge's whole-request deadline, and one that finishes late renames a
|
|
114
|
+
* file into place after the caller was told the pull failed.
|
|
115
|
+
*/
|
|
108
116
|
cancel(token: string): void {
|
|
109
117
|
const t = this.transfers.get(token);
|
|
110
118
|
if (t?.uploadDone && !t.consumed) {
|
|
111
119
|
t.uploadDone.reject(new Error("transfer cancelled"));
|
|
112
120
|
}
|
|
121
|
+
t?.abort?.abort(new Error("transfer cancelled"));
|
|
113
122
|
this.transfers.delete(token);
|
|
114
123
|
}
|
|
115
124
|
|
|
@@ -129,11 +138,16 @@ export class TransferStore {
|
|
|
129
138
|
if (!t)
|
|
130
139
|
return { ok: false, error: "Unknown or already-used transfer token." };
|
|
131
140
|
const tmp = `${t.localPath}.part-${randomBytes(4).toString("hex")}`;
|
|
141
|
+
const abort = new AbortController();
|
|
142
|
+
t.abort = abort;
|
|
132
143
|
try {
|
|
133
144
|
await mkdir(dirname(t.localPath), { recursive: true });
|
|
134
145
|
let bytes = 0;
|
|
135
146
|
body.on("data", (d: Buffer) => (bytes += d.length));
|
|
136
|
-
await pipeline(body, createWriteStream(tmp, { mode: 0o600 })
|
|
147
|
+
await pipeline(body, createWriteStream(tmp, { mode: 0o600 }), {
|
|
148
|
+
signal: abort.signal,
|
|
149
|
+
});
|
|
150
|
+
abort.signal.throwIfAborted();
|
|
137
151
|
await rename(tmp, t.localPath);
|
|
138
152
|
this.transfers.delete(token);
|
|
139
153
|
t.uploadDone?.resolve(bytes);
|
|
@@ -162,7 +176,6 @@ export class TransferStore {
|
|
|
162
176
|
if (!s.isFile()) throw new Error("not a file");
|
|
163
177
|
return { path: t.localPath, size: s.size };
|
|
164
178
|
} catch {
|
|
165
|
-
this.transfers.delete(token);
|
|
166
179
|
return null;
|
|
167
180
|
} finally {
|
|
168
181
|
// Single-use either way; the device retries by re-arranging.
|
|
@@ -60,10 +60,7 @@
|
|
|
60
60
|
* have their stored override returned verbatim — no way to validate.
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
|
-
import {
|
|
64
|
-
getChatModelForBackend,
|
|
65
|
-
getChatSettings,
|
|
66
|
-
} from "../../storage/chat-settings.js";
|
|
63
|
+
import { getChatModelForBackend } from "../../storage/chat-settings.js";
|
|
67
64
|
import type { Backend } from "../agent-runtime/capabilities.js";
|
|
68
65
|
import {
|
|
69
66
|
isBackendId,
|
|
@@ -82,7 +79,7 @@ import { logWarn } from "../../util/log.js";
|
|
|
82
79
|
* (default)" vs "Model: X (override)" vs "No model selected" without
|
|
83
80
|
* second-guessing the chain.
|
|
84
81
|
*/
|
|
85
|
-
|
|
82
|
+
type ActiveModelSource =
|
|
86
83
|
| "override-valid"
|
|
87
84
|
| "override-invalid-fallback"
|
|
88
85
|
| "backend-canonical"
|
|
@@ -311,25 +308,6 @@ async function safeBackendDefault(backend: Backend): Promise<string | null> {
|
|
|
311
308
|
}
|
|
312
309
|
}
|
|
313
310
|
|
|
314
|
-
/**
|
|
315
|
-
* Convenience: same as `resolveActiveModelForChat` but returns just
|
|
316
|
-
* the model id (or `null`). Use when the source tag isn't needed.
|
|
317
|
-
*/
|
|
318
|
-
export async function getActiveModelForChat(
|
|
319
|
-
chatId: string,
|
|
320
|
-
backend: Backend | null,
|
|
321
|
-
backendId: string | null,
|
|
322
|
-
config: TalonConfig,
|
|
323
|
-
): Promise<string | null> {
|
|
324
|
-
const { model } = await resolveActiveModelForChat(
|
|
325
|
-
chatId,
|
|
326
|
-
backend,
|
|
327
|
-
backendId,
|
|
328
|
-
config,
|
|
329
|
-
);
|
|
330
|
-
return model;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
311
|
// ── ref enrichment ──────────────────────────────────────────────────────────
|
|
334
312
|
|
|
335
313
|
/**
|
|
@@ -412,34 +390,3 @@ function mapCacheSupport(backend: Backend | null): CacheSupport {
|
|
|
412
390
|
return "none";
|
|
413
391
|
}
|
|
414
392
|
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Human-readable label for the source — used in toast wording so the
|
|
418
|
-
* user sees *why* the resolved model is what it is. Keeps the wording
|
|
419
|
-
* centralised so the UI stays consistent across frontends.
|
|
420
|
-
*/
|
|
421
|
-
export function describeActiveModelSource(source: ActiveModelSource): string {
|
|
422
|
-
switch (source) {
|
|
423
|
-
case "override-valid":
|
|
424
|
-
return "your pick";
|
|
425
|
-
case "override-invalid-fallback":
|
|
426
|
-
return "previous pick invalid — falling back to default";
|
|
427
|
-
case "backend-canonical":
|
|
428
|
-
return "backend default";
|
|
429
|
-
case "config-backend-defaults":
|
|
430
|
-
return "configured default";
|
|
431
|
-
case "config-legacy-global":
|
|
432
|
-
return "global default";
|
|
433
|
-
case "none":
|
|
434
|
-
return "no model selected";
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/** Read-only helper to surface the `modelByBackend` map for a chat
|
|
439
|
-
* (e.g. for /status rendering). Returns a shallow copy. */
|
|
440
|
-
export function getModelByBackendSnapshot(
|
|
441
|
-
chatId: string,
|
|
442
|
-
): Record<string, string> {
|
|
443
|
-
const settings = getChatSettings(chatId);
|
|
444
|
-
return { ...settings.modelByBackend };
|
|
445
|
-
}
|
|
@@ -14,6 +14,22 @@
|
|
|
14
14
|
import { logError } from "../../util/log.js";
|
|
15
15
|
import type { ActionResult } from "../types.js";
|
|
16
16
|
import { registry } from "./registry.js";
|
|
17
|
+
import type { TalonPlugin } from "./types.js";
|
|
18
|
+
|
|
19
|
+
/** Run a plugin that has `handleAction`; a throw becomes an error result. */
|
|
20
|
+
async function runAction(
|
|
21
|
+
plugin: TalonPlugin,
|
|
22
|
+
body: Record<string, unknown>,
|
|
23
|
+
chatId: string,
|
|
24
|
+
): Promise<ActionResult | null> {
|
|
25
|
+
try {
|
|
26
|
+
return await plugin.handleAction!(body, chatId);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
29
|
+
logError("plugin", `${plugin.name} action error: ${detail}`);
|
|
30
|
+
return { ok: false, error: `Plugin ${plugin.name}: ${detail}` };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
17
33
|
|
|
18
34
|
export async function handlePluginAction(
|
|
19
35
|
body: Record<string, unknown>,
|
|
@@ -21,19 +37,8 @@ export async function handlePluginAction(
|
|
|
21
37
|
): Promise<ActionResult | null> {
|
|
22
38
|
for (const { plugin } of registry.all) {
|
|
23
39
|
if (!plugin.handleAction) continue;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (result) return result;
|
|
27
|
-
} catch (err) {
|
|
28
|
-
logError(
|
|
29
|
-
"plugin",
|
|
30
|
-
`${plugin.name} action error: ${err instanceof Error ? err.message : err}`,
|
|
31
|
-
);
|
|
32
|
-
return {
|
|
33
|
-
ok: false,
|
|
34
|
-
error: `Plugin ${plugin.name}: ${err instanceof Error ? err.message : err}`,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
40
|
+
const result = await runAction(plugin, body, chatId);
|
|
41
|
+
if (result) return result;
|
|
37
42
|
}
|
|
38
43
|
return null;
|
|
39
44
|
}
|
|
@@ -56,11 +61,5 @@ export async function handlePluginActionIn(
|
|
|
56
61
|
): Promise<ActionResult | null> {
|
|
57
62
|
const entry = registry.all.find(({ plugin }) => plugin.name === name);
|
|
58
63
|
if (!entry?.plugin.handleAction) return null;
|
|
59
|
-
|
|
60
|
-
return await entry.plugin.handleAction(body, chatId);
|
|
61
|
-
} catch (err) {
|
|
62
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
63
|
-
logError("plugin", `${name} action error: ${detail}`);
|
|
64
|
-
return { ok: false, error: `Plugin ${name}: ${detail}` };
|
|
65
|
-
}
|
|
64
|
+
return runAction(entry.plugin, body, chatId);
|
|
66
65
|
}
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { log, logError, logWarn } from "../../util/log.js";
|
|
7
|
+
import { raiseAlert, resolveAlert } from "../frontend-runtime/alerts.js";
|
|
8
|
+
import { faultText } from "../engine/fault-text.js";
|
|
7
9
|
import type { TalonConfig } from "../config/index.js";
|
|
10
|
+
import type { TalonPlugin } from "./types.js";
|
|
8
11
|
import { registry, reloadState } from "./registry.js";
|
|
9
12
|
import type { ProvisionOutcome } from "./provision.js";
|
|
10
13
|
import { NATIVE_RUNTIMES, type NativePluginId } from "./native-runtimes.js";
|
|
@@ -37,6 +40,7 @@ function reportProvision(
|
|
|
37
40
|
if (outcome.status === "failed" && outcome.error) {
|
|
38
41
|
logError(pluginName, `provision failed: ${outcome.error}`);
|
|
39
42
|
}
|
|
43
|
+
alertOnProvision(pluginName, outcome.status, outcome.error);
|
|
40
44
|
const background = outcome.background;
|
|
41
45
|
if (background) {
|
|
42
46
|
// Tracked so the post-update report waits for it to settle (its
|
|
@@ -56,6 +60,27 @@ function reportProvision(
|
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
/**
|
|
64
|
+
* A native runtime that could not be provisioned leaves its plugin down
|
|
65
|
+
* until a human looks (disk, network, a broken toolchain) — alert on the
|
|
66
|
+
* failed pass, clear once a later pass leaves it usable.
|
|
67
|
+
*/
|
|
68
|
+
function alertOnProvision(
|
|
69
|
+
pluginName: NativePluginId,
|
|
70
|
+
status: ProvisionOutcome["status"] | "threw",
|
|
71
|
+
error: unknown,
|
|
72
|
+
): void {
|
|
73
|
+
const key = `provision.${pluginName}`;
|
|
74
|
+
if (status === "failed" || status === "threw") {
|
|
75
|
+
raiseAlert(
|
|
76
|
+
key,
|
|
77
|
+
`Installing the ${pluginName} runtime failed: ${faultText(error ?? "no detail")}. The ${pluginName} plugin will not work until it is fixed (see \`talon doctor\`).`,
|
|
78
|
+
);
|
|
79
|
+
} else if (status === "ready" || status === "degraded") {
|
|
80
|
+
resolveAlert(key, `The ${pluginName} runtime is installed and usable.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
59
84
|
/**
|
|
60
85
|
* Provision every enabled native runtime (see native-runtimes.ts) and
|
|
61
86
|
* return the outcomes so plugin construction can read what it got
|
|
@@ -77,136 +102,109 @@ async function provisionNativeRuntimes(
|
|
|
77
102
|
runtime.id,
|
|
78
103
|
`provision: ${err instanceof Error ? err.message : err}`,
|
|
79
104
|
);
|
|
105
|
+
alertOnProvision(runtime.id, "threw", err);
|
|
80
106
|
}
|
|
81
107
|
}
|
|
82
108
|
return outcomes;
|
|
83
109
|
}
|
|
84
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Register one built-in plugin from its config section and run its init.
|
|
113
|
+
* A plugin that fails to build or init is logged, never fatal.
|
|
114
|
+
*/
|
|
115
|
+
async function loadBuiltin(
|
|
116
|
+
label: string,
|
|
117
|
+
section: object,
|
|
118
|
+
initTimeoutMs: number,
|
|
119
|
+
build: () => Promise<TalonPlugin>,
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
try {
|
|
122
|
+
const loaded = registerPlugin(
|
|
123
|
+
await build(),
|
|
124
|
+
section as Record<string, unknown>,
|
|
125
|
+
);
|
|
126
|
+
if (loaded) {
|
|
127
|
+
await initPluginWithTimeout(
|
|
128
|
+
loaded.plugin,
|
|
129
|
+
loaded.config,
|
|
130
|
+
initTimeoutMs,
|
|
131
|
+
`${label} init`,
|
|
132
|
+
`${label} init`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logError(
|
|
137
|
+
"plugin",
|
|
138
|
+
`${label} init: ${err instanceof Error ? err.message : err}`,
|
|
139
|
+
);
|
|
140
|
+
raiseAlert(
|
|
141
|
+
`plugin.${label.toLowerCase()}`,
|
|
142
|
+
`Built-in plugin ${label} failed to load: ${faultText(err)}. Its tools are unavailable until the next reload or restart.`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
85
147
|
/**
|
|
86
148
|
* Load built-in plugins (GitHub, MemPalace, mem0, Playwright) based on config flags.
|
|
87
|
-
* Shared by both bootstrap and hot-reload
|
|
149
|
+
* Shared by both bootstrap and hot-reload.
|
|
88
150
|
*/
|
|
89
151
|
export async function loadBuiltinPlugins(config: TalonConfig): Promise<void> {
|
|
90
152
|
const provisioned = await provisionNativeRuntimes(config);
|
|
91
153
|
|
|
92
154
|
const github = config.github;
|
|
93
155
|
if (github?.enabled) {
|
|
94
|
-
|
|
156
|
+
await loadBuiltin("GitHub", github, 15_000, async () => {
|
|
95
157
|
const { createGitHubPlugin } =
|
|
96
158
|
await import("../../plugins/github/index.js");
|
|
97
|
-
|
|
159
|
+
return createGitHubPlugin({
|
|
98
160
|
token: github.token,
|
|
99
161
|
imageTag: github.imageTag,
|
|
100
162
|
});
|
|
101
|
-
|
|
102
|
-
const loaded = registerPlugin(gh, ghConfig);
|
|
103
|
-
if (loaded) {
|
|
104
|
-
await initPluginWithTimeout(
|
|
105
|
-
loaded.plugin,
|
|
106
|
-
loaded.config,
|
|
107
|
-
15_000,
|
|
108
|
-
"GitHub init",
|
|
109
|
-
"GitHub init",
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
logError(
|
|
114
|
-
"plugin",
|
|
115
|
-
`GitHub init: ${err instanceof Error ? err.message : err}`,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
163
|
+
});
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
const mempalace = config.mempalace;
|
|
121
167
|
if (mempalace?.enabled) {
|
|
122
|
-
|
|
168
|
+
await loadBuiltin("MemPalace", mempalace, 30_000, async () => {
|
|
123
169
|
const { createMempalacePlugin } =
|
|
124
170
|
await import("../../plugins/mempalace/index.js");
|
|
125
171
|
const { resolveMempalacePaths } =
|
|
126
172
|
await import("../../plugins/mempalace/provision.js");
|
|
127
173
|
const { pythonPath, palacePath } = resolveMempalacePaths(mempalace);
|
|
128
|
-
|
|
174
|
+
return createMempalacePlugin({
|
|
129
175
|
pythonPath,
|
|
130
176
|
palacePath,
|
|
131
177
|
entityLanguages: mempalace.entityLanguages,
|
|
132
178
|
verbose: mempalace.verbose,
|
|
133
179
|
installedVersion: provisioned.get("mempalace")?.version,
|
|
134
180
|
});
|
|
135
|
-
|
|
136
|
-
const loaded = registerPlugin(mp, mpConfig);
|
|
137
|
-
if (loaded) {
|
|
138
|
-
await initPluginWithTimeout(
|
|
139
|
-
loaded.plugin,
|
|
140
|
-
loaded.config,
|
|
141
|
-
30_000,
|
|
142
|
-
"MemPalace init",
|
|
143
|
-
"MemPalace init",
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
} catch (err) {
|
|
147
|
-
logError(
|
|
148
|
-
"plugin",
|
|
149
|
-
`MemPalace init: ${err instanceof Error ? err.message : err}`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
181
|
+
});
|
|
152
182
|
}
|
|
153
183
|
|
|
154
184
|
const mem0 = config.mem0;
|
|
155
185
|
if (mem0?.enabled) {
|
|
156
|
-
|
|
186
|
+
await loadBuiltin("mem0", mem0, 15_000, async () => {
|
|
157
187
|
const { createMem0Plugin } = await import("../../plugins/mem0/index.js");
|
|
158
|
-
|
|
188
|
+
return createMem0Plugin({
|
|
159
189
|
apiKey: mem0.apiKey,
|
|
160
190
|
host: mem0.host,
|
|
161
191
|
userId: mem0.userId,
|
|
162
192
|
});
|
|
163
|
-
|
|
164
|
-
const loaded = registerPlugin(m0, m0Config);
|
|
165
|
-
if (loaded) {
|
|
166
|
-
await initPluginWithTimeout(
|
|
167
|
-
loaded.plugin,
|
|
168
|
-
loaded.config,
|
|
169
|
-
15_000,
|
|
170
|
-
"mem0 init",
|
|
171
|
-
"mem0 init",
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
} catch (err) {
|
|
175
|
-
logError(
|
|
176
|
-
"plugin",
|
|
177
|
-
`mem0 init: ${err instanceof Error ? err.message : err}`,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
193
|
+
});
|
|
180
194
|
}
|
|
181
195
|
|
|
182
196
|
const playwright = config.playwright;
|
|
183
197
|
if (playwright?.enabled) {
|
|
184
|
-
|
|
198
|
+
await loadBuiltin("Playwright", playwright, 15_000, async () => {
|
|
185
199
|
const { createPlaywrightPlugin } =
|
|
186
200
|
await import("../../plugins/playwright/index.js");
|
|
187
|
-
|
|
188
|
-
const pw = createPlaywrightPlugin({
|
|
201
|
+
return createPlaywrightPlugin({
|
|
189
202
|
browser: playwright.browser,
|
|
190
203
|
headless: playwright.headless,
|
|
191
204
|
endpoint: playwright.endpoint,
|
|
192
205
|
endpointFile: playwright.endpointFile,
|
|
193
206
|
});
|
|
194
|
-
|
|
195
|
-
if (loaded) {
|
|
196
|
-
await initPluginWithTimeout(
|
|
197
|
-
loaded.plugin,
|
|
198
|
-
loaded.config,
|
|
199
|
-
15_000,
|
|
200
|
-
"Playwright init",
|
|
201
|
-
"Playwright init",
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
} catch (err) {
|
|
205
|
-
logError(
|
|
206
|
-
"plugin",
|
|
207
|
-
`Playwright init: ${err instanceof Error ? err.message : err}`,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
207
|
+
});
|
|
210
208
|
}
|
|
211
209
|
}
|
|
212
210
|
|
|
@@ -217,9 +215,9 @@ export async function loadBuiltinPlugins(config: TalonConfig): Promise<void> {
|
|
|
217
215
|
*
|
|
218
216
|
* Throws on config parse/validation failure so the gateway can report an error.
|
|
219
217
|
*
|
|
220
|
-
* Does NOT restart the main process,
|
|
221
|
-
* conversations continue uninterrupted — new MCP servers spawn
|
|
222
|
-
* on the next tool call.
|
|
218
|
+
* Does NOT restart the main process, backend session, or bot connection.
|
|
219
|
+
* Active conversations continue uninterrupted — new MCP servers spawn
|
|
220
|
+
* automatically on the next tool call.
|
|
223
221
|
*/
|
|
224
222
|
export async function reloadPlugins(
|
|
225
223
|
activeFrontends?: string[],
|
|
@@ -229,30 +227,22 @@ export async function reloadPlugins(
|
|
|
229
227
|
const { loadConfig, getFrontends } = await import("../config/index.js");
|
|
230
228
|
const config = loadConfig();
|
|
231
229
|
|
|
232
|
-
// Derive frontends from config if not explicitly provided
|
|
233
230
|
const frontends = activeFrontends ?? getFrontends(config);
|
|
234
231
|
|
|
235
|
-
//
|
|
236
|
-
// load — the Claude SDK will see a changed env and spawn fresh subprocesses,
|
|
237
|
-
// picking up any source-file changes without a full Talon restart.
|
|
232
|
+
// New cache-bust key: the re-import below must load edited plugin source.
|
|
238
233
|
reloadState.lastReloadAt = new Date().toISOString();
|
|
239
234
|
|
|
240
|
-
// Config is valid — safe to destroy current plugins now
|
|
241
235
|
log("plugin", "Hot-reload: destroying current plugins...");
|
|
242
236
|
await registry.destroyAndClear();
|
|
243
237
|
|
|
244
|
-
// Re-load external plugins
|
|
245
238
|
if (config.plugins.length > 0) {
|
|
246
239
|
await loadPlugins(config.plugins, frontends);
|
|
247
240
|
}
|
|
248
|
-
|
|
249
|
-
// Re-load built-in plugins using shared helper
|
|
250
241
|
await loadBuiltinPlugins(config);
|
|
251
242
|
|
|
252
243
|
// Retire the hub's MCP children: the next tool call (any chat) spawns
|
|
253
244
|
// fresh processes from the reloaded registry, while in-flight calls
|
|
254
|
-
// drain on the old ones
|
|
255
|
-
// the TALON_RELOAD_AT env bump; the hub owns lifecycles directly).
|
|
245
|
+
// drain on the old ones.
|
|
256
246
|
const { reloadHubChildren } = await import("../mcp-hub/index.js");
|
|
257
247
|
reloadHubChildren();
|
|
258
248
|
|
package/src/core/plugin/index.ts
CHANGED
|
@@ -7,10 +7,7 @@
|
|
|
7
7
|
* - `loader` — load/validate/register plugins, init hooks, query helpers
|
|
8
8
|
* - `builtins` — built-in plugin loading + hot-reload
|
|
9
9
|
* - `actions` — gateway-action routing through plugins
|
|
10
|
-
* - `mcp` — MCP server
|
|
11
|
-
*
|
|
12
|
-
* Re-exports the same public surface the old single-file module exposed,
|
|
13
|
-
* including the `_deps` test seam.
|
|
10
|
+
* - `mcp` — MCP server spec assembly for the hub
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
export { _deps } from "./registry.js";
|