@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,133 @@
|
|
|
1
|
+
import { createMemo, createSignal, Show } from "solid-js"
|
|
2
|
+
import { useRouteData } from "@tui/context/route"
|
|
3
|
+
import { useSync } from "@tui/context/sync"
|
|
4
|
+
import { useTheme } from "@tui/context/theme"
|
|
5
|
+
import { SplitBorder } from "@tui/component/border"
|
|
6
|
+
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
|
|
7
|
+
import { Locale } from "@/util/locale"
|
|
8
|
+
import { useTerminalDimensions } from "@opentui/solid"
|
|
9
|
+
import { useCommandPalette } from "../../context/command-palette"
|
|
10
|
+
import { useCommandShortcut } from "../../keymap"
|
|
11
|
+
|
|
12
|
+
export function SubagentFooter() {
|
|
13
|
+
const route = useRouteData("session")
|
|
14
|
+
const sync = useSync()
|
|
15
|
+
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
|
16
|
+
const session = createMemo(() => sync.session.get(route.sessionID))
|
|
17
|
+
|
|
18
|
+
const subagentInfo = createMemo(() => {
|
|
19
|
+
const s = session()
|
|
20
|
+
if (!s) return { label: "Subagent", index: 0, total: 0 }
|
|
21
|
+
const agentMatch = s.title.match(/@(\w+) subagent/)
|
|
22
|
+
const label = agentMatch ? Locale.titlecase(agentMatch[1]) : "Subagent"
|
|
23
|
+
|
|
24
|
+
if (!s.parentID) return { label, index: 0, total: 0 }
|
|
25
|
+
|
|
26
|
+
const siblings = sync.data.session
|
|
27
|
+
.filter((x) => x.parentID === s.parentID)
|
|
28
|
+
.toSorted((a, b) => a.time.created - b.time.created)
|
|
29
|
+
const index = siblings.findIndex((x) => x.id === s.id)
|
|
30
|
+
|
|
31
|
+
return { label, index: index + 1, total: siblings.length }
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const usage = createMemo(() => {
|
|
35
|
+
const msg = messages()
|
|
36
|
+
const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
|
|
37
|
+
if (!last) return
|
|
38
|
+
|
|
39
|
+
const tokens =
|
|
40
|
+
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
|
|
41
|
+
if (tokens <= 0) return
|
|
42
|
+
|
|
43
|
+
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
|
|
44
|
+
const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
|
|
45
|
+
const cost = session()?.cost ?? 0
|
|
46
|
+
|
|
47
|
+
const money = new Intl.NumberFormat("en-US", {
|
|
48
|
+
style: "currency",
|
|
49
|
+
currency: "USD",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens),
|
|
54
|
+
cost: cost > 0 ? money.format(cost) : undefined,
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const { theme } = useTheme()
|
|
59
|
+
const command = useCommandPalette()
|
|
60
|
+
const parentShortcut = useCommandShortcut("session.parent")
|
|
61
|
+
const previousShortcut = useCommandShortcut("session.child.previous")
|
|
62
|
+
const nextShortcut = useCommandShortcut("session.child.next")
|
|
63
|
+
const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null)
|
|
64
|
+
useTerminalDimensions()
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<box flexShrink={0}>
|
|
68
|
+
<box
|
|
69
|
+
paddingTop={1}
|
|
70
|
+
paddingBottom={1}
|
|
71
|
+
paddingLeft={2}
|
|
72
|
+
paddingRight={1}
|
|
73
|
+
{...SplitBorder}
|
|
74
|
+
border={["left"]}
|
|
75
|
+
borderColor={theme.border}
|
|
76
|
+
flexShrink={0}
|
|
77
|
+
backgroundColor={theme.backgroundPanel}
|
|
78
|
+
>
|
|
79
|
+
<box flexDirection="row" justifyContent="space-between" gap={1}>
|
|
80
|
+
<box flexDirection="row" gap={1}>
|
|
81
|
+
<text fg={theme.text}>
|
|
82
|
+
<b>{subagentInfo().label}</b>
|
|
83
|
+
</text>
|
|
84
|
+
<Show when={subagentInfo().total > 0}>
|
|
85
|
+
<text style={{ fg: theme.textMuted }}>
|
|
86
|
+
({subagentInfo().index} of {subagentInfo().total})
|
|
87
|
+
</text>
|
|
88
|
+
</Show>
|
|
89
|
+
<Show when={usage()}>
|
|
90
|
+
{(item) => (
|
|
91
|
+
<text fg={theme.textMuted} wrapMode="none">
|
|
92
|
+
{[item().context, item().cost].filter(Boolean).join(" · ")}
|
|
93
|
+
</text>
|
|
94
|
+
)}
|
|
95
|
+
</Show>
|
|
96
|
+
</box>
|
|
97
|
+
<box flexDirection="row" gap={2}>
|
|
98
|
+
<box
|
|
99
|
+
onMouseOver={() => setHover("parent")}
|
|
100
|
+
onMouseOut={() => setHover(null)}
|
|
101
|
+
onMouseUp={() => command.run("session.parent")}
|
|
102
|
+
backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel}
|
|
103
|
+
>
|
|
104
|
+
<text fg={theme.text}>
|
|
105
|
+
Parent <span style={{ fg: theme.textMuted }}>{parentShortcut()}</span>
|
|
106
|
+
</text>
|
|
107
|
+
</box>
|
|
108
|
+
<box
|
|
109
|
+
onMouseOver={() => setHover("prev")}
|
|
110
|
+
onMouseOut={() => setHover(null)}
|
|
111
|
+
onMouseUp={() => command.run("session.child.previous")}
|
|
112
|
+
backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel}
|
|
113
|
+
>
|
|
114
|
+
<text fg={theme.text}>
|
|
115
|
+
Prev <span style={{ fg: theme.textMuted }}>{previousShortcut()}</span>
|
|
116
|
+
</text>
|
|
117
|
+
</box>
|
|
118
|
+
<box
|
|
119
|
+
onMouseOver={() => setHover("next")}
|
|
120
|
+
onMouseOut={() => setHover(null)}
|
|
121
|
+
onMouseUp={() => command.run("session.child.next")}
|
|
122
|
+
backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel}
|
|
123
|
+
>
|
|
124
|
+
<text fg={theme.text}>
|
|
125
|
+
Next <span style={{ fg: theme.textMuted }}>{nextShortcut()}</span>
|
|
126
|
+
</text>
|
|
127
|
+
</box>
|
|
128
|
+
</box>
|
|
129
|
+
</box>
|
|
130
|
+
</box>
|
|
131
|
+
</box>
|
|
132
|
+
)
|
|
133
|
+
}
|
package/src/tui/run.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APX TUI entry point.
|
|
3
|
+
* Usage: bun --preload @opentui/solid/preload src/tui/run.ts --pid <projectId> [--agent <name>] [--model <model>]
|
|
4
|
+
*/
|
|
5
|
+
import { tui } from "./app.tsx"
|
|
6
|
+
import type { TuiConfig } from "./config/tui.ts"
|
|
7
|
+
|
|
8
|
+
// ─── CLI argument parsing ────────────────────────────────────────────────────
|
|
9
|
+
const args = process.argv.slice(2)
|
|
10
|
+
function getFlag(flag: string): string | undefined {
|
|
11
|
+
const i = args.indexOf(flag)
|
|
12
|
+
return i !== -1 ? args[i + 1] : undefined
|
|
13
|
+
}
|
|
14
|
+
function hasFlag(flag: string): boolean {
|
|
15
|
+
return args.includes(flag)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const pid = getFlag("--pid") ?? ""
|
|
19
|
+
const agent = getFlag("--agent")
|
|
20
|
+
const model = getFlag("--model")
|
|
21
|
+
const promptText = getFlag("--prompt")
|
|
22
|
+
const sessionID = getFlag("--session")
|
|
23
|
+
const continueSession = hasFlag("--continue") || hasFlag("-c")
|
|
24
|
+
|
|
25
|
+
if (!pid) {
|
|
26
|
+
process.stderr.write("APX TUI requires --pid <projectId>\n")
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const baseUrl = process.env.APX_URL ?? "http://127.0.0.1:7430"
|
|
31
|
+
|
|
32
|
+
// ─── Minimal keybind lookup (no-op bindings, defaults are loaded by opentui) ─
|
|
33
|
+
function createNoopKeybinds(): TuiConfig.Resolved["keybinds"] {
|
|
34
|
+
return {
|
|
35
|
+
get(_command: string) {
|
|
36
|
+
return []
|
|
37
|
+
},
|
|
38
|
+
gather(_name: string, _commands: readonly string[]) {
|
|
39
|
+
return []
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Minimal resolved TUI config ─────────────────────────────────────────────
|
|
45
|
+
let config: TuiConfig.Resolved
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Attempt to load the real config (may fail if opencode-core deps missing)
|
|
49
|
+
const { TuiConfig: TuiConfigModule } = await import("./config/tui.ts")
|
|
50
|
+
config = await TuiConfigModule.get()
|
|
51
|
+
} catch {
|
|
52
|
+
// Fall back to safe minimal config
|
|
53
|
+
config = {
|
|
54
|
+
theme: undefined,
|
|
55
|
+
keybinds: createNoopKeybinds(),
|
|
56
|
+
plugin: [],
|
|
57
|
+
plugin_enabled: {},
|
|
58
|
+
leader_timeout: 2000,
|
|
59
|
+
scroll_speed: undefined,
|
|
60
|
+
scroll_acceleration: undefined,
|
|
61
|
+
diff_style: undefined,
|
|
62
|
+
mouse: true,
|
|
63
|
+
plugin_origins: [],
|
|
64
|
+
} as unknown as TuiConfig.Resolved
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Launch TUI ──────────────────────────────────────────────────────────────
|
|
68
|
+
await tui({
|
|
69
|
+
url: baseUrl,
|
|
70
|
+
pid,
|
|
71
|
+
agent,
|
|
72
|
+
model,
|
|
73
|
+
args: {
|
|
74
|
+
prompt: promptText,
|
|
75
|
+
continue: continueSession,
|
|
76
|
+
sessionID,
|
|
77
|
+
fork: false,
|
|
78
|
+
agent,
|
|
79
|
+
model,
|
|
80
|
+
},
|
|
81
|
+
config,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
process.exit(0)
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { cmd } from "@/cli/cmd/cmd"
|
|
2
|
+
import { Rpc } from "@/util/rpc"
|
|
3
|
+
import { type rpc } from "./worker"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
import { UI } from "@/cli/ui"
|
|
7
|
+
import * as Log from "@opencode-ai/core/util/log"
|
|
8
|
+
import { errorMessage } from "@/util/error"
|
|
9
|
+
import { withTimeout } from "@/util/timeout"
|
|
10
|
+
import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network"
|
|
11
|
+
import { Filesystem } from "@/util/filesystem"
|
|
12
|
+
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
|
|
13
|
+
import type { EventSource } from "./context/sdk"
|
|
14
|
+
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
|
15
|
+
import { writeHeapSnapshot } from "v8"
|
|
16
|
+
import { TuiConfig } from "./config/tui"
|
|
17
|
+
import {
|
|
18
|
+
OPENCODE_PROCESS_ROLE,
|
|
19
|
+
OPENCODE_RUN_ID,
|
|
20
|
+
ensureRunID,
|
|
21
|
+
sanitizedProcessEnv,
|
|
22
|
+
} from "@opencode-ai/core/util/opencode-process"
|
|
23
|
+
import { validateSession } from "./validate-session"
|
|
24
|
+
|
|
25
|
+
declare global {
|
|
26
|
+
const OPENCODE_WORKER_PATH: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type RpcClient = ReturnType<typeof Rpc.client<typeof rpc>>
|
|
30
|
+
|
|
31
|
+
function createWorkerFetch(client: RpcClient): typeof fetch {
|
|
32
|
+
const fn = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
33
|
+
const request = new Request(input, init)
|
|
34
|
+
const body = request.body ? await request.text() : undefined
|
|
35
|
+
const result = await client.call("fetch", {
|
|
36
|
+
url: request.url,
|
|
37
|
+
method: request.method,
|
|
38
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
39
|
+
body,
|
|
40
|
+
})
|
|
41
|
+
return new Response(result.body, {
|
|
42
|
+
status: result.status,
|
|
43
|
+
headers: result.headers,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
return fn as typeof fetch
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createEventSource(client: RpcClient): EventSource {
|
|
50
|
+
return {
|
|
51
|
+
subscribe: async (handler) => {
|
|
52
|
+
return client.on<GlobalEvent>("global.event", (e) => {
|
|
53
|
+
handler(e)
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function target() {
|
|
60
|
+
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
|
|
61
|
+
const dist = new URL("./cli/cmd/tui/worker.js", import.meta.url)
|
|
62
|
+
if (await Filesystem.exists(fileURLToPath(dist))) return dist
|
|
63
|
+
return new URL("./worker.ts", import.meta.url)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function input(value?: string) {
|
|
67
|
+
const piped = process.stdin.isTTY ? undefined : await Bun.stdin.text()
|
|
68
|
+
if (!value) return piped
|
|
69
|
+
if (!piped) return value
|
|
70
|
+
return piped + "\n" + value
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolveThreadDirectory(project?: string, envPWD = process.env.PWD, cwd = process.cwd()) {
|
|
74
|
+
const root = Filesystem.resolve(envPWD ?? cwd)
|
|
75
|
+
if (project) return Filesystem.resolve(path.isAbsolute(project) ? project : path.join(root, project))
|
|
76
|
+
return Filesystem.resolve(cwd)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const TuiThreadCommand = cmd({
|
|
80
|
+
command: "$0 [project]",
|
|
81
|
+
describe: "start opencode tui",
|
|
82
|
+
builder: (yargs) =>
|
|
83
|
+
withNetworkOptions(yargs)
|
|
84
|
+
.positional("project", {
|
|
85
|
+
type: "string",
|
|
86
|
+
describe: "path to start opencode in",
|
|
87
|
+
})
|
|
88
|
+
.option("model", {
|
|
89
|
+
type: "string",
|
|
90
|
+
alias: ["m"],
|
|
91
|
+
describe: "model to use in the format of provider/model",
|
|
92
|
+
})
|
|
93
|
+
.option("continue", {
|
|
94
|
+
alias: ["c"],
|
|
95
|
+
describe: "continue the last session",
|
|
96
|
+
type: "boolean",
|
|
97
|
+
})
|
|
98
|
+
.option("session", {
|
|
99
|
+
alias: ["s"],
|
|
100
|
+
type: "string",
|
|
101
|
+
describe: "session id to continue",
|
|
102
|
+
})
|
|
103
|
+
.option("fork", {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
describe: "fork the session when continuing (use with --continue or --session)",
|
|
106
|
+
})
|
|
107
|
+
.option("prompt", {
|
|
108
|
+
type: "string",
|
|
109
|
+
describe: "prompt to use",
|
|
110
|
+
})
|
|
111
|
+
.option("agent", {
|
|
112
|
+
type: "string",
|
|
113
|
+
describe: "agent to use",
|
|
114
|
+
}),
|
|
115
|
+
handler: async (args) => {
|
|
116
|
+
// Keep ENABLE_PROCESSED_INPUT cleared even if other code flips it.
|
|
117
|
+
// (Important when running under `bun run` wrappers on Windows.)
|
|
118
|
+
const unguard = win32InstallCtrlCGuard()
|
|
119
|
+
try {
|
|
120
|
+
// Must be the very first thing — disables CTRL_C_EVENT before any Worker
|
|
121
|
+
// spawn or async work so the OS cannot kill the process group.
|
|
122
|
+
win32DisableProcessedInput()
|
|
123
|
+
|
|
124
|
+
if (args.fork && !args.continue && !args.session) {
|
|
125
|
+
UI.error("--fork requires --continue or --session")
|
|
126
|
+
process.exitCode = 1
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Resolve relative --project paths from PWD, then use the real cwd after
|
|
131
|
+
// chdir so the thread and worker share the same directory key.
|
|
132
|
+
const next = resolveThreadDirectory(args.project)
|
|
133
|
+
const file = await target()
|
|
134
|
+
try {
|
|
135
|
+
process.chdir(next)
|
|
136
|
+
} catch {
|
|
137
|
+
UI.error("Failed to change directory to " + next)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
const cwd = Filesystem.resolve(process.cwd())
|
|
141
|
+
const env = sanitizedProcessEnv({
|
|
142
|
+
[OPENCODE_PROCESS_ROLE]: "worker",
|
|
143
|
+
[OPENCODE_RUN_ID]: ensureRunID(),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const worker = new Worker(file, {
|
|
147
|
+
env,
|
|
148
|
+
})
|
|
149
|
+
worker.onerror = (e) => {
|
|
150
|
+
Log.Default.error("thread error", {
|
|
151
|
+
message: e.message,
|
|
152
|
+
filename: e.filename,
|
|
153
|
+
lineno: e.lineno,
|
|
154
|
+
colno: e.colno,
|
|
155
|
+
error: e.error,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const client = Rpc.client<typeof rpc>(worker)
|
|
160
|
+
const error = (e: unknown) => {
|
|
161
|
+
Log.Default.error("process error", { error: errorMessage(e) })
|
|
162
|
+
}
|
|
163
|
+
const reload = () => {
|
|
164
|
+
client.call("reload", undefined).catch((err) => {
|
|
165
|
+
Log.Default.warn("worker reload failed", {
|
|
166
|
+
error: errorMessage(err),
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
process.on("uncaughtException", error)
|
|
171
|
+
process.on("unhandledRejection", error)
|
|
172
|
+
process.on("SIGUSR2", reload)
|
|
173
|
+
|
|
174
|
+
let stopped = false
|
|
175
|
+
const stop = async () => {
|
|
176
|
+
if (stopped) return
|
|
177
|
+
stopped = true
|
|
178
|
+
process.off("uncaughtException", error)
|
|
179
|
+
process.off("unhandledRejection", error)
|
|
180
|
+
process.off("SIGUSR2", reload)
|
|
181
|
+
await withTimeout(client.call("shutdown", undefined), 5000).catch((error) => {
|
|
182
|
+
Log.Default.warn("worker shutdown failed", {
|
|
183
|
+
error: errorMessage(error),
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
worker.terminate()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const prompt = await input(args.prompt)
|
|
190
|
+
const config = await TuiConfig.get()
|
|
191
|
+
|
|
192
|
+
const network = resolveNetworkOptionsNoConfig(args)
|
|
193
|
+
const external =
|
|
194
|
+
process.argv.includes("--port") ||
|
|
195
|
+
process.argv.includes("--hostname") ||
|
|
196
|
+
process.argv.includes("--mdns") ||
|
|
197
|
+
network.mdns ||
|
|
198
|
+
network.port !== 0 ||
|
|
199
|
+
network.hostname !== "127.0.0.1"
|
|
200
|
+
|
|
201
|
+
const transport = external
|
|
202
|
+
? {
|
|
203
|
+
url: (await client.call("server", network)).url,
|
|
204
|
+
fetch: undefined,
|
|
205
|
+
events: undefined,
|
|
206
|
+
}
|
|
207
|
+
: {
|
|
208
|
+
url: "http://opencode.internal",
|
|
209
|
+
fetch: createWorkerFetch(client),
|
|
210
|
+
events: createEventSource(client),
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await validateSession({
|
|
215
|
+
url: transport.url,
|
|
216
|
+
sessionID: args.session,
|
|
217
|
+
directory: cwd,
|
|
218
|
+
fetch: transport.fetch,
|
|
219
|
+
})
|
|
220
|
+
} catch (error) {
|
|
221
|
+
UI.error(errorMessage(error))
|
|
222
|
+
process.exitCode = 1
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
client.call("checkUpgrade", { directory: cwd }).catch(() => {})
|
|
228
|
+
}, 1000).unref?.()
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const { tui } = await import("./app")
|
|
232
|
+
await tui({
|
|
233
|
+
url: transport.url,
|
|
234
|
+
async onSnapshot() {
|
|
235
|
+
const tui = writeHeapSnapshot("tui.heapsnapshot")
|
|
236
|
+
const server = await client.call("snapshot", undefined)
|
|
237
|
+
return [tui, server]
|
|
238
|
+
},
|
|
239
|
+
config,
|
|
240
|
+
directory: cwd,
|
|
241
|
+
fetch: transport.fetch,
|
|
242
|
+
events: transport.events,
|
|
243
|
+
args: {
|
|
244
|
+
continue: args.continue,
|
|
245
|
+
sessionID: args.session,
|
|
246
|
+
agent: args.agent,
|
|
247
|
+
model: args.model,
|
|
248
|
+
prompt,
|
|
249
|
+
fork: args.fork,
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
} finally {
|
|
253
|
+
await stop()
|
|
254
|
+
}
|
|
255
|
+
} finally {
|
|
256
|
+
unguard?.()
|
|
257
|
+
}
|
|
258
|
+
process.exit(0)
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
// scratch
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.cli.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "preserve",
|
|
5
|
+
"jsxImportSource": "@opentui/solid",
|
|
6
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"types": [],
|
|
9
|
+
"paths": {
|
|
10
|
+
"@tui/*": ["./*"],
|
|
11
|
+
"@/cli/cmd/tui/*": ["./*"],
|
|
12
|
+
"@opencode-ai/sdk/v2": ["./_shims/opencode-sdk-v2.ts"],
|
|
13
|
+
"@opencode-ai/core/flag/flag": ["./_shims/core-flag.ts"],
|
|
14
|
+
"@opencode-ai/core/util/binary": ["./_shims/core-binary.ts"],
|
|
15
|
+
"@opencode-ai/core/util/log": ["./_shims/core-log.ts"],
|
|
16
|
+
"@opencode-ai/core/*": ["./_shims/core-any.ts"],
|
|
17
|
+
"@opencode-ai/plugin/tui": ["./_shims/plugin-tui.ts"],
|
|
18
|
+
"@opencode-ai/plugin/*": ["./_shims/opencode-any.ts"],
|
|
19
|
+
"@/session/session": ["./_shims/session-session.ts"],
|
|
20
|
+
"@/session/retry": ["./_shims/session-retry.ts"],
|
|
21
|
+
"@/session/schema": ["./_shims/session-schema.ts"],
|
|
22
|
+
"@/provider/provider": ["./_shims/provider-provider.ts"],
|
|
23
|
+
"@/snapshot": ["./_shims/snapshot.ts"],
|
|
24
|
+
"@/config/console-state": ["./_shims/config-console-state.ts"],
|
|
25
|
+
"@/cli/error": ["./_shims/cli-error.ts"],
|
|
26
|
+
"@/cli/logo": ["./_shims/cli-logo.ts"],
|
|
27
|
+
"@/cli/ui.ts": ["./_shims/cli-ui.ts"],
|
|
28
|
+
"@/cli/ui": ["./_shims/cli-ui.ts"],
|
|
29
|
+
"@/util/locale": ["./_shims/util-locale.ts"],
|
|
30
|
+
"@/util/error": ["./_shims/util-error.ts"],
|
|
31
|
+
"@/util/filesystem": ["./_shims/util-filesystem.ts"],
|
|
32
|
+
"@/util/iife": ["./_shims/util-iife.ts"],
|
|
33
|
+
"@/util/format": ["./_shims/util-format.ts"],
|
|
34
|
+
"@/util/process": ["./_shims/util-process.ts"],
|
|
35
|
+
"@/lsp/language": ["./_shims/lsp-language.ts"],
|
|
36
|
+
"@/tool/*": ["./_shims/tool-any.ts"],
|
|
37
|
+
"@/*": ["./_shims/opencode-any.ts"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { TextAttributes } from "@opentui/core"
|
|
2
|
+
import { useTheme } from "../context/theme"
|
|
3
|
+
import { useDialog, type DialogContext } from "./dialog"
|
|
4
|
+
import { useBindings } from "../keymap"
|
|
5
|
+
|
|
6
|
+
export type DialogAlertProps = {
|
|
7
|
+
title: string
|
|
8
|
+
message: string
|
|
9
|
+
onConfirm?: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function DialogAlert(props: DialogAlertProps) {
|
|
13
|
+
const dialog = useDialog()
|
|
14
|
+
const { theme } = useTheme()
|
|
15
|
+
|
|
16
|
+
useBindings(() => ({
|
|
17
|
+
bindings: [
|
|
18
|
+
{
|
|
19
|
+
key: "return",
|
|
20
|
+
desc: "Confirm alert",
|
|
21
|
+
group: "Dialog",
|
|
22
|
+
cmd: () => {
|
|
23
|
+
props.onConfirm?.()
|
|
24
|
+
dialog.clear()
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}))
|
|
29
|
+
return (
|
|
30
|
+
<box paddingLeft={2} paddingRight={2} gap={1}>
|
|
31
|
+
<box flexDirection="row" justifyContent="space-between">
|
|
32
|
+
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
33
|
+
{props.title}
|
|
34
|
+
</text>
|
|
35
|
+
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
36
|
+
esc
|
|
37
|
+
</text>
|
|
38
|
+
</box>
|
|
39
|
+
<box paddingBottom={1}>
|
|
40
|
+
<text fg={theme.textMuted}>{props.message}</text>
|
|
41
|
+
</box>
|
|
42
|
+
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
|
43
|
+
<box
|
|
44
|
+
paddingLeft={3}
|
|
45
|
+
paddingRight={3}
|
|
46
|
+
backgroundColor={theme.primary}
|
|
47
|
+
onMouseUp={() => {
|
|
48
|
+
props.onConfirm?.()
|
|
49
|
+
dialog.clear()
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<text fg={theme.selectedListItemText}>ok</text>
|
|
53
|
+
</box>
|
|
54
|
+
</box>
|
|
55
|
+
</box>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
DialogAlert.show = (dialog: DialogContext, title: string, message: string) => {
|
|
60
|
+
return new Promise<void>((resolve) => {
|
|
61
|
+
dialog.replace(
|
|
62
|
+
() => <DialogAlert title={title} message={message} onConfirm={() => resolve()} />,
|
|
63
|
+
() => resolve(),
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { TextAttributes } from "@opentui/core"
|
|
2
|
+
import { useTheme } from "../context/theme"
|
|
3
|
+
import { useDialog, type DialogContext } from "./dialog"
|
|
4
|
+
import { createStore } from "solid-js/store"
|
|
5
|
+
import { For } from "solid-js"
|
|
6
|
+
import { Locale } from "@/util/locale"
|
|
7
|
+
import { useBindings } from "../keymap"
|
|
8
|
+
|
|
9
|
+
export type DialogConfirmProps = {
|
|
10
|
+
title: string
|
|
11
|
+
message: string
|
|
12
|
+
onConfirm?: () => void
|
|
13
|
+
onCancel?: () => void
|
|
14
|
+
label?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type DialogConfirmResult = boolean | undefined
|
|
18
|
+
|
|
19
|
+
export function DialogConfirm(props: DialogConfirmProps) {
|
|
20
|
+
const dialog = useDialog()
|
|
21
|
+
const { theme } = useTheme()
|
|
22
|
+
const [store, setStore] = createStore({
|
|
23
|
+
active: "confirm" as "confirm" | "cancel",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
useBindings(() => ({
|
|
27
|
+
bindings: [
|
|
28
|
+
{
|
|
29
|
+
key: "return",
|
|
30
|
+
desc: "Confirm dialog selection",
|
|
31
|
+
group: "Dialog",
|
|
32
|
+
cmd: () => {
|
|
33
|
+
if (store.active === "confirm") props.onConfirm?.()
|
|
34
|
+
if (store.active === "cancel") props.onCancel?.()
|
|
35
|
+
dialog.clear()
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "left",
|
|
40
|
+
desc: "Previous dialog option",
|
|
41
|
+
group: "Dialog",
|
|
42
|
+
cmd: () => {
|
|
43
|
+
setStore("active", store.active === "confirm" ? "cancel" : "confirm")
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "right",
|
|
48
|
+
desc: "Next dialog option",
|
|
49
|
+
group: "Dialog",
|
|
50
|
+
cmd: () => {
|
|
51
|
+
setStore("active", store.active === "confirm" ? "cancel" : "confirm")
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}))
|
|
56
|
+
return (
|
|
57
|
+
<box paddingLeft={2} paddingRight={2} gap={1}>
|
|
58
|
+
<box flexDirection="row" justifyContent="space-between">
|
|
59
|
+
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
60
|
+
{props.title}
|
|
61
|
+
</text>
|
|
62
|
+
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
63
|
+
esc
|
|
64
|
+
</text>
|
|
65
|
+
</box>
|
|
66
|
+
<box paddingBottom={1}>
|
|
67
|
+
<text fg={theme.textMuted}>{props.message}</text>
|
|
68
|
+
</box>
|
|
69
|
+
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
|
70
|
+
<For each={["cancel", "confirm"] as const}>
|
|
71
|
+
{(key) => (
|
|
72
|
+
<box
|
|
73
|
+
paddingLeft={1}
|
|
74
|
+
paddingRight={1}
|
|
75
|
+
backgroundColor={key === store.active ? theme.primary : undefined}
|
|
76
|
+
onMouseUp={() => {
|
|
77
|
+
if (key === "confirm") props.onConfirm?.()
|
|
78
|
+
if (key === "cancel") props.onCancel?.()
|
|
79
|
+
dialog.clear()
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<text fg={key === store.active ? theme.selectedListItemText : theme.textMuted}>
|
|
83
|
+
{Locale.titlecase(key === "cancel" ? (props.label ?? key) : key)}
|
|
84
|
+
</text>
|
|
85
|
+
</box>
|
|
86
|
+
)}
|
|
87
|
+
</For>
|
|
88
|
+
</box>
|
|
89
|
+
</box>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
DialogConfirm.show = (dialog: DialogContext, title: string, message: string, label?: string) => {
|
|
94
|
+
return new Promise<DialogConfirmResult>((resolve) => {
|
|
95
|
+
dialog.replace(
|
|
96
|
+
() => (
|
|
97
|
+
<DialogConfirm
|
|
98
|
+
title={title}
|
|
99
|
+
message={message}
|
|
100
|
+
onConfirm={() => resolve(true)}
|
|
101
|
+
onCancel={() => resolve(false)}
|
|
102
|
+
label={label}
|
|
103
|
+
/>
|
|
104
|
+
),
|
|
105
|
+
() => resolve(undefined),
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
}
|