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/authoring.js
CHANGED
|
@@ -1199,7 +1199,10 @@ function readPath(p, dotted) {
|
|
|
1199
1199
|
* @param {{catalogue?: Record<string, KindSpec>, listing?: string[]|null,
|
|
1200
1200
|
* guards?: {name: string, kind: string, params: Record<string, any>, fill: string[]}[]|null,
|
|
1201
1201
|
* envOwned?: string[]|null, envInjected?: Record<string, string>|null, deferListing?: boolean,
|
|
1202
|
-
* verdictType?: string|null, writeScope?: string[]|null}} [opts]
|
|
1202
|
+
* verdictType?: string|null, writeScope?: string[]|null, jobLines?: {n: number}[]|null}} [opts]
|
|
1203
|
+
* `jobLines` — the person's numbered job lines (P7). Present, every stage must name the line it serves and every
|
|
1204
|
+
* line needs a stage or a refusal ({@link checkJobLines}); absent (every spec that predates the job block), the
|
|
1205
|
+
* rule is not run.
|
|
1203
1206
|
* `writeScope` — the job's signed write fence. Every path a judged stage judges must sit inside it
|
|
1204
1207
|
* ({@link judgedOutsideFence}); absent, the rule is not run (the plain-folder deferral has no fence).
|
|
1205
1208
|
* `listing` — repo-relative paths at the seed (`git ls-tree -r --name-only`).
|
|
@@ -1218,7 +1221,7 @@ function readPath(p, dotted) {
|
|
|
1218
1221
|
export function validateDeclaration(declaration, opts = {}) {
|
|
1219
1222
|
const {
|
|
1220
1223
|
catalogue = KIND_CATALOGUE, listing = null, guards = null, envOwned = null, envInjected = null,
|
|
1221
|
-
verdictType = null, writeScope = null,
|
|
1224
|
+
verdictType = null, writeScope = null, jobLines = null,
|
|
1222
1225
|
} = opts;
|
|
1223
1226
|
const deferListing = opts.deferListing === true;
|
|
1224
1227
|
/** @type {Red[]} */
|
|
@@ -1443,11 +1446,71 @@ export function validateDeclaration(declaration, opts = {}) {
|
|
|
1443
1446
|
});
|
|
1444
1447
|
|
|
1445
1448
|
if (haveGuards) checkGuards({ declaration, guards: /** @type {any[]} */ (guards), red });
|
|
1449
|
+
checkJobLines({ declaration, stages, jobLines, guards: haveGuards ? /** @type {any[]} */ (guards) : [], red });
|
|
1446
1450
|
|
|
1447
1451
|
const ok = reds.length === 0;
|
|
1448
1452
|
return { ok, reds, declaration: ok ? normalizeDeclaration(declaration) : null, grounded: haveListing, scoped, ceiling };
|
|
1449
1453
|
}
|
|
1450
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* P7 rules 1-3 of the job block, at $0, in the gate that can still revise: every `fromLine` is a non-empty, duplicate-free array of real job line numbers (one check may serve several lines);
|
|
1457
|
+
* every numbered job line has at least one stage OR a refusal reason in `refused` (a note in `notes` does NOT cover a
|
|
1458
|
+
* line); the mandatory guards serve no line and carry no `fromLine`. The SHAPE of `fromLine` / `refused` is checked
|
|
1459
|
+
* whenever they appear; the coverage half runs only when the person's `jobLines` were handed in.
|
|
1460
|
+
* @param {{declaration: any, stages: any[], jobLines: {n: number}[]|null|undefined,
|
|
1461
|
+
* guards: {name: string}[], red: (c: string, p: string, d: string, e?: object) => void}} o
|
|
1462
|
+
*/
|
|
1463
|
+
function checkJobLines({ declaration, stages, jobLines, guards, red }) {
|
|
1464
|
+
const known = Array.isArray(jobLines) && jobLines.length > 0 ? jobLines.map((l) => l.n) : null;
|
|
1465
|
+
const guardNames = new Set(guards.map((g) => g.name));
|
|
1466
|
+
/** @type {Set<number>} */
|
|
1467
|
+
const served = new Set();
|
|
1468
|
+
stages.forEach((/** @type {any} */ s, /** @type {number} */ i) => {
|
|
1469
|
+
if (!isObj(s)) return;
|
|
1470
|
+
const at = `stages[${i}].fromLine`;
|
|
1471
|
+
const label = isNonEmptyString(s.name) ? s.name : `stages[${i}]`;
|
|
1472
|
+
const guard = isNonEmptyString(s.name) && guardNames.has(s.name);
|
|
1473
|
+
if (Object.hasOwn(s, 'fromLine')) {
|
|
1474
|
+
if (guard) {
|
|
1475
|
+
red('invalid-value', at, `stage "${label}" is a mandatory guard and serves no job line — remove its fromLine`);
|
|
1476
|
+
} else if (!Array.isArray(s.fromLine) || s.fromLine.length === 0 || !s.fromLine.every((/** @type {any} */ x) => Number.isInteger(x) && x >= 1)) {
|
|
1477
|
+
red('invalid-value', at, `stage "${label}" has fromLine ${JSON.stringify(s.fromLine)} — a non-empty array of job line numbers, e.g. [1] or [1, 3]`);
|
|
1478
|
+
} else if (new Set(s.fromLine).size !== s.fromLine.length) {
|
|
1479
|
+
red('invalid-value', at, `stage "${label}" lists a job line twice in fromLine ${JSON.stringify(s.fromLine)}`);
|
|
1480
|
+
} else if (known !== null && s.fromLine.some((/** @type {number} */ x) => !known.includes(x))) {
|
|
1481
|
+
red('invalid-value', at, `stage "${label}" has fromLine ${JSON.stringify(s.fromLine)}, but the job has lines ${known.join(', ')} only`);
|
|
1482
|
+
} else for (const x of s.fromLine) served.add(x);
|
|
1483
|
+
} else if (known !== null && !guard) {
|
|
1484
|
+
red('missing-field', at, `stage "${label}" does not say which job lines it serves — give it fromLine: [N, ...] (${known.join(', ')}); `
|
|
1485
|
+
+ 'only a mandatory guard carries none');
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
/** @type {Set<number>} */
|
|
1489
|
+
const refused = new Set();
|
|
1490
|
+
if (declaration.refused !== undefined) {
|
|
1491
|
+
if (!Array.isArray(declaration.refused)) {
|
|
1492
|
+
red('invalid-value', 'refused', 'an array of {line, reason} — the job lines no stage can check, each with a reason');
|
|
1493
|
+
} else {
|
|
1494
|
+
declaration.refused.forEach((/** @type {any} */ r, /** @type {number} */ j) => {
|
|
1495
|
+
const at = `refused[${j}]`;
|
|
1496
|
+
if (!isObj(r) || !Number.isInteger(r.line) || r.line < 1 || !isNonEmptyString(r.reason)) {
|
|
1497
|
+
red('invalid-value', at, '{line, reason}: a job line number and a plain-words reason');
|
|
1498
|
+
} else if (known !== null && !known.includes(r.line)) {
|
|
1499
|
+
red('invalid-value', `${at}.line`, `refused line ${r.line}, but the job has lines ${known.join(', ')} only`);
|
|
1500
|
+
} else refused.add(r.line);
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (known !== null) {
|
|
1505
|
+
for (const n of known) {
|
|
1506
|
+
if (!served.has(n) && !refused.has(n)) {
|
|
1507
|
+
red('job-line-uncovered', 'stages', `job line ${n} has no stage and no refusal. Give it a stage whose fromLine lists ${n}, or list `
|
|
1508
|
+
+ `it in "refused" as {"line": ${n}, "reason": "..."} — a note does not cover a line`, { line: n });
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1451
1514
|
/**
|
|
1452
1515
|
* COMPLETENESS — the deletion half of the owned-env rule, and the half a
|
|
1453
1516
|
* by-VALUE comparison structurally cannot cover.
|
package/src/authorjob.js
CHANGED
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
QUESTION_SETS, questionsFor, requiredAnswersFor, authorClose, makeCostBook,
|
|
114
114
|
runConfirmTurn, LANGUAGE_PICK_FIELD,
|
|
115
115
|
} from './authorflow.js';
|
|
116
|
+
import { thinkingCallOptions, assertThinkingMode } from './thinking.js';
|
|
116
117
|
import { runAuthorScout, buildSeedListing, SCOUT_ATTEMPTS } from './authorscout.js';
|
|
117
118
|
import {
|
|
118
119
|
DECLARED_GAP_PREFIX, DECLARED_GENRES, guardNames, isDeclaredClose, validateCloseDecl,
|
|
@@ -416,16 +417,19 @@ function composerRefusal(reds) {
|
|
|
416
417
|
* one-genre-at-a-time law is untouched.
|
|
417
418
|
*
|
|
418
419
|
* @param {{answers: Record<string|number, any>, repoPath?: string|null, lang: string,
|
|
420
|
+
* jobLines?: {n: number, text: string}[]|null, inputs?: {n: number, label: string, value: string}[]|null,
|
|
419
421
|
* verdictType?: string|null,
|
|
420
422
|
* questions?: Record<string|number, string>|null, generate?: Function, provider?: any,
|
|
421
423
|
* seedRef?: string|null, scout?: any, listing?: any, ceilingUsd?: number|null, judgeModel?: string|null,
|
|
422
|
-
* rates?: {in: number, out: number}|null,
|
|
424
|
+
* rates?: {in: number, out: number}|null, draftThinking?: 'on'|'off',
|
|
423
425
|
* onPhase?: (phase: string, data?: any) => void,
|
|
424
|
-
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
|
|
426
|
+
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string}) => void,
|
|
425
427
|
* seedFn?: Function, scoutFn?: Function, listingFn?: Function,
|
|
426
428
|
* authorFn?: Function, authorOpts?: object, writeScope?: string[]|null,
|
|
427
429
|
* signerFix?: Function|any, proposeFn?: Function, compileOpts?: object,
|
|
428
430
|
* ask?: ((step: {kind: string, [k: string]: any}) => Promise<string|null>)|null,
|
|
431
|
+
* answerQuestions?: ((o: {questions: {line: number, question: string}[], dropped: {reason: string, raw: any}[], confirmed: any}) =>
|
|
432
|
+
* Promise<{jobLines: {n: number, text: string, rule?: string}[], answers: Record<string|number, any>}|null>)|null,
|
|
429
433
|
* confirmGenerate?: Function|null, isRepo?: boolean,
|
|
430
434
|
* langResult?: {kind: string, candidates?: string[], [k: string]: any}|null}} o
|
|
431
435
|
* @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
|
|
@@ -434,9 +438,15 @@ function composerRefusal(reds) {
|
|
|
434
438
|
*/
|
|
435
439
|
export async function authorCloseForJob({
|
|
436
440
|
answers, repoPath = null, lang, verdictType = null, questions = null,
|
|
441
|
+
// P7: the person's numbered job lines and numbered inputs (src/jobblock.js). Absent = every caller that predates the
|
|
442
|
+
// job block (no line tagging, no coverage rule).
|
|
443
|
+
jobLines = null, inputs = null,
|
|
437
444
|
generate, provider = null, seedRef = null, scout = null, listing = null,
|
|
438
445
|
ceilingUsd = null,
|
|
439
446
|
rates = null,
|
|
447
|
+
// The drafter's THINKING knob ('on' | 'off', default 'off'; an operator param, never read from env here). It reaches the scout through
|
|
448
|
+
// `callOptions`; the `generate`/`confirmGenerate` the caller passes must be built with the SAME mode (`makeLoopGenerate`).
|
|
449
|
+
draftThinking = 'off',
|
|
440
450
|
// Q2 IS GONE from the numbered interview (PRD item 33 M3 piece 3) — the
|
|
441
451
|
// fence it used to describe in prose is now Destination's own proven
|
|
442
452
|
// `writeScope`, and this is what carries it into the composer prompt
|
|
@@ -479,7 +489,14 @@ export async function authorCloseForJob({
|
|
|
479
489
|
// shape matters here (a $0 language pick before the scout,
|
|
480
490
|
// D7); every other kind leaves `lang` exactly as given.
|
|
481
491
|
ask = null, confirmGenerate = null, isRepo = false, langResult = null,
|
|
492
|
+
// P7 item 5 (fwdloop am24/25): the seam that can really ASK the person. Present = the last drafting try, and only after
|
|
493
|
+
// the two before it both failed, may ask (`offerQuestions` below); the seam takes the machine-filtered questions, gets
|
|
494
|
+
// each answer (required, no skip), and hands back the job lines with the answers added as `~` rules plus the matching
|
|
495
|
+
// answers text. ABSENT (the CLI, every older caller) = nothing is ever offered and this file is byte-identical.
|
|
496
|
+
answerQuestions = null,
|
|
482
497
|
}) {
|
|
498
|
+
// an operator-param guard: a typo never silently means "default" (throws before any work, even when the survey is supplied)
|
|
499
|
+
assertThinkingMode(draftThinking);
|
|
483
500
|
/** @type {any} */
|
|
484
501
|
const base = {
|
|
485
502
|
ok: false, refusal: null, verdictType: null, closeDecl: null, seedRef: null,
|
|
@@ -580,7 +597,8 @@ export async function authorCloseForJob({
|
|
|
580
597
|
// real tree. `attempts` is the ceiling the scout enforces on itself, so the
|
|
581
598
|
// reader knows how many the line could repeat for.
|
|
582
599
|
onPhase('scout', { attempts: SCOUT_ATTEMPTS });
|
|
583
|
-
|
|
600
|
+
const scoutCallOptions = thinkingCallOptions(provider, draftThinking);
|
|
601
|
+
survey = await scoutFn({ workdir, provider, ceilingUsd, onCall, ...(rates ? { rates } : {}), ...(Object.keys(scoutCallOptions).length ? { callOptions: scoutCallOptions } : {}) });
|
|
584
602
|
// …and the survey's own VERDICT, said the moment it lands. `state` is what
|
|
585
603
|
// every downstream refusal keys on (F59: absent is never "no facts needed"),
|
|
586
604
|
// and the fact count is the one number that says whether the ladder came
|
|
@@ -627,7 +645,7 @@ export async function authorCloseForJob({
|
|
|
627
645
|
onPhase('confirm', {});
|
|
628
646
|
const confirm = await runConfirmTurn({
|
|
629
647
|
verdictType: picked, answers: interview.answers, questions: questions ?? questionsFor(picked),
|
|
630
|
-
facts: survey.facts, listing: null, writeScope, isRepo, lang, worseThanBefore,
|
|
648
|
+
facts: survey.facts, listing: null, writeScope, isRepo, lang, worseThanBefore, inputs,
|
|
631
649
|
generate: confirmGenerate, book: confirmBook, ask, onPhase,
|
|
632
650
|
});
|
|
633
651
|
// DISTINCT from `runConfirmTurn`'s own per-round 'confirm-done' (fired once
|
|
@@ -671,13 +689,38 @@ export async function authorCloseForJob({
|
|
|
671
689
|
// loop obeys — `author-call` carries the enforced cap instead, from where it
|
|
672
690
|
// is computed.
|
|
673
691
|
onPhase('author', {});
|
|
674
|
-
|
|
692
|
+
/** the answers text the later stages read; the try-3 redraft replaces it (the person's answers are new `~` rules) @type {Record<string|number, any>} */
|
|
693
|
+
let stageAnswers = interview.answers;
|
|
694
|
+
let authored = await authorFn({
|
|
675
695
|
workdir, seedRef: seed, lang, verdictType: picked,
|
|
676
|
-
answers:
|
|
696
|
+
answers: stageAnswers, questions: questions ?? questionsFor(picked),
|
|
677
697
|
scout: survey, listing: seeds, generate, ceilingUsd, onPhase, onCall, writeScope,
|
|
678
|
-
priorCalls: confirmPriorCalls, priorRaws: confirmPriorRaws, confirmed,
|
|
698
|
+
priorCalls: confirmPriorCalls, priorRaws: confirmPriorRaws, confirmed, jobLines, inputs,
|
|
699
|
+
...(answerQuestions ? { offerQuestions: true } : {}),
|
|
679
700
|
...authorOpts,
|
|
680
701
|
});
|
|
702
|
+
// P7 item 5: the drafter, on the last try after two failed ones, asked. The person answers (required, no skip); each
|
|
703
|
+
// answer becomes a `~` rule on its job line; the draft then runs ONCE more over the same survey, listing and confirmed
|
|
704
|
+
// plan, and may not ask again. The first leg's calls are carried as prior spend so the one ceiling still binds.
|
|
705
|
+
if (!authored.ok && authored.stop === 'questions-open' && answerQuestions && Array.isArray(authored.questions) && authored.questions.length) {
|
|
706
|
+
const dropped = (authored.iterations ?? []).flatMap((/** @type {any} */ it) => it?.droppedQuestions ?? []);
|
|
707
|
+
const updated = await answerQuestions({ questions: authored.questions, dropped, confirmed });
|
|
708
|
+
if (updated === null) return { ...base, interview, authoring: authored, reds: [], stop: 'confirm-abandoned', cost: authored.cost };
|
|
709
|
+
const scoutCalls = (survey?.calls ?? []).length;
|
|
710
|
+
const scoutRaws = (survey?.raws ?? []).length;
|
|
711
|
+
jobLines = updated.jobLines;
|
|
712
|
+
stageAnswers = updated.answers;
|
|
713
|
+
onPhase('author', {});
|
|
714
|
+
authored = await authorFn({
|
|
715
|
+
workdir, seedRef: seed, lang, verdictType: picked,
|
|
716
|
+
answers: stageAnswers, questions: questions ?? questionsFor(picked),
|
|
717
|
+
scout: survey, listing: seeds, generate, ceilingUsd, onPhase, onCall, writeScope,
|
|
718
|
+
priorCalls: (authored.cost?.calls ?? []).slice(scoutCalls), priorRaws: (authored.raws ?? []).slice(scoutRaws),
|
|
719
|
+
confirmed, jobLines, inputs,
|
|
720
|
+
...authorOpts,
|
|
721
|
+
offerQuestions: false,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
681
724
|
|
|
682
725
|
if (!authored.ok) {
|
|
683
726
|
return {
|
|
@@ -724,6 +767,11 @@ export async function authorCloseForJob({
|
|
|
724
767
|
...(authored.declaration.notes?.length
|
|
725
768
|
? { notes: authored.declaration.notes.map((/** @type {any} */ n) => (typeof n === 'string' ? redactSecrets(n) : n)) }
|
|
726
769
|
: {}),
|
|
770
|
+
// P7: the job lines the drafter could not turn into a stage, each with its reason — the Job tab shows them. Free
|
|
771
|
+
// text the model wrote, so it rides the same redactor `notes` does.
|
|
772
|
+
...(authored.declaration.refused?.length
|
|
773
|
+
? { refused: authored.declaration.refused.map((/** @type {any} */ r) => ({ line: r.line, reason: redactSecrets(String(r.reason)) })) }
|
|
774
|
+
: {}),
|
|
727
775
|
};
|
|
728
776
|
|
|
729
777
|
// ── THE TWO SIGNED ARTIFACTS (softgreen module 4, wired here by module 5) ──
|
|
@@ -750,7 +798,7 @@ export async function authorCloseForJob({
|
|
|
750
798
|
book.absorb(authored.cost?.calls ?? []);
|
|
751
799
|
onPhase('rubric', { size: CALIBRATION_SIZE });
|
|
752
800
|
const proposed = await proposeFn({
|
|
753
|
-
answers:
|
|
801
|
+
answers: stageAnswers,
|
|
754
802
|
questions: questions ?? questionsFor(picked),
|
|
755
803
|
generate,
|
|
756
804
|
book,
|
|
@@ -1253,7 +1301,7 @@ export async function prepareSigning({
|
|
|
1253
1301
|
}
|
|
1254
1302
|
// the class rides from the SPEC (PRD v1.57 §2) — gate 1a already refused a spec
|
|
1255
1303
|
// whose class is unknown or locked, so this is the class D5's battery hangs off
|
|
1256
|
-
const dv = validateCloseDecl(spec.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: spec.verdictType, writeScope: spec.writeScope });
|
|
1304
|
+
const dv = validateCloseDecl(spec.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: spec.verdictType, writeScope: spec.writeScope, jobLines: Array.isArray(spec.jobLines) ? spec.jobLines : null });
|
|
1257
1305
|
// the grounded gate's reds quote BOTH untrusted sources — the declaration, and
|
|
1258
1306
|
// the seed listing itself (the listing rule names what really sits beside an
|
|
1259
1307
|
// invented path). The listing half is a channel gate 1a structurally cannot
|
package/src/authorreadout.js
CHANGED
|
@@ -53,7 +53,7 @@ export function declarationLines({ goal, closeDecl }) {
|
|
|
53
53
|
* F176'S FALLBACK, SAID OUT LOUD — the revise ladder kept the NEWEST SOUND
|
|
54
54
|
* declaration rather than the last one it happened to try, because "kept the
|
|
55
55
|
* last accepted revision" is not the same claim as "kept the best revision"
|
|
56
|
-
* (docs/
|
|
56
|
+
* (docs/wiki/FINDINGS.md F176). Absent on every run where the fallback never
|
|
57
57
|
* fired (the overwhelming majority), which is why this returns an EMPTY list
|
|
58
58
|
* rather than a "no fallback" line — a green close has nothing to say here and
|
|
59
59
|
* printing it every run would be noise, not information.
|
|
@@ -572,6 +572,54 @@ export function proposalStopText({ stop, reds, cases = null, spend = null, sourc
|
|
|
572
572
|
return lines.join('\n');
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
+
// ── ANY DRAFTING STOP, in plain words (P7 item 6, live session smv2bzdqmp0f8, 2026-10-10) ───────────────────────────
|
|
576
|
+
// A provider-red used to reach the panel as the bare line "Stopped: provider-red" — a code, no reason. Every stop of the
|
|
577
|
+
// drafting loop that is not a rubric-proposal or signing refusal (those have their own wording above and below) now says
|
|
578
|
+
// what happened, what it cost and what to do, in words. The stop's own reds are quoted (scrubbed, bounded), never dropped.
|
|
579
|
+
|
|
580
|
+
/** the reason inside a `scout-absent` red's detail: `the survey is ABSENT (<reason>) — an absent facts object ...` (src/authorflow.js) */
|
|
581
|
+
const SCOUT_REASON_RE = /^the survey is [^(]*\((.*)\) — an absent facts/s;
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* @param {{stop: string, reds?: PlainRed[]|null, refusal?: {detail?: string}|null,
|
|
585
|
+
* spend?: {knownUsd?: number|null, spendComplete?: boolean|null}|null}} o
|
|
586
|
+
* @returns {string}
|
|
587
|
+
*/
|
|
588
|
+
export function draftStopText({ stop, reds = null, refusal = null, spend = null }) {
|
|
589
|
+
const spent = spendText(spend);
|
|
590
|
+
/** @type {Record<string, string>} */
|
|
591
|
+
const heads = {
|
|
592
|
+
'provider-red': 'The AI provider failed before a usable plan came back — a connection or service fault, not a problem with your job.',
|
|
593
|
+
'artifact-red': 'The AI answered, but its answer could not be used as a plan.',
|
|
594
|
+
'cap-halt': 'Drafting stopped on your cost cap.',
|
|
595
|
+
'pricing-red': 'Drafting stopped: the cost of a call could not be priced, so the cap cannot be enforced.',
|
|
596
|
+
'max-revisions': 'The AI used all its tries without a plan that passed the checks.',
|
|
597
|
+
precheck: 'Drafting stopped before any call: something it needs was missing.',
|
|
598
|
+
unchanged: 'Drafting stopped: the AI sent the same plan twice.',
|
|
599
|
+
'scout-absent': 'Drafting stopped: the first look at your repo did not complete.',
|
|
600
|
+
};
|
|
601
|
+
const redList = Array.isArray(reds) ? reds : [];
|
|
602
|
+
// F217: the first look at the repo failing (a dropped connection, a timeout) arrives as a `precheck` stop with a
|
|
603
|
+
// `scout-absent` red. That is NOT "something it needs was missing", and its reason sits past the 60-char quote bound
|
|
604
|
+
// behind a fixed lead-in, so the person saw neither. Word it as the scout's stop and quote the reason in full (scrubbed).
|
|
605
|
+
const scoutRed = stop === 'precheck' ? redList.find((r) => r?.code === 'scout-absent') : undefined;
|
|
606
|
+
const head = scoutRed ? heads['scout-absent'] : (heads[stop] ?? `Drafting stopped (${safeText(stop)}).`);
|
|
607
|
+
/** @type {string[]} */
|
|
608
|
+
const said = scoutRed
|
|
609
|
+
? [SCOUT_REASON_RE.exec(String(scoutRed.detail ?? ''))?.[1]
|
|
610
|
+
?.replace(/\s+/g, ' ').trim().slice(0, 240) ?? safeText(scoutRed.detail)].map((x) => redactSecrets(x)).filter(Boolean)
|
|
611
|
+
: [...new Set(redList.map((r) => safeText(r?.detail)).filter(Boolean))];
|
|
612
|
+
if (said.length === 0 && refusal?.detail) said.push(safeText(refusal.detail));
|
|
613
|
+
const lines = [`${head} Spent so far: ${spent ?? 'unknown'}.`];
|
|
614
|
+
if (said.length) {
|
|
615
|
+
lines.push('What it said:');
|
|
616
|
+
for (const x of said.slice(0, PLAIN_MAX_BULLETS)) lines.push(` • ${x}`);
|
|
617
|
+
if (said.length > PLAIN_MAX_BULLETS) lines.push(` • …and ${said.length - PLAIN_MAX_BULLETS} more.`);
|
|
618
|
+
}
|
|
619
|
+
lines.push(stop === 'cap-halt' || stop === 'pricing-red' ? 'What to do: raise the cap or fix the price in Settings, then draft again.' : 'What to do: draft again.');
|
|
620
|
+
return lines.join('\n');
|
|
621
|
+
}
|
|
622
|
+
|
|
575
623
|
// ── THE SIGNING GATES' REFUSAL, in plain words (hamr, 2026-10-09) ──────────────────────────
|
|
576
624
|
//
|
|
577
625
|
// `prepareSigning` (src/authorjob.js) can refuse with a list of reds from four gates. The panel used to say
|
|
@@ -598,7 +646,7 @@ export function proposalStopText({ stop, reds, cases = null, spend = null, sourc
|
|
|
598
646
|
export const DECLARATION_CODES = Object.freeze([
|
|
599
647
|
'bounds', 'class-absent', 'class-battery-locked', 'class-ceiling', 'cmd-denied', 'duplicate-kind', 'duplicate-name',
|
|
600
648
|
'env-ownership-absent', 'genre-env-missing', 'genre-env-ungrounded', 'genre-owned-env', 'guard-missing',
|
|
601
|
-
'guard-weakened', 'guards-absent', 'human-stage-not-last', 'human-stage-offered', 'invalid-value', 'judged-outside-write-scope', 'judged-stage-order',
|
|
649
|
+
'guard-weakened', 'guards-absent', 'human-stage-not-last', 'human-stage-offered', 'invalid-value', 'job-line-uncovered', 'judged-outside-write-scope', 'judged-stage-order',
|
|
602
650
|
'listing-absent', 'listing-conflict', 'locked-kind', 'missing-field', 'missing-required', 'one-population',
|
|
603
651
|
'path-not-in-listing', 'secret-literal', 'unknown-field', 'unknown-kind',
|
|
604
652
|
]);
|
package/src/authorrun.js
CHANGED
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
// in; a draft missing one, or naming one the provider factory
|
|
102
102
|
// does not know, dies here loud, listing the known table.
|
|
103
103
|
import { keysForDoor } from './keysfile.js';
|
|
104
|
-
import {
|
|
104
|
+
import { judgeRatesFor, judgeRowFor, keyNameFor, keyValueOf, ratesFor, ratesOfLine, resolveWorker, rowsForHome } from './providerrows.js';
|
|
105
105
|
import { ConfigError } from './config.js';
|
|
106
106
|
import {
|
|
107
107
|
readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, statSync,
|
|
@@ -466,13 +466,15 @@ export async function main(argv, deps = {}) {
|
|
|
466
466
|
try { return resolveProvider(draft?.provider); } catch (e) { return die(/** @type {Error} */ (e).message); }
|
|
467
467
|
})();
|
|
468
468
|
const PROVIDER_NAME = /** @type {string} */ (draft.provider);
|
|
469
|
-
// The
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
469
|
+
// The Settings LINE this authoring run works on (P8): the draft's own `model` on a ticked, priced line of a key row on the
|
|
470
|
+
// draft's API shape (and Base URL, when it names one). The row is the key, the Base URL and the old-max_tokens tick. Nothing is
|
|
471
|
+
// built in: a draft naming no model, or a model no row ticks, stops here at $0.
|
|
472
|
+
const keyCfg = rowsForHome(keys.home, env);
|
|
473
|
+
const W = resolveWorker(keyCfg, draft);
|
|
474
|
+
if (!W.ok) { err(W.say.includes('Nothing was spent') ? W.say : `${W.say} Nothing was spent.`); throw new ExitSignal(2); }
|
|
475
|
+
const MODEL = W.model;
|
|
476
|
+
const baseUrl = W.baseUrl;
|
|
477
|
+
const AUTHOR_ROW = W.row;
|
|
476
478
|
|
|
477
479
|
// THE JUDGE THIS RUN'S OWN WORKER RESOLVES TO (PRD item 32.1) — resolved ONCE,
|
|
478
480
|
// here, from the DRAFT, and reused for BOTH the compose-time stamp (the
|
|
@@ -508,12 +510,9 @@ export async function main(argv, deps = {}) {
|
|
|
508
510
|
|
|
509
511
|
// Secrets load from the environment; they never enter argv (a command line is
|
|
510
512
|
// world-readable on /proc) and they are never printed.
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
const authorEnv = applyConfiguredKey(env, PROVIDER_NAME, baseUrl, keyCfg);
|
|
515
|
-
const AUTHOR_ENV_KEY = keyNameFor(PROVIDER_NAME, baseUrl, keyCfg).name;
|
|
516
|
-
const apiKey = authorEnv[/** @type {NonNullable<typeof providerEntry>} */ (providerEntry).envKey];
|
|
513
|
+
// The key is the chosen row's own variable (P8: no built-in name). Names only.
|
|
514
|
+
const AUTHOR_ENV_KEY = AUTHOR_ROW.envName;
|
|
515
|
+
const apiKey = keyValueOf(env, AUTHOR_ENV_KEY);
|
|
517
516
|
if (!apiKey) { err(`${AUTHOR_ENV_KEY} not set (secrets load from the environment — never the tree, never argv)`); throw new ExitSignal(2); }
|
|
518
517
|
// F181 — a key that carries a line break/control char/stray whitespace (a
|
|
519
518
|
// two-line secret-store entry, e.g.) reads as "set" by the presence check
|
|
@@ -527,17 +526,17 @@ export async function main(argv, deps = {}) {
|
|
|
527
526
|
// loop, confirm turn, calibration judge). A bad price refuses here; it never falls back to the guess.
|
|
528
527
|
/** @type {ReturnType<typeof ratesFor>} */
|
|
529
528
|
let authorPrice = null;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
throw new ExitSignal(2);
|
|
529
|
+
{
|
|
530
|
+
const al = ratesOfLine(W.line);
|
|
531
|
+
authorPrice = al ? { ...al, envName: AUTHOR_ROW.envName } : null;
|
|
534
532
|
}
|
|
535
533
|
/** The judge's key follows the RESOLVED judge provider's own env var, with
|
|
536
534
|
* `JUDGE_API_KEY` as the role-named override in front (PRD item 32.3) — the same
|
|
537
535
|
* contract `scripts/run-u.mjs` keeps, so one story covers both surfaces. When the
|
|
538
536
|
* judge IS the authoring provider this is the key already read above. */
|
|
537
|
+
const judgeRowOf = (/** @type {{provider: string, model?: string|null}} */ j) => judgeRowFor(keyCfg, j, PROVIDER_NAME, AUTHOR_ROW);
|
|
539
538
|
const judgeKeyFor = (/** @type {string} */ providerName) => (
|
|
540
|
-
env.JUDGE_API_KEY ?? (providerName === PROVIDER_NAME ?
|
|
539
|
+
env.JUDGE_API_KEY ?? (providerName === PROVIDER_NAME ? apiKey : keyValueOf(env, judgeRowFor(keyCfg, { provider: providerName }, PROVIDER_NAME, AUTHOR_ROW)?.envName))
|
|
541
540
|
);
|
|
542
541
|
|
|
543
542
|
/** F6 — an unpriced call makes the TOTAL unknown, and unknown is reported as
|
|
@@ -570,7 +569,7 @@ export async function main(argv, deps = {}) {
|
|
|
570
569
|
out(` ${ceilingLine(CEILING_USD)}`);
|
|
571
570
|
out(' stops at prepareSigning — this script NEVER signs and NEVER runs the job\n');
|
|
572
571
|
|
|
573
|
-
// F190 (docs/
|
|
572
|
+
// F190 (docs/wiki/FINDINGS.md) — this used to be a hand-rolled `makeProvider`
|
|
574
573
|
// call, a duplicate of the SAME construction `buildRunnerProviders` already
|
|
575
574
|
// owns for `src/cli.js`. Routed through the one owner now: the judge half of
|
|
576
575
|
// this call is a THROWAWAY (`judgeProviderName`/`judgeModel`/`judgeBaseUrl` all
|
|
@@ -579,7 +578,7 @@ export async function main(argv, deps = {}) {
|
|
|
579
578
|
// one) — the real judge identity is not resolved until after `validateJob`,
|
|
580
579
|
// further down, where a second call takes only its `.judgeProvider`.
|
|
581
580
|
const { provider } = buildRunnerProviders({
|
|
582
|
-
providerName: PROVIDER_NAME, apiKey, model: MODEL,
|
|
581
|
+
providerName: PROVIDER_NAME, apiKey, model: MODEL, baseUrl, legacyMaxTokens: AUTHOR_ROW.legacyMaxTokens,
|
|
583
582
|
judgeApiKey: apiKey, judgeModel: MODEL, judgeProviderName: PROVIDER_NAME, judgeBaseUrl: baseUrl,
|
|
584
583
|
});
|
|
585
584
|
emit('author-start', { runid, source: SOURCE, lang: LANG, verdictType: VERDICT, provider: PROVIDER_NAME, model: MODEL, baseUrl: baseUrl ?? null, job: draft?.job ?? null, timeoutMs: TIMEOUT_MS, ceilingUsd: CEILING_USD });
|
|
@@ -818,7 +817,7 @@ export async function main(argv, deps = {}) {
|
|
|
818
817
|
// piece 4, step S6, D5 amended 2026-09-21) ──────────────────────────────────
|
|
819
818
|
// This USED TO run the whole confirm turn first (over a $0 manual listing,
|
|
820
819
|
// via `runConfirmTurn`), THEN give this stop once the person confirmed a plan
|
|
821
|
-
// — D5's original shape. Live run `mu4hc7sp` (docs/
|
|
820
|
+
// — D5's original shape. Live run `mu4hc7sp` (docs/wiki/FINDINGS.md F191)
|
|
822
821
|
// crashed inside that confirm turn instead: `confirmProtections`
|
|
823
822
|
// (`src/authorflow.js`) calls `classGuards` (`src/authoring.js`), which is
|
|
824
823
|
// keyed by CODE LANGUAGE and THROWS on a plain folder's `lang:
|
|
@@ -879,6 +878,9 @@ export async function main(argv, deps = {}) {
|
|
|
879
878
|
// fence is not this script's business to invent one for, so an absent or
|
|
880
879
|
// malformed field travels as `null` and `authorPrompt` simply states nothing.
|
|
881
880
|
writeScope: Array.isArray(draft.writeScope) ? draft.writeScope : null,
|
|
881
|
+
// P7: the person's numbered job lines and inputs ride the draft; the drafter tags every stage with a line
|
|
882
|
+
jobLines: Array.isArray(draft.jobLines) ? draft.jobLines : null,
|
|
883
|
+
inputs: Array.isArray(draft.inputs) ? draft.inputs : null,
|
|
882
884
|
provider,
|
|
883
885
|
generate: makeLoopGenerate(provider, { rates: authorPrice?.rates ?? null }),
|
|
884
886
|
rates: authorPrice?.rates ?? null,
|
|
@@ -1112,7 +1114,7 @@ export async function main(argv, deps = {}) {
|
|
|
1112
1114
|
const judgeKeyValue = judgeKeyFor(judge.provider);
|
|
1113
1115
|
const judgeKeyProblem = judgeKeyValue ? apiKeyProblem(judgeKeyValue) : null;
|
|
1114
1116
|
if (judgeKeyProblem) {
|
|
1115
|
-
const judgeEnvName = env.JUDGE_API_KEY ? 'JUDGE_API_KEY' :
|
|
1117
|
+
const judgeEnvName = env.JUDGE_API_KEY ? 'JUDGE_API_KEY' : (judgeRowOf(judge)?.envName ?? judge.provider);
|
|
1116
1118
|
err(`${judgeEnvName} ${judgeKeyProblem} — refusing rather than crashing mid-call (never trimmed or repaired; fix the value at its source)`);
|
|
1117
1119
|
// F186 — the scout above already ran and may have left its own gate
|
|
1118
1120
|
// audit in the tree. Under `process.exit()` this path used to skip
|
|
@@ -1146,9 +1148,10 @@ export async function main(argv, deps = {}) {
|
|
|
1146
1148
|
let judgeProvider = null;
|
|
1147
1149
|
if (judge) {
|
|
1148
1150
|
judgeProvider = buildRunnerProviders({
|
|
1149
|
-
providerName: PROVIDER_NAME, apiKey, model: MODEL,
|
|
1151
|
+
providerName: PROVIDER_NAME, apiKey, model: MODEL, baseUrl, legacyMaxTokens: AUTHOR_ROW.legacyMaxTokens,
|
|
1150
1152
|
judgeApiKey: judgeKeyFor(judge.provider), judgeModel: judge.model, judgeProviderName: judge.provider,
|
|
1151
|
-
judgeBaseUrl: judge.provider === PROVIDER_NAME ? baseUrl : undefined,
|
|
1153
|
+
judgeBaseUrl: judge.provider === PROVIDER_NAME ? baseUrl : (judgeRowOf(judge)?.baseUrl.trim() || undefined),
|
|
1154
|
+
judgeLegacyMaxTokens: judge.provider === PROVIDER_NAME ? AUTHOR_ROW.legacyMaxTokens : (judgeRowOf(judge)?.legacyMaxTokens ?? false),
|
|
1152
1155
|
}).judgeProvider;
|
|
1153
1156
|
}
|
|
1154
1157
|
if (judge) {
|
|
@@ -1167,7 +1170,7 @@ export async function main(argv, deps = {}) {
|
|
|
1167
1170
|
// number would silently widen it.
|
|
1168
1171
|
ceilingUsd: CEILING_USD,
|
|
1169
1172
|
priorCalls: [...metered],
|
|
1170
|
-
judgeLoop: judgeProvider ? (o) => defaultJudgeLoop({ provider: judgeProvider, system: o.system, rates: judgePrice?.rates ?? null }) : null,
|
|
1173
|
+
judgeLoop: judgeProvider ? (o) => defaultJudgeLoop({ provider: judgeProvider, system: o.system, rates: judgePrice?.rates ?? null, thinking: spec.thinking === true }) : null,
|
|
1171
1174
|
// the seam's other half (PRD item 32.1): the graded set is STAMPED with
|
|
1172
1175
|
// this, and the close later refuses to grade under any other identity.
|
|
1173
1176
|
judgeModel: judge?.model ?? null,
|
|
@@ -1294,7 +1297,7 @@ export async function main(argv, deps = {}) {
|
|
|
1294
1297
|
+ 'and run-u will enforce the remainder (Cap $ minus this) as ITS ceiling — pass --draft-spent-usd exactly as shown below, never a rounded or re-typed figure.');
|
|
1295
1298
|
}
|
|
1296
1299
|
out('\nTo run it (the same signature and gates as any other job — nothing here bypasses them):');
|
|
1297
|
-
out(` ${
|
|
1300
|
+
out(` ${AUTHOR_ENV_KEY}=... ${commandFor('run-u', deps.invokedAs)} --spec ${specFile} --approve ${hash}${draftSpentUsd > 0 ? ` --draft-spent-usd ${draftSpentUsd}${draftIncomplete ? ' --draft-spend-incomplete' : ''}` : ''}`);
|
|
1298
1301
|
emit('author-end', { outcome: 'prepared', specHash: hash });
|
|
1299
1302
|
}
|
|
1300
1303
|
}
|
package/src/authorscout.js
CHANGED
|
@@ -375,9 +375,9 @@ const defaultLoop = (/** @type {any} */ { system, policy, onLlmResult, provider,
|
|
|
375
375
|
* @param {{workdir: string, provider?: any,
|
|
376
376
|
* rounds?: number, minBytes?: number, blobMax?: number, maxTokens?: number, ctx?: boolean,
|
|
377
377
|
* attempts?: number, ceilingUsd?: number|null, callTimeoutMs?: number,
|
|
378
|
-
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
|
|
378
|
+
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string}) => void,
|
|
379
379
|
* createLoop?: (o: {system: string, policy: any, onLlmResult: any, provider: any, rates?: {in: number, out: number}}) => any,
|
|
380
|
-
* rates?: {in: number, out: number}|null,
|
|
380
|
+
* rates?: {in: number, out: number}|null, callOptions?: Record<string, any>,
|
|
381
381
|
* createSurveyor?: (o: {workdir: string, granted: readonly string[], ctx: boolean}) => Promise<any>}} o
|
|
382
382
|
* @returns {Promise<Survey & {raw: string, raws: ReturnType<typeof scrubRaw>[],
|
|
383
383
|
* calls: {label: string, costUsd: number|null, unpricedRounds: number}[],
|
|
@@ -400,6 +400,8 @@ export async function runAuthorScout({
|
|
|
400
400
|
onCall = () => {},
|
|
401
401
|
createLoop = defaultLoop,
|
|
402
402
|
rates = null,
|
|
403
|
+
// the drafter's per-call model options (`thinkingCallOptions`, src/thinking.js; e.g. thinking off) — {} = nothing extra, byte-identical
|
|
404
|
+
callOptions = {},
|
|
403
405
|
createSurveyor = defaultSurveyor,
|
|
404
406
|
}) {
|
|
405
407
|
const callTimeout = Number.isFinite(Number(callTimeoutMs)) && Number(callTimeoutMs) > 0
|
|
@@ -434,7 +436,8 @@ export async function runAuthorScout({
|
|
|
434
436
|
// metered without the report going out: exactly the pairing invariant the
|
|
435
437
|
// raw already rides. It REPORTS and decides nothing; the ceiling still binds
|
|
436
438
|
// through `capStop` between calls, untouched.
|
|
437
|
-
|
|
439
|
+
// bare-agent's per-run `thinkingStatus` rides the reporting seam only, never the stored entry (see makeCostBook.add)
|
|
440
|
+
onCall({ ...call, ...(r?.thinkingStatus === undefined ? {} : { thinkingStatus: r.thinkingStatus }) });
|
|
438
441
|
return raws.push(scrubRaw({ label, attempt, text: r?.text })) - 1;
|
|
439
442
|
};
|
|
440
443
|
// TIGHTEN-ONLY: an operator may lower the ceiling, never raise it. A garbage
|
|
@@ -526,7 +529,7 @@ export async function runAuthorScout({
|
|
|
526
529
|
loop = createLoop({ system, policy: surveyor.policy, onLlmResult: metered, provider, ...priced });
|
|
527
530
|
|
|
528
531
|
const r = await settled(loop.run([{ role: 'user', content: scoutPrompt(workdir) }], surveyor.tools,
|
|
529
|
-
{ cacheMessages: true, maxTokens, timeoutMs: callTimeout }));
|
|
532
|
+
{ cacheMessages: true, maxTokens, timeoutMs: callTimeout, ...callOptions }));
|
|
530
533
|
verdictAt = record(label, attempt, r);
|
|
531
534
|
blob = redactSecrets(String(r?.text ?? '')).slice(0, blobMax);
|
|
532
535
|
lastMsgs = Array.isArray(r?.msgs) && r.msgs.length ? r.msgs : null;
|
|
@@ -572,7 +575,7 @@ export async function runAuthorScout({
|
|
|
572
575
|
// backends 400 on it. Strip the leading one before re-running.
|
|
573
576
|
const rMsgs = r.msgs[0]?.role === 'system' ? r.msgs.slice(1) : r.msgs;
|
|
574
577
|
s2 = await settled(recovery.run([...rMsgs, { role: 'user', content: SCOUT_RECOVERY_PROMPT }], [],
|
|
575
|
-
{ cacheMessages: true, maxTokens, timeoutMs: callTimeout }));
|
|
578
|
+
{ cacheMessages: true, maxTokens, timeoutMs: callTimeout, ...callOptions }));
|
|
576
579
|
const recoveryAt = record('author-scout-recovery', attempt, s2);
|
|
577
580
|
const t = redactSecrets(String(s2?.text ?? '')).slice(0, blobMax);
|
|
578
581
|
// the verdict follows the blob that WON, never the call that came last
|
|
@@ -603,7 +606,7 @@ export async function runAuthorScout({
|
|
|
603
606
|
const askMsgs = lastMsgs[0]?.role === 'system' ? lastMsgs.slice(1) : lastMsgs;
|
|
604
607
|
const rr = await settled(reask.run(
|
|
605
608
|
[...askMsgs, { role: 'user', content: scoutReaskTurn(/** @type {Survey} */ (survey).reason ?? '') }], [],
|
|
606
|
-
{ cacheMessages: true, maxTokens, timeoutMs: callTimeout },
|
|
609
|
+
{ cacheMessages: true, maxTokens, timeoutMs: callTimeout, ...callOptions },
|
|
607
610
|
));
|
|
608
611
|
verdictAt = record(label, attempt, rr);
|
|
609
612
|
blob = redactSecrets(String(rr?.text ?? '')).slice(0, blobMax);
|
package/src/bridges.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Layer 3 — the REUSE registry (design record:
|
|
2
|
-
// docs/
|
|
2
|
+
// docs/logs/2026-08-01-layer-3-reuse-design.md, D1/D2/D6, frozen 2026-08-01).
|
|
3
3
|
//
|
|
4
4
|
// A BRIDGE is the plan a green actually executed, kept so the next run of the same
|
|
5
5
|
// SHAPE starts from it instead of cold. This module is the box and the label on it:
|
package/src/bundle.js
CHANGED
|
@@ -459,13 +459,16 @@ export function exportBundle({ spec, closeScripts, registryDir, outDir, bareloop
|
|
|
459
459
|
'',
|
|
460
460
|
'## Running this bundle',
|
|
461
461
|
'',
|
|
462
|
-
' ./node_modules/.bin/bareloop run . --repo <path> [--budget N] [--wall MIN] [--approve <bundleHash>]',
|
|
462
|
+
' ./node_modules/.bin/bareloop run . --repo <path> [--model <id>] [--budget N] [--wall MIN] [--approve <bundleHash>]',
|
|
463
463
|
'',
|
|
464
464
|
'Answer these, in the order `bareloop run` asks them:',
|
|
465
465
|
'',
|
|
466
|
-
|
|
467
|
-
'
|
|
468
|
-
'
|
|
466
|
+
"1. Provider key — put your provider's API key in your keys file (the NAME your",
|
|
467
|
+
' Settings row uses), never on the command line. A bundle signed without a',
|
|
468
|
+
' model needs `--model <id>` of a line ticked in Settings > Providers (with',
|
|
469
|
+
' both prices), plus `--provider <KEY>` when two keys tick it. With neither,',
|
|
470
|
+
' `bareloop run` prints these questions and the bundleHash again, and spends',
|
|
471
|
+
' nothing.',
|
|
469
472
|
`2. Approve — the FIRST run on this machine requires --approve ${hash}`,
|
|
470
473
|
" (this bundle's own signed hash, shown above) to prove a human read and",
|
|
471
474
|
' accepted it before anything runs. Once that first run GREENS, the',
|
package/src/bundlerun.js
CHANGED
|
@@ -204,13 +204,16 @@ export async function bundleMain(args, {
|
|
|
204
204
|
workdir: worktree,
|
|
205
205
|
seed,
|
|
206
206
|
approve: approveHash,
|
|
207
|
+
// an exported bundle follows the run-u rule: a spec that names no model takes `--model <id>` (+ `--provider <KEY>` when two keys tick it)
|
|
208
|
+
...(typeof flags.model === 'string' ? { model: flags.model } : {}),
|
|
209
|
+
...(typeof flags.provider === 'string' ? { providerKey: flags.provider } : {}),
|
|
207
210
|
deps: { ...deps, env, out, err, ...(keysHome === undefined ? {} : { keysHome }) },
|
|
208
211
|
bundle: {
|
|
209
212
|
runid,
|
|
210
213
|
runDir,
|
|
211
214
|
// the bare re-invocation prefix; the leg's OWN tightened ceilings (only those the person passed) ride as
|
|
212
215
|
// DATA so the engine, which knows the halt kind and the spend, spells the printed line. Text only.
|
|
213
|
-
invoke: `bareloop run ${bundleDirArg} --repo ${repo}`,
|
|
216
|
+
invoke: `bareloop run ${bundleDirArg} --repo ${repo}${typeof flags.model === 'string' ? ` --model ${flags.model}` : ''}${typeof flags.provider === 'string' ? ` --provider ${flags.provider}` : ''}`,
|
|
214
217
|
tightened: {
|
|
215
218
|
...(envelope.budgetUsd === undefined ? {} : { budgetUsd: envelope.budgetUsd }),
|
|
216
219
|
...(flags.wall === undefined ? {} : { wallMin: Number(flags.wall) }),
|
package/src/calibrate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// THE CALIBRATION GATE — softgreen module 5 (design record
|
|
2
|
-
// docs/
|
|
2
|
+
// docs/logs/2026-08-17-softgreen-review-door-design.md §4.4, the
|
|
3
3
|
// 2026-08-18 threshold addendum rulings 1–3, and the second same-day addendum's
|
|
4
4
|
// rulings 1 and 3).
|
|
5
5
|
//
|
package/src/cardauthor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// THE TWO SIGNED ARTIFACTS — softgreen module 4 (design record
|
|
2
|
-
// docs/
|
|
2
|
+
// docs/logs/2026-08-17-softgreen-review-door-design.md §4.3 and §4.4,
|
|
3
3
|
// plus the 2026-08-18 threshold addendum).
|
|
4
4
|
//
|
|
5
5
|
// Module 1 built the judged floor's core, module 2 wired the stage into a close,
|
|
@@ -240,6 +240,9 @@ THEIR ANSWER — verbatim, and the only input you have
|
|
|
240
240
|
Q${qKey}. ${q}
|
|
241
241
|
A${qKey}. ${answers?.[qKey] ?? '(no answer given)'}
|
|
242
242
|
|
|
243
|
+
Each example is kept with the number of the job line the person wrote it under. Never attach an example to a different
|
|
244
|
+
line than the one it names (run mv1j01sl guessed which example belonged to which part of the goal).
|
|
245
|
+
|
|
243
246
|
---
|
|
244
247
|
|
|
245
248
|
THE RULEBOOK — the only rules that exist. You SELECT from it; a rule that is not here cannot be asked for.
|
package/src/cli.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// run-u` drives (`src/userrun.js`): providers, keys (the keys file / env, any provider the
|
|
8
8
|
// bundle's spec names), judge, monthly limit and readout are that engine's. The test seam is
|
|
9
9
|
// the engine's too: `deps.provider` (and `providerFor`/`judgeProvider`) supplies a scripted
|
|
10
|
-
// provider and skips the real-key check (same as `tests/planrun
|
|
10
|
+
// provider and skips the real-key check (same as `tests/planrun-*.test.js`'s pattern).
|
|
11
11
|
//
|
|
12
12
|
// Money/arbiter rules the bundle door keeps: `--budget`/`--wall` only TIGHTEN a bundle's
|
|
13
13
|
// signed ceilings (`checkEnvelope`, M1); a bundle's `bundleHash` (the human-facing
|