@orchagent/cli 0.3.101 → 0.3.102
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/commands/init.js +22 -22
- package/dist/commands/publish.js +89 -2
- package/dist/commands/pull.js +19 -13
- package/dist/commands/run.js +25 -34
- package/dist/commands/templates/cron-job.js +1 -1
- package/dist/commands/templates/github-weekly-summary.js +1 -1
- package/dist/commands/test.js +2 -2
- package/dist/commands/validate.js +9 -0
- package/dist/lib/llm.js +37 -1
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -58,7 +58,7 @@ Reads JSON input from stdin, processes it, and writes JSON output to stdout.
|
|
|
58
58
|
This is the standard orchagent tool protocol.
|
|
59
59
|
|
|
60
60
|
Usage:
|
|
61
|
-
echo '{"input": "hello"}' |
|
|
61
|
+
echo '{"input": "hello"}' | python3 main.py
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
64
|
import json
|
|
@@ -136,7 +136,7 @@ IMPORTANT: Port 8080 is reserved by the platform health server.
|
|
|
136
136
|
Use a different port (default: 3000).
|
|
137
137
|
|
|
138
138
|
Local development:
|
|
139
|
-
|
|
139
|
+
python3 main.py
|
|
140
140
|
"""
|
|
141
141
|
|
|
142
142
|
import json
|
|
@@ -405,7 +405,7 @@ orchagent fan-out orchestrator.
|
|
|
405
405
|
Calls multiple agents in parallel, combines their results.
|
|
406
406
|
|
|
407
407
|
Usage:
|
|
408
|
-
echo '{"task": "analyze this"}' |
|
|
408
|
+
echo '{"task": "analyze this"}' | python3 main.py
|
|
409
409
|
"""
|
|
410
410
|
|
|
411
411
|
import asyncio
|
|
@@ -497,7 +497,7 @@ orchagent pipeline orchestrator.
|
|
|
497
497
|
Calls agents sequentially — each step's output feeds into the next.
|
|
498
498
|
|
|
499
499
|
Usage:
|
|
500
|
-
echo '{"task": "process this data"}' |
|
|
500
|
+
echo '{"task": "process this data"}' | python3 main.py
|
|
501
501
|
"""
|
|
502
502
|
|
|
503
503
|
import asyncio
|
|
@@ -584,7 +584,7 @@ orchagent map-reduce orchestrator.
|
|
|
584
584
|
Splits input into chunks, processes each in parallel (map), then aggregates (reduce).
|
|
585
585
|
|
|
586
586
|
Usage:
|
|
587
|
-
echo '{"items": ["item1", "item2", "item3"]}' |
|
|
587
|
+
echo '{"items": ["item1", "item2", "item3"]}' | python3 main.py
|
|
588
588
|
"""
|
|
589
589
|
|
|
590
590
|
import asyncio
|
|
@@ -803,7 +803,7 @@ At least one platform token is required.
|
|
|
803
803
|
|
|
804
804
|
\`\`\`sh
|
|
805
805
|
pip install -r requirements.txt
|
|
806
|
-
|
|
806
|
+
python3 main.py
|
|
807
807
|
\`\`\`
|
|
808
808
|
|
|
809
809
|
### 5. Deploy
|
|
@@ -848,7 +848,7 @@ cp .env.example .env
|
|
|
848
848
|
# Fill in DISCORD_BOT_TOKEN, ANTHROPIC_API_KEY, DISCORD_CHANNEL_IDS
|
|
849
849
|
|
|
850
850
|
pip install -r requirements.txt
|
|
851
|
-
|
|
851
|
+
python3 main.py
|
|
852
852
|
\`\`\`
|
|
853
853
|
|
|
854
854
|
### 4. Deploy
|
|
@@ -1068,7 +1068,7 @@ Reads JSON input from stdin, processes the task, and writes JSON output to stdou
|
|
|
1068
1068
|
This is a code-runtime agent — you control the logic and can call any LLM provider.
|
|
1069
1069
|
|
|
1070
1070
|
Usage:
|
|
1071
|
-
echo '{"task": "summarize this text"}' |
|
|
1071
|
+
echo '{"task": "summarize this text"}' | python3 main.py
|
|
1072
1072
|
"""
|
|
1073
1073
|
|
|
1074
1074
|
import json
|
|
@@ -1203,7 +1203,7 @@ Reads JSON input from stdin, calls dependency agents via the orchagent SDK,
|
|
|
1203
1203
|
and writes JSON output to stdout.
|
|
1204
1204
|
|
|
1205
1205
|
Usage:
|
|
1206
|
-
echo '{"task": "do something"}' |
|
|
1206
|
+
echo '{"task": "do something"}' | python3 main.py
|
|
1207
1207
|
"""
|
|
1208
1208
|
|
|
1209
1209
|
import asyncio
|
|
@@ -1328,7 +1328,7 @@ Listens for messages in configured channels and responds using the Anthropic API
|
|
|
1328
1328
|
Local development:
|
|
1329
1329
|
1. Copy .env.example to .env and fill in your tokens
|
|
1330
1330
|
2. pip install -r requirements.txt
|
|
1331
|
-
3.
|
|
1331
|
+
3. python3 main.py
|
|
1332
1332
|
"""
|
|
1333
1333
|
|
|
1334
1334
|
import asyncio
|
|
@@ -1686,7 +1686,7 @@ function registerInitCommand(program) {
|
|
|
1686
1686
|
type: 'agent',
|
|
1687
1687
|
description: 'Multi-platform support agent powered by Claude. Connects to Discord, Telegram, and/or Slack.',
|
|
1688
1688
|
run_mode: 'always_on',
|
|
1689
|
-
runtime: { command: '
|
|
1689
|
+
runtime: { command: 'python3 main.py' },
|
|
1690
1690
|
entrypoint: 'main.py',
|
|
1691
1691
|
supported_providers: ['anthropic'],
|
|
1692
1692
|
default_models: { anthropic: 'claude-sonnet-4-5-20250929' },
|
|
@@ -1734,7 +1734,7 @@ function registerInitCommand(program) {
|
|
|
1734
1734
|
process.stdout.write(` ${s}. Edit config.py with your product name and description\n`);
|
|
1735
1735
|
process.stdout.write(` ${s + 1}. Replace knowledge/ files with your own docs\n`);
|
|
1736
1736
|
process.stdout.write(` ${s + 2}. Copy .env.example to .env and add platform tokens\n`);
|
|
1737
|
-
process.stdout.write(` ${s + 3}. Test locally: pip install -r requirements.txt &&
|
|
1737
|
+
process.stdout.write(` ${s + 3}. Test locally: pip install -r requirements.txt && python3 main.py\n`);
|
|
1738
1738
|
process.stdout.write(` ${s + 4}. Deploy: orch publish && orch service deploy\n`);
|
|
1739
1739
|
process.stdout.write(AGENT_BUILDER_HINT);
|
|
1740
1740
|
return;
|
|
@@ -1904,7 +1904,7 @@ function registerInitCommand(program) {
|
|
|
1904
1904
|
type: 'agent',
|
|
1905
1905
|
description: `A ${templateLabel} orchestrator agent`,
|
|
1906
1906
|
run_mode: runMode,
|
|
1907
|
-
runtime: { command: isJavaScript ? 'node main.js' : '
|
|
1907
|
+
runtime: { command: isJavaScript ? 'node main.js' : 'python3 main.py' },
|
|
1908
1908
|
manifest: {
|
|
1909
1909
|
manifest_version: 1,
|
|
1910
1910
|
dependencies,
|
|
@@ -1977,7 +1977,7 @@ function registerInitCommand(program) {
|
|
|
1977
1977
|
type: 'tool',
|
|
1978
1978
|
description: 'A scheduled job that runs on a cron schedule',
|
|
1979
1979
|
run_mode: 'on_demand',
|
|
1980
|
-
runtime: { command: isJavaScript ? 'node main.js' : '
|
|
1980
|
+
runtime: { command: isJavaScript ? 'node main.js' : 'python3 main.py' },
|
|
1981
1981
|
required_secrets: [],
|
|
1982
1982
|
tags: ['scheduled', 'cron'],
|
|
1983
1983
|
};
|
|
@@ -2018,7 +2018,7 @@ function registerInitCommand(program) {
|
|
|
2018
2018
|
process.stdout.write(` 1. cd ${name}\n`);
|
|
2019
2019
|
}
|
|
2020
2020
|
const mainFile = isJavaScript ? 'main.js' : 'main.py';
|
|
2021
|
-
const testCmd = isJavaScript ? 'node main.js' : '
|
|
2021
|
+
const testCmd = isJavaScript ? 'node main.js' : 'python3 main.py';
|
|
2022
2022
|
process.stdout.write(` ${stepNum}. Edit ${mainFile} with your job logic\n`);
|
|
2023
2023
|
process.stdout.write(` ${stepNum + 1}. Test: echo '{}' | ${testCmd}\n`);
|
|
2024
2024
|
process.stdout.write(` ${stepNum + 2}. Publish: orch publish\n`);
|
|
@@ -2040,7 +2040,7 @@ function registerInitCommand(program) {
|
|
|
2040
2040
|
}
|
|
2041
2041
|
}
|
|
2042
2042
|
if (initMode.flavor !== 'code_runtime' && initMode.flavor !== 'orchestrator' && initMode.flavor !== 'discord' && runMode === 'always_on') {
|
|
2043
|
-
throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"
|
|
2043
|
+
throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python3 main.py\" }). Use --type tool or --type agentic for code-runtime agents.");
|
|
2044
2044
|
}
|
|
2045
2045
|
// Create manifest and type-specific files
|
|
2046
2046
|
const manifest = JSON.parse(MANIFEST_TEMPLATE);
|
|
@@ -2054,7 +2054,7 @@ function registerInitCommand(program) {
|
|
|
2054
2054
|
manifest.entrypoint = 'main.js';
|
|
2055
2055
|
}
|
|
2056
2056
|
else {
|
|
2057
|
-
manifest.runtime = { command: '
|
|
2057
|
+
manifest.runtime = { command: 'python3 main.py' };
|
|
2058
2058
|
}
|
|
2059
2059
|
manifest.manifest = {
|
|
2060
2060
|
manifest_version: 1,
|
|
@@ -2073,7 +2073,7 @@ function registerInitCommand(program) {
|
|
|
2073
2073
|
}
|
|
2074
2074
|
else if (initMode.flavor === 'discord') {
|
|
2075
2075
|
manifest.description = 'An always-on Discord bot powered by Claude';
|
|
2076
|
-
manifest.runtime = { command: '
|
|
2076
|
+
manifest.runtime = { command: 'python3 main.py' };
|
|
2077
2077
|
manifest.supported_providers = ['anthropic'];
|
|
2078
2078
|
manifest.required_secrets = ['ANTHROPIC_API_KEY', 'DISCORD_BOT_TOKEN', 'DISCORD_CHANNEL_IDS'];
|
|
2079
2079
|
manifest.tags = ['discord', 'always-on'];
|
|
@@ -2085,7 +2085,7 @@ function registerInitCommand(program) {
|
|
|
2085
2085
|
manifest.entrypoint = 'main.js';
|
|
2086
2086
|
}
|
|
2087
2087
|
else {
|
|
2088
|
-
manifest.runtime = { command: '
|
|
2088
|
+
manifest.runtime = { command: 'python3 main.py' };
|
|
2089
2089
|
}
|
|
2090
2090
|
manifest.required_secrets = [];
|
|
2091
2091
|
}
|
|
@@ -2202,7 +2202,7 @@ function registerInitCommand(program) {
|
|
|
2202
2202
|
process.stdout.write(` ${stepNum}. Create a Discord bot at https://discord.com/developers/applications\n`);
|
|
2203
2203
|
process.stdout.write(` ${stepNum + 1}. Enable Message Content Intent in bot settings\n`);
|
|
2204
2204
|
process.stdout.write(` ${stepNum + 2}. Copy .env.example to .env and fill in your tokens\n`);
|
|
2205
|
-
process.stdout.write(` ${stepNum + 3}. Test locally: pip install -r requirements.txt &&
|
|
2205
|
+
process.stdout.write(` ${stepNum + 3}. Test locally: pip install -r requirements.txt && python3 main.py\n`);
|
|
2206
2206
|
process.stdout.write(` ${stepNum + 4}. Deploy: orch publish\n`);
|
|
2207
2207
|
}
|
|
2208
2208
|
else if (initMode.flavor === 'code_runtime') {
|
|
@@ -2212,7 +2212,7 @@ function registerInitCommand(program) {
|
|
|
2212
2212
|
}
|
|
2213
2213
|
if (runMode === 'always_on') {
|
|
2214
2214
|
const mainFile = isJavaScript ? 'main.js' : 'main.py';
|
|
2215
|
-
const testCmd = isJavaScript ? 'node main.js' : '
|
|
2215
|
+
const testCmd = isJavaScript ? 'node main.js' : 'python3 main.py';
|
|
2216
2216
|
process.stdout.write(` ${stepNum}. Edit ${mainFile} with your service logic\n`);
|
|
2217
2217
|
process.stdout.write(` ${stepNum + 1}. Test locally: ${testCmd}\n`);
|
|
2218
2218
|
process.stdout.write(` ${stepNum + 2}. Publish: orch publish\n`);
|
|
@@ -2229,7 +2229,7 @@ function registerInitCommand(program) {
|
|
|
2229
2229
|
const inputField = initMode.type === 'agent' ? 'task' : 'input';
|
|
2230
2230
|
process.stdout.write(` ${stepNum}. Edit main.py with your agent logic\n`);
|
|
2231
2231
|
process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
|
|
2232
|
-
process.stdout.write(` ${stepNum + 2}. Test: echo '{"${inputField}": "test"}' |
|
|
2232
|
+
process.stdout.write(` ${stepNum + 2}. Test: echo '{"${inputField}": "test"}' | python3 main.py\n`);
|
|
2233
2233
|
process.stdout.write(` ${stepNum + 3}. Run: orchagent publish\n`);
|
|
2234
2234
|
}
|
|
2235
2235
|
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.scanUndeclaredEnvVars = scanUndeclaredEnvVars;
|
|
|
42
42
|
exports.scanReservedPort = scanReservedPort;
|
|
43
43
|
exports.detectSdkCompatible = detectSdkCompatible;
|
|
44
44
|
exports.checkDependencies = checkDependencies;
|
|
45
|
+
exports.checkWorkspaceLlmKeys = checkWorkspaceLlmKeys;
|
|
45
46
|
exports.batchPublish = batchPublish;
|
|
46
47
|
exports.registerPublishCommand = registerPublishCommand;
|
|
47
48
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
@@ -394,7 +395,7 @@ function commandForEntrypoint(entrypoint) {
|
|
|
394
395
|
if (entrypoint.endsWith('.js') || entrypoint.endsWith('.mjs') || entrypoint.endsWith('.cjs') || entrypoint.endsWith('.ts')) {
|
|
395
396
|
return `node ${entrypoint}`;
|
|
396
397
|
}
|
|
397
|
-
return `
|
|
398
|
+
return `python3 ${entrypoint}`;
|
|
398
399
|
}
|
|
399
400
|
/**
|
|
400
401
|
* Check if manifest dependencies are published and callable.
|
|
@@ -439,13 +440,71 @@ async function checkDependencies(config, dependencies, publishingOrgSlug, worksp
|
|
|
439
440
|
}
|
|
440
441
|
catch (err) {
|
|
441
442
|
if (err?.status === 404) {
|
|
442
|
-
|
|
443
|
+
// Could be unpublished OR published-but-private — we can't tell from a 404
|
|
444
|
+
return { ref, status: 'not_found_cross_org' };
|
|
443
445
|
}
|
|
444
446
|
// Network/unexpected error — don't false alarm
|
|
445
447
|
return { ref, status: 'found_callable' };
|
|
446
448
|
}
|
|
447
449
|
}));
|
|
448
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Provider names the platform supports for LLM vault keys.
|
|
453
|
+
* Must stay in sync with gateway's _PROVIDER_TO_SECRET_NAME (db.py).
|
|
454
|
+
*/
|
|
455
|
+
const PROVIDER_TO_SECRET_NAME = {
|
|
456
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
457
|
+
openai: 'OPENAI_API_KEY',
|
|
458
|
+
gemini: 'GEMINI_API_KEY',
|
|
459
|
+
};
|
|
460
|
+
/**
|
|
461
|
+
* After a successful publish, check whether the target workspace has LLM vault
|
|
462
|
+
* keys that match the agent's supported_providers. If not, print a warning so
|
|
463
|
+
* the user knows cloud runs will fail.
|
|
464
|
+
*
|
|
465
|
+
* Best-effort: API errors are silently swallowed (the agent is already published).
|
|
466
|
+
*/
|
|
467
|
+
async function checkWorkspaceLlmKeys(config, workspaceId, workspaceSlug, executionEngine, supportedProviders) {
|
|
468
|
+
// Only direct_llm and managed_loop engines need LLM keys
|
|
469
|
+
if (executionEngine !== 'direct_llm' && executionEngine !== 'managed_loop') {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
let secrets;
|
|
473
|
+
try {
|
|
474
|
+
const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/secrets`);
|
|
475
|
+
secrets = result.secrets;
|
|
476
|
+
if (!Array.isArray(secrets))
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return; // Can't reach API or unexpected response — skip warning silently
|
|
481
|
+
}
|
|
482
|
+
const llmKeys = secrets.filter(s => s.secret_type === 'llm_key' && s.llm_provider);
|
|
483
|
+
const availableProviders = new Set(llmKeys.map(s => s.llm_provider));
|
|
484
|
+
// Determine which providers the agent needs
|
|
485
|
+
const needsAny = supportedProviders.includes('any');
|
|
486
|
+
if (needsAny) {
|
|
487
|
+
// 'any' means any single provider works
|
|
488
|
+
if (availableProviders.size > 0)
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
// Check if at least one of the supported providers has a key
|
|
493
|
+
const hasMatch = supportedProviders.some(p => availableProviders.has(p));
|
|
494
|
+
if (hasMatch)
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
// Build the warning message
|
|
498
|
+
const providerList = needsAny
|
|
499
|
+
? Object.keys(PROVIDER_TO_SECRET_NAME).join(', ')
|
|
500
|
+
: supportedProviders.join(', ');
|
|
501
|
+
const exampleSecretName = needsAny
|
|
502
|
+
? 'ANTHROPIC_API_KEY'
|
|
503
|
+
: (PROVIDER_TO_SECRET_NAME[supportedProviders[0]] || `${supportedProviders[0].toUpperCase()}_API_KEY`);
|
|
504
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ No LLM vault keys found in workspace '${workspaceSlug}' for providers: ${providerList}\n`) +
|
|
505
|
+
` Cloud runs will fail until you add keys.\n` +
|
|
506
|
+
` Add a key: ${chalk_1.default.cyan(`orch secrets set ${exampleSecretName} <key>`)}\n`);
|
|
507
|
+
}
|
|
449
508
|
/**
|
|
450
509
|
* Batch publish all agents found in subdirectories, in dependency order.
|
|
451
510
|
* Discovers orchagent.json/SKILL.md in immediate subdirectories,
|
|
@@ -979,6 +1038,7 @@ function registerPublishCommand(program) {
|
|
|
979
1038
|
if (manifestDeps?.length) {
|
|
980
1039
|
const depResults = await checkDependencies(config, manifestDeps, org.slug, workspaceId);
|
|
981
1040
|
const notFound = depResults.filter(r => r.status === 'not_found');
|
|
1041
|
+
const notFoundCrossOrg = depResults.filter(r => r.status === 'not_found_cross_org');
|
|
982
1042
|
const notCallable = depResults.filter(r => r.status === 'found_not_callable');
|
|
983
1043
|
if (notFound.length > 0) {
|
|
984
1044
|
process.stderr.write(chalk_1.default.yellow(`\n⚠ Unpublished dependencies:\n`));
|
|
@@ -988,6 +1048,14 @@ function registerPublishCommand(program) {
|
|
|
988
1048
|
process.stderr.write(`\n These agents must be published before this orchestrator can call them.\n` +
|
|
989
1049
|
` Publish each dependency first, then re-run this publish.\n\n`);
|
|
990
1050
|
}
|
|
1051
|
+
if (notFoundCrossOrg.length > 0) {
|
|
1052
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies not found (unpublished or not accessible from this workspace):\n`));
|
|
1053
|
+
for (const dep of notFoundCrossOrg) {
|
|
1054
|
+
process.stderr.write(chalk_1.default.yellow(` - ${dep.ref}\n`));
|
|
1055
|
+
}
|
|
1056
|
+
process.stderr.write(`\n If the dependency is published in another workspace, ensure it's in the same\n` +
|
|
1057
|
+
` workspace as this orchestrator, or use agent access grants.\n\n`);
|
|
1058
|
+
}
|
|
991
1059
|
if (notCallable.length > 0) {
|
|
992
1060
|
process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies have callable: false:\n`));
|
|
993
1061
|
for (const dep of notCallable) {
|
|
@@ -998,6 +1066,23 @@ function registerPublishCommand(program) {
|
|
|
998
1066
|
` the field to use the default) and republish each dependency.\n\n`);
|
|
999
1067
|
}
|
|
1000
1068
|
}
|
|
1069
|
+
// UX-13-02: Warn when managed-loop orchestrator has dependencies but no custom_tools.
|
|
1070
|
+
// Without custom_tools, the LLM has no way to call its declared dependencies.
|
|
1071
|
+
if (executionEngine === 'managed_loop' && manifestDeps?.length) {
|
|
1072
|
+
const mergedTools = Array.isArray(loopConfig?.custom_tools) ? loopConfig.custom_tools : [];
|
|
1073
|
+
if (mergedTools.length === 0) {
|
|
1074
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ This managed-loop agent declares dependencies but no custom_tools.\n` +
|
|
1075
|
+
` Without custom_tools, the LLM cannot call dependencies at runtime —\n` +
|
|
1076
|
+
` it will waste turns exploring the filesystem instead.\n\n` +
|
|
1077
|
+
` Use 'orch scaffold orchestration' to auto-generate the correct\n` +
|
|
1078
|
+
` custom_tools configuration, or add them manually to your loop config:\n\n` +
|
|
1079
|
+
` "loop": {\n` +
|
|
1080
|
+
` "custom_tools": [\n` +
|
|
1081
|
+
` { "name": "call_worker", "description": "...", "command": "python3 /home/user/helpers/orch_call.py org/worker@v1" }\n` +
|
|
1082
|
+
` ]\n` +
|
|
1083
|
+
` }\n\n`));
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1001
1086
|
// UX-2: Default required_secrets to [] when omitted for tool/agent types.
|
|
1002
1087
|
// Prompt and skill types are exempt (prompt agents get LLM keys from platform,
|
|
1003
1088
|
// skills don't run standalone).
|
|
@@ -1409,6 +1494,8 @@ function registerPublishCommand(program) {
|
|
|
1409
1494
|
process.stderr.write(chalk_1.default.yellow(`⚠ ${warning}\n`));
|
|
1410
1495
|
}
|
|
1411
1496
|
}
|
|
1497
|
+
// Warn if workspace has no LLM vault keys for this agent's providers (UX-13-01)
|
|
1498
|
+
await checkWorkspaceLlmKeys(config, workspaceId || org.id, org.slug, executionEngine, supportedProviders);
|
|
1412
1499
|
// Show required secrets with setup instructions (F-18)
|
|
1413
1500
|
if (manifest.required_secrets?.length) {
|
|
1414
1501
|
process.stdout.write(`\nRequired secrets:\n`);
|
package/dist/commands/pull.js
CHANGED
|
@@ -112,11 +112,14 @@ async function resolveAgent(config, org, agent, version, workspaceId) {
|
|
|
112
112
|
if (!config.apiKey) {
|
|
113
113
|
throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
// Try workspace-scoped search first, then fall back to personal org.
|
|
116
|
+
// This handles the case where the user is in a team workspace but the
|
|
117
|
+
// agent lives in their personal org (or vice versa). The org_slug
|
|
118
|
+
// filter inside resolveFromMyAgents prevents cross-org contamination.
|
|
119
|
+
let data = await resolveFromMyAgents(config, agent, version, org, workspaceId);
|
|
120
|
+
if (!data && workspaceId) {
|
|
121
|
+
data = await resolveFromMyAgents(config, agent, version, org, undefined);
|
|
118
122
|
}
|
|
119
|
-
const data = await resolveFromMyAgents(config, agent, version, org, workspaceId);
|
|
120
123
|
if (!data) {
|
|
121
124
|
throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
|
|
122
125
|
}
|
|
@@ -124,15 +127,10 @@ async function resolveAgent(config, org, agent, version, workspaceId) {
|
|
|
124
127
|
}
|
|
125
128
|
async function tryOwnerFallback(config, org, agent, version, workspaceId) {
|
|
126
129
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
match =
|
|
131
|
-
.filter(a => a.name === agent && a.org_slug === org)
|
|
132
|
-
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
match = myAgents.find(a => a.name === agent && a.version === version && a.org_slug === org);
|
|
130
|
+
let match = findOwnerMatch(await (0, api_1.listMyAgents)(config, workspaceId), agent, version, org);
|
|
131
|
+
// Retry without workspace restriction to find agents in personal org
|
|
132
|
+
if (!match && workspaceId) {
|
|
133
|
+
match = findOwnerMatch(await (0, api_1.listMyAgents)(config, undefined), agent, version, org);
|
|
136
134
|
}
|
|
137
135
|
if (!match)
|
|
138
136
|
return null;
|
|
@@ -143,6 +141,14 @@ async function tryOwnerFallback(config, org, agent, version, workspaceId) {
|
|
|
143
141
|
return null;
|
|
144
142
|
}
|
|
145
143
|
}
|
|
144
|
+
function findOwnerMatch(agents, agent, version, org) {
|
|
145
|
+
if (version === 'latest') {
|
|
146
|
+
return agents
|
|
147
|
+
.filter(a => a.name === agent && a.org_slug === org)
|
|
148
|
+
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
149
|
+
}
|
|
150
|
+
return agents.find(a => a.name === agent && a.version === version && a.org_slug === org);
|
|
151
|
+
}
|
|
146
152
|
async function resolveFromMyAgents(config, agent, version, org, workspaceId) {
|
|
147
153
|
const agents = await (0, api_1.listMyAgents)(config, workspaceId);
|
|
148
154
|
const matching = agents.filter(a => a.name === agent && a.org_slug === org);
|
package/dist/commands/run.js
CHANGED
|
@@ -787,6 +787,13 @@ async function detectAllLlmKeys(supportedProviders, config) {
|
|
|
787
787
|
return providers;
|
|
788
788
|
}
|
|
789
789
|
async function executePromptLocally(agentData, inputData, skillPrompts = [], config, providerOverride, modelOverride) {
|
|
790
|
+
// Auto-detect provider from model name if not explicitly specified
|
|
791
|
+
if (!providerOverride && modelOverride) {
|
|
792
|
+
const detected = (0, llm_1.detectProviderFromModel)(modelOverride);
|
|
793
|
+
if (detected) {
|
|
794
|
+
providerOverride = detected;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
790
797
|
if (providerOverride) {
|
|
791
798
|
(0, llm_1.validateProvider)(providerOverride);
|
|
792
799
|
}
|
|
@@ -805,7 +812,7 @@ async function executePromptLocally(agentData, inputData, skillPrompts = [], con
|
|
|
805
812
|
throw new errors_1.CliError(`No LLM key found for: ${providers}\n` +
|
|
806
813
|
`Set an environment variable (e.g., OPENAI_API_KEY), run 'orch secrets set <PROVIDER>_API_KEY <key>', or configure in web dashboard`);
|
|
807
814
|
}
|
|
808
|
-
if (modelOverride &&
|
|
815
|
+
if (modelOverride && allProviders.length > 1) {
|
|
809
816
|
process.stderr.write(`Warning: --model specified without --provider. The model '${modelOverride}' will be used for all ${allProviders.length} fallback providers, which may cause errors if the model is incompatible.\n` +
|
|
810
817
|
`Consider specifying --provider to ensure correct model/provider pairing.\n\n`);
|
|
811
818
|
}
|
|
@@ -855,6 +862,13 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
|
|
|
855
862
|
throw new errors_1.CliError('Python 3 is required for local agent execution.\n' +
|
|
856
863
|
'Install Python 3: https://python.org/downloads');
|
|
857
864
|
}
|
|
865
|
+
// Auto-detect provider from model name if not explicitly specified
|
|
866
|
+
if (!providerOverride && modelOverride) {
|
|
867
|
+
const detected = (0, llm_1.detectProviderFromModel)(modelOverride);
|
|
868
|
+
if (detected) {
|
|
869
|
+
providerOverride = detected;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
858
872
|
// 2. Detect LLM provider + key
|
|
859
873
|
const supportedProviders = manifest?.supported_providers || ['any'];
|
|
860
874
|
const providersToCheck = providerOverride
|
|
@@ -1968,18 +1982,8 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1968
1982
|
throw new errors_1.CliError('When using --key, you must also specify --provider (openai, anthropic, or gemini)');
|
|
1969
1983
|
}
|
|
1970
1984
|
(0, llm_1.validateProvider)(effectiveProvider);
|
|
1971
|
-
if (options.model
|
|
1972
|
-
|
|
1973
|
-
const providerPatterns = {
|
|
1974
|
-
openai: /^(gpt-|o1-|o3-|davinci|text-)/,
|
|
1975
|
-
anthropic: /^claude-/,
|
|
1976
|
-
gemini: /^gemini-/,
|
|
1977
|
-
ollama: /^(llama|mistral|deepseek|phi|qwen)/,
|
|
1978
|
-
};
|
|
1979
|
-
const expectedPattern = providerPatterns[effectiveProvider];
|
|
1980
|
-
if (expectedPattern && !expectedPattern.test(modelLower)) {
|
|
1981
|
-
process.stderr.write(`Warning: Model '${options.model}' may not be a ${effectiveProvider} model.\n\n`);
|
|
1982
|
-
}
|
|
1985
|
+
if (options.model) {
|
|
1986
|
+
(0, llm_1.warnProviderModelMismatch)(options.model, effectiveProvider);
|
|
1983
1987
|
}
|
|
1984
1988
|
llmKey = options.key;
|
|
1985
1989
|
llmProvider = effectiveProvider;
|
|
@@ -1990,17 +1994,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1990
1994
|
(0, llm_1.validateProvider)(effectiveProvider);
|
|
1991
1995
|
providersToCheck = [effectiveProvider];
|
|
1992
1996
|
if (options.model) {
|
|
1993
|
-
|
|
1994
|
-
const providerPatterns = {
|
|
1995
|
-
openai: /^(gpt-|o1-|o3-|davinci|text-)/,
|
|
1996
|
-
anthropic: /^claude-/,
|
|
1997
|
-
gemini: /^gemini-/,
|
|
1998
|
-
ollama: /^(llama|mistral|deepseek|phi|qwen)/,
|
|
1999
|
-
};
|
|
2000
|
-
const expectedPattern = providerPatterns[effectiveProvider];
|
|
2001
|
-
if (expectedPattern && !expectedPattern.test(modelLower)) {
|
|
2002
|
-
process.stderr.write(`Warning: Model '${options.model}' may not be a ${effectiveProvider} model.\n\n`);
|
|
2003
|
-
}
|
|
1997
|
+
(0, llm_1.warnProviderModelMismatch)(options.model, effectiveProvider);
|
|
2004
1998
|
}
|
|
2005
1999
|
}
|
|
2006
2000
|
const detected = await (0, llm_1.detectLlmKey)(providersToCheck, resolved);
|
|
@@ -2637,16 +2631,13 @@ async function executeLocal(agentRef, args, options) {
|
|
|
2637
2631
|
options.input = JSON.stringify({ path: options.path });
|
|
2638
2632
|
}
|
|
2639
2633
|
if (options.model && options.provider) {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
const expectedPattern = providerPatterns[options.provider];
|
|
2648
|
-
if (expectedPattern && !expectedPattern.test(modelLower)) {
|
|
2649
|
-
process.stderr.write(`Warning: Model '${options.model}' may not be a ${options.provider} model.\n\n`);
|
|
2634
|
+
(0, llm_1.warnProviderModelMismatch)(options.model, options.provider);
|
|
2635
|
+
}
|
|
2636
|
+
else if (options.model && !options.provider) {
|
|
2637
|
+
const detected = (0, llm_1.detectProviderFromModel)(options.model);
|
|
2638
|
+
if (detected) {
|
|
2639
|
+
options.provider = detected;
|
|
2640
|
+
process.stderr.write(`Auto-detected provider: ${detected} (from model '${options.model}')\n\n`);
|
|
2650
2641
|
}
|
|
2651
2642
|
}
|
|
2652
2643
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
@@ -14,7 +14,7 @@ exports.TEMPLATE_MANIFEST = `{
|
|
|
14
14
|
"type": "agent",
|
|
15
15
|
"description": "Weekly GitHub activity summary delivered to Discord. Uses Claude to analyse commits, PRs, and issues — surfaces patterns, risks, and trends.",
|
|
16
16
|
"runtime": {
|
|
17
|
-
"command": "
|
|
17
|
+
"command": "python3 main.py"
|
|
18
18
|
},
|
|
19
19
|
"required_secrets": [
|
|
20
20
|
"ORCHAGENT_API_KEY",
|
package/dist/commands/test.js
CHANGED
|
@@ -640,7 +640,7 @@ function runEntrypointWithInput(agentDir, entrypoint, stdinData, verbose) {
|
|
|
640
640
|
/**
|
|
641
641
|
* Run fixture tests for code_runtime agents by executing the entrypoint
|
|
642
642
|
* with fixture input as stdin and validating the JSON output.
|
|
643
|
-
* Same interface as E2B:
|
|
643
|
+
* Same interface as E2B: python3 main.py < input.json
|
|
644
644
|
*/
|
|
645
645
|
async function runCodeRuntimeFixtureTests(agentDir, fixtures, entrypoint, verbose) {
|
|
646
646
|
process.stderr.write(chalk_1.default.blue('\nRunning fixture tests (code runtime)...\n\n'));
|
|
@@ -660,7 +660,7 @@ async function runCodeRuntimeFixtureTests(agentDir, fixtures, entrypoint, verbos
|
|
|
660
660
|
throw new errors_1.CliError(`Invalid JSON in ${fixtureName}: ${e.message}`);
|
|
661
661
|
}
|
|
662
662
|
const fixture = validateFixture(parsed, fixturePath);
|
|
663
|
-
// Run entrypoint with fixture input as stdin (same as E2B:
|
|
663
|
+
// Run entrypoint with fixture input as stdin (same as E2B: python3 main.py < input.json)
|
|
664
664
|
const inputJson = JSON.stringify(fixture.input);
|
|
665
665
|
const result = await runEntrypointWithInput(agentDir, entrypoint, inputJson, verbose);
|
|
666
666
|
if (result.code !== 0) {
|
|
@@ -172,6 +172,7 @@ Options:
|
|
|
172
172
|
const org = await (0, api_1.getOrg)(config, workspaceId);
|
|
173
173
|
const depResults = await (0, publish_1.checkDependencies)(config, deps, org.slug, workspaceId);
|
|
174
174
|
const notFound = depResults.filter(r => r.status === 'not_found');
|
|
175
|
+
const notFoundCrossOrg = depResults.filter(r => r.status === 'not_found_cross_org');
|
|
175
176
|
const notCallable = depResults.filter(r => r.status === 'found_not_callable');
|
|
176
177
|
if (notFound.length > 0) {
|
|
177
178
|
for (const dep of notFound) {
|
|
@@ -181,6 +182,14 @@ Options:
|
|
|
181
182
|
});
|
|
182
183
|
}
|
|
183
184
|
}
|
|
185
|
+
if (notFoundCrossOrg.length > 0) {
|
|
186
|
+
for (const dep of notFoundCrossOrg) {
|
|
187
|
+
serverIssues.push({
|
|
188
|
+
level: 'warning',
|
|
189
|
+
message: `Dependency not found (unpublished or not accessible from this workspace): ${dep.ref}`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
184
193
|
if (notCallable.length > 0) {
|
|
185
194
|
for (const dep of notCallable) {
|
|
186
195
|
serverIssues.push({
|
package/dist/lib/llm.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Used by run, call, and skill commands.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.DEFAULT_MODELS = exports.PROVIDER_ENV_VARS = exports.LlmError = void 0;
|
|
9
|
+
exports.MODEL_PROVIDER_PATTERNS = exports.DEFAULT_MODELS = exports.PROVIDER_ENV_VARS = exports.LlmError = void 0;
|
|
10
10
|
exports.isRateLimitError = isRateLimitError;
|
|
11
11
|
exports.detectLlmKeyFromEnv = detectLlmKeyFromEnv;
|
|
12
12
|
exports.detectLlmKey = detectLlmKey;
|
|
@@ -15,6 +15,8 @@ exports.buildPrompt = buildPrompt;
|
|
|
15
15
|
exports.callLlm = callLlm;
|
|
16
16
|
exports.callLlmWithFallback = callLlmWithFallback;
|
|
17
17
|
exports.validateProvider = validateProvider;
|
|
18
|
+
exports.detectProviderFromModel = detectProviderFromModel;
|
|
19
|
+
exports.warnProviderModelMismatch = warnProviderModelMismatch;
|
|
18
20
|
const errors_1 = require("./errors");
|
|
19
21
|
const llm_errors_1 = require("./llm-errors");
|
|
20
22
|
class LlmError extends errors_1.CliError {
|
|
@@ -265,3 +267,37 @@ function validateProvider(provider) {
|
|
|
265
267
|
throw new errors_1.CliError(`Invalid provider: ${provider}. Valid: ${validProviders.join(', ')}`);
|
|
266
268
|
}
|
|
267
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Model-name patterns for auto-detecting the LLM provider.
|
|
272
|
+
* Tested against the lowercased model string.
|
|
273
|
+
*/
|
|
274
|
+
exports.MODEL_PROVIDER_PATTERNS = {
|
|
275
|
+
openai: /^(gpt-|o1-|o3-|o4-|davinci|text-)/,
|
|
276
|
+
anthropic: /^claude-/,
|
|
277
|
+
gemini: /^gemini-/,
|
|
278
|
+
ollama: /^(llama|mistral|deepseek|phi|qwen)/,
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* Auto-detect the LLM provider from a model name using prefix patterns.
|
|
282
|
+
* Returns the provider string if a match is found, or null if ambiguous/unknown.
|
|
283
|
+
*/
|
|
284
|
+
function detectProviderFromModel(model) {
|
|
285
|
+
const modelLower = model.toLowerCase();
|
|
286
|
+
for (const [provider, pattern] of Object.entries(exports.MODEL_PROVIDER_PATTERNS)) {
|
|
287
|
+
if (pattern.test(modelLower)) {
|
|
288
|
+
return provider;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Warn if a model name doesn't match the expected provider's pattern.
|
|
295
|
+
* Used when both --model and --provider are explicitly specified.
|
|
296
|
+
*/
|
|
297
|
+
function warnProviderModelMismatch(model, provider) {
|
|
298
|
+
const modelLower = model.toLowerCase();
|
|
299
|
+
const expectedPattern = exports.MODEL_PROVIDER_PATTERNS[provider];
|
|
300
|
+
if (expectedPattern && !expectedPattern.test(modelLower)) {
|
|
301
|
+
process.stderr.write(`Warning: Model '${model}' may not be a ${provider} model.\n\n`);
|
|
302
|
+
}
|
|
303
|
+
}
|
package/package.json
CHANGED