rovecode 0.3.2 → 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 -45
- 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 +38 -11
- 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/core/loop.ts
DELETED
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
/** The agent loop (ADR-003): one loop, generator-based, event-streaming.
|
|
2
|
-
* Steering drained between tool batches; follow-ups drained at stop.
|
|
3
|
-
* Errors cross the provider seam as stopReasons, never exceptions.
|
|
4
|
-
* Cancellation (port #21): ONE AbortController per run — its signal reaches
|
|
5
|
-
* the provider fetch, ToolContext.signal, and (by identity) child processes. */
|
|
6
|
-
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
8
|
-
import type {
|
|
9
|
-
AgentDefinition, Message, MessagePart, RunEvent, RunConfig, RunOutstanding, StreamEvent, StreamFn, VerifyState,
|
|
10
|
-
ModelRef, ToolCallPart, AgentVars, ToolContext, ToolOutput, ToolSchema,
|
|
11
|
-
StopReason, TokenUsage,
|
|
12
|
-
} from "./types.ts";
|
|
13
|
-
import { ToolRegistry, ABORTED_TOOL_RESULT, type ExtensionHooks } from "./tools.ts";
|
|
14
|
-
import type { ToolGuard } from "./guardrails.ts";
|
|
15
|
-
import { servedBy } from "../providers/router.ts";
|
|
16
|
-
import { SessionStore } from "./session.ts";
|
|
17
|
-
import { assembleContext, estimateTokens, type ContextChunk } from "./context.ts";
|
|
18
|
-
import { compactionTrigger, planCompaction, applyCompaction, isContextOverflow, type CompactionCtx, type NativeCompactor } from "./compaction.ts";
|
|
19
|
-
import { verifyClause, verifyDetail, verifyNudgeText } from "./verify-gate.ts";
|
|
20
|
-
|
|
21
|
-
export interface LoopDeps {
|
|
22
|
-
stream: StreamFn;
|
|
23
|
-
registry: ToolRegistry;
|
|
24
|
-
store: SessionStore;
|
|
25
|
-
hooks?: ExtensionHooks;
|
|
26
|
-
summarize?: (texts: string[]) => Promise<string>; // weak-model head summarizer
|
|
27
|
-
/** port #25 provider-native compaction capability — set ONLY when the active provider does
|
|
28
|
-
* server-side compaction (none of rovecode's adapters do today; compaction.ts header) */
|
|
29
|
-
compactNative?: NativeCompactor;
|
|
30
|
-
tools?: ToolSchema[];
|
|
31
|
-
/** orchestrator seam: run a child agent; receives parent depth + 1 */
|
|
32
|
-
childRunner?: (agent: string, goal: string, vars: AgentVars | undefined, depth: number) => Promise<{ ok: boolean; summary: string; usage: TokenUsage }>;
|
|
33
|
-
/** tool-loop guardrails (port #4): loop signatures + duplicate-result stubs */
|
|
34
|
-
guard?: ToolGuard;
|
|
35
|
-
/** The plan reminder: called once per turn with the history that is about to be sent, it returns a
|
|
36
|
-
* line the model should see again — the live todo list — or null. The result rides as ONE extra user
|
|
37
|
-
* message on this request only: it is never appended to `history` and never persisted, so the plan
|
|
38
|
-
* cannot pile up copies of itself in the transcript, and the system prefix (and its prompt cache)
|
|
39
|
-
* is untouched. Without it a list written 20 turns ago is buried under tool results and the model
|
|
40
|
-
* stops maintaining it. */
|
|
41
|
-
planReminder?: (history: readonly Message[]) => string | null;
|
|
42
|
-
/** ToolContext cwd for this run — surfaces with a session cwd (ACP, server)
|
|
43
|
-
* pass it here; default is the agent process dir */
|
|
44
|
-
cwd?: string;
|
|
45
|
-
/** mid-turn cancellation (port #21): when this aborts, the run's OWN
|
|
46
|
-
* controller aborts — the in-flight provider fetch dies, every
|
|
47
|
-
* ToolContext.signal consumer (bash subprocess trees, MCP calls) is
|
|
48
|
-
* cancelled, and the run ends with run_end status "stopped". Same seam as
|
|
49
|
-
* `cwd`: every surface already constructs LoopDeps, so one optional field
|
|
50
|
-
* keeps tui/repl/acp/serve uniform. The loop still owns the per-run
|
|
51
|
-
* controller so bare consumers that only .return() the generator
|
|
52
|
-
* (orchestrator children, cmdRun, gauntlet) keep in-flight-tool
|
|
53
|
-
* cancellation without constructing anything. */
|
|
54
|
-
signal?: AbortSignal;
|
|
55
|
-
/** the run's clock for RunConfig.maxSeconds (default Date.now) — tests inject a fake one */
|
|
56
|
-
clock?: () => number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Synthesized output for a tool_call the abort left unanswered — ONE owner, tools.ts (dispatch
|
|
60
|
-
* synthesizes the same text); re-exported here for the loop's consumers (surfaces, tests). */
|
|
61
|
-
export { ABORTED_TOOL_RESULT };
|
|
62
|
-
|
|
63
|
-
export class SteeringQueue {
|
|
64
|
-
private queue: string[] = [];
|
|
65
|
-
push(text: string): void { this.queue.push(text); }
|
|
66
|
-
drainAll(): string[] { const q = this.queue; this.queue = []; return q; }
|
|
67
|
-
drainOne(): string | undefined { return this.queue.shift(); }
|
|
68
|
-
get size(): number { return this.queue.length; }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Deterministic toolCall part extraction + truncation safety (pi agent-loop.ts:344-380). */
|
|
72
|
-
export function extractToolCalls(parts: MessagePart[], stopReason: string): { calls: ToolCallPart[]; truncated: boolean } {
|
|
73
|
-
const calls = parts.filter((p): p is ToolCallPart => p.kind === "tool_call");
|
|
74
|
-
return { calls, truncated: stopReason === "length" };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** One controller per run (port #21 bar). agentLoop is a thin wrapper so the
|
|
78
|
-
* controller's lifetime is EXACTLY the generator's: it follows deps.signal
|
|
79
|
-
* while the run lives, and the finally aborts it on ANY settle — a returned/
|
|
80
|
-
* thrown/finished run owns nothing that may keep running (fetch, tools,
|
|
81
|
-
* subprocesses all hang off this one signal).
|
|
82
|
-
* Upstream shape: opencode threads one AbortSignal per task into every model
|
|
83
|
-
* call and tool (session/llm.ts:51,136,321; session/prompt.ts:323,329 with
|
|
84
|
-
* onInterrupt → taskAbort.abort()); codex aborts the active turn task from
|
|
85
|
-
* its Op loop (core/src/tasks/mod.rs:546-591 abort_turn_if_active). */
|
|
86
|
-
export async function* agentLoop(
|
|
87
|
-
def: AgentDefinition,
|
|
88
|
-
goal: string,
|
|
89
|
-
vars: AgentVars,
|
|
90
|
-
cfg: RunConfig,
|
|
91
|
-
deps: LoopDeps,
|
|
92
|
-
steering: SteeringQueue,
|
|
93
|
-
depth = 0,
|
|
94
|
-
followUps?: SteeringQueue,
|
|
95
|
-
): AsyncGenerator<RunEvent> {
|
|
96
|
-
const runAc = new AbortController();
|
|
97
|
-
const follow = () => runAc.abort();
|
|
98
|
-
if (deps.signal?.aborted) runAc.abort();
|
|
99
|
-
else deps.signal?.addEventListener("abort", follow, { once: true });
|
|
100
|
-
// port #29: run-level hooks ride the event stream (core/hooks.ts observer): pre_run / compaction /
|
|
101
|
-
// post_run are awaited BEFORE the event reaches the consumer (post_run must land before a cmdRun
|
|
102
|
-
// exit), on_event is a fire-and-forget tap; timeout + isolation live in the runner, never here
|
|
103
|
-
const obs = deps.hooks?.observer?.({ cwd: deps.cwd ?? process.cwd(), sessionId: deps.store.id });
|
|
104
|
-
try {
|
|
105
|
-
for await (const ev of runLoop(def, goal, vars, cfg, deps, steering, depth, followUps, runAc)) {
|
|
106
|
-
if (obs) await obs.observe(ev);
|
|
107
|
-
yield ev;
|
|
108
|
-
}
|
|
109
|
-
} finally {
|
|
110
|
-
deps.signal?.removeEventListener("abort", follow);
|
|
111
|
-
runAc.abort();
|
|
112
|
-
// fix-wave 4 (#39 MED-1): the CONSUMER closed the generator before run_end (serve disconnect,
|
|
113
|
-
// ACP session/cancel, TUI Esc all .return() it) — the run is over, nobody is listening, and the
|
|
114
|
-
// run-level hooks would never learn it (no OTel export, no reflection sweep). This same loop's
|
|
115
|
-
// teardown tells the observer once; it fires post_run with the abort shape and yields nothing
|
|
116
|
-
// (ADR-003: no second loop, no fabricated event — a yielded run_end makes close() a no-op).
|
|
117
|
-
if (obs) await obs.close();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
122
|
-
|
|
123
|
-
async function* runLoop(
|
|
124
|
-
def: AgentDefinition,
|
|
125
|
-
goal: string,
|
|
126
|
-
vars: AgentVars,
|
|
127
|
-
cfg: RunConfig,
|
|
128
|
-
deps: LoopDeps,
|
|
129
|
-
steering: SteeringQueue,
|
|
130
|
-
depth: number,
|
|
131
|
-
followUps: SteeringQueue | undefined,
|
|
132
|
-
runAc: AbortController,
|
|
133
|
-
): AsyncGenerator<RunEvent> {
|
|
134
|
-
const runId = randomUUID();
|
|
135
|
-
yield { type: "run_start", runId, sessionId: deps.store.id, goal };
|
|
136
|
-
const model: ModelRef = def.model ?? { provider: "mock", model: "default" };
|
|
137
|
-
const history: Message[] = [...deps.store.messages()];
|
|
138
|
-
const runStart = history.length; // the finish check reads this run's transcript only — an aborted call from the last run is not this run's failure
|
|
139
|
-
const userMsg: Message = {
|
|
140
|
-
id: randomUUID(), role: "user",
|
|
141
|
-
parts: [{ kind: "text", text: goal }],
|
|
142
|
-
parentId: history.at(-1)?.id ?? null, createdAt: Date.now(),
|
|
143
|
-
};
|
|
144
|
-
deps.store.append(userMsg);
|
|
145
|
-
history.push(userMsg);
|
|
146
|
-
|
|
147
|
-
const events: RunEvent[] = [];
|
|
148
|
-
const emit = (e: RunEvent) => { events.push(e); };
|
|
149
|
-
const flush = function* (): Generator<RunEvent> { yield* events.splice(0, events.length); };
|
|
150
|
-
|
|
151
|
-
// guard resets once per USER turn — one agentLoop invocation ≈ one hermes
|
|
152
|
-
// run_conversation (reset_for_turn at turn_context.py:700). The `turn` loop
|
|
153
|
-
// below is model iterations; resetting there would wipe the streak and make
|
|
154
|
-
// the guard inert. Follow-ups are new user turns (second reset below).
|
|
155
|
-
deps.guard?.onTurn();
|
|
156
|
-
|
|
157
|
-
// port #25 emergency compaction state: an overflow rejection (error-stop block) arms ONE
|
|
158
|
-
// aggressive compaction + re-drive for the next iteration; at most one re-drive per run. The
|
|
159
|
-
// armed value is the rejected turn's run_end summary — yielded verbatim when the compaction
|
|
160
|
-
// turns out to be a no-op (nothing droppable: re-driving the identical request is pointless)
|
|
161
|
-
let emergencyPending: string | null = null;
|
|
162
|
-
let emergencyRedrives = 0;
|
|
163
|
-
const clock = deps.clock ?? Date.now;
|
|
164
|
-
const startedAt = clock();
|
|
165
|
-
// running spend for RunConfig.maxCostUsd (checked at the turn boundary below)
|
|
166
|
-
let spentUsd = 0, unpricedTurns = 0;
|
|
167
|
-
// the finish check fires at most once per run (see the "done" exit below)
|
|
168
|
-
let nudged = false;
|
|
169
|
-
// the verify gate's memory: the write count the last check saw, and how it ended — a model that answers the
|
|
170
|
-
// failed check with prose (no new write) gets that answer represented, not a second two-minute run
|
|
171
|
-
let verifiedAtWrites = -1;
|
|
172
|
-
let lastVerify: VerifyState | undefined;
|
|
173
|
-
|
|
174
|
-
for (let turn = 1; turn <= cfg.maxTurns; turn++) {
|
|
175
|
-
// --- abort check: an abort that landed during the previous batch (or before
|
|
176
|
-
// turn 1) must not consume steering or touch the provider again
|
|
177
|
-
if (runAc.signal.aborted) { yield { type: "run_end", status: "stopped", summary: "run aborted" }; return; }
|
|
178
|
-
// --- wall clock (RunConfig.maxSeconds): a turn boundary, never mid-tool, so the run ends with every
|
|
179
|
-
// result it already has and the same "budget" status the turn cap uses — a spiral of short verification
|
|
180
|
-
// turns ends in a result object instead of an external kill
|
|
181
|
-
if (cfg.maxSeconds !== undefined && (clock() - startedAt) / 1000 >= cfg.maxSeconds) {
|
|
182
|
-
yield { type: "run_end", status: "budget", summary: `wall clock (${cfg.maxSeconds}s) reached after ${turn - 1} turn${turn === 2 ? "" : "s"}` };
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
// --- spend cap (RunConfig.maxCostUsd): the same boundary, the same status. Priced from each turn's own
|
|
186
|
-
// usage as it lands (below), so the cap is compared against dollars actually spent, never an estimate.
|
|
187
|
-
// Turns the catalog cannot price add nothing; the summary says how many, so "$0.40 spent" is never read
|
|
188
|
-
// as "$0.40 in total" when two turns were unpriced.
|
|
189
|
-
if (cfg.maxCostUsd !== undefined && spentUsd >= cfg.maxCostUsd) {
|
|
190
|
-
const unpriced = unpricedTurns > 0 ? `; ${unpricedTurns} turn${unpricedTurns === 1 ? "" : "s"} unpriced` : "";
|
|
191
|
-
yield { type: "run_end", status: "budget", summary: `cost cap ($${cfg.maxCostUsd.toFixed(2)}) reached after ${turn - 1} turn${turn === 2 ? "" : "s"} — $${spentUsd.toFixed(4)} spent${unpriced}` };
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// --- steering drain point: before the model call ---
|
|
196
|
-
for (const s of steering.drainAll()) {
|
|
197
|
-
const sm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text: s }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
|
|
198
|
-
deps.store.append(sm); history.push(sm);
|
|
199
|
-
yield { type: "steer", text: s };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
yield { type: "turn_start", turn };
|
|
203
|
-
|
|
204
|
-
// --- context assembly + compaction (ADR-007; strategy seam + adaptive trigger: port #25) ---
|
|
205
|
-
// counted over ALL parts (partsTokenText): tool calls/results dominate agentic histories,
|
|
206
|
-
// and a text-only count would keep this trigger permanently below threshold
|
|
207
|
-
const histTokens = history.reduce((n, m) => n + estimateTokens(partsTokenText(m.parts)), 0);
|
|
208
|
-
// speculative: the estimate crossed budget × threshold, before this turn's provider call;
|
|
209
|
-
// emergency: the previous turn was REJECTED as a context overflow (error-stop block) — plan
|
|
210
|
-
// against the observed size and re-drive once (compaction.ts header: senpi/opencode cites)
|
|
211
|
-
const trigger = compactionTrigger(histTokens, cfg, emergencyPending !== null);
|
|
212
|
-
const overflowSummary = emergencyPending;
|
|
213
|
-
emergencyPending = null; // consumed: one compaction per overflow
|
|
214
|
-
if (trigger) {
|
|
215
|
-
const cctx: CompactionCtx = { trigger, tokenText: (m) => partsTokenText(m.parts), summarize: deps.summarize, native: deps.compactNative, model, signal: runAc.signal };
|
|
216
|
-
const plan = planCompaction(history, cfg, cctx);
|
|
217
|
-
const out = plan ? await applyCompaction(history, plan, cfg, cctx) : null;
|
|
218
|
-
if (out) {
|
|
219
|
-
history.length = 0; history.push(...out.history);
|
|
220
|
-
const ev: RunEvent = { type: "compaction", strategy: out.strategy, trigger, tokensBefore: histTokens, tokensAfter: history.reduce((n, m) => n + estimateTokens(partsTokenText(m.parts)), 0) };
|
|
221
|
-
deps.store.appendEvent(ev); // real sessions carry the marker (export + replay), not just fixtures
|
|
222
|
-
yield ev;
|
|
223
|
-
} else if (overflowSummary !== null) {
|
|
224
|
-
// emergency with nothing droppable (a fresh session whose single turn overflows): the
|
|
225
|
-
// re-drive would repeat the rejected request byte-for-byte — end with the provider's error
|
|
226
|
-
yield { type: "run_end", status: "error", summary: overflowSummary };
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const systemText = typeof def.systemPrompt === "function" ? def.systemPrompt(vars) : def.systemPrompt;
|
|
232
|
-
const histNow = history.reduce((n, m) => n + estimateTokens(partsTokenText(m.parts)), 0);
|
|
233
|
-
const chunks: ContextChunk[] = [
|
|
234
|
-
{ name: "system", text: systemText, priority: 100, tokens: estimateTokens(systemText) },
|
|
235
|
-
...(def.contextChunks ?? []), // port #8: e.g. harvested config (priority 70) — evicted before system
|
|
236
|
-
{ name: "history", text: "", priority: 50, tokens: histNow }, // marker; history passed directly below
|
|
237
|
-
];
|
|
238
|
-
const asm = assembleContext(chunks, cfg.contextBudgetTokens);
|
|
239
|
-
if (asm.dropped.length > 0) {
|
|
240
|
-
const droppedTokens = asm.dropped.reduce((n, c) => n + c.tokens, 0);
|
|
241
|
-
yield { type: "compaction", strategy: "context-drop", tokensBefore: asm.totalTokens + droppedTokens, tokensAfter: asm.totalTokens };
|
|
242
|
-
}
|
|
243
|
-
// the system message = kept non-history chunks in priority order (system first);
|
|
244
|
-
// ONE prompt-assembly path — dropped chunks (e.g. config) never reach the wire
|
|
245
|
-
const promptChunks = asm.chunks.filter((c) => c.name !== "history");
|
|
246
|
-
const systemKept = promptChunks.length > 0;
|
|
247
|
-
const promptText = promptChunks.map((c) => c.text).join("\n\n");
|
|
248
|
-
|
|
249
|
-
// --- provider turn (never throws; errors are stopReasons) ---
|
|
250
|
-
// the run signal rides into the StreamFn options: both wire adapters hand it
|
|
251
|
-
// to fetch, so an abort kills the in-flight request itself (≤500ms bar)
|
|
252
|
-
const msgId = randomUUID();
|
|
253
|
-
let turnResult: TurnOutcome;
|
|
254
|
-
const sysMsg: Message = { id: "sys", role: "system", parts: [{ kind: "text", text: promptText }], parentId: null, createdAt: 0 };
|
|
255
|
-
try {
|
|
256
|
-
// request-only: appended to what goes on the wire, never to `history` or the store
|
|
257
|
-
const reminder = deps.planReminder?.(history) ?? null;
|
|
258
|
-
const withReminder: Message[] = reminder === null
|
|
259
|
-
? history
|
|
260
|
-
: [...history, { id: `reminder-${turn}`, role: "user", parts: [{ kind: "text", text: reminder }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() }];
|
|
261
|
-
// deltas yield LIVE, one RunEvent each, while the provider streams. The old shape awaited the
|
|
262
|
-
// whole turn and flushed the buffered message_updates afterwards — a generator cannot yield
|
|
263
|
-
// from a callback — so a 15 s reasoning phase (claude-opus-5 at --effort high, measured) put
|
|
264
|
-
// nothing on screen and read as a hang. Order is unchanged: every delta still precedes turn_end.
|
|
265
|
-
// the run's deadline rides into the provider call so a retry backoff cannot overshoot the clock the turn boundary enforces
|
|
266
|
-
const live = collectTurn(deps.stream, model, systemKept ? [sysMsg, ...withReminder] : withReminder, deps.tools, runAc.signal, cfg.maxSeconds !== undefined ? startedAt + cfg.maxSeconds * 1000 : undefined);
|
|
267
|
-
let reasoning = ""; // the reasoning text stays here: only its estimated size leaves the loop
|
|
268
|
-
for (;;) {
|
|
269
|
-
const step = await live.next();
|
|
270
|
-
if (step.done) { turnResult = step.value; break; }
|
|
271
|
-
if (step.value.type === "text_delta") yield { type: "message_update", messageId: msgId, delta: step.value.text };
|
|
272
|
-
else { reasoning += step.value.text; yield { type: "reasoning_update", messageId: msgId, tokens: estimateTokens(reasoning) }; }
|
|
273
|
-
}
|
|
274
|
-
} catch (e) {
|
|
275
|
-
turnResult = { parts: [], stopReason: "error", usage: { input: 0, output: 0 }, error: e instanceof Error ? e.message : String(e) };
|
|
276
|
-
}
|
|
277
|
-
yield* flush();
|
|
278
|
-
const { parts, stopReason, usage } = turnResult;
|
|
279
|
-
// --- abort landed during the provider turn: the fetch is already dead (or the
|
|
280
|
-
// stream reported "aborted" itself). Keep any partial text the adapter salvaged,
|
|
281
|
-
// but a tool_call in an aborted turn will never execute — append a synthesized
|
|
282
|
-
// failed result for each so the NEXT request carries no orphan tool_calls
|
|
283
|
-
// (codex context_manager/normalize.rs:51-67; opencode message-v2.ts:349-360
|
|
284
|
-
// "Anthropic/Claude APIs require every tool_use to have a corresponding tool_result").
|
|
285
|
-
const aborted = runAc.signal.aborted || stopReason === "aborted";
|
|
286
|
-
const assistant: Message = {
|
|
287
|
-
id: msgId, role: "assistant", parts, parentId: history.at(-1)?.id ?? null,
|
|
288
|
-
// origin = the model that SERVED the turn (router fallback may differ from the one
|
|
289
|
-
// asked for; /cost prices per-message via origin — port #14 HIGH-2), else the request
|
|
290
|
-
createdAt: Date.now(), origin: turnResult.origin ?? model, usage,
|
|
291
|
-
};
|
|
292
|
-
if (cfg.maxCostUsd !== undefined && (usage.input > 0 || usage.output > 0 || (usage.cacheRead ?? 0) > 0 || (usage.cacheWrite ?? 0) > 0)) {
|
|
293
|
-
const c = cfg.priceUsd?.(usage, assistant.origin ?? model);
|
|
294
|
-
if (c === undefined) unpricedTurns++; else spentUsd += c;
|
|
295
|
-
}
|
|
296
|
-
if (!aborted || parts.length > 0) { deps.store.append(assistant); history.push(assistant); }
|
|
297
|
-
yield { type: "turn_end", turn, stopReason: aborted ? "aborted" : stopReason };
|
|
298
|
-
if (aborted) {
|
|
299
|
-
for (const p of parts) {
|
|
300
|
-
if (p.kind === "tool_call") appendToolResult(deps.store, history, p.id, { ok: false, output: ABORTED_TOOL_RESULT });
|
|
301
|
-
}
|
|
302
|
-
yield { type: "run_end", status: "stopped", summary: "run aborted" };
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// --- error stops: the run ends in 'error', never a fake 'done' ---
|
|
307
|
-
if (stopReason === "error") {
|
|
308
|
-
const errText = turnResult.error ?? "provider stream failed";
|
|
309
|
-
const partial = partsText(parts);
|
|
310
|
-
const summary = partial ? `${partial}\nerror: ${errText}` : `error: ${errText}`;
|
|
311
|
-
// port #25: a context-overflow rejection arms an emergency compaction (next iteration's
|
|
312
|
-
// compaction block) and re-drives ONCE per run — only while a turn is left to re-drive in
|
|
313
|
-
// (on the last permitted turn the run ends HERE with the provider's text, not as "budget");
|
|
314
|
-
// a second overflow ends the run below
|
|
315
|
-
if (isContextOverflow(errText) && emergencyRedrives === 0 && turn < cfg.maxTurns) { emergencyRedrives++; emergencyPending = summary; continue; }
|
|
316
|
-
yield { type: "run_end", status: "error", summary };
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const { calls, truncated } = extractToolCalls(parts, stopReason);
|
|
321
|
-
if (truncated) {
|
|
322
|
-
// fail all calls unexecuted — args may be silently truncated JSON.
|
|
323
|
-
// still append tool_result{ok:false} so the next provider request stays valid
|
|
324
|
-
const detail = "response hit length limit; tool calls not executed";
|
|
325
|
-
for (const c of calls) {
|
|
326
|
-
yield { type: "tool_call_failed", callId: c.id, reason: "truncated", detail };
|
|
327
|
-
appendToolResult(deps.store, history, c.id, { ok: false, output: detail });
|
|
328
|
-
}
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
if (calls.length === 0) {
|
|
332
|
-
// --- follow-up drain point: a queued follow-up continues the run instead of ending it ---
|
|
333
|
-
const follow = followUps ? followUps.drainAll() : [];
|
|
334
|
-
if (follow.length > 0) {
|
|
335
|
-
deps.guard?.onTurn(); // a follow-up is a new user turn (upstream: new run_conversation → reset)
|
|
336
|
-
for (const f of follow) {
|
|
337
|
-
const fm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text: f }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
|
|
338
|
-
deps.store.append(fm); history.push(fm);
|
|
339
|
-
yield { type: "steer", text: f };
|
|
340
|
-
}
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
// --- the "done" exit. This is the ONLY path to status "done", and until now its whole condition was
|
|
344
|
-
// "this turn had no tool call" — the model's silence, ratified as completion. A write that failed, an
|
|
345
|
-
// ask_user nobody answered, a run that changed nothing: all ended here as "done", exit 0, and no
|
|
346
|
-
// surface could tell them from finished work (found 2026-09-06: "Done — I created src/a.ts" after the
|
|
347
|
-
// write was rejected). Two things now happen here, and they are separate:
|
|
348
|
-
// 1. REPRESENT: run_end carries `outstanding` — what the transcript says was left (assessOutstanding).
|
|
349
|
-
// Unconditional, so a surface can always say "done · 1 tool call failed in the last turn".
|
|
350
|
-
// 2. NUDGE, once per run, on evidence the transcripts actually contain (a failed tool call in the last
|
|
351
|
-
// turn, an unanswered question — never "no files changed", which is what every answered question
|
|
352
|
-
// looks like): one user-role turn naming exactly what is open and asking the model to finish it or
|
|
353
|
-
// say why it is not needed. Both are acceptable answers. The next silence is accepted whatever it
|
|
354
|
-
// says: a loop that will not stop is worse than one that stops early. The nudge is a turn like any
|
|
355
|
-
// other — the turn, clock and cost ceilings above apply to it, so a spent budget ends as "budget".
|
|
356
|
-
const outstanding = assessOutstanding(history.slice(runStart), cfg.todoState?.() ?? null, nudged);
|
|
357
|
-
if (cfg.finishCheck !== false && !nudged && (outstanding.failed.length > 0 || outstanding.unansweredAsk)) {
|
|
358
|
-
nudged = true;
|
|
359
|
-
const text = finishCheckText(outstanding);
|
|
360
|
-
const fm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
|
|
361
|
-
deps.store.append(fm); history.push(fm);
|
|
362
|
-
yield { type: "steer", text };
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
// --- the verify gate (core/verify-gate.ts): a run that wrote files runs the project's own check before it
|
|
366
|
-
// may end "done". Composition with the finish check above is one flag, not two: whichever fires first takes
|
|
367
|
-
// the run's single extra turn; if the finish check already fired, the gate still RUNS and represents, but
|
|
368
|
-
// never asks again. Nothing configured → nothing runs, and run_end says so ("not verified") rather than
|
|
369
|
-
// letting an unchecked change look finished. Nothing written → the gate is not consulted at all.
|
|
370
|
-
if (cfg.verify !== undefined && outstanding.writes > 0) {
|
|
371
|
-
const gate = cfg.verify;
|
|
372
|
-
const refused = gate.resolution?.refused && gate.resolution.refused.length > 0 ? { refused: gate.resolution.refused } : {};
|
|
373
|
-
if (gate.resolution === null || gate.resolution.commands.length === 0) {
|
|
374
|
-
outstanding.verify = { state: "unconfigured", ...(gate.resolution?.reason ? { reason: gate.resolution.reason } : {}), ...refused };
|
|
375
|
-
} else if (outstanding.writes === verifiedAtWrites && lastVerify !== undefined) {
|
|
376
|
-
outstanding.verify = lastVerify; // nothing written since that check: its verdict stands, it is not re-run
|
|
377
|
-
} else {
|
|
378
|
-
yield { type: "verify", command: gate.resolution.commands.join(" && "), state: "running" };
|
|
379
|
-
const r = await gate.run(runAc.signal);
|
|
380
|
-
if (runAc.signal.aborted) { yield { type: "run_end", status: "stopped", summary: "run aborted" }; return; }
|
|
381
|
-
verifiedAtWrites = outstanding.writes;
|
|
382
|
-
const state: VerifyState = r.ok ? { state: "passed", command: r.command, ms: r.ms, ...refused }
|
|
383
|
-
: r.timedOut ? { state: "timeout", command: r.command, seconds: Math.round(gate.timeoutMs / 1000), ...refused }
|
|
384
|
-
: { state: "failed", command: r.command, code: r.code, failure: r.failure, ...refused };
|
|
385
|
-
lastVerify = state;
|
|
386
|
-
yield { type: "verify", command: r.command, state: state.state, ms: r.ms, detail: verifyDetail(r) };
|
|
387
|
-
if (!r.ok && !nudged) {
|
|
388
|
-
nudged = true;
|
|
389
|
-
const text = verifyNudgeText(r, gate.timeoutMs);
|
|
390
|
-
const fm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
|
|
391
|
-
deps.store.append(fm); history.push(fm);
|
|
392
|
-
yield { type: "steer", text };
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
outstanding.verify = state;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
// attached only when there IS something to say: a run that answered a question (no files, no failures) ends
|
|
399
|
-
// byte-identical to before — "no files changed" is what every answered question looks like, not a finding
|
|
400
|
-
const notable = outstanding.failed.length > 0 || outstanding.unansweredAsk || (outstanding.todosOpen ?? 0) > 0 || outstanding.nudged || outstanding.verify !== undefined;
|
|
401
|
-
yield { type: "run_end", status: "done", summary: partsText(parts), ...(notable ? { outstanding } : {}) };
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// --- tool execution: ctx.signal IS the run controller's signal, so the ONE
|
|
406
|
-
// per-run controller reaches child processes by identity (executor G4 seam)
|
|
407
|
-
const ctx: ToolContext = {
|
|
408
|
-
sessionId: deps.store.id, cwd: deps.cwd ?? process.cwd(), signal: runAc.signal, runId,
|
|
409
|
-
spawn: undefined, permissions: { effect: "allow" },
|
|
410
|
-
};
|
|
411
|
-
const runChild = deps.childRunner;
|
|
412
|
-
if (runChild) {
|
|
413
|
-
ctx.spawn = async (req) => {
|
|
414
|
-
const r = await runChild(req.agent, req.goal, req.vars, depth + 1);
|
|
415
|
-
return { agent: req.agent, ok: r.ok, summary: r.summary, usage: r.usage };
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
let results = new Map<string, ToolOutput>();
|
|
419
|
-
let batchSettled = false;
|
|
420
|
-
try {
|
|
421
|
-
const batch = deps.registry.dispatchBatch(calls, ctx, deps.hooks, cfg.permissionRules, cfg.approval, emit, cfg.parallelTools, deps.guard);
|
|
422
|
-
// stream batch events while tools run; each yield is also a suspension
|
|
423
|
-
// point where a consumer .return()/.throw() lands. A mid-batch abort
|
|
424
|
-
// breaks the pump so the run settles even if a tool ignores its signal.
|
|
425
|
-
let settled = false;
|
|
426
|
-
void batch.then(() => { settled = true; }, () => { settled = true; });
|
|
427
|
-
while (!settled && !runAc.signal.aborted) {
|
|
428
|
-
await Promise.race([batch, sleep(5)]);
|
|
429
|
-
if (events.length > 0) yield* flush();
|
|
430
|
-
}
|
|
431
|
-
// aborted mid-batch: give the killed tools one bounded beat to report their
|
|
432
|
-
// real (error) outputs before results are synthesized — opencode waits the
|
|
433
|
-
// same way (session/processor.ts:571-575, 250ms) before marking calls interrupted
|
|
434
|
-
if (!settled && runAc.signal.aborted) await Promise.race([batch, sleep(250)]);
|
|
435
|
-
if (settled) { results = await batch; batchSettled = true; }
|
|
436
|
-
if (events.length > 0) yield* flush();
|
|
437
|
-
} finally {
|
|
438
|
-
// Every exit — normal completion, consumer .return()/.throw() at a pump
|
|
439
|
-
// yield, or a mid-batch abort — leaves the store WIRE-WELL-FORMED: the
|
|
440
|
-
// assistant message carrying these tool_calls is already appended, so each
|
|
441
|
-
// call gets a tool_result here. Ones the batch never delivered are
|
|
442
|
-
// synthesized as failed (upstream policy: codex normalize.rs:51-67,
|
|
443
|
-
// opencode message-v2.ts:349-360 + processor.ts:576-593).
|
|
444
|
-
if (!batchSettled) runAc.abort(); // consumer left mid-batch: kill in-flight tools + subprocess trees
|
|
445
|
-
for (const c of calls) {
|
|
446
|
-
appendToolResult(deps.store, history, c.id, results.get(c.id) ?? { ok: false, output: batchSettled ? "missing result" : ABORTED_TOOL_RESULT });
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (runAc.signal.aborted) { yield { type: "run_end", status: "stopped", summary: "run aborted" }; return; }
|
|
450
|
-
}
|
|
451
|
-
yield { type: "run_end", status: "budget", summary: `max turns (${cfg.maxTurns}) reached` };
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/** What the transcript says the run left behind, read at the "done" exit. `failed`/`unansweredAsk` look at
|
|
455
|
-
* the LAST turn that had tool calls — the one right before the final text — because that is the failure
|
|
456
|
-
* the model walked away from; an earlier failure it went on to retry is not outstanding. `writes` counts
|
|
457
|
-
* successful edit/write calls over the whole run. Pure over the history: no disk, no model. */
|
|
458
|
-
export function assessOutstanding(history: readonly Message[], todos: { open: number; total: number } | null, nudged: boolean): RunOutstanding {
|
|
459
|
-
type Call = Extract<MessagePart, { kind: "tool_call" }>;
|
|
460
|
-
const results = new Map<string, { ok: boolean; output: string }>();
|
|
461
|
-
for (const m of history) if (m.role === "tool") for (const p of m.parts) if (p.kind === "tool_result") results.set(p.callId, { ok: p.ok, output: p.output });
|
|
462
|
-
const describe = (c: Call, output: string): string => `${c.tool}: ${(output.split("\n")[0] ?? "").slice(0, 160)}`;
|
|
463
|
-
const pathOf = (c: Call): string | null => { const a = c.args as { path?: unknown; file_path?: unknown } | null; const v = a?.path ?? a?.file_path; return typeof v === "string" ? v : null; };
|
|
464
|
-
let writes = 0;
|
|
465
|
-
let lastCalls: Call[] = [];
|
|
466
|
-
// a file the model tried to change and never managed to: a failed edit/write to a path with no later successful
|
|
467
|
-
// edit/write to the same path. Found in the scripted proof — write fails, todo_write marks the item completed,
|
|
468
|
-
// "Done": the failure was one turn before the last, masked by bookkeeping, and "last turn only" let it through
|
|
469
|
-
const unrecovered = new Map<string, string>();
|
|
470
|
-
for (const m of history) {
|
|
471
|
-
if (m.role !== "assistant") continue;
|
|
472
|
-
const calls = m.parts.filter((p): p is Call => p.kind === "tool_call");
|
|
473
|
-
if (calls.length === 0) continue;
|
|
474
|
-
lastCalls = calls;
|
|
475
|
-
for (const c of calls) {
|
|
476
|
-
if (c.tool !== "edit" && c.tool !== "write") continue;
|
|
477
|
-
const r = results.get(c.id); const path = pathOf(c) ?? c.id;
|
|
478
|
-
if (r?.ok === true) { writes++; unrecovered.delete(path); }
|
|
479
|
-
else if (r && !harnessVerdict(r.output)) unrecovered.set(path, describe(c, r.output));
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
const failed = [...unrecovered.values()];
|
|
483
|
-
let unansweredAsk = false;
|
|
484
|
-
for (const c of lastCalls) {
|
|
485
|
-
const r = results.get(c.id);
|
|
486
|
-
if (r === undefined || r.ok) continue;
|
|
487
|
-
if (c.tool === "ask_user") { unansweredAsk = true; continue; }
|
|
488
|
-
if (harnessVerdict(r.output)) continue;
|
|
489
|
-
const d = describe(c, r.output);
|
|
490
|
-
if (!failed.includes(d)) failed.push(d);
|
|
491
|
-
}
|
|
492
|
-
return { failed, unansweredAsk, writes, ...(todos && todos.total > 0 ? { todosOpen: todos.open, todosTotal: todos.total } : {}), nudged };
|
|
493
|
-
}
|
|
494
|
-
/** ok:false results that are the harness's or the user's decision, not the model's failure: a permission denial,
|
|
495
|
-
* the loop guard's stub (it TOLD the model to stop repeating), an abort. Nudging "continue" past any of these would
|
|
496
|
-
* argue with the thing that stopped the call. */
|
|
497
|
-
function harnessVerdict(output: string): boolean {
|
|
498
|
-
return output.startsWith("Permission denied") || output.includes("loop guard: blocked") || output.startsWith(ABORTED_TOOL_RESULT);
|
|
499
|
-
}
|
|
500
|
-
export function finishCheckText(o: RunOutstanding): string {
|
|
501
|
-
const lines = ["<finish-check>", "You stopped, but this run is not in a finished state — this is a check by the harness, not a message from the user."];
|
|
502
|
-
for (const f of o.failed) lines.push(`- a tool call failed and nothing after it recovered from that: ${f}`);
|
|
503
|
-
if (o.unansweredAsk) lines.push("- your question to the user was not answered (there is no one to answer it in this run); decide with your best judgment instead of waiting");
|
|
504
|
-
if ((o.todosOpen ?? 0) > 0) lines.push(`- your own todo list still has ${o.todosOpen} of ${o.todosTotal} items open`);
|
|
505
|
-
lines.push("Either continue and finish the work now, or say plainly what is left undone and why it is not needed. This check runs once per run; your next reply ends it either way.", "</finish-check>");
|
|
506
|
-
return lines.join("\n");
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/** The one clause a surface appends to "done" — null when there is nothing a person needs to act on.
|
|
510
|
-
* "no files changed" is deliberately NOT here: every answered question looks like that, and a warning that
|
|
511
|
-
* fires on "what MCPs are configured" trains people to stop reading it. Headless text mode adds it itself,
|
|
512
|
-
* where a one-shot run that changed nothing is the whole complaint. */
|
|
513
|
-
export function outstandingClause(o: RunOutstanding): string | null {
|
|
514
|
-
const parts: string[] = [];
|
|
515
|
-
if (o.failed.length > 0) parts.push(`${o.failed.length} failed tool call${o.failed.length === 1 ? "" : "s"} not recovered (${[...new Set(o.failed.map((f) => f.split(":")[0]))].join(", ")})`);
|
|
516
|
-
if (o.unansweredAsk) parts.push("its question to you went unanswered");
|
|
517
|
-
if ((o.todosOpen ?? 0) > 0) parts.push(`${o.todosOpen} of ${o.todosTotal} items still open`);
|
|
518
|
-
if (o.verify !== undefined) parts.push(verifyClause(o.verify));
|
|
519
|
-
return parts.length > 0 ? parts.join(" · ") : null;
|
|
520
|
-
}
|
|
521
|
-
/** how a surface should colour the clause: a passed check is information, everything else is a warning */
|
|
522
|
-
export function outstandingTone(o: RunOutstanding): "info" | "warn" {
|
|
523
|
-
const onlyPassed = o.failed.length === 0 && !o.unansweredAsk && (o.todosOpen ?? 0) === 0 && o.verify?.state === "passed";
|
|
524
|
-
return onlyPassed ? "info" : "warn";
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function appendToolResult(store: SessionStore, history: Message[], callId: string, out: { ok: boolean; output: string }): void {
|
|
528
|
-
const rm: Message = {
|
|
529
|
-
id: randomUUID(), role: "tool",
|
|
530
|
-
parts: [{ kind: "tool_result", callId, ok: out.ok, output: out.output }],
|
|
531
|
-
parentId: history.at(-1)?.id ?? null, createdAt: Date.now(),
|
|
532
|
-
};
|
|
533
|
-
store.append(rm); history.push(rm);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
export interface TurnOutcome {
|
|
537
|
-
parts: MessagePart[]; stopReason: StopReason; usage: TokenUsage; error?: string;
|
|
538
|
-
/** model that actually SERVED the turn (router servedBy tag) — unset for unwrapped streams */
|
|
539
|
-
origin?: ModelRef;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/** Drive one provider turn: yields the text and reasoning deltas as they arrive (the caller turns
|
|
543
|
-
* them into RunEvents), returns the terminal turn as the outcome. tool_call_delta is not surfaced. */
|
|
544
|
-
async function* collectTurn(stream: StreamFn, model: ModelRef, messages: Message[], tools?: ToolSchema[], signal?: AbortSignal, deadlineAt?: number): AsyncGenerator<Extract<StreamEvent, { type: "text_delta" | "reasoning_delta" }>, TurnOutcome> {
|
|
545
|
-
let outcome: TurnOutcome = { parts: [], stopReason: "end_turn", usage: { input: 0, output: 0 } };
|
|
546
|
-
for await (const ev of stream(model, messages, { tools, signal, ...(deadlineAt !== undefined ? { deadlineAt } : {}) })) {
|
|
547
|
-
if (ev.type === "text_delta" || ev.type === "reasoning_delta") yield ev;
|
|
548
|
-
else if (ev.type === "turn") { outcome = { parts: ev.turn.parts, stopReason: ev.turn.stopReason, usage: ev.turn.usage, error: ev.turn.error, origin: servedBy(ev.turn) }; }
|
|
549
|
-
}
|
|
550
|
-
return outcome;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
export function partsText(parts: MessagePart[]): string {
|
|
554
|
-
return parts.filter((p) => p.kind === "text").map((p) => (p as { text: string }).text).join("");
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/** Token-bearing text of ALL parts — text, tool_call args, tool_result outputs. Context-size
|
|
558
|
-
* accounting must see what the provider sees: a tool-heavy history counted text-only reads
|
|
559
|
-
* near zero and never trips compaction / context-health. */
|
|
560
|
-
export function partsTokenText(parts: MessagePart[]): string {
|
|
561
|
-
return parts.map((p) =>
|
|
562
|
-
p.kind === "text" ? p.text
|
|
563
|
-
: p.kind === "tool_call" ? `${p.tool} ${JSON.stringify(p.args)}`
|
|
564
|
-
: p.kind === "tool_result" ? p.output
|
|
565
|
-
: "", // port #34 image parts carry no token text (image token cost is provider-specific; not estimated here)
|
|
566
|
-
).join("\n");
|
|
567
|
-
}
|