bareloop 0.40.0 → 0.42.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 +96 -1
- package/README.md +13 -21
- package/bareloop.context.md +141 -128
- package/package.json +5 -4
- package/src/authorflow.js +346 -38
- package/src/authoring.js +92 -7
- package/src/authorjob.js +82 -20
- package/src/authorreadout.js +89 -5
- package/src/authorrun.js +39 -30
- package/src/authorscout.js +112 -4
- package/src/bundle.js +7 -4
- package/src/bundlerun.js +4 -1
- package/src/cli.js +1 -1
- package/src/config.js +129 -29
- package/src/declaredclose.js +23 -12
- package/src/draftspend.js +4 -4
- package/src/facts.js +317 -0
- package/src/factwords.js +129 -0
- package/src/index.js +2 -4
- package/src/inputlook.js +301 -0
- package/src/interviewrun.js +17 -25
- package/src/job.js +15 -8
- package/src/jobblock.js +7 -2
- package/src/judged.js +11 -43
- package/src/keysfile.js +15 -7
- package/src/kinds.js +86 -5
- package/src/labelscan.js +170 -0
- package/src/modellist.js +84 -0
- package/src/monthly.js +23 -43
- package/src/pagefetch.js +125 -0
- package/src/panel/authorroutes.js +34 -16
- package/src/panel/authorsession.js +161 -80
- package/src/panel/index.html +553 -152
- package/src/panel/jobplan.js +34 -11
- package/src/panel/runroutes.js +3 -0
- package/src/panel/server.js +54 -18
- package/src/panel/settingsroutes.js +232 -173
- package/src/planrun.js +5 -4
- package/src/providerrows.js +226 -141
- package/src/providers.js +58 -332
- package/src/providershapes.js +18 -0
- package/src/replay.js +1 -1
- package/src/rubricstep.js +804 -0
- package/src/source.js +166 -1
- package/src/thinking.js +130 -0
- package/src/u-readout.js +1 -1
- package/src/userrun.js +64 -75
- package/types/authorflow.d.ts +272 -18
- package/types/authoring.d.ts +29 -1
- package/types/authorjob.d.ts +9 -7
- package/types/authorreadout.d.ts +27 -2
- package/types/authorscout.d.ts +72 -2
- package/types/config.d.ts +23 -6
- package/types/declaredclose.d.ts +4 -2
- package/types/draftspend.d.ts +8 -8
- package/types/facts.d.ts +71 -0
- package/types/factwords.d.ts +42 -0
- package/types/index.d.ts +1 -1
- package/types/inputlook.d.ts +238 -0
- package/types/job.d.ts +5 -5
- package/types/jobblock.d.ts +8 -1
- package/types/judged.d.ts +5 -36
- package/types/keysfile.d.ts +15 -4
- package/types/labelscan.d.ts +38 -0
- package/types/modellist.d.ts +50 -0
- package/types/monthly.d.ts +4 -3
- package/types/pagefetch.d.ts +71 -0
- package/types/panel/authorsession.d.ts +20 -11
- package/types/panel/jobplan.d.ts +12 -4
- package/types/panel/server.d.ts +18 -6
- package/types/providerrows.d.ts +192 -99
- package/types/providers.d.ts +34 -159
- package/types/providershapes.d.ts +10 -0
- package/types/rubricstep.d.ts +286 -0
- package/types/source.d.ts +94 -0
- package/types/thinking.d.ts +90 -0
- package/types/userrun.d.ts +17 -1
|
@@ -22,14 +22,16 @@ import { spawn as realSpawn } from 'node:child_process';
|
|
|
22
22
|
import { openSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { readFileSync } from 'node:fs';
|
|
25
|
+
import { planMatchesClose } from '../rubricstep.js';
|
|
25
26
|
import { createSession, validateJobCard, validateReuseCard, lockedFieldChanged, buildReuseSpec, cardFields, STEP_LABELS } from './authorsession.js';
|
|
26
27
|
import { parseInputs } from '../jobblock.js';
|
|
27
|
-
import { proveInputFiles } from '../source.js';
|
|
28
|
+
import { proveInputFiles, looksLikeRubricInput } from '../source.js';
|
|
28
29
|
import { validateJob } from '../job.js';
|
|
29
30
|
import { checkMonthlyRoom, monthlyRefusalText } from '../monthly.js';
|
|
30
31
|
import { ConfigError } from '../config.js';
|
|
31
32
|
import { keysForDoor, keysHome } from '../keysfile.js';
|
|
32
|
-
import {
|
|
33
|
+
import { endpointOf, judgePick, keyValueOf, rowsForHome, tickedLines } from '../providerrows.js';
|
|
34
|
+
import { thinkingForLine } from '../thinking.js';
|
|
33
35
|
import { apiKeyProblem, checkProviderReachable } from '../providers.js';
|
|
34
36
|
|
|
35
37
|
/** @returns {string} a fresh per-process token — never persisted, never logged */
|
|
@@ -137,13 +139,13 @@ export function createAuthorRoutes(opts) {
|
|
|
137
139
|
if (pathname === '/api/author/model-check') {
|
|
138
140
|
if (req.method !== 'GET') { send(405, { ok: false, error: 'GET only' }); return true; }
|
|
139
141
|
const q = new URL(/** @type {string} */ (req.url), 'http://127.0.0.1').searchParams;
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// the key of the Settings row this
|
|
145
|
-
const keyName =
|
|
146
|
-
const raw = envNow()
|
|
142
|
+
const rows = rowsForHome(keysHome(opts.home), envNow());
|
|
143
|
+
const pick = judgePick(rows, { provider: q.get('provider'), model: q.get('model') });
|
|
144
|
+
if (!pick.ok) { send(400, { ok: false, error: pick.say }); return true; }
|
|
145
|
+
const choice = pick.choice;
|
|
146
|
+
// the key of the Settings row this line belongs to
|
|
147
|
+
const keyName = choice.envName;
|
|
148
|
+
const raw = keyValueOf(envNow(), keyName);
|
|
147
149
|
const problem = raw ? apiKeyProblem(raw) : null;
|
|
148
150
|
const keyStatus = !raw ? 'missing' : (problem ? 'bad-shape' : 'found');
|
|
149
151
|
if (keyStatus !== 'found') {
|
|
@@ -157,7 +159,7 @@ export function createAuthorRoutes(opts) {
|
|
|
157
159
|
// `checkProviderReachable`'s own doc) — a short timeout so a flaky
|
|
158
160
|
// endpoint never hangs the card.
|
|
159
161
|
checkProviderReachable({
|
|
160
|
-
providerName: choice.provider, apiKey: /** @type {string} */ (
|
|
162
|
+
providerName: choice.provider, apiKey: /** @type {string} */ (raw), model: choice.name,
|
|
161
163
|
baseUrl: choice.baseUrl, fetchImpl, timeoutMs: 4000,
|
|
162
164
|
}).then((r) => {
|
|
163
165
|
send(200, {
|
|
@@ -180,7 +182,11 @@ export function createAuthorRoutes(opts) {
|
|
|
180
182
|
// ── P4b: Chat's Model menu IS the Settings rows (a blank Name is not offered). ──
|
|
181
183
|
if (pathname === '/api/author/models') {
|
|
182
184
|
if (req.method !== 'GET') { send(405, { ok: false, error: 'GET only' }); return true; }
|
|
183
|
-
|
|
185
|
+
// every ticked-and-priced line of a row, labelled `<KEY> · <model>`; `thinking` is the server's answer for the "Thinking on" box
|
|
186
|
+
const lines = tickedLines(rowsForHome(keysHome(opts.home), envNow())).map(({ row, line }) => ({
|
|
187
|
+
slot: row.envName, model: line.model, label: `${row.envName} · ${line.model}`, shape: row.provider, endpoint: endpointOf(row.provider, row.baseUrl), thinking: thinkingForLine(row.provider, line.model, row.baseUrl),
|
|
188
|
+
}));
|
|
189
|
+
send(200, { ok: true, lines, say: lines.length === 0 ? 'Tick a model in Settings > Providers.' : '' });
|
|
184
190
|
return true;
|
|
185
191
|
}
|
|
186
192
|
|
|
@@ -207,14 +213,14 @@ export function createAuthorRoutes(opts) {
|
|
|
207
213
|
const sp = new URL(/** @type {string} */ (req.url), 'http://127.0.0.1').searchParams;
|
|
208
214
|
const runid = sp.get('runid') ?? '';
|
|
209
215
|
// `model` = the card's Model box right now: the estimate line counts only runs on that worker
|
|
210
|
-
const asModel = sp.has('model') ? { model: sp.get('model') ?? '' } : {};
|
|
216
|
+
const asModel = sp.has('model') ? { model: sp.get('model') ?? '', provider: sp.get('provider') ?? '' } : {};
|
|
211
217
|
const pre = sp.has('import') ? opts.startFrom.getImport(sp.get('import') ?? '', asModel) : opts.startFrom.get(runid, asModel);
|
|
212
218
|
if (pre === null) { send(404, { ok: false, error: 'no such run' }); return true; }
|
|
213
219
|
if (!pre.ok) { send(409, pre); return true; }
|
|
214
220
|
send(200, {
|
|
215
221
|
ok: true, origin: pre.origin, card: pre.card, from: pre.from, note: pre.note,
|
|
216
222
|
locked: pre.locked, open: pre.open, specHash: pre.specHash, workflowKey: pre.workflowKey, trackRecord: pre.trackRecord,
|
|
217
|
-
line: pre.line,
|
|
223
|
+
line: pre.line, specNamesModel: pre.specNamesModel, specWorker: pre.specWorker,
|
|
218
224
|
});
|
|
219
225
|
return true;
|
|
220
226
|
}
|
|
@@ -225,7 +231,7 @@ export function createAuthorRoutes(opts) {
|
|
|
225
231
|
if (!opts.startFrom || !opts.startFrom.listJobs) { send(404, { ok: false, error: 'not found' }); return true; }
|
|
226
232
|
if (req.method !== 'GET') { send(405, { ok: false, error: 'GET only' }); return true; }
|
|
227
233
|
const sp = new URL(/** @type {string} */ (req.url), 'http://127.0.0.1').searchParams;
|
|
228
|
-
const asModel = sp.has('model') ? { model: sp.get('model') ?? '' } : {};
|
|
234
|
+
const asModel = sp.has('model') ? { model: sp.get('model') ?? '', provider: sp.get('provider') ?? '' } : {};
|
|
229
235
|
send(200, { ok: true, jobs: opts.startFrom.listJobs(asModel) });
|
|
230
236
|
return true;
|
|
231
237
|
}
|
|
@@ -243,7 +249,7 @@ export function createAuthorRoutes(opts) {
|
|
|
243
249
|
if (req.method !== 'POST') { send(405, { ok: false, error: 'POST only' }); return true; }
|
|
244
250
|
if (hasLiveSession()) { send(409, { ok: false, error: 'an authoring session is already live — one at a time' }); return true; }
|
|
245
251
|
const card = body ?? {};
|
|
246
|
-
const rows = rowsForHome(keysHome(opts.home));
|
|
252
|
+
const rows = rowsForHome(keysHome(opts.home), envNow());
|
|
247
253
|
// Reuse workflow — started from a green run or an imported job: only Source, Destination and the two caps may
|
|
248
254
|
// differ from the signed workflow. The SERVER refuses any other change (the page greys those boxes, but is
|
|
249
255
|
// never trusted); the spec it signs is a copy of the origin's with just those four set. Nothing is drafted.
|
|
@@ -280,7 +286,10 @@ export function createAuthorRoutes(opts) {
|
|
|
280
286
|
// A miss is a plain 400 naming the line, shown under the card exactly like every other refused start.
|
|
281
287
|
const ip = parseInputs(card.inputs);
|
|
282
288
|
if (!ip.ok) { send(400, { ok: false, error: ip.error }); return true; }
|
|
283
|
-
|
|
289
|
+
// Part 1a: a rubric job whose first input is a local FILE has no repo to prove inputs against; the session proves every
|
|
290
|
+
// line itself (`proveRubricInputs`), at $0, before any model call
|
|
291
|
+
const filesJob = reuse === null && card?.checkType === 'rubric' && looksLikeRubricInput(ip.inputs[0].value);
|
|
292
|
+
(filesJob ? Promise.resolve(/** @type {{ok: true}} */ ({ ok: true })) : proveInputFiles(ip.inputs)).then((proof) => {
|
|
284
293
|
if (!proof.ok) { send(400, { ok: false, error: proof.error }); return; }
|
|
285
294
|
if (hasLiveSession()) { send(409, { ok: false, error: 'an authoring session is already live — one at a time' }); return; }
|
|
286
295
|
const session = createSession(cardFields(card), {
|
|
@@ -388,6 +397,15 @@ export function signRun(session, claimedHash, o) {
|
|
|
388
397
|
if (e instanceof ConfigError) return { ok: false, error: `${e.message} — refusing rather than guess the monthly limit` };
|
|
389
398
|
return { ok: false, error: 'could not read the signed spec to check the monthly limit — refusing to sign' };
|
|
390
399
|
}
|
|
400
|
+
// PART 1a, the sign-vs-run invariant: the checks on the plan card the person saw are exactly the checks of the close about to be
|
|
401
|
+
// signed, per line. A mismatch is refused in plain words and nothing is spawned.
|
|
402
|
+
if (session.state.finalPlan) {
|
|
403
|
+
try {
|
|
404
|
+
const spec = JSON.parse(readFileSync(session.state.resolvedSpecPath, 'utf8'));
|
|
405
|
+
const m = planMatchesClose(session.state.finalPlan.checkItems, spec.closeDecl);
|
|
406
|
+
if (!m.ok) return { ok: false, error: m.error };
|
|
407
|
+
} catch { return { ok: false, error: 'could not read the signed spec to compare it with the plan you were shown — refusing to sign' }; }
|
|
408
|
+
}
|
|
391
409
|
const logFile = join(session.state.outDir, 'run.log');
|
|
392
410
|
// array argv, never a shell string — `--approve <hash>` is a literal
|
|
393
411
|
// element, checkable byte-for-byte by a test stubbing `spawnFn`, and there
|