bareloop 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/bareloop.context.md +137 -112
- package/package.json +2 -2
- package/src/authorflow.js +333 -58
- package/src/authoring.js +65 -2
- package/src/authorjob.js +57 -9
- package/src/authorreadout.js +50 -2
- package/src/authorrun.js +30 -27
- package/src/authorscout.js +9 -6
- package/src/bridges.js +1 -1
- package/src/bundle.js +7 -4
- package/src/bundlerun.js +4 -1
- package/src/calibrate.js +1 -1
- package/src/cardauthor.js +4 -1
- package/src/cli.js +1 -1
- package/src/config.js +129 -29
- package/src/declaredclose.js +4 -3
- package/src/draftspend.js +6 -1
- package/src/index.js +2 -4
- package/src/interviewrun.js +73 -52
- package/src/job.js +50 -11
- package/src/jobblock.js +140 -0
- package/src/judged.js +12 -44
- package/src/keysfile.js +15 -7
- package/src/kinds.js +2 -2
- package/src/ledger.js +1 -1
- package/src/modellist.js +84 -0
- package/src/monthly.js +23 -43
- package/src/panel/authorroutes.js +34 -19
- package/src/panel/authorsession.js +187 -72
- package/src/panel/index.html +699 -215
- package/src/panel/jobplan.js +107 -0
- package/src/panel/runroutes.js +3 -0
- package/src/panel/server.js +102 -28
- package/src/panel/settingsroutes.js +232 -173
- package/src/planrun.js +6 -6
- package/src/promptregisters.js +1 -1
- package/src/providerrows.js +226 -141
- package/src/providers.js +58 -250
- package/src/providershapes.js +18 -0
- package/src/replay.js +1 -1
- package/src/reuse.js +1 -1
- package/src/root.js +1 -1
- package/src/run.js +3 -3
- package/src/selection.js +1 -1
- package/src/source.js +64 -1
- package/src/text.js +13 -0
- package/src/thinking.js +130 -0
- package/src/transport.js +1 -1
- package/src/userrun.js +53 -76
- package/types/authorflow.d.ts +258 -39
- package/types/authoring.d.ts +7 -1
- package/types/authorjob.d.ts +35 -3
- package/types/authorreadout.d.ts +17 -1
- package/types/authorscout.d.ts +5 -3
- package/types/config.d.ts +23 -6
- package/types/declaredclose.d.ts +4 -1
- package/types/draftspend.d.ts +8 -2
- package/types/index.d.ts +1 -1
- package/types/job.d.ts +6 -6
- package/types/jobblock.d.ts +102 -0
- package/types/judged.d.ts +5 -36
- package/types/keysfile.d.ts +15 -4
- package/types/modellist.d.ts +50 -0
- package/types/monthly.d.ts +4 -3
- package/types/panel/authorsession.d.ts +44 -15
- package/types/panel/jobplan.d.ts +58 -0
- package/types/panel/server.d.ts +34 -4
- package/types/planrun.d.ts +1 -1
- package/types/providerrows.d.ts +192 -99
- package/types/providers.d.ts +35 -116
- package/types/providershapes.d.ts +10 -0
- package/types/run.d.ts +1 -1
- package/types/source.d.ts +26 -0
- package/types/thinking.d.ts +90 -0
- package/types/userrun.d.ts +6 -1
package/src/thinking.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// rewritten-from: fwdloop src/thinking.js@f9449e8
|
|
2
|
+
// PANEL-BUILD.md P8 batch 5 — THE one owner of what a drafting or judging model call sends for thinking. bareloop keeps NO per-model
|
|
3
|
+
// thinking list and NO vendor regex: bare-agent (>=0.52) owns which request shape switches thinking for which model, and
|
|
4
|
+
// `thinkingSupport(kind, model, {baseUrl})` is its own pure lookup (no network): `true` (measured switchable), `false` (measured
|
|
5
|
+
// NOT switchable), `'unverified'` (not measured — the shape ladder is tried at runtime; if every shape fails with a client
|
|
6
|
+
// validation error ONE plain request decides, and the call still runs, reported `thinkingStatus: 'unsupported'`).
|
|
7
|
+
//
|
|
8
|
+
// The tick is the person's, per job ("Thinking on", the signed optional `thinking` field; absent = off). It covers DRAFTING and the
|
|
9
|
+
// rubric JUDGE; the WORKER is never handed a thinking option. PER-CALL options, never constructor options (the drafter's provider is
|
|
10
|
+
// the worker's instance, deliberately left as it was). The raw `thinking` option is never sent (bare-agent throws if both are set).
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const { thinkingSupport: baseSupport, AnthropicProvider, OpenAIProvider, GeminiProvider } = require('bare-agent/providers');
|
|
15
|
+
|
|
16
|
+
/** The words under a greyed "Thinking on" box (the server's `thinking.canChange` decides; the page decides nothing). */
|
|
17
|
+
export const THINKING_FIXED_SAY = "can't change this for this model";
|
|
18
|
+
|
|
19
|
+
/** API shape id -> bare-agent's thinking-ladder kind. A missing shape (clipipe, a test double) has no kind. */
|
|
20
|
+
const KIND_OF_SHAPE = Object.freeze({ 'anthropic-api': 'anthropic', 'openai-api': 'openai', 'gemini-api': 'gemini' });
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* What bare-agent knows about switching this model's thinking. A missing kind, a non-function `impl`, or a throw all read
|
|
24
|
+
* `'unverified'` — never a guess in either direction.
|
|
25
|
+
* @param {string} shape an API shape id (src/providershapes.js)
|
|
26
|
+
* @param {string} model
|
|
27
|
+
* @param {{ baseUrl?: string|null, impl?: Function }} [o] `impl` is a test seam; production uses bare-agent's own
|
|
28
|
+
* @returns {true|false|'unverified'}
|
|
29
|
+
*/
|
|
30
|
+
export function thinkingSupport(shape, model, { baseUrl, impl = baseSupport } = {}) {
|
|
31
|
+
const kind = /** @type {Record<string, string>} */ (KIND_OF_SHAPE)[shape];
|
|
32
|
+
if (kind === undefined || typeof impl !== 'function') return 'unverified';
|
|
33
|
+
try {
|
|
34
|
+
const r = impl(kind, model, baseUrl ? { baseUrl } : undefined);
|
|
35
|
+
return r?.switchable === true ? true : (r?.switchable === false ? false : 'unverified');
|
|
36
|
+
} catch { return 'unverified'; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The per-call options for one support answer and one tick (`true` = "Thinking on"). The table (PANEL-BUILD.md P8, ruling Q1):
|
|
41
|
+
* tick on : true -> on | false -> {} (the box is greyed, cannot be on) | 'unverified' -> on
|
|
42
|
+
* tick off: true -> off | false -> {} (the model thinks as it would) | 'unverified' -> off (bare-agent decides; plain request on failure)
|
|
43
|
+
* @param {true|false|'unverified'} support
|
|
44
|
+
* @param {boolean} tick
|
|
45
|
+
* @returns {{ thinkingMode: 'on'|'off' } | {}}
|
|
46
|
+
*/
|
|
47
|
+
export function thinkingOptions(support, tick) {
|
|
48
|
+
if (support === false) return {};
|
|
49
|
+
return { thinkingMode: tick ? 'on' : 'off' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The enumerated operator knob a drafting call is built with: 'on' | 'off' (the tick, spelled as a word). */
|
|
53
|
+
export const THINKING_MODES = Object.freeze(['on', 'off']);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Throw unless `mode` is one of {@link THINKING_MODES} — an operator-param guard, so a typo never silently means a mode.
|
|
57
|
+
* @param {unknown} mode
|
|
58
|
+
* @returns {'on'|'off'}
|
|
59
|
+
*/
|
|
60
|
+
export function assertThinkingMode(mode) {
|
|
61
|
+
if (mode === 'on' || mode === 'off') return mode;
|
|
62
|
+
throw new Error(`[thinking] draftThinking must be one of ${THINKING_MODES.join(' | ')}, got ${JSON.stringify(mode)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The API shape of a constructed bare-agent provider (the three client classes only); `null` for a test double or a clipipe.
|
|
67
|
+
* @param {any} provider
|
|
68
|
+
* @returns {string|null}
|
|
69
|
+
*/
|
|
70
|
+
export function shapeOfProvider(provider) {
|
|
71
|
+
if (provider instanceof AnthropicProvider) return 'anthropic-api';
|
|
72
|
+
if (provider instanceof OpenAIProvider) return 'openai-api';
|
|
73
|
+
if (provider instanceof GeminiProvider) return 'gemini-api';
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The per-call options every DRAFTING and JUDGING model call on this provider instance carries for this mode. A provider that is
|
|
79
|
+
* not one of the three client classes (a test double, a clipipe) is never sent the switch.
|
|
80
|
+
* @param {any} provider a constructed bare-agent provider
|
|
81
|
+
* @param {unknown} [mode] 'on' | 'off' (default 'off')
|
|
82
|
+
* @returns {{ thinkingMode: 'on'|'off' } | {}}
|
|
83
|
+
*/
|
|
84
|
+
export function thinkingCallOptions(provider, mode = 'off') {
|
|
85
|
+
const m = assertThinkingMode(mode);
|
|
86
|
+
const shape = shapeOfProvider(provider);
|
|
87
|
+
if (shape === null) return {};
|
|
88
|
+
return thinkingOptions(thinkingSupport(shape, String(provider.model), { baseUrl: provider.baseUrl }), m === 'on');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* What the drafting calls will carry for a model the caller has only NAMED (shape, model id, Base URL) — no key, no network. The
|
|
93
|
+
* first drafting row is shown from this, before the key is read.
|
|
94
|
+
* @param {{providerName: string, model: string, baseUrl?: string|null}} o
|
|
95
|
+
* @param {unknown} [mode] 'on' | 'off'
|
|
96
|
+
* @returns {{ support: true|false|'unverified', mode: 'on'|'off', options: {thinkingMode?: 'on'|'off'} }}
|
|
97
|
+
*/
|
|
98
|
+
export function thinkingPlanFor({ providerName, model, baseUrl }, mode = 'off') {
|
|
99
|
+
const m = assertThinkingMode(mode);
|
|
100
|
+
const support = thinkingSupport(providerName, model, { baseUrl });
|
|
101
|
+
return { support, mode: m, options: thinkingOptions(support, m === 'on') };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The server's answer for one Model-menu line: whether the "Thinking on" box can change and the words under it. `false` greys and
|
|
106
|
+
* unticks the box (the note reads {@link THINKING_FIXED_SAY}); `'unverified'` leaves it enabled with no note.
|
|
107
|
+
* @param {string} shape @param {string} model @param {string|null|undefined} baseUrl
|
|
108
|
+
* @returns {{ support: true|false|'unverified', canChange: boolean, say: string }}
|
|
109
|
+
*/
|
|
110
|
+
export function thinkingForLine(shape, model, baseUrl) {
|
|
111
|
+
const support = thinkingSupport(shape, model, { baseUrl });
|
|
112
|
+
return { support, canChange: support !== false, say: support === false ? THINKING_FIXED_SAY : '' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The first drafting row's words — code-owned, ALWAYS starting "thinking on" or "thinking off". `status` is bare-agent's own
|
|
117
|
+
* `thinkingStatus` for a call that has run ('applied' | 'unsupported' | 'ignored' | 'unknown'); undefined = before any call, read from
|
|
118
|
+
* the plan (`support`, `mode`).
|
|
119
|
+
* @param {{ mode: 'on'|'off', support?: true|false|'unverified', status?: string }} s
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
export function thinkingRowText({ mode, support, status }) {
|
|
123
|
+
if (status === 'applied') return `thinking ${mode}`;
|
|
124
|
+
if (status === 'unsupported') return "thinking on (this model can't change it)";
|
|
125
|
+
if (status === 'ignored') return 'thinking on (the model thought anyway)';
|
|
126
|
+
if (status === 'unknown') return `thinking ${mode} (not confirmed)`;
|
|
127
|
+
if (support === false) return "thinking on (this model can't change it)";
|
|
128
|
+
if (support === 'unverified') return `thinking ${mode} (not verified for this model)`;
|
|
129
|
+
return `thinking ${mode}`;
|
|
130
|
+
}
|
package/src/transport.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// hamr's ruling, verbatim: "one retry for transport layer failure and
|
|
2
|
-
// reporting with the rest end of gate." (F115, docs/
|
|
2
|
+
// reporting with the rest end of gate." (F115, docs/wiki/FINDINGS.md.)
|
|
3
3
|
//
|
|
4
4
|
// A transport failure is fetch THROWING — the request never produced an HTTP
|
|
5
5
|
// response at all (a TLS fault mid-record, a reset socket, a DNS/network
|
package/src/userrun.js
CHANGED
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
// authored is preserved from the spine as a reusable artifact, and the next run of
|
|
26
26
|
// this shape reuses and fine-tunes it rather than starting cold.
|
|
27
27
|
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, rmSync, readdirSync, openSync, readSync, closeSync, fstatSync, unlinkSync } from 'node:fs';
|
|
28
|
-
import { createRequire } from 'node:module';
|
|
29
28
|
import { fileURLToPath } from 'node:url';
|
|
30
29
|
import { execFileSync, spawn } from 'node:child_process';
|
|
31
30
|
import { join, resolve, dirname, basename } from 'node:path';
|
|
@@ -83,14 +82,11 @@ import { parseJsonl } from './replayio.js';
|
|
|
83
82
|
import { claimRun, monthlyRefusalText, legSpend, chainSpend } from './monthly.js';
|
|
84
83
|
import { ConfigError } from './config.js';
|
|
85
84
|
import { commandFor } from './invoke.js';
|
|
86
|
-
import {
|
|
85
|
+
import { judgeRatesFor, keyNameFor, keyValueOf, priceReadout, ratesFor, ratesOfLine, resolveWorker, judgeRowFor, rowsForHome, REFUSE_NAME_MODEL } from './providerrows.js';
|
|
87
86
|
// the banner's wall arithmetic, extracted so it is reachable by a test (F83): the
|
|
88
87
|
// end-of-run readout sits past the approval gate, so nothing could ever drive it here
|
|
89
88
|
import { wallLine, doomedResume, deathAtOf, evidencePackage, doorLines, resumeAtLines, reviewDoorPackage, runDoorLines, tokensLine, doorTimingRedLines } from './u-readout.js';
|
|
90
89
|
|
|
91
|
-
const require = createRequire(import.meta.url);
|
|
92
|
-
const { AnthropicProvider } = require('bare-agent/providers');
|
|
93
|
-
|
|
94
90
|
const JOBS = {
|
|
95
91
|
'aurora-spawner': {
|
|
96
92
|
spec: 'aurora-u-spawner-types.json',
|
|
@@ -111,7 +107,7 @@ const JOBS = {
|
|
|
111
107
|
seed: '92d71a7c1253f8f2430e2d308ecfef01c826b5c2',
|
|
112
108
|
},
|
|
113
109
|
// Bench row G3 (the NEGATIVE row) — the planted contract contradiction
|
|
114
|
-
// (docs/
|
|
110
|
+
// (docs/logs/G3-SCOPING.md). A copy of the pulselog-u patient (patients are
|
|
115
111
|
// always copies) with one extra commit: two test/** files that pin
|
|
116
112
|
// assembleEmail's subject to two different strings, unsatisfiable together
|
|
117
113
|
// inside writeScope: src/**. Not frozen, not signed, no run — BUILT ON BRANCH
|
|
@@ -153,7 +149,7 @@ const JOBS = {
|
|
|
153
149
|
// Bench row G2 (the NEGATIVE row) — TESTGEN cold start in plan-v1. The legacy
|
|
154
150
|
// steps[] config went 0-of-43 (F38); this config's rate is UNKNOWN until its
|
|
155
151
|
// establishing run. Same patient/seed/grader as the l2accept battery
|
|
156
|
-
// (TESTGEN-PREREG); the spec is the cold goal. See docs/
|
|
152
|
+
// (TESTGEN-PREREG); the spec is the cold goal. See docs/logs/G2-SCOPING.md.
|
|
157
153
|
'aurora-testgen-cold': {
|
|
158
154
|
spec: 'aurora-testgen-cold.json',
|
|
159
155
|
workdir: '/home/hamr/PycharmProjects/bareloop-patients/aurora-soar',
|
|
@@ -174,10 +170,10 @@ const JOBS = {
|
|
|
174
170
|
},
|
|
175
171
|
// PRD item 28 / 30.9 — the openai-api probe through THIS runner (the shipped
|
|
176
172
|
// one), not poc-run-param.mjs. Same job shape, same seed, its own patient copy.
|
|
177
|
-
'bareguard-types-
|
|
178
|
-
spec: 'bareguard-u-types-
|
|
179
|
-
workdir: '/home/hamr/PycharmProjects/bareloop-patients/bareguard-u-
|
|
180
|
-
spine: 'bareguard-u-
|
|
173
|
+
'bareguard-types-gateway': {
|
|
174
|
+
spec: 'bareguard-u-types-gateway.json',
|
|
175
|
+
workdir: '/home/hamr/PycharmProjects/bareloop-patients/bareguard-u-gateway',
|
|
176
|
+
spine: 'bareguard-u-gateway-bareloop',
|
|
181
177
|
seed: '2ae8fcd37041c186524a6eb5e953b9752cd602fa',
|
|
182
178
|
},
|
|
183
179
|
// N4's hitl PROVING job (build plan §Proving; hamr's ruling, 2026-08-13). The job
|
|
@@ -273,7 +269,8 @@ class ExitSignal extends Error {
|
|
|
273
269
|
* @property {string} [into] the source door's own directory (`source.json`, and the spine directory beside it).
|
|
274
270
|
* Default `dirname(workdir)` — the copied-tree layout, where the tree is `<into>/tree`. A repo job on a WORKTREE
|
|
275
271
|
* (P6 item 1) has its tree inside the person's repo, so its `into` is passed explicitly.
|
|
276
|
-
* @property {string|null} [model] the raw `--model
|
|
272
|
+
* @property {string|null} [model] the raw `--model <id>` (a model id of a ticked Settings line), or null
|
|
273
|
+
* @property {string|null} [providerKey] the raw `--provider <KEY>` (a key's env name), or null
|
|
277
274
|
* @property {string|null} [readShim] the raw `--read-shim` arm name, or null for the default
|
|
278
275
|
* @property {string|null} [scout] the raw `--scout` value (`on`/`off`), or null for the default
|
|
279
276
|
* @property {string|null} [draftSpentUsd] the raw `--draft-spent-usd` value (hamr's
|
|
@@ -306,6 +303,7 @@ class ExitSignal extends Error {
|
|
|
306
303
|
* @property {string} spineName
|
|
307
304
|
* @property {string} [into] see `RunOpts.into`
|
|
308
305
|
* @property {string|null} model
|
|
306
|
+
* @property {string|null} providerKey
|
|
309
307
|
* @property {string|null} readShim
|
|
310
308
|
* @property {string|null} scout
|
|
311
309
|
* @property {string|null} draftSpentUsd
|
|
@@ -389,25 +387,13 @@ async function execute(ctx) {
|
|
|
389
387
|
/** @type {{workdir: string, seed: string, spine: string}} */
|
|
390
388
|
const target = { workdir: ctx.workdir, seed: ctx.seed, spine: ctx.spineName };
|
|
391
389
|
|
|
392
|
-
// --model
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
// (provider-gated, battery rule) — nothing to gate yet since neither tier sets effort.
|
|
398
|
-
// PRD v1.36 floor: the drafter/default tier is sonnet MINIMUM — measured, a haiku
|
|
399
|
-
// drafter died plan-red twice on the same rejection (ms7gne7s). `--model haiku` runs
|
|
400
|
-
// BELOW the floor: an explicit operator probe whose rows are probes, never battery
|
|
401
|
-
// evidence.
|
|
402
|
-
const DEFAULT_TIER_MODELS = { sonnet: 'claude-sonnet-5', haiku: 'claude-haiku-4-5-20251001' };
|
|
403
|
-
// `modelFlagArg` is null when --model was never passed at all — distinct from a
|
|
404
|
-
// flag that happens to resolve to the default tier's own id, because the SPEC
|
|
405
|
-
// (below, once loaded) needs to know whether a flag was actually given, not just
|
|
406
|
-
// what it resolved to (build-list #3: spec wins, a mismatched flag is refused).
|
|
390
|
+
// --model <id> names the Settings line a run works on when the SPEC names no model (an older spec, signed before a job carried its
|
|
391
|
+
// own); a spec's own `model` still wins and a different --model is refused. --provider <KEY> narrows to one key when two keys tick the
|
|
392
|
+
// same id. Runner territory: the spec names neither, so the signed hash is unaffected. There are no tier aliases and no built-in model.
|
|
393
|
+
// `modelFlagArg` is null when --model was never passed — distinct from a flag that happens to equal the spec's model, because the SPEC
|
|
394
|
+
// (below, once loaded) needs to know whether a flag was actually given (build-list #3: spec wins, a mismatched flag is refused).
|
|
407
395
|
const modelFlagArg = ctx.model ?? null;
|
|
408
|
-
const
|
|
409
|
-
const FLAG_TIER_MODEL = DEFAULT_TIER_MODELS[/** @type {keyof typeof DEFAULT_TIER_MODELS} */ (tierArg)];
|
|
410
|
-
if (!FLAG_TIER_MODEL) { err(`unknown --model "${tierArg}" — one of: ${Object.keys(DEFAULT_TIER_MODELS).join(', ')}`); throw new ExitSignal(2); }
|
|
396
|
+
const providerFlagArg = ctx.providerKey ?? null;
|
|
411
397
|
// --read-shim picks the READ SHIM's ARM (src/readshim.js, Phase 2 A0/A1/A2/A3). Runner
|
|
412
398
|
// territory exactly like --model: the spec names no shim, so the signed hash is
|
|
413
399
|
// unaffected. Phase 2 itself never flipped this default — its own "what this
|
|
@@ -445,7 +431,7 @@ async function execute(ctx) {
|
|
|
445
431
|
const SHIM_TAIL = shimArg === null ? '' : ` --read-shim ${shimArg}`;
|
|
446
432
|
|
|
447
433
|
// --scout picks whether the plan flow pays for the read-only survey at all
|
|
448
|
-
// (docs/
|
|
434
|
+
// (docs/logs/SCOUT-CONTRAST.md). Runner territory exactly like --read-shim
|
|
449
435
|
// and --model: the spec names no scout, so the signed hash is unaffected. An
|
|
450
436
|
// unrecognised value throws at argv, before the approval gate — the same
|
|
451
437
|
// param-guard class as --read-shim above, for the same reason: a silently
|
|
@@ -545,11 +531,12 @@ async function execute(ctx) {
|
|
|
545
531
|
try {
|
|
546
532
|
modelResolution = resolveWorkerModel({
|
|
547
533
|
specModel: spec.model,
|
|
548
|
-
flagModel: modelFlagArg === null ? undefined :
|
|
549
|
-
|
|
534
|
+
flagModel: modelFlagArg === null ? undefined : modelFlagArg,
|
|
535
|
+
// a scripted provider handed in by a test IS the run: it names its own model; a real run has no default
|
|
536
|
+
suppliedModel: deps.provider ? String(/** @type {any} */ (deps.provider).model ?? 'scripted') : undefined,
|
|
550
537
|
});
|
|
551
538
|
} catch (e) {
|
|
552
|
-
die(/** @type {Error} */ (e).message);
|
|
539
|
+
die(spec.model === undefined && modelFlagArg === null ? REFUSE_NAME_MODEL : /** @type {Error} */ (e).message);
|
|
553
540
|
}
|
|
554
541
|
const MODEL = modelResolution.model;
|
|
555
542
|
const MODEL_LABEL = modelResolution.source === 'spec' ? `${MODEL} (spec)` : MODEL;
|
|
@@ -1022,16 +1009,20 @@ async function execute(ctx) {
|
|
|
1022
1009
|
// spine, then handed down as data (`runJob`'s `rates`) — nothing deep in the run re-reads config. A bad
|
|
1023
1010
|
// price refuses here; it never falls back to the built-in guess. No price set = `null` = no `rates` key
|
|
1024
1011
|
// passed anywhere, byte-identical to before. The judge's own row is resolved below, beside its key.
|
|
1025
|
-
const keyCfg = cfgOn ? rowsForHome(cfgHome) : [];
|
|
1012
|
+
const keyCfg = cfgOn ? rowsForHome(cfgHome, env) : [];
|
|
1026
1013
|
/** @type {ReturnType<typeof ratesFor>} */
|
|
1027
1014
|
let workerPrice = null;
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1015
|
+
// WHICH SETTINGS LINE THIS RUN WORKS ON (P8): the row is the key, the Base URL and the old-max_tokens tick; the line is the model
|
|
1016
|
+
// and its price. A scripted provider handed in by a test IS the run (no row). A refusal here is $0, before anything else.
|
|
1017
|
+
const W = deps.provider ? null : resolveWorker(keyCfg, spec, { model: modelFlagArg, provider: providerFlagArg });
|
|
1018
|
+
const refuseWorker = () => { if (W && !W.ok) { err(W.say.includes('Nothing was spent') ? W.say : `${W.say} Nothing was spent.`); throw new ExitSignal(2); } };
|
|
1019
|
+
// the preview cannot name its key without a line: refuse it here. A SIGNED run meets its other gates first (pause door, wall) and
|
|
1020
|
+
// is refused at the key check below — still before anything is spent
|
|
1021
|
+
if ((ctx.approve ?? null) === null) refuseWorker();
|
|
1022
|
+
/** the endpoint the worker talks to: the chosen row's own Base URL (a spec signed before addresses were required carries none) */
|
|
1023
|
+
const baseUrl = W && W.ok ? W.baseUrl : (typeof spec.baseUrl === 'string' ? spec.baseUrl : undefined);
|
|
1024
|
+
const wLine = W && W.ok ? ratesOfLine(W.line) : null;
|
|
1025
|
+
workerPrice = W ? (wLine && W.ok ? { ...wLine, envName: W.row.envName } : null) : ratesFor(spec.provider, spec.baseUrl, keyCfg, spec.model);
|
|
1035
1026
|
|
|
1036
1027
|
if ((ctx.approve ?? null) !== specHash) {
|
|
1037
1028
|
out(dead ? 'U — RESUME, continuing a halted run, REAL dollars' : 'U — user-mode e2e, ONE run, REAL dollars');
|
|
@@ -1192,7 +1183,7 @@ async function execute(ctx) {
|
|
|
1192
1183
|
// right key only by accident of the runner's own generic error message,
|
|
1193
1184
|
// never from this hint. `keyNameFor` is the same resolved name
|
|
1194
1185
|
// the real key check at launch reads (the person's row variable, else the provider's built-in one).
|
|
1195
|
-
const invoke = (/** @type {string} */ tail) => ` ${keyNameFor(spec.provider, spec.baseUrl, keyCfg, spec.model).name}=... ${INVOKE}${dead ? ` --resume ${RESUME}` : ''}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL}${REGISTRY_TAIL}${tail} --approve ${PRINT_APPROVE}`;
|
|
1186
|
+
const invoke = (/** @type {string} */ tail) => ` ${(W && W.ok ? W.row.envName : null) ?? keyNameFor(spec.provider, spec.baseUrl, keyCfg, spec.model).name ?? '<KEY>'}=... ${INVOKE}${dead ? ` --resume ${RESUME}` : ''}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL}${REGISTRY_TAIL}${tail} --approve ${PRINT_APPROVE}`;
|
|
1196
1187
|
/** the door the operator has already picked, as flags — hoisted out of the else
|
|
1197
1188
|
* below so the inhibitor line at the bottom can print the WHOLE command rather
|
|
1198
1189
|
* than a shape the operator has to assemble. Empty on an ordinary run and on the
|
|
@@ -1446,13 +1437,15 @@ async function execute(ctx) {
|
|
|
1446
1437
|
let judgePrice = null;
|
|
1447
1438
|
if (JUDGES) {
|
|
1448
1439
|
try {
|
|
1449
|
-
judgePrice = judgeRatesFor(judge, spec.provider,
|
|
1440
|
+
judgePrice = judgeRatesFor(judge, spec.provider, baseUrl, workerPrice, keyCfg);
|
|
1450
1441
|
} catch (e) {
|
|
1451
1442
|
if (!(e instanceof ConfigError)) throw e;
|
|
1452
1443
|
err(`${e.message} — refusing to start rather than guess a price. Nothing spent.`);
|
|
1453
1444
|
throw new ExitSignal(2);
|
|
1454
1445
|
}
|
|
1455
1446
|
}
|
|
1447
|
+
/** the row the judge's key (and endpoint) comes from: the worker's own row when the judge shares its provider, else the judge provider's row */
|
|
1448
|
+
const judgeRow = judgeRowFor(keyCfg, judge, spec.provider, W && W.ok ? W.row : null);
|
|
1456
1449
|
// undefined, not null: both come straight off `env[...]` lookups below, which
|
|
1457
1450
|
// (like process.env) never produce null for a missing key.
|
|
1458
1451
|
/** @type {string|undefined} */
|
|
@@ -1468,13 +1461,10 @@ async function execute(ctx) {
|
|
|
1468
1461
|
// row = the built-in. Names only — values come from the
|
|
1469
1462
|
// env / keys file. The judge follows the worker's choice when it is the same provider
|
|
1470
1463
|
// (the same rule that gives it the worker's baseUrl below).
|
|
1471
|
-
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
// A green anthropic-api job's behaviour is byte-identical to before: its
|
|
1476
|
-
// worker key IS `ANTHROPIC_API_KEY`, demanded as ever.
|
|
1477
|
-
workerApiKey = workerEnv[providerEntry.envKey];
|
|
1464
|
+
refuseWorker();
|
|
1465
|
+
const workerKeyName = /** @type {NonNullable<typeof W> & {ok: true}} */ (W).row.envName;
|
|
1466
|
+
// A job's worker key is its Settings row's own variable — there is no built-in name.
|
|
1467
|
+
workerApiKey = keyValueOf(env, workerKeyName);
|
|
1478
1468
|
if (!workerApiKey) { err(`${workerKeyName} not set (secrets load from the environment — never the tree)`); throw new ExitSignal(2); }
|
|
1479
1469
|
// F181 — a key that reads as "set" above can still carry a line break/
|
|
1480
1470
|
// control char/stray whitespace (a two-line secret-store entry, e.g.) and
|
|
@@ -1487,10 +1477,10 @@ async function execute(ctx) {
|
|
|
1487
1477
|
// override matters most in the case this item exists for: a job whose worker and
|
|
1488
1478
|
// judge are the SAME provider but different accounts, and the anthropic case
|
|
1489
1479
|
// where one variable used to do two jobs.
|
|
1490
|
-
judgeApiKey = env.JUDGE_API_KEY ??
|
|
1480
|
+
judgeApiKey = env.JUDGE_API_KEY ?? keyValueOf(env, judgeRow?.envName);
|
|
1491
1481
|
if (JUDGES && !judgeApiKey) {
|
|
1492
1482
|
err(`This job's close JUDGES (${judgedStages(spec.closeDecl).length} judged stage(s)) and its judge is ${judge.model} on ${judge.provider}${spec.judge ? ' (signed override)' : " (this job's own worker model — no `judge` override signed)"}.`);
|
|
1493
|
-
err(`Neither JUDGE_API_KEY nor ${
|
|
1483
|
+
err(`Neither JUDGE_API_KEY nor ${judgeRow?.envName ?? `a key row for ${judge.provider}`} is set — secrets load from the environment, never the tree.`);
|
|
1494
1484
|
err('Refused at $0, BEFORE the worker spends anything: a judged run that discovers this at the close has paid for a verdict it cannot render.');
|
|
1495
1485
|
throw new ExitSignal(2);
|
|
1496
1486
|
}
|
|
@@ -1500,7 +1490,7 @@ async function execute(ctx) {
|
|
|
1500
1490
|
if (JUDGES) {
|
|
1501
1491
|
const judgeKeyProblem = apiKeyProblem(/** @type {string} */ (judgeApiKey));
|
|
1502
1492
|
if (judgeKeyProblem) {
|
|
1503
|
-
const judgeEnvName = env.JUDGE_API_KEY ? 'JUDGE_API_KEY' :
|
|
1493
|
+
const judgeEnvName = env.JUDGE_API_KEY ? 'JUDGE_API_KEY' : (judgeRow?.envName ?? judge.provider);
|
|
1504
1494
|
err(`${judgeEnvName} ${judgeKeyProblem} — refusing rather than crashing mid-call (never trimmed or repaired; fix the value at its source)`);
|
|
1505
1495
|
throw new ExitSignal(2);
|
|
1506
1496
|
}
|
|
@@ -1640,7 +1630,7 @@ async function execute(ctx) {
|
|
|
1640
1630
|
// OpenAI-compatible gateway/endpoint. Undefined for anthropic-api jobs and
|
|
1641
1631
|
// for an openai-api job that names none (bare-agent's OpenAIProvider then
|
|
1642
1632
|
// defaults to api.openai.com/v1 on its own).
|
|
1643
|
-
|
|
1633
|
+
// (`baseUrl` — the worker row's Base URL — was resolved above with the Settings line)
|
|
1644
1634
|
// TEST SEAM — same shape as the bundle door (src/bundlerun.js): a caller that supplies
|
|
1645
1635
|
// `deps.provider` skips the real-key check above entirely and this
|
|
1646
1636
|
// construction never runs (a scripted provider IS the run).
|
|
@@ -1648,26 +1638,11 @@ async function execute(ctx) {
|
|
|
1648
1638
|
let providerFor = deps.providerFor;
|
|
1649
1639
|
let judgeProvider = deps.judgeProvider ?? null;
|
|
1650
1640
|
if (!provider) {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
//
|
|
1654
|
-
//
|
|
1655
|
-
|
|
1656
|
-
// battery rule) - nothing to gate yet since neither tier sets effort params.
|
|
1657
|
-
// `sonnet` maps to the RESOLVED worker model (MODEL — spec/flag/default), not the
|
|
1658
|
-
// bare provider-table id, so a spec-named model reaches every plan step's own
|
|
1659
|
-
// provider (STEP_MODELS is sonnet-only), not just the top-level `provider` above.
|
|
1660
|
-
// ONLY when the SPEC named it: a `--model haiku` probe keeps its old reach (the
|
|
1661
|
-
// top-level provider), never silently widening into every step's tier. The tier
|
|
1662
|
-
// table itself comes from `providerEntry` (src/providers.js) — for openai-api
|
|
1663
|
-
// today that means BOTH tiers resolve to the same `deepseek-flash` id (hamr's
|
|
1664
|
-
// ruling, PRD 30.7: one secondary provider, not a menu; swapped from the
|
|
1665
|
-
// retired `deepseek-chat`, F171), so a `--model haiku` probe against an
|
|
1666
|
-
// openai-api job harmlessly re-resolves to the same model.
|
|
1667
|
-
const TIER_MODELS = modelResolution.source === 'spec' ? { ...providerEntry.tiers, sonnet: MODEL } : providerEntry.tiers;
|
|
1668
|
-
/** @type {Record<string, any>} */
|
|
1669
|
-
const tierCache = {};
|
|
1670
|
-
providerFor = (/** @type {string} */ tier) => (tierCache[tier] ??= TIER_MODELS[/** @type {keyof typeof TIER_MODELS} */ (tier)] === MODEL ? provider : makeProvider(spec.provider, { apiKey: /** @type {string} */ (workerApiKey), model: TIER_MODELS[/** @type {keyof typeof TIER_MODELS} */ (tier)], baseUrl }));
|
|
1641
|
+
const legacyMaxTokens = W && W.ok ? W.row.legacyMaxTokens : false;
|
|
1642
|
+
provider = makeProvider(spec.provider, { apiKey: /** @type {string} */ (workerApiKey), model: MODEL, baseUrl, legacyMaxTokens });
|
|
1643
|
+
// P: the per-step model factory. The ONLY tier a plan may name is the schema token `sonnet` (STEP_MODELS, src/plan.js — a
|
|
1644
|
+
// documented schema token, not a model id); it means THIS job's worker model, so every step's provider is the one above.
|
|
1645
|
+
providerFor = () => provider;
|
|
1671
1646
|
// SOFTGREEN — the JUDGED stage's own provider. Since PRD item 32.1 its identity is
|
|
1672
1647
|
// RESOLVED (`judge` above), not pinned: the spec's signed `judge` override, else this
|
|
1673
1648
|
// job's own worker model. It is still never a step knob and never agent-selectable —
|
|
@@ -1684,7 +1659,7 @@ async function execute(ctx) {
|
|
|
1684
1659
|
// every live softgreen run would have done: run-author wired this seam and this
|
|
1685
1660
|
// runner never did.
|
|
1686
1661
|
judgeProvider = judgeApiKey
|
|
1687
|
-
? makeProvider(judge.provider, { apiKey: /** @type {string} */ (judgeApiKey), model: judge.model, baseUrl: judge.provider === spec.provider ? baseUrl : undefined })
|
|
1662
|
+
? makeProvider(judge.provider, { apiKey: /** @type {string} */ (judgeApiKey), model: judge.model, baseUrl: judge.provider === spec.provider ? baseUrl : (judgeRow && judgeRow.baseUrl.trim() !== '' ? judgeRow.baseUrl.trim() : undefined), legacyMaxTokens: judgeRow ? judgeRow.legacyMaxTokens : false })
|
|
1688
1663
|
: null;
|
|
1689
1664
|
}
|
|
1690
1665
|
|
|
@@ -2433,6 +2408,7 @@ function buildCtx(mode, opts) {
|
|
|
2433
2408
|
spineName: /** @type {string} */ (opts.spineName),
|
|
2434
2409
|
...(opts.into === undefined ? {} : { into: opts.into }),
|
|
2435
2410
|
model: opts.model ?? null,
|
|
2411
|
+
providerKey: opts.providerKey ?? null,
|
|
2436
2412
|
readShim: opts.readShim ?? null,
|
|
2437
2413
|
scout: opts.scout ?? null,
|
|
2438
2414
|
draftSpentUsd: opts.draftSpentUsd ?? null,
|
|
@@ -2661,6 +2637,7 @@ export async function main(argv, deps = {}) {
|
|
|
2661
2637
|
spineName: target.spine,
|
|
2662
2638
|
...(target.into === undefined ? {} : { into: target.into }),
|
|
2663
2639
|
model: argFlag('model'),
|
|
2640
|
+
providerKey: argFlag('provider'),
|
|
2664
2641
|
readShim: argFlag('read-shim'),
|
|
2665
2642
|
scout: argFlag('scout'),
|
|
2666
2643
|
draftSpentUsd: argFlag('draft-spent-usd'),
|