@orchagent/cli 0.3.101 → 0.3.103
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/dag.js +4 -0
- package/dist/commands/health.js +2 -7
- package/dist/commands/init.js +26 -22
- package/dist/commands/publish.js +102 -5
- package/dist/commands/pull.js +20 -14
- package/dist/commands/run.js +65 -70
- package/dist/commands/schedule.js +3 -6
- 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/trace.js +4 -0
- package/dist/commands/validate.js +9 -0
- package/dist/lib/api.js +10 -7
- package/dist/lib/batch-publish.js +73 -0
- package/dist/lib/json-input.js +60 -0
- package/dist/lib/llm.js +37 -1
- package/package.json +1 -1
package/dist/commands/dag.js
CHANGED
|
@@ -193,6 +193,10 @@ function registerDagCommand(program) {
|
|
|
193
193
|
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
194
194
|
}
|
|
195
195
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
196
|
+
// Accept req_xxx format (gateway request_id shown in run output)
|
|
197
|
+
if (/^req_[0-9a-f]+$/i.test(runId)) {
|
|
198
|
+
runId = runId.slice(4);
|
|
199
|
+
}
|
|
196
200
|
// Resolve short run IDs
|
|
197
201
|
let resolvedRunId = runId;
|
|
198
202
|
if (isUuid(runId)) {
|
package/dist/commands/health.js
CHANGED
|
@@ -9,6 +9,7 @@ const config_1 = require("../lib/config");
|
|
|
9
9
|
const api_1 = require("../lib/api");
|
|
10
10
|
const agent_ref_1 = require("../lib/agent-ref");
|
|
11
11
|
const errors_1 = require("../lib/errors");
|
|
12
|
+
const json_input_1 = require("../lib/json-input");
|
|
12
13
|
const spinner_1 = require("../lib/spinner");
|
|
13
14
|
const output_1 = require("../lib/output");
|
|
14
15
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
@@ -194,13 +195,7 @@ function registerHealthCommand(program) {
|
|
|
194
195
|
const inputSchema = agentMeta.input_schema;
|
|
195
196
|
let body;
|
|
196
197
|
if (options.data) {
|
|
197
|
-
|
|
198
|
-
JSON.parse(options.data);
|
|
199
|
-
body = options.data;
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
throw new errors_1.CliError('Invalid JSON in --data option.');
|
|
203
|
-
}
|
|
198
|
+
body = await (0, json_input_1.resolveJsonBody)(options.data);
|
|
204
199
|
}
|
|
205
200
|
else {
|
|
206
201
|
const sample = generateSampleInput(inputSchema);
|
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
|
}
|
|
@@ -2242,6 +2242,10 @@ function registerInitCommand(program) {
|
|
|
2242
2242
|
process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
|
|
2243
2243
|
process.stdout.write(` ${stepNum + 2}. Run: orchagent publish\n`);
|
|
2244
2244
|
}
|
|
2245
|
+
if (initMode.flavor === 'managed_loop') {
|
|
2246
|
+
process.stdout.write(`\n Note: supported_providers: ["any"] means anthropic, openai, or gemini\n`);
|
|
2247
|
+
process.stdout.write(` Your vault key determines which is used (set with: orch secrets set)\n`);
|
|
2248
|
+
}
|
|
2245
2249
|
process.stdout.write(AGENT_BUILDER_HINT);
|
|
2246
2250
|
});
|
|
2247
2251
|
}
|
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,
|
|
@@ -489,11 +548,21 @@ async function batchPublish(rootDir, options) {
|
|
|
489
548
|
catch {
|
|
490
549
|
// Non-critical — just won't show org prefix
|
|
491
550
|
}
|
|
492
|
-
|
|
493
|
-
process.stderr.write(plan);
|
|
551
|
+
// Dry-run: show ordering preview and exit (no subprocesses)
|
|
494
552
|
if (options.dryRun) {
|
|
495
|
-
|
|
553
|
+
const summary = (0, batch_publish_1.formatDryRunSummary)(sorted, orgSlug);
|
|
554
|
+
process.stderr.write(summary);
|
|
555
|
+
await (0, analytics_1.track)('cli_publish_all', {
|
|
556
|
+
total: sorted.length,
|
|
557
|
+
succeeded: 0,
|
|
558
|
+
failed: 0,
|
|
559
|
+
skipped: sorted.length,
|
|
560
|
+
dry_run: true,
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
496
563
|
}
|
|
564
|
+
const plan = (0, batch_publish_1.formatPublishPlan)(sorted, orgSlug);
|
|
565
|
+
process.stderr.write(plan);
|
|
497
566
|
// Build the CLI args to forward (exclude --all)
|
|
498
567
|
const forwardArgs = [];
|
|
499
568
|
if (options.profile)
|
|
@@ -979,6 +1048,7 @@ function registerPublishCommand(program) {
|
|
|
979
1048
|
if (manifestDeps?.length) {
|
|
980
1049
|
const depResults = await checkDependencies(config, manifestDeps, org.slug, workspaceId);
|
|
981
1050
|
const notFound = depResults.filter(r => r.status === 'not_found');
|
|
1051
|
+
const notFoundCrossOrg = depResults.filter(r => r.status === 'not_found_cross_org');
|
|
982
1052
|
const notCallable = depResults.filter(r => r.status === 'found_not_callable');
|
|
983
1053
|
if (notFound.length > 0) {
|
|
984
1054
|
process.stderr.write(chalk_1.default.yellow(`\n⚠ Unpublished dependencies:\n`));
|
|
@@ -988,6 +1058,14 @@ function registerPublishCommand(program) {
|
|
|
988
1058
|
process.stderr.write(`\n These agents must be published before this orchestrator can call them.\n` +
|
|
989
1059
|
` Publish each dependency first, then re-run this publish.\n\n`);
|
|
990
1060
|
}
|
|
1061
|
+
if (notFoundCrossOrg.length > 0) {
|
|
1062
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies not found (unpublished or not accessible from this workspace):\n`));
|
|
1063
|
+
for (const dep of notFoundCrossOrg) {
|
|
1064
|
+
process.stderr.write(chalk_1.default.yellow(` - ${dep.ref}\n`));
|
|
1065
|
+
}
|
|
1066
|
+
process.stderr.write(`\n If the dependency is published in another workspace, ensure it's in the same\n` +
|
|
1067
|
+
` workspace as this orchestrator, or use agent access grants.\n\n`);
|
|
1068
|
+
}
|
|
991
1069
|
if (notCallable.length > 0) {
|
|
992
1070
|
process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies have callable: false:\n`));
|
|
993
1071
|
for (const dep of notCallable) {
|
|
@@ -998,6 +1076,23 @@ function registerPublishCommand(program) {
|
|
|
998
1076
|
` the field to use the default) and republish each dependency.\n\n`);
|
|
999
1077
|
}
|
|
1000
1078
|
}
|
|
1079
|
+
// UX-13-02: Warn when managed-loop orchestrator has dependencies but no custom_tools.
|
|
1080
|
+
// Without custom_tools, the LLM has no way to call its declared dependencies.
|
|
1081
|
+
if (executionEngine === 'managed_loop' && manifestDeps?.length) {
|
|
1082
|
+
const mergedTools = Array.isArray(loopConfig?.custom_tools) ? loopConfig.custom_tools : [];
|
|
1083
|
+
if (mergedTools.length === 0) {
|
|
1084
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ This managed-loop agent declares dependencies but no custom_tools.\n` +
|
|
1085
|
+
` Without custom_tools, the LLM cannot call dependencies at runtime —\n` +
|
|
1086
|
+
` it will waste turns exploring the filesystem instead.\n\n` +
|
|
1087
|
+
` Use 'orch scaffold orchestration' to auto-generate the correct\n` +
|
|
1088
|
+
` custom_tools configuration, or add them manually to your loop config:\n\n` +
|
|
1089
|
+
` "loop": {\n` +
|
|
1090
|
+
` "custom_tools": [\n` +
|
|
1091
|
+
` { "name": "call_worker", "description": "...", "command": "python3 /home/user/helpers/orch_call.py org/worker@v1" }\n` +
|
|
1092
|
+
` ]\n` +
|
|
1093
|
+
` }\n\n`));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1001
1096
|
// UX-2: Default required_secrets to [] when omitted for tool/agent types.
|
|
1002
1097
|
// Prompt and skill types are exempt (prompt agents get LLM keys from platform,
|
|
1003
1098
|
// skills don't run standalone).
|
|
@@ -1409,6 +1504,8 @@ function registerPublishCommand(program) {
|
|
|
1409
1504
|
process.stderr.write(chalk_1.default.yellow(`⚠ ${warning}\n`));
|
|
1410
1505
|
}
|
|
1411
1506
|
}
|
|
1507
|
+
// Warn if workspace has no LLM vault keys for this agent's providers (UX-13-01)
|
|
1508
|
+
await checkWorkspaceLlmKeys(config, workspaceId || org.id, org.slug, executionEngine, supportedProviders);
|
|
1412
1509
|
// Show required secrets with setup instructions (F-18)
|
|
1413
1510
|
if (manifest.required_secrets?.length) {
|
|
1414
1511
|
process.stdout.write(`\nRequired secrets:\n`);
|
package/dist/commands/pull.js
CHANGED
|
@@ -50,7 +50,7 @@ function commandForEntrypoint(entrypoint) {
|
|
|
50
50
|
|| entrypoint.endsWith('.ts')) {
|
|
51
51
|
return `node ${entrypoint}`;
|
|
52
52
|
}
|
|
53
|
-
return `
|
|
53
|
+
return `python3 ${entrypoint}`;
|
|
54
54
|
}
|
|
55
55
|
// ─── Agent Resolution ───────────────────────────────────────────────────────
|
|
56
56
|
async function resolveAgent(config, org, agent, version, workspaceId) {
|
|
@@ -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
|
@@ -57,6 +57,7 @@ const config_1 = require("../lib/config");
|
|
|
57
57
|
const resolve_agent_1 = require("../lib/resolve-agent");
|
|
58
58
|
const api_1 = require("../lib/api");
|
|
59
59
|
const errors_1 = require("../lib/errors");
|
|
60
|
+
const json_input_1 = require("../lib/json-input");
|
|
60
61
|
const output_1 = require("../lib/output");
|
|
61
62
|
const spinner_1 = require("../lib/spinner");
|
|
62
63
|
const llm_1 = require("../lib/llm");
|
|
@@ -360,32 +361,6 @@ async function buildMultipartBody(filePaths, metadata) {
|
|
|
360
361
|
sourceLabel: filePaths.length === 1 ? filePaths[0] : `${filePaths.length} files`,
|
|
361
362
|
};
|
|
362
363
|
}
|
|
363
|
-
async function resolveJsonBody(input) {
|
|
364
|
-
let raw = input;
|
|
365
|
-
if (input.startsWith('@')) {
|
|
366
|
-
const source = input.slice(1);
|
|
367
|
-
if (!source) {
|
|
368
|
-
throw new errors_1.CliError('Invalid JSON input. Use a JSON string or @file.');
|
|
369
|
-
}
|
|
370
|
-
if (source === '-') {
|
|
371
|
-
const stdinData = await readStdin();
|
|
372
|
-
if (!stdinData) {
|
|
373
|
-
throw new errors_1.CliError('No stdin provided for JSON input.');
|
|
374
|
-
}
|
|
375
|
-
raw = stdinData.toString('utf8');
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
await validateFilePath(source);
|
|
379
|
-
raw = await promises_1.default.readFile(source, 'utf8');
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
return JSON.stringify(JSON.parse(raw));
|
|
384
|
-
}
|
|
385
|
-
catch {
|
|
386
|
-
throw (0, errors_1.jsonInputError)('data');
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
364
|
// ─── Keyed file & mount helpers ──────────────────────────────────────────────
|
|
390
365
|
const KEYED_FILE_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
391
366
|
function isKeyedFileArg(arg) {
|
|
@@ -494,7 +469,7 @@ async function buildInjectedPayload(options) {
|
|
|
494
469
|
let merged = {};
|
|
495
470
|
// 1. Start with --data
|
|
496
471
|
if (options.dataOption) {
|
|
497
|
-
const resolved = await resolveJsonBody(options.dataOption);
|
|
472
|
+
const resolved = await (0, json_input_1.resolveJsonBody)(options.dataOption);
|
|
498
473
|
merged = JSON.parse(resolved);
|
|
499
474
|
}
|
|
500
475
|
let totalBytes = 0;
|
|
@@ -787,6 +762,13 @@ async function detectAllLlmKeys(supportedProviders, config) {
|
|
|
787
762
|
return providers;
|
|
788
763
|
}
|
|
789
764
|
async function executePromptLocally(agentData, inputData, skillPrompts = [], config, providerOverride, modelOverride) {
|
|
765
|
+
// Auto-detect provider from model name if not explicitly specified
|
|
766
|
+
if (!providerOverride && modelOverride) {
|
|
767
|
+
const detected = (0, llm_1.detectProviderFromModel)(modelOverride);
|
|
768
|
+
if (detected) {
|
|
769
|
+
providerOverride = detected;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
790
772
|
if (providerOverride) {
|
|
791
773
|
(0, llm_1.validateProvider)(providerOverride);
|
|
792
774
|
}
|
|
@@ -805,7 +787,7 @@ async function executePromptLocally(agentData, inputData, skillPrompts = [], con
|
|
|
805
787
|
throw new errors_1.CliError(`No LLM key found for: ${providers}\n` +
|
|
806
788
|
`Set an environment variable (e.g., OPENAI_API_KEY), run 'orch secrets set <PROVIDER>_API_KEY <key>', or configure in web dashboard`);
|
|
807
789
|
}
|
|
808
|
-
if (modelOverride &&
|
|
790
|
+
if (modelOverride && allProviders.length > 1) {
|
|
809
791
|
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
792
|
`Consider specifying --provider to ensure correct model/provider pairing.\n\n`);
|
|
811
793
|
}
|
|
@@ -855,6 +837,13 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
|
|
|
855
837
|
throw new errors_1.CliError('Python 3 is required for local agent execution.\n' +
|
|
856
838
|
'Install Python 3: https://python.org/downloads');
|
|
857
839
|
}
|
|
840
|
+
// Auto-detect provider from model name if not explicitly specified
|
|
841
|
+
if (!providerOverride && modelOverride) {
|
|
842
|
+
const detected = (0, llm_1.detectProviderFromModel)(modelOverride);
|
|
843
|
+
if (detected) {
|
|
844
|
+
providerOverride = detected;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
858
847
|
// 2. Detect LLM provider + key
|
|
859
848
|
const supportedProviders = manifest?.supported_providers || ['any'];
|
|
860
849
|
const providersToCheck = providerOverride
|
|
@@ -1938,12 +1927,19 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1938
1927
|
return;
|
|
1939
1928
|
}
|
|
1940
1929
|
}
|
|
1941
|
-
catch {
|
|
1942
|
-
//
|
|
1930
|
+
catch (err) {
|
|
1931
|
+
// Provide specific error messages based on failure type
|
|
1932
|
+
const detail = err instanceof api_1.ApiError && err.status === 404
|
|
1933
|
+
? 'Agent not found.'
|
|
1934
|
+
: err instanceof api_1.ApiError && err.status === 429
|
|
1935
|
+
? 'Rate limited — try again shortly.'
|
|
1936
|
+
: err instanceof api_1.ApiError
|
|
1937
|
+
? `API error (${err.status}).`
|
|
1938
|
+
: 'Network error — check your connection.';
|
|
1943
1939
|
if (options.estimateOnly) {
|
|
1944
|
-
throw new errors_1.CliError(
|
|
1940
|
+
throw new errors_1.CliError(`Could not fetch cost estimate: ${detail}`);
|
|
1945
1941
|
}
|
|
1946
|
-
process.stderr.write(chalk_1.default.gray(
|
|
1942
|
+
process.stderr.write(chalk_1.default.gray(`Could not fetch cost estimate: ${detail} Proceeding with run...\n\n`));
|
|
1947
1943
|
}
|
|
1948
1944
|
}
|
|
1949
1945
|
const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
|
|
@@ -1968,18 +1964,8 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1968
1964
|
throw new errors_1.CliError('When using --key, you must also specify --provider (openai, anthropic, or gemini)');
|
|
1969
1965
|
}
|
|
1970
1966
|
(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
|
-
}
|
|
1967
|
+
if (options.model) {
|
|
1968
|
+
(0, llm_1.warnProviderModelMismatch)(options.model, effectiveProvider);
|
|
1983
1969
|
}
|
|
1984
1970
|
llmKey = options.key;
|
|
1985
1971
|
llmProvider = effectiveProvider;
|
|
@@ -1990,17 +1976,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1990
1976
|
(0, llm_1.validateProvider)(effectiveProvider);
|
|
1991
1977
|
providersToCheck = [effectiveProvider];
|
|
1992
1978
|
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
|
-
}
|
|
1979
|
+
(0, llm_1.warnProviderModelMismatch)(options.model, effectiveProvider);
|
|
2004
1980
|
}
|
|
2005
1981
|
}
|
|
2006
1982
|
const detected = await (0, llm_1.detectLlmKey)(providersToCheck, resolved);
|
|
@@ -2075,7 +2051,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2075
2051
|
}
|
|
2076
2052
|
if (options.data && filePaths.length > 0) {
|
|
2077
2053
|
// Merge file content into --data
|
|
2078
|
-
const resolvedBody = await resolveJsonBody(options.data);
|
|
2054
|
+
const resolvedBody = await (0, json_input_1.resolveJsonBody)(options.data);
|
|
2079
2055
|
const bodyObj = JSON.parse(resolvedBody);
|
|
2080
2056
|
if (cloudEngine !== 'code_runtime') {
|
|
2081
2057
|
const fieldName = resolveFileField(options.fileField, agentMeta.input_schema);
|
|
@@ -2123,7 +2099,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2123
2099
|
}
|
|
2124
2100
|
}
|
|
2125
2101
|
else if (options.data) {
|
|
2126
|
-
const resolvedBody = await resolveJsonBody(options.data);
|
|
2102
|
+
const resolvedBody = await (0, json_input_1.resolveJsonBody)(options.data);
|
|
2127
2103
|
warnIfLocalPathReference(resolvedBody);
|
|
2128
2104
|
const parsedBody = JSON.parse(resolvedBody);
|
|
2129
2105
|
warnInputSchemaErrors(parsedBody, agentMeta.input_schema);
|
|
@@ -2579,7 +2555,22 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2579
2555
|
process.stdout.write(`${payload}\n`);
|
|
2580
2556
|
return;
|
|
2581
2557
|
}
|
|
2582
|
-
|
|
2558
|
+
// In verbose mode, strip stdout/stderr from the JSON payload since they'll
|
|
2559
|
+
// be displayed in dedicated colored sections below (avoids duplication)
|
|
2560
|
+
if (options.verbose && typeof payload === 'object' && payload !== null && 'metadata' in payload) {
|
|
2561
|
+
const payloadObj = payload;
|
|
2562
|
+
const meta = payloadObj.metadata;
|
|
2563
|
+
if (meta && (meta.stdout || meta.stderr)) {
|
|
2564
|
+
const { stdout: _s, stderr: _e, ...cleanMeta } = meta;
|
|
2565
|
+
(0, output_1.printJson)({ ...payloadObj, metadata: cleanMeta });
|
|
2566
|
+
}
|
|
2567
|
+
else {
|
|
2568
|
+
(0, output_1.printJson)(payload);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
else {
|
|
2572
|
+
(0, output_1.printJson)(payload);
|
|
2573
|
+
}
|
|
2583
2574
|
// Display timing metadata on stderr (non-json mode only)
|
|
2584
2575
|
if (typeof payload === 'object' && payload !== null && 'metadata' in payload) {
|
|
2585
2576
|
const meta = payload.metadata;
|
|
@@ -2591,7 +2582,14 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2591
2582
|
if (stderr) {
|
|
2592
2583
|
process.stderr.write(chalk_1.default.bold.yellow('\n--- stderr ---') + '\n' + stderr + '\n');
|
|
2593
2584
|
}
|
|
2594
|
-
|
|
2585
|
+
// For code_runtime agents, stdout IS the data — skip if it would
|
|
2586
|
+
// duplicate what's already visible in the JSON data field
|
|
2587
|
+
const dataStr = typeof payload.data === 'string'
|
|
2588
|
+
? payload.data
|
|
2589
|
+
: JSON.stringify(payload.data);
|
|
2590
|
+
const stdoutDuplicatesData = isCodeRuntimeAgent && stdout && dataStr &&
|
|
2591
|
+
stdout.trim() === dataStr.trim();
|
|
2592
|
+
if (stdout && !stdoutDuplicatesData) {
|
|
2595
2593
|
process.stderr.write(chalk_1.default.bold.cyan('\n--- stdout ---') + '\n' + stdout + '\n');
|
|
2596
2594
|
}
|
|
2597
2595
|
if (!stderr && !stdout) {
|
|
@@ -2637,16 +2635,13 @@ async function executeLocal(agentRef, args, options) {
|
|
|
2637
2635
|
options.input = JSON.stringify({ path: options.path });
|
|
2638
2636
|
}
|
|
2639
2637
|
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`);
|
|
2638
|
+
(0, llm_1.warnProviderModelMismatch)(options.model, options.provider);
|
|
2639
|
+
}
|
|
2640
|
+
else if (options.model && !options.provider) {
|
|
2641
|
+
const detected = (0, llm_1.detectProviderFromModel)(options.model);
|
|
2642
|
+
if (detected) {
|
|
2643
|
+
options.provider = detected;
|
|
2644
|
+
process.stderr.write(`Auto-detected provider: ${detected} (from model '${options.model}')\n\n`);
|
|
2650
2645
|
}
|
|
2651
2646
|
}
|
|
2652
2647
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
@@ -2694,7 +2689,7 @@ async function executeLocal(agentRef, args, options) {
|
|
|
2694
2689
|
return;
|
|
2695
2690
|
}
|
|
2696
2691
|
// Resolve @file.json / @- stdin syntax before parsing
|
|
2697
|
-
const resolvedInput = await resolveJsonBody(options.input);
|
|
2692
|
+
const resolvedInput = await (0, json_input_1.resolveJsonBody)(options.input);
|
|
2698
2693
|
let agentInputData;
|
|
2699
2694
|
try {
|
|
2700
2695
|
agentInputData = JSON.parse(resolvedInput);
|
|
@@ -11,6 +11,7 @@ const promises_1 = __importDefault(require("readline/promises"));
|
|
|
11
11
|
const config_1 = require("../lib/config");
|
|
12
12
|
const api_1 = require("../lib/api");
|
|
13
13
|
const errors_1 = require("../lib/errors");
|
|
14
|
+
const json_input_1 = require("../lib/json-input");
|
|
14
15
|
const output_1 = require("../lib/output");
|
|
15
16
|
const agent_ref_1 = require("../lib/agent-ref");
|
|
16
17
|
const api_2 = require("../lib/api");
|
|
@@ -187,12 +188,8 @@ function registerScheduleCommand(program) {
|
|
|
187
188
|
const rawInput = options.data ?? options.input;
|
|
188
189
|
let inputData;
|
|
189
190
|
if (rawInput) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
throw new errors_1.CliError('Invalid JSON in --data. Use single quotes: --data \'{"key": "value"}\'');
|
|
195
|
-
}
|
|
191
|
+
const resolved = await (0, json_input_1.resolveJsonBody)(rawInput);
|
|
192
|
+
inputData = JSON.parse(resolved);
|
|
196
193
|
}
|
|
197
194
|
const scheduleType = options.webhook ? 'webhook' : 'cron';
|
|
198
195
|
const body = {
|
|
@@ -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) {
|
package/dist/commands/trace.js
CHANGED
|
@@ -105,6 +105,10 @@ function registerTraceCommand(program) {
|
|
|
105
105
|
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
106
106
|
}
|
|
107
107
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
108
|
+
// Accept req_xxx format (gateway request_id shown in run output)
|
|
109
|
+
if (/^req_[0-9a-f]+$/i.test(runId)) {
|
|
110
|
+
runId = runId.slice(4);
|
|
111
|
+
}
|
|
108
112
|
// Resolve short run IDs
|
|
109
113
|
let resolvedRunId = runId;
|
|
110
114
|
if (isUuid(runId)) {
|
|
@@ -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/api.js
CHANGED
|
@@ -380,15 +380,18 @@ async function getAgentWithFallback(config, org, agentName, version, workspaceId
|
|
|
380
380
|
if (!config.apiKey) {
|
|
381
381
|
throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
|
|
382
382
|
}
|
|
383
|
-
|
|
384
|
-
if (userOrg.slug !== org) {
|
|
385
|
-
throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
|
|
386
|
-
}
|
|
383
|
+
// Try authenticated lookup in the resolved workspace context
|
|
387
384
|
const myAgent = await getMyAgent(config, agentName, version, workspaceId);
|
|
388
|
-
if (
|
|
389
|
-
|
|
385
|
+
if (myAgent)
|
|
386
|
+
return myAgent;
|
|
387
|
+
// Fallback: if workspace was specified, also check personal org —
|
|
388
|
+
// handles cross-workspace lookups (e.g., team context looking up personal agent)
|
|
389
|
+
if (workspaceId) {
|
|
390
|
+
const personalAgent = await getMyAgent(config, agentName, version);
|
|
391
|
+
if (personalAgent)
|
|
392
|
+
return personalAgent;
|
|
390
393
|
}
|
|
391
|
-
|
|
394
|
+
throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
|
|
392
395
|
}
|
|
393
396
|
/**
|
|
394
397
|
* Resolve a workspace ID from an org slug.
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.discoverAgents = discoverAgents;
|
|
7
7
|
exports.topoSort = topoSort;
|
|
8
8
|
exports.formatPublishPlan = formatPublishPlan;
|
|
9
|
+
exports.formatDryRunSummary = formatDryRunSummary;
|
|
9
10
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
11
12
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -221,3 +222,75 @@ function formatPublishPlan(sorted, orgSlug) {
|
|
|
221
222
|
lines.push('');
|
|
222
223
|
return lines.join('\n');
|
|
223
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Format an enhanced dry-run summary for --all --dry-run.
|
|
227
|
+
* Shows publish ordering, local/external dependencies, and graph health.
|
|
228
|
+
*/
|
|
229
|
+
function formatDryRunSummary(sorted, orgSlug) {
|
|
230
|
+
const lines = [];
|
|
231
|
+
const localNames = new Set(sorted.map(a => a.name));
|
|
232
|
+
// Collect external deps (referenced but not in the project)
|
|
233
|
+
const externalDeps = new Map(); // ref → [agent names that reference it]
|
|
234
|
+
for (const agent of sorted) {
|
|
235
|
+
for (const ref of agent.dependencyRefs) {
|
|
236
|
+
const depName = ref.includes('/') ? ref.split('/')[1] : ref;
|
|
237
|
+
if (!localNames.has(depName)) {
|
|
238
|
+
const consumers = externalDeps.get(ref) || [];
|
|
239
|
+
consumers.push(agent.name);
|
|
240
|
+
externalDeps.set(ref, consumers);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Publish order table
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push(chalk_1.default.bold(` Publish order (${sorted.length} agent${sorted.length === 1 ? '' : 's'}):`));
|
|
247
|
+
lines.push('');
|
|
248
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
249
|
+
const agent = sorted[i];
|
|
250
|
+
const type = agent.isSkill ? 'skill' : 'agent';
|
|
251
|
+
const prefix = orgSlug ? `${orgSlug}/` : '';
|
|
252
|
+
// Separate local and external deps for clarity
|
|
253
|
+
const localDeps = [];
|
|
254
|
+
const extDeps = [];
|
|
255
|
+
for (const ref of agent.dependencyRefs) {
|
|
256
|
+
const depName = ref.includes('/') ? ref.split('/')[1] : ref;
|
|
257
|
+
if (localNames.has(depName)) {
|
|
258
|
+
localDeps.push(depName);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
extDeps.push(ref);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
let depInfo = '';
|
|
265
|
+
if (localDeps.length > 0 || extDeps.length > 0) {
|
|
266
|
+
const parts = [];
|
|
267
|
+
if (localDeps.length > 0)
|
|
268
|
+
parts.push(localDeps.join(', '));
|
|
269
|
+
if (extDeps.length > 0)
|
|
270
|
+
parts.push(extDeps.map(d => `${d} ${chalk_1.default.yellow('(external)')}`).join(', '));
|
|
271
|
+
depInfo = ` ${chalk_1.default.gray('→')} ${parts.join(', ')}`;
|
|
272
|
+
}
|
|
273
|
+
lines.push(` ${chalk_1.default.bold(`${i + 1}.`)} ${prefix}${agent.name} ${chalk_1.default.gray(`[${type}]`)}${depInfo}`);
|
|
274
|
+
lines.push(` ${chalk_1.default.gray(agent.dirName + '/')}`);
|
|
275
|
+
}
|
|
276
|
+
// External dependencies section
|
|
277
|
+
if (externalDeps.size > 0) {
|
|
278
|
+
lines.push('');
|
|
279
|
+
lines.push(chalk_1.default.yellow(` External dependencies (must already be published):`));
|
|
280
|
+
for (const [ref, consumers] of externalDeps) {
|
|
281
|
+
lines.push(` ${ref} ${chalk_1.default.gray(`← ${consumers.join(', ')}`)}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Summary
|
|
285
|
+
lines.push('');
|
|
286
|
+
const skillCount = sorted.filter(a => a.isSkill).length;
|
|
287
|
+
const agentCount = sorted.length - skillCount;
|
|
288
|
+
const parts = [];
|
|
289
|
+
if (agentCount > 0)
|
|
290
|
+
parts.push(`${agentCount} agent${agentCount === 1 ? '' : 's'}`);
|
|
291
|
+
if (skillCount > 0)
|
|
292
|
+
parts.push(`${skillCount} skill${skillCount === 1 ? '' : 's'}`);
|
|
293
|
+
lines.push(chalk_1.default.green(` ✓ ${parts.join(', ')} ready to publish (no circular dependencies)`));
|
|
294
|
+
lines.push('');
|
|
295
|
+
return lines.join('\n');
|
|
296
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveJsonBody = resolveJsonBody;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
async function readStdin() {
|
|
10
|
+
if (process.stdin.isTTY)
|
|
11
|
+
return null;
|
|
12
|
+
const chunks = [];
|
|
13
|
+
for await (const chunk of process.stdin) {
|
|
14
|
+
chunks.push(Buffer.from(chunk));
|
|
15
|
+
}
|
|
16
|
+
if (!chunks.length)
|
|
17
|
+
return null;
|
|
18
|
+
return Buffer.concat(chunks);
|
|
19
|
+
}
|
|
20
|
+
async function validateFilePath(filePath) {
|
|
21
|
+
const stat = await promises_1.default.stat(filePath);
|
|
22
|
+
if (stat.isDirectory()) {
|
|
23
|
+
throw new errors_1.CliError(`Expected a file but got a directory: ${filePath}\n\n` +
|
|
24
|
+
`Provide a JSON file path, e.g. --data @input.json`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a --data value to a validated JSON string.
|
|
29
|
+
*
|
|
30
|
+
* Supports three input forms:
|
|
31
|
+
* - Plain JSON string: '{"key":"value"}'
|
|
32
|
+
* - File reference: @input.json
|
|
33
|
+
* - Stdin pipe: @-
|
|
34
|
+
*/
|
|
35
|
+
async function resolveJsonBody(input) {
|
|
36
|
+
let raw = input;
|
|
37
|
+
if (input.startsWith('@')) {
|
|
38
|
+
const source = input.slice(1);
|
|
39
|
+
if (!source) {
|
|
40
|
+
throw new errors_1.CliError('Invalid JSON input. Use a JSON string or @file.');
|
|
41
|
+
}
|
|
42
|
+
if (source === '-') {
|
|
43
|
+
const stdinData = await readStdin();
|
|
44
|
+
if (!stdinData) {
|
|
45
|
+
throw new errors_1.CliError('No stdin provided for JSON input.');
|
|
46
|
+
}
|
|
47
|
+
raw = stdinData.toString('utf8');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
await validateFilePath(source);
|
|
51
|
+
raw = await promises_1.default.readFile(source, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return JSON.stringify(JSON.parse(raw));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw (0, errors_1.jsonInputError)('data');
|
|
59
|
+
}
|
|
60
|
+
}
|
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