@opengeni/react 6.0.0-canary.1 → 6.1.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 +17 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/{chunk-VRBL3F5E.js → chunk-5GAL23FF.js} +10 -2
- package/dist/{chunk-VRBL3F5E.js.map → chunk-5GAL23FF.js.map} +1 -1
- package/dist/{chunk-IHQII5WO.js → chunk-G5JFM6ZE.js} +29 -4
- package/dist/chunk-G5JFM6ZE.js.map +1 -0
- package/dist/{chunk-UMECRJRV.js → chunk-HJ5VJ4JB.js} +21 -12
- package/dist/chunk-HJ5VJ4JB.js.map +1 -0
- package/dist/{chunk-R4KRSFGA.js → chunk-IP4BUBKH.js} +3 -3
- package/dist/{chunk-CC7AVRIJ.js → chunk-KZ4QDBGM.js} +87 -14
- package/dist/chunk-KZ4QDBGM.js.map +1 -0
- package/dist/{chunk-PZ27MCZP.js → chunk-L5VJYZIW.js} +34 -4
- package/dist/chunk-L5VJYZIW.js.map +1 -0
- package/dist/{chunk-45XEUVTK.js → chunk-UWVY3OXF.js} +12 -5
- package/dist/chunk-UWVY3OXF.js.map +1 -0
- package/dist/components/message-timeline.d.ts +2 -2
- package/dist/composer.js +4 -4
- package/dist/connect-panel.d.ts +2 -1
- package/dist/connect-setup.d.ts +3 -1
- package/dist/connect.d.ts +1 -0
- package/dist/connect.js +620 -424
- package/dist/connect.js.map +1 -1
- package/dist/connection-catalog.d.ts +6 -1
- package/dist/connection-installed.d.ts +1 -0
- package/dist/connection-logo.d.ts +4 -2
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/index.js +7 -7
- package/dist/{model-policy-picker-menu-3R336DP2.js → model-policy-picker-menu-RVOQ4ZZG.js} +4 -4
- package/dist/model-policy.d.ts +6 -2
- package/dist/model-policy.js +1 -1
- package/dist/plugin-discovery.d.ts +4 -0
- package/dist/realtime.js +2 -2
- package/dist/session-ui.js +6 -6
- package/dist/session.js +1 -1
- package/dist/skill-discovery.d.ts +11 -0
- package/dist/timeline/projection.d.ts +3 -1
- package/dist/timeline/turn-summary.d.ts +2 -2
- package/package.json +3 -3
- package/src/capability-catalog-row.tsx +94 -0
- package/src/components/markdown.tsx +32 -3
- package/src/components/message-timeline.tsx +88 -4
- package/src/components/model-picker.tsx +11 -3
- package/src/components/model-policy-picker-menu.tsx +10 -3
- package/src/components/session-conversation.tsx +1 -3
- package/src/components/session-status.tsx +2 -2
- package/src/connect-panel.tsx +19 -8
- package/src/connect-setup.tsx +49 -14
- package/src/connect.ts +6 -0
- package/src/connection-catalog.tsx +61 -39
- package/src/connection-installed.tsx +2 -0
- package/src/connection-logo.tsx +14 -10
- package/src/discovery-cache.ts +42 -0
- package/src/hooks/use-session-events.ts +24 -2
- package/src/model-policy.ts +21 -0
- package/src/plugin-details.tsx +8 -5
- package/src/plugin-discovery.tsx +87 -71
- package/src/skill-discovery.tsx +101 -56
- package/src/timeline/projection.ts +31 -1
- package/src/timeline/turn-summary.tsx +3 -3
- package/styles/compiled.css +27 -0
- package/styles/connect.css +56 -15
- package/dist/chunk-45XEUVTK.js.map +0 -1
- package/dist/chunk-CC7AVRIJ.js.map +0 -1
- package/dist/chunk-IHQII5WO.js.map +0 -1
- package/dist/chunk-PZ27MCZP.js.map +0 -1
- package/dist/chunk-UMECRJRV.js.map +0 -1
- /package/dist/{chunk-R4KRSFGA.js.map → chunk-IP4BUBKH.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-3R336DP2.js.map → model-policy-picker-menu-RVOQ4ZZG.js.map} +0 -0
package/src/plugin-discovery.tsx
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { createDiscoveryCache } from "./discovery-cache";
|
|
1
2
|
import { useEffect, useRef, useState } from "react";
|
|
2
3
|
import type { PluginDiscoveryItem, PluginDiscoveryPage } from "@opengeni/sdk";
|
|
4
|
+
import { BoxesIcon } from "lucide-react";
|
|
5
|
+
import { CapabilityCatalogRow } from "./capability-catalog-row";
|
|
6
|
+
import { ConnectionLogo } from "./connection-logo";
|
|
7
|
+
|
|
8
|
+
const discoveryCache = createDiscoveryCache<PluginDiscoveryPage>();
|
|
9
|
+
const EMPTY_INSTALLED_IDS: ReadonlySet<string> = new Set();
|
|
10
|
+
|
|
3
11
|
export type PluginDiscoveryProps = {
|
|
4
12
|
client: {
|
|
5
13
|
discoverPlugins(
|
|
@@ -9,30 +17,39 @@ export type PluginDiscoveryProps = {
|
|
|
9
17
|
};
|
|
10
18
|
workspaceId: string;
|
|
11
19
|
query: string;
|
|
20
|
+
/** Discovery identities already installed in the current workspace. */
|
|
21
|
+
installedIds?: ReadonlySet<string>;
|
|
22
|
+
resultLimit?: number;
|
|
23
|
+
onShowMore?: () => void;
|
|
12
24
|
onOpen: (item: PluginDiscoveryItem) => void;
|
|
13
25
|
};
|
|
14
26
|
export function PluginDiscovery(props: PluginDiscoveryProps) {
|
|
15
27
|
const [provider, setProvider] = useState("");
|
|
16
28
|
return (
|
|
17
|
-
<section
|
|
29
|
+
<section
|
|
30
|
+
className={`og-plugin-discovery ${props.resultLimit ? "og-catalog-overview" : ""}`}
|
|
31
|
+
aria-label="Discover plugins"
|
|
32
|
+
>
|
|
18
33
|
<header>
|
|
19
|
-
<h3>Browse plugins</h3>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
<h3>{props.resultLimit ? "Plugins" : "Browse plugins"}</h3>
|
|
35
|
+
{!props.resultLimit ? (
|
|
36
|
+
<div className="og-plugin-filters" role="group" aria-label="Plugin registry">
|
|
37
|
+
{[
|
|
38
|
+
{ value: "", label: "All" },
|
|
39
|
+
{ value: "openai", label: "OpenAI plugin registry" },
|
|
40
|
+
{ value: "anthropic", label: "Anthropic plugin registry" },
|
|
41
|
+
].map((option) => (
|
|
42
|
+
<button
|
|
43
|
+
key={option.value}
|
|
44
|
+
type="button"
|
|
45
|
+
aria-pressed={provider === option.value}
|
|
46
|
+
onClick={() => setProvider(option.value)}
|
|
47
|
+
>
|
|
48
|
+
{option.label}
|
|
49
|
+
</button>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
) : null}
|
|
36
53
|
</header>
|
|
37
54
|
<Results
|
|
38
55
|
key={`${props.workspaceId}:${props.query}:${provider}`}
|
|
@@ -47,39 +64,49 @@ function Results({
|
|
|
47
64
|
workspaceId,
|
|
48
65
|
query,
|
|
49
66
|
provider,
|
|
67
|
+
installedIds = EMPTY_INSTALLED_IDS,
|
|
50
68
|
onOpen,
|
|
69
|
+
resultLimit,
|
|
70
|
+
onShowMore,
|
|
51
71
|
}: PluginDiscoveryProps & { provider: string }) {
|
|
52
|
-
const [
|
|
72
|
+
const initial = discoveryCache.peek(client, JSON.stringify([workspaceId, query, provider, 0]));
|
|
73
|
+
const [items, setItems] = useState<PluginDiscoveryItem[]>(initial?.items ?? []);
|
|
53
74
|
const [offset, setOffset] = useState(0);
|
|
54
|
-
const [next, setNext] = useState<number | null>(null);
|
|
55
|
-
const [total, setTotal] = useState(0);
|
|
56
|
-
const [loading, setLoading] = useState(
|
|
75
|
+
const [next, setNext] = useState<number | null>(initial?.nextOffset ?? null);
|
|
76
|
+
const [total, setTotal] = useState(initial?.total ?? 0);
|
|
77
|
+
const [loading, setLoading] = useState(!initial);
|
|
57
78
|
const [error, setError] = useState(false);
|
|
58
79
|
const [retry, setRetry] = useState(0);
|
|
59
80
|
const sentinel = useRef<HTMLDivElement>(null);
|
|
60
81
|
useEffect(() => {
|
|
61
82
|
let active = true;
|
|
62
|
-
|
|
83
|
+
const cacheKey = JSON.stringify([workspaceId, query, provider, offset]);
|
|
84
|
+
const cached = discoveryCache.peek(client, cacheKey);
|
|
85
|
+
setLoading(!cached);
|
|
63
86
|
setError(false);
|
|
64
87
|
const timer = setTimeout(
|
|
65
88
|
() => {
|
|
66
|
-
void
|
|
67
|
-
(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
setError(true);
|
|
89
|
+
void discoveryCache
|
|
90
|
+
.read(client, cacheKey, () =>
|
|
91
|
+
client.discoverPlugins(workspaceId, { query, provider, offset }),
|
|
92
|
+
)
|
|
93
|
+
.then(
|
|
94
|
+
(page) => {
|
|
95
|
+
if (!active) return;
|
|
96
|
+
setItems((previous) => (offset ? [...previous, ...page.items] : page.items));
|
|
97
|
+
setTotal(page.total);
|
|
98
|
+
setNext(page.nextOffset);
|
|
77
99
|
setLoading(false);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
},
|
|
101
|
+
() => {
|
|
102
|
+
if (active) {
|
|
103
|
+
setError(true);
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
);
|
|
81
108
|
},
|
|
82
|
-
offset ? 0 : 200,
|
|
109
|
+
cached || offset ? 0 : 200,
|
|
83
110
|
);
|
|
84
111
|
return () => {
|
|
85
112
|
active = false;
|
|
@@ -87,7 +114,7 @@ function Results({
|
|
|
87
114
|
};
|
|
88
115
|
}, [client, workspaceId, query, provider, offset, retry]);
|
|
89
116
|
useEffect(() => {
|
|
90
|
-
if (loading || error || next === null || !sentinel.current) return;
|
|
117
|
+
if (resultLimit || loading || error || next === null || !sentinel.current) return;
|
|
91
118
|
const observer = new IntersectionObserver(
|
|
92
119
|
(entries) => {
|
|
93
120
|
if (entries.some((entry) => entry.isIntersecting)) setOffset(next);
|
|
@@ -96,7 +123,7 @@ function Results({
|
|
|
96
123
|
);
|
|
97
124
|
observer.observe(sentinel.current);
|
|
98
125
|
return () => observer.disconnect();
|
|
99
|
-
}, [loading, error, next]);
|
|
126
|
+
}, [loading, error, next, resultLimit]);
|
|
100
127
|
return (
|
|
101
128
|
<>
|
|
102
129
|
{!loading && !error ? (
|
|
@@ -105,39 +132,23 @@ function Results({
|
|
|
105
132
|
</p>
|
|
106
133
|
) : null}
|
|
107
134
|
<div className="og-plugin-discovery-grid">
|
|
108
|
-
{items.map((item) => (
|
|
109
|
-
<
|
|
135
|
+
{items.slice(0, resultLimit).map((item) => (
|
|
136
|
+
<CapabilityCatalogRow
|
|
110
137
|
key={item.id}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
{item.
|
|
116
|
-
|
|
138
|
+
data-plugin-id={item.id}
|
|
139
|
+
name={item.displayName.replace(/-/g, " ")}
|
|
140
|
+
description={item.description}
|
|
141
|
+
status={installedIds.has(item.id) ? "added" : "available"}
|
|
142
|
+
statusLabel={installedIds.has(item.id) ? "Installed" : "Available to install"}
|
|
143
|
+
onOpen={() => onOpen(item)}
|
|
144
|
+
icon={
|
|
145
|
+
<ConnectionLogo
|
|
117
146
|
src={item.logoUrl}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
onError={(event) => {
|
|
121
|
-
event.currentTarget.style.display = "none";
|
|
122
|
-
}}
|
|
147
|
+
name={item.displayName}
|
|
148
|
+
fallback={<BoxesIcon aria-hidden="true" />}
|
|
123
149
|
/>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<span className="og-plugin-title">
|
|
127
|
-
<strong>{item.displayName.replace(/-/g, " ")}</strong>
|
|
128
|
-
<small>
|
|
129
|
-
{item.provider === "openai"
|
|
130
|
-
? "OpenAI plugin registry"
|
|
131
|
-
: "Anthropic plugin registry"}
|
|
132
|
-
</small>
|
|
133
|
-
</span>
|
|
134
|
-
<span className="og-plugin-description">{item.description}</span>
|
|
135
|
-
{item.category ? <span className="og-plugin-category">{item.category}</span> : null}
|
|
136
|
-
</span>
|
|
137
|
-
<span className="og-plugin-open" aria-hidden="true">
|
|
138
|
-
›
|
|
139
|
-
</span>
|
|
140
|
-
</button>
|
|
150
|
+
}
|
|
151
|
+
/>
|
|
141
152
|
))}
|
|
142
153
|
</div>
|
|
143
154
|
{loading ? <p role="status">Loading plugins…</p> : null}
|
|
@@ -149,8 +160,13 @@ function Results({
|
|
|
149
160
|
</button>
|
|
150
161
|
</p>
|
|
151
162
|
) : null}
|
|
163
|
+
{resultLimit && !loading && !error && total > resultLimit ? (
|
|
164
|
+
<button className="og-catalog-more" type="button" onClick={onShowMore}>
|
|
165
|
+
View all plugins
|
|
166
|
+
</button>
|
|
167
|
+
) : null}
|
|
152
168
|
<div ref={sentinel}>
|
|
153
|
-
{next !== null && !loading && !error ? (
|
|
169
|
+
{!resultLimit && next !== null && !loading && !error ? (
|
|
154
170
|
<button className="og-plugin-more" type="button" onClick={() => setOffset(next)}>
|
|
155
171
|
Show more plugins
|
|
156
172
|
</button>
|
package/src/skill-discovery.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import { createDiscoveryCache } from "./discovery-cache";
|
|
3
|
+
import { BookOpenIcon } from "lucide-react";
|
|
4
|
+
import { CapabilityCatalogRow, type CapabilityCatalogStatus } from "./capability-catalog-row";
|
|
2
5
|
|
|
3
6
|
export type SkillDiscoveryItem = {
|
|
4
7
|
id: string;
|
|
@@ -8,6 +11,8 @@ export type SkillDiscoveryItem = {
|
|
|
8
11
|
url: string;
|
|
9
12
|
};
|
|
10
13
|
export type SkillDiscoveryPage = { items: SkillDiscoveryItem[]; nextCursor: null };
|
|
14
|
+
const discoveryCache = createDiscoveryCache<SkillDiscoveryPage>();
|
|
15
|
+
|
|
11
16
|
export type SkillDiscoveryClient = {
|
|
12
17
|
searchPublicSkills(workspaceId: string, query: string): Promise<SkillDiscoveryPage>;
|
|
13
18
|
};
|
|
@@ -18,35 +23,53 @@ export type SkillDiscoveryProps = {
|
|
|
18
23
|
query: string;
|
|
19
24
|
onImport: (url: string) => void;
|
|
20
25
|
canManage: boolean;
|
|
26
|
+
localSkills?: readonly {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
status?: CapabilityCatalogStatus;
|
|
31
|
+
statusLabel?: string;
|
|
32
|
+
onOpen: () => void;
|
|
33
|
+
}[];
|
|
34
|
+
resultLimit?: number;
|
|
35
|
+
onShowMore?: () => void;
|
|
21
36
|
onSearch?: (() => void) | undefined;
|
|
22
37
|
};
|
|
23
38
|
|
|
24
39
|
/** Host-owned query and import flow; browse presentation is reusable independently. */
|
|
25
40
|
export function SkillDiscovery(props: SkillDiscoveryProps) {
|
|
26
41
|
const query = props.query.trim();
|
|
42
|
+
const discoveryQuery = query || "agent";
|
|
27
43
|
return (
|
|
28
44
|
<section className="og-skill-discovery" aria-label="Discover skills">
|
|
29
45
|
<header>
|
|
30
|
-
<h3>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
<h3>{props.resultLimit ? "Skills" : query ? "Search skills" : "Browse skills"}</h3>
|
|
47
|
+
{!props.resultLimit ? (
|
|
48
|
+
<a
|
|
49
|
+
href="https://skills.sh/"
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noopener noreferrer"
|
|
52
|
+
className="og-skill-discovery-browse"
|
|
53
|
+
>
|
|
54
|
+
Browse skills.sh ↗
|
|
55
|
+
</a>
|
|
56
|
+
) : null}
|
|
39
57
|
</header>
|
|
40
|
-
{!query
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
{!query ? (
|
|
59
|
+
<p>Agent skills from skills.sh. Search above for another topic.</p>
|
|
60
|
+
) : query.length < 2 ? (
|
|
61
|
+
<p>Enter at least two characters to search skills.sh.</p>
|
|
44
62
|
) : null}
|
|
45
|
-
<DiscoveryResults
|
|
63
|
+
<DiscoveryResults
|
|
64
|
+
key={`${props.workspaceId}:${discoveryQuery}`}
|
|
65
|
+
{...props}
|
|
66
|
+
query={discoveryQuery}
|
|
67
|
+
/>
|
|
46
68
|
</section>
|
|
47
69
|
);
|
|
48
70
|
}
|
|
49
71
|
|
|
72
|
+
const EMPTY_LOCAL_SKILLS: NonNullable<SkillDiscoveryProps["localSkills"]> = [];
|
|
50
73
|
const EMPTY_INSTALLED_SKILLS: NonNullable<SkillDiscoveryProps["installedSkills"]> = [];
|
|
51
74
|
|
|
52
75
|
function DiscoveryResults({
|
|
@@ -55,21 +78,29 @@ function DiscoveryResults({
|
|
|
55
78
|
query,
|
|
56
79
|
canManage,
|
|
57
80
|
onImport,
|
|
81
|
+
resultLimit,
|
|
82
|
+
onShowMore,
|
|
83
|
+
localSkills = EMPTY_LOCAL_SKILLS,
|
|
58
84
|
installedSkills = EMPTY_INSTALLED_SKILLS,
|
|
59
85
|
}: SkillDiscoveryProps) {
|
|
60
|
-
const [
|
|
61
|
-
const
|
|
86
|
+
const cacheKey = JSON.stringify([workspaceId, query]);
|
|
87
|
+
const cached = discoveryCache.peek(client, cacheKey);
|
|
88
|
+
const [result, setResult] = useState<SkillDiscoveryPage | null>(cached ?? null);
|
|
89
|
+
const [loading, setLoading] = useState(query.length >= 2 && !cached);
|
|
62
90
|
const [error, setError] = useState(false);
|
|
63
91
|
const [retry, setRetry] = useState(0);
|
|
64
92
|
useEffect(() => {
|
|
65
93
|
let active = true;
|
|
66
94
|
setError(false);
|
|
67
95
|
if (query.length < 2) return;
|
|
68
|
-
|
|
96
|
+
const existing = discoveryCache.peek(client, cacheKey);
|
|
97
|
+
setLoading(!existing);
|
|
69
98
|
let timer: ReturnType<typeof setTimeout>;
|
|
70
99
|
const search = async (attempt: number) => {
|
|
71
100
|
try {
|
|
72
|
-
const data = await
|
|
101
|
+
const data = await discoveryCache.read(client, cacheKey, () =>
|
|
102
|
+
client.searchPublicSkills(workspaceId, query),
|
|
103
|
+
);
|
|
73
104
|
if (!active) return;
|
|
74
105
|
setResult(data);
|
|
75
106
|
setLoading(false);
|
|
@@ -90,51 +121,65 @@ function DiscoveryResults({
|
|
|
90
121
|
setLoading(false);
|
|
91
122
|
}
|
|
92
123
|
};
|
|
93
|
-
timer = setTimeout(() => void search(0), 300);
|
|
124
|
+
timer = setTimeout(() => void search(0), existing ? 0 : 300);
|
|
94
125
|
return () => {
|
|
95
126
|
active = false;
|
|
96
127
|
clearTimeout(timer);
|
|
97
128
|
};
|
|
98
|
-
}, [client, workspaceId, query, retry]);
|
|
129
|
+
}, [client, workspaceId, query, cacheKey, retry]);
|
|
130
|
+
const remoteSkills =
|
|
131
|
+
result?.items.filter(
|
|
132
|
+
(skill) =>
|
|
133
|
+
!localSkills.some((local) => local.name.toLowerCase() === skill.name.toLowerCase()),
|
|
134
|
+
) ?? [];
|
|
99
135
|
return (
|
|
100
136
|
<div aria-busy={loading}>
|
|
101
137
|
<div className="og-skill-discovery-grid">
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
{localSkills.slice(0, resultLimit).map((skill) => (
|
|
139
|
+
<CapabilityCatalogRow
|
|
140
|
+
key={skill.id}
|
|
141
|
+
name={skill.name}
|
|
142
|
+
description={skill.description}
|
|
143
|
+
icon={<BookOpenIcon aria-hidden="true" />}
|
|
144
|
+
status={skill.status ?? "added"}
|
|
145
|
+
statusLabel={skill.statusLabel}
|
|
146
|
+
onOpen={skill.onOpen}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
{remoteSkills
|
|
150
|
+
.slice(
|
|
151
|
+
0,
|
|
152
|
+
resultLimit === undefined ? undefined : Math.max(0, resultLimit - localSkills.length),
|
|
153
|
+
)
|
|
154
|
+
.map((skill) => {
|
|
155
|
+
const installed = installedSkills.some(
|
|
156
|
+
(entry) =>
|
|
157
|
+
entry.sourceUrl.replace(/\/$/, "").toLowerCase() === skill.url.toLowerCase() ||
|
|
158
|
+
(entry.repositoryUrl.replace(/\/$/, "").toLowerCase() ===
|
|
159
|
+
`https://github.com/${skill.source}`.toLowerCase() &&
|
|
160
|
+
entry.name.toLowerCase() === skill.name.toLowerCase()),
|
|
161
|
+
);
|
|
162
|
+
return (
|
|
163
|
+
<CapabilityCatalogRow
|
|
164
|
+
key={skill.id}
|
|
165
|
+
disabled={!canManage}
|
|
166
|
+
name={skill.name}
|
|
167
|
+
description={skill.source}
|
|
168
|
+
icon={<BookOpenIcon aria-hidden="true" />}
|
|
169
|
+
status={!canManage ? "unavailable" : installed ? "added" : "available"}
|
|
170
|
+
statusLabel={
|
|
171
|
+
!canManage ? "Admin required" : installed ? "Installed" : "Available to add"
|
|
172
|
+
}
|
|
173
|
+
onOpen={() => onImport(skill.url)}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
})}
|
|
137
177
|
</div>
|
|
178
|
+
{resultLimit && remoteSkills.length + localSkills.length > resultLimit && !loading ? (
|
|
179
|
+
<button className="og-catalog-more" type="button" onClick={onShowMore}>
|
|
180
|
+
View all skills
|
|
181
|
+
</button>
|
|
182
|
+
) : null}
|
|
138
183
|
{loading ? <p role="status">Loading skills…</p> : null}
|
|
139
184
|
{error ? (
|
|
140
185
|
<div role="alert" className="og-skill-discovery-error">
|
|
@@ -144,7 +189,7 @@ function DiscoveryResults({
|
|
|
144
189
|
</button>
|
|
145
190
|
</div>
|
|
146
191
|
) : null}
|
|
147
|
-
{result && !
|
|
192
|
+
{result && !remoteSkills.length && !localSkills.length && !loading && !error ? (
|
|
148
193
|
<p role="status">No skills found. Try a different search.</p>
|
|
149
194
|
) : null}
|
|
150
195
|
</div>
|
|
@@ -129,10 +129,20 @@ function timelineAnnotationToolOutputText(output: unknown): string | null {
|
|
|
129
129
|
return text.length > 0 ? text : null;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
export function buildTimeline(
|
|
132
|
+
export function buildTimeline(
|
|
133
|
+
events: SessionEvent[],
|
|
134
|
+
options: { partialStart?: boolean } = {},
|
|
135
|
+
): TimelineItem[] {
|
|
133
136
|
const items: TimelineItem[] = [];
|
|
134
137
|
const prescan = prescanTurnAnchors(events);
|
|
135
138
|
const ordered = orderTimelineEvents(events, prescan);
|
|
139
|
+
// A bounded replay can begin halfway through a message (including inside an
|
|
140
|
+
// interactive fence). Never parse that suffix as a complete Markdown source.
|
|
141
|
+
// Keep the raw events available for pagination; a completed receipt restores
|
|
142
|
+
// the authoritative message without requiring an unbounded history fetch.
|
|
143
|
+
let completeHistoryPrefix = options.partialStart !== true;
|
|
144
|
+
const knownMessagePrefixTurns = new Set<string | null>();
|
|
145
|
+
const incompleteMessageKeys = new Set<string>();
|
|
136
146
|
const pendingWaitOutcomeByTurn = new Map<string | null, PendingWaitOutcome>();
|
|
137
147
|
const latestAgentResponseByTurn = new Map<string | null, TrackedAgentResponse>();
|
|
138
148
|
const identifiedMessages = new Map<string, AgentMessageItem>();
|
|
@@ -299,6 +309,18 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
299
309
|
for (const event of ordered) {
|
|
300
310
|
const payload = asRecord(event.payload);
|
|
301
311
|
const turnId = event.turnId ?? null;
|
|
312
|
+
if (event.type === "session.created") {
|
|
313
|
+
completeHistoryPrefix = true;
|
|
314
|
+
} else if (
|
|
315
|
+
event.type === "turn.started" ||
|
|
316
|
+
(event.type === "user.message" &&
|
|
317
|
+
payload.delivery !== "steer" &&
|
|
318
|
+
payload.routing !== "accepted_for_steering")
|
|
319
|
+
) {
|
|
320
|
+
// Accepted Steer is visible before its replacement turn starts. It says
|
|
321
|
+
// nothing about the prefix of a still-streaming message from the old turn.
|
|
322
|
+
knownMessagePrefixTurns.add(turnId);
|
|
323
|
+
}
|
|
302
324
|
startupEvent = event;
|
|
303
325
|
startupTurnId = turnId;
|
|
304
326
|
startupAttemptId =
|
|
@@ -415,6 +437,13 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
415
437
|
}
|
|
416
438
|
const messageId = stringValue(payload.messageId);
|
|
417
439
|
const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
|
|
440
|
+
if (
|
|
441
|
+
(!completeHistoryPrefix && !knownMessagePrefixTurns.has(turnId)) ||
|
|
442
|
+
(messageKey && incompleteMessageKeys.has(messageKey))
|
|
443
|
+
) {
|
|
444
|
+
if (messageKey) incompleteMessageKeys.add(messageKey);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
418
447
|
const identified = messageKey ? identifiedMessages.get(messageKey) : undefined;
|
|
419
448
|
if (identified) {
|
|
420
449
|
if (identified.annotationSource?.eventType !== "agent.message.completed") {
|
|
@@ -470,6 +499,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
470
499
|
}
|
|
471
500
|
|
|
472
501
|
case "agent.message.completed": {
|
|
502
|
+
knownMessagePrefixTurns.add(turnId);
|
|
473
503
|
const text = stringValue(payload.text);
|
|
474
504
|
const messageId = stringValue(payload.messageId);
|
|
475
505
|
const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
|
|
@@ -125,8 +125,8 @@ export type TurnSummaryProps = {
|
|
|
125
125
|
items: ActivityItem[];
|
|
126
126
|
/**
|
|
127
127
|
* The settled verdict — or absent for a completed CLUSTER of a still-running
|
|
128
|
-
* turn, which folds neutrally
|
|
129
|
-
*
|
|
128
|
+
* turn, which folds neutrally. Only unfinished activity inside this summary
|
|
129
|
+
* animates; the parent turn may still be composing a response elsewhere.
|
|
130
130
|
*/
|
|
131
131
|
outcome?: TurnOutcome | undefined;
|
|
132
132
|
/** A short failure reason shown inline on a failed chip (never hidden). */
|
|
@@ -447,7 +447,7 @@ export function TurnSummary({
|
|
|
447
447
|
/>
|
|
448
448
|
{/* Completion is the quiet default and needs no repeated glyph. Failed,
|
|
449
449
|
cancelled, and still-running folds retain a visible state marker. */}
|
|
450
|
-
{outcome === "complete" || (
|
|
450
|
+
{outcome === "complete" || (!outcome && (liveHeader || context.settled)) ? null : (
|
|
451
451
|
<span
|
|
452
452
|
className={cn(
|
|
453
453
|
"inline-flex shrink-0 items-center justify-center",
|
package/styles/compiled.css
CHANGED
|
@@ -1623,6 +1623,9 @@
|
|
|
1623
1623
|
:where(.og-root).min-h-14,:where(.og-root) .min-h-14 {
|
|
1624
1624
|
min-height: calc(var(--spacing) * 14);
|
|
1625
1625
|
}
|
|
1626
|
+
:where(.og-root).min-h-32,:where(.og-root) .min-h-32 {
|
|
1627
|
+
min-height: calc(var(--spacing) * 32);
|
|
1628
|
+
}
|
|
1626
1629
|
:where(.og-root).min-h-56,:where(.og-root) .min-h-56 {
|
|
1627
1630
|
min-height: calc(var(--spacing) * 56);
|
|
1628
1631
|
}
|
|
@@ -1785,6 +1788,9 @@
|
|
|
1785
1788
|
:where(.og-root).max-w-md,:where(.og-root) .max-w-md {
|
|
1786
1789
|
max-width: var(--container-md);
|
|
1787
1790
|
}
|
|
1791
|
+
:where(.og-root).max-w-prose,:where(.og-root) .max-w-prose {
|
|
1792
|
+
max-width: 65ch;
|
|
1793
|
+
}
|
|
1788
1794
|
:where(.og-root).max-w-sm,:where(.og-root) .max-w-sm {
|
|
1789
1795
|
max-width: var(--container-sm);
|
|
1790
1796
|
}
|
|
@@ -4173,6 +4179,11 @@
|
|
|
4173
4179
|
padding-top: calc(var(--spacing) * 0);
|
|
4174
4180
|
}
|
|
4175
4181
|
}
|
|
4182
|
+
:where(.og-root).first\:pt-1,:where(.og-root) .first\:pt-1 {
|
|
4183
|
+
&:first-child {
|
|
4184
|
+
padding-top: calc(var(--spacing) * 1);
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4176
4187
|
:where(.og-root).first\:pl-0,:where(.og-root) .first\:pl-0 {
|
|
4177
4188
|
&:first-child {
|
|
4178
4189
|
padding-left: calc(var(--spacing) * 0);
|
|
@@ -5020,6 +5031,11 @@
|
|
|
5020
5031
|
}
|
|
5021
5032
|
}
|
|
5022
5033
|
}
|
|
5034
|
+
:where(.og-root).motion-safe\:animate-spin,:where(.og-root) .motion-safe\:animate-spin {
|
|
5035
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
5036
|
+
animation: og-spin 1s linear infinite;
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5023
5039
|
:where(.og-root).motion-reduce\:animate-none,:where(.og-root) .motion-reduce\:animate-none {
|
|
5024
5040
|
@media (prefers-reduced-motion: reduce) {
|
|
5025
5041
|
animation: none;
|
|
@@ -5544,6 +5560,17 @@
|
|
|
5544
5560
|
color: var(--color-fg-muted);
|
|
5545
5561
|
}
|
|
5546
5562
|
}
|
|
5563
|
+
:where(.og-root).\[\&\+section\]\:border-t,:where(.og-root) .\[\&\+section\]\:border-t {
|
|
5564
|
+
&+section {
|
|
5565
|
+
border-top-style: var(--tw-border-style);
|
|
5566
|
+
border-top-width: 1px;
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
:where(.og-root).\[\&\+section\]\:border-og-border,:where(.og-root) .\[\&\+section\]\:border-og-border {
|
|
5570
|
+
&+section {
|
|
5571
|
+
border-color: var(--og-color-border);
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5547
5574
|
:where(.og-root).\[\&\:\:-webkit-details-marker\]\:hidden,:where(.og-root) .\[\&\:\:-webkit-details-marker\]\:hidden {
|
|
5548
5575
|
&::-webkit-details-marker {
|
|
5549
5576
|
display: none;
|