loki-mode 7.19.1 → 7.19.3
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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +282 -0
- package/autonomy/config.example.yaml +26 -0
- package/autonomy/lib/proof-generator.py +20 -1
- package/autonomy/lib/proof-template.html +211 -16
- package/autonomy/run.sh +55 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/SHAREABLE-PROOF-PLAN.md +194 -0
- package/docs/UNCERTAINTY-ESCALATION-PLAN.md +396 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/skills/quality-gates.md +85 -0
|
@@ -89,30 +89,57 @@
|
|
|
89
89
|
.brand { font-weight: 600; color: var(--text); letter-spacing: 0.2px; }
|
|
90
90
|
.brand .dot { color: var(--accent); }
|
|
91
91
|
|
|
92
|
-
/* HERO */
|
|
92
|
+
/* HERO (branded proof card) */
|
|
93
93
|
.hero {
|
|
94
|
-
|
|
94
|
+
position: relative; overflow: hidden;
|
|
95
|
+
background:
|
|
96
|
+
radial-gradient(900px 300px at 100% -40%, var(--accent-soft), transparent 60%),
|
|
97
|
+
linear-gradient(180deg, var(--panel-2), var(--panel));
|
|
95
98
|
border: 1px solid var(--border);
|
|
96
|
-
border-radius:
|
|
99
|
+
border-radius: 16px;
|
|
97
100
|
padding: 28px;
|
|
98
101
|
margin-bottom: 22px;
|
|
99
102
|
}
|
|
103
|
+
.hero-brand {
|
|
104
|
+
display: flex; align-items: center; gap: 9px; margin-bottom: 18px;
|
|
105
|
+
color: var(--text);
|
|
106
|
+
}
|
|
107
|
+
.hero-brand .mark { display: inline-flex; color: var(--accent); }
|
|
108
|
+
.hero-brand .name { font-weight: 650; font-size: 15px; letter-spacing: 0.2px; }
|
|
109
|
+
.hero-brand .by { color: var(--faint); font-size: 12px; }
|
|
110
|
+
.hero-brand .by b { color: var(--muted); font-weight: 600; }
|
|
100
111
|
.hero h1 {
|
|
101
112
|
font-size: 26px; line-height: 1.25; margin: 0 0 14px;
|
|
102
113
|
font-weight: 650; letter-spacing: -0.2px;
|
|
103
114
|
}
|
|
104
115
|
.hero .sub { color: var(--muted); font-size: 14px; margin: 0; }
|
|
105
|
-
.hero-
|
|
116
|
+
.hero-stats {
|
|
117
|
+
display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
118
|
+
gap: 1px; margin-top: 20px; background: var(--border);
|
|
119
|
+
border: 1px solid var(--border); border-radius: 11px; overflow: hidden;
|
|
120
|
+
}
|
|
121
|
+
.hero-stat { background: rgba(20,23,30,0.72); padding: 13px 15px; }
|
|
122
|
+
.hero-stat .hk { color: var(--faint); font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px; }
|
|
123
|
+
.hero-stat .hv { font-size: 19px; font-family: var(--mono); margin-top: 5px; color: var(--text); word-break: break-word; }
|
|
124
|
+
.hero-stat .hv small { font-size: 12px; color: var(--muted); font-family: var(--sans); }
|
|
125
|
+
.hero-stat .hv.ok { color: var(--green); }
|
|
126
|
+
.hero-actions { margin-top: 18px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
|
106
127
|
|
|
107
128
|
/* Buttons */
|
|
108
129
|
.btn {
|
|
109
|
-
display: inline-
|
|
130
|
+
display: inline-flex; align-items: center; gap: 7px;
|
|
131
|
+
padding: 9px 15px; border-radius: 9px;
|
|
110
132
|
font-size: 14px; font-weight: 550; border: 1px solid var(--border);
|
|
111
133
|
background: var(--panel-2); color: var(--text); cursor: pointer;
|
|
112
134
|
}
|
|
113
135
|
.btn.primary { background: var(--accent); border-color: var(--accent); color: #0b0d12; }
|
|
114
136
|
.btn.primary:hover { text-decoration: none; filter: brightness(1.06); }
|
|
115
137
|
.btn:hover { text-decoration: none; border-color: var(--accent); }
|
|
138
|
+
.btn .ico { display: inline-flex; pointer-events: none; }
|
|
139
|
+
.share-sep { flex: 1 1 auto; }
|
|
140
|
+
.share-row { display: inline-flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
141
|
+
.share-row .lbl { color: var(--faint); font-size: 12px; margin-right: 2px; }
|
|
142
|
+
.btn.share { padding: 8px 12px; font-size: 13px; }
|
|
116
143
|
|
|
117
144
|
/* Sections */
|
|
118
145
|
.section { margin-top: 26px; }
|
|
@@ -250,17 +277,29 @@
|
|
|
250
277
|
.hide { display: none !important; }
|
|
251
278
|
</style>
|
|
252
279
|
</head>
|
|
253
|
-
<body>
|
|
280
|
+
<body data-share-buttons="__PROOF_SHARE_BUTTONS__">
|
|
254
281
|
<div class="wrap">
|
|
255
282
|
<div class="topbar">
|
|
256
283
|
<span class="brand">Loki Mode<span class="dot">.</span> proof of run</span>
|
|
257
284
|
<span id="runIdTop"></span>
|
|
258
285
|
</div>
|
|
259
286
|
|
|
260
|
-
<!-- HERO -->
|
|
287
|
+
<!-- HERO (branded proof card; rendered client-side from the redacted dict) -->
|
|
261
288
|
<section class="hero">
|
|
289
|
+
<div class="hero-brand">
|
|
290
|
+
<span class="mark" aria-hidden="true">
|
|
291
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
|
|
292
|
+
<path d="M12 2L4 6.5v6.2c0 4.6 3.3 8 8 9.3 4.7-1.3 8-4.7 8-9.3V6.5L12 2z" fill="currentColor" opacity="0.16"/>
|
|
293
|
+
<path d="M12 2L4 6.5v6.2c0 4.6 3.3 8 8 9.3 4.7-1.3 8-4.7 8-9.3V6.5L12 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
|
294
|
+
<path d="M9 12.2l2.1 2.1L15.4 10" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
|
|
295
|
+
</svg>
|
|
296
|
+
</span>
|
|
297
|
+
<span class="name">Loki Mode</span>
|
|
298
|
+
<span class="by">by <b>Autonomi</b></span>
|
|
299
|
+
</div>
|
|
262
300
|
<h1 id="heroLine"></h1>
|
|
263
301
|
<p class="sub" id="heroSub"></p>
|
|
302
|
+
<div class="hero-stats" id="heroStats"></div>
|
|
264
303
|
<div class="hero-actions" id="heroActions"></div>
|
|
265
304
|
</section>
|
|
266
305
|
|
|
@@ -445,6 +484,8 @@
|
|
|
445
484
|
if (prov || model) sub.push("via " + esc(prov) + (model ? " / " + esc(model) : ""));
|
|
446
485
|
document.getElementById("heroSub").innerHTML = sub.join(" · ");
|
|
447
486
|
|
|
487
|
+
renderHeroStats(p, usdRaw, fc, cs);
|
|
488
|
+
|
|
448
489
|
var actions = document.getElementById("heroActions");
|
|
449
490
|
var html = "";
|
|
450
491
|
var deployed = g(p, "deployment.deployed_url", null);
|
|
@@ -456,7 +497,101 @@
|
|
|
456
497
|
if (hasDiffs(p)) {
|
|
457
498
|
html += '<a class="btn" href="#secDiff">View the diff</a>';
|
|
458
499
|
}
|
|
459
|
-
|
|
500
|
+
if (!html) {
|
|
501
|
+
html = '<span class="note">No live URL recorded for this run.</span>';
|
|
502
|
+
}
|
|
503
|
+
// Share row. Honors the LOKI_PROOF_SHARE_BUTTONS opt-out: the generator sets
|
|
504
|
+
// body[data-share-buttons] to "0" when the operator disabled share buttons,
|
|
505
|
+
// in which case the entire share row is omitted (the actions show only the
|
|
506
|
+
// live-URL line, byte-equivalent intent to pre-share-row behavior).
|
|
507
|
+
// Otherwise: the X/LinkedIn buttons appear only when a public URL exists
|
|
508
|
+
// (default local runs have public_url=null, so they degrade to copy-only).
|
|
509
|
+
// The intent URLs are NOT in this markup: they are assembled at click time
|
|
510
|
+
// in wireShare() from data-share + the parsed proof, so no literal social
|
|
511
|
+
// URL ever appears in the static page (keeps the self-containment guard
|
|
512
|
+
// green).
|
|
513
|
+
var shareEnabled = (document.body.getAttribute("data-share-buttons") !== "0");
|
|
514
|
+
if (!shareEnabled) {
|
|
515
|
+
actions.innerHTML = html;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
var pub = g(p, "deployment.public_url", null);
|
|
519
|
+
var hasPub = !!pub && !isLocalUrl(pub);
|
|
520
|
+
var share = '<span class="share-sep"></span><span class="share-row">';
|
|
521
|
+
share += '<span class="lbl">Share</span>';
|
|
522
|
+
if (hasPub) {
|
|
523
|
+
share += '<button class="btn share" type="button" data-share="x" title="Share on X">' +
|
|
524
|
+
'<span class="ico" aria-hidden="true">' + icoX() + "</span>X</button>";
|
|
525
|
+
share += '<button class="btn share" type="button" data-share="linkedin" title="Share on LinkedIn">' +
|
|
526
|
+
'<span class="ico" aria-hidden="true">' + icoIn() + "</span>LinkedIn</button>";
|
|
527
|
+
}
|
|
528
|
+
share += '<button class="btn share" type="button" data-share="copy" title="Copy link to this proof">' +
|
|
529
|
+
'<span class="ico" aria-hidden="true">' + icoLink() + "</span>Copy link</button>";
|
|
530
|
+
share += "</span>";
|
|
531
|
+
actions.innerHTML = html + share;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function renderHeroStats(p, usdRaw, fc, cs) {
|
|
535
|
+
var el = document.getElementById("heroStats");
|
|
536
|
+
if (!el) return;
|
|
537
|
+
var cells = [];
|
|
538
|
+
// Built: files changed + where the spec came from.
|
|
539
|
+
var src = String(g(p, "spec.source", "") || "");
|
|
540
|
+
var srcLabel = src ? srcLabelFor(src) : "spec";
|
|
541
|
+
cells.push(["Built",
|
|
542
|
+
fc + " file" + (fc === 1 ? "" : "s") + " changed",
|
|
543
|
+
"from " + esc(srcLabel), ""]);
|
|
544
|
+
// Verified: council ratio (preferred) or quality-gate ratio.
|
|
545
|
+
var vMain, vSub;
|
|
546
|
+
if (cs) {
|
|
547
|
+
vMain = cs.count + "-of-" + cs.total + " approved";
|
|
548
|
+
vSub = "review council";
|
|
549
|
+
} else {
|
|
550
|
+
var gp = num(g(p, "quality_gates.passed", 0));
|
|
551
|
+
var gt = num(g(p, "quality_gates.total", 0));
|
|
552
|
+
if (gt > 0) { vMain = gp + "-of-" + gt + " gates"; vSub = "quality gates"; }
|
|
553
|
+
else { vMain = "not recorded"; vSub = "verification"; }
|
|
554
|
+
}
|
|
555
|
+
cells.push(["Verified", vMain, esc(vSub), cs ? "ok" : ""]);
|
|
556
|
+
// Cost: never render a zero-dollar total when cost was not collected.
|
|
557
|
+
if (usdRaw !== null) cells.push(["Cost", fmtUsd(usdRaw), "total for this run", ""]);
|
|
558
|
+
else cells.push(["Cost", "not recorded", "no cost data", ""]);
|
|
559
|
+
// Duration.
|
|
560
|
+
var dur = g(p, "wall_clock_sec", null);
|
|
561
|
+
if (dur !== null) cells.push(["Duration", fmtDuration(dur), "wall clock", ""]);
|
|
562
|
+
var html = "";
|
|
563
|
+
for (var i = 0; i < cells.length; i++) {
|
|
564
|
+
html += '<div class="hero-stat"><div class="hk">' + cells[i][0] +
|
|
565
|
+
'</div><div class="hv ' + cells[i][3] + '">' + cells[i][1] +
|
|
566
|
+
(cells[i][2] ? ' <small>' + cells[i][2] + "</small>" : "") + "</div></div>";
|
|
567
|
+
}
|
|
568
|
+
el.innerHTML = html;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Human label for the spec source. owner/repo#123 -> "GitHub issue
|
|
572
|
+
// owner/repo#123"; codebase-analysis -> "codebase analysis"; otherwise the
|
|
573
|
+
// raw (already-redacted) source string. esc() is applied by the caller.
|
|
574
|
+
function srcLabelFor(src) {
|
|
575
|
+
if (/^[\w.\-]+\/[\w.\-]+#\d+$/.test(src)) return "GitHub issue " + src;
|
|
576
|
+
if (src === "codebase-analysis") return "codebase analysis";
|
|
577
|
+
return src;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Inline brand-tinted glyphs for the share buttons. Pure <svg><path>, no
|
|
581
|
+
// external refs, no <image>/<img>, no src=. pointer-events are disabled in
|
|
582
|
+
// CSS so clicks land on the button (wireShare reads data-share).
|
|
583
|
+
function icoX() {
|
|
584
|
+
return '<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor">' +
|
|
585
|
+
'<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.66l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231 5.45-6.231zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77z"/></svg>';
|
|
586
|
+
}
|
|
587
|
+
function icoIn() {
|
|
588
|
+
return '<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor">' +
|
|
589
|
+
'<path d="M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.34V9h3.42v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.06 2.06 0 110-4.13 2.06 2.06 0 010 4.13zM7.12 20.45H3.55V9h3.57v11.45zM22.22.5H1.77C.79.5 0 1.28 0 2.24v19.52C0 22.72.79 23.5 1.77 23.5h20.45c.98 0 1.78-.78 1.78-1.74V2.24C24 1.28 23.2.5 22.22.5z"/></svg>';
|
|
590
|
+
}
|
|
591
|
+
function icoLink() {
|
|
592
|
+
return '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
|
|
593
|
+
'<path d="M10 13a5 5 0 007.07 0l3-3a5 5 0 00-7.07-7.07l-1.5 1.5"/>' +
|
|
594
|
+
'<path d="M14 11a5 5 0 00-7.07 0l-3 3a5 5 0 007.07 7.07l1.5-1.5"/></svg>';
|
|
460
595
|
}
|
|
461
596
|
|
|
462
597
|
function renderTier1(p) {
|
|
@@ -759,21 +894,80 @@
|
|
|
759
894
|
'</code><button class="copy" data-copy="' + esc(text) + '">Copy</button></div>';
|
|
760
895
|
}
|
|
761
896
|
|
|
897
|
+
// Shared clipboard write (navigator.clipboard with execCommand fallback).
|
|
898
|
+
// Used by both the command-copy buttons (wireCopy) and the share copy-link
|
|
899
|
+
// button (wireShare). onDone runs after a best-effort copy.
|
|
900
|
+
function copyText(text, onDone) {
|
|
901
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
902
|
+
navigator.clipboard.writeText(text).then(onDone, function () {});
|
|
903
|
+
} else {
|
|
904
|
+
try {
|
|
905
|
+
var ta = document.createElement("textarea");
|
|
906
|
+
ta.value = text; document.body.appendChild(ta); ta.select();
|
|
907
|
+
document.execCommand("copy"); document.body.removeChild(ta); onDone();
|
|
908
|
+
} catch (err) {}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Flash a transient label on a button, then restore its original content.
|
|
913
|
+
function flashBtn(btn, label) {
|
|
914
|
+
if (!btn) return;
|
|
915
|
+
var o = btn.innerHTML;
|
|
916
|
+
btn.textContent = label;
|
|
917
|
+
setTimeout(function () { btn.innerHTML = o; }, 1200);
|
|
918
|
+
}
|
|
919
|
+
|
|
762
920
|
function wireCopy() {
|
|
763
921
|
document.addEventListener("click", function (e) {
|
|
764
922
|
var t = e.target;
|
|
765
923
|
if (!t || !t.getAttribute || t.getAttribute("data-copy") === null) return;
|
|
766
924
|
var text = t.getAttribute("data-copy");
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
925
|
+
copyText(text, function () { flashBtn(t, "Copied"); });
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Delegated share handler. Mirrors wireCopy: data-share carries only the KIND
|
|
930
|
+
// ("x" | "linkedin" | "copy"), never a URL. The intent/share URLs are built
|
|
931
|
+
// here at click time by string concatenation so NO literal social URL appears
|
|
932
|
+
// anywhere in the static page. public_url comes from the parsed proof closed
|
|
933
|
+
// over by this IIFE; the hook comes from the og:description meta already in
|
|
934
|
+
// the page. The host strings are intentionally split across "+" so the source
|
|
935
|
+
// text contains no contiguous "https://<host>" or bare host literal.
|
|
936
|
+
function wireShare(proof) {
|
|
937
|
+
function publicUrl() {
|
|
938
|
+
var u = g(proof, "deployment.public_url", null);
|
|
939
|
+
return (u && !isLocalUrl(u)) ? String(u) : "";
|
|
940
|
+
}
|
|
941
|
+
function shareHook() {
|
|
942
|
+
var m = document.querySelector('meta[property="og:description"]');
|
|
943
|
+
var c = m ? (m.getAttribute("content") || "") : "";
|
|
944
|
+
return c || "Built and verified autonomously by Loki Mode";
|
|
945
|
+
}
|
|
946
|
+
document.addEventListener("click", function (e) {
|
|
947
|
+
var t = e.target && e.target.closest ? e.target.closest("[data-share]") : null;
|
|
948
|
+
if (!t) return;
|
|
949
|
+
var kind = t.getAttribute("data-share");
|
|
950
|
+
var url = publicUrl();
|
|
951
|
+
if (kind === "copy") {
|
|
952
|
+
// Copy the public link when present, else fall back to the hook text.
|
|
953
|
+
copyText(url || shareHook(), function () { flashBtn(t, "Copied"); });
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
// X / LinkedIn require a public URL. Without one, do nothing (the buttons
|
|
957
|
+
// are also hidden in that case): never emit a broken url=.
|
|
958
|
+
if (!url) return;
|
|
959
|
+
var target;
|
|
960
|
+
if (kind === "x") {
|
|
961
|
+
target = "https" + "://twitter" + ".com/intent/tweet?text=" +
|
|
962
|
+
encodeURIComponent(shareHook()) + "&url=" + encodeURIComponent(url);
|
|
963
|
+
} else if (kind === "linkedin") {
|
|
964
|
+
// LinkedIn ignores prefilled text; URL only.
|
|
965
|
+
target = "https" + "://www." + "linkedin" + ".com/sharing/share-offsite/?url=" +
|
|
966
|
+
encodeURIComponent(url);
|
|
770
967
|
} else {
|
|
771
|
-
|
|
772
|
-
var ta = document.createElement("textarea");
|
|
773
|
-
ta.value = text; document.body.appendChild(ta); ta.select();
|
|
774
|
-
document.execCommand("copy"); document.body.removeChild(ta); done();
|
|
775
|
-
} catch (err) {}
|
|
968
|
+
return;
|
|
776
969
|
}
|
|
970
|
+
window.open(target, "_blank", "noopener,noreferrer");
|
|
777
971
|
});
|
|
778
972
|
}
|
|
779
973
|
|
|
@@ -788,6 +982,7 @@
|
|
|
788
982
|
|
|
789
983
|
var proof = parseProof();
|
|
790
984
|
wireCopy();
|
|
985
|
+
wireShare(proof);
|
|
791
986
|
if (!proof || typeof proof !== "object") {
|
|
792
987
|
renderError();
|
|
793
988
|
return;
|
package/autonomy/run.sh
CHANGED
|
@@ -124,6 +124,26 @@
|
|
|
124
124
|
# LOKI_NOTIFICATIONS - Enable desktop notifications (default: true)
|
|
125
125
|
# LOKI_NOTIFICATION_SOUND - Play sound with notifications (default: true)
|
|
126
126
|
#
|
|
127
|
+
# Uncertainty-Gated Escalation (v7.19.2, default-on):
|
|
128
|
+
# LOKI_UNCERTAINTY_ESCALATION - Master on/off for proactive stuck-escalation (default: 1; set 0 to
|
|
129
|
+
# disable; byte-identical when off). Decision lives in
|
|
130
|
+
# completion-council.sh (uncertainty_should_escalate); action in run.sh.
|
|
131
|
+
# NOTE: AUTONOMY_MODE defaults to "perpetual"; in perpetual mode PAUSE
|
|
132
|
+
# is auto-cleared by check_human_intervention, so escalation degrades
|
|
133
|
+
# to notify-only (notification fires, run does NOT halt).
|
|
134
|
+
# LOKI_UNCERTAINTY_ROUNDS - Consecutive rounds where >=2 of 3 proxies must co-occur before
|
|
135
|
+
# escalating (default: 2; recommended range 2-3). Debounces transient
|
|
136
|
+
# noise: a single hot proxy never escalates alone.
|
|
137
|
+
# LOKI_UNCERTAINTY_NOCHANGE_MIN - Proxy 1 threshold: consecutive_no_change value that marks p1 hot.
|
|
138
|
+
# (default: COUNCIL_STAGNATION_LIMIT - 1, i.e. one below the circuit-
|
|
139
|
+
# breaker limit so escalation fires before the breaker ends the run).
|
|
140
|
+
# Floored at 1 at runtime.
|
|
141
|
+
# LOKI_UNCERTAINTY_SPLIT_ROUNDS - Proxy 3 threshold: number of consecutive trailing council verdicts
|
|
142
|
+
# that must be REJECTED-with-approver (split) to mark p3 hot
|
|
143
|
+
# (default: 2). Between council votes p3 may be stale; it is always
|
|
144
|
+
# fresh when proxy 1 is hot because proxy 1 hot forces a circuit-
|
|
145
|
+
# breaker vote that refreshes verdicts.
|
|
146
|
+
#
|
|
127
147
|
# Human Intervention (Auto-Claude pattern):
|
|
128
148
|
# PAUSE file: touch .loki/PAUSE - pauses after current session
|
|
129
149
|
# HUMAN_INPUT.md: echo "instructions" > .loki/HUMAN_INPUT.md
|
|
@@ -286,6 +306,10 @@ parse_simple_yaml() {
|
|
|
286
306
|
set_from_yaml "$file" "completion.council.check_interval" "LOKI_COUNCIL_CHECK_INTERVAL"
|
|
287
307
|
set_from_yaml "$file" "completion.council.min_iterations" "LOKI_COUNCIL_MIN_ITERATIONS"
|
|
288
308
|
set_from_yaml "$file" "completion.council.stagnation_limit" "LOKI_COUNCIL_STAGNATION_LIMIT"
|
|
309
|
+
set_from_yaml "$file" "completion.uncertainty.escalation" "LOKI_UNCERTAINTY_ESCALATION"
|
|
310
|
+
set_from_yaml "$file" "completion.uncertainty.rounds" "LOKI_UNCERTAINTY_ROUNDS"
|
|
311
|
+
set_from_yaml "$file" "completion.uncertainty.nochange_min" "LOKI_UNCERTAINTY_NOCHANGE_MIN"
|
|
312
|
+
set_from_yaml "$file" "completion.uncertainty.split_rounds" "LOKI_UNCERTAINTY_SPLIT_ROUNDS"
|
|
289
313
|
|
|
290
314
|
# Model
|
|
291
315
|
set_from_yaml "$file" "model.prompt_repetition" "LOKI_PROMPT_REPETITION"
|
|
@@ -428,6 +452,10 @@ parse_yaml_with_yq() {
|
|
|
428
452
|
"completion.council.check_interval:LOKI_COUNCIL_CHECK_INTERVAL"
|
|
429
453
|
"completion.council.min_iterations:LOKI_COUNCIL_MIN_ITERATIONS"
|
|
430
454
|
"completion.council.stagnation_limit:LOKI_COUNCIL_STAGNATION_LIMIT"
|
|
455
|
+
"completion.uncertainty.escalation:LOKI_UNCERTAINTY_ESCALATION"
|
|
456
|
+
"completion.uncertainty.rounds:LOKI_UNCERTAINTY_ROUNDS"
|
|
457
|
+
"completion.uncertainty.nochange_min:LOKI_UNCERTAINTY_NOCHANGE_MIN"
|
|
458
|
+
"completion.uncertainty.split_rounds:LOKI_UNCERTAINTY_SPLIT_ROUNDS"
|
|
431
459
|
"model.prompt_repetition:LOKI_PROMPT_REPETITION"
|
|
432
460
|
"model.confidence_routing:LOKI_CONFIDENCE_ROUTING"
|
|
433
461
|
"model.autonomy_mode:LOKI_AUTONOMY_MODE"
|
|
@@ -12390,6 +12418,33 @@ if __name__ == "__main__":
|
|
|
12390
12418
|
council_track_iteration "$log_file"
|
|
12391
12419
|
fi
|
|
12392
12420
|
|
|
12421
|
+
# Uncertainty-gated escalation (v7.19.2, Slice B action).
|
|
12422
|
+
# The decision lives in completion-council.sh:uncertainty_should_escalate
|
|
12423
|
+
# (pure, debounced once-per-stuck-episode, knob-first on
|
|
12424
|
+
# LOKI_UNCERTAINTY_ESCALATION). This block only ACTS when the function
|
|
12425
|
+
# returns rc 0. The type guard keeps it a silent no-op if the decision
|
|
12426
|
+
# function is not present (byte-identical when the feature is absent/off).
|
|
12427
|
+
if type uncertainty_should_escalate &>/dev/null && uncertainty_should_escalate; then
|
|
12428
|
+
log_error "[Uncertainty] Escalating to human: >=2 of 3 stuck-signals co-occurred for N rounds (no-change / oscillation / council-split). PAUSE written; handoff saved."
|
|
12429
|
+
log_warn "[Uncertainty] To opt out of proactive escalation: set LOKI_UNCERTAINTY_ESCALATION=0"
|
|
12430
|
+
# Structured handoff doc before the bare PAUSE (mirrors GATE precedent).
|
|
12431
|
+
write_structured_handoff "uncertainty_escalation"
|
|
12432
|
+
notify_intervention_needed "Uncertainty escalation: >=2 of 3 stuck-signals co-occurred for N rounds"
|
|
12433
|
+
# Marker file for dashboard / external consumers. Empty touch has no
|
|
12434
|
+
# partial-write window, so atomic temp+mv is not required here.
|
|
12435
|
+
mkdir -p "${TARGET_DIR:-.}/.loki/signals"
|
|
12436
|
+
touch "${TARGET_DIR:-.}/.loki/signals/UNCERTAINTY_ESCALATION"
|
|
12437
|
+
# PAUSE is consumed by check_human_intervention: it halts in
|
|
12438
|
+
# non-perpetual mode; in perpetual mode it auto-clears + notifies.
|
|
12439
|
+
# That degrade is free; we add no consumer logic here.
|
|
12440
|
+
touch "${TARGET_DIR:-.}/.loki/PAUSE"
|
|
12441
|
+
# Perpetual-mode honesty: detect with the SAME vars the existing PAUSE
|
|
12442
|
+
# consumer uses (run.sh check_human_intervention), print-only.
|
|
12443
|
+
if [ "$AUTONOMY_MODE" = "perpetual" ] || [ "$PERPETUAL_MODE" = "true" ]; then
|
|
12444
|
+
log_warn "[Uncertainty] Perpetual mode: PAUSE will be auto-cleared; this is notify-only and will NOT halt the run."
|
|
12445
|
+
fi
|
|
12446
|
+
fi
|
|
12447
|
+
|
|
12393
12448
|
# Check for success - ONLY stop on explicit completion promise
|
|
12394
12449
|
# There's never a "complete" product - always improvements, bugs, features
|
|
12395
12450
|
if [ $exit_code -eq 0 ]; then
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Shareable Proof-of-Run: Implementation Plan (v7.19.3)
|
|
2
|
+
|
|
3
|
+
Architect plan for adding opt-in shareability to the proof-of-run artifact
|
|
4
|
+
without breaking the v7.18.2 zero-egress posture. Design only.
|
|
5
|
+
|
|
6
|
+
## 1. Verified current state
|
|
7
|
+
|
|
8
|
+
All claims read from source.
|
|
9
|
+
|
|
10
|
+
**Generator** `autonomy/lib/proof-generator.py` (689 lines)
|
|
11
|
+
- `_build_proof` (line 366) assembles the frozen schema v1.0 dict. `deployment`
|
|
12
|
+
set at line 401: `{"deployed_url": deployed_url, "public_url": None}`.
|
|
13
|
+
`public_url` is ALWAYS `None` at generate time. This is the publish-time
|
|
14
|
+
injection slot the design hangs on.
|
|
15
|
+
- `_build_social_hook` (line 444) computes the one-line hook from already-redacted
|
|
16
|
+
fields only: `cost.usd` via `_fmt_usd_hook` (line 427), `files_changed.count`,
|
|
17
|
+
and council ratio via `_council_ratio` (line 406). Cost clause omitted when usd
|
|
18
|
+
is None (never fabricates `$0.00`).
|
|
19
|
+
- `_render_html` (line 562) loads `proof-template.html`, replaces
|
|
20
|
+
`__PROOF_OG_DESCRIPTION__` (og + twitter metas) with `_attr_esc(hook)`, then
|
|
21
|
+
replaces `__PROOF_JSON__` (escaping `<` as `<`). Falls back to
|
|
22
|
+
`_render_fallback_html` (line 470) when template missing.
|
|
23
|
+
- CHOKEPOINT: `generate` (line 582) calls `proof_redact.redact_tree(proof)` once
|
|
24
|
+
(line 609), refuses to emit if redaction did not run (line 613), applies length
|
|
25
|
+
caps AFTER redaction (line 619), then hashes and writes proof.json + index.html.
|
|
26
|
+
|
|
27
|
+
**Template** `autonomy/lib/proof-template.html` (803 lines)
|
|
28
|
+
- Self-contained / zero-egress by contract (header comment lines 1-40). Social
|
|
29
|
+
meta lines 46-55: og:title, og:description, twitter:card=summary,
|
|
30
|
+
twitter:description. Line 46: "No og:image (would break self-containment)" -
|
|
31
|
+
preserved by this plan.
|
|
32
|
+
- Hero markup lines 261-265 (`.hero`, `#heroLine`, `#heroSub`, `#heroActions`);
|
|
33
|
+
`.hero-actions`/`.btn` CSS at 105-115. `renderHero` (line 426) builds the hero
|
|
34
|
+
client-side. `renderCta` (line 732) emits command blocks with `data-copy`.
|
|
35
|
+
- `wireCopy` (line 762) is a delegated click handler keying off `data-copy`,
|
|
36
|
+
using `navigator.clipboard` with `execCommand` fallback. PATTERN to mirror for
|
|
37
|
+
share buttons + copy-link.
|
|
38
|
+
- Template reads `deployment.public_url` at line 466 (`renderTier1`).
|
|
39
|
+
|
|
40
|
+
**CLI** `autonomy/loki`
|
|
41
|
+
- `cmd_proof` (line 26277): list/show/open/share. `share` (26393) gates
|
|
42
|
+
`--hosted` to `_loki_hosted_publish_proof` (26113, posts redacted bytes to
|
|
43
|
+
`LOKI_HOSTED_ENDPOINT`, honest "no backend yet" otherwise), else uploads
|
|
44
|
+
index.html AS-IS to a gist via `_loki_gist_upload` (26021, `gh gist create`,
|
|
45
|
+
exposes `LOKI_LAST_GIST_URL`). The default gist path never fills public_url or
|
|
46
|
+
re-renders.
|
|
47
|
+
- Social hook is reimplemented inline in Python at 26503-26553 (duplicates
|
|
48
|
+
`_build_social_hook`). This plan adds no third copy.
|
|
49
|
+
|
|
50
|
+
**Dashboard** `dashboard/server.py`: `_proofs_dir` (7668), `_safe_proof_run_dir`
|
|
51
|
+
(7672, realpath-contained), `GET /api/proofs` (7689). Read-only consumer;
|
|
52
|
+
untouched.
|
|
53
|
+
|
|
54
|
+
**Redaction** `autonomy/lib/proof_redact.py`: `redact_tree` (266),
|
|
55
|
+
`redact_value` (260), `set_context`/`reset_context` (37/51), `RULES_VERSION =
|
|
56
|
+
"1.0"` (29). Reused, never reinvented.
|
|
57
|
+
|
|
58
|
+
**Existing tests**: `tests/test_proof_html.py` (`test_no_external_resource_refs`
|
|
59
|
+
forbids `src=`, `@import`, `url(http`, any non-allowlisted `https?://`; allowlist
|
|
60
|
+
only `github.com/asklokesh/loki-mode`; plus null-cost-never-renders-$0.00),
|
|
61
|
+
`tests/test_proof_redaction.py` (secret-class + e2e), `tests/cli/test-proof-command.sh`,
|
|
62
|
+
`tests/dashboard/test_proofs_routes.py`.
|
|
63
|
+
|
|
64
|
+
**Dependency posture**: no Pillow/cairo/cairosvg. Local PNG rasterization would
|
|
65
|
+
add a dependency and break the zero-dep local posture - ruled out.
|
|
66
|
+
|
|
67
|
+
## 2. The og:image decision
|
|
68
|
+
|
|
69
|
+
CHOSEN: keep the local proof zero-egress with NO og:image; add a branded in-page
|
|
70
|
+
card rendered from the redacted dict; inject og:image and share-target URLs ONLY
|
|
71
|
+
at publish time via the existing `deployment.public_url` slot, on an
|
|
72
|
+
HTML-serving host.
|
|
73
|
+
|
|
74
|
+
- Option (a) data-URI og:image: REJECTED. og:image is spec'd as a URL; scrapers
|
|
75
|
+
do not reliably honor data-URI og:image. Bloat for zero benefit.
|
|
76
|
+
- Option (b) separate card file referenced only at publish: PARTIALLY ADOPTED. A
|
|
77
|
+
real og:image needs a public URL, absent at generate time. Reference no image
|
|
78
|
+
at generate; at publish (only if the proof reaches an HTML-serving host
|
|
79
|
+
returning a public URL) that host is where og:image is set.
|
|
80
|
+
- Option (c) branded in-page card: ADOPTED as the always-on self-contained
|
|
81
|
+
visual. Inline CSS / inline SVG, rendered client-side from the redacted dict.
|
|
82
|
+
Zero egress, zero new deps, redaction-safe.
|
|
83
|
+
|
|
84
|
+
HONEST GIST LIMITATION: `loki proof share <id>` publishes to a GitHub Gist. The
|
|
85
|
+
gist page serves GitHub's own og tags (profile picture), not the uploaded HTML's
|
|
86
|
+
og tags; `raw.githubusercontent.com` serves `text/plain`, not scraped. So the
|
|
87
|
+
gist path does NOT produce a rich preview of the proof card. Only an
|
|
88
|
+
HTML-serving host returning a real public URL served as `text/html` (the
|
|
89
|
+
`LOKI_HOSTED_ENDPOINT` seam) can. Stated plainly in docs + CLI output.
|
|
90
|
+
|
|
91
|
+
UNIFYING INSIGHT: og:image, share-button `url=`, and copy-link all need a public
|
|
92
|
+
URL absent at `file://` generate time. All three key off
|
|
93
|
+
`deployment.public_url`: null at generate (share degrades to text-only/hidden),
|
|
94
|
+
populated at publish.
|
|
95
|
+
|
|
96
|
+
## 3. Disjoint dev slices
|
|
97
|
+
|
|
98
|
+
Binding constraints (every slice): no VERSION bump; no commits; no emojis; no em
|
|
99
|
+
or en dashes (ASCII hyphen only); no new runtime dependencies; the default
|
|
100
|
+
generate path stays byte-for-byte zero network calls.
|
|
101
|
+
|
|
102
|
+
**Slice A - Template: branded card + JS-click share buttons**
|
|
103
|
+
(`autonomy/lib/proof-template.html` only)
|
|
104
|
+
- Style the hero (261-265) into a branded card: what was built (files_changed +
|
|
105
|
+
spec source), verified (council verdict/ratio + gates), cost (cost.usd),
|
|
106
|
+
duration (wall_clock_sec), plus Loki/Autonomi branding. Inline CSS / inline SVG.
|
|
107
|
+
Rendered by `renderHero` from the redacted dict; no new schema fields.
|
|
108
|
+
- Add a share row in `.hero-actions` using `data-share` attributes (mirror
|
|
109
|
+
`data-copy`): X/Twitter, LinkedIn, Copy link. NO literal `https://` href in
|
|
110
|
+
static HTML (keeps `test_no_external_resource_refs` green). A new delegated
|
|
111
|
+
handler assembles the intent URL at click time:
|
|
112
|
+
- X: `https://twitter.com/intent/tweet?text=<hook>&url=<public_url>` (hook from
|
|
113
|
+
og:description meta; public_url from parsed proof).
|
|
114
|
+
- LinkedIn: `https://www.linkedin.com/sharing/share-offsite/?url=<public_url>`
|
|
115
|
+
(URL only; LinkedIn ignores prefilled text).
|
|
116
|
+
- Copy link: writes public_url via the clipboard path wireCopy uses.
|
|
117
|
+
- When public_url is null (default local case): share buttons hide or fall back
|
|
118
|
+
to copy-the-hook-text; never emit a broken `url=`.
|
|
119
|
+
- og:image meta only as empty/absent default; populated by the publish path.
|
|
120
|
+
|
|
121
|
+
**Slice B - Generator: hook plumbing + public_url passthrough**
|
|
122
|
+
(`autonomy/lib/proof-generator.py` only)
|
|
123
|
+
- Add `LOKI_PROOF_SHARE_BUTTONS` (default ON; justified below) conditionally
|
|
124
|
+
including the share-row markers. Reuse `_build_social_hook`.
|
|
125
|
+
- public_url stays None at generate. Optional `LOKI_PROOF_PUBLIC_URL` env, when
|
|
126
|
+
set, threads into deployment.public_url BEFORE the redaction chokepoint.
|
|
127
|
+
Default None. No og:image here. No network calls.
|
|
128
|
+
|
|
129
|
+
**Slice C - CLI: publish-time re-render + og:image/public_url injection**
|
|
130
|
+
(`autonomy/loki` only; OWNS the re-render)
|
|
131
|
+
- Hosted path (`_loki_hosted_publish_proof`, 26113): after the host returns a
|
|
132
|
+
public URL, re-render with deployment.public_url set + og:image set to the
|
|
133
|
+
host-minted card URL, then upload. Re-render reuses the generator (env var) so
|
|
134
|
+
redaction runs again over the URL-injected dict - never a hand-built body.
|
|
135
|
+
- Gist path: byte-for-byte unchanged EXCEPT one honest stdout note (gist previews
|
|
136
|
+
show GitHub's og tags, not the proof card; point to --hosted). Do not fabricate
|
|
137
|
+
a public_url for gist.
|
|
138
|
+
|
|
139
|
+
**Slice D - Docs** (this file + a short note in proof docs / wiki): zero-egress
|
|
140
|
+
invariant, opt-in publish path, gist-vs-hosted preview reality, LinkedIn text
|
|
141
|
+
limitation.
|
|
142
|
+
|
|
143
|
+
Recommended parallelism: A, B, D in parallel; C after B's flag contract is
|
|
144
|
+
agreed.
|
|
145
|
+
|
|
146
|
+
## 4. Test plan
|
|
147
|
+
|
|
148
|
+
Model on `tests/test_proof_html.py`, `tests/test_proof_redaction.py`.
|
|
149
|
+
|
|
150
|
+
1. Default generate path zero-egress: extend `test_no_external_resource_refs` -
|
|
151
|
+
generate with share buttons ON, assert NO `src=`, no `@import`, no `url(http`,
|
|
152
|
+
no `https?://` URL except the github.com/asklokesh/loki-mode allowlist. The
|
|
153
|
+
load-bearing guard that the JS-click design adds no literal share URLs.
|
|
154
|
+
2. Redaction holds on card + share text: reuse the e2e pattern - plant secrets in
|
|
155
|
+
council summaries / spec / diffs, generate, assert none survive anywhere in
|
|
156
|
+
index.html incl. card markup + og:description. Card renders ONLY redacted
|
|
157
|
+
fields.
|
|
158
|
+
3. Share button URLs well-formed + prefilled: unit-test the JS URL assembly -
|
|
159
|
+
given public_url, X intent contains URL-encoded hook + url=<public_url>;
|
|
160
|
+
LinkedIn contains url=<public_url> no text; copy-link copies public_url. Null
|
|
161
|
+
public_url -> no malformed url=.
|
|
162
|
+
4. og:image populated ONLY on publish: default page has no og:image; simulate
|
|
163
|
+
hosted re-render with a public URL, assert og:image + public_url populated and
|
|
164
|
+
re-redacted; gist path leaves page unchanged + prints the note.
|
|
165
|
+
5. Null-cost credibility preserved: card never shows `$0.00` when cost uncollected.
|
|
166
|
+
|
|
167
|
+
## 5. Honest limits
|
|
168
|
+
|
|
169
|
+
- Data-URI og:image not reliably scraped (og:image is spec'd as a URL); not used.
|
|
170
|
+
- GitHub gist yields no rich preview of the proof card (gist page = GitHub og
|
|
171
|
+
tags; raw = text/plain).
|
|
172
|
+
- A rich social preview requires an HTML-serving host returning a public URL (the
|
|
173
|
+
LOKI_HOSTED_ENDPOINT seam or any static HTML host the user configures). No
|
|
174
|
+
official Loki hosted backend yet; the seam is honest about this.
|
|
175
|
+
- LinkedIn share ignores prefilled text; it scrapes the destination og tags, so
|
|
176
|
+
LinkedIn previews depend on the hosted og:image. X intent carries the hook.
|
|
177
|
+
- Sharing is manual / opt-in: a button is inert until clicked; no auto egress.
|
|
178
|
+
- No hosted gallery / discovery feed in this release.
|
|
179
|
+
|
|
180
|
+
Share-buttons default ON: with JS-click construction there is no literal external
|
|
181
|
+
URL in the static page and no network call until the user clicks. The buttons are
|
|
182
|
+
inert markup until acted on, so they cannot violate the default zero-egress
|
|
183
|
+
invariant. Default ON maximizes the word-of-mouth lever at zero cost to the
|
|
184
|
+
posture; `LOKI_PROOF_SHARE_BUTTONS=0` opts out.
|
|
185
|
+
|
|
186
|
+
## Zero-egress invariant (precise)
|
|
187
|
+
|
|
188
|
+
- STAYS zero-egress (byte-for-byte zero network calls): generating a proof and
|
|
189
|
+
opening the local index.html. No og:image, no src=, no @import, no web fonts,
|
|
190
|
+
no literal share URLs. Card + hook render from the embedded redacted JSON.
|
|
191
|
+
Share buttons present but inert.
|
|
192
|
+
- OPT-IN egress (only on explicit user action): clicking a share button, copying
|
|
193
|
+
the link, and `loki proof share <id>` / `--hosted`. og:image and
|
|
194
|
+
deployment.public_url are populated only on the hosted publish re-render.
|