@panda-agent/panda-app-sdk 0.1.0 → 0.4.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.
@@ -1,9 +1,11 @@
1
1
  export { PANDA_APP_BRIDGE_EVENT, PANDA_APP_BRIDGE_INVOKE, PANDA_APP_BRIDGE_INVOKE_RESULT, } from './protocol.js';
2
2
  export { createAppSdk } from './create-app-sdk.js';
3
3
  export { createIframeBridgeTransport } from './transport/iframe.js';
4
+ export { newRequestId } from './request-id.browser.js';
4
5
  export type * from './types.js';
5
6
  export declare const PandaApp: {
6
- connectIframe(): {
7
+ connect: () => {
8
+ ready: () => Promise<void>;
7
9
  auth: {
8
10
  getCurrentUser: () => Promise<import("./types.js").AppUser | null>;
9
11
  };
@@ -17,6 +19,7 @@ export declare const PandaApp: {
17
19
  scene?: string;
18
20
  preferredModelId?: string;
19
21
  }) => Promise<import("./types.js").ModelListItem | null>;
22
+ chat: (request: import("./types.js").ModelChatRequest) => Promise<import("./types.js").ModelChatResult>;
20
23
  };
21
24
  workspace: {
22
25
  list: (request: {
@@ -28,9 +31,32 @@ export declare const PandaApp: {
28
31
  touch?: boolean;
29
32
  }) => Promise<import("./types.js").WorkspaceListItem>;
30
33
  };
34
+ artifacts: {
35
+ list: (request: import("./types.js").ArtifactsListRequest) => Promise<import("./types.js").ArtifactsListResult>;
36
+ snapshot: (request: import("./types.js").ArtifactsListRequest) => Promise<import("./types.js").ArtifactsSnapshotResult>;
37
+ readText: (request: import("./types.js").ArtifactsReadRequest) => Promise<import("./types.js").ArtifactReadTextResult>;
38
+ readDataUrl: (request: import("./types.js").ArtifactsReadRequest) => Promise<import("./types.js").ArtifactReadDataUrlResult>;
39
+ };
40
+ conversationData: {
41
+ readActivity: (ref: import("./types.js").ConversationRef, query: import("./types.js").ConversationActivityQuery) => Promise<import("./types.js").ConversationActivity>;
42
+ listConversations: (query?: import("./types.js").ConversationListQuery) => Promise<import("./types.js").ConversationDataPage<import("./types.js").ConversationDataListItem>>;
43
+ readRecords: (ref: import("./types.js").ConversationRef, query: import("./types.js").ConversationRecordsQuery) => Promise<import("./types.js").ConversationDataPage<import("./types.js").ConversationDataRecord>>;
44
+ readRecord: (ref: import("./types.js").ConversationRef, recordId: string, query?: import("./types.js").ConversationRecordQuery) => Promise<import("./types.js").ConversationDataRecord>;
45
+ getRuntime: (ref: import("./types.js").ConversationRef, options?: {
46
+ streamingPreview?: boolean;
47
+ }) => Promise<import("./types.js").ConversationRuntimeObservation>;
48
+ listRuntime: (query?: import("./types.js").ConversationListQuery) => Promise<import("./types.js").ConversationDataPage<import("./types.js").ConversationDataListItem>>;
49
+ getLocations: (ref: import("./types.js").ConversationRef) => Promise<import("./types.js").ConversationRecordLocation[]>;
50
+ };
31
51
  conversation: {
52
+ create: (options?: import("./types.js").CreateConversationOptions) => Promise<import("./types.js").CreateConversationResult>;
32
53
  createForApp: (options?: import("./types.js").CreateForAppOptions) => Promise<import("./types.js").CreateForAppResult>;
33
54
  disposeForApp: (conversationId: string) => Promise<unknown>;
55
+ listForApp: (workspaceId?: string) => Promise<unknown>;
56
+ attach: (conversationId: string) => Promise<{
57
+ conversationId: string;
58
+ }>;
59
+ detach: (conversationId: string) => Promise<unknown>;
34
60
  submitMessage: (conversationId: string, prompt: string) => Promise<unknown>;
35
61
  getMessages: (conversationId: string) => Promise<unknown>;
36
62
  subscribe: (options: import("./types.js").ConversationSubscribeOptions & {
@@ -46,9 +72,16 @@ export declare const PandaApp: {
46
72
  }) => Promise<unknown>;
47
73
  };
48
74
  session: {
49
- mount: (conversationId: string, appId: string, replaceConfirmed?: boolean) => Promise<unknown>;
50
- unmount: (conversationId: string) => Promise<unknown>;
51
- getMount: (conversationId: string) => Promise<unknown>;
75
+ getContext: () => Promise<import("./types.js").SessionContext>;
76
+ mount: (conversationId: string, appId: string) => Promise<unknown>;
77
+ unmount: (conversationId: string, appId: string) => Promise<unknown>;
78
+ listMounts: (conversationId: string) => Promise<unknown>;
79
+ };
80
+ todo: {
81
+ get: (conversationId: string) => Promise<import("./types.js").TodoSnapshot>;
82
+ };
83
+ memory: {
84
+ listSessionEntries: (conversationId: string) => Promise<import("./types.js").MemorySessionEntries>;
52
85
  };
53
86
  ingress: {
54
87
  submit: (conversationId: string, prompt: string) => Promise<{
@@ -56,6 +89,37 @@ export declare const PandaApp: {
56
89
  status: string;
57
90
  }>;
58
91
  };
92
+ agent: {
93
+ invokeSkill: (request: import("./types.js").InvokeSkillRequest) => Promise<import("./types.js").InvokeSkillResult>;
94
+ invokeSubagent: (request: import("./types.js").InvokeSubagentRequest) => Promise<import("./types.js").InvokeSubagentResult>;
95
+ cancelSubagent: (invocationId: string) => Promise<import("./types.js").CancelSubagentResult>;
96
+ };
97
+ ui: {
98
+ theme: () => Promise<import("./types.js").AppUiThemeResult>;
99
+ openExternal: (url: string) => Promise<void>;
100
+ sessionSurface: {
101
+ getState: () => Promise<import("./types.js").SessionSurfaceState>;
102
+ show: (options?: {
103
+ activate?: boolean;
104
+ reason?: string;
105
+ }) => Promise<unknown>;
106
+ collapse: () => Promise<unknown>;
107
+ };
108
+ };
109
+ storage: {
110
+ global: {
111
+ get: (key: string) => Promise<import("./types.js").AppJsonValue | undefined>;
112
+ set: (key: string, value: import("./types.js").AppJsonValue) => Promise<unknown>;
113
+ delete: (key: string) => Promise<unknown>;
114
+ list: (prefix?: string) => Promise<import("./types.js").AppStorageListItem[]>;
115
+ };
116
+ session: {
117
+ get: (key: string) => Promise<import("./types.js").AppJsonValue | undefined>;
118
+ set: (key: string, value: import("./types.js").AppJsonValue) => Promise<unknown>;
119
+ delete: (key: string) => Promise<unknown>;
120
+ list: (prefix?: string) => Promise<import("./types.js").AppStorageListItem[]>;
121
+ };
122
+ };
59
123
  tools: {
60
124
  list: (request: {
61
125
  workspace?: import("./types.js").WorkspaceRef;
@@ -100,7 +164,190 @@ export declare const PandaApp: {
100
164
  homeDir: string;
101
165
  pandaVersion: string;
102
166
  appSdkVersion: string;
167
+ capabilities?: {
168
+ invocationToolPolicy?: boolean;
169
+ appPrivateMcp?: boolean;
170
+ };
171
+ }>;
172
+ supports: (requiredVersion: string) => Promise<boolean>;
173
+ };
174
+ dialog: {
175
+ showOpen: (request?: import("./types.js").DialogShowOpenRequest) => Promise<import("./types.js").DialogShowOpenResult>;
176
+ readPickedFile: (request: import("./types.js").DialogReadPickedFileRequest) => Promise<import("./types.js").DialogReadPickedFileResult>;
177
+ releasePick: (pickToken: string) => Promise<unknown>;
178
+ showSave: (request?: import("./types.js").DialogShowSaveRequest) => Promise<import("./types.js").DialogShowSaveResult>;
179
+ writeSaveFile: (request: import("./types.js").DialogWriteSaveFileRequest) => Promise<import("./types.js").DialogWriteSaveFileResult>;
180
+ pickAndReadFile: (options?: import("./types.js").DialogPickAndReadFileOptions) => Promise<import("./types.js").DialogReadPickedFileResult | null>;
181
+ saveFile: (options: import("./types.js").DialogSaveFileOptions) => Promise<string | null>;
182
+ };
183
+ };
184
+ /** @deprecated Use PandaApp.connect() instead. */
185
+ connectIframe: () => {
186
+ ready: () => Promise<void>;
187
+ auth: {
188
+ getCurrentUser: () => Promise<import("./types.js").AppUser | null>;
189
+ };
190
+ model: {
191
+ list: (options?: {
192
+ enabledOnly?: boolean;
193
+ visibleOnly?: boolean;
194
+ scene?: string;
195
+ }) => Promise<import("./types.js").ModelListItem[]>;
196
+ resolveForScene: (request?: {
197
+ scene?: string;
198
+ preferredModelId?: string;
199
+ }) => Promise<import("./types.js").ModelListItem | null>;
200
+ chat: (request: import("./types.js").ModelChatRequest) => Promise<import("./types.js").ModelChatResult>;
201
+ };
202
+ workspace: {
203
+ list: (request: {
204
+ limit: number;
205
+ includeEphemeral?: boolean;
206
+ }) => Promise<import("./types.js").WorkspaceListItem[]>;
207
+ ensureLocal: (request: {
208
+ path: string;
209
+ touch?: boolean;
210
+ }) => Promise<import("./types.js").WorkspaceListItem>;
211
+ };
212
+ artifacts: {
213
+ list: (request: import("./types.js").ArtifactsListRequest) => Promise<import("./types.js").ArtifactsListResult>;
214
+ snapshot: (request: import("./types.js").ArtifactsListRequest) => Promise<import("./types.js").ArtifactsSnapshotResult>;
215
+ readText: (request: import("./types.js").ArtifactsReadRequest) => Promise<import("./types.js").ArtifactReadTextResult>;
216
+ readDataUrl: (request: import("./types.js").ArtifactsReadRequest) => Promise<import("./types.js").ArtifactReadDataUrlResult>;
217
+ };
218
+ conversationData: {
219
+ readActivity: (ref: import("./types.js").ConversationRef, query: import("./types.js").ConversationActivityQuery) => Promise<import("./types.js").ConversationActivity>;
220
+ listConversations: (query?: import("./types.js").ConversationListQuery) => Promise<import("./types.js").ConversationDataPage<import("./types.js").ConversationDataListItem>>;
221
+ readRecords: (ref: import("./types.js").ConversationRef, query: import("./types.js").ConversationRecordsQuery) => Promise<import("./types.js").ConversationDataPage<import("./types.js").ConversationDataRecord>>;
222
+ readRecord: (ref: import("./types.js").ConversationRef, recordId: string, query?: import("./types.js").ConversationRecordQuery) => Promise<import("./types.js").ConversationDataRecord>;
223
+ getRuntime: (ref: import("./types.js").ConversationRef, options?: {
224
+ streamingPreview?: boolean;
225
+ }) => Promise<import("./types.js").ConversationRuntimeObservation>;
226
+ listRuntime: (query?: import("./types.js").ConversationListQuery) => Promise<import("./types.js").ConversationDataPage<import("./types.js").ConversationDataListItem>>;
227
+ getLocations: (ref: import("./types.js").ConversationRef) => Promise<import("./types.js").ConversationRecordLocation[]>;
228
+ };
229
+ conversation: {
230
+ create: (options?: import("./types.js").CreateConversationOptions) => Promise<import("./types.js").CreateConversationResult>;
231
+ createForApp: (options?: import("./types.js").CreateForAppOptions) => Promise<import("./types.js").CreateForAppResult>;
232
+ disposeForApp: (conversationId: string) => Promise<unknown>;
233
+ listForApp: (workspaceId?: string) => Promise<unknown>;
234
+ attach: (conversationId: string) => Promise<{
235
+ conversationId: string;
236
+ }>;
237
+ detach: (conversationId: string) => Promise<unknown>;
238
+ submitMessage: (conversationId: string, prompt: string) => Promise<unknown>;
239
+ getMessages: (conversationId: string) => Promise<unknown>;
240
+ subscribe: (options: import("./types.js").ConversationSubscribeOptions & {
241
+ onEvent?: import("./types.js").AppEventHandler;
242
+ }) => Promise<{
243
+ subscriptionId: string;
244
+ unsubscribe: () => Promise<void>;
245
+ }>;
246
+ awaitTurn: (options: {
247
+ conversationId: string;
248
+ turnId?: string;
249
+ timeoutMs?: number;
250
+ }) => Promise<unknown>;
251
+ };
252
+ session: {
253
+ getContext: () => Promise<import("./types.js").SessionContext>;
254
+ mount: (conversationId: string, appId: string) => Promise<unknown>;
255
+ unmount: (conversationId: string, appId: string) => Promise<unknown>;
256
+ listMounts: (conversationId: string) => Promise<unknown>;
257
+ };
258
+ todo: {
259
+ get: (conversationId: string) => Promise<import("./types.js").TodoSnapshot>;
260
+ };
261
+ memory: {
262
+ listSessionEntries: (conversationId: string) => Promise<import("./types.js").MemorySessionEntries>;
263
+ };
264
+ ingress: {
265
+ submit: (conversationId: string, prompt: string) => Promise<{
266
+ conversationId: string;
267
+ status: string;
268
+ }>;
269
+ };
270
+ agent: {
271
+ invokeSkill: (request: import("./types.js").InvokeSkillRequest) => Promise<import("./types.js").InvokeSkillResult>;
272
+ invokeSubagent: (request: import("./types.js").InvokeSubagentRequest) => Promise<import("./types.js").InvokeSubagentResult>;
273
+ cancelSubagent: (invocationId: string) => Promise<import("./types.js").CancelSubagentResult>;
274
+ };
275
+ ui: {
276
+ theme: () => Promise<import("./types.js").AppUiThemeResult>;
277
+ openExternal: (url: string) => Promise<void>;
278
+ sessionSurface: {
279
+ getState: () => Promise<import("./types.js").SessionSurfaceState>;
280
+ show: (options?: {
281
+ activate?: boolean;
282
+ reason?: string;
283
+ }) => Promise<unknown>;
284
+ collapse: () => Promise<unknown>;
285
+ };
286
+ };
287
+ storage: {
288
+ global: {
289
+ get: (key: string) => Promise<import("./types.js").AppJsonValue | undefined>;
290
+ set: (key: string, value: import("./types.js").AppJsonValue) => Promise<unknown>;
291
+ delete: (key: string) => Promise<unknown>;
292
+ list: (prefix?: string) => Promise<import("./types.js").AppStorageListItem[]>;
293
+ };
294
+ session: {
295
+ get: (key: string) => Promise<import("./types.js").AppJsonValue | undefined>;
296
+ set: (key: string, value: import("./types.js").AppJsonValue) => Promise<unknown>;
297
+ delete: (key: string) => Promise<unknown>;
298
+ list: (prefix?: string) => Promise<import("./types.js").AppStorageListItem[]>;
299
+ };
300
+ };
301
+ tools: {
302
+ list: (request: {
303
+ workspace?: import("./types.js").WorkspaceRef;
304
+ workspaceId?: string;
305
+ }) => Promise<Array<{
306
+ name: string;
307
+ displayName: string;
308
+ description: string;
309
+ kind: string;
310
+ }>>;
311
+ execute: (request: {
312
+ toolName: string;
313
+ args?: Record<string, unknown>;
314
+ workspace?: import("./types.js").WorkspaceRef;
315
+ workspaceId?: string;
316
+ }) => Promise<{
317
+ toolName: string;
318
+ workspaceId: string;
319
+ ok: boolean;
320
+ llmContent?: string;
321
+ error?: {
322
+ message: string;
323
+ type?: string;
324
+ };
325
+ displayData?: unknown;
326
+ }>;
327
+ };
328
+ runtime: {
329
+ now: () => Promise<{
330
+ timestamp: number;
331
+ iso: string;
332
+ timeZone: string;
333
+ utcOffsetMinutes: number;
334
+ }>;
335
+ getHost: () => Promise<{
336
+ platform: string;
337
+ arch: string;
338
+ osName: string;
339
+ hostProduct: string;
340
+ shell: string;
341
+ shellKind: string;
342
+ homeDir: string;
343
+ pandaVersion: string;
344
+ appSdkVersion: string;
345
+ capabilities?: {
346
+ invocationToolPolicy?: boolean;
347
+ appPrivateMcp?: boolean;
348
+ };
103
349
  }>;
350
+ supports: (requiredVersion: string) => Promise<boolean>;
104
351
  };
105
352
  dialog: {
106
353
  showOpen: (request?: import("./types.js").DialogShowOpenRequest) => Promise<import("./types.js").DialogShowOpenResult>;
@@ -3,30 +3,75 @@ var PANDA_APP_BRIDGE_INVOKE = "panda-app-bridge:invoke";
3
3
  var PANDA_APP_BRIDGE_INVOKE_RESULT = "panda-app-bridge:invoke-result";
4
4
  var PANDA_APP_BRIDGE_EVENT = "panda-app-bridge:event";
5
5
 
6
+ // src/semver.ts
7
+ function compareSemver(v1, v2) {
8
+ const parse = (v) => {
9
+ const parts = v.split(".");
10
+ const nums = [];
11
+ for (let i = 0; i < 3; i++) {
12
+ const n = parseInt(parts[i] ?? "0", 10);
13
+ nums.push(Number.isFinite(n) ? n : 0);
14
+ }
15
+ return nums;
16
+ };
17
+ const a = parse(v1);
18
+ const b = parse(v2);
19
+ for (let i = 0; i < 3; i++) {
20
+ if (a[i] > b[i]) return 1;
21
+ if (a[i] < b[i]) return -1;
22
+ }
23
+ return 0;
24
+ }
25
+
6
26
  // src/create-app-sdk.ts
7
27
  function createAppSdk(transport) {
28
+ const policyPreflights = /* @__PURE__ */ new Map();
29
+ let ready;
8
30
  return {
31
+ /** Report this side ready. Initialization and the first usable render must finish before calling. */
32
+ ready: () => ready ??= transport.ready ? transport.ready() : Promise.reject(new Error("This Panda host does not support App readiness")),
9
33
  auth: {
10
34
  getCurrentUser: () => transport.invoke("auth.getCurrentUser", [])
11
35
  },
12
36
  model: {
13
37
  list: (options) => transport.invoke("model.list", [options ?? {}]),
14
- resolveForScene: (request) => transport.invoke("model.resolveForScene", [request ?? {}])
38
+ resolveForScene: (request) => transport.invoke("model.resolveForScene", [request ?? {}]),
39
+ chat: (request) => transport.invoke("model.chat", [request])
15
40
  },
16
41
  workspace: {
17
42
  list: (request) => transport.invoke("workspace.list", [request]),
18
43
  ensureLocal: (request) => transport.invoke("workspace.ensureLocal", [request])
19
44
  },
45
+ artifacts: {
46
+ list: (request) => transport.invoke("artifacts.list", [request]),
47
+ snapshot: (request) => transport.invoke("artifacts.snapshot", [request]),
48
+ readText: (request) => transport.invoke("artifacts.readText", [request]),
49
+ readDataUrl: (request) => transport.invoke("artifacts.readDataUrl", [request])
50
+ },
51
+ conversationData: {
52
+ readActivity: (ref, query) => transport.invoke("conversationData.readActivity", [ref, query]),
53
+ listConversations: (query) => transport.invoke("conversationData.listConversations", [query ?? {}]),
54
+ readRecords: (ref, query) => transport.invoke("conversationData.readRecords", [ref, query]),
55
+ readRecord: (ref, recordId, query) => transport.invoke("conversationData.readRecord", [ref, recordId, query ?? {}]),
56
+ getRuntime: (ref, options) => transport.invoke("conversationData.getRuntime", [ref, options ?? {}]),
57
+ listRuntime: (query) => transport.invoke("conversationData.listRuntime", [query ?? {}]),
58
+ getLocations: (ref) => transport.invoke("conversationData.getLocations", [ref])
59
+ },
20
60
  conversation: {
61
+ create: (options) => transport.invoke("conversation.create", [options ?? {}]),
21
62
  createForApp: (options) => transport.invoke("conversation.createForApp", [options ?? {}]),
22
63
  disposeForApp: (conversationId) => transport.invoke("conversation.disposeForApp", [conversationId]),
64
+ listForApp: (workspaceId) => transport.invoke("conversation.listForApp", workspaceId === void 0 ? [] : [workspaceId]),
65
+ attach: (conversationId) => transport.invoke("conversation.attach", [conversationId]),
66
+ detach: (conversationId) => transport.invoke("conversation.detach", [conversationId]),
23
67
  submitMessage: (conversationId, prompt) => transport.invoke("conversation.submitMessage", [conversationId, prompt]),
24
68
  getMessages: (conversationId) => transport.invoke("conversation.getMessages", [conversationId]),
25
69
  subscribe: async (options) => {
26
- const result = await transport.invoke("conversation.subscribe", [options]);
70
+ const { onEvent, ...request } = options;
71
+ const result = await transport.invoke("conversation.subscribe", [request]);
27
72
  let offEvent;
28
- if (options.onEvent) {
29
- offEvent = transport.subscribe(result.subscriptionId, options.onEvent);
73
+ if (onEvent) {
74
+ offEvent = transport.subscribe(result.subscriptionId, onEvent);
30
75
  }
31
76
  return {
32
77
  subscriptionId: result.subscriptionId,
@@ -39,20 +84,85 @@ function createAppSdk(transport) {
39
84
  awaitTurn: (options) => transport.invoke("conversation.awaitTurn", [options])
40
85
  },
41
86
  session: {
42
- mount: (conversationId, appId, replaceConfirmed) => transport.invoke("mountSession", [conversationId, appId, replaceConfirmed === true]),
43
- unmount: (conversationId) => transport.invoke("unmountSession", [conversationId]),
44
- getMount: (conversationId) => transport.invoke("getSessionMount", [conversationId])
87
+ getContext: () => transport.invoke("session.getContext", []),
88
+ mount: (conversationId, appId) => transport.invoke("mountSession", [conversationId, appId]),
89
+ unmount: (conversationId, appId) => transport.invoke("unmountSession", [conversationId, appId]),
90
+ listMounts: (conversationId) => transport.invoke("listSessionMounts", [conversationId])
91
+ },
92
+ todo: {
93
+ get: (conversationId) => transport.invoke("todo.get", [conversationId])
94
+ },
95
+ memory: {
96
+ listSessionEntries: (conversationId) => transport.invoke("memory.listSessionEntries", [conversationId])
45
97
  },
46
98
  ingress: {
47
99
  submit: (conversationId, prompt) => transport.invoke("ingress.submit", [conversationId, prompt])
48
100
  },
101
+ agent: {
102
+ invokeSkill: (request) => transport.invoke("agent.invokeSkill", [request]),
103
+ invokeSubagent: async (request) => {
104
+ const restricted = request.allowedTools !== void 0 || request.readableFiles !== void 0;
105
+ const id = restricted ? request.invocationId?.trim() : void 0;
106
+ if (id && policyPreflights.has(id)) throw new Error("Duplicate subagent invocationId");
107
+ const pending = { cancelled: false };
108
+ if (id) policyPreflights.set(id, pending);
109
+ try {
110
+ if (restricted) {
111
+ const host = await transport.invoke("runtime.getHost", []);
112
+ if (pending.cancelled) throw new Error("Subagent cancelled before dispatch");
113
+ if (host?.capabilities?.invocationToolPolicy !== true) {
114
+ throw Object.assign(new Error("Host does not support invocation tool policies; refusing an unrestricted fallback"), { code: "APP_HOST_CAPABILITY_REQUIRED" });
115
+ }
116
+ }
117
+ if (id) policyPreflights.delete(id);
118
+ return await transport.invoke("agent.invokeSubagent", [request]);
119
+ } finally {
120
+ if (id && policyPreflights.get(id) === pending) policyPreflights.delete(id);
121
+ }
122
+ },
123
+ cancelSubagent: async (invocationId) => {
124
+ const pending = policyPreflights.get(invocationId.trim());
125
+ if (pending) {
126
+ pending.cancelled = true;
127
+ return { cancelled: true };
128
+ }
129
+ return transport.invoke("agent.cancelSubagent", [{ invocationId }]);
130
+ }
131
+ },
132
+ ui: {
133
+ theme: () => transport.invoke("ui.theme", []),
134
+ openExternal: (url) => transport.invoke("ui.openExternal", [{ url }]),
135
+ sessionSurface: {
136
+ getState: () => transport.invoke("ui.sessionSurface.getState", []),
137
+ show: (options) => transport.invoke("ui.sessionSurface.show", [options ?? {}]),
138
+ collapse: () => transport.invoke("ui.sessionSurface.collapse", [])
139
+ }
140
+ },
141
+ storage: {
142
+ global: {
143
+ get: (key) => transport.invoke("storage.global.get", [key]),
144
+ set: (key, value) => transport.invoke("storage.global.set", [key, value]),
145
+ delete: (key) => transport.invoke("storage.global.delete", [key]),
146
+ list: (prefix) => transport.invoke("storage.global.list", prefix === void 0 ? [] : [prefix])
147
+ },
148
+ session: {
149
+ get: (key) => transport.invoke("storage.session.get", [key]),
150
+ set: (key, value) => transport.invoke("storage.session.set", [key, value]),
151
+ delete: (key) => transport.invoke("storage.session.delete", [key]),
152
+ list: (prefix) => transport.invoke("storage.session.list", prefix === void 0 ? [] : [prefix])
153
+ }
154
+ },
49
155
  tools: {
50
156
  list: (request) => transport.invoke("tools.list", [request]),
51
157
  execute: (request) => transport.invoke("tools.execute", [request])
52
158
  },
53
159
  runtime: {
54
160
  now: () => transport.invoke("runtime.now", []),
55
- getHost: () => transport.invoke("runtime.getHost", [])
161
+ getHost: () => transport.invoke("runtime.getHost", []),
162
+ supports: async (requiredVersion) => {
163
+ const host = await transport.invoke("runtime.getHost", []);
164
+ return compareSemver(host.appSdkVersion, requiredVersion) >= 0;
165
+ }
56
166
  },
57
167
  dialog: {
58
168
  showOpen: (request) => transport.invoke("dialog.showOpen", [request ?? {}]),
@@ -96,18 +206,42 @@ function createAppSdk(transport) {
96
206
  }
97
207
 
98
208
  // src/request-id.browser.ts
209
+ function randomByte() {
210
+ return Math.floor(Math.random() * 256);
211
+ }
212
+ function uuidFromBytes(bytes) {
213
+ bytes[6] = bytes[6] & 15 | 64;
214
+ bytes[8] = bytes[8] & 63 | 128;
215
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
216
+ return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
217
+ }
99
218
  function newRequestId() {
100
- return crypto.randomUUID();
219
+ const webCrypto = globalThis.crypto;
220
+ if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
221
+ const bytes = new Uint8Array(16);
222
+ if (typeof webCrypto?.getRandomValues === "function") webCrypto.getRandomValues(bytes);
223
+ else bytes.set(Array.from({ length: bytes.length }, randomByte));
224
+ return uuidFromBytes(bytes);
101
225
  }
102
226
 
103
227
  // src/transport/shared/pending-invoke.ts
104
228
  function createPendingInvoke(newRequestId2) {
105
229
  const pending = /* @__PURE__ */ new Map();
106
- function invoke(postMessage, invokeType, path, args) {
230
+ function invoke(postMessage, invokeType, path, args, timeoutMs) {
107
231
  const requestId = newRequestId2();
108
232
  return new Promise((resolve, reject) => {
109
- pending.set(requestId, { resolve, reject });
110
- postMessage({ type: invokeType, requestId, path, args });
233
+ const timer = timeoutMs ? setTimeout(() => {
234
+ pending.delete(requestId);
235
+ reject(new Error("App ready acknowledgement timed out; update or reopen the Panda host"));
236
+ }, timeoutMs) : void 0;
237
+ pending.set(requestId, { resolve, reject, timer });
238
+ try {
239
+ postMessage({ type: invokeType, requestId, path, args });
240
+ } catch (error) {
241
+ pending.delete(requestId);
242
+ clearTimeout(timer);
243
+ reject(error);
244
+ }
111
245
  });
112
246
  }
113
247
  function handleInvokeResult(message) {
@@ -119,6 +253,7 @@ function createPendingInvoke(newRequestId2) {
119
253
  return;
120
254
  }
121
255
  pending.delete(message.requestId);
256
+ clearTimeout(entry.timer);
122
257
  if (message.ok) {
123
258
  entry.resolve(message.result);
124
259
  } else {
@@ -162,15 +297,33 @@ function createSubscriptionEvents() {
162
297
  }
163
298
 
164
299
  // src/transport/iframe.ts
300
+ var documentIds = /* @__PURE__ */ new WeakMap();
165
301
  function createIframeBridgeTransport() {
166
302
  if (typeof window === "undefined") {
167
303
  throw new Error("[app-sdk] iframe bridge requires browser context");
168
304
  }
169
305
  const { invoke: createInvoke, handleInvokeResult } = createPendingInvoke(newRequestId);
170
306
  const subscriptions = createSubscriptionEvents();
307
+ const document = window.document ?? window;
308
+ let documentId = documentIds.get(document);
309
+ if (!documentId) {
310
+ documentId = newRequestId();
311
+ documentIds.set(document, documentId);
312
+ }
313
+ const bootId = new URL(window.location.href).searchParams.get("pandaAppBoot");
171
314
  window.addEventListener("message", (event) => {
172
315
  const data = event.data;
173
- if (!data || typeof data !== "object" || !data.type) {
316
+ if (event.source !== window.parent || !data || typeof data !== "object" || !data.type) {
317
+ return;
318
+ }
319
+ if (data.type === "panda-app-bridge:document-probe") {
320
+ const probe = data;
321
+ if (probe.bootId === bootId && typeof probe.challenge === "string") window.parent.postMessage({
322
+ type: "panda-app-bridge:document-identity",
323
+ bootId,
324
+ documentId,
325
+ challenge: probe.challenge
326
+ }, "*");
174
327
  return;
175
328
  }
176
329
  if (data.type === PANDA_APP_BRIDGE_EVENT && data.subscriptionId && data.event) {
@@ -181,7 +334,34 @@ function createIframeBridgeTransport() {
181
334
  handleInvokeResult(data);
182
335
  }
183
336
  });
337
+ const connected = bootId ? createInvoke(
338
+ (message) => window.parent.postMessage(message, "*"),
339
+ PANDA_APP_BRIDGE_INVOKE,
340
+ "app.frontend.connect",
341
+ [{ bootId, documentId }],
342
+ 3e4
343
+ ) : void 0;
344
+ void connected?.catch(() => void 0);
345
+ window.addEventListener("pagehide", () => {
346
+ if (bootId) window.parent.postMessage({
347
+ type: PANDA_APP_BRIDGE_INVOKE,
348
+ requestId: newRequestId(),
349
+ path: "app.frontend.unloading",
350
+ args: [{ bootId, documentId }]
351
+ }, "*");
352
+ });
184
353
  return {
354
+ ready: async () => {
355
+ if (!connected) throw new Error("App readiness requires a current Panda frontend host");
356
+ await connected;
357
+ await createInvoke(
358
+ (message) => window.parent.postMessage(message, "*"),
359
+ PANDA_APP_BRIDGE_INVOKE,
360
+ "app.ready",
361
+ [{ bootId, documentId }],
362
+ 3e4
363
+ );
364
+ },
185
365
  invoke(path, args) {
186
366
  return createInvoke(
187
367
  (message) => {
@@ -197,10 +377,11 @@ function createIframeBridgeTransport() {
197
377
  }
198
378
 
199
379
  // src/browser.ts
380
+ var connect = () => createAppSdk(createIframeBridgeTransport());
200
381
  var PandaApp = {
201
- connectIframe() {
202
- return createAppSdk(createIframeBridgeTransport());
203
- }
382
+ connect,
383
+ /** @deprecated Use PandaApp.connect() instead. */
384
+ connectIframe: connect
204
385
  };
205
386
  export {
206
387
  PANDA_APP_BRIDGE_EVENT,
@@ -208,5 +389,6 @@ export {
208
389
  PANDA_APP_BRIDGE_INVOKE_RESULT,
209
390
  PandaApp,
210
391
  createAppSdk,
211
- createIframeBridgeTransport
392
+ createIframeBridgeTransport,
393
+ newRequestId
212
394
  };