@agentprojectcontext/apx 1.15.5 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +40 -5
- package/src/cli/commands/log.js +113 -0
- package/src/cli/commands/overlay.js +253 -0
- package/src/cli/commands/sys.js +88 -16
- package/src/cli/index.js +23 -1
- package/src/cli/terminal-chat/renderer.js +71 -56
- package/src/cli-ts/commands/agent.ts +173 -0
- package/src/cli-ts/commands/chat.ts +119 -0
- package/src/cli-ts/commands/daemon.ts +112 -0
- package/src/cli-ts/commands/exec.ts +109 -0
- package/src/cli-ts/commands/mcp.ts +235 -0
- package/src/cli-ts/commands/session.ts +224 -0
- package/src/cli-ts/commands/status.ts +61 -0
- package/src/cli-ts/http.ts +36 -0
- package/src/cli-ts/index.ts +73 -0
- package/src/cli-ts/ui.ts +107 -0
- package/src/core/logging.js +81 -0
- package/src/daemon/api.js +58 -0
- package/src/daemon/engines/anthropic.js +60 -1
- package/src/daemon/engines/index.js +2 -1
- package/src/daemon/engines/ollama.js +70 -3
- package/src/daemon/index.js +58 -0
- package/src/daemon/overlay-ws.js +40 -0
- package/src/daemon/plugins/index.js +2 -1
- package/src/daemon/plugins/overlay.js +177 -0
- package/src/daemon/plugins/telegram.js +15 -3
- package/src/daemon/super-agent.js +102 -19
- package/src/daemon/transcription.js +262 -59
- package/src/daemon/wakeup.js +14 -19
- package/src/daemon/whisper-server.py +57 -6
- package/src/overlay/index.html +44 -0
- package/src/overlay/main.js +480 -0
- package/src/overlay/package.json +3 -0
- package/src/overlay/preload.js +34 -0
- package/src/overlay/renderer.js +371 -0
- package/src/overlay/style.css +250 -0
- package/src/tui/_shims/cli-error.ts +6 -0
- package/src/tui/_shims/cli-logo.ts +18 -0
- package/src/tui/_shims/cli-ui.ts +1 -0
- package/src/tui/_shims/config-console-state.ts +7 -0
- package/src/tui/_shims/core-any.ts +30 -0
- package/src/tui/_shims/core-binary.ts +13 -0
- package/src/tui/_shims/core-flag.ts +3 -0
- package/src/tui/_shims/core-log.ts +14 -0
- package/src/tui/_shims/lsp-language.ts +1 -0
- package/src/tui/_shims/opencode-any.ts +135 -0
- package/src/tui/_shims/opencode-sdk-v2.ts +48 -0
- package/src/tui/_shims/plugin-tui.ts +13 -0
- package/src/tui/_shims/provider-provider.ts +10 -0
- package/src/tui/_shims/session-retry.ts +1 -0
- package/src/tui/_shims/session-schema.ts +15 -0
- package/src/tui/_shims/session-session.ts +3 -0
- package/src/tui/_shims/snapshot.ts +4 -0
- package/src/tui/_shims/tool-any.ts +18 -0
- package/src/tui/_shims/util-error.ts +7 -0
- package/src/tui/_shims/util-filesystem.ts +79 -0
- package/src/tui/_shims/util-format.ts +7 -0
- package/src/tui/_shims/util-iife.ts +3 -0
- package/src/tui/_shims/util-locale.ts +10 -0
- package/src/tui/_shims/util-process.ts +38 -0
- package/src/tui/app.tsx +783 -0
- package/src/tui/asset/charge.wav +0 -0
- package/src/tui/asset/pulse-a.wav +0 -0
- package/src/tui/asset/pulse-b.wav +0 -0
- package/src/tui/asset/pulse-c.wav +0 -0
- package/src/tui/attach.ts +100 -0
- package/src/tui/component/bg-pulse-render.ts +436 -0
- package/src/tui/component/bg-pulse.tsx +99 -0
- package/src/tui/component/border.tsx +21 -0
- package/src/tui/component/dialog-agent.tsx +31 -0
- package/src/tui/component/dialog-console-org.tsx +103 -0
- package/src/tui/component/dialog-mcp.tsx +85 -0
- package/src/tui/component/dialog-model.tsx +175 -0
- package/src/tui/component/dialog-provider.tsx +456 -0
- package/src/tui/component/dialog-retry-action.tsx +160 -0
- package/src/tui/component/dialog-session-delete-failed.tsx +99 -0
- package/src/tui/component/dialog-session-list.tsx +323 -0
- package/src/tui/component/dialog-session-rename.tsx +31 -0
- package/src/tui/component/dialog-skill.tsx +36 -0
- package/src/tui/component/dialog-stash.tsx +87 -0
- package/src/tui/component/dialog-status.tsx +168 -0
- package/src/tui/component/dialog-tag.tsx +44 -0
- package/src/tui/component/dialog-theme-list.tsx +50 -0
- package/src/tui/component/dialog-variant.tsx +39 -0
- package/src/tui/component/dialog-workspace-create.tsx +302 -0
- package/src/tui/component/dialog-workspace-file-changes.tsx +138 -0
- package/src/tui/component/dialog-workspace-unavailable.tsx +69 -0
- package/src/tui/component/error-component.tsx +92 -0
- package/src/tui/component/logo.tsx +896 -0
- package/src/tui/component/plugin-route-missing.tsx +14 -0
- package/src/tui/component/prompt/autocomplete.tsx +869 -0
- package/src/tui/component/prompt/cwd.ts +0 -0
- package/src/tui/component/prompt/frecency.tsx +90 -0
- package/src/tui/component/prompt/history.tsx +108 -0
- package/src/tui/component/prompt/index.tsx +1809 -0
- package/src/tui/component/prompt/part.ts +16 -0
- package/src/tui/component/prompt/stash.tsx +101 -0
- package/src/tui/component/prompt/traits.ts +35 -0
- package/src/tui/component/spinner.tsx +24 -0
- package/src/tui/component/startup-loading.tsx +63 -0
- package/src/tui/component/todo-item.tsx +32 -0
- package/src/tui/component/use-connected.tsx +9 -0
- package/src/tui/component/workspace-label.tsx +19 -0
- package/src/tui/config/cwd.ts +5 -0
- package/src/tui/config/keybind.ts +432 -0
- package/src/tui/config/tui-migrate.ts +154 -0
- package/src/tui/config/tui-schema.ts +34 -0
- package/src/tui/config/tui.ts +46 -0
- package/src/tui/context/aggregate-failures.ts +34 -0
- package/src/tui/context/args.tsx +15 -0
- package/src/tui/context/command-palette.tsx +163 -0
- package/src/tui/context/directory.ts +15 -0
- package/src/tui/context/editor-zed.ts +283 -0
- package/src/tui/context/editor.ts +468 -0
- package/src/tui/context/event-apx.ts +22 -0
- package/src/tui/context/event.ts +6 -0
- package/src/tui/context/exit.tsx +60 -0
- package/src/tui/context/helper.tsx +25 -0
- package/src/tui/context/kv.tsx +81 -0
- package/src/tui/context/local.tsx +608 -0
- package/src/tui/context/path-format.tsx +39 -0
- package/src/tui/context/project-apx.tsx +48 -0
- package/src/tui/context/project.tsx +7 -0
- package/src/tui/context/prompt.tsx +18 -0
- package/src/tui/context/route.tsx +52 -0
- package/src/tui/context/sdk-apx.tsx +185 -0
- package/src/tui/context/sdk.tsx +6 -0
- package/src/tui/context/sync-apx.tsx +178 -0
- package/src/tui/context/sync-v2.tsx +16 -0
- package/src/tui/context/sync.tsx +118 -0
- package/src/tui/context/theme/aura.json +69 -0
- package/src/tui/context/theme/ayu.json +80 -0
- package/src/tui/context/theme/carbonfox.json +248 -0
- package/src/tui/context/theme/catppuccin-frappe.json +230 -0
- package/src/tui/context/theme/catppuccin-macchiato.json +230 -0
- package/src/tui/context/theme/catppuccin.json +112 -0
- package/src/tui/context/theme/cobalt2.json +225 -0
- package/src/tui/context/theme/cursor.json +249 -0
- package/src/tui/context/theme/dracula.json +219 -0
- package/src/tui/context/theme/everforest.json +241 -0
- package/src/tui/context/theme/flexoki.json +237 -0
- package/src/tui/context/theme/github.json +233 -0
- package/src/tui/context/theme/gruvbox.json +242 -0
- package/src/tui/context/theme/kanagawa.json +77 -0
- package/src/tui/context/theme/lucent-orng.json +234 -0
- package/src/tui/context/theme/material.json +235 -0
- package/src/tui/context/theme/matrix.json +77 -0
- package/src/tui/context/theme/mercury.json +252 -0
- package/src/tui/context/theme/monokai.json +221 -0
- package/src/tui/context/theme/nightowl.json +221 -0
- package/src/tui/context/theme/nord.json +223 -0
- package/src/tui/context/theme/one-dark.json +84 -0
- package/src/tui/context/theme/opencode.json +245 -0
- package/src/tui/context/theme/orng.json +249 -0
- package/src/tui/context/theme/osaka-jade.json +93 -0
- package/src/tui/context/theme/palenight.json +222 -0
- package/src/tui/context/theme/rosepine.json +234 -0
- package/src/tui/context/theme/solarized.json +223 -0
- package/src/tui/context/theme/synthwave84.json +226 -0
- package/src/tui/context/theme/tokyonight.json +243 -0
- package/src/tui/context/theme/vercel.json +245 -0
- package/src/tui/context/theme/vesper.json +218 -0
- package/src/tui/context/theme/zenburn.json +223 -0
- package/src/tui/context/theme.tsx +1247 -0
- package/src/tui/context/tui-config.tsx +9 -0
- package/src/tui/event.ts +16 -0
- package/src/tui/feature-plugins/home/footer.tsx +94 -0
- package/src/tui/feature-plugins/home/tips-view.tsx +166 -0
- package/src/tui/feature-plugins/home/tips.tsx +59 -0
- package/src/tui/feature-plugins/sidebar/context.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/files.tsx +63 -0
- package/src/tui/feature-plugins/sidebar/footer.tsx +94 -0
- package/src/tui/feature-plugins/sidebar/lsp.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/mcp.tsx +97 -0
- package/src/tui/feature-plugins/sidebar/todo.tsx +49 -0
- package/src/tui/feature-plugins/system/plugins.tsx +269 -0
- package/src/tui/feature-plugins/system/session-v2.tsx +1143 -0
- package/src/tui/feature-plugins/system/which-key.tsx +608 -0
- package/src/tui/keymap.tsx +166 -0
- package/src/tui/layer.ts +6 -0
- package/src/tui/plugin/api.tsx +381 -0
- package/src/tui/plugin/command-shim.ts +109 -0
- package/src/tui/plugin/internal.ts +33 -0
- package/src/tui/plugin/runtime.ts +1069 -0
- package/src/tui/plugin/slots.tsx +60 -0
- package/src/tui/routes/home.tsx +96 -0
- package/src/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
- package/src/tui/routes/session/dialog-message.tsx +108 -0
- package/src/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/tui/routes/session/footer.tsx +91 -0
- package/src/tui/routes/session/index.tsx +188 -0
- package/src/tui/routes/session/permission.tsx +722 -0
- package/src/tui/routes/session/question.tsx +490 -0
- package/src/tui/routes/session/sidebar.tsx +102 -0
- package/src/tui/routes/session/subagent-footer.tsx +133 -0
- package/src/tui/run.ts +84 -0
- package/src/tui/thread.ts +261 -0
- package/src/tui/tsconfig.json +40 -0
- package/src/tui/ui/dialog-alert.tsx +66 -0
- package/src/tui/ui/dialog-confirm.tsx +108 -0
- package/src/tui/ui/dialog-export-options.tsx +217 -0
- package/src/tui/ui/dialog-help.tsx +40 -0
- package/src/tui/ui/dialog-prompt.tsx +101 -0
- package/src/tui/ui/dialog-select.tsx +553 -0
- package/src/tui/ui/dialog.tsx +211 -0
- package/src/tui/ui/link.tsx +34 -0
- package/src/tui/ui/spinner.ts +368 -0
- package/src/tui/ui/toast.tsx +111 -0
- package/src/tui/util/clipboard.ts +217 -0
- package/src/tui/util/editor.ts +37 -0
- package/src/tui/util/model.ts +23 -0
- package/src/tui/util/provider-origin.ts +7 -0
- package/src/tui/util/revert-diff.ts +18 -0
- package/src/tui/util/scroll.ts +25 -0
- package/src/tui/util/selection.ts +65 -0
- package/src/tui/util/signal.ts +41 -0
- package/src/tui/util/sound.ts +156 -0
- package/src/tui/util/transcript.ts +112 -0
- package/src/tui/validate-session.ts +29 -0
- package/src/tui/win32.ts +130 -0
- package/src/tui/worker.ts +104 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { TextAttributes } from "@opentui/core"
|
|
2
|
+
import { fileURLToPath } from "bun"
|
|
3
|
+
import { useTheme } from "../context/theme"
|
|
4
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
+
import { useSync } from "@tui/context/sync"
|
|
6
|
+
import { For, Match, Switch, Show, createMemo } from "solid-js"
|
|
7
|
+
|
|
8
|
+
export type DialogStatusProps = {}
|
|
9
|
+
|
|
10
|
+
export function DialogStatus() {
|
|
11
|
+
const sync = useSync()
|
|
12
|
+
const { theme } = useTheme()
|
|
13
|
+
const dialog = useDialog()
|
|
14
|
+
|
|
15
|
+
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
|
|
16
|
+
|
|
17
|
+
const plugins = createMemo(() => {
|
|
18
|
+
const list = sync.data.config.plugin ?? []
|
|
19
|
+
const result = list.map((item) => {
|
|
20
|
+
const value = typeof item === "string" ? item : item[0]
|
|
21
|
+
if (value.startsWith("file://")) {
|
|
22
|
+
const path = fileURLToPath(value)
|
|
23
|
+
const parts = path.split("/")
|
|
24
|
+
const filename = parts.pop() || path
|
|
25
|
+
if (!filename.includes(".")) return { name: filename }
|
|
26
|
+
const basename = filename.split(".")[0]
|
|
27
|
+
if (basename === "index") {
|
|
28
|
+
const dirname = parts.pop()
|
|
29
|
+
const name = dirname || basename
|
|
30
|
+
return { name }
|
|
31
|
+
}
|
|
32
|
+
return { name: basename }
|
|
33
|
+
}
|
|
34
|
+
const index = value.lastIndexOf("@")
|
|
35
|
+
if (index <= 0) return { name: value, version: "latest" }
|
|
36
|
+
const name = value.substring(0, index)
|
|
37
|
+
const version = value.substring(index + 1)
|
|
38
|
+
return { name, version }
|
|
39
|
+
})
|
|
40
|
+
return result.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
|
45
|
+
<box flexDirection="row" justifyContent="space-between">
|
|
46
|
+
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
47
|
+
Status
|
|
48
|
+
</text>
|
|
49
|
+
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
50
|
+
esc
|
|
51
|
+
</text>
|
|
52
|
+
</box>
|
|
53
|
+
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
|
|
54
|
+
<box>
|
|
55
|
+
<text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
|
|
56
|
+
<For each={Object.entries(sync.data.mcp)}>
|
|
57
|
+
{([key, item]) => (
|
|
58
|
+
<box flexDirection="row" gap={1}>
|
|
59
|
+
<text
|
|
60
|
+
flexShrink={0}
|
|
61
|
+
style={{
|
|
62
|
+
fg: (
|
|
63
|
+
{
|
|
64
|
+
connected: theme.success,
|
|
65
|
+
failed: theme.error,
|
|
66
|
+
disabled: theme.textMuted,
|
|
67
|
+
needs_auth: theme.warning,
|
|
68
|
+
needs_client_registration: theme.error,
|
|
69
|
+
} as Record<string, typeof theme.success>
|
|
70
|
+
)[item.status],
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
•
|
|
74
|
+
</text>
|
|
75
|
+
<text fg={theme.text} wrapMode="word">
|
|
76
|
+
<b>{key}</b>{" "}
|
|
77
|
+
<span style={{ fg: theme.textMuted }}>
|
|
78
|
+
<Switch fallback={item.status}>
|
|
79
|
+
<Match when={item.status === "connected"}>Connected</Match>
|
|
80
|
+
<Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
|
|
81
|
+
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
|
|
82
|
+
<Match when={(item.status as string) === "needs_auth"}>
|
|
83
|
+
Needs authentication (run: opencode mcp auth {key})
|
|
84
|
+
</Match>
|
|
85
|
+
<Match when={(item.status as string) === "needs_client_registration" && item}>
|
|
86
|
+
{(val) => (val() as { error: string }).error}
|
|
87
|
+
</Match>
|
|
88
|
+
</Switch>
|
|
89
|
+
</span>
|
|
90
|
+
</text>
|
|
91
|
+
</box>
|
|
92
|
+
)}
|
|
93
|
+
</For>
|
|
94
|
+
</box>
|
|
95
|
+
</Show>
|
|
96
|
+
{sync.data.lsp.length > 0 && (
|
|
97
|
+
<box>
|
|
98
|
+
<text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
|
|
99
|
+
<For each={sync.data.lsp}>
|
|
100
|
+
{(item) => (
|
|
101
|
+
<box flexDirection="row" gap={1}>
|
|
102
|
+
<text
|
|
103
|
+
flexShrink={0}
|
|
104
|
+
style={{
|
|
105
|
+
fg: {
|
|
106
|
+
connected: theme.success,
|
|
107
|
+
error: theme.error,
|
|
108
|
+
}[item.status],
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
•
|
|
112
|
+
</text>
|
|
113
|
+
<text fg={theme.text} wrapMode="word">
|
|
114
|
+
<b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
|
|
115
|
+
</text>
|
|
116
|
+
</box>
|
|
117
|
+
)}
|
|
118
|
+
</For>
|
|
119
|
+
</box>
|
|
120
|
+
)}
|
|
121
|
+
<Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
|
|
122
|
+
<box>
|
|
123
|
+
<text fg={theme.text}>{enabledFormatters().length} Formatters</text>
|
|
124
|
+
<For each={enabledFormatters()}>
|
|
125
|
+
{(item) => (
|
|
126
|
+
<box flexDirection="row" gap={1}>
|
|
127
|
+
<text
|
|
128
|
+
flexShrink={0}
|
|
129
|
+
style={{
|
|
130
|
+
fg: theme.success,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
•
|
|
134
|
+
</text>
|
|
135
|
+
<text wrapMode="word" fg={theme.text}>
|
|
136
|
+
<b>{item.name}</b>
|
|
137
|
+
</text>
|
|
138
|
+
</box>
|
|
139
|
+
)}
|
|
140
|
+
</For>
|
|
141
|
+
</box>
|
|
142
|
+
</Show>
|
|
143
|
+
<Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
|
|
144
|
+
<box>
|
|
145
|
+
<text fg={theme.text}>{plugins().length} Plugins</text>
|
|
146
|
+
<For each={plugins()}>
|
|
147
|
+
{(item) => (
|
|
148
|
+
<box flexDirection="row" gap={1}>
|
|
149
|
+
<text
|
|
150
|
+
flexShrink={0}
|
|
151
|
+
style={{
|
|
152
|
+
fg: theme.success,
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
•
|
|
156
|
+
</text>
|
|
157
|
+
<text wrapMode="word" fg={theme.text}>
|
|
158
|
+
<b>{item.name}</b>
|
|
159
|
+
{item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
|
|
160
|
+
</text>
|
|
161
|
+
</box>
|
|
162
|
+
)}
|
|
163
|
+
</For>
|
|
164
|
+
</box>
|
|
165
|
+
</Show>
|
|
166
|
+
</box>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createMemo, createResource } from "solid-js"
|
|
2
|
+
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
3
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
4
|
+
import { useSDK } from "@tui/context/sdk"
|
|
5
|
+
import { createStore } from "solid-js/store"
|
|
6
|
+
|
|
7
|
+
export function DialogTag(props: { onSelect?: (value: string) => void }) {
|
|
8
|
+
const sdk = useSDK()
|
|
9
|
+
const dialog = useDialog()
|
|
10
|
+
|
|
11
|
+
const [store] = createStore({
|
|
12
|
+
filter: "",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const [files] = createResource(
|
|
16
|
+
() => [store.filter],
|
|
17
|
+
async () => {
|
|
18
|
+
const result = await sdk.client.find.files({
|
|
19
|
+
query: store.filter,
|
|
20
|
+
})
|
|
21
|
+
if (result.error) return []
|
|
22
|
+
const sliced = (result.data ?? []).slice(0, 5)
|
|
23
|
+
return sliced
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const options = createMemo(() =>
|
|
28
|
+
(files() ?? []).map((file) => ({
|
|
29
|
+
value: file,
|
|
30
|
+
title: file,
|
|
31
|
+
})),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<DialogSelect
|
|
36
|
+
title="Autocomplete"
|
|
37
|
+
options={options()}
|
|
38
|
+
onSelect={(option) => {
|
|
39
|
+
props.onSelect?.(option.value)
|
|
40
|
+
dialog.clear()
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -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,302 @@
|
|
|
1
|
+
import type { Workspace } from "@opencode-ai/sdk/v2"
|
|
2
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
3
|
+
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
|
|
4
|
+
import { useSync } from "@tui/context/sync"
|
|
5
|
+
import { useProject } from "@tui/context/project"
|
|
6
|
+
import { useRoute } from "@tui/context/route"
|
|
7
|
+
import { createMemo, createSignal, onMount } from "solid-js"
|
|
8
|
+
import { errorMessage } from "@/util/error"
|
|
9
|
+
import { useSDK } from "../context/sdk"
|
|
10
|
+
import { useToast } from "../ui/toast"
|
|
11
|
+
import { DialogAlert } from "../ui/dialog-alert"
|
|
12
|
+
import { DialogWorkspaceFileChanges } from "./dialog-workspace-file-changes"
|
|
13
|
+
|
|
14
|
+
type Adapter = {
|
|
15
|
+
type: string
|
|
16
|
+
name: string
|
|
17
|
+
description: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type WorkspaceSelection =
|
|
21
|
+
| {
|
|
22
|
+
type: "none"
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
type: "new"
|
|
26
|
+
workspaceType: string
|
|
27
|
+
workspaceName: string
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: "existing"
|
|
31
|
+
workspaceID: string
|
|
32
|
+
workspaceType: string
|
|
33
|
+
workspaceName: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" }
|
|
37
|
+
type ExistingWorkspaceSelectValue = { workspace: Workspace }
|
|
38
|
+
|
|
39
|
+
export function recentConnectedWorkspaces<WorkspaceInfo extends { id: string; timeUsed: number | string }>(input: {
|
|
40
|
+
workspaces: readonly WorkspaceInfo[]
|
|
41
|
+
status: (workspaceID: string) => string | undefined
|
|
42
|
+
limit?: number
|
|
43
|
+
omitWorkspaceID?: string
|
|
44
|
+
}) {
|
|
45
|
+
const allWorkspaces = input.workspaces.filter((workspace) => input.status(workspace.id) === "connected")
|
|
46
|
+
const workspaces = allWorkspaces.toSorted((a, b) => Number(b.timeUsed) - Number(a.timeUsed))
|
|
47
|
+
const recent = workspaces.slice(0, input.limit ?? 3)
|
|
48
|
+
|
|
49
|
+
return { recent, hasMore: recent.length < workspaces.length }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function warpReminderText(dir: string) {
|
|
53
|
+
return `<system-reminder>The user has changed the current working directory to "${dir}". This is still the same project but at a possibly new location; take this into account when working with any files from now on.</system-reminder>`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function loadWorkspaceAdapters(input: {
|
|
57
|
+
sdk: ReturnType<typeof useSDK>
|
|
58
|
+
sync: ReturnType<typeof useSync>
|
|
59
|
+
toast: ReturnType<typeof useToast>
|
|
60
|
+
}) {
|
|
61
|
+
const dir = input.sync.path.directory || input.sdk.directory
|
|
62
|
+
const url = new URL("/experimental/workspace/adapter", input.sdk.url)
|
|
63
|
+
if (dir) url.searchParams.set("directory", dir)
|
|
64
|
+
const res = await input.sdk
|
|
65
|
+
.fetch(url)
|
|
66
|
+
.then((x) => x.json() as Promise<Adapter[]>)
|
|
67
|
+
.catch(() => undefined)
|
|
68
|
+
if (res) return res
|
|
69
|
+
input.toast.show({
|
|
70
|
+
message: "Failed to load workspace adapters",
|
|
71
|
+
variant: "error",
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function openWorkspaceSelect(input: {
|
|
76
|
+
dialog: ReturnType<typeof useDialog>
|
|
77
|
+
sdk: ReturnType<typeof useSDK>
|
|
78
|
+
sync: ReturnType<typeof useSync>
|
|
79
|
+
project: ReturnType<typeof useProject>
|
|
80
|
+
toast: ReturnType<typeof useToast>
|
|
81
|
+
onSelect: (selection: WorkspaceSelection) => Promise<void> | void
|
|
82
|
+
}) {
|
|
83
|
+
input.dialog.clear()
|
|
84
|
+
await input.sdk.client.experimental.workspace.syncList().catch(() => undefined)
|
|
85
|
+
await input.project.workspace.sync().catch(() => undefined)
|
|
86
|
+
const adapters = await loadWorkspaceAdapters(input)
|
|
87
|
+
if (!adapters) return
|
|
88
|
+
input.dialog.replace(() => <DialogWorkspaceSelect adapters={adapters} onSelect={input.onSelect} />)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function warpWorkspaceSession(input: {
|
|
92
|
+
dialog: ReturnType<typeof useDialog>
|
|
93
|
+
sdk: ReturnType<typeof useSDK>
|
|
94
|
+
sync: ReturnType<typeof useSync>
|
|
95
|
+
project: ReturnType<typeof useProject>
|
|
96
|
+
toast: ReturnType<typeof useToast>
|
|
97
|
+
sourceWorkspaceID?: string
|
|
98
|
+
workspaceID: string | null
|
|
99
|
+
sessionID: string
|
|
100
|
+
copyChanges: boolean
|
|
101
|
+
done?: () => void
|
|
102
|
+
}): Promise<boolean> {
|
|
103
|
+
const result = await input.sdk.client.experimental.workspace
|
|
104
|
+
.warp({
|
|
105
|
+
id: input.workspaceID,
|
|
106
|
+
sessionID: input.sessionID,
|
|
107
|
+
copyChanges: input.copyChanges,
|
|
108
|
+
})
|
|
109
|
+
.catch(() => undefined)
|
|
110
|
+
if (!result?.data) {
|
|
111
|
+
if (result?.error?.name === "VcsApplyError") {
|
|
112
|
+
await DialogAlert.show(
|
|
113
|
+
input.dialog,
|
|
114
|
+
"Unable to Warp Session",
|
|
115
|
+
"Unable to apply file changes to this workspace. It has existing changes that conflict or is based off a different branch. Session has not been warped.",
|
|
116
|
+
)
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
input.toast.show({
|
|
121
|
+
message: `Failed to warp session: ${errorMessage(result?.error ?? "no response")}`,
|
|
122
|
+
variant: "error",
|
|
123
|
+
})
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
input.project.workspace.set(input.workspaceID)
|
|
128
|
+
|
|
129
|
+
await input.sync.bootstrap({ fatal: false }).catch(() => undefined)
|
|
130
|
+
|
|
131
|
+
const dir = input.project.instance.directory() || input.sync.path.directory
|
|
132
|
+
if (dir) {
|
|
133
|
+
await input.sdk.client.session
|
|
134
|
+
.promptAsync({
|
|
135
|
+
sessionID: input.sessionID,
|
|
136
|
+
workspace: input.workspaceID ?? undefined,
|
|
137
|
+
noReply: true,
|
|
138
|
+
parts: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: warpReminderText(dir),
|
|
142
|
+
synthetic: true,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
})
|
|
146
|
+
.catch(() => undefined)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await Promise.all([input.project.workspace.sync(), input.sync.session.refresh()])
|
|
150
|
+
|
|
151
|
+
if (input.done) {
|
|
152
|
+
input.done()
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
input.dialog.clear()
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function confirmWorkspaceFileChanges(input: {
|
|
160
|
+
dialog: ReturnType<typeof useDialog>
|
|
161
|
+
sdk: ReturnType<typeof useSDK>
|
|
162
|
+
sourceWorkspaceID?: string
|
|
163
|
+
}) {
|
|
164
|
+
const status = await input.sdk.client.vcs.status({ workspace: input.sourceWorkspaceID }).catch(() => undefined)
|
|
165
|
+
const fileChangeChoice = status?.data?.length
|
|
166
|
+
? await DialogWorkspaceFileChanges.show(input.dialog, status.data)
|
|
167
|
+
: "no"
|
|
168
|
+
if (!fileChangeChoice) return
|
|
169
|
+
return fileChangeChoice === "yes"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function DialogWorkspaceSelect(props: {
|
|
173
|
+
adapters?: Adapter[]
|
|
174
|
+
onSelect: (selection: WorkspaceSelection) => Promise<void> | void
|
|
175
|
+
}) {
|
|
176
|
+
const dialog = useDialog()
|
|
177
|
+
const project = useProject()
|
|
178
|
+
const route = useRoute()
|
|
179
|
+
const sync = useSync()
|
|
180
|
+
const sdk = useSDK()
|
|
181
|
+
const toast = useToast()
|
|
182
|
+
const [adapters, setAdapters] = createSignal<Adapter[] | undefined>(props.adapters)
|
|
183
|
+
const omittedWorkspaceID = createMemo(() => (route.data.type === "session" ? project.workspace.current() : undefined))
|
|
184
|
+
|
|
185
|
+
onMount(() => {
|
|
186
|
+
dialog.setSize("medium")
|
|
187
|
+
void (async () => {
|
|
188
|
+
if (adapters()) return
|
|
189
|
+
const res = await loadWorkspaceAdapters({ sdk, sync, toast })
|
|
190
|
+
if (!res) return
|
|
191
|
+
setAdapters(res)
|
|
192
|
+
})()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const options = createMemo<DialogSelectOption<WorkspaceSelectValue>[]>(() => {
|
|
196
|
+
const list = adapters()
|
|
197
|
+
if (!list) return []
|
|
198
|
+
const { recent, hasMore } = recentConnectedWorkspaces({
|
|
199
|
+
workspaces: project.workspace.list(),
|
|
200
|
+
status: project.workspace.status,
|
|
201
|
+
omitWorkspaceID: omittedWorkspaceID(),
|
|
202
|
+
})
|
|
203
|
+
return [
|
|
204
|
+
...list.map((adapter) => ({
|
|
205
|
+
title: adapter.name,
|
|
206
|
+
value: { type: "new" as const, workspaceType: adapter.type, workspaceName: adapter.name },
|
|
207
|
+
description: adapter.description,
|
|
208
|
+
category: "New workspace",
|
|
209
|
+
})),
|
|
210
|
+
{
|
|
211
|
+
title: "None",
|
|
212
|
+
value: { type: "none" as const },
|
|
213
|
+
description: "Use the local project",
|
|
214
|
+
category: "Choose workspace",
|
|
215
|
+
},
|
|
216
|
+
...recent.map((workspace: Workspace) => ({
|
|
217
|
+
title: workspace.name,
|
|
218
|
+
description: `(${workspace.type})`,
|
|
219
|
+
value: {
|
|
220
|
+
type: "existing" as const,
|
|
221
|
+
workspaceID: workspace.id,
|
|
222
|
+
workspaceType: workspace.type,
|
|
223
|
+
workspaceName: workspace.name,
|
|
224
|
+
},
|
|
225
|
+
category: "Choose workspace",
|
|
226
|
+
})),
|
|
227
|
+
...(hasMore
|
|
228
|
+
? [
|
|
229
|
+
{
|
|
230
|
+
title: "View all workspaces",
|
|
231
|
+
value: { type: "existing-list" as const },
|
|
232
|
+
description: "Choose from all workspaces",
|
|
233
|
+
category: "Choose workspace",
|
|
234
|
+
},
|
|
235
|
+
]
|
|
236
|
+
: []),
|
|
237
|
+
]
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (!adapters()) return null
|
|
241
|
+
return (
|
|
242
|
+
<DialogSelect<WorkspaceSelectValue>
|
|
243
|
+
title="Warp"
|
|
244
|
+
skipFilter={true}
|
|
245
|
+
renderFilter={false}
|
|
246
|
+
options={options()}
|
|
247
|
+
onSelect={(option) => {
|
|
248
|
+
if (!option.value) return
|
|
249
|
+
if (option.value.type === "none") {
|
|
250
|
+
void props.onSelect(option.value)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
if (option.value.type === "new") {
|
|
254
|
+
void props.onSelect(option.value)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
if (option.value.type === "existing") {
|
|
258
|
+
void props.onSelect(option.value)
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
dialog.replace(() => (
|
|
263
|
+
<DialogExistingWorkspaceSelect omitWorkspaceID={omittedWorkspaceID()} onSelect={props.onSelect} />
|
|
264
|
+
))
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function DialogExistingWorkspaceSelect(props: {
|
|
271
|
+
omitWorkspaceID?: string
|
|
272
|
+
onSelect: (selection: WorkspaceSelection) => Promise<void> | void
|
|
273
|
+
}) {
|
|
274
|
+
const project = useProject()
|
|
275
|
+
|
|
276
|
+
const options = createMemo<DialogSelectOption<ExistingWorkspaceSelectValue>[]>(() =>
|
|
277
|
+
project.workspace
|
|
278
|
+
.list()
|
|
279
|
+
.filter((workspace) => project.workspace.status(workspace.id) === "connected")
|
|
280
|
+
.filter((workspace) => workspace.id !== props.omitWorkspaceID)
|
|
281
|
+
.map((workspace: Workspace) => ({
|
|
282
|
+
title: workspace.name,
|
|
283
|
+
description: `(${workspace.type})`,
|
|
284
|
+
value: { workspace },
|
|
285
|
+
})),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<DialogSelect<ExistingWorkspaceSelectValue>
|
|
290
|
+
title="Existing Workspace"
|
|
291
|
+
options={options()}
|
|
292
|
+
onSelect={(option) => {
|
|
293
|
+
void props.onSelect({
|
|
294
|
+
type: "existing",
|
|
295
|
+
workspaceID: option.value.workspace.id,
|
|
296
|
+
workspaceType: option.value.workspace.type,
|
|
297
|
+
workspaceName: option.value.workspace.name,
|
|
298
|
+
})
|
|
299
|
+
}}
|
|
300
|
+
/>
|
|
301
|
+
)
|
|
302
|
+
}
|