clearotron 0.3.0-beta.10 → 0.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -13
- package/INSTALL.md +6 -110
- package/README.md +21 -31
- package/bin/connect.mjs +26 -71
- package/bin/disconnect.mjs +11 -16
- package/bin/example.mjs +2 -14
- package/bin/key.mjs +5 -23
- package/bin/onboard.mjs +44 -191
- package/bin/start.mjs +33 -197
- package/bin/status.mjs +3 -23
- package/bin/stop.mjs +8 -23
- package/bin/update.mjs +6 -38
- package/build-info.json +2 -2
- package/demo/full-country-search/run/status.json +1 -1
- package/demo/global-preliminary-search/run/status.json +1 -1
- package/demo/knockout-search/run/email-body.md +1 -1
- package/demo/knockout-search/run/status.json +2 -2
- package/demo/multi-country-focus-search/run/status.json +1 -1
- package/docs/architecture/04-configuration-reference.md +1 -2
- package/driver/CHANGELOG.md +8 -107
- package/driver/compose-read.mjs +2 -21
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/demo-container.mjs +2 -65
- package/driver/driver.config.mjs +0 -5
- package/driver/engine/anthropic-agent.mjs +17 -34
- package/driver/engine/jx-turn.mjs +1 -4
- package/driver/engine/openai-agent.mjs +2 -60
- package/driver/enqueue-schema.mjs +1 -3
- package/driver/gateway.mjs +15 -88
- package/driver/package.json +1 -1
- package/driver/portal-local-auth.mjs +3 -35
- package/driver/portal-service.mjs +22 -91
- package/driver/portal-upstream.mjs +1 -20
- package/driver/profile-service.mjs +3 -31
- package/driver/publish/index.mjs +9 -12
- package/driver/publish/knockout.mjs +2 -4
- package/driver/publish/office-record-links.mjs +21 -56
- package/driver/recipe-service.mjs +5 -14
- package/driver/record-origins.mjs +0 -14
- package/driver/search-policy.mjs +2 -7
- package/driver/suite-census.json +46 -154
- package/driver/systemd/render-units.mjs +13 -7
- package/mcp-server/CHANGELOG.md +2 -36
- package/mcp-server/CONNECT.md +2 -3
- package/mcp-server/lib/driver.mjs +0 -2
- package/mcp-server/lib/knockout.mjs +2 -2
- package/mcp-server/lib/options.mjs +5 -20
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-CwPAS0we.js → index-CcFjgM78.js} +383 -525
- package/portal-ui/dist/assets/{index-Cv-E_agg.css → index-CsCuPshD.css} +86 -199
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/clarivate/src/core.js +0 -5
- package/providers/oauth-mcp-bridge/CHANGELOG.md +0 -32
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/e2e-unread-terminals.mjs +1 -1
- package/scripts/e2e.mjs +20 -197
- package/scripts/engine-probe.mjs +2 -2
- package/scripts/env-classify.mjs +0 -3
- package/scripts/revisit-render-check.mjs +4 -22
- package/scripts/travelling-predicates.mjs +1 -1
- package/shared/access-audience.mjs +6 -7
- package/shared/brand.mjs +3 -7
- package/shared/client-door.mjs +0 -39
- package/shared/connect-clients.mjs +321 -290
- package/shared/env-file-merge.mjs +0 -24
- package/shared/invocation.mjs +2 -49
- package/shared/names-in-force.mjs +0 -2
- package/shared/stdio-connect.mjs +14 -95
- package/shared/store-in-repo.mjs +2 -50
- package/shared/verb-shim.mjs +1 -10
- package/shared/permanent-install.mjs +0 -237
- package/shared/running-start.mjs +0 -73
- package/shared/wsl.mjs +0 -23
|
@@ -285,13 +285,12 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
|
|
|
285
285
|
| `CLEAROTRON_DEMO` | unset | `1` puts this install in the DEMO posture. **Ordering is real**: the four products are listed and orderable, the form, the plan and the confirmation are the product's own, and the confirmation resolves to a finished report that already exists rather than dispatching — no engine turn, no register call, no queue entry, no run directory (ruling 2026-08-31, superseding the greyed-control ruling of the same day). A product the demo carries no finished report for refuses and names which one. It also re-aims two boot warnings written for an operator of a real deployment at the visitor who is not one, from one place (`driver/demo-posture.mjs`). **Set by `npx clearotron demo`, not by an operator** — it is passed explicitly to the two processes that have a reason to know (the portal and the MCP door; the worker is not told, because a demo never queues anything for it to drain), and those run with `CLEAROTRON_NO_ENV_FILE=1`, so a stray `.env` can neither put a live install into demo mode nor take a demo out of one. Anything but the literal `1` is not a demo. Replaces `PORTAL_DEMO`, which named only one of the processes that has to know. |
|
|
286
286
|
| `CLEAROTRON_TEST_FIXTURE_PROFILES` | unset | `1` makes the profile loader return the three suite fixtures, which are refused from every roster otherwise. Set by `scripts/test-run.mjs`, never by an operator; an explicit `includeTestFixtures` argument beats it. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
287
287
|
| `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo account, which a fresh install does not resolve (ruling 2026-09-08). Set by `clearotron demo`, `start --demo` and the suite runner, never by an operator. The gate is on the bundled layer, so a deployment's own configured store keeps its `demoData` accounts either way. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
288
|
-
| `CLEAROTRON_ORGANISATION_NAME` | unset | Your organisation's name, written quoted by `npx clearotron install`, which asks for it and not for a sign-in address. The first `clearotron start` files it as the first organisation in the grants file (`CLEAROTRON_ACCESS_FILE`) when that file holds none; from then on the grants file holds the name
|
|
288
|
+
| `CLEAROTRON_ORGANISATION_NAME` | unset | Your organisation's name, written quoted by `npx clearotron install`, which asks for it and not for a sign-in address. The first `clearotron start` files it as the first organisation in the grants file (`CLEAROTRON_ACCESS_FILE`) when that file holds none; from then on the grants file holds the name, renaming is an edit there, and this is not read. Unset ⇒ no organisation is invented. `clearotron start --organisation <name>` supplies it for one start, and a demo never reads it. Effect class `deployment`; the full contract is its row in `.env.example`. |
|
|
289
289
|
| `PORTAL_LOCAL_CREDENTIAL` | `~/.cordillera/portal-local-credential.json` | Where local sign-in keeps its passphrase DIGEST. `clearotron start` points it inside the install's own base directory for a new install, so it mints its own passphrase instead of adopting a digest another install left; an install that has been signing in with the shared file keeps it. `npx clearotron demo` always points it inside the demo's own base directory and mints a new passphrase there on every start, so a demo never inherits a digest minted for another address, and removing the demo stays one `rm -rf`. |
|
|
290
290
|
| `PORTAL_LOCAL_PASSPHRASE` | unset | **NEVER set this in a file.** An internal one-shot handoff, not an operator control: on a first FOREGROUND start the supervisor mints the passphrase and hands it to the portal it spawns *at the spawn call*, so the closing summary can print the value beside the address rather than sending a first-time reader back into eleven startup log lines for the one value in this product that cannot be read back. It is deliberately absent from the composed child environments, because that composition is what `--background` writes into the units' env file — a passphrase there would be a permanent plaintext copy on disk and the product's own sentence, "it is stored only as a digest", would stop being true. Setting it in any env file recreates exactly that. Lost passphrase: `clearotron passphrase --reset`. |
|
|
291
291
|
| `PORTAL_URL` | `http://127.0.0.1:18802`, or built from `PORTAL_SERVICE_HOST`/`PORTAL_SERVICE_PORT` | Where the deploy tick's live-surface check expects to reach the portal. |
|
|
292
292
|
| `PORTAL_OPS_TOKEN_FILE` | `~/.config/systemd/user/trademark-portal.service.d/secrets.conf` | The systemd drop-in the live-surface check reads `PORTAL_OPS_TOKEN` out of. It reads the FILE rather than the environment so a check run by hand sees the same token the service does. |
|
|
293
293
|
| `CLEAROTRON_INVOKED_AS` | unset (⇒ the verb's own `argv[1]`) | How the reader typed the command, so every command a verb prints for them to type next is spelled the way they type it: `clearotron …` after a global install, `npx clearotron …` otherwise. The dispatcher runs each verb as a process of its own, whose `argv[1]` is always `bin/<verb>.mjs`, so `bin/clearotron.mjs` passes its own `argv[1]` down in this name. **Set by the dispatcher, never by an operator.** Effect class `deployment`; the full contract is its row in `.env.example`. |
|
|
294
|
-
| `CLEAROTRON_RELOCATED` | unset (⇒ an install running from npx's cache moves itself to `~/.local` first) | Marks an install that has already made that move. `npx clearotron install` installs the same version under `~/.local`, then runs that copy's install with this set to `1`, so the copy does not try to move itself again. **Set by the install on the one process it starts, never by an operator.** Effect class `deployment`; the full contract is its row in `.env.example`. |
|
|
295
294
|
| `CLEAROTRON_REQUIRE_EXPLICIT_PORTS` | unset | `1` makes a service that would listen on a built-in default port refuse to start instead of warning. For a box that runs more than one instance, where one instance's default is another's port on the day that other instance is down. Unset, a service on a default port still says so as it starts. Effect class `deployment`. |
|
|
296
295
|
| `CLEAROTRON_UPDATER_STAMP` | `_updater-identity.json` beside the update script, in the directory the updater runs from | The full path of the file in which the updater that deploys this box records which copy of itself ran. The updater writes it and deploy health reads it under this one name, so a box that moves the stamp sets it once for both. On a box with no updater unit, setting it says an updater exists elsewhere and is to be judged. Effect class `deployment`. |
|
|
297
296
|
| `CLEAROTRON_CUT_REF` | `HEAD` | Which ref the cut decision reads the version from. **Read only by the release workflow, never set on a deployment.** The jobs that ask about `main` set it to `origin/main` explicitly, because their checkout is pinned to the run's own ref and `HEAD` there is that ref rather than the branch they are deciding about. A job that asks the wrong subject gets a confident wrong answer. |
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,104 +1,5 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
-
## 0.3.0-beta.10
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- fd3a4f7: Fixed: A portal address that does not exist now says so wherever it is. Addresses under the admin path used to show the Global config screen.
|
|
8
|
-
- fd3a4f7: New: The install guide now says how to remove Clearotron, naming every path it writes and which one holds your reports.
|
|
9
|
-
|
|
10
|
-
## 0.3.0-beta.9
|
|
11
|
-
|
|
12
|
-
### Patch Changes
|
|
13
|
-
|
|
14
|
-
- 86061f4: Fixed: Commands printed while running from npx name the published version, so they still work after npm cleans its cache.
|
|
15
|
-
- ab9dc2d: Fixed: When the engine cannot answer "Describe it", the page names the check that finds out why. It says "just now" only for a usage limit.
|
|
16
|
-
- ab9dc2d: Fixed: `clearotron status` says whether a product started in a terminal is up, and on which addresses. `clearotron stop` says how to stop it.
|
|
17
|
-
- ab9dc2d: Fixed: The demo no longer offers `start --background`, which set up an empty install in your home instead of running the demo.
|
|
18
|
-
- ab9dc2d: Fixed: The key command the demo prints issues a key the demo's client door accepts, run exactly as printed.
|
|
19
|
-
|
|
20
|
-
## 0.3.0-beta.8
|
|
21
|
-
|
|
22
|
-
### Patch Changes
|
|
23
|
-
|
|
24
|
-
- 9181d6a: Fixed: Commands the install, start and the New company screen print now run as printed, from any directory and after npm cleans its cache.
|
|
25
|
-
- 0d75b4a: Fixed: A demo started with npx prints commands that keep working after npm cleans its cache.
|
|
26
|
-
- 9181d6a: Fixed: Doctor, run before the first start, describes the local sign-in that start brings up, and drops checkout-only warnings on a packaged install.
|
|
27
|
-
- 91888b2: Fixed: The package no longer names the hosted service's own hostnames; a deployment names its own.
|
|
28
|
-
- 9181d6a: Fixed: The README installs with `npx clearotron install`, which needs no root, and no longer says the demo opens a browser.
|
|
29
|
-
|
|
30
|
-
## 0.3.0-beta.7
|
|
31
|
-
|
|
32
|
-
### Patch Changes
|
|
33
|
-
|
|
34
|
-
- c5744bf: Fixed: A demo started with npx keeps its assistant connection working after npm cleans its cache.
|
|
35
|
-
- c5744bf: Fixed: An assistant connected to the demo now opens each sample report on the demo's own portal, not on another server.
|
|
36
|
-
- c5744bf: Fixed: The portal names the organisation setup recorded, and the product keeps its own name beside it.
|
|
37
|
-
|
|
38
|
-
## 0.3.0-beta.6
|
|
39
|
-
|
|
40
|
-
### Patch Changes
|
|
41
|
-
|
|
42
|
-
- 2ca849d: Fixed: When codex's sign-in can no longer be refreshed, the search stops and says to run `codex login`. It used to retry with a bare exit code.
|
|
43
|
-
- a81279f: Fixed: When the portal address opens someone else's page, for example a port forwarded from outside WSL, Clearotron now says so and points you to `--port`.
|
|
44
|
-
- a81279f: Fixed: The sign-in page clears a session left by another Clearotron on the same address and says so. A refusal that is not about the passphrase now says what it is about.
|
|
45
|
-
- f133f7d: Fixed: A passphrase pasted with a space or line break at either end now signs in, instead of being refused as wrong.
|
|
46
|
-
- 2ca849d: Fixed: A manager who does not run the installation no longer sees server file paths when a new company cannot be recorded or filed.
|
|
47
|
-
- a81279f: Fixed: The local sign-in page no longer invites the browser to fill in a saved password from another install.
|
|
48
|
-
- a81279f: Fixed: `npx clearotron install` now installs Clearotron permanently under `~/.local` before setting up. The `clearotron` command and your assistants' connections keep working after npm cleans its cache.
|
|
49
|
-
- 2ca849d: Fixed: When the saved-search store exists but cannot be read, the connector and `clearotron doctor` now say so, instead of reporting no saved searches.
|
|
50
|
-
- a81279f: Fixed: On WSL, the "on this computer" connect steps now say to run them inside WSL. The Claude Code line registers Clearotron for every project and works in Windows PowerShell.
|
|
51
|
-
- a81279f: New: An assistant connected to `clearotron demo` can now list, brief and open the demo's four sample runs. They stay inside the demo's own folder.
|
|
52
|
-
- a81279f: Fixed: The sign-in page's reset line now runs for a demo started with npx, and resets that demo's own passphrase.
|
|
53
|
-
- a81279f: New: `clearotron update` now updates an npm-installed copy itself, and a beta install moves on to the release once it is published.
|
|
54
|
-
|
|
55
|
-
## 0.3.0-beta.5
|
|
56
|
-
|
|
57
|
-
### Patch Changes
|
|
58
|
-
|
|
59
|
-
- 5f7e295: Fixed: A Clearotron install signed in to OpenAI's `codex` with a subscription keeps working after codex refreshes its login. Before, every search after the first refresh failed within seconds until you signed in again.
|
|
60
|
-
- 2878809: Fixed: When the company store cannot record a new company, the New company page now says why and what fixes it, instead of "Try again shortly". Someone who does not run the installation is told to ask whoever does.
|
|
61
|
-
- e7cd9e1: Fixed: On an install run with `clearotron start`, a connected assistant now lists the same saved searches as the portal, including Generic's. `clearotron doctor` no longer says working saved searches are off, and it names one place profiles come from.
|
|
62
|
-
- e7cd9e1: Fixed: When setup offers to try your engine, it no longer names an Anthropic model to someone who chose OpenAI's `codex`.
|
|
63
|
-
|
|
64
|
-
## 0.3.0-beta.4
|
|
65
|
-
|
|
66
|
-
### Minor Changes
|
|
67
|
-
|
|
68
|
-
- a9f5375: New: Use your own AI asks first where Clearotron is running, then shows the steps for your app beside the list. The same five apps are offered either way.
|
|
69
|
-
|
|
70
|
-
New: Claude Code and Codex can connect to an installation running elsewhere, and the ChatGPT desktop app to one on the same machine.
|
|
71
|
-
|
|
72
|
-
Fixed: Codex was told to paste a settings block into a terminal. Its steps now name the file the block goes in.
|
|
73
|
-
|
|
74
|
-
For operators: `clearotron connect` and `clearotron disconnect` take `--where here` or `--where elsewhere`. Assistant names used before, such as `cowork`, still work.
|
|
75
|
-
|
|
76
|
-
### Patch Changes
|
|
77
|
-
|
|
78
|
-
- a9f5375: New: the company switcher in the sidebar ends with `+ New company`. Making a company is now one click from every screen, whether or not a company is selected.
|
|
79
|
-
|
|
80
|
-
## 0.3.0-beta.3
|
|
81
|
-
|
|
82
|
-
### Patch Changes
|
|
83
|
-
|
|
84
|
-
- a782aad: Fixed: Creating a company is refused, with nothing left behind, when the configuration store cannot record it. The company used to be created anyway, with no record of who made it or when, and its organisation was given access to it.
|
|
85
|
-
|
|
86
|
-
A store with no git identity is the usual cause on a new machine, and the refusal names the command that fixes it. Setup and `clearotron start` now check a store they adopt for this straight away.
|
|
87
|
-
- d1ef225: Fixed: A search step stopped at its time limit now records the output it actually produced. It used to record a small fraction, so a step that was working read as one that had stalled.
|
|
88
|
-
|
|
89
|
-
The token totals `clearotron tokens` reports for runs with a stopped step now include that output.
|
|
90
|
-
- 0ff42d1: Fixed: `clearotron doctor` now says when saved searches are switched off and why, and when a saved search file cannot be read.
|
|
91
|
-
|
|
92
|
-
An assistant asking for saved searches is told when they could not be read, instead of being told there are none.
|
|
93
|
-
- a782aad: Fixed: `clearotron doctor` reports the register and the research key the background services will use, read from the file they read. Run from a new terminal, it used to say no register was selected on an install whose searches were running.
|
|
94
|
-
- 0ff42d1: Fixed: Global config appears in the account menu only for people who can open it. Someone managing one organisation used to be offered it, and the page then said it was not available.
|
|
95
|
-
|
|
96
|
-
Clearances and People no longer ask for installation-wide data that a manager of one organisation cannot see.
|
|
97
|
-
- a782aad: Fixed: A clearance or knockout searched through Compumark now links each register record to the trade mark office's own page for it. Those records used to show an internal reference nobody could open.
|
|
98
|
-
|
|
99
|
-
The offices linked are the United States, the European Union, the United Kingdom, Canada, Australia, Switzerland, France, Norway, Sweden and WIPO. A record from any other office is cited by its number, and the report says why once, under the findings.
|
|
100
|
-
- a782aad: Fixed: Saved searches work on a fresh install and in the demo, for every company, Generic included. They used to fail to load for every company, with a message saying to try again shortly.
|
|
101
|
-
|
|
102
3
|
## 0.3.0-beta.2
|
|
103
4
|
|
|
104
5
|
### Minor Changes
|
|
@@ -140,11 +41,11 @@
|
|
|
140
41
|
|
|
141
42
|
Fixed: A search now records any of your default territories that the engine cannot search, in the record of that search. A mistyped default no longer narrows a search silently.
|
|
142
43
|
- eacfce4: Fixed: An assistant connected to a local install now sees the saved searches the portal shows, and can plan a run from one of them. Before, outside the demo, only the portal was told where saved searches are kept, so an assistant was told the install had none.
|
|
143
|
-
- 34cc1c7: For operators: A
|
|
44
|
+
- 34cc1c7: For operators: A health check that could not look now fails instead of reporting success. Two halves of the unit check can go quiet. One goes quiet when the installation does not say which installation it is; the other when the walk over the unit files does not finish. Both used to note that they had not run and then pass.
|
|
144
45
|
|
|
145
46
|
For operators: Set `CLEAROTRON_BOX` to `prod` or `test`. Those are the only two values the check accepts; anything else, including any other name, reads as unnamed and fails. The failure names the setting and says what went unchecked.
|
|
146
47
|
|
|
147
|
-
For operators: The
|
|
48
|
+
For operators: The half that goes quiet is the one that notices a service that has stopped and stayed stopped. The other half lists what is running, so it cannot see something that is no longer there. While that half is suppressed, a service can disappear without the check saying anything.
|
|
148
49
|
- b45a5cf: Fixed: `clearotron doctor` now reports everything a search would be refused for, checked against the environment the search will run in. So an installation it passes is one that can run a search.
|
|
149
50
|
|
|
150
51
|
Fixed: A token set in the installation's own configuration file now reaches the engine check. A headless server set up the documented way proves its engine instead of reporting it signed out.
|
|
@@ -189,7 +90,7 @@
|
|
|
189
90
|
Fixed: When a feature is switched off on your installation, the screen says so and what to change. It used to suggest trying again shortly.
|
|
190
91
|
|
|
191
92
|
Fixed: The link to the risk-framework guide opens in a new tab and goes straight to the section on writing your own. It also appears on the Company profile screen.
|
|
192
|
-
- ad088d6: Fixed: The audit workbook's "What was searched" sheet is now in plain words. Its Result and Note columns carry the search log's own notes.
|
|
93
|
+
- ad088d6: Fixed: The audit workbook's "What was searched" sheet is now in plain words. Its Result and Note columns carry the search log's own notes. Engine vocabulary could reach them: a receipt "deferred", a full web address, a bare HTTP code. Those words are now replaced as the workbook is built, a republished report included. A search recorded as not run still reads as not searched. Search terms and names stay exactly as written, because they record what was searched.
|
|
193
94
|
- ff16a3b: For operators: The deployment health check now reports whether the component that updates an installation is itself up to date. It was the one part of a deployment the check could not identify. An installation kept current by an out-of-date updater could report healthy while serving stale code.
|
|
194
95
|
|
|
195
96
|
For operators: An updater that cannot be identified is now reported as a failure rather than passed over. A copy old enough to predate this reporting writes nothing at all. That silence is the case worth knowing about, so it is treated as a finding.
|
|
@@ -219,7 +120,7 @@
|
|
|
219
120
|
|
|
220
121
|
The product now says company throughout. It used to say brand owner, account, client and customer for the same thing. The firm running the installation is named separately, in the top bar.
|
|
221
122
|
|
|
222
|
-
Companies created through the settings page were saved without a risk framework. Their matters were then rated under the default
|
|
123
|
+
Companies created through the settings page were saved without a risk framework. Their matters were then rated under the house default, with nothing on screen saying so. Every company created now carries one, and says which.
|
|
223
124
|
- f75266d: New: The setup wizard now asks for the organisation's name after the sign-in address, and the person who installs starts with access to everything.
|
|
224
125
|
|
|
225
126
|
`clearotron grant add` sets a person's two permissions with `--run` and `--manage`; with neither, the person can look and start nothing.
|
|
@@ -241,18 +142,18 @@
|
|
|
241
142
|
|
|
242
143
|
Getting the deck's shape wrong used to fail quietly. The profile screen showed the framework's title and your band colours, and silently omitted the box saying what the bands mean. The new command answers that question directly, using the screen's own read of the deck.
|
|
243
144
|
|
|
244
|
-
Fixed:
|
|
145
|
+
Fixed: a framework served from the product's own files, while you have a configuration store set, now says so.
|
|
245
146
|
|
|
246
147
|
Your store is looked in first, and the product's files answer when it is silent. The product ships decks under names you may also have chosen. So a deck that went missing from your store was replaced by ours rather than reported absent. Same band words, different rubric, nothing raised anywhere. The profile screen now writes one line naming what happened, and the new command reports it.
|
|
247
148
|
|
|
248
|
-
Fixed:
|
|
149
|
+
Fixed: the house triage ladder's profile page explains its bands again.
|
|
249
150
|
|
|
250
151
|
Its band sections stated their meanings as plain paragraphs, which the screen does not read. Every company without a framework of its own saw band colours and no explanation. The wording is unchanged.
|
|
251
|
-
- cfb9a9f: Fixed: `clearotron doctor` now
|
|
152
|
+
- cfb9a9f: Fixed: `clearotron doctor` now says which companies your portal's trigger key can start.
|
|
252
153
|
|
|
253
154
|
The key carries a list of the companies it may start runs for. A company added after the key was minted is outside it. Doctor reported the key's expiry and never its coverage. So the one command whose job is to tell you what a machine is configured for said nothing about it.
|
|
254
155
|
|
|
255
|
-
It reads the
|
|
156
|
+
It reads the roster the services read, not the one a command-line process resolves, and it says which. Those two can disagree, and when they do the difference is the whole answer.
|
|
256
157
|
|
|
257
158
|
The line is a note, not a failure. Your portal takes a fresh credential at the start of every call, so a company outside the key is not normally refused. It is refused when the portal cannot take a fresh one, and the line says so and gives you the command to widen the key.
|
|
258
159
|
- ba2899b: Fixed: On reports searched through Signa, each register finding now links to the office's own page for that record, where the office publishes one. Singapore publishes no such page, so its registrations are cited by number, and the report says why. A number that an office's page cannot take is cited the same way.
|
package/driver/compose-read.mjs
CHANGED
|
@@ -325,26 +325,7 @@ export function jsonFromTurnText(text) {
|
|
|
325
325
|
return null;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
* What the page says when the engine did not answer a read.
|
|
330
|
-
*
|
|
331
|
-
* "JUST NOW" ONLY WHERE IT IS TRUE. Every failed turn said the reader "could not reach the engine just
|
|
332
|
-
* now", and on a fresh install whose engine was not signed in that sent a stranger to press the button
|
|
333
|
-
* again, forever: the failure was the sign-in, which no retry changes (measured on a published beta,
|
|
334
|
-
* 2026-09-11). A rate limit is the one failure known to pass, so it alone says so, with its reset time
|
|
335
|
-
* when the engine gave one. Everything else names the check that finds the cause — `checkCommand`, in the
|
|
336
|
-
* reader's own invocation and with no machine path, because the page is not a place to print one.
|
|
337
|
-
*/
|
|
338
|
-
export function engineFailureMessage(t, { checkCommand = "clearotron doctor --probe-engine" } = {}) {
|
|
339
|
-
if (t?.rateLimited) {
|
|
340
|
-
const at = t.resetsAt ? new Date(t.resetsAt) : null;
|
|
341
|
-
const when = at && !Number.isNaN(at.getTime()) ? ` It resets at ${at.toISOString().slice(11, 16)} UTC.` : "";
|
|
342
|
-
return `The engine is at its usage limit just now.${when} Try again then, or set the search up below.`;
|
|
343
|
-
}
|
|
344
|
-
return `The request could not be read: the engine did not answer it. \`${checkCommand}\` finds out why — set the search up below meanwhile.`;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function makeComposeReader({ turn, now = () => new Date(), checkCommand } = {}) {
|
|
328
|
+
export function makeComposeReader({ turn, now = () => new Date() } = {}) {
|
|
348
329
|
if (typeof turn !== "function") return null;
|
|
349
330
|
return async function read(brief) {
|
|
350
331
|
const text = String(brief ?? "").trim();
|
|
@@ -380,7 +361,7 @@ export function makeComposeReader({ turn, now = () => new Date(), checkCommand }
|
|
|
380
361
|
+ `${JSON.stringify(READ_SCHEMA)}\n\nThe brief:\n${text}`,
|
|
381
362
|
});
|
|
382
363
|
if (!t?.ok) {
|
|
383
|
-
return { ok: false, error: "engine", message:
|
|
364
|
+
return { ok: false, error: "engine", message: "The reader could not reach the engine just now — set the search up below.",
|
|
384
365
|
cause: t?.cause ?? "the engine turn did not complete", vendor: t?.vendor ?? null, authMode: t?.authMode ?? null,
|
|
385
366
|
engine: t?.engine ?? null, model: t?.model ?? null };
|
|
386
367
|
}
|
|
@@ -221,7 +221,7 @@ export const E3_BACKLOG = [
|
|
|
221
221
|
where: "driver/skills/prelim-common-law/SKILL.md:192 (restated at driver/skills/prelim-search/synthesis-rules.md:394)",
|
|
222
222
|
surface: "skill-file",
|
|
223
223
|
evidence: "A clean PR/connotation row MUST cite its search — add a `**Connotation-search source:** <URL | \"perplexity_research — no result\">` line.",
|
|
224
|
-
reparsedBy: "driver/connotation-search.mjs — validators.commonLaw rejects a clean claim with no such line (connotation_search_missing); the hint is re-dictated
|
|
224
|
+
reparsedBy: "driver/connotation-search.mjs — validators.commonLaw rejects a clean claim with no such line (connotation_search_missing); the hint is re-dictated at driver/gateway.mjs:1824",
|
|
225
225
|
removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
|
|
226
226
|
},
|
|
227
227
|
{
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
// stage apart. This module is the single answer. `cut/` cannot import it (that directory does not travel
|
|
24
24
|
// and this one does), so the pack gate restates the disjunction and its own test pins the two together.
|
|
25
25
|
|
|
26
|
-
import { cpSync, existsSync,
|
|
26
|
+
import { cpSync, existsSync, mkdtempSync, readdirSync } from "node:fs";
|
|
27
27
|
import { tmpdir } from "node:os";
|
|
28
|
-
import {
|
|
28
|
+
import { join, resolve, sep } from "node:path";
|
|
29
29
|
|
|
30
30
|
/** The entry file each lane's publisher reads as its source, in the order a child is probed for one. */
|
|
31
31
|
export const ENTRY_FILES = Object.freeze(["report.md", "knockout-findings.json"]);
|
|
@@ -78,66 +78,3 @@ export function publishSource(dir, { repoRoot, tmp = tmpdir() } = {}) {
|
|
|
78
78
|
cpSync(here, copy, { recursive: true });
|
|
79
79
|
return copy;
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* THE DEMO'S SAMPLE RUNS, WHERE AN ASSISTANT LOOKS FOR RUNS.
|
|
84
|
-
*
|
|
85
|
-
* The demo published its samples as reports and made no run directory, so the connector its own connect
|
|
86
|
-
* line wires listed nothing: an assistant, one of the demo's three faces, had nothing to explore. Each
|
|
87
|
-
* sample's finished `run/` is copied to the layout the connector walks, under the DEMO'S OWN workspace:
|
|
88
|
-
* `<workspace>/workspace-<agent>/studio/prelim-search/<slug>/<date>-<codename>/`. Nothing is written
|
|
89
|
-
* anywhere else, so a real install started afterwards sees none of it (the demo is its own install).
|
|
90
|
-
*
|
|
91
|
-
* Copied, never linked: a connector reading a run may write beside it, and `demo/` is tracked. A run
|
|
92
|
-
* already in place is not copied again, so a visitor's second start changes nothing but its links.
|
|
93
|
-
*
|
|
94
|
-
* ITS REPORT LINKS POINT AT THIS DEMO'S PORTAL. Each sample's `status.json` carried the report URL it was
|
|
95
|
-
* stamped with where it was captured, a test instance's address, and the connector hands a run's `url` to
|
|
96
|
-
* the assistant as-is, so an assistant asked to open a demo report sent the person to that host (measured
|
|
97
|
-
* on a published beta, 2026-09-11). The tracked samples now carry the portal's own route,
|
|
98
|
-
* `/portal/report/<runId>/`, with no host, and each copy here is stamped with `portalOrigin`, the demo
|
|
99
|
-
* portal's address. Stamped on EVERY start, the copies already in place included: `--port` moves the
|
|
100
|
-
* portal, and a copy laid down by an earlier version still carries the old host.
|
|
101
|
-
*/
|
|
102
|
-
export function seedDemoRuns({ workspace, examplesDir, portalOrigin = null }) {
|
|
103
|
-
const seeded = [], already = [];
|
|
104
|
-
for (const name of demoChildren(examplesDir)) {
|
|
105
|
-
const run = join(examplesDir, name, "run");
|
|
106
|
-
let s;
|
|
107
|
-
try { s = JSON.parse(readFileSync(join(run, "status.json"), "utf8")); } catch { continue; }
|
|
108
|
-
if (!s?.slug || !s?.codename || !s?.date) continue;
|
|
109
|
-
const dir = join(workspace, `workspace-${s.agent || "clawdi"}`, "studio", "prelim-search", s.slug, `${s.date}-${s.codename}`);
|
|
110
|
-
if (existsSync(join(dir, "status.json"))) already.push(s.runId);
|
|
111
|
-
else {
|
|
112
|
-
mkdirSync(dirname(dir), { recursive: true });
|
|
113
|
-
cpSync(run, dir, { recursive: true });
|
|
114
|
-
seeded.push(s.runId);
|
|
115
|
-
}
|
|
116
|
-
if (portalOrigin) stampReportLinks(join(dir, "status.json"), portalOrigin);
|
|
117
|
-
}
|
|
118
|
-
return { seeded, already };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** The portal route that serves a run's report: the one `scanAccountRuns` hands the portal's own list. */
|
|
122
|
-
export const reportRoute = (runId) => `/portal/report/${encodeURIComponent(runId)}/`;
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Point a seeded run's report links at `origin`. A link already on the portal's route keeps its path; any
|
|
126
|
-
* other (the host an older sample carried) becomes the run's own route. Written only when something moved.
|
|
127
|
-
*/
|
|
128
|
-
export function stampReportLinks(statusFile, origin) {
|
|
129
|
-
let s;
|
|
130
|
-
try { s = JSON.parse(readFileSync(statusFile, "utf8")); } catch { return false; }
|
|
131
|
-
if (!s?.runId) return false;
|
|
132
|
-
const base = String(origin).replace(/\/+$/, "");
|
|
133
|
-
const local = (url) => {
|
|
134
|
-
const path = String(url ?? "").replace(/^https?:\/\/[^/]+/, "");
|
|
135
|
-
return `${base}${path.startsWith("/portal/report/") ? path : reportRoute(s.runId)}`;
|
|
136
|
-
};
|
|
137
|
-
const next = { ...s, url: local(s.url) };
|
|
138
|
-
if (Array.isArray(s.reports)) next.reports = s.reports.map((r) => (r && typeof r === "object" ? { ...r, url: local(r.url) } : r));
|
|
139
|
-
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
140
|
-
if (text === `${JSON.stringify(s, null, 2)}\n`) return false;
|
|
141
|
-
writeFileSync(statusFile, text);
|
|
142
|
-
return true;
|
|
143
|
-
}
|
package/driver/driver.config.mjs
CHANGED
|
@@ -1110,11 +1110,6 @@ export const PROVIDERS = {
|
|
|
1110
1110
|
office: row?.office ?? null,
|
|
1111
1111
|
application_date: row?.application_date ?? null,
|
|
1112
1112
|
registration_date: row?.registration_date ?? null,
|
|
1113
|
-
// The office's own numbers: publish addresses the filing at the office's page from these, the
|
|
1114
|
-
// way it does for this register's clearance findings. Compumark states no filing route or IR
|
|
1115
|
-
// number of its own, so a Madrid designation is addressed as the national filing it is listed as.
|
|
1116
|
-
application_number: row?.application_number ?? null,
|
|
1117
|
-
registration_number: row?.registration_number ?? null,
|
|
1118
1113
|
// hasPublicRecordUrl:false on this provider — there is no per-record page to link, and a
|
|
1119
1114
|
// fabricated one would be worse than none.
|
|
1120
1115
|
record_url: null,
|
|
@@ -467,32 +467,19 @@ export const anthropicAgentEngine = {
|
|
|
467
467
|
let rateLimitEvent = null; // a 429 session-cap rejection rides a `rate_limit_event` (status:"rejected" + resetsAt)
|
|
468
468
|
// Streamed-usage accumulator: what the turn PROVABLY moved, observed from the stream itself, so a
|
|
469
469
|
// killed turn is never journalled as usage:null when millions of tokens moved (the "137 + usage:null
|
|
470
|
-
// ⇒ mislabelled transient/lane-wedge" class).
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// assistant events counted a call's input and cache once per block and its output at whatever it
|
|
476
|
-
// was when the block went out, and dropping the delta lost the rest: a turn killed after 75 calls
|
|
477
|
-
// journalled 810 output tokens. Which event carries the final count need not be known here, and the
|
|
478
|
-
// order is inferred rather than recorded, so every reading is kept and the largest wins. A delta names
|
|
479
|
-
// no message, so it belongs to the one its last `message_start` opened; an assistant event with no id
|
|
480
|
-
// and no open message is a call of its own, as it always was.
|
|
481
|
-
const perMessage = new Map();
|
|
482
|
-
let openMessage = null, anonMessages = 0;
|
|
483
|
-
const foldUsage = (key, u) => {
|
|
484
|
-
if (!u) return;
|
|
485
|
-
const was = perMessage.get(key);
|
|
486
|
-
perMessage.set(key, was
|
|
487
|
-
? { input: Math.max(was.input, u.input), output: Math.max(was.output, u.output),
|
|
488
|
-
cacheRead: Math.max(was.cacheRead, u.cacheRead), cacheWrite: Math.max(was.cacheWrite, u.cacheWrite) }
|
|
489
|
-
: { input: u.input, output: u.output, cacheRead: u.cacheRead, cacheWrite: u.cacheWrite });
|
|
490
|
-
};
|
|
470
|
+
// ⇒ mislabelled transient/lane-wedge" class). Per completed API call the `assistant` event's usage is
|
|
471
|
+
// authoritative (summed); an in-flight call contributes its `message_start` usage + the latest
|
|
472
|
+
// `message_delta` output count until its own assistant event supersedes them (no double count).
|
|
473
|
+
const streamTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
474
|
+
let pendingStart = null, pendingDeltaOut = 0;
|
|
491
475
|
const streamedUsage = () => {
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
476
|
+
const p = mapUsage(pendingStart) ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
477
|
+
const u = {
|
|
478
|
+
input: streamTotals.input + p.input,
|
|
479
|
+
output: streamTotals.output + p.output + pendingDeltaOut,
|
|
480
|
+
cacheRead: streamTotals.cacheRead + p.cacheRead,
|
|
481
|
+
cacheWrite: streamTotals.cacheWrite + p.cacheWrite,
|
|
482
|
+
};
|
|
496
483
|
u.total = u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
497
484
|
return u.total > 0 ? u : null; // zero observed movement stays null — a never-admitted turn must keep classifying as a lane wedge
|
|
498
485
|
};
|
|
@@ -716,9 +703,9 @@ export const anthropicAgentEngine = {
|
|
|
716
703
|
periodStart = now; periodChunk = chunkSeq;
|
|
717
704
|
}
|
|
718
705
|
syncOpenAsk();
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
706
|
+
const u = mapUsage(ev.message?.usage);
|
|
707
|
+
if (u) { streamTotals.input += u.input; streamTotals.output += u.output; streamTotals.cacheRead += u.cacheRead; streamTotals.cacheWrite += u.cacheWrite; }
|
|
708
|
+
pendingStart = null; pendingDeltaOut = 0; // this call's usage is now authoritative — drop its partials
|
|
722
709
|
progress();
|
|
723
710
|
}
|
|
724
711
|
else if (ev.type === "user") {
|
|
@@ -750,12 +737,8 @@ export const anthropicAgentEngine = {
|
|
|
750
737
|
}
|
|
751
738
|
else if (ev.type === "stream_event") {
|
|
752
739
|
const t = ev.event?.type;
|
|
753
|
-
if (t === "message_start") {
|
|
754
|
-
|
|
755
|
-
foldUsage(openMessage, mapUsage(ev.event.message?.usage));
|
|
756
|
-
progress();
|
|
757
|
-
}
|
|
758
|
-
else if (t === "message_delta") { foldUsage(openMessage ?? "__delta_without_start", mapUsage(ev.event?.usage)); progress(); }
|
|
740
|
+
if (t === "message_start") { pendingStart = ev.event.message?.usage ?? null; progress(); }
|
|
741
|
+
else if (t === "message_delta") { const o = Number(ev.event?.usage?.output_tokens); if (o > 0) pendingDeltaOut = o; progress(); }
|
|
759
742
|
else if (t === "content_block_delta" || t === "content_block_start") progress();
|
|
760
743
|
// THINKING GAUGE (partials): the earliest tells. Any one is sufficient; belt-and-braces so a
|
|
761
744
|
// display-mode or CLI-version change cannot silently blind the gauge.
|
|
@@ -94,10 +94,7 @@ export function readJxTuple(tuple, { vendor, authMode, engine }) {
|
|
|
94
94
|
if (tuple?.killed || tuple?.signals?.stalled)
|
|
95
95
|
return { ok: false, cause: "the engine turn was killed before it answered (stall or wall)", truncationObservable: observable, ...base };
|
|
96
96
|
if (tuple?.signals?.rateLimited)
|
|
97
|
-
|
|
98
|
-
// needs to know that without parsing this sentence. `resetsAt` is the adapter's, when it read one.
|
|
99
|
-
return { ok: false, cause: "the engine turn was rate-limited", rateLimited: true, resetsAt: tuple?.signals?.resetsAt ?? null,
|
|
100
|
-
truncationObservable: observable, ...base };
|
|
97
|
+
return { ok: false, cause: "the engine turn was rate-limited", truncationObservable: observable, ...base };
|
|
101
98
|
if (tuple?.code !== 0 || tuple?.json?.status !== "ok")
|
|
102
99
|
return { ok: false, cause: `the engine turn did not complete cleanly (code ${tuple?.code ?? "?"}, status ${tuple?.json?.status ?? "none"})`,
|
|
103
100
|
truncationObservable: observable, ...base };
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
// CLEAROTRON_CODEX_SANDBOX_BYPASS=1 swaps that `--sandbox workspace-write` for
|
|
23
23
|
// `--dangerously-bypass-approvals-and-sandbox` — see buildCodexArgs below for why.
|
|
24
24
|
|
|
25
|
-
import { mkdtempSync, writeFileSync, copyFileSync, existsSync, rmSync, readFileSync, readdirSync, statSync
|
|
26
|
-
import { writeSecretFile } from "../../shared/secret-file.mjs"; // the rotated login goes back the way every credential is written
|
|
25
|
+
import { mkdtempSync, writeFileSync, copyFileSync, existsSync, rmSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
27
26
|
import { tmpdir, homedir } from "node:os";
|
|
28
27
|
import { join } from "node:path";
|
|
29
28
|
import { runStreamingChild, absolutizeSkillRefs, WRITE_DISCIPLINE, buildEnvelope, resolveSpawnCwd } from "./common.mjs";
|
|
@@ -175,49 +174,6 @@ export function spawnEnv(base = process.env, codexHome) {
|
|
|
175
174
|
|
|
176
175
|
const authFilePath = (env = process.env) => env.CLEAROTRON_OPENAI_AUTH_FILE || join(homedir(), ".codex", "auth.json");
|
|
177
176
|
|
|
178
|
-
// ── THE LOGIN IS THE MASTER'S, NOT A COPY'S ─────────────────────────────────────────────────────────
|
|
179
|
-
//
|
|
180
|
-
// codex refreshes its access token inside a turn and rotates the refresh token as it does, and the
|
|
181
|
-
// provider accepts each refresh token once. The stage home used to be seeded with a COPY of the master
|
|
182
|
-
// login and deleted when its ladder settled, so the rotated token was deleted with it. The master kept a
|
|
183
|
-
// token the provider had already consumed, and every later stage on every run failed its refresh with a
|
|
184
|
-
// 401 until a person signed in again. The engine's only report was an exit code.
|
|
185
|
-
//
|
|
186
|
-
// So the stage home LINKS to the master: a refresh codex writes in place lands where the next stage
|
|
187
|
-
// reads, including a ladder running beside this one. If codex replaces the link with a file of its own,
|
|
188
|
-
// `returnAuth` writes that file back.
|
|
189
|
-
|
|
190
|
-
/** Point `<codexHome>/auth.json` at the master login. Returns what the master held, for `returnAuth`. */
|
|
191
|
-
function seedAuth(masterPath, codexHome) {
|
|
192
|
-
const seat = join(codexHome, "auth.json");
|
|
193
|
-
// A LINK LEFT BY THIS LADDER'S PREVIOUS TURN is removed first: copying the master onto a link to
|
|
194
|
-
// itself would truncate the one file the login lives in.
|
|
195
|
-
rmSync(seat, { force: true });
|
|
196
|
-
symlinkSync(masterPath, seat);
|
|
197
|
-
return readFileSync(masterPath, "utf8");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* After a turn: a login codex rewrote as its own file goes back to the master.
|
|
202
|
-
*
|
|
203
|
-
* ONLY WHILE THE MASTER STILL HOLDS WHAT THIS TURN WAS SEEDED WITH. A master that moved meanwhile was
|
|
204
|
-
* rotated by another ladder, and the provider accepts one refresh per token, so of two rotations from
|
|
205
|
-
* one seed only one can be live. Writing ours over theirs could put back the spent one.
|
|
206
|
-
*/
|
|
207
|
-
export function returnAuth(masterPath, codexHome, seeded) { // @internal
|
|
208
|
-
const seat = join(codexHome, "auth.json");
|
|
209
|
-
let st;
|
|
210
|
-
try { st = lstatSync(seat); } catch { return false; }
|
|
211
|
-
if (st.isSymbolicLink() || !st.isFile()) return false; // written through the link, or not there
|
|
212
|
-
const now = readFileSync(seat, "utf8");
|
|
213
|
-
if (now === seeded) return false;
|
|
214
|
-
let master;
|
|
215
|
-
try { master = readFileSync(masterPath, "utf8"); } catch { return false; }
|
|
216
|
-
if (master !== seeded) return false;
|
|
217
|
-
writeSecretFile(masterPath, now);
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
177
|
// Build the codex argv. Prompt rides STDIN via the `-` placeholder (no MAX_ARG_STRLEN ceiling on a 150KB
|
|
222
178
|
// register prompt). Global flags precede the `resume` subcommand (flag-corpus ordering). config.toml
|
|
223
179
|
// (mcp_servers + developer_instructions) rides CODEX_HOME, not argv.
|
|
@@ -412,10 +368,6 @@ export function readServedModel(codexHome, sinceMs = 0) {
|
|
|
412
368
|
// that cannot recur. This alternation is still a list of prose a vendor may change without notice,
|
|
413
369
|
// which is exactly why `rateLimitBasis: "text-match"` stays on the record.
|
|
414
370
|
const RATE_LIMIT_RE = /\b429\b|rate.?limit|usage limit|quota|too many requests|insufficient_quota/i;
|
|
415
|
-
// A SIGN-IN THAT CAN NO LONGER BE REFRESHED, in codex's own words (measured on a live install, 2026-09-11).
|
|
416
|
-
// Narrow on purpose: a register provider's 401 or 403 can land in a stage's stderr too, and reading that as
|
|
417
|
-
// the operator's codex login having expired would send them to sign in again for nothing.
|
|
418
|
-
const REFRESH_FAILED_RE = /refresh token (?:has|was) already been used|access token could not be refreshed|failed to refresh token/i;
|
|
419
371
|
|
|
420
372
|
// A retry hint, WHEN THE MESSAGE HAPPENS TO CARRY ONE. Two anchored shapes and nothing clever: an
|
|
421
373
|
// explicit timestamp, or a relative delay with a unit. Anything else yields undefined and the driver
|
|
@@ -488,7 +440,6 @@ function settleTuple({ r, ev, resumeRef }) {
|
|
|
488
440
|
// backoff (CLEAROTRON_RATE_LIMIT_DEFAULT_BACKOFF_MS), exactly as the anthropic no-reset 429 path does.
|
|
489
441
|
const rateLimitText = `${ev.turnFailed || ""}\n${ev.streamError || ""}\n${r.stderr || ""}`;
|
|
490
442
|
const rateLimited = !killed && RATE_LIMIT_RE.test(rateLimitText);
|
|
491
|
-
const signedOut = !killed && REFRESH_FAILED_RE.test(rateLimitText);
|
|
492
443
|
const resetsAt = rateLimited ? parseResetHint(rateLimitText) : undefined;
|
|
493
444
|
return {
|
|
494
445
|
code: killed ? 137 : (failed ? (r.rawCode || 1) : 0),
|
|
@@ -526,9 +477,6 @@ function settleTuple({ r, ev, resumeRef }) {
|
|
|
526
477
|
// receives and is not this adapter's call — but nothing downstream can any longer fail to know.
|
|
527
478
|
mcpRefused: mcpToolGauge(ev).mcpToolCallsRefused > 0 || undefined,
|
|
528
479
|
rateLimited: rateLimited || undefined,
|
|
529
|
-
// The engine's sign-in could not be refreshed: what the operator runs to fix it. The gateway names the
|
|
530
|
-
// stage's failure with it.
|
|
531
|
-
signedOut: signedOut ? "codex sign-in expired — run `codex login`, then start the search again" : undefined,
|
|
532
480
|
rateLimitBasis: rateLimited ? "text-match" : undefined,
|
|
533
481
|
// resetsAtBasis (2026-08-20): same honesty as rateLimitBasis one line up, for the reset
|
|
534
482
|
// CLOCK rather than the classification. codex states its reset as human prose with NO timezone
|
|
@@ -582,7 +530,6 @@ export const openaiAgentEngine = {
|
|
|
582
530
|
const ownHome = !providedHome;
|
|
583
531
|
try { codexHome = providedHome ?? mkdtempSync(join(tmpdir(), "codex-home-")); }
|
|
584
532
|
catch (e) { return errResult(t0, e, resumeRef); }
|
|
585
|
-
let authMaster = null, seeded = null;
|
|
586
533
|
try {
|
|
587
534
|
// Auth toggle (may throw loud on api-key-without-key — a config error, not a retryable failure).
|
|
588
535
|
const { env, mode } = spawnEnv(process.env, codexHome);
|
|
@@ -590,9 +537,7 @@ export const openaiAgentEngine = {
|
|
|
590
537
|
const af = authFilePath(process.env);
|
|
591
538
|
if (!existsSync(af))
|
|
592
539
|
throw new Error(`CLEAROTRON_AI_BILLING=subscription but no auth.json at ${af} — run \`codex login\` (or set CLEAROTRON_OPENAI_AUTH_FILE — NOT an aliased name, and deliberately left as it is), or use CLEAROTRON_AI_BILLING=api-key + CODEX_API_KEY.`);
|
|
593
|
-
|
|
594
|
-
authMaster = realpathSync(af);
|
|
595
|
-
seeded = seedAuth(authMaster, codexHome);
|
|
540
|
+
copyFileSync(af, join(codexHome, "auth.json"));
|
|
596
541
|
}
|
|
597
542
|
// Per-run config.toml: the gather MCP servers (translated from the claude-shaped mcpConfig, so
|
|
598
543
|
// gather-config.mjs is untouched) + developer_instructions = WRITE_DISCIPLINE (codex's
|
|
@@ -629,9 +574,6 @@ export const openaiAgentEngine = {
|
|
|
629
574
|
if (!ev.model) ev.model = readServedModel(codexHome, spawnedAtMs);
|
|
630
575
|
return settleTuple({ r, ev, resumeRef });
|
|
631
576
|
} finally {
|
|
632
|
-
// BEFORE the home can be deleted, here or by the ladder that owns it. A failed write-back leaves the
|
|
633
|
-
// turn's result as it was; the next turn is seeded from the master either way.
|
|
634
|
-
try { if (authMaster) returnAuth(authMaster, codexHome, seeded); } catch { /* the master is unchanged */ }
|
|
635
577
|
try { if (ownHome && codexHome) rmSync(codexHome, { recursive: true, force: true }); } catch { /* best-effort cleanup */ }
|
|
636
578
|
}
|
|
637
579
|
},
|
|
@@ -456,9 +456,7 @@ export function validateJob(job, { atClaim = false } = {}) {
|
|
|
456
456
|
if (projectKey) consulted.push(`the project ${JSON.stringify(projectKey)}`);
|
|
457
457
|
// The saved search is a rung above the project. `clarify` means the selector itself is wrong, which
|
|
458
458
|
// the recipeKey/product checks further down report properly — here it just means no scope to read.
|
|
459
|
-
|
|
460
|
-
// cannot be read must not refuse a job that never asked for anything in it.
|
|
461
|
-
const resolved = resolveSearchPolicy(job, { profile, recipes: job.recipeKey ? loadRecipes({ force: true, proseGuard: recipeProseGuard }) : null });
|
|
459
|
+
const resolved = resolveSearchPolicy(job, { profile, recipes: loadRecipes({ force: true, proseGuard: recipeProseGuard }) });
|
|
462
460
|
if (job.recipeKey) consulted.push(`the saved search ${JSON.stringify(String(job.recipeKey))}`);
|
|
463
461
|
const scope = resolveEffectiveScope(job, profile, resolved?.clarify ? null : resolved);
|
|
464
462
|
inherited = Array.isArray(scope?.classes) ? scope.classes : [];
|