@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.
- package/dist/agent-instructions/modules/media.d.ts +3 -0
- package/dist/anthropic-messages.d.ts +13 -3
- package/dist/anthropic-request-error.d.ts +10 -0
- package/dist/assets/codemode-client.json +1 -1
- package/dist/{chunk-A22BKN4P.js → chunk-LJNCGUMB.js} +3 -3
- package/dist/chunk-LJNCGUMB.js.map +1 -0
- package/dist/{chunk-GTWKWJ67.js → chunk-P2OVGKDG.js} +505 -100
- package/dist/chunk-P2OVGKDG.js.map +1 -0
- package/dist/{chunk-D5DPVA2C.js → chunk-R25L27T3.js} +2047 -1275
- package/dist/chunk-R25L27T3.js.map +1 -0
- package/dist/claude-subscription-usage.d.ts +14 -4
- package/dist/context-compaction.d.ts +7 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -3
- package/dist/interaction-tools.d.ts +1 -1
- package/dist/mcp-network.js +1 -1
- package/dist/runtime-skills.d.ts +1 -0
- package/dist/sandbox/channel-a.d.ts +3 -0
- package/dist/sandbox/index.d.ts +3 -1
- package/dist/sandbox/index.js +7 -1
- package/dist/sandbox/provider-command-session.d.ts +21 -0
- package/dist/sandbox/providers/modal-command-argv.d.ts +4 -0
- package/dist/sandbox/providers/modal-command-control.d.ts +17 -5
- package/dist/sandbox/providers/modal-command-observation-errors.d.ts +3 -0
- package/dist/sandbox/providers/modal-command-raw-page.d.ts +100 -0
- package/dist/sandbox/providers/modal-command-router-wire.d.ts +17 -0
- package/dist/sandbox/routing/routing-session.d.ts +10 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +4 -0
- package/dist/workspace-tool-gateway.js +3 -3
- package/package.json +12 -12
- package/src/agent-instructions/PROMPT_CHANGELOG.md +23 -1
- package/src/agent-instructions/compose.ts +2 -0
- package/src/agent-instructions/modules/media.ts +20 -0
- package/src/agent-instructions/runtime-mechanics.ts +8 -0
- package/src/anthropic-messages.ts +223 -31
- package/src/anthropic-request-error.ts +45 -0
- package/src/bundled_default_skills/opengeni-client/SKILL.md +9 -7
- package/src/bundled_default_skills/opengeni-client/references/agent-recipes.md +1 -1
- package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +61 -0
- package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +86 -0
- package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +82 -0
- package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +32 -25
- package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +17 -0
- package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +156 -0
- package/src/bundled_schedule_skills/opengeni-schedules/SKILL.md +56 -0
- package/src/claude-subscription-usage.ts +55 -9
- package/src/context-compaction.ts +16 -9
- package/src/index.ts +85 -18
- package/src/interaction-tools.ts +113 -3
- package/src/lazy-tool-transport.ts +26 -10
- package/src/mcp-network.ts +4 -2
- package/src/model-provider-client.ts +11 -3
- package/src/model-provider-routing.ts +4 -0
- package/src/runtime-skills.ts +8 -0
- package/src/sandbox/channel-a.ts +3 -0
- package/src/sandbox/index.ts +11 -1
- package/src/sandbox/provider-command-session.ts +58 -0
- package/src/sandbox/provider-errors.ts +43 -9
- package/src/sandbox/providers/modal-command-argv.ts +20 -0
- package/src/sandbox/providers/modal-command-control.ts +422 -116
- package/src/sandbox/providers/modal-command-observation-errors.ts +65 -0
- package/src/sandbox/providers/modal-command-raw-page.ts +147 -0
- package/src/sandbox/providers/modal-command-router-wire.ts +104 -12
- package/src/sandbox/providers/modal-command-session.ts +26 -4
- package/src/sandbox/providers/modal-legacy-command-control.ts +2 -9
- package/src/sandbox/providers/modal-materialization-verification.ts +70 -6
- package/src/sandbox/routing/routing-session.ts +140 -41
- package/src/sandbox/run-credentials.ts +62 -19
- package/src/sandbox/turn-tool-cancellation.ts +159 -42
- package/dist/chunk-A22BKN4P.js.map +0 -1
- package/dist/chunk-D5DPVA2C.js.map +0 -1
- package/dist/chunk-GTWKWJ67.js.map +0 -1
package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md
CHANGED
|
@@ -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
|
|
41
|
-
|
|
42
|
-
silently: if the request or repository does
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
(`"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- **
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 = (
|
|
4
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
}
|