@ixo/oracle-runtime-workers 0.12.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 +14 -9
  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 +198 -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 +523 -111
  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,65 @@
1
+ export {
2
+ PodCreatorPlugin,
3
+ type PodCreatorPluginOptions,
4
+ } from './pod-creator.plugin';
5
+ export {
6
+ CapsuleContentClient,
7
+ DEFAULT_CAPSULES_BASE_URL,
8
+ createRegistryInstructionsFetcher,
9
+ MAX_CAPSULE_INSTRUCTIONS_BYTES,
10
+ type CapsuleContentClientOptions,
11
+ type CapsuleContentFetcher,
12
+ type CapsuleFetchContext,
13
+ type CapsuleUcanBuilder,
14
+ } from './capsule-content-client';
15
+ export {
16
+ DESIGN_POD_ROLES,
17
+ DESIGN_POD_STAGES,
18
+ type DesignPodRole,
19
+ type DesignPodStage,
20
+ } from './design-pod-roles';
21
+ export {
22
+ type BlueprintSection,
23
+ type PodBlueprint,
24
+ type ServicePodBlueprint,
25
+ } from './blueprint-types';
26
+ export {
27
+ KvBlueprintStore,
28
+ type BlueprintStore,
29
+ type BlueprintStoreFor,
30
+ } from './blueprint-store';
31
+ export {
32
+ SPECIALISTS_FOR_STAGE,
33
+ STAGE_ORDER,
34
+ assembleServicePodBlueprint,
35
+ computeReadiness,
36
+ deriveStage,
37
+ type Readiness,
38
+ } from './stage';
39
+ export { createOrchestrationTools } from './orchestration-tools';
40
+ export { buildStageSubAgents } from './sub-agents';
41
+ export {
42
+ SIGN_TRANSACTION_ACTION,
43
+ createCreateTools,
44
+ type RequestPodSignatureResult,
45
+ } from './create-tools';
46
+ export {
47
+ POD_BATCH_TYPE_URLS,
48
+ notConfiguredChainGateway,
49
+ podBatchProblem,
50
+ type ChainGateway,
51
+ type CreatedPod,
52
+ type PreparedPodBatch,
53
+ } from './chain-gateway';
54
+ export {
55
+ KvCreateSessionStore,
56
+ type ApproveOutcome,
57
+ type CreateSessionStore,
58
+ type CreateSessionStoreFor,
59
+ } from './create-session-store';
60
+ export {
61
+ DEFAULT_NETWORK,
62
+ podCreatorConfigSchema,
63
+ readPodCreatorConfig,
64
+ } from './config';
65
+ export { BoundedMap, type BoundedMapOptions } from './bounded-map';
@@ -0,0 +1,172 @@
1
+ import { z } from 'zod';
2
+ import { computeSubAgentToolName } from '../../core/subagent-as-tool';
3
+ import { tool } from '../../plugin-api/tool-helper';
4
+ import type { PluginTool, RuntimeContext } from '../../plugin-api/types';
5
+ import type { BlueprintStoreFor } from './blueprint-store';
6
+ import type { CreateSessionStoreFor } from './create-session-store';
7
+ import {
8
+ SPECIALISTS_FOR_STAGE,
9
+ assembleServicePodBlueprint,
10
+ computeReadiness,
11
+ sectionsView,
12
+ } from './stage';
13
+
14
+ const startSchema = z.object({
15
+ brief: z
16
+ .string()
17
+ .min(1)
18
+ .describe(
19
+ 'A concise statement of what the POD should do — the service, who it serves, and the outcome it produces.',
20
+ ),
21
+ restart: z
22
+ .boolean()
23
+ .optional()
24
+ .describe(
25
+ 'Discard any existing design in this conversation and start fresh with the new brief.',
26
+ ),
27
+ });
28
+
29
+ const getBlueprintSchema = z.object({
30
+ roles: z
31
+ .array(z.string())
32
+ .optional()
33
+ .describe(
34
+ 'Role ids whose full section content to include. Omit for the compact per-section summary.',
35
+ ),
36
+ });
37
+
38
+ const emptySchema = z.object({});
39
+
40
+ const threadId = (ctx: RuntimeContext): string => ctx.session.id;
41
+
42
+ /**
43
+ * The conductor's orchestration tools. They own the blueprint lifecycle —
44
+ * starting a session, scoring readiness, and assembling the final
45
+ * `service_pod_blueprint`. Sections are written ONLY by the specialist
46
+ * sub-agents' `submit_section`; the conductor gets no write path, so it cannot
47
+ * self-certify gate verdicts and skip the specialists. Stage and readiness are
48
+ * always derived from the recorded sections, never stored.
49
+ *
50
+ * The stores are resolved per call from the request context: on Workers they
51
+ * are the user's own database (`ctx.kv`), not plugin-instance memory.
52
+ * Discarding a design also discards any batch prepared or approved for it, so
53
+ * nothing prepared from the old design can reach the wallet.
54
+ */
55
+ export function createOrchestrationTools(
56
+ storeFor: BlueprintStoreFor,
57
+ sessionsFor: CreateSessionStoreFor,
58
+ ): PluginTool[] {
59
+ const startPodDesign = tool(
60
+ async (args, ctx) => {
61
+ const { brief, restart } = startSchema.parse(args);
62
+ const store = storeFor(ctx);
63
+ if (restart) {
64
+ await sessionsFor(ctx).clear(ctx.user.did, threadId(ctx));
65
+ await store.reset(threadId(ctx));
66
+ }
67
+ const bp = await store.init(threadId(ctx), brief);
68
+ const readiness = computeReadiness(bp);
69
+ return {
70
+ started: true,
71
+ threadId: bp.threadId,
72
+ brief: bp.brief,
73
+ stage: readiness.stage,
74
+ nextSpecialists: SPECIALISTS_FOR_STAGE[readiness.stage].map((id) =>
75
+ computeSubAgentToolName(id),
76
+ ),
77
+ };
78
+ },
79
+ {
80
+ name: 'start_pod_design',
81
+ description:
82
+ 'Open a POD design session and initialise the blueprint. Call this first when the user wants to create a POD; pass restart=true to discard the existing design and begin again.',
83
+ schema: startSchema,
84
+ },
85
+ );
86
+
87
+ const getBlueprint = tool(
88
+ async (args, ctx) => {
89
+ const { roles } = getBlueprintSchema.parse(args);
90
+ const bp = await storeFor(ctx).get(threadId(ctx));
91
+ if (!bp) {
92
+ return {
93
+ started: false,
94
+ message:
95
+ 'No POD design session started yet. Call start_pod_design first.',
96
+ };
97
+ }
98
+ return {
99
+ started: true,
100
+ ...(bp.brief !== undefined ? { brief: bp.brief } : {}),
101
+ readiness: computeReadiness(bp),
102
+ ...sectionsView(bp, roles),
103
+ };
104
+ },
105
+ {
106
+ name: 'get_blueprint',
107
+ description:
108
+ 'Read the POD blueprint: readiness plus a compact per-section summary. Pass roles=[...] to include the full content of specific sections.',
109
+ schema: getBlueprintSchema,
110
+ effect: 'read',
111
+ },
112
+ );
113
+
114
+ const computeReadinessTool = tool(
115
+ async (_args, ctx) => {
116
+ const bp = await storeFor(ctx).get(threadId(ctx));
117
+ if (!bp) {
118
+ return {
119
+ started: false,
120
+ message: 'No POD design session started yet.',
121
+ };
122
+ }
123
+ return computeReadiness(bp);
124
+ },
125
+ {
126
+ name: 'compute_readiness',
127
+ description:
128
+ "Score the blueprint's launch readiness and list the blockers that remain.",
129
+ schema: emptySchema,
130
+ effect: 'read',
131
+ },
132
+ );
133
+
134
+ const assembleBlueprint = tool(
135
+ async (_args, ctx) => {
136
+ const bp = await storeFor(ctx).get(threadId(ctx));
137
+ if (!bp) {
138
+ throw new Error(
139
+ 'No POD design session started yet. Call start_pod_design first.',
140
+ );
141
+ }
142
+ const readiness = computeReadiness(bp);
143
+ if (!readiness.complete) {
144
+ return {
145
+ assembled: false,
146
+ stage: readiness.stage,
147
+ blockers: readiness.blockers,
148
+ message:
149
+ 'Launch-readiness gate not passed. Resolve the blockers before assembling.',
150
+ };
151
+ }
152
+ return {
153
+ assembled: true,
154
+ blueprint: assembleServicePodBlueprint(bp),
155
+ };
156
+ },
157
+ {
158
+ name: 'assemble_blueprint',
159
+ description:
160
+ 'Assemble the final service_pod_blueprint once the launch-readiness gate passes.',
161
+ schema: emptySchema,
162
+ effect: 'read',
163
+ },
164
+ );
165
+
166
+ return [
167
+ startPodDesign,
168
+ getBlueprint,
169
+ computeReadinessTool,
170
+ assembleBlueprint,
171
+ ];
172
+ }
@@ -0,0 +1,190 @@
1
+ import { z } from 'zod';
2
+ import { OraclePlugin } from '../../plugin-api/oracle-plugin';
3
+ import type {
4
+ PluginManifest,
5
+ PluginSubAgent,
6
+ PluginTool,
7
+ RuntimeContext,
8
+ UserKvSurface,
9
+ } from '../../plugin-api/types';
10
+ import { KvBlueprintStore, type BlueprintStore } from './blueprint-store';
11
+ import {
12
+ CapsuleContentClient,
13
+ type CapsuleContentClientOptions,
14
+ type CapsuleContentFetcher,
15
+ } from './capsule-content-client';
16
+ import { notConfiguredChainGateway, type ChainGateway } from './chain-gateway';
17
+ import { podCreatorConfigSchema } from './config';
18
+ import {
19
+ KvCreateSessionStore,
20
+ type CreateSessionStore,
21
+ } from './create-session-store';
22
+ import { createCreateTools } from './create-tools';
23
+ import { createOrchestrationTools } from './orchestration-tools';
24
+ import { buildStageSubAgents } from './sub-agents';
25
+
26
+ /**
27
+ * Sibling env read at request time to configure the capsule client. The
28
+ * registry URL is owned by the skills plugin's configSchema and `NETWORK` by
29
+ * the base env schema; pod-creator reads them without redeclaring (which would
30
+ * collide in the config-schema registry).
31
+ */
32
+ const capsuleEnvSchema = z.object({
33
+ SKILLS_CAPSULES_BASE_URL: z.url().optional(),
34
+ NETWORK: z.enum(['mainnet', 'testnet', 'devnet']).optional(),
35
+ });
36
+
37
+ const manifest: PluginManifest = {
38
+ title: 'POD Creator',
39
+ summary:
40
+ 'Design and create an IXO Programmable Organisational Domain (POD) end to end — qualify, architect, build, evaluate, then prepare an on-chain creation batch for the user to sign.',
41
+ whenToUse: [
42
+ 'User wants to create, design, or stand up a new POD (Programmable Organisational Domain) or service domain on IXO.',
43
+ 'User describes a service or organisation they want to launch and asks the oracle to turn it into a working POD.',
44
+ ],
45
+ whenNotToUse: [
46
+ 'Operating or supporting an already-live POD — out of scope for now.',
47
+ 'Generic on-chain entity creation unrelated to POD design.',
48
+ ],
49
+ examples: [
50
+ {
51
+ user: 'Help me create a POD for a community solar monitoring service.',
52
+ thought:
53
+ 'A POD creation request — open the design session, then drive the specialists stage by stage.',
54
+ tool: 'start_pod_design',
55
+ args: { brief: 'Community solar monitoring service' },
56
+ },
57
+ ],
58
+ tags: ['pod', 'domain', 'design', 'orchestration', 'on-chain'],
59
+ category: 'automation',
60
+ visibility: 'on-demand',
61
+ stability: 'experimental',
62
+ };
63
+
64
+ export interface PodCreatorPluginOptions {
65
+ /**
66
+ * Registry retrieval for capsule `SKILL.md` text. When omitted, specialist
67
+ * sub-agents run on their built-in prompts. Pass
68
+ * `createRegistryInstructionsFetcher()` to load the registry's published
69
+ * instructions (`GET /skills/{name}/instructions`), or a test stub.
70
+ */
71
+ capsuleContentFetcher?: CapsuleContentFetcher;
72
+ /**
73
+ * Chain gateway for the create path. Injected by tests; the real IXO MCP
74
+ * server binding wires in here. When omitted, the create tools report
75
+ * on-chain creation as unavailable.
76
+ */
77
+ chainGateway?: ChainGateway;
78
+ }
79
+
80
+ /**
81
+ * The plugin's durable state lives in the user's own database; a host
82
+ * without `ctx.kv` cannot run the design pipeline without silently losing
83
+ * the blueprint, so it is refused loudly instead.
84
+ */
85
+ function requireUserKv(ctx: RuntimeContext): UserKvSurface {
86
+ if (!ctx.kv) {
87
+ throw new Error(
88
+ 'pod-creator needs the host per-user store (ctx.kv), which this host does not provide.',
89
+ );
90
+ }
91
+ return ctx.kv;
92
+ }
93
+
94
+ /**
95
+ * POD-creator plugin.
96
+ *
97
+ * Runs the design-pod lifecycle: a conductor (the main agent) drives the
98
+ * specialist sub-agents to design a POD, then prepares an unsigned on-chain
99
+ * creation batch for the user's wallet to sign. Each specialist's instructions
100
+ * are loaded from the ai-skills capsule registry per stage via the
101
+ * `CapsuleContentClient`.
102
+ *
103
+ * Exposes the conductor's orchestration tools (the blueprint lifecycle), the
104
+ * stage-gated specialist sub-agents, and the on-chain create path — a
105
+ * propose → approve → commit handoff with single-use approvals, ending in the
106
+ * blocking `sign_transaction` AG-UI round-trip. The chain encoding sits behind
107
+ * an injectable gateway.
108
+ *
109
+ * Per-thread blueprints and create sessions are rows in the user's own
110
+ * database (`ctx.kv`), so a design survives the user object being evicted and
111
+ * restored from its owner copy. Only the capsule-text cache is held on the
112
+ * instance.
113
+ */
114
+ export class PodCreatorPlugin extends OraclePlugin {
115
+ readonly name = 'pod-creator';
116
+ readonly version = '0.1.0';
117
+ readonly manifest = manifest;
118
+ override readonly softDependsOn = [
119
+ 'agui',
120
+ 'editor',
121
+ 'domain-indexer',
122
+ 'memory',
123
+ ];
124
+ override readonly configSchema = podCreatorConfigSchema;
125
+
126
+ private readonly capsuleContentFetcher?: CapsuleContentFetcher;
127
+
128
+ private readonly chainGateway: ChainGateway;
129
+
130
+ /**
131
+ * Built lazily from request config; cached so the per-thread prompt cache
132
+ * survives across requests.
133
+ */
134
+ private capsuleContent?: CapsuleContentClient;
135
+
136
+ constructor(options: PodCreatorPluginOptions = {}) {
137
+ super();
138
+ this.capsuleContentFetcher = options.capsuleContentFetcher;
139
+ this.chainGateway = options.chainGateway ?? notConfiguredChainGateway;
140
+ }
141
+
142
+ override getTools(): PluginTool[] {
143
+ return [
144
+ ...createOrchestrationTools(blueprintStoreFor, createSessionStoreFor),
145
+ ...createCreateTools(
146
+ blueprintStoreFor,
147
+ this.chainGateway,
148
+ createSessionStoreFor,
149
+ ),
150
+ ];
151
+ }
152
+
153
+ override async getRequestSubAgents(
154
+ rt: RuntimeContext,
155
+ ): Promise<PluginSubAgent[]> {
156
+ // This hook runs on every turn whether or not the capability is loaded:
157
+ // on a host without the store there is no design to offer specialists
158
+ // for, and the tools themselves report the missing store when called.
159
+ if (!rt.kv) return [];
160
+ return buildStageSubAgents(rt, blueprintStoreFor, this.capsuleClient(rt));
161
+ }
162
+
163
+ private capsuleClient(rt: RuntimeContext): CapsuleContentClient {
164
+ if (!this.capsuleContent) {
165
+ const env = capsuleEnvSchema.safeParse(rt.config);
166
+ const options: CapsuleContentClientOptions = {};
167
+ if (env.success) {
168
+ if (env.data.SKILLS_CAPSULES_BASE_URL !== undefined) {
169
+ options.baseUrl = env.data.SKILLS_CAPSULES_BASE_URL;
170
+ }
171
+ if (env.data.NETWORK !== undefined) {
172
+ options.network = env.data.NETWORK;
173
+ }
174
+ }
175
+ if (this.capsuleContentFetcher !== undefined) {
176
+ options.fetcher = this.capsuleContentFetcher;
177
+ }
178
+ this.capsuleContent = new CapsuleContentClient(options);
179
+ }
180
+ return this.capsuleContent;
181
+ }
182
+ }
183
+
184
+ function blueprintStoreFor(ctx: RuntimeContext): BlueprintStore {
185
+ return new KvBlueprintStore(requireUserKv(ctx));
186
+ }
187
+
188
+ function createSessionStoreFor(ctx: RuntimeContext): CreateSessionStore {
189
+ return new KvCreateSessionStore(requireUserKv(ctx));
190
+ }
@@ -0,0 +1,201 @@
1
+ import type {
2
+ BlueprintSection,
3
+ PodBlueprint,
4
+ ServicePodBlueprint,
5
+ } from './blueprint-types';
6
+ import {
7
+ DESIGN_POD_ROLES,
8
+ DESIGN_POD_STAGES,
9
+ type DesignPodStage,
10
+ } from './design-pod-roles';
11
+
12
+ /** The lifecycle stages in readiness order. */
13
+ export const STAGE_ORDER: readonly DesignPodStage[] = DESIGN_POD_STAGES;
14
+
15
+ /** Role ids required to complete each stage, grouped from the role catalogue. */
16
+ function groupRolesByStage(): Record<DesignPodStage, string[]> {
17
+ const out: Record<DesignPodStage, string[]> = {
18
+ qualify: [],
19
+ architect: [],
20
+ build: [],
21
+ evaluate: [],
22
+ package: [],
23
+ gate: [],
24
+ };
25
+ for (const role of DESIGN_POD_ROLES) {
26
+ out[role.stage].push(role.id);
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export const SPECIALISTS_FOR_STAGE: Record<DesignPodStage, readonly string[]> =
32
+ groupRolesByStage();
33
+
34
+ /**
35
+ * A section counts toward readiness when it exists — and, for gate-bearing
36
+ * roles (the evaluate oracles and the launch-readiness gate), only when it
37
+ * carries an explicit `'pass'` verdict. This stops a gate role that submitted
38
+ * content without a verdict from silently satisfying the launch gate.
39
+ */
40
+ function sectionSatisfies(section: BlueprintSection | undefined): boolean {
41
+ if (section === undefined) {
42
+ return false;
43
+ }
44
+ const isGateRole = section.stage === 'evaluate' || section.stage === 'gate';
45
+ return isGateRole ? section.verdict === 'pass' : section.verdict !== 'fail';
46
+ }
47
+
48
+ /**
49
+ * The current stage: the first stage in {@link STAGE_ORDER} whose required
50
+ * sections are not all satisfied. A failed gate verdict leaves its stage
51
+ * unsatisfied, so the stage re-opens until the specialist is re-run. When every
52
+ * stage is satisfied, returns `'gate'` (and {@link computeReadiness} reports
53
+ * `complete: true`).
54
+ */
55
+ export function deriveStage(bp: PodBlueprint): DesignPodStage {
56
+ for (const stage of STAGE_ORDER) {
57
+ const allSatisfied = SPECIALISTS_FOR_STAGE[stage].every((roleId) =>
58
+ sectionSatisfies(bp.sections[roleId]),
59
+ );
60
+ if (!allSatisfied) {
61
+ return stage;
62
+ }
63
+ }
64
+ return 'gate';
65
+ }
66
+
67
+ /** Readiness summary derived from the blueprint's recorded sections. */
68
+ export interface Readiness {
69
+ /** The stage the conductor should drive next. */
70
+ stage: DesignPodStage;
71
+ /** True once every required section is satisfied (gate passed). */
72
+ complete: boolean;
73
+ /** Stages whose required sections are all satisfied. */
74
+ completedStages: DesignPodStage[];
75
+ /** Human-readable blockers (missing sections, failed gates). */
76
+ blockers: string[];
77
+ /** Fraction of required sections satisfied, `0..1`. */
78
+ score: number;
79
+ }
80
+
81
+ export function computeReadiness(bp: PodBlueprint): Readiness {
82
+ const blockers: string[] = [];
83
+ const completedStages: DesignPodStage[] = [];
84
+ let satisfied = 0;
85
+ let required = 0;
86
+
87
+ for (const stage of STAGE_ORDER) {
88
+ let stageComplete = true;
89
+ for (const roleId of SPECIALISTS_FOR_STAGE[stage]) {
90
+ required += 1;
91
+ const section = bp.sections[roleId];
92
+ if (sectionSatisfies(section)) {
93
+ satisfied += 1;
94
+ } else {
95
+ stageComplete = false;
96
+ if (section === undefined) {
97
+ blockers.push(`${stage}: ${roleId} not recorded`);
98
+ } else if (section.verdict === 'fail') {
99
+ const detail = (section.blockers ?? []).join('; ') || 'no detail';
100
+ blockers.push(`${stage}: ${roleId} failed (${detail})`);
101
+ } else {
102
+ blockers.push(`${stage}: ${roleId} awaiting a pass verdict`);
103
+ }
104
+ }
105
+ }
106
+ if (stageComplete) {
107
+ completedStages.push(stage);
108
+ }
109
+ }
110
+
111
+ return {
112
+ stage: deriveStage(bp),
113
+ complete: required > 0 && satisfied === required,
114
+ completedStages,
115
+ blockers,
116
+ score: required === 0 ? 0 : satisfied / required,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Compact per-section view for blueprint reads. Sections carry arbitrary-size
122
+ * role-defined payloads, so blueprint reads return this summary by default and
123
+ * full content only for explicitly requested roles.
124
+ */
125
+ export function summarizeSection(section: BlueprintSection): {
126
+ role: string;
127
+ stage: DesignPodStage;
128
+ verdict?: 'pass' | 'fail';
129
+ recordedAt: string;
130
+ contentBytes: number;
131
+ } {
132
+ return {
133
+ role: section.role,
134
+ stage: section.stage,
135
+ ...(section.verdict !== undefined ? { verdict: section.verdict } : {}),
136
+ recordedAt: section.recordedAt,
137
+ contentBytes: JSON.stringify(section.content ?? null).length,
138
+ };
139
+ }
140
+
141
+ /**
142
+ * The blueprint read shared by the conductor's `get_blueprint` and the
143
+ * specialists' `read_blueprint`: the compact per-section summary, plus the
144
+ * full content of the requested roles when `roles` is given.
145
+ */
146
+ export function sectionsView(
147
+ bp: PodBlueprint,
148
+ roles: readonly string[] | undefined,
149
+ ): {
150
+ sections: ReturnType<typeof summarizeSection>[];
151
+ content?: Record<string, unknown>;
152
+ } {
153
+ const sections = Object.values(bp.sections);
154
+ return {
155
+ sections: sections.map(summarizeSection),
156
+ ...(roles !== undefined
157
+ ? {
158
+ content: Object.fromEntries(
159
+ sections
160
+ .filter((section) => roles.includes(section.role))
161
+ .map((section) => [section.role, section.content]),
162
+ ),
163
+ }
164
+ : {}),
165
+ };
166
+ }
167
+
168
+ /** Group the recorded sections by stage, preserving the stage ordering. */
169
+ function groupSectionsByStage(
170
+ bp: PodBlueprint,
171
+ ): Record<DesignPodStage, BlueprintSection[]> {
172
+ const out: Record<DesignPodStage, BlueprintSection[]> = {
173
+ qualify: [],
174
+ architect: [],
175
+ build: [],
176
+ evaluate: [],
177
+ package: [],
178
+ gate: [],
179
+ };
180
+ // The stored blueprint is schema-checked on read, so every section's stage
181
+ // is one of the catalogue's.
182
+ for (const section of Object.values(bp.sections)) {
183
+ out[section.stage].push(section);
184
+ }
185
+ return out;
186
+ }
187
+
188
+ /** Assemble the final `service_pod_blueprint` from a completed blueprint. */
189
+ export function assembleServicePodBlueprint(
190
+ bp: PodBlueprint,
191
+ ): ServicePodBlueprint {
192
+ const blueprint: ServicePodBlueprint = {
193
+ threadId: bp.threadId,
194
+ stages: groupSectionsByStage(bp),
195
+ assembledAt: new Date().toISOString(),
196
+ };
197
+ if (bp.brief !== undefined) {
198
+ blueprint.brief = bp.brief;
199
+ }
200
+ return blueprint;
201
+ }