@runtypelabs/cli 2.46.2 → 2.47.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.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/dist/index.js +1189 -183
  3. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { Command, InvalidArgumentError, Option } from "commander";
4
4
  import chalk from "chalk";
5
- import * as fs$1 from "node:fs";
6
- import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
5
+ import * as fs$2 from "node:fs";
6
+ import fs, { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
7
7
  import * as path$2 from "node:path";
8
8
  import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
9
  import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -15,7 +15,7 @@ import { ClientTokensNamespace, DEFAULT_STALL_STOP_AFTER, ENSURE_RELEASE_DEPLOY_
15
15
  import Conf from "conf";
16
16
  import os, { tmpdir } from "os";
17
17
  import path$1, { delimiter, dirname as dirname$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
18
- import fs, { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, unlinkSync, writeFileSync as writeFileSync$1 } from "fs";
18
+ import fs$1, { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, unlinkSync, writeFileSync as writeFileSync$1 } from "fs";
19
19
  import { fileURLToPath, pathToFileURL } from "node:url";
20
20
  import readline from "readline";
21
21
  import { ErrorDisplay, Spinner, StatusBar, StreamOutput, renderMarkdownToTerminal, theme } from "@runtypelabs/ink-components";
@@ -37,6 +37,7 @@ import { createServer } from "node:http";
37
37
  import { createServer as createServer$1 } from "node:net";
38
38
  import { zipSync } from "fflate";
39
39
  import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
40
+ import WebSocket from "ws";
40
41
  //#region \0rolldown/runtime.js
41
42
  var __defProp = Object.defineProperty;
42
43
  var __exportAll = (all, no_symbols) => {
@@ -7492,7 +7493,7 @@ const UNIFIED_SSE_EVENT_TYPES = _unifiedTuple([
7492
7493
  * Canonical record type guard: a non-null object that is NOT an array.
7493
7494
  * This is the array-EXCLUDING variant — `isRecord([])` is false.
7494
7495
  */
7495
- function isRecord$1(value) {
7496
+ function isRecord$3(value) {
7496
7497
  return typeof value === "object" && value !== null && !Array.isArray(value);
7497
7498
  }
7498
7499
  //#endregion
@@ -11142,19 +11143,19 @@ const toolElicitationConfigSchema = strictObject({
11142
11143
  });
11143
11144
  function canonicalToolInputDefinition(value) {
11144
11145
  if (Array.isArray(value)) return `[${value.map(canonicalToolInputDefinition).join(",")}]`;
11145
- if (isRecord$1(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalToolInputDefinition(value[key])}`).join(",")}}`;
11146
+ if (isRecord$3(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalToolInputDefinition(value[key])}`).join(",")}}`;
11146
11147
  return JSON.stringify(value) ?? "null";
11147
11148
  }
11148
11149
  /** Validate authored input against the final dispatch schema at every definition boundary. */
11149
11150
  function validateToolElicitationDefinition(tool, ctx) {
11150
- if (!isRecord$1(tool)) {
11151
+ if (!isRecord$3(tool)) {
11151
11152
  ctx.addIssue({
11152
11153
  code: "custom",
11153
11154
  message: "Runtime tool definition must be an object"
11154
11155
  });
11155
11156
  return;
11156
11157
  }
11157
- const config = isRecord$1(tool.config) ? tool.config : void 0;
11158
+ const config = isRecord$3(tool.config) ? tool.config : void 0;
11158
11159
  if (config?.elicit === void 0) return;
11159
11160
  const issue = (message) => {
11160
11161
  ctx.addIssue({
@@ -11188,7 +11189,7 @@ function validateToolElicitationDefinition(tool, ctx) {
11188
11189
  "required",
11189
11190
  "additionalProperties"
11190
11191
  ]);
11191
- if (!isSupportedElicitationSchema(final) || !isRecord$1(final) || final.type !== "object" || final.additionalProperties !== false || !isRecord$1(final.properties) || Object.keys(final).some((key) => !allowedRootKeys.has(key))) {
11192
+ if (!isSupportedElicitationSchema(final) || !isRecord$3(final) || final.type !== "object" || final.additionalProperties !== false || !isRecord$3(final.properties) || Object.keys(final).some((key) => !allowedRootKeys.has(key))) {
11192
11193
  issue("Final parametersSchema must be a closed, projectable object without cross-field constraints");
11193
11194
  return;
11194
11195
  }
@@ -13381,6 +13382,18 @@ _enum([
13381
13382
  * fragments, and the agent-facing skill references.
13382
13383
  */
13383
13384
  const DISPATCH_TRANSIENT_SECRETS_RETIRED_NOTE = `Retired on BOTH dispatch targets. A NON-EMPTY map is rejected with 400: RUNTIME_AGENT_TRANSIENT_SECRETS_UNSUPPORTED on an agent dispatch, RUNTIME_FLOW_TRANSIENT_SECRETS_UNSUPPORTED on a flow dispatch (the flow target used to drop the map silently). An empty map is accepted and does nothing. Store credentials as managed secrets and reference them as {{secret:NAME}}. The field is kept on the wire for compatibility only.`;
13385
+ /**
13386
+ * The normalizer names the ingest records, restated here because `@runtypelabs/shared` cannot import
13387
+ * `@runtypelabs/execution-ingest`. Pinned against the ingest's own constants by
13388
+ * `apps/api/tests/connect-agent-framework-producers.test.ts`.
13389
+ */
13390
+ const TRACE_NORMALIZER_NAMES = {
13391
+ flue: "runtype-ingest/flue-normalizer",
13392
+ openInference: "runtype-ingest/openinference-normalizer",
13393
+ aiSdk: "runtype-ingest/ai-sdk-normalizer",
13394
+ cloudflareAgents: "runtype-ingest/cloudflare-agents-normalizer"
13395
+ };
13396
+ TRACE_NORMALIZER_NAMES.flue, TRACE_NORMALIZER_NAMES.aiSdk, TRACE_NORMALIZER_NAMES.openInference, TRACE_NORMALIZER_NAMES.openInference, TRACE_NORMALIZER_NAMES.cloudflareAgents;
13384
13397
  //#endregion
13385
13398
  //#region ../shared/dist/container-sandbox-timeout.mjs
13386
13399
  /**
@@ -14784,9 +14797,9 @@ function walkSteps$1(steps, basePath, context, state) {
14784
14797
  steps.forEach((step, index) => walkStep(step, `${basePath}[${index}]`, context, state));
14785
14798
  }
14786
14799
  function walkStep(node, path, context, state) {
14787
- if (!isRecord$1(node)) return;
14800
+ if (!isRecord$3(node)) return;
14788
14801
  if (node.enabled === false && !state.includeDisabled) return;
14789
- const config = isRecord$1(node.config) ? node.config : void 0;
14802
+ const config = isRecord$3(node.config) ? node.config : void 0;
14790
14803
  if (!config) return;
14791
14804
  state.visitor.visitStep?.(node, config, path, context);
14792
14805
  if (node.type === "execute-agent") state.visitor.visitExecuteAgent(node, config, path, context);
@@ -14807,21 +14820,21 @@ function walkStep(node, path, context, state) {
14807
14820
  conditionalDepth: state.conditionalDepth + 1
14808
14821
  };
14809
14822
  if (Array.isArray(config.branches)) config.branches.forEach((branch, branchIndex) => {
14810
- if (!isRecord$1(branch)) return;
14823
+ if (!isRecord$3(branch)) return;
14811
14824
  walkSteps$1(branch.steps, `${path}.config.branches[${branchIndex}].steps`, context, nested);
14812
14825
  });
14813
14826
  for (const key of CONDITIONAL_BRANCH_STEP_KEYS$2) walkSteps$1(config[key], `${path}.config.${key}`, context, nested);
14814
14827
  }
14815
14828
  function visitFlowFallbacks(config, path, context, state) {
14816
14829
  const { visitFlowFallback, visitStepFallback } = state.visitor;
14817
- if (!visitFlowFallback && !visitStepFallback || !isRecord$1(config.errorHandling)) return;
14830
+ if (!visitFlowFallback && !visitStepFallback || !isRecord$3(config.errorHandling)) return;
14818
14831
  const fallbacks = config.errorHandling.fallbacks;
14819
14832
  if (!Array.isArray(fallbacks)) return;
14820
14833
  fallbacks.forEach((fallback, index) => {
14821
- if (!isRecord$1(fallback)) return;
14834
+ if (!isRecord$3(fallback)) return;
14822
14835
  const fallbackPath = `${path}.config.errorHandling.fallbacks[${index}]`;
14823
14836
  if (fallback.type === "flow") visitFlowFallback?.(fallback, fallbackPath, context);
14824
- else if (fallback.type === "step" && visitStepFallback && isRecord$1(fallback.stepConfig)) {
14837
+ else if (fallback.type === "step" && visitStepFallback && isRecord$3(fallback.stepConfig)) {
14825
14838
  visitStepFallback(fallback, fallback.stepConfig, fallbackPath, context);
14826
14839
  walkToolPayloads(fallback.stepConfig.tools, `${fallbackPath}.stepConfig.tools`, context, state);
14827
14840
  }
@@ -14830,7 +14843,7 @@ function visitFlowFallbacks(config, path, context, state) {
14830
14843
  const TOOL_BAG_KEYS = ["runtimeTools", "inlineTools"];
14831
14844
  /** Descend the inline flow-as-tool and skill payloads a step's tool bag can carry. */
14832
14845
  function walkToolPayloads(tools, path, context, state) {
14833
- if (!isRecord$1(tools)) return;
14846
+ if (!isRecord$3(tools)) return;
14834
14847
  for (const key of TOOL_BAG_KEYS) {
14835
14848
  const bag = tools[key];
14836
14849
  if (!Array.isArray(bag)) continue;
@@ -14844,12 +14857,12 @@ function walkRuntimeTools(runtimeTools, basePath, context, state) {
14844
14857
  walkDepth: state.walkDepth + 1
14845
14858
  };
14846
14859
  runtimeTools.forEach((tool, index) => {
14847
- if (!isRecord$1(tool)) return;
14848
- const config = isRecord$1(tool.config) ? tool.config : void 0;
14860
+ if (!isRecord$3(tool)) return;
14861
+ const config = isRecord$3(tool.config) ? tool.config : void 0;
14849
14862
  if (!config) return;
14850
14863
  const toolPath = `${basePath}[${index}].config`;
14851
- if (isRecord$1(config.skill) && Array.isArray(config.skill.resolvedTools)) walkRuntimeTools(config.skill.resolvedTools, `${toolPath}.skill.resolvedTools`, context, deeper);
14852
- if (!state.skipInlineFlows && isRecord$1(config.flow) && Array.isArray(config.flow.steps)) {
14864
+ if (isRecord$3(config.skill) && Array.isArray(config.skill.resolvedTools)) walkRuntimeTools(config.skill.resolvedTools, `${toolPath}.skill.resolvedTools`, context, deeper);
14865
+ if (!state.skipInlineFlows && isRecord$3(config.flow) && Array.isArray(config.flow.steps)) {
14853
14866
  const nestedContext = state.visitor.enterNestedFlow?.(config.flow, context) ?? context;
14854
14867
  walkSteps$1(config.flow.steps, `${toolPath}.flow.steps`, nestedContext, {
14855
14868
  ...deeper,
@@ -15963,7 +15976,7 @@ const EGRESS_PROVIDER_REMEDIATION = "Set sandboxProvider to \"cloudflare-worker\
15963
15976
  * Returns every issue so validators can surface them together.
15964
15977
  */
15965
15978
  function collectCustomToolEgressIssues(config) {
15966
- if (!isRecord$1(config)) return [];
15979
+ if (!isRecord$3(config)) return [];
15967
15980
  const issues = [];
15968
15981
  const provider = resolveSandboxProvider(config.sandboxProvider);
15969
15982
  const networkParse = config.networkAccess === void 0 ? void 0 : sandboxNetworkAccessSchema.safeParse(config.networkAccess);
@@ -18889,7 +18902,7 @@ function snakeToCamel(str) {
18889
18902
  * Input is unknown (not ApprovalConfig) because values come from unvalidated stored JSONB.
18890
18903
  */
18891
18904
  function isEffectiveApprovalPolicy(approval) {
18892
- if (!isRecord$1(approval)) return false;
18905
+ if (!isRecord$3(approval)) return false;
18893
18906
  const require = approval.require;
18894
18907
  if (require === true) return true;
18895
18908
  return Array.isArray(require) && require.length > 0;
@@ -18900,8 +18913,8 @@ function isEffectiveApprovalPolicy(approval) {
18900
18913
  * Applied to every step type, not just prompt, because step configs are stored JSONB read without re-parsing.
18901
18914
  */
18902
18915
  function configRequiresApproval(config) {
18903
- if (!isRecord$1(config)) return false;
18904
- return isEffectiveApprovalPolicy((isRecord$1(config.tools) ? config.tools : void 0)?.approval);
18916
+ if (!isRecord$3(config)) return false;
18917
+ return isEffectiveApprovalPolicy((isRecord$3(config.tools) ? config.tools : void 0)?.approval);
18905
18918
  }
18906
18919
  /**
18907
18920
  * System-scope variables (`_flow`, `_user`, `_execution`, `_record`) carry
@@ -18984,7 +18997,7 @@ for (const snakeKey of Object.keys(SNAKE_CASE_TOOL_KEYS)) if (!(snakeToCamel(sna
18984
18997
  */
18985
18998
  const LEGACY_TOOL_KEY_SUGGESTIONS = { builtinTools: "toolIds" };
18986
18999
  /** Alias of the canonical `isRecord` guard, kept for existing flow-validator consumers. */
18987
- const isObjectRecord$1 = isRecord$1;
19000
+ const isObjectRecord$1 = isRecord$3;
18988
19001
  /**
18989
19002
  * Sandbox providers that are network-isolated (fail-closed on egress) by default.
18990
19003
  * runtype-sandbox/cloudflare-sandbox included: DO egress treats unset network-access as 'off' (post-Fable handoff 05 F1).
@@ -21336,7 +21349,7 @@ function validateCodeModeConfig(value, stepTools, stepIndex, stepRef) {
21336
21349
  function validateCustomToolEgress(tools, stepIndex, stepRef, buckets) {
21337
21350
  if (!Array.isArray(tools.runtimeTools)) return;
21338
21351
  tools.runtimeTools.forEach((tool, toolIndex) => {
21339
- if (!isRecord$1(tool) || tool.toolType !== "custom") return;
21352
+ if (!isRecord$3(tool) || tool.toolType !== "custom") return;
21340
21353
  const toolName = typeof tool.name === "string" ? tool.name : `#${toolIndex}`;
21341
21354
  for (const issue of collectCustomToolEgressIssues(tool.config)) addIssue("error", {
21342
21355
  code: issue.code,
@@ -21740,18 +21753,6 @@ function extractSecretReferencesFromAnyValue(value, maxDepth = 10) {
21740
21753
  }
21741
21754
  }
21742
21755
  Math.ceil(26214400 * (4 / 3)) + 16;
21743
- /**
21744
- * The normalizer names the ingest records, restated here because `@runtypelabs/shared` cannot import
21745
- * `@runtypelabs/execution-ingest`. Pinned against the ingest's own constants by
21746
- * `apps/api/tests/connect-agent-framework-producers.test.ts`.
21747
- */
21748
- const TRACE_NORMALIZER_NAMES = {
21749
- flue: "runtype-ingest/flue-normalizer",
21750
- openInference: "runtype-ingest/openinference-normalizer",
21751
- aiSdk: "runtype-ingest/ai-sdk-normalizer",
21752
- cloudflareAgents: "runtype-ingest/cloudflare-agents-normalizer"
21753
- };
21754
- TRACE_NORMALIZER_NAMES.flue, TRACE_NORMALIZER_NAMES.aiSdk, TRACE_NORMALIZER_NAMES.openInference, TRACE_NORMALIZER_NAMES.openInference;
21755
21756
  Object.freeze([
21756
21757
  Object.freeze({
21757
21758
  id: "overview",
@@ -23791,7 +23792,7 @@ function appendForwardedCoreIssues(flow, base, result) {
23791
23792
  const coreResult = validateFlowStructure(parsed.data);
23792
23793
  const push = (issue, severity) => {
23793
23794
  if (!FORWARDED_CORE_CODES.has(issue.code)) return;
23794
- (severity === "error" ? result.errors : result.warnings).push(createIssue(severity, issue.code, issue.message, reprefixCorePath(issue.path, base), void 0, isRecord$1(issue.details) ? issue.details : void 0));
23795
+ (severity === "error" ? result.errors : result.warnings).push(createIssue(severity, issue.code, issue.message, reprefixCorePath(issue.path, base), void 0, isRecord$3(issue.details) ? issue.details : void 0));
23795
23796
  };
23796
23797
  for (const issue of coreResult.errors) push(issue, "error");
23797
23798
  for (const issue of coreResult.warnings) push(issue, "warning");
@@ -24143,37 +24144,37 @@ const CONDITIONAL_BRANCH_STEP_KEYS = [
24143
24144
  function scanRuntimeTools(tools, acc, depth) {
24144
24145
  if (depth > 10 || !Array.isArray(tools)) return;
24145
24146
  for (const tool of tools) {
24146
- if (!isRecord$1(tool) || tool.enabled === false) continue;
24147
+ if (!isRecord$3(tool) || tool.enabled === false) continue;
24147
24148
  if (isClientExecutedToolType(tool.toolType)) acc.hasClientExecutedTools = true;
24148
- const config = isRecord$1(tool.config) ? tool.config : void 0;
24149
+ const config = isRecord$3(tool.config) ? tool.config : void 0;
24149
24150
  if (!config) continue;
24150
24151
  if (config.elicit !== void 0 && depth > 0) acc.hasUnsupportedToolInput = true;
24151
- if (isRecord$1(config.flow)) scanSteps(config.flow.steps, acc, depth + 1);
24152
- const inlineAgent = isRecord$1(config.agent) ? config.agent : isRecord$1(config.subAgent) ? config.subAgent : void 0;
24152
+ if (isRecord$3(config.flow)) scanSteps(config.flow.steps, acc, depth + 1);
24153
+ const inlineAgent = isRecord$3(config.agent) ? config.agent : isRecord$3(config.subAgent) ? config.subAgent : void 0;
24153
24154
  if (inlineAgent) scanAgentDefinition(inlineAgent, acc, depth + 1);
24154
24155
  }
24155
24156
  }
24156
24157
  function scanToolsBlock(tools, acc, depth) {
24157
- if (!isRecord$1(tools)) return;
24158
+ if (!isRecord$3(tools)) return;
24158
24159
  scanRuntimeTools(tools.runtimeTools, acc, depth);
24159
24160
  }
24160
24161
  function scanSteps(steps, acc, depth) {
24161
24162
  if (depth > 10 || !Array.isArray(steps)) return;
24162
24163
  for (const step of steps) {
24163
- if (!isRecord$1(step) || step.enabled === false) continue;
24164
+ if (!isRecord$3(step) || step.enabled === false) continue;
24164
24165
  if (typeof step.type === "string") acc.stepTypes.add(step.type);
24165
- const config = isRecord$1(step.config) ? step.config : void 0;
24166
+ const config = isRecord$3(step.config) ? step.config : void 0;
24166
24167
  if (!config) continue;
24167
24168
  scanToolsBlock(config.tools, acc, depth);
24168
24169
  scanSteps(config.steps, acc, depth + 1);
24169
24170
  for (const key of CONDITIONAL_BRANCH_STEP_KEYS) scanSteps(config[key], acc, depth + 1);
24170
24171
  if (!Array.isArray(config.branches)) continue;
24171
- for (const branch of config.branches) if (isRecord$1(branch)) scanSteps(branch.steps, acc, depth + 1);
24172
+ for (const branch of config.branches) if (isRecord$3(branch)) scanSteps(branch.steps, acc, depth + 1);
24172
24173
  }
24173
24174
  }
24174
24175
  /** Read the tools block from either supported inline FPO agent shape. */
24175
24176
  function agentToolsBlock(agent) {
24176
- if (isRecord$1(agent.config)) return agent.config.tools;
24177
+ if (isRecord$3(agent.config)) return agent.config.tools;
24177
24178
  return agent.toolsConfig;
24178
24179
  }
24179
24180
  function scanAgentDefinition(agent, acc, depth) {
@@ -24181,9 +24182,9 @@ function scanAgentDefinition(agent, acc, depth) {
24181
24182
  scanToolsBlock(agentToolsBlock(agent), acc, depth);
24182
24183
  if (!Array.isArray(agent.capabilities)) return;
24183
24184
  for (const capability of agent.capabilities) {
24184
- if (!isRecord$1(capability)) continue;
24185
- if (isRecord$1(capability.flow)) scanSteps(capability.flow.steps, acc, depth + 1);
24186
- if (isRecord$1(capability.subAgent)) scanAgentDefinition(capability.subAgent, acc, depth + 1);
24185
+ if (!isRecord$3(capability)) continue;
24186
+ if (isRecord$3(capability.flow)) scanSteps(capability.flow.steps, acc, depth + 1);
24187
+ if (isRecord$3(capability.subAgent)) scanAgentDefinition(capability.subAgent, acc, depth + 1);
24187
24188
  }
24188
24189
  }
24189
24190
  /**
@@ -24194,10 +24195,10 @@ function scanAgentDefinition(agent, acc, depth) {
24194
24195
  function referencedCapabilityIds(agent) {
24195
24196
  const ids = [];
24196
24197
  if (Array.isArray(agent.capabilityToolRefs)) {
24197
- for (const ref of agent.capabilityToolRefs) if (isRecord$1(ref) && typeof ref.capabilityId === "string") ids.push(ref.capabilityId);
24198
+ for (const ref of agent.capabilityToolRefs) if (isRecord$3(ref) && typeof ref.capabilityId === "string") ids.push(ref.capabilityId);
24198
24199
  }
24199
24200
  const tools = agentToolsBlock(agent);
24200
- const toolIds = isRecord$1(tools) ? tools.toolIds : void 0;
24201
+ const toolIds = isRecord$3(tools) ? tools.toolIds : void 0;
24201
24202
  if (Array.isArray(toolIds)) {
24202
24203
  for (const toolId of toolIds) if (typeof toolId === "string" && toolId.startsWith("capability:")) ids.push(toolId.slice(11));
24203
24204
  }
@@ -24209,8 +24210,8 @@ function scanCapability(capabilityId, capabilities, acc, visited, nested = false
24209
24210
  visited.add(visitKey);
24210
24211
  const capability = capabilities.get(capabilityId);
24211
24212
  if (!capability) return;
24212
- if (isRecord$1(capability.flow)) scanSteps(capability.flow.steps, acc, nested ? 1 : 0);
24213
- if (!isRecord$1(capability.agent)) return;
24213
+ if (isRecord$3(capability.flow)) scanSteps(capability.flow.steps, acc, nested ? 1 : 0);
24214
+ if (!isRecord$3(capability.agent)) return;
24214
24215
  scanAgentDefinition(capability.agent, acc, 0);
24215
24216
  for (const ref of referencedCapabilityIds(capability.agent)) scanCapability(ref, capabilities, acc, visited, true);
24216
24217
  }
@@ -24233,7 +24234,7 @@ function agUiServerAuthoredPauseIssues(fpo) {
24233
24234
  if (surfaces.length === 0) return [];
24234
24235
  const capabilities = /* @__PURE__ */ new Map();
24235
24236
  for (const capability of Array.isArray(fpo.capabilities) ? fpo.capabilities : []) {
24236
- if (!isRecord$1(capability) || typeof capability.id !== "string") continue;
24237
+ if (!isRecord$3(capability) || typeof capability.id !== "string") continue;
24237
24238
  if (!capabilities.has(capability.id)) capabilities.set(capability.id, capability);
24238
24239
  }
24239
24240
  const reasonsByCapability = /* @__PURE__ */ new Map();
@@ -24256,11 +24257,11 @@ function agUiServerAuthoredPauseIssues(fpo) {
24256
24257
  };
24257
24258
  const issues = [];
24258
24259
  for (const [si, surface] of surfaces.entries()) {
24259
- if (!isRecord$1(surface) || surface.type !== "ag-ui") continue;
24260
+ if (!isRecord$3(surface) || surface.type !== "ag-ui") continue;
24260
24261
  const surfaceName = typeof surface.name === "string" ? surface.name : "ag-ui";
24261
24262
  const routes = Array.isArray(surface.routes) ? surface.routes : [];
24262
24263
  for (const [ri, route] of routes.entries()) {
24263
- if (!isRecord$1(route) || typeof route.capabilityId !== "string") continue;
24264
+ if (!isRecord$3(route) || typeof route.capabilityId !== "string") continue;
24264
24265
  const reasons = reasonsFor(route.capabilityId);
24265
24266
  if (reasons.length === 0) continue;
24266
24267
  issues.push(createIssue("warning", AG_UI_SERVER_AUTHORED_PAUSE_CODE, pauseMessage(surfaceName, route.capabilityId, reasons), `surfaces[${si}].routes[${ri}].capabilityId`, suggestedFixFor(reasons), { reasons }));
@@ -26040,7 +26041,7 @@ const MANAGED_DEPLOYMENT_STATUSES = [
26040
26041
  ];
26041
26042
  const managedDeploymentEnvironmentSchema = _enum(MANAGED_SANDBOX_ENVIRONMENTS);
26042
26043
  const managedSandboxCapabilitySchema = _enum(MANAGED_SANDBOX_CAPABILITIES);
26043
- const identifierSchema = string().min(1).max(200);
26044
+ const identifierSchema$1 = string().min(1).max(200);
26044
26045
  const sha256HexSchema = string().regex(/^[0-9a-f]{64}$/, "expected a lowercase hex SHA-256 digest");
26045
26046
  /**
26046
26047
  * The complete profile identity persisted with a deployment, repeating the
@@ -26048,11 +26049,11 @@ const sha256HexSchema = string().regex(/^[0-9a-f]{64}$/, "expected a lowercase h
26048
26049
  * stored fewer could be admitted against a drifted profile.
26049
26050
  */
26050
26051
  const managedSandboxProfileIdentitySchema = object({
26051
- providerId: identifierSchema,
26052
- profileId: identifierSchema,
26053
- sdkVersion: identifierSchema,
26054
- runtimeClass: identifierSchema,
26055
- accountTier: identifierSchema,
26052
+ providerId: identifierSchema$1,
26053
+ profileId: identifierSchema$1,
26054
+ sdkVersion: identifierSchema$1,
26055
+ runtimeClass: identifierSchema$1,
26056
+ accountTier: identifierSchema$1,
26056
26057
  environment: managedDeploymentEnvironmentSchema
26057
26058
  }).strict();
26058
26059
  /**
@@ -26061,10 +26062,10 @@ const managedSandboxProfileIdentitySchema = object({
26061
26062
  */
26062
26063
  const managedDeploymentCredentialAuthoritySchema = discriminatedUnion("kind", [object({
26063
26064
  kind: literal("organization-connection"),
26064
- connectionId: identifierSchema
26065
+ connectionId: identifierSchema$1
26065
26066
  }).strict(), object({
26066
26067
  kind: literal("platform-managed"),
26067
- providerPolicyId: identifierSchema
26068
+ providerPolicyId: identifierSchema$1
26068
26069
  }).strict()]);
26069
26070
  const managedDeploymentAvailabilitySchema = _enum(["on-demand", "warm"]);
26070
26071
  const managedDeploymentRestartPolicySchema = _enum([
@@ -26082,11 +26083,11 @@ const managedDeploymentHealthSchema = discriminatedUnion("kind", [object({ kind:
26082
26083
  expectedStatus: number$1().int().min(100).max(599)
26083
26084
  }).strict()]);
26084
26085
  const managedDeploymentSourceRefSchema = object({
26085
- sourceAssetId: identifierSchema,
26086
+ sourceAssetId: identifierSchema$1,
26086
26087
  sourceHash: sha256HexSchema
26087
26088
  }).strict();
26088
26089
  const managedDeploymentRuntimeProfileRefSchema = object({
26089
- runtimeProfileId: identifierSchema,
26090
+ runtimeProfileId: identifierSchema$1,
26090
26091
  runtimeProfileDigest: sha256HexSchema
26091
26092
  }).strict();
26092
26093
  object({
@@ -26110,7 +26111,7 @@ object({
26110
26111
  restartPolicy: managedDeploymentRestartPolicySchema,
26111
26112
  ingress: managedDeploymentIngressSchema,
26112
26113
  health: managedDeploymentHealthSchema,
26113
- networkPolicyRef: identifierSchema
26114
+ networkPolicyRef: identifierSchema$1
26114
26115
  }).strict(),
26115
26116
  displayName: string().min(1).max(200).optional()
26116
26117
  }).strict();
@@ -26508,7 +26509,7 @@ function createConf(encryptionKey) {
26508
26509
  cwd: CREDENTIALS_DIR
26509
26510
  });
26510
26511
  }
26511
- function isCorruptStoreError(error) {
26512
+ function isCorruptStoreError$1(error) {
26512
26513
  if (error instanceof SyntaxError) return true;
26513
26514
  const message = error instanceof Error ? error.message : String(error);
26514
26515
  return typeof message === "string" && message.includes("JSON");
@@ -26521,9 +26522,9 @@ var CredentialStore = class {
26521
26522
  try {
26522
26523
  this.config = createConf(this.encryptionKey);
26523
26524
  } catch (error) {
26524
- if (isCorruptStoreError(error)) {
26525
+ if (isCorruptStoreError$1(error)) {
26525
26526
  try {
26526
- if (fs.existsSync(CREDENTIALS_PATH)) fs.unlinkSync(CREDENTIALS_PATH);
26527
+ if (fs$1.existsSync(CREDENTIALS_PATH)) fs$1.unlinkSync(CREDENTIALS_PATH);
26527
26528
  } catch {}
26528
26529
  this.config = createConf(this.encryptionKey);
26529
26530
  } else throw error;
@@ -32020,7 +32021,7 @@ function formatToolMetadata(metadata) {
32020
32021
  if (!metadata || Object.keys(metadata).length === 0) return void 0;
32021
32022
  const { judgment, ...rest } = metadata;
32022
32023
  const parts = [];
32023
- if (isRecord$1(judgment)) {
32024
+ if (isRecord$3(judgment)) {
32024
32025
  const model = judgment.resolvedModelId ?? judgment.requestedModel ?? "unknown";
32025
32026
  const cost = typeof judgment.estimatedCostUsd === "number" ? `$${judgment.estimatedCostUsd.toFixed(6)}` : "unknown";
32026
32027
  parts.push(`judgment: model ${String(model)}, key source ${String(judgment.keySource ?? "unknown")}, estimated cost ${cost}`);
@@ -37646,7 +37647,7 @@ function applyMaxLines(raw, maxLines) {
37646
37647
  function readFileContent(filePath, snapshots, liveTools, maxLines = 5e3) {
37647
37648
  try {
37648
37649
  return {
37649
- ...applyMaxLines(fs$1.readFileSync(filePath, "utf-8"), maxLines),
37650
+ ...applyMaxLines(fs$2.readFileSync(filePath, "utf-8"), maxLines),
37650
37651
  source: "disk"
37651
37652
  };
37652
37653
  } catch {}
@@ -37664,7 +37665,7 @@ function readFileContent(filePath, snapshots, liveTools, maxLines = 5e3) {
37664
37665
  }
37665
37666
  //#endregion
37666
37667
  //#region src/marathon/sandbox.ts
37667
- function isRecord(value) {
37668
+ function isRecord$2(value) {
37668
37669
  return typeof value === "object" && value !== null && !Array.isArray(value);
37669
37670
  }
37670
37671
  function parseSandboxProvider(value) {
@@ -37844,7 +37845,7 @@ function createSandboxLocalTool(client, provider, debugMode) {
37844
37845
  };
37845
37846
  const language = parseSandboxLanguage(provider, args.language);
37846
37847
  const timeout = parseSandboxTimeout(args.timeoutMs, provider);
37847
- const parameters = isRecord(args.parameters) ? args.parameters : {};
37848
+ const parameters = isRecord$2(args.parameters) ? args.parameters : {};
37848
37849
  const gateDecision = evaluateGeneratedRuntimeToolProposal({
37849
37850
  name: "run_sandbox_code",
37850
37851
  description: `Execute code in ${provider}`,
@@ -38176,14 +38177,14 @@ function createEventLogWriter(stateFilePath, initialSessionIndex) {
38176
38177
  let stream = null;
38177
38178
  function ensureDir() {
38178
38179
  if (!dirCreated) {
38179
- fs$1.mkdirSync(eventsDir, { recursive: true });
38180
+ fs$2.mkdirSync(eventsDir, { recursive: true });
38180
38181
  dirCreated = true;
38181
38182
  }
38182
38183
  }
38183
38184
  function ensureStream() {
38184
38185
  if (!stream || stream.destroyed) {
38185
38186
  ensureDir();
38186
- stream = fs$1.createWriteStream(currentPath, { flags: "a" });
38187
+ stream = fs$2.createWriteStream(currentPath, { flags: "a" });
38187
38188
  stream.on("error", () => {});
38188
38189
  }
38189
38190
  }
@@ -38212,8 +38213,8 @@ function createEventLogWriter(stateFilePath, initialSessionIndex) {
38212
38213
  * Returns an empty array if the file doesn't exist. */
38213
38214
  function readSessionEventLog(stateFilePath, sessionIndex) {
38214
38215
  const logPath = sessionEventLogPath(stateFilePath, sessionIndex);
38215
- if (!fs$1.existsSync(logPath)) return [];
38216
- const content = fs$1.readFileSync(logPath, "utf-8");
38216
+ if (!fs$2.existsSync(logPath)) return [];
38217
+ const content = fs$2.readFileSync(logPath, "utf-8");
38217
38218
  const events = [];
38218
38219
  for (const line of content.split("\n")) {
38219
38220
  if (!line.trim()) continue;
@@ -38248,7 +38249,7 @@ function isTreeLogEntry(value) {
38248
38249
  function loadTreeLog(filePath) {
38249
38250
  let raw;
38250
38251
  try {
38251
- raw = fs$1.readFileSync(filePath, "utf-8");
38252
+ raw = fs$2.readFileSync(filePath, "utf-8");
38252
38253
  } catch {
38253
38254
  return null;
38254
38255
  }
@@ -38285,8 +38286,8 @@ function loadTreeLog(filePath) {
38285
38286
  };
38286
38287
  }
38287
38288
  function appendLine(log, entry) {
38288
- fs$1.mkdirSync(path$2.dirname(log.filePath), { recursive: true });
38289
- fs$1.appendFileSync(log.filePath, JSON.stringify(entry) + "\n");
38289
+ fs$2.mkdirSync(path$2.dirname(log.filePath), { recursive: true });
38290
+ fs$2.appendFileSync(log.filePath, JSON.stringify(entry) + "\n");
38290
38291
  log.entries.push(entry);
38291
38292
  if (entry.type !== "head") {
38292
38293
  log.byId.set(entry.id, entry);
@@ -41427,7 +41428,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
41427
41428
  setShowSessionMenu(false);
41428
41429
  try {
41429
41430
  let json;
41430
- if (stateFilePath && fs$1.existsSync(stateFilePath)) json = fs$1.readFileSync(stateFilePath, "utf-8");
41431
+ if (stateFilePath && fs$2.existsSync(stateFilePath)) json = fs$2.readFileSync(stateFilePath, "utf-8");
41431
41432
  else json = JSON.stringify({
41432
41433
  sessionSnapshots,
41433
41434
  content: state.content,
@@ -41450,8 +41451,8 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
41450
41451
  setShowSessionMenu(false);
41451
41452
  try {
41452
41453
  let json;
41453
- if (stateFilePath && fs$1.existsSync(stateFilePath)) {
41454
- const raw = fs$1.readFileSync(stateFilePath, "utf-8");
41454
+ if (stateFilePath && fs$2.existsSync(stateFilePath)) {
41455
+ const raw = fs$2.readFileSync(stateFilePath, "utf-8");
41455
41456
  const parsed = JSON.parse(raw);
41456
41457
  json = JSON.stringify(compactMarathonStateJsonForContextExport(parsed), null, 2);
41457
41458
  } else {
@@ -41477,7 +41478,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
41477
41478
  ]);
41478
41479
  const handleOpenStateFile = useCallback(() => {
41479
41480
  setShowSessionMenu(false);
41480
- if (stateFilePath && fs$1.existsSync(stateFilePath)) {
41481
+ if (stateFilePath && fs$2.existsSync(stateFilePath)) {
41481
41482
  open(stateFilePath);
41482
41483
  showFlash("Opening state file...");
41483
41484
  } else showFlash("No state file yet");
@@ -41872,7 +41873,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
41872
41873
  const file = detailFile ?? trackedFiles[fileCursor];
41873
41874
  if (file) {
41874
41875
  const filePath = path$2.resolve(file.path);
41875
- if (fs$1.existsSync(filePath)) {
41876
+ if (fs$2.existsSync(filePath)) {
41876
41877
  open(filePath);
41877
41878
  showFlash(`Opening ${file.path}`);
41878
41879
  } else showFlash("File not found on disk");
@@ -42387,7 +42388,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
42387
42388
  onOpenDashboard: handleOpenDashboard,
42388
42389
  onClose: () => setShowSessionMenu(false),
42389
42390
  hasDashboard: !!agentPageUrl,
42390
- hasStateFile: !!stateFilePath && fs$1.existsSync(stateFilePath)
42391
+ hasStateFile: !!stateFilePath && fs$2.existsSync(stateFilePath)
42391
42392
  })
42392
42393
  }),
42393
42394
  showUpgradeModal && upgradePrompt && /* @__PURE__ */ jsx(Box, {
@@ -42939,9 +42940,9 @@ function getProjectStateDir(projectDir) {
42939
42940
  const hash = crypto$2.createHash("sha256").update(dir).digest("hex").slice(0, 12);
42940
42941
  const stateDir = path$2.join(getRuntypeHomeDir(), "projects", hash);
42941
42942
  const breadcrumb = path$2.join(stateDir, "project-path.txt");
42942
- if (!fs$1.existsSync(breadcrumb)) {
42943
- fs$1.mkdirSync(stateDir, { recursive: true });
42944
- fs$1.writeFileSync(breadcrumb, dir);
42943
+ if (!fs$2.existsSync(breadcrumb)) {
42944
+ fs$2.mkdirSync(stateDir, { recursive: true });
42945
+ fs$2.writeFileSync(breadcrumb, dir);
42945
42946
  }
42946
42947
  return stateDir;
42947
42948
  }
@@ -42970,14 +42971,14 @@ function ensureMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFileP
42970
42971
  const normalizedTargetPath = path$2.resolve(targetPath);
42971
42972
  const relativeTargetPath = path$2.relative(process.cwd(), normalizedTargetPath);
42972
42973
  if (!relativeTargetPath || relativeTargetPath.startsWith("..")) return void 0;
42973
- if (!fs$1.existsSync(normalizedTargetPath)) return void 0;
42974
- if (!fs$1.statSync(normalizedTargetPath).isFile()) return void 0;
42974
+ if (!fs$2.existsSync(normalizedTargetPath)) return void 0;
42975
+ if (!fs$2.statSync(normalizedTargetPath).isFile()) return void 0;
42975
42976
  if (relativeTargetPath.replace(/\\/g, "/").startsWith(".runtype/")) return void 0;
42976
42977
  if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path$2.sep)) return void 0;
42977
42978
  const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir, stateFilePath);
42978
- if (fs$1.existsSync(checkpointPath)) return checkpointPath;
42979
- fs$1.mkdirSync(path$2.dirname(checkpointPath), { recursive: true });
42980
- fs$1.copyFileSync(normalizedTargetPath, checkpointPath);
42979
+ if (fs$2.existsSync(checkpointPath)) return checkpointPath;
42980
+ fs$2.mkdirSync(path$2.dirname(checkpointPath), { recursive: true });
42981
+ fs$2.copyFileSync(normalizedTargetPath, checkpointPath);
42981
42982
  return checkpointPath;
42982
42983
  }
42983
42984
  function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFilePath) {
@@ -42988,13 +42989,13 @@ function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFile
42988
42989
  error: `Cannot restore checkpoint outside the current repository: ${normalizedTargetPath}`
42989
42990
  };
42990
42991
  const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir, stateFilePath);
42991
- if (!fs$1.existsSync(checkpointPath)) return {
42992
+ if (!fs$2.existsSync(checkpointPath)) return {
42992
42993
  restored: false,
42993
42994
  checkpointPath,
42994
42995
  error: `No checkpoint found for ${normalizedTargetPath}`
42995
42996
  };
42996
- fs$1.mkdirSync(path$2.dirname(normalizedTargetPath), { recursive: true });
42997
- fs$1.copyFileSync(checkpointPath, normalizedTargetPath);
42997
+ fs$2.mkdirSync(path$2.dirname(normalizedTargetPath), { recursive: true });
42998
+ fs$2.copyFileSync(checkpointPath, normalizedTargetPath);
42998
42999
  return {
42999
43000
  restored: true,
43000
43001
  checkpointPath
@@ -43524,7 +43525,7 @@ function normalizeMarathonStatePath(candidatePath) {
43524
43525
  }
43525
43526
  function marathonStatePathExists(candidatePath) {
43526
43527
  if (!candidatePath) return false;
43527
- return fs$1.existsSync(path$2.resolve(candidatePath));
43528
+ return fs$2.existsSync(path$2.resolve(candidatePath));
43528
43529
  }
43529
43530
  function isMarathonArtifactStatePath(candidatePath) {
43530
43531
  if (!candidatePath) return false;
@@ -43685,7 +43686,7 @@ function sanitizeLoadedMarathonState(state) {
43685
43686
  }
43686
43687
  function loadState(filePath) {
43687
43688
  try {
43688
- const raw = fs$1.readFileSync(filePath, "utf-8");
43689
+ const raw = fs$2.readFileSync(filePath, "utf-8");
43689
43690
  return sanitizeLoadedMarathonState(JSON.parse(raw));
43690
43691
  } catch {
43691
43692
  return null;
@@ -43693,7 +43694,7 @@ function loadState(filePath) {
43693
43694
  }
43694
43695
  function saveState(filePath, state, options) {
43695
43696
  const dir = path$2.dirname(filePath);
43696
- fs$1.mkdirSync(dir, { recursive: true });
43697
+ fs$2.mkdirSync(dir, { recursive: true });
43697
43698
  const stateToWrite = options?.stripSnapshotEvents && state.sessionSnapshots?.length ? {
43698
43699
  ...state,
43699
43700
  sessionSnapshots: state.sessionSnapshots.map((snapshot) => ({
@@ -43701,7 +43702,7 @@ function saveState(filePath, state, options) {
43701
43702
  rawEvents: []
43702
43703
  }))
43703
43704
  } : state;
43704
- fs$1.writeFileSync(filePath, JSON.stringify(stateToWrite, null, 2));
43705
+ fs$2.writeFileSync(filePath, JSON.stringify(stateToWrite, null, 2));
43705
43706
  syncTreeLogWithState(filePath, state);
43706
43707
  }
43707
43708
  function extractRunTaskResumeState(state) {
@@ -43747,11 +43748,11 @@ function offsetNewContextCompactionSummaries(summaries, existingSummaryIds, sess
43747
43748
  function findStateFile(name, stateDir) {
43748
43749
  if (stateDir) return stateFilePath(name, stateDir);
43749
43750
  const homePath = stateFilePath(name, getMarathonStateDir());
43750
- if (fs$1.existsSync(homePath)) return homePath;
43751
+ if (fs$2.existsSync(homePath)) return homePath;
43751
43752
  const oldMarathonPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "marathons"));
43752
- if (fs$1.existsSync(oldMarathonPath)) return oldMarathonPath;
43753
+ if (fs$2.existsSync(oldMarathonPath)) return oldMarathonPath;
43753
43754
  const oldTaskPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "tasks"));
43754
- if (fs$1.existsSync(oldTaskPath)) return oldTaskPath;
43755
+ if (fs$2.existsSync(oldTaskPath)) return oldTaskPath;
43755
43756
  return homePath;
43756
43757
  }
43757
43758
  function findLatestStateFile(stateDir) {
@@ -43762,11 +43763,11 @@ function findLatestStateFile(stateDir) {
43762
43763
  ];
43763
43764
  let latest = null;
43764
43765
  for (const dir of dirs) {
43765
- if (!fs$1.existsSync(dir)) continue;
43766
- const files = fs$1.readdirSync(dir).filter((f) => f.endsWith(".json"));
43766
+ if (!fs$2.existsSync(dir)) continue;
43767
+ const files = fs$2.readdirSync(dir).filter((f) => f.endsWith(".json"));
43767
43768
  for (const file of files) {
43768
43769
  const fullPath = path$2.join(dir, file);
43769
- const stat = fs$1.statSync(fullPath);
43770
+ const stat = fs$2.statSync(fullPath);
43770
43771
  if (!latest || stat.mtimeMs > latest.mtime) latest = {
43771
43772
  name: file.replace(".json", ""),
43772
43773
  filePath: fullPath,
@@ -43850,7 +43851,7 @@ async function resolveMarathonStateResolution(options, taskName, filePath, promp
43850
43851
  };
43851
43852
  }
43852
43853
  if (options.fresh) return { action: "fresh" };
43853
- if (!fs$1.existsSync(filePath)) return { action: "fresh" };
43854
+ if (!fs$2.existsSync(filePath)) return { action: "fresh" };
43854
43855
  if (!allowPrompt) throw new Error(`Local marathon state already exists at ${filePath}. Use --resume to continue it or --fresh to start over.`);
43855
43856
  const choice = await promptForChoice({
43856
43857
  reason: "existing-state",
@@ -44130,9 +44131,9 @@ function shouldIgnoreRepoEntry(entryPath) {
44130
44131
  function safeReadTextFile(filePath) {
44131
44132
  try {
44132
44133
  if (isSensitivePath(normalizeToolPath(filePath).replace(/\\/g, "/"))) return null;
44133
- const stat = fs$1.statSync(filePath);
44134
+ const stat = fs$2.statSync(filePath);
44134
44135
  if (!stat.isFile() || stat.size > 1048576) return null;
44135
- const buffer = fs$1.readFileSync(filePath);
44136
+ const buffer = fs$2.readFileSync(filePath);
44136
44137
  if (buffer.includes(0)) return null;
44137
44138
  return buffer.toString("utf-8");
44138
44139
  } catch {
@@ -44140,14 +44141,14 @@ function safeReadTextFile(filePath) {
44140
44141
  }
44141
44142
  }
44142
44143
  function walkRepo(startPath, visitor) {
44143
- if (!fs$1.existsSync(startPath) || !fs$1.statSync(startPath).isDirectory()) return;
44144
+ if (!fs$2.existsSync(startPath) || !fs$2.statSync(startPath).isDirectory()) return;
44144
44145
  const stack = [startPath];
44145
44146
  while (stack.length > 0) {
44146
44147
  const currentDir = stack.pop();
44147
44148
  if (!currentDir) continue;
44148
44149
  let entries;
44149
44150
  try {
44150
- entries = fs$1.readdirSync(currentDir, { withFileTypes: true });
44151
+ entries = fs$2.readdirSync(currentDir, { withFileTypes: true });
44151
44152
  } catch {
44152
44153
  continue;
44153
44154
  }
@@ -44187,7 +44188,7 @@ function buildTree(dirPath, maxDepth, depth = 0) {
44187
44188
  if (depth > maxDepth || shouldIgnoreRepoEntry(dirPath)) return [];
44188
44189
  let entries;
44189
44190
  try {
44190
- entries = fs$1.readdirSync(dirPath, { withFileTypes: true });
44191
+ entries = fs$2.readdirSync(dirPath, { withFileTypes: true });
44191
44192
  } catch (error) {
44192
44193
  const message = error instanceof Error ? error.message : String(error);
44193
44194
  return [`${" ".repeat(depth)}[error reading ${normalizeToolPath(dirPath)}: ${message}]`];
@@ -44224,8 +44225,8 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
44224
44225
  let remainingSegments = absolutePath.slice(canonicalPath.length).split(path$2.sep);
44225
44226
  let symlinksFollowed = 0;
44226
44227
  try {
44227
- if (fs$1.existsSync(absolutePath)) return {
44228
- canonicalPath: fs$1.realpathSync.native(absolutePath),
44228
+ if (fs$2.existsSync(absolutePath)) return {
44229
+ canonicalPath: fs$2.realpathSync.native(absolutePath),
44229
44230
  exists: true
44230
44231
  };
44231
44232
  if (!allowMissing) return null;
@@ -44237,25 +44238,25 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
44237
44238
  continue;
44238
44239
  }
44239
44240
  const candidatePath = path$2.join(canonicalPath, segment);
44240
- const entry = fs$1.lstatSync(candidatePath, { throwIfNoEntry: false });
44241
+ const entry = fs$2.lstatSync(candidatePath, { throwIfNoEntry: false });
44241
44242
  if (!entry) {
44242
44243
  canonicalPath = candidatePath;
44243
44244
  continue;
44244
44245
  }
44245
44246
  if (entry.isSymbolicLink()) {
44246
44247
  if (++symlinksFollowed > MAX_TOOL_PATH_SYMLINKS) return null;
44247
- const target = fs$1.readlinkSync(candidatePath);
44248
+ const target = fs$2.readlinkSync(candidatePath);
44248
44249
  const targetRoot = path$2.isAbsolute(target) ? path$2.parse(target).root : "";
44249
44250
  if (targetRoot) canonicalPath = targetRoot;
44250
44251
  remainingSegments = [...target.slice(targetRoot.length).split(path$2.sep), ...remainingSegments];
44251
44252
  continue;
44252
44253
  }
44253
44254
  if (remainingSegments.length > 0 && !entry.isDirectory()) return null;
44254
- canonicalPath = fs$1.realpathSync.native(candidatePath);
44255
+ canonicalPath = fs$2.realpathSync.native(candidatePath);
44255
44256
  }
44256
44257
  return {
44257
44258
  canonicalPath,
44258
- exists: fs$1.existsSync(canonicalPath)
44259
+ exists: fs$2.existsSync(canonicalPath)
44259
44260
  };
44260
44261
  } catch {
44261
44262
  return null;
@@ -44281,7 +44282,7 @@ function resolveToolPath(toolPath, options = {}) {
44281
44282
  ok: false,
44282
44283
  error: `Path does not exist: ${requestedPath}`
44283
44284
  };
44284
- const workspaceRoot = fs$1.realpathSync.native(process.cwd());
44285
+ const workspaceRoot = fs$2.realpathSync.native(process.cwd());
44285
44286
  const extraRoots = (options.allowedRoots || []).map((rootPath) => canonicalizeAllowedRoot(rootPath));
44286
44287
  const matchedRoot = [...extraRoots, workspaceRoot].find((rootPath) => isPathWithinRoot(resolved.canonicalPath, rootPath));
44287
44288
  if (!matchedRoot) return {
@@ -44302,7 +44303,7 @@ function resolveToolPath(toolPath, options = {}) {
44302
44303
  }
44303
44304
  }
44304
44305
  if (resolved.exists) {
44305
- const stat = fs$1.statSync(resolved.canonicalPath);
44306
+ const stat = fs$2.statSync(resolved.canonicalPath);
44306
44307
  if (options.requireFile && !stat.isFile()) return {
44307
44308
  ok: false,
44308
44309
  error: `Path is not a file: ${requestedPath}`
@@ -44350,7 +44351,7 @@ function createDefaultLocalTools(context) {
44350
44351
  allowedRoots: allowedReadRoots
44351
44352
  });
44352
44353
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
44353
- return fs$1.readFileSync(resolvedPath.resolvedPath, "utf-8");
44354
+ return fs$2.readFileSync(resolvedPath.resolvedPath, "utf-8");
44354
44355
  }
44355
44356
  },
44356
44357
  write_file: {
@@ -44373,10 +44374,10 @@ function createDefaultLocalTools(context) {
44373
44374
  const resolvedPath = resolveToolPath(String(args.path || ""), { allowMissing: true });
44374
44375
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
44375
44376
  const content = String(args.content || "");
44376
- if (fs$1.existsSync(resolvedPath.resolvedPath) && fs$1.statSync(resolvedPath.resolvedPath).isDirectory()) return `Error: Path is a directory: ${String(args.path || "")}`;
44377
+ if (fs$2.existsSync(resolvedPath.resolvedPath) && fs$2.statSync(resolvedPath.resolvedPath).isDirectory()) return `Error: Path is a directory: ${String(args.path || "")}`;
44377
44378
  const dir = path$2.dirname(resolvedPath.resolvedPath);
44378
- fs$1.mkdirSync(dir, { recursive: true });
44379
- fs$1.writeFileSync(resolvedPath.resolvedPath, content);
44379
+ fs$2.mkdirSync(dir, { recursive: true });
44380
+ fs$2.writeFileSync(resolvedPath.resolvedPath, content);
44380
44381
  return "ok";
44381
44382
  }
44382
44383
  },
@@ -44392,7 +44393,7 @@ function createDefaultLocalTools(context) {
44392
44393
  execute: async (args) => {
44393
44394
  const resolvedPath = resolveToolPath(String(args.path || "."), { requireDirectory: true });
44394
44395
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
44395
- return fs$1.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path$2.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
44396
+ return fs$2.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path$2.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
44396
44397
  }
44397
44398
  },
44398
44399
  search_repo: {
@@ -44556,8 +44557,8 @@ function createCheckpointedWriteFileTool(taskName, stateDir, planPath, stateFile
44556
44557
  const content = String(args.content || "");
44557
44558
  ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir, stateFilePath);
44558
44559
  const dir = path$2.dirname(resolvedPath.resolvedPath);
44559
- fs$1.mkdirSync(dir, { recursive: true });
44560
- fs$1.writeFileSync(resolvedPath.resolvedPath, content);
44560
+ fs$2.mkdirSync(dir, { recursive: true });
44561
+ fs$2.writeFileSync(resolvedPath.resolvedPath, content);
44561
44562
  const ext = path$2.extname(resolvedPath.resolvedPath).toLowerCase();
44562
44563
  if (ext === ".js" || ext === ".jsx") {
44563
44564
  const syntaxError = quickJsSyntaxCheck(content);
@@ -44633,7 +44634,7 @@ function createEditFileTool(taskName, stateDir, planPath, stateFilePath) {
44633
44634
  if (oldString === newString) return "Error: old_string and new_string are identical";
44634
44635
  let content;
44635
44636
  try {
44636
- content = fs$1.readFileSync(resolvedPath.resolvedPath, "utf-8");
44637
+ content = fs$2.readFileSync(resolvedPath.resolvedPath, "utf-8");
44637
44638
  } catch {
44638
44639
  return `Error: Could not read file: ${String(args.path || "")}`;
44639
44640
  }
@@ -44644,7 +44645,7 @@ function createEditFileTool(taskName, stateDir, planPath, stateFilePath) {
44644
44645
  }
44645
44646
  ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir, stateFilePath);
44646
44647
  const updated = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
44647
- fs$1.writeFileSync(resolvedPath.resolvedPath, updated);
44648
+ fs$2.writeFileSync(resolvedPath.resolvedPath, updated);
44648
44649
  const ext = path$2.extname(resolvedPath.resolvedPath).toLowerCase();
44649
44650
  if (ext === ".js" || ext === ".jsx") {
44650
44651
  const syntaxError = quickJsSyntaxCheck(updated);
@@ -44696,13 +44697,13 @@ function createReadOffloadedOutputTool(taskName, stateDir, stateFilePath) {
44696
44697
  };
44697
44698
  const idEmbeddedSha256 = /^art_([0-9a-f]{64})$/.exec(outputId)?.[1];
44698
44699
  const outputPath = path$2.join(getOffloadedOutputDir(taskName, stateDir, stateFilePath), `${outputId}.txt`);
44699
- if (!fs$1.existsSync(outputPath) || !fs$1.statSync(outputPath).isFile()) {
44700
+ if (!fs$2.existsSync(outputPath) || !fs$2.statSync(outputPath).isFile()) {
44700
44701
  const resolvedStateFilePath = getStateFilePathForTask(taskName, stateDir, stateFilePath);
44701
44702
  const log = loadTreeLog(treeLogPathForStateFile(resolvedStateFilePath));
44702
44703
  const artifact = log ? findArtifactPayload(log, outputId) : void 0;
44703
44704
  if (artifact) {
44704
44705
  const ledgerPath = path$2.resolve(path$2.dirname(treeLogPathForStateFile(resolvedStateFilePath)), artifact.relativePath);
44705
- if (fs$1.existsSync(ledgerPath) && fs$1.statSync(ledgerPath).isFile()) return verifyContent(fs$1.readFileSync(ledgerPath, "utf-8"), artifact.sha256);
44706
+ if (fs$2.existsSync(ledgerPath) && fs$2.statSync(ledgerPath).isFile()) return verifyContent(fs$2.readFileSync(ledgerPath, "utf-8"), artifact.sha256);
44706
44707
  return [
44707
44708
  `Error: offloaded output recorded in the context ledger but missing locally: ${outputId}`,
44708
44709
  artifact.host ? `Stored on host: ${artifact.host}` : void 0,
@@ -44711,7 +44712,7 @@ function createReadOffloadedOutputTool(taskName, stateDir, stateFilePath) {
44711
44712
  }
44712
44713
  return `Error: offloaded output not found: ${outputId}`;
44713
44714
  }
44714
- return verifyContent(fs$1.readFileSync(outputPath, "utf-8"), idEmbeddedSha256);
44715
+ return verifyContent(fs$2.readFileSync(outputPath, "utf-8"), idEmbeddedSha256);
44715
44716
  }
44716
44717
  };
44717
44718
  }
@@ -44895,7 +44896,7 @@ function resolveLedgerToolResult(tool, stateFilePath, log) {
44895
44896
  if (!artifact) return result;
44896
44897
  const artifactPath = path$2.resolve(path$2.dirname(treeLogPathForStateFile(stateFilePath)), artifact.relativePath);
44897
44898
  try {
44898
- if (fs$1.existsSync(artifactPath) && fs$1.statSync(artifactPath).isFile()) return fs$1.readFileSync(artifactPath, "utf-8");
44899
+ if (fs$2.existsSync(artifactPath) && fs$2.statSync(artifactPath).isFile()) return fs$2.readFileSync(artifactPath, "utf-8");
44899
44900
  } catch {}
44900
44901
  return result;
44901
44902
  }
@@ -45109,8 +45110,8 @@ function offloadToolOutput(taskName, toolName, output, options = {}, stateDir) {
45109
45110
  const dir = artifactStoreDirForStateFile(stateFilePath);
45110
45111
  const fileName = `${outputId}.txt`;
45111
45112
  const filePath = path$2.join(dir, fileName);
45112
- fs$1.mkdirSync(dir, { recursive: true });
45113
- if (!fs$1.existsSync(filePath)) fs$1.writeFileSync(filePath, output, "utf-8");
45113
+ fs$2.mkdirSync(dir, { recursive: true });
45114
+ if (!fs$2.existsSync(filePath)) fs$2.writeFileSync(filePath, output, "utf-8");
45114
45115
  recordOffloadedArtifact({
45115
45116
  taskName,
45116
45117
  toolName,
@@ -45260,14 +45261,14 @@ const RULES_DIR = ".marathon/rules";
45260
45261
  function loadRules(cwd) {
45261
45262
  const baseCwd = cwd || process.cwd();
45262
45263
  const rulesDir = path$2.resolve(baseCwd, RULES_DIR);
45263
- if (!fs$1.existsSync(rulesDir)) return [];
45264
+ if (!fs$2.existsSync(rulesDir)) return [];
45264
45265
  const rules = [];
45265
45266
  try {
45266
- const entries = fs$1.readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
45267
+ const entries = fs$2.readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
45267
45268
  for (const entry of entries) {
45268
45269
  const filePath = path$2.join(rulesDir, entry);
45269
45270
  try {
45270
- const parsed = parseRuleFile(fs$1.readFileSync(filePath, "utf-8"));
45271
+ const parsed = parseRuleFile(fs$2.readFileSync(filePath, "utf-8"));
45271
45272
  rules.push({
45272
45273
  ...parsed,
45273
45274
  filePath
@@ -45443,7 +45444,7 @@ async function loadModulePlaybook(filePath) {
45443
45444
  return config;
45444
45445
  }
45445
45446
  function parsePlaybookFile(filePath) {
45446
- const raw = fs$1.readFileSync(filePath, "utf-8");
45447
+ const raw = fs$2.readFileSync(filePath, "utf-8");
45447
45448
  if (path$2.extname(filePath).toLowerCase() === ".json") return JSON.parse(raw);
45448
45449
  return parse(raw);
45449
45450
  }
@@ -45505,7 +45506,7 @@ async function loadPlaybookPlugins(plugins, playbookFilePath, playbookName) {
45505
45506
  const resolved = path$2.resolve(baseDir, plugin);
45506
45507
  if (resolved !== baseDir && !resolved.startsWith(baseDir + path$2.sep)) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" resolves outside the playbook's directory. Keep plugin modules next to the playbook.`);
45507
45508
  if (!PLUGIN_EXTENSIONS.has(path$2.extname(resolved).toLowerCase())) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" must be a .js, .mjs, or .cjs module.`);
45508
- if (!fs$1.existsSync(resolved)) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" not found at ${resolved}.`);
45509
+ if (!fs$2.existsSync(resolved)) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" not found at ${resolved}.`);
45509
45510
  const mod = await import(pathToFileURL(resolved).href);
45510
45511
  if (typeof mod.default !== "function") throw new Error(`Playbook '${playbookName}': plugin "${plugin}" must export a default function: export default ({ registerWorkflowHook }) => { ... }`);
45511
45512
  await mod.default({ registerWorkflowHook });
@@ -45538,7 +45539,7 @@ function toWorkflowConfig(config) {
45538
45539
  async function loadPlaybook(nameOrPath, cwd) {
45539
45540
  const candidates = getCandidatePaths(nameOrPath, cwd || process.cwd());
45540
45541
  for (const candidate of candidates) {
45541
- if (!fs$1.existsSync(candidate) || fs$1.statSync(candidate).isDirectory()) continue;
45542
+ if (!fs$2.existsSync(candidate) || fs$2.statSync(candidate).isDirectory()) continue;
45542
45543
  const ext = path$2.extname(candidate).toLowerCase();
45543
45544
  const config = MODULE_PLAYBOOK_EXTENSIONS.has(ext) ? await loadModulePlaybook(candidate) : parsePlaybookFile(candidate);
45544
45545
  validatePlaybook(config, candidate);
@@ -45930,7 +45931,7 @@ async function taskAction(agent, options) {
45930
45931
  forcedResumeFilePath = stateResolution.filePath;
45931
45932
  } else {
45932
45933
  resumeRequested = false;
45933
- if (options.fresh && fs$1.existsSync(filePath)) {
45934
+ if (options.fresh && fs$2.existsSync(filePath)) {
45934
45935
  if (useStartupShell) setStartupStatus("starting fresh");
45935
45936
  else console.log(chalk.gray(`Starting fresh and ignoring saved local state at ${filePath}`));
45936
45937
  }
@@ -48764,17 +48765,17 @@ const deployCommand = new Command("deploy").description("Export an agent and sca
48764
48765
  }
48765
48766
  slugSet.add(slug);
48766
48767
  }
48767
- fs$1.mkdirSync(path$2.join(outDir, "agents"), { recursive: true });
48768
- fs$1.mkdirSync(path$2.join(outDir, "packages"), { recursive: true });
48768
+ fs$2.mkdirSync(path$2.join(outDir, "agents"), { recursive: true });
48769
+ fs$2.mkdirSync(path$2.join(outDir, "packages"), { recursive: true });
48769
48770
  for (const { name, def } of agentDefs) {
48770
48771
  const filename = `${slugify(name)}.json`;
48771
- fs$1.writeFileSync(path$2.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
48772
+ fs$2.writeFileSync(path$2.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
48772
48773
  console.log(` Wrote agents/${filename}`);
48773
48774
  }
48774
48775
  const agentNames = agentDefs.map((a) => a.name);
48775
48776
  let monorepoRoot = process.cwd();
48776
48777
  for (let i = 0; i < 8; i++) {
48777
- if (fs$1.existsSync(path$2.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
48778
+ if (fs$2.existsSync(path$2.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
48778
48779
  const parent = path$2.dirname(monorepoRoot);
48779
48780
  if (parent === monorepoRoot) break;
48780
48781
  monorepoRoot = parent;
@@ -48790,35 +48791,35 @@ const deployCommand = new Command("deploy").description("Export an agent and sca
48790
48791
  const secretNames = Array.from(allSecrets);
48791
48792
  const optionalSettings = Array.from(allOptionalSettings.values());
48792
48793
  if (target === "cloudflare") {
48793
- fs$1.mkdirSync(path$2.join(outDir, "src"), { recursive: true });
48794
- fs$1.writeFileSync(path$2.join(outDir, "src", "index.ts"), workerIndexTs(agentSlugs));
48795
- fs$1.writeFileSync(path$2.join(outDir, "wrangler.toml"), wranglerToml(projectName));
48796
- fs$1.writeFileSync(path$2.join(outDir, "package.json"), workerPackageJson(projectName, "workspace:*"));
48797
- fs$1.writeFileSync(path$2.join(outDir, "tsconfig.json"), workerTsconfigJson());
48794
+ fs$2.mkdirSync(path$2.join(outDir, "src"), { recursive: true });
48795
+ fs$2.writeFileSync(path$2.join(outDir, "src", "index.ts"), workerIndexTs(agentSlugs));
48796
+ fs$2.writeFileSync(path$2.join(outDir, "wrangler.toml"), wranglerToml(projectName));
48797
+ fs$2.writeFileSync(path$2.join(outDir, "package.json"), workerPackageJson(projectName, "workspace:*"));
48798
+ fs$2.writeFileSync(path$2.join(outDir, "tsconfig.json"), workerTsconfigJson());
48798
48799
  const setupPath = path$2.join(outDir, "setup.sh");
48799
- fs$1.writeFileSync(setupPath, workerSetupSh(monorepoRoot));
48800
- fs$1.chmodSync(setupPath, 493);
48801
- fs$1.writeFileSync(path$2.join(outDir, "README.md"), workerReadme(agentNames, secretNames, optionalSettings));
48800
+ fs$2.writeFileSync(setupPath, workerSetupSh(monorepoRoot));
48801
+ fs$2.chmodSync(setupPath, 493);
48802
+ fs$2.writeFileSync(path$2.join(outDir, "README.md"), workerReadme(agentNames, secretNames, optionalSettings));
48802
48803
  } else if (target === "vercel") {
48803
- fs$1.mkdirSync(path$2.join(outDir, "api"), { recursive: true });
48804
- fs$1.writeFileSync(path$2.join(outDir, "api", "[[...route]].ts"), vercelRouteTs(agentSlugs));
48805
- fs$1.writeFileSync(path$2.join(outDir, "vercel.json"), vercelJson());
48806
- fs$1.writeFileSync(path$2.join(outDir, "package.json"), vercelPackageJson(projectName, "workspace:*"));
48807
- fs$1.writeFileSync(path$2.join(outDir, "tsconfig.json"), vercelTsconfigJson());
48804
+ fs$2.mkdirSync(path$2.join(outDir, "api"), { recursive: true });
48805
+ fs$2.writeFileSync(path$2.join(outDir, "api", "[[...route]].ts"), vercelRouteTs(agentSlugs));
48806
+ fs$2.writeFileSync(path$2.join(outDir, "vercel.json"), vercelJson());
48807
+ fs$2.writeFileSync(path$2.join(outDir, "package.json"), vercelPackageJson(projectName, "workspace:*"));
48808
+ fs$2.writeFileSync(path$2.join(outDir, "tsconfig.json"), vercelTsconfigJson());
48808
48809
  const setupPath = path$2.join(outDir, "setup.sh");
48809
- fs$1.writeFileSync(setupPath, vercelSetupSh(monorepoRoot));
48810
- fs$1.chmodSync(setupPath, 493);
48811
- fs$1.writeFileSync(path$2.join(outDir, "README.md"), vercelReadme(agentNames, secretNames, optionalSettings));
48810
+ fs$2.writeFileSync(setupPath, vercelSetupSh(monorepoRoot));
48811
+ fs$2.chmodSync(setupPath, 493);
48812
+ fs$2.writeFileSync(path$2.join(outDir, "README.md"), vercelReadme(agentNames, secretNames, optionalSettings));
48812
48813
  } else {
48813
- fs$1.writeFileSync(path$2.join(outDir, "server.ts"), serverTs());
48814
- fs$1.writeFileSync(path$2.join(outDir, "Dockerfile"), dockerfile());
48815
- fs$1.writeFileSync(path$2.join(outDir, ".dockerignore"), dockerignore());
48816
- fs$1.writeFileSync(path$2.join(outDir, "tsconfig.json"), tsconfigJson());
48817
- fs$1.writeFileSync(path$2.join(outDir, "package.json"), packageJson(projectName, "workspace:*"));
48814
+ fs$2.writeFileSync(path$2.join(outDir, "server.ts"), serverTs());
48815
+ fs$2.writeFileSync(path$2.join(outDir, "Dockerfile"), dockerfile());
48816
+ fs$2.writeFileSync(path$2.join(outDir, ".dockerignore"), dockerignore());
48817
+ fs$2.writeFileSync(path$2.join(outDir, "tsconfig.json"), tsconfigJson());
48818
+ fs$2.writeFileSync(path$2.join(outDir, "package.json"), packageJson(projectName, "workspace:*"));
48818
48819
  const setupPath = path$2.join(outDir, "setup.sh");
48819
- fs$1.writeFileSync(setupPath, setupSh(monorepoRoot));
48820
- fs$1.chmodSync(setupPath, 493);
48821
- fs$1.writeFileSync(path$2.join(outDir, "README.md"), readme(agentNames, secretNames, optionalSettings));
48820
+ fs$2.writeFileSync(setupPath, setupSh(monorepoRoot));
48821
+ fs$2.chmodSync(setupPath, 493);
48822
+ fs$2.writeFileSync(path$2.join(outDir, "README.md"), readme(agentNames, secretNames, optionalSettings));
48822
48823
  }
48823
48824
  console.log("");
48824
48825
  console.log(chalk.green(`✓ Scaffold written to ${outDir}`));
@@ -48887,13 +48888,13 @@ const agentExportRuntimeCommand = new Command("export-runtime").description("Exp
48887
48888
  if (options.format === "json") {
48888
48889
  const exported = await client.agents.exportRuntime(id, selector.params);
48889
48890
  const body = `${JSON.stringify(exported, null, 2)}\n`;
48890
- if (options.output) fs$1.writeFileSync(path$2.resolve(options.output), body);
48891
+ if (options.output) fs$2.writeFileSync(path$2.resolve(options.output), body);
48891
48892
  else process.stdout.write(body);
48892
48893
  return;
48893
48894
  }
48894
48895
  const bundle = await client.agents.exportRuntimeBundle(id, selector.params);
48895
48896
  const outPath = path$2.resolve(options.output ?? bundle.fileName);
48896
- fs$1.writeFileSync(outPath, bundle.bytes);
48897
+ fs$2.writeFileSync(outPath, bundle.bytes);
48897
48898
  console.log(JSON.stringify({
48898
48899
  path: outPath,
48899
48900
  bytes: bundle.bytes.byteLength,
@@ -51232,7 +51233,7 @@ async function storedOrganizationId() {
51232
51233
  return null;
51233
51234
  }
51234
51235
  }
51235
- const apiKeysCommand = new Command("api-keys").description("Manage API keys");
51236
+ const apiKeysCommand = new Command("api-keys").alias("keys").description("Manage API keys");
51236
51237
  apiKeysCommand.command("list").description("List your API keys").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
51237
51238
  const apiKey = await ensureAuth();
51238
51239
  if (!apiKey) return;
@@ -57955,13 +57956,987 @@ var DeviceProviderError = class extends Error {
57955
57956
  this.name = "DeviceProviderError";
57956
57957
  }
57957
57958
  };
57959
+ object({
57960
+ audience: literal("runtype-device-relay"),
57961
+ deviceId: string().min(1).max(128),
57962
+ connectionId: string().min(1).max(128),
57963
+ nonce: string().min(16).max(128),
57964
+ issuedAt: number$1().int().nonnegative(),
57965
+ expiresAt: number$1().int().positive()
57966
+ }).strict();
57967
+ new TextEncoder();
57968
+ function bytesToBase64Url(bytes) {
57969
+ let binary = "";
57970
+ for (const byte of bytes) binary += String.fromCharCode(byte);
57971
+ return btoa(binary).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
57972
+ }
57973
+ function base64UrlToBytes(value) {
57974
+ if (!/^[A-Za-z0-9_-]+$/u.test(value)) return null;
57975
+ const padded = value.replace(/-/gu, "+").replace(/_/gu, "/") + "=".repeat((4 - value.length % 4) % 4);
57976
+ try {
57977
+ const binary = atob(padded);
57978
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
57979
+ } catch {
57980
+ return null;
57981
+ }
57982
+ }
57983
+ //#endregion
57984
+ //#region ../device-bridge/src/device-identity.ts
57985
+ const encoder = new TextEncoder();
57986
+ function toArrayBuffer(bytes) {
57987
+ const copy = new Uint8Array(bytes.byteLength);
57988
+ copy.set(bytes);
57989
+ return copy.buffer;
57990
+ }
57991
+ function exportedKeyBytes(value) {
57992
+ if (!(value instanceof ArrayBuffer)) throw new Error("Expected a binary key export");
57993
+ return new Uint8Array(value);
57994
+ }
57995
+ /**
57996
+ * Builds the exact string a device signs to open a relay connection.
57997
+ * Format: `runtype-device-connect-v1`, device id, timestamp, and nonce, separated by newlines.
57998
+ */
57999
+ function deviceConnectionSignaturePayload(input) {
58000
+ return `runtype-device-connect-v1\n${input.deviceId}\n${input.timestamp}\n${input.nonce}`;
58001
+ }
58002
+ function requireCryptoKeyPair(value) {
58003
+ if ("publicKey" in value && "privateKey" in value) return value;
58004
+ throw new Error("Ed25519 identity must be a key pair");
58005
+ }
58006
+ async function generateDeviceIdentity() {
58007
+ const keyPair = requireCryptoKeyPair(await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]));
58008
+ const rawPublicKey = exportedKeyBytes(await crypto.subtle.exportKey("raw", keyPair.publicKey));
58009
+ if (rawPublicKey.byteLength !== 32) throw new Error("Ed25519 public key must be 32 bytes");
58010
+ return {
58011
+ keyPair,
58012
+ publicKey: bytesToBase64Url(rawPublicKey)
58013
+ };
58014
+ }
58015
+ async function exportDevicePrivateKey(privateKey) {
58016
+ return bytesToBase64Url(exportedKeyBytes(await crypto.subtle.exportKey("pkcs8", privateKey)));
58017
+ }
58018
+ async function importDevicePrivateKey(privateKey) {
58019
+ const pkcs8 = base64UrlToBytes(privateKey);
58020
+ if (!pkcs8) throw new Error("Device private key must be base64url PKCS8");
58021
+ return crypto.subtle.importKey("pkcs8", toArrayBuffer(pkcs8), { name: "Ed25519" }, false, ["sign"]);
58022
+ }
58023
+ async function signDeviceConnectionChallenge(input) {
58024
+ const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", input.privateKey, encoder.encode(deviceConnectionSignaturePayload({
58025
+ deviceId: input.deviceId,
58026
+ timestamp: input.timestamp,
58027
+ nonce: input.nonce
58028
+ }))));
58029
+ if (signature.byteLength !== 64) throw new Error("Ed25519 signature must be 64 bytes");
58030
+ return bytesToBase64Url(signature);
58031
+ }
58032
+ const DEVICE_MAX_FRAME_BYTES = 1048576;
58033
+ const identifierSchema = string().min(1).max(128);
58034
+ const isoDateSchema = datetime({ offset: true });
58035
+ const jsonObjectSchema = record(string(), unknown());
58036
+ const devicePlatformSchema = _enum(["macos", "cli"]);
58037
+ const devicePermissionStateSchema = _enum([
58038
+ "granted",
58039
+ "denied",
58040
+ "not_determined",
58041
+ "restricted",
58042
+ "unavailable"
58043
+ ]);
58044
+ const devicePluginHealthSchema = _enum([
58045
+ "healthy",
58046
+ "degraded",
58047
+ "unavailable",
58048
+ "disabled"
58049
+ ]);
58050
+ const deviceToolSchema = object({
58051
+ name: identifierSchema.regex(/^[a-z][a-z0-9_]*$/),
58052
+ title: string().min(1).max(120),
58053
+ description: string().min(1).max(2e3),
58054
+ inputSchema: jsonObjectSchema,
58055
+ sideEffect: boolean().default(false)
58056
+ }).strict();
58057
+ const deviceCapabilityManifestSchema = object({
58058
+ deviceId: identifierSchema,
58059
+ generatedAt: isoDateSchema,
58060
+ plugins: array(object({
58061
+ id: identifierSchema.regex(/^[a-z][a-z0-9_-]*$/),
58062
+ displayName: string().min(1).max(120),
58063
+ version: string().min(1).max(64),
58064
+ health: devicePluginHealthSchema,
58065
+ statusMessage: string().max(500).optional(),
58066
+ permissions: record(string(), devicePermissionStateSchema).default({}),
58067
+ tools: array(deviceToolSchema).max(50)
58068
+ }).strict()).max(20)
58069
+ }).strict().superRefine((manifest, ctx) => {
58070
+ const toolNames = /* @__PURE__ */ new Set();
58071
+ let toolCount = 0;
58072
+ for (const plugin of manifest.plugins) for (const tool of plugin.tools) {
58073
+ toolCount++;
58074
+ if (toolNames.has(tool.name)) ctx.addIssue({
58075
+ code: "custom",
58076
+ message: `Duplicate device tool name: ${tool.name}`,
58077
+ path: ["plugins"]
58078
+ });
58079
+ toolNames.add(tool.name);
58080
+ }
58081
+ if (toolCount > 50) ctx.addIssue({
58082
+ code: "custom",
58083
+ message: `Device manifests may advertise at most 50 tools`,
58084
+ path: ["plugins"]
58085
+ });
58086
+ });
58087
+ const deviceErrorSchema = object({
58088
+ code: _enum([
58089
+ "DEVICE_OFFLINE",
58090
+ "DEVICE_REVOKED",
58091
+ "DEVICE_BUSY",
58092
+ "PLUGIN_UNAVAILABLE",
58093
+ "PERMISSION_DENIED",
58094
+ "MESSAGES_NOT_READY",
58095
+ "APP_VERSION_UNSUPPORTED",
58096
+ "PROTOCOL_VERSION_UNSUPPORTED",
58097
+ "REQUEST_EXPIRED",
58098
+ "AMBIGUOUS_SIDE_EFFECT",
58099
+ "POLICY_DENIED",
58100
+ "TOOL_NOT_FOUND",
58101
+ "INVALID_REQUEST",
58102
+ "DUPLICATE_INVOCATION",
58103
+ "INTERNAL_ERROR"
58104
+ ]),
58105
+ message: string().min(1).max(1e3),
58106
+ retryable: boolean(),
58107
+ details: jsonObjectSchema.optional()
58108
+ }).strict();
58109
+ const deviceInvocationSchema = object({
58110
+ id: identifierSchema,
58111
+ idempotencyKey: string().min(16).max(256),
58112
+ toolName: identifierSchema,
58113
+ sideEffect: boolean(),
58114
+ arguments: jsonObjectSchema,
58115
+ requestedAt: isoDateSchema,
58116
+ deadline: isoDateSchema
58117
+ }).strict();
58118
+ const deviceInvocationResultSchema = object({
58119
+ invocationId: identifierSchema,
58120
+ status: _enum([
58121
+ "success",
58122
+ "error",
58123
+ "ambiguous"
58124
+ ]),
58125
+ completedAt: isoDateSchema,
58126
+ output: unknown().optional(),
58127
+ error: deviceErrorSchema.optional()
58128
+ }).strict().superRefine((result, ctx) => {
58129
+ if (result.status === "success" && result.error) ctx.addIssue({
58130
+ code: "custom",
58131
+ message: "Successful results cannot include an error"
58132
+ });
58133
+ if (result.status !== "success" && !result.error) ctx.addIssue({
58134
+ code: "custom",
58135
+ message: "Non-success results must include an error"
58136
+ });
58137
+ });
58138
+ const protocolEnvelopeSchema = object({ protocolVersion: literal(1) });
58139
+ const relayToDeviceMessageSchema = discriminatedUnion("type", [
58140
+ protocolEnvelopeSchema.extend({
58141
+ type: literal("invoke"),
58142
+ invocation: deviceInvocationSchema
58143
+ }).strict(),
58144
+ protocolEnvelopeSchema.extend({
58145
+ type: literal("cancel"),
58146
+ invocationId: identifierSchema,
58147
+ reason: string().max(500).optional()
58148
+ }).strict(),
58149
+ protocolEnvelopeSchema.extend({
58150
+ type: literal("event_ack"),
58151
+ eventId: identifierSchema
58152
+ }).strict(),
58153
+ protocolEnvelopeSchema.extend({
58154
+ type: literal("shutdown"),
58155
+ reason: _enum([
58156
+ "revoked",
58157
+ "replaced",
58158
+ "maintenance",
58159
+ "unsupported_version"
58160
+ ])
58161
+ }).strict()
58162
+ ]);
58163
+ const deviceToRelayMessageSchema = discriminatedUnion("type", [
58164
+ protocolEnvelopeSchema.extend({
58165
+ type: literal("hello"),
58166
+ deviceId: identifierSchema,
58167
+ connectionId: identifierSchema,
58168
+ platform: devicePlatformSchema,
58169
+ appVersion: string().min(1).max(64),
58170
+ connectedAt: isoDateSchema
58171
+ }).strict(),
58172
+ protocolEnvelopeSchema.extend({
58173
+ type: literal("capabilities"),
58174
+ manifest: deviceCapabilityManifestSchema
58175
+ }).strict(),
58176
+ protocolEnvelopeSchema.extend({
58177
+ type: literal("heartbeat"),
58178
+ sentAt: isoDateSchema
58179
+ }).strict(),
58180
+ protocolEnvelopeSchema.extend({
58181
+ type: literal("invocation_result"),
58182
+ result: deviceInvocationResultSchema
58183
+ }).strict(),
58184
+ protocolEnvelopeSchema.extend({
58185
+ type: literal("event"),
58186
+ eventId: identifierSchema,
58187
+ pluginId: identifierSchema,
58188
+ eventName: identifierSchema,
58189
+ occurredAt: isoDateSchema,
58190
+ payload: jsonObjectSchema
58191
+ }).strict()
58192
+ ]);
58193
+ object({
58194
+ deviceId: identifierSchema,
58195
+ online: boolean(),
58196
+ lastSeenAt: isoDateSchema.optional(),
58197
+ connectionId: identifierSchema.optional(),
58198
+ appVersion: string().max(64).optional(),
58199
+ protocolVersion: number$1().int().positive().optional(),
58200
+ manifest: deviceCapabilityManifestSchema.optional()
58201
+ }).strict();
58202
+ function deviceError(code, message, retryable, details) {
58203
+ return deviceErrorSchema.parse({
58204
+ code,
58205
+ message,
58206
+ retryable,
58207
+ details
58208
+ });
58209
+ }
58210
+ //#endregion
58211
+ //#region ../device-bridge/src/localhost-http.ts
58212
+ const LOCALHOST_HTTP_TOOL_NAME = "localhost_http_call";
58213
+ const LOCALHOST_HTTP_MAX_BODY_BYTES = DEVICE_MAX_FRAME_BYTES;
58214
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
58215
+ "127.0.0.1",
58216
+ "localhost",
58217
+ "::1"
58218
+ ]);
58219
+ const localhostHttpCallArgsSchema = object({
58220
+ method: _enum([
58221
+ "GET",
58222
+ "POST",
58223
+ "PUT",
58224
+ "PATCH",
58225
+ "DELETE",
58226
+ "HEAD"
58227
+ ]),
58228
+ url: string().url(),
58229
+ headers: record(string(), string().max(8192)).optional(),
58230
+ allowedAuthHeaderName: string().regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/).max(256).optional(),
58231
+ body: string().max(LOCALHOST_HTTP_MAX_BODY_BYTES).optional(),
58232
+ timeoutMs: number$1().int().min(100).max(3e5).optional()
58233
+ }).strict();
58234
+ const localhostHttpCallResultSchema = object({
58235
+ status: number$1().int().min(100).max(599),
58236
+ statusText: string().max(120),
58237
+ headers: record(string(), string()),
58238
+ body: string().max(LOCALHOST_HTTP_MAX_BODY_BYTES)
58239
+ }).strict();
58240
+ var LocalhostHttpCallError = class extends Error {
58241
+ code;
58242
+ constructor(message, code) {
58243
+ super(message);
58244
+ this.code = code;
58245
+ this.name = "LocalhostHttpCallError";
58246
+ }
58247
+ };
58248
+ function parseLoopbackHttpUrl(urlString) {
58249
+ let url;
58250
+ try {
58251
+ url = new URL(urlString);
58252
+ } catch {
58253
+ throw new LocalhostHttpCallError("URL is not valid", "INVALID_REQUEST");
58254
+ }
58255
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new LocalhostHttpCallError("Only http and https loopback URLs are allowed", "NOT_LOOPBACK");
58256
+ if (url.username || url.password) throw new LocalhostHttpCallError("Loopback URLs cannot include credentials", "INVALID_REQUEST");
58257
+ const hostname = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
58258
+ if (!LOOPBACK_HOSTS.has(hostname)) throw new LocalhostHttpCallError("Device HTTP calls may only target 127.0.0.1, localhost, or ::1", "NOT_LOOPBACK");
58259
+ return url;
58260
+ }
58261
+ function normalizedLoopbackPort(url) {
58262
+ if (url.port !== "") return url.port;
58263
+ return url.protocol === "https:" ? "443" : "80";
58264
+ }
58265
+ /**
58266
+ * Reports whether a device HTTP call may be forwarded to the listen target.
58267
+ * Loopback host aliases compare equal. A URL that is not loopback HTTP is refused.
58268
+ */
58269
+ function isAllowedListenTarget(requestUrl, forwardTo) {
58270
+ try {
58271
+ const request = parseLoopbackHttpUrl(requestUrl);
58272
+ const target = parseLoopbackHttpUrl(forwardTo);
58273
+ return request.protocol === target.protocol && normalizedLoopbackPort(request) === normalizedLoopbackPort(target);
58274
+ } catch (error) {
58275
+ if (error instanceof LocalhostHttpCallError) return false;
58276
+ throw error;
58277
+ }
58278
+ }
58279
+ const FORWARDED_REQUEST_HEADERS = /* @__PURE__ */ new Set([
58280
+ "accept",
58281
+ "authorization",
58282
+ "a2a-version",
58283
+ "content-type",
58284
+ "x-api-key"
58285
+ ]);
58286
+ const BLOCKED_AUTH_HEADERS = /* @__PURE__ */ new Set([
58287
+ "host",
58288
+ "cookie",
58289
+ "connection",
58290
+ "content-length",
58291
+ "transfer-encoding",
58292
+ "upgrade",
58293
+ "proxy-authorization",
58294
+ "proxy-authenticate",
58295
+ "te",
58296
+ "trailer",
58297
+ "keep-alive"
58298
+ ]);
58299
+ function forwardedRequestHeaders(headers, allowedAuthHeaderName) {
58300
+ const out = new Headers();
58301
+ if (!headers) return out;
58302
+ for (const [name, value] of Object.entries(headers)) {
58303
+ const normalized = name.toLowerCase();
58304
+ const configuredAuth = normalized === allowedAuthHeaderName?.toLowerCase() && !BLOCKED_AUTH_HEADERS.has(normalized) && !normalized.startsWith("sec-") && !normalized.startsWith("proxy-");
58305
+ if (!FORWARDED_REQUEST_HEADERS.has(normalized) && !configuredAuth) continue;
58306
+ out.set(name, value);
58307
+ }
58308
+ return out;
58309
+ }
58310
+ async function readResponseBody(response) {
58311
+ if (!response.body) return "";
58312
+ const reader = response.body.getReader();
58313
+ const decoder = new TextDecoder();
58314
+ const chunks = [];
58315
+ let bytes = 0;
58316
+ try {
58317
+ while (true) {
58318
+ const { done, value } = await reader.read();
58319
+ if (done) break;
58320
+ bytes += value.byteLength;
58321
+ if (bytes > LOCALHOST_HTTP_MAX_BODY_BYTES) {
58322
+ await reader.cancel();
58323
+ throw new LocalhostHttpCallError(`Response body exceeds ${LOCALHOST_HTTP_MAX_BODY_BYTES} bytes`, "BODY_TOO_LARGE");
58324
+ }
58325
+ chunks.push(decoder.decode(value, { stream: true }));
58326
+ }
58327
+ chunks.push(decoder.decode());
58328
+ return chunks.join("");
58329
+ } finally {
58330
+ reader.releaseLock();
58331
+ }
58332
+ }
58333
+ async function executeLocalhostHttpCall(args, fetchImpl = fetch, signal) {
58334
+ const url = parseLoopbackHttpUrl(args.url);
58335
+ const timeoutMs = args.timeoutMs ?? 3e4;
58336
+ const response = await fetchImpl(url, {
58337
+ method: args.method,
58338
+ headers: forwardedRequestHeaders(args.headers, args.allowedAuthHeaderName),
58339
+ body: args.method === "GET" || args.method === "HEAD" ? void 0 : args.body,
58340
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
58341
+ redirect: "error"
58342
+ });
58343
+ const body = await readResponseBody(response);
58344
+ const headers = {};
58345
+ response.headers.forEach((value, key) => {
58346
+ headers[key] = value;
58347
+ });
58348
+ return localhostHttpCallResultSchema.parse({
58349
+ status: response.status,
58350
+ statusText: response.statusText,
58351
+ headers,
58352
+ body
58353
+ });
58354
+ }
58355
+ //#endregion
58356
+ //#region src/lib/device-listen-identity.ts
58357
+ const DEVICE_LISTEN_CONFIG = "device-listen";
58358
+ function isRecord$1(value) {
58359
+ return typeof value === "object" && value !== null && !Array.isArray(value);
58360
+ }
58361
+ function copyIdentity(identity) {
58362
+ return {
58363
+ deviceId: identity.deviceId,
58364
+ name: identity.name,
58365
+ publicKey: identity.publicKey,
58366
+ privateKey: identity.privateKey
58367
+ };
58368
+ }
58369
+ function readIdentity(value) {
58370
+ if (!isRecord$1(value)) throw new Error("Stored device identity is invalid");
58371
+ const { deviceId, name, publicKey, privateKey } = value;
58372
+ if (deviceId !== null && (typeof deviceId !== "string" || deviceId.length === 0)) throw new Error("Stored device identity is invalid");
58373
+ if (typeof name !== "string" || name.trim().length === 0 || name.length > 120) throw new Error("Stored device identity is invalid");
58374
+ if (typeof publicKey !== "string" || !/^[A-Za-z0-9_-]{43}$/u.test(publicKey)) throw new Error("Stored device identity is invalid");
58375
+ if (typeof privateKey !== "string" || !/^[A-Za-z0-9_-]{40,}$/u.test(privateKey)) throw new Error("Stored device identity is invalid");
58376
+ return {
58377
+ deviceId,
58378
+ name,
58379
+ publicKey,
58380
+ privateKey
58381
+ };
58382
+ }
58383
+ /** Hostname shown in Runtype until the operator passes --name. */
58384
+ function resolveDeviceListenName(requested) {
58385
+ const explicit = requested?.trim();
58386
+ if (explicit) return explicit.slice(0, 120);
58387
+ const hostname = os$1.hostname().trim();
58388
+ if (!hostname) return "This computer";
58389
+ return hostname.slice(0, 120);
58390
+ }
58391
+ async function createDeviceListenIdentity(name) {
58392
+ const { keyPair, publicKey } = await generateDeviceIdentity();
58393
+ return {
58394
+ deviceId: null,
58395
+ name,
58396
+ publicKey,
58397
+ privateKey: await exportDevicePrivateKey(keyPair.privateKey)
58398
+ };
58399
+ }
58400
+ async function signDeviceListenChallenge(identity, input) {
58401
+ return signDeviceConnectionChallenge({
58402
+ privateKey: await importDevicePrivateKey(identity.privateKey),
58403
+ ...input
58404
+ });
58405
+ }
58406
+ function deviceListenEncryptionKey() {
58407
+ const hostname = os$1.hostname();
58408
+ const username = os$1.userInfo().username;
58409
+ return createHash("sha256").update(`runtype-device-listen-${hostname}-${username}`).digest("hex");
58410
+ }
58411
+ function isCorruptStoreError(error) {
58412
+ if (error instanceof SyntaxError) return true;
58413
+ const message = error instanceof Error ? error.message : String(error);
58414
+ return /JSON|bad decrypt|authenticate data|unable to authenticate/iu.test(message);
58415
+ }
58416
+ function createConfig(cwd, encryptionKey, configName) {
58417
+ return new Conf({
58418
+ projectName: "runtype-cli",
58419
+ projectSuffix: "",
58420
+ configName,
58421
+ cwd,
58422
+ encryptionKey,
58423
+ defaults: { identities: {} }
58424
+ });
58425
+ }
58426
+ function openConfig(cwd, encryptionKey, configName) {
58427
+ try {
58428
+ return createConfig(cwd, encryptionKey, configName);
58429
+ } catch (error) {
58430
+ if (!isCorruptStoreError(error)) throw error;
58431
+ const filePath = path.join(cwd, `${configName}.json`);
58432
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
58433
+ return createConfig(cwd, encryptionKey, configName);
58434
+ }
58435
+ }
58436
+ function createDeviceListenIdentityStore(options) {
58437
+ const config = openConfig(options.cwd ?? getRuntypeHomeDir(), options.encryptionKey ?? deviceListenEncryptionKey(), workspaceConfigName(options.workspace));
58438
+ return {
58439
+ async load(apiUrl) {
58440
+ const value = (config.get("identities") ?? {})[normalizeApiUrl(apiUrl)];
58441
+ if (value === void 0) return null;
58442
+ return readIdentity(value);
58443
+ },
58444
+ async save(apiUrl, identity) {
58445
+ const key = normalizeApiUrl(apiUrl);
58446
+ const identities = {
58447
+ ...config.get("identities"),
58448
+ [key]: copyIdentity(readIdentity(identity))
58449
+ };
58450
+ config.set("identities", identities);
58451
+ }
58452
+ };
58453
+ }
58454
+ function workspaceConfigName(workspace) {
58455
+ if (!workspace.userId) throw new Error("Authenticated device owner is required");
58456
+ const scope = createHash("sha256").update(JSON.stringify([workspace.userId, workspace.orgId])).digest("hex");
58457
+ return `${DEVICE_LISTEN_CONFIG}-${scope}`;
58458
+ }
58459
+ function frameText(data) {
58460
+ if (typeof data === "string") return data;
58461
+ if (Buffer.isBuffer(data)) return data.toString("utf8");
58462
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
58463
+ return Buffer.concat(data).toString("utf8");
58464
+ }
58465
+ function closeReason(reason) {
58466
+ return typeof reason === "string" ? reason : reason.toString("utf8");
58467
+ }
58468
+ function localhostFailureCode(error) {
58469
+ if (error.code === "NOT_LOOPBACK") return "POLICY_DENIED";
58470
+ if (error.code === "REQUEST_EXPIRED") return "REQUEST_EXPIRED";
58471
+ if (error.code === "INTERNAL_ERROR") return "INTERNAL_ERROR";
58472
+ return "INVALID_REQUEST";
58473
+ }
58474
+ function resultMessage(invocationId, status, output, error) {
58475
+ return deviceToRelayMessageSchema.parse({
58476
+ type: "invocation_result",
58477
+ protocolVersion: 1,
58478
+ result: {
58479
+ invocationId,
58480
+ status,
58481
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
58482
+ ...status === "success" ? { output } : { error }
58483
+ }
58484
+ });
58485
+ }
58486
+ function failureMessage(invocationId, code, message) {
58487
+ return resultMessage(invocationId, "error", void 0, deviceError(code, message.slice(0, 1e3), false));
58488
+ }
58489
+ async function executeInvocation(invocation, forwardTo, fetchImpl, signal) {
58490
+ if (invocation.toolName !== "localhost_http_call") return failureMessage(invocation.id, "TOOL_NOT_FOUND", `Tool ${invocation.toolName} is not available`);
58491
+ const deadline = Date.parse(invocation.deadline);
58492
+ if (Number.isNaN(deadline) || deadline <= Date.now()) return failureMessage(invocation.id, "REQUEST_EXPIRED", "Device invocation expired before local execution");
58493
+ const parsed = localhostHttpCallArgsSchema.safeParse(invocation.arguments);
58494
+ if (!parsed.success) return failureMessage(invocation.id, "INVALID_REQUEST", "localhost_http_call arguments are invalid");
58495
+ if (!isAllowedListenTarget(parsed.data.url, forwardTo)) return failureMessage(invocation.id, "POLICY_DENIED", "That URL is outside the forwarded loopback port");
58496
+ try {
58497
+ const timeoutMs = Math.min(parsed.data.timeoutMs ?? 3e4, deadline - Date.now());
58498
+ if (timeoutMs <= 0) return failureMessage(invocation.id, "REQUEST_EXPIRED", "Device invocation expired");
58499
+ const output = await executeLocalhostHttpCall({
58500
+ ...parsed.data,
58501
+ timeoutMs
58502
+ }, fetchImpl, signal);
58503
+ return resultMessage(invocation.id, "success", output);
58504
+ } catch (error) {
58505
+ if (error instanceof LocalhostHttpCallError) return failureMessage(invocation.id, localhostFailureCode(error), error.message);
58506
+ const message = error instanceof Error ? error.message : "The localhost request failed";
58507
+ return failureMessage(invocation.id, "INTERNAL_ERROR", message || "The localhost request failed");
58508
+ }
58509
+ }
58510
+ function connectRelaySocket(relayUrl, headers) {
58511
+ const socket = new WebSocket(relayUrl, { headers });
58512
+ return {
58513
+ send(data) {
58514
+ socket.send(data);
58515
+ },
58516
+ close(code, reason) {
58517
+ socket.close(code, reason);
58518
+ },
58519
+ onOpen(listener) {
58520
+ socket.on("open", listener);
58521
+ },
58522
+ onMessage(listener) {
58523
+ socket.on("message", (data) => listener(data));
58524
+ },
58525
+ onClose(listener) {
58526
+ socket.on("close", (code, reason) => listener(code, reason));
58527
+ },
58528
+ onError(listener) {
58529
+ socket.on("error", listener);
58530
+ }
58531
+ };
58532
+ }
58533
+ function startDeviceListenSession(options) {
58534
+ const fetchImpl = options.fetchImpl ?? fetch;
58535
+ const socket = (options.socketFactory ?? connectRelaySocket)(options.relayUrl, { Authorization: `Bearer ${options.token}` });
58536
+ let settled = false;
58537
+ let terminal = null;
58538
+ let heartbeat = null;
58539
+ const active = /* @__PURE__ */ new Map();
58540
+ const abortActive = () => {
58541
+ for (const controller of active.values()) controller.abort();
58542
+ active.clear();
58543
+ };
58544
+ let resolveClosed = () => {};
58545
+ const closed = new Promise((resolve) => {
58546
+ resolveClosed = resolve;
58547
+ });
58548
+ const finish = (code, reason) => {
58549
+ if (settled) return;
58550
+ settled = true;
58551
+ abortActive();
58552
+ if (heartbeat) clearInterval(heartbeat);
58553
+ heartbeat = null;
58554
+ resolveClosed({
58555
+ code,
58556
+ reason,
58557
+ intent: terminal ?? "unexpected"
58558
+ });
58559
+ try {
58560
+ socket.close(code, reason);
58561
+ } catch {
58562
+ return;
58563
+ }
58564
+ };
58565
+ const send = (message) => {
58566
+ socket.send(JSON.stringify(deviceToRelayMessageSchema.parse(message)));
58567
+ };
58568
+ const localhostManifest = () => ({
58569
+ deviceId: options.deviceId,
58570
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
58571
+ plugins: [{
58572
+ id: "localhost-http",
58573
+ displayName: "Localhost HTTP",
58574
+ version: "1.0.0",
58575
+ health: "healthy",
58576
+ permissions: {},
58577
+ tools: [{
58578
+ name: LOCALHOST_HTTP_TOOL_NAME,
58579
+ title: "Call a loopback HTTP origin",
58580
+ description: "Fetch a 127.0.0.1, localhost, or ::1 URL from the paired device. Used to call a local agent.",
58581
+ inputSchema: {
58582
+ type: "object",
58583
+ properties: {
58584
+ method: {
58585
+ type: "string",
58586
+ enum: [
58587
+ "GET",
58588
+ "POST",
58589
+ "PUT",
58590
+ "PATCH",
58591
+ "DELETE",
58592
+ "HEAD"
58593
+ ]
58594
+ },
58595
+ url: { type: "string" },
58596
+ headers: {
58597
+ type: "object",
58598
+ additionalProperties: { type: "string" }
58599
+ },
58600
+ body: { type: "string" },
58601
+ timeoutMs: {
58602
+ type: "integer",
58603
+ minimum: 100,
58604
+ maximum: 3e5
58605
+ },
58606
+ allowedAuthHeaderName: { type: "string" }
58607
+ },
58608
+ required: ["method", "url"],
58609
+ additionalProperties: false
58610
+ },
58611
+ sideEffect: true
58612
+ }]
58613
+ }]
58614
+ });
58615
+ socket.onOpen(() => {
58616
+ try {
58617
+ send({
58618
+ type: "hello",
58619
+ protocolVersion: 1,
58620
+ deviceId: options.deviceId,
58621
+ connectionId: options.connectionId,
58622
+ platform: "cli",
58623
+ appVersion: options.appVersion,
58624
+ connectedAt: (/* @__PURE__ */ new Date()).toISOString()
58625
+ });
58626
+ send({
58627
+ type: "capabilities",
58628
+ protocolVersion: 1,
58629
+ manifest: localhostManifest()
58630
+ });
58631
+ heartbeat = setInterval(() => {
58632
+ if (settled) return;
58633
+ try {
58634
+ send({
58635
+ type: "heartbeat",
58636
+ protocolVersion: 1,
58637
+ sentAt: (/* @__PURE__ */ new Date()).toISOString()
58638
+ });
58639
+ } catch {
58640
+ finish(1011, "Heartbeat failed");
58641
+ }
58642
+ }, options.heartbeatIntervalMs ?? 2e4);
58643
+ options.onListening?.();
58644
+ } catch (error) {
58645
+ finish(1011, error instanceof Error ? error.message : "Device hello failed");
58646
+ }
58647
+ });
58648
+ socket.onMessage((data) => {
58649
+ handleFrame(data).catch(() => {
58650
+ finish(1011, "Device session failed");
58651
+ });
58652
+ });
58653
+ socket.onClose((code, reason) => {
58654
+ finish(code, closeReason(reason));
58655
+ });
58656
+ socket.onError(() => {
58657
+ try {
58658
+ socket.close(1011, "Relay socket error");
58659
+ } catch {
58660
+ finish(1011, "Relay socket error");
58661
+ }
58662
+ });
58663
+ async function handleFrame(data) {
58664
+ let parsed;
58665
+ try {
58666
+ parsed = relayToDeviceMessageSchema.safeParse(JSON.parse(frameText(data)));
58667
+ } catch {
58668
+ return;
58669
+ }
58670
+ if (!parsed.success || settled) return;
58671
+ if (parsed.data.type === "shutdown") {
58672
+ abortActive();
58673
+ terminal = parsed.data.reason === "revoked" ? "revoked" : terminal;
58674
+ socket.close(1e3, parsed.data.reason);
58675
+ return;
58676
+ }
58677
+ if (parsed.data.type === "cancel") {
58678
+ active.get(parsed.data.invocationId)?.abort();
58679
+ return;
58680
+ }
58681
+ if (parsed.data.type !== "invoke") return;
58682
+ const invocation = parsed.data.invocation;
58683
+ if (active.has(invocation.id)) return;
58684
+ const controller = new AbortController();
58685
+ active.set(invocation.id, controller);
58686
+ try {
58687
+ const message = await executeInvocation(invocation, options.forwardTo, fetchImpl, controller.signal);
58688
+ if (!settled && !controller.signal.aborted) send(message);
58689
+ } finally {
58690
+ active.delete(invocation.id);
58691
+ }
58692
+ }
58693
+ return {
58694
+ closed,
58695
+ close(code = 1e3, reason = "Interrupted") {
58696
+ if (settled) return;
58697
+ terminal = terminal ?? "stop";
58698
+ abortActive();
58699
+ try {
58700
+ socket.close(code, reason);
58701
+ } catch {
58702
+ finish(code, reason);
58703
+ }
58704
+ }
58705
+ };
58706
+ }
58707
+ //#endregion
58708
+ //#region src/lib/device-listen.ts
58709
+ const DEVICE_LISTEN_RECONNECT_DELAYS_MS = [
58710
+ 250,
58711
+ 500,
58712
+ 1e3
58713
+ ];
58714
+ var DeviceListenError = class extends Error {
58715
+ constructor(message) {
58716
+ super(message);
58717
+ this.name = "DeviceListenError";
58718
+ }
58719
+ };
58720
+ function isRecord(value) {
58721
+ return typeof value === "object" && value !== null && !Array.isArray(value);
58722
+ }
58723
+ function httpStatus(error) {
58724
+ if (!isRecord(error) || typeof error.statusCode !== "number") return null;
58725
+ return error.statusCode;
58726
+ }
58727
+ function unwrapData$1(response) {
58728
+ if (!isRecord(response) || !("data" in response)) throw new DeviceListenError("Runtype API returned an invalid device response");
58729
+ return response.data;
58730
+ }
58731
+ function requireDeviceId(value) {
58732
+ if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0) throw new DeviceListenError("Runtype API returned an invalid device response");
58733
+ return value.id;
58734
+ }
58735
+ function readRelayConnection(value) {
58736
+ if (!isRecord(value) || typeof value.connectionId !== "string" || value.connectionId.length === 0 || typeof value.token !== "string" || value.token.length === 0 || typeof value.relayUrl !== "string" || value.relayUrl.length === 0) throw new DeviceListenError("Runtype API returned an invalid device response");
58737
+ return {
58738
+ connectionId: value.connectionId,
58739
+ token: value.token,
58740
+ relayUrl: value.relayUrl
58741
+ };
58742
+ }
58743
+ function delay(milliseconds) {
58744
+ return new Promise((resolve) => {
58745
+ setTimeout(resolve, milliseconds);
58746
+ });
58747
+ }
58748
+ function installProcessSignalHandlers(stop) {
58749
+ const onStop = () => stop();
58750
+ process.on("SIGINT", onStop);
58751
+ process.on("SIGTERM", onStop);
58752
+ return () => {
58753
+ process.off("SIGINT", onStop);
58754
+ process.off("SIGTERM", onStop);
58755
+ };
58756
+ }
58757
+ function deviceListenAppVersion(version = getCliVersion()) {
58758
+ const value = `runtype-cli/${version}`;
58759
+ return value.length <= 64 ? value : value.slice(0, 64);
58760
+ }
58761
+ function assertListenForwardTarget(value) {
58762
+ if (!value?.trim()) throw new DeviceListenError("Missing required option --forward-to <url>");
58763
+ try {
58764
+ const url = parseLoopbackHttpUrl(value.trim());
58765
+ return `${url.protocol}//${url.host}`;
58766
+ } catch (error) {
58767
+ throw new DeviceListenError(error instanceof Error ? error.message : String(error));
58768
+ }
58769
+ }
58770
+ function printListening(deviceId, forwardTo) {
58771
+ console.log("Listening for this workspace");
58772
+ console.log(` Device ${deviceId}`);
58773
+ console.log(` Forward ${forwardTo}`);
58774
+ console.log(" Only this Runtype workspace can call this process.");
58775
+ console.log(" Keep this running. Ctrl-C to stop.");
58776
+ }
58777
+ async function pairIdentity(client, identity, appVersion) {
58778
+ const session = unwrapData$1(await client.post("/devices/pairing-sessions", {}));
58779
+ if (!isRecord(session) || typeof session.code !== "string" || session.code.length === 0) throw new DeviceListenError("Runtype API returned an invalid device response");
58780
+ identity.deviceId = requireDeviceId(unwrapData$1(await client.post("/devices/pair", {
58781
+ code: session.code,
58782
+ name: identity.name,
58783
+ platform: "cli",
58784
+ publicKey: identity.publicKey,
58785
+ appVersion
58786
+ })));
58787
+ }
58788
+ async function requestConnectionToken(client, identity, nonce, timestamp) {
58789
+ if (!identity.deviceId) throw new DeviceListenError("Device is not paired");
58790
+ const signature = await signDeviceListenChallenge(identity, {
58791
+ deviceId: identity.deviceId,
58792
+ timestamp,
58793
+ nonce
58794
+ });
58795
+ return readRelayConnection(unwrapData$1(await client.post(`/devices/${encodeURIComponent(identity.deviceId)}/connection-token`, {
58796
+ timestamp,
58797
+ nonce,
58798
+ signature
58799
+ })));
58800
+ }
58801
+ async function verifyStoredDeviceOwnership(client, deviceId) {
58802
+ let response;
58803
+ try {
58804
+ response = await client.get(`/devices/${encodeURIComponent(deviceId)}`);
58805
+ } catch (error) {
58806
+ if (httpStatus(error) === 403 || httpStatus(error) === 404) throw new DeviceListenError("Cannot reuse this device in the authenticated workspace. Verify ownership and DEVICES:READ permission.");
58807
+ throw error;
58808
+ }
58809
+ if (requireDeviceId(unwrapData$1(response)) !== deviceId) throw new DeviceListenError("Runtype API returned a different device during ownership verification");
58810
+ }
58811
+ async function recoverRevokedIdentity(client, store, apiUrl, identity, appVersion) {
58812
+ const rotated = await createDeviceListenIdentity(identity.name);
58813
+ identity.publicKey = rotated.publicKey;
58814
+ identity.privateKey = rotated.privateKey;
58815
+ identity.deviceId = null;
58816
+ await store.save(apiUrl, identity);
58817
+ await pairIdentity(client, identity, appVersion);
58818
+ await store.save(apiUrl, identity);
58819
+ }
58820
+ async function obtainConnection(client, store, apiUrl, identity, appVersion, createNonce, now, repaired) {
58821
+ if (!identity.deviceId) {
58822
+ await pairIdentity(client, identity, appVersion);
58823
+ await store.save(apiUrl, identity);
58824
+ } else await verifyStoredDeviceOwnership(client, identity.deviceId);
58825
+ try {
58826
+ return await requestConnectionToken(client, identity, createNonce(), now());
58827
+ } catch (error) {
58828
+ if (httpStatus(error) === 503) throw new DeviceListenError("Device relay is not configured");
58829
+ if (httpStatus(error) !== 410) throw error;
58830
+ if (repaired.done) throw new DeviceListenError("This device was revoked");
58831
+ repaired.done = true;
58832
+ await recoverRevokedIdentity(client, store, apiUrl, identity, appVersion);
58833
+ try {
58834
+ return await requestConnectionToken(client, identity, createNonce(), now());
58835
+ } catch (retryError) {
58836
+ if (httpStatus(retryError) === 503) throw new DeviceListenError("Device relay is not configured");
58837
+ if (httpStatus(retryError) === 410) throw new DeviceListenError("This device was revoked");
58838
+ throw retryError;
58839
+ }
58840
+ }
58841
+ }
58842
+ async function runDeviceListen(options) {
58843
+ const forwardTo = assertListenForwardTarget(options.forwardTo);
58844
+ const sleep = options.sleep ?? delay;
58845
+ const setExitCode = options.setExitCode ?? ((code) => {
58846
+ process.exitCode = code;
58847
+ });
58848
+ const reconnectDelays = options.reconnectDelaysMs ?? DEVICE_LISTEN_RECONNECT_DELAYS_MS;
58849
+ const createNonce = options.createNonce ?? (() => randomUUID());
58850
+ const now = options.now ?? (() => Date.now());
58851
+ const appVersion = options.appVersion ?? deviceListenAppVersion();
58852
+ const runtime = {
58853
+ stopping: false,
58854
+ active: null
58855
+ };
58856
+ const requestStop = () => {
58857
+ runtime.stopping = true;
58858
+ runtime.active?.close(1e3, "Interrupted");
58859
+ };
58860
+ const uninstall = (options.installSignalHandlers ?? installProcessSignalHandlers)(requestStop);
58861
+ const repaired = { done: false };
58862
+ let announcedJson = false;
58863
+ let reconnects = 0;
58864
+ try {
58865
+ let identity = await options.identityStore.load(options.apiUrl);
58866
+ if (!identity) {
58867
+ identity = await createDeviceListenIdentity(resolveDeviceListenName(options.name));
58868
+ await options.identityStore.save(options.apiUrl, identity);
58869
+ } else if (options.name?.trim()) {
58870
+ identity = {
58871
+ ...identity,
58872
+ name: resolveDeviceListenName(options.name)
58873
+ };
58874
+ await options.identityStore.save(options.apiUrl, identity);
58875
+ }
58876
+ while (!runtime.stopping) {
58877
+ const credentials = await obtainConnection(options.client, options.identityStore, options.apiUrl, identity, appVersion, createNonce, now, repaired);
58878
+ if (runtime.stopping || !identity.deviceId) break;
58879
+ const deviceId = identity.deviceId;
58880
+ const session = startDeviceListenSession({
58881
+ relayUrl: credentials.relayUrl,
58882
+ token: credentials.token,
58883
+ deviceId,
58884
+ connectionId: credentials.connectionId,
58885
+ forwardTo,
58886
+ appVersion,
58887
+ socketFactory: options.socketFactory,
58888
+ fetchImpl: options.fetchImpl,
58889
+ onListening: () => {
58890
+ reconnects = 0;
58891
+ if (options.json) {
58892
+ if (announcedJson) return;
58893
+ announcedJson = true;
58894
+ console.log(JSON.stringify({
58895
+ deviceId,
58896
+ forwardTo,
58897
+ relayUrl: credentials.relayUrl
58898
+ }));
58899
+ return;
58900
+ }
58901
+ printListening(deviceId, forwardTo);
58902
+ }
58903
+ });
58904
+ runtime.active = session;
58905
+ if (runtime.stopping) session.close(1e3, "Interrupted");
58906
+ const close = await session.closed;
58907
+ runtime.active = null;
58908
+ if (runtime.stopping || close.intent === "stop") {
58909
+ setExitCode(0);
58910
+ return;
58911
+ }
58912
+ if (close.intent === "revoked") throw new DeviceListenError("This device was revoked");
58913
+ if (reconnects >= reconnectDelays.length) {
58914
+ console.error(chalk.red("Offline. Stopped listening after repeated disconnects."));
58915
+ setExitCode(1);
58916
+ return;
58917
+ }
58918
+ console.error(chalk.yellow("Offline. Reconnecting."));
58919
+ await sleep(reconnectDelays[reconnects] ?? 0);
58920
+ reconnects += 1;
58921
+ if (runtime.stopping) break;
58922
+ }
58923
+ setExitCode(0);
58924
+ } catch (error) {
58925
+ setExitCode(1);
58926
+ throw error;
58927
+ } finally {
58928
+ runtime.active?.close(1e3, "Stopped");
58929
+ runtime.active = null;
58930
+ uninstall();
58931
+ }
58932
+ }
57958
58933
  //#endregion
57959
58934
  //#region src/lib/macos-device-provider.ts
57960
58935
  const MAX_SIDECAR_OUTPUT_BYTES = 1048576;
57961
58936
  const MAX_HELPER_BYTES = 104857600;
57962
58937
  const DEFAULT_SIDECAR_TIMEOUT_MS = 3e4;
57963
58938
  function parseHello(value) {
57964
- if (!isRecord$1(value) || typeof value.providerId !== "string" || typeof value.protocolVersion !== "number" || typeof value.helperVersion !== "string" || typeof value.platform !== "string" || !Array.isArray(value.capabilities) || !value.capabilities.every((capability) => typeof capability === "string")) throw new DeviceProviderError("The native helper returned an invalid hello response", "INVALID_RESPONSE");
58939
+ if (!isRecord$3(value) || typeof value.providerId !== "string" || typeof value.protocolVersion !== "number" || typeof value.helperVersion !== "string" || typeof value.platform !== "string" || !Array.isArray(value.capabilities) || !value.capabilities.every((capability) => typeof capability === "string")) throw new DeviceProviderError("The native helper returned an invalid hello response", "INVALID_RESPONSE");
57965
58940
  return value;
57966
58941
  }
57967
58942
  async function isExecutable(filePath) {
@@ -58070,7 +59045,7 @@ var MacOSDeviceProvider = class {
58070
59045
  const binaryPath = await this.resolveBinary();
58071
59046
  if (!binaryPath) throw new DeviceProviderError("The Runtype macOS device helper is not installed. Run `runtype devices setup` first.", "HELPER_NOT_INSTALLED");
58072
59047
  const result = await this.invokeAt(binaryPath, method, params);
58073
- if (!isRecord$1(result)) throw new DeviceProviderError("The native helper returned a non-object result", "INVALID_RESPONSE");
59048
+ if (!isRecord$3(result)) throw new DeviceProviderError("The native helper returned a non-object result", "INVALID_RESPONSE");
58074
59049
  return result;
58075
59050
  }
58076
59051
  async removeInstalledBinary() {
@@ -58218,21 +59193,23 @@ const defaultDependencies = {
58218
59193
  sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
58219
59194
  setExitCode: (code) => {
58220
59195
  process.exitCode = code;
58221
- }
59196
+ },
59197
+ createIdentityStore: (workspace) => createDeviceListenIdentityStore({ workspace }),
59198
+ runListen: (options) => runDeviceListen(options)
58222
59199
  };
58223
59200
  function unwrapData(response) {
58224
- if (!isRecord$1(response) || !("data" in response)) throw new Error("Runtype API returned an invalid device response");
59201
+ if (!isRecord$3(response) || !("data" in response)) throw new Error("Runtype API returned an invalid device response");
58225
59202
  return response.data;
58226
59203
  }
58227
59204
  function errorMessage(error) {
58228
59205
  return error instanceof Error ? error.message : String(error);
58229
59206
  }
58230
59207
  function apiStatusCode(error) {
58231
- return isRecord$1(error) && typeof error.statusCode === "number" ? error.statusCode : null;
59208
+ return isRecord$3(error) && typeof error.statusCode === "number" ? error.statusCode : null;
58232
59209
  }
58233
59210
  function isRecordedRevocationError(error) {
58234
59211
  if (apiStatusCode(error) !== 503) return false;
58235
- const data = isRecord$1(error) && isRecord$1(error.data) ? error.data : null;
59212
+ const data = isRecord$3(error) && isRecord$3(error.data) ? error.data : null;
58236
59213
  return (typeof data?.error === "string" ? data.error : errorMessage(error)).toLowerCase().includes("revocation was recorded");
58237
59214
  }
58238
59215
  function printCommandError(label, error) {
@@ -58381,6 +59358,35 @@ function createDevicesCommand(overrides = {}) {
58381
59358
  dependencies.setExitCode(1);
58382
59359
  }
58383
59360
  });
59361
+ command.command("listen").description("Forward this Runtype workspace's calls to a localhost agent. Does not publish a URL. Does not bind that port.").option("--forward-to <url>", "Loopback HTTP URL of the local agent").option("--name <name>", "Device name shown in Runtype").option("--json", "Output the connected session as JSON").action(async (options) => {
59362
+ let forwardTo;
59363
+ try {
59364
+ forwardTo = assertListenForwardTarget(options.forwardTo);
59365
+ } catch (error) {
59366
+ printCommandError("Cannot forward to that URL", error);
59367
+ dependencies.setExitCode(1);
59368
+ return;
59369
+ }
59370
+ const client = await authenticatedClient(dependencies);
59371
+ if (!client) return;
59372
+ try {
59373
+ const workspace = await client.get("/auth/me");
59374
+ if (typeof workspace.userId !== "string" || !workspace.userId || workspace.orgId !== null && typeof workspace.orgId !== "string") throw new Error("Cannot verify the authenticated workspace");
59375
+ await dependencies.runListen({
59376
+ client,
59377
+ apiUrl: dependencies.getApiUrl(),
59378
+ forwardTo,
59379
+ name: options.name,
59380
+ json: Boolean(options.json),
59381
+ identityStore: dependencies.createIdentityStore(workspace),
59382
+ sleep: dependencies.sleep,
59383
+ setExitCode: dependencies.setExitCode
59384
+ });
59385
+ } catch (error) {
59386
+ printCommandError("Failed to listen for workspace calls", error);
59387
+ dependencies.setExitCode(1);
59388
+ }
59389
+ });
58384
59390
  command.command("list").description("List paired devices and relay status").option("--json", "Output as JSON").action(async (options) => {
58385
59391
  try {
58386
59392
  const client = await authenticatedClient(dependencies);
@@ -58579,7 +59585,7 @@ function createDevicesCommand(overrides = {}) {
58579
59585
  return;
58580
59586
  }
58581
59587
  const entries = (await readFile(status.logPath, "utf8").catch((error) => {
58582
- if ((isRecord$1(error) ? error.code : void 0) === "ENOENT") return "";
59588
+ if ((isRecord$3(error) ? error.code : void 0) === "ENOENT") return "";
58583
59589
  throw error;
58584
59590
  })).split(/\r?\n/u);
58585
59591
  if (entries.at(-1) === "") entries.pop();