@ixo/oracle-runtime-workers 0.24.0 → 0.26.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.
- package/package.json +4 -2
- package/src/action-proposals/store.ts +0 -40
- package/src/action-proposals/topic-store.ts +0 -42
- package/src/attachments/extract.ts +108 -14
- package/src/attachments/index.ts +15 -0
- package/src/attachments/pdf-text.ts +99 -0
- package/src/attachments/pipeline.ts +47 -4
- package/src/attachments/sanitize.ts +14 -0
- package/src/attachments/view-cache.ts +95 -0
- package/src/attachments/view.ts +308 -3
- package/src/delivery/plan.ts +4 -1
- package/src/do/contracts.ts +0 -1
- package/src/do/turn-messages.ts +54 -0
- package/src/do/user-oracle-do.ts +309 -143
- package/src/hub-actions/client.ts +58 -5
- package/src/hub-actions/outcome-signature.ts +227 -0
- package/src/hub-actions/request.ts +16 -7
- package/src/hub-actions/store.ts +61 -37
- package/src/hub-actions/transfer.ts +182 -0
- package/src/memory/user-context-invalidation.ts +6 -0
- package/src/plugin-api/types.ts +25 -2
- package/src/plugins/hub-actions/hub-actions.plugin.ts +154 -25
- package/src/plugins/memory/document-chunker.ts +313 -0
- package/src/plugins/memory/document-source.ts +229 -0
- package/src/plugins/memory/memory-engine-client.ts +352 -0
- package/src/plugins/memory/memory.plugin.ts +25 -2
- package/src/plugins/memory/oracle-document-tools.ts +677 -0
- package/src/plugins/vfs/index.ts +11 -2
- package/src/plugins/vfs/vfs-content.ts +86 -22
- package/src/plugins/vfs/vfs.plugin.ts +28 -19
- package/src/shell/app.ts +36 -20
package/src/do/user-oracle-do.ts
CHANGED
|
@@ -39,6 +39,11 @@ import type {
|
|
|
39
39
|
import { ChannelTurns } from '../channels/turns';
|
|
40
40
|
import { HubActionClient } from '../hub-actions/client';
|
|
41
41
|
import { prepareHubAction } from '../hub-actions/request';
|
|
42
|
+
import {
|
|
43
|
+
bankReceiptSummary,
|
|
44
|
+
describeAmount,
|
|
45
|
+
toBaseUnits,
|
|
46
|
+
} from '../hub-actions/transfer';
|
|
42
47
|
import {
|
|
43
48
|
HUB_CHECK_AFTER_EXPIRY_MS,
|
|
44
49
|
HubActionRefused,
|
|
@@ -49,6 +54,9 @@ import {
|
|
|
49
54
|
canonicalJson,
|
|
50
55
|
type ActionCapabilitiesV1,
|
|
51
56
|
type HubActionOptionsV2,
|
|
57
|
+
type HubActionOptionsV3,
|
|
58
|
+
parseOperation,
|
|
59
|
+
v1Receipt,
|
|
52
60
|
type ActionOperationV1,
|
|
53
61
|
type ActionOutcomeV1,
|
|
54
62
|
} from '@ixo/action-protocol';
|
|
@@ -103,16 +111,17 @@ import {
|
|
|
103
111
|
applyAttachmentRetention,
|
|
104
112
|
ATTACHMENT_PAYLOAD_TURNS,
|
|
105
113
|
ATTACHMENT_PLACEHOLDER_PREFIX,
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
createAttachmentViewSurface,
|
|
115
|
+
findPersistedAttachmentMeta,
|
|
116
|
+
IngestTextLru,
|
|
108
117
|
INLINE_PAYLOAD_NEEDLE,
|
|
109
118
|
isAttachmentViewMessage,
|
|
110
119
|
offloadInlinePayloads,
|
|
111
120
|
parseAttachmentInputs,
|
|
112
121
|
prepareAttachments,
|
|
113
|
-
viewAttachment,
|
|
114
122
|
type AttachmentViewSurface,
|
|
115
123
|
type MatrixMediaSource,
|
|
124
|
+
type ViewAttachmentDeps,
|
|
116
125
|
type SandboxUploadConfig,
|
|
117
126
|
DOWNLOAD_TIMEOUT_MS,
|
|
118
127
|
MAX_FILE_SIZE,
|
|
@@ -191,6 +200,7 @@ import {
|
|
|
191
200
|
} from '../sqlite/cache-config';
|
|
192
201
|
import { DoSqliteDatabase, type DoSqliteOpenOptions } from '../sqlite/database';
|
|
193
202
|
import type { TierFlushResult } from '../sqlite/do-vfs';
|
|
203
|
+
import type { AttachmentMeta } from '../sqlite/serialization';
|
|
194
204
|
import {
|
|
195
205
|
DEFAULT_TIER_HOT_BUDGET_BYTES,
|
|
196
206
|
type PageTierOptions,
|
|
@@ -264,6 +274,7 @@ import { decideBootDirty } from './boot-dirty';
|
|
|
264
274
|
import { decideFlush } from './flush-schedule';
|
|
265
275
|
import { mirrorTxnId, mirrorEventContent, RoomMirror } from './room-mirror';
|
|
266
276
|
import { prefixSpeaker } from './speaker-prefix';
|
|
277
|
+
import { assembleTurnMessages } from './turn-messages';
|
|
267
278
|
import {
|
|
268
279
|
type ObservedMessage,
|
|
269
280
|
summarizeObservedMessages,
|
|
@@ -599,6 +610,25 @@ const ACTION_TICK_BUDGET_MS = 15_000;
|
|
|
599
610
|
const ACTION_PRUNE_INTERVAL_MS = 60 * 60_000;
|
|
600
611
|
/** How long the Hub's capabilities answer is reused per object. */
|
|
601
612
|
const HUB_CAPABILITIES_CACHE_MS = 60_000;
|
|
613
|
+
/** A Hub network name as the chain-id config key it is configured under. */
|
|
614
|
+
/**
|
|
615
|
+
* Whether two Hub outcomes are the same event. Receipts are compared in the
|
|
616
|
+
* 0.4.1 shape: an action prepared at protocol version 2 (runtime 0.23.0) gets
|
|
617
|
+
* its callback without the 0.5.0 bank fields (`denom`, `amountBaseUnits`),
|
|
618
|
+
* while the confirming status read (version 3) carries them.
|
|
619
|
+
*/
|
|
620
|
+
function sameHubOutcome(a: ActionOutcomeV1, b: ActionOutcomeV1): boolean {
|
|
621
|
+
return (
|
|
622
|
+
canonicalJson({ ...a, receipt: v1Receipt(a.receipt) }) ===
|
|
623
|
+
canonicalJson({ ...b, receipt: v1Receipt(b.receipt) })
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const HUB_NETWORK_CHAIN = {
|
|
628
|
+
'ixo mainnet': 'mainnet',
|
|
629
|
+
'ixo testnet': 'testnet',
|
|
630
|
+
'ixo devnet': 'devnet',
|
|
631
|
+
} as const;
|
|
602
632
|
/** How long a `hub_action_options` answer vouches for a Gmail connection. */
|
|
603
633
|
const HUB_OPTIONS_REUSE_MS = 10 * 60_000;
|
|
604
634
|
/** A channel poll waits this long for Auth Hub, once per poll at most. */
|
|
@@ -1002,8 +1032,10 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
1002
1032
|
at: number;
|
|
1003
1033
|
} | null = null;
|
|
1004
1034
|
/** The last `hub_action_options` answer for this owner (Gmail connection checks). */
|
|
1005
|
-
private lastHubOptions: {
|
|
1006
|
-
|
|
1035
|
+
private lastHubOptions: {
|
|
1036
|
+
value: HubActionOptionsV2 | HubActionOptionsV3;
|
|
1037
|
+
at: number;
|
|
1038
|
+
} | null = null;
|
|
1007
1039
|
private topicProposals: TopicProposalStore | null = null;
|
|
1008
1040
|
/** `assertTopicDependenciesMemo`: card id and digest -> when it passed. */
|
|
1009
1041
|
private topicAuthorityMemo: Map<string, number> | undefined;
|
|
@@ -1011,6 +1043,8 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
1011
1043
|
private lastActionPruneAt: number | null = null;
|
|
1012
1044
|
/** Text `view_attachment` extracted, per session (`attachments/view-cache.ts`). */
|
|
1013
1045
|
private attachmentTextCache: AttachmentTextCacheStore | null = null;
|
|
1046
|
+
/** Large texts of document ingestion, in memory (a few, minutes). */
|
|
1047
|
+
private readonly ingestTextLru = new IngestTextLru();
|
|
1014
1048
|
/**
|
|
1015
1049
|
* Per-model context windows; learned limits persist in this object's KV
|
|
1016
1050
|
* storage (`ctxwin:<model>`), so a provider's rejection is remembered
|
|
@@ -2187,8 +2221,9 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
2187
2221
|
requestKey: string;
|
|
2188
2222
|
operation: ActionOperationV1;
|
|
2189
2223
|
}) => this.requestHubAction(input),
|
|
2224
|
+
transfer: (input) => this.transferHubAction(input),
|
|
2190
2225
|
status: (ownerDid: string, actionId: string) =>
|
|
2191
|
-
this.
|
|
2226
|
+
this.hubActionStatusForTool(ownerDid, actionId),
|
|
2192
2227
|
},
|
|
2193
2228
|
}
|
|
2194
2229
|
: {}),
|
|
@@ -3207,11 +3242,13 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3207
3242
|
this.markDirty();
|
|
3208
3243
|
await this.requestHousekeeping(Date.now() + 1000);
|
|
3209
3244
|
}
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3245
|
+
// The gateway parses with @ixo/action-protocol 0.4.1, whose bank
|
|
3246
|
+
// receipt is exact: the 0.5.0 `denom`/`amountBaseUnits` fields are
|
|
3247
|
+
// dropped here (the owner-facing amount line comes from
|
|
3248
|
+
// hub_action_status instead), so the emitted shape stays 0.4.1.
|
|
3249
|
+
const hubActionOutcomes = (
|
|
3250
|
+
await this.hubOutcomesForRun(ownerDid, result.sessionId, result.runId)
|
|
3251
|
+
).map((outcome) => ({ ...outcome, receipt: v1Receipt(outcome.receipt) }));
|
|
3215
3252
|
return {
|
|
3216
3253
|
...result,
|
|
3217
3254
|
...(actionOutcomes.length ? { actionOutcomes } : {}),
|
|
@@ -3219,14 +3256,64 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3219
3256
|
};
|
|
3220
3257
|
}
|
|
3221
3258
|
|
|
3222
|
-
|
|
3259
|
+
/** `hub_action_options` and the transfer path: version 3, with the owner's tokens. */
|
|
3260
|
+
private async hubActionOptions(
|
|
3261
|
+
input: {
|
|
3262
|
+
ownerDid: string;
|
|
3263
|
+
provider: string;
|
|
3264
|
+
bindingId: string;
|
|
3265
|
+
bindingRevision: number;
|
|
3266
|
+
},
|
|
3267
|
+
bindingChecked = false,
|
|
3268
|
+
): Promise<HubActionOptionsV3> {
|
|
3269
|
+
const client = this.hubActionClient;
|
|
3270
|
+
if (!client || input.ownerDid !== this.userDid)
|
|
3271
|
+
throw new Error('Hub action options are unavailable for this owner');
|
|
3272
|
+
return this.readHubOptions(input, bindingChecked, (binding) =>
|
|
3273
|
+
client.options(binding),
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
/**
|
|
3278
|
+
* Read the owner's options for the trusted binding (validated here
|
|
3279
|
+
* unless the caller already validated it in this same call) and keep
|
|
3280
|
+
* the read for the Gmail connection check.
|
|
3281
|
+
*/
|
|
3282
|
+
private async readHubOptions<
|
|
3283
|
+
T extends HubActionOptionsV2 | HubActionOptionsV3,
|
|
3284
|
+
>(
|
|
3285
|
+
input: {
|
|
3286
|
+
ownerDid: string;
|
|
3287
|
+
provider: string;
|
|
3288
|
+
bindingId: string;
|
|
3289
|
+
bindingRevision: number;
|
|
3290
|
+
},
|
|
3291
|
+
bindingChecked: boolean,
|
|
3292
|
+
read: (binding: {
|
|
3293
|
+
bindingId: string;
|
|
3294
|
+
bindingRevision: number;
|
|
3295
|
+
}) => Promise<T>,
|
|
3296
|
+
): Promise<T> {
|
|
3297
|
+
if (!bindingChecked) await this.assertHubBinding(input);
|
|
3298
|
+
const options = await read(input);
|
|
3299
|
+
if (
|
|
3300
|
+
options.ownerDid !== input.ownerDid ||
|
|
3301
|
+
options.bindingId !== input.bindingId ||
|
|
3302
|
+
options.bindingRevision !== input.bindingRevision
|
|
3303
|
+
)
|
|
3304
|
+
throw new Error(
|
|
3305
|
+
'Hub action options do not match this owner and binding',
|
|
3306
|
+
);
|
|
3307
|
+
this.lastHubOptions = { value: options, at: Date.now() };
|
|
3308
|
+
return options;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
private async assertHubBinding(input: {
|
|
3223
3312
|
ownerDid: string;
|
|
3224
3313
|
provider: string;
|
|
3225
3314
|
bindingId: string;
|
|
3226
3315
|
bindingRevision: number;
|
|
3227
|
-
}) {
|
|
3228
|
-
if (!this.hubActionClient || input.ownerDid !== this.userDid)
|
|
3229
|
-
throw new Error('Hub action options are unavailable for this owner');
|
|
3316
|
+
}): Promise<void> {
|
|
3230
3317
|
await assertActiveChannelBinding(
|
|
3231
3318
|
{
|
|
3232
3319
|
userDid: input.ownerDid,
|
|
@@ -3238,17 +3325,34 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3238
3325
|
},
|
|
3239
3326
|
this.env,
|
|
3240
3327
|
);
|
|
3241
|
-
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
/**
|
|
3331
|
+
* Only a run an inbound channel message started may prepare a review:
|
|
3332
|
+
* a scheduled or background run has no user asking for it. Checked
|
|
3333
|
+
* before any Hub read.
|
|
3334
|
+
*/
|
|
3335
|
+
private async assertHubTurnOrigin(input: {
|
|
3336
|
+
ownerDid: string;
|
|
3337
|
+
sessionId: string;
|
|
3338
|
+
runId: string;
|
|
3339
|
+
}): Promise<void> {
|
|
3340
|
+
const record = await this.runStore?.get(input.runId);
|
|
3341
|
+
const turn = record
|
|
3342
|
+
? (JSON.parse(record.request) as StoredRunRequest).turn
|
|
3343
|
+
: null;
|
|
3242
3344
|
if (
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3345
|
+
!turn ||
|
|
3346
|
+
turn.client !== 'channel' ||
|
|
3347
|
+
!turn.channel?.remoteMessageRef ||
|
|
3348
|
+
turn.identity.userDid !== input.ownerDid ||
|
|
3349
|
+
turn.sessionId !== input.sessionId ||
|
|
3350
|
+
turn.sessionId.startsWith(SYNTHETIC_SESSION_PREFIX)
|
|
3246
3351
|
)
|
|
3247
|
-
throw new
|
|
3248
|
-
'
|
|
3352
|
+
throw new HubActionRefused(
|
|
3353
|
+
'limit',
|
|
3354
|
+
'Reviews are prepared only in reply to a message from the user.',
|
|
3249
3355
|
);
|
|
3250
|
-
this.lastHubOptions = { value: options, at: Date.now() };
|
|
3251
|
-
return options;
|
|
3252
3356
|
}
|
|
3253
3357
|
|
|
3254
3358
|
/**
|
|
@@ -3280,7 +3384,9 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3280
3384
|
cached.value.bindingId === input.bindingId &&
|
|
3281
3385
|
cached.value.bindingRevision === input.bindingRevision
|
|
3282
3386
|
? cached.value
|
|
3283
|
-
: await this.
|
|
3387
|
+
: await this.readHubOptions(input, true, (binding) =>
|
|
3388
|
+
this.hubActionClient!.connectionOptions(binding),
|
|
3389
|
+
);
|
|
3284
3390
|
if (
|
|
3285
3391
|
!options.connections.some(
|
|
3286
3392
|
(c) =>
|
|
@@ -3320,50 +3426,32 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3320
3426
|
return value;
|
|
3321
3427
|
}
|
|
3322
3428
|
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3429
|
+
/**
|
|
3430
|
+
* `checked`: the transfer path already ran the turn-origin refusal and
|
|
3431
|
+
* validated the binding in this same call.
|
|
3432
|
+
*/
|
|
3433
|
+
private async requestHubAction(
|
|
3434
|
+
input: {
|
|
3435
|
+
ownerDid: string;
|
|
3436
|
+
sessionId: string;
|
|
3437
|
+
runId: string;
|
|
3438
|
+
requestId: string;
|
|
3439
|
+
provider: string;
|
|
3440
|
+
bindingId: string;
|
|
3441
|
+
bindingRevision: number;
|
|
3442
|
+
requestKey: string;
|
|
3443
|
+
operation: ActionOperationV1;
|
|
3444
|
+
},
|
|
3445
|
+
checked = false,
|
|
3446
|
+
) {
|
|
3334
3447
|
const store = this.hubActionStore;
|
|
3335
3448
|
const client = this.hubActionClient;
|
|
3336
3449
|
if (!store || !client || input.ownerDid !== this.userDid)
|
|
3337
3450
|
throw new Error('Hub actions are unavailable for this channel');
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
? (JSON.parse(record.request) as StoredRunRequest).turn
|
|
3343
|
-
: null;
|
|
3344
|
-
if (
|
|
3345
|
-
!turn ||
|
|
3346
|
-
turn.client !== 'channel' ||
|
|
3347
|
-
!turn.channel?.remoteMessageRef ||
|
|
3348
|
-
turn.identity.userDid !== input.ownerDid ||
|
|
3349
|
-
turn.sessionId !== input.sessionId ||
|
|
3350
|
-
turn.sessionId.startsWith(SYNTHETIC_SESSION_PREFIX)
|
|
3351
|
-
)
|
|
3352
|
-
throw new HubActionRefused(
|
|
3353
|
-
'limit',
|
|
3354
|
-
'Reviews are prepared only in reply to a message from the user.',
|
|
3355
|
-
);
|
|
3356
|
-
await assertActiveChannelBinding(
|
|
3357
|
-
{
|
|
3358
|
-
userDid: input.ownerDid,
|
|
3359
|
-
channel: {
|
|
3360
|
-
provider: input.provider,
|
|
3361
|
-
bindingId: input.bindingId,
|
|
3362
|
-
bindingRevision: input.bindingRevision,
|
|
3363
|
-
},
|
|
3364
|
-
},
|
|
3365
|
-
this.env,
|
|
3366
|
-
);
|
|
3451
|
+
if (!checked) {
|
|
3452
|
+
await this.assertHubTurnOrigin(input);
|
|
3453
|
+
await this.assertHubBinding(input);
|
|
3454
|
+
}
|
|
3367
3455
|
await this.assertGmailOperationAllowed(input);
|
|
3368
3456
|
const reference = await prepareHubAction(
|
|
3369
3457
|
{
|
|
@@ -3385,6 +3473,107 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3385
3473
|
return reference;
|
|
3386
3474
|
}
|
|
3387
3475
|
|
|
3476
|
+
/**
|
|
3477
|
+
* A bank send of one of the owner's listed tokens. The token comes from
|
|
3478
|
+
* a fresh options read (its balance must be current), the amount is
|
|
3479
|
+
* converted to base units with the listed decimals (unverified tokens
|
|
3480
|
+
* list 0: base units), refused when zero or above the balance; the
|
|
3481
|
+
* network is the account's capability, the chain id this Worker's own
|
|
3482
|
+
* for that network, the sender the owner's account. No fee is sent: the
|
|
3483
|
+
* Hub simulates gas and sets it.
|
|
3484
|
+
*/
|
|
3485
|
+
private async transferHubAction(input: {
|
|
3486
|
+
ownerDid: string;
|
|
3487
|
+
sessionId: string;
|
|
3488
|
+
runId: string;
|
|
3489
|
+
requestId: string;
|
|
3490
|
+
provider: string;
|
|
3491
|
+
bindingId: string;
|
|
3492
|
+
bindingRevision: number;
|
|
3493
|
+
requestKey: string;
|
|
3494
|
+
toAddress: string;
|
|
3495
|
+
denom: string;
|
|
3496
|
+
amount: string;
|
|
3497
|
+
memo: string;
|
|
3498
|
+
}) {
|
|
3499
|
+
if (!this.hubActionStore || input.ownerDid !== this.userDid)
|
|
3500
|
+
throw new Error('Hub actions are unavailable for this channel');
|
|
3501
|
+
await this.assertHubTurnOrigin(input);
|
|
3502
|
+
await this.assertHubBinding(input);
|
|
3503
|
+
const options = await this.hubActionOptions(input, true);
|
|
3504
|
+
const tokens = options.transfer?.tokens;
|
|
3505
|
+
if (!tokens?.length)
|
|
3506
|
+
throw new HubActionRefused(
|
|
3507
|
+
'refused',
|
|
3508
|
+
'Bank sends are not available for this owner right now (no token list from Auth Hub).',
|
|
3509
|
+
);
|
|
3510
|
+
const token = tokens.find((item) => item.denom === input.denom);
|
|
3511
|
+
if (!token)
|
|
3512
|
+
throw new HubActionRefused(
|
|
3513
|
+
'refused',
|
|
3514
|
+
`That token is not in the owner's list; use a denom exactly as hub_action_options lists it (${tokens
|
|
3515
|
+
.map((item) => item.denom)
|
|
3516
|
+
.slice(0, 10)
|
|
3517
|
+
.join(', ')}).`,
|
|
3518
|
+
);
|
|
3519
|
+
const amountBaseUnits = toBaseUnits(input.amount, token.decimals);
|
|
3520
|
+
if (amountBaseUnits > BigInt(token.balanceBaseUnits))
|
|
3521
|
+
throw new HubActionRefused(
|
|
3522
|
+
'refused',
|
|
3523
|
+
`The owner holds ${describeAmount(token.balanceBaseUnits, token)}, less than this transfer.`,
|
|
3524
|
+
);
|
|
3525
|
+
const capabilities = await this.cachedHubCapabilities();
|
|
3526
|
+
if (!capabilities.network)
|
|
3527
|
+
throw new HubActionRefused(
|
|
3528
|
+
'refused',
|
|
3529
|
+
'Bank sends are not enabled for this oracle account.',
|
|
3530
|
+
);
|
|
3531
|
+
const network = HUB_NETWORK_CHAIN[capabilities.network];
|
|
3532
|
+
const chainId = chainIdsFromConfig(chainIdConfigSchema.parse(this.env))[
|
|
3533
|
+
network
|
|
3534
|
+
];
|
|
3535
|
+
let operation: ActionOperationV1;
|
|
3536
|
+
try {
|
|
3537
|
+
operation = parseOperation({
|
|
3538
|
+
kind: 'ixo.bank.send.v1',
|
|
3539
|
+
chainId,
|
|
3540
|
+
network: capabilities.network,
|
|
3541
|
+
fromAddress: input.ownerDid.replace(/^did:ixo:/, ''),
|
|
3542
|
+
toAddress: input.toAddress,
|
|
3543
|
+
amountBaseUnits: amountBaseUnits.toString(),
|
|
3544
|
+
denom: token.denom,
|
|
3545
|
+
memo: input.memo,
|
|
3546
|
+
});
|
|
3547
|
+
} catch (error) {
|
|
3548
|
+
// Only the protocol refusing the operation built from the model's
|
|
3549
|
+
// fields (an invalid recipient or memo) is the model's to fix.
|
|
3550
|
+
throw new HubActionRefused(
|
|
3551
|
+
'refused',
|
|
3552
|
+
`The transfer is incomplete (${error instanceof Error ? error.message : String(error)}).`,
|
|
3553
|
+
);
|
|
3554
|
+
}
|
|
3555
|
+
const reference = await this.requestHubAction(
|
|
3556
|
+
{ ...input, operation },
|
|
3557
|
+
true,
|
|
3558
|
+
);
|
|
3559
|
+
await this.hubActionStore.setToken(reference.id, token);
|
|
3560
|
+
return reference;
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
/**
|
|
3564
|
+
* `hub_action_status`: the outcome plus, for a bank send, the
|
|
3565
|
+
* owner-facing line (amount from the token stored at preparation).
|
|
3566
|
+
*/
|
|
3567
|
+
private async hubActionStatusForTool(
|
|
3568
|
+
ownerDid: string,
|
|
3569
|
+
actionId: string,
|
|
3570
|
+
): Promise<{ outcome: ActionOutcomeV1 | null; summary: string | null }> {
|
|
3571
|
+
const outcome = await this.statusHubAction(ownerDid, actionId);
|
|
3572
|
+
if (!outcome) return { outcome, summary: null };
|
|
3573
|
+
const token = await this.hubActionStore!.token(ownerDid, actionId);
|
|
3574
|
+
return { outcome, summary: bankReceiptSummary(outcome.receipt, token) };
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3388
3577
|
private async statusHubAction(
|
|
3389
3578
|
ownerDid: string,
|
|
3390
3579
|
actionId: string,
|
|
@@ -3475,15 +3664,24 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3475
3664
|
input.reference.id,
|
|
3476
3665
|
);
|
|
3477
3666
|
if (!held || !this.hubActionClient) throw new Error('Unknown Hub action');
|
|
3667
|
+
if (canonicalJson(input.reference) !== canonicalJson(held.reference))
|
|
3668
|
+
throw new Error('Hub outcome does not match the prepared action');
|
|
3669
|
+
// A replay of an outcome already confirmed and stored (a Hub retry,
|
|
3670
|
+
// a callback delivered twice) is acknowledged without a Hub read.
|
|
3671
|
+
const stored = await this.hubActionStore!.outcomeAt(
|
|
3672
|
+
input.ownerDid,
|
|
3673
|
+
input.reference.id,
|
|
3674
|
+
input.sequence,
|
|
3675
|
+
);
|
|
3676
|
+
if (stored && sameHubOutcome(stored, input)) return;
|
|
3478
3677
|
const remote = await this.hubActionClient.status(input.reference.id);
|
|
3479
3678
|
if (
|
|
3480
3679
|
!remote.outcome ||
|
|
3481
3680
|
remote.ownerDid !== input.ownerDid ||
|
|
3482
3681
|
canonicalJson(remote.reference) !== canonicalJson(held.reference) ||
|
|
3483
|
-
canonicalJson(input.reference) !== canonicalJson(held.reference) ||
|
|
3484
3682
|
remote.outcome.sequence < input.sequence ||
|
|
3485
3683
|
(remote.outcome.sequence === input.sequence &&
|
|
3486
|
-
|
|
3684
|
+
!sameHubOutcome(remote.outcome, input))
|
|
3487
3685
|
)
|
|
3488
3686
|
throw new Error(
|
|
3489
3687
|
'Hub outcome was not confirmed by authenticated status',
|
|
@@ -3732,13 +3930,6 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3732
3930
|
input.evidenceRefs,
|
|
3733
3931
|
);
|
|
3734
3932
|
if (prior) return prior;
|
|
3735
|
-
// The caps before the authority read it would waste (create checks
|
|
3736
|
-
// them again, atomically).
|
|
3737
|
-
await topics.assertCapacity(
|
|
3738
|
-
this.userDid,
|
|
3739
|
-
input.origin.sessionId,
|
|
3740
|
-
input.origin.runId,
|
|
3741
|
-
);
|
|
3742
3933
|
const rpc = {
|
|
3743
3934
|
devnet: this.env.ACTION_PROPOSALS_RPC_DEVNET,
|
|
3744
3935
|
testnet: this.env.ACTION_PROPOSALS_RPC_TESTNET,
|
|
@@ -6691,54 +6882,34 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
6691
6882
|
ambientLlm: LlmAdapter;
|
|
6692
6883
|
platform: OpenRouterLlmAdapter;
|
|
6693
6884
|
signal: AbortSignal;
|
|
6885
|
+
/** Attachments of the turn now running (not necessarily persisted yet). */
|
|
6886
|
+
turnMetas: readonly AttachmentMeta[];
|
|
6694
6887
|
}): Promise<AttachmentViewSurface> {
|
|
6695
6888
|
const saver = this.saver!;
|
|
6696
6889
|
const offloaded = await saver.hasThreadMessageMatching(input.sessionId, {
|
|
6697
6890
|
messageType: 'human',
|
|
6698
6891
|
contentContains: ATTACHMENT_PLACEHOLDER_PREFIX,
|
|
6699
6892
|
});
|
|
6700
|
-
const findMeta =
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
};
|
|
6716
|
-
return {
|
|
6893
|
+
const findMeta = (ref: string) =>
|
|
6894
|
+
findPersistedAttachmentMeta(saver, input.sessionId, ref);
|
|
6895
|
+
const viewDeps = (): ViewAttachmentDeps => ({
|
|
6896
|
+
source: this.matrixMediaSource(),
|
|
6897
|
+
extraction: this.extractionProvider(input.ambientLlm, input.platform),
|
|
6898
|
+
caps: getModelCapabilities(input.model),
|
|
6899
|
+
roomId: input.roomId,
|
|
6900
|
+
signal: input.signal,
|
|
6901
|
+
logger: console,
|
|
6902
|
+
...(this.attachmentTextCache
|
|
6903
|
+
? { cache: this.attachmentTextCache.forSession(input.sessionId) }
|
|
6904
|
+
: {}),
|
|
6905
|
+
ingestCache: this.ingestTextLru.forSession(input.sessionId),
|
|
6906
|
+
});
|
|
6907
|
+
return createAttachmentViewSurface({
|
|
6717
6908
|
offloaded,
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
`no attachment with ref "${ref}" in this conversation`,
|
|
6723
|
-
);
|
|
6724
|
-
}
|
|
6725
|
-
const view = await viewAttachment(meta, {
|
|
6726
|
-
source: this.matrixMediaSource(),
|
|
6727
|
-
extraction: this.extractionProvider(
|
|
6728
|
-
input.ambientLlm,
|
|
6729
|
-
input.platform,
|
|
6730
|
-
),
|
|
6731
|
-
caps: getModelCapabilities(input.model),
|
|
6732
|
-
roomId: input.roomId,
|
|
6733
|
-
signal: input.signal,
|
|
6734
|
-
logger: console,
|
|
6735
|
-
...(this.attachmentTextCache
|
|
6736
|
-
? { cache: this.attachmentTextCache.forSession(input.sessionId) }
|
|
6737
|
-
: {}),
|
|
6738
|
-
});
|
|
6739
|
-
return { meta, view };
|
|
6740
|
-
},
|
|
6741
|
-
};
|
|
6909
|
+
turnMetas: input.turnMetas,
|
|
6910
|
+
findPersisted: findMeta,
|
|
6911
|
+
deps: viewDeps,
|
|
6912
|
+
});
|
|
6742
6913
|
}
|
|
6743
6914
|
|
|
6744
6915
|
/**
|
|
@@ -7114,6 +7285,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
7114
7285
|
ambientLlm: baseAmbient.llm,
|
|
7115
7286
|
platform: core.llm,
|
|
7116
7287
|
signal: abortController.signal,
|
|
7288
|
+
turnMetas: prepared?.metas ?? [],
|
|
7117
7289
|
});
|
|
7118
7290
|
|
|
7119
7291
|
// Request metadata (editor room, space, session run, entity) → state,
|
|
@@ -7511,36 +7683,30 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
7511
7683
|
? undefined
|
|
7512
7684
|
: renderTurnTimeNote(turnAt, req.identity.timezone);
|
|
7513
7685
|
const content = timeNote ? withTurnTimeNote(spoken, timeNote) : spoken;
|
|
7686
|
+
const human = new HumanMessage({
|
|
7687
|
+
content,
|
|
7688
|
+
additional_kwargs: {
|
|
7689
|
+
timestamp: turnAt.toISOString(),
|
|
7690
|
+
...(timeNote ? { [TURN_TIME_NOTE_KWARG]: timeNote } : {}),
|
|
7691
|
+
oracleName: core.identity.name,
|
|
7692
|
+
msgFromMatrixRoom: req.client === 'matrix',
|
|
7693
|
+
...speakerKwargs,
|
|
7694
|
+
...(req.channel
|
|
7695
|
+
? { 'org.ixo.qi.origin': channelOrigin(req.channel) }
|
|
7696
|
+
: {}),
|
|
7697
|
+
...attachmentKwargs,
|
|
7698
|
+
},
|
|
7699
|
+
});
|
|
7514
7700
|
const stateInput = {
|
|
7515
|
-
messages
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
...(req.channel
|
|
7525
|
-
? { 'org.ixo.qi.origin': channelOrigin(req.channel) }
|
|
7526
|
-
: {}),
|
|
7527
|
-
...attachmentKwargs,
|
|
7528
|
-
},
|
|
7529
|
-
}),
|
|
7530
|
-
// Extraction-lane text rides as hidden context messages (the
|
|
7531
|
-
// transcript hides AI messages that carry an `attachment`), as on Node.
|
|
7532
|
-
...(prepared?.extracted ?? []).map(
|
|
7533
|
-
(item) =>
|
|
7534
|
-
new AIMessage({
|
|
7535
|
-
content: item.text,
|
|
7536
|
-
additional_kwargs: {
|
|
7537
|
-
msgFromMatrixRoom: req.client === 'matrix',
|
|
7538
|
-
timestamp: new Date().toISOString(),
|
|
7539
|
-
attachment: item.meta,
|
|
7540
|
-
},
|
|
7541
|
-
}),
|
|
7542
|
-
),
|
|
7543
|
-
],
|
|
7701
|
+
// Hidden attachment context (the transcript hides AI messages that
|
|
7702
|
+
// carry an `attachment`): native-lane notes precede the person's
|
|
7703
|
+
// message, extraction-lane text follows it, as on Node.
|
|
7704
|
+
messages: assembleTurnMessages({
|
|
7705
|
+
human,
|
|
7706
|
+
extracted: prepared?.extracted ?? [],
|
|
7707
|
+
nativeNotes: prepared?.nativeNotes ?? [],
|
|
7708
|
+
matrixRoom: req.client === 'matrix',
|
|
7709
|
+
}),
|
|
7544
7710
|
config: { did: req.identity.userDid },
|
|
7545
7711
|
client: req.client,
|
|
7546
7712
|
// First contact's gathered context is checkpointed with the turn,
|