micro-models-agent 2.16.1 → 2.17.2
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/CHANGELOG.md +34 -0
- package/README.md +8 -3
- package/dist/assets/app.js +1 -314
- package/dist/assets/hljs.js +5 -1244
- package/dist/assets/index.html +11 -0
- package/dist/assets/js/api.js +1 -28
- package/dist/assets/js/core/menus.js +1 -0
- package/dist/assets/js/core/store/feed.js +1 -0
- package/dist/assets/js/core/store/index.js +1 -0
- package/dist/assets/js/core/store/session.js +1 -0
- package/dist/assets/js/core/store/settings.js +1 -0
- package/dist/assets/js/core/store/tabs.js +1 -0
- package/dist/assets/js/dialog.js +1 -105
- package/dist/assets/js/dom.js +1 -178
- package/dist/assets/js/feed/core.js +1 -0
- package/dist/assets/js/feed/history.js +1 -0
- package/dist/assets/js/feed/plan.js +1 -0
- package/dist/assets/js/feed/question.js +3 -0
- package/dist/assets/js/feed/sidebar.js +1 -0
- package/dist/assets/js/feed/stream.js +1 -0
- package/dist/assets/js/feed/turn.js +5 -0
- package/dist/assets/js/feed.js +1 -1250
- package/dist/assets/js/format.js +2 -122
- package/dist/assets/js/i18n.js +1 -46
- package/dist/assets/js/rail.js +1 -114
- package/dist/assets/js/session/cmdk.js +1 -0
- package/dist/assets/js/session/composer.js +1 -0
- package/dist/assets/js/session/sessions-menu.js +1 -0
- package/dist/assets/js/session/tabs.js +1 -0
- package/dist/assets/js/settings/controls.js +1 -0
- package/dist/assets/js/settings/editors.js +1 -0
- package/dist/assets/js/settings/form.js +1 -0
- package/dist/assets/js/settings/modal.js +1 -0
- package/dist/assets/js/settings/panels.js +1 -0
- package/dist/assets/js/settings/providers.js +1 -0
- package/dist/assets/js/settings/search.js +1 -0
- package/dist/assets/js/settings/subdialog.js +1 -0
- package/dist/assets/js/settings/write.js +1 -0
- package/dist/assets/js/settings.js +1 -1914
- package/dist/assets/js/sidebar/core.js +1 -0
- package/dist/assets/js/sidebar/lsp.js +2 -0
- package/dist/assets/js/sidebar/map.js +1 -0
- package/dist/assets/js/sidebar/state.js +1 -0
- package/dist/assets/js/sidebar/trace.js +5 -0
- package/dist/assets/js/sidebar.js +1 -705
- package/dist/assets/js/store.js +1 -82
- package/dist/assets/js/subdialog.js +1 -0
- package/dist/assets/md.js +1 -291
- package/dist/assets/setup.js +3 -816
- package/dist/assets/smd.js +29 -1665
- package/dist/assets/styles.css +1 -1598
- package/dist/certification/certifications.json +1 -494
- package/dist/i18n/en.json +1 -1851
- package/dist/i18n/ru.json +1 -1854
- package/dist/main.js +815 -55797
- package/dist/modules/browser/bridge-server.mjs +2 -252
- package/package.json +10 -5
- package/dist/assets/js/settings-controls.js +0 -452
- package/dist/assets/js/tabs.js +0 -867
- package/dist/cli/cache-line.js +0 -31
- package/dist/cli/command-suggest.js +0 -39
- package/dist/cli/commands.js +0 -781
- package/dist/cli/completer.js +0 -268
- package/dist/cli/index.js +0 -3
- package/dist/cli/json-payload.js +0 -32
- package/dist/cli/main.js +0 -346
- package/dist/cli/onboarding-commands.js +0 -126
- package/dist/cli/plugin-commands.js +0 -38
- package/dist/cli/relaunch.js +0 -40
- package/dist/cli/repl-commands-lsp.js +0 -75
- package/dist/cli/repl-commands-skills.js +0 -105
- package/dist/cli/repl-commands.js +0 -1218
- package/dist/cli/repl.js +0 -1238
- package/dist/cli/run-result.js +0 -38
- package/dist/cli/security-commands.js +0 -177
- package/dist/cli/setup-order.js +0 -6
- package/dist/cli/setup-prompt.js +0 -50
- package/dist/cli/setup.js +0 -161
- package/dist/cli/web-command.js +0 -98
- package/dist/config/budget.js +0 -48
- package/dist/config/config.js +0 -345
- package/dist/config/defaults.js +0 -197
- package/dist/config/domains.js +0 -135
- package/dist/config/env.js +0 -46
- package/dist/config/experts.js +0 -17
- package/dist/config/index.js +0 -5
- package/dist/config/paths.js +0 -56
- package/dist/config/redact.js +0 -44
- package/dist/config/registry-extra.js +0 -334
- package/dist/config/registry.js +0 -584
- package/dist/config/security.js +0 -212
- package/dist/config/timeouts.js +0 -74
- package/dist/config/types.js +0 -1
- package/dist/config/utils.js +0 -57
- package/dist/core/agent/artifact-progress.js +0 -59
- package/dist/core/agent/artifact-resolve.js +0 -79
- package/dist/core/agent/audit-gate.js +0 -53
- package/dist/core/agent/compaction.js +0 -89
- package/dist/core/agent/constants.js +0 -90
- package/dist/core/agent/context-log.js +0 -454
- package/dist/core/agent/context-renderer.js +0 -45
- package/dist/core/agent/critic-gate.js +0 -132
- package/dist/core/agent/hallucination-gate.js +0 -197
- package/dist/core/agent/loop-state.js +0 -68
- package/dist/core/agent/prefix-monitor.js +0 -101
- package/dist/core/agent/reasoning-resolver.js +0 -65
- package/dist/core/agent/token-tracker.js +0 -99
- package/dist/core/agent/tool-batch.js +0 -312
- package/dist/core/agent/tool-images.js +0 -18
- package/dist/core/agent/tool-output.js +0 -116
- package/dist/core/agent/tool-pairing.js +0 -60
- package/dist/core/agent-moe.js +0 -248
- package/dist/core/agent.js +0 -1299
- package/dist/core/bootstrap/agent-phase.js +0 -66
- package/dist/core/bootstrap/config-phase.js +0 -86
- package/dist/core/bootstrap/modules-phase.js +0 -269
- package/dist/core/bootstrap/provider-phase.js +0 -183
- package/dist/core/bootstrap/session-phase.js +0 -204
- package/dist/core/bootstrap/state.js +0 -94
- package/dist/core/bootstrap/workspace-phase.js +0 -115
- package/dist/core/bootstrap.js +0 -112
- package/dist/core/crash-handler.js +0 -62
- package/dist/core/environment.js +0 -224
- package/dist/core/host-plan.js +0 -43
- package/dist/core/host.js +0 -23
- package/dist/core/index.js +0 -3
- package/dist/core/prompt-builder.js +0 -95
- package/dist/core/prompt-overflow.js +0 -306
- package/dist/core/session-logger.js +0 -375
- package/dist/core/system-prompt.js +0 -117
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -28
- package/dist/core/web-host.js +0 -34
- package/dist/core/workspace.js +0 -80
- package/dist/i18n/index.js +0 -80
- package/dist/index.js +0 -30
- package/dist/llm/cache-usage.js +0 -76
- package/dist/llm/image-utils.js +0 -138
- package/dist/llm/index.js +0 -4
- package/dist/llm/llm-errors.js +0 -50
- package/dist/llm/model-loader.js +0 -86
- package/dist/llm/models-dev.js +0 -121
- package/dist/llm/openai-compat.js +0 -756
- package/dist/llm/orchestrator.js +0 -294
- package/dist/llm/provider-budget.js +0 -68
- package/dist/llm/provider-context.js +0 -233
- package/dist/llm/provider.js +0 -9
- package/dist/llm/response.js +0 -44
- package/dist/llm/stream-state.js +0 -26
- package/dist/llm/token-counter.js +0 -83
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -212
- package/dist/logger/file-log.js +0 -160
- package/dist/logger/index.js +0 -2
- package/dist/migration/backup.js +0 -56
- package/dist/migration/detect.js +0 -61
- package/dist/migration/index.js +0 -3
- package/dist/modules/artifacts/store.js +0 -62
- package/dist/modules/browser/actions.js +0 -79
- package/dist/modules/browser/bridge-client.js +0 -203
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/console-buffer.js +0 -37
- package/dist/modules/browser/cookie-store.js +0 -29
- package/dist/modules/browser/driver-types.js +0 -1
- package/dist/modules/browser/driver.js +0 -186
- package/dist/modules/browser/index.js +0 -9
- package/dist/modules/browser/module.js +0 -30
- package/dist/modules/browser/session.js +0 -322
- package/dist/modules/browser/snapshot.js +0 -154
- package/dist/modules/browser/types.js +0 -13
- package/dist/modules/certification/cli.js +0 -264
- package/dist/modules/certification/fact-checker.js +0 -90
- package/dist/modules/certification/loader.js +0 -127
- package/dist/modules/certification/manifest.js +0 -231
- package/dist/modules/certification/runner.js +0 -233
- package/dist/modules/certification/scenarios.js +0 -586
- package/dist/modules/certification/syntax-scenarios.js +0 -50
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/chunk-query.js +0 -122
- package/dist/modules/context/fact-extractor.js +0 -170
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -2
- package/dist/modules/context/manager.js +0 -482
- package/dist/modules/critic/client.js +0 -176
- package/dist/modules/critic/evidence.js +0 -81
- package/dist/modules/critic/provider.js +0 -45
- package/dist/modules/critic/tools.js +0 -78
- package/dist/modules/critic/types.js +0 -1
- package/dist/modules/execution/audit-runners.js +0 -206
- package/dist/modules/execution/auditor.js +0 -222
- package/dist/modules/execution/execution-plugin.js +0 -478
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/input-from.js +0 -70
- package/dist/modules/execution/module.js +0 -891
- package/dist/modules/execution/moe-executor.js +0 -443
- package/dist/modules/execution/plan-actions.js +0 -642
- package/dist/modules/execution/plan-checks.js +0 -35
- package/dist/modules/execution/plan-coverage.js +0 -71
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-steps.js +0 -31
- package/dist/modules/execution/plan-store.js +0 -274
- package/dist/modules/execution/plan-tool.js +0 -249
- package/dist/modules/execution/plan-validator.js +0 -197
- package/dist/modules/execution/planner.js +0 -103
- package/dist/modules/execution/stuck-detector.js +0 -834
- package/dist/modules/execution/tracker.js +0 -92
- package/dist/modules/execution/transient-error.js +0 -30
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -343
- package/dist/modules/execution/windows-commands.js +0 -52
- package/dist/modules/hallucination/artifact-claim.js +0 -93
- package/dist/modules/hallucination/confidence.js +0 -81
- package/dist/modules/hallucination/consistency.js +0 -37
- package/dist/modules/hallucination/detector.js +0 -57
- package/dist/modules/hallucination/factual.js +0 -205
- package/dist/modules/hallucination/index.js +0 -6
- package/dist/modules/hallucination/js-identifiers.js +0 -261
- package/dist/modules/hallucination/llm-judge.js +0 -116
- package/dist/modules/hallucination/success-claim.js +0 -59
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -88
- package/dist/modules/indexer/index.js +0 -4
- package/dist/modules/indexer/map-command.js +0 -33
- package/dist/modules/indexer/map-select.js +0 -87
- package/dist/modules/indexer/module.js +0 -293
- package/dist/modules/indexer/project-profile.js +0 -184
- package/dist/modules/indexer/symbols.js +0 -189
- package/dist/modules/indexer/walker.js +0 -285
- package/dist/modules/lsp/check-tool.js +0 -61
- package/dist/modules/lsp/client.js +0 -406
- package/dist/modules/lsp/command.js +0 -53
- package/dist/modules/lsp/config.js +0 -189
- package/dist/modules/lsp/index.js +0 -4
- package/dist/modules/lsp/module.js +0 -295
- package/dist/modules/lsp/probe.js +0 -86
- package/dist/modules/lsp/project-root.js +0 -69
- package/dist/modules/lsp/startup-check.js +0 -154
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -445
- package/dist/modules/mcp/index.js +0 -4
- package/dist/modules/mcp/module.js +0 -295
- package/dist/modules/mcp/registry.js +0 -20
- package/dist/modules/memory/index.js +0 -2
- package/dist/modules/memory/module.js +0 -101
- package/dist/modules/memory/search.js +0 -45
- package/dist/modules/memory/store.js +0 -107
- package/dist/modules/onboarding/init.js +0 -47
- package/dist/modules/onboarding/meet.js +0 -30
- package/dist/modules/onboarding/mode.js +0 -30
- package/dist/modules/onboarding/prompts.js +0 -43
- package/dist/modules/pipelines/engine.js +0 -70
- package/dist/modules/pipelines/index.js +0 -4
- package/dist/modules/pipelines/parser.js +0 -59
- package/dist/modules/pipelines/template.js +0 -18
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
- package/dist/modules/plugins/builtin/notify.js +0 -11
- package/dist/modules/plugins/index.js +0 -2
- package/dist/modules/plugins/loader.js +0 -85
- package/dist/modules/plugins/manager.js +0 -244
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/pricing/index.js +0 -70
- package/dist/modules/pricing/prices.js +0 -147
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -4
- package/dist/modules/processes/kill-tree.js +0 -52
- package/dist/modules/processes/registry.js +0 -294
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/providers/cache.js +0 -23
- package/dist/modules/providers/capabilities.js +0 -28
- package/dist/modules/providers/create.js +0 -21
- package/dist/modules/providers/factory.js +0 -28
- package/dist/modules/providers/fallback.js +0 -81
- package/dist/modules/providers/health.js +0 -47
- package/dist/modules/providers/index.js +0 -6
- package/dist/modules/providers/manager.js +0 -207
- package/dist/modules/providers/naming.js +0 -15
- package/dist/modules/providers/preflight.js +0 -71
- package/dist/modules/providers/presets.js +0 -232
- package/dist/modules/providers/registry.js +0 -27
- package/dist/modules/providers/types.js +0 -1
- package/dist/modules/reasoning/policy.js +0 -42
- package/dist/modules/reasoning/probe.js +0 -116
- package/dist/modules/registry.js +0 -48
- package/dist/modules/security/audit-log.js +0 -119
- package/dist/modules/security/audit-notifier.js +0 -267
- package/dist/modules/security/command-validator.js +0 -272
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -82
- package/dist/modules/security/encryption.js +0 -216
- package/dist/modules/security/index.js +0 -15
- package/dist/modules/security/network-validator.js +0 -236
- package/dist/modules/security/path-validator.js +0 -261
- package/dist/modules/security/protected-paths.js +0 -73
- package/dist/modules/security/rate-limiter.js +0 -98
- package/dist/modules/security/security-policies.js +0 -499
- package/dist/modules/security/session-encryption.js +0 -182
- package/dist/modules/security/session-isolation.js +0 -75
- package/dist/modules/session/auto-name.js +0 -63
- package/dist/modules/session/export.js +0 -98
- package/dist/modules/session/index.js +0 -4
- package/dist/modules/session/manager.js +0 -234
- package/dist/modules/session/module.js +0 -33
- package/dist/modules/session/store.js +0 -295
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/setup/answers.js +0 -35
- package/dist/modules/setup/apply.js +0 -79
- package/dist/modules/setup/discovery.js +0 -72
- package/dist/modules/skills/index.js +0 -3
- package/dist/modules/skills/loader.js +0 -86
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -141
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/changelog-reader.js +0 -93
- package/dist/modules/updater/checker.js +0 -101
- package/dist/modules/updater/dev-detect.js +0 -18
- package/dist/modules/updater/index.js +0 -4
- package/dist/modules/updater/module.js +0 -129
- package/dist/modules/user-profile/compressor.js +0 -18
- package/dist/modules/user-profile/index.js +0 -2
- package/dist/modules/user-profile/profile.js +0 -77
- package/dist/modules/webui/answer-api.js +0 -29
- package/dist/modules/webui/assets.js +0 -50
- package/dist/modules/webui/body.js +0 -21
- package/dist/modules/webui/commands-settings.js +0 -209
- package/dist/modules/webui/commands-shared.js +0 -60
- package/dist/modules/webui/commands.js +0 -174
- package/dist/modules/webui/config-api.js +0 -134
- package/dist/modules/webui/file-api.js +0 -202
- package/dist/modules/webui/forward.js +0 -159
- package/dist/modules/webui/history-api.js +0 -112
- package/dist/modules/webui/host-state.js +0 -60
- package/dist/modules/webui/i18n-api.js +0 -9
- package/dist/modules/webui/limits.js +0 -6
- package/dist/modules/webui/log-forward.js +0 -20
- package/dist/modules/webui/memory-commands.js +0 -64
- package/dist/modules/webui/model-cache.js +0 -18
- package/dist/modules/webui/module.js +0 -148
- package/dist/modules/webui/persist.js +0 -24
- package/dist/modules/webui/prompt-io.js +0 -97
- package/dist/modules/webui/providers-api.js +0 -337
- package/dist/modules/webui/providers-view.js +0 -56
- package/dist/modules/webui/reasoning-view.js +0 -15
- package/dist/modules/webui/run-agent.js +0 -81
- package/dist/modules/webui/server.js +0 -284
- package/dist/modules/webui/sessions-api.js +0 -178
- package/dist/modules/webui/settings-api.js +0 -561
- package/dist/modules/webui/setup-api.js +0 -201
- package/dist/modules/webui/state.js +0 -74
- package/dist/modules/webui/util.js +0 -261
- package/dist/output/bus.js +0 -37
- package/dist/output/channel.js +0 -285
- package/dist/output/format.js +0 -15
- package/dist/output/index.js +0 -8
- package/dist/output/json-sink.js +0 -24
- package/dist/output/machine.js +0 -9
- package/dist/output/session-sink.js +0 -27
- package/dist/output/types.js +0 -1
- package/dist/tools/approve.js +0 -41
- package/dist/tools/attach-image.js +0 -102
- package/dist/tools/auto-fixer.js +0 -180
- package/dist/tools/auto-tags.js +0 -83
- package/dist/tools/bash.js +0 -649
- package/dist/tools/browser.js +0 -160
- package/dist/tools/chunk-query.js +0 -112
- package/dist/tools/create-dir.js +0 -60
- package/dist/tools/delete-file.js +0 -67
- package/dist/tools/download-file.js +0 -142
- package/dist/tools/edit-file.js +0 -136
- package/dist/tools/enable-tools.js +0 -59
- package/dist/tools/executor.js +0 -304
- package/dist/tools/file-info.js +0 -49
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -37
- package/dist/tools/grep-tool.js +0 -142
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -88
- package/dist/tools/list-dir.js +0 -53
- package/dist/tools/load-skill.js +0 -51
- package/dist/tools/mcp-call.js +0 -80
- package/dist/tools/meet-done.js +0 -14
- package/dist/tools/move-file.js +0 -94
- package/dist/tools/path-utils.js +0 -108
- package/dist/tools/pipeline-run.js +0 -167
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-ids.js +0 -31
- package/dist/tools/process-kill.js +0 -33
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -49
- package/dist/tools/process-wait.js +0 -63
- package/dist/tools/prompt-io.js +0 -29
- package/dist/tools/question.js +0 -288
- package/dist/tools/read-file.js +0 -200
- package/dist/tools/recall.js +0 -124
- package/dist/tools/registry.js +0 -48
- package/dist/tools/remember.js +0 -72
- package/dist/tools/scope-check.js +0 -32
- package/dist/tools/scope-request.js +0 -96
- package/dist/tools/search-history.js +0 -88
- package/dist/tools/session-info.js +0 -46
- package/dist/tools/set-thinking.js +0 -75
- package/dist/tools/subagent.js +0 -257
- package/dist/tools/syntax-validator.js +0 -379
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -134
- package/dist/tools/web-browse.js +0 -93
- package/dist/tools/web-fetch.js +0 -108
- package/dist/tools/web-search.js +0 -107
- package/dist/tools/write-file.js +0 -128
- package/dist/ui/action-log.js +0 -13
- package/dist/ui/box.js +0 -78
- package/dist/ui/colors.js +0 -6
- package/dist/ui/diff.js +0 -189
- package/dist/ui/index.js +0 -7
- package/dist/ui/line-editor.js +0 -1083
- package/dist/ui/line-math.js +0 -98
- package/dist/ui/md-formatter.js +0 -322
- package/dist/ui/output.js +0 -19
- package/dist/ui/plan-view.js +0 -109
- package/dist/ui/renderer.js +0 -589
- package/dist/ui/spinner.js +0 -91
- package/dist/ui/table.js +0 -142
- package/dist/ui/theme.js +0 -143
- package/dist/utils/child-run.js +0 -20
- package/dist/utils/error.js +0 -4
- package/dist/utils/index.js +0 -6
- package/dist/utils/retry.js +0 -17
- package/dist/utils/sleep.js +0 -23
- package/dist/utils/truncate.js +0 -9
package/dist/assets/js/feed.js
CHANGED
|
@@ -1,1250 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// All SSE `on*` handlers live here; `app.js` only wires them to EventSource.
|
|
3
|
-
import { mountMarkdown, renderMarkdown, copyText } from "../md.js";
|
|
4
|
-
import {
|
|
5
|
-
msgs, planEl, planCountEl, usageEl, envEl, wsLine,
|
|
6
|
-
el, icon, setIcon, link, announce,
|
|
7
|
-
} from "./dom.js";
|
|
8
|
-
import { api } from "./api.js";
|
|
9
|
-
import { I18N, t, pluralCount } from "./i18n.js";
|
|
10
|
-
import { S } from "./store.js";
|
|
11
|
-
import {
|
|
12
|
-
FILE_TOOLS, OPENABLE_TOOLS, IMAGE_TOOLS, LINK_TOOLS,
|
|
13
|
-
categoryOf, labelOf, argText, formatMs, verbosity, firstOutputLine, fmtTokens,
|
|
14
|
-
} from "./format.js";
|
|
15
|
-
|
|
16
|
-
function insertAfter(ref, node) {
|
|
17
|
-
if (ref && ref.parentNode) ref.parentNode.insertBefore(node, ref.nextSibling);
|
|
18
|
-
else appendTo(liveFeed(), node);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function emptyView() {
|
|
23
|
-
const wrap = el("div", "empty");
|
|
24
|
-
wrap.appendChild(el("span", "g", "✦"));
|
|
25
|
-
wrap.appendChild(document.createTextNode(" " + t("web.empty.title")));
|
|
26
|
-
wrap.appendChild(document.createElement("br"));
|
|
27
|
-
wrap.appendChild(el("span", "hint", t("web.empty.hint")));
|
|
28
|
-
return wrap;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function makeFeed() {
|
|
32
|
-
const feed = el("div", "feed");
|
|
33
|
-
feed.appendChild(emptyView());
|
|
34
|
-
return feed;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function showEmptyFeed() {
|
|
38
|
-
msgs.replaceChildren(emptyView());
|
|
39
|
-
follow = true;
|
|
40
|
-
hideToLatest();
|
|
41
|
-
syncUrl();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function clearFeed(feed) {
|
|
45
|
-
if (!feed) return;
|
|
46
|
-
feed.replaceChildren(emptyView());
|
|
47
|
-
follow = true;
|
|
48
|
-
hideToLatest();
|
|
49
|
-
const tab = S.tabs.find((x) => x.feed === feed);
|
|
50
|
-
if (tab) tab.planCard = null;
|
|
51
|
-
if (feed === activeFeed()) scroll();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function activeFeed() {
|
|
55
|
-
const tab = S.tabs.find((x) => x.id === S.activeTabId);
|
|
56
|
-
return tab ? tab.feed : null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function liveFeed() {
|
|
60
|
-
const tab = S.tabs.find((x) => x.id === S.liveTabId) || S.tabs.find((x) => x.id === S.activeTabId);
|
|
61
|
-
return tab ? tab.feed : null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Route a server event to the tab that owns its session (item from `broadcast`
|
|
65
|
-
// enrichment); fall back to the live/active tab for events without one.
|
|
66
|
-
function feedForEvent(sessionId) {
|
|
67
|
-
if (sessionId) {
|
|
68
|
-
const tab = S.tabs.find((x) => x.id === sessionId);
|
|
69
|
-
if (tab) return tab.feed;
|
|
70
|
-
}
|
|
71
|
-
return liveFeed();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function nearBottom() {
|
|
75
|
-
return msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 140;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Sticky follow: while the reader is at the live edge we pin to the bottom
|
|
79
|
-
// on every append; the moment they scroll up we leave them alone (the
|
|
80
|
-
// "jump to latest" pill offers the trip back). A new user turn always
|
|
81
|
-
// force-scrolls — otherwise one missed pin cascades into "no autoscroll"
|
|
82
|
-
// for the whole turn, since every later `scroll()` would see `nearBottom()`
|
|
83
|
-
// as false and skip.
|
|
84
|
-
let follow = true;
|
|
85
|
-
|
|
86
|
-
function scroll(force) {
|
|
87
|
-
if (force || follow || nearBottom()) {
|
|
88
|
-
msgs.scrollTop = msgs.scrollHeight;
|
|
89
|
-
follow = true;
|
|
90
|
-
hideToLatest();
|
|
91
|
-
} else {
|
|
92
|
-
maybeShowToLatest();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Force-pin to the live edge (send / history load / pill click).
|
|
97
|
-
export function scrollToBottom(smooth) {
|
|
98
|
-
follow = true;
|
|
99
|
-
hideToLatest();
|
|
100
|
-
if (smooth && typeof msgs.scrollTo === "function") {
|
|
101
|
-
try {
|
|
102
|
-
msgs.scrollTo({ top: msgs.scrollHeight, behavior: "smooth" });
|
|
103
|
-
return;
|
|
104
|
-
} catch (err) {
|
|
105
|
-
/* fall through to instant */
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
msgs.scrollTop = msgs.scrollHeight;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function isFollowing() {
|
|
112
|
-
return follow;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Floating "jump to latest" pill: appears only when the reader scrolled up
|
|
116
|
-
// and new content landed below. Created lazily so tests/SSR without `.chat`
|
|
117
|
-
// never break.
|
|
118
|
-
let toLatestBtn = null;
|
|
119
|
-
function ensureToLatest() {
|
|
120
|
-
if (toLatestBtn) return toLatestBtn;
|
|
121
|
-
const chat = document.querySelector(".chat");
|
|
122
|
-
if (!chat) return null;
|
|
123
|
-
toLatestBtn = el("button", "to-latest");
|
|
124
|
-
toLatestBtn.type = "button";
|
|
125
|
-
toLatestBtn.hidden = true;
|
|
126
|
-
toLatestBtn.addEventListener("click", () => scrollToBottom(true));
|
|
127
|
-
chat.appendChild(toLatestBtn);
|
|
128
|
-
return toLatestBtn;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function maybeShowToLatest() {
|
|
132
|
-
const btn = ensureToLatest();
|
|
133
|
-
if (!btn) return;
|
|
134
|
-
btn.textContent = "↓ " + t("web.scroll.latest");
|
|
135
|
-
btn.hidden = false;
|
|
136
|
-
msgs.classList.add("unfollowed");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function hideToLatest() {
|
|
140
|
-
if (toLatestBtn) toLatestBtn.hidden = true;
|
|
141
|
-
msgs.classList.remove("unfollowed");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
msgs.addEventListener("scroll", () => {
|
|
145
|
-
const nb = nearBottom();
|
|
146
|
-
follow = nb;
|
|
147
|
-
msgs.classList.toggle("scrolled", msgs.scrollTop > 24);
|
|
148
|
-
if (nb) hideToLatest();
|
|
149
|
-
else if (liveFeed() && feedHasContent(liveFeed())) maybeShowToLatest();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
export function appendTo(feed, node) {
|
|
153
|
-
if (!feed) return node;
|
|
154
|
-
feed.appendChild(node);
|
|
155
|
-
if (feed === activeFeed()) scroll();
|
|
156
|
-
return node;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function append(node) {
|
|
160
|
-
return appendTo(liveFeed(), node);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function clearEmpty(feed) {
|
|
164
|
-
const empty = feed && feed.querySelector(".empty");
|
|
165
|
-
if (empty) empty.remove();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function clearBusy(feed) {
|
|
169
|
-
if (S.busyTimer) {
|
|
170
|
-
clearInterval(S.busyTimer);
|
|
171
|
-
S.busyTimer = null;
|
|
172
|
-
}
|
|
173
|
-
feed = feed || liveFeed();
|
|
174
|
-
if (!feed) return;
|
|
175
|
-
const line = feed.querySelector(".busy");
|
|
176
|
-
if (line) line.remove();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Show the in-feed "thinking" line. Called immediately after a successful
|
|
180
|
-
// `/api/message` POST (202) so the wait during a slow first-run
|
|
181
|
-
// summarization is visible before the first `phase` event arrives; the
|
|
182
|
-
// normal event flow (`onText`/`onPhase`/`onTurnEnd`) clears it.
|
|
183
|
-
export function showBusy(feed) {
|
|
184
|
-
feed = feed || liveFeed();
|
|
185
|
-
if (!feed) return;
|
|
186
|
-
// The delayed post-send call must not flash a spinner over a stream that
|
|
187
|
-
// already started (fast first token).
|
|
188
|
-
if (S.curMd || S.curThink) return;
|
|
189
|
-
clearEmpty(feed);
|
|
190
|
-
if (feed.querySelector(".busy")) return;
|
|
191
|
-
if (S.busyTimer) {
|
|
192
|
-
clearInterval(S.busyTimer);
|
|
193
|
-
S.busyTimer = null;
|
|
194
|
-
}
|
|
195
|
-
const line = el("div", "busy");
|
|
196
|
-
line.appendChild(el("span", "spin", "✦"));
|
|
197
|
-
line.appendChild(el("span", "lab", t("web.action.thinking")));
|
|
198
|
-
const time = el("span", "busytime", "· 0s");
|
|
199
|
-
line.appendChild(time);
|
|
200
|
-
line.appendChild(el("span", "", "· " + t("web.input.esc")));
|
|
201
|
-
appendTo(feed, line);
|
|
202
|
-
const started = Date.now();
|
|
203
|
-
S.busyTimer = setInterval(() => {
|
|
204
|
-
if (!line.parentNode) {
|
|
205
|
-
clearInterval(S.busyTimer);
|
|
206
|
-
S.busyTimer = null;
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
time.textContent = "· " + Math.round((Date.now() - started) / 1000) + "s";
|
|
210
|
-
}, 1000);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function flushRun() {
|
|
214
|
-
if (S.runCount <= 0) {
|
|
215
|
-
resetRun();
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
// Quiet: a run of successful read-only actions folds into one summary line,
|
|
219
|
-
// replacing the per-action lines (mirrors `Renderer.flushRecon`). `normal`
|
|
220
|
-
// and `verbose` keep the individual action lines and add nothing.
|
|
221
|
-
if (verbosity() === "quiet" && S.runReconOnly && S.runFails === 0 && S.runCount > 1) {
|
|
222
|
-
// Fade the folded lines out instead of snapping them away.
|
|
223
|
-
const folding = S.runNodes.slice();
|
|
224
|
-
folding.forEach((node) => node.classList.add("fold-out"));
|
|
225
|
-
window.setTimeout(() => folding.forEach((node) => node.remove()), 160);
|
|
226
|
-
const line = el("div", "grouped");
|
|
227
|
-
line.appendChild(el("span", "pipe", "│"));
|
|
228
|
-
const dur = formatMs(S.runDuration);
|
|
229
|
-
line.appendChild(
|
|
230
|
-
document.createTextNode(
|
|
231
|
-
" " + pluralCount("web.action.actions", S.runCount) + " ✓" + (dur ? " · " + dur : ""),
|
|
232
|
-
),
|
|
233
|
-
);
|
|
234
|
-
appendTo(liveFeed(), line);
|
|
235
|
-
}
|
|
236
|
-
resetRun();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function resetRun() {
|
|
240
|
-
S.runCount = 0;
|
|
241
|
-
S.runFails = 0;
|
|
242
|
-
S.runReconOnly = true;
|
|
243
|
-
S.runNodes = [];
|
|
244
|
-
S.runDuration = 0;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Options passed to the streaming Markdown renderer: localized code-block
|
|
248
|
-
// copy labels. Highlighting is applied by `md.js` once a fence closes.
|
|
249
|
-
// `animate` wraps live-streamed text chunks in `span.tk` (CSS `tkIn` fade);
|
|
250
|
-
// history render and code blocks skip it.
|
|
251
|
-
function mdOpts(animate) {
|
|
252
|
-
return {
|
|
253
|
-
copyLabel: t("web.code.copy"),
|
|
254
|
-
copiedLabel: t("web.code.copied"),
|
|
255
|
-
copyFailedLabel: t("web.code.copy_failed"),
|
|
256
|
-
animate: animate === true,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Close the active Markdown segment (if any) and clear the text S.state.
|
|
261
|
-
export function finishMd() {
|
|
262
|
-
flushText();
|
|
263
|
-
if (S.curMd) {
|
|
264
|
-
S.curMd.root.classList.remove("streaming");
|
|
265
|
-
S.curMd.end();
|
|
266
|
-
S.curMd = null;
|
|
267
|
-
}
|
|
268
|
-
S.curText = null;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Text chunks arrive in bursts (several SSE `text` events per frame); writing
|
|
272
|
-
// and scrolling on every one thrashes layout and stacks overlapping `tkIn`
|
|
273
|
-
// fades ("рвано"). Buffer them and flush once per animation frame instead.
|
|
274
|
-
let textBuf = "";
|
|
275
|
-
let textFeed = null;
|
|
276
|
-
let textScheduled = false;
|
|
277
|
-
|
|
278
|
-
function flushText() {
|
|
279
|
-
textScheduled = false;
|
|
280
|
-
if (!textBuf || !S.curMd) {
|
|
281
|
-
textBuf = "";
|
|
282
|
-
textFeed = null;
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const buf = textBuf;
|
|
286
|
-
const feed = textFeed;
|
|
287
|
-
textBuf = "";
|
|
288
|
-
textFeed = null;
|
|
289
|
-
S.curMd.write(buf);
|
|
290
|
-
if (feed && feed === activeFeed()) scroll();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function scheduleText() {
|
|
294
|
-
if (textScheduled) return;
|
|
295
|
-
textScheduled = true;
|
|
296
|
-
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
|
297
|
-
window.requestAnimationFrame(flushText);
|
|
298
|
-
} else {
|
|
299
|
-
flushText();
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function onText(chunk, sid) {
|
|
304
|
-
const feed = feedForEvent(sid);
|
|
305
|
-
clearBusy(feed);
|
|
306
|
-
if (!feed) return;
|
|
307
|
-
clearEmpty(feed);
|
|
308
|
-
if (S.curThink) {
|
|
309
|
-
finishThink();
|
|
310
|
-
}
|
|
311
|
-
if (S.curText == null) {
|
|
312
|
-
flushRun();
|
|
313
|
-
S.curText = "";
|
|
314
|
-
S.curMd = mountMarkdown(feed, mdOpts(true));
|
|
315
|
-
S.curMd.root.classList.add("streaming");
|
|
316
|
-
S.turnRoots.push(S.curMd.root);
|
|
317
|
-
}
|
|
318
|
-
S.curText += chunk;
|
|
319
|
-
S.turnMd += chunk;
|
|
320
|
-
// A second session streaming concurrently must not merge into another feed's buffer.
|
|
321
|
-
if (textFeed && textFeed !== feed) flushText();
|
|
322
|
-
textFeed = feed;
|
|
323
|
-
textBuf += chunk;
|
|
324
|
-
scheduleText();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export function finishThink() {
|
|
328
|
-
if (S.curThink) {
|
|
329
|
-
if (S.curThink.wrap) S.curThink.wrap.classList.remove("live");
|
|
330
|
-
if (S.curThink.startedAt) {
|
|
331
|
-
const dur = S.curThink.head.querySelector(".dur");
|
|
332
|
-
if (dur) dur.textContent = "· " + ((Date.now() - S.curThink.startedAt) / 1000).toFixed(1) + "s";
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
S.curThink = null;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Build the collapsible reasoning block. The global `.think-h` click handler
|
|
339
|
-
// depends on this exact structure: wrap > [head, body], body carries `hidden`.
|
|
340
|
-
// `live` marks an in-progress block (pulsing label); history renders static.
|
|
341
|
-
function buildThinkBlock(withDur, live) {
|
|
342
|
-
const wrap = el("div", "think" + (live === false ? "" : " live"));
|
|
343
|
-
const head = el("button", "think-h");
|
|
344
|
-
head.type = "button";
|
|
345
|
-
head.setAttribute("aria-expanded", "false");
|
|
346
|
-
head.appendChild(el("span", "g", "✦"));
|
|
347
|
-
head.appendChild(el("span", "lab", t("web.action.thinking")));
|
|
348
|
-
if (withDur) head.appendChild(el("span", "dur", ""));
|
|
349
|
-
const tog = el("span", "tog");
|
|
350
|
-
setIcon(tog, "chevron-right", t("web.action.show"));
|
|
351
|
-
head.appendChild(tog);
|
|
352
|
-
const body = el("div", "think-body hidden");
|
|
353
|
-
wrap.appendChild(head);
|
|
354
|
-
wrap.appendChild(body);
|
|
355
|
-
return { wrap, head, body };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export function onReasoning(chunk, sid) {
|
|
359
|
-
const feed = feedForEvent(sid);
|
|
360
|
-
clearBusy(feed);
|
|
361
|
-
if (!feed) return;
|
|
362
|
-
clearEmpty(feed);
|
|
363
|
-
finishMd();
|
|
364
|
-
if (!S.curThink) {
|
|
365
|
-
flushRun();
|
|
366
|
-
const { wrap, head, body } = buildThinkBlock(true);
|
|
367
|
-
appendTo(feed, wrap);
|
|
368
|
-
S.curThink = { wrap, head, body, startedAt: Date.now() };
|
|
369
|
-
}
|
|
370
|
-
S.curThink.body.textContent += chunk;
|
|
371
|
-
if (feed === activeFeed()) scroll();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Generic agent status (context stats, compaction, MoE status, warnings):
|
|
375
|
-
// always visible, one dim line per chunk, never collapsed. Tool bodies
|
|
376
|
-
// (`kind: "tool"` — display/output/diff/failure-gist from ToolBatch) are
|
|
377
|
-
// model-facing and hidden in quiet mode, mirroring the terminal renderer
|
|
378
|
-
// (`writeInlineBody`); failures still surface once via `tool_end` ↳ cause.
|
|
379
|
-
export function onMeta(chunk, sid, kind) {
|
|
380
|
-
if (kind === "tool" && verbosity() === "quiet") return;
|
|
381
|
-
const feed = feedForEvent(sid);
|
|
382
|
-
clearBusy(feed);
|
|
383
|
-
const text = (chunk || "").trim();
|
|
384
|
-
if (!feed || !text) return;
|
|
385
|
-
clearEmpty(feed);
|
|
386
|
-
finishMd();
|
|
387
|
-
const warn = text.indexOf("⚠") !== -1 || text.indexOf("✗") !== -1;
|
|
388
|
-
appendTo(feed, el("div", warn ? "metaln warn" : "metaln", text));
|
|
389
|
-
if (feed === activeFeed()) scroll();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Гейт забраковал уже отстримленный черновик (SSE `retract`): убираем его
|
|
393
|
-
// из ленты до переответа, иначе финал встанет дублем следом
|
|
394
|
-
// (ses_munvrcfz_na9ze3). Копипаст хода тоже чистим — отклонённого текста
|
|
395
|
-
// в «copy / copy as markdown» быть не должно. Без видимого черновика
|
|
396
|
-
// (пустой ответ, только тулы) — тихо, без шумовой строки.
|
|
397
|
-
export function onRetract(reason, sid) {
|
|
398
|
-
const feed = feedForEvent(sid);
|
|
399
|
-
if (!feed) return;
|
|
400
|
-
clearBusy(feed);
|
|
401
|
-
const draftRoot = S.curMd ? S.curMd.root : null;
|
|
402
|
-
const hadDraft = !!draftRoot || (S.curText != null && S.curText !== "");
|
|
403
|
-
const draftLen = S.curText ? S.curText.length : 0;
|
|
404
|
-
finishMd();
|
|
405
|
-
finishThink();
|
|
406
|
-
if (draftRoot) {
|
|
407
|
-
const idx = S.turnRoots.indexOf(draftRoot);
|
|
408
|
-
if (idx !== -1) S.turnRoots.splice(idx, 1);
|
|
409
|
-
draftRoot.remove();
|
|
410
|
-
}
|
|
411
|
-
if (draftLen > 0) S.turnMd = S.turnMd.slice(0, Math.max(0, S.turnMd.length - draftLen));
|
|
412
|
-
if (!hadDraft) return;
|
|
413
|
-
if (reason) appendTo(feed, el("div", "metaln", reason));
|
|
414
|
-
if (feed === activeFeed()) scroll();
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export function onPhase(phase, sid) {
|
|
418
|
-
const feed = feedForEvent(sid);
|
|
419
|
-
if (phase === "thinking") {
|
|
420
|
-
showBusy(feed);
|
|
421
|
-
} else {
|
|
422
|
-
clearBusy(feed);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export function onToolStart(data) {
|
|
427
|
-
const feed = feedForEvent(data.sessionId);
|
|
428
|
-
clearBusy(feed);
|
|
429
|
-
if (!feed) return;
|
|
430
|
-
clearEmpty(feed);
|
|
431
|
-
finishMd();
|
|
432
|
-
finishThink();
|
|
433
|
-
S.pendingArgs.set(data.toolCallId, data.args || {});
|
|
434
|
-
const line = el("div", "act " + categoryOf(data.name));
|
|
435
|
-
line.appendChild(el("span", "g", "◇"));
|
|
436
|
-
line.appendChild(el("span", "lab", labelOf(data.name)));
|
|
437
|
-
const args = argText(data.args);
|
|
438
|
-
if (args) {
|
|
439
|
-
line.appendChild(el("span", "sep", "·"));
|
|
440
|
-
line.appendChild(el("span", "args", args));
|
|
441
|
-
}
|
|
442
|
-
const status = el("span", "ok", "");
|
|
443
|
-
line.appendChild(status);
|
|
444
|
-
line.classList.add("running");
|
|
445
|
-
appendTo(feed, line);
|
|
446
|
-
S.toolNodes.set(data.toolCallId, { status, line });
|
|
447
|
-
if (categoryOf(data.name) !== "recon") S.runReconOnly = false;
|
|
448
|
-
S.runCount++;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function renderToolOutput(line, text) {
|
|
452
|
-
if (typeof text !== "string" || !text) return;
|
|
453
|
-
const wrap = el("div", "toolout");
|
|
454
|
-
const code = el("div", "code");
|
|
455
|
-
code.textContent = text;
|
|
456
|
-
wrap.appendChild(code);
|
|
457
|
-
const long = text.length > 600 || text.split("\n").length > 12;
|
|
458
|
-
if (long) {
|
|
459
|
-
wrap.classList.add("collapsed");
|
|
460
|
-
const outLabel = "▸ " + pluralCount("web.tool.output", text.length);
|
|
461
|
-
const more = el("span", "more", outLabel);
|
|
462
|
-
more.addEventListener("click", () => {
|
|
463
|
-
const collapsed = wrap.classList.toggle("collapsed");
|
|
464
|
-
more.textContent = collapsed ? outLabel : "▾ " + t("web.tool.collapse");
|
|
465
|
-
});
|
|
466
|
-
wrap.appendChild(more);
|
|
467
|
-
}
|
|
468
|
-
const feed = line && line.parentNode ? null : liveFeed();
|
|
469
|
-
if (line && line.parentNode) line.parentNode.insertBefore(wrap, line.nextSibling);
|
|
470
|
-
else appendTo(feed, wrap);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function safeUrl(url) {
|
|
474
|
-
if (typeof url !== "string" || !url) return null;
|
|
475
|
-
try {
|
|
476
|
-
const u = new URL(url, location.href);
|
|
477
|
-
if (u.protocol === "http:" || u.protocol === "https:" || u.protocol === "file:") {
|
|
478
|
-
return u.href;
|
|
479
|
-
}
|
|
480
|
-
} catch (err) {
|
|
481
|
-
return null;
|
|
482
|
-
}
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// File rows are always expanded: one `filerow` per file-tool call, no
|
|
487
|
-
// collapsing group. Consecutive rows stay a plain expanded list (the old
|
|
488
|
-
// E grouping with a chevron toggle hid the list and failed to open).
|
|
489
|
-
function displayPathFromRoot(path) {
|
|
490
|
-
const raw = String(path == null ? "" : path).replace(/\\/g, "/");
|
|
491
|
-
try {
|
|
492
|
-
const ws = S && S.state && S.state.host && S.state.host.workspace
|
|
493
|
-
? String(S.state.host.workspace).replace(/\\/g, "/").replace(/\/+$/, "")
|
|
494
|
-
: "";
|
|
495
|
-
if (ws && (raw === ws || raw.indexOf(ws + "/") === 0)) {
|
|
496
|
-
const rel = raw === ws ? "" : raw.slice(ws.length + 1);
|
|
497
|
-
if (rel) return rel;
|
|
498
|
-
}
|
|
499
|
-
} catch (err) {
|
|
500
|
-
/* fail-open: show raw path */
|
|
501
|
-
}
|
|
502
|
-
return raw;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function cardFor(name, args, sid, ok) {
|
|
506
|
-
const feed = feedForEvent(sid);
|
|
507
|
-
if (!feed) return null;
|
|
508
|
-
if (FILE_TOOLS.indexOf(name) !== -1) {
|
|
509
|
-
const path = args.path || args.file || args.dir;
|
|
510
|
-
if (!path) return null;
|
|
511
|
-
const shown = displayPathFromRoot(path);
|
|
512
|
-
const row = el("div", "filerow " + categoryOf(name));
|
|
513
|
-
row.appendChild(el("span", "g", "◇"));
|
|
514
|
-
row.appendChild(el("span", "lab", labelOf(name)));
|
|
515
|
-
row.appendChild(el("span", "sep", " · "));
|
|
516
|
-
if (OPENABLE_TOOLS.indexOf(name) !== -1) {
|
|
517
|
-
const openLink = el("a", "path", shown || String(path));
|
|
518
|
-
openLink.href = "/api/file?path=" + encodeURIComponent(String(path));
|
|
519
|
-
openLink.target = "_blank";
|
|
520
|
-
openLink.rel = "noopener noreferrer";
|
|
521
|
-
openLink.title = t("web.card.open_title") + " · " + String(path);
|
|
522
|
-
row.appendChild(openLink);
|
|
523
|
-
} else {
|
|
524
|
-
const s = el("span", "tail", shown || String(path));
|
|
525
|
-
s.title = String(path);
|
|
526
|
-
row.appendChild(s);
|
|
527
|
-
}
|
|
528
|
-
const copyBtn = el("button", "copy-btn");
|
|
529
|
-
copyBtn.type = "button";
|
|
530
|
-
copyBtn.title = t("web.card.copy_title");
|
|
531
|
-
copyBtn.setAttribute("aria-label", t("web.card.copy_title"));
|
|
532
|
-
copyBtn.appendChild(icon("copy"));
|
|
533
|
-
copyBtn.addEventListener("click", () => {
|
|
534
|
-
copyText(String(path)).then((ok) => {
|
|
535
|
-
copyBtn.replaceChildren(icon(ok ? "check" : "copy"));
|
|
536
|
-
if (!ok) { const st = row.querySelector(".st"); if (st) st.textContent = " ⚠"; }
|
|
537
|
-
window.setTimeout(() => copyBtn.replaceChildren(icon("copy")), 1400);
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
row.appendChild(copyBtn);
|
|
541
|
-
row.appendChild(el("span", "st", ok === false ? " ✗" : ok === true ? " ✓" : ""));
|
|
542
|
-
if (ok === false) row.dataset.fail = "1";
|
|
543
|
-
if (/\.drawio(\.xml)?$/i.test(String(path)) && verbosity() === "verbose") {
|
|
544
|
-
row.appendChild(el("span", "detail", "↳ " + t("web.card.drawio_hint")));
|
|
545
|
-
}
|
|
546
|
-
appendTo(feed, row);
|
|
547
|
-
return row;
|
|
548
|
-
}
|
|
549
|
-
if (LINK_TOOLS.indexOf(name) !== -1 && args.url) {
|
|
550
|
-
const line = el("div", "linkline");
|
|
551
|
-
line.appendChild(document.createTextNode("↳ "));
|
|
552
|
-
const safe = safeUrl(args.url);
|
|
553
|
-
if (safe) {
|
|
554
|
-
const a = el("a", "", args.url);
|
|
555
|
-
a.href = safe;
|
|
556
|
-
a.target = "_blank";
|
|
557
|
-
a.rel = "noopener noreferrer";
|
|
558
|
-
line.appendChild(a);
|
|
559
|
-
try {
|
|
560
|
-
const host = new URL(safe).hostname;
|
|
561
|
-
if (host) line.appendChild(el("span", "dom", " · " + host));
|
|
562
|
-
} catch (err) {
|
|
563
|
-
/* no hostname */
|
|
564
|
-
}
|
|
565
|
-
} else {
|
|
566
|
-
line.appendChild(el("span", "", args.url));
|
|
567
|
-
}
|
|
568
|
-
appendTo(feed, line);
|
|
569
|
-
return line;
|
|
570
|
-
}
|
|
571
|
-
if (IMAGE_TOOLS.indexOf(name) !== -1) {
|
|
572
|
-
const card = el("div", "imgcard");
|
|
573
|
-
card.appendChild(el("div", "ph", "◧ " + t("web.card.screenshot")));
|
|
574
|
-
card.appendChild(el("div", "cap", name === "attach_image" ? t("web.card.attached_image") : t("web.card.browser_viewport")));
|
|
575
|
-
appendTo(feed, card);
|
|
576
|
-
return card;
|
|
577
|
-
}
|
|
578
|
-
return null;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
export function onToolEnd(data) {
|
|
582
|
-
const node = S.toolNodes.get(data.toolCallId);
|
|
583
|
-
const line = node ? node.line : null;
|
|
584
|
-
if (node) {
|
|
585
|
-
node.line.classList.remove("running");
|
|
586
|
-
const dur = formatMs(data.durationMs);
|
|
587
|
-
node.status.textContent = data.success ? " ✓" + (dur ? " " + dur : "") : " ✗" + (dur ? " " + dur : "");
|
|
588
|
-
if (!data.success) {
|
|
589
|
-
node.status.classList.add("fail");
|
|
590
|
-
S.runFails++;
|
|
591
|
-
S.runReconOnly = false;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
S.toolNodes.delete(data.toolCallId);
|
|
595
|
-
const args = S.pendingArgs.get(data.toolCallId) || {};
|
|
596
|
-
S.pendingArgs.delete(data.toolCallId);
|
|
597
|
-
// File tools render as a single filerow (viewer link + copy): the `.act`
|
|
598
|
-
// line from `onToolStart` is replaced, otherwise the feed shows the same
|
|
599
|
-
// action twice (act + filerow). Without a path `cardFor` renders nothing,
|
|
600
|
-
// so keep the generic act line in that case.
|
|
601
|
-
const filePath = args.path || args.file || args.dir;
|
|
602
|
-
if (FILE_TOOLS.indexOf(data.name) !== -1 && filePath) {
|
|
603
|
-
if (line && line.parentNode) line.remove();
|
|
604
|
-
if (verbosity() === "quiet") {
|
|
605
|
-
if (data.success) {
|
|
606
|
-
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
607
|
-
if (card) S.runNodes.push(card);
|
|
608
|
-
S.runDuration += typeof data.durationMs === "number" ? data.durationMs : 0;
|
|
609
|
-
} else {
|
|
610
|
-
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
611
|
-
const cause = firstOutputLine(data.output);
|
|
612
|
-
if (cause) insertAfter(card || line, el("div", "detail", "↳ " + cause));
|
|
613
|
-
if (card) S.runNodes.push(card);
|
|
614
|
-
}
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
618
|
-
renderToolOutput(card || line, data.output);
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
if (verbosity() === "quiet") {
|
|
622
|
-
// Quiet hides tool bodies. Track successful nodes so a run of read-only
|
|
623
|
-
// actions can fold at the next segment; a failure keeps its line and adds
|
|
624
|
-
// a single `↳` cause instead of the whole model-facing body.
|
|
625
|
-
if (data.success) {
|
|
626
|
-
if (line) S.runNodes.push(line);
|
|
627
|
-
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
628
|
-
if (card) S.runNodes.push(card);
|
|
629
|
-
S.runDuration += typeof data.durationMs === "number" ? data.durationMs : 0;
|
|
630
|
-
} else {
|
|
631
|
-
const cause = firstOutputLine(data.output);
|
|
632
|
-
if (cause) insertAfter(line, el("div", "detail", "↳ " + cause));
|
|
633
|
-
}
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
if (data.success) cardFor(data.name, args, data.sessionId, data.success);
|
|
637
|
-
renderToolOutput(line, data.output);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
export function onTurnEnd(data) {
|
|
641
|
-
S.state.busy = false;
|
|
642
|
-
const feed = feedForEvent(data.sessionId);
|
|
643
|
-
clearBusy(feed);
|
|
644
|
-
finishThink();
|
|
645
|
-
finishMd();
|
|
646
|
-
flushRun();
|
|
647
|
-
// Interrupted tools never get `tool_end`: drop their pulse so nothing
|
|
648
|
-
// looks alive after the turn is over.
|
|
649
|
-
S.toolNodes.forEach((n) => {
|
|
650
|
-
if (n && n.line && feed && n.line.parentNode === feed) n.line.classList.remove("running");
|
|
651
|
-
});
|
|
652
|
-
const owner = S.tabs.find((x) => x.id === data.sessionId);
|
|
653
|
-
if (owner) owner.planCard = null;
|
|
654
|
-
if (!feed) {
|
|
655
|
-
resetTurnText();
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
if (data.success === false) {
|
|
659
|
-
if (data.interrupted) {
|
|
660
|
-
appendTo(feed, el("div", "warnline", "⚠ " + t("web.turn.stopped")));
|
|
661
|
-
announce(t("web.turn.stopped"));
|
|
662
|
-
} else {
|
|
663
|
-
appendTo(feed, el("div", "errline", "✗ " + t("web.turn.failed")));
|
|
664
|
-
announce(t("web.turn.failed"));
|
|
665
|
-
}
|
|
666
|
-
} else {
|
|
667
|
-
const parts = [];
|
|
668
|
-
if (typeof data.toolCalls === "number" && data.toolCalls > 0) {
|
|
669
|
-
parts.push(t("web.turn.tools", { count: data.toolCalls }));
|
|
670
|
-
}
|
|
671
|
-
if (typeof data.durationMs === "number") parts.push(formatMs(data.durationMs));
|
|
672
|
-
if (parts.length) appendTo(feed, el("div", "metric", "⟡ " + parts.join(" · ")));
|
|
673
|
-
// The visible turn-end banner is redundant noise; keep the screen-reader cue.
|
|
674
|
-
announce(t("web.turn.end", { count: data.toolCalls || 0 }));
|
|
675
|
-
}
|
|
676
|
-
const plain = S.turnRoots.map((r) => r.innerText || r.textContent || "").join("\n\n");
|
|
677
|
-
appendActionsRow(feed, plain, S.turnMd);
|
|
678
|
-
resetTurnText();
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
export function resetTurnText() {
|
|
682
|
-
S.turnMd = "";
|
|
683
|
-
S.turnRoots = [];
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Per-message copy actions (whole response as plain text / as Markdown).
|
|
687
|
-
function copyAction(payload, labelKey) {
|
|
688
|
-
const btn = el("button", "act-btn", t(labelKey));
|
|
689
|
-
btn.type = "button";
|
|
690
|
-
btn.addEventListener("click", () => {
|
|
691
|
-
copyText(payload).then((ok) => {
|
|
692
|
-
btn.textContent = ok ? "✓ " + t("web.code.copied") : "⚠ " + t("web.code.copy_failed");
|
|
693
|
-
window.setTimeout(() => {
|
|
694
|
-
btn.textContent = t(labelKey);
|
|
695
|
-
}, 1400);
|
|
696
|
-
});
|
|
697
|
-
});
|
|
698
|
-
return btn;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function appendActionsRow(feed, plain, markdown) {
|
|
702
|
-
const text = String(plain == null ? "" : plain);
|
|
703
|
-
const md = String(markdown == null ? "" : markdown);
|
|
704
|
-
if (!feed || (!text.trim() && !md.trim())) return;
|
|
705
|
-
const bar = el("div", "msg-actions");
|
|
706
|
-
if (text.trim()) bar.appendChild(copyAction(text, "web.msg.copy"));
|
|
707
|
-
if (md.trim()) bar.appendChild(copyAction(md, "web.msg.copy_md"));
|
|
708
|
-
appendTo(feed, bar);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
export function onErrorEvent(data) {
|
|
712
|
-
if (!data || !data.error) return;
|
|
713
|
-
const feed = feedForEvent(data.sessionId);
|
|
714
|
-
if (feed) appendTo(feed, el("div", "errline", "✗ " + data.error));
|
|
715
|
-
announce(String(data.error));
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
export function onCommandOutput(text, sid) {
|
|
719
|
-
const feed = feedForEvent(sid) || activeFeed();
|
|
720
|
-
if (!feed) return;
|
|
721
|
-
clearEmpty(feed);
|
|
722
|
-
String(text)
|
|
723
|
-
.split("\n")
|
|
724
|
-
.forEach((line) => appendTo(feed, el("div", "cmdout", line)));
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Server-forwarded `OutputBus` line (system-prompt overflow, context probe,
|
|
728
|
-
// summarization). Text is already redacted server-side; render via
|
|
729
|
-
// `textContent` so nothing untrusted is parsed as HTML.
|
|
730
|
-
export function onLog(data) {
|
|
731
|
-
if (!data || typeof data.text !== "string" || !data.text) return;
|
|
732
|
-
// Internal loop diagnostics (retry counters, audit/compaction internals) are
|
|
733
|
-
// session-log material — the chat shows them only in verbose mode.
|
|
734
|
-
if (data.diagnostic && verbosity() !== "verbose") return;
|
|
735
|
-
const feed = feedForEvent(data.sessionId) || activeFeed();
|
|
736
|
-
if (!feed) return;
|
|
737
|
-
const level = data.level;
|
|
738
|
-
const cls = level === "error" ? "errline" : level === "warn" ? "warnline" : "detail";
|
|
739
|
-
const glyph = level === "error" ? "✗ " : level === "warn" ? "⚠ " : "↳ ";
|
|
740
|
-
const line = el("div", cls);
|
|
741
|
-
line.textContent = glyph + data.text;
|
|
742
|
-
appendTo(feed, line);
|
|
743
|
-
if (level === "error" || level === "warn") announce(data.text);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// In-loop critic verdicts are session-log only (`critic_verdict` in
|
|
747
|
-
// `session.jsonl`): the chat keeps user-facing notices and verbose-mode
|
|
748
|
-
// diagnostics, not verifier verdicts.
|
|
749
|
-
|
|
750
|
-
function renderAssistantText(feed, text) {
|
|
751
|
-
const raw = String(text == null ? "" : text);
|
|
752
|
-
if (!raw.trim()) return;
|
|
753
|
-
const root = renderMarkdown(feed, raw, mdOpts());
|
|
754
|
-
appendActionsRow(feed, root.innerText || root.textContent || "", raw);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function renderThinkBlock(feed, text) {
|
|
758
|
-
const { wrap, body } = buildThinkBlock(false, false);
|
|
759
|
-
body.textContent = text || "";
|
|
760
|
-
feed.appendChild(wrap);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function renderHistoryAct(feed, entry, statusNodes) {
|
|
764
|
-
const line = el("div", "act " + categoryOf(entry.name));
|
|
765
|
-
line.appendChild(el("span", "g", "◇"));
|
|
766
|
-
line.appendChild(el("span", "lab", labelOf(entry.name)));
|
|
767
|
-
const args = argText(entry.args);
|
|
768
|
-
if (args) {
|
|
769
|
-
line.appendChild(el("span", "sep", "·"));
|
|
770
|
-
line.appendChild(el("span", "args", args));
|
|
771
|
-
}
|
|
772
|
-
const status = el("span", "ok", "");
|
|
773
|
-
line.appendChild(status);
|
|
774
|
-
feed.appendChild(line);
|
|
775
|
-
if (entry.toolCallId) statusNodes.set(entry.toolCallId, status);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function renderHistoryResult(feed, entry, statusNodes) {
|
|
779
|
-
const dur = formatMs(entry.duration);
|
|
780
|
-
const mark = (entry.success ? " ✓" : " ✗") + (dur ? " " + dur : "");
|
|
781
|
-
const status = entry.toolCallId ? statusNodes.get(entry.toolCallId) : null;
|
|
782
|
-
if (status) {
|
|
783
|
-
status.textContent = mark;
|
|
784
|
-
if (!entry.success) status.classList.add("fail");
|
|
785
|
-
statusNodes.delete(entry.toolCallId);
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
const line = el("div", "act " + categoryOf(entry.tool));
|
|
789
|
-
line.appendChild(el("span", "g", "↳"));
|
|
790
|
-
line.appendChild(el("span", "lab", labelOf(entry.tool)));
|
|
791
|
-
line.appendChild(el("span", "ok" + (entry.success ? "" : " fail"), mark));
|
|
792
|
-
feed.appendChild(line);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function renderHistory(feed, entries) {
|
|
796
|
-
const statusNodes = new Map();
|
|
797
|
-
entries.forEach((entry) => {
|
|
798
|
-
if (!entry || typeof entry.kind !== "string") return;
|
|
799
|
-
switch (entry.kind) {
|
|
800
|
-
case "user":
|
|
801
|
-
feed.appendChild(el("div", "u", entry.text || ""));
|
|
802
|
-
break;
|
|
803
|
-
case "assistant":
|
|
804
|
-
renderAssistantText(feed, entry.text || "");
|
|
805
|
-
break;
|
|
806
|
-
case "reasoning":
|
|
807
|
-
renderThinkBlock(feed, entry.text || "");
|
|
808
|
-
break;
|
|
809
|
-
case "tool_call":
|
|
810
|
-
renderHistoryAct(feed, entry, statusNodes);
|
|
811
|
-
break;
|
|
812
|
-
case "tool_result":
|
|
813
|
-
renderHistoryResult(feed, entry, statusNodes);
|
|
814
|
-
break;
|
|
815
|
-
case "plan":
|
|
816
|
-
if (entry.text) feed.appendChild(el("div", "sysline", "▸ " + entry.text));
|
|
817
|
-
break;
|
|
818
|
-
case "context":
|
|
819
|
-
if (entry.text) feed.appendChild(el("div", "detail", "↳ " + entry.text));
|
|
820
|
-
break;
|
|
821
|
-
case "error":
|
|
822
|
-
feed.appendChild(el("div", "errline", "✗ " + (entry.text || "")));
|
|
823
|
-
break;
|
|
824
|
-
case "system":
|
|
825
|
-
if (entry.text) feed.appendChild(el("div", "sysline", entry.text));
|
|
826
|
-
break;
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
export function feedHasContent(feed) {
|
|
832
|
-
return !!feed.querySelector(".u, .md, .ans, .code, .act, .think, .metaln, .errline, .sysline");
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
export async function loadHistory(tab) {
|
|
836
|
-
if (!tab || tab.historyLoaded || tab.historyLoading) return;
|
|
837
|
-
tab.historyLoading = true;
|
|
838
|
-
const placeholder = el("div", "detail", t("web.history.loading"));
|
|
839
|
-
if (!feedHasContent(tab.feed)) {
|
|
840
|
-
clearEmpty(tab.feed);
|
|
841
|
-
tab.feed.appendChild(placeholder);
|
|
842
|
-
}
|
|
843
|
-
const r = await api("/api/sessions/" + encodeURIComponent(tab.id) + "/history");
|
|
844
|
-
tab.historyLoading = false;
|
|
845
|
-
placeholder.remove();
|
|
846
|
-
tab.historyLoaded = true;
|
|
847
|
-
if (!r.ok || !r.data || !Array.isArray(r.data.history)) {
|
|
848
|
-
if (!feedHasContent(tab.feed)) tab.feed.appendChild(el("div", "detail", t("web.history.unavailable")));
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
// A live session already rendered its content over SSE: don't duplicate it.
|
|
852
|
-
if (feedHasContent(tab.feed)) return;
|
|
853
|
-
renderHistory(tab.feed, r.data.history);
|
|
854
|
-
if (!r.data.history.length) tab.feed.appendChild(el("div", "detail", t("web.history.empty")));
|
|
855
|
-
if (tab.feed === activeFeed()) scrollToBottom();
|
|
856
|
-
else if (tab.feed === liveFeed()) scrollToBottom();
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
export function syncUrl() {
|
|
860
|
-
const url = new URL(location.href);
|
|
861
|
-
const tab = S.tabs.find((x) => x.id === S.activeTabId);
|
|
862
|
-
if (tab && tab.kind === "session") url.searchParams.set("session", tab.id);
|
|
863
|
-
else url.searchParams.delete("session");
|
|
864
|
-
history.replaceState(null, "", url);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// One marking for a plan step, shared by the sidebar (`renderPlan`) and the
|
|
868
|
-
// feed card (`onPlan`): status class + leading glyph for done/failed/current.
|
|
869
|
-
function planStepMark(step, isCurrent) {
|
|
870
|
-
const status =
|
|
871
|
-
step.status === "done" ? "done" : step.status === "failed" ? "fail" : isCurrent ? "cur" : "";
|
|
872
|
-
const glyph =
|
|
873
|
-
step.status === "done" ? "✓ " : step.status === "failed" ? "✗ " : isCurrent ? "▸ " : "○ ";
|
|
874
|
-
return { status, glyph };
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
export function renderPlan(plan) {
|
|
878
|
-
// The sidebar polls `/api/state` every 2s; skip a rebuild (and the scroll
|
|
879
|
-
// reset it would cause) when the snapshot is unchanged.
|
|
880
|
-
const sig = JSON.stringify([plan, I18N.locale]);
|
|
881
|
-
if (sig === S.lastPlanSig) return;
|
|
882
|
-
S.lastPlanSig = sig;
|
|
883
|
-
planEl.textContent = "";
|
|
884
|
-
if (!plan || !Array.isArray(plan.steps) || !plan.steps.length) {
|
|
885
|
-
planCountEl.textContent = "";
|
|
886
|
-
planEl.appendChild(el("div", "kv dim", t("web.plan.none")));
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
890
|
-
const total = plan.steps.length;
|
|
891
|
-
const pct = total ? Math.round((done / total) * 100) : 0;
|
|
892
|
-
planCountEl.textContent = done + " / " + total;
|
|
893
|
-
const bar = el("div", "plan-bar");
|
|
894
|
-
const track = el("span", "pbar");
|
|
895
|
-
const fill = el("i");
|
|
896
|
-
fill.style.width = pct + "%";
|
|
897
|
-
track.appendChild(fill);
|
|
898
|
-
bar.appendChild(track);
|
|
899
|
-
bar.appendChild(el("span", "", pct + "%"));
|
|
900
|
-
planEl.appendChild(bar);
|
|
901
|
-
plan.steps.forEach((step, i) => {
|
|
902
|
-
const current = i === plan.currentIndex && step.status !== "done" && step.status !== "failed";
|
|
903
|
-
const { status, glyph } = planStepMark(step, current);
|
|
904
|
-
planEl.appendChild(el("div", ("plan-step " + status).trim(), glyph + step.text));
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
export function onPlan(data) {
|
|
909
|
-
const feed = feedForEvent(data && data.sessionId);
|
|
910
|
-
if (!feed || !data || !Array.isArray(data.steps) || !data.steps.length) return;
|
|
911
|
-
clearEmpty(feed);
|
|
912
|
-
const tab = S.tabs.find((x) => x.feed === feed);
|
|
913
|
-
let card = tab ? tab.planCard : null;
|
|
914
|
-
if (!card || card.parentNode !== feed) {
|
|
915
|
-
card = el("div", "planblock");
|
|
916
|
-
appendTo(feed, card);
|
|
917
|
-
if (tab) tab.planCard = card;
|
|
918
|
-
}
|
|
919
|
-
card.textContent = "";
|
|
920
|
-
const total = data.steps.length;
|
|
921
|
-
const cur = typeof data.currentIndex === "number" ? data.currentIndex : -1;
|
|
922
|
-
card.appendChild(el("div", "ph2", "◇ " + pluralCount("web.plan.heading", total)));
|
|
923
|
-
data.steps.forEach((step, i) => {
|
|
924
|
-
const { status, glyph } = planStepMark(step, i === cur);
|
|
925
|
-
card.appendChild(el("div", ("st " + status).trim(), glyph + step.text));
|
|
926
|
-
});
|
|
927
|
-
if (feed === activeFeed()) scroll();
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// `? ` glyph on the last line, matching the terminal menu (the tool may have
|
|
931
|
-
// already prefixed it — don't double it).
|
|
932
|
-
function questionLabel(text) {
|
|
933
|
-
const lines = String(text).split("\n");
|
|
934
|
-
const last = lines[lines.length - 1];
|
|
935
|
-
if (/^\s*\?/.test(last)) return String(text);
|
|
936
|
-
lines[lines.length - 1] = "? " + last;
|
|
937
|
-
return lines.join("\n");
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// Answered-state + settle wiring shared by both question kinds. `display` is
|
|
941
|
-
// what the user sees; `raw` is exactly what goes to `/api/answer`.
|
|
942
|
-
function makeQuestionBlock(feed, promptText, id) {
|
|
943
|
-
const block = el("div", "q");
|
|
944
|
-
block.appendChild(el("div", "qtext", promptText));
|
|
945
|
-
let answered = false;
|
|
946
|
-
const settle = (display, raw) => {
|
|
947
|
-
if (answered) return;
|
|
948
|
-
answered = true;
|
|
949
|
-
block.classList.add("answered");
|
|
950
|
-
block.textContent = "";
|
|
951
|
-
block.appendChild(el("div", "qtext", promptText));
|
|
952
|
-
block.appendChild(el("div", "qans", "↳ " + (String(display || "").trim() || t("web.question.answered"))));
|
|
953
|
-
api("/api/answer", { method: "POST", body: { id, value: raw } });
|
|
954
|
-
if (feed === activeFeed()) scroll();
|
|
955
|
-
};
|
|
956
|
-
return { block, feed, settle, isAnswered: () => answered };
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// Free-text prompt (a `text` question, or the tool's follow-up after the
|
|
960
|
-
// "own answer" choice — see `askUser`/`CUSTOM_INDEX`).
|
|
961
|
-
function questionTextInput(q, placeholder) {
|
|
962
|
-
const wrap = el("div", "qcustom");
|
|
963
|
-
const input = el("input", "qinput");
|
|
964
|
-
input.type = "text";
|
|
965
|
-
input.placeholder = placeholder;
|
|
966
|
-
input.setAttribute("aria-label", placeholder);
|
|
967
|
-
input.addEventListener("keydown", (e) => {
|
|
968
|
-
if (e.isComposing || e.keyCode === 229) return;
|
|
969
|
-
if (e.key === "Enter") {
|
|
970
|
-
e.preventDefault();
|
|
971
|
-
const value = input.value.trim();
|
|
972
|
-
if (value) q.settle(value, value);
|
|
973
|
-
}
|
|
974
|
-
});
|
|
975
|
-
wrap.appendChild(input);
|
|
976
|
-
wrap.appendChild(
|
|
977
|
-
link(t("web.question.custom_send"), "", () => {
|
|
978
|
-
const value = input.value.trim();
|
|
979
|
-
if (value) q.settle(value, value);
|
|
980
|
-
}),
|
|
981
|
-
);
|
|
982
|
-
if (q.feed === activeFeed()) setTimeout(() => input.focus(), 0);
|
|
983
|
-
return wrap;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
// Choice prompt: single-select settles on click, multi-select collects into a
|
|
987
|
-
// send button. The "own answer" entry maps to CUSTOM_INDEX (-1) so the tool
|
|
988
|
-
// then asks the free-text follow-up.
|
|
989
|
-
function questionChoice(q, data, options) {
|
|
990
|
-
const multiple = !!data.multiple;
|
|
991
|
-
const allowCustom = !!data.allowCustom;
|
|
992
|
-
const entries = options.map((label) => ({ label, raw: null }));
|
|
993
|
-
if (allowCustom) entries.push({ label: t("web.question.custom"), raw: -1 });
|
|
994
|
-
const rawOf = (i) => (entries[i].raw != null ? entries[i].raw : i);
|
|
995
|
-
const opts = el("div", "opts");
|
|
996
|
-
const selected = [];
|
|
997
|
-
let sendBtn = null;
|
|
998
|
-
entries.forEach((entry, i) => {
|
|
999
|
-
const row = el("button", "qopt", i + 1 + ". " + entry.label);
|
|
1000
|
-
row.type = "button";
|
|
1001
|
-
row.setAttribute("aria-checked", "false");
|
|
1002
|
-
row.addEventListener("click", () => {
|
|
1003
|
-
if (q.isAnswered()) return;
|
|
1004
|
-
if (multiple) {
|
|
1005
|
-
const pos = selected.indexOf(i);
|
|
1006
|
-
if (pos === -1) {
|
|
1007
|
-
selected.push(i);
|
|
1008
|
-
row.classList.add("on");
|
|
1009
|
-
row.setAttribute("aria-checked", "true");
|
|
1010
|
-
} else {
|
|
1011
|
-
selected.splice(pos, 1);
|
|
1012
|
-
row.classList.remove("on");
|
|
1013
|
-
row.setAttribute("aria-checked", "false");
|
|
1014
|
-
}
|
|
1015
|
-
if (sendBtn) sendBtn.disabled = selected.length === 0;
|
|
1016
|
-
} else {
|
|
1017
|
-
row.setAttribute("aria-checked", "true");
|
|
1018
|
-
q.settle(entry.label, [rawOf(i)]);
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
opts.appendChild(row);
|
|
1022
|
-
});
|
|
1023
|
-
q.block.appendChild(opts);
|
|
1024
|
-
if (multiple) {
|
|
1025
|
-
q.block.appendChild(el("div", "qmulti-hint", t("web.question.multi_hint")));
|
|
1026
|
-
sendBtn = el("button", "qsend", t("web.question.send"));
|
|
1027
|
-
sendBtn.type = "button";
|
|
1028
|
-
sendBtn.disabled = true;
|
|
1029
|
-
sendBtn.addEventListener("click", () => {
|
|
1030
|
-
if (q.isAnswered() || !selected.length) return;
|
|
1031
|
-
const chosen = selected.slice().sort((a, b) => a - b);
|
|
1032
|
-
q.settle(chosen.map((i) => entries[i].label).join(", "), chosen.map(rawOf));
|
|
1033
|
-
});
|
|
1034
|
-
q.block.appendChild(sendBtn);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
export function onQuestion(data) {
|
|
1039
|
-
if (!data || typeof data.text !== "string") return;
|
|
1040
|
-
const feed = feedForEvent(data.sessionId);
|
|
1041
|
-
if (!feed) return;
|
|
1042
|
-
clearEmpty(feed);
|
|
1043
|
-
const q = makeQuestionBlock(feed, questionLabel(data.text), data.id);
|
|
1044
|
-
const options = Array.isArray(data.options) ? data.options : [];
|
|
1045
|
-
if (data.kind === "choice" && options.length) questionChoice(q, data, options);
|
|
1046
|
-
else q.block.appendChild(questionTextInput(q, t("web.question.placeholder")));
|
|
1047
|
-
appendTo(feed, q.block);
|
|
1048
|
-
if (feed === activeFeed()) scroll();
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
function usageRow(label, value, cls) {
|
|
1052
|
-
const row = el("div", "kv");
|
|
1053
|
-
row.appendChild(el("span", "", label));
|
|
1054
|
-
row.appendChild(el("b", cls || "", value));
|
|
1055
|
-
return row;
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// Segmented context bar: system · tools · history · reserve · free, widths
|
|
1059
|
-
// relative to the full context window so the parts always sum to one bar.
|
|
1060
|
-
function contextSegBar(h, used, limit) {
|
|
1061
|
-
const bar = el("div", "bar segbar");
|
|
1062
|
-
const bd = h.contextBreakdown;
|
|
1063
|
-
if (!bd || typeof bd.window !== "number" || bd.window <= 0) {
|
|
1064
|
-
// Legacy fallback (pre-breakdown payloads): single used/limit fill.
|
|
1065
|
-
const fill = el("i");
|
|
1066
|
-
fill.style.width = Math.min(100, Math.round((used / limit) * 100)) + "%";
|
|
1067
|
-
bar.appendChild(fill);
|
|
1068
|
-
return bar;
|
|
1069
|
-
}
|
|
1070
|
-
const denom = Math.max(bd.window, bd.used + bd.reserve);
|
|
1071
|
-
const parts = [
|
|
1072
|
-
["seg-system", bd.system],
|
|
1073
|
-
["seg-tools", bd.tools],
|
|
1074
|
-
["seg-history", bd.history],
|
|
1075
|
-
["seg-reserve", bd.reserve],
|
|
1076
|
-
["seg-free", bd.free],
|
|
1077
|
-
];
|
|
1078
|
-
for (const [cls, n] of parts) {
|
|
1079
|
-
if (!n || n <= 0) continue;
|
|
1080
|
-
const seg = el("i", cls);
|
|
1081
|
-
seg.style.width = (n / denom) * 100 + "%";
|
|
1082
|
-
bar.appendChild(seg);
|
|
1083
|
-
}
|
|
1084
|
-
return bar;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
// Dim legend under the segmented bar: one dot+label+value per context part.
|
|
1088
|
-
function contextLegend(h) {
|
|
1089
|
-
const box = el("div", "seglegend");
|
|
1090
|
-
const bd = h.contextBreakdown;
|
|
1091
|
-
if (!bd) return box;
|
|
1092
|
-
const items = [
|
|
1093
|
-
["seg-system", "web.usage.part_system", bd.system],
|
|
1094
|
-
["seg-tools", "web.usage.part_tools", bd.tools],
|
|
1095
|
-
["seg-history", "web.usage.part_history", bd.history],
|
|
1096
|
-
["seg-reserve", "web.usage.part_reserve", bd.reserve],
|
|
1097
|
-
["seg-free", "web.usage.part_free", bd.free],
|
|
1098
|
-
];
|
|
1099
|
-
for (const [cls, labelKey, n] of items) {
|
|
1100
|
-
if (!n || n <= 0) continue;
|
|
1101
|
-
const item = el("span", "segl-item");
|
|
1102
|
-
item.appendChild(el("i", "segl-dot " + cls));
|
|
1103
|
-
item.appendChild(el("span", "segl-label", t(labelKey)));
|
|
1104
|
-
item.appendChild(el("b", "", fmtTokens(n)));
|
|
1105
|
-
box.appendChild(item);
|
|
1106
|
-
}
|
|
1107
|
-
return box;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// Session row with a copy button next to the id (icon-only, ✓ on success).
|
|
1111
|
-
function usageSessionRow(sessionId) {
|
|
1112
|
-
const row = el("div", "kv");
|
|
1113
|
-
row.appendChild(el("span", "", t("web.usage.session")));
|
|
1114
|
-
const val = el("span", "kv-val");
|
|
1115
|
-
if (!sessionId) {
|
|
1116
|
-
val.appendChild(el("b", "", "—"));
|
|
1117
|
-
} else {
|
|
1118
|
-
val.appendChild(el("b", "", sessionId));
|
|
1119
|
-
const btn = el("button", "copy-btn");
|
|
1120
|
-
btn.type = "button";
|
|
1121
|
-
btn.title = t("web.usage.copy_id");
|
|
1122
|
-
btn.setAttribute("aria-label", t("web.usage.copy_id"));
|
|
1123
|
-
btn.appendChild(icon("copy"));
|
|
1124
|
-
btn.addEventListener("click", () => {
|
|
1125
|
-
copyText(sessionId).then((ok) => {
|
|
1126
|
-
btn.replaceChildren(icon(ok ? "check" : "copy"));
|
|
1127
|
-
setTimeout(() => btn.replaceChildren(icon("copy")), 1500);
|
|
1128
|
-
});
|
|
1129
|
-
});
|
|
1130
|
-
val.appendChild(btn);
|
|
1131
|
-
}
|
|
1132
|
-
row.appendChild(val);
|
|
1133
|
-
return row;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
export function renderUsage(h) {
|
|
1137
|
-
const cfg = S.state.config || {};
|
|
1138
|
-
const sig = JSON.stringify([
|
|
1139
|
-
h.sessionId, h.model, h.contextUsed, h.contextLimit, h.cacheHitRate, h.costUsd,
|
|
1140
|
-
h.compactionCount, h.contextQuality, h.contextProbe, h.contextBreakdown, cfg.model, cfg.contextWindow,
|
|
1141
|
-
I18N.locale,
|
|
1142
|
-
]);
|
|
1143
|
-
if (sig === S.lastUsageSig) return;
|
|
1144
|
-
S.lastUsageSig = sig;
|
|
1145
|
-
usageEl.textContent = "";
|
|
1146
|
-
const sid = h.sessionId ? String(h.sessionId) : "";
|
|
1147
|
-
usageEl.appendChild(usageSessionRow(sid));
|
|
1148
|
-
usageEl.appendChild(usageRow(t("web.usage.model"), h.model || cfg.model || "—"));
|
|
1149
|
-
const used = typeof h.contextUsed === "number" ? h.contextUsed : null;
|
|
1150
|
-
const limit = typeof h.contextLimit === "number" ? h.contextLimit : cfg.contextWindow || null;
|
|
1151
|
-
usageEl.appendChild(usageRow(t("web.usage.context"), used != null && limit ? fmtTokens(used) + " / " + fmtTokens(limit) : "—"));
|
|
1152
|
-
if (used != null && limit) {
|
|
1153
|
-
usageEl.appendChild(contextSegBar(h, used, limit));
|
|
1154
|
-
usageEl.appendChild(contextLegend(h));
|
|
1155
|
-
}
|
|
1156
|
-
const hit = typeof h.cacheHitRate === "number" ? h.cacheHitRate : null;
|
|
1157
|
-
usageEl.appendChild(usageRow(t("web.usage.cache"), hit != null ? Math.round(hit * 100) + "%" : "—", hit != null ? "" : "dim"));
|
|
1158
|
-
usageEl.appendChild(usageRow(t("web.usage.cost"), typeof h.costUsd === "number" ? "$" + h.costUsd.toFixed(4) : "—"));
|
|
1159
|
-
usageEl.appendChild(usageRow(t("web.usage.compactions"), "⟳ " + (h.compactionCount || 0), h.compactionCount ? "wn" : "dim"));
|
|
1160
|
-
const q = typeof h.contextQuality === "number" ? h.contextQuality : null;
|
|
1161
|
-
usageEl.appendChild(usageRow(t("web.usage.quality"), q != null ? Math.round(q) + "%" : "—", q != null ? "" : "dim"));
|
|
1162
|
-
|
|
1163
|
-
// Context probe: real LM Studio loaded context vs configured. Pending/absent
|
|
1164
|
-
// renders a dim dash; loaded < configured warns.
|
|
1165
|
-
let probeText = "—";
|
|
1166
|
-
let probeCls = "dim";
|
|
1167
|
-
const probe = h.contextProbe;
|
|
1168
|
-
if (probe && typeof probe.actual === "number") {
|
|
1169
|
-
const actual = fmtTokens(probe.actual);
|
|
1170
|
-
const configured = h.contextLimit || cfg.contextWindow || null;
|
|
1171
|
-
if (configured && probe.actual < configured) {
|
|
1172
|
-
probeText = t("web.usage.probe_low", { actual, configured: fmtTokens(configured) });
|
|
1173
|
-
probeCls = "wn";
|
|
1174
|
-
} else {
|
|
1175
|
-
probeText = t("web.usage.probe_loaded", { actual });
|
|
1176
|
-
probeCls = "";
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
usageEl.appendChild(usageRow(t("web.usage.probe"), probeText, probeCls));
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Workspace path under the composer (dim, own line).
|
|
1183
|
-
export function renderWorkspace(h) {
|
|
1184
|
-
const ws = h.workspace || "";
|
|
1185
|
-
if (wsLine.dataset.ws === ws) return;
|
|
1186
|
-
wsLine.dataset.ws = ws;
|
|
1187
|
-
wsLine.textContent = "";
|
|
1188
|
-
wsLine.hidden = !ws;
|
|
1189
|
-
if (!ws) return;
|
|
1190
|
-
wsLine.appendChild(el("span", "ws-k", t("web.env.dir")));
|
|
1191
|
-
const v = el("span", "ws-v", ws);
|
|
1192
|
-
v.title = ws;
|
|
1193
|
-
wsLine.appendChild(v);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
// Environment (sidebar): stacked name/value items — AGENTS.md sources, map size.
|
|
1197
|
-
function envItem(label, values) {
|
|
1198
|
-
const item = el("div", "env-item");
|
|
1199
|
-
item.appendChild(el("div", "env-k", label));
|
|
1200
|
-
values.forEach((val) => {
|
|
1201
|
-
const v = el("div", "env-v", val);
|
|
1202
|
-
v.title = val;
|
|
1203
|
-
item.appendChild(v);
|
|
1204
|
-
});
|
|
1205
|
-
return item;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
export function renderEnvironment(h) {
|
|
1209
|
-
const disabled = !!(h.instructions && h.instructions.disabled);
|
|
1210
|
-
const files = h.instructions && !disabled ? h.instructions.files || [] : [];
|
|
1211
|
-
const map = h.projectMap || null;
|
|
1212
|
-
const sig = JSON.stringify([disabled, files, map, I18N.locale]);
|
|
1213
|
-
if (sig === S.lastEnvSig) return;
|
|
1214
|
-
S.lastEnvSig = sig;
|
|
1215
|
-
envEl.textContent = "";
|
|
1216
|
-
envEl.appendChild(
|
|
1217
|
-
envItem(
|
|
1218
|
-
t("web.env.agents"),
|
|
1219
|
-
disabled ? [t("web.env.agents_disabled")] : files.length ? files : [t("web.env.agents_none")],
|
|
1220
|
-
),
|
|
1221
|
-
);
|
|
1222
|
-
envEl.appendChild(
|
|
1223
|
-
envItem(
|
|
1224
|
-
t("web.env.map"),
|
|
1225
|
-
map && map.indexed
|
|
1226
|
-
? [
|
|
1227
|
-
[
|
|
1228
|
-
pluralCount("web.env.files", map.files),
|
|
1229
|
-
pluralCount("web.env.symbols", map.symbols),
|
|
1230
|
-
pluralCount("web.env.tokens", map.tokens),
|
|
1231
|
-
].join(" · "),
|
|
1232
|
-
]
|
|
1233
|
-
: [t("web.env.map_not_indexed")],
|
|
1234
|
-
),
|
|
1235
|
-
);
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// Global `.think-h` toggle (depends on the wrap > [head, body] structure
|
|
1239
|
-
// built by `buildThinkBlock`).
|
|
1240
|
-
msgs.addEventListener("click", (e) => {
|
|
1241
|
-
const head = e.target.closest(".think-h");
|
|
1242
|
-
if (!head) return;
|
|
1243
|
-
const body = head.nextElementSibling;
|
|
1244
|
-
if (!body) return;
|
|
1245
|
-
const hidden = body.classList.toggle("hidden");
|
|
1246
|
-
head.setAttribute("aria-expanded", hidden ? "false" : "true");
|
|
1247
|
-
const tog = head.querySelector(".tog");
|
|
1248
|
-
if (tog) setIcon(tog, hidden ? "chevron-right" : "chevron-down", t(hidden ? "web.action.show" : "web.action.hide"));
|
|
1249
|
-
});
|
|
1250
|
-
|
|
1
|
+
export*from"./feed/core.js";export*from"./feed/sidebar.js";export*from"./feed/history.js";export*from"./feed/plan.js";export*from"./feed/question.js";export*from"./feed/stream.js";export*from"./feed/turn.js";
|