@ixo/oracle-runtime-workers 0.22.0 → 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 (39) hide show
  1. package/README.md +15 -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 +89 -5
  20. package/src/core/main-agent-types.ts +8 -0
  21. package/src/core/main-agent.ts +2 -0
  22. package/src/core/runtime-context.ts +12 -0
  23. package/src/delivery/plan.ts +43 -2
  24. package/src/delivery/schema.ts +25 -1
  25. package/src/delivery/types.ts +13 -2
  26. package/src/do/ambient.ts +6 -0
  27. package/src/do/contracts.ts +39 -0
  28. package/src/do/user-oracle-do.ts +1253 -7
  29. package/src/hub-actions/client.ts +163 -0
  30. package/src/hub-actions/request.ts +110 -0
  31. package/src/hub-actions/store.ts +485 -0
  32. package/src/matrix/gateway-do.ts +176 -0
  33. package/src/matrix/reply-parts.ts +8 -2
  34. package/src/plugin-api/types.ts +54 -0
  35. package/src/plugins/action-proposals/action-proposals.plugin.ts +486 -0
  36. package/src/plugins/action-proposals/evidence.ts +67 -0
  37. package/src/plugins/hub-actions/hub-actions.plugin.ts +244 -0
  38. package/src/plugins/index.ts +6 -0
  39. package/src/shell/app.ts +195 -1
@@ -189,6 +189,8 @@ export interface AmbientServices {
189
189
  * `DecisionProviderUnavailableError`.
190
190
  */
191
191
  decisions?: DecisionEvaluator;
192
+ actionProposals?: RuntimeContext['actionProposals'];
193
+ hubActions?: RuntimeContext['hubActions'];
192
194
  /** Host task scheduler for the current user, when the host provides one. */
193
195
  tasks?: import('../plugin-api/types').OracleTasksSurface;
194
196
  /** Host user-preferences store for the current user, when provided. */
@@ -323,6 +325,12 @@ export interface RuntimeSessionContext {
323
325
  client: 'portal' | 'matrix' | 'slack' | 'channel';
324
326
  wsId?: string;
325
327
  requestId: string;
328
+ runId?: string;
329
+ channel?: {
330
+ provider: string;
331
+ bindingId: string;
332
+ bindingRevision: number;
333
+ };
326
334
  roomId?: string;
327
335
  surface?: SessionSurface;
328
336
  }
@@ -528,6 +536,10 @@ export function buildRuntimeContext<TConfig = MergedConfig>(
528
536
  logger: ambient.logger,
529
537
  abortSignal,
530
538
  ...(ambient.tasks ? { tasks: ambient.tasks } : {}),
539
+ ...(ambient.actionProposals
540
+ ? { actionProposals: ambient.actionProposals }
541
+ : {}),
542
+ ...(ambient.hubActions ? { hubActions: ambient.hubActions } : {}),
531
543
  ...(ambient.preferences ? { preferences: ambient.preferences } : {}),
532
544
  ...(ambient.kv ? { kv: ambient.kv } : {}),
533
545
  ...(ambient.frontend ? { frontend: ambient.frontend } : {}),
@@ -1,3 +1,5 @@
1
+ import { ActionReferenceSchema } from '@ixo/ixo-transaction/action-proposals';
2
+ import { parseReference } from '@ixo/action-protocol';
1
3
  /**
2
4
  * A finished run → its Reply Plan. Built once, when the run finishes, from
3
5
  * the turn's own messages (the model's checkpointed history is never
@@ -160,11 +162,42 @@ export function draftReplyPlan(input: {
160
162
  );
161
163
  else parts.push({ kind: 'spill', key: `step-${i}`, spill: shape.spill });
162
164
  }
163
- for (const call of step.toolCalls)
165
+ for (const call of step.toolCalls) {
166
+ if (call.name === 'request_hub_action') {
167
+ try {
168
+ const result = JSON.parse(input.toolResults.get(call.id) ?? 'null');
169
+ if (result?.status === 'prepared') {
170
+ const actionReference = parseReference(result.actionReference);
171
+ if (
172
+ typeof result.summary === 'string' &&
173
+ result.summary === actionReference.title
174
+ )
175
+ parts.push({
176
+ kind: 'hub-action',
177
+ actionReference,
178
+ summary: result.summary,
179
+ });
180
+ }
181
+ } catch {
182
+ // Only a parsed tool result becomes a structured Hub reference.
183
+ }
184
+ }
185
+ if (call.name === 'prepare_action') {
186
+ try {
187
+ const result = JSON.parse(input.toolResults.get(call.id) ?? 'null');
188
+ if (result?.status === 'prepared') {
189
+ const ref = ActionReferenceSchema.safeParse(result.action);
190
+ if (ref.success) parts.push({ kind: 'action', action: ref.data });
191
+ }
192
+ } catch {
193
+ /* A malformed result is not an action reference. */
194
+ }
195
+ }
164
196
  if (call.name === CREATE_ARTIFACT_TOOL)
165
197
  parts.push(
166
198
  ...createArtifactParts(call.args, input.toolResults.get(call.id)),
167
199
  );
200
+ }
168
201
  });
169
202
  return parts;
170
203
  }
@@ -221,7 +254,15 @@ function capParts(parts: PlanPart[], limits: ChatLimits): ReplyContent[] {
221
254
  (part): ReplyContent =>
222
255
  part.kind === 'text'
223
256
  ? { kind: 'text', text: part.text }
224
- : { kind: 'artifact', artifact: part.artifact },
257
+ : part.kind === 'artifact'
258
+ ? { kind: 'artifact', artifact: part.artifact }
259
+ : part.kind === 'action'
260
+ ? { kind: 'action', action: part.action }
261
+ : {
262
+ kind: 'hub-action',
263
+ actionReference: part.actionReference,
264
+ summary: part.summary,
265
+ },
225
266
  );
226
267
  }
227
268
 
@@ -1,3 +1,5 @@
1
+ import { ActionReferenceSchema } from '@ixo/ixo-transaction/action-proposals';
2
+ import { parseReference, type ActionReferenceV1 } from '@ixo/action-protocol';
1
3
  import { z } from 'zod';
2
4
  import type { ReplyContent, ReplyPlan } from './types';
3
5
 
@@ -19,6 +21,24 @@ export const ReplyPlanSchema = z.object({
19
21
  kind: z.literal('text'),
20
22
  text: z.string(),
21
23
  }),
24
+ z.object({
25
+ partId: z.string(),
26
+ kind: z.literal('action'),
27
+ action: ActionReferenceSchema,
28
+ }),
29
+ z.object({
30
+ partId: z.string(),
31
+ kind: z.literal('hub-action'),
32
+ actionReference: z.custom<ActionReferenceV1>((value) => {
33
+ try {
34
+ parseReference(value);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }),
40
+ summary: z.string().min(1).max(80),
41
+ }),
22
42
  z.object({
23
43
  partId: z.string(),
24
44
  kind: z.literal('artifact'),
@@ -55,7 +75,11 @@ export function planText(plan: { parts: readonly ReplyContent[] }): string {
55
75
  .map((part) =>
56
76
  part.kind === 'text'
57
77
  ? part.text
58
- : `[${linkLabel(part.artifact.title)}](${part.artifact.url})`,
78
+ : part.kind === 'action'
79
+ ? `[${linkLabel(part.action.title)}](${part.action.url})`
80
+ : part.kind === 'hub-action'
81
+ ? `[${linkLabel(part.summary)}](${part.actionReference.url})`
82
+ : `[${linkLabel(part.artifact.title)}](${part.artifact.url})`,
59
83
  )
60
84
  .join('\n\n');
61
85
  }
@@ -1,3 +1,5 @@
1
+ import type { ActionReference } from '@ixo/ixo-transaction/action-proposals';
2
+ import type { ActionReferenceV1 } from '@ixo/action-protocol';
1
3
  /**
2
4
  * Chat delivery: how a finished turn reaches a chat surface (an IXO Channels
3
5
  * provider such as WhatsApp, or a Matrix room) as a few short messages and
@@ -70,12 +72,21 @@ export interface ArtifactRef {
70
72
  */
71
73
  export type ReplyPart =
72
74
  | { partId: string; kind: 'text'; text: string }
73
- | { partId: string; kind: 'artifact'; artifact: ArtifactRef };
75
+ | { partId: string; kind: 'artifact'; artifact: ArtifactRef }
76
+ | { partId: string; kind: 'action'; action: ActionReference }
77
+ | {
78
+ partId: string;
79
+ kind: 'hub-action';
80
+ actionReference: ActionReferenceV1;
81
+ summary: string;
82
+ };
74
83
 
75
84
  /** A part before it is numbered: what a plan is assembled from. */
76
85
  export type ReplyContent =
77
86
  | { kind: 'text'; text: string }
78
- | { kind: 'artifact'; artifact: ArtifactRef };
87
+ | { kind: 'artifact'; artifact: ArtifactRef }
88
+ | { kind: 'action'; action: ActionReference }
89
+ | { kind: 'hub-action'; actionReference: ActionReferenceV1; summary: string };
79
90
 
80
91
  /** What a chat surface delivers for one finished run, in delivery order. */
81
92
  export interface ReplyPlan {
package/src/do/ambient.ts CHANGED
@@ -267,6 +267,8 @@ export function createUcanAdapter(
267
267
 
268
268
  export interface CreateAmbientInput {
269
269
  /** Host task scheduler surface for this user, when tasks are enabled. */
270
+ actionProposals?: AmbientServices['actionProposals'];
271
+ hubActions?: AmbientServices['hubActions'];
270
272
  tasks?: import('../plugin-api/types').OracleTasksSurface;
271
273
  preferences?: import('../plugin-api/types').UserPreferencesSurface;
272
274
  /** `ctx.kv` over the user's working copy (`SqliteUserKv`). */
@@ -302,6 +304,10 @@ export function createAmbientServices(
302
304
  ): AmbientServices {
303
305
  return {
304
306
  ...(input.tasks ? { tasks: input.tasks } : {}),
307
+ ...(input.actionProposals
308
+ ? { actionProposals: input.actionProposals }
309
+ : {}),
310
+ ...(input.hubActions ? { hubActions: input.hubActions } : {}),
305
311
  ...(input.preferences ? { preferences: input.preferences } : {}),
306
312
  ...(input.kv ? { kv: input.kv } : {}),
307
313
  ...(input.frontend ? { frontend: input.frontend } : {}),
@@ -1,3 +1,9 @@
1
+ import type {
2
+ BeginTopicStep,
3
+ TopicReceiptReferences,
4
+ } from '@ixo/ixo-transaction/topic-proposals';
5
+ import type { ProposalCommand } from '../action-proposals/store';
6
+ import type { ActionOutcomeV1 } from '@ixo/action-protocol';
1
7
  import type {
2
8
  TopicDeliverableCommand,
3
9
  TopicDeliverableResult,
@@ -50,6 +56,12 @@ import type { DomainContextProvenance } from '../core/domain-context';
50
56
  /** Bindings every oracle Worker must declare (see the example `wrangler.jsonc`). */
51
57
  export interface OracleWorkerEnv {
52
58
  TOPIC_DELIVERABLES_ENABLED?: string;
59
+ ACTION_PROPOSALS_RPC_TESTNET?: string;
60
+ ACTION_PROPOSALS_RPC_DEVNET?: string;
61
+ ACTION_PROPOSALS_RPC_MAINNET?: string;
62
+ ACTION_PROPOSALS_ENABLED?: string;
63
+ ACTION_TOPIC_PROPOSALS_ENABLED?: string;
64
+ ACTION_PROPOSALS_PORTAL_ORIGIN?: string;
53
65
  USER_ORACLE: DurableObjectNamespace<UserOracleObject>;
54
66
  MATRIX_GATEWAY: DurableObjectNamespace<MatrixGatewayObject>;
55
67
 
@@ -72,6 +84,8 @@ export interface OracleWorkerEnv {
72
84
  AUTH_HUB?: Fetcher;
73
85
  AUTH_HUB_URL?: string;
74
86
  AUTH_HUB_CHANNEL_SERVICE_KEY?: string;
87
+ HUB_ACTIONS_ENABLED?: string;
88
+ AUTH_HUB_ACTION_OUTCOME_KEY?: string;
75
89
 
76
90
  // --- matrix -------------------------------------------------------------
77
91
  MATRIX_BASE_URL: string;
@@ -371,6 +385,7 @@ export interface TurnRequest {
371
385
  /** A provider id (`/^[a-z][a-z0-9_-]{0,31}$/`), e.g. `whatsapp`. */
372
386
  provider: string;
373
387
  bindingId: string;
388
+ bindingRevision?: number;
374
389
  remoteMessageRef: string;
375
390
  };
376
391
  executionProfile?: TaskExecutionProfile;
@@ -721,6 +736,7 @@ export interface StorageStatus {
721
736
  * piped straight to the client.
722
737
  */
723
738
  export interface UserOracleObject extends Rpc.DurableObjectBranded {
739
+ recordHubActionOutcome(outcome: ActionOutcomeV1): Promise<void>;
724
740
  topicDeliverable(
725
741
  identity: TurnIdentity,
726
742
  operationId: string,
@@ -759,6 +775,18 @@ export interface UserOracleObject extends Rpc.DurableObjectBranded {
759
775
  opts?: { limit?: number; offset?: number },
760
776
  ): Promise<{ sessions: SessionSummary[]; total: number }>;
761
777
  deleteSession(identity: TurnIdentity, sessionId: string): Promise<boolean>;
778
+ actionProposal(
779
+ identity: TurnIdentity,
780
+ id: string,
781
+ command?:
782
+ | ProposalCommand
783
+ | { kind: 'topic-reconcile'; input: TopicReceiptReferences },
784
+ ): Promise<JsonString | null>;
785
+ beginTopicStep(
786
+ identity: TurnIdentity,
787
+ id: string,
788
+ input: BeginTopicStep,
789
+ ): Promise<JsonString | null>;
762
790
  /** `GET /artifacts/:id` — the owner's canonical copy, or null. */
763
791
  artifact(
764
792
  identity: TurnIdentity,
@@ -1066,6 +1094,17 @@ export interface MatrixGatewayObject extends Rpc.DurableObjectBranded {
1066
1094
  ): Promise<string>;
1067
1095
  /** Full room state as a JSON-encoded array of `{ type, state_key, content, sender, event_id }`. */
1068
1096
  getRoomState(roomId: string): Promise<JsonString>;
1097
+ getTopicRoomState(roomId: string): Promise<JsonString>;
1098
+ getTopicEvent(
1099
+ roomId: string,
1100
+ eventId: string,
1101
+ opts?: { deadlineAt?: number },
1102
+ ): Promise<JsonString | null>;
1103
+ getTopicHistory(
1104
+ roomId: string,
1105
+ root: string,
1106
+ opts?: { deadlineAt?: number; sender?: string },
1107
+ ): Promise<JsonString>;
1069
1108
  /** One timeline event (decrypted when E2EE) as JSON `{ event_id, type, content, sender, origin_server_ts }`, or null. */
1070
1109
  getEvent(roomId: string, eventId: string): Promise<JsonString | null>;
1071
1110
  /**