@cueloop/client 0.1.0-alpha.102 → 0.1.0-alpha.104
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/package.json +5 -5
- package/src/annotations/annotation-palette.ts +1 -0
- package/src/annotations/components/AnnotationCards.tsx +4 -0
- package/src/annotations/components/DiscussionMarkerRail.tsx +3 -0
- package/src/annotations/discussions.ts +1 -0
- package/src/annotations/thread-selection.ts +6 -0
- package/src/annotations/use-annotation-surface.tsx +97 -0
- package/src/app/App.deleted-scroll.test.tsx +3 -0
- package/src/app/App.harness-settings.test.tsx +77 -0
- package/src/app/App.plan-render.test.tsx +2 -0
- package/src/app/App.plan-review.test.tsx +1 -0
- package/src/app/App.readonly.test.tsx +63 -0
- package/src/app/App.test.tsx +1 -0
- package/src/app/App.tsx +94 -12
- package/src/app/App.visual.test.tsx +1 -0
- package/src/app/App.walk.test.tsx +1 -0
- package/src/app/App.workbench.test.tsx +1 -0
- package/src/app/app-screens.tsx +3 -2
- package/src/app/app-view-model.ts +11 -1
- package/src/app/components/WorkspacePanels.tsx +1 -0
- package/src/app/intent-dispatch.test.ts +1 -0
- package/src/app/intent-dispatch.ts +26 -0
- package/src/app/ready-signal.ts +2 -0
- package/src/app/run.ts +2 -0
- package/src/app/serve.test.ts +4 -0
- package/src/diff/components/AnnotatableFileView.tsx +1 -0
- package/src/diff/components/DiffContentView.scroll.test.tsx +9 -0
- package/src/diff/components/DiffContentView.syntax.test.tsx +1 -0
- package/src/diff/components/DiffContentView.tsx +25 -0
- package/src/diff/components/diff-content-view-layout.ts +3 -0
- package/src/diff/diff-fold.ts +4 -0
- package/src/diff/diff-hunk-curate.ts +3 -0
- package/src/diff/diff-intraline.ts +6 -0
- package/src/diff/diff-syntax.ts +8 -0
- package/src/diff/split-diff.ts +6 -0
- package/src/diff/view-diff.ts +18 -0
- package/src/diff/walk.ts +3 -0
- package/src/index.ts +1 -0
- package/src/integrations/attribution.ts +2 -0
- package/src/integrations/client-extension-registry.ts +4 -0
- package/src/integrations/clipboard.ts +1 -0
- package/src/integrations/components/ShareDialog.agent.test.tsx +2 -0
- package/src/integrations/components/ShareDialog.tsx +28 -0
- package/src/integrations/editor.ts +3 -0
- package/src/integrations/github-identity.test.ts +1 -0
- package/src/integrations/github-identity.ts +1 -0
- package/src/integrations/herdr.ts +2 -0
- package/src/integrations/pasted-image.ts +2 -0
- package/src/integrations/share.ts +46 -1
- package/src/keyboard/components/HandleChipsInput.tsx +4 -0
- package/src/keyboard/components/SlashComposer.tsx +3 -0
- package/src/keyboard/key-bindings.ts +3 -0
- package/src/keyboard/keymap.ts +51 -0
- package/src/keyboard/skills.ts +2 -0
- package/src/keyboard/slash-palette.ts +8 -0
- package/src/keyboard/thread-chords.ts +5 -0
- package/src/keyboard/use-shared-keyboard.ts +1 -0
- package/src/markdown/components/MarkdownThreadEditor.tsx +3 -0
- package/src/markdown/components/ThreadView.selection.test.tsx +3 -0
- package/src/markdown/components/ThreadView.tsx +13 -0
- package/src/markdown/mark-runs.ts +4 -0
- package/src/markdown/markdown-editor-syntax-style.ts +1 -0
- package/src/markdown/markdown-grid.ts +6 -0
- package/src/markdown/markdown-highlight.ts +1 -0
- package/src/markdown/thread-code-syntax.ts +3 -0
- package/src/markdown/view-plan.ts +52 -0
- package/src/performance/perf-report.ts +1 -0
- package/src/performance/perf-timings.ts +1 -0
- package/src/prototype/components/PrototypeContentView.tsx +17 -0
- package/src/prototype/prototype-browser.ts +11 -0
- package/src/settings/components/SettingsDialog.tsx +3 -0
- package/src/settings/config.test.ts +79 -1
- package/src/settings/config.ts +91 -1
- package/src/settings/use-settings-dialog.test.tsx +172 -0
- package/src/settings/use-settings-dialog.tsx +115 -1
- package/src/settings/use-thread-harness-setting.test.tsx +87 -0
- package/src/settings/use-thread-harness-setting.ts +86 -0
- package/src/stories/stories.test.tsx +4 -0
- package/src/stories/story.ts +6 -0
- package/src/terminal/ansi-screen.ts +5 -0
- package/src/terminal/components/terminal-pane.ts +12 -0
- package/src/terminal/ghostty-terminal.ts +10 -0
- package/src/terminal/kitty-image.ts +3 -0
- package/src/terminal/pty.ts +9 -0
- package/src/testing/test-support.test.ts +2 -0
- package/src/testing/test-support.ts +16 -0
- package/src/thread/agent-continuation.test.ts +2 -0
- package/src/thread/agent-transcript.ts +7 -0
- package/src/thread/components/AgentConfigControls.test.tsx +41 -0
- package/src/thread/components/AgentConfigControls.tsx +8 -1
- package/src/thread/components/AgentThreadPane.test.tsx +91 -3
- package/src/thread/components/AgentThreadPane.tsx +61 -0
- package/src/thread/components/ThreadTree.tsx +1 -0
- package/src/thread/components/thread-tree.test.ts +1 -0
- package/src/thread/thread-controller.curate.test.ts +2 -0
- package/src/thread/thread-controller.share.test.ts +47 -2
- package/src/thread/thread-controller.ts +129 -4
- package/src/thread/tree-view.ts +4 -0
- package/src/thread/use-thread-agent.test.tsx +1 -0
- package/src/thread/use-thread-agent.ts +5 -0
- package/src/ui/components/OverlayScrollbar.test.tsx +3 -0
- package/src/ui/components/OverlayScrollbar.tsx +5 -0
- package/src/ui/components/PromptDialog.tsx +2 -0
- package/src/ui/components/RootOverlay.tsx +1 -0
- package/src/ui/components/ScrollArea.tsx +2 -0
- package/src/ui/components/primitives/Button.tsx +1 -0
- package/src/ui/components/primitives/Tabs.tsx +1 -0
- package/src/ui/components/primitives/Tree.tsx +4 -0
- package/src/ui/components/primitives/tree-model.ts +7 -0
- package/src/ui/components/syntax-highlight.ts +5 -0
- package/src/ui/components/truncate-title.ts +1 -0
- package/src/ui/use-frame-measure.ts +3 -0
- package/src/ui/use-terminal-virtualizer.ts +6 -0
- package/src/workbench/components/ChangesColumn.tsx +7 -0
- package/src/workbench/components/ChangesFileTree.test.tsx +3 -0
- package/src/workbench/components/EditorGrid.tsx +8 -0
- package/src/workbench/components/FileContentsView.tsx +2 -0
- package/src/workbench/components/GridTabContent.tsx +2 -0
- package/src/workbench/components/ProjectTreeView.test.tsx +3 -0
- package/src/workbench/components/ProjectTreeView.tsx +8 -0
- package/src/workbench/components/TreePane.tsx +2 -0
- package/src/workbench/components/editor-grid.ts +3 -0
- package/src/workbench/components/file-tree.ts +2 -0
- package/src/workbench/use-changes-workbench.ts +5 -0
- package/src/workbench/use-remember-layout.ts +2 -0
- package/src/workbench/use-repo-changes.ts +2 -0
|
@@ -110,3 +110,44 @@ test("model and reasoning choices share the existing overlay and close after sel
|
|
|
110
110
|
setup.renderer.destroy();
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
|
+
|
|
114
|
+
for (const harness of ["pi", "fx"]) {
|
|
115
|
+
test(`${harness} footer identifies its harness, subscription, model and effort`, async () => {
|
|
116
|
+
const setup = await testRender(
|
|
117
|
+
<AgentConfigControls
|
|
118
|
+
theme={DARK}
|
|
119
|
+
onConfigure={() => {}}
|
|
120
|
+
state={{
|
|
121
|
+
...initial,
|
|
122
|
+
harness: { id: harness, label: harness },
|
|
123
|
+
configOptions: [
|
|
124
|
+
{
|
|
125
|
+
id: "provider",
|
|
126
|
+
name: "Provider",
|
|
127
|
+
currentValue: harness,
|
|
128
|
+
options: [
|
|
129
|
+
{
|
|
130
|
+
value: harness,
|
|
131
|
+
name: harness === "pi" ? "Codex subscription" : "Grok subscription",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
...initial.configOptions!,
|
|
136
|
+
],
|
|
137
|
+
}}
|
|
138
|
+
/>,
|
|
139
|
+
{ width: 100, height: 3 },
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await waitForText(setup, "First model");
|
|
144
|
+
const frame = setup.captureCharFrame();
|
|
145
|
+
|
|
146
|
+
expect(frame).toContain(harness);
|
|
147
|
+
expect(frame).toContain(harness === "pi" ? "Codex subscription" : "Grok subscription");
|
|
148
|
+
expect(frame).toContain("Medium");
|
|
149
|
+
} finally {
|
|
150
|
+
setup.renderer.destroy();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -18,10 +18,12 @@ export function AgentConfigControls({
|
|
|
18
18
|
}): React.ReactNode {
|
|
19
19
|
return (
|
|
20
20
|
<box style={{ flexDirection: "row", gap: 2, marginRight: 2 }}>
|
|
21
|
+
{state.harness ? <text fg={theme.textMuted}>{state.harness.label}</text> : null}
|
|
21
22
|
{state.configOptions
|
|
22
23
|
?.filter(
|
|
23
24
|
(option) =>
|
|
24
25
|
option.category === "thought_level" ||
|
|
26
|
+
option.id === "effort" ||
|
|
25
27
|
option.id === "model" ||
|
|
26
28
|
option.id === "provider",
|
|
27
29
|
)
|
|
@@ -62,6 +64,7 @@ function AgentConfigMenu({
|
|
|
62
64
|
|
|
63
65
|
useEffect(() => {
|
|
64
66
|
if (!open) return;
|
|
67
|
+
|
|
65
68
|
overlay.setOverlay(
|
|
66
69
|
"agent-config",
|
|
67
70
|
<box
|
|
@@ -103,7 +106,11 @@ function AgentConfigMenu({
|
|
|
103
106
|
return (
|
|
104
107
|
<box
|
|
105
108
|
ref={ref}
|
|
106
|
-
onMouseUp={
|
|
109
|
+
onMouseUp={
|
|
110
|
+
disabled || option.options.length < 2
|
|
111
|
+
? undefined
|
|
112
|
+
: () => menu.toggleMenu(`agent-config:${option.id}`)
|
|
113
|
+
}
|
|
107
114
|
>
|
|
108
115
|
<text fg={disabled ? theme.textDim : theme.textMuted}>
|
|
109
116
|
{option.options.find((choice) => choice.value === option.currentValue)?.name ??
|
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
} from "@cueloop/schema";
|
|
10
10
|
import { AgentThreadPane, AgentThreadPrototype } from "./AgentThreadPane";
|
|
11
11
|
import { ThreadFooter } from "./ThreadFooter";
|
|
12
|
-
import { ThreadView } from "../../markdown/components/ThreadView";
|
|
12
|
+
import { ThreadView, type ThreadViewProps } from "../../markdown/components/ThreadView";
|
|
13
13
|
import { buildDisplay, marksByDisplay } from "../../markdown/view-plan";
|
|
14
14
|
import { DARK } from "../../appearance/theme";
|
|
15
15
|
import {
|
|
16
16
|
locateText,
|
|
17
|
+
dragText,
|
|
17
18
|
waitForText,
|
|
18
19
|
pressKey,
|
|
19
20
|
typeText,
|
|
@@ -43,6 +44,33 @@ const empty: ThreadAgentState = {
|
|
|
43
44
|
comments: [],
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
test("a failed harness turn displays its error below the conversation", async () => {
|
|
48
|
+
const failure = "OAuth auth derivation failed: missing provider module";
|
|
49
|
+
const { client } = createTestAgentClient({ ...empty, phase: { kind: "failed", error: failure } });
|
|
50
|
+
const setup = await testRender(
|
|
51
|
+
<AgentThreadPane
|
|
52
|
+
thread={thread}
|
|
53
|
+
client={client}
|
|
54
|
+
focused
|
|
55
|
+
theme={DARK}
|
|
56
|
+
onActiveChange={noop}
|
|
57
|
+
onOpenFile={noop}
|
|
58
|
+
>
|
|
59
|
+
{artifactView(thread)}
|
|
60
|
+
</AgentThreadPane>,
|
|
61
|
+
{ width: 100, height: 24 },
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await waitForText(setup, "Original artifact");
|
|
66
|
+
await settle(setup);
|
|
67
|
+
expect(setup.captureCharFrame()).toContain(failure);
|
|
68
|
+
expect(setup.captureCharFrame()).not.toContain("Thinking");
|
|
69
|
+
} finally {
|
|
70
|
+
setup.renderer.destroy();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
46
74
|
function createTestAgentClient(initial: ThreadAgentState) {
|
|
47
75
|
let state = initial;
|
|
48
76
|
const prompts: AgentPromptRequest[] = [];
|
|
@@ -56,6 +84,7 @@ function createTestAgentClient(initial: ThreadAgentState) {
|
|
|
56
84
|
agentGet: async () => state,
|
|
57
85
|
agentPrompt: async (params) => {
|
|
58
86
|
prompts.push(params);
|
|
87
|
+
|
|
59
88
|
if (params.text)
|
|
60
89
|
state = {
|
|
61
90
|
...state,
|
|
@@ -90,6 +119,8 @@ function artifactView(
|
|
|
90
119
|
value: Thread,
|
|
91
120
|
onReply: (id: string, body: string) => void = noop,
|
|
92
121
|
onUpdateAnnotation: (id: string, body: string) => void = noop,
|
|
122
|
+
onAnnotate: ThreadViewProps["onAnnotate"] = noop,
|
|
123
|
+
observer = false,
|
|
93
124
|
) {
|
|
94
125
|
const display = buildDisplay(value.artifact.content);
|
|
95
126
|
|
|
@@ -99,8 +130,8 @@ function artifactView(
|
|
|
99
130
|
display={display}
|
|
100
131
|
marks={marksByDisplay(value.annotations, display)}
|
|
101
132
|
quickActions={[]}
|
|
102
|
-
observer={
|
|
103
|
-
onAnnotate={
|
|
133
|
+
observer={observer}
|
|
134
|
+
onAnnotate={onAnnotate}
|
|
104
135
|
onReply={onReply}
|
|
105
136
|
onUpdateAnnotation={onUpdateAnnotation}
|
|
106
137
|
onExit={noop}
|
|
@@ -287,6 +318,7 @@ test("three bottom prompts survive delayed acceptance and remain separate submis
|
|
|
287
318
|
|
|
288
319
|
client.agentPrompt = async (params) => {
|
|
289
320
|
prompts.push(params.text);
|
|
321
|
+
|
|
290
322
|
if (prompts.length === 1)
|
|
291
323
|
return new Promise((resolve) => {
|
|
292
324
|
release = resolve;
|
|
@@ -868,6 +900,7 @@ test("an arriving reply preserves a bottom draft and submits it as the next prom
|
|
|
868
900
|
|
|
869
901
|
client.agentPrompt = async (params) => {
|
|
870
902
|
prompts.push(params);
|
|
903
|
+
|
|
871
904
|
if (prompts.length === 1)
|
|
872
905
|
return new Promise<ThreadAgentState>((resolve) => {
|
|
873
906
|
finish = resolve;
|
|
@@ -1038,6 +1071,7 @@ test("a failed mutation flush restores the prompt alongside a newer visible draf
|
|
|
1038
1071
|
onOpenFile={noop}
|
|
1039
1072
|
flushMutations={() => {
|
|
1040
1073
|
if (flushStarted) return Promise.resolve();
|
|
1074
|
+
|
|
1041
1075
|
flushStarted = true;
|
|
1042
1076
|
|
|
1043
1077
|
return failedFlush;
|
|
@@ -1158,3 +1192,57 @@ test("shared viewers see pending activity without owner permission controls", as
|
|
|
1158
1192
|
setup.renderer.destroy();
|
|
1159
1193
|
}
|
|
1160
1194
|
});
|
|
1195
|
+
|
|
1196
|
+
for (const observer of [false, true]) {
|
|
1197
|
+
test(`shared snapshot preserves artifact annotation permissions (observer=${observer})`, async () => {
|
|
1198
|
+
const value = {
|
|
1199
|
+
...thread,
|
|
1200
|
+
agentSnapshot: {
|
|
1201
|
+
...empty,
|
|
1202
|
+
messages: [
|
|
1203
|
+
{
|
|
1204
|
+
id: "answer",
|
|
1205
|
+
role: "agent" as const,
|
|
1206
|
+
text: "Frozen conversation",
|
|
1207
|
+
complete: true,
|
|
1208
|
+
revision: 1,
|
|
1209
|
+
},
|
|
1210
|
+
],
|
|
1211
|
+
},
|
|
1212
|
+
};
|
|
1213
|
+
const annotations: string[] = [];
|
|
1214
|
+
const setup = await testRender(
|
|
1215
|
+
<AgentThreadPrototype
|
|
1216
|
+
enabled={false}
|
|
1217
|
+
observer={observer}
|
|
1218
|
+
pixelPrototype={false}
|
|
1219
|
+
thread={value}
|
|
1220
|
+
focused
|
|
1221
|
+
theme={DARK}
|
|
1222
|
+
onActiveChange={noop}
|
|
1223
|
+
onOpenFile={noop}
|
|
1224
|
+
>
|
|
1225
|
+
{artifactView(
|
|
1226
|
+
value,
|
|
1227
|
+
noop,
|
|
1228
|
+
noop,
|
|
1229
|
+
(_span, body) => {
|
|
1230
|
+
annotations.push(body);
|
|
1231
|
+
},
|
|
1232
|
+
observer,
|
|
1233
|
+
)}
|
|
1234
|
+
</AgentThreadPrototype>,
|
|
1235
|
+
{ width: 100, height: 24, kittyKeyboard: true },
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
try {
|
|
1239
|
+
await waitForText(setup, "Frozen conversation");
|
|
1240
|
+
await dragText(setup, "Original artifact", "Original artifact", "Original artifact".length);
|
|
1241
|
+
await typeText(setup, "Artifact feedback");
|
|
1242
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
1243
|
+
expect(annotations).toEqual(observer ? [] : ["Artifact feedback"]);
|
|
1244
|
+
} finally {
|
|
1245
|
+
setup.renderer.destroy();
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
@@ -80,6 +80,7 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
80
80
|
|
|
81
81
|
useEffect(() => {
|
|
82
82
|
if (!busy) return;
|
|
83
|
+
|
|
83
84
|
const timer = setInterval(() => setPulse((value) => !value), 600);
|
|
84
85
|
|
|
85
86
|
return () => clearInterval(timer);
|
|
@@ -119,7 +120,9 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
119
120
|
|
|
120
121
|
const invoke = async (commentId?: string): Promise<void> => {
|
|
121
122
|
if (thread.status !== "pending") return;
|
|
123
|
+
|
|
122
124
|
if (!commentId && !draft.current.trim()) return;
|
|
125
|
+
|
|
123
126
|
invocations.current.push({
|
|
124
127
|
text: draft.current,
|
|
125
128
|
commentId: commentId
|
|
@@ -128,7 +131,9 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
128
131
|
writes: pendingWrites.current.splice(0),
|
|
129
132
|
});
|
|
130
133
|
draft.current = "";
|
|
134
|
+
|
|
131
135
|
if (invoking.current) return;
|
|
136
|
+
|
|
132
137
|
invoking.current = true;
|
|
133
138
|
|
|
134
139
|
const rejected: string[] = [];
|
|
@@ -144,6 +149,7 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
144
149
|
rejected.push(input.text);
|
|
145
150
|
continue;
|
|
146
151
|
}
|
|
152
|
+
|
|
147
153
|
await props.flushMutations?.();
|
|
148
154
|
const accepted = await agent.act((client) =>
|
|
149
155
|
client.agentPrompt({
|
|
@@ -219,6 +225,7 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
219
225
|
);
|
|
220
226
|
|
|
221
227
|
if (!submission) return undefined;
|
|
228
|
+
|
|
222
229
|
if (submission.status === "failed" && props.client?.canControlAgent !== false)
|
|
223
230
|
return {
|
|
224
231
|
label: "Retry",
|
|
@@ -293,6 +300,7 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
293
300
|
|
|
294
301
|
return;
|
|
295
302
|
}
|
|
303
|
+
|
|
296
304
|
if (source?.kind === "mirror") {
|
|
297
305
|
const mirrorId = projection.marks.get(span.start.blockIndex)?.[0]?.annotationId;
|
|
298
306
|
|
|
@@ -300,15 +308,18 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
300
308
|
|
|
301
309
|
return;
|
|
302
310
|
}
|
|
311
|
+
|
|
303
312
|
if (source?.kind === "artifact" && end?.kind === "artifact") {
|
|
304
313
|
return child?.onAnnotate(span, body);
|
|
305
314
|
}
|
|
315
|
+
|
|
306
316
|
if (
|
|
307
317
|
source?.kind !== "message" ||
|
|
308
318
|
end?.kind !== "message" ||
|
|
309
319
|
source.messageId !== end.messageId
|
|
310
320
|
)
|
|
311
321
|
return;
|
|
322
|
+
|
|
312
323
|
const message = state.messages.find((entry) => entry.id === source.messageId)!;
|
|
313
324
|
const localState = { ...state, messages: [message] };
|
|
314
325
|
const comment = commentOnAgentSpan(
|
|
@@ -347,6 +358,7 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
347
358
|
{state.phase.kind === "offline" ? (
|
|
348
359
|
<text fg={theme.textMuted}>Owner offline</text>
|
|
349
360
|
) : null}
|
|
361
|
+
{state.phase.kind === "failed" ? <text fg={theme.red}>{state.phase.error}</text> : null}
|
|
350
362
|
{busy ? <text fg={pulse ? theme.textDim : theme.textMuted}>Thinking…</text> : null}
|
|
351
363
|
{state.phase.kind === "permission" && props.client?.canControlAgent !== false ? (
|
|
352
364
|
<>
|
|
@@ -386,6 +398,55 @@ export function AgentThreadPane(props: AgentThreadPaneProps): React.ReactNode {
|
|
|
386
398
|
export function AgentThreadPrototype(
|
|
387
399
|
props: AgentThreadPaneProps & { enabled: boolean; observer: boolean; pixelPrototype: boolean },
|
|
388
400
|
): React.ReactNode {
|
|
401
|
+
if (
|
|
402
|
+
(!props.enabled || props.observer) &&
|
|
403
|
+
props.thread.agentSnapshot &&
|
|
404
|
+
React.isValidElement<ThreadViewProps>(props.children) &&
|
|
405
|
+
props.children.type === ThreadView
|
|
406
|
+
) {
|
|
407
|
+
const child = props.children.props;
|
|
408
|
+
const display =
|
|
409
|
+
child.display ??
|
|
410
|
+
buildDisplay(
|
|
411
|
+
props.thread.artifact.type === "diff"
|
|
412
|
+
? (props.thread.artifact.meta.prBrief ?? "")
|
|
413
|
+
: (props.thread.workingCopy ?? props.thread.artifact.content),
|
|
414
|
+
);
|
|
415
|
+
const projection = projectThreadConversation(
|
|
416
|
+
props.thread,
|
|
417
|
+
props.thread.agentSnapshot,
|
|
418
|
+
display,
|
|
419
|
+
child.marks ?? marksByDisplay(props.thread.annotations, display),
|
|
420
|
+
false,
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<ThreadView
|
|
425
|
+
{...child}
|
|
426
|
+
session={projection.session}
|
|
427
|
+
display={projection.display}
|
|
428
|
+
marks={projection.marks}
|
|
429
|
+
canAnnotateBlock={(index) =>
|
|
430
|
+
projection.sources[index]?.kind === "artifact" &&
|
|
431
|
+
(child.canAnnotateBlock?.(index) ?? true)
|
|
432
|
+
}
|
|
433
|
+
isAnnotationReadOnly={(id) =>
|
|
434
|
+
!props.thread.annotations.some((entry) => entry.id === id) ||
|
|
435
|
+
Boolean(child.isAnnotationReadOnly?.(id))
|
|
436
|
+
}
|
|
437
|
+
onAnnotate={(span, body) => {
|
|
438
|
+
if (
|
|
439
|
+
projection.sources[span.start.blockIndex]?.kind !== "artifact" ||
|
|
440
|
+
projection.sources[span.end.blockIndex]?.kind !== "artifact"
|
|
441
|
+
)
|
|
442
|
+
return;
|
|
443
|
+
|
|
444
|
+
return child.onAnnotate(span, body);
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
389
450
|
if (!props.enabled || props.observer || props.pixelPrototype) return props.children;
|
|
390
451
|
|
|
391
452
|
return <AgentThreadPane {...props} />;
|
|
@@ -6,6 +6,7 @@ function session(id: string, title: string, rootCommit?: string, remote?: string
|
|
|
6
6
|
const workspace: WorkspaceKey = { repoRoot: `/home/dev/${id}-checkout`, branch: "main" };
|
|
7
7
|
|
|
8
8
|
if (rootCommit !== undefined) workspace.rootCommit = rootCommit;
|
|
9
|
+
|
|
9
10
|
if (remote !== undefined) workspace.remote = remote;
|
|
10
11
|
|
|
11
12
|
return {
|
|
@@ -99,6 +99,7 @@ function fakeClient(initial: Thread, sink: WorkingCopySink): ThreadClient {
|
|
|
99
99
|
|
|
100
100
|
sink.workingCopy = content;
|
|
101
101
|
session = { ...session, workingCopy: content };
|
|
102
|
+
|
|
102
103
|
if (content === undefined) delete session.workingCopy;
|
|
103
104
|
|
|
104
105
|
return session;
|
|
@@ -111,6 +112,7 @@ function fakeClient(initial: Thread, sink: WorkingCopySink): ThreadClient {
|
|
|
111
112
|
|
|
112
113
|
sink.workingCopy = content;
|
|
113
114
|
session = { ...session, workingCopy: content };
|
|
115
|
+
|
|
114
116
|
if (rejections.length) session.curation = rejections;
|
|
115
117
|
else delete session.curation;
|
|
116
118
|
|
|
@@ -8,9 +8,16 @@ import {
|
|
|
8
8
|
SHARE_RECONNECT_MIN_MS,
|
|
9
9
|
type ShareTransport,
|
|
10
10
|
} from "./thread-controller";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
snapshotThreadConversation,
|
|
13
|
+
mergeFromShare,
|
|
14
|
+
type ShareWatchHandlers,
|
|
15
|
+
} from "../integrations/share";
|
|
12
16
|
|
|
13
|
-
const publishShare = mock(async () => ({
|
|
17
|
+
const publishShare = mock(async (_session: Thread) => ({
|
|
18
|
+
line: "ssh p_abc123xy@cueloop.dev",
|
|
19
|
+
copied: true,
|
|
20
|
+
}));
|
|
14
21
|
let remote: Thread;
|
|
15
22
|
const pullShare = mock(async () => remote);
|
|
16
23
|
const pushShare = mock(
|
|
@@ -571,3 +578,41 @@ describe("revoke", () => {
|
|
|
571
578
|
expect(client.sessionDelete).toHaveBeenCalledWith(session.id);
|
|
572
579
|
});
|
|
573
580
|
});
|
|
581
|
+
|
|
582
|
+
test("share publication captures an empty artifact's separate conversation without execution records", async () => {
|
|
583
|
+
const thread = sessionFixture({ artifact: { type: "plan", content: "", meta: {} } });
|
|
584
|
+
const { controller, client } = await connectedController(thread);
|
|
585
|
+
const state = {
|
|
586
|
+
threadId: thread.id,
|
|
587
|
+
phase: { kind: "idle" as const },
|
|
588
|
+
harness: { id: "pi", label: "pi", sessionId: "private-session" },
|
|
589
|
+
messages: [
|
|
590
|
+
{
|
|
591
|
+
id: "answer",
|
|
592
|
+
role: "agent" as const,
|
|
593
|
+
text: "Visible conversation",
|
|
594
|
+
complete: true,
|
|
595
|
+
revision: 1,
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
comments: [],
|
|
599
|
+
tools: [],
|
|
600
|
+
promptOperations: [
|
|
601
|
+
{ operationId: "private-operation", fingerprint: "private-hash", result: [] },
|
|
602
|
+
],
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
client.agentGet = async () => state;
|
|
606
|
+
publishShare.mockClear();
|
|
607
|
+
controller.share();
|
|
608
|
+
await tick();
|
|
609
|
+
await tick();
|
|
610
|
+
const shared = publishShare.mock.calls[0]![0];
|
|
611
|
+
|
|
612
|
+
expect(shared.agentSnapshot?.messages).toEqual(state.messages);
|
|
613
|
+
expect(shared.agentSnapshot?.harness).toEqual({ id: "pi", label: "pi" });
|
|
614
|
+
expect(shared.agentSnapshot?.promptOperations).toBeUndefined();
|
|
615
|
+
expect(thread.agentSnapshot).toBeUndefined();
|
|
616
|
+
expect(await snapshotThreadConversation(thread, {})).toBe(thread);
|
|
617
|
+
controller.close();
|
|
618
|
+
});
|