@tangle-network/agent-app 0.60.195 → 0.60.197

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.
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The artifact origin: the only place cloud artifact HTML and scripts run.
3
+ *
4
+ * Mount it on a host of its own on a different site from the product (for
5
+ * GTM, `gtm-artifacts.tangle.sh` against `gtm.tangle.tools`), so a page an
6
+ * agent wrote never shares cookies, storage or same-site requests with a
7
+ * signed-in product session. Every response also carries a CSP `sandbox`
8
+ * without `allow-same-origin`, so each document runs in an opaque origin and
9
+ * one artifact cannot read another's storage.
10
+ *
11
+ * A request names `/<artifactId>/<grant>/<token>/<path>`. The token is a
12
+ * short-lived capability minted by the product after it authorized the viewer;
13
+ * `resolve` returns the record only while that grant is still live, so a
14
+ * revoked share stops serving on its next request.
15
+ *
16
+ * Files are streamed from the object store. For a `worker` artifact, a request
17
+ * that matches no file runs `_worker.js` in a Worker Loader isolate with
18
+ * `globalOutbound: null` (no network), CPU and subrequest limits, and only the
19
+ * bindings `workerEnv` grants. Its response headers are replaced with this
20
+ * origin's, so the isolate cannot loosen them.
21
+ */
22
+ import type { ObjectStore } from '../object-store';
23
+ import { type CloudArtifactGrant, type CloudArtifactRecord } from './cloud-artifacts';
24
+ /** Script and style CDNs a generated page may load. Everything else is local or inline. */
25
+ export declare const CLOUD_ARTIFACT_SCRIPT_HOSTS: readonly ['https://cdn.jsdelivr.net', 'https://cdnjs.cloudflare.com', 'https://unpkg.com'];
26
+ /** The iframe sandbox for an artifact frame; never add `allow-same-origin`. */
27
+ export declare const CLOUD_ARTIFACT_FRAME_SANDBOX = "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads";
28
+ /** The Content-Security-Policy every artifact response carries. */
29
+ export declare function cloudArtifactContentSecurityPolicy(input: {
30
+ origin: string;
31
+ frameAncestors: readonly string[];
32
+ }): string;
33
+ /** The code shape Worker Loader's `get` callback returns (Cloudflare `WorkerCode`). */
34
+ export interface CloudArtifactWorkerCode {
35
+ compatibilityDate: string;
36
+ compatibilityFlags?: string[];
37
+ mainModule: string;
38
+ modules: Record<string, string | {
39
+ js: string;
40
+ } | {
41
+ json: unknown;
42
+ } | {
43
+ text: string;
44
+ }>;
45
+ env?: Record<string, unknown>;
46
+ globalOutbound: null;
47
+ limits?: {
48
+ cpuMs?: number;
49
+ subRequests?: number;
50
+ };
51
+ }
52
+ /** Structural `env.LOADER` (a `worker_loaders` binding), so workers-types stays out of the public types. */
53
+ export interface CloudArtifactWorkerLoader {
54
+ get(id: string, load: () => Promise<CloudArtifactWorkerCode>): {
55
+ getEntrypoint(name?: string | null, options?: {
56
+ limits?: {
57
+ cpuMs?: number;
58
+ subRequests?: number;
59
+ };
60
+ }): {
61
+ fetch(request: Request): Promise<Response>;
62
+ };
63
+ };
64
+ }
65
+ export interface CloudArtifactOriginOptions {
66
+ /** This origin, such as `https://gtm-artifacts.tangle.sh`. Requests for another host get 404. */
67
+ origin: string;
68
+ /** Product origins allowed to frame an artifact (CSP `frame-ancestors`). */
69
+ frameAncestors: readonly string[];
70
+ /** The secret the product signs view links with. */
71
+ secret: string;
72
+ store: ObjectStore;
73
+ /**
74
+ * The artifact's current record for a verified link, or null when it no
75
+ * longer exists or the grant is no longer live (a revoked share).
76
+ */
77
+ resolve(view: {
78
+ artifactId: string;
79
+ grant: CloudArtifactGrant;
80
+ }): Promise<CloudArtifactRecord | null>;
81
+ /** The `worker_loaders` binding. Without it a `worker` artifact serves only its files. */
82
+ loader?: CloudArtifactWorkerLoader;
83
+ /** Bindings granted to a `worker` artifact's isolate; nothing else reaches it. */
84
+ workerEnv?(record: CloudArtifactRecord, grant: CloudArtifactGrant): Record<string, unknown>;
85
+ workerLimits?: {
86
+ cpuMs: number;
87
+ subRequests: number;
88
+ };
89
+ /** Compatibility date for isolates. */
90
+ workerCompatibilityDate?: string;
91
+ now?: () => number;
92
+ }
93
+ /** Serve cloud artifacts on their own origin. */
94
+ export declare function createCloudArtifactOrigin(options: CloudArtifactOriginOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1,135 @@
1
+ import type { ObjectStore } from '../object-store';
2
+ import type { WorkspaceRole } from '../teams/roles';
3
+ export type CloudArtifactKind = 'static' | 'worker' | 'openui';
4
+ /** One stored file of a version. Bytes live at {@link cloudArtifactObjectKey}. */
5
+ export interface CloudArtifactFile {
6
+ /** Relative to the bundle root, such as `index.html` or `images/hero.png`. */
7
+ path: string;
8
+ contentType: string;
9
+ size: number;
10
+ /** Hex SHA-256 of the bytes; identical files share one stored object. */
11
+ sha256: string;
12
+ }
13
+ /** An immutable snapshot. Its id changes when any path, type or byte changes. */
14
+ export interface CloudArtifactVersion {
15
+ id: string;
16
+ kind: CloudArtifactKind;
17
+ /** The file a link opens: HTML for `static` and `worker`, JSON for `openui`. */
18
+ entry: string;
19
+ files: CloudArtifactFile[];
20
+ createdAt: string;
21
+ }
22
+ /** What the origin needs from the product's record. Products persist more. */
23
+ export interface CloudArtifactRecord {
24
+ id: string;
25
+ workspaceId: string;
26
+ title: string;
27
+ version: CloudArtifactVersion;
28
+ }
29
+ /** Who a view link was minted for: a workspace member, or a public share. */
30
+ export type CloudArtifactGrant = {
31
+ type: 'member';
32
+ } | {
33
+ type: 'share';
34
+ shareId: string;
35
+ };
36
+ /** Opening an artifact needs `viewer`; creating or revoking a public link needs this role. */
37
+ export declare const CLOUD_ARTIFACT_SHARE_ROLE: WorkspaceRole;
38
+ /** The module a `worker` bundle runs, at the bundle root (the Cloudflare Pages name). */
39
+ export declare const CLOUD_ARTIFACT_WORKER_MODULE = "_worker.js";
40
+ export declare const CLOUD_ARTIFACT_MAX_FILES = 200;
41
+ export declare const CLOUD_ARTIFACT_MAX_FILE_BYTES: number;
42
+ export declare const CLOUD_ARTIFACT_MAX_BYTES: number;
43
+ /** Member view links are re-minted on every page load, so they can be short. */
44
+ export declare const CLOUD_ARTIFACT_VIEW_TTL_MS: number;
45
+ export type CloudArtifactErrorCode = 'invalid_path' | 'duplicate_path' | 'unsupported_type' | 'too_large' | 'too_many_files' | 'missing_entry' | 'invalid_entry';
46
+ export declare class CloudArtifactError extends Error {
47
+ readonly code: CloudArtifactErrorCode;
48
+ constructor(code: CloudArtifactErrorCode, message: string);
49
+ }
50
+ /** The served type for a path, or null when cloud artifacts do not serve it. */
51
+ export declare function cloudArtifactContentType(path: string): string | null;
52
+ /** A bundle path: relative, no `.` or `..` segments, no control or URL-special characters. */
53
+ export declare function isCloudArtifactPath(path: string): boolean;
54
+ export interface CloudArtifactInputFile {
55
+ path: string;
56
+ body: Uint8Array | string;
57
+ }
58
+ export interface PreparedCloudArtifact {
59
+ version: CloudArtifactVersion;
60
+ /** Bytes to store, by SHA-256. Carried files are already stored and absent here. */
61
+ bodies: ReadonlyMap<string, Uint8Array>;
62
+ }
63
+ /**
64
+ * Validate and hash a bundle. `carry` keeps files of an earlier version that
65
+ * the new source could not supply, such as images that existed only on a box
66
+ * that has since been recycled; their bytes are already stored.
67
+ */
68
+ export declare function prepareCloudArtifact(input: {
69
+ files: readonly CloudArtifactInputFile[];
70
+ entry: string;
71
+ /** Set for a declarative OpenUI view; otherwise `_worker.js` makes it `worker`. */
72
+ openui?: boolean;
73
+ carry?: readonly CloudArtifactFile[];
74
+ createdAt?: string;
75
+ }): Promise<PreparedCloudArtifact>;
76
+ /** Content-addressed per workspace, so erasing a workspace deletes one prefix. */
77
+ export declare function cloudArtifactObjectKey(workspaceId: string, sha256Hex: string): string;
78
+ /** The prefix holding every stored byte of a workspace's artifacts. */
79
+ export declare function cloudArtifactWorkspacePrefix(workspaceId: string): string;
80
+ /** Store a prepared version's new bytes; objects already present are not rewritten. */
81
+ export declare function storeCloudArtifact(store: ObjectStore, workspaceId: string, prepared: PreparedCloudArtifact): Promise<CloudArtifactVersion>;
82
+ /** Resolve a document-relative reference to a bundle path, or null when it is not a local file. */
83
+ export declare function resolveCloudArtifactReference(reference: string, documentPath: string): string | null;
84
+ /** Local files an HTML, CSS or JavaScript document refers to, as bundle paths. */
85
+ export declare function cloudArtifactReferences(text: string, documentPath: string): string[];
86
+ /**
87
+ * Gather an entry and every local file it refers to, transitively, plus
88
+ * `_worker.js` and its imports when the root has one. `readMany` returns the
89
+ * bytes of the paths it found (paths are bundle-relative); missing files are
90
+ * reported, not fatal, so a page with one broken image still publishes.
91
+ */
92
+ export declare function collectCloudArtifactBundle(input: {
93
+ entry: string;
94
+ readMany(paths: readonly string[]): Promise<ReadonlyMap<string, Uint8Array>>;
95
+ maxFiles?: number;
96
+ maxBytes?: number;
97
+ }): Promise<{
98
+ files: CloudArtifactInputFile[];
99
+ missing: string[];
100
+ }>;
101
+ /** A random 32-hex id for an artifact or a share. */
102
+ export declare function createCloudArtifactId(): string;
103
+ /**
104
+ * A short-lived URL on the artifact origin for one artifact and one grant.
105
+ * Every file of the bundle sits under the same prefix, so relative links work.
106
+ * The product mints it only after it has authorized the viewer.
107
+ */
108
+ export declare function createCloudArtifactViewUrl(input: {
109
+ origin: string;
110
+ secret: string;
111
+ artifactId: string;
112
+ grant: CloudArtifactGrant;
113
+ path: string;
114
+ ttlMs?: number;
115
+ now?: () => number;
116
+ }): Promise<string>;
117
+ export interface CloudArtifactView {
118
+ artifactId: string;
119
+ grant: CloudArtifactGrant;
120
+ /** Decoded bundle path; empty when the URL names the root. */
121
+ path: string;
122
+ }
123
+ /** Read and verify a view URL on the artifact origin; null for anything forged, expired or malformed. */
124
+ export declare function readCloudArtifactViewUrl(url: URL, options: {
125
+ secret: string;
126
+ now?: () => number;
127
+ }): Promise<CloudArtifactView | null>;
128
+ /**
129
+ * The secret part of a public link: `<shareId>.<HMAC>`. It is derived, so the
130
+ * product stores only the share id and can show the link again; revoking the
131
+ * share row turns the link off at once.
132
+ */
133
+ export declare function createCloudArtifactShareToken(shareId: string, secret: string): Promise<string>;
134
+ /** The share id a public link names, or null when its signature does not verify. */
135
+ export declare function readCloudArtifactShareToken(token: string, secret: string): Promise<string | null>;
@@ -72,3 +72,7 @@ export type WorkspaceAppBuilderInstructionsOptions = {
72
72
  export declare function workspaceAppBuilderInstructions(options: WorkspaceAppBuilderInstructionsOptions): string[];
73
73
  export { WorkspaceAppDataConflict, createWorkspaceAppDataClient, createWorkspaceAppDataHost, } from './data-bridge';
74
74
  export type { WorkspaceAppDataClient, WorkspaceAppDataClientOptions, WorkspaceAppDataEntry, WorkspaceAppDataHost, WorkspaceAppDataHostOptions, } from './data-bridge';
75
+ export { CLOUD_ARTIFACT_MAX_BYTES, CLOUD_ARTIFACT_MAX_FILES, CLOUD_ARTIFACT_MAX_FILE_BYTES, CLOUD_ARTIFACT_SHARE_ROLE, CLOUD_ARTIFACT_VIEW_TTL_MS, CLOUD_ARTIFACT_WORKER_MODULE, CloudArtifactError, cloudArtifactContentType, cloudArtifactObjectKey, cloudArtifactReferences, cloudArtifactWorkspacePrefix, collectCloudArtifactBundle, createCloudArtifactId, createCloudArtifactShareToken, createCloudArtifactViewUrl, isCloudArtifactPath, prepareCloudArtifact, readCloudArtifactShareToken, readCloudArtifactViewUrl, resolveCloudArtifactReference, storeCloudArtifact, } from './cloud-artifacts';
76
+ export type { CloudArtifactErrorCode, CloudArtifactFile, CloudArtifactGrant, CloudArtifactInputFile, CloudArtifactKind, CloudArtifactRecord, CloudArtifactVersion, CloudArtifactView, PreparedCloudArtifact, } from './cloud-artifacts';
77
+ export { CLOUD_ARTIFACT_FRAME_SANDBOX, CLOUD_ARTIFACT_SCRIPT_HOSTS, cloudArtifactContentSecurityPolicy, createCloudArtifactOrigin, } from './cloud-artifact-origin';
78
+ export type { CloudArtifactOriginOptions, CloudArtifactWorkerCode, CloudArtifactWorkerLoader, } from './cloud-artifact-origin';
@@ -1,3 +1,33 @@
1
+ import {
2
+ CLOUD_ARTIFACT_FRAME_SANDBOX,
3
+ CLOUD_ARTIFACT_MAX_BYTES,
4
+ CLOUD_ARTIFACT_MAX_FILES,
5
+ CLOUD_ARTIFACT_MAX_FILE_BYTES,
6
+ CLOUD_ARTIFACT_SCRIPT_HOSTS,
7
+ CLOUD_ARTIFACT_SHARE_ROLE,
8
+ CLOUD_ARTIFACT_VIEW_TTL_MS,
9
+ CLOUD_ARTIFACT_WORKER_MODULE,
10
+ CloudArtifactError,
11
+ cloudArtifactContentSecurityPolicy,
12
+ cloudArtifactContentType,
13
+ cloudArtifactObjectKey,
14
+ cloudArtifactReferences,
15
+ cloudArtifactWorkspacePrefix,
16
+ collectCloudArtifactBundle,
17
+ createCloudArtifactId,
18
+ createCloudArtifactOrigin,
19
+ createCloudArtifactShareToken,
20
+ createCloudArtifactViewUrl,
21
+ isCloudArtifactPath,
22
+ prepareCloudArtifact,
23
+ readCloudArtifactShareToken,
24
+ readCloudArtifactViewUrl,
25
+ resolveCloudArtifactReference,
26
+ storeCloudArtifact
27
+ } from "../chunk-7WEPGGOW.js";
28
+ import "../chunk-O7UX55EU.js";
29
+ import "../chunk-S5SRJJQG.js";
30
+
1
31
  // src/workspace-apps/data-bridge.ts
2
32
  var protocol = "tangle.workspace-app-data.v1";
3
33
  var maxBytes = 65536;
@@ -310,11 +340,36 @@ function workspaceAppBuilderInstructions(options) {
310
340
  ];
311
341
  }
312
342
  export {
343
+ CLOUD_ARTIFACT_FRAME_SANDBOX,
344
+ CLOUD_ARTIFACT_MAX_BYTES,
345
+ CLOUD_ARTIFACT_MAX_FILES,
346
+ CLOUD_ARTIFACT_MAX_FILE_BYTES,
347
+ CLOUD_ARTIFACT_SCRIPT_HOSTS,
348
+ CLOUD_ARTIFACT_SHARE_ROLE,
349
+ CLOUD_ARTIFACT_VIEW_TTL_MS,
350
+ CLOUD_ARTIFACT_WORKER_MODULE,
351
+ CloudArtifactError,
313
352
  WorkspaceAppDataConflict,
353
+ cloudArtifactContentSecurityPolicy,
354
+ cloudArtifactContentType,
355
+ cloudArtifactObjectKey,
356
+ cloudArtifactReferences,
357
+ cloudArtifactWorkspacePrefix,
358
+ collectCloudArtifactBundle,
314
359
  confirmWorkspaceAppReady,
360
+ createCloudArtifactId,
361
+ createCloudArtifactOrigin,
362
+ createCloudArtifactShareToken,
363
+ createCloudArtifactViewUrl,
315
364
  createWorkspaceAppDataClient,
316
365
  createWorkspaceAppDataHost,
366
+ isCloudArtifactPath,
367
+ prepareCloudArtifact,
368
+ readCloudArtifactShareToken,
369
+ readCloudArtifactViewUrl,
317
370
  refreshWorkspaceAppPreview,
371
+ resolveCloudArtifactReference,
372
+ storeCloudArtifact,
318
373
  workspaceAppBuilderInstructions,
319
374
  workspaceAppFromPreviewLink
320
375
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/workspace-apps/data-bridge.ts","../../src/workspace-apps/index.ts"],"sourcesContent":["import type { WorkspaceAppRecord } from './index'\n\nconst protocol = 'tangle.workspace-app-data.v1'\nconst maxBytes = 65_536\nconst maxPending = 16\nconst keyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/\nconst idPattern = /^[A-Za-z0-9_-]{1,80}$/\n\nexport interface WorkspaceAppDataEntry { value: string; revision: number }\nexport class WorkspaceAppDataConflict extends Error {\n constructor() {\n super('Workspace app data changed. Read it again before saving.')\n this.name = 'WorkspaceAppDataConflict'\n }\n}\nexport interface WorkspaceAppDataHostOptions {\n /** Product-authorized registration for the current viewer. */\n app: Pick<WorkspaceAppRecord, 'id' | 'previewUrl' | 'status'>\n frame: HTMLIFrameElement\n /** Product callbacks are bound to the authorized business and app namespace. */\n read(key: string): Promise<WorkspaceAppDataEntry | null>\n /** Null creates only if absent; a revision updates only that version. */\n write(key: string, value: string, expectedRevision: number | null): Promise<{ revision: number }>\n remove(key: string, expectedRevision: number): Promise<void>\n}\nexport interface WorkspaceAppDataHost { dispose(): void }\nexport interface WorkspaceAppDataClientOptions {\n appId: string\n /** Exact authenticated product origin, provided by the product. */\n parentOrigin: string\n}\nexport interface WorkspaceAppDataClient {\n ready(): Promise<void>\n read(key: string): Promise<WorkspaceAppDataEntry | null>\n write(key: string, value: string, expectedRevision: number | null): Promise<{ revision: number }>\n remove(key: string, expectedRevision: number): Promise<void>\n dispose(): void\n}\n\ntype Action = 'ready' | 'read' | 'write' | 'remove'\ntype Packet = {\n protocol: typeof protocol\n appId: string\n requestId: string\n action: Action\n key?: string\n value?: string\n expectedRevision?: number | null\n ok?: boolean\n entry?: WorkspaceAppDataEntry | null\n revision?: number\n error?: 'invalid' | 'conflict' | 'unavailable'\n}\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\nfunction key(value: unknown): value is string {\n return typeof value === 'string' && keyPattern.test(value) && value !== '.' && value !== '..'\n}\nfunction revision(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\nfunction bounded(value: unknown): value is string {\n return typeof value === 'string' && value.length <= maxBytes && new TextEncoder().encode(value).byteLength <= maxBytes\n}\nfunction appId(value: string): string {\n if (!idPattern.test(value)) throw new Error('Invalid workspace app ID')\n return value\n}\nfunction origin(value: string): string {\n const url = new URL(value)\n if (url.origin !== value || (url.protocol !== 'https:' &&\n !(url.protocol === 'http:' && ['localhost', '127.0.0.1'].includes(url.hostname)))) {\n throw new Error('Invalid workspace app data origin')\n }\n return url.origin\n}\nfunction packet(value: unknown): value is Packet {\n return object(value) && value.protocol === protocol &&\n typeof value.appId === 'string' && typeof value.requestId === 'string' &&\n /^[A-Za-z0-9_-]{1,64}$/.test(value.requestId) &&\n typeof value.action === 'string' && ['ready', 'read', 'write', 'remove'].includes(value.action)\n}\n\n/** Bind one registered preview to a product-authorized data adapter. */\nexport function createWorkspaceAppDataHost(options: WorkspaceAppDataHostOptions): WorkspaceAppDataHost {\n const { app, frame } = options\n appId(app.id)\n if (app.status !== 'ready') throw new Error('Workspace app is not ready')\n const preview = new URL(app.previewUrl)\n const previewOrigin = origin(preview.origin)\n if (preview.protocol !== 'https:' || preview.username || preview.password || preview.search || preview.hash ||\n new URL(frame.src).href !== preview.href) {\n throw new Error('Iframe does not render the registered workspace app preview')\n }\n let closed = false\n let active = 0\n let messagesLeft = 64\n let nextWindow = Date.now() + 1000\n const onMessage = (event: MessageEvent<unknown>): void => {\n if (closed || event.source !== frame.contentWindow || event.origin !== previewOrigin ||\n !packet(event.data) || event.data.appId !== app.id) return\n const now = Date.now()\n if (now >= nextWindow) { messagesLeft = 64; nextWindow = now + 1000 }\n if (messagesLeft-- <= 0) return\n const message = event.data\n const reply = (result: Pick<Packet, 'ok' | 'entry' | 'revision' | 'error'>): void => {\n if (closed || event.source !== frame.contentWindow) return\n frame.contentWindow?.postMessage({\n protocol, appId: app.id, requestId: message.requestId, action: message.action, ...result,\n } satisfies Packet, previewOrigin)\n }\n if (message.action === 'ready') { reply({ ok: true }); return }\n if (!key(message.key) ||\n (message.action === 'write' && (!bounded(message.value) ||\n !(message.expectedRevision === null || revision(message.expectedRevision)))) ||\n (message.action === 'remove' && !revision(message.expectedRevision))) {\n reply({ ok: false, error: 'invalid' }); return\n }\n if (active >= maxPending) { reply({ ok: false, error: 'unavailable' }); return }\n active += 1\n const checkedKey = message.key\n void (async () => {\n try {\n if (message.action === 'read') {\n const entry = await options.read(checkedKey)\n if (entry !== null && (!bounded(entry.value) || !revision(entry.revision))) throw new Error('Invalid entry')\n reply({ ok: true, entry })\n } else if (message.action === 'write') {\n const result = await options.write(checkedKey, message.value as string, message.expectedRevision as number | null)\n if (!revision(result.revision)) throw new Error('Invalid revision')\n reply({ ok: true, revision: result.revision })\n } else {\n await options.remove(checkedKey, message.expectedRevision as number)\n reply({ ok: true })\n }\n } catch (error) {\n reply({ ok: false, error: error instanceof WorkspaceAppDataConflict ? 'conflict' : 'unavailable' })\n } finally { active -= 1 }\n })()\n }\n window.addEventListener('message', onMessage)\n return { dispose() { closed = true; window.removeEventListener('message', onMessage) } }\n}\n\n/** Use inside a registered preview; no product credential crosses the frame. */\nexport function createWorkspaceAppDataClient(options: WorkspaceAppDataClientOptions): WorkspaceAppDataClient {\n const id = appId(options.appId)\n const parentOrigin = origin(options.parentOrigin)\n if (window.parent === window) throw new Error('Workspace app data requires an embedded preview')\n const pending = new Map<string, {\n action: Action\n resolve(value: Packet): void\n reject(error: Error): void\n timeout: ReturnType<typeof setTimeout>\n }>()\n let closed = false\n let readyPromise: Promise<void> | undefined\n const onMessage = (event: MessageEvent<unknown>): void => {\n if (closed || event.source !== window.parent || event.origin !== parentOrigin || !packet(event.data) ||\n event.data.appId !== id || typeof event.data.ok !== 'boolean') return\n const waiting = pending.get(event.data.requestId)\n if (!waiting || waiting.action !== event.data.action) return\n pending.delete(event.data.requestId)\n clearTimeout(waiting.timeout)\n if (event.data.ok) waiting.resolve(event.data)\n else waiting.reject(event.data.error === 'conflict' ? new WorkspaceAppDataConflict() :\n new Error('Workspace app data unavailable'))\n }\n window.addEventListener('message', onMessage)\n const send = (action: Action, fields: Partial<Packet> = {}, timeoutMs = 10_000): Promise<Packet> => {\n if (closed) return Promise.reject(new Error('Workspace app data client is closed'))\n if (pending.size >= maxPending) return Promise.reject(new Error('Too many workspace app data requests'))\n const requestId = crypto.randomUUID()\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => { pending.delete(requestId); reject(new Error('Workspace app data timed out')) }, timeoutMs)\n pending.set(requestId, { action, resolve, reject, timeout })\n window.parent.postMessage({ protocol, appId: id, requestId, action, ...fields } satisfies Packet, parentOrigin)\n })\n }\n const ready = (): Promise<void> => {\n if (readyPromise) return readyPromise\n readyPromise = new Promise<void>((resolve, reject) => {\n const deadline = Date.now() + 10_000\n const attempt = (): void => {\n if (closed) { reject(new Error('Workspace app data client is closed')); return }\n if (Date.now() >= deadline) { reject(new Error('Workspace app data host did not become ready')); return }\n void send('ready', {}, 300).then(() => resolve(), () => setTimeout(attempt, 200))\n }\n attempt()\n }).catch((error: unknown) => { readyPromise = undefined; throw error })\n return readyPromise\n }\n return {\n ready,\n async read(value) {\n if (!key(value)) throw new Error('Invalid workspace app data key')\n await ready()\n const response = await send('read', { key: value })\n if (response.entry !== null && (!object(response.entry) ||\n !bounded(response.entry.value) || !revision(response.entry.revision))) {\n throw new Error('Invalid workspace app data response')\n }\n return response.entry ?? null\n },\n async write(value, data, expectedRevision) {\n if (!key(value) || !bounded(data) || !(expectedRevision === null || revision(expectedRevision))) {\n throw new Error('Invalid workspace app data write')\n }\n await ready()\n const response = await send('write', { key: value, value: data, expectedRevision })\n if (!revision(response.revision)) throw new Error('Invalid workspace app data response')\n return { revision: response.revision }\n },\n async remove(value, expectedRevision) {\n if (!key(value) || !revision(expectedRevision)) throw new Error('Invalid workspace app data remove')\n await ready()\n await send('remove', { key: value, expectedRevision })\n },\n dispose() {\n closed = true\n window.removeEventListener('message', onMessage)\n for (const waiting of pending.values()) {\n clearTimeout(waiting.timeout)\n waiting.reject(new Error('Workspace app data client is closed'))\n }\n pending.clear()\n },\n }\n}\n","/** A sandbox app registered in one product workspace. Products persist and authorize these rows. */\nexport interface WorkspaceAppRecord {\n id: string\n workspaceId: string\n name: string\n sandboxId: string\n previewId: string\n port: number\n previewUrl: string\n status: 'starting' | 'ready' | 'unavailable'\n createdAt: string\n updatedAt: string\n}\n\n/** Structural shape returned by the Sandbox SDK previewLinks API. */\nexport interface WorkspaceAppPreviewLink {\n previewId: string\n sandboxId: string\n port: number\n protocol: 'tcp' | 'udp'\n hostname: string\n url: string\n status: 'provisioning' | 'ready' | 'error' | 'disabled'\n}\n\nexport interface WorkspaceAppIdentity {\n /** Assigned by the product, stable across preview updates and deliberate republishes. */\n id: string\n /** Taken from the authenticated route or session, never agent tool arguments. */\n workspaceId: string\n /** Current box resolved by the product from that workspace or session. */\n sandboxId: string\n name: string\n /** Preserve this when republishing the same app onto a replacement sandbox. */\n createdAt?: string\n updatedAt?: string\n}\n\nfunction nonempty(value: string, field: string): string {\n if (typeof value !== 'string' || !value.trim() || /[\\u0000-\\u001f\\u007f]/.test(value)) {\n throw new Error('Invalid workspace app ' + field)\n }\n return value.trim()\n}\n\nfunction checkedLink(link: WorkspaceAppPreviewLink, sandboxId: string): void {\n if (link.sandboxId !== sandboxId) {\n throw new Error('Preview link belongs to another sandbox')\n }\n nonempty(link.previewId, 'previewId')\n if (link.protocol !== 'tcp') throw new Error('Workspace app preview must use TCP')\n if (!Number.isInteger(link.port) || link.port < 1 || link.port > 65535) {\n throw new Error('Invalid workspace app port')\n }\n if (!['provisioning', 'ready', 'error', 'disabled'].includes(link.status)) {\n throw new Error('Invalid workspace app preview status')\n }\n let url: URL\n try {\n url = new URL(link.url)\n } catch {\n throw new Error('Invalid workspace app preview URL')\n }\n if (\n url.protocol !== 'https:' ||\n !url.hostname ||\n url.hostname !== link.hostname ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n throw new Error('Invalid workspace app preview URL')\n }\n}\n\nfunction appStatus(status: WorkspaceAppPreviewLink['status']): WorkspaceAppRecord['status'] {\n if (status === 'ready' || status === 'provisioning') return 'starting'\n return 'unavailable'\n}\n\n/**\n * Build a durable registration from a preview link created on the box that\n * the authenticated product request resolved. The product owns row identity,\n * access policy, persistence, and the decision to publish.\n */\nexport function workspaceAppFromPreviewLink(\n identity: WorkspaceAppIdentity,\n link: WorkspaceAppPreviewLink,\n): WorkspaceAppRecord {\n const sandboxId = nonempty(identity.sandboxId, 'sandboxId')\n checkedLink(link, sandboxId)\n const timestamp = identity.updatedAt ?? new Date().toISOString()\n return {\n id: nonempty(identity.id, 'id'),\n workspaceId: nonempty(identity.workspaceId, 'workspaceId'),\n name: nonempty(identity.name, 'name'),\n sandboxId,\n previewId: link.previewId,\n port: link.port,\n previewUrl: link.url,\n status: appStatus(link.status),\n createdAt: identity.createdAt ?? timestamp,\n updatedAt: timestamp,\n }\n}\n\nexport interface WorkspaceAppHttpProof {\n /** The exact trusted SDK preview URL requested by the server. */\n previewUrl: string\n status: number\n contentType: string\n /** Number of bytes read from the response body, excluding headers. */\n bodyBytes: number\n checkedAt: string\n}\n\n/**\n * Mark an app ready only after the host fetched its HTML over the trusted\n * preview URL. The host owns the request timeout, redirect and credential\n * policy, authorization, and compare-and-swap storage update.\n */\nexport function confirmWorkspaceAppReady(\n app: WorkspaceAppRecord,\n proof: WorkspaceAppHttpProof,\n): WorkspaceAppRecord {\n if (\n proof.previewUrl !== app.previewUrl ||\n proof.status !== 200 ||\n !/^text\\/html(?:\\s*;|\\s*$)/i.test(proof.contentType) ||\n !Number.isInteger(proof.bodyBytes) ||\n proof.bodyBytes < 1 ||\n !Number.isFinite(Date.parse(proof.checkedAt))\n ) {\n throw new Error('Workspace app HTML preview is not ready')\n }\n return { ...app, status: 'ready', updatedAt: proof.checkedAt }\n}\n\n/** Refresh delivery state without changing which sandbox service this app means. */\nexport function refreshWorkspaceAppPreview(\n app: WorkspaceAppRecord,\n link: WorkspaceAppPreviewLink,\n updatedAt = new Date().toISOString(),\n): WorkspaceAppRecord {\n checkedLink(link, app.sandboxId)\n if (link.previewId !== app.previewId || link.port !== app.port) {\n throw new Error('Preview link does not match workspace app')\n }\n return {\n ...app,\n previewUrl: link.url,\n status: appStatus(link.status),\n updatedAt,\n }\n}\n\nexport type WorkspaceAppBuilderInstructionsOptions =\n | {\n /** Host tool that writes the registration after creating a trusted preview. */\n publishTool: string\n /** Optional host tool for finding app IDs the builder may update. */\n listTool?: string\n manifestPath?: never\n }\n | {\n /** Host-scoped manifest the builder writes after creating runnable projects. */\n manifestPath: string\n publishTool?: never\n listTool?: never\n }\n\nfunction toolName(value: string): string {\n if (!/^[A-Za-z][A-Za-z0-9_.-]*$/.test(value)) {\n throw new Error('Invalid workspace app tool name')\n }\n return value\n}\n\nfunction builderManifestPath(value: string): string {\n if (\n !/^[A-Za-z0-9._-]+(?:[/][A-Za-z0-9._-]+)*$/.test(value) ||\n value.split('/').some(segment => segment === '..' || segment === '.')\n ) {\n throw new Error('Invalid workspace app manifest path')\n }\n return value\n}\n\n/** Portable instructions for an agent profile that builds workspace apps. */\nexport function workspaceAppBuilderInstructions(\n options: WorkspaceAppBuilderInstructionsOptions,\n): string[] {\n const common = [\n 'Build a working HTTP application in the current sandbox when the user asks for an app.',\n 'Keep the same app ID for later revisions of that app.',\n 'Treat a preview link as public. Do not embed secrets or private business facts in client code or responses.',\n 'Read real business data through authorized product APIs. Do not fabricate records to make the app look complete.',\n 'Report the app as available only after the host confirms its preview is ready.',\n ] as const\n if (options.manifestPath !== undefined) {\n const path = builderManifestPath(options.manifestPath)\n return [\n common[0],\n 'Inspect the existing app projects and manifest before creating or updating an app.',\n 'Keep each runnable project under apps/<id> relative to the sandbox working directory. Give it a package.json dev script; its HTTP server must listen on 0.0.0.0 and use PORT when supplied.',\n 'Write ' + path + ' as JSON with exactly an apps array of { id, name, projectPath } records; each projectPath is apps/<id>.',\n 'Do not put URLs, credentials, tokens, sandbox IDs, or preview IDs in that manifest.',\n 'The host reads this manifest and owns server launch, port selection, preview creation, registration, and access policy.',\n ...common.slice(1),\n ]\n }\n const publish = toolName(options.publishTool)\n const list = options.listTool ? toolName(options.listTool) : undefined\n return [\n common[0],\n ...(list ? ['Use ' + list + ' to inspect the existing apps before creating or updating one.'] : []),\n 'Start its server on 0.0.0.0 using an available, unclaimed port.',\n 'Call ' + publish + ' with the app name, stable app ID when updating, and listening port.',\n 'Use the host tool result as the source of the registered app URL and status; never invent a preview URL.',\n ...common.slice(1),\n ]\n}\n\nexport {\n WorkspaceAppDataConflict,\n createWorkspaceAppDataClient,\n createWorkspaceAppDataHost,\n} from './data-bridge'\nexport type {\n WorkspaceAppDataClient,\n WorkspaceAppDataClientOptions,\n WorkspaceAppDataEntry,\n WorkspaceAppDataHost,\n WorkspaceAppDataHostOptions,\n} from './data-bridge'\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/workspace-apps/data-bridge.ts","../../src/workspace-apps/index.ts"],"sourcesContent":["import type { WorkspaceAppRecord } from './index'\n\nconst protocol = 'tangle.workspace-app-data.v1'\nconst maxBytes = 65_536\nconst maxPending = 16\nconst keyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/\nconst idPattern = /^[A-Za-z0-9_-]{1,80}$/\n\nexport interface WorkspaceAppDataEntry { value: string; revision: number }\nexport class WorkspaceAppDataConflict extends Error {\n constructor() {\n super('Workspace app data changed. Read it again before saving.')\n this.name = 'WorkspaceAppDataConflict'\n }\n}\nexport interface WorkspaceAppDataHostOptions {\n /** Product-authorized registration for the current viewer. */\n app: Pick<WorkspaceAppRecord, 'id' | 'previewUrl' | 'status'>\n frame: HTMLIFrameElement\n /** Product callbacks are bound to the authorized business and app namespace. */\n read(key: string): Promise<WorkspaceAppDataEntry | null>\n /** Null creates only if absent; a revision updates only that version. */\n write(key: string, value: string, expectedRevision: number | null): Promise<{ revision: number }>\n remove(key: string, expectedRevision: number): Promise<void>\n}\nexport interface WorkspaceAppDataHost { dispose(): void }\nexport interface WorkspaceAppDataClientOptions {\n appId: string\n /** Exact authenticated product origin, provided by the product. */\n parentOrigin: string\n}\nexport interface WorkspaceAppDataClient {\n ready(): Promise<void>\n read(key: string): Promise<WorkspaceAppDataEntry | null>\n write(key: string, value: string, expectedRevision: number | null): Promise<{ revision: number }>\n remove(key: string, expectedRevision: number): Promise<void>\n dispose(): void\n}\n\ntype Action = 'ready' | 'read' | 'write' | 'remove'\ntype Packet = {\n protocol: typeof protocol\n appId: string\n requestId: string\n action: Action\n key?: string\n value?: string\n expectedRevision?: number | null\n ok?: boolean\n entry?: WorkspaceAppDataEntry | null\n revision?: number\n error?: 'invalid' | 'conflict' | 'unavailable'\n}\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\nfunction key(value: unknown): value is string {\n return typeof value === 'string' && keyPattern.test(value) && value !== '.' && value !== '..'\n}\nfunction revision(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\nfunction bounded(value: unknown): value is string {\n return typeof value === 'string' && value.length <= maxBytes && new TextEncoder().encode(value).byteLength <= maxBytes\n}\nfunction appId(value: string): string {\n if (!idPattern.test(value)) throw new Error('Invalid workspace app ID')\n return value\n}\nfunction origin(value: string): string {\n const url = new URL(value)\n if (url.origin !== value || (url.protocol !== 'https:' &&\n !(url.protocol === 'http:' && ['localhost', '127.0.0.1'].includes(url.hostname)))) {\n throw new Error('Invalid workspace app data origin')\n }\n return url.origin\n}\nfunction packet(value: unknown): value is Packet {\n return object(value) && value.protocol === protocol &&\n typeof value.appId === 'string' && typeof value.requestId === 'string' &&\n /^[A-Za-z0-9_-]{1,64}$/.test(value.requestId) &&\n typeof value.action === 'string' && ['ready', 'read', 'write', 'remove'].includes(value.action)\n}\n\n/** Bind one registered preview to a product-authorized data adapter. */\nexport function createWorkspaceAppDataHost(options: WorkspaceAppDataHostOptions): WorkspaceAppDataHost {\n const { app, frame } = options\n appId(app.id)\n if (app.status !== 'ready') throw new Error('Workspace app is not ready')\n const preview = new URL(app.previewUrl)\n const previewOrigin = origin(preview.origin)\n if (preview.protocol !== 'https:' || preview.username || preview.password || preview.search || preview.hash ||\n new URL(frame.src).href !== preview.href) {\n throw new Error('Iframe does not render the registered workspace app preview')\n }\n let closed = false\n let active = 0\n let messagesLeft = 64\n let nextWindow = Date.now() + 1000\n const onMessage = (event: MessageEvent<unknown>): void => {\n if (closed || event.source !== frame.contentWindow || event.origin !== previewOrigin ||\n !packet(event.data) || event.data.appId !== app.id) return\n const now = Date.now()\n if (now >= nextWindow) { messagesLeft = 64; nextWindow = now + 1000 }\n if (messagesLeft-- <= 0) return\n const message = event.data\n const reply = (result: Pick<Packet, 'ok' | 'entry' | 'revision' | 'error'>): void => {\n if (closed || event.source !== frame.contentWindow) return\n frame.contentWindow?.postMessage({\n protocol, appId: app.id, requestId: message.requestId, action: message.action, ...result,\n } satisfies Packet, previewOrigin)\n }\n if (message.action === 'ready') { reply({ ok: true }); return }\n if (!key(message.key) ||\n (message.action === 'write' && (!bounded(message.value) ||\n !(message.expectedRevision === null || revision(message.expectedRevision)))) ||\n (message.action === 'remove' && !revision(message.expectedRevision))) {\n reply({ ok: false, error: 'invalid' }); return\n }\n if (active >= maxPending) { reply({ ok: false, error: 'unavailable' }); return }\n active += 1\n const checkedKey = message.key\n void (async () => {\n try {\n if (message.action === 'read') {\n const entry = await options.read(checkedKey)\n if (entry !== null && (!bounded(entry.value) || !revision(entry.revision))) throw new Error('Invalid entry')\n reply({ ok: true, entry })\n } else if (message.action === 'write') {\n const result = await options.write(checkedKey, message.value as string, message.expectedRevision as number | null)\n if (!revision(result.revision)) throw new Error('Invalid revision')\n reply({ ok: true, revision: result.revision })\n } else {\n await options.remove(checkedKey, message.expectedRevision as number)\n reply({ ok: true })\n }\n } catch (error) {\n reply({ ok: false, error: error instanceof WorkspaceAppDataConflict ? 'conflict' : 'unavailable' })\n } finally { active -= 1 }\n })()\n }\n window.addEventListener('message', onMessage)\n return { dispose() { closed = true; window.removeEventListener('message', onMessage) } }\n}\n\n/** Use inside a registered preview; no product credential crosses the frame. */\nexport function createWorkspaceAppDataClient(options: WorkspaceAppDataClientOptions): WorkspaceAppDataClient {\n const id = appId(options.appId)\n const parentOrigin = origin(options.parentOrigin)\n if (window.parent === window) throw new Error('Workspace app data requires an embedded preview')\n const pending = new Map<string, {\n action: Action\n resolve(value: Packet): void\n reject(error: Error): void\n timeout: ReturnType<typeof setTimeout>\n }>()\n let closed = false\n let readyPromise: Promise<void> | undefined\n const onMessage = (event: MessageEvent<unknown>): void => {\n if (closed || event.source !== window.parent || event.origin !== parentOrigin || !packet(event.data) ||\n event.data.appId !== id || typeof event.data.ok !== 'boolean') return\n const waiting = pending.get(event.data.requestId)\n if (!waiting || waiting.action !== event.data.action) return\n pending.delete(event.data.requestId)\n clearTimeout(waiting.timeout)\n if (event.data.ok) waiting.resolve(event.data)\n else waiting.reject(event.data.error === 'conflict' ? new WorkspaceAppDataConflict() :\n new Error('Workspace app data unavailable'))\n }\n window.addEventListener('message', onMessage)\n const send = (action: Action, fields: Partial<Packet> = {}, timeoutMs = 10_000): Promise<Packet> => {\n if (closed) return Promise.reject(new Error('Workspace app data client is closed'))\n if (pending.size >= maxPending) return Promise.reject(new Error('Too many workspace app data requests'))\n const requestId = crypto.randomUUID()\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => { pending.delete(requestId); reject(new Error('Workspace app data timed out')) }, timeoutMs)\n pending.set(requestId, { action, resolve, reject, timeout })\n window.parent.postMessage({ protocol, appId: id, requestId, action, ...fields } satisfies Packet, parentOrigin)\n })\n }\n const ready = (): Promise<void> => {\n if (readyPromise) return readyPromise\n readyPromise = new Promise<void>((resolve, reject) => {\n const deadline = Date.now() + 10_000\n const attempt = (): void => {\n if (closed) { reject(new Error('Workspace app data client is closed')); return }\n if (Date.now() >= deadline) { reject(new Error('Workspace app data host did not become ready')); return }\n void send('ready', {}, 300).then(() => resolve(), () => setTimeout(attempt, 200))\n }\n attempt()\n }).catch((error: unknown) => { readyPromise = undefined; throw error })\n return readyPromise\n }\n return {\n ready,\n async read(value) {\n if (!key(value)) throw new Error('Invalid workspace app data key')\n await ready()\n const response = await send('read', { key: value })\n if (response.entry !== null && (!object(response.entry) ||\n !bounded(response.entry.value) || !revision(response.entry.revision))) {\n throw new Error('Invalid workspace app data response')\n }\n return response.entry ?? null\n },\n async write(value, data, expectedRevision) {\n if (!key(value) || !bounded(data) || !(expectedRevision === null || revision(expectedRevision))) {\n throw new Error('Invalid workspace app data write')\n }\n await ready()\n const response = await send('write', { key: value, value: data, expectedRevision })\n if (!revision(response.revision)) throw new Error('Invalid workspace app data response')\n return { revision: response.revision }\n },\n async remove(value, expectedRevision) {\n if (!key(value) || !revision(expectedRevision)) throw new Error('Invalid workspace app data remove')\n await ready()\n await send('remove', { key: value, expectedRevision })\n },\n dispose() {\n closed = true\n window.removeEventListener('message', onMessage)\n for (const waiting of pending.values()) {\n clearTimeout(waiting.timeout)\n waiting.reject(new Error('Workspace app data client is closed'))\n }\n pending.clear()\n },\n }\n}\n","/** A sandbox app registered in one product workspace. Products persist and authorize these rows. */\nexport interface WorkspaceAppRecord {\n id: string\n workspaceId: string\n name: string\n sandboxId: string\n previewId: string\n port: number\n previewUrl: string\n status: 'starting' | 'ready' | 'unavailable'\n createdAt: string\n updatedAt: string\n}\n\n/** Structural shape returned by the Sandbox SDK previewLinks API. */\nexport interface WorkspaceAppPreviewLink {\n previewId: string\n sandboxId: string\n port: number\n protocol: 'tcp' | 'udp'\n hostname: string\n url: string\n status: 'provisioning' | 'ready' | 'error' | 'disabled'\n}\n\nexport interface WorkspaceAppIdentity {\n /** Assigned by the product, stable across preview updates and deliberate republishes. */\n id: string\n /** Taken from the authenticated route or session, never agent tool arguments. */\n workspaceId: string\n /** Current box resolved by the product from that workspace or session. */\n sandboxId: string\n name: string\n /** Preserve this when republishing the same app onto a replacement sandbox. */\n createdAt?: string\n updatedAt?: string\n}\n\nfunction nonempty(value: string, field: string): string {\n if (typeof value !== 'string' || !value.trim() || /[\\u0000-\\u001f\\u007f]/.test(value)) {\n throw new Error('Invalid workspace app ' + field)\n }\n return value.trim()\n}\n\nfunction checkedLink(link: WorkspaceAppPreviewLink, sandboxId: string): void {\n if (link.sandboxId !== sandboxId) {\n throw new Error('Preview link belongs to another sandbox')\n }\n nonempty(link.previewId, 'previewId')\n if (link.protocol !== 'tcp') throw new Error('Workspace app preview must use TCP')\n if (!Number.isInteger(link.port) || link.port < 1 || link.port > 65535) {\n throw new Error('Invalid workspace app port')\n }\n if (!['provisioning', 'ready', 'error', 'disabled'].includes(link.status)) {\n throw new Error('Invalid workspace app preview status')\n }\n let url: URL\n try {\n url = new URL(link.url)\n } catch {\n throw new Error('Invalid workspace app preview URL')\n }\n if (\n url.protocol !== 'https:' ||\n !url.hostname ||\n url.hostname !== link.hostname ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n throw new Error('Invalid workspace app preview URL')\n }\n}\n\nfunction appStatus(status: WorkspaceAppPreviewLink['status']): WorkspaceAppRecord['status'] {\n if (status === 'ready' || status === 'provisioning') return 'starting'\n return 'unavailable'\n}\n\n/**\n * Build a durable registration from a preview link created on the box that\n * the authenticated product request resolved. The product owns row identity,\n * access policy, persistence, and the decision to publish.\n */\nexport function workspaceAppFromPreviewLink(\n identity: WorkspaceAppIdentity,\n link: WorkspaceAppPreviewLink,\n): WorkspaceAppRecord {\n const sandboxId = nonempty(identity.sandboxId, 'sandboxId')\n checkedLink(link, sandboxId)\n const timestamp = identity.updatedAt ?? new Date().toISOString()\n return {\n id: nonempty(identity.id, 'id'),\n workspaceId: nonempty(identity.workspaceId, 'workspaceId'),\n name: nonempty(identity.name, 'name'),\n sandboxId,\n previewId: link.previewId,\n port: link.port,\n previewUrl: link.url,\n status: appStatus(link.status),\n createdAt: identity.createdAt ?? timestamp,\n updatedAt: timestamp,\n }\n}\n\nexport interface WorkspaceAppHttpProof {\n /** The exact trusted SDK preview URL requested by the server. */\n previewUrl: string\n status: number\n contentType: string\n /** Number of bytes read from the response body, excluding headers. */\n bodyBytes: number\n checkedAt: string\n}\n\n/**\n * Mark an app ready only after the host fetched its HTML over the trusted\n * preview URL. The host owns the request timeout, redirect and credential\n * policy, authorization, and compare-and-swap storage update.\n */\nexport function confirmWorkspaceAppReady(\n app: WorkspaceAppRecord,\n proof: WorkspaceAppHttpProof,\n): WorkspaceAppRecord {\n if (\n proof.previewUrl !== app.previewUrl ||\n proof.status !== 200 ||\n !/^text\\/html(?:\\s*;|\\s*$)/i.test(proof.contentType) ||\n !Number.isInteger(proof.bodyBytes) ||\n proof.bodyBytes < 1 ||\n !Number.isFinite(Date.parse(proof.checkedAt))\n ) {\n throw new Error('Workspace app HTML preview is not ready')\n }\n return { ...app, status: 'ready', updatedAt: proof.checkedAt }\n}\n\n/** Refresh delivery state without changing which sandbox service this app means. */\nexport function refreshWorkspaceAppPreview(\n app: WorkspaceAppRecord,\n link: WorkspaceAppPreviewLink,\n updatedAt = new Date().toISOString(),\n): WorkspaceAppRecord {\n checkedLink(link, app.sandboxId)\n if (link.previewId !== app.previewId || link.port !== app.port) {\n throw new Error('Preview link does not match workspace app')\n }\n return {\n ...app,\n previewUrl: link.url,\n status: appStatus(link.status),\n updatedAt,\n }\n}\n\nexport type WorkspaceAppBuilderInstructionsOptions =\n | {\n /** Host tool that writes the registration after creating a trusted preview. */\n publishTool: string\n /** Optional host tool for finding app IDs the builder may update. */\n listTool?: string\n manifestPath?: never\n }\n | {\n /** Host-scoped manifest the builder writes after creating runnable projects. */\n manifestPath: string\n publishTool?: never\n listTool?: never\n }\n\nfunction toolName(value: string): string {\n if (!/^[A-Za-z][A-Za-z0-9_.-]*$/.test(value)) {\n throw new Error('Invalid workspace app tool name')\n }\n return value\n}\n\nfunction builderManifestPath(value: string): string {\n if (\n !/^[A-Za-z0-9._-]+(?:[/][A-Za-z0-9._-]+)*$/.test(value) ||\n value.split('/').some(segment => segment === '..' || segment === '.')\n ) {\n throw new Error('Invalid workspace app manifest path')\n }\n return value\n}\n\n/** Portable instructions for an agent profile that builds workspace apps. */\nexport function workspaceAppBuilderInstructions(\n options: WorkspaceAppBuilderInstructionsOptions,\n): string[] {\n const common = [\n 'Build a working HTTP application in the current sandbox when the user asks for an app.',\n 'Keep the same app ID for later revisions of that app.',\n 'Treat a preview link as public. Do not embed secrets or private business facts in client code or responses.',\n 'Read real business data through authorized product APIs. Do not fabricate records to make the app look complete.',\n 'Report the app as available only after the host confirms its preview is ready.',\n ] as const\n if (options.manifestPath !== undefined) {\n const path = builderManifestPath(options.manifestPath)\n return [\n common[0],\n 'Inspect the existing app projects and manifest before creating or updating an app.',\n 'Keep each runnable project under apps/<id> relative to the sandbox working directory. Give it a package.json dev script; its HTTP server must listen on 0.0.0.0 and use PORT when supplied.',\n 'Write ' + path + ' as JSON with exactly an apps array of { id, name, projectPath } records; each projectPath is apps/<id>.',\n 'Do not put URLs, credentials, tokens, sandbox IDs, or preview IDs in that manifest.',\n 'The host reads this manifest and owns server launch, port selection, preview creation, registration, and access policy.',\n ...common.slice(1),\n ]\n }\n const publish = toolName(options.publishTool)\n const list = options.listTool ? toolName(options.listTool) : undefined\n return [\n common[0],\n ...(list ? ['Use ' + list + ' to inspect the existing apps before creating or updating one.'] : []),\n 'Start its server on 0.0.0.0 using an available, unclaimed port.',\n 'Call ' + publish + ' with the app name, stable app ID when updating, and listening port.',\n 'Use the host tool result as the source of the registered app URL and status; never invent a preview URL.',\n ...common.slice(1),\n ]\n}\n\nexport {\n WorkspaceAppDataConflict,\n createWorkspaceAppDataClient,\n createWorkspaceAppDataHost,\n} from './data-bridge'\nexport type {\n WorkspaceAppDataClient,\n WorkspaceAppDataClientOptions,\n WorkspaceAppDataEntry,\n WorkspaceAppDataHost,\n WorkspaceAppDataHostOptions,\n} from './data-bridge'\n\nexport {\n CLOUD_ARTIFACT_MAX_BYTES,\n CLOUD_ARTIFACT_MAX_FILES,\n CLOUD_ARTIFACT_MAX_FILE_BYTES,\n CLOUD_ARTIFACT_SHARE_ROLE,\n CLOUD_ARTIFACT_VIEW_TTL_MS,\n CLOUD_ARTIFACT_WORKER_MODULE,\n CloudArtifactError,\n cloudArtifactContentType,\n cloudArtifactObjectKey,\n cloudArtifactReferences,\n cloudArtifactWorkspacePrefix,\n collectCloudArtifactBundle,\n createCloudArtifactId,\n createCloudArtifactShareToken,\n createCloudArtifactViewUrl,\n isCloudArtifactPath,\n prepareCloudArtifact,\n readCloudArtifactShareToken,\n readCloudArtifactViewUrl,\n resolveCloudArtifactReference,\n storeCloudArtifact,\n} from './cloud-artifacts'\nexport type {\n CloudArtifactErrorCode,\n CloudArtifactFile,\n CloudArtifactGrant,\n CloudArtifactInputFile,\n CloudArtifactKind,\n CloudArtifactRecord,\n CloudArtifactVersion,\n CloudArtifactView,\n PreparedCloudArtifact,\n} from './cloud-artifacts'\nexport {\n CLOUD_ARTIFACT_FRAME_SANDBOX,\n CLOUD_ARTIFACT_SCRIPT_HOSTS,\n cloudArtifactContentSecurityPolicy,\n createCloudArtifactOrigin,\n} from './cloud-artifact-origin'\nexport type {\n CloudArtifactOriginOptions,\n CloudArtifactWorkerCode,\n CloudArtifactWorkerLoader,\n} from './cloud-artifact-origin'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,aAAa;AACnB,IAAM,aAAa;AACnB,IAAM,YAAY;AAGX,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,cAAc;AACZ,UAAM,0DAA0D;AAChE,SAAK,OAAO;AAAA,EACd;AACF;AAuCA,SAAS,OAAO,OAAkD;AAChE,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AACA,SAAS,IAAI,OAAiC;AAC5C,SAAO,OAAO,UAAU,YAAY,WAAW,KAAK,KAAK,KAAK,UAAU,OAAO,UAAU;AAC3F;AACA,SAAS,SAAS,OAAiC;AACjD,SAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,QAAQ;AAC7E;AACA,SAAS,QAAQ,OAAiC;AAChD,SAAO,OAAO,UAAU,YAAY,MAAM,UAAU,YAAY,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE,cAAc;AAChH;AACA,SAAS,MAAM,OAAuB;AACpC,MAAI,CAAC,UAAU,KAAK,KAAK,EAAG,OAAM,IAAI,MAAM,0BAA0B;AACtE,SAAO;AACT;AACA,SAAS,OAAO,OAAuB;AACrC,QAAM,MAAM,IAAI,IAAI,KAAK;AACzB,MAAI,IAAI,WAAW,SAAU,IAAI,aAAa,YAC5C,EAAE,IAAI,aAAa,WAAW,CAAC,aAAa,WAAW,EAAE,SAAS,IAAI,QAAQ,IAAK;AACnF,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,SAAO,IAAI;AACb;AACA,SAAS,OAAO,OAAiC;AAC/C,SAAO,OAAO,KAAK,KAAK,MAAM,aAAa,YACzC,OAAO,MAAM,UAAU,YAAY,OAAO,MAAM,cAAc,YAC9D,wBAAwB,KAAK,MAAM,SAAS,KAC5C,OAAO,MAAM,WAAW,YAAY,CAAC,SAAS,QAAQ,SAAS,QAAQ,EAAE,SAAS,MAAM,MAAM;AAClG;AAGO,SAAS,2BAA2B,SAA4D;AACrG,QAAM,EAAE,KAAK,MAAM,IAAI;AACvB,QAAM,IAAI,EAAE;AACZ,MAAI,IAAI,WAAW,QAAS,OAAM,IAAI,MAAM,4BAA4B;AACxE,QAAM,UAAU,IAAI,IAAI,IAAI,UAAU;AACtC,QAAM,gBAAgB,OAAO,QAAQ,MAAM;AAC3C,MAAI,QAAQ,aAAa,YAAY,QAAQ,YAAY,QAAQ,YAAY,QAAQ,UAAU,QAAQ,QACrG,IAAI,IAAI,MAAM,GAAG,EAAE,SAAS,QAAQ,MAAM;AAC1C,UAAM,IAAI,MAAM,6DAA6D;AAAA,EAC/E;AACA,MAAI,SAAS;AACb,MAAI,SAAS;AACb,MAAI,eAAe;AACnB,MAAI,aAAa,KAAK,IAAI,IAAI;AAC9B,QAAM,YAAY,CAAC,UAAuC;AACxD,QAAI,UAAU,MAAM,WAAW,MAAM,iBAAiB,MAAM,WAAW,iBACrE,CAAC,OAAO,MAAM,IAAI,KAAK,MAAM,KAAK,UAAU,IAAI,GAAI;AACtD,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,OAAO,YAAY;AAAE,qBAAe;AAAI,mBAAa,MAAM;AAAA,IAAK;AACpE,QAAI,kBAAkB,EAAG;AACzB,UAAM,UAAU,MAAM;AACtB,UAAM,QAAQ,CAAC,WAAsE;AACnF,UAAI,UAAU,MAAM,WAAW,MAAM,cAAe;AACpD,YAAM,eAAe,YAAY;AAAA,QAC/B;AAAA,QAAU,OAAO,IAAI;AAAA,QAAI,WAAW,QAAQ;AAAA,QAAW,QAAQ,QAAQ;AAAA,QAAQ,GAAG;AAAA,MACpF,GAAoB,aAAa;AAAA,IACnC;AACA,QAAI,QAAQ,WAAW,SAAS;AAAE,YAAM,EAAE,IAAI,KAAK,CAAC;AAAG;AAAA,IAAO;AAC9D,QAAI,CAAC,IAAI,QAAQ,GAAG,KACjB,QAAQ,WAAW,YAAY,CAAC,QAAQ,QAAQ,KAAK,KACpD,EAAE,QAAQ,qBAAqB,QAAQ,SAAS,QAAQ,gBAAgB,OACzE,QAAQ,WAAW,YAAY,CAAC,SAAS,QAAQ,gBAAgB,GAAI;AACtE,YAAM,EAAE,IAAI,OAAO,OAAO,UAAU,CAAC;AAAG;AAAA,IAC1C;AACA,QAAI,UAAU,YAAY;AAAE,YAAM,EAAE,IAAI,OAAO,OAAO,cAAc,CAAC;AAAG;AAAA,IAAO;AAC/E,cAAU;AACV,UAAM,aAAa,QAAQ;AAC3B,UAAM,YAAY;AAChB,UAAI;AACF,YAAI,QAAQ,WAAW,QAAQ;AAC7B,gBAAM,QAAQ,MAAM,QAAQ,KAAK,UAAU;AAC3C,cAAI,UAAU,SAAS,CAAC,QAAQ,MAAM,KAAK,KAAK,CAAC,SAAS,MAAM,QAAQ,GAAI,OAAM,IAAI,MAAM,eAAe;AAC3G,gBAAM,EAAE,IAAI,MAAM,MAAM,CAAC;AAAA,QAC3B,WAAW,QAAQ,WAAW,SAAS;AACrC,gBAAM,SAAS,MAAM,QAAQ,MAAM,YAAY,QAAQ,OAAiB,QAAQ,gBAAiC;AACjH,cAAI,CAAC,SAAS,OAAO,QAAQ,EAAG,OAAM,IAAI,MAAM,kBAAkB;AAClE,gBAAM,EAAE,IAAI,MAAM,UAAU,OAAO,SAAS,CAAC;AAAA,QAC/C,OAAO;AACL,gBAAM,QAAQ,OAAO,YAAY,QAAQ,gBAA0B;AACnE,gBAAM,EAAE,IAAI,KAAK,CAAC;AAAA,QACpB;AAAA,MACF,SAAS,OAAO;AACd,cAAM,EAAE,IAAI,OAAO,OAAO,iBAAiB,2BAA2B,aAAa,cAAc,CAAC;AAAA,MACpG,UAAE;AAAU,kBAAU;AAAA,MAAE;AAAA,IAC1B,GAAG;AAAA,EACL;AACA,SAAO,iBAAiB,WAAW,SAAS;AAC5C,SAAO,EAAE,UAAU;AAAE,aAAS;AAAM,WAAO,oBAAoB,WAAW,SAAS;AAAA,EAAE,EAAE;AACzF;AAGO,SAAS,6BAA6B,SAAgE;AAC3G,QAAM,KAAK,MAAM,QAAQ,KAAK;AAC9B,QAAM,eAAe,OAAO,QAAQ,YAAY;AAChD,MAAI,OAAO,WAAW,OAAQ,OAAM,IAAI,MAAM,iDAAiD;AAC/F,QAAM,UAAU,oBAAI,IAKjB;AACH,MAAI,SAAS;AACb,MAAI;AACJ,QAAM,YAAY,CAAC,UAAuC;AACxD,QAAI,UAAU,MAAM,WAAW,OAAO,UAAU,MAAM,WAAW,gBAAgB,CAAC,OAAO,MAAM,IAAI,KACjG,MAAM,KAAK,UAAU,MAAM,OAAO,MAAM,KAAK,OAAO,UAAW;AACjE,UAAM,UAAU,QAAQ,IAAI,MAAM,KAAK,SAAS;AAChD,QAAI,CAAC,WAAW,QAAQ,WAAW,MAAM,KAAK,OAAQ;AACtD,YAAQ,OAAO,MAAM,KAAK,SAAS;AACnC,iBAAa,QAAQ,OAAO;AAC5B,QAAI,MAAM,KAAK,GAAI,SAAQ,QAAQ,MAAM,IAAI;AAAA,QACxC,SAAQ,OAAO,MAAM,KAAK,UAAU,aAAa,IAAI,yBAAyB,IACjF,IAAI,MAAM,gCAAgC,CAAC;AAAA,EAC/C;AACA,SAAO,iBAAiB,WAAW,SAAS;AAC5C,QAAM,OAAO,CAAC,QAAgB,SAA0B,CAAC,GAAG,YAAY,QAA4B;AAClG,QAAI,OAAQ,QAAO,QAAQ,OAAO,IAAI,MAAM,qCAAqC,CAAC;AAClF,QAAI,QAAQ,QAAQ,WAAY,QAAO,QAAQ,OAAO,IAAI,MAAM,sCAAsC,CAAC;AACvG,UAAM,YAAY,OAAO,WAAW;AACpC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,WAAW,MAAM;AAAE,gBAAQ,OAAO,SAAS;AAAG,eAAO,IAAI,MAAM,8BAA8B,CAAC;AAAA,MAAE,GAAG,SAAS;AAC5H,cAAQ,IAAI,WAAW,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AAC3D,aAAO,OAAO,YAAY,EAAE,UAAU,OAAO,IAAI,WAAW,QAAQ,GAAG,OAAO,GAAoB,YAAY;AAAA,IAChH,CAAC;AAAA,EACH;AACA,QAAM,QAAQ,MAAqB;AACjC,QAAI,aAAc,QAAO;AACzB,mBAAe,IAAI,QAAc,CAAC,SAAS,WAAW;AACpD,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,YAAM,UAAU,MAAY;AAC1B,YAAI,QAAQ;AAAE,iBAAO,IAAI,MAAM,qCAAqC,CAAC;AAAG;AAAA,QAAO;AAC/E,YAAI,KAAK,IAAI,KAAK,UAAU;AAAE,iBAAO,IAAI,MAAM,8CAA8C,CAAC;AAAG;AAAA,QAAO;AACxG,aAAK,KAAK,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,MAAM,QAAQ,GAAG,MAAM,WAAW,SAAS,GAAG,CAAC;AAAA,MAClF;AACA,cAAQ;AAAA,IACV,CAAC,EAAE,MAAM,CAAC,UAAmB;AAAE,qBAAe;AAAW,YAAM;AAAA,IAAM,CAAC;AACtE,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,KAAK,OAAO;AAChB,UAAI,CAAC,IAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC;AACjE,YAAM,MAAM;AACZ,YAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,KAAK,MAAM,CAAC;AAClD,UAAI,SAAS,UAAU,SAAS,CAAC,OAAO,SAAS,KAAK,KACpD,CAAC,QAAQ,SAAS,MAAM,KAAK,KAAK,CAAC,SAAS,SAAS,MAAM,QAAQ,IAAI;AACvE,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACvD;AACA,aAAO,SAAS,SAAS;AAAA,IAC3B;AAAA,IACA,MAAM,MAAM,OAAO,MAAM,kBAAkB;AACzC,UAAI,CAAC,IAAI,KAAK,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,qBAAqB,QAAQ,SAAS,gBAAgB,IAAI;AAC/F,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACpD;AACA,YAAM,MAAM;AACZ,YAAM,WAAW,MAAM,KAAK,SAAS,EAAE,KAAK,OAAO,OAAO,MAAM,iBAAiB,CAAC;AAClF,UAAI,CAAC,SAAS,SAAS,QAAQ,EAAG,OAAM,IAAI,MAAM,qCAAqC;AACvF,aAAO,EAAE,UAAU,SAAS,SAAS;AAAA,IACvC;AAAA,IACA,MAAM,OAAO,OAAO,kBAAkB;AACpC,UAAI,CAAC,IAAI,KAAK,KAAK,CAAC,SAAS,gBAAgB,EAAG,OAAM,IAAI,MAAM,mCAAmC;AACnG,YAAM,MAAM;AACZ,YAAM,KAAK,UAAU,EAAE,KAAK,OAAO,iBAAiB,CAAC;AAAA,IACvD;AAAA,IACA,UAAU;AACR,eAAS;AACT,aAAO,oBAAoB,WAAW,SAAS;AAC/C,iBAAW,WAAW,QAAQ,OAAO,GAAG;AACtC,qBAAa,QAAQ,OAAO;AAC5B,gBAAQ,OAAO,IAAI,MAAM,qCAAqC,CAAC;AAAA,MACjE;AACA,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AACF;;;AC/LA,SAAS,SAAS,OAAe,OAAuB;AACtD,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,KAAK,wBAAwB,KAAK,KAAK,GAAG;AACrF,UAAM,IAAI,MAAM,2BAA2B,KAAK;AAAA,EAClD;AACA,SAAO,MAAM,KAAK;AACpB;AAEA,SAAS,YAAY,MAA+B,WAAyB;AAC3E,MAAI,KAAK,cAAc,WAAW;AAChC,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,WAAS,KAAK,WAAW,WAAW;AACpC,MAAI,KAAK,aAAa,MAAO,OAAM,IAAI,MAAM,oCAAoC;AACjF,MAAI,CAAC,OAAO,UAAU,KAAK,IAAI,KAAK,KAAK,OAAO,KAAK,KAAK,OAAO,OAAO;AACtE,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AACA,MAAI,CAAC,CAAC,gBAAgB,SAAS,SAAS,UAAU,EAAE,SAAS,KAAK,MAAM,GAAG;AACzE,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,KAAK,GAAG;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,MACE,IAAI,aAAa,YACjB,CAAC,IAAI,YACL,IAAI,aAAa,KAAK,YACtB,IAAI,YACJ,IAAI,YACJ,IAAI,UACJ,IAAI,MACJ;AACA,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACF;AAEA,SAAS,UAAU,QAAyE;AAC1F,MAAI,WAAW,WAAW,WAAW,eAAgB,QAAO;AAC5D,SAAO;AACT;AAOO,SAAS,4BACd,UACA,MACoB;AACpB,QAAM,YAAY,SAAS,SAAS,WAAW,WAAW;AAC1D,cAAY,MAAM,SAAS;AAC3B,QAAM,YAAY,SAAS,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC/D,SAAO;AAAA,IACL,IAAI,SAAS,SAAS,IAAI,IAAI;AAAA,IAC9B,aAAa,SAAS,SAAS,aAAa,aAAa;AAAA,IACzD,MAAM,SAAS,SAAS,MAAM,MAAM;AAAA,IACpC;AAAA,IACA,WAAW,KAAK;AAAA,IAChB,MAAM,KAAK;AAAA,IACX,YAAY,KAAK;AAAA,IACjB,QAAQ,UAAU,KAAK,MAAM;AAAA,IAC7B,WAAW,SAAS,aAAa;AAAA,IACjC,WAAW;AAAA,EACb;AACF;AAiBO,SAAS,yBACd,KACA,OACoB;AACpB,MACE,MAAM,eAAe,IAAI,cACzB,MAAM,WAAW,OACjB,CAAC,4BAA4B,KAAK,MAAM,WAAW,KACnD,CAAC,OAAO,UAAU,MAAM,SAAS,KACjC,MAAM,YAAY,KAClB,CAAC,OAAO,SAAS,KAAK,MAAM,MAAM,SAAS,CAAC,GAC5C;AACA,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,SAAO,EAAE,GAAG,KAAK,QAAQ,SAAS,WAAW,MAAM,UAAU;AAC/D;AAGO,SAAS,2BACd,KACA,MACA,aAAY,oBAAI,KAAK,GAAE,YAAY,GACf;AACpB,cAAY,MAAM,IAAI,SAAS;AAC/B,MAAI,KAAK,cAAc,IAAI,aAAa,KAAK,SAAS,IAAI,MAAM;AAC9D,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY,KAAK;AAAA,IACjB,QAAQ,UAAU,KAAK,MAAM;AAAA,IAC7B;AAAA,EACF;AACF;AAiBA,SAAS,SAAS,OAAuB;AACvC,MAAI,CAAC,4BAA4B,KAAK,KAAK,GAAG;AAC5C,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,MACE,CAAC,2CAA2C,KAAK,KAAK,KACtD,MAAM,MAAM,GAAG,EAAE,KAAK,aAAW,YAAY,QAAQ,YAAY,GAAG,GACpE;AACA,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,SAAO;AACT;AAGO,SAAS,gCACd,SACU;AACV,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,QAAQ,iBAAiB,QAAW;AACtC,UAAM,OAAO,oBAAoB,QAAQ,YAAY;AACrD,WAAO;AAAA,MACL,OAAO,CAAC;AAAA,MACR;AAAA,MACA;AAAA,MACA,WAAW,OAAO;AAAA,MAClB;AAAA,MACA;AAAA,MACA,GAAG,OAAO,MAAM,CAAC;AAAA,IACnB;AAAA,EACF;AACA,QAAM,UAAU,SAAS,QAAQ,WAAW;AAC5C,QAAM,OAAO,QAAQ,WAAW,SAAS,QAAQ,QAAQ,IAAI;AAC7D,SAAO;AAAA,IACL,OAAO,CAAC;AAAA,IACR,GAAI,OAAO,CAAC,SAAS,OAAO,gEAAgE,IAAI,CAAC;AAAA,IACjG;AAAA,IACA,UAAU,UAAU;AAAA,IACpB;AAAA,IACA,GAAG,OAAO,MAAM,CAAC;AAAA,EACnB;AACF;","names":[]}
@@ -0,0 +1,25 @@
1
+ import type { MutationOutcome } from '../web-react/async/use-confirmed-mutation';
2
+ import { CLOUD_ARTIFACT_FRAME_SANDBOX } from './cloud-artifact-origin';
3
+ export { CLOUD_ARTIFACT_FRAME_SANDBOX };
4
+ /** Render a cloud artifact from its view URL on the artifact origin. */
5
+ export declare function CloudArtifactFrame({ src, title, className }: {
6
+ /** A view URL the server minted for this viewer (`createCloudArtifactViewUrl`). */
7
+ src: string;
8
+ title: string;
9
+ className?: string;
10
+ }): import("react").JSX.Element;
11
+ /** A live public link to one artifact. */
12
+ export interface CloudArtifactPublicLink {
13
+ id: string;
14
+ url: string;
15
+ createdAt: string;
16
+ }
17
+ export interface CloudArtifactSharePanelProps {
18
+ link: CloudArtifactPublicLink | null;
19
+ /** Whether this viewer may create or revoke the link (`CLOUD_ARTIFACT_SHARE_ROLE`). */
20
+ canShare: boolean;
21
+ onCreate(): Promise<MutationOutcome<CloudArtifactPublicLink>>;
22
+ onRevoke(link: CloudArtifactPublicLink): Promise<MutationOutcome<unknown>>;
23
+ }
24
+ /** Turn an artifact's public link on, copy it, or turn it off. */
25
+ export declare function CloudArtifactSharePanel({ link: initial, canShare, onCreate, onRevoke }: CloudArtifactSharePanelProps): import("react").JSX.Element;
@@ -0,0 +1,65 @@
1
+ import {
2
+ CLOUD_ARTIFACT_FRAME_SANDBOX
3
+ } from "../chunk-7WEPGGOW.js";
4
+ import "../chunk-O7UX55EU.js";
5
+ import "../chunk-S5SRJJQG.js";
6
+
7
+ // src/workspace-apps/react.tsx
8
+ import { useState } from "react";
9
+ import { Button, CopyButton, Input } from "@tangle-network/ui/primitives";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ function CloudArtifactFrame({ src, title, className }) {
12
+ return /* @__PURE__ */ jsx(
13
+ "iframe",
14
+ {
15
+ src,
16
+ title,
17
+ className: className ?? "h-full w-full border-0 bg-white",
18
+ sandbox: CLOUD_ARTIFACT_FRAME_SANDBOX,
19
+ referrerPolicy: "no-referrer",
20
+ allow: "fullscreen; clipboard-write"
21
+ }
22
+ );
23
+ }
24
+ function CloudArtifactSharePanel({ link: initial, canShare, onCreate, onRevoke }) {
25
+ const [link, setLink] = useState(initial);
26
+ const [busy, setBusy] = useState(false);
27
+ const [error, setError] = useState(null);
28
+ const create = async () => {
29
+ setBusy(true);
30
+ setError(null);
31
+ const outcome = await onCreate();
32
+ setBusy(false);
33
+ if (outcome.succeeded) setLink(outcome.value);
34
+ else setError(outcome.message);
35
+ };
36
+ const revoke = async () => {
37
+ if (!link) return;
38
+ setBusy(true);
39
+ setError(null);
40
+ const outcome = await onRevoke(link);
41
+ setBusy(false);
42
+ if (outcome.succeeded) setLink(null);
43
+ else setError(outcome.message);
44
+ };
45
+ return /* @__PURE__ */ jsxs("section", { "aria-labelledby": "cloud-artifact-share-title", className: "flex flex-col gap-3 text-sm", children: [
46
+ /* @__PURE__ */ jsxs("div", { children: [
47
+ /* @__PURE__ */ jsx("h2", { id: "cloud-artifact-share-title", className: "text-sm font-semibold text-foreground", children: "Public link" }),
48
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: link ? "Anyone with this link can open the page. Turning it off stops the link right away." : "Only members of this workspace can open this page." })
49
+ ] }),
50
+ link ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center", children: [
51
+ /* @__PURE__ */ jsx(Input, { readOnly: true, value: link.url, "aria-label": "Public link", className: "min-w-0 flex-1 text-sm", onFocus: (event) => event.currentTarget.select() }),
52
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 gap-2", children: [
53
+ /* @__PURE__ */ jsx(CopyButton, { text: link.url, label: "Copy link", copiedLabel: "Copied" }),
54
+ canShare && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: busy, onClick: () => void revoke(), children: busy ? "Turning off\u2026" : "Turn off link" })
55
+ ] })
56
+ ] }) : canShare ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", disabled: busy, onClick: () => void create(), children: busy ? "Creating\u2026" : "Create public link" }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Editors and owners can share it." }),
57
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error })
58
+ ] });
59
+ }
60
+ export {
61
+ CLOUD_ARTIFACT_FRAME_SANDBOX,
62
+ CloudArtifactFrame,
63
+ CloudArtifactSharePanel
64
+ };
65
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/workspace-apps/react.tsx"],"sourcesContent":["/**\n * Browser parts of cloud artifacts: the frame that renders one from the\n * artifact origin, and the owner's public-link control. Products supply the\n * minted view URL and the share calls; authorization stays on the server.\n */\nimport { useState } from 'react'\nimport { Button, CopyButton, Input } from '@tangle-network/ui/primitives'\nimport type { MutationOutcome } from '../web-react/async/use-confirmed-mutation'\nimport { CLOUD_ARTIFACT_FRAME_SANDBOX } from './cloud-artifact-origin'\n\nexport { CLOUD_ARTIFACT_FRAME_SANDBOX }\n\n/** Render a cloud artifact from its view URL on the artifact origin. */\nexport function CloudArtifactFrame({ src, title, className }: {\n /** A view URL the server minted for this viewer (`createCloudArtifactViewUrl`). */\n src: string\n title: string\n className?: string\n}) {\n return (\n <iframe\n src={src}\n title={title}\n className={className ?? 'h-full w-full border-0 bg-white'}\n sandbox={CLOUD_ARTIFACT_FRAME_SANDBOX}\n referrerPolicy=\"no-referrer\"\n allow=\"fullscreen; clipboard-write\"\n />\n )\n}\n\n/** A live public link to one artifact. */\nexport interface CloudArtifactPublicLink {\n id: string\n url: string\n createdAt: string\n}\n\nexport interface CloudArtifactSharePanelProps {\n link: CloudArtifactPublicLink | null\n /** Whether this viewer may create or revoke the link (`CLOUD_ARTIFACT_SHARE_ROLE`). */\n canShare: boolean\n onCreate(): Promise<MutationOutcome<CloudArtifactPublicLink>>\n onRevoke(link: CloudArtifactPublicLink): Promise<MutationOutcome<unknown>>\n}\n\n/** Turn an artifact's public link on, copy it, or turn it off. */\nexport function CloudArtifactSharePanel({ link: initial, canShare, onCreate, onRevoke }: CloudArtifactSharePanelProps) {\n const [link, setLink] = useState(initial)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const create = async () => {\n setBusy(true)\n setError(null)\n const outcome = await onCreate()\n setBusy(false)\n if (outcome.succeeded) setLink(outcome.value)\n else setError(outcome.message)\n }\n const revoke = async () => {\n if (!link) return\n setBusy(true)\n setError(null)\n const outcome = await onRevoke(link)\n setBusy(false)\n if (outcome.succeeded) setLink(null)\n else setError(outcome.message)\n }\n\n return (\n <section aria-labelledby=\"cloud-artifact-share-title\" className=\"flex flex-col gap-3 text-sm\">\n <div>\n <h2 id=\"cloud-artifact-share-title\" className=\"text-sm font-semibold text-foreground\">Public link</h2>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {link\n ? 'Anyone with this link can open the page. Turning it off stops the link right away.'\n : 'Only members of this workspace can open this page.'}\n </p>\n </div>\n {link ? (\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <Input readOnly value={link.url} aria-label=\"Public link\" className=\"min-w-0 flex-1 text-sm\" onFocus={(event) => event.currentTarget.select()} />\n <div className=\"flex shrink-0 gap-2\">\n <CopyButton text={link.url} label=\"Copy link\" copiedLabel=\"Copied\" />\n {canShare && (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={busy} onClick={() => void revoke()}>\n {busy ? 'Turning off…' : 'Turn off link'}\n </Button>\n )}\n </div>\n </div>\n ) : canShare ? (\n <div>\n <Button type=\"button\" size=\"sm\" disabled={busy} onClick={() => void create()}>\n {busy ? 'Creating…' : 'Create public link'}\n </Button>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">Editors and owners can share it.</p>\n )}\n {error && <p role=\"alert\" className=\"text-sm text-destructive\">{error}</p>}\n </section>\n )\n}\n"],"mappings":";;;;;;;AAKA,SAAS,gBAAgB;AACzB,SAAS,QAAQ,YAAY,aAAa;AActC,cAoDE,YApDF;AAPG,SAAS,mBAAmB,EAAE,KAAK,OAAO,UAAU,GAKxD;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,WAAW,aAAa;AAAA,MACxB,SAAS;AAAA,MACT,gBAAe;AAAA,MACf,OAAM;AAAA;AAAA,EACR;AAEJ;AAkBO,SAAS,wBAAwB,EAAE,MAAM,SAAS,UAAU,UAAU,SAAS,GAAiC;AACrH,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,OAAO;AACxC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,SAAS,YAAY;AACzB,YAAQ,IAAI;AACZ,aAAS,IAAI;AACb,UAAM,UAAU,MAAM,SAAS;AAC/B,YAAQ,KAAK;AACb,QAAI,QAAQ,UAAW,SAAQ,QAAQ,KAAK;AAAA,QACvC,UAAS,QAAQ,OAAO;AAAA,EAC/B;AACA,QAAM,SAAS,YAAY;AACzB,QAAI,CAAC,KAAM;AACX,YAAQ,IAAI;AACZ,aAAS,IAAI;AACb,UAAM,UAAU,MAAM,SAAS,IAAI;AACnC,YAAQ,KAAK;AACb,QAAI,QAAQ,UAAW,SAAQ,IAAI;AAAA,QAC9B,UAAS,QAAQ,OAAO;AAAA,EAC/B;AAEA,SACE,qBAAC,aAAQ,mBAAgB,8BAA6B,WAAU,+BAC9D;AAAA,yBAAC,SACC;AAAA,0BAAC,QAAG,IAAG,8BAA6B,WAAU,yCAAwC,yBAAW;AAAA,MACjG,oBAAC,OAAE,WAAU,sCACV,iBACG,uFACA,sDACN;AAAA,OACF;AAAA,IACC,OACC,qBAAC,SAAI,WAAU,mDACb;AAAA,0BAAC,SAAM,UAAQ,MAAC,OAAO,KAAK,KAAK,cAAW,eAAc,WAAU,0BAAyB,SAAS,CAAC,UAAU,MAAM,cAAc,OAAO,GAAG;AAAA,MAC/I,qBAAC,SAAI,WAAU,uBACb;AAAA,4BAAC,cAAW,MAAM,KAAK,KAAK,OAAM,aAAY,aAAY,UAAS;AAAA,QAClE,YACC,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,UAAU,MAAM,SAAS,MAAM,KAAK,OAAO,GAC1F,iBAAO,sBAAiB,iBAC3B;AAAA,SAEJ;AAAA,OACF,IACE,WACF,oBAAC,SACC,8BAAC,UAAO,MAAK,UAAS,MAAK,MAAK,UAAU,MAAM,SAAS,MAAM,KAAK,OAAO,GACxE,iBAAO,mBAAc,sBACxB,GACF,IAEA,oBAAC,OAAE,WAAU,iCAAgC,8CAAgC;AAAA,IAE9E,SAAS,oBAAC,OAAE,MAAK,SAAQ,WAAU,4BAA4B,iBAAM;AAAA,KACxE;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.60.195",
3
+ "version": "0.60.197",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [
@@ -433,6 +433,11 @@
433
433
  "import": "./dist/workspace-apps/index.js",
434
434
  "default": "./dist/workspace-apps/index.js"
435
435
  },
436
+ "./workspace-apps/react": {
437
+ "types": "./dist/workspace-apps/react.d.ts",
438
+ "import": "./dist/workspace-apps/react.js",
439
+ "default": "./dist/workspace-apps/react.js"
440
+ },
436
441
  "./workspace-react": {
437
442
  "types": "./dist/workspace-react/index.d.ts",
438
443
  "import": "./dist/workspace-react/index.js",