talentos 1.1.1 → 1.1.2
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/dist/agent/adapters.js +28 -21
- package/dist/agent/gateway.js +1 -1
- package/dist/agent/serve.js +4 -2
- package/dist/agent-config.js +4 -1
- package/dist/agent-http.js +9 -3
- package/dist/agent-service.js +23 -13
- package/dist/commands/agent-serve.js +65 -14
- package/dist/commands/agent.js +25 -5
- package/dist/index.js +2 -1
- package/dist/provider-binary.js +47 -0
- package/package.json +1 -1
package/dist/agent/adapters.js
CHANGED
|
@@ -8,6 +8,7 @@ import { promisify } from "node:util";
|
|
|
8
8
|
import { writePrivateFile } from "../agent-config.js";
|
|
9
9
|
import { redactAgentOutput, } from "../agent-link.js";
|
|
10
10
|
import { gatewayLauncherPath } from "../gateway-install.js";
|
|
11
|
+
import { providerPathEnv, resolveProviderBinary } from "../provider-binary.js";
|
|
11
12
|
const CHILD_ENV = [
|
|
12
13
|
"HOME",
|
|
13
14
|
"PATH",
|
|
@@ -42,24 +43,28 @@ const ROOM_TOOLS = [
|
|
|
42
43
|
"whoami",
|
|
43
44
|
];
|
|
44
45
|
const CLAUDE_TOOLS = ROOM_TOOLS.map((tool) => `mcp__talentos__${tool}`).join(",");
|
|
45
|
-
export function childEnvironment(source = process.env) {
|
|
46
|
-
return
|
|
47
|
-
key
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
export function childEnvironment(source = process.env, binary) {
|
|
47
|
+
return {
|
|
48
|
+
...Object.fromEntries(CHILD_ENV.filter((key) => source[key] !== undefined).map((key) => [
|
|
49
|
+
key,
|
|
50
|
+
source[key],
|
|
51
|
+
])),
|
|
52
|
+
PATH: providerPathEnv(binary, source.HOME),
|
|
53
|
+
};
|
|
50
54
|
}
|
|
51
|
-
export async function verifyClaudeCode() {
|
|
55
|
+
export async function verifyClaudeCode(configured) {
|
|
56
|
+
const binary = resolveProviderBinary("claude_code", configured);
|
|
52
57
|
const execute = promisify(execFile);
|
|
53
58
|
const options = {
|
|
54
|
-
env: childEnvironment(),
|
|
59
|
+
env: childEnvironment(process.env, binary),
|
|
55
60
|
timeout: 5000,
|
|
56
61
|
maxBuffer: 256_000,
|
|
57
62
|
};
|
|
58
63
|
let version = "unknown";
|
|
59
64
|
try {
|
|
60
|
-
const detected = await execute(
|
|
65
|
+
const detected = await execute(binary, ["--version"], options);
|
|
61
66
|
version = CLAUDE_VERSION.exec(detected.stdout.trim())?.[0] ?? "unknown";
|
|
62
|
-
const help = await execute(
|
|
67
|
+
const help = await execute(binary, ["--help"], options);
|
|
63
68
|
if (version !== "unknown" && DONT_ASK.test(help.stdout)) {
|
|
64
69
|
return;
|
|
65
70
|
}
|
|
@@ -69,22 +74,23 @@ export async function verifyClaudeCode() {
|
|
|
69
74
|
}
|
|
70
75
|
throw new Error(`Update Claude Code (${version}) to a version that supports --permission-mode dontAsk.`);
|
|
71
76
|
}
|
|
72
|
-
export async function verifyCodex() {
|
|
77
|
+
export async function verifyCodex(configured) {
|
|
78
|
+
const binary = resolveProviderBinary("codex", configured);
|
|
73
79
|
const execute = promisify(execFile);
|
|
74
80
|
const options = {
|
|
75
|
-
env: childEnvironment(),
|
|
81
|
+
env: childEnvironment(process.env, binary),
|
|
76
82
|
timeout: 5000,
|
|
77
83
|
maxBuffer: 256_000,
|
|
78
84
|
};
|
|
79
85
|
let version = "unknown";
|
|
80
86
|
try {
|
|
81
|
-
const detected = await execute(
|
|
87
|
+
const detected = await execute(binary, ["--version"], options);
|
|
82
88
|
version = CODEX_VERSION.exec(detected.stdout)?.[0] ?? "unknown";
|
|
83
89
|
for (const args of [
|
|
84
90
|
["exec", "--help"],
|
|
85
91
|
["exec", "resume", "--help"],
|
|
86
92
|
]) {
|
|
87
|
-
const help = await execute(
|
|
93
|
+
const help = await execute(binary, args, options);
|
|
88
94
|
if (![
|
|
89
95
|
"--json",
|
|
90
96
|
"--skip-git-repo-check",
|
|
@@ -95,7 +101,7 @@ export async function verifyCodex() {
|
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
if (version !== "unknown") {
|
|
98
|
-
await codexRoomCatalog();
|
|
104
|
+
await codexRoomCatalog(binary);
|
|
99
105
|
return;
|
|
100
106
|
}
|
|
101
107
|
}
|
|
@@ -104,10 +110,10 @@ export async function verifyCodex() {
|
|
|
104
110
|
}
|
|
105
111
|
throw new Error(`Update Codex (${version}) to a version supporting isolated room turns, exec/resume flags and a local model catalog.`);
|
|
106
112
|
}
|
|
107
|
-
async function codexRoomCatalog() {
|
|
113
|
+
async function codexRoomCatalog(binary) {
|
|
108
114
|
try {
|
|
109
|
-
const { stdout } = await promisify(execFile)(
|
|
110
|
-
env: childEnvironment(),
|
|
115
|
+
const { stdout } = await promisify(execFile)(binary, ["debug", "models", "--bundled"], {
|
|
116
|
+
env: childEnvironment(process.env, binary),
|
|
111
117
|
timeout: 5000,
|
|
112
118
|
maxBuffer: 4_000_000,
|
|
113
119
|
});
|
|
@@ -188,6 +194,7 @@ export async function runProvider(input) {
|
|
|
188
194
|
const { config, job, workDir, sessionId, signal } = input;
|
|
189
195
|
const prompt = JSON.stringify(job.packet), mcpPath = join(dirname(workDir), ".talentos-mcp.json");
|
|
190
196
|
const catalogPath = join(dirname(workDir), ".talentos-codex-models.json");
|
|
197
|
+
const binary = resolveProviderBinary(config.provider, config.providerPath);
|
|
191
198
|
let command, args;
|
|
192
199
|
if (config.provider === "claude_code") {
|
|
193
200
|
await writePrivateFile(mcpPath, JSON.stringify({
|
|
@@ -199,7 +206,7 @@ export async function runProvider(input) {
|
|
|
199
206
|
},
|
|
200
207
|
},
|
|
201
208
|
}));
|
|
202
|
-
command =
|
|
209
|
+
command = binary;
|
|
203
210
|
args = [
|
|
204
211
|
"-p",
|
|
205
212
|
prompt,
|
|
@@ -225,10 +232,10 @@ export async function runProvider(input) {
|
|
|
225
232
|
}
|
|
226
233
|
}
|
|
227
234
|
else {
|
|
228
|
-
command =
|
|
235
|
+
command = binary;
|
|
229
236
|
const launcher = gatewayLauncherPath();
|
|
230
237
|
const useLauncher = process.platform !== "win32" && existsSync(launcher);
|
|
231
|
-
await writePrivateFile(catalogPath, await codexRoomCatalog());
|
|
238
|
+
await writePrivateFile(catalogPath, await codexRoomCatalog(binary));
|
|
232
239
|
// Resume has a different flag surface. Root overrides are inherited by
|
|
233
240
|
// both exec paths; shell_tool=false enforces ADR 0093's default no-Bash.
|
|
234
241
|
args = [
|
|
@@ -275,7 +282,7 @@ export async function runProvider(input) {
|
|
|
275
282
|
}
|
|
276
283
|
const child = spawn(command, args, {
|
|
277
284
|
cwd: workDir,
|
|
278
|
-
env: childEnvironment(),
|
|
285
|
+
env: childEnvironment(process.env, binary),
|
|
279
286
|
stdio: ["ignore", "pipe", "ignore"],
|
|
280
287
|
detached: process.platform !== "win32",
|
|
281
288
|
});
|
package/dist/agent/gateway.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isSecureUrl } from "../agent-link.js";
|
|
|
4
4
|
export class GatewayError extends Error {
|
|
5
5
|
status;
|
|
6
6
|
constructor(status) {
|
|
7
|
-
super(status === 401 || status ===
|
|
7
|
+
super(status === 401 || status === 410
|
|
8
8
|
? "Agent is no longer available. Re-link it from TalentOS."
|
|
9
9
|
: "Agent gateway request failed; retrying.");
|
|
10
10
|
this.status = status;
|
package/dist/agent/serve.js
CHANGED
|
@@ -55,7 +55,7 @@ export async function installGatewayUpdate(directory, minimum, signal) {
|
|
|
55
55
|
signal,
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
export async function serveAgent(config, directory, version, stop) {
|
|
58
|
+
export async function serveAgent(config, directory, version, stop, verify) {
|
|
59
59
|
const release = await acquireLock(directory);
|
|
60
60
|
try {
|
|
61
61
|
const workDir = join(directory, "work"), sessions = new SessionMap(directory), gateway = new AgentGateway(config);
|
|
@@ -72,7 +72,8 @@ export async function serveAgent(config, directory, version, stop) {
|
|
|
72
72
|
let minimum = version, lastJobAt = Date.now(), push = false, dispose;
|
|
73
73
|
let terminalError;
|
|
74
74
|
const failService = (error) => {
|
|
75
|
-
if (error instanceof GatewayError &&
|
|
75
|
+
if (error instanceof GatewayError &&
|
|
76
|
+
(error.status === 401 || error.status === 410)) {
|
|
76
77
|
terminalError = error;
|
|
77
78
|
service.abort();
|
|
78
79
|
active?.cancel.abort();
|
|
@@ -126,6 +127,7 @@ export async function serveAgent(config, directory, version, stop) {
|
|
|
126
127
|
}
|
|
127
128
|
}, 1000);
|
|
128
129
|
try {
|
|
130
|
+
await verify?.();
|
|
129
131
|
await heartbeat();
|
|
130
132
|
try {
|
|
131
133
|
push = true;
|
package/dist/agent-config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { chmod, mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
4
|
import { redactAgentOutput } from "./agent-link.js";
|
|
5
5
|
import { warn } from "./format.js";
|
|
6
6
|
import { homeDir } from "./install.js";
|
|
@@ -95,6 +95,9 @@ function isLinkedAgent(value) {
|
|
|
95
95
|
typeof config.name === "string" &&
|
|
96
96
|
typeof config.linkHash === "string" &&
|
|
97
97
|
typeof config.linkedAt === "number" &&
|
|
98
|
+
(config.providerPath === undefined ||
|
|
99
|
+
(typeof config.providerPath === "string" &&
|
|
100
|
+
isAbsolute(config.providerPath))) &&
|
|
98
101
|
(config.provider === "claude_code" || config.provider === "codex"));
|
|
99
102
|
}
|
|
100
103
|
export function isMissing(error) {
|
package/dist/agent-http.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { childEnvironment } from "./agent/adapters.js";
|
|
2
3
|
import { isSecureUrl, } from "./agent-link.js";
|
|
4
|
+
import { resolveProviderBinary } from "./provider-binary.js";
|
|
3
5
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
4
6
|
const HEARTBEAT_WINDOW_MS = 60_000;
|
|
5
7
|
export class AgentHttpError extends Error {
|
|
6
8
|
status;
|
|
7
9
|
retryAfterMs;
|
|
8
10
|
constructor(operation, response) {
|
|
9
|
-
super(
|
|
11
|
+
super(response.status === 426
|
|
12
|
+
? "Update TalentOS CLI: run npx --yes --prefer-online talentos@latest agent link again with your setup URL."
|
|
13
|
+
: `${operation} (HTTP ${response.status}). Ask for a fresh setup prompt if this link expired.`);
|
|
10
14
|
this.status = response.status;
|
|
11
15
|
const seconds = Number(response.headers.get("Retry-After"));
|
|
12
16
|
this.retryAfterMs = Number.isFinite(seconds)
|
|
@@ -37,11 +41,12 @@ export async function setupApiOrigin(link, override) {
|
|
|
37
41
|
}
|
|
38
42
|
return apiOrigin(apiUrl);
|
|
39
43
|
}
|
|
40
|
-
export function inspectProvider(provider) {
|
|
41
|
-
const binary = provider
|
|
44
|
+
export function inspectProvider(provider, configured) {
|
|
45
|
+
const binary = resolveProviderBinary(provider, configured);
|
|
42
46
|
const version = spawnSync(binary, ["--version"], {
|
|
43
47
|
encoding: "utf8",
|
|
44
48
|
timeout: REQUEST_TIMEOUT_MS,
|
|
49
|
+
env: childEnvironment(process.env, binary),
|
|
45
50
|
});
|
|
46
51
|
if (version.status !== 0 || !version.stdout?.trim()) {
|
|
47
52
|
throw new Error(`Install the official ${binary} CLI and sign in before linking.`);
|
|
@@ -52,6 +57,7 @@ export function inspectProvider(provider) {
|
|
|
52
57
|
const login = spawnSync(binary, args, {
|
|
53
58
|
encoding: "utf8",
|
|
54
59
|
timeout: REQUEST_TIMEOUT_MS,
|
|
60
|
+
env: childEnvironment(process.env, binary),
|
|
55
61
|
});
|
|
56
62
|
if (login.status !== 0) {
|
|
57
63
|
throw new Error(`Sign in through the official ${binary} CLI before linking.`);
|
package/dist/agent-service.js
CHANGED
|
@@ -2,8 +2,9 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { chmod, mkdir, open, rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { agentLogPath, agentWorkDir, writePrivateFile, } from "./agent-config.js";
|
|
5
|
-
import { gatewayLauncherPath
|
|
5
|
+
import { gatewayLauncherPath } from "./gateway-install.js";
|
|
6
6
|
import { homeDir } from "./install.js";
|
|
7
|
+
import { providerPathEnv } from "./provider-binary.js";
|
|
7
8
|
export function runCommand(command, args) {
|
|
8
9
|
return (spawnSync(command, args, { stdio: "ignore", timeout: 15_000 }).status ?? 1);
|
|
9
10
|
}
|
|
@@ -32,9 +33,10 @@ export async function installAgentService(agent, options = {}) {
|
|
|
32
33
|
agent.agentId,
|
|
33
34
|
];
|
|
34
35
|
const workDir = agentWorkDir(agent.agentId, home);
|
|
36
|
+
const pathEnv = providerPathEnv(undefined, home, false);
|
|
35
37
|
const content = platform === "darwin"
|
|
36
|
-
? launchdUnit(name, args, workDir, logPath,
|
|
37
|
-
: systemdUnit(args, workDir, logPath,
|
|
38
|
+
? launchdUnit(name, args, workDir, logPath, pathEnv)
|
|
39
|
+
: systemdUnit(args, workDir, logPath, pathEnv);
|
|
38
40
|
const changed = await writePrivateFile(path, content);
|
|
39
41
|
if (options.reload === false) {
|
|
40
42
|
if (platform === "linux" && changed) {
|
|
@@ -76,26 +78,34 @@ export async function uninstallAgentService(agent, options = {}) {
|
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
80
|
const run = options.run ?? runCommand;
|
|
79
|
-
if (platform === "
|
|
80
|
-
run("launchctl", [
|
|
81
|
-
"bootout",
|
|
82
|
-
`gui/${options.uid ?? process.getuid?.() ?? 0}`,
|
|
83
|
-
path,
|
|
84
|
-
]);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
81
|
+
if (platform === "linux") {
|
|
87
82
|
run("systemctl", [
|
|
88
83
|
"--user",
|
|
89
84
|
"disable",
|
|
90
|
-
"--now",
|
|
91
85
|
`talentos-agent@${agent.agentId}.service`,
|
|
92
86
|
]);
|
|
93
87
|
}
|
|
88
|
+
// Delete before asking the supervisor to stop us: it can send SIGTERM immediately.
|
|
94
89
|
await rm(path, { force: true });
|
|
95
|
-
if (platform === "
|
|
90
|
+
if (platform === "darwin") {
|
|
91
|
+
unloadLaunchAgent(agent, options);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
96
94
|
run("systemctl", ["--user", "daemon-reload"]);
|
|
95
|
+
run("systemctl", [
|
|
96
|
+
"--user",
|
|
97
|
+
"--no-block",
|
|
98
|
+
"stop",
|
|
99
|
+
`talentos-agent@${agent.agentId}.service`,
|
|
100
|
+
]);
|
|
97
101
|
}
|
|
98
102
|
}
|
|
103
|
+
export function unloadLaunchAgent(agent, options = {}) {
|
|
104
|
+
(options.run ?? runCommand)("launchctl", [
|
|
105
|
+
"bootout",
|
|
106
|
+
`gui/${options.uid ?? process.getuid?.() ?? 0}/${serviceName(agent.agentId)}`,
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
99
109
|
function serviceName(agentId) {
|
|
100
110
|
return `so.talentos.agent.${agentId}`;
|
|
101
111
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { verifyClaudeCode, verifyCodex } from "../agent/adapters.js";
|
|
4
|
+
import { AgentGateway, GatewayError } from "../agent/gateway.js";
|
|
4
5
|
import { serveAgent } from "../agent/serve.js";
|
|
5
|
-
import { agentWorkDir, localAgents } from "../agent-config.js";
|
|
6
|
-
import {
|
|
6
|
+
import { agentWorkDir, localAgents, storeAgent, } from "../agent-config.js";
|
|
7
|
+
import { AgentHttpError, fetchAgentStatus } from "../agent-http.js";
|
|
8
|
+
import { installAgentService, uninstallAgentService, unloadLaunchAgent, } from "../agent-service.js";
|
|
7
9
|
import { installSharedGateway } from "../gateway-install.js";
|
|
8
|
-
|
|
10
|
+
import { ProviderBinaryError, resolveProviderBinary, } from "../provider-binary.js";
|
|
11
|
+
export async function runAgentServe(parsed, options = {}) {
|
|
9
12
|
const id = parsed.options.agent;
|
|
10
|
-
const agents = await localAgents();
|
|
13
|
+
const agents = await localAgents(options.home);
|
|
11
14
|
let config = agents.find((agent) => agent.agentId === id);
|
|
12
15
|
if (!id && agents.length === 1) {
|
|
13
16
|
config = agents[0];
|
|
@@ -16,30 +19,78 @@ export async function runAgentServe(parsed) {
|
|
|
16
19
|
throw new Error("Choose a linked agent with `talentos agent serve --agent <id>`.");
|
|
17
20
|
}
|
|
18
21
|
try {
|
|
19
|
-
if (process.platform !== "win32") {
|
|
20
|
-
await installSharedGateway();
|
|
22
|
+
if ((options.platform ?? process.platform) !== "win32") {
|
|
23
|
+
await installSharedGateway({ home: options.home });
|
|
21
24
|
}
|
|
22
|
-
await installAgentService(config, { reload: false });
|
|
25
|
+
await installAgentService(config, { ...options, reload: false });
|
|
23
26
|
}
|
|
24
27
|
catch {
|
|
25
28
|
console.error("Gateway installation unavailable. Serving the loaded version; retry talentos agent repair when the installation is available.");
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
await verifyClaudeCode();
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
await verifyCodex();
|
|
32
|
-
}
|
|
30
|
+
const agent = config;
|
|
33
31
|
const stop = new AbortController();
|
|
34
32
|
const abort = () => stop.abort();
|
|
35
33
|
process.once("SIGTERM", abort);
|
|
36
34
|
process.once("SIGINT", abort);
|
|
37
35
|
const version = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
38
36
|
try {
|
|
39
|
-
|
|
37
|
+
// Authenticate without publishing presence, including when the binary is gone.
|
|
38
|
+
await fetchAgentStatus(config);
|
|
39
|
+
await serveAgent(config, dirname(agentWorkDir(config.agentId, options.home)), version, stop.signal, () => verifyAgent(agent, options));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if ((error instanceof GatewayError || error instanceof AgentHttpError) &&
|
|
43
|
+
(error.status === 401 || error.status === 410)) {
|
|
44
|
+
await uninstallAgentService(config, options);
|
|
45
|
+
console.error("Agent access ended. Removed this agent's local service; link it again from TalentOS to reconnect.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
40
49
|
}
|
|
41
50
|
finally {
|
|
42
51
|
process.removeListener("SIGTERM", abort);
|
|
43
52
|
process.removeListener("SIGINT", abort);
|
|
44
53
|
}
|
|
45
54
|
}
|
|
55
|
+
async function verifyAgent(config, options) {
|
|
56
|
+
try {
|
|
57
|
+
let binary;
|
|
58
|
+
try {
|
|
59
|
+
binary = resolveProviderBinary(config.provider, config.providerPath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!(error instanceof ProviderBinaryError && config.providerPath)) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
binary = resolveProviderBinary(config.provider);
|
|
66
|
+
}
|
|
67
|
+
config.providerPath = binary;
|
|
68
|
+
await storeAgent(config, options.home);
|
|
69
|
+
if (config.provider === "claude_code") {
|
|
70
|
+
await verifyClaudeCode(binary);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
await verifyCodex(binary);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof ProviderBinaryError) {
|
|
78
|
+
try {
|
|
79
|
+
await new AgentGateway(config).request("/agent-gateway/provider-unavailable");
|
|
80
|
+
}
|
|
81
|
+
catch (reportError) {
|
|
82
|
+
if (reportError instanceof GatewayError &&
|
|
83
|
+
(reportError.status === 401 || reportError.status === 410)) {
|
|
84
|
+
throw reportError;
|
|
85
|
+
}
|
|
86
|
+
// Older servers may not support diagnostics; local shutdown still applies.
|
|
87
|
+
}
|
|
88
|
+
console.error(error.message);
|
|
89
|
+
// launchd cannot exclude exit 78; unload but retain the plist for repair.
|
|
90
|
+
if ((options.platform ?? process.platform) === "darwin") {
|
|
91
|
+
unloadLaunchAgent(config, options);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
package/dist/commands/agent.js
CHANGED
|
@@ -10,6 +10,7 @@ import { emit, emitJson, renderTable, warn } from "../format.js";
|
|
|
10
10
|
import { installSharedGateway } from "../gateway-install.js";
|
|
11
11
|
import { homeDir } from "../install.js";
|
|
12
12
|
import { defaultDeviceName, hashKey, mintKey } from "../login.js";
|
|
13
|
+
import { resolveProviderBinary } from "../provider-binary.js";
|
|
13
14
|
const MAX_PREVIOUS_AGENT_IDS = 100;
|
|
14
15
|
export async function runAgent(parsed, dependencies = {}) {
|
|
15
16
|
switch (parsed.action) {
|
|
@@ -20,7 +21,7 @@ export async function runAgent(parsed, dependencies = {}) {
|
|
|
20
21
|
const { agent, service } = await installLinkedAgent(parsed);
|
|
21
22
|
emit(`Linked ${redactAgentOutput(agent.name)} (${agent.agentId}). Waiting for agent service…`);
|
|
22
23
|
if (!service.started) {
|
|
23
|
-
warn(`Start it in the foreground: npx --yes talentos@
|
|
24
|
+
warn(`Start it in the foreground: npx --yes --prefer-online talentos@latest agent serve --agent ${agent.agentId}`);
|
|
24
25
|
}
|
|
25
26
|
const status = await waitForHeartbeat(agent);
|
|
26
27
|
if (!status) {
|
|
@@ -52,10 +53,26 @@ export async function runAgent(parsed, dependencies = {}) {
|
|
|
52
53
|
home: dependencies.home,
|
|
53
54
|
source: dependencies.gatewaySource,
|
|
54
55
|
});
|
|
56
|
+
let failed = 0;
|
|
55
57
|
for (const agent of agents) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
try {
|
|
59
|
+
agent.providerPath = (dependencies.resolveBinary ?? resolveProviderBinary)(agent.provider);
|
|
60
|
+
(dependencies.probe ?? inspectProvider)(agent.provider, agent.providerPath);
|
|
61
|
+
await storeAgent(agent, dependencies.home);
|
|
62
|
+
const service = await (dependencies.install ??
|
|
63
|
+
((config) => installAgentService(config, { home: dependencies.home })))(agent);
|
|
64
|
+
emit(`Repaired ${redactAgentOutput(agent.name)} (${agent.agentId}).${service.started ? "" : " Start talentos agent serve in the foreground."}`);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
failed++;
|
|
68
|
+
const reason = error instanceof Error
|
|
69
|
+
? error.message
|
|
70
|
+
: "Check the provider installation and login.";
|
|
71
|
+
warn(redactAgentOutput(`Could not repair ${agent.name} (${agent.agentId}): ${reason}`, [agent.apiKey]));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (failed) {
|
|
75
|
+
throw new Error(`${failed} agent${failed === 1 ? "" : "s"} could not be repaired. See the messages above.`);
|
|
59
76
|
}
|
|
60
77
|
return;
|
|
61
78
|
}
|
|
@@ -81,9 +98,11 @@ export async function installLinkedAgent(parsed, dependencies = {}) {
|
|
|
81
98
|
const link = parseAgentLink(parsed.options.url ?? "");
|
|
82
99
|
const home = dependencies.home ?? homeDir();
|
|
83
100
|
const linkHash = hashKey(link.token);
|
|
101
|
+
const providerPath = (dependencies.resolveBinary ?? resolveProviderBinary)(link.provider);
|
|
84
102
|
const savedAgents = await localAgents(home);
|
|
85
103
|
let agent = savedAgents.find((item) => item.linkHash === linkHash && item.provider === link.provider);
|
|
86
104
|
if (agent) {
|
|
105
|
+
agent.providerPath = providerPath;
|
|
87
106
|
await storeAgent(agent, home);
|
|
88
107
|
}
|
|
89
108
|
else {
|
|
@@ -137,7 +156,7 @@ export async function installLinkedAgent(parsed, dependencies = {}) {
|
|
|
137
156
|
previousAgentIds: candidates.map((item) => item.agentId),
|
|
138
157
|
keyHash: hashKey(apiKey),
|
|
139
158
|
provider: link.provider,
|
|
140
|
-
providerVersion: (dependencies.probe ?? inspectProvider)(link.provider),
|
|
159
|
+
providerVersion: (dependencies.probe ?? inspectProvider)(link.provider, providerPath),
|
|
141
160
|
deviceName: parsed.options.name ?? defaultDeviceName(),
|
|
142
161
|
os: process.platform,
|
|
143
162
|
cliVersion: packageVersion(),
|
|
@@ -146,6 +165,7 @@ export async function installLinkedAgent(parsed, dependencies = {}) {
|
|
|
146
165
|
agentId: identity.agentId,
|
|
147
166
|
name: identity.name,
|
|
148
167
|
provider: link.provider,
|
|
168
|
+
providerPath,
|
|
149
169
|
apiKey,
|
|
150
170
|
apiUrl,
|
|
151
171
|
linkHash,
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { redactAgentOutput } from "./agent-link.js";
|
|
4
4
|
import { parseArgv } from "./args.js";
|
|
5
5
|
import { emit } from "./format.js";
|
|
6
|
+
import { ProviderBinaryError } from "./provider-binary.js";
|
|
6
7
|
const USAGE = `talentos — your team's skills, memory and Talent, from any coding agent.
|
|
7
8
|
|
|
8
9
|
Usage: talentos <command> [options]
|
|
@@ -144,5 +145,5 @@ main(process.argv.slice(2)).catch((error) => {
|
|
|
144
145
|
}
|
|
145
146
|
});
|
|
146
147
|
process.stderr.write(`${redactAgentOutput(message, fragments).split("\n")[0]}\n`);
|
|
147
|
-
process.exit(1);
|
|
148
|
+
process.exit(error instanceof ProviderBinaryError ? error.exitCode : 1);
|
|
148
149
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
2
|
+
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { homeDir } from "./install.js";
|
|
4
|
+
export class ProviderBinaryError extends Error {
|
|
5
|
+
exitCode = 78;
|
|
6
|
+
}
|
|
7
|
+
export function resolveProviderBinary(provider, configured) {
|
|
8
|
+
const binary = provider === "claude_code" ? "claude" : "codex";
|
|
9
|
+
const candidates = configured
|
|
10
|
+
? [configured]
|
|
11
|
+
: (process.env.PATH ?? "")
|
|
12
|
+
.split(delimiter)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map((directory) => resolve(directory, process.platform === "win32" ? `${binary}.exe` : binary));
|
|
15
|
+
for (const candidate of candidates) {
|
|
16
|
+
if (!isAbsolute(candidate)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
accessSync(candidate, constants.X_OK);
|
|
21
|
+
if (!statSync(candidate).isFile()) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Preserve stable provider symlinks; realpaths can point into deleted versions.
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
/* Try the next PATH directory. */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const label = provider === "claude_code" ? "Claude Code" : "Codex";
|
|
32
|
+
throw new ProviderBinaryError(`${label} not found at ${configured ?? join(homeDir(), ".local/bin", binary)}; run talentos agent repair`);
|
|
33
|
+
}
|
|
34
|
+
export function providerPathEnv(binary, home = homeDir(), includeRuntimeNode = true) {
|
|
35
|
+
return [
|
|
36
|
+
...new Set([
|
|
37
|
+
...(binary ? [dirname(binary)] : []),
|
|
38
|
+
...(includeRuntimeNode ? [dirname(process.execPath)] : []),
|
|
39
|
+
join(home, ".local/bin"),
|
|
40
|
+
join(home, ".npm-global/bin"),
|
|
41
|
+
"/opt/homebrew/bin",
|
|
42
|
+
"/usr/local/bin",
|
|
43
|
+
"/usr/bin",
|
|
44
|
+
"/bin",
|
|
45
|
+
]),
|
|
46
|
+
].join(delimiter);
|
|
47
|
+
}
|
package/package.json
CHANGED