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
package/src/panel/index.html
CHANGED
|
@@ -311,9 +311,28 @@
|
|
|
311
311
|
.imported-box .summary-row{flex-wrap:wrap;}
|
|
312
312
|
.imported-box .sum-val{flex:1 1 180px;min-width:0;overflow-wrap:anywhere;}
|
|
313
313
|
|
|
314
|
-
.map-box{border:1px solid var(--border-strong);padding:10px;
|
|
314
|
+
.map-box{border:1px solid var(--border-strong);padding:10px;margin-top:16px;margin-bottom:14px;background:var(--panel2);}
|
|
315
315
|
.map-box::before{content:"┤ MAP ├";display:block;font-size:11px;color:var(--text-faint);letter-spacing:0.08em;margin:-2px 0 8px 0;}
|
|
316
|
-
|
|
316
|
+
/* ruling 1A: chips as wide as their own text, side by side, wrapping — a name is never truncated */
|
|
317
|
+
.map-chips{display:flex;flex-wrap:wrap;gap:8px 4px;align-items:flex-start;padding-left:1.4em;}
|
|
318
|
+
/* ruling A: a step + its trailing arrow are ONE unit (the arrow never wraps alone); the lead arrow of a wrapped line
|
|
319
|
+
hangs in the container's left padding (absolute, zero layout impact — toggling it can never move a chip) */
|
|
320
|
+
.map-item{position:relative;display:inline-flex;align-items:center;gap:4px;max-width:100%;}
|
|
321
|
+
.map-arrow{color:var(--text-dim);font-size:13px;flex:none;}
|
|
322
|
+
.map-lead{display:none;position:absolute;left:-1.3em;top:50%;transform:translateY(-50%);color:var(--text-dim);font-size:13px;}
|
|
323
|
+
.map-item.line-start .map-lead{display:block;}
|
|
324
|
+
.chip-retry{color:var(--text-dim);font-size:11px;}
|
|
325
|
+
.map-chip{display:inline-block;width:auto;max-width:100%;min-width:0;box-sizing:border-box;font:inherit;font-size:12px;line-height:1.4;text-align:left;padding:4px 8px;border:2px solid var(--border-strong);border-radius:0;background:var(--panel2);color:var(--text-faint);white-space:normal;overflow-wrap:anywhere;}
|
|
326
|
+
button.map-chip{cursor:pointer;}
|
|
327
|
+
.map-chip.s-done{border-color:var(--green);color:var(--text);} .map-chip.s-done .chip-sign{color:#2e7d32;}
|
|
328
|
+
.map-chip.s-running{border-color:var(--amber);color:var(--text);} .map-chip.s-running .chip-sign{color:#b8860b;}
|
|
329
|
+
.map-chip.s-stopped{border-color:var(--red);color:var(--text);} .map-chip.s-stopped .chip-sign{color:var(--red);}
|
|
330
|
+
.map-chip.s-died{border-color:var(--magenta);color:var(--text);} .map-chip.s-died .chip-sign{color:var(--magenta);}
|
|
331
|
+
.map-chip.retry{border-style:dashed;}
|
|
332
|
+
.map-chip.resumed-next{border-right-style:dotted;}
|
|
333
|
+
.map-chip .chip-blocked{color:var(--red);font-size:10px;}
|
|
334
|
+
.map-chip.s-running .chip-sign{animation:chip-pulse 1.2s infinite;}
|
|
335
|
+
@keyframes chip-pulse{0%,100%{opacity:1}50%{opacity:0.3}}
|
|
317
336
|
.map-key{display:flex;gap:14px;font-size:11px;color:var(--text-dim);margin-top:6px;flex-wrap:wrap;}
|
|
318
337
|
|
|
319
338
|
.step-card{position:relative;border:1px solid var(--border);padding:10px;margin-bottom:8px;background:var(--panel2);}
|
|
@@ -613,7 +632,7 @@
|
|
|
613
632
|
animated dots, then a check or a cross with its code-owned reason; chat turns stay in the thread above it, right over the ask box. -->
|
|
614
633
|
<ol class="chat-steps" id="chat-progress-row" data-testid="chat-steps" aria-live="polite" hidden></ol>
|
|
615
634
|
|
|
616
|
-
<!-- build item 1 — a failed/rejected send, revise, sign-prepare,
|
|
635
|
+
<!-- build item 1 — a failed/rejected send, revise (reopen), sign-prepare,
|
|
617
636
|
sign or check-deps POST shows here and STAYS shown: it is set
|
|
618
637
|
only by this tab's own action handlers below and is never
|
|
619
638
|
touched by renderActions()/poll() (which own #chat-card-error
|
|
@@ -623,10 +642,12 @@
|
|
|
623
642
|
|
|
624
643
|
<div class="chat-input-row">
|
|
625
644
|
<label for="chat-msg" style="display:none">Message</label>
|
|
626
|
-
<textarea id="chat-msg" rows="2" placeholder="
|
|
645
|
+
<textarea id="chat-msg" rows="2" placeholder="Answer here when the draft asks you a question…" data-testid="chat-msg"></textarea>
|
|
627
646
|
</div>
|
|
628
647
|
<button class="btn primary wide" type="button" id="chat-main-btn" data-testid="chat-main" disabled>Start drafting</button>
|
|
629
648
|
<div class="hint" id="chat-main-hint" data-testid="chat-main-hint"></div>
|
|
649
|
+
<!-- hamr 2026-10-09: Revise = reopen the boxes, filled as drafted, and draft again. Shown only while a plan waits for the OK. -->
|
|
650
|
+
<button class="btn wide" type="button" id="chat-reopen-btn" data-testid="chat-reopen" hidden>Revise</button>
|
|
630
651
|
<div class="hint">Only your click signs. The chat can't.</div>
|
|
631
652
|
</section>
|
|
632
653
|
|
|
@@ -869,7 +890,6 @@
|
|
|
869
890
|
btn.setAttribute("aria-selected", "true");
|
|
870
891
|
var panel = document.getElementById(btn.getAttribute("aria-controls"));
|
|
871
892
|
if(panel){ panel.hidden = false; panel.classList.add("active"); }
|
|
872
|
-
if(btn.id === "tab-run"){ reflowMap(); }
|
|
873
893
|
});
|
|
874
894
|
});
|
|
875
895
|
}
|
|
@@ -884,33 +904,16 @@
|
|
|
884
904
|
});
|
|
885
905
|
}
|
|
886
906
|
|
|
887
|
-
// ---- step map renderer (
|
|
888
|
-
// buildStepMapSVG — ONE renderer, no second hand-authored map anywhere on this page) ----
|
|
889
|
-
function stepMapColors(state){
|
|
890
|
-
if(state === "done") return {stroke:"var(--green)", textFill:"#2e7d32"};
|
|
891
|
-
if(state === "running") return {stroke:"var(--amber)", textFill:"#b8860b"};
|
|
892
|
-
if(state === "stopped") return {stroke:"var(--red)", textFill:"var(--red)"};
|
|
893
|
-
// died (hamr's ruling B, 2026-09-25): distinct from stopped/red — no
|
|
894
|
-
// verdict was ever reached, so this box never shares the red look.
|
|
895
|
-
if(state === "died") return {stroke:"var(--magenta)", textFill:"var(--magenta)"};
|
|
896
|
-
return {stroke:"var(--border-strong)", textFill:"var(--text-faint)"};
|
|
897
|
-
}
|
|
907
|
+
// ---- step map renderer (buildStepMapHTML below — ONE renderer, no second hand-authored map anywhere on this page) ----
|
|
898
908
|
function escapeXml(s){
|
|
899
909
|
return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");
|
|
900
910
|
}
|
|
901
|
-
var stepMapUidCounter = 0;
|
|
902
|
-
var MAP_MARGIN = 10, MAP_GAP = 28;
|
|
903
|
-
var CHAR_W = 7.2;
|
|
904
|
-
var TITLE_HPAD = 16;
|
|
905
|
-
var MIN_BOX_W = 100;
|
|
906
|
-
var BOX_H = 60, BOX_H_2LINE = 78;
|
|
907
|
-
var ROW_GAP = 70;
|
|
908
911
|
// s.noNumber (F196): a synthetic placeholder box (e.g. "died during
|
|
909
912
|
// planning") is a LABEL, never a real step — it renders without the
|
|
910
913
|
// leading "<n> " step number a real step gets.
|
|
911
914
|
// item 4 (2026-09-25): each step box shows its attempts INLINE, e.g.
|
|
912
915
|
// "attempt 1 [✗] · attempt 2 [✓]" — appended to the box's own title text
|
|
913
|
-
// (the
|
|
916
|
+
// (the chip wraps long text inside itself the same way
|
|
914
917
|
// it already does for a long step id; no third renderer, no new box shape).
|
|
915
918
|
// item 1 (2026-09-27, panel P2 defect): an attempt's own outcome is null
|
|
916
919
|
// in exactly two situations — its window is still open on a LIVE run (the
|
|
@@ -935,9 +938,9 @@
|
|
|
935
938
|
if(g === "✗") return '<span class="glyph-red">' + g + '</span>';
|
|
936
939
|
return g;
|
|
937
940
|
}
|
|
938
|
-
// PLAIN TEXT ONLY — feeds the
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
+
// PLAIN TEXT ONLY — feeds the step map chip label (`stepTitleText`, escaped
|
|
942
|
+
// before it goes into the chip); a colour span here would render as literal
|
|
943
|
+
// "<span>" characters inside the map.
|
|
941
944
|
// {@link glyphSpan} (the P3 colour addition) is used only by the DOM
|
|
942
945
|
// part-card renderer below, never by this function.
|
|
943
946
|
function attemptsInlineText(attempts, boxIsLive){
|
|
@@ -1042,224 +1045,102 @@
|
|
|
1042
1045
|
function boxStatusText(box){
|
|
1043
1046
|
return box.status && box.status.word ? "[" + box.status.sign + "] " + boxStatusWord(box) : boxStatusWord(box);
|
|
1044
1047
|
}
|
|
1045
|
-
|
|
1048
|
+
// Ruling 1A (hamr, 2026-10-09): the step map is a row of CHIPS — each box as wide as its own text, flowing
|
|
1049
|
+
// side by side and wrapping to the next line. Plain HTML + CSS (.map-chips / .map-chip): the browser does the
|
|
1050
|
+
// wrapping, so there is no width arithmetic, no resize observer, and no horizontal scroll at phone width. A name
|
|
1051
|
+
// is never truncated (it wraps inside its chip when it is longer than the line).
|
|
1052
|
+
// Marks that were line styles on the old SVG are now border styles: a retried step has a DASHED border, and a step
|
|
1053
|
+
// whose leg ended there (the run resumed after it) has a DOTTED right edge. The sign + word come from
|
|
1054
|
+
// boxStatusText (src/panel/status.js's table) — the chip shows "[sign title]" and keeps the word in its tooltip.
|
|
1055
|
+
function buildStepMapHTML(steps){
|
|
1046
1056
|
var numIdx = stepNumberIndices(steps);
|
|
1047
|
-
var
|
|
1048
|
-
steps.
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
var
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1057
|
+
var last = steps.length - 1;
|
|
1058
|
+
var body = steps.map(function(s, idx){
|
|
1059
|
+
var sign = s.status && s.status.sign ? s.status.sign : "";
|
|
1060
|
+
var tries = boxRetryTry(s);
|
|
1061
|
+
var cls = "map-chip s-" + String(s.state).replace(/[^a-z-]/g, "") +
|
|
1062
|
+
(tries ? " retry" : "") + (s.resumedNext ? " resumed-next" : "");
|
|
1063
|
+
var label = escapeXml(stepTitleText(numIdx[idx], s));
|
|
1064
|
+
var blocked = s.blocked > 0 ? ' <span class="chip-blocked">' + s.blocked + ' blocked</span>' : '';
|
|
1065
|
+
var retry = tries ? '<span class="chip-retry">\u21bb' + tries + '</span> ' : '';
|
|
1066
|
+
var inner = '[' + retry + '<span class="chip-sign">' + escapeXml(sign) + '</span> ' + label + blocked + ']';
|
|
1067
|
+
var tip = ' title="' + escapeXml(boxStatusText(s)) + '"';
|
|
1068
|
+
var attrs = ' class="' + cls + '"' + tip + (s.resumedNext ? ' data-resumed="1"' : '') + (tries ? ' data-retry="' + tries + '"' : '');
|
|
1069
|
+
var chip = typeof s.partIndex === "number"
|
|
1070
|
+
? '<button type="button"' + attrs + ' data-part-index="' + s.partIndex + '">' + inner + '</button>'
|
|
1071
|
+
: '<span' + attrs + '>' + inner + '</span>';
|
|
1072
|
+
return '<span class="map-item" role="listitem">' +
|
|
1073
|
+
(idx > 0 ? '<span class="map-lead" aria-hidden="true">\u2192</span>' : '') +
|
|
1074
|
+
chip + (idx < last ? '<span class="map-arrow" aria-hidden="true">\u2192</span>' : '') + '</span>';
|
|
1075
|
+
}).join("");
|
|
1076
|
+
return '<div class="map-chips" role="list" aria-label="step map">' + body + '</div>';
|
|
1077
|
+
}
|
|
1078
|
+
// Ruling A: the first chip of each WRAPPED line shows a leading arrow. CSS cannot know which chip starts a line, so
|
|
1079
|
+
// after render/resize this compares each item's offsetTop with the previous item's (same line = same top). The lead
|
|
1080
|
+
// arrow is absolutely positioned (no layout impact), so toggling the class can never reflow a chip — no loop.
|
|
1081
|
+
// Takes anything with offsetTop + classList (real elements, or stubs in tests).
|
|
1082
|
+
function markLineStarts(items){
|
|
1083
|
+
for(var i = 0; i < items.length; i++){
|
|
1084
|
+
var starts = i > 0 && items[i].offsetTop > items[i - 1].offsetTop;
|
|
1085
|
+
if(starts) items[i].classList.add("line-start"); else items[i].classList.remove("line-start");
|
|
1072
1086
|
}
|
|
1073
|
-
if(!line1) return [text.slice(0, maxChars), text.slice(maxChars)];
|
|
1074
|
-
var line2 = words.slice(i).join(' ');
|
|
1075
|
-
return line2 ? [line1, line2] : [line1];
|
|
1076
1087
|
}
|
|
1077
|
-
// ONE shared rule for "does this box draw a retry
|
|
1088
|
+
// ONE shared rule for "does this box draw a retry mark, and what try number" — a multi-attempt
|
|
1078
1089
|
// box (a re-run step, or the fix loop; `run` on an old-shape spine falls in here too, naturally,
|
|
1079
|
-
// never special-cased) gets a dashed
|
|
1080
|
-
//
|
|
1090
|
+
// never special-cased) gets a dashed border; anything else (incl. every no-verdict kind, whose
|
|
1091
|
+
// attempts is already forced to [] upstream) gets 0.
|
|
1081
1092
|
function boxRetryTry(box){
|
|
1082
1093
|
var attempts = Array.isArray(box.attempts) ? box.attempts : [];
|
|
1083
1094
|
return attempts.length > 1 ? attempts.length : 0;
|
|
1084
1095
|
}
|
|
1085
|
-
//
|
|
1086
|
-
// start/control/end, and the "try N" label) is kept inside the box that
|
|
1087
|
-
// owns it — MIN_BOX_W (100) is comfortably wider than 2*RETRY_INSET, so
|
|
1088
|
-
// this never inverts the clamp range.
|
|
1089
|
-
var RETRY_INSET = 4;
|
|
1090
|
-
function clampRetryX(v, boxX, boxW){
|
|
1091
|
-
var lo = boxX + RETRY_INSET, hi = boxX + boxW - RETRY_INSET;
|
|
1092
|
-
return Math.min(hi, Math.max(lo, v));
|
|
1093
|
-
}
|
|
1094
|
-
function buildStepMapSVG(steps, availWidth){
|
|
1095
|
-
var n = steps.length;
|
|
1096
|
-
var naturalW = naturalBoxWidth(steps);
|
|
1097
|
-
var layout = computeMapLayout(n, availWidth, naturalW);
|
|
1098
|
-
var perRow = layout.perRow, boxW = layout.boxW, gap = MAP_GAP;
|
|
1099
|
-
var rows = Math.ceil(n / perRow);
|
|
1100
|
-
var margin = MAP_MARGIN;
|
|
1101
|
-
var numIdx = stepNumberIndices(steps);
|
|
1102
|
-
var titleLines = steps.map(function(s, idx){ return wrapTitleLines(stepTitleText(numIdx[idx], s), boxW); });
|
|
1103
|
-
var maxLines = titleLines.reduce(function(m, l){ return Math.max(m, l.length); }, 1);
|
|
1104
|
-
var boxH = maxLines > 1 ? BOX_H_2LINE : BOX_H;
|
|
1105
|
-
var rowH = boxH + ROW_GAP;
|
|
1106
|
-
var totalW = margin * 2 + perRow * boxW + (perRow - 1) * gap;
|
|
1107
|
-
var totalH = rows * rowH;
|
|
1108
|
-
var uid = "sm" + (stepMapUidCounter++);
|
|
1109
|
-
var body =
|
|
1110
|
-
'<defs>' +
|
|
1111
|
-
'<marker id="' + uid + '-arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">' +
|
|
1112
|
-
'<path d="M0,0 L6,3 L0,6 z" fill="var(--text-dim)"></path></marker>' +
|
|
1113
|
-
'<marker id="' + uid + '-arrow-grey" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">' +
|
|
1114
|
-
'<path d="M0,0 L6,3 L0,6 z" fill="var(--text-faint)"></path></marker>' +
|
|
1115
|
-
'</defs>';
|
|
1116
|
-
steps.forEach(function(s, idx){
|
|
1117
|
-
var row = Math.floor(idx / perRow);
|
|
1118
|
-
var col = idx % perRow;
|
|
1119
|
-
var rtl = (row % 2) === 1;
|
|
1120
|
-
var slotCol = rtl ? (perRow - 1 - col) : col;
|
|
1121
|
-
var x = margin + slotCol * (boxW + gap);
|
|
1122
|
-
var y = 30 + row * rowH;
|
|
1123
|
-
var colors = stepMapColors(s.state);
|
|
1124
|
-
var lines = titleLines[idx];
|
|
1125
|
-
var nextRow = idx < n - 1 ? Math.floor((idx + 1) / perRow) : null;
|
|
1126
|
-
var hasDrop = nextRow !== null && nextRow !== row;
|
|
1127
|
-
var stateY = y + boxH - 18;
|
|
1128
|
-
var partIdxAttr = typeof s.partIndex === "number" ? (' data-part-index="' + s.partIndex + '" style="cursor:pointer;"') : '';
|
|
1129
|
-
body += '<rect x="' + x + '" y="' + y + '" width="' + boxW + '" height="' + boxH +
|
|
1130
|
-
'" rx="0" fill="var(--panel2)" stroke="' + colors.stroke + '" stroke-width="2"' + partIdxAttr + '></rect>';
|
|
1131
|
-
if(s.blocked > 0){
|
|
1132
|
-
body += '<text x="' + (x + boxW - 6) + '" y="' + (y + 12) +
|
|
1133
|
-
'" text-anchor="end" font-size="10" fill="var(--red)">' + s.blocked + ' blocked</text>';
|
|
1134
|
-
}
|
|
1135
|
-
if(lines.length === 1){
|
|
1136
|
-
body += '<text x="' + (x + boxW / 2) + '" y="' + (y + 25) +
|
|
1137
|
-
'" text-anchor="middle" font-size="12" fill="var(--text)">' + escapeXml(lines[0]) + '</text>';
|
|
1138
|
-
} else {
|
|
1139
|
-
body += '<text x="' + (x + boxW / 2) + '" y="' + (y + 17) +
|
|
1140
|
-
'" text-anchor="middle" font-size="12" fill="var(--text)">' + escapeXml(lines[0]) + '</text>';
|
|
1141
|
-
body += '<text x="' + (x + boxW / 2) + '" y="' + (y + 29) +
|
|
1142
|
-
'" text-anchor="middle" font-size="12" fill="var(--text)">' + escapeXml(lines[1]) + '</text>';
|
|
1143
|
-
}
|
|
1144
|
-
body += '<text x="' + (x + boxW / 2) + '" y="' + stateY +
|
|
1145
|
-
'" text-anchor="middle" font-size="10" fill="' + colors.textFill + '">' + escapeXml(boxStatusText(s)) + '</text>';
|
|
1146
|
-
// P2 (2026-09-27 ruling): a running box gets the mockup's pulsing
|
|
1147
|
-
// amber dot (design/panel-mockup.html ~line 1648) — same fixed color
|
|
1148
|
-
// and animation, never re-derived from the state's own stroke color.
|
|
1149
|
-
if(s.state === "running"){
|
|
1150
|
-
body += '<circle cx="' + (x + 20) + '" cy="' + (y + 12) + '" r="4" fill="#b8860b">' +
|
|
1151
|
-
'<animate attributeName="opacity" values="1;0.3;1" dur="1.2s" repeatCount="indefinite"></animate></circle>';
|
|
1152
|
-
}
|
|
1153
|
-
var retryTry = boxRetryTry(s);
|
|
1154
|
-
if(retryTry){
|
|
1155
|
-
// dashed self-loop under the box, same proportions as the design mockup's curve,
|
|
1156
|
-
// scaled to this box's own width. When this same box also carries the row-to-row
|
|
1157
|
-
// snake drop (which leaves from dead-center, x=cx), shift the loop's start away from
|
|
1158
|
-
// center by a fixed pixel offset so the two never begin at the same point.
|
|
1159
|
-
// item 2 (2026-09-27): at a narrow box width, subtracting that same
|
|
1160
|
-
// fixed pixel shift from proportionally-small x-fractions (e.g. the
|
|
1161
|
-
// curve's own endpoint at 2.8% of boxW) can push the coordinate past
|
|
1162
|
-
// the box's own left edge — the fixed shift was sized for desktop
|
|
1163
|
-
// widths and never scaled down with boxW. clampRetryX is the ONE
|
|
1164
|
-
// place every retry x-coordinate (curve start/control/end AND the
|
|
1165
|
-
// "try N" label) is kept inside [x + RETRY_INSET, x + boxW -
|
|
1166
|
-
// RETRY_INSET] — never re-clamped ad hoc per coordinate — so the
|
|
1167
|
-
// whole loop and its label always stay inside the box that owns it,
|
|
1168
|
-
// at every width, while keeping the mockup's proportions untouched
|
|
1169
|
-
// wherever they already fit (every desktop-width box already fits).
|
|
1170
|
-
var retryShift = hasDrop ? 14 : 0;
|
|
1171
|
-
var bottom = y + boxH;
|
|
1172
|
-
var sx = clampRetryX(x + boxW * 0.50 - retryShift, x, boxW);
|
|
1173
|
-
var sy = bottom + 5;
|
|
1174
|
-
var c1x = clampRetryX(x + boxW * 0.278 - retryShift, x, boxW), c1y = bottom + 30;
|
|
1175
|
-
var c2x = clampRetryX(x + boxW * 0.167 - retryShift, x, boxW), c2y = bottom + 30;
|
|
1176
|
-
var ex = clampRetryX(x + boxW * 0.028 - retryShift, x, boxW), ey = bottom + 2;
|
|
1177
|
-
var labelX = clampRetryX(x + boxW * 0.267 - retryShift, x, boxW);
|
|
1178
|
-
body += '<path d="M ' + sx + ' ' + sy + ' C ' + c1x + ' ' + c1y + ', ' + c2x + ' ' + c2y + ', ' + ex + ' ' + ey +
|
|
1179
|
-
'" fill="none" stroke="var(--text-faint)" stroke-width="1.5" stroke-dasharray="3,3" marker-end="url(#' + uid + '-arrow-grey)"></path>';
|
|
1180
|
-
body += '<text x="' + labelX + '" y="' + (bottom + 32) +
|
|
1181
|
-
'" text-anchor="middle" font-size="10" fill="var(--text-faint)">try ' + retryTry + '</text>';
|
|
1182
|
-
}
|
|
1183
|
-
if(idx < n - 1){
|
|
1184
|
-
// P5-R: where a leg ended and the next leg picked up, the connector is DOTTED (and says so) —
|
|
1185
|
-
// one run, one map; the dotted line is the only mark of the resume.
|
|
1186
|
-
var dash = s.resumedNext ? ' stroke-dasharray="2,4" data-resumed="1"' : '';
|
|
1187
|
-
if(nextRow === row){
|
|
1188
|
-
var midY = y + boxH / 2;
|
|
1189
|
-
if(!rtl){
|
|
1190
|
-
var nx = x + boxW + gap;
|
|
1191
|
-
body += '<line x1="' + (x + boxW) + '" y1="' + midY + '" x2="' + (nx - 2) + '" y2="' + midY +
|
|
1192
|
-
'" stroke="var(--text-dim)" stroke-width="2"' + dash + ' marker-end="url(#' + uid + '-arrow)"></line>';
|
|
1193
|
-
} else {
|
|
1194
|
-
var px = x - gap - boxW;
|
|
1195
|
-
body += '<line x1="' + x + '" y1="' + midY + '" x2="' + (px + boxW + 2) + '" y2="' + midY +
|
|
1196
|
-
'" stroke="var(--text-dim)" stroke-width="2"' + dash + ' marker-end="url(#' + uid + '-arrow)"></line>';
|
|
1197
|
-
}
|
|
1198
|
-
} else {
|
|
1199
|
-
var cx = x + boxW / 2;
|
|
1200
|
-
body += '<line x1="' + cx + '" y1="' + (y + boxH) + '" x2="' + cx + '" y2="' + (y + rowH - 2) +
|
|
1201
|
-
'" stroke="var(--text-dim)" stroke-width="2"' + dash + ' marker-end="url(#' + uid + '-arrow)"></line>';
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
});
|
|
1205
|
-
return '<svg width="' + totalW + '" height="' + totalH + '" viewBox="0 0 ' + totalW + ' ' + totalH + '" role="img" aria-label="step map">' + body + '</svg>';
|
|
1206
|
-
}
|
|
1207
|
-
// the line-style key under the MAP (hamr 2026-10-04): only the two line styles, never a sign legend
|
|
1096
|
+
// the line-style key under the MAP (hamr 2026-10-04): only the two border styles, never a sign legend
|
|
1208
1097
|
function stepMapKeyHTML(steps){
|
|
1209
1098
|
var resumed = (steps || []).some(function(b){ return b.resumedNext; });
|
|
1210
1099
|
return '<div class="map-key" data-testid="map-key">' +
|
|
1211
|
-
'<span
|
|
1212
|
-
(resumed ? '<span
|
|
1100
|
+
'<span>\u21bbN = took N tries (dashed box)</span>' +
|
|
1101
|
+
(resumed ? '<span>dotted edge = resumed after</span>' : '') +
|
|
1213
1102
|
'</div>';
|
|
1214
1103
|
}
|
|
1215
|
-
var lastSteps = null;
|
|
1216
|
-
var lastWidth = null;
|
|
1217
|
-
function mapAvailWidth(wrapper){
|
|
1218
|
-
if(!wrapper) return null;
|
|
1219
|
-
var w = wrapper.getBoundingClientRect().width;
|
|
1220
|
-
if(!w) return null;
|
|
1221
|
-
return Math.max(0, w - 20);
|
|
1222
|
-
}
|
|
1223
1104
|
function renderStepMap(steps){
|
|
1224
1105
|
var mount = document.getElementById("map-mount");
|
|
1225
1106
|
var wrapper = document.getElementById("map-box");
|
|
1226
|
-
lastSteps = steps;
|
|
1227
1107
|
if(!steps || !steps.length){
|
|
1228
1108
|
mount.innerHTML = "";
|
|
1229
1109
|
wrapper.hidden = true;
|
|
1230
1110
|
return;
|
|
1231
1111
|
}
|
|
1232
1112
|
wrapper.hidden = false;
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
// build item B: a map box click jumps to the Audit tab, that same part
|
|
1113
|
+
mount.innerHTML = buildStepMapHTML(steps) + stepMapKeyHTML(steps);
|
|
1114
|
+
layoutMap();
|
|
1115
|
+
// build item B: a map chip click jumps to the Audit tab, that same part
|
|
1237
1116
|
// expanded and scrolled into view — same click-through as the card list,
|
|
1238
|
-
// sharing partIndex so the two can never disagree about which row a
|
|
1239
|
-
// maps to.
|
|
1240
|
-
//
|
|
1241
|
-
mount.querySelectorAll("
|
|
1242
|
-
|
|
1243
|
-
var idx = Number(
|
|
1117
|
+
// sharing partIndex so the two can never disagree about which row a chip
|
|
1118
|
+
// maps to. Bound once per render (mount.innerHTML above already wiped any
|
|
1119
|
+
// previous listeners' targets along with the old markup).
|
|
1120
|
+
mount.querySelectorAll(".map-chip[data-part-index]").forEach(function(chip){
|
|
1121
|
+
chip.addEventListener("click", function(){
|
|
1122
|
+
var idx = Number(chip.getAttribute("data-part-index"));
|
|
1244
1123
|
document.getElementById("tab-audit").click();
|
|
1245
1124
|
openAuditPart(idx);
|
|
1246
1125
|
});
|
|
1247
1126
|
});
|
|
1248
1127
|
}
|
|
1249
|
-
function
|
|
1250
|
-
var
|
|
1251
|
-
if(!
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1128
|
+
function layoutMap(){
|
|
1129
|
+
var box = document.getElementById("map-box");
|
|
1130
|
+
if(!box || box.hidden) return;
|
|
1131
|
+
markLineStarts(Array.prototype.slice.call(box.querySelectorAll(".map-item")));
|
|
1132
|
+
}
|
|
1133
|
+
var layoutTimer = null;
|
|
1134
|
+
function layoutMapSoon(){
|
|
1135
|
+
if(layoutTimer) clearTimeout(layoutTimer);
|
|
1136
|
+
layoutTimer = setTimeout(function(){ layoutTimer = null; layoutMap(); }, 60);
|
|
1256
1137
|
}
|
|
1257
1138
|
if(typeof ResizeObserver !== "undefined"){
|
|
1258
|
-
var
|
|
1259
|
-
var
|
|
1260
|
-
if(
|
|
1139
|
+
var mapRo = new ResizeObserver(layoutMapSoon);
|
|
1140
|
+
var mapBoxEl = document.getElementById("map-box");
|
|
1141
|
+
if(mapBoxEl) mapRo.observe(mapBoxEl);
|
|
1261
1142
|
} else {
|
|
1262
|
-
window.addEventListener("resize",
|
|
1143
|
+
window.addEventListener("resize", layoutMapSoon);
|
|
1263
1144
|
}
|
|
1264
1145
|
|
|
1265
1146
|
// F196: a `synthetic` step (e.g. the "died during planning" placeholder
|
|
@@ -3675,6 +3556,7 @@
|
|
|
3675
3556
|
var msgInput = document.getElementById("chat-msg");
|
|
3676
3557
|
var mainBtn = document.getElementById("chat-main-btn");
|
|
3677
3558
|
var mainHint = document.getElementById("chat-main-hint");
|
|
3559
|
+
var reopenBtn = document.getElementById("chat-reopen-btn");
|
|
3678
3560
|
var mainAction = "none";
|
|
3679
3561
|
var lastState = null;
|
|
3680
3562
|
var startOk = false;
|
|
@@ -4127,26 +4009,20 @@
|
|
|
4127
4009
|
}
|
|
4128
4010
|
|
|
4129
4011
|
// hamr's ruling 2026-10-05: ONE wide button below the message box. Its wording, action and enabled state are ONE
|
|
4130
|
-
// pure function of (state, typed-text-empty, reuse); state = {session, startOk, phase, pendingAsk,
|
|
4131
|
-
// signClicked, specHash}. Actions: start | check-deps | send |
|
|
4012
|
+
// pure function of (state, typed-text-empty, reuse); state = {session, startOk, phase, pendingAsk,
|
|
4013
|
+
// signClicked, specHash}. Actions: start | check-deps | send | sign-prepare | sign | none.
|
|
4132
4014
|
function mainButtonFor(st, textEmpty, reuse){
|
|
4133
4015
|
var ask = st.pendingAsk;
|
|
4134
4016
|
var atMenu = !!ask && ask.kind === "menu";
|
|
4135
|
-
var
|
|
4136
|
-
var hint = atMenu ? left + (left === 1 ? " change left" : " changes left") : "";
|
|
4017
|
+
var hint = "";
|
|
4137
4018
|
if(!st.session){
|
|
4138
4019
|
return {text: reuse ? "Sign & run" : "Start drafting", action: "start", disabled: !st.startOk, hint: ""};
|
|
4139
4020
|
}
|
|
4140
4021
|
if(ask && ask.kind === "install-needed"){
|
|
4141
4022
|
return {text: "Check again", action: "check-deps", disabled: false, hint: ""};
|
|
4142
4023
|
}
|
|
4143
|
-
if(ask && !textEmpty){
|
|
4144
|
-
return atMenu
|
|
4145
|
-
? {text: "Send", action: "revise", disabled: left <= 0, hint: hint}
|
|
4146
|
-
: {text: "Send", action: "send", disabled: false, hint: ""};
|
|
4147
|
-
}
|
|
4148
4024
|
if(ask && !atMenu){
|
|
4149
|
-
return {text: "Send", action: "send", disabled:
|
|
4025
|
+
return {text: "Send", action: "send", disabled: textEmpty, hint: ""};
|
|
4150
4026
|
}
|
|
4151
4027
|
if(atMenu || st.phase === "prepared"){
|
|
4152
4028
|
if(st.phase === "prepared" && st.signClicked){
|
|
@@ -4156,16 +4032,21 @@
|
|
|
4156
4032
|
}
|
|
4157
4033
|
return {text: "Sign & run", action: "none", disabled: true, hint: ""};
|
|
4158
4034
|
}
|
|
4159
|
-
// pure: the ask box takes typed text only when the session has a pending ask that accepts it (the plan
|
|
4160
|
-
//
|
|
4035
|
+
// pure: the ask box takes typed text only when the session has a pending ask that accepts it (a question the plan
|
|
4036
|
+
// raised); not with no session, no ask, while the plan waits for the OK (that is Sign & run or Revise, never typed
|
|
4037
|
+
// text), or while waiting on an install
|
|
4161
4038
|
function askBoxOpenFor(st){
|
|
4162
|
-
return !!st.session && !!st.pendingAsk && st.pendingAsk.kind !== "install-needed";
|
|
4039
|
+
return !!st.session && !!st.pendingAsk && st.pendingAsk.kind !== "install-needed" && st.pendingAsk.kind !== "menu";
|
|
4040
|
+
}
|
|
4041
|
+
// pure: Revise shows only while a plan waits for the OK
|
|
4042
|
+
function reviseShownFor(st){
|
|
4043
|
+
return !!st.session && !!st.pendingAsk && st.pendingAsk.kind === "menu";
|
|
4163
4044
|
}
|
|
4164
4045
|
function renderMain(){
|
|
4165
4046
|
var st = {session: sessionLive, startOk: startOk, signClicked: signClickedOnce};
|
|
4166
4047
|
if(sessionLive && lastState){
|
|
4167
4048
|
st.phase = lastState.phase; st.pendingAsk = lastState.pendingAsk;
|
|
4168
|
-
st.
|
|
4049
|
+
st.specHash = lastState.specHash;
|
|
4169
4050
|
}
|
|
4170
4051
|
// hamr 2026-10-05: the ask box is open only while something asks for a reply; otherwise dimmed and emptied
|
|
4171
4052
|
var open = askBoxOpenFor(st);
|
|
@@ -4176,6 +4057,7 @@
|
|
|
4176
4057
|
mainBtn.disabled = b.disabled;
|
|
4177
4058
|
mainAction = b.action;
|
|
4178
4059
|
mainHint.textContent = b.hint;
|
|
4060
|
+
reopenBtn.hidden = !reviseShownFor(st);
|
|
4179
4061
|
fitBox(msgInput);
|
|
4180
4062
|
fitCardBoxes();
|
|
4181
4063
|
}
|
|
@@ -4444,10 +4326,10 @@
|
|
|
4444
4326
|
// build item 1 — the "lost answer" fix. ROOT CAUSE: `renderActions`
|
|
4445
4327
|
// (fed by the every-2s `poll()`) unconditionally overwrites
|
|
4446
4328
|
// `#chat-card-error` on every tick (`errEl.textContent = state.error ||
|
|
4447
|
-
// ""`), including the SAME tick the send/
|
|
4329
|
+
// ""`), including the SAME tick the send/sign handlers below
|
|
4448
4330
|
// used to call `poll()` in their own `.then` — so a refused/failed POST's
|
|
4449
4331
|
// error was wiped before a person could read it (often on the very next
|
|
4450
|
-
// line of the same callback). Compounding it, `send
|
|
4332
|
+
// line of the same callback). Compounding it, `send` cleared
|
|
4451
4333
|
// `msgInput.value` unconditionally, even on failure — so a rejected
|
|
4452
4334
|
// answer vanished from the box with no trace at all. Fixed by: (1) a
|
|
4453
4335
|
// dedicated `#chat-action-error` element only these handlers touch,
|
|
@@ -4508,20 +4390,20 @@
|
|
|
4508
4390
|
}).catch(function(){ chatActionFailed("network error — could not reach the panel server, your text was NOT sent"); });
|
|
4509
4391
|
}
|
|
4510
4392
|
|
|
4511
|
-
|
|
4393
|
+
// hamr 2026-10-09 (as fwdloop's Revise): the draft is ended and the New job card opens with the SAME values in every
|
|
4394
|
+
// box, all editable; Start drafting then drafts fresh from whatever the boxes now say. No typed change request goes
|
|
4395
|
+
// to the model. The ended draft is an Abandon: money already spent stays booked.
|
|
4396
|
+
function doReopen(){
|
|
4512
4397
|
if(!sessionId) return;
|
|
4513
|
-
|
|
4514
|
-
authorPost("/api/author/" + sessionId + "/revise", {text: text}).then(function(r){
|
|
4398
|
+
authorPost("/api/author/" + sessionId + "/abandon", {}).then(function(r){
|
|
4515
4399
|
var o = chatPostOutcome(r);
|
|
4516
|
-
if(o.ok){
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
}
|
|
4522
|
-
poll();
|
|
4523
|
-
}).catch(function(){ chatActionFailed("network error — could not reach the panel server, your text was NOT sent"); });
|
|
4400
|
+
if(!o.ok){ chatActionFailed(o.error); return; }
|
|
4401
|
+
openNewCard();
|
|
4402
|
+
chatActionOk();
|
|
4403
|
+
thread.innerHTML = '<div class="msg system">' + escapeXml("The boxes are open as you drafted them. Edit what you want, then Start drafting. The first draft's cost stays counted.") + "</div>";
|
|
4404
|
+
}).catch(function(){ chatActionFailed("network error — could not reach the panel server, not revised"); });
|
|
4524
4405
|
}
|
|
4406
|
+
reopenBtn.addEventListener("click", doReopen);
|
|
4525
4407
|
|
|
4526
4408
|
function doSign(){
|
|
4527
4409
|
if(!sessionId) return;
|
|
@@ -4553,7 +4435,6 @@
|
|
|
4553
4435
|
if(mainAction === "start") doStart();
|
|
4554
4436
|
else if(mainAction === "check-deps") doCheckDeps();
|
|
4555
4437
|
else if(mainAction === "send") doSend();
|
|
4556
|
-
else if(mainAction === "revise") doRevise();
|
|
4557
4438
|
else if(mainAction === "sign-prepare" || mainAction === "sign") doSign();
|
|
4558
4439
|
});
|
|
4559
4440
|
|
package/src/planrun.js
CHANGED
|
@@ -36,6 +36,7 @@ import { createLadder, STRIKE_LIMIT } from './ladder.js';
|
|
|
36
36
|
import { createTrend, FIX_STRIKE_LIMIT } from './trend.js';
|
|
37
37
|
import { TOOL_MENU, STORE_VERBS, checkMenu } from './job.js';
|
|
38
38
|
import { TOOL_BY_VERB, CTX_TOOLS, createCtxTools, toolAction, PERSONA_TOOLS, strategyFor } from './tools.js';
|
|
39
|
+
import { anySuppressionWarning } from './authoring.js';
|
|
39
40
|
import { createReadShim, readShimArm, readShimStrategy } from './readshim.js';
|
|
40
41
|
import { globToPrefix, redactSecrets, SECRET_PATTERNS } from './validate.js';
|
|
41
42
|
import { validateBridge, loadGate, newestEligibleVersion, reuseEligibility, quarantinesCredit, QUARANTINED_CODE } from './bridges.js';
|
|
@@ -2035,7 +2036,7 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
2035
2036
|
}
|
|
2036
2037
|
const listed = await seedListing(workdir, s.seedRef);
|
|
2037
2038
|
const v = listed.stop === null
|
|
2038
|
-
? validateCloseDecl(job.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: job.verdictType })
|
|
2039
|
+
? validateCloseDecl(job.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: job.verdictType, writeScope: job.writeScope })
|
|
2039
2040
|
: { ok: false, grounded: false, reds: [{ code: 'listing-unreadable', path: 'closeDecl', detail: listed.stop }] };
|
|
2040
2041
|
emit('close-decl', {
|
|
2041
2042
|
genre: job.closeDecl.genre,
|
|
@@ -2546,7 +2547,7 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
2546
2547
|
const readLines = grantedNames.has(TOOL_BY_VERB.read)
|
|
2547
2548
|
? (native && !shimArm.cap ? NATIVE_READ_STRATEGY : '') + readShimStrategy(shimArm)
|
|
2548
2549
|
: '';
|
|
2549
|
-
const system = PERSONA_TOOLS + strategyFor(granted) + readLines;
|
|
2550
|
+
const system = PERSONA_TOOLS + strategyFor(granted) + readLines + anySuppressionWarning(closeStagesOf(job));
|
|
2550
2551
|
/** @param {any} u @returns {{inputTokens: number, outputTokens: number, cacheReadTokens: number, cacheCreationTokens: number}} */
|
|
2551
2552
|
const usageOf = (u) => ({ inputTokens: u?.inputTokens ?? 0, outputTokens: u?.outputTokens ?? 0, cacheReadTokens: u?.cacheReadTokens ?? 0, cacheCreationTokens: u?.cacheCreationTokens ?? 0 });
|
|
2552
2553
|
|
package/src/providers.js
CHANGED
|
@@ -115,6 +115,33 @@ const OPENAI_MODEL_OPTIONS = Object.freeze({
|
|
|
115
115
|
'deepseek-flash': Object.freeze({ legacyMaxTokens: true }),
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Per-MODEL JUDGE-ONLY request options (F192, hamr ruling A 2026-10-09). deepseek-flash
|
|
120
|
+
* thinks by default and bills the reasoning as output: run mv117wde's rubric judge locate
|
|
121
|
+
* truncated at JUDGE_MAX_TOKENS=4000 four times, and a probe measured 1835 of 2409
|
|
122
|
+
* completion tokens as reasoning. The judge extracts quotes and decides nothing, so it
|
|
123
|
+
* runs with thinking off. These are PER-CALL options (bare-agent >=0.49 forwards
|
|
124
|
+
* `thinking` verbatim to `body.thinking`), never constructor options: the judge may be
|
|
125
|
+
* the very same provider instance as the worker (`buildRunnerProviders` reuses it), and
|
|
126
|
+
* the worker is deliberately left as it was. A model with no entry gets nothing; the
|
|
127
|
+
* field is never sent to a model not listed here (OpenAI/Anthropic would reject or
|
|
128
|
+
* misread it). Read through `judgeCallOptions` only — one owner.
|
|
129
|
+
*/
|
|
130
|
+
const OPENAI_JUDGE_CALL_OPTIONS = Object.freeze({
|
|
131
|
+
'deepseek-flash': Object.freeze({ thinking: Object.freeze({ type: 'disabled' }) }),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The per-call options the rubric JUDGE's model calls carry for this provider
|
|
136
|
+
* instance, from the allow-list above. Empty for any provider/model not listed.
|
|
137
|
+
* @param {any} provider a constructed bare-agent provider
|
|
138
|
+
* @returns {Record<string, any>}
|
|
139
|
+
*/
|
|
140
|
+
export function judgeCallOptions(provider) {
|
|
141
|
+
if (!(provider instanceof OpenAIProvider)) return {};
|
|
142
|
+
return OPENAI_JUDGE_CALL_OPTIONS[/** @type {keyof typeof OPENAI_JUDGE_CALL_OPTIONS} */ (provider.model)] ?? {};
|
|
143
|
+
}
|
|
144
|
+
|
|
118
145
|
/**
|
|
119
146
|
* @typedef {object} ProviderTableEntry
|
|
120
147
|
* @property {new (options: any) => any} ctor the bare-agent provider class
|
package/src/trend.js
CHANGED
|
@@ -272,6 +272,7 @@ export function createTrend({ stageOrder = [], limit = FIX_STRIKE_LIMIT, blindCa
|
|
|
272
272
|
* @param {{gap?: string, stage?: string|null, value?: number|null}} o either a
|
|
273
273
|
* raw gap (parsed here) or an already-read `{stage, value}` — never both
|
|
274
274
|
* spellings of the same reading in one call.
|
|
275
|
+
* (A judged stage's value is its distinct red COUNT, supplied by the caller.)
|
|
275
276
|
* @returns {{iteration: number, stage: string, value: number|null, improved: boolean,
|
|
276
277
|
* comparable: boolean, noProgress: number, limit: number, stageIndex: number}}
|
|
277
278
|
*/
|