@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
|
@@ -137,6 +137,10 @@
|
|
|
137
137
|
"post": {
|
|
138
138
|
"$ref": "#/$defs/post"
|
|
139
139
|
},
|
|
140
|
+
"post_ready": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "A finished PR review that was not posted: the file it is kept in, which a host that asks the person later posts through the gate exactly as `--to pr` would have."
|
|
143
|
+
},
|
|
140
144
|
"run_id": {
|
|
141
145
|
"type": "string",
|
|
142
146
|
"description": "A detached run's id (start, status, stop)."
|
|
@@ -149,6 +153,14 @@
|
|
|
149
153
|
"type": "string",
|
|
150
154
|
"description": "What a detached run reviews."
|
|
151
155
|
},
|
|
156
|
+
"started_at": {
|
|
157
|
+
"type": "number",
|
|
158
|
+
"description": "When a detached run was started, ms since the epoch."
|
|
159
|
+
},
|
|
160
|
+
"ended_at": {
|
|
161
|
+
"type": "number",
|
|
162
|
+
"description": "When a detached run ended (its exit or its stop), ms since the epoch; absent while it runs."
|
|
163
|
+
},
|
|
152
164
|
"elapsed_s": {
|
|
153
165
|
"type": "integer",
|
|
154
166
|
"minimum": 0
|
|
@@ -165,6 +177,50 @@
|
|
|
165
177
|
"items": {
|
|
166
178
|
"type": "string"
|
|
167
179
|
}
|
|
180
|
+
},
|
|
181
|
+
"live": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"description": "The running round's live log, projected: what the parent and each lens are doing now.",
|
|
184
|
+
"properties": {
|
|
185
|
+
"round": { "type": "integer", "minimum": 1 },
|
|
186
|
+
"parent_status": { "enum": ["active", "settled"] },
|
|
187
|
+
"last_activity": { "type": "string" },
|
|
188
|
+
"updated_at": { "type": "string" },
|
|
189
|
+
"active_tool": { "type": "string" },
|
|
190
|
+
"agents": {
|
|
191
|
+
"type": "array",
|
|
192
|
+
"items": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"required": ["agent", "status", "duration_ms", "tool_count"],
|
|
195
|
+
"properties": {
|
|
196
|
+
"agent": { "type": "string" },
|
|
197
|
+
"status": { "enum": ["running", "completed", "failed"] },
|
|
198
|
+
"duration_ms": {
|
|
199
|
+
"type": "number",
|
|
200
|
+
"minimum": 0,
|
|
201
|
+
"description": "The lens's time so far; a running lens's counts from its start between updates."
|
|
202
|
+
},
|
|
203
|
+
"tool_count": {
|
|
204
|
+
"type": "number",
|
|
205
|
+
"minimum": 0,
|
|
206
|
+
"description": "Tool calls the lens has started; a finished lens keeps its final count."
|
|
207
|
+
},
|
|
208
|
+
"current_tool": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "The running (or last) tool, as `<tool>: <args>` or the bare name."
|
|
211
|
+
},
|
|
212
|
+
"current_target": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "While the lens runs: the path its current tool works on, when it names one."
|
|
215
|
+
},
|
|
216
|
+
"line": {
|
|
217
|
+
"type": "string",
|
|
218
|
+
"description": "The lens as one line a person reads: `security · read src/x.ts · 7 tools · 0:41`."
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
168
224
|
}
|
|
169
225
|
}
|
|
170
226
|
},
|
|
@@ -234,6 +290,10 @@
|
|
|
234
290
|
"kind": {
|
|
235
291
|
"enum": ["pr", "branch", "uncommitted", "diff"],
|
|
236
292
|
"description": "What is reviewed: a pull request, the current branch against its base, the uncommitted working tree, or a unified diff handed in (`--diff`). Only a pr target can be posted."
|
|
293
|
+
},
|
|
294
|
+
"uncommitted": {
|
|
295
|
+
"const": true,
|
|
296
|
+
"description": "A branch target reviewed together with the working tree's uncommitted edits, as one review. Absent otherwise."
|
|
237
297
|
}
|
|
238
298
|
}
|
|
239
299
|
},
|
package/scripts/deploy.sh
CHANGED
|
@@ -46,9 +46,10 @@
|
|
|
46
46
|
# cloudflared when the inventory enables it), the
|
|
47
47
|
# containers, the image stamp, the pi settings and the
|
|
48
48
|
# reviewer stub, and the state/status.json heartbeat the
|
|
49
|
-
# status collector reads. It also warns 14 days before
|
|
50
|
-
#
|
|
51
|
-
# scripts/vm-ghcr-login.sh recorded
|
|
49
|
+
# status collector reads. It also warns 14 days before a
|
|
50
|
+
# private mirror's ghcr pull token expires (the expiry
|
|
51
|
+
# that scripts/vm-ghcr-login.sh recorded; the public
|
|
52
|
+
# release images need no login, so no record).
|
|
52
53
|
#
|
|
53
54
|
# `sync`, `dev` and `rebuild` are gone (the on-VM rsync and build are deleted):
|
|
54
55
|
# they refuse with a pointer to `release`. A pre-release fix is a release.
|
|
@@ -1083,8 +1084,9 @@ release_problem() {
|
|
|
1083
1084
|
fi
|
|
1084
1085
|
}
|
|
1085
1086
|
|
|
1086
|
-
# The
|
|
1087
|
-
#
|
|
1087
|
+
# The release images are public and pull with no login. A private mirror's
|
|
1088
|
+
# ghcr pull token expires (1 year, by ruling), and a lapsed one fails the
|
|
1089
|
+
# updater's pull. scripts/vm-ghcr-login.sh records the expiry on this
|
|
1088
1090
|
# controller; release and verify read it back. Within GHCR_WARN_DAYS of it
|
|
1089
1091
|
# (or past it) the plan line starts with WARNING. Warn only, never a refusal.
|
|
1090
1092
|
GHCR_WARN_DAYS=14
|
|
@@ -1095,7 +1097,7 @@ ghcr_expiry_status() {
|
|
|
1095
1097
|
local file iso epoch now days
|
|
1096
1098
|
file="$(qcr_ghcr_expiry_file 2>/dev/null)" || file=""
|
|
1097
1099
|
if [[ -z "$file" || ! -s "$file" ]]; then
|
|
1098
|
-
GHCR_EXPIRY_LINE="no
|
|
1100
|
+
GHCR_EXPIRY_LINE="no ghcr login recorded: the release images are public and pull without one (a private mirror logs the VM in with scripts/vm-ghcr-login.sh, which records its expiry)"
|
|
1099
1101
|
return 0
|
|
1100
1102
|
fi
|
|
1101
1103
|
iso="$(head -1 "$file" | tr -d '[:space:]')"
|
package/scripts/qcr-host.sh
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
# `stop` find it by label. `stop` sends `docker stop -t
|
|
13
13
|
# <grace>`, then `docker kill`: the container is the
|
|
14
14
|
# hard-kill backstop for a driver whose loop is frozen.
|
|
15
|
-
# qcr a bare qcr opens the
|
|
15
|
+
# qcr a bare qcr (or `qcr home`) opens the home: the image's
|
|
16
|
+
# pi with the plugin and its panel (home mode)
|
|
17
|
+
# qcr tui the operator TUI (tui mode), the one verb that starts
|
|
18
|
+
# the companion
|
|
16
19
|
# qcr login [provider] pi's own /login, stored in the qcr-login volume
|
|
17
20
|
# qcr update [version] pull a newer image and repin this file
|
|
18
21
|
#
|
|
@@ -21,8 +24,8 @@
|
|
|
21
24
|
# a worktree's common git dir at its own path read-only
|
|
22
25
|
# the qcr-state volume /opt/qcr/state read-write
|
|
23
26
|
# the qcr-login volume /qcr-login read-only
|
|
24
|
-
# (only `login`
|
|
25
|
-
# at pi's agent dir; its files are in
|
|
27
|
+
# (only `login` and the home mount it
|
|
28
|
+
# read-write, at pi's agent dir; its files are in
|
|
26
29
|
# group 4747, which every call joins,
|
|
27
30
|
# so a uid change never loses the login)
|
|
28
31
|
# ~/.config/qcr/profiles /qcr-config/profiles read-only
|
|
@@ -219,11 +222,20 @@ split=1
|
|
|
219
222
|
drop=0
|
|
220
223
|
gh=1
|
|
221
224
|
case "$verb" in
|
|
225
|
+
# The home is the person's own pi, as `qcr` from npm opens it: pi's agent
|
|
226
|
+
# dir on the login volume, read-write (its device login lands there), its
|
|
227
|
+
# config and sessions on the state volume, and the GitHub token for its `gh`.
|
|
222
228
|
'')
|
|
223
|
-
mode=
|
|
229
|
+
mode=home
|
|
224
230
|
tty=1
|
|
225
231
|
split=0
|
|
226
232
|
;;
|
|
233
|
+
home)
|
|
234
|
+
mode=home
|
|
235
|
+
tty=1
|
|
236
|
+
split=0
|
|
237
|
+
drop=1
|
|
238
|
+
;;
|
|
227
239
|
tui)
|
|
228
240
|
mode=tui
|
|
229
241
|
tty=1
|
|
@@ -555,7 +567,8 @@ fi
|
|
|
555
567
|
# ---- the GitHub token (GITHUB_TOKEN, GH_TOKEN, gh; no App on a laptop) -----
|
|
556
568
|
|
|
557
569
|
# The claim call only reads git: it needs no token, and a token file is read once.
|
|
558
|
-
|
|
570
|
+
# The home is handed it too: its `gh` lists and opens the pull requests.
|
|
571
|
+
if { [ "$split" = 1 ] || [ "$mode" = home ]; } && [ "$gh" = 1 ] && [ "$phase" != claim ]; then
|
|
559
572
|
token="${GITHUB_TOKEN:-}"
|
|
560
573
|
[ -n "$token" ] || token="${GH_TOKEN:-}"
|
|
561
574
|
if [ -z "$token" ] && command -v gh >/dev/null 2>&1; then
|
|
@@ -603,6 +616,16 @@ if [ "$mode" = login ]; then
|
|
|
603
616
|
# pi's own /login, written to the login volume: the only read-write mount of
|
|
604
617
|
# it. A login needs no repo, no state and no profiles.
|
|
605
618
|
set -- "$@" --mount "type=volume,src=qcr-login,dst=/home/qcr/.pi/agent"
|
|
619
|
+
elif [ "$mode" = home ]; then
|
|
620
|
+
# The home works on a GitHub repository, not on the directory you stand in:
|
|
621
|
+
# no repo mount. pi's agent dir is the login volume, read-write, as login's
|
|
622
|
+
# is. Its state home, sessions and config.toml (the repository it picks,
|
|
623
|
+
# which it writes) live on the state volume, so they persist and stay
|
|
624
|
+
# writable; the install home is never mounted.
|
|
625
|
+
set -- "$@" --mount "type=volume,src=qcr-login,dst=/home/qcr/.pi/agent"
|
|
626
|
+
set -- "$@" --mount "type=volume,src=qcr-state,dst=/opt/qcr/state"
|
|
627
|
+
set -- "$@" -e QCR_STATE_HOME=/opt/qcr/state/home
|
|
628
|
+
set -- "$@" -e QCR_CONFIG=/opt/qcr/state/home/config.toml
|
|
606
629
|
else
|
|
607
630
|
# The repo, read-only; a worktree's common git dir at its own path.
|
|
608
631
|
set -- "$@" --mount "type=bind,src=$root,dst=/repo,readonly" -w "/repo$rel"
|
package/scripts/vm-ghcr-login.sh
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# vm-ghcr-login.sh — log the review VM in to ghcr.io, so it can pull
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# vm-ghcr-login.sh — log the review VM in to ghcr.io, so it can pull images
|
|
3
|
+
# that are not public: a private mirror named by QC_RELEASE_IMAGE and
|
|
4
|
+
# QC_RELEASE_MEMORY_IMAGE. The release images are public and pull with no
|
|
5
|
+
# login, so a VM on them needs none of this. Run by the owner, on the
|
|
6
|
+
# controller. Idempotent: running it again replaces the login and the recorded
|
|
7
|
+
# expiry.
|
|
5
8
|
#
|
|
6
9
|
# The credential is a CLASSIC personal access token, scoped read:packages and
|
|
7
10
|
# nothing else (the one carve-out from the App-only credential rule, for the
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
# scripts/vm-ghcr-login.sh --version X.Y.Z [--vm HOST] [--login USER] [--token-file FILE]
|
|
27
30
|
# pbpaste | scripts/vm-ghcr-login.sh --version X.Y.Z # token on stdin
|
|
28
31
|
#
|
|
29
|
-
# --version a published
|
|
32
|
+
# --version a published version; both images must exist at it
|
|
30
33
|
# --vm ssh target (default: $QC_VM_HOST, else the inventory's first host)
|
|
31
34
|
# --login the GitHub login to store (default: the token's own; if given
|
|
32
35
|
# it must be the token's)
|
package/src/archive/restore.ts
CHANGED
|
@@ -32,13 +32,7 @@ import { readdir } from "node:fs/promises";
|
|
|
32
32
|
import { join, relative } from "node:path";
|
|
33
33
|
import type { StateStore } from "../state.js";
|
|
34
34
|
import { yieldToLoop } from "@quickchatai/pi-quickreview/src/yieldloop.js";
|
|
35
|
-
import {
|
|
36
|
-
archiveStamp,
|
|
37
|
-
bundlePassOf,
|
|
38
|
-
freshPassId,
|
|
39
|
-
identityFields,
|
|
40
|
-
sameRecorded,
|
|
41
|
-
} from "./archivewrite.js";
|
|
35
|
+
import { archiveStamp, bundlePassOf, freshPassId, identityFields } from "./archivewrite.js";
|
|
42
36
|
import { readBundleRecords } from "./bundle.js";
|
|
43
37
|
import { sameIdentity } from "./fsutil.js";
|
|
44
38
|
import {
|
|
@@ -182,20 +176,23 @@ export async function restorePr(
|
|
|
182
176
|
const steady = sameIdentity(live, after);
|
|
183
177
|
if (steady && d.bytes === r.bytes && d.sha256 === r.sha256) {
|
|
184
178
|
out.present += 1;
|
|
185
|
-
// Its identity is recorded
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
179
|
+
// Its identity is recorded, always. A file an earlier restore pass
|
|
180
|
+
// put back — one that died before its entry was written — is
|
|
181
|
+
// unknown to the manifest otherwise, and a re-archive after a
|
|
182
|
+
// re-close would compress the same bytes again under `.v2` instead
|
|
183
|
+
// of reusing the archived copy. Its identity is no sign of whether
|
|
184
|
+
// it was ever unlinked: the filesystem hands a freed inode number
|
|
185
|
+
// back, and the restore gave the file its original size and mtime,
|
|
186
|
+
// so a file put back can carry exactly the identity the archive
|
|
187
|
+
// record holds. Recording a source that was never unlinked costs
|
|
188
|
+
// one `reused` entry at the re-archive, nothing more.
|
|
189
|
+
restored.push({
|
|
190
|
+
source: r.source,
|
|
191
|
+
archive: r.archive,
|
|
192
|
+
sha256: r.sha256,
|
|
193
|
+
...identityFields(live),
|
|
194
|
+
present: true,
|
|
195
|
+
});
|
|
199
196
|
} else out.liveDiffers.push(r.source);
|
|
200
197
|
continue;
|
|
201
198
|
}
|
package/src/botworker.ts
CHANGED
|
@@ -439,7 +439,14 @@ export async function runBotWorker(args: WorkerArgs, deps: WorkerDeps): Promise<
|
|
|
439
439
|
// The login: borrowed, blanked, and checked against this worker's own cap.
|
|
440
440
|
let auth: EngineAuth;
|
|
441
441
|
try {
|
|
442
|
-
auth = engineCredentials({
|
|
442
|
+
auth = engineCredentials({
|
|
443
|
+
srcDir: spec.login_dir ?? spec.agent_dir,
|
|
444
|
+
needMs: spec.wall_clock_ms,
|
|
445
|
+
now,
|
|
446
|
+
// a shared pi home holds the person's other logins too: only the
|
|
447
|
+
// review's providers are admitted
|
|
448
|
+
...(spec.providers !== undefined ? { providers: spec.providers } : {}),
|
|
449
|
+
});
|
|
443
450
|
} catch (e) {
|
|
444
451
|
if (e instanceof AuthError) throw new Stop(EXIT.auth, e.message);
|
|
445
452
|
throw e;
|
|
@@ -461,7 +468,11 @@ export async function runBotWorker(args: WorkerArgs, deps: WorkerDeps): Promise<
|
|
|
461
468
|
ownsProcess: true,
|
|
462
469
|
homeDir: homedir(),
|
|
463
470
|
...(ids !== null && !("refused" in ids) ? { toolUid: ids } : {}),
|
|
464
|
-
fenceDirs: [
|
|
471
|
+
fenceDirs: [
|
|
472
|
+
...loginSourceDirs(env),
|
|
473
|
+
...(spec.login_dir ? [spec.login_dir] : []),
|
|
474
|
+
...credentialPaths(env, spec.env),
|
|
475
|
+
],
|
|
465
476
|
credentials: auth.credentials,
|
|
466
477
|
settings: agentDirSettings(spec.agent_dir),
|
|
467
478
|
});
|
package/src/channelfs.ts
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
// imports (scanreport.ts, stage2request.ts) share it without reaching each
|
|
5
|
-
// other at runtime.
|
|
6
|
-
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { dirname } from "node:path";
|
|
8
|
-
|
|
9
|
-
/** A file's text, or null when it is ABSENT — any other failure throws, so a
|
|
10
|
-
* reader can tell "not written yet" from "cannot be read". */
|
|
11
|
-
export function readTextIfPresent(path: string): string | null {
|
|
12
|
-
try {
|
|
13
|
-
return readFileSync(path, "utf8");
|
|
14
|
-
} catch (err) {
|
|
15
|
-
if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
16
|
-
throw err;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** tmp+rename: a reader never sees a half-written file. */
|
|
21
|
-
export function writeAtomic(path: string, text: string): void {
|
|
22
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
-
const tmp = `${path}.${process.pid}.tmp`;
|
|
24
|
-
writeFileSync(tmp, text);
|
|
25
|
-
renameSync(tmp, path);
|
|
26
|
-
}
|
|
1
|
+
// Moved to the plugin (packages/pi-plugin/src/scan/channelfs.ts): this name
|
|
2
|
+
// stays for the app's importers.
|
|
3
|
+
export * from "@quickchatai/pi-quickreview/src/scan/channelfs.js";
|
package/src/cli.ts
CHANGED
|
@@ -531,6 +531,8 @@ function drainHolderLine(snapshot: StatusSnapshot): string {
|
|
|
531
531
|
/** What lifts the drain that stands. A snapshot with no hold marker comes from
|
|
532
532
|
* a daemon that does not report holds: whether its boot lifts it is unknown. */
|
|
533
533
|
function drainLiftLine(snapshot: StatusSnapshot): string {
|
|
534
|
+
if (snapshot.draining_reason && snapshot.draining_until && snapshot.draining_hold !== true)
|
|
535
|
+
return `lifts itself at ${snapshot.draining_until} and retries; qcr drain off to lift now`;
|
|
534
536
|
if (snapshot.draining_hold === true || snapshot.draining_reason)
|
|
535
537
|
return "only qcr drain off lifts it — no boot does";
|
|
536
538
|
if (snapshot.draining_hold === undefined)
|
|
@@ -1857,55 +1859,6 @@ export async function cmdSetup(rest: string[]): Promise<number> {
|
|
|
1857
1859
|
return 0;
|
|
1858
1860
|
}
|
|
1859
1861
|
|
|
1860
|
-
/** `qcr onboard` — the guided setup wizard: pi at the pinned
|
|
1861
|
-
* version via npx (works from a bare clone; pi stays a non-dependency), the
|
|
1862
|
-
* onboarding prompt + extension + a restricted tool set, then the TUI handoff
|
|
1863
|
-
* when the checklist verifies complete. Resumable: run it again after a crash
|
|
1864
|
-
* and completed steps are re-verified, not repeated. */
|
|
1865
|
-
export async function cmdOnboard(): Promise<number> {
|
|
1866
|
-
const { piPinFromPackageJson, onboardingSessionDir, runOnboard } =
|
|
1867
|
-
await import("./onboard/launch.js");
|
|
1868
|
-
const { checklistComplete } = await import("./onboard/engine.js");
|
|
1869
|
-
const { spawn } = await import("node:child_process");
|
|
1870
|
-
const root = sandboxRoot();
|
|
1871
|
-
// Pin source: config.toml [pi] when it loads; package.json's "qcrPi" block on a
|
|
1872
|
-
// bare clone (config.toml is written later, BY onboarding). Same pinned
|
|
1873
|
-
// version by the rule-7 mirror discipline.
|
|
1874
|
-
let pin: { package: string; version: string } | null = null;
|
|
1875
|
-
try {
|
|
1876
|
-
const { package: pkg, version } = loadConfig().pi;
|
|
1877
|
-
pin = { package: pkg, version };
|
|
1878
|
-
} catch {
|
|
1879
|
-
pin = piPinFromPackageJson(root);
|
|
1880
|
-
}
|
|
1881
|
-
if (!pin)
|
|
1882
|
-
throw new Error(
|
|
1883
|
-
'onboard: pi pin not found (need config.toml [pi] or the package.json "qcrPi" block)',
|
|
1884
|
-
);
|
|
1885
|
-
mkdirSync(onboardingSessionDir(root), { recursive: true });
|
|
1886
|
-
const run = (cmd: string, args: string[]): Promise<number> =>
|
|
1887
|
-
new Promise((resolvePromise) => {
|
|
1888
|
-
const child = spawn(cmd, args, {
|
|
1889
|
-
stdio: "inherit",
|
|
1890
|
-
cwd: root,
|
|
1891
|
-
env: { ...process.env, QCR_ROOT: root },
|
|
1892
|
-
});
|
|
1893
|
-
child.on("error", (err) => {
|
|
1894
|
-
console.error(`onboard: ${cmd} failed to start: ${err.message}`);
|
|
1895
|
-
resolvePromise(1);
|
|
1896
|
-
});
|
|
1897
|
-
child.on("close", (code) => resolvePromise(code ?? 1));
|
|
1898
|
-
});
|
|
1899
|
-
return runOnboard({
|
|
1900
|
-
pin,
|
|
1901
|
-
root,
|
|
1902
|
-
spawn: run,
|
|
1903
|
-
isComplete: () => checklistComplete(join(root, "state")),
|
|
1904
|
-
log: (line) => console.log(line),
|
|
1905
|
-
cliPath: join(root, "dist", "src", "cli.js"),
|
|
1906
|
-
});
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
1862
|
export const ARCHIVE_USAGE =
|
|
1910
1863
|
"usage: qcr archive status [--plan] [--json] | qcr archive run | qcr archive restore <pr> | " +
|
|
1911
1864
|
"qcr archive extract <pr> --to <dir>";
|
|
@@ -2154,6 +2107,16 @@ export async function main(rawArgv: string[]): Promise<void> {
|
|
|
2154
2107
|
const { runTui } = await import("./tui/index.js");
|
|
2155
2108
|
return runTui(rest);
|
|
2156
2109
|
}
|
|
2110
|
+
case "home": {
|
|
2111
|
+
// the home (src/home/launch.ts): the bin routes a bare `qcr` here too
|
|
2112
|
+
// (src/qcrbin.ts); the image's home mode reaches it through this verb
|
|
2113
|
+
if (rest.length > 0) throw new Error("usage: qcr home");
|
|
2114
|
+
const { homeRunDeps, runHome } = await import("./home/launch.js");
|
|
2115
|
+
// the token taken at startup, when the wrapper handed one over
|
|
2116
|
+
const githubToken = takeHandoffToken();
|
|
2117
|
+
process.exitCode = runHome({ ...homeRunDeps(sandboxRoot()), githubToken: () => githubToken });
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2157
2120
|
case "companion":
|
|
2158
2121
|
if (rest.length !== 1) throw new Error("usage: qcr companion <up|down|status|rebuild|plan>");
|
|
2159
2122
|
return cmdCompanion(rest[0]);
|
|
@@ -2161,10 +2124,6 @@ export async function main(rawArgv: string[]): Promise<void> {
|
|
|
2161
2124
|
process.exitCode = await cmdMemory(rest);
|
|
2162
2125
|
return;
|
|
2163
2126
|
}
|
|
2164
|
-
case "onboard": {
|
|
2165
|
-
process.exitCode = await cmdOnboard();
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
2127
|
case "setup": {
|
|
2169
2128
|
process.exitCode = await cmdSetup(rest);
|
|
2170
2129
|
return;
|
|
@@ -2273,7 +2232,7 @@ export async function main(rawArgv: string[]): Promise<void> {
|
|
|
2273
2232
|
}
|
|
2274
2233
|
default:
|
|
2275
2234
|
console.log(
|
|
2276
|
-
"usage: qcr <version|config check [--config-home <dir>]|
|
|
2235
|
+
"usage: qcr <version|config check [--config-home <dir>]|home|setup [validate [<profile>]|commit]|status|spend|today|why <n>|metrics [--pr <n>]|events|takeover <n>|debug-round <n>|release <n>|tui [pr|overseer]|drain <on [--hold]|off>|pause-pr <pr> [--reason <text>]|resume-pr <pr>|review-now <pr>|github-token sync [--gh <path>] [--secrets-dir <dir>]|message <pr> [--may-post] <text...>|archive <status [--plan]|run|restore <pr>|extract <pr> --to <dir>>|install <where|migrate>|" +
|
|
2277
2236
|
"companion <up|down|status|rebuild>|review [start|status|stop|branch|--uncommitted|--diff FILE|-|<PR number|URL>] [--repo OWNER/NAME] [--base REF] [--preview] [--key K] [--limit USD] [--json]|live [RUN_ID|DIR] [--lens ID] [--round N] [--follow] [--json]|open [RUN_ID|DIR|FILE.jsonl|scan[:RUN]|pr N [--round R]] [--lens ID] [--write] [--print]|scan <preview|start|status|stop|findings|matches|match|suggestions|stage2 propose|stage2 start|dismiss|units show|units propose|units adjust|units save|login|doctor|rebuild>|memory reembed [--user <name>]|memory relayout [--plan|--apply]|backfill [--dry-run] [--limit <n>]|history <digest|pull>|outcomes [--days <n>] [--capture [--read-github]]|lessons [mine|shadow-report [--days <n>]|off|on|suppress <id>|unsuppress <id>]|checklist|compare <pr...>|" +
|
|
2278
2237
|
"regression [--set <path>] [--clone <path>] [--change <name>] [--limit <n>] [--out <path>]>",
|
|
2279
2238
|
);
|
package/src/config.ts
CHANGED
|
@@ -1158,6 +1158,43 @@ export interface Config extends CoreConfig {
|
|
|
1158
1158
|
/** The bot login's single refresher (`src/loginrefresh/`). Optional
|
|
1159
1159
|
* in the file; off unless the cutover's config turns it on. */
|
|
1160
1160
|
login_refresh: LoginRefreshConfig;
|
|
1161
|
+
/** The home, the app's own pi a bare `qcr` opens (src/home/launch.ts).
|
|
1162
|
+
* Optional in the file and here, so hand-rolled configs keep compiling;
|
|
1163
|
+
* the parser fills every key. */
|
|
1164
|
+
home?: HomeConfig;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/** Which tools the home's model may call: `reviewer` is the plugin's tools,
|
|
1168
|
+
* the home's own `home_*` tools and pi's read tools; `full` is all of pi's. */
|
|
1169
|
+
export type HomeToolSet = "reviewer" | "full";
|
|
1170
|
+
|
|
1171
|
+
/** `[home]`: the home a bare `qcr` opens (src/home/launch.ts reads it). */
|
|
1172
|
+
export interface HomeConfig {
|
|
1173
|
+
/** The chat's model, `provider/id`. The panel's login line counts only
|
|
1174
|
+
* this model's provider as signed in. */
|
|
1175
|
+
model: string;
|
|
1176
|
+
tools: HomeToolSet;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
export const HOME_DEFAULTS: Readonly<HomeConfig> = {
|
|
1180
|
+
model: "openai-codex/gpt-6.1-sol",
|
|
1181
|
+
tools: "reviewer",
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
/** `[home]`, every key defaulted; a value of the wrong kind is refused. */
|
|
1185
|
+
export function parseHomeSection(raw: unknown): HomeConfig {
|
|
1186
|
+
const t = (raw ?? {}) as Record<string, unknown>;
|
|
1187
|
+
const model = t["model"] ?? HOME_DEFAULTS.model;
|
|
1188
|
+
if (typeof model !== "string" || !/^[^/\s]+\/\S+$/.test(model))
|
|
1189
|
+
throw new Error(
|
|
1190
|
+
`config.toml [home]: model must be "provider/id" (got ${JSON.stringify(model)})`,
|
|
1191
|
+
);
|
|
1192
|
+
const tools = t["tools"] ?? HOME_DEFAULTS.tools;
|
|
1193
|
+
if (tools !== "reviewer" && tools !== "full")
|
|
1194
|
+
throw new Error(
|
|
1195
|
+
`config.toml [home]: tools must be "reviewer" or "full" (got ${JSON.stringify(tools)})`,
|
|
1196
|
+
);
|
|
1197
|
+
return { model, tools };
|
|
1161
1198
|
}
|
|
1162
1199
|
|
|
1163
1200
|
/**
|
|
@@ -1822,6 +1859,7 @@ export function parseConfig(toml: string): Config {
|
|
|
1822
1859
|
(raw.login_refresh ?? {}) as Record<string, unknown>,
|
|
1823
1860
|
Number((req(raw, "daemon") as { round_deadline_min?: number }).round_deadline_min ?? 75),
|
|
1824
1861
|
),
|
|
1862
|
+
home: parseHomeSection(raw.home),
|
|
1825
1863
|
};
|
|
1826
1864
|
validateReviewSection(cfg);
|
|
1827
1865
|
foldLegacyOwnerSlackId(raw, cfg.owner);
|
package/src/daemon.ts
CHANGED
|
@@ -832,6 +832,8 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
|
|
|
832
832
|
|
|
833
833
|
/** The open PRs the last listing reconcile pass saw (status.json `open_prs`). */
|
|
834
834
|
let openPrs: OpenPrsSnapshot | undefined;
|
|
835
|
+
/** The last reconcile pass (status.json `poll`): null until one has ended. */
|
|
836
|
+
let lastPoll: { at: string; ok: boolean; error?: string } | null = null;
|
|
835
837
|
let firstReconcileSeen = false;
|
|
836
838
|
let firstHealthyReconcileAt: Date | null = null;
|
|
837
839
|
// Deploy-window drain auto-lift: only the boot's first reconcile pass may
|
|
@@ -888,6 +890,7 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
|
|
|
888
890
|
const bootFirstPass = !firstReconcileSeen;
|
|
889
891
|
firstReconcileSeen = true;
|
|
890
892
|
let completed = false;
|
|
893
|
+
let thrown: unknown = undefined;
|
|
891
894
|
await guard("reconcile", async () => {
|
|
892
895
|
const report = await reconcileOnce({
|
|
893
896
|
cfg,
|
|
@@ -914,7 +917,15 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
|
|
|
914
917
|
// sweep reads session transcripts and is not needed for that, so it
|
|
915
918
|
// starts on the next pass to keep the boot pass short.
|
|
916
919
|
receiptSweep: !bootFirstPass,
|
|
920
|
+
}).catch((err: unknown) => {
|
|
921
|
+
thrown = err;
|
|
922
|
+
throw err;
|
|
917
923
|
});
|
|
924
|
+
lastPoll = {
|
|
925
|
+
at: report.ran_at,
|
|
926
|
+
ok: report.errors.length === 0,
|
|
927
|
+
...(report.errors[0] !== undefined ? { error: report.errors[0] } : {}),
|
|
928
|
+
};
|
|
918
929
|
// The board's "review a PR" list: kept from the last pass that listed.
|
|
919
930
|
if (report.open_list) openPrs = { listed_at: report.ran_at, prs: report.open_list };
|
|
920
931
|
log(
|
|
@@ -943,8 +954,10 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
|
|
|
943
954
|
} else if (bootFirstPass && standing.draining && standing.reason !== null) {
|
|
944
955
|
// A drain a system set for a reason (the worker crash-loop breaker,
|
|
945
956
|
// `worker_failures`) is not a deploy window: a restart does not clear what
|
|
946
|
-
// made it.
|
|
947
|
-
log(
|
|
957
|
+
// made it. It lapses at its own expiry, or an explicit `drain off` lifts it.
|
|
958
|
+
log(
|
|
959
|
+
`drain stands — held for ${standing.reason}; ${standing.expires_at ? `it lifts itself at ${standing.expires_at}, or` : "only"} an explicit drain off lifts it`,
|
|
960
|
+
);
|
|
948
961
|
} else if (bootFirstPass && report.errors.length === 0 && standing.draining) {
|
|
949
962
|
// The operator's lift: this is the deploy flow's drain completing, so
|
|
950
963
|
// it clears whatever hold stands — a session's included.
|
|
@@ -957,14 +970,25 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
|
|
|
957
970
|
}
|
|
958
971
|
completed = true;
|
|
959
972
|
});
|
|
973
|
+
if (thrown !== undefined)
|
|
974
|
+
lastPoll = {
|
|
975
|
+
at: (deps.now ? deps.now() : new Date()).toISOString(),
|
|
976
|
+
ok: false,
|
|
977
|
+
error: `the reconcile pass failed: ${thrown instanceof Error ? thrown.message : JSON.stringify(thrown)}`,
|
|
978
|
+
};
|
|
979
|
+
// The poll's outcome reaches status.json now, not at the next heartbeat:
|
|
980
|
+
// "working" is judged on it.
|
|
981
|
+
writeStatus();
|
|
960
982
|
return completed;
|
|
961
983
|
};
|
|
962
984
|
|
|
963
985
|
const pump = async (): Promise<void> => {
|
|
964
986
|
await guard("dispatch", async () => {
|
|
965
987
|
// Session levers that lapsed since the last tick leave the ledger here;
|
|
966
|
-
// the readers below already treat them as gone.
|
|
967
|
-
|
|
988
|
+
// the readers below already treat them as gone. A drain that lapsed (a
|
|
989
|
+
// session's, or the worker breaker's healing itself) dropped thread
|
|
990
|
+
// replies as any drain does: sweep them as a lift would.
|
|
991
|
+
if (sweepExpiredLevers(store, (line) => log(line)).drain) threadDecisions.requestSweep();
|
|
968
992
|
const orders = await dispatchOnce({
|
|
969
993
|
cfg,
|
|
970
994
|
store,
|
|
@@ -1122,6 +1146,12 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
|
|
|
1122
1146
|
};
|
|
1123
1147
|
}
|
|
1124
1148
|
if (openPrs) snapshot.open_prs = openPrs;
|
|
1149
|
+
snapshot.poll = {
|
|
1150
|
+
last_at: lastPoll?.at ?? null,
|
|
1151
|
+
ok: lastPoll?.ok ?? null,
|
|
1152
|
+
...(lastPoll?.error !== undefined ? { error: lastPoll.error } : {}),
|
|
1153
|
+
interval_s: cfg.daemon.poll_interval_s,
|
|
1154
|
+
};
|
|
1125
1155
|
if (deps.runHost) snapshot.run_host = deps.runHost;
|
|
1126
1156
|
writeStatusJson(stateDir, snapshot);
|
|
1127
1157
|
} catch (err) {
|
package/src/debuground.ts
CHANGED
|
@@ -123,6 +123,8 @@ export interface DebugRoundSpec {
|
|
|
123
123
|
cwd: string;
|
|
124
124
|
/** The login's owner dir: read-only, borrowed from. */
|
|
125
125
|
agent_dir: string;
|
|
126
|
+
/** The login's dir when it is not `agent_dir` (the worker spec's). */
|
|
127
|
+
login_dir?: string;
|
|
126
128
|
/** Where the pi's own agent dir (the blanked login copy) is made. */
|
|
127
129
|
borrow_dir: string;
|
|
128
130
|
/** The window the borrowed login must outlast. */
|
|
@@ -603,6 +605,7 @@ export class DebugRoundManager {
|
|
|
603
605
|
root: spec.root,
|
|
604
606
|
cwd: spec.cwd,
|
|
605
607
|
agent_dir: spec.agent_dir,
|
|
608
|
+
...(spec.login_dir !== undefined ? { login_dir: spec.login_dir } : {}),
|
|
606
609
|
borrow_dir: borrow,
|
|
607
610
|
idle_ms: this.idleMs,
|
|
608
611
|
...(file !== undefined ? { session_file: file } : {}),
|
package/src/debugroundpi.ts
CHANGED
|
@@ -83,6 +83,7 @@ export function readDebugSpec(file: string): DebugRoundSpec {
|
|
|
83
83
|
raw.session !== debugSession(raw.pr) ||
|
|
84
84
|
typeof raw.command_id !== "string" ||
|
|
85
85
|
typeof raw.agent_dir !== "string" ||
|
|
86
|
+
(raw.login_dir !== undefined && typeof raw.login_dir !== "string") ||
|
|
86
87
|
typeof raw.borrow_dir !== "string" ||
|
|
87
88
|
typeof raw.session_dir !== "string" ||
|
|
88
89
|
typeof raw.model !== "string" ||
|