@runtypelabs/cli 0.1.10 → 0.2.1
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/README.md +34 -0
- package/dist/index.js +517 -174
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2783,7 +2783,7 @@ var import_chalk16 = __toESM(require("chalk"));
|
|
|
2783
2783
|
var import_ora11 = __toESM(require("ora"));
|
|
2784
2784
|
var import_sdk6 = require("@runtypelabs/sdk");
|
|
2785
2785
|
|
|
2786
|
-
// src/commands/agents-
|
|
2786
|
+
// src/commands/agents-task.ts
|
|
2787
2787
|
init_cjs_shims();
|
|
2788
2788
|
var import_commander11 = require("commander");
|
|
2789
2789
|
var import_chalk15 = __toESM(require("chalk"));
|
|
@@ -2810,206 +2810,546 @@ function loadState(filePath) {
|
|
|
2810
2810
|
function saveState(filePath, state) {
|
|
2811
2811
|
const dir = path4.dirname(filePath);
|
|
2812
2812
|
fs3.mkdirSync(dir, { recursive: true });
|
|
2813
|
-
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2814
2813
|
fs3.writeFileSync(filePath, JSON.stringify(state, null, 2));
|
|
2815
2814
|
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2815
|
+
function isRecord(value) {
|
|
2816
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2817
|
+
}
|
|
2818
|
+
function parseSandboxProvider(value) {
|
|
2819
|
+
if (!value) return void 0;
|
|
2820
|
+
const normalized = value.trim().toLowerCase();
|
|
2821
|
+
if (normalized === "quickjs" || normalized === "daytona") return normalized;
|
|
2822
|
+
return void 0;
|
|
2823
|
+
}
|
|
2824
|
+
function parseSandboxLanguage(provider, value) {
|
|
2825
|
+
if (provider === "quickjs") return "javascript";
|
|
2826
|
+
if (typeof value === "string") {
|
|
2827
|
+
const normalized = value.trim().toLowerCase();
|
|
2828
|
+
if (normalized === "javascript" || normalized === "typescript" || normalized === "python") {
|
|
2829
|
+
return normalized;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return "javascript";
|
|
2833
|
+
}
|
|
2834
|
+
function parseSandboxTimeout(value, provider) {
|
|
2835
|
+
const fallback = provider === "quickjs" ? 5e3 : 3e4;
|
|
2836
|
+
let numericValue;
|
|
2837
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2838
|
+
numericValue = value;
|
|
2839
|
+
} else if (typeof value === "string" && value.trim() !== "") {
|
|
2840
|
+
const parsed = Number(value);
|
|
2841
|
+
if (Number.isFinite(parsed)) {
|
|
2842
|
+
numericValue = parsed;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
if (numericValue === void 0) return fallback;
|
|
2846
|
+
return Math.max(1, Math.min(3e4, Math.floor(numericValue)));
|
|
2847
|
+
}
|
|
2848
|
+
function parseDaytonaExecutionResult(value) {
|
|
2849
|
+
if (typeof value !== "string") return value;
|
|
2850
|
+
const trimmed = value.trim();
|
|
2851
|
+
if (!trimmed) return "";
|
|
2852
|
+
try {
|
|
2853
|
+
return JSON.parse(trimmed);
|
|
2854
|
+
} catch {
|
|
2855
|
+
const lines = trimmed.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
2856
|
+
const lastLine = lines[lines.length - 1];
|
|
2857
|
+
if (!lastLine) return value;
|
|
2858
|
+
try {
|
|
2859
|
+
return JSON.parse(lastLine);
|
|
2860
|
+
} catch {
|
|
2861
|
+
return value;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
function createSandboxInstructions(provider) {
|
|
2866
|
+
if (provider === "quickjs") {
|
|
2867
|
+
return [
|
|
2868
|
+
"--- Sandbox Tooling (QuickJS) ---",
|
|
2869
|
+
"You can execute JavaScript snippets with the local tool `run_sandbox_code`.",
|
|
2870
|
+
"Call shape:",
|
|
2871
|
+
'{ "code": "...", "parameters": { ... }, "timeoutMs": 5000 }',
|
|
2872
|
+
"QuickJS rules:",
|
|
2873
|
+
"1. Use JavaScript only (no TypeScript or Python).",
|
|
2874
|
+
"2. Inputs are passed in the `parameters` object (for example: `const x = parameters.x`).",
|
|
2875
|
+
"3. The snippet is wrapped in a function. Use top-level `return ...` to produce the result.",
|
|
2876
|
+
"4. Return JSON-serializable values (object, array, string, number, boolean, null).",
|
|
2877
|
+
"5. No Node/Bun/Deno APIs, imports/require, process, filesystem, or network calls.",
|
|
2878
|
+
"Example:",
|
|
2879
|
+
"const nums = parameters.nums || []",
|
|
2880
|
+
"const sum = nums.reduce((acc, n) => acc + n, 0)",
|
|
2881
|
+
"return { sum, count: nums.length }"
|
|
2882
|
+
].join("\n");
|
|
2883
|
+
}
|
|
2884
|
+
return [
|
|
2885
|
+
"--- Sandbox Tooling (Daytona) ---",
|
|
2886
|
+
"You can execute code snippets with the local tool `run_sandbox_code`.",
|
|
2887
|
+
"Call shape:",
|
|
2888
|
+
'{ "code": "...", "parameters": { ... }, "language": "javascript|typescript|python", "timeoutMs": 30000 }',
|
|
2889
|
+
"Daytona rules:",
|
|
2890
|
+
"1. Choose one language: javascript, typescript, or python.",
|
|
2891
|
+
"2. Parameters are injected as top-level variables before your code (do not rely on `parameters`).",
|
|
2892
|
+
"3. Your snippet runs as a full program. Do not use top-level `return`.",
|
|
2893
|
+
"4. For structured results, write a single JSON value to stdout as the final output.",
|
|
2894
|
+
"5. JS/TS: `console.log(JSON.stringify({ ... }))`; Python: `import json` then `print(json.dumps({ ... }))`.",
|
|
2895
|
+
"6. Avoid extra logs before the final JSON line, or parsing may fail."
|
|
2896
|
+
].join("\n");
|
|
2897
|
+
}
|
|
2898
|
+
function buildResumeCommand(agent, message, options, parsedSandbox) {
|
|
2899
|
+
const nameFlag = options.name ? ` --name ${options.name}` : "";
|
|
2900
|
+
const sandboxFlag = parsedSandbox ? ` --sandbox ${parsedSandbox}` : "";
|
|
2901
|
+
return `runtype marathon ${agent} -m "${message}" --resume${nameFlag}${sandboxFlag}`;
|
|
2902
|
+
}
|
|
2903
|
+
function createSandboxLocalTool(client, provider, debugMode) {
|
|
2904
|
+
return {
|
|
2905
|
+
description: provider === "quickjs" ? "Execute JavaScript code in QuickJS sandbox. Inputs are passed via parameters object." : "Execute JavaScript/TypeScript/Python code in Daytona sandbox. Inputs are injected as top-level variables.",
|
|
2906
|
+
parametersSchema: {
|
|
2907
|
+
type: "object",
|
|
2908
|
+
properties: {
|
|
2909
|
+
code: { type: "string", description: "Code snippet to execute" },
|
|
2910
|
+
parameters: {
|
|
2911
|
+
type: "object",
|
|
2912
|
+
description: "Input parameters for the code (JSON object)"
|
|
2913
|
+
},
|
|
2914
|
+
language: {
|
|
2915
|
+
type: "string",
|
|
2916
|
+
enum: provider === "quickjs" ? ["javascript"] : ["javascript", "typescript", "python"],
|
|
2917
|
+
description: provider === "quickjs" ? "QuickJS only accepts javascript" : "Daytona code language"
|
|
2918
|
+
},
|
|
2919
|
+
timeoutMs: { type: "number", description: "Execution timeout in ms (max 30000)" }
|
|
2920
|
+
},
|
|
2921
|
+
required: ["code"]
|
|
2922
|
+
},
|
|
2923
|
+
execute: async (args) => {
|
|
2924
|
+
const rawCode = args.code;
|
|
2925
|
+
const code = typeof rawCode === "string" ? rawCode : "";
|
|
2926
|
+
if (!code.trim()) {
|
|
2927
|
+
return { success: false, error: "code is required" };
|
|
2928
|
+
}
|
|
2929
|
+
const language = parseSandboxLanguage(provider, args.language);
|
|
2930
|
+
const timeout = parseSandboxTimeout(args.timeoutMs, provider);
|
|
2931
|
+
const parameters = isRecord(args.parameters) ? args.parameters : {};
|
|
2932
|
+
const gateDecision = (0, import_sdk5.evaluateGeneratedRuntimeToolProposal)(
|
|
2933
|
+
{
|
|
2934
|
+
name: "run_sandbox_code",
|
|
2935
|
+
description: `Execute code in ${provider}`,
|
|
2936
|
+
toolType: "custom",
|
|
2937
|
+
parametersSchema: { type: "object" },
|
|
2938
|
+
config: {
|
|
2939
|
+
code,
|
|
2940
|
+
timeout,
|
|
2941
|
+
sandboxProvider: provider,
|
|
2942
|
+
language
|
|
2943
|
+
}
|
|
2944
|
+
},
|
|
2945
|
+
{
|
|
2946
|
+
allowedToolTypes: ["custom"],
|
|
2947
|
+
allowedSandboxProviders: [provider],
|
|
2948
|
+
allowedLanguages: provider === "quickjs" ? ["javascript"] : ["javascript", "typescript", "python"],
|
|
2949
|
+
maxTimeoutMs: 3e4,
|
|
2950
|
+
maxCodeLength: 12e3
|
|
2839
2951
|
}
|
|
2952
|
+
);
|
|
2953
|
+
if (!gateDecision.approved) {
|
|
2954
|
+
return {
|
|
2955
|
+
success: false,
|
|
2956
|
+
error: gateDecision.reason,
|
|
2957
|
+
violations: gateDecision.violations
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
const tempToolName = `marathon_${provider}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2961
|
+
let tempToolId;
|
|
2962
|
+
try {
|
|
2963
|
+
const createRequest = {
|
|
2964
|
+
name: tempToolName,
|
|
2965
|
+
description: `Ephemeral ${provider} sandbox execution`,
|
|
2966
|
+
toolType: "custom",
|
|
2967
|
+
parametersSchema: { type: "object", properties: {} },
|
|
2968
|
+
config: {
|
|
2969
|
+
code,
|
|
2970
|
+
timeout,
|
|
2971
|
+
allowedApis: [],
|
|
2972
|
+
sandboxProvider: provider,
|
|
2973
|
+
language
|
|
2974
|
+
}
|
|
2975
|
+
};
|
|
2976
|
+
const created = await client.tools.create(createRequest);
|
|
2977
|
+
tempToolId = created.id;
|
|
2978
|
+
const executeRequest = {
|
|
2979
|
+
toolId: created.id,
|
|
2980
|
+
parameters
|
|
2981
|
+
};
|
|
2982
|
+
const execution = await client.tools.execute(created.id, executeRequest);
|
|
2983
|
+
const parsedResult = provider === "daytona" ? parseDaytonaExecutionResult(execution.result) : execution.result;
|
|
2984
|
+
return {
|
|
2985
|
+
success: execution.status === "success",
|
|
2986
|
+
sandboxProvider: provider,
|
|
2987
|
+
language,
|
|
2988
|
+
result: parsedResult,
|
|
2989
|
+
executionId: execution.executionId,
|
|
2990
|
+
...execution.errorMessage ? { error: execution.errorMessage } : {}
|
|
2991
|
+
};
|
|
2840
2992
|
} catch (error) {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2993
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2994
|
+
if (debugMode) {
|
|
2995
|
+
console.log(import_chalk15.default.gray(` [sandbox:${provider}] execution error: ${message}`));
|
|
2996
|
+
}
|
|
2997
|
+
return {
|
|
2998
|
+
success: false,
|
|
2999
|
+
sandboxProvider: provider,
|
|
3000
|
+
language,
|
|
3001
|
+
error: message
|
|
3002
|
+
};
|
|
3003
|
+
} finally {
|
|
3004
|
+
if (tempToolId) {
|
|
3005
|
+
try {
|
|
3006
|
+
await client.tools.delete(tempToolId);
|
|
3007
|
+
} catch {
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
2845
3010
|
}
|
|
2846
3011
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
priorSessionCount = localState.sessionCount;
|
|
2869
|
-
priorCost = localState.totalCost;
|
|
2870
|
-
console.log(
|
|
2871
|
-
import_chalk15.default.cyan(
|
|
2872
|
-
`Resuming from session ${priorSessionCount} ($${priorCost.toFixed(4)} spent)`
|
|
2873
|
-
)
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
async function taskAction(agent, options) {
|
|
3015
|
+
const apiKey = await ensureAuth();
|
|
3016
|
+
if (!apiKey) return;
|
|
3017
|
+
const client = new import_sdk5.RuntypeClient({
|
|
3018
|
+
apiKey,
|
|
3019
|
+
baseUrl: getApiUrl()
|
|
3020
|
+
});
|
|
3021
|
+
const parsedSandbox = parseSandboxProvider(options.sandbox);
|
|
3022
|
+
if (options.sandbox && !parsedSandbox) {
|
|
3023
|
+
console.error(import_chalk15.default.red(`Invalid --sandbox value "${options.sandbox}". Use: quickjs or daytona`));
|
|
3024
|
+
process.exit(1);
|
|
3025
|
+
}
|
|
3026
|
+
let agentId = agent;
|
|
3027
|
+
if (!agent.startsWith("agent_")) {
|
|
3028
|
+
const spinner = (0, import_ora10.default)("Looking up agent by name...").start();
|
|
3029
|
+
try {
|
|
3030
|
+
const list = await client.agents.list();
|
|
3031
|
+
const found = list.data.find(
|
|
3032
|
+
(a) => a.name.toLowerCase() === agent.toLowerCase()
|
|
2874
3033
|
);
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
agentId
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3034
|
+
if (found) {
|
|
3035
|
+
agentId = found.id;
|
|
3036
|
+
spinner.succeed(`Found agent: ${import_chalk15.default.green(agentId)}`);
|
|
3037
|
+
} else {
|
|
3038
|
+
spinner.text = `Creating agent "${agent}"...`;
|
|
3039
|
+
try {
|
|
3040
|
+
const created = await client.agents.create({ name: agent });
|
|
3041
|
+
agentId = created.id;
|
|
3042
|
+
spinner.succeed(`Created agent: ${import_chalk15.default.green(agentId)}`);
|
|
3043
|
+
} catch (createErr) {
|
|
3044
|
+
spinner.fail(`Failed to create agent "${agent}"`);
|
|
3045
|
+
const errMsg = createErr instanceof Error ? createErr.message : String(createErr);
|
|
3046
|
+
console.error(import_chalk15.default.red(errMsg));
|
|
3047
|
+
process.exit(1);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
} catch (error) {
|
|
3051
|
+
spinner.fail("Failed to list agents");
|
|
3052
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
3053
|
+
console.error(import_chalk15.default.red(errMsg));
|
|
3054
|
+
process.exit(1);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
const taskName = options.name || agentId;
|
|
3058
|
+
const filePath = stateFilePath(taskName, options.stateDir);
|
|
3059
|
+
const maxSessions = parseInt(options.maxSessions, 10);
|
|
3060
|
+
const maxCost = options.maxCost ? parseFloat(options.maxCost) : void 0;
|
|
3061
|
+
let priorSessionCount = 0;
|
|
3062
|
+
let priorCost = 0;
|
|
3063
|
+
if (options.resume) {
|
|
3064
|
+
const existing = loadState(filePath);
|
|
3065
|
+
if (!existing) {
|
|
3066
|
+
console.error(import_chalk15.default.red(`No state file found at ${filePath}`));
|
|
3067
|
+
console.log(import_chalk15.default.gray(" Run without --resume to start a new task."));
|
|
3068
|
+
process.exit(1);
|
|
3069
|
+
}
|
|
3070
|
+
if (existing.status === "complete") {
|
|
3071
|
+
console.log(import_chalk15.default.yellow("This task already completed."));
|
|
3072
|
+
if (options.json) printJson(existing);
|
|
3073
|
+
return;
|
|
2889
3074
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3075
|
+
priorSessionCount = existing.sessionCount;
|
|
3076
|
+
priorCost = existing.totalCost;
|
|
3077
|
+
console.log(
|
|
3078
|
+
import_chalk15.default.cyan(
|
|
3079
|
+
`Resuming from session ${priorSessionCount} ($${priorCost.toFixed(4)} spent)`
|
|
3080
|
+
)
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
let lastKnownState = null;
|
|
3084
|
+
let interrupted = false;
|
|
3085
|
+
const onSigint = () => {
|
|
3086
|
+
if (interrupted) process.exit(1);
|
|
3087
|
+
interrupted = true;
|
|
3088
|
+
console.log(import_chalk15.default.yellow("\n\nInterrupted \u2014 saving state..."));
|
|
3089
|
+
if (lastKnownState) {
|
|
3090
|
+
lastKnownState.status = "paused";
|
|
3091
|
+
saveState(filePath, lastKnownState);
|
|
2897
3092
|
console.log(import_chalk15.default.green(`State saved to ${filePath}`));
|
|
2898
|
-
|
|
2899
|
-
import_chalk15.default.gray(
|
|
2900
|
-
` Resume with: runtype agents run-task ${agent} -m "${options.message}" --resume${options.name ? ` --name ${options.name}` : ""}`
|
|
2901
|
-
)
|
|
2902
|
-
);
|
|
2903
|
-
process.exit(0);
|
|
2904
|
-
};
|
|
2905
|
-
process.on("SIGINT", onSigint);
|
|
2906
|
-
const remainingSessions = maxSessions - priorSessionCount;
|
|
2907
|
-
console.log(import_chalk15.default.cyan(`
|
|
2908
|
-
Running task "${taskName}" on ${agentId}`));
|
|
3093
|
+
}
|
|
2909
3094
|
console.log(
|
|
2910
3095
|
import_chalk15.default.gray(
|
|
2911
|
-
`
|
|
3096
|
+
` Resume with: ${buildResumeCommand(agent, options.message, options, parsedSandbox)}`
|
|
2912
3097
|
)
|
|
2913
3098
|
);
|
|
2914
|
-
|
|
3099
|
+
process.exit(0);
|
|
3100
|
+
};
|
|
3101
|
+
process.on("SIGINT", onSigint);
|
|
3102
|
+
const remainingSessions = maxSessions - priorSessionCount;
|
|
3103
|
+
const remainingCost = maxCost ? maxCost - priorCost : void 0;
|
|
3104
|
+
const sandboxPrompt = parsedSandbox ? createSandboxInstructions(parsedSandbox) : "";
|
|
3105
|
+
const taskMessage = sandboxPrompt ? `${options.message}
|
|
3106
|
+
|
|
3107
|
+
${sandboxPrompt}` : options.message;
|
|
3108
|
+
console.log(import_chalk15.default.cyan(`
|
|
3109
|
+
Running task "${taskName}" on ${agentId}`));
|
|
3110
|
+
console.log(
|
|
3111
|
+
import_chalk15.default.gray(
|
|
3112
|
+
` Sessions: ${priorSessionCount > 0 ? `${priorSessionCount} done, ` : ""}${remainingSessions} remaining${maxCost ? ` | Budget: $${maxCost.toFixed(2)}` : ""}${options.model ? ` | Model: ${options.model}` : ""}${parsedSandbox ? ` | Sandbox: ${parsedSandbox}` : ""}`
|
|
3113
|
+
)
|
|
3114
|
+
);
|
|
3115
|
+
console.log(import_chalk15.default.gray(` State: ${filePath}
|
|
2915
3116
|
`));
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
3117
|
+
try {
|
|
3118
|
+
let currentSession = priorSessionCount;
|
|
3119
|
+
let thinkingSpinner = null;
|
|
3120
|
+
let thinkingChars = 0;
|
|
3121
|
+
const streamCallbacks = {
|
|
3122
|
+
onAgentStart: () => {
|
|
3123
|
+
currentSession++;
|
|
3124
|
+
console.log(import_chalk15.default.cyan(`
|
|
3125
|
+
\u2500\u2500 Session ${currentSession} \u2500\u2500
|
|
3126
|
+
`));
|
|
3127
|
+
thinkingChars = 0;
|
|
3128
|
+
thinkingSpinner = (0, import_ora10.default)({ text: import_chalk15.default.dim("Thinking..."), color: "gray" }).start();
|
|
3129
|
+
},
|
|
3130
|
+
onTurnDelta: (event) => {
|
|
3131
|
+
if (event.contentType === "text") {
|
|
3132
|
+
if (thinkingSpinner) {
|
|
3133
|
+
thinkingSpinner.stop();
|
|
3134
|
+
thinkingSpinner = null;
|
|
2924
3135
|
}
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
{
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
trackProgress: options.trackProgress ? taskName : void 0,
|
|
2943
|
-
debugMode: options.debug,
|
|
2944
|
-
onSession: (session) => {
|
|
2945
|
-
const sessionCost = session.sessionCount > 1 ? session.totalCost - localState.sessions.slice(-session.sessionCount + 1).reduce((s, e) => s + e.cost, 0) : session.totalCost;
|
|
2946
|
-
const thisCost = sessionCost > 0 ? sessionCost : session.totalCost / session.sessionCount;
|
|
2947
|
-
localState.sessionCount = priorSessionCount + session.sessionCount;
|
|
2948
|
-
localState.totalCost = priorCost + session.totalCost;
|
|
2949
|
-
Object.assign(localState.context, session.context);
|
|
2950
|
-
localState.lastOutput = session.lastOutput;
|
|
2951
|
-
localState.status = session.status;
|
|
2952
|
-
localState.sessions.push({
|
|
2953
|
-
index: localState.sessionCount,
|
|
2954
|
-
cost: thisCost,
|
|
2955
|
-
stopReason: session.lastStopReason,
|
|
2956
|
-
outputPreview: session.lastOutput.slice(0, 200),
|
|
2957
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2958
|
-
});
|
|
2959
|
-
saveState(filePath, localState);
|
|
2960
|
-
const total = localState.sessionCount;
|
|
2961
|
-
const costStr = import_chalk15.default.yellow(`$${localState.totalCost.toFixed(4)}`);
|
|
2962
|
-
const reasonColor = session.lastStopReason === "complete" ? import_chalk15.default.green : import_chalk15.default.gray;
|
|
2963
|
-
console.log(
|
|
2964
|
-
` ${import_chalk15.default.dim(`[${total}/${maxSessions}]`)} ${reasonColor(session.lastStopReason)} | total: ${costStr}`
|
|
2965
|
-
);
|
|
2966
|
-
if (session.lastOutput) {
|
|
2967
|
-
const preview = session.lastOutput.slice(0, 120).replace(/\n/g, " ");
|
|
2968
|
-
console.log(
|
|
2969
|
-
` ${import_chalk15.default.dim(preview)}${session.lastOutput.length > 120 ? "..." : ""}`
|
|
2970
|
-
);
|
|
3136
|
+
process.stdout.write(event.delta);
|
|
3137
|
+
} else if (event.contentType === "thinking") {
|
|
3138
|
+
thinkingChars += event.delta.length;
|
|
3139
|
+
const approxTokens = Math.round(thinkingChars / 4);
|
|
3140
|
+
const tokenStr = approxTokens.toLocaleString();
|
|
3141
|
+
if (!thinkingSpinner) {
|
|
3142
|
+
thinkingSpinner = (0, import_ora10.default)({
|
|
3143
|
+
text: import_chalk15.default.dim(`Thinking... (~${tokenStr} tokens)`),
|
|
3144
|
+
color: "gray"
|
|
3145
|
+
}).start();
|
|
3146
|
+
} else {
|
|
3147
|
+
thinkingSpinner.text = import_chalk15.default.dim(`Thinking... (~${tokenStr} tokens)`);
|
|
3148
|
+
}
|
|
3149
|
+
if (options.debug) {
|
|
3150
|
+
thinkingSpinner.stop();
|
|
3151
|
+
process.stdout.write(import_chalk15.default.dim(event.delta));
|
|
3152
|
+
thinkingSpinner.start();
|
|
2971
3153
|
}
|
|
2972
3154
|
}
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3155
|
+
},
|
|
3156
|
+
onToolStart: (event) => {
|
|
3157
|
+
if (thinkingSpinner) {
|
|
3158
|
+
thinkingSpinner.stop();
|
|
3159
|
+
thinkingSpinner = null;
|
|
3160
|
+
}
|
|
3161
|
+
if (options.debug) {
|
|
3162
|
+
console.log(import_chalk15.default.gray(`
|
|
3163
|
+
[tool] ${event.toolName}...`));
|
|
3164
|
+
}
|
|
3165
|
+
},
|
|
3166
|
+
onToolComplete: (event) => {
|
|
3167
|
+
if (options.debug) {
|
|
3168
|
+
const status = event.success ? import_chalk15.default.green("ok") : import_chalk15.default.red("failed");
|
|
3169
|
+
console.log(import_chalk15.default.gray(` [tool] ${event.toolName} \u2192 ${status}`));
|
|
3170
|
+
}
|
|
3171
|
+
},
|
|
3172
|
+
onAgentPaused: (event) => {
|
|
3173
|
+
if (thinkingSpinner) {
|
|
3174
|
+
thinkingSpinner.stop();
|
|
3175
|
+
thinkingSpinner = null;
|
|
3176
|
+
}
|
|
3177
|
+
const rawParams = event.parameters;
|
|
3178
|
+
const paramPreview = typeof rawParams === "string" ? `string(${rawParams.length} chars)` : rawParams && typeof rawParams === "object" ? `object(${Object.keys(rawParams).join(", ")})` : typeof rawParams;
|
|
2986
3179
|
console.log(
|
|
2987
|
-
import_chalk15.default.
|
|
3180
|
+
import_chalk15.default.cyan(
|
|
2988
3181
|
`
|
|
2989
|
-
|
|
3182
|
+
[local tool] ${event.toolName} called (params: ${paramPreview})`
|
|
2990
3183
|
)
|
|
2991
3184
|
);
|
|
3185
|
+
if (options.debug) {
|
|
3186
|
+
console.log(import_chalk15.default.gray(` executionId: ${event.executionId}`));
|
|
3187
|
+
console.log(import_chalk15.default.gray(` raw params: ${JSON.stringify(event.parameters).slice(0, 300)}`));
|
|
3188
|
+
}
|
|
3189
|
+
},
|
|
3190
|
+
onError: (event) => {
|
|
3191
|
+
if (thinkingSpinner) {
|
|
3192
|
+
thinkingSpinner.stop();
|
|
3193
|
+
thinkingSpinner = null;
|
|
3194
|
+
}
|
|
3195
|
+
console.error(import_chalk15.default.red(`
|
|
3196
|
+
Error: ${event.error}`));
|
|
2992
3197
|
}
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
3198
|
+
};
|
|
3199
|
+
const defaultLocalTools = {
|
|
3200
|
+
read_file: {
|
|
3201
|
+
description: "Read the contents of a file at the given path",
|
|
3202
|
+
parametersSchema: {
|
|
3203
|
+
type: "object",
|
|
3204
|
+
properties: { path: { type: "string", description: "File path to read" } },
|
|
3205
|
+
required: ["path"]
|
|
3206
|
+
},
|
|
3207
|
+
execute: async (args) => {
|
|
3208
|
+
const filePath2 = String(args.path || "");
|
|
3209
|
+
if (!filePath2) return "Error: path is required";
|
|
3210
|
+
return fs3.readFileSync(filePath2, "utf-8");
|
|
3211
|
+
}
|
|
3212
|
+
},
|
|
3213
|
+
write_file: {
|
|
3214
|
+
description: "Write content to a file, creating directories as needed",
|
|
3215
|
+
parametersSchema: {
|
|
3216
|
+
type: "object",
|
|
3217
|
+
properties: {
|
|
3218
|
+
path: { type: "string", description: "File path to write" },
|
|
3219
|
+
content: { type: "string", description: "Content to write" }
|
|
3220
|
+
},
|
|
3221
|
+
required: ["path", "content"]
|
|
3222
|
+
},
|
|
3223
|
+
execute: async (args) => {
|
|
3224
|
+
const filePath2 = String(args.path || "");
|
|
3225
|
+
if (!filePath2) return "Error: path is required";
|
|
3226
|
+
const content = String(args.content || "");
|
|
3227
|
+
const dir = path4.dirname(filePath2);
|
|
3228
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
3229
|
+
fs3.writeFileSync(filePath2, content);
|
|
3230
|
+
return "ok";
|
|
3231
|
+
}
|
|
3232
|
+
},
|
|
3233
|
+
list_directory: {
|
|
3234
|
+
description: "List files and directories at the given path",
|
|
3235
|
+
parametersSchema: {
|
|
3236
|
+
type: "object",
|
|
3237
|
+
properties: { path: { type: "string", description: 'Directory path (defaults to ".")' } }
|
|
3238
|
+
},
|
|
3239
|
+
execute: async (args) => {
|
|
3240
|
+
const dirPath = String(args.path || ".");
|
|
3241
|
+
return fs3.readdirSync(dirPath).join("\n");
|
|
3242
|
+
}
|
|
2996
3243
|
}
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3244
|
+
};
|
|
3245
|
+
const enabledLocalTools = {};
|
|
3246
|
+
if (!options.noLocalTools) {
|
|
3247
|
+
Object.assign(enabledLocalTools, defaultLocalTools);
|
|
3248
|
+
}
|
|
3249
|
+
if (parsedSandbox) {
|
|
3250
|
+
enabledLocalTools.run_sandbox_code = createSandboxLocalTool(
|
|
3251
|
+
client,
|
|
3252
|
+
parsedSandbox,
|
|
3253
|
+
options.debug
|
|
3254
|
+
);
|
|
3006
3255
|
}
|
|
3256
|
+
const localTools = Object.keys(enabledLocalTools).length > 0 ? enabledLocalTools : void 0;
|
|
3257
|
+
const result = await client.agents.runTask(agentId, {
|
|
3258
|
+
message: taskMessage,
|
|
3259
|
+
maxSessions: remainingSessions,
|
|
3260
|
+
maxCost: remainingCost,
|
|
3261
|
+
model: options.model,
|
|
3262
|
+
debugMode: options.debug,
|
|
3263
|
+
stream: true,
|
|
3264
|
+
streamCallbacks,
|
|
3265
|
+
localTools,
|
|
3266
|
+
trackProgress: options.track ? taskName : void 0,
|
|
3267
|
+
onSession: (state) => {
|
|
3268
|
+
const adjustedState = {
|
|
3269
|
+
...state,
|
|
3270
|
+
sessionCount: priorSessionCount + state.sessionCount,
|
|
3271
|
+
totalCost: priorCost + state.totalCost
|
|
3272
|
+
};
|
|
3273
|
+
lastKnownState = adjustedState;
|
|
3274
|
+
saveState(filePath, adjustedState);
|
|
3275
|
+
const latest = state.sessions[state.sessions.length - 1];
|
|
3276
|
+
if (latest) {
|
|
3277
|
+
const total = adjustedState.sessionCount;
|
|
3278
|
+
const costStr = import_chalk15.default.yellow(`$${adjustedState.totalCost.toFixed(4)}`);
|
|
3279
|
+
const reasonColor = latest.stopReason === "complete" ? import_chalk15.default.green : latest.stopReason === "error" ? import_chalk15.default.red : import_chalk15.default.gray;
|
|
3280
|
+
console.log(
|
|
3281
|
+
`
|
|
3282
|
+
${import_chalk15.default.dim(`[${total}/${maxSessions}]`)} ${reasonColor(latest.stopReason)} | total: ${costStr}`
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
if (interrupted) return false;
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
const finalState = {
|
|
3289
|
+
agentId,
|
|
3290
|
+
agentName: result.sessions[0]?.stopReason ? agentId : agentId,
|
|
3291
|
+
taskName,
|
|
3292
|
+
status: result.status,
|
|
3293
|
+
sessionCount: priorSessionCount + result.sessionCount,
|
|
3294
|
+
totalCost: priorCost + result.totalCost,
|
|
3295
|
+
lastOutput: result.lastOutput,
|
|
3296
|
+
lastStopReason: result.sessions[result.sessions.length - 1]?.stopReason || "complete",
|
|
3297
|
+
sessions: result.sessions,
|
|
3298
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3299
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3300
|
+
};
|
|
3301
|
+
saveState(filePath, finalState);
|
|
3302
|
+
process.removeListener("SIGINT", onSigint);
|
|
3303
|
+
console.log();
|
|
3304
|
+
const statusColor = result.status === "complete" ? import_chalk15.default.green : result.status === "budget_exceeded" ? import_chalk15.default.red : import_chalk15.default.yellow;
|
|
3305
|
+
console.log(`Status: ${statusColor(result.status)}`);
|
|
3306
|
+
console.log(`Sessions: ${finalState.sessionCount}`);
|
|
3307
|
+
console.log(`Total cost: ${import_chalk15.default.yellow(`$${finalState.totalCost.toFixed(4)}`)}`);
|
|
3308
|
+
console.log(`State: ${import_chalk15.default.gray(filePath)}`);
|
|
3309
|
+
if (result.recordId) {
|
|
3310
|
+
console.log(`Record: ${import_chalk15.default.gray(result.recordId)}`);
|
|
3311
|
+
}
|
|
3312
|
+
if (result.status === "paused" || result.status === "max_sessions") {
|
|
3313
|
+
console.log(
|
|
3314
|
+
import_chalk15.default.gray(
|
|
3315
|
+
`
|
|
3316
|
+
Resume: ${buildResumeCommand(agent, options.message, options, parsedSandbox)}`
|
|
3317
|
+
)
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
if (options.json) {
|
|
3321
|
+
console.log();
|
|
3322
|
+
printJson(finalState);
|
|
3323
|
+
}
|
|
3324
|
+
} catch (error) {
|
|
3325
|
+
const stateAtError = lastKnownState;
|
|
3326
|
+
if (stateAtError) {
|
|
3327
|
+
stateAtError.status = "paused";
|
|
3328
|
+
saveState(filePath, stateAtError);
|
|
3329
|
+
}
|
|
3330
|
+
process.removeListener("SIGINT", onSigint);
|
|
3331
|
+
const errMsg = error instanceof Error ? error.message : "Unknown error";
|
|
3332
|
+
console.error(import_chalk15.default.red(`
|
|
3333
|
+
Task failed: ${errMsg}`));
|
|
3334
|
+
console.log(import_chalk15.default.gray(`State saved to ${filePath} \u2014 resume with --resume`));
|
|
3335
|
+
process.exit(1);
|
|
3007
3336
|
}
|
|
3337
|
+
}
|
|
3338
|
+
function applyTaskOptions(cmd) {
|
|
3339
|
+
return cmd.argument("<agent>", "Agent ID or name").requiredOption("-m, --message <text>", "Task message for the agent").option("--max-sessions <n>", "Maximum sessions", "50").option("--max-cost <n>", "Budget in USD").option("--model <modelId>", "Model ID to use (overrides agent config)").option("--name <name>", "Task name (used for state file, defaults to agent name)").option("--state-dir <path>", "Directory for state files (default: .runtype/tasks/)").option("--resume", "Resume from existing local state").option("--track", "Sync progress to a Runtype record (visible in dashboard)").option("--debug", "Show debug output from each session").option("--json", "Output final result as JSON").option("--sandbox <provider>", "Enable sandbox code execution tool (quickjs or daytona)").option("--no-local-tools", "Disable built-in local tool execution (read_file, write_file, list_directory)").action(taskAction);
|
|
3340
|
+
}
|
|
3341
|
+
var taskCommand = applyTaskOptions(
|
|
3342
|
+
new import_commander11.Command("task").description("Run a multi-session agent task")
|
|
3008
3343
|
);
|
|
3344
|
+
function createMarathonCommand() {
|
|
3345
|
+
return applyTaskOptions(
|
|
3346
|
+
new import_commander11.Command("marathon").description("Run a multi-session agent task (alias for agents task)")
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3009
3349
|
|
|
3010
3350
|
// src/commands/agents.ts
|
|
3011
3351
|
var agentsCommand = new import_commander12.Command("agents").description("Manage agents");
|
|
3012
|
-
agentsCommand.addCommand(
|
|
3352
|
+
agentsCommand.addCommand(taskCommand);
|
|
3013
3353
|
agentsCommand.command("list").description("List all agents").option("--json", "Output as JSON").option("--limit <n>", "Limit results", "20").action(async (options) => {
|
|
3014
3354
|
const apiKey = await ensureAuth();
|
|
3015
3355
|
if (!apiKey) return;
|
|
@@ -4077,12 +4417,15 @@ program.addCommand(apiKeysCommand);
|
|
|
4077
4417
|
program.addCommand(analyticsCommand);
|
|
4078
4418
|
program.addCommand(billingCommand);
|
|
4079
4419
|
program.addCommand(flowVersionsCommand);
|
|
4420
|
+
program.addCommand(createMarathonCommand());
|
|
4080
4421
|
program.exitOverride();
|
|
4081
4422
|
try {
|
|
4082
|
-
|
|
4423
|
+
const userArgs = process.argv.slice(2);
|
|
4424
|
+
const cleanArgs = userArgs[0] === "--" ? userArgs.slice(1) : userArgs;
|
|
4425
|
+
if (!cleanArgs.length) {
|
|
4083
4426
|
handleNoCommand();
|
|
4084
4427
|
} else {
|
|
4085
|
-
program.parse(
|
|
4428
|
+
program.parse(cleanArgs, { from: "user" });
|
|
4086
4429
|
}
|
|
4087
4430
|
} catch (error) {
|
|
4088
4431
|
const commanderError = error;
|