rovecode 0.4.0-beta.2 → 0.4.0
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/README.md +67 -69
- package/THIRD_PARTY_NOTICES.md +0 -44
- package/bin/rovecode.ts +21 -0
- package/package.json +16 -37
- package/src/account/keys.ts +97 -0
- package/src/account/login.ts +158 -0
- package/src/account/provision.ts +47 -0
- package/src/account/store.ts +63 -0
- package/src/acp/server.ts +373 -0
- package/src/cli/account-cmd.ts +116 -0
- package/src/cli/connect.ts +244 -0
- package/src/cli/context-cmd.ts +199 -0
- package/src/cli/dispatch.ts +109 -0
- package/src/cli/doctor.ts +324 -0
- package/src/cli/export.ts +278 -0
- package/src/cli/help.ts +240 -0
- package/src/cli/is-tui-invocation.ts +8 -0
- package/src/cli/main.ts +599 -0
- package/src/cli/market-cmd.ts +658 -0
- package/src/cli/mcp-market-cmd.ts +299 -0
- package/src/cli/output.ts +382 -0
- package/src/cli/repl.ts +172 -0
- package/src/cli/resume.ts +32 -0
- package/src/cli/run-limits.ts +78 -0
- package/src/cli/runtime.ts +792 -0
- package/src/cli/setup.ts +187 -0
- package/src/cli/update-cmd.ts +78 -0
- package/src/cli/workflow-cmd.ts +100 -0
- package/src/coding/checkpoints.ts +270 -0
- package/src/coding/diff.ts +136 -0
- package/src/coding/files.ts +339 -0
- package/src/coding/hashline.ts +319 -0
- package/src/coding/lsp.ts +406 -0
- package/src/coding/repomap-cache.ts +99 -0
- package/src/coding/repomap-files.ts +110 -0
- package/src/coding/repomap.ts +392 -0
- package/src/core/compaction.ts +399 -0
- package/src/core/config.ts +289 -0
- package/src/core/context-report.ts +228 -0
- package/src/core/context.ts +60 -0
- package/src/core/count-remote.ts +107 -0
- package/src/core/execpolicy-rules.ts +196 -0
- package/src/core/execpolicy.ts +385 -0
- package/src/core/executor.ts +397 -0
- package/src/core/guardrails.ts +400 -0
- package/src/core/hooks.ts +398 -0
- package/src/core/images.ts +230 -0
- package/src/core/intro.ts +236 -0
- package/src/core/loop.ts +621 -0
- package/src/core/modes.ts +372 -0
- package/src/core/orchestrator.ts +207 -0
- package/src/core/reflection.ts +165 -0
- package/src/core/sandbox-config.ts +167 -0
- package/src/core/session-images.ts +73 -0
- package/src/core/session.ts +398 -0
- package/src/core/settings.ts +98 -0
- package/src/core/stuck-detector.ts +273 -0
- package/src/core/tasks.ts +374 -0
- package/src/core/token-scale.ts +108 -0
- package/src/core/tool-output-budget.ts +166 -0
- package/src/core/tools.ts +288 -0
- package/src/core/types.ts +330 -0
- package/src/core/update-check.ts +171 -0
- package/src/core/update.ts +158 -0
- package/src/core/usage.ts +204 -0
- package/src/core/validate.ts +121 -0
- package/src/core/verify-gate.ts +159 -0
- package/src/core/verify.ts +237 -0
- package/src/core/voice.ts +158 -0
- package/src/core/win-job.ts +183 -0
- package/src/design/audit.ts +797 -0
- package/src/design/direction.ts +190 -0
- package/src/design/rules.ts +157 -0
- package/src/eval/bench.ts +150 -0
- package/src/eval/gauntlet-runner.ts +218 -0
- package/src/eval/gauntlet.ts +226 -0
- package/src/eval/grader.ts +186 -0
- package/src/eval/record.ts +202 -0
- package/src/eval/redact.ts +141 -0
- package/src/eval/replay.ts +147 -0
- package/src/eval/trajectory.ts +373 -0
- package/src/index.ts +17 -0
- package/src/market/catalogs/mcp-docs.json +111 -0
- package/src/market/catalogs/plugins.json +111 -0
- package/src/market/catalogs/skills.json +478 -0
- package/src/market/clone.ts +72 -0
- package/src/market/context-cost.ts +121 -0
- package/src/market/digest.ts +106 -0
- package/src/market/index.ts +22 -0
- package/src/market/install.ts +578 -0
- package/src/market/manifest.ts +187 -0
- package/src/market/prereq.ts +145 -0
- package/src/market/registry.ts +363 -0
- package/src/market/resolve.ts +111 -0
- package/src/market/types.ts +236 -0
- package/src/market/validate.ts +227 -0
- package/src/mcp/client.ts +431 -0
- package/src/mcp/config.ts +239 -0
- package/src/mcp/local-package.ts +211 -0
- package/src/mcp/market-catalog.ts +84 -0
- package/src/mcp/market-install.ts +289 -0
- package/src/mcp/market.ts +0 -0
- package/src/mcp/tools.ts +131 -0
- package/src/mcp/trust.ts +49 -0
- package/src/memory/blocks.ts +175 -0
- package/src/memory/recall.ts +355 -0
- package/src/memory/store.ts +105 -0
- package/src/memory/tools.ts +99 -0
- package/src/plugins/cli.ts +123 -0
- package/src/plugins/discover.ts +108 -0
- package/src/plugins/index.ts +50 -0
- package/src/plugins/init.ts +140 -0
- package/src/plugins/install.ts +184 -0
- package/src/plugins/load.ts +149 -0
- package/src/plugins/manifest.ts +106 -0
- package/src/plugins/state.ts +83 -0
- package/src/providers/auth.ts +293 -0
- package/src/providers/cache.ts +223 -0
- package/src/providers/catalog-local.ts +160 -0
- package/src/providers/catalog.ts +408 -0
- package/src/providers/middleware-context.ts +86 -0
- package/src/providers/middleware.ts +373 -0
- package/src/providers/profile-glm53.ts +111 -0
- package/src/providers/profile-sonnet5-persona.ts +65 -0
- package/src/providers/profile-sonnet5-voice.ts +23 -0
- package/src/providers/profiles.ts +156 -0
- package/src/providers/provider-config.ts +311 -0
- package/src/providers/registry.ts +302 -0
- package/src/providers/response-validation.ts +80 -0
- package/src/providers/retry.ts +234 -0
- package/src/providers/router.ts +294 -0
- package/src/providers/sse.ts +26 -0
- package/src/providers/stream-errors.ts +117 -0
- package/src/providers/stream.ts +569 -0
- package/src/providers/thinking.ts +189 -0
- package/src/providers/wire-messages.ts +129 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/index.ts +3 -0
- package/src/server/dashboard.ts +144 -0
- package/src/server/http.ts +343 -0
- package/src/server/openapi.ts +246 -0
- package/src/sextant/card-hits.ts +102 -0
- package/src/sextant/card-keys.ts +55 -0
- package/src/sextant/context-source.ts +157 -0
- package/src/sextant/draw-agents.ts +273 -0
- package/src/sextant/draw-code.ts +388 -0
- package/src/sextant/draw-context.ts +222 -0
- package/src/sextant/draw-frame.ts +164 -0
- package/src/sextant/draw-market.ts +573 -0
- package/src/sextant/draw-messages.ts +386 -0
- package/src/sextant/draw-pet.ts +230 -0
- package/src/sextant/draw-plan.ts +159 -0
- package/src/sextant/draw-tabs.ts +85 -0
- package/src/sextant/draw-util.ts +65 -0
- package/src/sextant/engine.ts +230 -0
- package/src/sextant/frame-hits.ts +25 -0
- package/src/sextant/frame.ts +101 -0
- package/src/sextant/git-status.ts +197 -0
- package/src/sextant/grid.ts +59 -0
- package/src/sextant/input.ts +119 -0
- package/src/sextant/keys.ts +488 -0
- package/src/sextant/layout.ts +86 -0
- package/src/sextant/local-commands.ts +156 -0
- package/src/sextant/market-source.ts +287 -0
- package/src/sextant/mentions.ts +141 -0
- package/src/sextant/message-hits.ts +26 -0
- package/src/sextant/model.ts +387 -0
- package/src/sextant/overlays.ts +451 -0
- package/src/sextant/panel-hits.ts +38 -0
- package/src/sextant/pet.ts +399 -0
- package/src/sextant/screen.ts +324 -0
- package/src/sextant/scroll-hits.ts +66 -0
- package/src/sextant/scrollbar.ts +82 -0
- package/src/sextant/selection.ts +123 -0
- package/src/sextant/sextant-bridge.ts +174 -0
- package/src/sextant/sextant-cards.ts +142 -0
- package/src/sextant/sextant-diff-base.ts +63 -0
- package/src/sextant/sextant-files.ts +154 -0
- package/src/sextant/sextant-frame-loop.ts +314 -0
- package/src/sextant/sextant-renderer.ts +478 -0
- package/src/sextant/sextant-repo.ts +131 -0
- package/src/sextant/theme.ts +66 -0
- package/src/sextant/tool-rows.ts +189 -0
- package/src/sextant/types.ts +473 -0
- package/src/skills/index.ts +306 -0
- package/src/skills/tools.ts +69 -0
- package/src/skills/versioned.ts +227 -0
- package/src/telemetry/otel.ts +353 -0
- package/src/telemetry/otlp.ts +68 -0
- package/src/tools/ask-user.ts +156 -0
- package/src/tools/design.ts +151 -0
- package/src/tools/evalcell.ts +338 -0
- package/src/tools/html-text.ts +139 -0
- package/src/tools/provider.ts +149 -0
- package/src/tools/task.ts +216 -0
- package/src/tools/todo.ts +320 -0
- package/src/tools/webfetch.ts +331 -0
- package/src/tui/app.ts +608 -0
- package/src/tui/attach.ts +127 -0
- package/src/tui/checkpoints-cmd.ts +70 -0
- package/src/tui/clipboard-image.ts +81 -0
- package/src/tui/commands.ts +277 -0
- package/src/tui/cost.ts +108 -0
- package/src/tui/info-cmd.ts +144 -0
- package/src/tui/mcp-cmd.ts +128 -0
- package/src/tui/modes-cmd.ts +45 -0
- package/src/tui/overlays.ts +97 -0
- package/src/tui/pi-renderer.ts +424 -0
- package/src/tui/providers-cmd.ts +366 -0
- package/src/tui/renderer.ts +101 -0
- package/src/tui/replay-marker.ts +29 -0
- package/src/tui/session-cmd.ts +146 -0
- package/src/tui/sextant-attach.ts +68 -0
- package/src/tui/sextant-io.ts +184 -0
- package/src/tui/sextant-smoke.ts +110 -0
- package/src/tui/smoke.ts +72 -0
- package/src/tui/theme.ts +59 -0
- package/src/tui/todo-label.ts +7 -0
- package/src/workflow/engine.ts +266 -0
- package/tsconfig.json +30 -0
- package/vendor/pi-tui/LICENSE +21 -0
- package/vendor/pi-tui/PATCHES.md +12 -0
- package/vendor/pi-tui/PROVENANCE.md +12 -0
- package/vendor/pi-tui/README.upstream.md +854 -0
- 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 +158 -0
- package/vendor/pi-tui/src/autocomplete.ts +827 -0
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
- package/vendor/pi-tui/src/components/box.ts +138 -0
- package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
- package/vendor/pi-tui/src/components/editor.ts +2364 -0
- package/vendor/pi-tui/src/components/h-stack.ts +45 -0
- package/vendor/pi-tui/src/components/image.ts +128 -0
- package/vendor/pi-tui/src/components/input.ts +448 -0
- package/vendor/pi-tui/src/components/loader.ts +93 -0
- package/vendor/pi-tui/src/components/markdown.ts +1016 -0
- package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
- package/vendor/pi-tui/src/components/select-list.ts +230 -0
- package/vendor/pi-tui/src/components/settings-list.ts +277 -0
- package/vendor/pi-tui/src/components/spacer.ts +29 -0
- package/vendor/pi-tui/src/components/stack.ts +155 -0
- package/vendor/pi-tui/src/components/text.ts +108 -0
- package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
- package/vendor/pi-tui/src/components/v-stack.ts +34 -0
- package/vendor/pi-tui/src/editor-component.ts +75 -0
- package/vendor/pi-tui/src/fuzzy.ts +138 -0
- package/vendor/pi-tui/src/index.ts +149 -0
- package/vendor/pi-tui/src/keybindings.ts +321 -0
- package/vendor/pi-tui/src/keys.ts +1402 -0
- package/vendor/pi-tui/src/kill-ring.ts +47 -0
- package/vendor/pi-tui/src/latex.ts +1381 -0
- package/vendor/pi-tui/src/layout-node.ts +52 -0
- package/vendor/pi-tui/src/layout.ts +411 -0
- package/vendor/pi-tui/src/native-modifiers.ts +60 -0
- package/vendor/pi-tui/src/native-module-path.ts +32 -0
- package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
- package/vendor/pi-tui/src/terminal-colors.ts +74 -0
- package/vendor/pi-tui/src/terminal-image.ts +701 -0
- package/vendor/pi-tui/src/terminal.ts +554 -0
- package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
- package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
- package/vendor/pi-tui/src/tui.ts +1264 -0
- package/vendor/pi-tui/src/undo-stack.ts +29 -0
- package/vendor/pi-tui/src/utils.ts +1327 -0
- package/vendor/pi-tui/src/word-navigation.ts +118 -0
- package/vendor/pi-tui/test/test-themes.ts +39 -0
- package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
- package/CHANGELOG.md +0 -512
- package/bin/rovecode.js +0 -24
- package/dist/cli/app-dybnr56b.js +0 -2
- package/dist/cli/ask-user-p8hq4xgj.js +0 -2
- package/dist/cli/auth-login-ewpgw5sm.js +0 -2
- package/dist/cli/auth-m8p9grty.js +0 -2
- package/dist/cli/bench-xv3ypwev.js +0 -9
- package/dist/cli/catalog-737wb2s0.js +0 -2
- package/dist/cli/cli-arhg40m0.js +0 -2
- package/dist/cli/client-cf2pxx8q.js +0 -2
- package/dist/cli/commands-3p7e4xxs.js +0 -2
- package/dist/cli/connect-3q93d7cb.js +0 -2
- package/dist/cli/context-cmd-eqxmxhzq.js +0 -2
- package/dist/cli/context-report-hbw9zfes.js +0 -2
- package/dist/cli/count-remote-mby98cd0.js +0 -2
- package/dist/cli/design-122y0axd.js +0 -2
- package/dist/cli/dispatch-b4egzvvh.js +0 -2
- package/dist/cli/doctor-x4jkv72e.js +0 -3
- package/dist/cli/executor-ftvg6tsy.js +0 -2
- package/dist/cli/export-pdgdhkch.js +0 -2
- package/dist/cli/files-cez9a96p.js +0 -2
- package/dist/cli/gauntlet-r3xxaszc.js +0 -2
- package/dist/cli/gauntlet-runner-r515m7kk.js +0 -10
- package/dist/cli/gauntlet-wave3-bnkjk2v2.js +0 -5
- package/dist/cli/gauntlet-wave4-acs9s60q.js +0 -14
- package/dist/cli/hashline-ewg5hbe3.js +0 -2
- package/dist/cli/http-n0kehsk8.js +0 -5
- package/dist/cli/index-z5qt1s76.js +0 -2
- package/dist/cli/install-80mp63kx.js +0 -2
- package/dist/cli/loop-12twjcat.js +0 -2
- package/dist/cli/main-01pv9206.js +0 -4
- package/dist/cli/main-0jys2ccn.js +0 -3
- package/dist/cli/main-1ztz6fkj.js +0 -10
- package/dist/cli/main-23q7cmww.js +0 -9
- package/dist/cli/main-2rzbexn2.js +0 -3
- package/dist/cli/main-2wyax8k9.js +0 -9
- package/dist/cli/main-2yeveeve.js +0 -6
- package/dist/cli/main-2z3dek0b.js +0 -3
- package/dist/cli/main-2zgsknth.js +0 -3
- package/dist/cli/main-45ejth3a.js +0 -4
- package/dist/cli/main-45rn3trk.js +0 -22
- package/dist/cli/main-4p4e2w7x.js +0 -4
- package/dist/cli/main-4y0tnfpa.js +0 -16
- package/dist/cli/main-5py0rkmc.js +0 -4
- package/dist/cli/main-6dtqmbt6.js +0 -7
- package/dist/cli/main-6h9x282m.js +0 -4
- package/dist/cli/main-6vjeds42.js +0 -3
- package/dist/cli/main-78gq4bt9.js +0 -6
- package/dist/cli/main-7jd5vh3x.js +0 -4
- package/dist/cli/main-7kt6r53y.js +0 -4
- package/dist/cli/main-8c1tbazx.js +0 -58
- package/dist/cli/main-9a9rnh47.js +0 -19
- package/dist/cli/main-9ht36z12.js +0 -3
- package/dist/cli/main-a2yfvcy9.js +0 -7
- package/dist/cli/main-a3f51n0x.js +0 -5
- package/dist/cli/main-b8zq261k.js +0 -3
- package/dist/cli/main-bxtvnf6d.js +0 -13
- package/dist/cli/main-edxc3yzt.js +0 -4
- package/dist/cli/main-evgz4mp5.js +0 -21
- package/dist/cli/main-f33fc5je.js +0 -9
- package/dist/cli/main-fvnpq46y.js +0 -12
- package/dist/cli/main-gbbty4d4.js +0 -3
- package/dist/cli/main-gth53dnt.js +0 -25
- package/dist/cli/main-hqbz10aw.js +0 -9
- package/dist/cli/main-hrrvcfan.js +0 -38
- package/dist/cli/main-hzwtsb2m.js +0 -5
- package/dist/cli/main-j7ttv0sd.js +0 -34
- package/dist/cli/main-jak598k9.js +0 -5
- package/dist/cli/main-kba6zeyd.js +0 -6
- package/dist/cli/main-kwwsz6rq.js +0 -3
- package/dist/cli/main-m8vm17zq.js +0 -3
- package/dist/cli/main-mg4f96e1.js +0 -3
- package/dist/cli/main-mg9b20ac.js +0 -18
- package/dist/cli/main-mgb9ccnx.js +0 -3
- package/dist/cli/main-mjt2p7aj.js +0 -3
- package/dist/cli/main-n6qrdbmy.js +0 -3
- package/dist/cli/main-na7wse0x.js +0 -5
- package/dist/cli/main-nqveez48.js +0 -4
- package/dist/cli/main-ntqef02r.js +0 -10
- package/dist/cli/main-nvc3yjay.js +0 -136
- package/dist/cli/main-p0cfn6nr.js +0 -16
- package/dist/cli/main-qj2djy17.js +0 -19
- package/dist/cli/main-qsevpgsv.js +0 -3
- package/dist/cli/main-qvarybsp.js +0 -3
- package/dist/cli/main-rebtt91r.js +0 -5
- package/dist/cli/main-rpg7h8mb.js +0 -3
- package/dist/cli/main-rsy72qmw.js +0 -15
- package/dist/cli/main-rvetps99.js +0 -18
- package/dist/cli/main-s4bb0jav.js +0 -3
- package/dist/cli/main-s9v8k74e.js +0 -3
- package/dist/cli/main-tjvwmscs.js +0 -3
- package/dist/cli/main-tkgarpjj.js +0 -4
- package/dist/cli/main-v8y60bb2.js +0 -3
- package/dist/cli/main-vhrrq337.js +0 -3
- package/dist/cli/main-vp2dfb7s.js +0 -4
- package/dist/cli/main-vqbr22sz.js +0 -8
- package/dist/cli/main-vxnwe5xx.js +0 -18
- package/dist/cli/main-wgph00xf.js +0 -5
- package/dist/cli/main-wk2csfnj.js +0 -5
- package/dist/cli/main-wm997zjx.js +0 -3
- package/dist/cli/main-wpkyraxh.js +0 -3
- package/dist/cli/main-wqt32p5x.js +0 -4
- package/dist/cli/main-x9ct6y1a.js +0 -3
- package/dist/cli/main-xfekqh9m.js +0 -7
- package/dist/cli/main-xt9zc3n6.js +0 -7
- package/dist/cli/main-xx2z3zh5.js +0 -4
- package/dist/cli/main-y5c82rxr.js +0 -3
- package/dist/cli/main-yrjt2sqt.js +0 -14
- package/dist/cli/main-ys6zj3yr.js +0 -3
- package/dist/cli/main-ywbxshqc.js +0 -8
- package/dist/cli/main-z13755t8.js +0 -25
- package/dist/cli/main-zc7pyrbj.js +0 -4
- package/dist/cli/main.js +0 -279
- package/dist/cli/market-cmd-bm5xvn9f.js +0 -5
- package/dist/cli/mcp-login-bthtfpt7.js +0 -2
- package/dist/cli/mcp-market-cmd-mbeshfyd.js +0 -2
- package/dist/cli/notify-54v5z9dz.js +0 -2
- package/dist/cli/oauth-g5gme95c.js +0 -2
- package/dist/cli/output-satndjap.js +0 -16
- package/dist/cli/profiles-sfhpbq3m.js +0 -2
- package/dist/cli/provider-config-hv3xtdt4.js +0 -2
- package/dist/cli/provider-kwzq6g84.js +0 -2
- package/dist/cli/registry-fh0hdnyn.js +0 -2
- package/dist/cli/registry-y1y8e94r.js +0 -2
- package/dist/cli/repl-t4z03mqq.js +0 -11
- package/dist/cli/resume-fqt4chg8.js +0 -2
- package/dist/cli/run-flags-rysbag9t.js +0 -2
- package/dist/cli/runtime-j19fjbsa.js +0 -2
- package/dist/cli/sandbox-config-g4qxd7y5.js +0 -2
- package/dist/cli/server-r0b6bksk.js +0 -5
- package/dist/cli/session-arg-txmn5g4x.js +0 -2
- package/dist/cli/session-ed250d9j.js +0 -2
- package/dist/cli/sessions-cmd-adw7svfn.js +0 -7
- package/dist/cli/settings-y9rzcqx8.js +0 -2
- package/dist/cli/setup-jmbr11j0.js +0 -2
- package/dist/cli/sextant-smoke-tcth0vea.js +0 -5
- package/dist/cli/skills-cmd-zbdy99v6.js +0 -2
- package/dist/cli/smoke-1bg937kx.js +0 -8
- package/dist/cli/start-chat-p01cdks3.js +0 -12
- package/dist/cli/stream-4wmyaypz.js +0 -2
- package/dist/cli/task-eg4s093s.js +0 -2
- package/dist/cli/tasks-12v9rr9k.js +0 -2
- package/dist/cli/thinking-a5ngvqyh.js +0 -2
- package/dist/cli/todo-1wxpcecx.js +0 -2
- package/dist/cli/tools-2ftsya7w.js +0 -2
- package/dist/cli/tools-x1tj4fxm.js +0 -2
- package/dist/cli/trust-cmd-hccxehzb.js +0 -2
- package/dist/cli/update-check-ygt3vd7m.js +0 -2
- package/dist/cli/update-cmd-v23qhr8c.js +0 -2
- package/dist/cli/voice-g1gtck92.js +0 -2
- package/dist/cli/webfetch-0nnrjgb5.js +0 -2
- package/dist/cli/websearch-f0vr2p7d.js +0 -2
- package/dist/cli/workspace-9rq1w4ta.js +0 -2
- package/dist/lib/index.js +0 -62
- package/dist/lib/models-index.json +0 -1
- package/dist/lib/plugins.js +0 -6
- package/dist/lib/providers.js +0 -17
- package/dist/lib/public-api.js +0 -20
- package/dist/rovecode.exe +0 -4
- /package/{dist/cli → src/providers}/models-index.json +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** provider_list / provider_edit — the agent's window onto the provider registry (providers/registry.ts).
|
|
2
|
+
*
|
|
3
|
+
* Split like task/task_status (port #26): provider_list is kind "read" (policy action file.read →
|
|
4
|
+
* auto-allowed under gated rules and in plan mode; it never mutates and never prompts), provider_edit
|
|
5
|
+
* is kind "custom" → action tool.provider_edit, PROMPT under gated rules (cli/runtime.ts buildCfg),
|
|
6
|
+
* denied in plan mode by the blanket tool.* rule, allowed under yolo. Children (task tool) get neither.
|
|
7
|
+
*
|
|
8
|
+
* Secrets: NEITHER tool accepts, returns or echoes an API key. provider_list prints key SOURCES
|
|
9
|
+
* (stored / env NAME / none); provider_edit refuses any key-shaped argument (looksLikeSecret) and
|
|
10
|
+
* tells the model how the human sets one — `rovecode auth set <id>` (masked prompt), `/provider key`
|
|
11
|
+
* in the TUI, or the env var named by keyEnv. A key the model was given would otherwise sit in the
|
|
12
|
+
* transcript and in the provider's logs forever.
|
|
13
|
+
*
|
|
14
|
+
* Effects are live: the registry re-reads its files, so the run that follows an add/use — and the
|
|
15
|
+
* TUI, through registry.onChange — sees the new provider without a restart. */
|
|
16
|
+
|
|
17
|
+
import type { Tool, ToolOutput } from "../core/types.ts";
|
|
18
|
+
import { inferProtocol, isConfigured } from "../providers/provider-config.ts";
|
|
19
|
+
import { formatProviderList, type ProviderRegistry } from "../providers/registry.ts";
|
|
20
|
+
|
|
21
|
+
const SECRET_FIELD_RE = /^(api[_-]?key|key|secret|token|password|auth(orization)?|bearer)$/i;
|
|
22
|
+
/** common key prefixes — a value that starts like this is a secret whatever the field is called */
|
|
23
|
+
const SECRET_VALUE_RE = /^(sk-|sk_|key-|xai-|gsk_|AKIA|Bearer\s|ghp_)/;
|
|
24
|
+
|
|
25
|
+
/** The argument name that carries something key-shaped, or null. `keyEnv`/`noKey` are legitimate. */
|
|
26
|
+
export function looksLikeSecret(args: unknown): string | null {
|
|
27
|
+
if (typeof args !== "object" || args === null) return null;
|
|
28
|
+
for (const [k, v] of Object.entries(args as Record<string, unknown>)) {
|
|
29
|
+
if (k === "keyEnv" || k === "noKey") continue;
|
|
30
|
+
if (typeof v !== "string" || v.trim().length === 0) continue;
|
|
31
|
+
if (SECRET_FIELD_RE.test(k)) return k;
|
|
32
|
+
if (SECRET_VALUE_RE.test(v.trim())) return k;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const refusal = (field: string): string =>
|
|
38
|
+
`provider_edit never accepts API keys (argument "${field}" looks like one) — nothing was stored. Keys stay with the human: ask them to run \`rovecode auth set <id>\` (masked prompt) or \`/provider key <id> <key>\` in the TUI, or to set the env var you name in keyEnv. Then retry without the key.`;
|
|
39
|
+
|
|
40
|
+
export function providerListTool(reg: ProviderRegistry): Tool {
|
|
41
|
+
return {
|
|
42
|
+
schema: {
|
|
43
|
+
name: "provider_list",
|
|
44
|
+
description: "Inspect model providers (never prompts, never mutates, never shows a key). `list` shows the configured providers, the default provider/model and where each key comes from (stored / env NAME / none; `all` adds keyless built-ins); `models` lists the model ids a provider offers (its providers.json entry or its /models endpoint); `test` makes one tiny real call to a provider (optionally a specific model) and reports ok or the error. Use it before switching models, or when a run ends with a `config:` error.",
|
|
45
|
+
args: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
action: { type: "string", enum: ["list", "models", "test"] },
|
|
49
|
+
id: { type: "string", description: "provider id (models / test)" },
|
|
50
|
+
model: { type: "string", description: "model id to test (default: the provider's default model)" },
|
|
51
|
+
all: { type: "boolean", description: "list: include built-in providers that have no key yet" },
|
|
52
|
+
},
|
|
53
|
+
required: ["action"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
kind: "read",
|
|
57
|
+
sequential: false,
|
|
58
|
+
async execute(args): Promise<ToolOutput> {
|
|
59
|
+
const a = (args && typeof args === "object" ? args : {}) as { action?: string; id?: string; model?: string; all?: boolean };
|
|
60
|
+
switch (a.action) {
|
|
61
|
+
case "list":
|
|
62
|
+
return { ok: true, output: formatProviderList(reg, { all: a.all === true }) };
|
|
63
|
+
case "models": {
|
|
64
|
+
if (!a.id) return { ok: false, output: "models needs `id`" };
|
|
65
|
+
const r = await reg.models(a.id);
|
|
66
|
+
if (!r.ok) return { ok: false, output: r.error };
|
|
67
|
+
return r.models.length > 0
|
|
68
|
+
? { ok: true, output: `${a.id} models (${r.source}, ${r.models.length}):\n${r.models.join("\n")}` }
|
|
69
|
+
: { ok: true, output: `${a.id}: the endpoint listed no models (it may not implement /models — try a model id directly with test)` };
|
|
70
|
+
}
|
|
71
|
+
case "test": {
|
|
72
|
+
if (!a.id) return { ok: false, output: "test needs `id`" };
|
|
73
|
+
const r = await reg.probe(a.id, a.model);
|
|
74
|
+
return { ok: r.ok, output: `${a.id}/${r.model}: ${r.detail}` };
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
return { ok: false, output: "action must be list | models | test" };
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function providerEditTool(reg: ProviderRegistry): Tool {
|
|
84
|
+
return {
|
|
85
|
+
schema: {
|
|
86
|
+
name: "provider_edit",
|
|
87
|
+
description: "Add, remove or select model providers; the human approves each call. `add` registers an endpoint {id, baseUrl, protocol? (openai|anthropic — inferred from the URL), keyEnv? (env var holding the key), defaultModel?, noKey? (local servers), scope? (user|project)} in providers.json. `remove` deletes a file-defined provider. `use` sets the default provider/model (`selector` = \"provider/model\" or a provider id) and takes effect immediately: the TUI switches now, the next run uses it, no restart. NEVER pass an API key here — the call is refused and nothing is stored. After `add`, tell the user to run `rovecode auth set <id>` (or `/provider key <id> <key>` in the TUI), or to set the keyEnv variable; the key is picked up live.",
|
|
88
|
+
args: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
action: { type: "string", enum: ["add", "remove", "use"] },
|
|
92
|
+
id: { type: "string" },
|
|
93
|
+
baseUrl: { type: "string", description: "http(s) endpoint root, e.g. https://llm.example.com/v1" },
|
|
94
|
+
protocol: { type: "string", enum: ["openai", "anthropic"] },
|
|
95
|
+
keyEnv: { type: "string", description: "env var that holds the key (default <ID>_API_KEY)" },
|
|
96
|
+
defaultModel: { type: "string" },
|
|
97
|
+
noKey: { type: "boolean", description: "local server: no key required" },
|
|
98
|
+
scope: { type: "string", enum: ["user", "project"], description: "which providers.json: ~/.rovecode (default) or ./.rovecode" },
|
|
99
|
+
selector: { type: "string", description: "use: provider/model" },
|
|
100
|
+
},
|
|
101
|
+
required: ["action"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
kind: "custom",
|
|
105
|
+
sequential: true,
|
|
106
|
+
async execute(args): Promise<ToolOutput> {
|
|
107
|
+
const leaked = looksLikeSecret(args);
|
|
108
|
+
if (leaked !== null) return { ok: false, output: refusal(leaked) };
|
|
109
|
+
const a = (args && typeof args === "object" ? args : {}) as {
|
|
110
|
+
action?: string; id?: string; baseUrl?: string; protocol?: "openai" | "anthropic"; keyEnv?: string;
|
|
111
|
+
defaultModel?: string; noKey?: boolean; scope?: "user" | "project"; selector?: string;
|
|
112
|
+
};
|
|
113
|
+
const scope = a.scope === "project" ? "project" : "user";
|
|
114
|
+
switch (a.action) {
|
|
115
|
+
case "add": {
|
|
116
|
+
if (!a.id || !a.baseUrl) return { ok: false, output: "add needs `id` and `baseUrl`" };
|
|
117
|
+
const r = reg.add({
|
|
118
|
+
id: a.id, baseUrl: a.baseUrl, protocol: a.protocol ?? inferProtocol(a.baseUrl),
|
|
119
|
+
...(a.keyEnv !== undefined ? { keyEnv: a.keyEnv } : {}),
|
|
120
|
+
...(a.defaultModel !== undefined ? { defaultModel: a.defaultModel } : {}),
|
|
121
|
+
...(a.noKey === true ? { noKey: true } : {}),
|
|
122
|
+
}, scope);
|
|
123
|
+
if ("error" in r) return { ok: false, output: r.error };
|
|
124
|
+
const key = !isConfigured(r)
|
|
125
|
+
? `key: NOT SET — the user must run \`rovecode auth set ${r.id}\` (or \`/provider key ${r.id} <key>\` in the TUI), or set ${r.keyEnv}; it is picked up immediately, no restart`
|
|
126
|
+
: r.keySource === "stored" ? "key: stored credential" : r.keySource === "env" ? `key: env ${r.keyEnv} (present)` : "key: not needed";
|
|
127
|
+
return { ok: true, output: `added provider "${r.id}" (${r.protocol}, ${r.baseUrl}) to the ${scope} providers.json\n${key}${r.defaultModel !== undefined ? `\ndefault model: ${r.defaultModel}` : ""}\nselect it with provider_edit use ${r.id}/<model>` };
|
|
128
|
+
}
|
|
129
|
+
case "remove": {
|
|
130
|
+
if (!a.id) return { ok: false, output: "remove needs `id`" };
|
|
131
|
+
const r = reg.remove(a.id);
|
|
132
|
+
if ("error" in r) return { ok: false, output: r.error };
|
|
133
|
+
return { ok: true, output: r.removed.length > 0 ? `removed provider "${a.id}" from the ${r.removed.join(" and ")} providers.json` : `provider "${a.id}" was not in any providers.json` };
|
|
134
|
+
}
|
|
135
|
+
case "use": {
|
|
136
|
+
const sel = a.selector ?? a.id;
|
|
137
|
+
if (!sel) return { ok: false, output: "use needs `selector` (provider/model)" };
|
|
138
|
+
const r = reg.setDefault(sel, scope);
|
|
139
|
+
if ("error" in r) return { ok: false, output: r.error };
|
|
140
|
+
const p = reg.get(r.provider);
|
|
141
|
+
const note = p !== undefined && !isConfigured(p) ? `\nnote: ${reg.keyHint(p)}` : "";
|
|
142
|
+
return { ok: true, output: `default → ${r.provider}/${r.model} (saved to the ${scope} providers.json; the TUI switches now, headless runs use it from the next run)${note}` };
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
return { ok: false, output: "action must be add | remove | use" };
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/** `task` + `task_status` tools (port #26): the model's door to background subagents over
|
|
2
|
+
* core/tasks.ts TaskManager. Children run through orchestrator.ts runChild (the ONE
|
|
3
|
+
* agentLoop); these tools never run a loop themselves.
|
|
4
|
+
*
|
|
5
|
+
* Source shape: opencode packages/opencode/src/tool/task.ts (MIT, snapshot
|
|
6
|
+
* research/source_snapshots/opencode-2026 @ ebece6e) — parameters description/prompt/
|
|
7
|
+
* subagent_type + background flag (:43-62), "started" text that tells the model NOT to
|
|
8
|
+
* poll and to keep working or end its turn (:31-35), depth walk refusal (:104-117),
|
|
9
|
+
* result rendered as a tagged block with the child's final text (:64-79, :341-345).
|
|
10
|
+
* Departures: TWO tools split by policy class instead of background:true on a foreground
|
|
11
|
+
* tool — `task` (kind spawn: start|cancel — creating a child, or destroying its work) and
|
|
12
|
+
* `task_status` (kind read: status|result|list — registry reads; `result` is an explicit
|
|
13
|
+
* bounded wait). Refusals come from the orchestrator's preflight (depth cap / spawn
|
|
14
|
+
* policy) as tool output, never exceptions.
|
|
15
|
+
*
|
|
16
|
+
* Policy: `task` kind "spawn" → action "spawn" (core/tools.ts actionFor): the default gated
|
|
17
|
+
* rules PROMPT for it (cli/runtime.ts buildCfg) — once per start, since "once" is never
|
|
18
|
+
* cached — yolo allows, plan mode denies (core/modes.ts); children derive prompt→deny
|
|
19
|
+
* (orchestrator deriveChildRules), so nested starts exist only under an allow rule.
|
|
20
|
+
* `task_status` kind "read" → action "file.read", which the gated rules ALLOW: collecting a
|
|
21
|
+
* result never prompts, headless surfaces (run/serve/acp) and children read it, plan mode
|
|
22
|
+
* keeps it. Its schema declares no path/command/url, so the policy resource is the tool
|
|
23
|
+
* NAME and a smuggled `path` cannot re-aim a `deny file.read task_status` rule (tools.ts
|
|
24
|
+
* describeResource honors declared keys only). Fix-wave MED-2: one action-agnostic
|
|
25
|
+
* spawn-kind tool prompted for status/result/list too (≥2 prompts per task) and failed
|
|
26
|
+
* them closed on headless surfaces. */
|
|
27
|
+
|
|
28
|
+
import type { Tool, ToolContext, ToolOutput } from "../core/types.ts";
|
|
29
|
+
import { formatTaskList, isTerminal, type TaskId, type TaskInfo, type TaskManager } from "../core/tasks.ts";
|
|
30
|
+
import type { SteeringQueue } from "../core/loop.ts";
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_WAIT_MS = 60_000;
|
|
33
|
+
export const MAX_WAIT_MS = 600_000;
|
|
34
|
+
|
|
35
|
+
export interface TaskToolOptions {
|
|
36
|
+
/** depth of the loop this tool serves (root = 0); children start at depth + 1 */
|
|
37
|
+
parentDepth?: number;
|
|
38
|
+
/** completion notes for tasks started here (default: the manager's attached queue) */
|
|
39
|
+
notify?: SteeringQueue;
|
|
40
|
+
/** the task id of the child this tool serves (nested); enables slot lending */
|
|
41
|
+
caller?: TaskId;
|
|
42
|
+
/** owner signal for tasks started here (nested: the serving task's own run signal, so
|
|
43
|
+
* cancelling a task cancels its children). Root tools leave it unset — the manager's
|
|
44
|
+
* bindRun() signal owns root tasks; ToolContext.signal is NOT usable (loop.ts:91). */
|
|
45
|
+
owner?: AbortSignal;
|
|
46
|
+
/** agent name used when `agent` is omitted */
|
|
47
|
+
defaultAgent?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TaskStatusToolOptions {
|
|
51
|
+
/** the task id of the child this tool serves (nested): a `result` wait lends its slot to
|
|
52
|
+
* the waited-on queued task (TaskManager.result slot lending) */
|
|
53
|
+
caller?: TaskId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const NOT_POLLING =
|
|
57
|
+
"You will be notified in this conversation when it finishes — do NOT poll or sleep: " +
|
|
58
|
+
"continue other non-overlapping work or end your turn. Use `task_status result` to wait for it when you need its output.";
|
|
59
|
+
/** one-shot policy (cli/main.ts cmdRun exit): tasks never outlive the process that started them */
|
|
60
|
+
const ONE_SHOT =
|
|
61
|
+
"In a one-shot run (`rovecode run`) background tasks do not outlive the run: collect what you need with " +
|
|
62
|
+
"`task_status result` before ending your turn.";
|
|
63
|
+
|
|
64
|
+
interface Args {
|
|
65
|
+
action?: unknown; goal?: unknown; agent?: unknown; isolated?: unknown; label?: unknown; id?: unknown; timeout_ms?: unknown;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const err = (output: string): ToolOutput => ({ ok: false, output: `Error: ${output}` });
|
|
69
|
+
const need = (tool: string, what: string): ToolOutput => err(`${tool} ${what} requires a string \`id\` (see \`task_status list\`)`);
|
|
70
|
+
|
|
71
|
+
function describe(t: TaskInfo, now: number): string {
|
|
72
|
+
const secs = t.startedAt !== undefined ? Math.max(0, Math.round(((t.finishedAt ?? now) - t.startedAt) / 1000)) : 0;
|
|
73
|
+
const usage = t.usage ? `, tokens ${t.usage.input}in/${t.usage.output}out` : "";
|
|
74
|
+
return `task ${t.id} (${t.label}) ${t.status} — agent ${t.agent}, ${t.isolated ? "isolated, " : ""}${secs}s${usage}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderResult(t: TaskInfo, now: number): ToolOutput {
|
|
78
|
+
const head = describe(t, now);
|
|
79
|
+
if (t.status === "done") {
|
|
80
|
+
const patch = t.patchLines !== undefined ? `\n[isolated: ${t.patchLines === 0 ? "no file changes" : `patch of ${t.patchLines} lines merged back`}]` : "";
|
|
81
|
+
return { ok: true, output: `${head}${patch}\n${t.summary ?? "(no output)"}`, data: t };
|
|
82
|
+
}
|
|
83
|
+
if (t.status === "failed") return { ok: false, output: `${head}\n${t.error ?? "unknown error"}`, data: t };
|
|
84
|
+
if (t.status === "cancelled") return { ok: false, output: head, data: t };
|
|
85
|
+
return { ok: true, output: `${head}. ${NOT_POLLING}`, data: t };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** `timeout_ms`: numbers and numeric strings ("5000" — models quote integers) clamp to
|
|
89
|
+
* [0, MAX_WAIT_MS]; absent → the default; anything else is a clear error, never a silent
|
|
90
|
+
* default (fix-wave L6: a quoted "5000" used to become the 60s default). */
|
|
91
|
+
function parseTimeout(v: unknown): number | { error: string } {
|
|
92
|
+
if (v === undefined || v === null) return DEFAULT_WAIT_MS;
|
|
93
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
94
|
+
if (!Number.isFinite(n)) return { error: `task_status result: \`timeout_ms\` must be a number of milliseconds (got ${JSON.stringify(v) ?? String(v)})` };
|
|
95
|
+
return Math.min(MAX_WAIT_MS, Math.max(0, Math.floor(n)));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** kind spawn: `start` (a new child) and `cancel` (destroys the child's work) — the two
|
|
99
|
+
* actions a gated policy should ask about. Reads live on `task_status`. */
|
|
100
|
+
export function createTaskTool(tasks: TaskManager, opts: TaskToolOptions = {}): Tool {
|
|
101
|
+
const defaultAgent = opts.defaultAgent ?? "main";
|
|
102
|
+
return {
|
|
103
|
+
schema: {
|
|
104
|
+
name: "task",
|
|
105
|
+
description:
|
|
106
|
+
"Run subagents as background tasks. `start` launches a child agent session on `goal` and returns " +
|
|
107
|
+
"at once with a task id; the child works while you continue, and a note lands in this conversation " +
|
|
108
|
+
"when it finishes (do not poll). Read its state or output with the `task_status` tool (status/result/list " +
|
|
109
|
+
"— never prompts); `cancel` aborts a task. Children see only `goal` (write it self-contained). " +
|
|
110
|
+
`At most ${tasks.maxConcurrent} run concurrently; extra starts queue FIFO. ` +
|
|
111
|
+
"`isolated` runs the child in a git worktree copy and merges its file changes back as a patch on success. " +
|
|
112
|
+
"Policy: under gated rules each `start` needs approval and the child runs read-only (prompt-gated actions " +
|
|
113
|
+
"are denied for children) unless allow rules cover them; under yolo it inherits allow-all. " + ONE_SHOT,
|
|
114
|
+
args: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
action: { type: "string", enum: ["start", "cancel"] },
|
|
118
|
+
goal: { type: "string", description: "start: the child's task, self-contained (it has no access to this conversation)" },
|
|
119
|
+
agent: { type: "string", description: `start: agent definition to run (default "${defaultAgent}")` },
|
|
120
|
+
isolated: { type: "boolean", description: "start: run in an isolated worktree copy; file changes merge back as a patch when the task finishes ok" },
|
|
121
|
+
label: { type: "string", description: "start: short label (3-5 words) shown in task lists and completion notes" },
|
|
122
|
+
id: { type: "string", description: "cancel: task id (e.g. t1)" },
|
|
123
|
+
},
|
|
124
|
+
required: ["action"],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
kind: "spawn",
|
|
128
|
+
sequential: true,
|
|
129
|
+
async execute(args: unknown): Promise<ToolOutput> {
|
|
130
|
+
const a = (args ?? {}) as Args;
|
|
131
|
+
const id = typeof a.id === "string" ? a.id.trim() : "";
|
|
132
|
+
switch (a.action) {
|
|
133
|
+
case "start": {
|
|
134
|
+
if (typeof a.goal !== "string" || a.goal.trim() === "") return err("task start requires a non-empty string `goal`");
|
|
135
|
+
const agent = typeof a.agent === "string" && a.agent.trim() !== "" ? a.agent.trim() : defaultAgent;
|
|
136
|
+
const r = tasks.start(
|
|
137
|
+
{ agent, goal: a.goal, isolated: a.isolated === true, background: true },
|
|
138
|
+
{ label: typeof a.label === "string" ? a.label : undefined, parentDepth: opts.parentDepth ?? 0, notify: opts.notify, caller: opts.caller, owner: opts.owner },
|
|
139
|
+
);
|
|
140
|
+
if (!r.ok) return err(`task refused: ${r.reason}`);
|
|
141
|
+
const t = tasks.status(r.id)!;
|
|
142
|
+
const state = t.status === "running" ? "running" : `queued (${tasks.counts().running} running, bound ${tasks.maxConcurrent})`;
|
|
143
|
+
// the approver/model must know what the child CAN do (deriveChildRules: prompt → deny)
|
|
144
|
+
const policy = r.childPolicy === "gated"
|
|
145
|
+
? "Policy: gated — the child runs read-only (prompt-gated actions are DENIED for children) unless allow rules cover them."
|
|
146
|
+
: "Policy: the child inherits your allow rules.";
|
|
147
|
+
return { ok: true, output: `task ${t.id} (${t.label}) started: ${state}. ${policy} ${NOT_POLLING}`, data: t };
|
|
148
|
+
}
|
|
149
|
+
case "cancel": {
|
|
150
|
+
if (id === "") return need("task", "cancel");
|
|
151
|
+
const before = tasks.status(id);
|
|
152
|
+
if (!before) return err(`unknown task '${id}'`);
|
|
153
|
+
if (isTerminal(before.status)) return { ok: true, output: `task ${id} already ${before.status}`, data: before };
|
|
154
|
+
const t = tasks.cancel(id)!;
|
|
155
|
+
return { ok: true, output: `task ${t.id} (${t.label}) cancelled`, data: t };
|
|
156
|
+
}
|
|
157
|
+
default:
|
|
158
|
+
return err(`task requires action start|cancel (got ${JSON.stringify(a.action ?? null)}); status|result|list live on task_status`);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** kind read: `status` | `result` (bounded wait, aborts with the run) | `list`. Auto-allowed by
|
|
165
|
+
* the gated rules (file.read) and plan mode; no path/command/url in the schema, so the policy
|
|
166
|
+
* resource is always the tool name. */
|
|
167
|
+
export function createTaskStatusTool(tasks: TaskManager, opts: TaskStatusToolOptions = {}): Tool {
|
|
168
|
+
return {
|
|
169
|
+
schema: {
|
|
170
|
+
name: "task_status",
|
|
171
|
+
description:
|
|
172
|
+
"Read background tasks started with `task` (never prompts, never mutates). `status` shows one task's " +
|
|
173
|
+
"state; `result` waits (bounded by `timeout_ms`, default 60s) for a task to finish and returns the " +
|
|
174
|
+
"child's final output — call it when you need the output, otherwise wait for the completion note; " +
|
|
175
|
+
"`list` shows every task. " + ONE_SHOT,
|
|
176
|
+
args: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
action: { type: "string", enum: ["status", "result", "list"] },
|
|
180
|
+
id: { type: "string", description: "status/result: task id (e.g. t1)" },
|
|
181
|
+
timeout_ms: { type: "integer", description: `result: max wait in ms (default ${DEFAULT_WAIT_MS}, max ${MAX_WAIT_MS}); 0 = report the current state at once` },
|
|
182
|
+
},
|
|
183
|
+
required: ["action"],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
kind: "read",
|
|
187
|
+
sequential: true,
|
|
188
|
+
async execute(args: unknown, ctx: ToolContext): Promise<ToolOutput> {
|
|
189
|
+
const a = (args ?? {}) as Args;
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
const id = typeof a.id === "string" ? a.id.trim() : "";
|
|
192
|
+
switch (a.action) {
|
|
193
|
+
case "status": {
|
|
194
|
+
if (id === "") return need("task_status", "status");
|
|
195
|
+
const t = tasks.status(id);
|
|
196
|
+
return t ? { ok: true, output: describe(t, now), data: t } : err(`unknown task '${id}'`);
|
|
197
|
+
}
|
|
198
|
+
case "result": {
|
|
199
|
+
if (id === "") return need("task_status", "result");
|
|
200
|
+
if (!tasks.status(id)) return err(`unknown task '${id}'`);
|
|
201
|
+
const timeoutMs = parseTimeout(a.timeout_ms);
|
|
202
|
+
if (typeof timeoutMs !== "number") return err(timeoutMs.error);
|
|
203
|
+
const t = await tasks.result(id, { timeoutMs, signal: ctx.signal, caller: opts.caller });
|
|
204
|
+
if (!t) return err(`unknown task '${id}'`);
|
|
205
|
+
if (!isTerminal(t.status) && ctx.signal.aborted) return err(`wait for task ${id} aborted`);
|
|
206
|
+
if (!isTerminal(t.status)) return { ok: true, output: `${describe(t, Date.now())} — still running after ${Math.round(timeoutMs / 1000)}s. ${NOT_POLLING}`, data: t };
|
|
207
|
+
return renderResult(t, Date.now());
|
|
208
|
+
}
|
|
209
|
+
case "list":
|
|
210
|
+
return { ok: true, output: formatTaskList(tasks.list(), now), data: tasks.counts() };
|
|
211
|
+
default:
|
|
212
|
+
return err(`task_status requires action status|result|list (got ${JSON.stringify(a.action ?? null)}); start|cancel live on task`);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|