@sleepy-ai/cli 0.1.6 → 0.1.7
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/{src/skill/compose/LICENSE-superpowers → LICENSE} +3 -6
- package/README.md +139 -6
- package/package.json +35 -199
- package/postinstall.mjs +102 -0
- package/.sleepycode-test-fixtures-50158/sleepycode-test-eqsxv84ythu/.watcher-b67v64k2mnv +0 -1
- package/Dockerfile +0 -18
- package/drizzle.config.ts +0 -10
- package/git +0 -0
- package/migration/20260127222353_familiar_lady_ursula/migration.sql +0 -90
- package/migration/20260127222353_familiar_lady_ursula/snapshot.json +0 -796
- package/migration/20260211171708_add_project_commands/migration.sql +0 -1
- package/migration/20260211171708_add_project_commands/snapshot.json +0 -806
- package/migration/20260213144116_wakeful_the_professor/migration.sql +0 -11
- package/migration/20260213144116_wakeful_the_professor/snapshot.json +0 -897
- package/migration/20260225215848_workspace/migration.sql +0 -7
- package/migration/20260225215848_workspace/snapshot.json +0 -959
- package/migration/20260227213759_add_session_workspace_id/migration.sql +0 -2
- package/migration/20260227213759_add_session_workspace_id/snapshot.json +0 -983
- package/migration/20260228203230_blue_harpoon/migration.sql +0 -17
- package/migration/20260228203230_blue_harpoon/snapshot.json +0 -1102
- package/migration/20260303231226_add_workspace_fields/migration.sql +0 -5
- package/migration/20260303231226_add_workspace_fields/snapshot.json +0 -1013
- package/migration/20260309230000_move_org_to_state/migration.sql +0 -3
- package/migration/20260309230000_move_org_to_state/snapshot.json +0 -1156
- package/migration/20260312043431_session_message_cursor/migration.sql +0 -4
- package/migration/20260312043431_session_message_cursor/snapshot.json +0 -1168
- package/migration/20260323234822_events/migration.sql +0 -13
- package/migration/20260323234822_events/snapshot.json +0 -1271
- package/migration/20260410174513_workspace-name/migration.sql +0 -16
- package/migration/20260410174513_workspace-name/snapshot.json +0 -1271
- package/migration/20260413175956_chief_energizer/migration.sql +0 -13
- package/migration/20260413175956_chief_energizer/snapshot.json +0 -1399
- package/migration/20260422160000_context_inheritance/migration.sql +0 -3
- package/migration/20260422170000_task_registry/migration.sql +0 -18
- package/migration/20260423145421_remove_session_entry/migration.sql +0 -4
- package/migration/20260515000000_actor_rename/migration.sql +0 -7
- package/migration/20260515010000_memory_fts/migration.sql +0 -33
- package/migration/20260515020000_user_task/migration.sql +0 -29
- package/migration/20260519000000_last_checkpoint_message_id/migration.sql +0 -1
- package/migration/20260521000000_message_agent_id/migration.sql +0 -2
- package/migration/20260521000100_actor_registry_v6/migration.sql +0 -25
- package/migration/20260521010000_memory_fts_v6/migration.sql +0 -33
- package/migration/20260521020000_memory_fts_triggers/migration.sql +0 -17
- package/migration/20260526000000_agent_id_main/migration.sql +0 -14
- package/migration/20260527000000_actor_lifecycle/migration.sql +0 -8
- package/migration/20260527000100_inbox/migration.sql +0 -12
- package/migration/20260529000000_task_todo_redesign/migration.sql +0 -16
- package/migration/20260603000000_task_in_progress_owner/migration.sql +0 -1
- package/migration/20260603000000_workflow_run/migration.sql +0 -17
- package/migration/20260604000000_workflow_script_sha/migration.sql +0 -1
- package/migration/20260608000000_claude_import/migration.sql +0 -7
- package/migration/20260608010000_claude_import_message_ids/migration.sql +0 -1
- package/migration/20260609000000_history_fts/migration.sql +0 -29
- package/migration/20260609230000_workflow_agent_timeout/migration.sql +0 -1
- package/migration/20260612000000_external_import/migration.sql +0 -16
- package/parsers-config.ts +0 -290
- package/src/account/account.sql.ts +0 -39
- package/src/account/account.ts +0 -456
- package/src/account/repo.ts +0 -166
- package/src/account/schema.ts +0 -99
- package/src/account/url.ts +0 -8
- package/src/acp/README.md +0 -174
- package/src/acp/agent.ts +0 -1787
- package/src/acp/session.ts +0 -116
- package/src/acp/types.ts +0 -24
- package/src/actor/actor.sql.ts +0 -38
- package/src/actor/events.ts +0 -67
- package/src/actor/index.ts +0 -2
- package/src/actor/registry.ts +0 -412
- package/src/actor/return-header.ts +0 -24
- package/src/actor/schema.ts +0 -47
- package/src/actor/spawn-ref.ts +0 -16
- package/src/actor/spawn.ts +0 -756
- package/src/actor/turn.ts +0 -49
- package/src/actor/waiter.ts +0 -166
- package/src/agent/agent.ts +0 -574
- package/src/agent/config.ts +0 -5
- package/src/agent/generate.txt +0 -75
- package/src/agent/prompt/checkpoint-writer.txt +0 -167
- package/src/agent/prompt/compaction.txt +0 -9
- package/src/agent/prompt/distill.txt +0 -199
- package/src/agent/prompt/dream.txt +0 -155
- package/src/agent/prompt/explore.txt +0 -18
- package/src/agent/prompt/summary.txt +0 -11
- package/src/agent/prompt/title.txt +0 -44
- package/src/audio.d.ts +0 -9
- package/src/auth/index.ts +0 -97
- package/src/bus/bus-event.ts +0 -33
- package/src/bus/global.ts +0 -12
- package/src/bus/index.ts +0 -193
- package/src/cli/bootstrap.ts +0 -33
- package/src/cli/cmd/account.ts +0 -258
- package/src/cli/cmd/acp.ts +0 -70
- package/src/cli/cmd/agent.ts +0 -248
- package/src/cli/cmd/cmd.ts +0 -7
- package/src/cli/cmd/db.ts +0 -120
- package/src/cli/cmd/debug/agent.ts +0 -192
- package/src/cli/cmd/debug/config.ts +0 -17
- package/src/cli/cmd/debug/file.ts +0 -100
- package/src/cli/cmd/debug/index.ts +0 -48
- package/src/cli/cmd/debug/lsp.ts +0 -61
- package/src/cli/cmd/debug/ripgrep.ts +0 -105
- package/src/cli/cmd/debug/scrap.ts +0 -16
- package/src/cli/cmd/debug/skill.ts +0 -23
- package/src/cli/cmd/debug/snapshot.ts +0 -53
- package/src/cli/cmd/doctor.ts +0 -116
- package/src/cli/cmd/export.ts +0 -306
- package/src/cli/cmd/generate.ts +0 -50
- package/src/cli/cmd/github.ts +0 -1647
- package/src/cli/cmd/import.ts +0 -208
- package/src/cli/cmd/init.ts +0 -97
- package/src/cli/cmd/login.ts +0 -402
- package/src/cli/cmd/mcp.ts +0 -812
- package/src/cli/cmd/models.ts +0 -88
- package/src/cli/cmd/plug.ts +0 -233
- package/src/cli/cmd/pr.ts +0 -138
- package/src/cli/cmd/providers.ts +0 -713
- package/src/cli/cmd/run-completion.ts +0 -77
- package/src/cli/cmd/run.ts +0 -694
- package/src/cli/cmd/serve.ts +0 -30
- package/src/cli/cmd/session.ts +0 -181
- package/src/cli/cmd/stats.ts +0 -413
- package/src/cli/cmd/tui/app.tsx +0 -1181
- package/src/cli/cmd/tui/asset/TEN_VAD_LICENSE +0 -12
- package/src/cli/cmd/tui/asset/charge.wav +0 -0
- package/src/cli/cmd/tui/asset/pulse-a.wav +0 -0
- package/src/cli/cmd/tui/asset/pulse-b.wav +0 -0
- package/src/cli/cmd/tui/asset/pulse-c.wav +0 -0
- package/src/cli/cmd/tui/asset/ten_vad.wasm +0 -0
- package/src/cli/cmd/tui/asset/ten_vad_loader.js +0 -30
- package/src/cli/cmd/tui/attach.ts +0 -84
- package/src/cli/cmd/tui/component/background-image.tsx +0 -150
- package/src/cli/cmd/tui/component/banner-session-expired.tsx +0 -48
- package/src/cli/cmd/tui/component/bg-pulse.tsx +0 -130
- package/src/cli/cmd/tui/component/border.tsx +0 -21
- package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
- package/src/cli/cmd/tui/component/dialog-agreement.tsx +0 -111
- package/src/cli/cmd/tui/component/dialog-command.tsx +0 -219
- package/src/cli/cmd/tui/component/dialog-console-org.tsx +0 -103
- package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +0 -157
- package/src/cli/cmd/tui/component/dialog-image-list.tsx +0 -111
- package/src/cli/cmd/tui/component/dialog-logo-design.tsx +0 -37
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
- package/src/cli/cmd/tui/component/dialog-model.tsx +0 -299
- package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -449
- package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +0 -101
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -269
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
- package/src/cli/cmd/tui/component/dialog-skill.tsx +0 -42
- package/src/cli/cmd/tui/component/dialog-sleepy-login.tsx +0 -288
- package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
- package/src/cli/cmd/tui/component/dialog-status.tsx +0 -170
- package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
- package/src/cli/cmd/tui/component/dialog-token-plan.tsx +0 -77
- package/src/cli/cmd/tui/component/dialog-variant.tsx +0 -39
- package/src/cli/cmd/tui/component/dialog-workflows.tsx +0 -51
- package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +0 -289
- package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +0 -81
- package/src/cli/cmd/tui/component/dialog-worktree.tsx +0 -90
- package/src/cli/cmd/tui/component/error-component.tsx +0 -92
- package/src/cli/cmd/tui/component/logo.tsx +0 -961
- package/src/cli/cmd/tui/component/plugin-route-missing.tsx +0 -14
- package/src/cli/cmd/tui/component/prompt/autocomplete-detect.ts +0 -34
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -668
- package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -90
- package/src/cli/cmd/tui/component/prompt/history.tsx +0 -108
- package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1869
- package/src/cli/cmd/tui/component/prompt/offset.ts +0 -45
- package/src/cli/cmd/tui/component/prompt/part.ts +0 -36
- package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
- package/src/cli/cmd/tui/component/spinner.tsx +0 -24
- package/src/cli/cmd/tui/component/starry-background.tsx +0 -305
- package/src/cli/cmd/tui/component/startup-loading.tsx +0 -67
- package/src/cli/cmd/tui/component/task-item.tsx +0 -63
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
- package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
- package/src/cli/cmd/tui/component/workflow-tree.tsx +0 -177
- package/src/cli/cmd/tui/config/cwd.ts +0 -5
- package/src/cli/cmd/tui/config/tui-migrate.ts +0 -151
- package/src/cli/cmd/tui/config/tui-schema.ts +0 -38
- package/src/cli/cmd/tui/config/tui.ts +0 -219
- package/src/cli/cmd/tui/context/args.tsx +0 -16
- package/src/cli/cmd/tui/context/directory.ts +0 -15
- package/src/cli/cmd/tui/context/event.ts +0 -45
- package/src/cli/cmd/tui/context/exit.tsx +0 -65
- package/src/cli/cmd/tui/context/helper.tsx +0 -25
- package/src/cli/cmd/tui/context/keybind.tsx +0 -105
- package/src/cli/cmd/tui/context/kv.tsx +0 -86
- package/src/cli/cmd/tui/context/language.tsx +0 -91
- package/src/cli/cmd/tui/context/local.tsx +0 -469
- package/src/cli/cmd/tui/context/plugin-keybinds.ts +0 -41
- package/src/cli/cmd/tui/context/project.tsx +0 -109
- package/src/cli/cmd/tui/context/prompt.tsx +0 -18
- package/src/cli/cmd/tui/context/route.tsx +0 -68
- package/src/cli/cmd/tui/context/sdk.tsx +0 -150
- package/src/cli/cmd/tui/context/sync.tsx +0 -884
- package/src/cli/cmd/tui/context/theme/aura.json +0 -69
- package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
- package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -230
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -230
- package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
- package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -225
- package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
- package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
- package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
- package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
- package/src/cli/cmd/tui/context/theme/github.json +0 -233
- package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -242
- package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -234
- package/src/cli/cmd/tui/context/theme/material.json +0 -235
- package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
- package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
- package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
- package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
- package/src/cli/cmd/tui/context/theme/nord.json +0 -223
- package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
- package/src/cli/cmd/tui/context/theme/orng.json +0 -249
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
- package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
- package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
- package/src/cli/cmd/tui/context/theme/sleepycode.json +0 -245
- package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
- package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
- package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
- package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
- package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
- package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
- package/src/cli/cmd/tui/context/theme.tsx +0 -1298
- package/src/cli/cmd/tui/context/thinking.ts +0 -48
- package/src/cli/cmd/tui/context/tui-config.tsx +0 -9
- package/src/cli/cmd/tui/event.ts +0 -62
- package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +0 -93
- package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +0 -193
- package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +0 -54
- package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +0 -267
- package/src/cli/cmd/tui/feature-plugins/sidebar/cwd.tsx +0 -45
- package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +0 -62
- package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +0 -93
- package/src/cli/cmd/tui/feature-plugins/sidebar/goal.tsx +0 -84
- package/src/cli/cmd/tui/feature-plugins/sidebar/instructions.tsx +0 -54
- package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +0 -66
- package/src/cli/cmd/tui/feature-plugins/sidebar/mascot.tsx +0 -69
- package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +0 -98
- package/src/cli/cmd/tui/feature-plugins/sidebar/task.tsx +0 -95
- package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +0 -51
- package/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts +0 -31
- package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +0 -274
- package/src/cli/cmd/tui/i18n/en.ts +0 -465
- package/src/cli/cmd/tui/i18n/es.ts +0 -508
- package/src/cli/cmd/tui/i18n/fr.ts +0 -515
- package/src/cli/cmd/tui/i18n/ja.ts +0 -463
- package/src/cli/cmd/tui/i18n/locales.ts +0 -82
- package/src/cli/cmd/tui/i18n/ru.ts +0 -527
- package/src/cli/cmd/tui/i18n/slash-command.ts +0 -11
- package/src/cli/cmd/tui/i18n/zh.ts +0 -454
- package/src/cli/cmd/tui/i18n/zht.ts +0 -430
- package/src/cli/cmd/tui/layer.ts +0 -6
- package/src/cli/cmd/tui/plugin/api.tsx +0 -402
- package/src/cli/cmd/tui/plugin/index.ts +0 -3
- package/src/cli/cmd/tui/plugin/internal.ts +0 -37
- package/src/cli/cmd/tui/plugin/runtime.ts +0 -1057
- package/src/cli/cmd/tui/plugin/slots.tsx +0 -60
- package/src/cli/cmd/tui/routes/home.tsx +0 -165
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -76
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -116
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -47
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
- package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
- package/src/cli/cmd/tui/routes/session/index.tsx +0 -3073
- package/src/cli/cmd/tui/routes/session/permission.tsx +0 -697
- package/src/cli/cmd/tui/routes/session/question.tsx +0 -488
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -97
- package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +0 -143
- package/src/cli/cmd/tui/thread.ts +0 -324
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -61
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -95
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -223
- package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -42
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -123
- package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -467
- package/src/cli/cmd/tui/ui/dialog.tsx +0 -207
- package/src/cli/cmd/tui/ui/link.tsx +0 -28
- package/src/cli/cmd/tui/ui/spinner.ts +0 -378
- package/src/cli/cmd/tui/ui/toast.tsx +0 -102
- package/src/cli/cmd/tui/util/clipboard.ts +0 -203
- package/src/cli/cmd/tui/util/editor.ts +0 -36
- package/src/cli/cmd/tui/util/image-protocol.ts +0 -35
- package/src/cli/cmd/tui/util/index.ts +0 -6
- package/src/cli/cmd/tui/util/model.ts +0 -23
- package/src/cli/cmd/tui/util/pinyin.ts +0 -20
- package/src/cli/cmd/tui/util/provider-origin.ts +0 -7
- package/src/cli/cmd/tui/util/revert-diff.ts +0 -18
- package/src/cli/cmd/tui/util/scroll.ts +0 -23
- package/src/cli/cmd/tui/util/selection.ts +0 -23
- package/src/cli/cmd/tui/util/signal.ts +0 -41
- package/src/cli/cmd/tui/util/sound.ts +0 -154
- package/src/cli/cmd/tui/util/system-locale.ts +0 -209
- package/src/cli/cmd/tui/util/terminal.ts +0 -110
- package/src/cli/cmd/tui/util/transcript.ts +0 -112
- package/src/cli/cmd/tui/util/vad.ts +0 -229
- package/src/cli/cmd/tui/util/voice.ts +0 -450
- package/src/cli/cmd/tui/win32.ts +0 -130
- package/src/cli/cmd/tui/worker.ts +0 -104
- package/src/cli/cmd/uninstall.ts +0 -351
- package/src/cli/cmd/upgrade.ts +0 -79
- package/src/cli/cmd/web.ts +0 -96
- package/src/cli/effect/prompt.ts +0 -25
- package/src/cli/error.ts +0 -82
- package/src/cli/heap.ts +0 -59
- package/src/cli/i18n.ts +0 -15
- package/src/cli/logo.ts +0 -53
- package/src/cli/network.ts +0 -68
- package/src/cli/ui.ts +0 -133
- package/src/cli/upgrade.ts +0 -41
- package/src/command/index.ts +0 -276
- package/src/command/template/initialize.txt +0 -66
- package/src/command/template/review.txt +0 -101
- package/src/config/agent.ts +0 -197
- package/src/config/command.ts +0 -69
- package/src/config/compose.ts +0 -26
- package/src/config/config.ts +0 -1047
- package/src/config/console-state.ts +0 -16
- package/src/config/entry-name.ts +0 -16
- package/src/config/error.ts +0 -21
- package/src/config/formatter.ts +0 -17
- package/src/config/history.ts +0 -21
- package/src/config/index.ts +0 -17
- package/src/config/keybinds.ts +0 -127
- package/src/config/layout.ts +0 -10
- package/src/config/lsp.ts +0 -45
- package/src/config/managed.ts +0 -70
- package/src/config/markdown.ts +0 -97
- package/src/config/mcp.ts +0 -172
- package/src/config/model-id.ts +0 -14
- package/src/config/parse.ts +0 -44
- package/src/config/paths.ts +0 -85
- package/src/config/permission.ts +0 -76
- package/src/config/plugin.ts +0 -88
- package/src/config/provider.ts +0 -118
- package/src/config/server.ts +0 -20
- package/src/config/skills.ts +0 -16
- package/src/config/variable.ts +0 -90
- package/src/control-plane/adaptors/index.ts +0 -52
- package/src/control-plane/adaptors/worktree.ts +0 -47
- package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
- package/src/control-plane/schema.ts +0 -19
- package/src/control-plane/sse.ts +0 -66
- package/src/control-plane/types.ts +0 -34
- package/src/control-plane/util.ts +0 -37
- package/src/control-plane/workspace-context.ts +0 -26
- package/src/control-plane/workspace.sql.ts +0 -17
- package/src/control-plane/workspace.ts +0 -615
- package/src/effect/app-runtime.ts +0 -146
- package/src/effect/bootstrap-runtime.ts +0 -33
- package/src/effect/bridge.ts +0 -48
- package/src/effect/cross-spawn-spawner.ts +0 -514
- package/src/effect/index.ts +0 -5
- package/src/effect/instance-ref.ts +0 -11
- package/src/effect/instance-registry.ts +0 -12
- package/src/effect/instance-state.ts +0 -81
- package/src/effect/logger.ts +0 -73
- package/src/effect/memo-map.ts +0 -3
- package/src/effect/observability.ts +0 -107
- package/src/effect/run-service.ts +0 -52
- package/src/effect/runner.ts +0 -210
- package/src/effect/runtime.ts +0 -19
- package/src/env/index.ts +0 -37
- package/src/file/ignore.ts +0 -81
- package/src/file/index.ts +0 -664
- package/src/file/protected.ts +0 -59
- package/src/file/ripgrep.ts +0 -485
- package/src/file/watcher.ts +0 -163
- package/src/flag/flag.ts +0 -185
- package/src/format/formatter.ts +0 -403
- package/src/format/index.ts +0 -203
- package/src/git/index.ts +0 -260
- package/src/global/index.ts +0 -54
- package/src/history/backfill.ts +0 -162
- package/src/history/extract.ts +0 -67
- package/src/history/fts-query.ts +0 -15
- package/src/history/fts.sql.ts +0 -20
- package/src/history/index.ts +0 -10
- package/src/history/resolve.ts +0 -65
- package/src/history/service.ts +0 -258
- package/src/history/writer.ts +0 -112
- package/src/id/id.ts +0 -87
- package/src/ide/index.ts +0 -73
- package/src/inbox/inbox-ref.ts +0 -38
- package/src/inbox/inbox.sql.ts +0 -26
- package/src/inbox/inbox.ts +0 -223
- package/src/inbox/index.ts +0 -3
- package/src/inbox/render.ts +0 -40
- package/src/index.ts +0 -268
- package/src/installation/index.ts +0 -362
- package/src/installation/version.ts +0 -8
- package/src/lsp/client.ts +0 -249
- package/src/lsp/diagnostic.ts +0 -29
- package/src/lsp/index.ts +0 -3
- package/src/lsp/language.ts +0 -120
- package/src/lsp/launch.ts +0 -21
- package/src/lsp/lsp.ts +0 -519
- package/src/lsp/server.ts +0 -1956
- package/src/mcp/auth.ts +0 -144
- package/src/mcp/index.ts +0 -939
- package/src/mcp/oauth-callback.ts +0 -236
- package/src/mcp/oauth-provider.ts +0 -214
- package/src/memory/fts-query.ts +0 -37
- package/src/memory/fts.sql.ts +0 -19
- package/src/memory/index.ts +0 -1
- package/src/memory/paths.ts +0 -116
- package/src/memory/reconcile.ts +0 -144
- package/src/memory/service.ts +0 -144
- package/src/metrics/client.ts +0 -40
- package/src/metrics/event.ts +0 -43
- package/src/metrics/index.ts +0 -5
- package/src/metrics/installation.ts +0 -18
- package/src/metrics/subscriber.ts +0 -58
- package/src/metrics/util.ts +0 -9
- package/src/node.ts +0 -6
- package/src/npm/config.ts +0 -0
- package/src/npm/index.ts +0 -293
- package/src/npmcli-config.d.ts +0 -43
- package/src/patch/index.ts +0 -680
- package/src/permission/arity.ts +0 -163
- package/src/permission/evaluate.ts +0 -15
- package/src/permission/index.ts +0 -382
- package/src/permission/schema.ts +0 -17
- package/src/plugin/checkpoint-splitover.ts +0 -60
- package/src/plugin/cloudflare.ts +0 -76
- package/src/plugin/codex.ts +0 -611
- package/src/plugin/github-copilot/copilot.ts +0 -368
- package/src/plugin/github-copilot/models.ts +0 -153
- package/src/plugin/index.ts +0 -637
- package/src/plugin/install.ts +0 -439
- package/src/plugin/loader.ts +0 -216
- package/src/plugin/matcher.ts +0 -33
- package/src/plugin/meta.ts +0 -188
- package/src/plugin/shared.ts +0 -323
- package/src/plugin/sleepy.ts +0 -244
- package/src/plugin/subagent-progress-checker.ts +0 -147
- package/src/project/bootstrap.ts +0 -59
- package/src/project/index.ts +0 -2
- package/src/project/instance.ts +0 -215
- package/src/project/project-id.ts +0 -48
- package/src/project/project.sql.ts +0 -16
- package/src/project/project.ts +0 -522
- package/src/project/schema.ts +0 -15
- package/src/project/vcs.ts +0 -227
- package/src/project/workspace-trust.ts +0 -67
- package/src/provider/auth.ts +0 -234
- package/src/provider/error.ts +0 -216
- package/src/provider/index.ts +0 -5
- package/src/provider/models-snapshot.d.ts +0 -2
- package/src/provider/models-snapshot.js +0 -3
- package/src/provider/models.ts +0 -180
- package/src/provider/provider.ts +0 -2098
- package/src/provider/schema.ts +0 -36
- package/src/provider/sdk/copilot/README.md +0 -5
- package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
- package/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
- package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
- package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -815
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
- package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
- package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
- package/src/provider/sdk/copilot/copilot-provider.ts +0 -100
- package/src/provider/sdk/copilot/index.ts +0 -2
- package/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
- package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
- package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
- package/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
- package/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
- package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
- package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1770
- package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
- package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
- package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
- package/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
- package/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
- package/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
- package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
- package/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
- package/src/provider/session-check.ts +0 -163
- package/src/provider/transform.ts +0 -1376
- package/src/pty/index.ts +0 -364
- package/src/pty/pty.bun.ts +0 -26
- package/src/pty/pty.node.ts +0 -27
- package/src/pty/pty.ts +0 -25
- package/src/pty/schema.ts +0 -17
- package/src/question/index.ts +0 -252
- package/src/question/schema.ts +0 -17
- package/src/server/adapter.bun.ts +0 -40
- package/src/server/adapter.node.ts +0 -66
- package/src/server/adapter.ts +0 -21
- package/src/server/auth.ts +0 -16
- package/src/server/error.ts +0 -53
- package/src/server/event.ts +0 -7
- package/src/server/fence.ts +0 -81
- package/src/server/mdns.ts +0 -60
- package/src/server/middleware.ts +0 -94
- package/src/server/projectors.ts +0 -28
- package/src/server/proxy.ts +0 -171
- package/src/server/pty-ticket.ts +0 -42
- package/src/server/rate-limit.ts +0 -38
- package/src/server/routes/control/index.ts +0 -160
- package/src/server/routes/control/workspace.ts +0 -203
- package/src/server/routes/global.ts +0 -367
- package/src/server/routes/instance/bash-interactive.ts +0 -82
- package/src/server/routes/instance/config.ts +0 -89
- package/src/server/routes/instance/event.ts +0 -108
- package/src/server/routes/instance/experimental.ts +0 -408
- package/src/server/routes/instance/file.ts +0 -190
- package/src/server/routes/instance/httpapi/config.ts +0 -51
- package/src/server/routes/instance/httpapi/permission.ts +0 -72
- package/src/server/routes/instance/httpapi/project.ts +0 -62
- package/src/server/routes/instance/httpapi/provider.ts +0 -142
- package/src/server/routes/instance/httpapi/question.ts +0 -121
- package/src/server/routes/instance/httpapi/server.ts +0 -153
- package/src/server/routes/instance/index.ts +0 -301
- package/src/server/routes/instance/mcp.ts +0 -260
- package/src/server/routes/instance/middleware.ts +0 -44
- package/src/server/routes/instance/permission.ts +0 -73
- package/src/server/routes/instance/project.ts +0 -122
- package/src/server/routes/instance/provider.ts +0 -158
- package/src/server/routes/instance/pty.ts +0 -302
- package/src/server/routes/instance/question.ts +0 -162
- package/src/server/routes/instance/session.ts +0 -1328
- package/src/server/routes/instance/sync.ts +0 -143
- package/src/server/routes/instance/trace.ts +0 -59
- package/src/server/routes/instance/tui.ts +0 -384
- package/src/server/routes/instance/workflows.ts +0 -142
- package/src/server/routes/ui.ts +0 -37
- package/src/server/server.ts +0 -146
- package/src/server/workspace.ts +0 -122
- package/src/session/auto-dream.ts +0 -123
- package/src/session/boundary.ts +0 -77
- package/src/session/budgeted-read.ts +0 -118
- package/src/session/checkpoint-align.ts +0 -29
- package/src/session/checkpoint-context.ts +0 -36
- package/src/session/checkpoint-paths.ts +0 -86
- package/src/session/checkpoint-progress-reconcile.ts +0 -111
- package/src/session/checkpoint-retry.ts +0 -192
- package/src/session/checkpoint-templates.ts +0 -114
- package/src/session/checkpoint-validator.ts +0 -259
- package/src/session/checkpoint.ts +0 -1560
- package/src/session/classify.ts +0 -117
- package/src/session/claude-import.ts +0 -381
- package/src/session/codex-import.ts +0 -416
- package/src/session/compaction.ts +0 -545
- package/src/session/external-import.sql.ts +0 -18
- package/src/session/external-import.ts +0 -136
- package/src/session/goal.ts +0 -232
- package/src/session/index.ts +0 -1
- package/src/session/instruction.ts +0 -276
- package/src/session/last-message-info.ts +0 -32
- package/src/session/llm-request-prefix.ts +0 -82
- package/src/session/llm.ts +0 -737
- package/src/session/max-mode.ts +0 -410
- package/src/session/message-v2.ts +0 -1138
- package/src/session/message.ts +0 -191
- package/src/session/opencode-import.ts +0 -281
- package/src/session/overflow.ts +0 -53
- package/src/session/prefix-capture-ref.ts +0 -48
- package/src/session/processor.ts +0 -983
- package/src/session/projectors.ts +0 -137
- package/src/session/prompt/anthropic.txt +0 -154
- package/src/session/prompt/beast.txt +0 -155
- package/src/session/prompt/build-switch.txt +0 -5
- package/src/session/prompt/codex.txt +0 -79
- package/src/session/prompt/compose.txt +0 -119
- package/src/session/prompt/copilot-gpt-5.txt +0 -143
- package/src/session/prompt/deepseek.txt +0 -131
- package/src/session/prompt/default.old.txt +0 -151
- package/src/session/prompt/default.txt +0 -172
- package/src/session/prompt/gemini.txt +0 -155
- package/src/session/prompt/glm.txt +0 -51
- package/src/session/prompt/gpt.txt +0 -107
- package/src/session/prompt/kimi.txt +0 -95
- package/src/session/prompt/max-steps.txt +0 -16
- package/src/session/prompt/minimax.txt +0 -140
- package/src/session/prompt/text-loop-recovery.ts +0 -40
- package/src/session/prompt/text-ngram-detection.ts +0 -87
- package/src/session/prompt/trinity.txt +0 -97
- package/src/session/prompt.ts +0 -3878
- package/src/session/prune.ts +0 -481
- package/src/session/retry.ts +0 -178
- package/src/session/revert.ts +0 -161
- package/src/session/run-state.ts +0 -135
- package/src/session/schema.ts +0 -36
- package/src/session/session.sql.ts +0 -110
- package/src/session/session.ts +0 -908
- package/src/session/status.ts +0 -89
- package/src/session/summary.ts +0 -163
- package/src/session/system.ts +0 -96
- package/src/session/todo.ts +0 -77
- package/src/session/trajectory.ts +0 -98
- package/src/share/index.ts +0 -2
- package/src/share/session.ts +0 -57
- package/src/share/share-next.ts +0 -381
- package/src/share/share.sql.ts +0 -13
- package/src/shell/shell.ts +0 -124
- package/src/skill/builtin/.bundle/self-extend/SKILL.md +0 -131
- package/src/skill/builtin/.bundle/self-extend/reference/hook-api.md +0 -242
- package/src/skill/builtin/.bundle/self-extend/reference/skill-api.md +0 -114
- package/src/skill/builtin/.bundle/self-extend/reference/tool-api.md +0 -115
- package/src/skill/builtin/.bundle/self-extend/reference/tui-api.md +0 -258
- package/src/skill/builtin/bundle.macro.ts +0 -30
- package/src/skill/builtin/extract.ts +0 -41
- package/src/skill/compose/.bundle/ask/SKILL.md +0 -58
- package/src/skill/compose/.bundle/brainstorm/SKILL.md +0 -200
- package/src/skill/compose/.bundle/brainstorm/scripts/frame-template.html +0 -214
- package/src/skill/compose/.bundle/brainstorm/scripts/helper.js +0 -88
- package/src/skill/compose/.bundle/brainstorm/scripts/server.cjs +0 -354
- package/src/skill/compose/.bundle/brainstorm/scripts/start-server.sh +0 -148
- package/src/skill/compose/.bundle/brainstorm/scripts/stop-server.sh +0 -56
- package/src/skill/compose/.bundle/brainstorm/spec-document-reviewer-prompt.md +0 -50
- package/src/skill/compose/.bundle/brainstorm/visual-companion.md +0 -258
- package/src/skill/compose/.bundle/debug/CREATION-LOG.md +0 -119
- package/src/skill/compose/.bundle/debug/SKILL.md +0 -297
- package/src/skill/compose/.bundle/debug/condition-based-waiting-example.ts +0 -158
- package/src/skill/compose/.bundle/debug/condition-based-waiting.md +0 -106
- package/src/skill/compose/.bundle/debug/defense-in-depth.md +0 -122
- package/src/skill/compose/.bundle/debug/find-polluter.sh +0 -63
- package/src/skill/compose/.bundle/debug/root-cause-tracing.md +0 -144
- package/src/skill/compose/.bundle/debug/test-academic.md +0 -14
- package/src/skill/compose/.bundle/debug/test-pressure-1.md +0 -58
- package/src/skill/compose/.bundle/debug/test-pressure-2.md +0 -68
- package/src/skill/compose/.bundle/debug/test-pressure-3.md +0 -69
- package/src/skill/compose/.bundle/execute/SKILL.md +0 -71
- package/src/skill/compose/.bundle/feedback/SKILL.md +0 -214
- package/src/skill/compose/.bundle/merge/SKILL.md +0 -252
- package/src/skill/compose/.bundle/new-skill/SKILL.md +0 -211
- package/src/skill/compose/.bundle/parallel/SKILL.md +0 -168
- package/src/skill/compose/.bundle/plan/SKILL.md +0 -183
- package/src/skill/compose/.bundle/plan/plan-document-reviewer-prompt.md +0 -50
- package/src/skill/compose/.bundle/report/SKILL.md +0 -180
- package/src/skill/compose/.bundle/review/SKILL.md +0 -104
- package/src/skill/compose/.bundle/review/code-reviewer.md +0 -178
- package/src/skill/compose/.bundle/subagent/SKILL.md +0 -325
- package/src/skill/compose/.bundle/subagent/code-quality-reviewer-prompt.md +0 -26
- package/src/skill/compose/.bundle/subagent/implementer-prompt.md +0 -128
- package/src/skill/compose/.bundle/subagent/spec-reviewer-prompt.md +0 -118
- package/src/skill/compose/.bundle/tdd/SKILL.md +0 -360
- package/src/skill/compose/.bundle/tdd/testing-anti-patterns.md +0 -299
- package/src/skill/compose/.bundle/verify/SKILL.md +0 -140
- package/src/skill/compose/.bundle/worktree/SKILL.md +0 -234
- package/src/skill/compose/LICENSE-karpathy +0 -28
- package/src/skill/compose/bundle.macro.ts +0 -30
- package/src/skill/compose/extract.ts +0 -85
- package/src/skill/discovery.ts +0 -114
- package/src/skill/index.ts +0 -328
- package/src/snapshot/index.ts +0 -777
- package/src/sql.d.ts +0 -4
- package/src/storage/db.bun.ts +0 -8
- package/src/storage/db.node.ts +0 -8
- package/src/storage/db.ts +0 -172
- package/src/storage/index.ts +0 -26
- package/src/storage/json-migration.ts +0 -426
- package/src/storage/read-sqlite.bun.ts +0 -11
- package/src/storage/read-sqlite.node.ts +0 -13
- package/src/storage/read-sqlite.ts +0 -10
- package/src/storage/schema.sql.ts +0 -10
- package/src/storage/schema.ts +0 -7
- package/src/storage/storage.ts +0 -331
- package/src/sync/README.md +0 -179
- package/src/sync/event.sql.ts +0 -16
- package/src/sync/index.ts +0 -278
- package/src/sync/schema.ts +0 -14
- package/src/task/events.ts +0 -28
- package/src/task/gate-state.ts +0 -54
- package/src/task/gate.ts +0 -116
- package/src/task/index.ts +0 -1
- package/src/task/registry.ts +0 -394
- package/src/task/schema.ts +0 -43
- package/src/task/task.sql.ts +0 -50
- package/src/team/events.ts +0 -22
- package/src/team/index.ts +0 -113
- package/src/team/schema.ts +0 -31
- package/src/temporary.ts +0 -33
- package/src/tool/actor.shell.txt +0 -72
- package/src/tool/actor.ts +0 -804
- package/src/tool/actor.txt +0 -103
- package/src/tool/apply_patch.ts +0 -308
- package/src/tool/apply_patch.txt +0 -33
- package/src/tool/bash-interactive.ts +0 -183
- package/src/tool/bash.ts +0 -704
- package/src/tool/bash.txt +0 -123
- package/src/tool/change-directory.ts +0 -91
- package/src/tool/codesearch.ts +0 -63
- package/src/tool/codesearch.txt +0 -12
- package/src/tool/edit.ts +0 -693
- package/src/tool/edit.txt +0 -10
- package/src/tool/external-directory.ts +0 -132
- package/src/tool/glob.ts +0 -100
- package/src/tool/glob.txt +0 -6
- package/src/tool/grep.ts +0 -145
- package/src/tool/grep.txt +0 -8
- package/src/tool/history.ts +0 -146
- package/src/tool/history.txt +0 -17
- package/src/tool/index.ts +0 -4
- package/src/tool/invalid.ts +0 -20
- package/src/tool/invocation-style.ts +0 -17
- package/src/tool/lsp.ts +0 -91
- package/src/tool/lsp.txt +0 -19
- package/src/tool/mcp-exa.ts +0 -78
- package/src/tool/memory-path-guard.ts +0 -162
- package/src/tool/memory.ts +0 -81
- package/src/tool/memory.txt +0 -69
- package/src/tool/multiedit.ts +0 -54
- package/src/tool/multiedit.txt +0 -41
- package/src/tool/notebook-edit.ts +0 -225
- package/src/tool/notebook-edit.txt +0 -10
- package/src/tool/plan-enter.txt +0 -16
- package/src/tool/plan-exit.txt +0 -14
- package/src/tool/plan.ts +0 -179
- package/src/tool/question.ts +0 -67
- package/src/tool/question.txt +0 -10
- package/src/tool/read-state.ts +0 -44
- package/src/tool/read.ts +0 -327
- package/src/tool/read.txt +0 -14
- package/src/tool/recoverable.ts +0 -35
- package/src/tool/registry.ts +0 -429
- package/src/tool/schema.ts +0 -17
- package/src/tool/session-cwd.ts +0 -35
- package/src/tool/shell-tokenize.ts +0 -374
- package/src/tool/shell-wrap.ts +0 -235
- package/src/tool/skill.ts +0 -76
- package/src/tool/skill.txt +0 -5
- package/src/tool/task.shell.txt +0 -57
- package/src/tool/task.ts +0 -456
- package/src/tool/task.txt +0 -56
- package/src/tool/tool.ts +0 -166
- package/src/tool/truncate.ts +0 -201
- package/src/tool/truncation-dir.ts +0 -4
- package/src/tool/webfetch.ts +0 -208
- package/src/tool/webfetch.txt +0 -13
- package/src/tool/websearch/index.ts +0 -104
- package/src/tool/websearch/sleepy.ts +0 -118
- package/src/tool/websearch/websearch.txt +0 -14
- package/src/tool/workflow.ts +0 -357
- package/src/tool/workflow.txt +0 -25
- package/src/tool/write.ts +0 -88
- package/src/tool/write.txt +0 -10
- package/src/util/abort.ts +0 -35
- package/src/util/archive.ts +0 -15
- package/src/util/color.ts +0 -17
- package/src/util/data-url.ts +0 -9
- package/src/util/defer.ts +0 -10
- package/src/util/effect-http-client.ts +0 -11
- package/src/util/effect-zod.ts +0 -367
- package/src/util/env-info.ts +0 -62
- package/src/util/error.ts +0 -78
- package/src/util/filesystem.ts +0 -243
- package/src/util/fn.ts +0 -21
- package/src/util/format.ts +0 -20
- package/src/util/iife.ts +0 -3
- package/src/util/index.ts +0 -14
- package/src/util/keybind.ts +0 -101
- package/src/util/lazy.ts +0 -18
- package/src/util/local-context.ts +0 -23
- package/src/util/locale.ts +0 -79
- package/src/util/lock.ts +0 -96
- package/src/util/log.ts +0 -234
- package/src/util/media.ts +0 -26
- package/src/util/network.ts +0 -9
- package/src/util/process.ts +0 -174
- package/src/util/provider-priority.ts +0 -48
- package/src/util/queue.ts +0 -60
- package/src/util/record.ts +0 -3
- package/src/util/rpc.ts +0 -64
- package/src/util/schema.ts +0 -53
- package/src/util/scrap.ts +0 -10
- package/src/util/signal.ts +0 -12
- package/src/util/sleepy-process.ts +0 -24
- package/src/util/ssrf.ts +0 -116
- package/src/util/timeout.ts +0 -14
- package/src/util/token.ts +0 -5
- package/src/util/tool-compat.ts +0 -144
- package/src/util/update-schema.ts +0 -13
- package/src/util/which.ts +0 -14
- package/src/util/wildcard.ts +0 -57
- package/src/workflow/builtin/compose.js +0 -749
- package/src/workflow/builtin/deep-research.js +0 -398
- package/src/workflow/builtin.ts +0 -59
- package/src/workflow/events.ts +0 -72
- package/src/workflow/meta.ts +0 -335
- package/src/workflow/persistence.ts +0 -312
- package/src/workflow/resolve.ts +0 -45
- package/src/workflow/runtime-ref.ts +0 -18
- package/src/workflow/runtime.ts +0 -1447
- package/src/workflow/sandbox.ts +0 -286
- package/src/workflow/workflow.sql.ts +0 -31
- package/src/workflow/workspace.ts +0 -69
- package/src/worktree/index.ts +0 -629
|
@@ -1,3073 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
batch,
|
|
3
|
-
createContext,
|
|
4
|
-
createEffect,
|
|
5
|
-
createMemo,
|
|
6
|
-
createSignal,
|
|
7
|
-
For,
|
|
8
|
-
Match,
|
|
9
|
-
on,
|
|
10
|
-
onCleanup,
|
|
11
|
-
Show,
|
|
12
|
-
Switch,
|
|
13
|
-
useContext,
|
|
14
|
-
} from "solid-js"
|
|
15
|
-
import { Dynamic } from "solid-js/web"
|
|
16
|
-
import path from "path"
|
|
17
|
-
import { useCurrentAgentID, useRoute, useRouteData } from "@tui/context/route"
|
|
18
|
-
import { useProject } from "@tui/context/project"
|
|
19
|
-
import { useSync } from "@tui/context/sync"
|
|
20
|
-
import { useEvent } from "@tui/context/event"
|
|
21
|
-
import { SplitBorder } from "@tui/component/border"
|
|
22
|
-
import { Spinner } from "@tui/component/spinner"
|
|
23
|
-
import { selectedForeground, useTheme } from "@tui/context/theme"
|
|
24
|
-
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA, MouseEvent } from "@opentui/core"
|
|
25
|
-
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
|
26
|
-
import type {
|
|
27
|
-
AssistantMessage,
|
|
28
|
-
Part,
|
|
29
|
-
Provider,
|
|
30
|
-
ToolPart,
|
|
31
|
-
UserMessage,
|
|
32
|
-
TextPart,
|
|
33
|
-
ReasoningPart,
|
|
34
|
-
} from "@sleepy-ai/sdk/v2"
|
|
35
|
-
import { useLocal } from "@tui/context/local"
|
|
36
|
-
import { Locale } from "@/util"
|
|
37
|
-
import type { Tool } from "@/tool"
|
|
38
|
-
import type { ReadTool } from "@/tool/read"
|
|
39
|
-
import type { WriteTool } from "@/tool/write"
|
|
40
|
-
import { BashTool } from "@/tool/bash"
|
|
41
|
-
import type { GlobTool } from "@/tool/glob"
|
|
42
|
-
import type { GrepTool } from "@/tool/grep"
|
|
43
|
-
import type { EditTool } from "@/tool/edit"
|
|
44
|
-
import type { ApplyPatchTool } from "@/tool/apply_patch"
|
|
45
|
-
import type { WebFetchTool } from "@/tool/webfetch"
|
|
46
|
-
import type { CodeSearchTool } from "@/tool/codesearch"
|
|
47
|
-
import type { WebSearchTool } from "@/tool/websearch"
|
|
48
|
-
import type { ActorTool } from "@/tool/actor"
|
|
49
|
-
import type { TaskTool } from "@/tool/task"
|
|
50
|
-
import type { QuestionTool } from "@/tool/question"
|
|
51
|
-
import type { SkillTool } from "@/tool/skill"
|
|
52
|
-
import type { WorkflowTool } from "@/tool/workflow"
|
|
53
|
-
import { useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
|
54
|
-
import { useSDK } from "@tui/context/sdk"
|
|
55
|
-
import { useCommandDialog } from "@tui/component/dialog-command"
|
|
56
|
-
import { useLanguage } from "@tui/context/language"
|
|
57
|
-
import type { DialogContext } from "@tui/ui/dialog"
|
|
58
|
-
import { useKeybind } from "@tui/context/keybind"
|
|
59
|
-
import { useDialog } from "../../ui/dialog"
|
|
60
|
-
import { DialogMessage } from "./dialog-message"
|
|
61
|
-
import type { PromptInfo } from "../../component/prompt/history"
|
|
62
|
-
import { DialogConfirm } from "@tui/ui/dialog-confirm"
|
|
63
|
-
import { DialogTimeline } from "./dialog-timeline"
|
|
64
|
-
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
|
|
65
|
-
import { DialogSessionRename } from "../../component/dialog-session-rename"
|
|
66
|
-
import { Sidebar } from "./sidebar"
|
|
67
|
-
import { WorkflowTree } from "@tui/component/workflow-tree"
|
|
68
|
-
import { SubagentFooter } from "./subagent-footer.tsx"
|
|
69
|
-
import { DialogSubagent } from "./dialog-subagent.tsx"
|
|
70
|
-
import { Flag } from "@/flag/flag"
|
|
71
|
-
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
|
72
|
-
import parsers from "../../../../../../parsers-config.ts"
|
|
73
|
-
import * as Clipboard from "../../util/clipboard"
|
|
74
|
-
import { Toast, useToast } from "../../ui/toast"
|
|
75
|
-
import { useKV } from "../../context/kv.tsx"
|
|
76
|
-
import * as Editor from "../../util/editor"
|
|
77
|
-
import stripAnsi from "strip-ansi"
|
|
78
|
-
import { usePromptRef } from "../../context/prompt"
|
|
79
|
-
import { useExit } from "../../context/exit"
|
|
80
|
-
import { Filesystem } from "@/util"
|
|
81
|
-
import { Global } from "@/global"
|
|
82
|
-
import { PermissionPrompt } from "./permission"
|
|
83
|
-
import { QuestionPrompt } from "./question"
|
|
84
|
-
import { DialogExportOptions } from "../../ui/dialog-export-options"
|
|
85
|
-
import * as Model from "../../util/model"
|
|
86
|
-
import { formatTranscript } from "../../util/transcript"
|
|
87
|
-
import { UI } from "@/cli/ui.ts"
|
|
88
|
-
import { useTuiConfig } from "../../context/tui-config"
|
|
89
|
-
import { getScrollAcceleration } from "../../util/scroll"
|
|
90
|
-
import { nextThinkingMode, reasoningSummary, useThinkingMode, type ThinkingMode } from "../../context/thinking"
|
|
91
|
-
import { TuiPluginRuntime } from "../../plugin"
|
|
92
|
-
import { DialogGoUpsell } from "../../component/dialog-go-upsell"
|
|
93
|
-
import { DialogTokenPlan } from "../../component/dialog-token-plan"
|
|
94
|
-
import { BannerSessionExpired } from "../../component/banner-session-expired"
|
|
95
|
-
import { SessionRetry } from "@/session/retry"
|
|
96
|
-
import { getRevertDiffFiles } from "../../util/revert-diff"
|
|
97
|
-
|
|
98
|
-
addDefaultParsers(parsers.parsers)
|
|
99
|
-
|
|
100
|
-
const GO_UPSELL_LAST_SEEN_AT = "go_upsell_last_seen_at"
|
|
101
|
-
const GO_UPSELL_DONT_SHOW = "go_upsell_dont_show"
|
|
102
|
-
const GO_UPSELL_WINDOW = 86_400_000 // 24 hrs
|
|
103
|
-
|
|
104
|
-
const QUEUE_TOKEN_PLAN_LAST_SEEN_AT = "queue_token_plan_last_seen_at"
|
|
105
|
-
const QUEUE_TOKEN_PLAN_WINDOW = 86_400_000 // 24 hrs
|
|
106
|
-
|
|
107
|
-
const context = createContext<{
|
|
108
|
-
width: number
|
|
109
|
-
sessionID: string
|
|
110
|
-
conceal: () => boolean
|
|
111
|
-
thinkingMode: () => ThinkingMode
|
|
112
|
-
showThinking: () => boolean
|
|
113
|
-
showTimestamps: () => boolean
|
|
114
|
-
showDetails: () => boolean
|
|
115
|
-
showGenericToolOutput: () => boolean
|
|
116
|
-
diffWrapMode: () => "word" | "none"
|
|
117
|
-
providers: () => ReadonlyMap<string, Provider>
|
|
118
|
-
sync: ReturnType<typeof useSync>
|
|
119
|
-
tui: ReturnType<typeof useTuiConfig>
|
|
120
|
-
}>()
|
|
121
|
-
|
|
122
|
-
function use() {
|
|
123
|
-
const ctx = useContext(context)
|
|
124
|
-
if (!ctx) throw new Error("useContext must be used within a Session component")
|
|
125
|
-
return ctx
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function Session() {
|
|
129
|
-
const route = useRouteData("session")
|
|
130
|
-
const fullRoute = useRoute()
|
|
131
|
-
const navigate = fullRoute.navigate
|
|
132
|
-
const sync = useSync()
|
|
133
|
-
const event = useEvent()
|
|
134
|
-
const project = useProject()
|
|
135
|
-
const tuiConfig = useTuiConfig()
|
|
136
|
-
const kv = useKV()
|
|
137
|
-
const { theme } = useTheme()
|
|
138
|
-
const promptRef = usePromptRef()
|
|
139
|
-
const session = createMemo(() => sync.session.get(route.sessionID))
|
|
140
|
-
const currentAgentID = useCurrentAgentID()
|
|
141
|
-
const actors = createMemo(() => sync.data.actor[route.sessionID] ?? [])
|
|
142
|
-
const messages = createMemo(() => sync.data.message[route.sessionID]?.[currentAgentID()] ?? [])
|
|
143
|
-
const permissions = createMemo(() => sync.data.permission[route.sessionID] ?? [])
|
|
144
|
-
const questions = createMemo(() => sync.data.question[route.sessionID] ?? [])
|
|
145
|
-
const visible = createMemo(
|
|
146
|
-
() =>
|
|
147
|
-
!session()?.parentID &&
|
|
148
|
-
currentAgentID() === "main" &&
|
|
149
|
-
permissions().length === 0 &&
|
|
150
|
-
questions().length === 0,
|
|
151
|
-
)
|
|
152
|
-
const disabled = createMemo(() => permissions().length > 0 || questions().length > 0)
|
|
153
|
-
const [sessionExpired, setSessionExpired] = createSignal<{ expired: boolean; message: string }>({
|
|
154
|
-
expired: false,
|
|
155
|
-
message: "",
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
const pending = createMemo(() => {
|
|
159
|
-
return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
const lastAssistant = createMemo(() => {
|
|
163
|
-
return messages().findLast((x) => x.role === "assistant")
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const dimensions = useTerminalDimensions()
|
|
167
|
-
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto")
|
|
168
|
-
const [sidebarOpen, setSidebarOpen] = createSignal(false)
|
|
169
|
-
const [conceal, setConceal] = createSignal(true)
|
|
170
|
-
const thinking = useThinkingMode()
|
|
171
|
-
const thinkingMode = thinking.mode
|
|
172
|
-
const showThinking = createMemo(() => true)
|
|
173
|
-
const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide")
|
|
174
|
-
const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
|
|
175
|
-
const [showAssistantMetadata, _setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
|
|
176
|
-
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
|
|
177
|
-
const [scrolling, setScrolling] = createSignal(false)
|
|
178
|
-
let scrollHideTimer: ReturnType<typeof setTimeout> | undefined
|
|
179
|
-
const scrollbarVisible = createMemo(() => showScrollbar() || scrolling())
|
|
180
|
-
const onWheel = (evt: MouseEvent) => {
|
|
181
|
-
if (evt.type !== "scroll") return
|
|
182
|
-
setScrolling(true)
|
|
183
|
-
if (scrollHideTimer) clearTimeout(scrollHideTimer)
|
|
184
|
-
scrollHideTimer = setTimeout(() => setScrolling(false), 1000)
|
|
185
|
-
}
|
|
186
|
-
onCleanup(() => {
|
|
187
|
-
if (scrollHideTimer) clearTimeout(scrollHideTimer)
|
|
188
|
-
})
|
|
189
|
-
const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
|
|
190
|
-
const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true)
|
|
191
|
-
const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false)
|
|
192
|
-
|
|
193
|
-
// The workflow run shown as a FULL-SCREEN page (replacing the message stream),
|
|
194
|
-
// mirroring how agentID renders a subagent's conversation. Driven by the route so
|
|
195
|
-
// it's a real navigable view, not a side panel.
|
|
196
|
-
const workflowRunID = createMemo(() => route.workflowRunID)
|
|
197
|
-
const fromWorkflowRunID = createMemo(() => route.fromWorkflowRunID)
|
|
198
|
-
|
|
199
|
-
const wide = createMemo(() => dimensions().width > 120)
|
|
200
|
-
const sidebarVisible = createMemo(() => {
|
|
201
|
-
if (session()?.parentID) return false
|
|
202
|
-
if (currentAgentID() !== "main") return false
|
|
203
|
-
if (sidebarOpen()) return true
|
|
204
|
-
if (sidebar() === "auto" && wide()) return true
|
|
205
|
-
return false
|
|
206
|
-
})
|
|
207
|
-
const showTimestamps = createMemo(() => timestamps() === "show")
|
|
208
|
-
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
|
|
209
|
-
const providers = createMemo(() => Model.index(sync.data.provider))
|
|
210
|
-
|
|
211
|
-
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
|
|
212
|
-
const toast = useToast()
|
|
213
|
-
const sdk = useSDK()
|
|
214
|
-
|
|
215
|
-
createEffect(async () => {
|
|
216
|
-
const previousWorkspace = project.workspace.current()
|
|
217
|
-
const result = await sdk.client.session.get({ sessionID: route.sessionID }, { throwOnError: true })
|
|
218
|
-
if (!result.data) {
|
|
219
|
-
toast.show({
|
|
220
|
-
message: `Session not found: ${route.sessionID}`,
|
|
221
|
-
variant: "error",
|
|
222
|
-
})
|
|
223
|
-
navigate({ type: "home" })
|
|
224
|
-
return
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (result.data.workspaceID !== previousWorkspace) {
|
|
228
|
-
project.workspace.set(result.data.workspaceID)
|
|
229
|
-
|
|
230
|
-
// Sync all the data for this workspace. Note that this
|
|
231
|
-
// workspace may not exist anymore which is why this is not
|
|
232
|
-
// fatal. If it doesn't we still want to show the session
|
|
233
|
-
// (which will be non-interactive)
|
|
234
|
-
try {
|
|
235
|
-
await sync.bootstrap({ fatal: false })
|
|
236
|
-
} catch (e) {}
|
|
237
|
-
}
|
|
238
|
-
await sync.session.sync(route.sessionID)
|
|
239
|
-
if (scroll) scroll.scrollBy(100_000)
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
let lastSwitch: string | undefined = undefined
|
|
243
|
-
event.on("message.part.updated", (evt) => {
|
|
244
|
-
const part = evt.properties.part
|
|
245
|
-
if (part.type !== "tool") return
|
|
246
|
-
if (part.sessionID !== route.sessionID) return
|
|
247
|
-
if (part.state.status !== "completed") return
|
|
248
|
-
if (part.id === lastSwitch) return
|
|
249
|
-
|
|
250
|
-
if (part.tool === "plan_exit" && part.state.metadata?.switched) {
|
|
251
|
-
local.agent.set("build")
|
|
252
|
-
lastSwitch = part.id
|
|
253
|
-
} else if (part.tool === "plan_enter") {
|
|
254
|
-
local.agent.set("plan")
|
|
255
|
-
lastSwitch = part.id
|
|
256
|
-
}
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
let seeded = false
|
|
260
|
-
let scroll: ScrollBoxRenderable
|
|
261
|
-
let prompt: PromptRef | undefined
|
|
262
|
-
const bind = (r: PromptRef | undefined) => {
|
|
263
|
-
prompt = r
|
|
264
|
-
promptRef.set(r)
|
|
265
|
-
if (seeded || !route.prompt || !r) return
|
|
266
|
-
seeded = true
|
|
267
|
-
r.set(route.prompt)
|
|
268
|
-
}
|
|
269
|
-
const keybind = useKeybind()
|
|
270
|
-
const dialog = useDialog()
|
|
271
|
-
const renderer = useRenderer()
|
|
272
|
-
|
|
273
|
-
event.on("session.status", (evt) => {
|
|
274
|
-
if (evt.properties.sessionID !== route.sessionID) return
|
|
275
|
-
if (evt.properties.status.type !== "retry") return
|
|
276
|
-
if (evt.properties.status.message !== SessionRetry.GO_UPSELL_MESSAGE) return
|
|
277
|
-
if (dialog.stack.length > 0) return
|
|
278
|
-
|
|
279
|
-
const seen = kv.get(GO_UPSELL_LAST_SEEN_AT)
|
|
280
|
-
if (typeof seen === "number" && Date.now() - seen < GO_UPSELL_WINDOW) return
|
|
281
|
-
|
|
282
|
-
if (kv.get(GO_UPSELL_DONT_SHOW)) return
|
|
283
|
-
|
|
284
|
-
void DialogGoUpsell.show(dialog).then((dontShowAgain) => {
|
|
285
|
-
if (dontShowAgain) kv.set(GO_UPSELL_DONT_SHOW, true)
|
|
286
|
-
kv.set(GO_UPSELL_LAST_SEEN_AT, Date.now())
|
|
287
|
-
})
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
// Allow exit when in child session (prompt is hidden)
|
|
291
|
-
const exit = useExit()
|
|
292
|
-
|
|
293
|
-
createEffect(() => {
|
|
294
|
-
const title = Locale.truncate(session()?.title ?? "", 50)
|
|
295
|
-
const pad = (text: string) => text.padEnd(10, " ")
|
|
296
|
-
const weak = (text: string) => UI.Style.TEXT_DIM + pad(text) + UI.Style.TEXT_NORMAL
|
|
297
|
-
const logo = UI.logo(" ").split(/\r?\n/)
|
|
298
|
-
return exit.message.set(
|
|
299
|
-
[
|
|
300
|
-
...logo,
|
|
301
|
-
``,
|
|
302
|
-
` ${weak("Session")}${UI.Style.TEXT_NORMAL_BOLD}${title}${UI.Style.TEXT_NORMAL}`,
|
|
303
|
-
` ${weak("Continue")}${UI.Style.TEXT_NORMAL_BOLD}sleepy -s ${session()?.id}${UI.Style.TEXT_NORMAL}`,
|
|
304
|
-
``,
|
|
305
|
-
].join("\n"),
|
|
306
|
-
)
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
useKeyboard((evt) => {
|
|
310
|
-
if (!session()?.parentID && currentAgentID() === "main") return
|
|
311
|
-
if (keybind.match("app_exit", evt)) {
|
|
312
|
-
const status = sync.data.session_status?.[route.sessionID]
|
|
313
|
-
if (status && status.type !== "idle") {
|
|
314
|
-
void sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
void exit()
|
|
318
|
-
}
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
// Helper: Find next visible message boundary in direction
|
|
322
|
-
const findNextVisibleMessage = (direction: "next" | "prev"): string | null => {
|
|
323
|
-
const children = scroll.getChildren()
|
|
324
|
-
const messagesList = messages()
|
|
325
|
-
const scrollTop = scroll.y
|
|
326
|
-
|
|
327
|
-
// Get visible messages sorted by position, filtering for valid non-synthetic, non-ignored content
|
|
328
|
-
const visibleMessages = children
|
|
329
|
-
.filter((c) => {
|
|
330
|
-
if (!c.id) return false
|
|
331
|
-
const message = messagesList.find((m) => m.id === c.id)
|
|
332
|
-
if (!message) return false
|
|
333
|
-
|
|
334
|
-
// Check if message has valid non-synthetic, non-ignored text parts
|
|
335
|
-
const parts = sync.data.part[message.id]
|
|
336
|
-
if (!parts || !Array.isArray(parts)) return false
|
|
337
|
-
|
|
338
|
-
return parts.some((part) => part && part.type === "text" && !part.synthetic && !part.ignored)
|
|
339
|
-
})
|
|
340
|
-
.sort((a, b) => a.y - b.y)
|
|
341
|
-
|
|
342
|
-
if (visibleMessages.length === 0) return null
|
|
343
|
-
|
|
344
|
-
if (direction === "next") {
|
|
345
|
-
// Find first message below current position
|
|
346
|
-
return visibleMessages.find((c) => c.y > scrollTop + 10)?.id ?? null
|
|
347
|
-
}
|
|
348
|
-
// Find last message above current position
|
|
349
|
-
return [...visibleMessages].reverse().find((c) => c.y < scrollTop - 10)?.id ?? null
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Helper: Scroll to message in direction or fallback to page scroll
|
|
353
|
-
const scrollToMessage = (direction: "next" | "prev", dialog: ReturnType<typeof useDialog>) => {
|
|
354
|
-
const targetID = findNextVisibleMessage(direction)
|
|
355
|
-
|
|
356
|
-
if (!targetID) {
|
|
357
|
-
scroll.scrollBy(direction === "next" ? scroll.height : -scroll.height)
|
|
358
|
-
dialog.clear()
|
|
359
|
-
return
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const child = scroll.getChildren().find((c) => c.id === targetID)
|
|
363
|
-
if (child) scroll.scrollBy(child.y - scroll.y - 1)
|
|
364
|
-
dialog.clear()
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function toBottom() {
|
|
368
|
-
setTimeout(() => {
|
|
369
|
-
if (!scroll || scroll.isDestroyed) return
|
|
370
|
-
scroll.scrollTo(scroll.scrollHeight)
|
|
371
|
-
}, 50)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const local = useLocal()
|
|
375
|
-
|
|
376
|
-
// Free "sleepy-auto" channel: on a rate-limit / queue ("too many requests"),
|
|
377
|
-
// nudge the user toward a Token Plan — at most once per 24h.
|
|
378
|
-
event.on("tui.session.expired", (evt) => {
|
|
379
|
-
setSessionExpired({ expired: true, message: evt.properties.message })
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
event.on("session.status", (evt) => {
|
|
383
|
-
if (evt.properties.sessionID !== route.sessionID) return
|
|
384
|
-
if (evt.properties.status.type !== "retry") return
|
|
385
|
-
if (!SessionRetry.isRateLimitMessage(evt.properties.status.message)) return
|
|
386
|
-
const model = local.model.current()
|
|
387
|
-
if (!model || model.providerID !== "sleepy" || model.modelID !== "sleepy-auto") return
|
|
388
|
-
if (dialog.stack.length > 0) return
|
|
389
|
-
|
|
390
|
-
const seen = kv.get(QUEUE_TOKEN_PLAN_LAST_SEEN_AT)
|
|
391
|
-
if (typeof seen === "number" && Date.now() - seen < QUEUE_TOKEN_PLAN_WINDOW) return
|
|
392
|
-
|
|
393
|
-
// Record the 24h cooldown only after the user dismisses, so a show() that
|
|
394
|
-
// fails (or never reaches the user) doesn't silently burn the whole day.
|
|
395
|
-
void DialogTokenPlan.show(dialog).then(() => {
|
|
396
|
-
kv.set(QUEUE_TOKEN_PLAN_LAST_SEEN_AT, Date.now())
|
|
397
|
-
})
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
function moveFirstChild() {
|
|
401
|
-
const list = actors().filter((a) => a.mode === "subagent")
|
|
402
|
-
if (list.length === 0) {
|
|
403
|
-
dialog.replace(() => <DialogSubagent sessionID={route.sessionID} />)
|
|
404
|
-
return
|
|
405
|
-
}
|
|
406
|
-
if (fullRoute.data.type !== "session") return
|
|
407
|
-
navigate({ ...fullRoute.data, agentID: list[0].actor_id, fromWorkflowRunID: undefined })
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function moveChild(direction: 1 | -1) {
|
|
411
|
-
const list = actors().filter((a) => a.mode === "subagent")
|
|
412
|
-
if (list.length === 0) return
|
|
413
|
-
if (fullRoute.data.type !== "session") return
|
|
414
|
-
const cur = currentAgentID()
|
|
415
|
-
const idx = list.findIndex((a) => a.actor_id === cur)
|
|
416
|
-
const next =
|
|
417
|
-
idx === -1
|
|
418
|
-
? direction === 1
|
|
419
|
-
? 0
|
|
420
|
-
: list.length - 1
|
|
421
|
-
: (idx + direction + list.length) % list.length
|
|
422
|
-
navigate({ ...fullRoute.data, agentID: list[next].actor_id, fromWorkflowRunID: undefined })
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const command = useCommandDialog()
|
|
426
|
-
const t = useLanguage().t
|
|
427
|
-
command.register(() => [
|
|
428
|
-
{
|
|
429
|
-
title: t(session()?.share?.url ? "tui.command.session.share.copy_link" : "tui.command.session.share.title"),
|
|
430
|
-
value: "session.share",
|
|
431
|
-
suggested: route.type === "session",
|
|
432
|
-
keybind: "session_share",
|
|
433
|
-
category: "session",
|
|
434
|
-
enabled: sync.data.config.share !== "disabled",
|
|
435
|
-
slash: {
|
|
436
|
-
name: "share",
|
|
437
|
-
},
|
|
438
|
-
onSelect: async (dialog) => {
|
|
439
|
-
const copy = (url: string) =>
|
|
440
|
-
Clipboard.copy(url)
|
|
441
|
-
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
|
|
442
|
-
.catch(() => toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }))
|
|
443
|
-
const url = session()?.share?.url
|
|
444
|
-
if (url) {
|
|
445
|
-
await copy(url)
|
|
446
|
-
dialog.clear()
|
|
447
|
-
return
|
|
448
|
-
}
|
|
449
|
-
if (!kv.get("share_consent", false)) {
|
|
450
|
-
const ok = await DialogConfirm.show(dialog, "Share Session", "Are you sure you want to share it?")
|
|
451
|
-
if (ok !== true) return
|
|
452
|
-
kv.set("share_consent", true)
|
|
453
|
-
}
|
|
454
|
-
await sdk.client.session
|
|
455
|
-
.share({
|
|
456
|
-
sessionID: route.sessionID,
|
|
457
|
-
})
|
|
458
|
-
.then((res) => copy(res.data!.share!.url))
|
|
459
|
-
.catch((error) => {
|
|
460
|
-
toast.show({
|
|
461
|
-
message: error instanceof Error ? error.message : "Failed to share session",
|
|
462
|
-
variant: "error",
|
|
463
|
-
})
|
|
464
|
-
})
|
|
465
|
-
dialog.clear()
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
title: t("tui.command.session.rename.title"),
|
|
470
|
-
value: "session.rename",
|
|
471
|
-
keybind: "session_rename",
|
|
472
|
-
category: "session",
|
|
473
|
-
slash: {
|
|
474
|
-
name: "rename",
|
|
475
|
-
},
|
|
476
|
-
onSelect: (dialog) => {
|
|
477
|
-
dialog.replace(() => <DialogSessionRename session={route.sessionID} />)
|
|
478
|
-
},
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
title: t("tui.command.session.timeline.title"),
|
|
482
|
-
value: "session.timeline",
|
|
483
|
-
keybind: "session_timeline",
|
|
484
|
-
category: "session",
|
|
485
|
-
slash: {
|
|
486
|
-
name: "timeline",
|
|
487
|
-
},
|
|
488
|
-
onSelect: (dialog) => {
|
|
489
|
-
dialog.replace(() => (
|
|
490
|
-
<DialogTimeline
|
|
491
|
-
onMove={(messageID) => {
|
|
492
|
-
const child = scroll.getChildren().find((child) => {
|
|
493
|
-
return child.id === messageID
|
|
494
|
-
})
|
|
495
|
-
if (child) scroll.scrollBy(child.y - scroll.y - 1)
|
|
496
|
-
}}
|
|
497
|
-
sessionID={route.sessionID}
|
|
498
|
-
setPrompt={(promptInfo) => prompt?.set(promptInfo)}
|
|
499
|
-
/>
|
|
500
|
-
))
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
title: t("tui.command.session.fork.title"),
|
|
505
|
-
value: "session.fork",
|
|
506
|
-
keybind: "session_fork",
|
|
507
|
-
category: "session",
|
|
508
|
-
slash: {
|
|
509
|
-
name: "fork",
|
|
510
|
-
},
|
|
511
|
-
onSelect: (dialog) => {
|
|
512
|
-
dialog.replace(() => (
|
|
513
|
-
<DialogForkFromTimeline
|
|
514
|
-
onMove={(messageID) => {
|
|
515
|
-
if (!messageID) return
|
|
516
|
-
const child = scroll.getChildren().find((child) => {
|
|
517
|
-
return child.id === messageID
|
|
518
|
-
})
|
|
519
|
-
if (child) scroll.scrollBy(child.y - scroll.y - 1)
|
|
520
|
-
}}
|
|
521
|
-
sessionID={route.sessionID}
|
|
522
|
-
/>
|
|
523
|
-
))
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
title: t("tui.command.session.compact.title"),
|
|
528
|
-
value: "session.compact",
|
|
529
|
-
keybind: "session_compact",
|
|
530
|
-
category: "session",
|
|
531
|
-
slash: {
|
|
532
|
-
name: "compact",
|
|
533
|
-
aliases: ["summarize"],
|
|
534
|
-
},
|
|
535
|
-
onSelect: (dialog) => {
|
|
536
|
-
const selectedModel = local.model.current()
|
|
537
|
-
if (!selectedModel) {
|
|
538
|
-
toast.show({
|
|
539
|
-
variant: "warning",
|
|
540
|
-
message: "Connect a provider to summarize this session",
|
|
541
|
-
duration: 3000,
|
|
542
|
-
})
|
|
543
|
-
return
|
|
544
|
-
}
|
|
545
|
-
void sdk.client.session.summarize({
|
|
546
|
-
sessionID: route.sessionID,
|
|
547
|
-
modelID: selectedModel.modelID,
|
|
548
|
-
providerID: selectedModel.providerID,
|
|
549
|
-
})
|
|
550
|
-
dialog.clear()
|
|
551
|
-
},
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
title: t("tui.command.session.unshare.title"),
|
|
555
|
-
value: "session.unshare",
|
|
556
|
-
keybind: "session_unshare",
|
|
557
|
-
category: "session",
|
|
558
|
-
enabled: !!session()?.share?.url,
|
|
559
|
-
slash: {
|
|
560
|
-
name: "unshare",
|
|
561
|
-
},
|
|
562
|
-
onSelect: async (dialog) => {
|
|
563
|
-
await sdk.client.session
|
|
564
|
-
.unshare({
|
|
565
|
-
sessionID: route.sessionID,
|
|
566
|
-
})
|
|
567
|
-
.then(() => toast.show({ message: "Session unshared successfully", variant: "success" }))
|
|
568
|
-
.catch((error) => {
|
|
569
|
-
toast.show({
|
|
570
|
-
message: error instanceof Error ? error.message : "Failed to unshare session",
|
|
571
|
-
variant: "error",
|
|
572
|
-
})
|
|
573
|
-
})
|
|
574
|
-
dialog.clear()
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
title: t("tui.command.session.undo.title"),
|
|
579
|
-
value: "session.undo",
|
|
580
|
-
keybind: "messages_undo",
|
|
581
|
-
category: "session",
|
|
582
|
-
slash: {
|
|
583
|
-
name: "undo",
|
|
584
|
-
},
|
|
585
|
-
onSelect: async (dialog) => {
|
|
586
|
-
const status = sync.data.session_status?.[route.sessionID]
|
|
587
|
-
if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
|
|
588
|
-
const revert = session()?.revert?.messageID
|
|
589
|
-
const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
|
|
590
|
-
if (!message) return
|
|
591
|
-
void sdk.client.session
|
|
592
|
-
.revert({
|
|
593
|
-
sessionID: route.sessionID,
|
|
594
|
-
messageID: message.id,
|
|
595
|
-
})
|
|
596
|
-
.then(() => {
|
|
597
|
-
toBottom()
|
|
598
|
-
})
|
|
599
|
-
const parts = sync.data.part[message.id]
|
|
600
|
-
prompt?.set(
|
|
601
|
-
parts.reduce(
|
|
602
|
-
(agg, part) => {
|
|
603
|
-
if (part.type === "text") {
|
|
604
|
-
if (!part.synthetic) agg.input += part.text
|
|
605
|
-
}
|
|
606
|
-
if (part.type === "file") agg.parts.push(part)
|
|
607
|
-
return agg
|
|
608
|
-
},
|
|
609
|
-
{ input: "", parts: [] as PromptInfo["parts"] },
|
|
610
|
-
),
|
|
611
|
-
)
|
|
612
|
-
dialog.clear()
|
|
613
|
-
},
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
title: t("tui.command.session.redo.title"),
|
|
617
|
-
value: "session.redo",
|
|
618
|
-
keybind: "messages_redo",
|
|
619
|
-
category: "session",
|
|
620
|
-
enabled: !!session()?.revert?.messageID,
|
|
621
|
-
slash: {
|
|
622
|
-
name: "redo",
|
|
623
|
-
},
|
|
624
|
-
onSelect: (dialog) => {
|
|
625
|
-
dialog.clear()
|
|
626
|
-
const messageID = session()?.revert?.messageID
|
|
627
|
-
if (!messageID) return
|
|
628
|
-
const message = messages().find((x) => x.role === "user" && x.id > messageID)
|
|
629
|
-
if (!message) {
|
|
630
|
-
void sdk.client.session.unrevert({
|
|
631
|
-
sessionID: route.sessionID,
|
|
632
|
-
})
|
|
633
|
-
prompt?.set({ input: "", parts: [] })
|
|
634
|
-
return
|
|
635
|
-
}
|
|
636
|
-
void sdk.client.session.revert({
|
|
637
|
-
sessionID: route.sessionID,
|
|
638
|
-
messageID: message.id,
|
|
639
|
-
})
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
title: t(sidebarVisible() ? "tui.command.session.sidebar.hide" : "tui.command.session.sidebar.show"),
|
|
644
|
-
value: "session.sidebar.toggle",
|
|
645
|
-
keybind: "sidebar_toggle",
|
|
646
|
-
category: "session",
|
|
647
|
-
onSelect: (dialog) => {
|
|
648
|
-
batch(() => {
|
|
649
|
-
const isVisible = sidebarVisible()
|
|
650
|
-
setSidebar(() => (isVisible ? "hide" : "auto"))
|
|
651
|
-
setSidebarOpen(!isVisible)
|
|
652
|
-
})
|
|
653
|
-
dialog.clear()
|
|
654
|
-
},
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
title: t(conceal() ? "tui.command.session.conceal.disable" : "tui.command.session.conceal.enable"),
|
|
658
|
-
value: "session.toggle.conceal",
|
|
659
|
-
keybind: "messages_toggle_conceal",
|
|
660
|
-
category: "session",
|
|
661
|
-
onSelect: (dialog) => {
|
|
662
|
-
setConceal((prev) => !prev)
|
|
663
|
-
dialog.clear()
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
title: t(showTimestamps() ? "tui.command.session.timestamps.hide" : "tui.command.session.timestamps.show"),
|
|
668
|
-
value: "session.toggle.timestamps",
|
|
669
|
-
category: "session",
|
|
670
|
-
slash: {
|
|
671
|
-
name: "timestamps",
|
|
672
|
-
aliases: ["toggle-timestamps"],
|
|
673
|
-
},
|
|
674
|
-
onSelect: (dialog) => {
|
|
675
|
-
setTimestamps((prev) => (prev === "show" ? "hide" : "show"))
|
|
676
|
-
dialog.clear()
|
|
677
|
-
},
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
title: t(
|
|
681
|
-
nextThinkingMode(thinkingMode()) === "hide"
|
|
682
|
-
? "tui.command.session.thinking.collapse"
|
|
683
|
-
: "tui.command.session.thinking.expand",
|
|
684
|
-
),
|
|
685
|
-
value: "session.toggle.thinking",
|
|
686
|
-
keybind: "display_thinking",
|
|
687
|
-
category: "session",
|
|
688
|
-
slash: {
|
|
689
|
-
name: "thinking",
|
|
690
|
-
aliases: ["toggle-thinking"],
|
|
691
|
-
},
|
|
692
|
-
onSelect: (dialog) => {
|
|
693
|
-
thinking.set(nextThinkingMode(thinkingMode()))
|
|
694
|
-
dialog.clear()
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
{
|
|
698
|
-
title: t(showDetails() ? "tui.command.session.tool_details.hide" : "tui.command.session.tool_details.show"),
|
|
699
|
-
value: "session.toggle.actions",
|
|
700
|
-
keybind: "tool_details",
|
|
701
|
-
category: "session",
|
|
702
|
-
onSelect: (dialog) => {
|
|
703
|
-
setShowDetails((prev) => !prev)
|
|
704
|
-
dialog.clear()
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
title: t("tui.command.session.scrollbar.toggle"),
|
|
709
|
-
value: "session.toggle.scrollbar",
|
|
710
|
-
keybind: "scrollbar_toggle",
|
|
711
|
-
category: "session",
|
|
712
|
-
onSelect: (dialog) => {
|
|
713
|
-
setShowScrollbar((prev) => !prev)
|
|
714
|
-
dialog.clear()
|
|
715
|
-
},
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
title: t(
|
|
719
|
-
showGenericToolOutput()
|
|
720
|
-
? "tui.command.session.generic_tool_output.hide"
|
|
721
|
-
: "tui.command.session.generic_tool_output.show",
|
|
722
|
-
),
|
|
723
|
-
value: "session.toggle.generic_tool_output",
|
|
724
|
-
category: "session",
|
|
725
|
-
onSelect: (dialog) => {
|
|
726
|
-
setShowGenericToolOutput((prev) => !prev)
|
|
727
|
-
dialog.clear()
|
|
728
|
-
},
|
|
729
|
-
},
|
|
730
|
-
{
|
|
731
|
-
title: t("tui.command.session.page_up.title"),
|
|
732
|
-
value: "session.page.up",
|
|
733
|
-
keybind: "messages_page_up",
|
|
734
|
-
category: "session",
|
|
735
|
-
hidden: true,
|
|
736
|
-
onSelect: (dialog) => {
|
|
737
|
-
scroll.scrollBy(-scroll.height / 2)
|
|
738
|
-
dialog.clear()
|
|
739
|
-
},
|
|
740
|
-
},
|
|
741
|
-
{
|
|
742
|
-
title: t("tui.command.session.page_down.title"),
|
|
743
|
-
value: "session.page.down",
|
|
744
|
-
keybind: "messages_page_down",
|
|
745
|
-
category: "session",
|
|
746
|
-
hidden: true,
|
|
747
|
-
onSelect: (dialog) => {
|
|
748
|
-
scroll.scrollBy(scroll.height / 2)
|
|
749
|
-
dialog.clear()
|
|
750
|
-
},
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
title: t("tui.command.session.line_up.title"),
|
|
754
|
-
value: "session.line.up",
|
|
755
|
-
keybind: "messages_line_up",
|
|
756
|
-
category: "session",
|
|
757
|
-
disabled: true,
|
|
758
|
-
onSelect: (dialog) => {
|
|
759
|
-
scroll.scrollBy(-1)
|
|
760
|
-
dialog.clear()
|
|
761
|
-
},
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
title: t("tui.command.session.line_down.title"),
|
|
765
|
-
value: "session.line.down",
|
|
766
|
-
keybind: "messages_line_down",
|
|
767
|
-
category: "session",
|
|
768
|
-
disabled: true,
|
|
769
|
-
onSelect: (dialog) => {
|
|
770
|
-
scroll.scrollBy(1)
|
|
771
|
-
dialog.clear()
|
|
772
|
-
},
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
title: t("tui.command.session.half_page_up.title"),
|
|
776
|
-
value: "session.half.page.up",
|
|
777
|
-
keybind: "messages_half_page_up",
|
|
778
|
-
category: "session",
|
|
779
|
-
hidden: true,
|
|
780
|
-
onSelect: (dialog) => {
|
|
781
|
-
scroll.scrollBy(-scroll.height / 4)
|
|
782
|
-
dialog.clear()
|
|
783
|
-
},
|
|
784
|
-
},
|
|
785
|
-
{
|
|
786
|
-
title: t("tui.command.session.half_page_down.title"),
|
|
787
|
-
value: "session.half.page.down",
|
|
788
|
-
keybind: "messages_half_page_down",
|
|
789
|
-
category: "session",
|
|
790
|
-
hidden: true,
|
|
791
|
-
onSelect: (dialog) => {
|
|
792
|
-
scroll.scrollBy(scroll.height / 4)
|
|
793
|
-
dialog.clear()
|
|
794
|
-
},
|
|
795
|
-
},
|
|
796
|
-
{
|
|
797
|
-
title: t("tui.command.session.first.title"),
|
|
798
|
-
value: "session.first",
|
|
799
|
-
keybind: "messages_first",
|
|
800
|
-
category: "session",
|
|
801
|
-
hidden: true,
|
|
802
|
-
onSelect: (dialog) => {
|
|
803
|
-
scroll.scrollTo(0)
|
|
804
|
-
dialog.clear()
|
|
805
|
-
},
|
|
806
|
-
},
|
|
807
|
-
{
|
|
808
|
-
title: t("tui.command.session.last.title"),
|
|
809
|
-
value: "session.last",
|
|
810
|
-
keybind: "messages_last",
|
|
811
|
-
category: "session",
|
|
812
|
-
hidden: true,
|
|
813
|
-
onSelect: (dialog) => {
|
|
814
|
-
scroll.scrollTo(scroll.scrollHeight)
|
|
815
|
-
dialog.clear()
|
|
816
|
-
},
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
title: t("tui.command.session.last_user.title"),
|
|
820
|
-
value: "session.messages_last_user",
|
|
821
|
-
keybind: "messages_last_user",
|
|
822
|
-
category: "session",
|
|
823
|
-
hidden: true,
|
|
824
|
-
onSelect: () => {
|
|
825
|
-
const msgs = messages()
|
|
826
|
-
if (!msgs || !msgs.length) return
|
|
827
|
-
|
|
828
|
-
// Find the most recent user message with non-ignored, non-synthetic text parts
|
|
829
|
-
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
830
|
-
const message = msgs[i]
|
|
831
|
-
if (!message || message.role !== "user") continue
|
|
832
|
-
|
|
833
|
-
const parts = sync.data.part[message.id]
|
|
834
|
-
if (!parts || !Array.isArray(parts)) continue
|
|
835
|
-
|
|
836
|
-
const hasValidTextPart = parts.some(
|
|
837
|
-
(part) => part && part.type === "text" && !part.synthetic && !part.ignored,
|
|
838
|
-
)
|
|
839
|
-
|
|
840
|
-
if (hasValidTextPart) {
|
|
841
|
-
const child = scroll.getChildren().find((child) => {
|
|
842
|
-
return child.id === message.id
|
|
843
|
-
})
|
|
844
|
-
if (child) scroll.scrollBy(child.y - scroll.y - 1)
|
|
845
|
-
break
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
},
|
|
849
|
-
},
|
|
850
|
-
{
|
|
851
|
-
title: t("tui.command.session.message_next.title"),
|
|
852
|
-
value: "session.message.next",
|
|
853
|
-
keybind: "messages_next",
|
|
854
|
-
category: "session",
|
|
855
|
-
hidden: true,
|
|
856
|
-
onSelect: (dialog) => scrollToMessage("next", dialog),
|
|
857
|
-
},
|
|
858
|
-
{
|
|
859
|
-
title: t("tui.command.session.message_previous.title"),
|
|
860
|
-
value: "session.message.previous",
|
|
861
|
-
keybind: "messages_previous",
|
|
862
|
-
category: "session",
|
|
863
|
-
hidden: true,
|
|
864
|
-
onSelect: (dialog) => scrollToMessage("prev", dialog),
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
title: t("tui.command.messages.copy.title"),
|
|
868
|
-
value: "messages.copy",
|
|
869
|
-
keybind: "messages_copy",
|
|
870
|
-
category: "session",
|
|
871
|
-
onSelect: (dialog) => {
|
|
872
|
-
const revertID = session()?.revert?.messageID
|
|
873
|
-
const lastAssistantMessage = messages().findLast(
|
|
874
|
-
(msg) => msg.role === "assistant" && (!revertID || msg.id < revertID),
|
|
875
|
-
)
|
|
876
|
-
if (!lastAssistantMessage) {
|
|
877
|
-
toast.show({ message: "No assistant messages found", variant: "error" })
|
|
878
|
-
dialog.clear()
|
|
879
|
-
return
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
const parts = sync.data.part[lastAssistantMessage.id] ?? []
|
|
883
|
-
const textParts = parts.filter((part) => part.type === "text")
|
|
884
|
-
if (textParts.length === 0) {
|
|
885
|
-
toast.show({ message: "No text parts found in last assistant message", variant: "error" })
|
|
886
|
-
dialog.clear()
|
|
887
|
-
return
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
const text = textParts
|
|
891
|
-
.map((part) => part.text)
|
|
892
|
-
.join("\n")
|
|
893
|
-
.trim()
|
|
894
|
-
if (!text) {
|
|
895
|
-
toast.show({
|
|
896
|
-
message: "No text content found in last assistant message",
|
|
897
|
-
variant: "error",
|
|
898
|
-
})
|
|
899
|
-
dialog.clear()
|
|
900
|
-
return
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
Clipboard.copy(text)
|
|
904
|
-
.then(() => toast.show({ message: "Message copied to clipboard!", variant: "success" }))
|
|
905
|
-
.catch(() => toast.show({ message: "Failed to copy to clipboard", variant: "error" }))
|
|
906
|
-
dialog.clear()
|
|
907
|
-
},
|
|
908
|
-
},
|
|
909
|
-
{
|
|
910
|
-
title: t("tui.command.session.copy.title"),
|
|
911
|
-
value: "session.copy",
|
|
912
|
-
category: "session",
|
|
913
|
-
slash: {
|
|
914
|
-
name: "copy",
|
|
915
|
-
},
|
|
916
|
-
onSelect: async (dialog) => {
|
|
917
|
-
try {
|
|
918
|
-
const sessionData = session()
|
|
919
|
-
if (!sessionData) return
|
|
920
|
-
const sessionMessages = messages()
|
|
921
|
-
const transcript = formatTranscript(
|
|
922
|
-
sessionData,
|
|
923
|
-
sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
|
|
924
|
-
{
|
|
925
|
-
thinking: showThinking(),
|
|
926
|
-
toolDetails: showDetails(),
|
|
927
|
-
assistantMetadata: showAssistantMetadata(),
|
|
928
|
-
providers: sync.data.provider,
|
|
929
|
-
},
|
|
930
|
-
)
|
|
931
|
-
await Clipboard.copy(transcript)
|
|
932
|
-
toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
|
|
933
|
-
} catch {
|
|
934
|
-
toast.show({ message: "Failed to copy session transcript", variant: "error" })
|
|
935
|
-
}
|
|
936
|
-
dialog.clear()
|
|
937
|
-
},
|
|
938
|
-
},
|
|
939
|
-
{
|
|
940
|
-
title: t("tui.command.session.export.title"),
|
|
941
|
-
value: "session.export",
|
|
942
|
-
keybind: "session_export",
|
|
943
|
-
category: "session",
|
|
944
|
-
slash: {
|
|
945
|
-
name: "export",
|
|
946
|
-
},
|
|
947
|
-
onSelect: async (dialog) => {
|
|
948
|
-
try {
|
|
949
|
-
const sessionData = session()
|
|
950
|
-
if (!sessionData) return
|
|
951
|
-
const sessionMessages = messages()
|
|
952
|
-
|
|
953
|
-
const defaultFilename = `session-${sessionData.id.slice(0, 8)}.md`
|
|
954
|
-
|
|
955
|
-
const options = await DialogExportOptions.show(
|
|
956
|
-
dialog,
|
|
957
|
-
defaultFilename,
|
|
958
|
-
showThinking(),
|
|
959
|
-
showDetails(),
|
|
960
|
-
showAssistantMetadata(),
|
|
961
|
-
false,
|
|
962
|
-
)
|
|
963
|
-
|
|
964
|
-
if (options === null) return
|
|
965
|
-
|
|
966
|
-
const transcript = formatTranscript(
|
|
967
|
-
sessionData,
|
|
968
|
-
sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
|
|
969
|
-
{
|
|
970
|
-
thinking: options.thinking,
|
|
971
|
-
toolDetails: options.toolDetails,
|
|
972
|
-
assistantMetadata: options.assistantMetadata,
|
|
973
|
-
providers: sync.data.provider,
|
|
974
|
-
},
|
|
975
|
-
)
|
|
976
|
-
|
|
977
|
-
if (options.openWithoutSaving) {
|
|
978
|
-
// Just open in editor without saving
|
|
979
|
-
await Editor.open({ value: transcript, renderer })
|
|
980
|
-
} else {
|
|
981
|
-
const exportDir = process.cwd()
|
|
982
|
-
const filename = options.filename.trim()
|
|
983
|
-
const filepath = path.join(exportDir, filename)
|
|
984
|
-
|
|
985
|
-
await Filesystem.write(filepath, transcript)
|
|
986
|
-
|
|
987
|
-
// Open with EDITOR if available
|
|
988
|
-
const result = await Editor.open({ value: transcript, renderer })
|
|
989
|
-
if (result !== undefined) {
|
|
990
|
-
await Filesystem.write(filepath, result)
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
toast.show({ message: `Session exported to ${filename}`, variant: "success" })
|
|
994
|
-
}
|
|
995
|
-
} catch {
|
|
996
|
-
toast.show({ message: "Failed to export session", variant: "error" })
|
|
997
|
-
}
|
|
998
|
-
dialog.clear()
|
|
999
|
-
},
|
|
1000
|
-
},
|
|
1001
|
-
{
|
|
1002
|
-
title: t("tui.command.session.child_first.title"),
|
|
1003
|
-
value: "session.child.first",
|
|
1004
|
-
keybind: "session_child_first",
|
|
1005
|
-
category: "session",
|
|
1006
|
-
hidden: true,
|
|
1007
|
-
onSelect: (dialog) => {
|
|
1008
|
-
moveFirstChild()
|
|
1009
|
-
dialog.clear()
|
|
1010
|
-
},
|
|
1011
|
-
},
|
|
1012
|
-
{
|
|
1013
|
-
title: t("tui.command.session.parent.title"),
|
|
1014
|
-
value: "session.parent",
|
|
1015
|
-
keybind: "session_parent",
|
|
1016
|
-
category: "session",
|
|
1017
|
-
hidden: true,
|
|
1018
|
-
enabled: currentAgentID() !== "main" || !!workflowRunID() || !!session()?.parentID,
|
|
1019
|
-
onSelect: (dialog) => {
|
|
1020
|
-
// Workflow page → back to the conversation (parallels agentID → main).
|
|
1021
|
-
if (fullRoute.data.type === "session" && workflowRunID()) {
|
|
1022
|
-
navigate({ ...fullRoute.data, workflowRunID: undefined })
|
|
1023
|
-
dialog.clear()
|
|
1024
|
-
return
|
|
1025
|
-
}
|
|
1026
|
-
// Agent opened FROM a workflow page → back returns to that workflow.
|
|
1027
|
-
if (fullRoute.data.type === "session" && currentAgentID() !== "main" && fromWorkflowRunID()) {
|
|
1028
|
-
navigate({ ...fullRoute.data, agentID: undefined, fromWorkflowRunID: undefined, workflowRunID: fromWorkflowRunID() })
|
|
1029
|
-
dialog.clear()
|
|
1030
|
-
return
|
|
1031
|
-
}
|
|
1032
|
-
if (fullRoute.data.type === "session" && currentAgentID() !== "main") {
|
|
1033
|
-
navigate({ ...fullRoute.data, agentID: undefined })
|
|
1034
|
-
dialog.clear()
|
|
1035
|
-
return
|
|
1036
|
-
}
|
|
1037
|
-
const parentID = session()?.parentID
|
|
1038
|
-
if (parentID) {
|
|
1039
|
-
navigate({
|
|
1040
|
-
type: "session",
|
|
1041
|
-
sessionID: parentID,
|
|
1042
|
-
})
|
|
1043
|
-
}
|
|
1044
|
-
dialog.clear()
|
|
1045
|
-
},
|
|
1046
|
-
},
|
|
1047
|
-
{
|
|
1048
|
-
title: t("tui.command.session.child_next.title"),
|
|
1049
|
-
value: "session.child.next",
|
|
1050
|
-
keybind: "session_child_cycle",
|
|
1051
|
-
category: "session",
|
|
1052
|
-
hidden: true,
|
|
1053
|
-
onSelect: (dialog) => {
|
|
1054
|
-
moveChild(1)
|
|
1055
|
-
dialog.clear()
|
|
1056
|
-
},
|
|
1057
|
-
},
|
|
1058
|
-
{
|
|
1059
|
-
title: t("tui.command.session.child_previous.title"),
|
|
1060
|
-
value: "session.child.previous",
|
|
1061
|
-
keybind: "session_child_cycle_reverse",
|
|
1062
|
-
category: "session",
|
|
1063
|
-
hidden: true,
|
|
1064
|
-
onSelect: (dialog) => {
|
|
1065
|
-
moveChild(-1)
|
|
1066
|
-
dialog.clear()
|
|
1067
|
-
},
|
|
1068
|
-
},
|
|
1069
|
-
])
|
|
1070
|
-
|
|
1071
|
-
const revertInfo = createMemo(() => session()?.revert)
|
|
1072
|
-
const revertMessageID = createMemo(() => revertInfo()?.messageID)
|
|
1073
|
-
|
|
1074
|
-
const revertDiffFiles = createMemo(() => getRevertDiffFiles(revertInfo()?.diff ?? ""))
|
|
1075
|
-
|
|
1076
|
-
const revertRevertedMessages = createMemo(() => {
|
|
1077
|
-
const messageID = revertMessageID()
|
|
1078
|
-
if (!messageID) return []
|
|
1079
|
-
return messages().filter((x) => x.id >= messageID && x.role === "user")
|
|
1080
|
-
})
|
|
1081
|
-
|
|
1082
|
-
const revert = createMemo(() => {
|
|
1083
|
-
const info = revertInfo()
|
|
1084
|
-
if (!info) return
|
|
1085
|
-
if (!info.messageID) return
|
|
1086
|
-
return {
|
|
1087
|
-
messageID: info.messageID,
|
|
1088
|
-
reverted: revertRevertedMessages(),
|
|
1089
|
-
diff: info.diff,
|
|
1090
|
-
diffFiles: revertDiffFiles(),
|
|
1091
|
-
}
|
|
1092
|
-
})
|
|
1093
|
-
|
|
1094
|
-
// snap to bottom when session changes
|
|
1095
|
-
createEffect(on(() => route.sessionID, toBottom))
|
|
1096
|
-
|
|
1097
|
-
return (
|
|
1098
|
-
<context.Provider
|
|
1099
|
-
value={{
|
|
1100
|
-
get width() {
|
|
1101
|
-
return contentWidth()
|
|
1102
|
-
},
|
|
1103
|
-
sessionID: route.sessionID,
|
|
1104
|
-
conceal,
|
|
1105
|
-
thinkingMode,
|
|
1106
|
-
showThinking,
|
|
1107
|
-
showTimestamps,
|
|
1108
|
-
showDetails,
|
|
1109
|
-
showGenericToolOutput,
|
|
1110
|
-
diffWrapMode,
|
|
1111
|
-
providers,
|
|
1112
|
-
sync,
|
|
1113
|
-
tui: tuiConfig,
|
|
1114
|
-
}}
|
|
1115
|
-
>
|
|
1116
|
-
<box flexDirection="row">
|
|
1117
|
-
<box flexGrow={1} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1} onMouse={onWheel}>
|
|
1118
|
-
<Show
|
|
1119
|
-
when={!workflowRunID()}
|
|
1120
|
-
fallback={
|
|
1121
|
-
<WorkflowPage
|
|
1122
|
-
runID={workflowRunID()!}
|
|
1123
|
-
onBack={() => navigate({ ...route, workflowRunID: undefined })}
|
|
1124
|
-
onOpenAgent={(actorID) =>
|
|
1125
|
-
navigate({ ...route, workflowRunID: undefined, agentID: actorID, fromWorkflowRunID: workflowRunID() })
|
|
1126
|
-
}
|
|
1127
|
-
onOpenChild={(childRunID) => navigate({ ...route, workflowRunID: childRunID })}
|
|
1128
|
-
/>
|
|
1129
|
-
}
|
|
1130
|
-
>
|
|
1131
|
-
<Show when={session()}>
|
|
1132
|
-
<scrollbox
|
|
1133
|
-
ref={(r) => (scroll = r)}
|
|
1134
|
-
viewportOptions={{
|
|
1135
|
-
paddingRight: 1,
|
|
1136
|
-
}}
|
|
1137
|
-
verticalScrollbarOptions={{
|
|
1138
|
-
paddingLeft: 1,
|
|
1139
|
-
visible: true,
|
|
1140
|
-
trackOptions: {
|
|
1141
|
-
backgroundColor: scrollbarVisible() ? theme.backgroundElement : theme.background,
|
|
1142
|
-
foregroundColor: scrollbarVisible() ? theme.border : theme.background,
|
|
1143
|
-
},
|
|
1144
|
-
}}
|
|
1145
|
-
stickyScroll={true}
|
|
1146
|
-
stickyStart="bottom"
|
|
1147
|
-
flexGrow={1}
|
|
1148
|
-
scrollAcceleration={scrollAcceleration()}
|
|
1149
|
-
>
|
|
1150
|
-
<box height={1} />
|
|
1151
|
-
<For each={messages()}>
|
|
1152
|
-
{(message, index) => (
|
|
1153
|
-
<Switch>
|
|
1154
|
-
<Match when={message.id === revert()?.messageID}>
|
|
1155
|
-
{(function () {
|
|
1156
|
-
const command = useCommandDialog()
|
|
1157
|
-
const [hover, setHover] = createSignal(false)
|
|
1158
|
-
const dialog = useDialog()
|
|
1159
|
-
|
|
1160
|
-
const handleUnrevert = async () => {
|
|
1161
|
-
const confirmed = await DialogConfirm.show(
|
|
1162
|
-
dialog,
|
|
1163
|
-
"Confirm Redo",
|
|
1164
|
-
"Are you sure you want to restore the reverted messages?",
|
|
1165
|
-
)
|
|
1166
|
-
if (confirmed) {
|
|
1167
|
-
command.trigger("session.redo")
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
return (
|
|
1172
|
-
<box
|
|
1173
|
-
onMouseOver={() => setHover(true)}
|
|
1174
|
-
onMouseOut={() => setHover(false)}
|
|
1175
|
-
onMouseUp={handleUnrevert}
|
|
1176
|
-
marginTop={1}
|
|
1177
|
-
flexShrink={0}
|
|
1178
|
-
border={["left"]}
|
|
1179
|
-
customBorderChars={SplitBorder.customBorderChars}
|
|
1180
|
-
borderColor={theme.backgroundPanel}
|
|
1181
|
-
>
|
|
1182
|
-
<box
|
|
1183
|
-
paddingTop={1}
|
|
1184
|
-
paddingBottom={1}
|
|
1185
|
-
paddingLeft={2}
|
|
1186
|
-
backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel}
|
|
1187
|
-
>
|
|
1188
|
-
<text fg={theme.textMuted}>{revert()!.reverted.length} message reverted</text>
|
|
1189
|
-
<text fg={theme.textMuted}>
|
|
1190
|
-
<span style={{ fg: theme.text }}>{keybind.print("messages_redo")}</span> or /redo to
|
|
1191
|
-
restore
|
|
1192
|
-
</text>
|
|
1193
|
-
<Show when={revert()!.diffFiles?.length}>
|
|
1194
|
-
<box marginTop={1}>
|
|
1195
|
-
<For each={revert()!.diffFiles}>
|
|
1196
|
-
{(file) => (
|
|
1197
|
-
<text fg={theme.text}>
|
|
1198
|
-
{file.filename}
|
|
1199
|
-
<Show when={file.additions > 0}>
|
|
1200
|
-
<span style={{ fg: theme.diffAdded }}> +{file.additions}</span>
|
|
1201
|
-
</Show>
|
|
1202
|
-
<Show when={file.deletions > 0}>
|
|
1203
|
-
<span style={{ fg: theme.diffRemoved }}> -{file.deletions}</span>
|
|
1204
|
-
</Show>
|
|
1205
|
-
</text>
|
|
1206
|
-
)}
|
|
1207
|
-
</For>
|
|
1208
|
-
</box>
|
|
1209
|
-
</Show>
|
|
1210
|
-
</box>
|
|
1211
|
-
</box>
|
|
1212
|
-
)
|
|
1213
|
-
})()}
|
|
1214
|
-
</Match>
|
|
1215
|
-
<Match when={revert()?.messageID && message.id >= revert()!.messageID}>
|
|
1216
|
-
<></>
|
|
1217
|
-
</Match>
|
|
1218
|
-
<Match when={message.role === "user"}>
|
|
1219
|
-
<UserMessage
|
|
1220
|
-
index={index()}
|
|
1221
|
-
onMouseUp={() => {
|
|
1222
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
1223
|
-
dialog.replace(() => (
|
|
1224
|
-
<DialogMessage
|
|
1225
|
-
messageID={message.id}
|
|
1226
|
-
sessionID={route.sessionID}
|
|
1227
|
-
setPrompt={(promptInfo) => prompt?.set(promptInfo)}
|
|
1228
|
-
/>
|
|
1229
|
-
))
|
|
1230
|
-
}}
|
|
1231
|
-
message={message as UserMessage}
|
|
1232
|
-
parts={sync.data.part[message.id] ?? []}
|
|
1233
|
-
pending={pending()}
|
|
1234
|
-
/>
|
|
1235
|
-
</Match>
|
|
1236
|
-
<Match when={message.role === "assistant"}>
|
|
1237
|
-
<AssistantMessage
|
|
1238
|
-
last={lastAssistant()?.id === message.id}
|
|
1239
|
-
message={message as AssistantMessage}
|
|
1240
|
-
parts={sync.data.part[message.id] ?? []}
|
|
1241
|
-
/>
|
|
1242
|
-
</Match>
|
|
1243
|
-
</Switch>
|
|
1244
|
-
)}
|
|
1245
|
-
</For>
|
|
1246
|
-
</scrollbox>
|
|
1247
|
-
<box flexShrink={0}>
|
|
1248
|
-
<Show when={permissions().length > 0}>
|
|
1249
|
-
<PermissionPrompt request={permissions()[0]} />
|
|
1250
|
-
</Show>
|
|
1251
|
-
<Show when={permissions().length === 0 && questions().length > 0}>
|
|
1252
|
-
<QuestionPrompt request={questions()[0]} />
|
|
1253
|
-
</Show>
|
|
1254
|
-
<Show when={session()?.parentID || currentAgentID() !== "main"}>
|
|
1255
|
-
<SubagentFooter />
|
|
1256
|
-
</Show>
|
|
1257
|
-
<Show when={sessionExpired().expired}>
|
|
1258
|
-
<BannerSessionExpired message={sessionExpired().message} />
|
|
1259
|
-
</Show>
|
|
1260
|
-
<Show when={visible()}>
|
|
1261
|
-
<TuiPluginRuntime.Slot
|
|
1262
|
-
name="session_prompt"
|
|
1263
|
-
mode="replace"
|
|
1264
|
-
session_id={route.sessionID}
|
|
1265
|
-
visible={visible()}
|
|
1266
|
-
disabled={disabled() || sessionExpired().expired}
|
|
1267
|
-
on_submit={toBottom}
|
|
1268
|
-
ref={bind}
|
|
1269
|
-
>
|
|
1270
|
-
<Prompt
|
|
1271
|
-
visible={visible()}
|
|
1272
|
-
ref={bind}
|
|
1273
|
-
disabled={disabled() || sessionExpired().expired}
|
|
1274
|
-
onSubmit={() => {
|
|
1275
|
-
toBottom()
|
|
1276
|
-
}}
|
|
1277
|
-
sessionID={route.sessionID}
|
|
1278
|
-
right={<TuiPluginRuntime.Slot name="session_prompt_right" session_id={route.sessionID} />}
|
|
1279
|
-
/>
|
|
1280
|
-
</TuiPluginRuntime.Slot>
|
|
1281
|
-
</Show>
|
|
1282
|
-
</box>
|
|
1283
|
-
</Show>
|
|
1284
|
-
</Show>
|
|
1285
|
-
<Toast />
|
|
1286
|
-
</box>
|
|
1287
|
-
<Show when={sidebarVisible()}>
|
|
1288
|
-
<Switch>
|
|
1289
|
-
<Match when={wide()}>
|
|
1290
|
-
<Sidebar sessionID={route.sessionID} />
|
|
1291
|
-
</Match>
|
|
1292
|
-
<Match when={!wide()}>
|
|
1293
|
-
<box
|
|
1294
|
-
position="absolute"
|
|
1295
|
-
top={0}
|
|
1296
|
-
left={0}
|
|
1297
|
-
right={0}
|
|
1298
|
-
bottom={0}
|
|
1299
|
-
alignItems="flex-end"
|
|
1300
|
-
backgroundColor={RGBA.fromInts(0, 0, 0, 70)}
|
|
1301
|
-
>
|
|
1302
|
-
<Sidebar sessionID={route.sessionID} />
|
|
1303
|
-
</box>
|
|
1304
|
-
</Match>
|
|
1305
|
-
</Switch>
|
|
1306
|
-
</Show>
|
|
1307
|
-
</box>
|
|
1308
|
-
</context.Provider>
|
|
1309
|
-
)
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
const MIME_BADGE: Record<string, string> = {
|
|
1313
|
-
"text/plain": "txt",
|
|
1314
|
-
"image/png": "img",
|
|
1315
|
-
"image/jpeg": "img",
|
|
1316
|
-
"image/gif": "img",
|
|
1317
|
-
"image/webp": "img",
|
|
1318
|
-
"application/pdf": "pdf",
|
|
1319
|
-
"application/x-directory": "dir",
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
function UserMessage(props: {
|
|
1323
|
-
message: UserMessage
|
|
1324
|
-
parts: Part[]
|
|
1325
|
-
onMouseUp: () => void
|
|
1326
|
-
index: number
|
|
1327
|
-
pending?: string
|
|
1328
|
-
}) {
|
|
1329
|
-
const ctx = use()
|
|
1330
|
-
const local = useLocal()
|
|
1331
|
-
const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
|
|
1332
|
-
const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
|
|
1333
|
-
const { theme } = useTheme()
|
|
1334
|
-
const [hover, setHover] = createSignal(false)
|
|
1335
|
-
const queued = createMemo(() => props.pending && props.message.id > props.pending)
|
|
1336
|
-
const color = createMemo(() => local.agent.color(props.message.agent))
|
|
1337
|
-
const queuedFg = createMemo(() => selectedForeground(theme, color()))
|
|
1338
|
-
const metadataVisible = createMemo(() => queued() || ctx.showTimestamps())
|
|
1339
|
-
|
|
1340
|
-
return (
|
|
1341
|
-
<>
|
|
1342
|
-
<Show when={text()}>
|
|
1343
|
-
<box
|
|
1344
|
-
id={props.message.id}
|
|
1345
|
-
border={["left"]}
|
|
1346
|
-
borderColor={color()}
|
|
1347
|
-
customBorderChars={SplitBorder.customBorderChars}
|
|
1348
|
-
marginTop={props.index === 0 ? 0 : 1}
|
|
1349
|
-
>
|
|
1350
|
-
<box
|
|
1351
|
-
onMouseOver={() => {
|
|
1352
|
-
setHover(true)
|
|
1353
|
-
}}
|
|
1354
|
-
onMouseOut={() => {
|
|
1355
|
-
setHover(false)
|
|
1356
|
-
}}
|
|
1357
|
-
onMouseUp={props.onMouseUp}
|
|
1358
|
-
paddingTop={1}
|
|
1359
|
-
paddingBottom={1}
|
|
1360
|
-
paddingLeft={2}
|
|
1361
|
-
backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel}
|
|
1362
|
-
flexShrink={0}
|
|
1363
|
-
>
|
|
1364
|
-
<text fg={theme.text}>{text()?.text}</text>
|
|
1365
|
-
<Show when={files().length}>
|
|
1366
|
-
<box flexDirection="row" paddingBottom={metadataVisible() ? 1 : 0} paddingTop={1} gap={1} flexWrap="wrap">
|
|
1367
|
-
<For each={files()}>
|
|
1368
|
-
{(file) => {
|
|
1369
|
-
const bg = createMemo(() => {
|
|
1370
|
-
if (file.mime.startsWith("image/")) return theme.accent
|
|
1371
|
-
if (file.mime === "application/pdf") return theme.primary
|
|
1372
|
-
return theme.secondary
|
|
1373
|
-
})
|
|
1374
|
-
return (
|
|
1375
|
-
<text fg={theme.text}>
|
|
1376
|
-
<span style={{ bg: bg(), fg: theme.background }}> {MIME_BADGE[file.mime] ?? file.mime} </span>
|
|
1377
|
-
<span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}> {file.filename} </span>
|
|
1378
|
-
</text>
|
|
1379
|
-
)
|
|
1380
|
-
}}
|
|
1381
|
-
</For>
|
|
1382
|
-
</box>
|
|
1383
|
-
</Show>
|
|
1384
|
-
<Show
|
|
1385
|
-
when={queued()}
|
|
1386
|
-
fallback={
|
|
1387
|
-
<Show when={ctx.showTimestamps()}>
|
|
1388
|
-
<text fg={theme.textMuted}>
|
|
1389
|
-
<span style={{ fg: theme.textMuted }}>
|
|
1390
|
-
{Locale.todayTimeOrDateTime(props.message.time.created)}
|
|
1391
|
-
</span>
|
|
1392
|
-
</text>
|
|
1393
|
-
</Show>
|
|
1394
|
-
}
|
|
1395
|
-
>
|
|
1396
|
-
<text fg={theme.textMuted}>
|
|
1397
|
-
<span style={{ bg: color(), fg: queuedFg(), bold: true }}> QUEUED </span>
|
|
1398
|
-
</text>
|
|
1399
|
-
</Show>
|
|
1400
|
-
</box>
|
|
1401
|
-
</box>
|
|
1402
|
-
</Show>
|
|
1403
|
-
</>
|
|
1404
|
-
)
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; last: boolean }) {
|
|
1408
|
-
const ctx = use()
|
|
1409
|
-
const local = useLocal()
|
|
1410
|
-
const { theme } = useTheme()
|
|
1411
|
-
const sync = useSync()
|
|
1412
|
-
const toast = useToast()
|
|
1413
|
-
const renderer = useRenderer()
|
|
1414
|
-
const t = useLanguage().t
|
|
1415
|
-
const [copyHover, setCopyHover] = createSignal(false)
|
|
1416
|
-
const messages = createMemo(() => sync.data.message[props.message.sessionID]?.[props.message.agentID ?? "main"] ?? [])
|
|
1417
|
-
const model = createMemo(() =>
|
|
1418
|
-
props.message.modelID === "sleepy-auto"
|
|
1419
|
-
? t("tui.model.sleepy_auto.name")
|
|
1420
|
-
: Model.name(ctx.providers(), props.message.providerID, props.message.modelID),
|
|
1421
|
-
)
|
|
1422
|
-
|
|
1423
|
-
const final = createMemo(() => {
|
|
1424
|
-
return props.message.finish && props.message.finish !== "tool-calls"
|
|
1425
|
-
})
|
|
1426
|
-
|
|
1427
|
-
const duration = createMemo(() => {
|
|
1428
|
-
if (!final()) return 0
|
|
1429
|
-
if (!props.message.time.completed) return 0
|
|
1430
|
-
const user = messages().find((x) => x.role === "user" && x.id === props.message.parentID)
|
|
1431
|
-
if (!user || !user.time) return 0
|
|
1432
|
-
return props.message.time.completed - user.time.created
|
|
1433
|
-
})
|
|
1434
|
-
|
|
1435
|
-
const keybind = useKeybind()
|
|
1436
|
-
|
|
1437
|
-
const handleCopy = () => {
|
|
1438
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
1439
|
-
const text = props.parts
|
|
1440
|
-
.filter((p) => p.type === "text")
|
|
1441
|
-
.map((p) => (p as TextPart).text)
|
|
1442
|
-
.join("\n")
|
|
1443
|
-
.trim()
|
|
1444
|
-
if (!text) return
|
|
1445
|
-
Clipboard.copy(text)
|
|
1446
|
-
.then(() => toast.show({ message: t("tui.toast.copied_to_clipboard"), variant: "success" }))
|
|
1447
|
-
.catch(() => toast.show({ message: "Failed to copy to clipboard", variant: "error" }))
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// Goal judge verdict for this specific turn, if the stop-condition judge
|
|
1451
|
-
// evaluated it. Rendered as a foldable per-turn marker so the user can trace
|
|
1452
|
-
// back which turn failed the check — without polluting the message stream.
|
|
1453
|
-
const verdict = createMemo(() => sync.data.session_goal?.[props.message.sessionID]?.verdicts?.[props.message.id])
|
|
1454
|
-
const [verdictOpen, setVerdictOpen] = createSignal(false)
|
|
1455
|
-
const verdictMark = createMemo(() => {
|
|
1456
|
-
const v = verdict()
|
|
1457
|
-
if (!v) return undefined
|
|
1458
|
-
if (v.error) return { icon: "!", fg: theme.textMuted, label: "Judge: error (stopped)" }
|
|
1459
|
-
if (v.ok) return { icon: "✓", fg: theme.success, label: "Judge: met" }
|
|
1460
|
-
if (v.impossible) return { icon: "⊘", fg: theme.error, label: "Judge: impossible" }
|
|
1461
|
-
return { icon: "⟳", fg: theme.warning, label: `Judge [round ${v.attempt}]: not met` }
|
|
1462
|
-
})
|
|
1463
|
-
|
|
1464
|
-
// Both the `actor` and `workflow` tools spawn agents that register as
|
|
1465
|
-
// subagents in this session, so the `session_child_first` keybind opens the
|
|
1466
|
-
// Subagents panel for either. Advertise it with copy matching the tool that
|
|
1467
|
-
// produced the message; a mixed message falls back to the generic subagent
|
|
1468
|
-
// wording.
|
|
1469
|
-
const hasActorPart = createMemo(() => props.parts.some((x) => x.type === "tool" && x.tool === "actor"))
|
|
1470
|
-
const hasWorkflowPart = createMemo(() => props.parts.some((x) => x.type === "tool" && x.tool === "workflow"))
|
|
1471
|
-
|
|
1472
|
-
return (
|
|
1473
|
-
<>
|
|
1474
|
-
<For each={props.parts}>
|
|
1475
|
-
{(part, index) => {
|
|
1476
|
-
// The StructuredOutput tool call is the mechanism that produces
|
|
1477
|
-
// message.structured; we render that value as a dedicated colored block
|
|
1478
|
-
// below, so skip the redundant gray one-liner tool part here.
|
|
1479
|
-
if (part.type === "tool" && part.tool === "StructuredOutput") return null
|
|
1480
|
-
const component = createMemo(() => PART_MAPPING[part.type as keyof typeof PART_MAPPING])
|
|
1481
|
-
return (
|
|
1482
|
-
<Show when={component()}>
|
|
1483
|
-
<Dynamic
|
|
1484
|
-
last={index() === props.parts.length - 1}
|
|
1485
|
-
component={component()}
|
|
1486
|
-
part={part as any}
|
|
1487
|
-
message={props.message}
|
|
1488
|
-
/>
|
|
1489
|
-
</Show>
|
|
1490
|
-
)
|
|
1491
|
-
}}
|
|
1492
|
-
</For>
|
|
1493
|
-
<Show when={props.message.structured !== undefined && props.message.structured !== null}>
|
|
1494
|
-
<StructuredOutput value={props.message.structured} />
|
|
1495
|
-
</Show>
|
|
1496
|
-
<Show when={hasActorPart() || hasWorkflowPart()}>
|
|
1497
|
-
<box paddingTop={1} paddingLeft={3}>
|
|
1498
|
-
<text fg={theme.text}>
|
|
1499
|
-
{keybind.print("session_child_first")}
|
|
1500
|
-
<span style={{ fg: theme.textMuted }}>{hasWorkflowPart() ? " view workflow agents" : " view subagents"}</span>
|
|
1501
|
-
</text>
|
|
1502
|
-
</box>
|
|
1503
|
-
</Show>
|
|
1504
|
-
<Show when={props.message.error && props.message.error.name !== "MessageAbortedError"}>
|
|
1505
|
-
<ErrorBlock error={props.message.error!} />
|
|
1506
|
-
</Show>
|
|
1507
|
-
<Switch>
|
|
1508
|
-
<Match when={props.last || final() || props.message.error?.name === "MessageAbortedError"}>
|
|
1509
|
-
<box paddingLeft={3} flexDirection="row" justifyContent="space-between" marginTop={1}>
|
|
1510
|
-
<text>
|
|
1511
|
-
<span
|
|
1512
|
-
style={{
|
|
1513
|
-
fg:
|
|
1514
|
-
props.message.error?.name === "MessageAbortedError"
|
|
1515
|
-
? theme.textMuted
|
|
1516
|
-
: local.agent.color(props.message.agent),
|
|
1517
|
-
}}
|
|
1518
|
-
>
|
|
1519
|
-
▣{" "}
|
|
1520
|
-
</span>{" "}
|
|
1521
|
-
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
|
|
1522
|
-
<span style={{ fg: theme.textMuted }}> · {model()}</span>
|
|
1523
|
-
<Show when={duration()}>
|
|
1524
|
-
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
|
|
1525
|
-
</Show>
|
|
1526
|
-
<Show when={props.message.error?.name === "MessageAbortedError"}>
|
|
1527
|
-
<span style={{ fg: theme.textMuted }}> · interrupted</span>
|
|
1528
|
-
</Show>
|
|
1529
|
-
</text>
|
|
1530
|
-
<Show when={props.message.time.completed}>
|
|
1531
|
-
<box
|
|
1532
|
-
onMouseOver={() => setCopyHover(true)}
|
|
1533
|
-
onMouseOut={() => setCopyHover(false)}
|
|
1534
|
-
onMouseUp={handleCopy}
|
|
1535
|
-
>
|
|
1536
|
-
<text fg={copyHover() ? theme.text : theme.textMuted}>⎘ copy</text>
|
|
1537
|
-
</box>
|
|
1538
|
-
</Show>
|
|
1539
|
-
</box>
|
|
1540
|
-
</Match>
|
|
1541
|
-
</Switch>
|
|
1542
|
-
<Show when={verdictMark()}>
|
|
1543
|
-
{(mark) => (
|
|
1544
|
-
<box paddingLeft={3} onMouseUp={() => setVerdictOpen((x) => !x)}>
|
|
1545
|
-
<text>
|
|
1546
|
-
<span style={{ fg: theme.textMuted }}>{verdictOpen() ? "▼" : "▶"} </span>
|
|
1547
|
-
<span style={{ fg: mark().fg }}>
|
|
1548
|
-
{mark().icon} {mark().label}
|
|
1549
|
-
</span>
|
|
1550
|
-
</text>
|
|
1551
|
-
<Show when={verdictOpen()}>
|
|
1552
|
-
<box paddingLeft={2}>
|
|
1553
|
-
<text fg={theme.textMuted} wrapMode="word">
|
|
1554
|
-
{verdict()!.reason}
|
|
1555
|
-
</text>
|
|
1556
|
-
</box>
|
|
1557
|
-
</Show>
|
|
1558
|
-
</box>
|
|
1559
|
-
)}
|
|
1560
|
-
</Show>
|
|
1561
|
-
</>
|
|
1562
|
-
)
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
const PART_MAPPING = {
|
|
1566
|
-
text: TextPart,
|
|
1567
|
-
tool: ToolPart,
|
|
1568
|
-
reasoning: ReasoningPart,
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
type MessageError = NonNullable<AssistantMessage["error"]>
|
|
1572
|
-
|
|
1573
|
-
function errorBody(error: MessageError): string {
|
|
1574
|
-
if (error.name === "MessageOutputLengthError") return "Output length limit reached"
|
|
1575
|
-
return (error.data as { message?: string }).message ?? "Unknown error"
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
function errorMeta(error: MessageError): string | undefined {
|
|
1579
|
-
if (error.name === "APIError") {
|
|
1580
|
-
const parts: string[] = []
|
|
1581
|
-
if (error.data.statusCode !== undefined) parts.push(`status ${error.data.statusCode}`)
|
|
1582
|
-
parts.push(error.data.isRetryable ? "retryable" : "non-retryable")
|
|
1583
|
-
return parts.join(" · ")
|
|
1584
|
-
}
|
|
1585
|
-
if (error.name === "ProviderAuthError") return `provider: ${error.data.providerID}`
|
|
1586
|
-
if (error.name === "StructuredOutputError") return `retries: ${error.data.retries}`
|
|
1587
|
-
return undefined
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
function ErrorBlock(props: { error: MessageError }) {
|
|
1591
|
-
const { theme } = useTheme()
|
|
1592
|
-
const meta = createMemo(() => errorMeta(props.error))
|
|
1593
|
-
return (
|
|
1594
|
-
<box flexDirection="column" paddingLeft={3} marginTop={1}>
|
|
1595
|
-
<text fg={theme.error} wrapMode="word">
|
|
1596
|
-
<span style={{ fg: theme.error }}>✗ </span>
|
|
1597
|
-
{errorBody(props.error)}
|
|
1598
|
-
</text>
|
|
1599
|
-
<Show when={meta()}>
|
|
1600
|
-
<box paddingLeft={3}>
|
|
1601
|
-
<text fg={theme.textMuted} wrapMode="word">
|
|
1602
|
-
{meta()}
|
|
1603
|
-
</text>
|
|
1604
|
-
</box>
|
|
1605
|
-
</Show>
|
|
1606
|
-
</box>
|
|
1607
|
-
)
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// Structured output is a message-level field (AssistantMessage.structured), not a
|
|
1611
|
-
// part, so the parts loop never shows it. Agents called with a schema (common in
|
|
1612
|
-
// workflows) put their whole answer here — render it as syntax-highlighted JSON so
|
|
1613
|
-
// it's not invisible. Collapsible for large payloads.
|
|
1614
|
-
function StructuredOutput(props: { value: unknown }) {
|
|
1615
|
-
const { theme, syntax } = useTheme()
|
|
1616
|
-
const [collapsed, setCollapsed] = createSignal(false)
|
|
1617
|
-
const json = createMemo(() => {
|
|
1618
|
-
try {
|
|
1619
|
-
return JSON.stringify(props.value, null, 2)
|
|
1620
|
-
} catch {
|
|
1621
|
-
return String(props.value)
|
|
1622
|
-
}
|
|
1623
|
-
})
|
|
1624
|
-
const lineCount = createMemo(() => json().split("\n").length)
|
|
1625
|
-
const overflow = createMemo(() => lineCount() > 20)
|
|
1626
|
-
const shown = createMemo(() => (collapsed() ? json().split("\n").slice(0, 20).join("\n") + "\n…" : json()))
|
|
1627
|
-
return (
|
|
1628
|
-
<box paddingLeft={3} marginTop={1} flexDirection="column" flexShrink={0}>
|
|
1629
|
-
<box flexDirection="row" gap={1} onMouseUp={() => overflow() && setCollapsed((p) => !p)}>
|
|
1630
|
-
<text fg={theme.accent} attributes={TextAttributes.BOLD}>
|
|
1631
|
-
⊟ structured output
|
|
1632
|
-
</text>
|
|
1633
|
-
<Show when={overflow()}>
|
|
1634
|
-
<text fg={theme.textMuted}>
|
|
1635
|
-
· {lineCount()} lines{collapsed() ? " · click to expand" : ""}
|
|
1636
|
-
</text>
|
|
1637
|
-
</Show>
|
|
1638
|
-
</box>
|
|
1639
|
-
<box marginTop={1}>
|
|
1640
|
-
<code filetype="json" drawUnstyledText={false} syntaxStyle={syntax()} content={shown()} fg={theme.text} />
|
|
1641
|
-
</box>
|
|
1642
|
-
</box>
|
|
1643
|
-
)
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
|
|
1647
|
-
const { theme, subtleSyntax } = useTheme()
|
|
1648
|
-
const ctx = use()
|
|
1649
|
-
const [expanded, setExpanded] = createSignal(false)
|
|
1650
|
-
|
|
1651
|
-
const content = createMemo(() => {
|
|
1652
|
-
return props.part.text.replace("[REDACTED]", "").trim()
|
|
1653
|
-
})
|
|
1654
|
-
const isDone = createMemo(() => props.part.time.end !== undefined)
|
|
1655
|
-
const inMinimal = createMemo(() => ctx.thinkingMode() === "hide")
|
|
1656
|
-
const duration = createMemo(() => {
|
|
1657
|
-
const end = props.part.time.end
|
|
1658
|
-
return end === undefined ? 0 : Math.max(0, end - props.part.time.start)
|
|
1659
|
-
})
|
|
1660
|
-
const summary = createMemo(() => reasoningSummary(content()))
|
|
1661
|
-
|
|
1662
|
-
const toggle = () => {
|
|
1663
|
-
if (!inMinimal()) return
|
|
1664
|
-
setExpanded((prev) => !prev)
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
return (
|
|
1668
|
-
<Show when={content()}>
|
|
1669
|
-
<box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexDirection="column" flexShrink={0}>
|
|
1670
|
-
<box onMouseUp={toggle}>
|
|
1671
|
-
<ReasoningHeader
|
|
1672
|
-
toggleable={inMinimal()}
|
|
1673
|
-
open={!inMinimal() || expanded()}
|
|
1674
|
-
done={isDone()}
|
|
1675
|
-
title={summary().title}
|
|
1676
|
-
duration={isDone() ? Locale.duration(duration()) : undefined}
|
|
1677
|
-
/>
|
|
1678
|
-
</box>
|
|
1679
|
-
<Show when={(!inMinimal() || expanded()) && summary().body}>
|
|
1680
|
-
<box paddingLeft={inMinimal() ? 2 : 0} marginTop={1}>
|
|
1681
|
-
<code
|
|
1682
|
-
filetype="markdown"
|
|
1683
|
-
drawUnstyledText={false}
|
|
1684
|
-
streaming={true}
|
|
1685
|
-
syntaxStyle={subtleSyntax()}
|
|
1686
|
-
content={summary().body}
|
|
1687
|
-
conceal={ctx.conceal()}
|
|
1688
|
-
fg={theme.textMuted}
|
|
1689
|
-
/>
|
|
1690
|
-
</box>
|
|
1691
|
-
</Show>
|
|
1692
|
-
</box>
|
|
1693
|
-
</Show>
|
|
1694
|
-
)
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
function ReasoningHeader(props: {
|
|
1698
|
-
toggleable: boolean
|
|
1699
|
-
open: boolean
|
|
1700
|
-
done: boolean
|
|
1701
|
-
title: string | null
|
|
1702
|
-
duration?: string
|
|
1703
|
-
}) {
|
|
1704
|
-
const { theme } = useTheme()
|
|
1705
|
-
const fg = () =>
|
|
1706
|
-
props.open
|
|
1707
|
-
? RGBA.fromValues(theme.warning.r, theme.warning.g, theme.warning.b, theme.thinkingOpacity)
|
|
1708
|
-
: theme.warning
|
|
1709
|
-
|
|
1710
|
-
return (
|
|
1711
|
-
<Switch>
|
|
1712
|
-
<Match when={!props.done}>
|
|
1713
|
-
<box flexDirection="row">
|
|
1714
|
-
<Spinner color={fg()}>{props.title ? "Thinking: " + props.title : "Thinking"}</Spinner>
|
|
1715
|
-
</box>
|
|
1716
|
-
</Match>
|
|
1717
|
-
<Match when={true}>
|
|
1718
|
-
<text fg={fg()} wrapMode="none">
|
|
1719
|
-
<Show when={props.toggleable}>
|
|
1720
|
-
<span>{props.open ? "- " : "+ "}</span>
|
|
1721
|
-
</Show>
|
|
1722
|
-
<span>Thought</span>
|
|
1723
|
-
<Show when={props.title || props.duration}>
|
|
1724
|
-
<span>: </span>
|
|
1725
|
-
</Show>
|
|
1726
|
-
<Show when={props.title}>
|
|
1727
|
-
<span>{props.title}</span>
|
|
1728
|
-
</Show>
|
|
1729
|
-
<Show when={props.duration}>
|
|
1730
|
-
<span>
|
|
1731
|
-
{props.title ? " · " : ""}
|
|
1732
|
-
{props.duration}
|
|
1733
|
-
</span>
|
|
1734
|
-
</Show>
|
|
1735
|
-
</text>
|
|
1736
|
-
</Match>
|
|
1737
|
-
</Switch>
|
|
1738
|
-
)
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function TextPart(props: { last: boolean; part: TextPart; message: AssistantMessage }) {
|
|
1742
|
-
const ctx = use()
|
|
1743
|
-
const { theme, syntax } = useTheme()
|
|
1744
|
-
return (
|
|
1745
|
-
<Show when={props.part.text.trim()}>
|
|
1746
|
-
<box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexShrink={0}>
|
|
1747
|
-
<Switch>
|
|
1748
|
-
<Match when={Flag.SLEEPYCODE_EXPERIMENTAL_MARKDOWN}>
|
|
1749
|
-
<markdown
|
|
1750
|
-
syntaxStyle={syntax()}
|
|
1751
|
-
streaming={true}
|
|
1752
|
-
content={props.part.text.trim()}
|
|
1753
|
-
conceal={ctx.conceal()}
|
|
1754
|
-
fg={theme.markdownText}
|
|
1755
|
-
bg={theme.background}
|
|
1756
|
-
/>
|
|
1757
|
-
</Match>
|
|
1758
|
-
<Match when={!Flag.SLEEPYCODE_EXPERIMENTAL_MARKDOWN}>
|
|
1759
|
-
<code
|
|
1760
|
-
filetype="markdown"
|
|
1761
|
-
drawUnstyledText={false}
|
|
1762
|
-
streaming={true}
|
|
1763
|
-
syntaxStyle={syntax()}
|
|
1764
|
-
content={props.part.text.trim()}
|
|
1765
|
-
conceal={ctx.conceal()}
|
|
1766
|
-
fg={theme.text}
|
|
1767
|
-
/>
|
|
1768
|
-
</Match>
|
|
1769
|
-
</Switch>
|
|
1770
|
-
</box>
|
|
1771
|
-
</Show>
|
|
1772
|
-
)
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
// Pending messages moved to individual tool pending functions
|
|
1776
|
-
|
|
1777
|
-
function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMessage }) {
|
|
1778
|
-
const ctx = use()
|
|
1779
|
-
const sync = useSync()
|
|
1780
|
-
|
|
1781
|
-
// Hide tool if showDetails is false and tool completed successfully
|
|
1782
|
-
const shouldHide = createMemo(() => {
|
|
1783
|
-
if (ctx.showDetails()) return false
|
|
1784
|
-
if (props.part.state.status !== "completed") return false
|
|
1785
|
-
return true
|
|
1786
|
-
})
|
|
1787
|
-
|
|
1788
|
-
const toolprops = {
|
|
1789
|
-
get metadata() {
|
|
1790
|
-
return props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
|
|
1791
|
-
},
|
|
1792
|
-
get input() {
|
|
1793
|
-
return props.part.state.input ?? {}
|
|
1794
|
-
},
|
|
1795
|
-
get output() {
|
|
1796
|
-
return props.part.state.status === "completed" ? props.part.state.output : undefined
|
|
1797
|
-
},
|
|
1798
|
-
get permission() {
|
|
1799
|
-
const permissions = sync.data.permission[props.message.sessionID] ?? []
|
|
1800
|
-
const permissionIndex = permissions.findIndex((x) => x.tool?.callID === props.part.callID)
|
|
1801
|
-
return permissions[permissionIndex]
|
|
1802
|
-
},
|
|
1803
|
-
get tool() {
|
|
1804
|
-
return props.part.tool
|
|
1805
|
-
},
|
|
1806
|
-
get part() {
|
|
1807
|
-
return props.part
|
|
1808
|
-
},
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
return (
|
|
1812
|
-
<Show when={!shouldHide()}>
|
|
1813
|
-
<Switch>
|
|
1814
|
-
<Match when={props.part.tool === "bash"}>
|
|
1815
|
-
<Bash {...toolprops} />
|
|
1816
|
-
</Match>
|
|
1817
|
-
<Match when={props.part.tool === "glob"}>
|
|
1818
|
-
<Glob {...toolprops} />
|
|
1819
|
-
</Match>
|
|
1820
|
-
<Match when={props.part.tool === "read"}>
|
|
1821
|
-
<Read {...toolprops} />
|
|
1822
|
-
</Match>
|
|
1823
|
-
<Match when={props.part.tool === "grep"}>
|
|
1824
|
-
<Grep {...toolprops} />
|
|
1825
|
-
</Match>
|
|
1826
|
-
<Match when={props.part.tool === "webfetch"}>
|
|
1827
|
-
<WebFetch {...toolprops} />
|
|
1828
|
-
</Match>
|
|
1829
|
-
<Match when={props.part.tool === "codesearch"}>
|
|
1830
|
-
<CodeSearch {...toolprops} />
|
|
1831
|
-
</Match>
|
|
1832
|
-
<Match when={props.part.tool === "websearch"}>
|
|
1833
|
-
<WebSearch {...toolprops} />
|
|
1834
|
-
</Match>
|
|
1835
|
-
<Match when={props.part.tool === "write"}>
|
|
1836
|
-
<Write {...toolprops} />
|
|
1837
|
-
</Match>
|
|
1838
|
-
<Match when={props.part.tool === "edit"}>
|
|
1839
|
-
<Edit {...toolprops} />
|
|
1840
|
-
</Match>
|
|
1841
|
-
<Match when={props.part.tool === "actor"}>
|
|
1842
|
-
<Task {...toolprops} />
|
|
1843
|
-
</Match>
|
|
1844
|
-
<Match when={props.part.tool === "task"}>
|
|
1845
|
-
<WorkItemTask {...toolprops} />
|
|
1846
|
-
</Match>
|
|
1847
|
-
<Match when={props.part.tool === "apply_patch"}>
|
|
1848
|
-
<ApplyPatch {...toolprops} />
|
|
1849
|
-
</Match>
|
|
1850
|
-
<Match when={props.part.tool === "question"}>
|
|
1851
|
-
<Question {...toolprops} />
|
|
1852
|
-
</Match>
|
|
1853
|
-
<Match when={props.part.tool === "skill"}>
|
|
1854
|
-
<Skill {...toolprops} />
|
|
1855
|
-
</Match>
|
|
1856
|
-
<Match when={props.part.tool === "workflow"}>
|
|
1857
|
-
<Workflow {...toolprops} />
|
|
1858
|
-
</Match>
|
|
1859
|
-
<Match when={props.part.tool === "plan_exit"}>
|
|
1860
|
-
<PlanExit {...toolprops} />
|
|
1861
|
-
</Match>
|
|
1862
|
-
<Match when={true}>
|
|
1863
|
-
<GenericTool {...toolprops} />
|
|
1864
|
-
</Match>
|
|
1865
|
-
</Switch>
|
|
1866
|
-
</Show>
|
|
1867
|
-
)
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
type ToolProps<T> = {
|
|
1871
|
-
input: Partial<Tool.InferParameters<T>>
|
|
1872
|
-
metadata: Partial<Tool.InferMetadata<T>>
|
|
1873
|
-
permission: Record<string, any>
|
|
1874
|
-
tool: string
|
|
1875
|
-
output?: string
|
|
1876
|
-
part: ToolPart
|
|
1877
|
-
}
|
|
1878
|
-
function PlanExit(props: ToolProps<any>) {
|
|
1879
|
-
const { theme } = useTheme()
|
|
1880
|
-
const dismissed = createMemo(
|
|
1881
|
-
() => props.part.state.status === "completed" && props.part.state.metadata?.switched === false,
|
|
1882
|
-
)
|
|
1883
|
-
const feedback = createMemo(() => (dismissed() ? props.metadata.feedback : undefined))
|
|
1884
|
-
|
|
1885
|
-
return (
|
|
1886
|
-
<>
|
|
1887
|
-
<InlineTool icon="⚙" pending="Asking..." complete={true} part={props.part} dismissed={dismissed()}>
|
|
1888
|
-
plan_exit
|
|
1889
|
-
</InlineTool>
|
|
1890
|
-
<Show when={feedback()}>
|
|
1891
|
-
<box paddingLeft={6}>
|
|
1892
|
-
<text fg={theme.textMuted}>{feedback()}</text>
|
|
1893
|
-
</box>
|
|
1894
|
-
</Show>
|
|
1895
|
-
</>
|
|
1896
|
-
)
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
function GenericTool(props: ToolProps<any>) {
|
|
1900
|
-
const { theme } = useTheme()
|
|
1901
|
-
const ctx = use()
|
|
1902
|
-
const output = createMemo(() => props.output?.trim() ?? "")
|
|
1903
|
-
const [expanded, setExpanded] = createSignal(false)
|
|
1904
|
-
const lines = createMemo(() => output().split("\n"))
|
|
1905
|
-
const maxLines = 3
|
|
1906
|
-
const overflow = createMemo(() => lines().length > maxLines)
|
|
1907
|
-
const limited = createMemo(() => {
|
|
1908
|
-
if (expanded() || !overflow()) return output()
|
|
1909
|
-
return [...lines().slice(0, maxLines), "…"].join("\n")
|
|
1910
|
-
})
|
|
1911
|
-
|
|
1912
|
-
return (
|
|
1913
|
-
<Show
|
|
1914
|
-
when={props.output && ctx.showGenericToolOutput()}
|
|
1915
|
-
fallback={
|
|
1916
|
-
<InlineTool icon="⚙" pending="Writing command..." complete={true} part={props.part}>
|
|
1917
|
-
{props.tool} {input(props.input)}
|
|
1918
|
-
</InlineTool>
|
|
1919
|
-
}
|
|
1920
|
-
>
|
|
1921
|
-
<BlockTool
|
|
1922
|
-
title={`# ${props.tool} ${input(props.input)}`}
|
|
1923
|
-
part={props.part}
|
|
1924
|
-
onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
|
|
1925
|
-
>
|
|
1926
|
-
<box gap={1}>
|
|
1927
|
-
<text fg={theme.text}>{limited()}</text>
|
|
1928
|
-
<Show when={overflow()}>
|
|
1929
|
-
<text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text>
|
|
1930
|
-
</Show>
|
|
1931
|
-
</box>
|
|
1932
|
-
</BlockTool>
|
|
1933
|
-
</Show>
|
|
1934
|
-
)
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
// Inline renderer for the work-item `task` tool (distinct from <Task>, which
|
|
1938
|
-
// renders the subagent-spawning `actor` tool). Shows the operation as a concise
|
|
1939
|
-
// one-liner derived from the nested `{ operation: { action } }` discriminator,
|
|
1940
|
-
// so task create/start/done don't fall through to GenericTool's raw-JSON dump.
|
|
1941
|
-
function WorkItemTask(props: ToolProps<typeof TaskTool>) {
|
|
1942
|
-
const summary = createMemo(() => {
|
|
1943
|
-
const op = (props.input as { operation?: Record<string, any> }).operation
|
|
1944
|
-
if (!op || typeof op !== "object") return "task"
|
|
1945
|
-
const verb = typeof op.action === "string" ? op.action : "task"
|
|
1946
|
-
if (verb === "create") return op.summary ? `create "${op.summary}"` : "create"
|
|
1947
|
-
if (verb === "list") return op.status ? `list ${op.status}` : "list"
|
|
1948
|
-
if (op.id) return `${verb} ${op.id}`
|
|
1949
|
-
return verb
|
|
1950
|
-
})
|
|
1951
|
-
return (
|
|
1952
|
-
<InlineTool icon="#" pending="Updating tasks..." complete={true} part={props.part}>
|
|
1953
|
-
task {summary()}
|
|
1954
|
-
</InlineTool>
|
|
1955
|
-
)
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
// Inline renderer for the dynamic-workflow `workflow` tool. The "run" op blocks
|
|
1959
|
-
// until terminal and streams a transcript (phase transitions + log() messages)
|
|
1960
|
-
// into part-state metadata via ctx.metadata; the tool's `Workflow` view here
|
|
1961
|
-
// reads metadata.transcript reactively (each ctx.metadata call fires a
|
|
1962
|
-
// message.part.delta) and renders it as multi-line chat content alongside the
|
|
1963
|
-
// live header from sync.data.workflow[runID]. That way phase/log events show
|
|
1964
|
-
// up in the main agent's conversation as the workflow runs, not as a single
|
|
1965
|
-
// silent line that only updates once the run finishes.
|
|
1966
|
-
function Workflow(props: ToolProps<typeof WorkflowTool>) {
|
|
1967
|
-
const sync = useSync()
|
|
1968
|
-
const fullRoute = useRoute()
|
|
1969
|
-
|
|
1970
|
-
const operation = createMemo(() => {
|
|
1971
|
-
const op = (props.input as { operation?: string }).operation
|
|
1972
|
-
return typeof op === "string" ? op : "run"
|
|
1973
|
-
})
|
|
1974
|
-
|
|
1975
|
-
const runID = createMemo(
|
|
1976
|
-
() => (props.metadata.runID as string | undefined) ?? (props.input as { run_id?: string }).run_id,
|
|
1977
|
-
)
|
|
1978
|
-
|
|
1979
|
-
const run = createMemo(() => {
|
|
1980
|
-
const id = runID()
|
|
1981
|
-
if (!id) return undefined
|
|
1982
|
-
return sync.data.workflow[id]
|
|
1983
|
-
})
|
|
1984
|
-
|
|
1985
|
-
// Spinner is true while EITHER side reports running — the tool part stays
|
|
1986
|
-
// running until execute() returns (the whole workflow duration, since we
|
|
1987
|
-
// block), and the bus-fed run row independently reports "running" until the
|
|
1988
|
-
// workflow.finished event lands. Either signal alone is enough.
|
|
1989
|
-
const isRunning = createMemo(() => {
|
|
1990
|
-
if (props.part.state.status === "running") return true
|
|
1991
|
-
const r = run()
|
|
1992
|
-
return r?.status === "running"
|
|
1993
|
-
})
|
|
1994
|
-
|
|
1995
|
-
const transcript = createMemo(() => {
|
|
1996
|
-
const t = (props.metadata as { transcript?: { kind: "phase" | "log"; text: string }[] }).transcript
|
|
1997
|
-
return Array.isArray(t) ? t : []
|
|
1998
|
-
})
|
|
1999
|
-
|
|
2000
|
-
const name = createMemo(() => run()?.name ?? (props.input as { name?: string }).name ?? "inline")
|
|
2001
|
-
const status = createMemo(() => run()?.status ?? (props.metadata.status as string | undefined))
|
|
2002
|
-
|
|
2003
|
-
// Counters/phase prefer the live metadata streamed by the tool's 250ms flush
|
|
2004
|
-
// loop, falling back to the bus run row. The bus row only learns counters via
|
|
2005
|
-
// loadWorkflows polling (which only the /workflows dialog runs), so during a run
|
|
2006
|
-
// the inline panel would otherwise sit at 0✓ 0✗ 0⟳ — the streamed metadata is
|
|
2007
|
-
// the authoritative live source for this in-conversation view.
|
|
2008
|
-
const counters = createMemo(() => {
|
|
2009
|
-
const m = (props.metadata as { counters?: { running: number; succeeded: number; failed: number } }).counters
|
|
2010
|
-
if (m) return m
|
|
2011
|
-
const r = run()
|
|
2012
|
-
return r ? { running: r.running, succeeded: r.succeeded, failed: r.failed } : undefined
|
|
2013
|
-
})
|
|
2014
|
-
const currentPhase = createMemo(
|
|
2015
|
-
() => (props.metadata as { currentPhase?: string }).currentPhase ?? run()?.currentPhase,
|
|
2016
|
-
)
|
|
2017
|
-
|
|
2018
|
-
// Non-"run" ops (status/wait/cancel/resume) are one-shot control calls with no
|
|
2019
|
-
// live transcript — keep them as a compact inline line.
|
|
2020
|
-
return (
|
|
2021
|
-
<Show when={operation() === "run"} fallback={
|
|
2022
|
-
<InlineTool icon="⚡" spinner={isRunning()} pending="Starting workflow..." complete={true} part={props.part}>
|
|
2023
|
-
{`workflow ${operation()}${runID() ? ` ${runID()}` : ""}`}
|
|
2024
|
-
</InlineTool>
|
|
2025
|
-
}>
|
|
2026
|
-
<WorkflowPanel
|
|
2027
|
-
name={name()}
|
|
2028
|
-
status={status()}
|
|
2029
|
-
counters={counters()}
|
|
2030
|
-
currentPhase={currentPhase()}
|
|
2031
|
-
transcript={transcript()}
|
|
2032
|
-
running={isRunning()}
|
|
2033
|
-
part={props.part}
|
|
2034
|
-
onOpen={
|
|
2035
|
-
runID() && fullRoute.data.type === "session"
|
|
2036
|
-
? () => {
|
|
2037
|
-
const d = fullRoute.data
|
|
2038
|
-
if (d.type === "session") fullRoute.navigate({ ...d, workflowRunID: runID() })
|
|
2039
|
-
}
|
|
2040
|
-
: undefined
|
|
2041
|
-
}
|
|
2042
|
-
/>
|
|
2043
|
-
</Show>
|
|
2044
|
-
)
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
// Bold panel for a `workflow run`. The transcript (phase + log lines streamed
|
|
2048
|
-
// every 250ms into part metadata) is the run's live activity — agents spawning,
|
|
2049
|
-
// per-source hits, facts checked. The old renderer dumped it all as one muted-
|
|
2050
|
-
// gray InlineTool blob, so a busy run read as "stuck". Here phases are bold
|
|
2051
|
-
// accent section headers, logs render in readable text, and a running run shows
|
|
2052
|
-
// a spinner on its current phase so progress is always visible. Bounded to the
|
|
2053
|
-
// last N lines in the conversation flow; full history lives in the detail dialog.
|
|
2054
|
-
const WORKFLOW_PANEL_TAIL = 12
|
|
2055
|
-
|
|
2056
|
-
// WorkflowPage is conditionally rendered (fallback of the conversation Show), so it
|
|
2057
|
-
// fully unmounts when you navigate into a subagent and remounts on return — which
|
|
2058
|
-
// would reset its scrollbox to the top. Remember the last scroll offset per runID
|
|
2059
|
-
// here so returning restores the position, like the persistent conversation scroll.
|
|
2060
|
-
const workflowScrollByRun = new Map<string, number>()
|
|
2061
|
-
function WorkflowPanel(props: {
|
|
2062
|
-
name: string
|
|
2063
|
-
status?: string
|
|
2064
|
-
counters?: { succeeded: number; failed: number; running: number }
|
|
2065
|
-
currentPhase?: string
|
|
2066
|
-
transcript: { kind: "phase" | "log"; text: string }[]
|
|
2067
|
-
running: boolean
|
|
2068
|
-
part: ToolPart
|
|
2069
|
-
onOpen?: () => void
|
|
2070
|
-
}) {
|
|
2071
|
-
const { theme } = useTheme()
|
|
2072
|
-
const renderer = useRenderer()
|
|
2073
|
-
const [collapsed, setCollapsed] = createSignal(false)
|
|
2074
|
-
const [openHover, setOpenHover] = createSignal(false)
|
|
2075
|
-
|
|
2076
|
-
const statusColor = createMemo(() => {
|
|
2077
|
-
const s = props.status
|
|
2078
|
-
if (s === "completed") return theme.success
|
|
2079
|
-
if (s === "failed") return theme.error
|
|
2080
|
-
if (s === "cancelled") return theme.textMuted
|
|
2081
|
-
return theme.warning
|
|
2082
|
-
})
|
|
2083
|
-
|
|
2084
|
-
const hiddenCount = createMemo(() => Math.max(0, props.transcript.length - WORKFLOW_PANEL_TAIL))
|
|
2085
|
-
const entries = createMemo(() => (collapsed() ? [] : props.transcript.slice(-WORKFLOW_PANEL_TAIL)))
|
|
2086
|
-
|
|
2087
|
-
return (
|
|
2088
|
-
<box
|
|
2089
|
-
border={["left"]}
|
|
2090
|
-
paddingTop={1}
|
|
2091
|
-
paddingBottom={1}
|
|
2092
|
-
paddingLeft={2}
|
|
2093
|
-
marginTop={1}
|
|
2094
|
-
gap={1}
|
|
2095
|
-
backgroundColor={theme.backgroundPanel}
|
|
2096
|
-
customBorderChars={SplitBorder.customBorderChars}
|
|
2097
|
-
borderColor={statusColor()}
|
|
2098
|
-
onMouseUp={() => {
|
|
2099
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
2100
|
-
setCollapsed((p) => !p)
|
|
2101
|
-
}}
|
|
2102
|
-
>
|
|
2103
|
-
<box flexDirection="row" gap={1} paddingLeft={3}>
|
|
2104
|
-
<Show when={props.running} fallback={<text fg={theme.accent} attributes={TextAttributes.BOLD}>⚡</text>}>
|
|
2105
|
-
<spinner frames={["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]} interval={80} color={theme.accent} />
|
|
2106
|
-
</Show>
|
|
2107
|
-
<text attributes={TextAttributes.BOLD} fg={theme.accent}>
|
|
2108
|
-
{props.name}
|
|
2109
|
-
</text>
|
|
2110
|
-
<Show when={props.status}>
|
|
2111
|
-
<text fg={statusColor()} attributes={TextAttributes.BOLD}>
|
|
2112
|
-
{props.status}
|
|
2113
|
-
</text>
|
|
2114
|
-
</Show>
|
|
2115
|
-
<Show when={props.currentPhase}>
|
|
2116
|
-
<text fg={theme.textMuted}>· {props.currentPhase}</text>
|
|
2117
|
-
</Show>
|
|
2118
|
-
<Show when={props.counters}>
|
|
2119
|
-
<text fg={theme.success}>{props.counters!.succeeded}✓</text>
|
|
2120
|
-
<text fg={props.counters!.failed > 0 ? theme.error : theme.textMuted}>{props.counters!.failed}✗</text>
|
|
2121
|
-
<text fg={props.counters!.running > 0 ? theme.warning : theme.textMuted}>{props.counters!.running}⟳</text>
|
|
2122
|
-
</Show>
|
|
2123
|
-
<Show when={props.onOpen}>
|
|
2124
|
-
<box flexGrow={1} />
|
|
2125
|
-
<text
|
|
2126
|
-
fg={openHover() ? theme.text : theme.markdownLink}
|
|
2127
|
-
onMouseOver={() => setOpenHover(true)}
|
|
2128
|
-
onMouseOut={() => setOpenHover(false)}
|
|
2129
|
-
onMouseUp={(evt) => {
|
|
2130
|
-
evt.stopPropagation()
|
|
2131
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
2132
|
-
props.onOpen?.()
|
|
2133
|
-
}}
|
|
2134
|
-
>
|
|
2135
|
-
open ↗
|
|
2136
|
-
</text>
|
|
2137
|
-
</Show>
|
|
2138
|
-
</box>
|
|
2139
|
-
<Show
|
|
2140
|
-
when={!collapsed()}
|
|
2141
|
-
fallback={
|
|
2142
|
-
<text paddingLeft={3} fg={theme.textMuted}>
|
|
2143
|
-
{props.transcript.length} lines · click to expand
|
|
2144
|
-
</text>
|
|
2145
|
-
}
|
|
2146
|
-
>
|
|
2147
|
-
<box paddingLeft={3}>
|
|
2148
|
-
<Show when={hiddenCount() > 0}>
|
|
2149
|
-
<text fg={theme.textMuted}>+{hiddenCount()} earlier lines · open detail for full history</text>
|
|
2150
|
-
</Show>
|
|
2151
|
-
<For each={entries()}>
|
|
2152
|
-
{(e) => (
|
|
2153
|
-
<Show
|
|
2154
|
-
when={e.kind === "phase"}
|
|
2155
|
-
fallback={<text fg={theme.text}>{e.text}</text>}
|
|
2156
|
-
>
|
|
2157
|
-
<text fg={theme.accent} attributes={TextAttributes.BOLD}>
|
|
2158
|
-
▸ {e.text}
|
|
2159
|
-
</text>
|
|
2160
|
-
</Show>
|
|
2161
|
-
)}
|
|
2162
|
-
</For>
|
|
2163
|
-
</box>
|
|
2164
|
-
</Show>
|
|
2165
|
-
</box>
|
|
2166
|
-
)
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
// Full-screen workflow detail page: occupies the conversation column (like a
|
|
2170
|
-
// subagent view) and shows one run's structure tree + transcript, live while
|
|
2171
|
-
// running. An agent row navigates to that subagent's full conversation; a nested
|
|
2172
|
-
// workflow row drills into the child run; "Main" returns to the conversation.
|
|
2173
|
-
function WorkflowPage(props: {
|
|
2174
|
-
runID: string
|
|
2175
|
-
onBack: () => void
|
|
2176
|
-
onOpenAgent: (actorID: string) => void
|
|
2177
|
-
onOpenChild: (childRunID: string) => void
|
|
2178
|
-
}) {
|
|
2179
|
-
const sync = useSync()
|
|
2180
|
-
const dialog = useDialog()
|
|
2181
|
-
const keybind = useKeybind()
|
|
2182
|
-
const { theme } = useTheme()
|
|
2183
|
-
const renderer = useRenderer()
|
|
2184
|
-
const tuiConfig = useTuiConfig()
|
|
2185
|
-
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
|
|
2186
|
-
let pageScroll: ScrollBoxRenderable | undefined
|
|
2187
|
-
|
|
2188
|
-
const run = createMemo(() => sync.data.workflow[props.runID])
|
|
2189
|
-
|
|
2190
|
-
// Describe what a running subagent is currently doing, from its live message
|
|
2191
|
-
// stream (last message's last meaningful part): a tool call → "⚙ <tool>", else
|
|
2192
|
-
// the latest text snippet. Returns undefined when nothing's streamed yet.
|
|
2193
|
-
const liveActivity = (actorID: string): string | undefined => {
|
|
2194
|
-
const sid = run()?.sessionID
|
|
2195
|
-
if (!sid) return undefined
|
|
2196
|
-
const msgs = sync.data.message[sid]?.[actorID]
|
|
2197
|
-
const last = msgs?.[msgs.length - 1]
|
|
2198
|
-
if (!last) return undefined
|
|
2199
|
-
const parts = sync.data.part[last.id] ?? []
|
|
2200
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
2201
|
-
const p = parts[i] as { type?: string; tool?: string; text?: string }
|
|
2202
|
-
if (p.type === "tool" && p.tool) return `⚙ ${p.tool}`
|
|
2203
|
-
if (p.type === "text" && p.text) return p.text
|
|
2204
|
-
}
|
|
2205
|
-
return undefined
|
|
2206
|
-
}
|
|
2207
|
-
const transcript = createMemo(() => sync.data.workflowTranscript[props.runID] ?? [])
|
|
2208
|
-
const structure = createMemo(() => sync.data.workflowStructure[props.runID] ?? [])
|
|
2209
|
-
|
|
2210
|
-
// Keyed on props.runID so a parent→child sub-workflow navigation (which does NOT
|
|
2211
|
-
// remount this component, since the <Show> fallback stays mounted while
|
|
2212
|
-
// workflowRunID is merely a different value) re-loads the new run's data, restores
|
|
2213
|
-
// its scroll, and re-arms the poll. The effect's onCleanup runs with `runID` bound
|
|
2214
|
-
// to the run being LEFT, so it saves that run's scroll under the correct key —
|
|
2215
|
-
// unlike reading props.runID at unmount, which is already stale.
|
|
2216
|
-
createEffect(
|
|
2217
|
-
on(
|
|
2218
|
-
() => props.runID,
|
|
2219
|
-
(runID) => {
|
|
2220
|
-
sync.loadWorkflowTranscript(runID)
|
|
2221
|
-
sync.loadWorkflowStructure(runID)
|
|
2222
|
-
// Restore the scroll position saved when we last left this run's page.
|
|
2223
|
-
// Deferred + retried so the cards (from the persisted structure store) are
|
|
2224
|
-
// laid out first, giving scrollTop a real range to land within.
|
|
2225
|
-
const saved = workflowScrollByRun.get(runID)
|
|
2226
|
-
if (saved) {
|
|
2227
|
-
let tries = 0
|
|
2228
|
-
const restore = () => {
|
|
2229
|
-
if (pageScroll) pageScroll.scrollTop = saved
|
|
2230
|
-
if (++tries < 5 && (pageScroll?.scrollTop ?? 0) < saved - 1) setTimeout(restore, 60)
|
|
2231
|
-
}
|
|
2232
|
-
setTimeout(restore, 0)
|
|
2233
|
-
} else if (pageScroll) {
|
|
2234
|
-
pageScroll.scrollTop = 0
|
|
2235
|
-
}
|
|
2236
|
-
const interval = setInterval(() => {
|
|
2237
|
-
sync.loadWorkflowStructure(runID)
|
|
2238
|
-
sync.loadWorkflowTranscript(runID)
|
|
2239
|
-
}, 1000)
|
|
2240
|
-
onCleanup(() => {
|
|
2241
|
-
clearInterval(interval)
|
|
2242
|
-
if (pageScroll) workflowScrollByRun.set(runID, pageScroll.scrollTop)
|
|
2243
|
-
})
|
|
2244
|
-
},
|
|
2245
|
-
),
|
|
2246
|
-
)
|
|
2247
|
-
|
|
2248
|
-
const statusColor = createMemo(() => {
|
|
2249
|
-
const s = run()?.status
|
|
2250
|
-
if (s === "completed") return theme.success
|
|
2251
|
-
if (s === "failed") return theme.error
|
|
2252
|
-
if (s === "cancelled") return theme.textMuted
|
|
2253
|
-
return theme.warning
|
|
2254
|
-
})
|
|
2255
|
-
|
|
2256
|
-
const resumable = createMemo(() => {
|
|
2257
|
-
const s = run()?.status
|
|
2258
|
-
return s === "running" || s === "failed" || s === "cancelled"
|
|
2259
|
-
})
|
|
2260
|
-
const resume = async () => {
|
|
2261
|
-
const ok = await DialogConfirm.show(
|
|
2262
|
-
dialog,
|
|
2263
|
-
"Resume workflow",
|
|
2264
|
-
`Re-run "${run()?.name ?? props.runID}"? This re-executes the workflow and may incur cost.`,
|
|
2265
|
-
)
|
|
2266
|
-
if (ok === true) void sync.resumeWorkflow(props.runID)
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
return (
|
|
2270
|
-
<box flexGrow={1} gap={1}>
|
|
2271
|
-
<box flexDirection="row" gap={1} flexShrink={0}>
|
|
2272
|
-
<text
|
|
2273
|
-
fg={theme.text}
|
|
2274
|
-
onMouseUp={() => {
|
|
2275
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
2276
|
-
props.onBack()
|
|
2277
|
-
}}
|
|
2278
|
-
>
|
|
2279
|
-
‹ Main <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span>
|
|
2280
|
-
</text>
|
|
2281
|
-
<box flexGrow={1} />
|
|
2282
|
-
<text attributes={TextAttributes.BOLD} fg={theme.accent}>
|
|
2283
|
-
{run()?.name ?? props.runID}
|
|
2284
|
-
</text>
|
|
2285
|
-
<Show when={run()?.status}>
|
|
2286
|
-
<text attributes={TextAttributes.BOLD} fg={statusColor()}>
|
|
2287
|
-
{run()!.status}
|
|
2288
|
-
</text>
|
|
2289
|
-
</Show>
|
|
2290
|
-
</box>
|
|
2291
|
-
<Show when={run()}>
|
|
2292
|
-
<box flexDirection="row" gap={1} flexShrink={0}>
|
|
2293
|
-
<Show when={run()!.currentPhase}>
|
|
2294
|
-
<text fg={theme.textMuted}>{run()!.currentPhase}</text>
|
|
2295
|
-
</Show>
|
|
2296
|
-
<text fg={theme.success}>{run()!.succeeded}✓</text>
|
|
2297
|
-
<text fg={run()!.failed > 0 ? theme.error : theme.textMuted}>{run()!.failed}✗</text>
|
|
2298
|
-
<text fg={run()!.running > 0 ? theme.warning : theme.textMuted}>{run()!.running}⟳</text>
|
|
2299
|
-
<Show when={resumable()}>
|
|
2300
|
-
<text fg={theme.markdownLink} onMouseUp={() => void resume()}>
|
|
2301
|
-
↻ resume
|
|
2302
|
-
</text>
|
|
2303
|
-
</Show>
|
|
2304
|
-
</box>
|
|
2305
|
-
</Show>
|
|
2306
|
-
<scrollbox
|
|
2307
|
-
ref={(r) => (pageScroll = r)}
|
|
2308
|
-
flexGrow={1}
|
|
2309
|
-
scrollAcceleration={scrollAcceleration()}
|
|
2310
|
-
>
|
|
2311
|
-
<WorkflowTree
|
|
2312
|
-
nodes={structure()}
|
|
2313
|
-
onOpenChild={props.onOpenChild}
|
|
2314
|
-
onOpenAgent={props.onOpenAgent}
|
|
2315
|
-
liveActivity={liveActivity}
|
|
2316
|
-
/>
|
|
2317
|
-
<Show when={transcript().length > 0}>
|
|
2318
|
-
<box paddingTop={1}>
|
|
2319
|
-
<text fg={theme.textMuted}>transcript</text>
|
|
2320
|
-
<For each={transcript()}>
|
|
2321
|
-
{(e) => (
|
|
2322
|
-
<Show when={e.kind === "phase"} fallback={<text fg={theme.text}>{e.text}</text>}>
|
|
2323
|
-
<text attributes={TextAttributes.BOLD} fg={theme.accent}>
|
|
2324
|
-
▸ {e.text}
|
|
2325
|
-
</text>
|
|
2326
|
-
</Show>
|
|
2327
|
-
)}
|
|
2328
|
-
</For>
|
|
2329
|
-
</box>
|
|
2330
|
-
</Show>
|
|
2331
|
-
<Show when={run()?.error}>
|
|
2332
|
-
<text fg={theme.error}>{run()!.error}</text>
|
|
2333
|
-
</Show>
|
|
2334
|
-
</scrollbox>
|
|
2335
|
-
</box>
|
|
2336
|
-
)
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
function CollapsibleError(props: { error: string; paddingLeft?: number }) {
|
|
2340
|
-
const { theme } = useTheme()
|
|
2341
|
-
const renderer = useRenderer()
|
|
2342
|
-
const [expanded, setExpanded] = createSignal(false)
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
const lineCount = createMemo(() => props.error.split("\n").length)
|
|
2346
|
-
|
|
2347
|
-
return (
|
|
2348
|
-
<box
|
|
2349
|
-
paddingLeft={props.paddingLeft}
|
|
2350
|
-
onMouseUp={(evt) => {
|
|
2351
|
-
evt.stopPropagation()
|
|
2352
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
2353
|
-
setExpanded((prev) => !prev)
|
|
2354
|
-
}}
|
|
2355
|
-
>
|
|
2356
|
-
<Show
|
|
2357
|
-
when={expanded()}
|
|
2358
|
-
fallback={
|
|
2359
|
-
<text fg={theme.error}>
|
|
2360
|
-
+ Error ({lineCount()} {lineCount() === 1 ? "line" : "lines"})
|
|
2361
|
-
</text>
|
|
2362
|
-
}
|
|
2363
|
-
>
|
|
2364
|
-
<text fg={theme.error}>- Error</text>
|
|
2365
|
-
<box paddingLeft={2}>
|
|
2366
|
-
<text fg={theme.error}>{props.error}</text>
|
|
2367
|
-
</box>
|
|
2368
|
-
</Show>
|
|
2369
|
-
</box>
|
|
2370
|
-
)
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
function InlineTool(props: {
|
|
2374
|
-
icon: string
|
|
2375
|
-
iconColor?: RGBA
|
|
2376
|
-
complete: any
|
|
2377
|
-
pending: string
|
|
2378
|
-
spinner?: boolean
|
|
2379
|
-
dismissed?: boolean
|
|
2380
|
-
children: JSX.Element
|
|
2381
|
-
part: ToolPart
|
|
2382
|
-
onClick?: () => void
|
|
2383
|
-
}) {
|
|
2384
|
-
const [margin, setMargin] = createSignal(0)
|
|
2385
|
-
const { theme } = useTheme()
|
|
2386
|
-
const ctx = use()
|
|
2387
|
-
const sync = useSync()
|
|
2388
|
-
const renderer = useRenderer()
|
|
2389
|
-
const [hover, setHover] = createSignal(false)
|
|
2390
|
-
|
|
2391
|
-
const permission = createMemo(() => {
|
|
2392
|
-
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
|
|
2393
|
-
if (!callID) return false
|
|
2394
|
-
return callID === props.part.callID
|
|
2395
|
-
})
|
|
2396
|
-
|
|
2397
|
-
const fg = createMemo(() => {
|
|
2398
|
-
if (permission()) return theme.warning
|
|
2399
|
-
if (hover() && props.onClick) return theme.text
|
|
2400
|
-
if (props.complete) return theme.textMuted
|
|
2401
|
-
return theme.text
|
|
2402
|
-
})
|
|
2403
|
-
|
|
2404
|
-
const error = createMemo(() => (props.part.state.status === "error" ? props.part.state.error : undefined))
|
|
2405
|
-
|
|
2406
|
-
const denied = createMemo(
|
|
2407
|
-
() =>
|
|
2408
|
-
error()?.includes("QuestionRejectedError") ||
|
|
2409
|
-
error()?.includes("rejected permission") ||
|
|
2410
|
-
error()?.includes("specified a rule") ||
|
|
2411
|
-
error()?.includes("user dismissed"),
|
|
2412
|
-
)
|
|
2413
|
-
|
|
2414
|
-
// Agent-recoverable failures (bad args, malformed call, unknown task/actor id)
|
|
2415
|
-
// are flagged on the error state. Render them muted (struck through, no red
|
|
2416
|
-
// block) like denials — the agent self-corrects; the user needn't be alarmed.
|
|
2417
|
-
const recoverable = createMemo(() => {
|
|
2418
|
-
const state = props.part.state
|
|
2419
|
-
return state.status === "error" && state.metadata?.recoverable === true
|
|
2420
|
-
})
|
|
2421
|
-
|
|
2422
|
-
return (
|
|
2423
|
-
<box
|
|
2424
|
-
marginTop={margin()}
|
|
2425
|
-
paddingLeft={3}
|
|
2426
|
-
onMouseOver={() => props.onClick && setHover(true)}
|
|
2427
|
-
onMouseOut={() => setHover(false)}
|
|
2428
|
-
onMouseUp={() => {
|
|
2429
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
2430
|
-
props.onClick?.()
|
|
2431
|
-
}}
|
|
2432
|
-
renderBefore={function () {
|
|
2433
|
-
const el = this as BoxRenderable
|
|
2434
|
-
const parent = el.parent
|
|
2435
|
-
if (!parent) {
|
|
2436
|
-
return
|
|
2437
|
-
}
|
|
2438
|
-
if (el.height > 1) {
|
|
2439
|
-
setMargin(1)
|
|
2440
|
-
return
|
|
2441
|
-
}
|
|
2442
|
-
const children = parent.getChildren()
|
|
2443
|
-
const index = children.indexOf(el)
|
|
2444
|
-
const previous = children[index - 1]
|
|
2445
|
-
if (!previous) {
|
|
2446
|
-
setMargin(0)
|
|
2447
|
-
return
|
|
2448
|
-
}
|
|
2449
|
-
if (previous.height > 1 || previous.id.startsWith("text-")) {
|
|
2450
|
-
setMargin(1)
|
|
2451
|
-
return
|
|
2452
|
-
}
|
|
2453
|
-
}}
|
|
2454
|
-
>
|
|
2455
|
-
<Switch>
|
|
2456
|
-
<Match when={props.spinner}>
|
|
2457
|
-
<Spinner color={fg()} children={props.children} />
|
|
2458
|
-
</Match>
|
|
2459
|
-
<Match when={true}>
|
|
2460
|
-
<text paddingLeft={3} fg={fg()} attributes={denied() || recoverable() || props.dismissed ? TextAttributes.STRIKETHROUGH : undefined}>
|
|
2461
|
-
<Show fallback={<>~ {props.pending}</>} when={props.complete}>
|
|
2462
|
-
<span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
|
|
2463
|
-
</Show>
|
|
2464
|
-
</text>
|
|
2465
|
-
</Match>
|
|
2466
|
-
</Switch>
|
|
2467
|
-
<Show when={error() && !denied() && !recoverable()}>
|
|
2468
|
-
<CollapsibleError error={error()!} paddingLeft={3} />
|
|
2469
|
-
</Show>
|
|
2470
|
-
</box>
|
|
2471
|
-
)
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
function BlockTool(props: {
|
|
2475
|
-
title: string
|
|
2476
|
-
children: JSX.Element
|
|
2477
|
-
onClick?: () => void
|
|
2478
|
-
part?: ToolPart
|
|
2479
|
-
spinner?: boolean
|
|
2480
|
-
}) {
|
|
2481
|
-
const { theme } = useTheme()
|
|
2482
|
-
const renderer = useRenderer()
|
|
2483
|
-
const [hover, setHover] = createSignal(false)
|
|
2484
|
-
const error = createMemo(() => (props.part?.state.status === "error" ? props.part.state.error : undefined))
|
|
2485
|
-
return (
|
|
2486
|
-
<box
|
|
2487
|
-
border={["left"]}
|
|
2488
|
-
paddingTop={1}
|
|
2489
|
-
paddingBottom={1}
|
|
2490
|
-
paddingLeft={2}
|
|
2491
|
-
marginTop={1}
|
|
2492
|
-
gap={1}
|
|
2493
|
-
backgroundColor={hover() ? theme.backgroundMenu : theme.backgroundPanel}
|
|
2494
|
-
customBorderChars={SplitBorder.customBorderChars}
|
|
2495
|
-
borderColor={theme.background}
|
|
2496
|
-
onMouseOver={() => props.onClick && setHover(true)}
|
|
2497
|
-
onMouseOut={() => setHover(false)}
|
|
2498
|
-
onMouseUp={() => {
|
|
2499
|
-
if (renderer.getSelection()?.getSelectedText()) return
|
|
2500
|
-
props.onClick?.()
|
|
2501
|
-
}}
|
|
2502
|
-
>
|
|
2503
|
-
<Show
|
|
2504
|
-
when={props.spinner}
|
|
2505
|
-
fallback={
|
|
2506
|
-
<text paddingLeft={3} fg={theme.textMuted}>
|
|
2507
|
-
{props.title}
|
|
2508
|
-
</text>
|
|
2509
|
-
}
|
|
2510
|
-
>
|
|
2511
|
-
<Spinner color={theme.textMuted}>{props.title.replace(/^# /, "")}</Spinner>
|
|
2512
|
-
</Show>
|
|
2513
|
-
{props.children}
|
|
2514
|
-
<Show when={error()}>
|
|
2515
|
-
<CollapsibleError error={error()!} />
|
|
2516
|
-
</Show>
|
|
2517
|
-
</box>
|
|
2518
|
-
)
|
|
2519
|
-
}
|
|
2520
|
-
|
|
2521
|
-
const TOOL_COLLAPSE_MAX_LINES = 3
|
|
2522
|
-
const TOOL_COLLAPSE_MAX_LINE_LENGTH = 120
|
|
2523
|
-
|
|
2524
|
-
function displayLines(content: string) {
|
|
2525
|
-
if (!content) return []
|
|
2526
|
-
return content.replace(/\n$/, "").split("\n")
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
|
-
function hasLongDisplayLine(content: string) {
|
|
2530
|
-
return displayLines(content).some((line) => line.length > TOOL_COLLAPSE_MAX_LINE_LENGTH)
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
function Bash(props: ToolProps<typeof BashTool>) {
|
|
2534
|
-
const { theme } = useTheme()
|
|
2535
|
-
const sync = useSync()
|
|
2536
|
-
const isRunning = createMemo(() => props.part.state.status === "running")
|
|
2537
|
-
const output = createMemo(() => stripAnsi(props.metadata.output?.trim() ?? ""))
|
|
2538
|
-
const [expanded, setExpanded] = createSignal(false)
|
|
2539
|
-
const lines = createMemo(() => output().split("\n"))
|
|
2540
|
-
const overflow = createMemo(() => lines().length > 10)
|
|
2541
|
-
const limited = createMemo(() => {
|
|
2542
|
-
if (expanded() || !overflow()) return output()
|
|
2543
|
-
return [...lines().slice(0, 10), "…"].join("\n")
|
|
2544
|
-
})
|
|
2545
|
-
|
|
2546
|
-
const workdirDisplay = createMemo(() => {
|
|
2547
|
-
const workdir = props.input.workdir
|
|
2548
|
-
if (!workdir || workdir === ".") return undefined
|
|
2549
|
-
|
|
2550
|
-
const base = sync.path.directory
|
|
2551
|
-
if (!base) return undefined
|
|
2552
|
-
|
|
2553
|
-
const absolute = path.resolve(base, workdir)
|
|
2554
|
-
if (absolute === base) return undefined
|
|
2555
|
-
|
|
2556
|
-
const home = Global.Path.home
|
|
2557
|
-
if (!home) return absolute
|
|
2558
|
-
|
|
2559
|
-
const match = absolute === home || absolute.startsWith(home + path.sep)
|
|
2560
|
-
return match ? absolute.replace(home, "~") : absolute
|
|
2561
|
-
})
|
|
2562
|
-
|
|
2563
|
-
const title = createMemo(() => {
|
|
2564
|
-
const desc = props.input.description ?? "Shell"
|
|
2565
|
-
const wd = workdirDisplay()
|
|
2566
|
-
if (!wd) return `# ${desc}`
|
|
2567
|
-
if (desc.includes(wd)) return `# ${desc}`
|
|
2568
|
-
return `# ${desc} in ${wd}`
|
|
2569
|
-
})
|
|
2570
|
-
|
|
2571
|
-
return (
|
|
2572
|
-
<Switch>
|
|
2573
|
-
<Match when={props.metadata.output !== undefined}>
|
|
2574
|
-
<BlockTool
|
|
2575
|
-
title={title()}
|
|
2576
|
-
part={props.part}
|
|
2577
|
-
spinner={isRunning()}
|
|
2578
|
-
onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
|
|
2579
|
-
>
|
|
2580
|
-
<box gap={1}>
|
|
2581
|
-
<text fg={theme.text}>$ {props.input.command}</text>
|
|
2582
|
-
<Show when={output()}>
|
|
2583
|
-
<text fg={theme.text}>{limited()}</text>
|
|
2584
|
-
</Show>
|
|
2585
|
-
<Show when={overflow()}>
|
|
2586
|
-
<text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text>
|
|
2587
|
-
</Show>
|
|
2588
|
-
</box>
|
|
2589
|
-
</BlockTool>
|
|
2590
|
-
</Match>
|
|
2591
|
-
<Match when={true}>
|
|
2592
|
-
<InlineTool icon="$" pending="Writing command..." complete={props.input.command} part={props.part}>
|
|
2593
|
-
{props.input.command}
|
|
2594
|
-
</InlineTool>
|
|
2595
|
-
</Match>
|
|
2596
|
-
</Switch>
|
|
2597
|
-
)
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
function Write(props: ToolProps<typeof WriteTool>) {
|
|
2601
|
-
const { theme, syntax } = useTheme()
|
|
2602
|
-
const [expanded, setExpanded] = createSignal(false)
|
|
2603
|
-
const code = createMemo(() => {
|
|
2604
|
-
if (!props.input.content) return ""
|
|
2605
|
-
return props.input.content
|
|
2606
|
-
})
|
|
2607
|
-
const lineCount = createMemo(() => displayLines(code()).length)
|
|
2608
|
-
const collapsed = createMemo(() => lineCount() > TOOL_COLLAPSE_MAX_LINES || hasLongDisplayLine(code()))
|
|
2609
|
-
|
|
2610
|
-
return (
|
|
2611
|
-
<Switch>
|
|
2612
|
-
<Match when={props.metadata.diagnostics !== undefined}>
|
|
2613
|
-
<BlockTool
|
|
2614
|
-
title={"# Wrote " + normalizePath(props.input.file_path!)}
|
|
2615
|
-
part={props.part}
|
|
2616
|
-
onClick={collapsed() ? () => setExpanded((prev) => !prev) : undefined}
|
|
2617
|
-
>
|
|
2618
|
-
<Show
|
|
2619
|
-
when={!collapsed() || expanded()}
|
|
2620
|
-
fallback={
|
|
2621
|
-
<text fg={theme.textMuted}>
|
|
2622
|
-
Click to expand ({lineCount()} {lineCount() === 1 ? "line" : "lines"})
|
|
2623
|
-
</text>
|
|
2624
|
-
}
|
|
2625
|
-
>
|
|
2626
|
-
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
|
|
2627
|
-
<code
|
|
2628
|
-
conceal={false}
|
|
2629
|
-
fg={theme.text}
|
|
2630
|
-
filetype={filetype(props.input.file_path!)}
|
|
2631
|
-
syntaxStyle={syntax()}
|
|
2632
|
-
content={code()}
|
|
2633
|
-
/>
|
|
2634
|
-
</line_number>
|
|
2635
|
-
<Show when={collapsed()}>
|
|
2636
|
-
<text fg={theme.textMuted}>Click to collapse</text>
|
|
2637
|
-
</Show>
|
|
2638
|
-
</Show>
|
|
2639
|
-
<Diagnostics diagnostics={props.metadata.diagnostics} filePath={props.input.file_path ?? ""} />
|
|
2640
|
-
</BlockTool>
|
|
2641
|
-
</Match>
|
|
2642
|
-
<Match when={true}>
|
|
2643
|
-
<InlineTool icon="←" pending="Preparing write..." complete={props.input.file_path} part={props.part}>
|
|
2644
|
-
Write {normalizePath(props.input.file_path!)}
|
|
2645
|
-
</InlineTool>
|
|
2646
|
-
</Match>
|
|
2647
|
-
</Switch>
|
|
2648
|
-
)
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
function Glob(props: ToolProps<typeof GlobTool>) {
|
|
2652
|
-
return (
|
|
2653
|
-
<InlineTool icon="✱" pending="Finding files..." complete={props.input.pattern} part={props.part}>
|
|
2654
|
-
Glob "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
|
|
2655
|
-
<Show when={props.metadata.count}>
|
|
2656
|
-
({props.metadata.count} {props.metadata.count === 1 ? "match" : "matches"})
|
|
2657
|
-
</Show>
|
|
2658
|
-
</InlineTool>
|
|
2659
|
-
)
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
function Read(props: ToolProps<typeof ReadTool>) {
|
|
2663
|
-
const { theme } = useTheme()
|
|
2664
|
-
const isRunning = createMemo(() => props.part.state.status === "running")
|
|
2665
|
-
const loaded = createMemo(() => {
|
|
2666
|
-
if (props.part.state.status !== "completed") return []
|
|
2667
|
-
if (props.part.state.time.compacted) return []
|
|
2668
|
-
const value = props.metadata.loaded
|
|
2669
|
-
if (!value || !Array.isArray(value)) return []
|
|
2670
|
-
return value.filter((p): p is string => typeof p === "string")
|
|
2671
|
-
})
|
|
2672
|
-
return (
|
|
2673
|
-
<>
|
|
2674
|
-
<InlineTool
|
|
2675
|
-
icon="→"
|
|
2676
|
-
pending="Reading file..."
|
|
2677
|
-
complete={props.input.file_path}
|
|
2678
|
-
spinner={isRunning()}
|
|
2679
|
-
part={props.part}
|
|
2680
|
-
>
|
|
2681
|
-
Read {normalizePath(props.input.file_path!)} {input(props.input, ["file_path"])}
|
|
2682
|
-
</InlineTool>
|
|
2683
|
-
<For each={loaded()}>
|
|
2684
|
-
{(filepath) => (
|
|
2685
|
-
<box paddingLeft={3}>
|
|
2686
|
-
<text paddingLeft={3} fg={theme.textMuted}>
|
|
2687
|
-
↳ Loaded {normalizePath(filepath)}
|
|
2688
|
-
</text>
|
|
2689
|
-
</box>
|
|
2690
|
-
)}
|
|
2691
|
-
</For>
|
|
2692
|
-
</>
|
|
2693
|
-
)
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
function Grep(props: ToolProps<typeof GrepTool>) {
|
|
2697
|
-
return (
|
|
2698
|
-
<InlineTool icon="✱" pending="Searching content..." complete={props.input.pattern} part={props.part}>
|
|
2699
|
-
Grep "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
|
|
2700
|
-
<Show when={props.metadata.matches}>
|
|
2701
|
-
({props.metadata.matches} {props.metadata.matches === 1 ? "match" : "matches"})
|
|
2702
|
-
</Show>
|
|
2703
|
-
</InlineTool>
|
|
2704
|
-
)
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
|
-
function WebFetch(props: ToolProps<typeof WebFetchTool>) {
|
|
2708
|
-
return (
|
|
2709
|
-
<InlineTool icon="%" pending="Fetching from the web..." complete={props.input.url} part={props.part}>
|
|
2710
|
-
WebFetch {props.input.url}
|
|
2711
|
-
</InlineTool>
|
|
2712
|
-
)
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
function CodeSearch(props: ToolProps<typeof CodeSearchTool>) {
|
|
2716
|
-
const metadata = props.metadata as { results?: number }
|
|
2717
|
-
return (
|
|
2718
|
-
<InlineTool icon="◇" pending="Searching code..." complete={props.input.query} part={props.part}>
|
|
2719
|
-
Exa Code Search "{props.input.query}" <Show when={metadata.results}>({metadata.results} results)</Show>
|
|
2720
|
-
</InlineTool>
|
|
2721
|
-
)
|
|
2722
|
-
}
|
|
2723
|
-
|
|
2724
|
-
function WebSearch(props: ToolProps<typeof WebSearchTool>) {
|
|
2725
|
-
const metadata = props.metadata as { numResults?: number }
|
|
2726
|
-
return (
|
|
2727
|
-
<InlineTool icon="◈" pending="Searching web..." complete={props.input.query} part={props.part}>
|
|
2728
|
-
Web Search "{props.input.query}" <Show when={metadata.numResults}>({metadata.numResults} results)</Show>
|
|
2729
|
-
</InlineTool>
|
|
2730
|
-
)
|
|
2731
|
-
}
|
|
2732
|
-
|
|
2733
|
-
function Task(props: ToolProps<typeof ActorTool>) {
|
|
2734
|
-
const route = useRoute()
|
|
2735
|
-
const sync = useSync()
|
|
2736
|
-
|
|
2737
|
-
const input = createMemo(() => {
|
|
2738
|
-
const raw = props.input as Partial<{ operation: { description: string; subagent_type: string } } & {
|
|
2739
|
-
description: string
|
|
2740
|
-
subagent_type: string
|
|
2741
|
-
}>
|
|
2742
|
-
return (raw?.operation ?? raw) as Partial<{ description: string; subagent_type: string }>
|
|
2743
|
-
})
|
|
2744
|
-
|
|
2745
|
-
const targetSession = createMemo(() => props.metadata.sessionId as string | undefined)
|
|
2746
|
-
const targetBucket = createMemo(() => (props.metadata.actorId as string | undefined) ?? "main")
|
|
2747
|
-
|
|
2748
|
-
createEffect(() => {
|
|
2749
|
-
const session = targetSession()
|
|
2750
|
-
const bucket = targetBucket()
|
|
2751
|
-
if (session && !sync.data.message[session]?.[bucket]?.length)
|
|
2752
|
-
void sync.session.sync(session)
|
|
2753
|
-
})
|
|
2754
|
-
|
|
2755
|
-
const messages = createMemo(() => sync.data.message[targetSession() ?? ""]?.[targetBucket()] ?? [])
|
|
2756
|
-
|
|
2757
|
-
const tools = createMemo(() => {
|
|
2758
|
-
return messages().flatMap((msg) =>
|
|
2759
|
-
(sync.data.part[msg.id] ?? [])
|
|
2760
|
-
.filter((part): part is ToolPart => part.type === "tool")
|
|
2761
|
-
.map((part) => ({ tool: part.tool, state: part.state })),
|
|
2762
|
-
)
|
|
2763
|
-
})
|
|
2764
|
-
|
|
2765
|
-
const current = createMemo(() =>
|
|
2766
|
-
tools().findLast((x) => (x.state.status === "running" || x.state.status === "completed") && x.state.title),
|
|
2767
|
-
)
|
|
2768
|
-
|
|
2769
|
-
const isRunning = createMemo(() => props.part.state.status === "running")
|
|
2770
|
-
|
|
2771
|
-
const duration = createMemo(() => {
|
|
2772
|
-
const first = messages().find((x) => x.role === "user")?.time.created
|
|
2773
|
-
const assistant = messages().findLast((x) => x.role === "assistant")?.time.completed
|
|
2774
|
-
if (!first || !assistant) return 0
|
|
2775
|
-
return assistant - first
|
|
2776
|
-
})
|
|
2777
|
-
|
|
2778
|
-
const content = createMemo(() => {
|
|
2779
|
-
if (!input().description) return ""
|
|
2780
|
-
let content = [`${Locale.titlecase(input().subagent_type ?? "General")} Task — ${input().description}`]
|
|
2781
|
-
|
|
2782
|
-
if (isRunning() && tools().length > 0) {
|
|
2783
|
-
// content[0] += ` · ${tools().length} toolcalls`
|
|
2784
|
-
if (current()) {
|
|
2785
|
-
const state = current()!.state
|
|
2786
|
-
const title = state.status === "running" || state.status === "completed" ? state.title : undefined
|
|
2787
|
-
content.push(`↳ ${Locale.titlecase(current()!.tool)} ${title}`)
|
|
2788
|
-
} else content.push(`↳ ${tools().length} toolcalls`)
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
|
-
if (props.part.state.status === "completed") {
|
|
2792
|
-
content.push(`└ ${tools().length} toolcalls · ${Locale.duration(duration())}`)
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
return content.join("\n")
|
|
2796
|
-
})
|
|
2797
|
-
|
|
2798
|
-
return (
|
|
2799
|
-
<InlineTool
|
|
2800
|
-
icon="│"
|
|
2801
|
-
spinner={isRunning()}
|
|
2802
|
-
complete={input().description}
|
|
2803
|
-
pending="Delegating..."
|
|
2804
|
-
part={props.part}
|
|
2805
|
-
onClick={() => {
|
|
2806
|
-
const session = targetSession()
|
|
2807
|
-
if (!session) return
|
|
2808
|
-
const actor = targetBucket()
|
|
2809
|
-
if (
|
|
2810
|
-
route.data.type === "session" &&
|
|
2811
|
-
session === route.data.sessionID &&
|
|
2812
|
-
actor !== "main"
|
|
2813
|
-
) {
|
|
2814
|
-
route.navigate({ ...route.data, agentID: actor })
|
|
2815
|
-
return
|
|
2816
|
-
}
|
|
2817
|
-
route.navigate({ type: "session", sessionID: session, agentID: actor !== "main" ? actor : undefined })
|
|
2818
|
-
}}
|
|
2819
|
-
>
|
|
2820
|
-
{content()}
|
|
2821
|
-
</InlineTool>
|
|
2822
|
-
)
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
function Edit(props: ToolProps<typeof EditTool>) {
|
|
2826
|
-
const ctx = use()
|
|
2827
|
-
const { theme, syntax } = useTheme()
|
|
2828
|
-
|
|
2829
|
-
const view = createMemo(() => {
|
|
2830
|
-
const diffStyle = ctx.tui.diff_style
|
|
2831
|
-
if (diffStyle === "stacked") return "unified"
|
|
2832
|
-
// Default to "auto" behavior
|
|
2833
|
-
return ctx.width > 120 ? "split" : "unified"
|
|
2834
|
-
})
|
|
2835
|
-
|
|
2836
|
-
const ft = createMemo(() => filetype(props.input.file_path))
|
|
2837
|
-
|
|
2838
|
-
const diffContent = createMemo(() => props.metadata.diff)
|
|
2839
|
-
|
|
2840
|
-
return (
|
|
2841
|
-
<Switch>
|
|
2842
|
-
<Match when={props.metadata.diff !== undefined}>
|
|
2843
|
-
<BlockTool title={"← Edit " + normalizePath(props.input.file_path!)} part={props.part}>
|
|
2844
|
-
<box paddingLeft={1}>
|
|
2845
|
-
<diff
|
|
2846
|
-
diff={diffContent()}
|
|
2847
|
-
view={view()}
|
|
2848
|
-
filetype={ft()}
|
|
2849
|
-
syntaxStyle={syntax()}
|
|
2850
|
-
showLineNumbers={true}
|
|
2851
|
-
width="100%"
|
|
2852
|
-
wrapMode={ctx.diffWrapMode()}
|
|
2853
|
-
fg={theme.text}
|
|
2854
|
-
addedBg={theme.diffAddedBg}
|
|
2855
|
-
removedBg={theme.diffRemovedBg}
|
|
2856
|
-
contextBg={theme.diffContextBg}
|
|
2857
|
-
addedSignColor={theme.diffHighlightAdded}
|
|
2858
|
-
removedSignColor={theme.diffHighlightRemoved}
|
|
2859
|
-
lineNumberFg={theme.diffLineNumber}
|
|
2860
|
-
lineNumberBg={theme.diffContextBg}
|
|
2861
|
-
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
|
2862
|
-
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
|
2863
|
-
/>
|
|
2864
|
-
</box>
|
|
2865
|
-
<Diagnostics diagnostics={props.metadata.diagnostics} filePath={props.input.file_path ?? ""} />
|
|
2866
|
-
</BlockTool>
|
|
2867
|
-
</Match>
|
|
2868
|
-
<Match when={true}>
|
|
2869
|
-
<InlineTool icon="←" pending="Preparing edit..." complete={props.input.file_path} part={props.part}>
|
|
2870
|
-
Edit {normalizePath(props.input.file_path!)} {input({ replace_all: props.input.replace_all })}
|
|
2871
|
-
</InlineTool>
|
|
2872
|
-
</Match>
|
|
2873
|
-
</Switch>
|
|
2874
|
-
)
|
|
2875
|
-
}
|
|
2876
|
-
|
|
2877
|
-
function ApplyPatch(props: ToolProps<typeof ApplyPatchTool>) {
|
|
2878
|
-
const ctx = use()
|
|
2879
|
-
const { theme, syntax } = useTheme()
|
|
2880
|
-
const [expanded, setExpanded] = createSignal<string[]>([])
|
|
2881
|
-
|
|
2882
|
-
const files = createMemo(() => props.metadata.files ?? [])
|
|
2883
|
-
|
|
2884
|
-
const view = createMemo(() => {
|
|
2885
|
-
const diffStyle = ctx.tui.diff_style
|
|
2886
|
-
if (diffStyle === "stacked") return "unified"
|
|
2887
|
-
return ctx.width > 120 ? "split" : "unified"
|
|
2888
|
-
})
|
|
2889
|
-
|
|
2890
|
-
function Diff(p: { diff: string; filePath: string }) {
|
|
2891
|
-
return (
|
|
2892
|
-
<box paddingLeft={1}>
|
|
2893
|
-
<diff
|
|
2894
|
-
diff={p.diff}
|
|
2895
|
-
view={view()}
|
|
2896
|
-
filetype={filetype(p.filePath)}
|
|
2897
|
-
syntaxStyle={syntax()}
|
|
2898
|
-
showLineNumbers={true}
|
|
2899
|
-
width="100%"
|
|
2900
|
-
wrapMode={ctx.diffWrapMode()}
|
|
2901
|
-
fg={theme.text}
|
|
2902
|
-
addedBg={theme.diffAddedBg}
|
|
2903
|
-
removedBg={theme.diffRemovedBg}
|
|
2904
|
-
contextBg={theme.diffContextBg}
|
|
2905
|
-
addedSignColor={theme.diffHighlightAdded}
|
|
2906
|
-
removedSignColor={theme.diffHighlightRemoved}
|
|
2907
|
-
lineNumberFg={theme.diffLineNumber}
|
|
2908
|
-
lineNumberBg={theme.diffContextBg}
|
|
2909
|
-
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
|
2910
|
-
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
|
2911
|
-
/>
|
|
2912
|
-
</box>
|
|
2913
|
-
)
|
|
2914
|
-
}
|
|
2915
|
-
|
|
2916
|
-
function title(file: { type: string; relativePath: string; filePath: string; deletions: number }) {
|
|
2917
|
-
if (file.type === "delete") return "# Deleted " + file.relativePath
|
|
2918
|
-
if (file.type === "add") return "# Created " + file.relativePath
|
|
2919
|
-
if (file.type === "move") return "# Moved " + normalizePath(file.filePath) + " → " + file.relativePath
|
|
2920
|
-
return "← Patched " + file.relativePath
|
|
2921
|
-
}
|
|
2922
|
-
|
|
2923
|
-
function toggle(filePath: string) {
|
|
2924
|
-
setExpanded((prev) => (prev.includes(filePath) ? prev.filter((item) => item !== filePath) : [...prev, filePath]))
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
return (
|
|
2928
|
-
<Switch>
|
|
2929
|
-
<Match when={files().length > 0}>
|
|
2930
|
-
<For each={files()}>
|
|
2931
|
-
{(file) => {
|
|
2932
|
-
const open = createMemo(() => expanded().includes(file.filePath))
|
|
2933
|
-
const count = createMemo(() => file.additions + file.deletions)
|
|
2934
|
-
const collapsed = createMemo(() => count() > TOOL_COLLAPSE_MAX_LINES || hasLongDisplayLine(file.patch))
|
|
2935
|
-
|
|
2936
|
-
return (
|
|
2937
|
-
<BlockTool
|
|
2938
|
-
title={title(file)}
|
|
2939
|
-
part={props.part}
|
|
2940
|
-
onClick={file.type !== "delete" && collapsed() ? () => toggle(file.filePath) : undefined}
|
|
2941
|
-
>
|
|
2942
|
-
<Show
|
|
2943
|
-
when={file.type !== "delete"}
|
|
2944
|
-
fallback={
|
|
2945
|
-
<text fg={theme.diffRemoved}>
|
|
2946
|
-
-{file.deletions} line{file.deletions !== 1 ? "s" : ""}
|
|
2947
|
-
</text>
|
|
2948
|
-
}
|
|
2949
|
-
>
|
|
2950
|
-
<Show
|
|
2951
|
-
when={!collapsed() || open()}
|
|
2952
|
-
fallback={
|
|
2953
|
-
<text fg={theme.textMuted}>
|
|
2954
|
-
Click to expand ({count()} change{count() !== 1 ? "s" : ""})
|
|
2955
|
-
</text>
|
|
2956
|
-
}
|
|
2957
|
-
>
|
|
2958
|
-
<Diff diff={file.patch} filePath={file.filePath} />
|
|
2959
|
-
<Show when={collapsed()}>
|
|
2960
|
-
<text fg={theme.textMuted}>Click to collapse</text>
|
|
2961
|
-
</Show>
|
|
2962
|
-
</Show>
|
|
2963
|
-
<Diagnostics diagnostics={props.metadata.diagnostics} filePath={file.movePath ?? file.filePath} />
|
|
2964
|
-
</Show>
|
|
2965
|
-
</BlockTool>
|
|
2966
|
-
)
|
|
2967
|
-
}}
|
|
2968
|
-
</For>
|
|
2969
|
-
</Match>
|
|
2970
|
-
<Match when={true}>
|
|
2971
|
-
<InlineTool icon="%" pending="Preparing patch..." complete={false} part={props.part}>
|
|
2972
|
-
Patch
|
|
2973
|
-
</InlineTool>
|
|
2974
|
-
</Match>
|
|
2975
|
-
</Switch>
|
|
2976
|
-
)
|
|
2977
|
-
}
|
|
2978
|
-
|
|
2979
|
-
function Question(props: ToolProps<typeof QuestionTool>) {
|
|
2980
|
-
const { theme } = useTheme()
|
|
2981
|
-
const count = createMemo(() => props.input.questions?.length ?? 0)
|
|
2982
|
-
|
|
2983
|
-
function format(answer?: ReadonlyArray<string>) {
|
|
2984
|
-
if (!answer?.length) return "(no answer)"
|
|
2985
|
-
return answer.join(", ")
|
|
2986
|
-
}
|
|
2987
|
-
|
|
2988
|
-
return (
|
|
2989
|
-
<Switch>
|
|
2990
|
-
<Match when={props.metadata.answers}>
|
|
2991
|
-
<BlockTool title="# Questions" part={props.part}>
|
|
2992
|
-
<box gap={1}>
|
|
2993
|
-
<For each={props.input.questions ?? []}>
|
|
2994
|
-
{(q, i) => (
|
|
2995
|
-
<box flexDirection="column">
|
|
2996
|
-
<text fg={theme.textMuted}>{q.question}</text>
|
|
2997
|
-
<text fg={theme.text}>{format(props.metadata.answers?.[i()])}</text>
|
|
2998
|
-
</box>
|
|
2999
|
-
)}
|
|
3000
|
-
</For>
|
|
3001
|
-
</box>
|
|
3002
|
-
</BlockTool>
|
|
3003
|
-
</Match>
|
|
3004
|
-
<Match when={true}>
|
|
3005
|
-
<InlineTool icon="→" pending="Asking questions..." complete={count()} part={props.part}>
|
|
3006
|
-
Asked {count()} question{count() !== 1 ? "s" : ""}
|
|
3007
|
-
</InlineTool>
|
|
3008
|
-
</Match>
|
|
3009
|
-
</Switch>
|
|
3010
|
-
)
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
function Skill(props: ToolProps<typeof SkillTool>) {
|
|
3014
|
-
return (
|
|
3015
|
-
<InlineTool icon="→" pending="Loading skill..." complete={props.input.name} part={props.part}>
|
|
3016
|
-
Skill "{props.input.name}"
|
|
3017
|
-
</InlineTool>
|
|
3018
|
-
)
|
|
3019
|
-
}
|
|
3020
|
-
|
|
3021
|
-
function Diagnostics(props: { diagnostics?: Record<string, Record<string, any>[]>; filePath: string }) {
|
|
3022
|
-
const { theme } = useTheme()
|
|
3023
|
-
const errors = createMemo(() => {
|
|
3024
|
-
const normalized = Filesystem.normalizePath(props.filePath)
|
|
3025
|
-
const arr = props.diagnostics?.[normalized] ?? []
|
|
3026
|
-
return arr.filter((x) => x.severity === 1).slice(0, 3)
|
|
3027
|
-
})
|
|
3028
|
-
|
|
3029
|
-
return (
|
|
3030
|
-
<Show when={errors().length}>
|
|
3031
|
-
<box>
|
|
3032
|
-
<For each={errors()}>
|
|
3033
|
-
{(diagnostic) => (
|
|
3034
|
-
<text fg={theme.error}>
|
|
3035
|
-
Error [{diagnostic.range.start.line + 1}:{diagnostic.range.start.character + 1}] {diagnostic.message}
|
|
3036
|
-
</text>
|
|
3037
|
-
)}
|
|
3038
|
-
</For>
|
|
3039
|
-
</box>
|
|
3040
|
-
</Show>
|
|
3041
|
-
)
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
function normalizePath(input?: string) {
|
|
3045
|
-
if (!input) return ""
|
|
3046
|
-
|
|
3047
|
-
const cwd = process.cwd()
|
|
3048
|
-
const absolute = path.isAbsolute(input) ? input : path.resolve(cwd, input)
|
|
3049
|
-
const relative = path.relative(cwd, absolute)
|
|
3050
|
-
|
|
3051
|
-
if (!relative) return "."
|
|
3052
|
-
if (!relative.startsWith("..")) return relative
|
|
3053
|
-
|
|
3054
|
-
// outside cwd - use absolute
|
|
3055
|
-
return absolute
|
|
3056
|
-
}
|
|
3057
|
-
|
|
3058
|
-
function input(input: Record<string, any>, omit?: string[]): string {
|
|
3059
|
-
const primitives = Object.entries(input).filter(([key, value]) => {
|
|
3060
|
-
if (omit?.includes(key)) return false
|
|
3061
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean"
|
|
3062
|
-
})
|
|
3063
|
-
if (primitives.length === 0) return ""
|
|
3064
|
-
return `[${primitives.map(([key, value]) => `${key}=${value}`).join(", ")}]`
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
|
-
function filetype(input?: string) {
|
|
3068
|
-
if (!input) return "none"
|
|
3069
|
-
const ext = path.extname(input)
|
|
3070
|
-
const language = LANGUAGE_EXTENSIONS[ext]
|
|
3071
|
-
if (["typescriptreact", "javascriptreact", "javascript"].includes(language)) return "typescript"
|
|
3072
|
-
return language
|
|
3073
|
-
}
|