@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.
- package/README.md +11 -1
- package/dist/index.js +1189 -183
- 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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
14800
|
+
if (!isRecord$3(node)) return;
|
|
14788
14801
|
if (node.enabled === false && !state.includeDisabled) return;
|
|
14789
|
-
const config = isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
14848
|
-
const config = isRecord$
|
|
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$
|
|
14852
|
-
if (!state.skipInlineFlows && isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
18904
|
-
return isEffectiveApprovalPolicy((isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
24147
|
+
if (!isRecord$3(tool) || tool.enabled === false) continue;
|
|
24147
24148
|
if (isClientExecutedToolType(tool.toolType)) acc.hasClientExecutedTools = true;
|
|
24148
|
-
const config = isRecord$
|
|
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$
|
|
24152
|
-
const inlineAgent = isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
24185
|
-
if (isRecord$
|
|
24186
|
-
if (isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
24213
|
-
if (!isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
38216
|
-
const content = fs$
|
|
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$
|
|
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$
|
|
38289
|
-
fs$
|
|
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$
|
|
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$
|
|
41454
|
-
const raw = fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
42943
|
-
fs$
|
|
42944
|
-
fs$
|
|
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$
|
|
42974
|
-
if (!fs$
|
|
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$
|
|
42979
|
-
fs$
|
|
42980
|
-
fs$
|
|
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$
|
|
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$
|
|
42997
|
-
fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
43751
|
+
if (fs$2.existsSync(homePath)) return homePath;
|
|
43751
43752
|
const oldMarathonPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "marathons"));
|
|
43752
|
-
if (fs$
|
|
43753
|
+
if (fs$2.existsSync(oldMarathonPath)) return oldMarathonPath;
|
|
43753
43754
|
const oldTaskPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "tasks"));
|
|
43754
|
-
if (fs$
|
|
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$
|
|
43766
|
-
const files = fs$
|
|
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$
|
|
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$
|
|
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$
|
|
44134
|
+
const stat = fs$2.statSync(filePath);
|
|
44134
44135
|
if (!stat.isFile() || stat.size > 1048576) return null;
|
|
44135
|
-
const buffer = fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
44228
|
-
canonicalPath: fs$
|
|
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$
|
|
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$
|
|
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$
|
|
44255
|
+
canonicalPath = fs$2.realpathSync.native(candidatePath);
|
|
44255
44256
|
}
|
|
44256
44257
|
return {
|
|
44257
44258
|
canonicalPath,
|
|
44258
|
-
exists: fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
44379
|
-
fs$
|
|
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$
|
|
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$
|
|
44560
|
-
fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
45113
|
-
if (!fs$
|
|
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$
|
|
45264
|
+
if (!fs$2.existsSync(rulesDir)) return [];
|
|
45264
45265
|
const rules = [];
|
|
45265
45266
|
try {
|
|
45266
|
-
const entries = fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
48768
|
-
fs$
|
|
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$
|
|
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$
|
|
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$
|
|
48794
|
-
fs$
|
|
48795
|
-
fs$
|
|
48796
|
-
fs$
|
|
48797
|
-
fs$
|
|
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$
|
|
48800
|
-
fs$
|
|
48801
|
-
fs$
|
|
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$
|
|
48804
|
-
fs$
|
|
48805
|
-
fs$
|
|
48806
|
-
fs$
|
|
48807
|
-
fs$
|
|
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$
|
|
48810
|
-
fs$
|
|
48811
|
-
fs$
|
|
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$
|
|
48814
|
-
fs$
|
|
48815
|
-
fs$
|
|
48816
|
-
fs$
|
|
48817
|
-
fs$
|
|
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$
|
|
48820
|
-
fs$
|
|
48821
|
-
fs$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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();
|