reigncode-app 1.3.2
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/AGENTS.md +30 -0
- package/Dockerfile +21 -0
- package/README.md +51 -0
- package/bunfig.toml +3 -0
- package/create-effect-simplification-spec.md +515 -0
- package/e2e/AGENTS.md +226 -0
- package/e2e/actions.ts +1018 -0
- package/e2e/app/home.spec.ts +24 -0
- package/e2e/app/navigation.spec.ts +10 -0
- package/e2e/app/palette.spec.ts +20 -0
- package/e2e/app/server-default.spec.ts +58 -0
- package/e2e/app/session.spec.ts +16 -0
- package/e2e/app/titlebar-history.spec.ts +120 -0
- package/e2e/commands/input-focus.spec.ts +15 -0
- package/e2e/commands/panels.spec.ts +33 -0
- package/e2e/commands/tab-close.spec.ts +32 -0
- package/e2e/files/file-open.spec.ts +31 -0
- package/e2e/files/file-tree.spec.ts +56 -0
- package/e2e/files/file-viewer.spec.ts +156 -0
- package/e2e/fixtures.ts +154 -0
- package/e2e/models/model-picker.spec.ts +48 -0
- package/e2e/models/models-visibility.spec.ts +61 -0
- package/e2e/projects/project-edit.spec.ts +43 -0
- package/e2e/projects/projects-close.spec.ts +54 -0
- package/e2e/projects/projects-switch.spec.ts +116 -0
- package/e2e/projects/workspace-new-session.spec.ts +94 -0
- package/e2e/projects/workspaces.spec.ts +375 -0
- package/e2e/prompt/context.spec.ts +95 -0
- package/e2e/prompt/prompt-async.spec.ts +76 -0
- package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
- package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
- package/e2e/prompt/prompt-history.spec.ts +184 -0
- package/e2e/prompt/prompt-mention.spec.ts +26 -0
- package/e2e/prompt/prompt-multiline.spec.ts +24 -0
- package/e2e/prompt/prompt-shell.spec.ts +62 -0
- package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
- package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
- package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
- package/e2e/prompt/prompt.spec.ts +55 -0
- package/e2e/selectors.ts +75 -0
- package/e2e/session/session-child-navigation.spec.ts +37 -0
- package/e2e/session/session-composer-dock.spec.ts +530 -0
- package/e2e/session/session-model-persistence.spec.ts +359 -0
- package/e2e/session/session-review.spec.ts +426 -0
- package/e2e/session/session-undo-redo.spec.ts +233 -0
- package/e2e/session/session.spec.ts +174 -0
- package/e2e/settings/settings-keybinds.spec.ts +389 -0
- package/e2e/settings/settings-models.spec.ts +122 -0
- package/e2e/settings/settings-providers.spec.ts +136 -0
- package/e2e/settings/settings.spec.ts +519 -0
- package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
- package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
- package/e2e/sidebar/sidebar.spec.ts +40 -0
- package/e2e/status/status-popover.spec.ts +94 -0
- package/e2e/terminal/terminal-init.spec.ts +28 -0
- package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
- package/e2e/terminal/terminal-tabs.spec.ts +168 -0
- package/e2e/terminal/terminal.spec.ts +18 -0
- package/e2e/thinking-level.spec.ts +25 -0
- package/e2e/tsconfig.json +9 -0
- package/e2e/utils.ts +63 -0
- package/happydom.ts +75 -0
- package/index.html +23 -0
- package/package.json +77 -0
- package/playwright.config.ts +45 -0
- package/public/_headers +17 -0
- package/public/oc-theme-preload.js +35 -0
- package/script/e2e-local.ts +180 -0
- package/src/addons/serialize.test.ts +319 -0
- package/src/addons/serialize.ts +634 -0
- package/src/app.tsx +308 -0
- package/src/components/debug-bar.tsx +443 -0
- package/src/components/dialog-connect-provider.tsx +617 -0
- package/src/components/dialog-custom-provider-form.ts +158 -0
- package/src/components/dialog-custom-provider.test.ts +80 -0
- package/src/components/dialog-custom-provider.tsx +329 -0
- package/src/components/dialog-edit-project.tsx +255 -0
- package/src/components/dialog-fork.tsx +108 -0
- package/src/components/dialog-manage-models.tsx +101 -0
- package/src/components/dialog-release-notes.tsx +144 -0
- package/src/components/dialog-select-directory.tsx +392 -0
- package/src/components/dialog-select-file.tsx +466 -0
- package/src/components/dialog-select-mcp.tsx +107 -0
- package/src/components/dialog-select-model-unpaid.tsx +137 -0
- package/src/components/dialog-select-model.tsx +220 -0
- package/src/components/dialog-select-provider.tsx +86 -0
- package/src/components/dialog-select-server.tsx +649 -0
- package/src/components/dialog-settings.tsx +73 -0
- package/src/components/file-tree.test.ts +78 -0
- package/src/components/file-tree.tsx +507 -0
- package/src/components/link.tsx +26 -0
- package/src/components/model-tooltip.tsx +91 -0
- package/src/components/prompt-input/attachments.test.ts +44 -0
- package/src/components/prompt-input/attachments.ts +201 -0
- package/src/components/prompt-input/build-request-parts.test.ts +312 -0
- package/src/components/prompt-input/build-request-parts.ts +175 -0
- package/src/components/prompt-input/context-items.tsx +88 -0
- package/src/components/prompt-input/drag-overlay.tsx +25 -0
- package/src/components/prompt-input/editor-dom.test.ts +99 -0
- package/src/components/prompt-input/editor-dom.ts +148 -0
- package/src/components/prompt-input/files.ts +66 -0
- package/src/components/prompt-input/history.test.ts +153 -0
- package/src/components/prompt-input/history.ts +256 -0
- package/src/components/prompt-input/image-attachments.tsx +58 -0
- package/src/components/prompt-input/paste.ts +24 -0
- package/src/components/prompt-input/placeholder.test.ts +48 -0
- package/src/components/prompt-input/placeholder.ts +15 -0
- package/src/components/prompt-input/slash-popover.tsx +141 -0
- package/src/components/prompt-input/submit.test.ts +346 -0
- package/src/components/prompt-input/submit.ts +579 -0
- package/src/components/prompt-input.tsx +1595 -0
- package/src/components/server/server-row.tsx +130 -0
- package/src/components/session/index.ts +5 -0
- package/src/components/session/session-context-breakdown.test.ts +61 -0
- package/src/components/session/session-context-breakdown.ts +132 -0
- package/src/components/session/session-context-format.ts +20 -0
- package/src/components/session/session-context-metrics.test.ts +101 -0
- package/src/components/session/session-context-metrics.ts +82 -0
- package/src/components/session/session-context-tab.tsx +339 -0
- package/src/components/session/session-header.tsx +486 -0
- package/src/components/session/session-new-view.tsx +91 -0
- package/src/components/session/session-sortable-tab.tsx +70 -0
- package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
- package/src/components/session-context-usage.tsx +122 -0
- package/src/components/settings-general.tsx +585 -0
- package/src/components/settings-keybinds.tsx +453 -0
- package/src/components/settings-list.tsx +5 -0
- package/src/components/settings-models.tsx +137 -0
- package/src/components/settings-providers.tsx +251 -0
- package/src/components/status-popover.tsx +419 -0
- package/src/components/terminal.tsx +653 -0
- package/src/components/titlebar-history.test.ts +63 -0
- package/src/components/titlebar-history.ts +57 -0
- package/src/components/titlebar.tsx +312 -0
- package/src/constants/file-picker.ts +89 -0
- package/src/context/command-keybind.test.ts +69 -0
- package/src/context/command.test.ts +25 -0
- package/src/context/command.tsx +437 -0
- package/src/context/comments.test.ts +186 -0
- package/src/context/comments.tsx +243 -0
- package/src/context/file/content-cache.ts +88 -0
- package/src/context/file/path.test.ts +360 -0
- package/src/context/file/path.ts +151 -0
- package/src/context/file/tree-store.ts +170 -0
- package/src/context/file/types.ts +41 -0
- package/src/context/file/view-cache.ts +146 -0
- package/src/context/file/watcher.test.ts +149 -0
- package/src/context/file/watcher.ts +53 -0
- package/src/context/file-content-eviction-accounting.test.ts +65 -0
- package/src/context/file.tsx +280 -0
- package/src/context/global-sdk.tsx +232 -0
- package/src/context/global-sync/bootstrap.ts +206 -0
- package/src/context/global-sync/child-store.test.ts +38 -0
- package/src/context/global-sync/child-store.ts +281 -0
- package/src/context/global-sync/event-reducer.test.ts +552 -0
- package/src/context/global-sync/event-reducer.ts +359 -0
- package/src/context/global-sync/eviction.ts +28 -0
- package/src/context/global-sync/queue.ts +83 -0
- package/src/context/global-sync/session-cache.test.ts +102 -0
- package/src/context/global-sync/session-cache.ts +62 -0
- package/src/context/global-sync/session-load.ts +25 -0
- package/src/context/global-sync/session-prefetch.test.ts +96 -0
- package/src/context/global-sync/session-prefetch.ts +100 -0
- package/src/context/global-sync/session-trim.test.ts +59 -0
- package/src/context/global-sync/session-trim.ts +56 -0
- package/src/context/global-sync/types.ts +133 -0
- package/src/context/global-sync/utils.ts +25 -0
- package/src/context/global-sync.test.ts +122 -0
- package/src/context/global-sync.tsx +408 -0
- package/src/context/highlights.tsx +233 -0
- package/src/context/language.tsx +248 -0
- package/src/context/layout-scroll.test.ts +64 -0
- package/src/context/layout-scroll.ts +126 -0
- package/src/context/layout.test.ts +69 -0
- package/src/context/layout.tsx +937 -0
- package/src/context/local.tsx +422 -0
- package/src/context/model-variant.test.ts +86 -0
- package/src/context/model-variant.ts +52 -0
- package/src/context/models.tsx +163 -0
- package/src/context/notification.tsx +373 -0
- package/src/context/permission-auto-respond.test.ts +102 -0
- package/src/context/permission-auto-respond.ts +51 -0
- package/src/context/permission.tsx +277 -0
- package/src/context/platform.tsx +99 -0
- package/src/context/prompt.tsx +297 -0
- package/src/context/sdk.tsx +49 -0
- package/src/context/server.tsx +295 -0
- package/src/context/settings.tsx +241 -0
- package/src/context/sync-optimistic.test.ts +123 -0
- package/src/context/sync.tsx +618 -0
- package/src/context/terminal-title.ts +51 -0
- package/src/context/terminal.test.ts +82 -0
- package/src/context/terminal.tsx +437 -0
- package/src/entry.tsx +144 -0
- package/src/env.d.ts +18 -0
- package/src/hooks/use-providers.ts +44 -0
- package/src/i18n/ar.ts +855 -0
- package/src/i18n/br.ts +867 -0
- package/src/i18n/bs.ts +943 -0
- package/src/i18n/da.ts +937 -0
- package/src/i18n/de.ts +879 -0
- package/src/i18n/en.ts +948 -0
- package/src/i18n/es.ts +950 -0
- package/src/i18n/fr.ts +878 -0
- package/src/i18n/ja.ts +861 -0
- package/src/i18n/ko.ts +860 -0
- package/src/i18n/no.ts +944 -0
- package/src/i18n/parity.test.ts +32 -0
- package/src/i18n/pl.ts +865 -0
- package/src/i18n/ru.ts +946 -0
- package/src/i18n/th.ts +933 -0
- package/src/i18n/tr.ts +952 -0
- package/src/i18n/zh.ts +930 -0
- package/src/i18n/zht.ts +925 -0
- package/src/index.css +29 -0
- package/src/index.ts +6 -0
- package/src/pages/directory-layout.tsx +88 -0
- package/src/pages/error.tsx +327 -0
- package/src/pages/home.tsx +131 -0
- package/src/pages/layout/deep-links.ts +50 -0
- package/src/pages/layout/helpers.test.ts +211 -0
- package/src/pages/layout/helpers.ts +98 -0
- package/src/pages/layout/inline-editor.tsx +126 -0
- package/src/pages/layout/sidebar-items.tsx +437 -0
- package/src/pages/layout/sidebar-project.tsx +384 -0
- package/src/pages/layout/sidebar-shell.tsx +125 -0
- package/src/pages/layout/sidebar-workspace.tsx +504 -0
- package/src/pages/layout.tsx +2509 -0
- package/src/pages/session/composer/index.ts +2 -0
- package/src/pages/session/composer/session-composer-region.tsx +255 -0
- package/src/pages/session/composer/session-composer-state.test.ts +128 -0
- package/src/pages/session/composer/session-composer-state.ts +249 -0
- package/src/pages/session/composer/session-followup-dock.tsx +109 -0
- package/src/pages/session/composer/session-permission-dock.tsx +74 -0
- package/src/pages/session/composer/session-question-dock.tsx +449 -0
- package/src/pages/session/composer/session-request-tree.ts +52 -0
- package/src/pages/session/composer/session-revert-dock.tsx +99 -0
- package/src/pages/session/composer/session-todo-dock.tsx +330 -0
- package/src/pages/session/file-tab-scroll.test.ts +40 -0
- package/src/pages/session/file-tab-scroll.ts +67 -0
- package/src/pages/session/file-tabs.tsx +456 -0
- package/src/pages/session/handoff.ts +36 -0
- package/src/pages/session/helpers.test.ts +181 -0
- package/src/pages/session/helpers.ts +198 -0
- package/src/pages/session/message-gesture.test.ts +62 -0
- package/src/pages/session/message-gesture.ts +21 -0
- package/src/pages/session/message-id-from-hash.ts +6 -0
- package/src/pages/session/message-timeline.tsx +1013 -0
- package/src/pages/session/review-tab.tsx +170 -0
- package/src/pages/session/session-layout.ts +20 -0
- package/src/pages/session/session-model-helpers.test.ts +51 -0
- package/src/pages/session/session-model-helpers.ts +16 -0
- package/src/pages/session/session-side-panel.tsx +453 -0
- package/src/pages/session/terminal-label.ts +16 -0
- package/src/pages/session/terminal-panel.test.ts +25 -0
- package/src/pages/session/terminal-panel.tsx +326 -0
- package/src/pages/session/use-session-commands.tsx +495 -0
- package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
- package/src/pages/session/use-session-hash-scroll.ts +197 -0
- package/src/pages/session.tsx +1841 -0
- package/src/sst-env.d.ts +12 -0
- package/src/testing/model-selection.ts +80 -0
- package/src/testing/prompt.ts +56 -0
- package/src/testing/session-composer.ts +84 -0
- package/src/testing/terminal.ts +118 -0
- package/src/theme-preload.test.ts +46 -0
- package/src/utils/agent.ts +23 -0
- package/src/utils/aim.ts +138 -0
- package/src/utils/base64.ts +10 -0
- package/src/utils/comment-note.ts +88 -0
- package/src/utils/id.ts +99 -0
- package/src/utils/notification-click.test.ts +27 -0
- package/src/utils/notification-click.ts +13 -0
- package/src/utils/persist.test.ts +115 -0
- package/src/utils/persist.ts +476 -0
- package/src/utils/prompt.test.ts +44 -0
- package/src/utils/prompt.ts +203 -0
- package/src/utils/runtime-adapters.test.ts +62 -0
- package/src/utils/runtime-adapters.ts +39 -0
- package/src/utils/same.ts +6 -0
- package/src/utils/scoped-cache.test.ts +69 -0
- package/src/utils/scoped-cache.ts +104 -0
- package/src/utils/server-errors.test.ts +131 -0
- package/src/utils/server-errors.ts +80 -0
- package/src/utils/server-health.test.ts +123 -0
- package/src/utils/server-health.ts +91 -0
- package/src/utils/server.ts +22 -0
- package/src/utils/solid-dnd.tsx +49 -0
- package/src/utils/sound.ts +117 -0
- package/src/utils/terminal-writer.test.ts +64 -0
- package/src/utils/terminal-writer.ts +65 -0
- package/src/utils/time.ts +22 -0
- package/src/utils/uuid.test.ts +78 -0
- package/src/utils/uuid.ts +12 -0
- package/src/utils/worktree.test.ts +46 -0
- package/src/utils/worktree.ts +73 -0
- package/sst-env.d.ts +10 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +15 -0
- package/vite.js +26 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { ToolPart } from "@reign-labs/sdk/v2/client"
|
|
2
|
+
import type { Page } from "@playwright/test"
|
|
3
|
+
import { test, expect } from "../fixtures"
|
|
4
|
+
import { withSession } from "../actions"
|
|
5
|
+
import { promptSelector } from "../selectors"
|
|
6
|
+
|
|
7
|
+
const text = (value: string | null) => (value ?? "").replace(/\u200B/g, "").trim()
|
|
8
|
+
|
|
9
|
+
const isBash = (part: unknown): part is ToolPart => {
|
|
10
|
+
if (!part || typeof part !== "object") return false
|
|
11
|
+
if (!("type" in part) || part.type !== "tool") return false
|
|
12
|
+
if (!("tool" in part) || part.tool !== "bash") return false
|
|
13
|
+
return "state" in part
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function edge(page: Page, pos: "start" | "end") {
|
|
17
|
+
await page.locator(promptSelector).evaluate((el: HTMLDivElement, pos: "start" | "end") => {
|
|
18
|
+
const selection = window.getSelection()
|
|
19
|
+
if (!selection) return
|
|
20
|
+
|
|
21
|
+
const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
|
|
22
|
+
const nodes: Text[] = []
|
|
23
|
+
for (let node = walk.nextNode(); node; node = walk.nextNode()) {
|
|
24
|
+
nodes.push(node as Text)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (nodes.length === 0) {
|
|
28
|
+
const node = document.createTextNode("")
|
|
29
|
+
el.appendChild(node)
|
|
30
|
+
nodes.push(node)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const node = pos === "start" ? nodes[0]! : nodes[nodes.length - 1]!
|
|
34
|
+
const range = document.createRange()
|
|
35
|
+
range.setStart(node, pos === "start" ? 0 : (node.textContent ?? "").length)
|
|
36
|
+
range.collapse(true)
|
|
37
|
+
selection.removeAllRanges()
|
|
38
|
+
selection.addRange(range)
|
|
39
|
+
}, pos)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function wait(page: Page, value: string) {
|
|
43
|
+
await expect.poll(async () => text(await page.locator(promptSelector).textContent())).toBe(value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function reply(sdk: Parameters<typeof withSession>[0], sessionID: string, token: string) {
|
|
47
|
+
await expect
|
|
48
|
+
.poll(
|
|
49
|
+
async () => {
|
|
50
|
+
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
|
|
51
|
+
return messages
|
|
52
|
+
.filter((item) => item.info.role === "assistant")
|
|
53
|
+
.flatMap((item) => item.parts)
|
|
54
|
+
.filter((item) => item.type === "text")
|
|
55
|
+
.map((item) => item.text)
|
|
56
|
+
.join("\n")
|
|
57
|
+
},
|
|
58
|
+
{ timeout: 90_000 },
|
|
59
|
+
)
|
|
60
|
+
.toContain(token)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function shell(sdk: Parameters<typeof withSession>[0], sessionID: string, cmd: string, token: string) {
|
|
64
|
+
await expect
|
|
65
|
+
.poll(
|
|
66
|
+
async () => {
|
|
67
|
+
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
|
|
68
|
+
const part = messages
|
|
69
|
+
.filter((item) => item.info.role === "assistant")
|
|
70
|
+
.flatMap((item) => item.parts)
|
|
71
|
+
.filter(isBash)
|
|
72
|
+
.find((item) => item.state.input?.command === cmd && item.state.status === "completed")
|
|
73
|
+
|
|
74
|
+
if (!part || part.state.status !== "completed") return
|
|
75
|
+
return typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
|
|
76
|
+
},
|
|
77
|
+
{ timeout: 90_000 },
|
|
78
|
+
)
|
|
79
|
+
.toContain(token)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test("prompt history restores unsent draft with arrow navigation", async ({ page, sdk, gotoSession }) => {
|
|
83
|
+
test.setTimeout(120_000)
|
|
84
|
+
|
|
85
|
+
await withSession(sdk, `e2e prompt history ${Date.now()}`, async (session) => {
|
|
86
|
+
await gotoSession(session.id)
|
|
87
|
+
|
|
88
|
+
const prompt = page.locator(promptSelector)
|
|
89
|
+
const firstToken = `E2E_HISTORY_ONE_${Date.now()}`
|
|
90
|
+
const secondToken = `E2E_HISTORY_TWO_${Date.now()}`
|
|
91
|
+
const first = `Reply with exactly: ${firstToken}`
|
|
92
|
+
const second = `Reply with exactly: ${secondToken}`
|
|
93
|
+
const draft = `draft ${Date.now()}`
|
|
94
|
+
|
|
95
|
+
await prompt.click()
|
|
96
|
+
await page.keyboard.type(first)
|
|
97
|
+
await page.keyboard.press("Enter")
|
|
98
|
+
await wait(page, "")
|
|
99
|
+
await reply(sdk, session.id, firstToken)
|
|
100
|
+
|
|
101
|
+
await prompt.click()
|
|
102
|
+
await page.keyboard.type(second)
|
|
103
|
+
await page.keyboard.press("Enter")
|
|
104
|
+
await wait(page, "")
|
|
105
|
+
await reply(sdk, session.id, secondToken)
|
|
106
|
+
|
|
107
|
+
await prompt.click()
|
|
108
|
+
await page.keyboard.type(draft)
|
|
109
|
+
await wait(page, draft)
|
|
110
|
+
|
|
111
|
+
// Clear the draft before navigating history (ArrowUp only works when prompt is empty)
|
|
112
|
+
await prompt.fill("")
|
|
113
|
+
await wait(page, "")
|
|
114
|
+
|
|
115
|
+
await page.keyboard.press("ArrowUp")
|
|
116
|
+
await wait(page, second)
|
|
117
|
+
|
|
118
|
+
await page.keyboard.press("ArrowUp")
|
|
119
|
+
await wait(page, first)
|
|
120
|
+
|
|
121
|
+
await page.keyboard.press("ArrowDown")
|
|
122
|
+
await wait(page, second)
|
|
123
|
+
|
|
124
|
+
await page.keyboard.press("ArrowDown")
|
|
125
|
+
await wait(page, "")
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("shell history stays separate from normal prompt history", async ({ page, sdk, gotoSession }) => {
|
|
130
|
+
test.setTimeout(120_000)
|
|
131
|
+
|
|
132
|
+
await withSession(sdk, `e2e shell history ${Date.now()}`, async (session) => {
|
|
133
|
+
await gotoSession(session.id)
|
|
134
|
+
|
|
135
|
+
const prompt = page.locator(promptSelector)
|
|
136
|
+
const firstToken = `E2E_SHELL_ONE_${Date.now()}`
|
|
137
|
+
const secondToken = `E2E_SHELL_TWO_${Date.now()}`
|
|
138
|
+
const normalToken = `E2E_NORMAL_${Date.now()}`
|
|
139
|
+
const first = `echo ${firstToken}`
|
|
140
|
+
const second = `echo ${secondToken}`
|
|
141
|
+
const normal = `Reply with exactly: ${normalToken}`
|
|
142
|
+
|
|
143
|
+
await prompt.click()
|
|
144
|
+
await page.keyboard.type("!")
|
|
145
|
+
await page.keyboard.type(first)
|
|
146
|
+
await page.keyboard.press("Enter")
|
|
147
|
+
await wait(page, "")
|
|
148
|
+
await shell(sdk, session.id, first, firstToken)
|
|
149
|
+
|
|
150
|
+
await prompt.click()
|
|
151
|
+
await page.keyboard.type("!")
|
|
152
|
+
await page.keyboard.type(second)
|
|
153
|
+
await page.keyboard.press("Enter")
|
|
154
|
+
await wait(page, "")
|
|
155
|
+
await shell(sdk, session.id, second, secondToken)
|
|
156
|
+
|
|
157
|
+
await prompt.click()
|
|
158
|
+
await page.keyboard.type("!")
|
|
159
|
+
await page.keyboard.press("ArrowUp")
|
|
160
|
+
await wait(page, second)
|
|
161
|
+
|
|
162
|
+
await page.keyboard.press("ArrowUp")
|
|
163
|
+
await wait(page, first)
|
|
164
|
+
|
|
165
|
+
await page.keyboard.press("ArrowDown")
|
|
166
|
+
await wait(page, second)
|
|
167
|
+
|
|
168
|
+
await page.keyboard.press("ArrowDown")
|
|
169
|
+
await wait(page, "")
|
|
170
|
+
|
|
171
|
+
await page.keyboard.press("Escape")
|
|
172
|
+
await wait(page, "")
|
|
173
|
+
|
|
174
|
+
await prompt.click()
|
|
175
|
+
await page.keyboard.type(normal)
|
|
176
|
+
await page.keyboard.press("Enter")
|
|
177
|
+
await wait(page, "")
|
|
178
|
+
await reply(sdk, session.id, normalToken)
|
|
179
|
+
|
|
180
|
+
await prompt.click()
|
|
181
|
+
await page.keyboard.press("ArrowUp")
|
|
182
|
+
await wait(page, normal)
|
|
183
|
+
})
|
|
184
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { promptSelector } from "../selectors"
|
|
3
|
+
|
|
4
|
+
test("smoke @mention inserts file pill token", async ({ page, gotoSession }) => {
|
|
5
|
+
await gotoSession()
|
|
6
|
+
|
|
7
|
+
await page.locator(promptSelector).click()
|
|
8
|
+
const sep = process.platform === "win32" ? "\\" : "/"
|
|
9
|
+
const file = ["packages", "app", "package.json"].join(sep)
|
|
10
|
+
const filePattern = /packages[\\/]+app[\\/]+\s*package\.json/
|
|
11
|
+
|
|
12
|
+
await page.keyboard.type(`@${file}`)
|
|
13
|
+
|
|
14
|
+
const suggestion = page.getByRole("button", { name: filePattern }).first()
|
|
15
|
+
await expect(suggestion).toBeVisible()
|
|
16
|
+
await suggestion.hover()
|
|
17
|
+
|
|
18
|
+
await page.keyboard.press("Tab")
|
|
19
|
+
|
|
20
|
+
const pill = page.locator(`${promptSelector} [data-type="file"]`).first()
|
|
21
|
+
await expect(pill).toBeVisible()
|
|
22
|
+
await expect(pill).toHaveAttribute("data-path", filePattern)
|
|
23
|
+
|
|
24
|
+
await page.keyboard.type(" ok")
|
|
25
|
+
await expect(page.locator(promptSelector)).toContainText("ok")
|
|
26
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { promptSelector } from "../selectors"
|
|
3
|
+
|
|
4
|
+
test("shift+enter inserts a newline without submitting", async ({ page, gotoSession }) => {
|
|
5
|
+
await gotoSession()
|
|
6
|
+
|
|
7
|
+
await expect(page).toHaveURL(/\/session\/?$/)
|
|
8
|
+
|
|
9
|
+
const prompt = page.locator(promptSelector)
|
|
10
|
+
await prompt.focus()
|
|
11
|
+
await expect(prompt).toBeFocused()
|
|
12
|
+
|
|
13
|
+
await prompt.pressSequentially("line one")
|
|
14
|
+
await expect(prompt).toBeFocused()
|
|
15
|
+
|
|
16
|
+
await prompt.press("Shift+Enter")
|
|
17
|
+
await expect(page).toHaveURL(/\/session\/?$/)
|
|
18
|
+
await expect(prompt).toBeFocused()
|
|
19
|
+
|
|
20
|
+
await prompt.pressSequentially("line two")
|
|
21
|
+
|
|
22
|
+
await expect(page).toHaveURL(/\/session\/?$/)
|
|
23
|
+
await expect.poll(() => prompt.evaluate((el) => el.innerText)).toBe("line one\nline two")
|
|
24
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ToolPart } from "@reign-labs/sdk/v2/client"
|
|
2
|
+
import { test, expect } from "../fixtures"
|
|
3
|
+
import { sessionIDFromUrl } from "../actions"
|
|
4
|
+
import { promptSelector } from "../selectors"
|
|
5
|
+
import { createSdk } from "../utils"
|
|
6
|
+
|
|
7
|
+
const isBash = (part: unknown): part is ToolPart => {
|
|
8
|
+
if (!part || typeof part !== "object") return false
|
|
9
|
+
if (!("type" in part) || part.type !== "tool") return false
|
|
10
|
+
if (!("tool" in part) || part.tool !== "bash") return false
|
|
11
|
+
return "state" in part
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("shell mode runs a command in the project directory", async ({ page, withProject }) => {
|
|
15
|
+
test.setTimeout(120_000)
|
|
16
|
+
|
|
17
|
+
await withProject(async ({ directory, gotoSession, trackSession }) => {
|
|
18
|
+
const sdk = createSdk(directory)
|
|
19
|
+
const prompt = page.locator(promptSelector)
|
|
20
|
+
const cmd = process.platform === "win32" ? "dir" : "ls"
|
|
21
|
+
|
|
22
|
+
await gotoSession()
|
|
23
|
+
await prompt.click()
|
|
24
|
+
await page.keyboard.type("!")
|
|
25
|
+
await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
|
|
26
|
+
|
|
27
|
+
await page.keyboard.type(cmd)
|
|
28
|
+
await page.keyboard.press("Enter")
|
|
29
|
+
|
|
30
|
+
await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
|
|
31
|
+
|
|
32
|
+
const id = sessionIDFromUrl(page.url())
|
|
33
|
+
if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
|
|
34
|
+
trackSession(id, directory)
|
|
35
|
+
|
|
36
|
+
await expect
|
|
37
|
+
.poll(
|
|
38
|
+
async () => {
|
|
39
|
+
const list = await sdk.session.messages({ sessionID: id, limit: 50 }).then((x) => x.data ?? [])
|
|
40
|
+
const msg = list.findLast(
|
|
41
|
+
(item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === directory,
|
|
42
|
+
)
|
|
43
|
+
if (!msg) return
|
|
44
|
+
|
|
45
|
+
const part = msg.parts
|
|
46
|
+
.filter(isBash)
|
|
47
|
+
.find((item) => item.state.input?.command === cmd && item.state.status === "completed")
|
|
48
|
+
|
|
49
|
+
if (!part || part.state.status !== "completed") return
|
|
50
|
+
const output =
|
|
51
|
+
typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
|
|
52
|
+
if (!output.includes("README.md")) return
|
|
53
|
+
|
|
54
|
+
return { cwd: directory, output }
|
|
55
|
+
},
|
|
56
|
+
{ timeout: 90_000 },
|
|
57
|
+
)
|
|
58
|
+
.toEqual(expect.objectContaining({ cwd: directory, output: expect.stringContaining("README.md") }))
|
|
59
|
+
|
|
60
|
+
await expect(prompt).toHaveText("")
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { promptSelector } from "../selectors"
|
|
3
|
+
|
|
4
|
+
test("smoke /open opens file picker dialog", async ({ page, gotoSession }) => {
|
|
5
|
+
await gotoSession()
|
|
6
|
+
|
|
7
|
+
await page.locator(promptSelector).click()
|
|
8
|
+
await page.keyboard.type("/open")
|
|
9
|
+
|
|
10
|
+
const command = page.locator('[data-slash-id="file.open"]')
|
|
11
|
+
await expect(command).toBeVisible()
|
|
12
|
+
await command.hover()
|
|
13
|
+
|
|
14
|
+
await page.keyboard.press("Enter")
|
|
15
|
+
|
|
16
|
+
const dialog = page.getByRole("dialog")
|
|
17
|
+
await expect(dialog).toBeVisible()
|
|
18
|
+
await expect(dialog.getByRole("textbox").first()).toBeVisible()
|
|
19
|
+
|
|
20
|
+
await page.keyboard.press("Escape")
|
|
21
|
+
await expect(dialog).toHaveCount(0)
|
|
22
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { promptSelector } from "../selectors"
|
|
3
|
+
import { withSession } from "../actions"
|
|
4
|
+
|
|
5
|
+
const shareDisabled = process.env.REIGNCODE_DISABLE_SHARE === "true" || process.env.REIGNCODE_DISABLE_SHARE === "1"
|
|
6
|
+
|
|
7
|
+
async function seed(sdk: Parameters<typeof withSession>[0], sessionID: string) {
|
|
8
|
+
await sdk.session.promptAsync({
|
|
9
|
+
sessionID,
|
|
10
|
+
noReply: true,
|
|
11
|
+
parts: [{ type: "text", text: "e2e share seed" }],
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
await expect
|
|
15
|
+
.poll(
|
|
16
|
+
async () => {
|
|
17
|
+
const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
|
|
18
|
+
return messages.length
|
|
19
|
+
},
|
|
20
|
+
{ timeout: 30_000 },
|
|
21
|
+
)
|
|
22
|
+
.toBeGreaterThan(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test("/share and /unshare update session share state", async ({ page, sdk, gotoSession }) => {
|
|
26
|
+
test.skip(shareDisabled, "Share is disabled in this environment (REIGNCODE_DISABLE_SHARE).")
|
|
27
|
+
|
|
28
|
+
await withSession(sdk, `e2e slash share ${Date.now()}`, async (session) => {
|
|
29
|
+
const prompt = page.locator(promptSelector)
|
|
30
|
+
|
|
31
|
+
await seed(sdk, session.id)
|
|
32
|
+
await gotoSession(session.id)
|
|
33
|
+
|
|
34
|
+
await prompt.click()
|
|
35
|
+
await page.keyboard.type("/share")
|
|
36
|
+
await expect(page.locator('[data-slash-id="session.share"]').first()).toBeVisible()
|
|
37
|
+
await page.keyboard.press("Enter")
|
|
38
|
+
|
|
39
|
+
await expect
|
|
40
|
+
.poll(
|
|
41
|
+
async () => {
|
|
42
|
+
const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
|
|
43
|
+
return data?.share?.url || undefined
|
|
44
|
+
},
|
|
45
|
+
{ timeout: 30_000 },
|
|
46
|
+
)
|
|
47
|
+
.not.toBeUndefined()
|
|
48
|
+
|
|
49
|
+
await prompt.click()
|
|
50
|
+
await page.keyboard.type("/unshare")
|
|
51
|
+
await expect(page.locator('[data-slash-id="session.unshare"]').first()).toBeVisible()
|
|
52
|
+
await page.keyboard.press("Enter")
|
|
53
|
+
|
|
54
|
+
await expect
|
|
55
|
+
.poll(
|
|
56
|
+
async () => {
|
|
57
|
+
const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
|
|
58
|
+
return data?.share?.url || undefined
|
|
59
|
+
},
|
|
60
|
+
{ timeout: 30_000 },
|
|
61
|
+
)
|
|
62
|
+
.toBeUndefined()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { runPromptSlash, waitTerminalFocusIdle } from "../actions"
|
|
3
|
+
import { promptSelector, terminalSelector } from "../selectors"
|
|
4
|
+
|
|
5
|
+
test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => {
|
|
6
|
+
await gotoSession()
|
|
7
|
+
|
|
8
|
+
const prompt = page.locator(promptSelector)
|
|
9
|
+
const terminal = page.locator(terminalSelector)
|
|
10
|
+
|
|
11
|
+
await expect(terminal).not.toBeVisible()
|
|
12
|
+
|
|
13
|
+
await runPromptSlash(page, { prompt, text: "/terminal", id: "terminal.toggle" })
|
|
14
|
+
await waitTerminalFocusIdle(page, { term: terminal })
|
|
15
|
+
|
|
16
|
+
await runPromptSlash(page, { prompt, text: "/terminal", id: "terminal.toggle" })
|
|
17
|
+
await expect(terminal).not.toBeVisible()
|
|
18
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { promptSelector } from "../selectors"
|
|
3
|
+
import { cleanupSession, sessionIDFromUrl, withSession } from "../actions"
|
|
4
|
+
|
|
5
|
+
test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession }) => {
|
|
6
|
+
test.setTimeout(120_000)
|
|
7
|
+
|
|
8
|
+
const pageErrors: string[] = []
|
|
9
|
+
const onPageError = (err: Error) => {
|
|
10
|
+
pageErrors.push(err.message)
|
|
11
|
+
}
|
|
12
|
+
page.on("pageerror", onPageError)
|
|
13
|
+
|
|
14
|
+
await gotoSession()
|
|
15
|
+
|
|
16
|
+
const token = `E2E_OK_${Date.now()}`
|
|
17
|
+
|
|
18
|
+
const prompt = page.locator(promptSelector)
|
|
19
|
+
await prompt.click()
|
|
20
|
+
await page.keyboard.type(`Reply with exactly: ${token}`)
|
|
21
|
+
await page.keyboard.press("Enter")
|
|
22
|
+
|
|
23
|
+
await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
|
|
24
|
+
|
|
25
|
+
const sessionID = (() => {
|
|
26
|
+
const id = sessionIDFromUrl(page.url())
|
|
27
|
+
if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
|
|
28
|
+
return id
|
|
29
|
+
})()
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await expect
|
|
33
|
+
.poll(
|
|
34
|
+
async () => {
|
|
35
|
+
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
|
|
36
|
+
return messages
|
|
37
|
+
.filter((m) => m.info.role === "assistant")
|
|
38
|
+
.flatMap((m) => m.parts)
|
|
39
|
+
.filter((p) => p.type === "text")
|
|
40
|
+
.map((p) => p.text)
|
|
41
|
+
.join("\n")
|
|
42
|
+
},
|
|
43
|
+
{ timeout: 90_000 },
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
.toContain(token)
|
|
47
|
+
} finally {
|
|
48
|
+
page.off("pageerror", onPageError)
|
|
49
|
+
await cleanupSession({ sdk, sessionID })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (pageErrors.length > 0) {
|
|
53
|
+
throw new Error(`Page error(s):\n${pageErrors.join("\n")}`)
|
|
54
|
+
}
|
|
55
|
+
})
|
package/e2e/selectors.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const promptSelector = '[data-component="prompt-input"]'
|
|
2
|
+
export const terminalPanelSelector = '#terminal-panel[aria-hidden="false"]'
|
|
3
|
+
export const terminalSelector = `${terminalPanelSelector} [data-component="terminal"]`
|
|
4
|
+
export const sessionComposerDockSelector = '[data-component="session-prompt-dock"]'
|
|
5
|
+
export const questionDockSelector = '[data-component="dock-prompt"][data-kind="question"]'
|
|
6
|
+
export const permissionDockSelector = '[data-component="dock-prompt"][data-kind="permission"]'
|
|
7
|
+
export const permissionRejectSelector = `${permissionDockSelector} [data-slot="permission-footer-actions"] [data-component="button"]:nth-child(1)`
|
|
8
|
+
export const permissionAllowAlwaysSelector = `${permissionDockSelector} [data-slot="permission-footer-actions"] [data-component="button"]:nth-child(2)`
|
|
9
|
+
export const permissionAllowOnceSelector = `${permissionDockSelector} [data-slot="permission-footer-actions"] [data-component="button"]:nth-child(3)`
|
|
10
|
+
export const sessionTodoDockSelector = '[data-component="session-todo-dock"]'
|
|
11
|
+
export const sessionTodoToggleSelector = '[data-action="session-todo-toggle"]'
|
|
12
|
+
export const sessionTodoToggleButtonSelector = '[data-action="session-todo-toggle-button"]'
|
|
13
|
+
export const sessionTodoListSelector = '[data-slot="session-todo-list"]'
|
|
14
|
+
|
|
15
|
+
export const modelVariantCycleSelector = '[data-action="model-variant-cycle"]'
|
|
16
|
+
export const promptAgentSelector = '[data-component="prompt-agent-control"]'
|
|
17
|
+
export const promptModelSelector = '[data-component="prompt-model-control"]'
|
|
18
|
+
export const promptVariantSelector = '[data-component="prompt-variant-control"]'
|
|
19
|
+
export const settingsLanguageSelectSelector = '[data-action="settings-language"]'
|
|
20
|
+
export const settingsColorSchemeSelector = '[data-action="settings-color-scheme"]'
|
|
21
|
+
export const settingsThemeSelector = '[data-action="settings-theme"]'
|
|
22
|
+
export const settingsFontSelector = '[data-action="settings-font"]'
|
|
23
|
+
export const settingsNotificationsAgentSelector = '[data-action="settings-notifications-agent"]'
|
|
24
|
+
export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]'
|
|
25
|
+
export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]'
|
|
26
|
+
export const settingsSoundsAgentSelector = '[data-action="settings-sounds-agent"]'
|
|
27
|
+
export const settingsSoundsPermissionsSelector = '[data-action="settings-sounds-permissions"]'
|
|
28
|
+
export const settingsSoundsErrorsSelector = '[data-action="settings-sounds-errors"]'
|
|
29
|
+
export const settingsUpdatesStartupSelector = '[data-action="settings-updates-startup"]'
|
|
30
|
+
export const settingsReleaseNotesSelector = '[data-action="settings-release-notes"]'
|
|
31
|
+
|
|
32
|
+
export const sidebarNavSelector = '[data-component="sidebar-nav-desktop"]'
|
|
33
|
+
|
|
34
|
+
export const projectSwitchSelector = (slug: string) =>
|
|
35
|
+
`${sidebarNavSelector} [data-action="project-switch"][data-project="${slug}"]`
|
|
36
|
+
|
|
37
|
+
export const projectMenuTriggerSelector = (slug: string) =>
|
|
38
|
+
`${sidebarNavSelector} [data-action="project-menu"][data-project="${slug}"]`
|
|
39
|
+
|
|
40
|
+
export const projectCloseMenuSelector = (slug: string) => `[data-action="project-close-menu"][data-project="${slug}"]`
|
|
41
|
+
|
|
42
|
+
export const projectClearNotificationsSelector = (slug: string) =>
|
|
43
|
+
`[data-action="project-clear-notifications"][data-project="${slug}"]`
|
|
44
|
+
|
|
45
|
+
export const projectWorkspacesToggleSelector = (slug: string) =>
|
|
46
|
+
`[data-action="project-workspaces-toggle"][data-project="${slug}"]`
|
|
47
|
+
|
|
48
|
+
export const titlebarRightSelector = "#opencode-titlebar-right"
|
|
49
|
+
|
|
50
|
+
export const popoverBodySelector = '[data-slot="popover-body"]'
|
|
51
|
+
|
|
52
|
+
export const dropdownMenuTriggerSelector = '[data-slot="dropdown-menu-trigger"]'
|
|
53
|
+
|
|
54
|
+
export const dropdownMenuContentSelector = '[data-component="dropdown-menu-content"]'
|
|
55
|
+
|
|
56
|
+
export const inlineInputSelector = '[data-component="inline-input"]'
|
|
57
|
+
|
|
58
|
+
export const sessionItemSelector = (sessionID: string) => `${sidebarNavSelector} [data-session-id="${sessionID}"]`
|
|
59
|
+
|
|
60
|
+
export const workspaceItemSelector = (slug: string) =>
|
|
61
|
+
`${sidebarNavSelector} [data-component="workspace-item"][data-workspace="${slug}"]`
|
|
62
|
+
|
|
63
|
+
export const workspaceMenuTriggerSelector = (slug: string) =>
|
|
64
|
+
`${sidebarNavSelector} [data-action="workspace-menu"][data-workspace="${slug}"]`
|
|
65
|
+
|
|
66
|
+
export const workspaceNewSessionSelector = (slug: string) =>
|
|
67
|
+
`${sidebarNavSelector} [data-action="workspace-new-session"][data-workspace="${slug}"]`
|
|
68
|
+
|
|
69
|
+
export const listItemSelector = '[data-slot="list-item"]'
|
|
70
|
+
|
|
71
|
+
export const listItemKeyStartsWithSelector = (prefix: string) => `${listItemSelector}[data-key^="${prefix}"]`
|
|
72
|
+
|
|
73
|
+
export const listItemKeySelector = (key: string) => `${listItemSelector}[data-key="${key}"]`
|
|
74
|
+
|
|
75
|
+
export const keybindButtonSelector = (id: string) => `[data-keybind-id="${id}"]`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { seedSessionTask, withSession } from "../actions"
|
|
2
|
+
import { test, expect } from "../fixtures"
|
|
3
|
+
|
|
4
|
+
test("task tool child-session link does not trigger stale show errors", async ({ page, sdk, gotoSession }) => {
|
|
5
|
+
test.setTimeout(120_000)
|
|
6
|
+
|
|
7
|
+
const errs: string[] = []
|
|
8
|
+
const onError = (err: Error) => {
|
|
9
|
+
errs.push(err.message)
|
|
10
|
+
}
|
|
11
|
+
page.on("pageerror", onError)
|
|
12
|
+
|
|
13
|
+
await withSession(sdk, `e2e child nav ${Date.now()}`, async (session) => {
|
|
14
|
+
const child = await seedSessionTask(sdk, {
|
|
15
|
+
sessionID: session.id,
|
|
16
|
+
description: "Open child session",
|
|
17
|
+
prompt: "Search the repository for AssistantParts and then reply with exactly CHILD_OK.",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await gotoSession(session.id)
|
|
22
|
+
|
|
23
|
+
const link = page
|
|
24
|
+
.locator("a.subagent-link")
|
|
25
|
+
.filter({ hasText: /open child session/i })
|
|
26
|
+
.first()
|
|
27
|
+
await expect(link).toBeVisible({ timeout: 30_000 })
|
|
28
|
+
await link.click()
|
|
29
|
+
|
|
30
|
+
await expect(page).toHaveURL(new RegExp(`/session/${child.sessionID}(?:[/?#]|$)`), { timeout: 30_000 })
|
|
31
|
+
await page.waitForTimeout(1000)
|
|
32
|
+
expect(errs).toEqual([])
|
|
33
|
+
} finally {
|
|
34
|
+
page.off("pageerror", onError)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
})
|