@codyswann/lisa 4.56.4 → 4.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +7 -0
  2. package/dist/cli-load-diagnostic.d.ts +8 -0
  3. package/dist/cli-load-diagnostic.d.ts.map +1 -0
  4. package/dist/cli-load-diagnostic.js +83 -0
  5. package/dist/cli-load-diagnostic.js.map +1 -0
  6. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  7. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  8. package/dist/core/upstream-evidence-manifest.js +6 -2
  9. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  10. package/dist/index.js +7 -1
  11. package/dist/index.js.map +1 -1
  12. package/package.json +4 -4
  13. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-agy/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  19. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo-agy/plugin.json +1 -1
  26. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  46. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails-agy/plugin.json +1 -1
  51. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  56. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  61. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  63. package/ui/README.md +6 -5
  64. package/ui/index.html +235 -37
package/ui/index.html CHANGED
@@ -1835,14 +1835,15 @@
1835
1835
  id="saveBtn"
1836
1836
  type="button"
1837
1837
  disabled
1838
- title="read-only: the write-path has not shipped yet"
1838
+ title="Open this console with lisa ui to save changes"
1839
1839
  aria-describedby="saveReadonlyReason"
1840
1840
  >
1841
1841
  Save changes
1842
1842
  </button>
1843
1843
  <span class="status-why readonly-reason" id="saveReadonlyReason">
1844
- read-only: the write-path has not shipped yet
1844
+ Open this console with lisa ui to save changes
1845
1845
  </span>
1846
+ <span id="saveError" role="alert" hidden></span>
1846
1847
  </div>
1847
1848
  <div class="toast" id="toast"></div>
1848
1849
  <script>
@@ -11053,14 +11054,16 @@
11053
11054
  /* LISA_UI_CATALOG_END */
11054
11055
 
11055
11056
  /* ------------------------------------------------------------------ *
11056
- * Rendering + interactions (prototype: state is in-memory only)
11057
+ * Rendering + interactions
11057
11058
  * ------------------------------------------------------------------ */
11058
11059
 
11059
11060
  const $nav = document.getElementById("nav");
11060
11061
  const $main = document.getElementById("main");
11061
11062
  const $savebar = document.getElementById("savebar");
11062
11063
  const $toast = document.getElementById("toast");
11063
- const dirty = new Set();
11064
+ const dirty = new Map();
11065
+ const initialControls = new Map();
11066
+ let saving = false;
11064
11067
  let lisaLiveMode = false;
11065
11068
  let lisaCatalogAudit = null;
11066
11069
  let lisaCatalogPreparation = null;
@@ -11160,16 +11163,41 @@
11160
11163
  .replace(/"/g, "&quot;");
11161
11164
  }
11162
11165
 
11163
- function markDirty(id, rowEl) {
11164
- dirty.add(id);
11165
- if (rowEl) rowEl.classList.add("modified");
11166
+ function markDirty(id, rowEl, value) {
11167
+ const baseline = lisaLiveMode
11168
+ ? liveConfigEntry(window.LISA_LIVE_CONFIG, id)
11169
+ : { present: false };
11170
+ if (
11171
+ baseline.present &&
11172
+ JSON.stringify(baseline.value) === JSON.stringify(value)
11173
+ )
11174
+ dirty.delete(id);
11175
+ else dirty.set(id, value);
11176
+ if (rowEl)
11177
+ rowEl.classList.toggle(
11178
+ "modified",
11179
+ isDirtyRow(rowEl.dataset.configKey || id)
11180
+ );
11181
+ document.getElementById("saveError").hidden = true;
11166
11182
  updateSavebar();
11167
11183
  }
11184
+ function isDirtyRow(key) {
11185
+ return [...dirty.keys()].some(
11186
+ changed => changed === key || changed.startsWith(key + ".")
11187
+ );
11188
+ }
11168
11189
  function updateSavebar() {
11169
11190
  const n = dirty.size;
11170
11191
  document.getElementById("dirtyCount").textContent = n;
11171
11192
  document.getElementById("dirtyPlural").textContent = n === 1 ? "" : "s";
11172
11193
  $savebar.classList.toggle("show", n > 0);
11194
+ document.getElementById("saveBtn").disabled =
11195
+ !lisaLiveMode || saving || n === 0;
11196
+ document.getElementById("saveBtn").textContent = saving
11197
+ ? "Saving…"
11198
+ : "Save changes";
11199
+ document.getElementById("discardBtn").disabled = saving;
11200
+ $main.inert = saving;
11173
11201
  }
11174
11202
  const toastState = { timer: 0 };
11175
11203
  function showToast(message) {
@@ -11186,6 +11214,98 @@
11186
11214
  updateSavebar();
11187
11215
  }
11188
11216
 
11217
+ /** Visit existing config rows, including rows inside provider tabs. */
11218
+ function forEachConfigRow(visit) {
11219
+ const walk = blocks =>
11220
+ (blocks || []).forEach(block => {
11221
+ if (block.type === "tabs")
11222
+ (block.tabs || []).forEach(tab => walk(tab.blocks));
11223
+ if (!block.type)
11224
+ (block.rows || []).forEach(row => {
11225
+ if (row.key && row.control) visit(row);
11226
+ });
11227
+ });
11228
+ Object.values(DATA.sections).forEach(section => walk(section.blocks));
11229
+ }
11230
+
11231
+ /** Restore config controls without resetting separately loaded probe results. */
11232
+ function restoreConfigControls() {
11233
+ forEachConfigRow(row => {
11234
+ const original = initialControls.get(row.key);
11235
+ if (!original) return;
11236
+ const entry = lisaLiveMode
11237
+ ? liveConfigEntry(window.LISA_LIVE_CONFIG, row.key)
11238
+ : null;
11239
+ row.control = { ...original };
11240
+ if (entry) {
11241
+ const hydrated =
11242
+ entry.present && hydrateControl(original, entry.value);
11243
+ if (hydrated) row.control = hydrated;
11244
+ else
11245
+ unknownControl(
11246
+ row,
11247
+ entry.present
11248
+ ? "invalid live config value"
11249
+ : "missing live config key"
11250
+ );
11251
+ }
11252
+ });
11253
+ clearDirty();
11254
+ document.getElementById("saveError").hidden = true;
11255
+ renderAll();
11256
+ showSection(activeSection);
11257
+ }
11258
+
11259
+ async function saveConfig() {
11260
+ if (!lisaLiveMode || saving || dirty.size === 0) return;
11261
+ const errorNode = document.getElementById("saveError");
11262
+ saving = true;
11263
+ updateSavebar();
11264
+ errorNode.hidden = true;
11265
+ $toast.classList.remove("show");
11266
+ try {
11267
+ if (
11268
+ [...dirty.values()].some(
11269
+ value => typeof value === "number" && !Number.isFinite(value)
11270
+ )
11271
+ )
11272
+ throw new Error("Enter a valid number before saving.");
11273
+ const response = await fetch("/api/config", {
11274
+ method: "POST",
11275
+ headers: {
11276
+ "content-type": "application/json",
11277
+ accept: "application/json",
11278
+ },
11279
+ body: JSON.stringify({ changes: Object.fromEntries(dirty) }),
11280
+ });
11281
+ const result = await response.json();
11282
+ if (!response.ok)
11283
+ throw new Error(
11284
+ result.error ||
11285
+ "Unable to save changes (HTTP " + response.status + ")"
11286
+ );
11287
+ if (
11288
+ result.ok !== true ||
11289
+ !result.config ||
11290
+ typeof result.config !== "object" ||
11291
+ Array.isArray(result.config)
11292
+ )
11293
+ throw new Error(
11294
+ "The server did not confirm the saved configuration. Your changes are still pending."
11295
+ );
11296
+ window.LISA_LIVE_CONFIG = result.config;
11297
+ restoreConfigControls();
11298
+ showToast("Saved changes");
11299
+ } catch (error) {
11300
+ errorNode.textContent =
11301
+ error instanceof Error ? error.message : String(error);
11302
+ errorNode.hidden = false;
11303
+ } finally {
11304
+ saving = false;
11305
+ updateSavebar();
11306
+ }
11307
+ }
11308
+
11189
11309
  /** Mark the DOM owner of a catalog value with its audited provenance. */
11190
11310
  function markRenderedValue(node, value) {
11191
11311
  if (!node || !value) return node;
@@ -11216,6 +11336,17 @@
11216
11336
  table cells ({t, v, syncKey}) and card rows ({control:{value,
11217
11337
  syncKey}}). */
11218
11338
  function syncControlValue(syncKey, value) {
11339
+ forEachConfigRow(row => {
11340
+ if (row.key === syncKey) {
11341
+ const hydrated = hydrateControl(row.control, value);
11342
+ if (hydrated) row.control = hydrated;
11343
+ } else if (
11344
+ row.control.type === "envmap" &&
11345
+ syncKey.startsWith(row.key + ".")
11346
+ ) {
11347
+ row.control[syncKey.slice(row.key.length + 1)] = value;
11348
+ }
11349
+ });
11219
11350
  Object.values(DATA.sections).forEach(section => {
11220
11351
  (section.blocks || []).forEach(block => {
11221
11352
  if (!Array.isArray(block.rows)) return;
@@ -11229,7 +11360,11 @@
11229
11360
  row.control &&
11230
11361
  (row.control.syncKey === syncKey || row.key === syncKey)
11231
11362
  ) {
11232
- row.control.value = value;
11363
+ if (
11364
+ row.control.type !== "envmap" &&
11365
+ row.control.type !== "tags"
11366
+ )
11367
+ row.control.value = value;
11233
11368
  }
11234
11369
  });
11235
11370
  });
@@ -11237,28 +11372,65 @@
11237
11372
  document
11238
11373
  .querySelectorAll('[data-sync-key="' + syncKey + '"]')
11239
11374
  .forEach(node => {
11240
- if (node.value !== String(value)) node.value = value;
11375
+ if (node.type === "checkbox") {
11376
+ node.checked = value;
11377
+ node.dispatchEvent(new Event("lisa-sync"));
11378
+ } else if (node.value !== String(value)) node.value = value;
11241
11379
  });
11242
- }
11243
-
11244
- /* Mark a control as one surface of a shared setting. */
11245
- function bindSync(input, control) {
11246
- if (!control || !control.syncKey) return;
11247
- input.dataset.syncKey = control.syncKey;
11248
- input.addEventListener("change", () =>
11249
- syncControlValue(control.syncKey, input.value)
11250
- );
11380
+ document
11381
+ .querySelectorAll(".row[data-config-key]")
11382
+ .forEach(row =>
11383
+ row.classList.toggle("modified", isDirtyRow(row.dataset.configKey))
11384
+ );
11251
11385
  }
11252
11386
 
11253
11387
  function buildControl(control, dirtyId) {
11254
11388
  const id = dirtyId || "ctl-" + ++ctlSeq;
11389
+ const configKey = dirtyId || (control && control.syncKey);
11255
11390
  const wrap = el("div", "control");
11256
- const bind = (input, rowFinder) => {
11257
- input.addEventListener("change", () =>
11258
- markDirty(
11259
- id + "·" + ++ctlSeq,
11260
- rowFinder ? rowFinder(input) : input.closest(".row")
11261
- )
11391
+ const changed = (value, owner, key = configKey) => {
11392
+ if (lisaLiveMode && configKey && control.type === "envmap") {
11393
+ const baseline = liveConfigEntry(
11394
+ window.LISA_LIVE_CONFIG,
11395
+ configKey
11396
+ );
11397
+ if (typeof baseline.value === "string") {
11398
+ // A legacy scalar means production. Expanding it must retain
11399
+ // that value when the user edits another environment.
11400
+ const expanded = {
11401
+ production: baseline.value,
11402
+ ...(dirty.get(configKey) || {}),
11403
+ [key.slice(configKey.length + 1)]: value,
11404
+ };
11405
+ const restored =
11406
+ Object.keys(expanded).length === 1 &&
11407
+ expanded.production === baseline.value;
11408
+ const next = restored ? baseline.value : expanded;
11409
+ markDirty(configKey, owner, next);
11410
+ syncControlValue(configKey, next);
11411
+ return;
11412
+ }
11413
+ }
11414
+ markDirty(key || id, owner, value);
11415
+ if (key) syncControlValue(key, value);
11416
+ };
11417
+ const bind = (input, rowFinder, key = configKey) => {
11418
+ if (key) input.dataset.syncKey = key;
11419
+ if (lisaLiveMode && !key) input.disabled = true;
11420
+ input.addEventListener(
11421
+ input.type === "text" || input.type === "number"
11422
+ ? "input"
11423
+ : "change",
11424
+ () =>
11425
+ changed(
11426
+ input.type === "checkbox"
11427
+ ? input.checked
11428
+ : input.type === "number"
11429
+ ? input.valueAsNumber
11430
+ : input.value,
11431
+ rowFinder ? rowFinder(input) : input.closest(".row"),
11432
+ key
11433
+ )
11262
11434
  );
11263
11435
  };
11264
11436
  if (!control) return wrap;
@@ -11278,6 +11450,7 @@
11278
11450
  };
11279
11451
  paintState();
11280
11452
  input.addEventListener("change", paintState);
11453
+ input.addEventListener("lisa-sync", paintState);
11281
11454
  bind(input, i => i.closest(".row") || i.closest("tr"));
11282
11455
  box.appendChild(label);
11283
11456
  box.appendChild(state);
@@ -11304,7 +11477,6 @@
11304
11477
  s.appendChild(opt);
11305
11478
  });
11306
11479
  bind(s, i => i.closest(".row") || i.closest("tr"));
11307
- bindSync(s, control);
11308
11480
  wrap.appendChild(s);
11309
11481
  } else if (control.type === "text") {
11310
11482
  const i = document.createElement("input");
@@ -11321,9 +11493,11 @@
11321
11493
  const i = document.createElement("input");
11322
11494
  i.type = "number";
11323
11495
  i.className = "ctl num";
11324
- i.value = control.value;
11496
+ const pendingValue = dirty.has(configKey)
11497
+ ? dirty.get(configKey)
11498
+ : control.value;
11499
+ i.value = Number.isNaN(pendingValue) ? "" : pendingValue;
11325
11500
  bind(i);
11326
- bindSync(i, control);
11327
11501
  wrap.appendChild(i);
11328
11502
  if (control.unit)
11329
11503
  wrap.appendChild(el("span", "unit", esc(control.unit)));
@@ -11352,7 +11526,7 @@
11352
11526
  if (cb.checked) values.add(o);
11353
11527
  else values.delete(o);
11354
11528
  paint();
11355
- markDirty(id + "·" + ++ctlSeq, box.closest("tr"));
11529
+ changed([...values], box.closest("tr"));
11356
11530
  });
11357
11531
  lab.appendChild(cb);
11358
11532
  lab.appendChild(el("span", "", esc(o)));
@@ -11376,14 +11550,17 @@
11376
11550
  wrap.appendChild(box);
11377
11551
  } else if (control.type === "tags") {
11378
11552
  const box = el("div", "tagbox");
11553
+ const values = () =>
11554
+ [...box.querySelectorAll(".tag")].map(tag => tag.dataset.value);
11379
11555
  (control.values || []).forEach(v => {
11380
11556
  const t = el("span", "tag", esc(v) + " ");
11557
+ t.dataset.value = v;
11381
11558
  const x = el("button", "", "×");
11382
11559
  x.type = "button";
11383
- x.title = "Remove (prototype)";
11560
+ x.title = "Remove";
11384
11561
  x.addEventListener("click", () => {
11385
11562
  t.remove();
11386
- markDirty(id + "·" + v, box.closest(".row"));
11563
+ changed(values(), box.closest(".row"));
11387
11564
  });
11388
11565
  t.appendChild(x);
11389
11566
  box.appendChild(t);
@@ -11391,18 +11568,19 @@
11391
11568
  const add = el("button", "tag-add", "+ add");
11392
11569
  add.type = "button";
11393
11570
  add.addEventListener("click", () => {
11394
- const v = prompt("Add value (prototype only):");
11571
+ const v = prompt("Add value:");
11395
11572
  if (!v) return;
11396
11573
  const t = el("span", "tag", esc(v) + " ");
11574
+ t.dataset.value = v;
11397
11575
  const x = el("button", "", "×");
11398
11576
  x.type = "button";
11399
11577
  x.addEventListener("click", () => {
11400
11578
  t.remove();
11401
- markDirty(id + "·rm·" + v, box.closest(".row"));
11579
+ changed(values(), box.closest(".row"));
11402
11580
  });
11403
11581
  t.appendChild(x);
11404
11582
  box.insertBefore(t, add);
11405
- markDirty(id + "·add·" + v, box.closest(".row"));
11583
+ changed(values(), box.closest(".row"));
11406
11584
  });
11407
11585
  box.appendChild(add);
11408
11586
  wrap.appendChild(box);
@@ -11415,7 +11593,11 @@
11415
11593
  i.type = "text";
11416
11594
  i.className = "ctl mono";
11417
11595
  i.value = control[envName] || "";
11418
- bind(i, inp => inp.closest(".row"));
11596
+ bind(
11597
+ i,
11598
+ inp => inp.closest(".row"),
11599
+ configKey ? configKey + "." + envName : undefined
11600
+ );
11419
11601
  cell.appendChild(i);
11420
11602
  box.appendChild(cell);
11421
11603
  });
@@ -11483,6 +11665,10 @@
11483
11665
 
11484
11666
  function buildRow(row) {
11485
11667
  const r = el("div", "row");
11668
+ if (row.key) {
11669
+ r.dataset.configKey = row.key;
11670
+ r.classList.toggle("modified", isDirtyRow(row.key));
11671
+ }
11486
11672
  const left = el("div");
11487
11673
  const label = el("div", "label");
11488
11674
  label.appendChild(el("span", "", esc(row.label)));
@@ -13936,7 +14122,18 @@
13936
14122
 
13937
14123
  /* --- boot (deferred so an injected live-config tag after this script is seen) --- */
13938
14124
  function boot() {
14125
+ forEachConfigRow(row =>
14126
+ initialControls.set(row.key, { ...row.control })
14127
+ );
13939
14128
  hydrateFromLiveConfig();
14129
+ if (lisaLiveMode) {
14130
+ document.getElementById("saveBtn").removeAttribute("title");
14131
+ document
14132
+ .getElementById("saveBtn")
14133
+ .removeAttribute("aria-describedby");
14134
+ document.getElementById("saveReadonlyReason").hidden = true;
14135
+ }
14136
+ updateSavebar();
13940
14137
  renderAll();
13941
14138
  showSection(location.hash ? location.hash.slice(1) : "overview");
13942
14139
  if (!document.getElementById("section-" + activeSection))
@@ -13968,10 +14165,11 @@
13968
14165
  });
13969
14166
 
13970
14167
  document.getElementById("discardBtn").addEventListener("click", () => {
13971
- clearDirty();
13972
- renderAll();
13973
- showSection(activeSection);
14168
+ if (!saving) restoreConfigControls();
13974
14169
  });
14170
+ document
14171
+ .getElementById("saveBtn")
14172
+ .addEventListener("click", () => void saveConfig());
13975
14173
 
13976
14174
  document.getElementById("howtoClose").addEventListener("click", () => {
13977
14175
  document.getElementById("howto").close();