@ixo/oracle-runtime-workers 0.21.2 → 0.23.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 (59) hide show
  1. package/README.md +16 -13
  2. package/package.json +10 -4
  3. package/src/action-proposals/policy.ts +54 -0
  4. package/src/action-proposals/reconcile.ts +87 -0
  5. package/src/action-proposals/store.ts +863 -0
  6. package/src/action-proposals/topic-budget.ts +84 -0
  7. package/src/action-proposals/topic-chain.ts +272 -0
  8. package/src/action-proposals/topic-effects.ts +44 -0
  9. package/src/action-proposals/topic-errors.ts +79 -0
  10. package/src/action-proposals/topic-history.ts +267 -0
  11. package/src/action-proposals/topic-matrix.ts +292 -0
  12. package/src/action-proposals/topic-prepare.ts +299 -0
  13. package/src/action-proposals/topic-store.ts +999 -0
  14. package/src/action-proposals/topic-verify.ts +279 -0
  15. package/src/action-proposals/verify.ts +223 -0
  16. package/src/channels/auth.ts +6 -1
  17. package/src/channels/contract.ts +4 -0
  18. package/src/channels/turns.ts +1 -0
  19. package/src/core/env.ts +107 -5
  20. package/src/core/index.ts +8 -2
  21. package/src/core/main-agent-types.ts +16 -0
  22. package/src/core/main-agent.ts +26 -0
  23. package/src/core/middlewares/tool-execution.ts +87 -46
  24. package/src/core/middlewares/tool-marks.ts +54 -19
  25. package/src/core/runtime-context.ts +27 -1
  26. package/src/core/state.ts +2 -0
  27. package/src/core/tool-scheduler.ts +129 -24
  28. package/src/delivery/plan.ts +43 -2
  29. package/src/delivery/schema.ts +25 -1
  30. package/src/delivery/types.ts +13 -2
  31. package/src/do/active-runs.ts +171 -0
  32. package/src/do/ambient.ts +6 -0
  33. package/src/do/contracts.ts +86 -3
  34. package/src/do/run-coordinator.ts +30 -0
  35. package/src/do/run-request.ts +15 -2
  36. package/src/do/run-store-test-do.ts +10 -0
  37. package/src/do/run-store.ts +66 -13
  38. package/src/do/turn-body.ts +13 -1
  39. package/src/do/user-oracle-do.ts +1420 -17
  40. package/src/hub-actions/client.ts +163 -0
  41. package/src/hub-actions/request.ts +110 -0
  42. package/src/hub-actions/store.ts +485 -0
  43. package/src/matrix/gateway-do.ts +176 -0
  44. package/src/matrix/reply-parts.ts +8 -2
  45. package/src/plugin-api/tool-helper.ts +6 -0
  46. package/src/plugin-api/types.ts +117 -7
  47. package/src/plugins/action-proposals/action-proposals.plugin.ts +486 -0
  48. package/src/plugins/action-proposals/evidence.ts +67 -0
  49. package/src/plugins/agui/agui.plugin.ts +7 -5
  50. package/src/plugins/hub-actions/hub-actions.plugin.ts +244 -0
  51. package/src/plugins/index.ts +6 -0
  52. package/src/plugins/ixo-transaction/wallet-signing.ts +5 -2
  53. package/src/plugins/portal/declared-tools.ts +12 -0
  54. package/src/plugins/portal/portal.plugin.ts +22 -8
  55. package/src/realtime/frontend-call-registry.ts +147 -27
  56. package/src/realtime/realtime-endpoint.ts +335 -41
  57. package/src/realtime/session-socket-hub.ts +90 -19
  58. package/src/shell/app.ts +218 -2
  59. package/src/sqlite/sessions-store.ts +22 -0
package/README.md CHANGED
@@ -90,6 +90,7 @@ Every variable, binding and migration is explained in
90
90
  | `GET /matrix/status`, `POST /matrix/start` | Gateway status and start |
91
91
  | `POST/GET /sessions`, `POST/GET /messages/:id` | The chat API |
92
92
  | `GET /runs/:runId`, `GET /sessions/:id/messages` | Re-join a durable run; one page of a transcript |
93
+ | `GET /sessions/:id/run`, `GET /runs/active` | A session's active run; every active run of the user |
93
94
  | `/delegation`, `/models`, `/socket.io/`, `/byo-llm/*` | Delegation, models, realtime channel, BYO LLM |
94
95
  | `POST /channels/turn` | IXO Channels ingress; refusals carry a `code` |
95
96
  | `POST /messages/:sessionId/:messageId/feedback` | Anonymous feedback on a reply (when configured) |
@@ -103,19 +104,21 @@ lifetime are in [operations](docs/operations.md).
103
104
 
104
105
  ## Documentation
105
106
 
106
- | Page | What it covers |
107
- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
108
- | [architecture](docs/architecture.md) | The objects, the gateway split, self-sovereign storage, cost model, workerd rules, tasks |
109
- | [node-parity](docs/node-parity.md) | What is identical to the Node runtime, what differs, what is Workers-only, what is not ported |
110
- | [configuration](docs/configuration.md) | Wrangler config, migrations, every env var and binding, `createOracleWorker` options, setup |
111
- | [operations](docs/operations.md) | Routes, status fields, gateway and user-object lifecycle, the ChatGPT proxy, the runbook |
112
- | [chat-delivery](docs/chat-delivery.md) | Chat replies on WhatsApp and Matrix: profiles, Reply Plans, artefacts and their links |
113
- | [channels](docs/channels.md) | The IXO Channels ingress: authorization, retries, Reply Plans, Matrix continuity |
114
- | [frontend-bridge](docs/frontend-bridge.md) | Browser tools and AG-UI actions over the socket: one invocation, one socket, unknown outcomes |
115
- | [pod-creator](docs/pod-creator.md) | The POD Creator plugin: roles, state in `ctx.kv`, the create path and its launch gate |
116
- | [ixo-transaction](docs/ixo-transaction.md) | The opt-in wallet-signing plugin: chain ids, signing outcomes, the mainnet receipt rule |
117
- | [testing](docs/testing.md) | Unit, harness e2e, the devnet feature matrix, stress |
118
- | [load-tests](docs/load-tests.md) | Measurements and the findings that shaped the gateway |
107
+ | Page | What it covers |
108
+ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
109
+ | [architecture](docs/architecture.md) | The objects, the gateway split, self-sovereign storage, cost model, workerd rules, tasks |
110
+ | [node-parity](docs/node-parity.md) | What is identical to the Node runtime, what differs, what is Workers-only, what is not ported |
111
+ | [configuration](docs/configuration.md) | Wrangler config, migrations, every env var and binding, `createOracleWorker` options, setup |
112
+ | [operations](docs/operations.md) | Routes, status fields, gateway and user-object lifecycle, the ChatGPT proxy, the runbook |
113
+ | [chat-delivery](docs/chat-delivery.md) | Chat replies on WhatsApp and Matrix: profiles, Reply Plans, artefacts and their links |
114
+ | [channels](docs/channels.md) | The IXO Channels ingress: authorization, retries, Reply Plans, Matrix continuity |
115
+ | [frontend-bridge](docs/frontend-bridge.md) | Browser tools and AG-UI actions over the socket: one invocation, one socket, unknown outcomes |
116
+ | [pod-creator](docs/pod-creator.md) | The POD Creator plugin: roles, state in `ctx.kv`, the create path and its launch gate |
117
+ | [ixo-transaction](docs/ixo-transaction.md) | The opt-in wallet-signing plugin: chain ids, signing outcomes, the mainnet receipt rule |
118
+ | [action-proposals](docs/action-proposals.md) | Action cards: a transfer prepared from chat, signed in the Portal, verified on chain, receipted |
119
+ | [topic-action-proposals](docs/topic-action-proposals.md) | Topic cards: discussion drafts and claim-access grants as multi-step journals with one-use permits |
120
+ | [testing](docs/testing.md) | Unit, harness e2e, the devnet feature matrix, stress |
121
+ | [load-tests](docs/load-tests.md) | Measurements and the findings that shaped the gateway |
119
122
 
120
123
  ## Status (September 2026)
121
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixo/oracle-runtime-workers",
3
- "version": "0.21.2",
3
+ "version": "0.23.0",
4
4
  "description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -19,6 +19,8 @@
19
19
  "exports": {
20
20
  ".": "./src/index.ts",
21
21
  "./gateway": "./src/gateway-worker.ts",
22
+ "./hub-actions/client": "./src/hub-actions/client.ts",
23
+ "./hub-actions/request": "./src/hub-actions/request.ts",
22
24
  "./prompt": "./src/core/prompt-composer.ts"
23
25
  },
24
26
  "files": [
@@ -31,6 +33,7 @@
31
33
  "!src/plugins/pod-creator/test-fixtures.ts"
32
34
  ],
33
35
  "dependencies": {
36
+ "@ixo/action-protocol": "0.4.1",
34
37
  "@blocknote/server-util": "0.29.1",
35
38
  "@composio/core": "0.10.0",
36
39
  "@composio/langchain": "0.9.2",
@@ -58,9 +61,12 @@
58
61
  "wa-sqlite": "1.0.0",
59
62
  "yjs": "13.6.32",
60
63
  "zod": "^4.4.3",
61
- "@ixo/ucan": "2.2.1",
62
- "@ixo/common": "1.7.0",
63
- "@ixo/ixo-transaction": "0.1.0"
64
+ "@ixo/impactxclient-sdk": "^3.0.0",
65
+ "protobufjs": "^7.6.5",
66
+ "@ixo/topic-protocol": "1.0.0-rc.8",
67
+ "@ixo/common": "1.7.1",
68
+ "@ixo/ixo-transaction": "0.2.0",
69
+ "@ixo/ucan": "2.3.0"
64
70
  },
65
71
  "devDependencies": {
66
72
  "@cloudflare/vitest-pool-workers": "^0.22.0",
@@ -0,0 +1,54 @@
1
+ import type { ActionProposalSubject } from '@ixo/ixo-transaction/action-proposals';
2
+ import { ActionProposalConflict } from './store';
3
+
4
+ /** How long a prepared card can be claimed: the plugin's expiry and the owner object's ceiling. */
5
+ export const ACTION_PROPOSAL_TTL_MS = 15 * 60_000;
6
+ /** How far a card's `createdAt` may sit from the object's clock. */
7
+ export const ACTION_PROPOSAL_CLOCK_SKEW_MS = 5 * 60_000;
8
+
9
+ type Network = ActionProposalSubject['action']['network'];
10
+
11
+ /**
12
+ * What the owner object requires of every card before it is stored, whichever
13
+ * plugin prepared it: the authenticated owner signs from their own account,
14
+ * on a non-mainnet chain the Worker is configured for and can verify against,
15
+ * stamped now, expiring inside the policy window. The contract schema already
16
+ * pins the memo to the card id and one transfer per card.
17
+ */
18
+ export function assertActionProposalPolicy(
19
+ subject: ActionProposalSubject,
20
+ policy: {
21
+ ownerDid: string;
22
+ chainIds: Readonly<Record<Network, string>>;
23
+ rpc: Readonly<Record<Network, string | undefined>>;
24
+ now: number;
25
+ },
26
+ ): void {
27
+ if (subject.ownerDid !== policy.ownerDid)
28
+ throw new ActionProposalConflict('Proposal owner mismatch');
29
+ if (subject.action.network === 'mainnet')
30
+ throw new ActionProposalConflict('Mainnet action proposals are disabled');
31
+ if (subject.action.signerAddress !== policy.ownerDid.replace(/^did:ixo:/, ''))
32
+ throw new ActionProposalConflict(
33
+ 'The signer must be the authenticated account',
34
+ );
35
+ if (subject.action.chainId !== policy.chainIds[subject.action.network])
36
+ throw new ActionProposalConflict(
37
+ 'Chain ID does not match the configured network',
38
+ );
39
+ // A card the Worker could never verify would sit `unknown` for ever.
40
+ if (!policy.rpc[subject.action.network])
41
+ throw new ActionProposalConflict(
42
+ `No chain endpoint is configured for ${subject.action.network}`,
43
+ );
44
+ const createdAt = Date.parse(subject.createdAt);
45
+ if (Math.abs(createdAt - policy.now) > ACTION_PROPOSAL_CLOCK_SKEW_MS)
46
+ throw new ActionProposalConflict('Proposal timestamp is not current');
47
+ if (
48
+ Date.parse(subject.expiresAt) >
49
+ policy.now + ACTION_PROPOSAL_TTL_MS + ACTION_PROPOSAL_CLOCK_SKEW_MS
50
+ )
51
+ throw new ActionProposalConflict(
52
+ 'Proposal expiry exceeds the policy window',
53
+ );
54
+ }
@@ -0,0 +1,87 @@
1
+ import type { ActionProposal } from '@ixo/ixo-transaction/action-proposals';
2
+ import {
3
+ TRANSACTION_CARD_BOUND_MISMATCH,
4
+ TRANSACTION_REJECTIONS,
5
+ type ActionProposalStore,
6
+ } from './store';
7
+ import { TRANSACTION_NOT_FOUND, verifyActionTransaction } from './verify';
8
+ export async function refreshActionProposal(
9
+ store: ActionProposalStore,
10
+ proposal: ActionProposal,
11
+ rpc: string | undefined,
12
+ fetcher: typeof fetch = fetch,
13
+ /** The caller's wall budget: an abort ends the lookup like an RPC outage. */
14
+ signal?: AbortSignal,
15
+ ): Promise<{ proposal: ActionProposal; attempted: boolean }> {
16
+ if (
17
+ proposal.lifecycle.status !== 'unknown' ||
18
+ !proposal.lifecycle.transactionHash ||
19
+ proposal.lifecycle.executionBlockedAt
20
+ )
21
+ return { proposal, attempted: false };
22
+ if (!(await store.claimVerification(proposal.ownerDid, proposal.id)))
23
+ return { proposal, attempted: false };
24
+ if (!rpc) {
25
+ // No endpoint for this card's network (removed after the card was
26
+ // prepared; preparation refuses such a network): the cooldown slot is
27
+ // spent so the alarm backs off and stops instead of ticking every second.
28
+ console.warn(
29
+ `[action-proposals] verification of ${proposal.id} pending: no RPC configured for ${proposal.action.network}`,
30
+ );
31
+ return { proposal, attempted: false };
32
+ }
33
+ try {
34
+ const verified = await verifyActionTransaction(
35
+ proposal,
36
+ proposal.lifecycle.transactionHash,
37
+ rpc,
38
+ fetcher,
39
+ signal,
40
+ );
41
+ return {
42
+ proposal:
43
+ (await store.recordVerified(
44
+ proposal.ownerDid,
45
+ proposal.id,
46
+ verified,
47
+ )) ?? proposal,
48
+ attempted: true,
49
+ };
50
+ } catch (error) {
51
+ // Indexing lag, an RPC outage and a byte mismatch all leave the card
52
+ // `unknown`; the reason is for the operator, never for the user. A
53
+ // memo mismatch proves the hash is not this card's, so a later
54
+ // reconcile may replace it; a transaction with this card's memo and
55
+ // other messages is the owner's card-bound effect gone wrong, so the
56
+ // card is blocked with the hash listed.
57
+ const message = error instanceof Error ? error.message : String(error);
58
+ console.warn(
59
+ `[action-proposals] verification of ${proposal.id} pending: ${message}`,
60
+ );
61
+ if (message === TRANSACTION_NOT_FOUND)
62
+ // Inside the grace window after the hash arrived this is the
63
+ // broadcast or the indexer still in flight, not a spent attempt.
64
+ await store.deferNotFound(
65
+ proposal.ownerDid,
66
+ proposal.id,
67
+ proposal.lifecycle.transactionHash,
68
+ );
69
+ else if (message === TRANSACTION_CARD_BOUND_MISMATCH)
70
+ await store.blockExecution(
71
+ proposal.ownerDid,
72
+ proposal.id,
73
+ message,
74
+ proposal.lifecycle.transactionHash,
75
+ );
76
+ else if (TRANSACTION_REJECTIONS.has(message))
77
+ await store.markTransactionRejected(
78
+ proposal.ownerDid,
79
+ proposal.id,
80
+ proposal.lifecycle.transactionHash,
81
+ );
82
+ return {
83
+ proposal: (await store.get(proposal.ownerDid, proposal.id)) ?? proposal,
84
+ attempted: true,
85
+ };
86
+ }
87
+ }