bareloop 0.40.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 +33 -0
- package/bareloop.context.md +97 -124
- package/package.json +2 -2
- package/src/authorflow.js +9 -9
- package/src/authorjob.js +4 -4
- package/src/authorrun.js +25 -25
- package/src/authorscout.js +4 -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/draftspend.js +4 -4
- package/src/index.js +2 -4
- package/src/interviewrun.js +17 -25
- package/src/job.js +13 -7
- package/src/judged.js +11 -43
- package/src/keysfile.js +15 -7
- package/src/kinds.js +2 -2
- package/src/modellist.js +84 -0
- package/src/monthly.js +23 -43
- package/src/panel/authorroutes.js +19 -14
- package/src/panel/authorsession.js +62 -58
- package/src/panel/index.html +520 -145
- package/src/panel/runroutes.js +3 -0
- package/src/panel/server.js +35 -15
- package/src/panel/settingsroutes.js +232 -173
- package/src/planrun.js +2 -2
- 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/thinking.js +130 -0
- package/src/userrun.js +50 -73
- package/types/authorflow.d.ts +5 -5
- package/types/authorjob.d.ts +2 -2
- package/types/authorscout.d.ts +2 -2
- package/types/config.d.ts +23 -6
- package/types/draftspend.d.ts +8 -8
- package/types/index.d.ts +1 -1
- package/types/job.d.ts +5 -5
- 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 +2 -9
- package/types/panel/server.d.ts +14 -4
- package/types/providerrows.d.ts +192 -99
- package/types/providers.d.ts +34 -159
- package/types/providershapes.d.ts +10 -0
- package/types/thinking.d.ts +90 -0
- package/types/userrun.d.ts +6 -1
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// `src/panel/server.js`'s POST routes). Nothing here changes what a step
|
|
21
21
|
// means or what an answer does — only where the answer comes from.
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { judgePick, judgeRatesFor, judgeRowFor, keyValueOf, modelChoiceFor, ratesOfLine, rowsForHome } from '../providerrows.js';
|
|
24
24
|
import { ConfigError } from '../config.js';
|
|
25
25
|
import {
|
|
26
26
|
mkdirSync, existsSync, writeFileSync, readdirSync,
|
|
@@ -45,9 +45,10 @@ import {
|
|
|
45
45
|
questionsFor, requiredAnswersFor, makeLoopGenerate, CONFIRM_SYSTEM, SOFTGREEN_JUDGE_EXAMPLES_KEY,
|
|
46
46
|
} from '../authorflow.js';
|
|
47
47
|
import {
|
|
48
|
-
resolveProvider, buildRunnerProviders, apiKeyProblem,
|
|
48
|
+
resolveProvider, buildRunnerProviders, apiKeyProblem,
|
|
49
49
|
} from '../providers.js';
|
|
50
50
|
import { resolveJobJudge, defaultJudgeLoop } from '../judged.js';
|
|
51
|
+
import { assertThinkingMode, thinkingPlanFor, thinkingRowText } from '../thinking.js';
|
|
51
52
|
import { closeJudges } from '../kinds.js';
|
|
52
53
|
import { redactSecrets } from '../validate.js';
|
|
53
54
|
import { tallyCalls } from '../text.js';
|
|
@@ -110,19 +111,7 @@ export const PHASE_STEP = Object.freeze({
|
|
|
110
111
|
'rubric-scrubbed': 'calibrate',
|
|
111
112
|
});
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
* The first drafting row's words (hamr 2026-10-11) — code-owned, one owner, ALWAYS starting "thinking on" or "thinking off".
|
|
115
|
-
* `sent` is what the drafting calls carry (`draftThinkingFor`); `thoughtAnyway` = a call reported `thinkingApplied === false`
|
|
116
|
-
* after 'off' was sent (bare-agent saw reasoning in the reply).
|
|
117
|
-
* @param {'on'|'off'|'not-switchable'|'unverified'} sent @param {boolean} [thoughtAnyway]
|
|
118
|
-
* @returns {string}
|
|
119
|
-
*/
|
|
120
|
-
export function thinkingRowText(sent, thoughtAnyway = false) {
|
|
121
|
-
if (sent === 'off') return thoughtAnyway ? 'thinking on (the model thought anyway)' : 'thinking off';
|
|
122
|
-
if (sent === 'on') return 'thinking on';
|
|
123
|
-
if (sent === 'not-switchable') return "thinking on (this model can't turn it off)";
|
|
124
|
-
return 'thinking on (not verified for this model)';
|
|
125
|
-
}
|
|
114
|
+
export { thinkingRowText };
|
|
126
115
|
|
|
127
116
|
/** @typedef {{id: string, label: string, status: 'running'|'done'|'failed', detail: string}} StepLine */
|
|
128
117
|
|
|
@@ -194,7 +183,7 @@ export function jobNameTaken(name, opts = {}) {
|
|
|
194
183
|
}
|
|
195
184
|
|
|
196
185
|
/** the form's fields, in card order — what `card.json` stores, verbatim (P5 item 3) */
|
|
197
|
-
export const CARD_FIELDS = Object.freeze(['jobName', 'checkType', 'model', 'jobText', 'inputs', 'destination', 'capUsd', 'maxWallMs']);
|
|
186
|
+
export const CARD_FIELDS = Object.freeze(['jobName', 'checkType', 'provider', 'model', 'thinking', 'jobText', 'inputs', 'destination', 'capUsd', 'maxWallMs']);
|
|
198
187
|
|
|
199
188
|
/**
|
|
200
189
|
* The repo a card names: Inputs line 1 (P7 — the Source box is gone; the first input IS the repo). `''` when the Inputs
|
|
@@ -268,13 +257,13 @@ export function buildReuseSpec(originSpec, card, rows) {
|
|
|
268
257
|
spec.budgetUsd = card.capUsd;
|
|
269
258
|
if (typeof card.maxWallMs === 'number' && Number.isFinite(card.maxWallMs) && card.maxWallMs > 0) spec.maxWallMs = card.maxWallMs;
|
|
270
259
|
else delete spec.maxWallMs;
|
|
271
|
-
const choice = rows && typeof card.model === 'string' ? modelChoiceFor(rows, card.model) : null;
|
|
260
|
+
const choice = rows && typeof card.model === 'string' ? modelChoiceFor(rows, { provider: card.provider, model: card.model }) : null;
|
|
272
261
|
if (choice) {
|
|
273
262
|
if (spec.verdictType === 'soft-green' && !spec.judge) spec.judge = resolveJobJudge(originSpec, originSpec.provider ?? choice.provider, resolveWorkerModel);
|
|
274
|
-
|
|
263
|
+
resolveProvider(choice.provider);
|
|
275
264
|
spec.provider = choice.provider;
|
|
276
265
|
if (choice.baseUrl) spec.baseUrl = choice.baseUrl; else delete spec.baseUrl;
|
|
277
|
-
|
|
266
|
+
spec.model = choice.name; // a job always carries its own model
|
|
278
267
|
}
|
|
279
268
|
return spec;
|
|
280
269
|
}
|
|
@@ -289,7 +278,8 @@ export function buildReuseSpec(originSpec, card, rows) {
|
|
|
289
278
|
*/
|
|
290
279
|
export function validateReuseCard(card, opts = {}) {
|
|
291
280
|
if (!card || typeof card !== 'object') return { ok: false, error: 'missing job card' };
|
|
292
|
-
|
|
281
|
+
const pick = judgePick(opts.rows ?? [], { provider: card.provider, model: card.model });
|
|
282
|
+
if (!pick.ok) return { ok: false, error: pick.say };
|
|
293
283
|
const ip = parseInputs(card.inputs);
|
|
294
284
|
if (!ip.ok) return { ok: false, error: ip.error };
|
|
295
285
|
if (typeof card.destination !== 'string' || card.destination.trim() === '') return { ok: false, error: 'Destination is required' };
|
|
@@ -332,7 +322,9 @@ export function validateJobCard(card, opts = {}) {
|
|
|
332
322
|
return { ok: false, error: `a job named "${jobName}" already exists — P3 is new jobs only (Q4=A)` };
|
|
333
323
|
}
|
|
334
324
|
if (checkType !== 'deterministic' && checkType !== 'rubric') return { ok: false, error: 'Check type must be deterministic or rubric' };
|
|
335
|
-
|
|
325
|
+
const pick = judgePick(opts.rows ?? [], { provider: card.provider, model });
|
|
326
|
+
if (!pick.ok) return { ok: false, error: pick.say };
|
|
327
|
+
if (card.thinking !== undefined && typeof card.thinking !== 'boolean') return { ok: false, error: 'Thinking on must be on or off' };
|
|
336
328
|
const jb = parseJobBlock(card.jobText);
|
|
337
329
|
if (!jb.ok) return { ok: false, error: jb.error };
|
|
338
330
|
const misfit = jobBlockFitsCheck(jb.lines, checkType === 'rubric');
|
|
@@ -361,9 +353,9 @@ export function validateJobCard(card, opts = {}) {
|
|
|
361
353
|
*/
|
|
362
354
|
export function createSession(card, deps = {}) {
|
|
363
355
|
const env = deps.env ?? process.env;
|
|
364
|
-
// The drafter's THINKING
|
|
365
|
-
//
|
|
366
|
-
const draftThinking =
|
|
356
|
+
// The drafter's THINKING mode ('on' | 'off'): the card's "Thinking on" tick (unticked = 'off'), signed into the job only when ticked.
|
|
357
|
+
// `deps.draftThinking` is an operator override for tests/probes; a bad value throws here, before a session exists.
|
|
358
|
+
const draftThinking = assertThinkingMode(deps.draftThinking ?? (card.thinking === true ? 'on' : 'off'));
|
|
367
359
|
// P7: the person's own words, parsed ONCE by the one parser (src/jobblock.js). The card validator refused a malformed
|
|
368
360
|
// block before a session exists; a reuse card's job text is the origin's and is not drafted from.
|
|
369
361
|
const jobParsed = parseJobBlock(card.jobText);
|
|
@@ -557,16 +549,22 @@ export function createSession(card, deps = {}) {
|
|
|
557
549
|
// the call's label, the drafting model, its cost (null stays null: unpriced is never $0). One owner: this onCall.
|
|
558
550
|
const runningNo = state.steps.findLastIndex((/** @type {any} */ x) => x.status === 'running');
|
|
559
551
|
const callNo = runningNo === -1 ? state.steps.length - 1 : runningNo;
|
|
560
|
-
// bare-agent's own verdict on the call's run
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
552
|
+
// bare-agent's own verdict on the call's run — `thinkingStatus`: 'applied' | 'unsupported' | 'ignored' | 'unknown' — present only
|
|
553
|
+
// when thinkingMode was sent. The first drafting row then says what REALLY happened ("the model thought anyway", "can't change it",
|
|
554
|
+
// "not confirmed"); a row that moved to a worse word never moves back.
|
|
555
|
+
if (draftThinkingPlan !== null && call.thinkingStatus !== undefined && call.thinkingStatus !== thinkingStatusSeen
|
|
556
|
+
&& !(thinkingStatusSeen !== null && thinkingStatusSeen !== 'applied' && call.thinkingStatus === 'applied')) {
|
|
557
|
+
thinkingStatusSeen = call.thinkingStatus;
|
|
564
558
|
const row = latestStep(state.steps, 'thinking');
|
|
565
|
-
if (row) {
|
|
559
|
+
if (row) {
|
|
560
|
+
row.label = thinkingRowText({ mode: draftThinkingPlan.mode, support: draftThinkingPlan.support, status: call.thinkingStatus });
|
|
561
|
+
appendDraftLog(outDir, { kind: 'step-label', no: state.steps.indexOf(row), label: row.label, at: new Date().toISOString() });
|
|
562
|
+
}
|
|
566
563
|
}
|
|
567
564
|
appendDraftLog(outDir, {
|
|
568
565
|
kind: 'call', no: callNo, step: state.steps[callNo]?.id ?? null, label: call.label, model: draftIdentity?.model ?? null,
|
|
569
|
-
thinking:
|
|
566
|
+
thinking: draftThinkingPlan === null ? null : draftThinkingPlan.mode, thinkingSupport: draftThinkingPlan === null ? null : draftThinkingPlan.support,
|
|
567
|
+
...(call.thinkingStatus === undefined ? {} : { thinkingStatus: call.thinkingStatus }),
|
|
570
568
|
costUsd: call.costUsd ?? null, unpricedRounds: call.unpricedRounds ?? 0, at: nowIso,
|
|
571
569
|
});
|
|
572
570
|
try {
|
|
@@ -578,10 +576,11 @@ export function createSession(card, deps = {}) {
|
|
|
578
576
|
};
|
|
579
577
|
/** @type {string|null} */
|
|
580
578
|
let draftStartedAt = null;
|
|
581
|
-
/** what the drafting calls
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
579
|
+
/** what the drafting calls will carry for thinking — the tick's mode and bare-agent's support answer — set once the Model resolves; null before
|
|
580
|
+
* @type {ReturnType<typeof thinkingPlanFor>|null} */
|
|
581
|
+
let draftThinkingPlan = null;
|
|
582
|
+
/** the worst-so-far `thinkingStatus` a drafting call reported (null until one did) @type {string|null} */
|
|
583
|
+
let thinkingStatusSeen = null;
|
|
585
584
|
/** P6 item 1: the worktree this session made in the person's repo (null until made, and again once removed) @type {{repo: string, dir: string}|null} */
|
|
586
585
|
let madeWorktree = null;
|
|
587
586
|
/** a session that ends WITHOUT ever running (refused, errored, abandoned) takes its worktree with it; a signed one keeps it for the run */
|
|
@@ -607,36 +606,37 @@ export function createSession(card, deps = {}) {
|
|
|
607
606
|
|
|
608
607
|
async function run() {
|
|
609
608
|
// ── keys, at $0, before anything is prepared ────────────────────────
|
|
610
|
-
const keyRows = rowsForHome(configHome);
|
|
611
|
-
const
|
|
609
|
+
const keyRows = rowsForHome(configHome, env);
|
|
610
|
+
const pick = judgePick(keyRows, { provider: card.provider, model: card.model });
|
|
611
|
+
const modelChoice = pick.ok ? pick.choice : null;
|
|
612
612
|
// hamr 2026-10-11: the FIRST drafting row says whether the drafter thinks, before any other line ("checking setup" follows).
|
|
613
613
|
// A Reuse session drafts nothing, so it has no row. A provider shape that cannot be built is refused below, in words.
|
|
614
614
|
if (modelChoice && deps.reuse === undefined) {
|
|
615
615
|
try {
|
|
616
|
-
|
|
617
|
-
state.steps.push({ id: 'thinking', label: thinkingRowText(
|
|
616
|
+
draftThinkingPlan = thinkingPlanFor({ providerName: modelChoice.provider, model: modelChoice.name, baseUrl: modelChoice.baseUrl ?? null }, draftThinking);
|
|
617
|
+
state.steps.push({ id: 'thinking', label: thinkingRowText({ mode: draftThinkingPlan.mode, support: draftThinkingPlan.support }), status: 'done', detail: '' });
|
|
618
618
|
logSteps();
|
|
619
|
-
} catch {
|
|
619
|
+
} catch { draftThinkingPlan = null; }
|
|
620
620
|
}
|
|
621
621
|
stepStart('setup');
|
|
622
|
-
if (!modelChoice) { refuse(
|
|
622
|
+
if (!modelChoice) { refuse(pick.ok ? 'Pick a model. Nothing was spent.' : pick.say); return; }
|
|
623
623
|
draftIdentity = { provider: modelChoice.provider, baseUrl: modelChoice.baseUrl ?? null, model: modelChoice.name };
|
|
624
624
|
let providerEntry;
|
|
625
625
|
try { providerEntry = resolveProvider(modelChoice.provider); } catch (e) { refuse(/** @type {Error} */ (e).message); return; }
|
|
626
|
-
//
|
|
626
|
+
// the key is the chosen row's own variable (P8: no built-in name)
|
|
627
627
|
const keyCfg = keyRows;
|
|
628
|
-
const
|
|
629
|
-
const
|
|
630
|
-
const apiKey = draftEnv[providerEntry.envKey];
|
|
628
|
+
const draftKeyName = modelChoice.envName;
|
|
629
|
+
const apiKey = keyValueOf(env, draftKeyName);
|
|
631
630
|
if (!apiKey) { refuse(`${draftKeyName} not set. Stopped — nothing spent.`); return; }
|
|
632
631
|
// the customer's own price on this row (USD per 1M in config.json), resolved once here at $0 and
|
|
633
632
|
// handed to every model call this session makes; a bad price refuses, never falls back to the guess
|
|
634
|
-
/** @type {
|
|
633
|
+
/** @type {import('../providerrows.js').PriceResult} */
|
|
635
634
|
let draftPrice = null;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
635
|
+
{
|
|
636
|
+
const chosenRow = keyCfg.find((r) => r.envName === modelChoice.envName);
|
|
637
|
+
const dl = chosenRow?.models.find((l) => l.model === modelChoice.name);
|
|
638
|
+
const dp = dl ? ratesOfLine(dl) : null;
|
|
639
|
+
draftPrice = dp ? { ...dp, envName: modelChoice.envName } : null;
|
|
640
640
|
}
|
|
641
641
|
const keyProblem = apiKeyProblem(apiKey);
|
|
642
642
|
if (keyProblem) { refuse(`${draftKeyName} ${keyProblem}. Stopped — nothing spent.`); return; }
|
|
@@ -791,9 +791,10 @@ export function createSession(card, deps = {}) {
|
|
|
791
791
|
const judges = closeJudges(spec.closeDecl);
|
|
792
792
|
const judge = judges ? resolveJobJudge(spec, modelChoice.provider, resolveWorkerModel) : null;
|
|
793
793
|
let judgeProvider = null;
|
|
794
|
-
/** @type {
|
|
794
|
+
/** @type {import('../providerrows.js').PriceResult} */
|
|
795
795
|
let judgePrice = null;
|
|
796
796
|
if (judge) {
|
|
797
|
+
const judgeRow = judgeRowFor(keyCfg, judge, modelChoice.provider, keyCfg.find((r) => r.envName === modelChoice.envName) ?? null);
|
|
797
798
|
try {
|
|
798
799
|
judgePrice = judgeRatesFor(judge, modelChoice.provider, modelChoice.baseUrl, draftPrice, keyCfg);
|
|
799
800
|
} catch (e) {
|
|
@@ -801,22 +802,23 @@ export function createSession(card, deps = {}) {
|
|
|
801
802
|
refuse(`${e.message} — refusing before gate 4 spends anything`);
|
|
802
803
|
return;
|
|
803
804
|
}
|
|
804
|
-
const judgeKeyValue = env.JUDGE_API_KEY ?? (judge.provider === modelChoice.provider ?
|
|
805
|
+
const judgeKeyValue = env.JUDGE_API_KEY ?? (judge.provider === modelChoice.provider ? apiKey : keyValueOf(env, judgeRow?.envName));
|
|
805
806
|
const judgeKeyProblem = judgeKeyValue ? apiKeyProblem(judgeKeyValue) : null;
|
|
806
807
|
if (!judgeKeyValue || judgeKeyProblem) {
|
|
807
808
|
refuse(`the judge key is not usable (${judgeKeyProblem ?? 'not set'}) — refusing before gate 4 spends anything`);
|
|
808
809
|
return;
|
|
809
810
|
}
|
|
810
811
|
judgeProvider = buildRunnerProviders({
|
|
811
|
-
providerName: modelChoice.provider, apiKey, model: MODEL,
|
|
812
|
+
providerName: modelChoice.provider, apiKey, model: MODEL, baseUrl: modelChoice.baseUrl, legacyMaxTokens: modelChoice.legacyMaxTokens,
|
|
812
813
|
judgeApiKey: judgeKeyValue, judgeModel: judge.model, judgeProviderName: judge.provider,
|
|
813
|
-
judgeBaseUrl: judge.provider === modelChoice.provider ? modelChoice.baseUrl : undefined,
|
|
814
|
+
judgeBaseUrl: judge.provider === modelChoice.provider ? modelChoice.baseUrl : (judgeRow?.baseUrl.trim() || undefined),
|
|
815
|
+
judgeLegacyMaxTokens: judge.provider === modelChoice.provider ? modelChoice.legacyMaxTokens : (judgeRow?.legacyMaxTokens ?? false),
|
|
814
816
|
}).judgeProvider;
|
|
815
817
|
}
|
|
816
818
|
const signing = await prepareSigningFn({
|
|
817
819
|
spec, workdir: prep.tree, seedRef: seedRef, timeoutMs,
|
|
818
820
|
shellCapUsd: spec.budgetUsd, ceilingUsd: card.capUsd, priorCalls: [...metered],
|
|
819
|
-
judgeLoop: judgeProvider ? (o) => defaultJudgeLoop({ provider: judgeProvider, system: o.system, rates: judgePrice?.rates ?? null }) : null,
|
|
821
|
+
judgeLoop: judgeProvider ? (o) => defaultJudgeLoop({ provider: judgeProvider, system: o.system, rates: judgePrice?.rates ?? null, thinking: spec.thinking === true }) : null,
|
|
820
822
|
judgeModel: judge?.model ?? null,
|
|
821
823
|
onJudgeCost: (c) => onCall({ label: `${c.label}:${c.id}`, costUsd: c.costUsd, unpricedRounds: c.unpricedRounds }),
|
|
822
824
|
});
|
|
@@ -886,8 +888,10 @@ export function createSession(card, deps = {}) {
|
|
|
886
888
|
inputs: INPUTS,
|
|
887
889
|
provider: modelChoice.provider,
|
|
888
890
|
...(modelChoice.baseUrl ? { baseUrl: modelChoice.baseUrl } : {}),
|
|
889
|
-
//
|
|
890
|
-
|
|
891
|
+
// a job always carries its own model (P8: there is no provider default to omit it against)
|
|
892
|
+
model: modelChoice.name,
|
|
893
|
+
// the "Thinking on" tick is signed ONLY when ticked (unticked writes no field: the hash stays what it would have been)
|
|
894
|
+
...(card.thinking === true && draftThinkingPlan?.support !== false ? { thinking: true } : {}),
|
|
891
895
|
cadence: { unit: 'day', every: 1 },
|
|
892
896
|
budgetUsd: card.capUsd,
|
|
893
897
|
...(typeof card.maxWallMs === 'number' ? { maxWallMs: card.maxWallMs } : {}),
|
|
@@ -911,10 +915,10 @@ export function createSession(card, deps = {}) {
|
|
|
911
915
|
}
|
|
912
916
|
|
|
913
917
|
const draftJudge = (() => {
|
|
914
|
-
try { return resolveJobJudge(draft, modelChoice.provider, resolveWorkerModel); } catch { return { provider: modelChoice.provider, model:
|
|
918
|
+
try { return resolveJobJudge(draft, modelChoice.provider, resolveWorkerModel); } catch { return { provider: modelChoice.provider, model: modelChoice.name }; }
|
|
915
919
|
})();
|
|
916
920
|
const { provider } = buildRunnerProviders({
|
|
917
|
-
providerName: modelChoice.provider, apiKey, model: MODEL,
|
|
921
|
+
providerName: modelChoice.provider, apiKey, model: MODEL, baseUrl: modelChoice.baseUrl, legacyMaxTokens: modelChoice.legacyMaxTokens,
|
|
918
922
|
judgeApiKey: apiKey, judgeModel: MODEL, judgeProviderName: modelChoice.provider, judgeBaseUrl: modelChoice.baseUrl,
|
|
919
923
|
});
|
|
920
924
|
// TEST SEAMS (see the constructor's own note): a test overrides
|