@tangle-network/agent-app 0.27.2 → 0.29.0
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/DesignCanvas-AOIVN3SJ.js +12 -0
- package/dist/DesignCanvasEditor-IB2FMBBD.js +13 -0
- package/dist/InviteAcceptPage-EUOGZO2X.js +7 -0
- package/dist/MembersPanel-4CGUQRPF.js +8 -0
- package/dist/MembersPanel-4CGUQRPF.js.map +1 -0
- package/dist/{TimelineEditor-JDG54OBP.js → TimelineEditor-Z7C6BFP5.js} +2 -2
- package/dist/TimelineEditor-Z7C6BFP5.js.map +1 -0
- package/dist/access-ChAHG4Tp.d.ts +539 -0
- package/dist/{apply-Cp8c3K9D.d.ts → apply-Gk4raT2j.d.ts} +3 -1
- package/dist/{chunk-AFDROJ64.js → chunk-2QI7XV2T.js} +1 -1
- package/dist/chunk-2QI7XV2T.js.map +1 -0
- package/dist/chunk-3G334FVA.js +162 -0
- package/dist/chunk-3G334FVA.js.map +1 -0
- package/dist/chunk-3SVAA3MA.js +41 -0
- package/dist/chunk-3SVAA3MA.js.map +1 -0
- package/dist/{chunk-LUE4HO5C.js → chunk-4IQMTTNE.js} +10 -10
- package/dist/chunk-4IQMTTNE.js.map +1 -0
- package/dist/chunk-535V6BH6.js +128 -0
- package/dist/chunk-535V6BH6.js.map +1 -0
- package/dist/chunk-63CE7FEZ.js +63 -0
- package/dist/chunk-63CE7FEZ.js.map +1 -0
- package/dist/{chunk-5RT6KY4G.js → chunk-GQRL6VNU.js} +1 -1
- package/dist/chunk-GQRL6VNU.js.map +1 -0
- package/dist/{chunk-SCG5JJ4C.js → chunk-HFC4BTWJ.js} +32 -4
- package/dist/chunk-HFC4BTWJ.js.map +1 -0
- package/dist/{chunk-7QCIYDGC.js → chunk-IOREXWLM.js} +2 -2
- package/dist/{chunk-6UOE5CTA.js → chunk-JT6HHO5P.js} +5 -10
- package/dist/chunk-JT6HHO5P.js.map +1 -0
- package/dist/{chunk-JZAJE3JL.js → chunk-M2EBUDZ7.js} +19 -5
- package/dist/chunk-M2EBUDZ7.js.map +1 -0
- package/dist/{chunk-HABVCJJT.js → chunk-N4ZFKQ5C.js} +271 -404
- package/dist/chunk-N4ZFKQ5C.js.map +1 -0
- package/dist/chunk-S5CAJX6O.js +14 -0
- package/dist/chunk-S5CAJX6O.js.map +1 -0
- package/dist/{chunk-RJ2ZHK5D.js → chunk-S6WS2B32.js} +132 -7
- package/dist/chunk-S6WS2B32.js.map +1 -0
- package/dist/chunk-SQV7NUEM.js +136 -0
- package/dist/chunk-SQV7NUEM.js.map +1 -0
- package/dist/chunk-UAKFCMWK.js +1 -0
- package/dist/chunk-UAKFCMWK.js.map +1 -0
- package/dist/chunk-V5XZKHCL.js +296 -0
- package/dist/chunk-V5XZKHCL.js.map +1 -0
- package/dist/chunk-XWJXZ6WE.js +604 -0
- package/dist/chunk-XWJXZ6WE.js.map +1 -0
- package/dist/chunk-Y2SZVW35.js +125 -0
- package/dist/chunk-Y2SZVW35.js.map +1 -0
- package/dist/{chunk-27LQXQSS.js → chunk-YEWTVDDT.js} +224 -767
- package/dist/chunk-YEWTVDDT.js.map +1 -0
- package/dist/{chunk-UDXMR3AD.js → chunk-YNALYIQ3.js} +56 -18
- package/dist/chunk-YNALYIQ3.js.map +1 -0
- package/dist/command-stack-CkjelfK_.d.ts +267 -0
- package/dist/design-canvas/drizzle.d.ts +2 -2
- package/dist/design-canvas/index.d.ts +7 -6
- package/dist/design-canvas/index.js +3 -2
- package/dist/design-canvas-react/engine.d.ts +349 -0
- package/dist/design-canvas-react/engine.js +86 -0
- package/dist/design-canvas-react/engine.js.map +1 -0
- package/dist/design-canvas-react/index.d.ts +69 -649
- package/dist/design-canvas-react/index.js +84 -173
- package/dist/design-canvas-react/index.js.map +1 -1
- package/dist/design-canvas-react/lazy.d.ts +6 -0
- package/dist/design-canvas-react/lazy.js +9 -0
- package/dist/design-canvas-react/lazy.js.map +1 -0
- package/dist/{export-presets-Dl5Aa5xj.d.ts → export-presets-DAsU5Wal.d.ts} +2 -143
- package/dist/flow-types-Cb_AblZs.d.ts +25 -0
- package/dist/index.d.ts +11 -8
- package/dist/index.js +21 -8
- package/dist/lazy-BHHqS8-j.d.ts +72 -0
- package/dist/missions/index.d.ts +12 -0
- package/dist/missions/index.js +1 -1
- package/dist/{model-BHLN208Z.d.ts → model-BBwyOuWG.d.ts} +3 -1
- package/dist/operations-DLIvvD00.d.ts +144 -0
- package/dist/platform/index.js +1 -1
- package/dist/roles-CLtYKHHl.d.ts +49 -0
- package/dist/runtime/index.d.ts +33 -33
- package/dist/runtime/index.js +1 -1
- package/dist/sequences/index.d.ts +1 -1
- package/dist/sequences/index.js +2 -1
- package/dist/sequences-react/index.d.ts +15 -1
- package/dist/sequences-react/index.js +2 -2
- package/dist/{store-CUStmtdH.d.ts → store-CFyxgTlD.d.ts} +1 -1
- package/dist/teams/drizzle.d.ts +42 -0
- package/dist/teams/drizzle.js +194 -0
- package/dist/teams/drizzle.js.map +1 -0
- package/dist/teams/index.d.ts +44 -0
- package/dist/teams/index.js +39 -0
- package/dist/teams/index.js.map +1 -0
- package/dist/teams/members-api.d.ts +152 -0
- package/dist/teams/members-api.js +261 -0
- package/dist/teams/members-api.js.map +1 -0
- package/dist/teams-react/index.d.ts +81 -0
- package/dist/teams-react/index.js +12 -0
- package/dist/teams-react/index.js.map +1 -0
- package/dist/teams-react/lazy.d.ts +9 -0
- package/dist/teams-react/lazy.js +13 -0
- package/dist/teams-react/lazy.js.map +1 -0
- package/dist/theme/index.d.ts +80 -0
- package/dist/theme/index.js +14 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/tailwind-preset.d.ts +65 -0
- package/dist/theme/tailwind-preset.js +33 -0
- package/dist/theme/tailwind-preset.js.map +1 -0
- package/dist/theme/tokens.css +95 -0
- package/dist/trace/index.d.ts +2 -18
- package/dist/trace/index.js +2 -2
- package/dist/web/index.d.ts +25 -2
- package/dist/web/index.js +3 -1
- package/dist/web-react/index.d.ts +71 -31
- package/dist/web-react/index.js +625 -486
- package/dist/web-react/index.js.map +1 -1
- package/package.json +53 -3
- package/dist/DesignCanvas-73IUJJUP.js +0 -10
- package/dist/DesignCanvasEditor-6F2UTOSW.js +0 -9
- package/dist/chunk-27LQXQSS.js.map +0 -1
- package/dist/chunk-5RT6KY4G.js.map +0 -1
- package/dist/chunk-6UOE5CTA.js.map +0 -1
- package/dist/chunk-AFDROJ64.js.map +0 -1
- package/dist/chunk-HABVCJJT.js.map +0 -1
- package/dist/chunk-JZAJE3JL.js.map +0 -1
- package/dist/chunk-LUE4HO5C.js.map +0 -1
- package/dist/chunk-RJ2ZHK5D.js.map +0 -1
- package/dist/chunk-SCG5JJ4C.js.map +0 -1
- package/dist/chunk-UDXMR3AD.js.map +0 -1
- /package/dist/{DesignCanvas-73IUJJUP.js.map → DesignCanvas-AOIVN3SJ.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-6F2UTOSW.js.map → DesignCanvasEditor-IB2FMBBD.js.map} +0 -0
- /package/dist/{TimelineEditor-JDG54OBP.js.map → InviteAcceptPage-EUOGZO2X.js.map} +0 -0
- /package/dist/{chunk-7QCIYDGC.js.map → chunk-IOREXWLM.js.map} +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/teams/roles.ts
|
|
2
|
+
var WORKSPACE_ROLES = ["viewer", "editor", "admin", "owner"];
|
|
3
|
+
var ASSIGNABLE_WORKSPACE_ROLES = ["viewer", "editor", "admin"];
|
|
4
|
+
var ORGANIZATION_ROLES = ["owner", "admin", "member", "billing"];
|
|
5
|
+
var WORKSPACE_ROLE_RANK = {
|
|
6
|
+
viewer: 0,
|
|
7
|
+
editor: 1,
|
|
8
|
+
admin: 2,
|
|
9
|
+
owner: 3
|
|
10
|
+
};
|
|
11
|
+
var ORGANIZATION_ROLE_RANK = {
|
|
12
|
+
member: 0,
|
|
13
|
+
billing: 1,
|
|
14
|
+
admin: 2,
|
|
15
|
+
owner: 3
|
|
16
|
+
};
|
|
17
|
+
function hasWorkspaceRole(actual, minimum) {
|
|
18
|
+
return WORKSPACE_ROLE_RANK[actual] >= WORKSPACE_ROLE_RANK[minimum];
|
|
19
|
+
}
|
|
20
|
+
function hasOrganizationRole(actual, minimum) {
|
|
21
|
+
return ORGANIZATION_ROLE_RANK[actual] >= ORGANIZATION_ROLE_RANK[minimum];
|
|
22
|
+
}
|
|
23
|
+
function isAssignableWorkspaceRole(value) {
|
|
24
|
+
return typeof value === "string" && ASSIGNABLE_WORKSPACE_ROLES.includes(value);
|
|
25
|
+
}
|
|
26
|
+
function organizationRoleGrantsWorkspaceOwner(role) {
|
|
27
|
+
return role === "owner" || role === "admin";
|
|
28
|
+
}
|
|
29
|
+
function resolveWorkspaceRole(organizationRole, workspaceRole) {
|
|
30
|
+
return organizationRoleGrantsWorkspaceOwner(organizationRole) ? "owner" : workspaceRole ?? null;
|
|
31
|
+
}
|
|
32
|
+
function canManageWorkspaceMemberRole(actorRole, targetRole) {
|
|
33
|
+
return actorRole === "owner" || !hasWorkspaceRole(targetRole, actorRole);
|
|
34
|
+
}
|
|
35
|
+
function workspaceRoleToCollaborationAccess(role) {
|
|
36
|
+
return role === "viewer" ? "read" : "write";
|
|
37
|
+
}
|
|
38
|
+
function workspaceRoleToSandboxRole(role) {
|
|
39
|
+
const mapping = {
|
|
40
|
+
owner: "owner",
|
|
41
|
+
admin: "admin",
|
|
42
|
+
editor: "developer",
|
|
43
|
+
viewer: "viewer"
|
|
44
|
+
};
|
|
45
|
+
return mapping[role];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
WORKSPACE_ROLES,
|
|
50
|
+
ASSIGNABLE_WORKSPACE_ROLES,
|
|
51
|
+
ORGANIZATION_ROLES,
|
|
52
|
+
WORKSPACE_ROLE_RANK,
|
|
53
|
+
ORGANIZATION_ROLE_RANK,
|
|
54
|
+
hasWorkspaceRole,
|
|
55
|
+
hasOrganizationRole,
|
|
56
|
+
isAssignableWorkspaceRole,
|
|
57
|
+
organizationRoleGrantsWorkspaceOwner,
|
|
58
|
+
resolveWorkspaceRole,
|
|
59
|
+
canManageWorkspaceMemberRole,
|
|
60
|
+
workspaceRoleToCollaborationAccess,
|
|
61
|
+
workspaceRoleToSandboxRole
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=chunk-63CE7FEZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams/roles.ts"],"sourcesContent":["/**\n * Pure role algebra for the teams capability — the tenancy/membership model\n * shared across the fleet. Zero dependencies: no drizzle, no env, no react, no\n * I/O. The DB layer (`./teams/drizzle`), the members API (`./teams/members-api`)\n * and the React surface (`./teams-react`) all build on these functions; this\n * leaf imports nothing back, so a consumer can pull just the role math.\n *\n * Two role ladders, deliberately distinct:\n * - Organization roles rank the tenant (who owns/administers the org and its\n * billing). An org owner/admin is an owner of every workspace under it.\n * - Workspace roles rank a single workspace. They are the access primitive\n * every route checks via `hasWorkspaceRole(actual, minimum)`.\n *\n * `resolveWorkspaceRole` is the bridge: it folds the org role and the\n * per-workspace role into the one effective workspace role a request runs at.\n */\n\nexport const WORKSPACE_ROLES = ['viewer', 'editor', 'admin', 'owner'] as const\nexport type WorkspaceRole = typeof WORKSPACE_ROLES[number]\n\nexport const ASSIGNABLE_WORKSPACE_ROLES = ['viewer', 'editor', 'admin'] as const\nexport type AssignableWorkspaceRole = typeof ASSIGNABLE_WORKSPACE_ROLES[number]\n\nexport const ORGANIZATION_ROLES = ['owner', 'admin', 'member', 'billing'] as const\nexport type OrganizationRole = typeof ORGANIZATION_ROLES[number]\n\nexport type WorkspaceCollaborationAccess = 'read' | 'write'\nexport type SandboxWorkspaceRole = 'owner' | 'admin' | 'developer' | 'viewer'\n\nexport const WORKSPACE_ROLE_RANK: Record<WorkspaceRole, number> = {\n viewer: 0,\n editor: 1,\n admin: 2,\n owner: 3,\n}\n\nexport const ORGANIZATION_ROLE_RANK: Record<OrganizationRole, number> = {\n member: 0,\n billing: 1,\n admin: 2,\n owner: 3,\n}\n\n/** True when `actual` is at least `minimum` on the workspace ladder. */\nexport function hasWorkspaceRole(actual: WorkspaceRole, minimum: WorkspaceRole): boolean {\n return WORKSPACE_ROLE_RANK[actual] >= WORKSPACE_ROLE_RANK[minimum]\n}\n\n/** True when `actual` is at least `minimum` on the organization ladder. */\nexport function hasOrganizationRole(actual: OrganizationRole, minimum: OrganizationRole): boolean {\n return ORGANIZATION_ROLE_RANK[actual] >= ORGANIZATION_ROLE_RANK[minimum]\n}\n\nexport function isAssignableWorkspaceRole(value: unknown): value is AssignableWorkspaceRole {\n return typeof value === 'string' && ASSIGNABLE_WORKSPACE_ROLES.includes(value as AssignableWorkspaceRole)\n}\n\n/** Org owners and admins are workspace owners across the whole org. */\nexport function organizationRoleGrantsWorkspaceOwner(role: OrganizationRole | string | null | undefined): boolean {\n return role === 'owner' || role === 'admin'\n}\n\n/**\n * The effective workspace role a request runs at: org owner/admin → owner of\n * every workspace; otherwise the explicit per-workspace role (or null = no\n * access). This is the single fold every access check goes through.\n */\nexport function resolveWorkspaceRole(\n organizationRole: OrganizationRole | string | null | undefined,\n workspaceRole: WorkspaceRole | null | undefined,\n): WorkspaceRole | null {\n return organizationRoleGrantsWorkspaceOwner(organizationRole) ? 'owner' : workspaceRole ?? null\n}\n\n/**\n * Whether `actorRole` may set/clear a member currently at `targetRole`. Owners\n * can manage anyone; everyone else can only manage members strictly below\n * their own rank (an admin cannot demote another admin or an owner).\n */\nexport function canManageWorkspaceMemberRole(actorRole: WorkspaceRole, targetRole: WorkspaceRole): boolean {\n return actorRole === 'owner' || !hasWorkspaceRole(targetRole, actorRole)\n}\n\nexport function workspaceRoleToCollaborationAccess(role: WorkspaceRole): WorkspaceCollaborationAccess {\n return role === 'viewer' ? 'read' : 'write'\n}\n\nexport function workspaceRoleToSandboxRole(role: WorkspaceRole): SandboxWorkspaceRole {\n const mapping: Record<WorkspaceRole, SandboxWorkspaceRole> = {\n owner: 'owner',\n admin: 'admin',\n editor: 'developer',\n viewer: 'viewer',\n }\n return mapping[role]\n}\n"],"mappings":";AAiBO,IAAM,kBAAkB,CAAC,UAAU,UAAU,SAAS,OAAO;AAG7D,IAAM,6BAA6B,CAAC,UAAU,UAAU,OAAO;AAG/D,IAAM,qBAAqB,CAAC,SAAS,SAAS,UAAU,SAAS;AAMjE,IAAM,sBAAqD;AAAA,EAChE,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AACT;AAEO,IAAM,yBAA2D;AAAA,EACtE,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AACT;AAGO,SAAS,iBAAiB,QAAuB,SAAiC;AACvF,SAAO,oBAAoB,MAAM,KAAK,oBAAoB,OAAO;AACnE;AAGO,SAAS,oBAAoB,QAA0B,SAAoC;AAChG,SAAO,uBAAuB,MAAM,KAAK,uBAAuB,OAAO;AACzE;AAEO,SAAS,0BAA0B,OAAkD;AAC1F,SAAO,OAAO,UAAU,YAAY,2BAA2B,SAAS,KAAgC;AAC1G;AAGO,SAAS,qCAAqC,MAA6D;AAChH,SAAO,SAAS,WAAW,SAAS;AACtC;AAOO,SAAS,qBACd,kBACA,eACsB;AACtB,SAAO,qCAAqC,gBAAgB,IAAI,UAAU,iBAAiB;AAC7F;AAOO,SAAS,6BAA6B,WAA0B,YAAoC;AACzG,SAAO,cAAc,WAAW,CAAC,iBAAiB,YAAY,SAAS;AACzE;AAEO,SAAS,mCAAmC,MAAmD;AACpG,SAAO,SAAS,WAAW,SAAS;AACtC;AAEO,SAAS,2BAA2B,MAA2C;AACpF,QAAM,UAAuD;AAAA,IAC3D,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,SAAO,QAAQ,IAAI;AACrB;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trace/mission-trace.ts","../src/trace/index.ts"],"sourcesContent":["/**\n * Mission trace context — mint + thread the trace ids that join a mission's\n * step attempts and its delegated agent runs into ONE trace tree.\n *\n * ID formats are byte-compatible with agent-runtime's trace propagation\n * (`readTraceContextFromEnv` / OTLP export): 32 lowercase hex chars for a\n * trace id (16 bytes), 16 for a span id (8 bytes). The env pair from\n * {@link traceEnv} is exactly what agent-runtime's MCP subprocess reads at\n * startup (`TRACE_ID` + `PARENT_SPAN_ID`), so a delegation dispatched with it\n * parents its loop→round→iteration spans under the mission's step span.\n *\n * Pure functions, no deps. Ids are DETERMINISTIC when a key is supplied\n * (missionId / step-attempt seed) so a crashed driver re-mints the identical\n * context on re-dispatch and the re-run joins the same trace instead of\n * forking a new one; without a key they are random.\n */\n\nexport interface MissionTraceContext {\n /** 32-hex trace id shared by every span in the mission's tree. */\n traceId: string\n /** 16-hex span id of the mission root — the parent of every step span. */\n rootSpanId: string\n}\n\nexport interface StepSpanContext {\n traceId: string\n /** 16-hex span id of this step attempt (or any nested unit of work). */\n spanId: string\n /** The span this one nests under. */\n parentSpanId: string\n}\n\n/**\n * Mint a mission's trace context. With `missionId` the ids are a pure\n * function of it; omitted, both ids are random.\n */\nexport function createMissionTraceContext(missionId?: string): MissionTraceContext {\n if (missionId !== undefined && missionId !== '') {\n return {\n traceId: hex64(fnv1a64(`mission-trace:${missionId}`)) + hex64(fnv1a64(`mission-trace:2:${missionId}`)),\n rootSpanId: hex64(fnv1a64(`mission-root-span:${missionId}`)),\n }\n }\n return { traceId: randomHex(16), rootSpanId: randomHex(8) }\n}\n\n/**\n * Derive a child span context under `parent` — one per step attempt (seed\n * e.g. `\"${stepId}#${attempt}\"`), or nested under another step span. With a\n * seed the span id is deterministic for the same parent + seed; omitted, it\n * is random.\n */\nexport function childSpanContext(\n parent: MissionTraceContext | StepSpanContext,\n seed?: string,\n): StepSpanContext {\n const parentSpanId = 'rootSpanId' in parent ? parent.rootSpanId : parent.spanId\n const spanId =\n seed !== undefined && seed !== ''\n ? hex64(fnv1a64(`span:${parent.traceId}:${parentSpanId}:${seed}`))\n : randomHex(8)\n return { traceId: parent.traceId, spanId, parentSpanId }\n}\n\n/**\n * The env pair a delegation subprocess inherits — agent-runtime's\n * `readTraceContextFromEnv` reads exactly these names. `PARENT_SPAN_ID` is\n * the span the dispatched work nests under: the root for a mission context,\n * the step-attempt span for a step context.\n */\nexport function traceEnv(ctx: MissionTraceContext | StepSpanContext): {\n TRACE_ID: string\n PARENT_SPAN_ID: string\n} {\n return {\n TRACE_ID: ctx.traceId,\n PARENT_SPAN_ID: 'rootSpanId' in ctx ? ctx.rootSpanId : ctx.spanId,\n }\n}\n\n// FNV-1a 64-bit over UTF-16 code units — a stable, dependency-free digest for\n// deterministic ids. Not cryptographic; trace ids only need uniqueness within\n// a tenant's missions, not unforgeability.\nconst FNV_OFFSET = 0xcbf29ce484222325n\nconst FNV_PRIME = 0x100000001b3n\nconst MASK_64 = 0xffffffffffffffffn\n\nfunction fnv1a64(input: string): bigint {\n let hash = FNV_OFFSET\n for (let i = 0; i < input.length; i++) {\n hash ^= BigInt(input.charCodeAt(i))\n hash = (hash * FNV_PRIME) & MASK_64\n }\n return hash\n}\n\nfunction hex64(value: bigint): string {\n return value.toString(16).padStart(16, '0')\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength)\n globalThis.crypto.getRandomValues(bytes)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n","/**\n * `@tangle-network/agent-app/trace` — flow observability for agent turns.\n *\n * The turn buffer stamps `_t` (ms since turn start) on every event, so any\n * live stream OR any historical turn replayed from a TurnEventStore can be\n * reconstructed into a span trace: pipeline overhead, model segments (with\n * thinking TTFT), tool executions, token usage, and cost. Renderers turn\n * traces and multi-run samples into ASCII waterfalls and histograms — the\n * default artifact for \"how did this run actually behave\" questions across\n * evals, hill-climbs, and production debugging.\n *\n * Span boundaries derived from a buffered stream are quantized by the\n * pump's flush window and the reader's poll cadence (~100–400ms); spans\n * carry `approx: true` to keep reports honest about that.\n */\n\nexport * from './mission-trace'\nexport * from './flow-types'\nexport * from './mission-flow'\n\nimport type { FlowSpan, FlowTrace } from './flow-types'\n\nexport interface TimedEvent {\n /** ms since turn start (`_t` stamped by pumpBufferedTurn). */\n t: number\n event: Record<string, unknown>\n}\n\n/** Parse stored turn-event lines (JSON strings with `_t`) into TimedEvents. */\nexport function timedEventsFromLines(lines: string[]): TimedEvent[] {\n const out: TimedEvent[] = []\n for (const line of lines) {\n try {\n const parsed = JSON.parse(line) as Record<string, unknown>\n if (typeof parsed._t === 'number') out.push({ t: parsed._t, event: parsed })\n } catch {\n /* skip torn lines */\n }\n }\n return out.sort((a, b) => a.t - b.t)\n}\n\nfunction innerOf(e: Record<string, unknown>): Record<string, unknown> {\n return (e.kind === 'event' ? (e.event as Record<string, unknown>) : e) ?? {}\n}\n\n/**\n * Derive a span trace from timestamped turn events. Model segments are runs\n * of text/reasoning deltas; a tool span opens at the last delta before its\n * tool_call emission and closes at the matching tool_result.\n */\nexport function buildFlowTrace(\n events: TimedEvent[],\n opts?: { pricing?: { prompt?: string | number; completion?: string | number } },\n): FlowTrace {\n const spans: FlowSpan[] = []\n let promptTokens = 0\n let completionTokens = 0\n let toolCalls = 0\n\n const first = events[0]?.t ?? 0\n if (first > 0) {\n spans.push({ kind: 'pipeline', name: 'dispatch → first event', startMs: 0, endMs: first })\n }\n\n let segStart: number | null = null\n let segEnd = 0\n let segKinds = new Set<string>()\n let lastDeltaT = first\n const openCalls = new Map<string, { name: string; emitT: number; lastDeltaT: number }>()\n\n const closeSegment = () => {\n if (segStart !== null) {\n spans.push({\n kind: 'model',\n name: segKinds.has('reasoning') ? 'model turn (reasoning + text)' : 'model turn',\n startMs: segStart,\n endMs: segEnd,\n approx: true,\n })\n segStart = null\n segKinds = new Set()\n }\n }\n\n for (const { t, event } of events) {\n const inner = innerOf(event)\n const type = String(event.kind === 'tool_result' ? 'tool_result' : (inner.type ?? ''))\n\n if (type === 'text' || type === 'reasoning') {\n if (segStart === null) segStart = t\n segEnd = t\n segKinds.add(type)\n lastDeltaT = t\n } else if (type === 'tool_call') {\n closeSegment()\n toolCalls++\n const call = (inner.call ?? inner) as Record<string, unknown>\n const id = String(call.toolCallId ?? `call_${toolCalls}`)\n openCalls.set(id, { name: String(call.toolName ?? 'tool'), emitT: t, lastDeltaT })\n } else if (type === 'tool_result') {\n const id = String(event.toolCallId ?? inner.toolCallId ?? '')\n const open = openCalls.get(id)\n if (open) {\n spans.push({\n kind: 'tool',\n name: open.name,\n // Execution happens between the end of the model turn that emitted\n // the call and the result landing in the buffer.\n startMs: open.lastDeltaT,\n endMs: t,\n approx: true,\n meta: { ok: ((event.outcome ?? inner.outcome) as { ok?: boolean } | undefined)?.ok },\n })\n openCalls.delete(id)\n }\n } else if (type === 'usage') {\n const u = (inner.usage ?? {}) as { promptTokens?: number; completionTokens?: number }\n promptTokens += u.promptTokens ?? 0\n completionTokens += u.completionTokens ?? 0\n }\n }\n closeSegment()\n\n const totalMs = events.length ? events[events.length - 1]!.t : 0\n const trace: FlowTrace = { spans, totalMs, promptTokens, completionTokens, toolCalls }\n const p = opts?.pricing\n if (p && (p.prompt != null || p.completion != null)) {\n trace.costUsd = promptTokens * Number(p.prompt ?? 0) + completionTokens * Number(p.completion ?? 0)\n }\n return trace\n}\n\nconst fmtS = (ms: number) => `${(ms / 1000).toFixed(1)}s`\n\n/** ASCII waterfall cascade — the default artifact for explaining a flow. */\nexport function renderWaterfall(trace: FlowTrace, opts?: { width?: number }): string {\n const width = opts?.width ?? 40\n const scale = trace.totalMs > 0 ? width / trace.totalMs : 0\n const lines: string[] = []\n const spans = [...trace.spans].sort((a, b) => a.startMs - b.startMs)\n for (let i = 0; i < spans.length; i++) {\n const s = spans[i]!\n const offset = Math.round(s.startMs * scale)\n const len = Math.max(1, Math.round((s.endMs - s.startMs) * scale))\n const bar = ' '.repeat(offset) + (s.kind === 'tool' ? '▓' : s.kind === 'pipeline' ? '░' : '█').repeat(len)\n const branch = i === spans.length - 1 ? '└─' : '├─'\n const dur = `${fmtS(s.endMs - s.startMs)}${s.approx ? '~' : ''}`\n lines.push(`${fmtS(s.startMs).padStart(7)} ${branch} ${bar.padEnd(width + 2)} ${s.name} (${dur})`)\n }\n const cost = trace.costUsd != null ? ` $${trace.costUsd.toFixed(trace.costUsd < 0.01 ? 6 : 4)}` : ''\n lines.push(\n `${fmtS(trace.totalMs).padStart(7)} ── total · ${trace.promptTokens}p + ${trace.completionTokens}c tok · ${trace.toolCalls} tool calls${cost}`,\n )\n return lines.join('\\n')\n}\n\nexport interface DistributionSummary {\n n: number\n min: number\n p50: number\n p90: number\n max: number\n}\n\nexport function summarize(values: number[]): DistributionSummary {\n const sorted = [...values].sort((a, b) => a - b)\n const q = (p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))] ?? 0\n return { n: sorted.length, min: sorted[0] ?? 0, p50: q(0.5), p90: q(0.9), max: sorted[sorted.length - 1] ?? 0 }\n}\n\n/** ASCII histogram for multi-run samples (eval latencies, costs, scores). */\nexport function renderHistogram(\n values: number[],\n opts?: { buckets?: number; width?: number; unit?: string; format?: (v: number) => string },\n): string {\n if (!values.length) return '(no samples)'\n const buckets = opts?.buckets ?? 6\n const width = opts?.width ?? 24\n const fmt = opts?.format ?? ((v: number) => `${Math.round(v)}${opts?.unit ?? ''}`)\n const s = summarize(values)\n const lo = s.min\n const hi = s.max === s.min ? s.min + 1 : s.max\n const counts = new Array<number>(buckets).fill(0)\n for (const v of values) {\n counts[Math.min(buckets - 1, Math.floor(((v - lo) / (hi - lo)) * buckets))]!++\n }\n const maxCount = Math.max(...counts)\n const lines = [\n `n=${s.n} min=${fmt(s.min)} p50=${fmt(s.p50)} p90=${fmt(s.p90)} max=${fmt(s.max)}`,\n ]\n for (let i = 0; i < buckets; i++) {\n const a = lo + ((hi - lo) * i) / buckets\n const b = lo + ((hi - lo) * (i + 1)) / buckets\n const bar = '█'.repeat(Math.max(counts[i]! > 0 ? 1 : 0, Math.round((counts[i]! / maxCount) * width)))\n lines.push(`${fmt(a).padStart(8)}-${fmt(b).padEnd(8)} ${bar} ${counts[i]}`)\n }\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -32,13 +32,28 @@ async function checkRateLimit(kv, key, limit, windowSeconds) {
|
|
|
32
32
|
const windowStart = now - windowSeconds;
|
|
33
33
|
const kvKey = `rl:${key}`;
|
|
34
34
|
const raw = await kv.get(kvKey);
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
const parsed = parseRateLimitState(raw);
|
|
36
|
+
if (parsed === POISONED_STATE) {
|
|
37
|
+
return { allowed: false, remaining: 0, resetAt: now + windowSeconds };
|
|
38
|
+
}
|
|
39
|
+
const valid = parsed.filter((t) => t > windowStart);
|
|
37
40
|
if (valid.length >= limit) return { allowed: false, remaining: 0, resetAt: (valid[0] ?? now) + windowSeconds };
|
|
38
41
|
valid.push(now);
|
|
39
42
|
await kv.put(kvKey, JSON.stringify(valid), { expirationTtl: windowSeconds * 2 });
|
|
40
43
|
return { allowed: true, remaining: limit - valid.length, resetAt: now + windowSeconds };
|
|
41
44
|
}
|
|
45
|
+
var POISONED_STATE = /* @__PURE__ */ Symbol("rate-limit-poisoned-state");
|
|
46
|
+
function parseRateLimitState(raw) {
|
|
47
|
+
if (raw === null) return [];
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(raw);
|
|
51
|
+
} catch {
|
|
52
|
+
return POISONED_STATE;
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(parsed)) return POISONED_STATE;
|
|
55
|
+
return parsed.filter((t) => typeof t === "number" && Number.isFinite(t));
|
|
56
|
+
}
|
|
42
57
|
function serializeCookie(value, opts) {
|
|
43
58
|
if (opts.sameSite === "None" && !opts.secure) {
|
|
44
59
|
throw new Error("SameSite=None cookies require secure: true (browsers reject them otherwise)");
|
|
@@ -78,6 +93,18 @@ function addSecurityHeaders(response, opts = {}) {
|
|
|
78
93
|
for (const [k, v] of Object.entries(opts.extra ?? {})) response.headers.set(k, v);
|
|
79
94
|
return response;
|
|
80
95
|
}
|
|
96
|
+
var REJECTED_MEDIA_SCHEMES = ["file:", "data:", "blob:", "javascript:", "vbscript:"];
|
|
97
|
+
function assertMediaUrl(url, what = "media url") {
|
|
98
|
+
const trimmed = url.trim();
|
|
99
|
+
if (/^https?:\/\//i.test(trimmed)) return;
|
|
100
|
+
if (trimmed.startsWith("/api/")) return;
|
|
101
|
+
const shown = trimmed.length > 96 ? `${trimmed.slice(0, 96)}\u2026` : trimmed;
|
|
102
|
+
const lower = trimmed.toLowerCase();
|
|
103
|
+
if (REJECTED_MEDIA_SCHEMES.some((scheme) => lower.startsWith(scheme)) || lower.startsWith("/tmp/") || lower.startsWith("/home/")) {
|
|
104
|
+
throw new Error(`${what} must reference a provider http(s) URL or a rooted /api/ path, not a local sandbox file (${shown})`);
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`${what} must be http(s) or a rooted /api/ path (${shown})`);
|
|
107
|
+
}
|
|
81
108
|
|
|
82
109
|
export {
|
|
83
110
|
parseJsonObjectBody,
|
|
@@ -87,6 +114,7 @@ export {
|
|
|
87
114
|
serializeCookie,
|
|
88
115
|
clearCookieHeader,
|
|
89
116
|
readCookieValue,
|
|
90
|
-
addSecurityHeaders
|
|
117
|
+
addSecurityHeaders,
|
|
118
|
+
assertMediaUrl
|
|
91
119
|
};
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
120
|
+
//# sourceMappingURL=chunk-HFC4BTWJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web/index.ts"],"sourcesContent":["/**\n * Web-boundary utilities every agent app's routes hand-roll: JSON body parsing\n * + narrowing, request-context extraction (real client IP behind Cloudflare),\n * a KV-backed sliding-window rate limiter, and security response headers. Pure\n * mechanism — no DB, no domain. The KV is a structural interface so this needs\n * no `@cloudflare/workers-types` dependency.\n */\n\nexport type JsonObject = Record<string, unknown>\n\n/** Parse + object-narrow a Request body. `[body, null]` on success, `[null,\n * errorResponse]` on a non-object body (callers `if (err) return err`). */\nexport async function parseJsonObjectBody(request: Request): Promise<[JsonObject, null] | [null, Response]> {\n let raw: unknown\n try {\n raw = await request.json()\n } catch {\n return [null, Response.json({ error: 'Invalid JSON body' }, { status: 400 })]\n }\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n return [null, Response.json({ error: 'Body must be a JSON object' }, { status: 400 })]\n }\n return [raw as JsonObject, null]\n}\n\n/** Narrow one required string field, 400 if missing/empty. */\nexport function requireString(body: JsonObject, field: string): string | Response {\n const v = body[field]\n if (typeof v !== 'string' || v.length === 0) {\n return Response.json({ error: `Missing or non-string field: ${field}` }, { status: 400 })\n }\n return v\n}\n\nexport interface RequestContext {\n ipAddress: string\n userAgent: string\n timestamp: string\n requestId: string\n}\n\n/** Extract request context for audit trails. Uses `CF-Connecting-IP` for the\n * real client IP behind Cloudflare. */\nexport function extractRequestContext(request: Request): RequestContext {\n const ipAddress =\n request.headers.get('CF-Connecting-IP') ??\n request.headers.get('X-Forwarded-For')?.split(',')[0]?.trim() ??\n '0.0.0.0'\n return {\n ipAddress,\n userAgent: request.headers.get('User-Agent') ?? '',\n timestamp: new Date().toISOString(),\n requestId: crypto.randomUUID(),\n }\n}\n\n/** Minimal KV contract (Cloudflare `KVNamespace` satisfies it structurally). */\nexport interface KvLike {\n get(key: string): Promise<string | null>\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>\n}\n\nexport interface RateLimitResult {\n allowed: boolean\n remaining: number\n resetAt: number\n}\n\n/** KV-backed sliding-window rate limit. Stores recent timestamps per key,\n * prunes the window, allows until `limit` is hit.\n *\n * Read-modify-write is best-effort, NOT atomic: KV has no compare-and-swap, so\n * two requests racing on the same key can each read the same pre-state and both\n * write — a concurrent burst can momentarily admit up to one extra request per\n * racing writer. This is acceptable for coarse abuse limiting; it is NOT a hard\n * quota gate.\n *\n * Fail-CLOSED on unreadable state: corrupt/non-array KV (a poisoned or\n * truncated value) is treated as a full window, so a tampered key cannot reset\n * the count and bypass the limiter. A bare `JSON.parse` here would throw and\n * abort the request handler, silently disabling the limit (fail-open). */\nexport async function checkRateLimit(kv: KvLike, key: string, limit: number, windowSeconds: number): Promise<RateLimitResult> {\n const now = Math.floor(Date.now() / 1000)\n const windowStart = now - windowSeconds\n const kvKey = `rl:${key}`\n const raw = await kv.get(kvKey)\n const parsed = parseRateLimitState(raw)\n if (parsed === POISONED_STATE) {\n // Unreadable state (parse threw or value is not a JSON array): deny rather\n // than reset the window to empty. A poisoned key must not become a bypass.\n return { allowed: false, remaining: 0, resetAt: now + windowSeconds }\n }\n const valid = parsed.filter((t) => t > windowStart)\n if (valid.length >= limit) return { allowed: false, remaining: 0, resetAt: (valid[0] ?? now) + windowSeconds }\n valid.push(now)\n await kv.put(kvKey, JSON.stringify(valid), { expirationTtl: windowSeconds * 2 })\n return { allowed: true, remaining: limit - valid.length, resetAt: now + windowSeconds }\n}\n\n/** Sentinel returned by `parseRateLimitState` when the stored value cannot be\n * read as a timestamp array — distinct from an empty window so the limiter can\n * fail closed instead of treating corruption as a fresh window. */\nconst POISONED_STATE = Symbol('rate-limit-poisoned-state')\n\n/** Parse stored rate-limit state into a timestamp array. Absent state is a\n * fresh (empty) window. A value that fails to parse, or parses to a non-array,\n * returns `POISONED_STATE`; numeric junk inside a valid array is dropped. */\nfunction parseRateLimitState(raw: string | null): number[] | typeof POISONED_STATE {\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return POISONED_STATE\n }\n if (!Array.isArray(parsed)) return POISONED_STATE\n return parsed.filter((t): t is number => typeof t === 'number' && Number.isFinite(t))\n}\n\nexport interface CookieOptions {\n name: string\n /** Default '/'. */\n path?: string\n /** Default true. */\n httpOnly?: boolean\n /** Adds the `Secure` attribute. Default false. */\n secure?: boolean\n /** Default 'Lax'. */\n sameSite?: 'Lax' | 'Strict' | 'None'\n maxAgeSeconds?: number\n}\n\n/** Serialize a Set-Cookie header value: `name=encodeURIComponent(value)` plus\n * attributes in Path / HttpOnly / SameSite / Max-Age / Secure order.\n * Throws on `SameSite=None` without `secure` — browsers silently drop that\n * combination, which would otherwise fail invisibly. */\nexport function serializeCookie(value: string, opts: CookieOptions): string {\n if (opts.sameSite === 'None' && !opts.secure) {\n throw new Error('SameSite=None cookies require secure: true (browsers reject them otherwise)')\n }\n const parts = [`${opts.name}=${encodeURIComponent(value)}`, `Path=${opts.path ?? '/'}`]\n if (opts.httpOnly !== false) parts.push('HttpOnly')\n parts.push(`SameSite=${opts.sameSite ?? 'Lax'}`)\n if (opts.maxAgeSeconds !== undefined) parts.push(`Max-Age=${opts.maxAgeSeconds}`)\n if (opts.secure) parts.push('Secure')\n return parts.join('; ')\n}\n\n/** Set-Cookie header value that deletes the cookie (empty value, Max-Age=0). */\nexport function clearCookieHeader(opts: Omit<CookieOptions, 'maxAgeSeconds'>): string {\n return serializeCookie('', { ...opts, maxAgeSeconds: 0 })\n}\n\n/** Read + decode one cookie from a Cookie request header; null when absent. */\nexport function readCookieValue(cookieHeader: string | null, name: string): string | null {\n if (!cookieHeader) return null\n for (const part of cookieHeader.split(/;\\s*/)) {\n const [cookieName, ...rest] = part.split('=')\n if (cookieName === name) {\n try {\n return decodeURIComponent(rest.join('='))\n } catch {\n return null\n }\n }\n }\n return null\n}\n\nexport interface SecurityHeaderOptions {\n /** Product disclaimer (e.g. \"AI-powered tool. Not legal advice.\"). Omitted if absent. */\n disclaimer?: string\n /** Data-retention label (e.g. \"7-years\"). Omitted if absent. */\n retention?: string\n /** Extra headers to set. */\n extra?: Record<string, string>\n}\n\n/** Set standard security headers on a response (HSTS, nosniff, frame-options,\n * referrer-policy, XSS) + optional product disclaimer/retention. The security\n * set is generic; the disclaimer/retention are the product's. */\nexport function addSecurityHeaders(response: Response, opts: SecurityHeaderOptions = {}): Response {\n response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload')\n response.headers.set('X-Content-Type-Options', 'nosniff')\n response.headers.set('X-Frame-Options', 'SAMEORIGIN')\n response.headers.set('Referrer-Policy', 'same-origin')\n response.headers.set('X-XSS-Protection', '1; mode=block')\n if (opts.disclaimer) response.headers.set('X-AI-Disclaimer', opts.disclaimer)\n if (opts.retention) response.headers.set('X-Data-Retention', opts.retention)\n for (const [k, v] of Object.entries(opts.extra ?? {})) response.headers.set(k, v)\n return response\n}\n\n/** Local-sandbox / inline schemes a stored media reference must never use.\n * Reachable from neither a browser nor the product worker, and a `file:`/`data:`\n * url is the tell of an agent substituting local ffmpeg output for a real\n * provider artifact. `blob:` and `javascript:` are inert/active client schemes\n * with no server reachability. */\nconst REJECTED_MEDIA_SCHEMES = ['file:', 'data:', 'blob:', 'javascript:', 'vbscript:'] as const\n\n/**\n * Canonical media-reference boundary shared by every surface that persists a\n * media url (sequences clips, design-canvas image/video src). The ONE rule:\n * remote `http(s)` or a rooted `/api/` path are allowed; everything else is\n * rejected, with a named reason for known-bad local/inline schemes so the\n * thrown message is actionable for an LLM planner. The url is trimmed before\n * the scheme check so leading whitespace cannot smuggle a rejected scheme past\n * a naive `startsWith`.\n *\n * @param what - noun for the error message (e.g. 'media url', 'src').\n */\nexport function assertMediaUrl(url: string, what = 'media url'): void {\n const trimmed = url.trim()\n if (/^https?:\\/\\//i.test(trimmed)) return\n if (trimmed.startsWith('/api/')) return\n const shown = trimmed.length > 96 ? `${trimmed.slice(0, 96)}…` : trimmed\n const lower = trimmed.toLowerCase()\n if (\n REJECTED_MEDIA_SCHEMES.some((scheme) => lower.startsWith(scheme)) ||\n lower.startsWith('/tmp/') ||\n lower.startsWith('/home/')\n ) {\n throw new Error(`${what} must reference a provider http(s) URL or a rooted /api/ path, not a local sandbox file (${shown})`)\n }\n throw new Error(`${what} must be http(s) or a rooted /api/ path (${shown})`)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
elementAabb,
|
|
6
6
|
requirePage,
|
|
7
7
|
storeApplyScenePlan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-M2EBUDZ7.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_HEADER_NAMES,
|
|
11
11
|
buildHttpMcpServer,
|
|
@@ -1116,4 +1116,4 @@ export {
|
|
|
1116
1116
|
DEFAULT_DESIGN_CANVAS_MCP_DESCRIPTION,
|
|
1117
1117
|
buildDesignCanvasMcpServerEntry
|
|
1118
1118
|
};
|
|
1119
|
-
//# sourceMappingURL=chunk-
|
|
1119
|
+
//# sourceMappingURL=chunk-IOREXWLM.js.map
|
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
snapshotFrame,
|
|
10
10
|
trackIntervals
|
|
11
11
|
} from "./chunk-ZYBWGSAZ.js";
|
|
12
|
+
import {
|
|
13
|
+
assertMediaUrl
|
|
14
|
+
} from "./chunk-HFC4BTWJ.js";
|
|
12
15
|
import {
|
|
13
16
|
DEFAULT_HEADER_NAMES,
|
|
14
17
|
buildHttpMcpServer,
|
|
@@ -403,15 +406,7 @@ function lastClipEndFrame(timeline) {
|
|
|
403
406
|
return timeline.clips.reduce((max, clip) => Math.max(max, clip.startFrame + clip.durationFrames), 0);
|
|
404
407
|
}
|
|
405
408
|
function assertSequenceMediaUrl(url) {
|
|
406
|
-
|
|
407
|
-
if (/^https?:\/\//i.test(trimmed)) return;
|
|
408
|
-
if (trimmed.startsWith("/api/")) return;
|
|
409
|
-
const shown = trimmed.length > 96 ? `${trimmed.slice(0, 96)}\u2026` : trimmed;
|
|
410
|
-
const lower = trimmed.toLowerCase();
|
|
411
|
-
if (lower.startsWith("file:") || lower.startsWith("data:") || lower.startsWith("/tmp/") || lower.startsWith("/home/")) {
|
|
412
|
-
throw new Error(`media url must reference a provider URL or rooted /api/ path, not a local sandbox file (${shown})`);
|
|
413
|
-
}
|
|
414
|
-
throw new Error(`media url must be http(s) or a rooted /api/ path (${shown})`);
|
|
409
|
+
assertMediaUrl(url, "media url");
|
|
415
410
|
}
|
|
416
411
|
function requireClip(timeline, clipId) {
|
|
417
412
|
const clip = timeline.clips.find((candidate) => candidate.id === clipId);
|
|
@@ -1644,4 +1639,4 @@ export {
|
|
|
1644
1639
|
DEFAULT_SEQUENCES_MCP_DESCRIPTION,
|
|
1645
1640
|
buildSequencesMcpServerEntry
|
|
1646
1641
|
};
|
|
1647
|
-
//# sourceMappingURL=chunk-
|
|
1642
|
+
//# sourceMappingURL=chunk-JT6HHO5P.js.map
|