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.
@@ -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 Object.fromEntries(CHILD_ENV.filter((key) => source[key] !== undefined).map((key) => [
47
- key,
48
- source[key],
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("claude", ["--version"], options);
65
+ const detected = await execute(binary, ["--version"], options);
61
66
  version = CLAUDE_VERSION.exec(detected.stdout.trim())?.[0] ?? "unknown";
62
- const help = await execute("claude", ["--help"], options);
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("codex", ["--version"], options);
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("codex", args, options);
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)("codex", ["debug", "models", "--bundled"], {
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 = "claude";
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 = "codex";
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
  });
@@ -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 === 409
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;
@@ -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 && error.status === 401) {
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;
@@ -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) {
@@ -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(`${operation} (HTTP ${response.status}). Ask for a fresh setup prompt if this link expired.`);
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 === "claude_code" ? "claude" : "codex";
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.`);
@@ -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, servicePathEnv } from "./gateway-install.js";
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, servicePathEnv(home))
37
- : systemdUnit(args, workDir, logPath, servicePathEnv(home));
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 === "darwin") {
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 === "linux") {
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 { installAgentService } from "../agent-service.js";
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
- export async function runAgentServe(parsed) {
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
- if (config.provider === "claude_code") {
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
- await serveAgent(config, dirname(agentWorkDir(config.agentId)), version, stop.signal);
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
+ }
@@ -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@1 agent serve --agent ${agent.agentId}`);
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
- const service = await (dependencies.install ??
57
- ((config) => installAgentService(config, { home: dependencies.home })))(agent);
58
- emit(`Repaired ${redactAgentOutput(agent.name)} (${agent.agentId}).${service.started ? "" : " Start talentos agent serve in the foreground."}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talentos",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "description": "TalentOS CLI: push and pull skills, read and write team memory, and delegate work to your Talent from any coding agent.",
6
6
  "repository": {