@ixo/oracle-runtime-workers 0.13.0 → 0.15.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 (205) hide show
  1. package/README.md +32 -10
  2. package/package.json +13 -8
  3. package/src/artifacts/config.ts +158 -0
  4. package/src/artifacts/crypto.ts +94 -0
  5. package/src/artifacts/routes.ts +73 -0
  6. package/src/artifacts/store.ts +238 -0
  7. package/src/artifacts/sweep.ts +200 -0
  8. package/src/artifacts/tool.ts +103 -0
  9. package/src/artifacts/viewer-page.ts +614 -0
  10. package/src/attachments/download.ts +168 -45
  11. package/src/attachments/index.ts +9 -0
  12. package/src/attachments/magic.ts +33 -0
  13. package/src/attachments/pipeline.ts +105 -49
  14. package/src/attachments/sandbox-archive.ts +9 -8
  15. package/src/attachments/view-cache.ts +142 -0
  16. package/src/attachments/view.ts +53 -12
  17. package/src/channels/auth.ts +267 -0
  18. package/src/channels/contract.ts +137 -0
  19. package/src/channels/turns.ts +270 -0
  20. package/src/core/budgeted-llm.ts +223 -0
  21. package/src/core/capability-router.ts +273 -0
  22. package/src/core/context-budget.ts +97 -10
  23. package/src/core/env.ts +111 -0
  24. package/src/core/execution-profile.ts +17 -0
  25. package/src/core/index.ts +267 -9
  26. package/src/core/llm.ts +30 -9
  27. package/src/core/main-agent-types.ts +41 -1
  28. package/src/core/main-agent.ts +367 -65
  29. package/src/core/manifest.ts +34 -0
  30. package/src/core/meta-tools.ts +128 -18
  31. package/src/core/middlewares/capability-gate.ts +130 -12
  32. package/src/core/middlewares/context-guard.ts +55 -24
  33. package/src/core/middlewares/index.ts +20 -2
  34. package/src/core/middlewares/page-context.ts +46 -2
  35. package/src/core/middlewares/return-direct-first.ts +47 -0
  36. package/src/core/middlewares/summarization.ts +152 -10
  37. package/src/core/middlewares/tool-execution.ts +323 -0
  38. package/src/core/middlewares/tool-repetition-guard.ts +202 -35
  39. package/src/core/middlewares/turn-boundary.ts +162 -0
  40. package/src/core/openrouter-pricing.ts +155 -22
  41. package/src/core/prompt-composer.ts +112 -7
  42. package/src/core/registries.ts +253 -48
  43. package/src/core/request-admission.ts +159 -0
  44. package/src/core/runtime-context.ts +107 -8
  45. package/src/core/state.ts +1 -1
  46. package/src/core/sub-agent-fallback.ts +21 -2
  47. package/src/core/subagent-as-tool.ts +78 -43
  48. package/src/core/tool-access.ts +189 -0
  49. package/src/core/tool-scheduler.ts +125 -0
  50. package/src/core/turn-budget.ts +205 -0
  51. package/src/core/turn-time-note.ts +43 -0
  52. package/src/core/user-kv.ts +446 -0
  53. package/src/core/wrap-plugin-tool.ts +20 -0
  54. package/src/delivery/plan.ts +278 -0
  55. package/src/delivery/profile.ts +161 -0
  56. package/src/delivery/prompt.ts +27 -0
  57. package/src/delivery/schema.ts +61 -0
  58. package/src/delivery/shaper.ts +389 -0
  59. package/src/delivery/types.ts +84 -0
  60. package/src/do/ambient.ts +51 -15
  61. package/src/do/contracts.ts +170 -6
  62. package/src/do/idle-eviction.ts +38 -0
  63. package/src/do/matrix-turn-ledger.ts +24 -5
  64. package/src/do/result-store-test-do.ts +137 -3
  65. package/src/do/result-store.ts +239 -58
  66. package/src/do/room-mirror.ts +29 -0
  67. package/src/do/run-buffer.ts +64 -9
  68. package/src/do/run-coordinator.ts +451 -76
  69. package/src/do/run-request.ts +2 -0
  70. package/src/do/run-store-test-do.ts +301 -1
  71. package/src/do/run-store.ts +385 -43
  72. package/src/do/secrets-adapter.ts +22 -3
  73. package/src/do/sse-stream.ts +60 -9
  74. package/src/do/transcript.ts +48 -5
  75. package/src/do/turn-body.ts +105 -5
  76. package/src/do/ucan-service.ts +204 -34
  77. package/src/do/user-oracle-do.ts +1883 -264
  78. package/src/feedback/config.ts +127 -0
  79. package/src/feedback/contract.ts +148 -0
  80. package/src/feedback/linear-sink.ts +285 -0
  81. package/src/feedback/privacy.ts +150 -0
  82. package/src/feedback/reservation.ts +298 -0
  83. package/src/feedback/submit.ts +247 -0
  84. package/src/index.ts +138 -6
  85. package/src/llm/byo-adapter.ts +27 -6
  86. package/src/llm/byo-catalog.ts +13 -0
  87. package/src/llm/byo-model-fallback.ts +248 -0
  88. package/src/llm/byo-service.ts +45 -8
  89. package/src/llm/provider-error.ts +139 -2
  90. package/src/matrix/gateway-do.ts +504 -108
  91. package/src/matrix/group-chat.ts +66 -23
  92. package/src/matrix/inbox-store.ts +16 -1
  93. package/src/matrix/ingest.ts +79 -14
  94. package/src/matrix/media-deadline.ts +130 -0
  95. package/src/matrix/replay-format.ts +5 -3
  96. package/src/matrix/reply-parts.ts +92 -0
  97. package/src/matrix/room-alias.ts +96 -0
  98. package/src/matrix/user-homeserver.ts +166 -7
  99. package/src/owner-store/ixo-vfs-store.ts +170 -43
  100. package/src/owner-store/migrating-store.ts +31 -15
  101. package/src/owner-store/types.ts +18 -3
  102. package/src/plugin-api/index.ts +43 -2
  103. package/src/plugin-api/oracle-plugin.ts +35 -0
  104. package/src/plugin-api/request-admission.ts +41 -0
  105. package/src/plugin-api/tool-helper.ts +25 -1
  106. package/src/plugin-api/tool-plane.ts +92 -0
  107. package/src/plugin-api/types.ts +194 -6
  108. package/src/plugins/agui/agui.plugin.ts +31 -16
  109. package/src/plugins/composio/composio-tools.ts +57 -6
  110. package/src/plugins/composio/composio-ucan.ts +3 -2
  111. package/src/plugins/composio/composio.plugin.ts +2 -0
  112. package/src/plugins/delegated-capabilities.ts +25 -0
  113. package/src/plugins/editor/content-session.ts +173 -83
  114. package/src/plugins/editor/content-tools.ts +76 -17
  115. package/src/plugins/editor/editor-mx.ts +28 -9
  116. package/src/plugins/editor/editor.plugin.ts +3 -1
  117. package/src/plugins/editor/failures.ts +28 -7
  118. package/src/plugins/editor/prompts.ts +2 -1
  119. package/src/plugins/editor/provider.ts +553 -40
  120. package/src/plugins/editor/standalone-editor-tool.ts +61 -18
  121. package/src/plugins/firecrawl/firecrawl-tools.ts +13 -5
  122. package/src/plugins/flows/action-metadata.ts +111 -11
  123. package/src/plugins/flows/actions.ts +18 -0
  124. package/src/plugins/flows/decision-context.ts +31 -0
  125. package/src/plugins/flows/edit.ts +223 -52
  126. package/src/plugins/flows/errors.ts +22 -1
  127. package/src/plugins/flows/flow-doc.ts +92 -9
  128. package/src/plugins/flows/flows.plugin.ts +11 -5
  129. package/src/plugins/flows/forms.ts +34 -0
  130. package/src/plugins/flows/index.ts +2 -0
  131. package/src/plugins/flows/input-policy.ts +140 -0
  132. package/src/plugins/flows/prompts.ts +14 -11
  133. package/src/plugins/flows/read.ts +49 -9
  134. package/src/plugins/flows/ref-syntax.ts +129 -0
  135. package/src/plugins/flows/test-homeserver.ts +245 -0
  136. package/src/plugins/flows/tools/authoring.ts +216 -112
  137. package/src/plugins/flows/tools/discovery.ts +4 -1
  138. package/src/plugins/flows/tools/forms.ts +21 -2
  139. package/src/plugins/flows/tools/settings.ts +34 -13
  140. package/src/plugins/flows/translator.ts +13 -10
  141. package/src/plugins/flows/types.ts +167 -42
  142. package/src/plugins/index.ts +11 -1
  143. package/src/plugins/ixo-transaction/chain-ids.ts +43 -0
  144. package/src/plugins/ixo-transaction/index.ts +2 -0
  145. package/src/plugins/ixo-transaction/ixo-transaction.plugin.ts +119 -0
  146. package/src/plugins/ixo-transaction/receipts.ts +99 -0
  147. package/src/plugins/ixo-transaction/tools.ts +329 -0
  148. package/src/plugins/ixo-transaction/wallet-signing.ts +202 -0
  149. package/src/plugins/mcp-tool-adapter.ts +23 -2
  150. package/src/plugins/memory/memory-tools.ts +7 -2
  151. package/src/plugins/memory/memory-ucan.ts +3 -2
  152. package/src/plugins/memory/memory.plugin.ts +2 -0
  153. package/src/plugins/pod-creator/blueprint-store.ts +163 -0
  154. package/src/plugins/pod-creator/blueprint-types.ts +61 -0
  155. package/src/plugins/pod-creator/bounded-map.ts +87 -0
  156. package/src/plugins/pod-creator/capsule-content-client.ts +285 -0
  157. package/src/plugins/pod-creator/chain-gateway.ts +110 -0
  158. package/src/plugins/pod-creator/config.ts +85 -0
  159. package/src/plugins/pod-creator/create-session-store.ts +180 -0
  160. package/src/plugins/pod-creator/create-tools.ts +538 -0
  161. package/src/plugins/pod-creator/design-pod-roles.ts +111 -0
  162. package/src/plugins/pod-creator/index.ts +65 -0
  163. package/src/plugins/pod-creator/orchestration-tools.ts +172 -0
  164. package/src/plugins/pod-creator/pod-creator.plugin.ts +190 -0
  165. package/src/plugins/pod-creator/stage.ts +201 -0
  166. package/src/plugins/pod-creator/sub-agents.ts +189 -0
  167. package/src/plugins/portal/action-log.ts +21 -1
  168. package/src/plugins/portal/declared-tools.ts +90 -0
  169. package/src/plugins/portal/portal.plugin.ts +42 -16
  170. package/src/plugins/sandbox/sandbox-bridge.ts +79 -10
  171. package/src/plugins/sandbox/sandbox-mcp.ts +3 -2
  172. package/src/plugins/sandbox/sandbox-write-blob.ts +11 -1
  173. package/src/plugins/sandbox/sandbox.plugin.ts +8 -16
  174. package/src/plugins/tasks/manifest.ts +1 -1
  175. package/src/plugins/tasks/middleware.ts +20 -7
  176. package/src/plugins/tasks/tasks-tools.ts +30 -3
  177. package/src/plugins/tasks/tasks.plugin.ts +64 -11
  178. package/src/plugins/user-preferences/schema.ts +12 -1
  179. package/src/plugins/user-preferences/user-preferences-store.ts +38 -13
  180. package/src/plugins/user-preferences/user-preferences-tool.ts +3 -2
  181. package/src/plugins/vfs/vfs-client.ts +250 -71
  182. package/src/plugins/vfs/vfs-content.ts +30 -11
  183. package/src/plugins/vfs/vfs-errors.ts +22 -0
  184. package/src/plugins/vfs/vfs-sandbox-tools.ts +12 -5
  185. package/src/plugins/vfs/vfs-tools.ts +60 -20
  186. package/src/realtime/frontend-call-registry.ts +228 -81
  187. package/src/realtime/realtime-endpoint.ts +215 -44
  188. package/src/realtime/session-socket-hub.ts +47 -2
  189. package/src/secrets/secrets-service.ts +9 -3
  190. package/src/shell/app.ts +529 -82
  191. package/src/shell/auth.ts +131 -37
  192. package/src/shell/turn-body-cap.ts +11 -13
  193. package/src/sqlite/blob-compactor.ts +61 -28
  194. package/src/sqlite/database.ts +156 -90
  195. package/src/sqlite/do-vfs.ts +416 -129
  196. package/src/sqlite/page-tier.ts +133 -40
  197. package/src/sqlite/serialization.ts +40 -0
  198. package/src/sqlite/sessions-store.ts +10 -2
  199. package/src/sqlite/sqlite-saver.ts +139 -19
  200. package/src/sqlite/user-kv-store.ts +317 -0
  201. package/src/tasks/schedule.ts +201 -12
  202. package/src/tasks/scheduler.ts +586 -98
  203. package/src/tasks/spec.ts +67 -3
  204. package/src/tasks/store.ts +292 -27
  205. package/src/tasks/topic-deliverables.ts +70 -0
@@ -0,0 +1,538 @@
1
+ import {
2
+ ITrxMsgSchema,
3
+ NetworkSchema,
4
+ RiskConfirmationSchema,
5
+ SIGN_TRANSACTION_ACTION_NAME,
6
+ buildBatchSignTransactionActionArgs,
7
+ describeValidationError,
8
+ validateTransactionBatch,
9
+ type Network,
10
+ type RiskConfirmation,
11
+ type ValidatedTransactionBatch,
12
+ } from '@ixo/ixo-transaction';
13
+ import { z } from 'zod';
14
+ import { tool } from '../../plugin-api/tool-helper';
15
+ import type { PluginTool, RuntimeContext } from '../../plugin-api/types';
16
+ import {
17
+ requestWalletSignature,
18
+ walletUnavailableReason,
19
+ } from '../ixo-transaction/wallet-signing';
20
+ import type { BlueprintStoreFor } from './blueprint-store';
21
+ import {
22
+ notConfiguredChainGateway,
23
+ podBatchProblem,
24
+ type ChainGateway,
25
+ } from './chain-gateway';
26
+ import { readPodCreatorConfig } from './config';
27
+ import type { CreateSessionStoreFor } from './create-session-store';
28
+ import { assembleServicePodBlueprint, computeReadiness } from './stage';
29
+
30
+ const threadId = (ctx: RuntimeContext): string => ctx.session.id;
31
+
32
+ /**
33
+ * The AG-UI action that signs POD creation batches: the same
34
+ * `sign_transaction` action, and the same Portal handler, as
35
+ * `IxoTransactionPlugin` (`@ixo/ixo-transaction/react`).
36
+ */
37
+ export const SIGN_TRANSACTION_ACTION = SIGN_TRANSACTION_ACTION_NAME;
38
+
39
+ /**
40
+ * Wallet review + broadcast takes far longer than a UI render, so the sign
41
+ * round-trip gets its own generous deadline.
42
+ */
43
+ export const SIGN_TIMEOUT_MS = 120_000;
44
+
45
+ /** Cosmos SDK transaction hash: 32 bytes hex. */
46
+ const TX_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
47
+
48
+ /** The blob store name of a prepared batch. */
49
+ export const POD_BATCH_BLOB_NAME = 'pod-batch-messages';
50
+
51
+ const blobIdSchema = z.object({
52
+ blobId: z
53
+ .string()
54
+ .describe('The blobId returned by prepare_pod_transaction.'),
55
+ });
56
+
57
+ const approveSchema = blobIdSchema.extend({
58
+ riskConfirmation: RiskConfirmationSchema.describe(
59
+ 'The risks prepare_pod_transaction listed, each quoted exactly as the user accepted it, with confirmed: true.',
60
+ ),
61
+ });
62
+
63
+ const confirmSchema = z.object({
64
+ txHash: z
65
+ .string()
66
+ .regex(TX_HASH_PATTERN, 'expected a 64-character hex transaction hash')
67
+ .describe('The transaction hash the wallet returned after broadcasting.'),
68
+ });
69
+
70
+ /**
71
+ * The prepared batch as the blob store keeps it: the gateway's messages after
72
+ * catalog validation (canonical proto-JSON), and what they are for. The
73
+ * model only ever sees the blobId and the message names.
74
+ */
75
+ const storedBatchSchema = z
76
+ .object({
77
+ network: NetworkSchema,
78
+ summary: z.string(),
79
+ messages: z.array(ITrxMsgSchema),
80
+ })
81
+ .strict();
82
+
83
+ type StoredBatch = z.infer<typeof storedBatchSchema>;
84
+
85
+ const CHAIN_UNAVAILABLE_MESSAGE =
86
+ 'On-chain POD creation is not yet enabled on this oracle — the chain ' +
87
+ 'gateway is not configured. The design blueprint is saved; creation can ' +
88
+ 'proceed once the operator wires the IXO MCP chain gateway.';
89
+
90
+ /**
91
+ * Validate a blobId and return the stored batch, throwing when it is missing,
92
+ * expired or not a POD batch so the agent knows to re-prepare.
93
+ */
94
+ async function requirePreparedBatch(
95
+ ctx: RuntimeContext,
96
+ blobId: string,
97
+ ): Promise<StoredBatch> {
98
+ if (!ctx.blobStore.isValidBlobId(blobId)) {
99
+ throw new Error(`Invalid blobId: ${blobId}`);
100
+ }
101
+ const blob = await ctx.blobStore.get({ userDid: ctx.user.did, blobId });
102
+ if (!blob) {
103
+ throw new Error(
104
+ 'Prepared transaction not found or expired. Call prepare_pod_transaction again.',
105
+ );
106
+ }
107
+ const stored =
108
+ blob.name === POD_BATCH_BLOB_NAME
109
+ ? storedBatchSchema.safeParse(parseJson(blob.value))
110
+ : undefined;
111
+ if (!stored?.success) {
112
+ throw new Error(
113
+ 'That blobId does not hold a prepared POD batch. Call prepare_pod_transaction again.',
114
+ );
115
+ }
116
+ return stored.data;
117
+ }
118
+
119
+ function parseJson(text: string): unknown {
120
+ try {
121
+ return JSON.parse(text);
122
+ } catch {
123
+ return undefined;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * The stored batch through the catalog again (it was valid when prepared;
129
+ * the catalog is the authority at every step), with the user's risk
130
+ * acceptance when given.
131
+ */
132
+ function validateStoredBatch(
133
+ batch: StoredBatch,
134
+ options: {
135
+ mainnetAllowed: boolean;
136
+ riskConfirmation?: RiskConfirmation;
137
+ },
138
+ ): ValidatedTransactionBatch {
139
+ return validateTransactionBatch(
140
+ { ...batch, riskConfirmation: options.riskConfirmation },
141
+ {
142
+ allowMainnet: options.mainnetAllowed,
143
+ requireRiskConfirmation: options.riskConfirmation !== undefined,
144
+ },
145
+ );
146
+ }
147
+
148
+ /** What `request_pod_signature` returns to the model. */
149
+ export type RequestPodSignatureResult =
150
+ | {
151
+ status: 'signed';
152
+ network: Network;
153
+ chainId: string;
154
+ /** Absent when the wallet gave none; confirm_pod_creation needs it. */
155
+ txHash?: string;
156
+ code?: number;
157
+ height?: string | number;
158
+ message: string;
159
+ }
160
+ | {
161
+ /** Included in a block but failed: nothing was created. */
162
+ status: 'failed';
163
+ network: Network;
164
+ chainId: string;
165
+ code: number;
166
+ txHash?: string;
167
+ height?: string | number;
168
+ error: string;
169
+ message: string;
170
+ }
171
+ | {
172
+ status: 'rejected' | 'error';
173
+ network: Network;
174
+ error: string;
175
+ message: string;
176
+ }
177
+ | {
178
+ status: 'timeout';
179
+ /** The wallet may still sign: the write claim stays. */
180
+ outcome: 'unknown';
181
+ network: Network;
182
+ error: string;
183
+ }
184
+ | { status: 'unavailable'; error: string; message: string };
185
+
186
+ const RETRY_AFTER_SPENT_APPROVAL =
187
+ 'Nothing was signed and the approval was spent: to retry, the user confirms again in a new message and you call approve_pod_transaction, then request_pod_signature.';
188
+
189
+ /**
190
+ * The on-chain create path — a propose → approve → commit handoff that keeps
191
+ * the oracle out of the signing loop:
192
+ *
193
+ * - `prepare_pod_transaction` (propose) has the gateway compose the batch's
194
+ * proto-JSON messages, validates every one against the `@ixo/ixo-transaction`
195
+ * catalog (and that it is a POD batch), and stashes them in the blob store
196
+ * (the LLM only sees a short `blobId`, the message names and the risks). It
197
+ * refuses on mainnet unless `POD_CREATOR_ALLOW_MAINNET` is set, and
198
+ * supersedes any prior approval.
199
+ * - `approve_pod_transaction` (approve) binds the user's go-ahead to the exact
200
+ * batch prepared in this conversation — with every risk of every message
201
+ * accepted word for word — and refuses within the turn that prepared it,
202
+ * so approval needs a new user message after the summary was shown. The
203
+ * model still calls it: what it records is "a later turn said yes", not a
204
+ * verified human act.
205
+ * - `request_pod_signature` (commit) re-checks the launch gate, SPENDS the
206
+ * approval, then sends the batch as the batch form of the ixo-transaction
207
+ * `sign_transaction` action over the realtime channel — the same contract
208
+ * and the same Portal handler as `sign_ixo_transaction` — which re-validates
209
+ * every message and signs them in one wallet transaction. A second dispatch
210
+ * always needs a fresh approval — a sign request cannot be replayed.
211
+ * - `confirm_pod_creation` resolves the created POD from the broadcast tx and
212
+ * closes the session.
213
+ *
214
+ * The whole path only matters once the launch-readiness gate passes. The
215
+ * binding hard gates are the operator's mainnet opt-in and the wallet
216
+ * signature — the user reviewing and signing in their own wallet is the real
217
+ * human gate. The oracle never signs creation: nothing here holds or asks for
218
+ * a signing key, and what leaves is the validated, unsigned messages.
219
+ *
220
+ * Both stores are resolved per call from the request context (on Workers they
221
+ * are rows in the user's own database, so an approval survives the user
222
+ * object being evicted between the user's "yes" and the sign request).
223
+ */
224
+ export function createCreateTools(
225
+ blueprintsFor: BlueprintStoreFor,
226
+ gateway: ChainGateway,
227
+ sessionsFor: CreateSessionStoreFor,
228
+ ): PluginTool[] {
229
+ const chainUnavailable = gateway === notConfiguredChainGateway;
230
+
231
+ const prepare = tool(
232
+ async (_args, ctx) => {
233
+ const bp = await blueprintsFor(ctx).get(threadId(ctx));
234
+ if (!bp) {
235
+ return {
236
+ prepared: false,
237
+ message:
238
+ 'No POD design session started yet. Call start_pod_design first.',
239
+ };
240
+ }
241
+ const readiness = computeReadiness(bp);
242
+ if (!readiness.complete) {
243
+ return {
244
+ prepared: false,
245
+ stage: readiness.stage,
246
+ blockers: readiness.blockers,
247
+ message:
248
+ 'Launch-readiness gate not passed; cannot prepare the creation transaction yet.',
249
+ };
250
+ }
251
+ if (chainUnavailable) {
252
+ return { prepared: false, message: CHAIN_UNAVAILABLE_MESSAGE };
253
+ }
254
+ const { network, mainnetAllowed } = readPodCreatorConfig(ctx);
255
+ if (network === 'mainnet' && !mainnetAllowed) {
256
+ return {
257
+ prepared: false,
258
+ message:
259
+ 'Mainnet POD creation is disabled. Set POD_CREATOR_ALLOW_MAINNET=true in the oracle config to allow preparing a mainnet creation batch.',
260
+ };
261
+ }
262
+ const blueprint = assembleServicePodBlueprint(bp);
263
+ const prepared = await gateway.preparePodBatch(
264
+ { blueprint, network },
265
+ ctx,
266
+ );
267
+ // The wallet's handler refuses anything outside the catalog, so a
268
+ // batch it would refuse is caught here, before the user is asked.
269
+ let batch: ValidatedTransactionBatch;
270
+ try {
271
+ batch = validateTransactionBatch(
272
+ {
273
+ messages: prepared.messages,
274
+ summary: prepared.summary,
275
+ network,
276
+ },
277
+ { allowMainnet: mainnetAllowed },
278
+ );
279
+ } catch (error) {
280
+ throw new Error(
281
+ `The chain gateway built a batch the wallet would refuse: ${describeValidationError(error)}`,
282
+ );
283
+ }
284
+ const notPod = podBatchProblem(batch.routes);
285
+ if (notPod !== null) {
286
+ throw new Error(`The chain gateway built a wrong batch: ${notPod}`);
287
+ }
288
+ const stored: StoredBatch = {
289
+ network: batch.network,
290
+ summary: batch.summary,
291
+ messages: batch.messages,
292
+ };
293
+ const blobId = await ctx.blobStore.put({
294
+ userDid: ctx.user.did,
295
+ name: POD_BATCH_BLOB_NAME,
296
+ value: JSON.stringify(stored),
297
+ });
298
+ await sessionsFor(ctx).prepared(
299
+ ctx.user.did,
300
+ threadId(ctx),
301
+ blobId,
302
+ ctx.session.requestId,
303
+ );
304
+ ctx.logger.log(
305
+ `[pod-creator] prepared batch ${blobId} (${batch.messages.length} msgs, ${network}) user=${ctx.user.did} thread=${threadId(ctx)}`,
306
+ );
307
+ return {
308
+ prepared: true,
309
+ blobId,
310
+ summary: batch.summary,
311
+ messageCount: batch.messages.length,
312
+ messages: batch.routes.map((route) => route.messageName),
313
+ risks: batch.risks,
314
+ riskLevel: batch.riskLevel,
315
+ ...(prepared.estimatedCost !== undefined
316
+ ? { estimatedCost: prepared.estimatedCost }
317
+ : {}),
318
+ message:
319
+ 'Transaction batch prepared. Show the user the summary and every risk, word for word, and stop: approve_pod_transaction only works on a later message in which the user explicitly confirms, and needs each risk they accepted quoted exactly.',
320
+ };
321
+ },
322
+ {
323
+ name: 'prepare_pod_transaction',
324
+ description:
325
+ 'Build the on-chain POD creation batch (unsigned messages) from the approved blueprint and stash it for the user to sign. Returns its summary, message names and risks. Only works once the launch-readiness gate has passed; refuses on mainnet unless the operator opted in.',
326
+ schema: z.object({}),
327
+ },
328
+ );
329
+
330
+ const approve = tool(
331
+ async (args, ctx) => {
332
+ const { blobId, riskConfirmation } = approveSchema.parse(args);
333
+ const batch = await requirePreparedBatch(ctx, blobId);
334
+ // Approval means every risk of every message was accepted word for
335
+ // word, as `sign_ixo_transaction` requires of a single message.
336
+ try {
337
+ validateStoredBatch(batch, {
338
+ mainnetAllowed: readPodCreatorConfig(ctx).mainnetAllowed,
339
+ riskConfirmation,
340
+ });
341
+ } catch (error) {
342
+ return {
343
+ approved: false,
344
+ message: describeValidationError(error),
345
+ };
346
+ }
347
+ const outcome = await sessionsFor(ctx).approve(
348
+ ctx.user.did,
349
+ threadId(ctx),
350
+ blobId,
351
+ ctx.session.requestId,
352
+ );
353
+ if (outcome === 'not-prepared') {
354
+ return {
355
+ approved: false,
356
+ message:
357
+ 'That blobId is not the batch prepared in this conversation. Call prepare_pod_transaction and approve the blobId it returns.',
358
+ };
359
+ }
360
+ if (outcome === 'same-request') {
361
+ return {
362
+ approved: false,
363
+ message:
364
+ 'The batch was prepared in this same turn. Show the user the summary and wait for their reply; approval is only accepted on a later message in which they confirm it.',
365
+ };
366
+ }
367
+ ctx.logger.log(
368
+ `[pod-creator] approved batch ${blobId} user=${ctx.user.did} thread=${threadId(ctx)}`,
369
+ );
370
+ return {
371
+ approved: true,
372
+ message:
373
+ 'Approval recorded. Call request_pod_signature to send the batch to the wallet for signing.',
374
+ };
375
+ },
376
+ {
377
+ name: 'approve_pod_transaction',
378
+ description:
379
+ "Record the user's explicit approval of the prepared batch (blobId), with every risk prepare_pod_transaction listed quoted exactly in riskConfirmation.acceptedRisks. Call only after showing the batch summary and its risks and the user confirms in their own words in a later message — it refuses in the turn that prepared the batch. request_pod_signature refuses until this approval is recorded.",
380
+ schema: approveSchema,
381
+ },
382
+ );
383
+
384
+ const requestSignature = tool(
385
+ async (args, ctx): Promise<RequestPodSignatureResult> => {
386
+ const { blobId } = blobIdSchema.parse(args);
387
+ const batch = await requirePreparedBatch(ctx, blobId);
388
+ const { network, mainnetAllowed, chainIds } = readPodCreatorConfig(ctx);
389
+ if (
390
+ (network === 'mainnet' || batch.network === 'mainnet') &&
391
+ !mainnetAllowed
392
+ ) {
393
+ throw new Error(
394
+ 'Mainnet POD creation is disabled; cannot request a signature.',
395
+ );
396
+ }
397
+ // The gate is checked again here, before the approval is spent: a
398
+ // design restarted, expired or re-opened by a failed gate verdict since
399
+ // the batch was prepared must not reach the wallet.
400
+ const bp = await blueprintsFor(ctx).get(threadId(ctx));
401
+ if (!bp || !computeReadiness(bp).complete) {
402
+ throw new Error(
403
+ 'The launch-readiness gate no longer passes for this design (it was restarted, expired or re-opened since the batch was prepared). Resolve the blockers and call prepare_pod_transaction again.',
404
+ );
405
+ }
406
+ // Nothing is sent without a way to the wallet, so the approval is kept.
407
+ const unavailable = walletUnavailableReason(ctx);
408
+ if (unavailable !== null) {
409
+ return {
410
+ status: 'unavailable',
411
+ error: unavailable,
412
+ message:
413
+ 'Nothing was sent and the approval is kept: call request_pod_signature again once the user has the chat open in the Portal.',
414
+ };
415
+ }
416
+ // The approval stands for every risk accepted word for word (checked
417
+ // by approve_pod_transaction against this same stored batch).
418
+ const risks = validateStoredBatch(batch, { mainnetAllowed }).risks;
419
+ const signArgs = buildBatchSignTransactionActionArgs(
420
+ {
421
+ ...batch,
422
+ riskConfirmation: { confirmed: true, acceptedRisks: risks },
423
+ },
424
+ { allowMainnet: mainnetAllowed, chainIds },
425
+ );
426
+ const consumed = await sessionsFor(ctx).consume(
427
+ ctx.user.did,
428
+ threadId(ctx),
429
+ blobId,
430
+ );
431
+ if (!consumed) {
432
+ throw new Error(
433
+ 'Transaction not approved (or its approval was already used). Call approve_pod_transaction after the user explicitly confirms the batch.',
434
+ );
435
+ }
436
+ const outcome = await requestWalletSignature(ctx, {
437
+ args: signArgs,
438
+ callIdPrefix: 'pod',
439
+ timeoutMs: SIGN_TIMEOUT_MS,
440
+ });
441
+ const audit = `batch ${blobId} (${signArgs.chainId}) user=${ctx.user.did} thread=${threadId(ctx)}`;
442
+ switch (outcome.status) {
443
+ case 'signed': {
444
+ const { transactionHash, ...rest } = outcome;
445
+ ctx.logger.log(
446
+ `[pod-creator] wallet signed ${audit} tx=${transactionHash ?? 'none'}`,
447
+ );
448
+ return {
449
+ ...rest,
450
+ network: signArgs.network,
451
+ chainId: signArgs.chainId,
452
+ ...(transactionHash !== undefined
453
+ ? {
454
+ txHash: transactionHash,
455
+ message:
456
+ 'The wallet signed and broadcast the batch. Call confirm_pod_creation with this txHash.',
457
+ }
458
+ : {
459
+ message:
460
+ 'The wallet reports the batch signed but returned no transaction hash. Ask the user for it, then call confirm_pod_creation with it.',
461
+ }),
462
+ };
463
+ }
464
+ case 'failed': {
465
+ const { transactionHash, ...rest } = outcome;
466
+ ctx.logger.warn(
467
+ `[pod-creator] batch failed on chain ${audit} code=${outcome.code} tx=${transactionHash ?? 'none'}`,
468
+ );
469
+ return {
470
+ ...rest,
471
+ network: signArgs.network,
472
+ chainId: signArgs.chainId,
473
+ ...(transactionHash !== undefined
474
+ ? { txHash: transactionHash }
475
+ : {}),
476
+ message:
477
+ 'The transaction was included in a block but failed, so no POD was created (a transaction is all or nothing). Tell the user the code and error; once the cause is fixed, call prepare_pod_transaction again.',
478
+ };
479
+ }
480
+ case 'rejected':
481
+ case 'error':
482
+ ctx.logger.warn(
483
+ `[pod-creator] wallet ${outcome.status} ${audit}: ${outcome.error}`,
484
+ );
485
+ return {
486
+ ...outcome,
487
+ network: signArgs.network,
488
+ message: RETRY_AFTER_SPENT_APPROVAL,
489
+ };
490
+ case 'timeout':
491
+ ctx.logger.warn(`[pod-creator] sign outcome unknown ${audit}`);
492
+ return { ...outcome, network: signArgs.network };
493
+ case 'unavailable':
494
+ ctx.logger.warn(
495
+ `[pod-creator] wallet unavailable ${audit}: ${outcome.error}`,
496
+ );
497
+ return { ...outcome, message: RETRY_AFTER_SPENT_APPROVAL };
498
+ }
499
+ },
500
+ {
501
+ name: 'request_pod_signature',
502
+ description:
503
+ "Send the approved POD creation batch to the user's Portal wallet to sign and broadcast as one transaction, and return the outcome: signed (with the txHash for confirm_pod_creation), failed (on chain, with code and hash; nothing was created), rejected, error, timeout (outcome unknown: do not send it again, ask the user) or unavailable. Spends the approval once the request is sent — each dispatch needs a fresh approve_pod_transaction.",
504
+ schema: blobIdSchema,
505
+ },
506
+ );
507
+
508
+ const confirm = tool(
509
+ async (args, ctx) => {
510
+ const { txHash } = confirmSchema.parse(args);
511
+ if (chainUnavailable) {
512
+ return { created: false, message: CHAIN_UNAVAILABLE_MESSAGE };
513
+ }
514
+ const { network } = readPodCreatorConfig(ctx);
515
+ const created = await gateway.confirmPodCreation(
516
+ { txHash, network },
517
+ ctx,
518
+ );
519
+ await sessionsFor(ctx).clear(ctx.user.did, threadId(ctx));
520
+ ctx.logger.log(
521
+ `[pod-creator] confirmed POD ${created.podDid} tx=${txHash} user=${ctx.user.did} thread=${threadId(ctx)}`,
522
+ );
523
+ return {
524
+ created: true,
525
+ podDid: created.podDid,
526
+ summary: created.summary,
527
+ };
528
+ },
529
+ {
530
+ name: 'confirm_pod_creation',
531
+ description:
532
+ 'Confirm the POD was created on-chain from the signed transaction hash; returns the new POD DID.',
533
+ schema: confirmSchema,
534
+ },
535
+ );
536
+
537
+ return [prepare, approve, requestSignature, confirm];
538
+ }
@@ -0,0 +1,111 @@
1
+ /** The lifecycle stages in readiness order. */
2
+ export const DESIGN_POD_STAGES = [
3
+ 'qualify',
4
+ 'architect',
5
+ 'build',
6
+ 'evaluate',
7
+ 'package',
8
+ 'gate',
9
+ ] as const;
10
+
11
+ /** Lifecycle stage a specialist role belongs to (see the readiness pipeline). */
12
+ export type DesignPodStage = (typeof DESIGN_POD_STAGES)[number];
13
+
14
+ /** A design-pod specialist role, 1:1 with a published ai-skills capsule. */
15
+ export interface DesignPodRole {
16
+ /** Stable role id; the sub-agent tool the conductor sees is `call_<id>_agent`. */
17
+ id: string;
18
+ /** Capsule name in the ai-skills registry (the published SKILL.md folder). */
19
+ capsule: string;
20
+ /** Short description surfaced to the conductor. */
21
+ description: string;
22
+ /** Lifecycle stage that gates when this role runs. */
23
+ stage: DesignPodStage;
24
+ }
25
+
26
+ /**
27
+ * The twelve specialist roles. `concierge` and `orchestration` are NOT here —
28
+ * they are realised by the main-agent conductor (manifest + orchestration
29
+ * tools), not as sub-agents, since sub-agents are leaves and cannot call peers.
30
+ */
31
+ export const DESIGN_POD_ROLES = [
32
+ {
33
+ id: 'service_intent_scorer',
34
+ capsule: 'design-pod-service-intent-scorer',
35
+ description:
36
+ 'Scores and qualifies the incoming service intent (viability and fit).',
37
+ stage: 'qualify',
38
+ },
39
+ {
40
+ id: 'service_architect',
41
+ capsule: 'design-pod-service-architect',
42
+ description:
43
+ 'Designs the POD service structure: roles, workspaces, and services.',
44
+ stage: 'architect',
45
+ },
46
+ {
47
+ id: 'claims_architect',
48
+ capsule: 'design-pod-claims-architect',
49
+ description: 'Designs the claim schemas and the UDID model.',
50
+ stage: 'architect',
51
+ },
52
+ {
53
+ id: 'ucan_rights_architect',
54
+ capsule: 'design-pod-ucan-rights-architect',
55
+ description:
56
+ 'Designs the rights model: UCAN delegations and root documents.',
57
+ stage: 'architect',
58
+ },
59
+ {
60
+ id: 'flow_builder',
61
+ capsule: 'design-pod-flow-builder',
62
+ description:
63
+ "Builds the Flow pages — the POD's executable workflow and UX.",
64
+ stage: 'build',
65
+ },
66
+ {
67
+ id: 'playbook_creation_agent',
68
+ capsule: 'design-pod-playbook-creation-agent',
69
+ description: 'Authors operating playbooks and rule cards.',
70
+ stage: 'build',
71
+ },
72
+ {
73
+ id: 'automation_feasibility_oracle',
74
+ capsule: 'design-pod-automation-feasibility-oracle',
75
+ description:
76
+ 'Evaluates what can be automated versus what needs a human in the loop.',
77
+ stage: 'evaluate',
78
+ },
79
+ {
80
+ id: 'governance_risk_oracle',
81
+ capsule: 'design-pod-governance-risk-oracle',
82
+ description: 'Evaluates the governance and risk posture.',
83
+ stage: 'evaluate',
84
+ },
85
+ {
86
+ id: 'outcome_contract_oracle',
87
+ capsule: 'design-pod-outcome-contract-oracle',
88
+ description:
89
+ 'Defines and evaluates the outcome contract (what success pays for).',
90
+ stage: 'evaluate',
91
+ },
92
+ {
93
+ id: 'commercial_packager',
94
+ capsule: 'design-pod-commercial-packager',
95
+ description:
96
+ 'Packages the commercial offer and drafts the marketplace listing.',
97
+ stage: 'package',
98
+ },
99
+ {
100
+ id: 'demo_builder',
101
+ capsule: 'design-pod-demo-builder',
102
+ description: 'Builds a runnable demo of the POD.',
103
+ stage: 'package',
104
+ },
105
+ {
106
+ id: 'qa_launch_readiness_oracle',
107
+ capsule: 'design-pod-qa-launch-readiness-oracle',
108
+ description: 'Runs final QA and the launch-readiness gate.',
109
+ stage: 'gate',
110
+ },
111
+ ] as const satisfies readonly DesignPodRole[];