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/coding/bash.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/** The bash tool (ports #10, #21, #55). Runs a shell command through the Executor seam — the probed rung
|
|
2
|
-
* (direct / wsl / docker) shapes the argv, and the abort path is the rung's tree kill (Windows Job Object
|
|
3
|
-
* + taskkill sweep; SIGTERM to the process group on POSIX).
|
|
4
|
-
*
|
|
5
|
-
* It lived at the end of coding/hashline.ts until #55 gave it a timeout and a background flag. That file
|
|
6
|
-
* is the anchored-edit implementation; shell execution had simply drifted into its last fifty lines.
|
|
7
|
-
* hashline.ts re-exports `bashTool`, so the ten files importing it from there are untouched.
|
|
8
|
-
*
|
|
9
|
-
* ONE TOOL, THREE SHAPES, and the reason it is one tool rather than three: policy runs UPSTREAM of
|
|
10
|
-
* execute() — the permission rules, execpolicy, the hooks and the human approval all decide before this
|
|
11
|
-
* function is entered. A separate `bash_background` tool would be a second door onto the same room, and
|
|
12
|
-
* every rule written for `bash` would have to be written again for it.
|
|
13
|
-
* plain await it, one automatic retry on a non-zero exit
|
|
14
|
-
* timeout_ms the same, with a deadline; a timed-out run KEEPS its partial output and says so
|
|
15
|
-
* run_in_background hand it to the job manager and return the id at once (tools/bash-jobs.ts) */
|
|
16
|
-
|
|
17
|
-
import type { Tool, ToolContext, ToolOutput } from "../core/types.ts";
|
|
18
|
-
import { getExecutor } from "../core/executor.ts";
|
|
19
|
-
import { startBashJob, hasJobManager } from "../tools/bash-jobs.ts";
|
|
20
|
-
|
|
21
|
-
/** Upper bound on `timeout_ms`, so a model cannot ask for a deadline that never arrives. Ten minutes:
|
|
22
|
-
* past that the honest answer is a background job, which is the flag sitting next to it. */
|
|
23
|
-
export const MAX_TIMEOUT_MS = 600_000;
|
|
24
|
-
|
|
25
|
-
export const bashTool: Tool = {
|
|
26
|
-
schema: {
|
|
27
|
-
name: "bash",
|
|
28
|
-
description:
|
|
29
|
-
"Run a shell command in the workspace (cwd locked to the session cwd). One automatic retry on non-zero exit. " +
|
|
30
|
-
"Destructive system commands are refused by a best-effort blocklist — this is NOT a sandbox. Output truncated to 10k chars. " +
|
|
31
|
-
`\`timeout_ms\` gives up after that long and RETURNS what the command printed (max ${MAX_TIMEOUT_MS}ms); the process tree is killed. ` +
|
|
32
|
-
"`run_in_background: true` returns a job id at once instead of waiting — for a dev server, a long build, a slow test run. " +
|
|
33
|
-
"Read it later with bash_output (only NEW output per read), see them with bash_list, stop one with bash_kill; a finished job also posts one note here by itself.",
|
|
34
|
-
args: {
|
|
35
|
-
type: "object",
|
|
36
|
-
properties: {
|
|
37
|
-
command: { type: "string" },
|
|
38
|
-
timeout_ms: { type: "number", description: `give up after this many ms and return the partial output (1..${MAX_TIMEOUT_MS}); a timeout is never retried` },
|
|
39
|
-
run_in_background: { type: "boolean", description: "start it as a background job and return its id immediately (bash_output reads it)" },
|
|
40
|
-
},
|
|
41
|
-
required: ["command"],
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
kind: "execute",
|
|
45
|
-
sequential: true,
|
|
46
|
-
async execute(args, ctx): Promise<ToolOutput> {
|
|
47
|
-
const a = args as { command: string; timeout_ms?: unknown; run_in_background?: unknown };
|
|
48
|
-
const cmd = String(a.command);
|
|
49
|
-
const denied = deniedCommand(cmd);
|
|
50
|
-
if (denied) return { ok: false, output: denied };
|
|
51
|
-
|
|
52
|
-
// #55: the background flag is read AFTER the blocklist and BEFORE anything runs — a command we
|
|
53
|
-
// would refuse in the foreground must not become startable by asking for it in the background.
|
|
54
|
-
if (a.run_in_background === true) {
|
|
55
|
-
if (!hasJobManager()) {
|
|
56
|
-
// Deliberately a refusal, not a silent foreground run: the caller asked NOT to be blocked, and
|
|
57
|
-
// quietly blocking it is the wrong answer to a request we cannot honour.
|
|
58
|
-
return { ok: false, output: "background jobs are not available on this surface — drop run_in_background to run it in the foreground" };
|
|
59
|
-
}
|
|
60
|
-
const started = startBashJob(cmd, ctx.cwd);
|
|
61
|
-
if (!started.ok) return { ok: false, output: started.reason };
|
|
62
|
-
return {
|
|
63
|
-
ok: true,
|
|
64
|
-
output: `started background job ${started.info.id}: ${cmd}\nIt runs while you continue — do NOT poll in a loop. \`bash_output ${started.info.id}\` reads what is new; a note lands here when it finishes.`,
|
|
65
|
-
data: started.info,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const timeout = timeoutFrom(a.timeout_ms);
|
|
70
|
-
if (timeout instanceof Error) return { ok: false, output: timeout.message };
|
|
71
|
-
|
|
72
|
-
// port #10: shell execution goes through the Executor seam (direct/wsl/docker
|
|
73
|
-
// rungs, probed not assumed). Direct rung is byte-compatible with the old
|
|
74
|
-
// inline runOnce; a missing bash now returns exit=-1 instead of throwing.
|
|
75
|
-
const run = async (): Promise<{ code: number; text: string; timedOut: boolean }> => {
|
|
76
|
-
if (timeout === undefined) {
|
|
77
|
-
const r = await getExecutor().run(cmd, ctx.cwd, ctx.signal);
|
|
78
|
-
return { code: r.code, text: r.text, timedOut: false };
|
|
79
|
-
}
|
|
80
|
-
// The deadline aborts a controller the executor is watching, which IS the tree-kill path — the
|
|
81
|
-
// same one Esc uses. A timeout that returned while the process kept running would be a lie in
|
|
82
|
-
// the other direction, and on Windows it would hold the pipe open too.
|
|
83
|
-
const ac = new AbortController();
|
|
84
|
-
const onOuter = (): void => ac.abort();
|
|
85
|
-
ctx.signal.addEventListener("abort", onOuter, { once: true });
|
|
86
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
87
|
-
let fired = false;
|
|
88
|
-
try {
|
|
89
|
-
timer = setTimeout(() => { fired = true; ac.abort(); }, timeout);
|
|
90
|
-
const r = await getExecutor().run(cmd, ctx.cwd, ac.signal);
|
|
91
|
-
return { code: r.code, text: r.text, timedOut: fired };
|
|
92
|
-
} finally {
|
|
93
|
-
clearTimeout(timer);
|
|
94
|
-
ctx.signal.removeEventListener("abort", onOuter);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
let r = await run();
|
|
99
|
-
// single self-contained retry — but NEVER after an abort (port #21): the kill
|
|
100
|
-
// makes the exit non-zero, and a blind retry would respawn the cancelled
|
|
101
|
-
// command as a detached subprocess that outlives the run. Nor after a TIMEOUT:
|
|
102
|
-
// a command that needed more time than it was given needs more time, not twice.
|
|
103
|
-
if (r.code !== 0 && !r.timedOut && !ctx.signal.aborted) r = await run();
|
|
104
|
-
if (r.timedOut) {
|
|
105
|
-
// The partial output is the whole point. A build that timed out has usually already printed the
|
|
106
|
-
// reason it was slow, and throwing that away leaves the model with only "it took too long".
|
|
107
|
-
return { ok: false, output: `timed out after ${timeout}ms (process tree killed; what it printed follows)\n${r.text}` };
|
|
108
|
-
}
|
|
109
|
-
return { ok: r.code === 0, output: `exit=${r.code}\n${r.text}` };
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/** undefined = no deadline; an Error is a refusal to be RETURNED as tool output, never thrown. */
|
|
114
|
-
function timeoutFrom(raw: unknown): number | undefined | Error {
|
|
115
|
-
if (raw === undefined || raw === null) return undefined;
|
|
116
|
-
const n = typeof raw === "number" ? raw : Number(raw);
|
|
117
|
-
if (!Number.isFinite(n) || n <= 0) return new Error(`timeout_ms must be a positive number of milliseconds (got ${JSON.stringify(raw)})`);
|
|
118
|
-
if (n > MAX_TIMEOUT_MS) return new Error(`timeout_ms ${n} is longer than the ${MAX_TIMEOUT_MS}ms cap — use run_in_background for work that takes longer`);
|
|
119
|
-
return Math.floor(n);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ---------- Bash safety (best-effort blocklist, NOT a sandbox) ----------
|
|
123
|
-
|
|
124
|
-
/** Footgun guard on the raw command string. A determined agent bypasses it;
|
|
125
|
-
* real isolation belongs at the process/OS layer. */
|
|
126
|
-
const denyPatterns: RegExp[] = [
|
|
127
|
-
/rm\s+(-[a-z]*\s+)*\/(\s|$)/, // rm -rf /
|
|
128
|
-
/rm\s+(-[a-z]*\s+)*\/\*/, // rm -rf /*
|
|
129
|
-
/rm\s+(-[a-z]*\s+)*(--no-preserve-root\s+)?\*(\s|$)/, // rm -rf *
|
|
130
|
-
/:\(\)\s*\{/, // fork bomb body :(){ ...
|
|
131
|
-
/\bmkfs(\.\w+)?\b/,
|
|
132
|
-
/\b(shutdown|reboot|poweroff|halt)\b/,
|
|
133
|
-
/(^|[;&|\s])(sudo\s+)?format\s+(\/|[c-z]:)/i, // windows format
|
|
134
|
-
/(^|[;&|\s])(sudo\s+)?del\s+\/[fqs]/i, // windows del /f
|
|
135
|
-
/(^|[;&|\s])(sudo\s+)?rd\s+\/[sq]/i,
|
|
136
|
-
/(^|[;&|\s])(sudo\s+)?remove-item\s+-(rec|r|f|force)/i,
|
|
137
|
-
/\bdd\s+[^|]*of=\/dev\/(sd|nvme|hd|disk)/, // raw disk overwrite
|
|
138
|
-
/>\s*\/dev\/(sd|nvme|hd|disk)/,
|
|
139
|
-
/\bsudo\b.*\b(rm|mkfs|dd|shutdown|reboot|halt|format)\b/, // sudo + destructive core
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
export function deniedCommand(cmd: string): string | null {
|
|
143
|
-
const hit = denyPatterns.find((re) => re.test(cmd));
|
|
144
|
-
return hit
|
|
145
|
-
? `command refused by safety blocklist (matched ${hit.source}): this tool is not a sandbox; rephrase without destructive system commands`
|
|
146
|
-
: null;
|
|
147
|
-
}
|
|
148
|
-
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
/** Shadow-git checkpoints (PORT #11, cline port, Apache-2.0 — see THIRD_PARTY_NOTICES).
|
|
2
|
-
*
|
|
3
|
-
* KNOWN COST, and the design that would remove it — measured 2026-09-06, kept here so the next reader
|
|
4
|
-
* starts from the answer rather than the question. The shadow git-dir is per SESSION, so every new
|
|
5
|
-
* session pays `git init` + a full `add` of the workspace inside its FIRST write or bash call: 3.45–3.56 s
|
|
6
|
-
* in this repository (init+config 160 ms, first add 1.4 s, first commit 1.5 s writing 565 loose objects
|
|
7
|
-
* on Windows). Later calls in that session are 210–335 ms, of which ~200 ms is git. It does not amortise
|
|
8
|
-
* across sessions because nothing is shared.
|
|
9
|
-
*
|
|
10
|
-
* The cut is one shadow repo per REPOSITORY with a ref per session — a new session's first snapshot
|
|
11
|
-
* becomes an incremental add, ~80 ms measured. cline keeps one shadow repo per TASK because a task is its
|
|
12
|
-
* unit of restore; ours is the repository, so sessions in one workspace can share an object store and
|
|
13
|
-
* differ only by ref. It is NOT a configuration change: a shared git-dir means a shared index and a
|
|
14
|
-
* shared HEAD, and today's `add . && commit` / `reset --hard && clean -fd` would collide on index.lock
|
|
15
|
-
* (the second writer silently gets no checkpoint) and move HEAD under another session. It has to be
|
|
16
|
-
* re-done on plumbing: GIT_INDEX_FILE=<shadow>/<session>.index per session, then add → write-tree →
|
|
17
|
-
* commit-tree -p <that session's last> → update-ref refs/rovecode/<session> (atomic per ref, and the
|
|
18
|
-
* object store is atomic per object); restore becomes read-tree --reset -u then clean -fd against that
|
|
19
|
-
* same index, with no HEAD involved. A hash stays a hash, so restoring one session's checkpoint from
|
|
20
|
-
* another finally works — the case per-session storage never allowed, and the one that needs its own
|
|
21
|
-
* test, alongside a two-writers concurrency test. Sessions whose old per-session repo exists keep using
|
|
22
|
-
* it; nothing needs migrating.
|
|
23
|
-
*
|
|
24
|
-
* A SECOND git repository whose git-dir lives under .rovecode/checkpoints/<session> and whose
|
|
25
|
-
* work-tree is the WORKSPACE, so the user's own .git is never written. This is cline's
|
|
26
|
-
* shadow-git design: the @8eb5f3d snapshot's docs still describe it (docs/core-workflows/
|
|
27
|
-
* checkpoints.mdx:17 "shadow Git repository separate from your project's actual Git
|
|
28
|
-
* history … Your main Git repository stays untouched"), but its v4 CODE moved to in-repo
|
|
29
|
-
* `git stash create` + refs/cline/* (sdk/packages/core/src/hooks/checkpoint-hooks.ts:172,
|
|
30
|
-
* sdk/packages/core/src/session/checkpoint-restore.ts:444-477), which requires a git
|
|
31
|
-
* workspace and rewrites the user's HEAD — incompatible with this bar. The mechanics here
|
|
32
|
-
* therefore port cline's last shadow-git implementation, v3.89.2
|
|
33
|
-
* apps/vscode/src/integrations/checkpoints/:
|
|
34
|
-
* - `git init` in the checkpoints dir, then core.worktree=<workspace>, commit.gpgSign
|
|
35
|
-
* off, own identity (CheckpointGitOperations.ts:88-94); git-dir = <dir>/.git and
|
|
36
|
-
* worktree-mismatch reuse check (CheckpointUtils.ts:20-23, GitOperations.ts:70-73)
|
|
37
|
-
* - excludes written to <git-dir>/info/exclude, list headed by ".git/"
|
|
38
|
-
* (CheckpointExclusions.ts:42-46 + 297-301)
|
|
39
|
-
* - snapshot = `add . --ignore-errors` (CheckpointGitOperations.ts:213) +
|
|
40
|
-
* `commit --allow-empty --no-verify` (CheckpointTracker.ts:251-253)
|
|
41
|
-
* - restore = `reset --hard <hash>` (CheckpointTracker.ts:364) + `clean -fd` so files
|
|
42
|
-
* created after the checkpoint are rewound away while ignored paths (node_modules,
|
|
43
|
-
* .rovecode, build output) survive — the reset+clean pair of the snapshot's own restore
|
|
44
|
-
* (checkpoint-restore.ts:458-470)
|
|
45
|
-
* Deviations from v3.89.2: the shadow repo lives IN-WORKSPACE under .rovecode (bar) so
|
|
46
|
-
* ".rovecode/" is excluded from itself; the nested-.git rename dance
|
|
47
|
-
* (CheckpointGitOperations.ts:148-166, 207 ".git_disabled") is NOT ported — renaming the
|
|
48
|
-
* user's nested .git would violate "user .git never touched", so nested repos become
|
|
49
|
-
* inert gitlink entries instead (their contents are not checkpointed, never modified);
|
|
50
|
-
* core.autocrlf=false is set so restores are byte-exact on Windows.
|
|
51
|
-
*
|
|
52
|
-
* Conversation restore returns the session entryId to branch to — the CALLER feeds it to
|
|
53
|
-
* SessionStore.branch() (port #2 leaf machinery); this module never imports session.ts. */
|
|
54
|
-
|
|
55
|
-
import { execFile } from "node:child_process";
|
|
56
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
57
|
-
import { join, resolve } from "node:path";
|
|
58
|
-
|
|
59
|
-
export interface Checkpoint {
|
|
60
|
-
hash: string; // shadow commit hash
|
|
61
|
-
label: string; // e.g. the mutating tool's name
|
|
62
|
-
entryId?: string; // session entry to branch to on conversation restore
|
|
63
|
-
createdAt: number;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type RestoreMode = "files" | "conversation" | "both";
|
|
67
|
-
|
|
68
|
-
export type RestoreResult =
|
|
69
|
-
| { ok: true; mode: RestoreMode; checkpoint: Checkpoint; entryId?: string }
|
|
70
|
-
| { ok: false; error: string };
|
|
71
|
-
|
|
72
|
-
/** ToolKind values whose calls mutate the workspace → snapshot after each (bar:
|
|
73
|
-
* "snapshot commit after every mutating tool call"). memory writes land under the
|
|
74
|
-
* excluded .rovecode/; spawned children's own write/execute calls hit the same hook. */
|
|
75
|
-
export const MUTATING_KINDS: ReadonlySet<string> = new Set(["write", "execute"]);
|
|
76
|
-
|
|
77
|
-
/** Conversation-restore anchor for a snapshot: the LAST role:"user" message on the
|
|
78
|
-
* active path. At snapshot time the tail entry is the assistant message that ISSUED
|
|
79
|
-
* the in-flight tool call (the loop appends it pre-dispatch), so anchoring the tail
|
|
80
|
-
* branches to a history ending in tool_calls with no tool replies → provider 400.
|
|
81
|
-
* cline anchors the user run message instead (checkpoint-restore.ts:217-250).
|
|
82
|
-
* Wiring contract (like MUTATING_KINDS): runtime.ts withCheckpoint computes
|
|
83
|
-
* `anchorEntryId(activeStore.messages())`. */
|
|
84
|
-
export function anchorEntryId(messages: ReadonlyArray<{ id: string; role: string }>): string | undefined {
|
|
85
|
-
return messages.findLast((m) => m.role === "user")?.id;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface CheckpointsInit {
|
|
89
|
-
workspace: string;
|
|
90
|
-
sessionId: string;
|
|
91
|
-
/** override the shadow root (default <workspace>/.rovecode/checkpoints) — tests/global mode */
|
|
92
|
-
shadowRoot?: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** cline's default exclusions (CheckpointExclusions.ts:42-70), structural entries first, then the media /
|
|
96
|
-
* archive / binary categories. The first port kept only the structural entries, and the snapshot's
|
|
97
|
-
* `git add .` then hashed every file the WORKSPACE tracks: in a repo carrying 165 MB of tracked video
|
|
98
|
-
* (site/media-src) the first mutating tool call of every session took 26–35 s and left a 232 MB shadow
|
|
99
|
-
* repo under .rovecode/checkpoints/<session> (measured 2026-09-06, scripts/probe-turn.ts). A checkpoint
|
|
100
|
-
* exists to restore what the agent changed, and the agent does not edit videos, screenshots, archives or
|
|
101
|
-
* compiled binaries — those are excluded by extension, like cline does. With videos alone excluded the
|
|
102
|
-
* same repo still took 10.7 s and 79 MB: 82 MB of site/screenshots/*.png. Text of any size is still
|
|
103
|
-
* snapshotted, and so is SVG (text a designer or the agent writes). */
|
|
104
|
-
const EXCLUDES = [
|
|
105
|
-
".git/",
|
|
106
|
-
".rovecode/", // the shadow repo itself lives here (deviation: in-workspace)
|
|
107
|
-
"node_modules/",
|
|
108
|
-
"dist/",
|
|
109
|
-
"build/",
|
|
110
|
-
"out/",
|
|
111
|
-
".next/",
|
|
112
|
-
"__pycache__/",
|
|
113
|
-
".venv/",
|
|
114
|
-
"venv/",
|
|
115
|
-
".DS_Store",
|
|
116
|
-
// media (cline getMediaFilePatterns): video, audio, raster images — not SVG
|
|
117
|
-
"*.mp4", "*.m4v", "*.mov", "*.avi", "*.mkv", "*.webm", "*.wmv", "*.flv", "*.mpg", "*.mpeg",
|
|
118
|
-
"*.mp3", "*.m4a", "*.wav", "*.flac", "*.ogg", "*.aac", "*.wma",
|
|
119
|
-
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.ico", "*.webp", "*.tif", "*.tiff", "*.heic", "*.avif", "*.psd",
|
|
120
|
-
// archives and disk images (getLargeDataFilePatterns)
|
|
121
|
-
"*.zip", "*.tar", "*.gz", "*.tgz", "*.bz2", "*.xz", "*.7z", "*.rar", "*.iso", "*.dmg",
|
|
122
|
-
// compiled binaries and native libraries
|
|
123
|
-
"*.exe", "*.dll", "*.so", "*.dylib", "*.node", "*.wasm", "*.o", "*.a", "*.class", "*.jar", "*.pyc",
|
|
124
|
-
// databases and caches (getDatabaseFilePatterns / getCacheFilePatterns)
|
|
125
|
-
"*.sqlite", "*.sqlite3", "*.db", "*.mdb", "*.log",
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
/** `verb` names the failing subcommand in errors; the default suits bare invocations
|
|
129
|
-
* like ["init"], but --git-dir'd calls must pass it (the first non-dash arg there is
|
|
130
|
-
* the git-dir PATH — blaming a path instead of the verb misled /restore users). */
|
|
131
|
-
function runGit(args: string[], cwd: string, verb = args.find((a) => !a.startsWith("-")) ?? "", extra: Record<string, string> = {}): Promise<string> {
|
|
132
|
-
// Explicit env hygiene: a caller's GIT_* vars must not redirect shadow commands
|
|
133
|
-
// at the USER repo (cline relies on simple-git cwd instead — GitOperations.ts:88).
|
|
134
|
-
// `extra` is OUR explicit override (position()'s scratch GIT_INDEX_FILE), applied after the scrub.
|
|
135
|
-
const env = { ...process.env };
|
|
136
|
-
for (const k of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_COMMON_DIR"]) delete env[k];
|
|
137
|
-
Object.assign(env, extra);
|
|
138
|
-
return new Promise((res, rej) => {
|
|
139
|
-
execFile("git", args, { cwd, env, windowsHide: true, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
140
|
-
if (err) rej(new Error(`git ${verb} failed: ${stderr.trim() || err.message}`));
|
|
141
|
-
else res(stdout.trim());
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Canonical form for workspace-identity compares: realpath fixes case/8.3 aliases of
|
|
147
|
-
* EXISTING paths (C:\foo vs c:\foo reopened the shadow repo as "another workspace"
|
|
148
|
-
* and silently disabled checkpoints); the case-fold below covers paths realpath
|
|
149
|
-
* cannot resolve, on the case-insensitive platform only. */
|
|
150
|
-
function canonPath(p: string): string {
|
|
151
|
-
let r = p;
|
|
152
|
-
try { r = (realpathSync.native ?? realpathSync)(p); } catch { /* nonexistent: compare as given */ }
|
|
153
|
-
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** The ONE spelling of a session's shadow directory, `<workspace>/.rovecode/checkpoints/<sanitised id>`: the id is
|
|
157
|
-
* folded charwise to [A-Za-z0-9._-] and dot-only ids ("."/"..", which survive the charwise filter but escape or
|
|
158
|
-
* collapse the shadow root under join()) and "" become underscores. init() creates it; `rovecode sessions delete`
|
|
159
|
-
* (core/session-ops.ts) removes it — both through here, so a delete never builds the path a second way. */
|
|
160
|
-
export function checkpointShadowDir(workspace: string, sessionId: string, shadowRoot?: string): string {
|
|
161
|
-
const cleaned = sessionId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
162
|
-
const session = /^\.*$/.test(cleaned) ? cleaned.replace(/\./g, "_") || "_" : cleaned;
|
|
163
|
-
return join(shadowRoot ?? join(workspace, ".rovecode", "checkpoints"), session);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export class Checkpoints {
|
|
167
|
-
/** history, oldest first (sidecar-backed: survives process restarts) */
|
|
168
|
-
private readonly log: Checkpoint[] = [];
|
|
169
|
-
|
|
170
|
-
private constructor(
|
|
171
|
-
readonly workspace: string,
|
|
172
|
-
/** shadow repo GIT DIR: <workspace>/.rovecode/checkpoints/<session>/.git */
|
|
173
|
-
readonly gitDir: string,
|
|
174
|
-
private readonly sidecar: string,
|
|
175
|
-
) {}
|
|
176
|
-
|
|
177
|
-
/** Every shadow command names its git-dir and work-tree explicitly, so no cwd or
|
|
178
|
-
* environment state can ever point one at the user's repo. */
|
|
179
|
-
private git(...args: string[]): Promise<string> {
|
|
180
|
-
return runGit(["--git-dir", this.gitDir, "--work-tree", this.workspace, ...args], this.workspace, args[0]);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Create or reopen the shadow repo for a session. Works whether or not the workspace
|
|
184
|
-
* is a git repo — the shadow git-dir is entirely separate (non-git workspaces bar). */
|
|
185
|
-
static async init(opts: CheckpointsInit): Promise<Checkpoints> {
|
|
186
|
-
const workspace = resolve(opts.workspace);
|
|
187
|
-
const shadowDir = checkpointShadowDir(workspace, opts.sessionId, opts.shadowRoot);
|
|
188
|
-
const gitDir = join(shadowDir, ".git"); // cline layout: <checkpointsDir>/.git (CheckpointUtils.ts:20-23)
|
|
189
|
-
mkdirSync(shadowDir, { recursive: true });
|
|
190
|
-
const cp = new Checkpoints(workspace, gitDir, join(shadowDir, "checkpoints.jsonl"));
|
|
191
|
-
|
|
192
|
-
if (!existsSync(join(gitDir, "HEAD"))) {
|
|
193
|
-
// plain `git init` in the shadow dir, exactly GitOperations.ts:88
|
|
194
|
-
await runGit(["init"], shadowDir);
|
|
195
|
-
// GitOperations.ts:91-94 config block (identity ours; autocrlf is an rovecode addition)
|
|
196
|
-
for (const [k, v] of [
|
|
197
|
-
["core.worktree", workspace],
|
|
198
|
-
["commit.gpgSign", "false"],
|
|
199
|
-
["core.autocrlf", "false"],
|
|
200
|
-
["user.name", "Rovecode Checkpoint"],
|
|
201
|
-
["user.email", "checkpoint@rovecode.local"],
|
|
202
|
-
] as const) await cp.git("config", k, v);
|
|
203
|
-
} else {
|
|
204
|
-
// reuse check: refuse a shadow repo whose recorded worktree is another path
|
|
205
|
-
// (GitOperations.ts:70-73 "Checkpoints can only be used in the original workspace").
|
|
206
|
-
// Compared canonically — a case-variant reopen (C:\foo vs c:\foo) is the SAME dir.
|
|
207
|
-
const wt = await cp.git("config", "core.worktree").catch(() => "");
|
|
208
|
-
if (canonPath(resolve(wt)) !== canonPath(workspace)) throw new Error(`checkpoints: shadow repo belongs to ${wt}, not ${workspace}`);
|
|
209
|
-
}
|
|
210
|
-
// (re)write excludes into the shadow git-dir every init (CheckpointExclusions.ts:297-301)
|
|
211
|
-
mkdirSync(join(gitDir, "info"), { recursive: true });
|
|
212
|
-
writeFileSync(join(gitDir, "info", "exclude"), EXCLUDES.join("\n") + "\n");
|
|
213
|
-
cp.loadSidecar();
|
|
214
|
-
return cp;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private loadSidecar(): void {
|
|
218
|
-
if (!existsSync(this.sidecar)) return;
|
|
219
|
-
for (const line of readFileSync(this.sidecar, "utf8").split("\n")) {
|
|
220
|
-
if (!line.trim()) continue;
|
|
221
|
-
try {
|
|
222
|
-
const c = JSON.parse(line) as Checkpoint;
|
|
223
|
-
if (typeof c.hash === "string" && typeof c.label === "string") this.log.push(c);
|
|
224
|
-
} catch { /* corrupt sidecar line: skip, never throw (session.ts reload pattern) */ }
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** Snapshot the whole workspace: stage-all + allow-empty commit
|
|
229
|
-
* (GitOperations.ts:213 + CheckpointTracker.ts:251-253). Call after every mutating
|
|
230
|
-
* tool call; `entryId` is the session entry a conversation restore should branch to. */
|
|
231
|
-
async snapshot(label: string, entryId?: string): Promise<Checkpoint> {
|
|
232
|
-
await this.git("add", ".", "--ignore-errors");
|
|
233
|
-
await this.git("commit", "--allow-empty", "--no-verify", "-m", `rovecode-checkpoint: ${label}`);
|
|
234
|
-
const hash = await this.git("rev-parse", "HEAD");
|
|
235
|
-
const c: Checkpoint = { hash, label, createdAt: Date.now(), ...(entryId !== undefined ? { entryId } : {}) };
|
|
236
|
-
appendFileSync(this.sidecar, JSON.stringify(c) + "\n");
|
|
237
|
-
this.log.push(c);
|
|
238
|
-
return c;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/** History oldest→newest. */
|
|
242
|
-
list(): Checkpoint[] { return [...this.log]; }
|
|
243
|
-
|
|
244
|
-
/** Where the workspace sits in the snapshot history (port #65 /undo). `at` — the NEWEST checkpoint whose tree
|
|
245
|
-
* equals the work tree as it is now (staged into a SCRATCH index under the shadow git-dir, so the real index —
|
|
246
|
-
* and changedSince's untracked list — stay untouched); null when no snapshot matches (the workspace drifted).
|
|
247
|
-
* `previous` — the nearest ANCESTOR of `at` whose content differs (the pre-edit state; re-snapshots of an
|
|
248
|
-
* unchanged tree are stepped over; after a restore the shadow history forks, so the parent chain is followed,
|
|
249
|
-
* not the list order); null when nothing older differs. The git-call count does not grow with the history
|
|
250
|
-
* (rev-parse takes the hashes in batches); any git failure degrades to `{ at: null, previous: null }`, which a
|
|
251
|
-
* caller treats as a drifted workspace. */
|
|
252
|
-
async position(): Promise<{ at: Checkpoint | null; previous: Checkpoint | null }> {
|
|
253
|
-
const none = { at: null, previous: null };
|
|
254
|
-
if (this.log.length === 0) return none;
|
|
255
|
-
const scratch = join(this.gitDir, "rovecode-undo-index");
|
|
256
|
-
const base = ["--git-dir", this.gitDir, "--work-tree", this.workspace];
|
|
257
|
-
try {
|
|
258
|
-
rmSync(scratch, { force: true });
|
|
259
|
-
await runGit([...base, "add", ".", "--ignore-errors"], this.workspace, "add", { GIT_INDEX_FILE: scratch });
|
|
260
|
-
const tree = await runGit([...base, "write-tree"], this.workspace, "write-tree", { GIT_INDEX_FILE: scratch });
|
|
261
|
-
const trees: string[] = [];
|
|
262
|
-
for (let i = 0; i < this.log.length; i += 200) {
|
|
263
|
-
trees.push(...(await this.git("rev-parse", ...this.log.slice(i, i + 200).map((c) => `${c.hash}^{tree}`))).split("\n").map((s) => s.trim()));
|
|
264
|
-
}
|
|
265
|
-
const atIdx = trees.lastIndexOf(tree);
|
|
266
|
-
if (atIdx < 0) return none;
|
|
267
|
-
const at = this.log[atIdx]!;
|
|
268
|
-
for (const h of (await this.git("rev-list", at.hash)).split("\n").slice(1)) {
|
|
269
|
-
const i = this.log.findLastIndex((c) => c.hash === h.trim());
|
|
270
|
-
if (i >= 0 && trees[i] !== tree) return { at, previous: this.log[i]! };
|
|
271
|
-
}
|
|
272
|
-
return { at, previous: null };
|
|
273
|
-
} catch { return none; }
|
|
274
|
-
finally { rmSync(scratch, { force: true }); }
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** What a "files" restore of `hash` would touch (port #65 /undo's confirmation card): tracked paths whose
|
|
278
|
-
* work-tree content differs from the snapshot (`diff --name-status <hash>`: M rewritten, D recreated) plus
|
|
279
|
-
* the untracked, non-excluded paths `clean -fd` would remove (`?`). Read-only; null when git fails, so a
|
|
280
|
-
* caller falls back to a generic card instead of a wrong list. */
|
|
281
|
-
async changedSince(hash: string): Promise<{ path: string; status: string }[] | null> {
|
|
282
|
-
try {
|
|
283
|
-
const out: { path: string; status: string }[] = [];
|
|
284
|
-
for (const line of (await this.git("diff", "--name-status", hash)).split("\n")) {
|
|
285
|
-
const m = /^([A-Z])\S*\t(.+)$/.exec(line.trim());
|
|
286
|
-
if (m) out.push({ status: m[1]!, path: m[2]!.split("\t").at(-1)! });
|
|
287
|
-
}
|
|
288
|
-
for (const p of (await this.git("ls-files", "--others", "--exclude-standard")).split("\n")) if (p.trim()) out.push({ status: "?", path: p.trim() });
|
|
289
|
-
return out;
|
|
290
|
-
} catch { return null; }
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/** Restore a checkpoint by full hash or unique prefix.
|
|
294
|
-
* - "files": worktree → checkpoint state (reset --hard + clean -fd; ignored paths survive)
|
|
295
|
-
* - "conversation": NO file changes; returns the entryId for SessionStore.branch()
|
|
296
|
-
* - "both": files restored AND entryId returned
|
|
297
|
-
* Never throws — bad refs/modes AND shadow-git failures (a stale index.lock used to
|
|
298
|
-
* escape here and kill the TUI on unhandled rejection) come back structured, the
|
|
299
|
-
* error naming the failing verb (reset/clean). */
|
|
300
|
-
async restore(ref: string, mode: RestoreMode): Promise<RestoreResult> {
|
|
301
|
-
const hits = this.log.filter((c) => c.hash === ref || c.hash.startsWith(ref));
|
|
302
|
-
// duplicate hashes (identical content re-snapshotted) are ONE candidate — the
|
|
303
|
-
// LATEST entry wins so its (newer) conversation anchor is the one restored
|
|
304
|
-
const target = hits.at(-1);
|
|
305
|
-
if (!target || ref.length < 4) return { ok: false, error: `no checkpoint matches ${ref}` };
|
|
306
|
-
if (new Set(hits.map((h) => h.hash)).size > 1) return { ok: false, error: `ambiguous checkpoint prefix ${ref}` };
|
|
307
|
-
// conversation restore needs a recorded entryId — reject BEFORE touching any file,
|
|
308
|
-
// so "both" can never half-apply
|
|
309
|
-
if (mode !== "files" && target.entryId === undefined) {
|
|
310
|
-
return { ok: false, error: `checkpoint ${target.hash.slice(0, 8)} has no session entryId` };
|
|
311
|
-
}
|
|
312
|
-
if (mode !== "conversation") {
|
|
313
|
-
try {
|
|
314
|
-
await this.git("reset", "--hard", target.hash); // CheckpointTracker.ts:364
|
|
315
|
-
// remove files created after the checkpoint; single -f spares nested git repos,
|
|
316
|
-
// no -x spares ignored/excluded paths (checkpoint-restore.ts:458-470)
|
|
317
|
-
await this.git("clean", "-fd");
|
|
318
|
-
} catch (e) {
|
|
319
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return {
|
|
323
|
-
ok: true, mode, checkpoint: target,
|
|
324
|
-
...(mode !== "files" && target.entryId !== undefined ? { entryId: target.entryId } : {}),
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
}
|
package/src/coding/diff.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/** Port #24 — bounded unified-diff preview for edit/write approvals.
|
|
2
|
-
*
|
|
3
|
-
* The post-change content is computed IN MEMORY (edit: hashline's pure apply over the
|
|
4
|
-
* current file; write: the proposed content), diffed against the file with jsdiff
|
|
5
|
-
* (`diff`, BSD-3-Clause — see THIRD_PARTY_NOTICES.md), and rendered git-style, clipped
|
|
6
|
-
* to maxLines with an exact "… +N more lines" marker. Never throws: stale anchors,
|
|
7
|
-
* binary content, malformed args and I/O errors all come back as kind "unavailable"
|
|
8
|
-
* carrying the reason, so the approval overlay degrades instead of blocking the ask.
|
|
9
|
-
* Shape follows gemini-cli's confirmation diffs — structuredPatch with a small context
|
|
10
|
-
* (packages/core/src/tools/diffOptions.ts:10-18) and CR-tolerant line comparison
|
|
11
|
-
* (diff-utils.ts:22 splits on /\r?\n/; here jsdiff's stripTrailingCr). No code copied. */
|
|
12
|
-
|
|
13
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
-
import { relative } from "node:path";
|
|
15
|
-
import { toolPath } from "../core/workspace.ts";
|
|
16
|
-
import { structuredPatch } from "diff";
|
|
17
|
-
import { applyEditsToContent, type EditFailure, type EditOp } from "./hashline.ts";
|
|
18
|
-
|
|
19
|
-
export type DiffKind = "modify" | "create" | "unchanged" | "unavailable";
|
|
20
|
-
export interface DiffPreview { text: string; truncated: boolean; kind: DiffKind }
|
|
21
|
-
export interface DiffPreviewOptions { maxLines?: number; context?: number }
|
|
22
|
-
|
|
23
|
-
/** default cap on rendered lines (headers + hunks); the TUI clips further to its rows */
|
|
24
|
-
export const DIFF_MAX_LINES = 40;
|
|
25
|
-
const DIFF_CONTEXT = 3;
|
|
26
|
-
/** git's buffer_is_binary heuristic: a NUL byte within the first 8000 bytes */
|
|
27
|
-
const BINARY_SNIFF = 8000;
|
|
28
|
-
/** Myers budget: a rewrite beyond this many edited lines is reported, not diffed (bounded CPU);
|
|
29
|
-
* the wall-clock cap is a safety net for huge files, the edit-length cap is the deterministic one */
|
|
30
|
-
const MAX_EDIT_LENGTH = 2000;
|
|
31
|
-
const MAX_DIFF_MS = 1000;
|
|
32
|
-
const MAX_CHARS = 4_000_000;
|
|
33
|
-
|
|
34
|
-
/** `… +N more lines` — the exact marker appended when the output is clipped. */
|
|
35
|
-
export function moreMarker(n: number): string { return `… +${n} more line${n === 1 ? "" : "s"}`; }
|
|
36
|
-
|
|
37
|
-
/** Keep the first `max` lines; the marker accounts for every hidden line. */
|
|
38
|
-
export function clipLines(lines: string[], max: number): string[] {
|
|
39
|
-
return lines.length > max ? [...lines.slice(0, max), moreMarker(lines.length - max)] : lines;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** the ladder's own spelling (core/workspace.ts toolPath): the preview names the file the tool will open */
|
|
43
|
-
function resolvePath(cwd: string, p: string): string { return toolPath(cwd, p); }
|
|
44
|
-
|
|
45
|
-
function hasNul(buf: Uint8Array): boolean {
|
|
46
|
-
const n = Math.min(buf.length, BINARY_SNIFF);
|
|
47
|
-
for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const unavailable = (reason: string): DiffPreview => ({ text: `diff unavailable: ${reason}`, truncated: false, kind: "unavailable" });
|
|
52
|
-
|
|
53
|
-
function failureReason(f: EditFailure): string {
|
|
54
|
-
switch (f.kind) {
|
|
55
|
-
case "tag-mismatch": return `stale read — file TAG is now ${f.actual}, the edit expects ${f.expected}`;
|
|
56
|
-
case "hash-mismatch": return `stale anchor — line ${f.line} hash is ${f.actual}, the edit expects ${f.expected}; ${f.nearest}`;
|
|
57
|
-
case "out-of-range": return `line ${f.line} out of range (file has ${f.lineCount} lines)`;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** The edit tool's args, validated (a malformed call yields "unavailable", never a throw). */
|
|
62
|
-
function editOps(args: Record<string, unknown>, path: string): EditOp[] | null {
|
|
63
|
-
if (!Array.isArray(args.edits)) return null;
|
|
64
|
-
const ops: EditOp[] = [];
|
|
65
|
-
for (const e of args.edits as unknown[]) {
|
|
66
|
-
const o = (typeof e === "object" && e !== null ? e : {}) as Record<string, unknown>;
|
|
67
|
-
if (typeof o.tag !== "string" || typeof o.anchorHash !== "string" || typeof o.anchorLine !== "number") return null;
|
|
68
|
-
if (!Array.isArray(o.newLines) || !o.newLines.every((l) => typeof l === "string")) return null;
|
|
69
|
-
ops.push({ path, tag: o.tag, anchorLine: o.anchorLine, anchorHash: o.anchorHash, newLines: o.newLines as string[] });
|
|
70
|
-
}
|
|
71
|
-
return ops;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Content → signed display lines: CRLF-tolerant, trailing newline dropped, missing EOF newline noted. */
|
|
75
|
-
function signedLines(content: string, sign: "+" | "-"): string[] {
|
|
76
|
-
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
77
|
-
const eofNewline = lines[lines.length - 1] === "";
|
|
78
|
-
if (eofNewline) lines.pop();
|
|
79
|
-
const out = lines.map((l) => sign + l);
|
|
80
|
-
if (!eofNewline && out.length > 0) out.push("\");
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function renderUnified(rel: string, before: string | null, after: string, opts: DiffPreviewOptions): DiffPreview {
|
|
85
|
-
const maxLines = opts.maxLines ?? DIFF_MAX_LINES;
|
|
86
|
-
if (before === after) return { text: "", truncated: false, kind: "unchanged" };
|
|
87
|
-
const body: string[] = [];
|
|
88
|
-
let kind: DiffKind = "modify";
|
|
89
|
-
if (before === null) {
|
|
90
|
-
// new file: every line is an add — no Myers pass needed (nor its cost on big files)
|
|
91
|
-
kind = "create";
|
|
92
|
-
const adds = signedLines(after, "+");
|
|
93
|
-
if (adds.length > 0) body.push(`@@ -0,0 +1,${adds.filter((l) => l[0] === "+").length} @@`, ...adds);
|
|
94
|
-
} else {
|
|
95
|
-
if (before.length + after.length > MAX_CHARS) return unavailable("file too large to preview");
|
|
96
|
-
// stripTrailingCr: a CRLF file diffs by content, not by line ending (gemini-cli diff-utils.ts:22)
|
|
97
|
-
const patch = structuredPatch(rel, rel, before, after, undefined, undefined,
|
|
98
|
-
{ context: opts.context ?? DIFF_CONTEXT, stripTrailingCr: true, maxEditLength: MAX_EDIT_LENGTH, timeout: MAX_DIFF_MS });
|
|
99
|
-
if (patch === undefined) return unavailable(`change too large to preview (over ${MAX_EDIT_LENGTH} edited lines or ${MAX_DIFF_MS}ms)`);
|
|
100
|
-
for (const h of patch.hunks) body.push(`@@ -${h.oldStart},${h.oldLines} +${h.newStart},${h.newLines} @@`, ...h.lines);
|
|
101
|
-
if (body.length === 0) return { text: "no content changes (line endings only)", truncated: false, kind: "unchanged" };
|
|
102
|
-
}
|
|
103
|
-
const lines = [`--- ${before === null ? "/dev/null" : `a/${rel}`}`, `+++ b/${rel}`, ...body];
|
|
104
|
-
return { text: clipLines(lines, maxLines).join("\n"), truncated: lines.length > maxLines, kind };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Preview what an edit/write tool call would do to its file, as a bounded unified diff.
|
|
108
|
-
* `args` are the (revised) tool args exactly as the tool would receive them. */
|
|
109
|
-
export function previewDiff(tool: "edit" | "write", args: unknown, cwd: string, opts: DiffPreviewOptions = {}): DiffPreview {
|
|
110
|
-
try {
|
|
111
|
-
const a = (typeof args === "object" && args !== null ? args : {}) as Record<string, unknown>;
|
|
112
|
-
if (typeof a.path !== "string" || a.path === "") return unavailable("missing path");
|
|
113
|
-
const abs = resolvePath(cwd, a.path);
|
|
114
|
-
const rel = relative(cwd, abs).replace(/\\/g, "/") || a.path;
|
|
115
|
-
let before: string | null = null;
|
|
116
|
-
if (existsSync(abs)) {
|
|
117
|
-
const buf = readFileSync(abs);
|
|
118
|
-
if (hasNul(buf)) return unavailable("binary file");
|
|
119
|
-
before = buf.toString("utf8");
|
|
120
|
-
}
|
|
121
|
-
let after: string;
|
|
122
|
-
if (tool === "write") {
|
|
123
|
-
if (typeof a.content !== "string") return unavailable("missing content");
|
|
124
|
-
after = a.content;
|
|
125
|
-
} else {
|
|
126
|
-
if (before === null) return unavailable(`file not found: ${abs}`);
|
|
127
|
-
const ops = editOps(a, abs);
|
|
128
|
-
if (ops === null) return unavailable("malformed edits");
|
|
129
|
-
const r = applyEditsToContent(before, ops, abs);
|
|
130
|
-
if (!r.ok) return unavailable(failureReason(r.failure));
|
|
131
|
-
after = r.content;
|
|
132
|
-
}
|
|
133
|
-
if (after.includes("\0")) return unavailable("binary content");
|
|
134
|
-
return renderUnified(rel, before, after, opts);
|
|
135
|
-
} catch (e) {
|
|
136
|
-
return unavailable(e instanceof Error ? e.message : String(e));
|
|
137
|
-
}
|
|
138
|
-
}
|