snow-flow 10.0.185 → 10.0.186-dev.682
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/bin/index.js.map +9 -9
- package/bin/worker.js.map +7 -7
- package/mcp/servicenow-unified.js +116 -116
- package/package.json +1 -1
- package/parsers-config.ts +2 -1
- package/src/bun/index.ts +10 -9
- package/src/cli/cmd/agent.ts +3 -3
- package/src/cli/cmd/auth.ts +46 -0
- package/src/cli/cmd/import.ts +2 -2
- package/src/cli/cmd/session.ts +9 -12
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +2 -1
- package/src/cli/cmd/tui/component/prompt/index.tsx +19 -6
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/context/exit.tsx +1 -1
- package/src/cli/cmd/tui/routes/home.tsx +16 -2
- package/src/cli/cmd/tui/routes/session/index.tsx +122 -53
- package/src/cli/cmd/tui/routes/session/permission.tsx +9 -1
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +9 -1
- package/src/cli/cmd/tui/thread.ts +4 -1
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +1 -1
- package/src/cli/cmd/tui/util/clipboard.ts +3 -3
- package/src/cli/cmd/tui/worker.ts +6 -1
- package/src/config/config.ts +28 -0
- package/src/context/context-db.ts +437 -0
- package/src/format/formatter.ts +14 -5
- package/src/global/index.ts +3 -4
- package/src/mcp/index.ts +7 -2
- package/src/mcp/oauth-callback.ts +7 -15
- package/src/mcp/oauth-provider.ts +34 -3
- package/src/project/project.ts +8 -4
- package/src/provider/models.ts +1 -1
- package/src/provider/provider.ts +88 -9
- package/src/provider/transform.ts +7 -2
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_capacity_plan.ts +20 -7
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_retrospective.ts +6 -8
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_sprint_manage.ts +46 -28
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_team_manage.ts +53 -41
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_velocity_report.ts +8 -1
- package/src/servicenow/servicenow-mcp-unified/tools/automation/snow_schedule_script_job.ts +388 -243
- package/src/session/compaction.ts +126 -23
- package/src/session/message-v2.ts +33 -10
- package/src/session/processor.ts +29 -17
- package/src/session/prompt.ts +34 -6
- package/src/share/share-next.ts +2 -2
- package/src/shell/shell.ts +2 -1
- package/src/tool/edit.ts +15 -1
- package/src/tool/registry.ts +9 -1
- package/src/tool/truncation.ts +17 -0
- package/src/tool/websearch.ts +1 -1
- package/src/tool/websearch.txt +2 -2
- package/src/tool/write.ts +3 -4
- package/src/util/filesystem.ts +36 -7
- package/src/util/keybind.ts +1 -1
- package/src/util/log.ts +8 -5
- package/src/util/token.ts +28 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/fixture/fixture.ts +3 -0
- package/test/mcp/oauth-auto-connect.test.ts +197 -0
- package/test/project/project.test.ts +47 -0
- package/test/provider/provider.test.ts +2 -0
- package/test/provider/transform.test.ts +32 -0
- package/test/tool/edit.test.ts +679 -0
package/package.json
CHANGED
package/parsers-config.ts
CHANGED
|
@@ -214,7 +214,8 @@ export default {
|
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
filetype: "clojure",
|
|
217
|
-
|
|
217
|
+
// temporarily using fork to fix issues
|
|
218
|
+
wasm: "https://github.com/anomalyco/tree-sitter-clojure/releases/download/v0.0.1/tree-sitter-clojure.wasm",
|
|
218
219
|
queries: {
|
|
219
220
|
highlights: [
|
|
220
221
|
"https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/clojure/highlights.scm",
|
package/src/bun/index.ts
CHANGED
|
@@ -66,14 +66,14 @@ export namespace BunProc {
|
|
|
66
66
|
using _ = await Lock.write("bun-install")
|
|
67
67
|
|
|
68
68
|
const mod = path.join(Global.Path.cache, "node_modules", pkg)
|
|
69
|
-
const
|
|
70
|
-
const parsed = await
|
|
71
|
-
const result = { dependencies: {} }
|
|
72
|
-
await
|
|
69
|
+
const pkgjsonPath = path.join(Global.Path.cache, "package.json")
|
|
70
|
+
const parsed = await Filesystem.readJson<{ dependencies: Record<string, string> }>(pkgjsonPath).catch(async () => {
|
|
71
|
+
const result = { dependencies: {} as Record<string, string> }
|
|
72
|
+
await Filesystem.writeJson(pkgjsonPath, result)
|
|
73
73
|
return result
|
|
74
74
|
})
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
if (!parsed.dependencies) parsed.dependencies = {} as Record<string, string>
|
|
76
|
+
const dependencies = parsed.dependencies
|
|
77
77
|
const modExists = await Filesystem.exists(mod)
|
|
78
78
|
if (dependencies[pkg] === version && modExists) return mod
|
|
79
79
|
|
|
@@ -120,15 +120,16 @@ export namespace BunProc {
|
|
|
120
120
|
// This ensures subsequent starts use the cached version until explicitly updated
|
|
121
121
|
let resolvedVersion = version
|
|
122
122
|
if (version === "latest") {
|
|
123
|
-
const
|
|
124
|
-
|
|
123
|
+
const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch(
|
|
124
|
+
() => null,
|
|
125
|
+
)
|
|
125
126
|
if (installedPkg?.version) {
|
|
126
127
|
resolvedVersion = installedPkg.version
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
parsed.dependencies[pkg] = resolvedVersion
|
|
131
|
-
await
|
|
132
|
+
await Filesystem.writeJson(pkgjsonPath, parsed)
|
|
132
133
|
return mod
|
|
133
134
|
}
|
|
134
135
|
}
|
package/src/cli/cmd/agent.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Agent } from "../../agent/agent"
|
|
|
6
6
|
import { Provider } from "../../provider/provider"
|
|
7
7
|
import path from "path"
|
|
8
8
|
import fs from "fs/promises"
|
|
9
|
+
import { Filesystem } from "../../util/filesystem"
|
|
9
10
|
import matter from "gray-matter"
|
|
10
11
|
import { Instance } from "../../project/instance"
|
|
11
12
|
import { EOL } from "os"
|
|
@@ -202,8 +203,7 @@ const AgentCreateCommand = cmd({
|
|
|
202
203
|
|
|
203
204
|
await fs.mkdir(targetPath, { recursive: true })
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
if (await file.exists()) {
|
|
206
|
+
if (await Filesystem.exists(filePath)) {
|
|
207
207
|
if (isFullyNonInteractive) {
|
|
208
208
|
console.error(`Error: Agent file already exists: ${filePath}`)
|
|
209
209
|
process.exit(1)
|
|
@@ -212,7 +212,7 @@ const AgentCreateCommand = cmd({
|
|
|
212
212
|
throw new UI.CancelledError()
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
await
|
|
215
|
+
await Filesystem.write(filePath, content)
|
|
216
216
|
|
|
217
217
|
if (isFullyNonInteractive) {
|
|
218
218
|
console.log(filePath)
|
package/src/cli/cmd/auth.ts
CHANGED
|
@@ -159,6 +159,38 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string):
|
|
|
159
159
|
return false
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Build a deduplicated list of plugin-registered auth providers that are not
|
|
164
|
+
* already present in models.dev, respecting enabled/disabled provider lists.
|
|
165
|
+
* Pure function with no side effects; safe to test without mocking.
|
|
166
|
+
*/
|
|
167
|
+
export function resolvePluginProviders(input: {
|
|
168
|
+
hooks: Hooks[]
|
|
169
|
+
existingProviders: Record<string, unknown>
|
|
170
|
+
disabled: Set<string>
|
|
171
|
+
enabled?: Set<string>
|
|
172
|
+
providerNames: Record<string, string | undefined>
|
|
173
|
+
}): Array<{ id: string; name: string }> {
|
|
174
|
+
const seen = new Set<string>()
|
|
175
|
+
const result: Array<{ id: string; name: string }> = []
|
|
176
|
+
|
|
177
|
+
for (const hook of input.hooks) {
|
|
178
|
+
if (!hook.auth) continue
|
|
179
|
+
const id = hook.auth.provider
|
|
180
|
+
if (seen.has(id)) continue
|
|
181
|
+
seen.add(id)
|
|
182
|
+
if (Object.hasOwn(input.existingProviders, id)) continue
|
|
183
|
+
if (input.disabled.has(id)) continue
|
|
184
|
+
if (input.enabled && !input.enabled.has(id)) continue
|
|
185
|
+
result.push({
|
|
186
|
+
id,
|
|
187
|
+
name: input.providerNames[id] ?? id,
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
}
|
|
193
|
+
|
|
162
194
|
export const AuthCommand = cmd({
|
|
163
195
|
command: "auth",
|
|
164
196
|
describe: "manage credentials",
|
|
@@ -277,6 +309,15 @@ export const AuthLoginCommand = cmd({
|
|
|
277
309
|
openrouter: 5,
|
|
278
310
|
vercel: 6,
|
|
279
311
|
}
|
|
312
|
+
const pluginProviders = resolvePluginProviders({
|
|
313
|
+
hooks: await Plugin.list(),
|
|
314
|
+
existingProviders: providers,
|
|
315
|
+
disabled,
|
|
316
|
+
enabled,
|
|
317
|
+
providerNames: Object.fromEntries(
|
|
318
|
+
Object.entries(config.provider ?? {}).map(([id, p]) => [id, p.name]),
|
|
319
|
+
),
|
|
320
|
+
})
|
|
280
321
|
let provider = await prompts.autocomplete({
|
|
281
322
|
message: "Select provider",
|
|
282
323
|
maxItems: 8,
|
|
@@ -298,6 +339,11 @@ export const AuthLoginCommand = cmd({
|
|
|
298
339
|
}[x.id],
|
|
299
340
|
})),
|
|
300
341
|
),
|
|
342
|
+
...pluginProviders.map((x) => ({
|
|
343
|
+
label: x.name,
|
|
344
|
+
value: x.id,
|
|
345
|
+
hint: "plugin",
|
|
346
|
+
})),
|
|
301
347
|
{
|
|
302
348
|
value: "other",
|
|
303
349
|
label: "Other",
|
package/src/cli/cmd/import.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { bootstrap } from "../bootstrap"
|
|
|
5
5
|
import { Storage } from "../../storage/storage"
|
|
6
6
|
import { Instance } from "../../project/instance"
|
|
7
7
|
import { EOL } from "os"
|
|
8
|
+
import { Filesystem } from "../../util/filesystem"
|
|
8
9
|
|
|
9
10
|
export const ImportCommand = cmd({
|
|
10
11
|
command: "import <file>",
|
|
@@ -66,8 +67,7 @@ export const ImportCommand = cmd({
|
|
|
66
67
|
}),
|
|
67
68
|
}
|
|
68
69
|
} else {
|
|
69
|
-
|
|
70
|
-
exportData = await file.json().catch(() => {})
|
|
70
|
+
exportData = await Filesystem.readJson<NonNullable<typeof exportData>>(args.file).catch(() => undefined)
|
|
71
71
|
if (!exportData) {
|
|
72
72
|
process.stdout.write(`File not found: ${args.file}`)
|
|
73
73
|
process.stdout.write(EOL)
|
package/src/cli/cmd/session.ts
CHANGED
|
@@ -61,26 +61,23 @@ export const SessionListCommand = cmd({
|
|
|
61
61
|
},
|
|
62
62
|
handler: async (args) => {
|
|
63
63
|
await bootstrap(process.cwd(), async () => {
|
|
64
|
-
const sessions = []
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
const sessions: Session.Info[] = []
|
|
65
|
+
let count = 0
|
|
66
|
+
for await (const s of Session.list()) {
|
|
67
|
+
sessions.push(s)
|
|
68
|
+
count++
|
|
69
|
+
if (args.maxCount && count >= args.maxCount) break
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
sessions.
|
|
72
|
-
|
|
73
|
-
const limitedSessions = args.maxCount ? sessions.slice(0, args.maxCount) : sessions
|
|
74
|
-
|
|
75
|
-
if (limitedSessions.length === 0) {
|
|
72
|
+
if (sessions.length === 0) {
|
|
76
73
|
return
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
let output: string
|
|
80
77
|
if (args.format === "json") {
|
|
81
|
-
output = formatSessionJSON(
|
|
78
|
+
output = formatSessionJSON(sessions)
|
|
82
79
|
} else {
|
|
83
|
-
output = formatSessionTable(
|
|
80
|
+
output = formatSessionTable(sessions)
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
const shouldPaginate = process.stdout.isTTY && !args.maxCount && args.format === "table"
|
|
@@ -236,7 +236,8 @@ export function Autocomplete(props: {
|
|
|
236
236
|
const width = props.anchor().width - 4
|
|
237
237
|
options.push(
|
|
238
238
|
...sortedFiles.map((item): AutocompleteOption => {
|
|
239
|
-
|
|
239
|
+
const baseDir = (sync.data.path?.directory || process.cwd()).replace(/\/+$/, "")
|
|
240
|
+
let url = `file://${baseDir}/${item}`
|
|
240
241
|
let filename = item
|
|
241
242
|
if (lineRange && !item.endsWith("/")) {
|
|
242
243
|
filename = `${item}#${lineRange.startLine}${lineRange.endLine ? `-${lineRange.endLine}` : ""}`
|
|
@@ -519,12 +519,25 @@ export function Prompt(props: PromptProps) {
|
|
|
519
519
|
promptModelWarning()
|
|
520
520
|
return
|
|
521
521
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
522
|
+
|
|
523
|
+
let sessionID = props.sessionID
|
|
524
|
+
if (sessionID == null) {
|
|
525
|
+
const res = await sdk.client.session.create({})
|
|
526
|
+
|
|
527
|
+
if (res.error) {
|
|
528
|
+
console.log("Creating a session failed:", res.error)
|
|
529
|
+
|
|
530
|
+
toast.show({
|
|
531
|
+
message: "Creating a session failed. Open console for more details.",
|
|
532
|
+
variant: "error",
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
return
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
sessionID = res.data.id
|
|
539
|
+
}
|
|
540
|
+
|
|
528
541
|
const messageID = Identifier.ascending("message")
|
|
529
542
|
let inputText = store.prompt.input
|
|
530
543
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Show } from "solid-js"
|
|
2
|
+
import { useTheme } from "../context/theme"
|
|
3
|
+
import { useKV } from "../context/kv"
|
|
4
|
+
import type { JSX } from "@opentui/solid"
|
|
5
|
+
import type { RGBA } from "@opentui/core"
|
|
6
|
+
import "opentui-spinner/solid"
|
|
7
|
+
|
|
8
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
9
|
+
|
|
10
|
+
export function Spinner(props: { children?: JSX.Element; color?: RGBA }) {
|
|
11
|
+
const { theme } = useTheme()
|
|
12
|
+
const kv = useKV()
|
|
13
|
+
const color = () => props.color ?? theme.textMuted
|
|
14
|
+
return (
|
|
15
|
+
<Show when={kv.get("animations_enabled", true)} fallback={<text fg={color()}>⋯ {props.children}</text>}>
|
|
16
|
+
<box flexDirection="row" gap={1}>
|
|
17
|
+
<spinner frames={frames} interval={80} color={color()} />
|
|
18
|
+
<Show when={props.children}>
|
|
19
|
+
<text fg={color()}>{props.children}</text>
|
|
20
|
+
</Show>
|
|
21
|
+
</box>
|
|
22
|
+
</Show>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -10,13 +10,13 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
|
|
|
10
10
|
// Reset window title before destroying renderer
|
|
11
11
|
renderer.setTerminalTitle("")
|
|
12
12
|
renderer.destroy()
|
|
13
|
-
await input.onExit?.()
|
|
14
13
|
if (reason) {
|
|
15
14
|
const formatted = FormatError(reason) ?? FormatUnknownError(reason)
|
|
16
15
|
if (formatted) {
|
|
17
16
|
process.stderr.write(formatted + "\n")
|
|
18
17
|
}
|
|
19
18
|
}
|
|
19
|
+
await input.onExit?.()
|
|
20
20
|
process.exit(0)
|
|
21
21
|
}
|
|
22
22
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
|
2
|
-
import { createMemo, Match, onMount, Show, Switch } from "solid-js"
|
|
2
|
+
import { createEffect, createMemo, Match, on, onMount, Show, Switch } from "solid-js"
|
|
3
3
|
import { useTheme } from "@tui/context/theme"
|
|
4
4
|
import { useKeybind } from "@tui/context/keybind"
|
|
5
5
|
import { Logo } from "../component/logo"
|
|
@@ -14,6 +14,7 @@ import { usePromptRef } from "../context/prompt"
|
|
|
14
14
|
import { Installation } from "@/installation"
|
|
15
15
|
import { useKV } from "../context/kv"
|
|
16
16
|
import { useCommandDialog } from "../component/dialog-command"
|
|
17
|
+
import { useLocal } from "../context/local"
|
|
17
18
|
|
|
18
19
|
// TODO: what is the best way to do this?
|
|
19
20
|
let once = false
|
|
@@ -76,6 +77,7 @@ export function Home() {
|
|
|
76
77
|
|
|
77
78
|
let prompt: PromptRef
|
|
78
79
|
const args = useArgs()
|
|
80
|
+
const local = useLocal()
|
|
79
81
|
onMount(() => {
|
|
80
82
|
if (once) return
|
|
81
83
|
if (route.initialPrompt) {
|
|
@@ -84,9 +86,21 @@ export function Home() {
|
|
|
84
86
|
} else if (args.prompt) {
|
|
85
87
|
prompt.set({ input: args.prompt, parts: [] })
|
|
86
88
|
once = true
|
|
87
|
-
prompt.submit()
|
|
88
89
|
}
|
|
89
90
|
})
|
|
91
|
+
|
|
92
|
+
// Wait for sync and model store to be ready before auto-submitting --prompt
|
|
93
|
+
createEffect(
|
|
94
|
+
on(
|
|
95
|
+
() => sync.ready && local.model.ready,
|
|
96
|
+
(ready) => {
|
|
97
|
+
if (!ready) return
|
|
98
|
+
if (!args.prompt) return
|
|
99
|
+
if (prompt.current?.input !== args.prompt) return
|
|
100
|
+
prompt.submit()
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
)
|
|
90
104
|
const directory = useDirectory()
|
|
91
105
|
|
|
92
106
|
const keybind = useKeybind()
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
For,
|
|
8
8
|
Match,
|
|
9
9
|
on,
|
|
10
|
+
onMount,
|
|
10
11
|
Show,
|
|
11
12
|
Switch,
|
|
12
13
|
useContext,
|
|
@@ -16,6 +17,7 @@ import path from "path"
|
|
|
16
17
|
import { useRoute, useRouteData } from "@tui/context/route"
|
|
17
18
|
import { useSync } from "@tui/context/sync"
|
|
18
19
|
import { SplitBorder } from "@tui/component/border"
|
|
20
|
+
import { Spinner } from "@tui/component/spinner"
|
|
19
21
|
import { useTheme } from "@tui/context/theme"
|
|
20
22
|
import {
|
|
21
23
|
BoxRenderable,
|
|
@@ -95,6 +97,7 @@ const context = createContext<{
|
|
|
95
97
|
showThinking: () => boolean
|
|
96
98
|
showTimestamps: () => boolean
|
|
97
99
|
showDetails: () => boolean
|
|
100
|
+
showGenericToolOutput: () => boolean
|
|
98
101
|
diffWrapMode: () => "word" | "none"
|
|
99
102
|
sync: ReturnType<typeof useSync>
|
|
100
103
|
}>()
|
|
@@ -148,6 +151,7 @@ export function Session() {
|
|
|
148
151
|
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
|
|
149
152
|
const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
|
|
150
153
|
const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true)
|
|
154
|
+
const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false)
|
|
151
155
|
|
|
152
156
|
const wide = createMemo(() => dimensions().width > 120)
|
|
153
157
|
const sidebarVisible = createMemo(() => {
|
|
@@ -549,6 +553,15 @@ export function Session() {
|
|
|
549
553
|
dialog.clear()
|
|
550
554
|
},
|
|
551
555
|
},
|
|
556
|
+
{
|
|
557
|
+
title: showGenericToolOutput() ? "Hide generic tool output" : "Show generic tool output",
|
|
558
|
+
value: "session.toggle.generic_tool_output",
|
|
559
|
+
category: "Session",
|
|
560
|
+
onSelect: (dialog) => {
|
|
561
|
+
setShowGenericToolOutput((prev) => !prev)
|
|
562
|
+
dialog.clear()
|
|
563
|
+
},
|
|
564
|
+
},
|
|
552
565
|
{
|
|
553
566
|
title: "Toggle session scrollbar",
|
|
554
567
|
value: "session.toggle.scrollbar",
|
|
@@ -943,6 +956,7 @@ export function Session() {
|
|
|
943
956
|
showThinking,
|
|
944
957
|
showTimestamps,
|
|
945
958
|
showDetails,
|
|
959
|
+
showGenericToolOutput,
|
|
946
960
|
diffWrapMode,
|
|
947
961
|
sync,
|
|
948
962
|
}}
|
|
@@ -1230,6 +1244,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
|
|
1230
1244
|
const local = useLocal()
|
|
1231
1245
|
const { theme } = useTheme()
|
|
1232
1246
|
const sync = useSync()
|
|
1247
|
+
const keybind = useKeybind()
|
|
1233
1248
|
const messages = createMemo(() => sync.data.message[props.message.sessionID] ?? [])
|
|
1234
1249
|
|
|
1235
1250
|
const final = createMemo(() => {
|
|
@@ -1261,6 +1276,14 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
|
|
1261
1276
|
)
|
|
1262
1277
|
}}
|
|
1263
1278
|
</For>
|
|
1279
|
+
<Show when={props.parts.some((x) => x.type === "tool" && x.tool === "task")}>
|
|
1280
|
+
<box paddingTop={1} paddingLeft={3}>
|
|
1281
|
+
<text fg={theme.text}>
|
|
1282
|
+
{keybind.print("session_child_cycle")}
|
|
1283
|
+
<span style={{ fg: theme.textMuted }}> view subagents</span>
|
|
1284
|
+
</text>
|
|
1285
|
+
</box>
|
|
1286
|
+
</Show>
|
|
1264
1287
|
<Show when={props.message.error && props.message.error.name !== "MessageAbortedError"}>
|
|
1265
1288
|
<box
|
|
1266
1289
|
border={["left"]}
|
|
@@ -1462,10 +1485,40 @@ type ToolProps<T extends Tool.Info> = {
|
|
|
1462
1485
|
part: ToolPart
|
|
1463
1486
|
}
|
|
1464
1487
|
function GenericTool(props: ToolProps<any>) {
|
|
1488
|
+
const { theme } = useTheme()
|
|
1489
|
+
const ctx = use()
|
|
1490
|
+
const output = createMemo(() => props.output?.trim() ?? "")
|
|
1491
|
+
const [expanded, setExpanded] = createSignal(false)
|
|
1492
|
+
const lines = createMemo(() => output().split("\n"))
|
|
1493
|
+
const maxLines = 3
|
|
1494
|
+
const overflow = createMemo(() => lines().length > maxLines)
|
|
1495
|
+
const limited = createMemo(() => {
|
|
1496
|
+
if (expanded() || !overflow()) return output()
|
|
1497
|
+
return [...lines().slice(0, maxLines), "…"].join("\n")
|
|
1498
|
+
})
|
|
1499
|
+
|
|
1465
1500
|
return (
|
|
1466
|
-
<
|
|
1467
|
-
{props.
|
|
1468
|
-
|
|
1501
|
+
<Show
|
|
1502
|
+
when={props.output && ctx.showGenericToolOutput()}
|
|
1503
|
+
fallback={
|
|
1504
|
+
<InlineTool icon="⚙" pending="Writing command..." complete={true} part={props.part}>
|
|
1505
|
+
{props.tool} {input(props.input)}
|
|
1506
|
+
</InlineTool>
|
|
1507
|
+
}
|
|
1508
|
+
>
|
|
1509
|
+
<BlockTool
|
|
1510
|
+
title={`# ${props.tool} ${input(props.input)}`}
|
|
1511
|
+
part={props.part}
|
|
1512
|
+
onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
|
|
1513
|
+
>
|
|
1514
|
+
<box gap={1}>
|
|
1515
|
+
<text fg={theme.text}>{limited()}</text>
|
|
1516
|
+
<Show when={overflow()}>
|
|
1517
|
+
<text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text>
|
|
1518
|
+
</Show>
|
|
1519
|
+
</box>
|
|
1520
|
+
</BlockTool>
|
|
1521
|
+
</Show>
|
|
1469
1522
|
)
|
|
1470
1523
|
}
|
|
1471
1524
|
|
|
@@ -1485,6 +1538,7 @@ function InlineTool(props: {
|
|
|
1485
1538
|
iconColor?: RGBA
|
|
1486
1539
|
complete: any
|
|
1487
1540
|
pending: string
|
|
1541
|
+
spinner?: boolean
|
|
1488
1542
|
children: JSX.Element
|
|
1489
1543
|
part: ToolPart
|
|
1490
1544
|
}) {
|
|
@@ -1541,11 +1595,18 @@ function InlineTool(props: {
|
|
|
1541
1595
|
}
|
|
1542
1596
|
}}
|
|
1543
1597
|
>
|
|
1544
|
-
<
|
|
1545
|
-
<
|
|
1546
|
-
<
|
|
1547
|
-
</
|
|
1548
|
-
|
|
1598
|
+
<Switch>
|
|
1599
|
+
<Match when={props.spinner}>
|
|
1600
|
+
<Spinner color={fg()} children={props.children} />
|
|
1601
|
+
</Match>
|
|
1602
|
+
<Match when={true}>
|
|
1603
|
+
<text paddingLeft={3} fg={fg()} attributes={denied() ? TextAttributes.STRIKETHROUGH : undefined}>
|
|
1604
|
+
<Show fallback={<>~ {props.pending}</>} when={props.complete}>
|
|
1605
|
+
<span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
|
|
1606
|
+
</Show>
|
|
1607
|
+
</text>
|
|
1608
|
+
</Match>
|
|
1609
|
+
</Switch>
|
|
1549
1610
|
<Show when={error() && !denied()}>
|
|
1550
1611
|
<text fg={theme.error}>{error()}</text>
|
|
1551
1612
|
</Show>
|
|
@@ -1784,55 +1845,63 @@ function WebSearch(props: ToolProps<any>) {
|
|
|
1784
1845
|
|
|
1785
1846
|
function Task(props: ToolProps<typeof TaskTool>) {
|
|
1786
1847
|
const { theme } = useTheme()
|
|
1787
|
-
const keybind = useKeybind()
|
|
1788
1848
|
const { navigate } = useRoute()
|
|
1789
|
-
const
|
|
1849
|
+
const sync = useSync()
|
|
1850
|
+
|
|
1851
|
+
onMount(() => {
|
|
1852
|
+
if (props.metadata.sessionId && !sync.data.message[props.metadata.sessionId]?.length)
|
|
1853
|
+
sync.session.sync(props.metadata.sessionId)
|
|
1854
|
+
})
|
|
1855
|
+
|
|
1856
|
+
const messages = createMemo(() => sync.data.message[props.metadata.sessionId ?? ""] ?? [])
|
|
1857
|
+
|
|
1858
|
+
const tools = createMemo(() => {
|
|
1859
|
+
return messages().flatMap((msg) =>
|
|
1860
|
+
(sync.data.part[msg.id] ?? [])
|
|
1861
|
+
.filter((part): part is ToolPart => part.type === "tool")
|
|
1862
|
+
.map((part) => ({ tool: part.tool, state: part.state })),
|
|
1863
|
+
)
|
|
1864
|
+
})
|
|
1865
|
+
|
|
1866
|
+
const current = createMemo(() => tools().findLast((x) => (x.state as any).title))
|
|
1790
1867
|
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1868
|
+
const isRunning = createMemo(() => props.part.state.status === "running")
|
|
1869
|
+
|
|
1870
|
+
const duration = createMemo(() => {
|
|
1871
|
+
const first = messages().find((x) => x.role === "user")?.time.created
|
|
1872
|
+
const assistant = messages().findLast((x) => x.role === "assistant")?.time.completed
|
|
1873
|
+
if (!first || !assistant) return 0
|
|
1874
|
+
return assistant - first
|
|
1875
|
+
})
|
|
1793
1876
|
|
|
1794
1877
|
return (
|
|
1795
|
-
<
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
</Match>
|
|
1823
|
-
<Match when={true}>
|
|
1824
|
-
<InlineTool
|
|
1825
|
-
icon="◉"
|
|
1826
|
-
iconColor={color()}
|
|
1827
|
-
pending="Delegating..."
|
|
1828
|
-
complete={props.input.subagent_type ?? props.input.description}
|
|
1829
|
-
part={props.part}
|
|
1830
|
-
>
|
|
1831
|
-
<span style={{ fg: theme.text }}>{Locale.titlecase(props.input.subagent_type ?? "unknown")}</span> Task "
|
|
1832
|
-
{props.input.description}"
|
|
1833
|
-
</InlineTool>
|
|
1834
|
-
</Match>
|
|
1835
|
-
</Switch>
|
|
1878
|
+
<InlineTool
|
|
1879
|
+
icon="≡"
|
|
1880
|
+
spinner={isRunning()}
|
|
1881
|
+
complete={props.input.description}
|
|
1882
|
+
pending="Delegating..."
|
|
1883
|
+
part={props.part}
|
|
1884
|
+
>
|
|
1885
|
+
{props.input.description}
|
|
1886
|
+
<Show when={isRunning() && tools().length > 0}>
|
|
1887
|
+
{" "}
|
|
1888
|
+
· {tools().length} toolcalls
|
|
1889
|
+
<Show fallback={"\n⤷ Running..."} when={current()}>
|
|
1890
|
+
{(item) => {
|
|
1891
|
+
const title = createMemo(() => (item().state as any).title)
|
|
1892
|
+
return (
|
|
1893
|
+
<>
|
|
1894
|
+
{"\n"}⤷ {Locale.titlecase(item().tool)} {title()}
|
|
1895
|
+
</>
|
|
1896
|
+
)
|
|
1897
|
+
}}
|
|
1898
|
+
</Show>
|
|
1899
|
+
</Show>
|
|
1900
|
+
<Show when={duration() && props.part.state.status === "completed"}>
|
|
1901
|
+
{"\n "}
|
|
1902
|
+
{tools().length} toolcalls · {Locale.duration(duration())}
|
|
1903
|
+
</Show>
|
|
1904
|
+
</InlineTool>
|
|
1836
1905
|
)
|
|
1837
1906
|
}
|
|
1838
1907
|
|
|
@@ -69,7 +69,15 @@ function EditBody(props: { request: PermissionRequest }) {
|
|
|
69
69
|
<text fg={theme.textMuted}>Edit {normalizePath(filepath())}</text>
|
|
70
70
|
</box>
|
|
71
71
|
<Show when={diff()}>
|
|
72
|
-
<scrollbox
|
|
72
|
+
<scrollbox
|
|
73
|
+
height="100%"
|
|
74
|
+
verticalScrollbarOptions={{
|
|
75
|
+
trackOptions: {
|
|
76
|
+
backgroundColor: theme.background,
|
|
77
|
+
foregroundColor: theme.borderActive,
|
|
78
|
+
},
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
73
81
|
<diff
|
|
74
82
|
diff={diff()}
|
|
75
83
|
view={view()}
|
|
@@ -80,7 +80,15 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|
|
80
80
|
paddingRight={2}
|
|
81
81
|
position={props.overlay ? "absolute" : "relative"}
|
|
82
82
|
>
|
|
83
|
-
<scrollbox
|
|
83
|
+
<scrollbox
|
|
84
|
+
flexGrow={1}
|
|
85
|
+
verticalScrollbarOptions={{
|
|
86
|
+
trackOptions: {
|
|
87
|
+
backgroundColor: theme.background,
|
|
88
|
+
foregroundColor: theme.borderActive,
|
|
89
|
+
},
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
84
92
|
<box flexShrink={0} gap={1} paddingRight={1}>
|
|
85
93
|
<box paddingRight={1}>
|
|
86
94
|
<text fg={theme.text}>
|
|
@@ -3,10 +3,12 @@ import { tui } from "./app"
|
|
|
3
3
|
import { Rpc } from "@/util/rpc"
|
|
4
4
|
import { type rpc } from "./worker"
|
|
5
5
|
import path from "path"
|
|
6
|
+
import { fileURLToPath } from "url"
|
|
6
7
|
import { UI } from "@/cli/ui"
|
|
7
8
|
import { iife } from "@/util/iife"
|
|
8
9
|
import { Log } from "@/util/log"
|
|
9
10
|
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
|
11
|
+
import { Filesystem } from "@/util/filesystem"
|
|
10
12
|
import type { Event } from "@opencode-ai/sdk/v2"
|
|
11
13
|
import type { EventSource } from "./context/sdk"
|
|
12
14
|
|
|
@@ -131,7 +133,7 @@ export const TuiThreadCommand = cmd({
|
|
|
131
133
|
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
|
|
132
134
|
const workerPath = await iife(async () => {
|
|
133
135
|
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
|
|
134
|
-
if (await
|
|
136
|
+
if (await Filesystem.exists(fileURLToPath(distWorker))) return distWorker
|
|
135
137
|
return localWorker
|
|
136
138
|
})
|
|
137
139
|
|
|
@@ -199,5 +201,6 @@ export const TuiThreadCommand = cmd({
|
|
|
199
201
|
onExit,
|
|
200
202
|
onUpgrade,
|
|
201
203
|
})
|
|
204
|
+
process.exit(0)
|
|
202
205
|
},
|
|
203
206
|
})
|