@tangle-network/agent-app 0.60.160 → 0.60.161
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/dist/assistant/index.js +1 -1
- package/dist/chat-routes/index.js +5 -1
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/turn-routes.d.ts +2 -0
- package/dist/{chunk-N5M7YT6P.js → chunk-PYPWEXTD.js} +11 -2
- package/dist/{chunk-N5M7YT6P.js.map → chunk-PYPWEXTD.js.map} +1 -1
- package/dist/{chunk-T4JJXUJI.js → chunk-UMWASXC2.js} +3 -7
- package/dist/{chunk-T4JJXUJI.js.map → chunk-UMWASXC2.js.map} +1 -1
- package/dist/operator/chat-adapter.d.ts +34 -0
- package/dist/operator/index.js +48 -11
- package/dist/operator/index.js.map +1 -1
- package/dist/public-consultation/index.js +1 -1
- package/dist/web-react/agent-setup-block.d.ts +3 -1
- package/dist/web-react/index.js +1 -1
- package/package.json +1 -1
|
@@ -26,7 +26,38 @@ export interface ChatOperatorWorkspace {
|
|
|
26
26
|
role?: OperatorRole;
|
|
27
27
|
createdAt?: Date | string | number | null;
|
|
28
28
|
}
|
|
29
|
+
/** A durable execution claim, separate from the chat transcript that owns results. */
|
|
30
|
+
export interface ChatOperatorAdmission {
|
|
31
|
+
threadId: string;
|
|
32
|
+
/** Only an explicit refusal before a usable stream opened is recorded here. */
|
|
33
|
+
refusal?: {
|
|
34
|
+
code?: string;
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Product-owned atomic storage for duplicate suppression across processes.
|
|
39
|
+
* A claim with no transcript stays unknown after a crash; it never licenses a
|
|
40
|
+
* second execution. This is not a completion ledger or an automatic retry queue.
|
|
41
|
+
*/
|
|
42
|
+
export interface ChatOperatorAdmissionStore<Identity> {
|
|
43
|
+
get(ctx: OperatorContext<Identity>, workspaceId: string, turnId: string): Promise<ChatOperatorAdmission | null>;
|
|
44
|
+
claim(ctx: OperatorContext<Identity>, input: {
|
|
45
|
+
workspaceId: string;
|
|
46
|
+
threadId: string;
|
|
47
|
+
turnId: string;
|
|
48
|
+
}): Promise<boolean>;
|
|
49
|
+
recordRefusal(ctx: OperatorContext<Identity>, input: {
|
|
50
|
+
workspaceId: string;
|
|
51
|
+
threadId: string;
|
|
52
|
+
turnId: string;
|
|
53
|
+
}, refusal: {
|
|
54
|
+
code?: string;
|
|
55
|
+
message: string;
|
|
56
|
+
}): Promise<void>;
|
|
57
|
+
}
|
|
29
58
|
export interface ChatOperatorAdapterOptions<Identity> {
|
|
59
|
+
/** Optional durable claims; without one, transcript retry detection is not an atomic admission lock. */
|
|
60
|
+
admissions?: ChatOperatorAdmissionStore<Identity>;
|
|
30
61
|
/** The app's public origin, the base of every browser link. */
|
|
31
62
|
origin: string;
|
|
32
63
|
/** Browser path of a conversation; defaults to `/app/<workspace>/chat/<thread>`. */
|
|
@@ -75,6 +106,9 @@ export interface ChatOperatorAdapterOptions<Identity> {
|
|
|
75
106
|
content: string;
|
|
76
107
|
model?: string;
|
|
77
108
|
}): Promise<Response>;
|
|
109
|
+
/** Optional authoritative status of this user-turn anchor. When supplied,
|
|
110
|
+
* missing/running status without a live lease never promotes a partial reply to success. */
|
|
111
|
+
turnStatus?(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string, user: ChatOperatorMessage): Promise<'running' | 'complete' | 'error' | null>;
|
|
78
112
|
/** Turn ids still running on a thread, or null when the app cannot tell. */
|
|
79
113
|
runningTurns?(ctx: OperatorContext<Identity>, workspaceId: string, threadId: string): Promise<string[] | null>;
|
|
80
114
|
/** Decisions open in the workspace, or on one conversation when `threadId` is set. */
|
package/dist/operator/index.js
CHANGED
|
@@ -606,6 +606,8 @@ function createChatOperatorAdapter(options) {
|
|
|
606
606
|
const assistant = [...window].reverse().find((message) => message.role === "assistant") ?? null;
|
|
607
607
|
const latest = nextUser < 0;
|
|
608
608
|
const running = latest && options.runningTurns ? await options.runningTurns(ctx, workspaceId, threadId) : [];
|
|
609
|
+
const status = options.turnStatus ? await options.turnStatus(ctx, workspaceId, threadId, messages[userIndex]) : void 0;
|
|
610
|
+
const terminal = status === void 0 || status === "complete" || status === "error";
|
|
609
611
|
const turn = {
|
|
610
612
|
workspaceId,
|
|
611
613
|
threadId,
|
|
@@ -625,8 +627,8 @@ function createChatOperatorAdapter(options) {
|
|
|
625
627
|
const model = assistant.servedModel ?? assistant.model;
|
|
626
628
|
if (model) turn.model = model;
|
|
627
629
|
if (typeof assistant.costUsd === "number") turn.costUsd = assistant.costUsd;
|
|
628
|
-
if (state !== "working") {
|
|
629
|
-
const failure2 = (options.failureOf ?? defaultFailure)(assistant);
|
|
630
|
+
if (state !== "working" && terminal) {
|
|
631
|
+
const failure2 = (options.failureOf ?? defaultFailure)(assistant) ?? (status === "error" ? { code: "turn.error", message: "The turn failed; retained text may be partial" } : null);
|
|
630
632
|
if (failure2) {
|
|
631
633
|
state = "failed";
|
|
632
634
|
turn.failure = failure2;
|
|
@@ -637,9 +639,9 @@ function createChatOperatorAdapter(options) {
|
|
|
637
639
|
const completedAt = iso(assistant.createdAt);
|
|
638
640
|
if (completedAt && (state === "succeeded" || state === "failed")) turn.completedAt = completedAt;
|
|
639
641
|
}
|
|
640
|
-
} else if (state !== "working" && !latest) {
|
|
642
|
+
} else if (state !== "working" && terminal && (!latest || status === "error" || status === "complete")) {
|
|
641
643
|
state = "failed";
|
|
642
|
-
turn.failure = { message: "The turn ended without a reply" };
|
|
644
|
+
turn.failure = { code: status === "error" ? "turn.error" : "turn.no_reply", message: "The turn ended without a reply" };
|
|
643
645
|
}
|
|
644
646
|
turn.state = state;
|
|
645
647
|
if (state !== "succeeded" && state !== "failed" && options.approvals) {
|
|
@@ -647,6 +649,23 @@ function createChatOperatorAdapter(options) {
|
|
|
647
649
|
}
|
|
648
650
|
return turn;
|
|
649
651
|
}
|
|
652
|
+
async function admittedTurn(ctx, workspaceId, turnId, admission) {
|
|
653
|
+
if (!await threadIn(ctx, workspaceId, admission.threadId)) throw new OperatorError("operator.turn_identity_conflict", 409);
|
|
654
|
+
const persisted = await turnOf(ctx, workspaceId, admission.threadId, turnId);
|
|
655
|
+
if (persisted) return persisted;
|
|
656
|
+
return {
|
|
657
|
+
workspaceId,
|
|
658
|
+
threadId: admission.threadId,
|
|
659
|
+
turnId,
|
|
660
|
+
state: admission.refusal ? "failed" : "unknown",
|
|
661
|
+
url: threadUrl(workspaceId, admission.threadId),
|
|
662
|
+
reply: null,
|
|
663
|
+
failure: admission.refusal ?? null,
|
|
664
|
+
assets: [],
|
|
665
|
+
files: [],
|
|
666
|
+
approvals: []
|
|
667
|
+
};
|
|
668
|
+
}
|
|
650
669
|
async function ensureThread(ctx, workspaceId, input) {
|
|
651
670
|
const id = (await sha256Hex(`operator-thread\0${ctx.key.keyId}\0${workspaceId}\0${input.turnId}`)).slice(0, 32);
|
|
652
671
|
const existing = await options.threads.get(id, ctx);
|
|
@@ -671,6 +690,11 @@ function createChatOperatorAdapter(options) {
|
|
|
671
690
|
return (await options.workspaces.list(ctx)).map(workspaceView);
|
|
672
691
|
},
|
|
673
692
|
async startTurn(ctx, workspace, input) {
|
|
693
|
+
const priorAdmission = await options.admissions?.get(ctx, workspace.id, input.turnId);
|
|
694
|
+
if (priorAdmission) {
|
|
695
|
+
if (input.threadId && input.threadId !== priorAdmission.threadId) throw new OperatorError("operator.turn_identity_conflict", 409);
|
|
696
|
+
return admittedTurn(ctx, workspace.id, input.turnId, priorAdmission);
|
|
697
|
+
}
|
|
674
698
|
let threadId = input.threadId;
|
|
675
699
|
if (threadId === void 0) {
|
|
676
700
|
threadId = await ensureThread(ctx, workspace.id, input);
|
|
@@ -679,6 +703,13 @@ function createChatOperatorAdapter(options) {
|
|
|
679
703
|
}
|
|
680
704
|
const existing = await turnOf(ctx, workspace.id, threadId, input.turnId);
|
|
681
705
|
if (existing) return existing;
|
|
706
|
+
const target = { workspaceId: workspace.id, threadId, turnId: input.turnId };
|
|
707
|
+
if (options.admissions && !await options.admissions.claim(ctx, target)) {
|
|
708
|
+
const raced = await options.admissions.get(ctx, workspace.id, input.turnId);
|
|
709
|
+
if (!raced) throw new OperatorError("operator.admission_unavailable", 503, "Admission state is unavailable", true);
|
|
710
|
+
if (raced.threadId !== threadId) throw new OperatorError("operator.turn_identity_conflict", 409);
|
|
711
|
+
return admittedTurn(ctx, workspace.id, input.turnId, raced);
|
|
712
|
+
}
|
|
682
713
|
const response = await options.runTurn(ctx, {
|
|
683
714
|
workspaceId: workspace.id,
|
|
684
715
|
threadId,
|
|
@@ -689,12 +720,15 @@ function createChatOperatorAdapter(options) {
|
|
|
689
720
|
if (!response.ok) {
|
|
690
721
|
const body = await response.json().catch(() => ({}));
|
|
691
722
|
const nested = body.error && typeof body.error === "object" ? body.error : {};
|
|
692
|
-
|
|
693
|
-
typeof body.code === "string" ? body.code : typeof nested.code === "string" ? nested.code : `operator.chat_${response.status}`,
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
723
|
+
const failure2 = {
|
|
724
|
+
code: typeof body.code === "string" ? body.code : typeof nested.code === "string" ? nested.code : `operator.chat_${response.status}`,
|
|
725
|
+
message: typeof body.error === "string" ? body.error : typeof nested.message === "string" ? nested.message : "The app refused the turn"
|
|
726
|
+
};
|
|
727
|
+
if (options.admissions) {
|
|
728
|
+
await options.admissions.recordRefusal(ctx, target, failure2);
|
|
729
|
+
return admittedTurn(ctx, workspace.id, input.turnId, { threadId, refusal: failure2 });
|
|
730
|
+
}
|
|
731
|
+
throw new OperatorError(failure2.code, response.status, failure2.message, response.status === 429 || response.status >= 500);
|
|
698
732
|
}
|
|
699
733
|
await drain(response, ctx.request.signal);
|
|
700
734
|
return await turnOf(ctx, workspace.id, threadId, input.turnId) ?? {
|
|
@@ -712,7 +746,10 @@ function createChatOperatorAdapter(options) {
|
|
|
712
746
|
},
|
|
713
747
|
async getTurn(ctx, workspace, target) {
|
|
714
748
|
if (!await threadIn(ctx, workspace.id, target.threadId)) return null;
|
|
715
|
-
|
|
749
|
+
const persisted = await turnOf(ctx, workspace.id, target.threadId, target.turnId);
|
|
750
|
+
if (persisted) return persisted;
|
|
751
|
+
const admission = await options.admissions?.get(ctx, workspace.id, target.turnId);
|
|
752
|
+
return admission?.threadId === target.threadId ? admittedTurn(ctx, workspace.id, target.turnId, admission) : null;
|
|
716
753
|
},
|
|
717
754
|
async getThread(ctx, workspace, threadId) {
|
|
718
755
|
const thread = await threadIn(ctx, workspace.id, threadId);
|