@quickchatai/quickreview 1.2.0 → 1.3.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/README.md +116 -53
- package/bin/qcr.js +1 -1
- package/config.example.toml +25 -7
- package/deploy/ansible/README.md +20 -12
- package/deploy/ansible/inventory.example.ini +1 -1
- package/deploy/collect-secrets.sh +1 -1
- package/deploy/qcr-update.sh +1 -1
- package/dist/extensions/cli/home.js +14 -0
- package/dist/extensions/cli/home.js.map +1 -0
- package/dist/extensions/cli/selfimprove.js +18 -0
- package/dist/extensions/cli/selfimprove.js.map +1 -0
- package/dist/extensions/home.js +350 -0
- package/dist/extensions/home.js.map +1 -0
- package/dist/extensions/login-route.js +1 -1
- package/dist/extensions/login-route.js.map +1 -1
- package/dist/extensions/onboarding.js +8 -7
- package/dist/extensions/onboarding.js.map +1 -1
- package/dist/extensions/selfimprove.js +49 -0
- package/dist/extensions/selfimprove.js.map +1 -0
- package/dist/extensions/setup-scan.js +23 -592
- package/dist/extensions/setup-scan.js.map +1 -1
- package/dist/extensions/setup.js +2 -47
- package/dist/extensions/setup.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/index.js +4 -0
- package/dist/extensions/vendor/openviking-pi/index.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +3 -1
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/sync.js +81 -0
- package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -1
- package/dist/src/archive/restore.js +18 -15
- package/dist/src/archive/restore.js.map +1 -1
- package/dist/src/botworker.js +13 -2
- package/dist/src/botworker.js.map +1 -1
- package/dist/src/channelfs.js +3 -26
- package/dist/src/channelfs.js.map +1 -1
- package/dist/src/cli.js +14 -50
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +16 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/daemon.js +33 -4
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/debuground.js +1 -0
- package/dist/src/debuground.js.map +1 -1
- package/dist/src/debugroundpi.js +1 -0
- package/dist/src/debugroundpi.js.map +1 -1
- package/dist/src/engine/caserunner-sdk.js +15 -251
- package/dist/src/engine/caserunner-sdk.js.map +1 -1
- package/dist/src/home/adapters.js +349 -0
- package/dist/src/home/adapters.js.map +1 -0
- package/dist/src/home/advanced.js +318 -0
- package/dist/src/home/advanced.js.map +1 -0
- package/dist/src/home/advancedtools.js +57 -0
- package/dist/src/home/advancedtools.js.map +1 -0
- package/dist/src/home/autoreview.js +325 -0
- package/dist/src/home/autoreview.js.map +1 -0
- package/dist/src/home/daemonstatus.js +32 -0
- package/dist/src/home/daemonstatus.js.map +1 -0
- package/dist/src/home/devicelogin.js +61 -0
- package/dist/src/home/devicelogin.js.map +1 -0
- package/dist/src/home/launch.js +202 -0
- package/dist/src/home/launch.js.map +1 -0
- package/dist/src/home/loginowner.js +205 -0
- package/dist/src/home/loginowner.js.map +1 -0
- package/dist/src/home/memory.js +122 -0
- package/dist/src/home/memory.js.map +1 -0
- package/dist/src/home/model.js +811 -0
- package/dist/src/home/model.js.map +1 -0
- package/dist/src/home/panel.js +520 -0
- package/dist/src/home/panel.js.map +1 -0
- package/dist/src/home/ports.js +19 -0
- package/dist/src/home/ports.js.map +1 -0
- package/dist/src/home/review.js +163 -0
- package/dist/src/home/review.js.map +1 -0
- package/dist/src/home/reviews.js +478 -0
- package/dist/src/home/reviews.js.map +1 -0
- package/dist/src/home/setup.js +507 -0
- package/dist/src/home/setup.js.map +1 -0
- package/dist/src/home/setupadapters.js +214 -0
- package/dist/src/home/setupadapters.js.map +1 -0
- package/dist/src/home/setupcard.js +199 -0
- package/dist/src/home/setupcard.js.map +1 -0
- package/dist/src/home/setuptools.js +45 -0
- package/dist/src/home/setuptools.js.map +1 -0
- package/dist/src/home/spend.js +160 -0
- package/dist/src/home/spend.js.map +1 -0
- package/dist/src/home/switches.js +209 -0
- package/dist/src/home/switches.js.map +1 -0
- package/dist/src/home/switchon.js +439 -0
- package/dist/src/home/switchon.js.map +1 -0
- package/dist/src/home/toolnames.js +41 -0
- package/dist/src/home/toolnames.js.map +1 -0
- package/dist/src/home/tune.js +341 -0
- package/dist/src/home/tune.js.map +1 -0
- package/dist/src/home/words.js +7 -0
- package/dist/src/home/words.js.map +1 -0
- package/dist/src/homes.js +14 -0
- package/dist/src/homes.js.map +1 -1
- package/dist/src/install.js +1 -1
- package/dist/src/install.js.map +1 -1
- package/dist/src/ipc/workerspec.js +8 -0
- package/dist/src/ipc/workerspec.js.map +1 -1
- package/dist/src/ledgercheck.js +39 -10
- package/dist/src/ledgercheck.js.map +1 -1
- package/dist/src/live/publish.js +1 -1
- package/dist/src/live/publish.js.map +1 -1
- package/dist/src/livecli.js +1 -1
- package/dist/src/livecli.js.map +1 -1
- package/dist/src/loginrefresh/handover.js +121 -0
- package/dist/src/loginrefresh/handover.js.map +1 -0
- package/dist/src/loginrefresh/lock.js +54 -3
- package/dist/src/loginrefresh/lock.js.map +1 -1
- package/dist/src/loginrefresh/refresher.js +33 -5
- package/dist/src/loginrefresh/refresher.js.map +1 -1
- package/dist/src/loginrefresh/supervisor.js +18 -1
- package/dist/src/loginrefresh/supervisor.js.map +1 -1
- package/dist/src/npmverbs.js +42 -12
- package/dist/src/npmverbs.js.map +1 -1
- package/dist/src/onboard/choices.js +6 -3
- package/dist/src/onboard/choices.js.map +1 -1
- package/dist/src/onboard/engine.js +33 -292
- package/dist/src/onboard/engine.js.map +1 -1
- package/dist/src/onboard/steps/bringup.js +3 -2
- package/dist/src/onboard/steps/bringup.js.map +1 -1
- package/dist/src/onboard/steps/configfill.js +18 -7
- package/dist/src/onboard/steps/configfill.js.map +1 -1
- package/dist/src/onboard/steps/loginshare.js +213 -0
- package/dist/src/onboard/steps/loginshare.js.map +1 -0
- package/dist/src/onboard/steps/memory.js +14 -2
- package/dist/src/onboard/steps/memory.js.map +1 -1
- package/dist/src/onboard/steps/repoconfig.js +72 -0
- package/dist/src/onboard/steps/repoconfig.js.map +1 -0
- package/dist/src/onboard/toolnames.js +2 -2
- package/dist/src/onboard/toolnames.js.map +1 -1
- package/dist/src/piagentdir.js +13 -0
- package/dist/src/piagentdir.js.map +1 -0
- package/dist/src/pilogin.js +31 -16
- package/dist/src/pilogin.js.map +1 -1
- package/dist/src/qcrbin.js +16 -11
- package/dist/src/qcrbin.js.map +1 -1
- package/dist/src/reviewargs.js +122 -0
- package/dist/src/reviewargs.js.map +1 -0
- package/dist/src/reviewcli.js +7 -161
- package/dist/src/reviewcli.js.map +1 -1
- package/dist/src/reviewdetach.js +76 -11
- package/dist/src/reviewdetach.js.map +1 -1
- package/dist/src/scancli.js +39 -691
- package/dist/src/scancli.js.map +1 -1
- package/dist/src/selfimprove.js +66 -0
- package/dist/src/selfimprove.js.map +1 -0
- package/dist/src/session-command-supervisor.js +38 -9
- package/dist/src/session-command-supervisor.js.map +1 -1
- package/dist/src/setup/adapters.js +5 -223
- package/dist/src/setup/adapters.js.map +1 -1
- package/dist/src/setup/caserunner.js +15 -249
- package/dist/src/setup/caserunner.js.map +1 -1
- package/dist/src/setup/engine.js +32 -341
- package/dist/src/setup/engine.js.map +1 -1
- package/dist/src/setup/facts.js +3 -592
- package/dist/src/setup/facts.js.map +1 -1
- package/dist/src/setup/findings.js +3 -947
- package/dist/src/setup/findings.js.map +1 -1
- package/dist/src/setup/presentation.js +3 -8
- package/dist/src/setup/presentation.js.map +1 -1
- package/dist/src/setup/scan.js +3 -724
- package/dist/src/setup/scan.js.map +1 -1
- package/dist/src/setup/scanbuild.js +9 -92
- package/dist/src/setup/scanbuild.js.map +1 -1
- package/dist/src/setup/scanlease.js +3 -250
- package/dist/src/setup/scanlease.js.map +1 -1
- package/dist/src/setup/scanreport.js +3 -288
- package/dist/src/setup/scanreport.js.map +1 -1
- package/dist/src/setup/scanrun.js +3 -1732
- package/dist/src/setup/scanrun.js.map +1 -1
- package/dist/src/setup/scantree.js +3 -256
- package/dist/src/setup/scantree.js.map +1 -1
- package/dist/src/setup/stage2request.js +3 -258
- package/dist/src/setup/stage2request.js.map +1 -1
- package/dist/src/setup/unitcut.js +3 -436
- package/dist/src/setup/unitcut.js.map +1 -1
- package/dist/src/state.js +75 -9
- package/dist/src/state.js.map +1 -1
- package/dist/src/takeover.js +1 -0
- package/dist/src/takeover.js.map +1 -1
- package/dist/src/takeoverpi.js +2 -1
- package/dist/src/takeoverpi.js.map +1 -1
- package/dist/src/tui/alerts.js +4 -1
- package/dist/src/tui/alerts.js.map +1 -1
- package/dist/src/tui/board.js +2 -2
- package/dist/src/tui/board.js.map +1 -1
- package/dist/src/tui/companion.js +2 -1
- package/dist/src/tui/companion.js.map +1 -1
- package/dist/src/tui/deploytab.js +2 -2
- package/dist/src/tui/deploytab.js.map +1 -1
- package/dist/src/tui/index.js +9 -6
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/memory.js +3 -16
- package/dist/src/tui/memory.js.map +1 -1
- package/dist/src/tui/memoryurl.js +22 -0
- package/dist/src/tui/memoryurl.js.map +1 -0
- package/dist/src/tui/remote.js +1 -1
- package/dist/src/tui/remote.js.map +1 -1
- package/dist/src/tui/route.js +2 -2
- package/dist/src/tui/route.js.map +1 -1
- package/dist/src/tui/setupstate.js +8 -5
- package/dist/src/tui/setupstate.js.map +1 -1
- package/dist/src/tui/sshtriage.js +1 -1
- package/dist/src/tui/sshtriage.js.map +1 -1
- package/dist/src/verdicts.js +12 -0
- package/dist/src/verdicts.js.map +1 -1
- package/dist/src/wire.js +9 -4
- package/dist/src/wire.js.map +1 -1
- package/dist/src/workerbreaker.js +32 -5
- package/dist/src/workerbreaker.js.map +1 -1
- package/dist/src/workerworld.js +4 -0
- package/dist/src/workerworld.js.map +1 -1
- package/docker/README.md +1 -1
- package/docker/entrypoint.sh +40 -6
- package/docker-compose.yml +16 -1
- package/extensions/cli/home.ts +14 -0
- package/extensions/cli/selfimprove.ts +18 -0
- package/extensions/home.ts +484 -0
- package/extensions/login-route.ts +1 -1
- package/extensions/onboarding.ts +8 -7
- package/extensions/selfimprove.ts +65 -0
- package/extensions/setup-scan.ts +79 -766
- package/extensions/setup.ts +14 -70
- package/extensions/vendor/openviking-pi/NOTICE +5 -0
- package/extensions/vendor/openviking-pi/README.md +39 -5
- package/extensions/vendor/openviking-pi/index.ts +4 -0
- package/extensions/vendor/openviking-pi/patches/0014-one-turn-sync-in-flight.patch +239 -0
- package/extensions/vendor/openviking-pi/qcr-hooks.ts +3 -1
- package/extensions/vendor/openviking-pi/sync.ts +80 -0
- package/extensions/vendor/openviking-pi/upstream.json +2 -1
- package/package.json +6 -4
- package/prompts/onboarding.md +8 -7
- package/schemas/review-result.v1.json +60 -0
- package/scripts/deploy.sh +8 -6
- package/scripts/qcr-host.sh +28 -5
- package/scripts/vm-ghcr-login.sh +7 -4
- package/src/archive/restore.ts +18 -21
- package/src/botworker.ts +13 -2
- package/src/channelfs.ts +3 -26
- package/src/cli.ts +13 -54
- package/src/config.ts +38 -0
- package/src/daemon.ts +34 -4
- package/src/debuground.ts +3 -0
- package/src/debugroundpi.ts +1 -0
- package/src/engine/caserunner-sdk.ts +29 -314
- package/src/home/adapters.ts +418 -0
- package/src/home/advanced.ts +421 -0
- package/src/home/advancedtools.ts +96 -0
- package/src/home/autoreview.ts +385 -0
- package/src/home/daemonstatus.ts +55 -0
- package/src/home/devicelogin.ts +107 -0
- package/src/home/launch.ts +257 -0
- package/src/home/loginowner.ts +248 -0
- package/src/home/memory.ts +176 -0
- package/src/home/model.ts +965 -0
- package/src/home/panel.ts +666 -0
- package/src/home/ports.ts +338 -0
- package/src/home/review.ts +206 -0
- package/src/home/reviews.ts +565 -0
- package/src/home/setup.ts +689 -0
- package/src/home/setupadapters.ts +217 -0
- package/src/home/setupcard.ts +288 -0
- package/src/home/setuptools.ts +57 -0
- package/src/home/spend.ts +194 -0
- package/src/home/switches.ts +293 -0
- package/src/home/switchon.ts +554 -0
- package/src/home/toolnames.ts +46 -0
- package/src/home/tune.ts +405 -0
- package/src/home/words.ts +8 -0
- package/src/homes.ts +17 -0
- package/src/install.ts +1 -1
- package/src/ipc/workerspec.ts +15 -0
- package/src/ledgercheck.ts +45 -9
- package/src/live/publish.ts +4 -1
- package/src/livecli.ts +6 -1
- package/src/loginrefresh/handover.ts +138 -0
- package/src/loginrefresh/lock.ts +72 -3
- package/src/loginrefresh/refresher.ts +34 -5
- package/src/loginrefresh/supervisor.ts +25 -2
- package/src/npmverbs.ts +51 -11
- package/src/onboard/choices.ts +6 -3
- package/src/onboard/engine.ts +64 -434
- package/src/onboard/steps/bringup.ts +3 -2
- package/src/onboard/steps/configfill.ts +23 -7
- package/src/onboard/steps/loginshare.ts +258 -0
- package/src/onboard/steps/memory.ts +18 -2
- package/src/onboard/steps/repoconfig.ts +85 -0
- package/src/onboard/toolnames.ts +2 -2
- package/src/piagentdir.ts +12 -0
- package/src/pilogin.ts +32 -16
- package/src/qcrbin.ts +16 -11
- package/src/reviewargs.ts +119 -0
- package/src/reviewcli.ts +6 -167
- package/src/reviewdetach.ts +107 -13
- package/src/scancli.ts +75 -826
- package/src/selfimprove.ts +90 -0
- package/src/session-command-supervisor.ts +41 -8
- package/src/setup/adapters.ts +13 -221
- package/src/setup/caserunner.ts +34 -273
- package/src/setup/engine.ts +62 -496
- package/src/setup/facts.ts +3 -627
- package/src/setup/findings.ts +3 -1264
- package/src/setup/presentation.ts +3 -18
- package/src/setup/scan.ts +3 -1033
- package/src/setup/scanbuild.ts +20 -114
- package/src/setup/scanlease.ts +3 -297
- package/src/setup/scanreport.ts +3 -373
- package/src/setup/scanrun.ts +3 -2268
- package/src/setup/scantree.ts +3 -259
- package/src/setup/stage2request.ts +3 -347
- package/src/setup/unitcut.ts +3 -523
- package/src/state.ts +126 -11
- package/src/takeover.ts +5 -0
- package/src/takeoverpi.ts +3 -2
- package/src/tui/alerts.ts +9 -2
- package/src/tui/board.ts +2 -2
- package/src/tui/companion.ts +2 -1
- package/src/tui/deploytab.ts +2 -2
- package/src/tui/index.ts +9 -6
- package/src/tui/memory.ts +3 -15
- package/src/tui/memoryurl.ts +22 -0
- package/src/tui/remote.ts +1 -1
- package/src/tui/route.ts +2 -2
- package/src/tui/setupstate.ts +20 -16
- package/src/tui/sshtriage.ts +1 -1
- package/src/verdicts.ts +13 -0
- package/src/wire.ts +9 -4
- package/src/workerbreaker.ts +41 -5
- package/src/workerworld.ts +9 -1
- package/dist/src/live/projection.js +0 -450
- package/dist/src/live/projection.js.map +0 -1
- package/dist/src/live/read.js +0 -60
- package/dist/src/live/read.js.map +0 -1
- package/dist/src/onboard/launch.js +0 -75
- package/dist/src/onboard/launch.js.map +0 -1
- package/dist/src/setup/ledger.js +0 -207
- package/dist/src/setup/ledger.js.map +0 -1
- package/dist/src/setup/stage2.js +0 -617
- package/dist/src/setup/stage2.js.map +0 -1
- package/dist/src/setup/textsniff.js +0 -89
- package/dist/src/setup/textsniff.js.map +0 -1
- package/src/live/projection.ts +0 -491
- package/src/live/read.ts +0 -86
- package/src/onboard/launch.ts +0 -103
- package/src/setup/ledger.ts +0 -324
- package/src/setup/stage2.ts +0 -926
- package/src/setup/textsniff.ts +0 -95
package/docker-compose.yml
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Nothing here builds the app or needs a checkout: `daemon` and `memory` are
|
|
7
7
|
# pulled, `QCR_VERSION` pins them, and the project directory holds this file,
|
|
8
8
|
# a .env and (for a tunnel) docker/tunnel/ -- all of which the image carries and
|
|
9
|
-
# the onboarding wizard hands you the command to extract (
|
|
9
|
+
# the onboarding wizard hands you the command to extract (its step 70):
|
|
10
10
|
#
|
|
11
11
|
# docker run --rm --entrypoint tar ghcr.io/quickchatai/quickreview:<version> \
|
|
12
12
|
# -C /opt/qcr -cf - docker-compose.yml docker/tunnel | tar -xf -
|
|
@@ -170,6 +170,16 @@ services:
|
|
|
170
170
|
# pi's home: OAuth tokens (auth.json), session JSONLs, models.json --
|
|
171
171
|
# must survive container recreates (sticky sessions + /login tokens).
|
|
172
172
|
- qcr-pihome:/home/qcr/.pi
|
|
173
|
+
# The OpenAI login, on a single box: the host pi home's agent dir, the
|
|
174
|
+
# one the home signed in to, so the box has one login (FT-0658). The
|
|
175
|
+
# home's switch-on writes QCR_PI_LOGIN_DIR into the project .env and
|
|
176
|
+
# turns [login_refresh] on: the daemon's refresher then owns that
|
|
177
|
+
# auth.json while auto-review runs, and the home's pi takes it back when
|
|
178
|
+
# auto-review stops (src/home/loginowner.ts). Only the login is read
|
|
179
|
+
# there; pi's settings stay in the volume above. Unset (the VM, a
|
|
180
|
+
# wizard-made stack), a volume of its own nobody reads, and the daemon
|
|
181
|
+
# keeps its login in pi's home above.
|
|
182
|
+
- ${QCR_PI_LOGIN_DIR:-qcr-login}:/opt/qcr/login
|
|
173
183
|
# Everything else the daemon writes under the read-only app root, the
|
|
174
184
|
# same set the VM's run spec mounts (deploy/qcr-update.sh; the two are
|
|
175
185
|
# held equal by tests/compose-stack.test.ts): the PR worktrees, the
|
|
@@ -200,6 +210,9 @@ services:
|
|
|
200
210
|
QCR_SECRETS_MOUNT: /opt/quickreview/secrets
|
|
201
211
|
# The uid the model's tools run as: never QCR_UID.
|
|
202
212
|
QCR_TOOLS_AS: ${QCR_TOOLS_AS:-65534}
|
|
213
|
+
# Where the login is (src/loginrefresh/supervisor.ts loginDirOf): the
|
|
214
|
+
# mount above when the host's login is shared, else empty (pi's home).
|
|
215
|
+
QCR_LOGIN_DIR: ${QCR_PI_LOGIN_DIR:+/opt/qcr/login}
|
|
203
216
|
|
|
204
217
|
# ---- the memory sidecar --------------------------------------------------
|
|
205
218
|
# OpenViking, its venv, llama-cpp-python and the pinned GGUF in a container
|
|
@@ -341,6 +354,8 @@ volumes:
|
|
|
341
354
|
qcr-verdict:
|
|
342
355
|
qcr-sessions-home:
|
|
343
356
|
qcr-base-clone:
|
|
357
|
+
# The login mount's stand-in while no host login is shared (see the daemon).
|
|
358
|
+
qcr-login:
|
|
344
359
|
# The memory store, the sidecar's alone.
|
|
345
360
|
qcr-memory:
|
|
346
361
|
# Shared by the daemon (read) and a tunnel sidecar (write): the hostname
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The env shim for the home's extension: the one place the process's env
|
|
2
|
+
// becomes its options (extensions/home.ts). A bare `qcr` loads it by path
|
|
3
|
+
// (src/home/launch.ts), with the model and tool set it launched on in the env.
|
|
4
|
+
import { VERSION, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { homeOptionsFromEnv } from "../../src/home/adapters.js";
|
|
6
|
+
import { homeSetupFromEnv } from "../../src/home/setupadapters.js";
|
|
7
|
+
import { createHome } from "../home.js";
|
|
8
|
+
|
|
9
|
+
export default function activate(pi: ExtensionAPI): void {
|
|
10
|
+
void createHome({
|
|
11
|
+
...homeOptionsFromEnv(process.env),
|
|
12
|
+
setup: homeSetupFromEnv(process.env, VERSION),
|
|
13
|
+
})(pi);
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The env shim for the app's /selfimprove (extensions/selfimprove.ts): the
|
|
2
|
+
// one place the process's env, home and standard output become its config.
|
|
3
|
+
// `qcr pi` loads this file beside the plugin.
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { VERSION, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { nodeRunner } from "@quickchatai/pi-quickreview/src/plugin/runner.js";
|
|
7
|
+
import { createSelfImprove } from "../selfimprove.js";
|
|
8
|
+
|
|
9
|
+
export default function activate(pi: ExtensionAPI): void {
|
|
10
|
+
const env = process.env;
|
|
11
|
+
createSelfImprove({
|
|
12
|
+
env,
|
|
13
|
+
home: (env["HOME"] ?? "").trim() || homedir(),
|
|
14
|
+
runner: nodeRunner(env),
|
|
15
|
+
print: (text) => void process.stdout.write(text.endsWith("\n") ? text : `${text}\n`),
|
|
16
|
+
piVersion: VERSION,
|
|
17
|
+
})(pi);
|
|
18
|
+
}
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// home — the app's home: the panel at the top right of the app's own pi, and
|
|
2
|
+
// the `home_*` tools. Loaded by a bare `qcr` (src/home/launch.ts) through its
|
|
3
|
+
// env shim, extensions/cli/home.ts, beside the plugin; a factory over explicit
|
|
4
|
+
// options, so it reads no process state.
|
|
5
|
+
//
|
|
6
|
+
// Every action is one HomeModel method (src/home/model.ts), called by a key
|
|
7
|
+
// and by a tool alike:
|
|
8
|
+
//
|
|
9
|
+
// action key, while typing key, panel focused tool
|
|
10
|
+
// move the cursor alt+j / alt+k j / k, ↓ / ↑ home_move
|
|
11
|
+
// select a PR (hover, click) (hover, click) home_select
|
|
12
|
+
// open it on GitHub alt+o o home_open_pr
|
|
13
|
+
// read it all again alt+u u home_refresh
|
|
14
|
+
// review it alt+r ⏎, r home_review_pr
|
|
15
|
+
// show its review alt+r (reviewed) ⏎ (reviewed) home_show_review
|
|
16
|
+
// stop its review alt+s s home_stop_review
|
|
17
|
+
// what it shows — — home_status
|
|
18
|
+
// auto-review on/off alt+a a home_auto_review
|
|
19
|
+
// memory on/off alt+m m home_memory
|
|
20
|
+
// sign in again alt+l l home_relogin
|
|
21
|
+
//
|
|
22
|
+
// Signing in again is for a login that expired or was revoked: the login
|
|
23
|
+
// line says "login expired" (src/home/loginowner.ts reads the refresher's
|
|
24
|
+
// record, pi's own error in the chat and auto-review's pause). It is pi's
|
|
25
|
+
// device login, with auto-review's login refresher paused around it when it
|
|
26
|
+
// runs, so the box keeps one login that one process refreshes.
|
|
27
|
+
// tune to this repo alt+t t home_tune,
|
|
28
|
+
// home_tune_write
|
|
29
|
+
// advanced alt+x x home_advanced
|
|
30
|
+
//
|
|
31
|
+
// The two switches ask the person every question in the home (pi's select):
|
|
32
|
+
// a tool starts one but cannot answer it, so the confirm that turns
|
|
33
|
+
// auto-review on (in dry-run) and going live's own, after its proof round,
|
|
34
|
+
// are the person's (invariant 6).
|
|
35
|
+
//
|
|
36
|
+
// The review runs detached (the app's `qcr review start`, on the person's own
|
|
37
|
+
// pi login), writes its report to a file and never posts by itself; once it
|
|
38
|
+
// is done the person's posting rule (pi's settings, `quickreview.posting`)
|
|
39
|
+
// applies, asking with "No, keep it here" first; a rule pi's settings spell
|
|
40
|
+
// wrong is said, as the plugin says it. A review's findings go to the chat as
|
|
41
|
+
// the plugin's review message: shown, never a turn of its own, so a review
|
|
42
|
+
// that finishes while the agent works never steers it.
|
|
43
|
+
// next setup step alt+n ⏎ (on the card) home_setup_next
|
|
44
|
+
// each setup step — — home_sign_in,
|
|
45
|
+
// home_check_github,
|
|
46
|
+
// home_choose_repo
|
|
47
|
+
//
|
|
48
|
+
// On a machine that is not set up, the setup checklist (src/home/setup.ts)
|
|
49
|
+
// shows as a centred card (src/home/setupcard.ts) instead of the panel; when
|
|
50
|
+
// its last step is done the card gives way to the panel, and back again if a
|
|
51
|
+
// read shows a step missing once more.
|
|
52
|
+
//
|
|
53
|
+
// f2 or a click focuses the panel for its plain keys, and Esc gives the
|
|
54
|
+
// editor back: the panel never takes the keyboard by itself (nonCapturing).
|
|
55
|
+
// The first frame is drawn before any port is read; each read lands into it.
|
|
56
|
+
//
|
|
57
|
+
// The session's end (a quit, or /new replacing it) makes pi's context for it
|
|
58
|
+
// stale: the home lets go of it there, and a read still in flight lands into
|
|
59
|
+
// nothing.
|
|
60
|
+
import type {
|
|
61
|
+
ExtensionAPI,
|
|
62
|
+
ExtensionContext,
|
|
63
|
+
ExtensionFactory,
|
|
64
|
+
} from "@earendil-works/pi-coding-agent";
|
|
65
|
+
import type { KeyId, OverlayHandle, TUI } from "@earendil-works/pi-tui";
|
|
66
|
+
import {
|
|
67
|
+
registerQcrTool,
|
|
68
|
+
textResult,
|
|
69
|
+
type QcrTool,
|
|
70
|
+
} from "@quickchatai/pi-quickreview/extensions/pi-tool.js";
|
|
71
|
+
import { REVIEW_MESSAGE } from "@quickchatai/pi-quickreview/src/plugin/messages.js";
|
|
72
|
+
import { readQuickReviewSettings } from "@quickchatai/pi-quickreview/src/plugin/settings.js";
|
|
73
|
+
import type { HomeToolSet } from "../src/config.js";
|
|
74
|
+
import { piDeviceLogin } from "../src/home/devicelogin.js";
|
|
75
|
+
import { Relogin, RELOGIN_KEY, isLoginError } from "../src/home/loginowner.js";
|
|
76
|
+
import { HomeAdvanced, type AdvancedAsk, type AdvancedPort } from "../src/home/advanced.js";
|
|
77
|
+
import { advancedTools } from "../src/home/advancedtools.js";
|
|
78
|
+
import { HomeModel } from "../src/home/model.js";
|
|
79
|
+
import { HOME_OVERLAY, HomePanel } from "../src/home/panel.js";
|
|
80
|
+
import type { HomeAdapters } from "../src/home/ports.js";
|
|
81
|
+
import type { Ask } from "../src/home/switches.js";
|
|
82
|
+
import { HomeSetup, type HomeSetupPorts, type SetupUi } from "../src/home/setup.js";
|
|
83
|
+
import { SETUP_OVERLAY, SetupCard } from "../src/home/setupcard.js";
|
|
84
|
+
import { setupTools } from "../src/home/setuptools.js";
|
|
85
|
+
import { HOME_TOOL_NAMES } from "../src/home/toolnames.js";
|
|
86
|
+
import { HomeTune, type TunePort } from "../src/home/tune.js";
|
|
87
|
+
|
|
88
|
+
type HomeToolName = (typeof HOME_TOOL_NAMES)[number];
|
|
89
|
+
|
|
90
|
+
export interface HomeOptions {
|
|
91
|
+
adapters: HomeAdapters;
|
|
92
|
+
/** The model the home was launched on, `provider/id`. */
|
|
93
|
+
model: string;
|
|
94
|
+
/** The tool set it was launched with, shown on the login line. */
|
|
95
|
+
tools: HomeToolSet;
|
|
96
|
+
/** Run `fn` every `ms` until the returned stop is called. */
|
|
97
|
+
every?: (ms: number, fn: () => void) => () => void;
|
|
98
|
+
/** Run `fn` once after `ms`; the returned function cancels it. */
|
|
99
|
+
after?: (ms: number, fn: () => void) => () => void;
|
|
100
|
+
/** The setup checklist's ports beyond the home's own. */
|
|
101
|
+
setup?: HomeSetupPorts;
|
|
102
|
+
/** "Tune to this repo": Setup over a checkout (src/home/tune.ts). */
|
|
103
|
+
tune?: TunePort;
|
|
104
|
+
/** Advanced: the wizard's steps beyond switch-on (src/home/advanced.ts). */
|
|
105
|
+
advanced?: AdvancedPort;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** How often the home reads its sources again by itself. */
|
|
109
|
+
export const REFRESH_MS = 60_000;
|
|
110
|
+
const SPIN_MS = 100;
|
|
111
|
+
|
|
112
|
+
const realEvery = (ms: number, fn: () => void): (() => void) => {
|
|
113
|
+
const t = setInterval(fn, ms);
|
|
114
|
+
t.unref?.();
|
|
115
|
+
return () => clearInterval(t);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const realAfter = (ms: number, fn: () => void): (() => void) => {
|
|
119
|
+
const t = setTimeout(fn, ms);
|
|
120
|
+
t.unref?.();
|
|
121
|
+
return () => clearTimeout(t);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const NO_PARAMS = { type: "object", properties: {}, additionalProperties: false } as const;
|
|
125
|
+
const PR_PARAM = {
|
|
126
|
+
type: "integer",
|
|
127
|
+
minimum: 1,
|
|
128
|
+
description: "The pull request's number.",
|
|
129
|
+
} as const;
|
|
130
|
+
|
|
131
|
+
export function createHome(o: HomeOptions): ExtensionFactory {
|
|
132
|
+
return (pi: ExtensionAPI) => {
|
|
133
|
+
const every = o.every ?? realEvery;
|
|
134
|
+
let tui: TUI | undefined;
|
|
135
|
+
let handle: OverlayHandle | undefined;
|
|
136
|
+
let close: (() => void) | undefined;
|
|
137
|
+
let mounted: "setup" | "home" | undefined;
|
|
138
|
+
let session: ExtensionContext | undefined;
|
|
139
|
+
let frame = 0;
|
|
140
|
+
/** The session's UI, for the switches' questions. */
|
|
141
|
+
let sessionUi: Ask | undefined;
|
|
142
|
+
const askOf = (ctx: ExtensionContext | undefined): Ask | undefined =>
|
|
143
|
+
ctx?.hasUI ? ctx.ui : sessionUi;
|
|
144
|
+
const stops: Array<() => void> = [];
|
|
145
|
+
const redraw = (): void => tui?.requestRender();
|
|
146
|
+
// the session's UI, for the post question; none outside the TUI
|
|
147
|
+
let ui: ExtensionContext["ui"] | undefined;
|
|
148
|
+
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
149
|
+
// a read nobody awaits: what went wrong is said, while the session lasts
|
|
150
|
+
const refresh = (): void => {
|
|
151
|
+
model.refresh().catch((err: unknown) => {
|
|
152
|
+
if (session) model.say(`Couldn't read everything again: ${message(err)}`, "warn");
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
const model: HomeModel = new HomeModel({
|
|
156
|
+
adapters: o.adapters,
|
|
157
|
+
model: o.model,
|
|
158
|
+
tools: o.tools,
|
|
159
|
+
onChange: redraw,
|
|
160
|
+
after: o.after ?? realAfter,
|
|
161
|
+
ask: async (question, options) => ui?.select(question, options),
|
|
162
|
+
// shown, never a turn of its own: while the agent streams, the message
|
|
163
|
+
// waits for the turn to end instead of steering it
|
|
164
|
+
send: (m) =>
|
|
165
|
+
pi.sendMessage(
|
|
166
|
+
{ customType: REVIEW_MESSAGE, content: m.content, display: true, details: m.details },
|
|
167
|
+
{ triggerTurn: false },
|
|
168
|
+
),
|
|
169
|
+
postingRule: () => {
|
|
170
|
+
let all: unknown;
|
|
171
|
+
try {
|
|
172
|
+
all = pi.getSettings();
|
|
173
|
+
} catch {
|
|
174
|
+
all = undefined;
|
|
175
|
+
}
|
|
176
|
+
const read = readQuickReviewSettings(all);
|
|
177
|
+
for (const w of read.warnings) warn(w);
|
|
178
|
+
return read.settings.posting;
|
|
179
|
+
},
|
|
180
|
+
gaps: (): string[] => setup.gaps(),
|
|
181
|
+
onRead: (): Promise<void> => setup.sync(),
|
|
182
|
+
});
|
|
183
|
+
// a word to the person: pi's notice, else the panel's line
|
|
184
|
+
const warn = (text: string): void => {
|
|
185
|
+
if (session?.hasUI) session.ui.notify(text, "warning");
|
|
186
|
+
else model.say(text, "warn");
|
|
187
|
+
};
|
|
188
|
+
const setup: HomeSetup = new HomeSetup({
|
|
189
|
+
model,
|
|
190
|
+
github: o.adapters.github,
|
|
191
|
+
ports: o.setup ?? {},
|
|
192
|
+
deviceLogin: () => deviceLogin(),
|
|
193
|
+
onChange: redraw,
|
|
194
|
+
onPhase: () => {
|
|
195
|
+
if (session?.mode === "tui") mount(session);
|
|
196
|
+
},
|
|
197
|
+
interactive: () => session?.mode === "tui",
|
|
198
|
+
});
|
|
199
|
+
const deviceLogin = () =>
|
|
200
|
+
o.setup?.deviceLogin ?? (session ? piDeviceLogin(session.modelRegistry) : undefined);
|
|
201
|
+
const relogin = new Relogin({
|
|
202
|
+
model,
|
|
203
|
+
owner: o.adapters.loginOwner,
|
|
204
|
+
deviceLogin,
|
|
205
|
+
onChange: redraw,
|
|
206
|
+
});
|
|
207
|
+
const tune = new HomeTune({
|
|
208
|
+
...(o.tune ? { port: o.tune } : {}),
|
|
209
|
+
repo: () => model.repo(),
|
|
210
|
+
say: (text, kind) => model.say(text, kind),
|
|
211
|
+
after: o.after ?? realAfter,
|
|
212
|
+
onChange: redraw,
|
|
213
|
+
});
|
|
214
|
+
const advanced = new HomeAdvanced({
|
|
215
|
+
...(o.advanced ? { port: o.advanced } : {}),
|
|
216
|
+
...(o.adapters.autoReview.control ? { autoReview: o.adapters.autoReview.control } : {}),
|
|
217
|
+
say: (text, kind) => model.say(text, kind),
|
|
218
|
+
refresh: () => model.refresh(),
|
|
219
|
+
selected: () => model.selected,
|
|
220
|
+
});
|
|
221
|
+
// Setup's brief goes to the chat as the person's message, after the
|
|
222
|
+
// agent's turn when one is running
|
|
223
|
+
const tellAgent = (text: string): void => pi.sendUserMessage(text, { deliverAs: "followUp" });
|
|
224
|
+
const advancedAsk = (ctx: ExtensionContext | undefined): AdvancedAsk | undefined =>
|
|
225
|
+
ctx?.hasUI ? ctx.ui : session?.hasUI ? session.ui : undefined;
|
|
226
|
+
const dialogs = (ctx: ExtensionContext | undefined): SetupUi | undefined =>
|
|
227
|
+
ctx?.hasUI
|
|
228
|
+
? {
|
|
229
|
+
select: (title, options) => ctx.ui.select(title, options),
|
|
230
|
+
input: (title, placeholder) => ctx.ui.input(title, placeholder),
|
|
231
|
+
}
|
|
232
|
+
: undefined;
|
|
233
|
+
const nextStep = (ctx: ExtensionContext | undefined): Promise<string> =>
|
|
234
|
+
setup.next(dialogs(ctx));
|
|
235
|
+
|
|
236
|
+
// The card while setup has a gap, the panel otherwise; a change of phase
|
|
237
|
+
// closes the one and opens the other.
|
|
238
|
+
const mount = (ctx: ExtensionContext): void => {
|
|
239
|
+
const want = setup.phase();
|
|
240
|
+
if (close && mounted === want) return;
|
|
241
|
+
close?.();
|
|
242
|
+
close = undefined;
|
|
243
|
+
handle = undefined;
|
|
244
|
+
mounted = want;
|
|
245
|
+
const focused = (): boolean => handle?.isFocused() ?? false;
|
|
246
|
+
void ctx.ui.custom<void>(
|
|
247
|
+
(t, theme, _keys, done) => {
|
|
248
|
+
tui = t;
|
|
249
|
+
close = () => done();
|
|
250
|
+
return want === "setup"
|
|
251
|
+
? new SetupCard(theme, {
|
|
252
|
+
setup,
|
|
253
|
+
model,
|
|
254
|
+
focused,
|
|
255
|
+
rows: () => t.terminal.rows,
|
|
256
|
+
unfocus: () => handle?.unfocus(),
|
|
257
|
+
frame: () => frame,
|
|
258
|
+
next: () => void nextStep(session),
|
|
259
|
+
})
|
|
260
|
+
: new HomePanel(theme, {
|
|
261
|
+
model,
|
|
262
|
+
focused,
|
|
263
|
+
rows: () => t.terminal.rows,
|
|
264
|
+
unfocus: () => handle?.unfocus(),
|
|
265
|
+
frame: () => frame,
|
|
266
|
+
ask: () => sessionUi,
|
|
267
|
+
relogin: () => void relogin.start(),
|
|
268
|
+
signingAgain: () => relogin.running() !== null,
|
|
269
|
+
tune: () => void tune.tune(tellAgent),
|
|
270
|
+
advanced: () => void advanced.open(advancedAsk(session)),
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
overlay: true,
|
|
275
|
+
overlayOptions:
|
|
276
|
+
want === "setup"
|
|
277
|
+
? { ...SETUP_OVERLAY }
|
|
278
|
+
: { ...HOME_OVERLAY, margin: { ...HOME_OVERLAY.margin } },
|
|
279
|
+
onHandle: (h) => (handle = h),
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
pi.on("session_start", (_e, ctx) => {
|
|
285
|
+
session = ctx;
|
|
286
|
+
model.setChatModel(ctx.model?.provider);
|
|
287
|
+
ui = ctx.mode === "tui" ? ctx.ui : undefined;
|
|
288
|
+
sessionUi = ctx.hasUI ? ctx.ui : undefined;
|
|
289
|
+
if (ctx.mode === "tui") {
|
|
290
|
+
mount(ctx);
|
|
291
|
+
// the spinner turns only while a read or a setup step is in flight
|
|
292
|
+
stops.push(
|
|
293
|
+
every(SPIN_MS, () => {
|
|
294
|
+
if (!model.busy() && !setup.busy() && relogin.running() === null) return;
|
|
295
|
+
frame++;
|
|
296
|
+
redraw();
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
stops.push(every(REFRESH_MS, refresh));
|
|
301
|
+
refresh();
|
|
302
|
+
});
|
|
303
|
+
pi.on("model_select", (e) => model.setChatModel(e.model.provider));
|
|
304
|
+
// pi's own word on the login: a reply refused for it, or one that came back
|
|
305
|
+
pi.on("message_end", (e) => {
|
|
306
|
+
const m = e.message as { role?: unknown; stopReason?: unknown; errorMessage?: unknown };
|
|
307
|
+
if (m.role !== "assistant") return;
|
|
308
|
+
if (m.stopReason === "error") {
|
|
309
|
+
if (typeof m.errorMessage === "string" && isLoginError(m.errorMessage))
|
|
310
|
+
model.loginFailed(m.errorMessage);
|
|
311
|
+
} else if (m.stopReason !== "aborted") model.chatAnswered();
|
|
312
|
+
});
|
|
313
|
+
pi.on("session_shutdown", () => {
|
|
314
|
+
// the session's timers and its pricing stop; a running review goes on
|
|
315
|
+
model.dispose();
|
|
316
|
+
relogin.close();
|
|
317
|
+
tune.dispose();
|
|
318
|
+
ui = undefined;
|
|
319
|
+
for (const stop of stops.splice(0)) stop();
|
|
320
|
+
setup.close();
|
|
321
|
+
close?.();
|
|
322
|
+
close = undefined;
|
|
323
|
+
mounted = undefined;
|
|
324
|
+
// pi's context for this session is stale now: nothing may reach it
|
|
325
|
+
session = undefined;
|
|
326
|
+
sessionUi = undefined;
|
|
327
|
+
tui = undefined;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const key = (
|
|
331
|
+
id: string,
|
|
332
|
+
description: string,
|
|
333
|
+
run: (ctx: ExtensionContext | undefined) => unknown,
|
|
334
|
+
): void =>
|
|
335
|
+
pi.registerShortcut(id as KeyId, {
|
|
336
|
+
description,
|
|
337
|
+
handler: (ctx) => {
|
|
338
|
+
void new Promise((resolve) => resolve(run(ctx))).catch((err: unknown) => {
|
|
339
|
+
if (session) model.say(message(err), "warn");
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
key("alt+j", "quickreview home: next pull request", () => model.move(1));
|
|
344
|
+
key("alt+k", "quickreview home: previous pull request", () => model.move(-1));
|
|
345
|
+
key("alt+o", "quickreview home: open the pull request on GitHub", () => model.openPr());
|
|
346
|
+
key("alt+u", "quickreview home: read everything again", () => refresh());
|
|
347
|
+
key("alt+r", "quickreview home: review the pull request, or show its review", () =>
|
|
348
|
+
model.enter(),
|
|
349
|
+
);
|
|
350
|
+
key("alt+s", "quickreview home: stop the pull request's review", () => model.stopReview());
|
|
351
|
+
key("f2", "quickreview home: focus the panel", () => handle?.focus());
|
|
352
|
+
key("alt+a", "quickreview home: switch auto-review on or off", (ctx) =>
|
|
353
|
+
model.switchAutoReview(askOf(ctx)),
|
|
354
|
+
);
|
|
355
|
+
key("alt+m", "quickreview home: switch memory on or off", (ctx) =>
|
|
356
|
+
model.switchMemory(askOf(ctx)),
|
|
357
|
+
);
|
|
358
|
+
key(RELOGIN_KEY, "quickreview home: sign in again (an expired login)", () => relogin.start());
|
|
359
|
+
key("alt+t", "quickreview home: tune the review to this repo (Setup)", () =>
|
|
360
|
+
tune.tune(tellAgent),
|
|
361
|
+
);
|
|
362
|
+
key(
|
|
363
|
+
"alt+x",
|
|
364
|
+
"quickreview home: advanced (a GitHub App, webhooks, a server, Slack, live)",
|
|
365
|
+
(ctx) => advanced.open(advancedAsk(ctx)),
|
|
366
|
+
);
|
|
367
|
+
pi.registerShortcut("alt+n", {
|
|
368
|
+
description: "quickreview home: run the next setup step",
|
|
369
|
+
handler: (ctx) => {
|
|
370
|
+
void nextStep(ctx);
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// one definition per HOME_TOOL_NAMES entry: a name without one does not compile
|
|
375
|
+
const tools: { [N in HomeToolName]: QcrTool & { name: N } } = {
|
|
376
|
+
home_status: {
|
|
377
|
+
name: "home_status",
|
|
378
|
+
label: "Home status",
|
|
379
|
+
description:
|
|
380
|
+
"Read what the quickreview home shows: the repository, the logins, auto-review, memory, spend and the open pull requests (To review, Reviewed). A value that could not be read is null.",
|
|
381
|
+
parameters: NO_PARAMS,
|
|
382
|
+
execute: () => textResult(JSON.stringify(model.status(), null, 1)),
|
|
383
|
+
},
|
|
384
|
+
home_refresh: {
|
|
385
|
+
name: "home_refresh",
|
|
386
|
+
label: "Home refresh",
|
|
387
|
+
description:
|
|
388
|
+
"Read the home's sources again: the logins, the repository and its open pull requests.",
|
|
389
|
+
parameters: NO_PARAMS,
|
|
390
|
+
execute: async () => textResult(await model.refresh()),
|
|
391
|
+
},
|
|
392
|
+
home_move: {
|
|
393
|
+
name: "home_move",
|
|
394
|
+
label: "Home move",
|
|
395
|
+
description:
|
|
396
|
+
"Move the home's selection through its pull request lists: 1 is the next row, -1 the previous.",
|
|
397
|
+
parameters: {
|
|
398
|
+
type: "object",
|
|
399
|
+
properties: { by: { type: "integer", description: "Rows to move; negative moves up." } },
|
|
400
|
+
required: ["by"],
|
|
401
|
+
additionalProperties: false,
|
|
402
|
+
},
|
|
403
|
+
execute: (_id, p) => textResult(model.move(Number(p["by"]))),
|
|
404
|
+
},
|
|
405
|
+
home_select: {
|
|
406
|
+
name: "home_select",
|
|
407
|
+
label: "Home select",
|
|
408
|
+
description: "Select one open pull request in the home's lists, by number.",
|
|
409
|
+
parameters: {
|
|
410
|
+
type: "object",
|
|
411
|
+
properties: { pr: PR_PARAM },
|
|
412
|
+
required: ["pr"],
|
|
413
|
+
additionalProperties: false,
|
|
414
|
+
},
|
|
415
|
+
execute: (_id, p) => textResult(model.select(Number(p["pr"]))),
|
|
416
|
+
},
|
|
417
|
+
home_open_pr: {
|
|
418
|
+
name: "home_open_pr",
|
|
419
|
+
label: "Home open PR",
|
|
420
|
+
description:
|
|
421
|
+
"Open a pull request on GitHub in the person's browser: the one given, or the home's selected one.",
|
|
422
|
+
parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
|
|
423
|
+
execute: async (_id, p) =>
|
|
424
|
+
textResult(await model.openPr(typeof p["pr"] === "number" ? p["pr"] : undefined)),
|
|
425
|
+
},
|
|
426
|
+
home_review_pr: {
|
|
427
|
+
name: "home_review_pr",
|
|
428
|
+
label: "Home review PR",
|
|
429
|
+
description:
|
|
430
|
+
"Review a pull request with QuickReview in the background: the one given, or the home's selected one. It previews the price, starts the review and returns at once; the home shows its progress and findings, and nothing is posted to GitHub unless the person's posting rule says so.",
|
|
431
|
+
parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
|
|
432
|
+
execute: async (_id, p) =>
|
|
433
|
+
textResult(await model.review(typeof p["pr"] === "number" ? p["pr"] : undefined)),
|
|
434
|
+
},
|
|
435
|
+
home_show_review: {
|
|
436
|
+
name: "home_show_review",
|
|
437
|
+
label: "Home show review",
|
|
438
|
+
description:
|
|
439
|
+
"Put a reviewed pull request's findings in the chat: the one given, or the home's selected one.",
|
|
440
|
+
parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
|
|
441
|
+
execute: (_id, p) =>
|
|
442
|
+
textResult(model.showReview(typeof p["pr"] === "number" ? p["pr"] : undefined)),
|
|
443
|
+
},
|
|
444
|
+
home_stop_review: {
|
|
445
|
+
name: "home_stop_review",
|
|
446
|
+
label: "Home stop review",
|
|
447
|
+
description:
|
|
448
|
+
"Stop the running QuickReview review of a pull request: the one given, or the home's selected one.",
|
|
449
|
+
parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
|
|
450
|
+
execute: async (_id, p) =>
|
|
451
|
+
textResult(await model.stopReview(typeof p["pr"] === "number" ? p["pr"] : undefined)),
|
|
452
|
+
},
|
|
453
|
+
home_auto_review: {
|
|
454
|
+
name: "home_auto_review",
|
|
455
|
+
label: "Home auto-review",
|
|
456
|
+
description:
|
|
457
|
+
"Switch auto-review on or off. Switching on checks this machine, then asks the person where it runs, whether to turn memory on too, and for their one confirm, and it comes up in dry-run; switching off warns them which review is running, and from dry-run offers going live (a proof round on the selected pull request, then one more confirm). Every question is the person's to answer in the home: this tool cannot answer or confirm for them.",
|
|
458
|
+
parameters: NO_PARAMS,
|
|
459
|
+
execute: async (_id, _p, _signal, _onUpdate, ctx) =>
|
|
460
|
+
textResult(await model.switchAutoReview(askOf(ctx))),
|
|
461
|
+
},
|
|
462
|
+
home_memory: {
|
|
463
|
+
name: "home_memory",
|
|
464
|
+
label: "Home memory",
|
|
465
|
+
description:
|
|
466
|
+
"Switch reviewer memory on or off, on its own: auto-review is not touched. The person confirms in the home, after seeing its RAM cost.",
|
|
467
|
+
parameters: NO_PARAMS,
|
|
468
|
+
execute: async (_id, _p, _signal, _onUpdate, ctx) =>
|
|
469
|
+
textResult(await model.switchMemory(askOf(ctx))),
|
|
470
|
+
},
|
|
471
|
+
home_relogin: {
|
|
472
|
+
name: "home_relogin",
|
|
473
|
+
label: "Home sign in again",
|
|
474
|
+
description:
|
|
475
|
+
"Sign in again with pi's own device-code login, for the provider the chat runs on, when the login has expired or was revoked (the home's login line says \"login expired\"). When auto-review runs on this machine its login refresher is paused during the sign-in and resumes on the new login. Answers with the URL and the code to enter once pi has them; the sign-in finishes in the background when the person approves it.",
|
|
476
|
+
parameters: NO_PARAMS,
|
|
477
|
+
execute: async () => textResult(await relogin.start()),
|
|
478
|
+
},
|
|
479
|
+
...setupTools(setup),
|
|
480
|
+
...advancedTools({ tune, advanced, tellAgent, ask: (ctx) => advancedAsk(ctx) }),
|
|
481
|
+
};
|
|
482
|
+
for (const name of HOME_TOOL_NAMES) registerQcrTool(pi, tools[name]);
|
|
483
|
+
};
|
|
484
|
+
}
|
|
@@ -190,7 +190,7 @@ export function openLogin(ui: HintUi, open: LoginOpen): "submitted" | "prefilled
|
|
|
190
190
|
|
|
191
191
|
/** Register the model route and the login hint on `pi`. Exported so a host that
|
|
192
192
|
* builds its own onboarding extension (over `toolDefinitions`) gets the same
|
|
193
|
-
* first-run login as
|
|
193
|
+
* first-run login as the onboarding wizard. */
|
|
194
194
|
export function registerLoginRoute(pi: ExtensionAPI, config: LoginRouteConfig): void {
|
|
195
195
|
if (typeof pi.on !== "function") return;
|
|
196
196
|
let shown = false;
|
package/extensions/onboarding.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
// onboarding — the wizard's tool belt. Loaded ONLY by
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// onboarding — the wizard's tool belt. Loaded ONLY by a pi started with it
|
|
2
|
+
// named by path (its env shim, extensions/cli/onboarding.ts, as `-e` alongside
|
|
3
|
+
// --no-extensions, so no ambient extension rides that session), never
|
|
4
|
+
// registered globally. No qcr verb starts such a session any more: the home
|
|
5
|
+
// runs the wizard's steps itself (src/home/switchon.ts, Advanced). pi jiti-loads this TS source
|
|
4
6
|
// on the HOST, so it imports the OnboardingEngine source directly and runs it
|
|
5
7
|
// in-process: pi talks, these deterministic tools act, and the LLM never
|
|
6
8
|
// freestyles a shell command (the built-in belt is read-only by argv).
|
|
@@ -11,8 +13,7 @@
|
|
|
11
13
|
// - finish_and_exit: re-runs the FULL verification; anything red comes back
|
|
12
14
|
// as the tool result (no exit, ever, while red); all green marks the
|
|
13
15
|
// checklist complete, writes the companion's first-run marker
|
|
14
|
-
// (state/companion/first-run.json), and triggers pi's deferred shutdown
|
|
15
|
-
// the `qcr onboard` process then opens the TUI.
|
|
16
|
+
// (state/companion/first-run.json), and triggers pi's deferred shutdown.
|
|
16
17
|
//
|
|
17
18
|
// Convention notes (same shape as companion-guard/tui-view): degrade, never
|
|
18
19
|
// wedge — a pi without registerTool registers nothing and says so; a missing
|
|
@@ -228,8 +229,8 @@ export function createOnboarding(config: OnboardingConfig): ExtensionFactory {
|
|
|
228
229
|
* The onboarding session's first-run login: when pi has no usable model
|
|
229
230
|
* login, pi's own /login menu opens with a widget that says which logins work
|
|
230
231
|
* (any OpenAI one), and an OpenAI model whose provider has no login runs on
|
|
231
|
-
* the twin login that exists (extensions/login-route.ts).
|
|
232
|
-
* registers it with the tools; a host that builds its own onboarding
|
|
232
|
+
* the twin login that exists (extensions/login-route.ts). The wizard's
|
|
233
|
+
* session registers it with the tools; a host that builds its own onboarding
|
|
233
234
|
* extension over `toolDefinitions` + `registerQcrTool` calls it too.
|
|
234
235
|
*/
|
|
235
236
|
export function registerOnboardingSession(pi: ExtensionAPI): void {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// /selfimprove in the app's own pi (`qcr pi`, src/npmverbs.ts): scan this
|
|
2
|
+
// repo, then have this session's agent fix the findings (src/selfimprove.ts).
|
|
3
|
+
// The plugin, loaded beside it, carries /qcr-review, /qcr-scan and /qcr-setup;
|
|
4
|
+
// this command is the app's (ADR 0001). A factory over its config: the env
|
|
5
|
+
// shim extensions/cli/selfimprove.ts reads the process.
|
|
6
|
+
import {
|
|
7
|
+
getAgentDir,
|
|
8
|
+
type ExtensionAPI,
|
|
9
|
+
type ExtensionCommandContext,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { hostAuth, sessionUi } from "@quickchatai/pi-quickreview/extensions/quickreview.js";
|
|
12
|
+
import type { PluginHost, Runner } from "@quickchatai/pi-quickreview/src/plugin/session.js";
|
|
13
|
+
import { runSelfImproveCommand } from "../src/selfimprove.js";
|
|
14
|
+
|
|
15
|
+
export interface SelfImproveConfig {
|
|
16
|
+
/** The process environment, read once by the shim. */
|
|
17
|
+
env: NodeJS.ProcessEnv;
|
|
18
|
+
/** The user's home. */
|
|
19
|
+
home: string;
|
|
20
|
+
runner: Runner;
|
|
21
|
+
/** Standard output (pi's print mode shows a command's result there). */
|
|
22
|
+
print: (text: string) => void;
|
|
23
|
+
/** Test seams: the running pi's version and its agent dir. */
|
|
24
|
+
piVersion?: string;
|
|
25
|
+
agentDir?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The extension factory: registers /selfimprove, one run at a time. */
|
|
29
|
+
export function createSelfImprove(config: SelfImproveConfig): (pi: ExtensionAPI) => void {
|
|
30
|
+
return (pi) => {
|
|
31
|
+
// The current session's end: a scan in flight stops with it.
|
|
32
|
+
let shutdown = new AbortController();
|
|
33
|
+
pi.on("session_start", () => {
|
|
34
|
+
if (shutdown.signal.aborted) shutdown = new AbortController();
|
|
35
|
+
});
|
|
36
|
+
pi.on("session_shutdown", () => shutdown.abort());
|
|
37
|
+
let busy = false;
|
|
38
|
+
pi.registerCommand("selfimprove", {
|
|
39
|
+
description: "QuickReview: scan this repo, then have this session's agent fix the findings",
|
|
40
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
41
|
+
if (busy) {
|
|
42
|
+
ctx.ui.notify("/selfimprove is already running in this session", "warning");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
busy = true;
|
|
46
|
+
try {
|
|
47
|
+
const agentDir = config.agentDir ?? getAgentDir();
|
|
48
|
+
const host: PluginHost = {
|
|
49
|
+
cwd: ctx.cwd,
|
|
50
|
+
env: config.env,
|
|
51
|
+
home: config.home,
|
|
52
|
+
piVersion: config.piVersion ?? "",
|
|
53
|
+
agentDir,
|
|
54
|
+
auth: hostAuth(ctx, agentDir),
|
|
55
|
+
runner: config.runner,
|
|
56
|
+
shutdown: shutdown.signal,
|
|
57
|
+
};
|
|
58
|
+
await runSelfImproveCommand(args.trim(), host, sessionUi(pi, ctx, config.print));
|
|
59
|
+
} finally {
|
|
60
|
+
busy = false;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
}
|