saeeol 1.2.0 → 1.2.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/package.json +14 -14
- package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
- package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
- package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
- package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
- package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
- package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
- package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
- package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
- package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
- package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
- package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
- package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
- package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
- package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
- package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
- package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
- package/src/session/compaction-helpers.ts +1 -169
- package/src/session/compaction.ts +1 -712
- package/src/session/core/compaction/compaction-helpers.ts +169 -0
- package/src/session/core/compaction/compaction.ts +712 -0
- package/src/session/core/compaction/overflow.ts +28 -0
- package/src/session/core/instruction.ts +234 -0
- package/src/session/core/llm.ts +504 -0
- package/src/session/core/network.ts +392 -0
- package/src/session/core/processor.ts +731 -0
- package/src/session/core/projectors.ts +139 -0
- package/src/session/core/resolve-tools.ts +241 -0
- package/src/session/core/retry.ts +149 -0
- package/src/session/core/revert.ts +173 -0
- package/src/session/core/run-state.ts +110 -0
- package/src/session/core/schema.ts +35 -0
- package/src/session/core/session-types.ts +160 -0
- package/src/session/core/session.sql.ts +124 -0
- package/src/session/core/session.ts +948 -0
- package/src/session/core/shell-exec.ts +205 -0
- package/src/session/core/status.ts +100 -0
- package/src/session/core/subtask.ts +268 -0
- package/src/session/core/summary.ts +173 -0
- package/src/session/core/system.ts +114 -0
- package/src/session/core/todo.ts +86 -0
- package/src/session/core/user-part.ts +293 -0
- package/src/session/instruction.ts +1 -234
- package/src/session/llm.ts +1 -504
- package/src/session/message/message-errors.ts +83 -0
- package/src/session/message/message-parts.ts +89 -0
- package/src/session/message/message-query.ts +107 -0
- package/src/session/message/message-transform.ts +156 -0
- package/src/session/message/message-types.ts +68 -0
- package/src/session/message/message-v2.ts +73 -0
- package/src/session/message/message.ts +192 -0
- package/src/session/message-errors.ts +1 -83
- package/src/session/message-parts.ts +1 -89
- package/src/session/message-query.ts +1 -107
- package/src/session/message-transform.ts +1 -156
- package/src/session/message-types.ts +1 -68
- package/src/session/message-v2.ts +1 -73
- package/src/session/message.ts +1 -192
- package/src/session/network.ts +1 -392
- package/src/session/overflow.ts +1 -28
- package/src/session/processor.ts +1 -731
- package/src/session/projectors.ts +2 -139
- package/src/session/prompt/prompt-command.ts +93 -0
- package/src/session/prompt/prompt-loop.ts +299 -0
- package/src/session/prompt/prompt-model.ts +44 -0
- package/src/session/prompt/prompt-reminders.ts +120 -0
- package/src/session/prompt/prompt-resolve.ts +42 -0
- package/src/session/prompt/prompt-schemas.ts +128 -0
- package/src/session/prompt/prompt-title.ts +55 -0
- package/src/session/prompt/prompt-types.ts +47 -0
- package/src/session/prompt/prompt-user-msg.ts +80 -0
- package/src/session/prompt/prompt.ts +211 -0
- package/src/session/prompt-command.ts +1 -93
- package/src/session/prompt-loop.ts +1 -299
- package/src/session/prompt-model.ts +1 -44
- package/src/session/prompt-reminders.ts +1 -120
- package/src/session/prompt-resolve.ts +1 -42
- package/src/session/prompt-schemas.ts +1 -128
- package/src/session/prompt-title.ts +1 -55
- package/src/session/prompt-types.ts +1 -47
- package/src/session/prompt-user-msg.ts +1 -80
- package/src/session/prompt.ts +1 -211
- package/src/session/resolve-tools.ts +1 -241
- package/src/session/retry.ts +1 -149
- package/src/session/revert.ts +1 -173
- package/src/session/run-state.ts +1 -110
- package/src/session/schema.ts +1 -35
- package/src/session/session-types.ts +1 -160
- package/src/session/session.sql.ts +1 -124
- package/src/session/session.ts +1 -948
- package/src/session/shell-exec.ts +1 -205
- package/src/session/status.ts +1 -100
- package/src/session/subtask.ts +1 -268
- package/src/session/summary.ts +1 -173
- package/src/session/system.ts +1 -114
- package/src/session/todo.ts +1 -86
- package/src/session/user-part.ts +1 -293
- package/src/tool/apply_patch.ts +1 -334
- package/src/tool/bash.ts +1 -656
- package/src/tool/core/external-directory.ts +55 -0
- package/src/tool/core/invalid.ts +21 -0
- package/src/tool/core/recall.ts +164 -0
- package/src/tool/core/recall.txt +12 -0
- package/src/tool/core/schema.ts +16 -0
- package/src/tool/core/tool.ts +162 -0
- package/src/tool/core/truncate.ts +160 -0
- package/src/tool/core/truncation-dir.ts +4 -0
- package/src/tool/diagnostics.ts +1 -20
- package/src/tool/edit-replacers.ts +1 -288
- package/src/tool/edit-utils.ts +1 -86
- package/src/tool/edit.ts +1 -262
- package/src/tool/external-directory.ts +1 -55
- package/src/tool/file/apply_patch.ts +334 -0
- package/src/tool/file/apply_patch.txt +33 -0
- package/src/tool/file/bash.ts +656 -0
- package/src/tool/file/bash.txt +119 -0
- package/src/tool/file/edit-replacers.ts +288 -0
- package/src/tool/file/edit-utils.ts +86 -0
- package/src/tool/file/edit.ts +262 -0
- package/src/tool/file/edit.txt +10 -0
- package/src/tool/file/read.ts +389 -0
- package/src/tool/file/read.txt +14 -0
- package/src/tool/file/write.ts +114 -0
- package/src/tool/file/write.txt +8 -0
- package/src/tool/glob.ts +1 -115
- package/src/tool/grep.ts +1 -151
- package/src/tool/integration/diagnostics.ts +20 -0
- package/src/tool/integration/lsp.ts +113 -0
- package/src/tool/integration/lsp.txt +24 -0
- package/src/tool/integration/mcp-exa.ts +73 -0
- package/src/tool/integration/package.ts +168 -0
- package/src/tool/integration/registry.ts +375 -0
- package/src/tool/invalid.ts +1 -21
- package/src/tool/lsp.ts +1 -113
- package/src/tool/mcp-exa.ts +1 -73
- package/src/tool/package.ts +1 -168
- package/src/tool/plan.ts +1 -30
- package/src/tool/question.ts +1 -52
- package/src/tool/read.ts +1 -389
- package/src/tool/recall.ts +1 -164
- package/src/tool/registry.ts +1 -375
- package/src/tool/schema.ts +1 -16
- package/src/tool/search/glob.ts +115 -0
- package/src/tool/search/glob.txt +6 -0
- package/src/tool/search/grep.ts +151 -0
- package/src/tool/search/grep.txt +8 -0
- package/src/tool/search/warpgrep.ts +107 -0
- package/src/tool/search/warpgrep.txt +10 -0
- package/src/tool/search/webfetch.ts +202 -0
- package/src/tool/search/webfetch.txt +13 -0
- package/src/tool/search/websearch.ts +71 -0
- package/src/tool/search/websearch.txt +14 -0
- package/src/tool/skill.ts +1 -91
- package/src/tool/task.ts +1 -197
- package/src/tool/todo.ts +1 -62
- package/src/tool/tool.ts +1 -162
- package/src/tool/truncate.ts +1 -160
- package/src/tool/truncation-dir.ts +1 -4
- package/src/tool/warpgrep.ts +1 -107
- package/src/tool/webfetch.ts +1 -202
- package/src/tool/websearch.ts +1 -71
- package/src/tool/workflow/plan-enter.txt +14 -0
- package/src/tool/workflow/plan-exit.txt +13 -0
- package/src/tool/workflow/plan.ts +30 -0
- package/src/tool/workflow/question.ts +52 -0
- package/src/tool/workflow/question.txt +11 -0
- package/src/tool/workflow/skill.ts +91 -0
- package/src/tool/workflow/skill.txt +5 -0
- package/src/tool/workflow/task.ts +197 -0
- package/src/tool/workflow/task.txt +57 -0
- package/src/tool/workflow/todo.ts +62 -0
- package/src/tool/workflow/todowrite.txt +167 -0
- package/src/tool/write.ts +1 -114
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select"
|
|
2
|
+
import { useTheme } from "../context/theme"
|
|
3
|
+
import { useDialog } from "../ui/dialog"
|
|
4
|
+
import { onCleanup } from "solid-js"
|
|
5
|
+
|
|
6
|
+
export function DialogThemeList() {
|
|
7
|
+
const theme = useTheme()
|
|
8
|
+
const options = Object.keys(theme.all())
|
|
9
|
+
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
|
|
10
|
+
.map((value) => ({
|
|
11
|
+
title: value,
|
|
12
|
+
value: value,
|
|
13
|
+
}))
|
|
14
|
+
const dialog = useDialog()
|
|
15
|
+
let confirmed = false
|
|
16
|
+
let ref: DialogSelectRef<string>
|
|
17
|
+
const initial = theme.selected
|
|
18
|
+
|
|
19
|
+
onCleanup(() => {
|
|
20
|
+
if (!confirmed) theme.set(initial)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<DialogSelect
|
|
25
|
+
title="Themes"
|
|
26
|
+
options={options}
|
|
27
|
+
current={initial}
|
|
28
|
+
onMove={(opt) => {
|
|
29
|
+
theme.set(opt.value)
|
|
30
|
+
}}
|
|
31
|
+
onSelect={(opt) => {
|
|
32
|
+
theme.set(opt.value)
|
|
33
|
+
confirmed = true
|
|
34
|
+
dialog.clear()
|
|
35
|
+
}}
|
|
36
|
+
ref={(r) => {
|
|
37
|
+
ref = r
|
|
38
|
+
}}
|
|
39
|
+
onFilter={(query) => {
|
|
40
|
+
if (query.length === 0) {
|
|
41
|
+
theme.set(initial)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const first = ref.filtered[0]
|
|
46
|
+
if (first) theme.set(first.value)
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createMemo } from "solid-js"
|
|
2
|
+
import { useLocal } from "@tui/context/local"
|
|
3
|
+
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
4
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
+
|
|
6
|
+
export function DialogVariant() {
|
|
7
|
+
const local = useLocal()
|
|
8
|
+
const dialog = useDialog()
|
|
9
|
+
|
|
10
|
+
const options = createMemo(() => {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
value: "default",
|
|
14
|
+
title: "Default",
|
|
15
|
+
onSelect: () => {
|
|
16
|
+
dialog.clear()
|
|
17
|
+
local.model.variant.set(undefined)
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
...local.model.variant.list().map((variant) => ({
|
|
21
|
+
value: variant,
|
|
22
|
+
title: variant,
|
|
23
|
+
onSelect: () => {
|
|
24
|
+
dialog.clear()
|
|
25
|
+
local.model.variant.set(variant)
|
|
26
|
+
},
|
|
27
|
+
})),
|
|
28
|
+
]
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<DialogSelect<string>
|
|
33
|
+
options={options()}
|
|
34
|
+
title={"Select variant"}
|
|
35
|
+
current={local.model.variant.selected()}
|
|
36
|
+
flat={true}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createSaeeolClient } from "@saeeol/sdk/v2"
|
|
2
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
3
|
+
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
4
|
+
import { useRoute } from "@tui/context/route"
|
|
5
|
+
import { useSync } from "@tui/context/sync"
|
|
6
|
+
import { useProject } from "@tui/context/project"
|
|
7
|
+
import { createMemo, createSignal, onMount } from "solid-js"
|
|
8
|
+
import { setTimeout as sleep } from "node:timers/promises"
|
|
9
|
+
import { errorMessage } from "@/util/error"
|
|
10
|
+
import { useSDK } from "../context/sdk"
|
|
11
|
+
import { useToast } from "../ui/toast"
|
|
12
|
+
|
|
13
|
+
type Adapter = {
|
|
14
|
+
type: string
|
|
15
|
+
name: string
|
|
16
|
+
description: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function scoped(sdk: ReturnType<typeof useSDK>, sync: ReturnType<typeof useSync>, workspaceID: string) {
|
|
20
|
+
return createSaeeolClient({
|
|
21
|
+
baseUrl: sdk.url,
|
|
22
|
+
fetch: sdk.fetch,
|
|
23
|
+
directory: sync.path.directory || sdk.directory,
|
|
24
|
+
experimental_workspaceID: workspaceID,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function openWorkspaceSession(input: {
|
|
29
|
+
dialog: ReturnType<typeof useDialog>
|
|
30
|
+
route: ReturnType<typeof useRoute>
|
|
31
|
+
sdk: ReturnType<typeof useSDK>
|
|
32
|
+
sync: ReturnType<typeof useSync>
|
|
33
|
+
toast: ReturnType<typeof useToast>
|
|
34
|
+
workspaceID: string
|
|
35
|
+
}) {
|
|
36
|
+
const client = scoped(input.sdk, input.sync, input.workspaceID)
|
|
37
|
+
|
|
38
|
+
while (true) {
|
|
39
|
+
const result = await client.session.create({ workspace: input.workspaceID }).catch(() => undefined)
|
|
40
|
+
if (!result) {
|
|
41
|
+
input.toast.show({
|
|
42
|
+
message: "Failed to create workspace session",
|
|
43
|
+
variant: "error",
|
|
44
|
+
})
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
if (result.response?.status && result.response.status >= 500 && result.response.status < 600) {
|
|
48
|
+
await sleep(1000)
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
if (!result.data) {
|
|
52
|
+
input.toast.show({
|
|
53
|
+
message: "Failed to create workspace session",
|
|
54
|
+
variant: "error",
|
|
55
|
+
})
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
input.route.navigate({
|
|
60
|
+
type: "session",
|
|
61
|
+
sessionID: result.data.id,
|
|
62
|
+
})
|
|
63
|
+
input.dialog.clear()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function restoreWorkspaceSession(input: {
|
|
69
|
+
dialog: ReturnType<typeof useDialog>
|
|
70
|
+
sdk: ReturnType<typeof useSDK>
|
|
71
|
+
sync: ReturnType<typeof useSync>
|
|
72
|
+
project: ReturnType<typeof useProject>
|
|
73
|
+
toast: ReturnType<typeof useToast>
|
|
74
|
+
workspaceID: string
|
|
75
|
+
sessionID: string
|
|
76
|
+
done?: () => void
|
|
77
|
+
}) {
|
|
78
|
+
const result = await input.sdk.client.experimental.workspace
|
|
79
|
+
.sessionRestore({ id: input.workspaceID, sessionID: input.sessionID })
|
|
80
|
+
.catch(() => undefined)
|
|
81
|
+
if (!result?.data) {
|
|
82
|
+
input.toast.show({
|
|
83
|
+
message: `Failed to restore session: ${errorMessage(result?.error ?? "no response")}`,
|
|
84
|
+
variant: "error",
|
|
85
|
+
})
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
input.project.workspace.set(input.workspaceID)
|
|
90
|
+
|
|
91
|
+
await input.sync.bootstrap({ fatal: false }).catch(() => undefined)
|
|
92
|
+
|
|
93
|
+
await Promise.all([input.project.workspace.sync(), input.sync.session.sync(input.sessionID)])
|
|
94
|
+
|
|
95
|
+
input.toast.show({
|
|
96
|
+
message: "Session restored into the new workspace",
|
|
97
|
+
variant: "success",
|
|
98
|
+
})
|
|
99
|
+
input.done?.()
|
|
100
|
+
if (input.done) return
|
|
101
|
+
input.dialog.clear()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promise<void> | void }) {
|
|
105
|
+
const dialog = useDialog()
|
|
106
|
+
const sync = useSync()
|
|
107
|
+
const project = useProject()
|
|
108
|
+
const sdk = useSDK()
|
|
109
|
+
const toast = useToast()
|
|
110
|
+
const [creating, setCreating] = createSignal<string>()
|
|
111
|
+
const [adapters, setAdapters] = createSignal<Adapter[]>()
|
|
112
|
+
|
|
113
|
+
onMount(() => {
|
|
114
|
+
dialog.setSize("medium")
|
|
115
|
+
void (async () => {
|
|
116
|
+
const dir = sync.path.directory || sdk.directory
|
|
117
|
+
const url = new URL("/experimental/workspace/adapter", sdk.url)
|
|
118
|
+
if (dir) url.searchParams.set("directory", dir)
|
|
119
|
+
const res = await sdk
|
|
120
|
+
.fetch(url)
|
|
121
|
+
.then((x) => x.json() as Promise<Adapter[]>)
|
|
122
|
+
.catch(() => undefined)
|
|
123
|
+
if (!res) {
|
|
124
|
+
toast.show({
|
|
125
|
+
message: "Failed to load workspace adapters",
|
|
126
|
+
variant: "error",
|
|
127
|
+
})
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
setAdapters(res)
|
|
131
|
+
})()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const options = createMemo(() => {
|
|
135
|
+
const type = creating()
|
|
136
|
+
if (type) {
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
title: `Creating ${type} workspace...`,
|
|
140
|
+
value: "creating" as const,
|
|
141
|
+
description: "This can take a while for remote environments",
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
const list = adapters()
|
|
146
|
+
if (!list) {
|
|
147
|
+
return [
|
|
148
|
+
{
|
|
149
|
+
title: "Loading workspaces...",
|
|
150
|
+
value: "loading" as const,
|
|
151
|
+
description: "Fetching available workspace adapters",
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
return list.map((item) => ({
|
|
156
|
+
title: item.name,
|
|
157
|
+
value: item.type,
|
|
158
|
+
description: item.description,
|
|
159
|
+
}))
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const create = async (type: string) => {
|
|
163
|
+
if (creating()) return
|
|
164
|
+
setCreating(type)
|
|
165
|
+
|
|
166
|
+
const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch(() => {
|
|
167
|
+
toast.show({
|
|
168
|
+
message: "Creating workspace failed",
|
|
169
|
+
variant: "error",
|
|
170
|
+
})
|
|
171
|
+
return undefined
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const workspace = result?.data
|
|
175
|
+
if (!workspace) {
|
|
176
|
+
setCreating(undefined)
|
|
177
|
+
toast.show({
|
|
178
|
+
message: `Failed to create workspace: ${errorMessage(result?.error ?? "no response")}`,
|
|
179
|
+
variant: "error",
|
|
180
|
+
})
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await project.workspace.sync()
|
|
185
|
+
await props.onSelect(workspace.id)
|
|
186
|
+
setCreating(undefined)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<DialogSelect
|
|
191
|
+
title={creating() ? "Creating Workspace" : "New Workspace"}
|
|
192
|
+
skipFilter={true}
|
|
193
|
+
options={options()}
|
|
194
|
+
onSelect={(option) => {
|
|
195
|
+
if (option.value === "creating" || option.value === "loading") return
|
|
196
|
+
void create(option.value)
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { TextAttributes } from "@opentui/core"
|
|
2
|
+
import { useKeyboard } from "@opentui/solid"
|
|
3
|
+
import { createStore } from "solid-js/store"
|
|
4
|
+
import { For } from "solid-js"
|
|
5
|
+
import { useTheme } from "../context/theme"
|
|
6
|
+
import { useDialog } from "../ui/dialog"
|
|
7
|
+
|
|
8
|
+
export function DialogWorkspaceUnavailable(props: { onRestore?: () => boolean | void | Promise<boolean | void> }) {
|
|
9
|
+
const dialog = useDialog()
|
|
10
|
+
const { theme } = useTheme()
|
|
11
|
+
const [store, setStore] = createStore({
|
|
12
|
+
active: "restore" as "cancel" | "restore",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const options = ["cancel", "restore"] as const
|
|
16
|
+
|
|
17
|
+
async function confirm() {
|
|
18
|
+
if (store.active === "cancel") {
|
|
19
|
+
dialog.clear()
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
const result = await props.onRestore?.()
|
|
23
|
+
if (result === false) return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
useKeyboard((evt) => {
|
|
27
|
+
if (evt.name === "return") {
|
|
28
|
+
evt.preventDefault()
|
|
29
|
+
evt.stopPropagation()
|
|
30
|
+
void confirm()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
if (evt.name === "left") {
|
|
34
|
+
evt.preventDefault()
|
|
35
|
+
evt.stopPropagation()
|
|
36
|
+
setStore("active", "cancel")
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (evt.name === "right") {
|
|
40
|
+
evt.preventDefault()
|
|
41
|
+
evt.stopPropagation()
|
|
42
|
+
setStore("active", "restore")
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<box paddingLeft={2} paddingRight={2} gap={1}>
|
|
48
|
+
<box flexDirection="row" justifyContent="space-between">
|
|
49
|
+
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
50
|
+
Workspace Unavailable
|
|
51
|
+
</text>
|
|
52
|
+
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
53
|
+
esc
|
|
54
|
+
</text>
|
|
55
|
+
</box>
|
|
56
|
+
<text fg={theme.textMuted} wrapMode="word">
|
|
57
|
+
This session is attached to a workspace that is no longer available.
|
|
58
|
+
</text>
|
|
59
|
+
<text fg={theme.textMuted} wrapMode="word">
|
|
60
|
+
Would you like to restore this session into a new workspace?
|
|
61
|
+
</text>
|
|
62
|
+
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1} gap={1}>
|
|
63
|
+
<For each={options}>
|
|
64
|
+
{(item) => (
|
|
65
|
+
<box
|
|
66
|
+
paddingLeft={2}
|
|
67
|
+
paddingRight={2}
|
|
68
|
+
backgroundColor={item === store.active ? theme.primary : undefined}
|
|
69
|
+
onMouseUp={() => {
|
|
70
|
+
setStore("active", item)
|
|
71
|
+
void confirm()
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<text fg={item === store.active ? theme.selectedListItemText : theme.textMuted}>{item}</text>
|
|
75
|
+
</box>
|
|
76
|
+
)}
|
|
77
|
+
</For>
|
|
78
|
+
</box>
|
|
79
|
+
</box>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -1,32 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { useLocal } from "@tui/context/local"
|
|
3
|
-
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
4
|
-
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
-
|
|
6
|
-
export function DialogAgent() {
|
|
7
|
-
const local = useLocal()
|
|
8
|
-
const dialog = useDialog()
|
|
9
|
-
|
|
10
|
-
const options = createMemo(() =>
|
|
11
|
-
local.agent.list().map((item) => {
|
|
12
|
-
return {
|
|
13
|
-
value: item.name,
|
|
14
|
-
title: item.displayName ?? item.name,
|
|
15
|
-
description:
|
|
16
|
-
[item.deprecated && "deprecated", item.native && "native"].filter(Boolean).join(", ") || item.description,
|
|
17
|
-
}
|
|
18
|
-
}),
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<DialogSelect
|
|
23
|
-
title="Select agent"
|
|
24
|
-
current={local.agent.current()?.name ?? ""}
|
|
25
|
-
options={options()}
|
|
26
|
-
onSelect={(option) => {
|
|
27
|
-
local.agent.set(option.value)
|
|
28
|
-
dialog.clear()
|
|
29
|
-
}}
|
|
30
|
-
/>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
1
|
+
export * from "./dialog/dialog-agent"
|
|
@@ -1,190 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
|
|
3
|
-
import {
|
|
4
|
-
createContext,
|
|
5
|
-
createMemo,
|
|
6
|
-
createSignal,
|
|
7
|
-
getOwner,
|
|
8
|
-
onCleanup,
|
|
9
|
-
runWithOwner,
|
|
10
|
-
useContext,
|
|
11
|
-
type Accessor,
|
|
12
|
-
type ParentProps,
|
|
13
|
-
} from "solid-js"
|
|
14
|
-
import { useKeyboard } from "@opentui/solid"
|
|
15
|
-
import { useKeybind } from "@tui/context/keybind"
|
|
16
|
-
import { t } from "@/util/i18n"
|
|
17
|
-
|
|
18
|
-
type Context = ReturnType<typeof init>
|
|
19
|
-
const ctx = createContext<Context>()
|
|
20
|
-
|
|
21
|
-
export type Slash = {
|
|
22
|
-
name: string
|
|
23
|
-
aliases?: string[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type CommandOption = DialogSelectOption<string> & {
|
|
27
|
-
keybind?: string
|
|
28
|
-
suggested?: boolean
|
|
29
|
-
slash?: Slash
|
|
30
|
-
hidden?: boolean
|
|
31
|
-
enabled?: boolean
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function init() {
|
|
35
|
-
const root = getOwner()
|
|
36
|
-
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
|
37
|
-
const [suspendCount, setSuspendCount] = createSignal(0)
|
|
38
|
-
const dialog = useDialog()
|
|
39
|
-
const keybind = useKeybind()
|
|
40
|
-
|
|
41
|
-
// Double-tap tracking for agent_cycle keybinds
|
|
42
|
-
const DOUBLE_TAB_KEYS = new Set(["agent_cycle", "agent_cycle_reverse"])
|
|
43
|
-
const DOUBLE_TAB_WINDOW = 400
|
|
44
|
-
let lastTabKey = ""
|
|
45
|
-
let lastTabTime = 0
|
|
46
|
-
|
|
47
|
-
const entries = createMemo(() => {
|
|
48
|
-
const all = registrations().flatMap((x) => x())
|
|
49
|
-
return all.map((x) => ({
|
|
50
|
-
...x,
|
|
51
|
-
footer: x.keybind ? keybind.print(x.keybind) : undefined,
|
|
52
|
-
}))
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const isEnabled = (option: CommandOption) => option.enabled !== false
|
|
56
|
-
const isVisible = (option: CommandOption) => isEnabled(option) && !option.hidden
|
|
57
|
-
|
|
58
|
-
const visibleOptions = createMemo(() => entries().filter((option) => isVisible(option)))
|
|
59
|
-
const suggestedOptions = createMemo(() =>
|
|
60
|
-
visibleOptions()
|
|
61
|
-
.filter((option) => option.suggested)
|
|
62
|
-
.map((option) => ({
|
|
63
|
-
...option,
|
|
64
|
-
value: `suggested:${option.value}`,
|
|
65
|
-
category: t("cmd.suggested"),
|
|
66
|
-
})),
|
|
67
|
-
)
|
|
68
|
-
const suspended = () => suspendCount() > 0
|
|
69
|
-
|
|
70
|
-
useKeyboard((evt) => {
|
|
71
|
-
if (suspended()) return
|
|
72
|
-
if (dialog.stack.length > 0) return
|
|
73
|
-
if (evt.defaultPrevented) return
|
|
74
|
-
for (const option of entries()) {
|
|
75
|
-
if (!isEnabled(option)) continue
|
|
76
|
-
if (option.keybind && keybind.match(option.keybind, evt)) {
|
|
77
|
-
// Require double-tap for agent cycle keybinds
|
|
78
|
-
if (DOUBLE_TAB_KEYS.has(option.keybind)) {
|
|
79
|
-
const now = Date.now()
|
|
80
|
-
const match = option.keybind === lastTabKey && now - lastTabTime < DOUBLE_TAB_WINDOW
|
|
81
|
-
lastTabKey = option.keybind
|
|
82
|
-
lastTabTime = now
|
|
83
|
-
if (!match) {
|
|
84
|
-
evt.preventDefault()
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
evt.preventDefault()
|
|
89
|
-
option.onSelect?.(dialog)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const result = {
|
|
96
|
-
trigger(name: string) {
|
|
97
|
-
for (const option of entries()) {
|
|
98
|
-
if (option.value === name) {
|
|
99
|
-
if (!isEnabled(option)) return
|
|
100
|
-
option.onSelect?.(dialog)
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
slashes() {
|
|
106
|
-
return visibleOptions().flatMap((option) => {
|
|
107
|
-
const slash = option.slash
|
|
108
|
-
if (!slash) return []
|
|
109
|
-
return {
|
|
110
|
-
display: "/" + slash.name,
|
|
111
|
-
description: option.description ?? option.title,
|
|
112
|
-
aliases: slash.aliases?.map((alias) => "/" + alias),
|
|
113
|
-
onSelect: () => result.trigger(option.value),
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
},
|
|
117
|
-
keybinds(enabled: boolean) {
|
|
118
|
-
setSuspendCount((count) => count + (enabled ? -1 : 1))
|
|
119
|
-
},
|
|
120
|
-
suspended,
|
|
121
|
-
show() {
|
|
122
|
-
dialog.replace(() => <DialogCommand options={visibleOptions()} suggestedOptions={suggestedOptions()} />)
|
|
123
|
-
},
|
|
124
|
-
register(cb: () => CommandOption[]) {
|
|
125
|
-
const owner = getOwner() ?? root
|
|
126
|
-
if (!owner) return () => {}
|
|
127
|
-
|
|
128
|
-
let list: Accessor<CommandOption[]> | undefined
|
|
129
|
-
|
|
130
|
-
// TUI plugins now register commands via an async store that runs outside an active reactive scope.
|
|
131
|
-
// runWithOwner attaches createMemo/onCleanup to this owner so plugin registrations stay reactive and dispose correctly.
|
|
132
|
-
runWithOwner(owner, () => {
|
|
133
|
-
list = createMemo(cb)
|
|
134
|
-
const ref = list
|
|
135
|
-
if (!ref) return
|
|
136
|
-
setRegistrations((arr) => [ref, ...arr])
|
|
137
|
-
onCleanup(() => {
|
|
138
|
-
setRegistrations((arr) => arr.filter((x) => x !== ref))
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
if (!list) return () => {}
|
|
143
|
-
let done = false
|
|
144
|
-
return () => {
|
|
145
|
-
if (done) return
|
|
146
|
-
done = true
|
|
147
|
-
const ref = list
|
|
148
|
-
if (!ref) return
|
|
149
|
-
setRegistrations((arr) => arr.filter((x) => x !== ref))
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
}
|
|
153
|
-
return result
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function useCommandDialog() {
|
|
157
|
-
const value = useContext(ctx)
|
|
158
|
-
if (!value) {
|
|
159
|
-
throw new Error("useCommandDialog must be used within a CommandProvider")
|
|
160
|
-
}
|
|
161
|
-
return value
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function CommandProvider(props: ParentProps) {
|
|
165
|
-
const value = init()
|
|
166
|
-
const dialog = useDialog()
|
|
167
|
-
const keybind = useKeybind()
|
|
168
|
-
|
|
169
|
-
useKeyboard((evt) => {
|
|
170
|
-
if (value.suspended()) return
|
|
171
|
-
if (dialog.stack.length > 0) return
|
|
172
|
-
if (evt.defaultPrevented) return
|
|
173
|
-
if (keybind.match("command_list", evt)) {
|
|
174
|
-
evt.preventDefault()
|
|
175
|
-
value.show()
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function DialogCommand(props: { options: CommandOption[]; suggestedOptions: CommandOption[] }) {
|
|
184
|
-
let ref: DialogSelectRef<string>
|
|
185
|
-
const list = () => {
|
|
186
|
-
if (ref?.filter) return props.options
|
|
187
|
-
return [...props.suggestedOptions, ...props.options]
|
|
188
|
-
}
|
|
189
|
-
return <DialogSelect ref={(r) => (ref = r)} title={t("cmd.title")} options={list()} />
|
|
190
|
-
}
|
|
1
|
+
export * from "./dialog/dialog-command"
|
|
@@ -1,103 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
3
|
-
import { useSDK } from "@tui/context/sdk"
|
|
4
|
-
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
-
import { useToast } from "@tui/ui/toast"
|
|
6
|
-
import { useTheme } from "@tui/context/theme"
|
|
7
|
-
import type { ExperimentalConsoleListOrgsResponse } from "@saeeol/sdk/v2"
|
|
8
|
-
|
|
9
|
-
type OrgOption = ExperimentalConsoleListOrgsResponse["orgs"][number]
|
|
10
|
-
|
|
11
|
-
const accountHost = (url: string) => {
|
|
12
|
-
try {
|
|
13
|
-
return new URL(url).host
|
|
14
|
-
} catch {
|
|
15
|
-
return url
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const accountLabel = (item: Pick<OrgOption, "accountEmail" | "accountUrl">) =>
|
|
20
|
-
`${item.accountEmail} ${accountHost(item.accountUrl)}`
|
|
21
|
-
|
|
22
|
-
export function DialogConsoleOrg() {
|
|
23
|
-
const sdk = useSDK()
|
|
24
|
-
const dialog = useDialog()
|
|
25
|
-
const toast = useToast()
|
|
26
|
-
const { theme } = useTheme()
|
|
27
|
-
|
|
28
|
-
const [orgs] = createResource(async () => {
|
|
29
|
-
const result = await sdk.client.experimental.console.listOrgs({}, { throwOnError: true })
|
|
30
|
-
return result.data?.orgs ?? []
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const current = createMemo(() => orgs()?.find((item) => item.active))
|
|
34
|
-
|
|
35
|
-
const options = createMemo(() => {
|
|
36
|
-
const listed = orgs()
|
|
37
|
-
if (listed === undefined) {
|
|
38
|
-
return [
|
|
39
|
-
{
|
|
40
|
-
title: "Loading orgs...",
|
|
41
|
-
value: "loading",
|
|
42
|
-
onSelect: () => {},
|
|
43
|
-
},
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (listed.length === 0) {
|
|
48
|
-
return [
|
|
49
|
-
{
|
|
50
|
-
title: "No orgs found",
|
|
51
|
-
value: "empty",
|
|
52
|
-
onSelect: () => {},
|
|
53
|
-
},
|
|
54
|
-
]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return listed
|
|
58
|
-
.toSorted((a, b) => {
|
|
59
|
-
const activeAccountA = a.active ? 0 : 1
|
|
60
|
-
const activeAccountB = b.active ? 0 : 1
|
|
61
|
-
if (activeAccountA !== activeAccountB) return activeAccountA - activeAccountB
|
|
62
|
-
|
|
63
|
-
const accountCompare = accountLabel(a).localeCompare(accountLabel(b))
|
|
64
|
-
if (accountCompare !== 0) return accountCompare
|
|
65
|
-
|
|
66
|
-
return a.orgName.localeCompare(b.orgName)
|
|
67
|
-
})
|
|
68
|
-
.map((item) => ({
|
|
69
|
-
title: item.orgName,
|
|
70
|
-
value: item,
|
|
71
|
-
category: accountLabel(item),
|
|
72
|
-
categoryView: (
|
|
73
|
-
<box flexDirection="row" gap={2}>
|
|
74
|
-
<text fg={theme.accent}>{item.accountEmail}</text>
|
|
75
|
-
<text fg={theme.textMuted}>{accountHost(item.accountUrl)}</text>
|
|
76
|
-
</box>
|
|
77
|
-
),
|
|
78
|
-
onSelect: async () => {
|
|
79
|
-
if (item.active) {
|
|
80
|
-
dialog.clear()
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
await sdk.client.experimental.console.switchOrg(
|
|
85
|
-
{
|
|
86
|
-
accountID: item.accountID,
|
|
87
|
-
orgID: item.orgID,
|
|
88
|
-
},
|
|
89
|
-
{ throwOnError: true },
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
await sdk.client.instance.dispose()
|
|
93
|
-
toast.show({
|
|
94
|
-
message: `Switched to ${item.orgName}`,
|
|
95
|
-
variant: "info",
|
|
96
|
-
})
|
|
97
|
-
dialog.clear()
|
|
98
|
-
},
|
|
99
|
-
}))
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
return <DialogSelect<string | OrgOption> title="Switch org" options={options()} current={current()} />
|
|
103
|
-
}
|
|
1
|
+
export * from "./dialog/dialog-console-org"
|