@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/CHANGELOG.md +119 -13
- package/README.md +10 -1
- package/flowpilot-core.css +15 -0
- package/flowpilot-node-entry.js +15 -0
- package/flowpilot.js +296 -63
- package/lib/agent-contract.js +8 -3
- package/lib/core/history.js +151 -9
- package/lib/core/init.js +90 -21
- package/lib/core/main.js +120 -26
- package/lib/core/modes.js +237 -21
- package/lib/core/selection-context.js +17 -0
- package/lib/default-system-prompt.js +2 -2
- package/lib/document-system-prompt.js +4 -2
- package/lib/generation-system-prompt.js +2 -2
- package/lib/modify-system-prompt.js +1 -1
- package/lib/prompt-fragments.js +10 -6
- package/lib/provider-anthropic.js +12 -2
- package/lib/provider-openai-compatible.js +14 -2
- package/lib/provider-shape-check.js +1 -1
- package/lib/storage.js +6 -0
- package/package.json +3 -2
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
|
|
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
|
-
|
|
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" :
|
|
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
|
|
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
|
-
//
|
|
1320
|
-
//
|
|
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-
|
|
1389
|
+
el("#fp-dev-warning-status").addClass("fp-hidden");
|
|
1323
1390
|
} else {
|
|
1324
|
-
el("#fp-dev-
|
|
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
|
-
//
|
|
1372
|
-
//
|
|
1373
|
-
|
|
1374
|
-
var
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
//
|
|
1378
|
-
//
|
|
1379
|
-
//
|
|
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:
|
|
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
|
|
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 =
|
|
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");
|