rovecode 0.3.3 → 0.4.0-beta.3
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 +527 -0
- package/README.md +46 -48
- package/bin/rovecode.js +24 -0
- package/dist/cli/app-j6gn14w3.js +2 -0
- package/dist/cli/ask-user-cwstt8fz.js +2 -0
- package/dist/cli/auth-login-9bbp9915.js +2 -0
- package/dist/cli/auth-m8p9grty.js +2 -0
- package/dist/cli/bench-16zqdms5.js +9 -0
- package/dist/cli/catalog-1xchffa4.js +2 -0
- package/dist/cli/cli-1n1zb64f.js +2 -0
- package/dist/cli/client-2t9gjkck.js +2 -0
- package/dist/cli/commands-exafvm2b.js +2 -0
- package/dist/cli/connect-6zde0kn3.js +2 -0
- package/dist/cli/context-cmd-5t43wgqt.js +2 -0
- package/dist/cli/context-report-kt01pw8y.js +2 -0
- package/dist/cli/count-remote-ap7x3vh6.js +2 -0
- package/dist/cli/design-ne5zszyh.js +2 -0
- package/dist/cli/dispatch-2r5myxye.js +2 -0
- package/dist/cli/doctor-ws4fh4tn.js +3 -0
- package/dist/cli/executor-bdrjn634.js +2 -0
- package/dist/cli/export-1mxb9g5p.js +2 -0
- package/dist/cli/files-g104xghh.js +2 -0
- package/dist/cli/gauntlet-07xrjpj7.js +2 -0
- package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
- package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
- package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
- package/dist/cli/hashline-znvrat11.js +2 -0
- package/dist/cli/http-xafw6fsh.js +143 -0
- package/dist/cli/index-1sgjm25y.js +2 -0
- package/dist/cli/init-g2m0tn4m.js +51 -0
- package/dist/cli/install-avaqjjqq.js +2 -0
- package/dist/cli/loop-mmpfft01.js +2 -0
- package/dist/cli/main-0904f6ps.js +5 -0
- package/dist/cli/main-0ab9fc26.js +9 -0
- package/dist/cli/main-0jys2ccn.js +3 -0
- package/dist/cli/main-0mtcdbs7.js +3 -0
- package/dist/cli/main-0z1w2zsg.js +3 -0
- package/dist/cli/main-1dchs7xv.js +18 -0
- package/dist/cli/main-1ereejm1.js +3 -0
- package/dist/cli/main-1k1kw6b5.js +3 -0
- package/dist/cli/main-27y4sm2k.js +38 -0
- package/dist/cli/main-2wwjex5j.js +58 -0
- package/dist/cli/main-2yeveeve.js +6 -0
- package/dist/cli/main-2yfck9b5.js +3 -0
- package/dist/cli/main-2zmzgkwh.js +3 -0
- package/dist/cli/main-351pz3z7.js +7 -0
- package/dist/cli/main-3gjqfh7a.js +6 -0
- package/dist/cli/main-3nf3kgve.js +3 -0
- package/dist/cli/main-3pjrb2hd.js +3 -0
- package/dist/cli/main-3rxcvgna.js +19 -0
- package/dist/cli/main-4b3jgy66.js +19 -0
- package/dist/cli/main-4wndhjdc.js +7 -0
- package/dist/cli/main-4xcmvxnk.js +3 -0
- package/dist/cli/main-5tbz0wbz.js +4 -0
- package/dist/cli/main-5ywnwthm.js +3 -0
- package/dist/cli/main-6b62vkz0.js +14 -0
- package/dist/cli/main-6dnk69vp.js +3 -0
- package/dist/cli/main-6genrmhs.js +136 -0
- package/dist/cli/main-73g7eff4.js +15 -0
- package/dist/cli/main-7c5thhjd.js +5 -0
- package/dist/cli/main-7rn6bqje.js +3 -0
- package/dist/cli/main-80haw7qk.js +4 -0
- package/dist/cli/main-875s60s2.js +4 -0
- package/dist/cli/main-8kjxbpw4.js +8 -0
- package/dist/cli/main-90ds1z4e.js +10 -0
- package/dist/cli/main-9etavkew.js +3 -0
- package/dist/cli/main-a9njrkk1.js +3 -0
- package/dist/cli/main-aecrjq2d.js +12 -0
- package/dist/cli/main-ck9asesq.js +9 -0
- package/dist/cli/main-cta9racd.js +4 -0
- package/dist/cli/main-ddv7j2ag.js +3 -0
- package/dist/cli/main-dfreez27.js +10 -0
- package/dist/cli/main-f7rw7des.js +3 -0
- package/dist/cli/main-ggcn7rd7.js +5 -0
- package/dist/cli/main-gzkmycnv.js +3 -0
- package/dist/cli/main-hq51jg8v.js +18 -0
- package/dist/cli/main-jft389w9.js +8 -0
- package/dist/cli/main-k1eqkg83.js +3 -0
- package/dist/cli/main-k2y8a2aw.js +9 -0
- package/dist/cli/main-kcpbykxz.js +4 -0
- package/dist/cli/main-kd488vje.js +22 -0
- package/dist/cli/main-kh32yvgk.js +5 -0
- package/dist/cli/main-kqxnqjnv.js +25 -0
- package/dist/cli/main-kyn0xnsg.js +3 -0
- package/dist/cli/main-m1kk6fp5.js +21 -0
- package/dist/cli/main-mv40pcr2.js +4 -0
- package/dist/cli/main-n0t3973w.js +3 -0
- package/dist/cli/main-nqveez48.js +4 -0
- package/dist/cli/main-pknhvrmj.js +3 -0
- package/dist/cli/main-pn1w7a7j.js +3 -0
- package/dist/cli/main-prxxs70n.js +4 -0
- package/dist/cli/main-q3vsesf9.js +3 -0
- package/dist/cli/main-qsevpgsv.js +3 -0
- package/dist/cli/main-rdgdw24b.js +25 -0
- package/dist/cli/main-rfth4tbm.js +16 -0
- package/dist/cli/main-rg0wn0xf.js +5 -0
- package/dist/cli/main-sdmxhtv8.js +4 -0
- package/dist/cli/main-skbp13js.js +18 -0
- package/dist/cli/main-t4xnd213.js +7 -0
- package/dist/cli/main-vqak588n.js +4 -0
- package/dist/cli/main-w2n1303f.js +9 -0
- package/dist/cli/main-wbrdspr2.js +5 -0
- package/dist/cli/main-wsrg79c1.js +7 -0
- package/dist/cli/main-x4r0fne4.js +5 -0
- package/dist/cli/main-xea2f3tn.js +6 -0
- package/dist/cli/main-xg704a3c.js +3 -0
- package/dist/cli/main-xvnrabfp.js +16 -0
- package/dist/cli/main-xy53xf0r.js +4 -0
- package/dist/cli/main-y1fqy60y.js +3 -0
- package/dist/cli/main-yn8cd281.js +34 -0
- package/dist/cli/main-yr0ksc0h.js +4 -0
- package/dist/cli/main-z2ex2vyf.js +4 -0
- package/dist/cli/main-z3aayzvq.js +3 -0
- package/dist/cli/main-zaqh35jg.js +3 -0
- package/dist/cli/main-zc2e8e46.js +4 -0
- package/dist/cli/main-zzrfw6cf.js +13 -0
- package/dist/cli/main.js +280 -0
- package/dist/cli/market-cmd-e14kmx9n.js +5 -0
- package/dist/cli/mcp-login-wq7ktdek.js +2 -0
- package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
- package/dist/cli/notify-b7qc0cjb.js +2 -0
- package/dist/cli/oauth-z8whcgfx.js +2 -0
- package/dist/cli/output-b3ewj3ps.js +16 -0
- package/dist/cli/profiles-6mr5he5e.js +2 -0
- package/dist/cli/provider-config-g7j42q8x.js +2 -0
- package/dist/cli/provider-jr1y8vvm.js +2 -0
- package/dist/cli/registry-s8yk86g0.js +2 -0
- package/dist/cli/registry-t6p8d4mn.js +2 -0
- package/dist/cli/repl-bajwe1mh.js +11 -0
- package/dist/cli/resume-rwn9nz7y.js +2 -0
- package/dist/cli/run-flags-nah7ndpt.js +2 -0
- package/dist/cli/runtime-n7gafzhb.js +2 -0
- package/dist/cli/sandbox-config-emdy18x4.js +2 -0
- package/dist/cli/server-b0nvs2bn.js +5 -0
- package/dist/cli/session-arg-y75wd4kj.js +2 -0
- package/dist/cli/session-j62evmjq.js +2 -0
- package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
- package/dist/cli/settings-df10wfez.js +2 -0
- package/dist/cli/setup-jzvv72fg.js +2 -0
- package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
- package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
- package/dist/cli/smoke-p7748apt.js +8 -0
- package/dist/cli/start-chat-s4st3mm0.js +12 -0
- package/dist/cli/stream-gmeyewds.js +2 -0
- package/dist/cli/task-gh0kkp3n.js +2 -0
- package/dist/cli/tasks-z1kfpe8e.js +2 -0
- package/dist/cli/thinking-0eqkrz6t.js +2 -0
- package/dist/cli/todo-5brcrt9m.js +2 -0
- package/dist/cli/tools-7pzm0vj9.js +2 -0
- package/dist/cli/tools-s635p6s8.js +2 -0
- package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
- package/dist/cli/update-check-pt31bm2f.js +2 -0
- package/dist/cli/update-cmd-tk131s9t.js +2 -0
- package/dist/cli/voice-56nabd8d.js +2 -0
- package/dist/cli/webfetch-xd8q596m.js +2 -0
- package/dist/cli/websearch-5hkf98k1.js +2 -0
- package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
- package/dist/cli/workspace-q10g5z3e.js +2 -0
- package/dist/lib/index.js +62 -0
- package/dist/lib/models-index.json +1 -0
- package/dist/lib/plugins.js +55 -0
- package/dist/lib/providers.js +17 -0
- package/dist/lib/public-api.js +20 -0
- package/dist/lib/sdk.js +360 -0
- package/package.json +84 -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
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/** Sextant renderer-local commands (port #43): the slash lines and palette actions the renderer
|
|
2
|
-
* answers itself — /help /theme /open /diff /focus /agents — the /undo /permissions /mode notes
|
|
3
|
-
* (ALIAS_NOTE) and the state-then-hook effect helpers keys.ts shares with them. Ported from the
|
|
4
|
-
* user's own sextant v0.4.0 prototype app.js:1024-1029 (runSlash) and :944 (openFile); split out
|
|
5
|
-
* of keys.ts for the line budget — also the home for #44's further renderer-local commands.
|
|
6
|
-
* Pure over (state, ctx): no clock, no timers, no process access. A state field is written BEFORE
|
|
7
|
-
* the matching KeyCtx.local hook fires, so the renderer only loads content / rebuilds the palette.
|
|
8
|
-
* Everything that is not renderer-local (built-ins, custom commands, `!cmd`, free text, unknown
|
|
9
|
-
* `/x`) reaches ctx.hooks.onSubmit unchanged — app.ts handleSlash owns the rest. */
|
|
10
|
-
|
|
11
|
-
import type { KeyCtx } from "./keys.ts";
|
|
12
|
-
import { parseInput, resolveFile } from "./overlays.ts";
|
|
13
|
-
import { expandMentions } from "./mentions.ts";
|
|
14
|
-
import { THEME_ORDER, type CodeMode, type Focus, type SextantState, type ThemeName } from "./types.ts";
|
|
15
|
-
import { openHelp, openNotices } from "./overlays.ts";
|
|
16
|
-
|
|
17
|
-
/** prototype commands with an rovecode equivalent: a toast instead of a submission — unless
|
|
18
|
-
* setCommands lists a custom command of that name, which then runs like any other.
|
|
19
|
-
* `undo` is GONE from here since port #65: /undo is a real built-in that reaches onSubmit
|
|
20
|
-
* (tui/git-cmds.ts restores the previous checkpoint), no longer an alias note. */
|
|
21
|
-
export const ALIAS_NOTE: Record<string, string> = {
|
|
22
|
-
permissions: "/permissions → use /yolo",
|
|
23
|
-
mode: "/mode → use /plan or /act",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const isTheme = (v: string): v is ThemeName => (THEME_ORDER as readonly string[]).includes(v);
|
|
27
|
-
|
|
28
|
-
// ------------------------------------------------------------------ effects (state first, then the hook)
|
|
29
|
-
|
|
30
|
-
export function setTheme(s: SextantState, ctx: KeyCtx, name: ThemeName): void {
|
|
31
|
-
s.theme = name;
|
|
32
|
-
ctx.local.setTheme(name);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function setMode(s: SextantState, ctx: KeyCtx, mode: CodeMode): void {
|
|
36
|
-
s.code.mode = mode;
|
|
37
|
-
s.code.scroll = 0;
|
|
38
|
-
ctx.local.setMode(mode);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** the crew board in the code panel (⌃a, /agents): lane closed, focus follows */
|
|
42
|
-
export function showAgents(s: SextantState, ctx: KeyCtx): void {
|
|
43
|
-
setMode(s, ctx, "agents");
|
|
44
|
-
s.code.laneOpen = false;
|
|
45
|
-
s.focus = "code";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** app.js:944 — parent dirs expand, the code panel shows the file; focus is the caller's call */
|
|
49
|
-
export function openFile(s: SextantState, ctx: KeyCtx, path: string): void {
|
|
50
|
-
const parts = path.split("/");
|
|
51
|
-
for (let i = 1; i < parts.length; i++) s.files.expanded.add(parts.slice(0, i).join("/"));
|
|
52
|
-
s.code.file = path;
|
|
53
|
-
s.code.mode = "code";
|
|
54
|
-
s.code.scroll = 0;
|
|
55
|
-
ctx.local.openFile(path);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** the files panel only takes focus while the layout shows it (≥ 140 columns) */
|
|
59
|
-
export function setFocus(s: SextantState, ctx: KeyCtx, f: Focus): void {
|
|
60
|
-
// a narrow terminal has no files column: page files into the main slot (draw-tabs.ts) instead of
|
|
61
|
-
// refusing — "the files panel needs ≥ 140 columns" was the old answer, and it left files unreachable
|
|
62
|
-
if (f === "files" && !ctx.layout.files) s.page = "files";
|
|
63
|
-
s.focus = f;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ------------------------------------------------------------------ dispatch
|
|
67
|
-
|
|
68
|
-
/** renderer-local slash commands run here; everything else (built-ins, custom, `!cmd`, free
|
|
69
|
-
* text, unknown `/x`) reaches onSubmit unchanged — app.ts handleSlash owns the rest */
|
|
70
|
-
export function dispatch(s: SextantState, text: string, ctx: KeyCtx): void {
|
|
71
|
-
// the connect wizard IS this surface's /connect — THE connect command (Berkay, 2026-09-10: setup
|
|
72
|
-
// is gone from the table; /setup still opens the wizard as a soft alias for muscle memory). With
|
|
73
|
-
// an argument, /connect keeps its one-line contract: the answers are on the line
|
|
74
|
-
if (text === "/setup" || text === "/connect") { ctx.local.openWizard(); return; }
|
|
75
|
-
const p = parseInput(text);
|
|
76
|
-
// `@file` in free text: the file goes with the message as a `read` result (mentions.ts) — this is the one
|
|
77
|
-
// consumer of parseInput's `mentions`, and what the footer's "@ mentions attach files" has meant since
|
|
78
|
-
if (p.kind === "text" && p.mentions.length > 0) {
|
|
79
|
-
// port #54: an IMAGE mention takes the /attach seam (local.attachImage → the store's stage) instead of
|
|
80
|
-
// dying as "a binary file" — it reaches the model as an image part and spends none of the text budget
|
|
81
|
-
const r = expandMentions(text, { cwd: s.cwd, mentions: p.mentions, resolve: (m) => resolveFile(m, s.files.paths), attachImage: ctx.local.attachImage?.bind(ctx.local) });
|
|
82
|
-
for (const n of r.notes) ctx.local.toast(n);
|
|
83
|
-
ctx.hooks.onSubmit(r.text);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (p.kind !== "slash" || !runLocal(s, p.cmd ?? "", p.arg ?? "", ctx)) ctx.hooks.onSubmit(text);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** true when the line was answered here and must not reach onSubmit */
|
|
90
|
-
export function runLocal(s: SextantState, cmd: string, arg: string, ctx: KeyCtx): boolean {
|
|
91
|
-
switch (cmd) {
|
|
92
|
-
case "help":
|
|
93
|
-
openHelp(s);
|
|
94
|
-
return false; // the card opens AND /help reaches the transcript
|
|
95
|
-
case "notices":
|
|
96
|
-
openNotices(s);
|
|
97
|
-
return true;
|
|
98
|
-
case "market":
|
|
99
|
-
// the overlay opens empty with a "loading" status; the renderer fills it when the catalog answers
|
|
100
|
-
ctx.local.openMarket();
|
|
101
|
-
return true;
|
|
102
|
-
case "context":
|
|
103
|
-
// counting is synchronous but not free on a long transcript, so the renderer does it off the frame
|
|
104
|
-
ctx.local.openContext();
|
|
105
|
-
case "wizard":
|
|
106
|
-
// /connect and /setup on the sextant surface: the columned connect wizard (draw-wizard.ts),
|
|
107
|
-
// reached here so the classic surface keeps its own pickOne chain untouched
|
|
108
|
-
ctx.local.openWizard();
|
|
109
|
-
return true;
|
|
110
|
-
return true;
|
|
111
|
-
case "theme":
|
|
112
|
-
if (isTheme(arg)) setTheme(s, ctx, arg);
|
|
113
|
-
else ctx.local.toast(`unknown theme "${arg}" · night, ember or contrast`);
|
|
114
|
-
return true;
|
|
115
|
-
case "open": {
|
|
116
|
-
const p = resolveFile(arg, s.files.paths, ctx.fuzzy);
|
|
117
|
-
if (!p) { ctx.local.toast(arg ? `no file matches "${arg}"` : "usage: /open <file>"); return true; }
|
|
118
|
-
openFile(s, ctx, p);
|
|
119
|
-
s.focus = "code";
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
case "diff": {
|
|
123
|
-
if (arg) {
|
|
124
|
-
const p = resolveFile(arg, s.files.paths, ctx.fuzzy);
|
|
125
|
-
if (!p) { ctx.local.toast(`no file matches "${arg}"`); return true; }
|
|
126
|
-
s.code.file = p;
|
|
127
|
-
}
|
|
128
|
-
setMode(s, ctx, "diff");
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
case "focus":
|
|
132
|
-
if (arg === "messages" || arg === "code" || arg === "files") setFocus(s, ctx, arg);
|
|
133
|
-
else ctx.local.toast("focus is messages, code or files");
|
|
134
|
-
return true;
|
|
135
|
-
case "agents":
|
|
136
|
-
if (arg) return false; // port #62: `/agents list` (any argument) reaches app.ts handleSlash — the definitions list; the BARE /agents is this board
|
|
137
|
-
showAgents(s, ctx);
|
|
138
|
-
return true;
|
|
139
|
-
default: {
|
|
140
|
-
const note = ALIAS_NOTE[cmd];
|
|
141
|
-
// a custom command of that name (listed by setCommands) wins over the note
|
|
142
|
-
if (note && !s.commands.some((c) => c.name === cmd)) { ctx.local.toast(note); return true; }
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** palette actions (overlays.ts paletteItems): a slash line, or theme: / mode: / focus: / open: */
|
|
149
|
-
export function runAction(s: SextantState, action: string, ctx: KeyCtx): void {
|
|
150
|
-
if (action.startsWith("/")) {
|
|
151
|
-
const p = parseInput(action), needsArg = ["theme", "open", "focus"].includes(p.cmd ?? "");
|
|
152
|
-
if (needsArg && !p.arg) { // the argument is still missing: park the line in the prompt, options open
|
|
153
|
-
s.input.text = action + " ";
|
|
154
|
-
s.input.cur = s.input.text.length;
|
|
155
|
-
s.input.sgSel = 0;
|
|
156
|
-
s.focus = "messages";
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
dispatch(s, action, ctx);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const i = action.indexOf(":"), kind = action.slice(0, i), rest = action.slice(i + 1);
|
|
163
|
-
if (kind === "theme" && isTheme(rest)) setTheme(s, ctx, rest);
|
|
164
|
-
else if (kind === "mode") {
|
|
165
|
-
setMode(s, ctx, rest as CodeMode);
|
|
166
|
-
if (rest === "agents") s.code.laneOpen = false;
|
|
167
|
-
} else if (kind === "focus") setFocus(s, ctx, rest as Focus);
|
|
168
|
-
else if (kind === "open") { openFile(s, ctx, rest); s.focus = "code"; }
|
|
169
|
-
}
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/** The seam between the market overlay and the market module.
|
|
2
|
-
*
|
|
3
|
-
* draw-market.ts is pure over (state, screen): it takes rows and a status and never fetches. This file is
|
|
4
|
-
* the only place that knows src/market/ exists — it lazily imports the module (the boot rule: nothing in
|
|
5
|
-
* the market's dependency tree is loaded until someone opens /market), maps src/market/types.ts onto the
|
|
6
|
-
* overlay's flattened view rows, and turns every failure into a status the overlay can draw.
|
|
7
|
-
*
|
|
8
|
-
* Nothing here throws for a data problem, because the module it wraps does not either: a source that fails
|
|
9
|
-
* comes back as `SourceStatus {ok:false, reason}` and is shown as the error state, a source answered from
|
|
10
|
-
* the cache is shown as the offline state with its age, and an install that fails is a `{ok:false, error}`
|
|
11
|
-
* outcome printed on the plan card. The one thing this file DOES guard is the module not being there yet:
|
|
12
|
-
* while src/market/ is being written (types.ts landed first), an absent registry.ts must read as "the
|
|
13
|
-
* market module is not wired yet", not as a crash in the middle of a frame. */
|
|
14
|
-
|
|
15
|
-
import type { MarketDocLine, MarketPlan, MarketStatus, MarketViewRow } from "./draw-market.ts";
|
|
16
|
-
import { npxPackage } from "../mcp/local-package.ts";
|
|
17
|
-
import type { MarketInstall } from "../mcp/market.ts";
|
|
18
|
-
|
|
19
|
-
/** what the overlay needs to open: the rows, why they are what they are, and anything worth saying once */
|
|
20
|
-
export interface MarketLoad {
|
|
21
|
-
rows: MarketViewRow[];
|
|
22
|
-
status: MarketStatus;
|
|
23
|
-
notes: string[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** src/market/types.ts, structurally — declared here so this file compiles before the module lands and so
|
|
27
|
-
* the overlay never imports the market's types directly */
|
|
28
|
-
interface Env { name: string; description?: string; required: boolean; secret: boolean; default?: string }
|
|
29
|
-
interface Item {
|
|
30
|
-
id: string; kind: "mcp" | "skill" | "plugin"; title: string; publisher: string; description: string;
|
|
31
|
-
version?: string; repository?: string; homepage?: string; tags: string[]; status?: string;
|
|
32
|
-
env: Env[]; install: unknown; planNote?: string[];
|
|
33
|
-
/** the item's own documentation, as the catalog carries it (nimbus-24's writer): third-party markdown */
|
|
34
|
-
docs?: { source: string; format: string; bytes: number; truncated: boolean; body: string };
|
|
35
|
-
}
|
|
36
|
-
interface Row extends Item {
|
|
37
|
-
installed?: { path: string; scope: "user" | "project"; version?: string; updateAvailable?: boolean; trusted?: boolean };
|
|
38
|
-
}
|
|
39
|
-
type Status =
|
|
40
|
-
| { ok: true; from: "live" }
|
|
41
|
-
| { ok: true; from: "cache"; ageMs: number }
|
|
42
|
-
| { ok: true; from: "curated" }
|
|
43
|
-
/** the source was not consulted at all (an empty query never asks the registry, --offline skips it) */
|
|
44
|
-
| { ok: true; from: "skipped"; why: string }
|
|
45
|
-
| { ok: false; reason: string };
|
|
46
|
-
interface Result { items: Item[]; sources: Record<string, Status>; notes: string[] }
|
|
47
|
-
interface PlanView {
|
|
48
|
-
item: Item; target: string; scope: "user" | "project"; preview: string[];
|
|
49
|
-
asks: Env[]; pending: string[]; replaces?: string;
|
|
50
|
-
}
|
|
51
|
-
type Outcome =
|
|
52
|
-
| { ok: true; item: Item; target: string; scope: "user" | "project"; envNames: string[]; trusted?: boolean; next?: string;
|
|
53
|
-
/** install-once: what npm put on disk (src/market/types.ts InstallOutcome.package) */
|
|
54
|
-
package?: { name: string; version: string; prefix: string; integrity?: string; missing?: string[] } }
|
|
55
|
-
| { ok: false; error: string };
|
|
56
|
-
/** the plan/install options the overlay passes through; `local` is the chooser's answer (mcp/local-package.ts) */
|
|
57
|
-
interface Ctx { scope: "user" | "project"; cwd: string; home: string; local?: boolean }
|
|
58
|
-
|
|
59
|
-
/** the module's public surface, as much of it as the overlay uses (src/market/registry.ts + install.ts;
|
|
60
|
-
* there is no barrel file, so the two are imported separately) */
|
|
61
|
-
interface RegistryModule {
|
|
62
|
-
searchMarket(query: string, deps?: { offline?: boolean; withDocs?: boolean }): Promise<Result>;
|
|
63
|
-
/** one item WITH its documentation body: search and list deliberately leave the bodies out (they are
|
|
64
|
-
* hundreds of kilobytes the list never reads), so the docs pane asks for the row it is about to show */
|
|
65
|
-
findItem(kind: "mcp" | "skill" | "plugin", id: string, deps?: { offline?: boolean }): Promise<{ item?: Item; notes: string[] }>;
|
|
66
|
-
}
|
|
67
|
-
interface InstallModule {
|
|
68
|
-
planInstall(item: Item, opts: Ctx): PlanView | { error: string };
|
|
69
|
-
runInstall(plan: PlanView, answers: Record<string, string>, opts: Ctx, deps?: unknown): Promise<Outcome>;
|
|
70
|
-
withInstalled(items: readonly Item[], cwd: string, home: string): Row[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** "the market module is not wired yet" rather than an exception mid-frame */
|
|
74
|
-
const NOT_WIRED = "the market module is not available in this build (src/market/ is still landing)";
|
|
75
|
-
|
|
76
|
-
async function load(): Promise<{ registry: RegistryModule; install: InstallModule } | null> {
|
|
77
|
-
try {
|
|
78
|
-
// two lazy imports, resolved at call time so a missing file is a null, not a boot failure
|
|
79
|
-
const [registry, install] = await Promise.all([
|
|
80
|
-
import("../market/registry.ts") as Promise<Partial<RegistryModule>>,
|
|
81
|
-
import("../market/install.ts") as Promise<Partial<InstallModule>>,
|
|
82
|
-
]);
|
|
83
|
-
if (typeof registry.searchMarket !== "function" || typeof install.planInstall !== "function") return null;
|
|
84
|
-
return { registry: registry as RegistryModule, install: install as InstallModule };
|
|
85
|
-
} catch {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** what an item runs once installed, in one line — the install spec's own words, per arm */
|
|
91
|
-
function runsLine(item: Item): string {
|
|
92
|
-
const spec = item.install as { kind?: string; entry?: { installs?: { kind?: string; url?: string; command?: string; args?: string[]; runtime?: string }[] }; source?: unknown; git?: boolean; files?: { path: string }[] } | undefined;
|
|
93
|
-
if (!spec) return "";
|
|
94
|
-
if (spec.kind === "mcp") {
|
|
95
|
-
const first = spec.entry?.installs?.[0];
|
|
96
|
-
if (!first) return "";
|
|
97
|
-
return first.kind === "http" ? `remote ${first.url ?? ""}` : [first.command, ...(first.args ?? [])].join(" ");
|
|
98
|
-
}
|
|
99
|
-
if (spec.kind === "plugin") return `${spec.git ? "clone" : "copy"} ${String(spec.source ?? "")}`;
|
|
100
|
-
if (spec.kind === "skill") {
|
|
101
|
-
if (spec.files?.length) return `${spec.files.length} file(s), written verbatim — runs nothing`;
|
|
102
|
-
const src = spec.source as { git?: string; subfolder?: string } | undefined;
|
|
103
|
-
return src?.git ? `clone ${src.git}${src.subfolder ? ` (${src.subfolder})` : ""}` : "a SKILL.md the model reads when it matches";
|
|
104
|
-
}
|
|
105
|
-
return "";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** the other ways in an MCP entry offers (a remote endpoint beside a local runtime) */
|
|
109
|
-
function alternatives(item: Item): string[] {
|
|
110
|
-
const spec = item.install as { kind?: string; entry?: { installs?: { kind?: string; url?: string; command?: string; args?: string[]; runtime?: string }[] } } | undefined;
|
|
111
|
-
if (spec?.kind !== "mcp") return [];
|
|
112
|
-
return (spec.entry?.installs ?? []).slice(1).map((i) => (i.kind === "http" ? `remote ${i.url ?? ""}` : `${i.runtime ?? "stdio"}: ${[i.command, ...(i.args ?? [])].join(" ")}`));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** the npm package an mcp row's first form would run through npx — the install-once offer's subject */
|
|
116
|
-
function localOfferOf(item: Item): string | undefined {
|
|
117
|
-
const spec = item.install as { kind?: string; entry?: { installs?: unknown[] } } | undefined;
|
|
118
|
-
if (spec?.kind !== "mcp") return undefined;
|
|
119
|
-
const first = spec.entry?.installs?.[0];
|
|
120
|
-
if (first === undefined || typeof first !== "object" || first === null) return undefined;
|
|
121
|
-
return npxPackage(first as MarketInstall)?.spec;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function toViewRow(row: Row): MarketViewRow {
|
|
125
|
-
const localOffer = localOfferOf(row);
|
|
126
|
-
return {
|
|
127
|
-
...(localOffer !== undefined ? { localOffer } : {}),
|
|
128
|
-
id: row.id,
|
|
129
|
-
kind: row.kind,
|
|
130
|
-
title: row.title,
|
|
131
|
-
publisher: row.publisher,
|
|
132
|
-
description: row.description,
|
|
133
|
-
...(row.version !== undefined ? { version: row.version } : {}),
|
|
134
|
-
runs: runsLine(row),
|
|
135
|
-
env: row.env.map((v) => ({ name: v.name, required: v.required, secret: v.secret, ...(v.description !== undefined ? { description: v.description } : {}) })),
|
|
136
|
-
alternatives: alternatives(row),
|
|
137
|
-
pending: row.planNote ?? [],
|
|
138
|
-
...(row.installed ? { installed: row.installed } : {}),
|
|
139
|
-
// search carries the metadata, not the body: the pane fills `lines` from docsFor() when it opens
|
|
140
|
-
...(row.docs ? { docs: { source: row.docs.source, truncated: row.docs.truncated === true, lines: typeof row.docs.body === "string" && row.docs.body !== "" ? docLines(row.docs.body) : [] } } : {}),
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** ESC and the rest of C0 (tab and newline excepted), DEL, and the C1 range some terminals still read
|
|
145
|
-
* as CSI — a document must not be able to move the cursor, change a colour or clear the screen */
|
|
146
|
-
// eslint-disable-next-line no-control-regex
|
|
147
|
-
const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
|
|
148
|
-
|
|
149
|
-
/** A document, flattened to lines a cockpit can draw and stripped of everything a terminal would obey.
|
|
150
|
-
* The body is third-party text, so escape sequences and control characters go before anything else; then
|
|
151
|
-
* markdown is flattened rather than rendered — headings and fenced code keep their shape, and the rest is
|
|
152
|
-
* prose wrapped to a column. A README must not be able to move the cursor or repaint the screen. */
|
|
153
|
-
export function docLines(body: string, width = 96): MarketDocLine[] {
|
|
154
|
-
const safe = body
|
|
155
|
-
.replace(/\r\n?/g, "\n")
|
|
156
|
-
.replace(/<(script|style|iframe|object|embed|template|noscript)\b[\s\S]*?<\/\1\s*>/gi, "")
|
|
157
|
-
.replace(/<\/?[a-zA-Z][^>]*>/g, "")
|
|
158
|
-
.replace(CONTROL, "")
|
|
159
|
-
.replace(/\t/g, " ")
|
|
160
|
-
.replace(/^---\n[\s\S]*?\n---\n/, "");
|
|
161
|
-
const out: MarketDocLine[] = [];
|
|
162
|
-
let fence = false;
|
|
163
|
-
for (const raw of safe.split("\n")) {
|
|
164
|
-
if (/^\s*```/.test(raw)) { fence = !fence; out.push({ kind: "rule", text: "" }); continue; }
|
|
165
|
-
if (fence) { out.push({ kind: "code", text: raw.slice(0, width) }); continue; }
|
|
166
|
-
const h = /^(#{1,6})\s+(.*)$/.exec(raw);
|
|
167
|
-
if (h) { out.push({ kind: "head", text: h[2]!.replace(/[`*]/g, "").slice(0, width) }); continue; }
|
|
168
|
-
const text = raw.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/[`*]/g, "");
|
|
169
|
-
if (text.trim() === "") { out.push({ kind: "blank", text: "" }); continue; }
|
|
170
|
-
let line = "";
|
|
171
|
-
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
172
|
-
const piece = word.length > width ? word.slice(0, width) : word;
|
|
173
|
-
if (!line) { line = piece; continue; }
|
|
174
|
-
if (line.length + 1 + piece.length <= width) line += ` ${piece}`;
|
|
175
|
-
else { out.push({ kind: "text", text: line }); line = piece; }
|
|
176
|
-
}
|
|
177
|
-
if (line) out.push({ kind: "text", text: line });
|
|
178
|
-
}
|
|
179
|
-
return out;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** the three drawn states, decided from the sources the module consulted — never from an empty list */
|
|
183
|
-
export function statusFrom(sources: Record<string, Status>): MarketStatus {
|
|
184
|
-
const failed = Object.entries(sources).filter(([, v]) => !v.ok) as [string, { ok: false; reason: string }][];
|
|
185
|
-
if (failed.length) {
|
|
186
|
-
const [name, s] = failed[0]!;
|
|
187
|
-
return { kind: "error", reason: failed.length === 1 ? `${name}: ${s.reason}` : `${name}: ${s.reason} (+${failed.length - 1} more)` };
|
|
188
|
-
}
|
|
189
|
-
const cached = Object.values(sources).find((v) => v.ok && v.from === "cache") as { ok: true; from: "cache"; ageMs: number } | undefined;
|
|
190
|
-
if (cached) {
|
|
191
|
-
const mins = Math.round(cached.ageMs / 60000);
|
|
192
|
-
return { kind: "offline", note: `showing cached results${mins > 0 ? `, ${mins}m old` : ""}` };
|
|
193
|
-
}
|
|
194
|
-
// "skipped" is not a failure and not staleness: the source was left out on purpose, and `why` says so.
|
|
195
|
-
// It is worth one line because a reader who does not see a registry row deserves to know it was not asked.
|
|
196
|
-
const skipped = Object.values(sources).find((v) => v.ok && v.from === "skipped") as { ok: true; from: "skipped"; why: string } | undefined;
|
|
197
|
-
if (skipped) return { kind: "offline", note: skipped.why };
|
|
198
|
-
return { kind: "ready" };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Everything the overlay opens with. `offline` forces the no-network path (the curated shelf and the
|
|
202
|
-
* repository catalogs), which is also what a failed network falls back to. */
|
|
203
|
-
export async function loadMarket(cwd: string, home: string, opts: { offline?: boolean } = {}): Promise<MarketLoad> {
|
|
204
|
-
const mod = await load();
|
|
205
|
-
if (!mod) return { rows: [], status: { kind: "error", reason: NOT_WIRED }, notes: [] };
|
|
206
|
-
const result = await mod.registry.searchMarket("", opts.offline === true ? { offline: true } : {});
|
|
207
|
-
// installed state is disk truth, joined onto the catalog by withInstalled (never read from a catalog)
|
|
208
|
-
let rows: Row[] = result.items as Row[];
|
|
209
|
-
try { rows = mod.install.withInstalled(result.items, cwd, home); } catch { /* disk unreadable: the rows still list */ }
|
|
210
|
-
return { rows: rows.map(toViewRow), status: statusFrom(result.sources), notes: result.notes };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/** The documentation for one row, fetched when the reader asks for it.
|
|
214
|
-
*
|
|
215
|
-
* `searchMarket` carries docs METADATA but not the bodies — they are ~400 KB nobody reads while browsing —
|
|
216
|
-
* so the list cannot fill this in advance. `findItem` reads the one row with its body, and the body is
|
|
217
|
-
* flattened to terminal-safe lines here, once, at the moment the pane opens. */
|
|
218
|
-
export async function docsFor(row: MarketViewRow): Promise<{ source: string; truncated: boolean; lines: MarketDocLine[] } | null> {
|
|
219
|
-
const mod = await load();
|
|
220
|
-
if (!mod || typeof mod.registry.findItem !== "function") return null;
|
|
221
|
-
const { item } = await mod.registry.findItem(row.kind, row.id, { offline: true });
|
|
222
|
-
const docs = item?.docs;
|
|
223
|
-
if (!docs || typeof docs.body !== "string" || docs.body.trim() === "") return null;
|
|
224
|
-
return { source: docs.source, truncated: docs.truncated === true, lines: docLines(docs.body) };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** the plan for one row, or the reason there is none. Writes nothing. `local` is the chooser's answer for an npx
|
|
228
|
-
* row: true draws the install-once plan (`node <bin>`, the `installs`/`records` rows), false or absent the npx
|
|
229
|
-
* line as today — and the plan remembers it, so the install runs the plan that was approved. */
|
|
230
|
-
export async function planFor(row: MarketViewRow, ctx: Ctx, local?: boolean): Promise<MarketPlan | { error: string }> {
|
|
231
|
-
const mod = await load();
|
|
232
|
-
if (!mod) return { error: NOT_WIRED };
|
|
233
|
-
const result = await mod.registry.searchMarket(row.id, { offline: true });
|
|
234
|
-
const item = result.items.find((i) => i.kind === row.kind && i.id === row.id);
|
|
235
|
-
if (!item) return { error: `${row.kind}:${row.id} is not in the catalog any more` };
|
|
236
|
-
const plan = mod.install.planInstall(item, { ...ctx, ...(local === true ? { local: true } : {}) });
|
|
237
|
-
if ("error" in plan) return { error: plan.error };
|
|
238
|
-
return {
|
|
239
|
-
row,
|
|
240
|
-
title: `${plan.item.kind}:${plan.item.id} — ${plan.item.title}`,
|
|
241
|
-
target: plan.target,
|
|
242
|
-
scope: plan.scope,
|
|
243
|
-
preview: plan.preview,
|
|
244
|
-
asks: plan.asks.map((a) => ({ name: a.name, required: a.required, secret: a.secret })),
|
|
245
|
-
pending: plan.pending,
|
|
246
|
-
...(plan.replaces !== undefined ? { replaces: plan.replaces } : {}),
|
|
247
|
-
...(local === true ? { local: true } : {}),
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Run a plan the human has just confirmed. The overlay only ever sees the outcome sentence. `opts.local` must be
|
|
252
|
-
* the approved plan's own `local` — npm runs here, and only here, after the card said yes. `opts.deps` is the
|
|
253
|
-
* installer's seam (a fake `npm` in tests). */
|
|
254
|
-
export async function install(row: MarketViewRow, ctx: Ctx, opts: { local?: boolean; deps?: unknown } = {}): Promise<{ ok: boolean; text: string }> {
|
|
255
|
-
const mod = await load();
|
|
256
|
-
if (!mod) return { ok: false, text: NOT_WIRED };
|
|
257
|
-
const result = await mod.registry.searchMarket(row.id, { offline: true });
|
|
258
|
-
const item = result.items.find((i) => i.kind === row.kind && i.id === row.id);
|
|
259
|
-
if (!item) return { ok: false, text: `${row.kind}:${row.id} is not in the catalog any more` };
|
|
260
|
-
const withLocal: Ctx = { ...ctx, ...(opts.local === true ? { local: true } : {}) };
|
|
261
|
-
const plan = mod.install.planInstall(item, withLocal);
|
|
262
|
-
if ("error" in plan) return { ok: false, text: plan.error };
|
|
263
|
-
// A SECRET cannot be finished inside the overlay: it has to be typed on a shell, masked, not into a
|
|
264
|
-
// query line that echoes. The overlay says so and hands the exact command over.
|
|
265
|
-
//
|
|
266
|
-
// `pending` is not that, and treating it as if it were made six of the sixteen curated MCP servers
|
|
267
|
-
// uninstallable from the cockpit — including `filesystem`, which is the first one anybody tries.
|
|
268
|
-
// Nothing is asked for a pending value: it is a placeholder the installer writes into the config
|
|
269
|
-
// (`<directory the server may touch>`) for the human to replace afterwards, and the CLI installs it
|
|
270
|
-
// exactly that way. Refusing here was the overlay inventing a requirement the installer does not have.
|
|
271
|
-
if (plan.asks.length > 0) {
|
|
272
|
-
const what = plan.asks.map((a) => a.name).join(", ");
|
|
273
|
-
return { ok: false, text: `${what} must be typed where it can be masked — run: rovecode market install ${item.kind}:${item.id}` };
|
|
274
|
-
}
|
|
275
|
-
const outcome = await mod.install.runInstall(plan, {}, withLocal, opts.deps);
|
|
276
|
-
if (!outcome.ok) return { ok: false, text: outcome.error };
|
|
277
|
-
const bits = [`installed into ${outcome.target}`];
|
|
278
|
-
if (outcome.package) {
|
|
279
|
-
bits.push(`${outcome.package.name} ${outcome.package.version} installed once → ${outcome.package.prefix}${outcome.package.integrity !== undefined ? " (integrity recorded)" : ""}`);
|
|
280
|
-
if (outcome.package.missing?.length) bits.push(`record incomplete: ${outcome.package.missing.join("; ")}`);
|
|
281
|
-
}
|
|
282
|
-
if (outcome.envNames.length) bits.push(`export ${outcome.envNames.join(", ")}`);
|
|
283
|
-
// the placeholder is the one thing between this install and a working server, so it leads
|
|
284
|
-
for (const p of plan.pending) bits.push(`fill in ${p}`);
|
|
285
|
-
if (outcome.next) bits.push(outcome.next);
|
|
286
|
-
return { ok: true, text: bits.join(" · ") };
|
|
287
|
-
}
|
package/src/sextant/mentions.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/** `@file` mentions, made true. overlays.ts has parsed `@path` into `mentions[]` (with a fuzzy resolveFile) since
|
|
2
|
-
* the port, and the input footer promised "@ mentions attach files" — but nothing ever read `.mentions`, so the
|
|
3
|
-
* promise did nothing. This module is the consumer: on submit, each mention that resolves to a workspace file is
|
|
4
|
-
* appended to the message exactly as the `read` tool would return it (hashline `path#TAG` header, `N#hash|text`
|
|
5
|
-
* lines), so the model has the contents — and valid edit anchors — without spending a tool round-trip.
|
|
6
|
-
*
|
|
7
|
-
* Why here and not in tui/commands.ts: that file's note ("no `!shell` / `@file` injection … the template reaches
|
|
8
|
-
* the model as plain text") is about CUSTOM COMMAND TEMPLATES — files a repo or a plugin ships, where injecting
|
|
9
|
-
* files or shell output from a template is a way for a project to read the user's disk. That reason stands and
|
|
10
|
-
* is untouched. A `@file` the human types into their own prompt is the human choosing to show a file; the only
|
|
11
|
-
* risk is cost, so the caps below exist and every cap is SAID, in the message and as a toast.
|
|
12
|
-
*
|
|
13
|
-
* What is refused, and said: a mention no workspace file matches; a directory; a file outside the workspace
|
|
14
|
-
* (resolveFile only knows the scanned list, and the absolute path is checked against cwd again); a binary; a
|
|
15
|
-
* file past MAX_BYTES. What is capped, and said: lines per file (MAX_LINES — the footer names the offset that
|
|
16
|
-
* continues), files per message (MAX_FILES), characters per message (MAX_CHARS). Never silent: `@` must not be a
|
|
17
|
-
* way to spend a context window without knowing.
|
|
18
|
-
*
|
|
19
|
-
* Three widenings (2026-09-07), each because the promise was narrower than the words: `@~/notes.md` and an
|
|
20
|
-
* ABSOLUTE mention name a file outright and are attached wherever they point (the person typed the whole path —
|
|
21
|
-
* that IS the choice; a RELATIVE mention still has to land in the workspace); a relative mention that resolves
|
|
22
|
-
* nowhere under cwd is tried against the `--add-dir` roots, without which a file in an added root could not be
|
|
23
|
-
* mentioned at all; and an IMAGE mention takes the /attach seam so it reaches the model as an image part instead
|
|
24
|
-
* of being refused as "a binary file". The token class carries `~`, `:` and `\` for the first of those, which is
|
|
25
|
-
* what lets `@C:\Users\me\notes.md` tokenize on Windows — the cost is that a prose `@foo:bar` is read as a path
|
|
26
|
-
* and reported as not found, which is a note, not a loss. */
|
|
27
|
-
|
|
28
|
-
import { existsSync, openSync, closeSync, readSync, readFileSync, statSync } from "node:fs";
|
|
29
|
-
import { homedir } from "node:os";
|
|
30
|
-
import { isAbsolute, resolve, sep } from "node:path";
|
|
31
|
-
import { sniffImageMime } from "../core/images.ts";
|
|
32
|
-
import { fileTag, lineHash, readAnchored, renderAnchored, type AnchoredFile } from "../coding/hashline.ts";
|
|
33
|
-
|
|
34
|
-
/** `@path` tokens in free text: at the start or after whitespace, so `me@example.com` is not one. The one
|
|
35
|
-
* regex both surfaces and overlays.ts parseInput read — this module imports nothing from the sextant, so the
|
|
36
|
-
* classic renderer can use it without loading the cockpit's module graph. */
|
|
37
|
-
export const MENTION_RE = /(?:^|\s)@([\w./\\:~-]+)/g;
|
|
38
|
-
/** the mentions of a typed line — none for a `/command` or a `!shell` line, which are never expanded */
|
|
39
|
-
export function mentionsIn(text: string): string[] {
|
|
40
|
-
const t = text.trim();
|
|
41
|
-
if (t[0] === "/" || t[0] === "!") return [];
|
|
42
|
-
return [...t.matchAll(MENTION_RE)].map((m) => m[1]!);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** the most lines one mention contributes — the rest is one `read` with an offset away */
|
|
46
|
-
export const MENTION_MAX_LINES = 400;
|
|
47
|
-
/** mentions expanded per message; the rest are named and left for the model to read */
|
|
48
|
-
export const MENTION_MAX_FILES = 8;
|
|
49
|
-
/** characters all expansions together may add to one message (~15k tokens) */
|
|
50
|
-
export const MENTION_MAX_CHARS = 60_000;
|
|
51
|
-
/** a file larger than this is not read at all — name it, let the model ask for a window */
|
|
52
|
-
export const MENTION_MAX_BYTES = 2 * 1024 * 1024;
|
|
53
|
-
/** with less than this left of the character budget, a further file is named rather than squeezed to a
|
|
54
|
-
* line or two — a three-line fragment of a file is worse context than "read it yourself" */
|
|
55
|
-
export const MENTION_MIN_BLOCK = 500;
|
|
56
|
-
|
|
57
|
-
/** the line that opens the attached section — what userRow cuts the transcript at */
|
|
58
|
-
export const MENTION_FRAME = "(files attached by @mention — each block is what `read` returns for the file; its edit anchors are valid)";
|
|
59
|
-
/** one per attached file, right above its read block: `[@src/x.ts — attached: 120 lines]` */
|
|
60
|
-
export const MENTION_HEAD = /^\[@(\S+) — attached: (\d+)(?: of (\d+))? lines(, capped[^\]]*)?\]$/;
|
|
61
|
-
|
|
62
|
-
export interface AttachedFile { path: string; shown: number; total: number; capped: boolean }
|
|
63
|
-
export interface MentionExpansion {
|
|
64
|
-
/** the message as submitted: the typed text, then the attached section (unchanged when nothing attached) */
|
|
65
|
-
text: string;
|
|
66
|
-
attached: AttachedFile[];
|
|
67
|
-
/** what was refused or capped, one sentence each — the renderer toasts them */
|
|
68
|
-
notes: string[];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface ExpandOptions {
|
|
72
|
-
cwd: string;
|
|
73
|
-
/** a mention → the cwd-relative posix path it names, or null. The sextant ranks over its scanned file list
|
|
74
|
-
* (overlays.ts resolveFile: exact, unique basename, fuzzy); the classic renderer, which has no list, takes
|
|
75
|
-
* the exact path only — what its own `@` autocomplete inserts. */
|
|
76
|
-
resolve: (mention: string) => string | null;
|
|
77
|
-
/** already parsed mentions (default: mentionsIn(text)) */
|
|
78
|
-
mentions?: readonly string[];
|
|
79
|
-
/** `--add-dir` roots (core/workspace.ts): a RELATIVE mention that resolves nowhere under cwd is tried against
|
|
80
|
-
* each of these in order, so a file in an added root can be mentioned at all. cwd always wins a tie. */
|
|
81
|
-
roots?: readonly string[];
|
|
82
|
-
/** an image mention takes the /attach seam instead of being refused as binary — it reaches the model as an
|
|
83
|
-
* image part, and costs nothing against the text budget. Without this the old "binary file" refusal stands. */
|
|
84
|
-
attachImage?: (abs: string) => void;
|
|
85
|
-
/** seams for tests */
|
|
86
|
-
stat?: (abs: string) => { isFile(): boolean; size: number };
|
|
87
|
-
read?: (abs: string) => string;
|
|
88
|
-
exists?: (abs: string) => boolean;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Where a mention points, and by what right. `~`/`~/x` and an ABSOLUTE mention are the person naming a file
|
|
92
|
-
* outright — that is the choice, so they are allowed wherever they point; a RELATIVE mention must land inside the
|
|
93
|
-
* cwd or one of the `--add-dir` roots, which is the containment rule the workspace already enforces everywhere
|
|
94
|
-
* else. `label` is what the notes and the attached header show. */
|
|
95
|
-
export function locateMention(m: string, opts: ExpandOptions): { abs: string; label: string; root?: string } | null {
|
|
96
|
-
const exists = opts.exists ?? existsSync;
|
|
97
|
-
if (m === "~" || m.startsWith("~/") || m.startsWith("~\\")) { const abs = resolve(homedir(), m.slice(2)); return { abs, label: m }; }
|
|
98
|
-
if (isAbsolute(m)) return { abs: resolve(m), label: m };
|
|
99
|
-
const rel = opts.resolve(m);
|
|
100
|
-
if (rel !== null) return { abs: resolve(opts.cwd, rel), label: rel };
|
|
101
|
-
for (const root of opts.roots ?? []) {
|
|
102
|
-
const abs = resolve(root, m);
|
|
103
|
-
if ((abs === resolve(root) || abs.startsWith(resolve(root) + sep)) && exists(abs)) return { abs, label: m, root };
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const BINARY_PROBE = 8 * 1024;
|
|
109
|
-
|
|
110
|
-
/** the file's image MIME by magic bytes (core/images.ts), reading only the first bytes — an image mention must not
|
|
111
|
-
* pull a 4 MB photo into memory just to learn it is a photo. null when it is not an image we accept. */
|
|
112
|
-
function sniffImage(abs: string, opts: ExpandOptions): string | null {
|
|
113
|
-
try {
|
|
114
|
-
if (opts.read) { const head = Buffer.from(opts.read(abs).slice(0, 32), "binary"); return sniffImageMime(head) ?? null; }
|
|
115
|
-
const fd = openSync(abs, "r");
|
|
116
|
-
try { const buf = Buffer.alloc(32); const n = readSync(fd, buf, 0, 32, 0); return sniffImageMime(buf.subarray(0, n)) ?? null; }
|
|
117
|
-
finally { closeSync(fd); }
|
|
118
|
-
} catch { return null; }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Expand every `@mention` in a typed line. Pure apart from the reads; never throws — a file that cannot be
|
|
122
|
-
* read becomes a note and the message goes out without it. */
|
|
123
|
-
export function expandMentions(text: string, opts: ExpandOptions): MentionExpansion {
|
|
124
|
-
const mentions = [...new Set(opts.mentions ?? mentionsIn(text))];
|
|
125
|
-
const notes: string[] = [];
|
|
126
|
-
const attached: AttachedFile[] = [];
|
|
127
|
-
const blocks: string[] = [];
|
|
128
|
-
if (mentions.length === 0) return { text, attached, notes };
|
|
129
|
-
const root = resolve(opts.cwd);
|
|
130
|
-
const stat = opts.stat ?? ((p: string) => statSync(p));
|
|
131
|
-
let budget = MENTION_MAX_CHARS;
|
|
132
|
-
for (const m of mentions) {
|
|
133
|
-
if (attached.length >= MENTION_MAX_FILES) { notes.push(`@${m}: not attached — ${MENTION_MAX_FILES} files per message is the cap; ask me to read it`); continue; }
|
|
134
|
-
const found = locateMention(m, opts);
|
|
135
|
-
if (found === null) { notes.push(`@${m}: no file in the workspace matches`); continue; }
|
|
136
|
-
const { abs } = found;
|
|
137
|
-
const rel = found.label;
|
|
138
|
-
// a relative mention resolved through the workspace list stays inside it; `~`/absolute and the added roots
|
|
139
|
-
// carry their own right (locateMention), so only the cwd-relative case is re-checked here
|
|
140
|
-
if (found.root === undefined && !isAbsolute(m) && !m.startsWith("~") && abs !== root && !abs.startsWith(root + sep)) { notes.push(`@${rel}: outside the workspace — not attached`); continue; }
|
|
141
|
-
let st: { isFile(): boolean; size: number };
|
|
142
|
-
try { st = stat(abs); } catch { notes.push(`@${rel}: cannot be read — not attached`); continue; }
|
|
143
|
-
if (!st.isFile()) { notes.push(`@${rel}: a directory — name a file in it`); continue; }
|
|
144
|
-
// an IMAGE takes the /attach seam BEFORE the size and budget checks: it reaches the model as an image part
|
|
145
|
-
// and spends none of the text budget, so refusing it as "binary" was the wrong answer to the right question
|
|
146
|
-
if (opts.attachImage) {
|
|
147
|
-
const mime = sniffImage(abs, opts);
|
|
148
|
-
if (mime !== null) {
|
|
149
|
-
opts.attachImage(abs);
|
|
150
|
-
notes.push(`@${rel}: attached as an image (${mime})${found.root ? ` (from ${found.root})` : ""}`);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (st.size > MENTION_MAX_BYTES) { notes.push(`@${rel}: ${(st.size / 1048576).toFixed(1)} MB is too large to attach — ask me to read a window of it`); continue; }
|
|
155
|
-
let content: string;
|
|
156
|
-
try { content = opts.read ? opts.read(abs) : readFileSync(abs, "utf8"); }
|
|
157
|
-
catch { notes.push(`@${rel}: cannot be read — not attached`); continue; }
|
|
158
|
-
if (content.slice(0, BINARY_PROBE).includes("\0")) { notes.push(`@${rel}: a binary file — not attached`); continue; }
|
|
159
|
-
if (budget < MENTION_MIN_BLOCK) { notes.push(`@${rel}: not attached — this message already carries ${MENTION_MAX_CHARS.toLocaleString()} characters of files; ask me to read it`); continue; }
|
|
160
|
-
const file = opts.read ? anchoredFrom(abs, content) : readAnchored(abs);
|
|
161
|
-
const total = file.lines.length;
|
|
162
|
-
// the line cap first, then the character budget: whichever is hit, the footer says where to continue
|
|
163
|
-
let shown = Math.min(total, MENTION_MAX_LINES);
|
|
164
|
-
let body = renderAnchored({ ...file, lines: file.lines.slice(0, shown) }).replace(/\n$/, "");
|
|
165
|
-
while (body.length > budget && shown > 1) {
|
|
166
|
-
shown = Math.max(1, Math.floor(shown * budget / body.length));
|
|
167
|
-
body = renderAnchored({ ...file, lines: file.lines.slice(0, shown) }).replace(/\n$/, "");
|
|
168
|
-
}
|
|
169
|
-
const capped = shown < total;
|
|
170
|
-
budget -= body.length;
|
|
171
|
-
const head = capped
|
|
172
|
-
? `[@${rel} — attached: ${shown} of ${total} lines, capped: read it with offset ${shown + 1} for the rest]`
|
|
173
|
-
: `[@${rel} — attached: ${total} lines]`;
|
|
174
|
-
blocks.push(`${head}\n${body}\n(showing lines ${total === 0 ? 0 : 1}-${shown} of ${total})`);
|
|
175
|
-
attached.push({ path: rel, shown, total, capped });
|
|
176
|
-
if (capped) notes.push(`@${rel}: ${total} lines — attached the first ${shown}; the rest is a read away`);
|
|
177
|
-
}
|
|
178
|
-
if (blocks.length === 0) return { text, attached, notes };
|
|
179
|
-
return { text: `${text.trimEnd()}\n\n${MENTION_FRAME}\n\n${blocks.join("\n\n")}`, attached, notes };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** an AnchoredFile from content already in hand (the `read` seam), split and hashed exactly as readAnchored does —
|
|
183
|
-
* including the empty last line a trailing newline yields, so the counts match what the read tool reports */
|
|
184
|
-
function anchoredFrom(abs: string, content: string): AnchoredFile {
|
|
185
|
-
return { path: abs, tag: fileTag(content), lines: content.split("\n").map((t, i) => ({ n: i + 1, hash: lineHash(t), text: t })) };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** The transcript's view of a submitted line: the typed text, and one chip per attached file — never the file
|
|
189
|
-
* bodies, which would flood the messages panel (they are in the session, where the model reads them). */
|
|
190
|
-
export function splitAttached(text: string): { text: string; files: string[] } {
|
|
191
|
-
const lines = text.split("\n");
|
|
192
|
-
const at = lines.indexOf(MENTION_FRAME);
|
|
193
|
-
if (at < 0) return { text, files: [] };
|
|
194
|
-
const files: string[] = [];
|
|
195
|
-
for (const l of lines.slice(at + 1)) {
|
|
196
|
-
const m = MENTION_HEAD.exec(l);
|
|
197
|
-
if (m) files.push(m[3] !== undefined ? `${m[1]} · ${m[2]}/${m[3]} lines, capped` : `${m[1]} · ${m[2]} lines`);
|
|
198
|
-
}
|
|
199
|
-
return { text: lines.slice(0, at).join("\n").trimEnd(), files };
|
|
200
|
-
}
|