@manny-est/node-red-flowpilot 0.6.0-beta.1 → 0.6.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.
package/lib/core/main.js CHANGED
@@ -35,7 +35,8 @@
35
35
  // Every code path that appends DOM to #fp-messages also appends a record
36
36
  // here. refreshView() clears the message container and re-renders from
37
37
  // records, restoring interactive elements without losing conversation.
38
- // Records are in-memory only — no sessionStorage, no persistence.
38
+ // Records are in-memory only — no full sessionStorage persistence.
39
+ // Only the minimal interrupted-run marker is persisted separately.
39
40
  // ---------------------------------------------------------------------
40
41
  var messageRecords = [];
41
42
  var _nextRecordId = 0;
@@ -722,6 +723,11 @@
722
723
  // Clears the visible chat AND resets the conversation history the model
723
724
  // sees — "start a fresh conversation".
724
725
  function clearChat() {
726
+ flowpilotStorageLog("log", "clearChat-enter", {
727
+ conversationId: conversationId,
728
+ messageCount: messageRecords.length,
729
+ href: location.href
730
+ });
725
731
  el("#fp-messages").empty();
726
732
  messageRecords = [];
727
733
  relayClearMessagesToPopout();
@@ -731,6 +737,7 @@
731
737
  disarmExecuteAction(); // also clears pinnedSelectionIds
732
738
  delete appliedOpsByConversation[conversationId];
733
739
  conversationId = newConversationId();
740
+ clearRunMarker("clearChat");
734
741
  fpChatSnappedToBottom = true;
735
742
  updateSelectionStatus();
736
743
  updateDebugStatus();
@@ -846,11 +853,18 @@
846
853
  // onError: optional — CLAUDE-029's page-load rehydration passes one to
847
854
  // stay quiet (no chat error bubble) and clear a stale sessionStorage
848
855
  // entry on 404, instead of the ajaxJson default of addMessage("error", …).
849
- function loadConversation(id, onError) {
856
+ function loadConversation(id, onError, onLoaded, options) {
857
+ options = options || {};
858
+ flowpilotStorageLog("log", "loadConversation-start", {
859
+ requestedConversationId: id,
860
+ currentConversationId: conversationId,
861
+ href: location.href
862
+ });
850
863
  ajaxJson("GET", "flowpilot/conversations/" + encodeURIComponent(id), null, function (data) {
851
864
  delete appliedOpsByConversation[conversationId];
852
865
  conversationId = id;
853
- try { sessionStorage.setItem("fp-conversation-id", id); } catch (e) { /* storage unavailable */ }
866
+ persistConversationId(id, "loadConversation success");
867
+ if (!options.preserveRunMarker) { clearRunMarker("loadConversation"); }
854
868
 
855
869
  conversationHistory = [];
856
870
  relayClearMessagesToPopout();
@@ -865,6 +879,7 @@
865
879
  pinnedSelectionIds = null;
866
880
  updateSelectionStatus();
867
881
  showChat();
882
+ if (onLoaded) { onLoaded(data); }
868
883
  }, onError);
869
884
  }
870
885
 
@@ -1032,7 +1047,9 @@
1032
1047
  if (!$box.length) { return; }
1033
1048
 
1034
1049
  var label = role === "user" ? "YOU" : role === "error" ? "ERROR" : "FLOWPILOT";
1035
- var cls = "fp-message" + (role === "user" ? " fp-user" : role === "error" ? " fp-error" : "");
1050
+ var cls = "fp-message" + (role === "user" ? " fp-user" :
1051
+ role === "error" ? " fp-error" :
1052
+ role === "fp-notice" ? " fp-secondary" : "");
1036
1053
 
1037
1054
  var $msg = $("<div>").addClass(cls);
1038
1055
  $("<div>").addClass("fp-label").text(label).appendTo($msg);
@@ -1270,6 +1287,7 @@
1270
1287
  el("#fp-api-key").val(p.apiKey || "");
1271
1288
  el("#fp-api-key").attr("placeholder", p.hasApiKey ? "Saved — leave to keep, retype to change" : "Optional");
1272
1289
  el("#fp-model").val(p.model || "");
1290
+ el("#fp-num-ctx").val(p.numCtx !== undefined ? p.numCtx : 0);
1273
1291
  el("#fp-temperature").val(p.temperature !== undefined ? p.temperature : 0.2);
1274
1292
  // Test provider is disabled until this provider has a model.
1275
1293
  el("#fp-test-provider").prop("disabled", !(p.model && String(p.model).trim()));
@@ -1294,13 +1312,60 @@
1294
1312
  el("#fp-persona-label").text(personaLabelFor(n));
1295
1313
  }
1296
1314
 
1297
- function fillSettings(settings) {
1315
+ function hideUpdateBanner() {
1316
+ el("#fp-update-banner").addClass("fp-hidden").empty();
1317
+ }
1318
+
1319
+ function showUpdateBanner(data) {
1320
+ var $banner = el("#fp-update-banner");
1321
+ if (!$banner.length) { return; }
1322
+ $banner.empty().removeClass("fp-hidden");
1323
+ $banner.append(document.createTextNode("Update available (v" + data.latestVersion + ") — see Palette Manager to install. "));
1324
+ $("<a>").attr("href", "#").text("Dismiss").on("click", function (ev) {
1325
+ ev.preventDefault();
1326
+ try {
1327
+ sessionStorage.setItem("fp-update-dismissed-" + data.latestVersion, "1");
1328
+ } catch (e) { /* storage unavailable */ }
1329
+ hideUpdateBanner();
1330
+ }).appendTo($banner);
1331
+ }
1332
+
1333
+ function checkForFlowPilotUpdates() {
1334
+ if (isPopoutContext) { return; }
1335
+ ajaxJson("GET", "flowpilot/update-check", null, function (data) {
1336
+ if (!data || data.enabled === false || data.updateAvailable === false) {
1337
+ hideUpdateBanner();
1338
+ return;
1339
+ }
1340
+ try {
1341
+ if (sessionStorage.getItem("fp-update-dismissed-" + data.latestVersion)) {
1342
+ hideUpdateBanner();
1343
+ return;
1344
+ }
1345
+ } catch (e) { /* storage unavailable */ }
1346
+ showUpdateBanner(data);
1347
+ }, function () {
1348
+ hideUpdateBanner();
1349
+ });
1350
+ }
1351
+
1352
+ function fillSettings(settings, reason) {
1298
1353
  settings = settings || {};
1299
1354
  currentSettings = settings;
1300
1355
 
1301
1356
  renderProviderDropdown();
1302
1357
  fillProviderFields(activeProvider());
1303
1358
 
1359
+ el("#fp-flowpilot-version").text("FlowPilot v" + (settings.flowpilotVersion || "unknown"));
1360
+ var systemPromptCurrent = el("#fp-system-prompt").val();
1361
+ var systemPromptIncoming = settings.systemPrompt || "";
1362
+ console.log("[FlowPilot][G3] fillSettings systemPrompt", {
1363
+ reason: reason || "unspecified",
1364
+ current: systemPromptCurrent,
1365
+ incoming: settings.systemPrompt,
1366
+ writing: systemPromptIncoming,
1367
+ differs: systemPromptCurrent !== systemPromptIncoming
1368
+ });
1304
1369
  el("#fp-system-prompt").val(settings.systemPrompt || "");
1305
1370
  el("#fp-persona-intensity").val(settings.personaIntensity !== undefined ? settings.personaIntensity : 2);
1306
1371
  updatePersonaLabel();
@@ -1309,19 +1374,24 @@
1309
1374
  el("#fp-history-max").val(settings.historyMaxExchanges !== undefined ? settings.historyMaxExchanges : 10);
1310
1375
  el("#fp-streaming-enabled").prop("checked", !!settings.streamingEnabled);
1311
1376
  el("#fp-request-timeout").val(settings.requestTimeoutMs !== undefined ? (settings.requestTimeoutMs / 1000) : 180);
1377
+ el("#fp-check-for-updates").prop("checked", settings.checkForUpdates !== false);
1312
1378
  el("#fp-agent-turn-max-tokens").val(settings.agentTurnMaxTokens !== undefined ? settings.agentTurnMaxTokens : 4096);
1379
+ el("#fp-agent-loop-token-ceiling").val(settings.agentLoopTokenCeiling !== undefined ? settings.agentLoopTokenCeiling : 50000);
1313
1380
  el("#fp-agent-loop-max-iterations").val(settings.agentLoopMaxIterations !== undefined ? settings.agentLoopMaxIterations : 5);
1314
1381
  el("#fp-loop-hold-step").prop("checked", !!settings.loopHoldStep);
1315
1382
  el("#fp-suppress-warnings").prop("checked", !!settings.suppressContextWarnings);
1316
1383
  el("#fp-redaction-disabled").prop("checked", settings.redactionEnabled === false);
1317
1384
  el("#fp-debug-logging").prop("checked", !!settings.debugLogging);
1318
1385
 
1319
- // The dev/test banner is part of the warning set the user can silence
1320
- // via the type-to-confirm acknowledgement.
1386
+ // Plain checkbox-driven dev/test warning icon visibility; this warning
1387
+ // no longer uses a type-to-confirm acknowledgement.
1321
1388
  if (settings.suppressContextWarnings) {
1322
- el("#fp-dev-banner").addClass("fp-hidden");
1389
+ el("#fp-dev-warning-status").addClass("fp-hidden");
1323
1390
  } else {
1324
- el("#fp-dev-banner").removeClass("fp-hidden");
1391
+ el("#fp-dev-warning-status").removeClass("fp-hidden");
1392
+ }
1393
+ if (settings.checkForUpdates === false) {
1394
+ hideUpdateBanner();
1325
1395
  }
1326
1396
 
1327
1397
  // Custom intents may have changed; rebuild buttons and the editor list.
@@ -1363,21 +1433,21 @@
1363
1433
  ap.baseUrl = el("#fp-base-url").val() || "";
1364
1434
  ap.apiKey = el("#fp-api-key").val() || "";
1365
1435
  ap.model = el("#fp-model").val() || "";
1436
+ ap.numCtx = Math.max(0, Number(el("#fp-num-ctx").val() || 0));
1366
1437
  ap.temperature = Number(el("#fp-temperature").val() || 0.2);
1367
1438
  currentSettings.providers = list;
1368
1439
  }
1369
1440
 
1370
1441
  function collectSettings() {
1371
- // Only honour the suppression toggle if the confirmation phrase was
1372
- // typed exactly. Otherwise warnings stay on regardless of the checkbox.
1373
- var wantSuppress = el("#fp-suppress-warnings").prop("checked");
1374
- var typed = (el("#fp-suppress-confirm").val() || "").trim();
1375
- var suppress = wantSuppress && typed === "I understand the risk";
1376
-
1377
- // Same type-to-confirm gate as suppressContextWarnings above, and for
1378
- // the same reason: the confirm box is never pre-filled from settings,
1379
- // so disabling redaction stays off unless re-confirmed on every save —
1380
- // "off-able, not off-by-accident".
1442
+ // Plain checkbox no confirmation phrase required (Manny's explicit
1443
+ // call: excessive friction for this particular warning, unlike the
1444
+ // redaction opt-out below which stays gated).
1445
+ var suppress = el("#fp-suppress-warnings").prop("checked");
1446
+
1447
+ // Same type-to-confirm gate as suppressContextWarnings used to have,
1448
+ // and for the same reason: the confirm box is never pre-filled from
1449
+ // settings, so disabling redaction stays off unless re-confirmed on
1450
+ // every save "off-able, not off-by-accident".
1381
1451
  var wantRedactionOff = el("#fp-redaction-disabled").prop("checked");
1382
1452
  var redactionTyped = (el("#fp-redaction-confirm").val() || "").trim();
1383
1453
  var redactionEnabled = !(wantRedactionOff && redactionTyped === "disable redaction");
@@ -1397,18 +1467,25 @@
1397
1467
  if (!isFinite(agentLoopMaxIterations) || agentLoopMaxIterations < 1) { agentLoopMaxIterations = 5; }
1398
1468
 
1399
1469
  var agentTurnMaxTokens = Number(el("#fp-agent-turn-max-tokens").val());
1470
+ var agentLoopTokenCeiling = Number(el("#fp-agent-loop-token-ceiling").val());
1471
+ var systemPrompt = el("#fp-system-prompt").val();
1472
+ console.log("[FlowPilot][G3] saveSettings reading systemPrompt", {
1473
+ value: systemPrompt
1474
+ });
1400
1475
 
1401
1476
  return {
1402
1477
  providers: providersList(),
1403
1478
  activeProviderId: currentSettings.activeProviderId,
1404
- systemPrompt: el("#fp-system-prompt").val(),
1479
+ systemPrompt: systemPrompt,
1405
1480
  personaIntensity: personaIntensity,
1406
1481
  contextWarnTokens: Number(el("#fp-warn-tokens").val() || 4000),
1407
1482
  contextHighTokens: Number(el("#fp-high-tokens").val() || 8000),
1408
1483
  historyMaxExchanges: historyMax,
1409
1484
  streamingEnabled: el("#fp-streaming-enabled").prop("checked"),
1410
1485
  requestTimeoutMs: Math.round(requestTimeoutSec * 1000),
1486
+ checkForUpdates: el("#fp-check-for-updates").prop("checked"),
1411
1487
  agentTurnMaxTokens: agentTurnMaxTokens,
1488
+ agentLoopTokenCeiling: agentLoopTokenCeiling,
1412
1489
  agentLoopMaxIterations: agentLoopMaxIterations,
1413
1490
  loopHoldStep: el("#fp-loop-hold-step").prop("checked"),
1414
1491
  suppressContextWarnings: suppress,
@@ -1449,6 +1526,7 @@
1449
1526
  baseUrl: "http://localhost:8080",
1450
1527
  apiKey: "",
1451
1528
  model: "",
1529
+ numCtx: 0,
1452
1530
  temperature: 0.2
1453
1531
  });
1454
1532
  currentSettings.providers = list;
@@ -1542,7 +1620,7 @@
1542
1620
  return;
1543
1621
  }
1544
1622
  ajaxJson("GET", "flowpilot/settings", null, function (data) {
1545
- fillSettings(data);
1623
+ fillSettings(data, "loadSettings GET response");
1546
1624
  maybeShowFirstRun(data);
1547
1625
  updateSelectionStatus();
1548
1626
  }, function (msg) {
@@ -1567,12 +1645,19 @@
1567
1645
  return;
1568
1646
  }
1569
1647
  if (!isFinite(payload.agentTurnMaxTokens) || payload.agentTurnMaxTokens <= 0 || Math.floor(payload.agentTurnMaxTokens) !== payload.agentTurnMaxTokens) {
1570
- var tokenMsg = "Cannot save: max tokens per agent turn must be a positive whole number.";
1648
+ var tokenMsg = "Cannot save: max tokens per model response (agent step) must be a positive whole number.";
1571
1649
  addMessage("error", tokenMsg);
1572
1650
  if (announce) { showSaveStatus(tokenMsg, true); }
1573
1651
  showSettings();
1574
1652
  return;
1575
1653
  }
1654
+ if (!isFinite(payload.agentLoopTokenCeiling) || payload.agentLoopTokenCeiling <= 0 || Math.floor(payload.agentLoopTokenCeiling) !== payload.agentLoopTokenCeiling) {
1655
+ var ceilingMsg = "Cannot save: max total tokens per agent turn must be a positive whole number.";
1656
+ addMessage("error", ceilingMsg);
1657
+ if (announce) { showSaveStatus(ceilingMsg, true); }
1658
+ showSettings();
1659
+ return;
1660
+ }
1576
1661
 
1577
1662
  // Validation 1: every non-Anthropic provider needs a base URL.
1578
1663
  // Anthropic providers default to api.anthropic.com when baseUrl is blank.
@@ -1627,7 +1712,7 @@
1627
1712
  }
1628
1713
 
1629
1714
  ajaxJson("POST", "flowpilot/settings", payload, function (data) {
1630
- fillSettings(data);
1715
+ fillSettings(data, "saveSettings POST response");
1631
1716
  addMessage("assistant", "Settings saved.");
1632
1717
  if (announce) { showSaveStatus("Settings saved."); }
1633
1718
  updateSelectionStatus();
@@ -2116,8 +2201,8 @@
2116
2201
  if (typeof args.selectionHint === "string" && args.selectionHint.trim()) {
2117
2202
  result.suggestedAction.selectionHint = args.selectionHint.trim();
2118
2203
  }
2119
- if (args.targetNodeIds === "all") {
2120
- result.suggestedAction.targetNodeIds = "all";
2204
+ if (args.targetNodeIds === "all" || args.targetNodeIds === "instance") {
2205
+ result.suggestedAction.targetNodeIds = args.targetNodeIds;
2121
2206
  } else if (Array.isArray(args.targetNodeIds)) {
2122
2207
  var ids = args.targetNodeIds
2123
2208
  .filter(function (id) { return typeof id === "string" && id.trim(); })
@@ -2422,6 +2507,9 @@
2422
2507
  } else {
2423
2508
  var warnAt = Number(currentSettings.contextWarnTokens) || 4000;
2424
2509
  var highAt = Number(currentSettings.contextHighTokens) || 8000;
2510
+ var ap = activeProvider();
2511
+ var numCtx = ap ? Number(ap.numCtx) : 0;
2512
+ var hasNumCtx = isFinite(numCtx) && numCtx > 0;
2425
2513
 
2426
2514
  var parts = [];
2427
2515
  if (contextTokens) { parts.push("context ~" + contextTokens.toLocaleString()); }
@@ -2432,9 +2520,15 @@
2432
2520
  }
2433
2521
  var sizeText = "~" + tokens.toLocaleString() + " tokens" +
2434
2522
  (parts.length ? " (" + parts.join(", ") + ")" : "");
2523
+ if (hasNumCtx) {
2524
+ sizeText += " — " + Math.round((tokens / numCtx) * 100) + "% of " +
2525
+ (ap.providerName || "this provider") + "'s " + numCtx.toLocaleString() +
2526
+ " context window";
2527
+ }
2528
+ var nearProviderLimit = hasNumCtx && tokens >= numCtx * 0.8;
2435
2529
 
2436
2530
  $size.removeClass("fp-hidden fp-size-warn fp-size-high");
2437
- if (tokens >= highAt) {
2531
+ if (tokens >= highAt || nearProviderLimit) {
2438
2532
  $size.text(sizeText + " — large; may exceed smaller local models. " +
2439
2533
  "Consider selecting fewer nodes, clearing chat history, or splitting your request.")
2440
2534
  .addClass("fp-size-high");