rovecode 0.3.3 → 0.4.0-beta.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 +512 -0
- package/README.md +33 -48
- package/bin/rovecode.js +24 -0
- package/dist/cli/app-dybnr56b.js +2 -0
- package/dist/cli/ask-user-p8hq4xgj.js +2 -0
- package/dist/cli/auth-login-ewpgw5sm.js +2 -0
- package/dist/cli/auth-m8p9grty.js +2 -0
- package/dist/cli/bench-xv3ypwev.js +9 -0
- package/dist/cli/catalog-737wb2s0.js +2 -0
- package/dist/cli/cli-arhg40m0.js +2 -0
- package/dist/cli/client-cf2pxx8q.js +2 -0
- package/dist/cli/commands-3p7e4xxs.js +2 -0
- package/dist/cli/connect-3q93d7cb.js +2 -0
- package/dist/cli/context-cmd-eqxmxhzq.js +2 -0
- package/dist/cli/context-report-hbw9zfes.js +2 -0
- package/dist/cli/count-remote-mby98cd0.js +2 -0
- package/dist/cli/design-122y0axd.js +2 -0
- package/dist/cli/dispatch-b4egzvvh.js +2 -0
- package/dist/cli/doctor-x4jkv72e.js +3 -0
- package/dist/cli/executor-ftvg6tsy.js +2 -0
- package/dist/cli/export-pdgdhkch.js +2 -0
- package/dist/cli/files-cez9a96p.js +2 -0
- package/dist/cli/gauntlet-r3xxaszc.js +2 -0
- package/dist/cli/gauntlet-runner-r515m7kk.js +10 -0
- package/dist/cli/gauntlet-wave3-bnkjk2v2.js +5 -0
- package/dist/cli/gauntlet-wave4-acs9s60q.js +14 -0
- package/dist/cli/hashline-ewg5hbe3.js +2 -0
- package/dist/cli/http-n0kehsk8.js +5 -0
- package/dist/cli/index-z5qt1s76.js +2 -0
- package/dist/cli/install-80mp63kx.js +2 -0
- package/dist/cli/loop-12twjcat.js +2 -0
- package/dist/cli/main-01pv9206.js +4 -0
- package/dist/cli/main-0jys2ccn.js +3 -0
- package/dist/cli/main-1ztz6fkj.js +10 -0
- package/dist/cli/main-23q7cmww.js +9 -0
- package/dist/cli/main-2rzbexn2.js +3 -0
- package/dist/cli/main-2wyax8k9.js +9 -0
- package/dist/cli/main-2yeveeve.js +6 -0
- package/dist/cli/main-2z3dek0b.js +3 -0
- package/dist/cli/main-2zgsknth.js +3 -0
- package/dist/cli/main-45ejth3a.js +4 -0
- package/dist/cli/main-45rn3trk.js +22 -0
- package/dist/cli/main-4p4e2w7x.js +4 -0
- package/dist/cli/main-4y0tnfpa.js +16 -0
- package/dist/cli/main-5py0rkmc.js +4 -0
- package/dist/cli/main-6dtqmbt6.js +7 -0
- package/dist/cli/main-6h9x282m.js +4 -0
- package/dist/cli/main-6vjeds42.js +3 -0
- package/dist/cli/main-78gq4bt9.js +6 -0
- package/dist/cli/main-7jd5vh3x.js +4 -0
- package/dist/cli/main-7kt6r53y.js +4 -0
- package/dist/cli/main-8c1tbazx.js +58 -0
- package/dist/cli/main-9a9rnh47.js +19 -0
- package/dist/cli/main-9ht36z12.js +3 -0
- package/dist/cli/main-a2yfvcy9.js +7 -0
- package/dist/cli/main-a3f51n0x.js +5 -0
- package/dist/cli/main-b8zq261k.js +3 -0
- package/dist/cli/main-bxtvnf6d.js +13 -0
- package/dist/cli/main-edxc3yzt.js +4 -0
- package/dist/cli/main-evgz4mp5.js +21 -0
- package/dist/cli/main-f33fc5je.js +9 -0
- package/dist/cli/main-fvnpq46y.js +12 -0
- package/dist/cli/main-gbbty4d4.js +3 -0
- package/dist/cli/main-gth53dnt.js +25 -0
- package/dist/cli/main-hqbz10aw.js +9 -0
- package/dist/cli/main-hrrvcfan.js +38 -0
- package/dist/cli/main-hzwtsb2m.js +5 -0
- package/dist/cli/main-j7ttv0sd.js +34 -0
- package/dist/cli/main-jak598k9.js +5 -0
- package/dist/cli/main-kba6zeyd.js +6 -0
- package/dist/cli/main-kwwsz6rq.js +3 -0
- package/dist/cli/main-m8vm17zq.js +3 -0
- package/dist/cli/main-mg4f96e1.js +3 -0
- package/dist/cli/main-mg9b20ac.js +18 -0
- package/dist/cli/main-mgb9ccnx.js +3 -0
- package/dist/cli/main-mjt2p7aj.js +3 -0
- package/dist/cli/main-n6qrdbmy.js +3 -0
- package/dist/cli/main-na7wse0x.js +5 -0
- package/dist/cli/main-nqveez48.js +4 -0
- package/dist/cli/main-ntqef02r.js +10 -0
- package/dist/cli/main-nvc3yjay.js +136 -0
- package/dist/cli/main-p0cfn6nr.js +16 -0
- package/dist/cli/main-qj2djy17.js +19 -0
- package/dist/cli/main-qsevpgsv.js +3 -0
- package/dist/cli/main-qvarybsp.js +3 -0
- package/dist/cli/main-rebtt91r.js +5 -0
- package/dist/cli/main-rpg7h8mb.js +3 -0
- package/dist/cli/main-rsy72qmw.js +15 -0
- package/dist/cli/main-rvetps99.js +18 -0
- package/dist/cli/main-s4bb0jav.js +3 -0
- package/dist/cli/main-s9v8k74e.js +3 -0
- package/dist/cli/main-tjvwmscs.js +3 -0
- package/dist/cli/main-tkgarpjj.js +4 -0
- package/dist/cli/main-v8y60bb2.js +3 -0
- package/dist/cli/main-vhrrq337.js +3 -0
- package/dist/cli/main-vp2dfb7s.js +4 -0
- package/dist/cli/main-vqbr22sz.js +8 -0
- package/dist/cli/main-vxnwe5xx.js +18 -0
- package/dist/cli/main-wgph00xf.js +5 -0
- package/dist/cli/main-wk2csfnj.js +5 -0
- package/dist/cli/main-wm997zjx.js +3 -0
- package/dist/cli/main-wpkyraxh.js +3 -0
- package/dist/cli/main-wqt32p5x.js +4 -0
- package/dist/cli/main-x9ct6y1a.js +3 -0
- package/dist/cli/main-xfekqh9m.js +7 -0
- package/dist/cli/main-xt9zc3n6.js +7 -0
- package/dist/cli/main-xx2z3zh5.js +4 -0
- package/dist/cli/main-y5c82rxr.js +3 -0
- package/dist/cli/main-yrjt2sqt.js +14 -0
- package/dist/cli/main-ys6zj3yr.js +3 -0
- package/dist/cli/main-ywbxshqc.js +8 -0
- package/dist/cli/main-z13755t8.js +25 -0
- package/dist/cli/main-zc7pyrbj.js +4 -0
- package/dist/cli/main.js +279 -0
- package/dist/cli/market-cmd-bm5xvn9f.js +5 -0
- package/dist/cli/mcp-login-bthtfpt7.js +2 -0
- package/dist/cli/mcp-market-cmd-mbeshfyd.js +2 -0
- package/dist/cli/notify-54v5z9dz.js +2 -0
- package/dist/cli/oauth-g5gme95c.js +2 -0
- package/dist/cli/output-satndjap.js +16 -0
- package/dist/cli/profiles-sfhpbq3m.js +2 -0
- package/dist/cli/provider-config-hv3xtdt4.js +2 -0
- package/dist/cli/provider-kwzq6g84.js +2 -0
- package/dist/cli/registry-fh0hdnyn.js +2 -0
- package/dist/cli/registry-y1y8e94r.js +2 -0
- package/dist/cli/repl-t4z03mqq.js +11 -0
- package/dist/cli/resume-fqt4chg8.js +2 -0
- package/dist/cli/run-flags-rysbag9t.js +2 -0
- package/dist/cli/runtime-j19fjbsa.js +2 -0
- package/dist/cli/sandbox-config-g4qxd7y5.js +2 -0
- package/dist/cli/server-r0b6bksk.js +5 -0
- package/dist/cli/session-arg-txmn5g4x.js +2 -0
- package/dist/cli/session-ed250d9j.js +2 -0
- package/dist/cli/sessions-cmd-adw7svfn.js +7 -0
- package/dist/cli/settings-y9rzcqx8.js +2 -0
- package/dist/cli/setup-jmbr11j0.js +2 -0
- package/dist/cli/sextant-smoke-tcth0vea.js +5 -0
- package/dist/cli/skills-cmd-zbdy99v6.js +2 -0
- package/dist/cli/smoke-1bg937kx.js +8 -0
- package/dist/cli/start-chat-p01cdks3.js +12 -0
- package/dist/cli/stream-4wmyaypz.js +2 -0
- package/dist/cli/task-eg4s093s.js +2 -0
- package/dist/cli/tasks-12v9rr9k.js +2 -0
- package/dist/cli/thinking-a5ngvqyh.js +2 -0
- package/dist/cli/todo-1wxpcecx.js +2 -0
- package/dist/cli/tools-2ftsya7w.js +2 -0
- package/dist/cli/tools-x1tj4fxm.js +2 -0
- package/dist/cli/trust-cmd-hccxehzb.js +2 -0
- package/dist/cli/update-check-ygt3vd7m.js +2 -0
- package/dist/cli/update-cmd-v23qhr8c.js +2 -0
- package/dist/cli/voice-g1gtck92.js +2 -0
- package/dist/cli/webfetch-0nnrjgb5.js +2 -0
- package/dist/cli/websearch-f0vr2p7d.js +2 -0
- package/dist/cli/workspace-9rq1w4ta.js +2 -0
- package/dist/lib/index.js +62 -0
- package/dist/lib/models-index.json +1 -0
- package/dist/lib/plugins.js +6 -0
- package/dist/lib/providers.js +17 -0
- package/dist/lib/public-api.js +20 -0
- package/dist/rovecode.exe +4 -0
- package/package.json +83 -72
- package/bin/rovecode.ts +0 -21
- package/src/acp/server.ts +0 -374
- package/src/cli/auth-login.ts +0 -122
- package/src/cli/connect.ts +0 -244
- package/src/cli/context-cmd.ts +0 -199
- package/src/cli/dispatch.ts +0 -82
- package/src/cli/doctor.ts +0 -362
- package/src/cli/export.ts +0 -276
- package/src/cli/help.ts +0 -293
- package/src/cli/is-tui-invocation.ts +0 -8
- package/src/cli/main.ts +0 -583
- package/src/cli/market-cmd.ts +0 -658
- package/src/cli/mcp-login.ts +0 -141
- package/src/cli/mcp-market-cmd.ts +0 -302
- package/src/cli/output.ts +0 -382
- package/src/cli/repl.ts +0 -250
- package/src/cli/repomap-root.ts +0 -14
- package/src/cli/resume.ts +0 -57
- package/src/cli/run-flags.ts +0 -43
- package/src/cli/run-limits.ts +0 -78
- package/src/cli/runtime.ts +0 -931
- package/src/cli/session-arg.ts +0 -30
- package/src/cli/sessions-cmd.ts +0 -145
- package/src/cli/setup.ts +0 -153
- package/src/cli/skills-cmd.ts +0 -194
- package/src/cli/start-chat.ts +0 -65
- package/src/cli/trust-cmd.ts +0 -52
- package/src/coding/bash.ts +0 -148
- package/src/coding/checkpoints.ts +0 -327
- package/src/coding/diff.ts +0 -138
- package/src/coding/files.ts +0 -341
- package/src/coding/hashline.ts +0 -274
- package/src/coding/lsp-gate.ts +0 -254
- package/src/coding/lsp-servers.ts +0 -147
- package/src/coding/lsp.ts +0 -283
- package/src/coding/repomap-cache.ts +0 -99
- package/src/coding/repomap-files.ts +0 -192
- package/src/coding/repomap.ts +0 -481
- package/src/core/agents.ts +0 -255
- package/src/core/compaction.ts +0 -259
- package/src/core/config.ts +0 -289
- package/src/core/context-report.ts +0 -228
- package/src/core/context.ts +0 -60
- package/src/core/count-remote.ts +0 -107
- package/src/core/execpolicy-rules.ts +0 -196
- package/src/core/execpolicy.ts +0 -385
- package/src/core/executor.ts +0 -454
- package/src/core/guardrails.ts +0 -400
- package/src/core/hooks.ts +0 -411
- package/src/core/images.ts +0 -230
- package/src/core/intro.ts +0 -266
- package/src/core/loop.ts +0 -567
- package/src/core/modes.ts +0 -372
- package/src/core/orchestrator.ts +0 -245
- package/src/core/proc-group.ts +0 -48
- package/src/core/project-trust.ts +0 -98
- package/src/core/reflection.ts +0 -165
- package/src/core/sandbox-config.ts +0 -186
- package/src/core/session-id.ts +0 -24
- package/src/core/session-images.ts +0 -73
- package/src/core/session-ops.ts +0 -183
- package/src/core/session-text.ts +0 -29
- package/src/core/session.ts +0 -469
- package/src/core/settings.ts +0 -170
- package/src/core/tasks.ts +0 -646
- package/src/core/token-scale.ts +0 -108
- package/src/core/tools.ts +0 -309
- package/src/core/trust.ts +0 -104
- package/src/core/types.ts +0 -330
- package/src/core/update-check.ts +0 -171
- package/src/core/usage.ts +0 -204
- package/src/core/validate.ts +0 -121
- package/src/core/verify-gate.ts +0 -159
- package/src/core/verify.ts +0 -236
- package/src/core/voice.ts +0 -158
- package/src/core/win-job.ts +0 -183
- package/src/core/workspace.ts +0 -184
- package/src/design/audit.ts +0 -797
- package/src/design/direction.ts +0 -190
- package/src/design/rules.ts +0 -157
- package/src/eval/bench.ts +0 -150
- package/src/eval/gauntlet-runner.ts +0 -215
- package/src/eval/gauntlet-support.ts +0 -84
- package/src/eval/gauntlet-wave3.ts +0 -269
- package/src/eval/gauntlet-wave4.ts +0 -217
- package/src/eval/gauntlet.ts +0 -253
- package/src/index.ts +0 -17
- package/src/lanes/agy.ts +0 -95
- package/src/lanes/approval.ts +0 -24
- package/src/lanes/claude.ts +0 -129
- package/src/lanes/codex.ts +0 -127
- package/src/lanes/events.ts +0 -130
- package/src/lanes/job.ts +0 -142
- package/src/lanes/opencode.ts +0 -122
- package/src/lanes/process.ts +0 -184
- package/src/lanes/progress.ts +0 -183
- package/src/lanes/registry.ts +0 -178
- package/src/lanes/runner.ts +0 -124
- package/src/lanes/types.ts +0 -112
- package/src/market/catalogs/mcp-docs.json +0 -111
- package/src/market/catalogs/plugins.json +0 -111
- package/src/market/catalogs/skills.json +0 -478
- package/src/market/clone.ts +0 -72
- package/src/market/context-cost.ts +0 -121
- package/src/market/digest.ts +0 -106
- package/src/market/index.ts +0 -22
- package/src/market/install.ts +0 -578
- package/src/market/manifest.ts +0 -187
- package/src/market/prereq.ts +0 -145
- package/src/market/registry.ts +0 -363
- package/src/market/resolve.ts +0 -111
- package/src/market/types.ts +0 -236
- package/src/market/validate.ts +0 -227
- package/src/mcp/client.ts +0 -449
- package/src/mcp/config.ts +0 -252
- package/src/mcp/local-package.ts +0 -211
- package/src/mcp/market-catalog.ts +0 -84
- package/src/mcp/market-install.ts +0 -289
- package/src/mcp/market.ts +0 -362
- package/src/mcp/oauth.ts +0 -251
- package/src/mcp/prompts-resources.ts +0 -249
- package/src/mcp/shared.ts +0 -149
- package/src/mcp/status.ts +0 -67
- package/src/mcp/tools.ts +0 -275
- package/src/mcp/transport.ts +0 -122
- package/src/mcp/trust.ts +0 -25
- package/src/memory/blocks.ts +0 -278
- package/src/memory/recall.ts +0 -355
- package/src/memory/scope.ts +0 -182
- package/src/memory/store.ts +0 -105
- package/src/memory/tools.ts +0 -99
- package/src/plugins/cli.ts +0 -119
- package/src/plugins/discover.ts +0 -108
- package/src/plugins/index.ts +0 -50
- package/src/plugins/install.ts +0 -184
- package/src/plugins/load.ts +0 -124
- package/src/plugins/manifest.ts +0 -92
- package/src/plugins/state.ts +0 -83
- package/src/providers/auth.ts +0 -408
- package/src/providers/cache.ts +0 -223
- package/src/providers/catalog-local.ts +0 -160
- package/src/providers/catalog.ts +0 -421
- package/src/providers/middleware-context.ts +0 -86
- package/src/providers/middleware.ts +0 -373
- package/src/providers/model-list.ts +0 -23
- package/src/providers/oauth/common.ts +0 -105
- package/src/providers/oauth/device-code.ts +0 -107
- package/src/providers/oauth/github-copilot.ts +0 -146
- package/src/providers/oauth/loopback.ts +0 -158
- package/src/providers/oauth/openai.ts +0 -163
- package/src/providers/oauth/openrouter.ts +0 -89
- package/src/providers/oauth/pkce.ts +0 -45
- package/src/providers/oauth/registry.ts +0 -39
- package/src/providers/oauth/seam.ts +0 -89
- package/src/providers/profile-glm53.ts +0 -111
- package/src/providers/profile-sonnet5-persona.ts +0 -65
- package/src/providers/profile-sonnet5-voice.ts +0 -23
- package/src/providers/profiles.ts +0 -156
- package/src/providers/provider-config.ts +0 -311
- package/src/providers/registry.ts +0 -333
- package/src/providers/responses.ts +0 -209
- package/src/providers/retry.ts +0 -234
- package/src/providers/router.ts +0 -294
- package/src/providers/sse.ts +0 -26
- package/src/providers/stream-errors.ts +0 -117
- package/src/providers/stream.ts +0 -566
- package/src/providers/thinking.ts +0 -189
- package/src/providers/wire-messages.ts +0 -129
- package/src/providers/wire-responses.ts +0 -79
- package/src/providers/wire-select.ts +0 -53
- package/src/server/http.ts +0 -291
- package/src/server/openapi.ts +0 -246
- package/src/sextant/card-hits.ts +0 -102
- package/src/sextant/card-keys.ts +0 -55
- package/src/sextant/context-source.ts +0 -157
- package/src/sextant/crew-cards.ts +0 -350
- package/src/sextant/draw-agents.ts +0 -273
- package/src/sextant/draw-code.ts +0 -388
- package/src/sextant/draw-context.ts +0 -222
- package/src/sextant/draw-frame.ts +0 -164
- package/src/sextant/draw-market.ts +0 -573
- package/src/sextant/draw-messages.ts +0 -386
- package/src/sextant/draw-pet.ts +0 -230
- package/src/sextant/draw-plan.ts +0 -187
- package/src/sextant/draw-tabs.ts +0 -85
- package/src/sextant/draw-util.ts +0 -65
- package/src/sextant/draw-wizard.ts +0 -378
- package/src/sextant/engine.ts +0 -230
- package/src/sextant/frame-hits.ts +0 -25
- package/src/sextant/frame.ts +0 -101
- package/src/sextant/git-status.ts +0 -197
- package/src/sextant/grid.ts +0 -59
- package/src/sextant/input.ts +0 -119
- package/src/sextant/keys.ts +0 -521
- package/src/sextant/layout.ts +0 -86
- package/src/sextant/local-commands.ts +0 -169
- package/src/sextant/market-source.ts +0 -287
- package/src/sextant/mentions.ts +0 -200
- package/src/sextant/message-hits.ts +0 -26
- package/src/sextant/model.ts +0 -387
- package/src/sextant/overlays.ts +0 -456
- package/src/sextant/panel-hits.ts +0 -38
- package/src/sextant/pet.ts +0 -399
- package/src/sextant/screen.ts +0 -324
- package/src/sextant/scroll-hits.ts +0 -66
- package/src/sextant/scrollbar.ts +0 -82
- package/src/sextant/sextant-bridge.ts +0 -174
- package/src/sextant/sextant-cards.ts +0 -142
- package/src/sextant/sextant-diff-base.ts +0 -63
- package/src/sextant/sextant-files.ts +0 -154
- package/src/sextant/sextant-frame-loop.ts +0 -335
- package/src/sextant/sextant-renderer.ts +0 -574
- package/src/sextant/sextant-repo.ts +0 -140
- package/src/sextant/theme.ts +0 -66
- package/src/sextant/tool-rows.ts +0 -189
- package/src/sextant/types.ts +0 -493
- package/src/skills/index.ts +0 -387
- package/src/skills/pack.ts +0 -220
- package/src/skills/spec.ts +0 -162
- package/src/skills/tools.ts +0 -69
- package/src/skills/versioned.ts +0 -227
- package/src/telemetry/otel-export.ts +0 -122
- package/src/telemetry/otel-lanes.ts +0 -89
- package/src/telemetry/otel-logs.ts +0 -131
- package/src/telemetry/otel-metrics.ts +0 -136
- package/src/telemetry/otel.ts +0 -397
- package/src/telemetry/otlp.ts +0 -76
- package/src/tools/ask-user.ts +0 -156
- package/src/tools/bash-bg.ts +0 -94
- package/src/tools/bash-jobs.ts +0 -237
- package/src/tools/design.ts +0 -151
- package/src/tools/evalcell.ts +0 -338
- package/src/tools/html-text.ts +0 -139
- package/src/tools/provider.ts +0 -149
- package/src/tools/task.ts +0 -250
- package/src/tools/todo.ts +0 -320
- package/src/tools/webfetch.ts +0 -332
- package/src/tools/websearch.ts +0 -359
- package/src/tui/agents-cmd.ts +0 -41
- package/src/tui/app.ts +0 -749
- package/src/tui/attach.ts +0 -127
- package/src/tui/boot-notes.ts +0 -41
- package/src/tui/builtin-prompts.ts +0 -59
- package/src/tui/checkpoints-cmd.ts +0 -70
- package/src/tui/clipboard-image.ts +0 -81
- package/src/tui/clipboard.ts +0 -78
- package/src/tui/commands.ts +0 -283
- package/src/tui/config-view.ts +0 -53
- package/src/tui/context-cmds.ts +0 -282
- package/src/tui/cost.ts +0 -108
- package/src/tui/crash-guard.ts +0 -173
- package/src/tui/focus-terminal.ts +0 -34
- package/src/tui/git-cmds.ts +0 -273
- package/src/tui/git-plain.ts +0 -58
- package/src/tui/info-cmd.ts +0 -150
- package/src/tui/input-plain.ts +0 -76
- package/src/tui/mcp-cmd.ts +0 -128
- package/src/tui/memory-note.ts +0 -77
- package/src/tui/modes-cmd.ts +0 -45
- package/src/tui/notify-seq.ts +0 -100
- package/src/tui/notify.ts +0 -318
- package/src/tui/overlays.ts +0 -97
- package/src/tui/pi-renderer.ts +0 -428
- package/src/tui/providers-cmd.ts +0 -377
- package/src/tui/reasoning-view.ts +0 -56
- package/src/tui/renderer.ts +0 -128
- package/src/tui/replay-marker.ts +0 -29
- package/src/tui/session-cmd.ts +0 -148
- package/src/tui/session-manage.ts +0 -95
- package/src/tui/sextant-attach.ts +0 -102
- package/src/tui/sextant-io.ts +0 -202
- package/src/tui/sextant-smoke.ts +0 -110
- package/src/tui/shell-cmd.ts +0 -158
- package/src/tui/smoke.ts +0 -72
- package/src/tui/staged-terminal.ts +0 -50
- package/src/tui/startup.ts +0 -12
- package/src/tui/theme.ts +0 -59
- package/src/tui/todo-label.ts +0 -7
- package/src/tui/trust-card.ts +0 -107
- package/src/tui/tui-commands.ts +0 -87
- package/tsconfig.json +0 -30
- package/vendor/pi-tui/LICENSE +0 -21
- package/vendor/pi-tui/PATCHES.md +0 -12
- package/vendor/pi-tui/PROVENANCE.md +0 -12
- package/vendor/pi-tui/README.upstream.md +0 -854
- package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
- package/vendor/pi-tui/src/autocomplete.ts +0 -827
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
- package/vendor/pi-tui/src/components/box.ts +0 -138
- package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
- package/vendor/pi-tui/src/components/editor.ts +0 -2364
- package/vendor/pi-tui/src/components/h-stack.ts +0 -45
- package/vendor/pi-tui/src/components/image.ts +0 -128
- package/vendor/pi-tui/src/components/input.ts +0 -448
- package/vendor/pi-tui/src/components/loader.ts +0 -93
- package/vendor/pi-tui/src/components/markdown.ts +0 -1016
- package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
- package/vendor/pi-tui/src/components/select-list.ts +0 -230
- package/vendor/pi-tui/src/components/settings-list.ts +0 -277
- package/vendor/pi-tui/src/components/spacer.ts +0 -29
- package/vendor/pi-tui/src/components/stack.ts +0 -155
- package/vendor/pi-tui/src/components/text.ts +0 -108
- package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
- package/vendor/pi-tui/src/components/v-stack.ts +0 -34
- package/vendor/pi-tui/src/editor-component.ts +0 -75
- package/vendor/pi-tui/src/fuzzy.ts +0 -138
- package/vendor/pi-tui/src/index.ts +0 -149
- package/vendor/pi-tui/src/keybindings.ts +0 -321
- package/vendor/pi-tui/src/keys.ts +0 -1402
- package/vendor/pi-tui/src/kill-ring.ts +0 -47
- package/vendor/pi-tui/src/latex.ts +0 -1381
- package/vendor/pi-tui/src/layout-node.ts +0 -52
- package/vendor/pi-tui/src/layout.ts +0 -411
- package/vendor/pi-tui/src/native-modifiers.ts +0 -60
- package/vendor/pi-tui/src/native-module-path.ts +0 -32
- package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
- package/vendor/pi-tui/src/terminal-colors.ts +0 -74
- package/vendor/pi-tui/src/terminal-image.ts +0 -701
- package/vendor/pi-tui/src/terminal.ts +0 -554
- package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
- package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
- package/vendor/pi-tui/src/tui.ts +0 -1264
- package/vendor/pi-tui/src/undo-stack.ts +0 -29
- package/vendor/pi-tui/src/utils.ts +0 -1327
- package/vendor/pi-tui/src/word-navigation.ts +0 -118
- package/vendor/pi-tui/test/test-themes.ts +0 -39
- package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
- /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/tui/commands.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/** Custom slash commands (port #30): `.rovecode/commands/*.md` (project) and `~/.rovecode/commands/*.md`
|
|
2
|
-
* (user scope, ROVECODE_HOME-aware) become `/name` commands in the TUI — autocomplete, /help,
|
|
3
|
-
* and dispatch through the same user-turn path a typed prompt takes.
|
|
4
|
-
*
|
|
5
|
-
* Ported from opencode @ ebece6e (MIT), packages/opencode/src unless noted:
|
|
6
|
-
* - discovery: markdown files under a `command(s)/` dir, name = path sans extension
|
|
7
|
-
* (config/command.ts:13-39 load, config/entry-name.ts:15-19 configEntryNameFromPath);
|
|
8
|
-
* the frontmatter is the command's metadata and the markdown body its template
|
|
9
|
-
* (config/command.ts:26-30 `{ name, ...md.data, template: md.content.trim() }`); fields
|
|
10
|
-
* description/model (packages/core/src/v1/config/command.ts:5-12 — agent/variant/subtask
|
|
11
|
-
* have no rovecode counterpart; rovecode adds `mode` for plan/act, port #20).
|
|
12
|
-
* - precedence: the global config dir is loaded before the project's and later entries
|
|
13
|
-
* replace earlier ones (config/config.ts:473 mergeDeep) → project shadows user here.
|
|
14
|
-
* - templating (session/prompt.ts:1372-1395): quote-aware argument split (argsRegex :1594,
|
|
15
|
-
* quoteTrimRegex :1596), `$N` positional (placeholderRegex :1595), `$ARGUMENTS` = the whole
|
|
16
|
-
* argument string (:1391), and a template with no placeholder gets the arguments appended
|
|
17
|
-
* after a blank line (:1393-1395); `hints` (command/index.ts:36-44) lists the placeholders.
|
|
18
|
-
* - a per-command `model` overrides the session model for THAT prompt only (:1411-1412).
|
|
19
|
-
* Pattern reference only, no code ported: gemini-cli @ 0bd1d43 packages/cli/src/services/
|
|
20
|
-
* FileCommandLoader.ts — user dir first, project dir second, "last wins" (:85-90, :204-228),
|
|
21
|
-
* invalid files skipped with a report, never fatal (:277-298).
|
|
22
|
-
*
|
|
23
|
-
* Deviations: flat directory (no subdirectory namespaces); names are lowercased and must
|
|
24
|
-
* match [a-z0-9_-]+; YAML-lite frontmatter (`key: value` lines — skills/index.ts
|
|
25
|
-
* parseFrontmatter, reused) instead of gray-matter, so an unterminated block, a `mode`
|
|
26
|
-
* outside plan|act, or an empty body skips the file with a boot warning (never a throw);
|
|
27
|
-
* substitution is ONE pass, so substituted text is never re-scanned, `$$` is a literal `$`,
|
|
28
|
-
* and a positional `$N` is exactly one token (upstream's "last placeholder absorbs the rest",
|
|
29
|
-
* prompt.ts:1387, is not ported); no `!\`shell\`` / `@file` injection (prompt.ts:1397-1408) —
|
|
30
|
-
* the template reaches the model as plain text; a built-in TUI command always beats a custom
|
|
31
|
-
* one of the same name (warned at boot); a symlinked *.md counts when it points at a file (a
|
|
32
|
-
* dangling link is reported unreadable); boot-warning echoes and descriptions are bounded.
|
|
33
|
-
*
|
|
34
|
-
* TUI semantics (runCustomCommand): `mode` switches through the /plan-/act path and STAYS
|
|
35
|
-
* switched after the run — the switch is a durable session entry (port #20), and silently
|
|
36
|
-
* flipping back would desynchronize the status line from the session record; `model` is set
|
|
37
|
-
* the way /model does and restored after the run unless the user re-pointed it mid-run
|
|
38
|
-
* (/model is not busy-gated). With planActSeparateModels off, /model mirrors writes into
|
|
39
|
-
* both slots, so the restore does too — same as typing /model <prev> by hand. */
|
|
40
|
-
|
|
41
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
42
|
-
import { join, resolve } from "node:path";
|
|
43
|
-
import type { AgentMode, ModeManager } from "../core/modes.ts";
|
|
44
|
-
import { rovecodeHome } from "../providers/auth.ts";
|
|
45
|
-
import { parseFrontmatter } from "../skills/index.ts";
|
|
46
|
-
import { togglePlanAct, type ModeStateSlice } from "./modes-cmd.ts";
|
|
47
|
-
import { expandBuiltinSlash } from "./builtin-prompts.ts"; // port #53: built-in prompt commands beat same-named custom ones
|
|
48
|
-
import type { Renderer, SlashCommand } from "./renderer.ts";
|
|
49
|
-
|
|
50
|
-
export type CommandScope = "project" | "user";
|
|
51
|
-
|
|
52
|
-
export interface CustomCommand {
|
|
53
|
-
/** `/name`: the filename sans `.md`, lowercased; must match [a-z0-9_-]+ */
|
|
54
|
-
name: string;
|
|
55
|
-
description: string;
|
|
56
|
-
/** frontmatter `model:` — per-run override, applied the way /model does */
|
|
57
|
-
model?: string;
|
|
58
|
-
/** frontmatter `mode:` — the run happens in this plan/act mode */
|
|
59
|
-
mode?: AgentMode;
|
|
60
|
-
/** the markdown body: the prompt template */
|
|
61
|
-
body: string;
|
|
62
|
-
/** placeholders the template uses, for /help — opencode hints(): "$1 $2 $ARGUMENTS" */
|
|
63
|
-
hints: string[];
|
|
64
|
-
path: string;
|
|
65
|
-
scope: CommandScope;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface ParsedCommandFile { description?: string; model?: string; mode?: AgentMode; body: string }
|
|
69
|
-
|
|
70
|
-
const NAME_RE = /^[a-z0-9_-]+$/;
|
|
71
|
-
/** opencode prompt.ts:1594 minus its `[Image N]` alternative (no image parts reach this path). */
|
|
72
|
-
const ARGS_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
|
|
73
|
-
const QUOTE_TRIM_RE = /^["']|["']$/g;
|
|
74
|
-
/** one pass: `$$` (literal), `$ARGUMENTS`, `$1`..`$9` */
|
|
75
|
-
const PLACEHOLDER_RE = /\$(\$|ARGUMENTS|[1-9])/g;
|
|
76
|
-
/** /help + palette rows stay one line; a 20k-char frontmatter field must not become one */
|
|
77
|
-
const MAX_DESCRIPTION_CHARS = 200;
|
|
78
|
-
|
|
79
|
-
/** Bounded echo (todo.ts `show` idiom): file content quoted back in a warning is clipped. */
|
|
80
|
-
function clip(s: string, max: number): string {
|
|
81
|
-
return s.length > max ? s.slice(0, max - 1) + "…" : s;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ---------- file format ----------
|
|
85
|
-
|
|
86
|
-
/** Optional `---` frontmatter + body → command fields. `{ error }` names why a file must be
|
|
87
|
-
* skipped (unterminated block, `mode` outside plan|act, empty body). Never throws. */
|
|
88
|
-
export function parseCommandFile(text: string): ParsedCommandFile | { error: string } {
|
|
89
|
-
let src = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text; // drop a UTF-8 BOM
|
|
90
|
-
let fm: Record<string, string> = {};
|
|
91
|
-
let body = src;
|
|
92
|
-
if (src.startsWith("---")) {
|
|
93
|
-
if (!src.endsWith("\n")) src += "\n"; // parseFrontmatter finds the closing fence by its line end
|
|
94
|
-
const parsed = parseFrontmatter(src);
|
|
95
|
-
if (!parsed) return { error: "unterminated frontmatter (no closing ---)" };
|
|
96
|
-
fm = parsed.fm; body = parsed.body;
|
|
97
|
-
}
|
|
98
|
-
body = body.trim();
|
|
99
|
-
if (body === "") return { error: "empty command body (nothing to send)" };
|
|
100
|
-
const field = (k: string): string | undefined => { const v = fm[k]?.trim(); return v ? v : undefined; };
|
|
101
|
-
const mode = field("mode");
|
|
102
|
-
if (mode !== undefined && mode !== "plan" && mode !== "act") return { error: `mode must be "plan" or "act" (got "${clip(mode, 40)}")` }; // LOW-2: bounded echo
|
|
103
|
-
return { description: field("description"), model: field("model"), mode, body };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Placeholders a template uses: `$1..$9` sorted, then `$ARGUMENTS` (command/index.ts:36-44).
|
|
107
|
-
* `$$` escapes are dropped first so `$$1` does not count. */
|
|
108
|
-
export function hints(body: string): string[] {
|
|
109
|
-
const src = body.replace(/\$\$/g, "");
|
|
110
|
-
const out = [...new Set(src.match(/\$[1-9]/g) ?? [])].sort();
|
|
111
|
-
if (src.includes("$ARGUMENTS")) out.push("$ARGUMENTS");
|
|
112
|
-
return out;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ---------- discovery ----------
|
|
116
|
-
|
|
117
|
-
export interface DiscoverOptions {
|
|
118
|
-
/** user-scope rovecode dir; default rovecodeHome() (ROVECODE_HOME → ~/.rovecode). Commands: `<home>/commands` */
|
|
119
|
-
home?: string;
|
|
120
|
-
/** built-in command names — a custom command with one of these names is dropped, with a warning */
|
|
121
|
-
reserved?: readonly string[];
|
|
122
|
-
/** more command folders — a plugin's (src/plugins) — scanned AFTER the folder of the same scope, so
|
|
123
|
-
* your own `~/.rovecode/commands/x.md` or `.rovecode/commands/x.md` keeps `/x` over a plugin's */
|
|
124
|
-
extraDirs?: readonly { dir: string; scope: CommandScope }[];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface DiscoveredCommands { commands: CustomCommand[]; warnings: string[] }
|
|
128
|
-
|
|
129
|
-
/** Scan `<home>/commands` then `<cwd>/.rovecode/commands`; a later (project) entry replaces an
|
|
130
|
-
* earlier (user) one of the same name. Missing dirs are silent (the common case); every
|
|
131
|
-
* other problem is a warning for the boot transcript, never a throw. Sorted by name. */
|
|
132
|
-
export function discoverCommands(cwd: string, opts: DiscoverOptions = {}): DiscoveredCommands {
|
|
133
|
-
const warnings: string[] = [];
|
|
134
|
-
const reserved = new Set(opts.reserved ?? []);
|
|
135
|
-
const userDir = join(opts.home ?? rovecodeHome(), "commands");
|
|
136
|
-
const projectDir = join(cwd, ".rovecode", "commands");
|
|
137
|
-
const extra = (scope: CommandScope): [CommandScope, string][] => (opts.extraDirs ?? []).filter((e) => e.scope === scope).map((e) => [scope, e.dir]);
|
|
138
|
-
// cwd IS the rovecode home: one directory, scanned once (gemini-cli FileCommandLoader.ts:221-228)
|
|
139
|
-
const dirs: [CommandScope, string][] = resolve(userDir) === resolve(projectDir)
|
|
140
|
-
? [["project", projectDir], ...extra("user"), ...extra("project")]
|
|
141
|
-
: [["user", userDir], ...extra("user"), ["project", projectDir], ...extra("project")];
|
|
142
|
-
const byName = new Map<string, CustomCommand>();
|
|
143
|
-
for (const [scope, dir] of dirs) {
|
|
144
|
-
for (const cmd of scanCommandDir(dir, scope, warnings)) {
|
|
145
|
-
// the verdict first, the path last: an 80-column terminal wraps a long path mid-line and the
|
|
146
|
-
// sentence must survive at the line start
|
|
147
|
-
if (reserved.has(cmd.name)) { warnings.push(`/${cmd.name} is a built-in command — built-in kept (${cmd.path})`); continue; }
|
|
148
|
-
const prev = byName.get(cmd.name);
|
|
149
|
-
if (prev?.scope === scope) { warnings.push(`${cmd.path}: /${cmd.name} already defined by ${prev.path} — first kept`); continue; }
|
|
150
|
-
byName.set(cmd.name, cmd); // project (scanned second) shadows user
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return { commands: [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)), warnings };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** LOW-3: a symlinked `*.md` counts when its target is a file (a link to a directory is skipped
|
|
157
|
-
* like any directory); a DANGLING link stays in the list so the read below reports it unreadable. */
|
|
158
|
-
function linksToFile(path: string): boolean {
|
|
159
|
-
try { return statSync(path).isFile(); } catch { return true; }
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** One directory's `*.md` files (top level only, sorted), each parsed or warned about. */
|
|
163
|
-
function scanCommandDir(dir: string, scope: CommandScope, warnings: string[]): CustomCommand[] {
|
|
164
|
-
let files: string[];
|
|
165
|
-
try {
|
|
166
|
-
files = readdirSync(dir, { withFileTypes: true })
|
|
167
|
-
.filter((e) => /\.md$/i.test(e.name) && (e.isFile() || (e.isSymbolicLink() && linksToFile(join(dir, e.name)))))
|
|
168
|
-
.map((e) => e.name).sort();
|
|
169
|
-
} catch {
|
|
170
|
-
return []; // no such directory — silent, like the skills store
|
|
171
|
-
}
|
|
172
|
-
const out: CustomCommand[] = [];
|
|
173
|
-
for (const file of files) {
|
|
174
|
-
const path = join(dir, file);
|
|
175
|
-
const name = file.slice(0, -3).toLowerCase();
|
|
176
|
-
if (!NAME_RE.test(name)) { warnings.push(`${path}: skipped — command name "${name}" must match [a-z0-9_-]+`); continue; }
|
|
177
|
-
let text: string;
|
|
178
|
-
try {
|
|
179
|
-
text = readFileSync(path, "utf8");
|
|
180
|
-
} catch (e) {
|
|
181
|
-
warnings.push(`${path}: skipped — unreadable (${e instanceof Error ? e.message : String(e)})`);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const parsed = parseCommandFile(text);
|
|
185
|
-
if ("error" in parsed) { warnings.push(`${path}: skipped — ${parsed.error}`); continue; }
|
|
186
|
-
out.push({
|
|
187
|
-
name, description: clip(parsed.description ?? `custom command (${file})`, MAX_DESCRIPTION_CHARS), model: parsed.model, mode: parsed.mode,
|
|
188
|
-
body: parsed.body, hints: hints(parsed.body), path, scope,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
return out;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ---------- templating ----------
|
|
195
|
-
|
|
196
|
-
/** Template → prompt. `$ARGUMENTS` = the whole argument string, `$1..$9` = one quote-aware token
|
|
197
|
-
* each ("" when missing), `$$` = a literal `$`. One pass, so substituted text is never
|
|
198
|
-
* re-scanned. A template without placeholders gets the arguments appended after a blank line
|
|
199
|
-
* (prompt.ts:1393-1395) so nothing the user typed is silently dropped. */
|
|
200
|
-
export function renderCommand(cmd: Pick<CustomCommand, "body">, argString: string): string {
|
|
201
|
-
const raw = argString.trim();
|
|
202
|
-
const args = (raw.match(ARGS_RE) ?? []).map((a) => a.replace(QUOTE_TRIM_RE, ""));
|
|
203
|
-
let placeholder = false;
|
|
204
|
-
const out = cmd.body.replace(PLACEHOLDER_RE, (_m, tok: string) => {
|
|
205
|
-
if (tok === "$") return "$";
|
|
206
|
-
placeholder = true;
|
|
207
|
-
return tok === "ARGUMENTS" ? raw : (args[Number(tok) - 1] ?? "");
|
|
208
|
-
});
|
|
209
|
-
return placeholder || raw === "" ? out : `${out}\n\n${raw}`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** `rovecode run "/name args"` hook (main.ts call sites — see the port notes): a leading `/name`
|
|
213
|
-
* that names a discovered command renders to its prompt; anything else passes through
|
|
214
|
-
* verbatim. `model`/`mode` are TUI semantics and are not applied here. */
|
|
215
|
-
export function expandSlashPrompt(prompt: string, cwd: string, opts: DiscoverOptions = {}): string {
|
|
216
|
-
// port #53: the built-in prompt commands (/init) are consulted FIRST — a built-in beats a
|
|
217
|
-
// same-named custom command, as in the TUI (aion commands.ts:216). Order matters: reversed, a
|
|
218
|
-
// repo file named init.md would silently swallow rovecode run "/init".
|
|
219
|
-
const builtin = expandBuiltinSlash(prompt, cwd);
|
|
220
|
-
if (builtin !== undefined) return builtin;
|
|
221
|
-
const m = /^\/(\S+)(?:\s+([\s\S]*))?$/.exec(prompt.trim());
|
|
222
|
-
if (!m) return prompt;
|
|
223
|
-
const cmd = discoverCommands(cwd, opts).commands.find((c) => c.name === m[1]!.toLowerCase());
|
|
224
|
-
return cmd ? renderCommand(cmd, m[2] ?? "") : prompt;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ---------- TUI surface ----------
|
|
228
|
-
|
|
229
|
-
/** Entries for Renderer.setCommands (editor autocomplete) — appended after the built-ins. */
|
|
230
|
-
export function commandsForPalette(list: readonly CustomCommand[]): SlashCommand[] {
|
|
231
|
-
return list.map((c) => ({ name: c.name, description: c.description }));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** `/help` tail: "" when there are none, else a "custom:" section (name, hints, description, scope). */
|
|
235
|
-
export function helpForCommands(list: readonly CustomCommand[]): string {
|
|
236
|
-
if (list.length === 0) return "";
|
|
237
|
-
return "\ncustom:\n" + list
|
|
238
|
-
.map((c) => `/${c.name}${c.hints.length > 0 ? " " + c.hints.join(" ") : ""} — ${c.description} (${c.scope})`)
|
|
239
|
-
.join("\n");
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** What the TUI hands the dispatcher (app.ts builds it once; state is read live). */
|
|
243
|
-
export interface CustomCommandCtx {
|
|
244
|
-
renderer: Renderer;
|
|
245
|
-
modes: ModeManager;
|
|
246
|
-
state: ModeStateSlice;
|
|
247
|
-
pushStatus: () => void;
|
|
248
|
-
/** the plain user-turn path (app.ts `submit`): echo, flush a pending mode switch, start the run */
|
|
249
|
-
submit: (text: string) => Promise<void>;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** handleSlash `default:` hook: false when `name` is not a custom command (the caller reports
|
|
253
|
-
* "unknown command"); true after kicking off the run — fire-and-forget, the same shape as the
|
|
254
|
-
* other async slash commands. */
|
|
255
|
-
export function dispatchCustomCommand(ctx: CustomCommandCtx, list: readonly CustomCommand[], name: string, arg: string): boolean {
|
|
256
|
-
const cmd = list.find((c) => c.name === name);
|
|
257
|
-
if (!cmd) return false;
|
|
258
|
-
void runCustomCommand(ctx, cmd, arg);
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** Busy-gated like /plan and /checkpoints — a rendered prompt with a model/mode attached cannot
|
|
263
|
-
* be queued as steering. `mode` → togglePlanAct (stays); `model` → setModel like /model,
|
|
264
|
-
* restored after the run unless the user re-pointed it meanwhile (header: TUI semantics). */
|
|
265
|
-
export async function runCustomCommand(ctx: CustomCommandCtx, cmd: CustomCommand, arg: string): Promise<void> {
|
|
266
|
-
if (ctx.state.busy) { ctx.renderer.addSystemNote("finish or interrupt the run first (Esc)", "warn"); return; }
|
|
267
|
-
if (cmd.mode !== undefined && cmd.mode !== ctx.modes.mode) togglePlanAct(ctx.modes, cmd.mode, ctx.state, ctx.renderer, ctx.pushStatus);
|
|
268
|
-
const prev = ctx.modes.modelFor().model;
|
|
269
|
-
const override = cmd.model !== undefined && cmd.model !== prev;
|
|
270
|
-
if (override) {
|
|
271
|
-
ctx.modes.setModel({ model: cmd.model });
|
|
272
|
-
ctx.state.model = ctx.modes.modelFor().model;
|
|
273
|
-
ctx.renderer.addSystemNote(`model → ${cmd.model} for /${cmd.name} (restored after the run)`);
|
|
274
|
-
ctx.pushStatus();
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
await ctx.submit(renderCommand(cmd, arg));
|
|
278
|
-
} finally {
|
|
279
|
-
if (override && ctx.modes.modelFor().model === cmd.model) {
|
|
280
|
-
ctx.modes.setModel({ model: prev }); ctx.state.model = prev; ctx.pushStatus();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
package/src/tui/config-view.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/** `/config [all]` (aion port, re-shaped): the read-only TUI view of rovecode's ACTUAL settings model — the two
|
|
2
|
-
* settings.json scopes (core/settings.ts) merged the way the loaders merge them, plus the environment the
|
|
3
|
-
* process is running with. Aion's `describeSettings`/knob table is NOT ported: rovecode has no such table, and
|
|
4
|
-
* a view that invented one would describe a settings model the real loaders do not use. What IS shown is what
|
|
5
|
-
* the loaders read, keyed the way they read it:
|
|
6
|
-
* - files first: which of the two settings.json files exists (a missing project file is the common case,
|
|
7
|
-
* and "you could create one" is more useful than pretending it is empty);
|
|
8
|
-
* - then every settings.json key that is SET, with the scope that wins for it (project beats user — the
|
|
9
|
-
* merge in loadSettings) and the value; `verify: false` shown as read (a refusal, honoured from any file);
|
|
10
|
-
* - then the ROVECODE_* variables this process carries (names + values, values of keys never shown);
|
|
11
|
-
* - the permission level is resolved the way the CLI resolves it and stated in words.
|
|
12
|
-
* Nothing is written here; editing is the file by hand (rovecode has no `config set`). A value changed on
|
|
13
|
-
* disk mid-session is picked up on the next read — the loaders read per call — so this view is a snapshot of
|
|
14
|
-
* NOW, not of boot. Same ctx idiom as info-cmd.ts: renderer + cwd. */
|
|
15
|
-
|
|
16
|
-
import type { Renderer } from "./renderer.ts";
|
|
17
|
-
import { existsSync } from "node:fs";
|
|
18
|
-
import { loadSettings, loadSettingsScoped, resolvePermission, settingsPath, type Settings } from "../core/settings.ts";
|
|
19
|
-
import { rovecodeHome } from "../providers/auth.ts";
|
|
20
|
-
|
|
21
|
-
export interface ConfigViewCtx { renderer: Pick<Renderer, "addSystemNote">; cwd: string; /** the CLI's explicit flag, when it gave one */ permissionFlag?: Parameters<typeof resolvePermission>[1] }
|
|
22
|
-
|
|
23
|
-
const KEYS: readonly (keyof Settings)[] = ["permission", "effort", "bell", "notify", "notify_when", "notify_command", "verify", "lsp"];
|
|
24
|
-
const short = (v: unknown): string => { const s = typeof v === "string" ? v : JSON.stringify(v); return s.length > 80 ? `${s.slice(0, 79)}…` : s; };
|
|
25
|
-
|
|
26
|
-
/** the note text, pure — `all` also lists the keys nothing set and the env vars named-but-unset is out of scope:
|
|
27
|
-
* rovecode's env page (`rovecode help env`) is the complete list, this view shows what THIS run carries */
|
|
28
|
-
export function renderConfigView(cwd: string, all: boolean, env: Readonly<Record<string, string | undefined>> = process.env, home: string = rovecodeHome(), flag?: Parameters<typeof resolvePermission>[1]): string {
|
|
29
|
-
const user = settingsPath("user", cwd);
|
|
30
|
-
const project = settingsPath("project", cwd);
|
|
31
|
-
const scoped = loadSettingsScoped(cwd, home);
|
|
32
|
-
const merged = loadSettings(cwd);
|
|
33
|
-
const lines: string[] = [];
|
|
34
|
-
lines.push(`settings — user: ${user}${existsSync(user) ? "" : " (absent)"} · project: ${project}${existsSync(project) ? "" : " (absent)"}`);
|
|
35
|
-
lines.push(`permission: ${resolvePermission(cwd, flag, env)}${scoped.dropped.length > 0 ? ` · the project file's ${scoped.dropped.join(", ")} ${scoped.dropped.length === 1 ? "is" : "are"} ignored until you trust that file (rovecode trust show)` : ""}`);
|
|
36
|
-
const set = KEYS.filter((k) => merged[k] !== undefined && k !== "permission");
|
|
37
|
-
if (set.length === 0) lines.push("(no settings.json key is set — every knob is at its default)");
|
|
38
|
-
for (const k of set) {
|
|
39
|
-
const scope = scoped.project[k] !== undefined ? "project" : "user";
|
|
40
|
-
lines.push(`${k} = ${short(merged[k])} [${scope}]`);
|
|
41
|
-
}
|
|
42
|
-
const envNames = Object.keys(env).filter((k) => k.startsWith("ROVECODE_") && env[k] !== "").sort();
|
|
43
|
-
if (envNames.length > 0) {
|
|
44
|
-
if (all) for (const n of envNames) lines.push(`${n} = ${/KEY|TOKEN|SECRET/i.test(n) ? "(set — value not shown)" : short(env[n])} [env]`);
|
|
45
|
-
else lines.push(`env: ${envNames.length} ROVECODE_* variable${envNames.length === 1 ? "" : "s"} set (/config all names them)`);
|
|
46
|
-
}
|
|
47
|
-
return lines.join("\n");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** `/config [all]` */
|
|
51
|
-
export function cmdConfigView(ctx: ConfigViewCtx, arg: string): void {
|
|
52
|
-
ctx.renderer.addSystemNote(renderConfigView(ctx.cwd, arg.trim() === "all", process.env, rovecodeHome(), ctx.permissionFlag));
|
|
53
|
-
}
|
package/src/tui/context-cmds.ts
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
/** TUI context commands (port #53): /compact [focus] · /clear · /init · /copy [n]. Split out of
|
|
2
|
-
* app.ts for the ADR-002 cap; app.ts adds the four palette entries (CONTEXT_COMMANDS) and the
|
|
3
|
-
* dispatch cases. The CALLER owns the swappable store, the mode manager and the session-switch
|
|
4
|
-
* routine, so everything runs through the injected ctx (session-cmd.ts shape).
|
|
5
|
-
*
|
|
6
|
-
* /compact ADR-007: the ONE strategy set. compactSession calls core/compaction.ts planCompaction +
|
|
7
|
-
* applyCompaction with the run's own RunConfig (rt.buildCfg) and the loop's CompactionCtx
|
|
8
|
-
* shape (trigger "manual"; tokenText = partsTokenText). PORT NOTE — this tree has no
|
|
9
|
-
* summarizer wired anywhere (rovecode's runtime builds none; grep `.summarize` finds only
|
|
10
|
-
* memory/recall's own), and its `Summarizer` is `(texts) => Promise<string>` with no signal
|
|
11
|
-
* argument (aion's #68 one takes the signal and returns usage/origin). So on THIS tree
|
|
12
|
-
* head-summarize falls back to keep-window at PLAN time and the note says "no summarizer
|
|
13
|
-
* wired on this surface" — that note is the feature working, not a degradation, and it is
|
|
14
|
-
* the user-visible text until #68 lands (owned by nimbus-2d as a core piece). The signal
|
|
15
|
-
* still matters: loop.ts:215 threads ctx.signal, and when #68 lands the abort path is
|
|
16
|
-
* already here — cmdCompact OWNS an AbortController bound through ctx.bindAbort (the app's
|
|
17
|
-
* Esc / ⌃c target, ShellCtx.bindAbort shape; the --plain REPL binds Ctrl-C the same way).
|
|
18
|
-
* A manual compaction must be DURABLE or the next run would see the full history again
|
|
19
|
-
* (the automatic one is per-run and transient — loop.ts rebuilds history from
|
|
20
|
-
* store.messages() every run): the outcome history becomes a new branch of the append-only
|
|
21
|
-
* tree (fresh-id copies chained under the summary/marker message as a new root; ADR-004 —
|
|
22
|
-
* nothing deleted, the earlier turns stay in the file on the previous branch), the leaf
|
|
23
|
-
* moves there (meta.json, like /new), and the compaction marker is persisted with
|
|
24
|
-
* store.appendEvent — the same event shape and call the loop uses (loop.ts:216-217) — so
|
|
25
|
-
* replay and `rovecode export` show it. A focus hint is accepted but NOT applied: none of
|
|
26
|
-
* the strategies takes instructions (the summarizer signature is texts only); the note says
|
|
27
|
-
* so.
|
|
28
|
-
* /clear a fresh session in place through the app's switchSession: same cwd/renderer, the live
|
|
29
|
-
* mode and the model slots survive, the transcript is emptied, the previous session is
|
|
30
|
-
* untouched and resumable.
|
|
31
|
-
* /init submits the built-in AGENTS.md prompt (builtin-prompts.ts, ported alongside) as a plain
|
|
32
|
-
* user turn — the write/edit goes through the approval seam like any tool call.
|
|
33
|
-
* /copy the last (or n-th from the end) assistant message's text → the clipboard (clipboard.ts).
|
|
34
|
-
* All four are busy-gated with the shared "finish or interrupt the run first" note.
|
|
35
|
-
*
|
|
36
|
-
* Pattern reference only, no code ported: gemini-cli @ 0bd1d43 packages/cli/src/ui/commands/
|
|
37
|
-
* {compressCommand,clearCommand,initCommand,copyCommand}.ts — force-compress shows original→new
|
|
38
|
-
* token counts (:49-61); /clear ends the recording, mints a new session id and clears the UI
|
|
39
|
-
* (:41-76); /copy takes the last model message's text parts (:24-41). */
|
|
40
|
-
|
|
41
|
-
import { randomUUID } from "node:crypto";
|
|
42
|
-
import { existsSync } from "node:fs";
|
|
43
|
-
import { basename, isAbsolute, join } from "node:path";
|
|
44
|
-
import { applyCompaction, planCompaction, DEFAULT_COMPACTION_STRATEGY, type CompactionCtx, type CompactionStrategy } from "../core/compaction.ts";
|
|
45
|
-
import { estimateTokens } from "../core/context.ts";
|
|
46
|
-
import { partsText, partsTokenText } from "../core/loop.ts";
|
|
47
|
-
import { buildModeChangeEntry, modeFromEntries, type AgentMode, type ModeManager } from "../core/modes.ts";
|
|
48
|
-
import type { SessionStore } from "../core/session.ts";
|
|
49
|
-
import { ATTACHMENTS_DIR } from "../core/session-images.ts";
|
|
50
|
-
import type { Message, MessagePart, RunConfig } from "../core/types.ts";
|
|
51
|
-
import { expandBuiltinSlash, INIT_TARGET } from "./builtin-prompts.ts";
|
|
52
|
-
import { copyToClipboard, type ClipboardOptions } from "./clipboard.ts";
|
|
53
|
-
import type { ModeStateSlice } from "./modes-cmd.ts";
|
|
54
|
-
import type { Renderer, SlashCommand } from "./renderer.ts";
|
|
55
|
-
import { compactionNote, type CompactionEvent } from "./replay-marker.ts";
|
|
56
|
-
|
|
57
|
-
/** palette + /help entries, appended to TUI_COMMANDS (reserved against custom commands like the rest).
|
|
58
|
-
* rovecode's SlashCommand carries a `group` — the /help topic (info-cmd.ts). /compact and /copy are
|
|
59
|
-
* session-scope facts (what the context holds, what to copy out of it); /init writes a project file,
|
|
60
|
-
* /clear starts over, so they sit with files & history and session. */
|
|
61
|
-
export const CONTEXT_COMMANDS: SlashCommand[] = [
|
|
62
|
-
{ name: "compact", description: "Compact the context now: /compact [focus] — the automatic strategy set, marker persisted", group: "session" },
|
|
63
|
-
{ name: "clear", description: "Fresh session in place (transcript emptied; the previous session is kept — /resume <id>)", group: "session" },
|
|
64
|
-
{ name: "init", description: "Analyse the repo and write AGENTS.md (an existing one gets targeted improvements)", group: "files & history" },
|
|
65
|
-
{ name: "copy", description: "Copy the last assistant message to the clipboard: /copy [n] (n-th from the end)", group: "session" },
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
/** a session below this many messages on the active path is "short": one note, no plan, no model call */
|
|
69
|
-
export const MIN_COMPACT_MESSAGES = 6;
|
|
70
|
-
const BUSY_WARN = "finish or interrupt the run first (Esc)";
|
|
71
|
-
|
|
72
|
-
export interface ContextCmdCtx {
|
|
73
|
-
renderer: Renderer;
|
|
74
|
-
cwd: string;
|
|
75
|
-
busy(): boolean;
|
|
76
|
-
/** the ACTIVE session store, read live */
|
|
77
|
-
store(): SessionStore;
|
|
78
|
-
modes: ModeManager;
|
|
79
|
-
/** the app's status slice (mode/model/provider/busy) — mutated in place like modes-cmd does */
|
|
80
|
-
state: ModeStateSlice;
|
|
81
|
-
/** the modes config's defaultMode ?? "act": what a session with no mode entry resumes to */
|
|
82
|
-
defaultMode: AgentMode;
|
|
83
|
-
/** the RunConfig a run would start with right now (rt.buildCfg) — the compaction strategy/budget source */
|
|
84
|
-
buildCfg(): RunConfig;
|
|
85
|
-
/** THIS TREE: no summarizer is wired anywhere (see the header), so this stays undefined in the TUI;
|
|
86
|
-
* the field exists so #68's runtime wiring has a seam to land in and the tests can inject one.
|
|
87
|
-
* rovecode's Summarizer shape: (texts: string[]) => Promise<string> */
|
|
88
|
-
summarize?: CompactionCtx["summarize"];
|
|
89
|
-
/** the app's interrupt target (Esc / ⌃c / quit abort it) — bound for /compact's lifetime, then
|
|
90
|
-
* cleared with null (ShellCtx.bindAbort shape) */
|
|
91
|
-
bindAbort?(ac: AbortController | null): void;
|
|
92
|
-
/** rebind the app to session id (store, memory, mode) and replay it (app.ts switchSession) */
|
|
93
|
-
switchSession(id: string, announce?: boolean): void;
|
|
94
|
-
replayHistory(): void;
|
|
95
|
-
refreshUsage(): void;
|
|
96
|
-
pushStatus(): void;
|
|
97
|
-
/** the plain user-turn path (app.ts submit) */
|
|
98
|
-
submit(text: string): Promise<void>;
|
|
99
|
-
/** test seam: platform + spawner for /copy; default = the real chain */
|
|
100
|
-
clipboard?: ClipboardOptions;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ---------- /compact ----------
|
|
104
|
-
|
|
105
|
-
export type CompactOutcome =
|
|
106
|
-
| { kind: "compacted"; event: CompactionEvent; fallbackFrom?: CompactionStrategy; dropped: number; kept: number }
|
|
107
|
-
| { kind: "nothing"; reason: string };
|
|
108
|
-
|
|
109
|
-
/** the surface's half of the loop's CompactionCtx (loop.ts:215) — what it wires for automatic compaction */
|
|
110
|
-
export interface CompactDeps { model?: CompactionCtx["model"]; summarize?: CompactionCtx["summarize"]; native?: CompactionCtx["native"]; signal?: AbortSignal }
|
|
111
|
-
|
|
112
|
-
/** The on-disk form of copied parts: a hydrated sidecar path (absolute, under <session>/attachments)
|
|
113
|
-
* goes back to the canonical `attachments/<file>` — a persisted absolute path is dropped at load
|
|
114
|
-
* (session-images.ts F3), so re-appending the in-memory form would silently lose the image. */
|
|
115
|
-
function persistableParts(parts: readonly MessagePart[]): MessagePart[] {
|
|
116
|
-
return parts.map((p) => p.kind === "image" && p.path !== undefined && isAbsolute(p.path) ? { ...p, path: `${ATTACHMENTS_DIR}/${basename(p.path)}` } : p);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Compact the store's active path NOW through the ONE strategy set and make the result durable
|
|
120
|
-
* (header). Pure over (store, cfg, deps): no renderer, so the --plain REPL shares it. */
|
|
121
|
-
export async function compactSession(store: SessionStore, cfg: RunConfig, deps: CompactDeps = {}): Promise<CompactOutcome> {
|
|
122
|
-
const history = store.messages();
|
|
123
|
-
const n = history.length;
|
|
124
|
-
if (n < MIN_COMPACT_MESSAGES) return { kind: "nothing", reason: `nothing to compact — ${n} message${n === 1 ? "" : "s"} on the active path (compaction needs at least ${MIN_COMPACT_MESSAGES})` };
|
|
125
|
-
const tokenText = (m: Message): string => partsTokenText(m.parts);
|
|
126
|
-
const tokensOf = (ms: readonly Message[]): number => ms.reduce((t, m) => t + estimateTokens(tokenText(m)), 0);
|
|
127
|
-
// PORT NOTE: aion passes trigger "manual"; rovecode's CompactionTrigger is "speculative" |
|
|
128
|
-
// "emergency" (aion's "manual" arrives with #68's core work, owned by 2d). "speculative" is the
|
|
129
|
-
// honest stand-in: a threshold-triggered plan — and the marker this writes says strategy + token
|
|
130
|
-
// counts only (compactionNote), so no surface reads the trigger word.
|
|
131
|
-
const cctx: CompactionCtx = { trigger: "speculative", tokenText, summarize: deps.summarize, native: deps.native, model: deps.model, signal: deps.signal };
|
|
132
|
-
const plan = planCompaction(history, cfg, cctx);
|
|
133
|
-
const out = plan ? await applyCompaction(history, plan, cfg, cctx) : null;
|
|
134
|
-
if (!out) return { kind: "nothing", reason: `nothing to compact — ${cfg.compactionStrategy ?? DEFAULT_COMPACTION_STRATEGY} found nothing droppable (${tokensOf(history)} tokens)` };
|
|
135
|
-
const tokensBefore = tokensOf(history);
|
|
136
|
-
const tokensAfter = tokensOf(out.history);
|
|
137
|
-
// durable branch: fresh-id copies (the same ids would be duplicate-id corruption) chained under a
|
|
138
|
-
// new root; the stage is parked meanwhile — append() folds staged attachments into ANY user message
|
|
139
|
-
const staged = store.stagedAttachments;
|
|
140
|
-
store.stageAttachments([]);
|
|
141
|
-
const prevLeaf = history.at(-1)?.id;
|
|
142
|
-
let parentId: string | null = null;
|
|
143
|
-
try {
|
|
144
|
-
for (const m of out.history) {
|
|
145
|
-
const copy: Message = { ...m, id: randomUUID(), parentId, parts: persistableParts(m.parts) };
|
|
146
|
-
store.append(copy);
|
|
147
|
-
parentId = copy.id;
|
|
148
|
-
}
|
|
149
|
-
if (parentId !== null) store.branch(parentId); // durable leaf (meta.json), like /new — a resume lands on the compacted branch
|
|
150
|
-
} catch (e) {
|
|
151
|
-
if (prevLeaf !== undefined) store.branch(prevLeaf); // a half-written branch is abandoned, the old path stays active
|
|
152
|
-
throw e;
|
|
153
|
-
} finally {
|
|
154
|
-
store.stageAttachments(staged);
|
|
155
|
-
}
|
|
156
|
-
// the marker, exactly as loop.ts persists the automatic one (rovecode's CompactionEvent has no
|
|
157
|
-
// fallback tag/usage fields — those arrive with #68; the marker already shows before → after tokens)
|
|
158
|
-
// the marker, exactly as loop.ts persists the automatic one (loop.ts:216-217). trigger is omitted:
|
|
159
|
-
// rovecode's RunEvent types it "speculative" | "emergency" only, and a manual compaction has no
|
|
160
|
-
// honest word in that union yet (#68) — absent beats a false one, and compactionNote renders
|
|
161
|
-
// strategy + counts, never the trigger.
|
|
162
|
-
const event: CompactionEvent = { type: "compaction", strategy: out.strategy, tokensBefore, tokensAfter };
|
|
163
|
-
store.appendEvent(event);
|
|
164
|
-
const original = new Set(history.map((m) => m.id));
|
|
165
|
-
const kept = out.history.filter((m) => original.has(m.id)).length;
|
|
166
|
-
return { kind: "compacted", event, dropped: n - kept, kept, ...(out.fallbackFrom ? { fallbackFrom: out.fallbackFrom } : {}) };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** The detail line after the marker's before/after line. rovecode's plan-time fallback carries
|
|
170
|
-
* `fallbackFrom` ("head-summarize" when no summarizer is wired — THIS TREE'S normal case) but no
|
|
171
|
-
* reason text; the wording names the wiring, which is the thing missing here (aion #68 adds the
|
|
172
|
-
* apply-time reason with the fenced summarizer). */
|
|
173
|
-
export function compactDetails(res: Extract<CompactOutcome, { kind: "compacted" }>, focus: string): string {
|
|
174
|
-
const s = (k: number): string => (k === 1 ? "" : "s");
|
|
175
|
-
return `${res.dropped} message${s(res.dropped)} dropped, ${res.kept} kept — the earlier turns stay in the session file on the previous branch` +
|
|
176
|
-
(res.fallbackFrom ? ` · ${res.fallbackFrom} could not run on this surface (no summarizer wired) — ${res.event.strategy} ran instead` : "") +
|
|
177
|
-
(focus ? ` · focus "${focus}" not applied — the compaction strategies take no instructions` : "");
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** Both lines for a surface without replay (the --plain REPL): the marker wording, then the details. */
|
|
181
|
-
export function compactNoteLines(res: CompactOutcome, focus: string): string[] {
|
|
182
|
-
return res.kind === "nothing" ? [res.reason] : [compactionNote(res.event), compactDetails(res, focus)];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** /compact [focus] — busy-gated, and it HOLDS the busy flag while it runs (a concurrent submit queues
|
|
186
|
-
* as steering instead of starting a run — a future summarizer would be a model call) without a
|
|
187
|
-
* renderer spinner: there is no run for the surface to settle (the sextant would mark one "stopped").
|
|
188
|
-
* The transcript is replayed from the compacted path, so what the user sees is what the model gets:
|
|
189
|
-
* the summary/marker note, the kept turns, then the persisted marker line (before → after tokens). */
|
|
190
|
-
export async function cmdCompact(ctx: ContextCmdCtx, focus: string): Promise<void> {
|
|
191
|
-
if (ctx.busy()) { ctx.renderer.addSystemNote(BUSY_WARN, "warn"); return; }
|
|
192
|
-
const store = ctx.store();
|
|
193
|
-
ctx.state.busy = true;
|
|
194
|
-
const ac = new AbortController(); // Esc / ⌃c abort the in-flight summarize call — bound like `!cmd` (shell-cmd.ts), cleared after
|
|
195
|
-
ctx.bindAbort?.(ac);
|
|
196
|
-
let res: CompactOutcome;
|
|
197
|
-
try {
|
|
198
|
-
res = await compactSession(store, ctx.buildCfg(), { model: ctx.modes.modelFor(), summarize: ctx.summarize, signal: ac.signal }); // the runtime's summarizer, the command's signal
|
|
199
|
-
} catch (e) {
|
|
200
|
-
res = { kind: "nothing", reason: `compaction failed: ${e instanceof Error ? e.message : String(e)} — the session is unchanged` };
|
|
201
|
-
} finally {
|
|
202
|
-
ctx.bindAbort?.(null);
|
|
203
|
-
ctx.state.busy = false;
|
|
204
|
-
}
|
|
205
|
-
if (res.kind === "nothing") { ctx.renderer.addSystemNote(res.reason, res.reason.startsWith("compaction failed") ? "error" : "info"); ctx.pushStatus(); return; }
|
|
206
|
-
// a mode entry that lived in the dropped head would make a resume land in the default mode:
|
|
207
|
-
// re-record the live mode on the new branch (durable entry, same shape flushModeSwitch writes)
|
|
208
|
-
const resumesAs = modeFromEntries(store.messages()) ?? ctx.defaultMode;
|
|
209
|
-
if (resumesAs !== ctx.modes.mode) store.append(buildModeChangeEntry({ from: resumesAs, to: ctx.modes.mode }, store.messages().at(-1)?.id ?? null));
|
|
210
|
-
ctx.replayHistory(); ctx.refreshUsage(); ctx.pushStatus();
|
|
211
|
-
ctx.renderer.addSystemNote(compactDetails(res, focus));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ---------- /clear ----------
|
|
215
|
-
|
|
216
|
-
/** /clear — a fresh session in place. switchSession restores the CONFIG default mode for a session
|
|
217
|
-
* with no entries; a clear keeps the LIVE mode, recorded the way /plan records it (a pending notice
|
|
218
|
-
* the next submit — or quit — flushes as a durable entry), and the in-memory model slots carry over. */
|
|
219
|
-
export function cmdClear(ctx: ContextCmdCtx): void {
|
|
220
|
-
if (ctx.busy()) { ctx.renderer.addSystemNote(BUSY_WARN, "warn"); return; }
|
|
221
|
-
const old = ctx.store().id;
|
|
222
|
-
const mode = ctx.modes.mode;
|
|
223
|
-
const id = randomUUID();
|
|
224
|
-
ctx.switchSession(id, false);
|
|
225
|
-
if (ctx.modes.mode !== mode) {
|
|
226
|
-
ctx.modes.toggle(mode);
|
|
227
|
-
const cur = ctx.modes.modelFor();
|
|
228
|
-
ctx.state.mode = mode; ctx.state.model = cur.model; ctx.state.provider = cur.provider;
|
|
229
|
-
ctx.pushStatus();
|
|
230
|
-
}
|
|
231
|
-
ctx.renderer.addSystemNote(`cleared — fresh session ${id.slice(0, 8)} in the same directory; session ${old.slice(0, 8)} kept (/resume ${old.slice(0, 8)})`);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ---------- /init ----------
|
|
235
|
-
|
|
236
|
-
/** /init — the built-in AGENTS.md prompt through the plain user-turn path. Busy-gated like a custom
|
|
237
|
-
* command (runCustomCommand): a built-in prompt is never queued as steering. */
|
|
238
|
-
export async function cmdInit(ctx: ContextCmdCtx): Promise<void> {
|
|
239
|
-
if (ctx.busy()) { ctx.renderer.addSystemNote(BUSY_WARN, "warn"); return; }
|
|
240
|
-
const prompt = expandBuiltinSlash("/init", ctx.cwd);
|
|
241
|
-
if (prompt === undefined) return; // unreachable: "init" is in BUILTIN_PROMPTS
|
|
242
|
-
ctx.renderer.addSystemNote(existsSync(join(ctx.cwd, INIT_TARGET))
|
|
243
|
-
? `${INIT_TARGET} exists — asking the agent for targeted improvements (edits go through the usual approval)`
|
|
244
|
-
: `analysing the repository to write ${INIT_TARGET} (the write goes through the usual approval)`);
|
|
245
|
-
await ctx.submit(prompt);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ---------- /copy ----------
|
|
249
|
-
|
|
250
|
-
/** The n-th-from-the-end assistant message WITH text (tool-call-only turns do not count) → the
|
|
251
|
-
* clipboard; one note either way, never a throw. Shared with the --plain REPL. */
|
|
252
|
-
export async function copyAssistantMessage(messages: readonly Message[], n: number, opts: ClipboardOptions = {}): Promise<{ text: string; tone: "info" | "warn" }> {
|
|
253
|
-
const texts = messages.filter((m) => m.role === "assistant").map((m) => partsText(m.parts)).filter((t) => t.trim() !== "");
|
|
254
|
-
if (texts.length === 0) return { text: "nothing to copy — no assistant message with text yet", tone: "warn" };
|
|
255
|
-
const pick = texts[texts.length - n];
|
|
256
|
-
if (pick === undefined) return { text: `nothing to copy — only ${texts.length} assistant message${texts.length === 1 ? "" : "s"} with text (asked for number ${n} from the end)`, tone: "warn" };
|
|
257
|
-
const res = await copyToClipboard(pick, opts);
|
|
258
|
-
if (res.ok) return { text: `copied ${n === 1 ? "the last assistant message" : `assistant message ${n} from the end`} (${pick.length} chars) to the clipboard via ${res.tool}`, tone: "info" };
|
|
259
|
-
return { text: `clipboard unavailable — tried ${res.tried.join(", ") || "nothing"}${res.detail ? ` (${res.detail})` : ""}; the text stays in the transcript`, tone: "warn" };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** /copy [n] — n defaults to 1 (the last); any other argument is a usage note. */
|
|
263
|
-
export async function cmdCopy(ctx: ContextCmdCtx, arg: string): Promise<void> {
|
|
264
|
-
if (ctx.busy()) { ctx.renderer.addSystemNote(BUSY_WARN, "warn"); return; }
|
|
265
|
-
const n = arg === "" ? 1 : /^[1-9]\d*$/.test(arg) ? Number(arg) : NaN;
|
|
266
|
-
if (Number.isNaN(n)) { ctx.renderer.addSystemNote("usage: /copy [n] — copies the last assistant message; n counts back from the latest (2 = the one before)", "warn"); return; }
|
|
267
|
-
const line = await copyAssistantMessage(ctx.store().messages(), n, ctx.clipboard ?? {});
|
|
268
|
-
ctx.renderer.addSystemNote(line.text, line.tone);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ---------- dispatch ----------
|
|
272
|
-
|
|
273
|
-
/** handleSlash hook for the four names (CONTEXT_COMMANDS); fire-and-forget like the other async slash commands. */
|
|
274
|
-
export async function runContextCommand(ctx: ContextCmdCtx, cmd: string, arg: string): Promise<void> {
|
|
275
|
-
switch (cmd) {
|
|
276
|
-
case "compact": return cmdCompact(ctx, arg);
|
|
277
|
-
case "clear": return cmdClear(ctx);
|
|
278
|
-
case "init": return cmdInit(ctx);
|
|
279
|
-
case "copy": return cmdCopy(ctx, arg);
|
|
280
|
-
default: return;
|
|
281
|
-
}
|
|
282
|
-
}
|