@opengeni/runtime 4.6.0 → 4.7.0-canary.37090756480001

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 (72) hide show
  1. package/dist/agent-instructions/modules/media.d.ts +3 -0
  2. package/dist/anthropic-messages.d.ts +13 -3
  3. package/dist/anthropic-request-error.d.ts +10 -0
  4. package/dist/assets/codemode-client.json +1 -1
  5. package/dist/{chunk-A22BKN4P.js → chunk-LJNCGUMB.js} +3 -3
  6. package/dist/chunk-LJNCGUMB.js.map +1 -0
  7. package/dist/{chunk-GTWKWJ67.js → chunk-P2OVGKDG.js} +505 -100
  8. package/dist/chunk-P2OVGKDG.js.map +1 -0
  9. package/dist/{chunk-D5DPVA2C.js → chunk-R25L27T3.js} +2047 -1275
  10. package/dist/chunk-R25L27T3.js.map +1 -0
  11. package/dist/claude-subscription-usage.d.ts +14 -4
  12. package/dist/context-compaction.d.ts +7 -7
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +13 -3
  15. package/dist/interaction-tools.d.ts +1 -1
  16. package/dist/mcp-network.js +1 -1
  17. package/dist/runtime-skills.d.ts +1 -0
  18. package/dist/sandbox/channel-a.d.ts +3 -0
  19. package/dist/sandbox/index.d.ts +3 -1
  20. package/dist/sandbox/index.js +7 -1
  21. package/dist/sandbox/provider-command-session.d.ts +21 -0
  22. package/dist/sandbox/providers/modal-command-argv.d.ts +4 -0
  23. package/dist/sandbox/providers/modal-command-control.d.ts +17 -5
  24. package/dist/sandbox/providers/modal-command-observation-errors.d.ts +3 -0
  25. package/dist/sandbox/providers/modal-command-raw-page.d.ts +100 -0
  26. package/dist/sandbox/providers/modal-command-router-wire.d.ts +17 -0
  27. package/dist/sandbox/routing/routing-session.d.ts +10 -0
  28. package/dist/sandbox/turn-tool-cancellation.d.ts +4 -0
  29. package/dist/workspace-tool-gateway.js +3 -3
  30. package/package.json +12 -12
  31. package/src/agent-instructions/PROMPT_CHANGELOG.md +23 -1
  32. package/src/agent-instructions/compose.ts +2 -0
  33. package/src/agent-instructions/modules/media.ts +20 -0
  34. package/src/agent-instructions/runtime-mechanics.ts +8 -0
  35. package/src/anthropic-messages.ts +223 -31
  36. package/src/anthropic-request-error.ts +45 -0
  37. package/src/bundled_default_skills/opengeni-client/SKILL.md +9 -7
  38. package/src/bundled_default_skills/opengeni-client/references/agent-recipes.md +1 -1
  39. package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +61 -0
  40. package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +86 -0
  41. package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +82 -0
  42. package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +32 -25
  43. package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +17 -0
  44. package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +156 -0
  45. package/src/bundled_schedule_skills/opengeni-schedules/SKILL.md +56 -0
  46. package/src/claude-subscription-usage.ts +55 -9
  47. package/src/context-compaction.ts +16 -9
  48. package/src/index.ts +85 -18
  49. package/src/interaction-tools.ts +113 -3
  50. package/src/lazy-tool-transport.ts +26 -10
  51. package/src/mcp-network.ts +4 -2
  52. package/src/model-provider-client.ts +11 -3
  53. package/src/model-provider-routing.ts +4 -0
  54. package/src/runtime-skills.ts +8 -0
  55. package/src/sandbox/channel-a.ts +3 -0
  56. package/src/sandbox/index.ts +11 -1
  57. package/src/sandbox/provider-command-session.ts +58 -0
  58. package/src/sandbox/provider-errors.ts +43 -9
  59. package/src/sandbox/providers/modal-command-argv.ts +20 -0
  60. package/src/sandbox/providers/modal-command-control.ts +422 -116
  61. package/src/sandbox/providers/modal-command-observation-errors.ts +65 -0
  62. package/src/sandbox/providers/modal-command-raw-page.ts +147 -0
  63. package/src/sandbox/providers/modal-command-router-wire.ts +104 -12
  64. package/src/sandbox/providers/modal-command-session.ts +26 -4
  65. package/src/sandbox/providers/modal-legacy-command-control.ts +2 -9
  66. package/src/sandbox/providers/modal-materialization-verification.ts +70 -6
  67. package/src/sandbox/routing/routing-session.ts +140 -41
  68. package/src/sandbox/run-credentials.ts +62 -19
  69. package/src/sandbox/turn-tool-cancellation.ts +159 -42
  70. package/dist/chunk-A22BKN4P.js.map +0 -1
  71. package/dist/chunk-D5DPVA2C.js.map +0 -1
  72. package/dist/chunk-GTWKWJ67.js.map +0 -1
@@ -47,6 +47,92 @@ module.exports = { createOpenGeniClient };
47
47
 
48
48
  Keep this initialization on the server: never send the API key to a browser.
49
49
 
50
+ ## Host errors and uncertain actions
51
+
52
+ Keep SDK/provider details server-only and return friendly host-branded notices,
53
+ not `error.message`, `error.body`, raw stack traces or a provider's product name.
54
+ Put configuration, dynamic SDK import/initialization, identity resolution,
55
+ upstream calls and local persistence inside the route's error boundary, not only
56
+ the final call. Log a redacted diagnostic with a non-secret correlation ID on
57
+ the server; never copy an SDK exception into the browser response or DOM.
58
+
59
+ Branding applies to UI-owned labels/notices, not legitimate source quotations,
60
+ code, or user/assistant transcript content; do not rewrite those to hide names.
61
+ Check the installed SDK/React error-copy API separately: changing conversation
62
+ labels does not itself prove that upstream error messages are remapped. Host
63
+ safe-copy guidance alone does not change a package's default error behavior.
64
+
65
+ When the installed SDK exports `formatErrorMessage(error, fallback?)` (root,
66
+ `@opengeni/sdk/core` or `@opengeni/sdk/browser`), use it for custom-renderer error
67
+ copy. Typed errors produce neutral, state-aware messages and retain a bounded
68
+ support reference; an unrecognized error uses the supplied safe fallback, never
69
+ its raw message. The original error's `message`, `body`, `details`, `status`,
70
+ `code`, `retryable` and `outcomeUnknown` remain diagnostic/policy facts.
71
+
72
+ The matching React package provides `OpenGeniProvider.formatError`, with the
73
+ exported type `ErrorMessageFormatter`:
74
+ `(error: unknown, defaultMessage: string) => string | undefined`. It receives
75
+ the original error and neutral default before presentation. Return a host
76
+ message; `undefined` or an empty string keeps the default. A minimal branding
77
+ callback preserves its state guidance and support reference. A throwing callback
78
+ or invalid runtime return also keeps the neutral default; presentation must not
79
+ interrupt delivery-state settlement:
80
+
81
+ ```js
82
+ function formatAssistantError(_error, defaultMessage) {
83
+ return `ACME Assistant: ${defaultMessage}`;
84
+ }
85
+ ```
86
+
87
+ Pass this as `formatError={formatAssistantError}` on the existing provider.
88
+ Keep heading/placeholder/label overrides separately. Explicit host-authored
89
+ error props and custom command messages still need safe host copy; this callback
90
+ does not rewrite user, assistant, tool, worker or Skill content. Do not regex
91
+ scrub those strings or stringify diagnostic errors. Check the installed exports
92
+ and provider prop before using these additive APIs; source guidance is not
93
+ proof that an older installed/published package contains them.
94
+
95
+ Formatting is presentation only: it does not authorize calls, change retry
96
+ policy or reconcile an uncertain mutation. Preserve the default's sign-in,
97
+ permission, setup/allowance, HTTPS-upload and unknown-outcome instructions when
98
+ customizing it. Keep diagnostics server-only/redacted and retain only the
99
+ bounded support reference needed by the UI, not a raw diagnostic body.
100
+
101
+ For an approval route, distinguish an accepted decision from completed tool
102
+ execution. If the call returns a decision event and local mapping/receipt save
103
+ then fails, the decision may already be durable. `outcomeUnknown: true` also
104
+ means acceptance may have occurred. Neither case justifies "the article is
105
+ unchanged", a fresh approval ID or a blind replay. Keep the original session,
106
+ approval ID and persisted `clientEventId`; reread ordered events and the
107
+ authorized provider record to reconcile. Only retry the exact original request
108
+ under the installed endpoint's supported idempotency contract after reconciliation.
109
+ For stale/no-pending approval conflicts, refresh current state instead of
110
+ recreating a pending card or exposing the raw upstream error.
111
+
112
+ ```js
113
+ function assistantFailureNotice(error, { decisionAccepted = false } = {}) {
114
+ if (decisionAccepted || error?.outcomeUnknown === true) {
115
+ return {
116
+ state: "reconciling",
117
+ message: "The action may have been accepted. Check its status before trying again.",
118
+ };
119
+ }
120
+ if (error?.status === 409) {
121
+ return { state: "refresh", message: "This request has changed. Refresh before deciding." };
122
+ }
123
+ return { state: "failed", message: "The assistant is unavailable. Please try again later." };
124
+ }
125
+ ```
126
+
127
+ This helper is for the host's approval-action error boundary, not a replacement
128
+ for authentication/authorization responses. Set `decisionAccepted = true`
129
+ immediately after `await sendApprovalDecision(...)`, before any local save.
130
+ Disable repeat actions while `state === "reconciling"`; render only this safe
131
+ notice and use the existing host status/error conventions. Apply the same
132
+ safe-copy boundary to polling, configuration and generic SDK failures. Test
133
+ stale approval, an unknown mutation outcome, accepted-decision/local-save failure,
134
+ SDK initialization failure and late errors after an identity switch.
135
+
50
136
  ## Decide what is being replaced
51
137
 
52
138
  | Customer dependency | Evidence needed |
@@ -160,6 +160,88 @@ or rotation through the authorized owner.
160
160
 
161
161
  Tool selection is not data authorization. The customer API must validate the presented credential on every operation and derive or verify the allowed tenant, user, report, and row scope. Do not trust model-supplied tenant IDs. Prefer endpoints whose server derives scope from token claims; when an ID is accepted, verify it belongs to those claims.
162
162
 
163
+ An agent bridge needs an independently scoped provider credential, not an
164
+ ordinary host login JWT with fewer tools listed. A token signed with the host's
165
+ normal login secret and user ID may still authorize its account/password APIs;
166
+ an OpenAPI/MCP allowlist does not restrict that bearer outside the tool surface.
167
+ Prefer a separate signing key/token namespace plus a distinct issuer/audience,
168
+ short expiry and explicit operation/record scope. Verify those claims on the
169
+ bridge, and make ordinary host endpoints reject this credential entirely. Adding
170
+ an audience claim is ineffective if the ordinary host verifier never checks it.
171
+ Reuse authorized business logic, not the ordinary login credential or middleware.
172
+
173
+ Before launch, test the actual middleware in both directions: the provider token
174
+ can perform only its allowed read/title-only operation on an owned record; it
175
+ cannot call the ordinary account/password/admin APIs, another user's record or
176
+ an extra body field; an ordinary login token is not a bridge credential. Check
177
+ expiry, issuer/audience and operation scope with negative tests. These are host
178
+ authorization tests, not proof supplied by OpenGeni's tool selection or approval.
179
+
180
+ ### Reauthorize current host policy, not only signed claims
181
+
182
+ After verifying the provider token's signature, issuer/audience and expiry,
183
+ reload the current host user and its membership/record-owner policy for each
184
+ call. A disabled owner or downgraded role must lose authority even while an old
185
+ JWT remains cryptographically valid. For writes, recheck at the transaction
186
+ boundary; a prior read is not a permission lease. The host-owned adapter below
187
+ uses illustrative policy fields, not an OpenGeni token/SDK schema:
188
+
189
+ ```js
190
+ function requireLiveToolAuthority(claims, user, grant, operation) {
191
+ if (typeof claims?.subjectId !== "string" || !claims.subjectId ||
192
+ typeof claims.tenantId !== "string" || !claims.tenantId ||
193
+ user?.enabled !== true || user.id !== claims.subjectId ||
194
+ grant?.active !== true || grant.subjectId !== user.id ||
195
+ grant.tenantId !== claims.tenantId ||
196
+ !Array.isArray(grant.operations) || !grant.operations.includes(operation) ||
197
+ !Array.isArray(claims.operations) || !claims.operations.includes(operation)) {
198
+ throw new Error("Tool request is not authorized.");
199
+ }
200
+ return { subjectId: user.id, tenantId: grant.tenantId };
201
+ }
202
+
203
+ // Website-share credentials are not team-report credentials.
204
+ function websiteShareMayReadReport(share, report) {
205
+ return share?.kind === "website-share" &&
206
+ typeof share.websiteId === "string" && share.websiteId.length > 0 &&
207
+ report?.scope?.kind === "website" &&
208
+ report.scope.websiteId === share.websiteId;
209
+ }
210
+
211
+ // Host parses dates into validated UTC milliseconds; this example is half-open.
212
+ function requireAuthorizedWindow(requested, allowed) {
213
+ if (![requested.start, requested.end, allowed.start, allowed.end].every(Number.isSafeInteger) ||
214
+ requested.start >= requested.end || allowed.start >= allowed.end ||
215
+ requested.start < allowed.start || requested.end > allowed.end) {
216
+ throw new Error("Requested date window is not authorized.");
217
+ }
218
+ return requested;
219
+ }
220
+ ```
221
+
222
+ Load `user` and `grant` from fresh trusted host records, never from browser/model
223
+ fields or the token's old role alone. The token is only a ceiling. After this
224
+ check, still authorize each requested record and field against that context.
225
+ A one-user workspace still requires enabled-owner and record-ownership checks.
226
+
227
+ ### Scope report reads and publication
228
+
229
+ Authorize the stored report's actual scope on list, summary, detail and export
230
+ routes. A website-share credential must not expose a team report merely because
231
+ that team contains the shared website. Filter summaries before returning them;
232
+ transcript access is a separate check. Signed-in team sharing remains valid under
233
+ its own current membership policy; the website-share helper is not that policy.
234
+ The host derives this persisted scope from authorized records, not an unchecked
235
+ editor-submitted authorization/provenance field.
236
+
237
+ Enforce the advertised analytics window in the server/provider query, including
238
+ time zone and the provider's boundary conventions, not only in prompts or UI.
239
+ Bind reopening/export headers and session lookup to the currently selected
240
+ report's immutable ID and authorized session, not the first mounted report.
241
+ Persist generated-report provenance on the trusted backend from verified
242
+ session/job receipts. An ordinary editor's submitted body or `generated` flag
243
+ cannot establish that provenance or impersonate a different session/author.
244
+
163
245
  Separate operations by risk. Read-only analytics, data export, saved-report mutation, and administrative actions should not share an unnecessarily broad token or approval policy. Keep destructive or consequential writes absent or approval-gated unless the customer explicitly wants autonomous writes.
164
246
 
165
247
  For analytics, return structured, bounded data with clear units, time zones, filters, pagination, and aggregation semantics. Provide server-side aggregates where practical. The agent may combine tool calls or use CodeMode to transform authorized results without placing every intermediate row in conversational context. Code execution happens in the selected OpenGeni sandbox or Connected Machine; provider credentials remain in the broker. Confirm that the installed tool surface is available to CodeMode before relying on that optimization.
@@ -37,11 +37,13 @@ platform work separate from an ordinary customer's integration responsibilities.
37
37
 
38
38
  ## Ask the exact amount
39
39
 
40
- The four user-owned choices (who shares what, when things run and in which time
41
- zone, where outputs land, whether the agent may write) are never defaulted
42
- silently: if the request or repository does not settle one, send the single
43
- bundled question before building the parts that depend on it, and continue only
44
- independent discovery while waiting. Skip this only when the user explicitly
40
+ The user-owned choices (who shares what, whether the agent may change data,
41
+ and, only for background or scheduled work, when it runs and where its results
42
+ should appear) are never defaulted silently: if the request or repository does
43
+ not settle a relevant one, send a single short question in plain product terms
44
+ before building the parts that depend on it, and continue only independent
45
+ discovery while waiting. A chat assistant's replies appear in the chat; that is
46
+ not a question to ask. Skip this only when the user explicitly
45
47
  said not to ask; then state the defaults you chose in the handoff. For other
46
48
  choices, first use facts already available from the product, repository, live
47
49
  service, or prior direction, and use a reversible recommendation instead of a
@@ -64,26 +66,31 @@ user accept it or adjust individual choices. Do not build a new questionnaire or
64
66
  ask every integration the same questions. A suggested answer is not consent to
65
67
  send data, share private content, or perform an external action.
66
68
 
67
- Use product language for the relevant unresolved choices:
68
-
69
- - **Learning across chats:** no new lasting learning, remember for each person,
70
- or shared team knowledge. Explain briefly that chat history/retention is separate
71
- and disabling learning does not delete history or existing authorized Knowledge.
72
- - **Who can open chats:** only their owner (`chats: "private"`), the team
73
- (`"shared"`), or a workspace per user (`"isolated"`). Choose the
74
- workspace mapping from the actual sharing boundary; private chats alone do not
75
- require a workspace per person.
76
- - **How the agent gets data:** current-page snapshots, read-only tools to fetch
77
- more reports, or controlled queries for deeper analysis. State the meaningful
78
- limitation of the recommendation. Confirm broader access or writes separately
79
- only when they are part of the requested product.
80
- - **When things run:** on demand, or on a schedule (time and time zone).
81
- - **Where results land:** the chat, a product record or screen, or a channel;
82
- delivery goes through product tools, or signed workspace webhooks where the
83
- deployment offers them.
84
-
85
- For example, if all three choices are unresolved for a simple dashboard, propose
86
- “Private chats, no learning between chats, and current-page data only” with a short
69
+ Write every question the way the end user talks about their own product. Never
70
+ use internal terms such as "on-demand", "learning across chats", "shape",
71
+ "capabilities" or "visibility". Only ask about what applies:
72
+
73
+ - **Who can see a chat:** only the person who started it (`chats: "private"`),
74
+ their whole team (`"shared"`), or each user gets a fully separate space
75
+ (`"isolated"`). Choose the workspace mapping from the actual sharing boundary;
76
+ private chats alone do not require a workspace per person.
77
+ - **What the agent may do:** only look things up (read-only), or also make
78
+ changes. Name the actual things ("read your analytics", "can't change
79
+ websites or users"). Confirm writes separately, only when they are part of
80
+ the requested product.
81
+ - **What data it uses:** only what's on the current page, or it can look up
82
+ more on its own. State the meaningful limitation of the recommendation.
83
+ - **Only when the requested feature is itself scheduled or runs in the
84
+ background** (for example "email me a weekly report"): the missing details
85
+ (when it runs, "every Monday at 9:00, Oslo time", and where results should
86
+ appear) with one sentence on why. Never ask about this up front, and never
87
+ for a chat assistant: long sessions work without any user decision.
88
+ - **Memory:** don't ask by default. Use no lasting memory between chats unless
89
+ the product clearly needs the assistant to remember things ("remember my
90
+ preferences"). If it does, ask in those words. Chat history is kept either way.
91
+
92
+ For example, for a simple dashboard assistant, propose
93
+ “Only you can see your chats; it uses what's on the current page and can't change anything” with a short
87
94
  explanation that the agent cannot fetch another report on its own. Do not reuse
88
95
  that default for a team assistant whose requirements already imply shared work.
89
96
  Continue independent discovery while awaiting an answer; ask again only when new
@@ -82,6 +82,14 @@ invalid Unicode are rejected. A native-looking ID does not impersonate a native
82
82
  user. Workspace mapping identity passed to `ensureWorkspace` is a separate
83
83
  concept from this acting-user identity.
84
84
 
85
+ Use an immutable database/auth subject ID, never an editable username, email or
86
+ display name. Renaming must keep the same external ID and private history;
87
+ reassigning a username must not reassign the previous actor's identity. History
88
+ collision on username reuse is conditional on the host allowing reuse, not a
89
+ guarantee about every host. Derive the ID anew from current authentication rather
90
+ than a mounted component's old user object; apply the
91
+ [identity/session epoch fence](product-shapes-and-ui.md#bind-asynchronous-ui-work-to-the-current-identity-and-session).
92
+
85
93
  User mode lazily establishes an external identity but does not grant access to a
86
94
  shared workspace. An explicitly authorized service onboarding operation may use:
87
95
 
@@ -102,6 +110,15 @@ needs `capabilities:manage`; do not add it unless installation is a product
102
110
  feature the user may perform. User requests intersect actual membership with
103
111
  the initiating key's permissions. Service administration remains separate.
104
112
 
113
+ Check the host user's current enabled state, tenant membership and role on every
114
+ session read/control route and every provider call. A still-valid token or an
115
+ old `sessions:control` onboarding grant is not proof of current host authority.
116
+ Block newly disallowed requests immediately and reconcile OpenGeni grants using
117
+ the authorized membership lifecycle below; do not silently re-onboard or widen
118
+ them on a user request. Permission-changing revocation can cancel running work.
119
+ Per-user workspaces and deliberate team/site sharing are valid product choices,
120
+ but neither replaces these current host-policy checks.
121
+
105
122
  ### Read the member inventory: SDK versus REST
106
123
 
107
124
  ```ts
@@ -12,6 +12,25 @@
12
12
 
13
13
  Default to the full conversation component. Deviate only when the product needs a materially different interaction model, a non-React frontend, or compute surfaces, and record why. Styling differences alone are not a reason: theme with `--og-*` tokens and density props. Do not mount the workbench for an ordinary analytics chat, and do not rebuild session streaming, replay, queueing, approval, or timeline projection that a package already supplies.
14
14
 
15
+ ## Stock and host-branded appearance
16
+
17
+ For a custom-branded embed, visibly match the host's fonts, colors, spacing,
18
+ radius and current theme using the shipped stylesheet, scoped `--og-*` tokens
19
+ and supported theme/density/label hooks. Keep UI-owned copy free of OpenGeni
20
+ branding; source quotations and user/assistant content are not UI labels. Verify
21
+ the installed error-copy API separately, not just the heading/placeholder.
22
+
23
+ Stock mode uses the shipped components and `compiled.css` without extra host
24
+ cosmetic CSS. The quality target is simple, polished and smooth at desktop
25
+ around 1440px and mobile around 390px in supported light/dark themes. Host
26
+ placement/available height remains host-owned. A stock defect belongs in the
27
+ package's React/CSS, not a host styling workaround or a replacement chat UI.
28
+ Do not present this expectation as proof that a particular build passed.
29
+
30
+ Preserve first-try evaluation evidence when later repairs improve the result.
31
+ In coordinated trials, the coordinator captures the actual browser matrix;
32
+ do not make screenshot submission a prerequisite for the coding-agent handoff.
33
+
15
34
  ## When deviating in React
16
35
 
17
36
  Inspect the installed OpenGeni React package before creating replacement components. Its subpaths are composable, and the styled surfaces use scoped compiled CSS plus runtime theme and density tokens. Prefer, in order: `SessionConversation` customized through `composerProps` and `renderMessageText`; `MessageTimeline`, `ChatComposer`, and the session hooks composed into product layout; then a fully custom SDK-driven UI. Do not force a packaged component when the product needs a materially different interaction model.
@@ -145,6 +164,70 @@ For live sessions, preserve event sequence, reconnect, replay, and duplicate sup
145
164
 
146
165
  Uploads may send bytes directly to a short-lived signed storage URL returned by the trusted flow. That URL is narrow transfer authority, not the OpenGeni API key. Verify storage CORS for every intended browser origin.
147
166
 
167
+ ### Bind asynchronous UI work to the current identity and session
168
+
169
+ In a custom polling/static UI, backend authorization does not stop an already
170
+ authorized response for account A arriving after account B signs in. On logout,
171
+ login, tenant switch or session switch, invalidate the UI epoch, abort outstanding
172
+ requests, stop polling, and clear private messages, approval cards, session IDs,
173
+ cursors and local caches. Do not reuse A's session ID for B. Abort alone is not
174
+ enough: a completed request or a transport that ignores abort can still resolve.
175
+ Guard both success and failure rendering by the captured identity/session epoch.
176
+
177
+ These framework-neutral helpers belong in the host's existing frontend module:
178
+
179
+ ```js
180
+ function createIdentityBoundView(clearPrivateState) {
181
+ let epoch = 0;
182
+ let identityKey = null;
183
+ const requests = new Set();
184
+ return {
185
+ reset(nextIdentityKey) {
186
+ epoch += 1; // also fences A -> B -> A and a new session for the same user
187
+ identityKey = nextIdentityKey;
188
+ for (const controller of requests) controller.abort();
189
+ requests.clear();
190
+ clearPrivateState(); // include polling timers, cached IDs and decisions
191
+ },
192
+ begin() {
193
+ if (identityKey === null) throw new Error("No authenticated view");
194
+ const capturedEpoch = epoch;
195
+ const controller = new AbortController();
196
+ requests.add(controller);
197
+ return {
198
+ signal: controller.signal,
199
+ isCurrent: () => capturedEpoch === epoch && !controller.signal.aborted,
200
+ finish: () => requests.delete(controller),
201
+ };
202
+ },
203
+ };
204
+ }
205
+
206
+ async function readForCurrentView(view, load, render, showFailure) {
207
+ const request = view.begin();
208
+ try {
209
+ const result = await load(request.signal);
210
+ if (request.isCurrent()) render(result);
211
+ } catch {
212
+ if (request.isCurrent()) showFailure("The assistant could not refresh.");
213
+ } finally {
214
+ request.finish();
215
+ }
216
+ }
217
+ ```
218
+
219
+ Call `view.reset(null)` before clearing host authentication; after authenticated
220
+ mapping, reset with a key for the tenant/user/session tuple before loading its
221
+ view. `load(signal)` calls the host's authenticated same-origin route and passes
222
+ the signal to `fetch`. The epoch is a presentation fence, not authorization:
223
+ the backend must still check every request's user and session ownership. Do not
224
+ run these browser helpers with an organization key or put that key in storage.
225
+
226
+ For reopened reports, key the selected report and its authorized session together.
227
+ Refresh request headers/mappings when that selection changes; do not keep the
228
+ first report's session in a mounted closure. A new actor object in the same
229
+ component still requires reset/invalidation, not only a logout-time unmount.
230
+
148
231
  ## Decide what the user sees
149
232
 
150
233
  OpenGeni's durable event stream can support different product projections:
@@ -159,6 +242,79 @@ The customer frontend chooses which event types and fields to render. Hiding an
159
242
 
160
243
  Each `session.requiresAction` event replaces the pending approval set. Read only `payload.approvals[].id`, `.name`, and `.arguments` (SDK type `SessionApprovalRequest`); other fields differ between a turn's first pause and later ones and exist for compatibility. Send `sendApprovalDecision({ approvalId: approval.id, decision })`: `id` is the pending tool call id, not the event id. `approvalsFromRequiresAction` / `projectPendingApprovals` from `@opengeni/react` already normalize older events.
161
244
 
245
+ For a custom non-React projection, fold ordered, deduplicated events rather than
246
+ appending every historical approval as a new pending card. Keep the original
247
+ approval ID and the durable decision event ID. A decision is terminal for that
248
+ approval; a replay/reload must not restore its Approve button. Turn settlement
249
+ clears that turn's undecided approvals, not approvals owned by a different turn.
250
+ The following minimal projection uses current stable fields; use the packaged
251
+ normalizer for older events, not generated IDs or guessed compatibility fields:
252
+
253
+ ```js
254
+ function projectApprovalState(events) {
255
+ let pending = new Map();
256
+ let owningTurnId = null;
257
+ const decisions = new Map();
258
+ const settledTurns = new Set();
259
+ for (const event of events) {
260
+ const payload = event.payload ?? {};
261
+ if (event.type === "session.requiresAction") {
262
+ owningTurnId = event.turnId ?? null;
263
+ pending = new Map();
264
+ if (owningTurnId !== null && settledTurns.has(owningTurnId)) continue;
265
+ for (const approval of Array.isArray(payload.approvals) ? payload.approvals : []) {
266
+ if (!approval || typeof approval.id !== "string" || !approval.id ||
267
+ typeof approval.name !== "string" || !approval.name || decisions.has(approval.id)) continue;
268
+ pending.set(approval.id, approval);
269
+ }
270
+ } else if (event.type === "user.approvalDecision") {
271
+ if (typeof payload.approvalId !== "string" || !payload.approvalId ||
272
+ !["approve", "reject"].includes(payload.decision) || decisions.has(payload.approvalId)) continue;
273
+ decisions.set(payload.approvalId, {
274
+ approvalId: payload.approvalId,
275
+ decision: payload.decision,
276
+ decisionEventId: event.id,
277
+ });
278
+ pending.delete(payload.approvalId);
279
+ } else if (["turn.completed", "turn.failed", "turn.cancelled"].includes(event.type)) {
280
+ if (event.turnId != null) settledTurns.add(event.turnId);
281
+ if (owningTurnId === null || event.turnId == null || event.turnId === owningTurnId) {
282
+ pending.clear();
283
+ owningTurnId = null;
284
+ }
285
+ }
286
+ }
287
+ return { pending: [...pending.values()], decisions: [...decisions.values()] };
288
+ }
289
+
290
+ // Example for a known title-only operation, not a generic approval formatter.
291
+ function exactTitleProposal(approval) {
292
+ try {
293
+ const args = typeof approval.arguments === "string"
294
+ ? JSON.parse(approval.arguments) : approval.arguments;
295
+ const body = args?.body;
296
+ if (!body || Array.isArray(body) || Object.keys(body).some((key) => key !== "title")) return null;
297
+ return typeof body.title === "string" && body.title.trim() ? body.title : null;
298
+ } catch {
299
+ return null;
300
+ }
301
+ }
302
+ ```
303
+
304
+ Render arguments using the selected operation's schema: for this example the
305
+ title is `arguments.body.title`, not `arguments.title`. Use escaped text, retain
306
+ the exact string (do not silently trim/change it), and also display the target
307
+ record and operation. Enable Approve only for a currently pending, authorized
308
+ request whose exact proposal and target can be shown. Missing/malformed proposal
309
+ means disabled Approve, not a placeholder beside an enabled button; Reject or
310
+ refresh can remain available. The provider still enforces record ownership,
311
+ allowed fields and any expected-version/CAS precondition on the actual write.
312
+
313
+ Persist a stable `clientEventId` before `sendApprovalDecision`, retain the
314
+ returned decision event, and reconcile after an uncertain response or a local
315
+ save failure. Accepted approval means a decision was accepted, not that its
316
+ provider write completed. See [Failure and reconciliation](compatibility-and-troubleshooting.md#host-errors-and-uncertain-actions).
317
+
162
318
  Even a final-answer-only UI should surface states the user must act on: failure, cancellation, credit or policy denial, approval requests, human-input requests, reconnect status, and a way to retry safely. Avoid presenting tool failures as ordinary assistant prose when product state can represent them more clearly.
163
319
 
164
320
  ## Opening host-owned workbench tabs
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: opengeni-schedules
3
+ description: Create a schedule, recurring task, reminder, or monitor in OpenGeni. Read this before turning a user's request into scheduled work, including Create with OpenGeni from Schedules. Discover the required resources and integrations, then create the task with the user's cadence and time zone.
4
+ ---
5
+
6
+ # Create a schedule
7
+
8
+ Turn the user's description into scheduled work in the current workspace.
9
+ Research what is available before asking questions. Ask only for essential
10
+ details you cannot discover or reasonably decide from the request.
11
+
12
+ ## Discover what the task needs
13
+
14
+ - Use `scheduled_tasks_list` to avoid duplicating an existing schedule. Follow
15
+ pagination when needed and use `scheduled_tasks_get` for a relevant task's
16
+ details. A list summary is not its complete configuration.
17
+ - If the work needs a repository, use `github_repositories_list` to find the
18
+ authorized repository and select the required resources.
19
+ - If it needs sandbox credentials, use `variable_set_list` to find the suitable
20
+ Variable Set. Attach its identifier; do not copy secret values into prompts.
21
+ - If it needs an integration such as Slack or Sentry, use
22
+ `capability_catalog_search` and the available tool discovery to find the
23
+ capability and exact operations. Follow returned connection/setup and approval
24
+ requirements. Do not claim that discovery means the integration is ready.
25
+
26
+ Select only the resources, Variable Set, and tools the task needs. Missing
27
+ optional dependencies are not a reason to ask for unnecessary setup. If a
28
+ required capability or authority is unavailable, explain the remaining setup
29
+ briefly instead of inventing a tool or borrowing another person's connection.
30
+
31
+ ## Create and verify
32
+
33
+ Use `scheduled_tasks_create` with:
34
+
35
+ - A short, descriptive name.
36
+ - A self-contained `agentConfig.prompt` that each run can start from: the work,
37
+ relevant sources and destinations, what to report, and when to stay quiet.
38
+ Preserve the user's requested brevity and notification conditions.
39
+ - The requested cadence and time zone. Use the time zone supplied in the
40
+ request unless the user explicitly chooses another. Inspect the current tool
41
+ schema for supported schedule shapes rather than guessing cron fields.
42
+ - The required repository resources, Variable Set, and tool selections.
43
+ Scheduled runs inherit the creating session's tool and permission ceiling;
44
+ this Skill does not grant authority or change approvals.
45
+
46
+ Honor an explicit run destination. A task prompt must not depend on unrecorded
47
+ details from the setup conversation. Do not trigger an extra run or send a test
48
+ message unless the user asks for one.
49
+
50
+ Check the creation receipt and follow its `scheduled_tasks_get` next action to
51
+ verify the saved task before claiming success. If it reports a committed
52
+ task with a synchronization failure, report that state and recover the existing
53
+ task rather than creating a duplicate. On success, briefly tell the user the
54
+ schedule's name and first expected run in their time zone. If the exact first
55
+ firing cannot be verified from the saved schedule, say so rather than inventing
56
+ a timestamp.
@@ -1,11 +1,26 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
 
3
- type Observer = (providerId: string, response: Response) => void;
4
- type Prepare = (providerId: string, headers: Headers) => Promise<Headers>;
3
+ type Observer = (
4
+ providerId: string,
5
+ response: Response,
6
+ upstreamModelId?: string,
7
+ requestToken?: string | null,
8
+ ) => void;
9
+ type PreparedUsageRequest = { headers: Headers; observe: Observer };
10
+ type Prepare = (providerId: string, headers: Headers) => Promise<Headers | PreparedUsageRequest>;
5
11
  const observers = new AsyncLocalStorage<{
6
12
  observe: Observer;
7
13
  prepare?: Prepare;
8
14
  }>();
15
+ const modelRequests = new AsyncLocalStorage<string>();
16
+
17
+ /** The native adapter knows the exact model without reading or cloning the wire body. */
18
+ export function withClaudeModelRequest<T>(
19
+ upstreamModelId: string,
20
+ run: () => Promise<T>,
21
+ ): Promise<T> {
22
+ return modelRequests.run(upstreamModelId, run);
23
+ }
9
24
 
10
25
  /** Like Codex usage headers: free observations within this turn's provider context. */
11
26
  export function withClaudeUsageObserver<T>(
@@ -20,15 +35,46 @@ export async function prepareClaudeSubscriptionRequest(
20
35
  input: Parameters<typeof fetch>[0],
21
36
  init?: RequestInit,
22
37
  ) {
23
- const prepare = observers.getStore()?.prepare;
24
- if (!prepare) return init;
25
- const headers = new Headers(input instanceof Request ? input.headers : undefined);
26
- new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
27
- return { ...init, headers: await prepare(providerId, headers) };
38
+ const context = observers.getStore();
39
+ const upstreamModelId = modelRequests.getStore();
40
+ let observe = context?.observe;
41
+ if (context?.prepare) {
42
+ const headers = new Headers(input instanceof Request ? input.headers : undefined);
43
+ new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
44
+ const prepared = await context.prepare(providerId, headers);
45
+ init = { ...init, headers: prepared instanceof Headers ? prepared : prepared.headers };
46
+ if (!(prepared instanceof Headers)) observe = prepared.observe;
47
+ }
48
+ return {
49
+ init,
50
+ observe(response: Response, requestToken?: string | null) {
51
+ try {
52
+ observe?.(providerId, response, upstreamModelId, requestToken);
53
+ } catch {
54
+ // Telemetry must neither consume nor change the model response.
55
+ }
56
+ },
57
+ };
58
+ }
59
+ export function captureClaudeRequestToken(input: Parameters<typeof fetch>[0], init?: RequestInit) {
60
+ if (!observers.getStore()) return undefined;
61
+ const headers = new Headers(
62
+ init?.headers !== undefined
63
+ ? init.headers
64
+ : input instanceof Request
65
+ ? input.headers
66
+ : undefined,
67
+ );
68
+ return headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1] ?? null;
28
69
  }
29
- export function observeClaudeUsageResponse(providerId: string, response: Response): void {
70
+
71
+ export function observeClaudeUsageResponse(
72
+ providerId: string,
73
+ response: Response,
74
+ requestToken?: string | null,
75
+ ): void {
30
76
  try {
31
- observers.getStore()?.observe(providerId, response);
77
+ observers.getStore()?.observe(providerId, response, modelRequests.getStore(), requestToken);
32
78
  } catch {
33
79
  // Usage telemetry must never change or consume a model response.
34
80
  }