bareloop 0.38.0 → 0.39.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 +32 -0
- package/bareloop.context.md +26 -15
- package/package.json +3 -3
- package/src/authorflow.js +3 -2
- package/src/authoring.js +62 -3
- package/src/authorjob.js +37 -24
- package/src/authorreadout.js +449 -2
- package/src/authorrun.js +21 -4
- package/src/calibrate.js +84 -10
- package/src/cardauthor.js +4 -1
- package/src/declaredclose.js +10 -4
- package/src/job.js +1 -1
- package/src/judged.js +322 -28
- package/src/kinds.js +13 -1
- package/src/panel/authorroutes.js +2 -6
- package/src/panel/authorsession.js +61 -45
- package/src/panel/index.html +123 -242
- package/src/planrun.js +3 -2
- package/src/providers.js +27 -0
- package/src/trend.js +1 -0
- package/types/authoring.d.ts +24 -1
- package/types/authorjob.d.ts +17 -10
- package/types/authorreadout.d.ts +106 -0
- package/types/calibrate.d.ts +4 -1
- package/types/declaredclose.d.ts +2 -1
- package/types/judged.d.ts +21 -2
- package/types/panel/authorsession.d.ts +15 -1
- package/types/providers.d.ts +7 -0
- package/types/trend.d.ts +1 -0
|
@@ -52,6 +52,7 @@ import { closeJudges } from '../kinds.js';
|
|
|
52
52
|
import { redactSecrets } from '../validate.js';
|
|
53
53
|
import { tallyCalls } from '../text.js';
|
|
54
54
|
import { writeDraftSpend, appendDraftLog } from '../draftspend.js';
|
|
55
|
+
import { PLAIN_PROPOSAL_STOPS, proposalStopText, signingStopText, redsRecord, calibrationSummary } from '../authorreadout.js';
|
|
55
56
|
import { runNpmCi, NPM_CI_LOCKS } from '../npminstall.js';
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -132,13 +133,24 @@ const SOURCE_CHECK_CODES = new Set([
|
|
|
132
133
|
'source-file-oversize', 'source-changed-after-scan', 'source-untracked-in-repo',
|
|
133
134
|
]);
|
|
134
135
|
|
|
135
|
-
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
|
|
136
|
+
/**
|
|
137
|
+
* The confirm turn's plan as the chat shows it: ONE owner of the text (hamr, 2026-10-09: the one-blob "Plan: ...
|
|
138
|
+
* Checks: ... Not checked: ..." was unreadable). Each section is a heading on its own line, its content on the next,
|
|
139
|
+
* and a blank line between sections. `#NOT CHECKED:` and `#QUESTIONS:` appear only when there is something to say.
|
|
140
|
+
* The CLI composes its own plan screen (src/authorrun.js) and shares no text with this one.
|
|
141
|
+
* @param {{goal?: string, checks?: string[], notChecked?: string[], questions?: string[]}} p
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
export function planText(p) {
|
|
145
|
+
const sections = [
|
|
146
|
+
`#PLAN:\n${JSON.stringify(p.goal ?? '')}`,
|
|
147
|
+
`#CHECKS:\n${(p.checks ?? []).join(' · ') || '(none)'}`,
|
|
148
|
+
];
|
|
149
|
+
if ((p.notChecked ?? []).length) sections.push(`#NOT CHECKED:\n${(p.notChecked ?? []).join(' · ')}`);
|
|
150
|
+
if ((p.questions ?? []).length) sections.push(`#QUESTIONS:\n${(p.questions ?? []).join(' · ')}`);
|
|
151
|
+
sections.push('Sign & run to confirm this plan, or Revise to edit the boxes and draft again.');
|
|
152
|
+
return sections.join('\n\n');
|
|
153
|
+
}
|
|
142
154
|
|
|
143
155
|
/** @param {string} name @returns {boolean} kebab-case slug, same rule `job.js`'s `SLUG_RE` enforces */
|
|
144
156
|
function isSlug(name) {
|
|
@@ -304,7 +316,7 @@ export function validateJobCard(card, opts = {}) {
|
|
|
304
316
|
/**
|
|
305
317
|
* ONE session's engine — created already RUNNING (the caller awaits nothing;
|
|
306
318
|
* the pipeline drives itself, reporting through `state`). `state.pendingAsk`
|
|
307
|
-
* is the ONE thing a person answers next; `answer()`/`send()
|
|
319
|
+
* is the ONE thing a person answers next; `answer()`/`send()`/
|
|
308
320
|
* `signPrepare()` are the only ways in.
|
|
309
321
|
* @param {any} card the validated job card (see {@link validateJobCard})
|
|
310
322
|
* @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
|
|
@@ -322,7 +334,7 @@ export function createSession(card, deps = {}) {
|
|
|
322
334
|
const timeoutMs = deps.timeoutMs ?? 300_000;
|
|
323
335
|
// TEST SEAMS ONLY (never set by `src/panel/authorroutes.js`'s real caller):
|
|
324
336
|
// override the declaration composer and/or `prepareSigning` itself so a
|
|
325
|
-
// test can drive the REAL confirm turn / ask() channel /
|
|
337
|
+
// test can drive the REAL confirm turn / ask() channel /
|
|
326
338
|
// hash-matching machinery this file owns, without also re-running (and
|
|
327
339
|
// re-proving) `authorClose`'s own composer ladder or `prepareSigning`'s own
|
|
328
340
|
// gates — both already have their own test suites. Absent, both default to
|
|
@@ -342,7 +354,6 @@ export function createSession(card, deps = {}) {
|
|
|
342
354
|
messages: /** @type {{role: string, text: string}[]} */ ([]),
|
|
343
355
|
pendingAsk: /** @type {any} */ (null),
|
|
344
356
|
cost: /** @type {any} */ (null),
|
|
345
|
-
revisesLeft: CONFIRM_ROUND_CAP,
|
|
346
357
|
specHash: /** @type {string|null} */ (null),
|
|
347
358
|
resolvedSpecPath: /** @type {string|null} */ (null),
|
|
348
359
|
error: /** @type {string|null} */ (null),
|
|
@@ -436,15 +447,9 @@ export function createSession(card, deps = {}) {
|
|
|
436
447
|
resolvePending = resolveFn;
|
|
437
448
|
if (step.kind === 'language') say('bot', `${step.field?.prompt ?? 'Which language is this job about?'} (${(step.candidates ?? []).join(', ')})`);
|
|
438
449
|
else if (step.kind === 'menu') {
|
|
439
|
-
|
|
440
|
-
const lines = [`Plan: ${JSON.stringify(p.goal ?? '')}`, `Checks: ${(p.checks ?? []).join(' · ') || '(none)'}`];
|
|
441
|
-
if ((p.notChecked ?? []).length) lines.push(`Not checked: ${p.notChecked.join(' · ')}`);
|
|
442
|
-
if ((p.questions ?? []).length) lines.push(`Questions: ${p.questions.join(' · ')}`);
|
|
443
|
-
lines.push('Sign & run to confirm this plan, or Revise to describe a change.');
|
|
444
|
-
say('bot', lines.join('\n'));
|
|
450
|
+
say('bot', planText(step.plan ?? {}));
|
|
445
451
|
} else if (step.kind === 'answer') say('bot', `A question the plan raised (${step.index} of ${step.total}): ${step.question}`);
|
|
446
452
|
else if (step.kind === 'goal') say('bot', 'Type the goal sentence yourself.');
|
|
447
|
-
else if (step.kind === 'fix') say('bot', 'What should change?');
|
|
448
453
|
});
|
|
449
454
|
|
|
450
455
|
/** @param {string|null} value @returns {boolean} */
|
|
@@ -457,21 +462,7 @@ export function createSession(card, deps = {}) {
|
|
|
457
462
|
return true;
|
|
458
463
|
};
|
|
459
464
|
|
|
460
|
-
/** @param {'menu'|'fix'|'answer'|'language'|'goal'} kind @param {number} timeoutMsInner */
|
|
461
|
-
const waitForPendingKind = async (kind, timeoutMsInner = 2000) => {
|
|
462
|
-
const start = Date.now();
|
|
463
|
-
while (Date.now() - start < timeoutMsInner) {
|
|
464
|
-
if (state.pendingAsk?.kind === kind) return true;
|
|
465
|
-
// eslint-disable-next-line no-await-in-loop
|
|
466
|
-
await new Promise((r) => { setTimeout(r, 10); });
|
|
467
|
-
}
|
|
468
|
-
return state.pendingAsk?.kind === kind;
|
|
469
|
-
};
|
|
470
|
-
|
|
471
465
|
const onPhase = (name, data = {}) => {
|
|
472
|
-
if (name === 'confirm-round' && typeof data.round === 'number') {
|
|
473
|
-
state.revisesLeft = Math.max(0, CONFIRM_ROUND_CAP - (data.round - 1));
|
|
474
|
-
}
|
|
475
466
|
// COLLAPSED INTO THE PROGRESS INDICATOR ONLY (build item 3) — this used
|
|
476
467
|
// to also `say('system', `… ${name}`)`, one jargon chat bubble per
|
|
477
468
|
// library phase (`… confirm-turn-done`, `… seed-read`, …), read back to
|
|
@@ -764,9 +755,28 @@ export function createSession(card, deps = {}) {
|
|
|
764
755
|
});
|
|
765
756
|
const signingFile = join(outDir, 'signing.json');
|
|
766
757
|
writeFileSync(signingFile, `${JSON.stringify(signing, null, 2)}\n`);
|
|
758
|
+
// The `calibrating` line is the rubric phase the person watched; its ✓ was set long before the gate graded
|
|
759
|
+
// anything. Once the gate has a result, the line says THAT, on its own line (hamr, 2026-10-09).
|
|
760
|
+
const calSum = calibrationSummary(signing.gates?.calibration);
|
|
761
|
+
const calStep = latestStep(state.steps, 'calibrate');
|
|
762
|
+
if (calSum && calStep) {
|
|
763
|
+
calStep.status = calSum.ok ? 'done' : 'failed';
|
|
764
|
+
calStep.detail = calSum.note;
|
|
765
|
+
logSteps();
|
|
766
|
+
}
|
|
767
767
|
|
|
768
768
|
if (!signing.ok) {
|
|
769
|
-
|
|
769
|
+
// plain words for the person (src/authorreadout.js, the one owner the CLI prints too); the full reds go into
|
|
770
|
+
// the drafting log on the failed step, so the cause survives the session
|
|
771
|
+
const runningNo = state.steps.findLastIndex((/** @type {any} */ x) => x.status === 'running');
|
|
772
|
+
appendDraftLog(outDir, {
|
|
773
|
+
kind: 'step-reds', no: runningNo === -1 ? state.steps.length - 1 : runningNo, stop: 'signing-gates-failed',
|
|
774
|
+
reds: redsRecord(signing.reds ?? [], { signing: true }), at: new Date().toISOString(),
|
|
775
|
+
});
|
|
776
|
+
refuse(signingStopText({
|
|
777
|
+
reds: signing.reds ?? [], refusal: signing.refusal,
|
|
778
|
+
spend: { knownUsd: state.draftSpentUsd, spendComplete: state.draftSpendComplete },
|
|
779
|
+
}));
|
|
770
780
|
return;
|
|
771
781
|
}
|
|
772
782
|
state.specHash = signing.specHash;
|
|
@@ -840,7 +850,7 @@ export function createSession(card, deps = {}) {
|
|
|
840
850
|
// TEST SEAMS (see the constructor's own note): a test overrides
|
|
841
851
|
// `generate`/`confirmGenerate` directly (bypassing this real, constructed
|
|
842
852
|
// `provider` for the model boundary) and/or `scout` (bypassing the real
|
|
843
|
-
// paid scout) so it can drive the REAL confirm turn/ask()/
|
|
853
|
+
// paid scout) so it can drive the REAL confirm turn/ask()/hash
|
|
844
854
|
// machinery below with a deterministic fake, never a live provider call.
|
|
845
855
|
// no NEW model call starts after an abandon; a call already in flight books its usage via onCall and is discarded
|
|
846
856
|
const noCallAfterAbandon = (fn) => (...a) => { if (abandoned) throw new Error('abandoned — no new model call'); return fn(...a); };
|
|
@@ -865,6 +875,23 @@ export function createSession(card, deps = {}) {
|
|
|
865
875
|
...(authorFnOverride ? { authorFn: authorFnOverride } : {}),
|
|
866
876
|
});
|
|
867
877
|
|
|
878
|
+
if (!authored.ok && PLAIN_PROPOSAL_STOPS.includes(String(authored.stop))) {
|
|
879
|
+
// a refused rubric proposal: the person gets the reason in plain words (src/authorreadout.js, the one owner the
|
|
880
|
+
// CLI prints too), and the full reds go into the drafting log on the failed step so the cause survives the session
|
|
881
|
+
const cases = authored.judged?.signed?.cases ?? authored.judged?.proposal?.proposal?.cases ?? null;
|
|
882
|
+
const reds = authored.reds ?? [];
|
|
883
|
+
const runningNo = state.steps.findLastIndex((/** @type {any} */ x) => x.status === 'running');
|
|
884
|
+
appendDraftLog(outDir, {
|
|
885
|
+
kind: 'step-reds', no: runningNo === -1 ? state.steps.length - 1 : runningNo, stop: authored.stop,
|
|
886
|
+
reds: redsRecord(reds, { cases }), at: new Date().toISOString(),
|
|
887
|
+
});
|
|
888
|
+
refuse(proposalStopText({
|
|
889
|
+
stop: String(authored.stop), reds, cases,
|
|
890
|
+
spend: { knownUsd: state.draftSpentUsd, spendComplete: state.draftSpendComplete },
|
|
891
|
+
source: authored.judged?.source === 'signer' ? 'signer' : 'proposal',
|
|
892
|
+
}));
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
868
895
|
if (!authored.ok) {
|
|
869
896
|
refuse(`Stopped: ${authored.stop ?? 'authoring-failed'}${authored.refusal ? ` — ${authored.refusal.detail}` : ''}`, authored.stop === 'confirm-abandoned' ? 'abandoned' : 'refused');
|
|
870
897
|
return;
|
|
@@ -888,7 +915,7 @@ export function createSession(card, deps = {}) {
|
|
|
888
915
|
/** @param {string} text */
|
|
889
916
|
send: (text) => {
|
|
890
917
|
if (!state.pendingAsk) return { ok: false, error: 'nothing is being asked right now' };
|
|
891
|
-
if (state.pendingAsk.kind === 'menu') return { ok: false, error: 'use Sign & run
|
|
918
|
+
if (state.pendingAsk.kind === 'menu') return { ok: false, error: 'use Sign & run for a plan, never Send — the chat can never sign or pick a plan action' };
|
|
892
919
|
if (state.pendingAsk.kind === 'install-needed') return { ok: false, error: 'install the packages, then click Check again — nothing to send here' };
|
|
893
920
|
say('you', text);
|
|
894
921
|
answer(text);
|
|
@@ -917,17 +944,6 @@ export function createSession(card, deps = {}) {
|
|
|
917
944
|
r();
|
|
918
945
|
return { ok: true };
|
|
919
946
|
},
|
|
920
|
-
/** @param {string} text */
|
|
921
|
-
revise: async (text) => {
|
|
922
|
-
if (!state.pendingAsk || state.pendingAsk.kind !== 'menu') return { ok: false, error: 'no plan is waiting for a Revise right now' };
|
|
923
|
-
if (state.revisesLeft <= 0) return { ok: false, error: 'no revises left (D3: max 2 rounds)' };
|
|
924
|
-
answer('fix');
|
|
925
|
-
const reached = await waitForPendingKind('fix', 3000);
|
|
926
|
-
if (!reached) return { ok: false, error: 'the confirm turn did not ask for a fix in time' };
|
|
927
|
-
say('you', text);
|
|
928
|
-
answer(text);
|
|
929
|
-
return { ok: true };
|
|
930
|
-
},
|
|
931
947
|
/** click 1 — the confirm pick. Never signs on its own. */
|
|
932
948
|
signPrepare: () => {
|
|
933
949
|
if (!state.pendingAsk || state.pendingAsk.kind !== 'menu') return { ok: false, error: 'no plan is waiting for a decision right now' };
|