@ixo/oracle-runtime-workers 0.25.0 → 0.27.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 +3 -2
- 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/channels/auth.ts +2 -1
- package/src/channels/contract.ts +104 -0
- package/src/channels/replies.ts +58 -0
- package/src/channels/routing.ts +222 -0
- package/src/channels/turns.ts +1252 -16
- package/src/core/env.ts +17 -0
- package/src/do/contracts.ts +19 -0
- package/src/do/room-mirror.ts +8 -1
- package/src/do/run-coordinator.ts +27 -0
- package/src/do/transcript.ts +14 -11
- package/src/do/turn-messages.ts +54 -0
- package/src/do/user-oracle-do.ts +321 -135
- package/src/memory/session-history-indexer.ts +442 -103
- package/src/memory/user-context-fetcher.ts +13 -0
- package/src/memory/user-context-invalidation.ts +8 -2
- package/src/plugins/action-proposals/evidence.ts +7 -8
- 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/tasks/middleware.ts +4 -9
- 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 +70 -0
- package/src/sqlite/serialization.ts +20 -0
- package/src/sqlite/sessions-store.ts +43 -0
package/src/core/env.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
decisionProviderEnvShape,
|
|
4
4
|
} from '@ixo/common/ai/decisions';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
import { channelRoutingEnvShape } from '../channels/routing';
|
|
6
7
|
import type { OraclePlugin } from '../plugin-api/oracle-plugin';
|
|
7
8
|
import type { Logger } from '../plugin-api/types';
|
|
8
9
|
import { NOOP_LOGGER } from './utils';
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
USER_CONTEXT_BLOCKING_CAP_MS_DEFAULT,
|
|
17
18
|
USER_CONTEXT_CACHE_TTL_MS_DEFAULT,
|
|
18
19
|
} from '../memory/user-context-fetcher';
|
|
20
|
+
import { CHANNEL_HISTORY_INDEX_DEBOUNCE_MS_DEFAULT } from '../memory/session-history-indexer';
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Tier-0 (core) environment for the Workers runtime. Mirrors
|
|
@@ -303,6 +305,7 @@ export const baseEnvSchema = z.object({
|
|
|
303
305
|
// runtime consumer of Decisions. It predicts the on-demand plugin a message
|
|
304
306
|
// needs and preloads it for the turn; it needs a `DECISION_PROVIDER`.
|
|
305
307
|
...capabilityRouterEnvShape,
|
|
308
|
+
...channelRoutingEnvShape,
|
|
306
309
|
|
|
307
310
|
// --- durable runs (docs/plans/durable-runs.md) ---------------------------
|
|
308
311
|
// Parsed by `runDurabilityConfig` (src/do/run-store.ts) with defaults:
|
|
@@ -393,6 +396,20 @@ export const baseEnvSchema = z.object({
|
|
|
393
396
|
.min(1_000)
|
|
394
397
|
.max(3_600_000)
|
|
395
398
|
.default(USER_CONTEXT_CACHE_TTL_MS_DEFAULT),
|
|
399
|
+
/**
|
|
400
|
+
* How long a channel session must be quiet after its last finished run
|
|
401
|
+
* before its new history is indexed into the memory engine (ms): a burst
|
|
402
|
+
* of messages is one upload, and one prefetch-cache invalidation. `0`
|
|
403
|
+
* indexes after every finished run. At most 10 min: the wait is an
|
|
404
|
+
* in-memory timer that keeps the user object resident, and a Durable
|
|
405
|
+
* Object keeps pending work alive for 15 min at most.
|
|
406
|
+
*/
|
|
407
|
+
CHANNEL_HISTORY_INDEX_DEBOUNCE_MS: z.coerce
|
|
408
|
+
.number()
|
|
409
|
+
.int()
|
|
410
|
+
.min(0)
|
|
411
|
+
.max(600_000)
|
|
412
|
+
.default(CHANNEL_HISTORY_INDEX_DEBOUNCE_MS_DEFAULT),
|
|
396
413
|
|
|
397
414
|
// --- misc ---------------------------------------------------------------
|
|
398
415
|
LOG_LEVEL: z.string().default('info'),
|
package/src/do/contracts.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChannelControlInput,
|
|
3
|
+
ChannelControlOutcome,
|
|
4
|
+
ChannelReplyContext,
|
|
5
|
+
ChannelReplyReference,
|
|
6
|
+
} from '../channels/contract';
|
|
1
7
|
import type {
|
|
2
8
|
BeginTopicStep,
|
|
3
9
|
TopicReceiptReferences,
|
|
@@ -314,6 +320,10 @@ export interface OracleWorkerEnv {
|
|
|
314
320
|
* `on` preloads the predicted on-demand plugin's tools for the turn.
|
|
315
321
|
*/
|
|
316
322
|
CAPABILITY_ROUTER?: CapabilityRouterMode;
|
|
323
|
+
/** Applies exclusively to authenticated channel admission; Portal/Matrix turns never route. */
|
|
324
|
+
CHANNEL_SEMANTIC_ROUTING?: 'off' | 'shadow' | 'on';
|
|
325
|
+
CHANNEL_SEMANTIC_ROUTING_BINDINGS?: string;
|
|
326
|
+
CHANNEL_SEMANTIC_ROUTING_MIN_PROBABILITY?: string;
|
|
317
327
|
|
|
318
328
|
// --- langsmith tracing ---------------------------------------------------
|
|
319
329
|
/** `'true'` traces every turn (explicit tracer — Workers has no env auto-attach). */
|
|
@@ -386,6 +396,10 @@ export interface TurnRequest {
|
|
|
386
396
|
bindingId: string;
|
|
387
397
|
bindingRevision?: number;
|
|
388
398
|
remoteMessageRef: string;
|
|
399
|
+
replyContext?: ChannelReplyContext;
|
|
400
|
+
replyTo?: ChannelReplyReference;
|
|
401
|
+
/** Host-generated direct reply: no agent or tools run for an ambiguous destination. */
|
|
402
|
+
routingClarification?: string;
|
|
389
403
|
};
|
|
390
404
|
executionProfile?: TaskExecutionProfile;
|
|
391
405
|
identity: TurnIdentity;
|
|
@@ -741,6 +755,11 @@ export interface UserOracleObject extends Rpc.DurableObjectBranded {
|
|
|
741
755
|
operationId: string,
|
|
742
756
|
command: TopicDeliverableCommand,
|
|
743
757
|
): Promise<TopicDeliverableResult>;
|
|
758
|
+
channelControl(
|
|
759
|
+
identity: TurnIdentity,
|
|
760
|
+
input: ChannelControlInput,
|
|
761
|
+
requestHash: string,
|
|
762
|
+
): Promise<ChannelControlOutcome>;
|
|
744
763
|
channelTurn(
|
|
745
764
|
identity: TurnIdentity,
|
|
746
765
|
input: ChannelTurnInput,
|
package/src/do/room-mirror.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
channelOrigin,
|
|
3
|
+
type ChannelReplyReference,
|
|
4
|
+
} from '../channels/contract';
|
|
2
5
|
/**
|
|
3
6
|
* The room mirror of HTTP chats: every Portal turn is replayed into the
|
|
4
7
|
* user's oracle room as a thread under the session's marker event, the user
|
|
@@ -46,6 +49,7 @@ export interface MirrorSend {
|
|
|
46
49
|
provider: string;
|
|
47
50
|
bindingId: string;
|
|
48
51
|
remoteMessageRef: string;
|
|
52
|
+
replyTo?: ChannelReplyReference;
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -75,6 +79,9 @@ export function mirrorEventContent(send: MirrorSend) {
|
|
|
75
79
|
...(send.origin
|
|
76
80
|
? {
|
|
77
81
|
'org.ixo.qi.origin': channelOrigin(send.origin),
|
|
82
|
+
...(send.origin.replyTo
|
|
83
|
+
? { 'org.ixo.qi.reply': send.origin.replyTo }
|
|
84
|
+
: {}),
|
|
78
85
|
}
|
|
79
86
|
: {}),
|
|
80
87
|
};
|
|
@@ -453,6 +453,33 @@ export class RunCoordinator {
|
|
|
453
453
|
return stopped;
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
/** Stop exactly the run a channel control names; never a later turn. */
|
|
457
|
+
abortById(runId: string): Promise<void> {
|
|
458
|
+
return this.abortByIds([runId]);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Stop exactly the runs a channel control names, never a later turn.
|
|
463
|
+
* Every run is signalled before any is waited for, and the wait is one
|
|
464
|
+
* shared supersede grace: a control naming many runs must answer well
|
|
465
|
+
* inside the caller's timeout. A run may still be winding down when this
|
|
466
|
+
* resolves. Runs that are not live here (ended, never begun) are skipped.
|
|
467
|
+
*/
|
|
468
|
+
async abortByIds(runIds: readonly string[]): Promise<void> {
|
|
469
|
+
const runs = [...new Set(runIds)].flatMap((runId) => {
|
|
470
|
+
const run = this.live.get(runId);
|
|
471
|
+
return run ? [run] : [];
|
|
472
|
+
});
|
|
473
|
+
if (runs.length === 0) return;
|
|
474
|
+
// Queued runs first, so an attempt that ends below dequeues none of them.
|
|
475
|
+
for (const run of runs)
|
|
476
|
+
if (run.record.status === 'queued')
|
|
477
|
+
void this.cancelQueued(run, 'aborted');
|
|
478
|
+
for (const run of runs)
|
|
479
|
+
if (run.record.status !== 'queued') this.abortRun(run, 'aborted');
|
|
480
|
+
await withinGrace(this.graceMs, Promise.all(runs.map((run) => run.done)));
|
|
481
|
+
}
|
|
482
|
+
|
|
456
483
|
/**
|
|
457
484
|
* End every run of the session: the executing attempt is aborted, queued
|
|
458
485
|
* runs and runs waiting for a recovery attempt are closed, and messages
|
package/src/do/transcript.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { userContent } from '../channels/replies';
|
|
1
2
|
/**
|
|
2
3
|
* Transcript → client DTO. Port of `@ixo/common`'s
|
|
3
4
|
* `transformGraphStateMessageToListMessageResponse` so `GET /messages/:id`
|
|
@@ -13,11 +14,7 @@ import {
|
|
|
13
14
|
isAttachmentViewMessage,
|
|
14
15
|
} from '../attachments/retention';
|
|
15
16
|
import { SUMMARY_PREFIX } from '../core/middlewares/summarization';
|
|
16
|
-
import {
|
|
17
|
-
stripTurnTimeNote,
|
|
18
|
-
TURN_TIME_NOTE_KWARG,
|
|
19
|
-
} from '../core/turn-time-note';
|
|
20
|
-
import { ChannelOrigin } from '../channels/contract';
|
|
17
|
+
import { ChannelOrigin, ChannelReplyReference } from '../channels/contract';
|
|
21
18
|
import { CREATE_ARTIFACT_TOOL, createArtifactParts } from '../artifacts/tool';
|
|
22
19
|
import { planText } from '../delivery/schema';
|
|
23
20
|
import type {
|
|
@@ -59,7 +56,10 @@ export interface MessageDto {
|
|
|
59
56
|
createdAt?: string;
|
|
60
57
|
attachment?: AttachmentMeta;
|
|
61
58
|
attachments?: AttachmentMeta[];
|
|
62
|
-
metadata?: {
|
|
59
|
+
metadata?: {
|
|
60
|
+
'org.ixo.qi.origin': ChannelOrigin;
|
|
61
|
+
'org.ixo.qi.reply'?: ChannelReplyReference;
|
|
62
|
+
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export interface ListMessagesResponse {
|
|
@@ -131,10 +131,7 @@ export function contentToText(content: BaseMessage['content']): string {
|
|
|
131
131
|
*/
|
|
132
132
|
export function visibleContent(message: BaseMessage): BaseMessage['content'] {
|
|
133
133
|
if (message.type !== 'human') return message.content;
|
|
134
|
-
return
|
|
135
|
-
message.content,
|
|
136
|
-
message.additional_kwargs?.[TURN_TIME_NOTE_KWARG],
|
|
137
|
-
);
|
|
134
|
+
return userContent(message.content, message.additional_kwargs);
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
/** `visibleContent` as text (titles, mirrors, indexing). */
|
|
@@ -216,7 +213,13 @@ export async function transformTranscript(
|
|
|
216
213
|
if (attachments?.length) dto.attachments = attachments;
|
|
217
214
|
if (message.type === 'human') {
|
|
218
215
|
const origin = ChannelOrigin.safeParse(kw['org.ixo.qi.origin']);
|
|
219
|
-
if (origin.success)
|
|
216
|
+
if (origin.success) {
|
|
217
|
+
const reply = ChannelReplyReference.safeParse(kw['org.ixo.qi.reply']);
|
|
218
|
+
dto.metadata = {
|
|
219
|
+
'org.ixo.qi.origin': origin.data,
|
|
220
|
+
...(reply.success ? { 'org.ixo.qi.reply': reply.data } : {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
220
223
|
}
|
|
221
224
|
acc.push(dto);
|
|
222
225
|
for (const call of dto.toolCalls ?? [])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordering of the messages a turn feeds the graph: hidden attachment context
|
|
3
|
+
* and the person's message.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AIMessage,
|
|
8
|
+
type BaseMessage,
|
|
9
|
+
type HumanMessage,
|
|
10
|
+
} from '@langchain/core/messages';
|
|
11
|
+
|
|
12
|
+
import type { ExtractedAttachment } from '../attachments';
|
|
13
|
+
|
|
14
|
+
export interface TurnMessageParts {
|
|
15
|
+
human: HumanMessage;
|
|
16
|
+
/** Extraction-lane text: follows the HumanMessage (as on Node). */
|
|
17
|
+
extracted: readonly ExtractedAttachment[];
|
|
18
|
+
/**
|
|
19
|
+
* One line per natively-sent file naming its reference. These go BEFORE the
|
|
20
|
+
* HumanMessage so the turn never ends on an assistant message (providers
|
|
21
|
+
* reject or mis-handle an assistant-last conversation).
|
|
22
|
+
*/
|
|
23
|
+
nativeNotes: readonly ExtractedAttachment[];
|
|
24
|
+
matrixRoom: boolean;
|
|
25
|
+
now?: () => Date;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hiddenNote(
|
|
29
|
+
item: ExtractedAttachment,
|
|
30
|
+
matrixRoom: boolean,
|
|
31
|
+
now: () => Date,
|
|
32
|
+
): AIMessage {
|
|
33
|
+
return new AIMessage({
|
|
34
|
+
content: item.text,
|
|
35
|
+
additional_kwargs: {
|
|
36
|
+
msgFromMatrixRoom: matrixRoom,
|
|
37
|
+
timestamp: now().toISOString(),
|
|
38
|
+
attachment: item.meta,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Native notes, then the HumanMessage, then the extraction-lane text. The
|
|
45
|
+
* hidden messages carry an `attachment` kwarg, which the transcript hides.
|
|
46
|
+
*/
|
|
47
|
+
export function assembleTurnMessages(parts: TurnMessageParts): BaseMessage[] {
|
|
48
|
+
const now = parts.now ?? (() => new Date());
|
|
49
|
+
return [
|
|
50
|
+
...parts.nativeNotes.map((n) => hiddenNote(n, parts.matrixRoom, now)),
|
|
51
|
+
parts.human,
|
|
52
|
+
...parts.extracted.map((n) => hiddenNote(n, parts.matrixRoom, now)),
|
|
53
|
+
];
|
|
54
|
+
}
|