@tangle-network/agent-app 0.44.60 → 0.45.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/index.js +3 -3
- package/dist/chat-routes/index.js +4 -4
- package/dist/{chunk-CQZSAR77.js → chunk-73F3CKVK.js} +28 -18
- package/dist/chunk-73F3CKVK.js.map +1 -0
- package/dist/{chunk-APFJITYT.js → chunk-AOQ4FCSH.js} +5 -343
- package/dist/chunk-AOQ4FCSH.js.map +1 -0
- package/dist/{chunk-S4YW2Y52.js → chunk-BATKJP3P.js} +1 -1
- package/dist/chunk-BATKJP3P.js.map +1 -0
- package/dist/{chunk-ICOHEZK6.js → chunk-ITCINLSU.js} +2 -2
- package/dist/{chunk-3EJ6SFJI.js → chunk-IVEKCOM7.js} +68 -26
- package/dist/chunk-IVEKCOM7.js.map +1 -0
- package/dist/{chunk-WXWU2FEB.js → chunk-RDK3CN7Y.js} +2 -2
- package/dist/design-canvas/index.d.ts +2 -1
- package/dist/design-canvas/index.js +1 -1
- package/dist/harness/index.d.ts +25 -8
- package/dist/harness/index.js +1 -1
- package/dist/mcp-BOgNWSED.d.ts +174 -0
- package/dist/run/index.js +1 -1
- package/dist/runtime/index.d.ts +17 -7
- package/dist/runtime/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +42 -503
- package/dist/sandbox/index.js +4 -38
- package/dist/sequences/index.d.ts +2 -1
- package/dist/sequences/index.js +1 -1
- package/dist/skills-placement/index.js +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +5 -3
- package/dist/web-react/index.js +3 -3
- package/dist/web-react/terminal.d.ts +9 -13
- package/dist/web-react/terminal.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-3EJ6SFJI.js.map +0 -1
- package/dist/chunk-APFJITYT.js.map +0 -1
- package/dist/chunk-CQZSAR77.js.map +0 -1
- package/dist/chunk-S4YW2Y52.js.map +0 -1
- package/dist/mcp-Dt4V4ZLT.d.ts +0 -95
- /package/dist/{chunk-ICOHEZK6.js.map → chunk-ITCINLSU.js.map} +0 -0
- /package/dist/{chunk-WXWU2FEB.js.map → chunk-RDK3CN7Y.js.map} +0 -0
package/dist/assistant/index.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
ChatMessages,
|
|
4
4
|
ModelPicker,
|
|
5
5
|
ProviderLogo
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-RDK3CN7Y.js";
|
|
7
7
|
import "../chunk-FBVLEGEG.js";
|
|
8
8
|
import "../chunk-GINKOZFD.js";
|
|
9
9
|
import "../chunk-YTMKRL3L.js";
|
|
10
10
|
import "../chunk-GEYACSFW.js";
|
|
11
11
|
import "../chunk-6MUJROBT.js";
|
|
12
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-BATKJP3P.js";
|
|
13
13
|
import "../chunk-QY4BRKRJ.js";
|
|
14
14
|
import "../chunk-WBHPN5DY.js";
|
|
15
15
|
import "../chunk-UXMIPX3Z.js";
|
|
@@ -18,7 +18,7 @@ import "../chunk-KWXUBMXU.js";
|
|
|
18
18
|
import "../chunk-5EPIPT4V.js";
|
|
19
19
|
import "../chunk-3ZK5IJSW.js";
|
|
20
20
|
import "../chunk-YJMCRXQQ.js";
|
|
21
|
-
import "../chunk-
|
|
21
|
+
import "../chunk-73F3CKVK.js";
|
|
22
22
|
|
|
23
23
|
// src/assistant/sse.ts
|
|
24
24
|
var SSEChunkParser = class {
|
|
@@ -100,11 +100,11 @@ import {
|
|
|
100
100
|
flattenHistory,
|
|
101
101
|
readSandboxBinaryBytes,
|
|
102
102
|
statSandboxFileSize
|
|
103
|
-
} from "../chunk-
|
|
103
|
+
} from "../chunk-AOQ4FCSH.js";
|
|
104
104
|
import "../chunk-LWSJK546.js";
|
|
105
|
-
import "../chunk-
|
|
106
|
-
import "../chunk-
|
|
107
|
-
import "../chunk-
|
|
105
|
+
import "../chunk-73F3CKVK.js";
|
|
106
|
+
import "../chunk-ITCINLSU.js";
|
|
107
|
+
import "../chunk-IVEKCOM7.js";
|
|
108
108
|
import "../chunk-YEFFHORB.js";
|
|
109
109
|
import "../chunk-S5SRJJQG.js";
|
|
110
110
|
import "../chunk-JML7WKWU.js";
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
// src/harness/index.ts
|
|
2
2
|
import {
|
|
3
3
|
harnessSupportsModel,
|
|
4
|
+
harnessTypeSchema,
|
|
4
5
|
modelProvider,
|
|
5
6
|
preferredHarnessForModel,
|
|
6
7
|
snapHarnessToModel as aiSnapHarnessToModel,
|
|
7
8
|
snapModelToHarness as aiSnapModelToHarness
|
|
8
9
|
} from "@tangle-network/agent-interface";
|
|
10
|
+
var NON_BACKEND_HARNESSES = ["gemini"];
|
|
11
|
+
var EXTRA_HARNESSES = ["forge", "cursor"];
|
|
12
|
+
var nonBackendHarnesses = new Set(NON_BACKEND_HARNESSES);
|
|
9
13
|
var KNOWN_HARNESSES = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"codex",
|
|
15
|
-
"amp",
|
|
16
|
-
"factory-droids",
|
|
17
|
-
"pi",
|
|
18
|
-
"hermes",
|
|
19
|
-
"forge",
|
|
20
|
-
"openclaw",
|
|
21
|
-
"acp",
|
|
22
|
-
"cursor",
|
|
23
|
-
"cli-base"
|
|
14
|
+
...harnessTypeSchema.options.filter(
|
|
15
|
+
(harness) => !nonBackendHarnesses.has(harness)
|
|
16
|
+
),
|
|
17
|
+
...EXTRA_HARNESSES
|
|
24
18
|
];
|
|
25
19
|
var DEFAULT_HARNESS = "opencode";
|
|
26
20
|
var HARNESS_SET = new Set(KNOWN_HARNESSES);
|
|
@@ -40,14 +34,30 @@ function resolveSessionHarness(input = {}) {
|
|
|
40
34
|
const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback));
|
|
41
35
|
return { harness, locked: false, swapAttempted: false };
|
|
42
36
|
}
|
|
37
|
+
var canonicalHarnesses = new Set(harnessTypeSchema.options);
|
|
38
|
+
function canonicalHarness(harness) {
|
|
39
|
+
return canonicalHarnesses.has(harness) ? harness : null;
|
|
40
|
+
}
|
|
43
41
|
function isModelCompatibleWithHarness(harness, modelId) {
|
|
44
|
-
|
|
42
|
+
const canonical = canonicalHarness(harness);
|
|
43
|
+
if (!canonical) return true;
|
|
44
|
+
return harnessSupportsModel(canonical, modelId);
|
|
45
45
|
}
|
|
46
46
|
function snapModelToHarness(harness, modelId, canonicalIds) {
|
|
47
|
-
|
|
47
|
+
const canonical = canonicalHarness(harness);
|
|
48
|
+
if (!canonical) return modelId;
|
|
49
|
+
return aiSnapModelToHarness(canonical, modelId, canonicalIds);
|
|
48
50
|
}
|
|
49
51
|
function snapHarnessToModel(harness, modelId) {
|
|
50
|
-
|
|
52
|
+
const canonical = canonicalHarness(harness);
|
|
53
|
+
if (!canonical) return harness;
|
|
54
|
+
const snapped = aiSnapHarnessToModel(canonical, modelId);
|
|
55
|
+
if (!isHarness(snapped)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Harness "${harness}" snapped to "${snapped}" for model "${modelId}", which this platform has no backend for.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return snapped;
|
|
51
61
|
}
|
|
52
62
|
function assertHarnessModelCompatible(harness, modelId) {
|
|
53
63
|
if (!isModelCompatibleWithHarness(harness, modelId)) {
|
|
@@ -71,4 +81,4 @@ export {
|
|
|
71
81
|
snapHarnessToModel,
|
|
72
82
|
assertHarnessModelCompatible
|
|
73
83
|
};
|
|
74
|
-
//# sourceMappingURL=chunk-
|
|
84
|
+
//# sourceMappingURL=chunk-73F3CKVK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/harness/index.ts"],"sourcesContent":["/**\n * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.\n *\n * A \"harness\" is the coding-agent CLI a sandbox drives (opencode / codex /\n * claude-code / …). The shell governs WHICH harness a chat session uses and\n * enforces that a session is LOCKED to the harness it started with — the model\n * may change mid-session, the harness may not (swapping it mid-session would\n * orphan the session's running agent state). Every product otherwise hand-rolls\n * this and hard-codes a single harness; this is the one place the rule lives.\n *\n * Substrate-free: the harness list is derived from `@tangle-network/agent-interface`'s canonical\n * harness enum and mirrors the sandbox SDK's `BackendType` (no sandbox dependency). The consumer\n * owns storage — which harness a workspace defaults to, which one a session locked — and maps the\n * resolved value onto the SDK's `backend.type`.\n *\n * Harness↔model COMPATIBILITY (which models a harness can run, snapping) is NOT defined here — it\n * comes from `@tangle-network/agent-interface`, the single source of truth shared with the\n * sandbox-ui pickers and the cli-bridge backends. This module owns the harness TAXONOMY + the\n * session lock.\n */\n\nimport {\n harnessSupportsModel,\n harnessTypeSchema,\n modelProvider,\n preferredHarnessForModel,\n snapHarnessToModel as aiSnapHarnessToModel,\n snapModelToHarness as aiSnapModelToHarness,\n type HarnessType,\n} from '@tangle-network/agent-interface'\n\n/**\n * Canonical harnesses that are NOT sandbox backends. `KNOWN_HARNESSES` is dispatched as\n * `backend.type`, so a harness the platform ships no provider adapter for must stay out —\n * offering it would resolve a session onto a runner that cannot start.\n */\nconst NON_BACKEND_HARNESSES = ['gemini'] as const satisfies readonly HarnessType[]\n\n/**\n * Backends the platform runs that the canonical enum does not carry yet. The guard below turns\n * their adoption upstream into a compile error, so this list is emptied rather than duplicating a\n * canonical id.\n */\nconst EXTRA_HARNESSES = ['forge', 'cursor'] as const\n\ntype ExtrasAlreadyCanonical = Extract<HarnessType, (typeof EXTRA_HARNESSES)[number]>\nconst _extrasAreNotYetCanonical: ExtrasAlreadyCanonical extends never ? true : never = true\nvoid _extrasAreNotYetCanonical\n\nconst nonBackendHarnesses = new Set<string>(NON_BACKEND_HARNESSES)\n\n/** A coding-agent backend this shell can dispatch a session onto. */\nexport type Harness =\n | Exclude<HarnessType, (typeof NON_BACKEND_HARNESSES)[number]>\n | (typeof EXTRA_HARNESSES)[number]\n\n/**\n * The known coding-agent backends: the canonical harness set minus what has no provider adapter,\n * plus adapters the canonical set has yet to adopt. Derived from `harnessTypeSchema.options` so a\n * harness added upstream reaches this shell without an edit here, and kept structural so this\n * module still needs no sandbox dependency.\n */\nexport const KNOWN_HARNESSES: readonly Harness[] = [\n ...harnessTypeSchema.options.filter(\n (harness): harness is Exclude<HarnessType, (typeof NON_BACKEND_HARNESSES)[number]> =>\n !nonBackendHarnesses.has(harness),\n ),\n ...EXTRA_HARNESSES,\n]\n\n/** Define the default harness to use for code execution and testing environments */\nexport const DEFAULT_HARNESS: Harness = 'opencode'\n\nconst HARNESS_SET: ReadonlySet<string> = new Set(KNOWN_HARNESSES)\n\n/** Determine if a value is a recognized harness string identifier */\nexport function isHarness(value: unknown): value is Harness {\n return typeof value === 'string' && HARNESS_SET.has(value)\n}\n\n/** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */\nexport function coerceHarness(value: unknown, fallback: Harness = DEFAULT_HARNESS): Harness {\n return isHarness(value) ? value : fallback\n}\n\n/** Resolve input options to determine the appropriate session harness to use */\nexport interface ResolveSessionHarnessInput {\n /** The harness already locked to this session (recorded at its first turn). */\n sessionHarness?: unknown\n /** The harness requested now — a new session's choice, or a turn's attempt to switch. */\n requested?: unknown\n /** The workspace's default harness, used only when starting a fresh session. */\n workspaceDefault?: unknown\n /** Final fallback when nothing else resolves (default `opencode`). */\n fallback?: Harness\n}\n\n/** Represent resolved session state including harness, lock status, and swap attempt flag */\nexport interface ResolvedSessionHarness {\n /** The harness to actually run — the locked one when the session already has it. */\n harness: Harness\n /** True when the session already had a locked harness (this turn did not pick it). */\n locked: boolean\n /** True when `requested` differs from the locked harness — a forbidden mid-session\n * swap the caller should reject or warn on. The lock always wins regardless. */\n swapAttempted: boolean\n}\n\n/**\n * Resolve the harness for a turn, enforcing the session lock.\n *\n * - **Session already started** (`sessionHarness` is a known harness): that harness\n * wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller\n * can reject the swap. The model is a separate per-turn concern and is unaffected.\n * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller\n * persists the result as the session's lock for every subsequent turn.\n */\nexport function resolveSessionHarness(input: ResolveSessionHarnessInput = {}): ResolvedSessionHarness {\n const fallback = input.fallback ?? DEFAULT_HARNESS\n if (isHarness(input.sessionHarness)) {\n const locked = input.sessionHarness\n const swapAttempted = isHarness(input.requested) && input.requested !== locked\n return { harness: locked, locked: true, swapAttempted }\n }\n const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback))\n return { harness, locked: false, swapAttempted: false }\n}\n\n/**\n * Harness ↔ model compatibility + snapping — delegated to `@tangle-network/agent-interface`.\n *\n * `Harness` and `HarnessType` OVERLAP rather than nest: this shell drops canonical harnesses the\n * platform has no backend for, and carries backends the canonical enum has yet to adopt. So a\n * harness is narrowed to a canonical id before any capability lookup, and the non-canonical\n * remainder is answered here — those runners are multi-provider CLIs with no vendor lock, so they\n * run any model and never snap. As the canonical enum adopts them, `canonicalHarness` starts\n * returning them and the lookup below takes over with no change at these call sites.\n */\n\nconst canonicalHarnesses: ReadonlySet<string> = new Set(harnessTypeSchema.options)\n\n/** The canonical id for a harness, or `null` for a backend the canonical enum does not list. */\nfunction canonicalHarness(harness: Harness): HarnessType | null {\n return canonicalHarnesses.has(harness) ? (harness as HarnessType) : null\n}\n\nexport { modelProvider }\n\n/** Provider-less ids (sentinels like \"default\", or a session's own config) are\n * compatible everywhere — every harness honors its own configuration. */\nexport function isModelCompatibleWithHarness(harness: Harness, modelId: string): boolean {\n const canonical = canonicalHarness(harness)\n if (!canonical) return true\n return harnessSupportsModel(canonical, modelId)\n}\n\n/** Keep `modelId` when the harness can run it; else the harness's best compatible\n * catalog id (preferred patterns in order, highest version). When nothing in the\n * catalog fits, return the original so the caller sees the incompatibility. */\nexport function snapModelToHarness(harness: Harness, modelId: string, canonicalIds: readonly string[]): string {\n const canonical = canonicalHarness(harness)\n if (!canonical) return modelId\n return aiSnapModelToHarness(canonical, modelId, canonicalIds)\n}\n\n/** Keep the harness when it can run `modelId`; else the model's native harness\n * (anthropic → claude-code, openai → codex, moonshot → kimi-code), falling back to opencode. */\nexport function snapHarnessToModel(harness: Harness, modelId: string): Harness {\n const canonical = canonicalHarness(harness)\n if (!canonical) return harness\n const snapped = aiSnapHarnessToModel(canonical, modelId)\n if (!isHarness(snapped)) {\n throw new Error(\n `Harness \"${harness}\" snapped to \"${snapped}\" for model \"${modelId}\", which this platform has no backend for.`,\n )\n }\n return snapped\n}\n\n/** Fail-loud server guard: throw when a harness is asked to run a model it can't.\n * Call before dispatching a sandbox turn so a bypassed UI can't reach the sidecar\n * with an incompatible pair. */\nexport function assertHarnessModelCompatible(harness: Harness, modelId: string): void {\n if (!isModelCompatibleWithHarness(harness, modelId)) {\n const provider = modelProvider(modelId)\n const native = preferredHarnessForModel(modelId)\n throw new Error(\n `Harness \"${harness}\" cannot run model \"${modelId}\" (provider \"${provider}\"). ` +\n `Use ${native ?? 'a router-backed harness (opencode)'} or an allowed model.`,\n )\n }\n}\n"],"mappings":";AAqBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,OAEjB;AAOP,IAAM,wBAAwB,CAAC,QAAQ;AAOvC,IAAM,kBAAkB,CAAC,SAAS,QAAQ;AAM1C,IAAM,sBAAsB,IAAI,IAAY,qBAAqB;AAa1D,IAAM,kBAAsC;AAAA,EACjD,GAAG,kBAAkB,QAAQ;AAAA,IAC3B,CAAC,YACC,CAAC,oBAAoB,IAAI,OAAO;AAAA,EACpC;AAAA,EACA,GAAG;AACL;AAGO,IAAM,kBAA2B;AAExC,IAAM,cAAmC,IAAI,IAAI,eAAe;AAGzD,SAAS,UAAU,OAAkC;AAC1D,SAAO,OAAO,UAAU,YAAY,YAAY,IAAI,KAAK;AAC3D;AAGO,SAAS,cAAc,OAAgB,WAAoB,iBAA0B;AAC1F,SAAO,UAAU,KAAK,IAAI,QAAQ;AACpC;AAkCO,SAAS,sBAAsB,QAAoC,CAAC,GAA2B;AACpG,QAAM,WAAW,MAAM,YAAY;AACnC,MAAI,UAAU,MAAM,cAAc,GAAG;AACnC,UAAM,SAAS,MAAM;AACrB,UAAM,gBAAgB,UAAU,MAAM,SAAS,KAAK,MAAM,cAAc;AACxE,WAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM,cAAc;AAAA,EACxD;AACA,QAAM,UAAU,cAAc,MAAM,WAAW,cAAc,MAAM,kBAAkB,QAAQ,CAAC;AAC9F,SAAO,EAAE,SAAS,QAAQ,OAAO,eAAe,MAAM;AACxD;AAaA,IAAM,qBAA0C,IAAI,IAAI,kBAAkB,OAAO;AAGjF,SAAS,iBAAiB,SAAsC;AAC9D,SAAO,mBAAmB,IAAI,OAAO,IAAK,UAA0B;AACtE;AAMO,SAAS,6BAA6B,SAAkB,SAA0B;AACvF,QAAM,YAAY,iBAAiB,OAAO;AAC1C,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,qBAAqB,WAAW,OAAO;AAChD;AAKO,SAAS,mBAAmB,SAAkB,SAAiB,cAAyC;AAC7G,QAAM,YAAY,iBAAiB,OAAO;AAC1C,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,qBAAqB,WAAW,SAAS,YAAY;AAC9D;AAIO,SAAS,mBAAmB,SAAkB,SAA0B;AAC7E,QAAM,YAAY,iBAAiB,OAAO;AAC1C,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,UAAU,qBAAqB,WAAW,OAAO;AACvD,MAAI,CAAC,UAAU,OAAO,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,YAAY,OAAO,iBAAiB,OAAO,gBAAgB,OAAO;AAAA,IACpE;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,6BAA6B,SAAkB,SAAuB;AACpF,MAAI,CAAC,6BAA6B,SAAS,OAAO,GAAG;AACnD,UAAM,WAAW,cAAc,OAAO;AACtC,UAAM,SAAS,yBAAyB,OAAO;AAC/C,UAAM,IAAI;AAAA,MACR,YAAY,OAAO,uBAAuB,OAAO,gBAAgB,QAAQ,WAChE,UAAU,oCAAoC;AAAA,IACzD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -4,16 +4,10 @@ import {
|
|
|
4
4
|
} from "./chunk-LWSJK546.js";
|
|
5
5
|
import {
|
|
6
6
|
assertHarnessModelCompatible
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-73F3CKVK.js";
|
|
8
8
|
import {
|
|
9
9
|
buildAppToolMcpServer
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
base64UrlDecodeText,
|
|
13
|
-
base64UrlEncodeText,
|
|
14
|
-
constantTimeEqual,
|
|
15
|
-
hmacSha256Base64Url
|
|
16
|
-
} from "./chunk-S5SRJJQG.js";
|
|
10
|
+
} from "./chunk-IVEKCOM7.js";
|
|
17
11
|
import {
|
|
18
12
|
resolveTangleExecutionEnvironment,
|
|
19
13
|
trimOrNull
|
|
@@ -161,35 +155,7 @@ async function readSandboxBinaryBytes(box, absolutePath, expectedSize, options)
|
|
|
161
155
|
return { succeeded: true, value: { bytes, size: expectedSize } };
|
|
162
156
|
}
|
|
163
157
|
|
|
164
|
-
// src/sandbox/
|
|
165
|
-
var TERMINAL_PROXY_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
166
|
-
async function mintTerminalProxyToken(secret, identity, ttlMs = TERMINAL_PROXY_TOKEN_TTL_MS, now = Date.now) {
|
|
167
|
-
if (!secret) return fail(new Error("mintTerminalProxyToken: secret is required"));
|
|
168
|
-
if (!identity.userId || !identity.workspaceId || !identity.sandboxId) {
|
|
169
|
-
return fail(new Error("mintTerminalProxyToken: userId/workspaceId/sandboxId are required"));
|
|
170
|
-
}
|
|
171
|
-
const expiresAt = new Date(now() + ttlMs);
|
|
172
|
-
const payload = { ...identity, exp: Math.floor(expiresAt.getTime() / 1e3) };
|
|
173
|
-
const encoded = base64UrlEncodeText(JSON.stringify(payload));
|
|
174
|
-
const sig = await hmacSha256Base64Url(encoded, secret);
|
|
175
|
-
return ok({ token: `${encoded}.${sig}`, expiresAt });
|
|
176
|
-
}
|
|
177
|
-
async function verifyTerminalProxyToken(secret, token, expected, now = Date.now) {
|
|
178
|
-
if (!secret) return false;
|
|
179
|
-
const [encoded, sig, extra] = token.split(".");
|
|
180
|
-
if (!encoded || !sig || extra !== void 0) return false;
|
|
181
|
-
const expectedSig = await hmacSha256Base64Url(encoded, secret);
|
|
182
|
-
if (!constantTimeEqual(sig, expectedSig)) return false;
|
|
183
|
-
let payload;
|
|
184
|
-
try {
|
|
185
|
-
payload = JSON.parse(base64UrlDecodeText(encoded));
|
|
186
|
-
} catch {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
return payload.userId === expected.userId && payload.workspaceId === expected.workspaceId && payload.sandboxId === expected.sandboxId && Number.isFinite(payload.exp) && payload.exp > Math.floor(now() / 1e3);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/sandbox/workspace-terminal.ts
|
|
158
|
+
// src/sandbox/workspace-sandbox-manager.ts
|
|
193
159
|
function createWorkspaceSandboxManager(opts) {
|
|
194
160
|
return {
|
|
195
161
|
async ensureWorkspaceSandbox(workspaceId, userId, options) {
|
|
@@ -222,293 +188,6 @@ function createWorkspaceSandboxManager(opts) {
|
|
|
222
188
|
}
|
|
223
189
|
};
|
|
224
190
|
}
|
|
225
|
-
var DEFAULT_TERMINAL_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
226
|
-
var BEARER_SUBPROTOCOL_PREFIX = "bearer.";
|
|
227
|
-
var LEGACY_TERMINAL_TOKEN_PREFIX = "sbxt_";
|
|
228
|
-
async function createSandboxTerminalToken(subject, opts) {
|
|
229
|
-
validateTerminalSubject(subject);
|
|
230
|
-
const secret = opts.secret?.trim();
|
|
231
|
-
if (!secret) throw new Error("terminal token secret is required");
|
|
232
|
-
const now = opts.now ?? Date.now;
|
|
233
|
-
const expiresInMs = opts.expiresInMs ?? DEFAULT_TERMINAL_TOKEN_TTL_MS;
|
|
234
|
-
if (!Number.isFinite(expiresInMs) || expiresInMs <= 0) throw new Error("expiresInMs must be a positive number");
|
|
235
|
-
const minted = await mintTerminalProxyToken(secret, subject, expiresInMs, now);
|
|
236
|
-
if (!minted.succeeded) throw minted.error;
|
|
237
|
-
return minted.value;
|
|
238
|
-
}
|
|
239
|
-
async function verifySandboxTerminalToken(token, expected, opts) {
|
|
240
|
-
validateTerminalSubject(expected);
|
|
241
|
-
const secret = opts.secret?.trim();
|
|
242
|
-
const now = opts.now ?? Date.now;
|
|
243
|
-
const normalized = token.startsWith(LEGACY_TERMINAL_TOKEN_PREFIX) ? token.slice(LEGACY_TERMINAL_TOKEN_PREFIX.length) : token;
|
|
244
|
-
return verifyTerminalProxyToken(secret ?? "", normalized, expected, now);
|
|
245
|
-
}
|
|
246
|
-
function createWorkspaceSandboxConnectionHandler(opts) {
|
|
247
|
-
return async function handleWorkspaceSandboxConnection({ request, params }) {
|
|
248
|
-
const user = await opts.requireUser(request);
|
|
249
|
-
const workspaceId = params.workspaceId;
|
|
250
|
-
if (!workspaceId) return Response.json({ error: "workspaceId is required" }, { status: 400 });
|
|
251
|
-
await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId });
|
|
252
|
-
let box;
|
|
253
|
-
try {
|
|
254
|
-
box = await opts.ensureWorkspaceSandbox(workspaceId, user.id);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
return Response.json(
|
|
257
|
-
{ error: err instanceof Error ? err.message : "Failed to provision workspace sandbox" },
|
|
258
|
-
{ status: 500 }
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
const directSidecarUrl = box.connection?.sidecarUrl ?? box.connection?.runtimeUrl;
|
|
262
|
-
const directSidecarToken = box.connection?.authToken ?? box.connection?.sidecarToken;
|
|
263
|
-
const directSidecarExpiresAt = box.connection?.authTokenExpiresAt;
|
|
264
|
-
if (opts.exposeDirectSidecar && directSidecarUrl && directSidecarToken && directSidecarExpiresAt) {
|
|
265
|
-
return Response.json({
|
|
266
|
-
runtimeUrl: directSidecarUrl,
|
|
267
|
-
sidecarUrl: directSidecarUrl,
|
|
268
|
-
token: directSidecarToken,
|
|
269
|
-
expiresAt: directSidecarExpiresAt,
|
|
270
|
-
status: box.status,
|
|
271
|
-
sandboxId: box.id
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
if (!directSidecarUrl) {
|
|
275
|
-
return Response.json(
|
|
276
|
-
{
|
|
277
|
-
error: "Workspace sandbox runtime not ready. The sandbox is still initializing -- retry in a few seconds.",
|
|
278
|
-
status: box.status
|
|
279
|
-
},
|
|
280
|
-
{ status: 503 }
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
|
|
284
|
-
let scoped;
|
|
285
|
-
try {
|
|
286
|
-
scoped = await createSandboxTerminalToken(
|
|
287
|
-
{ userId: user.id, workspaceId, sandboxId: box.id },
|
|
288
|
-
{ secret, expiresInMs: opts.tokenExpiresInMs }
|
|
289
|
-
);
|
|
290
|
-
} catch (err) {
|
|
291
|
-
return Response.json(
|
|
292
|
-
{ error: err instanceof Error ? err.message : "Failed to mint sandbox token" },
|
|
293
|
-
{ status: 503 }
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
const runtimeUrl = opts.proxyRuntimeUrl ? opts.proxyRuntimeUrl({ request, workspaceId, sandboxId: box.id, box }) : `/api/workspaces/${encodeURIComponent(workspaceId)}/sandbox/runtime/${encodeURIComponent(box.id)}`;
|
|
297
|
-
return Response.json({
|
|
298
|
-
runtimeUrl,
|
|
299
|
-
sidecarUrl: runtimeUrl,
|
|
300
|
-
token: scoped.token,
|
|
301
|
-
expiresAt: scoped.expiresAt.toISOString(),
|
|
302
|
-
status: box.status,
|
|
303
|
-
sandboxId: box.id
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
function sandboxSidecarProxyUrl(baseUrl, sandboxId) {
|
|
308
|
-
if (!baseUrl) throw new Error("baseUrl is required");
|
|
309
|
-
if (!sandboxId) throw new Error("sandboxId is required");
|
|
310
|
-
return new URL(`/v1/sidecar-proxy/${encodeURIComponent(sandboxId)}`, baseUrl).toString().replace(/\/+$/, "");
|
|
311
|
-
}
|
|
312
|
-
function createWorkspaceSandboxRuntimeProxyHandler(opts) {
|
|
313
|
-
return async function handleWorkspaceSandboxRuntimeProxy({ request, params }) {
|
|
314
|
-
const user = await opts.requireUser(request);
|
|
315
|
-
const workspaceId = params.workspaceId;
|
|
316
|
-
const sandboxId = params.sandboxId;
|
|
317
|
-
const runtimePath = params["*"];
|
|
318
|
-
if (!workspaceId || !sandboxId || !runtimePath) {
|
|
319
|
-
return Response.json({ error: "workspaceId, sandboxId, and runtime path are required" }, { status: 400 });
|
|
320
|
-
}
|
|
321
|
-
const encodedRuntimePath = encodeSandboxRuntimePath(runtimePath);
|
|
322
|
-
if (!encodedRuntimePath) return Response.json({ error: "Invalid sandbox runtime path" }, { status: 400 });
|
|
323
|
-
await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId });
|
|
324
|
-
const token = terminalTokenFromRequest(request.headers);
|
|
325
|
-
const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
|
|
326
|
-
if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret })) {
|
|
327
|
-
return Response.json({ error: "Invalid terminal token" }, { status: 403 });
|
|
328
|
-
}
|
|
329
|
-
const requestUrl = new URL(request.url);
|
|
330
|
-
const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId });
|
|
331
|
-
const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null;
|
|
332
|
-
const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId });
|
|
333
|
-
const upstreamUrl = directRuntimeConnection ? new URL(encodedRuntimePath, `${directRuntimeConnection.runtimeUrl.replace(/\/+$/, "")}/`) : new URL(encodedRuntimePath, `${sandboxSidecarProxyUrl(credentials.baseUrl, sandboxId)}/`);
|
|
334
|
-
upstreamUrl.search = requestUrl.search;
|
|
335
|
-
const headers = buildSandboxRuntimeProxyHeaders(
|
|
336
|
-
request.headers,
|
|
337
|
-
directRuntimeConnection?.authToken ?? credentials.apiKey,
|
|
338
|
-
opts.forwardHeaders
|
|
339
|
-
);
|
|
340
|
-
const init = {
|
|
341
|
-
method: request.method,
|
|
342
|
-
headers,
|
|
343
|
-
redirect: "manual"
|
|
344
|
-
};
|
|
345
|
-
if (request.method !== "GET" && request.method !== "HEAD" && request.body) {
|
|
346
|
-
init.body = request.body;
|
|
347
|
-
init.duplex = "half";
|
|
348
|
-
}
|
|
349
|
-
const fetchImpl = opts.fetch ?? fetch;
|
|
350
|
-
const response = await fetchImpl(upstreamUrl, init);
|
|
351
|
-
const responseHeaders = new Headers(response.headers);
|
|
352
|
-
responseHeaders.delete("set-cookie");
|
|
353
|
-
return new Response(response.body, {
|
|
354
|
-
status: response.status,
|
|
355
|
-
statusText: response.statusText,
|
|
356
|
-
headers: responseHeaders
|
|
357
|
-
});
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
var SANDBOX_TERMINAL_WS_PATHNAME = /^\/api\/workspaces\/([^/]+)\/sandbox\/runtime\/([^/]+)\/(terminals\/[^/]+\/ws)$/;
|
|
361
|
-
function matchSandboxTerminalWsPath(pathname) {
|
|
362
|
-
const m = SANDBOX_TERMINAL_WS_PATHNAME.exec(pathname);
|
|
363
|
-
if (!m) return null;
|
|
364
|
-
const [, workspaceId, sandboxId, subPath] = m;
|
|
365
|
-
if (!workspaceId || !sandboxId || !subPath) return null;
|
|
366
|
-
const decodedWorkspaceId = safeDecodeURIComponent(workspaceId);
|
|
367
|
-
const decodedSandboxId = safeDecodeURIComponent(sandboxId);
|
|
368
|
-
if (!decodedWorkspaceId || !decodedSandboxId) return null;
|
|
369
|
-
return { workspaceId: decodedWorkspaceId, sandboxId: decodedSandboxId, subPath };
|
|
370
|
-
}
|
|
371
|
-
function isSandboxTerminalWsUpgrade(request) {
|
|
372
|
-
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") return false;
|
|
373
|
-
try {
|
|
374
|
-
return matchSandboxTerminalWsPath(new URL(request.url).pathname) !== null;
|
|
375
|
-
} catch {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
function createWorkspaceSandboxTerminalUpgradeHandler(opts) {
|
|
380
|
-
return async function handleWorkspaceSandboxTerminalUpgrade(request) {
|
|
381
|
-
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") return null;
|
|
382
|
-
let url;
|
|
383
|
-
try {
|
|
384
|
-
url = new URL(request.url);
|
|
385
|
-
} catch {
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
const match = matchSandboxTerminalWsPath(url.pathname);
|
|
389
|
-
if (!match) return null;
|
|
390
|
-
const { workspaceId, sandboxId, subPath } = match;
|
|
391
|
-
let user;
|
|
392
|
-
try {
|
|
393
|
-
user = await opts.requireUser(request);
|
|
394
|
-
} catch {
|
|
395
|
-
return new Response("Unauthorized", { status: 401 });
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId });
|
|
399
|
-
} catch {
|
|
400
|
-
return new Response("Forbidden", { status: 403 });
|
|
401
|
-
}
|
|
402
|
-
const token = terminalTokenFromRequest(request.headers);
|
|
403
|
-
const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
|
|
404
|
-
if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret })) {
|
|
405
|
-
return new Response("Invalid terminal token", { status: 403 });
|
|
406
|
-
}
|
|
407
|
-
const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId });
|
|
408
|
-
const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null;
|
|
409
|
-
const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId });
|
|
410
|
-
const upstreamUrl = directRuntimeConnection ? new URL(subPath, `${directRuntimeConnection.runtimeUrl.replace(/\/+$/, "")}/`) : new URL(subPath, `${sandboxSidecarProxyUrl(credentials.baseUrl, sandboxId)}/`);
|
|
411
|
-
upstreamUrl.search = url.search;
|
|
412
|
-
const upstreamHeaders = new Headers(request.headers);
|
|
413
|
-
const upstreamBearer = directRuntimeConnection?.authToken ?? credentials.apiKey;
|
|
414
|
-
upstreamHeaders.set("Authorization", `Bearer ${upstreamBearer}`);
|
|
415
|
-
upstreamHeaders.delete("host");
|
|
416
|
-
const browserProtocol = selectedBearerSubprotocol(request.headers.get("Sec-WebSocket-Protocol"));
|
|
417
|
-
stripBearerSubprotocol(upstreamHeaders);
|
|
418
|
-
const fetchImpl = opts.fetch ?? fetch;
|
|
419
|
-
const upstream = await fetchImpl(upstreamUrl.toString(), { method: request.method, headers: upstreamHeaders });
|
|
420
|
-
const echo = terminalUpgradeSubprotocolEcho(upstream, browserProtocol);
|
|
421
|
-
if (!echo) return upstream;
|
|
422
|
-
return new Response(null, {
|
|
423
|
-
status: echo.status,
|
|
424
|
-
statusText: echo.statusText,
|
|
425
|
-
headers: echo.headers,
|
|
426
|
-
webSocket: upstream.webSocket ?? null
|
|
427
|
-
});
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
function terminalUpgradeSubprotocolEcho(upstream, browserProtocol) {
|
|
431
|
-
if (upstream.status !== 101 || !browserProtocol) return null;
|
|
432
|
-
if (upstream.headers.has("Sec-WebSocket-Protocol")) return null;
|
|
433
|
-
const headers = new Headers(upstream.headers);
|
|
434
|
-
headers.set("Sec-WebSocket-Protocol", browserProtocol);
|
|
435
|
-
return { status: upstream.status, statusText: upstream.statusText ?? "", headers };
|
|
436
|
-
}
|
|
437
|
-
function selectedBearerSubprotocol(value) {
|
|
438
|
-
if (!value) return null;
|
|
439
|
-
for (const part of value.split(",")) {
|
|
440
|
-
const protocol = part.trim();
|
|
441
|
-
if (protocol.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX)) return protocol;
|
|
442
|
-
}
|
|
443
|
-
return null;
|
|
444
|
-
}
|
|
445
|
-
var DEFAULT_RUNTIME_PROXY_HEADERS = ["accept", "content-type", "last-event-id", "x-session-id"];
|
|
446
|
-
function buildSandboxRuntimeProxyHeaders(source, sandboxApiKey, forwardHeaders = DEFAULT_RUNTIME_PROXY_HEADERS) {
|
|
447
|
-
const headers = new Headers();
|
|
448
|
-
headers.set("Authorization", `Bearer ${sandboxApiKey}`);
|
|
449
|
-
for (const name of forwardHeaders) {
|
|
450
|
-
const value = source.get(name);
|
|
451
|
-
if (value) headers.set(name, value);
|
|
452
|
-
}
|
|
453
|
-
return headers;
|
|
454
|
-
}
|
|
455
|
-
function encodeSandboxRuntimePath(runtimePath) {
|
|
456
|
-
const segments = runtimePath.split("/");
|
|
457
|
-
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return null;
|
|
458
|
-
return segments.map((segment) => encodeURIComponent(segment)).join("/");
|
|
459
|
-
}
|
|
460
|
-
function bearerToken(value) {
|
|
461
|
-
if (!value) return null;
|
|
462
|
-
const trimmed = value.trim();
|
|
463
|
-
if (!trimmed) return null;
|
|
464
|
-
if (trimmed.toLowerCase() === "bearer") return null;
|
|
465
|
-
if (trimmed.toLowerCase().startsWith("bearer ")) {
|
|
466
|
-
const token = trimmed.slice("bearer ".length).trim();
|
|
467
|
-
return token || null;
|
|
468
|
-
}
|
|
469
|
-
return trimmed;
|
|
470
|
-
}
|
|
471
|
-
function bearerSubprotocolToken(value) {
|
|
472
|
-
if (!value) return null;
|
|
473
|
-
for (const part of value.split(",")) {
|
|
474
|
-
const protocol = part.trim();
|
|
475
|
-
if (!protocol.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX)) continue;
|
|
476
|
-
const encoded = protocol.slice(BEARER_SUBPROTOCOL_PREFIX.length);
|
|
477
|
-
if (!encoded) return null;
|
|
478
|
-
try {
|
|
479
|
-
const token = base64UrlDecodeText(encoded).trim();
|
|
480
|
-
return token || null;
|
|
481
|
-
} catch {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return null;
|
|
486
|
-
}
|
|
487
|
-
function terminalTokenFromRequest(headers) {
|
|
488
|
-
return bearerToken(headers.get("Authorization")) ?? bearerSubprotocolToken(headers.get("Sec-WebSocket-Protocol"));
|
|
489
|
-
}
|
|
490
|
-
function safeDecodeURIComponent(value) {
|
|
491
|
-
try {
|
|
492
|
-
return decodeURIComponent(value);
|
|
493
|
-
} catch {
|
|
494
|
-
return null;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
function stripBearerSubprotocol(headers) {
|
|
498
|
-
const value = headers.get("Sec-WebSocket-Protocol");
|
|
499
|
-
if (!value) return;
|
|
500
|
-
const protocols = value.split(",").map((part) => part.trim()).filter((part) => part && !part.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX));
|
|
501
|
-
if (protocols.length) {
|
|
502
|
-
headers.set("Sec-WebSocket-Protocol", protocols.join(", "));
|
|
503
|
-
} else {
|
|
504
|
-
headers.delete("Sec-WebSocket-Protocol");
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
function validateTerminalSubject(subject) {
|
|
508
|
-
if (!subject.userId) throw new Error("userId is required");
|
|
509
|
-
if (!subject.workspaceId) throw new Error("workspaceId is required");
|
|
510
|
-
if (!subject.sandboxId) throw new Error("sandboxId is required");
|
|
511
|
-
}
|
|
512
191
|
|
|
513
192
|
// src/sandbox/terminal-connection.ts
|
|
514
193
|
var DEFAULT_TTL_MINUTES = 15;
|
|
@@ -840,7 +519,7 @@ function buildAppToolMcpServers(options) {
|
|
|
840
519
|
entries[key] = buildAppToolMcpServer({
|
|
841
520
|
tool,
|
|
842
521
|
baseUrl: options.baseUrl,
|
|
843
|
-
|
|
522
|
+
tokenEnvKey: options.tokenEnvKey,
|
|
844
523
|
ctx: options.ctx,
|
|
845
524
|
description,
|
|
846
525
|
headerNames: options.headerNames
|
|
@@ -2046,24 +1725,7 @@ export {
|
|
|
2046
1725
|
shellQuote,
|
|
2047
1726
|
statSandboxFileSize,
|
|
2048
1727
|
readSandboxBinaryBytes,
|
|
2049
|
-
mintTerminalProxyToken,
|
|
2050
|
-
verifyTerminalProxyToken,
|
|
2051
1728
|
createWorkspaceSandboxManager,
|
|
2052
|
-
createSandboxTerminalToken,
|
|
2053
|
-
verifySandboxTerminalToken,
|
|
2054
|
-
createWorkspaceSandboxConnectionHandler,
|
|
2055
|
-
sandboxSidecarProxyUrl,
|
|
2056
|
-
createWorkspaceSandboxRuntimeProxyHandler,
|
|
2057
|
-
matchSandboxTerminalWsPath,
|
|
2058
|
-
isSandboxTerminalWsUpgrade,
|
|
2059
|
-
createWorkspaceSandboxTerminalUpgradeHandler,
|
|
2060
|
-
terminalUpgradeSubprotocolEcho,
|
|
2061
|
-
selectedBearerSubprotocol,
|
|
2062
|
-
buildSandboxRuntimeProxyHeaders,
|
|
2063
|
-
encodeSandboxRuntimePath,
|
|
2064
|
-
bearerToken,
|
|
2065
|
-
bearerSubprotocolToken,
|
|
2066
|
-
terminalTokenFromRequest,
|
|
2067
1729
|
createSandboxTerminalConnectionRoute,
|
|
2068
1730
|
createSandboxPrewarmer,
|
|
2069
1731
|
DEFAULT_PREWARM_CLAIM_TABLE,
|
|
@@ -2114,4 +1776,4 @@ export {
|
|
|
2114
1776
|
isTerminalPromptEvent,
|
|
2115
1777
|
detectInteractiveQuestion
|
|
2116
1778
|
};
|
|
2117
|
-
//# sourceMappingURL=chunk-
|
|
1779
|
+
//# sourceMappingURL=chunk-AOQ4FCSH.js.map
|