@ixo/oracle-runtime-workers 0.41.1 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixo/oracle-runtime-workers",
3
- "version": "0.41.1",
3
+ "version": "0.41.2",
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": {
@@ -67,9 +67,9 @@
67
67
  "protobufjs": "^7.6.5",
68
68
  "@ixo/topic-protocol": "1.0.0-rc.8",
69
69
  "survey-core": "2.3.11",
70
- "@ixo/ucan": "2.3.0",
71
- "@ixo/common": "1.8.0",
72
70
  "@ixo/ixo-transaction": "0.2.0",
71
+ "@ixo/common": "1.8.0",
72
+ "@ixo/ucan": "2.3.0",
73
73
  "@ixo/action-interactions": "0.1.1"
74
74
  },
75
75
  "devDependencies": {
@@ -903,6 +903,41 @@ function issuesOf(
903
903
  );
904
904
  }
905
905
 
906
+ /** Why a Hub action tool did nothing: Hub actions are off for this oracle. */
907
+ export const HUB_ACTIONS_OFF_REASON =
908
+ 'Hub actions are not enabled for this oracle.';
909
+
910
+ /**
911
+ * Why a Hub action tool did nothing on a Portal, Matrix or other non-channel
912
+ * turn. Auth Hub reviews are bound to a channel binding, so nothing can be
913
+ * prepared here; the reason says so in words the model can relay, so it
914
+ * never reads the refusal as an Auth Hub outage.
915
+ */
916
+ export const NOT_A_CHANNEL_CHAT_REASON =
917
+ 'Coin sends, chain transactions and other Auth Hub actions can be prepared only in a channel chat with this oracle (such as WhatsApp), not in this chat. Auth Hub was not contacted and nothing was prepared or sent. Tell the person to ask in their channel chat.';
918
+
919
+ /**
920
+ * The turn's channel binding and Hub client when Hub actions can be
921
+ * prepared on this turn, else the refusal that names why not.
922
+ */
923
+ function channelTurnOf(ctx: RuntimeContext):
924
+ | {
925
+ channel: NonNullable<RuntimeContext['session']['channel']>;
926
+ hubActions: NonNullable<RuntimeContext['hubActions']>;
927
+ }
928
+ | { refusal: { status: 'unavailable'; reason: string } } {
929
+ if (ctx.config.HUB_ACTIONS_ENABLED !== 'true' || !ctx.hubActions)
930
+ return {
931
+ refusal: { status: 'unavailable', reason: HUB_ACTIONS_OFF_REASON },
932
+ };
933
+ const channel = ctx.session.channel;
934
+ if (ctx.session.client !== 'channel' || !channel)
935
+ return {
936
+ refusal: { status: 'unavailable', reason: NOT_A_CHANNEL_CHAT_REASON },
937
+ };
938
+ return { channel, hubActions: ctx.hubActions };
939
+ }
940
+
906
941
  /**
907
942
  * The catalogue types this account may use: the Hub capabilities'
908
943
  * `chainTx.messageTypes`, which follow the protocol level the Hub speaks
@@ -916,16 +951,11 @@ function issuesOf(
916
951
  async function availableChainTxTypes(
917
952
  ctx: RuntimeContext,
918
953
  ): Promise<readonly ChainTxMessageType[]> {
919
- if (
920
- ctx.config.HUB_ACTIONS_ENABLED !== 'true' ||
921
- ctx.session.client !== 'channel' ||
922
- !ctx.session.channel ||
923
- !ctx.hubActions
924
- )
925
- return [];
954
+ const turn = channelTurnOf(ctx);
955
+ if ('refusal' in turn) return [];
926
956
  let chainTx;
927
957
  try {
928
- chainTx = (await ctx.hubActions.capabilities({ turn: true })).chainTx;
958
+ chainTx = (await turn.hubActions.capabilities({ turn: true })).chainTx;
929
959
  } catch (error) {
930
960
  ctx.logger.warn(
931
961
  `[hub-actions] capabilities unavailable, no chain transaction types this turn: ${error instanceof Error ? error.message : String(error)}`,
@@ -1101,12 +1131,16 @@ async function hubActionStatus(
1101
1131
  ctx: RuntimeContext,
1102
1132
  ): Promise<unknown> {
1103
1133
  const parsed = statusSchema.safeParse(input);
1104
- if (
1105
- !parsed.success ||
1106
- !ctx.hubActions ||
1107
- ctx.config.HUB_ACTIONS_ENABLED !== 'true'
1108
- )
1109
- return { status: 'unavailable' };
1134
+ if (!parsed.success)
1135
+ return {
1136
+ status: 'unavailable',
1137
+ reason:
1138
+ 'An actionId is required: the 32-character id of a Hub action prepared for this owner.',
1139
+ };
1140
+ // Not only a channel turn: an action prepared in a channel chat is read
1141
+ // from the Portal too, where the channel's conversation is mirrored.
1142
+ if (ctx.config.HUB_ACTIONS_ENABLED !== 'true' || !ctx.hubActions)
1143
+ return { status: 'unavailable', reason: HUB_ACTIONS_OFF_REASON };
1110
1144
  try {
1111
1145
  const read = await ctx.hubActions.status(
1112
1146
  ctx.user.did,
@@ -1138,15 +1172,10 @@ async function hubActionCapabilities(
1138
1172
  _input: unknown,
1139
1173
  ctx: RuntimeContext,
1140
1174
  ): Promise<unknown> {
1141
- if (
1142
- ctx.config.HUB_ACTIONS_ENABLED !== 'true' ||
1143
- ctx.session.client !== 'channel' ||
1144
- !ctx.session.channel ||
1145
- !ctx.hubActions
1146
- )
1147
- return { status: 'unavailable' };
1175
+ const turn = channelTurnOf(ctx);
1176
+ if ('refusal' in turn) return turn.refusal;
1148
1177
  try {
1149
- return await ctx.hubActions.capabilities();
1178
+ return await turn.hubActions.capabilities();
1150
1179
  } catch (error) {
1151
1180
  const refused = refusalOf(error);
1152
1181
  if (refused) return refused;
@@ -1158,17 +1187,12 @@ async function hubActionOptions(
1158
1187
  _input: unknown,
1159
1188
  ctx: RuntimeContext,
1160
1189
  ): Promise<unknown> {
1161
- const channel = ctx.session.channel;
1162
- if (
1163
- ctx.config.HUB_ACTIONS_ENABLED !== 'true' ||
1164
- ctx.session.client !== 'channel' ||
1165
- !channel ||
1166
- !ctx.hubActions
1167
- )
1168
- return { status: 'unavailable' };
1190
+ const turn = channelTurnOf(ctx);
1191
+ if ('refusal' in turn) return turn.refusal;
1192
+ const { channel } = turn;
1169
1193
  let options;
1170
1194
  try {
1171
- options = await ctx.hubActions.options({
1195
+ options = await turn.hubActions.options({
1172
1196
  ownerDid: ctx.user.did,
1173
1197
  provider: channel.provider,
1174
1198
  bindingId: channel.bindingId,
@@ -1184,6 +1208,60 @@ async function hubActionOptions(
1184
1208
  return { ...options, ownerAddress: ixoAddressOf(ctx.user.did) };
1185
1209
  }
1186
1210
 
1211
+ /**
1212
+ * The tools that prepare a Hub action, offered only on a channel turn
1213
+ * (`getRequestTools`): Auth Hub binds every review to the turn's channel
1214
+ * binding, so on a Portal, Matrix or other turn they could only refuse, and
1215
+ * a model offered them reads the refusal as an Auth Hub outage. Their
1216
+ * handlers still refuse off a channel turn, with a reason. Built once.
1217
+ */
1218
+ export const HUB_ACTION_CHANNEL_TOOLS: readonly PluginTool[] = [
1219
+ {
1220
+ name: 'hub_action_capabilities',
1221
+ description:
1222
+ "Read this authenticated oracle account's enabled operation kinds and exact registered form field schema before proposing a Hub action.",
1223
+ schema: discoverSchema,
1224
+ effect: 'read',
1225
+ handler: hubActionCapabilities,
1226
+ },
1227
+ {
1228
+ name: 'hub_action_options',
1229
+ description:
1230
+ "Read the owner’s confirmed Gmail and Calendar connections, requestable documents, action resources and bank tokens (`transfer.tokens`: denom, symbol, decimals, verified, balance in base units). Use each returned service, identifier and label to prepare a related Hub review. A Gmail profile connection does not authorize sending. For a token that is not verified, name its full denom and say it is unverified; its amounts are base units. ownerDid is the authenticated user's DID and ownerAddress their IXO account address.",
1231
+ schema: discoverSchema,
1232
+ effect: 'read',
1233
+ handler: hubActionOptions,
1234
+ },
1235
+ {
1236
+ name: 'request_hub_action',
1237
+ description:
1238
+ 'Prepare a supported operation for the authenticated channel owner to review in Auth Hub. Never approves, signs, or executes. For email, propose one plain-text message to one recipient using a confirmed Gmail send connection. Supply only the operation candidate; the runtime supplies identity and turn binding. Coin sends and other chain transactions have their own tool (request_chain_transaction). Never prepare a replacement email after an unknown send without the user checking Gmail’s Sent folder and asking again. Limits are checked on the call; `startsAt` and `endsAt` are ISO 8601 with an offset, a saved `filename` ends in .txt, a form field `id` is lower_snake_case.',
1239
+ schema: requestSchema,
1240
+ effect: 'write',
1241
+ handler: requestHubAction,
1242
+ },
1243
+ {
1244
+ name: 'describe_chain_messages',
1245
+ description: DESCRIBE_CHAIN_MESSAGES_TOOL_DESCRIPTION,
1246
+ schema: describeChainMessagesSchema,
1247
+ effect: 'read',
1248
+ handler: describeChainMessages,
1249
+ },
1250
+ ];
1251
+
1252
+ /**
1253
+ * hub_action_status, offered on every turn: an action prepared in a channel
1254
+ * chat is followed from the Portal too, where that conversation is mirrored.
1255
+ */
1256
+ export const HUB_ACTION_STATUS_TOOL: PluginTool = {
1257
+ name: 'hub_action_status',
1258
+ description:
1259
+ 'Read the verified status of a Hub action already prepared for this owner. Never executes. A `cancelled` outcome was withdrawn before anything was sent, by the person or by Auth Hub (`meaning` says so): never say the person cancelled it. When `meaning` is present, it says what the outcome means for the person (for example a carried-out group proposal whose actions failed): follow it. Coin sends, chain transactions and other Hub actions are prepared only in a channel chat (such as WhatsApp): when this chat has no tool for one, tell the person to ask there, and never call it an Auth Hub outage.',
1260
+ schema: statusSchema,
1261
+ effect: 'read',
1262
+ handler: hubActionStatus,
1263
+ };
1264
+
1187
1265
  const manifest: PluginManifest = {
1188
1266
  title: 'Auth Hub action review',
1189
1267
  summary:
@@ -1224,63 +1302,26 @@ export class HubActionsPlugin extends OraclePlugin {
1224
1302
  );
1225
1303
  }
1226
1304
  /**
1305
+ * The tools that prepare a Hub action, on a channel turn only (none on a
1306
+ * Portal, Matrix or other turn, where Auth Hub cannot bind a review).
1227
1307
  * request_chain_transaction is built per turn: its schema names only the
1228
1308
  * message types this account may use (Auth Hub capabilities, cached by
1229
1309
  * the owner object), so 37 types do not reach a model that may use 4.
1230
1310
  */
1231
1311
  override async getRequestTools(ctx: RuntimeContext): Promise<PluginTool[]> {
1312
+ if ('refusal' in channelTurnOf(ctx)) return [];
1232
1313
  const [available, described] = await Promise.all([
1233
1314
  chainTxTypesForTurn(ctx),
1234
1315
  describedChainTxTypes(ctx, ctx.history.messages),
1235
1316
  ]);
1236
1317
  const tool = chainTransactionTool(available, described);
1237
- return tool ? [tool] : [];
1318
+ return [...HUB_ACTION_CHANNEL_TOOLS, ...(tool ? [tool] : [])];
1238
1319
  }
1239
1320
  /** Gives each model call the request tool with the fields described so far in the turn. */
1240
1321
  override getRequestMiddlewares(ctx: RuntimeContext): AgentMiddleware[] {
1241
1322
  return [chainTxFieldsMiddleware(ctx)];
1242
1323
  }
1243
1324
  override getTools(): PluginTool[] {
1244
- return [
1245
- {
1246
- name: 'hub_action_capabilities',
1247
- description:
1248
- "Read this authenticated oracle account's enabled operation kinds and exact registered form field schema before proposing a Hub action.",
1249
- schema: discoverSchema,
1250
- effect: 'read',
1251
- handler: hubActionCapabilities,
1252
- },
1253
- {
1254
- name: 'hub_action_options',
1255
- description:
1256
- "Read the owner’s confirmed Gmail and Calendar connections, requestable documents, action resources and bank tokens (`transfer.tokens`: denom, symbol, decimals, verified, balance in base units). Use each returned service, identifier and label to prepare a related Hub review. A Gmail profile connection does not authorize sending. For a token that is not verified, name its full denom and say it is unverified; its amounts are base units. ownerDid is the authenticated user's DID and ownerAddress their IXO account address.",
1257
- schema: discoverSchema,
1258
- effect: 'read',
1259
- handler: hubActionOptions,
1260
- },
1261
- {
1262
- name: 'request_hub_action',
1263
- description:
1264
- 'Prepare a supported operation for the authenticated channel owner to review in Auth Hub. Never approves, signs, or executes. For email, propose one plain-text message to one recipient using a confirmed Gmail send connection. Supply only the operation candidate; the runtime supplies identity and turn binding. Coin sends and other chain transactions have their own tool (request_chain_transaction). Never prepare a replacement email after an unknown send without the user checking Gmail’s Sent folder and asking again. Limits are checked on the call; `startsAt` and `endsAt` are ISO 8601 with an offset, a saved `filename` ends in .txt, a form field `id` is lower_snake_case.',
1265
- schema: requestSchema,
1266
- effect: 'write',
1267
- handler: requestHubAction,
1268
- },
1269
- {
1270
- name: 'describe_chain_messages',
1271
- description: DESCRIBE_CHAIN_MESSAGES_TOOL_DESCRIPTION,
1272
- schema: describeChainMessagesSchema,
1273
- effect: 'read',
1274
- handler: describeChainMessages,
1275
- },
1276
- {
1277
- name: 'hub_action_status',
1278
- description:
1279
- 'Read the verified status of a Hub action already prepared for this owner. Never executes. A `cancelled` outcome was withdrawn before anything was sent, by the person or by Auth Hub (`meaning` says so): never say the person cancelled it. When `meaning` is present, it says what the outcome means for the person (for example a carried-out group proposal whose actions failed): follow it.',
1280
- schema: statusSchema,
1281
- effect: 'read',
1282
- handler: hubActionStatus,
1283
- },
1284
- ];
1325
+ return [HUB_ACTION_STATUS_TOOL];
1285
1326
  }
1286
1327
  }