@manny-est/node-red-flowpilot 0.5.1 → 0.6.0-beta.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 +58 -0
- package/README.md +21 -7
- package/USER-GUIDE.md +27 -14
- package/flowpilot-core.css +159 -5
- package/flowpilot.js +1353 -172
- package/lib/agent-contract.js +45 -0
- package/lib/build-core-script.js +1 -0
- package/lib/build-system-prompt.js +26 -4
- package/lib/chat-data.js +106 -0
- package/lib/core/apply-review.js +268 -64
- package/lib/core/graph-truth.js +63 -0
- package/lib/core/history.js +10 -1
- package/lib/core/init.js +148 -5
- package/lib/core/main.js +755 -21
- package/lib/core/modes.js +1523 -67
- package/lib/core/selection-context.js +31 -1
- package/lib/default-system-prompt.js +17 -12
- package/lib/document-system-prompt.js +22 -32
- package/lib/envelope.js +13 -7
- package/lib/generation-system-prompt.js +29 -44
- package/lib/modify-system-prompt.js +152 -72
- package/lib/persona-prompt.js +81 -54
- package/lib/prompt-fragments.js +56 -0
- package/lib/provider-anthropic.js +388 -0
- package/lib/provider-openai-compatible.js +49 -12
- package/lib/provider-shape-check.js +34 -0
- package/lib/storage.js +160 -12
- package/lib/validator.js +238 -0
- package/package.json +1 -1
package/lib/core/init.js
CHANGED
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"- `/build` — describe a goal; I'll plan, propose, and walk an iterative build → deploy → debug → review → fix loop with you\n" +
|
|
25
25
|
"- `/compact` — hide labels on the selected node(s) (icon-only); `/expand` restores them. Instant, no AI involved — one Ctrl+Z undoes it.\n" +
|
|
26
26
|
"- `/disable` — disable the selected node(s) (skipped on Deploy); `/enable` re-enables them. Instant, no AI involved — one Ctrl+Z undoes it.\n" +
|
|
27
|
-
"- `/refresh` — re-render all messages from the in-memory record store (restores interactive Apply buttons if they were lost).\n
|
|
27
|
+
"- `/refresh` — re-render all messages from the in-memory record store (restores interactive Apply buttons if they were lost).\n" +
|
|
28
|
+
"- `/debug` — toggle debug logging (full prompts/replies/decisions to `flowpilot/debug.log`) on/off. Instant, no AI involved.\n\n" +
|
|
28
29
|
"### Also worth knowing\n\n" +
|
|
29
30
|
"- Action chips (paper-plane buttons) offer a one-click follow-up — review and send, nothing fires automatically.\n" +
|
|
30
31
|
"- When I ask a clarifying question, I'll often offer quick-reply buttons (plus \"Other\" for your own answer) — clicking one sends it right away.\n" +
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
{ cmd: "/disable", desc: "Disable selected nodes" },
|
|
65
66
|
{ cmd: "/enable", desc: "Enable selected nodes" },
|
|
66
67
|
{ cmd: "/refresh", desc: "Re-render all messages from shadow record store" },
|
|
68
|
+
{ cmd: "/debug", desc: "Toggle debug logging on/off" },
|
|
67
69
|
{ cmd: "/demo", desc: "Type in a demo prompt" },
|
|
68
70
|
{ cmd: "/help", desc: "Show all available commands" },
|
|
69
71
|
{ cmd: "/feedback", desc: "Show feedback info" }
|
|
@@ -237,6 +239,17 @@
|
|
|
237
239
|
refreshView();
|
|
238
240
|
if ($promptBox.length) { $promptBox.val(""); }
|
|
239
241
|
break;
|
|
242
|
+
// Deterministic, no AI round-trip: flips the same
|
|
243
|
+
// settings.debugLogging field the Hangar checkbox binds to, via
|
|
244
|
+
// the normal saveSettings() round trip, so the two never drift.
|
|
245
|
+
case "/debug":
|
|
246
|
+
var newDebugState = !el("#fp-debug-logging").prop("checked");
|
|
247
|
+
el("#fp-debug-logging").prop("checked", newDebugState);
|
|
248
|
+
saveSettings(function () {
|
|
249
|
+
addMessage("assistant", "Debug logging is now " + (newDebugState ? "ON" : "OFF") + ".");
|
|
250
|
+
});
|
|
251
|
+
if ($promptBox.length) { $promptBox.val(""); }
|
|
252
|
+
break;
|
|
240
253
|
// Deterministic, no LLM round-trip: just invokes Node-RED's own
|
|
241
254
|
// native "show/hide selected node labels" action (RED.actions
|
|
242
255
|
// "core:show-selected-node-labels" / "core:hide-selected-node-
|
|
@@ -567,7 +580,7 @@
|
|
|
567
580
|
$panel.data("fp-review-apply-bound", true);
|
|
568
581
|
var recordId = parseInt($panel.attr("data-fp-record-id"), 10);
|
|
569
582
|
if (isNaN(recordId)) { return; }
|
|
570
|
-
$panel.find(".fp-review-actions button.
|
|
583
|
+
$panel.find(".fp-review-actions button.fp-chip.fp-chip-card:not(:disabled)").on("click", function () {
|
|
571
584
|
var $btn = $(this);
|
|
572
585
|
if ($btn.prop("disabled")) { return; }
|
|
573
586
|
$btn.prop("disabled", true).text("Applying…");
|
|
@@ -579,6 +592,50 @@
|
|
|
579
592
|
});
|
|
580
593
|
}
|
|
581
594
|
|
|
595
|
+
// Interactive question/consent buttons are cloned into the pop-out as
|
|
596
|
+
// HTML, so their main-window click closures do not survive. Relay the
|
|
597
|
+
// record id plus the selected action back to the parent, where the live
|
|
598
|
+
// record still owns the continuation callback/state.
|
|
599
|
+
function bindRecordActionButtons($scope) {
|
|
600
|
+
$scope.filter("[data-fp-record-id]").add($scope.find("[data-fp-record-id]"))
|
|
601
|
+
.filter("button").each(function () {
|
|
602
|
+
var $btn = $(this);
|
|
603
|
+
if ($btn.data("fp-record-action-bound") || $btn.prop("disabled")) { return; }
|
|
604
|
+
var recordId = parseInt($btn.attr("data-fp-record-id"), 10);
|
|
605
|
+
var action = $btn.attr("data-fp-record-action");
|
|
606
|
+
if (isNaN(recordId) || !action) { return; }
|
|
607
|
+
$btn.data("fp-record-action-bound", true).on("click", function () {
|
|
608
|
+
if (action === "show-other") {
|
|
609
|
+
$btn.closest(".fp-question-row").next(".fp-question-other-row")
|
|
610
|
+
.removeClass("fp-hidden").find("input").focus();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
var relayAction = action;
|
|
614
|
+
var value = $btn.attr("data-fp-record-value") || "";
|
|
615
|
+
if (action === "answer-other") {
|
|
616
|
+
value = $btn.siblings("input").val().trim();
|
|
617
|
+
if (!value) { return; }
|
|
618
|
+
relayAction = "answer";
|
|
619
|
+
}
|
|
620
|
+
var $questionRow = $btn.closest(".fp-question-row");
|
|
621
|
+
var $otherRow = $btn.closest(".fp-question-other-row");
|
|
622
|
+
$questionRow.find("button, input").prop("disabled", true);
|
|
623
|
+
$questionRow.next(".fp-question-other-row").find("button, input").prop("disabled", true);
|
|
624
|
+
$otherRow.find("button, input").prop("disabled", true);
|
|
625
|
+
$otherRow.prev(".fp-question-row").find("button, input").prop("disabled", true);
|
|
626
|
+
if (!window.opener || window.opener.closed) { return; }
|
|
627
|
+
try {
|
|
628
|
+
window.opener.postMessage({
|
|
629
|
+
event: "resolveRecordAction",
|
|
630
|
+
recordId: recordId,
|
|
631
|
+
action: relayAction,
|
|
632
|
+
value: value
|
|
633
|
+
}, location.origin);
|
|
634
|
+
} catch (e) { /* ignore */ }
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
582
639
|
// The loop stepper's "Stop build loop" button is relayed the same
|
|
583
640
|
// generic way as any other chat message (renderLoopStepper appends/
|
|
584
641
|
// replaces a #fp-loop-stepper element, which the MutationObserver
|
|
@@ -830,6 +887,7 @@
|
|
|
830
887
|
if (data.event === "initialSync") {
|
|
831
888
|
el("#fp-messages").html(data.html);
|
|
832
889
|
bindReviewApplyButtons(el("#fp-messages"));
|
|
890
|
+
bindRecordActionButtons(el("#fp-messages"));
|
|
833
891
|
bindStopLoopButton(el("#fp-messages"));
|
|
834
892
|
bindTabSwitching(el("#fp-messages"));
|
|
835
893
|
bindDebugAttachButtons(el("#fp-messages"));
|
|
@@ -837,6 +895,7 @@
|
|
|
837
895
|
} else if (data.event === "appendMessage") {
|
|
838
896
|
el("#fp-messages").append(data.html);
|
|
839
897
|
bindReviewApplyButtons(el("#fp-messages").children().last());
|
|
898
|
+
bindRecordActionButtons(el("#fp-messages").children().last());
|
|
840
899
|
bindStopLoopButton(el("#fp-messages").children().last());
|
|
841
900
|
bindTabSwitching(el("#fp-messages").children().last());
|
|
842
901
|
bindDebugAttachButtons(el("#fp-messages").children().last());
|
|
@@ -1000,8 +1059,14 @@
|
|
|
1000
1059
|
' </div>' +
|
|
1001
1060
|
' <label>Provider Name</label>' +
|
|
1002
1061
|
' <input id="fp-provider-name" type="text" placeholder="LocalAI">' +
|
|
1062
|
+
' <label>Provider type</label>' +
|
|
1063
|
+
' <select id="fp-provider-type">' +
|
|
1064
|
+
' <option value="openai-compatible">OpenAI-compatible (LocalAI, Ollama, SGLang, OpenAI, etc.)</option>' +
|
|
1065
|
+
' <option value="anthropic">Anthropic</option>' +
|
|
1066
|
+
' </select>' +
|
|
1003
1067
|
' <label>Base URL</label>' +
|
|
1004
1068
|
' <input id="fp-base-url" type="text" placeholder="http://localhost:8080">' +
|
|
1069
|
+
' <div id="fp-base-url-hint" class="fp-consent-hint fp-hidden">Leave blank to use api.anthropic.com.</div>' +
|
|
1005
1070
|
' <label>API Key</label>' +
|
|
1006
1071
|
' <input id="fp-api-key" type="password" placeholder="Optional">' +
|
|
1007
1072
|
' <label>Model</label>' +
|
|
@@ -1028,8 +1093,8 @@
|
|
|
1028
1093
|
' </div>' +
|
|
1029
1094
|
|
|
1030
1095
|
' <div class="fp-settings-section">Personality</div>' +
|
|
1031
|
-
' <label>Persona intensity: <span id="fp-persona-value">
|
|
1032
|
-
' <input id="fp-persona-intensity" type="range" min="1" max="
|
|
1096
|
+
' <label>Persona intensity: <span id="fp-persona-value">2</span>/5</label>' +
|
|
1097
|
+
' <input id="fp-persona-intensity" type="range" min="1" max="5" step="1">' +
|
|
1033
1098
|
' <div id="fp-persona-label" class="fp-consent-hint"></div>' +
|
|
1034
1099
|
' <div class="fp-consent-hint">Chat only. Scales the AI\'s voice at greetings, ' +
|
|
1035
1100
|
' capability questions, and brief transitions — 1 is a plain Node-RED engineer, ' +
|
|
@@ -1056,10 +1121,14 @@
|
|
|
1056
1121
|
|
|
1057
1122
|
' <div class="fp-settings-section">Request timeout</div>' +
|
|
1058
1123
|
' <label>Give up after (seconds)</label>' +
|
|
1059
|
-
' <input id="fp-request-timeout" type="number"
|
|
1124
|
+
' <input id="fp-request-timeout" type="number" step="any" placeholder="180">' +
|
|
1060
1125
|
' <div class="fp-consent-hint">How long to wait for a provider response before ' +
|
|
1061
1126
|
' giving up. Raise this if you\'re running a large local model on slow hardware ' +
|
|
1062
1127
|
' (e.g. Ollama without a GPU) and seeing timeout errors.</div>' +
|
|
1128
|
+
' <label>Max tokens per agent turn</label>' +
|
|
1129
|
+
' <input id="fp-agent-turn-max-tokens" type="number" min="1" max="65536" step="1" placeholder="4096">' +
|
|
1130
|
+
' <div class="fp-consent-hint">Hard output limit for each tool-calling agent step. ' +
|
|
1131
|
+
' Classic (no-tools) requests are not capped.</div>' +
|
|
1063
1132
|
|
|
1064
1133
|
' <div class="fp-settings-section">Agentic build loop</div>' +
|
|
1065
1134
|
' <label>Max build/fix attempts</label>' +
|
|
@@ -1119,6 +1188,15 @@
|
|
|
1119
1188
|
' sent either way. To disable, check the box and type ' +
|
|
1120
1189
|
' <strong>disable redaction</strong> below.</div>' +
|
|
1121
1190
|
' <input id="fp-redaction-confirm" type="text" placeholder="Type: disable redaction">' +
|
|
1191
|
+
|
|
1192
|
+
' <div class="fp-settings-section">Debug mode</div>' +
|
|
1193
|
+
' <label class="fp-checkbox-row">' +
|
|
1194
|
+
' <input id="fp-debug-logging" type="checkbox"> ' +
|
|
1195
|
+
' Debug mode' +
|
|
1196
|
+
' </label>' +
|
|
1197
|
+
' <div class="fp-consent-hint">Logs full prompts, replies, and decisions to ' +
|
|
1198
|
+
' <code>flowpilot/debug.log</code> for troubleshooting. Off by default — ' +
|
|
1199
|
+
' this file can get large.</div>' +
|
|
1122
1200
|
' </details>' +
|
|
1123
1201
|
|
|
1124
1202
|
' <div class="fp-settings-actions">' +
|
|
@@ -1173,6 +1251,7 @@
|
|
|
1173
1251
|
// Nothing is sent to the backend until the user explicitly
|
|
1174
1252
|
// attaches a message and sends a request.
|
|
1175
1253
|
try { RED.comms.subscribe("debug", onDebugMessage); } catch (e) { /* comms unavailable */ }
|
|
1254
|
+
try { RED.comms.subscribe("status/#", onNodeStatus); } catch (e) { /* comms unavailable */ }
|
|
1176
1255
|
|
|
1177
1256
|
// Track whether the user is scrolled to the bottom of the chat,
|
|
1178
1257
|
// so "Cruising…"/streaming updates only auto-follow when they
|
|
@@ -1216,6 +1295,9 @@
|
|
|
1216
1295
|
content.find("#fp-provider-select").on("change", function () {
|
|
1217
1296
|
switchProvider($(this).val());
|
|
1218
1297
|
});
|
|
1298
|
+
content.find("#fp-provider-type").on("change", function () {
|
|
1299
|
+
toggleAnthropicHint($(this).val());
|
|
1300
|
+
});
|
|
1219
1301
|
content.find("#fp-add-provider").on("click", function () { addProvider(); });
|
|
1220
1302
|
content.find("#fp-remove-provider").on("click", function () { removeProvider(); });
|
|
1221
1303
|
content.find("#fp-test-provider").on("click", function () { testProvider(); });
|
|
@@ -1277,6 +1359,13 @@
|
|
|
1277
1359
|
RED.events.on("deploy", function () {
|
|
1278
1360
|
if (activeBuildLoop && activeBuildLoop.waypoint === "apply") {
|
|
1279
1361
|
activeBuildLoop.waypoint = "attach";
|
|
1362
|
+
// CLAUDE-025: marks the start of THIS attempt's own
|
|
1363
|
+
// evidence window — see freshBuildLoopEvidence. Anything
|
|
1364
|
+
// that arrived before this (a prior attempt's debug
|
|
1365
|
+
// output, a manual attach, an unrelated still-running
|
|
1366
|
+
// flow) is stale and must not count toward this
|
|
1367
|
+
// attempt's own goal.
|
|
1368
|
+
activeBuildLoop.deployedAt = Date.now();
|
|
1280
1369
|
renderLoopStepper(activeBuildLoop);
|
|
1281
1370
|
// Start a timer so flows with no debug nodes (e.g. HTTP
|
|
1282
1371
|
// endpoints) don't leave the loop stuck silently waiting.
|
|
@@ -1358,6 +1447,36 @@
|
|
|
1358
1447
|
}
|
|
1359
1448
|
rec.state = "applied";
|
|
1360
1449
|
}
|
|
1450
|
+
} else if (data.event === "resolveRecordAction" && typeof data.recordId === "number") {
|
|
1451
|
+
var actionRec = null;
|
|
1452
|
+
for (var ai = 0; ai < messageRecords.length; ai++) {
|
|
1453
|
+
if (messageRecords[ai].id === data.recordId) { actionRec = messageRecords[ai]; break; }
|
|
1454
|
+
}
|
|
1455
|
+
if (actionRec && actionRec.kind === "question" && !actionRec.decision) {
|
|
1456
|
+
if (actionRec.buildConsentGate && (data.action === "proceed" || data.action === "skip")) {
|
|
1457
|
+
actionRec.decision = data.action;
|
|
1458
|
+
runBuildConsentDecision(actionRec, data.action === "proceed");
|
|
1459
|
+
} else if (actionRec.agentToolConsent && (data.action === "proceed" || data.action === "skip")) {
|
|
1460
|
+
actionRec.decision = data.action;
|
|
1461
|
+
if (typeof actionRec.onResume === "function") { actionRec.onResume(data.action === "proceed"); }
|
|
1462
|
+
} else if (actionRec.askUserTool && data.action === "answer") {
|
|
1463
|
+
actionRec.decision = "answered";
|
|
1464
|
+
actionRec.answerText = String(data.value || "");
|
|
1465
|
+
if (typeof actionRec.onAnswer === "function") { actionRec.onAnswer(actionRec.answerText); }
|
|
1466
|
+
} else if (actionRec.loopCheckpoint && (data.action === "continue" || data.action === "stop")) {
|
|
1467
|
+
actionRec.decision = data.action;
|
|
1468
|
+
if (typeof actionRec.onResume === "function") { actionRec.onResume(data.action); }
|
|
1469
|
+
}
|
|
1470
|
+
} else if (actionRec && actionRec.kind === "chip" && actionRec.chipType === "suggestedAction"
|
|
1471
|
+
&& data.action === "apply-suggested-action") {
|
|
1472
|
+
// CLAUDE-028: the redirect chip's own click handler (bound
|
|
1473
|
+
// directly on the button at render time) doesn't survive the
|
|
1474
|
+
// pop-out's innerHTML clone. Rides the same rebind-by-record-id
|
|
1475
|
+
// relay renderAskUserQuestion's quick-reply buttons already use
|
|
1476
|
+
// (bindRecordActionButtons in this file) — no chip-specific wiring
|
|
1477
|
+
// needed on the pop-out side.
|
|
1478
|
+
applySuggestedAction(actionRec.suggestedAction);
|
|
1479
|
+
}
|
|
1361
1480
|
} else if (data.event === "stopBuildLoop") {
|
|
1362
1481
|
stopBuildLoop("Build loop stopped — applied nodes remain as-is.");
|
|
1363
1482
|
} else if (data.event === "clearChat") {
|
|
@@ -1430,6 +1549,30 @@
|
|
|
1430
1549
|
loadSettings();
|
|
1431
1550
|
updateSelectionStatus();
|
|
1432
1551
|
showChat();
|
|
1552
|
+
rehydrateConversationOnLoad();
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// CLAUDE-029: on a page reload, sessionStorage already retains the
|
|
1556
|
+
// conversation-id (see history.js) and the backend already has the full
|
|
1557
|
+
// transcript (History panel proves it's retrievable) — but nothing ever
|
|
1558
|
+
// fetched and rendered it into the Chat panel on init, so a reload left
|
|
1559
|
+
// Chat looking empty even though the conversation was never lost.
|
|
1560
|
+
// Reuses loadConversation() verbatim (same fetch + same addMessage()
|
|
1561
|
+
// render path the History panel's click handler uses) so there is no
|
|
1562
|
+
// second copy of the message-rendering logic to drift out of sync.
|
|
1563
|
+
function rehydrateConversationOnLoad() {
|
|
1564
|
+
if (!conversationIdWasRestored) { return; }
|
|
1565
|
+
loadConversation(conversationId, function (msg, xhr) {
|
|
1566
|
+
// Conversation no longer exists server-side (404) — the
|
|
1567
|
+
// sessionStorage entry is stale, so drop it rather than retry
|
|
1568
|
+
// this same failed fetch on every future reload. Any other
|
|
1569
|
+
// failure (network hiccup, etc.) leaves it in place in case it
|
|
1570
|
+
// was transient. Either way: stay quiet, Chat simply stays
|
|
1571
|
+
// empty exactly as it does today — no error bubble on load.
|
|
1572
|
+
if (xhr && xhr.status === 404) {
|
|
1573
|
+
try { sessionStorage.removeItem("fp-conversation-id"); } catch (e) { /* storage unavailable */ }
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1433
1576
|
}
|
|
1434
1577
|
|
|
1435
1578
|
window.FlowPilotCore = { initMainWindow: initMainWindow, initPopout: initPopout };
|