@agentprojectcontext/apx 1.15.6 → 1.17.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 +46 -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-langchain.js +296 -0
- package/src/daemon/super-agent.js +115 -19
- package/src/daemon/transcription.js +262 -59
- 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,156 @@
|
|
|
1
|
+
import { Player } from "cli-sound"
|
|
2
|
+
import { mkdirSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { basename, join } from "node:path"
|
|
5
|
+
import { Process } from "@/util/process"
|
|
6
|
+
import { which } from "@/util/which"
|
|
7
|
+
import pulseA from "../asset/pulse-a.wav" with { type: "file" }
|
|
8
|
+
import pulseB from "../asset/pulse-b.wav" with { type: "file" }
|
|
9
|
+
import pulseC from "../asset/pulse-c.wav" with { type: "file" }
|
|
10
|
+
import charge from "../asset/charge.wav" with { type: "file" }
|
|
11
|
+
|
|
12
|
+
const FILE = [pulseA, pulseB, pulseC]
|
|
13
|
+
|
|
14
|
+
const HUM = charge
|
|
15
|
+
const DIR = join(tmpdir(), "opencode-sfx")
|
|
16
|
+
|
|
17
|
+
const LIST = [
|
|
18
|
+
"ffplay",
|
|
19
|
+
"mpv",
|
|
20
|
+
"mpg123",
|
|
21
|
+
"mpg321",
|
|
22
|
+
"mplayer",
|
|
23
|
+
"afplay",
|
|
24
|
+
"play",
|
|
25
|
+
"omxplayer",
|
|
26
|
+
"aplay",
|
|
27
|
+
"cmdmp3",
|
|
28
|
+
"cvlc",
|
|
29
|
+
"powershell.exe",
|
|
30
|
+
] as const
|
|
31
|
+
|
|
32
|
+
type Kind = (typeof LIST)[number]
|
|
33
|
+
|
|
34
|
+
function args(kind: Kind, file: string, volume: number) {
|
|
35
|
+
if (kind === "ffplay") return [kind, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file]
|
|
36
|
+
if (kind === "mpv")
|
|
37
|
+
return [kind, "--no-video", "--audio-display=no", "--volume", String(Math.round(volume * 100)), file]
|
|
38
|
+
if (kind === "mpg123" || kind === "mpg321") return [kind, "-g", String(Math.round(volume * 100)), file]
|
|
39
|
+
if (kind === "mplayer") return [kind, "-vo", "null", "-volume", String(Math.round(volume * 100)), file]
|
|
40
|
+
if (kind === "afplay" || kind === "omxplayer" || kind === "aplay" || kind === "cmdmp3") return [kind, file]
|
|
41
|
+
if (kind === "play") return [kind, "-v", String(volume), file]
|
|
42
|
+
if (kind === "cvlc") return [kind, `--gain=${volume}`, "--play-and-exit", file]
|
|
43
|
+
return [kind, "-c", `(New-Object Media.SoundPlayer '${file.replace(/'/g, "''")}').PlaySync()`]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let item: Player | null | undefined
|
|
47
|
+
let kind: Kind | null | undefined
|
|
48
|
+
let proc: Process.Child | undefined
|
|
49
|
+
let tail: ReturnType<typeof setTimeout> | undefined
|
|
50
|
+
let cache: Promise<{ hum: string; pulse: string[] }> | undefined
|
|
51
|
+
let seq = 0
|
|
52
|
+
let shot = 0
|
|
53
|
+
|
|
54
|
+
function load() {
|
|
55
|
+
if (item !== undefined) return item
|
|
56
|
+
try {
|
|
57
|
+
item = new Player({ volume: 0.35 })
|
|
58
|
+
} catch {
|
|
59
|
+
item = null
|
|
60
|
+
}
|
|
61
|
+
return item
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function file(path: string) {
|
|
65
|
+
mkdirSync(DIR, { recursive: true })
|
|
66
|
+
const next = join(DIR, basename(path))
|
|
67
|
+
const out = Bun.file(next)
|
|
68
|
+
if (await out.exists()) return next
|
|
69
|
+
await Bun.write(out, Bun.file(path))
|
|
70
|
+
return next
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function asset() {
|
|
74
|
+
cache ??= Promise.all([file(HUM), Promise.all(FILE.map(file))]).then(([hum, pulse]) => ({ hum, pulse }))
|
|
75
|
+
return cache
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function pick() {
|
|
79
|
+
if (kind !== undefined) return kind
|
|
80
|
+
kind = LIST.find((item) => which(item)) ?? null
|
|
81
|
+
return kind
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function run(file: string, volume: number) {
|
|
85
|
+
const kind = pick()
|
|
86
|
+
if (!kind) return
|
|
87
|
+
return Process.spawn(args(kind, file, volume), {
|
|
88
|
+
stdin: "ignore",
|
|
89
|
+
stdout: "ignore",
|
|
90
|
+
stderr: "ignore",
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function clear() {
|
|
95
|
+
if (!tail) return
|
|
96
|
+
clearTimeout(tail)
|
|
97
|
+
tail = undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function play(file: string, volume: number) {
|
|
101
|
+
const item = load()
|
|
102
|
+
if (!item) return run(file, volume)?.exited
|
|
103
|
+
return item.play(file, { volume }).catch(() => run(file, volume)?.exited)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function start() {
|
|
107
|
+
stop()
|
|
108
|
+
const id = ++seq
|
|
109
|
+
void asset().then(({ hum }) => {
|
|
110
|
+
if (id !== seq) return
|
|
111
|
+
const next = run(hum, 0.24)
|
|
112
|
+
if (!next) return
|
|
113
|
+
proc = next
|
|
114
|
+
void next.exited.then(
|
|
115
|
+
() => {
|
|
116
|
+
if (id !== seq) return
|
|
117
|
+
if (proc === next) proc = undefined
|
|
118
|
+
},
|
|
119
|
+
() => {
|
|
120
|
+
if (id !== seq) return
|
|
121
|
+
if (proc === next) proc = undefined
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function stop(delay = 0) {
|
|
128
|
+
seq++
|
|
129
|
+
clear()
|
|
130
|
+
if (!proc) return
|
|
131
|
+
const next = proc
|
|
132
|
+
if (delay <= 0) {
|
|
133
|
+
proc = undefined
|
|
134
|
+
void Process.stop(next).catch(() => undefined)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
tail = setTimeout(() => {
|
|
138
|
+
tail = undefined
|
|
139
|
+
if (proc === next) proc = undefined
|
|
140
|
+
void Process.stop(next).catch(() => undefined)
|
|
141
|
+
}, delay)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function pulse(scale = 1) {
|
|
145
|
+
stop(140)
|
|
146
|
+
const index = shot++ % FILE.length
|
|
147
|
+
void asset()
|
|
148
|
+
.then(({ pulse }) => play(pulse[index], 0.26 + 0.14 * scale))
|
|
149
|
+
.catch(() => undefined)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function dispose() {
|
|
153
|
+
stop()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export * as Sound from "./sound"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { AssistantMessage, Part, Provider, UserMessage } from "@opencode-ai/sdk/v2"
|
|
2
|
+
import { Locale } from "@/util/locale"
|
|
3
|
+
import * as Model from "./model"
|
|
4
|
+
|
|
5
|
+
export type TranscriptOptions = {
|
|
6
|
+
thinking: boolean
|
|
7
|
+
toolDetails: boolean
|
|
8
|
+
assistantMetadata: boolean
|
|
9
|
+
providers?: Provider[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SessionInfo = {
|
|
13
|
+
id: string
|
|
14
|
+
title: string
|
|
15
|
+
time: {
|
|
16
|
+
created: number
|
|
17
|
+
updated: number
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type MessageWithParts = {
|
|
22
|
+
info: UserMessage | AssistantMessage
|
|
23
|
+
parts: Part[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatTranscript(
|
|
27
|
+
session: SessionInfo,
|
|
28
|
+
messages: MessageWithParts[],
|
|
29
|
+
options: TranscriptOptions,
|
|
30
|
+
): string {
|
|
31
|
+
const providers = Model.index(options.providers)
|
|
32
|
+
let transcript = `# ${session.title}\n\n`
|
|
33
|
+
transcript += `**Session ID:** ${session.id}\n`
|
|
34
|
+
transcript += `**Created:** ${new Date(session.time.created).toLocaleString()}\n`
|
|
35
|
+
transcript += `**Updated:** ${new Date(session.time.updated).toLocaleString()}\n\n`
|
|
36
|
+
transcript += `---\n\n`
|
|
37
|
+
|
|
38
|
+
for (const msg of messages) {
|
|
39
|
+
transcript += formatMessage(msg.info, msg.parts, options, providers)
|
|
40
|
+
transcript += `---\n\n`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return transcript
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatMessage(
|
|
47
|
+
msg: UserMessage | AssistantMessage,
|
|
48
|
+
parts: Part[],
|
|
49
|
+
options: TranscriptOptions,
|
|
50
|
+
providers?: Provider[] | ReadonlyMap<string, Provider>,
|
|
51
|
+
): string {
|
|
52
|
+
let result = ""
|
|
53
|
+
|
|
54
|
+
if (msg.role === "user") {
|
|
55
|
+
result += `## User\n\n`
|
|
56
|
+
} else {
|
|
57
|
+
result += formatAssistantHeader(msg, options.assistantMetadata, providers ?? options.providers)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const part of parts) {
|
|
61
|
+
result += formatPart(part, options)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatAssistantHeader(
|
|
68
|
+
msg: AssistantMessage,
|
|
69
|
+
includeMetadata: boolean,
|
|
70
|
+
providers?: Provider[] | ReadonlyMap<string, Provider>,
|
|
71
|
+
): string {
|
|
72
|
+
if (!includeMetadata) {
|
|
73
|
+
return `## Assistant\n\n`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const duration =
|
|
77
|
+
msg.time.completed && msg.time.created ? ((msg.time.completed - msg.time.created) / 1000).toFixed(1) + "s" : ""
|
|
78
|
+
|
|
79
|
+
const modelName = Model.name(providers, msg.providerID, msg.modelID)
|
|
80
|
+
|
|
81
|
+
return `## Assistant (${Locale.titlecase(msg.agent)} · ${modelName}${duration ? ` · ${duration}` : ""})\n\n`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function formatPart(part: Part, options: TranscriptOptions): string {
|
|
85
|
+
if (part.type === "text" && !part.synthetic) {
|
|
86
|
+
return `${part.text}\n\n`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (part.type === "reasoning") {
|
|
90
|
+
if (options.thinking) {
|
|
91
|
+
return `_Thinking:_\n\n${part.text}\n\n`
|
|
92
|
+
}
|
|
93
|
+
return ""
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (part.type === "tool") {
|
|
97
|
+
let result = `**Tool: ${part.tool}**\n`
|
|
98
|
+
if (options.toolDetails && part.state.input) {
|
|
99
|
+
result += `\n**Input:**\n\`\`\`json\n${JSON.stringify(part.state.input, null, 2)}\n\`\`\`\n`
|
|
100
|
+
}
|
|
101
|
+
if (options.toolDetails && part.state.status === "completed" && part.state.output) {
|
|
102
|
+
result += `\n**Output:**\n\`\`\`\n${part.state.output}\n\`\`\`\n`
|
|
103
|
+
}
|
|
104
|
+
if (options.toolDetails && part.state.status === "error" && part.state.error) {
|
|
105
|
+
result += `\n**Error:**\n\`\`\`\n${part.state.error}\n\`\`\`\n`
|
|
106
|
+
}
|
|
107
|
+
result += `\n`
|
|
108
|
+
return result
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return ""
|
|
112
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
|
2
|
+
import { SessionID } from "@/session/schema"
|
|
3
|
+
import { Schema } from "effect"
|
|
4
|
+
|
|
5
|
+
const decodeSessionID = Schema.decodeUnknownSync(SessionID)
|
|
6
|
+
|
|
7
|
+
export async function validateSession(input: {
|
|
8
|
+
url: string
|
|
9
|
+
sessionID?: string
|
|
10
|
+
directory?: string
|
|
11
|
+
fetch?: typeof fetch
|
|
12
|
+
headers?: RequestInit["headers"]
|
|
13
|
+
}) {
|
|
14
|
+
if (!input.sessionID) return
|
|
15
|
+
|
|
16
|
+
let sessionID: SessionID
|
|
17
|
+
try {
|
|
18
|
+
sessionID = decodeSessionID(input.sessionID)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`Invalid session ID: ${error instanceof Error ? error.message : "unknown error"}`, { cause: error })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await createOpencodeClient({
|
|
24
|
+
baseUrl: input.url,
|
|
25
|
+
directory: input.directory,
|
|
26
|
+
fetch: input.fetch,
|
|
27
|
+
headers: input.headers,
|
|
28
|
+
}).session.get({ sessionID }, { throwOnError: true })
|
|
29
|
+
}
|
package/src/tui/win32.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { dlopen, ptr } from "bun:ffi"
|
|
2
|
+
import type { ReadStream } from "node:tty"
|
|
3
|
+
|
|
4
|
+
const STD_INPUT_HANDLE = -10
|
|
5
|
+
const ENABLE_PROCESSED_INPUT = 0x0001
|
|
6
|
+
|
|
7
|
+
const kernel = () =>
|
|
8
|
+
dlopen("kernel32.dll", {
|
|
9
|
+
GetStdHandle: { args: ["i32"], returns: "ptr" },
|
|
10
|
+
GetConsoleMode: { args: ["ptr", "ptr"], returns: "i32" },
|
|
11
|
+
SetConsoleMode: { args: ["ptr", "u32"], returns: "i32" },
|
|
12
|
+
FlushConsoleInputBuffer: { args: ["ptr"], returns: "i32" },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
let k32: ReturnType<typeof kernel> | undefined
|
|
16
|
+
|
|
17
|
+
function load() {
|
|
18
|
+
if (process.platform !== "win32") return false
|
|
19
|
+
try {
|
|
20
|
+
k32 ??= kernel()
|
|
21
|
+
return true
|
|
22
|
+
} catch {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clear ENABLE_PROCESSED_INPUT on the console stdin handle.
|
|
29
|
+
*/
|
|
30
|
+
export function win32DisableProcessedInput() {
|
|
31
|
+
if (process.platform !== "win32") return
|
|
32
|
+
if (!process.stdin.isTTY) return
|
|
33
|
+
if (!load()) return
|
|
34
|
+
|
|
35
|
+
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
|
36
|
+
const buf = new Uint32Array(1)
|
|
37
|
+
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
|
38
|
+
|
|
39
|
+
const mode = buf[0]!
|
|
40
|
+
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
|
41
|
+
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Discard any queued console input (mouse events, key presses, etc.).
|
|
46
|
+
*/
|
|
47
|
+
export function win32FlushInputBuffer() {
|
|
48
|
+
if (process.platform !== "win32") return
|
|
49
|
+
if (!process.stdin.isTTY) return
|
|
50
|
+
if (!load()) return
|
|
51
|
+
|
|
52
|
+
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
|
53
|
+
k32!.symbols.FlushConsoleInputBuffer(handle)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let unhook: (() => void) | undefined
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Keep ENABLE_PROCESSED_INPUT disabled.
|
|
60
|
+
*
|
|
61
|
+
* On Windows, Ctrl+C becomes a CTRL_C_EVENT (instead of stdin input) when
|
|
62
|
+
* ENABLE_PROCESSED_INPUT is set. Various runtimes can re-apply console modes
|
|
63
|
+
* (sometimes on a later tick), and the flag is console-global, not per-process.
|
|
64
|
+
*
|
|
65
|
+
* We combine:
|
|
66
|
+
* - A `setRawMode(...)` hook to re-clear after known raw-mode toggles.
|
|
67
|
+
* - A low-frequency poll as a backstop for native/external mode changes.
|
|
68
|
+
*/
|
|
69
|
+
export function win32InstallCtrlCGuard() {
|
|
70
|
+
if (process.platform !== "win32") return
|
|
71
|
+
if (!process.stdin.isTTY) return
|
|
72
|
+
if (!load()) return
|
|
73
|
+
if (unhook) return unhook
|
|
74
|
+
|
|
75
|
+
const stdin = process.stdin as ReadStream
|
|
76
|
+
const original = stdin.setRawMode
|
|
77
|
+
|
|
78
|
+
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
|
79
|
+
const buf = new Uint32Array(1)
|
|
80
|
+
|
|
81
|
+
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
|
82
|
+
const initial = buf[0]!
|
|
83
|
+
|
|
84
|
+
const enforce = () => {
|
|
85
|
+
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
|
86
|
+
const mode = buf[0]!
|
|
87
|
+
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
|
88
|
+
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Some runtimes can re-apply console modes on the next tick; enforce twice.
|
|
92
|
+
const later = () => {
|
|
93
|
+
enforce()
|
|
94
|
+
setImmediate(enforce)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let wrapped: ReadStream["setRawMode"] | undefined
|
|
98
|
+
|
|
99
|
+
if (typeof original === "function") {
|
|
100
|
+
wrapped = (mode: boolean) => {
|
|
101
|
+
const result = original.call(stdin, mode)
|
|
102
|
+
later()
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stdin.setRawMode = wrapped
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Ensure it's cleared immediately too (covers any earlier mode changes).
|
|
110
|
+
later()
|
|
111
|
+
|
|
112
|
+
const interval = setInterval(enforce, 100)
|
|
113
|
+
interval.unref()
|
|
114
|
+
|
|
115
|
+
let done = false
|
|
116
|
+
unhook = () => {
|
|
117
|
+
if (done) return
|
|
118
|
+
done = true
|
|
119
|
+
|
|
120
|
+
clearInterval(interval)
|
|
121
|
+
if (wrapped && stdin.setRawMode === wrapped) {
|
|
122
|
+
stdin.setRawMode = original
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
k32!.symbols.SetConsoleMode(handle, initial)
|
|
126
|
+
unhook = undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return unhook
|
|
130
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Installation } from "@/installation"
|
|
2
|
+
import { Server } from "@/server/server"
|
|
3
|
+
import * as Log from "@opencode-ai/core/util/log"
|
|
4
|
+
import { InstanceRuntime } from "@/project/instance-runtime"
|
|
5
|
+
import { WithInstance } from "@/project/with-instance"
|
|
6
|
+
import { Rpc } from "@/util/rpc"
|
|
7
|
+
import { upgrade } from "@/cli/upgrade"
|
|
8
|
+
import { Config } from "@/config/config"
|
|
9
|
+
import { GlobalBus } from "@/bus/global"
|
|
10
|
+
import { ServerAuth } from "@/server/auth"
|
|
11
|
+
import { writeHeapSnapshot } from "node:v8"
|
|
12
|
+
import { Heap } from "@/cli/heap"
|
|
13
|
+
import { AppRuntime } from "@/effect/app-runtime"
|
|
14
|
+
import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
|
|
15
|
+
import { Effect } from "effect"
|
|
16
|
+
import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecycle"
|
|
17
|
+
|
|
18
|
+
ensureProcessMetadata("worker")
|
|
19
|
+
|
|
20
|
+
await Log.init({
|
|
21
|
+
print: process.argv.includes("--print-logs"),
|
|
22
|
+
dev: Installation.isLocal(),
|
|
23
|
+
level: (() => {
|
|
24
|
+
if (Installation.isLocal()) return "DEBUG"
|
|
25
|
+
return "INFO"
|
|
26
|
+
})(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
Heap.start()
|
|
30
|
+
|
|
31
|
+
process.on("unhandledRejection", (e) => {
|
|
32
|
+
Log.Default.error("rejection", {
|
|
33
|
+
e: e instanceof Error ? e.message : e,
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
process.on("uncaughtException", (e) => {
|
|
38
|
+
Log.Default.error("exception", {
|
|
39
|
+
e: e instanceof Error ? e.message : e,
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Subscribe to global events and forward them via RPC
|
|
44
|
+
GlobalBus.on("event", (event) => {
|
|
45
|
+
Rpc.emit("global.event", event)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
let server: Awaited<ReturnType<typeof Server.listen>> | undefined
|
|
49
|
+
|
|
50
|
+
export const rpc = {
|
|
51
|
+
async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) {
|
|
52
|
+
const headers = { ...input.headers }
|
|
53
|
+
const auth = ServerAuth.header()
|
|
54
|
+
if (auth && !headers["authorization"] && !headers["Authorization"]) {
|
|
55
|
+
headers["Authorization"] = auth
|
|
56
|
+
}
|
|
57
|
+
const request = new Request(input.url, {
|
|
58
|
+
method: input.method,
|
|
59
|
+
headers,
|
|
60
|
+
body: input.body,
|
|
61
|
+
})
|
|
62
|
+
const response = await Server.Default().app.fetch(request)
|
|
63
|
+
const body = await response.text()
|
|
64
|
+
return {
|
|
65
|
+
status: response.status,
|
|
66
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
67
|
+
body,
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
snapshot() {
|
|
71
|
+
const result = writeHeapSnapshot("server.heapsnapshot")
|
|
72
|
+
return result
|
|
73
|
+
},
|
|
74
|
+
async server(input: { port: number; hostname: string; mdns?: boolean; cors?: string[] }) {
|
|
75
|
+
if (server) await server.stop(true)
|
|
76
|
+
server = await Server.listen(input)
|
|
77
|
+
return { url: server.url.toString() }
|
|
78
|
+
},
|
|
79
|
+
async checkUpgrade(input: { directory: string }) {
|
|
80
|
+
await WithInstance.provide({
|
|
81
|
+
directory: input.directory,
|
|
82
|
+
fn: async () => {
|
|
83
|
+
await upgrade().catch(() => {})
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
async reload() {
|
|
88
|
+
await AppRuntime.runPromise(
|
|
89
|
+
Effect.gen(function* () {
|
|
90
|
+
const cfg = yield* Config.Service
|
|
91
|
+
yield* cfg.invalidate()
|
|
92
|
+
yield* disposeAllInstancesAndEmitGlobalDisposed({ swallowErrors: true })
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
},
|
|
96
|
+
async shutdown() {
|
|
97
|
+
Log.Default.info("worker shutting down")
|
|
98
|
+
|
|
99
|
+
await InstanceRuntime.disposeAllInstances()
|
|
100
|
+
if (server) await server.stop(true)
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Rpc.listen(rpc)
|