talon-agent 3.22.1 → 3.23.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/bin/talon.js +6 -4
- package/package.json +8 -5
- package/src/backend/codex/discovery.ts +8 -1
- package/src/core/daemon/control.ts +10 -4
- package/src/core/engine/gateway.ts +14 -2
- package/src/core/plugin/mcp.ts +10 -4
- package/src/core/tools/bridge.ts +7 -1
- package/src/core/tools/mcp-env.ts +6 -0
- package/src/frontend/native/index.ts +8 -5
- package/src/util/runtime.ts +20 -0
package/bin/talon.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
(process.versions.bun ? Promise.resolve() : import("tsx"))
|
|
3
|
+
.then(() => import("../src/cli.ts"))
|
|
4
|
+
.catch((err) => {
|
|
5
|
+
console.error("Failed to start Talon:", err.message);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talon-agent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.1",
|
|
4
4
|
"description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
|
|
5
5
|
"author": "Dylan Neve",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,10 +55,13 @@
|
|
|
55
55
|
"tsconfig.json"
|
|
56
56
|
],
|
|
57
57
|
"scripts": {
|
|
58
|
-
"start": "
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
58
|
+
"start": "bun src/index.ts",
|
|
59
|
+
"start:node": "tsx src/index.ts",
|
|
60
|
+
"cli": "bun src/cli.ts",
|
|
61
|
+
"cli:node": "tsx src/cli.ts",
|
|
62
|
+
"setup": "bun src/cli.ts setup",
|
|
63
|
+
"dev": "bun --watch src/index.ts",
|
|
64
|
+
"dev:node": "tsx --watch src/index.ts",
|
|
62
65
|
"test": "vitest run",
|
|
63
66
|
"test:ci": "vitest run --reporter=verbose --reporter=json --outputFile=test-results.json",
|
|
64
67
|
"test:functional": "vitest run --reporter=verbose --reporter=json --outputFile=functional-results.json src/__tests__/package.functional.test.ts src/__tests__/tool-functional.test.ts src/__tests__/mcp-launcher.test.ts src/__tests__/mcp-launcher-functional.test.ts src/__tests__/integration/sdk-stub.test.ts src/__tests__/integration/talon-functional.test.ts",
|
|
@@ -90,7 +90,14 @@ interface CodexCacheFile {
|
|
|
90
90
|
|
|
91
91
|
/** Where the Codex CLI writes its model cache. Exported for tests. */
|
|
92
92
|
export function getCodexCachePath(): string {
|
|
93
|
-
|
|
93
|
+
// Honor CODEX_HOME like token-usage.ts and plan-usage.ts do — the CLI
|
|
94
|
+
// itself relocates with it, so a pinned deployment would otherwise
|
|
95
|
+
// read a cache the CLI never writes. (Also what lets tests fake the
|
|
96
|
+
// path under bun, whose os.homedir() ignores $HOME.)
|
|
97
|
+
const home = process.env.CODEX_HOME?.trim();
|
|
98
|
+
return home && home.length > 0
|
|
99
|
+
? join(home, "models_cache.json")
|
|
100
|
+
: join(homedir(), ".codex", "models_cache.json");
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
/** Default soft timeout when callers await an in-flight discovery. */
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { spawn } from "node:child_process";
|
|
16
16
|
import { dirname, resolve } from "node:path";
|
|
17
|
+
import { isBunRuntime } from "../../util/runtime.js";
|
|
17
18
|
import {
|
|
18
19
|
readPidRecord,
|
|
19
20
|
writePidRecord,
|
|
@@ -93,13 +94,18 @@ export async function startDaemon(opts: {
|
|
|
93
94
|
// Compiled binaries: re-invoke `process.execPath` with no extra args.
|
|
94
95
|
// The binary's own entrypoint (src/index.ts, embedded) handles the
|
|
95
96
|
// server startup the same way a tsx run would.
|
|
97
|
+
//
|
|
98
|
+
// Bun source runs need no loader at all — bun executes the TS entry
|
|
99
|
+
// directly, so a bun-launched CLI spawns a bun daemon.
|
|
96
100
|
const spawnCmd = process.execPath;
|
|
97
101
|
const spawnArgs = isBunBinary
|
|
98
102
|
? []
|
|
99
|
-
:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
: isBunRuntime()
|
|
104
|
+
? [resolve(opts.pkgRoot, "src", "index.ts")]
|
|
105
|
+
: [
|
|
106
|
+
resolve(opts.pkgRoot, "node_modules", "tsx", "dist", "cli.mjs"),
|
|
107
|
+
resolve(opts.pkgRoot, "src", "index.ts"),
|
|
108
|
+
];
|
|
103
109
|
const spawnCwd = isBunBinary ? dirname(process.execPath) : opts.pkgRoot;
|
|
104
110
|
|
|
105
111
|
let child;
|
|
@@ -596,16 +596,28 @@ export class Gateway {
|
|
|
596
596
|
resolve();
|
|
597
597
|
return;
|
|
598
598
|
}
|
|
599
|
-
this.server
|
|
599
|
+
const server = this.server;
|
|
600
|
+
const settle = (): void => {
|
|
600
601
|
this.server = null;
|
|
601
602
|
this.port = 0;
|
|
602
603
|
resolve();
|
|
604
|
+
};
|
|
605
|
+
// Bun's closeAllConnections() (≤1.3.x) doesn't sever live SSE
|
|
606
|
+
// streams, so close()'s callback can never fire there and stop()
|
|
607
|
+
// would hang shutdown (and every test teardown). Resolve on a
|
|
608
|
+
// deadline either way: by then no new connections are accepted,
|
|
609
|
+
// and the process this runs in is exiting anyway.
|
|
610
|
+
const deadline = setTimeout(settle, 2_000);
|
|
611
|
+
deadline.unref?.();
|
|
612
|
+
server.close(() => {
|
|
613
|
+
clearTimeout(deadline);
|
|
614
|
+
settle();
|
|
603
615
|
});
|
|
604
616
|
// `close()` only stops NEW connections; MCP hub sessions hold
|
|
605
617
|
// long-lived SSE streams that would keep the close callback from
|
|
606
618
|
// ever firing. Terminate them — everything on this server is
|
|
607
619
|
// localhost request/response or SSE, safe to drop at stop time.
|
|
608
|
-
|
|
620
|
+
server.closeAllConnections();
|
|
609
621
|
});
|
|
610
622
|
}
|
|
611
623
|
}
|
package/src/core/plugin/mcp.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { wrapMcpServer } from "../../util/mcp-launcher.js";
|
|
9
|
+
import { isBunRuntime } from "../../util/runtime.js";
|
|
9
10
|
import { registry, reloadState } from "./registry.js";
|
|
10
11
|
import type { McpServerConfig } from "./types.js";
|
|
11
12
|
|
|
@@ -60,11 +61,16 @@ export function getPluginMcpServers(
|
|
|
60
61
|
env: baseEnv,
|
|
61
62
|
});
|
|
62
63
|
} else if (plugin.mcpServerPath) {
|
|
63
|
-
//
|
|
64
|
+
// TS server entry: bun runs it directly; node needs the tsx loader.
|
|
64
65
|
servers[`${plugin.name}-tools`] = wrapMcpServer({
|
|
65
|
-
command:
|
|
66
|
-
|
|
67
|
-
process.platform === "win32"
|
|
66
|
+
command: isBunRuntime()
|
|
67
|
+
? process.execPath
|
|
68
|
+
: process.platform === "win32"
|
|
69
|
+
? "npx"
|
|
70
|
+
: "node",
|
|
71
|
+
args: isBunRuntime()
|
|
72
|
+
? [plugin.mcpServerPath]
|
|
73
|
+
: process.platform === "win32"
|
|
68
74
|
? ["tsx", plugin.mcpServerPath]
|
|
69
75
|
: ["--import", tsxPath, plugin.mcpServerPath],
|
|
70
76
|
env: baseEnv,
|
package/src/core/tools/bridge.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
9
|
+
import { isBunRuntime } from "../../util/runtime.js";
|
|
9
10
|
import type { BridgeFunction } from "./types.js";
|
|
10
11
|
|
|
11
12
|
/** Default wall-clock budget for a bridge action. */
|
|
@@ -95,8 +96,13 @@ export function createBridge(
|
|
|
95
96
|
json(): Promise<unknown>;
|
|
96
97
|
};
|
|
97
98
|
try {
|
|
99
|
+
// Bun has no 300s header watchdog and aliases the npm undici
|
|
100
|
+
// module to its own internals (so the Agent knobs do nothing
|
|
101
|
+
// there anyway) — its global fetch serves long-haul calls fine,
|
|
102
|
+
// governed by the AbortSignal alone. The undici detour is a
|
|
103
|
+
// Node-only workaround.
|
|
98
104
|
resp =
|
|
99
|
-
timeoutMs > BUILTIN_FETCH_HEADERS_CEILING_MS
|
|
105
|
+
timeoutMs > BUILTIN_FETCH_HEADERS_CEILING_MS && !isBunRuntime()
|
|
100
106
|
? await undiciFetch(`${bridgeUrl}/action`, {
|
|
101
107
|
...init,
|
|
102
108
|
dispatcher: dispatcher(),
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { dirname, resolve } from "node:path";
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
|
+
import { isBunRuntime } from "../../util/runtime.js";
|
|
21
22
|
|
|
22
23
|
// ── Env-var names (shared constants, not stringly-typed call sites) ────────
|
|
23
24
|
|
|
@@ -76,6 +77,11 @@ export function buildTalonMcpEnv(
|
|
|
76
77
|
*/
|
|
77
78
|
export function talonMcpServerCommand(): string[] {
|
|
78
79
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
80
|
+
// Bun executes the TS entry natively — no loader, and use the running
|
|
81
|
+
// bun binary itself so the child matches the daemon's runtime.
|
|
82
|
+
if (isBunRuntime()) {
|
|
83
|
+
return [process.execPath, resolve(here, "mcp-server.ts")];
|
|
84
|
+
}
|
|
79
85
|
const tsxImport = pathToFileURL(
|
|
80
86
|
resolve(here, "../../../node_modules/tsx/dist/esm/index.mjs"),
|
|
81
87
|
).href;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { TalonConfig } from "../../util/config.js";
|
|
19
|
+
import { isBunRuntime } from "../../util/runtime.js";
|
|
19
20
|
import type { ContextManager } from "../../core/types.js";
|
|
20
21
|
import type { Gateway } from "../../core/engine/gateway.js";
|
|
21
22
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
@@ -983,11 +984,13 @@ export function createNativeFrontend(
|
|
|
983
984
|
const cmd = process.execPath;
|
|
984
985
|
const args = isBunBinary
|
|
985
986
|
? ["restart"]
|
|
986
|
-
:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
987
|
+
: isBunRuntime()
|
|
988
|
+
? [resolve(PKG_ROOT, "src", "cli.ts"), "restart"]
|
|
989
|
+
: [
|
|
990
|
+
resolve(PKG_ROOT, "node_modules", "tsx", "dist", "cli.mjs"),
|
|
991
|
+
resolve(PKG_ROOT, "src", "cli.ts"),
|
|
992
|
+
"restart",
|
|
993
|
+
];
|
|
991
994
|
const cwd = isBunBinary ? dirname(process.execPath) : PKG_ROOT;
|
|
992
995
|
const child = spawn(cmd, args, {
|
|
993
996
|
cwd,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection for source runs.
|
|
3
|
+
*
|
|
4
|
+
* Talon source runs work on two runtimes: Node (which needs the tsx
|
|
5
|
+
* CLI/loader to execute `.ts` entrypoints) and Bun (which executes
|
|
6
|
+
* TypeScript natively). Every place that re-invokes a TS entrypoint in
|
|
7
|
+
* a child process — the daemon spawn, the restart handoff, MCP server
|
|
8
|
+
* commands — branches on this instead of hardcoding the tsx recipe, so
|
|
9
|
+
* `bun src/cli.ts start` yields a bun daemon with bun children while
|
|
10
|
+
* node+tsx runs stay byte-identical to before.
|
|
11
|
+
*
|
|
12
|
+
* Bun-COMPILED binaries are a third shape detected separately (argv[1]
|
|
13
|
+
* points into the embedded virtual fs) — see the existing `$bunfs`
|
|
14
|
+
* checks at the spawn sites and in `util/mcp-launcher.ts`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** True when the current process is executing under the Bun runtime. */
|
|
18
|
+
export function isBunRuntime(): boolean {
|
|
19
|
+
return typeof process.versions.bun === "string";
|
|
20
|
+
}
|