@ixo/oracle-runtime-workers 0.22.0 → 0.24.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/README.md +15 -13
- package/package.json +10 -4
- package/src/action-proposals/policy.ts +54 -0
- package/src/action-proposals/reconcile.ts +87 -0
- package/src/action-proposals/store.ts +863 -0
- package/src/action-proposals/topic-budget.ts +84 -0
- package/src/action-proposals/topic-chain.ts +272 -0
- package/src/action-proposals/topic-effects.ts +44 -0
- package/src/action-proposals/topic-errors.ts +79 -0
- package/src/action-proposals/topic-history.ts +267 -0
- package/src/action-proposals/topic-matrix.ts +292 -0
- package/src/action-proposals/topic-prepare.ts +299 -0
- package/src/action-proposals/topic-store.ts +999 -0
- package/src/action-proposals/topic-verify.ts +279 -0
- package/src/action-proposals/verify.ts +223 -0
- package/src/channels/auth.ts +6 -1
- package/src/channels/contract.ts +4 -0
- package/src/channels/turns.ts +1 -0
- package/src/core/env.ts +93 -6
- package/src/core/main-agent-types.ts +8 -0
- package/src/core/main-agent.ts +2 -0
- package/src/core/runtime-context.ts +12 -0
- package/src/delivery/plan.ts +43 -2
- package/src/delivery/schema.ts +25 -1
- package/src/delivery/types.ts +13 -2
- package/src/do/ambient.ts +6 -0
- package/src/do/contracts.ts +39 -0
- package/src/do/user-oracle-do.ts +1275 -8
- package/src/hub-actions/client.ts +163 -0
- package/src/hub-actions/request.ts +110 -0
- package/src/hub-actions/store.ts +485 -0
- package/src/matrix/gateway-do.ts +176 -0
- package/src/matrix/reply-parts.ts +8 -2
- package/src/memory/session-history-indexer.ts +30 -3
- package/src/memory/user-context-fetcher.ts +36 -12
- package/src/plugin-api/types.ts +54 -0
- package/src/plugins/action-proposals/action-proposals.plugin.ts +486 -0
- package/src/plugins/action-proposals/evidence.ts +67 -0
- package/src/plugins/hub-actions/hub-actions.plugin.ts +244 -0
- package/src/plugins/index.ts +6 -0
- package/src/plugins/memory/index.ts +4 -0
- package/src/plugins/memory/memory-entity.ts +49 -0
- package/src/plugins/memory/memory-tools.ts +7 -4
- package/src/plugins/memory/memory-ucan.ts +10 -1
- package/src/plugins/memory/memory.plugin.ts +22 -7
- package/src/shell/app.ts +195 -1
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* - Auth is the oracle's two-hop UCAN invocation minted from the user's
|
|
17
17
|
* delegation (`ixo:memory` / `memory/*`) — no delegation, no indexing.
|
|
18
18
|
* - Endpoint: `POST <MEMORY_ENGINE_URL>/messages` with `x-room-id`, as
|
|
19
|
-
* `@ixo/common`'s `MemoryEngineService.processConversationHistory
|
|
19
|
+
* `@ixo/common`'s `MemoryEngineService.processConversationHistory`, plus
|
|
20
|
+
* `x-oracle-entity-did`: the engine keys the user's space by the oracle's
|
|
21
|
+
* domain entity (`ORACLE_ENTITY_DID`). Without one nothing is indexed
|
|
22
|
+
* (one warning per indexer) and the watermark stays put.
|
|
20
23
|
* - A per-session lock prevents overlapping runs; a failed upload is
|
|
21
24
|
* retried 3× with a 10 s pause (Node's numbers).
|
|
22
25
|
*
|
|
@@ -24,6 +27,7 @@
|
|
|
24
27
|
* preferred name is set; here the fallback is the literal "Me" straight away.
|
|
25
28
|
*/
|
|
26
29
|
import type { Logger } from '../plugin-api/types';
|
|
30
|
+
import { MEMORY_ENTITY_HEADER } from '../plugins/memory/memory-entity';
|
|
27
31
|
|
|
28
32
|
export interface HistoryMessage {
|
|
29
33
|
/** LangChain message type: `human` | `ai` | `tool` | `system`. */
|
|
@@ -53,6 +57,12 @@ export interface SpeakerLabels {
|
|
|
53
57
|
export interface SessionHistoryIndexerDeps {
|
|
54
58
|
/** `MEMORY_ENGINE_URL`; undefined disables indexing (memory plugin absent). */
|
|
55
59
|
memoryEngineUrl: string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The configured `ORACLE_ENTITY_DID` (a well-formed domain entity DID),
|
|
62
|
+
* sent as `x-oracle-entity-did`. Never the `ORACLE_DID` fallback:
|
|
63
|
+
* undefined → every session is `skipped`.
|
|
64
|
+
*/
|
|
65
|
+
entityDid: string | undefined;
|
|
56
66
|
getSession(sessionId: string): Promise<IndexableSession | undefined>;
|
|
57
67
|
/** The session transcript, summarisation bookkeeping already removed. */
|
|
58
68
|
listMessages(sessionId: string): Promise<HistoryMessage[]>;
|
|
@@ -153,6 +163,9 @@ export class SessionHistoryIndexer {
|
|
|
153
163
|
|
|
154
164
|
private readonly requestTimeoutMs: number;
|
|
155
165
|
|
|
166
|
+
/** The missing-entity warning has been logged (it is logged once). */
|
|
167
|
+
private warnedNoEntity = false;
|
|
168
|
+
|
|
156
169
|
constructor(private readonly deps: SessionHistoryIndexerDeps) {
|
|
157
170
|
this.fetchImpl = deps.fetchImpl ?? fetch.bind(globalThis);
|
|
158
171
|
this.sleep =
|
|
@@ -187,6 +200,16 @@ export class SessionHistoryIndexer {
|
|
|
187
200
|
);
|
|
188
201
|
return 'skipped';
|
|
189
202
|
}
|
|
203
|
+
const entityDid = this.deps.entityDid;
|
|
204
|
+
if (!entityDid) {
|
|
205
|
+
if (!this.warnedNoEntity) {
|
|
206
|
+
this.warnedNoEntity = true;
|
|
207
|
+
logger.warn(
|
|
208
|
+
`[history-indexer] ORACLE_ENTITY_DID is not a domain entity DID (did:ixo:entity:<32 hex>); the memory engine keys every space by it, so session history is not indexed`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return 'skipped';
|
|
212
|
+
}
|
|
190
213
|
if (this.inFlight.has(sessionId)) {
|
|
191
214
|
logger.log(
|
|
192
215
|
`[history-indexer] session ${sessionId} is already being indexed, skipping`,
|
|
@@ -197,7 +220,7 @@ export class SessionHistoryIndexer {
|
|
|
197
220
|
try {
|
|
198
221
|
for (let attempt = 1; attempt <= this.maxRetries; attempt += 1) {
|
|
199
222
|
try {
|
|
200
|
-
const outcome = await this.processOnce(sessionId);
|
|
223
|
+
const outcome = await this.processOnce(sessionId, entityDid);
|
|
201
224
|
if (outcome === 'processed') {
|
|
202
225
|
logger.log(
|
|
203
226
|
`[history-indexer] indexed session ${sessionId} into the memory engine`,
|
|
@@ -223,7 +246,10 @@ export class SessionHistoryIndexer {
|
|
|
223
246
|
}
|
|
224
247
|
}
|
|
225
248
|
|
|
226
|
-
private async processOnce(
|
|
249
|
+
private async processOnce(
|
|
250
|
+
sessionId: string,
|
|
251
|
+
entityDid: string,
|
|
252
|
+
): Promise<IndexOutcome> {
|
|
227
253
|
const { deps } = this;
|
|
228
254
|
const { logger } = deps;
|
|
229
255
|
const memoryEngineUrl = deps.memoryEngineUrl!.replace(/\/+$/, '');
|
|
@@ -306,6 +332,7 @@ export class SessionHistoryIndexer {
|
|
|
306
332
|
headers: {
|
|
307
333
|
Authorization: `Bearer ${invocation}`,
|
|
308
334
|
'X-Auth-Type': 'ucan',
|
|
335
|
+
[MEMORY_ENTITY_HEADER]: entityDid,
|
|
309
336
|
'x-room-id': roomId,
|
|
310
337
|
'Content-Type': 'application/json',
|
|
311
338
|
},
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* memory-context block before the model's first call.
|
|
10
10
|
*
|
|
11
11
|
* Policy:
|
|
12
|
-
* - The result is a function of the room (and the oracle
|
|
12
|
+
* - The result is a function of the room (and the oracle's domain entity,
|
|
13
|
+
* which keys every memory space on the engine), not the session,
|
|
13
14
|
* so the cache is keyed by room: a new session in the same room reuses it.
|
|
14
15
|
* Positive entries live `cacheTtlMs` (5 min) in an LRU of `maxCachedRooms`.
|
|
15
16
|
* A successful batch is cached even when every slot is empty — "no
|
|
@@ -25,7 +26,9 @@
|
|
|
25
26
|
* a memory write and after session history is indexed.
|
|
26
27
|
* - A missing signing key or a failed invocation mint is a skip, never a
|
|
27
28
|
* negative-cache entry: the user may delegate `ixo:memory` before the next
|
|
28
|
-
* turn.
|
|
29
|
+
* turn. So is a missing domain entity (`ORACLE_ENTITY_DID`): the engine
|
|
30
|
+
* refuses a request that names none, so none is sent; it is warned about
|
|
31
|
+
* once per fetcher.
|
|
29
32
|
* - One gather per room at a time; a second caller joins it. The blocking
|
|
30
33
|
* `fetch` waits at most `blockingCapMs`; past the cap the gather keeps
|
|
31
34
|
* running and fills the cache for later turns (the caller keeps the
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
*/
|
|
42
45
|
import type { MemoryContextSection } from '../core/prompt-composer';
|
|
43
46
|
import type { Logger, UserContextData } from '../plugin-api/types';
|
|
47
|
+
import { MEMORY_ENTITY_HEADER } from '../plugins/memory/memory-entity';
|
|
44
48
|
import type {
|
|
45
49
|
ComparisonOperator,
|
|
46
50
|
EntityType,
|
|
@@ -99,15 +103,17 @@ const RECENT_WINDOW_DAYS = 90;
|
|
|
99
103
|
* brittle), `node_labels` only where it discriminates, and the `recent`
|
|
100
104
|
* slot bounded to the last 90 days explicitly (the server's
|
|
101
105
|
* `recent_memory` strategy injects the same lower bound and keeps an
|
|
102
|
-
* existing one).
|
|
106
|
+
* existing one). `oracle_dids` names the oracle's domain entity.
|
|
103
107
|
*/
|
|
104
108
|
export function buildUserContextQueries(
|
|
105
|
-
|
|
109
|
+
entityDid: string,
|
|
106
110
|
now: Date,
|
|
107
111
|
): UserContextQuery[] {
|
|
108
112
|
const recentSince = new Date(now.getTime());
|
|
109
113
|
recentSince.setUTCDate(recentSince.getUTCDate() - RECENT_WINDOW_DAYS);
|
|
110
|
-
|
|
114
|
+
// The wire name predates entity keying; the engine reads its entries as
|
|
115
|
+
// domain entity DIDs (the knowledge spaces the queries also search).
|
|
116
|
+
const oracle_dids = [entityDid];
|
|
111
117
|
return [
|
|
112
118
|
{
|
|
113
119
|
oracle_dids,
|
|
@@ -471,8 +477,13 @@ function perSlot<T>(
|
|
|
471
477
|
export interface UserContextFetcherDeps {
|
|
472
478
|
/** `MEMORY_ENGINE_URL`; undefined → every call is `skipped`, no request. */
|
|
473
479
|
memoryEngineUrl: string | undefined;
|
|
474
|
-
/**
|
|
475
|
-
|
|
480
|
+
/**
|
|
481
|
+
* The configured `ORACLE_ENTITY_DID` (a well-formed domain entity DID) —
|
|
482
|
+
* sent as `x-oracle-entity-did` and as the queries' `oracle_dids`. Never
|
|
483
|
+
* the `ORACLE_DID` fallback: undefined → every call is `skipped`
|
|
484
|
+
* (`no-entity`), no request, one warning per fetcher.
|
|
485
|
+
*/
|
|
486
|
+
entityDid: string | undefined;
|
|
476
487
|
/** The user's UCAN surface, bound to the user's DID by the caller. */
|
|
477
488
|
ucan: {
|
|
478
489
|
hasSigningKey(): boolean;
|
|
@@ -515,6 +526,7 @@ const DEFAULT_MAX_CACHED_ROOMS = 64;
|
|
|
515
526
|
|
|
516
527
|
export type UserContextSkipReason =
|
|
517
528
|
| 'no-engine-url'
|
|
529
|
+
| 'no-entity'
|
|
518
530
|
| 'no-signing-key'
|
|
519
531
|
| 'no-invocation'
|
|
520
532
|
| 'recently-failed'
|
|
@@ -616,6 +628,9 @@ export class UserContextFetcher {
|
|
|
616
628
|
|
|
617
629
|
private readonly maxCachedRooms: number;
|
|
618
630
|
|
|
631
|
+
/** The missing-entity warning has been logged (it is logged once). */
|
|
632
|
+
private warnedNoEntity = false;
|
|
633
|
+
|
|
619
634
|
constructor(
|
|
620
635
|
private readonly deps: UserContextFetcherDeps,
|
|
621
636
|
options: UserContextFetcherOptions = {},
|
|
@@ -773,6 +788,16 @@ export class UserContextFetcher {
|
|
|
773
788
|
);
|
|
774
789
|
return { kind: 'skipped', reason: 'no-engine-url' };
|
|
775
790
|
}
|
|
791
|
+
const entityDid = this.deps.entityDid;
|
|
792
|
+
if (!entityDid) {
|
|
793
|
+
if (!this.warnedNoEntity) {
|
|
794
|
+
this.warnedNoEntity = true;
|
|
795
|
+
logger.warn(
|
|
796
|
+
`[user-context] skipped: ORACLE_ENTITY_DID is not a domain entity DID (did:ixo:entity:<32 hex>); the memory engine keys every space by it, so no first-turn context is gathered`,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return { kind: 'skipped', reason: 'no-entity' };
|
|
800
|
+
}
|
|
776
801
|
if (!roomId) {
|
|
777
802
|
logger.warn(`[user-context] skipped: no oracle room for this turn`);
|
|
778
803
|
return { kind: 'skipped', reason: 'no-room' };
|
|
@@ -807,7 +832,7 @@ export class UserContextFetcher {
|
|
|
807
832
|
|
|
808
833
|
const entry: InFlightGather = {
|
|
809
834
|
startedAt: this.now(),
|
|
810
|
-
promise: this.gather(engineUrl, roomId).then(
|
|
835
|
+
promise: this.gather(engineUrl, entityDid, roomId).then(
|
|
811
836
|
(result) => this.settle(roomId, entry, result),
|
|
812
837
|
(err: unknown) =>
|
|
813
838
|
this.settle(roomId, entry, {
|
|
@@ -870,6 +895,7 @@ export class UserContextFetcher {
|
|
|
870
895
|
/** Mint the invocation, run the batch, map the slots. Never rejects. */
|
|
871
896
|
private async gather(
|
|
872
897
|
engineUrl: string,
|
|
898
|
+
entityDid: string,
|
|
873
899
|
roomId: string,
|
|
874
900
|
): Promise<GatherResult> {
|
|
875
901
|
const { logger, ucan } = this.deps;
|
|
@@ -905,14 +931,12 @@ export class UserContextFetcher {
|
|
|
905
931
|
headers: {
|
|
906
932
|
Authorization: `Bearer ${invocation}`,
|
|
907
933
|
'X-Auth-Type': 'ucan',
|
|
934
|
+
[MEMORY_ENTITY_HEADER]: entityDid,
|
|
908
935
|
'x-room-id': roomId,
|
|
909
936
|
'Content-Type': 'application/json',
|
|
910
937
|
},
|
|
911
938
|
body: JSON.stringify({
|
|
912
|
-
queries: buildUserContextQueries(
|
|
913
|
-
this.deps.oracleDid,
|
|
914
|
-
new Date(this.now()),
|
|
915
|
-
),
|
|
939
|
+
queries: buildUserContextQueries(entityDid, new Date(this.now())),
|
|
916
940
|
}),
|
|
917
941
|
signal: AbortSignal.timeout(this.requestTimeoutMs),
|
|
918
942
|
});
|
package/src/plugin-api/types.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TopicActionProposal,
|
|
3
|
+
TopicProposalSubject,
|
|
4
|
+
} from '@ixo/ixo-transaction/topic-proposals';
|
|
5
|
+
import type { TopicCandidate } from '../action-proposals/topic-prepare';
|
|
6
|
+
import type {
|
|
7
|
+
ActionProposal,
|
|
8
|
+
ActionProposalSubject,
|
|
9
|
+
} from '@ixo/ixo-transaction/action-proposals';
|
|
1
10
|
import type { TaskExecutionProfile } from '../core/execution-profile';
|
|
2
11
|
import type {
|
|
3
12
|
DecisionDefinition,
|
|
@@ -10,6 +19,12 @@ import type { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
|
|
10
19
|
import type { UserPreferences } from '../core/state';
|
|
11
20
|
import type { AttachmentViewSurface } from '../attachments/view';
|
|
12
21
|
import type { DeliveryConfig } from '../delivery/profile';
|
|
22
|
+
import type {
|
|
23
|
+
ActionOperationV1,
|
|
24
|
+
ActionReferenceV1,
|
|
25
|
+
ActionOutcomeV1,
|
|
26
|
+
ActionCapabilitiesV1,
|
|
27
|
+
} from '@ixo/action-protocol';
|
|
13
28
|
|
|
14
29
|
/**
|
|
15
30
|
* Route shape returned by `OraclePlugin.getAuthExcludedRoutes()`. Paths are
|
|
@@ -530,6 +545,13 @@ export interface RuntimeContext<TConfig = MergedConfig> {
|
|
|
530
545
|
client: 'portal' | 'matrix' | 'slack' | 'channel';
|
|
531
546
|
wsId?: string;
|
|
532
547
|
requestId: string;
|
|
548
|
+
runId?: string;
|
|
549
|
+
/** Authenticated channel identity captured by the runtime for this turn. */
|
|
550
|
+
channel?: {
|
|
551
|
+
provider: string;
|
|
552
|
+
bindingId: string;
|
|
553
|
+
bindingRevision: number;
|
|
554
|
+
};
|
|
533
555
|
roomId?: string;
|
|
534
556
|
/**
|
|
535
557
|
* Where the reply lands. `stream` is the Portal; `chat` is a chat app
|
|
@@ -723,6 +745,38 @@ export interface RuntimeContext<TConfig = MergedConfig> {
|
|
|
723
745
|
* Bounded semantic decisions. A decision only returns structured judgment;
|
|
724
746
|
* policy, authority checks, and side effects remain outside this primitive.
|
|
725
747
|
*/
|
|
748
|
+
actionProposals?: {
|
|
749
|
+
create(subject: ActionProposalSubject): Promise<ActionProposal>;
|
|
750
|
+
prepareTopic?(input: {
|
|
751
|
+
candidate: TopicCandidate;
|
|
752
|
+
origin: TopicProposalSubject['origin'];
|
|
753
|
+
evidenceRefs: string[];
|
|
754
|
+
}): Promise<TopicActionProposal>;
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
/** Host-owned action coordinator. It accepts only runtime-derived channel context. */
|
|
758
|
+
hubActions?: {
|
|
759
|
+
capabilities(): Promise<ActionCapabilitiesV1>;
|
|
760
|
+
options(input: {
|
|
761
|
+
ownerDid: string;
|
|
762
|
+
provider: string;
|
|
763
|
+
bindingId: string;
|
|
764
|
+
bindingRevision: number;
|
|
765
|
+
}): Promise<import('@ixo/action-protocol').HubActionOptionsV2>;
|
|
766
|
+
request(input: {
|
|
767
|
+
ownerDid: string;
|
|
768
|
+
sessionId: string;
|
|
769
|
+
runId: string;
|
|
770
|
+
requestId: string;
|
|
771
|
+
provider: string;
|
|
772
|
+
bindingId: string;
|
|
773
|
+
bindingRevision: number;
|
|
774
|
+
requestKey: string;
|
|
775
|
+
operation: ActionOperationV1;
|
|
776
|
+
}): Promise<ActionReferenceV1>;
|
|
777
|
+
status(ownerDid: string, actionId: string): Promise<ActionOutcomeV1 | null>;
|
|
778
|
+
};
|
|
779
|
+
|
|
726
780
|
decisions: {
|
|
727
781
|
evaluate<TSchema extends z.ZodType>(
|
|
728
782
|
definition: DecisionDefinition<TSchema>,
|