@opengeni/react 5.2.0-canary.0 → 6.0.0-canary.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 +12 -0
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +3 -1
- package/dist/artifacts.js.map +1 -1
- package/dist/{chunk-QXJCNVSD.js → chunk-CC7AVRIJ.js} +1769 -1493
- package/dist/chunk-CC7AVRIJ.js.map +1 -0
- package/dist/{chunk-5PQJOCX5.js → chunk-KANFU34B.js} +6 -4
- package/dist/chunk-KANFU34B.js.map +1 -0
- package/dist/{chunk-IYCR4343.js → chunk-PZ27MCZP.js} +36 -1
- package/dist/chunk-PZ27MCZP.js.map +1 -0
- package/dist/{chunk-2TKI3WEG.js → chunk-WYWRYUNQ.js} +12 -1
- package/dist/chunk-WYWRYUNQ.js.map +1 -0
- package/dist/{chunk-T5FRXMC3.js → chunk-YS24CFDI.js} +24 -9
- package/dist/chunk-YS24CFDI.js.map +1 -0
- package/dist/components/session-chrome.d.ts +6 -1
- package/dist/{computer-viewer-AA4JH2AP.js → computer-viewer-CTFCJVS7.js} +2 -2
- package/dist/connect.js +11 -19
- package/dist/connect.js.map +1 -1
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +1 -1
- package/dist/{platform-activity-row-UW4UVVM4.js → platform-activity-row-DUE2W74J.js} +1 -1
- package/dist/platform-activity-row-DUE2W74J.js.map +1 -0
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +6 -107
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +2 -2
- package/dist/timeline/genie-loading.d.ts +9 -1
- package/dist/timeline/index.d.ts +3 -1
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/platform-activity-row.d.ts +0 -1
- package/dist/timeline/retained-image.d.ts +2 -0
- package/dist/timeline/types.d.ts +13 -2
- package/package.json +3 -3
- package/src/artifacts.ts +1 -1
- package/src/components/computer-viewer.tsx +11 -3
- package/src/components/message-timeline.tsx +42 -12
- package/src/components/session-chrome.tsx +14 -2
- package/src/connection-catalog.tsx +8 -21
- package/src/hooks/use-computer-frame-stream.ts +17 -5
- package/src/hooks/use-file-attachments.ts +4 -1
- package/src/hooks/use-goal.ts +3 -1
- package/src/index.ts +6 -0
- package/src/session-ui.ts +3 -0
- package/src/timeline/activity-rail.tsx +11 -1
- package/src/timeline/genie-loading.tsx +18 -4
- package/src/timeline/index.ts +4 -0
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/platform-activity-row.tsx +0 -1
- package/src/timeline/projection.ts +40 -0
- package/src/timeline/retained-image.ts +12 -0
- package/src/timeline/tool-renderers.tsx +115 -16
- package/src/timeline/types.ts +14 -0
- package/dist/chunk-2TKI3WEG.js.map +0 -1
- package/dist/chunk-5PQJOCX5.js.map +0 -1
- package/dist/chunk-IYCR4343.js.map +0 -1
- package/dist/chunk-QXJCNVSD.js.map +0 -1
- package/dist/chunk-T5FRXMC3.js.map +0 -1
- package/dist/platform-activity-row-UW4UVVM4.js.map +0 -1
- /package/dist/{computer-viewer-AA4JH2AP.js.map → computer-viewer-CTFCJVS7.js.map} +0 -0
|
@@ -159,9 +159,19 @@ export function useComputerFrameStream(
|
|
|
159
159
|
pendingFrame = null;
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
+
const clearRecoveryTimers = () => {
|
|
163
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
164
|
+
if (expiryTimer) clearTimeout(expiryTimer);
|
|
165
|
+
reconnectTimer = null;
|
|
166
|
+
expiryTimer = null;
|
|
167
|
+
};
|
|
168
|
+
|
|
162
169
|
const scheduleReconnect = () => {
|
|
163
170
|
if (disposed || reconnectTimer) return;
|
|
164
|
-
if (failures >= MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME)
|
|
171
|
+
if (failures >= MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME) {
|
|
172
|
+
clearRecoveryTimers();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
165
175
|
const delay = Math.min(5_000, 250 * 2 ** Math.min(failures, 5));
|
|
166
176
|
failures += 1;
|
|
167
177
|
setResult((current) => ({ ...current, state: "reconnecting" }));
|
|
@@ -177,6 +187,7 @@ export function useComputerFrameStream(
|
|
|
177
187
|
setResult((current) => ({ ...current, state: "error", error }));
|
|
178
188
|
clearSocket();
|
|
179
189
|
if (reconnectAutomatically) scheduleReconnect();
|
|
190
|
+
else clearRecoveryTimers();
|
|
180
191
|
};
|
|
181
192
|
|
|
182
193
|
const onOpen = (source: ComputerFrameWebSocket) => {
|
|
@@ -295,8 +306,7 @@ export function useComputerFrameStream(
|
|
|
295
306
|
|
|
296
307
|
const onClose = (source: ComputerFrameWebSocket) => {
|
|
297
308
|
if (disposed || source !== socket) return;
|
|
298
|
-
|
|
299
|
-
scheduleReconnect();
|
|
309
|
+
fail(new Error("Desktop view lost connection."));
|
|
300
310
|
};
|
|
301
311
|
|
|
302
312
|
const connectSocket = () => {
|
|
@@ -366,6 +376,9 @@ export function useComputerFrameStream(
|
|
|
366
376
|
throw new Error("desktop attachment does not match the requested resource");
|
|
367
377
|
}
|
|
368
378
|
controllerGeneration = attachment.controllerGeneration;
|
|
379
|
+
// A fresh attachment owns a new producer whose sequence can restart.
|
|
380
|
+
// Transport reconnects reuse the attachment and retain their sequence fence.
|
|
381
|
+
latestRef.current = { key: "", sequence: -1 };
|
|
369
382
|
activeAttachment = attachment;
|
|
370
383
|
activeStream = attachment.stream;
|
|
371
384
|
attachmentExpiresAt = Date.parse(attachment.expiresAt);
|
|
@@ -406,8 +419,7 @@ export function useComputerFrameStream(
|
|
|
406
419
|
disposed = true;
|
|
407
420
|
pendingFrame = null;
|
|
408
421
|
attachmentAbort.abort();
|
|
409
|
-
|
|
410
|
-
if (expiryTimer) clearTimeout(expiryTimer);
|
|
422
|
+
clearRecoveryTimers();
|
|
411
423
|
clearSocket(true);
|
|
412
424
|
};
|
|
413
425
|
}, [client, enabled, nonce, options.computerSessionId, options.targetId, workspaceId]);
|
|
@@ -13,6 +13,8 @@ export type UseFileAttachmentsOptions = EmbeddedFileAttachmentClientOverride & {
|
|
|
13
13
|
* UseFileAttachmentsResult.addFiles} (the explicit picker / drop path)
|
|
14
14
|
* bypasses it.
|
|
15
15
|
*/
|
|
16
|
+
/** Retain original uploads in the chat owner’s personal scope when private. */
|
|
17
|
+
scope?: "workspace" | "personal";
|
|
16
18
|
pasteFilter?: ((file: File) => boolean) | undefined;
|
|
17
19
|
};
|
|
18
20
|
|
|
@@ -212,6 +214,7 @@ export function useFileAttachments(
|
|
|
212
214
|
filename: file.name || "file",
|
|
213
215
|
contentType: file.type || "application/octet-stream",
|
|
214
216
|
data: file,
|
|
217
|
+
...(options.scope ? { scope: options.scope } : {}),
|
|
215
218
|
})
|
|
216
219
|
.then((asset) => {
|
|
217
220
|
if (scopeGeneration.current !== generation) return;
|
|
@@ -252,7 +255,7 @@ export function useFileAttachments(
|
|
|
252
255
|
);
|
|
253
256
|
});
|
|
254
257
|
},
|
|
255
|
-
[client, workspaceId],
|
|
258
|
+
[client, workspaceId, options.scope],
|
|
256
259
|
);
|
|
257
260
|
|
|
258
261
|
const addFiles = useCallback(
|
package/src/hooks/use-goal.ts
CHANGED
|
@@ -138,7 +138,9 @@ export function useGoal(
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
if (sharedFeed) {
|
|
141
|
-
|
|
141
|
+
// A shared log supplies invalidations, not the authoritative goal snapshot.
|
|
142
|
+
setLoading(true);
|
|
143
|
+
void load();
|
|
142
144
|
return () => {
|
|
143
145
|
retireLoads();
|
|
144
146
|
};
|
package/src/index.ts
CHANGED
|
@@ -617,3 +617,9 @@ export {
|
|
|
617
617
|
tryParseJson,
|
|
618
618
|
} from "./lib/format";
|
|
619
619
|
export { SessionCommandsPanel } from "./components/session-commands-panel";
|
|
620
|
+
|
|
621
|
+
export { KnowledgeActivityProvider } from "./timeline/knowledge-receipt";
|
|
622
|
+
export type { KnowledgeActivityActions } from "./timeline/knowledge-receipt";
|
|
623
|
+
|
|
624
|
+
export { StartupTimings } from "./timeline/startup-timings";
|
|
625
|
+
export { setStartupDetails, useStartupDetails } from "./timeline/startup-preference";
|
package/src/session-ui.ts
CHANGED
|
@@ -46,6 +46,9 @@ export type {
|
|
|
46
46
|
SessionChromeSignalTone,
|
|
47
47
|
} from "./components/session-chrome";
|
|
48
48
|
export { SessionCommandsPanel } from "./components/session-commands-panel";
|
|
49
|
+
|
|
50
|
+
export { KnowledgeActivityProvider } from "./timeline/knowledge-receipt";
|
|
51
|
+
export type { KnowledgeActivityActions } from "./timeline/knowledge-receipt";
|
|
49
52
|
export { StartupTimings } from "./timeline/startup-timings";
|
|
50
53
|
export { useStartupDetails, setStartupDetails } from "./timeline/startup-preference";
|
|
51
54
|
export type { GenieLoadingOptions } from "./timeline/genie-loading";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KnowledgeReceiptRow } from "./knowledge-receipt";
|
|
1
2
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
2
3
|
import { GenieLoading } from "./genie-loading";
|
|
3
4
|
import { useStartupDetails } from "./startup-preference";
|
|
@@ -241,7 +242,6 @@ export function renderActivity(
|
|
|
241
242
|
t={toolDisplayName}
|
|
242
243
|
b={BotIcon}
|
|
243
244
|
m={Markdown}
|
|
244
|
-
r={truncate}
|
|
245
245
|
j={rowJsx}
|
|
246
246
|
s={rowJsxs}
|
|
247
247
|
/>
|
|
@@ -261,6 +261,16 @@ export function renderActivity(
|
|
|
261
261
|
}
|
|
262
262
|
case "worker":
|
|
263
263
|
return <WorkerRow item={item} onOpenSession={onOpenSession} />;
|
|
264
|
+
case "knowledge":
|
|
265
|
+
return (
|
|
266
|
+
<KnowledgeReceiptRow
|
|
267
|
+
outcome={item.outcome}
|
|
268
|
+
entryId={item.entryId}
|
|
269
|
+
fileId={item.fileId}
|
|
270
|
+
title={item.filename}
|
|
271
|
+
source={Boolean(item.fileId)}
|
|
272
|
+
/>
|
|
273
|
+
);
|
|
264
274
|
case "memory":
|
|
265
275
|
return <MemoryRow item={item} onMemoryClick={onMemoryClick} />;
|
|
266
276
|
case "fleet-decision":
|
|
@@ -12,7 +12,15 @@ export type GenieLoadingOptions = {
|
|
|
12
12
|
/** Replace the visual while preserving SDK loading visibility and transitions. */
|
|
13
13
|
render?: (props: GenieLoadingRenderProps) => ReactNode;
|
|
14
14
|
phrases?: readonly string[];
|
|
15
|
-
/**
|
|
15
|
+
/** Native copy overrides. Omitted messages retain the default English copy. */
|
|
16
|
+
messages?: {
|
|
17
|
+
status?: string;
|
|
18
|
+
slowStatus?: string;
|
|
19
|
+
slowText?: string;
|
|
20
|
+
showDetails?: string;
|
|
21
|
+
hideDetails?: string;
|
|
22
|
+
};
|
|
23
|
+
/** Public adapter options; the renderer dependency's declarations stay private. */
|
|
16
24
|
orb?: {
|
|
17
25
|
state?:
|
|
18
26
|
| "working"
|
|
@@ -90,10 +98,14 @@ export function GenieLoading({
|
|
|
90
98
|
</div>
|
|
91
99
|
<div className="og-genie-copy">
|
|
92
100
|
<span className="sr-only" role="status">
|
|
93
|
-
{slow
|
|
101
|
+
{slow
|
|
102
|
+
? (options?.messages?.slowStatus ?? "Preparing your task. Taking longer than usual.")
|
|
103
|
+
: (options?.messages?.status ?? "Preparing your task.")}
|
|
94
104
|
</span>
|
|
95
105
|
<span key={slow ? "slow" : phrase} className="og-genie-phrase" aria-hidden="true">
|
|
96
|
-
{slow
|
|
106
|
+
{slow
|
|
107
|
+
? (options?.messages?.slowText ?? "A little longer than usual…")
|
|
108
|
+
: phrases[phrase % phrases.length]}
|
|
97
109
|
</span>
|
|
98
110
|
{showDetails || detailsOpen ? (
|
|
99
111
|
<button
|
|
@@ -102,7 +114,9 @@ export function GenieLoading({
|
|
|
102
114
|
aria-expanded={detailsOpen}
|
|
103
115
|
onClick={onShowDetails}
|
|
104
116
|
>
|
|
105
|
-
{detailsOpen
|
|
117
|
+
{detailsOpen
|
|
118
|
+
? (options?.messages?.hideDetails ?? "Hide details")
|
|
119
|
+
: (options?.messages?.showDetails ?? "Behind the magic")}
|
|
106
120
|
<span aria-hidden="true"> ↗</span>
|
|
107
121
|
</button>
|
|
108
122
|
) : null}
|
package/src/timeline/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ export type {
|
|
|
41
41
|
MachineInputBatchItem,
|
|
42
42
|
MachineInputMember,
|
|
43
43
|
MemoryItem,
|
|
44
|
+
KnowledgeItem,
|
|
44
45
|
NoticeItem,
|
|
45
46
|
ReasoningItem,
|
|
46
47
|
SandboxItem,
|
|
@@ -139,3 +140,6 @@ export {
|
|
|
139
140
|
v4aToGitFileDiff,
|
|
140
141
|
} from "./parsers";
|
|
141
142
|
export type { ApplyPatchOperation } from "./parsers";
|
|
143
|
+
|
|
144
|
+
export { KnowledgeActivityProvider } from "./knowledge-receipt";
|
|
145
|
+
export type { KnowledgeActivityActions } from "./knowledge-receipt";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createContext, useContext, useState, type ReactNode } from "react";
|
|
2
|
+
import { BrainCircuitIcon } from "lucide-react";
|
|
3
|
+
import { ActivityDisclosure, BodyNote } from "./shared";
|
|
4
|
+
|
|
5
|
+
export type KnowledgeActivityActions = {
|
|
6
|
+
onInspect?: ((entryId: string) => void) | undefined;
|
|
7
|
+
onRetryFile?: ((fileId: string) => Promise<boolean | void>) | undefined;
|
|
8
|
+
retryDisabled?: boolean | undefined;
|
|
9
|
+
};
|
|
10
|
+
const KnowledgeActions = createContext<KnowledgeActivityActions>({});
|
|
11
|
+
export function KnowledgeActivityProvider({
|
|
12
|
+
children,
|
|
13
|
+
...actions
|
|
14
|
+
}: KnowledgeActivityActions & { children: ReactNode }) {
|
|
15
|
+
return <KnowledgeActions.Provider value={actions}>{children}</KnowledgeActions.Provider>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A saved/pending result never opens or blocks the human-input surface. */
|
|
19
|
+
export function KnowledgeReceiptRow(props: {
|
|
20
|
+
outcome: "published" | "pending" | "rejected" | "archived" | "failed";
|
|
21
|
+
entryId?: string | undefined;
|
|
22
|
+
fileId?: string | undefined;
|
|
23
|
+
title?: string | undefined;
|
|
24
|
+
source?: boolean | undefined;
|
|
25
|
+
}) {
|
|
26
|
+
const actions = useContext(KnowledgeActions);
|
|
27
|
+
const [retrying, setRetrying] = useState(false);
|
|
28
|
+
const [retrySent, setRetrySent] = useState(false);
|
|
29
|
+
const [error, setError] = useState<string | null>(null);
|
|
30
|
+
const label =
|
|
31
|
+
props.outcome === "pending"
|
|
32
|
+
? "Knowledge saved for review"
|
|
33
|
+
: props.outcome === "failed"
|
|
34
|
+
? "Source preparation failed"
|
|
35
|
+
: props.outcome === "rejected"
|
|
36
|
+
? "Source remains rejected"
|
|
37
|
+
: props.outcome === "archived"
|
|
38
|
+
? "Knowledge archived"
|
|
39
|
+
: props.source
|
|
40
|
+
? "Source saved to Knowledge"
|
|
41
|
+
: "Saved to Knowledge";
|
|
42
|
+
const actionClass =
|
|
43
|
+
"rounded-og-sm px-1 py-0.5 text-og-sm text-og-fg-muted hover:text-og-fg focus-visible:outline-2 focus-visible:outline-og-accent disabled:opacity-50";
|
|
44
|
+
return (
|
|
45
|
+
<ActivityDisclosure
|
|
46
|
+
icon={<BrainCircuitIcon className="size-3.5" />}
|
|
47
|
+
iconTone="muted"
|
|
48
|
+
title={label}
|
|
49
|
+
preview={props.title}
|
|
50
|
+
>
|
|
51
|
+
{props.outcome === "pending" ? (
|
|
52
|
+
<BodyNote>
|
|
53
|
+
Saved in Needs review. This task continues; future tasks can use it after approval.
|
|
54
|
+
</BodyNote>
|
|
55
|
+
) : null}
|
|
56
|
+
{props.outcome === "failed" ? (
|
|
57
|
+
<BodyNote>
|
|
58
|
+
The original file remains attached. Searchable text has not been retained.
|
|
59
|
+
</BodyNote>
|
|
60
|
+
) : null}
|
|
61
|
+
{props.outcome === "rejected" ? (
|
|
62
|
+
<BodyNote>Processing the file again leaves the review decision unchanged.</BodyNote>
|
|
63
|
+
) : null}
|
|
64
|
+
{props.entryId && actions.onInspect ? (
|
|
65
|
+
<button
|
|
66
|
+
className={actionClass}
|
|
67
|
+
type="button"
|
|
68
|
+
onClick={() => actions.onInspect?.(props.entryId!)}
|
|
69
|
+
>
|
|
70
|
+
{props.outcome === "pending" ? "Review in Knowledge" : "View in Knowledge"}
|
|
71
|
+
</button>
|
|
72
|
+
) : null}
|
|
73
|
+
{props.outcome === "failed" && props.fileId && actions.onRetryFile ? (
|
|
74
|
+
<button
|
|
75
|
+
className={actionClass}
|
|
76
|
+
type="button"
|
|
77
|
+
disabled={retrying || retrySent || actions.retryDisabled}
|
|
78
|
+
onClick={() => {
|
|
79
|
+
setRetrying(true);
|
|
80
|
+
setError(null);
|
|
81
|
+
void actions.onRetryFile!(props.fileId!)
|
|
82
|
+
.then((sent) => {
|
|
83
|
+
if (sent !== false) setRetrySent(true);
|
|
84
|
+
else setError("Could not start the retry. Try again when the chat is ready.");
|
|
85
|
+
})
|
|
86
|
+
.catch(() => setError("Could not start the retry. Please try again."))
|
|
87
|
+
.finally(() => setRetrying(false));
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{retrying ? "Starting retry…" : retrySent ? "Retry requested" : "Retry preparation"}
|
|
91
|
+
</button>
|
|
92
|
+
) : null}
|
|
93
|
+
{error ? (
|
|
94
|
+
<p role="alert" className="text-og-sm">
|
|
95
|
+
{error}
|
|
96
|
+
</p>
|
|
97
|
+
) : null}
|
|
98
|
+
</ActivityDisclosure>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -19,7 +19,6 @@ export default function PlatformActivityRow({
|
|
|
19
19
|
t: (name: string) => string;
|
|
20
20
|
b: typeof import("lucide-react").BotIcon;
|
|
21
21
|
m: typeof import("../components/markdown").Markdown;
|
|
22
|
-
r: typeof import("../lib/format").truncate;
|
|
23
22
|
j: RowJsx;
|
|
24
23
|
s: RowJsxs;
|
|
25
24
|
}) {
|
|
@@ -1175,6 +1175,45 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1175
1175
|
break;
|
|
1176
1176
|
}
|
|
1177
1177
|
|
|
1178
|
+
case "instruction.confirmation.recovered": {
|
|
1179
|
+
const receipt = asRecord(payload.receipt);
|
|
1180
|
+
if (receipt.outcome !== "published") break;
|
|
1181
|
+
closeStreamingTail();
|
|
1182
|
+
items.push({
|
|
1183
|
+
kind: "notice",
|
|
1184
|
+
id: event.id,
|
|
1185
|
+
tone: "input",
|
|
1186
|
+
text: "Your previously approved workspace instruction was saved.",
|
|
1187
|
+
occurredAt: event.occurredAt,
|
|
1188
|
+
});
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
case "knowledge.confirmation.recovered":
|
|
1192
|
+
case "knowledge.source.prepared":
|
|
1193
|
+
case "knowledge.source.failed": {
|
|
1194
|
+
const receipt = asRecord(payload.receipt);
|
|
1195
|
+
const outcome = event.type === "knowledge.source.failed" ? "failed" : receipt.outcome;
|
|
1196
|
+
if (
|
|
1197
|
+
(event.type !== "knowledge.confirmation.recovered" &&
|
|
1198
|
+
typeof payload.fileId !== "string") ||
|
|
1199
|
+
!["published", "pending", "rejected", "archived", "failed"].includes(String(outcome))
|
|
1200
|
+
)
|
|
1201
|
+
break;
|
|
1202
|
+
closeStreamingTail();
|
|
1203
|
+
items.push({
|
|
1204
|
+
kind: "knowledge",
|
|
1205
|
+
id: event.id,
|
|
1206
|
+
turnId,
|
|
1207
|
+
fileId: typeof payload.fileId === "string" ? payload.fileId : undefined,
|
|
1208
|
+
filename: typeof payload.filename === "string" ? payload.filename : undefined,
|
|
1209
|
+
entryId: typeof receipt.entryId === "string" ? receipt.entryId : undefined,
|
|
1210
|
+
status: outcome === "failed" ? "failed" : "complete",
|
|
1211
|
+
outcome: outcome as "published" | "pending" | "rejected" | "archived" | "failed",
|
|
1212
|
+
occurredAt: event.occurredAt,
|
|
1213
|
+
});
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1178
1217
|
case "memory.saved":
|
|
1179
1218
|
case "memory.corrected": {
|
|
1180
1219
|
// A first-party memory write is a discrete step, not streamed text, so it
|
|
@@ -1517,6 +1556,7 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
|
|
|
1517
1556
|
case "sandbox":
|
|
1518
1557
|
case "startup-phase":
|
|
1519
1558
|
case "memory":
|
|
1559
|
+
case "knowledge":
|
|
1520
1560
|
case "fleet-decision":
|
|
1521
1561
|
return true;
|
|
1522
1562
|
default:
|
|
@@ -7,6 +7,18 @@ type RetainedImageState =
|
|
|
7
7
|
| { kind: "unavailable"; label: string }
|
|
8
8
|
| { kind: "error"; message: string };
|
|
9
9
|
|
|
10
|
+
/** Shared browser preview allowlist; other published files remain downloads. */
|
|
11
|
+
export function isRetainedImageContentType(contentType: string): boolean {
|
|
12
|
+
return [
|
|
13
|
+
"image/png",
|
|
14
|
+
"image/jpeg",
|
|
15
|
+
"image/gif",
|
|
16
|
+
"image/webp",
|
|
17
|
+
"image/avif",
|
|
18
|
+
"image/svg+xml",
|
|
19
|
+
].includes(contentType);
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
export function useRetainedImageObjectUrl(
|
|
11
23
|
artifact: RetainedArtifactReference,
|
|
12
24
|
load: ToolRendererProps["loadRetainedArtifact"],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { KnowledgeReceiptRow } from "./knowledge-receipt";
|
|
2
|
+
import { isRetainedImageContentType, useRetainedImageObjectUrl } from "./retained-image";
|
|
2
3
|
import {
|
|
3
4
|
parseSandboxFileArtifactReceipt,
|
|
4
5
|
type GitFileDiff,
|
|
@@ -782,9 +783,12 @@ function RetainedSessionImageDisclosure({
|
|
|
782
783
|
batched,
|
|
783
784
|
failed,
|
|
784
785
|
cancelled,
|
|
786
|
+
filename,
|
|
787
|
+
defaultOpen,
|
|
788
|
+
children,
|
|
785
789
|
}: {
|
|
786
790
|
artifact: RetainedArtifactReference;
|
|
787
|
-
load: ToolRendererProps["
|
|
791
|
+
load: ToolRendererProps["loadRetainedArtifact"];
|
|
788
792
|
title: string;
|
|
789
793
|
caption: string;
|
|
790
794
|
noun: "image" | "screenshot";
|
|
@@ -793,15 +797,19 @@ function RetainedSessionImageDisclosure({
|
|
|
793
797
|
batched: string | null;
|
|
794
798
|
failed: boolean;
|
|
795
799
|
cancelled: boolean;
|
|
800
|
+
filename?: string;
|
|
801
|
+
defaultOpen?: boolean;
|
|
802
|
+
children?: ReactNode;
|
|
796
803
|
}) {
|
|
797
804
|
const state = useRetainedImageObjectUrl(artifact, load);
|
|
798
|
-
const downloadFilename = retainedImageFilename(artifact);
|
|
805
|
+
const downloadFilename = filename ?? retainedImageFilename(artifact);
|
|
799
806
|
|
|
800
807
|
return (
|
|
801
808
|
<ActivityDisclosure
|
|
802
809
|
icon={icon}
|
|
803
810
|
iconTone={failed ? "failed" : state.kind === "ready" ? "accent" : "muted"}
|
|
804
811
|
title={title}
|
|
812
|
+
defaultOpen={defaultOpen}
|
|
805
813
|
failed={failed}
|
|
806
814
|
cancelled={cancelled}
|
|
807
815
|
preview={
|
|
@@ -851,6 +859,7 @@ function RetainedSessionImageDisclosure({
|
|
|
851
859
|
</BodyNote>
|
|
852
860
|
)}
|
|
853
861
|
{batched ? <BodyNote>batched: {batched}</BodyNote> : null}
|
|
862
|
+
{children}
|
|
854
863
|
</ActivityDisclosure>
|
|
855
864
|
);
|
|
856
865
|
}
|
|
@@ -944,6 +953,22 @@ function SandboxFilePublishRenderer({ item, loadRetainedArtifact }: ToolRenderer
|
|
|
944
953
|
return <GenericRenderer item={item} />;
|
|
945
954
|
}
|
|
946
955
|
|
|
956
|
+
// The closed SDK receipt has already checked the workspace-qualified route.
|
|
957
|
+
const workspaceId = /^\/v1\/workspaces\/([0-9a-f-]+)\/artifacts\//.exec(
|
|
958
|
+
receipt.artifact.retrieval.path,
|
|
959
|
+
)?.[1];
|
|
960
|
+
const openLink = workspaceId ? (
|
|
961
|
+
<a
|
|
962
|
+
href={`/workspaces/${workspaceId}/artifacts/files/${receipt.artifact.artifactId}`}
|
|
963
|
+
aria-label={`Open ${receipt.filename} in Artifacts`}
|
|
964
|
+
className="inline-flex min-h-7 items-center rounded-og-sm px-2 text-og-sm font-medium text-og-accent-strong hover:bg-og-surface-2 hover:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-10"
|
|
965
|
+
onClick={(event) => event.stopPropagation()}
|
|
966
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
967
|
+
>
|
|
968
|
+
Open in Artifacts
|
|
969
|
+
</a>
|
|
970
|
+
) : null;
|
|
971
|
+
|
|
947
972
|
const download = async () => {
|
|
948
973
|
if (!loadRetainedArtifact) {
|
|
949
974
|
setDownloadState("error");
|
|
@@ -976,6 +1001,44 @@ function SandboxFilePublishRenderer({ item, loadRetainedArtifact }: ToolRenderer
|
|
|
976
1001
|
}
|
|
977
1002
|
};
|
|
978
1003
|
|
|
1004
|
+
const downloadButton = (
|
|
1005
|
+
<button
|
|
1006
|
+
type="button"
|
|
1007
|
+
onClick={() => void download()}
|
|
1008
|
+
disabled={downloadState === "loading"}
|
|
1009
|
+
className="inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1.5 text-og-sm font-medium text-og-fg transition-colors hover:border-og-border-strong hover:bg-og-surface-2 disabled:cursor-wait disabled:opacity-60"
|
|
1010
|
+
>
|
|
1011
|
+
<DownloadIcon className="size-3.5" />
|
|
1012
|
+
{downloadState === "loading"
|
|
1013
|
+
? "Preparing…"
|
|
1014
|
+
: downloadState === "error"
|
|
1015
|
+
? "Retry download"
|
|
1016
|
+
: "Download"}
|
|
1017
|
+
</button>
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
if (isRetainedImageContentType(receipt.artifact.contentType)) {
|
|
1021
|
+
return (
|
|
1022
|
+
<RetainedSessionImageDisclosure
|
|
1023
|
+
artifact={receipt.artifact}
|
|
1024
|
+
load={loadRetainedArtifact}
|
|
1025
|
+
title={`Published ${receipt.filename}`}
|
|
1026
|
+
caption={receipt.filename}
|
|
1027
|
+
noun="image"
|
|
1028
|
+
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1029
|
+
lightboxLabel="Image"
|
|
1030
|
+
batched={null}
|
|
1031
|
+
failed={false}
|
|
1032
|
+
cancelled={false}
|
|
1033
|
+
filename={receipt.filename}
|
|
1034
|
+
defaultOpen
|
|
1035
|
+
>
|
|
1036
|
+
{downloadButton}
|
|
1037
|
+
{openLink}
|
|
1038
|
+
</RetainedSessionImageDisclosure>
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
979
1042
|
return (
|
|
980
1043
|
<ActivityDisclosure
|
|
981
1044
|
icon={<DownloadIcon className={ICON_SIZE} />}
|
|
@@ -984,19 +1047,8 @@ function SandboxFilePublishRenderer({ item, loadRetainedArtifact }: ToolRenderer
|
|
|
984
1047
|
defaultOpen
|
|
985
1048
|
preview={formatBytes(receipt.artifact.originalBytes)}
|
|
986
1049
|
>
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
onClick={() => void download()}
|
|
990
|
-
disabled={downloadState === "loading"}
|
|
991
|
-
className="inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1.5 text-og-sm font-medium text-og-fg transition-colors hover:border-og-border-strong hover:bg-og-surface-2 disabled:cursor-wait disabled:opacity-60"
|
|
992
|
-
>
|
|
993
|
-
<DownloadIcon className="size-3.5" />
|
|
994
|
-
{downloadState === "loading"
|
|
995
|
-
? "Preparing…"
|
|
996
|
-
: downloadState === "error"
|
|
997
|
-
? "Retry download"
|
|
998
|
-
: "Download"}
|
|
999
|
-
</button>
|
|
1050
|
+
{downloadButton}
|
|
1051
|
+
{openLink}
|
|
1000
1052
|
</ActivityDisclosure>
|
|
1001
1053
|
);
|
|
1002
1054
|
}
|
|
@@ -2286,7 +2338,54 @@ function GenericToolIcon({ name }: { name: string }) {
|
|
|
2286
2338
|
|
|
2287
2339
|
/* ---- the default registry -------------------------------------------------- */
|
|
2288
2340
|
|
|
2341
|
+
function KnowledgeSaveRenderer({ item }: ToolRendererProps) {
|
|
2342
|
+
const output = unwrapMcpOutput(item.output);
|
|
2343
|
+
const parsed = tryParseJson(output.text);
|
|
2344
|
+
if (item.status === "running" || output.isError || !parsed || typeof parsed !== "object")
|
|
2345
|
+
return <GenericRenderer item={item} />;
|
|
2346
|
+
const value = parsed as Record<string, unknown>;
|
|
2347
|
+
const receipt = (value.status === "retained" ? value.receipt : value) as
|
|
2348
|
+
| Record<string, unknown>
|
|
2349
|
+
| undefined;
|
|
2350
|
+
if (
|
|
2351
|
+
!receipt ||
|
|
2352
|
+
typeof receipt.entryId !== "string" ||
|
|
2353
|
+
!["published", "pending", "rejected", "archived"].includes(String(receipt.outcome))
|
|
2354
|
+
)
|
|
2355
|
+
return <GenericRenderer item={item} />;
|
|
2356
|
+
const args = parseToolArgs(item.arguments);
|
|
2357
|
+
const entry = args.entry as Record<string, unknown> | undefined;
|
|
2358
|
+
return (
|
|
2359
|
+
<KnowledgeReceiptRow
|
|
2360
|
+
outcome={receipt.outcome as "published" | "pending" | "rejected" | "archived"}
|
|
2361
|
+
entryId={receipt.entryId}
|
|
2362
|
+
title={
|
|
2363
|
+
typeof entry?.title === "string"
|
|
2364
|
+
? entry.title
|
|
2365
|
+
: typeof value.filename === "string"
|
|
2366
|
+
? value.filename
|
|
2367
|
+
: undefined
|
|
2368
|
+
}
|
|
2369
|
+
source={value.status === "retained" || value.retained === true}
|
|
2370
|
+
/>
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2289
2374
|
const BASE_ENTRIES: ToolRegistryEntry[] = [
|
|
2375
|
+
...[
|
|
2376
|
+
"knowledge_save",
|
|
2377
|
+
"knowledge_archive",
|
|
2378
|
+
"knowledge_retain_file",
|
|
2379
|
+
"knowledge_retain_message",
|
|
2380
|
+
"task_note_promote_knowledge",
|
|
2381
|
+
].flatMap((name) =>
|
|
2382
|
+
[name, `opengeni__${name}`, `mcp__opengeni__${name}`].map((trustedName) => ({
|
|
2383
|
+
match: "name" as const,
|
|
2384
|
+
name: trustedName,
|
|
2385
|
+
matchPrefixedLeaf: false,
|
|
2386
|
+
render: KnowledgeSaveRenderer,
|
|
2387
|
+
})),
|
|
2388
|
+
),
|
|
2290
2389
|
// Provider-native items carry `raw.type` on the wire — this is their source of
|
|
2291
2390
|
// truth and is consulted first by the registry.
|
|
2292
2391
|
{ match: "rawType", type: "apply_patch_call", render: ApplyPatchRenderer },
|
package/src/timeline/types.ts
CHANGED
|
@@ -228,6 +228,18 @@ export type StartupPhaseItem = {
|
|
|
228
228
|
* `onMemoryClick` handler the row also deep-links to the record in its memory
|
|
229
229
|
* pane; without one it is non-interactive rich content.
|
|
230
230
|
*/
|
|
231
|
+
export type KnowledgeItem = {
|
|
232
|
+
kind: "knowledge";
|
|
233
|
+
id: string;
|
|
234
|
+
turnId: string | null;
|
|
235
|
+
status: "complete" | "failed";
|
|
236
|
+
outcome: "published" | "pending" | "rejected" | "archived" | "failed";
|
|
237
|
+
fileId?: string | undefined;
|
|
238
|
+
filename?: string | undefined;
|
|
239
|
+
entryId?: string | undefined;
|
|
240
|
+
occurredAt: string;
|
|
241
|
+
};
|
|
242
|
+
|
|
231
243
|
export type MemoryItem = {
|
|
232
244
|
kind: "memory";
|
|
233
245
|
id: string;
|
|
@@ -478,6 +490,7 @@ export type TimelineItem =
|
|
|
478
490
|
| MachineInputBatchItem
|
|
479
491
|
| AuthNeededItem
|
|
480
492
|
| MemoryItem
|
|
493
|
+
| KnowledgeItem
|
|
481
494
|
| FleetDecisionItem
|
|
482
495
|
| TurnEndItem;
|
|
483
496
|
|
|
@@ -489,6 +502,7 @@ export type ActivityItem =
|
|
|
489
502
|
| SandboxItem
|
|
490
503
|
| StartupPhaseItem
|
|
491
504
|
| MemoryItem
|
|
505
|
+
| KnowledgeItem
|
|
492
506
|
| FleetDecisionItem;
|
|
493
507
|
|
|
494
508
|
export type TimelineGroup =
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/timeline/retained-image.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { RetainedArtifactReference } from \"@opengeni/sdk\";\nimport type { ToolRendererProps } from \"./registry\";\ntype RetainedImageState =\n | { kind: \"loading\" }\n | { kind: \"ready\"; url: string }\n | { kind: \"unavailable\"; label: string }\n | { kind: \"error\"; message: string };\n\nexport function useRetainedImageObjectUrl(\n artifact: RetainedArtifactReference,\n load: ToolRendererProps[\"loadRetainedArtifact\"],\n): RetainedImageState {\n const [state, setState] = useState<RetainedImageState>({ kind: \"loading\" });\n // Function outputs are commonly serialized JSON. Parsing them creates a new\n // object on every render, so depend on the immutable wire value rather than\n // object identity; otherwise the loader can refetch after its own setState.\n const artifactValue = retainedArtifactValue(artifact);\n const stableArtifactRef = useRef({ value: artifactValue, artifact });\n if (stableArtifactRef.current.value !== artifactValue) {\n stableArtifactRef.current = { value: artifactValue, artifact };\n }\n const stableArtifact = stableArtifactRef.current.artifact;\n useEffect(() => {\n if (!load) {\n setState({ kind: \"unavailable\", label: \"retrieval is not configured\" });\n return;\n }\n const controller = new AbortController();\n let objectUrl: string | null = null;\n setState({ kind: \"loading\" });\n void load(stableArtifact, controller.signal)\n .then((source) => {\n if (controller.signal.aborted) return;\n if (!source) {\n setState({ kind: \"unavailable\", label: \"bytes are unavailable\" });\n return;\n }\n if (!(source instanceof Uint8Array)) {\n if (!source.url.trim()) {\n setState({ kind: \"unavailable\", label: \"URL is unavailable\" });\n return;\n }\n setState({ kind: \"ready\", url: source.url });\n return;\n }\n objectUrl = URL.createObjectURL(\n new Blob([source as unknown as BlobPart], { type: stableArtifact.contentType }),\n );\n setState({ kind: \"ready\", url: objectUrl });\n })\n .catch((error: unknown) => {\n if (controller.signal.aborted) return;\n const status =\n error && typeof error === \"object\" && \"status\" in error\n ? Number((error as { status?: unknown }).status)\n : null;\n setState(\n status === 404\n ? { kind: \"unavailable\", label: \"deleted\" }\n : status === 410\n ? { kind: \"unavailable\", label: \"expired or unavailable\" }\n : { kind: \"error\", message: \"retrieval failed\" },\n );\n });\n return () => {\n controller.abort();\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [stableArtifact, load]);\n return state;\n}\n\nfunction retainedArtifactValue(artifact: RetainedArtifactReference): string {\n return [\n artifact.artifactId,\n artifact.kind,\n artifact.contentType,\n artifact.originalBytes,\n artifact.sha256,\n artifact.retainedAt,\n artifact.dimensions?.width ?? \"\",\n artifact.dimensions?.height ?? \"\",\n artifact.retention.policy,\n artifact.retention.expiresAt ?? \"\",\n artifact.retrieval.method,\n artifact.retrieval.path,\n artifact.retrieval.acceptRanges,\n artifact.retrieval.maxRangeBytes,\n ].join(\"\\0\");\n}\n"],"mappings":";AAAA,SAAS,WAAW,QAAQ,gBAAgB;AASrC,SAAS,0BACd,UACA,MACoB;AACpB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA6B,EAAE,MAAM,UAAU,CAAC;AAI1E,QAAM,gBAAgB,sBAAsB,QAAQ;AACpD,QAAM,oBAAoB,OAAO,EAAE,OAAO,eAAe,SAAS,CAAC;AACnE,MAAI,kBAAkB,QAAQ,UAAU,eAAe;AACrD,sBAAkB,UAAU,EAAE,OAAO,eAAe,SAAS;AAAA,EAC/D;AACA,QAAM,iBAAiB,kBAAkB,QAAQ;AACjD,YAAU,MAAM;AACd,QAAI,CAAC,MAAM;AACT,eAAS,EAAE,MAAM,eAAe,OAAO,8BAA8B,CAAC;AACtE;AAAA,IACF;AACA,UAAM,aAAa,IAAI,gBAAgB;AACvC,QAAI,YAA2B;AAC/B,aAAS,EAAE,MAAM,UAAU,CAAC;AAC5B,SAAK,KAAK,gBAAgB,WAAW,MAAM,EACxC,KAAK,CAAC,WAAW;AAChB,UAAI,WAAW,OAAO,QAAS;AAC/B,UAAI,CAAC,QAAQ;AACX,iBAAS,EAAE,MAAM,eAAe,OAAO,wBAAwB,CAAC;AAChE;AAAA,MACF;AACA,UAAI,EAAE,kBAAkB,aAAa;AACnC,YAAI,CAAC,OAAO,IAAI,KAAK,GAAG;AACtB,mBAAS,EAAE,MAAM,eAAe,OAAO,qBAAqB,CAAC;AAC7D;AAAA,QACF;AACA,iBAAS,EAAE,MAAM,SAAS,KAAK,OAAO,IAAI,CAAC;AAC3C;AAAA,MACF;AACA,kBAAY,IAAI;AAAA,QACd,IAAI,KAAK,CAAC,MAA6B,GAAG,EAAE,MAAM,eAAe,YAAY,CAAC;AAAA,MAChF;AACA,eAAS,EAAE,MAAM,SAAS,KAAK,UAAU,CAAC;AAAA,IAC5C,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI,WAAW,OAAO,QAAS;AAC/B,YAAM,SACJ,SAAS,OAAO,UAAU,YAAY,YAAY,QAC9C,OAAQ,MAA+B,MAAM,IAC7C;AACN;AAAA,QACE,WAAW,MACP,EAAE,MAAM,eAAe,OAAO,UAAU,IACxC,WAAW,MACT,EAAE,MAAM,eAAe,OAAO,yBAAyB,IACvD,EAAE,MAAM,SAAS,SAAS,mBAAmB;AAAA,MACrD;AAAA,IACF,CAAC;AACH,WAAO,MAAM;AACX,iBAAW,MAAM;AACjB,UAAI,UAAW,KAAI,gBAAgB,SAAS;AAAA,IAC9C;AAAA,EACF,GAAG,CAAC,gBAAgB,IAAI,CAAC;AACzB,SAAO;AACT;AAEA,SAAS,sBAAsB,UAA6C;AAC1E,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,YAAY,SAAS;AAAA,IAC9B,SAAS,YAAY,UAAU;AAAA,IAC/B,SAAS,UAAU;AAAA,IACnB,SAAS,UAAU,aAAa;AAAA,IAChC,SAAS,UAAU;AAAA,IACnB,SAAS,UAAU;AAAA,IACnB,SAAS,UAAU;AAAA,IACnB,SAAS,UAAU;AAAA,EACrB,EAAE,KAAK,IAAI;AACb;","names":[]}
|