@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/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-run-task.ts
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
- var runTaskCommand = new import_commander11.Command("run-task").description("Run a long-task agent with local state persistence").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("--name <name>", "Task name (used for state file, defaults to agent ID)").option("--state-dir <path>", "Directory for state files (default: .runtype/tasks/)").option("--resume", "Resume from existing local state").option("--track-progress", "Also sync progress to the Runtype dashboard").option("--debug", "Show debug output from each session").option("--json", "Output final result as JSON").action(
2817
- async (agent, options) => {
2818
- const apiKey = await ensureAuth();
2819
- if (!apiKey) return;
2820
- const client = new import_sdk5.RuntypeClient({
2821
- apiKey,
2822
- baseUrl: getApiUrl()
2823
- });
2824
- let agentId = agent;
2825
- if (!agent.startsWith("agent_")) {
2826
- const spinner = (0, import_ora10.default)("Looking up agent by name...").start();
2827
- try {
2828
- const list = await client.agents.list();
2829
- const found = list.data.find(
2830
- (a) => a.name.toLowerCase() === agent.toLowerCase()
2831
- );
2832
- if (found) {
2833
- agentId = found.id;
2834
- spinner.succeed(`Found agent: ${import_chalk15.default.green(agentId)}`);
2835
- } else {
2836
- spinner.fail(`No agent found with name "${agent}"`);
2837
- console.log(import_chalk15.default.gray(` Create one with: runtype agents create -n "${agent}"`));
2838
- process.exit(1);
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
- spinner.fail("Failed to look up agent");
2842
- const message = error instanceof Error ? error.message : "Unknown error";
2843
- console.error(import_chalk15.default.red(message));
2844
- process.exit(1);
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
- const taskName = options.name || agentId;
2848
- const filePath = stateFilePath(taskName, options.stateDir);
2849
- const maxSessions = parseInt(options.maxSessions, 10);
2850
- const maxCost = options.maxCost ? parseFloat(options.maxCost) : void 0;
2851
- let localState;
2852
- let priorSessionCount = 0;
2853
- let priorCost = 0;
2854
- if (options.resume) {
2855
- const existing = loadState(filePath);
2856
- if (!existing) {
2857
- console.error(import_chalk15.default.red(`No state file found at ${filePath}`));
2858
- console.log(import_chalk15.default.gray(" Run without --resume to start a new task."));
2859
- process.exit(1);
2860
- }
2861
- if (existing.status === "complete") {
2862
- console.log(import_chalk15.default.yellow("This task already completed."));
2863
- if (options.json) printJson(existing);
2864
- return;
2865
- }
2866
- localState = existing;
2867
- localState.status = "running";
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
- } else {
2876
- localState = {
2877
- agentId,
2878
- taskName,
2879
- message: options.message,
2880
- status: "running",
2881
- sessionCount: 0,
2882
- totalCost: 0,
2883
- context: {},
2884
- lastOutput: "",
2885
- sessions: [],
2886
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2887
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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
- let interrupted = false;
2891
- const onSigint = () => {
2892
- if (interrupted) process.exit(1);
2893
- interrupted = true;
2894
- console.log(import_chalk15.default.yellow("\n\nInterrupted \u2014 saving state..."));
2895
- localState.status = "paused";
2896
- saveState(filePath, localState);
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
- console.log(
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
- ` Sessions: ${priorSessionCount > 0 ? `${priorSessionCount} done, ` : ""}${remainingSessions} remaining${maxCost ? ` | Budget: $${maxCost.toFixed(2)}` : ""}`
3096
+ ` Resume with: ${buildResumeCommand(agent, options.message, options, parsedSandbox)}`
2912
3097
  )
2913
3098
  );
2914
- console.log(import_chalk15.default.gray(` State: ${filePath}
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
- try {
2917
- const result = await client.agents.runTask(agentId, {
2918
- buildMessages: (state) => {
2919
- const merged = { ...localState.context, ...state.context };
2920
- const isFirstSession = state.sessionCount === 1;
2921
- const totalSession = priorSessionCount + state.sessionCount;
2922
- if (isFirstSession && !options.resume) {
2923
- return [{ role: "user", content: options.message }];
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
- const progress = merged.progressNotes || merged.progress;
2926
- const progressStr = progress ? typeof progress === "string" ? progress : JSON.stringify(progress) : "No structured progress captured yet";
2927
- const resumeCtx = options.resume && isFirstSession ? `
2928
-
2929
- Resuming from session ${priorSessionCount}. Previous context: ${JSON.stringify(localState.context)}` : "";
2930
- return [
2931
- {
2932
- role: "user",
2933
- content: `${options.message}${resumeCtx}
2934
-
2935
- Progress so far: ${progressStr}
2936
- Session ${totalSession} of ${maxSessions}.`
2937
- }
2938
- ];
2939
- },
2940
- maxSessions: remainingSessions,
2941
- maxCost,
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
- localState.status = result.status;
2975
- localState.totalCost = priorCost + result.totalCost;
2976
- Object.assign(localState.context, result.context);
2977
- saveState(filePath, localState);
2978
- process.removeListener("SIGINT", onSigint);
2979
- console.log();
2980
- const statusColor = result.status === "complete" ? import_chalk15.default.green : result.status === "budget_exceeded" ? import_chalk15.default.red : import_chalk15.default.yellow;
2981
- console.log(`Status: ${statusColor(localState.status)}`);
2982
- console.log(`Sessions: ${localState.sessionCount}`);
2983
- console.log(`Total cost: ${import_chalk15.default.yellow(`$${localState.totalCost.toFixed(4)}`)}`);
2984
- console.log(`State: ${import_chalk15.default.gray(filePath)}`);
2985
- if (localState.status === "paused" || localState.status === "max_sessions") {
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.gray(
3180
+ import_chalk15.default.cyan(
2988
3181
  `
2989
- Resume: runtype agents run-task ${agent} -m "${options.message}" --resume${options.name ? ` --name ${options.name}` : ""}`
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
- if (options.json) {
2994
- console.log();
2995
- printJson(localState);
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
- } catch (error) {
2998
- localState.status = "paused";
2999
- saveState(filePath, localState);
3000
- process.removeListener("SIGINT", onSigint);
3001
- const message = error instanceof Error ? error.message : "Unknown error";
3002
- console.error(import_chalk15.default.red(`
3003
- Task failed: ${message}`));
3004
- console.log(import_chalk15.default.gray(`State saved to ${filePath} \u2014 resume with --resume`));
3005
- process.exit(1);
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(runTaskCommand);
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
- if (!process.argv.slice(2).length) {
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(process.argv);
4428
+ program.parse(cleanArgs, { from: "user" });
4086
4429
  }
4087
4430
  } catch (error) {
4088
4431
  const commanderError = error;