@onereach/idw-apps 0.1.2-beta.6.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.
Files changed (39) hide show
  1. package/README.md +411 -0
  2. package/dist/app/createIdwApp.d.ts +104 -0
  3. package/dist/app/createIdwApp.d.ts.map +1 -0
  4. package/dist/app/createIdwApp.js +151 -0
  5. package/dist/app/createIdwApp.js.map +1 -0
  6. package/dist/app/index.d.ts +15 -0
  7. package/dist/app/index.d.ts.map +1 -0
  8. package/dist/app/index.js +3 -0
  9. package/dist/app/index.js.map +1 -0
  10. package/dist/app/installIdwAppApi.d.ts +98 -0
  11. package/dist/app/installIdwAppApi.d.ts.map +1 -0
  12. package/dist/app/installIdwAppApi.js +314 -0
  13. package/dist/app/installIdwAppApi.js.map +1 -0
  14. package/dist/createIdwAppBridge.d.ts +13 -0
  15. package/dist/createIdwAppBridge.d.ts.map +1 -0
  16. package/dist/createIdwAppBridge.js +555 -0
  17. package/dist/createIdwAppBridge.js.map +1 -0
  18. package/dist/hostStateChannel.d.ts +51 -0
  19. package/dist/hostStateChannel.d.ts.map +1 -0
  20. package/dist/hostStateChannel.js +104 -0
  21. package/dist/hostStateChannel.js.map +1 -0
  22. package/dist/index.d.ts +9 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +6 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/protocol.d.ts +81 -0
  27. package/dist/protocol.d.ts.map +1 -0
  28. package/dist/protocol.js +67 -0
  29. package/dist/protocol.js.map +1 -0
  30. package/dist/stateStore.d.ts +23 -0
  31. package/dist/stateStore.d.ts.map +1 -0
  32. package/dist/stateStore.js +70 -0
  33. package/dist/stateStore.js.map +1 -0
  34. package/dist/types.d.ts +268 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +44 -0
  39. package/skills/idw-app-development/SKILL.md +458 -0
@@ -0,0 +1,458 @@
1
+ ---
2
+ name: idw-app-development
3
+ description: Use when building IDW-compatible MCP Apps, generic IDW Web apps, or adapting apps to @onereach/idw-apps, including widget state, lazy auth, IDW actions, agent context, RWC boundaries, and OpenAI/standard MCP compatibility.
4
+ ---
5
+
6
+ # IDW App Development
7
+
8
+ Use this skill when creating an app that will run inside IDW through `@onereach/idw-apps`. This covers MCP Apps and generic IDW Web apps. Keep the app protocol close to MCP Apps; do not invent a custom bridge unless the package already exposes it.
9
+
10
+ For full API details, read the package `README.md`. This skill captures the implementation decisions and app-building defaults.
11
+
12
+ ## One Skill Or Many
13
+
14
+ Use this single skill for now. MCP Apps and IDW Web apps share the same app primitives:
15
+
16
+ - durable widget state
17
+ - optional lazy `orToken`
18
+ - optional host actions
19
+ - optional agent/model context updates
20
+ - app/backend-owned authoritative data
21
+
22
+ “IDW Web app” means the app is not backed by an MCP server or tool resource. It
23
+ still uses `createIdwApp()` and the MCP Apps handshake as the universal IDW app
24
+ protocol; only its business-data backend differs.
25
+
26
+ Split into separate skills only if one app type grows a truly different build workflow.
27
+
28
+ ## Choose The App Type
29
+
30
+ | App type | Use when | Main differences |
31
+ | --- | --- | --- |
32
+ | MCP App | An MCP server exposes an interactive UI resource or URL. | Prefer standard MCP Apps APIs for tools/resources/prompts. Use IDW additions only when the app needs IDW widget state, IDW actions, lazy auth, or agent context. |
33
+ | Standard external MCP App | The user brings an external MCP server/app built directly with `@modelcontextprotocol/ext-apps`. | It must work in IDW without importing `@onereach/idw-apps/app`. Do not require IDW-only messages for basic MCP behavior. |
34
+ | IDW Web app | A hosted IDW user app embedded or opened by IDW, often with its own backend. | Same protocol regardless of where IDW renders it. Request `orToken` lazily when the app needs IDW/API auth. Store only restore hints in widget state. Use `surface` only as a layout/context hint. |
35
+ | RWC app | Existing Rich Web Chat app/skill. | Treat as separate unless the user explicitly asks to migrate it. RWC continues using `pageData`/`orToken` because its backend expects data through its UI. |
36
+
37
+ ## Preferred App Import
38
+
39
+ For new IDW-aware apps, import from `@onereach/idw-apps/app`:
40
+
41
+ ```ts
42
+ import { createIdwApp } from '@onereach/idw-apps/app';
43
+ ```
44
+
45
+ IDW app authors should not need to install `@modelcontextprotocol/ext-apps` separately. The IDW `App` extends the MCP Apps `App`, so standard MCP methods remain available.
46
+
47
+ External MCP apps built directly with `@modelcontextprotocol/ext-apps` should still work with the IDW host, but they will not have the `window.idw.app` helpers unless they include this package or an equivalent bootstrap.
48
+
49
+ Treat current OpenAI Apps SDK apps as standard MCP Apps when they use the MCP
50
+ Apps bridge. Do not assume IDW implements the complete ChatGPT-only
51
+ `window.openai` runtime. IDW currently aliases widget state only; feature-detect
52
+ any optional `window.openai` methods and keep core behavior on MCP Apps.
53
+
54
+ New IDW apps should not call `installIdwAppApi()` directly. It is reserved for
55
+ custom bootstraps and legacy integrations.
56
+
57
+ ## Choose The API By Direction
58
+
59
+ | Intent | API |
60
+ | --- | --- |
61
+ | App calls its MCP server | `app.callServerTool()` |
62
+ | IDW host/agent calls a live app tool | app `onlisttools` / `oncalltool`; host `listAppTools()` / `callAppTool()` |
63
+ | App requests an IDW host/platform action | `app.callIdwAction()` |
64
+ | App gives concise context to the agent | `app.updateAgentContext()` |
65
+ | App saves a lightweight restore hint | `app.state.setWidgetState()` |
66
+
67
+ Do not invent another action channel for app-provided tools.
68
+
69
+ ## Basic IDW App Skeleton
70
+
71
+ ```ts
72
+ import { createIdwApp } from '@onereach/idw-apps/app';
73
+
74
+ const app = createIdwApp({
75
+ appInfo: {
76
+ name: 'My IDW App',
77
+ version: '1.0.0',
78
+ },
79
+ });
80
+
81
+ app.ontoolinput = ({ arguments: args }) => {
82
+ // Standard MCP Apps tool input notification from the host/model turn.
83
+ };
84
+
85
+ app.ontoolresult = (result) => {
86
+ // The latest tool result (especially structuredContent) is authoritative
87
+ // business data. Store it and render from it.
88
+ };
89
+
90
+ app.state.subscribeWidgetState((state) => {
91
+ // Apply only route, selected ID, active tab, or another restore/view hint.
92
+ });
93
+
94
+ await app.connect();
95
+
96
+ const widgetState = app.state.getWidgetState();
97
+ const hostContext = app.getHostContext();
98
+ const hostCapabilities = app.getHostCapabilities();
99
+ ```
100
+
101
+ `capabilities` is optional. Add `capabilities.tools` only when the app itself
102
+ exposes MCP-style tools to the host.
103
+
104
+ Use `app.dispose()` when the app is being torn down by your framework.
105
+
106
+ ## Widget State
107
+
108
+ Widget state is durable UI restoration state owned by the host. It is not the app's authoritative data store.
109
+
110
+ The host stores widget state for the durable app instance, not for a single tool call. On the next app render or reload, it can arrive before or after the MCP tool result, and there may be no saved widget state at all. Connect immediately and render authoritative tool results without waiting for state.
111
+
112
+ Use these names in new code:
113
+
114
+ ```ts
115
+ app.state.getWidgetState();
116
+ app.state.setWidgetState({ route: '/lessons/lesson-3', selectedLessonId: 'lesson-3' });
117
+ app.state.subscribeWidgetState((state) => {
118
+ // sync local UI
119
+ });
120
+ ```
121
+
122
+ Use `whenWidgetStateReady()` only after `app.connect()` when a restore hint is specifically needed. It resolves on a host reply or the fallback timeout; it is not a startup prerequisite.
123
+
124
+ Deprecated aliases may exist for compatibility: `getState`, `setState`, `whenStateReady`, `subscribeState`. Do not use them in new apps.
125
+
126
+ `setWidgetState(state)` replaces the whole widget-state object. Keep it JSON-serializable, small, and non-sensitive.
127
+
128
+ Good widget state:
129
+
130
+ ```ts
131
+ {
132
+ route: '/lessons/lesson-3/remediation',
133
+ selectedLessonId: 'lesson-3',
134
+ activeTab: 'quiz',
135
+ draftId: 'draft-abc',
136
+ currentStep: 2
137
+ }
138
+ ```
139
+
140
+ Bad widget state:
141
+
142
+ ```ts
143
+ {
144
+ orToken: '...',
145
+ fullApiResponse: {},
146
+ entireUserProfile: {},
147
+ largeDataset: [],
148
+ lastToolResult: {}
149
+ }
150
+ ```
151
+
152
+ For apps with their own backend, widget state should contain restore hints such as IDs/routes/tabs. On reload, use those hints to fetch authoritative data from the app backend.
153
+
154
+ For small apps without a backend, such as a simple game, it is acceptable to store full lightweight local state if it is small, non-sensitive, and JSON-serializable.
155
+
156
+ Do not block UI on persistence. The host updates local state immediately and persists asynchronously.
157
+
158
+ Avoid writing every tiny transient interaction. Persist restore-critical changes such as route, selected IDs, active tab, draft ID, current workflow step, or compact local state for small backend-less apps.
159
+
160
+ ## Rendering And State Ownership
161
+
162
+ Keep these layers separate:
163
+
164
+ - **Tool result** — authoritative business state. A tool that renders or updates the app should return the complete current business snapshot in `structuredContent`; every new result replaces the previous snapshot.
165
+ - **Widget state** — lightweight UI/restore hint. Reapply it on top of the latest business snapshot only when it is still valid.
166
+ - **Local component state** — ephemeral rendering details.
167
+
168
+ Tool-result and widget-state messages have no guaranteed relative order. A new app must work when the first render has only tool input/result and no widget state. On a reload, it may render a persisted tool result first and receive `{ sessionId, route }` later.
169
+
170
+ If a restore hint causes a backend fetch, keep that request from overwriting a newer tool-result snapshot. Use your framework's normal cancellation or request-generation pattern when both can race.
171
+
172
+ ## Backend Data
173
+
174
+ Use this split:
175
+
176
+ - IDW widget state: lightweight restore checkpoint.
177
+ - App backend/session: authoritative business or game data.
178
+ - Agent context: selected model-visible activity/context only.
179
+ - IDW auth token: requested lazily as a user identity token and never persisted in widget state.
180
+
181
+ When restoring:
182
+
183
+ 1. Register tool-result and widget-state handlers.
184
+ 2. Call `app.connect()` immediately.
185
+ 3. Render every authoritative tool result as it arrives.
186
+ 4. Apply widget state as a route/selection/session hint whenever it arrives.
187
+ 5. If the hint needs backend hydration, request auth only if needed and fetch without replacing a newer tool result.
188
+
189
+ ## Lazy Auth
190
+
191
+ The only current token type is `orToken`. It is a user identity token issued for
192
+ one web app session. It lets the embedded app call IDW/platform APIs as the
193
+ current user, subject to the platform's normal authorization. Request it only
194
+ when needed:
195
+
196
+ ```ts
197
+ const auth = await app.getAuthToken();
198
+
199
+ // Use auth.token for app/API calls that require IDW identity.
200
+ ```
201
+
202
+ The host must opt in with `auth.allowTokenRequest: true`. If it uses
203
+ `allowedActions`, it must also include `getAuthToken` (or its configured token
204
+ action name). Otherwise `getAuthToken` returns a normal MCP-style error result.
205
+
206
+ The host may provide a static `auth.orToken` or an async
207
+ `auth.getOrToken(runtimeContext)` provider. The provider is called lazily for
208
+ each actual host token-action request and receives the current runtime context;
209
+ it is never exposed through MCP host context. App-side `getAuthToken()` still
210
+ caches normal requests, while `forceRefresh: true` sends another host request.
211
+
212
+ `app.getAuthToken()` caches the in-memory token for the current app instance, so
213
+ calling `app.validateAccess()` and then making app API requests, or doing those
214
+ in the opposite order, does not require duplicate host token requests. Use
215
+ `forceRefresh: true` only when the product explicitly needs a new session token.
216
+
217
+ The package does not verify `orToken` and should treat it as opaque. Verification belongs to IDW/platform APIs or the app backend using the platform's normal server-side authorization flow.
218
+
219
+ Do not put tokens into widget state, model/agent context, logs, URLs, or localStorage unless the product explicitly requires and reviews it.
220
+
221
+ ## Web App Access Validation
222
+
223
+ For Web Apps/skills replacing `idw-skill`, validate access from the app after
224
+ the MCP Apps handshake:
225
+
226
+ ```ts
227
+ await app.connect();
228
+
229
+ const { skill, user } = await app.validateAccess({
230
+ url: window.location.href,
231
+ });
232
+ ```
233
+
234
+ `validateAccess()` keeps the old app-side request model: it reads
235
+ `hostContext.idw.idwId` and `hostContext.idw.auth.apiBaseUrl`, requests a lazy
236
+ `orToken` through the cached `app.getAuthToken()` helper, then calls:
237
+
238
+ ```text
239
+ POST {apiBaseUrl}/idw/{idwId}/skills/access
240
+ ```
241
+
242
+ The host must provide `idwId`, `auth.apiBaseUrl`, `auth.allowTokenRequest: true`,
243
+ and, when `allowedActions` is set, the configured token action name. Do not use
244
+ host-mediated `callIdwAction()` access checks when the product requires the app
245
+ to perform this request itself.
246
+
247
+ ## MCP Server Tools And Resources
248
+
249
+ Use standard MCP Apps methods for MCP server capabilities:
250
+
251
+ - `callServerTool()` for MCP server tools.
252
+ - resource/prompt list and read APIs for MCP resources/prompts.
253
+ - `updateModelContext()` for standard MCP model context updates.
254
+
255
+ Use IDW methods only for IDW-specific behavior:
256
+
257
+ - `callIdwAction()` for host/platform actions.
258
+ - `updateAgentContext()` as a semantic IDW alias for `updateModelContext()`.
259
+ - widget-state helpers for durable UI restoration.
260
+
261
+ If an MCP app needs external scripts, styles, or assets, define UI CSP metadata on the app resource, not on the tool. Include the needed asset origins in the resource UI CSP. Do not assume arbitrary external asset origins will be allowed.
262
+
263
+ For local development, localhost app/resource URLs can be valid if the host environment is configured to allow them.
264
+
265
+ ## App-Provided Agent Tools
266
+
267
+ An app can expose standard MCP tools for IDW to make available to the agent.
268
+ Use this for either UI actions (`openRequest`, `selectTab`) or app/API actions
269
+ (`listTasks`, `createTask`); the app owns the tool implementation.
270
+
271
+ This is the reverse of `callServerTool()`:
272
+
273
+ - `callServerTool()` is app → MCP server.
274
+ - `onlisttools` / `oncalltool` is IDW host/agent → app.
275
+
276
+ Declare tool capability and register handlers before connecting:
277
+
278
+ ```ts
279
+ const app = createIdwApp({
280
+ appInfo: {
281
+ name: 'Tasks',
282
+ version: '1.0.0',
283
+ },
284
+ capabilities: {
285
+ tools: {},
286
+ },
287
+ });
288
+
289
+ app.onlisttools = async () => ({
290
+ tools: [{
291
+ name: 'listTasks',
292
+ description: 'List tasks visible to the current user.',
293
+ inputSchema: {
294
+ type: 'object',
295
+ properties: {},
296
+ },
297
+ annotations: {
298
+ readOnlyHint: true,
299
+ },
300
+ }],
301
+ });
302
+
303
+ app.oncalltool = async ({ name, arguments: args }) => {
304
+ if (name !== 'listTasks') {
305
+ return { content: [], isError: true };
306
+ }
307
+
308
+ const tasks = await appBackend.listTasks(args);
309
+ return {
310
+ content: [{ type: 'text', text: `Found ${tasks.length} tasks.` }],
311
+ structuredContent: { tasks },
312
+ };
313
+ };
314
+
315
+ await app.connect();
316
+ ```
317
+
318
+ Tool descriptions, input schemas, output, and annotations are part of the
319
+ agent-facing contract. Keep them concise and accurate. Mark read-only tools
320
+ with `readOnlyHint`; provide honest destructive/idempotency annotations for
321
+ side effects. The IDW host decides whether a declared tool is agent-visible or
322
+ requires confirmation.
323
+
324
+ App-provided tools run in the live iframe. For a backend capability that must
325
+ work when the app is not mounted, expose it as a durable MCP or backend tool
326
+ instead. Do not put raw backend URLs, credentials, or authorization policy in
327
+ the tool manifest; use the app's normal backend/auth flow inside the handler.
328
+
329
+ ## IDW Host Actions
330
+
331
+ Use `callIdwAction()` for host-defined platform actions:
332
+
333
+ ```ts
334
+ const result = await app.callIdwAction({
335
+ name: 'openSkill',
336
+ arguments: {
337
+ slug: 'generate-image',
338
+ },
339
+ });
340
+
341
+ if (result.isError) {
342
+ // Show a local app error state if useful.
343
+ }
344
+ ```
345
+
346
+ Action names are host-defined strings. Do not assume built-in actions other than `getAuthToken`.
347
+
348
+ The host may pass `allowedActions`. If an action is not allowed, including
349
+ `getAuthToken`, the app receives `CallToolResult.isError = true`.
350
+
351
+ For MCP server tools, do not use `callIdwAction`; use `callServerTool`.
352
+
353
+ ## Agent Context
354
+
355
+ Use `updateAgentContext()` when the app knows something useful for the IDW agent's future reasoning:
356
+
357
+ ```ts
358
+ await app.updateAgentContext({
359
+ content: [{
360
+ type: 'text',
361
+ text: 'User selected lesson 3 and opened quiz remediation.',
362
+ }],
363
+ structuredContent: {
364
+ selectedLessonId: 'lesson-3',
365
+ currentView: 'quiz-remediation',
366
+ },
367
+ });
368
+ ```
369
+
370
+ Rules:
371
+
372
+ - It accepts the same payload shape as MCP Apps `updateModelContext()`.
373
+ - Keep it concise and relevant to the agent.
374
+ - Do not use it for monitoring telemetry or debug logs.
375
+ - Do not include secrets, auth tokens, private backend payloads, or large data.
376
+ - Use `sendLog()` / logging APIs for debug or diagnostic events.
377
+
378
+ ## Host Context
379
+
380
+ The host may include a compact IDW block in MCP host context:
381
+
382
+ ```ts
383
+ const hostContext = app.getHostContext();
384
+ const idw = hostContext.idw as {
385
+ idwId?: string;
386
+ surface?: string;
387
+ appInstanceId?: string;
388
+ auth?: {
389
+ apiBaseUrl?: string;
390
+ orToken?: string;
391
+ };
392
+ allowedActions?: string[];
393
+ } | undefined;
394
+ ```
395
+
396
+ By default, `auth.orToken` is omitted. Prefer lazy `getAuthToken`.
397
+
398
+ Use `surface` to adjust layout, not to fork the app protocol.
399
+
400
+ ## OpenAI Compatibility
401
+
402
+ `@onereach/idw-apps/app` provides a minimal OpenAI widget-state compatibility shim:
403
+
404
+ ```ts
405
+ window.openai?.widgetState;
406
+ window.openai?.setWidgetState?.({ route: '/details' });
407
+ ```
408
+
409
+ This mirrors/delegates to IDW widget state. It is not a full OpenAI Apps SDK implementation.
410
+
411
+ Apps built for OpenAI Apps SDK may need adaptation unless they only depend on compatible widget-state behavior and standard MCP Apps behavior. Prefer the explicit IDW APIs for new IDW apps.
412
+
413
+ ## IDW Web Apps (Not MCP-Server-Backed)
414
+
415
+ For IDW Web apps:
416
+
417
+ - Use the same `createIdwApp()` skeleton above; Web Apps do not use a separate app class.
418
+ - They still use the standard MCP Apps transport/handshake as the universal IDW
419
+ app protocol; “non-MCP” refers only to the absence of an MCP server backend.
420
+ - Omit `capabilities` unless the Web App exposes app-side MCP tools.
421
+ - Use the same widget-state and lazy-auth rules.
422
+ - Use `app.validateAccess()` after `app.connect()` when replacing old
423
+ `idw-skill` Web Skill access checks.
424
+ - Treat `surface` as a layout hint.
425
+ - Resolve business/session data from the app backend.
426
+ - Request `app.getAuthToken()` only when the app needs IDW/API identity.
427
+ - Use `callIdwAction` for host actions, not for app-backend business operations.
428
+ - Use `updateAgentContext` only for concise model-visible app activity.
429
+
430
+ If the app is cross-origin and does not bundle `@onereach/idw-apps/app` or receive an equivalent bootstrap, the host cannot reliably add `window.idw.app` after the page has loaded.
431
+
432
+ ## Security And Privacy
433
+
434
+ - Feature-detect optional APIs before using them when building portable apps.
435
+ - Never persist or expose tokens in widget state or agent context.
436
+ - Do not leak `chatId`, backend URLs, token values, or private API payloads into model-visible context unless explicitly required.
437
+ - Avoid storing PII in widget state; store IDs and reload data from the backend.
438
+ - Validate and sanitize user-controlled values before rendering.
439
+ - Treat host action failures as normal app errors, not crashes.
440
+ - Keep app state JSON-serializable so it can cross `postMessage` and persist cleanly.
441
+
442
+ ## Build Checklist
443
+
444
+ Before finishing an app:
445
+
446
+ - The app uses `createIdwApp()` and connects immediately after registering handlers.
447
+ - The app renders from tool results even when no widget state exists.
448
+ - Widget state is applied only as a view/restore overlay and never replaces business data.
449
+ - Stateful reload works from `getWidgetState()` or `subscribeWidgetState()` without blocking the initial MCP render.
450
+ - `setWidgetState()` is called only for lightweight restore-critical state.
451
+ - Backend-backed data reloads from the app/backend using IDs from widget state.
452
+ - Auth is requested lazily with `getAuthToken` only when needed.
453
+ - MCP server operations use `callServerTool`, not `callIdwAction`.
454
+ - Agent-callable app tools use `capabilities.tools`, `onlisttools`, and `oncalltool`.
455
+ - IDW host/platform operations use `callIdwAction`.
456
+ - Agent context is concise, non-secret, and useful to the model.
457
+ - The app behaves reasonably on its target surface.
458
+ - Standard external MCP compatibility is not broken by IDW-specific assumptions.