@opengeni/react 0.13.0 → 0.20.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 +129 -23
- package/dist/chunk-6BXBGJ3B.js +1956 -0
- package/dist/chunk-6BXBGJ3B.js.map +1 -0
- package/dist/chunk-6UT5OC3P.js +2133 -0
- package/dist/chunk-6UT5OC3P.js.map +1 -0
- package/dist/chunk-EBTT3MYN.js +92 -0
- package/dist/chunk-EBTT3MYN.js.map +1 -0
- package/dist/chunk-HHFA4AFX.js +425 -0
- package/dist/chunk-HHFA4AFX.js.map +1 -0
- package/dist/chunk-LAZ2A743.js +2393 -0
- package/dist/chunk-LAZ2A743.js.map +1 -0
- package/dist/chunk-MRSECXRP.js +563 -0
- package/dist/chunk-MRSECXRP.js.map +1 -0
- package/dist/chunk-TK7G6XLT.js +18 -0
- package/dist/chunk-TK7G6XLT.js.map +1 -0
- package/dist/composer-DvUaa5ki.d.ts +585 -0
- package/dist/composer.d.ts +6 -0
- package/dist/composer.js +56 -0
- package/dist/composer.js.map +1 -0
- package/dist/index.d.ts +642 -1080
- package/dist/index.js +7672 -8005
- package/dist/index.js.map +1 -1
- package/dist/machines.d.ts +412 -3
- package/dist/machines.js +25 -1
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
- package/dist/queue-surface-implementation-NQMV2ML3.js +890 -0
- package/dist/queue-surface-implementation-NQMV2ML3.js.map +1 -0
- package/dist/session-Gd4iVPYw.d.ts +425 -0
- package/dist/session-context-DIFFlXKc.d.ts +52 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +50 -0
- package/dist/session.js.map +1 -0
- package/dist/use-composer-BCfm4mzX.d.ts +112 -0
- package/package.json +23 -4
- package/src/approvals.ts +5 -3
- package/src/client.ts +50 -1
- package/src/components/approval-surface.tsx +198 -0
- package/src/components/chat-composer.tsx +104 -751
- package/src/components/code-editor.tsx +104 -26
- package/src/components/command-palette.tsx +8 -2
- package/src/components/composer-transcription-control.tsx +211 -0
- package/src/components/composer.tsx +1536 -0
- package/src/components/desktop-viewer.tsx +3 -3
- package/src/components/enrollment-consent.tsx +2 -2
- package/src/components/file-browser.tsx +367 -29
- package/src/components/human-input-form.tsx +417 -0
- package/src/components/machine-card.tsx +82 -16
- package/src/components/machine-health-pill.tsx +68 -0
- package/src/components/machine-metrics.tsx +10 -24
- package/src/components/machines/health.ts +146 -0
- package/src/components/machines/machine-detail.tsx +220 -0
- package/src/components/machines/metric-history-chart.tsx +298 -0
- package/src/components/machines/metric-sparkline.tsx +76 -0
- package/src/components/machines/series.ts +113 -0
- package/src/components/machines-dashboard.tsx +16 -4
- package/src/components/message-timeline.tsx +15 -7
- package/src/components/model-picker.tsx +12 -3
- package/src/components/pierre-diff.tsx +32 -6
- package/src/components/queue-surface-implementation.tsx +1041 -0
- package/src/components/queue-surface-state.tsx +94 -0
- package/src/components/queue-surface.tsx +60 -0
- package/src/components/sandbox-files.tsx +230 -21
- package/src/components/sandbox-terminal.tsx +8 -2
- package/src/components/sandbox-workspace.tsx +489 -135
- package/src/components/session-status.tsx +0 -6
- package/src/components/workbench-changes.tsx +145 -50
- package/src/components/workspace-dock.tsx +329 -68
- package/src/composer.ts +60 -0
- package/src/hooks/internal.ts +115 -34
- package/src/hooks/use-composer.ts +635 -74
- package/src/hooks/use-human-input.ts +131 -0
- package/src/hooks/use-machine-chip.ts +2 -2
- package/src/hooks/use-machines.ts +27 -11
- package/src/hooks/use-sandbox-files.ts +310 -62
- package/src/hooks/use-sandbox-git.ts +154 -40
- package/src/hooks/use-sandbox-terminal.ts +28 -6
- package/src/hooks/use-session-capabilities.ts +38 -9
- package/src/hooks/use-session-control.ts +43 -15
- package/src/hooks/use-session-events.ts +523 -48
- package/src/hooks/use-session-lineage.ts +15 -6
- package/src/hooks/use-session.ts +10 -2
- package/src/hooks/use-slash-commands.ts +38 -38
- package/src/hooks/use-transcription.ts +757 -0
- package/src/hooks/use-turn-queue.ts +273 -77
- package/src/hooks/use-windowed-sections.ts +2 -2
- package/src/hooks/use-workspace-capture.ts +146 -40
- package/src/hooks/use-workspace-edit.ts +50 -14
- package/src/hooks/use-workspace-sessions.ts +3 -0
- package/src/human-input.ts +72 -0
- package/src/index.ts +88 -12
- package/src/lib/noto-sans-arabic-loader.ts +21 -0
- package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
- package/src/lib/use-portal-token-style.ts +51 -0
- package/src/lib/use-unicode-fonts.ts +57 -0
- package/src/machines.ts +16 -0
- package/src/provider.tsx +163 -41
- package/src/session-context.ts +135 -0
- package/src/session.ts +79 -0
- package/src/timeline/parsers.ts +78 -6
- package/src/timeline/projection.ts +36 -6
- package/src/timeline/tool-renderers.tsx +41 -0
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +16 -8
- package/styles/index.css +78 -0
- package/styles/index.d.ts +2 -0
- package/styles/tokens.css +11 -6
- package/styles/tokens.d.ts +2 -0
- package/dist/chunk-NFYVQWIB.js +0 -1377
- package/dist/chunk-NFYVQWIB.js.map +0 -1
- package/dist/machines-CnlMb7E-.d.ts +0 -329
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { StreamConnectionState, WorkspaceControlEvent } from "@opengeni/sdk";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import type {
|
|
4
|
+
EmbeddedHumanInputSessionClientLike,
|
|
5
|
+
EmbeddedSessionClientLike,
|
|
6
|
+
SessionClientLike,
|
|
7
|
+
} from "./client";
|
|
8
|
+
|
|
9
|
+
export type OpenGeniContextValue = {
|
|
10
|
+
client: SessionClientLike;
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
workspaceControlEvent: WorkspaceControlEvent | null;
|
|
13
|
+
workspaceControlConnectionState: StreamConnectionState | "idle" | "error";
|
|
14
|
+
registerSessionReconciler: (
|
|
15
|
+
sessionId: string,
|
|
16
|
+
key: string,
|
|
17
|
+
reconcile: () => Promise<void>,
|
|
18
|
+
) => () => void;
|
|
19
|
+
reconcileSession: (sessionId: string) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const OpenGeniContext = createContext<OpenGeniContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
const NOOP_REGISTER_RECONCILER: OpenGeniContextValue["registerSessionReconciler"] = () => () =>
|
|
25
|
+
undefined;
|
|
26
|
+
const NOOP_RECONCILE_SESSION: OpenGeniContextValue["reconcileSession"] = async () => undefined;
|
|
27
|
+
|
|
28
|
+
export type ClientOverride = {
|
|
29
|
+
client?: SessionClientLike | undefined;
|
|
30
|
+
workspaceId?: string | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type EmbeddedSessionClientOverride = {
|
|
34
|
+
client?: EmbeddedSessionClientLike | undefined;
|
|
35
|
+
workspaceId?: string | undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type EmbeddedHumanInputClientOverride = {
|
|
39
|
+
client?: EmbeddedHumanInputSessionClientLike | undefined;
|
|
40
|
+
workspaceId?: string | undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type EmbeddedSessionContextValue = Omit<OpenGeniContextValue, "client"> & {
|
|
44
|
+
client: EmbeddedSessionClientLike;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Resolve client + workspace from explicit overrides or the provider. */
|
|
48
|
+
export function useOpenGeni(override: ClientOverride = {}): OpenGeniContextValue {
|
|
49
|
+
const context = useContext(OpenGeniContext);
|
|
50
|
+
const client = override.client ?? context?.client;
|
|
51
|
+
const workspaceId = override.workspaceId ?? context?.workspaceId;
|
|
52
|
+
if (!client || !workspaceId) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
client,
|
|
59
|
+
workspaceId,
|
|
60
|
+
workspaceControlEvent: context?.workspaceControlEvent ?? null,
|
|
61
|
+
workspaceControlConnectionState: context?.workspaceControlConnectionState ?? "idle",
|
|
62
|
+
registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,
|
|
63
|
+
reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the narrow client required by the session-only hooks. The full
|
|
69
|
+
* provider client is structurally compatible, while an explicit host proxy
|
|
70
|
+
* only needs to expose session/event/composer/queue/control operations.
|
|
71
|
+
*/
|
|
72
|
+
export function useEmbeddedSession(
|
|
73
|
+
override: EmbeddedSessionClientOverride = {},
|
|
74
|
+
): EmbeddedSessionContextValue {
|
|
75
|
+
const context = useContext(OpenGeniContext);
|
|
76
|
+
const client = override.client ?? context?.client;
|
|
77
|
+
const workspaceId = override.workspaceId ?? context?.workspaceId;
|
|
78
|
+
if (!client || !workspaceId) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
client,
|
|
85
|
+
workspaceId,
|
|
86
|
+
workspaceControlEvent: context?.workspaceControlEvent ?? null,
|
|
87
|
+
workspaceControlConnectionState: context?.workspaceControlConnectionState ?? "idle",
|
|
88
|
+
registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,
|
|
89
|
+
reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the structured-input refinement without widening the baseline
|
|
95
|
+
* session-only proxy contract.
|
|
96
|
+
*/
|
|
97
|
+
export function useEmbeddedHumanInputSession(override: EmbeddedHumanInputClientOverride = {}): Omit<
|
|
98
|
+
EmbeddedSessionContextValue,
|
|
99
|
+
"client"
|
|
100
|
+
> & {
|
|
101
|
+
client: EmbeddedHumanInputSessionClientLike;
|
|
102
|
+
} {
|
|
103
|
+
const embedded = useEmbeddedSession(override);
|
|
104
|
+
const client = embedded.client as Partial<EmbeddedHumanInputSessionClientLike>;
|
|
105
|
+
if (
|
|
106
|
+
typeof client.listHumanInputRequests !== "function" ||
|
|
107
|
+
typeof client.getHumanInputRequest !== "function" ||
|
|
108
|
+
typeof client.submitHumanInputResponse !== "function"
|
|
109
|
+
) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"@opengeni/react: useHumanInputRequests requires listHumanInputRequests, getHumanInputRequest, and submitHumanInputResponse.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
...embedded,
|
|
116
|
+
client: client as EmbeddedHumanInputSessionClientLike,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the client only — for hooks that are not workspace-scoped
|
|
122
|
+
* (`useWorkspaces`, `useBillingUsage`).
|
|
123
|
+
*/
|
|
124
|
+
export function useOpenGeniClient(
|
|
125
|
+
override: Pick<ClientOverride, "client"> = {},
|
|
126
|
+
): SessionClientLike {
|
|
127
|
+
const context = useContext(OpenGeniContext);
|
|
128
|
+
const client = override.client ?? context?.client;
|
|
129
|
+
if (!client) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"@opengeni/react: no OpenGeni client available. Wrap the tree in <OpenGeniProvider> or pass { client } to the hook.",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return client;
|
|
135
|
+
}
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// @opengeni/react/session — session state and pure timeline projection.
|
|
2
|
+
//
|
|
3
|
+
// This entry deliberately excludes styled components, workbench surfaces, CSS,
|
|
4
|
+
// and their optional peers. Hosts keep OpenGeni's session semantics while
|
|
5
|
+
// rendering their own product UI.
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
EmbeddedHumanInputSessionClientLike as HumanInputSessionClientLike,
|
|
9
|
+
EmbeddedSessionClientLike as SessionClientLike,
|
|
10
|
+
} from "./client";
|
|
11
|
+
export type {
|
|
12
|
+
EmbeddedHumanInputClientOverride as HumanInputClientOverride,
|
|
13
|
+
EmbeddedSessionClientOverride as ClientOverride,
|
|
14
|
+
} from "./session-context";
|
|
15
|
+
|
|
16
|
+
export { useSessionEvents } from "./hooks/use-session-events";
|
|
17
|
+
export type {
|
|
18
|
+
SessionEventsConnectionState,
|
|
19
|
+
UseSessionEventsOptions,
|
|
20
|
+
UseSessionEventsResult,
|
|
21
|
+
} from "./hooks/use-session-events";
|
|
22
|
+
export {
|
|
23
|
+
useComposer,
|
|
24
|
+
composeSendInput,
|
|
25
|
+
shouldSteerOnKey,
|
|
26
|
+
shouldSubmitOnKey,
|
|
27
|
+
FILE_ONLY_MESSAGE_TEXT,
|
|
28
|
+
} from "./hooks/use-composer";
|
|
29
|
+
export type { ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
|
|
30
|
+
export { useTurnQueue, isTurnQueueEvent } from "./hooks/use-turn-queue";
|
|
31
|
+
export type {
|
|
32
|
+
QueueMutationKind,
|
|
33
|
+
UseTurnQueueOptions,
|
|
34
|
+
UseTurnQueueResult,
|
|
35
|
+
} from "./hooks/use-turn-queue";
|
|
36
|
+
export { useSessionControl } from "./hooks/use-session-control";
|
|
37
|
+
export type {
|
|
38
|
+
UseSessionControlOptions,
|
|
39
|
+
UseSessionControlResult,
|
|
40
|
+
} from "./hooks/use-session-control";
|
|
41
|
+
|
|
42
|
+
export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
|
|
43
|
+
export type { PendingApproval } from "./approvals";
|
|
44
|
+
|
|
45
|
+
export { humanInputRequestFromEvent, projectPendingHumanInputRequests } from "./human-input";
|
|
46
|
+
export type { PendingHumanInputRequest } from "./human-input";
|
|
47
|
+
export { isHumanInputEvent, useHumanInputRequests } from "./hooks/use-human-input";
|
|
48
|
+
export type {
|
|
49
|
+
UseHumanInputRequestsOptions,
|
|
50
|
+
UseHumanInputRequestsResult,
|
|
51
|
+
} from "./hooks/use-human-input";
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
buildTimeline,
|
|
55
|
+
creditExhaustedFromEvents,
|
|
56
|
+
extractSessionRef,
|
|
57
|
+
groupTimeline,
|
|
58
|
+
sessionStatusFromEvents,
|
|
59
|
+
toolDisplayName,
|
|
60
|
+
} from "./timeline/projection";
|
|
61
|
+
export type {
|
|
62
|
+
ActivityItem,
|
|
63
|
+
AgentMessageItem,
|
|
64
|
+
AuthNeededItem,
|
|
65
|
+
GoalItem,
|
|
66
|
+
MemoryItem,
|
|
67
|
+
NoticeItem,
|
|
68
|
+
ReasoningItem,
|
|
69
|
+
SandboxItem,
|
|
70
|
+
SessionStatusItem,
|
|
71
|
+
TimelineGroup,
|
|
72
|
+
TimelineItem,
|
|
73
|
+
TurnOutcome,
|
|
74
|
+
ToolCallItem,
|
|
75
|
+
TurnEndItem,
|
|
76
|
+
UserMessageItem,
|
|
77
|
+
WorkerCompletionItem,
|
|
78
|
+
WorkerItem,
|
|
79
|
+
} from "./timeline/types";
|
package/src/timeline/parsers.ts
CHANGED
|
@@ -148,17 +148,34 @@ export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
|
|
|
148
148
|
// `@@ -0,0 +1,N @@` from the range fields once newLines is counted — a
|
|
149
149
|
// pre-baked partial header (e.g. `@@ +1 @@`) renders zero lines in a
|
|
150
150
|
// generic unified-diff parser.
|
|
151
|
-
cur = {
|
|
151
|
+
cur = {
|
|
152
|
+
oldStart: 0,
|
|
153
|
+
oldLines: 0,
|
|
154
|
+
newStart: 1,
|
|
155
|
+
newLines: 0,
|
|
156
|
+
header: "",
|
|
157
|
+
lines: [],
|
|
158
|
+
};
|
|
152
159
|
hunks.push(cur);
|
|
153
160
|
oldNo = 0;
|
|
154
161
|
newNo = 1;
|
|
155
162
|
}
|
|
156
163
|
if (raw.startsWith("+")) {
|
|
157
|
-
cur.lines.push({
|
|
164
|
+
cur.lines.push({
|
|
165
|
+
type: "add",
|
|
166
|
+
oldNo: null,
|
|
167
|
+
newNo: newNo++,
|
|
168
|
+
text: raw.slice(1),
|
|
169
|
+
});
|
|
158
170
|
cur.newLines += 1;
|
|
159
171
|
additions += 1;
|
|
160
172
|
} else if (raw.startsWith("-")) {
|
|
161
|
-
cur.lines.push({
|
|
173
|
+
cur.lines.push({
|
|
174
|
+
type: "del",
|
|
175
|
+
oldNo: oldNo++,
|
|
176
|
+
newNo: null,
|
|
177
|
+
text: raw.slice(1),
|
|
178
|
+
});
|
|
162
179
|
cur.oldLines += 1;
|
|
163
180
|
deletions += 1;
|
|
164
181
|
} else {
|
|
@@ -200,7 +217,10 @@ export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
|
|
|
200
217
|
* renderer and the turn-summary facet counter never drift.
|
|
201
218
|
*/
|
|
202
219
|
export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
|
|
203
|
-
const r = (raw ?? {}) as {
|
|
220
|
+
const r = (raw ?? {}) as {
|
|
221
|
+
operation?: ApplyPatchOperation;
|
|
222
|
+
operations?: ApplyPatchOperation[];
|
|
223
|
+
};
|
|
204
224
|
if (Array.isArray(r.operations)) {
|
|
205
225
|
return r.operations;
|
|
206
226
|
}
|
|
@@ -264,7 +284,21 @@ export function tailPeek(text: string): string {
|
|
|
264
284
|
* into a flat `{ text, isError }`. Non-MCP outputs pass through as their string
|
|
265
285
|
* form.
|
|
266
286
|
*/
|
|
267
|
-
export function unwrapMcpOutput(output: unknown): {
|
|
287
|
+
export function unwrapMcpOutput(output: unknown): {
|
|
288
|
+
text: string;
|
|
289
|
+
isError: boolean;
|
|
290
|
+
} {
|
|
291
|
+
// Managed MCP events persist a normalized single text part as
|
|
292
|
+
// `{ type: "text", text }`; accept it alongside the SDK-native content array.
|
|
293
|
+
if (
|
|
294
|
+
output &&
|
|
295
|
+
typeof output === "object" &&
|
|
296
|
+
(output as { type?: unknown }).type === "text" &&
|
|
297
|
+
typeof (output as { text?: unknown }).text === "string"
|
|
298
|
+
) {
|
|
299
|
+
const record = output as { text: string; isError?: unknown };
|
|
300
|
+
return { text: record.text, isError: Boolean(record.isError) };
|
|
301
|
+
}
|
|
268
302
|
if (output && typeof output === "object" && "content" in output) {
|
|
269
303
|
const record = output as { content?: unknown; isError?: unknown };
|
|
270
304
|
const isError = Boolean(record.isError);
|
|
@@ -273,7 +307,10 @@ export function unwrapMcpOutput(output: unknown): { text: string; isError: boole
|
|
|
273
307
|
(part): part is { type: string; text: string } =>
|
|
274
308
|
!!part && typeof part === "object" && (part as { type?: unknown }).type === "text",
|
|
275
309
|
);
|
|
276
|
-
return {
|
|
310
|
+
return {
|
|
311
|
+
text: textPart ? String(textPart.text) : JSON.stringify(output),
|
|
312
|
+
isError,
|
|
313
|
+
};
|
|
277
314
|
}
|
|
278
315
|
return { text: JSON.stringify(output), isError };
|
|
279
316
|
}
|
|
@@ -350,6 +387,41 @@ export function screenshotDataUrl(out: unknown): string | null {
|
|
|
350
387
|
return null;
|
|
351
388
|
}
|
|
352
389
|
|
|
390
|
+
export type TimelineMediaPreview = {
|
|
391
|
+
type: "media_preview";
|
|
392
|
+
mediaType: string;
|
|
393
|
+
inlineBytes: number | null;
|
|
394
|
+
fullOutputAvailable: false;
|
|
395
|
+
preview: string;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/** Find the explicit non-retained inline-media fact in a tool output. */
|
|
399
|
+
export function mediaPreviewFact(out: unknown): TimelineMediaPreview | null {
|
|
400
|
+
if (typeof out === "string" && (out.startsWith("{") || out.startsWith("["))) {
|
|
401
|
+
const parsed = tryParseJson(out);
|
|
402
|
+
if (parsed !== undefined && parsed !== out) return mediaPreviewFact(parsed);
|
|
403
|
+
}
|
|
404
|
+
if (Array.isArray(out)) {
|
|
405
|
+
for (const entry of out) {
|
|
406
|
+
const preview = mediaPreviewFact(entry);
|
|
407
|
+
if (preview) return preview;
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
if (!out || typeof out !== "object") return null;
|
|
412
|
+
const record = out as Record<string, unknown>;
|
|
413
|
+
if (
|
|
414
|
+
record.type !== "media_preview" ||
|
|
415
|
+
typeof record.mediaType !== "string" ||
|
|
416
|
+
record.fullOutputAvailable !== false ||
|
|
417
|
+
typeof record.preview !== "string" ||
|
|
418
|
+
(record.inlineBytes !== null && typeof record.inlineBytes !== "number")
|
|
419
|
+
) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
return record as TimelineMediaPreview;
|
|
423
|
+
}
|
|
424
|
+
|
|
353
425
|
/** Serialize whatever a Uint8Array became in JSON (number[], {"0":n,…} index
|
|
354
426
|
* map, or Buffer-JSON {type:"Buffer",data:[…]}) back into base64. */
|
|
355
427
|
function bytesToBase64(data: unknown): string | null {
|
|
@@ -393,7 +393,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
393
393
|
tone: "waiting",
|
|
394
394
|
text:
|
|
395
395
|
before && after
|
|
396
|
-
? `
|
|
396
|
+
? `Active conversation history compacted from approximately ${before} to ${after} tokens.`
|
|
397
397
|
: "Context compacted so the turn could continue.",
|
|
398
398
|
occurredAt: event.occurredAt,
|
|
399
399
|
});
|
|
@@ -406,13 +406,17 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
406
406
|
items.push({
|
|
407
407
|
kind: "notice",
|
|
408
408
|
id: event.id,
|
|
409
|
-
tone: "waiting",
|
|
409
|
+
tone: reason === "summarization_failed" ? "failed" : "waiting",
|
|
410
410
|
text:
|
|
411
411
|
reason === "no_history"
|
|
412
412
|
? "Context compaction skipped because there is no active history to compact."
|
|
413
413
|
: reason === "replacement_not_smaller"
|
|
414
414
|
? "Context compaction skipped because the generated checkpoint would not reduce the context."
|
|
415
|
-
:
|
|
415
|
+
: reason === "replacement_unchanged"
|
|
416
|
+
? "Context compaction stopped because it reproduced the same checkpoint without making progress."
|
|
417
|
+
: reason === "summarization_failed"
|
|
418
|
+
? "Context compaction failed without replacing the active conversation history. Request it again to retry."
|
|
419
|
+
: "Context compaction was not needed.",
|
|
416
420
|
occurredAt: event.occurredAt,
|
|
417
421
|
});
|
|
418
422
|
break;
|
|
@@ -432,7 +436,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
432
436
|
break;
|
|
433
437
|
}
|
|
434
438
|
|
|
435
|
-
case "tool.auth_needed":
|
|
439
|
+
case "tool.auth_needed":
|
|
440
|
+
case "credential.auth_needed": {
|
|
436
441
|
// Keep the whole structured payload — the renderer turns it into a clean
|
|
437
442
|
// inline reconnect card, and the app starts the recovery flow off the
|
|
438
443
|
// connectionId/resource. Losing it to a plain-text notice was the ugly
|
|
@@ -708,13 +713,13 @@ function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
|
|
|
708
713
|
cancelledTurnIds.add(turnId);
|
|
709
714
|
}
|
|
710
715
|
|
|
711
|
-
if (turnId && event.type
|
|
716
|
+
if (turnId && isTurnExecutionEvidence(event.type)) {
|
|
712
717
|
const previous = fallbackSeqByTurn.get(turnId);
|
|
713
718
|
if (previous === undefined || event.sequence < previous) {
|
|
714
719
|
fallbackSeqByTurn.set(turnId, event.sequence);
|
|
715
720
|
}
|
|
716
721
|
}
|
|
717
|
-
if (turnId &&
|
|
722
|
+
if (turnId && isTurnExecutionEvidence(event.type)) {
|
|
718
723
|
startedTurnIds.add(turnId);
|
|
719
724
|
}
|
|
720
725
|
}
|
|
@@ -798,6 +803,29 @@ function isAgentActivityEvent(type: string): boolean {
|
|
|
798
803
|
return type.startsWith("agent.") || type.startsWith("sandbox.");
|
|
799
804
|
}
|
|
800
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Evidence that a queued prompt crossed the execution boundary when an older
|
|
808
|
+
* or partially recovered ledger is missing its canonical `turn.started`.
|
|
809
|
+
* Queue/control bookkeeping intentionally does not qualify: moving, editing,
|
|
810
|
+
* steering, or deleting a waiting row must never make its prompt appear in the
|
|
811
|
+
* transcript as though inference had begun.
|
|
812
|
+
*/
|
|
813
|
+
function isTurnExecutionEvidence(type: string): boolean {
|
|
814
|
+
return (
|
|
815
|
+
isAgentActivityEvent(type) ||
|
|
816
|
+
type === "turn.completed" ||
|
|
817
|
+
type === "turn.failed" ||
|
|
818
|
+
type === "turn.recovery.requested" ||
|
|
819
|
+
type === "turn.capacity_waiting" ||
|
|
820
|
+
type === "session.requiresAction" ||
|
|
821
|
+
type === "tool.auth_needed" ||
|
|
822
|
+
type === "credential.auth_needed" ||
|
|
823
|
+
type.startsWith("rig.setup.") ||
|
|
824
|
+
type === "codex.capacity.waiting" ||
|
|
825
|
+
type === "codex.capacity.resumed"
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
801
829
|
function turnEndItem(
|
|
802
830
|
event: SessionEvent,
|
|
803
831
|
outcome: TurnEndItem["outcome"],
|
|
@@ -1171,6 +1199,8 @@ const AUTH_NEEDED_REASONS: ReadonlySet<string> = new Set([
|
|
|
1171
1199
|
"expired",
|
|
1172
1200
|
"insufficient_scope",
|
|
1173
1201
|
"refresh_failed",
|
|
1202
|
+
"unsupported_auth",
|
|
1203
|
+
"resource_scope_unavailable",
|
|
1174
1204
|
]);
|
|
1175
1205
|
|
|
1176
1206
|
function authNeededReason(value: unknown): AuthNeededItem["reason"] {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
execTruncated,
|
|
23
23
|
isExecSessionLostBanner,
|
|
24
24
|
looksBinary,
|
|
25
|
+
mediaPreviewFact,
|
|
25
26
|
parseExecBannerSessionId,
|
|
26
27
|
parseToolArgs,
|
|
27
28
|
redactSecrets,
|
|
@@ -555,6 +556,7 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
555
556
|
const rejected = raw.providerData?.approvalStatus === "rejected";
|
|
556
557
|
const readOnly = typeof out === "string" && out.includes("read-only");
|
|
557
558
|
const shotUrl = screenshotDataUrl(out);
|
|
559
|
+
const omittedMedia = mediaPreviewFact(out);
|
|
558
560
|
const empty = out === "" || out == null;
|
|
559
561
|
const batched = actions.length > 1 ? actions.map((a) => computerVerb(a)).join(" · ") : null;
|
|
560
562
|
// Fold the batched-action count into the title (one media affordance per row),
|
|
@@ -635,6 +637,26 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
635
637
|
);
|
|
636
638
|
}
|
|
637
639
|
|
|
640
|
+
if (omittedMedia) {
|
|
641
|
+
return (
|
|
642
|
+
<ActivityDisclosure
|
|
643
|
+
icon={<CameraOffIcon className={ICON_SIZE} />}
|
|
644
|
+
iconTone={isFailed ? "failed" : "muted"}
|
|
645
|
+
title={`${verb}${countSuffix} · image omitted · not retained`}
|
|
646
|
+
failed={isFailed}
|
|
647
|
+
cancelled={isCancelled}
|
|
648
|
+
preview="inline image omitted · not retained"
|
|
649
|
+
media={<MediaEmpty />}
|
|
650
|
+
>
|
|
651
|
+
<BodyNote>
|
|
652
|
+
The inline {omittedMedia.mediaType} output was omitted from the audit timeline and its
|
|
653
|
+
source bytes were not retained.
|
|
654
|
+
</BodyNote>
|
|
655
|
+
{batched ? <BodyNote>batched: {batched}</BodyNote> : null}
|
|
656
|
+
</ActivityDisclosure>
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
638
660
|
if (empty) {
|
|
639
661
|
return (
|
|
640
662
|
<ActivityDisclosure
|
|
@@ -759,6 +781,7 @@ function ViewImageRenderer({ item }: ToolRendererProps) {
|
|
|
759
781
|
const path = typeof args.path === "string" ? args.path : "";
|
|
760
782
|
const out = item.output;
|
|
761
783
|
const text = typeof out === "string" ? out : "";
|
|
784
|
+
const omittedMedia = mediaPreviewFact(out);
|
|
762
785
|
|
|
763
786
|
if (item.status === "running") {
|
|
764
787
|
return (
|
|
@@ -807,6 +830,24 @@ function ViewImageRenderer({ item }: ToolRendererProps) {
|
|
|
807
830
|
</ActivityDisclosure>
|
|
808
831
|
);
|
|
809
832
|
}
|
|
833
|
+
if (omittedMedia) {
|
|
834
|
+
return (
|
|
835
|
+
<ActivityDisclosure
|
|
836
|
+
icon={<ImageIcon className={ICON_SIZE} />}
|
|
837
|
+
iconTone={viewFailed ? "failed" : "muted"}
|
|
838
|
+
title={`Viewed ${basename(path)} · image omitted · not retained`}
|
|
839
|
+
failed={viewFailed}
|
|
840
|
+
cancelled={viewCancelled}
|
|
841
|
+
preview="inline image omitted · not retained"
|
|
842
|
+
media={<MediaEmpty />}
|
|
843
|
+
>
|
|
844
|
+
<BodyNote>
|
|
845
|
+
The inline {omittedMedia.mediaType} output was omitted from the audit timeline and its
|
|
846
|
+
source bytes were not retained.
|
|
847
|
+
</BodyNote>
|
|
848
|
+
</ActivityDisclosure>
|
|
849
|
+
);
|
|
850
|
+
}
|
|
810
851
|
if (text.includes("No image data")) {
|
|
811
852
|
return (
|
|
812
853
|
<ActivityDisclosure
|
|
@@ -4,7 +4,7 @@ import { Collapsible } from "radix-ui";
|
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { useForcedDefaultOpen } from "./disclosure-context";
|
|
6
6
|
import { useEntranceAnimation } from "./entrance";
|
|
7
|
-
import { applyPatchOps, isApplyPatch, screenshotDataUrl } from "./parsers";
|
|
7
|
+
import { applyPatchOps, isApplyPatch, mediaPreviewFact, screenshotDataUrl } from "./parsers";
|
|
8
8
|
import { rawTypeOf } from "./registry";
|
|
9
9
|
import type { ActivityItem, TurnOutcome } from "./types";
|
|
10
10
|
export type { TurnOutcome } from "./types";
|
|
@@ -181,7 +181,7 @@ function summarizeTurn(items: ActivityItem[], durationMs?: number): string {
|
|
|
181
181
|
item.name === "computer_call" ||
|
|
182
182
|
item.name === "computer_screenshot"
|
|
183
183
|
) {
|
|
184
|
-
if (screenshotDataUrl(item.output) !== null) {
|
|
184
|
+
if (screenshotDataUrl(item.output) !== null || mediaPreviewFact(item.output) !== null) {
|
|
185
185
|
screenshots += 1;
|
|
186
186
|
}
|
|
187
187
|
}
|
package/src/timeline/types.ts
CHANGED
|
@@ -167,13 +167,14 @@ export type NoticeItem = {
|
|
|
167
167
|
};
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* A tool call hit a connection
|
|
171
|
-
*
|
|
172
|
-
* structured `tool.auth_needed` payload so the renderer can draw a clean inline
|
|
173
|
-
* reconnect affordance
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* shapes the human copy but is never
|
|
170
|
+
* A tool call hit a connection that needs action or cannot satisfy the selected
|
|
171
|
+
* endpoint/resource contract. Carries the
|
|
172
|
+
* structured `tool.auth_needed` or `credential.auth_needed` payload so the renderer can draw a clean inline
|
|
173
|
+
* reconnect affordance when recovery is possible, or a calm unavailable state
|
|
174
|
+
* for incompatible auth/resource containment. The app can start the right
|
|
175
|
+
* recovery flow (OAuth reconnect for the surviving connection, or credential
|
|
176
|
+
* re-entry for an api-key one). The `reason` shapes the human copy but is never
|
|
177
|
+
* shown raw.
|
|
177
178
|
*/
|
|
178
179
|
export type AuthNeededItem = {
|
|
179
180
|
kind: "auth-needed";
|
|
@@ -183,7 +184,14 @@ export type AuthNeededItem = {
|
|
|
183
184
|
providerDomain: string;
|
|
184
185
|
/** The lapsed connection to reconnect, when the row survived. */
|
|
185
186
|
connectionId: string | null;
|
|
186
|
-
reason:
|
|
187
|
+
reason:
|
|
188
|
+
| "missing_connection"
|
|
189
|
+
| "expired"
|
|
190
|
+
| "insufficient_scope"
|
|
191
|
+
| "refresh_failed"
|
|
192
|
+
| "unsupported_auth"
|
|
193
|
+
| "resource_scope_unavailable"
|
|
194
|
+
| null;
|
|
187
195
|
/** Scopes the provider now needs; may inform the copy, never shown as a raw label. */
|
|
188
196
|
scopes: string[];
|
|
189
197
|
/** The OAuth `resource` (RFC 8707) the reconnect should target, when supplied. */
|
package/styles/index.css
CHANGED
|
@@ -165,6 +165,28 @@
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/* Metric-history line draw-in. The chart path carries `pathLength={1}`, so the
|
|
169
|
+
dash math is resolution-independent: one full-length dash whose offset animates
|
|
170
|
+
from 1 (hidden) to 0 (drawn). Degrades to a solid line if animations are off. */
|
|
171
|
+
@keyframes og-mh-draw {
|
|
172
|
+
from {
|
|
173
|
+
stroke-dashoffset: 1;
|
|
174
|
+
}
|
|
175
|
+
to {
|
|
176
|
+
stroke-dashoffset: 0;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
.og-mh-draw {
|
|
180
|
+
stroke-dasharray: 1;
|
|
181
|
+
animation: og-mh-draw 900ms var(--og-ease-out) forwards;
|
|
182
|
+
}
|
|
183
|
+
@media (prefers-reduced-motion: reduce) {
|
|
184
|
+
.og-mh-draw {
|
|
185
|
+
animation: none;
|
|
186
|
+
stroke-dasharray: none;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
168
190
|
/* Shimmer helper: gradient text the keyframe scrolls through. */
|
|
169
191
|
.og-shimmer-text {
|
|
170
192
|
background: linear-gradient(
|
|
@@ -194,6 +216,17 @@
|
|
|
194
216
|
scrollbar-color: var(--og-color-border-strong) transparent;
|
|
195
217
|
}
|
|
196
218
|
|
|
219
|
+
/* The machine popover stays mounted so its trigger's `aria-controls` always
|
|
220
|
+
resolves. Keep Radix's positioning wrapper measurable while closed, but
|
|
221
|
+
remove the entire fixed layer from painting, hit testing, and the a11y tree.
|
|
222
|
+
Hiding only the content leaves the wrapper able to intercept pointer input. */
|
|
223
|
+
[data-radix-popper-content-wrapper]:has(
|
|
224
|
+
> [data-machine-state-popover][data-state="closed"]
|
|
225
|
+
) {
|
|
226
|
+
visibility: hidden;
|
|
227
|
+
pointer-events: none;
|
|
228
|
+
}
|
|
229
|
+
|
|
197
230
|
@media (prefers-reduced-motion: reduce) {
|
|
198
231
|
.og-root *,
|
|
199
232
|
.og-root *::before,
|
|
@@ -206,6 +239,51 @@
|
|
|
206
239
|
}
|
|
207
240
|
}
|
|
208
241
|
|
|
242
|
+
@media (forced-colors: active) {
|
|
243
|
+
/* Forced-colors can replace a painted background with Canvas while custom
|
|
244
|
+
properties retain their authored dark/light values. That combination is
|
|
245
|
+
dangerous: descendants may resolve a pale dark-theme foreground against a
|
|
246
|
+
light system canvas. Collapse the whole component palette onto guaranteed
|
|
247
|
+
system colors. Meaning in status/diff UI is also carried by text, icons,
|
|
248
|
+
and +/- markers, so it never depends on these colors alone. */
|
|
249
|
+
.og-root {
|
|
250
|
+
--og-color-bg: Canvas;
|
|
251
|
+
--og-color-surface-1: Canvas;
|
|
252
|
+
--og-color-surface-2: Canvas;
|
|
253
|
+
--og-color-surface-3: Canvas;
|
|
254
|
+
--og-color-border: CanvasText;
|
|
255
|
+
--og-color-border-strong: CanvasText;
|
|
256
|
+
--og-color-fg: CanvasText;
|
|
257
|
+
--og-color-fg-muted: CanvasText;
|
|
258
|
+
--og-color-fg-subtle: CanvasText;
|
|
259
|
+
--og-color-accent: CanvasText;
|
|
260
|
+
--og-color-accent-strong: CanvasText;
|
|
261
|
+
--og-color-accent-deep: CanvasText;
|
|
262
|
+
--og-color-accent-fg: CanvasText;
|
|
263
|
+
--og-color-accent-soft: Canvas;
|
|
264
|
+
--og-color-status-queued: CanvasText;
|
|
265
|
+
--og-color-status-running: CanvasText;
|
|
266
|
+
--og-color-status-idle: CanvasText;
|
|
267
|
+
--og-color-status-waiting: CanvasText;
|
|
268
|
+
--og-color-status-failed: CanvasText;
|
|
269
|
+
--og-color-status-cancelled: CanvasText;
|
|
270
|
+
--og-color-danger: CanvasText;
|
|
271
|
+
--og-color-diff-add-bg: Canvas;
|
|
272
|
+
--og-color-diff-del-bg: Canvas;
|
|
273
|
+
--og-shadow-sm: none;
|
|
274
|
+
--og-shadow-md: none;
|
|
275
|
+
--og-shadow-lg: none;
|
|
276
|
+
--og-shadow-glow: none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Selected fills are flattened by the OS palette. Preserve a visible,
|
|
280
|
+
non-color-only tab selection affordance in every high-contrast theme. */
|
|
281
|
+
.og-root [role="tab"][aria-selected="true"] {
|
|
282
|
+
outline: 2px solid Highlight;
|
|
283
|
+
outline-offset: -2px;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
209
287
|
/* ----------------------------------------------------------------------------
|
|
210
288
|
xterm.js hardening (SandboxTerminal)
|
|
211
289
|
|