@superxd-studio/adptr 0.5.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/GENERATIVE.md +101 -0
- package/INTERACTIONS.md +51 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/REUSE.md +170 -0
- package/REVIEW.md +68 -0
- package/THIRD-PARTY-NOTICES.md +28 -0
- package/ai-sdk/index.js +96 -0
- package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
- package/assistant-ui/index.js +139 -0
- package/chunks/callout-DKMHSnjc.js +342 -0
- package/chunks/canvas-BJ15-XxE.js +598 -0
- package/chunks/controller-Cfpg0C7v.js +180 -0
- package/chunks/core-BCqVRLUy.js +312 -0
- package/chunks/text-field-BLtoaZFn.js +41 -0
- package/chunks/types-BGF7r1my.js +115 -0
- package/chunks/ui-9DyNsYXY.js +967 -0
- package/core/index.js +4 -0
- package/custom/index.js +64 -0
- package/examples/ai-sdk-backend/package.json +11 -0
- package/examples/ai-sdk-backend/server.mjs +316 -0
- package/examples/custom-backend/copy.mjs +7 -0
- package/examples/custom-backend/server.mjs +118 -0
- package/examples/generative/specs.json +2094 -0
- package/examples/review-backend/copy.mjs +27 -0
- package/examples/review-backend/package.json +11 -0
- package/examples/review-backend/server.mjs +276 -0
- package/examples/web-ai-sdk/index.html +12 -0
- package/examples/web-ai-sdk/package.json +21 -0
- package/examples/web-ai-sdk/src/copy.ts +1 -0
- package/examples/web-ai-sdk/src/main.tsx +64 -0
- package/examples/web-ai-sdk/tsconfig.json +13 -0
- package/examples/web-generative/index.html +12 -0
- package/examples/web-generative/package.json +23 -0
- package/examples/web-generative/src/copy.ts +42 -0
- package/examples/web-generative/src/main.tsx +74 -0
- package/examples/web-generative/tsconfig.json +14 -0
- package/examples/web-independent/index.html +12 -0
- package/examples/web-independent/package.json +20 -0
- package/examples/web-independent/src/main.tsx +32 -0
- package/examples/web-independent/tsconfig.json +14 -0
- package/examples/web-interactions/index.html +12 -0
- package/examples/web-interactions/package.json +21 -0
- package/examples/web-interactions/src/copy.ts +215 -0
- package/examples/web-interactions/src/examples.tsx +628 -0
- package/examples/web-interactions/src/main.tsx +116 -0
- package/examples/web-interactions/tsconfig.json +14 -0
- package/examples/web-review/index.html +12 -0
- package/examples/web-review/package.json +20 -0
- package/examples/web-review/src/main.tsx +59 -0
- package/examples/web-review/tsconfig.json +14 -0
- package/fonts.css +117 -0
- package/generative/index.js +1560 -0
- package/interactions/index.js +1591 -0
- package/langgraph-review/index.js +36 -0
- package/licenses/ai-sdk.txt +13 -0
- package/licenses/assistant-ui-generative.txt +21 -0
- package/licenses/assistant-ui.txt +23 -0
- package/licenses/ibm-plex-mono.txt +93 -0
- package/licenses/instrument-sans.txt +93 -0
- package/licenses/internationalized-date.txt +201 -0
- package/licenses/langgraph.txt +21 -0
- package/licenses/lucide.txt +43 -0
- package/licenses/react-markdown.txt +21 -0
- package/licenses/remark-gfm.txt +22 -0
- package/licenses/zod.txt +21 -0
- package/package.json +138 -0
- package/review/index.js +31 -0
- package/source/components/avatar.tsx +50 -0
- package/source/components/button.tsx +155 -0
- package/source/components/callout.tsx +65 -0
- package/source/components/canvas/connector.tsx +14 -0
- package/source/components/canvas/copy.ts +34 -0
- package/source/components/canvas/index.tsx +257 -0
- package/source/components/canvas/recorder.ts +36 -0
- package/source/components/date-time-field.tsx +77 -0
- package/source/components/flow-motion.tsx +109 -0
- package/source/components/icons.tsx +275 -0
- package/source/components/responsive-overlay.tsx +91 -0
- package/source/components/scenario-picker/copy.ts +5 -0
- package/source/components/scenario-picker/index.tsx +142 -0
- package/source/components/scroll-area.tsx +59 -0
- package/source/components/search-picker.tsx +127 -0
- package/source/components/segmented-control.tsx +123 -0
- package/source/components/skeleton.tsx +11 -0
- package/source/components/spinner.tsx +19 -0
- package/source/components/superxd-arrow.tsx +22 -0
- package/source/components/switch.tsx +45 -0
- package/source/components/text-field.tsx +81 -0
- package/source/components/thinking.tsx +16 -0
- package/source/components/ui/copy.ts +12 -0
- package/source/components/ui/display.tsx +276 -0
- package/source/components/ui/navigation.tsx +346 -0
- package/source/components/ui/portal-theme.tsx +28 -0
- package/source/components/ui/selection.tsx +336 -0
- package/source/lib/cn.ts +23 -0
- package/source/reusable/ai-sdk/index.ts +114 -0
- package/source/reusable/assistant-ui/index.tsx +266 -0
- package/source/reusable/core/attachments.ts +163 -0
- package/source/reusable/core/index.ts +417 -0
- package/source/reusable/custom/index.ts +90 -0
- package/source/reusable/generative/actions.ts +188 -0
- package/source/reusable/generative/copy.ts +47 -0
- package/source/reusable/generative/data.tsx +126 -0
- package/source/reusable/generative/index.tsx +517 -0
- package/source/reusable/generative/schema.ts +327 -0
- package/source/reusable/generative/timeline.tsx +214 -0
- package/source/reusable/generative/vocabulary.tsx +847 -0
- package/source/reusable/interactions/agent.ts +259 -0
- package/source/reusable/interactions/artifact.ts +236 -0
- package/source/reusable/interactions/context.tsx +38 -0
- package/source/reusable/interactions/copy.ts +117 -0
- package/source/reusable/interactions/feedback.ts +73 -0
- package/source/reusable/interactions/index.ts +11 -0
- package/source/reusable/interactions/presentation.tsx +483 -0
- package/source/reusable/interactions/store.ts +74 -0
- package/source/reusable/interactions/threads.ts +126 -0
- package/source/reusable/interactions/web.tsx +574 -0
- package/source/reusable/langgraph-review/index.ts +73 -0
- package/source/reusable/review/controller.ts +263 -0
- package/source/reusable/review/index.ts +47 -0
- package/source/reusable/review/types.ts +189 -0
- package/source/reusable/ui/index.ts +25 -0
- package/source/reusable/uploads/index.ts +48 -0
- package/source/reusable/web/attachments/copy.ts +23 -0
- package/source/reusable/web/attachments/index.tsx +233 -0
- package/source/reusable/web/copy.ts +22 -0
- package/source/reusable/web/index.tsx +470 -0
- package/source/reusable/web/message/copy.ts +26 -0
- package/source/reusable/web/message/index.tsx +388 -0
- package/source/reusable/web/message/streaming.ts +64 -0
- package/source/reusable/web/message/styles.css +21 -0
- package/source/reusable/web/review/copy.ts +78 -0
- package/source/reusable/web/review/flow.ts +24 -0
- package/source/reusable/web/review/index.tsx +506 -0
- package/source/reusable/web/styles.ts +2 -0
- package/source/styles/library.css +612 -0
- package/source/styles/theme.css +228 -0
- package/styles.css +3 -0
- package/styles.d.ts +1 -0
- package/types/components/avatar.d.ts +14 -0
- package/types/components/button.d.ts +36 -0
- package/types/components/callout.d.ts +19 -0
- package/types/components/canvas/connector.d.ts +2 -0
- package/types/components/canvas/copy.d.ts +34 -0
- package/types/components/canvas/index.d.ts +33 -0
- package/types/components/canvas/recorder.d.ts +10 -0
- package/types/components/date-time-field.d.ts +11 -0
- package/types/components/flow-motion.d.ts +38 -0
- package/types/components/icons.d.ts +138 -0
- package/types/components/responsive-overlay.d.ts +15 -0
- package/types/components/scenario-picker/copy.d.ts +5 -0
- package/types/components/scenario-picker/index.d.ts +14 -0
- package/types/components/scroll-area.d.ts +14 -0
- package/types/components/search-picker.d.ts +27 -0
- package/types/components/segmented-control.d.ts +23 -0
- package/types/components/skeleton.d.ts +4 -0
- package/types/components/spinner.d.ts +4 -0
- package/types/components/superxd-arrow.d.ts +5 -0
- package/types/components/switch.d.ts +8 -0
- package/types/components/text-field.d.ts +19 -0
- package/types/components/thinking.d.ts +8 -0
- package/types/components/ui/copy.d.ts +12 -0
- package/types/components/ui/display.d.ts +50 -0
- package/types/components/ui/navigation.d.ts +63 -0
- package/types/components/ui/portal-theme.d.ts +8 -0
- package/types/components/ui/selection.d.ts +37 -0
- package/types/lib/cn.d.ts +3 -0
- package/types/reusable/ai-sdk/index.d.ts +15 -0
- package/types/reusable/assistant-ui/index.d.ts +12 -0
- package/types/reusable/core/attachments.d.ts +54 -0
- package/types/reusable/core/index.d.ts +107 -0
- package/types/reusable/custom/index.d.ts +14 -0
- package/types/reusable/generative/actions.d.ts +45 -0
- package/types/reusable/generative/copy.d.ts +48 -0
- package/types/reusable/generative/data.d.ts +4 -0
- package/types/reusable/generative/index.d.ts +30 -0
- package/types/reusable/generative/schema.d.ts +303 -0
- package/types/reusable/generative/timeline.d.ts +21 -0
- package/types/reusable/generative/vocabulary.d.ts +34 -0
- package/types/reusable/interactions/agent.d.ts +98 -0
- package/types/reusable/interactions/artifact.d.ts +118 -0
- package/types/reusable/interactions/context.d.ts +130 -0
- package/types/reusable/interactions/copy.d.ts +115 -0
- package/types/reusable/interactions/feedback.d.ts +34 -0
- package/types/reusable/interactions/index.d.ts +10 -0
- package/types/reusable/interactions/presentation.d.ts +97 -0
- package/types/reusable/interactions/store.d.ts +29 -0
- package/types/reusable/interactions/threads.d.ts +38 -0
- package/types/reusable/interactions/web.d.ts +22 -0
- package/types/reusable/langgraph-review/index.d.ts +5 -0
- package/types/reusable/review/controller.d.ts +52 -0
- package/types/reusable/review/index.d.ts +12 -0
- package/types/reusable/review/types.d.ts +61 -0
- package/types/reusable/ui/index.d.ts +19 -0
- package/types/reusable/uploads/index.d.ts +11 -0
- package/types/reusable/web/attachments/copy.d.ts +25 -0
- package/types/reusable/web/attachments/index.d.ts +24 -0
- package/types/reusable/web/copy.d.ts +24 -0
- package/types/reusable/web/index.d.ts +41 -0
- package/types/reusable/web/message/copy.d.ts +27 -0
- package/types/reusable/web/message/index.d.ts +56 -0
- package/types/reusable/web/message/streaming.d.ts +27 -0
- package/types/reusable/web/review/copy.d.ts +72 -0
- package/types/reusable/web/review/flow.d.ts +3 -0
- package/types/reusable/web/review/index.d.ts +38 -0
- package/types/reusable/web/styles.d.ts +2 -0
- package/ui/index.js +4 -0
- package/uploads/index.js +34 -0
- package/web/index.js +931 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
InteractionStore,
|
|
4
|
+
identity,
|
|
5
|
+
shortText,
|
|
6
|
+
bodyText,
|
|
7
|
+
jsonSchema,
|
|
8
|
+
newIdentity,
|
|
9
|
+
type JsonValue,
|
|
10
|
+
} from "./store.js";
|
|
11
|
+
export const agentStepSchema = z.strictObject({
|
|
12
|
+
id: identity,
|
|
13
|
+
title: shortText,
|
|
14
|
+
tool: shortText,
|
|
15
|
+
input: jsonSchema,
|
|
16
|
+
scope: shortText,
|
|
17
|
+
});
|
|
18
|
+
export type AgentStep = z.infer<typeof agentStepSchema>;
|
|
19
|
+
export interface AgentRequest {
|
|
20
|
+
requestId: string;
|
|
21
|
+
step: AgentStep;
|
|
22
|
+
approvedUntil: number;
|
|
23
|
+
}
|
|
24
|
+
const resultSchema = z.discriminatedUnion("status", [
|
|
25
|
+
z.strictObject({ requestId: identity, status: z.literal("completed"), output: bodyText }),
|
|
26
|
+
z.strictObject({ requestId: identity, status: z.literal("rejected"), reason: shortText }),
|
|
27
|
+
z.strictObject({ requestId: identity, status: z.literal("unknown") }),
|
|
28
|
+
]);
|
|
29
|
+
export type AgentResult = z.infer<typeof resultSchema>;
|
|
30
|
+
export type AgentReceipt = AgentResult | { requestId: string; status: "pending" | "not-found" };
|
|
31
|
+
const receiptSchema = z.union([
|
|
32
|
+
resultSchema,
|
|
33
|
+
z.strictObject({ requestId: identity, status: z.enum(["pending", "not-found"]) }),
|
|
34
|
+
]);
|
|
35
|
+
export interface AgentAdapter {
|
|
36
|
+
/** Authorise on the server and deduplicate by requestId. Approval is only for this frozen step. */
|
|
37
|
+
execute(
|
|
38
|
+
request: AgentRequest,
|
|
39
|
+
signal: AbortSignal,
|
|
40
|
+
progress: (message: string) => void,
|
|
41
|
+
): Promise<AgentResult>;
|
|
42
|
+
/** Look up the same receipt. not-found must mean definitely not accepted, not eventually invisible. */
|
|
43
|
+
lookup(requestId: string, signal: AbortSignal): Promise<AgentReceipt>;
|
|
44
|
+
}
|
|
45
|
+
export interface AgentSnapshot {
|
|
46
|
+
steps: readonly AgentStep[];
|
|
47
|
+
completed: readonly { stepId: string; requestId: string; output: string }[];
|
|
48
|
+
phase:
|
|
49
|
+
| "ready"
|
|
50
|
+
| "approval"
|
|
51
|
+
| "running"
|
|
52
|
+
| "checking"
|
|
53
|
+
| "failed"
|
|
54
|
+
| "uncertain"
|
|
55
|
+
| "denied"
|
|
56
|
+
| "expired"
|
|
57
|
+
| "complete";
|
|
58
|
+
request?: AgentRequest;
|
|
59
|
+
progress?: string;
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
type AgentEvent =
|
|
63
|
+
| { type: "propose"; request: AgentRequest }
|
|
64
|
+
| { type: "approve" }
|
|
65
|
+
| { type: "check" }
|
|
66
|
+
| { type: "progress"; message: string }
|
|
67
|
+
| { type: "settle"; result: AgentResult }
|
|
68
|
+
| { type: "uncertain" }
|
|
69
|
+
| { type: "deny" }
|
|
70
|
+
| { type: "expire" }
|
|
71
|
+
| { type: "revise"; steps: readonly AgentStep[] };
|
|
72
|
+
export function agentReducer(state: AgentSnapshot, event: AgentEvent): AgentSnapshot {
|
|
73
|
+
switch (event.type) {
|
|
74
|
+
case "propose":
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
phase: "approval",
|
|
78
|
+
request: event.request,
|
|
79
|
+
progress: undefined,
|
|
80
|
+
reason: undefined,
|
|
81
|
+
};
|
|
82
|
+
case "approve":
|
|
83
|
+
return { ...state, phase: "running", progress: undefined };
|
|
84
|
+
case "check":
|
|
85
|
+
return { ...state, phase: "checking" };
|
|
86
|
+
case "progress":
|
|
87
|
+
return { ...state, progress: event.message };
|
|
88
|
+
case "uncertain":
|
|
89
|
+
return { ...state, phase: "uncertain", progress: undefined };
|
|
90
|
+
case "deny":
|
|
91
|
+
return { ...state, phase: "denied", progress: undefined };
|
|
92
|
+
case "expire":
|
|
93
|
+
return { ...state, phase: "expired" };
|
|
94
|
+
case "revise":
|
|
95
|
+
return { ...state, steps: event.steps };
|
|
96
|
+
case "settle": {
|
|
97
|
+
if (event.result.status === "unknown")
|
|
98
|
+
return { ...state, phase: "uncertain", progress: undefined };
|
|
99
|
+
if (event.result.status === "rejected")
|
|
100
|
+
return { ...state, phase: "failed", reason: event.result.reason, progress: undefined };
|
|
101
|
+
if (!state.request) return state;
|
|
102
|
+
const completed = [
|
|
103
|
+
...state.completed,
|
|
104
|
+
{
|
|
105
|
+
stepId: state.request.step.id,
|
|
106
|
+
requestId: state.request.requestId,
|
|
107
|
+
output: event.result.output,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
return {
|
|
111
|
+
...state,
|
|
112
|
+
completed,
|
|
113
|
+
phase: completed.length === state.steps.length ? "complete" : "ready",
|
|
114
|
+
request: undefined,
|
|
115
|
+
progress: undefined,
|
|
116
|
+
reason: undefined,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Supervises a finite plan. It never schedules work or invents a tool implementation. */
|
|
122
|
+
export class AgentController extends InteractionStore<AgentSnapshot> {
|
|
123
|
+
private expiry?: ReturnType<typeof setTimeout>;
|
|
124
|
+
private ttl: number;
|
|
125
|
+
constructor(
|
|
126
|
+
private adapter: AgentAdapter,
|
|
127
|
+
steps: readonly AgentStep[],
|
|
128
|
+
private options: { id?: () => string; now?: () => number; approvalMs?: number } = {},
|
|
129
|
+
) {
|
|
130
|
+
const parsed = z.array(agentStepSchema).min(1).max(50).parse(steps);
|
|
131
|
+
if (new Set(parsed.map((s) => s.id)).size !== parsed.length)
|
|
132
|
+
throw new Error("Duplicate plan step");
|
|
133
|
+
super({ steps: parsed, completed: [], phase: "ready" });
|
|
134
|
+
this.ttl = z
|
|
135
|
+
.number()
|
|
136
|
+
.finite()
|
|
137
|
+
.min(1000)
|
|
138
|
+
.max(300_000)
|
|
139
|
+
.parse(options.approvalMs ?? 60_000);
|
|
140
|
+
}
|
|
141
|
+
private now() {
|
|
142
|
+
return (this.options.now ?? Date.now)();
|
|
143
|
+
}
|
|
144
|
+
private send(event: AgentEvent) {
|
|
145
|
+
this.publish(agentReducer(this.state, event));
|
|
146
|
+
}
|
|
147
|
+
private propose(request: AgentRequest) {
|
|
148
|
+
clearTimeout(this.expiry);
|
|
149
|
+
this.send({ type: "propose", request });
|
|
150
|
+
this.expiry = setTimeout(
|
|
151
|
+
() => {
|
|
152
|
+
if (this.state.phase === "approval" && this.state.request?.requestId === request.requestId)
|
|
153
|
+
this.send({ type: "expire" });
|
|
154
|
+
},
|
|
155
|
+
Math.max(0, request.approvedUntil - this.now()),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
revise(steps: readonly AgentStep[]) {
|
|
159
|
+
if (this.disposed || this.state.phase !== "ready" || this.state.completed.length) return false;
|
|
160
|
+
const parsed = z.array(agentStepSchema).min(1).max(50).safeParse(steps);
|
|
161
|
+
if (!parsed.success || new Set(parsed.data.map((s) => s.id)).size !== parsed.data.length)
|
|
162
|
+
return false;
|
|
163
|
+
this.send({ type: "revise", steps: parsed.data });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
next() {
|
|
167
|
+
if (this.disposed || this.state.phase !== "ready") return false;
|
|
168
|
+
const step = this.state.steps.find((s) => !this.state.completed.some((r) => r.stepId === s.id));
|
|
169
|
+
if (!step) return false;
|
|
170
|
+
this.propose({
|
|
171
|
+
requestId: this.requestIdentity(this.options.id ?? newIdentity),
|
|
172
|
+
step,
|
|
173
|
+
approvedUntil: this.now() + this.ttl,
|
|
174
|
+
});
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
approve() {
|
|
178
|
+
if (this.disposed || this.state.phase !== "approval" || !this.state.request) return false;
|
|
179
|
+
if (this.now() >= this.state.request.approvedUntil) {
|
|
180
|
+
this.send({ type: "expire" });
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
clearTimeout(this.expiry);
|
|
184
|
+
const request = this.state.request;
|
|
185
|
+
const { token, signal } = this.request();
|
|
186
|
+
this.send({ type: "approve" });
|
|
187
|
+
void (async () => {
|
|
188
|
+
try {
|
|
189
|
+
const raw = await this.adapter.execute(request, signal, (message) => {
|
|
190
|
+
if (
|
|
191
|
+
this.current(token) &&
|
|
192
|
+
this.state.phase === "running" &&
|
|
193
|
+
this.state.request?.requestId === request.requestId
|
|
194
|
+
) {
|
|
195
|
+
const text = shortText.safeParse(message);
|
|
196
|
+
if (text.success) this.send({ type: "progress", message: text.data });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const result = resultSchema.parse(raw);
|
|
200
|
+
if (result.requestId !== request.requestId) throw new Error("Wrong receipt");
|
|
201
|
+
if (this.current(token)) this.send({ type: "settle", result });
|
|
202
|
+
} catch {
|
|
203
|
+
if (this.current(token)) this.send({ type: "uncertain" });
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
deny() {
|
|
209
|
+
if (this.disposed || this.state.phase !== "approval") return false;
|
|
210
|
+
clearTimeout(this.expiry);
|
|
211
|
+
this.send({ type: "deny" });
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
stop() {
|
|
215
|
+
if (this.disposed || this.state.phase !== "running") return false;
|
|
216
|
+
++this.generation;
|
|
217
|
+
this.abort?.abort();
|
|
218
|
+
this.send({ type: "uncertain" });
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
renew() {
|
|
222
|
+
if (this.disposed || !["failed", "expired"].includes(this.state.phase) || !this.state.request)
|
|
223
|
+
return false;
|
|
224
|
+
this.propose({
|
|
225
|
+
...this.state.request,
|
|
226
|
+
requestId:
|
|
227
|
+
this.state.phase === "failed"
|
|
228
|
+
? this.requestIdentity(this.options.id ?? newIdentity)
|
|
229
|
+
: this.state.request.requestId,
|
|
230
|
+
approvedUntil: this.now() + this.ttl,
|
|
231
|
+
});
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
check() {
|
|
235
|
+
if (this.disposed || this.state.phase !== "uncertain" || !this.state.request) return false;
|
|
236
|
+
const request = this.state.request;
|
|
237
|
+
const { token, signal } = this.request();
|
|
238
|
+
this.send({ type: "check" });
|
|
239
|
+
void (async () => {
|
|
240
|
+
try {
|
|
241
|
+
const receipt = receiptSchema.parse(await this.adapter.lookup(request.requestId, signal));
|
|
242
|
+
if (receipt.requestId !== request.requestId) throw new Error("Wrong receipt");
|
|
243
|
+
if (!this.current(token)) return;
|
|
244
|
+
if (receipt.status === "not-found")
|
|
245
|
+
this.propose({ ...request, approvedUntil: this.now() + this.ttl });
|
|
246
|
+
else if (receipt.status === "pending") this.send({ type: "uncertain" });
|
|
247
|
+
else this.send({ type: "settle", result: resultSchema.parse(receipt) });
|
|
248
|
+
} catch {
|
|
249
|
+
if (this.current(token)) this.send({ type: "uncertain" });
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
override dispose() {
|
|
255
|
+
clearTimeout(this.expiry);
|
|
256
|
+
super.dispose();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
export type { JsonValue };
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { InteractionStore, identity, shortText, bodyText, newIdentity } from "./store.js";
|
|
3
|
+
export const artifactSchema = z
|
|
4
|
+
.strictObject({
|
|
5
|
+
id: identity,
|
|
6
|
+
title: shortText,
|
|
7
|
+
revision: identity,
|
|
8
|
+
content: bodyText,
|
|
9
|
+
canEdit: z.boolean(),
|
|
10
|
+
history: z.array(z.strictObject({ revision: identity, content: bodyText })).max(100),
|
|
11
|
+
})
|
|
12
|
+
.refine(
|
|
13
|
+
(item) => new Set(item.history.map((v) => v.revision)).size === item.history.length,
|
|
14
|
+
"Duplicate revision",
|
|
15
|
+
);
|
|
16
|
+
export type ArtifactRecord = z.infer<typeof artifactSchema>;
|
|
17
|
+
export interface ArtifactRequest {
|
|
18
|
+
requestId: string;
|
|
19
|
+
artifactId: string;
|
|
20
|
+
baseRevision: string;
|
|
21
|
+
content: string;
|
|
22
|
+
}
|
|
23
|
+
const saveSchema = z.discriminatedUnion("status", [
|
|
24
|
+
z.strictObject({ requestId: identity, status: z.literal("saved"), record: artifactSchema }),
|
|
25
|
+
z.strictObject({ requestId: identity, status: z.literal("conflict"), record: artifactSchema }),
|
|
26
|
+
z.strictObject({ requestId: identity, status: z.literal("rejected"), reason: shortText }),
|
|
27
|
+
z.strictObject({ requestId: identity, status: z.literal("unknown") }),
|
|
28
|
+
]);
|
|
29
|
+
export type ArtifactResult = z.infer<typeof saveSchema>;
|
|
30
|
+
export type ArtifactReceipt =
|
|
31
|
+
ArtifactResult | { requestId: string; status: "pending" | "not-found" };
|
|
32
|
+
const receiptSchema = z.union([
|
|
33
|
+
saveSchema,
|
|
34
|
+
z.strictObject({ requestId: identity, status: z.enum(["pending", "not-found"]) }),
|
|
35
|
+
]);
|
|
36
|
+
export interface ArtifactAdapter {
|
|
37
|
+
load(id: string, signal: AbortSignal): Promise<ArtifactRecord>;
|
|
38
|
+
/** Use compare-and-swap on baseRevision, server permissions and a durable idempotent receipt. */
|
|
39
|
+
save(request: ArtifactRequest, signal: AbortSignal): Promise<ArtifactResult>;
|
|
40
|
+
lookup(requestId: string, signal: AbortSignal): Promise<ArtifactReceipt>;
|
|
41
|
+
}
|
|
42
|
+
export interface ArtifactSnapshot {
|
|
43
|
+
phase:
|
|
44
|
+
| "idle"
|
|
45
|
+
| "loading"
|
|
46
|
+
| "load-error"
|
|
47
|
+
| "viewing"
|
|
48
|
+
| "editing"
|
|
49
|
+
| "saving"
|
|
50
|
+
| "conflict"
|
|
51
|
+
| "unknown"
|
|
52
|
+
| "checking";
|
|
53
|
+
record?: ArtifactRecord;
|
|
54
|
+
draft: string;
|
|
55
|
+
latest?: ArtifactRecord;
|
|
56
|
+
request?: ArtifactRequest;
|
|
57
|
+
reason?: string;
|
|
58
|
+
loadId?: string;
|
|
59
|
+
}
|
|
60
|
+
type ArtifactEvent =
|
|
61
|
+
| { type: "load"; id: string }
|
|
62
|
+
| { type: "loaded"; record: ArtifactRecord }
|
|
63
|
+
| { type: "load-error" }
|
|
64
|
+
| { type: "edit"; content: string }
|
|
65
|
+
| { type: "save"; request: ArtifactRequest }
|
|
66
|
+
| { type: "settle"; result: ArtifactResult }
|
|
67
|
+
| { type: "unknown" }
|
|
68
|
+
| { type: "check" }
|
|
69
|
+
| { type: "not-found" }
|
|
70
|
+
| { type: "adopt"; keepDraft: boolean };
|
|
71
|
+
export function artifactReducer(state: ArtifactSnapshot, event: ArtifactEvent): ArtifactSnapshot {
|
|
72
|
+
switch (event.type) {
|
|
73
|
+
case "load":
|
|
74
|
+
return { ...state, phase: "loading", loadId: event.id, reason: undefined };
|
|
75
|
+
case "loaded":
|
|
76
|
+
return { phase: "viewing", record: event.record, draft: event.record.content };
|
|
77
|
+
case "load-error":
|
|
78
|
+
return { ...state, phase: "load-error" };
|
|
79
|
+
case "edit":
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
phase: "editing",
|
|
83
|
+
draft: event.content,
|
|
84
|
+
request: undefined,
|
|
85
|
+
reason: undefined,
|
|
86
|
+
};
|
|
87
|
+
case "save":
|
|
88
|
+
return { ...state, phase: "saving", request: event.request, reason: undefined };
|
|
89
|
+
case "unknown":
|
|
90
|
+
return { ...state, phase: "unknown" };
|
|
91
|
+
case "check":
|
|
92
|
+
return { ...state, phase: "checking" };
|
|
93
|
+
case "not-found":
|
|
94
|
+
return { ...state, phase: "editing" };
|
|
95
|
+
case "adopt":
|
|
96
|
+
return state.latest
|
|
97
|
+
? {
|
|
98
|
+
phase: event.keepDraft ? "editing" : "viewing",
|
|
99
|
+
record: state.latest,
|
|
100
|
+
draft: event.keepDraft ? state.draft : state.latest.content,
|
|
101
|
+
}
|
|
102
|
+
: state;
|
|
103
|
+
case "settle": {
|
|
104
|
+
if (event.result.status === "saved")
|
|
105
|
+
return {
|
|
106
|
+
phase: "viewing",
|
|
107
|
+
record: event.result.record,
|
|
108
|
+
draft: event.result.record.content,
|
|
109
|
+
};
|
|
110
|
+
if (event.result.status === "conflict")
|
|
111
|
+
return { ...state, phase: "conflict", latest: event.result.record };
|
|
112
|
+
if (event.result.status === "rejected")
|
|
113
|
+
return { ...state, phase: "editing", reason: event.result.reason, request: undefined };
|
|
114
|
+
return { ...state, phase: "unknown" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export class ArtifactController extends InteractionStore<ArtifactSnapshot> {
|
|
119
|
+
constructor(
|
|
120
|
+
private adapter: ArtifactAdapter,
|
|
121
|
+
private id: () => string = newIdentity,
|
|
122
|
+
) {
|
|
123
|
+
super({ phase: "idle", draft: "" });
|
|
124
|
+
}
|
|
125
|
+
private send(event: ArtifactEvent) {
|
|
126
|
+
this.publish(artifactReducer(this.state, event));
|
|
127
|
+
}
|
|
128
|
+
open(id: string) {
|
|
129
|
+
if (
|
|
130
|
+
this.disposed ||
|
|
131
|
+
["saving", "unknown", "checking", "conflict"].includes(this.state.phase) ||
|
|
132
|
+
(this.state.record && this.state.draft !== this.state.record.content)
|
|
133
|
+
)
|
|
134
|
+
return false;
|
|
135
|
+
const parsed = identity.safeParse(id);
|
|
136
|
+
if (!parsed.success) return false;
|
|
137
|
+
const { token, signal } = this.request();
|
|
138
|
+
this.send({ type: "load", id: parsed.data });
|
|
139
|
+
void (async () => {
|
|
140
|
+
try {
|
|
141
|
+
const record = artifactSchema.parse(await this.adapter.load(parsed.data, signal));
|
|
142
|
+
if (record.id !== parsed.data) throw new Error("Wrong artifact");
|
|
143
|
+
if (this.current(token)) this.send({ type: "loaded", record });
|
|
144
|
+
} catch {
|
|
145
|
+
if (this.current(token)) this.send({ type: "load-error" });
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
edit(content: string) {
|
|
151
|
+
if (
|
|
152
|
+
this.disposed ||
|
|
153
|
+
!this.state.record?.canEdit ||
|
|
154
|
+
!["viewing", "editing"].includes(this.state.phase) ||
|
|
155
|
+
!bodyText.safeParse(content).success
|
|
156
|
+
)
|
|
157
|
+
return false;
|
|
158
|
+
this.send({ type: "edit", content });
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
discard() {
|
|
162
|
+
if (
|
|
163
|
+
this.disposed ||
|
|
164
|
+
!this.state.record ||
|
|
165
|
+
!["viewing", "editing", "load-error"].includes(this.state.phase)
|
|
166
|
+
)
|
|
167
|
+
return false;
|
|
168
|
+
this.send({ type: "loaded", record: this.state.record });
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
save() {
|
|
172
|
+
const state = this.state;
|
|
173
|
+
if (
|
|
174
|
+
this.disposed ||
|
|
175
|
+
state.phase !== "editing" ||
|
|
176
|
+
!state.record?.canEdit ||
|
|
177
|
+
state.draft === state.record.content
|
|
178
|
+
)
|
|
179
|
+
return false;
|
|
180
|
+
const request =
|
|
181
|
+
state.request ??
|
|
182
|
+
Object.freeze({
|
|
183
|
+
requestId: this.requestIdentity(this.id),
|
|
184
|
+
artifactId: state.record.id,
|
|
185
|
+
baseRevision: state.record.revision,
|
|
186
|
+
content: state.draft,
|
|
187
|
+
});
|
|
188
|
+
const { token, signal } = this.request();
|
|
189
|
+
this.send({ type: "save", request });
|
|
190
|
+
void (async () => {
|
|
191
|
+
try {
|
|
192
|
+
const result = saveSchema.parse(await this.adapter.save(request, signal));
|
|
193
|
+
this.validateResult(result, request);
|
|
194
|
+
if (this.current(token)) this.send({ type: "settle", result });
|
|
195
|
+
} catch {
|
|
196
|
+
if (this.current(token)) this.send({ type: "unknown" });
|
|
197
|
+
}
|
|
198
|
+
})();
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
private validateResult(result: ArtifactResult, request: ArtifactRequest) {
|
|
202
|
+
if (
|
|
203
|
+
result.requestId !== request.requestId ||
|
|
204
|
+
("record" in result && result.record.id !== request.artifactId) ||
|
|
205
|
+
(result.status === "saved" && result.record.revision === request.baseRevision)
|
|
206
|
+
)
|
|
207
|
+
throw new Error("Invalid artifact receipt");
|
|
208
|
+
}
|
|
209
|
+
adoptLatest(keepDraft: boolean) {
|
|
210
|
+
if (this.disposed || this.state.phase !== "conflict" || !this.state.latest) return false;
|
|
211
|
+
this.send({ type: "adopt", keepDraft });
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
check() {
|
|
215
|
+
if (this.disposed || this.state.phase !== "unknown" || !this.state.request) return false;
|
|
216
|
+
const request = this.state.request;
|
|
217
|
+
const { token, signal } = this.request();
|
|
218
|
+
this.send({ type: "check" });
|
|
219
|
+
void (async () => {
|
|
220
|
+
try {
|
|
221
|
+
const receipt = receiptSchema.parse(await this.adapter.lookup(request.requestId, signal));
|
|
222
|
+
if (receipt.requestId !== request.requestId) throw new Error("Wrong receipt");
|
|
223
|
+
if (!this.current(token)) return;
|
|
224
|
+
if (receipt.status === "not-found") this.send({ type: "not-found" });
|
|
225
|
+
else if (receipt.status === "pending") this.send({ type: "unknown" });
|
|
226
|
+
else {
|
|
227
|
+
this.validateResult(saveSchema.parse(receipt), request);
|
|
228
|
+
this.send({ type: "settle", result: saveSchema.parse(receipt) });
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
if (this.current(token)) this.send({ type: "unknown" });
|
|
232
|
+
}
|
|
233
|
+
})();
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
import { interactionCopy } from "./copy.js";
|
|
3
|
+
type Widen<T> = T extends string
|
|
4
|
+
? string
|
|
5
|
+
: T extends (...args: never[]) => unknown
|
|
6
|
+
? T
|
|
7
|
+
: T extends object
|
|
8
|
+
? { [K in keyof T]: Widen<T[K]> }
|
|
9
|
+
: T;
|
|
10
|
+
export type InteractionCopy = Widen<typeof interactionCopy>;
|
|
11
|
+
export type InteractionCopyOverrides = {
|
|
12
|
+
[K in keyof InteractionCopy]?: InteractionCopy[K] extends (...args: never[]) => unknown
|
|
13
|
+
? InteractionCopy[K]
|
|
14
|
+
: InteractionCopy[K] extends object
|
|
15
|
+
? Partial<InteractionCopy[K]>
|
|
16
|
+
: InteractionCopy[K];
|
|
17
|
+
};
|
|
18
|
+
const CopyContext = createContext<InteractionCopy>(interactionCopy);
|
|
19
|
+
/** Scope user-facing copy independently from state identities and server contracts. */
|
|
20
|
+
export function InteractionProvider({
|
|
21
|
+
copy = {},
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
copy?: InteractionCopyOverrides;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}) {
|
|
27
|
+
const parent = useContext(CopyContext);
|
|
28
|
+
const merged = {
|
|
29
|
+
...parent,
|
|
30
|
+
...copy,
|
|
31
|
+
toolStates: { ...parent.toolStates, ...copy.toolStates },
|
|
32
|
+
agentStates: { ...parent.agentStates, ...copy.agentStates },
|
|
33
|
+
artifactStates: { ...parent.artifactStates, ...copy.artifactStates },
|
|
34
|
+
connection: { ...parent.connection, ...copy.connection },
|
|
35
|
+
};
|
|
36
|
+
return <CopyContext.Provider value={merged}>{children}</CopyContext.Provider>;
|
|
37
|
+
}
|
|
38
|
+
export const useInteractionCopy = () => useContext(CopyContext);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const interactionCopy = {
|
|
2
|
+
code: "Code",
|
|
3
|
+
files: "Files",
|
|
4
|
+
checks: "Checks",
|
|
5
|
+
passed: "Passed",
|
|
6
|
+
failed: "Failed",
|
|
7
|
+
artifactStates: {
|
|
8
|
+
idle: "Choose an artifact",
|
|
9
|
+
loading: "Loading artifact",
|
|
10
|
+
"load-error": "Artifact unavailable",
|
|
11
|
+
viewing: "Saved version",
|
|
12
|
+
editing: "Draft edited",
|
|
13
|
+
saving: "Saving revision",
|
|
14
|
+
conflict: "Saved version changed",
|
|
15
|
+
unknown: "Save result unknown",
|
|
16
|
+
checking: "Checking save",
|
|
17
|
+
},
|
|
18
|
+
invalid: "This content could not be displayed. Ask for an updated version.",
|
|
19
|
+
explanation: "Progress explanation",
|
|
20
|
+
supplied: "Reported by the host",
|
|
21
|
+
inputs: "Inputs",
|
|
22
|
+
output: "Result",
|
|
23
|
+
tool: "Tool details",
|
|
24
|
+
sources: "Sources",
|
|
25
|
+
source: (index: number, title: string) => `Source ${index}: ${title}`,
|
|
26
|
+
unavailable: "Source unavailable",
|
|
27
|
+
noSources: "No sources were supplied.",
|
|
28
|
+
retrieval: "Context used for this response",
|
|
29
|
+
included: "Included",
|
|
30
|
+
excluded: "Not included",
|
|
31
|
+
checkClaim: "Check this claim",
|
|
32
|
+
blocked: "This action cannot proceed",
|
|
33
|
+
model: "Model",
|
|
34
|
+
context: "Context to include",
|
|
35
|
+
commands: "Commands",
|
|
36
|
+
quote: "Quoted excerpt",
|
|
37
|
+
reply: "Use excerpt in reply",
|
|
38
|
+
connection: {
|
|
39
|
+
connected: "Connected",
|
|
40
|
+
reconnecting: "Reconnecting",
|
|
41
|
+
disconnected: "Disconnected",
|
|
42
|
+
},
|
|
43
|
+
disconnected:
|
|
44
|
+
"The connection does not tell us whether remote work finished. Check the result before repeating an action.",
|
|
45
|
+
reconnect: "Reconnect",
|
|
46
|
+
trace: "Run measurements",
|
|
47
|
+
notReported: "Not reported",
|
|
48
|
+
agent: "Supervised plan",
|
|
49
|
+
plan: "Proposed steps",
|
|
50
|
+
request: "Review next step",
|
|
51
|
+
scope: "Approval scope",
|
|
52
|
+
allow: "Approve this step",
|
|
53
|
+
deny: "Decline",
|
|
54
|
+
stop: "Stop waiting",
|
|
55
|
+
check: "Check status",
|
|
56
|
+
renew: "Review again",
|
|
57
|
+
unknown: "The result is unknown. Check the original request before running it again.",
|
|
58
|
+
approvalHint:
|
|
59
|
+
"Approval applies only to the displayed step and inputs. The host still checks your permissions.",
|
|
60
|
+
expires: "Approval expires if you wait too long. Review the step again to continue.",
|
|
61
|
+
denied: "The step was declined. Earlier completed work stays completed.",
|
|
62
|
+
complete: "All steps are complete.",
|
|
63
|
+
toolStates: {
|
|
64
|
+
pending: "Pending",
|
|
65
|
+
approval: "Awaiting approval",
|
|
66
|
+
running: "Running",
|
|
67
|
+
completed: "Complete",
|
|
68
|
+
rejected: "Not applied",
|
|
69
|
+
unknown: "Result unknown",
|
|
70
|
+
},
|
|
71
|
+
agentStates: {
|
|
72
|
+
ready: "Ready to review",
|
|
73
|
+
approval: "Awaiting approval",
|
|
74
|
+
running: "Working",
|
|
75
|
+
checking: "Checking result",
|
|
76
|
+
failed: "Step not applied",
|
|
77
|
+
uncertain: "Result unknown",
|
|
78
|
+
denied: "Declined",
|
|
79
|
+
expired: "Approval expired",
|
|
80
|
+
complete: "Complete",
|
|
81
|
+
},
|
|
82
|
+
artifact: "Artifact",
|
|
83
|
+
edit: "Edit draft",
|
|
84
|
+
save: "Save revision",
|
|
85
|
+
saving: "Saving revision",
|
|
86
|
+
discard: "Discard edits",
|
|
87
|
+
revision: "Revision",
|
|
88
|
+
artifactContent: "Artifact content",
|
|
89
|
+
latest: "Latest saved version",
|
|
90
|
+
localDraft: "Your draft",
|
|
91
|
+
conflict: "The saved version changed. Compare it with your draft before choosing what to keep.",
|
|
92
|
+
useLatest: "Use latest saved version",
|
|
93
|
+
keepDraft: "Keep my draft against the latest version",
|
|
94
|
+
loadError: "Couldn’t load this artifact. Try again; the current work is still here.",
|
|
95
|
+
retry: "Try again",
|
|
96
|
+
idleArtifact: "Choose an artifact to open.",
|
|
97
|
+
feedback: "Response feedback",
|
|
98
|
+
helpful: "Helpful",
|
|
99
|
+
unhelpful: "Needs work",
|
|
100
|
+
comment: "What should change?",
|
|
101
|
+
sendFeedback: "Save feedback",
|
|
102
|
+
savedFeedback: "Feedback saved for this response.",
|
|
103
|
+
failedFeedback: "Feedback is not confirmed. Retry with the same feedback.",
|
|
104
|
+
threads: "Conversation history",
|
|
105
|
+
refresh: "Refresh conversations",
|
|
106
|
+
active: "Active",
|
|
107
|
+
archived: "Archived",
|
|
108
|
+
archive: "Archive conversation",
|
|
109
|
+
restore: "Restore conversation",
|
|
110
|
+
rename: "Rename conversation",
|
|
111
|
+
title: "Conversation title",
|
|
112
|
+
saveTitle: "Save title",
|
|
113
|
+
chooseThread: "Choose a conversation to see its saved context.",
|
|
114
|
+
noThreads: "No conversations in this view.",
|
|
115
|
+
threadError: "Couldn’t complete that request. The current conversation is still here.",
|
|
116
|
+
loading: "Loading",
|
|
117
|
+
} as const;
|