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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// The job plan MODEL (P7, docs/product/PANEL-BUILD.md "The job" block): ONE owner of the structure the page's single
|
|
2
|
+
// `renderJobPlan()` draws in two places — the drafting confirm card (the plan bubble) and the read-only Job tab. The
|
|
3
|
+
// server builds it; the page only draws it (so the two places can never read the same plan two ways).
|
|
4
|
+
//
|
|
5
|
+
// { lines: [{n, text, rule, checks: [{name, cls}], refused: [reason...]}],
|
|
6
|
+
// loose: [{name, cls}], checks that name no job line (an older caller, or a model slip)
|
|
7
|
+
// notChecked: [text...], "the AI's own reading" — what the drafter says nothing checks
|
|
8
|
+
// alwaysOn: [text...], the mandatory guards / protections, which serve no job line
|
|
9
|
+
// older: boolean } an older job: the numbered lines were not saved
|
|
10
|
+
//
|
|
11
|
+
// `cls` is 'machine check' | 'judge' | null (null = not known yet: the confirm card's checks are the model's plan, the
|
|
12
|
+
// stage kinds exist only after drafting). Pure; no file IO.
|
|
13
|
+
import { KIND_CATALOGUE } from '../authoring.js';
|
|
14
|
+
|
|
15
|
+
/** @typedef {{name: string, cls: 'machine check'|'judge'|null}} PlanCheck */
|
|
16
|
+
/** @typedef {{n: number, text: string, rule: string, checks: PlanCheck[], refused: string[]}} PlanLine */
|
|
17
|
+
/** @typedef {{lines: PlanLine[], loose: PlanCheck[], notChecked: string[], alwaysOn: string[], older: boolean, goal?: string}} JobPlan */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `machine check` for a kind a command decides, `judge` for one that needs judgement; `null` for a stage whose kind is
|
|
21
|
+
* unknown (a hand-written command close has no kind: it is a command, so a machine check).
|
|
22
|
+
* @param {unknown} kind
|
|
23
|
+
* @returns {'machine check'|'judge'|null}
|
|
24
|
+
*/
|
|
25
|
+
export function checkClassOfKind(kind) {
|
|
26
|
+
if (typeof kind !== 'string') return null;
|
|
27
|
+
const spec = Object.hasOwn(KIND_CATALOGUE, kind) ? KIND_CATALOGUE[kind] : null;
|
|
28
|
+
if (!spec) return null;
|
|
29
|
+
return spec.verdictClass === 'green' ? 'machine check' : 'judge';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The plan of a SIGNED spec. With `jobLines`, each stage lands under EACH line its `fromLine` array names (a stage with none is
|
|
34
|
+
* a mandatory guard: "Always on"), `closeDecl.refused` reasons land under their line, and `closeDecl.notes` are the
|
|
35
|
+
* drafter's own "not checked" words. Without `jobLines` (a spec that predates the job block) the signed goal sentence is
|
|
36
|
+
* line 1 and every stage name is a check under it — `older: true`.
|
|
37
|
+
* @param {any} spec
|
|
38
|
+
* @returns {JobPlan}
|
|
39
|
+
*/
|
|
40
|
+
export function planFromSpec(spec) {
|
|
41
|
+
/** @type {{name: string, cls: 'machine check'|'judge'|null, fromLine: number[]|null}[]} */
|
|
42
|
+
let stages = [];
|
|
43
|
+
if (spec && Array.isArray(spec.closeDecl?.stages)) {
|
|
44
|
+
stages = spec.closeDecl.stages.filter((/** @type {any} */ s) => s && typeof s.name === 'string' && s.name)
|
|
45
|
+
.map((/** @type {any} */ s) => ({ name: s.name, cls: checkClassOfKind(s.kind), fromLine: Array.isArray(s.fromLine) && s.fromLine.length ? s.fromLine.filter(Number.isInteger) : null }));
|
|
46
|
+
} else if (spec && Array.isArray(spec.close)) {
|
|
47
|
+
stages = spec.close.filter((/** @type {any} */ s) => s && typeof s.name === 'string' && s.name)
|
|
48
|
+
.map((/** @type {any} */ s) => ({ name: s.name, cls: /** @type {'machine check'} */ ('machine check'), fromLine: null }));
|
|
49
|
+
}
|
|
50
|
+
const notes = Array.isArray(spec?.closeDecl?.notes) ? spec.closeDecl.notes.filter((/** @type {any} */ n) => typeof n === 'string' && n) : [];
|
|
51
|
+
const jl = Array.isArray(spec?.jobLines) ? spec.jobLines.filter((/** @type {any} */ l) => l && Number.isInteger(l.n) && typeof l.text === 'string') : [];
|
|
52
|
+
if (jl.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
lines: [{
|
|
55
|
+
n: 1, text: typeof spec?.goal === 'string' && spec.goal ? spec.goal : '(no goal recorded)', rule: '',
|
|
56
|
+
checks: stages.map((s) => ({ name: s.name, cls: s.cls })), refused: [],
|
|
57
|
+
}],
|
|
58
|
+
loose: [], notChecked: notes, alwaysOn: [], older: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** @type {PlanLine[]} */
|
|
62
|
+
const lines = jl.map((/** @type {any} */ l) => ({ n: l.n, text: l.text, rule: typeof l.rule === 'string' ? l.rule : '', checks: [], refused: [] }));
|
|
63
|
+
/** @type {PlanCheck[]} */
|
|
64
|
+
const loose = [];
|
|
65
|
+
/** @type {string[]} */
|
|
66
|
+
const alwaysOn = [];
|
|
67
|
+
for (const s of stages) {
|
|
68
|
+
if (s.fromLine === null) { alwaysOn.push(s.name); continue; }
|
|
69
|
+
// a check that serves several job lines is shown under EACH line it serves (no new element: the same check line, repeated)
|
|
70
|
+
const served = lines.filter((l) => /** @type {number[]} */ (s.fromLine).includes(l.n));
|
|
71
|
+
for (const line of served) line.checks.push({ name: s.name, cls: s.cls });
|
|
72
|
+
if (served.length === 0) loose.push({ name: s.name, cls: s.cls });
|
|
73
|
+
}
|
|
74
|
+
const refused = Array.isArray(spec?.closeDecl?.refused) ? spec.closeDecl.refused : [];
|
|
75
|
+
for (const r of refused) {
|
|
76
|
+
const line = lines.find((l) => l.n === r?.line);
|
|
77
|
+
if (line && typeof r.reason === 'string') line.refused.push(r.reason);
|
|
78
|
+
}
|
|
79
|
+
// the model's signed goal sentence rides along: the page draws it as the "AI's summary" row (an older job shows it as line 1 instead)
|
|
80
|
+
return { lines, loose, notChecked: notes, alwaysOn, older: false, ...(typeof spec?.goal === 'string' && spec.goal ? { goal: spec.goal } : {}) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The plan of the confirm turn, before anything is drafted: the person's own job lines, the model's checks under the
|
|
85
|
+
* line each names, its own "not checked" words, and the real protections (code-owned) as "Always on".
|
|
86
|
+
* @param {{n: number, text: string, rule: string}[]|null|undefined} jobLines
|
|
87
|
+
* @param {{checkItems?: {text: string, fromLine: number[]|null}[], checks?: string[], notChecked?: string[], protections?: string[]}} plan
|
|
88
|
+
* @returns {JobPlan}
|
|
89
|
+
*/
|
|
90
|
+
export function planFromConfirm(jobLines, plan) {
|
|
91
|
+
const items = Array.isArray(plan?.checkItems) && plan.checkItems.length
|
|
92
|
+
? plan.checkItems
|
|
93
|
+
: (Array.isArray(plan?.checks) ? plan.checks.map((c) => ({ text: String(c), fromLine: null })) : []);
|
|
94
|
+
const jl = Array.isArray(jobLines) ? jobLines : [];
|
|
95
|
+
/** @type {PlanLine[]} */
|
|
96
|
+
const lines = jl.map((l) => ({ n: l.n, text: l.text, rule: l.rule, checks: [], refused: [] }));
|
|
97
|
+
/** @type {PlanCheck[]} */
|
|
98
|
+
const loose = [];
|
|
99
|
+
for (const c of items) {
|
|
100
|
+
const served = lines.filter((l) => Array.isArray(c.fromLine) && c.fromLine.includes(l.n));
|
|
101
|
+
for (const line of served) line.checks.push({ name: c.text, cls: null });
|
|
102
|
+
if (served.length === 0) loose.push({ name: c.text, cls: null });
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
lines, loose, notChecked: [...(plan?.notChecked ?? [])], alwaysOn: [...(plan?.protections ?? [])], older: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
package/src/panel/runroutes.js
CHANGED
|
@@ -175,6 +175,9 @@ export function createRunRoutes(opts) {
|
|
|
175
175
|
'--wait', 'systemd-inhibit', '--why=bareloop panel resume',
|
|
176
176
|
process.execPath, bareloopBin,
|
|
177
177
|
'run-u', '--spec', specPath, '--resume', m[1], '--approve', specHash,
|
|
178
|
+
// an older spec that names no model runs on the Settings line the person picks at resume (hash untouched, run-u's own rule)
|
|
179
|
+
...(typeof body?.model === 'string' && body.model !== '' ? ['--model', body.model] : []),
|
|
180
|
+
...(typeof body?.provider === 'string' && body.provider !== '' ? ['--provider', body.provider] : []),
|
|
178
181
|
// the drafting fold rides every leg of the chain (a leg that dropped it would widen the ceiling)
|
|
179
182
|
...(plan.draftSpentUsd !== null ? ['--draft-spent-usd', String(plan.draftSpentUsd), ...(plan.draftSpendComplete === false ? ['--draft-spend-incomplete'] : [])] : []),
|
|
180
183
|
];
|
package/src/panel/server.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
replayOne, parseJsonl, resolveSiblings, isSidecarByName,
|
|
37
37
|
} from '../replayio.js';
|
|
38
38
|
import { summarizeForAllLine, auditWindowsOf } from '../replay.js';
|
|
39
|
-
import { legsOf, legsWallMs, stopFilePath } from '../legs.js';
|
|
39
|
+
import { legsOf, legsWallMs, stopFilePath, capKindOf } from '../legs.js';
|
|
40
40
|
import { chainSpend } from '../ledger.js';
|
|
41
41
|
import { runBehaviour } from '../behaviour.js';
|
|
42
42
|
import { SPEND_RECORD_TYPES, floorsFromRecords } from '../ledger.js';
|
|
@@ -49,12 +49,14 @@ import { parseImportedRunId, safeSpinePath, spineStartedAt } from './importrun.j
|
|
|
49
49
|
import { checkBundleDeps, resolveBundleSpec } from '../bundle.js';
|
|
50
50
|
import { checkCloseByteSignature } from '../close-integrity.js';
|
|
51
51
|
import { keysHome } from '../keysfile.js';
|
|
52
|
-
import { rowsForHome, findRow, modelChoiceFor } from '../providerrows.js';
|
|
52
|
+
import { rowsForHome, findRow, modelChoiceFor, tickedLines, endpointOf } from '../providerrows.js';
|
|
53
|
+
import { SHAPES } from '../providershapes.js';
|
|
53
54
|
import { resolveProvider } from '../providers.js';
|
|
54
55
|
import { REUSE_LOCKED_FIELDS, REUSE_OPEN_FIELDS } from './authorsession.js';
|
|
55
56
|
import { readResume, checkpointAgeGate, CHECKPOINT_OUTCOMES } from '../reuse.js';
|
|
56
57
|
import { statusFor, partStatusFor, GOAL_MET_LINE } from './status.js';
|
|
57
58
|
import { createSettingsRoutes } from './settingsroutes.js';
|
|
59
|
+
import { planFromSpec } from './jobplan.js';
|
|
58
60
|
|
|
59
61
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
60
62
|
|
|
@@ -479,7 +481,7 @@ function endedBase(summary, death, o) {
|
|
|
479
481
|
if (outcome === 'escalated') {
|
|
480
482
|
const strikes = esc?.spend?.strikes;
|
|
481
483
|
const limit = esc?.spend?.strikeLimit;
|
|
482
|
-
if (
|
|
484
|
+
if (capKindOf({ outcome, category: cat, moneyHalt: o.moneyHalt }) === 'money') {
|
|
483
485
|
const money = typeof summary.spentUsd === 'number' && typeof summary.budgetUsd === 'number'
|
|
484
486
|
? ` (${panelMoney2(summary.spentUsd)} of ${panelMoney2(summary.budgetUsd)})` : '';
|
|
485
487
|
return {
|
|
@@ -494,7 +496,7 @@ function endedBase(summary, death, o) {
|
|
|
494
496
|
actions: [],
|
|
495
497
|
};
|
|
496
498
|
}
|
|
497
|
-
if (cat === '
|
|
499
|
+
if (capKindOf({ outcome, category: cat, moneyHalt: o.moneyHalt }) === 'time') {
|
|
498
500
|
return { reason: 'Time cap reached.', next: CHANGE, line: 'time cap', actions: [] };
|
|
499
501
|
}
|
|
500
502
|
if (cat === 'provider-red') {
|
|
@@ -2098,6 +2100,42 @@ function toolsFromSpec(spec) {
|
|
|
2098
2100
|
return names ? names.join(' · ') : null;
|
|
2099
2101
|
}
|
|
2100
2102
|
|
|
2103
|
+
/**
|
|
2104
|
+
* The Signed row of the Job tab: `YYYY-MM-DD HH:MM · <first 6 of specHash>`, the time being the run's FIRST `job-start`
|
|
2105
|
+
* in local time (hamr, P7). `null` when either part is not on record — never a made-up stamp.
|
|
2106
|
+
* @param {string|null|undefined} ts an ISO stamp @param {string|null|undefined} hash
|
|
2107
|
+
* @returns {string|null}
|
|
2108
|
+
*/
|
|
2109
|
+
export function signedLine(ts, hash) {
|
|
2110
|
+
const t = typeof ts === 'string' ? new Date(ts) : null;
|
|
2111
|
+
if (!t || Number.isNaN(t.getTime()) || typeof hash !== 'string' || hash.length < 6) return null;
|
|
2112
|
+
const p2 = (/** @type {number} */ n) => String(n).padStart(2, '0');
|
|
2113
|
+
return `${t.getFullYear()}-${p2(t.getMonth() + 1)}-${p2(t.getDate())} ${p2(t.getHours())}:${p2(t.getMinutes())} · ${hash.slice(0, 6)}`;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* The Job box's text for a signed spec: the person's own numbered lines (a `~` line per entry of `rules`, or — for a spec
|
|
2118
|
+
* signed before `rules` existed — per '; '-split piece of `rule`), or — for
|
|
2119
|
+
* an older job that saved no job lines — the signed goal sentence as the one line.
|
|
2120
|
+
* @param {any} spec @returns {string}
|
|
2121
|
+
*/
|
|
2122
|
+
export function jobTextFromSpec(spec) {
|
|
2123
|
+
if (Array.isArray(spec?.jobLines) && spec.jobLines.length) {
|
|
2124
|
+
return spec.jobLines.map((/** @type {any} */ l) => [String(l.text), ...(Array.isArray(l.rules) ? l.rules.map((r) => `~ ${r}`) : l.rule ? String(l.rule).split('; ').map((r) => `~ ${r}`) : [])].join('\n')).join('\n');
|
|
2125
|
+
}
|
|
2126
|
+
return typeof spec?.goal === 'string' ? spec.goal : '';
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
/**
|
|
2130
|
+
* The Inputs box's text for a signed spec: its numbered inputs, or — for an older job — `repo: <source>` from the
|
|
2131
|
+
* caller's best knowledge of the source (or empty).
|
|
2132
|
+
* @param {any} spec @param {string} source @returns {string}
|
|
2133
|
+
*/
|
|
2134
|
+
export function inputsTextFromSpec(spec, source) {
|
|
2135
|
+
if (Array.isArray(spec?.inputs) && spec.inputs.length) return spec.inputs.map((/** @type {any} */ i) => `${i.label}: ${i.value}`).join('\n');
|
|
2136
|
+
return source ? `repo: ${source}` : '';
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2101
2139
|
/**
|
|
2102
2140
|
* `<src/panel>/../../jobs` — the repo's `jobs/` directory of signed specs,
|
|
2103
2141
|
* resolved from the bareloop PACKAGE ROOT (this module's own on-disk
|
|
@@ -2180,8 +2218,20 @@ export function getRunJob(runid, opts = {}) {
|
|
|
2180
2218
|
: (typeof jobStart?.model === 'string' ? jobStart.model : null),
|
|
2181
2219
|
);
|
|
2182
2220
|
|
|
2221
|
+
// the Signed row: the run's FIRST job-start, local time (P7)
|
|
2222
|
+
let firstStart = null;
|
|
2223
|
+
if (existsSync(row.spine)) {
|
|
2224
|
+
try { firstStart = parseJsonl(row.spine).records.find((r) => r && typeof r === 'object' && r.type === 'job-start') ?? null; } catch { firstStart = null; }
|
|
2225
|
+
}
|
|
2183
2226
|
/** @param {any} spec @param {boolean} resolved @param {string} resolvedFrom @param {string|null} note */
|
|
2184
2227
|
const fromSpec = (spec, resolved, resolvedFrom, note) => ({
|
|
2228
|
+
// P7: the same plan model the chat's plan bubble is drawn from (src/panel/jobplan.js), read from the SIGNED spec
|
|
2229
|
+
plan: planFromSpec(spec),
|
|
2230
|
+
jobLines: Array.isArray(spec.jobLines) ? spec.jobLines : null,
|
|
2231
|
+
inputs: Array.isArray(spec.inputs) && spec.inputs.length ? spec.inputs : [{ n: 1, label: 'repo', value: sourceDisplay() }],
|
|
2232
|
+
signedAt: typeof firstStart?.ts === 'string' ? firstStart.ts : null,
|
|
2233
|
+
specHash: typeof jobStart?.specHash === 'string' ? jobStart.specHash : null,
|
|
2234
|
+
signed: signedLine(firstStart?.ts, typeof jobStart?.specHash === 'string' ? jobStart.specHash : null),
|
|
2185
2235
|
runid,
|
|
2186
2236
|
job: typeof spec.job === 'string' ? spec.job : row.job,
|
|
2187
2237
|
resolved,
|
|
@@ -2202,6 +2252,7 @@ export function getRunJob(runid, opts = {}) {
|
|
|
2202
2252
|
note,
|
|
2203
2253
|
});
|
|
2204
2254
|
const none = () => ({
|
|
2255
|
+
plan: null, jobLines: null, inputs: [{ n: 1, label: 'repo', value: sourceDisplay() }], signedAt: null, specHash: null, signed: null,
|
|
2205
2256
|
runid,
|
|
2206
2257
|
job: row.job,
|
|
2207
2258
|
resolved: false,
|
|
@@ -2263,6 +2314,10 @@ export function getRunJob(runid, opts = {}) {
|
|
|
2263
2314
|
// (d) the run's own job-start record — narrower, unsigned.
|
|
2264
2315
|
if (jobStart) {
|
|
2265
2316
|
return {
|
|
2317
|
+
plan: null, jobLines: null, inputs: [{ n: 1, label: 'repo', value: sourceDisplay() }],
|
|
2318
|
+
signedAt: typeof jobStart.ts === 'string' ? jobStart.ts : null,
|
|
2319
|
+
specHash: typeof jobStart.specHash === 'string' ? jobStart.specHash : null,
|
|
2320
|
+
signed: signedLine(jobStart.ts, jobStart.specHash),
|
|
2266
2321
|
runid,
|
|
2267
2322
|
job: typeof jobStart.job === 'string' ? jobStart.job : row.job,
|
|
2268
2323
|
resolved: false,
|
|
@@ -2320,28 +2375,38 @@ function signedSpecForRun(row, records) {
|
|
|
2320
2375
|
}
|
|
2321
2376
|
|
|
2322
2377
|
/**
|
|
2323
|
-
* The worker a signed spec ran: provider, baseUrl and model
|
|
2324
|
-
* when the spec names none (the same fallback `resolveWorkerModel` makes at run time).
|
|
2378
|
+
* The worker a signed spec ran: provider, baseUrl and model ('' when an older spec names none: bareloop keeps no default).
|
|
2325
2379
|
* @param {any} spec
|
|
2326
2380
|
* @returns {{provider: string, baseUrl?: string, model: string}}
|
|
2327
2381
|
*/
|
|
2328
2382
|
function workerOfSpec(spec) {
|
|
2329
2383
|
const provider = typeof spec?.provider === 'string' ? spec.provider : '';
|
|
2330
|
-
|
|
2331
|
-
if (!model) { try { model = resolveProvider(provider).tiers.sonnet; } catch { model = ''; } }
|
|
2384
|
+
const model = typeof spec?.model === 'string' ? spec.model : '';
|
|
2332
2385
|
return { provider, ...(typeof spec?.baseUrl === 'string' && spec.baseUrl ? { baseUrl: spec.baseUrl } : {}), model };
|
|
2333
2386
|
}
|
|
2334
2387
|
/** @param {{provider: string, baseUrl?: string, model: string}} w @returns {string} one spelling of "the same worker" */
|
|
2335
2388
|
const workerId = (w) => `${w.provider}\u0000${w.baseUrl ?? ''}\u0000${w.model}`;
|
|
2389
|
+
/**
|
|
2390
|
+
* The model id a reuse card's Model box shows for a row: the spec's own model when the row has a line for it, else the
|
|
2391
|
+
* row's first ticked line; `undefined` when there is no row or no such line.
|
|
2392
|
+
* @param {import('../providerrows.js').KeyRow|null} row
|
|
2393
|
+
* @param {string|null|undefined} specModel
|
|
2394
|
+
* @returns {string|undefined}
|
|
2395
|
+
*/
|
|
2396
|
+
function rowModelOf(row, specModel) {
|
|
2397
|
+
if (!row) return undefined;
|
|
2398
|
+
if (specModel && row.models.some((l) => l.model === specModel)) return specModel;
|
|
2399
|
+
return tickedLines([row])[0]?.line.model;
|
|
2400
|
+
}
|
|
2336
2401
|
/**
|
|
2337
2402
|
* The worker the reuse card's CURRENT Model box names: the Settings row's provider, baseUrl and Name. `null` when the
|
|
2338
2403
|
* box is blank or names no row.
|
|
2339
2404
|
* @param {string} model a Settings Name
|
|
2340
|
-
* @param {{ home?: string }} opts
|
|
2405
|
+
* @param {{ home?: string, provider?: string }} opts `provider` is the key's env name the card picked
|
|
2341
2406
|
* @returns {{provider: string, baseUrl?: string, model: string}|null}
|
|
2342
2407
|
*/
|
|
2343
2408
|
function workerOfChoice(model, opts) {
|
|
2344
|
-
const c = modelChoiceFor(rowsForHome(keysHome(opts.home)), model);
|
|
2409
|
+
const c = modelChoiceFor(rowsForHome(keysHome(opts.home)), { provider: opts.provider, model });
|
|
2345
2410
|
return c ? { provider: c.provider, ...(c.baseUrl ? { baseUrl: c.baseUrl } : {}), model: c.name } : null;
|
|
2346
2411
|
}
|
|
2347
2412
|
|
|
@@ -2424,10 +2489,11 @@ export function reuseLine(record) {
|
|
|
2424
2489
|
* $ cap and the Time cap — start at what the run had. `ok:false` when the run's signed job is not on disk (nothing
|
|
2425
2490
|
* to copy: change the job: Clear the card). `null` when the runid is not listed.
|
|
2426
2491
|
* @param {string} runid
|
|
2427
|
-
* @param {{ home?: string, model?: string }} [opts] `model` = the Model box's current Name (the estimate counts only runs on that worker); absent = the card's own Model
|
|
2492
|
+
* @param {{ home?: string, model?: string, provider?: string }} [opts] `model` = the Model box's current Name (the estimate counts only runs on that worker); absent = the card's own Model
|
|
2428
2493
|
* @returns {{ok: true, origin: {runid: string, job: string}, outcome: string|null, card: Record<string, any>, from: 'signed job',
|
|
2429
2494
|
* locked: readonly string[], open: readonly string[], note: string|null,
|
|
2430
|
-
* specHash: string, workflowKey: string, spec: any, specPath: string,
|
|
2495
|
+
* specHash: string, workflowKey: string, spec: any, specPath: string, specNamesModel: boolean,
|
|
2496
|
+
* specWorker: {provider: string, endpoint: string, label: string},
|
|
2431
2497
|
* trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
|
|
2432
2498
|
*/
|
|
2433
2499
|
export function getStartFrom(runid, opts = {}) {
|
|
@@ -2453,14 +2519,15 @@ export function getStartFrom(runid, opts = {}) {
|
|
|
2453
2519
|
const jobStart = records.find((r) => r && r.type === 'job-start') ?? null;
|
|
2454
2520
|
const card = reuseCardFromSpec(spec, {
|
|
2455
2521
|
model: typeof saved.model === 'string' && saved.model ? saved.model
|
|
2456
|
-
: (findRow(rowsForHome(keysHome(opts.home)), { provider: spec.provider, baseUrl: spec.baseUrl, model: spec.model })
|
|
2522
|
+
: (rowModelOf(findRow(rowsForHome(keysHome(opts.home)), { provider: spec.provider, baseUrl: spec.baseUrl, model: spec.model }), spec.model)
|
|
2457
2523
|
?? (typeof jobStart?.model === 'string' ? jobStart.model : '')),
|
|
2458
2524
|
source: typeof saved.source === 'string' && saved.source ? saved.source : (typeof manifest?.source === 'string' ? manifest.source : (typeof row.patient === 'string' ? row.patient : '')),
|
|
2459
|
-
|
|
2525
|
+
// a card saved by P7 or later keeps the Inputs text the person typed
|
|
2526
|
+
inputs: typeof saved.inputs === 'string' && saved.inputs ? saved.inputs : '',
|
|
2460
2527
|
jobName: row.job,
|
|
2461
2528
|
});
|
|
2462
2529
|
const key = workflowKey(spec);
|
|
2463
|
-
const trackRecord = trackRecordFor(key, { ...opts, worker: workerOfChoice(opts.model ?? card.model, opts) });
|
|
2530
|
+
const trackRecord = trackRecordFor(key, { ...opts, worker: workerOfChoice(opts.model ?? card.model, { ...opts, provider: opts.provider ?? card.provider }) });
|
|
2464
2531
|
return {
|
|
2465
2532
|
ok: true,
|
|
2466
2533
|
origin: { runid, job: row.job },
|
|
@@ -2470,11 +2537,19 @@ export function getStartFrom(runid, opts = {}) {
|
|
|
2470
2537
|
from: 'signed job',
|
|
2471
2538
|
locked: REUSE_LOCKED_FIELDS,
|
|
2472
2539
|
open: REUSE_OPEN_FIELDS,
|
|
2473
|
-
note:
|
|
2540
|
+
note: Array.isArray(spec.jobLines) && spec.jobLines.length ? null : 'older job — numbered lines were not saved',
|
|
2474
2541
|
specHash: signed.specHash,
|
|
2475
2542
|
workflowKey: key,
|
|
2476
2543
|
spec,
|
|
2477
2544
|
specPath: signed.specPath,
|
|
2545
|
+
// an older spec signed before models were required names none: the Resume card then opens its Model box (run-u takes `--model`)
|
|
2546
|
+
specNamesModel: typeof spec.model === 'string' && spec.model !== '',
|
|
2547
|
+
// what resolveWorker will hold a picked line to: the spec's shape, and its endpoint when it names one ('' = any endpoint)
|
|
2548
|
+
specWorker: {
|
|
2549
|
+
provider: String(spec.provider),
|
|
2550
|
+
endpoint: typeof spec.baseUrl === 'string' && spec.baseUrl.trim() !== '' ? endpointOf(String(spec.provider), spec.baseUrl) : '',
|
|
2551
|
+
label: SHAPES.find((x) => x.id === spec.provider)?.label ?? String(spec.provider),
|
|
2552
|
+
},
|
|
2478
2553
|
trackRecord,
|
|
2479
2554
|
line: reuseLine(trackRecord),
|
|
2480
2555
|
};
|
|
@@ -2485,7 +2560,7 @@ export function getStartFrom(runid, opts = {}) {
|
|
|
2485
2560
|
* that has at least one GREEN local run, newest green first. `runid` = that job's newest green run, so the page hands
|
|
2486
2561
|
* it to the existing start-from flow unchanged; `line` is `getStartFrom`'s own estimate line (never a second
|
|
2487
2562
|
* spelling). Local runs only — an imported job keeps its Reuse button in the Import view. $0, read-only.
|
|
2488
|
-
* @param {{ home?: string, model?: string }} [opts] `model` passes through to the estimate line, as start-from's does
|
|
2563
|
+
* @param {{ home?: string, model?: string, provider?: string }} [opts] `model` passes through to the estimate line, as start-from's does
|
|
2489
2564
|
* @returns {{job: string, runid: string, checkType: 'deterministic'|'rubric', line: string}[]}
|
|
2490
2565
|
*/
|
|
2491
2566
|
export function listReuseJobs(opts = {}) {
|
|
@@ -2518,7 +2593,7 @@ export function listReuseJobs(opts = {}) {
|
|
|
2518
2593
|
* The reuse card for a signed spec: locked boxes from the spec through the Job tab's own readers, open boxes from
|
|
2519
2594
|
* the spec's own fence and caps (Source and the saved text from the caller — a spec carries neither).
|
|
2520
2595
|
* @param {any} spec
|
|
2521
|
-
* @param {{model: string, source: string,
|
|
2596
|
+
* @param {{model: string, source: string, inputs?: string, jobName: string}} extra `inputs` = the Inputs text the person saved (else built from the spec's inputs, else `repo: <source>`)
|
|
2522
2597
|
* @returns {Record<string, any>}
|
|
2523
2598
|
*/
|
|
2524
2599
|
function reuseCardFromSpec(spec, extra) {
|
|
@@ -2527,12 +2602,9 @@ function reuseCardFromSpec(spec, extra) {
|
|
|
2527
2602
|
jobName: typeof spec.job === 'string' && spec.job ? spec.job : extra.jobName,
|
|
2528
2603
|
checkType: spec.verdictType === 'soft-green' ? 'rubric' : 'deterministic',
|
|
2529
2604
|
model: typeof spec.model === 'string' && spec.model && !extra.model ? spec.model : extra.model,
|
|
2530
|
-
|
|
2531
|
-
|
|
2605
|
+
jobText: jobTextFromSpec(spec),
|
|
2606
|
+
inputs: extra.inputs || inputsTextFromSpec(spec, extra.source),
|
|
2532
2607
|
destination: writeScope.join(', '),
|
|
2533
|
-
success: successFromSpec(spec) ?? '',
|
|
2534
|
-
guardrails: guardrailsFromSpec(spec) ?? '',
|
|
2535
|
-
judgeExamples: extra.judgeExamples,
|
|
2536
2608
|
capUsd: typeof spec.budgetUsd === 'number' ? spec.budgetUsd : null,
|
|
2537
2609
|
maxWallMs: typeof spec.maxWallMs === 'number' ? spec.maxWallMs : undefined,
|
|
2538
2610
|
};
|
|
@@ -2542,7 +2614,7 @@ function reuseCardFromSpec(spec, extra) {
|
|
|
2542
2614
|
* P5 item 4 — the facts of a spec for an IMPORTED job's view, read by the SAME readers the Job tab uses for a run
|
|
2543
2615
|
* (so an imported job and a run read alike): check type, goal, success checks, guardrails, model, caps.
|
|
2544
2616
|
* @param {any} spec
|
|
2545
|
-
* @returns {{checkType: string, goal: string, success: string, guardrails: string, model: string, budgetUsd: number|null, maxWallMs: number|null}}
|
|
2617
|
+
* @returns {{checkType: string, goal: string, success: string, guardrails: string, plan: ReturnType<typeof planFromSpec>, inputs: any[]|null, model: string, budgetUsd: number|null, maxWallMs: number|null}}
|
|
2546
2618
|
*/
|
|
2547
2619
|
function describeSpec(spec) {
|
|
2548
2620
|
const nr = (/** @type {string|null} */ v) => (typeof v === 'string' && v.length > 0 ? v : 'not recorded');
|
|
@@ -2551,6 +2623,8 @@ function describeSpec(spec) {
|
|
|
2551
2623
|
goal: nr(typeof spec.goal === 'string' ? spec.goal : null),
|
|
2552
2624
|
success: nr(successFromSpec(spec)),
|
|
2553
2625
|
guardrails: nr(guardrailsFromSpec(spec)),
|
|
2626
|
+
plan: planFromSpec(spec),
|
|
2627
|
+
inputs: Array.isArray(spec.inputs) && spec.inputs.length ? spec.inputs : null,
|
|
2554
2628
|
model: nr(typeof spec.model === 'string' ? spec.model : null),
|
|
2555
2629
|
budgetUsd: typeof spec.budgetUsd === 'number' ? spec.budgetUsd : null,
|
|
2556
2630
|
maxWallMs: typeof spec.maxWallMs === 'number' ? spec.maxWallMs : null,
|
|
@@ -2567,7 +2641,7 @@ function describeSpec(spec) {
|
|
|
2567
2641
|
* signs it on this machine at [Sign & run]. Same card, same locked/open boxes as a run's reuse. `null` when no such
|
|
2568
2642
|
* imported job.
|
|
2569
2643
|
* @param {string} id
|
|
2570
|
-
* @param {{ home?: string, model?: string }} [opts] `model` as for {@link getStartFrom}
|
|
2644
|
+
* @param {{ home?: string, model?: string, provider?: string }} [opts] `model` as for {@link getStartFrom}
|
|
2571
2645
|
* @returns {{ok: true, origin: {runid: null, job: string, importId: string}, card: Record<string, any>, from: 'imported job',
|
|
2572
2646
|
* locked: readonly string[], open: readonly string[], note: string|null, specHash: string, workflowKey: string, spec: any,
|
|
2573
2647
|
* specPath: null, trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
|
|
@@ -2586,10 +2660,10 @@ export function getStartFromImport(id, opts = {}) {
|
|
|
2586
2660
|
// the bundle's provider is no longer forced: Model is open on a reuse, so the person's chosen Settings row is the worker.
|
|
2587
2661
|
// The card opens on the row matching the bundle's own provider when there is one, else blank (the page's menu picks).
|
|
2588
2662
|
const rows = rowsForHome(keysHome(opts.home));
|
|
2589
|
-
const named = findRow(rows, { provider: spec.provider, baseUrl: spec.baseUrl, model: spec.model })
|
|
2590
|
-
const card = reuseCardFromSpec(spec, { model: named, source: '',
|
|
2663
|
+
const named = rowModelOf(findRow(rows, { provider: spec.provider, baseUrl: spec.baseUrl, model: spec.model }), spec.model) ?? '';
|
|
2664
|
+
const card = reuseCardFromSpec(spec, { model: named, source: '', jobName: row.job });
|
|
2591
2665
|
const key = workflowKey(spec);
|
|
2592
|
-
const trackRecord = trackRecordFor(key, { ...opts, worker: workerOfChoice(opts.model ?? card.model, opts) });
|
|
2666
|
+
const trackRecord = trackRecordFor(key, { ...opts, worker: workerOfChoice(opts.model ?? card.model, { ...opts, provider: opts.provider ?? card.provider }) });
|
|
2593
2667
|
return {
|
|
2594
2668
|
ok: true,
|
|
2595
2669
|
origin: { runid: null, job: row.job, importId: id },
|