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.
Files changed (76) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/bareloop.context.md +137 -112
  3. package/package.json +2 -2
  4. package/src/authorflow.js +333 -58
  5. package/src/authoring.js +65 -2
  6. package/src/authorjob.js +57 -9
  7. package/src/authorreadout.js +50 -2
  8. package/src/authorrun.js +30 -27
  9. package/src/authorscout.js +9 -6
  10. package/src/bridges.js +1 -1
  11. package/src/bundle.js +7 -4
  12. package/src/bundlerun.js +4 -1
  13. package/src/calibrate.js +1 -1
  14. package/src/cardauthor.js +4 -1
  15. package/src/cli.js +1 -1
  16. package/src/config.js +129 -29
  17. package/src/declaredclose.js +4 -3
  18. package/src/draftspend.js +6 -1
  19. package/src/index.js +2 -4
  20. package/src/interviewrun.js +73 -52
  21. package/src/job.js +50 -11
  22. package/src/jobblock.js +140 -0
  23. package/src/judged.js +12 -44
  24. package/src/keysfile.js +15 -7
  25. package/src/kinds.js +2 -2
  26. package/src/ledger.js +1 -1
  27. package/src/modellist.js +84 -0
  28. package/src/monthly.js +23 -43
  29. package/src/panel/authorroutes.js +34 -19
  30. package/src/panel/authorsession.js +187 -72
  31. package/src/panel/index.html +699 -215
  32. package/src/panel/jobplan.js +107 -0
  33. package/src/panel/runroutes.js +3 -0
  34. package/src/panel/server.js +102 -28
  35. package/src/panel/settingsroutes.js +232 -173
  36. package/src/planrun.js +6 -6
  37. package/src/promptregisters.js +1 -1
  38. package/src/providerrows.js +226 -141
  39. package/src/providers.js +58 -250
  40. package/src/providershapes.js +18 -0
  41. package/src/replay.js +1 -1
  42. package/src/reuse.js +1 -1
  43. package/src/root.js +1 -1
  44. package/src/run.js +3 -3
  45. package/src/selection.js +1 -1
  46. package/src/source.js +64 -1
  47. package/src/text.js +13 -0
  48. package/src/thinking.js +130 -0
  49. package/src/transport.js +1 -1
  50. package/src/userrun.js +53 -76
  51. package/types/authorflow.d.ts +258 -39
  52. package/types/authoring.d.ts +7 -1
  53. package/types/authorjob.d.ts +35 -3
  54. package/types/authorreadout.d.ts +17 -1
  55. package/types/authorscout.d.ts +5 -3
  56. package/types/config.d.ts +23 -6
  57. package/types/declaredclose.d.ts +4 -1
  58. package/types/draftspend.d.ts +8 -2
  59. package/types/index.d.ts +1 -1
  60. package/types/job.d.ts +6 -6
  61. package/types/jobblock.d.ts +102 -0
  62. package/types/judged.d.ts +5 -36
  63. package/types/keysfile.d.ts +15 -4
  64. package/types/modellist.d.ts +50 -0
  65. package/types/monthly.d.ts +4 -3
  66. package/types/panel/authorsession.d.ts +44 -15
  67. package/types/panel/jobplan.d.ts +58 -0
  68. package/types/panel/server.d.ts +34 -4
  69. package/types/planrun.d.ts +1 -1
  70. package/types/providerrows.d.ts +192 -99
  71. package/types/providers.d.ts +35 -116
  72. package/types/providershapes.d.ts +10 -0
  73. package/types/run.d.ts +1 -1
  74. package/types/source.d.ts +26 -0
  75. package/types/thinking.d.ts +90 -0
  76. package/types/userrun.d.ts +6 -1
@@ -23,11 +23,14 @@ import { openSync } from 'node:fs';
23
23
  import { join } from 'node:path';
24
24
  import { readFileSync } from 'node:fs';
25
25
  import { createSession, validateJobCard, validateReuseCard, lockedFieldChanged, buildReuseSpec, cardFields, STEP_LABELS } from './authorsession.js';
26
+ import { parseInputs } from '../jobblock.js';
27
+ import { proveInputFiles } from '../source.js';
26
28
  import { validateJob } from '../job.js';
27
29
  import { checkMonthlyRoom, monthlyRefusalText } from '../monthly.js';
28
30
  import { ConfigError } from '../config.js';
29
31
  import { keysForDoor, keysHome } from '../keysfile.js';
30
- import { keyNameFor, modelChoiceFor, rowsForHome, chatModels, usableKey } from '../providerrows.js';
32
+ import { endpointOf, judgePick, keyValueOf, rowsForHome, tickedLines } from '../providerrows.js';
33
+ import { thinkingForLine } from '../thinking.js';
31
34
  import { apiKeyProblem, checkProviderReachable } from '../providers.js';
32
35
 
33
36
  /** @returns {string} a fresh per-process token — never persisted, never logged */
@@ -135,13 +138,13 @@ export function createAuthorRoutes(opts) {
135
138
  if (pathname === '/api/author/model-check') {
136
139
  if (req.method !== 'GET') { send(405, { ok: false, error: 'GET only' }); return true; }
137
140
  const q = new URL(/** @type {string} */ (req.url), 'http://127.0.0.1').searchParams;
138
- const modelId = q.get('model') ?? '';
139
- const rows = rowsForHome(keysHome(opts.home));
140
- const choice = modelChoiceFor(rows, modelId);
141
- if (!choice) { send(400, { ok: false, error: `unknown model "${modelId}"` }); return true; }
142
- // the key of the Settings row this Name belongs to
143
- const keyName = keyNameFor(choice.provider, choice.baseUrl, rows, choice.name).name;
144
- const raw = envNow()[keyName];
141
+ const rows = rowsForHome(keysHome(opts.home), envNow());
142
+ const pick = judgePick(rows, { provider: q.get('provider'), model: q.get('model') });
143
+ if (!pick.ok) { send(400, { ok: false, error: pick.say }); return true; }
144
+ const choice = pick.choice;
145
+ // the key of the Settings row this line belongs to
146
+ const keyName = choice.envName;
147
+ const raw = keyValueOf(envNow(), keyName);
145
148
  const problem = raw ? apiKeyProblem(raw) : null;
146
149
  const keyStatus = !raw ? 'missing' : (problem ? 'bad-shape' : 'found');
147
150
  if (keyStatus !== 'found') {
@@ -155,7 +158,7 @@ export function createAuthorRoutes(opts) {
155
158
  // `checkProviderReachable`'s own doc) — a short timeout so a flaky
156
159
  // endpoint never hangs the card.
157
160
  checkProviderReachable({
158
- providerName: choice.provider, apiKey: /** @type {string} */ (usableKey(raw)), model: choice.name,
161
+ providerName: choice.provider, apiKey: /** @type {string} */ (raw), model: choice.name,
159
162
  baseUrl: choice.baseUrl, fetchImpl, timeoutMs: 4000,
160
163
  }).then((r) => {
161
164
  send(200, {
@@ -178,7 +181,11 @@ export function createAuthorRoutes(opts) {
178
181
  // ── P4b: Chat's Model menu IS the Settings rows (a blank Name is not offered). ──
179
182
  if (pathname === '/api/author/models') {
180
183
  if (req.method !== 'GET') { send(405, { ok: false, error: 'GET only' }); return true; }
181
- send(200, { ok: true, models: chatModels(rowsForHome(keysHome(opts.home))) });
184
+ // every ticked-and-priced line of a row, labelled `<KEY> · <model>`; `thinking` is the server's answer for the "Thinking on" box
185
+ const lines = tickedLines(rowsForHome(keysHome(opts.home), envNow())).map(({ row, line }) => ({
186
+ 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),
187
+ }));
188
+ send(200, { ok: true, lines, say: lines.length === 0 ? 'Tick a model in Settings > Providers.' : '' });
182
189
  return true;
183
190
  }
184
191
 
@@ -205,14 +212,14 @@ export function createAuthorRoutes(opts) {
205
212
  const sp = new URL(/** @type {string} */ (req.url), 'http://127.0.0.1').searchParams;
206
213
  const runid = sp.get('runid') ?? '';
207
214
  // `model` = the card's Model box right now: the estimate line counts only runs on that worker
208
- const asModel = sp.has('model') ? { model: sp.get('model') ?? '' } : {};
215
+ const asModel = sp.has('model') ? { model: sp.get('model') ?? '', provider: sp.get('provider') ?? '' } : {};
209
216
  const pre = sp.has('import') ? opts.startFrom.getImport(sp.get('import') ?? '', asModel) : opts.startFrom.get(runid, asModel);
210
217
  if (pre === null) { send(404, { ok: false, error: 'no such run' }); return true; }
211
218
  if (!pre.ok) { send(409, pre); return true; }
212
219
  send(200, {
213
220
  ok: true, origin: pre.origin, card: pre.card, from: pre.from, note: pre.note,
214
221
  locked: pre.locked, open: pre.open, specHash: pre.specHash, workflowKey: pre.workflowKey, trackRecord: pre.trackRecord,
215
- line: pre.line,
222
+ line: pre.line, specNamesModel: pre.specNamesModel, specWorker: pre.specWorker,
216
223
  });
217
224
  return true;
218
225
  }
@@ -223,7 +230,7 @@ export function createAuthorRoutes(opts) {
223
230
  if (!opts.startFrom || !opts.startFrom.listJobs) { send(404, { ok: false, error: 'not found' }); return true; }
224
231
  if (req.method !== 'GET') { send(405, { ok: false, error: 'GET only' }); return true; }
225
232
  const sp = new URL(/** @type {string} */ (req.url), 'http://127.0.0.1').searchParams;
226
- const asModel = sp.has('model') ? { model: sp.get('model') ?? '' } : {};
233
+ const asModel = sp.has('model') ? { model: sp.get('model') ?? '', provider: sp.get('provider') ?? '' } : {};
227
234
  send(200, { ok: true, jobs: opts.startFrom.listJobs(asModel) });
228
235
  return true;
229
236
  }
@@ -241,7 +248,7 @@ export function createAuthorRoutes(opts) {
241
248
  if (req.method !== 'POST') { send(405, { ok: false, error: 'POST only' }); return true; }
242
249
  if (hasLiveSession()) { send(409, { ok: false, error: 'an authoring session is already live — one at a time' }); return true; }
243
250
  const card = body ?? {};
244
- const rows = rowsForHome(keysHome(opts.home));
251
+ const rows = rowsForHome(keysHome(opts.home), envNow());
245
252
  // Reuse workflow — started from a green run or an imported job: only Source, Destination and the two caps may
246
253
  // differ from the signed workflow. The SERVER refuses any other change (the page greys those boxes, but is
247
254
  // never trusted); the spec it signs is a copy of the origin's with just those four set. Nothing is drafted.
@@ -274,11 +281,19 @@ export function createAuthorRoutes(opts) {
274
281
  const v = validateJobCard(card, { jobsDir: opts.jobsDir, rows });
275
282
  if (!v.ok) { send(400, { ok: false, error: v.error }); return true; }
276
283
  }
277
- const session = createSession(cardFields(card), {
278
- env: envNow(), sessionsRoot: opts.sessionsRoot, ...(opts.home !== undefined ? { home: opts.home } : {}), ...(reuse ? { reuse } : {}),
279
- });
280
- sessions.set(session.id, session);
281
- send(200, { ok: true, sessionId: session.id, reuse: reuse !== null, state: session.state });
284
+ // P7: the Inputs are proven at $0 before a session (so before any token): line 1 the repo, lines 2+ tracked files inside it.
285
+ // A miss is a plain 400 naming the line, shown under the card exactly like every other refused start.
286
+ const ip = parseInputs(card.inputs);
287
+ if (!ip.ok) { send(400, { ok: false, error: ip.error }); return true; }
288
+ proveInputFiles(ip.inputs).then((proof) => {
289
+ if (!proof.ok) { send(400, { ok: false, error: proof.error }); return; }
290
+ if (hasLiveSession()) { send(409, { ok: false, error: 'an authoring session is already live — one at a time' }); return; }
291
+ const session = createSession(cardFields(card), {
292
+ env: envNow(), sessionsRoot: opts.sessionsRoot, ...(opts.home !== undefined ? { home: opts.home } : {}), ...(reuse ? { reuse } : {}),
293
+ });
294
+ sessions.set(session.id, session);
295
+ send(200, { ok: true, sessionId: session.id, reuse: reuse !== null, state: session.state });
296
+ }).catch((e) => { send(500, { ok: false, error: `could not check the inputs: ${/** @type {Error} */ (e)?.message ?? e}` }); });
282
297
  return true;
283
298
  }
284
299