@orchagent/cli 0.3.90 → 0.3.92
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/completion.js +379 -0
- package/dist/commands/dag.js +16 -7
- package/dist/commands/delete.js +9 -4
- package/dist/commands/diff-format.js +300 -0
- package/dist/commands/diff.js +12 -131
- package/dist/commands/estimate.js +5 -2
- package/dist/commands/fork.js +7 -1
- package/dist/commands/health.js +90 -7
- package/dist/commands/index.js +6 -0
- package/dist/commands/info.js +8 -1
- package/dist/commands/init-wizard.js +225 -0
- package/dist/commands/init.js +109 -3
- package/dist/commands/login.js +8 -0
- package/dist/commands/logs.js +17 -7
- package/dist/commands/metrics.js +16 -7
- package/dist/commands/publish.js +74 -66
- package/dist/commands/replay.js +16 -7
- package/dist/commands/run.js +158 -33
- package/dist/commands/scaffold.js +213 -0
- package/dist/commands/schedule.js +112 -11
- package/dist/commands/secrets.js +16 -7
- package/dist/commands/service.js +16 -7
- package/dist/commands/skill.js +84 -8
- package/dist/commands/templates/cron-job.js +259 -0
- package/dist/commands/trace.js +16 -7
- package/dist/commands/tree.js +7 -1
- package/dist/commands/update.js +46 -9
- package/dist/commands/validate.js +264 -0
- package/dist/commands/workspace.js +16 -7
- package/dist/lib/agent-ref.js +4 -1
- package/dist/lib/browser-auth.js +1 -0
- package/dist/lib/scaffold-orchestration.js +237 -0
- package/dist/lib/validate.js +478 -0
- package/package.json +1 -1
package/dist/commands/publish.js
CHANGED
|
@@ -584,7 +584,7 @@ function registerPublishCommand(program) {
|
|
|
584
584
|
.option('--skills-locked', 'Lock default skills (callers cannot override via headers)')
|
|
585
585
|
.option('--docker', 'Include Dockerfile for custom environment (builds E2B template)')
|
|
586
586
|
.option('--local-download', 'Allow users to download and run locally (default: server-only)')
|
|
587
|
-
.option('--no-required-secrets', '
|
|
587
|
+
.option('--no-required-secrets', '(deprecated) No longer needed — required_secrets defaults to []')
|
|
588
588
|
.option('--all', 'Publish all agents in subdirectories (dependency order)')
|
|
589
589
|
.action(async (options) => {
|
|
590
590
|
const cwd = process.cwd();
|
|
@@ -718,44 +718,62 @@ function registerPublishCommand(program) {
|
|
|
718
718
|
}
|
|
719
719
|
throw new errors_1.CliError(`Failed to read orchagent.json: ${err}`);
|
|
720
720
|
}
|
|
721
|
+
// UX-1: Collect validation errors and report them all at once
|
|
722
|
+
const validationErrors = [];
|
|
721
723
|
// Validate manifest
|
|
722
724
|
if (!manifest.name) {
|
|
723
|
-
|
|
725
|
+
validationErrors.push('orchagent.json must have name');
|
|
724
726
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
727
|
+
else {
|
|
728
|
+
// Validate agent name format (must match gateway rules)
|
|
729
|
+
const agentNameRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
730
|
+
const agentName = manifest.name;
|
|
731
|
+
if (agentName.length < 2 || agentName.length > 50) {
|
|
732
|
+
validationErrors.push('Agent name must be 2-50 characters');
|
|
733
|
+
}
|
|
734
|
+
if (agentName !== agentName.toLowerCase()) {
|
|
735
|
+
validationErrors.push('Agent name must be lowercase');
|
|
736
|
+
}
|
|
737
|
+
if (agentName.length > 1 && !agentNameRegex.test(agentName)) {
|
|
738
|
+
validationErrors.push('Agent name must contain only lowercase letters, numbers, and hyphens, and must start/end with a letter or number');
|
|
739
|
+
}
|
|
740
|
+
if (agentName.includes('--')) {
|
|
741
|
+
validationErrors.push('Agent name must not contain consecutive hyphens');
|
|
742
|
+
}
|
|
739
743
|
}
|
|
740
744
|
const { canonicalType, rawType } = canonicalizeManifestType(manifest.type);
|
|
741
745
|
const runMode = normalizeRunMode(manifest.run_mode);
|
|
742
746
|
const executionEngine = inferExecutionEngineFromManifest(manifest, rawType);
|
|
743
747
|
const callable = manifest.callable !== undefined ? Boolean(manifest.callable) : true;
|
|
744
748
|
if (canonicalType === 'skill') {
|
|
745
|
-
throw new errors_1.CliError(
|
|
749
|
+
throw new errors_1.CliError('Skills use a different publishing format (SKILL.md with YAML front matter).\n\n' +
|
|
750
|
+
' To publish a skill:\n' +
|
|
751
|
+
' 1. Run: orchagent skill create ' + (manifest.name || '<name>') + '\n' +
|
|
752
|
+
' 2. Edit the generated SKILL.md with your skill content\n' +
|
|
753
|
+
' 3. Run: orchagent publish\n\n' +
|
|
754
|
+
' orchagent.json is not used for skills — SKILL.md replaces it entirely.\n' +
|
|
755
|
+
' See: https://orchagent.io/docs/skills');
|
|
746
756
|
}
|
|
747
757
|
if (runMode === 'always_on' && executionEngine === 'direct_llm') {
|
|
748
|
-
|
|
758
|
+
validationErrors.push('run_mode=always_on requires runtime.command or loop configuration');
|
|
749
759
|
}
|
|
750
760
|
if (manifest.timeout_seconds !== undefined) {
|
|
751
761
|
if (!Number.isInteger(manifest.timeout_seconds) || manifest.timeout_seconds <= 0) {
|
|
752
|
-
|
|
762
|
+
validationErrors.push('timeout_seconds must be a positive integer');
|
|
753
763
|
}
|
|
754
764
|
}
|
|
755
765
|
// Warn about deprecated prompt field
|
|
756
766
|
if (manifest.prompt) {
|
|
757
767
|
process.stderr.write(chalk_1.default.yellow('Warning: "prompt" field in orchagent.json is ignored. Use prompt.md file instead.\n'));
|
|
758
768
|
}
|
|
769
|
+
// UX-9: Warn about model (singular) vs default_models
|
|
770
|
+
if (manifest.model && !manifest.default_models) {
|
|
771
|
+
const modelVal = manifest.model;
|
|
772
|
+
process.stderr.write(chalk_1.default.yellow(`\nWarning: "model" field in orchagent.json is not recognized.\n` +
|
|
773
|
+
` Use "default_models" instead to set per-provider defaults:\n\n` +
|
|
774
|
+
` ${chalk_1.default.cyan(`"default_models": { "anthropic": "${modelVal}" }`)}\n\n` +
|
|
775
|
+
` The model resolution order is: caller --model flag → agent default_models → platform default.\n\n`));
|
|
776
|
+
}
|
|
759
777
|
// Auto-migrate inline schemas to schema.json
|
|
760
778
|
const schemaPath = path_1.default.join(cwd, 'schema.json');
|
|
761
779
|
let schemaFileExists = false;
|
|
@@ -789,20 +807,8 @@ function registerPublishCommand(program) {
|
|
|
789
807
|
const manifestFields = ['manifest_version', 'dependencies', 'max_hops', 'timeout_ms', 'per_call_downstream_cap'];
|
|
790
808
|
const misplacedFields = manifestFields.filter(f => f in manifest && !manifest.manifest);
|
|
791
809
|
if (misplacedFields.length > 0) {
|
|
792
|
-
|
|
793
|
-
`These must be nested under a "manifest" key.
|
|
794
|
-
` {\n` +
|
|
795
|
-
` "name": "${manifest.name}",\n` +
|
|
796
|
-
` "type": "${manifest.type || 'agent'}",\n` +
|
|
797
|
-
` "manifest": {\n` +
|
|
798
|
-
` "manifest_version": 1,\n` +
|
|
799
|
-
` "dependencies": [...],\n` +
|
|
800
|
-
` "max_hops": 2,\n` +
|
|
801
|
-
` "timeout_ms": 60000,\n` +
|
|
802
|
-
` "per_call_downstream_cap": 50\n` +
|
|
803
|
-
` }\n` +
|
|
804
|
-
` }\n\n` +
|
|
805
|
-
`See docs/manifest.md for details.`);
|
|
810
|
+
validationErrors.push(`Found manifest fields (${misplacedFields.join(', ')}) at top level of orchagent.json. ` +
|
|
811
|
+
`These must be nested under a "manifest" key. See docs/manifest.md for details.`);
|
|
806
812
|
}
|
|
807
813
|
// Read prompt for LLM-driven engines (direct_llm + managed_loop).
|
|
808
814
|
let prompt;
|
|
@@ -813,11 +819,11 @@ function registerPublishCommand(program) {
|
|
|
813
819
|
}
|
|
814
820
|
catch (err) {
|
|
815
821
|
if (err.code === 'ENOENT') {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
822
|
+
validationErrors.push('No prompt.md found. Create a prompt.md file with your prompt template.');
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
throw err;
|
|
819
826
|
}
|
|
820
|
-
throw err;
|
|
821
827
|
}
|
|
822
828
|
}
|
|
823
829
|
// Validate managed-loop specific fields + normalize loop payload
|
|
@@ -825,7 +831,7 @@ function registerPublishCommand(program) {
|
|
|
825
831
|
if (executionEngine === 'managed_loop') {
|
|
826
832
|
if (manifest.max_turns !== undefined) {
|
|
827
833
|
if (typeof manifest.max_turns !== 'number' || manifest.max_turns < 1 || manifest.max_turns > 50) {
|
|
828
|
-
|
|
834
|
+
validationErrors.push('max_turns must be a number between 1 and 50');
|
|
829
835
|
}
|
|
830
836
|
}
|
|
831
837
|
const providedLoop = manifest.loop && typeof manifest.loop === 'object'
|
|
@@ -849,17 +855,17 @@ function registerPublishCommand(program) {
|
|
|
849
855
|
const seenNames = new Set();
|
|
850
856
|
for (const tool of mergedTools) {
|
|
851
857
|
if (!tool.name || !tool.command) {
|
|
852
|
-
|
|
853
|
-
`Found: ${JSON.stringify(tool)}`);
|
|
854
|
-
}
|
|
855
|
-
if (reservedNames.has(tool.name)) {
|
|
856
|
-
throw new errors_1.CliError(`Custom tool '${tool.name}' conflicts with a built-in tool name.\n` +
|
|
857
|
-
`Reserved names: ${[...reservedNames].join(', ')}`);
|
|
858
|
+
validationErrors.push(`Invalid custom_tool: each tool must have 'name' and 'command' fields. Found: ${JSON.stringify(tool)}`);
|
|
858
859
|
}
|
|
859
|
-
|
|
860
|
-
|
|
860
|
+
else {
|
|
861
|
+
if (reservedNames.has(tool.name)) {
|
|
862
|
+
validationErrors.push(`Custom tool '${tool.name}' conflicts with a built-in tool name. Reserved names: ${[...reservedNames].join(', ')}`);
|
|
863
|
+
}
|
|
864
|
+
if (seenNames.has(tool.name)) {
|
|
865
|
+
validationErrors.push(`Duplicate custom tool name: '${tool.name}'`);
|
|
866
|
+
}
|
|
867
|
+
seenNames.add(tool.name);
|
|
861
868
|
}
|
|
862
|
-
seenNames.add(tool.name);
|
|
863
869
|
}
|
|
864
870
|
}
|
|
865
871
|
if (!manifest.supported_providers) {
|
|
@@ -921,11 +927,13 @@ function registerPublishCommand(program) {
|
|
|
921
927
|
}
|
|
922
928
|
if (!options.url) {
|
|
923
929
|
if (!bundleEntrypoint) {
|
|
924
|
-
|
|
930
|
+
validationErrors.push('Tool requires either --url <url> or an entry point file (main.py, app.py, index.js, etc.)');
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
shouldUploadBundle = true;
|
|
934
|
+
agentUrl = 'https://tool.internal';
|
|
935
|
+
process.stdout.write(`Detected code runtime entrypoint: ${bundleEntrypoint}\n`);
|
|
925
936
|
}
|
|
926
|
-
shouldUploadBundle = true;
|
|
927
|
-
agentUrl = 'https://tool.internal';
|
|
928
|
-
process.stdout.write(`Detected code runtime entrypoint: ${bundleEntrypoint}\n`);
|
|
929
937
|
}
|
|
930
938
|
let runtimeCommand = manifest.runtime?.command?.trim() || '';
|
|
931
939
|
if (!runtimeCommand && manifest.run_command?.trim()) {
|
|
@@ -940,7 +948,7 @@ function registerPublishCommand(program) {
|
|
|
940
948
|
agentUrl = agentUrl || 'https://prompt-agent.internal';
|
|
941
949
|
}
|
|
942
950
|
if (options.docker && executionEngine !== 'code_runtime') {
|
|
943
|
-
|
|
951
|
+
validationErrors.push('--docker is only supported for code runtime agents');
|
|
944
952
|
}
|
|
945
953
|
// Get org info (workspace-aware — returns workspace org if workspace is active)
|
|
946
954
|
const org = await (0, api_1.getOrg)(config, workspaceId);
|
|
@@ -982,23 +990,23 @@ function registerPublishCommand(program) {
|
|
|
982
990
|
` the field to use the default) and republish each dependency.\n\n`);
|
|
983
991
|
}
|
|
984
992
|
}
|
|
985
|
-
//
|
|
993
|
+
// UX-2: Default required_secrets to [] when omitted for tool/agent types.
|
|
986
994
|
// Prompt and skill types are exempt (prompt agents get LLM keys from platform,
|
|
987
995
|
// skills don't run standalone).
|
|
988
|
-
// An explicit empty array (required_secrets: []) is a valid declaration
|
|
989
|
-
// meaning "this agent deliberately needs no secrets."
|
|
990
|
-
// Runs before dry-run so --dry-run catches the same errors as real publish (BUG-11).
|
|
991
996
|
if ((canonicalType === 'tool' || canonicalType === 'agent') &&
|
|
992
|
-
manifest.required_secrets === undefined
|
|
993
|
-
|
|
994
|
-
process.stderr.write(chalk_1.default.
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
const
|
|
997
|
+
manifest.required_secrets === undefined) {
|
|
998
|
+
manifest.required_secrets = [];
|
|
999
|
+
process.stderr.write(chalk_1.default.dim(` ℹ No required_secrets declared — defaulting to [] (no secrets needed)\n`));
|
|
1000
|
+
}
|
|
1001
|
+
// UX-1: Report all validation errors at once
|
|
1002
|
+
if (validationErrors.length > 0) {
|
|
1003
|
+
if (validationErrors.length === 1) {
|
|
1004
|
+
throw new errors_1.CliError(validationErrors[0]);
|
|
1005
|
+
}
|
|
1006
|
+
const numbered = validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join('\n');
|
|
1007
|
+
process.stderr.write(chalk_1.default.red(`\nFound ${validationErrors.length} validation errors:\n\n`) +
|
|
1008
|
+
numbered + '\n');
|
|
1009
|
+
const err = new errors_1.CliError(`Found ${validationErrors.length} validation errors:\n${numbered}`, errors_1.ExitCodes.INVALID_INPUT);
|
|
1002
1010
|
err.displayed = true;
|
|
1003
1011
|
throw err;
|
|
1004
1012
|
}
|
package/dist/commands/replay.js
CHANGED
|
@@ -15,15 +15,24 @@ const output_1 = require("../lib/output");
|
|
|
15
15
|
async function resolveWorkspaceId(config, slug) {
|
|
16
16
|
const configFile = await (0, config_1.loadConfig)();
|
|
17
17
|
const targetSlug = slug ?? configFile.workspace;
|
|
18
|
-
if (!targetSlug) {
|
|
19
|
-
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
20
|
-
}
|
|
21
18
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
if (targetSlug) {
|
|
20
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
21
|
+
if (!workspace) {
|
|
22
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
23
|
+
}
|
|
24
|
+
return workspace.id;
|
|
25
|
+
}
|
|
26
|
+
// No workspace specified — auto-select if user has exactly one
|
|
27
|
+
if (response.workspaces.length === 0) {
|
|
28
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
29
|
+
}
|
|
30
|
+
if (response.workspaces.length === 1) {
|
|
31
|
+
return response.workspaces[0].id;
|
|
25
32
|
}
|
|
26
|
-
|
|
33
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
34
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
35
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
27
36
|
}
|
|
28
37
|
function isUuid(value) {
|
|
29
38
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
package/dist/commands/run.js
CHANGED
|
@@ -37,7 +37,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.localCommandForEntrypoint = localCommandForEntrypoint;
|
|
40
|
+
exports.inferFileField = inferFileField;
|
|
40
41
|
exports.validateInputSchema = validateInputSchema;
|
|
42
|
+
exports.tryParseJsonObject = tryParseJsonObject;
|
|
41
43
|
exports.isKeyedFileArg = isKeyedFileArg;
|
|
42
44
|
exports.readKeyedFiles = readKeyedFiles;
|
|
43
45
|
exports.mountDirectory = mountDirectory;
|
|
@@ -155,6 +157,13 @@ function warnIfLocalPathReference(jsonBody) {
|
|
|
155
157
|
// If parsing fails, skip the warning
|
|
156
158
|
}
|
|
157
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Infer which schema field should receive file content.
|
|
162
|
+
*
|
|
163
|
+
* Returns the detected field name, or null when the schema has properties
|
|
164
|
+
* but none of the heuristics can determine the right field.
|
|
165
|
+
* Returns 'content' (default) only when there is no schema to check against.
|
|
166
|
+
*/
|
|
158
167
|
function inferFileField(inputSchema) {
|
|
159
168
|
if (!inputSchema || typeof inputSchema !== 'object')
|
|
160
169
|
return 'content';
|
|
@@ -162,6 +171,7 @@ function inferFileField(inputSchema) {
|
|
|
162
171
|
if (!props || typeof props !== 'object')
|
|
163
172
|
return 'content';
|
|
164
173
|
const properties = props;
|
|
174
|
+
// 1. Check well-known content field names
|
|
165
175
|
for (const field of CONTENT_FIELD_NAMES) {
|
|
166
176
|
if (properties[field] && properties[field].type === 'string')
|
|
167
177
|
return field;
|
|
@@ -170,12 +180,41 @@ function inferFileField(inputSchema) {
|
|
|
170
180
|
const stringProps = Object.entries(properties)
|
|
171
181
|
.filter(([, v]) => v.type === 'string')
|
|
172
182
|
.map(([k]) => k);
|
|
183
|
+
// 2. Only one string property in the schema — use it
|
|
173
184
|
if (stringProps.length === 1)
|
|
174
185
|
return stringProps[0];
|
|
186
|
+
// 3. Only one required string property — use it
|
|
175
187
|
const requiredStrings = stringProps.filter(k => required.includes(k));
|
|
176
188
|
if (requiredStrings.length === 1)
|
|
177
189
|
return requiredStrings[0];
|
|
178
|
-
return
|
|
190
|
+
// 4. Schema exists but detection is ambiguous — return null so callers
|
|
191
|
+
// can surface a clear error instead of silently using the wrong field
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Resolve the file field name, throwing a clear error when auto-detection fails.
|
|
196
|
+
* Used at call sites where a file is being injected into a JSON payload.
|
|
197
|
+
*/
|
|
198
|
+
function resolveFileField(fileFieldOption, inputSchema) {
|
|
199
|
+
if (fileFieldOption)
|
|
200
|
+
return fileFieldOption;
|
|
201
|
+
const detected = inferFileField(inputSchema);
|
|
202
|
+
if (detected !== null)
|
|
203
|
+
return detected;
|
|
204
|
+
// Detection failed — build a helpful error message
|
|
205
|
+
const props = inputSchema?.properties;
|
|
206
|
+
const stringFields = props
|
|
207
|
+
? Object.entries(props).filter(([, v]) => v.type === 'string').map(([k]) => k)
|
|
208
|
+
: [];
|
|
209
|
+
const fieldList = stringFields.length > 0
|
|
210
|
+
? `String fields in schema: ${stringFields.map(f => `"${f}"`).join(', ')}`
|
|
211
|
+
: 'No string fields found in schema';
|
|
212
|
+
throw new errors_1.CliError(`Could not determine which input field to use for file content.\n\n` +
|
|
213
|
+
`${fieldList}\n\n` +
|
|
214
|
+
`Specify the field explicitly:\n` +
|
|
215
|
+
` orch run <agent> --file-field <field> input.json\n` +
|
|
216
|
+
` orch run <agent> --data @input.json\n` +
|
|
217
|
+
` orch run <agent> --file <field>=input.json`);
|
|
179
218
|
}
|
|
180
219
|
function applySchemaDefaults(body, schema) {
|
|
181
220
|
if (!schema)
|
|
@@ -264,6 +303,26 @@ async function readStdin() {
|
|
|
264
303
|
return null;
|
|
265
304
|
return Buffer.concat(chunks);
|
|
266
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Try to parse a Buffer as a JSON object (not array).
|
|
308
|
+
* Returns the parsed object on success, null on failure or if the content
|
|
309
|
+
* is not a JSON object (e.g. array, string, number).
|
|
310
|
+
*/
|
|
311
|
+
function tryParseJsonObject(buf) {
|
|
312
|
+
const text = buf.toString('utf8').trim();
|
|
313
|
+
if (!text.startsWith('{'))
|
|
314
|
+
return null;
|
|
315
|
+
try {
|
|
316
|
+
const parsed = JSON.parse(text);
|
|
317
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
318
|
+
return parsed;
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
267
326
|
async function buildMultipartBody(filePaths, metadata) {
|
|
268
327
|
if (!filePaths || filePaths.length === 0) {
|
|
269
328
|
const stdinData = await readStdin();
|
|
@@ -2016,7 +2075,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2016
2075
|
const resolvedBody = await resolveJsonBody(options.data);
|
|
2017
2076
|
const bodyObj = JSON.parse(resolvedBody);
|
|
2018
2077
|
if (cloudEngine !== 'code_runtime') {
|
|
2019
|
-
const fieldName = options.fileField
|
|
2078
|
+
const fieldName = resolveFileField(options.fileField, agentMeta.input_schema);
|
|
2020
2079
|
if (filePaths.length === 1) {
|
|
2021
2080
|
await validateFilePath(filePaths[0]);
|
|
2022
2081
|
bodyObj[fieldName] = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
@@ -2072,7 +2131,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2072
2131
|
headers['Content-Type'] = 'application/json';
|
|
2073
2132
|
}
|
|
2074
2133
|
else if ((filePaths.length > 0 || options.metadata) && cloudEngine !== 'code_runtime') {
|
|
2075
|
-
const fieldName = options.fileField
|
|
2134
|
+
const fieldName = resolveFileField(options.fileField, agentMeta.input_schema);
|
|
2076
2135
|
let bodyObj = {};
|
|
2077
2136
|
if (options.metadata) {
|
|
2078
2137
|
try {
|
|
@@ -2127,13 +2186,39 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2127
2186
|
sourceLabel = multipart.sourceLabel;
|
|
2128
2187
|
}
|
|
2129
2188
|
else if (llmCredentials) {
|
|
2130
|
-
|
|
2189
|
+
// Check for piped JSON stdin to merge with credentials
|
|
2190
|
+
const stdinData = await readStdin();
|
|
2191
|
+
const stdinJson = stdinData ? tryParseJsonObject(stdinData) : null;
|
|
2192
|
+
if (stdinJson) {
|
|
2193
|
+
stdinJson.llm_credentials = llmCredentials;
|
|
2194
|
+
warnInputSchemaErrors(stdinJson, agentMeta.input_schema);
|
|
2195
|
+
body = JSON.stringify(stdinJson);
|
|
2196
|
+
sourceLabel = 'stdin';
|
|
2197
|
+
}
|
|
2198
|
+
else {
|
|
2199
|
+
body = JSON.stringify({ llm_credentials: llmCredentials });
|
|
2200
|
+
}
|
|
2131
2201
|
headers['Content-Type'] = 'application/json';
|
|
2132
2202
|
}
|
|
2133
2203
|
else {
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2204
|
+
// No --data, no --file, no --metadata — check for piped JSON stdin
|
|
2205
|
+
const stdinData = await readStdin();
|
|
2206
|
+
if (stdinData) {
|
|
2207
|
+
const stdinJson = tryParseJsonObject(stdinData);
|
|
2208
|
+
if (stdinJson) {
|
|
2209
|
+
warnInputSchemaErrors(stdinJson, agentMeta.input_schema);
|
|
2210
|
+
body = JSON.stringify(stdinJson);
|
|
2211
|
+
headers['Content-Type'] = 'application/json';
|
|
2212
|
+
sourceLabel = 'stdin';
|
|
2213
|
+
}
|
|
2214
|
+
else {
|
|
2215
|
+
// Non-JSON stdin — send as binary file attachment
|
|
2216
|
+
const form = new FormData();
|
|
2217
|
+
form.append('files[]', new Blob([new Uint8Array(stdinData)]), 'stdin');
|
|
2218
|
+
body = form;
|
|
2219
|
+
sourceLabel = 'stdin';
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2137
2222
|
}
|
|
2138
2223
|
} // end of non-injection path
|
|
2139
2224
|
const verboseQs = options.verbose ? '?verbose=true' : '';
|
|
@@ -2157,8 +2242,11 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2157
2242
|
? { spinner: null, dispose: () => { } }
|
|
2158
2243
|
: (0, spinner_1.createElapsedSpinner)(`Running ${org}/${parsed.agent}@${parsed.version}...`);
|
|
2159
2244
|
spinner?.start();
|
|
2160
|
-
// Streamed sandbox runs can take longer; use 10 min timeout.
|
|
2161
|
-
const
|
|
2245
|
+
// Streamed sandbox runs can take longer; use 10 min timeout (or --wait-timeout).
|
|
2246
|
+
const waitTimeoutSec = options.waitTimeout ? parseInt(options.waitTimeout, 10) : undefined;
|
|
2247
|
+
const timeoutMs = wantStream
|
|
2248
|
+
? (waitTimeoutSec && waitTimeoutSec > 0 ? waitTimeoutSec * 1000 : 600000)
|
|
2249
|
+
: undefined;
|
|
2162
2250
|
let response;
|
|
2163
2251
|
try {
|
|
2164
2252
|
response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
|
|
@@ -2328,38 +2416,74 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2328
2416
|
process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${parsed.agent}@${parsed.version}:\n`));
|
|
2329
2417
|
}
|
|
2330
2418
|
let progressErrorShown = false;
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2419
|
+
let streamTimedOut = false;
|
|
2420
|
+
try {
|
|
2421
|
+
for await (const { event, data } of parseSSE(response.body)) {
|
|
2422
|
+
if (event === 'progress') {
|
|
2423
|
+
try {
|
|
2424
|
+
const parsed = JSON.parse(data);
|
|
2425
|
+
renderProgress(parsed, !!options.verbose);
|
|
2426
|
+
if (parsed.type === 'error') {
|
|
2427
|
+
progressErrorShown = true;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
catch {
|
|
2431
|
+
// ignore malformed progress events
|
|
2338
2432
|
}
|
|
2339
2433
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2434
|
+
else if (event === 'result') {
|
|
2435
|
+
try {
|
|
2436
|
+
finalPayload = JSON.parse(data);
|
|
2437
|
+
}
|
|
2438
|
+
catch {
|
|
2439
|
+
finalPayload = data;
|
|
2440
|
+
}
|
|
2347
2441
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2442
|
+
else if (event === 'error') {
|
|
2443
|
+
hadError = true;
|
|
2444
|
+
try {
|
|
2445
|
+
finalPayload = JSON.parse(data);
|
|
2446
|
+
}
|
|
2447
|
+
catch {
|
|
2448
|
+
finalPayload = data;
|
|
2449
|
+
}
|
|
2350
2450
|
}
|
|
2351
2451
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2452
|
+
}
|
|
2453
|
+
catch (streamErr) {
|
|
2454
|
+
// BUG-6: Detect timeout/abort errors — the server-side job may still be running.
|
|
2455
|
+
const errName = streamErr instanceof DOMException ? streamErr.name
|
|
2456
|
+
: streamErr instanceof Error ? streamErr.name
|
|
2457
|
+
: '';
|
|
2458
|
+
if (errName === 'TimeoutError' || errName === 'AbortError') {
|
|
2459
|
+
streamTimedOut = true;
|
|
2460
|
+
}
|
|
2461
|
+
else {
|
|
2462
|
+
throw streamErr;
|
|
2360
2463
|
}
|
|
2361
2464
|
}
|
|
2362
2465
|
process.stderr.write('\n');
|
|
2466
|
+
// BUG-6: When the stream timed out, the run is likely still in progress on server.
|
|
2467
|
+
if (streamTimedOut) {
|
|
2468
|
+
const runId = response.headers?.get?.('x-run-id');
|
|
2469
|
+
process.stderr.write(chalk_1.default.yellow('\nRun still in progress on server — the CLI stopped waiting.\n') +
|
|
2470
|
+
(runId
|
|
2471
|
+
? chalk_1.default.yellow(`Check status with: orch logs ${runId}\n`)
|
|
2472
|
+
: chalk_1.default.yellow('Check recent runs with: orch runs\n')) +
|
|
2473
|
+
(options.waitTimeout
|
|
2474
|
+
? ''
|
|
2475
|
+
: chalk_1.default.gray('Tip: Use --wait-timeout <seconds> to wait longer.\n')));
|
|
2476
|
+
await (0, analytics_1.track)('cli_run', {
|
|
2477
|
+
agent: `${org}/${parsed.agent}@${parsed.version}`,
|
|
2478
|
+
input_type: hasInjection ? 'file_injection' : unkeyedFileArgs.length > 0 ? 'file' : options.data ? 'json' : 'empty',
|
|
2479
|
+
mode: 'cloud',
|
|
2480
|
+
streamed: true,
|
|
2481
|
+
timed_out: true,
|
|
2482
|
+
});
|
|
2483
|
+
const err = new errors_1.CliError('CLI wait timeout — run still in progress on server', errors_1.ExitCodes.TIMEOUT);
|
|
2484
|
+
err.displayed = true;
|
|
2485
|
+
throw err;
|
|
2486
|
+
}
|
|
2363
2487
|
await (0, analytics_1.track)('cli_run', {
|
|
2364
2488
|
agent: `${org}/${parsed.agent}@${parsed.version}`,
|
|
2365
2489
|
input_type: hasInjection ? 'file_injection' : unkeyedFileArgs.length > 0 ? 'file' : options.data ? 'json' : 'empty',
|
|
@@ -2747,6 +2871,7 @@ function registerRunCommand(program) {
|
|
|
2747
2871
|
.option('--skills-only <skills>', 'Use only these skills')
|
|
2748
2872
|
.option('--no-skills', 'Ignore default skills')
|
|
2749
2873
|
.option('--no-stream', 'Disable real-time streaming for stream-capable sandbox runs')
|
|
2874
|
+
.option('--wait-timeout <seconds>', 'Max seconds to wait for streaming result (default: 600)')
|
|
2750
2875
|
// Cloud-only options
|
|
2751
2876
|
.option('--endpoint <endpoint>', 'Override agent endpoint (cloud only)')
|
|
2752
2877
|
.option('--tenant <tenant>', 'Tenant identifier for multi-tenant callers (cloud only)')
|